com.github.haixing_hu.swt.menu.MenuManagerEx.java Source code

Java tutorial

Introduction

Here is the source code for com.github.haixing_hu.swt.menu.MenuManagerEx.java

Source

/******************************************************************************
 *
 * Copyright (c) 2014  Haixing Hu
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
 * and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Haixing Hu (https://github.com/Haixing-Hu/) - Initial implementation and API.
 *
 ******************************************************************************/

package com.github.haixing_hu.swt.menu;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.core.runtime.ListenerList;
import org.eclipse.jface.action.ContributionManager;
import org.eclipse.jface.action.ExternalActionManager;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IContributionItem;
import org.eclipse.jface.action.IContributionManager;
import org.eclipse.jface.action.IContributionManagerOverrides;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuListener2;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.SubContributionItem;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.resource.LocalResourceManager;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.MenuAdapter;
import org.eclipse.swt.events.MenuEvent;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.CoolBar;
import org.eclipse.swt.widgets.Decorations;
import org.eclipse.swt.widgets.Item;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.ToolBar;

import com.github.haixing_hu.swt.action.ActionContributionItemEx;
import com.github.haixing_hu.swt.action.ActionEx;

/**
 * A menu manager is a contribution manager which realizes itself and its items
 * in a menu control; either as a menu bar, a sub-menu, or a context menu.
 * <p>
 * This class may be instantiated; it may also be subclassed.
 * </p>
 * <p>
 * This class is a modified version of the {@link org.eclipse.jface.action.MenuManager},
 * providing the following enhancements:
 * <ul>
 * <li>It provides a function to create a sub-menu for a parent menu.</li>
 * <li>Controls whether to show the image on the menu items
 * depending on the modified version of action's "showImage" property.</li>
 * <li>Append "..." to the title of the menu item if the modified
 * version of action's "showDialog" property is true.
 * </ul>
 *
 * @author Haixing Hu
 */
public class MenuManagerEx extends ContributionManager implements IMenuManager {

    private static final String MANAGER_KEY = "org.eclipse.jface.action.MenuManager.managerKey"; //$NON-NLS-1$

    /**
     * The menu id.
     */
    private final String id;

    /**
     * List of registered menu listeners (element type: <code>IMenuListener</code>
     * ).
     */
    private final ListenerList listeners = new ListenerList();

    /**
     * The menu control; <code>null</code> before creation and after disposal.
     */
    private Menu menu = null;

    /**
     * The menu item widget; <code>null</code> before creation and after disposal.
     * This field is used when this menu manager is a sub-menu.
     */
    private MenuItem menuItem;

    /**
     * The text for a sub-menu.
     */
    private final String menuText;

    /**
     * The image for a sub-menu.
     */
    private final ImageDescriptor image;

    /**
     * A resource manager to remember all of the images that have been used by
     * this menu.
     */
    private LocalResourceManager imageManager;

    /**
     * The overrides for items of this manager
     */
    private IContributionManagerOverrides overrides;

    /**
     * The parent contribution manager.
     */
    private IContributionManager parent;

    /**
     * Indicates whether <code>removeAll</code> should be called just before the
     * menu is displayed.
     */
    private boolean removeAllWhenShown = false;

    /**
     * Indicates this item is visible in its manager; <code>true</code> by
     * default.
     *
     * @since 3.3
     */
    protected boolean visible = true;

    /**
     * allows a submenu to display a shortcut key. This is often used with the
     * QuickMenu command or action which can pop up a menu using the shortcut.
     */
    private String definitionId = null;

    /**
     * Sets whether to show the image of the action. The default value of this
     * attribute is <code>true</code>.
     *
     * @author Haixing Hu
     */
    private boolean showImage = true;

    /**
     * Creates a menu manager. The text and id are <code>null</code>. Typically
     * used for creating a context menu, where it doesn't need to be referred to
     * by id.
     */
    public MenuManagerEx() {
        this(null, null, null);
    }

    /**
     * Creates a menu manager. The text and id are <code>null</code>. Typically
     * used for creating a context menu, where it doesn't need to be referred to
     * by id.
     *
     * @param showImage
     *          indicates whether the new manager manager should display images on
     *          its children menu items.
     */
    public MenuManagerEx(boolean showImage) {
        this(null, null, null);
        this.showImage = showImage;
    }

