org.eclipse.swt.examples.fileviewer.FileViewer.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.swt.examples.fileviewer.FileViewer.java

Source

/*******************************************************************************
 * Copyright (c) 2000, 2006 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.swt.examples.fileviewer;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.text.DateFormat;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.Vector;

import lrscp.lib.Common;
import lrscp.lib.Log;
import lrscp.lib.Preferences;
import lrscp.lib.oscmd.OsCmd;

import org.apache.commons.io.FileUtils;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.DragSource;
import org.eclipse.swt.dnd.DragSourceEvent;
import org.eclipse.swt.dnd.DragSourceListener;
import org.eclipse.swt.dnd.DropTarget;
import org.eclipse.swt.dnd.DropTargetAdapter;
import org.eclipse.swt.dnd.DropTargetEvent;
import org.eclipse.swt.dnd.FileTransfer;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.events.MenuAdapter;
import org.eclipse.swt.events.MenuEvent;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.events.ShellAdapter;
import org.eclipse.swt.events.ShellEvent;
import org.eclipse.swt.events.TreeAdapter;
import org.eclipse.swt.events.TreeEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.program.Program;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.MessageBox;
import org.eclipse.swt.widgets.ProgressBar;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.swt.widgets.ToolItem;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;

import com.android.ddmlib.AndroidDebugBridge;
import com.android.ddmlib.AndroidDebugBridge.IDeviceChangeListener;
import com.android.ddmlib.IDevice;

/**
 * File Viewer example
 */
public class FileViewer implements IDeviceChangeListener {
    private static ResourceBundle resourceBundle = ResourceBundle.getBundle("examples_fileviewer");
    static Preferences mPref = Preferences.instance("config");
    // Preference keys
    private static final String PREF_SHELL_LOCATION_X = "PREF_SHELL_LOCATION_X";
    private static final String PREF_SHELL_LOCATION_Y = "PREF_SHELL_LOCATION_Y";
    private static final String PREF_SHELL_MAXIUM = "PREF_SHELL_MAXIUM";
    private static final String PREF_SASH_TABLE_WEIGHT = "PREF_SASH_TABLE_WEIGHT";
    private static final String PREF_SASH_TREE_WEIGHT = "PREF_SASH_TREE_WEIGHT";

    /* UI elements */
    private Display display;
    private Shell shell;
    private ToolBar toolBar;

    private Label numObjectsLabel;
    private Label diskSpaceLabel;

    private File currentDirectory = null;
    private boolean initial = true;

    /* Drag and drop optimizations */
    private boolean isDragging = false; // if this app is dragging
    private boolean isDropping = false; // if this app is dropping

    private File[] processedDropFiles = null; // so Drag only deletes what it
                                              // needs to
    private File[] deferredRefreshFiles = null; // to defer notifyRefreshFiles
                                                // while we do DND
    private boolean deferredRefreshRequested = false; // to defer
                                                      // notifyRefreshFiles
                                                      // while we do DND
    private ProgressDialog progressDialog = null; // progress dialog for
                                                  // locally-initiated
                                                  // operations
                                                  // Save the current selection in table and restore the view after refresh.
    private File[] mSelectedFiles;
    private File[] mCurTableFiles;
    private int mCurTableTopIndex;

    /* Combo view */
    private static final String COMBODATA_ROOTS = "Combo.roots";
    // File[]: Array of files whose paths are currently displayed in the combo
    private static final String COMBODATA_LASTTEXT = "Combo.lastText";
    // String: Previous selection text string

    private Combo combo;

    /* Tree view */
    private IconCache iconCache = new IconCache();
    private static final String TREEITEMDATA_FILE = "TreeItem.file";
    // File: File associated with tree item
    private static final String TREEITEMDATA_IMAGEEXPANDED = "TreeItem.imageExpanded";
    // Image: shown when item is expanded
    private static final String TREEITEMDATA_IMAGECOLLAPSED = "TreeItem.imageCollapsed";
    // Image: shown when item is collapsed
    private static final String TREEITEMDATA_STUB = "TreeItem.stub";
    // Object: if not present or null then the item has not been populated
    private static final String TMP_DIR = "tmp";

    private Tree tree;
    private Label treeScopeLabel;

    IDevice mDevice = null;
    private WaitDeviceDialog waitDeviceDialog = null;

