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