/*******************************************************************************
 * Copyright (c) 2000, 2015 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
 *******************************************************************************/
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 = event -> actionPropertyChange(event);

	/**
	 * 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) {
			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) {
			int flags = SWT.PUSH;
			if (action != null) {
				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.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());
			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) {
			int flags = SWT.PUSH;
			if (action != null) {
				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(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) {
			// 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) {
			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();
						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) {
			// 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)
					|| propertyName
							.equals(IContributionManagerOverrides.P_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) {
				ToolItem ti = (ToolItem) widget;
				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.length() == 0)) {
						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);
					}
				}
				return;
			}

			if (widget instanceof MenuItem) {
				MenuItem mi = (MenuItem) widget;

				if (textChanged) {
					int accelerator = 0;
					String acceleratorText = null;
					IAction updatedAction = getAction();
					String text = null;
					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);
					}
				}

				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);
					}
				}

				return;
			}

			if (widget instanceof Button) {
				Button button = (Button) widget;

				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);
					}
				}
				return;
			}
		}
	}

	/**
	 * 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) {
			if (USE_COLOR_ICONS) {
				ImageDescriptor image = action.getHoverImageDescriptor();
				if (image == null) {
					image = action.getImageDescriptor();
				}
				ImageDescriptor disabledImage = action
						.getDisabledImageDescriptor();

				// Make sure there is a valid image.
				if (image == null && forceImage) {
					image = ImageDescriptor.getMissingImageDescriptor();
				}

				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).setImage(image == null ? null
						: localManager.createImageWithDefault(image));

				disposeOldImages();
				imageManager = localManager;

				return image != null;
			}
			ImageDescriptor image = action.getImageDescriptor();
			ImageDescriptor hoverImage = action.getHoverImageDescriptor();
			ImageDescriptor disabledImage = action.getDisabledImageDescriptor();

			// If there is no regular image, but there is a hover image,
			// convert the hover image to gray and use it as the regular image.
			if (image == null && hoverImage != null) {
				image = ImageDescriptor.createWithFlags(action
						.getHoverImageDescriptor(), SWT.IMAGE_GRAY);
			} else {
				// If there is no hover image, use the regular image as the
				// hover image,
				// and convert the regular image to gray
				if (hoverImage == null && image != null) {
					hoverImage = image;
					image = ImageDescriptor.createWithFlags(action
							.getImageDescriptor(), SWT.IMAGE_GRAY);
				}
			}

			// Make sure there is a valid image.
			if (hoverImage == null && image == null && forceImage) {
				image = ImageDescriptor.getMissingImageDescriptor();
			}

			// 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;
	}
}