    /* Table view */
    private static final DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.MEDIUM,
            DateFormat.MEDIUM);
    private static final String TABLEITEMDATA_FILE = "TableItem.file";
    // File: File associated with table row
    private static final String TABLEDATA_DIR = "Table.dir";
    // File: Currently visible directory
    // private static final int[] tableWidths = new int[] { 150, 60, 75, 150 };

    protected static final int REFRESH_INTERVAL = 3000;

    private final String[] tableTitles = new String[] { FileViewer.getResourceString("table.Name.title"),
            FileViewer.getResourceString("table.Permission.title"),
            FileViewer.getResourceString("table.Link.title"), FileViewer.getResourceString("table.Size.title"),
            FileViewer.getResourceString("table.Type.title"),
            FileViewer.getResourceString("table.Modified.title") };
    private static final String[] mTableWidthPrefKeys = new String[] { "nameWidth", "permissionWidth", "linkWidth",
            "sizeWidth", "typeWidth", "modifiedWidth" };

    private Table table;
    private Label tableContentsOfLabel;
    private SashForm sashForm;

    /* Table update worker */
    // Control data
    private final Object workerLock = new Object();
    // Lock for all worker control data and state
    private volatile Thread workerThread = null;
    // The worker's thread
    private volatile boolean workerStopped = false;
    // True if the worker must exit on completion of the current cycle
    private volatile boolean workerCancelled = false;
    // True if the worker must cancel its operations prematurely perhaps due to
    // a state update

    // Worker state information -- this is what gets synchronized by an update
    private volatile File workerStateDir = null;

    // State information to use for the next cycle
    private volatile File workerNextDir = null;

    /* Simulate only flag */
    // when true, disables actual filesystem manipulations and outputs results
    // to standard out
    private boolean simulateOnly = true;

    private RefreshThread mRefreshThread = null;

    private String mDeviceName = "unknown";

    // Copy Cut and Paste
    private File[] mCopyFiles = null;

    private static final int STATE_COPY = 1;
    private static final int STATE_CUT = 2;
    private static final int STATE_NONE = 0;
    private int copyOrCut = STATE_NONE;

    /**
     * Runs main program.
     */
    public static void main(String[] args) {
        try {
            Display display = new Display();
            FileViewer application = new FileViewer();
            Shell shell = application.open(display);
            AndroidDebugBridge.init(true);
            AndroidDebugBridge.createBridge("adb", true);
            AndroidDebugBridge.addDeviceChangeListener(application);
            application.showConnectDialog();
            while (!shell.isDisposed()) {
                if (!display.readAndDispatch())
                    display.sleep();
            }
            application.close();
            display.dispose();
            System.exit(0);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    void postInit() {
        java.io.File tmpDir = new java.io.File(TMP_DIR);
        // if (!tmpDir.exists() || !tmpDir.isDirectory()) {
        tmpDir.mkdirs();
        // }
    }

    /**
     * Opens the main program.
     */
    public Shell open(Display display) {
        // Create the window
        this.display = display;
        iconCache.initResources(display);
        shell = new Shell();
        postInit();
        createShellContents();
        shell.open();
        return shell;
    }

    /**
     * Closes the main program.
     */
    void close() {
        workerStop();
        iconCache.freeResources();
    }

    /**
     * Returns a string from the resource bundle. We don't want to crash because
     * of a missing String. Returns the key if not found.
     */
    static String getResourceString(String key) {
        try {
            return resourceBundle.getString(key);
        } catch (MissingResourceException e) {
            return key;
        } catch (NullPointerException e) {
            return "!" + key + "!";
        }
    }

    /**
     * Returns a string from the resource bundle and binds it with the given
     * arguments. If the key is not found, return the key.
     */
    static String getResourceString(String key, Object[] args) {
        try {
            return MessageFormat.format(getResourceString(key), args);
        } catch (MissingResourceException e) {
            return key;
        } catch (NullPointerException e) {
            return "!" + key + "!";
        }
    }

    void saveAllPreferences() {
        Log.i("save");
        // Save table column width
        for (int i = 0; i < tableTitles.length; ++i) {
            mPref.setInt(mTableWidthPrefKeys[i], table.getColumn(i).getWidth());
        }
        // Save sash weight
        mPref.setInt(PREF_SASH_TREE_WEIGHT, sashForm.getWeights()[0]);
        mPref.setInt(PREF_SASH_TABLE_WEIGHT, sashForm.getWeights()[1]);
        // Save shell prefs
        mPref.setInt(PREF_SHELL_LOCATION_X, shell.getLocation().x);
        mPref.setInt(PREF_SHELL_LOCATION_Y, shell.getLocation().y);
        mPref.setBoolean(PREF_SHELL_MAXIUM, shell.getMaximized());
    }

    /**
     * Construct the UI
     * 
     * @param container
     *            the ShellContainer managing the Shell we are rendering inside
     */
    private void createShellContents() {
        shell.setText(getResourceString("Title", new Object[] { mDeviceName, "" }));
        shell.setImage(iconCache.stockImages[iconCache.shellIcon]);
        // Restore last shell location
        shell.setLocation(mPref.getInt(PREF_SHELL_LOCATION_X, shell.getLocation().x),
                mPref.getInt(PREF_SHELL_LOCATION_Y, shell.getLocation().x));
        // Restore last shell maxium state
        shell.setMaximized(mPref.getBoolean(PREF_SHELL_MAXIUM, false));

        shell.addShellListener(new ShellAdapter() {
            @Override
            public void shellClosed(ShellEvent e) {
                mRefreshThread.exit = true;
                saveAllPreferences();
            }

            @Override
            public void shellDeiconified(ShellEvent e) {
                if (mRefreshThread != null)
                    mRefreshThread.pauseRefreshing(false);
            }

            @Override
            public void shellIconified(ShellEvent e) {
                if (mRefreshThread != null)
                    mRefreshThread.pauseRefreshing(true);
            }
        });

        Menu bar = new Menu(shell, SWT.BAR);
        shell.setMenuBar(bar);
        createFileMenu(bar);
        createCommandMenu(bar);
        createHelpMenu(bar);

        GridLayout gridLayout = new GridLayout();
        gridLayout.numColumns = 3;
        gridLayout.marginHeight = gridLayout.marginWidth = 0;
        shell.setLayout(gridLayout);

        GridData gridData = new GridData(GridData.HORIZONTAL_ALIGN_FILL);
        gridData.widthHint = 185;
        createComboView(shell, gridData);
        gridData = new GridData(GridData.HORIZONTAL_ALIGN_FILL);
        gridData.horizontalSpan = 2;
        createToolBar(shell, gridData);

        sashForm = new SashForm(shell, SWT.NONE);
        sashForm.setOrientation(SWT.HORIZONTAL);
        gridData = new GridData(GridData.FILL_HORIZONTAL | GridData.FILL_VERTICAL);
        gridData.horizontalSpan = 3;
        sashForm.setLayoutData(gridData);
        createTreeView(sashForm);
        createTableView(sashForm);
        sashForm.setWeights(
                new int[] { mPref.getInt(PREF_SASH_TREE_WEIGHT, 2), mPref.getInt(PREF_SASH_TABLE_WEIGHT, 5) });

        numObjectsLabel = new Label(shell, SWT.BORDER);
        gridData = new GridData(GridData.FILL_HORIZONTAL | GridData.VERTICAL_ALIGN_FILL);
        gridData.widthHint = 185;
        numObjectsLabel.setLayoutData(gridData);

        diskSpaceLabel = new Label(shell, SWT.BORDER);
        gridData = new GridData(GridData.FILL_HORIZONTAL | GridData.VERTICAL_ALIGN_FILL);
        gridData.horizontalSpan = 2;
        diskSpaceLabel.setLayoutData(gridData);
    }

    /**
     * Creates the File Menu.
     * 
     * @param parent
     *            the parent menu
     */
    private void createFileMenu(Menu parent) {
        Menu menu = new Menu(parent);
        MenuItem header = new MenuItem(parent, SWT.CASCADE);
        header.setText(getResourceString("menu.File.text"));
        header.setMenu(menu);

        final MenuItem simulateItem = new MenuItem(menu, SWT.CHECK);
        simulateItem.setText(getResourceString("menu.File.SimulateOnly.text"));
        simulateItem.setSelection(simulateOnly);
        simulateItem.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                simulateOnly = simulateItem.getSelection();
            }
        });

        MenuItem item = new MenuItem(menu, SWT.PUSH);
        item.setText(getResourceString("menu.File.Close.text"));
        item.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                shell.close();
            }
        });
    }

    private void createCommandMenu(Menu parent) {
        MenuItem cmdItem = new MenuItem(parent, SWT.CASCADE);
        cmdItem.setText("&Command");

        Menu cmdMenu = new Menu(parent);
        cmdItem.setMenu(cmdMenu);

        // command menu
        final MenuItem rebootItem = new MenuItem(cmdMenu, SWT.NONE);
        rebootItem.setText("reboot");
        rebootItem.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                try {
                    mDevice.reboot(null);
                } catch (Exception e1) {
                    e1.printStackTrace();
                }
            }
        });

        final MenuItem remountItem = new MenuItem(cmdMenu, SWT.NONE);
        remountItem.setText("remount");
        remountItem.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                String result = null;
                try {
                    result = mDevice.execRawCmd("remount:");
                } catch (Exception e1) {
                    result = "remount failed!!!";
                    e1.printStackTrace();
                } finally {
                    MessageBox msg = new MessageBox(shell);
                    msg.setMessage(result);
                    msg.open();
                }
            }
        });

        cmdMenu.addMenuListener(new MenuAdapter() {
            @Override
            public void menuShown(MenuEvent e) {
                boolean deviceEnabled = mDevice != null;
                rebootItem.setEnabled(deviceEnabled);
                remountItem.setEnabled(deviceEnabled);
            }
        });
    }

    /**
     * Creates the Help Menu.
     * 
     * @param parent
     *            the parent menu
     */
    private void createHelpMenu(Menu parent) {
        Menu menu = new Menu(parent);
        MenuItem header = new MenuItem(parent, SWT.CASCADE);
        header.setText(getResourceString("menu.Help.text"));
        header.setMenu(menu);

        MenuItem item = new MenuItem(menu, SWT.PUSH);
        item.setText(getResourceString("menu.Help.About.text"));
        item.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                MessageBox box = new MessageBox(shell, SWT.ICON_INFORMATION | SWT.OK);
                box.setText(getResourceString("dialog.About.title"));
                box.setMessage(getResourceString("dialog.About.description",
                        new Object[] { System.getProperty("os.name") }));
                box.open();
            }
        });
    }

    class RefreshThread extends Thread {
        private static final int CHECK_INTERVAL = 200;
        private static final int DEFAULT_REFRESH_INTERVAL = 5000;
        private static final int QUICK_REFRESH_INTERVAL = 500;

        private boolean pause = false;
        private int keepQuickRefreshPeriod = -1;

        public boolean exit = false;

        public void quickRefresh(int period) {
            keepQuickRefreshPeriod = period;
        }

        public void pauseRefreshing(boolean state) {
            // Log.i("pauseRefreshing");
            pause = state;
        }

        @Override
        public void run() {
            while (!exit) {
                if (!pause) {
                    display.syncExec(new Runnable() {
                        @Override
                        public void run() {
                            if (!shell.isDisposed()) {
                                // Log.i("refresh");
                                doRefresh();
                            }
                        }
                    });
                }
                if (keepQuickRefreshPeriod > 0) {
                    keepQuickRefreshPeriod -= QUICK_REFRESH_INTERVAL;
                    int i = 0;
                    while (i < QUICK_REFRESH_INTERVAL / CHECK_INTERVAL) {
                        i++;
                        Common.sleep(CHECK_INTERVAL);
                    }
                } else {
                    int i = 0;
                    while (i < DEFAULT_REFRESH_INTERVAL / CHECK_INTERVAL && keepQuickRefreshPeriod <= 0) {
                        i++;
                        Common.sleep(CHECK_INTERVAL);
                    }
                }

            }
        }
    }

    SelectionAdapter unimplementedListener = new SelectionAdapter() {
        public void widgetSelected(SelectionEvent e) {
            MessageBox box = new MessageBox(shell, SWT.ICON_INFORMATION | SWT.OK);
            box.setText(getResourceString("dialog.NotImplemented.title"));
            box.setMessage(getResourceString("dialog.ActionNotImplemented.description"));
            box.open();
        }
    };

    /**
     * Creates the toolbar
     * 
     * @param shell
     *            the shell on which to attach the toolbar
     * @param layoutData
     *            the layout data
     */
    private void createToolBar(final Shell shell, Object layoutData) {
        toolBar = new ToolBar(shell, SWT.NONE);
        toolBar.setLayoutData(layoutData);
        ToolItem item = new ToolItem(toolBar, SWT.SEPARATOR);
        item = new ToolItem(toolBar, SWT.PUSH);
        item.setImage(iconCache.stockImages[iconCache.cmdParent]);
        item.setToolTipText(getResourceString("tool.Parent.tiptext"));
        item.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                doParent();
            }
        });
        item = new ToolItem(toolBar, SWT.PUSH);
        item.setImage(iconCache.stockImages[iconCache.cmdRefresh]);
        item.setToolTipText(getResourceString("tool.Refresh.tiptext"));
        item.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                doRefresh();
            }
        });

        // item = new ToolItem(toolBar, SWT.SEPARATOR);
        // item = new ToolItem(toolBar, SWT.PUSH);
        // item.setImage(iconCache.stockImages[iconCache.cmdCut]);
        // item.setToolTipText(getResourceString("tool.Cut.tiptext"));
        // item.addSelectionListener(unimplementedListener);
        // item = new ToolItem(toolBar, SWT.PUSH);
        // item.setImage(iconCache.stockImages[iconCache.cmdCopy]);
        // item.setToolTipText(getResourceString("tool.Copy.tiptext"));
        // item.addSelectionListener(unimplementedListener);
        // item = new ToolItem(toolBar, SWT.PUSH);
        // item.setImage(iconCache.stockImages[iconCache.cmdPaste]);
        // item.setToolTipText(getResourceString("tool.Paste.tiptext"));
        // item.addSelectionListener(unimplementedListener);

        item = new ToolItem(toolBar, SWT.SEPARATOR);
        item = new ToolItem(toolBar, SWT.PUSH);
        item.setImage(iconCache.stockImages[iconCache.cmdDelete]);
        item.setToolTipText(getResourceString("tool.Delete.tiptext"));
        item.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                for (File f : getTableSelectFiles()) {
                    f.delete();
                }
                mRefreshThread.quickRefresh(5000);
            }
        });

        // item = new ToolItem(toolBar, SWT.PUSH);
        // item.setImage(iconCache.stockImages[iconCache.cmdRename]);
        // item.setToolTipText(getResourceString("tool.Rename.tiptext"));
        // item.addSelectionListener(unimplementedListener);

        item = new ToolItem(toolBar, SWT.SEPARATOR);
        item = new ToolItem(toolBar, SWT.PUSH);
        item.setImage(iconCache.stockImages[iconCache.cmdSearch]);
        item.setToolTipText("open local file location");
        item.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                openWindowsFileExplorer(TMP_DIR, false);
            }
        });
        // item = new ToolItem(toolBar, SWT.PUSH);
        // item.setImage(iconCache.stockImages[iconCache.cmdPrint]);
        // item.setToolTipText(getResourceString("tool.Print.tiptext"));
        // item.addSelectionListener(unimplementedListener);
    }

    /**
     * Creates the combo box view.
     * 
     * @param parent
     *            the parent control
     */
    private void createComboView(Composite parent, Object layoutData) {
        combo = new Combo(parent, SWT.NONE);
        combo.setLayoutData(layoutData);
        combo.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                final File[] roots = (File[]) combo.getData(COMBODATA_ROOTS);
                if (roots == null)
                    return;
                int selection = combo.getSelectionIndex();
                if (selection >= 0 && selection < roots.length) {
                    notifySelectedDirectory(roots[selection], true);
                }
            }

            public void widgetDefaultSelected(SelectionEvent e) {
                final String lastText = (String) combo.getData(COMBODATA_LASTTEXT);
                String text = combo.getText();
                if (text == null)
                    return;
                if (lastText != null && lastText.equals(text))
                    return;
                combo.setData(COMBODATA_LASTTEXT, text);
                notifySelectedDirectory(new File(text), true);
            }
        });
    }

    /**
     * Creates the file tree view.
     * 
     * @param parent
     *            the parent control
     */
    private void createTreeView(Composite parent) {
        Composite composite = new Composite(parent, SWT.NONE);
        GridLayout gridLayout = new GridLayout();
        gridLayout.numColumns = 1;
        gridLayout.marginHeight = gridLayout.marginWidth = 2;
        gridLayout.horizontalSpacing = gridLayout.verticalSpacing = 0;
        composite.setLayout(gridLayout);

        treeScopeLabel = new Label(composite, SWT.BORDER);
        treeScopeLabel.setText(FileViewer.getResourceString("details.AllFolders.text"));
        treeScopeLabel.setLayoutData(new GridData(GridData.FILL_HORIZONTAL | GridData.VERTICAL_ALIGN_FILL));

        tree = new Tree(composite, SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL | SWT.SINGLE);
        tree.setLayoutData(new GridData(GridData.FILL_HORIZONTAL | GridData.FILL_VERTICAL));

        tree.addSelectionListener(new SelectionListener() {
            public void widgetSelected(SelectionEvent event) {
                Log.i("widgetSelected " + event);
                final TreeItem[] selection = tree.getSelection();
                if (selection != null && selection.length != 0) {
                    TreeItem item = selection[0];
                    File file = (File) item.getData(TREEITEMDATA_FILE);

                    notifySelectedDirectory(file, true);
                }
            }

            public void widgetDefaultSelected(SelectionEvent event) {
                Log.i("widgetDefaultSelected " + event);
                final TreeItem[] selection = tree.getSelection();
                if (selection != null && selection.length != 0) {
                    TreeItem item = selection[0];
                    item.setExpanded(true);
                    treeExpandItem(item);
                }
            }
        });
        tree.addTreeListener(new TreeAdapter() {
            public void treeExpanded(TreeEvent event) {
                Log.i("treeExpanded " + event);
                final TreeItem item = (TreeItem) event.item;
                final Image image = (Image) item.getData(TREEITEMDATA_IMAGEEXPANDED);
                if (image != null)
                    item.setImage(image);
                treeExpandItem(item);
            }

            public void treeCollapsed(TreeEvent event) {
                Log.i("collapsed " + event);
                final TreeItem item = (TreeItem) event.item;
                final Image image = (Image) item.getData(TREEITEMDATA_IMAGECOLLAPSED);
                if (image != null)
                    item.setImage(image);
            }
        });
        // createTreeDragSource(tree);
        // createTreeDropTarget(tree);
    }

    File[] getTableSelectFiles() {
        return getTableData(table.getSelection());
    }

    File[] getTableData(TableItem[] items) {
        File[] files = new File[items.length];
        for (int i = 0; i < items.length; i++) {
            files[i] = (File) items[i].getData(TABLEITEMDATA_FILE);
        }
        return files;
    }

    File[] getTreeSelectFiles() {
        TreeItem[] items = tree.getSelection();
        File[] files = new File[items.length];
        for (int i = 0; i < items.length; i++) {
            files[i] = (File) items[i].getData(TREEITEMDATA_FILE);
        }
        return files;
    }

    /**
     * Creates the Drag & Drop DragSource for items being dragged from the tree.
     * 
     * @return the DragSource for the tree
     */
    private DragSource createTreeDragSource(final Tree tree) {
        DragSource dragSource = new DragSource(tree, DND.DROP_MOVE | DND.DROP_COPY);
        dragSource.setTransfer(new Transfer[] { FileTransfer.getInstance() });
        dragSource.addDragListener(new DragSourceListener() {
            TreeItem[] dndSelection = null;
            String[] sourceNames = null;

            public void dragStart(DragSourceEvent event) {
                dndSelection = tree.getSelection();
                sourceNames = null;
                event.doit = dndSelection.length > 0;
                isDragging = true;
                processedDropFiles = null;
            }

            public void dragFinished(DragSourceEvent event) {
                dragSourceHandleDragFinished(event, sourceNames);
                dndSelection = null;
                sourceNames = null;
                isDragging = false;
                processedDropFiles = null;
                handleDeferredRefresh();
            }

            public void dragSetData(DragSourceEvent event) {
                if (dndSelection == null || dndSelection.length == 0)
                    return;
                if (!FileTransfer.getInstance().isSupportedType(event.dataType))
                    return;

                sourceNames = new String[dndSelection.length];
                for (int i = 0; i < dndSelection.length; i++) {
                    File file = (File) dndSelection[i].getData(TREEITEMDATA_FILE);
                    sourceNames[i] = file.getAbsolutePath();
                }
                event.data = sourceNames;
            }
        });
        return dragSource;
    }

    /**
     * Creates the Drag & Drop DropTarget for items being dropped onto the tree.
     * 
     * @return the DropTarget for the tree
     */
    private DropTarget createTreeDropTarget(final Tree tree) {
        DropTarget dropTarget = new DropTarget(tree, DND.DROP_MOVE | DND.DROP_COPY);
        dropTarget.setTransfer(new Transfer[] { FileTransfer.getInstance() });
        dropTarget.addDropListener(new DropTargetAdapter() {
            public void dragEnter(DropTargetEvent event) {
                isDropping = true;
            }

            public void dragLeave(DropTargetEvent event) {
                isDropping = false;
                handleDeferredRefresh();
            }

            public void dragOver(DropTargetEvent event) {
                dropTargetValidate(event, getTargetFile(event));
                event.feedback |= DND.FEEDBACK_EXPAND | DND.FEEDBACK_SCROLL;
            }

            public void drop(DropTargetEvent event) {
                File targetFile = getTargetFile(event);
                if (dropTargetValidate(event, targetFile))
                    dropTargetHandleDrop(event, targetFile);
            }

            private File getTargetFile(DropTargetEvent event) {
                // Determine the target File for the drop
                TreeItem item = tree.getItem(tree.toControl(new Point(event.x, event.y)));
                File targetFile = null;
                if (item != null) {
                    // We are over a particular item in the tree, use the item's
                    // file
                    targetFile = (File) item.getData(TREEITEMDATA_FILE);
                }
                return targetFile;
            }
        });
        return dropTarget;
    }

    /**
     * Handles expand events on a tree item.
     * 
     * @param item
     *            the TreeItem to fill in
     */
    private void treeExpandItem(TreeItem item) {
        shell.setCursor(iconCache.stockCursors[iconCache.cursorWait]);
        final Object stub = item.getData(TREEITEMDATA_STUB);
        if (stub == null)
            treeRefreshItem(item, true);
        shell.setCursor(iconCache.stockCursors[iconCache.cursorDefault]);
    }

    /**
     * Traverse the entire tree and update only what has changed.
     * 
     * @param roots
     *            the root directory listing
     */
    private void treeRefresh(File[] masterFiles) {
        TreeItem[] items = tree.getItems();
        int masterIndex = 0;
        int itemIndex = 0;

        for (int i = 0; i < items.length; ++i) {
            final TreeItem item = items[i];
            final File itemFile = (File) item.getData(TREEITEMDATA_FILE);
            if ((itemFile == null) || (masterIndex == masterFiles.length)) {
                // remove bad item or placeholder
                item.dispose();
                continue;
            }
            final File masterFile = masterFiles[masterIndex];
            int compare = compareFiles(masterFile, itemFile);
            if (compare == 0) {
                // same file, update it
                treeRefreshItem(item, false);
                ++itemIndex;
                ++masterIndex;
            } else if (compare < 0) {
                // should appear before file, insert it
                TreeItem newItem = new TreeItem(tree, SWT.NONE, itemIndex);
                treeInitVolume(newItem, masterFile);
                new TreeItem(newItem, SWT.NONE); // placeholder child item to
                                                 // get "expand" button
                ++itemIndex;
                ++masterIndex;
                --i;
            } else {
                // should appear after file, delete stale item
                item.dispose();
            }
        }
        for (; masterIndex < masterFiles.length; ++masterIndex) {
            final File masterFile = masterFiles[masterIndex];
            TreeItem newItem = new TreeItem(tree, SWT.NONE);
            treeInitVolume(newItem, masterFile);
            new TreeItem(newItem, SWT.NONE); // placeholder child item to get
                                             // "expand" button
        }
    }

    /**
     * Traverse an item in the tree and update only what has changed.
     * 
     * @param dirItem
     *            the tree item of the directory
     * @param forcePopulate
     *            true iff we should populate non-expanded items as well
     */
    private void treeRefreshItem(TreeItem dirItem, boolean forcePopulate) {
        final File dir = (File) dirItem.getData(TREEITEMDATA_FILE);

        if (!forcePopulate && !dirItem.getExpanded()) {
            // Refresh non-expanded item
            if (dirItem.getData(TREEITEMDATA_STUB) != null) {
                Log.i("here");

                treeItemRemoveAll(dirItem);
                new TreeItem(dirItem, SWT.NONE); // placeholder child item to
                                                 // get "expand" button
                dirItem.setData(TREEITEMDATA_STUB, null);
            }
            return;
        }
        // Refresh expanded item
        dirItem.setData(TREEITEMDATA_STUB, this); // clear stub flag

        /* Get directory listing */
        File[] subFiles = (dir != null) ? FileViewer.getDirectoryList(dir) : null;
        if (subFiles == null || subFiles.length == 0) {
            /* Error or no contents */
            treeItemRemoveAll(dirItem);
            dirItem.setExpanded(false);
            return;
        }

        /* Refresh sub-items */
        TreeItem[] items = dirItem.getItems();
        final File[] masterFiles = subFiles;
        int masterIndex = 0;
        int itemIndex = 0;
        File masterFile = null;
        for (int i = 0; i < items.length; ++i) {
            while ((masterFile == null) && (masterIndex < masterFiles.length)) {
                masterFile = masterFiles[masterIndex++];
                if (!masterFile.isDirectory())
                    masterFile = null;
            }

            final TreeItem item = items[i];
            final File itemFile = (File) item.getData(TREEITEMDATA_FILE);
            if ((itemFile == null) || (masterFile == null)) {
                // remove bad item or placeholder
                item.dispose();
                continue;
            }
            int compare = compareFiles(masterFile, itemFile);
            if (compare == 0) {
                // same file, update it
                treeRefreshItem(item, false);
                masterFile = null;
                ++itemIndex;
            } else if (compare < 0) {
                Log.i("less:" + itemFile.getName());
                // should appear before file, insert it
                TreeItem newItem = new TreeItem(dirItem, SWT.NONE, itemIndex);
                treeInitFolder(newItem, masterFile);
                new TreeItem(newItem, SWT.NONE); // add a placeholder child item
                                                 // so we get the "expand"
                                                 // button
                masterFile = null;
                ++itemIndex;
                --i;
            } else {
                Log.i("big" + itemFile.getName());
                // should appear after file, delete stale item
                item.dispose();
            }
        }
        while ((masterFile != null) || (masterIndex < masterFiles.length)) {
            if (masterFile != null) {
                TreeItem newItem = new TreeItem(dirItem, SWT.NONE);
                treeInitFolder(newItem, masterFile);
                new TreeItem(newItem, SWT.NONE); // add a placeholder child item
                                                 // so we get the "expand"
                                                 // button
                if (masterIndex == masterFiles.length)
                    break;
            }
            masterFile = masterFiles[masterIndex++];
            if (!masterFile.isDirectory())
                masterFile = null;
        }
    }

    /**
     * Foreign method: removes all children of a TreeItem.
     * 
     * @param treeItem
     *            the TreeItem
     */
    private static void treeItemRemoveAll(TreeItem treeItem) {
        final TreeItem[] children = treeItem.getItems();
        for (int i = 0; i < children.length; ++i) {
            children[i].dispose();
        }
    }

    /**
     * Initializes a folder item.
     * 
     * @param item
     *            the TreeItem to initialize
     * @param folder
     *            the File associated with this TreeItem
     */
    private void treeInitFolder(TreeItem item, File folder) {
        item.setText(folder.getName());
        item.setImage(iconCache.stockImages[iconCache.iconClosedFolder]);
        item.setData(TREEITEMDATA_FILE, folder);
        item.setData(TREEITEMDATA_IMAGEEXPANDED, iconCache.stockImages[iconCache.iconOpenFolder]);
        item.setData(TREEITEMDATA_IMAGECOLLAPSED, iconCache.stockImages[iconCache.iconClosedFolder]);
    }

    /**
     * Initializes a volume item.
     * 
     * @param item
     *            the TreeItem to initialize
     * @param volume
     *            the File associated with this TreeItem
     */
    private void treeInitVolume(TreeItem item, File volume) {
        item.setText(volume.getPath());
        item.setImage(iconCache.stockImages[iconCache.iconClosedDrive]);
        item.setData(TREEITEMDATA_FILE, volume);
        item.setData(TREEITEMDATA_IMAGEEXPANDED, iconCache.stockImages[iconCache.iconOpenDrive]);
        item.setData(TREEITEMDATA_IMAGECOLLAPSED, iconCache.stockImages[iconCache.iconClosedDrive]);
    }

    /**
     * Creates the file details table.
     * 
     * @param parent
     *            the parent control
     */
    private void createTableView(Composite parent) {
        Composite composite = new Composite(parent, SWT.NONE);
        GridLayout gridLayout = new GridLayout();
        gridLayout.numColumns = 1;
        gridLayout.marginHeight = gridLayout.marginWidth = 2;
        gridLayout.horizontalSpacing = gridLayout.verticalSpacing = 0;
        composite.setLayout(gridLayout);
        tableContentsOfLabel = new Label(composite, SWT.BORDER);
        tableContentsOfLabel.setLayoutData(new GridData(GridData.FILL_HORIZONTAL | GridData.VERTICAL_ALIGN_FILL));

        table = new Table(composite, SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL | SWT.MULTI | SWT.FULL_SELECTION);
        table.setLayoutData(new GridData(GridData.FILL_HORIZONTAL | GridData.FILL_VERTICAL));
        table.setMenu(createPopupMenu());

        for (int i = 0; i < tableTitles.length; ++i) {
            TableColumn column = new TableColumn(table, SWT.NONE);
            column.setText(tableTitles[i]);
            column.setWidth(mPref.getInt(mTableWidthPrefKeys[i], 100));
        }

        table.setHeaderVisible(true);
        table.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent event) {
                notifySelectedFiles(getSelectedFiles());
            }

            public void widgetDefaultSelected(SelectionEvent event) {
                doDefaultFileAction(getSelectedFiles());
            }

            private File[] getSelectedFiles() {
                final TableItem[] items = table.getSelection();
                final File[] files = new File[items.length];

                for (int i = 0; i < items.length; ++i) {
                    files[i] = (File) items[i].getData(TABLEITEMDATA_FILE);
                }
                return files;
            }
        });

        // createTableDragSource(table);
        createTableDropTarget(table);
    }

    void saveTableColumnWidth() {

    }

    private Menu createPopupMenu() {
        Menu popUpMenu = new Menu(shell, SWT.POP_UP);

        final MenuItem itemEdit = new MenuItem(popUpMenu, SWT.PUSH);
        itemEdit.setText("pull and open explorer");
        itemEdit.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                File[] files = getTableData(table.getSelection());
                Log.i(files[0]);
                FileUtils.deleteQuietly(new java.io.File(TMP_DIR));
                new java.io.File(TMP_DIR).mkdirs();
                files[0].pullFiles(files, TMP_DIR, shell, new Runnable() {
                    @Override
                    public void run() {
                        openWindowsFileExplorer(new java.io.File(TMP_DIR).getAbsolutePath(), false);
                    }
                });
            }
        });
        new MenuItem(popUpMenu, SWT.SEPARATOR);

        // copy
        final MenuItem itemCopy = new MenuItem(popUpMenu, SWT.PUSH);
        itemCopy.setText("copy");
        itemCopy.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                mCopyFiles = getTableData(table.getSelection());
                if (mCopyFiles != null) {
                    copyOrCut = STATE_COPY;
                }
            }
        });

        final MenuItem itemCut = new MenuItem(popUpMenu, SWT.PUSH);
        itemCut.setText("cut");
        itemCut.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                mCopyFiles = getTableData(table.getSelection());
                if (mCopyFiles != null) {
                    copyOrCut = STATE_CUT;
                }
            }
        });

        final MenuItem itemPaste = new MenuItem(popUpMenu, SWT.PUSH);
        itemPaste.setText("paste");
        itemPaste.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                if (copyOrCut != STATE_NONE && mCopyFiles != null) {
                    for (File f : mCopyFiles) {
                        if (copyOrCut == STATE_COPY) {
                            f.copyTo(currentDirectory);
                        } else if (copyOrCut == STATE_CUT) {
                            f.moveTo(currentDirectory);
                        }
                    }
                    copyOrCut = STATE_NONE;
                    mCopyFiles = null;
                    mRefreshThread.quickRefresh(5000);
                }
            }
        });

        new MenuItem(popUpMenu, SWT.SEPARATOR);

        // delete
        final MenuItem itemDelete = new MenuItem(popUpMenu, SWT.PUSH);
        itemDelete.setText("delete");
        itemDelete.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                TableItem[] items = table.getSelection();
                for (int i = 0; i < items.length; i++) {
                    ((File) items[i].getData(TABLEITEMDATA_FILE)).delete();
                }
                mRefreshThread.quickRefresh(5000);
            }
        });

        /**
         * Adds a listener to handle enabling and disabling some items in the
         * Edit submenu.
         */
        popUpMenu.addMenuListener(new MenuAdapter() {
            public void menuShown(MenuEvent e) {
                int count = table.getSelectionCount();
                itemEdit.setEnabled(count != 0); // edit
                itemDelete.setEnabled(count != 0); // delete
                itemCopy.setEnabled(count != 0);
                itemCut.setEnabled(count != 0);
                itemPaste.setEnabled(copyOrCut != STATE_NONE && mCopyFiles != null);
            }
        });
        return popUpMenu;
    }

    void openWindowsFileExplorer(String path, boolean select) {
        if (select) {
            OsCmd.exec("Explorer /select," + path);
        } else {
            OsCmd.exec("Explorer " + path);
        }
    }

    /**
     * Creates the Drag & Drop DragSource for items being dragged from the
     * table.
     * 
     * @return the DragSource for the table
     */
    private DragSource createTableDragSource(final Table table) {
        DragSource dragSource = new DragSource(table, DND.DROP_MOVE | DND.DROP_COPY);
        // dragSource.setTransfer(new Transfer[] { AdbTransfer.getInstance() });
        dragSource.setTransfer(new Transfer[] { FileTransfer.getInstance() });
        dragSource.addDragListener(new DragSourceListener() {
            TableItem[] dndSelection = null;
            String[] sourceNames = null;

            public void dragStart(DragSourceEvent event) {
                Log.i("");
                dndSelection = table.getSelection();
                sourceNames = null;
                event.doit = dndSelection.length > 0;
                isDragging = true;
            }

            public void dragFinished(DragSourceEvent event) {
                Log.i(event.widget);
                dragSourceHandleDragFinished(event, sourceNames);
                dndSelection = null;
                sourceNames = null;
                isDragging = false;
                handleDeferredRefresh();
            }

            public void dragSetData(DragSourceEvent event) {
                Log.i("");
                Common.sleep(5000);
                if (dndSelection == null || dndSelection.length == 0)
                    return;
                if (!FileTransfer.getInstance().isSupportedType(event.dataType))
                    return;

                sourceNames = new String[dndSelection.length];
                for (int i = 0; i < dndSelection.length; i++) {
                    File file = (File) dndSelection[i].getData(TABLEITEMDATA_FILE);
                    sourceNames[i] = file.getAbsolutePath();
                }
                event.data = sourceNames;
            }
        });
        return dragSource;
    }

    /**
     * Creates the Drag & Drop DropTarget for items being dropped onto the
     * table.
     * 
     * @return the DropTarget for the table
     */
    private DropTarget createTableDropTarget(final Table table) {
        DropTarget dropTarget = new DropTarget(table, DND.DROP_MOVE | DND.DROP_COPY | DND.DROP_LINK);
        dropTarget.setTransfer(new Transfer[] { FileTransfer.getInstance() });
        dropTarget.addDropListener(new DropTargetAdapter() {
            public void dragEnter(DropTargetEvent event) {
                isDropping = true;
            }

            public void dragLeave(DropTargetEvent event) {
                isDropping = false;
                handleDeferredRefresh();
            }

            public void dragOver(DropTargetEvent event) {
                // dropTargetValidate(event, getTargetFile(event));
                // event.feedback |= DND.FEEDBACK_EXPAND | DND.FEEDBACK_SCROLL;
            }

            public void drop(DropTargetEvent event) {
                String[] sourceFiles = (String[]) event.data;
                if (sourceFiles == null)
                    return;
                final File dropFolder = (File) table.getData(TABLEDATA_DIR);
                dropFolder.pushFiles(shell, sourceFiles, dropFolder, new Runnable() {
                    @Override
                    public void run() {
                        mRefreshThread.quickRefresh(5000);
                    }
                });
            }

            private File getTargetFile(DropTargetEvent event) {
                // Determine the target File for the drop
                TableItem item = table.getItem(table.toControl(new Point(event.x, event.y)));
                File targetFile = null;
                if (item == null) {
                    // We are over an unoccupied area of the table.
                    // If it is a COPY, we can use the table's root file.
                    if (event.detail == DND.DROP_COPY) {
                        targetFile = (File) table.getData(TABLEDATA_DIR);
                    }
                } else {
                    // We are over a particular item in the table, use the
                    // item's file
                    targetFile = (File) item.getData(TABLEITEMDATA_FILE);
                }
                return targetFile;
            }
        });
        return dropTarget;
    }

    /**
     * Notifies the application components that a new current directory has been
     * selected
     * 
     * @param dir
     *            the directory that was selected, null is ignored
     */
    void notifySelectedDirectory(File dir, boolean select) {
        if (dir == null)
            return;
        // Log.i("notifySelectedDirectory:" + dir.getPath());
        if (currentDirectory != null && dir.equals(currentDirectory))
            return;
        currentDirectory = dir;
        notifySelectedFiles(null);

        /*
         * Shell: Sets the title to indicate the selected directory
         */
        shell.setText(getResourceString("Title", new Object[] { mDeviceName, currentDirectory.getPath() }));

        /*
         * Table view: Displays the contents of the selected directory.
         */
        workerUpdate(dir, false);

        /*
         * Combo view: Sets the combo box to point to the selected directory.
         */
        final File[] comboRoots = (File[]) combo.getData(COMBODATA_ROOTS);
        int comboEntry = -1;
        if (comboRoots != null) {
            for (int i = 0; i < comboRoots.length; ++i) {
                if (dir.equals(comboRoots[i])) {
                    comboEntry = i;
                    break;
                }
            }
        }
        if (comboEntry == -1)
            combo.setText(dir.getPath());
        else
            combo.select(comboEntry);

        /*
         * Tree view: If not already expanded, recursively expands the parents
         * of the specified directory until it is visible.
         */
        Vector /* of File */ path = new Vector();
        // Build a stack of paths from the root of the tree
        while (dir != null) {
            path.add(dir);
            dir = dir.getParentFile();
        }
        // Recursively expand the tree to get to the specified directory
        TreeItem[] items = tree.getItems();
        TreeItem lastItem = null;
        for (int i = path.size() - 1; i >= 0; --i) {
            final File pathElement = (File) path.elementAt(i);
            // Search for a particular File in the array of tree items
            // No guarantee that the items are sorted in any recognizable
            // fashion, so we'll
            // just sequential scan. There shouldn't be more than a few thousand
            // entries.
            TreeItem item = null;
            for (int k = 0; k < items.length; ++k) {
                item = items[k];
                if (item.isDisposed())
                    continue;
                final File itemFile = (File) item.getData(TREEITEMDATA_FILE);
                if (itemFile != null && itemFile.equals(pathElement))
                    break;
            }
            if (item == null)
                break;
            lastItem = item;
            if (i != 0 && !item.getExpanded()) {
                treeExpandItem(item);
                item.setExpanded(true);
            }
            items = item.getItems();
        }
        if (select)
            tree.setSelection((lastItem != null) ? new TreeItem[] { lastItem } : new TreeItem[0]);
    }

    /**
     * Notifies the application components that files have been selected
     * 
     * @param files
     *            the files that were selected, null or empty array indicates no
     *            active selection
     */
    void notifySelectedFiles(File[] files) {
        /*
         * Details: Update the details that are visible on screen.
         */
        if ((files != null) && (files.length != 0)) {
            numObjectsLabel.setText(getResourceString("details.NumberOfSelectedFiles.text",
                    new Object[] { new Integer(files.length) }));
            long fileSize = 0L;
            for (int i = 0; i < files.length; ++i) {
                fileSize += files[i].length();
            }
            diskSpaceLabel.setText(getResourceString("details.FileSize.text", new Object[] { new Long(fileSize) }));
        } else {
            // No files selected
            diskSpaceLabel.setText("");
            if (currentDirectory != null) {
                int numObjects = getDirectoryList(currentDirectory).length;
                numObjectsLabel.setText(getResourceString("details.DirNumberOfObjects.text",
                        new Object[] { new Integer(numObjects) }));
            } else {
                numObjectsLabel.setText("");
            }
        }
    }

    /**
     * Notifies the application components that files must be refreshed
     * 
     * @param files
     *            the files that need refreshing, empty array is a no-op, null
     *            refreshes all
     */
    void notifyRefreshFiles(File[] files) {
        if (files != null && files.length == 0)
            return;

        if ((deferredRefreshRequested) && (deferredRefreshFiles != null) && (files != null)) {
            // merge requests
            File[] newRequest = new File[deferredRefreshFiles.length + files.length];
            System.arraycopy(deferredRefreshFiles, 0, newRequest, 0, deferredRefreshFiles.length);
            System.arraycopy(files, 0, newRequest, deferredRefreshFiles.length, files.length);
            deferredRefreshFiles = newRequest;
        } else {
            deferredRefreshFiles = files;
            deferredRefreshRequested = true;
        }
        handleDeferredRefresh();
    }

    /**
     * Handles deferred Refresh notifications (due to Drag & Drop)
     */
    void handleDeferredRefresh() {
        if (isDragging || isDropping || !deferredRefreshRequested)
            return;
        if (progressDialog != null) {
            progressDialog.close();
            progressDialog = null;
        }

        deferredRefreshRequested = false;
        File[] files = deferredRefreshFiles;
        deferredRefreshFiles = null;

        // shell.setCursor(iconCache.stockCursors[iconCache.cursorWait]);

        /*
         * Table view: Refreshes information about any files in the list and
         * their children.
         */
        boolean refreshTable = false;
        if (files != null) {
            for (int i = 0; i < files.length; ++i) {
                final File file = files[i];
                if (file.equals(currentDirectory)) {
                    refreshTable = true;
                    break;
                }
                File parentFile = file.getParentFile();
                if ((parentFile != null) && (parentFile.equals(currentDirectory))) {
                    refreshTable = true;
                    break;
                }
            }
        } else
            refreshTable = true;
        if (refreshTable)
            workerUpdate(currentDirectory, true);

        /*
         * Combo view: Refreshes the list of roots
         */
        final File[] roots = getRoots();

        if (files == null) {
            boolean refreshCombo = false;
            final File[] comboRoots = (File[]) combo.getData(COMBODATA_ROOTS);

            if ((comboRoots != null) && (comboRoots.length == roots.length)) {
                for (int i = 0; i < roots.length; ++i) {
                    if (!roots[i].equals(comboRoots[i])) {
                        refreshCombo = true;
                        break;
                    }
                }
            } else
                refreshCombo = true;

            if (refreshCombo) {
                combo.removeAll();
                combo.setData(COMBODATA_ROOTS, roots);
                for (int i = 0; i < roots.length; ++i) {
                    final File file = roots[i];
                    combo.add(file.getPath());
                }
            }
        }

        /*
         * Tree view: Refreshes information about any files in the list and
         * their children.
         */
        treeRefresh(roots);

        // Remind everyone where we are in the filesystem
        final File dir = currentDirectory;
        currentDirectory = null;
        notifySelectedDirectory(dir, false);

        // shell.setCursor(iconCache.stockCursors[iconCache.cursorDefault]);
    }

    /**
     * Performs the default action on a set of files.
     * 
     * @param files
     *            the array of files to process
     */
    void doDefaultFileAction(File[] files) {
        // only uses the 1st file (for now)
        if (files.length == 0)
            return;
        final File file = files[0];
        if (!file.exists())
            return;

        if (file.isDirectory()) {
            notifySelectedDirectory(file, true);
        } else if (file.isLink()) {
            File lfile = new File(file.mEntry.info);
            doDefaultFileAction(new File[] { lfile });
        } else {
            final String dest = "./" + TMP_DIR + "/" + file.getName();
            file.pull(dest, shell, new Runnable() {
                @Override
                public void run() {
                    if (new java.io.File(dest).exists()) {
                        Program program = Program.findProgram(dest.substring(dest.lastIndexOf(".")));
                        if (program == null) {
                            program = Program.findProgram(".txt");
                        }
                        if (program != null)
                            program.execute(dest);
                    }
                }
            });
        }
    }

    /**
     * Navigates to the parent directory
     */
    void doParent() {
        if (currentDirectory == null)
            return;
        File parentDirectory = currentDirectory.getParentFile();
        notifySelectedDirectory(parentDirectory, true);
    }

    /**
     * Performs a refresh
     */
    void doRefresh() {
        notifyRefreshFiles(null);
    }

    /**
     * Validates a drop target as a candidate for a drop operation.
     * <p>
     * Used in dragOver() and dropAccept().<br>
     * Note event.detail is set to DND.DROP_NONE by this method if the target is
     * not valid.
     * </p>
     * 
     * @param event
     *            the DropTargetEvent to validate
     * @param targetFile
     *            the File representing the drop target location under
     *            inspection, or null if none
     */
    private boolean dropTargetValidate(DropTargetEvent event, File targetFile) {
        if (targetFile != null && targetFile.isDirectory()) {
            if (event.detail != DND.DROP_COPY && event.detail != DND.DROP_MOVE) {
                event.detail = DND.DROP_MOVE;
            }
        } else {
            event.detail = DND.DROP_NONE;
        }
        return event.detail != DND.DROP_NONE;
    }

    /**
     * Handles a drop on a dropTarget.
     * <p>
     * Used in drop().<br>
     * Note event.detail is modified by this method.
     * </p>
     * 
     * @param event
     *            the DropTargetEvent passed as parameter to the drop() method
     * @param targetFile
     *            the File representing the drop target location under
     *            inspection, or null if none
     */
    private void dropTargetHandleDrop(DropTargetEvent event, File targetFile) {
        Log.i("");
        // Get dropped data (an array of filenames)
        if (!dropTargetValidate(event, targetFile))
            return;
        final String[] sourceNames = (String[]) event.data;
        if (sourceNames == null)
            event.detail = DND.DROP_NONE;
        if (event.detail == DND.DROP_NONE)
            return;

        // Open progress dialog
        progressDialog = new ProgressDialog(shell,
                (event.detail == DND.DROP_MOVE) ? ProgressDialog.MOVE : ProgressDialog.COPY);
        progressDialog.setTotalWorkUnits(sourceNames.length);
        progressDialog.open();

        // Copy each file
        Vector /* of File */ processedFiles = new Vector();
        for (int i = 0; (i < sourceNames.length) && (!progressDialog.isCancelled()); i++) {
            final File source = new File(sourceNames[i]);
            final File dest = new File(targetFile, source.getName());
            if (source.equals(dest))
                continue; // ignore if in same location

            progressDialog.setDetailFile(source, ProgressDialog.COPY);
            while (!progressDialog.isCancelled()) {
                if (copyFileStructure(source, dest)) {
                    processedFiles.add(source);
                    break;
                } else if (!progressDialog.isCancelled()) {
                    if (event.detail == DND.DROP_MOVE && (!isDragging)) {
                        // It is not possible to notify an external drag source
                        // that a drop
                        // operation was only partially successful. This is
                        // particularly a
                        // problem for DROP_MOVE operations since unless the
                        // source gets
                        // DROP_NONE, it will delete the original data including
                        // bits that
                        // may not have been transferred successfully.
                        MessageBox box = new MessageBox(shell, SWT.ICON_ERROR | SWT.RETRY | SWT.CANCEL);
                        box.setText(getResourceString("dialog.FailedCopy.title"));
                        box.setMessage(
                                getResourceString("dialog.FailedCopy.description", new Object[] { source, dest }));
                        int button = box.open();
                        if (button == SWT.CANCEL) {
                            i = sourceNames.length;
                            event.detail = DND.DROP_NONE;
                            break;
                        }
                    } else {
                        // We can recover gracefully from errors if the drag
                        // source belongs
                        // to this application since it will look at
                        // processedDropFiles.
                        MessageBox box = new MessageBox(shell, SWT.ICON_ERROR | SWT.ABORT | SWT.RETRY | SWT.IGNORE);
                        box.setText(getResourceString("dialog.FailedCopy.title"));
                        box.setMessage(
                                getResourceString("dialog.FailedCopy.description", new Object[] { source, dest }));
                        int button = box.open();
                        if (button == SWT.ABORT)
                            i = sourceNames.length;
                        if (button != SWT.RETRY)
                            break;
                    }
                }
                progressDialog.addProgress(1);
            }
        }
        if (isDragging) {
            // Remember exactly which files we processed
            processedDropFiles = ((File[]) processedFiles.toArray(new File[processedFiles.size()]));
        } else {
            progressDialog.close();
            progressDialog = null;
        }
        notifyRefreshFiles(new File[] { targetFile });
    }

    /**
     * Handles the completion of a drag on a dragSource.
     * <p>
     * Used in dragFinished().<br>
     * </p>
     * 
     * @param event
     *            the DragSourceEvent passed as parameter to the dragFinished()
     *            method
     * @param sourceNames
     *            the names of the files that were dragged (event.data is
     *            invalid)
     */
    private void dragSourceHandleDragFinished(DragSourceEvent event, String[] sourceNames) {
        Log.i("");
        if (sourceNames == null)
            return;
        if (event.detail != DND.DROP_MOVE)
            return;

        // Get array of files that were actually transferred
        final File[] sourceFiles;
        if (processedDropFiles != null) {
            sourceFiles = processedDropFiles;
        } else {
            sourceFiles = new File[sourceNames.length];
            for (int i = 0; i < sourceNames.length; ++i)
                sourceFiles[i] = new File(sourceNames[i]);
        }
        if (progressDialog == null)
            progressDialog = new ProgressDialog(shell, ProgressDialog.MOVE);
        progressDialog.setTotalWorkUnits(sourceFiles.length);
        progressDialog.setProgress(0);
        progressDialog.open();

        // Delete each file
        for (int i = 0; (i < sourceFiles.length) && (!progressDialog.isCancelled()); i++) {
            final File source = sourceFiles[i];
            progressDialog.setDetailFile(source, ProgressDialog.DELETE);
            while (!progressDialog.isCancelled()) {
                if (deleteFileStructure(source)) {
                    break;
                } else if (!progressDialog.isCancelled()) {
                    MessageBox box = new MessageBox(shell, SWT.ICON_ERROR | SWT.ABORT | SWT.RETRY | SWT.IGNORE);
                    box.setText(getResourceString("dialog.FailedDelete.title"));
                    box.setMessage(getResourceString("dialog.FailedDelete.description", new Object[] { source }));
                    Log.i(getResourceString("dialog.FailedDelete.title"));
                    Log.i(getResourceString("dialog.FailedDelete.description"));
                    int button = box.open();
                    if (button == SWT.ABORT)
                        i = sourceNames.length;
                    if (button == SWT.RETRY)
                        break;
                }
            }
            progressDialog.addProgress(1);
        }
        notifyRefreshFiles(sourceFiles);
        progressDialog.close();
        progressDialog = null;
    }

    /**
     * Gets filesystem root entries
     * 
     * @return an array of Files corresponding to the root directories on the
     *         platform, may be empty but not null
     */
    File[] getRoots() {
        File root = new File(File.separator);
        if (initial) {
            currentDirectory = root;
            initial = false;
        }
        return new File[] { root };
    }

    /**
     * Gets a directory listing
     * 
     * @param file
     *            the directory to be listed
     * @return an array of files this directory contains, may be empty but not
     *         null
     */
    static File[] getDirectoryList(File file) {
        File[] list = file.listFiles();
        if (list == null)
            return new File[0];
        sortFiles(list);
        return list;
    }

    /**
     * Copies a file or entire directory structure.
     * 
     * @param oldFile
     *            the location of the old file or directory
     * @param newFile
     *            the location of the new file or directory
     * @return true iff the operation succeeds without errors
     */
    boolean copyFileStructure(File oldFile, File newFile) {
        Log.i("");
        if (oldFile == null || newFile == null)
            return false;

        // ensure that newFile is not a child of oldFile or a dupe
        File searchFile = newFile;
        do {
            if (oldFile.equals(searchFile))
                return false;
            searchFile = searchFile.getParentFile();
        } while (searchFile != null);

        if (oldFile.isDirectory()) {
            /*
             * Copy a directory
             */
            if (progressDialog != null) {
                progressDialog.setDetailFile(oldFile, ProgressDialog.COPY);
            }
            if (simulateOnly) {
                // System.out.println(getResourceString("simulate.DirectoriesCreated.text",
                // new Object[] { newFile.getPath() }));
            } else {
                if (!newFile.mkdirs())
                    return false;
            }
            File[] subFiles = oldFile.listFiles();
            if (subFiles != null) {
                if (progressDialog != null) {
                    progressDialog.addWorkUnits(subFiles.length);
                }
                for (int i = 0; i < subFiles.length; i++) {
                    File oldSubFile = subFiles[i];
                    File newSubFile = new File(newFile, oldSubFile.getName());
                    if (!copyFileStructure(oldSubFile, newSubFile))
                        return false;
                    if (progressDialog != null) {
                        progressDialog.addProgress(1);
                        if (progressDialog.isCancelled())
                            return false;
                    }
                }
            }
        } else {
            /*
             * Copy a file
             */
            if (simulateOnly) {
                // System.out.println(getResourceString("simulate.CopyFromTo.text",
                // new Object[] { oldFile.getPath(), newFile.getPath() }));
            } else {
                InputStreamReader in = null;
                OutputStreamWriter out = null;
                try {
                    in = new InputStreamReader(oldFile.getInputStream());
                    out = new OutputStreamWriter(newFile.getOutputStream());

                    int count;
                    while ((count = in.read()) != -1)
                        out.write(count);
                } catch (FileNotFoundException e) {
                    return false;
                } catch (IOException e) {
                    return false;
                } finally {
                    try {
                        if (in != null)
                            in.close();
                        if (out != null)
                            out.close();
                    } catch (IOException e) {
                        return false;
                    }
                }
            }
        }
        return true;
    }

    /**
     * Deletes a file or entire directory structure.
     * 
     * @param oldFile
     *            the location of the old file or directory
     * @return true iff the operation succeeds without errors
     */
    boolean deleteFileStructure(File oldFile) {
        Log.i("");
        if (oldFile == null)
            return false;
        if (oldFile.isDirectory()) {
            /*
             * Delete a directory
             */
            if (progressDialog != null) {
                progressDialog.setDetailFile(oldFile, ProgressDialog.DELETE);
            }
            File[] subFiles = oldFile.listFiles();
            if (subFiles != null) {
                if (progressDialog != null) {
                    progressDialog.addWorkUnits(subFiles.length);
                }
                for (int i = 0; i < subFiles.length; i++) {
                    File oldSubFile = subFiles[i];
                    if (!deleteFileStructure(oldSubFile))
                        return false;
                    if (progressDialog != null) {
                        progressDialog.addProgress(1);
                        if (progressDialog.isCancelled())
                            return false;
                    }
                }
            }
        }
        if (simulateOnly) {
            // System.out.println(getResourceString("simulate.Delete.text",
            // new Object[] { oldFile.getPath(), oldFile.getPath() }));
            return true;
        }
        return oldFile.delete();
    }

    /**
     * Sorts files lexicographically by name.
     * 
     * @param files
     *            the array of Files to be sorted
     */
    static void sortFiles(File[] files) {
        /* Very lazy merge sort algorithm */
        sortBlock(files, 0, files.length - 1, new File[files.length]);
    }

    private static void sortBlock(File[] files, int start, int end, File[] mergeTemp) {
        final int length = end - start + 1;
        if (length < 8) {
            for (int i = end; i > start; --i) {
                for (int j = end; j > start; --j) {
                    if (compareFiles(files[j - 1], files[j]) > 0) {
                        final File temp = files[j];
                        files[j] = files[j - 1];
                        files[j - 1] = temp;
                    }
                }
            }
            return;
        }
        final int mid = (start + end) / 2;
        sortBlock(files, start, mid, mergeTemp);
        sortBlock(files, mid + 1, end, mergeTemp);
        int x = start;
        int y = mid + 1;
        for (int i = 0; i < length; ++i) {
            if ((x > mid) || ((y <= end) && compareFiles(files[x], files[y]) > 0)) {
                mergeTemp[i] = files[y++];
            } else {
                mergeTemp[i] = files[x++];
            }
        }
        for (int i = 0; i < length; ++i)
            files[i + start] = mergeTemp[i];
    }

    private static int compareFiles(File a, File b) {
        // boolean aIsDir = a.isDirectory();
        // boolean bIsDir = b.isDirectory();
        // if (aIsDir && ! bIsDir) return -1;
        // if (bIsDir && ! aIsDir) return 1;

        // sort case-sensitive files in a case-insensitive manner
        int compare = a.getName().compareToIgnoreCase(b.getName());
        if (compare == 0)
            compare = a.getName().compareTo(b.getName());
        return compare;
    }

    /*
     * This worker updates the table with file information in the background.
     * <p> Implementation notes: <ul> <li> It is designed such that it can be
     * interrupted cleanly. <li> It uses asyncExec() in some places to ensure
     * that SWT Widgets are manipulated in the right thread. Exclusive use of
     * syncExec() would be inappropriate as it would require a pair of context
     * switches between each table update operation. </ul> </p>
     */

    /**
     * Stops the worker and waits for it to terminate.
     */
    void workerStop() {
        if (workerThread == null)
            return;
        synchronized (workerLock) {
            workerCancelled = true;
            workerStopped = true;
            workerLock.notifyAll();
        }
        while (workerThread != null) {
            if (!display.readAndDispatch())
                display.sleep();
        }
    }

    /**
     * Notifies the worker that it should update itself with new data. Cancels
     * any previous operation and begins a new one.
     * 
     * @param dir
     *            the new base directory for the table, null is ignored
     * @param force
     *            if true causes a refresh even if the data is the same
     */
    void workerUpdate(File dir, boolean force) {
        if (dir == null)
            return;
        if ((!force) && (workerNextDir != null) && (workerNextDir.equals(dir)))
            return;

        synchronized (workerLock) {
            workerNextDir = dir;
            workerStopped = false;
            workerCancelled = true;
            workerLock.notifyAll();
        }
        if (workerThread == null) {
            workerThread = new Thread(workerRunnable);
            workerThread.start();
        }
    }

    /**
     * Manages the worker's thread
     */
    private final Runnable workerRunnable = new Runnable() {
        public void run() {
            while (!workerStopped) {
                synchronized (workerLock) {
                    workerCancelled = false;
                    workerStateDir = workerNextDir;
                }
                workerExecute();
                synchronized (workerLock) {
                    try {
                        if ((!workerCancelled) && (workerStateDir == workerNextDir))
                            workerLock.wait();
                    } catch (InterruptedException e) {
                    }
                }
            }
            workerThread = null;
            // wake up UI thread in case it is in a modal loop awaiting thread
            // termination
            // (see workerStop())
            display.wake();
        }
    };

    /**
     * Updates the table's contents
     */
    private void workerExecute() {
        File[] dirList;

        dirList = getDirectoryList(workerStateDir);
        display.syncExec(new Runnable() {
            @Override
            public void run() {
                mCurTableFiles = getTableData(table.getItems());
            }
        });

        // Only update the table when the current directory files have changed.
        if (isSameFiles(dirList, mCurTableFiles)) {
            // Log.i("same file, cancel update");
            return;
        }

        // Save the current selection and restore that after refresh.
        display.syncExec(new Runnable() {
            @Override
            public void run() {
                mSelectedFiles = getTableSelectFiles();
                mCurTableTopIndex = table.getTopIndex();
            }
        });

        // Clear existing information
        display.syncExec(new Runnable() {
            public void run() {
                tableContentsOfLabel.setText(FileViewer.getResourceString("details.ContentsOf.text",
                        new Object[] { workerStateDir.getPath() }));
                table.removeAll();
                table.setData(TABLEDATA_DIR, workerStateDir);
            }
        });

        for (int i = 0; (!workerCancelled) && (i < dirList.length); i++) {
            workerAddFileDetails(dirList[i]);
        }
        display.syncExec(new Runnable() {
            @Override
            public void run() {
                File[] files = getTableData(table.getItems());
                for (File file : mSelectedFiles) {
                    table.select(Arrays.binarySearch(files, file));
                }
                table.setTopIndex(mCurTableTopIndex);
            }
        });
    }

    boolean isSameFiles(File[] f1, File[] f2) {
        if (f1.length != f2.length)
            return false;
        for (int i = 0; i < f1.length; i++) {
            if (!f1[i].equals(f2[i]))
                return false;
        }
        return true;
    }

    /**
     * Adds a file's detail information to the directory list
     */
    private void workerAddFileDetails(final File file) {
        final String nameString = file.getName();
        final String dateString = file.lastModified();
        final String sizeString;
        final String typeString;
        String linkString = "";
        String permissionString = "";
        final Image iconImage;

        if (file.isDirectory()) {
            typeString = getResourceString("filetype.Folder");
            sizeString = "";
            iconImage = iconCache.stockImages[iconCache.iconClosedFolder];
        } else if (file.isLink()) {
            linkString = file.mEntry.getInfo();
            sizeString = "";
            iconImage = iconCache.stockImages[iconCache.iconLink];
            typeString = ".link";
        } else {
            permissionString = file.mEntry.getPermissions();
            sizeString = getResourceString("filesize.KB", new Object[] { new Long((file.length() + 512) / 1024) });

            int dot = nameString.lastIndexOf('.');
            if (dot != -1) {
                String extension = nameString.substring(dot);
                Program program = Program.findProgram(extension);
                if (program != null) {
                    typeString = program.getName();
                    iconImage = iconCache.getIconFromProgram(program);
                } else {
                    typeString = getResourceString("filetype.Unknown", new Object[] { extension.toUpperCase() });
                    iconImage = iconCache.stockImages[iconCache.iconFile];
                }
            } else {
                typeString = getResourceString("filetype.None");
                iconImage = iconCache.stockImages[iconCache.iconFile];
            }
        }
        final String[] strings = new String[] { nameString, permissionString, linkString, sizeString, typeString,
                dateString };

        display.syncExec(new Runnable() {
            public void run() {
                // guard against the shell being closed before this runs
                if (shell.isDisposed())
                    return;
                TableItem tableItem = new TableItem(table, 0);
                tableItem.setText(strings);
                tableItem.setImage(iconImage);
                tableItem.setData(TABLEITEMDATA_FILE, file);
            }
        });
    }

    /**
     * Instances of this class manage a progress dialog for file operations.
     */
    class ProgressDialog {
        public final static int COPY = 0;
        public final static int DELETE = 1;
        public final static int MOVE = 2;

        Shell shell;
        Label messageLabel, detailLabel;
        ProgressBar progressBar;
        Button cancelButton;
        boolean isCancelled = false;

        final String operationKeyName[] = { "Copy", "Delete", "Move" };

        /**
         * Creates a progress dialog but does not open it immediately.
         * 
         * @param parent
         *            the parent Shell
         * @param style
         *            one of COPY, MOVE
         */
        public ProgressDialog(Shell parent, int style) {
            shell = new Shell(parent, SWT.BORDER | SWT.TITLE | SWT.APPLICATION_MODAL);
            GridLayout gridLayout = new GridLayout();
            shell.setLayout(gridLayout);
            shell.setText(getResourceString("progressDialog." + operationKeyName[style] + ".title"));
            shell.addShellListener(new ShellAdapter() {
                public void shellClosed(ShellEvent e) {
                    isCancelled = true;
                }
            });

            messageLabel = new Label(shell, SWT.HORIZONTAL);
            messageLabel.setLayoutData(new GridData(GridData.FILL_HORIZONTAL | GridData.VERTICAL_ALIGN_FILL));
            messageLabel.setText(getResourceString("progressDialog." + operationKeyName[style] + ".description"));

            progressBar = new ProgressBar(shell, SWT.HORIZONTAL | SWT.WRAP);
            progressBar.setLayoutData(new GridData(GridData.FILL_HORIZONTAL | GridData.VERTICAL_ALIGN_FILL));
            progressBar.setMinimum(0);
            progressBar.setMaximum(0);

            detailLabel = new Label(shell, SWT.HORIZONTAL);
            GridData gridData = new GridData(GridData.FILL_HORIZONTAL | GridData.VERTICAL_ALIGN_BEGINNING);
            gridData.widthHint = 400;
            detailLabel.setLayoutData(gridData);

            cancelButton = new Button(shell, SWT.PUSH);
            cancelButton.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_END | GridData.VERTICAL_ALIGN_FILL));
            cancelButton.setText(getResourceString("progressDialog.cancelButton.text"));
            cancelButton.addSelectionListener(new SelectionAdapter() {
                public void widgetSelected(SelectionEvent e) {
                    isCancelled = true;
                    cancelButton.setEnabled(false);
                }
            });
        }

        /**
         * Sets the detail text to show the filename along with a string
         * representing the operation being performed on that file.
         * 
         * @param file
         *            the file to be detailed
         * @param operation
         *            one of COPY, DELETE
         */
        public void setDetailFile(File file, int operation) {
            detailLabel.setText(getResourceString("progressDialog." + operationKeyName[operation] + ".operation",
                    new Object[] { file }));
        }

        /**
         * Returns true if the Cancel button was been clicked.
         * 
         * @return true if the Cancel button was clicked.
         */
        public boolean isCancelled() {
            return isCancelled;
        }

        /**
         * Sets the total number of work units to be performed.
         * 
         * @param work
         *            the total number of work units
         */
        public void setTotalWorkUnits(int work) {
            progressBar.setMaximum(work);
        }

        /**
         * Adds to the total number of work units to be performed.
         * 
         * @param work
         *            the number of work units to add
         */
        public void addWorkUnits(int work) {
            setTotalWorkUnits(progressBar.getMaximum() + work);
        }

        /**
         * Sets the progress of completion of the total work units.
         * 
         * @param work
         *            the total number of work units completed
         */
        public void setProgress(int work) {
            progressBar.setSelection(work);
            while (display.readAndDispatch()) {
            } // enable event processing
        }

        /**
         * Adds to the progress of completion of the total work units.
         * 
         * @param work
         *            the number of work units completed to add
         */
        public void addProgress(int work) {
            setProgress(progressBar.getSelection() + work);
        }

        /**
         * Opens the dialog.
         */
        public void open() {
            shell.pack();
            final Shell parentShell = (Shell) shell.getParent();
            Rectangle rect = parentShell.getBounds();
            Rectangle bounds = shell.getBounds();
            bounds.x = rect.x + (rect.width - bounds.width) / 2;
            bounds.y = rect.y + (rect.height - bounds.height) / 2;
            shell.setBounds(bounds);
            shell.open();
        }

        /**
         * Closes the dialog and disposes its resources.
         */
        public void close() {
            shell.close();
            shell.dispose();
            shell = null;
            messageLabel = null;
            detailLabel = null;
            progressBar = null;
            cancelButton = null;
        }
    }

    class WaitDeviceDialog extends MessageDialog {
        public WaitDeviceDialog(Shell parentShell, String dialogTitle, String dialogMessage,
                String[] dialogButtonLabels, int defaultIndex) {
            super(parentShell, dialogTitle, null, dialogMessage, MessageDialog.NONE, dialogButtonLabels,
                    defaultIndex);
        }

        @Override
        protected void buttonPressed(int buttonId) {
            super.buttonPressed(1);
        }
    }

    void showConnectDialog() {
        display.asyncExec(new Runnable() {
            @Override
            public void run() {
                if (waitDeviceDialog == null) {
                    waitDeviceDialog = new WaitDeviceDialog(shell, "waiting for devices...",
                            "device is not online, would you to quit now?", new String[] { "quit" }, 0);
                    int retCode = waitDeviceDialog.open();
                    if (retCode == 1) {
                        shell.dispose();
                    }
                }
            }
        });
    }

    @Override
    public void deviceConnected(IDevice device) {
        Log.i("device connect");
        mDevice = device;
        try {
            mDeviceName = device.getPropertyCacheOrSync("ro.product.manufacturer") + "-"
                    + device.getPropertyCacheOrSync("ro.product.model");
        } catch (Exception e) {
            e.printStackTrace();
        }

        File.sv = new FileListingService(mDevice);

        display.asyncExec(new Runnable() {
            @Override
            public void run() {
                if (waitDeviceDialog != null) {
                    waitDeviceDialog.close();
                    waitDeviceDialog = null;
                }
                mRefreshThread = new RefreshThread();
                mRefreshThread.start();
                notifyRefreshFiles(null);
                TreeItem item = tree.getItem(0);
                item.setExpanded(true);
                treeExpandItem(item);
            }
        });
    }

    @Override
    public void deviceDisconnected(IDevice device) {
        Log.i("device disconnect");
        mDevice = null;
        mDeviceName = "offline";
        mRefreshThread.exit = true;
        showConnectDialog();
    }

    @Override
    public void deviceChanged(IDevice device, int changeMask) {
    }
}