diff -r 3ef8edbf91d6 -r 7b2efc89e581 javafx-ui-controls/src/com/sun/javafx/scene/control/skin/ScrollPaneSkin.java --- a/javafx-ui-controls/src/com/sun/javafx/scene/control/skin/ScrollPaneSkin.java Thu Jan 17 21:05:37 2013 +0100 +++ b/javafx-ui-controls/src/com/sun/javafx/scene/control/skin/ScrollPaneSkin.java Thu Jan 17 23:06:02 2013 +0100 @@ -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>() { + + @Override + public void handle(ScrollToEvent 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(); } diff -r 3ef8edbf91d6 -r 7b2efc89e581 javafx-ui-controls/src/com/sun/javafx/scene/control/skin/VirtualContainerBase.java --- a/javafx-ui-controls/src/com/sun/javafx/scene/control/skin/VirtualContainerBase.java Thu Jan 17 21:05:37 2013 +0100 +++ b/javafx-ui-controls/src/com/sun/javafx/scene/control/skin/VirtualContainerBase.java Thu Jan 17 23:06:02 2013 +0100 @@ -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, I extends IndexedCell> extends BehaviorSkinBase { - 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(); - flow = new VirtualFlow(); - handleControlProperties(control); + control.addEventHandler(ScrollToEvent.SCROLL_TO_TOP_INDEX, new EventHandler>() { - control.getProperties().addListener(new MapChangeListener() { @Override - public void onChanged(Change c) { - if (c.wasAdded()) { - handleControlProperties(control); - } + public void handle(ScrollToEvent event) { + flow.scrollTo(event.getScrollTarget()); } - }); + }); } /** @@ -100,23 +94,4 @@ return height + padding.getTop() + padding.getBottom(); } - private void handleControlProperties(C control) { - Mapproperties = 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); - } - } } diff -r 3ef8edbf91d6 -r 7b2efc89e581 javafx-ui-controls/src/javafx/scene/control/ControlUtils.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/javafx-ui-controls/src/javafx/scene/control/ControlUtils.java Thu Jan 17 23:06:02 2013 +0100 @@ -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(node, node, ScrollToEvent.SCROLL_TO_TOP_INDEX, idx)); + } + node.sceneProperty().removeListener(this); + } + }); + } + node.getProperties().put(CACHE_KEY, index); + } else { + Event.fireEvent(node, new ScrollToEvent(node, node, ScrollToEvent.SCROLL_TO_TOP_INDEX, index)); + } + } +} diff -r 3ef8edbf91d6 -r 7b2efc89e581 javafx-ui-controls/src/javafx/scene/control/ListView.java --- a/javafx-ui-controls/src/javafx/scene/control/ListView.java Thu Jan 17 21:05:37 2013 +0100 +++ b/javafx-ui-controls/src/javafx/scene/control/ListView.java Thu Jan 17 23:06:02 2013 +0100 @@ -49,6 +49,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; @@ -722,7 +723,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>> onScrollTo; + + public void setOnScrollTo(EventHandler> value) { + onScrollToProperty().set(value); + } + + public EventHandler> getOnScrollTo() { + if( onScrollTo != null ) { + return onScrollTo.get(); + } + return null; + } + + public ObjectProperty>> onScrollToProperty() { + if( onScrollTo == null ) { + onScrollTo = new ObjectPropertyBase>>() { + @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 ; diff -r 3ef8edbf91d6 -r 7b2efc89e581 javafx-ui-controls/src/javafx/scene/control/ScrollPane.java --- a/javafx-ui-controls/src/javafx/scene/control/ScrollPane.java Thu Jan 17 21:05:37 2013 +0100 +++ b/javafx-ui-controls/src/javafx/scene/control/ScrollPane.java Thu Jan 17 23:06:02 2013 +0100 @@ -40,6 +40,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; @@ -49,6 +50,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 @@ -728,6 +731,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(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>> onScrollTo; + + public void setOnScrollTo(EventHandler> value) { + onScrollToProperty().set(value); + } + + public EventHandler> getOnScrollTo() { + if( onScrollTo != null ) { + return onScrollTo.get(); + } + return null; + } + + public ObjectProperty>> onScrollToProperty() { + if( onScrollTo == null ) { + onScrollTo = new ObjectPropertyBase>>() { + @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. */ diff -r 3ef8edbf91d6 -r 7b2efc89e581 javafx-ui-controls/src/javafx/scene/control/ScrollToEvent.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/javafx-ui-controls/src/javafx/scene/control/ScrollToEvent.java Thu Jan 17 23:06:02 2013 +0100 @@ -0,0 +1,46 @@ +package javafx.scene.control; + +import javafx.event.Event; +import javafx.event.EventTarget; +import javafx.event.EventType; +import javafx.scene.Node; + +/** + * Event related to {@link ScrollPane} + */ +public class ScrollToEvent extends Event { + /** + * This event occurs if the user requests scrolling a node into view + */ + public static final EventType> SCROLL_TO_NODE = new EventType>(Event.ANY, "SCROLL_TO_NODE"); + + /** + * This event occurs if the user requests scrolling top index in a view + */ + public static final EventType> SCROLL_TO_TOP_INDEX = new EventType>(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> type, T scrollTarget) { + super(source, target, type); + assert scrollTarget != null; + this.scrollTarget = scrollTarget; + + } + + public T getScrollTarget() { + return scrollTarget; + } +} \ No newline at end of file diff -r 3ef8edbf91d6 -r 7b2efc89e581 javafx-ui-controls/src/javafx/scene/control/TableView.java --- a/javafx-ui-controls/src/javafx/scene/control/TableView.java Thu Jan 17 21:05:37 2013 +0100 +++ b/javafx-ui-controls/src/javafx/scene/control/TableView.java Thu Jan 17 23:06:02 2013 +0100 @@ -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>> onScrollTo; + + public void setOnScrollTo(EventHandler> value) { + onScrollToProperty().set(value); + } + + public EventHandler> getOnScrollTo() { + if( onScrollTo != null ) { + return onScrollTo.get(); + } + return null; + } + + public ObjectProperty>> onScrollToProperty() { + if( onScrollTo == null ) { + onScrollTo = new ObjectPropertyBase>>() { + @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; } /** diff -r 3ef8edbf91d6 -r 7b2efc89e581 javafx-ui-controls/src/javafx/scene/control/TreeTableView.java --- a/javafx-ui-controls/src/javafx/scene/control/TreeTableView.java Thu Jan 17 21:05:37 2013 +0100 +++ b/javafx-ui-controls/src/javafx/scene/control/TreeTableView.java Thu Jan 17 23:06:02 2013 +0100 @@ -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>> onScrollTo; + + public void setOnScrollTo(EventHandler> value) { + onScrollToProperty().set(value); + } + + public EventHandler> getOnScrollTo() { + if( onScrollTo != null ) { + return onScrollTo.get(); + } + return null; + } + + public ObjectProperty>> onScrollToProperty() { + if( onScrollTo == null ) { + onScrollTo = new ObjectPropertyBase>>() { + @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; } /** diff -r 3ef8edbf91d6 -r 7b2efc89e581 javafx-ui-controls/src/javafx/scene/control/TreeView.java --- a/javafx-ui-controls/src/javafx/scene/control/TreeView.java Thu Jan 17 21:05:37 2013 +0100 +++ b/javafx-ui-controls/src/javafx/scene/control/TreeView.java Thu Jan 17 23:06:02 2013 +0100 @@ -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>> onScrollTo; + + public void setOnScrollTo(EventHandler> value) { + onScrollToProperty().set(value); + } + + public EventHandler> getOnScrollTo() { + if( onScrollTo != null ) { + return onScrollTo.get(); + } + return null; + } + + public ObjectProperty>> onScrollToProperty() { + if( onScrollTo == null ) { + onScrollTo = new ObjectPropertyBase>>() { + @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). *