X-Git-Url: https://gerrit.simantics.org/r/gitweb?p=simantics%2Fplatform.git;a=blobdiff_plain;f=bundles%2Forg.simantics.browsing.ui.swt%2Fsrc%2Forg%2Fsimantics%2Fbrowsing%2Fui%2Fswt%2FGraphExplorerImpl.java;h=df736c1e6dc2603315bea23f47b46a2170a52f55;hp=0168ad864ae5e98258498d76fef7f04f42ee4282;hb=b7250834202635a09dd18e25370ed9b4c32b1380;hpb=dfd710b04c7d7cdfab7747ba9a650b2cdc650092 diff --git a/bundles/org.simantics.browsing.ui.swt/src/org/simantics/browsing/ui/swt/GraphExplorerImpl.java b/bundles/org.simantics.browsing.ui.swt/src/org/simantics/browsing/ui/swt/GraphExplorerImpl.java index 0168ad864..df736c1e6 100644 --- a/bundles/org.simantics.browsing.ui.swt/src/org/simantics/browsing/ui/swt/GraphExplorerImpl.java +++ b/bundles/org.simantics.browsing.ui.swt/src/org/simantics/browsing/ui/swt/GraphExplorerImpl.java @@ -50,7 +50,6 @@ import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.resource.JFaceResources; import org.eclipse.jface.resource.LocalResourceManager; import org.eclipse.jface.resource.ResourceManager; -import org.eclipse.jface.util.OpenStrategy; import org.eclipse.jface.viewers.IPostSelectionProvider; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.ISelectionChangedListener; @@ -69,6 +68,7 @@ import org.eclipse.swt.events.KeyEvent; import org.eclipse.swt.events.KeyListener; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.events.MouseListener; +import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.events.SelectionListener; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Font; @@ -289,6 +289,24 @@ class GraphExplorerImpl extends GraphExplorerImplBase implements Listener, Graph public static final int DEFAULT_MAX_CHILDREN = 1000; + private static final long POST_SELECTION_DELAY = 300; + + /** + * The time in milliseconds that must elapse between consecutive + * {@link Tree} {@link SelectionListener#widgetSelected(SelectionEvent)} + * invocations in order for this class to construct a new selection. + * + *

+ * This is done because selection construction can be very expensive as the + * selected set grows larger when the user is pressing shift+arrow keys. + * GraphExplorerImpl will naturally listen to all changes in the tree + * selection, but as an optimization will not construct new + * StructuredSelection instances for every selection change event. A new + * selection will be constructed and set only if the selection hasn't + * changed for the amount of milliseconds specified by this constant. + */ + private static final long SELECTION_CHANGE_QUIET_TIME = 150; + private final IThreadWorkQueue thread; /** @@ -310,11 +328,11 @@ class GraphExplorerImpl extends GraphExplorerImplBase implements Listener, Graph Tree tree; @SuppressWarnings({ "rawtypes" }) - final HashMap, NodeQueryProcessor> processors = new HashMap<>(); + final HashMap, NodeQueryProcessor> processors = new HashMap, NodeQueryProcessor>(); @SuppressWarnings({ "rawtypes" }) - final HashMap primitiveProcessors = new HashMap<>(); + final HashMap primitiveProcessors = new HashMap(); @SuppressWarnings({ "rawtypes" }) - final HashMap dataSources = new HashMap<>(); + final HashMap dataSources = new HashMap(); class GraphExplorerContext extends AbstractDisposable implements IGraphExplorerContext { // This is for query debugging only. @@ -333,7 +351,7 @@ class GraphExplorerImpl extends GraphExplorerImplBase implements Listener, Graph * null there's nothing scheduled yet in which case * scheduling can commence. Otherwise the update should be skipped. */ - AtomicReference currentQueryUpdater = new AtomicReference<>(); + AtomicReference currentQueryUpdater = new AtomicReference(); /** * Keeps track of nodes that have already been auto-expanded. After @@ -341,7 +359,7 @@ class GraphExplorerImpl extends GraphExplorerImplBase implements Listener, Graph * expanded state after that. This makes it possible for the user to * close auto-expanded nodes. */ - Map autoExpanded = new WeakHashMap<>(); + Map autoExpanded = new WeakHashMap(); @Override @@ -464,7 +482,7 @@ class GraphExplorerImpl extends GraphExplorerImplBase implements Listener, Graph GraphExplorerContext explorerContext = new GraphExplorerContext(); - HashSet pendingItems = new HashSet<>(); + HashSet pendingItems = new HashSet(); boolean updating = false; boolean pendingRoot = false; @@ -485,14 +503,14 @@ class GraphExplorerImpl extends GraphExplorerImplBase implements Listener, Graph * Access this map only in the SWT thread to keep it thread-safe. *

