From cc3fdc4e536ecbc6ab25f29bf63ddbf8d0b92509 Mon Sep 17 00:00:00 2001 From: Goetz Lindenmaier Date: Mon, 18 Mar 2024 09:15:42 +0100 Subject: [PATCH] Backport ... --- .../javax/swing/plaf/metal/MetalBorders.java | 172 ++++++++--- .../InternalFrameBorderTest.java | 266 ++++++++++++++++++ 2 files changed, 395 insertions(+), 43 deletions(-) create mode 100644 test/jdk/javax/swing/JInternalFrame/InternalFrameBorderTest.java diff --git a/src/java.desktop/share/classes/javax/swing/plaf/metal/MetalBorders.java b/src/java.desktop/share/classes/javax/swing/plaf/metal/MetalBorders.java index 5461d7485b1..1ce233de93b 100644 --- a/src/java.desktop/share/classes/javax/swing/plaf/metal/MetalBorders.java +++ b/src/java.desktop/share/classes/javax/swing/plaf/metal/MetalBorders.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2022, 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 @@ -25,24 +25,46 @@ package javax.swing.plaf.metal; -import javax.swing.*; -import javax.swing.border.*; -import javax.swing.plaf.*; -import javax.swing.plaf.basic.BasicBorders; -import javax.swing.text.JTextComponent; - -import java.awt.Component; -import java.awt.Insets; +import java.awt.BasicStroke; import java.awt.Color; +import java.awt.Component; import java.awt.Dialog; import java.awt.Frame; import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Insets; +import java.awt.Stroke; import java.awt.Window; +import java.awt.geom.AffineTransform; + +import javax.swing.AbstractButton; +import javax.swing.ButtonModel; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JInternalFrame; +import javax.swing.JMenu; +import javax.swing.JMenuBar; +import javax.swing.JMenuItem; +import javax.swing.JOptionPane; +import javax.swing.JScrollPane; +import javax.swing.JToolBar; +import javax.swing.SwingConstants; +import javax.swing.SwingUtilities; +import javax.swing.UIManager; +import javax.swing.border.AbstractBorder; +import javax.swing.border.Border; +import javax.swing.border.CompoundBorder; +import javax.swing.border.EmptyBorder; +import javax.swing.border.LineBorder; +import javax.swing.border.MatteBorder; +import javax.swing.plaf.BorderUIResource; +import javax.swing.plaf.UIResource; +import javax.swing.plaf.basic.BasicBorders; +import javax.swing.text.JTextComponent; import sun.swing.StringUIClientPropertyKey; import sun.swing.SwingUtilities2; - /** * Factory object that can vend Borders appropriate for the metal L & F. * @author Steve Wilson @@ -203,10 +225,22 @@ public Insets getBorderInsets(Component c, Insets newInsets) { */ @SuppressWarnings("serial") // Superclass is not serializable across versions public static class InternalFrameBorder extends AbstractBorder implements UIResource { - private static final int corner = 14; + private static final int CORNER = 14; + + /** + * Rounds a double to the nearest integer. It rounds 0.5 down, + * for example 1.5 is rounded to 1.0. + * + * @param d number to be rounded + * @return the rounded value + */ + private static int roundHalfDown(double d) { + double decP = (Math.ceil(d) - d); + return (int)((decP == 0.5) ? Math.floor(d) : Math.round(d)); + } public void paintBorder(Component c, Graphics g, int x, int y, - int w, int h) { + int w, int h) { Color background; Color highlight; @@ -222,41 +256,93 @@ public void paintBorder(Component c, Graphics g, int x, int y, shadow = MetalLookAndFeel.getControlInfo(); } - g.setColor(background); - // Draw outermost lines - g.drawLine( 1, 0, w-2, 0); - g.drawLine( 0, 1, 0, h-2); - g.drawLine( w-1, 1, w-1, h-2); - g.drawLine( 1, h-1, w-2, h-1); + Graphics2D g2d = (Graphics2D) g; + AffineTransform at = g2d.getTransform(); + Stroke oldStk = g2d.getStroke(); + Color oldColor = g2d.getColor(); + int stkWidth = 1; + + // if m01 or m10 is non-zero, then there is a rotation or shear + // skip resetting the transform + boolean resetTransform = ((at.getShearX() == 0) && (at.getShearY() == 0)); + + int xtranslation; + int ytranslation; + int width; + int height; + + if (resetTransform) { + g2d.setTransform(new AffineTransform()); + stkWidth = roundHalfDown(Math.min(at.getScaleX(), at.getScaleY())); + + double xx = at.getScaleX() * x + at.getTranslateX(); + double yy = at.getScaleY() * y + at.getTranslateY(); + xtranslation = roundHalfDown(xx); + ytranslation = roundHalfDown(yy); + width = roundHalfDown(at.getScaleX() * w + xx) - xtranslation; + height = roundHalfDown(at.getScaleY() * h + yy) - ytranslation; + } else { + width = w; + height = h; + xtranslation = x; + ytranslation = y; + } + g2d.translate(xtranslation, ytranslation); - // Draw the bulk of the border - for (int i = 1; i < 5; i++) { - g.drawRect(x+i,y+i,w-(i*2)-1, h-(i*2)-1); - } + // scaled border + int thickness = (int) Math.ceil(4 * at.getScaleX()); - if (c instanceof JInternalFrame && - ((JInternalFrame)c).isResizable()) { - g.setColor(highlight); - // Draw the Long highlight lines - g.drawLine( corner+1, 3, w-corner, 3); - g.drawLine( 3, corner+1, 3, h-corner); - g.drawLine( w-2, corner+1, w-2, h-corner); - g.drawLine( corner+1, h-2, w-corner, h-2); - - g.setColor(shadow); - // Draw the Long shadow lines - g.drawLine( corner, 2, w-corner-1, 2); - g.drawLine( 2, corner, 2, h-corner-1); - g.drawLine( w-3, corner, w-3, h-corner-1); - g.drawLine( corner, h-3, w-corner-1, h-3); - } + g.setColor(background); + // Draw the bulk of the border + for (int i = 0; i <= thickness; i++) { + g.drawRect(i, i, width - (i * 2), height - (i * 2)); + } + + if (c instanceof JInternalFrame && ((JInternalFrame)c).isResizable()) { + // set new stroke to draw shadow and highlight lines + g2d.setStroke(new BasicStroke((float) stkWidth)); + + // midpoint at which highlight & shadow lines + // are positioned on the border + int midPoint = thickness / 2; + int offset = ((at.getScaleX() - stkWidth) >= 0 && stkWidth % 2 != 0) ? 1 : 0; + int loc1 = thickness % 2 == 0 ? midPoint + stkWidth / 2 - stkWidth : midPoint; + int loc2 = thickness % 2 == 0 ? midPoint + stkWidth / 2 : midPoint + stkWidth; + // scaled corner + int corner = (int) Math.round(CORNER * at.getScaleX()); + + // Draw the Long highlight lines + g.setColor(highlight); + g.drawLine(corner + 1, loc2, width - corner, loc2); //top + g.drawLine(loc2, corner + 1, loc2, height - corner); //left + g.drawLine((width - offset) - loc1, corner + 1, + (width - offset) - loc1, height - corner); //right + g.drawLine(corner + 1, (height - offset) - loc1, + width - corner, (height - offset) - loc1); //bottom - } + // Draw the Long shadow lines + g.setColor(shadow); + g.drawLine(corner, loc1, width - corner - 1, loc1); + g.drawLine(loc1, corner, loc1, height - corner - 1); + g.drawLine((width - offset) - loc2, corner, + (width - offset) - loc2, height - corner - 1); + g.drawLine(corner, (height - offset) - loc2, + width - corner - 1, (height - offset) - loc2); + } - public Insets getBorderInsets(Component c, Insets newInsets) { - newInsets.set(5, 5, 5, 5); - return newInsets; - } + // restore previous transform + g2d.translate(-xtranslation, -ytranslation); + if (resetTransform) { + g2d.setColor(oldColor); + g2d.setTransform(at); + g2d.setStroke(oldStk); + } + } + + public Insets getBorderInsets(Component c, Insets newInsets) { + newInsets.set(4, 4, 4, 4); + return newInsets; + } } /** diff --git a/test/jdk/javax/swing/JInternalFrame/InternalFrameBorderTest.java b/test/jdk/javax/swing/JInternalFrame/InternalFrameBorderTest.java new file mode 100644 index 00000000000..ba48c39628e --- /dev/null +++ b/test/jdk/javax/swing/JInternalFrame/InternalFrameBorderTest.java @@ -0,0 +1,266 @@ +/* + * Copyright (c) 2022, 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.AWTException; +import java.awt.Color; +import java.awt.GridBagLayout; +import java.awt.Image; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.Robot; +import java.awt.image.MultiResolutionImage; +import java.awt.image.RenderedImage; +import java.io.File; +import java.lang.reflect.InvocationTargetException; +import java.util.List; + +import javax.imageio.ImageIO; +import javax.swing.JFrame; +import javax.swing.JInternalFrame; +import javax.swing.JLabel; +import javax.swing.SwingUtilities; +import javax.swing.UIManager; + +/* + * @test + * @bug 8015739 + * @key headful + * @summary Tests whether background color of JInternalFrame is visible + * in the border region at different scales by checking the midpoints + * and corners of the border. + * + * @requires (os.family == "windows") + * @run main/othervm -Dsun.java2d.uiScale=1 InternalFrameBorderTest + * @run main/othervm -Dsun.java2d.uiScale=1.25 InternalFrameBorderTest + * @run main/othervm -Dsun.java2d.uiScale=1.5 InternalFrameBorderTest + * @run main/othervm -Dsun.java2d.uiScale=1.75 InternalFrameBorderTest + * @run main/othervm -Dsun.java2d.uiScale=2 InternalFrameBorderTest + * @run main/othervm -Dsun.java2d.uiScale=2.5 InternalFrameBorderTest + * @run main/othervm -Dsun.java2d.uiScale=3 InternalFrameBorderTest + */ + +/* + * @test + * @bug 8015739 + * @key headful + * @summary Tests whether background color of JInternalFrame is visible + * in the border region at different scales by checking the midpoints + * and corners of the border. + * + * @requires (os.family == "mac" | os.family == "linux") + * @run main/othervm -Dsun.java2d.uiScale=1 InternalFrameBorderTest + * @run main/othervm -Dsun.java2d.uiScale=2 InternalFrameBorderTest + */ + +public class InternalFrameBorderTest { + private static final int FRAME_SIZE = 300; + private static final int INTFRAME_SIZE = 150; + private static final int MIDPOINT = INTFRAME_SIZE / 2; + private static final int BORDER_THICKNESS = 4; + + private static final StringBuffer errorLog = new StringBuffer(); + + private static JFrame jFrame; + private static Rectangle jFrameBounds; + private static JInternalFrame iFrame; + private static Point iFrameLoc; + private static int iFrameMaxX; + private static int iFrameMaxY; + + private static Robot robot; + private static String uiScale; + + public static void main(String[] args) throws AWTException, + InterruptedException, InvocationTargetException { + try { + UIManager.setLookAndFeel("javax.swing.plaf.metal.MetalLookAndFeel"); + } catch (Exception e) { + System.out.println("Metal LAF class not supported"); + return; + } + + try { + robot = new Robot(); + robot.setAutoDelay(200); + uiScale = System.getProperty("sun.java2d.uiScale"); + + SwingUtilities.invokeAndWait(InternalFrameBorderTest::createAndShowGUI); + robot.waitForIdle(); + robot.delay(500); + + SwingUtilities.invokeAndWait(() -> { + iFrameLoc = iFrame.getLocationOnScreen(); + iFrameMaxX = iFrameLoc.x + INTFRAME_SIZE; + iFrameMaxY = iFrameLoc.y + INTFRAME_SIZE; + jFrameBounds = jFrame.getBounds(); + }); + + // Check Borders + checkBorderMidPoints("TOP"); + checkBorderMidPoints("RIGHT"); + checkBorderMidPoints("BOTTOM"); + checkBorderMidPoints("LEFT"); + + // Check Corner Diagonals + checkCorners("TOP_LEFT"); + checkCorners("TOP_RIGHT"); + checkCorners("BOTTOM_RIGHT"); + checkCorners("BOTTOM_LEFT"); + + if (errorLog.length() != 0) { + saveScreenCapture("JIF_uiScale_" + uiScale + ".png"); + throw new RuntimeException("Following error(s) occurred: \n" + + errorLog); + } + } finally { + if (jFrame != null) { + jFrame.dispose(); + } + robot.delay(500); + } + } + + private static void checkBorderMidPoints(String borderDirection) { + int x, y; + int start, stop; + + switch (borderDirection) { + case "TOP": { + x = iFrameLoc.x + MIDPOINT; + y = iFrameLoc.y + BORDER_THICKNESS; + start = iFrameLoc.y; + stop = iFrameLoc.y + BORDER_THICKNESS - 1; + } break; + case "RIGHT": { + x = iFrameMaxX - BORDER_THICKNESS; + y = iFrameLoc.y + MIDPOINT; + start = iFrameMaxX - BORDER_THICKNESS + 1; + stop = iFrameMaxX; + } break; + case "BOTTOM": { + x = iFrameLoc.x + MIDPOINT; + y = iFrameMaxY - BORDER_THICKNESS; + start = iFrameMaxY - BORDER_THICKNESS + 1; + stop = iFrameMaxY; + } break; + case "LEFT": { + x = iFrameLoc.x; + y = iFrameLoc.y + MIDPOINT; + start = iFrameLoc.x; + stop = iFrameLoc.x + BORDER_THICKNESS - 1; + } break; + default: throw new IllegalStateException("Unexpected value: " + + borderDirection); + } + + boolean isVertical = borderDirection.equals("RIGHT") + || borderDirection.equals("LEFT"); + boolean isHorizontal = borderDirection.equals("TOP") + || borderDirection.equals("BOTTOM"); + + robot.mouseMove(x, y); + for (int i = start; i < stop; i++) { + int locX = isVertical ? i : (iFrameLoc.x + MIDPOINT); + int locY = isHorizontal ? i : (iFrameLoc.y + MIDPOINT); + if (Color.RED.equals(robot.getPixelColor(locX, locY))) { + errorLog.append("At uiScale: " + uiScale + + ", Red background color detected at " + + borderDirection + " border.\n"); + break; + } + } + robot.delay(300); + } + + private static void checkCorners(String cornerLocation) { + int x, y; + + switch (cornerLocation) { + case "TOP_LEFT": { + x = iFrameLoc.x; + y = iFrameLoc.y; + } break; + case "TOP_RIGHT": { + x = iFrameMaxX; + y = iFrameLoc.y; + } break; + case "BOTTOM_RIGHT": { + x = iFrameMaxX; + y = iFrameMaxY; + } break; + case "BOTTOM_LEFT": { + x = iFrameLoc.x; + y = iFrameMaxY; + } break; + default: throw new IllegalStateException("Unexpected value: " + + cornerLocation); + } + + boolean isTop = cornerLocation.equals("TOP_LEFT") + || cornerLocation.equals("TOP_RIGHT"); + boolean isLeft = cornerLocation.equals("TOP_LEFT") + || cornerLocation.equals("BOTTOM_LEFT"); + + robot.mouseMove(x, y); + for (int i = 0; i < BORDER_THICKNESS - 1; i++) { + int locX = isLeft ? (x + i) : (x - i); + int locY = isTop ? (y + i) : (y - i); + if (Color.RED.equals(robot.getPixelColor(locX, locY))) { + errorLog.append("At uiScale: " + uiScale + ", Red background color" + + " detected at " + cornerLocation + " corner.\n"); + break; + } + } + robot.delay(300); + } + + private static void createAndShowGUI() { + jFrame = new JFrame(); + jFrame.setSize(FRAME_SIZE, FRAME_SIZE); + jFrame.setLayout(null); + jFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + + JLabel scale = new JLabel("UI Scale: " + uiScale); + iFrame = new JInternalFrame("iframe", true); + iFrame.setLayout(new GridBagLayout()); + iFrame.setBackground(Color.RED); + iFrame.add(scale); + iFrame.setLocation(30, 30); + jFrame.getContentPane().add(iFrame); + iFrame.setSize(INTFRAME_SIZE, INTFRAME_SIZE); + iFrame.setVisible(true); + jFrame.setLocation(150, 150); + jFrame.setVisible(true); + } + + private static void saveScreenCapture(String filename) { + MultiResolutionImage mrImage = robot.createMultiResolutionScreenCapture(jFrameBounds); + List variants = mrImage.getResolutionVariants(); + RenderedImage image = (RenderedImage) variants.get(variants.size() - 1); + try { + ImageIO.write(image, "png", new File(filename)); + } catch (Exception e) { + e.printStackTrace(); + } + } +}