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.behavior;
  27 
  28 import static javafx.scene.input.KeyCode.*;
  29 
  30 import javafx.beans.value.ChangeListener;
  31 import javafx.beans.value.WeakChangeListener;
  32 import javafx.collections.ObservableList;
  33 import javafx.geometry.NodeOrientation;
  34 import javafx.scene.control.TableColumnBase;
  35 import javafx.scene.control.TableFocusModel;
  36 import javafx.scene.control.TablePositionBase;
  37 import javafx.scene.control.TableSelectionModel;
  38 import javafx.scene.control.TreeItem;
  39 import javafx.scene.control.TreeTableColumn;
  40 import javafx.scene.control.TreeTablePosition;
  41 import javafx.scene.control.TreeTableView;
  42 import javafx.scene.input.KeyEvent;
  43 import javafx.util.Callback;
  44 
  45 import java.util.ArrayList;
  46 import java.util.List;
  47 
  48 public class TreeTableViewBehavior<T> extends TableViewBehaviorBase<TreeTableView<T>, TreeItem<T>, TreeTableColumn<T, ?>> {
  49     
  50     /**************************************************************************
  51      *                                                                        *
  52      * Setup key bindings                                                     *
  53      *                                                                        *  
  54      *************************************************************************/
  55 
  56     protected static final List<KeyBinding> TREE_TABLE_VIEW_BINDINGS = new ArrayList<KeyBinding>();
  57     
  58     static {
  59         // Add these bindings at the front of the list, so they take precedence
  60         TREE_TABLE_VIEW_BINDINGS.add(new KeyBinding(LEFT, "CollapseRow"));
  61         TREE_TABLE_VIEW_BINDINGS.add(new KeyBinding(KP_LEFT, "CollapseRow"));
  62         TREE_TABLE_VIEW_BINDINGS.add(new KeyBinding(RIGHT, "ExpandRow"));
  63         TREE_TABLE_VIEW_BINDINGS.add(new KeyBinding(KP_RIGHT, "ExpandRow"));
  64 
  65         TREE_TABLE_VIEW_BINDINGS.add(new KeyBinding(MULTIPLY, "ExpandAll"));
  66         TREE_TABLE_VIEW_BINDINGS.add(new KeyBinding(ADD, "ExpandRow"));
  67         TREE_TABLE_VIEW_BINDINGS.add(new KeyBinding(SUBTRACT, "CollapseRow"));
  68 
  69         TREE_TABLE_VIEW_BINDINGS.addAll(TABLE_VIEW_BINDINGS);
  70     }
  71 
  72     @Override protected /*final*/ String matchActionForEvent(KeyEvent e) {
  73         String action = super.matchActionForEvent(e);
  74         if (getControl().getEffectiveNodeOrientation() == NodeOrientation.RIGHT_TO_LEFT) {
  75             // Rather than switching the result of the action lookup in this way, the preferred
  76             // way to do this according to the current architecture would be to hoist the
  77             // getEffectiveNodeOrientation call up into the key bindings, the same way that ListView
  78             // orientation (horizontal vs. vertical) is handled with the OrientedKeyBinding class.
  79             if ("CollapseRow".equals(action) && (e.getCode() == LEFT || e.getCode() == KP_LEFT)) {
  80                 action = "ExpandRow";
  81             } else if ("ExpandRow".equals(action) && (e.getCode() == RIGHT || e.getCode() == KP_RIGHT)) {
  82                 action = "CollapseRow";
  83             }
  84         }
  85         return action;
  86     }
  87 
  88     @Override protected void callAction(String name) {
  89         if ("ExpandRow".equals(name)) rightArrowPressed();
  90         else if ("CollapseRow".equals(name)) leftArrowPressed();
  91         else if ("ExpandAll".equals(name)) expandAll();
  92         else super.callAction(name);
  93     }
  94     
  95 
  96     
  97     /**************************************************************************
  98      *                                                                        *
  99      * Listeners                                                              *
 100      *                                                                        *  
 101      *************************************************************************/
 102     
 103     private final ChangeListener<TreeTableView.TreeTableViewSelectionModel<T>> selectionModelListener =
 104             (observable, oldValue, newValue) -> {
 105                 if (oldValue != null) {
 106                     oldValue.getSelectedCells().removeListener(weakSelectedCellsListener);
 107                 }
 108                 if (newValue != null) {
 109                     newValue.getSelectedCells().addListener(weakSelectedCellsListener);
 110                 }
 111             };
 112     
 113     private final WeakChangeListener<TreeTableView.TreeTableViewSelectionModel<T>> weakSelectionModelListener = 
 114             new WeakChangeListener<TreeTableView.TreeTableViewSelectionModel<T>>(selectionModelListener);
 115     
 116     
 117     
 118     /**************************************************************************
 119      *                                                                        *
 120      * Constructors                                                           *
 121      *                                                                        *  
 122      *************************************************************************/
 123 
 124     public TreeTableViewBehavior(TreeTableView<T>  control) {
 125         super(control, TREE_TABLE_VIEW_BINDINGS);
 126 
 127         // Fix for RT-16565
 128         control.selectionModelProperty().addListener(weakSelectionModelListener);
 129         if (getSelectionModel() != null) {
 130             control.getSelectionModel().getSelectedCells().addListener(selectedCellsListener);
 131         }
 132     }
 133 
 134     
 135     
 136     /**************************************************************************
 137      *                                                                        *
 138      * Implement TableViewBehaviorBase abstract methods                       *
 139      *                                                                        *  
 140      *************************************************************************/
 141     
 142     /** {@inheritDoc}  */
 143     @Override protected int getItemCount() {
 144         return getControl().getExpandedItemCount();
 145     }
 146 
 147     /** {@inheritDoc}  */
 148     @Override protected TableFocusModel getFocusModel() {
 149         return getControl().getFocusModel();
 150     }
 151 
 152     /** {@inheritDoc}  */
 153     @Override protected TableSelectionModel<TreeItem<T>> getSelectionModel() {
 154         return getControl().getSelectionModel();
 155     }
 156 
 157     /** {@inheritDoc}  */
 158     @Override protected ObservableList<TreeTablePosition<T,?>> getSelectedCells() {
 159         return getControl().getSelectionModel().getSelectedCells();
 160     }
 161 
 162     /** {@inheritDoc}  */
 163     @Override protected TablePositionBase getFocusedCell() {
 164         return getControl().getFocusModel().getFocusedCell();
 165     }
 166 
 167     /** {@inheritDoc}  */
 168     @Override protected int getVisibleLeafIndex(TableColumnBase tc) {
 169         return getControl().getVisibleLeafIndex((TreeTableColumn)tc);
 170     }
 171 
 172     /** {@inheritDoc}  */
 173     @Override protected TreeTableColumn getVisibleLeafColumn(int index) {
 174         return getControl().getVisibleLeafColumn(index);
 175     }
 176 
 177     /** {@inheritDoc}  */
 178     @Override protected void editCell(int row, TableColumnBase tc) {
 179         getControl().edit(row, (TreeTableColumn)tc);
 180     }
 181 
 182     /** {@inheritDoc}  */
 183     @Override protected ObservableList<TreeTableColumn<T,?>> getVisibleLeafColumns() {
 184         return getControl().getVisibleLeafColumns();
 185     }
 186 
 187     /** {@inheritDoc}  */
 188     @Override protected TablePositionBase<TreeTableColumn<T, ?>> 
 189             getTablePosition(int row, TableColumnBase<TreeItem<T>, ?> tc) {
 190         return new TreeTablePosition(getControl(), row, (TreeTableColumn)tc);
 191     }
 192 
 193 
 194 
 195     /**************************************************************************
 196      *                                                                        *
 197      * Modify TableViewBehaviorBase behavior                                  *
 198      *                                                                        *
 199      *************************************************************************/
 200 
 201     /** {@inheritDoc} */
 202     @Override protected void selectAllToFocus(boolean setAnchorToFocusIndex) {
 203         // Fix for RT-31241
 204         if (getControl().getEditingCell() != null) return;
 205 
 206         super.selectAllToFocus(setAnchorToFocusIndex);
 207     }
 208 
 209     /**************************************************************************
 210      *                                                                        *
 211      * Tree-related implementation                                            *
 212      *                                                                        *  
 213      *************************************************************************/
 214     
 215     /**
 216      * The next methods handle the left/right arrow input differently depending
 217      * on whether we are in row or cell selection.
 218      */
 219     private void rightArrowPressed() {
 220         if (getControl().getSelectionModel().isCellSelectionEnabled()) {
 221             if (isRTL()) {
 222                 selectLeftCell();
 223             } else {
 224                 selectRightCell();
 225             }
 226         } else {
 227             expandRow();
 228         }
 229     }
 230     
 231     private void leftArrowPressed() {
 232         if (getControl().getSelectionModel().isCellSelectionEnabled()) {
 233             if (isRTL()) {
 234                 selectRightCell();
 235             } else {
 236                 selectLeftCell();
 237             }
 238         } else {
 239             collapseRow();
 240         }
 241     }
 242     
 243     private void expandRow() {
 244         Callback<TreeItem<T>, Integer> getIndex = p -> getControl().getRow(p);
 245         TreeViewBehavior.expandRow(getControl().getSelectionModel(), getIndex);
 246     }
 247     
 248     private void expandAll() {
 249         TreeViewBehavior.expandAll(getControl().getRoot());
 250     }
 251     
 252     private void collapseRow() {
 253         TreeTableView<T> control = getControl();
 254         TreeViewBehavior.collapseRow(control.getSelectionModel(), control.getRoot(), control.isShowRoot());
 255     }
 256 }