com.architexa.org.eclipse.gef.ui.palette.customize.PaletteCustomizerDialog.java Source code

Java tutorial

Introduction

Here is the source code for com.architexa.org.eclipse.gef.ui.palette.customize.PaletteCustomizerDialog.java

Source

/*******************************************************************************
 * Copyright (c) 2000, 2005 IBM Corporation and others.
 * 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:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package com.architexa.org.eclipse.gef.ui.palette.customize;

import com.architexa.org.eclipse.draw2d.ColorConstants;
import com.architexa.org.eclipse.draw2d.widgets.MultiLineLabel;
import com.architexa.org.eclipse.gef.internal.Internal;
import com.architexa.org.eclipse.gef.internal.ui.palette.ToolbarDropdownContributionItem;
import com.architexa.org.eclipse.gef.palette.PaletteEntry;
import com.architexa.org.eclipse.gef.palette.PaletteRoot;
import com.architexa.org.eclipse.gef.ui.palette.PaletteCustomizer;
import com.architexa.org.eclipse.gef.ui.palette.PaletteMessages;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;

import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CLabel;
import org.eclipse.swt.custom.StackLayout;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
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.Menu;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.ToolItem;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.swt.widgets.Widget;

import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.ActionContributionItem;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IMenuCreator;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.action.ToolBarManager;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.resource.JFaceColors;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.util.Assert;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.ui.ISharedImages;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.part.PageBook;

/**
 * This class implements a default dialog that allows customization of the different
 * entries/items on a GEF palette, i.e. the model behind the palette.
 * <p>
 * The construction of the dialog is broken down into different methods in order to allow
 * clients to further customize the appearance of the dialog, if so desired.
 * </p><p>
 * This dialog can be re-used, i.e., it can be re-opened once closed.  There is no need to
 * create a new <code>PaletteCustomizerDialog</code> everytime a palette needs to be
 * customized.
 * </p>
 * @author Pratik Shah
 * @see com.architexa.org.eclipse.gef.palette.PaletteEntry
 * @see com.architexa.org.eclipse.gef.ui.palette.PaletteCustomizer
 */
public class PaletteCustomizerDialog extends Dialog implements EntryPageContainer {

    /**
     * The unique ID for the Apply Button.  It can be used to retrieve
     * that widget from the internal map (using {@link #getWidget(int)} or
     * {@link #getButton(int)}), or to identify that widget in {@link #buttonPressed(int)}.
     */
    protected static final int APPLY_ID = IDialogConstants.CLIENT_ID + 1;

    /**
     * Sub-classes that need to create their own unique IDs should do so by adding to this ID.
     */
    protected static final int CLIENT_ID = 16;

    private HashMap widgets = new HashMap();
    private HashMap entriesToPages = new HashMap();
    private List actions;

    private String errorMessage;
    private Tree tree;
    private Composite titlePage, errorPage;
    private PageBook propertiesPanelContainer;
    // This PageBook is used to switch the title of the properties panel to either an error
    // message or the currently active entry's label
    private PageBook titleSwitcher;
    private PaletteCustomizer customizer;
    private EntryPage activePage, noSelectionPage;
    private CLabel title;
    private MultiLineLabel errorTitle;
    private Image titleImage;
    private TreeViewer treeviewer;
    private ILabelProvider treeViewerLabelProvider;
    private PaletteEntry activeEntry;
    private PaletteEntry initialSelection;
    private PaletteRoot root;
    private PropertyChangeListener titleUpdater = new PropertyChangeListener() {
        public void propertyChange(PropertyChangeEvent evt) {
            if (title == null) {
                return;
            }

            title.setText(((PaletteEntry) evt.getSource()).getLabel());
        }
    };
    private ISelectionChangedListener pageFlippingPreventer = new ISelectionChangedListener() {
        public void selectionChanged(SelectionChangedEvent event) {
            treeviewer.removePostSelectionChangedListener(this);
            treeviewer.setSelection(new StructuredSelection(activeEntry));
        }
    };
    private boolean isSetup = true;

    /**
     * Constructs a new customizer dialog.
     * @param shell the parent Shell
     * @param customizer the customizer
     * @param root the palette root
     */
    public PaletteCustomizerDialog(Shell shell, PaletteCustomizer customizer, PaletteRoot root) {
        super(shell);
        this.customizer = customizer;
        this.root = root;
        setShellStyle(getShellStyle() | SWT.RESIZE);
    }

    /**
     * This method will be invoked whenever any <code>Button</code> created using 
     * {@link #createButton(Composite, int, String, int, ImageDescriptor)} or 
     * {@link Dialog#createButton(Composite, int, String, boolean)} is selected.
     * 
     * @see Dialog#buttonPressed(int)
     */
    protected void buttonPressed(int buttonId) {
        if (APPLY_ID == buttonId) {
            handleApplyPressed();
        } else {
            super.buttonPressed(buttonId);
        }
    }

