/*******************************************************************************
* Copyright (c) 2010, 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,398865,398866,403081,403083
* Bruce Skingle <Bruce.Skingle@immutify.com> - Bug 442570
* Lars Vogel <Lars.Vogel@vogella.com> - Bug 472654
*******************************************************************************/
package org.eclipse.e4.ui.workbench.renderers.swt;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.inject.Inject;
import org.eclipse.e4.core.contexts.ContextInjectionFactory;
import org.eclipse.e4.core.contexts.EclipseContextFactory;
import org.eclipse.e4.core.contexts.IEclipseContext;
import org.eclipse.e4.core.di.annotations.Optional;
import org.eclipse.e4.core.services.contributions.IContributionFactory;
import org.eclipse.e4.core.services.log.Logger;
import org.eclipse.e4.ui.di.AboutToShow;
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.menu.MDynamicMenuContribution;
import org.eclipse.e4.ui.model.application.ui.menu.MMenu;
import org.eclipse.e4.ui.model.application.ui.menu.MMenuElement;
import org.eclipse.e4.ui.model.application.ui.menu.MPopupMenu;
import org.eclipse.e4.ui.workbench.modeling.EModelService;
import org.eclipse.e4.ui.workbench.swt.factories.IRendererFactory;
import org.eclipse.jface.action.IContributionItem;
import org.eclipse.jface.action.IMenuListener2;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.swt.widgets.Menu;
/**
* <code>MenuManagerShowProcessor</code> provides hooks for renderer processing
* before and after the <code>MenuManager</code> calls out to its
* <code>IMenuManagerListener2</code> for the <code>menuAboutToShow</code>
* events.
*/
public class MenuManagerShowProcessor implements IMenuListener2 {
private static void trace(String msg, MenuManager menuManager, MMenu menuModel) {
WorkbenchSWTActivator.trace(Policy.DEBUG_MENUS_FLAG,
msg + ": " + menuManager + ": " + menuManager.getMenu() + ": " //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ menuModel, null);
}
@Inject
private EModelService modelService;
@Inject
private IRendererFactory rendererFactory;
@Inject
private MenuManagerRenderer renderer;
@Inject
private IContributionFactory contributionFactory;
@Inject
@Optional
private Logger logger;
@Override
public void menuAboutToShow(IMenuManager manager) {
if (!(manager instanceof MenuManager)) {
return;
}
MenuManager menuManager = (MenuManager) manager;
final MMenu menuModel = renderer.getMenuModel(menuManager);
final Menu menu = menuManager.getMenu();
if (menuModel != null && menuManager != null) {
cleanUp(menuModel, menuManager);
}
if (menuModel instanceof MPopupMenu) {
showPopup(menu, (MPopupMenu) menuModel, menuManager);
}
AbstractPartRenderer obj = rendererFactory.getRenderer(menuModel,
menu.getParent());
if (!(obj instanceof MenuManagerRenderer)) {
if (Policy.DEBUG_MENUS) {
trace("Not the correct renderer: " + obj, menuManager, menuModel); //$NON-NLS-1$
}
return;
}
MenuManagerRenderer renderer = (MenuManagerRenderer) obj;
if (menuModel.getWidget() == null) {
renderer.bindWidget(menuModel, menuManager.getMenu());
}
}
@Override
public void menuAboutToHide(IMenuManager manager) {
if (!(manager instanceof MenuManager)) {
return;
}
MenuManager menuManager = (MenuManager) manager;
final MMenu menuModel = renderer.getMenuModel(menuManager);
final Menu menu = menuManager.getMenu();
if (menuModel != null) {
processDynamicElements(menuModel, menuManager);
showMenu(menu, menuModel, menuManager);
}
}
/**
* HashMap key for storage of {@link MDynamicMenuContribution} elements used
* in {@link #processDynamicElements(MMenu, MenuManager)}
*/
protected static final String DYNAMIC_ELEMENT_STORAGE_KEY = MenuManagerShowProcessor.class
.getSimpleName() + ".dynamicElements"; //$NON-NLS-1$
/**
* Process dynamic menu contributions provided by
* {@link MDynamicMenuContribution} application model elements
*
* @param menuModel
* @param menuManager
*
*/
private void processDynamicElements(MMenu menuModel, MenuManager menuManager) {
MMenuElement[] menuElements = menuModel.getChildren().toArray(
new MMenuElement[menuModel.getChildren().size()]);
for (MMenuElement currentMenuElement : menuElements) {
if (currentMenuElement instanceof MDynamicMenuContribution) {
MDynamicMenuContribution dmc = (MDynamicMenuContribution) currentMenuElement;
Object contribution = dmc.getObject();
if (contribution == null) {
IEclipseContext context = modelService.getContainingContext(menuModel);
contribution = contributionFactory.create(dmc.getContributionURI(), context);
dmc.setObject(contribution);
}
IEclipseContext dynamicMenuContext = EclipseContextFactory.create();
ArrayList<MMenuElement> mel = new ArrayList<>();
dynamicMenuContext.set(List.class, mel);
dynamicMenuContext.set(MDynamicMenuContribution.class, dmc);
IEclipseContext parentContext = modelService.getContainingContext(currentMenuElement);
Object rc = ContextInjectionFactory.invoke(contribution,
AboutToShow.class, parentContext, dynamicMenuContext,
this);
dynamicMenuContext.dispose();
if (rc == this) {
if (logger != null) {
logger.error("Missing @AboutToShow method in " + contribution); //$NON-NLS-1$
}
continue;
}
if (mel.size() > 0) {
int position = 0;
while (position < menuModel.getChildren().size()) {
if (currentMenuElement == menuModel.getChildren().get(
position)) {
position++;
break;
}
position++;
}
// ensure that each element of the list has a valid element
// id
// and set the parent of the entries
for (int j = 0; j < mel.size(); j++) {
MMenuElement menuElement = mel.get(j);
if (menuElement.getElementId() == null
|| menuElement.getElementId().length() < 1) {
menuElement.setElementId(currentMenuElement
.getElementId() + "." + j); //$NON-NLS-1$
}
menuModel.getChildren().add(position++, menuElement);
renderer.modelProcessSwitch(menuManager, menuElement);
}
currentMenuElement.getTransientData().put(DYNAMIC_ELEMENT_STORAGE_KEY, mel);
}
}
}
}
/**
* Remove all of the items created by any dynamic contributions on the
* menuModel. In addition removes all of the items of menuModel in the case
* all items of menuManager need removal when the menu is about to show.
* This needs to be done or else menu items get added multiple times to
* MenuModel which results in incorrect behavior and memory leak - bug
* 486474
*
* @param menuModel
* @param menuManager
*/
private void cleanUp(MMenu menuModel, MenuManager menuManager) {
if (Policy.DEBUG_MENUS) {
trace("\nCleaning up the dynamic menu contributions", menuManager, menuModel); //$NON-NLS-1$
}
renderer.removeDynamicMenuContributions(menuManager, menuModel);
if (menuManager.getRemoveAllWhenShown()) {
// remove the items from the model related to contributions defined
// with location URIs
if (Policy.DEBUG_MENUS) {
trace("\nCleaning up all of the menu model items", menuManager, menuModel); //$NON-NLS-1$
}
renderer.cleanUp(menuModel);
// cleanup any leftovers - opaque items etc
for (Iterator<MMenuElement> it = menuModel.getChildren().iterator(); it.hasNext();) {
MMenuElement mMenuElement = it.next();
// remove item from the menu model
it.remove();
// cleanup the renderer
IContributionItem ici = renderer.getContribution(mMenuElement);
if (ici == null && mMenuElement instanceof MMenu) {
MMenu menuElement = (MMenu) mMenuElement;
ici = renderer.getManager(menuElement);
renderer.clearModelToManager(menuElement, (MenuManager) ici);
}
renderer.clearModelToContribution(mMenuElement, ici);
}
}
}
private void showPopup(final Menu menu, final MPopupMenu menuModel,
MenuManager menuManager) {
// System.err.println("showPopup: " + menuModel + "\n\t" + menu);
// we need some context foolery here
final IEclipseContext popupContext = menuModel.getContext();
final IEclipseContext parentContext = popupContext.getParent();
final IEclipseContext originalChild = parentContext.getActiveChild();
popupContext.activate();
popupContext.set(MenuManagerRendererFilter.TMP_ORIGINAL_CONTEXT,
originalChild);
}
private void showMenu(final Menu menu, final MMenu menuModel,
MenuManager menuManager) {
final IEclipseContext evalContext;
if (menuModel instanceof MContext) {
evalContext = ((MContext) menuModel).getContext();
} else {
evalContext = modelService.getContainingContext(menuModel);
}
MenuManagerRendererFilter.updateElementVisibility(menuModel, renderer,
menuManager, evalContext, 2, true);
}
}