    /**
     * Creates a menu manager with the given text. The id of the menu is
     * <code>null</code>. Typically used for creating a sub-menu, where it doesn't
     * need to be referred to by id.
     *
     * @param text
     *          the text for the menu, or <code>null</code> if none
     */
    public MenuManagerEx(String text) {
        this(text, null, null);
    }

    /**
     * Creates a menu manager with the given text and id. Typically used for
     * creating a sub-menu, where it needs to be referred to by id.
     *
     * @param text
     *          the text for the menu, or <code>null</code> if none
     * @param id
     *          the menu id, or <code>null</code> if it is to have no id
     */
    public MenuManagerEx(String text, String id) {
        this(text, null, id);
    }

    /**
     * Creates a menu manager with the given text, image, and id. Typically used
     * for creating a sub-menu, where it needs to be referred to by id.
     *
     * @param text
     *          the text for the menu, or <code>null</code> if none
     * @param image
     *          the image for the menu, or <code>null</code> if none
     * @param id
     *          the menu id, or <code>null</code> if it is to have no id
     * @since 3.4
     */
    public MenuManagerEx(String text, ImageDescriptor image, String id) {
        menuText = text;
        this.image = image;
        this.id = id;
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * org.eclipse.jface.action.IMenuManager#addMenuListener(org.eclipse.jface
     * .action.IMenuListener)
     */
    @Override
    public void addMenuListener(IMenuListener listener) {
        listeners.add(listener);
    }

    /**
     * Creates and returns an SWT context menu control for this menu, and installs
     * all registered contributions. Does not create a new control if one already
     * exists.
     * <p>
     * Note that the menu is not expected to be dynamic.
     * </p>
     *
     * @param parent
     *          the parent control
     * @return the menu control
     */
    public Menu createContextMenu(Control parent) {
        if (!menuExist()) {
            menu = new Menu(parent);
            menu.setData(MANAGER_KEY, this);
            initializeMenu();
        }
        return menu;
    }

    /**
     * Creates and returns an SWT context menu control for this menu, and installs
     * all registered contributions. Does not create a new control if one already
     * exists.
     * <p>
     * Note that the menu is not expected to be dynamic.
     * </p>
     *
     * @param parent
     *          the parent menu.
     * @return the menu control
     * @author Haixing Hu
     */
    public Menu createSubMenu(Menu parent) {
        if (!menuExist()) {
            menu = new Menu(parent);
            menu.setData(MANAGER_KEY, this);
            initializeMenu();
        }
        return menu;
    }

    /**
     * Creates and returns an SWT menu bar control for this menu, for use in the
     * given <code>Decorations</code>, and installs all registered contributions.
     * Does not create a new control if one already exists.
     *
     * @param parent
     *          the parent decorations
     * @return the menu control
     * @since 2.1
     */
    public Menu createMenuBar(Decorations parent) {
        if (!menuExist()) {
            menu = new Menu(parent, SWT.BAR);
            menu.setData(MANAGER_KEY, this);
            update(false);
        }
        return menu;
    }

