1 /*
   2  * Copyright (c) 2011, 2012, 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.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 /*
  25  * @test
  26  * @bug 6462008
  27  * @summary Tests that mouse/keyboard work properly on JList with lead < 0 or > list.getModel().getSize()
  28  * @author Shannon Hickey
  29  * @run main bug6462008
  30  */
  31 import java.awt.*;
  32 import java.awt.event.*;
  33 import javax.swing.*;
  34 import java.util.*;
  35 import sun.awt.SunToolkit;
  36 
  37 public class bug6462008 {
  38 
  39     private static final int DONT_CARE = -2;
  40     private static int anchorLead;
  41     private static boolean isAquaLAF;
  42     private static int controlKey;
  43     private static JList list;
  44     private static SunToolkit toolkit;
  45     private static Robot robot;
  46 
  47     public static void main(String[] args) throws Exception {
  48         toolkit = (SunToolkit) Toolkit.getDefaultToolkit();
  49         robot = new Robot();
  50         robot.setAutoDelay(100);
  51 
  52         isAquaLAF = "Aqua".equals(UIManager.getLookAndFeel().getID());
  53         controlKey = isAquaLAF ? KeyEvent.VK_META : KeyEvent.VK_CONTROL;
  54 
  55         SwingUtilities.invokeAndWait(new Runnable() {
  56 
  57             @Override
  58             public void run() {
  59                 createAndShowGUI();
  60             }
  61         });
  62 
  63         toolkit.realSync();
  64 
  65         setAnchorLead(-1);
  66         toolkit.realSync();
  67 
  68         testListSelection();
  69 
  70         setAnchorLead(100);
  71         toolkit.realSync();
  72 
  73         testListSelection();
  74     }
  75 
  76     public static void testListSelection() throws Exception {
  77 
  78         // Space
  79         robot.keyPress(KeyEvent.VK_SPACE);
  80         robot.keyRelease(KeyEvent.VK_SPACE);
  81 
  82         toolkit.realSync();
  83         checkSelection();
  84         resetList();
  85         toolkit.realSync();
  86 
  87         // Control + Space
  88         robot.keyPress(KeyEvent.VK_CONTROL);
  89         robot.keyPress(KeyEvent.VK_SPACE);
  90         robot.keyRelease(KeyEvent.VK_SPACE);
  91         robot.keyRelease(KeyEvent.VK_CONTROL);
  92 
  93         toolkit.realSync();
  94         checkSelection();
  95         resetList();
  96         toolkit.realSync();
  97 
  98         // Shift + Space
  99         robot.keyPress(KeyEvent.VK_SHIFT);
 100         robot.keyPress(KeyEvent.VK_SPACE);
 101         robot.keyRelease(KeyEvent.VK_SPACE);
 102         robot.keyRelease(KeyEvent.VK_SHIFT);
 103 
 104         toolkit.realSync();
 105         checkSelection();
 106         resetList();
 107         toolkit.realSync();
 108 
 109         // Control + Shift + Space
 110         robot.keyPress(KeyEvent.VK_CONTROL);
 111         robot.keyPress(KeyEvent.VK_SHIFT);
 112         robot.keyPress(KeyEvent.VK_SPACE);
 113         robot.keyRelease(KeyEvent.VK_SPACE);
 114         robot.keyRelease(KeyEvent.VK_SHIFT);
 115         robot.keyRelease(KeyEvent.VK_CONTROL);
 116 
 117         toolkit.realSync();
 118         checkSelection();
 119         resetList();
 120         toolkit.realSync();
 121 
 122 
 123         // Control + A  Multiple Selection
 124 
 125         robot.keyPress(controlKey);
 126         robot.keyPress(KeyEvent.VK_A);
 127         robot.keyRelease(KeyEvent.VK_A);
 128         robot.keyRelease(controlKey);
 129 
 130         toolkit.realSync();
 131         checkSelectionAL(-1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9);
 132         resetList();
 133         setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
 134         toolkit.realSync();
 135 
 136         // Control + A Single Selection
 137         robot.keyPress(controlKey);
 138         robot.keyPress(KeyEvent.VK_A);
 139         robot.keyRelease(KeyEvent.VK_A);
 140         robot.keyRelease(controlKey);
 141 
 142         toolkit.realSync();
 143         checkSelectionAL(0, 0, 0);
 144         resetList();
 145         setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
 146         setSelectionInterval(5, 5);
 147         toolkit.realSync();
 148 
 149 
 150         // Control + A Selection interval (5, 5)
 151         robot.keyPress(controlKey);
 152         robot.keyPress(KeyEvent.VK_A);
 153         robot.keyRelease(KeyEvent.VK_A);
 154         robot.keyRelease(controlKey);
 155 
 156         toolkit.realSync();
 157         checkSelection(5);
 158         resetList();
 159         toolkit.realSync();
 160 
 161         // Page Down
 162         // Not applicable for the Aqua L&F
 163         if (!isAquaLAF) {
 164             robot.keyPress(KeyEvent.VK_PAGE_DOWN);
 165             robot.keyRelease(KeyEvent.VK_PAGE_DOWN);
 166 
 167             toolkit.realSync();
 168             checkSelection(9, 9, 9);
 169             resetList();
 170             toolkit.realSync();
 171         }
 172 
 173         // Shift + Page Down
 174         /*
 175          * We really want to use robot here, but there seems to be a bug in AWT's
 176          * robot implementation (see 6463168). For now, we'll invoke the action
 177          * directly instead. When the bug is fixed, we'll use the following four
 178          * lines instead:
 179          *     robot.keyPress(KeyEvent.VK_SHIFT);
 180          *     robot.keyPress(KeyEvent.VK_PAGE_DOWN);
 181          *     robot.keyRelease(KeyEvent.VK_PAGE_DOWN);
 182          *     robot.keyRelease(KeyEvent.VK_SHIFT);
 183          */
 184 
 185         scrollDownExtendSelection();
 186 
 187         toolkit.realSync();
 188         checkSelection(0, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9);
 189         resetList();
 190         toolkit.realSync();
 191 
 192         // Down
 193         robot.keyPress(KeyEvent.VK_DOWN);
 194         robot.keyRelease(KeyEvent.VK_DOWN);
 195 
 196         toolkit.realSync();
 197         checkSelectionAL(0, 0, 0);
 198         resetList();
 199         toolkit.realSync();
 200 
 201         // L
 202         robot.keyPress(KeyEvent.VK_L);
 203         robot.keyRelease(KeyEvent.VK_L);
 204 
 205         toolkit.realSync();
 206         checkSelectionAL(0, 0, 0);
 207         resetList();
 208         toolkit.realSync();
 209 
 210         // Click item 4
 211         Point p = clickItem4();
 212         robot.mouseMove(p.x, p.y);
 213         robot.mousePress(InputEvent.BUTTON1_MASK);
 214         robot.mouseRelease(InputEvent.BUTTON1_MASK);
 215 
 216 
 217         toolkit.realSync();
 218         checkSelectionAL(4, 4, 4);
 219         resetList();
 220         toolkit.realSync();
 221 
 222 
 223         // Control + Click item 4
 224         robot.keyPress(controlKey);
 225         p = clickItem4();
 226         robot.mouseMove(p.x, p.y);
 227         robot.mousePress(InputEvent.BUTTON1_MASK);
 228         robot.mouseRelease(InputEvent.BUTTON1_MASK);
 229         robot.keyRelease(controlKey);
 230 
 231 
 232         toolkit.realSync();
 233         checkSelectionAL(4, 4, 4);
 234         resetList();
 235         toolkit.realSync();
 236 
 237         // Shift + Click item 4
 238         robot.keyPress(KeyEvent.VK_SHIFT);
 239         p = clickItem4();
 240         robot.mouseMove(p.x, p.y);
 241         robot.mousePress(InputEvent.BUTTON1_MASK);
 242         robot.mouseRelease(InputEvent.BUTTON1_MASK);
 243         robot.keyRelease(KeyEvent.VK_SHIFT);
 244 
 245 
 246         toolkit.realSync();
 247         checkSelectionAL(0, 4, 0, 1, 2, 3, 4);
 248         resetList();
 249         toolkit.realSync();
 250 
 251 
 252         // Control + Shift + Click item 4
 253         robot.keyPress(controlKey);
 254         robot.keyPress(KeyEvent.VK_SHIFT);
 255         p = clickItem4();
 256         robot.mouseMove(p.x, p.y);
 257         robot.mousePress(InputEvent.BUTTON1_MASK);
 258         robot.mouseRelease(InputEvent.BUTTON1_MASK);
 259         robot.keyRelease(KeyEvent.VK_SHIFT);
 260         robot.keyRelease(controlKey);
 261 
 262         toolkit.realSync();
 263         checkSelectionAL(0, 4);
 264         resetList();
 265         toolkit.realSync();
 266     }
 267 
 268     private static DefaultListModel getModel() {
 269         DefaultListModel listModel = new DefaultListModel();
 270         for (int i = 0; i < 10; i++) {
 271             listModel.addElement("List Item " + i);
 272         }
 273         return listModel;
 274     }
 275 
 276     private static Point clickItem4() throws Exception {
 277 
 278         final Point[] result = new Point[1];
 279         SwingUtilities.invokeAndWait(new Runnable() {
 280 
 281             @Override
 282             public void run() {
 283                 Rectangle r = list.getCellBounds(4, 4);
 284                 Point p = new Point(r.x + r.width / 2, r.y + r.height / 2);
 285                 SwingUtilities.convertPointToScreen(p, list);
 286                 result[0] = p;
 287             }
 288         });
 289 
 290         return result[0];
 291     }
 292 
 293     private static void resetList() throws Exception {
 294         SwingUtilities.invokeAndWait(new Runnable() {
 295 
 296             @Override
 297             public void run() {
 298                 list.getSelectionModel().setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
 299                 list.getSelectionModel().clearSelection();
 300                 setAnchorLeadNonThreadSafe();
 301             }
 302         });
 303     }
 304 
 305     private static void scrollDownExtendSelection() throws Exception {
 306         SwingUtilities.invokeAndWait(new Runnable() {
 307 
 308             @Override
 309             public void run() {
 310                 list.getActionMap().get("scrollDownExtendSelection").
 311                         actionPerformed(new ActionEvent(list,
 312                         ActionEvent.ACTION_PERFORMED, null));
 313             }
 314         });
 315     }
 316 
 317     private static void setSelectionMode(final int selectionMode) throws Exception {
 318         SwingUtilities.invokeAndWait(new Runnable() {
 319 
 320             @Override
 321             public void run() {
 322                 list.getSelectionModel().setSelectionMode(selectionMode);
 323                 setAnchorLeadNonThreadSafe();
 324             }
 325         });
 326     }
 327 
 328     private static void setSelectionInterval(final int index0, final int index1) throws Exception {
 329         SwingUtilities.invokeAndWait(new Runnable() {
 330 
 331             @Override
 332             public void run() {
 333                 list.getSelectionModel().setSelectionInterval(index0, index1);
 334                 setAnchorLeadNonThreadSafe();
 335             }
 336         });
 337     }
 338 
 339     private static void setAnchorLead(final int anchorLeadValue) throws Exception {
 340         SwingUtilities.invokeAndWait(new Runnable() {
 341 
 342             @Override
 343             public void run() {
 344                 anchorLead = anchorLeadValue;
 345                 setAnchorLeadNonThreadSafe();
 346             }
 347         });
 348     }
 349 
 350     private static void setAnchorLeadNonThreadSafe() {
 351         list.getSelectionModel().setAnchorSelectionIndex(anchorLead);
 352         ((DefaultListSelectionModel) list.getSelectionModel()).moveLeadSelectionIndex(anchorLead);
 353     }
 354 
 355     private static void createAndShowGUI() {
 356         JFrame frame = new JFrame("bug6462008");
 357         frame.setSize(200, 500);
 358         frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 359 
 360         list = new JList(getModel());
 361         JPanel panel = new JPanel(new BorderLayout());
 362         panel.add(list);
 363         frame.getContentPane().add(panel);
 364 
 365         frame.setVisible(true);
 366     }
 367 
 368     private static void checkSelection(int... sels) throws Exception {
 369         checkSelectionAL(DONT_CARE, DONT_CARE, sels);
 370     }
 371 
 372     private static void checkSelectionAL(final int anchor, final int lead, final int... sels) throws Exception {
 373         SwingUtilities.invokeAndWait(new Runnable() {
 374 
 375             @Override
 376             public void run() {
 377                 checkSelectionNonThreadSafe(anchor, lead, sels);
 378             }
 379         });
 380     }
 381 
 382     private static void checkSelectionNonThreadSafe(int anchor, int lead, int... sels) {
 383         ListSelectionModel lsm = list.getSelectionModel();
 384 
 385         int actualAnchor = lsm.getAnchorSelectionIndex();
 386         int actualLead = lsm.getLeadSelectionIndex();
 387 
 388         if (anchor != DONT_CARE && actualAnchor != anchor) {
 389             throw new RuntimeException("anchor is " + actualAnchor + ", should be " + anchor);
 390         }
 391 
 392         if (lead != DONT_CARE && actualLead != lead) {
 393             throw new RuntimeException("lead is " + actualLead + ", should be " + lead);
 394         }
 395 
 396         Arrays.sort(sels);
 397         boolean[] checks = new boolean[list.getModel().getSize()];
 398         for (int i : sels) {
 399             checks[i] = true;
 400         }
 401 
 402         int index0 = Math.min(lsm.getMinSelectionIndex(), 0);
 403         int index1 = Math.max(lsm.getMaxSelectionIndex(), list.getModel().getSize() - 1);
 404 
 405         for (int i = index0; i <= index1; i++) {
 406             if (lsm.isSelectedIndex(i)) {
 407                 if (i < 0 || i >= list.getModel().getSize() || !checks[i]) {
 408                     throw new RuntimeException(i + " is selected when it should not be");
 409                 }
 410             } else if (i >= 0 && i < list.getModel().getSize() && checks[i]) {
 411                 throw new RuntimeException(i + " is supposed to be selected");
 412             }
 413         }
 414     }
 415 }