org.eclipse.sirius.common.ui.tools.api.util.SWTUtil.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.sirius.common.ui.tools.api.util.SWTUtil.java

Source

/*******************************************************************************
 * Copyright (c) 2007, 2009 THALES GLOBAL SERVICES.
 * 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:
 *    Obeo - initial API and implementation
 *******************************************************************************/
package org.eclipse.sirius.common.ui.tools.api.util;

import java.util.Map;

import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.dialogs.MessageDialogWithToggle;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.window.Window;
import org.eclipse.osgi.util.NLS;
import org.eclipse.sirius.common.ui.tools.api.dialog.SiriusMessageDialogWithToggle;
import org.eclipse.sirius.common.ui.tools.internal.util.ISaveDialogExtensionDescriptor;
import org.eclipse.sirius.common.ui.tools.internal.util.ISaveDialogExtensionRegistry;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
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.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.ISaveablePart2;
import org.eclipse.ui.IWorkbenchPreferenceConstants;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.dialogs.FilteredTree;
import org.eclipse.ui.dialogs.PatternFilter;
import org.eclipse.ui.internal.WorkbenchMessages;
import org.eclipse.ui.part.PageBook;

import com.google.common.collect.Maps;

/**
 * Utility class to avoid verbose SWT code.
 * 
 * @author Mariot Chauvin (mchauvin)
 */
public final class SWTUtil {

    private static final String SAVE_CHANGES_QUESTION = "{0} have been modified{1}. Save changes{2}?";

    private static final String OPEN_ELSEWHERE_MESSAGE = ", but are still open elsewhere with identical changes. Closing this will not lose those changes";

    private static final String OPEN_ELSEWHERE_QUESTION = " now anyway";

    /**
     * avoid instantiation
     */
    private SWTUtil() {
    }

    /**
     * utility method to create a {@link Label} widget.
     * 
     * @param parent
     *            the parent widget
     * @param image
     *            the image for the label
     * @param style
     *            see {@link SWT}
     * @return the new label widget
     */
    public static Label createLabel(final Composite parent, final Image image, final int style) {
        final Label label = new Label(parent, style);
        label.setImage(image);
        return label;
    }

    /**
     * utility method to create a {@link Label} widget.
     * 
     * @param parent
     *            the parent widget
     * @param text
     *            the text for the label
     * @param style
     *            see {@link SWT}
     * @return the new label widget
     */
    public static Label createLabel(final Composite parent, final String text, final int style) {
        final Label label = new Label(parent, style);
        label.setText(text);
        return label;
    }

    /**
     * Utility method to create a {@link Label} widget with the SWT.NONE style.
     * 
     * @param parent
     *            the parent widget
     * @param text
     *            the text for the label
     * @return the new label widget
     */
    public static Label createLabel(final Composite parent, final String text) {
        return SWTUtil.createLabel(parent, text, SWT.NONE);
    }

    /**
     * Utility method to create a {@link Label} widget with the SWT.NONE style.
     * 
     * @param parent
     *            the parent widget
     * @param image
     *            the image for the label
     * @return the new label widget
     */
    public static Label createLabel(final Composite parent, final Image image) {
        return SWTUtil.createLabel(parent, image, SWT.NONE);
    }

    /**
     * Utility method to create a {@link PageBook} widget dispose it when the
     * parent will be disposed.
     * 
     * @param parent
     *            the parent widget
     * @param style
     *            see {@link SWT}
     * @return the new pagebook widget
     */
    public static PageBook createPageBook(final Composite parent, final int style) {
        final PageBook pageBook = new PageBook(parent, style);

        // add a dispose listener
        SWTUtil.addDisposeListener(parent, pageBook);

        return pageBook;
    }

    /**
     * Utility method to create a {@link PageBook} widget with the SWT.NONE
     * style and dispose it when the parent will be disposed.
     * 
     * @param parent
     *            the parent widget
     * @return the new pagebook widget
     */
    public static PageBook createPageBook(final Composite parent) {
        return SWTUtil.createPageBook(parent, SWT.NONE);
    }

    /**
     * Utility method to create a {@link FilteredTree} widget and dispose it
     * when the parent will be disposed.
     * 
     * @param parent
     *            the parent widget
     * @param style
     *            see {@link SWT}
     * @param filter
     *            see {@link PatternFilter}
     * @return the new filtered tree widget
     */
    public static FilteredTree createFilteredTree(final Composite parent, final int style,
            final PatternFilter filter) {
        final FilteredTree filteredTree = new FilteredTree(parent, style, filter, true);

        // add a dispose listener
        SWTUtil.addDisposeListener(parent, filteredTree);

        return filteredTree;

    }