*/ - BijectionMap contextToItem = new BijectionMap<>(); + BijectionMap contextToItem = new BijectionMap(); /** * Columns of the UI viewer. Use {@link #setColumns(Column[])} to * initialize. */ Column[] columns = new Column[0]; - Map columnKeyToIndex = new HashMap<>(); + Map columnKeyToIndex = new HashMap(); boolean refreshingColumnSizes = false; boolean columnsAreVisible = true; @@ -519,12 +537,12 @@ class GraphExplorerImpl extends GraphExplorerImplBase implements Listener, Graph /** Set to true when the Tree widget is disposed. */ private boolean disposed = false; - private final CopyOnWriteArrayList focusListeners = new CopyOnWriteArrayList<>(); - private final CopyOnWriteArrayList mouseListeners = new CopyOnWriteArrayList<>(); - private final CopyOnWriteArrayList keyListeners = new CopyOnWriteArrayList<>(); + private final CopyOnWriteArrayList focusListeners = new CopyOnWriteArrayList(); + private final CopyOnWriteArrayList mouseListeners = new CopyOnWriteArrayList(); + private final CopyOnWriteArrayList keyListeners = new CopyOnWriteArrayList(); /** Selection provider */ - private GraphExplorerPostSelectionProvider postSelectionProvider = new GraphExplorerPostSelectionProvider(this); + private GraphExplorerPostSelectionProvider postSelectionProvider = new GraphExplorerPostSelectionProvider(this); protected BasePostSelectionProvider selectionProvider = new BasePostSelectionProvider(); protected SelectionDataResolver selectionDataResolver; protected SelectionFilter selectionFilter; @@ -552,12 +570,12 @@ class GraphExplorerImpl extends GraphExplorerImplBase implements Listener, Graph * item was a part of the current selection in which case the selection must * be updated. */ - private final Map selectedItems = new HashMap<>(); + private final Map selectedItems = new HashMap(); /** * TODO: specify what this is for */ - private final Set selectionRefreshContexts = new HashSet<>(); + private final Set selectionRefreshContexts = new HashSet(); /** * If this field is non-null, it means that if {@link #setData(Event)} @@ -631,7 +649,7 @@ class GraphExplorerImpl extends GraphExplorerImplBase implements Listener, Graph * * @see #setPendingImages(IProgressMonitor) */ - Map imageTasks = new THashMap<>(); + Map imageTasks = new THashMap(); /** * A state flag indicating whether the vertical scroll bar was visible for @@ -695,7 +713,7 @@ class GraphExplorerImpl extends GraphExplorerImplBase implements Listener, Graph updateCounter = 0; uiUpdateScheduler.schedule(this, 50, TimeUnit.MILLISECONDS); } else { - tree.getDisplay().asyncExec(new UpdateRunner(GraphExplorerImpl.this)); + tree.getDisplay().asyncExec(new UpdateRunner(GraphExplorerImpl.this, GraphExplorerImpl.this.explorerContext)); } } @@ -770,7 +788,7 @@ class GraphExplorerImpl extends GraphExplorerImplBase implements Listener, Graph * modified. These are used internally to prevent duplicate edits from being * initiated which should always be a sensible thing to do. */ - private Set currentlyModifiedNodes = new THashSet<>(); + private Set currentlyModifiedNodes = new THashSet(); private final TreeEditor editor; private Color invalidModificationColor = null; @@ -1363,14 +1381,17 @@ class GraphExplorerImpl extends GraphExplorerImplBase implements Listener, Graph // Keep track of the previous single selection to help // decide whether to start editing a tree node on mouse // downs or not. - tree.addListener(SWT.Selection, event -> { - TreeItem[] selection = tree.getSelection(); - if (selection.length == 1) { - //for (TreeItem item : selection) - // System.out.println("selection: " + item); - previousSingleSelection = selection[0]; - } else { - previousSingleSelection = null; + tree.addListener(SWT.Selection, new Listener() { + @Override + public void handleEvent(Event event) { + TreeItem[] selection = tree.getSelection(); + if (selection.length == 1) { + //for (TreeItem item : selection) + // System.out.println("selection: " + item); + previousSingleSelection = selection[0]; + } else { + previousSingleSelection = null; + } } }); @@ -1472,33 +1493,39 @@ class GraphExplorerImpl extends GraphExplorerImplBase implements Listener, Graph }; tree.addListener(SWT.MouseDown, mouseEditListener); tree.addListener(SWT.DragDetect, mouseEditListener); - tree.addListener(SWT.DragDetect, event -> { - Point test = new Point(event.x, event.y); - TreeItem item = tree.getItem(test); - if(item != null) { - for(int i=0;i { - Point test = new Point(event.x, event.y); - TreeItem item = tree.getItem(test); - if(item != null) { - for(int i=0;i { - //System.out.println("OPENSTRATEGY: post selection changed: " + e); - resetSelection(); - selectionProvider.firePostSelection(selectionProvider.getSelection()); - })); + // Add a tree selection listener for keeping the selection of + // GraphExplorer's ISelectionProvider up-to-date. + tree.addSelectionListener(new SelectionListener() { + @Override + public void widgetDefaultSelected(SelectionEvent e) { + widgetSelected(e); + } + @Override + public void widgetSelected(SelectionEvent e) { + widgetSelectionChanged(false); + } + }); // This listener takes care of updating the set of currently selected // TreeItem instances. This set is needed because we need to know in @@ -1578,14 +1611,108 @@ class GraphExplorerImpl extends GraphExplorerImplBase implements Listener, Graph }); } + /** + * Mod count for delaying post selection changed events. + */ + int postSelectionModCount = 0; + + /** + * Last tree selection modification time for implementing a quiet + * time for selection changes. + */ + long lastSelectionModTime = System.currentTimeMillis() - 10000; + + /** + * Current target time for the selection to be set. Calculated + * according to the set quiet time and last selection modification + * time. + */ + long selectionSetTargetTime = 0; + + /** + * true if delayed selection runnable is current scheduled or + * running. + */ + boolean delayedSelectionScheduled = false; + + Runnable SELECTION_DELAY = new Runnable() { + @Override + public void run() { + if (tree.isDisposed()) + return; + long now = System.currentTimeMillis(); + long waitTimeLeft = selectionSetTargetTime - now; + if (waitTimeLeft > 0) { + // Not enough quiet time, reschedule. + delayedSelectionScheduled = true; + tree.getDisplay().timerExec((int) waitTimeLeft, this); + } else { + // Time to perform selection, stop rescheduling. + delayedSelectionScheduled = false; + resetSelection(); + } + } + }; + + private void widgetSelectionChanged(boolean forceSelectionChange) { + long modTime = System.currentTimeMillis(); + long delta = modTime - lastSelectionModTime; + lastSelectionModTime = modTime; + if (!forceSelectionChange && delta < SELECTION_CHANGE_QUIET_TIME) { + long msToWait = SELECTION_CHANGE_QUIET_TIME - delta; + selectionSetTargetTime = modTime + msToWait; + if (!delayedSelectionScheduled) { + delayedSelectionScheduled = true; + tree.getDisplay().timerExec((int) msToWait, SELECTION_DELAY); + } + // Make sure that post selection change events do not fire. + ++postSelectionModCount; + return; + } + + // Immediate selection reconstruction. + resetSelection(); + } + private void resetSelection() { final ISelection selection = getWidgetSelection(); -// System.out.println("resetSelection()"); -// System.out.println(" provider selection: " + selectionProvider.getSelection()); -// System.out.println(" widget selection: " + selection); + + //System.out.println("resetSelection(" + postSelectionModCount + ")"); + //System.out.println(" provider selection: " + selectionProvider.getSelection()); + //System.out.println(" widget selection: " + selection); + selectionProvider.setAndFireNonEqualSelection(selection); - } + // Implement deferred firing of post selection events + final int count = ++postSelectionModCount; + //System.out.println("[" + System.currentTimeMillis() + "] scheduling postSelectionChanged " + count + ": " + selection); + ThreadUtils.getNonBlockingWorkExecutor().schedule(new Runnable() { + @Override + public void run() { + int newCount = postSelectionModCount; + // Don't publish selection yet, there's another change incoming. + //System.out.println("[" + System.currentTimeMillis() + "] checking post selection publish: " + count + " vs. " + newCount + ": " + selection); + if (newCount != count) + return; + //System.out.println("[" + System.currentTimeMillis() + "] " + count + " count equals, firing post selection listeners: " + selection); + + if (tree.isDisposed()) + return; + + //System.out.println("scheduling fire post selection changed: " + selection); + tree.getDisplay().asyncExec(new Runnable() { + @Override + public void run() { + if (tree.isDisposed() || selectionProvider == null) + return; + //System.out.println("firing post selection changed: " + selection); + selectionProvider.firePostSelection(selection); + } + }); + } + }, POST_SELECTION_DELAY, TimeUnit.MILLISECONDS); + } + protected void setDefaultProcessors() { // Add a simple IMAGER query processor that always returns null. // With this processor no images will ever be shown. @@ -2300,7 +2427,7 @@ class GraphExplorerImpl extends GraphExplorerImplBase implements Listener, Graph // System.out.println("MODCOUNT: " + modCount + " vs. " + count); if (modCount != count) return; - resetSelection(); + widgetSelectionChanged(true); } }); }