net.sourceforge.vaticanfetcher.gui.ResultPanel.java Source code

Java tutorial

Introduction

Here is the source code for net.sourceforge.vaticanfetcher.gui.ResultPanel.java

Source

/*******************************************************************************
 * Copyright (c) 2011 Tran Nam Quang.
 * 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:
 *    Tran Nam Quang - initial API and implementation
 *******************************************************************************/

package net.sourceforge.vaticanfetcher.gui;

import java.io.File;
import java.io.FileNotFoundException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import net.sourceforge.vaticanfetcher.enums.Img;
import net.sourceforge.vaticanfetcher.enums.Msg;
import net.sourceforge.vaticanfetcher.enums.SettingsConf;
import net.sourceforge.vaticanfetcher.model.FileResource;
import net.sourceforge.vaticanfetcher.model.Path;
import net.sourceforge.vaticanfetcher.model.Path.PathParts;
import net.sourceforge.vaticanfetcher.model.parse.ParseException;
import net.sourceforge.vaticanfetcher.model.search.ResultDocument;
import net.sourceforge.vaticanfetcher.util.AppUtil;
import net.sourceforge.vaticanfetcher.util.Event;
import net.sourceforge.vaticanfetcher.util.Util;
import net.sourceforge.vaticanfetcher.util.annotations.MutableCopy;
import net.sourceforge.vaticanfetcher.util.annotations.NotNull;
import net.sourceforge.vaticanfetcher.util.annotations.Nullable;
import net.sourceforge.vaticanfetcher.util.collect.AlphanumComparator;
import net.sourceforge.vaticanfetcher.util.gui.ContextMenuManager;
import net.sourceforge.vaticanfetcher.util.gui.FileIconCache;
import net.sourceforge.vaticanfetcher.util.gui.MenuAction;
import net.sourceforge.vaticanfetcher.util.gui.viewer.VirtualTableViewer;
import net.sourceforge.vaticanfetcher.util.gui.viewer.VirtualTableViewer.Column;

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Table;

import com.google.common.primitives.Longs;

public final class ResultPanel {

    // TODO post-release-1.1: show an additional icon if an email has attachments
    // TODO post-release-1.1: show some helpful overlay message if a search yielded no results
    // TODO post-release-1.1: implement context menu: copy paths to clipboard, including Mod1+C shortcut (also: Mod1+A for selecting all items)

    public enum HeaderMode {
        FILES {
            protected void setLabel(VariableHeaderColumn<?> column) {
                column.setLabel(column.fileHeader);
            }
        },
        EMAILS {
            protected void setLabel(VariableHeaderColumn<?> column) {
                column.setLabel(column.emailHeader);
            }
        },
        FILES_AND_EMAILS {
            protected void setLabel(VariableHeaderColumn<?> column) {
                column.setLabel(column.combinedHeader);
            }
        },;

        protected abstract void setLabel(@NotNull VariableHeaderColumn<?> column);

        @NotNull
        public static HeaderMode getInstance(boolean filesFound, boolean emailsFound) {
            final HeaderMode mode;
            if (filesFound)
                mode = emailsFound ? HeaderMode.FILES_AND_EMAILS : HeaderMode.FILES;
            else
                mode = HeaderMode.EMAILS;
            return mode;
        }
    }

    private static final DateFormat dateFormat = new SimpleDateFormat();

    public final Event<List<ResultDocument>> evtSelection = new Event<List<ResultDocument>>();
    public final Event<Void> evtHideInSystemTray = new Event<Void>();

    private final VirtualTableViewer<ResultDocument> viewer;
    private final FileIconCache iconCache;
    private HeaderMode presetHeaderMode = HeaderMode.FILES; // externally suggested header mode
    private HeaderMode actualHeaderMode = HeaderMode.FILES; // header mode after examining each visible element