    /**
     * Utility method to create a composite widget which fill horizontally and
     * dispose it when the parent will be disposed.
     * 
     * @param parent
     *            the parent widget
     * @param columns
     *            the number of columns in the grid layout for the new
     *            composite.
     * @param equalColumns
     *            make columns equals width
     * @return the new composite widget
     */
    public static Composite createCompositeHorizontalFill(final Composite parent, final int columns,
            final boolean equalColumns) {
        return SWTUtil.createCompositeFill(parent, columns, equalColumns, true, false);
    }

    /**
     * Utility method to create a composite widget which fill vertically and
     * dispose it when the parent will be disposed.
     * 
     * @param parent
     *            the parent widget
     * @param columns
     *            the number of columns in the grid layout for the new
     *            composite.
     * @param equalColumns
     *            make columns equals width
     * @return the new composite widget
     */
    public static Composite createCompositeVerticalFill(final Composite parent, final int columns,
            final boolean equalColumns) {
        return SWTUtil.createCompositeFill(parent, columns, equalColumns, false, true);
    }

    /**
     * Utility method to create a composite widget which fill both horizontally
     * and vertically, and dispose it when the parent will be disposed.
     * 
     * @param parent
     *            the parent widget
     * @param columns
     *            the number of columns in the grid layout for the new
     *            composite.
     * @param equalColumns
     *            make columns equals width
     * @return the new composite widget
     */
    public static Composite createCompositeBothFill(final Composite parent, final int columns,
            final boolean equalColumns) {
        return SWTUtil.createCompositeFill(parent, columns, equalColumns, true, true);
    }

