blob: 1a6f5a42ee2f6e48ad7a77ce195ac38c02f7377f [file] [log] [blame]
/*******************************************************************************
* 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.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.swt.SWT;
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.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
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.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.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.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 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();
}
}
}