    /**
     * This method should be invoked by EntryPages when an error that they had earlier
     * reported (using {@link #showProblem(String)}) is fixed.  This will hide the error
     * message, enable the OK and Apply buttons and re-allow changing selection in the outline
     * tree.
     * 
     * @see com.architexa.org.eclipse.gef.ui.palette.customize.EntryPageContainer#clearProblem()
     * @see #showProblem(String)
     */
    public void clearProblem() {
        if (errorMessage != null) {
            titleSwitcher.showPage(titlePage);
            getButton(IDialogConstants.OK_ID).setEnabled(true);
            getButton(APPLY_ID).setEnabled(true);
            errorMessage = null;
        }
    }

    /**
     * <p> 
     * NOTE: This dialog can be re-opened.
     * </p>
     * 
     * @see org.eclipse.jface.window.Window#close()
     */
    public boolean close() {
        // Remove listeners
        if (activeEntry != null) {
            activeEntry.removePropertyChangeListener(titleUpdater);
        }

        // Save or dump changes
        // This needs to be done here and not in the handle methods because the user can
        // also close the dialog with the 'X' in the top right of the window (which 
        // corresponds to a cancel).
        if (getReturnCode() == OK) {
            save();
        } else {
            revertToSaved();
        }

        // Close the dialog
        boolean returnVal = super.close();

        // Reset variables
        entriesToPages.clear();
        widgets.clear();
        actions = null;
        activePage = null;
        tree = null;
        propertiesPanelContainer = null;
        titleSwitcher = null;
        titlePage = null;
        errorPage = null;
        title = null;
        errorTitle = null;
        treeviewer = null;
        noSelectionPage = null;
        initialSelection = null;
        activeEntry = null;
        errorMessage = null;
        isSetup = true;

        return returnVal;
    }

    /**
     * @see org.eclipse.jface.window.Window#configureShell(Shell)
     */
    protected void configureShell(Shell newShell) {
        newShell.setText(PaletteMessages.CUSTOMIZE_DIALOG_TITLE);
        super.configureShell(newShell);
    }

