com.aptana.ui.QuickMenuDialog.java Source code

Java tutorial

Introduction

Here is the source code for com.aptana.ui.QuickMenuDialog.java

Source

/**
 * Aptana Studio
 * Copyright (c) 2005-2011 by Appcelerator, Inc. All Rights Reserved.
 * Licensed under the terms of the GNU Public License (GPL) v3 (with exceptions).
 * Please see the license.html included with this distribution for details.
 * Any modifications to this file must keep this entire header intact.
 */
package com.aptana.ui;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.PopupDialog;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.TableEditor;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.events.TraverseEvent;
import org.eclipse.swt.events.TraverseListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
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.ui.progress.UIJob;

import com.aptana.core.util.StringUtil;

/**
 * Pop up a quick menu. useful for commands when we want users to be able to select a value without having to take their
 * hands off the keyboard. Much better UI than a dialog with a combo!
 * 
 * @author cwilliams
 */
public class QuickMenuDialog extends PopupDialog {

    /**
     * Keys used to set internal values in TableItems data map.
     */
    private static final String MNEMONIC = "mnemonic"; //$NON-NLS-1$
    private static final String INDEX = "index"; //$NON-NLS-1$
    private static final String IS_SEPARATOR = "isSeparator"; //$NON-NLS-1$

    private static final String MNEMONICS = "123456789"; //$NON-NLS-1$

    private Table fTable;
    private List<MenuDialogItem> menuItems = new ArrayList<MenuDialogItem>();

    public QuickMenuDialog(Shell parent) {
        super(parent, PopupDialog.INFOPOPUP_SHELLSTYLE, true, false, false, false, false, null, null);
    }

    public void setInput(List<MenuDialogItem> items) {
        this.menuItems = items;
    }

    /**
     * Creates the content area for the key assistant. This creates a table and places it inside the composite. The
     * composite will contain a list of all the key bindings.
     * 
     * @param parent
     *            The parent composite to contain the dialog area; must not be <code>null</code>.
     */
    protected final Control createDialogArea(final Composite parent) {
        // Create a composite for the dialog area.
        final Composite composite = new Composite(parent, SWT.NONE);
        final GridLayout compositeLayout = new GridLayout();
        compositeLayout.marginHeight = 0;
        compositeLayout.marginWidth = 0;
        composite.setLayout(compositeLayout);
        composite.setLayoutData(new GridData(GridData.FILL_BOTH));
        composite.setBackground(parent.getBackground());

        if (menuItems.isEmpty()) {
            createEmptyDialogArea(composite);
        } else {
            createTableDialogArea(composite, menuItems);
        }
        return composite;
    }

    @Override
    protected Point getInitialLocation(Point initialSize) {
        Display display = getShell().getDisplay();
        if (display != null && !display.isDisposed()) {
            return display.getCursorLocation();
        }
        return super.getInitialLocation(initialSize);
    }

    protected Color getBackground() {
        // TODO Use themes for colors?
        return getShell().getDisplay().getSystemColor(SWT.COLOR_WIDGET_BACKGROUND);
    }

    /**
     * Creates an empty dialog area with a simple message saying there were no matches. This is used if no partial
     * matches could be found. This should not really ever happen, but might be possible if the commands are changing
     * while waiting for this dialog to open.
     * 
     * @param parent
     *            The parent composite for the dialog area; must not be <code>null</code>.
     */
    private final void createEmptyDialogArea(final Composite parent) {
        final Label noMatchesLabel = new Label(parent, SWT.NULL);
        noMatchesLabel.setText(Messages.QuickMenuDialog_NoMatchesFound);
        noMatchesLabel.setLayoutData(new GridData(GridData.FILL_BOTH));
        noMatchesLabel.setBackground(parent.getBackground());
    }

