8176097: Window set location to a display with different DPI does not properly work
authoralexsch
Mon, 06 Mar 2017 08:06:54 +0300
changeset 44152 af74e15ef830
parent 44151 bb5790b4b278
child 44153 43fac9627342
8176097: Window set location to a display with different DPI does not properly work 8173972: createScreenCapture not working as expected on multimonitor setup with different DPI scales Reviewed-by: serb, azvegint
jdk/src/java.desktop/share/classes/java/awt/Robot.java
jdk/src/java.desktop/share/classes/sun/swing/SwingUtilities2.java
jdk/src/java.desktop/windows/classes/sun/awt/windows/WWindowPeer.java
jdk/test/java/awt/Robot/HiDPIScreenCapture/RobotMultiDPIScreenTest.java
jdk/test/java/awt/Window/WindowResizingOnDPIChanging/WindowResizingOnSetLocationTest.java
--- a/jdk/src/java.desktop/share/classes/java/awt/Robot.java	Sun Mar 05 23:02:04 2017 +0300
+++ b/jdk/src/java.desktop/share/classes/java/awt/Robot.java	Mon Mar 06 08:06:54 2017 +0300
@@ -41,6 +41,7 @@
 import sun.awt.ComponentFactory;
 import sun.awt.SunToolkit;
 import sun.awt.image.SunWritableRaster;
+import sun.swing.SwingUtilities2;
 
 /**
  * This class is used to generate native system input events
@@ -499,9 +500,15 @@
         // need to sync the toolkit prior to grabbing the pixels since in some
         // cases rendering to the screen may be delayed
         Toolkit.getDefaultToolkit().sync();
-        AffineTransform tx = GraphicsEnvironment.
-                getLocalGraphicsEnvironment().getDefaultScreenDevice().
-                getDefaultConfiguration().getDefaultTransform();
+
+        GraphicsConfiguration gc = GraphicsEnvironment
+                .getLocalGraphicsEnvironment()
+                .getDefaultScreenDevice().
+                getDefaultConfiguration();
+        gc = SwingUtilities2.getGraphicsConfigurationAtPoint(
+                gc, screenRect.getCenterX(), screenRect.getCenterY());
+
+        AffineTransform tx = gc.getDefaultTransform();
         double uiScaleX = tx.getScaleX();
         double uiScaleY = tx.getScaleY();
         int pixels[];
--- a/jdk/src/java.desktop/share/classes/sun/swing/SwingUtilities2.java	Sun Mar 05 23:02:04 2017 +0300
+++ b/jdk/src/java.desktop/share/classes/sun/swing/SwingUtilities2.java	Mon Mar 06 08:06:54 2017 +0300
@@ -2197,6 +2197,35 @@
     }
 
     /**
+     *
+     * Returns the graphics configuration which bounds contain the given
+     * point
+     *
+     * @param current the default configuration which is checked in the first place
+     * @param x the x coordinate of the given point
+     * @param y the y coordinate of the given point
+     * @return the graphics configuration
+     */
+    public static GraphicsConfiguration getGraphicsConfigurationAtPoint(GraphicsConfiguration current, double x, double y) {
+
+        if (current.getBounds().contains(x, y)) {
+            return current;
+        }
+
+        GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
+        GraphicsDevice[] devices = env.getScreenDevices();
+
+        for (GraphicsDevice device : devices) {
+            GraphicsConfiguration config = device.getDefaultConfiguration();
+            if (config.getBounds().contains(x, y)) {
+                return config;
+            }
+        }
+
+        return current;
+    }
+
+    /**
      * Used to listen to "blit" repaints in RepaintManager.
      */
     public interface RepaintListener {
--- a/jdk/src/java.desktop/windows/classes/sun/awt/windows/WWindowPeer.java	Sun Mar 05 23:02:04 2017 +0300
+++ b/jdk/src/java.desktop/windows/classes/sun/awt/windows/WWindowPeer.java	Mon Mar 06 08:06:54 2017 +0300
@@ -38,6 +38,7 @@
 import sun.awt.*;
 
 import sun.java2d.pipe.Region;
+import sun.swing.SwingUtilities2;
 
 public class WWindowPeer extends WPanelPeer implements WindowPeer,
        DisplayChangedListener
@@ -630,9 +631,42 @@
          sysW = width;
          sysH = height;
 
+         int cx = x + width / 2;
+         int cy = y + height / 2;
+         GraphicsConfiguration current = getGraphicsConfiguration();
+         GraphicsConfiguration other = SwingUtilities2.getGraphicsConfigurationAtPoint(current, cx, cy);
+         if (!current.equals(other)) {
+             AffineTransform tx = other.getDefaultTransform();
+             double otherScaleX = tx.getScaleX();
+             double otherScaleY = tx.getScaleY();
+             initScales();
+             if (scaleX != otherScaleX || scaleY != otherScaleY) {
+                 x = (int) Math.floor(x * otherScaleX / scaleX);
+                 y = (int) Math.floor(y * otherScaleY / scaleY);
+             }
+         }
+
          super.setBounds(x, y, width, height, op);
      }
 