    /**
     * This method should not be used to create buttons for the button bar.  Use
     * {@link Dialog#createButton(Composite, int, String, boolean)} for that.   This method
     * can be used to create any other button in the dialog.  The parent 
     * <code>Composite</code> must have a GridLayout.  These buttons will be  available
     * through {@link #getButton(int)} and {@link #getWidget(int)}.  Ensure that the various
     * buttons created by this method are given unique IDs.  Pass in a <code>null</code> image
     * descriptor if  you don't want the button to have an icon.  This method will take care
     * of  disposing the images that it creates.  {@link #buttonPressed(int)} will be called
     * when any of the buttons created by this method are clicked (selected).
     * 
     * @param   parent      The composite in which the button is to be created
     * @param   id         The button's unique ID
     * @param   label      The button's text
     * @param   stylebits   The style bits for creating the button (eg., 
     *                   <code>SWT.PUSH</code> or <code>SWT.CHECK</code>)
     * @param   descriptor   The ImageDescriptor from which the image/icon for this
     *                   button should be created
     * @return            The newly created button for convenience
     */
    protected Button createButton(Composite parent, int id, String label, int stylebits,
            ImageDescriptor descriptor) {
        Button button = new Button(parent, stylebits);
        button.setText(label);
        button.setFont(parent.getFont());
        GridData data = new GridData(GridData.HORIZONTAL_ALIGN_FILL);
        button.setLayoutData(data);

        button.setData(new Integer(id));
        button.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent event) {
                buttonPressed(((Integer) event.widget.getData()).intValue());
            }
        });
        widgets.put(new Integer(id), button);

        if (descriptor != null) {
            button.setImage(new Image(parent.getDisplay(), descriptor.getImageData()));
            button.addDisposeListener(new DisposeListener() {
                public void widgetDisposed(DisposeEvent e) {
                    Image img = ((Button) e.getSource()).getImage();
                    if (img != null && !img.isDisposed()) {
                        img.dispose();
                    }
                }
            });
        }

        return button;
    }

    /**
     * Creates the OK, Cancel and Apply buttons
     * 
     * @see org.eclipse.jface.dialogs.Dialog#createButtonsForButtonBar(Composite)
     */
    protected void createButtonsForButtonBar(Composite parent) {
        super.createButtonsForButtonBar(parent);
        createButton(parent, APPLY_ID, PaletteMessages.APPLY_LABEL, false);
    }

    /**
     * The dialog area contains the following:
     * <UL>
     *       <LI>Outline ({@link #createOutline(Composite)})</LI>
     *       <LI>Properties Panel ({@link #createPropertiesPanel(Composite)})</LI>
     * </UL>
     * 
     * <p>
     * It is recommended that this method not be overridden. Override one of the methods that
     * this method calls in order to customize the appearance of the dialog.
     * </p>
     * 
     * @see org.eclipse.jface.dialogs.Dialog#createDialogArea(Composite)
     */
    protected Control createDialogArea(Composite parent) {
        Composite composite = (Composite) super.createDialogArea(parent);
        GridLayout gridLayout = (GridLayout) composite.getLayout();
        gridLayout.numColumns = 2;
        gridLayout.horizontalSpacing = 10;

        // Create the tree
        Control child = createOutline(composite);
        GridData data = new GridData(GridData.VERTICAL_ALIGN_FILL);
        data.verticalSpan = 2;
        child.setLayoutData(data);

        // Create the panel where the properties of the selected palette entry will
        // be shown
        child = createPropertiesPanel(composite);
        child.setLayoutData(new GridData(GridData.FILL_BOTH));

        // Create the separator b/w the dialog area and the button bar
        Label label = new Label(composite, SWT.SEPARATOR | SWT.HORIZONTAL);
        data = new GridData(GridData.FILL_HORIZONTAL);
        data.horizontalSpan = 2;
        label.setLayoutData(data);

        // Select an element in the outline and set focus on the outline.
        if (initialSelection == null) {
            // We have to manually select the first item in the tree, because otherwise the
            // will scroll to show the last item, and then will select the first visible item.
            List children = getPaletteRoot().getChildren();
            if (!children.isEmpty()) {
                initialSelection = (PaletteEntry) children.get(0);
            }
        }
        if (initialSelection != null) {
            treeviewer.setSelection(new StructuredSelection(initialSelection));
        } else {
            setActiveEntry(null);
        }
        isSetup = false;
        tree.setFocus();

        return composite;
    }

    /**
     * Creates the outline part of the dialog.
     * 
     * <p>
     * The outline creates the following:
     * <UL>
     *       <LI>ToolBar ({@link #createOutlineToolBar(Composite)})</LI>
     *       <LI>TreeViewer ({@link #createOutlineTreeViewer(Composite)})</LI>
     *       <LI>Context menu ({@link #createOutlineContextMenu()})</LI>
     * </UL>
     * </p>
     * 
     * @param container   The Composite within which the outline has to be created
     * @return          The newly created Control that has the outline
     */
    protected Control createOutline(Composite container) {
        // Create the Composite that will contain the outline
        Composite composite = new Composite(container, SWT.NONE);
        composite.setFont(container.getFont());
        GridLayout layout = new GridLayout();
        layout.horizontalSpacing = 0;
        layout.verticalSpacing = 0;
        layout.marginHeight = 0;
        layout.marginWidth = 0;
        composite.setLayout(layout);

        // Create the ToolBar
        createOutlineToolBar(composite);

        // Create the actual outline (TreeViewer)         
        treeviewer = createOutlineTreeViewer(composite);
        tree = treeviewer.getTree();

        // Create the context menu for the Tree
        tree.setMenu(createOutlineContextMenu());

        return composite;
    }

    /**
     * Creates the actions that manipulate the palette model.  These actions will populate the
     * toolbar and the outline's context menu.
     * 
     * <p>
     * IMPORTANT: All the elements in the returned List MUST be
     * <code>PaletteCustomizationAction</code>s.
     * </p>
     * 
     * @return A List of {@link PaletteCustomizationAction PaletteCustomizationActions}
     */
    protected List createOutlineActions() {
        List actions = new ArrayList();
        actions.add(new NewAction());
        actions.add(new DeleteAction());
        actions.add(new MoveDownAction());
        actions.add(new MoveUpAction());
        return actions;
    }

    /**
     * Uses a <code>MenuManager</code> to create the context menu for the outline.  The
     * <code>IActions</code> used to create the context menu are those created in 
     * {@link #createOutlineActions()}.
     * 
     * @return   The newly created Menu
     */
    protected Menu createOutlineContextMenu() {
        // MenuManager for the tree's context menu
        final MenuManager outlineMenu = new MenuManager();

        List actions = getOutlineActions();
        // Add all the actions to the context menu
        for (Iterator iter = actions.iterator(); iter.hasNext();) {
            IAction action = (IAction) iter.next();
            if (action instanceof IMenuCreator)
                outlineMenu.add(new ActionContributionItem(action) {
                    public boolean isDynamic() {
                        return true;
                    }
                });
            else
                outlineMenu.add(action);
            // Add separators after new and delete
            if (action instanceof NewAction || action instanceof DeleteAction) {
                outlineMenu.add(new Separator());
            }
        }

        outlineMenu.addMenuListener(new IMenuListener() {
            public void menuAboutToShow(IMenuManager manager) {
                outlineMenu.update(true);
            }
        });

        outlineMenu.createContextMenu(tree);
        return outlineMenu.getMenu();
    }

    /**
     * Uses a ToolBarManager to create the ToolBar in the outline part of the
     * dialog.  The Actions used in the ToolBarManager are those that are
     * created in {@link #createOutlineActions()}.
     * 
     * @param   parent      The Composite to which the ToolBar is to be added
     * @return            The newly created ToolBar
     */
    protected Control createOutlineToolBar(Composite parent) {
        // A customized composite for the toolbar 
        final Composite composite = new Composite(parent, SWT.NONE) {
            public Rectangle getClientArea() {
                Rectangle area = super.getClientArea();
                area.x += 2;
                area.y += 2;
                area.height -= 2;
                area.width -= 4;
                return area;
            }

            public Point computeSize(int wHint, int hHint, boolean changed) {
                Point size = super.computeSize(wHint, hHint, changed);
                size.x += 4;
                size.y += 2;
                return size;
            }
        };
        composite.setFont(parent.getFont());
        composite.setLayout(new FillLayout());

        // A paint listener that draws an etched border around the toolbar
        composite.addPaintListener(new PaintListener() {
            public void paintControl(PaintEvent e) {
                Rectangle area = composite.getBounds();
                GC gc = e.gc;
                gc.setLineStyle(SWT.LINE_SOLID);
                gc.setForeground(ColorConstants.buttonDarker);
                gc.drawLine(area.x, area.y, area.x + area.width - 2, area.y);
                gc.drawLine(area.x, area.y, area.x, area.y + area.height - 1);
                gc.drawLine(area.x + area.width - 2, area.y, area.x + area.width - 2, area.y + area.height - 1);
                gc.setForeground(ColorConstants.buttonLightest);
                gc.drawLine(area.x + 1, area.y + 1, area.x + area.width - 3, area.y + 1);
                gc.drawLine(area.x + area.width - 1, area.y + 1, area.x + area.width - 1, area.y + area.height - 1);
                gc.drawLine(area.x + 1, area.y + 1, area.x + 1, area.y + area.height - 1);
            }
        });

        // Create the ToolBarManager and add all the actions to it
        ToolBarManager tbMgr = new ToolBarManager(SWT.FLAT | SWT.HORIZONTAL);
        List actions = getOutlineActions();
        for (int i = 0; i < actions.size(); i++) {
            tbMgr.add(new ToolbarDropdownContributionItem(((IAction) actions.get(i))));
        }
        tbMgr.createControl(composite);
        tbMgr.getControl().setFont(composite.getFont());

        // By default, the ToolBarManager does not set text on ToolItems.  Since,
        // we want to display the text, we will have to do it manually.
        ToolItem[] items = tbMgr.getControl().getItems();
        for (int i = 0; i < items.length; i++) {
            ToolItem item = items[i];
            item.setText(((IAction) actions.get(i)).getText());
        }

        return tbMgr.getControl();
    }

    /**
     * Creates the TreeViewer that is the outline of the model.
     * 
     * @param   composite   The Composite to which the ToolBar is to be added
     * @return            The newly created TreeViewer
     */
    protected TreeViewer createOutlineTreeViewer(Composite composite) {
        Tree treeForViewer = new Tree(composite, SWT.BORDER);
        treeForViewer.setFont(composite.getFont());
        GridData data = new GridData(GridData.FILL_VERTICAL | GridData.HORIZONTAL_ALIGN_FILL);
        data.widthHint = 185;
        // Make the tree this tall even when there is nothing in it.  This will keep the
        // dialog from shrinking to an unusually small size.
        data.heightHint = 200;
        treeForViewer.setLayoutData(data);
        TreeViewer viewer = new TreeViewer(treeForViewer) {
            protected void preservingSelection(Runnable updateCode) {
                if ((getTree().getStyle() & SWT.SINGLE) != 0)
                    updateCode.run();
                else
                    super.preservingSelection(updateCode);
            }
        };
        viewer.setContentProvider(new PaletteTreeProvider(viewer));
        treeViewerLabelProvider = new PaletteLabelProvider(viewer);
        viewer.setLabelProvider(treeViewerLabelProvider);
        viewer.setInput(getPaletteRoot());
        viewer.addSelectionChangedListener(new ISelectionChangedListener() {
            public void selectionChanged(SelectionChangedEvent event) {
                handleOutlineSelectionChanged();
            }
        });

        return viewer;
    }

    /**
     * Creates the part of the dialog where the properties of the element selected
     * in the outline will be displayed.
     * 
     * <p>
     * The properties panel contains the following:
     * <UL>
     *       <LI>Title ({@link #createPropertiesPanelTitle(Composite)})</LI>
     * </UL> 
     * The rest of the panel is constructed in this method.
     * </p> 
     * 
     * @param container   The Composite to which this part is to be added
     * @return          The properties panel
     */
    protected Control createPropertiesPanel(Composite container) {
        Composite composite = new Composite(container, SWT.NONE);
        composite.setFont(container.getFont());
        GridLayout layout = new GridLayout(1, false);
        layout.horizontalSpacing = 0;
        layout.marginWidth = 0;
        layout.marginHeight = 0;
        layout.verticalSpacing = 0;
        composite.setLayout(layout);

        titleSwitcher = createPropertiesPanelTitle(composite);

        propertiesPanelContainer = new PageBook(composite, SWT.NONE);
        propertiesPanelContainer.setFont(composite.getFont());
        GridData data = new GridData(GridData.HORIZONTAL_ALIGN_FILL | GridData.FILL_VERTICAL);
        data.horizontalSpan = 2;
        propertiesPanelContainer.setLayoutData(data);
        propertiesPanelContainer.addListener(SWT.Resize, new Listener() {
            public void handleEvent(Event event) {
                if (activePage != null) {
                    propertiesPanelContainer.layout();
                }
            }
        });

        return composite;
    }

    /**
     * Creates the title for the properties panel.  It is a PageBook that can switch between
     * showing the regular title (the selected entry's label and icon) and an error message if
     * an error has occured.
     * 
     * @param parent   The parent composite
     * @return       The newly created PageBook title
     */
    protected PageBook createPropertiesPanelTitle(Composite parent) {
        GridLayout layout;
        PageBook book = new PageBook(parent, SWT.NONE);
        book.setFont(parent.getFont());
        book.setLayoutData(new GridData(GridData.FILL_HORIZONTAL | GridData.VERTICAL_ALIGN_FILL));

        titlePage = new Composite(book, SWT.NONE);
        titlePage.setFont(book.getFont());
        layout = new GridLayout(2, false);
        layout.horizontalSpacing = 0;
        layout.marginWidth = 0;
        layout.marginHeight = 0;
        layout.verticalSpacing = 0;
        titlePage.setLayout(layout);
        title = createSectionTitle(titlePage, PaletteMessages.NO_SELECTION_TITLE);

        errorPage = new Composite(book, SWT.NONE);
        errorPage.setFont(book.getFont());
        layout = new GridLayout(1, false);
        layout.horizontalSpacing = 0;
        layout.marginWidth = 0;
        layout.marginHeight = 0;
        layout.verticalSpacing = 0;
        errorPage.setLayout(layout);
        Composite intermediary = new Composite(errorPage, SWT.NONE) {
            public Point computeSize(int wHint, int hHint, boolean changed) {
                Rectangle bounds = title.getBounds();
                return new Point(bounds.width, bounds.height);
            }
        };
        intermediary.setLayoutData(new GridData(GridData.FILL_HORIZONTAL | GridData.VERTICAL_ALIGN_FILL));
        StackLayout stackLayout = new StackLayout();
        intermediary.setLayout(stackLayout);
        errorTitle = new MultiLineLabel(intermediary);
        stackLayout.topControl = errorTitle;
        errorTitle.setImage(JFaceResources.getImage(DLG_IMG_MESSAGE_ERROR));
        errorTitle.setFont(errorPage.getFont());
        Label separator = new Label(errorPage, SWT.SEPARATOR | SWT.HORIZONTAL);
        separator.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));

        book.showPage(titlePage);
        return book;
    }

    /**
     * A convenient method to create CLabel titles (like the ones used in the
     * Preferences dialog in the Eclipse workbench) throughout the dialog.
     * 
     * @param composite   The composite in which the title is to be created (it must have a
     *                   GridLayout with two columns).
     * @param text         The title to be displayed
     * @return          The newly created CLabel for convenience
     */
    protected CLabel createSectionTitle(Composite composite, String text) {
        CLabel cTitle = new CLabel(composite, SWT.LEFT);
        Color background = JFaceColors.getBannerBackground(composite.getDisplay());
        Color foreground = JFaceColors.getBannerForeground(composite.getDisplay());
        JFaceColors.setColors(cTitle, foreground, background);
        cTitle.setFont(JFaceResources.getBannerFont());
        cTitle.setText(text);
        cTitle.setLayoutData(new GridData(GridData.FILL_HORIZONTAL | GridData.VERTICAL_ALIGN_FILL));

        if (titleImage == null) {
            titleImage = new Image(composite.getDisplay(), ImageDescriptor
                    .createFromFile(Internal.class, "icons/customizer_dialog_title.gif").getImageData()); //$NON-NLS-1$
            composite.addDisposeListener(new DisposeListener() {
                public void widgetDisposed(DisposeEvent e) {
                    titleImage.dispose();
                    titleImage = null;
                }
            });
        }

        Label imageLabel = new Label(composite, SWT.LEFT);
        imageLabel.setBackground(background);
        imageLabel.setImage(titleImage);
        imageLabel.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_FILL | GridData.VERTICAL_ALIGN_FILL));

        Label separator = new Label(composite, SWT.SEPARATOR | SWT.HORIZONTAL);
        GridData data = new GridData(GridData.HORIZONTAL_ALIGN_FILL);
        data.horizontalSpan = 2;
        separator.setLayoutData(data);

        return cTitle;
    }

    /**
     * Returns the Button with the given id; or <code>null</code> if none was found.
     * 
     * @see org.eclipse.jface.dialogs.Dialog#getButton(int)
     */
    protected Button getButton(int id) {
        Button button = null;
        Widget widget = getWidget(id);
        if (widget instanceof Button) {
            button = (Button) widget;
        }

        return button;
    }

    /**
     * @return The customizer that is responsible for handling the various tasks
     * and updating the model.
     */
    protected PaletteCustomizer getCustomizer() {
        return customizer;
    }

    /**
     * Returns the <code>EntryPage</code> for the given <code>PaletteEntry</code>.  The 
     * <code>EntryPage</code> is retrieved from the customizer.  If the given entry is 
     * <code>null</code>, <code>null</code> will be returned.  If the customizer returns 
     * <code>null</code> for the valid entry, a default page will be created and returned.
     * 
     * @param entry      The PaletteEntry whose properties need to be displayed
     * @return The EntryPage with the properties of the given PaletteEntry
     */
    protected EntryPage getEntryPage(PaletteEntry entry) {
        if (entry == null) {
            return null;
        }

        if (entriesToPages.containsKey(entry)) {
            return (EntryPage) entriesToPages.get(entry);
        }

        EntryPage page = getCustomizer().getPropertiesPage(entry);
        if (page == null) {
            page = new DefaultEntryPage();
        }
        page.createControl(propertiesPanelContainer, entry);
        page.setPageContainer(this);
        entriesToPages.put(entry, page);

        return page;
    }

    /**
     * Provides access to the actions that are used to manipulate the model.  The actions will
     * be created, if they haven't been yet.  
     * 
     * @return the list of <code>PaletteCustomizationAction</code>s
     * @see #createOutlineActions()
     */
    protected final List getOutlineActions() {
        if (actions == null) {
            actions = createOutlineActions();
        }
        return actions;
    }

    /**
     * Provides sub-classes with access to the PaletteRoot
     * 
     * @return the palette root
     */
    protected PaletteRoot getPaletteRoot() {
        return root;
    }

    /**
     * @return      The PaletteEntry that is currently selected in the Outline Tree;
     *             <code>null</code> if none is selected
     */
    protected PaletteEntry getSelectedPaletteEntry() {
        TreeItem item = getSelectedTreeItem();
        if (item != null) {
            return (PaletteEntry) item.getData();
        }
        return null;

    }

    /**
     * @return    The TreeItem that is currently selected in the Outline Tree; <code>null</code>
     *             if none is selected
     */
    protected TreeItem getSelectedTreeItem() {
        TreeItem[] items = tree.getSelection();
        if (items.length > 0) {
            return items[0];
        }
        return null;
    }

    /**
     * The <code>Widget</code>s that were created with a unique ID and added to this class'
     * internal map can be retrieved through this method.
     * 
     * @param    id   The unique ID of the Widget that you wish to retrieve
     * @return    The Widget, if one with the given id exists; <code>null</code> otherwise
     */
    protected Widget getWidget(int id) {
        Widget widget = (Widget) widgets.get(new Integer(id));
        if (widget == null) {
            widget = super.getButton(id);
        }

        return widget;
    }

    /**
     * This method is invoked when the Apply button is pressed
     * <p>
     * IMPORTANT: It is recommended that you not override this method.  Closing the dialog
     * with the 'X' at the top right of the window, or by hitting 'Esc' or any other way,
     * corresponds to a "Cancel."  That will, however, not result in this method being
     * invoked.  To handle such cases, saving or rejecting the changes is handled in {@link
     * #close()}.  Override {@link #save()} and {@link #revertToSaved()} to add to what needs
     * to be done when saving or cancelling.
     * </p>
     */
    protected final void handleApplyPressed() {
        save();
    }

    /**
     * This method is called when the "Delete" action is run (either through the context
     * menu or the toolbar).  It deletes the selected palette entry.
     */
    protected void handleDelete() {
        getCustomizer().performDelete(getSelectedPaletteEntry());
        handleOutlineSelectionChanged();
    }

    /**
     * This method is called when the "Move Down" action is run (either through the context
     * menu or the toolbar).  It moves the selected palette entry down.
     */
    protected void handleMoveDown() {
        PaletteEntry entry = getSelectedPaletteEntry();
        getCustomizer().performMoveDown(entry);
        treeviewer.setSelection(new StructuredSelection(entry), true);
        updateActions();
    }

    /**
     * This method is called when the "Move Up" action is run (either through the context
     * menu or the toolbar).  It moves the selected entry up.
     */
    protected void handleMoveUp() {
        PaletteEntry entry = getSelectedPaletteEntry();
        getCustomizer().performMoveUp(entry);
        treeviewer.setSelection(new StructuredSelection(entry), true);
        updateActions();
    }

    /**
     * This is the method that is called everytime the selection in the outline
     * (treeviewer) changes.  
     */
    protected void handleOutlineSelectionChanged() {
        PaletteEntry entry = getSelectedPaletteEntry();

        if (activeEntry == entry) {
            return;
        }

        if (errorMessage != null) {
            MessageDialog dialog = new MessageDialog(getShell(), PaletteMessages.ERROR, null,
                    PaletteMessages.ABORT_PAGE_FLIPPING_MESSAGE + "\n" + errorMessage, //$NON-NLS-1$
                    MessageDialog.ERROR, new String[] { IDialogConstants.OK_LABEL }, 0);
            dialog.open();
            treeviewer.addPostSelectionChangedListener(pageFlippingPreventer);
        } else {
            setActiveEntry(entry);
        }
        updateActions();
    }

    /**
     * This method is invoked when the changes made since the last save need to be cancelled.
     */
    protected void revertToSaved() {
        getCustomizer().revertToSaved();
    }

    /**
     * This method is invoked when the changes made since the last save need to be saved.
     */
    protected void save() {
        if (activePage != null) {
            activePage.apply();
        }
        getCustomizer().save();
    }

    /**
     * This methods sets the active entry.  Based on the selection, this method
     * will appropriately enable or disable the ToolBar items, will change the CLabel heading
     * of the propreties panel, and will show the properties of the selected item in the
     * properties panel.
     * 
     * @param    entry   The new active entry, i.e., the new selected entry (it can be
     *                <code>null</code>)
     */
    protected void setActiveEntry(PaletteEntry entry) {
        if (activeEntry != null) {
            activeEntry.removePropertyChangeListener(titleUpdater);
        }

        activeEntry = entry;

        if (entry != null) {
            title.setText(entry.getLabel());
            Image img = treeViewerLabelProvider.getImage(entry);
            if (img == null) {
                img = getSelectedTreeItem().getImage();
            }
            title.setImage(img);
            entry.addPropertyChangeListener(titleUpdater);
            EntryPage panel = getEntryPage(entry);
            setActiveEntryPage(panel);
        } else {
            title.setImage(null);
            title.setText(PaletteMessages.NO_SELECTION_TITLE);
            //Lazy creation
            if (noSelectionPage == null) {
                noSelectionPage = new EntryPage() {
                    private Text text;

                    public void apply() {
                    }

                    public void createControl(Composite parent, PaletteEntry entry) {
                        text = new Text(parent, SWT.READ_ONLY);
                        text.setFont(parent.getFont());
                        text.setText(PaletteMessages.NO_SELECTION_MADE);
                    }

                    public Control getControl() {
                        return text;
                    }

                    public void setPageContainer(EntryPageContainer pageContainer) {
                    }
                };
                noSelectionPage.createControl(propertiesPanelContainer, null);
            }
            setActiveEntryPage(noSelectionPage);
        }
    }

    /**
     * Sets the given EntryPage as the top page in the PageBook that shows
     * the properties of the item selected in the Outline.  If the given EntryPage
     * is null, nothing will be shown.
     * 
     * @param    page   The EntryPage to be shown
     */
    protected void setActiveEntryPage(EntryPage page) {
        // Have the currently displayed page save its changes
        if (activePage != null) {
            activePage.apply();
        }

        if (page == null) {
            // No page available to display, so hide the panel container
            propertiesPanelContainer.setVisible(false);
        } else {
            // Show the page and grow the shell, if necessary, so that the page is
            // completely visible
            Point oldSize = getShell().getSize();
            propertiesPanelContainer.showPage(page.getControl());

            /*
             * Fix for bug #34748
             * There's no need to resize the Shell if initializeBounds() hasn't been called
             * yet.  It will automatically resize the Shell so that everything fits in the
             * Dialog.  After that, we can resize the Shell whenever there's an entry page
             * that cannot fit in the dialog.
             */
            if (!isSetup) {
                Point newSize = getShell().computeSize(SWT.DEFAULT, SWT.DEFAULT, true);
                int x = newSize.x - oldSize.x;
                x = (x < 0) ? 0 : x;
                int y = newSize.y - oldSize.y;
                y = (y < 0) ? 0 : y;
                if (x > 0 || y > 0) {
                    getShell().setSize(oldSize.x + x, oldSize.y + y);
                }
            }

            // Show the property panel container if it was hidden
            if (!propertiesPanelContainer.isVisible()) {
                propertiesPanelContainer.setVisible(true);
            }
        }

        activePage = page;
    }

    /**
     * Sets the given PaletteEntry as the one to be selected when the dialog
     * opens.  It is discarded when the dialog is closed.
     * 
     * @param   entry   The PaletteEntry that should be selected when the dialog is opened
     */
    public void setDefaultSelection(PaletteEntry entry) {
        initialSelection = entry;
    }

    /**
     * This method should be invoked by EntryPages when there is an error.  It will show the
     * given error in the title of the properties panel.  OK and Apply buttons will be
     * disabled.  Selecting some other entry in the outline tree will not be allowed until the
     * error is fixed.
     * 
     * @see com.architexa.org.eclipse.gef.ui.palette.customize.EntryPageContainer#showProblem(String)
     */
    public void showProblem(String error) {
        Assert.isNotNull(error);
        errorTitle.setText(error);
        titleSwitcher.showPage(errorPage);
        getButton(IDialogConstants.OK_ID).setEnabled(false);
        getButton(APPLY_ID).setEnabled(false);
        errorMessage = error;
    }

    /**
     * Updates the actions created in {@link #createOutlineActions()}, enabling or
     * disabling them as necessary.
     */
    protected void updateActions() {
        List actions = getOutlineActions();
        for (Iterator iter = actions.iterator(); iter.hasNext();) {
            PaletteCustomizationAction action = (PaletteCustomizationAction) iter.next();
            action.update();
        }
    }

    /*
     * Delete Action
     */
    private class DeleteAction extends PaletteCustomizationAction {
        public DeleteAction() {
            setEnabled(false);
            setText(PaletteMessages.DELETE_LABEL);
            ISharedImages sharedImages = PlatformUI.getWorkbench().getSharedImages();
            setImageDescriptor(sharedImages.getImageDescriptor(ISharedImages.IMG_TOOL_DELETE));
            setDisabledImageDescriptor(sharedImages.getImageDescriptor(ISharedImages.IMG_TOOL_DELETE_DISABLED));
        }

        public void run() {
            handleDelete();
        }

        public void update() {
            boolean enabled = false;
            PaletteEntry entry = getSelectedPaletteEntry();
            if (entry != null) {
                enabled = getCustomizer().canDelete(entry);
            }
            setEnabled(enabled);
        }
    }

    /*
     * Move Down Action
     */
    private class MoveDownAction extends PaletteCustomizationAction {
        public MoveDownAction() {
            setEnabled(false);
            setText(PaletteMessages.MOVE_DOWN_LABEL);
            setImageDescriptor(ImageDescriptor.createFromFile(Internal.class, "icons/next_nav.gif"));//$NON-NLS-1$
            setDisabledImageDescriptor(
                    ImageDescriptor.createFromFile(Internal.class, "icons/move_down_disabled.gif"));//$NON-NLS-1$
        }

        public void run() {
            handleMoveDown();
        }

        public void update() {
            boolean enabled = false;
            PaletteEntry entry = getSelectedPaletteEntry();
            if (entry != null) {
                enabled = getCustomizer().canMoveDown(entry);
            }
            setEnabled(enabled);
        }
    }

    /*
     * Move Up Action
     */
    private class MoveUpAction extends PaletteCustomizationAction {
        public MoveUpAction() {
            setEnabled(false);
            setText(PaletteMessages.MOVE_UP_LABEL);
            setImageDescriptor(ImageDescriptor.createFromFile(Internal.class, "icons/prev_nav.gif"));//$NON-NLS-1$
            setDisabledImageDescriptor(
                    ImageDescriptor.createFromFile(Internal.class, "icons/move_up_disabled.gif")); //$NON-NLS-1$
        }

        public void run() {
            handleMoveUp();
        }

        public void update() {
            boolean enabled = false;
            PaletteEntry entry = getSelectedPaletteEntry();
            if (entry != null) {
                enabled = getCustomizer().canMoveUp(entry);
            }
            setEnabled(enabled);
        }
    }

    /*
     * New Action
     */
    private class NewAction extends PaletteCustomizationAction implements IMenuCreator {
        private List factories;
        private MenuManager menuMgr;

        public NewAction() {
            factories = wrap(getCustomizer().getNewEntryFactories());
            if (factories.isEmpty()) {
                setEnabled(false);
            } else {
                setMenuCreator(this);
            }

            setText(PaletteMessages.NEW_LABEL);
            setImageDescriptor(ImageDescriptor.createFromFile(Internal.class, "icons/add.gif")); //$NON-NLS-1$
            setDisabledImageDescriptor(ImageDescriptor.createFromFile(Internal.class, "icons/add-disabled.gif")); //$NON-NLS-1$
        }

        private void addActionToMenu(Menu parent, IAction action) {
            ActionContributionItem item = new ActionContributionItem(action);
            item.fill(parent, -1);
        }

        public void dispose() {
            if (menuMgr != null) {
                menuMgr.dispose();
                menuMgr = null;
            }
        }

        public Menu getMenu(Control parent) {
            // Create the menu manager and add all the NewActions to it
            if (menuMgr == null) {
                // Lazily create the manager
                menuMgr = new MenuManager();
                menuMgr.createContextMenu(parent);
            }

            updateMenuManager(menuMgr);
            return menuMgr.getMenu();
        }

        public Menu getMenu(Menu parent) {
            Menu menu = new Menu(parent);
            for (Iterator iter = factories.iterator(); iter.hasNext();) {
                FactoryWrapperAction action = (FactoryWrapperAction) iter.next();
                if (action.isEnabled()) {
                    addActionToMenu(menu, action);
                }
            }

            return menu;
        }

        public void run() {
        }

        public void update() {
            boolean enabled = false;
            PaletteEntry entry = getSelectedPaletteEntry();
            if (entry == null) {
                entry = getPaletteRoot();
            }
            // Enable or disable the FactoryWrapperActions
            for (Iterator iter = factories.iterator(); iter.hasNext();) {
                FactoryWrapperAction action = (FactoryWrapperAction) iter.next();
                action.setEnabled(action.canCreate(entry));
                enabled = enabled || action.isEnabled();
            }

            // Enable this action IFF at least one of the new actions is enabled
            setEnabled(enabled);
        }

        protected void updateMenuManager(MenuManager manager) {
            manager.removeAll();
            for (Iterator iter = factories.iterator(); iter.hasNext();) {
                FactoryWrapperAction action = (FactoryWrapperAction) iter.next();
                if (action.isEnabled()) {
                    manager.add(action);
                }
            }
        }

        private List wrap(List list) {
            List newList = new ArrayList();
            if (list != null) {
                for (Iterator iter = list.iterator(); iter.hasNext();) {
                    PaletteEntryFactory element = (PaletteEntryFactory) iter.next();
                    newList.add(new FactoryWrapperAction(element));
                }
            }

            return newList;
        }
    }

    /*
     * FactoryWrapperAction class
     */
    private class FactoryWrapperAction extends Action {
        private PaletteEntryFactory factory;

        public FactoryWrapperAction(PaletteEntryFactory factory) {
            this.factory = factory;
            setText(factory.getLabel());
            setImageDescriptor(factory.getImageDescriptor());
            setHoverImageDescriptor(factory.getImageDescriptor());
        }

        public boolean canCreate(PaletteEntry entry) {
            return factory.canCreate(entry);
        }

        public void run() {
            PaletteEntry selected = getSelectedPaletteEntry();
            if (selected == null)
                selected = getPaletteRoot();
            PaletteEntry newEntry = factory.createNewEntry(getShell(), selected);
            treeviewer.setSelection(new StructuredSelection(newEntry), true);
            updateActions();
        }
    }

}