org.eclipse.jface.dialogs.TrayDialog.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.jface.dialogs.TrayDialog.java

Source

/*******************************************************************************
 * Copyright (c) 2005, 2018 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.jface.dialogs;

import static org.eclipse.swt.events.SelectionListener.widgetSelectedAdapter;

import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.window.IShellProvider;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ControlAdapter;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.graphics.Image;
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.Layout;
import org.eclipse.swt.widgets.Link;
import org.eclipse.swt.widgets.Sash;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.swt.widgets.ToolItem;

/**
 * A <code>TrayDialog</code> is a specialized <code>Dialog</code> that can contain
 * a tray on its side. The tray's content is provided as a <code>DialogTray</code>.
 * <p>
 * It is recommended to subclass this class instead of <code>Dialog</code> in all
 * cases except where the dialog should never show a tray. For example, dialogs
 * which are very short, simple, and quick to dismiss (e.g. a message dialog with
 * an OK button) should subclass <code>Dialog</code>.
 * </p>
 * <p>
 * Note: Trays are not supported on dialogs that use a custom layout on the <code>
 * Shell</code> by overriding <code>Window#getLayout()</code>.
 * </p>
 *
 * @see org.eclipse.jface.dialogs.DialogTray
 * @see org.eclipse.jface.window.Window#getLayout()
 * @since 3.2
 */
public abstract class TrayDialog extends Dialog {

    private final class ResizeListener extends ControlAdapter {

        private final GridData data;
        private final Shell shell;
        private final int TRAY_RATIO = 100; // Percentage of extra width devoted to tray when resizing
        private int remainder = 0; // Used to prevent rounding errors from accumulating

        private ResizeListener(GridData data, Shell shell) {
            this.data = data;
            this.shell = shell;
        }

        @Override
        public void controlResized(ControlEvent event) {
            int newWidth = shell.getSize().x;
            if (newWidth != shellWidth) {
                int shellWidthIncrease = newWidth - shellWidth;
                int trayWidthIncreaseTimes100 = (shellWidthIncrease * TRAY_RATIO) + remainder;
                int trayWidthIncrease = trayWidthIncreaseTimes100 / 100;
                remainder = trayWidthIncreaseTimes100 - (100 * trayWidthIncrease);
                data.widthHint = data.widthHint + trayWidthIncrease;
                shellWidth = newWidth;
                if (!shell.isDisposed()) {
                    shell.layout();
                }
            }
        }
    }

    private static boolean dialogHelpAvailable;

    /**
     * The dialog's tray (null if none).
     */
    private DialogTray tray;

    /**
     * The tray's control (null if none).
     */
    private Control trayControl;

    /**
     * The control that had focus before the tray was opened (null if none).
     */
    private Control nonTrayFocusControl;

    /*
     * The separator to the left of the sash.
     */
    private Label leftSeparator;

    /*
     * The separator to the right of the sash.
     */
    private Label rightSeparator;

    /*
     * The sash that allows the user to resize the tray.
     */
    private Sash sash;

    /*
     * Whether or not help is available for this dialog.
     */
    private boolean helpAvailable = isDialogHelpAvailable();

    private int shellWidth;

    private ControlAdapter resizeListener;

    /**
     * The help button (null if none).
     */
    private ToolItem fHelpButton;

    /**
     * Creates a tray dialog instance. Note that the window will have no visual
     * representation (no widgets) until it is told to open.
     *
     * @param shell the parent shell, or <code>null</code> to create a top-level shell
     */
    protected TrayDialog(Shell shell) {
        super(shell);
    }

    /**
     * Creates a tray dialog with the given parent.
     *
     * @param parentShell the object that returns the current parent shell
     */
    protected TrayDialog(IShellProvider parentShell) {
        super(parentShell);
    }

