| /******************************************************************************* |
| * Copyright (c) 2012 BestSolution.at 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: |
| * Tom Schindl<tom.schindl@bestsolution.at> - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.fx.ui.workbench.renderers.base; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.Optional; |
| |
| import javax.inject.Inject; |
| |
| import org.eclipse.core.expressions.ExpressionInfo; |
| import org.eclipse.e4.core.commands.ExpressionContext; |
| import org.eclipse.e4.core.contexts.ContextInjectionFactory; |
| import org.eclipse.e4.core.contexts.IEclipseContext; |
| import org.eclipse.e4.core.contexts.RunAndTrack; |
| import org.eclipse.e4.core.services.events.IEventBroker; |
| import org.eclipse.e4.ui.internal.workbench.ContributionsAnalyzer; |
| import org.eclipse.e4.ui.model.application.MApplicationElement; |
| import org.eclipse.e4.ui.model.application.ui.MContext; |
| 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.MUIElement; |
| import org.eclipse.e4.ui.model.application.ui.MUILabel; |
| import org.eclipse.e4.ui.model.application.ui.advanced.MPlaceholder; |
| import org.eclipse.e4.ui.model.application.ui.basic.MPart; |
| import org.eclipse.e4.ui.model.application.ui.basic.MWindow; |
| import org.eclipse.e4.ui.workbench.IPresentationEngine; |
| import org.eclipse.e4.ui.workbench.UIEvents; |
| import org.eclipse.e4.ui.workbench.UIEvents.ApplicationElement; |
| import org.eclipse.e4.ui.workbench.modeling.EModelService; |
| import org.eclipse.e4.ui.workbench.modeling.EPartService; |
| import org.eclipse.emf.ecore.EAttribute; |
| import org.eclipse.emf.ecore.EClass; |
| import org.eclipse.emf.ecore.EObject; |
| import org.eclipse.emf.ecore.EStructuralFeature; |
| import org.eclipse.fx.core.log.Log; |
| import org.eclipse.fx.core.log.Logger; |
| import org.eclipse.fx.core.log.Logger.Level; |
| import org.eclipse.fx.ui.services.Constants; |
| import org.eclipse.fx.ui.workbench.base.rendering.ElementRenderer; |
| import org.eclipse.fx.ui.workbench.renderers.base.widget.WPropertyChangeHandler.WPropertyChangeEvent; |
| import org.eclipse.fx.ui.workbench.renderers.base.widget.WLayoutedWidget; |
| import org.eclipse.fx.ui.workbench.renderers.base.widget.WPlaceholderWidget; |
| import org.eclipse.fx.ui.workbench.renderers.base.widget.WWidget; |
| import org.eclipse.fx.ui.workbench.renderers.base.widget.WWidget.WidgetState; |
| import org.eclipse.fx.ui.workbench.services.EModelStylingService; |
| import org.eclipse.jdt.annotation.NonNull; |
| import org.eclipse.jdt.annotation.Nullable; |
| import org.osgi.service.event.Event; |
| |
| /** |
| * Base class foe all renderers |
| * |
| * @param <M> |
| * the model type |
| * @param <W> |
| * the abstracted widget type |
| */ |
| @SuppressWarnings("restriction") |
| public abstract class BaseRenderer<M extends MUIElement, W extends WWidget<M>> implements ElementRenderer<M, W> { |
| private static final String RENDERING_CONTEXT_KEY = "fx.rendering.context"; //$NON-NLS-1$ |
| |
| /** |
| * Key used to store the dom element in the context |
| */ |
| public static final String CONTEXT_DOM_ELEMENT = "fx.rendering.domElement"; //$NON-NLS-1$ |
| |
| private static final String RENDER_KEY = "_renderer"; //$NON-NLS-1$ |
| |
| // /** |
| // * Key used to store the localized label in the context |
| // */ |
| // public static final String ATTRIBUTE_localizedLabel = "localizedLabel"; |
| // //$NON-NLS-1$ |
| // /** |
| // * Key used to store the localized tooltip in the context |
| // */ |
| // public static final String ATTRIBUTE_localizedTooltip = |
| // "localizedTooltip"; //$NON-NLS-1$ |
| |
| @Inject |
| IEclipseContext _context; // The |
| // rendering |
| // context |
| |
| @Inject |
| EModelService modelService; |
| |
| @Inject |
| EModelStylingService modelStylingService; |
| |
| // boolean inContentProcessing; |
| // |
| // boolean inContextModification; |
| // |
| // boolean inUIModification; |
| |
| Map<String, EAttribute> attributeMap = new HashMap<String, EAttribute>(); |
| |
| private Map<MUIElement, Boolean> contentProcessing = new HashMap<MUIElement, Boolean>(); |
| Map<MUIElement, Boolean> contextModification = new HashMap<MUIElement, Boolean>(); |
| Map<MUIElement, Boolean> uiModification = new HashMap<MUIElement, Boolean>(); |
| |
| @Inject |
| @Log |
| Logger logger; |
| |
| @NonNull |
| final Map<@NonNull MUIElement, @NonNull ActiveLeafRunAndTrack> visibleWhenElements = new HashMap<>(); |
| |
| /** |
| * Transient data key used to remember the calculated visibility which is |
| * built from |
| * <ul> |
| * <li>{@link MUIElement#getVisibleWhen()}</li> |
| * <li>{@link MUIElement#isVisible()}</li> |
| * </ul> |
| */ |
| public static final String CALCULATED_VISIBILITY = "efx_calculated_visibility"; //$NON-NLS-1$ |
| |
| /** |
| * Constant used to remember the current state of the visible when |
| * expression |
| */ |
| public static final String CURRENT_VISIBLE_WHEN = "efx_current_visible_when"; //$NON-NLS-1$ |
| |
| /** |
| * @return the logger |
| */ |
| protected Logger getLogger() { |
| return this.logger; |
| } |
| |
| /** |
| * Check if we are currently processing this element |
| * |
| * @param element |
| * the element to check |
| * @return <code>true</code> if element is currently processed |
| */ |
| protected boolean inContentProcessing(MUIElement element) { |
| return this.contentProcessing.get(element) == Boolean.TRUE; |
| } |
| |
| /** |
| * Check if we are currently modifying the context of this element |
| * |
| * @param element |
| * the element to check |
| * @return <code>true</code> if the elements context is currently modified |
| */ |
| protected boolean inContextModification(@NonNull MUIElement element) { |
| return this.contextModification.get(element) == Boolean.TRUE; |
| } |
| |
| /** |
| * Check if we are currently modifying the UI of the given element |
| * |
| * @param element |
| * the element to check |
| * @return <code>true</code> if the elements ui is currently modified |
| */ |
| protected boolean inUIModification(@NonNull MUIElement element) { |
| return this.uiModification.get(element) == Boolean.TRUE; |
| } |
| |
| @Override |
| @NonNull |
| public final W createWidget(final @NonNull M element) { |
| final IEclipseContext context = setupRenderingContext(element); |
| |
| W widget = ContextInjectionFactory.make(getWidgetClass(element), context); |
| // Bug 433845 |
| widget.setPropertyChangeHandler((WPropertyChangeEvent<W> e) -> propertyObjectChanged(element, e)); |
| initWidget(element, widget); |
| IEventBroker broker = this._context.get(IEventBroker.class); |
| if (broker != null) { |
| initDefaultEventListeners(broker); |
| } else { |
| this.logger.error("No event broker was found. Most things will not operate appropiately!"); //$NON-NLS-1$ |
| } |
| |
| element.getTransientData().put(CALCULATED_VISIBILITY, Boolean.valueOf(element.isVisible() && checkVisibleWhen(element, getModelContext(element)))); |
| |
| if (element.getVisibleWhen() != null) { |
| ActiveLeafRunAndTrack rat = new ActiveLeafRunAndTrack(element, this._context.get(IEventBroker.class)); |
| this.visibleWhenElements.put(element, rat); |
| |
| IEclipseContext modelContext = getModelContext(element); |
| if (modelContext != null) { |
| modelContext.runAndTrack(rat); |
| } |
| } |
| |
| return widget; |
| } |
| |
| /** |
| * Run code without informing the UI about updates |
| * |
| * @param element |
| * the element the code is executed on |
| * @param codeBlock |
| * the code to run |
| */ |
| public void syncUIModifications(@NonNull MUIElement element, @NonNull Runnable codeBlock) { |
| if (inUIModification(element)) { |
| codeBlock.run(); |
| return; |
| } |
| |
| try { |
| BaseRenderer.this.uiModification.put(element, Boolean.TRUE); |
| codeBlock.run(); |
| } finally { |
| BaseRenderer.this.uiModification.remove(element); |
| } |
| } |
| |
| private void propertyObjectChanged(@NonNull M element, @NonNull WPropertyChangeEvent<W> event) { |
| // There is already a modification in process |
| if (inUIModification(element) || inContextModification(element)) { |
| return; |
| } |
| |
| try { |
| BaseRenderer.this.uiModification.put(element, Boolean.TRUE); |
| |
| EAttribute attribute = BaseRenderer.this.attributeMap.get(event.propertyname); |
| EObject eo = (EObject) element; |
| |
| if (attribute == null) { |
| EStructuralFeature f = eo.eClass().getEStructuralFeature(event.propertyname); |
| if (f instanceof EAttribute) { |
| attribute = (EAttribute) f; |
| BaseRenderer.this.attributeMap.put(event.propertyname, attribute); |
| } |
| } |
| |
| if (attribute != null) { |
| if (attribute.getEType().getInstanceClass() == int.class) { |
| Object v = event.newValue; |
| if (v == null) { |
| eo.eSet(attribute, Integer.valueOf(0)); |
| } else { |
| eo.eSet(attribute, Integer.valueOf(((Number) v).intValue())); |
| } |
| } else { |
| eo.eSet(attribute, event.newValue); |
| } |
| } |
| } finally { |
| BaseRenderer.this.uiModification.remove(element); |
| } |
| } |
| |
| @SuppressWarnings("null") |
| private void initDefaultEventListeners(@NonNull IEventBroker broker) { |
| registerEventListener(broker, UIEvents.ApplicationElement.TOPIC_PERSISTEDSTATE); |
| registerEventListener(broker, UIEvents.ApplicationElement.TOPIC_TAGS); |
| registerEventListener(broker, UIEvents.UIElement.TOPIC_CONTAINERDATA); |
| |
| broker.subscribe(UIEvents.UIElement.TOPIC_VISIBLEWHEN, this::handleVisibleWhen); |
| } |
| |
| private void handleVisibleWhen(Event event) { |
| Object element = event.getProperty(UIEvents.EventTags.ELEMENT); |
| |
| if (element instanceof MUIElement) { |
| MUIElement e = (MUIElement) element; |
| |
| if (e.getRenderer() == this) { |
| if (e.getVisibleWhen() == null) { |
| this.visibleWhenElements.remove(e); |
| } else { |
| this.visibleWhenElements.put(e, new ActiveLeafRunAndTrack(e, this._context.get(IEventBroker.class))); |
| } |
| } |
| |
| IEventBroker broker = this._context.get(IEventBroker.class); |
| broker.send(Constants.UPDATE_VISIBLE_WHEN_RESULT, e); |
| } |
| } |
| |
| /** |
| * Check if this renderer is the one responsible for this class |
| * |
| * @param element |
| * the element |
| * @return <code>true</code> if renderer is the one associated with this |
| * element |
| */ |
| protected boolean isRenderer(@NonNull MUIElement element) { |
| if (element.getRenderer() != null) { |
| return element.getRenderer() == this; |
| } |
| @Nullable |
| IEclipseContext renderingContext = getRenderingContext(element); |
| if (renderingContext != null) { |
| return renderingContext.get(RENDER_KEY) == this; |
| } |
| return false; |
| } |
| |
| @Override |
| public final IEclipseContext setupRenderingContext(@NonNull M element) { |
| IEclipseContext context = (IEclipseContext) element.getTransientData().get(RENDERING_CONTEXT_KEY); |
| if (context == null) { |
| context = this._context.createChild("Element RenderingContext"); //$NON-NLS-1$ |
| context.set(RENDER_KEY, this); |
| element.getTransientData().put(RENDERING_CONTEXT_KEY, context); |
| context.set(CONTEXT_DOM_ELEMENT, element); |
| initRenderingContext(element, context); |
| |
| try { |
| this.contextModification.put(element, Boolean.TRUE); |
| EObject eo; |
| if (element instanceof MPlaceholder) { |
| eo = (EObject) ((MPlaceholder) element).getRef(); |
| } else { |
| eo = (EObject) element; |
| } |
| |
| if (eo != null) { |
| initContext(eo, context); |
| if (element instanceof MPlaceholder) { |
| initContext((EObject) element, context); |
| } |
| } else { |
| throw new IllegalStateException("The placeholder reference of '" + element + "' is null"); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| |
| } finally { |
| this.contextModification.remove(element); |
| } |
| } |
| return context; |
| } |
| |
| /** |
| * Initialize the context |
| * |
| * @param eo |
| * the object the context should be populated with |
| * @param context |
| * the context |
| */ |
| @SuppressWarnings("static-method") |
| protected void initContext(@NonNull EObject eo, @NonNull IEclipseContext context) { |
| for (EAttribute e : eo.eClass().getEAllAttributes()) { |
| context.set(e.getName(), eo.eGet(e)); |
| } |
| |
| if (eo instanceof MApplicationElement) { |
| for (Entry<String, String> e : ((MApplicationElement) eo).getPersistedState().entrySet()) { |
| context.set(UIEvents.ApplicationElement.PERSISTEDSTATE + "_" //$NON-NLS-1$ |
| + e.getKey(), e.getValue()); |
| } |
| } |
| |
| // // Localized Label/Tooltip treatment |
| // if (eo instanceof MUILabel) { |
| // MUILabel l = (MUILabel) eo; |
| // context.set(ATTRIBUTE_localizedLabel, l.getLocalizedLabel()); |
| // context.set(ATTRIBUTE_localizedTooltip, l.getLocalizedTooltip()); |
| // } |
| } |
| |
| /** |
| * Register an event listener for the give topic and translate it into |
| * context informations |
| * |
| * @param broker |
| * the event broker |
| * @param topic |
| * the topic |
| */ |
| protected void registerEventListener(@NonNull IEventBroker broker, @NonNull String topic) { |
| broker.subscribe(topic, this::handleEvent); |
| } |
| |
| @SuppressWarnings("null") |
| void handleEvent(Event event) { |
| // System.err.println("EVENT: " + this + " ===> " + event); |
| Object changedObj = event.getProperty(UIEvents.EventTags.ELEMENT); |
| if (!(changedObj instanceof MUIElement)) { |
| return; |
| } |
| |
| Object newValue = event.getProperty(UIEvents.EventTags.NEW_VALUE); |
| String attributeName = event.getProperty(UIEvents.EventTags.ATTNAME).toString(); |
| |
| // for now only process set and tag events |
| // TODO Should we skip none attribute changes??? |
| if (!UIEvents.isSET(event) && !UIEvents.ApplicationElement.TAGS.equals(attributeName)) { |
| return; |
| } |
| |
| MUIElement e = (MUIElement) changedObj; |
| // There is already a modification in process |
| if (inContextModification(e)) { |
| return; |
| } |
| |
| try { |
| BaseRenderer.this.contextModification.put(e, Boolean.TRUE); |
| if (changedObj instanceof MUIElement) { |
| if (isRenderer(e)) { |
| if (attributeName.equals(UIEvents.ApplicationElement.TAGS)) { |
| MUIElement m = (MUIElement) changedObj; |
| if (m.getWidget() != null) { |
| if (UIEvents.isADD(event)) { |
| Collection<String> addedTags = Util.<String> asCollection(event, UIEvents.EventTags.NEW_VALUE); |
| ((WWidget<?>) m.getWidget()).addStyleClasses(this.modelStylingService.getStylesFromTags(new ArrayList<String>(addedTags))); |
| } else if (UIEvents.isREMOVE(event)) { |
| Collection<String> removedTags = Util.<String> asCollection(event, UIEvents.EventTags.OLD_VALUE); |
| ((WWidget<?>) m.getWidget()).removeStyleClasses(this.modelStylingService.getStylesFromTags(new ArrayList<String>(removedTags))); |
| } |
| } |
| } |
| |
| IEclipseContext ctx = (IEclipseContext) e.getTransientData().get(RENDERING_CONTEXT_KEY); |
| if (ctx != null) { |
| if (attributeName.equals(UIEvents.ApplicationElement.PERSISTEDSTATE) && newValue instanceof Entry) { |
| @SuppressWarnings("unchecked") |
| Entry<String, String> entry = (Entry<String, String>) newValue; |
| ctx.set(attributeName + "_" //$NON-NLS-1$ |
| + entry.getKey(), entry.getValue()); |
| } else if (attributeName.equals(UIEvents.ApplicationElement.TAGS)) { |
| ctx.set(ApplicationElement.TAGS, new ArrayList<>(e.getTags())); |
| } else { |
| ctx.set(attributeName, newValue); |
| } |
| handleAttributeChange(e, ctx, attributeName, newValue); |
| } |
| } |
| } |
| } finally { |
| BaseRenderer.this.contextModification.remove(e); |
| } |
| } |
| |
| /** |
| * Handle the change of an attribute e.g. to publish the localized string as |
| * well when the label value changes |
| * |
| * @param e |
| * the ui element |
| * @param context |
| * the context |
| * @param attributeName |
| * the attribute name |
| * @param newValue |
| * the new value |
| */ |
| @SuppressWarnings("static-method") |
| protected void handleAttributeChange(MUIElement e, IEclipseContext context, String attributeName, Object newValue) { |
| if (e instanceof MUILabel) { |
| MUILabel l = (MUILabel) e; |
| if (UIEvents.UILabel.LABEL.equals(attributeName)) { |
| context.set(UIEvents.UILabel.LOCALIZED_LABEL, l.getLocalizedLabel()); |
| } else if (UIEvents.UILabel.TOOLTIP.equals(attributeName)) { |
| context.set(UIEvents.UILabel.LOCALIZED_TOOLTIP, l.getLocalizedTooltip()); |
| } |
| } |
| } |
| |
| /** |
| * Initialize the rendering context |
| * |
| * @param element |
| * the element |
| * @param context |
| * the context |
| */ |
| protected void initRenderingContext(@NonNull M element, @NonNull IEclipseContext context) { |
| // nothing todo |
| } |
| |
| /** |
| * Initialize the widget |
| * |
| * @param element |
| * the model element |
| * @param widget |
| * the widget |
| */ |
| protected void initWidget(@NonNull M element, @NonNull W widget) { |
| // nothing todo |
| } |
| |
| @SuppressWarnings({ "unchecked", "all" }) |
| @Override |
| public void destroyWidget(@NonNull M element) { |
| if (element.getTransientData().containsKey(RENDERING_CONTEXT_KEY)) { |
| if (element.getWidget() instanceof WWidget<?>) { |
| ((WWidget<?>) element.getWidget()).setWidgetState(WidgetState.DISPOSED); |
| } |
| unbindWidget(element, (W) element.getWidget()); |
| |
| IEclipseContext ctx = (IEclipseContext) element.getTransientData().get(RENDERING_CONTEXT_KEY); |
| ctx.dispose(); |
| element.getTransientData().remove(RENDERING_CONTEXT_KEY); |
| } |
| |
| if (element.getVisibleWhen() != null) { |
| this.visibleWhenElements.remove(element); |
| } |
| } |
| |
| private void unbindWidget(@NonNull M me, @NonNull W widget) { |
| widget.setDomElement(null); |
| me.setWidget(null); |
| } |
| |
| @SuppressWarnings("null") |
| @Override |
| public void bindWidget(@NonNull M me, @NonNull W widget) { |
| widget.setDomElement(me); |
| widget.addStyleClasses(this.modelStylingService.getStyles(me)); |
| |
| EObject eo = (EObject) me; |
| widget.addStyleClasses("M" + eo.eClass().getName()); //$NON-NLS-1$ |
| |
| for (EClass e : eo.eClass().getEAllSuperTypes()) { |
| widget.addStyleClasses("M" + e.getName()); //$NON-NLS-1$ |
| } |
| |
| String elementId = me.getElementId(); |
| if (elementId != null) { |
| widget.setStyleId(Util.toCSSId(elementId)); |
| } |
| me.setWidget(widget); |
| } |
| |
| @SuppressWarnings("all") |
| @Override |
| public void postProcess(@NonNull M element) { |
| if (element.getWidget() instanceof WWidget<?>) { |
| ((WWidget<?>) element.getWidget()).setWidgetState(WidgetState.CREATED); |
| } |
| } |
| |
| @SuppressWarnings("all") |
| @Override |
| public void preDestroy(@NonNull M element) { |
| if (element.getWidget() instanceof WWidget<?>) { |
| ((WWidget<?>) element.getWidget()).setWidgetState(WidgetState.IN_TEAR_DOWN); |
| } |
| } |
| |
| /** |
| * @return get the presentation engine |
| */ |
| @NonNull |
| protected IPresentationEngine getPresentationEngine() { |
| IPresentationEngine p = this._context.get(IPresentationEngine.class); |
| if (p == null) { |
| throw new IllegalStateException("IPresentationEngine not available"); //$NON-NLS-1$ |
| } |
| return p; |
| } |
| |
| /** |
| * Get the widgets class |
| * |
| * @param element |
| * the widget class |
| * @return the widget class |
| */ |
| @NonNull |
| protected abstract Class<@NonNull ? extends W> getWidgetClass(@NonNull M element); |
| |
| /** |
| * Create a widget for the model element through the |
| * {@link IPresentationEngine#createGui(MUIElement)} |
| * |
| * @param pm |
| * the model element |
| * @param <LW> |
| * the widget type |
| * @param <PM> |
| * the model type |
| * @return the widget |
| */ |
| @SuppressWarnings("unchecked") |
| @Nullable |
| protected <LW extends WWidget<PM>, PM extends MUIElement> LW engineCreateWidget(@NonNull PM pm) { |
| return (LW) getPresentationEngine().createGui(pm); |
| } |
| |
| /** |
| * Create a widget for the model element through |
| * {@link IPresentationEngine#createGui(MUIElement, Object, IEclipseContext)} |
| * |
| * @param pm |
| * the model element |
| * @param context |
| * the context |
| * @param <LW> |
| * the widget type |
| * @param <PM> |
| * the model type |
| * @return the widget |
| */ |
| @SuppressWarnings("unchecked") |
| @Nullable |
| protected <LW extends WWidget<PM>, PM extends MUIElement> LW engineCreateWidget(@NonNull PM pm, @NonNull IEclipseContext context) { |
| return (LW) getPresentationEngine().createGui(pm, null, context); |
| } |
| |
| /** |
| * Get the rendering context of the element |
| * |
| * @param element |
| * the element |
| * @return the context |
| */ |
| @SuppressWarnings("static-method") |
| @Nullable |
| protected IEclipseContext getRenderingContext(@NonNull MUIElement element) { |
| return (IEclipseContext) element.getTransientData().get(RENDERING_CONTEXT_KEY); |
| } |
| |
| /** |
| * Get the context for the parent using |
| * {@link EModelService#getContainingContext(MUIElement)} |
| * |
| * @param element |
| * the element |
| * @return the context |
| */ |
| @Nullable |
| protected IEclipseContext getContextForParent(@NonNull MUIElement element) { |
| return this.modelService.getContainingContext(element); |
| } |
| |
| @Override |
| @Nullable |
| public IEclipseContext getModelContext(@NonNull MUIElement element) { |
| if (element instanceof MContext) { |
| return ((MContext) element).getContext(); |
| } |
| return getContextForParent(element); |
| } |
| |
| /** |
| * Activate the given part using |
| * {@link EPartService#activate(MPart, boolean)} |
| * |
| * @param element |
| * the element |
| * @param requiresFocus |
| * true of focus is required |
| */ |
| protected void activate(@NonNull MPart element, boolean requiresFocus) { |
| this.logger.debug("Activating " + element); //$NON-NLS-1$ |
| IEclipseContext curContext = getModelContext(element); |
| if (curContext != null) { |
| printContextHierarchy(this.logger, curContext); |
| EPartService ps = (EPartService) curContext.get(EPartService.class.getName()); |
| this.logger.debug("activating with: " + ps); //$NON-NLS-1$ |
| this.logger.debug("The window: " + curContext.get(MWindow.class)); //$NON-NLS-1$ |
| if (ps != null) |
| ps.activate(element, requiresFocus); |
| } |
| } |
| |
| private static final void printContextHierarchy(Logger logger, IEclipseContext c) { |
| if (logger.isEnabled(Level.DEBUG)) { |
| IEclipseContext context = c; |
| logger.debug("=== Context ==="); //$NON-NLS-1$ |
| do { |
| logger.debug(context + ""); //$NON-NLS-1$ |
| } while ((context = context.getParent()) != null); |
| } |
| } |
| |
| @Override |
| public final void processContent(@NonNull M element) { |
| try { |
| this.contentProcessing.put(element, Boolean.TRUE); |
| doProcessContent(element); |
| } finally { |
| this.contentProcessing.remove(element); |
| } |
| } |
| |
| /** |
| * Check the visible when expression |
| * |
| * @param item |
| * the item |
| * @param context |
| * the context |
| * @return <code>true</code> if visible |
| */ |
| public static boolean checkVisibleWhen(MUIElement item, IEclipseContext context) { |
| if (item.getVisibleWhen() != null && item.getVisibleWhen() instanceof MCoreExpression) { |
| ExpressionContext exprContext = new ExpressionContext(context.getActiveLeaf()); |
| boolean value = ContributionsAnalyzer.isVisible((MCoreExpression) item.getVisibleWhen(), exprContext); |
| item.getTransientData().put(CURRENT_VISIBLE_WHEN, Boolean.valueOf(value)); |
| return value; |
| } |
| |
| return true; |
| } |
| |
| /** |
| * Get the last calculated visible when state or calculates one |
| * |
| * @param item |
| * the item |
| * @param context |
| * the context |
| * @return the current visible when status |
| */ |
| public static boolean getVisibleWhen(MUIElement item, IEclipseContext context) { |
| if (item.getVisibleWhen() != null) { |
| if (item.getTransientData().get(CURRENT_VISIBLE_WHEN) != null) { |
| return ((Boolean) item.getTransientData().get(CURRENT_VISIBLE_WHEN)).booleanValue(); |
| } |
| return checkVisibleWhen(item, context); |
| } |
| return true; |
| } |
| |
| /** |
| * Get the rendering index of the element |
| * |
| * @param parent |
| * the parent |
| * @param element |
| * the element |
| * @return the index or <code>-1</code> |
| */ |
| protected final int getRenderedIndex(@NonNull MUIElement parent, @NonNull MUIElement element) { |
| EObject eElement = (EObject) element; |
| |
| EObject container = eElement.eContainer(); |
| @SuppressWarnings("unchecked") |
| List<MUIElement> list = (List<MUIElement>) container.eGet(eElement.eContainmentFeature()); |
| int idx = 0; |
| for (MUIElement u : list) { |
| if (u == null) { |
| getLogger().error("Found a null element in " + list); //$NON-NLS-1$ |
| continue; |
| } |
| if (isChildRenderedAndVisible(u)) { |
| if (u == element) { |
| return idx; |
| } |
| idx++; |
| } |
| } |
| return -1; |
| } |
| |
| /** |
| * Check if the item is visible and to be rendered |
| * |
| * @param u |
| * the element |
| * @return <code>true</code> if item is to be shown |
| */ |
| @Override |
| public boolean isChildRenderedAndVisible(@NonNull MUIElement u) { |
| return u.isToBeRendered() && u.isVisible() && getVisibleWhen(u, getModelContext(u)); |
| } |
| |
| /** |
| * Process the content of an element |
| * |
| * @param element |
| * the element |
| */ |
| protected abstract void doProcessContent(@NonNull M element); |
| |
| /** |
| * Check that the selected element is a valid one or if not set the |
| * selection to the first item in the container or <code>null</code> |
| * |
| * @param element |
| * the element |
| */ |
| @SuppressWarnings("null") |
| protected void checkSelectedElement(MUIElement element) { |
| if (element instanceof MElementContainer<?>) { |
| @SuppressWarnings("unchecked") |
| MElementContainer<MUIElement> parent = (MElementContainer<MUIElement>) element; |
| if (parent.getSelectedElement() != null) { |
| if (parent.getChildren().isEmpty()) { |
| parent.setSelectedElement(null); |
| } else { |
| Optional<MUIElement> first = parent.getChildren().stream().filter(c -> c == parent.getSelectedElement() && isChildRenderedAndVisible(c)).findFirst(); |
| if (!first.isPresent()) { |
| first = parent.getChildren().stream().filter(c -> isChildRenderedAndVisible(c)).findFirst(); |
| if (first.isPresent()) { |
| parent.setSelectedElement(first.get()); |
| } else { |
| parent.setSelectedElement(null); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * Fix the context hierarchy |
| * |
| * @param elements |
| * the elements |
| */ |
| protected void fixContextHierarchy(@NonNull Collection<@NonNull ? extends MUIElement> elements) { |
| elements.stream().forEach(this::fixContextHierarchy); |
| } |
| |
| /** |
| * Fix the context hierarchy for given element |
| * |
| * @param element |
| * the element |
| */ |
| protected void fixContextHierarchy(@NonNull MUIElement element) { |
| MUIElement tmp = element; |
| if (!tmp.isToBeRendered()) { |
| return; |
| } |
| |
| if (tmp instanceof MPlaceholder && tmp.getWidget() != null) { |
| MPlaceholder ph = (MPlaceholder) tmp; |
| MUIElement ref = ph.getRef(); |
| |
| if (ref.getCurSharedRef() != ph) { |
| ref.setCurSharedRef(ph); |
| WPlaceholderWidget placeholder = (WPlaceholderWidget) ph.getWidget(); |
| @SuppressWarnings("unchecked") |
| WLayoutedWidget<MUIElement> content = (WLayoutedWidget<MUIElement>) ref.getWidget(); |
| placeholder.setContent(content); |
| } |
| |
| tmp = ref; |
| } |
| |
| if (tmp instanceof MContext) { |
| IEclipseContext context = ((MContext) tmp).getContext(); |
| if (context != null) { |
| IEclipseContext newParentContext = this.modelService.getContainingContext(tmp); |
| if (context.getParent() != newParentContext) { |
| Util.setParentContext(context, newParentContext); |
| } |
| } |
| } |
| |
| // Currently not supported in the model but will very likely be in |
| // future |
| if (tmp instanceof MElementContainer<?>) { |
| MElementContainer<?> container = (MElementContainer<?>) tmp; |
| List<MUIElement> kids = new ArrayList<MUIElement>(container.getChildren()); |
| for (MUIElement childElement : kids) { |
| fixContextHierarchy(childElement); |
| } |
| } |
| } |
| |
| @Override |
| public void focus(@NonNull MUIElement element) { |
| if (element.getWidget() instanceof WWidget) { |
| WWidget<?> widget = (WWidget<?>) element.getWidget(); |
| widget.activate(); |
| } |
| |
| } |
| |
| @SuppressWarnings("unchecked") |
| @Override |
| public W getWidget(@NonNull M element) { |
| return (W) element.getWidget(); |
| } |
| |
| // /** |
| // * Update all visible when elements |
| // */ |
| // protected void updateVisibleWhen() { |
| // for( MUIElement e : this.visibleWhenElements.keySet() ) { |
| // getModelContext(e); |
| // } |
| // |
| // } |
| |
| class ActiveLeafRunAndTrack extends RunAndTrack { |
| RunAndTrack currentActiveLeafRAT; |
| |
| final MUIElement element; |
| final HashSet<String> variables; |
| final IEventBroker broker; |
| |
| public ActiveLeafRunAndTrack(MUIElement element, IEventBroker broker) { |
| this.element = element; |
| this.broker = broker; |
| ExpressionInfo info = new ExpressionInfo(); |
| ContributionsAnalyzer.collectInfo(info, element.getVisibleWhen()); |
| this.variables = new HashSet<String>(Arrays.asList(info.getAccessedVariableNames())); |
| } |
| |
| @Override |
| public boolean changed(IEclipseContext context) { |
| if (BaseRenderer.this.visibleWhenElements.get(this.element) == this) { |
| this.currentActiveLeafRAT = new RunAndTrack() { |
| |
| @Override |
| public boolean changed(IEclipseContext context) { |
| if (ActiveLeafRunAndTrack.this.currentActiveLeafRAT == this) { |
| for (String v : ActiveLeafRunAndTrack.this.variables) { |
| context.get(v); |
| } |
| ActiveLeafRunAndTrack.this.broker.send(Constants.UPDATE_VISIBLE_WHEN_RESULT, ActiveLeafRunAndTrack.this.element); |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| }; |
| context.getActiveLeaf().runAndTrack(this.currentActiveLeafRAT); |
| return true; |
| } else { |
| this.currentActiveLeafRAT = null; |
| return false; |
| } |
| } |
| } |
| |
| /** |
| * Populate the context with all interfaces from this model element type |
| * |
| * @param element |
| * the element |
| * @param context |
| * the context |
| * @param clazz |
| * the root interface type |
| */ |
| public static <M extends MApplicationElement> void populateModelInterfaces(M element, IEclipseContext context, Class<M> clazz) { |
| populateModelInterfaces(element, context, clazz.getInterfaces()); |
| } |
| |
| private static void populateModelInterfaces(MApplicationElement element, IEclipseContext context, Class<?>[] interfaces) { |
| for (Class<?> intf : interfaces) { |
| context.set(intf.getName(), element); |
| |
| populateModelInterfaces(element, context, intf.getInterfaces()); |
| } |
| } |
| } |