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