diff --git a/javafx-ui-controls/src/com/sun/javafx/scene/control/TableColumnComparator.java b/javafx-ui-controls/src/com/sun/javafx/scene/control/TableColumnComparator.java --- a/javafx-ui-controls/src/com/sun/javafx/scene/control/TableColumnComparator.java +++ b/javafx-ui-controls/src/com/sun/javafx/scene/control/TableColumnComparator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -22,37 +22,37 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ -/* - * To change this template, choose Tools | Templates - * and open the template in the editor. - */ - package com.sun.javafx.scene.control; import java.util.Comparator; +import java.util.List; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.scene.control.TableColumnBase; -public class TableColumnComparator implements Comparator { +public class TableColumnComparator implements Comparator { - private final ObservableList> columns; + private final ObservableList> columns; - public TableColumnComparator() { - this.columns = FXCollections.observableArrayList(); + public TableColumnComparator(TableColumnBase... columns) { + this.columns = FXCollections.observableArrayList(columns); + } + + public TableColumnComparator(List> columns) { + this.columns = FXCollections.observableArrayList(columns); } - public ObservableList> getColumns() { - return columns; - } +// public ObservableList> getColumns() { +// return columns; +// } - @Override public int compare(Object o1, Object o2) { - for (TableColumnBase tc : columns) { - Comparator c = tc.getComparator(); + @Override public int compare(S o1, S o2) { + for (TableColumnBase tc : columns) { + Comparator c = tc.getComparator(); - Object value1 = tc.getCellData(o1); - Object value2 = tc.getCellData(o2); + T value1 = tc.getCellData(o1); + T value2 = tc.getCellData(o2); int result = 0; switch (tc.getSortType()) { diff --git a/javafx-ui-controls/src/javafx/scene/control/SortEvent.java b/javafx-ui-controls/src/javafx/scene/control/SortEvent.java new file mode 100644 --- /dev/null +++ b/javafx-ui-controls/src/javafx/scene/control/SortEvent.java @@ -0,0 +1,39 @@ +package javafx.scene.control; + +import javafx.event.Event; +import javafx.event.EventTarget; +import javafx.event.EventType; + +/** + * Event related to {@link TableView} and {@link TreeTableView} sorting. + */ +public class SortEvent extends Event { + + @SuppressWarnings("unchecked") + public static EventType> sortEvent() { + return (EventType>) SORT_EVENT; + } + + @SuppressWarnings("unchecked") + private static final EventType SORT_EVENT = new EventType(Event.ANY, "SORT_EVENT"); + +// /** +// * 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 SortEvent(C source, EventTarget target) { + super(source, target, sortEvent()); + + } + + @SuppressWarnings("unchecked") + @Override public C getSource() { + return (C) super.getSource(); + } +} \ No newline at end of file diff --git a/javafx-ui-controls/src/javafx/scene/control/TableView.java b/javafx-ui-controls/src/javafx/scene/control/TableView.java --- a/javafx-ui-controls/src/javafx/scene/control/TableView.java +++ b/javafx-ui-controls/src/javafx/scene/control/TableView.java @@ -28,9 +28,7 @@ import com.sun.javafx.collections.NonIterableChange; import java.util.ArrayList; -import java.util.Collections; import java.util.List; -import java.util.Set; import javafx.beans.InvalidationListener; import javafx.beans.Observable; @@ -57,21 +55,20 @@ 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 javafx.event.EventType; import com.sun.javafx.scene.control.skin.TableViewSkin; import com.sun.javafx.scene.control.skin.TableViewSkinBase; -import com.sun.javafx.scene.control.skin.VirtualContainerBase; +import java.lang.instrument.UnmodifiableClassException; import java.lang.ref.WeakReference; +import java.util.Comparator; import java.util.HashMap; import javafx.beans.DefaultProperty; import javafx.beans.WeakInvalidationListener; import javafx.beans.property.ReadOnlyObjectProperty; import javafx.beans.property.ReadOnlyObjectWrapper; import javafx.beans.value.WeakChangeListener; -import javafx.scene.control.cell.TreeItemPropertyValueFactory; /** * The TableView control is designed to visualize an unlimited number of rows @@ -318,6 +315,28 @@ } }; + // TODO document + public static final Callback DEFAULT_SORT_POLICY = new Callback() { + @Override public Boolean call(TableView table) { + try { + FXCollections.sort(table.getItems(), table.getComparator()); + return true; + } catch (UnsupportedOperationException e) { + // TODO might need to support other exception types including: + // ClassCastException - if the class of the specified element prevents it from being added to this list + // NullPointerException - if the specified element is null and this list does not permit null elements + // IllegalArgumentException - if some property of this element prevents it from being added to this list + + // TODO + // the list does not support sorting, so we should gracefully fail + // the sort request and ensure the UI is put back to its previous + // state + + return false; + } + } + }; + /*************************************************************************** @@ -394,7 +413,7 @@ // the sort method. getSortOrder().addListener(new ListChangeListener>() { @Override public void onChanged(Change> c) { - sort(); + doSort(); } }); @@ -489,7 +508,7 @@ @Override public void invalidated(Observable valueModel) { TableColumn col = (TableColumn) ((BooleanProperty)valueModel).getBean(); if (! getSortOrder().contains(col)) return; - sort(); + doSort(); } }; @@ -497,7 +516,7 @@ @Override public void invalidated(Observable valueModel) { TableColumn col = (TableColumn) ((ObjectProperty)valueModel).getBean(); if (! getSortOrder().contains(col)) return; - sort(); + doSort(); } }; @@ -816,6 +835,90 @@ return editingCell; } + + // --- Comparator (built via sortOrder list, so read-only) + // TODO javadoc + private ReadOnlyObjectWrapper> comparator; + private void setComparator(Comparator value) { + comparatorPropertyImpl().set(value); + } + public final Comparator getComparator() { + return comparator == null ? null : comparator.get(); + } + public final ReadOnlyObjectProperty> comparatorProperty() { + return comparatorPropertyImpl().getReadOnlyProperty(); + } + private ReadOnlyObjectWrapper> comparatorPropertyImpl() { + if (comparator == null) { + comparator = new ReadOnlyObjectWrapper>(this, "comparator"); + } + return comparator; + } + + + // --- sortPolicy + // TODO javadoc + private ObjectProperty, Boolean>> sortPolicy; + public final void setSortPolicy(Callback, Boolean> callback) { + sortPolicyProperty().set(callback); + } + @SuppressWarnings("unchecked") + public final Callback, Boolean> getSortPolicy() { + return sortPolicy == null ? + (Callback, Boolean>)(Object) DEFAULT_SORT_POLICY : + sortPolicy.get(); + } + @SuppressWarnings("unchecked") + public final ObjectProperty, Boolean>> sortPolicyProperty() { + if (sortPolicy == null) { + sortPolicy = new SimpleObjectProperty, Boolean>>( + this, "sortPolicy", (Callback, Boolean>)(Object) DEFAULT_SORT_POLICY) { + @Override protected void invalidated() { + // TODO + } + }; + } + return sortPolicy; + } + + + // onSort + /** + * Called when there's a request to sort the control. + */ + private ObjectProperty>>> onSort; + + public void setOnSort(EventHandler>> value) { + onSortProperty().set(value); + } + + public EventHandler>> getOnSort() { + if( onSort != null ) { + return onSort.get(); + } + return null; + } + + public ObjectProperty>>> onSortProperty() { + if( onSort == null ) { + onSort = new ObjectPropertyBase>>>() { + @Override protected void invalidated() { + EventType>> eventType = SortEvent.sortEvent(); + EventHandler>> eventHandler = get(); + setEventHandler(eventType, eventHandler); + } + + @Override public Object getBean() { + return TableView.this; + } + + @Override public String getName() { + return "onSort"; + } + }; + } + return onSort; + } /*************************************************************************** @@ -1029,12 +1132,48 @@ @Override protected Skin createDefaultSkin() { return new TableViewSkin(this); } + +// /** +// * Sometimes we want to force a sort to run - this is the recommended way +// * of doing it internally. External users of the TableView API should just +// * stick to modifying the TableView.sortOrder ObservableList (or the contents +// * of the TableColumns within it - in particular the +// * TableColumn.sortAscending boolean). +// */ + // TODO javadoc + public void sort() { + Callback, Boolean> sortPolicy = getSortPolicy(); + if (sortPolicy == null) return; + Boolean success = sortPolicy.call(this); + + if (success == null || ! success) { + // TODO the sort was a failure. Need to resolve. + } + } + + /*************************************************************************** * * * Private Implementation * * * **************************************************************************/ + + private void doSort() { + // update the Comparator property + Comparator comparator = new TableColumnComparator(getSortOrder()); + setComparator(comparator); + + // fire the onSort event and check if it is consumed, if + // so, don't run the sort + SortEvent sortEvent = new SortEvent(TableView.this, TableView.this); + fireEvent(sortEvent); + if (sortEvent.isConsumed()) return; + + // run the sort method (which will then defer to the currently + // set sort callback) + sort(); + } /** * Call this function to force the TableView to re-evaluate itself. This is @@ -1048,56 +1187,6 @@ getProperties().put(TableViewSkinBase.REFRESH, Boolean.TRUE); } - /** - * Sometimes we want to force a sort to run - this is the recommended way - * of doing it internally. External users of the TableView API should just - * stick to modifying the TableView.sortOrder ObservableList (or the contents - * of the TableColumns within it - in particular the - * TableColumn.sortAscending boolean). - */ - private void sort() { - // build up a new comparator based on the current table columms - TableColumnComparator comparator = new TableColumnComparator(); - for (TableColumn tc : getSortOrder()) { - comparator.getColumns().add(tc); - } - - // If the items are a TransformationList, but not a SortableList, then we need - // to get the source of the TransformationList and check it. - if (getItems() instanceof TransformationList) { - // FIXME this is temporary code whilst I await for similar functionality - // within FXCollections.sort, such that it does the unwrapping that is - // shown below - List list = getItems(); - while (list != null) { - if (list instanceof SortableList) { - break; - } else if (list instanceof TransformationList) { - list = ((TransformationList)list).getDirectSource(); - } else { - break; - } - } - - if (list instanceof SortableList) { - SortableList sortableList = (SortableList) list; - // TODO review - note that we're changing the comparator based on - // what columns the user has set. - sortableList.setComparator(comparator); - - if (sortableList.getMode() == SortableList.SortMode.BATCH) { - sortableList.sort(); - } - - return; - } - } - - // If we are here, we will use the default sort functionality available - // in FXCollections - FXCollections.sort(getItems(), comparator); - } - // --- Content width private void setContentWidth(double contentWidth) { diff --git a/javafx-ui-controls/src/javafx/scene/control/TreeTableView.java b/javafx-ui-controls/src/javafx/scene/control/TreeTableView.java --- a/javafx-ui-controls/src/javafx/scene/control/TreeTableView.java +++ b/javafx-ui-controls/src/javafx/scene/control/TreeTableView.java @@ -35,6 +35,7 @@ import com.sun.javafx.scene.control.skin.VirtualContainerBase; import java.lang.ref.WeakReference; import java.util.ArrayList; +import java.util.Comparator; import java.util.List; import java.util.Set; import javafx.application.Platform; @@ -321,7 +322,7 @@ // the sort method. getSortOrder().addListener(new ListChangeListener>() { @Override public void onChanged(ListChangeListener.Change> c) { - sort(); + doSort(); } }); @@ -479,6 +480,36 @@ } }; + // TODO document + public static final Callback DEFAULT_SORT_POLICY = new Callback() { + @Override public Boolean call(TreeTableView table) { + try { + TreeItem rootItem = table.getRoot(); + if (rootItem == null) return false; + + TreeSortMode sortMode = table.getSortMode(); + if (sortMode == null) return false; + + rootItem.lastSortMode = sortMode; + rootItem.lastComparator = table.getComparator(); + rootItem.sort(); + return true; + } catch (UnsupportedOperationException e) { + // TODO might need to support other exception types including: + // ClassCastException - if the class of the specified element prevents it from being added to this list + // NullPointerException - if the specified element is null and this list does not permit null elements + // IllegalArgumentException - if some property of this element prevents it from being added to this list + + // TODO + // the list does not support sorting, so we should gracefully fail + // the sort request and ensure the UI is put back to its previous + // state + + return false; + } + } + }; + /*************************************************************************** @@ -580,7 +611,7 @@ @Override public void invalidated(Observable valueModel) { TreeTableColumn col = (TreeTableColumn) ((BooleanProperty)valueModel).getBean(); if (! getSortOrder().contains(col)) return; - sort(); + doSort(); } }; @@ -588,7 +619,7 @@ @Override public void invalidated(Observable valueModel) { TreeTableColumn col = (TreeTableColumn) ((ObjectProperty)valueModel).getBean(); if (! getSortOrder().contains(col)) return; - sort(); + doSort(); } }; @@ -1205,6 +1236,91 @@ } + // --- Comparator (built via sortOrder list, so read-only) + // TODO javadoc + private ReadOnlyObjectWrapper> comparator; + private void setComparator(Comparator value) { + comparatorPropertyImpl().set(value); + } + public final Comparator getComparator() { + return comparator == null ? null : comparator.get(); + } + public final ReadOnlyObjectProperty> comparatorProperty() { + return comparatorPropertyImpl().getReadOnlyProperty(); + } + private ReadOnlyObjectWrapper> comparatorPropertyImpl() { + if (comparator == null) { + comparator = new ReadOnlyObjectWrapper>(this, "comparator"); + } + return comparator; + } + + + // --- sortPolicy + // TODO javadoc + private ObjectProperty, Boolean>> sortPolicy; + public final void setSortPolicy(Callback, Boolean> callback) { + sortPolicyProperty().set(callback); + } + @SuppressWarnings("unchecked") + public final Callback, Boolean> getSortPolicy() { + return sortPolicy == null ? + (Callback, Boolean>)(Object) DEFAULT_SORT_POLICY : + sortPolicy.get(); + } + @SuppressWarnings("unchecked") + public final ObjectProperty, Boolean>> sortPolicyProperty() { + if (sortPolicy == null) { + sortPolicy = new SimpleObjectProperty, Boolean>>( + this, "sortPolicy", (Callback, Boolean>)(Object) DEFAULT_SORT_POLICY) { + @Override protected void invalidated() { + // TODO + } + }; + } + return sortPolicy; + } + + + // onSort + /** + * Called when there's a request to sort the control. + */ + private ObjectProperty>>> onSort; + + public void setOnSort(EventHandler>> value) { + onSortProperty().set(value); + } + + public EventHandler>> getOnSort() { + if( onSort != null ) { + return onSort.get(); + } + return null; + } + + public ObjectProperty>>> onSortProperty() { + if( onSort == null ) { + onSort = new ObjectPropertyBase>>>() { + @Override protected void invalidated() { + EventType>> eventType = SortEvent.sortEvent(); + EventHandler>> eventHandler = get(); + setEventHandler(eventType, eventHandler); + } + + @Override public Object getBean() { + return TreeTableView.this; + } + + @Override public String getName() { + return "onSort"; + } + }; + } + return onSort; + } + + /*************************************************************************** * * @@ -1454,6 +1570,24 @@ return visibleLeafColumns.get(column); } +// /** +// * Sometimes we want to force a sort to run - this is the recommended way +// * of doing it internally. External users of the TableView API should just +// * stick to modifying the TableView.sortOrder ObservableList (or the contents +// * of the TreeTableColumns within it - in particular the +// * TreeTableColumn.sortAscending boolean). +// */ + // TODO javadoc + private void sort() { + Callback, Boolean> sortPolicy = getSortPolicy(); + if (sortPolicy == null) return; + Boolean success = sortPolicy.call(this); + + if (success == null || ! success) { + // TODO the sort was a failure. Need to resolve. + } + } + /*************************************************************************** @@ -1462,6 +1596,22 @@ * * **************************************************************************/ + private void doSort() { + // update the Comparator property + Comparator comparator = new TableColumnComparator(getSortOrder()); + setComparator(comparator); + + // fire the onSort event and check if it is consumed, if + // so, don't run the sort + SortEvent sortEvent = new SortEvent(TreeTableView.this, TreeTableView.this); + fireEvent(sortEvent); + if (sortEvent.isConsumed()) return; + + // run the sort method (which will then defer to the currently + // set sort callback) + sort(); + } + private void updateExpandedItemCount(TreeItem treeItem) { setExpandedItemCount(TreeUtil.updateExpandedItemCount(treeItem, expandedItemCountDirty, isShowRoot())); expandedItemCountDirty = false; @@ -1487,31 +1637,6 @@ getProperties().put(TableViewSkinBase.REFRESH, Boolean.TRUE); } - /** - * Sometimes we want to force a sort to run - this is the recommended way - * of doing it internally. External users of the TableView API should just - * stick to modifying the TableView.sortOrder ObservableList (or the contents - * of the TreeTableColumns within it - in particular the - * TreeTableColumn.sortAscending boolean). - */ - private void sort() { - TreeItem rootItem = getRoot(); - if (rootItem == null) return; - - TreeSortMode sortMode = getSortMode(); - if (sortMode == null) return; - - // build up a new comparator based on the current table columms - TableColumnComparator comparator = new TableColumnComparator(); - for (TreeTableColumn tc : getSortOrder()) { - comparator.getColumns().add(tc); - } - - rootItem.lastSortMode = sortMode; - rootItem.lastComparator = comparator; - rootItem.sort(); - } - // --- Content width private void setContentWidth(double contentWidth) { this.contentWidth = contentWidth;