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 javafx.scene.control;
  27 
  28 import static org.junit.Assert.assertEquals;
  29 import static org.junit.Assert.assertFalse;
  30 import static org.junit.Assert.assertNull;
  31 import static org.junit.Assert.assertTrue;
  32 
  33 import java.util.List;
  34 
  35 import com.sun.javafx.scene.control.infrastructure.MouseEventFirer;
  36 import com.sun.javafx.tk.Toolkit;
  37 import javafx.collections.ListChangeListener;
  38 import javafx.scene.Scene;
  39 import javafx.scene.layout.VBox;
  40 
  41 import org.junit.After;
  42 import org.junit.Before;
  43 import org.junit.Test;
  44 
  45 import com.sun.javafx.scene.control.behavior.TreeViewAnchorRetriever;
  46 import com.sun.javafx.scene.control.infrastructure.KeyModifier;
  47 import com.sun.javafx.scene.control.infrastructure.StageLoader;
  48 import com.sun.javafx.scene.control.infrastructure.VirtualFlowTestUtils;
  49 
  50 //@Ignore("Disabling tests as they fail with OOM in continuous builds")
  51 public class TreeViewMouseInputTest {
  52     private TreeView<String> treeView;
  53     private MultipleSelectionModel<TreeItem<String>> sm;
  54     private FocusModel<TreeItem<String>> fm;
  55 
  56     private TreeItem<String> root;                  // 0
  57         private TreeItem<String> child1;            // 1
  58         private TreeItem<String> child2;            // 2
  59         private TreeItem<String> child3;            // 3
  60             private TreeItem<String> subchild1;     // 4
  61             private TreeItem<String> subchild2;     // 5
  62             private TreeItem<String> subchild3;     // 6
  63         private TreeItem<String> child4;            // 7
  64         private TreeItem<String> child5;            // 8
  65         private TreeItem<String> child6;            // 9
  66         private TreeItem<String> child7;            // 10
  67         private TreeItem<String> child8;            // 11
  68         private TreeItem<String> child9;            // 12
  69         private TreeItem<String> child10;           // 13
  70 
  71     @Before public void setup() {
  72         root = new TreeItem<String>("Root");             // 0
  73         child1 = new TreeItem<String>("Child 1");        // 1
  74         child2 = new TreeItem<String>("Child 2");        // 2
  75         child3 = new TreeItem<String>("Child 3");        // 3
  76         subchild1 = new TreeItem<String>("Subchild 1");  // 4
  77         subchild2 = new TreeItem<String>("Subchild 2");  // 5
  78         subchild3 = new TreeItem<String>("Subchild 3");  // 6
  79         child4 = new TreeItem<String>("Child 4");        // 7
  80         child5 = new TreeItem<String>("Child 5");        // 8
  81         child6 = new TreeItem<String>("Child 6");        // 9
  82         child7 = new TreeItem<String>("Child 7");        // 10
  83         child8 = new TreeItem<String>("Child 8");        // 11
  84         child9 = new TreeItem<String>("Child 9");        // 12
  85         child10 = new TreeItem<String>("Child 10");      // 13
  86 
  87         // reset tree structure
  88         root.getChildren().clear();
  89         root.setExpanded(true);
  90         root.getChildren().setAll(child1, child2, child3, child4, child5, child6, child7, child8, child9, child10 );
  91         child1.getChildren().clear();
  92         child1.setExpanded(false);
  93         child2.getChildren().clear();
  94         child2.setExpanded(false);
  95         child3.getChildren().clear();
  96         child3.setExpanded(true);
  97         child3.getChildren().setAll(subchild1, subchild2, subchild3);
  98         child4.getChildren().clear();
  99         child4.setExpanded(false);
 100         child5.getChildren().clear();
 101         child5.setExpanded(false);
 102         child6.getChildren().clear();
 103         child6.setExpanded(false);
 104         child7.getChildren().clear();
 105         child7.setExpanded(false);
 106         child8.getChildren().clear();
 107         child8.setExpanded(false);
 108         child9.getChildren().clear();
 109         child9.setExpanded(false);
 110         child10.getChildren().clear();
 111         child10.setExpanded(false);
 112         
 113         // recreate treeview and gather models
 114         treeView = new TreeView<String>();
 115         treeView.setRoot(root);
 116         sm = treeView.getSelectionModel();
 117         sm.setSelectionMode(SelectionMode.MULTIPLE);
 118         fm = treeView.getFocusModel();
 119     }
 120     
 121     @After public void tearDown() {
 122         treeView.getSkin().dispose();
 123     }
 124     
 125     
 126     /***************************************************************************
 127      * Util methods
 128      **************************************************************************/
 129     
 130     private String debug() {
 131         StringBuilder sb = new StringBuilder("Selected Indices: [");
 132         
 133         List<Integer> indices = sm.getSelectedIndices();
 134         for (Integer index : indices) {
 135             sb.append(index);
 136             sb.append(", ");
 137         }
 138         
 139         sb.append("] \nFocus: " + fm.getFocusedIndex());
 140         sb.append(" \nAnchor: " + getAnchor());
 141         return sb.toString();
 142     }
 143     
 144     // Returns true if ALL indices are selected
 145     private boolean isSelected(int... indices) {
 146         for (int index : indices) {
 147             if (! sm.isSelected(index)) return false;
 148         }
 149         return true;
 150     }
 151     
 152     // Returns true if ALL indices are NOT selected
 153     private boolean isNotSelected(int... indices) {
 154         for (int index : indices) {
 155             if (sm.isSelected(index)) return false;
 156         }
 157         return true;
 158     }
 159     
 160     private int getAnchor() {
 161         return TreeViewAnchorRetriever.getAnchor(treeView);
 162     }
 163     
 164     private boolean isAnchor(int index) {
 165         return getAnchor() == index;
 166     }
 167     
 168     private int getItemCount() {
 169         return root.getChildren().size() + child3.getChildren().size();
 170     }
 171     
 172     
 173     /***************************************************************************
 174      * Tests for specific bug reports
 175      **************************************************************************/
 176     
 177     @Test public void test_rt29833_mouse_select_upwards() {
 178         sm.setSelectionMode(SelectionMode.MULTIPLE);
 179         
 180         sm.clearAndSelect(9);
 181         
 182         // select all from 9 - 7
 183         VirtualFlowTestUtils.clickOnRow(treeView, 7, KeyModifier.SHIFT);
 184         assertTrue(debug(), isSelected(7,8,9));
 185         
 186         // select all from 9 - 7 - 5
 187         VirtualFlowTestUtils.clickOnRow(treeView, 5, KeyModifier.SHIFT);
 188         assertTrue(debug(),isSelected(5,6,7,8,9));
 189     }
 190     
 191     @Test public void test_rt29833_mouse_select_downwards() {
 192         sm.setSelectionMode(SelectionMode.MULTIPLE);
 193         
 194         sm.clearAndSelect(5);
 195         
 196         // select all from 5 - 7
 197         VirtualFlowTestUtils.clickOnRow(treeView, 7, KeyModifier.SHIFT);
 198         assertTrue(debug(), isSelected(5,6,7));
 199         
 200         // select all from 5 - 7 - 9
 201         VirtualFlowTestUtils.clickOnRow(treeView, 9, KeyModifier.SHIFT);
 202         assertTrue(debug(),isSelected(5,6,7,8,9));
 203     }
 204 
 205     private int rt30394_count = 0;
 206     @Test public void test_rt30394() {
 207         sm.setSelectionMode(SelectionMode.MULTIPLE);
 208         sm.clearSelection();
 209 
 210         final FocusModel fm = treeView.getFocusModel();
 211         fm.focus(-1);
 212 
 213         fm.focusedIndexProperty().addListener((observable, oldValue, newValue) -> {
 214             rt30394_count++;
 215             assertEquals(0, fm.getFocusedIndex());
 216         });
 217 
 218         // test pre-conditions
 219         assertEquals(0,rt30394_count);
 220         assertFalse(fm.isFocused(0));
 221 
 222         // select the first row with the shift key held down. The focus event
 223         // should only fire once - for focus on 0 (never -1 as this bug shows).
 224         VirtualFlowTestUtils.clickOnRow(treeView, 0, KeyModifier.SHIFT);
 225         assertEquals(1, rt30394_count);
 226         assertTrue(fm.isFocused(0));
 227     }
 228 
 229     @Test public void test_rt32119() {
 230         sm.setSelectionMode(SelectionMode.MULTIPLE);
 231         sm.clearSelection();
 232 
 233         // select rows 2, 3, and 4
 234         VirtualFlowTestUtils.clickOnRow(treeView, 2);
 235         VirtualFlowTestUtils.clickOnRow(treeView, 4, KeyModifier.SHIFT);
 236         assertFalse(sm.isSelected(1));
 237         assertTrue(sm.isSelected(2));
 238         assertTrue(sm.isSelected(3));
 239         assertTrue(sm.isSelected(4));
 240         assertFalse(sm.isSelected(5));
 241 
 242         // now shift click on the 2nd row - this should make only row 2 be
 243         // selected. The bug is that row 4 remains selected also.
 244         VirtualFlowTestUtils.clickOnRow(treeView, 2, KeyModifier.SHIFT);
 245         assertFalse(sm.isSelected(1));
 246         assertTrue(sm.isSelected(2));
 247         assertFalse(sm.isSelected(3));
 248         assertFalse(sm.isSelected(4));
 249         assertFalse(sm.isSelected(5));
 250     }
 251 
 252     @Test public void test_rt21444_up() {
 253         final int items = 8;
 254         root.getChildren().clear();
 255         for (int i = 0; i < items; i++) {
 256             root.getChildren().add(new TreeItem<>("Row " + i));
 257         }
 258 
 259         final int selectRow = 3;
 260 
 261         treeView.setShowRoot(false);
 262         final MultipleSelectionModel<TreeItem<String>> sm = treeView.getSelectionModel();
 263         sm.setSelectionMode(SelectionMode.MULTIPLE);
 264         sm.clearAndSelect(selectRow);
 265 
 266         assertEquals(selectRow, sm.getSelectedIndex());
 267         assertEquals("Row 3", sm.getSelectedItem().getValue());
 268 
 269         VirtualFlowTestUtils.clickOnRow(treeView, selectRow - 1, KeyModifier.SHIFT);
 270         assertEquals(2, sm.getSelectedItems().size());
 271         assertEquals("Row 2", sm.getSelectedItem().getValue());
 272         assertEquals("Row 2", sm.getSelectedItems().get(0).getValue());
 273     }
 274 
 275     @Test public void test_rt21444_down() {
 276         final int items = 8;
 277         root.getChildren().clear();
 278         for (int i = 0; i < items; i++) {
 279             root.getChildren().add(new TreeItem<>("Row " + i));
 280         }
 281 
 282         final int selectRow = 3;
 283 
 284         treeView.setShowRoot(false);
 285         final MultipleSelectionModel<TreeItem<String>> sm = treeView.getSelectionModel();
 286         sm.setSelectionMode(SelectionMode.MULTIPLE);
 287         sm.clearAndSelect(selectRow);
 288 
 289         assertEquals(selectRow, sm.getSelectedIndex());
 290         assertEquals("Row 3", sm.getSelectedItem().getValue());
 291 
 292         VirtualFlowTestUtils.clickOnRow(treeView, selectRow + 1, KeyModifier.SHIFT);
 293         assertEquals(2, sm.getSelectedItems().size());
 294         assertEquals("Row 4", sm.getSelectedItem().getValue());
 295         assertEquals("Row 4", sm.getSelectedItems().get(1).getValue());
 296     }
 297 
 298     @Test public void test_rt32560() {
 299         final int items = 8;
 300         root.getChildren().clear();
 301         for (int i = 0; i < items; i++) {
 302             root.getChildren().add(new TreeItem<>("Row " + i));
 303         }
 304 
 305         final MultipleSelectionModel sm = treeView.getSelectionModel();
 306         sm.setSelectionMode(SelectionMode.MULTIPLE);
 307         sm.clearAndSelect(0);
 308 
 309         assertEquals(0, sm.getSelectedIndex());
 310         assertEquals(root, sm.getSelectedItem());
 311         assertEquals(0, fm.getFocusedIndex());
 312 
 313         VirtualFlowTestUtils.clickOnRow(treeView, 5, KeyModifier.SHIFT);
 314         assertEquals(5, sm.getSelectedIndex());
 315         assertEquals(5, fm.getFocusedIndex());
 316         assertEquals(6, sm.getSelectedItems().size());
 317 
 318         VirtualFlowTestUtils.clickOnRow(treeView, 0, KeyModifier.SHIFT);
 319         assertEquals(0, sm.getSelectedIndex());
 320         assertEquals(0, fm.getFocusedIndex());
 321         assertEquals(1, sm.getSelectedItems().size());
 322     }
 323 
 324     @Test public void test_rt_32963() {
 325         final int items = 8;
 326         root.getChildren().clear();
 327         root.setExpanded(true);
 328         for (int i = 0; i < items; i++) {
 329             root.getChildren().add(new TreeItem<>("Row " + i));
 330         }
 331         treeView.setRoot(root);
 332 
 333         treeView.setShowRoot(true);
 334         final MultipleSelectionModel sm = treeView.getSelectionModel();
 335         sm.setSelectionMode(SelectionMode.MULTIPLE);
 336         sm.clearAndSelect(0);
 337 
 338         assertEquals(9, treeView.getExpandedItemCount());
 339 
 340         assertEquals(0, sm.getSelectedIndex());
 341         assertEquals(0, fm.getFocusedIndex());
 342         assertEquals(root, sm.getSelectedItem());
 343         assertEquals(1, sm.getSelectedItems().size());
 344 
 345         VirtualFlowTestUtils.clickOnRow(treeView, 5, KeyModifier.SHIFT);
 346         assertEquals("Actual selected index: " + sm.getSelectedIndex(), 5, sm.getSelectedIndex());
 347         assertEquals("Actual focused index: " + fm.getFocusedIndex(), 5, fm.getFocusedIndex());
 348         assertTrue("Selected indices: " + sm.getSelectedIndices(), sm.getSelectedIndices().contains(0));
 349         assertTrue("Selected items: " + sm.getSelectedItems(), sm.getSelectedItems().contains(root));
 350         assertEquals(6, sm.getSelectedItems().size());
 351     }
 352 
 353     private int rt_30626_count = 0;
 354     @Test public void test_rt_30626() {
 355         final int items = 8;
 356         root.getChildren().clear();
 357         root.setExpanded(true);
 358         for (int i = 0; i < items; i++) {
 359             root.getChildren().add(new TreeItem<>("Row " + i));
 360         }
 361         treeView.setRoot(root);
 362 
 363         treeView.setShowRoot(true);
 364         final MultipleSelectionModel sm = treeView.getSelectionModel();
 365         sm.setSelectionMode(SelectionMode.MULTIPLE);
 366         sm.clearAndSelect(0);
 367 
 368         treeView.getSelectionModel().getSelectedItems().addListener((ListChangeListener) c -> {
 369             while (c.next()) {
 370                 rt_30626_count++;
 371             }
 372         });
 373 
 374         assertEquals(0, rt_30626_count);
 375         VirtualFlowTestUtils.clickOnRow(treeView, 1);
 376         assertEquals(1, rt_30626_count);
 377 
 378         VirtualFlowTestUtils.clickOnRow(treeView, 1);
 379         assertEquals(1, rt_30626_count);
 380     }
 381 
 382     @Test public void test_rt_34649() {
 383         final int items = 8;
 384         root.getChildren().clear();
 385         root.setExpanded(true);
 386         for (int i = 0; i < items; i++) {
 387             root.getChildren().add(new TreeItem<>("Row " + i));
 388         }
 389         treeView.setRoot(root);
 390 
 391         final MultipleSelectionModel sm = treeView.getSelectionModel();
 392         final FocusModel fm = treeView.getFocusModel();
 393         sm.setSelectionMode(SelectionMode.SINGLE);
 394 
 395         assertFalse(sm.isSelected(4));
 396         assertFalse(fm.isFocused(4));
 397         VirtualFlowTestUtils.clickOnRow(treeView, 4, KeyModifier.getShortcutKey());
 398         assertTrue(sm.isSelected(4));
 399         assertTrue(fm.isFocused(4));
 400 
 401         VirtualFlowTestUtils.clickOnRow(treeView, 4, KeyModifier.getShortcutKey());
 402         assertFalse(sm.isSelected(4));
 403         assertTrue(fm.isFocused(4));
 404     }
 405 
 406     @Test public void test_rt_36509() {
 407         final int items = 8;
 408         root.getChildren().clear();
 409         root.setExpanded(false);
 410         for (int i = 0; i < items; i++) {
 411             root.getChildren().add(new TreeItem<>("Row " + i));
 412         }
 413         treeView.setRoot(root);
 414 
 415         // expand
 416         assertFalse(root.isExpanded());
 417         VirtualFlowTestUtils.clickOnRow(treeView, 0, 2);
 418         assertTrue(root.isExpanded());
 419 
 420         // collapse
 421         VirtualFlowTestUtils.clickOnRow(treeView, 0, 2);
 422         assertFalse(root.isExpanded());
 423 
 424         // expand again
 425         VirtualFlowTestUtils.clickOnRow(treeView, 0, 2);
 426         assertTrue(root.isExpanded());
 427     }
 428 
 429     @Test public void test_rt_37069() {
 430         final int items = 8;
 431         root.getChildren().clear();
 432         root.setExpanded(false);
 433         for (int i = 0; i < items; i++) {
 434             root.getChildren().add(new TreeItem<>("Row " + i));
 435         }
 436         treeView.setRoot(root);
 437         treeView.setFocusTraversable(false);
 438 
 439         Button btn = new Button("Button");
 440         VBox vbox = new VBox(btn, treeView);
 441 
 442         StageLoader sl = new StageLoader(vbox);
 443         sl.getStage().requestFocus();
 444         btn.requestFocus();
 445         Toolkit.getToolkit().firePulse();
 446         Scene scene = sl.getStage().getScene();
 447 
 448         assertTrue(btn.isFocused());
 449         assertFalse(treeView.isFocused());
 450 
 451         ScrollBar vbar = VirtualFlowTestUtils.getVirtualFlowVerticalScrollbar(treeView);
 452         MouseEventFirer mouse = new MouseEventFirer(vbar);
 453         mouse.fireMousePressAndRelease();
 454 
 455         assertTrue(btn.isFocused());
 456         assertFalse(treeView.isFocused());
 457 
 458         sl.dispose();
 459     }
 460 }