1 /*
   2  * Copyright (c) 2011, 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 com.sun.javafx.scene.control.SizeLimitedList;
  29 import javafx.collections.ListChangeListener;
  30 import javafx.collections.ObservableList;
  31 import javafx.collections.WeakListChangeListener;
  32 import javafx.geometry.NodeOrientation;
  33 import javafx.scene.control.*;
  34 import javafx.scene.input.KeyEvent;
  35 import javafx.scene.input.MouseEvent;
  36 import javafx.util.Callback;
  37 import java.util.ArrayList;
  38 import java.util.List;
  39 import com.sun.javafx.PlatformUtil;
  40 import static javafx.scene.input.KeyCode.A;
  41 import static javafx.scene.input.KeyCode.DOWN;
  42 import static javafx.scene.input.KeyCode.END;
  43 import static javafx.scene.input.KeyCode.ENTER;
  44 import static javafx.scene.input.KeyCode.ESCAPE;
  45 import static javafx.scene.input.KeyCode.F2;
  46 import static javafx.scene.input.KeyCode.HOME;
  47 import static javafx.scene.input.KeyCode.KP_DOWN;
  48 import static javafx.scene.input.KeyCode.KP_LEFT;
  49 import static javafx.scene.input.KeyCode.KP_RIGHT;
  50 import static javafx.scene.input.KeyCode.KP_UP;
  51 import static javafx.scene.input.KeyCode.LEFT;
  52 import static javafx.scene.input.KeyCode.PAGE_DOWN;
  53 import static javafx.scene.input.KeyCode.PAGE_UP;
  54 import static javafx.scene.input.KeyCode.RIGHT;
  55 import static javafx.scene.input.KeyCode.SPACE;
  56 import static javafx.scene.input.KeyCode.TAB;
  57 import static javafx.scene.input.KeyCode.UP;
  58 
  59 public abstract class TableViewBehaviorBase<C extends Control, T, TC extends TableColumnBase<T,?>> extends BehaviorBase<C> {
  60 
  61     /**************************************************************************
  62      *                                                                        *
  63      * Setup key bindings                                                     *
  64      *                                                                        *  
  65      *************************************************************************/
  66     protected static final List<KeyBinding> TABLE_VIEW_BINDINGS = new ArrayList<KeyBinding>();
  67 
  68     static {
  69         TABLE_VIEW_BINDINGS.add(new KeyBinding(TAB, "TraverseNext"));
  70         TABLE_VIEW_BINDINGS.add(new KeyBinding(TAB, "TraversePrevious").shift());
  71 
  72         TABLE_VIEW_BINDINGS.add(new KeyBinding(HOME, "SelectFirstRow"));
  73         TABLE_VIEW_BINDINGS.add(new KeyBinding(END, "SelectLastRow"));
  74         
  75         TABLE_VIEW_BINDINGS.add(new KeyBinding(PAGE_UP, "ScrollUp"));
  76         TABLE_VIEW_BINDINGS.add(new KeyBinding(PAGE_DOWN, "ScrollDown"));
  77 
  78         TABLE_VIEW_BINDINGS.add(new KeyBinding(LEFT, "SelectLeftCell"));
  79         TABLE_VIEW_BINDINGS.add(new KeyBinding(KP_LEFT, "SelectLeftCell"));
  80         TABLE_VIEW_BINDINGS.add(new KeyBinding(RIGHT, "SelectRightCell"));
  81         TABLE_VIEW_BINDINGS.add(new KeyBinding(KP_RIGHT, "SelectRightCell"));
  82 
  83         TABLE_VIEW_BINDINGS.add(new KeyBinding(UP, "SelectPreviousRow"));
  84         TABLE_VIEW_BINDINGS.add(new KeyBinding(KP_UP, "SelectPreviousRow"));
  85         TABLE_VIEW_BINDINGS.add(new KeyBinding(DOWN, "SelectNextRow"));
  86         TABLE_VIEW_BINDINGS.add(new KeyBinding(KP_DOWN, "SelectNextRow"));
  87 
  88         TABLE_VIEW_BINDINGS.add(new KeyBinding(LEFT, "TraverseLeft"));
  89         TABLE_VIEW_BINDINGS.add(new KeyBinding(KP_LEFT, "TraverseLeft"));
  90         TABLE_VIEW_BINDINGS.add(new KeyBinding(RIGHT, "SelectNextRow"));
  91         TABLE_VIEW_BINDINGS.add(new KeyBinding(KP_RIGHT, "SelectNextRow"));
  92         TABLE_VIEW_BINDINGS.add(new KeyBinding(UP, "TraverseUp"));
  93         TABLE_VIEW_BINDINGS.add(new KeyBinding(KP_UP, "TraverseUp"));
  94         TABLE_VIEW_BINDINGS.add(new KeyBinding(DOWN, "TraverseDown"));
  95         TABLE_VIEW_BINDINGS.add(new KeyBinding(KP_DOWN, "TraverseDown"));
  96 
  97         TABLE_VIEW_BINDINGS.add(new KeyBinding(HOME, "SelectAllToFirstRow").shift());
  98         TABLE_VIEW_BINDINGS.add(new KeyBinding(END, "SelectAllToLastRow").shift());
  99         TABLE_VIEW_BINDINGS.add(new KeyBinding(PAGE_UP, "SelectAllPageUp").shift());
 100         TABLE_VIEW_BINDINGS.add(new KeyBinding(PAGE_DOWN, "SelectAllPageDown").shift());
 101 
 102         TABLE_VIEW_BINDINGS.add(new KeyBinding(UP, "AlsoSelectPrevious").shift());
 103         TABLE_VIEW_BINDINGS.add(new KeyBinding(KP_UP, "AlsoSelectPrevious").shift());
 104         TABLE_VIEW_BINDINGS.add(new KeyBinding(DOWN, "AlsoSelectNext").shift());
 105         TABLE_VIEW_BINDINGS.add(new KeyBinding(KP_DOWN, "AlsoSelectNext").shift());
 106         
 107         TABLE_VIEW_BINDINGS.add(new KeyBinding(SPACE, "SelectAllToFocus").shift());
 108         TABLE_VIEW_BINDINGS.add(new KeyBinding(SPACE, "SelectAllToFocusAndSetAnchor").shortcut().shift());
 109 
 110 //        TABLE_VIEW_BINDINGS.add(new KeyBinding(UP, "AlsoSelectPreviousCell").shift());
 111 //        TABLE_VIEW_BINDINGS.add(new KeyBinding(KP_UP, "AlsoSelectPreviousCell").shift());
 112 //        TABLE_VIEW_BINDINGS.add(new KeyBinding(DOWN, "AlsoSelectNextCell").shift());
 113 //        TABLE_VIEW_BINDINGS.add(new KeyBinding(KP_DOWN, "AlsoSelectNextCell").shift());
 114         TABLE_VIEW_BINDINGS.add(new KeyBinding(LEFT, "AlsoSelectLeftCell").shift());
 115         TABLE_VIEW_BINDINGS.add(new KeyBinding(KP_LEFT, "AlsoSelectLeftCell").shift());
 116         TABLE_VIEW_BINDINGS.add(new KeyBinding(RIGHT, "AlsoSelectRightCell").shift());
 117         TABLE_VIEW_BINDINGS.add(new KeyBinding(KP_RIGHT, "AlsoSelectRightCell").shift());
 118 
 119         TABLE_VIEW_BINDINGS.add(new KeyBinding(UP, "FocusPreviousRow").shortcut());
 120         TABLE_VIEW_BINDINGS.add(new KeyBinding(DOWN, "FocusNextRow").shortcut());
 121         TABLE_VIEW_BINDINGS.add(new KeyBinding(RIGHT, "FocusRightCell").shortcut());
 122         TABLE_VIEW_BINDINGS.add(new KeyBinding(KP_RIGHT, "FocusRightCell").shortcut());
 123         TABLE_VIEW_BINDINGS.add(new KeyBinding(LEFT, "FocusLeftCell").shortcut());
 124         TABLE_VIEW_BINDINGS.add(new KeyBinding(KP_LEFT, "FocusLeftCell").shortcut());
 125         TABLE_VIEW_BINDINGS.add(new KeyBinding(A, "SelectAll").shortcut());
 126         TABLE_VIEW_BINDINGS.add(new KeyBinding(HOME, "FocusFirstRow").shortcut());
 127         TABLE_VIEW_BINDINGS.add(new KeyBinding(END, "FocusLastRow").shortcut());
 128         TABLE_VIEW_BINDINGS.add(new KeyBinding(PAGE_UP, "FocusPageUp").shortcut());
 129         TABLE_VIEW_BINDINGS.add(new KeyBinding(PAGE_DOWN, "FocusPageDown").shortcut());
 130 
 131         TABLE_VIEW_BINDINGS.add(new KeyBinding(UP, "DiscontinuousSelectPreviousRow").shortcut().shift());
 132         TABLE_VIEW_BINDINGS.add(new KeyBinding(DOWN, "DiscontinuousSelectNextRow").shortcut().shift());
 133         TABLE_VIEW_BINDINGS.add(new KeyBinding(LEFT, "DiscontinuousSelectPreviousColumn").shortcut().shift());
 134         TABLE_VIEW_BINDINGS.add(new KeyBinding(RIGHT, "DiscontinuousSelectNextColumn").shortcut().shift());
 135         TABLE_VIEW_BINDINGS.add(new KeyBinding(PAGE_UP, "DiscontinuousSelectPageUp").shortcut().shift());
 136         TABLE_VIEW_BINDINGS.add(new KeyBinding(PAGE_DOWN, "DiscontinuousSelectPageDown").shortcut().shift());
 137         TABLE_VIEW_BINDINGS.add(new KeyBinding(HOME, "DiscontinuousSelectAllToFirstRow").shortcut().shift());
 138         TABLE_VIEW_BINDINGS.add(new KeyBinding(END, "DiscontinuousSelectAllToLastRow").shortcut().shift());
 139         
 140         if (PlatformUtil.isMac()) {
 141             TABLE_VIEW_BINDINGS.add(new KeyBinding(SPACE, "toggleFocusOwnerSelection").ctrl().shortcut());
 142         } else {
 143             TABLE_VIEW_BINDINGS.add(new KeyBinding(SPACE, "toggleFocusOwnerSelection").ctrl());
 144         }
 145 
 146         TABLE_VIEW_BINDINGS.add(new KeyBinding(ENTER, "Activate"));
 147         TABLE_VIEW_BINDINGS.add(new KeyBinding(SPACE, "Activate"));
 148         TABLE_VIEW_BINDINGS.add(new KeyBinding(F2, "Activate"));
 149 //        TABLE_VIEW_BINDINGS.add(new KeyBinding(SPACE, "Activate").ctrl());
 150         
 151         TABLE_VIEW_BINDINGS.add(new KeyBinding(ESCAPE, "CancelEdit"));
 152     }
 153 
 154     @Override protected void callAction(String name) {
 155         boolean rtl = (getControl().getEffectiveNodeOrientation() == NodeOrientation.RIGHT_TO_LEFT);
 156 
 157         if ("SelectPreviousRow".equals(name)) selectPreviousRow();
 158         else if ("SelectNextRow".equals(name)) selectNextRow();
 159         else if ("SelectLeftCell".equals(name)) { if (rtl) selectRightCell(); else selectLeftCell(); }
 160         else if ("SelectRightCell".equals(name)) { if (rtl) selectLeftCell(); else selectRightCell(); }
 161         else if ("SelectFirstRow".equals(name)) selectFirstRow();
 162         else if ("SelectLastRow".equals(name)) selectLastRow();
 163         else if ("SelectAll".equals(name)) selectAll();
 164         else if ("SelectAllPageUp".equals(name)) selectAllPageUp();
 165         else if ("SelectAllPageDown".equals(name)) selectAllPageDown();
 166         else if ("SelectAllToFirstRow".equals(name)) selectAllToFirstRow();
 167         else if ("SelectAllToLastRow".equals(name)) selectAllToLastRow();
 168         else if ("AlsoSelectNext".equals(name)) alsoSelectNext();
 169         else if ("AlsoSelectPrevious".equals(name)) alsoSelectPrevious();
 170         else if ("AlsoSelectLeftCell".equals(name)) { if (rtl) alsoSelectRightCell(); else alsoSelectLeftCell(); }
 171         else if ("AlsoSelectRightCell".equals(name)) { if (rtl) alsoSelectLeftCell(); else alsoSelectRightCell(); }
 172         else if ("ClearSelection".equals(name)) clearSelection();
 173         else if ("ScrollUp".equals(name)) scrollUp();
 174         else if ("ScrollDown".equals(name)) scrollDown();
 175         else if ("FocusPreviousRow".equals(name)) focusPreviousRow();
 176         else if ("FocusNextRow".equals(name)) focusNextRow();
 177         else if ("FocusLeftCell".equals(name)) { if (rtl) focusRightCell(); else focusLeftCell(); }
 178         else if ("FocusRightCell".equals(name)) { if (rtl) focusLeftCell(); else focusRightCell(); }
 179         else if ("Activate".equals(name)) activate();
 180         else if ("CancelEdit".equals(name)) cancelEdit();
 181         else if ("FocusFirstRow".equals(name)) focusFirstRow();
 182         else if ("FocusLastRow".equals(name)) focusLastRow();
 183         else if ("toggleFocusOwnerSelection".equals(name)) toggleFocusOwnerSelection();
 184 
 185         else if ("SelectAllToFocus".equals(name)) selectAllToFocus(false);
 186         else if ("SelectAllToFocusAndSetAnchor".equals(name)) selectAllToFocus(true);
 187 
 188         else if ("FocusPageUp".equals(name)) focusPageUp();
 189         else if ("FocusPageDown".equals(name)) focusPageDown();
 190         else if ("DiscontinuousSelectNextRow".equals(name)) discontinuousSelectNextRow();
 191         else if ("DiscontinuousSelectPreviousRow".equals(name)) discontinuousSelectPreviousRow();
 192         else if ("DiscontinuousSelectNextColumn".equals(name)) { if (rtl) discontinuousSelectPreviousColumn(); else discontinuousSelectNextColumn(); }
 193         else if ("DiscontinuousSelectPreviousColumn".equals(name)) { if (rtl) discontinuousSelectNextColumn(); else discontinuousSelectPreviousColumn(); }
 194         else if ("DiscontinuousSelectPageUp".equals(name)) discontinuousSelectPageUp();
 195         else if ("DiscontinuousSelectPageDown".equals(name)) discontinuousSelectPageDown();
 196         else if ("DiscontinuousSelectAllToLastRow".equals(name)) discontinuousSelectAllToLastRow();
 197         else if ("DiscontinuousSelectAllToFirstRow".equals(name)) discontinuousSelectAllToFirstRow();
 198         else super.callAction(name);
 199     }
 200 
 201     @Override protected void callActionForEvent(KeyEvent e) {
 202         // RT-12751: we want to keep an eye on the user holding down the shift key, 
 203         // so that we know when they enter/leave multiple selection mode. This
 204         // changes what happens when certain key combinations are pressed.
 205         isShiftDown = e.getEventType() == KeyEvent.KEY_PRESSED && e.isShiftDown();
 206         isShortcutDown = e.getEventType() == KeyEvent.KEY_PRESSED && e.isShortcutDown();
 207         
 208         super.callActionForEvent(e);
 209     }
 210     
 211     
 212     
 213     /**************************************************************************
 214      *                                                                        *
 215      * Internal fields                                                        *
 216      *                                                                        *  
 217      *************************************************************************/
 218     
 219     protected boolean isShortcutDown = false;
 220     protected boolean isShiftDown = false;
 221     private boolean selectionPathDeviated = false;
 222     protected boolean selectionChanging = false;
 223 
 224     private final SizeLimitedList<TablePositionBase> selectionHistory = new SizeLimitedList<>(10);
 225 
 226     protected final ListChangeListener<TablePositionBase> selectedCellsListener = c -> {
 227         while (c.next()) {
 228             if (c.wasReplaced()) {
 229                 if (TreeTableCellBehavior.hasDefaultAnchor(getControl())) {
 230                     TreeTableCellBehavior.removeAnchor(getControl());
 231                 }
 232             }
 233 
 234             if (! c.wasAdded()) {
 235                 continue;
 236             }
 237 
 238             TableSelectionModel sm = getSelectionModel();
 239             if (sm == null) return;
 240 
 241             TablePositionBase anchor = getAnchor();
 242             boolean cellSelectionEnabled = sm.isCellSelectionEnabled();
 243 
 244             int addedSize = c.getAddedSize();
 245             List<TablePositionBase> addedSubList = (List<TablePositionBase>) c.getAddedSubList();
 246 
 247             for (TablePositionBase tpb : addedSubList) {
 248                 if (! selectionHistory.contains(tpb)) {
 249                     selectionHistory.add(tpb);
 250                 }
 251             }
 252 
 253             // newest selection
 254             if (addedSize > 0 && ! hasAnchor()) {
 255                 TablePositionBase tp = addedSubList.get(addedSize - 1);
 256                 setAnchor(tp);
 257             }
 258 
 259             if (anchor != null && cellSelectionEnabled && ! selectionPathDeviated) {
 260                 // check if the selection is on the same row or column,
 261                 // otherwise set selectionPathDeviated to true
 262                 for (int i = 0; i < addedSize; i++) {
 263                     TablePositionBase tp = addedSubList.get(i);
 264                     if (anchor.getRow() != -1 && tp.getRow() != anchor.getRow() && tp.getColumn() != anchor.getColumn()) {
 265                         setSelectionPathDeviated(true);
 266                         break;
 267                     }
 268                 }
 269             }
 270         }
 271     };
 272     
 273     protected final WeakListChangeListener<TablePositionBase> weakSelectedCellsListener = 
 274             new WeakListChangeListener<TablePositionBase>(selectedCellsListener);
 275     
 276     
 277 
 278     /**************************************************************************
 279      *                                                                        *
 280      * Constructors                                                           *
 281      *                                                                        *  
 282      *************************************************************************/
 283 
 284     public TableViewBehaviorBase(C control) {
 285         this(control, null);
 286     }
 287 
 288     public TableViewBehaviorBase(C control, List<KeyBinding> bindings) {
 289         super(control, bindings == null ? TABLE_VIEW_BINDINGS : bindings);
 290     }
 291 
 292     
 293     
 294     /**************************************************************************
 295      *                                                                        *
 296      * Abstract API                                                           *
 297      *                                                                        *  
 298      *************************************************************************/    
 299     
 300     /**
 301      * Call to record the current anchor position
 302      */
 303     protected void setAnchor(TablePositionBase tp) {
 304         TableCellBehaviorBase.setAnchor(getControl(), tp, false);
 305         setSelectionPathDeviated(false);
 306     }
 307     
 308     /**
 309      * Will return the current anchor position.
 310      */
 311     protected TablePositionBase getAnchor() {
 312         return TableCellBehaviorBase.getAnchor(getControl(), getFocusedCell());
 313     }
 314     
 315     /**
 316      * Returns true if there is an anchor set, and false if not anchor is set.
 317      */
 318     protected boolean hasAnchor() {
 319         return TableCellBehaviorBase.hasNonDefaultAnchor(getControl());
 320     }
 321     
 322     /**
 323      * Returns the number of items in the underlying data model.
 324      */
 325     protected abstract int getItemCount();
 326 
 327     /**
 328      * Returns the focus model for the underlying UI control (which must extend
 329      * from TableFocusModel).
 330      */
 331     protected abstract TableFocusModel getFocusModel();
 332     
 333     /**
 334      * Returns the selection model for the underlying UI control (which must extend
 335      * from TableSelectionModel).
 336      */
 337     protected abstract TableSelectionModel<T> getSelectionModel();
 338     
 339     /**
 340      * Returns an observable list of all cells that are currently selected in
 341      * the selection model of the underlying control.
 342      */
 343     protected abstract ObservableList<? extends TablePositionBase/*<C,TC>*/> getSelectedCells();
 344     
 345     /**
 346      * Returns the focused cell from the focus model of the underlying control.
 347      */
 348     protected abstract TablePositionBase getFocusedCell();
 349 
 350     /**
 351      * Returns the position of the given table column in the visible leaf columns
 352      * list of the underlying control.
 353      */
 354     protected abstract int getVisibleLeafIndex(TableColumnBase tc);
 355     
 356     /**
 357      * Returns the column at the given index in the visible leaf columns list of
 358      * the underlying control.
 359      */
 360     protected abstract TableColumnBase getVisibleLeafColumn(int index);
 361     
 362     /**
 363      * Begins the edit process in the underlying control for the given row/column
 364      * position.
 365      */
 366     protected abstract void editCell(int row, TableColumnBase tc);
 367     
 368     /**
 369      * Returns an observable list of all visible leaf columns in the underlying
 370      * control.
 371      */
 372     protected abstract ObservableList<? extends TableColumnBase> getVisibleLeafColumns();
 373 
 374     /**
 375      * Creates a TablePositionBase instance using the underlying controls
 376      * concrete implementation for the given row/column intersection.
 377      */
 378     protected abstract TablePositionBase<TC> getTablePosition(int row, TableColumnBase<T,?> tc);
 379     
 380     
 381     
 382     /**************************************************************************
 383      *                                                                        *
 384      * Public API                                                             *
 385      *                                                                        *  
 386      *************************************************************************/     
 387     
 388     /*
 389      * Anchor is created upon
 390      * - initial selection of an item (by mouse or keyboard)
 391      * 
 392      * Anchor is changed when you
 393      * - move the selection to an item by UP/DOWN/LEFT/RIGHT arrow keys
 394      * - select an item by mouse click
 395      * - add/remove an item to/from an existing selection by CTRL+SPACE shortcut
 396      * - add/remove an items to/from an existing selection by CTRL+mouse click
 397      * 
 398      * Note that if an item is removed from an existing selection by 
 399      * CTRL+SPACE/CTRL+mouse click, anchor still remains on this item even 
 400      * though it is not selected.
 401      * 
 402      * Anchor is NOT changed when you
 403      * - create linear multi-selection by SHIFT+UP/DOWN/LEFT/RIGHT arrow keys
 404      * - create linear multi-selection by SHIFT+SPACE arrow keys
 405      * - create linear multi-selection by SHIFT+mouse click
 406      * 
 407      * In case there is a discontinuous selection in the list, creating linear 
 408      * multi-selection between anchor and focused item will cancel the 
 409      * discontinuous selection. It means that only items that are located between
 410      * anchor and focused item will be selected. 
 411      */
 412     protected void setAnchor(int row, TableColumnBase col) {
 413         setAnchor(row == -1 && col == null ? null : getTablePosition(row, col));
 414     }
 415     
 416     private Callback<Boolean, Integer> onScrollPageUp;
 417     public void setOnScrollPageUp(Callback<Boolean, Integer> c) { onScrollPageUp = c; }
 418 
 419     private Callback<Boolean, Integer> onScrollPageDown;
 420     public void setOnScrollPageDown(Callback<Boolean, Integer> c) { onScrollPageDown = c; }
 421 
 422     private Runnable onFocusPreviousRow;
 423     public void setOnFocusPreviousRow(Runnable r) { onFocusPreviousRow = r; }
 424 
 425     private Runnable onFocusNextRow;
 426     public void setOnFocusNextRow(Runnable r) { onFocusNextRow = r; }
 427 
 428     private Runnable onSelectPreviousRow;
 429     public void setOnSelectPreviousRow(Runnable r) { onSelectPreviousRow = r; }
 430 
 431     private Runnable onSelectNextRow;
 432     public void setOnSelectNextRow(Runnable r) { onSelectNextRow = r; }
 433 
 434     private Runnable onMoveToFirstCell;
 435     public void setOnMoveToFirstCell(Runnable r) { onMoveToFirstCell = r; }
 436 
 437     private Runnable onMoveToLastCell;
 438     public void setOnMoveToLastCell(Runnable r) { onMoveToLastCell = r; }
 439 
 440     private Runnable onSelectRightCell;
 441     public void setOnSelectRightCell(Runnable r) { onSelectRightCell = r; }
 442 
 443     private Runnable onSelectLeftCell;
 444     public void setOnSelectLeftCell(Runnable r) { onSelectLeftCell = r; }
 445     
 446     @Override public void mousePressed(MouseEvent e) {
 447         super.mousePressed(e);
 448         
 449 //        // FIXME can't assume (yet) cells.get(0) is necessarily the lead cell
 450 //        ObservableList<? extends TablePositionBase> cells = getSelectedCells();
 451 //        setAnchor(cells.isEmpty() ? null : cells.get(0));
 452         
 453         if (!getControl().isFocused() && getControl().isFocusTraversable()) {
 454             getControl().requestFocus();
 455         }
 456     }
 457 
 458     protected boolean isRTL() {
 459         return (getControl().getEffectiveNodeOrientation() == NodeOrientation.RIGHT_TO_LEFT);
 460     }
 461     
 462     
 463     /**************************************************************************
 464      *                                                                        *
 465      * Private implementation                                                 *
 466      *                                                                        *  
 467      *************************************************************************/
 468 
 469     private void setSelectionPathDeviated(boolean selectionPathDeviated) {
 470         this.selectionPathDeviated = selectionPathDeviated;
 471     }
 472     
 473     protected void scrollUp() {
 474         TableSelectionModel<T> sm = getSelectionModel();
 475         if (sm == null || getSelectedCells().isEmpty()) return;
 476         
 477         TablePositionBase<TC> selectedCell = getSelectedCells().get(0);
 478         
 479         int newSelectedIndex = -1;
 480         if (onScrollPageUp != null) {
 481             newSelectedIndex = onScrollPageUp.call(false);
 482         }
 483         if (newSelectedIndex == -1) return;
 484         
 485         sm.clearAndSelect(newSelectedIndex, selectedCell.getTableColumn());
 486     }
 487 
 488     protected void scrollDown() {
 489         TableSelectionModel<T> sm = getSelectionModel();
 490         if (sm == null || getSelectedCells().isEmpty()) return;
 491         
 492         TablePositionBase<TC> selectedCell = getSelectedCells().get(0);
 493         
 494         int newSelectedIndex = -1;
 495         if (onScrollPageDown != null) {
 496             newSelectedIndex = onScrollPageDown.call(false);
 497         }
 498         if (newSelectedIndex == -1) return;
 499         
 500         sm.clearAndSelect(newSelectedIndex, selectedCell.getTableColumn());
 501     }
 502     
 503     protected void focusFirstRow() {
 504         TableFocusModel fm = getFocusModel();
 505         if (fm == null) return;
 506         
 507         TableColumnBase tc = getFocusedCell() == null ? null : getFocusedCell().getTableColumn();
 508         fm.focus(0, tc);
 509         
 510         if (onMoveToFirstCell != null) onMoveToFirstCell.run();
 511     }
 512     
 513     protected void focusLastRow() {
 514         TableFocusModel fm = getFocusModel();
 515         if (fm == null) return;
 516         
 517         TableColumnBase tc = getFocusedCell() == null ? null : getFocusedCell().getTableColumn();
 518         fm.focus(getItemCount() - 1, tc);
 519         
 520         if (onMoveToLastCell != null) onMoveToLastCell.run();
 521     }
 522 
 523     protected void focusPreviousRow() {
 524         TableSelectionModel sm = getSelectionModel();
 525         if (sm == null) return;
 526 
 527         TableFocusModel fm = getFocusModel();
 528         if (fm == null) return;
 529 
 530         if (sm.isCellSelectionEnabled()) {
 531             fm.focusAboveCell();
 532         } else {
 533             fm.focusPrevious();
 534         }
 535         
 536         if (! isShortcutDown || getAnchor() == null) {
 537             setAnchor(fm.getFocusedIndex(), null);
 538         }
 539 
 540         if (onFocusPreviousRow != null) onFocusPreviousRow.run();
 541     }
 542 
 543     protected void focusNextRow() {
 544         TableSelectionModel sm = getSelectionModel();
 545         if (sm == null) return;
 546 
 547         TableFocusModel fm = getFocusModel();
 548         if (fm == null) return;
 549         
 550         if (sm.isCellSelectionEnabled()) {
 551             fm.focusBelowCell();
 552         } else {
 553             fm.focusNext();
 554         }
 555         
 556         if (! isShortcutDown || getAnchor() == null) {
 557             setAnchor(fm.getFocusedIndex(), null);
 558         }
 559         
 560         if (onFocusNextRow != null) onFocusNextRow.run();
 561     }
 562 
 563     protected void focusLeftCell() {
 564         TableSelectionModel sm = getSelectionModel();
 565         if (sm == null) return;
 566 
 567         TableFocusModel fm = getFocusModel();
 568         if (fm == null) return;
 569 
 570         fm.focusLeftCell();
 571         if (onFocusPreviousRow != null) onFocusPreviousRow.run();
 572     }
 573 
 574     protected void focusRightCell() {
 575         TableSelectionModel sm = getSelectionModel();
 576         if (sm == null) return;
 577 
 578         TableFocusModel fm = getFocusModel();
 579         if (fm == null) return;
 580 
 581         fm.focusRightCell();
 582         if (onFocusNextRow != null) onFocusNextRow.run();
 583     }
 584     
 585     protected void focusPageUp() {
 586         int newFocusIndex = onScrollPageUp.call(true);
 587         
 588         TableFocusModel fm = getFocusModel();
 589         if (fm == null) return;
 590         TableColumnBase tc = getFocusedCell() == null ? null : getFocusedCell().getTableColumn();
 591         fm.focus(newFocusIndex, tc);
 592     }
 593     
 594     protected void focusPageDown() {
 595         int newFocusIndex = onScrollPageDown.call(true);
 596         
 597         TableFocusModel fm = getFocusModel();
 598         if (fm == null) return;
 599         TableColumnBase tc = getFocusedCell() == null ? null : getFocusedCell().getTableColumn();
 600         fm.focus(newFocusIndex, tc);
 601     }
 602 
 603     protected void clearSelection() {
 604         TableSelectionModel sm = getSelectionModel();
 605         if (sm == null) return;
 606 
 607         sm.clearSelection();
 608     }
 609     
 610     protected void clearSelectionOutsideRange(int start, int end, TableColumnBase<T,?> column) {
 611         TableSelectionModel<T> sm = getSelectionModel();
 612         if (sm == null) return;
 613         
 614         int min = Math.min(start, end);
 615         int max = Math.max(start, end);
 616         
 617         List<Integer> indices = new ArrayList<Integer>(sm.getSelectedIndices());
 618         
 619         selectionChanging = true;
 620         for (int i = 0; i < indices.size(); i++) {
 621             int index = indices.get(i);
 622             if (index < min || index > max) {
 623                 sm.clearSelection(index, column);
 624             }
 625         }
 626         selectionChanging = false;
 627     }
 628 
 629     protected void alsoSelectPrevious() {
 630         TableSelectionModel sm = getSelectionModel();
 631         if (sm == null) return;
 632 
 633         if (sm.getSelectionMode() == SelectionMode.SINGLE) {
 634             selectPreviousRow();
 635             return;
 636         }
 637         
 638         TableFocusModel fm = getFocusModel();
 639         if (fm == null) return;
 640         
 641         if (sm.isCellSelectionEnabled()) {
 642             updateCellVerticalSelection(-1, () -> {
 643                 getSelectionModel().selectAboveCell();
 644             });
 645         } else {
 646             if (isShiftDown && hasAnchor()) {
 647                 updateRowSelection(-1);
 648             } else {
 649                 sm.selectPrevious();
 650             }
 651         }
 652         onSelectPreviousRow.run();
 653     }
 654     
 655     protected void alsoSelectNext() {
 656         TableSelectionModel sm = getSelectionModel();
 657         if (sm == null) return;
 658 
 659         if (sm.getSelectionMode() == SelectionMode.SINGLE) {
 660             selectNextRow();
 661             return;
 662         }
 663         
 664         TableFocusModel fm = getFocusModel();
 665         if (fm == null) return;
 666 
 667         if (sm.isCellSelectionEnabled()) {
 668             updateCellVerticalSelection(1, () -> {
 669                 getSelectionModel().selectBelowCell();
 670             });
 671         } else {
 672             if (isShiftDown && hasAnchor()) {
 673                 updateRowSelection(1);
 674             } else {
 675                 sm.selectNext();
 676             }
 677         }
 678         onSelectNextRow.run();
 679     }
 680     
 681     protected void alsoSelectLeftCell() {
 682         updateCellHorizontalSelection(-1, () -> {
 683             getSelectionModel().selectLeftCell();
 684         });
 685 
 686         onSelectLeftCell.run();
 687     }
 688 
 689     protected void alsoSelectRightCell() {
 690         updateCellHorizontalSelection(1, () -> {
 691             getSelectionModel().selectRightCell();
 692         });
 693 
 694         onSelectRightCell.run();
 695     }
 696     
 697     protected void updateRowSelection(int delta) {
 698         TableSelectionModel sm = getSelectionModel();
 699         if (sm == null || sm.getSelectionMode() == SelectionMode.SINGLE) return;
 700         
 701         TableFocusModel fm = getFocusModel();
 702         if (fm == null) return;
 703         
 704         int newRow = fm.getFocusedIndex() + delta;
 705         TablePositionBase anchor = getAnchor();
 706         
 707         if (! hasAnchor()) {
 708             setAnchor(getFocusedCell());
 709         } 
 710 
 711         if (sm.getSelectedIndices().size() > 1) {
 712             clearSelectionOutsideRange(anchor.getRow(), newRow, null);
 713         }
 714 
 715         if (anchor.getRow() > newRow) {
 716             sm.selectRange(anchor.getRow(), newRow - 1);
 717         } else {
 718             sm.selectRange(anchor.getRow(), newRow + 1);
 719         }
 720     }
 721     
 722     protected void updateCellVerticalSelection(int delta, Runnable defaultAction) {
 723         TableSelectionModel sm = getSelectionModel();
 724         if (sm == null || sm.getSelectionMode() == SelectionMode.SINGLE) return;
 725         
 726         TableFocusModel fm = getFocusModel();
 727         if (fm == null) return;
 728         
 729         final TablePositionBase focusedCell = getFocusedCell();
 730         final int focusedCellRow = focusedCell.getRow();
 731 
 732         if (isShiftDown && sm.isSelected(focusedCellRow + delta, focusedCell.getTableColumn())) {
 733             int newFocusOwner = focusedCellRow + delta;
 734 
 735             // work out if we're backtracking
 736             boolean backtracking = false;
 737             if (selectionHistory.size() >= 2) {
 738                 TablePositionBase<TC> secondToLastSelectedCell = selectionHistory.get(1);
 739                 backtracking = secondToLastSelectedCell.getRow() == newFocusOwner &&
 740                         secondToLastSelectedCell.getColumn() == focusedCell.getColumn();
 741             }
 742 
 743             // if the selection path has deviated from the anchor row / column, then we need to see if we're moving
 744             // backwards to the previous selection or not (as it determines what cell row we clear out)
 745             int cellRowToClear = selectionPathDeviated ?
 746                     (backtracking ? focusedCellRow : newFocusOwner) :
 747                     focusedCellRow;
 748 
 749             sm.clearSelection(cellRowToClear, focusedCell.getTableColumn());
 750             fm.focus(newFocusOwner, focusedCell.getTableColumn());
 751         } else if (isShiftDown && getAnchor() != null && ! selectionPathDeviated) {
 752             int newRow = fm.getFocusedIndex() + delta;
 753             
 754             // we don't let the newRow go outside the bounds of the data
 755             newRow = Math.max(Math.min(getItemCount() - 1, newRow), 0);
 756 
 757             int start = Math.min(getAnchor().getRow(), newRow);
 758             int end = Math.max(getAnchor().getRow(), newRow);
 759 
 760             if (sm.getSelectedIndices().size() > 1) {
 761                 clearSelectionOutsideRange(start, end, focusedCell.getTableColumn());
 762             }
 763 
 764             for (int _row = start; _row <= end; _row++) {
 765                 if (sm.isSelected(_row, focusedCell.getTableColumn())) {
 766                     continue;
 767                 }
 768                 sm.select(_row, focusedCell.getTableColumn());
 769             }
 770             fm.focus(newRow, focusedCell.getTableColumn());
 771         } else {
 772             final int focusIndex = fm.getFocusedIndex();
 773             if (! sm.isSelected(focusIndex, focusedCell.getTableColumn())) {
 774                 sm.select(focusIndex, focusedCell.getTableColumn());
 775             }
 776             defaultAction.run();
 777         }
 778     }
 779     
 780     protected void updateCellHorizontalSelection(int delta, Runnable defaultAction) {
 781         TableSelectionModel sm = getSelectionModel();
 782         if (sm == null || sm.getSelectionMode() == SelectionMode.SINGLE) return;
 783 
 784         TableFocusModel fm = getFocusModel();
 785         if (fm == null) return;
 786         
 787         final TablePositionBase focusedCell = getFocusedCell();
 788         if (focusedCell == null || focusedCell.getTableColumn() == null) return;
 789 
 790         boolean atEnd = false;
 791         TableColumnBase adjacentColumn = getColumn(focusedCell.getTableColumn(), delta);
 792         if (adjacentColumn == null) {
 793             // if adjacentColumn is null, we use the focusedCell column, as we are
 794             // most probably at the very beginning or end of the row
 795             adjacentColumn = focusedCell.getTableColumn();
 796             atEnd = true;
 797         }
 798 
 799         final int focusedCellRow = focusedCell.getRow();
 800 
 801         if (isShiftDown && sm.isSelected(focusedCellRow, adjacentColumn)) {
 802             if (atEnd) {
 803                 return;
 804             }
 805 
 806             // work out if we're backtracking
 807             boolean backtracking = false;
 808             ObservableList<? extends TablePositionBase> selectedCells = getSelectedCells();
 809             if (selectedCells.size() >= 2) {
 810                 TablePositionBase<TC> secondToLastSelectedCell = selectedCells.get(selectedCells.size() - 2);
 811                 backtracking = secondToLastSelectedCell.getRow() == focusedCellRow &&
 812                         secondToLastSelectedCell.getTableColumn().equals(adjacentColumn);
 813             }
 814 
 815             // if the selection path has deviated from the anchor row / column, then we need to see if we're moving
 816             // backwards to the previous selection or not (as it determines what cell column we clear out)
 817             TableColumnBase<?,?> cellColumnToClear = selectionPathDeviated ?
 818                     (backtracking ? focusedCell.getTableColumn() : adjacentColumn) :
 819                     focusedCell.getTableColumn();
 820 
 821             sm.clearSelection(focusedCellRow, cellColumnToClear);
 822             fm.focus(focusedCellRow, adjacentColumn);
 823         } else if (isShiftDown && getAnchor() != null && ! selectionPathDeviated) {
 824             final int anchorColumn = getAnchor().getColumn();
 825 
 826             // we don't let the newColumn go outside the bounds of the data
 827             int newColumn = getVisibleLeafIndex(focusedCell.getTableColumn()) + delta;
 828             newColumn = Math.max(Math.min(getVisibleLeafColumns().size() - 1, newColumn), 0);
 829 
 830             int start = Math.min(anchorColumn, newColumn);
 831             int end = Math.max(anchorColumn, newColumn);
 832 
 833             for (int _col = start; _col <= end; _col++) {
 834                 sm.select(focusedCell.getRow(), getColumn(_col));
 835             }
 836             fm.focus(focusedCell.getRow(), getColumn(newColumn));
 837         } else {
 838             defaultAction.run();
 839         }
 840     }
 841     
 842     protected TableColumnBase getColumn(int index) {
 843         return getVisibleLeafColumn(index);
 844     }
 845     
 846     protected TableColumnBase getColumn(TableColumnBase tc, int delta) {
 847         return getVisibleLeafColumn(getVisibleLeafIndex(tc) + delta);
 848     }
 849 
 850     protected void selectFirstRow() {
 851         TableSelectionModel sm = getSelectionModel();
 852         if (sm == null) return;
 853 
 854         ObservableList<? extends TablePositionBase> selection = getSelectedCells();
 855         TableColumnBase<?,?> selectedColumn = selection.size() == 0 ? null : selection.get(0).getTableColumn();
 856         sm.clearAndSelect(0, selectedColumn);
 857 
 858         if (onMoveToFirstCell != null) onMoveToFirstCell.run();
 859     }
 860 
 861     protected void selectLastRow() {
 862         TableSelectionModel sm = getSelectionModel();
 863         if (sm == null) return;
 864 
 865         ObservableList<? extends TablePositionBase> selection = getSelectedCells();
 866         TableColumnBase<?,?> selectedColumn = selection.size() == 0 ? null : selection.get(0).getTableColumn();
 867         sm.clearAndSelect(getItemCount() - 1, selectedColumn);
 868 
 869         if (onMoveToLastCell != null) onMoveToLastCell.run();
 870     }
 871 
 872     protected void selectPreviousRow() {
 873         selectCell(-1, 0);
 874         if (onSelectPreviousRow != null) onSelectPreviousRow.run();
 875     }
 876 
 877     protected void selectNextRow() {
 878         selectCell(1, 0);
 879         if (onSelectNextRow != null) onSelectNextRow.run();
 880     }
 881 
 882     protected void selectLeftCell() {
 883         selectCell(0, -1);
 884         if (onSelectLeftCell != null) onSelectLeftCell.run();
 885     }
 886 
 887     protected void selectRightCell() {
 888         selectCell(0, 1);
 889         if (onSelectRightCell != null) onSelectRightCell.run();
 890     }
 891 
 892     protected void selectCell(int rowDiff, int columnDiff) {
 893         TableSelectionModel sm = getSelectionModel();
 894         if (sm == null) return;
 895 
 896         TableFocusModel fm = getFocusModel();
 897         if (fm == null) return;
 898 
 899         TablePositionBase<TC> focusedCell = getFocusedCell();
 900         int currentRow = focusedCell.getRow();
 901         int currentColumn = getVisibleLeafIndex(focusedCell.getTableColumn());
 902 
 903         if (rowDiff < 0 && currentRow <= 0) return;
 904         else if (rowDiff > 0 && currentRow >= getItemCount() - 1) return;
 905         else if (columnDiff < 0 && currentColumn <= 0) return;
 906         else if (columnDiff > 0 && currentColumn >= getVisibleLeafColumns().size() - 1) return;
 907         else if (columnDiff > 0 && currentColumn == -1) return;
 908 
 909         TableColumnBase tc = focusedCell.getTableColumn();
 910         tc = getColumn(tc, columnDiff);
 911         
 912         int row = focusedCell.getRow() + rowDiff;
 913         sm.clearAndSelect(row, tc);
 914         setAnchor(row, tc);
 915     }
 916     
 917     protected void cancelEdit() {
 918         editCell(-1, null);
 919     }
 920 
 921     protected void activate() {
 922         TableSelectionModel sm = getSelectionModel();
 923         if (sm == null) return;
 924 
 925         TableFocusModel fm = getFocusModel();
 926         if (fm == null) return;
 927 
 928         TablePositionBase<TC> cell = getFocusedCell();
 929         sm.select(cell.getRow(), cell.getTableColumn());
 930         setAnchor(cell);
 931 
 932         // edit this row also
 933         if (cell.getRow() >= 0) {
 934             editCell(cell.getRow(), cell.getTableColumn());
 935         }
 936     }
 937     
 938     protected void selectAllToFocus(boolean setAnchorToFocusIndex) {
 939         TableSelectionModel sm = getSelectionModel();
 940         if (sm == null) return;
 941 
 942         TableFocusModel fm = getFocusModel();
 943         if (fm == null) return;
 944 
 945         TablePositionBase<TC> focusedCell = getFocusedCell();
 946         int focusRow = focusedCell.getRow();
 947         
 948         TablePositionBase<TC> anchor = getAnchor();
 949         int anchorRow = anchor.getRow();
 950         
 951         sm.clearSelection();
 952         if (! sm.isCellSelectionEnabled()) {
 953             int startPos = anchorRow;
 954             int endPos = anchorRow > focusRow ? focusRow - 1 : focusRow + 1;
 955             sm.selectRange(startPos, endPos);
 956         } else {
 957             // we add all cells/rows between the current selection focus and
 958             // the anchor (inclusive) to the current selection.
 959             // We want focus to end up on the current focus position.
 960             sm.selectRange(anchor.getRow(), anchor.getTableColumn(),
 961                            focusedCell.getRow(), focusedCell.getTableColumn());
 962         }
 963         
 964         setAnchor(setAnchorToFocusIndex ? focusedCell : anchor);
 965     }
 966     
 967     protected void selectAll() {
 968         TableSelectionModel sm = getSelectionModel();
 969         if (sm == null) return;
 970         sm.selectAll();
 971     }
 972 
 973     protected void selectAllToFirstRow() {
 974         TableSelectionModel sm = getSelectionModel();
 975         if (sm == null) return;
 976 
 977         TableFocusModel fm = getFocusModel();
 978         if (fm == null) return;
 979 
 980         final boolean isSingleSelection = sm.getSelectionMode() == SelectionMode.SINGLE;
 981         final TablePositionBase focusedCell = getFocusedCell();
 982         final TableColumnBase<?,?> column = getFocusedCell().getTableColumn();
 983         int leadIndex = focusedCell.getRow();
 984         
 985         if (isShiftDown) {
 986             leadIndex = getAnchor() == null ? leadIndex : getAnchor().getRow();
 987         }
 988 
 989         sm.clearSelection();
 990         if (! sm.isCellSelectionEnabled()) {
 991             // we are going from 0 to one before the focused cell as that is
 992             // the requirement of selectRange, so we call focus on the 0th row
 993             if (isSingleSelection) {
 994                 sm.select(0);
 995             } else {
 996                 sm.selectRange(leadIndex, -1);
 997             }
 998             fm.focus(0);
 999         } else {
1000             if (isSingleSelection) {
1001                 sm.select(0, column);
1002             } else {
1003                 sm.selectRange(leadIndex, column, -1, column);
1004             }
1005             fm.focus(0, column);
1006         }
1007         
1008         if (isShiftDown) {
1009             setAnchor(leadIndex, column);
1010         }
1011 
1012         if (onMoveToFirstCell != null) onMoveToFirstCell.run();
1013     }
1014 
1015     protected void selectAllToLastRow() {
1016         TableSelectionModel sm = getSelectionModel();
1017         if (sm == null) return;
1018 
1019         TableFocusModel fm = getFocusModel();
1020         if (fm == null) return;
1021 
1022         final int itemCount = getItemCount();
1023         final TablePositionBase focusedCell = getFocusedCell();
1024         final TableColumnBase<?,?> column = getFocusedCell().getTableColumn();
1025         int leadIndex = focusedCell.getRow();
1026         
1027         if (isShiftDown) {
1028             leadIndex = getAnchor() == null ? leadIndex : getAnchor().getRow();
1029         }
1030         
1031         sm.clearSelection();
1032         if (! sm.isCellSelectionEnabled()) {
1033             sm.selectRange(leadIndex, itemCount);
1034         } else {
1035             sm.selectRange(leadIndex, column, itemCount - 1, column);
1036         }
1037         
1038         if (isShiftDown) {
1039             setAnchor(leadIndex, column);
1040         }
1041 
1042         if (onMoveToLastCell != null) onMoveToLastCell.run();
1043     }
1044     
1045     protected void selectAllPageUp() {
1046         TableSelectionModel sm = getSelectionModel();
1047         if (sm == null) return;
1048 
1049         TableFocusModel fm = getFocusModel();
1050         if (fm == null) return;
1051 
1052         int leadIndex = fm.getFocusedIndex();
1053         final TableColumnBase col = sm.isCellSelectionEnabled() ? getFocusedCell().getTableColumn() : null;
1054         if (isShiftDown) {
1055             leadIndex = getAnchor() == null ? leadIndex : getAnchor().getRow();
1056             setAnchor(leadIndex, col);
1057         }
1058         
1059         int leadSelectedIndex = onScrollPageUp.call(false);
1060 
1061         selectionChanging = true;
1062         if (sm.getSelectionMode() == null || sm.getSelectionMode() == SelectionMode.SINGLE) {
1063             if (sm.isCellSelectionEnabled()) {
1064                 sm.select(leadSelectedIndex, col);
1065             } else {
1066                 sm.select(leadSelectedIndex);
1067             }
1068         } else {
1069             sm.clearSelection();
1070             if (sm.isCellSelectionEnabled()) {
1071                 sm.selectRange(leadIndex, col, leadSelectedIndex, col);
1072             } else {
1073                 // fix for RT-34407
1074                 int adjust = leadIndex < leadSelectedIndex ? 1 : -1;
1075                 sm.selectRange(leadIndex, leadSelectedIndex + adjust);
1076             }
1077         }
1078         selectionChanging = false;
1079     }
1080     
1081     protected void selectAllPageDown() {
1082         TableSelectionModel sm = getSelectionModel();
1083         if (sm == null) return;
1084 
1085         TableFocusModel fm = getFocusModel();
1086         if (fm == null) return;
1087         
1088         int leadIndex = fm.getFocusedIndex();
1089         final TableColumnBase col = sm.isCellSelectionEnabled() ? getFocusedCell().getTableColumn() : null;
1090         if (isShiftDown) {
1091             leadIndex = getAnchor() == null ? leadIndex : getAnchor().getRow();
1092             setAnchor(leadIndex, col);
1093         }
1094         
1095         int leadSelectedIndex = onScrollPageDown.call(false);
1096 
1097         selectionChanging = true;
1098         if (sm.getSelectionMode() == null || sm.getSelectionMode() == SelectionMode.SINGLE) {
1099             if (sm.isCellSelectionEnabled()) {
1100                 sm.select(leadSelectedIndex, col);
1101             } else {
1102                 sm.select(leadSelectedIndex);
1103             }
1104         } else {
1105             sm.clearSelection();
1106 
1107             if (sm.isCellSelectionEnabled()) {
1108                 sm.selectRange(leadIndex, col, leadSelectedIndex, col);
1109             } else {
1110                 // fix for RT-34407
1111                 int adjust = leadIndex < leadSelectedIndex ? 1 : -1;
1112                 sm.selectRange(leadIndex, leadSelectedIndex + adjust);
1113             }
1114         }
1115         selectionChanging = false;
1116     }
1117     
1118     protected void toggleFocusOwnerSelection() {
1119         TableSelectionModel sm = getSelectionModel();
1120         if (sm == null) return;
1121 
1122         TableFocusModel fm = getFocusModel();
1123         if (fm == null) return;
1124 
1125         TablePositionBase focusedCell = getFocusedCell();
1126         
1127         if (sm.isSelected(focusedCell.getRow(), focusedCell.getTableColumn())) {
1128             sm.clearSelection(focusedCell.getRow(), focusedCell.getTableColumn());
1129             fm.focus(focusedCell.getRow(), focusedCell.getTableColumn());
1130         } else {
1131             sm.select(focusedCell.getRow(), focusedCell.getTableColumn());
1132         }
1133         
1134         setAnchor(focusedCell.getRow(), focusedCell.getTableColumn());
1135     }
1136     
1137     // This functionality was added, but then removed when it was realised by 
1138     // UX that TableView should not include 'spreadsheet-like' functionality.
1139     // When / if we ever introduce this kind of control, this functionality can
1140     // be re-enabled then.
1141     /*
1142     protected void moveToLeftMostColumn() {
1143         // Functionality as described in RT-12752
1144         if (onMoveToLeftMostColumn != null) onMoveToLeftMostColumn.run();
1145         
1146         TableSelectionModel sm = getSelectionModel();
1147         if (sm == null || ! sm.isCellSelectionEnabled()) return;
1148         
1149         TableFocusModel fm = getFocusModel();
1150         if (fm == null) return;
1151 
1152         TablePosition focusedCell = fm.getFocusedCell();
1153         
1154         TableColumn endColumn = getControl().getVisibleLeafColumn(0);
1155         sm.clearAndSelect(focusedCell.getRow(), endColumn);
1156     }
1157     
1158     protected void moveToRightMostColumn() {
1159         // Functionality as described in RT-12752
1160         if (onMoveToRightMostColumn != null) onMoveToRightMostColumn.run();
1161         
1162         TableSelectionModel sm = getSelectionModel();
1163         if (sm == null || ! sm.isCellSelectionEnabled()) return;
1164         
1165         TableFocusModel fm = getFocusModel();
1166         if (fm == null) return;
1167 
1168         TablePosition focusedCell = fm.getFocusedCell();
1169         
1170         TableColumn endColumn = getControl().getVisibleLeafColumn(getControl().getVisibleLeafColumns().size() - 1);
1171         sm.clearAndSelect(focusedCell.getRow(), endColumn);
1172     }
1173      */
1174     
1175     
1176     /**************************************************************************
1177      * Discontinuous Selection                                                *
1178      *************************************************************************/
1179     
1180     protected void discontinuousSelectPreviousRow() {
1181         TableSelectionModel sm = getSelectionModel();
1182         if (sm == null) return;
1183 
1184         if (sm.getSelectionMode() != SelectionMode.MULTIPLE) {
1185             selectPreviousRow();
1186             return;
1187         }
1188         
1189         TableFocusModel fm = getFocusModel();
1190         if (fm == null) return;
1191         
1192         int focusIndex = fm.getFocusedIndex();
1193         final int newFocusIndex = focusIndex - 1;
1194         if (newFocusIndex < 0) return;
1195 
1196         int startIndex = focusIndex;
1197         final TableColumnBase col = sm.isCellSelectionEnabled() ? getFocusedCell().getTableColumn() : null;
1198         if (isShiftDown) {
1199             startIndex = getAnchor() == null ? focusIndex : getAnchor().getRow();
1200         }
1201 
1202         if (! sm.isCellSelectionEnabled()) {
1203             sm.selectRange(newFocusIndex, startIndex + 1);
1204             fm.focus(newFocusIndex);
1205         } else {
1206             for (int i = newFocusIndex; i < startIndex + 1; i++) {
1207                 sm.select(i, col);
1208             }
1209             fm.focus(newFocusIndex, col);
1210         }
1211 
1212         if (onFocusPreviousRow != null) onFocusPreviousRow.run();
1213     }
1214     
1215     protected void discontinuousSelectNextRow() {
1216         TableSelectionModel sm = getSelectionModel();
1217         if (sm == null) return;
1218 
1219         if (sm.getSelectionMode() != SelectionMode.MULTIPLE) {
1220             selectNextRow();
1221             return;
1222         }
1223 
1224         TableFocusModel fm = getFocusModel();
1225         if (fm == null) return;
1226 
1227         int focusIndex = fm.getFocusedIndex();
1228         final int newFocusIndex = focusIndex + 1;
1229         if (newFocusIndex >= getItemCount()) return;
1230 
1231         int startIndex = focusIndex;
1232         final TableColumnBase col = sm.isCellSelectionEnabled() ? getFocusedCell().getTableColumn() : null;
1233         if (isShiftDown) {
1234             startIndex = getAnchor() == null ? focusIndex : getAnchor().getRow();
1235         }
1236 
1237         if (! sm.isCellSelectionEnabled()) {
1238             sm.selectRange(startIndex, newFocusIndex + 1);
1239             fm.focus(newFocusIndex);
1240         } else {
1241             for (int i = startIndex; i < newFocusIndex + 1; i++) {
1242                 sm.select(i, col);
1243             }
1244             fm.focus(newFocusIndex, col);
1245         }
1246 
1247         if (onFocusNextRow != null) onFocusNextRow.run();
1248     }
1249     
1250     protected void discontinuousSelectPreviousColumn() {
1251         TableSelectionModel sm = getSelectionModel();
1252         if (sm == null || ! sm.isCellSelectionEnabled()) return;
1253         
1254         TableFocusModel fm = getFocusModel();
1255         if (fm == null) return;
1256 
1257         TableColumnBase tc = getColumn(getFocusedCell().getTableColumn(), -1);
1258         sm.select(fm.getFocusedIndex(), tc);
1259     }
1260     
1261     protected void discontinuousSelectNextColumn() {
1262         TableSelectionModel sm = getSelectionModel();
1263         if (sm == null || ! sm.isCellSelectionEnabled()) return;
1264         
1265         TableFocusModel fm = getFocusModel();
1266         if (fm == null) return;
1267 
1268         TableColumnBase tc = getColumn(getFocusedCell().getTableColumn(), 1);
1269         sm.select(fm.getFocusedIndex(), tc);
1270     }
1271     
1272     protected void discontinuousSelectPageUp() {
1273         TableSelectionModel sm = getSelectionModel();
1274         if (sm == null) return;
1275         
1276         TableFocusModel fm = getFocusModel();
1277         if (fm == null) return;
1278 
1279         int anchor = hasAnchor() ? getAnchor().getRow() : fm.getFocusedIndex();
1280         int leadSelectedIndex = onScrollPageUp.call(false);
1281         
1282         if (! sm.isCellSelectionEnabled()) {
1283             sm.selectRange(anchor, leadSelectedIndex - 1);
1284         }
1285     }
1286     
1287     protected void discontinuousSelectPageDown() {
1288         TableSelectionModel sm = getSelectionModel();
1289         if (sm == null) return;
1290         
1291         TableFocusModel fm = getFocusModel();
1292         if (fm == null) return;
1293 
1294         int anchor = hasAnchor() ? getAnchor().getRow() : fm.getFocusedIndex();
1295         int leadSelectedIndex = onScrollPageDown.call(false);
1296         
1297         if (! sm.isCellSelectionEnabled()) {
1298             sm.selectRange(anchor, leadSelectedIndex + 1);
1299         }
1300     }
1301     
1302     protected void discontinuousSelectAllToFirstRow() {
1303         TableSelectionModel sm = getSelectionModel();
1304         if (sm == null) return;
1305         
1306         TableFocusModel fm = getFocusModel();
1307         if (fm == null) return;
1308 
1309         int index = fm.getFocusedIndex();
1310         
1311         if (! sm.isCellSelectionEnabled()) {
1312             sm.selectRange(0, index);
1313             fm.focus(0);
1314         } else {
1315             for (int i = 0; i < index; i++) {
1316                 sm.select(i, getFocusedCell().getTableColumn());
1317             }
1318             fm.focus(0, getFocusedCell().getTableColumn());
1319         }
1320         
1321         if (onMoveToFirstCell != null) onMoveToFirstCell.run();
1322     }
1323     
1324     protected void discontinuousSelectAllToLastRow() {
1325         TableSelectionModel sm = getSelectionModel();
1326         if (sm == null) return;
1327         
1328         TableFocusModel fm = getFocusModel();
1329         if (fm == null) return;
1330 
1331         int index = fm.getFocusedIndex() + 1;
1332         
1333         if (! sm.isCellSelectionEnabled()) {
1334             sm.selectRange(index, getItemCount());
1335         } else {
1336             for (int i = index; i < getItemCount(); i++) {
1337                 sm.select(i, getFocusedCell().getTableColumn());
1338             }
1339         }
1340 
1341         if (onMoveToLastCell != null) onMoveToLastCell.run();
1342     }   
1343 }