com.mulgasoft.emacsplus.execute.SelectionDialog.java Source code

Java tutorial

Introduction

Here is the source code for com.mulgasoft.emacsplus.execute.SelectionDialog.java

Source

/**
 * Copyright (c) 2009, 2010 Mark Feber, MulgaSoft
 *
 * 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
 *
 */
package com.mulgasoft.emacsplus.execute;

import java.util.SortedMap;

import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.PopupDialog;
import org.eclipse.jface.layout.TableColumnLayout;
import org.eclipse.jface.viewers.ColumnWeightData;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseTrackListener;
import org.eclipse.swt.graphics.Color;
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.widgets.Composite;
import org.eclipse.swt.widgets.Control;
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.contexts.IContextService;
import org.eclipse.ui.texteditor.ITextEditor;

// with help from org.eclipse.ui.internal.keys.KeyAssistDialog;
/**
 * @author Mark Feber - initial API and implementation
 */
public class SelectionDialog extends PopupDialog {

    // TODO: there should be a way to determine this value
    private static int SIZE_ADJUST = 15;
    private static int MIN_COLUMNS = 3;

    private Point sizeHint;
    private boolean clicked = false;
    private boolean mouseIn = false;

    protected ITextEditor editor;
    protected ISelectExecute minibuffer;

    private SortedMap<String, ?> selectables = null;
    protected Shell tip = null;

    protected void setSelectables(SortedMap<String, ?> selectables) {
        this.selectables = selectables;
    }

    protected SortedMap<String, ?> getSelectables() {
        return selectables;
    }

    protected boolean hasSelectables() {
        return selectables != null && selectables.size() > 0;
    }

    protected void clearSelectables() {
        selectables = null;
    }

    protected String[] getSelectableKeys() {
        return ((selectables != null) ? selectables.keySet().toArray(new String[0]) : null);
    }

    //      super(parent, shellStyle, takeFocusOnOpen, persistSize, persistLocation, showDialogMenu, showPersistActions, titleText, infoText);
    @SuppressWarnings("deprecation") // backward compatibility
    public SelectionDialog(Shell parent, ISelectExecute mini, ITextEditor editor) {
        // Europa compatible constructor
        super((Shell) null, PopupDialog.HOVER_SHELLSTYLE, false, false, false, false, null, null);
        this.editor = editor;
        this.minibuffer = mini;
    }

    public void shutdown() {
        this.editor = null;
        this.minibuffer = null;
        clearSelectables();
        close();
    }

    // TODO rework..as super.open seems to repeat the create!
    public final int open(SortedMap<String, ?> selectables) {

        setSelectables(selectables);

        Shell shell = getShell();
        if (shell != null) {
            close();
        }
        sizeHint = configureSize();
        create();
        // Configure the size and location.
        configureLocation(sizeHint);
        // and open the dialog
        return super.open();
    }

    @Override
    public boolean close() {
        // make sure we're clean
        disposeTip();
        return super.close();
    }

    public boolean mouseIn() {
        return mouseIn;
    }

    private void disposeTip() {
        if (tip != null && !tip.isDisposed()) {
            tip.dispose();
            tip = null;
        }
    }

    /**
     * behavior on mouse click
     */
    protected void execute(String key) {
        minibuffer.execute(key);
    }

    protected int getSizeAdjustment() {
        return SIZE_ADJUST;
    }

    /**
     * Sets the size for the dialog.
     *  
     * The width is slightly less the workbench window's width. 
     * The dialog's height is the packed height of the dialog up to a 
     * maximum of half the height of the workbench window.
     * 
     * @return The size of the dialog
     */
    private final Point configureSize() {

        int maxW = 0;
        int maxH = 0;
        // Enforce maximum sizing.
        Shell workbenchWindowShell = editor.getEditorSite().getShell();
        if (workbenchWindowShell != null) {
            Point workbenchWindowSize = workbenchWindowShell.getSize();
            // TODO: just a guess for now.  I'd like to determine the current borders and subtract them
            //         int maxW = workbenchWindowSize.x -25;
            maxW = workbenchWindowShell.getClientArea().width - getSizeAdjustment();
            maxH = workbenchWindowSize.y / 2;

        }
        return new Point(maxW, maxH);
    }

