/* * Copyright (c) 2012 Hai Bison * * See the file LICENSE at the root directory of this project for copying * permission. */ package group.pals.android.lib.ui.filechooser; import group.pals.android.lib.ui.filechooser.io.IFile; import group.pals.android.lib.ui.filechooser.io.IFileFilter; import group.pals.android.lib.ui.filechooser.prefs.DisplayPrefs; import group.pals.android.lib.ui.filechooser.prefs.DisplayPrefs.FileTimeDisplay; import group.pals.android.lib.ui.filechooser.services.IFileProvider; import group.pals.android.lib.ui.filechooser.services.IFileProvider.FilterMode; import group.pals.android.lib.ui.filechooser.utils.Converter; import group.pals.android.lib.ui.filechooser.utils.DateUtils; import group.pals.android.lib.ui.filechooser.utils.FileUtils; import group.pals.android.lib.ui.filechooser.utils.ui.ContextMenuUtils; import group.pals.android.lib.ui.filechooser.utils.ui.LoadingDialog; import java.util.ArrayList; import java.util.Collection; import java.util.List; import android.content.Context; import android.graphics.Paint; import android.preference.PreferenceActivity; import android.preference.PreferenceFragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.GridView; import android.widget.ImageView; import android.widget.TextView; /** * The adapter to be used in {@link android.widget.ListView} * * @author Hai Bison * */ public class IFileAdapter extends BaseAdapter { /** * Used for logging... */ public static final String _ClassName = IFileAdapter.class.getName(); private final Integer[] mAdvancedSelectionOptions; private final IFileProvider.FilterMode mFilterMode; private final Context mContext; private final FileTimeDisplay mFileTimeDisplay; private List<IFileDataModel> mData; private LayoutInflater mInflater; private boolean mMultiSelection; /** * Creates new {@link IFileAdapter} * * @param context * {@link Context} * @param objects * the data * @param filterMode * see {@link IFileProvider.FilterMode} * @param multiSelection * see {@link FileChooserActivity#_MultiSelection} */ public IFileAdapter(Context context, List<IFileDataModel> objects, IFileProvider.FilterMode filterMode, boolean multiSelection) { // DO NOT use getApplicationContext(), due to this bug: // http://stackoverflow.com/questions/2634991/android-1-6-android-view-windowmanagerbadtokenexception-unable-to-add-window // http://code.google.com/p/android/issues/detail?id=11199 mContext = context; mData = objects; mInflater = LayoutInflater.from(mContext); mFilterMode = filterMode; mMultiSelection = multiSelection; switch (mFilterMode) { case DirectoriesOnly: case FilesOnly: mAdvancedSelectionOptions = new Integer[] { R.string.afc_cmd_advanced_selection_all, R.string.afc_cmd_advanced_selection_none, R.string.afc_cmd_advanced_selection_invert }; break;// DirectoriesOnly and FilesOnly default: mAdvancedSelectionOptions = new Integer[] { R.string.afc_cmd_advanced_selection_all, R.string.afc_cmd_advanced_selection_none, R.string.afc_cmd_advanced_selection_invert, R.string.afc_cmd_select_all_files, R.string.afc_cmd_select_all_folders }; break;// FilesAndDirectories } mFileTimeDisplay = new FileTimeDisplay(DisplayPrefs.isShowTimeForOldDaysThisYear(mContext), DisplayPrefs.isShowTimeForOldDays(mContext)); }// IFileAdapter @Override public void notifyDataSetChanged() { updateEnvironments(); super.notifyDataSetChanged(); }// notifyDataSetChanged() /** * Updates environments such as file time display... For example, this * method is useful if you have {@link PreferenceActivity} or * {@link PreferenceFragment} to let the user change preferences. So after * the user changed preferences, you just simply call this method <i>and</i> * {@link #notifyDataSetChanged()}, or just call * {@link #notifyDataSetChanged()} (which also calls this method) to update * the UI of this adapter. */ public void updateEnvironments() { mFileTimeDisplay.setShowTimeForOldDaysThisYear(DisplayPrefs.isShowTimeForOldDaysThisYear(mContext)); mFileTimeDisplay.setShowTimeForOldDays(DisplayPrefs.isShowTimeForOldDays(mContext)); }// updateEnvironments() @Override public int getCount() { return mData != null ? mData.size() : 0; } @Override public IFileDataModel getItem(int position) { return mData != null ? mData.get(position) : null; } @Override public long getItemId(int position) { return position; } public boolean isMultiSelection() { return mMultiSelection; } /** * Sets multi-selection mode.<br> * <b>Note:</b><br> * * <li>If {@code v = true}, this method will also update adapter.</li> * * <li>If {@code v = false}, this method will iterate all items, set their * selection to {@code false}. So you should consider using a * {@link LoadingDialog}. This will not update adapter, you must do it * yourself.</li> * * @param v * {@code true} if multi-selection is enabled */ public void setMultiSelection(boolean v) { if (mMultiSelection != v) { mMultiSelection = v; if (mMultiSelection) { notifyDataSetChanged(); } else { if (getCount() > 0) { for (int i = 0; i < mData.size(); i++) mData.get(i).setSelected(false); } } } }// setMultiSelection() /** * Gets selected items. * * @return list of selected items, can be empty but never be {@code null} */ public ArrayList<IFileDataModel> getSelectedItems() { ArrayList<IFileDataModel> res = new ArrayList<IFileDataModel>(); for (int i = 0; i < getCount(); i++) if (getItem(i).isSelected()) res.add(getItem(i)); return res; }// getSelectedItems() /** * Adds an {@code item}. <b>Note:</b> this does not notify the adapter that * data set has been changed. * * @param item * {@link IFileDataModel} */ public void add(IFileDataModel item) { if (mData != null) mData.add(item); } /** * Removes {@code item}. <b>Note:</b> this does not notify the adapter that * data set has been changed. * * @param item * {@link IFileDataModel} */ public void remove(IFileDataModel item) { if (mData != null) { mData.remove(item); } }// remove() /** * Removes all {@code items}. <b>Note:</b> this does not notify the adapter * that data set has been changed. * * @param items * the items you want to remove. */ public void removeAll(Collection<IFileDataModel> items) { if (mData != null) mData.removeAll(items); }// removeAll() /** * Clears all items.<br> * <b>Note:</b><br> * <li>This does not notify the adapter that data set has been changed.</li> */ public void clear() { if (mData != null) mData.clear(); }// clear() /** * The "view holder" * * @author Hai Bison * */ private static final class Bag { ImageView mImageIcon; TextView mTxtFileName; TextView mTxtFileInfo; CheckBox mCheckboxSelection; } @Override public View getView(int position, View convertView, ViewGroup parent) { IFileDataModel data = getItem(position); Bag bag; if (convertView == null) { convertView = mInflater.inflate(R.layout.afc_file_item, null); bag = new Bag(); bag.mImageIcon = (ImageView) convertView.findViewById(R.id.afc_file_item_imageview_icon); bag.mTxtFileName = (TextView) convertView.findViewById(R.id.afc_file_item_textview_filename); bag.mTxtFileInfo = (TextView) convertView.findViewById(R.id.afc_file_item_textview_file_info); bag.mCheckboxSelection = (CheckBox) convertView.findViewById(R.id.afc_file_item_checkbox_selection); convertView.setTag(bag); } else { bag = (Bag) convertView.getTag(); } // update view updateView(parent, convertView, bag, data, data.getFile()); return convertView; } /** * Updates the view. * * @param parent * the parent view * @param childView * the child view. * @param bag * the "view holder", see {@link Bag} * @param data * {@link IFileDataModel} * @param file * {@link IFile} * @since v2.0 alpha */ private void updateView(ViewGroup parent, View childView, Bag bag, final IFileDataModel data, IFile file) { /* * Use single line for grid view, multiline for list view */ bag.mTxtFileName.setSingleLine(parent instanceof GridView); // file icon bag.mImageIcon.setImageResource(FileUtils.getResIcon(file)); // filename bag.mTxtFileName.setText(file.getName()); // check if this file has been marked as to be deleted or not if (data.isTobeDeleted()) bag.mTxtFileName.setPaintFlags(bag.mTxtFileName.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG); else bag.mTxtFileName.setPaintFlags(bag.mTxtFileName.getPaintFlags() & ~Paint.STRIKE_THRU_TEXT_FLAG); // file info String time = DateUtils.formatDate(mContext, file.lastModified(), mFileTimeDisplay); if (file.isDirectory()) bag.mTxtFileInfo.setText(time); else bag.mTxtFileInfo.setText(String.format("%s, %s", Converter.sizeToStr(file.length()), time)); // checkbox if (mMultiSelection) { if (FilterMode.FilesOnly.equals(mFilterMode) && file.isDirectory()) { bag.mCheckboxSelection.setVisibility(View.GONE); } else { bag.mCheckboxSelection.setVisibility(View.VISIBLE); bag.mCheckboxSelection.setFocusable(false); bag.mCheckboxSelection.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { data.setSelected(isChecked); } }); bag.mCheckboxSelection.setOnLongClickListener(mCheckboxSelectionOnLongClickListener); bag.mCheckboxSelection.setChecked(data.isSelected()); } } else bag.mCheckboxSelection.setVisibility(View.GONE); }// updateView // ========= // UTILITIES /** * Selects all items. * * @param notifyDataSetChanged * {@code true} if you want to notify that data set changed * @param filter * {@link IFileFilter} */ public void selectAll(boolean notifyDataSetChanged, IFileFilter filter) { for (int i = 0; i < getCount(); i++) { IFileDataModel item = getItem(i); item.setSelected(filter == null ? true : filter.accept(item.getFile())); } if (notifyDataSetChanged) notifyDataSetChanged(); }// selectAll() /** * Selects no items. * * @param notifyDataSetChanged * {@code true} if you want to notify that data set changed */ public void selectNone(boolean notifyDataSetChanged) { for (int i = 0; i < getCount(); i++) getItem(i).setSelected(false); if (notifyDataSetChanged) notifyDataSetChanged(); }// selectNone() /** * Inverts selection. * * @param notifyDataSetChanged * {@code true} if you want to notify that data set changed */ public void invertSelection(boolean notifyDataSetChanged) { for (int i = 0; i < getCount(); i++) { IFileDataModel item = getItem(i); item.setSelected(!item.isSelected()); } if (notifyDataSetChanged) notifyDataSetChanged(); }// invertSelection() // ========= // LISTENERS private final View.OnLongClickListener mCheckboxSelectionOnLongClickListener = new View.OnLongClickListener() { @Override public boolean onLongClick(final View view) { ContextMenuUtils.showContextMenu(view.getContext(), 0, R.string.afc_title_advanced_selection, mAdvancedSelectionOptions, new ContextMenuUtils.OnMenuItemClickListener() { @Override public void onClick(final int resId) { new LoadingDialog(view.getContext(), R.string.afc_msg_loading, false) { @Override protected Object doInBackground(Void... arg0) { if (resId == R.string.afc_cmd_advanced_selection_all) { selectAll(false, null); } else if (resId == R.string.afc_cmd_advanced_selection_none) { selectNone(false); } else if (resId == R.string.afc_cmd_advanced_selection_invert) { invertSelection(false); } else if (resId == R.string.afc_cmd_select_all_files) { selectAll(false, new IFileFilter() { @Override public boolean accept(IFile pathname) { return pathname.isFile(); } }); } else if (resId == R.string.afc_cmd_select_all_folders) { selectAll(false, new IFileFilter() { @Override public boolean accept(IFile pathname) { return pathname.isDirectory(); } }); } return null; }// doInBackground() @Override protected void onPostExecute(Object result) { super.onPostExecute(result); notifyDataSetChanged(); }// onPostExecute() }.execute();// LoadingDialog }// onClick() }); return true; }// onLongClick() };// mCheckboxSelectionOnLongClickListener }