From 60435238bc95703f5456ca84c9825f64dfd5b440 Mon Sep 17 00:00:00 2001 From: duke Date: Tue, 4 Jul 2023 16:04:06 +0000 Subject: [PATCH] Backport d73fc70ea272be6bf4f1c82608c73ff0a8f6032d --- .../swing/Popup/TaskbarPositionTest.java | 306 +++++++++++------- 1 file changed, 192 insertions(+), 114 deletions(-) diff --git a/test/jdk/javax/swing/Popup/TaskbarPositionTest.java b/test/jdk/javax/swing/Popup/TaskbarPositionTest.java index a088cb07a9a..6a6b93a1080 100644 --- a/test/jdk/javax/swing/Popup/TaskbarPositionTest.java +++ b/test/jdk/javax/swing/Popup/TaskbarPositionTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 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 @@ -24,6 +24,8 @@ import java.awt.Component; import java.awt.Dimension; import java.awt.GraphicsConfiguration; +import java.awt.GraphicsDevice; +import java.awt.GraphicsEnvironment; import java.awt.Insets; import java.awt.Point; import java.awt.Rectangle; @@ -33,8 +35,9 @@ import java.awt.event.ActionListener; import java.awt.event.InputEvent; import java.awt.event.KeyEvent; -import java.awt.event.MouseEvent; import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; + import javax.swing.AbstractAction; import javax.swing.JComboBox; import javax.swing.JFrame; @@ -47,54 +50,61 @@ import javax.swing.JTextField; import javax.swing.KeyStroke; import javax.swing.SwingUtilities; -import javax.swing.event.PopupMenuListener; import javax.swing.event.PopupMenuEvent; +import javax.swing.event.PopupMenuListener; + +import jtreg.SkippedException; -/** +/* * @test * @bug 4245587 4474813 4425878 4767478 8015599 * @key headful * @summary Tests the location of the heavy weight popup portion of JComboBox, * JMenu and JPopupMenu. + * The test uses Ctrl+Down Arrow (↓) which is a system shortcut on macOS, + * disable it in system settings, otherwise the test will fail * @library ../regtesthelpers + * @library /test/lib * @build Util + * @build jtreg.SkippedException * @run main TaskbarPositionTest */ public class TaskbarPositionTest implements ActionListener { - private boolean done; - private Throwable error; - private static TaskbarPositionTest test; private static JFrame frame; private static JPopupMenu popupMenu; private static JPanel panel; + private static JComboBox combo1; private static JComboBox combo2; - private static JMenuBar menubar; + private static JMenu menu1; private static JMenu menu2; + private static JMenu submenu; + private static Rectangle fullScreenBounds; // The usable desktop space: screen size - screen insets. private static Rectangle screenBounds; - private static String[] numData = { + + private static final String[] numData = { "One", "Two", "Three", "Four", "Five", "Six", "Seven" }; - private static String[] dayData = { + private static final String[] dayData = { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" }; - private static char[] mnDayData = { + private static final char[] mnDayData = { 'M', 'T', 'W', 'R', 'F', 'S', 'U' }; public TaskbarPositionTest() { frame = new JFrame("Use CTRL-down to show a JPopupMenu"); frame.setContentPane(panel = createContentPane()); - frame.setJMenuBar(createMenuBar("1 - First Menu", true)); + frame.setJMenuBar(createMenuBar()); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // CTRL-down will show the popup. panel.getInputMap().put(KeyStroke.getKeyStroke( - KeyEvent.VK_DOWN, InputEvent.CTRL_MASK), "OPEN_POPUP"); + KeyEvent.VK_DOWN, InputEvent.CTRL_DOWN_MASK), "OPEN_POPUP"); panel.getActionMap().put("OPEN_POPUP", new PopupHandler()); frame.pack(); @@ -103,7 +113,6 @@ public TaskbarPositionTest() { fullScreenBounds = new Rectangle(new Point(), toolkit.getScreenSize()); screenBounds = new Rectangle(new Point(), toolkit.getScreenSize()); - // Reduce the screen bounds by the insets. GraphicsConfiguration gc = frame.getGraphicsConfiguration(); if (gc != null) { @@ -116,37 +125,56 @@ public TaskbarPositionTest() { } // Place the frame near the bottom. - frame.setLocation(0, screenBounds.y + screenBounds.height - frame.getHeight()); + frame.setLocation(screenBounds.x, + screenBounds.y + screenBounds.height - frame.getHeight()); frame.setVisible(true); } - public static class ComboPopupCheckListener implements PopupMenuListener { + private static class ComboPopupCheckListener implements PopupMenuListener { + @Override public void popupMenuCanceled(PopupMenuEvent ev) { } + @Override public void popupMenuWillBecomeVisible(PopupMenuEvent ev) { } + @Override public void popupMenuWillBecomeInvisible(PopupMenuEvent ev) { - Point cpos = combo1.getLocation(); - SwingUtilities.convertPointToScreen(cpos, panel); - - JPopupMenu pm = (JPopupMenu) combo1.getUI().getAccessibleChild(combo1, 0); - - if (pm != null) { - Point p = pm.getLocation(); - SwingUtilities.convertPointToScreen(p, pm); - if (p.y+1 < cpos.y) { - System.out.println("p.y " + p.y + " cpos.y " + cpos.y); - throw new RuntimeException("ComboBox popup is wrongly aligned"); - } // check that popup was opened down + JComboBox combo = (JComboBox) ev.getSource(); + Point comboLoc = combo.getLocationOnScreen(); + + JPopupMenu popupMenu = (JPopupMenu) combo.getUI().getAccessibleChild(combo, 0); + + Point popupMenuLoc = popupMenu.getLocationOnScreen(); + Dimension popupSize = popupMenu.getSize(); + + isPopupOnScreen(popupMenu, fullScreenBounds); + + if (comboLoc.x > 0) { + // The frame is located at the bottom of the screen, + // the combo popups should open upwards + if (popupMenuLoc.y + popupSize.height < comboLoc.y) { + System.err.println("popup " + popupMenuLoc + + " combo " + comboLoc); + throw new RuntimeException("ComboBox popup should open upwards"); + } + } else { + // The frame has been moved to negative position away from + // the bottom of the screen, the combo popup should + // open downwards in this case + if (popupMenuLoc.y + 1 < comboLoc.y) { + System.err.println("popup " + popupMenuLoc + + " combo " + comboLoc); + throw new RuntimeException("ComboBox popup should open downwards"); + } } } } - private class PopupHandler extends AbstractAction { - + private static class PopupHandler extends AbstractAction { + @Override public void actionPerformed(ActionEvent e) { if (!popupMenu.isVisible()) { popupMenu.show((Component) e.getSource(), 40, 40); @@ -155,18 +183,20 @@ public void actionPerformed(ActionEvent e) { } } - class PopupListener extends MouseAdapter { + private static class PopupListener extends MouseAdapter { - private JPopupMenu popup; + private final JPopupMenu popup; public PopupListener(JPopupMenu popup) { this.popup = popup; } + @Override public void mousePressed(MouseEvent e) { maybeShowPopup(e); } + @Override public void mouseReleased(MouseEvent e) { maybeShowPopup(e); } @@ -182,121 +212,137 @@ private void maybeShowPopup(MouseEvent e) { /** * Tests if the popup is on the screen. */ - public static void isPopupOnScreen(JPopupMenu popup, Rectangle checkBounds) { + private static void isPopupOnScreen(JPopupMenu popup, Rectangle checkBounds) { + if (!popup.isVisible()) { + throw new RuntimeException("Popup not visible"); + } Dimension dim = popup.getSize(); - Point pt = new Point(); - SwingUtilities.convertPointToScreen(pt, popup); + Point pt = popup.getLocationOnScreen(); Rectangle bounds = new Rectangle(pt, dim); if (!SwingUtilities.isRectangleContainingRectangle(checkBounds, bounds)) { - throw new RuntimeException("We do not match! " + checkBounds + " / " + bounds); + throw new RuntimeException("Popup is outside of screen bounds " + + checkBounds + " / " + bounds); } + } + private static void isComboPopupOnScreen(JComboBox comboBox) { + if (!comboBox.isPopupVisible()) { + throw new RuntimeException("ComboBox popup not visible"); + } + JPopupMenu popupMenu = (JPopupMenu) comboBox.getUI().getAccessibleChild(comboBox, 0); + isPopupOnScreen(popupMenu, screenBounds); } - private JPanel createContentPane() { - JPanel panel = new JPanel(); + private JPanel createContentPane() { combo1 = new JComboBox<>(numData); - panel.add(combo1); + combo1.addPopupMenuListener(new ComboPopupCheckListener()); + combo2 = new JComboBox<>(dayData); combo2.setEditable(true); - panel.add(combo2); - panel.setSize(300, 200); + combo2.addPopupMenuListener(new ComboPopupCheckListener()); popupMenu = new JPopupMenu(); - JMenuItem item; for (int i = 0; i < dayData.length; i++) { - item = popupMenu.add(new JMenuItem(dayData[i], mnDayData[i])); + JMenuItem item = popupMenu.add(new JMenuItem(dayData[i], mnDayData[i])); item.addActionListener(this); } - panel.addMouseListener(new PopupListener(popupMenu)); JTextField field = new JTextField("CTRL+down for Popup"); // CTRL-down will show the popup. field.getInputMap().put(KeyStroke.getKeyStroke( - KeyEvent.VK_DOWN, InputEvent.CTRL_MASK), "OPEN_POPUP"); + KeyEvent.VK_DOWN, InputEvent.CTRL_DOWN_MASK), "OPEN_POPUP"); field.getActionMap().put("OPEN_POPUP", new PopupHandler()); + JPanel panel = new JPanel(); + panel.add(combo1); + panel.add(combo2); + panel.setSize(300, 200); + panel.addMouseListener(new PopupListener(popupMenu)); panel.add(field); return panel; } - /** - * @param str name of Menu - * @param bFlag set mnemonics on menu items - */ - private JMenuBar createMenuBar(String str, boolean bFlag) { - menubar = new JMenuBar(); - - menu1 = new JMenu(str); - menu1.setMnemonic(str.charAt(0)); - menu1.addActionListener(this); + private JMenuBar createMenuBar() { + JMenuBar menubar = new JMenuBar(); + menu1 = new JMenu("1 - First Menu"); + menu1.setMnemonic('1'); + createSubMenu(menu1, "1 JMenuItem", 8, null); menubar.add(menu1); - for (int i = 0; i < 8; i++) { - JMenuItem menuitem = new JMenuItem("1 JMenuItem" + i); - menuitem.addActionListener(this); - if (bFlag) { - menuitem.setMnemonic('0' + i); - } - menu1.add(menuitem); - } - // second menu menu2 = new JMenu("2 - Second Menu"); - menu2.addActionListener(this); menu2.setMnemonic('2'); - + createSubMenu(menu2, "2 JMenuItem", 4, null); + menu2.add(new JSeparator()); menubar.add(menu2); - for (int i = 0; i < 5; i++) { - JMenuItem menuitem = new JMenuItem("2 JMenuItem" + i); - menuitem.addActionListener(this); - if (bFlag) { - menuitem.setMnemonic('0' + i); - } - menu2.add(menuitem); - } - JMenu submenu = new JMenu("Sub Menu"); + submenu = new JMenu("Sub Menu"); submenu.setMnemonic('S'); - submenu.addActionListener(this); - for (int i = 0; i < 5; i++) { - JMenuItem menuitem = new JMenuItem("S JMenuItem" + i); - menuitem.addActionListener(this); - if (bFlag) { - menuitem.setMnemonic('0' + i); - } - submenu.add(menuitem); - } - menu2.add(new JSeparator()); + createSubMenu(submenu, "S JMenuItem", 4, this); menu2.add(submenu); return menubar; } + private static void createSubMenu(JMenu menu, String prefix, int count, ActionListener action) { + for (int i = 0; i < count; ++i) { + JMenuItem menuitem = new JMenuItem(prefix + i); + menu.add(menuitem); + if (action != null) { + menuitem.addActionListener(action); + } + } + } + + public void actionPerformed(ActionEvent evt) { Object obj = evt.getSource(); if (obj instanceof JMenuItem) { - // put the focus on the noneditable combo. + // put the focus on the non-editable combo. combo1.requestFocus(); } } + private static void hidePopup(Robot robot) { + robot.keyPress(KeyEvent.VK_ESCAPE); + robot.keyRelease(KeyEvent.VK_ESCAPE); + } + public static void main(String[] args) throws Throwable { + GraphicsDevice mainScreen = GraphicsEnvironment.getLocalGraphicsEnvironment() + .getDefaultScreenDevice(); + Rectangle mainScreenBounds = mainScreen.getDefaultConfiguration() + .getBounds(); + GraphicsDevice[] screens = GraphicsEnvironment.getLocalGraphicsEnvironment() + .getScreenDevices(); + for (GraphicsDevice screen : screens) { + if (screen == mainScreen) { + continue; + } + + Rectangle bounds = screen.getDefaultConfiguration() + .getBounds(); + if (bounds.x < 0) { + // The test may fail if a screen have negative origin + throw new SkippedException("Configurations with negative screen" + + " origin are not supported"); + } + if (bounds.y >= mainScreenBounds.height) { + // The test may fail if there's a screen to bottom of the main monitor + throw new SkippedException("Configurations with a screen beneath" + + " the main one are not supported"); + } + } try { // Use Robot to automate the test - Robot robot; - robot = new Robot(); + Robot robot = new Robot(); robot.setAutoDelay(50); - SwingUtilities.invokeAndWait(new Runnable() { - public void run() { - test = new TaskbarPositionTest(); - } - }); + SwingUtilities.invokeAndWait(TaskbarPositionTest::new); robot.waitForIdle(); robot.delay(1000); @@ -310,58 +356,90 @@ public void run() { // 2 menu with sub menu robot.keyPress(KeyEvent.VK_RIGHT); robot.keyRelease(KeyEvent.VK_RIGHT); - Util.hitMnemonics(robot, KeyEvent.VK_S); + // Open the submenu + robot.keyPress(KeyEvent.VK_S); + robot.keyRelease(KeyEvent.VK_S); robot.waitForIdle(); SwingUtilities.invokeAndWait(() -> isPopupOnScreen(menu2.getPopupMenu(), screenBounds)); + SwingUtilities.invokeAndWait(() -> isPopupOnScreen(submenu.getPopupMenu(), screenBounds)); + // Hit Enter to perform the action of + // a selected menu item in the submenu + // which requests focus on combo1, non-editable combo box robot.keyPress(KeyEvent.VK_ENTER); robot.keyRelease(KeyEvent.VK_ENTER); - // Focus should go to non editable combo box robot.waitForIdle(); - robot.delay(500); + // Focus should go to combo1 + // Open combo1 popup robot.keyPress(KeyEvent.VK_DOWN); + robot.keyRelease(KeyEvent.VK_DOWN); - // How do we check combo boxes? + robot.waitForIdle(); + SwingUtilities.invokeAndWait(() -> isComboPopupOnScreen(combo1)); + hidePopup(robot); - // Editable combo box + // Move focus to combo2, editable combo box robot.keyPress(KeyEvent.VK_TAB); robot.keyRelease(KeyEvent.VK_TAB); + + robot.waitForIdle(); + + // Open combo2 popup robot.keyPress(KeyEvent.VK_DOWN); robot.keyRelease(KeyEvent.VK_DOWN); - // combo1.getUI(); + robot.waitForIdle(); + SwingUtilities.invokeAndWait(() -> isComboPopupOnScreen(combo2)); + hidePopup(robot); - // Popup from Text field + // Move focus to the text field robot.keyPress(KeyEvent.VK_TAB); robot.keyRelease(KeyEvent.VK_TAB); + + robot.waitForIdle(); + + // Open its popup robot.keyPress(KeyEvent.VK_CONTROL); robot.keyPress(KeyEvent.VK_DOWN); robot.keyRelease(KeyEvent.VK_DOWN); robot.keyRelease(KeyEvent.VK_CONTROL); - // Popup from a mouse click. - Point pt = new Point(2, 2); - SwingUtilities.convertPointToScreen(pt, panel); - robot.mouseMove(pt.x, pt.y); - robot.mousePress(InputEvent.BUTTON3_MASK); - robot.mouseRelease(InputEvent.BUTTON3_MASK); + robot.waitForIdle(); + SwingUtilities.invokeAndWait(() -> isPopupOnScreen(popupMenu, fullScreenBounds)); + hidePopup(robot); + + // Popup from a mouse click + SwingUtilities.invokeAndWait(() -> { + Point pt = panel.getLocationOnScreen(); + pt.translate(4, 4); + robot.mouseMove(pt.x, pt.y); + }); + robot.mousePress(InputEvent.BUTTON3_DOWN_MASK); + robot.mouseRelease(InputEvent.BUTTON3_DOWN_MASK); + // Ensure popupMenu is shown within screen bounds robot.waitForIdle(); - SwingUtilities.invokeAndWait(new Runnable() { - public void run() { - frame.setLocation(-30, 100); - combo1.addPopupMenuListener(new ComboPopupCheckListener()); - combo1.requestFocus(); - } + SwingUtilities.invokeAndWait(() -> isPopupOnScreen(popupMenu, fullScreenBounds)); + hidePopup(robot); + + robot.waitForIdle(); + SwingUtilities.invokeAndWait(() -> { + frame.setLocation(-30, 100); + combo1.requestFocus(); }); + robot.waitForIdle(); + + // Open combo1 popup again robot.keyPress(KeyEvent.VK_DOWN); robot.keyRelease(KeyEvent.VK_DOWN); - robot.keyPress(KeyEvent.VK_ESCAPE); - robot.keyRelease(KeyEvent.VK_ESCAPE); + + robot.waitForIdle(); + SwingUtilities.invokeAndWait(() -> isComboPopupOnScreen(combo1)); + hidePopup(robot); robot.waitForIdle(); } finally {