From 80e2d52f76806bc886138a0fd4c34b1ca3dc4c0b Mon Sep 17 00:00:00 2001 From: Damon Nguyen Date: Mon, 27 Mar 2023 19:03:56 +0000 Subject: [PATCH] 8302558: Editable JComboBox's popup blocks user from seeing characters in Aqua look and feel Reviewed-by: psadhukhan, azvegint --- .../com/apple/laf/AquaComboBoxPopup.java | 14 +- .../JComboBox/EditableComboBoxPopupPos.java | 215 ++++++++++++++++++ 2 files changed, 227 insertions(+), 2 deletions(-) create mode 100644 test/jdk/javax/swing/JComboBox/EditableComboBoxPopupPos.java diff --git a/src/java.desktop/macosx/classes/com/apple/laf/AquaComboBoxPopup.java b/src/java.desktop/macosx/classes/com/apple/laf/AquaComboBoxPopup.java index 7336fbd703c..981a00d5588 100644 --- a/src/java.desktop/macosx/classes/com/apple/laf/AquaComboBoxPopup.java +++ b/src/java.desktop/macosx/classes/com/apple/laf/AquaComboBoxPopup.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2023, 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 @@ -185,8 +185,14 @@ final class AquaComboBoxPopup extends BasicComboPopup { updateContents(true); } + int popupBoundsY = comboBox.getBounds().height; + if (comboBox.isEditable() && comboBox.getBorder() != null) { + Insets inset = comboBox.getBorder().getBorderInsets(comboBox); + popupBoundsY += inset.top + inset.bottom; + } + final Dimension popupSize = getBestPopupSizeForRowCount(comboBox.getMaximumRowCount()); - final Rectangle popupBounds = computePopupBounds(0, comboBox.getBounds().height, popupSize.width, popupSize.height); + final Rectangle popupBounds = computePopupBounds(0, popupBoundsY, popupSize.width, popupSize.height); if (popupBounds == null) return null; // returning null means don't show anything final Dimension realPopupSize = popupBounds.getSize(); @@ -343,6 +349,10 @@ final class AquaComboBoxPopup extends BasicComboPopup { final Rectangle r = new Rectangle(px, py, pw, ph); if (r.y + r.height < top.y + scrBounds.y + scrBounds.height) { + // Adjust popup location to match popup for non-editable without border + if (!comboBox.isEditable()) { + r.y += (comboBoxInsets.top + comboBoxInsets.bottom) / 2; + } return r; } // Check whether it goes below the bottom of the screen, if so flip it diff --git a/test/jdk/javax/swing/JComboBox/EditableComboBoxPopupPos.java b/test/jdk/javax/swing/JComboBox/EditableComboBoxPopupPos.java new file mode 100644 index 00000000000..717a81fe972 --- /dev/null +++ b/test/jdk/javax/swing/JComboBox/EditableComboBoxPopupPos.java @@ -0,0 +1,215 @@ +/* + * Copyright (c) 2023, 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. + */ + +/* + * @test + * @key headful + * @bug 8302558 + * @summary Tests if the Popup from an editable ComboBox with a border + * is in the correct position + * @run main EditableComboBoxPopupPos + */ + +import java.awt.AWTException; +import java.awt.GridLayout; +import java.awt.Point; +import java.awt.Robot; +import java.awt.event.InputEvent; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; + +import javax.swing.BorderFactory; +import javax.swing.JComboBox; +import javax.swing.JFrame; +import javax.swing.JPanel; +import javax.swing.SwingUtilities; +import javax.swing.UIManager; +import javax.swing.UnsupportedLookAndFeelException; + +public class EditableComboBoxPopupPos { + private static Robot robot; + private static JFrame frame; + private static JPanel panel; + private static JComboBox cb1, cb2, cb3, cb4; + private static String lafName, cb1Str, cb2Str, cb3Str, cb4Str; + private static Point cb1Point, cb2Point, cb3Point, cb4Point; + private static int cb1Width, cb1Height, cb2Width, cb2Height, + cb3Width, cb3Height, cb4Width, cb4Height; + + private static final int BUTTON_OFFSET = 8; + private static final int POPUP_OFFSET = 6; + + public static void main(String[] args) throws InterruptedException, + InvocationTargetException, AWTException, IOException { + robot = new Robot(); + robot.setAutoDelay(100); + robot.setAutoWaitForIdle(true); + + for (UIManager.LookAndFeelInfo laf : UIManager.getInstalledLookAndFeels()) { + try { + lafName = laf.getName(); + SwingUtilities.invokeAndWait(() -> { + setLookAndFeel(laf); + panel = new JPanel(); + GridLayout gridLayout = new GridLayout(2, 2); + panel.setLayout(gridLayout); + String[] comboStrings = {"One", "Two", "Three"}; + + cb1 = new JComboBox<>(comboStrings); + cb1.setEditable(true); + cb1.setBorder(BorderFactory.createTitledBorder( + "Editable JComboBox")); + + cb2 = new JComboBox<>(comboStrings); + cb2.setEditable(true); + + cb3 = new JComboBox<>(comboStrings); + cb3.setEditable(false); + cb3.setBorder(BorderFactory.createTitledBorder( + "Non-editable JComboBox")); + + cb4 = new JComboBox<>(comboStrings); + cb4.setEditable(false); + + panel.add(cb1); + panel.add(cb2); + panel.add(cb3); + panel.add(cb4); + + // Change starting selection to check if the position of the + // first selection item is in the correct position on screen. + cb1.setSelectedIndex(1); + cb2.setSelectedIndex(1); + cb3.setSelectedIndex(1); + cb4.setSelectedIndex(1); + + frame = new JFrame(); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + frame.add(panel); + frame.pack(); + frame.setLocationRelativeTo(null); + frame.setVisible(true); + }); + + robot.delay(1000); + robot.waitForIdle(); + + SwingUtilities.invokeAndWait(() -> { + cb1Point = cb1.getLocationOnScreen(); + cb1Width = cb1.getWidth(); + cb1Height = cb1.getHeight(); + cb2Point = cb2.getLocationOnScreen(); + cb2Width = cb2.getWidth(); + cb2Height = cb2.getHeight(); + cb3Point = cb3.getLocationOnScreen(); + cb3Width = cb3.getWidth(); + cb3Height = cb3.getHeight(); + cb4Point = cb4.getLocationOnScreen(); + cb4Width = cb4.getWidth(); + cb4Height = cb4.getHeight(); + }); + + runTestOnComboBox(cb1Point, cb1Width, cb1Height); + runTestOnComboBox(cb2Point, cb2Width, cb2Height); + runTestOnComboBox(cb3Point, cb3Width, cb3Height); + runTestOnComboBox(cb4Point, cb4Width, cb4Height); + + SwingUtilities.invokeAndWait(() -> { + cb1Str = cb1.getSelectedItem().toString(); + cb2Str = cb2.getSelectedItem().toString(); + cb3Str = cb3.getSelectedItem().toString(); + cb4Str = cb4.getSelectedItem().toString(); + }); + + checkSelection(cb1Str, cb2Str, cb3Str, cb4Str); + } finally { + if (frame != null) SwingUtilities.invokeAndWait(() -> frame.dispose()); + } + } + } + + private static void setLookAndFeel(UIManager.LookAndFeelInfo laf) { + try { + UIManager.setLookAndFeel(laf.getClassName()); + } catch (UnsupportedLookAndFeelException ignored){ + System.out.println("Unsupported LookAndFeel: " + laf.getClassName()); + } catch (ClassNotFoundException | InstantiationException | + IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + private static void runTestOnComboBox(Point p, int width, int height) + throws InterruptedException, InvocationTargetException { + if (lafName.equals("Mac OS X")) { + if (p == cb3Point || p == cb4Point) { + // Set isPopDown property to ensure popup matches native MacOS + SwingUtilities.invokeAndWait(() -> { + cb3.putClientProperty("JComboBox.isPopDown", Boolean.FALSE); + cb4.putClientProperty("JComboBox.isPopDown", Boolean.FALSE); + }); + + robot.mouseMove(p.x + width - BUTTON_OFFSET, + p.y + (height / 2) + POPUP_OFFSET); + robot.mousePress(InputEvent.BUTTON1_DOWN_MASK); + robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK); + + robot.mouseMove(p.x + (width / 2) - BUTTON_OFFSET, + p.y + POPUP_OFFSET); + robot.mousePress(InputEvent.BUTTON1_DOWN_MASK); + robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK); + } else { + robot.mouseMove(p.x + width - BUTTON_OFFSET, + p.y + (height / 2) + POPUP_OFFSET); + robot.mousePress(InputEvent.BUTTON1_DOWN_MASK); + robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK); + + robot.mouseMove(p.x + (width / 2) - BUTTON_OFFSET, + p.y + height + POPUP_OFFSET - 8); + robot.mousePress(InputEvent.BUTTON1_DOWN_MASK); + robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK); + } + } else { + robot.mouseMove(p.x + width - BUTTON_OFFSET, + p.y + (height / 2)); + robot.mousePress(InputEvent.BUTTON1_DOWN_MASK); + robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK); + + robot.mouseMove(p.x + (width / 2) - BUTTON_OFFSET, + p.y + height + POPUP_OFFSET); + robot.mousePress(InputEvent.BUTTON1_DOWN_MASK); + robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK); + } + + } + + private static void checkSelection(String s1, String s2, + String s3, String s4) { + if (s1.equals("One") && s2.equals("One") + && s3.equals("One") && s4.equals("One")) { + System.out.println(lafName + " Passed"); + } else { + throw new RuntimeException(lafName + " Failed"); + } + } +}