/******************************************************************************* * Copyright (c) 2015 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: * Jonas Helming, Dirk Fauth - Bug 410087 ******************************************************************************/ package org.eclipse.e4.ui.workbench.renderers.swt; import javax.inject.Inject; import org.eclipse.core.runtime.ISafeRunnable; import org.eclipse.core.runtime.SafeRunner; import org.eclipse.e4.core.contexts.IContextFunction; import org.eclipse.e4.core.contexts.IEclipseContext; import org.eclipse.e4.core.di.annotations.Optional; import org.eclipse.e4.core.services.log.Logger; import org.eclipse.e4.ui.internal.workbench.RenderedElementUtil; import org.eclipse.e4.ui.internal.workbench.swt.AbstractPartRenderer; import org.eclipse.e4.ui.internal.workbench.swt.Policy; import org.eclipse.e4.ui.internal.workbench.swt.WorkbenchSWTActivator; import org.eclipse.e4.ui.model.application.ui.MContext; import org.eclipse.e4.ui.model.application.ui.MUIElement; import org.eclipse.e4.ui.model.application.ui.menu.ItemType; import org.eclipse.e4.ui.model.application.ui.menu.MItem; import org.eclipse.e4.ui.model.application.ui.menu.MMenu; import org.eclipse.e4.ui.model.application.ui.menu.MToolItem; import org.eclipse.e4.ui.services.help.EHelpService; import org.eclipse.e4.ui.workbench.IPresentationEngine; import org.eclipse.e4.ui.workbench.IResourceUtilities; import org.eclipse.e4.ui.workbench.modeling.EModelService; import org.eclipse.e4.ui.workbench.swt.util.ISWTResourceUtilities; import org.eclipse.emf.common.util.URI; import org.eclipse.jface.action.ContributionItem; import org.eclipse.jface.action.IContributionManager; import org.eclipse.jface.action.IMenuCreator; import org.eclipse.jface.action.IMenuListener; import org.eclipse.jface.action.IMenuManager; import org.eclipse.jface.resource.DeviceResourceException; 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.graphics.Image; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; 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; /** * Common super class for HandledContributionItem and DirectContributionItem */ public abstract class AbstractContributionItem extends ContributionItem { protected static final String FORCE_TEXT = "FORCE_TEXT"; //$NON-NLS-1$ protected static final String ICON_URI = "iconURI"; //$NON-NLS-1$ protected static final String DISABLED_URI = "disabledURI"; //$NON-NLS-1$ /** * Internal key for transient maps to provide a runnable on widget disposal */ public static final String DISPOSABLE = "IDisposable"; //$NON-NLS-1$ @Inject @Optional protected Logger logger; @Inject private EModelService modelService; @Inject @Optional protected EHelpService helpService; protected Widget widget; protected Listener menuItemListener; protected LocalResourceManager localResourceManager; MItem modelItem; private ISafeRunnable updateRunner; private ISWTResourceUtilities resUtils = null; protected IMenuListener menuListener = manager -> update(null); /** * Flag to ensure that an error during updates are only logged once to * prevent spamming the log. Is set to <code>true</code> after an error on * update was logged the first time. */ private boolean logged = false; /** * */ public AbstractContributionItem() { super(); } /** * @param id */ public AbstractContributionItem(String id) { super(id); } @Override public void update() { update(null); } @Override public void update(String id) { updateIcons(); if (widget instanceof MenuItem) { updateMenuItem(); } else if (widget instanceof ToolItem) { updateToolItem(); } } protected abstract void updateMenuItem(); protected abstract void updateToolItem(); @Inject void setResourceUtils(IResourceUtilities<ImageDescriptor> utils) { resUtils = (ISWTResourceUtilities) utils; } protected Image getImage(String iconURI, LocalResourceManager resourceManager) { Image image = null; if (iconURI != null && iconURI.length() > 0) { ImageDescriptor iconDescriptor = resUtils.imageDescriptorFromURI(URI.createURI(iconURI)); if (iconDescriptor != null) { try { image = resourceManager.createImage(iconDescriptor); } catch (DeviceResourceException e) { iconDescriptor = ImageDescriptor.getMissingImageDescriptor(); image = resourceManager.createImage(iconDescriptor); // as we replaced the failed icon, log the message once. if (Policy.DEBUG_MENUS) { WorkbenchSWTActivator.trace(Policy.DEBUG_MENUS_FLAG, "failed to create image " + iconURI, e); //$NON-NLS-1$ } } } } return image; } protected void updateIcons() { if (!(widget instanceof Item)) { return; } Item item = (Item) widget; String iconURI = modelItem.getIconURI() != null ? modelItem.getIconURI() : ""; //$NON-NLS-1$ String disabledURI = getDisabledIconURI(modelItem); Object disabledData = item.getData(DISABLED_URI); if (disabledData == null) disabledData = ""; //$NON-NLS-1$ if (!iconURI.equals(item.getData(ICON_URI)) || !disabledURI.equals(disabledData)) { LocalResourceManager resourceManager = new LocalResourceManager(JFaceResources.getResources()); Image iconImage = getImage(iconURI, resourceManager); item.setImage(iconImage); item.setData(ICON_URI, iconURI); if (item instanceof ToolItem) { iconImage = getImage(disabledURI, resourceManager); ((ToolItem) item).setDisabledImage(iconImage); item.setData(DISABLED_URI, disabledURI); } disposeOldImages(); localResourceManager = resourceManager; } } private String getDisabledIconURI(MItem toolItem) { Object obj = toolItem.getTransientData().get(IPresentationEngine.DISABLED_ICON_IMAGE_KEY); return obj instanceof String ? (String) obj : ""; //$NON-NLS-1$ } protected void disposeOldImages() { if (localResourceManager != null) { localResourceManager.dispose(); localResourceManager = null; } } /** * @param item * the model item */ public void setModel(MItem item) { modelItem = item; setId(modelItem.getElementId()); updateVisible(); } /** * @return the model */ public MItem getModel() { return modelItem; } @Override public void setParent(IContributionManager parent) { if (getParent() instanceof IMenuManager) { IMenuManager menuMgr = (IMenuManager) getParent(); menuMgr.removeMenuListener(menuListener); } if (parent instanceof IMenuManager) { IMenuManager menuMgr = (IMenuManager) parent; menuMgr.addMenuListener(menuListener); } super.setParent(parent); } @Override public void fill(Menu menu, int index) { if (modelItem == null) { return; } if (widget != null) { return; } int style = SWT.PUSH; if (modelItem.getType() == ItemType.PUSH) style = SWT.PUSH; else if (modelItem.getType() == ItemType.CHECK) style = SWT.CHECK; else if (modelItem.getType() == ItemType.RADIO) style = SWT.RADIO; MenuItem item = null; if (index >= 0) { item = new MenuItem(menu, style, index); } else { item = new MenuItem(menu, style); } item.setData(this); item.addListener(SWT.Dispose, getItemListener()); item.addListener(SWT.Selection, getItemListener()); item.addListener(SWT.DefaultSelection, getItemListener()); item.addListener(SWT.Help, getItemListener()); widget = item; modelItem.setWidget(widget); widget.setData(AbstractPartRenderer.OWNING_ME, modelItem); update(null); postMenuFill(); } /** * This method is intended to perform actions additionally to the common * actions in {@link AbstractContributionItem#fill(Menu, int)} */ protected void postMenuFill() { } @Override public void fill(ToolBar parent, int index) { if (modelItem == null) { return; } if (widget != null) { return; } boolean isDropdown = false; if (modelItem instanceof MToolItem) { MMenu menu = ((MToolItem) modelItem).getMenu(); isDropdown = menu != null; } int style = SWT.PUSH; if (isDropdown) style = SWT.DROP_DOWN; else if (modelItem.getType() == ItemType.CHECK) style = SWT.CHECK; else if (modelItem.getType() == ItemType.RADIO) style = SWT.RADIO; ToolItem item = null; if (index >= 0) { item = new ToolItem(parent, style, index); } else { item = new ToolItem(parent, style); } item.setData(this); item.addListener(SWT.Dispose, getItemListener()); item.addListener(SWT.Selection, getItemListener()); item.addListener(SWT.DefaultSelection, getItemListener()); widget = item; modelItem.setWidget(widget); widget.setData(AbstractPartRenderer.OWNING_ME, modelItem); ToolItemUpdater updater = getUpdater(); if (updater != null) { updater.registerItem(this); } update(null); postToolbarFill(); } /** * This method is intended to perform actions additionally to the common * actions in {@link AbstractContributionItem#fill(ToolBar, int)} */ protected void postToolbarFill() { } /** * Return a parent context for this part. * * @param element * the part to start searching from * @return the parent's closest context, or global context if none in the * hierarchy */ protected IEclipseContext getContextForParent(MUIElement element) { return modelService.getContainingContext(element); } /** * Return a context for this part. * * @param part * the part to start searching from * @return the closest context, or global context if none in the hierarchy */ protected IEclipseContext getContext(MUIElement part) { if (part instanceof MContext) { return ((MContext) part).getContext(); } return getContextForParent(part); } /** * @return the widgets of the contribution */ public Widget getWidget() { return widget; } protected Menu getMenu(final MMenu mmenu, ToolItem toolItem) { Object obj = mmenu.getWidget(); if (obj instanceof Menu && !((Menu) obj).isDisposed()) { return (Menu) obj; } // this is a temporary passthrough of the IMenuCreator if (RenderedElementUtil.isRenderedMenu(mmenu)) { obj = RenderedElementUtil.getContributionManager(mmenu); if (obj instanceof IContextFunction) { final IEclipseContext lclContext = getContext(mmenu); obj = ((IContextFunction) obj).compute(lclContext, null); RenderedElementUtil.setContributionManager(mmenu, obj); } if (obj instanceof IMenuCreator) { final IMenuCreator creator = (IMenuCreator) obj; final Menu menu = creator.getMenu(toolItem.getParent().getShell()); if (menu != null) { toolItem.addDisposeListener(e -> { if (menu != null && !menu.isDisposed()) { creator.dispose(); mmenu.setWidget(null); } }); menu.setData(AbstractPartRenderer.OWNING_ME, menu); return menu; } } } else { final IEclipseContext lclContext = getContext(getModel()); IPresentationEngine engine = lclContext.get(IPresentationEngine.class); obj = engine.createGui(mmenu, toolItem.getParent(), lclContext); if (obj instanceof Menu) { return (Menu) obj; } if (logger != null) { logger.debug("Rendering returned " + obj); //$NON-NLS-1$ } } return null; } /** * @param event * @return whether the event was a drop down on a toolitem */ protected boolean dropdownEvent(Event event) { if (event.detail == SWT.ARROW && modelItem instanceof MToolItem) { ToolItem ti = (ToolItem) event.widget; MMenu mmenu = ((MToolItem) modelItem).getMenu(); if (mmenu == null) { return false; } Menu menu = getMenu(mmenu, ti); if (menu == null || menu.isDisposed()) { return true; } Rectangle itemBounds = ti.getBounds(); Point displayAt = ti.getParent().toDisplay(itemBounds.x, itemBounds.y + itemBounds.height); menu.setLocation(displayAt); menu.setVisible(true); Display display = menu.getDisplay(); while (!menu.isDisposed() && menu.isVisible()) { if (!display.readAndDispatch()) { display.sleep(); } } return true; } return false; } protected void handleWidgetSelection(Event event) { if (widget != null && !widget.isDisposed()) { if (dropdownEvent(event)) { return; } if (modelItem.getType() == ItemType.CHECK || modelItem.getType() == ItemType.RADIO) { boolean selection = false; if (widget instanceof MenuItem) { selection = ((MenuItem) widget).getSelection(); } else if (widget instanceof ToolItem) { selection = ((ToolItem) widget).getSelection(); } modelItem.setSelected(selection); } if (canExecuteItem(event)) { executeItem(event); } } } /** * @param event */ protected abstract void executeItem(Event event); /** * @param event * @return if the item can be executed */ protected abstract boolean canExecuteItem(Event event); protected Listener getItemListener() { if (menuItemListener == null) { menuItemListener = event -> { switch (event.type) { case SWT.Dispose: handleWidgetDispose(event); break; case SWT.DefaultSelection: case SWT.Selection: if (event.widget != null) { handleWidgetSelection(event); } break; case SWT.Help: handleHelpRequest(); break; } }; } return menuItemListener; } /** * */ protected void handleHelpRequest() { if (helpService == null) return; String helpContextId = getModel().getPersistedState().get(EHelpService.HELP_CONTEXT_ID); if (helpContextId != null) helpService.displayHelp(helpContextId); } /** * @param event */ protected abstract void handleWidgetDispose(Event event); protected void updateVisible() { setVisible((modelItem).isVisible()); final IContributionManager parent = getParent(); if (parent != null) { parent.markDirty(); } } private ISafeRunnable getUpdateRunner() { if (updateRunner == null) { updateRunner = new ISafeRunnable() { @Override public void run() throws Exception { boolean shouldEnable = canExecuteItem(null); if (shouldEnable != modelItem.isEnabled()) { modelItem.setEnabled(shouldEnable); update(); } } @Override public void handleException(Throwable exception) { if (!logged) { logged = true; if (logger != null) { logger.error(exception, "Internal error during tool item enablement updating, this is only logged once per tool item."); //$NON-NLS-1$ } } } }; } return updateRunner; } protected ToolItemUpdater getUpdater() { if (modelItem != null) { Object obj = modelItem.getRenderer(); if (obj instanceof ToolBarManagerRenderer) { return ((ToolBarManagerRenderer) obj).getUpdater(); } } return null; } protected void updateItemEnablement() { if (!(modelItem.getWidget() instanceof ToolItem)) return; ToolItem widget = (ToolItem) modelItem.getWidget(); if (widget == null || widget.isDisposed()) return; SafeRunner.run(getUpdateRunner()); } }