    /**
     * Disposes of this menu manager and frees all allocated SWT resources.
     * Notifies all contribution items of the dispose. Note that this method does
     * not clean up references between this menu manager and its associated
     * contribution items. Use <code>removeAll</code> for that purpose.
     */
    @Override
    public void dispose() {
        if (menuExist()) {
            menu.dispose();
        }
        menu = null;

        if (menuItem != null) {
            menuItem.dispose();
            menuItem = null;
        }

        disposeOldImages();

        final IContributionItem[] items = getItems();
        for (final IContributionItem item : items) {
            item.dispose();
        }

        markDirty();
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * org.eclipse.jface.action.IContributionItem#fill(org.eclipse.swt.widgets
     * .Composite)
     */
    @Override
    public void fill(Composite parent) {
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * org.eclipse.jface.action.IContributionItem#fill(org.eclipse.swt.widgets
     * .CoolBar, int)
     */
    @Override
    public void fill(CoolBar parent, int index) {
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * org.eclipse.jface.action.IContributionItem#fill(org.eclipse.swt.widgets
     * .Menu, int)
     */
    @Override
    public void fill(Menu parent, int index) {
        if ((menuItem == null) || menuItem.isDisposed()) {
            if (index >= 0) {
                menuItem = new MenuItem(parent, SWT.CASCADE, index);
            } else {
                menuItem = new MenuItem(parent, SWT.CASCADE);
            }

            final String text = getMenuText();
            if (text != null) {
                menuItem.setText(text);
            }

            if (image != null) {
                final LocalResourceManager localManager = new LocalResourceManager(JFaceResources.getResources());
                menuItem.setImage(localManager.createImage(image));
                disposeOldImages();
                imageManager = localManager;
            }

            if (!menuExist()) {
                menu = new Menu(parent);
                menu.setData(MANAGER_KEY, this);
            }

            menuItem.setMenu(menu);

            initializeMenu();

            setDirty(true);
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * org.eclipse.jface.action.IContributionItem#fill(org.eclipse.swt.widgets
     * .ToolBar, int)
     */
    @Override
    public void fill(ToolBar parent, int index) {
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * org.eclipse.jface.action.IMenuManager#findMenuUsingPath(java.lang.String)
     */
    @Override
    public IMenuManager findMenuUsingPath(String path) {
        final IContributionItem item = findUsingPath(path);
        if (item instanceof IMenuManager) {
            return (IMenuManager) item;
        }
        return null;
    }

    /*
     * (non-Javadoc)
     *
     * @see org.eclipse.jface.action.IMenuManager#findUsingPath(java.lang.String)
     */
    @Override
    public IContributionItem findUsingPath(String path) {
        String id = path;
        String rest = null;
        final int separator = path.indexOf('/');
        if (separator != -1) {
            id = path.substring(0, separator);
            rest = path.substring(separator + 1);
        } else {
            return super.find(path);
        }

        final IContributionItem item = super.find(id);
        if (item instanceof IMenuManager) {
            final IMenuManager manager = (IMenuManager) item;
            return manager.findUsingPath(rest);
        }
        return null;
    }

    /**
     * Notifies any menu listeners that a menu is about to show. Only listeners
     * registered at the time this method is called are notified.
     *
     * @param manager
     *          the menu manager
     *
     * @see IMenuListener#menuAboutToShow
     */
    private void fireAboutToShow(IMenuManager manager) {
        final Object[] listeners = this.listeners.getListeners();
        for (final Object listener : listeners) {
            ((IMenuListener) listener).menuAboutToShow(manager);
        }
    }

    /**
     * Notifies any menu listeners that a menu is about to hide. Only listeners
     * registered at the time this method is called are notified.
     *
     * @param manager
     *          the menu manager
     *
     */
    private void fireAboutToHide(IMenuManager manager) {
        final Object[] listeners = this.listeners.getListeners();
        for (final Object listener : listeners) {
            if (listener instanceof IMenuListener2) {
                final IMenuListener2 listener2 = (IMenuListener2) listener;
                listener2.menuAboutToHide(manager);
            }
        }
    }

    /**
     * Returns the menu id. The menu id is used when creating a contribution item
     * for adding this menu as a sub menu of another.
     *
     * @return the menu id
     */
    @Override
    public String getId() {
        return id;
    }

    /**
     * Returns the SWT menu control for this menu manager.
     *
     * @return the menu control
     */
    public Menu getMenu() {
        return menu;
    }

    /**
     * Returns the text shown in the menu, potentially with a shortcut appended.
     *
     * @return the menu text
     */
    public String getMenuText() {
        if (definitionId == null) {
            return menuText;
        }
        final ExternalActionManager.ICallback callback = ExternalActionManager.getInstance().getCallback();
        if (callback != null) {
            final String shortCut = callback.getAcceleratorText(definitionId);
            if (shortCut == null) {
                return menuText;
            }
            return menuText + "\t" + shortCut; //$NON-NLS-1$
        }
        return menuText;
    }

    /**
     * Returns the image for this menu as an image descriptor.
     *
     * @return the image, or <code>null</code> if this menu has no image
     * @since 3.4
     */
    public ImageDescriptor getImageDescriptor() {
        return image;
    }

    /*
     * (non-Javadoc)
     *
     * @see org.eclipse.jface.action.IContributionManager#getOverrides()
     */
    @Override
    public IContributionManagerOverrides getOverrides() {
        if (overrides == null) {
            if (parent == null) {
                overrides = new IContributionManagerOverrides() {
                    @Override
                    public Integer getAccelerator(IContributionItem item) {
                        return null;
                    }

                    @Override
                    public String getAcceleratorText(IContributionItem item) {
                        return null;
                    }

                    @Override
                    public Boolean getEnabled(IContributionItem item) {
                        return null;
                    }

                    @Override
                    public String getText(IContributionItem item) {
                        return null;
                    }

                    @Override
                    public Boolean getVisible(IContributionItem item) {
                        return null;
                    }
                };
            } else {
                overrides = parent.getOverrides();
            }
            super.setOverrides(overrides);
        }
        return overrides;
    }

    /**
     * Returns the parent contribution manager of this manger.
     *
     * @return the parent contribution manager
     * @since 2.0
     */
    public IContributionManager getParent() {
        return parent;
    }

    /*
     * (non-Javadoc)
     *
     * @see org.eclipse.jface.action.IMenuManager#getRemoveAllWhenShown()
     */
    @Override
    public boolean getRemoveAllWhenShown() {
        return removeAllWhenShown;
    }

    /**
     * Notifies all listeners that this menu is about to appear.
     */
    private void handleAboutToShow() {
        if (removeAllWhenShown) {
            removeAll();
        }
        fireAboutToShow(this);
        update(false, false);
    }

    /**
     * Notifies all listeners that this menu is about to disappear.
     */
    private void handleAboutToHide() {
        fireAboutToHide(this);
    }

    /**
     * Initializes the menu control.
     */
    private void initializeMenu() {
        menu.addMenuListener(new MenuAdapter() {
            @Override
            public void menuHidden(MenuEvent e) {
                // ApplicationWindowEx.resetDescription(e.widget);
                handleAboutToHide();
            }

            @Override
            public void menuShown(MenuEvent e) {
                handleAboutToShow();
            }
        });
        // Don't do an update(true) here, in case menu is never opened.
        // Always do it lazily in handleAboutToShow().
    }

    /**
     * Tests whether this menu manager will show the image on the children menu
     * item.
     * <p>
     * <b>NOTE:</b> Each {@link ActionEx} could set its own "showImage" property.
     * If a {@link MenuManagerEx} sets its "showImage" property property to false,
     * it will suppress all "showImage" property in its actions. Otherwise,
     * whether the image should be displayed depends on the "showImage" property
     * of the action.
     *
     * @return <code>true</code> if this menu manager will show the image on the
     *         children menu item; <code>false</code> otherwise.
     * @author Haixing Hu
     */
    public boolean isShowImage() {
        return showImage;
    }

    /**
     * Sets whether this menu manager will show the image on the children menu
     * item.
     * <p>
     * <b>NOTE:</b> Each {@link ActionEx} could set its own "showImage" property.
     * If a {@link MenuManagerEx} sets its "showImage" property property to false,
     * it will suppress all "showImage" property in its actions. Otherwise,
     * whether the image should be displayed depends on the "showImage" property
     * of the action.
     *
     * @param showImage
     *          <code>true</code> if this menu manager should show the image on the
     *          children menu item; <code>false</code> otherwise.
     * @author Haixing Hu
     */
    public void setShowImage(boolean showImage) {
        this.showImage = showImage;
    }

    /*
     * (non-Javadoc)
     *
     * @see org.eclipse.jface.action.IContributionItem#isDynamic()
     */
    @Override
    public boolean isDynamic() {
        return false;
    }

    /**
     * Returns whether this menu should be enabled or not. Used to enable the menu
     * item containing this menu when it is realized as a sub-menu.
     * <p>
     * The default implementation of this framework method returns
     * <code>true</code>. Subclasses may reimplement.
     * </p>
     *
     * @return <code>true</code> if enabled, and <code>false</code> if disabled
     */
    @Override
    public boolean isEnabled() {
        return true;
    }

    /*
     * (non-Javadoc)
     *
     * @see org.eclipse.jface.action.IContributionItem#isGroupMarker()
     */
    @Override
    public boolean isGroupMarker() {
        return false;
    }

    /*
     * (non-Javadoc)
     *
     * @see org.eclipse.jface.action.IContributionItem#isSeparator()
     */
    @Override
    public boolean isSeparator() {
        return false;
    }

    /*
     * (non-Javadoc)
     *
     * @see org.eclipse.jface.action.IContributionItem#isVisible()
     */
    @Override
    public boolean isVisible() {
        if (!visible) {
            return false; // short circuit calculations in this case
        }

        if (removeAllWhenShown) {
            // we have no way of knowing if the menu has children
            return true;
        }

        // menus aren't visible if all of its children are invisible (or only
        // contains visible separators).
        final IContributionItem[] childItems = getItems();
        boolean visibleChildren = false;
        for (int j = 0; j < childItems.length; j++) {
            if (isChildVisible(childItems[j]) && !childItems[j].isSeparator()) {
                visibleChildren = true;
                break;
            }
        }

        return visibleChildren;
    }

    /**
     * The <code>MenuManagerEx</code> implementation of this
     * <code>ContributionManager</code> method also propagates the dirty flag up
     * the parent chain.
     *
     * @since 3.1
     */
    @Override
    public void markDirty() {
        super.markDirty();
        // Can't optimize by short-circuiting when the first dirty manager is
        // encountered,
        // since non-visible children are not even processed.
        // That is, it's possible to have a dirty sub-menu under a non-dirty parent
        // menu
        // even after the parent menu has been updated.
        // If items are added/removed in the sub-menu, we still need to propagate
        // the dirty flag up,
        // even if the sub-menu is already dirty, since the result of isVisible()
        // may change
        // due to the added/removed items.
        final IContributionManager parent = getParent();
        if (parent != null) {
            parent.markDirty();
        }
    }

    /**
     * Returns whether the menu control is created and not disposed.
     *
     * @return <code>true</code> if the control is created and not disposed,
     *         <code>false</code> otherwise
     * @since 3.4 protected, was added in 3.1 as private method
     */
    protected boolean menuExist() {
        return (menu != null) && !menu.isDisposed();
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * org.eclipse.jface.action.IMenuManager#removeMenuListener(org.eclipse.jface
     * .action.IMenuListener)
     */
    @Override
    public void removeMenuListener(IMenuListener listener) {
        listeners.remove(listener);
    }

    /*
     * (non-Javadoc)
     *
     * @see org.eclipse.jface.action.IContributionItem#saveWidgetState()
     */
    @Override
    public void saveWidgetState() {
    }

    /**
     * Sets the overrides for this contribution manager
     *
     * @param newOverrides
     *          the overrides for the items of this manager
     * @since 2.0
     */
    @Override
    public void setOverrides(IContributionManagerOverrides newOverrides) {
        overrides = newOverrides;
        super.setOverrides(overrides);
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * org.eclipse.jface.action.IContributionItem#setParent(org.eclipse.jface.
     * action.IContributionManager)
     */
    @Override
    public void setParent(IContributionManager manager) {
        parent = manager;
    }

    /*
     * (non-Javadoc)
     *
     * @see org.eclipse.jface.action.IMenuManager#setRemoveAllWhenShown(boolean)
     */
    @Override
    public void setRemoveAllWhenShown(boolean removeAll) {
        removeAllWhenShown = removeAll;
    }

    /*
     * (non-Javadoc)
     *
     * @see org.eclipse.jface.action.IContributionItem#setVisible(boolean)
     */
    @Override
    public void setVisible(boolean visible) {
        this.visible = visible;
    }

    /**
     * Sets the action definition id of this action. This simply allows the menu
     * item text to include a short cut if available. It can be used to notify a
     * user of a key combination that will open a quick menu.
     *
     * @param definitionId
     *          the command definition id
     * @since 3.4
     */
    public void setActionDefinitionId(String definitionId) {
        this.definitionId = definitionId;
    }

    /*
     * (non-Javadoc)
     *
     * @see org.eclipse.jface.action.IContributionItem#update()
     */
    @Override
    public void update() {
        updateMenuItem();
    }

    /**
     * The <code>MenuManagerEx</code> implementation of this
     * <code>IContributionManager</code> updates this menu, but not any of its
     * submenus.
     *
     * @see #updateAll
     */
    @Override
    public void update(boolean force) {
        update(force, false);
    }

    /**
     * Get all the items from the implementation's widget.
     *
     * @return the menu items
     * @since 3.4
     */
    protected Item[] getMenuItems() {
        if (menu != null) {
            return menu.getItems();
        }
        return null;
    }

    /**
     * Get an item from the implementation's widget.
     *
     * @param index
     *          of the item
     * @return the menu item
     * @since 3.4
     */
    protected Item getMenuItem(int index) {
        if (menu != null) {
            return menu.getItem(index);
        }
        return null;
    }

    /**
     * Get the menu item count for the implementation's widget.
     *
     * @return the number of items
     * @since 3.4
     */
    protected int getMenuItemCount() {
        if (menu != null) {
            return menu.getItemCount();
        }
        return 0;
    }

    /**
     * Call an <code>IContributionItem</code>'s fill method with the
     * implementation's widget. The default is to use the <code>Menu</code>
     * widget.<br>
     * <code>fill(Menu menu, int index)</code>
     *
     * @param ci
     *          An <code>IContributionItem</code> whose <code>fill()</code> method
     *          should be called.
     * @param index
     *          The position the <code>fill()</code> method should start inserting
     *          at.
     * @since 3.4
     */
    protected void doItemFill(IContributionItem ci, int index) {
        ci.fill(menu, index);
    }

    /**
     * Incrementally builds the menu from the contribution items. This method
     * leaves out double separators and separators in the first or last position.
     *
     * @param force
     *          <code>true</code> means update even if not dirty, and
     *          <code>false</code> for normal incremental updating
     * @param recursive
     *          <code>true</code> means recursively update all submenus, and
     *          <code>false</code> means just this menu
     */
    protected void update(boolean force, boolean recursive) {
        if (isDirty() || force) {
            if (menuExist()) {
                // clean contains all active items without double separators
                final IContributionItem[] items = getItems();
                final List<IContributionItem> clean = new ArrayList<IContributionItem>(items.length);
                IContributionItem separator = null;
                for (final IContributionItem item : items) {
                    final IContributionItem ci = item;
                    if (!isChildVisible(ci)) {
                        continue;
                    }
                    if (ci.isSeparator()) {
                        // delay creation until necessary
                        // (handles both adjacent separators, and separator at end)
                        separator = ci;
                    } else {
                        if (separator != null) {
                            if (clean.size() > 0) {
                                clean.add(separator);
                            }
                            separator = null;
                        }
                        clean.add(ci);
                    }
                }

                // remove obsolete (removed or non active)
                Item[] mi = getMenuItems();

                for (final Item element : mi) {
                    final Object data = element.getData();

                    if ((data == null) || !clean.contains(data)) {
                        element.dispose();
                    } else if ((data instanceof IContributionItem) && ((IContributionItem) data).isDynamic()
                            && ((IContributionItem) data).isDirty()) {
                        element.dispose();
                    }
                }

                // add new
                mi = getMenuItems();
                int srcIx = 0;
                int destIx = 0;

                for (final Object element : clean) {
                    final IContributionItem src = (IContributionItem) element;
                    IContributionItem dest;

                    // get corresponding item in SWT widget
                    if (srcIx < mi.length) {
                        dest = (IContributionItem) mi[srcIx].getData();
                    } else {
                        dest = null;
                    }

                    if ((dest != null) && src.equals(dest)) {
                        srcIx++;
                        destIx++;
                    } else if ((dest != null) && dest.isSeparator() && src.isSeparator()) {
                        mi[srcIx].setData(src);
                        srcIx++;
                        destIx++;
                    } else {
                        final int start = getMenuItemCount();
                        doItemFill(src, destIx);
                        final int newItems = getMenuItemCount() - start;
                        for (int i = 0; i < newItems; i++) {
                            final Item item = getMenuItem(destIx++);
                            item.setData(src);
                        }
                    }

                    // May be we can optimize this call. If the menu has just
                    // been created via the call src.fill(fMenuBar, destIx) then
                    // the menu has already been updated with update(true)
                    // (see MenuManagerEx). So if force is true we do it again. But
                    // we can't set force to false since then information for the
                    // sub sub menus is lost.
                    if (recursive) {
                        IContributionItem item = src;
                        if (item instanceof SubContributionItem) {
                            item = ((SubContributionItem) item).getInnerItem();
                        }
                        if (item instanceof IMenuManager) {
                            ((IMenuManager) item).updateAll(force);
                        }
                    }

                }

                // remove any old menu items not accounted for
                for (; srcIx < mi.length; srcIx++) {
                    mi[srcIx].dispose();
                }

                setDirty(false);
            }
        } else {
            // I am not dirty. Check if I must recursivly walk down the hierarchy.
            if (recursive) {
                final IContributionItem[] items = getItems();
                for (final IContributionItem ci : items) {
                    if (ci instanceof IMenuManager) {
                        final IMenuManager mm = (IMenuManager) ci;
                        if (isChildVisible(mm)) {
                            mm.updateAll(force);
                        }
                    }
                }
            }
        }
        updateMenuItem();
    }

    /*
     * (non-Javadoc)
     *
     * @see org.eclipse.jface.action.IContributionItem#update(java.lang.String)
     */
    @Override
    public void update(String property) {
        final IContributionItem items[] = getItems();

        for (final IContributionItem item : items) {
            item.update(property);
        }

        if ((menu != null) && !menu.isDisposed() && (menu.getParentItem() != null)) {
            if (IAction.TEXT.equals(property)) {
                String text = getOverrides().getText(this);

                if (text == null) {
                    text = getMenuText();
                }

                if (text != null) {
                    final ExternalActionManager.ICallback callback = ExternalActionManager.getInstance()
                            .getCallback();

                    if (callback != null) {
                        final int index = text.indexOf('&');

                        if ((index >= 0) && (index < (text.length() - 1))) {
                            final char character = Character.toUpperCase(text.charAt(index + 1));

                            if (callback.isAcceleratorInUse(SWT.ALT | character) && isTopLevelMenu()) {
                                if (index == 0) {
                                    text = text.substring(1);
                                } else {
                                    text = text.substring(0, index) + text.substring(index + 1);
                                }
                            }
                        }
                    }

                    menu.getParentItem().setText(text);
                }
            } else if (IAction.IMAGE.equals(property) && (image != null)) {
                final LocalResourceManager localManager = new LocalResourceManager(JFaceResources.getResources());
                menu.getParentItem().setImage(localManager.createImage(image));
                disposeOldImages();
                imageManager = localManager;
            }
        }
    }

    private boolean isTopLevelMenu() {
        if ((menu != null) && !menu.isDisposed() && (menuItem != null) && !menuItem.isDisposed()) {
            final Menu parentMenu = menuItem.getParent();
            return (parentMenu != null) && ((parentMenu.getStyle() & SWT.BAR) == SWT.BAR);
        }
        return false;
    }

    /**
     * Dispose any images allocated for this menu
     */
    private void disposeOldImages() {
        if (imageManager != null) {
            imageManager.dispose();
            imageManager = null;
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see org.eclipse.jface.action.IMenuManager#updateAll(boolean)
     */
    @Override
    public void updateAll(boolean force) {
        update(force, true);
    }

    /**
     * Updates the menu item for this sub menu. The menu item is disabled if this
     * sub menu is empty. Does nothing if this menu is not a submenu.
     */
    private void updateMenuItem() {
        /*
         * Commented out until proper solution to enablement of menu item for a
         * sub-menu is found. See bug 30833 for more details.
         *
         * if (menuItem != null && !menuItem.isDisposed() && menuExist()) {
         * IContributionItem items[] = getItems(); boolean enabled = false; for (int
         * i = 0; i < items.length; i++) { IContributionItem item = items[i];
         * enabled = item.isEnabled(); if(enabled) break; } // Workaround for
         * 1GDDCN2: SWT:Linux - MenuItem.setEnabled() always causes a redraw if
         * (menuItem.getEnabled() != enabled) menuItem.setEnabled(enabled); }
         */
        // Partial fix for bug #34969 - diable the menu item if no
        // items in sub-menu (for context menus).
        if ((menuItem != null) && !menuItem.isDisposed() && menuExist()) {
            final boolean enabled = removeAllWhenShown || (menu.getItemCount() > 0);
            // Workaround for 1GDDCN2: SWT:Linux - MenuItem.setEnabled() always causes
            // a redraw
            if (menuItem.getEnabled() != enabled) {
                // We only do this for context menus (for bug #34969)
                Menu topMenu = menu;
                while (topMenu.getParentMenu() != null) {
                    topMenu = topMenu.getParentMenu();
                }
                if ((topMenu.getStyle() & SWT.BAR) == 0) {
                    menuItem.setEnabled(enabled);
                }
            }
        }
    }

    private boolean isChildVisible(IContributionItem item) {
        final Boolean v = getOverrides().getVisible(item);
        if (v != null) {
            return v.booleanValue();
        }
        return item.isVisible();
    }

    /**
     * Adds an action to this menu.
     * <p>
     * <b>NOTE:</b> We override this method in order to enable or disable the
     * image on the children menu item according to the <code>showImage</code>
     * property of this menu manager.
     *
     * @param action
     *          the action to be added.
     * @see #isShowImage()
     * @see #setShowImage(boolean)
     * @author Haixing Hu
     */
    public void add(ActionEx action) {
        if (action == null) {
            SWT.error(SWT.ERROR_NULL_ARGUMENT);
        }
        final ActionContributionItemEx item = new ActionContributionItemEx(action);
        if (!showImage) {
            action.setShowImage(false);
            item.setShowImage(false);
        }
        super.add(item);
    }
}