/******************************************************************************* * Copyright (c) 2009, 2016 IBM Corporation and others. * All rights reserved. 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: * IBM Corporation - initial API and implementation * Marco Descher <marco@descher.at> - Bug 389063, Bug 398865, Bug 398866, Bug 405471 * Sopot Cela <sopotcela@gmail.com> * Steven Spungin <steven@spungin.tv> - Bug 437747 * Alan Staves <alan.staves@microfocus.com> - Bug 435274 * Patrick Naish <patrick.naish@microfocus.com> - Bug 435274 * René Brandstetter <Rene.Brandstetter@gmx.net> - Bug 378849 * Andrey Loskutov <loskutov@gmx.de> - Bug 378849 * Dirk Fauth <dirk.fauth@googlemail.com> - Bug 460556 * Lars Vogel <Lars.Vogel@vogella.com> - Bug 391430, 472654, 460886 * Daniel Kruegler <daniel.kruegler@gmail.com> - Bug 473779 * Simon Scholz <simon.scholz@vogella.com> - Bug 506306 * Axel Richard <axel.richard@oebo.fr> - Bug 354538 *******************************************************************************/ package org.eclipse.e4.ui.workbench.renderers.swt; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.inject.Inject; import org.eclipse.e4.core.commands.ExpressionContext; import org.eclipse.e4.core.contexts.ContextInjectionFactory; import org.eclipse.e4.core.contexts.IContextFunction; import org.eclipse.e4.core.contexts.IEclipseContext; import org.eclipse.e4.core.contexts.RunAndTrack; import org.eclipse.e4.core.di.annotations.Optional; import org.eclipse.e4.core.services.events.IEventBroker; import org.eclipse.e4.core.services.log.Logger; import org.eclipse.e4.ui.di.UIEventTopic; import org.eclipse.e4.ui.internal.workbench.ContributionsAnalyzer; import org.eclipse.e4.ui.internal.workbench.OpaqueElementUtil; import org.eclipse.e4.ui.internal.workbench.RenderedElementUtil; import org.eclipse.e4.ui.internal.workbench.swt.Policy; import org.eclipse.e4.ui.model.application.MApplication; import org.eclipse.e4.ui.model.application.ui.MElementContainer; import org.eclipse.e4.ui.model.application.ui.MUIElement; import org.eclipse.e4.ui.model.application.ui.MUILabel; import org.eclipse.e4.ui.model.application.ui.basic.MPart; import org.eclipse.e4.ui.model.application.ui.basic.MWindow; import org.eclipse.e4.ui.model.application.ui.menu.MDirectMenuItem; import org.eclipse.e4.ui.model.application.ui.menu.MDynamicMenuContribution; import org.eclipse.e4.ui.model.application.ui.menu.MHandledMenuItem; import org.eclipse.e4.ui.model.application.ui.menu.MMenu; import org.eclipse.e4.ui.model.application.ui.menu.MMenuContribution; import org.eclipse.e4.ui.model.application.ui.menu.MMenuElement; import org.eclipse.e4.ui.model.application.ui.menu.MMenuItem; import org.eclipse.e4.ui.model.application.ui.menu.MMenuSeparator; import org.eclipse.e4.ui.model.application.ui.menu.MPopupMenu; import org.eclipse.e4.ui.workbench.IPresentationEngine; import org.eclipse.e4.ui.workbench.IResourceUtilities; import org.eclipse.e4.ui.workbench.UIEvents; import org.eclipse.e4.ui.workbench.UIEvents.ElementContainer; import org.eclipse.e4.ui.workbench.swt.util.ISWTResourceUtilities; import org.eclipse.emf.common.util.URI; import org.eclipse.jface.action.AbstractGroupMarker; import org.eclipse.jface.action.ContributionItem; import org.eclipse.jface.action.GroupMarker; import org.eclipse.jface.action.IAction; import org.eclipse.jface.action.IContributionItem; import org.eclipse.jface.action.IContributionManager; import org.eclipse.jface.action.MenuManager; import org.eclipse.jface.action.Separator; import org.eclipse.jface.action.SubContributionItem; import org.eclipse.jface.internal.MenuManagerEventHelper; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.swt.SWT; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Decorations; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Menu; import org.osgi.service.event.Event; import org.osgi.service.event.EventHandler; /** * Create a contribute part. */ public class MenuManagerRenderer extends SWTPartRenderer { public static final String VISIBILITY_IDENTIFIER = "IIdentifier"; //$NON-NLS-1$ private static final String NO_LABEL = "UnLabled"; //$NON-NLS-1$ public static final String GROUP_MARKER = "org.eclipse.jface.action.GroupMarker.GroupMarker(String)"; //$NON-NLS-1$ private Map<MMenu, MenuManager> modelToManager = new HashMap<>(); private Map<MenuManager, MMenu> managerToModel = new HashMap<>(); private Map<MMenuElement, IContributionItem> modelToContribution = new HashMap<>(); private Map<IContributionItem, MMenuElement> contributionToModel = new HashMap<>(); private Map<MMenuElement, ContributionRecord> modelContributionToRecord = new HashMap<>(); private Map<MMenuElement, ArrayList<ContributionRecord>> sharedElementToRecord = new HashMap<>(); private Collection<IContributionManager> mgrToUpdate = new LinkedHashSet<>(); @Inject private Logger logger; @Inject private MApplication application; @Inject IEventBroker eventBroker; @Inject @Optional private void subscribeUIElementTopicAllRenderedVisibility(@UIEventTopic(UIEvents.UIElement.TOPIC_ALL) Event event) { Object element = event.getProperty(UIEvents.EventTags.ELEMENT); String attName = (String) event.getProperty(UIEvents.EventTags.ATTNAME); if (element instanceof MMenuItem) { MMenuItem itemModel = (MMenuItem) element; if (UIEvents.UIElement.TOBERENDERED.equals(attName)) { Object obj = itemModel.getParent(); if (!(obj instanceof MMenu)) { return; } MenuManager parent = getManager((MMenu) obj); if (itemModel.isToBeRendered()) { if (parent != null) { modelProcessSwitch(parent, itemModel); } } else { IContributionItem ici = getContribution(itemModel); clearModelToContribution(itemModel, ici); if (ici != null && parent != null) { parent.remove(ici); } if (ici != null) { ici.dispose(); } } } } if (element instanceof MPart) { MPart part = (MPart) element; if (UIEvents.UIElement.TOBERENDERED.equals(attName)) { boolean tbr = (Boolean) event.getProperty(UIEvents.EventTags.NEW_VALUE); if (!tbr) { List<MMenu> menus = part.getMenus(); for (MMenu menu : menus) { if (menu instanceof MPopupMenu) unlinkMenu(menu); } } } } if (UIEvents.UIElement.VISIBLE.equals(attName)) { if (element instanceof MMenu) { MMenu menuModel = (MMenu) element; MenuManager manager = getManager(menuModel); if (manager == null) { return; } boolean visible = menuModel.isVisible(); manager.setVisible(visible); if (manager.getParent() != null) { manager.getParent().markDirty(); scheduleManagerUpdate(manager.getParent()); } if (menuModel.getParent() == null) { if (menuModel instanceof MPopupMenu) { Object data = menuModel.getTransientData().get(IPresentationEngine.RENDERING_PARENT_KEY); if (data instanceof Control) { Menu menu = (Menu) menuModel.getWidget(); if (visible && menuModel.isToBeRendered() && menu != null && !menu.isDisposed()) { ((Control) data).setMenu(menu); } if (!visible) { ((Control) data).setMenu(null); } } } } } else if (element instanceof MMenuElement) { MMenuElement itemModel = (MMenuElement) element; Object obj = getContribution(itemModel); if (!(obj instanceof ContributionItem)) { return; } ContributionItem item = (ContributionItem) obj; item.setVisible(itemModel.isVisible()); if (item.getParent() != null) { item.getParent().markDirty(); scheduleManagerUpdate(item.getParent()); } } } } @Inject @Optional private void subscribeUILabelTopicAll(@UIEventTopic(UIEvents.UILabel.TOPIC_ALL) Event event) { Object element = event.getProperty(UIEvents.EventTags.ELEMENT); if (element instanceof MMenuItem) { handleLabelOfMenuItem(event, element); } else if (element instanceof MMenu) { updateLabelOfMenu(event); } // nothing to do otherwise } @Inject @Optional private void subscribeItemEnabledUpdate(@UIEventTopic(UIEvents.Item.TOPIC_ENABLED) Event event) { Object element = event.getProperty(UIEvents.EventTags.ELEMENT); if (!(element instanceof MMenuItem)) { return; } MMenuItem itemModel = (MMenuItem) element; IContributionItem ici = getContribution(itemModel); if (ici != null) { ici.update(); } } /** * @param event */ private void updateLabelOfMenu(Event event) { String attName = (String) event.getProperty(UIEvents.EventTags.ATTNAME); MMenu model = (MMenu) event.getProperty(UIEvents.EventTags.ELEMENT); MenuManager manager = getManager(model); if ((manager == null)) { return; } if (UIEvents.UILabel.LABEL.equals(attName) || UIEvents.UILabel.LOCALIZED_LABEL.equals(attName)) { manager.setMenuText(getText(model)); manager.update(IAction.TEXT); } if (UIEvents.UILabel.ICONURI.equals(attName)) { manager.setImageDescriptor(getImageDescriptor(model)); manager.update(IAction.IMAGE); } } /** * @param event * @param element */ private void handleLabelOfMenuItem(Event event, Object element) { String attName = (String) event.getProperty(UIEvents.EventTags.ATTNAME); MMenuItem itemModel = (MMenuItem) element; IContributionItem ici = getContribution(itemModel); if (ici == null) { return; } if (UIEvents.UILabel.LABEL.equals(attName) || UIEvents.UILabel.LOCALIZED_LABEL.equals(attName)) { ici.update(); } else if (UIEvents.UILabel.ICONURI.equals(attName)) { ici.update(); } else if (UIEvents.UILabel.TOOLTIP.equals(attName) || UIEvents.UILabel.LOCALIZED_TOOLTIP.equals(attName)) { ici.update(); } } private EventHandler selectionUpdater = event -> { // Ensure that this event is for a MToolItem if (!(event.getProperty(UIEvents.EventTags.ELEMENT) instanceof MMenuItem)) return; MMenuItem itemModel = (MMenuItem) event.getProperty(UIEvents.EventTags.ELEMENT); IContributionItem ici = getContribution(itemModel); if (ici != null) { ici.update(); } }; private MenuManagerRendererFilter rendererFilter; @PostConstruct public void init() { eventBroker.subscribe(UIEvents.Item.TOPIC_SELECTED, selectionUpdater); context.set(MenuManagerRenderer.class, this); Display display = context.get(Display.class); rendererFilter = ContextInjectionFactory.make(MenuManagerRendererFilter.class, context); display.addFilter(SWT.Show, rendererFilter); display.addFilter(SWT.Hide, rendererFilter); display.addFilter(SWT.Dispose, rendererFilter); context.set(MenuManagerRendererFilter.class, rendererFilter); MenuManagerEventHelper.getInstance() .setShowHelper(ContextInjectionFactory.make(MenuManagerShowProcessor.class, context)); MenuManagerEventHelper.getInstance() .setHideHelper(ContextInjectionFactory.make(MenuManagerHideProcessor.class, context)); } @SuppressWarnings("unchecked") @Inject @Optional private void subscribeTopicChildAdded(@UIEventTopic(ElementContainer.TOPIC_CHILDREN) Event event) { // Ensure that this event is for a MMenuItem if (!(event.getProperty(UIEvents.EventTags.ELEMENT) instanceof MMenu)) { return; } MMenu menuModel = (MMenu) event.getProperty(UIEvents.EventTags.ELEMENT); if (UIEvents.isADD(event)) { Object obj = menuModel; processContents((MElementContainer<MUIElement>) obj); } } @PreDestroy public void contextDisposed() { eventBroker.unsubscribe(selectionUpdater); ContextInjectionFactory.uninject(MenuManagerEventHelper.getInstance().getShowHelper(), context); MenuManagerEventHelper.getInstance().setShowHelper(null); ContextInjectionFactory.uninject(MenuManagerEventHelper.getInstance().getHideHelper(), context); MenuManagerEventHelper.getInstance().setHideHelper(null); context.remove(MenuManagerRendererFilter.class); Display display = context.get(Display.class); if (display != null && !display.isDisposed() && rendererFilter != null) { display.removeFilter(SWT.Show, rendererFilter); display.removeFilter(SWT.Hide, rendererFilter); display.removeFilter(SWT.Dispose, rendererFilter); } if (rendererFilter != null) { ContextInjectionFactory.uninject(rendererFilter, context); rendererFilter = null; } context.remove(MenuManagerRenderer.class); if (Policy.DEBUG_RENDERER) { logger.debug("\nMMR:dispose: modelToManager size = {0}, managerToModel size = {1}", //$NON-NLS-1$ modelToManager.size(), managerToModel.size()); } } @Override public Object createWidget(MUIElement element, Object parent) { if (!(element instanceof MMenu)) return null; final MMenu menuModel = (MMenu) element; Menu newMenu = null; MenuManager menuManager = null; boolean menuBar = false; if (parent instanceof Decorations) { MUIElement container = modelService.getContainer(element); if (container instanceof MWindow) { menuManager = getManager(menuModel); if (menuManager == null) { menuManager = new MenuManager(NO_LABEL, menuModel.getElementId()); linkModelToManager(menuModel, menuManager); } newMenu = menuManager.createMenuBar((Decorations) parent); ((Decorations) parent).setMenuBar(newMenu); newMenu.setData(menuManager); menuBar = true; } else { menuManager = getManager(menuModel); if (menuManager == null) { menuManager = new MenuManager(NO_LABEL, menuModel.getElementId()); linkModelToManager(menuModel, menuManager); } newMenu = menuManager.createContextMenu((Control) parent); // we can't be sure this is the correct parent. // ((Control) parent).setMenu(newMenu); newMenu.setData(menuManager); } } else if (parent instanceof Menu) { // Object data = ((Menu) parent).getData(); logger.debug(new Exception(), "Trying to render a sub menu " //$NON-NLS-1$ + menuModel + "\n\t" + parent); //$NON-NLS-1$ return null; } else if (parent instanceof Control) { menuManager = getManager(menuModel); if (menuManager == null) { menuManager = new MenuManager(NO_LABEL, menuModel.getElementId()); linkModelToManager(menuModel, menuManager); } newMenu = menuManager.createContextMenu((Control) parent); // we can't be sure this is the correct parent. // ((Control) parent).setMenu(newMenu); if (element instanceof MPopupMenu && element.isVisible()) { Object data = getUIContainer(element); if (data instanceof Control && parent.equals(data)) { ((Control) parent).setMenu(newMenu); } } newMenu.setData(menuManager); } if (menuManager != null && !menuManager.getRemoveAllWhenShown()) { processContributions(menuModel, menuModel.getElementId(), menuBar, menuModel instanceof MPopupMenu); } if (newMenu != null) { newMenu.addDisposeListener(e -> { cleanUp(menuModel); MenuManager manager = getManager(menuModel); if (manager != null) { manager.markDirty(); } }); } return newMenu; } /** * @param menuModel */ public void cleanUp(MMenu menuModel) { for (MMenuElement childElement : menuModel.getChildren()) { if (childElement instanceof MMenu) { cleanUp((MMenu) childElement); } } Collection<ContributionRecord> vals = modelContributionToRecord.values(); List<ContributionRecord> disposedRecords = new ArrayList<>(); for (ContributionRecord record : vals.toArray(new ContributionRecord[vals.size()])) { if (record.menuModel == menuModel) { record.dispose(); for (MMenuElement copy : record.getGeneratedElements()) { cleanUpCopy(record, copy); } for (MMenuElement copy : record.getSharedElements()) { cleanUpCopy(record, copy); } record.getGeneratedElements().clear(); record.getSharedElements().clear(); disposedRecords.add(record); } } Iterator<Entry<MMenuElement, ContributionRecord>> iterator = modelContributionToRecord.entrySet().iterator(); for (; iterator.hasNext();) { Entry<MMenuElement, ContributionRecord> entry = iterator.next(); ContributionRecord record = entry.getValue(); if (disposedRecords.contains(record)) iterator.remove(); } } public void cleanUpCopy(ContributionRecord record, MMenuElement copy) { modelContributionToRecord.remove(copy); if (copy instanceof MMenu) { MMenu menuCopy = (MMenu) copy; cleanUp(menuCopy); MenuManager copyManager = getManager(menuCopy); clearModelToManager(menuCopy, copyManager); if (copyManager != null) { record.getManagerForModel().remove(copyManager); copyManager.dispose(); } } else { IContributionItem ici = getContribution(copy); clearModelToContribution(copy, ici); if (ici != null) { record.getManagerForModel().remove(ici); } } } /** * @param menuModel * @param isMenuBar * @param isPopup */ public void processContributions(MMenu menuModel, String elementId, boolean isMenuBar, boolean isPopup) { if (elementId == null) { return; } final ArrayList<MMenuContribution> toContribute = new ArrayList<>(); ContributionsAnalyzer.XXXgatherMenuContributions(menuModel, application.getMenuContributions(), elementId, toContribute, null, isPopup); generateContributions(menuModel, toContribute, isMenuBar); for (MMenuElement element : menuModel.getChildren()) { if (element instanceof MMenu) { processContributions((MMenu) element, element.getElementId(), false, isPopup); } } } /** * @param menuModel * @param toContribute */ private void generateContributions(MMenu menuModel, ArrayList<MMenuContribution> toContribute, boolean menuBar) { HashSet<String> existingMenuIds = new HashSet<>(); HashSet<String> existingSeparatorNames = new HashSet<>(); for (MMenuElement child : menuModel.getChildren()) { String elementId = child.getElementId(); if (child instanceof MMenu && elementId != null) { existingMenuIds.add(elementId); } else if (child instanceof MMenuSeparator && elementId != null) { existingSeparatorNames.add(elementId); } } MenuManager manager = getManager(menuModel); boolean done = toContribute.size() == 0; while (!done) { ArrayList<MMenuContribution> curList = new ArrayList<>(toContribute); int retryCount = toContribute.size(); toContribute.clear(); for (MMenuContribution menuContribution : curList) { if (!processAddition(menuModel, manager, menuContribution, existingMenuIds, existingSeparatorNames, menuBar)) { toContribute.add(menuContribution); } } // We're done if the retryList is now empty (everything done) or // if the list hasn't changed at all (no hope) done = (toContribute.size() == 0) || (toContribute.size() == retryCount); } } /** * @param menuModel * @param manager * @param menuContribution * @return true if the menuContribution was processed */ private boolean processAddition(MMenu menuModel, final MenuManager manager, MMenuContribution menuContribution, final HashSet<String> existingMenuIds, HashSet<String> existingSeparatorNames, boolean menuBar) { final ContributionRecord record = new ContributionRecord(menuModel, menuContribution, this); if (!record.mergeIntoModel()) { return false; } if (menuBar || isPartMenu(menuModel)) { final IEclipseContext parentContext = getContext(menuModel); parentContext.runAndTrack(new RunAndTrack() { @Override public boolean changed(IEclipseContext context) { record.updateVisibility(parentContext.getActiveLeaf()); scheduleManagerUpdate(manager); return true; } }); } return true; } private boolean isPartMenu(MMenu menuModel) { // don't want popup menus as their visibility does not need to be // tracked by a separate RunAndTrack return !(menuModel instanceof MPopupMenu) && modelService.getContainer(menuModel) instanceof MPart; } private static ArrayList<ContributionRecord> DEFAULT = new ArrayList<>(); public ArrayList<ContributionRecord> getList(MMenuElement item) { ArrayList<ContributionRecord> tmp = sharedElementToRecord.get(item); if (tmp == null) { tmp = DEFAULT; } return tmp; } public void addRecord(MMenuElement item, ContributionRecord rec) { ArrayList<ContributionRecord> tmp = sharedElementToRecord.get(item); if (tmp == null) { tmp = new ArrayList<>(); sharedElementToRecord.put(item, tmp); } tmp.add(rec); } public void removeRecord(MMenuElement item, ContributionRecord rec) { ArrayList<ContributionRecord> tmp = sharedElementToRecord.get(item); if (tmp != null) { tmp.remove(rec); if (tmp.isEmpty()) { sharedElementToRecord.remove(item); } } } void removeMenuContributions(final MMenu menuModel, final ArrayList<MMenuElement> menuContributionsToRemove) { for (MMenuElement item : menuContributionsToRemove) { menuModel.getChildren().remove(item); if (item instanceof MMenu) { removeMenuContribution((MMenu) item); } } } /** * Ensure when a menu contribution is removed, if it contains nested menus, * their contributions are also removed. * * @param menuModel */ private void removeMenuContribution(final MMenu menuModel) { clearModelToContribution(menuModel, modelToContribution.get(menuModel)); if (menuModel.getChildren() != null) { for (MMenuElement child : menuModel.getChildren()) { if (child instanceof MMenu) { removeMenuContribution((MMenu) child); } else { clearModelToContribution(child, modelToContribution.get(child)); } } } } @Override public void processContents(MElementContainer<MUIElement> container) { // I can either simply stop processing, or we can walk the model // ourselves like the "old" days // EMF gives us null lists if empty if (container == null) return; // this is in direct violation of good programming MenuManager parentManager = getManager((MMenu) ((Object) container)); if (parentManager == null) { return; } // Process any contents of the newly created ME List<MUIElement> parts = container.getChildren(); if (parts != null) { MUIElement[] plist = parts.toArray(new MUIElement[parts.size()]); for (MUIElement childME : plist) { modelProcessSwitch(parentManager, (MMenuElement) childME); } } scheduleManagerUpdate(parentManager); } private void addToManager(MenuManager parentManager, MMenuElement model, IContributionItem menuManager) { MElementContainer<MUIElement> parent = model.getParent(); // technically this shouldn't happen if (parent == null) { parentManager.add(menuManager); } else { int index = parent.getChildren().indexOf(model); // shouldn't be -1, but better safe than sorry if (index > parentManager.getSize() || index == -1) { parentManager.add(menuManager); } else { parentManager.insert(index, menuManager); } } } /** * @param parentManager * @param menuModel */ private void processMenu(MenuManager parentManager, MMenu menuModel) { MenuManager menuManager = getManager(menuModel); if (menuManager == null) { menuModel.setRenderer(this); String menuText = getText(menuModel); ImageDescriptor desc = getImageDescriptor(menuModel); menuManager = new MenuManager(menuText, desc, menuModel.getElementId()); linkModelToManager(menuModel, menuManager); menuManager.setVisible(menuModel.isVisible()); addToManager(parentManager, menuModel, menuManager); } // processContributions(menuModel, false); List<MMenuElement> parts = menuModel.getChildren(); if (parts != null) { MMenuElement[] plist = parts.toArray(new MMenuElement[parts.size()]); for (MMenuElement childME : plist) { modelProcessSwitch(menuManager, childME); } } } /** * @param menuManager * @param childME */ void modelProcessSwitch(MenuManager menuManager, MMenuElement childME) { if (!childME.isToBeRendered()) { return; } if (RenderedElementUtil.isRenderedMenuItem(childME)) { MMenuItem itemModel = (MMenuItem) childME; processRenderedItem(menuManager, itemModel); } else if (OpaqueElementUtil.isOpaqueMenuItem(childME)) { MMenuItem itemModel = (MMenuItem) childME; processOpaqueItem(menuManager, itemModel); } else if (childME instanceof MHandledMenuItem) { MHandledMenuItem itemModel = (MHandledMenuItem) childME; processHandledItem(menuManager, itemModel); } else if (childME instanceof MDirectMenuItem) { MDirectMenuItem itemModel = (MDirectMenuItem) childME; processDirectItem(menuManager, itemModel, null); } else if (childME instanceof MMenuSeparator) { MMenuSeparator sep = (MMenuSeparator) childME; processSeparator(menuManager, sep); // } else if (childME instanceof MOpaqueMenu) { // I'm not sure what to do here // so I'll just take it out of the running } else if (childME instanceof MMenu) { MMenu itemModel = (MMenu) childME; processMenu(menuManager, itemModel); } else if (childME instanceof MDynamicMenuContribution) { MDynamicMenuContribution itemModel = (MDynamicMenuContribution) childME; processDynamicMenuContribution(menuManager, itemModel); } } /** * @param parentManager * @param itemModel */ void processRenderedItem(MenuManager parentManager, MMenuItem itemModel) { IContributionItem ici = getContribution(itemModel); if (ici != null) { return; } itemModel.setRenderer(this); Object obj = RenderedElementUtil.getContributionManager(itemModel); if (obj instanceof IContextFunction) { final IEclipseContext lclContext = getContext(itemModel); ici = (IContributionItem) ((IContextFunction) obj).compute(lclContext, null); RenderedElementUtil.setContributionManager(itemModel, ici); } else if (obj instanceof IContributionItem) { ici = (IContributionItem) obj; } else { // TODO potentially log the state, we've got something we're not // happy with return; } ici.setVisible(itemModel.isVisible()); addToManager(parentManager, itemModel, ici); linkModelToContribution(itemModel, ici); } void processOpaqueItem(MenuManager parentManager, MMenuItem itemModel) { IContributionItem ici = getContribution(itemModel); if (ici != null) { return; } itemModel.setRenderer(this); Object obj = OpaqueElementUtil.getOpaqueItem(itemModel); if (obj instanceof IContributionItem) { ici = (IContributionItem) obj; } else { return; } ici.setVisible(itemModel.isVisible()); addToManager(parentManager, itemModel, ici); linkModelToContribution(itemModel, ici); } /** * @param menuManager * @param itemModel */ private void processSeparator(MenuManager menuManager, MMenuSeparator itemModel) { IContributionItem ici = getContribution(itemModel); if (ici != null) { return; } itemModel.setRenderer(this); AbstractGroupMarker marker = null; if (itemModel.getTags().contains(GROUP_MARKER) || !itemModel.isVisible()) { if (itemModel.getElementId() != null) { marker = new GroupMarker(itemModel.getElementId()); } } else { marker = new Separator(); marker.setId(itemModel.getElementId()); } if (marker == null) { return; } addToManager(menuManager, itemModel, marker); linkModelToContribution(itemModel, marker); } /** * @param parentManager * @param itemModel * @param id */ void processDirectItem(MenuManager parentManager, MDirectMenuItem itemModel, String id) { IContributionItem ici = getContribution(itemModel); if (ici != null) { return; } itemModel.setRenderer(this); final IEclipseContext lclContext = getContext(itemModel); DirectContributionItem ci = ContextInjectionFactory.make(DirectContributionItem.class, lclContext); ci.setModel(itemModel); ci.setVisible(itemModel.isVisible()); addToManager(parentManager, itemModel, ci); linkModelToContribution(itemModel, ci); } /** * @param menuManager * @param itemModel */ private void processDynamicMenuContribution(MenuManager menuManager, MDynamicMenuContribution itemModel) { IContributionItem ici = getContribution(itemModel); if (ici != null) { return; } itemModel.setRenderer(this); DynamicContributionContributionItem ci = new DynamicContributionContributionItem(itemModel); addToManager(menuManager, itemModel, ci); linkModelToContribution(itemModel, ci); } /** * @param parentManager * @param itemModel */ void processHandledItem(MenuManager parentManager, MHandledMenuItem itemModel) { IContributionItem ici = getContribution(itemModel); if (ici != null) { return; } itemModel.setRenderer(this); final IEclipseContext lclContext = getContext(itemModel); HandledContributionItem ci = ContextInjectionFactory.make(HandledContributionItem.class, lclContext); ci.setModel(itemModel); ci.setVisible(itemModel.isVisible()); addToManager(parentManager, itemModel, ci); linkModelToContribution(itemModel, ci); } private String getText(MMenu menuModel) { String text = menuModel.getLocalizedLabel(); if (text == null || text.length() == 0) { return NO_LABEL; } return text; } private ImageDescriptor getImageDescriptor(MUILabel element) { IEclipseContext localContext = context; String iconURI = element.getIconURI(); if (iconURI != null && iconURI.length() > 0) { ISWTResourceUtilities resUtils = (ISWTResourceUtilities) localContext .get(IResourceUtilities.class.getName()); return resUtils.imageDescriptorFromURI(URI.createURI(iconURI)); } return null; } public MenuManager getManager(MMenu model) { return modelToManager.get(model); } public MMenu getMenuModel(MenuManager manager) { return managerToModel.get(manager); } public void linkModelToManager(MMenu model, MenuManager manager) { modelToManager.put(model, manager); managerToModel.put(manager, model); if (Policy.DEBUG_RENDERER) { logger.debug("\nMMR:linkModelToManager: modelToManager size = {0}, managerToModel size = {1}", //$NON-NLS-1$ modelToManager.size(), managerToModel.size()); } } public void clearModelToManager(MMenu model, MenuManager manager) { for (MMenuElement element : model.getChildren()) { if (element instanceof MMenu) { clearModelToManager((MMenu) element, getManager((MMenu) element)); } IContributionItem ici = getContribution(element); clearModelToContribution(element, ici); } modelToManager.remove(model); managerToModel.remove(manager); if (Policy.DEBUG_RENDERER) { logger.debug("\nMMR:clearModelToManager: modelToManager size = {0}, managerToModel size = {1}", //$NON-NLS-1$ modelToManager.size(), managerToModel.size()); } } public IContributionItem getContribution(MMenuElement model) { return modelToContribution.get(model); } public MMenuElement getMenuElement(IContributionItem item) { return contributionToModel.get(item); } public void linkModelToContribution(MMenuElement model, IContributionItem item) { modelToContribution.put(model, item); contributionToModel.put(item, model); if (Policy.DEBUG_RENDERER) { logger.debug( "\nMMR:linkModelToContribution: modelToContribution size = {0}, contributionToModel size = {1}", //$NON-NLS-1$ modelToContribution.size(), contributionToModel.size()); } } public void clearModelToContribution(MMenuElement model, IContributionItem item) { if (model instanceof MMenu) { for (MMenuElement element : ((MMenu) model).getChildren()) { IContributionItem ici = getContribution(element); clearModelToContribution(element, ici); } } modelToContribution.remove(model); contributionToModel.remove(item); if (Policy.DEBUG_RENDERER) { logger.debug( "\nMMR:clearModelToContribution: modelToContribution size = {0}, contributionToModel size = {1}", //$NON-NLS-1$ modelToContribution.size(), contributionToModel.size()); } } public ContributionRecord getContributionRecord(MMenuElement element) { return modelContributionToRecord.get(element); } public void linkElementToContributionRecord(MMenuElement element, ContributionRecord record) { modelContributionToRecord.put(element, record); } /** * Search the records for testing. Look, but don't touch! * * @return the array of active ContributionRecords. */ public ContributionRecord[] getContributionRecords() { HashSet<ContributionRecord> records = new HashSet<>(modelContributionToRecord.values()); return records.toArray(new ContributionRecord[records.size()]); } @Override public IEclipseContext getContext(MUIElement el) { return super.getContext(el); } /** * @param menuManager * @param menuModel */ public void reconcileManagerToModel(MenuManager menuManager, MMenu menuModel) { List<MMenuElement> modelChildren = menuModel.getChildren(); HashSet<MMenuItem> oldModelItems = new HashSet<>(); HashSet<MMenu> oldMenus = new HashSet<>(); HashSet<MMenuSeparator> oldSeps = new HashSet<>(); for (MMenuElement itemModel : modelChildren) { if (OpaqueElementUtil.isOpaqueMenuSeparator(itemModel)) { oldSeps.add((MMenuSeparator) itemModel); } else if (OpaqueElementUtil.isOpaqueMenuItem(itemModel)) { oldModelItems.add((MMenuItem) itemModel); } else if (OpaqueElementUtil.isOpaqueMenu(itemModel)) { oldMenus.add((MMenu) itemModel); } } IContributionItem[] items = menuManager.getItems(); for (int src = 0, dest = 0; src < items.length; src++, dest++) { IContributionItem item = items[src]; if (item instanceof SubContributionItem) { // get the wrapped contribution item item = ((SubContributionItem) item).getInnerItem(); } if (item instanceof MenuManager) { MenuManager childManager = (MenuManager) item; MMenu childModel = getMenuModel(childManager); if (childModel == null) { MMenu legacyModel = OpaqueElementUtil.createOpaqueMenu(); legacyModel.setElementId(childManager.getId()); legacyModel.setVisible(childManager.isVisible()); legacyModel.setLabel(childManager.getMenuText()); linkModelToManager(legacyModel, childManager); OpaqueElementUtil.setOpaqueItem(legacyModel, childManager); if (modelChildren.size() > dest) { modelChildren.add(dest, legacyModel); } else { modelChildren.add(legacyModel); } reconcileManagerToModel(childManager, legacyModel); } else { if (OpaqueElementUtil.isOpaqueMenu(childModel)) { oldMenus.remove(childModel); } if (modelChildren.size() > dest) { if (modelChildren.get(dest) != childModel) { modelChildren.remove(childModel); modelChildren.add(dest, childModel); } } else { modelChildren.add(childModel); } if (childModel instanceof MPopupMenu) { if (((MPopupMenu) childModel).getContext() == null) { IEclipseContext lclContext = getContext(menuModel); if (lclContext != null) { ((MPopupMenu) childModel).setContext(lclContext.createChild(childModel.getElementId())); } } } if (childModel.getChildren().size() != childManager.getSize()) { reconcileManagerToModel(childManager, childModel); } } } else if (item.isSeparator() || item.isGroupMarker()) { MMenuElement menuElement = getMenuElement(item); if (menuElement == null) { MMenuSeparator legacySep = OpaqueElementUtil.createOpaqueMenuSeparator(); legacySep.setElementId(item.getId()); legacySep.setVisible(item.isVisible()); OpaqueElementUtil.setOpaqueItem(legacySep, item); linkModelToContribution(legacySep, item); if (modelChildren.size() > dest) { modelChildren.add(dest, legacySep); } else { modelChildren.add(legacySep); } } else if (OpaqueElementUtil.isOpaqueMenuSeparator(menuElement)) { MMenuSeparator legacySep = (MMenuSeparator) menuElement; oldSeps.remove(legacySep); if (modelChildren.size() > dest) { if (modelChildren.get(dest) != legacySep) { modelChildren.remove(legacySep); modelChildren.add(dest, legacySep); } } else { modelChildren.add(legacySep); } } } else { MMenuElement menuElement = getMenuElement(item); if (menuElement == null) { MMenuItem legacyItem = OpaqueElementUtil.createOpaqueMenuItem(); legacyItem.setElementId(item.getId()); legacyItem.setVisible(item.isVisible()); OpaqueElementUtil.setOpaqueItem(legacyItem, item); linkModelToContribution(legacyItem, item); if (modelChildren.size() > dest) { modelChildren.add(dest, legacyItem); } else { modelChildren.add(legacyItem); } } else if (OpaqueElementUtil.isOpaqueMenuItem(menuElement)) { MMenuItem legacyItem = (MMenuItem) menuElement; oldModelItems.remove(legacyItem); if (modelChildren.size() > dest) { if (modelChildren.get(dest) != legacyItem) { modelChildren.remove(legacyItem); modelChildren.add(dest, legacyItem); } } else { modelChildren.add(legacyItem); } } } } if (!oldModelItems.isEmpty()) { modelChildren.removeAll(oldModelItems); for (MMenuItem model : oldModelItems) { IContributionItem ici = (IContributionItem) OpaqueElementUtil.getOpaqueItem(model); clearModelToContribution(model, ici); } } if (!oldMenus.isEmpty()) { modelChildren.removeAll(oldMenus); for (MMenu oldMenu : oldMenus) { MenuManager oldManager = getManager(oldMenu); clearModelToManager(oldMenu, oldManager); } } if (!oldSeps.isEmpty()) { modelChildren.removeAll(oldSeps); for (MMenuSeparator model : oldSeps) { IContributionItem item = (IContributionItem) OpaqueElementUtil.getOpaqueItem(model); clearModelToContribution(model, item); } } } /** * @param menuManager * @param element * @param evalContext */ public static void updateVisibility(MenuManager menuManager, MMenuElement element, ExpressionContext evalContext) { boolean current = element.isVisible(); boolean visible = true; boolean evaluated = false; if (element.getPersistedState().get(VISIBILITY_IDENTIFIER) != null) { evaluated = true; String identifier = element.getPersistedState().get(VISIBILITY_IDENTIFIER); Object rc = evalContext.eclipseContext.get(identifier); if (rc instanceof Boolean) { visible = ((Boolean) rc).booleanValue(); } } if (visible && element.getVisibleWhen() != null) { evaluated = true; visible = ContributionsAnalyzer.isVisible(element.getVisibleWhen(), evalContext); } if (evaluated && visible != current) { element.setVisible(visible); menuManager.markDirty(); } } /** * Clean dynamic menu contributions provided by * {@link MDynamicMenuContribution} application model elements * * @param menuManager * @param menuModel * @param dump */ public void removeDynamicMenuContributions(MenuManager menuManager, MMenu menuModel, ArrayList<MMenuElement> dump) { removeMenuContributions(menuModel, dump); for (MMenuElement mMenuElement : dump) { IContributionItem ici = getContribution(mMenuElement); if (ici == null && mMenuElement instanceof MMenu) { MMenu menuElement = (MMenu) mMenuElement; ici = getManager(menuElement); clearModelToManager(menuElement, (MenuManager) ici); } else { clearModelToContribution(menuModel, ici); } menuManager.remove(ici); clearModelToContribution(mMenuElement, ici); } } /** * Remove all dynamic contribution items and their model for the MenuManager * specified. * * @param menuManager * @param menuModel */ @SuppressWarnings("unchecked") public void removeDynamicMenuContributions(MenuManager menuManager, MMenu menuModel) { for (MMenuElement menuElement : new HashSet<>(modelToContribution.keySet())) { if (menuElement instanceof MDynamicMenuContribution) { // // Find Dynamic MMenuElements for the MenuManager specified. // final IContributionItem contributionItem = modelToContribution.get(menuElement); if (contributionItem instanceof DynamicContributionContributionItem) { final DynamicContributionContributionItem dynamicContributionItem = (DynamicContributionContributionItem) contributionItem; if ((dynamicContributionItem.getParent() instanceof MenuManager) && dynamicContributionItem.getParent().equals(menuManager)) { // // Remove the dynamically created menu elements. // final ArrayList<MMenuElement> childElements = (ArrayList<MMenuElement>) menuElement .getTransientData().get(MenuManagerShowProcessor.DYNAMIC_ELEMENT_STORAGE_KEY); if (childElements != null) { removeDynamicMenuContributions(menuManager, menuModel, childElements); } } } } } } private void unlinkMenu(MMenu menu) { List<MMenuElement> children = menu.getChildren(); for (MMenuElement child : children) { if (child instanceof MMenu) unlinkMenu((MMenu) child); else { IContributionItem contribution = getContribution(child); clearModelToContribution(child, contribution); } } MenuManager mm = getManager(menu); clearModelToManager(menu, mm); } private void scheduleManagerUpdate(IContributionManager mgr) { // Bug 467000: Avoid repeatedly updating menu managers // This workaround is opt-in for 4.5 boolean workaroundEnabled = Boolean.getBoolean("eclipse.workaround.bug467000"); //$NON-NLS-1$ if (!workaroundEnabled) { mgr.update(false); return; } synchronized (mgrToUpdate) { if (this.mgrToUpdate.isEmpty()) { Display display = context.get(Display.class); if (display != null && !display.isDisposed()) { display.timerExec(100, () -> { Collection<IContributionManager> toUpdate = new LinkedHashSet<>(); synchronized (mgrToUpdate) { toUpdate.addAll(mgrToUpdate); mgrToUpdate.clear(); } for (IContributionManager mgr1 : toUpdate) { mgr1.update(false); } }); } this.mgrToUpdate.add(mgr); } } } }