]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.browsing.ui.swt/src/org/simantics/browsing/ui/swt/GraphExplorerImpl.java
Revert "Usability fixes for GraphExplorerImpl -related WB selection propagation"
[simantics/platform.git] / bundles / org.simantics.browsing.ui.swt / src / org / simantics / browsing / ui / swt / GraphExplorerImpl.java
index 0168ad864ae5e98258498d76fef7f04f42ee4282..df736c1e6dc2603315bea23f47b46a2170a52f55 100644 (file)
@@ -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.
+     * 
+     * <p>
+     * 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<CacheKey<?>, NodeQueryProcessor> processors            = new HashMap<>();
+    final HashMap<CacheKey<?>, NodeQueryProcessor> processors            = new HashMap<CacheKey<?>, NodeQueryProcessor>();
     @SuppressWarnings({ "rawtypes" })
-    final HashMap<Object, PrimitiveQueryProcessor> primitiveProcessors   = new HashMap<>();
+    final HashMap<Object, PrimitiveQueryProcessor> primitiveProcessors   = new HashMap<Object, PrimitiveQueryProcessor>();
     @SuppressWarnings({ "rawtypes" })
-    final HashMap<Class, DataSource>               dataSources           = new HashMap<>();
+    final HashMap<Class, DataSource>               dataSources           = new HashMap<Class, DataSource>();
     
     class GraphExplorerContext extends AbstractDisposable implements IGraphExplorerContext {
         // This is for query debugging only.
@@ -333,7 +351,7 @@ class GraphExplorerImpl extends GraphExplorerImplBase implements Listener, Graph
          * <code>null</code> there's nothing scheduled yet in which case
          * scheduling can commence. Otherwise the update should be skipped.
          */
-        AtomicReference<Runnable> currentQueryUpdater = new AtomicReference<>();
+        AtomicReference<Runnable> currentQueryUpdater = new AtomicReference<Runnable>();
 
         /**
          * 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<NodeContext, Boolean>     autoExpanded  = new WeakHashMap<>();
+        Map<NodeContext, Boolean>     autoExpanded  = new WeakHashMap<NodeContext, Boolean>();
 
         
         @Override
@@ -464,7 +482,7 @@ class GraphExplorerImpl extends GraphExplorerImplBase implements Listener, Graph
 
     GraphExplorerContext                         explorerContext     = new GraphExplorerContext();
 
-    HashSet<TreeItem>                            pendingItems        = new HashSet<>();
+    HashSet<TreeItem>                            pendingItems        = new HashSet<TreeItem>();
     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.
      * </p>
      */
-    BijectionMap<NodeContext, TreeItem>         contextToItem     = new BijectionMap<>();
+    BijectionMap<NodeContext, TreeItem>         contextToItem     = new BijectionMap<NodeContext, TreeItem>();
 
     /**
      * Columns of the UI viewer. Use {@link #setColumns(Column[])} to
      * initialize.
      */
     Column[]                                     columns           = new Column[0];
-    Map<String, Integer>                         columnKeyToIndex  = new HashMap<>();
+    Map<String, Integer>                         columnKeyToIndex  = new HashMap<String, Integer>();
     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<FocusListener>  focusListeners     = new CopyOnWriteArrayList<>();
-    private final CopyOnWriteArrayList<MouseListener>  mouseListeners     = new CopyOnWriteArrayList<>();
-    private final CopyOnWriteArrayList<KeyListener>    keyListeners       = new CopyOnWriteArrayList<>();
+    private final CopyOnWriteArrayList<FocusListener>  focusListeners           = new CopyOnWriteArrayList<FocusListener>();
+    private final CopyOnWriteArrayList<MouseListener>  mouseListeners           = new CopyOnWriteArrayList<MouseListener>();
+    private final CopyOnWriteArrayList<KeyListener>    keyListeners             = new CopyOnWriteArrayList<KeyListener>();
 
     /** 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<TreeItem, NodeContext>     selectedItems            = new HashMap<>();
+    private final Map<TreeItem, NodeContext>     selectedItems            = new HashMap<TreeItem, NodeContext>();
 
     /**
      * TODO: specify what this is for
      */
-    private final Set<NodeContext>               selectionRefreshContexts = new HashSet<>();
+    private final Set<NodeContext>               selectionRefreshContexts = new HashSet<NodeContext>();
 
     /**
      * 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<TreeItem, ImageTask> imageTasks     = new THashMap<>();
+    Map<TreeItem, ImageTask> imageTasks     = new THashMap<TreeItem, ImageTask>();
 
     /**
      * 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<NodeContext> currentlyModifiedNodes   = new THashSet<>();
+    private Set<NodeContext> currentlyModifiedNodes   = new THashSet<NodeContext>();
 
     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<tree.getColumnCount();i++) {
-                    Rectangle rect = item.getBounds(i);
-                    if(rect.contains(test)) {
-                        tree.setData(KEY_DRAG_COLUMN, i);
-                        return;
+        tree.addListener(SWT.DragDetect, new Listener() {
+            @Override
+            public void handleEvent(Event event) {
+                Point test = new Point(event.x, event.y);
+                TreeItem item = tree.getItem(test);
+                if(item != null) {
+                    for(int i=0;i<tree.getColumnCount();i++) {
+                        Rectangle rect = item.getBounds(i);
+                        if(rect.contains(test)) {
+                            tree.setData(KEY_DRAG_COLUMN, i);
+                            return;
+                        }
                     }
                 }
+                tree.setData(KEY_DRAG_COLUMN, -1);
             }
-            tree.setData(KEY_DRAG_COLUMN, -1);
         });
-        tree.addListener(SWT.MouseMove, event -> {
-            Point test = new Point(event.x, event.y);
-            TreeItem item = tree.getItem(test);
-            if(item != null) {
-                for(int i=0;i<tree.getColumnCount();i++) {
-                    Rectangle rect = item.getBounds(i);
-                    if(rect.contains(test)) {
-                        transientState.setActiveColumn(i);
-                        return;
+        tree.addListener(SWT.MouseMove, new Listener() {
+            @Override
+            public void handleEvent(Event event) {
+                Point test = new Point(event.x, event.y);
+                TreeItem item = tree.getItem(test);
+                if(item != null) {
+                    for(int i=0;i<tree.getColumnCount();i++) {
+                        Rectangle rect = item.getBounds(i);
+                        if(rect.contains(test)) {
+                               transientState.setActiveColumn(i);
+                            return;
+                        }
                     }
                 }
+               transientState.setActiveColumn(null);
             }
-            transientState.setActiveColumn(null);
         });
 
         // Add focus/mouse/key listeners for supporting the respective
@@ -1551,12 +1578,18 @@ class GraphExplorerImpl extends GraphExplorerImplBase implements Listener, Graph
             }
         });
 
-        OpenStrategy os = new OpenStrategy(tree);
-        os.addPostSelectionListener(SelectionListener.widgetSelectedAdapter(e -> {
-            //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;
+
+    /**
+     * <code>true</code> 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);
                 }
             });
         }