+    private final void initScales() {
+
+        if (scaleX >= 1 && scaleY >= 1) {
+            return;
+        }
+
+        GraphicsConfiguration gc = getGraphicsConfiguration();
+        if (gc instanceof Win32GraphicsConfig) {
+            Win32GraphicsDevice gd = ((Win32GraphicsConfig) gc).getDevice();
+            scaleX = gd.getDefaultScaleX();
+            scaleY = gd.getDefaultScaleY();
+        } else {
+            AffineTransform tx = gc.getDefaultTransform();
+            scaleX = (float) tx.getScaleX();
+            scaleY = (float) tx.getScaleY();
+        }
+    }
+
     @Override
     public void print(Graphics g) {
         // We assume we print the whole frame,
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/awt/Robot/HiDPIScreenCapture/RobotMultiDPIScreenTest.java	Mon Mar 06 08:06:54 2017 +0300
@@ -0,0 +1,269 @@
+/*
+ * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.awt.BasicStroke;
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.GraphicsConfiguration;
+import java.awt.GraphicsDevice;
+import java.awt.GraphicsEnvironment;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Image;
+import java.awt.Rectangle;
+import java.awt.Robot;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.awt.geom.AffineTransform;
+import java.awt.image.BufferedImage;
+import java.awt.image.MultiResolutionImage;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+import javax.swing.JTextArea;
+import javax.swing.SwingUtilities;
+
+/* @test
+ * @bug 8173972
+ * @summary createScreenCapture not working as expected on multimonitor setup
+ *          with different DPI scales.
+ * @run main/manual/othervm RobotMultiDPIScreenTest
+ */
+public class RobotMultiDPIScreenTest {
+
+    private static volatile boolean testResult = false;
+    private static volatile CountDownLatch countDownLatch;
+    private static JFrame mainFrame;
+    private static Rectangle maxBounds;
+    private static Rectangle[] screenBounds;
+    private static double[][] scales;
+
+    private static final String INSTRUCTIONS = "INSTRUCTIONS:\n"
+            + "Verify that screenshots are properly taken from monitors"
+            + " with different DPI.\n"
+            + "\n"
+            + "The test is applicable for a multi-monitor system where displays"
+            + " are configured to have different DPI\n"
+            + "\n"
+            + "1. Press Take Screenshots button\n"
+            + "Check that screenshots shown on the panel are properly taken.\n";
+
+    public static void main(String args[]) throws Exception {
+
+        countDownLatch = new CountDownLatch(1);
+        SwingUtilities.invokeLater(RobotMultiDPIScreenTest::createUI);
+        countDownLatch.await(15, TimeUnit.MINUTES);
+        if (!testResult) {
+            throw new RuntimeException("Test fails!");
+        }
+    }
+
+    private static void createUI() {
+
+        initScreenBounds();
+
+        mainFrame = new JFrame("DPI change test");
+        GridBagLayout layout = new GridBagLayout();
+        JPanel mainControlPanel = new JPanel(layout);
+        JPanel resultButtonPanel = new JPanel(layout);
+
+        GridBagConstraints gbc = new GridBagConstraints();
+
+        JPanel testPanel = new JPanel(new BorderLayout());
+
+        final BufferedImage screensImage = getScreenImages();
+        final JPanel screensPanel = new JPanel() {
+
+            @Override
+            public void paint(Graphics g) {
+                super.paint(g);
+                g.drawImage(screensImage, 0, 0, getWidth(), getHeight(), this);
+            }
+        };
+
+        screensPanel.setPreferredSize(new Dimension(400, 200));
+
+        JButton frameButton = new JButton("Take Screenshots");
+        frameButton.addActionListener((e) -> {
+
+            try {
+                Robot robot = new Robot();
+                Graphics2D g = screensImage.createGraphics();
+                g.translate(-maxBounds.x, -maxBounds.y);
+
+                for (Rectangle rect : screenBounds) {
+                    MultiResolutionImage mrImage = robot.createMultiResolutionScreenCapture(rect);
+
+                    List<Image> resolutionVariants = mrImage.getResolutionVariants();
+                    Image rvImage = resolutionVariants.get(resolutionVariants.size() - 1);
+                    g.drawImage(rvImage, rect.x, rect.y, rect.width, rect.height, null);
+                }
+
+                g.dispose();
+                screensPanel.repaint();
+            } catch (Exception ex) {
+                throw new RuntimeException(ex);
+            }
+        });
+
+        testPanel.add(screensPanel, BorderLayout.CENTER);
+        testPanel.add(frameButton, BorderLayout.SOUTH);
+
+        gbc.gridx = 0;
+        gbc.gridy = 0;
+        gbc.fill = GridBagConstraints.HORIZONTAL;
+        mainControlPanel.add(testPanel, gbc);
+
+        JTextArea instructionTextArea = new JTextArea();
+        instructionTextArea.setText(INSTRUCTIONS);
+        instructionTextArea.setEditable(false);
+        instructionTextArea.setBackground(Color.white);
+
+        gbc.gridx = 0;
+        gbc.gridy = 1;
+        gbc.fill = GridBagConstraints.HORIZONTAL;
+        mainControlPanel.add(instructionTextArea, gbc);
+
+        JButton passButton = new JButton("Pass");
+        passButton.setActionCommand("Pass");
+        passButton.addActionListener((ActionEvent e) -> {
+            testResult = true;
+            disposeFrames();
+            countDownLatch.countDown();
+
+        });
+
+        JButton failButton = new JButton("Fail");
+        failButton.setActionCommand("Fail");
+        failButton.addActionListener(new ActionListener() {
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                disposeFrames();
+                countDownLatch.countDown();
+            }
+        });
+
+        gbc.gridx = 0;
+        gbc.gridy = 0;
+        resultButtonPanel.add(passButton, gbc);
+
+        gbc.gridx = 1;
+        gbc.gridy = 0;
+        resultButtonPanel.add(failButton, gbc);
+
+        gbc.gridx = 0;
+        gbc.gridy = 2;
+        mainControlPanel.add(resultButtonPanel, gbc);
+
+        mainFrame.add(mainControlPanel);
+        mainFrame.pack();
+
+        mainFrame.addWindowListener(new WindowAdapter() {
+
+            @Override
+            public void windowClosing(WindowEvent e) {
+                disposeFrames();
+                countDownLatch.countDown();
+            }
+        });
+        mainFrame.setVisible(true);
+    }
+
+    private static void disposeFrames() {
+        if (mainFrame != null && mainFrame.isVisible()) {
+            mainFrame.dispose();
+        }
+    }
+
+    static void initScreenBounds() {
+
+        GraphicsDevice[] devices = GraphicsEnvironment
+                .getLocalGraphicsEnvironment()
+                .getScreenDevices();
+
+        screenBounds = new Rectangle[devices.length];
+        scales = new double[devices.length][2];
+        for (int i = 0; i < devices.length; i++) {
+            GraphicsConfiguration gc = devices[i].getDefaultConfiguration();
+            screenBounds[i] = gc.getBounds();
+            AffineTransform tx = gc.getDefaultTransform();
+            scales[i][0] = tx.getScaleX();
+            scales[i][1] = tx.getScaleY();
+        }
+
+        maxBounds = screenBounds[0];
+        for (int i = 0; i < screenBounds.length; i++) {
+            maxBounds = maxBounds.union(screenBounds[i]);
+        }
+    }
+
+    private static Rectangle getCenterRect(Rectangle rect) {
+        int w = rect.width / 2;
+        int h = rect.height / 2;
+        int x = rect.x + w / 2;
+        int y = rect.y + h / 2;
+
+        return new Rectangle(x, y, w, h);
+    }
+
+    static BufferedImage getScreenImages() {
+
+        final BufferedImage img = new BufferedImage(maxBounds.width, maxBounds.height, BufferedImage.TYPE_INT_RGB);
+        Graphics2D g = img.createGraphics();
+        g.setColor(Color.WHITE);
+        g.fillRect(0, 0, maxBounds.width, maxBounds.height);
+        g.translate(-maxBounds.x, -maxBounds.y);
+
+        g.setStroke(new BasicStroke(8f));
+        for (int i = 0; i < screenBounds.length; i++) {
+            Rectangle r = screenBounds[i];
+            g.setColor(Color.BLACK);
+            g.drawRect(r.x, r.y, r.width, r.height);
+
+            g.setColor(Color.ORANGE);
+            Rectangle cr = getCenterRect(r);
+            g.fillRect(cr.x, cr.y, cr.width, cr.height);
+
+            double scaleX = scales[i][0];
+            double scaleY = scales[i][1];
+            float fontSize = maxBounds.height / 7;
+            g.setFont(g.getFont().deriveFont(fontSize));
+            g.setColor(Color.BLUE);
+            g.drawString(String.format("Scale: [%2.1f, %2.1f]", scaleX, scaleY),
+                    r.x + r.width / 8, r.y + r.height / 2);
+
+        }
+
+        g.dispose();
+
+        return img;
+    }
+}
--- a/jdk/test/java/awt/Window/WindowResizingOnDPIChanging/WindowResizingOnSetLocationTest.java	Sun Mar 05 23:02:04 2017 +0300
+++ b/jdk/test/java/awt/Window/WindowResizingOnDPIChanging/WindowResizingOnSetLocationTest.java	Mon Mar 06 08:06:54 2017 +0300
@@ -21,7 +21,10 @@
  * questions.
  */
 