    /**
     * Closes this dialog's tray, disposing its widgets.
     *
     * @throws IllegalStateException if the tray was not open
     */
    public void closeTray() throws IllegalStateException {
        if (getTray() == null) {
            throw new IllegalStateException("Tray was not open"); //$NON-NLS-1$
        }
        Shell shell = getShell();
        Control focusControl = shell.getDisplay().getFocusControl();
        if (focusControl != null && isContained(trayControl, focusControl)) {
            if (nonTrayFocusControl != null && !nonTrayFocusControl.isDisposed()) {
                nonTrayFocusControl.setFocus();
            } else {
                shell.setFocus();
            }
        }
        nonTrayFocusControl = null;
        shell.removeControlListener(resizeListener);
        resizeListener = null;
        int trayWidth = trayControl.getSize().x + leftSeparator.getSize().x + sash.getSize().x
                + rightSeparator.getSize().x;
        trayControl.dispose();
        trayControl = null;
        tray = null;
        leftSeparator.dispose();
        leftSeparator = null;
        rightSeparator.dispose();
        rightSeparator = null;
        sash.dispose();
        sash = null;
        Rectangle bounds = shell.getBounds();
        shell.setBounds(bounds.x + ((getDefaultOrientation() == SWT.RIGHT_TO_LEFT) ? trayWidth : 0), bounds.y,
                bounds.width - trayWidth, bounds.height);
        if (fHelpButton != null) {
            fHelpButton.setSelection(false);
        }
    }

    /**
     * Returns true if the given Control is a direct or indirect child of
     * container.
     *
     * @param container
     *            the potential parent
     * @param control
     * @return boolean <code>true</code> if control is a child of container
     */
    private boolean isContained(Control container, Control control) {
        Composite parent;
        while ((parent = control.getParent()) != null) {
            if (parent == container) {
                return true;
            }
            control = parent;
        }
        return false;
    }

    @Override
    protected void handleShellCloseEvent() {
        /*
         * Close the tray to ensure that those dialogs that remember their
         * size do not store the tray size.
         */
        if (getTray() != null) {
            closeTray();
        }

        super.handleShellCloseEvent();
    }