    public ResultPanel(@NotNull Composite parent) {
        iconCache = new FileIconCache(parent);

        int treeStyle = SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL | SWT.FULL_SELECTION | SWT.BORDER;
        viewer = new VirtualTableViewer<ResultDocument>(parent, treeStyle) {
            @SuppressWarnings("unchecked")
            protected List<ResultDocument> getElements(Object rootElement) {
                return (List<ResultDocument>) rootElement;
            }
        };

        // Open result document on double-click
        Table table = viewer.getControl();
        table.addMouseListener(new MouseAdapter() {
            public void mouseDoubleClick(MouseEvent e) {
                launchSelection();
            }
        });

        table.addKeyListener(new KeyAdapter() {
            public void keyReleased(KeyEvent e) {
                if (Util.isEnterKey(e.keyCode))
                    launchSelection();
                else if (e.stateMask == SWT.MOD1 && e.keyCode == 'c')
                    copyToClipboard();
            }
        });

        viewer.setSortingEnabled(true);
        initContextMenu();

        table.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                evtSelection.fire(viewer.getSelection());
            }
        });

        viewer.addColumn(new VariableHeaderColumn<ResultDocument>(Msg.title.get(), Msg.subject.get()) {
            protected String getLabel(ResultDocument element) {
                return element.getTitle();
            }

            protected Image getImage(ResultDocument element) {
                if (element.isEmail())
                    return Img.EMAIL.get();
                return iconCache.getIcon(element.getFilename(), Img.FILE.get());
            }

            protected int compare(ResultDocument e1, ResultDocument e2) {
                return compareAlphanum(e1.getTitle(), e2.getTitle());
            }
        });

        viewer.addColumn(new Column<ResultDocument>(Msg.score.get(), SWT.RIGHT) {
            protected String getLabel(ResultDocument element) {
                return String.valueOf(element.getScore());
            }

            protected int compare(ResultDocument e1, ResultDocument e2) {
                return -1 * Float.compare(e1.getScore(), e2.getScore());
            }
        });

        viewer.addColumn(new Column<ResultDocument>(Msg.size.get(), SWT.RIGHT) {
            protected String getLabel(ResultDocument element) {
                return String.format("%,d KB", element.getSizeInKB());
            }

            protected int compare(ResultDocument e1, ResultDocument e2) {
                return -1 * Longs.compare(e1.getSizeInKB(), e2.getSizeInKB());
            }
        });

        viewer.addColumn(new VariableHeaderColumn<ResultDocument>(Msg.filename.get(), Msg.sender.get()) {
            protected String getLabel(ResultDocument element) {
                if (element.isEmail())
                    return element.getSender();
                return element.getFilename();
            }

            protected int compare(ResultDocument e1, ResultDocument e2) {
                return compareAlphanum(getLabel(e1), getLabel(e2));
            }
        });

        viewer.addColumn(new Column<ResultDocument>(Msg.type.get()) {
            protected String getLabel(ResultDocument element) {
                return element.getType();
            }

            protected int compare(ResultDocument e1, ResultDocument e2) {
                return compareAlphanum(e1.getType(), e2.getType());
            }
        });

        viewer.addColumn(new Column<ResultDocument>(Msg.path.get()) {
            protected String getLabel(ResultDocument element) {
                return element.getPath().getPath();
            }

            protected int compare(ResultDocument e1, ResultDocument e2) {
                return compareAlphanum(getLabel(e1), getLabel(e2));
            }
        });

        viewer.addColumn(new VariableHeaderColumn<ResultDocument>(Msg.authors.get(), Msg.sender.get()) {
            protected String getLabel(ResultDocument element) {
                return element.getAuthors();
            }

            protected int compare(ResultDocument e1, ResultDocument e2) {
                return compareAlphanum(e1.getAuthors(), e2.getAuthors());
            }
        });

        viewer.addColumn(new VariableHeaderColumn<ResultDocument>(Msg.last_modified.get(), Msg.send_date.get()) {
            protected String getLabel(ResultDocument element) {
                Date date = getDate(element);
                return date == null ? "" : dateFormat.format(date);
            }

            protected int compare(ResultDocument e1, ResultDocument e2) {
                Date date1 = getDate(e1);
                Date date2 = getDate(e2);
                if (date1 == null) // Place null dates before non-null dates
                    return date2 == null ? 0 : -1;
                else if (date2 == null)
                    return 1;
                return date1.compareTo(date2);
            }

            @Nullable
            private Date getDate(ResultDocument element) {
                if (element.isEmail())
                    return element.getDate();
                return element.getLastModified();
            }
        });

        SettingsConf.ColumnWidths.ResultPanel.bind(table);
        SettingsConf.ColumnOrder.ResultPanelColumnOrder.bind(table);
    }

    private void launchSelection() {
        List<ResultDocument> selection = viewer.getSelection();
        if (selection.isEmpty())
            return;
        ResultDocument doc = selection.get(0);
        if (!doc.isEmail())
            launchFiles(Collections.singletonList(doc));
    }

    private static int compareAlphanum(@NotNull String s1, @NotNull String s2) {
        return AlphanumComparator.ignoreCaseInstance.compare(s1, s2);
    }

    private void initContextMenu() {
        ContextMenuManager menuManager = new ContextMenuManager(viewer.getControl());

        menuManager.add(new MenuAction(Msg.open.get()) {
            public boolean isEnabled() {
                List<ResultDocument> sel = viewer.getSelection();
                if (sel.isEmpty())
                    return false;
                for (ResultDocument doc : sel)
                    if (doc.isEmail())
                        return false;
                return true;
            }

            public void run() {
                launchFiles(viewer.getSelection());
            }

            public boolean isDefaultItem() {
                return true;
            }
        });

        menuManager.add(new MenuAction(Msg.open_parent.get()) {
            public boolean isEnabled() {
                return !viewer.getSelection().isEmpty();
            }

            public void run() {
                MultiFileLauncher launcher = new MultiFileLauncher();
                for (ResultDocument doc : viewer.getSelection()) {
                    Path path = doc.getPath();
                    try {
                        launcher.addFile(getParent(path));
                    } catch (FileNotFoundException e) {
                        launcher.addMissing(path.getCanonicalPath());
                    }
                }
                if (launcher.launch() && SettingsConf.Bool.HideOnOpen.get())
                    evtHideInSystemTray.fire(null);
            }

            @NotNull
            private File getParent(@NotNull Path path) throws FileNotFoundException {
                /*
                 * The possible cases:
                 * - Path points to an ordinary file
                 * - Path points to an archive entry
                 * - Path points to an item in a PST file
                 * 
                 * In each case, the target may or may not exist.
                 */
                PathParts pathParts = path.splitAtExistingFile();

                if (pathParts.getRight().isEmpty()) // Existing ordinary file
                    return Util.getParentFile(path.getCanonicalFile());

                File leftFile = pathParts.getLeft().getCanonicalFile();
                if (leftFile.isDirectory())
                    // File, archive entry or PST item does not exist
                    throw new FileNotFoundException();

                // Existing PST item
                if (Util.hasExtension(pathParts.getLeft().getName(), "pst"))
                    return Util.getParentFile(leftFile);

                // Existing archive entry -> return the archive
                return leftFile;
            }
        });

        menuManager.addSeparator();

        menuManager.add(new MenuAction(Msg.copy.get()) {
            public boolean isEnabled() {
                return !viewer.getSelection().isEmpty();
            }

            public void run() {
                copyToClipboard();
            }
        });
    }

    private void copyToClipboard() {
        List<ResultDocument> docs = getSelection();
        if (docs.isEmpty())
            return;
        List<File> files = new ArrayList<File>(docs.size());
        for (ResultDocument doc : docs)
            files.add(doc.getPath().getCanonicalFile());
        Util.setClipboard(files);
    }

    @NotNull
    public Table getControl() {
        return viewer.getControl();
    }

    public int getItemCount() {
        return viewer.getControl().getItemCount();
    }

    @MutableCopy
    @NotNull
    public List<ResultDocument> getSelection() {
        return viewer.getSelection();
    }

    // header mode: auto-detect for "files + emails", no auto-detect for files and emails mode
    public void setResults(@NotNull List<ResultDocument> results, @NotNull HeaderMode headerMode) {
        Util.checkNotNull(results, headerMode);

        if (this.presetHeaderMode != headerMode) {
            if (headerMode != HeaderMode.FILES_AND_EMAILS)
                updateColumnHeaders(headerMode);
            this.presetHeaderMode = headerMode;
        }
        setActualHeaderMode(results); // TODO post-release-1.1: needs some refactoring

        viewer.setRoot(results);
        viewer.scrollToTop();
    }

    private void setActualHeaderMode(List<ResultDocument> elements) {
        if (presetHeaderMode != HeaderMode.FILES_AND_EMAILS) {
            actualHeaderMode = presetHeaderMode;
            return;
        }
        boolean filesFound = false;
        boolean emailsFound = false;
        for (ResultDocument element : elements) {
            if (element.isEmail())
                emailsFound = true;
            else
                filesFound = true;
        }
        actualHeaderMode = HeaderMode.getInstance(filesFound, emailsFound);
        updateColumnHeaders(actualHeaderMode);
    }

    private void updateColumnHeaders(HeaderMode headerMode) {
        for (Column<ResultDocument> column : viewer.getColumns()) {
            if (!(column instanceof VariableHeaderColumn))
                continue;
            headerMode.setLabel((VariableHeaderColumn<?>) column);
        }
    }

    // sign of given index specifies direction of sorting
    // zero and out-of-range values will be ignored
    // column numbering starts at 1
    // the index points at the column in visual order, not in creation order
    public void sortByColumn(int columnIndex) {
        if (columnIndex == 0)
            return;
        /*
         * Note: The column to sort by must be specified as an index, since the
         * column names may change.
         */
        try {
            int index = Math.abs(columnIndex) - 1;
            List<Column<ResultDocument>> columns = viewer.getColumnsVisualOrder();
            if (index >= columns.size())
                return;
            boolean up = Math.signum(columnIndex) > 0;
            viewer.sortByColumn(columns.get(index), up);
        } catch (NumberFormatException e) {
            return;
        }
    }

    // Should not be called with emails
    private void launchFiles(@NotNull List<ResultDocument> docs) {
        assert !docs.isEmpty();
        MultiFileLauncher launcher = new MultiFileLauncher();
        Set<FileResource> resources = new HashSet<FileResource>();
        try {
            for (ResultDocument doc : docs) {
                try {
                    FileResource fileResource = doc.getFileResource();
                    resources.add(fileResource);
                    launcher.addFile(fileResource.getFile());
                } catch (FileNotFoundException e) {
                    launcher.addMissing(doc.getPath().getCanonicalPath());
                } catch (ParseException e) {
                    AppUtil.showError(e.getMessage(), true, false);
                    return;
                }
            }
            if (launcher.launch() && SettingsConf.Bool.HideOnOpen.get())
                evtHideInSystemTray.fire(null);
        } finally {
            for (FileResource fileResource : resources)
                fileResource.dispose();
        }
    }

    private static abstract class VariableHeaderColumn<T> extends Column<T> {
        private final String fileHeader;
        private final String emailHeader;
        private final String combinedHeader;

        public VariableHeaderColumn(@NotNull String fileHeader, @NotNull String emailHeader) {
            super(fileHeader);
            Util.checkNotNull(fileHeader, emailHeader);
            this.fileHeader = fileHeader;
            this.emailHeader = emailHeader;
            combinedHeader = fileHeader + " / " + emailHeader;
        }
    }

}