blob: bec1a177382288869f4246108003cd6bf146a038 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2003 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Common Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/cpl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.ui.internal.commands.ws;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.eclipse.core.runtime.Platform;
import org.eclipse.jface.action.ContributionItem;
import org.eclipse.jface.action.ExternalActionManager;
import org.eclipse.jface.action.IContributionManagerOverrides;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.HelpListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.swt.widgets.ToolItem;
import org.eclipse.swt.widgets.Widget;
import org.eclipse.ui.commands.CommandEvent;
import org.eclipse.ui.commands.ICommand;
import org.eclipse.ui.commands.ICommandListener;
import org.eclipse.ui.commands.NotDefinedException;
import org.eclipse.ui.commands.NotHandledException;
import org.eclipse.ui.help.WorkbenchHelp;
/**
* <p>
* A contribution item which delegates to a command. This is a contribution
* item that just passes as much of the complexity as it can on to the
* underlying command.
* </p>
* <p>
* This class may be instantiated; it is not intended to be subclassed.
* </p>
*
* @since 3.0
*/
public class CommandContributionItem
extends ContributionItem
implements ICommandListener {
/**
* A cache of images loaded by the command contribution item.
*/
private static class ImageCache {
/**
* An entry in the cache for tracking images.
*/
private class Entry {
/** The greyed representation of the image. */
Image grayImage;
/** The plain image (unmodified). */
Image image;
/** Disposes this entry by releasing its native resources, if any. */
void dispose() {
if (image != null) {
image.dispose();
image = null;
}
if (grayImage != null) {
grayImage.dispose();
grayImage = null;
}
}
}
/** Map from ImageDescriptor to Entry */
private Map entries = new HashMap(11);
/** The image used to represent an image that is not available. */
private Image missingImage;
/** Disposes the cache by calling dispose an all of its entries. */
void dispose() {
for (Iterator i = entries.values().iterator(); i.hasNext();) {
((Entry) i.next()).dispose();
}
entries.clear();
}
/**
* If the entry is already in the cache, then it simply returns a
* reference. Otherwise, it creates a new entry in the cache, and
* returns a reference to that.
*
* @param descriptor
* The descriptor to look up in the cache; should not be
* <code>null</code>.
* @return The entry for that descriptor; never <code>null</code>.
*/
Entry getEntry(ImageDescriptor descriptor) {
Entry entry = (Entry) entries.get(descriptor);
if (entry == null) {
entry = new Entry();
entries.put(descriptor, entry);
}
return entry;
}
/**
* Retrieves a greyed representation of the image of the corresponding
* URI from the cache. If the image is not in the cache, then it tries
* to load the image. If the image URI is invalid, then it returns the
* missing image.
*
* @param imageURI
* The URI at which the image is located; may be <code>null</code>
* @return The image at the URI, or the missing image if it is invalid.
* If the URI is <code>null</code>, then this returns <code>null</code>
* as well.
*/
Image getGrayImage(String imageURI) {
if (imageURI == null) {
return null;
}
try {
ImageDescriptor descriptor =
ImageDescriptor.createFromURL(new URL(imageURI));
Entry entry = getEntry(descriptor);
if (entry.grayImage == null) {
Image image = getImage(imageURI);
if (image != null) {
entry.grayImage =
new Image(null, image, SWT.IMAGE_GRAY);
}
}
return entry.grayImage;
} catch (MalformedURLException e) {
return new Image(null, getMissingImage(), SWT.IMAGE_GRAY);
}
}
/**
* Retrieves the image of the corresponding URI from the cache. If the
* image is not in the cache, then it tries to load the image. If the
* image URI is invalid, then it returns the missing image.
*
* @param imageURI
* The URI at which the image is located; may be <code>null</code>
* @return The image at the URI, or the missing image if it is invalid.
* If the URI is <code>null</code>, then this returns <code>null</code>
* as well.
*/
Image getImage(String imageURI) {
if (imageURI == null) {
return null;
}
try {
ImageDescriptor descriptor =
ImageDescriptor.createFromURL(new URL(imageURI));
Entry entry = getEntry(descriptor);
if (entry.image == null) {
entry.image = descriptor.createImage();
}
return entry.image;
} catch (MalformedURLException e) {
return getMissingImage();
}
}
/**
* Retrieves the missing image from the cache. If the image isn't
* available, then it loads it into the cache.
*
* @return The missing image; never <code>null</code>.
*/
Image getMissingImage() {
if (missingImage == null) {
ImageDescriptor descriptor =
ImageDescriptor.getMissingImageDescriptor();
Entry entry = getEntry(descriptor);
if (entry.image == null) {
entry.image = descriptor.createImage();
}
missingImage = entry.image;
}
return missingImage;
}
}
/** The global cache of images used by this contribution item. */
private static ImageCache globalImageCache;
/** Whether command contribution items should use colour icons. */
private static boolean useColourIcons = true;
/**
* Checks whether the given menu item belongs to a context menu (the one
* that pops up if the user presses the right mouse button).
*/
// private static boolean belongsToContextMenu(MenuItem item) {
// Menu menu = item.getParent();
// if (menu == null)
// return false;
// while (menu.getParentMenu() != null)
// menu = menu.getParentMenu();
// return (menu.getStyle() & SWT.BAR) == 0;
// }
/**
* Returns whether color icons should be used in toolbars.
*
* @return <code>true</code> if color icons should be used in toolbars;
* <code>false</code> otherwise.
*/
public static boolean getUseColourIconsInToolbars() {
return useColourIcons;
}
/**
* Convenience method for removing any optional accelerator text from the
* given string. The accelerator text appears at the end of the text, and
* is separated from the main part by a single tab character <code>'\t'</code>.
*
* @param text
* The text to be stripped; must not be <code>null</code>.
* @return The text sans accelerator; never <code>null</code>.
*/
public static String removeAcceleratorText(String text) {
int index = text.lastIndexOf('\t');
if (index == -1)
index = text.lastIndexOf('@');
if (index >= 0)
return text.substring(0, index);
return text;
}
/**
* Sets whether color icons should be used in toolbars.
*
* @param newValue
* <code>true</code> if color icons should be used in
* toolbars, <code>false</code> otherwise
*/
public static void setUseColourIconsInToolbars(boolean newValue) {
useColourIcons = newValue;
}
/** Listener for SWT button widget events. */
private Listener buttonListener;
/** The command that this contribution item represents. */
private ICommand command;
/** The help listener for the command. */
private HelpListener helpListener;
/** Listener for SWT menu item widget events. */
private Listener menuItemListener;
/** Listener for SWT tool item widget events. */
private Listener toolItemListener;
/**
* The widget created for this item; <code>null</code> before creation
* and after disposal.
*/
private Widget widget = null;
/**
* Creates a new contribution item from the given command. The id of the
* command is used as the id of the item.
*
* @param command
* The command from which this contribution item should be
* constructed; must not be <code>null</code>.
*/
public CommandContributionItem(ICommand commandToUse) {
super(commandToUse.getId());
command = commandToUse;
helpListener = WorkbenchHelp.createHelpListener(commandToUse);
}
/**
* Handles a change event on the command. This performs an update of the
* underlying widget to reflect the change.
*
* @param e
* The triggering event; must not be <code>null</code>.
*/
public void commandChanged(final CommandEvent e) {
// This code should be removed. Avoid using free asyncExec
if (isVisible() && widget != null) {
Display display = widget.getDisplay();
if (display.getThread() == Thread.currentThread()) {
update(e);
} else {
display.asyncExec(new Runnable() {
public void run() {
update(e);
}
});
}
}
}
/**
* Compares this command contribution item with another object. Two command
* contribution items are equal if they refer to the equivalent command.
*
* @param o
* The object with which to compare; may be <code>null</code>.
*/
public boolean equals(Object o) {
if (!(o instanceof CommandContributionItem)) {
return false;
}
return command.equals(((CommandContributionItem) o).command);
}
/**
* The <code>CommandContributionItem</code> implementation of this <code>IContributionItem</code>
* method creates an SWT <code>Button</code> for the command using the
* command's style. If the command's checked property has been set, the
* button is created and primed to the value of the checked property.
*
* @param parent
* The composite parent which this contribution should place
* itself on; must not be <code>null</code>.
*/
public void fill(Composite parent) {
if (widget == null && parent != null) {
int flags = SWT.PUSH;
if (command != null) {
// TODO STYLE
// if (action.getStyle() == IAction.AS_CHECK_BOX)
// flags = SWT.TOGGLE;
//if (action.getStyle() == IAction.AS_RADIO_BUTTON)
// flags = SWT.RADIO;
}
Button b = new Button(parent, flags);
b.setData(this);
b.addListener(SWT.Dispose, getButtonListener());
// Don't hook a dispose listener on the parent.
b.addListener(SWT.Selection, getButtonListener());
b.addHelpListener(helpListener);
widget = b;
update();
command.addCommandListener(this);
}
}
/**
* The <code>CommandContributionItem</code> implementation of this <code>IContributionItem</code>
* method creates an SWT <code>MenuItem</code> for the action using the
* command's style. If the command's checked property has been set, a
* button is created and primed to the value of the checked property. If
* the command's menu creator property has been set, a cascading submenu is
* created.
*
* @param parent
* The menu on which this contribution item should place itself;
* must not be <code>null</code>.
* @param index
* The index at which this contribution item should place
* itself. If it is a negative number, then this simply appends
* the item.
*/
public void fill(Menu parent, int index) {
if (widget == null && parent != null) {
Menu subMenu = null;
int flags = SWT.PUSH;
if (command != null) {
// TODO STYLE
//int style = action.getStyle();
//if (style == IAction.AS_CHECK_BOX)
// flags = SWT.CHECK;
//else if (style == IAction.AS_RADIO_BUTTON)
// flags = SWT.RADIO;
//else if (style == IAction.AS_DROP_DOWN_MENU) {
// IMenuCreator mc = action.getMenuCreator();
// if (mc != null) {
// subMenu = mc.getMenu(parent);
// flags = SWT.CASCADE;
// }
//}
}
MenuItem mi = null;
if (index >= 0)
mi = new MenuItem(parent, flags, index);
else
mi = new MenuItem(parent, flags);
widget = mi;
mi.setData(this);
mi.addListener(SWT.Dispose, getMenuItemListener());
mi.addListener(SWT.Selection, getMenuItemListener());
mi.addHelpListener(helpListener);
if (subMenu != null)
mi.setMenu(subMenu);
update();
command.addCommandListener(this);
}
}
/**
* The <code>CommandContributionItem</code> implementation of this <code>IContributionItem</code>
* method creates an SWT <code>ToolItem</code> for the command using the
* command's style. If the command's checked property has been set, a
* button is created and primed to the value of the checked property. If
* the command's menu creator property has been set, a drop-down tool item
* is created.
*
* @param parent
* The tool bar on which this contribution item should place
* itself; must not be <code>null</code>.
* @param index
* The index at which this contribution item should place
* itself. If it is a negative number, then this simply appends
* the item.
*/
public void fill(ToolBar parent, int index) {
if (widget == null && parent != null) {
int flags = SWT.PUSH;
if (command != null) {
// TODO STYLE
//int style = action.getStyle();
//if (style == IAction.AS_CHECK_BOX)
// flags = SWT.CHECK;
//else if (style == IAction.AS_RADIO_BUTTON)
// flags = SWT.RADIO;
//else if (style == IAction.AS_DROP_DOWN_MENU)
// flags = SWT.DROP_DOWN;
}
ToolItem ti = null;
if (index >= 0)
ti = new ToolItem(parent, flags, index);
else
ti = new ToolItem(parent, flags);
ti.setData(this);
ti.addListener(SWT.Selection, getToolItemListener());
ti.addListener(SWT.Dispose, getToolItemListener());
widget = ti;
update();
command.addCommandListener(this);
}
}
/**
* Returns the listener for SWT button widget events. This lazy initializes
* the listener.
*
* @return A listener for button events; never <code>null</code>.
*/
private Listener getButtonListener() {
if (buttonListener == null) {
buttonListener = new Listener() {
public void handleEvent(Event event) {
switch (event.type) {
case SWT.Dispose :
handleWidgetDispose(event);
break;
case SWT.Selection :
Widget ew = event.widget;
if (ew != null) {
handleWidgetSelection(
event,
((Button) ew).getSelection());
}
break;
}
}
};
}
return buttonListener;
}
/**
* Returns the command associated with this contribution item.
*
* @return The associated command; never <code>null</code>.
*/
public ICommand getCommand() {
return command;
}
/**
* Returns the image cache. The cache is global, and is shared by all
* command contribution items. This has the disadvantage that once an image
* is allocated, it is never freed until the display is disposed. However,
* it has the advantage that the same image in different contribution
* managers is only ever created once.
*
* @param The
* global image cache for command contribution items; never
* <code>null</code>.
*/
private ImageCache getImageCache() {
ImageCache cache = globalImageCache;
if (cache == null) {
globalImageCache = cache = new ImageCache();
Display display = Display.getDefault();
if (display != null) {
display.disposeExec(new Runnable() {
public void run() {
if (globalImageCache != null) {
globalImageCache.dispose();
globalImageCache = null;
}
}
});
}
}
return cache;
}
/**
* Returns the listener for SWT menu item widget events. The listener is
* lazy initialized when this method is first called.
*
* @return A listener for menu item events; never <code>null</code>.
*/
private Listener getMenuItemListener() {
if (menuItemListener == null) {
menuItemListener = new Listener() {
public void handleEvent(Event event) {
switch (event.type) {
case SWT.Dispose :
handleWidgetDispose(event);
break;
case SWT.Selection :
Widget ew = event.widget;
if (ew != null) {
handleWidgetSelection(
event,
((MenuItem) ew).getSelection());
}
break;
}
}
};
}
return menuItemListener;
}
/**
* Returns the listener for SWT tool item widget events. The listener is
* lazy initialized when this method is first called.
*
* @return A listener for tool item events; never <code>null</code>.
*/
private Listener getToolItemListener() {
if (toolItemListener == null) {
toolItemListener = new Listener() {
public void handleEvent(Event event) {
switch (event.type) {
case SWT.Dispose :
handleWidgetDispose(event);
break;
case SWT.Selection :
Widget ew = event.widget;
if (ew != null) {
handleWidgetSelection(
event,
((ToolItem) ew).getSelection());
}
break;
}
}
};
}
return toolItemListener;
}
/**
* Handles a widget dispose event for the widget corresponding to this
* item. This detaches the command listener, and disposes the menu creator
* if there is one.
*
* @param e
* The triggering dispose event; must not be <code>null</code>.
*/
private void handleWidgetDispose(Event e) {
if (e.widget == widget) {
// the item is being disposed
// TODO STYLE
//if (action.getStyle() == IAction.AS_DROP_DOWN_MENU) {
// IMenuCreator mc = action.getMenuCreator();
// if (mc != null) {
// mc.dispose();
// }
//}
command.removeCommandListener(this);
widget = null;
}
}
/**
* Handles a widget selection event.
*
* @param e
* The triggering selection event; must not be <code>null</code>
* @param selection
* Whether the item is becoming selected (as opposed to
* de-selected).
*/
private void handleWidgetSelection(Event e, boolean selection) {
Widget item = e.widget;
if (item != null) {
// TODO STYLE
//int style = item.getStyle();
//
//if ((style & (SWT.TOGGLE | SWT.CHECK)) != 0) {
// if (action.getStyle() == IAction.AS_CHECK_BOX) {
// action.setChecked(selection);
// }
//} else if ((style & SWT.RADIO) != 0) {
// if (action.getStyle() == IAction.AS_RADIO_BUTTON) {
// action.setChecked(selection);
// }
//} else if ((style & SWT.DROP_DOWN) != 0) {
// if (e.detail == 4) { // on drop-down button
// if (action.getStyle() == IAction.AS_DROP_DOWN_MENU) {
// IMenuCreator mc = action.getMenuCreator();
// ToolItem ti = (ToolItem) item;
// // we create the menu as a sub-menu of "dummy" so that
// // we can use
// // it in a cascading menu too.
// // If created on a SWT control we would get an SWT
// // error...
// //Menu dummy= new Menu(ti.getParent());
// //Menu m= mc.getMenu(dummy);
// //dummy.dispose();
// if (mc != null) {
// Menu m = mc.getMenu(ti.getParent());
// if (m != null) {
// // position the menu below the drop down item
// Rectangle b = ti.getBounds();
// Point p = ti.getParent().toDisplay(new Point(b.x, b.y +
// b.height));
// m.setLocation(p.x, p.y); // waiting for SWT
// // 0.42
// m.setVisible(true);
// return; // we don't fire the action
// }
// }
// }
// }
//}
// Ensure command is enabled first.
// See 1GAN3M6: ITPUI:WINNT - Any IAction in the workbench can be
// executed while disabled.
if (isEnabled(command)) {
boolean trace = "true".equalsIgnoreCase(Platform.getDebugOption("org.eclipse.jface/trace/actions")); //$NON-NLS-1$ //$NON-NLS-2$
long ms = System.currentTimeMillis();
try {
if (trace)
System.out.println("Running command: " + command.getName()); //$NON-NLS-1$
// TODO Dispatching commands
//command.runWithEvent(e);
if (trace)
System.out.println((System.currentTimeMillis() - ms) + " ms to run command: " + command.getName()); //$NON-NLS-1$
} catch (NotDefinedException nde) {
// TODO Warn the user that the command is now gone.
update(); // update the GUI
}
}
}
}
/*
* (non-Javadoc) Method declared on Object.
*/
public int hashCode() {
return command.hashCode();
}
/**
* The command item implementation of this <code>IContributionItem</code>
* method returns <code>true</code> for menu items and <code>false</code>
* for everything else.
*/
public boolean isDynamic() {
if (widget instanceof MenuItem) {
//Optimization. Only recreate the item is the check or radio style
// has changed.
// TODO STYLE
boolean itemIsCheck = (widget.getStyle() & SWT.CHECK) != 0;
//boolean actionIsCheck = getAction() != null &&
// getAction().getStyle() == IAction.AS_CHECK_BOX;
boolean itemIsRadio = (widget.getStyle() & SWT.RADIO) != 0;
//boolean actionIsRadio = getAction() != null &&
// getAction().getStyle() == IAction.AS_RADIO_BUTTON;
//return (itemIsCheck != actionIsCheck) || (itemIsRadio !=
// actionIsRadio);
return false;
}
return false;
}
/*
* (non-Javadoc) Method declared on IContributionItem.
*/
public boolean isEnabled() {
return isEnabled(command);
}
private static boolean isEnabled(ICommand command) {
try {
Map attributeValuesByName = command.getAttributeValuesByName();
if (attributeValuesByName.containsKey("enabled") //$NON-NLS-1$
&& !Boolean.TRUE.equals(attributeValuesByName.get("enabled"))) //$NON-NLS-1$
return false;
else
return true;
} catch (NotHandledException eNotHandled) {
return false;
}
}
/**
* Returns <code>true</code> if this item is allowed to enable, <code>false</code>
* otherwise.
*
* @return If this item is allowed to be enabled
* @since 2.0
*/
protected boolean isEnabledAllowed() {
if (getParent() == null)
return true;
Boolean value = getParent().getOverrides().getEnabled(this);
return (value == null) ? true : value.booleanValue();
}
/**
* Whether this contribution item should be visible.
*
* @return <code>true</code> if the command is active; <code>false</code>
* otherwise.
*/
public boolean isVisible() {
return true; // TODO visiblity should consider the activity and context managers.
}
/**
* The command item implementation of this <code>IContributionItem</code>
* method calls <code>update(null)</code>.
*/
public void update() {
update((CommandEvent) null);
}
/**
* Synchronizes the UI with the given property.
*
* @param event
* The event triggering the update (which specifies how much of
* the command changed). If <code>null</code>, then
* everything is updated.
*/
public void update(CommandEvent event) {
if (widget != null) {
ICommand currentCommand = getCommand();
// Determine what to do
boolean descriptionChanged = true;
boolean nameChanged = true;
boolean enabledChanged = true;
boolean checkedChanged = true;
if (event != null) {
descriptionChanged = event.hasDescriptionChanged();
nameChanged = event.hasNameChanged();
// TODO Enable change notification
//enabledChanged = event.hasEnabledChanged();
// TODO Checked state notification
//checkedChanged = event.hasSelectionChanged();
if ((event.hasDefinedChanged() && !currentCommand.isDefined())
/* TODO || (event.hasActiveChanged() && !currentCommand.isActive())*/) {
// TODO Dispose of the item?
}
}
try {
// Update the widget as a ToolItem
if (widget instanceof ToolItem) {
ToolItem ti = (ToolItem) widget;
if (descriptionChanged)
ti.setToolTipText(currentCommand.getDescription());
if (enabledChanged) {
boolean shouldBeEnabled =
isEnabled(currentCommand) && isEnabledAllowed();
if (ti.getEnabled() != shouldBeEnabled)
ti.setEnabled(shouldBeEnabled);
}
if (checkedChanged) {
// TODO Selection state
//boolean bv = command.isChecked();
//
//if (ti.getSelection() != bv)
// ti.setSelection(bv);
}
return;
}
// Update the widget as a MenuItem
if (widget instanceof MenuItem) {
MenuItem mi = (MenuItem) widget;
if (nameChanged) {
Integer accelerator = null;
String acceleratorText = null;
String name = null;
ExternalActionManager.ICallback callback =
ExternalActionManager.getInstance().getCallback();
if (callback != null) {
String commandId = currentCommand.getId();
if (commandId != null) {
accelerator =
callback.getAccelerator(commandId);
acceleratorText =
callback.getAcceleratorText(commandId);
}
}
IContributionManagerOverrides overrides = null;
if (getParent() != null)
overrides = getParent().getOverrides();
if (overrides != null)
name = getParent().getOverrides().getText(this);
//
//if (accelerator == null)
// // TODO Not necessary?
// accelerator = new Integer(command.getAccelerator());
mi.setAccelerator(accelerator.intValue());
if (name == null)
name = currentCommand.getName();
if (name == null)
name = ""; //$NON-NLS-1$
else
name = removeAcceleratorText(name);
if (acceleratorText == null)
mi.setText(name);
else
mi.setText(name + '\t' + acceleratorText);
}
if (enabledChanged) {
boolean shouldBeEnabled =
isEnabled(currentCommand) && isEnabledAllowed();
if (mi.getEnabled() != shouldBeEnabled)
mi.setEnabled(shouldBeEnabled);
}
if (checkedChanged) {
// TODO Selection state needed.
//boolean bv = command.isChecked();
//
//if (mi.getSelection() != bv)
// mi.setSelection(bv);
}
return;
}
// Update the widget as a button.
if (widget instanceof Button) {
Button button = (Button) widget;
if (nameChanged) {
String name = currentCommand.getName();
if (name != null)
button.setText(name);
}
if (descriptionChanged)
button.setToolTipText(currentCommand.getDescription());
if (enabledChanged) {
boolean shouldBeEnabled =
isEnabled(currentCommand) && isEnabledAllowed();
if (button.getEnabled() != shouldBeEnabled)
button.setEnabled(shouldBeEnabled);
}
if (checkedChanged) {
// TODO Selection state needed.
//boolean bv = action.isChecked();
//
//if (button.getSelection() != bv)
// button.setSelection(bv);
}
return;
}
} catch (NotDefinedException e) {
/*
* This shouldn't happen very often. It can only happen in a
* multi-threaded environment where a command becomes undefined
* on one thread while another thread is attempting an update.
*/
// TODO Dispose of the item.
}
}
}
}