org.eclipse.jface.action.ActionContributionItem Maven / Gradle / Ivy
The newest version!
/*******************************************************************************
* Copyright (c) 2000, 2020 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
* Jan-Ove Weichel - Bug 475879
* Christoph Läubrich - Bug 567905 - [JFace] ActionContributionItem violates IAction API contract for images
*******************************************************************************/
package org.eclipse.jface.action;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.commands.NotEnabledException;
import org.eclipse.jface.action.ExternalActionManager.IBindingManagerCallback;
import org.eclipse.jface.bindings.Trigger;
import org.eclipse.jface.bindings.TriggerSequence;
import org.eclipse.jface.bindings.keys.IKeyLookup;
import org.eclipse.jface.bindings.keys.KeyLookupFactory;
import org.eclipse.jface.bindings.keys.KeyStroke;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.resource.LocalResourceManager;
import org.eclipse.jface.resource.ResourceManager;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.Policy;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.util.Util;
import org.eclipse.pde.api.tools.annotations.NoExtend;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
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.Item;
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;
/**
* A contribution item which delegates to an action.
*
* This class may be instantiated; it is not intended to be subclassed.
*
*/
@NoExtend
public class ActionContributionItem extends ContributionItem {
/**
* Mode bit: Show text on tool items or buttons, even if an image is
* present. If this mode bit is not set, text is only shown on tool items if
* there is no image present.
*
* @since 3.0
*/
public static int MODE_FORCE_TEXT = 1;
/** a string inserted in the middle of text that has been shortened */
private static final String ellipsis = "..."; //$NON-NLS-1$
/**
* Stores the result of the action. False when the action returned failure.
*/
private Boolean result = null;
private static boolean USE_COLOR_ICONS = true;
/**
* Returns whether color icons should be used in toolbars.
*
* @return true
if color icons should be used in toolbars,
* false
otherwise
*/
public static boolean getUseColorIconsInToolbars() {
return USE_COLOR_ICONS;
}
/**
* Sets whether color icons should be used in toolbars.
*
* @param useColorIcons
* true
if color icons should be used in toolbars,
* false
otherwise
*/
public static void setUseColorIconsInToolbars(boolean useColorIcons) {
USE_COLOR_ICONS = useColorIcons;
}
/**
* The presentation mode.
*/
private int mode = 0;
/**
* The action.
*/
private IAction action;
/**
* The listener for changes to the text of the action contributed by an
* external source.
*/
private final IPropertyChangeListener actionTextListener = event -> update(event.getProperty());
/**
* Remembers all images in use by this contribution item
*/
private LocalResourceManager imageManager;
/**
* Listener for SWT button widget events.
*/
private Listener buttonListener;
/**
* Listener for SWT menu item widget events.
*/
private Listener menuItemListener;
/**
* Listener for action property change notifications.
*/
private final IPropertyChangeListener propertyListener = this::actionPropertyChange;
/**
* Listener for SWT tool item widget events.
*/
private Listener toolItemListener;
/**
* The widget created for this item; null
before creation and
* after disposal.
*/
private Widget widget = null;
private Listener menuCreatorListener;
/**
* Creates a new contribution item from the given action. The id of the
* action is used as the id of the item.
*
* @param action
* the action
*/
public ActionContributionItem(IAction action) {
super(action.getId());
this.action = action;
}
/**
* Handles a property change event on the action (forwarded by nested
* listener).
*/
private void actionPropertyChange(final PropertyChangeEvent 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.getProperty());
} else {
display.asyncExec(() -> update(e.getProperty()));
}
}
}
/**
* Compares this action contribution item with another object. Two action
* contribution items are equal if they refer to the identical Action.
*/
@Override
public boolean equals(Object o) {
if (!(o instanceof ActionContributionItem)) {
return false;
}
return action.equals(((ActionContributionItem) o).action);
}
/**
* The ActionContributionItem
implementation of this
* IContributionItem
method creates an SWT
* Button
for the action using the action's style. If the
* action's checked property has been set, the button is created and primed
* to the value of the checked property.
*/
@Override
public void fill(Composite parent) {
if (widget != null || parent == null) {
return;
}
int flags = SWT.PUSH;
if (action != null) {
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());
if (action.getHelpListener() != null) {
b.addHelpListener(action.getHelpListener());
}
widget = b;
update(null);
// Attach some extra listeners.
action.addPropertyChangeListener(propertyListener);
if (action != null) {
String commandId = action.getActionDefinitionId();
ExternalActionManager.ICallback callback = ExternalActionManager.getInstance().getCallback();
if ((callback != null) && (commandId != null)) {
callback.addPropertyChangeListener(commandId, actionTextListener);
}
}
}
/**
* The ActionContributionItem
implementation of this
* IContributionItem
method creates an SWT
* MenuItem
for the action using the action's style. If the
* action's checked property has been set, a button is created and primed to
* the value of the checked property. If the action's menu creator property
* has been set, a cascading submenu is created.
*/
@Override
public void fill(Menu parent, int index) {
if (widget != null || parent == null) {
return;
}
int flags = SWT.PUSH;
if (action != null) {
int style = action.getStyle();
switch (style) {
case IAction.AS_CHECK_BOX:
flags = SWT.CHECK;
break;
case IAction.AS_RADIO_BUTTON:
flags = SWT.RADIO;
break;
case IAction.AS_DROP_DOWN_MENU:
flags = SWT.CASCADE;
break;
default:
break;
}
}
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());
if (action.getHelpListener() != null) {
mi.addHelpListener(action.getHelpListener());
}
if (flags == SWT.CASCADE) {
// just create a proxy for now, if the user shows it then
// fill it in
Menu subMenu = new Menu(parent);
subMenu.addListener(SWT.Show, getMenuCreatorListener());
subMenu.addListener(SWT.Hide, getMenuCreatorListener());
mi.setMenu(subMenu);
}
update(null);
// Attach some extra listeners.
action.addPropertyChangeListener(propertyListener);
if (action != null) {
String commandId = action.getActionDefinitionId();
ExternalActionManager.ICallback callback = ExternalActionManager.getInstance().getCallback();
if ((callback != null) && (commandId != null)) {
callback.addPropertyChangeListener(commandId, actionTextListener);
}
}
}
/**
* The ActionContributionItem
implementation of this ,
* IContributionItem
method creates an SWT
* ToolItem
for the action using the action's style. If the
* action's checked property has been set, a button is created and primed to
* the value of the checked property. If the action's menu creator property
* has been set, a drop-down tool item is created.
*/
@Override
public void fill(ToolBar parent, int index) {
if (widget != null || parent == null) {
return;
}
int flags = SWT.PUSH;
if (action != null) {
int style = action.getStyle();
switch (style) {
case IAction.AS_CHECK_BOX:
flags = SWT.CHECK;
break;
case IAction.AS_RADIO_BUTTON:
flags = SWT.RADIO;
break;
case IAction.AS_DROP_DOWN_MENU:
flags = SWT.DROP_DOWN;
break;
default:
break;
}
}
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(null);
// Attach some extra listeners.
action.addPropertyChangeListener(propertyListener);
if (action != null) {
String commandId = action.getActionDefinitionId();
ExternalActionManager.ICallback callback = ExternalActionManager.getInstance().getCallback();
if ((callback != null) && (commandId != null)) {
callback.addPropertyChangeListener(commandId, actionTextListener);
}
}
}
/**
* Returns the action associated with this contribution item.
*
* @return the action
*/
public IAction getAction() {
return action;
}
/**
* Returns the listener for SWT button widget events.
*
* @return a listener for button events
*/
private Listener getButtonListener() {
if (buttonListener == null) {
buttonListener = 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 listener for SWT menu item widget events.
*
* @return a listener for menu item events
*/
private Listener getMenuItemListener() {
if (menuItemListener == null) {
menuItemListener = 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 presentation mode, which is the bitwise-or of the
* MODE_*
constants. The default mode setting is 0, meaning
* that for menu items, both text and image are shown (if present), but for
* tool items, the text is shown only if there is no image.
*
* @return the presentation mode settings
*
* @since 3.0
*/
public int getMode() {
return mode;
}
/**
* Returns the listener for SWT tool item widget events.
*
* @return a listener for tool item events
*/
private Listener getToolItemListener() {
if (toolItemListener == null) {
toolItemListener = 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.
*/
private void handleWidgetDispose(Event e) {
// Check if our widget is the one being disposed.
if (e.widget != widget) {
return; // Not for us.
}
// Dispose of the menu creator.
if (action.getStyle() == IAction.AS_DROP_DOWN_MENU && menuCreatorCalled) {
IMenuCreator mc = action.getMenuCreator();
if (mc != null) {
mc.dispose();
}
}
// Unhook all of the listeners.
action.removePropertyChangeListener(propertyListener);
if (action != null) {
String commandId = action.getActionDefinitionId();
ExternalActionManager.ICallback callback = ExternalActionManager.getInstance().getCallback();
if ((callback != null) && (commandId != null)) {
callback.removePropertyChangeListener(commandId, actionTextListener);
}
}
// Clear the widget field.
widget = null;
disposeOldImages();
}
/**
* Handles a widget selection event.
*/
private void handleWidgetSelection(Event e, boolean selection) {
Widget item = e.widget;
if (item == null || item != widget) {
return; // Not for us
}
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 == SWT.ARROW) { // on drop-down button
if (action.getStyle() == IAction.AS_DROP_DOWN_MENU) {
IMenuCreator mc = action.getMenuCreator();
menuCreatorCalled = true;
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
Point point = ti.getParent().toDisplay(new Point(e.x, e.y));
m.setLocation(point.x, point.y); // waiting
// for SWT
// 0.42
m.setVisible(true);
return; // we don't fire the action
}
}
}
}
}
ExternalActionManager.IExecuteCallback callback = null;
String actionDefinitionId = action.getActionDefinitionId();
if (actionDefinitionId != null) {
Object obj = ExternalActionManager.getInstance().getCallback();
if (obj instanceof ExternalActionManager.IExecuteCallback) {
callback = (ExternalActionManager.IExecuteCallback) obj;
}
}
// Ensure action is enabled first.
// See 1GAN3M6: ITPUI:WINNT - Any IAction in the workbench can be
// executed while disabled.
if (action.isEnabled()) {
boolean trace = Policy.TRACE_ACTIONS;
long ms = 0L;
if (trace) {
ms = System.currentTimeMillis();
System.out.println("Running action: " + action.getText()); //$NON-NLS-1$
}
IPropertyChangeListener resultListener = null;
if (callback != null) {
resultListener = event -> {
// Check on result
if (event.getProperty().equals(IAction.RESULT)) {
if (event.getNewValue() instanceof Boolean) {
result = (Boolean) event.getNewValue();
}
}
};
action.addPropertyChangeListener(resultListener);
callback.preExecute(action, e);
}
action.runWithEvent(e);
if (callback != null) {
if (result == null || result.equals(Boolean.TRUE)) {
callback.postExecuteSuccess(action, Boolean.TRUE);
} else {
callback.postExecuteFailure(action,
new ExecutionException(action.getText() + " returned failure.")); //$NON-NLS-1$
}
}
if (resultListener != null) {
result = null;
action.removePropertyChangeListener(resultListener);
}
if (trace) {
System.out.println((System.currentTimeMillis() - ms) + " ms to run action: " + action.getText()); //$NON-NLS-1$
}
} else if (callback != null) {
callback.notEnabled(action, new NotEnabledException(action.getText() + " is not enabled.")); //$NON-NLS-1$
}
}
@Override
public int hashCode() {
return action.hashCode();
}
/**
* Returns whether the given action has any images.
*
* @param actionToCheck
* the action
* @return true
if the action has any images,
* false
if not
*/
private boolean hasImages(IAction actionToCheck) {
return actionToCheck.getImageDescriptor() != null
|| actionToCheck.getHoverImageDescriptor() != null
|| actionToCheck.getDisabledImageDescriptor() != null;
}
/**
* Returns whether the command corresponding to this action is active.
*/
private boolean isCommandActive() {
IAction actionToCheck = getAction();
if (actionToCheck != null) {
String commandId = actionToCheck.getActionDefinitionId();
ExternalActionManager.ICallback callback = ExternalActionManager
.getInstance().getCallback();
if (callback != null) {
return callback.isActive(commandId);
}
}
return true;
}
/**
* The action item implementation of this IContributionItem
* method returns true
for menu items and false
* for everything else.
*/
@Override
public boolean isDynamic() {
if (widget instanceof MenuItem) {
// Optimization. Only recreate the item is the check or radio style
// has changed.
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;
}
@Override
public boolean isEnabled() {
return action != null && action.isEnabled();
}
/**
* Returns true
if this item is allowed to enable,
* false
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();
}
/**
* The ActionContributionItem
implementation of this
* ContributionItem
method extends the super implementation
* by also checking whether the command corresponding to this action is
* active.
*/
@Override
public boolean isVisible() {
return super.isVisible() && isCommandActive();
}
/**
* Sets the presentation mode, which is the bitwise-or of the
* MODE_*
constants.
*
* @param mode
* the presentation mode settings
*
* @since 3.0
*/
public void setMode(int mode) {
this.mode = mode;
update();
}
/**
* The action item implementation of this IContributionItem
* method calls update(null)
.
*/
@Override
public final void update() {
update(null);
}
/**
* Synchronizes the UI with the given property.
*
* @param propertyName
* the name of the property, or null
meaning all
* applicable properties
*/
@Override
public void update(String propertyName) {
if (widget == null) {
return;
}
// Determine what to do.
boolean textChanged = propertyName == null || propertyName.equals(IAction.TEXT);
boolean imageChanged = propertyName == null || propertyName.equals(IAction.IMAGE);
boolean tooltipTextChanged = propertyName == null || propertyName.equals(IAction.TOOL_TIP_TEXT);
boolean enableStateChanged = propertyName == null || propertyName.equals(IAction.ENABLED);
boolean checkChanged = (action.getStyle() == IAction.AS_CHECK_BOX
|| action.getStyle() == IAction.AS_RADIO_BUTTON)
&& (propertyName == null || propertyName.equals(IAction.CHECKED));
if (widget instanceof ToolItem) {
updateToolItem((ToolItem) widget, textChanged, imageChanged, tooltipTextChanged, enableStateChanged,
checkChanged);
} else if (widget instanceof MenuItem) {
updateMenuItem((MenuItem) widget, textChanged, imageChanged, tooltipTextChanged, enableStateChanged,
checkChanged);
} else if (widget instanceof Button) {
updateButton((Button) widget, textChanged, imageChanged, tooltipTextChanged, enableStateChanged,
checkChanged);
}
}
private void updateToolItem(ToolItem ti, boolean textChanged, boolean imageChanged, boolean tooltipTextChanged,
boolean enableStateChanged, boolean checkChanged) {
String text = action.getText();
// The set text is shown only if there is no image or if forced by
// MODE_FORCE_TEXT.
boolean showText = text != null && ((getMode() & MODE_FORCE_TEXT) != 0 || !hasImages(action));
// Only do the trimming if the text will be used
if (showText && text != null) {
text = Action.removeAcceleratorText(text);
text = Action.removeMnemonics(text);
}
if (textChanged) {
String textToSet = showText ? text : ""; //$NON-NLS-1$
boolean rightStyle = (ti.getParent().getStyle() & SWT.RIGHT) != 0;
if (rightStyle || !ti.getText().equals(textToSet)) {
// In addition to being required to update the text if it gets nulled out in the
// action, this is also a workaround for bug 50151: Using SWT.RIGHT on a ToolBar
// leaves blank space.
ti.setText(textToSet);
}
}
if (imageChanged) {
// Only substitute a missing image if it has no text.
updateImages(!showText);
}
if (tooltipTextChanged || textChanged) {
String toolTip = action.getToolTipText();
if ((toolTip == null) || (toolTip.isEmpty())) {
toolTip = text;
}
ExternalActionManager.ICallback callback = ExternalActionManager.getInstance().getCallback();
String commandId = action.getActionDefinitionId();
if ((callback != null) && (commandId != null) && (toolTip != null)) {
String acceleratorText = callback.getAcceleratorText(commandId);
if (acceleratorText != null && acceleratorText.length() != 0) {
toolTip = JFaceResources.format("Toolbar_Tooltip_Accelerator", //$NON-NLS-1$
toolTip, acceleratorText);
}
}
// If the text is showing, then only set the tooltip if different.
if (!showText || toolTip != null && !toolTip.equals(text)) {
ti.setToolTipText(toolTip);
} else {
ti.setToolTipText(null);
}
}
if (enableStateChanged) {
boolean shouldBeEnabled = action.isEnabled() && isEnabledAllowed();
if (ti.getEnabled() != shouldBeEnabled) {
ti.setEnabled(shouldBeEnabled);
}
}
if (checkChanged) {
boolean bv = action.isChecked();
if (ti.getSelection() != bv) {
ti.setSelection(bv);
}
}
}
private void updateMenuItemText(MenuItem mi) {
String acceleratorText = null;
IAction updatedAction = getAction();
String text = null;
int accelerator = updatedAction.getAccelerator();
ExternalActionManager.ICallback callback = ExternalActionManager.getInstance().getCallback();
// Block accelerators that are already in use.
if ((accelerator != 0) && (callback != null) && (callback.isAcceleratorInUse(accelerator))) {
accelerator = 0;
}
/*
* Process accelerators on GTK in a special way to avoid Bug 42009. We will
* override the native input method by allowing these reserved accelerators to
* be placed on the menu. We will only do this for "Ctrl+Shift+[0-9A-FU]".
*/
final String commandId = updatedAction.getActionDefinitionId();
if ((Util.isGtk()) && (callback instanceof IBindingManagerCallback) && (commandId != null)) {
final IBindingManagerCallback bindingManagerCallback = (IBindingManagerCallback) callback;
final IKeyLookup lookup = KeyLookupFactory.getDefault();
final TriggerSequence[] triggerSequences = bindingManagerCallback.getActiveBindingsFor(commandId);
for (final TriggerSequence triggerSequence : triggerSequences) {
final Trigger[] triggers = triggerSequence.getTriggers();
if (triggers.length == 1) {
final Trigger trigger = triggers[0];
if (trigger instanceof KeyStroke) {
final KeyStroke currentKeyStroke = (KeyStroke) trigger;
final int currentNaturalKey = currentKeyStroke.getNaturalKey();
if ((currentKeyStroke.getModifierKeys() == (lookup.getCtrl() | lookup.getShift()))
&& ((currentNaturalKey >= '0' && currentNaturalKey <= '9')
|| (currentNaturalKey >= 'A' && currentNaturalKey <= 'F')
|| (currentNaturalKey == 'U'))) {
accelerator = currentKeyStroke.getModifierKeys() | currentNaturalKey;
acceleratorText = triggerSequence.format();
break;
}
}
}
}
}
if (accelerator == 0) {
if ((callback != null) && (commandId != null)) {
acceleratorText = callback.getAcceleratorText(commandId);
}
}
IContributionManagerOverrides overrides = null;
if (getParent() != null) {
overrides = getParent().getOverrides();
}
if (overrides != null) {
text = getParent().getOverrides().getText(this);
}
mi.setAccelerator(accelerator);
if (text == null) {
text = updatedAction.getText();
}
if (text != null && acceleratorText == null) {
// Use extracted accelerator text in case accelerator cannot be fully
// represented in one int (e.g. multi-stroke keys).
acceleratorText = LegacyActionTools.extractAcceleratorText(text);
if (acceleratorText == null && accelerator != 0) {
acceleratorText = Action.convertAccelerator(accelerator);
}
}
if (text == null) {
text = ""; //$NON-NLS-1$
} else {
text = Action.removeAcceleratorText(text);
}
if (acceleratorText == null) {
mi.setText(text);
} else {
mi.setText(text + '\t' + acceleratorText);
}
}
private void updateMenuItem(MenuItem mi, boolean textChanged, boolean imageChanged, boolean tooltipTextChanged,
boolean enableStateChanged, boolean checkChanged) {
if (textChanged) {
updateMenuItemText(mi);
}
if (tooltipTextChanged) {
mi.setToolTipText(action.getToolTipText());
}
if (imageChanged) {
updateImages(false);
}
if (enableStateChanged) {
boolean shouldBeEnabled = action.isEnabled() && isEnabledAllowed();
if (mi.getEnabled() != shouldBeEnabled) {
mi.setEnabled(shouldBeEnabled);
}
}
if (checkChanged) {
boolean bv = action.isChecked();
if (mi.getSelection() != bv) {
mi.setSelection(bv);
}
}
}
private void updateButton(Button button, boolean textChanged, boolean imageChanged, boolean tooltipTextChanged,
boolean enableStateChanged, boolean checkChanged) {
if (imageChanged) {
updateImages(false);
}
if (textChanged) {
String text = action.getText();
boolean showText = text != null && ((getMode() & MODE_FORCE_TEXT) != 0 || !hasImages(action));
// Only do the trimming if the text will be used.
if (showText) {
text = Action.removeAcceleratorText(text);
}
String textToSet = showText ? text : ""; //$NON-NLS-1$
button.setText(textToSet);
}
if (tooltipTextChanged) {
button.setToolTipText(action.getToolTipText());
}
if (enableStateChanged) {
boolean shouldBeEnabled = action.isEnabled() && isEnabledAllowed();
if (button.getEnabled() != shouldBeEnabled) {
button.setEnabled(shouldBeEnabled);
}
}
if (checkChanged) {
boolean bv = action.isChecked();
if (button.getSelection() != bv) {
button.setSelection(bv);
}
}
}
/**
* Updates the images for this action.
*
* @param forceImage
* true
if some form of image is compulsory, and
* false
if it is acceptable for this item to have
* no image
* @return true
if there are images for this action,
* false
if not
*/
private boolean updateImages(boolean forceImage) {
ResourceManager parentResourceManager = JFaceResources.getResources();
if (widget instanceof ToolItem) {
ImageDescriptor image = action.getImageDescriptor();
ImageDescriptor hoverImage = action.getHoverImageDescriptor();
ImageDescriptor disabledImage = action.getDisabledImageDescriptor();
// Make sure there is a valid image in case images are forced.
if (image == null && forceImage) {
image = ImageDescriptor.getMissingImageDescriptor();
}
// in grayscale mode, use the regular image if no explicit hover is given
if (hoverImage == null && !USE_COLOR_ICONS) {
hoverImage = image;
}
// If there is no disabled image, but there is a regular image generate a
// disabled one
if (disabledImage == null && image != null) {
disabledImage = ImageDescriptor.createWithFlags(image, SWT.IMAGE_DISABLE);
}
if (image != null && !USE_COLOR_ICONS) {
image = ImageDescriptor.createWithFlags(image, SWT.IMAGE_GRAY);
}
// Create a local resource manager to remember the images we've
// allocated for this tool item
LocalResourceManager localManager = new LocalResourceManager(parentResourceManager);
// performance: more efficient in SWT to set disabled and hot image
// before regular image
((ToolItem) widget).setDisabledImage(
disabledImage == null ? null : localManager.createImageWithDefault(disabledImage));
((ToolItem) widget)
.setHotImage(hoverImage == null ? null : localManager.createImageWithDefault(hoverImage));
((ToolItem) widget).setImage(image == null ? null : localManager.createImageWithDefault(image));
// Now that we're no longer referencing the old images, clear them
// out.
disposeOldImages();
imageManager = localManager;
return image != null;
} else if (widget instanceof Item || widget instanceof Button) {
// Use hover image if there is one, otherwise use regular image.
ImageDescriptor image = action.getHoverImageDescriptor();
if (image == null) {
image = action.getImageDescriptor();
}
// Make sure there is a valid image.
if (image == null && forceImage) {
image = ImageDescriptor.getMissingImageDescriptor();
}
// Create a local resource manager to remember the images we've
// allocated for this widget
LocalResourceManager localManager = new LocalResourceManager(
parentResourceManager);
if (widget instanceof Item) {
((Item) widget).setImage(image == null ? null : localManager
.createImageWithDefault(image));
} else if (widget instanceof Button) {
((Button) widget).setImage(image == null ? null : localManager
.createImageWithDefault(image));
}
// Now that we're no longer referencing the old images, clear them
// out.
disposeOldImages();
imageManager = localManager;
return image != null;
}
return false;
}
/**
* Dispose any images allocated for this contribution item
*/
private void disposeOldImages() {
if (imageManager != null) {
imageManager.dispose();
imageManager = null;
}
}
/**
* Shorten the given text t
so that its length doesn't exceed
* the width of the given ToolItem.The default implementation replaces
* characters in the center of the original string with an ellipsis ("...").
* Override if you need a different strategy.
*
* @param textValue
* the text to shorten
* @param item
* the tool item the text belongs to
* @return the shortened string
*/
protected String shortenText(String textValue, ToolItem item) {
if (textValue == null) {
return null;
}
GC gc = new GC(item.getParent());
int maxWidth = item.getImage().getBounds().width * 4;
if (gc.textExtent(textValue).x < maxWidth) {
gc.dispose();
return textValue;
}
for (int i = textValue.length(); i > 0; i--) {
String test = textValue.substring(0, i);
test = test + ellipsis;
if (gc.textExtent(test).x < maxWidth) {
gc.dispose();
return test;
}
}
gc.dispose();
// If for some reason we fall through abort
return textValue;
}
@Override
public void dispose() {
if (widget != null) {
widget.dispose();
widget = null;
}
holdMenu = null;
}
/**
* Handle show and hide on the proxy menu for IAction.AS_DROP_DOWN_MENU
* actions.
*
* @return the appropriate listener
* @since 3.4
*/
private Listener getMenuCreatorListener() {
if (menuCreatorListener == null) {
menuCreatorListener = event -> {
switch (event.type) {
case SWT.Show:
handleShowProxy((Menu) event.widget);
break;
case SWT.Hide:
handleHideProxy((Menu) event.widget);
break;
}
};
}
return menuCreatorListener;
}
/**
* This is the easiest way to hold the menu until we can swap it in to the
* proxy.
*/
private Menu holdMenu = null;
private boolean menuCreatorCalled = false;
/**
* The proxy menu is being shown, we better get the real menu.
*
* @param proxy
* the proxy menu
* @since 3.4
*/
private void handleShowProxy(Menu proxy) {
proxy.removeListener(SWT.Show, getMenuCreatorListener());
IMenuCreator mc = action.getMenuCreator();
menuCreatorCalled = true;
if (mc == null) {
return;
}
holdMenu = mc.getMenu(proxy.getParentMenu());
if (holdMenu == null) {
return;
}
copyMenu(holdMenu, proxy);
}
/**
* Create MenuItems in the proxy menu that can execute the real menu items
* if selected. Create proxy menus for any real item submenus.
*
* @param realMenu
* the real menu to copy from
* @param proxy
* the proxy menu to populate
* @since 3.4
*/
private void copyMenu(Menu realMenu, Menu proxy) {
if (realMenu.isDisposed() || proxy.isDisposed()) {
return;
}
// we notify the real menu so it can populate itself if it was
// listening for SWT.Show
realMenu.notifyListeners(SWT.Show, null);
final Listener passThrough = event -> {
if (!event.widget.isDisposed()) {
Widget realItem = (Widget) event.widget.getData();
if (!realItem.isDisposed()) {
int style = event.widget.getStyle();
if (event.type == SWT.Selection
&& ((style & (SWT.TOGGLE | SWT.CHECK | SWT.RADIO)) != 0)
&& realItem instanceof MenuItem) {
((MenuItem) realItem)
.setSelection(((MenuItem) event.widget)
.getSelection());
}
event.widget = realItem;
realItem.notifyListeners(event.type, event);
}
}
};
MenuItem[] items = realMenu.getItems();
for (final MenuItem realItem : items) {
final MenuItem proxyItem = new MenuItem(proxy, realItem.getStyle());
proxyItem.setData(realItem);
proxyItem.setAccelerator(realItem.getAccelerator());
proxyItem.setEnabled(realItem.getEnabled());
proxyItem.setImage(realItem.getImage());
proxyItem.setSelection(realItem.getSelection());
proxyItem.setText(realItem.getText());
// pass through any events
proxyItem.addListener(SWT.Selection, passThrough);
proxyItem.addListener(SWT.Arm, passThrough);
proxyItem.addListener(SWT.Help, passThrough);
final Menu itemMenu = realItem.getMenu();
if (itemMenu != null) {
// create a proxy for any sub menu items
final Menu subMenu = new Menu(proxy);
subMenu.setData(itemMenu);
proxyItem.setMenu(subMenu);
subMenu.addListener(SWT.Show, new Listener() {
@Override
public void handleEvent(Event event) {
event.widget.removeListener(SWT.Show, this);
if (event.type == SWT.Show) {
copyMenu(itemMenu, subMenu);
}
}
});
subMenu.addListener(SWT.Help, passThrough);
subMenu.addListener(SWT.Hide, passThrough);
}
}
}
/**
* The proxy menu is being hidden, so we need to make it go away.
*
* @param proxy
* the proxy menu
* @since 3.4
*/
private void handleHideProxy(final Menu proxy) {
proxy.removeListener(SWT.Hide, getMenuCreatorListener());
proxy.getDisplay().asyncExec(() -> {
if (!proxy.isDisposed()) {
MenuItem parentItem = proxy.getParentItem();
proxy.dispose();
parentItem.setMenu(holdMenu);
}
if (holdMenu != null && !holdMenu.isDisposed()) {
holdMenu.notifyListeners(SWT.Hide, null);
}
holdMenu = null;
});
}
/**
* Return the widget associated with this contribution item. It should not
* be cached, as it can be disposed and re-created by its containing
* ContributionManager, which controls all of the widgets lifecycle methods.
*
* This can be used to set layout data on the widget if appropriate. The
* actual type of the widget can be any valid control for this
* ContributionItem's current ContributionManager.
*
*
* @return the widget, or null
depending on the lifecycle.
* @since 3.4
*/
public Widget getWidget() {
return widget;
}
}