1 /*
   2  * Copyright (c) 2012, 2017, 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      7123767
  27  *
  28  * @summary  Check if a tooltip location in Multi-Monitor
  29  *           configurations is correct.
  30  *           If the configurations number per device exceeds 5,
  31  *           then some 5 random configurations will be checked.
  32  *           Please Use -Dseed=X to set the random generator seed
  33  *           (if necessary).
  34  *
  35  * @author   Vladislav Karnaukhov
  36  *
  37  * @key      headful randomness
  38  *
  39  * @modules  java.desktop/sun.awt
  40  * @library  /test/lib
  41  *
  42  * @run      main/timeout=300 bug7123767
  43  */
  44 
  45 import javax.swing.*;
  46 import javax.swing.plaf.metal.MetalLookAndFeel;
  47 import java.awt.*;
  48 import java.awt.event.MouseEvent;
  49 import java.lang.reflect.InvocationTargetException;
  50 
  51 import java.util.List;
  52 import java.util.ArrayList;
  53 import java.util.Collections;
  54 import java.util.Random;
  55 
  56 import jdk.test.lib.RandomFactory;
  57 
  58 
  59 public class bug7123767 extends JFrame {
  60 
  61     // maximum number of GraphicsConfigurations checked per GraphicsDevice
  62     private static final int MAX_N_CONFIGS = 5;
  63     private static final List<GraphicsConfiguration> CONFIGS = getConfigs();
  64 
  65     private static List<GraphicsConfiguration> getConfigs() {
  66 
  67         Random rnd = RandomFactory.getRandom();
  68 
  69         List<GraphicsConfiguration> configs = new ArrayList<>();
  70 
  71         GraphicsEnvironment ge =
  72                 GraphicsEnvironment.getLocalGraphicsEnvironment();
  73         GraphicsDevice[] devices = ge.getScreenDevices();
  74 
  75         for (GraphicsDevice device : devices) {
  76             GraphicsConfiguration[] allConfigs = device.getConfigurations();
  77             int nConfigs = allConfigs.length;
  78             if (nConfigs <= MAX_N_CONFIGS) {
  79                 Collections.addAll(configs, allConfigs);
  80             } else { // see JDK-8159454
  81                 System.out.println("check only " + MAX_N_CONFIGS +
  82                     " configurations for device " + device);
  83                 configs.add(device.getDefaultConfiguration()); // check default
  84                 for (int j = 0; j < MAX_N_CONFIGS - 1; j++) {
  85                     int k = rnd.nextInt(nConfigs);
  86                     configs.add(allConfigs[k]);
  87                 }
  88             }
  89         }
  90 
  91         return configs;
  92     }
  93 
  94 
  95     private static class TestFactory extends PopupFactory {
  96 
  97         private static TestFactory newFactory = new TestFactory();
  98         private static PopupFactory oldFactory;
  99 
 100         private TestFactory() {
 101             super();
 102         }
 103 
 104         public static void install() {
 105             if (oldFactory == null) {
 106                 oldFactory = getSharedInstance();
 107                 setSharedInstance(newFactory);
 108             }
 109         }
 110 
 111         public static void uninstall() {
 112             if (oldFactory != null) {
 113                 setSharedInstance(oldFactory);
 114             }
 115         }
 116 
 117         // Actual test happens here
 118         @Override
 119         public Popup getPopup(Component owner, Component contents, int x, int y) {
 120 
 121             GraphicsConfiguration mouseGC =
 122                 testGC(MouseInfo.getPointerInfo().getLocation());
 123 
 124             if (mouseGC == null) {
 125                 throw new RuntimeException("Can't find GraphicsConfiguration "
 126                         + "that mouse pointer belongs to");
 127             }
 128 
 129             GraphicsConfiguration tipGC = testGC(new Point(x, y));
 130             if (tipGC == null) {
 131                 throw new RuntimeException(
 132                         "Can't find GraphicsConfiguration that tip belongs to");
 133             }
 134 
 135             if (!mouseGC.equals(tipGC)) {
 136                 throw new RuntimeException("Mouse and tip GCs are not equal");
 137             }
 138 
 139             return super.getPopup(owner, contents, x, y);
 140         }
 141 
 142         private static GraphicsConfiguration testGC(Point pt) {
 143 
 144             for (GraphicsConfiguration config: CONFIGS) {
 145 
 146                 Rectangle rect = config.getBounds();
 147                 Insets insets =
 148                     Toolkit.getDefaultToolkit().getScreenInsets(config);
 149                 adjustInsets(rect, insets);
 150                 if (rect.contains(pt)) { return config; }
 151             }
 152 
 153             return null;
 154         }
 155     }
 156 
 157     private static final int MARGIN = 10;
 158     private static bug7123767 frame;
 159     private static Robot robot;
 160 
 161     public static void main(String[] args) throws Exception {
 162 
 163         UIManager.setLookAndFeel(new MetalLookAndFeel());
 164         setUp();
 165         testToolTip();
 166         TestFactory.uninstall();
 167         if (frame != null) { frame.dispose(); }
 168     }
 169 
 170     // Creates a window that is stretched across all available monitors
 171     // and adds itself as ContainerListener to track tooltips drawing
 172     private bug7123767() {
 173 
 174         super();
 175 
 176         ToolTipManager.sharedInstance().setInitialDelay(0);
 177         setDefaultCloseOperation(DISPOSE_ON_CLOSE);
 178         TestFactory.install();
 179 
 180         JLabel label1 = new JLabel("no preferred location");
 181         label1.setToolTipText("tip");
 182         add(label1, BorderLayout.WEST);
 183 
 184         JLabel label2 = new JLabel("preferred location (20000, 20000)") {
 185             public Point getToolTipLocation(MouseEvent event) {
 186                 return new Point(20000, 20000);
 187             }
 188         };
 189 
 190         label2.setToolTipText("tip");
 191         add(label2, BorderLayout.EAST);
 192 
 193         setUndecorated(true);
 194         pack();
 195 
 196         Rectangle rect = new Rectangle();
 197 
 198         for (GraphicsConfiguration config: CONFIGS) {
 199 
 200             Insets localInsets =
 201                 Toolkit.getDefaultToolkit().getScreenInsets(config);
 202             Rectangle localRect = config.getBounds();
 203             adjustInsets(localRect, localInsets);
 204             rect.add(localRect);
 205         }
 206 
 207         setBounds(rect);
 208     }
 209 
 210     private static void setUp() throws InterruptedException, InvocationTargetException {
 211         SwingUtilities.invokeAndWait(new Runnable() {
 212             @Override
 213             public void run() {
 214                 frame = new bug7123767();
 215                 frame.setVisible(true);
 216             }
 217         });
 218     }
 219 
 220     // Moves mouse pointer to the corners of every GraphicsConfiguration
 221     private static void testToolTip() throws AWTException {
 222 
 223         robot = new Robot();
 224         robot.setAutoDelay(20);
 225         robot.waitForIdle();
 226 
 227         for (GraphicsConfiguration config: CONFIGS) {
 228 
 229             Rectangle rect = config.getBounds();
 230             Insets insets = Toolkit.getDefaultToolkit().getScreenInsets(config);
 231             adjustInsets(rect, insets);
 232 
 233             // Upper left
 234             glide(rect.x + rect.width / 2, rect.y + rect.height / 2,
 235                     rect.x + MARGIN, rect.y + MARGIN);
 236             robot.waitForIdle();
 237 
 238             // Lower left
 239             glide(rect.x + rect.width / 2, rect.y + rect.height / 2,
 240                     rect.x + MARGIN, rect.y + rect.height - MARGIN);
 241             robot.waitForIdle();
 242 
 243             // Upper right
 244             glide(rect.x + rect.width / 2, rect.y + rect.height / 2,
 245                     rect.x + rect.width - MARGIN, rect.y + MARGIN);
 246             robot.waitForIdle();
 247 
 248             // Lower right
 249             glide(rect.x + rect.width / 2, rect.y + rect.height / 2,
 250                     rect.x + rect.width - MARGIN, rect.y + rect.height - MARGIN);
 251 
 252             robot.waitForIdle();
 253         }
 254     }
 255 
 256     private static void glide(int x0, int y0, int x1, int y1) throws AWTException {
 257         if (robot == null) {
 258             robot = new Robot();
 259             robot.setAutoDelay(20);
 260         }
 261 
 262         float dmax = (float) Math.max(Math.abs(x1 - x0), Math.abs(y1 - y0));
 263         float dx = (x1 - x0) / dmax;
 264         float dy = (y1 - y0) / dmax;
 265 
 266         robot.mouseMove(x0, y0);
 267         for (int i = 1; i <= dmax; i += 10) {
 268             robot.mouseMove((int) (x0 + dx * i), (int) (y0 + dy * i));
 269         }
 270     }
 271 
 272     private static void adjustInsets(Rectangle rect, final Insets insets) {
 273         rect.x += insets.left;
 274         rect.y += insets.top;
 275         rect.width -= (insets.left + insets.right);
 276         rect.height -= (insets.top + insets.bottom);
 277     }
 278 }