    /**
     * Utility method to create a composite widget and dispose it when the
     * parent will be disposed.
     * 
     * @param parent
     *            the parent widget
     * @param columns
     *            the number of columns in the grid layout for the new
     *            composite.
     * @param equalColumns
     *            make columns equals width
     * @return the new composite widget
     */
    private static Composite createCompositeFill(final Composite parent, final int columns,
            final boolean equalColumns, final boolean horizontalFill, final boolean verticalFill) {
        final Composite composite = new Composite(parent, SWT.NONE);

        final GridLayout gridLayout = new GridLayout();
        gridLayout.numColumns = columns;
        gridLayout.marginHeight = 0;
        gridLayout.marginWidth = 0;
        gridLayout.makeColumnsEqualWidth = equalColumns;

        if (horizontalFill && !verticalFill) {
            composite.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
        } else if (!horizontalFill && verticalFill) {
            composite.setLayoutData(new GridData(SWT.BEGINNING, SWT.FILL, false, true));
        } else if (!horizontalFill && !verticalFill) {
            composite.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false));
        } else {
            composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
        }

        composite.setLayout(gridLayout);

        // add a dispose listener
        SWTUtil.addDisposeListener(parent, composite);

        return composite;
    }

    /**
     * Utility method to create a composite widget and dispose it when the
     * parent will be disposed.
     * 
     * @param parent
     *            the parent widget
     * @param columns
     *            the number of columns in the grid layout for the new
     *            composite.
     * @param equalColumns
     *            make columns equals width
     * @param layoutData
     *            the grid data
     * @return the new composite widget
     */
    public static Composite createComposite(final Composite parent, final int columns, final boolean equalColumns,
            final GridData layoutData) {
        final Composite composite = new Composite(parent, SWT.NONE);

        final GridLayout gridLayout = new GridLayout();
        gridLayout.numColumns = columns;
        gridLayout.marginHeight = 0;
        gridLayout.marginWidth = 0;
        gridLayout.makeColumnsEqualWidth = equalColumns;

        composite.setLayoutData(layoutData);
        composite.setLayout(gridLayout);

        // add a dispose listener
        SWTUtil.addDisposeListener(parent, composite);

        return composite;
    }

    /**
     * Dispose the child when the parent is disposed.
     * 
     * @param parent
     *            the parent composite
     * @param child
     *            the child composite
     */
    public static void addDisposeListener(final Composite parent, final Composite child) {
        parent.addDisposeListener(new DefaultDisposeListener(child));
    }

    /**
     * Show a dialog to ask the user to save or not the content of the Session.
     * The window's return codes can be ISaveablePart2.CANCEL,
     * ISaveablePart2.YES or ISaveablePart2.NO.
     * 
     * @param objectToSave
     *            the object to save
     * @param label
     *            the name of the element to save or any other label
     * @param canCancel
     *            <code>true</code> if the save can be cancel,
     *            <code>false</code> otherwise
     * @return the return code
     */
    public static int showSaveDialog(final Object objectToSave, final String label, final boolean canCancel) {
        return showSaveDialog(objectToSave, label, canCancel, false, false);
    }

    /**
     * Show a dialog to ask the user to save or not the content of the Session.
     * The window's return codes can be ISaveablePart2.CANCEL,
     * ISaveablePart2.YES or ISaveablePart2.NO.
     * 
     * @param objectToSave
     *            the object to save
     * @param label
     *            the name of the element to save or any other label
     * @param canCancel
     *            <code>true</code> if the save can be cancel,
     *            <code>false</code> otherwise
     * @param stillOpenElsewhere
     *            <code>true</code> the object to save is open elsewhere, prompt
     *            if IWorkbenchPreferenceConstants.
     *            PROMPT_WHEN_SAVEABLE_STILL_OPEN, <code>false</code> otherwise
     * @param onlyIfCustomFound
     *            force to return ISaveablePart2.DEFAULT if no
     *            saveDialogExtension was found.
     * 
     * @return the return code
     */
    public static int showSaveDialog(final Object objectToSave, final String label, final boolean canCancel,
            final boolean stillOpenElsewhere, final boolean onlyIfCustomFound) {

        final RunnableWithResult runnable = new RunnableWithResult() {
            private int result = ISaveablePart2.DEFAULT;

            public void run() {
                // Case 1 : an ISaveDialogExtension has been contributed
                boolean customSaveDialogProvided = false;
                for (ISaveDialogExtensionDescriptor saveDialogExtensionDescriptor : ISaveDialogExtensionRegistry
                        .getRegisteredExtensions()) {
                    ISaveDialogExtension saveDialogExtension = saveDialogExtensionDescriptor
                            .getSaveDialogExtension();
                    if (saveDialogExtension.isSaveDialogFor(objectToSave)) {
                        // we use it to build the save dialog
                        result = showProvidedSaveDialog(saveDialogExtension, objectToSave, label, canCancel,
                                stillOpenElsewhere);
                        customSaveDialogProvided = true;
                    }
                }

                // Case 2 : no custom save dialog is provided
                // we build a default save dialog
                if (!customSaveDialogProvided && !onlyIfCustomFound) {
                    result = showStandardSaveDialog(label, canCancel, stillOpenElsewhere);
                }
            }

            public int getResult() {
                return result;
            }
        };

        Display.getDefault().syncExec(runnable);
        return runnable.getResult();
    }

    /**
     * Shows a dialog to ask the user to save or not the content of the Session,
     * using the given saveDialogExtension, that may define new buttons and
     * additional behavior. The window's return codes can be
     * ISaveablePart2.CANCEL, ISaveablePart2.YES or ISaveablePart2.NO.
     * 
     * 
     * @param saveDialogExtension
     *            the {@link ISaveDialogExtension} to use for showing this save
     *            window
     * @param objectToSave
     *            the object to save
     * @param label
     *            the label of the window
     * @param canCancel
     *            indicates whether user can cancel this save action or not
     * @param stillOpenElsewhere
     *            <code>true</code> the object to save is open elsewhere,
     *            <code>false</code> otherwise
     * @return the return code
     */
    private static int showProvidedSaveDialog(ISaveDialogExtension saveDialogExtension, Object objectToSave,
            String label, final boolean canCancel, boolean stillOpenElsewhere) {
        // Step 1: getting the save buttons
        Map<String, Integer> buttons = saveDialogExtension.getButtons(stillOpenElsewhere);

        // Step 2 :opening window
        int temporaryResult = openSaveDialog(label, canCancel, buttons, stillOpenElsewhere);

        // Step 3 : using save contribution to compute additional actions
        // according to the selected choice
        // an
        return saveDialogExtension.reactToValue(objectToSave, temporaryResult, stillOpenElsewhere);
    }

    /**
     * Show a dialog to ask the user to save or not the content of the Session.
     * The window's return codes can be ISaveablePart2.CANCEL,
     * ISaveablePart2.YES or ISaveablePart2.NO.
     * 
     * @param label
     *            the name of the element to save or any other label
     * @param canCancel
     *            <code>true</code> if the save can be cancel,
     *            <code>false</code> otherwise
     * @param stillOpenElsewhere
     *            <code>true</code> the object to save is open elsewhere,
     *            <code>false</code> otherwise
     * @return the return code
     */
    private static int showStandardSaveDialog(final String label, final boolean canCancel,
            boolean stillOpenElsewhere) {
        // Step 1: getting the save buttons
        Map<String, Integer> buttons = Maps.newLinkedHashMap();
        buttons.put(IDialogConstants.YES_LABEL, IDialogConstants.YES_ID);
        buttons.put(IDialogConstants.NO_LABEL, IDialogConstants.NO_ID);

        // Step 2 :opening window
        int temporaryResult = openSaveDialog(label, canCancel, buttons, stillOpenElsewhere);

        return temporaryResult;
    }

    /**
     * @see org.eclipse.internal.SaveablesList#promptForSaving(List,
     *      org.eclipse.jface.window.IShellProvider,
     *      org.eclipse.jface.operation.IRunnableContext, boolean, boolean)
     */
    private static int openSaveDialog(String label, final boolean canCancel, Map<String, Integer> buttons,
            boolean stillOpenElsewhere) {
        int choice = ISaveablePart2.YES;

        // Get user preference for still open beahvior
        IPreferenceStore platformUIPrefStore = PlatformUI.getPreferenceStore();
        boolean dontPrompt = stillOpenElsewhere
                && !platformUIPrefStore.getBoolean(IWorkbenchPreferenceConstants.PROMPT_WHEN_SAVEABLE_STILL_OPEN);
        if (dontPrompt) {
            choice = ISaveablePart2.NO;
        } else {
            final IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
            if (window != null && buttons != null) {
                final MessageDialog dialog = createSaveDialog(label, canCancel, buttons, stillOpenElsewhere,
                        window);

                choice = dialog.open();

                // User has pressed "Escape" or has closed the dialog
                if (choice == SWT.DEFAULT) {
                    choice = IDialogConstants.CANCEL_ID;
                }

                // React to the use preference choice.
                // map value of choice back to ISaveablePart2 values
                switch (choice) {
                case IDialogConstants.YES_ID:
                    choice = ISaveablePart2.YES;
                    break;
                case IDialogConstants.NO_ID:
                    choice = ISaveablePart2.NO;
                    break;
                case IDialogConstants.CANCEL_ID:
                    choice = ISaveablePart2.CANCEL;
                    break;
                default:
                    break;
                }

                if (stillOpenElsewhere) {
                    MessageDialogWithToggle dialogWithToggle = (MessageDialogWithToggle) dialog;
                    if (choice != ISaveablePart2.CANCEL && dialogWithToggle.getToggleState()) {
                        // change the rpeference
                        platformUIPrefStore.setValue(IWorkbenchPreferenceConstants.PROMPT_WHEN_SAVEABLE_STILL_OPEN,
                                false);
                    }
                }
            }
        }

        return choice;
    }

    private static MessageDialog createSaveDialog(String label, final boolean canCancel,
            Map<String, Integer> buttons, boolean stillOpenElsewhere, final IWorkbenchWindow window) {
        final MessageDialog dialog;
        if (canCancel) {
            buttons.put(IDialogConstants.CANCEL_LABEL, IDialogConstants.CANCEL_ID);
        }

        // Provide a dialog allowing the user to change the
        // preference if several editors are opened
        Object[] bindings = null;
        if (stillOpenElsewhere) {
            bindings = new Object[] { label, OPEN_ELSEWHERE_MESSAGE, OPEN_ELSEWHERE_QUESTION };
        } else {
            bindings = new Object[] { label, "", "" };
        }
        final String message = NLS.bind(SAVE_CHANGES_QUESTION, bindings);
        dialog = new SiriusMessageDialogWithToggle(window.getShell(), "Save", null, message, MessageDialog.QUESTION,
                buttons, 0, WorkbenchMessages.EditorManager_closeWithoutPromptingOption, false,
                stillOpenElsewhere) {
            protected int getShellStyle() {
                return getSaveDialogStyle(canCancel);
            }
        };
        return dialog;
    }

    private static int getSaveDialogStyle(final boolean canCancel) {
        int style = canCancel ? SWT.CLOSE : SWT.NONE;
        style = style | SWT.TITLE | SWT.BORDER;
        style = style | SWT.APPLICATION_MODAL | Window.getDefaultOrientation();
        return style;
    }

    /**
     * A runnable which should returns a integer data type.
     * 
     * @author mchauvin
     */
    private interface RunnableWithResult extends Runnable {

        /**
         * get the result.
         * 
         * @return the result
         */
        int getResult();

    }

    /**
     * Utility class to dispose a composite on a dispose event
     * 
     * @author Mariot Chauvin (mchauvin)
     */
    private static class DefaultDisposeListener implements DisposeListener {

        private Composite composite;

        /**
         * Constructor
         * 
         * @param composite
         */
        public DefaultDisposeListener(final Composite composite) {
            this.composite = composite;
        }

        public void widgetDisposed(final DisposeEvent e) {
            if (composite != null && !composite.isDisposed()) {
                composite.dispose();
            }
        }

    }

    /**
     * Get the number of lines needed to display the <code>text</code> according
     * to the default font and the specified width.
     * 
     * @param text
     *            The text to display
     * @param width
     *            The width available to display the control
     * @return The number of lines needed.
     */
    public static int getNbLines(final String text, final int width) {
        if (text != null && text.length() > 0) {
            RunnableWithResult runnable = new RunnableWithResult() {
                int nbLines = 1;

                public void run() {
                    Shell shell = new Shell(Display.getCurrent());
                    Label label = new Label(shell, SWT.NONE);
                    nbLines = getNbLines(label, text, width);
                    label.dispose();
                    shell.dispose();
                }

                public int getResult() {
                    return nbLines;
                }
            };
            Display.getDefault().syncExec(runnable);
            return runnable.getResult();
        }
        return 1;
    }

    /**
     * Get the height, in pixel, of text with default System font.
     * 
     * @return The height of a line of text.
     */
    public static int getDefaultFontTextHeight() {
        RunnableWithResult runnable = new RunnableWithResult() {
            int height;

            public void run() {
                Shell shell = new Shell(Display.getCurrent());
                Label label = new Label(shell, SWT.NONE);
                GC gc = new GC(label);
                height = gc.textExtent("Some text").y;
                gc.dispose();
                label.dispose();
                shell.dispose();
            }

            public int getResult() {
                return height;
            }
        };
        Display.getDefault().syncExec(runnable);
        return runnable.getResult();
    }

    /**
     * Get the number of lines needed to display the <code>text</code> according
     * to the default font and the specified width.
     * 
     * @param control
     *            The control used to compute the text size
     * @param text
     *            The text to display
     * @param width
     *            The width available to display the control
     * @return The number of lines needed.
     */
    public static int getNbLines(Control control, final String text, final int width) {
        int nbLines = 1;
        if (text != null && text.length() > 0) {
            GC gc = new GC(control);
            int maxExtent = gc.textExtent(text).x;
            nbLines = maxExtent / width;
            if (maxExtent % width > 0) {
                // Some characters need another line to display
                nbLines++;
            }
            gc.dispose();
        }
        return nbLines;
    }

    /**
     * Compute a new text to display, correctly truncated and with "..." at end
     * if needed.
     * 
     * @param textValue
     *            The text to display
     * @param control
     *            The control used to display text. The parent of this control
     *            is used to get the maximum width and height available to
     *            display text.
     * @param heightOfOneLine
     *            The height in pixel of one line
     * @return the new text to display.
     */
    public static String shortenText(String textValue, Control control, int heightOfOneLine) {
        String result = null;
        if (textValue != null && textValue.length() > 0) {
            GC gc = new GC(control);
            try {
                int maxWidth = control.getParent().getBounds().width;
                int maxHeight = control.getParent().getBounds().height;
                int nbLinesAvailable = maxHeight / heightOfOneLine;
                if (nbLinesAvailable == 0) {
                    nbLinesAvailable = 1;
                }
                int currentWidth = gc.textExtent(textValue).x;
                if (currentWidth / nbLinesAvailable > maxWidth) {
                    int length = textValue.length() - 4;
                    while (length > 0 && result == null) {
                        String newText = textValue.substring(0, length) + "...";
                        currentWidth = gc.textExtent(newText).x;
                        // We remove 1 to the maxWidth because there is
                        // sometimes characters that takes less or more place.
                        if ((currentWidth / nbLinesAvailable) < (maxWidth - 1)) {
                            result = newText;
                        }
                        length--;
                    }
                }
            } finally {
                gc.dispose();
            }
        }
        if (result == null) {
            result = textValue;
        }
        return result;
    }
}