1 /*
   2  * Copyright (c) 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 import java.awt.Color;
  25 import java.awt.FlowLayout;
  26 import java.awt.Font;
  27 import java.awt.Graphics;
  28 import java.awt.Graphics2D;
  29 import java.awt.GraphicsConfiguration;
  30 import java.awt.GraphicsDevice;
  31 import java.awt.GraphicsEnvironment;
  32 import java.awt.GridBagConstraints;
  33 import java.awt.GridBagLayout;
  34 import java.awt.HeadlessException;
  35 import java.awt.Image;
  36 import java.awt.Insets;
  37 import java.awt.Rectangle;
  38 import java.awt.event.ActionEvent;
  39 import java.awt.event.ActionListener;
  40 import java.awt.event.WindowAdapter;
  41 import java.awt.event.WindowEvent;
  42 import java.awt.geom.AffineTransform;
  43 import java.awt.image.AbstractMultiResolutionImage;
  44 import java.awt.image.BufferedImage;
  45 import java.awt.image.ImageObserver;
  46 import java.util.Arrays;
  47 import java.util.Collections;
  48 import java.util.List;
  49 import java.util.concurrent.CountDownLatch;
  50 import java.util.concurrent.TimeUnit;
  51 import javax.swing.JButton;
  52 import javax.swing.JFrame;
  53 import javax.swing.JPanel;
  54 import javax.swing.JTextArea;
  55 import javax.swing.SwingUtilities;
  56 
  57 /* @test
  58  * @bug 8175293
  59  * @summary HiDPI (Windows): Swing components have incorrect sizes after
  60  *          changing display resolution
  61  * @run main/manual/othervm WindowResizingOnSetLocationTest
  62  */
  63 public class WindowResizingOnSetLocationTest {
  64 
  65     private static volatile boolean testResult = false;
  66     private static volatile CountDownLatch countDownLatch;
  67     private static TestFrame frame;
  68     private static JFrame mainFrame;
  69 
  70     private static final String INSTRUCTIONS = "INSTRUCTIONS:\n"
  71             + "Verify that a window is properly resized after setting the location"
  72             + " to a display with different DPI.\n"
  73             + "\n"
  74             + "The test is applicable for a multi-monitor system where displays"
  75             + " are configured to have different DPI\n"
  76             + "\n"
  77             + "1. Press Show Frame button\n"
  78             + "The frame appear.\n"
  79             + "2. Check that the string \"scales [ScaleX, ScaleY]\" is painted on the window"
  80             + " where ScaleX and ScaleY are the scales for current display.\n"
  81             + "The scales are calculated as DPI / 96 and are 1 for the DPI value 96"
  82             + " and 2 for the DPI value 192.\n"
  83             + "3. Press 'Move to another display' button.\n"
  84             + "4. Check that the frame appears on the another display.\n"
  85             + "5. Check that the string \"scales [ScaleX, ScaleY]\" is updated"
  86             + " to show the right display scales.\n"
  87             + "6. Check that the window is properly resized.\n"
  88             + "7. Check that the window is properly repainted and does not contain drawing artifacts\n"
  89             + "Try different display positions (left, right, top, bottom).\n"
  90             + "If all tests are passed, press PASS, else press FAIL.\n";
  91 
  92     public static void main(String args[]) throws Exception {
  93 
  94         countDownLatch = new CountDownLatch(1);
  95         SwingUtilities.invokeLater(WindowResizingOnSetLocationTest::createUI);
  96         countDownLatch.await(15, TimeUnit.MINUTES);
  97         if (!testResult) {
  98             throw new RuntimeException("Test fails!");
  99         }
 100     }
 101 
 102     private static void createUI() {
 103 
 104         mainFrame = new JFrame("DPI change test");
 105         GridBagLayout layout = new GridBagLayout();
 106         JPanel mainControlPanel = new JPanel(layout);
 107         JPanel resultButtonPanel = new JPanel(layout);
 108 
 109         GridBagConstraints gbc = new GridBagConstraints();
 110 
 111         JPanel testPanel = new JPanel(new FlowLayout());
 112         JButton frameButton = new JButton("Show Frame");
 113         frameButton.addActionListener((e) -> {
 114             int x = 20;
 115             int y = 10;
 116             int w = 400;
 117             int h = 300;
 118 
 119             frame = new TestFrame(w, h);
 120             frame.setLocation(x, y);
 121             frame.setVisible(true);
 122 
 123         });
 124         testPanel.add(frameButton);
 125 
 126         gbc.gridx = 0;
 127         gbc.gridy = 0;
 128         gbc.fill = GridBagConstraints.HORIZONTAL;
 129         mainControlPanel.add(testPanel, gbc);
 130 
 131         JTextArea instructionTextArea = new JTextArea();
 132         instructionTextArea.setText(INSTRUCTIONS);
 133         instructionTextArea.setEditable(false);
 134         instructionTextArea.setBackground(Color.white);
 135 
 136         gbc.gridx = 0;
 137         gbc.gridy = 1;
 138         gbc.fill = GridBagConstraints.HORIZONTAL;
 139         mainControlPanel.add(instructionTextArea, gbc);
 140 
 141         JButton passButton = new JButton("Pass");
 142         passButton.setActionCommand("Pass");
 143         passButton.addActionListener((ActionEvent e) -> {
 144             testResult = true;
 145             disposeFrames();
 146             countDownLatch.countDown();
 147 
 148         });
 149 
 150         JButton failButton = new JButton("Fail");
 151         failButton.setActionCommand("Fail");
 152         failButton.addActionListener(new ActionListener() {
 153             @Override
 154             public void actionPerformed(ActionEvent e) {
 155                 disposeFrames();
 156                 countDownLatch.countDown();
 157             }
 158         });
 159 
 160         gbc.gridx = 0;
 161         gbc.gridy = 0;
 162         resultButtonPanel.add(passButton, gbc);
 163 
 164         gbc.gridx = 1;
 165         gbc.gridy = 0;
 166         resultButtonPanel.add(failButton, gbc);
 167 
 168         gbc.gridx = 0;
 169         gbc.gridy = 2;
 170         mainControlPanel.add(resultButtonPanel, gbc);
 171 
 172         mainFrame.add(mainControlPanel);
 173         mainFrame.pack();
 174 
 175         mainFrame.addWindowListener(new WindowAdapter() {
 176 
 177             @Override
 178             public void windowClosing(WindowEvent e) {
 179                 disposeFrames();
 180                 countDownLatch.countDown();
 181             }
 182         });
 183         mainFrame.setVisible(true);
 184     }
 185 
 186     private static void disposeFrames() {
 187         if (frame != null && frame.isVisible()) {
 188             frame.dispose();
 189         }
 190 
 191         if (mainFrame != null && mainFrame.isVisible()) {
 192             mainFrame.dispose();
 193         }
 194     }
 195 
 196     static class TestFrame extends JFrame {
 197 
 198         private final TestMultiResolutionImage mrImage;
 199 
 200         public TestFrame(int width, int height) throws HeadlessException {
 201             super("Test Frame");
 202             setSize(width, height);
 203             mrImage = new TestMultiResolutionImage(width, height);
 204 
 205             JPanel panel = new JPanel(new FlowLayout()) {
 206                 @Override
 207                 public void paint(Graphics g) {
 208                     super.paint(g);
 209                     AffineTransform tx = ((Graphics2D) g).getTransform();
 210                     mrImage.scaleX = tx.getScaleX();
 211                     mrImage.scaleY = tx.getScaleY();
 212                     Insets insets = getInsets();
 213                     g.drawImage(mrImage, insets.left, insets.bottom, null);
 214                 }
 215             };
 216 
 217             JButton button = new JButton("Move to another display");
 218             button.addActionListener((e) -> {
 219                 GraphicsConfiguration config = getGraphicsConfiguration();
 220                 GraphicsDevice device = config.getDevice();
 221 
 222                 GraphicsDevice[] devices = GraphicsEnvironment
 223                         .getLocalGraphicsEnvironment()
 224                         .getScreenDevices();
 225 
 226                 boolean found = false;
 227                 for (GraphicsDevice dev : devices) {
 228                     if (!dev.equals(device)) {
 229                         found = true;
 230                         Rectangle bounds = dev.getDefaultConfiguration().getBounds();
 231 
 232                         AffineTransform tx = config.getDefaultTransform();
 233                         int x = (int) Math.round(bounds.x / tx.getScaleX()) + 15;
 234                         int y = (int) Math.round(bounds.y / tx.getScaleY()) + 15;
 235                         frame.setLocation(x, y);
 236                         break;
 237                     }
 238                 }
 239 
 240                 if (!found) {
 241                     System.out.println("Another display not found!");
 242                 }
 243             });
 244 
 245             panel.add(button);
 246             add(panel);
 247         }
 248     }
 249 
 250     static class TestMultiResolutionImage extends AbstractMultiResolutionImage {
 251 
 252         final int width;
 253         final int height;
 254         double scaleX;
 255         double scaleY;
 256 
 257         public TestMultiResolutionImage(int width, int height) {
 258             this.width = width;
 259             this.height = height;
 260         }
 261 
 262         @Override
 263         public int getWidth(ImageObserver observer) {
 264             return width;
 265         }
 266 
 267         @Override
 268         public int getHeight(ImageObserver observer) {
 269             return height;
 270         }
 271 
 272         @Override
 273         protected Image getBaseImage() {
 274             return getResolutionVariant(width, height);
 275         }
 276 
 277         @Override
 278         public Image getResolutionVariant(double destImageWidth, double destImageHeight) {
 279 
 280             int w = (int) destImageWidth;
 281             int h = (int) destImageHeight;
 282 
 283             BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
 284             Graphics2D g = img.createGraphics();
 285             g.scale(scaleX, scaleY);
 286             int red = (int) (255 / scaleX);
 287             int green = (int) (250 / scaleX);
 288             int blue = (int) (20 / scaleX);
 289             g.setColor(new Color(red, green, blue));
 290             g.fillRect(0, 0, width, height);
 291 
 292             g.setColor(Color.decode("#87CEFA"));
 293             Font f = g.getFont();
 294             g.setFont(new Font(f.getName(), Font.BOLD, 24));
 295             g.drawString(String.format("scales: [%1.2fx, %1.2fx]", scaleX, scaleY),
 296                     width / 6, height / 2);
 297 
 298             g.dispose();
 299             return img;
 300         }
 301 
 302         @Override
 303         public List<Image> getResolutionVariants() {
 304             return Collections.unmodifiableList(Arrays.asList(getBaseImage()));
 305         }
 306     }
 307 }