+import java.awt.BasicStroke;
+import java.awt.BorderLayout;
 import java.awt.Color;
+import java.awt.Dimension;
 import java.awt.FlowLayout;
 import java.awt.Font;
 import java.awt.Graphics;
@@ -49,15 +52,15 @@
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import javax.swing.JButton;
+import javax.swing.JComponent;
 import javax.swing.JFrame;
 import javax.swing.JPanel;
 import javax.swing.JTextArea;
 import javax.swing.SwingUtilities;
 
 /* @test
- * @bug 8175293
- * @summary HiDPI (Windows): Swing components have incorrect sizes after
- *          changing display resolution
+ * @bug 8175293 8176097
+ * @summary Window set location to a display with different DPI does not properly work
  * @run main/manual/othervm WindowResizingOnSetLocationTest
  */
 public class WindowResizingOnSetLocationTest {
@@ -66,6 +69,10 @@
     private static volatile CountDownLatch countDownLatch;
     private static TestFrame frame;
     private static JFrame mainFrame;
+    private static Rectangle[] screenBounds;
+    private static double[][] scales;
+    private static int screen1 = -1;
+    private static int screen2 = -1;
 
     private static final String INSTRUCTIONS = "INSTRUCTIONS:\n"
             + "Verify that a window is properly resized after setting the location"
@@ -75,7 +82,10 @@
             + " are configured to have different DPI\n"
             + "\n"
             + "1. Press Show Frame button\n"
-            + "The frame appear.\n"
+            + "  (note that the button is disabled in case there are no two monitors"
+            + " with different DPI)\n"
+            + "The frame should appear in the center of the display (either"
+            + " on the first or on the second).\n"
             + "2. Check that the string \"scales [ScaleX, ScaleY]\" is painted on the window"
             + " where ScaleX and ScaleY are the scales for current display.\n"
             + "The scales are calculated as DPI / 96 and are 1 for the DPI value 96"
@@ -86,7 +96,6 @@
             + " to show the right display scales.\n"
             + "6. Check that the window is properly resized.\n"
             + "7. Check that the window is properly repainted and does not contain drawing artifacts\n"
-            + "Try different display positions (left, right, top, bottom).\n"
             + "If all tests are passed, press PASS, else press FAIL.\n";
 
     public static void main(String args[]) throws Exception {
@@ -101,6 +110,8 @@
 
     private static void createUI() {
 
+        initScreenBounds();
+
         mainFrame = new JFrame("DPI change test");
         GridBagLayout layout = new GridBagLayout();
         JPanel mainControlPanel = new JPanel(layout);
@@ -108,20 +119,22 @@
 
         GridBagConstraints gbc = new GridBagConstraints();
 
-        JPanel testPanel = new JPanel(new FlowLayout());
+        JPanel testPanel = new JPanel(new BorderLayout());
         JButton frameButton = new JButton("Show Frame");
         frameButton.addActionListener((e) -> {
-            int x = 20;
-            int y = 10;
-            int w = 400;
-            int h = 300;
+            GraphicsConfiguration gc = GraphicsEnvironment
+                    .getLocalGraphicsEnvironment()
+                    .getScreenDevices()[screen1]
+                    .getDefaultConfiguration();
 
-            frame = new TestFrame(w, h);
-            frame.setLocation(x, y);
+            Rectangle rect = getCenterRect(screenBounds[screen2]);
+            frame = new TestFrame(gc, rect);
             frame.setVisible(true);
 
         });
-        testPanel.add(frameButton);
+        frameButton.setEnabled(screen1 != -1 && screen2 != -1);
+        testPanel.add(getDisplaysComponent(), BorderLayout.CENTER);
+        testPanel.add(frameButton, BorderLayout.SOUTH);
 
         gbc.gridx = 0;
         gbc.gridy = 0;
@@ -193,14 +206,99 @@
         }
     }
 
+    static void initScreenBounds() {
+
+        GraphicsDevice[] devices = GraphicsEnvironment
+                .getLocalGraphicsEnvironment()
+                .getScreenDevices();
+
+        screenBounds = new Rectangle[devices.length];
+        scales = new double[devices.length][2];
+        for (int i = 0; i < devices.length; i++) {
+            GraphicsConfiguration gc = devices[i].getDefaultConfiguration();
+            screenBounds[i] = gc.getBounds();
+            AffineTransform tx = gc.getDefaultTransform();
+            scales[i][0] = tx.getScaleX();
+            scales[i][1] = tx.getScaleY();
+        }
+
+        for (int i = 0; i < devices.length; i++) {
+            for (int j = i + 1; j < devices.length; j++) {
+                if (scales[i][0] != scales[j][0] || scales[i][1] != scales[j][1]) {
+                    screen1 = i;
+                    screen2 = j;
+                }
+            }
+        }
+    }
+
+    private static Rectangle getCenterRect(Rectangle rect) {
+        int w = rect.width / 2;
+        int h = rect.height / 2;
+        int x = rect.x + w / 2;
+        int y = rect.y + h / 2;
+
+        return new Rectangle(x, y, w, h);
+    }
+
+    static JComponent getDisplaysComponent() {
+
+        Rectangle rect = screenBounds[0];
+        for (int i = 0; i < screenBounds.length; i++) {
+            rect = rect.union(screenBounds[i]);
+        }
+
+        final BufferedImage img = new BufferedImage(rect.width, rect.height, BufferedImage.TYPE_INT_RGB);
+        Graphics2D g = img.createGraphics();
+        g.setColor(Color.WHITE);
+        g.fillRect(0, 0, rect.width, rect.height);
+        g.translate(-rect.x, -rect.y);
+
+        g.setStroke(new BasicStroke(8f));
+        for (int i = 0; i < screenBounds.length; i++) {
+            Rectangle r = screenBounds[i];
+            g.setColor(Color.BLACK);
+            g.drawRect(r.x, r.y, r.width, r.height);
+
+            g.setColor(Color.ORANGE);
+            Rectangle cr = getCenterRect(r);
+            g.fillRect(cr.x, cr.y, cr.width, cr.height);
+
+            double scaleX = scales[i][0];
+            double scaleY = scales[i][1];
+            float fontSize = rect.height / 7;
+            g.setFont(g.getFont().deriveFont(fontSize));
+            g.setColor(Color.BLUE);
+            g.drawString(String.format("Scale: [%2.1f, %2.1f]", scaleX, scaleY),
+                    r.x + r.width / 8, r.y + r.height / 2);
+
+        }
+
+        g.dispose();
+
+        JPanel panel = new JPanel() {
+
+            @Override
+            public void paint(Graphics g) {
+                super.paint(g);
+                g.drawImage(img, 0, 0, getWidth(), getHeight(), this);
+
+            }
+        };
+
+        panel.setPreferredSize(new Dimension(400, 200));
+
+        return panel;
+    }
+
     static class TestFrame extends JFrame {
 
         private final TestMultiResolutionImage mrImage;
 
-        public TestFrame(int width, int height) throws HeadlessException {
-            super("Test Frame");
-            setSize(width, height);
-            mrImage = new TestMultiResolutionImage(width, height);
+        public TestFrame(GraphicsConfiguration gc, Rectangle rect) throws HeadlessException {
+            super(gc);
+            setBounds(rect);
+            mrImage = new TestMultiResolutionImage(rect.width, rect.height);
 
             JPanel panel = new JPanel(new FlowLayout()) {
                 @Override
@@ -217,29 +315,17 @@
             JButton button = new JButton("Move to another display");
             button.addActionListener((e) -> {
                 GraphicsConfiguration config = getGraphicsConfiguration();
-                GraphicsDevice device = config.getDevice();
 
                 GraphicsDevice[] devices = GraphicsEnvironment
                         .getLocalGraphicsEnvironment()
                         .getScreenDevices();
 
-                boolean found = false;
-                for (GraphicsDevice dev : devices) {
-                    if (!dev.equals(device)) {
-                        found = true;
-                        Rectangle bounds = dev.getDefaultConfiguration().getBounds();
 
-                        AffineTransform tx = config.getDefaultTransform();
-                        int x = (int) Math.round(bounds.x / tx.getScaleX()) + 15;
-                        int y = (int) Math.round(bounds.y / tx.getScaleY()) + 15;
-                        frame.setLocation(x, y);
-                        break;
-                    }
-                }
+                int index = devices[screen1].getDefaultConfiguration().equals(config)
+                        ? screen2 : screen1;
 
-                if (!found) {
-                    System.out.println("Another display not found!");
-                }
+                Rectangle r = getCenterRect(screenBounds[index]);
+                frame.setBounds(r);
             });
 
             panel.add(button);