changeset 2339:a5f4d40a0d4f

RT-27467: ScrollPane should allow to scroll node into view (+Generalize scrollTo using event system) Reviewed-by: jgiles Contributed-by: Tom Schindl <tom.schindl@bestsolution.at>
author jgiles
date Tue, 22 Jan 2013 12:21:23 +1300
parents 490b6aa0ba8e
children 2c55d2dc23bf
files javafx-ui-controls/src/com/sun/javafx/scene/control/skin/ScrollPaneSkin.java javafx-ui-controls/src/com/sun/javafx/scene/control/skin/VirtualContainerBase.java javafx-ui-controls/src/javafx/scene/control/ControlUtils.java javafx-ui-controls/src/javafx/scene/control/ListView.java javafx-ui-controls/src/javafx/scene/control/ScrollPane.java javafx-ui-controls/src/javafx/scene/control/ScrollToEvent.java javafx-ui-controls/src/javafx/scene/control/TableView.java javafx-ui-controls/src/javafx/scene/control/TreeTableView.java javafx-ui-controls/src/javafx/scene/control/TreeView.java
diffstat 9 files changed, 316 insertions(+), 43 deletions(-) [+]
line wrap: on
line diff
--- a/javafx-ui-controls/src/com/sun/javafx/scene/control/skin/ScrollPaneSkin.java	Tue Jan 22 12:11:20 2013 +1300
+++ b/javafx-ui-controls/src/com/sun/javafx/scene/control/skin/ScrollPaneSkin.java	Tue Jan 22 12:21:23 2013 +1300
@@ -40,6 +40,7 @@
 import javafx.event.EventDispatchChain;
 import javafx.event.EventDispatcher;
 import javafx.event.EventHandler;
+import javafx.event.EventType;
 import javafx.geometry.BoundingBox;
 import javafx.geometry.Bounds;
 import javafx.geometry.Orientation;
@@ -48,6 +49,7 @@
 import javafx.scene.control.ScrollBar;
 import javafx.scene.control.ScrollPane;
 import javafx.scene.control.ScrollPane.ScrollBarPolicy;
+import javafx.scene.control.ScrollToEvent;
 import javafx.scene.input.MouseEvent;
 import javafx.scene.input.ScrollEvent;
 import javafx.scene.input.TouchEvent;
@@ -123,7 +125,7 @@
      *                                                                         *
      **************************************************************************/
 
-    public ScrollPaneSkin(ScrollPane scrollpane) {
+    public ScrollPaneSkin(final ScrollPane scrollpane) {
         super(scrollpane, new ScrollPaneBehavior(scrollpane));
         initialize();
         // Register listeners
@@ -136,6 +138,15 @@
         registerChangeListener(scrollpane.vvalueProperty(), "VVALUE");
         registerChangeListener(scrollpane.prefViewportWidthProperty(), "PREF_VIEWPORT_WIDTH");
         registerChangeListener(scrollpane.prefViewportHeightProperty(), "PREF_VIEWPORT_HEIGHT");
+        scrollpane.addEventHandler(ScrollToEvent.SCROLL_TO_NODE, new EventHandler<ScrollToEvent<Node>>() {
+
+            @Override
+            public void handle(ScrollToEvent<Node> event) {
+                Node n = event.getScrollTarget();
+                Bounds b = scrollpane.sceneToLocal(n.localToScene(n.getLayoutBounds()));
+                scrollBoundsIntoView(b);
+            }
+        });
     }
 
     private final InvalidationListener nodeListener = new InvalidationListener() {
@@ -614,11 +625,8 @@
             getSkinnable().requestLayout();
         }
     }
-
-    /*
-    ** auto-scroll so node is within (0,0),(contentWidth,contentHeight)
-    */
-    @Override public void onTraverse(Node n, Bounds b) {
+    
+    void scrollBoundsIntoView(Bounds b) {
         double dx = 0.0;
         double dy = 0.0;
         boolean needsLayout = false;
@@ -667,6 +675,13 @@
         }
     }
 
+    /*
+    ** auto-scroll so node is within (0,0),(contentWidth,contentHeight)
+    */
+    @Override public void onTraverse(Node n, Bounds b) {
+        scrollBoundsIntoView(b);
+    }
+
     public void hsbIncrement() {
         if (hsb != null) hsb.increment();
     }
