package de.fun2code.android.piratebox.dialog.directory; import java.io.File; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import android.app.AlertDialog; import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; import android.os.Handler; import android.view.View; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.Button; import android.widget.EditText; import android.widget.ListView; import android.widget.TextView; import de.fun2code.android.piratebox.R; import de.fun2code.android.piratebox.util.DialogUtil; /** * Directory chooser dialog * * @author joschi * */ public class DirectoryDialog extends Dialog implements OnItemClickListener, DialogInterface.OnCancelListener { private DirectoryDialog dialog; private List<File> fileList; private List<NavBackStackInfo> navBackStack; private int navDirection; private DirectoryAdapter adapter; private Handler handler; private ListView listView; private Button buttonCancel; private Button buttonOk; private TextView currentDirText; DirectorySelectListener directorySelectListener; private String currentDir; private String topDir = "/"; private boolean showOnlyWritableDirs = false; private Context context; private boolean showCreateButton = true; private int NAV_NEUTRAL = 0; private int NAV_UP = 1; private int NAV_DOWN = 2; /** * Constructs a directory chooser dialog * * @param context {@code Context} to use * @param showCreateButton show create directory button */ public DirectoryDialog(Context context, boolean showCreateButton) { super(context); this.context = context; this.showCreateButton = showCreateButton; init(); } /** * Constructs a directory chooser dialog * * @param context Constructs a directory chooser dialog * @param theme theme to use * @param showCreateButton show create directory button */ public DirectoryDialog(Context context, int theme, boolean showCreateButton) { super(context, theme); this.context = context; this.showCreateButton = showCreateButton; init(); } /** * Initializes the layout */ private void init() { handler = new Handler(); navBackStack = new ArrayList<NavBackStackInfo>(); navDirection = NAV_NEUTRAL; setContentView(R.layout.dialog_directory); dialog = this; handler = new Handler(); currentDirText = (TextView) findViewById(R.id.currentDirectory); currentDirText.setText(currentDir == null ? "" : currentDir); buttonCancel = (Button) findViewById(R.id.directory_dialog_cancel); buttonOk = (Button) findViewById(R.id.directory_dialog_ok); buttonCancel.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { if (directorySelectListener != null) { directorySelectListener.onDirectorySelected(null); } dismiss(); } }); Button buttonCreateDirectory = (Button) findViewById(R.id.createDirectory); buttonCreateDirectory.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { createDirectory(); } }); buttonCreateDirectory.setVisibility(showCreateButton ? View.VISIBLE : View.GONE); buttonOk.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { if (directorySelectListener != null) { directorySelectListener.onDirectorySelected(currentDir); dismiss(); } } }); setOnCancelListener(this); if (fileList == null) { fileList = new ArrayList<File>(); adapter = new DirectoryAdapter(context, fileList); refresh(); } listView = (ListView) findViewById(R.id.directory_list); listView.setAdapter(adapter); listView.setOnItemClickListener(this); } @Override public void onCancel(DialogInterface dialog) { if (directorySelectListener != null) { directorySelectListener.onDirectorySelected(null); } dismiss(); } @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { File f = fileList.get((int) id); if (f.getName().equals("..")) { currentDir = new File(currentDir).getParentFile().getAbsolutePath(); navDirection = NAV_UP; updateListInfo(); } else if (f.isDirectory()) { currentDir = f.getAbsolutePath(); navDirection = NAV_DOWN; pushNavBackStack(); updateListInfo(); } } /** * Notifies the adapter that data has changed */ private void notifyAdapter() { if (adapter == null) { return; } handler.post(new Runnable() { public void run() { adapter.notifyDataSetChanged(); if (navDirection == NAV_UP) { popNavBackStack(); } else { // Set list to top position listView.setSelectionFromTop(0, 0); } } }); } /** * Sets the {@code DirectorySelectListener} that is called when a * directory has been selected * * @param listener specifies the {@code DirectorySelectListener} */ public void setOnDirectorySelectListener(DirectorySelectListener listener) { directorySelectListener = listener; } /** * Set start directory * * @param dir directory to use */ public void setCurrentDirectory(String dir) { currentDir = dir; } /** * Returns the start directory * * @return name of the start directory */ public String getCurrentDirectory() { return currentDir; } /** * Sets the top directory, default is {@literal /} * * @param dir name of top directory */ public void setTopDirectory(String dir) { topDir = dir; } /** * Returns the top directory * * @return name of top directory */ public String getTopDirectory() { return topDir; } /** * Defines if only writable directories should be displayed * * @param state if {@code true} only writable directories are displayed, * otherwise all directories are shown */ public void showOnlyWritableDirs(boolean state) { showOnlyWritableDirs = state; } /** * Updates the title to display the currently selected directory */ private void updateInfoText() { handler.post(new Runnable() { public void run() { currentDirText.setText(currentDir); } }); } /** * Updates file and directory lists before the adapter is updated */ private void updateListInfo() { if (currentDir == null) { return; } List<File> dirs = new ArrayList<File>(); List<File> files = new ArrayList<File>(); File[] allFiles = new File(currentDir).listFiles(); if (allFiles == null) { return; } fileList.clear(); for (File f : allFiles) { if (f.isDirectory() && !f.getName().startsWith(".")) { // If showOnlyWritableDirs is true, show only writable directories if(!showOnlyWritableDirs || f.canWrite()) { dirs.add(f); } } else { files.add(f); } } orderFileNames(dirs); fileList.addAll(dirs); if (!currentDir.equals(topDir) && new File(currentDir).getParent() != null) { fileList.add(0, new File("..")); } notifyAdapter(); updateInfoText(); } /** * Refreshes the list information */ public void refresh() { handler.post(new Runnable() { @Override public void run() { updateListInfo(); } }); } /** * Sorts the file names * * @param list {@code File} list to sort */ private void orderFileNames(List<File> list) { Collections.sort(list, new Comparator<File>() { public int compare(File f1, File f2) { return f1.getName().toLowerCase() .compareTo(f2.getName().toLowerCase()); } }); } /** * Pushes the current scroll position to the navigation stack * * Taken from: * http://stackoverflow.com/questions/3014089/scroll-to-a-position * -in-a-listview */ private void pushNavBackStack() { int index = listView.getFirstVisiblePosition(); View v = listView.getChildAt(0); int top = (v == null) ? 0 : v.getTop(); NavBackStackInfo nbsi = new NavBackStackInfo(); nbsi.index = index; nbsi.top = top; navBackStack.add(0, nbsi); } /** * Pops an entry from the navigation stack and restores the scroll position */ private void popNavBackStack() { if (navBackStack.size() > 0) { NavBackStackInfo info = navBackStack.remove(0); listView.setSelectionFromTop(info.index, info.top); } } /** * Displays an input dialog * * @param title title to display * @param body message to show * @param input input to use * @param iconId icon to use, may be {@code null} * @param positiveListener listener for positive button * @param negativeListener listener for negative button */ private void showInputDialog(final CharSequence title, final CharSequence body, final EditText input, final int iconId, final DialogInterface.OnClickListener positiveListener, final DialogInterface.OnClickListener negativeListener) { handler.post(new Runnable() { public void run() { AlertDialog.Builder builder = new AlertDialog.Builder(context); builder.setTitle(title); builder.setIcon(iconId); builder.setMessage(body); builder.setView(input); builder.setPositiveButton(context.getText(android.R.string.ok), positiveListener); builder.setNegativeButton( context.getText(android.R.string.cancel), negativeListener); AlertDialog ad = builder.create(); ad.show(); } }); } /** * Shows the create directory dialog */ private void createDirectory() { final EditText dirName = new EditText(context); dirName.setSingleLine(true); dirName.setHint(R.string.dialog_label_directory_name); handler.post(new Runnable() { @Override public void run() { DialogInterface.OnClickListener positiveListener = new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { String newDir = currentDir + File.separator + dirName.getText(); if (new File(newDir).mkdir()) { currentDir = newDir; navDirection = NAV_DOWN; pushNavBackStack(); updateListInfo(); } } }; DialogInterface.OnClickListener negativeListener = new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // Do nothing } }; DialogUtil.showInputDialog(context, context.getText(R.string.dialog_title_create_directory), null, dirName, R.drawable.directory, positiveListener, negativeListener); } }); } /** * Navigation back stack class * * @author joschi * */ private class NavBackStackInfo { int index; int top; } }