blob: 58f556b9f59ed6acdb00d15172ce28e6d687373f [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2012 Rushan R. Gilmullin and others.
* All rights reserved. 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:
* Rushan R. Gilmullin - initial API and implementation
*******************************************************************************/
package org.eclipse.osbp.vaaclipse.contributions;
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.internal.expressions.OrExpression;
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.e4.core.commands.ExpressionContext;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.util.EcoreUtil;
public class ContributionRecord {
public static final String FACTORY = "ContributionFactory"; //$NON-NLS-1$
static final String STATIC_CONTEXT = "ContributionFactoryContext"; //$NON-NLS-1$
MMenu menuModel;
MMenuContribution menuContribution;
ArrayList<MMenuElement> generatedElements = new ArrayList<MMenuElement>();
HashSet<MMenuElement> sharedElements = new HashSet<MMenuElement>();
MenuContributionProcessor renderer;
boolean isVisible = true;
private IEclipseContext infoContext;
private Runnable factoryDispose;
public ContributionRecord(MMenu menuModel, MMenuContribution contribution,
MenuContributionProcessor renderer) {
this.menuModel = menuModel;
this.menuContribution = contribution;
this.renderer = renderer;
}
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<ContributionRecord>();
recentlyUpdated.add(this);
for (MMenuElement item : generatedElements) {
boolean currentVisibility = computeVisibility(recentlyUpdated,
item, exprContext);
if (item.isVisible() != currentVisibility) {
item.setVisible(currentVisibility);
}
}
for (MMenuElement item : sharedElements) {
boolean currentVisibility = computeVisibility(recentlyUpdated,
item, exprContext);
if (item.isVisible() != currentVisibility) {
item.setVisible(currentVisibility);
}
}
}
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.getVisibleWhen() instanceof MCoreExpression) {
boolean val = ContributionsAnalyzer.isVisible(
(MCoreExpression) 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;
}
// 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<MMenuElement>();
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;
menuModel.getChildren().add(idx++, copy);
} else {
shared.setVisibleWhen(merge(
menuContribution.getVisibleWhen(),
shared.getVisibleWhen()));
copy = shared;
}
sharedElements.add(shared);
} else if (copy instanceof MMenuSeparator) {
MMenuSeparator shared = findExistingSeparator(copy
.getElementId());
if (shared == null) {
shared = (MMenuSeparator) copy;
menuModel.getChildren().add(idx++, copy);
} else {
copy = shared;
}
sharedElements.add(shared);
} else {
generatedElements.add(copy);
menuModel.getChildren().add(idx++, copy);
}
if (copy instanceof MMenu || copy instanceof MMenuSeparator) {
ArrayList<ContributionRecord> array = renderer.getList(copy);
array.add(this);
}
}
return true;
}
/**
* @return
*/
private List<MMenuElement> mergeFactoryIntoModel() {
Object obj = menuContribution.getTransientData().get(FACTORY);
if (!(obj instanceof IContextFunction)) {
return Collections.EMPTY_LIST;
}
IEclipseContext staticContext = getStaticContext();
staticContext.remove(List.class);
// TODO fp
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(MenuContributionProcessor.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) {
ArrayList<ContributionRecord> array = renderer.getList(shared);
array.remove(this);
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];
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$
}
}