| /******************************************************************************* |
| * Copyright (c) 2011, 2015 IBM Corporation and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| * Lars Vogel <Lars.Vogel@vogella.com> - Bug 472654 |
| * Simon Scholz <simon.scholz@vogella.com> - Bug 484427 |
| ******************************************************************************/ |
| |
| package org.eclipse.e4.ui.workbench.renderers.swt; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import org.eclipse.core.expressions.Expression; |
| import org.eclipse.core.expressions.ExpressionInfo; |
| import org.eclipse.core.expressions.OrExpression; |
| import org.eclipse.e4.core.commands.ExpressionContext; |
| import org.eclipse.e4.core.contexts.EclipseContextFactory; |
| import org.eclipse.e4.core.contexts.IContextFunction; |
| import org.eclipse.e4.core.contexts.IEclipseContext; |
| import org.eclipse.e4.ui.internal.workbench.ContributionsAnalyzer; |
| import org.eclipse.e4.ui.model.application.ui.MCoreExpression; |
| import org.eclipse.e4.ui.model.application.ui.MElementContainer; |
| import org.eclipse.e4.ui.model.application.ui.MExpression; |
| import org.eclipse.e4.ui.model.application.ui.impl.UiFactoryImpl; |
| 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.MMenuSeparator; |
| import org.eclipse.emf.ecore.EObject; |
| import org.eclipse.emf.ecore.util.EcoreUtil; |
| import org.eclipse.jface.action.MenuManager; |
| |
| public class ContributionRecord { |
| public static final String FACTORY = "ContributionFactory"; //$NON-NLS-1$ |
| static final String STATIC_CONTEXT = "ContributionFactoryContext"; //$NON-NLS-1$ |
| |
| MMenu menuModel; |
| private MMenuContribution menuContribution; |
| private ArrayList<MMenuElement> generatedElements = new ArrayList<>(); |
| private HashSet<MMenuElement> sharedElements = new HashSet<>(); |
| private MenuManagerRenderer renderer; |
| boolean isVisible = true; |
| private IEclipseContext infoContext; |
| private Runnable factoryDispose; |
| |
| public ContributionRecord(MMenu menuModel, MMenuContribution contribution, |
| MenuManagerRenderer renderer) { |
| this.menuModel = menuModel; |
| this.menuContribution = contribution; |
| this.renderer = renderer; |
| } |
| |
| public MenuManager getManagerForModel() { |
| return renderer.getManager(menuModel); |
| } |
| |
| public MMenuContribution getMenuContribution() { |
| return menuContribution; |
| } |
| |
| /** |
| * Access to analyze for tests. For Looking, not touching! |
| * |
| * @return the shared elements collection |
| */ |
| public Collection<MMenuElement> getSharedElements() { |
| return sharedElements; |
| } |
| |
| /** |
| * Access to analyze for tests. For Looking, not touching! |
| * |
| * @return the generated elements collection |
| */ |
| public Collection<MMenuElement> getGeneratedElements() { |
| return generatedElements; |
| } |
| |
| /** |
| * @param context |
| */ |
| public void updateVisibility(IEclipseContext context) { |
| ExpressionContext exprContext = new ExpressionContext(context); |
| updateIsVisible(exprContext); |
| HashSet<ContributionRecord> recentlyUpdated = new HashSet<>(); |
| recentlyUpdated.add(this); |
| boolean changed = false; |
| for (MMenuElement item : generatedElements) { |
| boolean currentVisibility = computeVisibility(recentlyUpdated, |
| item, exprContext); |
| if (item.isVisible() != currentVisibility) { |
| changed = true; |
| item.setVisible(currentVisibility); |
| } |
| } |
| for (MMenuElement item : sharedElements) { |
| boolean currentVisibility = computeVisibility(recentlyUpdated, |
| item, exprContext); |
| if (item.isVisible() != currentVisibility) { |
| changed = true; |
| item.setVisible(currentVisibility); |
| } |
| } |
| |
| if (changed) { |
| MenuManager manager = getManagerForModel(); |
| if (manager != null) { |
| manager.markDirty(); |
| } |
| } |
| } |
| |
| public void collectInfo(ExpressionInfo info) { |
| ContributionsAnalyzer.collectInfo(info, |
| menuContribution.getVisibleWhen()); |
| for (MMenuElement item : generatedElements) { |
| ContributionsAnalyzer.collectInfo(info, item.getVisibleWhen()); |
| } |
| for (MMenuElement item : sharedElements) { |
| ContributionsAnalyzer.collectInfo(info, item.getVisibleWhen()); |
| } |
| } |
| |
| public void updateIsVisible(ExpressionContext exprContext) { |
| isVisible = ContributionsAnalyzer.isVisible(menuContribution, |
| exprContext); |
| } |
| |
| public boolean computeVisibility( |
| HashSet<ContributionRecord> recentlyUpdated, MMenuElement item, |
| ExpressionContext exprContext) { |
| boolean currentVisibility = isVisible; |
| if (item instanceof MMenu || item instanceof MMenuSeparator) { |
| ArrayList<ContributionRecord> list = renderer.getList(item); |
| if (list != null) { |
| Iterator<ContributionRecord> cr = list.iterator(); |
| while (!currentVisibility && cr.hasNext()) { |
| ContributionRecord rec = cr.next(); |
| if (!recentlyUpdated.contains(rec)) { |
| rec.updateIsVisible(exprContext); |
| recentlyUpdated.add(rec); |
| } |
| currentVisibility |= rec.isVisible; |
| } |
| } |
| } |
| if (currentVisibility |
| && item.getPersistedState().get( |
| MenuManagerRenderer.VISIBILITY_IDENTIFIER) != null) { |
| String identifier = item.getPersistedState().get( |
| MenuManagerRenderer.VISIBILITY_IDENTIFIER); |
| Object rc = exprContext.eclipseContext.get(identifier); |
| if (rc instanceof Boolean) { |
| currentVisibility = ((Boolean) rc).booleanValue(); |
| } |
| } |
| if (currentVisibility && item.getVisibleWhen() != null) { |
| boolean val = ContributionsAnalyzer.isVisible(item.getVisibleWhen(), exprContext); |
| currentVisibility = val; |
| } |
| return currentVisibility; |
| } |
| |
| private Expression getExpression(MExpression expression) { |
| if (expression instanceof MCoreExpression) { |
| Object coreExpression = ((MCoreExpression) expression) |
| .getCoreExpression(); |
| return coreExpression instanceof Expression ? (Expression) coreExpression |
| : null; |
| } |
| return null; |
| } |
| |
| private MExpression merge(MExpression expressionA, MExpression expressionB) { |
| Expression coreExpressionA = getExpression(expressionA); |
| Expression coreExpressionB = getExpression(expressionB); |
| if (coreExpressionA == null || coreExpressionB == null) { |
| // implied to always be visible |
| return null; |
| } |
| if (coreExpressionA.equals(coreExpressionB)) { |
| return expressionA; |
| } |
| |
| // combine the two expressions |
| OrExpression expression = new OrExpression(); |
| expression.add(coreExpressionA); |
| expression.add(coreExpressionB); |
| |
| MCoreExpression exp = UiFactoryImpl.eINSTANCE.createCoreExpression(); |
| exp.setCoreExpressionId("programmatic.value"); //$NON-NLS-1$ |
| exp.setCoreExpression(expression); |
| return exp; |
| } |
| |
| public boolean mergeIntoModel() { |
| int idx = getIndex(menuModel, menuContribution.getPositionInParent()); |
| if (idx == -1) { |
| return false; |
| } |
| |
| final List<MMenuElement> copyElements; |
| if (menuContribution.getTransientData().get(FACTORY) != null) { |
| copyElements = mergeFactoryIntoModel(); |
| } else { |
| copyElements = new ArrayList<>(); |
| for (MMenuElement item : menuContribution.getChildren()) { |
| MMenuElement copy = (MMenuElement) EcoreUtil |
| .copy((EObject) item); |
| copyElements.add(copy); |
| } |
| } |
| |
| for (MMenuElement copy : copyElements) { |
| if (copy instanceof MMenu) { |
| MMenu shared = findExistingMenu(copy.getElementId()); |
| if (shared == null) { |
| shared = (MMenu) copy; |
| renderer.linkElementToContributionRecord(copy, this); |
| menuModel.getChildren().add(idx++, copy); |
| } else { |
| shared.setVisibleWhen(merge(copy.getVisibleWhen(), |
| shared.getVisibleWhen())); |
| copy = shared; |
| } |
| sharedElements.add(shared); |
| } else if (copy instanceof MMenuSeparator) { |
| MMenuSeparator shared = findExistingSeparator(copy |
| .getElementId()); |
| if (shared == null) { |
| shared = (MMenuSeparator) copy; |
| renderer.linkElementToContributionRecord(copy, this); |
| menuModel.getChildren().add(idx++, copy); |
| } else { |
| copy = shared; |
| } |
| sharedElements.add(shared); |
| } else { |
| generatedElements.add(copy); |
| renderer.linkElementToContributionRecord(copy, this); |
| menuModel.getChildren().add(idx++, copy); |
| } |
| if (copy instanceof MMenu || copy instanceof MMenuSeparator) { |
| renderer.addRecord(copy, this); |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * @return |
| */ |
| private List<MMenuElement> mergeFactoryIntoModel() { |
| Object obj = menuContribution.getTransientData().get(FACTORY); |
| if (!(obj instanceof IContextFunction)) { |
| return Collections.emptyList(); |
| } |
| IEclipseContext staticContext = getStaticContext(); |
| staticContext.remove(List.class); |
| factoryDispose = (Runnable) ((IContextFunction) obj).compute( |
| staticContext, null); |
| return staticContext.get(List.class); |
| } |
| |
| private IEclipseContext getStaticContext() { |
| if (infoContext == null) { |
| IEclipseContext parentContext = renderer.getContext(menuModel); |
| if (parentContext != null) { |
| infoContext = parentContext.createChild(STATIC_CONTEXT); |
| } else { |
| infoContext = EclipseContextFactory.create(STATIC_CONTEXT); |
| } |
| ContributionsAnalyzer.populateModelInterfaces(menuModel, |
| infoContext, menuModel.getClass().getInterfaces()); |
| infoContext.set(MenuManagerRenderer.class, renderer); |
| } |
| return infoContext; |
| } |
| |
| MMenu findExistingMenu(String id) { |
| if (id == null) { |
| return null; |
| } |
| for (MMenuElement item : menuModel.getChildren()) { |
| if (item instanceof MMenu && id.equals(item.getElementId())) { |
| return (MMenu) item; |
| } |
| } |
| return null; |
| } |
| |
| MMenuSeparator findExistingSeparator(String id) { |
| if (id == null) { |
| return null; |
| } |
| for (MMenuElement item : menuModel.getChildren()) { |
| if (item instanceof MMenuSeparator |
| && id.equals(item.getElementId())) { |
| return (MMenuSeparator) item; |
| } |
| } |
| return null; |
| } |
| |
| public void dispose() { |
| for (MMenuElement copy : generatedElements) { |
| menuModel.getChildren().remove(copy); |
| } |
| for (MMenuElement shared : sharedElements) { |
| renderer.removeRecord(shared, this); |
| ArrayList<ContributionRecord> array = renderer.getList(shared); |
| if (array.isEmpty()) { |
| menuModel.getChildren().remove(shared); |
| } |
| } |
| if (factoryDispose != null) { |
| factoryDispose.run(); |
| factoryDispose = null; |
| } |
| } |
| |
| private static int getIndex(MElementContainer<?> menuModel, |
| String positionInParent) { |
| String id = null; |
| String modifier = null; |
| if (positionInParent != null && positionInParent.length() > 0) { |
| String[] array = positionInParent.split("="); //$NON-NLS-1$ |
| modifier = array[0]; |
| if (array.length > 1) { |
| id = array[1]; |
| } |
| } |
| if (id == null) { |
| return menuModel.getChildren().size(); |
| } |
| |
| int idx = 0; |
| int size = menuModel.getChildren().size(); |
| while (idx < size) { |
| if (id.equals(menuModel.getChildren().get(idx).getElementId())) { |
| if ("after".equals(modifier)) { //$NON-NLS-1$ |
| idx++; |
| } else if ("endof".equals(modifier)) { //$NON-NLS-1$ |
| // Skip current menu item |
| idx++; |
| |
| // Skip all menu items until next MenuSeparator is found |
| while (idx < size |
| && !(menuModel.getChildren().get(idx) instanceof MMenuSeparator && menuModel |
| .getChildren().get(idx).getElementId() != null)) { |
| idx++; |
| } |
| } |
| return idx; |
| } |
| idx++; |
| } |
| return id.equals("additions") ? menuModel.getChildren().size() : -1; //$NON-NLS-1$ |
| } |
| } |