blob: ba334a6770efdb3f84ff408371858f6284a88f41 [file] [log] [blame]
/*******************************************************************************
* 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$
}
}