1 /*
2 * Copyright (c) 2012, 2014, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 package com.sun.javafx.scene.control.skin;
27
28 import com.sun.javafx.collections.NonIterableChange;
29 import com.sun.javafx.scene.control.ReadOnlyUnbackedObservableList;
30
31 import javafx.event.WeakEventHandler;
32 import javafx.scene.control.*;
33
34 import com.sun.javafx.scene.control.behavior.TreeTableViewBehavior;
35
36 import java.lang.ref.WeakReference;
37 import java.util.ArrayList;
38 import java.util.List;
39
40 import javafx.beans.property.BooleanProperty;
41 import javafx.beans.property.ObjectProperty;
42 import javafx.beans.property.SimpleObjectProperty;
43 import javafx.collections.FXCollections;
44 import javafx.collections.ObservableList;
45 import javafx.event.EventHandler;
46 import javafx.event.EventType;
47 import javafx.scene.AccessibleAction;
48 import javafx.scene.AccessibleAttribute;
49 import javafx.scene.Node;
50 import javafx.scene.control.TreeItem.TreeModificationEvent;
51 import javafx.scene.input.MouseEvent;
52 import javafx.scene.layout.Region;
53 import javafx.scene.layout.StackPane;
54 import javafx.util.Callback;
55
56 public class TreeTableViewSkin<S> extends TableViewSkinBase<S, TreeItem<S>, TreeTableView<S>, TreeTableViewBehavior<S>, TreeTableRow<S>, TreeTableColumn<S,?>> {
57
58 public TreeTableViewSkin(final TreeTableView<S> treeTableView) {
59 super(treeTableView, new TreeTableViewBehavior<S>(treeTableView));
60
61 this.treeTableView = treeTableView;
62 this.tableBackingList = new TreeTableViewBackingList<S>(treeTableView);
63 this.tableBackingListProperty = new SimpleObjectProperty<ObservableList<TreeItem<S>>>(tableBackingList);
64
65 flow.setFixedCellSize(treeTableView.getFixedCellSize());
66
67 super.init(treeTableView);
68
69 setRoot(getSkinnable().getRoot());
70
71 EventHandler<MouseEvent> ml = event -> {
72 // RT-15127: cancel editing on scroll. This is a bit extreme
73 // (we are cancelling editing on touching the scrollbars).
74 // This can be improved at a later date.
75 if (treeTableView.getEditingCell() != null) {
76 treeTableView.edit(-1, null);
77 }
78
79 // This ensures that the table maintains the focus, even when the vbar
80 // and hbar controls inside the flow are clicked. Without this, the
81 // focus border will not be shown when the user interacts with the
82 // scrollbars, and more importantly, keyboard navigation won't be
83 // available to the user.
84 if (treeTableView.isFocusTraversable()) {
85 treeTableView.requestFocus();
86 }
87 };
88 flow.getVbar().addEventFilter(MouseEvent.MOUSE_PRESSED, ml);
89 flow.getHbar().addEventFilter(MouseEvent.MOUSE_PRESSED, ml);
90
91 // init the behavior 'closures'
92 TreeTableViewBehavior<S> behavior = getBehavior();
93 behavior.setOnFocusPreviousRow(() -> { onFocusPreviousCell(); });
94 behavior.setOnFocusNextRow(() -> { onFocusNextCell(); });
95 behavior.setOnMoveToFirstCell(() -> { onMoveToFirstCell(); });
96 behavior.setOnMoveToLastCell(() -> { onMoveToLastCell(); });
97 behavior.setOnScrollPageDown(isFocusDriven -> onScrollPageDown(isFocusDriven));
98 behavior.setOnScrollPageUp(isFocusDriven -> onScrollPageUp(isFocusDriven));
99 behavior.setOnSelectPreviousRow(() -> { onSelectPreviousCell(); });
100 behavior.setOnSelectNextRow(() -> { onSelectNextCell(); });
101 behavior.setOnSelectLeftCell(() -> { onSelectLeftCell(); });
102 behavior.setOnSelectRightCell(() -> { onSelectRightCell(); });
103
104 registerChangeListener(treeTableView.rootProperty(), "ROOT");
105 registerChangeListener(treeTableView.showRootProperty(), "SHOW_ROOT");
106 registerChangeListener(treeTableView.rowFactoryProperty(), "ROW_FACTORY");
107 registerChangeListener(treeTableView.expandedItemCountProperty(), "TREE_ITEM_COUNT");
108 registerChangeListener(treeTableView.fixedCellSizeProperty(), "FIXED_CELL_SIZE");
109 }
110
111 @Override protected void handleControlPropertyChanged(String p) {
112 super.handleControlPropertyChanged(p);
113
114 if ("ROOT".equals(p)) {
115 // fix for RT-37853
116 getSkinnable().edit(-1, null);
117
118 setRoot(getSkinnable().getRoot());
119 } else if ("SHOW_ROOT".equals(p)) {
120 // if we turn off showing the root, then we must ensure the root
121 // is expanded - otherwise we end up with no visible items in
122 // the tree.
123 if (! getSkinnable().isShowRoot() && getRoot() != null) {
124 getRoot().setExpanded(true);
125 }
126 // update the item count in the flow and behavior instances
127 updateRowCount();
128 } else if ("ROW_FACTORY".equals(p)) {
129 flow.recreateCells();
130 } else if ("TREE_ITEM_COUNT".equals(p)) {
131 rowCountDirty = true;
132 } else if ("FIXED_CELL_SIZE".equals(p)) {
133 flow.setFixedCellSize(getSkinnable().getFixedCellSize());
134 }
135 }
136
137 /***************************************************************************
138 * *
139 * Listeners *
140 * *
141 **************************************************************************/
142
143
144
145 /***************************************************************************
146 * *
147 * Internal Fields *
148 * *
149 **************************************************************************/
150
151 private TreeTableViewBackingList<S> tableBackingList;
152 private ObjectProperty<ObservableList<TreeItem<S>>> tableBackingListProperty;
153 private TreeTableView<S> treeTableView;
154 private WeakReference<TreeItem<S>> weakRootRef;
155
156 private EventHandler<TreeItem.TreeModificationEvent<S>> rootListener = e -> {
157 if (e.wasAdded() && e.wasRemoved() && e.getAddedSize() == e.getRemovedSize()) {
158 // Fix for RT-14842, where the children of a TreeItem were changing,
159 // but because the overall item count was staying the same, there was
160 // no event being fired to the skin to be informed that the items
161 // had changed. So, here we just watch for the case where the number
162 // of items being added is equal to the number of items being removed.
163 rowCountDirty = true;
164 getSkinnable().requestLayout();
165 } else if (e.getEventType().equals(TreeItem.valueChangedEvent())) {
166 // Fix for RT-14971 and RT-15338.
167 needCellsRebuilt = true;
168 getSkinnable().requestLayout();
169 } else {
170 // Fix for RT-20090. We are checking to see if the event coming
171 // from the TreeItem root is an event where the count has changed.
172 EventType<?> eventType = e.getEventType();
173 while (eventType != null) {
174 if (eventType.equals(TreeItem.<S>expandedItemCountChangeEvent())) {
175 rowCountDirty = true;
176 getSkinnable().requestLayout();
177 break;
178 }
179 eventType = eventType.getSuperType();
180 }
181 }
182
183 // fix for RT-37853
184 getSkinnable().edit(-1, null);
185 };
186
187 private WeakEventHandler<TreeModificationEvent<S>> weakRootListener;
188
189
190 // private WeakReference<TreeItem> weakRoot;
191 private TreeItem<S> getRoot() {
192 return weakRootRef == null ? null : weakRootRef.get();
193 }
194 private void setRoot(TreeItem<S> newRoot) {
195 if (getRoot() != null && weakRootListener != null) {
196 getRoot().removeEventHandler(TreeItem.<S>treeNotificationEvent(), weakRootListener);
197 }
198 weakRootRef = new WeakReference<>(newRoot);
199 if (getRoot() != null) {
200 weakRootListener = new WeakEventHandler<>(rootListener);
201 getRoot().addEventHandler(TreeItem.<S>treeNotificationEvent(), weakRootListener);
202 }
203
204 updateRowCount();
205 }
206
207
208 /***************************************************************************
209 * *
210 * Public API *
211 * *
212 **************************************************************************/
213
214 /** {@inheritDoc} */
215 @Override protected ObservableList<TreeTableColumn<S, ?>> getVisibleLeafColumns() {
216 return treeTableView.getVisibleLeafColumns();
217 }
218
219 @Override protected int getVisibleLeafIndex(TreeTableColumn<S,?> tc) {
220 return treeTableView.getVisibleLeafIndex(tc);
221 }
222
223 @Override protected TreeTableColumn<S,?> getVisibleLeafColumn(int col) {
224 return treeTableView.getVisibleLeafColumn(col);
225 }
226
227 /** {@inheritDoc} */
228 @Override protected TreeTableView.TreeTableViewFocusModel<S> getFocusModel() {
229 return treeTableView.getFocusModel();
230 }
231
232 /** {@inheritDoc} */
233 @Override protected TreeTablePosition<S, ?> getFocusedCell() {
234 return treeTableView.getFocusModel().getFocusedCell();
235 }
236
237 /** {@inheritDoc} */
238 @Override protected TableSelectionModel<TreeItem<S>> getSelectionModel() {
239 return treeTableView.getSelectionModel();
240 }
241
242 /** {@inheritDoc} */
243 @Override protected ObjectProperty<Callback<TreeTableView<S>, TreeTableRow<S>>> rowFactoryProperty() {
244 return treeTableView.rowFactoryProperty();
245 }
246
247 /** {@inheritDoc} */
248 @Override protected ObjectProperty<Node> placeholderProperty() {
249 return treeTableView.placeholderProperty();
250 }
251
252 /** {@inheritDoc} */
253 @Override protected ObjectProperty<ObservableList<TreeItem<S>>> itemsProperty() {
254 return tableBackingListProperty;
255 }
256
257 /** {@inheritDoc} */
258 @Override protected ObservableList<TreeTableColumn<S,?>> getColumns() {
259 return treeTableView.getColumns();
260 }
261
262 /** {@inheritDoc} */
263 @Override protected BooleanProperty tableMenuButtonVisibleProperty() {
264 return treeTableView.tableMenuButtonVisibleProperty();
265 }
266
267 /** {@inheritDoc} */
268 @Override protected ObjectProperty<Callback<ResizeFeaturesBase, Boolean>> columnResizePolicyProperty() {
269 // TODO Ugly!
270 return (ObjectProperty<Callback<ResizeFeaturesBase, Boolean>>) (Object) treeTableView.columnResizePolicyProperty();
271 }
272
273 /** {@inheritDoc} */
274 @Override protected ObservableList<TreeTableColumn<S,?>> getSortOrder() {
275 return treeTableView.getSortOrder();
276 }
277
278 @Override protected boolean resizeColumn(TreeTableColumn<S,?> tc, double delta) {
279 return treeTableView.resizeColumn(tc, delta);
280 }
281
282 @Override protected void edit(int index, TreeTableColumn<S, ?> column) {
283 treeTableView.edit(index, column);
284 }
285
286 /*
287 * FIXME: Naive implementation ahead
288 * Attempts to resize column based on the pref width of all items contained
289 * in this column. This can be potentially very expensive if the number of
290 * rows is large.
291 */
292 @Override protected void resizeColumnToFitContent(TreeTableColumn<S,?> tc, int maxRows) {
293 final TreeTableColumn col = tc;
294 List<?> items = itemsProperty().get();
295 if (items == null || items.isEmpty()) return;
296
297 Callback cellFactory = col.getCellFactory();
298 if (cellFactory == null) return;
299
300 TreeTableCell<S,?> cell = (TreeTableCell) cellFactory.call(col);
301 if (cell == null) return;
302
303 // set this property to tell the TableCell we want to know its actual
304 // preferred width, not the width of the associated TableColumnBase
305 cell.getProperties().put(TableCellSkin.DEFER_TO_PARENT_PREF_WIDTH, Boolean.TRUE);
306
307 // determine cell padding
308 double padding = 10;
309 Node n = cell.getSkin() == null ? null : cell.getSkin().getNode();
310 if (n instanceof Region) {
311 Region r = (Region) n;
312 padding = r.snappedLeftInset() + r.snappedRightInset();
313 }
314
315 TreeTableRow<S> treeTableRow = new TreeTableRow<>();
316 treeTableRow.updateTreeTableView(treeTableView);
317
318 int rows = maxRows == -1 ? items.size() : Math.min(items.size(), maxRows);
319 double maxWidth = 0;
320 for (int row = 0; row < rows; row++) {
321 treeTableRow.updateIndex(row);
322 treeTableRow.updateTreeItem(treeTableView.getTreeItem(row));
323
324 cell.updateTreeTableColumn(col);
325 cell.updateTreeTableView(treeTableView);
326 cell.updateTreeTableRow(treeTableRow);
327 cell.updateIndex(row);
328
329 if ((cell.getText() != null && !cell.getText().isEmpty()) || cell.getGraphic() != null) {
330 getChildren().add(cell);
331 cell.applyCss();
332
333 double w = cell.prefWidth(-1);
334
335 maxWidth = Math.max(maxWidth, w);
342
343 // RT-36855 - take into account the column header text / graphic widths.
344 // Magic 10 is to allow for sort arrow to appear without text truncation.
345 TableColumnHeader header = getTableHeaderRow().getColumnHeaderFor(tc);
346 double headerTextWidth = Utils.computeTextWidth(header.label.getFont(), tc.getText(), -1);
347 Node graphic = header.label.getGraphic();
348 double headerGraphicWidth = graphic == null ? 0 : graphic.prefWidth(-1) + header.label.getGraphicTextGap();
349 double headerWidth = headerTextWidth + headerGraphicWidth + 10 + header.snappedLeftInset() + header.snappedRightInset();
350 maxWidth = Math.max(maxWidth, headerWidth);
351
352 // RT-23486
353 maxWidth += padding;
354 if(treeTableView.getColumnResizePolicy() == TreeTableView.CONSTRAINED_RESIZE_POLICY) {
355 maxWidth = Math.max(maxWidth, col.getWidth());
356 }
357
358 col.impl_setWidth(maxWidth);
359 }
360
361 /** {@inheritDoc} */
362 @Override public int getItemCount() {
363 return treeTableView.getExpandedItemCount();
364 }
365
366 /** {@inheritDoc} */
367 @Override public TreeTableRow<S> createCell() {
368 TreeTableRow<S> cell;
369
370 if (treeTableView.getRowFactory() != null) {
371 cell = treeTableView.getRowFactory().call(treeTableView);
372 } else {
373 cell = new TreeTableRow<S>();
374 }
375
376 // If there is no disclosure node, then add one of my own
377 if (cell.getDisclosureNode() == null) {
378 final StackPane disclosureNode = new StackPane();
379 disclosureNode.getStyleClass().setAll("tree-disclosure-node");
380 disclosureNode.setMouseTransparent(true);
381
382 final StackPane disclosureNodeArrow = new StackPane();
383 disclosureNodeArrow.getStyleClass().setAll("arrow");
384 disclosureNode.getChildren().add(disclosureNodeArrow);
385
386 cell.setDisclosureNode(disclosureNode);
387 }
388
389 cell.updateTreeTableView(treeTableView);
390 return cell;
391 }
392
393 @Override protected void horizontalScroll() {
394 super.horizontalScroll();
395 if (getSkinnable().getFixedCellSize() > 0) {
396 flow.requestCellLayout();
397 }
398 }
399
400 @Override
401 protected Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... parameters) {
402 switch (attribute) {
403 case ROW_AT_INDEX: {
404 final int rowIndex = (Integer)parameters[0];
405 return rowIndex < 0 ? null : flow.getPrivateCell(rowIndex);
406 }
407 case SELECTED_ITEMS: {
408 List<Node> selection = new ArrayList<>();
409 TreeTableView.TreeTableViewSelectionModel<S> sm = getSkinnable().getSelectionModel();
410 for (TreeTablePosition<S,?> pos : sm.getSelectedCells()) {
411 TreeTableRow<S> row = flow.getPrivateCell(pos.getRow());
412 if (row != null) selection.add(row);
413 }
414 return FXCollections.observableArrayList(selection);
415 }
416 case FOCUS_ITEM: // TableViewSkinBase
417 case CELL_AT_ROW_COLUMN: // TableViewSkinBase
418 case COLUMN_AT_INDEX: // TableViewSkinBase
419 case HEADER: // TableViewSkinBase
420 case VERTICAL_SCROLLBAR: // TableViewSkinBase
421 case HORIZONTAL_SCROLLBAR: // TableViewSkinBase
422 default: return super.queryAccessibleAttribute(attribute, parameters);
423 }
424 }
425
426 @Override
427 protected void executeAccessibleAction(AccessibleAction action, Object... parameters) {
428 switch (action) {
429 case SHOW_ITEM: {
430 Node item = (Node)parameters[0];
431 if (item instanceof TreeTableCell) {
432 @SuppressWarnings("unchecked")
433 TreeTableCell<S, ?> cell = (TreeTableCell<S, ?>)item;
434 flow.show(cell.getIndex());
435 }
436 break;
437 }
438 case SET_SELECTED_ITEMS: {
439 @SuppressWarnings("unchecked")
440 ObservableList<Node> items = (ObservableList<Node>)parameters[0];
441 if (items != null) {
442 TreeTableView.TreeTableViewSelectionModel<S> sm = getSkinnable().getSelectionModel();
443 if (sm != null) {
444 sm.clearSelection();
445 for (Node item : items) {
446 if (item instanceof TreeTableCell) {
447 @SuppressWarnings("unchecked")
448 TreeTableCell<S, ?> cell = (TreeTableCell<S, ?>)item;
449 sm.select(cell.getIndex(), cell.getTableColumn());
450 }
451 }
452 }
453 }
454 break;
455 }
456 default: super.executeAccessibleAction(action, parameters);
457 }
458 }
459
460 /***************************************************************************
461 * *
462 * Layout *
463 * *
464 **************************************************************************/
465
466
467
468
469 /***************************************************************************
470 * *
471 * Private methods *
472 * *
473 **************************************************************************/
474
475 @Override protected void updateRowCount() {
476 updatePlaceholderRegionVisibility();
477
478 tableBackingList.resetSize();
479
480 int oldCount = flow.getCellCount();
481 int newCount = getItemCount();
482
483 // if this is not called even when the count is the same, we get a
484 // memory leak in VirtualFlow.sheet.children. This can probably be
485 // optimised in the future when time permits.
486 flow.setCellCount(newCount);
487
488 if (forceCellRecreate) {
489 needCellsRecreated = true;
490 forceCellRecreate = false;
491 } else if (newCount != oldCount) {
492 needCellsRebuilt = true;
493 } else {
494 needCellsReconfigured = true;
495 }
496 }
497
498 /**
499 * A simple read only list structure that maps into the TreeTableView tree
500 * structure.
501 */
502 private static class TreeTableViewBackingList<S> extends ReadOnlyUnbackedObservableList<TreeItem<S>> {
503 private final TreeTableView<S> treeTable;
504
505 private int size = -1;
506
507 TreeTableViewBackingList(TreeTableView<S> treeTable) {
508 this.treeTable = treeTable;
509 }
510
511 void resetSize() {
512 int oldSize = size;
513 size = -1;
514
515 // TODO we can certainly make this better....but it may not really matter
516 callObservers(new NonIterableChange.GenericAddRemoveChange<TreeItem<S>>(
517 0, oldSize, FXCollections.<TreeItem<S>>emptyObservableList(), this));
518 }
519
520 @Override public TreeItem<S> get(int i) {
521 return treeTable.getTreeItem(i);
522 }
523
524 @Override public int size() {
525 if (size == -1) {
526 size = treeTable.getExpandedItemCount();
527 }
528 return size;
529 }
530 }
531 }
|
1 /*
2 * Copyright (c) 2012, 2015, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 package javafx.scene.control.skin;
27
28 import com.sun.javafx.collections.NonIterableChange;
29 import com.sun.javafx.scene.control.Properties;
30 import com.sun.javafx.scene.control.ReadOnlyUnbackedObservableList;
31
32 import com.sun.javafx.scene.control.behavior.BehaviorBase;
33 import com.sun.javafx.scene.control.skin.Utils;
34 import javafx.event.WeakEventHandler;
35 import javafx.scene.control.*;
36
37 import com.sun.javafx.scene.control.behavior.TreeTableViewBehavior;
38
39 import java.lang.ref.WeakReference;
40 import java.util.ArrayList;
41 import java.util.List;
42
43 import javafx.beans.property.BooleanProperty;
44 import javafx.beans.property.ObjectProperty;
45 import javafx.beans.property.SimpleObjectProperty;
46 import javafx.collections.FXCollections;
47 import javafx.collections.ObservableList;
48 import javafx.event.EventHandler;
49 import javafx.event.EventType;
50 import javafx.scene.AccessibleAction;
51 import javafx.scene.AccessibleAttribute;
52 import javafx.scene.Node;
53 import javafx.scene.control.TreeItem.TreeModificationEvent;
54 import javafx.scene.input.MouseEvent;
55 import javafx.scene.layout.Region;
56 import javafx.scene.layout.StackPane;
57 import javafx.util.Callback;
58
59 /**
60 * Default skin implementation for the {@link TreeTableView} control.
61 *
62 * @see TreeTableView
63 * @since 9
64 */
65 public class TreeTableViewSkin<T> extends TableViewSkinBase<T, TreeItem<T>, TreeTableView<T>, TreeTableRow<T>, TreeTableColumn<T,?>> {
66
67 /***************************************************************************
68 * *
69 * Private Fields *
70 * *
71 **************************************************************************/
72
73 private TreeTableViewBackingList<T> tableBackingList;
74 private ObjectProperty<ObservableList<TreeItem<T>>> tableBackingListProperty;
75 private WeakReference<TreeItem<T>> weakRootRef;
76 private final TreeTableViewBehavior<T> behavior;
77
78
79
80 /***************************************************************************
81 * *
82 * Listeners *
83 * *
84 **************************************************************************/
85
86 private EventHandler<TreeItem.TreeModificationEvent<T>> rootListener = e -> {
87 if (e.wasAdded() && e.wasRemoved() && e.getAddedSize() == e.getRemovedSize()) {
88 // Fix for RT-14842, where the children of a TreeItem were changing,
89 // but because the overall item count was staying the same, there was
90 // no event being fired to the skin to be informed that the items
91 // had changed. So, here we just watch for the case where the number
92 // of items being added is equal to the number of items being removed.
93 rowCountDirty = true;
94 getSkinnable().requestLayout();
95 } else if (e.getEventType().equals(TreeItem.valueChangedEvent())) {
96 // Fix for RT-14971 and RT-15338.
97 needCellsRebuilt = true;
98 getSkinnable().requestLayout();
99 } else {
100 // Fix for RT-20090. We are checking to see if the event coming
101 // from the TreeItem root is an event where the count has changed.
102 EventType<?> eventType = e.getEventType();
103 while (eventType != null) {
104 if (eventType.equals(TreeItem.<T>expandedItemCountChangeEvent())) {
105 rowCountDirty = true;
106 getSkinnable().requestLayout();
107 break;
108 }
109 eventType = eventType.getSuperType();
110 }
111 }
112
113 // fix for RT-37853
114 getSkinnable().edit(-1, null);
115 };
116
117 private WeakEventHandler<TreeModificationEvent<T>> weakRootListener;
118
119
120
121 /***************************************************************************
122 * *
123 * Constructors *
124 * *
125 **************************************************************************/
126
127 /**
128 * Creates a new TreeTableViewSkin instance, installing the necessary child
129 * nodes into the Control {@link Control#getChildren() children} list, as
130 * well as the necessary input mappings for handling key, mouse, etc events.
131 *
132 * @param control The control that this skin should be installed onto.
133 */
134 public TreeTableViewSkin(final TreeTableView<T> control) {
135 super(control);
136
137 // install default input map for the TreeTableView control
138 behavior = new TreeTableViewBehavior<>(control);
139 // control.setInputMap(behavior.getInputMap());
140
141 flow.setFixedCellSize(control.getFixedCellSize());
142 flow.setCellFactory(flow -> createCell());
143
144 setRoot(getSkinnable().getRoot());
145
146 EventHandler<MouseEvent> ml = event -> {
147 // RT-15127: cancel editing on scroll. This is a bit extreme
148 // (we are cancelling editing on touching the scrollbars).
149 // This can be improved at a later date.
150 if (control.getEditingCell() != null) {
151 control.edit(-1, null);
152 }
153
154 // This ensures that the table maintains the focus, even when the vbar
155 // and hbar controls inside the flow are clicked. Without this, the
156 // focus border will not be shown when the user interacts with the
157 // scrollbars, and more importantly, keyboard navigation won't be
158 // available to the user.
159 if (control.isFocusTraversable()) {
160 control.requestFocus();
161 }
162 };
163 flow.getVbar().addEventFilter(MouseEvent.MOUSE_PRESSED, ml);
164 flow.getHbar().addEventFilter(MouseEvent.MOUSE_PRESSED, ml);
165
166 // init the behavior 'closures'
167 behavior.setOnFocusPreviousRow(() -> onFocusPreviousCell());
168 behavior.setOnFocusNextRow(() -> onFocusNextCell());
169 behavior.setOnMoveToFirstCell(() -> onMoveToFirstCell());
170 behavior.setOnMoveToLastCell(() -> onMoveToLastCell());
171 behavior.setOnScrollPageDown(isFocusDriven -> onScrollPageDown(isFocusDriven));
172 behavior.setOnScrollPageUp(isFocusDriven -> onScrollPageUp(isFocusDriven));
173 behavior.setOnSelectPreviousRow(() -> onSelectPreviousCell());
174 behavior.setOnSelectNextRow(() -> onSelectNextCell());
175 behavior.setOnSelectLeftCell(() -> onSelectLeftCell());
176 behavior.setOnSelectRightCell(() -> onSelectRightCell());
177
178 registerChangeListener(control.rootProperty(), e -> {
179 // fix for RT-37853
180 getSkinnable().edit(-1, null);
181
182 setRoot(getSkinnable().getRoot());
183 });
184 registerChangeListener(control.showRootProperty(), e -> {
185 // if we turn off showing the root, then we must ensure the root
186 // is expanded - otherwise we end up with no visible items in
187 // the tree.
188 if (! getSkinnable().isShowRoot() && getRoot() != null) {
189 getRoot().setExpanded(true);
190 }
191 // update the item count in the flow and behavior instances
192 updateRowCount();
193 });
194 registerChangeListener(control.rowFactoryProperty(), e -> flow.recreateCells());
195 registerChangeListener(control.expandedItemCountProperty(), e -> rowCountDirty = true);
196 registerChangeListener(control.fixedCellSizeProperty(), e -> flow.setFixedCellSize(getSkinnable().getFixedCellSize()));
197 }
198
199
200
201 /***************************************************************************
202 * *
203 * Public API *
204 * *
205 **************************************************************************/
206
207 /** {@inheritDoc} */
208 @Override public void dispose() {
209 super.dispose();
210
211 if (behavior != null) {
212 behavior.dispose();
213 }
214 }
215
216 /** {@inheritDoc} */
217 @Override protected Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... parameters) {
218 switch (attribute) {
219 case ROW_AT_INDEX: {
220 final int rowIndex = (Integer)parameters[0];
221 return rowIndex < 0 ? null : flow.getPrivateCell(rowIndex);
222 }
223 case SELECTED_ITEMS: {
224 List<Node> selection = new ArrayList<>();
225 TreeTableView.TreeTableViewSelectionModel<T> sm = getSkinnable().getSelectionModel();
226 for (TreeTablePosition<T,?> pos : sm.getSelectedCells()) {
227 TreeTableRow<T> row = flow.getPrivateCell(pos.getRow());
228 if (row != null) selection.add(row);
229 }
230 return FXCollections.observableArrayList(selection);
231 }
232 case FOCUS_ITEM: // TableViewSkinBase
233 case CELL_AT_ROW_COLUMN: // TableViewSkinBase
234 case COLUMN_AT_INDEX: // TableViewSkinBase
235 case HEADER: // TableViewSkinBase
236 case VERTICAL_SCROLLBAR: // TableViewSkinBase
237 case HORIZONTAL_SCROLLBAR: // TableViewSkinBase
238 default: return super.queryAccessibleAttribute(attribute, parameters);
239 }
240 }
241
242 @Override
243 protected void executeAccessibleAction(AccessibleAction action, Object... parameters) {
244 switch (action) {
245 case SHOW_ITEM: {
246 Node item = (Node)parameters[0];
247 if (item instanceof TreeTableCell) {
248 @SuppressWarnings("unchecked")
249 TreeTableCell<T, ?> cell = (TreeTableCell<T, ?>)item;
250 flow.scrollTo(cell.getIndex());
251 }
252 break;
253 }
254 case SET_SELECTED_ITEMS: {
255 @SuppressWarnings("unchecked")
256 ObservableList<Node> items = (ObservableList<Node>)parameters[0];
257 if (items != null) {
258 TreeTableView.TreeTableViewSelectionModel<T> sm = getSkinnable().getSelectionModel();
259 if (sm != null) {
260 sm.clearSelection();
261 for (Node item : items) {
262 if (item instanceof TreeTableCell) {
263 @SuppressWarnings("unchecked")
264 TreeTableCell<T, ?> cell = (TreeTableCell<T, ?>)item;
265 sm.select(cell.getIndex(), cell.getTableColumn());
266 }
267 }
268 }
269 }
270 break;
271 }
272 default: super.executeAccessibleAction(action, parameters);
273 }
274 }
275
276
277
278 /***************************************************************************
279 * *
280 * Private methods *
281 * *
282 **************************************************************************/
283
284 /** {@inheritDoc} */
285 private TreeTableRow<T> createCell() {
286 TreeTableRow<T> cell;
287
288 TreeTableView<T> treeTableView = getSkinnable();
289 if (treeTableView.getRowFactory() != null) {
290 cell = treeTableView.getRowFactory().call(treeTableView);
291 } else {
292 cell = new TreeTableRow<T>();
293 }
294
295 // If there is no disclosure node, then add one of my own
296 if (cell.getDisclosureNode() == null) {
297 final StackPane disclosureNode = new StackPane();
298 disclosureNode.getStyleClass().setAll("tree-disclosure-node");
299 disclosureNode.setMouseTransparent(true);
300
301 final StackPane disclosureNodeArrow = new StackPane();
302 disclosureNodeArrow.getStyleClass().setAll("arrow");
303 disclosureNode.getChildren().add(disclosureNodeArrow);
304
305 cell.setDisclosureNode(disclosureNode);
306 }
307
308 cell.updateTreeTableView(treeTableView);
309 return cell;
310 }
311
312 private TreeItem<T> getRoot() {
313 return weakRootRef == null ? null : weakRootRef.get();
314 }
315 private void setRoot(TreeItem<T> newRoot) {
316 if (getRoot() != null && weakRootListener != null) {
317 getRoot().removeEventHandler(TreeItem.<T>treeNotificationEvent(), weakRootListener);
318 }
319 weakRootRef = new WeakReference<>(newRoot);
320 if (getRoot() != null) {
321 weakRootListener = new WeakEventHandler<>(rootListener);
322 getRoot().addEventHandler(TreeItem.<T>treeNotificationEvent(), weakRootListener);
323 }
324
325 updateRowCount();
326 }
327
328 /** {@inheritDoc} */
329 @Override ObservableList<TreeTableColumn<T, ?>> getVisibleLeafColumns() {
330 return getSkinnable().getVisibleLeafColumns();
331 }
332
333 @Override int getVisibleLeafIndex(TreeTableColumn<T,?> tc) {
334 return getSkinnable().getVisibleLeafIndex(tc);
335 }
336
337 @Override TreeTableColumn<T,?> getVisibleLeafColumn(int col) {
338 return getSkinnable().getVisibleLeafColumn(col);
339 }
340
341 /** {@inheritDoc} */
342 @Override TreeTableView.TreeTableViewFocusModel<T> getFocusModel() {
343 return getSkinnable().getFocusModel();
344 }
345
346 /** {@inheritDoc} */
347 @Override TreeTablePosition<T, ?> getFocusedCell() {
348 return getSkinnable().getFocusModel().getFocusedCell();
349 }
350
351 /** {@inheritDoc} */
352 @Override TableSelectionModel<TreeItem<T>> getSelectionModel() {
353 return getSkinnable().getSelectionModel();
354 }
355
356 /** {@inheritDoc} */
357 @Override ObjectProperty<Callback<TreeTableView<T>, TreeTableRow<T>>> rowFactoryProperty() {
358 return getSkinnable().rowFactoryProperty();
359 }
360
361 /** {@inheritDoc} */
362 @Override ObjectProperty<Node> placeholderProperty() {
363 return getSkinnable().placeholderProperty();
364 }
365
366 /** {@inheritDoc} */
367 @Override ObjectProperty<ObservableList<TreeItem<T>>> itemsProperty() {
368 if (tableBackingListProperty == null) {
369 this.tableBackingList = new TreeTableViewBackingList<>(getSkinnable());
370 this.tableBackingListProperty = new SimpleObjectProperty<>(tableBackingList);
371 }
372 return tableBackingListProperty;
373 }
374
375 /** {@inheritDoc} */
376 @Override ObservableList<TreeTableColumn<T,?>> getColumns() {
377 return getSkinnable().getColumns();
378 }
379
380 /** {@inheritDoc} */
381 @Override BooleanProperty tableMenuButtonVisibleProperty() {
382 return getSkinnable().tableMenuButtonVisibleProperty();
383 }
384
385 /** {@inheritDoc} */
386 @Override ObjectProperty<Callback<ResizeFeaturesBase, Boolean>> columnResizePolicyProperty() {
387 return (ObjectProperty<Callback<ResizeFeaturesBase, Boolean>>) (Object) getSkinnable().columnResizePolicyProperty();
388 }
389
390 /** {@inheritDoc} */
391 @Override ObservableList<TreeTableColumn<T,?>> getSortOrder() {
392 return getSkinnable().getSortOrder();
393 }
394
395 @Override boolean resizeColumn(TreeTableColumn<T,?> tc, double delta) {
396 return getSkinnable().resizeColumn(tc, delta);
397 }
398
399 /*
400 * FIXME: Naive implementation ahead
401 * Attempts to resize column based on the pref width of all items contained
402 * in this column. This can be potentially very expensive if the number of
403 * rows is large.
404 */
405 @Override void resizeColumnToFitContent(TreeTableColumn<T,?> tc, int maxRows) {
406 final TreeTableColumn col = tc;
407 List<?> items = itemsProperty().get();
408 if (items == null || items.isEmpty()) return;
409
410 Callback cellFactory = col.getCellFactory();
411 if (cellFactory == null) return;
412
413 TreeTableCell<T,?> cell = (TreeTableCell) cellFactory.call(col);
414 if (cell == null) return;
415
416 // set this property to tell the TableCell we want to know its actual
417 // preferred width, not the width of the associated TableColumnBase
418 cell.getProperties().put(Properties.DEFER_TO_PARENT_PREF_WIDTH, Boolean.TRUE);
419
420 // determine cell padding
421 double padding = 10;
422 Node n = cell.getSkin() == null ? null : cell.getSkin().getNode();
423 if (n instanceof Region) {
424 Region r = (Region) n;
425 padding = r.snappedLeftInset() + r.snappedRightInset();
426 }
427
428 final TreeTableView<T> treeTableView = getSkinnable();
429
430 TreeTableRow<T> treeTableRow = new TreeTableRow<>();
431 treeTableRow.updateTreeTableView(treeTableView);
432
433 int rows = maxRows == -1 ? items.size() : Math.min(items.size(), maxRows);
434 double maxWidth = 0;
435 for (int row = 0; row < rows; row++) {
436 treeTableRow.updateIndex(row);
437 treeTableRow.updateTreeItem(treeTableView.getTreeItem(row));
438
439 cell.updateTreeTableColumn(col);
440 cell.updateTreeTableView(treeTableView);
441 cell.updateTreeTableRow(treeTableRow);
442 cell.updateIndex(row);
443
444 if ((cell.getText() != null && !cell.getText().isEmpty()) || cell.getGraphic() != null) {
445 getChildren().add(cell);
446 cell.applyCss();
447
448 double w = cell.prefWidth(-1);
449
450 maxWidth = Math.max(maxWidth, w);
457
458 // RT-36855 - take into account the column header text / graphic widths.
459 // Magic 10 is to allow for sort arrow to appear without text truncation.
460 TableColumnHeader header = getTableHeaderRow().getColumnHeaderFor(tc);
461 double headerTextWidth = Utils.computeTextWidth(header.label.getFont(), tc.getText(), -1);
462 Node graphic = header.label.getGraphic();
463 double headerGraphicWidth = graphic == null ? 0 : graphic.prefWidth(-1) + header.label.getGraphicTextGap();
464 double headerWidth = headerTextWidth + headerGraphicWidth + 10 + header.snappedLeftInset() + header.snappedRightInset();
465 maxWidth = Math.max(maxWidth, headerWidth);
466
467 // RT-23486
468 maxWidth += padding;
469 if(treeTableView.getColumnResizePolicy() == TreeTableView.CONSTRAINED_RESIZE_POLICY) {
470 maxWidth = Math.max(maxWidth, col.getWidth());
471 }
472
473 col.impl_setWidth(maxWidth);
474 }
475
476 /** {@inheritDoc} */
477 @Override int getItemCount() {
478 return getSkinnable().getExpandedItemCount();
479 }
480
481 /** {@inheritDoc} */
482 @Override void horizontalScroll() {
483 super.horizontalScroll();
484 if (getSkinnable().getFixedCellSize() > 0) {
485 flow.requestCellLayout();
486 }
487 }
488
489 /** {@inheritDoc} */
490 @Override void updateRowCount() {
491 updatePlaceholderRegionVisibility();
492
493 tableBackingList.resetSize();
494
495 int oldCount = flow.getCellCount();
496 int newCount = getItemCount();
497
498 // if this is not called even when the count is the same, we get a
499 // memory leak in VirtualFlow.sheet.children. This can probably be
500 // optimised in the future when time permits.
501 flow.setCellCount(newCount);
502
503 if (forceCellRecreate) {
504 needCellsRecreated = true;
505 forceCellRecreate = false;
506 } else if (newCount != oldCount) {
507 needCellsRebuilt = true;
508 } else {
509 needCellsReconfigured = true;
510 }
511 }
512
513
514
515 /***************************************************************************
516 * *
517 * Support classes *
518 * *
519 **************************************************************************/
520
521 /**
522 * A simple read only list structure that maps into the TreeTableView tree
523 * structure.
524 */
525 private static class TreeTableViewBackingList<T> extends ReadOnlyUnbackedObservableList<TreeItem<T>> {
526 private final TreeTableView<T> treeTable;
527
528 private int size = -1;
529
530 TreeTableViewBackingList(TreeTableView<T> treeTable) {
531 this.treeTable = treeTable;
532 }
533
534 void resetSize() {
535 int oldSize = size;
536 size = -1;
537
538 // TODO we can certainly make this better....but it may not really matter
539 callObservers(new NonIterableChange.GenericAddRemoveChange<TreeItem<T>>(
540 0, oldSize, FXCollections.<TreeItem<T>>emptyObservableList(), this));
541 }
542
543 @Override public TreeItem<T> get(int i) {
544 return treeTable.getTreeItem(i);
545 }
546
547 @Override public int size() {
548 if (size == -1) {
549 size = treeTable.getExpandedItemCount();
550 }
551 return size;
552 }
553 }
554 }
|