    /**
     * Creates a dialog area with a table of the partial matches for the current key binding state. The table will be
     * either the minimum width, or <code>previousWidth</code> if it is not <code>NO_REMEMBERED_WIDTH</code>.
     * 
     * @param parent
     *            The parent composite for the dialog area; must not be <code>null</code>.
     * @param partialMatches
     *            The lexicographically sorted map of partial matches for the current state; must not be
     *            <code>null</code> or empty.
     */
    private final void createTableDialogArea(final Composite parent, final List<MenuDialogItem> partialMatches) {
        // Layout the table.
        fTable = new Table(parent, SWT.FULL_SELECTION | SWT.SINGLE);
        final GridData gridData = new GridData(GridData.FILL_BOTH);
        fTable.setLayoutData(gridData);
        fTable.setBackground(parent.getBackground());
        fTable.setLinesVisible(false);

        List<TableColumn> columns = new ArrayList<TableColumn>();

        // Initialize the columns and rows.
        int mnemonic = 0;
        int index = -1;

        // image, display, insert, tool_tip
        columns.add(new TableColumn(fTable, SWT.LEFT, 0));
        columns.add(new TableColumn(fTable, SWT.LEFT, 1));
        columns.add(new TableColumn(fTable, SWT.CENTER, 2));

        for (MenuDialogItem item : partialMatches) {
            index++;
            if (item.isSeparator()) {
                insertSeparator(3);
                continue;
            }
            final TableItem tableItem = new TableItem(fTable, SWT.NULL);
            Image image = item.getImage();
            if (image != null) {
                tableItem.setImage(0, image);
            }

            tableItem.setText(1, item.getText());
            tableItem.setData(MNEMONIC, mnemonic); // FIXME This is really off by one, but we expect it to be later.
            // Funky code from Sandip. Just use real value maybe?
            tableItem.setText(2, (mnemonic < MNEMONICS.length()) ? String.valueOf(MNEMONICS.charAt(mnemonic++))
                    : StringUtil.EMPTY);
            tableItem.setData(INDEX, index);
        }

        Dialog.applyDialogFont(parent);
        for (TableColumn tableColumn : columns) {
            tableColumn.pack();
        }
        // FIXME Need to limit vertical size of list! If we have 100 items we shouldn't let this dialog grow to take up
        // entire vertical space of screen/IDE!

        /*
         * If you double-click on the table, it should execute the selected command.
         */
        fTable.addListener(SWT.DefaultSelection, new Listener() {
            public final void handleEvent(final Event event) {
                select();
            }
        });

        fTable.addKeyListener(new KeyListener() {
            public void keyReleased(KeyEvent e) {
            }

            public void keyPressed(KeyEvent e) {
                if (!e.doit) {
                    return;
                }
                int index = MNEMONICS.indexOf(e.character);
                if (index != -1) {
                    if (index < fTable.getItemCount()) {
                        e.doit = false;
                        // I need to return the index of the item as it was in partialMatches!
                        int returnCode = index;
                        for (TableItem item : fTable.getItems()) {
                            Object data = item.getData(MNEMONIC);
                            if (data instanceof Integer) {
                                Integer value = (Integer) data;
                                if (value == index) // does mnemonic match?
                                {
                                    // OK We found the table item that was assigned this mnemonic, now we need to find
                                    // it's index in partialMatches!
                                    returnCode = (Integer) item.getData(INDEX);
                                    break;
                                }
                            }
                        }
                        setReturnCode(returnCode);
                        close();
                    }
                }
            }
        });

        // Don't ever draw separators as selected!
        fTable.addListener(SWT.EraseItem, new Listener() {

            public void handleEvent(Event event) {
                if ((event.detail & SWT.SELECTED) != 0) {
                    TableItem item = (TableItem) event.item;
                    if (isSeparator(item)) {
                        event.detail &= ~SWT.SELECTED;
                        event.detail &= ~SWT.BACKGROUND;
                    }
                }
            }
        });

        fTable.addTraverseListener(new TraverseListener() {

            public void keyTraversed(TraverseEvent e) {
                int selectionIndex = fTable.getSelectionIndex();
                final int initialIndex = selectionIndex;
                if (e.detail == SWT.TRAVERSE_ARROW_NEXT) {
                    selectionIndex++;
                    while (isSeparator(fTable.getItem(selectionIndex))) {
                        selectionIndex++;
                        if (selectionIndex >= fTable.getItemCount())
                            return;
                    }
                    selectionIndex--;
                } else if (e.detail == SWT.TRAVERSE_ARROW_PREVIOUS) {
                    selectionIndex--;
                    while (isSeparator(fTable.getItem(selectionIndex))) {
                        selectionIndex--;
                        if (selectionIndex < 0) {
                            // HACK have to run this in a job for some reason. Just setting selection index doesn't
                            // work.
                            new UIJob("retaining selection") //$NON-NLS-1$
                            {

                                @Override
                                public IStatus runInUIThread(IProgressMonitor monitor) {
                                    fTable.setSelection(initialIndex);
                                    return Status.OK_STATUS;
                                }
                            }.schedule();
                            e.doit = false;
                            return;
                        }
                    }
                    selectionIndex++;
                } else {
                    return;
                }

                if (selectionIndex < fTable.getItemCount() && selectionIndex >= 0) {
                    fTable.setSelection(selectionIndex);
                    e.doit = false;
                }
            }
        });
    }

    protected boolean isSeparator(TableItem item) {
        Object obj = item.getData(IS_SEPARATOR);
        if (obj instanceof Boolean) {
            return (Boolean) obj;
        }
        return false;
    }

    protected void insertSeparator(int columns) {
        TableItem item = new TableItem(fTable, SWT.NULL);
        for (int i = 0; i < columns; i++) {
            TableEditor editor = new TableEditor(fTable);
            Label label = new Label(fTable, SWT.SEPARATOR | SWT.HORIZONTAL);
            editor.grabHorizontal = true;
            editor.setEditor(label, item, i);
        }
        item.setData(IS_SEPARATOR, true);
    }

    protected void select() {
        int index = fTable.getSelectionIndex();
        TableItem item = fTable.getItem(index);
        int returnCode = (Integer) item.getData(INDEX);
        setReturnCode(returnCode);
        close();
    }

    @Override
    public int open() {
        setReturnCode(-1); // set return code back to -1
        setBlockOnOpen(true);
        super.open();

        // run the event loop if specified
        runEventLoop(getShell());

        return getReturnCode();
    }

    @Override
    protected void handleShellCloseEvent() {
        super.handleShellCloseEvent();
        setReturnCode(-1);
    }

    /**
     * Runs the event loop for the given shell.
     * 
     * @param loopShell
     *            the shell
     */
    private void runEventLoop(Shell loopShell) {
        // Use the display provided by the shell if possible
        Display display = loopShell.getDisplay();
        while (!loopShell.isDisposed()) {
            try {
                if (!display.readAndDispatch()) {
                    display.sleep();
                }
            } catch (Throwable e) {
                // FIXME Handle exception in some way
                // exceptionHandler.handleException(e);
            }
        }
        if (!display.isDisposed())
            display.update();
    }
}