/*******************************************************************************
 * Copyright (c) 2000, 2010 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 org.eclipse.gef.ui.palette.customize;

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.ControlPaintHandler;
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.core.runtime.Assert;
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.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;

import org.eclipse.draw2d.ColorConstants;
import org.eclipse.draw2d.widgets.MultiLineLabel;

import org.eclipse.gef.internal.Internal;
import org.eclipse.gef.internal.ui.palette.ToolbarDropdownContributionItem;
import org.eclipse.gef.palette.PaletteEntry;
import org.eclipse.gef.palette.PaletteRoot;
import org.eclipse.gef.ui.palette.PaletteCustomizer;
import org.eclipse.gef.ui.palette.PaletteMessages;

/**
 * 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 org.eclipse.gef.palette.PaletteEntry
 * @see 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 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.get().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.get().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
		ControlPaintHandler helper = new ControlPaintHandler(composite);
		helper.addPaintListener(new PaintListener() {
			public void paintControl(PaintEvent e) {
				Rectangle area = composite.getBounds();
				GC gc = e.gc;
				// UNSUPPORTED - api not implemented in RAP
				// 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.get().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.get().ERROR,
					null,
					PaletteMessages.get().ABORT_PAGE_FLIPPING_MESSAGE
							+ "\n" + errorMessage, //$NON-NLS-1$
					MessageDialog.ERROR,
					new String[] { IDialogConstants.get().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.get().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.get().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 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.get().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.get().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.get().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.get().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();
		}
	}

}