--- a/javafx-ui-controls/src/com/sun/javafx/scene/control/skin/VirtualContainerBase.java	Tue Jan 22 12:11:20 2013 +1300
+++ b/javafx-ui-controls/src/com/sun/javafx/scene/control/skin/VirtualContainerBase.java	Tue Jan 22 12:21:23 2013 +1300
@@ -25,13 +25,13 @@
 
 package com.sun.javafx.scene.control.skin;
 
-import javafx.collections.MapChangeListener;
+import javafx.event.EventHandler;
+import javafx.geometry.Insets;
 import javafx.scene.control.Control;
 import javafx.scene.control.IndexedCell;
+import javafx.scene.control.ScrollToEvent;
 
 import com.sun.javafx.scene.control.behavior.BehaviorBase;
-import java.util.Map;
-import javafx.geometry.Insets;
 
 /**
  * Parent class to control skins whose contents are virtualized and scrollable.
@@ -43,23 +43,17 @@
  */
 public abstract class VirtualContainerBase<C extends Control, B extends BehaviorBase<C>, I extends IndexedCell> extends BehaviorSkinBase<C, B> {
 
-    public static final String SCROLL_TO_INDEX_TOP = "VirtualContainerBase.scrollToIndexTop";
-    public static final String SCROLL_TO_OFFSET = "VirtualContainerBase.scrollToOffset";    
-
     public VirtualContainerBase(final C control, B behavior) {
         super(control, behavior);
+        flow = new VirtualFlow<I>();
         
-        flow = new VirtualFlow<I>();
-        handleControlProperties(control);
+        control.addEventHandler(ScrollToEvent.SCROLL_TO_TOP_INDEX, new EventHandler<ScrollToEvent<Integer>>() {
 
-        control.getProperties().addListener(new MapChangeListener<Object, Object>() {
             @Override
-            public void onChanged(Change<? extends Object, ? extends Object> c) {
-                if (c.wasAdded()) {
-                    handleControlProperties(control);
-                }
+            public void handle(ScrollToEvent<Integer> event) {
+                flow.scrollTo(event.getScrollTarget());
             }
-        });
+        });        
     }
 
     /**
@@ -100,23 +94,4 @@
         return height + padding.getTop() + padding.getBottom();
     }
     
-    private void handleControlProperties(C control) {
-        Map<Object, Object>properties = control.getProperties();
-        if (properties.containsKey(SCROLL_TO_INDEX_TOP)) {
-            Object index = properties.get(SCROLL_TO_INDEX_TOP);
-            if (index instanceof Integer) {
-                // we don't want the index to be centered
-                flow.scrollTo((Integer)index);
-            }
-
-            properties.remove(SCROLL_TO_INDEX_TOP);
-        } else if (properties.containsKey(SCROLL_TO_OFFSET)) {
-            Object offset = properties.get(SCROLL_TO_OFFSET);
-            if (offset instanceof Integer) {
-                flow.scrollToOffset((Integer)offset);
-            }
-
-            properties.remove(SCROLL_TO_OFFSET);
-        }        
-    }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/javafx-ui-controls/src/javafx/scene/control/ControlUtils.java	Tue Jan 22 12:21:23 2013 +1300
@@ -0,0 +1,30 @@
+package javafx.scene.control;
+
+import javafx.beans.InvalidationListener;
+import javafx.beans.Observable;
+import javafx.event.Event;
+import javafx.scene.Node;
+
+class ControlUtils {
+    private static final String CACHE_KEY = "util.scroll.index";
+    public static void scrollToIndex(final Node node, int index) {
+        if( node.getScene() == null ) {
+            if( ! node.getProperties().containsKey(CACHE_KEY) ) {
+                node.sceneProperty().addListener(new InvalidationListener() {
+                    
+                    @Override
+                    public void invalidated(Observable observable) {
+                        Integer idx = (Integer) node.getProperties().remove(CACHE_KEY);
+                        if( idx != null ) {
+                            Event.fireEvent(node, new ScrollToEvent<Integer>(node, node, ScrollToEvent.SCROLL_TO_TOP_INDEX, idx));    
+                        }
+                        node.sceneProperty().removeListener(this);
+                    }
+                });
+            }
+            node.getProperties().put(CACHE_KEY, index);
+        } else {
+            Event.fireEvent(node, new ScrollToEvent<Integer>(node, node, ScrollToEvent.SCROLL_TO_TOP_INDEX, index));  
+        }
+    }
+}
--- a/javafx-ui-controls/src/javafx/scene/control/ListView.java	Tue Jan 22 12:11:20 2013 +1300
+++ b/javafx-ui-controls/src/javafx/scene/control/ListView.java	Tue Jan 22 12:21:23 2013 +1300
@@ -48,6 +48,7 @@
 import javafx.event.EventHandler;
 import javafx.event.EventType;
 import javafx.geometry.Orientation;
+import javafx.scene.Node;
 import javafx.util.Callback;
 
 import javafx.css.StyleableObjectProperty;
@@ -721,7 +722,44 @@
      *      size of the items list contained within the given ListView.
      */
     public void scrollTo(int index) {
-       getProperties().put(VirtualContainerBase.SCROLL_TO_INDEX_TOP, index);
+        ControlUtils.scrollToIndex(this, index);
+    }
+    
+    /**
+     * Called when there's a request to scroll an index into view using {@link #scrollTo(int)}
+     */
+    private ObjectProperty<EventHandler<ScrollToEvent<Integer>>> onScrollTo;
+    
+    public void setOnScrollTo(EventHandler<ScrollToEvent<Integer>> value) {
+        onScrollToProperty().set(value);
+    }
+    
+    public EventHandler<ScrollToEvent<Integer>> getOnScrollTo() {
+        if( onScrollTo != null ) {
+            return onScrollTo.get();
+        }
+        return null;
+    }
+    
+    public ObjectProperty<EventHandler<ScrollToEvent<Integer>>> onScrollToProperty() {
+        if( onScrollTo == null ) {
+            onScrollTo = new ObjectPropertyBase<EventHandler<ScrollToEvent<Integer>>>() {
+                @Override
+                protected void invalidated() {
+                    setEventHandler(ScrollToEvent.SCROLL_TO_TOP_INDEX, get());
+                }
+                @Override
+                public Object getBean() {
+                    return ListView.this;
+                }
+
+                @Override
+                public String getName() {
+                    return "onScrollTo";
+                }
+            };
+        }
+        return onScrollTo;
     }
 
     private AccessibleList accListView ;
--- a/javafx-ui-controls/src/javafx/scene/control/ScrollPane.java	Tue Jan 22 12:11:20 2013 +1300
+++ b/javafx-ui-controls/src/javafx/scene/control/ScrollPane.java	Tue Jan 22 12:21:23 2013 +1300
@@ -39,6 +39,7 @@
 import javafx.beans.property.BooleanProperty;
 import javafx.beans.property.DoubleProperty;
 import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.ObjectPropertyBase;
 import javafx.beans.property.SimpleDoubleProperty;
 import javafx.beans.property.SimpleObjectProperty;
 
@@ -48,6 +49,8 @@
 
 import javafx.beans.DefaultProperty;
 import javafx.css.StyleableProperty;
+import javafx.event.Event;
+import javafx.event.EventHandler;
 
 /**
  * A Control that provides a scrolled, clipped viewport of its contents. It
@@ -727,6 +730,51 @@
     }
     
     /**
+     * Scroll the given node into view
+     * @param node the node to scroll into view
+     */
+    public void scrollTo(Node node) {
+        Event.fireEvent(this, new ScrollToEvent<Node>(this, this, ScrollToEvent.SCROLL_TO_NODE, node));        
+    }
+    
+    /**
+     * Called when there's a request to scroll a node into view using {@link #scrollTo(Node)}
+     */
+    private ObjectProperty<EventHandler<ScrollToEvent<Node>>> onScrollTo;
+    
+    public void setOnScrollTo(EventHandler<ScrollToEvent<Node>> value) {
+        onScrollToProperty().set(value);
+    }
+    
+    public EventHandler<ScrollToEvent<Node>> getOnScrollTo() {
+        if( onScrollTo != null ) {
+            return onScrollTo.get();
+        }
+        return null;
+    }
+    
+    public ObjectProperty<EventHandler<ScrollToEvent<Node>>> onScrollToProperty() {
+        if( onScrollTo == null ) {
+            onScrollTo = new ObjectPropertyBase<EventHandler<ScrollToEvent<Node>>>() {
+                @Override
+                protected void invalidated() {
+                    setEventHandler(ScrollToEvent.SCROLL_TO_NODE, get());
+                }
+                @Override
+                public Object getBean() {
+                    return ScrollPane.this;
+                }
+
+                @Override
+                public String getName() {
+                    return "onScrollTo";
+                }
+            };
+        }
+        return onScrollTo;
+    }
+    
+    /**
      * An enumeration denoting the policy to be used by a scrollable
      * Control in deciding whether to show a scroll bar.
      */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/javafx-ui-controls/src/javafx/scene/control/ScrollToEvent.java	Tue Jan 22 12:21:23 2013 +1300
@@ -0,0 +1,50 @@
+package javafx.scene.control;
+
+import javafx.event.Event;
+import javafx.event.EventTarget;
+import javafx.event.EventType;
+import javafx.scene.Node;
+import javafx.scene.control.*;
+
+/**
+ * Event related to {@link ScrollPane} and virtualised controls such as 
+ * {@link ListView}, {@link TableView}, {@link TreeView} and {@link TreeTableView}.
+ */
+public class ScrollToEvent<T> extends Event {
+    /**
+     * This event occurs if the user requests scrolling a node into view.
+     */
+    public static final EventType<ScrollToEvent<Node>> SCROLL_TO_NODE = 
+            new EventType<ScrollToEvent<Node>>(Event.ANY, "SCROLL_TO_NODE");
+    
+    /**
+     * This event occurs if the user requests scrolling a given index into view.
+     */
+    public static final EventType<ScrollToEvent<Integer>> SCROLL_TO_TOP_INDEX = 
+            new EventType<ScrollToEvent<Integer>>(Event.ANY, "SCROLL_TO_TOP_INDEX");
+
+    private static final long serialVersionUID = -8557345736849482516L;
+    
+    private final T scrollTarget;
+
+    /**
+     * Construct a new {@code Event} with the specified event source, target
+     * and type. If the source or target is set to {@code null}, it is replaced
+     * by the {@code NULL_SOURCE_TARGET} value.
+     * 
+     * @param source the event source which sent the event
+     * @param target the event source which sent the event
+     * @param type the event type
+     * @param target the target of the scroll to operation
+     */
+    public ScrollToEvent(Object source, EventTarget target, EventType<ScrollToEvent<T>> type, T scrollTarget) {
+        super(source, target, type);
+        assert scrollTarget != null;
+        this.scrollTarget = scrollTarget;
+        
+    }
+    
+    public T getScrollTarget() {
+        return scrollTarget;
+    }
+}
\ No newline at end of file
--- a/javafx-ui-controls/src/javafx/scene/control/TableView.java	Tue Jan 22 12:11:20 2013 +1300
+++ b/javafx-ui-controls/src/javafx/scene/control/TableView.java	Tue Jan 22 12:21:23 2013 +1300
@@ -36,6 +36,7 @@
 import javafx.beans.Observable;
 import javafx.beans.property.BooleanProperty;
 import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.ObjectPropertyBase;
 import javafx.beans.property.SimpleBooleanProperty;
 import javafx.beans.property.SimpleObjectProperty;
 import javafx.beans.value.ChangeListener;
@@ -56,6 +57,9 @@
 import com.sun.javafx.scene.control.ReadOnlyUnbackedObservableList;
 import com.sun.javafx.scene.control.TableColumnComparator;
 import javafx.collections.WeakListChangeListener;
+import javafx.event.Event;
+import javafx.event.EventHandler;
+
 import com.sun.javafx.scene.control.skin.TableViewSkin;
 import com.sun.javafx.scene.control.skin.TableViewSkinBase;
 import com.sun.javafx.scene.control.skin.VirtualContainerBase;
@@ -855,7 +859,44 @@
      * @param index The index of an item that should be visible to the user.
      */
     public void scrollTo(int index) {
-       getProperties().put(VirtualContainerBase.SCROLL_TO_INDEX_TOP, index);
+       ControlUtils.scrollToIndex(this, index);
+    }
+    
+    /**
+     * Called when there's a request to scroll an index into view using {@link #scrollTo(int)}
+     */
+    private ObjectProperty<EventHandler<ScrollToEvent<Integer>>> onScrollTo;
+    
+    public void setOnScrollTo(EventHandler<ScrollToEvent<Integer>> value) {
+        onScrollToProperty().set(value);
+    }
+    
+    public EventHandler<ScrollToEvent<Integer>> getOnScrollTo() {
+        if( onScrollTo != null ) {
+            return onScrollTo.get();
+        }
+        return null;
+    }
+    
+    public ObjectProperty<EventHandler<ScrollToEvent<Integer>>> onScrollToProperty() {
+        if( onScrollTo == null ) {
+            onScrollTo = new ObjectPropertyBase<EventHandler<ScrollToEvent<Integer>>>() {
+                @Override
+                protected void invalidated() {
+                    setEventHandler(ScrollToEvent.SCROLL_TO_TOP_INDEX, get());
+                }
+                @Override
+                public Object getBean() {
+                    return TableView.this;
+                }
+
+                @Override
+                public String getName() {
+                    return "onScrollTo";
+                }
+            };
+        }
+        return onScrollTo;
     }
 
     /**
--- a/javafx-ui-controls/src/javafx/scene/control/TreeTableView.java	Tue Jan 22 12:11:20 2013 +1300
+++ b/javafx-ui-controls/src/javafx/scene/control/TreeTableView.java	Tue Jan 22 12:21:23 2013 +1300
@@ -44,6 +44,7 @@
 import javafx.beans.property.BooleanProperty;
 import javafx.beans.property.IntegerProperty;
 import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.ObjectPropertyBase;
 import javafx.beans.property.ReadOnlyIntegerProperty;
 import javafx.beans.property.ReadOnlyIntegerWrapper;
 import javafx.beans.property.ReadOnlyObjectProperty;
@@ -1243,7 +1244,44 @@
      *      number of the visible items in the TreeTableView.
      */
     public void scrollTo(int index) {
-       getProperties().put(VirtualContainerBase.SCROLL_TO_INDEX_TOP, index);
+        ControlUtils.scrollToIndex(this, index);
+    }
+    
+    /**
+     * Called when there's a request to scroll an index into view using {@link #scrollTo(int)}
+     */
+    private ObjectProperty<EventHandler<ScrollToEvent<Integer>>> onScrollTo;
+    
+    public void setOnScrollTo(EventHandler<ScrollToEvent<Integer>> value) {
+        onScrollToProperty().set(value);
+    }
+    
+    public EventHandler<ScrollToEvent<Integer>> getOnScrollTo() {
+        if( onScrollTo != null ) {
+            return onScrollTo.get();
+        }
+        return null;
+    }
+    
+    public ObjectProperty<EventHandler<ScrollToEvent<Integer>>> onScrollToProperty() {
+        if( onScrollTo == null ) {
+            onScrollTo = new ObjectPropertyBase<EventHandler<ScrollToEvent<Integer>>>() {
+                @Override
+                protected void invalidated() {
+                    setEventHandler(ScrollToEvent.SCROLL_TO_TOP_INDEX, get());
+                }
+                @Override
+                public Object getBean() {
+                    return TreeTableView.this;
+                }
+
+                @Override
+                public String getName() {
+                    return "onScrollTo";
+                }
+            };
+        }
+        return onScrollTo;
     }
 
     /**
--- a/javafx-ui-controls/src/javafx/scene/control/TreeView.java	Tue Jan 22 12:11:20 2013 +1300
+++ b/javafx-ui-controls/src/javafx/scene/control/TreeView.java	Tue Jan 22 12:21:23 2013 +1300
@@ -29,6 +29,7 @@
 import javafx.beans.property.BooleanProperty;
 import javafx.beans.property.IntegerProperty;
 import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.ObjectPropertyBase;
 import javafx.beans.property.SimpleIntegerProperty;
 import javafx.beans.property.SimpleObjectProperty;
 import javafx.beans.value.ChangeListener;
@@ -724,10 +725,47 @@
      *      number of the visible items in the TreeView.
      */
     public void scrollTo(int index) {
-       getProperties().put(VirtualContainerBase.SCROLL_TO_INDEX_TOP, index);
+        ControlUtils.scrollToIndex(this, index);
     }
 
     /**
+     * Called when there's a request to scroll an index into view using {@link #scrollTo(int)}
+     */
+    private ObjectProperty<EventHandler<ScrollToEvent<Integer>>> onScrollTo;
+    
+    public void setOnScrollTo(EventHandler<ScrollToEvent<Integer>> value) {
+        onScrollToProperty().set(value);
+    }
+    
+    public EventHandler<ScrollToEvent<Integer>> getOnScrollTo() {
+        if( onScrollTo != null ) {
+            return onScrollTo.get();
+        }
+        return null;
+    }
+    
+    public ObjectProperty<EventHandler<ScrollToEvent<Integer>>> onScrollToProperty() {
+        if( onScrollTo == null ) {
+            onScrollTo = new ObjectPropertyBase<EventHandler<ScrollToEvent<Integer>>>() {
+                @Override
+                protected void invalidated() {
+                    setEventHandler(ScrollToEvent.SCROLL_TO_TOP_INDEX, get());
+                }
+                @Override
+                public Object getBean() {
+                    return TreeView.this;
+                }
+
+                @Override
+                public String getName() {
+                    return "onScrollTo";
+                }
+            };
+        }
+        return onScrollTo;
+    }
+    
+    /**
      * Returns the index position of the given TreeItem, taking into account the
      * current state of each TreeItem (i.e. whether or not it is expanded).
      *