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