    @Override
    protected Control createButtonBar(Composite parent) {
        Composite composite = new Composite(parent, SWT.NONE);
        GridLayout layout = new GridLayout();
        layout.marginWidth = 0;
        layout.marginHeight = 0;
        layout.horizontalSpacing = 0;
        composite.setLayout(layout);
        composite.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false));
        composite.setFont(parent.getFont());

        // create help control if needed
        if (isHelpAvailable()) {
            Control helpControl = createHelpControl(composite);
            ((GridData) helpControl.getLayoutData()).horizontalIndent = convertHorizontalDLUsToPixels(
                    IDialogConstants.HORIZONTAL_MARGIN);
        }
        Control buttonSection = super.createButtonBar(composite);
        ((GridData) buttonSection.getLayoutData()).grabExcessHorizontalSpace = true;
        return composite;
    }

    /**
     * Creates a new help control that provides access to context help.
     * <p>
     * The <code>TrayDialog</code> implementation of this method creates
     * the control, registers it for selection events including selection,
     * Note that the parent's layout is assumed to be a <code>GridLayout</code>
     * and the number of columns in this layout is incremented. Subclasses may
     * override.
     * </p>
     *
     * @param parent the parent composite
     * @return the help control
     */
    protected Control createHelpControl(Composite parent) {
        Image helpImage = JFaceResources.getImage(DLG_IMG_HELP);
        if (helpImage != null) {
            return createHelpImageButton(parent, helpImage);
        }
        return createHelpLink(parent);
    }

    /*
     * Creates a button with a help image. This is only used if there
     * is an image available.
     */
    private ToolBar createHelpImageButton(Composite parent, Image image) {
        ToolBar toolBar = new ToolBar(parent, SWT.FLAT | SWT.NO_FOCUS);
        ((GridLayout) parent.getLayout()).numColumns++;
        toolBar.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_CENTER));
        toolBar.setCursor(parent.getDisplay().getSystemCursor(SWT.CURSOR_HAND));
        fHelpButton = new ToolItem(toolBar, SWT.CHECK);
        fHelpButton.setImage(image);
        fHelpButton.setToolTipText(JFaceResources.getString("helpToolTip")); //$NON-NLS-1$
        fHelpButton.addSelectionListener(widgetSelectedAdapter(e -> helpPressed()));
        return toolBar;
    }

    /*
     * Creates a help link. This is used when there is no help image
     * available.
     */
    private Link createHelpLink(Composite parent) {
        Link link = new Link(parent, SWT.WRAP | SWT.NO_FOCUS);
        ((GridLayout) parent.getLayout()).numColumns++;
        link.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_CENTER));
        link.setText("<a>" + IDialogConstants.HELP_LABEL + "</a>"); //$NON-NLS-1$ //$NON-NLS-2$
        link.setToolTipText(IDialogConstants.HELP_LABEL);
        link.addSelectionListener(widgetSelectedAdapter(e -> helpPressed()));
        return link;
    }

    /*
     * Returns whether or not the given layout can support the addition of a tray.
     */
    private boolean isCompatibleLayout(Layout layout) {
        if (layout != null && layout instanceof GridLayout) {
            GridLayout grid = (GridLayout) layout;
            return !grid.makeColumnsEqualWidth && (grid.horizontalSpacing == 0) && (grid.marginWidth == 0)
                    && (grid.marginHeight == 0) && (grid.numColumns == 5);
        }
        return false;
    }

    /**
     * Returns whether or not context help is available for this dialog. This
     * can affect whether or not the dialog will display additional help
     * mechanisms such as a help control in the button bar.
     *
     * @return whether or not context help is available for this dialog
     */
    public boolean isHelpAvailable() {
        return helpAvailable;
    }

    /**
     * The tray dialog's default layout is a modified version of the default
     * <code>Window</code> layout that can accomodate a tray, however it still
     * conforms to the description of the <code>Window</code> default layout.
     * <p>
     * Note: Trays may not be supported with all custom layouts on the dialog's
     * Shell. To avoid problems, use a single outer <code>Composite</code> for
     * your dialog area, and set your custom layout on that <code>Composite</code>.
     * </p>
     *
     * @see org.eclipse.jface.window.Window#getLayout()
     * @return a newly created layout or <code>null</code> for no layout
     */
    @Override
    protected Layout getLayout() {
        GridLayout layout = (GridLayout) super.getLayout();
        layout.numColumns = 5;
        layout.horizontalSpacing = 0;
        return layout;
    }

    /**
     * Returns the tray currently shown in the dialog, or <code>null</code>
     * if there is no tray.
     *
     * @return the dialog's current tray, or <code>null</code> if there is none
     */
    public DialogTray getTray() {
        return tray;
    }

    /*
     * Called when the help control is invoked. This emulates the keyboard
     * context help behavior (e.g. F1 on Windows). It traverses the widget
     * tree upward until it finds a widget that has a help listener on it,
     * then invokes a help event on that widget.
     * If the help tray is already open, it closes it and doesn't invoke
     * any help listener.
     */
    private void helpPressed() {
        if (fHelpButton != null && fHelpButton.getSelection()) {
            DialogTray tray = getTray();
            if (tray != null) {
                // help is selected, but another tray is already visible. Close it now so that
                // help can be displayed.
                closeTray();
            }
        }
        if (tray == null || fHelpButton != null && fHelpButton.getSelection()) { // help button was not selected before
            if (getShell() != null) {
                Control c = getShell().getDisplay().getFocusControl();
                while (c != null) {
                    if (c.isListening(SWT.Help)) {
                        c.notifyListeners(SWT.Help, new Event());
                        break;
                    }
                    c = c.getParent();
                }
                if (fHelpButton != null) {
                    if (getTray() != null)
                        fHelpButton.setSelection(true);
                    else
                        fHelpButton.setSelection(false); // tray with help content was not created, disabled selection
                }
            }
        } else {
            closeTray();
        }
    }

    /**
     * Constructs the tray's widgets and displays the tray in this dialog. The
     * dialog's size will be adjusted to accommodate the tray.
     *
     * @param tray the tray to show in this dialog
     * @throws IllegalStateException if the dialog already has a tray open
     * @throws UnsupportedOperationException if the dialog does not support trays,
     *            for example if it uses a custom layout.
     */
    public void openTray(DialogTray tray) throws IllegalStateException, UnsupportedOperationException {
        if (tray == null) {
            throw new NullPointerException("Tray was null"); //$NON-NLS-1$
        }
        if (getTray() != null) {
            throw new IllegalStateException("Tray was already open"); //$NON-NLS-1$
        }
        if (!isCompatibleLayout(getShell().getLayout())) {
            throw new UnsupportedOperationException("Trays not supported with custom layouts"); //$NON-NLS-1$
        }
        final Shell shell = getShell();
        Control focusControl = shell.getDisplay().getFocusControl();
        if (focusControl != null && isContained(shell, focusControl)) {
            nonTrayFocusControl = focusControl;
        }
        leftSeparator = new Label(shell, SWT.SEPARATOR | SWT.VERTICAL);
        leftSeparator.setLayoutData(new GridData(GridData.FILL_VERTICAL));
        sash = new Sash(shell, SWT.VERTICAL);
        sash.setLayoutData(new GridData(GridData.FILL_VERTICAL));
        rightSeparator = new Label(shell, SWT.SEPARATOR | SWT.VERTICAL);
        rightSeparator.setLayoutData(new GridData(GridData.FILL_VERTICAL));
        trayControl = tray.createContents(shell);
        Rectangle clientArea = shell.getClientArea();
        final GridData data = new GridData(GridData.FILL_VERTICAL);
        data.widthHint = trayControl.computeSize(SWT.DEFAULT, clientArea.height).x;
        trayControl.setLayoutData(data);
        int trayWidth = leftSeparator.computeSize(SWT.DEFAULT, clientArea.height).x
                + sash.computeSize(SWT.DEFAULT, clientArea.height).x
                + rightSeparator.computeSize(SWT.DEFAULT, clientArea.height).x + data.widthHint;
        Rectangle bounds = shell.getBounds();
        shell.setBounds(bounds.x - ((getDefaultOrientation() == SWT.RIGHT_TO_LEFT) ? trayWidth : 0), bounds.y,
                bounds.width + trayWidth, bounds.height);
        sash.addListener(SWT.Selection, event -> {
            if (event.detail != SWT.DRAG) {
                Rectangle clientArea1 = shell.getClientArea();
                int newWidth = clientArea1.width - event.x - (sash.getSize().x + rightSeparator.getSize().x);
                if (newWidth != data.widthHint) {
                    data.widthHint = newWidth;
                    shell.layout();
                }
            }
        });
        shellWidth = shell.getSize().x;

        resizeListener = new ResizeListener(data, shell);
        shell.addControlListener(resizeListener);

        this.tray = tray;
    }

    /**
     * Sets whether or not context help is available for this dialog. This
     * can affect whether or not the dialog will display additional help
     * mechanisms such as a help control in the button bar.
     *
     * @param helpAvailable whether or not context help is available for the dialog
     */
    public void setHelpAvailable(boolean helpAvailable) {
        this.helpAvailable = helpAvailable;
    }

    /**
     * Tests if dialogs that have help control should show it
     * all the time or only when explicitly requested for
     * each dialog instance.
     *
     * @return <code>true</code> if dialogs that support help
     * control should show it by default, <code>false</code> otherwise.
     * @since 3.2
     */
    public static boolean isDialogHelpAvailable() {
        return dialogHelpAvailable;
    }

    /**
     * Sets whether JFace dialogs that support help control should
     * show the control by default. If set to <code>false</code>,
     * help control can still be shown on a per-dialog basis.
     *
     * @param helpAvailable <code>true</code> to show the help
     * control, <code>false</code> otherwise.
     * @since 3.2
     */
    public static void setDialogHelpAvailable(boolean helpAvailable) {
        dialogHelpAvailable = helpAvailable;
    }
}