    /**
     * Sets the position for the dialog based on the position of the workbench
     * window. The dialog is flush with the bottom right corner of the workbench
     * window. However, the dialog will not appear outside of the display's
     * client area.
     * 
     * @param size
     *            The final size of the dialog; must not be <code>null</code>.
     */
    private final void configureLocation(final Point size) {
        final Shell shell = getShell();

        final Shell workbenchWindowShell = editor.getEditorSite().getShell();
        final int xCoord;
        final int yCoord;
        if (workbenchWindowShell != null) {
            /*
             * Position the shell at the bottom right corner of the workbench
             * window
             */
            // TODO: The constants are just guesses
            final Rectangle workbenchWindowBounds = workbenchWindowShell.getBounds();
            xCoord = workbenchWindowBounds.x + workbenchWindowBounds.width - size.x - 10;
            yCoord = workbenchWindowBounds.y + workbenchWindowBounds.height - size.y - 35;

        } else {
            xCoord = 0;
            yCoord = 0;

        }
        final Rectangle bounds = new Rectangle(xCoord, yCoord, size.x, size.y);
        shell.setBounds(getConstrainedShellBounds(bounds));
    }

    /**
    * 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) {
        // First, register the shell type with the context support
        registerShellType();

        // Create a composite for the dialog area.
        final Composite composite = new Composite(parent, SWT.NONE);
        composite.setBackground(parent.getBackground());
        createTableDialogArea(composite);

        return composite;
    }

    private final void createTableDialogArea(final Composite parent) {

        String[] inputKeys = getSelectableKeys();
        int columnCount = 0;
        Point dimens = getColumnCount(parent, inputKeys, sizeHint.x);
        int count = dimens.x;

        GridLayout compositeLayout = new GridLayout(count, true);
        parent.setLayout(compositeLayout);
        parent.setLayoutData(new GridData(GridData.FILL_BOTH));

        Table table = new Table(parent, SWT.V_SCROLL | SWT.HORIZONTAL | SWT.WRAP | SWT.FULL_SELECTION); //| SWT.MULTI);
        GridData gridData = new GridData(GridData.FILL_BOTH);
        table.setLayoutData(gridData);
        table.setBackground(parent.getBackground());
        table.setLinesVisible(true);
        table.setHeaderVisible(false);

        int columnWidth = (sizeHint.x - getSizeAdjustment()) / count;

        TableColumn[] columns = new TableColumn[count];
        for (int i = 0; i < count; i++) {
            columns[i] = new TableColumn(table, SWT.LEFT, columnCount++);
            columns[i].setWidth(columnWidth);
        }
        TableColumnLayout layout = new TableColumnLayout();
        for (int i = 0; i < count; i++) {
            layout.setColumnData(columns[i], new ColumnWeightData(100 / count, columnWidth, false));
        }
        parent.setLayout(layout);

        int len = inputKeys.length;
        int rowCount = len / columnCount;
        if ((len - rowCount * columnCount) > 0) {
            rowCount++;
        }
        for (int i = 0; i < rowCount; i++) {
            String[] row = new String[columnCount];
            for (int j = 0; j < columnCount; j++) {
                int sourceIndex = i * columnCount + j;
                row[j] = (sourceIndex < len ? (String) inputKeys[sourceIndex] : ""); //$NON-NLS-1$ 
            }
            TableItem item = new TableItem(table, SWT.NULL);
            item.setText(row);
        }

        table.pack();
        sizeHint.y = Math.min(table.getBounds().height + getSizeAdjustment(), sizeHint.y);

        Dialog.applyDialogFont(parent);
        addTableListeners(table);
    }

    /**
     * Determine the appropriate number of columns.  If the number of items is less than
     * the computed number, choose the smaller.
     * 
     * @param parent
     * @param vals
     * @param width
     * @return
     */
    private Point getColumnCount(Composite parent, Object[] vals, int width) {
        Point result = new Point(0, 0);
        int iWidth = MIN_COLUMNS;
        int len = vals.length;
        int size = 0;
        Label l = null;
        try {
            l = new Label(parent, SWT.NULL);
            for (int i = 0; i < len; i++) {
                l.setText((String) vals[i]);
                size = Math.max(size, l.computeSize(SWT.DEFAULT, SWT.DEFAULT).x);
            }
            result = l.getSize();
            l.dispose();

            result.x = Math.max(iWidth, width / (size + 5));
            result.x = Math.min(result.x, vals.length);
        } catch (Exception e) {
            result = new Point(0, 0);
        }
        return result;
    }

    // Listeners

    class ItemPkg {
        int index = 0;
        TableItem item = null;

        ItemPkg(TableItem item, int index) {
            this.item = item;
            this.index = index;
        }

        String getText() {
            return item.getText(index);
        }

        Rectangle getBounds() {
            return item.getBounds(index);
        }
    }

