Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChristian W. Damus2015-03-31 19:38:13 +0000
committerChristian W. Damus2015-03-31 19:39:32 +0000
commit703b3a53f2150d1e879ff3ca53fd88f608831a1a (patch)
tree0f8bd68b8a1b406b7f5cbe34950f8dd996e8e35a /plugins
parenta34f04eaef02b4b0f9330780ebc7d069dd027d61 (diff)
downloadorg.eclipse.papyrus-703b3a53f2150d1e879ff3ca53fd88f608831a1a.tar.gz
org.eclipse.papyrus-703b3a53f2150d1e879ff3ca53fd88f608831a1a.tar.xz
org.eclipse.papyrus-703b3a53f2150d1e879ff3ca53fd88f608831a1a.zip
Bug 433206: Papyrus shall enable local synchronization between graphical element and element in the model
https://bugs.eclipse.org/bugs/show_bug.cgi?id=433206 Improve the layout of canonical views by deferring the arrangement of new views until all views created by canonical refresh have been created, so that they may all be laid out together and thus more effectively constrain the overall layout.
Diffstat (limited to 'plugins')
-rw-r--r--plugins/infra/gmfdiag/org.eclipse.papyrus.infra.gmfdiag.canonical/src/org/eclipse/papyrus/infra/gmfdiag/canonical/editpolicy/PapyrusCanonicalEditPolicy.java33
-rw-r--r--plugins/infra/gmfdiag/org.eclipse.papyrus.infra.gmfdiag.common/src/org/eclipse/papyrus/infra/gmfdiag/common/commands/requests/RollingDeferredArrangeRequest.java394
-rw-r--r--plugins/infra/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/util/Iterables2.java66
3 files changed, 477 insertions, 16 deletions
diff --git a/plugins/infra/gmfdiag/org.eclipse.papyrus.infra.gmfdiag.canonical/src/org/eclipse/papyrus/infra/gmfdiag/canonical/editpolicy/PapyrusCanonicalEditPolicy.java b/plugins/infra/gmfdiag/org.eclipse.papyrus.infra.gmfdiag.canonical/src/org/eclipse/papyrus/infra/gmfdiag/canonical/editpolicy/PapyrusCanonicalEditPolicy.java
index 43a3a51f3a6..924099e0c67 100644
--- a/plugins/infra/gmfdiag/org.eclipse.papyrus.infra.gmfdiag.canonical/src/org/eclipse/papyrus/infra/gmfdiag/canonical/editpolicy/PapyrusCanonicalEditPolicy.java
+++ b/plugins/infra/gmfdiag/org.eclipse.papyrus.infra.gmfdiag.canonical/src/org/eclipse/papyrus/infra/gmfdiag/canonical/editpolicy/PapyrusCanonicalEditPolicy.java
@@ -31,8 +31,6 @@ import org.eclipse.gef.commands.CompoundCommand;
import org.eclipse.gef.requests.CreateRequest;
import org.eclipse.gmf.runtime.common.core.command.CommandResult;
import org.eclipse.gmf.runtime.common.core.command.ICommand;
-import org.eclipse.gmf.runtime.diagram.ui.commands.DeferredLayoutCommand;
-import org.eclipse.gmf.runtime.diagram.ui.commands.ICommandProxy;
import org.eclipse.gmf.runtime.diagram.ui.editparts.IBorderItemEditPart;
import org.eclipse.gmf.runtime.diagram.ui.editparts.IGraphicalEditPart;
import org.eclipse.gmf.runtime.diagram.ui.editparts.IResizableCompartmentEditPart;
@@ -57,6 +55,7 @@ import org.eclipse.papyrus.infra.gmfdiag.canonical.strategy.ISemanticChildrenStr
import org.eclipse.papyrus.infra.gmfdiag.canonical.strategy.IVisualChildrenStrategy;
import org.eclipse.papyrus.infra.gmfdiag.canonical.strategy.SemanticChildrenStrategyRegistry;
import org.eclipse.papyrus.infra.gmfdiag.common.commands.requests.CanonicalDropObjectsRequest;
+import org.eclipse.papyrus.infra.gmfdiag.common.commands.requests.RollingDeferredArrangeRequest;
import org.eclipse.papyrus.infra.gmfdiag.common.editpolicies.IPapyrusCanonicalEditPolicy;
import org.eclipse.papyrus.infra.gmfdiag.common.helper.DiagramHelper;
@@ -84,6 +83,19 @@ public class PapyrusCanonicalEditPolicy extends CanonicalEditPolicy implements I
private static final Set<View> createdByCanonical = Sets.newSetFromMap(new WeakHashMap<View, Boolean>());
+ private final RollingDeferredArrangeRequest.IArrangementContext arrangeContext = new RollingDeferredArrangeRequest.IArrangementContext() {
+
+ @Override
+ public EditPart getHost() {
+ return PapyrusCanonicalEditPolicy.this.getHost();
+ }
+
+ @Override
+ public void execute(Command command) {
+ executeCommand(command);
+ }
+ };
+
private ISemanticChildrenStrategy semanticChildrenStrategy = null;
private ICreationTargetStrategy creationTargetStrategy;
@@ -687,16 +699,7 @@ public class PapyrusCanonicalEditPolicy extends CanonicalEditPolicy implements I
@Override
protected void complete(List<IAdaptable> accumulator) {
if (!accumulator.isEmpty()) {
- final DeferredLayoutCommand layout = new DeferredLayoutCommand(host().getEditingDomain(), accumulator, host());
- if (layout.canExecute()) {
- DiagramHelper.asyncExec(getHost(), new Runnable() {
-
- @Override
- public void run() {
- executeCommand(new ICommandProxy(layout));
- }
- });
- }
+ RollingDeferredArrangeRequest.post(arrangeContext, accumulator);
}
}
};
@@ -717,9 +720,7 @@ public class PapyrusCanonicalEditPolicy extends CanonicalEditPolicy implements I
//
protected enum State {
- INACTIVE,
- SEMIACTIVE,
- ACTIVE;
+ INACTIVE, SEMIACTIVE, ACTIVE;
boolean validateTransition(State next) {
switch (this) {
@@ -729,7 +730,7 @@ public class PapyrusCanonicalEditPolicy extends CanonicalEditPolicy implements I
case ACTIVE:
return next == SEMIACTIVE;
default:
- throw new IllegalStateException("No such state: " + this); //$NON-NLS-1$
+ throw new IllegalStateException("No such state: " + this); //$NON-NLS-1$
}
}
}
diff --git a/plugins/infra/gmfdiag/org.eclipse.papyrus.infra.gmfdiag.common/src/org/eclipse/papyrus/infra/gmfdiag/common/commands/requests/RollingDeferredArrangeRequest.java b/plugins/infra/gmfdiag/org.eclipse.papyrus.infra.gmfdiag.common/src/org/eclipse/papyrus/infra/gmfdiag/common/commands/requests/RollingDeferredArrangeRequest.java
new file mode 100644
index 00000000000..97806f638a1
--- /dev/null
+++ b/plugins/infra/gmfdiag/org.eclipse.papyrus.infra.gmfdiag.common/src/org/eclipse/papyrus/infra/gmfdiag/common/commands/requests/RollingDeferredArrangeRequest.java
@@ -0,0 +1,394 @@
+/*****************************************************************************
+ * Copyright (c) 2015 CEA LIST 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:
+ * CEA LIST - Initial API and implementation
+ *
+ *****************************************************************************/
+
+package org.eclipse.papyrus.infra.gmfdiag.common.commands.requests;
+
+import static org.eclipse.papyrus.infra.tools.util.Iterables2.topoSort;
+
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.eclipse.core.runtime.IAdaptable;
+import org.eclipse.gef.EditPart;
+import org.eclipse.gef.EditPartViewer;
+import org.eclipse.gef.commands.Command;
+import org.eclipse.gef.commands.CompoundCommand;
+import org.eclipse.gmf.runtime.diagram.ui.requests.ArrangeRequest;
+import org.eclipse.gmf.runtime.diagram.ui.requests.RequestConstants;
+import org.eclipse.gmf.runtime.notation.Connector;
+import org.eclipse.gmf.runtime.notation.Shape;
+import org.eclipse.gmf.runtime.notation.View;
+import org.eclipse.papyrus.infra.gmfdiag.common.Activator;
+import org.eclipse.papyrus.infra.gmfdiag.common.helper.DiagramHelper;
+
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.ListMultimap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.MapMaker;
+
+/**
+ * A deferred layout of a set of views that rolls along in the future accumulating
+ * views to arrange until there are no more to collect, at which point it actually
+ * lays them out. This deferral is important because the eventual optimal layout
+ * is naturally informed by the relationships between the entire set that needs
+ * arranging.
+ */
+public class RollingDeferredArrangeRequest implements Runnable {
+ private static ConcurrentMap<IArrangementContext, RollingDeferredArrangeRequest> deferredArrangements = new MapMaker().weakKeys().weakValues().makeMap();
+
+ private final IArrangementContext context;
+
+ private List<IAdaptable> viewAdapters;
+
+ private volatile boolean cancelled;
+
+ private AtomicBoolean bump = new AtomicBoolean(); // Whether to bump the layout to another deferred/asynchronous execution
+
+ private RollingDeferredArrangeRequest(IArrangementContext context, Iterable<? extends IAdaptable> viewAdapters) {
+ super();
+
+ this.context = context;
+ this.viewAdapters = Lists.newArrayList(viewAdapters);
+ }
+
+ /**
+ * Posts a collection of views (as view adapters) to be arranged later when all views needing
+ * to be arranged are ready.
+ *
+ * @param context
+ * the calling edit-policy context in which the arrangement is requested
+ * @param viewAdapters
+ * the views to arrange, as view adapters
+ * @return the pending deferred arrangement request that will arrange the given views
+ */
+ public static RollingDeferredArrangeRequest post(IArrangementContext context, Iterable<? extends IAdaptable> viewAdapters) {
+ RollingDeferredArrangeRequest result = deferredArrangements.get(context);
+
+ if (result == null) {
+ // Look for opportunities to roll up arrange to a common ancestor edit-part
+ result = rollup(context);
+ }
+
+ if (result != null) {
+ // Just add more to it
+ result.addAll(viewAdapters);
+ } else {
+ // This is the first layout request in this diagram
+ result = new RollingDeferredArrangeRequest(context, viewAdapters);
+
+ // Double-check
+ RollingDeferredArrangeRequest pending = deferredArrangements.putIfAbsent(context, result);
+ if (pending != null) {
+ pending.addAll(viewAdapters);
+ } else {
+ result.schedule();
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Rolls up, if necessary, an edit-part arrangement {@code context} with the others
+ * that are already pending. The result of roll-up is either an existing context that
+ * contains this {@code context}, or the common ancestor of this {@code context} and one
+ * or more others already pending. The resulting roll-up maps to the aggregate of all
+ * of the view-adapter collections corresponding to the contexts that are rolled up into
+ * it.
+ *
+ * @param context
+ * a context to roll up
+ * @return its roll-up super-context
+ */
+ private static RollingDeferredArrangeRequest rollup(IArrangementContext context) {
+ RollingDeferredArrangeRequest result = null;
+
+ // First look for a strict ancestor
+ for (Map.Entry<IArrangementContext, RollingDeferredArrangeRequest> next : deferredArrangements.entrySet()) {
+ if (isAncestor(next.getKey(), context)) {
+ result = next.getValue();
+ break;
+ }
+ }
+
+ if (result == null) {
+ // Find a common ancestor
+ for (Map.Entry<IArrangementContext, RollingDeferredArrangeRequest> next : deferredArrangements.entrySet()) {
+ IArrangementContext commonAncestor = commonAncestor(next.getKey(), context);
+ if (commonAncestor != null) {
+ // Obviously it isn't in the map, otherwise we would have found it in the first loop
+ result = new RollingDeferredArrangeRequest(commonAncestor, next.getValue().viewAdapters);
+ next.getValue().cancel();
+ deferredArrangements.remove(next.getKey());
+ deferredArrangements.put(commonAncestor, result);
+ break;
+ }
+ }
+ }
+
+ return result;
+ }
+
+ private static boolean isAncestor(IArrangementContext putativeAncestor, IArrangementContext context) {
+ return isAncestor(putativeAncestor.getHost(), context.getHost());
+ }
+
+ private static boolean isAncestor(EditPart putativeAncestor, EditPart editPart) {
+ boolean result = false;
+
+ for (EditPart next = editPart; !result && (next != null); next = next.getParent()) {
+ result = next == putativeAncestor;
+ }
+
+ return result;
+ }
+
+ /**
+ * A partial ordering on edit parts that sorts edit parts ahead of those that contain them (directly
+ * or indirectly).
+ *
+ * @return an ancestors-last partial ordering on edit parts
+ */
+ private static Comparator<EditPart> ancestorComparator() {
+ return new Comparator<EditPart>() {
+ @Override
+ public int compare(EditPart o1, EditPart o2) {
+ int result = 0;
+
+ if (isAncestor(o1, o2)) {
+ result = +1;
+ } else if (isAncestor(o2, o1)) {
+ result = -1;
+ }
+
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return (obj != null) && (obj.getClass() == getClass());
+ }
+ };
+ }
+
+ private static IArrangementContext commonAncestor(IArrangementContext context1, IArrangementContext context2) {
+ IArrangementContext result = null;
+ final EditPart ancestor = commonAncestor(context1.getHost(), context2.getHost());
+
+ if (ancestor != null) {
+ final IArrangementContext executor = context1;
+ result = new IArrangementContext() {
+ @Override
+ public EditPart getHost() {
+ return ancestor;
+ }
+
+ @Override
+ public void execute(Command command) {
+ executor.execute(command);
+ }
+ };
+ }
+
+ return result;
+ }
+
+ private static EditPart commonAncestor(EditPart ep1, EditPart ep2) {
+ EditPart result = null;
+
+ if ((ep1 != null) && (ep2 != null)) {
+ if (isAncestor(ep1, ep2)) {
+ result = ep1;
+ } else {
+ result = commonAncestor(ep1.getParent(), ep2.getParent());
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Schedules me to run at "some time in the future", usually either at the end of the active transaction
+ * (if any) or at the end of the display's event queue.
+ */
+ private void schedule() {
+ DiagramHelper.asyncExec(context.getHost(), this);
+ }
+
+ /**
+ * <p>
+ * Performs the deferred arrangement, unless I am bumped (in which case I re-schedule myself for
+ * still later execution) or I am cancelled (in which case I will never perform the layout).
+ * </p>
+ * <p>
+ * <b>Note</b> that this API must <em>not</em> be invoked by clients. It is for internal use only.
+ * </p>
+ */
+ @Override
+ public void run() {
+ if (bump.compareAndSet(true, false)) {
+ schedule();
+ } else if (deferredArrangements.remove(context, this) && !cancelled) {
+ doArrange();
+ }
+ }
+
+ /**
+ * Adds more views to be arranged in my edit-part context. This further defers my execution,
+ * waiting for yet more views to be added to the overall arragement operation.
+ *
+ * @param viewAdapters
+ * views to be arranged, as view adapters
+ * @return myself, for the convenience of call chaining
+ */
+ public RollingDeferredArrangeRequest addAll(Iterable<? extends IAdaptable> viewAdapters) {
+ bump();
+ Iterables.addAll(this.viewAdapters, viewAdapters);
+ return this;
+ }
+
+ /**
+ * Re-defers my execution. If I am pending execution, I shall skip execution when my
+ * turn comes and instead just re-schedule myself.
+ */
+ void bump() {
+ if (!cancelled) {
+ bump.set(true);
+ }
+ }
+
+ /**
+ * Cancels my execution. This is appropriate when I am obsoleted by a {@linkplain #rollup(IArrangementContext) roll-up}
+ * transformation.
+ */
+ void cancel() {
+ cancelled = true;
+ bump.set(false);
+ }
+
+ /**
+ * Computes and executes the layout commands that arrange the views I have accumulated.
+ */
+ protected void doArrange() {
+ // Partition the views to be arranged by their parent edit-parts because each parent
+ // is responsible for arranging its children
+ ListMultimap<EditPart, IAdaptable> toArrange = partition(viewAdapters);
+
+ if (!toArrange.isEmpty()) {
+ CompoundCommand arrange = new CompoundCommand("Arrange Views");
+
+ // Process arrange commands bottom-up in the edit part tree to ensure that
+ // details are arranged before the macro level is arranged
+ for (EditPart targetEditPart : topoSort(toArrange.keySet(), ancestorComparator())) {
+ ArrangeRequest request = new ArrangeRequest(RequestConstants.REQ_ARRANGE_DEFERRED);
+ request.setViewAdaptersToArrange(toArrange.get(targetEditPart));
+ Command command = targetEditPart.getCommand(request);
+ if ((command != null) && command.canExecute()) {
+ arrange.add(command);
+ }
+ }
+
+ if (!arrange.isEmpty()) {
+ context.execute(arrange.unwrap());
+ }
+ }
+ }
+
+ /**
+ * Partitions a collection of view adapters by the edit-parts that are the parents of (containing)
+ * the edit-parts managing the given views. The idea being that these parent edit-parts are the
+ * ones that are responsible for providing the layout commands of their child views.
+ *
+ * @param viewAdapters
+ * views to be arranged, as view adapters
+ * @return the views, partitioned by parent edit-part
+ */
+ private ListMultimap<EditPart, IAdaptable> partition(Iterable<? extends IAdaptable> viewAdapters) {
+ ListMultimap<EditPart, IAdaptable> result = ArrayListMultimap.create();
+ EditPartViewer viewer = context.getHost().getViewer();
+
+ @SuppressWarnings("unchecked")
+ Map<?, ? extends EditPart> registry = viewer.getEditPartRegistry();
+ for (IAdaptable next : viewAdapters) {
+ View view = resolveArrangeableView(next);
+ if (view != null) {
+ EditPart editPart = registry.get(view);
+ if (editPart != null) {
+ EditPart parent = editPart.getParent();
+ if (parent == null) {
+ Activator.log.warn("Attempt to arrange the root edit part: " + editPart); //$NON-NLS-1$
+ } else {
+ result.put(parent, next);
+ }
+ }
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Resolves a view adapter as an arrangeable view. Labels (as for messages in communication diagrams)
+ * are not arrangeable, as such, but the views (shapes and connectors) that they decorate are the views
+ * that should be arranged.
+ *
+ * @param viewAdapter
+ * a view adapter
+ * @return the arrangeable view that it adapts, or {@code null} if no arrangeable view can be resolved
+ */
+ private View resolveArrangeableView(IAdaptable viewAdapter) {
+ View result = null;
+
+ for (View view = viewAdapter.getAdapter(View.class); view != null; view = (View) view.eContainer()) {
+ if ((view instanceof Shape) || (view instanceof Connector)) {
+ result = view;
+ break;
+ }
+ }
+ return result;
+ }
+
+ //
+ // Nested types
+ //
+
+ /**
+ * The context in which an arrangement of views is requested.
+ *
+ * @see RollingDeferredArrangeRequest#post(IArrangementContext, Iterable)
+ */
+ public interface IArrangementContext {
+ /**
+ * The edit-part requesting an arrangement of views, which presumably has children
+ * managing those views (the child edit-parts need not yet exist; that is one reason
+ * why arrangement may be deferred).
+ *
+ * @return the requesting edit-part
+ */
+ EditPart getHost();
+
+ /**
+ * Executes an arrange command in the appropriate context, which usually should be
+ * folded into some other user-triggered operation, but which context is determined
+ * by the edit-part requesting the arrangement.
+ *
+ * @param command
+ * a command to execute
+ */
+ void execute(Command command);
+ }
+}
diff --git a/plugins/infra/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/util/Iterables2.java b/plugins/infra/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/util/Iterables2.java
new file mode 100644
index 00000000000..826a0fbf094
--- /dev/null
+++ b/plugins/infra/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/util/Iterables2.java
@@ -0,0 +1,66 @@
+/*****************************************************************************
+ * Copyright (c) 2015 Christian W. Damus 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:
+ * Christian W. Damus - Initial API and implementation
+ *
+ *****************************************************************************/
+
+package org.eclipse.papyrus.infra.tools.util;
+
+import java.util.Comparator;
+import java.util.List;
+import java.util.ListIterator;
+
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+
+/**
+ * Utilities for working with iterables that are not provided by {@linkplain Iterables Guava}.
+ */
+public class Iterables2 {
+ /**
+ * Not instantiable by clients.
+ */
+ private Iterables2() {
+ super();
+ }
+
+ /**
+ * Brute-force topological sort of objects by a partial ordering relation.
+ *
+ * @param items
+ * the items to be sorted
+ * @param partOrder
+ * a partial ordering relation on the items
+ * @return the topologically sorted {@code items} as a new mutable list
+ */
+ public static <T> List<T> topoSort(Iterable<T> items, Comparator<? super T> partOrder) {
+ List<T> unsorted = Lists.newLinkedList(items);
+ List<T> result = Lists.newArrayListWithCapacity(unsorted.size());
+
+ while (!unsorted.isEmpty()) {
+ T min = unsorted.remove(0);
+
+ for (ListIterator<T> iter = unsorted.listIterator(); iter.hasNext();) {
+ T next = iter.next();
+ if (partOrder.compare(next, min) < 0) {
+ // Found a new minimum. Put the old one back for next pass
+ iter.set(min);
+ min = next;
+ }
+ }
+
+ // Whatever's the minimum now is the next in our partial ordering
+ result.add(min);
+ }
+
+ return result;
+ }
+
+}

Back to the top