1 /*
   2  * Copyright (c) 2016, 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 WindowResizingOnDPIChangingTest
  61  */
  62 public class WindowResizingOnDPIChangingTest {
  63 
  64     private static volatile boolean testResult = false;
  65     private static volatile CountDownLatch countDownLatch;
  66     private static TestFrame undecoratedFrame;
  67     private static TestFrame decoratedFrame;
  68     private static JFrame mainFrame;
  69 
  70     private static final String INSTRUCTIONS = "INSTRUCTIONS:\n"
  71             + "Verify that window is properly resized after the display DPI updating.\n"
  72             + "\n"
  73             + "The test is applicable for OSes that allows to change the display DPI size\n"
  74             + "without the system rebooting (like Windows 8.1 and higher). Press PASS for other\n"
  75             + "systems. \n"
  76             + "\n"
  77             + "1. Set the display DPI size to 192 (DPI scale factor 200%)\n"
  78             + "2. Press Show Frames button\n"
  79             + "Two frames decorated and undecorated appear.\n"
  80             + "3. Check that the string \"scale 2x\" is painted on the windows.\n"
  81             + "4. Set the display DPI size to 96 (DPI scale factor 100%)\n"
  82             + "5. Check that the string \"scale: 1x\" is painted on the windows.\n"
  83             + "6. Check that the windows are properly resized in the same way as native applications\n"
  84             + "7. Check that the windows are properly repainted and do not contain drawing artifacts\n"
  85             + "If so, press PASS, else press FAIL.\n";
  86 
  87     public static void main(String args[]) throws Exception {
  88 
  89         countDownLatch = new CountDownLatch(1);
  90         SwingUtilities.invokeLater(WindowResizingOnDPIChangingTest::createUI);
  91         countDownLatch.await(15, TimeUnit.MINUTES);
  92         if (!testResult) {
  93             throw new RuntimeException("Test fails!");
  94         }
  95     }
  96 
  97     private static void createUI() {
  98 
  99         mainFrame = new JFrame("DPI change test");
 100         GridBagLayout layout = new GridBagLayout();
 101         JPanel mainControlPanel = new JPanel(layout);
 102         JPanel resultButtonPanel = new JPanel(layout);
 103 
 104         GridBagConstraints gbc = new GridBagConstraints();
 105 
 106         JPanel testPanel = new JPanel(new FlowLayout());
 107         JButton frameButton = new JButton("Show Frames");
 108         frameButton.addActionListener((e) -> {
 109             int x = 20;
 110             int y = 10;
 111             int w = 400;
 112             int h = 300;
 113 
 114             undecoratedFrame = new TestFrame(w, h, true);
 115             undecoratedFrame.setLocation(x, y);
 116             undecoratedFrame.setVisible(true);
 117 
 118             decoratedFrame = new TestFrame(w, h, false);
 119             decoratedFrame.setLocation(x + w + 10, y);
 120             decoratedFrame.setVisible(true);
 121 
 122         });
 123         testPanel.add(frameButton);
 124 
 125         gbc.gridx = 0;
 126         gbc.gridy = 0;
 127         gbc.fill = GridBagConstraints.HORIZONTAL;
 128         mainControlPanel.add(testPanel, gbc);
 129 
 130         JTextArea instructionTextArea = new JTextArea();
 131         instructionTextArea.setText(INSTRUCTIONS);
 132         instructionTextArea.setEditable(false);
 133         instructionTextArea.setBackground(Color.white);
 134 
 135         gbc.gridx = 0;
 136         gbc.gridy = 1;
 137         gbc.fill = GridBagConstraints.HORIZONTAL;
 138         mainControlPanel.add(instructionTextArea, gbc);
 139 
 140         JButton passButton = new JButton("Pass");
 141         passButton.setActionCommand("Pass");
 142         passButton.addActionListener((ActionEvent e) -> {
 143             testResult = true;
 144             disposeFrames();
 145             countDownLatch.countDown();
 146 
 147         });
 148 
 149         JButton failButton = new JButton("Fail");
 150         failButton.setActionCommand("Fail");
 151         failButton.addActionListener(new ActionListener() {
 152             @Override
 153             public void actionPerformed(ActionEvent e) {
 154                 disposeFrames();
 155                 countDownLatch.countDown();
 156             }
 157         });
 158 
 159         gbc.gridx = 0;
 160         gbc.gridy = 0;
 161         resultButtonPanel.add(passButton, gbc);
 162 
 163         gbc.gridx = 1;
 164         gbc.gridy = 0;
 165         resultButtonPanel.add(failButton, gbc);
 166 
 167         gbc.gridx = 0;
 168         gbc.gridy = 2;
 169         mainControlPanel.add(resultButtonPanel, gbc);
 170 
 171         mainFrame.add(mainControlPanel);
 172         mainFrame.pack();
 173 
 174         mainFrame.addWindowListener(new WindowAdapter() {
 175 
 176             @Override
 177             public void windowClosing(WindowEvent e) {
 178                 disposeFrames();
 179                 countDownLatch.countDown();
 180             }
 181         });
 182         mainFrame.setVisible(true);
 183     }
 184 
 185     private static void disposeFrames() {
 186         if (decoratedFrame != null && decoratedFrame.isVisible()) {
 187             decoratedFrame.dispose();
 188         }
 189         if (undecoratedFrame != null && undecoratedFrame.isVisible()) {
 190             undecoratedFrame.dispose();
 191         }
 192         if (mainFrame != null && mainFrame.isVisible()) {
 193             mainFrame.dispose();
 194         }
 195     }
 196 
 197     static class TestFrame extends Frame {
 198 
 199         private final TestMultiResolutionImage mrImage;
 200 
 201         public TestFrame(int width, int height, boolean undecorated) throws HeadlessException {
 202             super("Test Frame. Undecorated: " + undecorated);
 203             setSize(width, height);
 204             mrImage = new TestMultiResolutionImage(width, height);
 205 
 206             setUndecorated(undecorated);
 207             Panel panel = new Panel(new FlowLayout()) {
 208                 @Override
 209                 public void paint(Graphics g) {
 210                     super.paint(g);
 211                     AffineTransform tx = ((Graphics2D) g).getTransform();
 212                     mrImage.scaleX = tx.getScaleX();
 213                     mrImage.scaleY = tx.getScaleY();
 214                     Insets insets = getInsets();
 215                     g.drawImage(mrImage, insets.left, insets.bottom, null);
 216                 }
 217             };
 218             add(panel);
 219         }
 220     }
 221 
 222     static class TestMultiResolutionImage extends AbstractMultiResolutionImage {
 223 
 224         final int width;
 225         final int height;
 226         double scaleX;
 227         double scaleY;
 228 
 229         public TestMultiResolutionImage(int width, int height) {
 230             this.width = width;
 231             this.height = height;
 232         }
 233 
 234         @Override
 235         public int getWidth(ImageObserver observer) {
 236             return width;
 237         }
 238 
 239         @Override
 240         public int getHeight(ImageObserver observer) {
 241             return height;
 242         }
 243 
 244         @Override
 245         protected Image getBaseImage() {
 246             return getResolutionVariant(width, height);
 247         }
 248 
 249         @Override
 250         public Image getResolutionVariant(double destImageWidth, double destImageHeight) {
 251 
 252             int w = (int) destImageWidth;
 253             int h = (int) destImageHeight;
 254 
 255             BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
 256             Graphics2D g = img.createGraphics();
 257             g.scale(scaleX, scaleY);
 258             int red = (int) (255 / scaleX);
 259             int green = (int) (250 / scaleX);
 260             int blue = (int) (20 / scaleX);
 261             g.setColor(new Color(red, green, blue));
 262             g.fillRect(0, 0, width, height);
 263 
 264             g.setColor(Color.decode("#87CEFA"));
 265             Font f = g.getFont();
 266             g.setFont(new Font(f.getName(), Font.BOLD, 24));
 267             g.drawString(String.format("scales: [%1.2fx, %1.2fx]", scaleX, scaleY),
 268                     width / 6, height / 2);
 269 
 270             g.dispose();
 271             return img;
 272         }
 273 
 274         @Override
 275         public List<Image> getResolutionVariants() {
 276             return Collections.unmodifiableList(Arrays.asList(getBaseImage()));
 277         }
 278     }
 279 }