    private ItemPkg getCell(Event event, Table table) {
        ItemPkg result = null;
        TableItem item = table.getItem(new Point(event.x, event.y));

        if (item != null) {
            for (int i = 0; i < table.getColumnCount(); i++) {
                if (item.getBounds(i).contains(event.x, event.y)) {
                    result = new ItemPkg(item, i);
                    break;
                }
            }
        }
        return result;
    }

    private String getCellText(Event event, Table table) {
        String result = null;
        ItemPkg item = getCell(event, table);
        if (item != null) {
            result = item.getText();
        }
        return result;
    }

    private ItemPkg getCell(MouseEvent event, Table table) {
        ItemPkg result = null;
        TableItem item = table.getItem(new Point(event.x, event.y));

        if (item != null) {
            for (int i = 0; i < table.getColumnCount(); i++) {
                if (item.getBounds(i).contains(event.x, event.y)) {
                    result = new ItemPkg(item, i);
                    break;
                }
            }
        }
        return result;
    }

    protected boolean hasToolTips() {
        return false;
    }

    void showTip(String txt, ItemPkg tp, Table table) {
    }

    private void addTableListeners(final Table table) {

        final Color fc = table.getForeground();
        final Color bc = table.getBackground();

        if (hasToolTips()) {
            addToolTipListener(table);
        }

        // We need to listen to enter/exit so we know on
        // minibuffer focusLost whether the focus was 
        // lost to the selection dialog, or some other part
        table.addListener(SWT.MouseEnter, new Listener() {

            public void handleEvent(Event event) {
                mouseIn = true;
            }
        });

        table.addListener(SWT.MouseExit, new Listener() {

            public void handleEvent(Event event) {
                mouseIn = false;
            }
        });

        table.addListener(SWT.MouseDown, new Listener() {

            public void handleEvent(Event event) {
                clicked = true;
            }
        });

        table.addListener(SWT.MouseDoubleClick, new Listener() {

            public void handleEvent(Event event) {
                String t = getCellText(event, table);
                if (t != null) {
                    disposeTip();
                    execute(t);
                }
            }
        });

        // TODO: Can we do better?
        // Disable lame eclipse selection behavior
        table.addListener(SWT.EraseItem, new Listener() {
            public void handleEvent(Event event) {
                if ((event.detail & SWT.SELECTED) != 0) {
                    //                  Color ofc = event.gc.getForeground();
                    Color obc = event.gc.getBackground();
                    event.gc.setForeground(fc);
                    event.gc.setBackground(bc);
                    TableItem item = (TableItem) event.item;
                    //                  Rectangle r = item.getBounds(event.index);
                    event.gc.setBackground(item.getBackground(event.index));
                    event.gc.setForeground(item.getForeground(event.index));
                    //                     event.gc.drawText(" ", r.x, r.y, false);
                    //                     event.gc.drawText(item.getText(), r.x, r.y, false);

                    //                   event.gc.drawString(event.text, event.x, event.y);
                    event.detail &= ~SWT.SELECTED;
                    event.detail &= ~SWT.BACKGROUND;
                    event.detail &= ~SWT.FOCUSED;
                    event.detail &= ~SWT.HOT;
                    //                     event.doit = false;
                    //                     event.gc.setForeground(ofc);
                    event.gc.setBackground(obc);
                }
            }
        });
    }

    /**
     * If sub class support tool tips, than add generic tip handling
     * 
     * @param table
     */
    private void addToolTipListener(final Table table) {
        table.addMouseTrackListener(new MouseTrackListener() {

            public void mouseEnter(MouseEvent e) {
                disposeTip();
            }

            public void mouseExit(MouseEvent e) {
                disposeTip();
                if (clicked) {
                    close();
                }
            }

            public void mouseHover(MouseEvent e) {
                ItemPkg tp = getCell(e, table);
                if (tp != null) {
                    String t = tp.getText();
                    disposeTip();
                    if (t != null) {
                        showTip(t, tp, table);
                    }
                }
            }
        });
    }

    // TODO: What does it all mean Mr. Natural?
    /**
     * Registers the shell as the same type as its parent with the context
     * support. This ensures that it does not modify the current state of the
     * application.
     */
    private final void registerShellType() {
        final Shell shell = getShell();

        final IContextService contextService = (IContextService) editor.getEditorSite().getWorkbenchWindow()
                .getWorkbench().getService(IContextService.class);
        contextService.registerShell(shell, contextService.getShellType((Shell) shell.getParent()));
    }

}