/*******************************************************************************
 * 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 <janove.weichel@vogella.com> - 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.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.
 * <p>
 * This class may be instantiated; it is not intended to be subclassed.
 * </p>
 * @noextend This class is not intended to be subclassed by clients.
 */
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 <code>true</code> if color icons should be used in toolbars,
	 *         <code>false</code> otherwise
	 */
	public static boolean getUseColorIconsInToolbars() {
		return USE_COLOR_ICONS;
	}

	/**
	 * Sets whether color icons should be used in toolbars.
	 *
	 * @param useColorIcons
	 *            <code>true</code> if color icons should be used in toolbars,
	 *            <code>false</code> 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; <code>null</code> 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 <code>ActionContributionItem</code> implementation of this
	 * <code>IContributionItem</code> method creates an SWT
	 * <code>Button</code> 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 <code>ActionContributionItem</code> implementation of this
	 * <code>IContributionItem</code> method creates an SWT
	 * <code>MenuItem</code> 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 <code>ActionContributionItem</code> implementation of this ,
	 * <code>IContributionItem</code> method creates an SWT
	 * <code>ToolItem</code> 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
	 * <code>MODE_*</code> 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 <code>true</code> if the action has any images,
	 *         <code>false</code> 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 <code>IContributionItem</code>
	 * method returns <code>true</code> for menu items and <code>false</code>
	 * 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 <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();
	}

	/**
	 * The <code>ActionContributionItem</code> implementation of this
	 * <code>ContributionItem</code> 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
	 * <code>MODE_*</code> 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 <code>IContributionItem</code>
	 * method calls <code>update(null)</code>.
	 */
	@Override
	public final void update() {
		update(null);
	}

	/**
	 * Synchronizes the UI with the given property.
	 *
	 * @param propertyName
	 *            the name of the property, or <code>null</code> 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
	 *            <code>true</code> if some form of image is compulsory, and
	 *            <code>false</code> if it is acceptable for this item to have
	 *            no image
	 * @return <code>true</code> if there are images for this action,
	 *         <code>false</code> 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 <code>t</code> 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.
	 * <p>
	 * 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.
	 * </p>
	 *
	 * @return the widget, or <code>null</code> depending on the lifecycle.
	 * @since 3.4
	 */
	public Widget getWidget() {
		return widget;
	}
}
