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