diff --git a/CHANGELOG.md b/CHANGELOG.md index c53ad431..90c0cb40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ FlatLaf Change Log #### New features and improvements +- Menus: Improved usability of submenus. (PR #490; issue #247) - Linux: Support using custom window decorations. Enable with `JFrame.setDefaultLookAndFeelDecorated(true)` and `JDialog.setDefaultLookAndFeelDecorated(true)` before creating a window. diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatLaf.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatLaf.java index f12a6a41..9e287b9d 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatLaf.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatLaf.java @@ -103,6 +103,7 @@ public abstract class FlatLaf private PopupFactory oldPopupFactory; private MnemonicHandler mnemonicHandler; + private SubMenuUsabilityHelper subMenuUsabilityHelper; private Consumer postInitialization; private List> uiDefaultsGetters; @@ -244,6 +245,10 @@ public abstract class FlatLaf mnemonicHandler = new MnemonicHandler(); mnemonicHandler.install(); + // install submenu usability helper + subMenuUsabilityHelper = new SubMenuUsabilityHelper(); + subMenuUsabilityHelper.install(); + // listen to desktop property changes to update UI if system font or scaling changes if( SystemInfo.isWindows ) { // Windows 10 allows increasing font size independent of scaling: @@ -323,6 +328,12 @@ public abstract class FlatLaf mnemonicHandler = null; } + // uninstall submenu usability helper + if( subMenuUsabilityHelper != null ) { + subMenuUsabilityHelper.uninstall(); + subMenuUsabilityHelper = null; + } + // restore default link color new HTMLEditorKit().getStyleSheet().addRule( "a, address { color: blue; }" ); postInitialization = null; diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/SubMenuUsabilityHelper.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/SubMenuUsabilityHelper.java new file mode 100644 index 00000000..19958f4f --- /dev/null +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/SubMenuUsabilityHelper.java @@ -0,0 +1,316 @@ +/* + * Copyright 2022 FormDev Software GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.formdev.flatlaf; + +import java.awt.AWTEvent; +import java.awt.Color; +import java.awt.Component; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.EventQueue; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.MouseInfo; +import java.awt.Point; +import java.awt.PointerInfo; +import java.awt.Polygon; +import java.awt.Rectangle; +import java.awt.Toolkit; +import java.awt.Window; +import java.awt.event.MouseEvent; +import javax.swing.JComponent; +import javax.swing.JLayeredPane; +import javax.swing.JMenu; +import javax.swing.JPopupMenu; +import javax.swing.MenuElement; +import javax.swing.MenuSelectionManager; +import javax.swing.RootPaneContainer; +import javax.swing.SwingUtilities; +import javax.swing.Timer; +import javax.swing.UIManager; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import com.formdev.flatlaf.ui.FlatUIUtils; + +/** + * Improves usability of submenus by using a + * safe triangle + * to avoid that the submenu closes while the user moves the mouse to it. + * + * @author Karl Tauber + */ +class SubMenuUsabilityHelper + implements ChangeListener +{ + private static final String KEY_USE_SAFE_TRIANGLE = "Menu.useSafeTriangle"; + private static final String KEY_SHOW_SAFE_TRIANGLE = "FlatLaf.debug.menu.showSafeTriangle"; + + private SubMenuEventQueue subMenuEventQueue; + private SafeTrianglePainter safeTrianglePainter; + private boolean changePending; + + // mouse location in screen coordinates + private int mouseX; + private int mouseY; + + // target popup bounds in screen coordinates + private int targetX; + private int targetTopY; + private int targetBottomY; + + private Rectangle invokerBounds; + + void install() { + MenuSelectionManager.defaultManager().addChangeListener( this ); + } + + void uninstall() { + MenuSelectionManager.defaultManager().removeChangeListener( this ); + uninstallEventQueue(); + } + + @Override + public void stateChanged( ChangeEvent e ) { + if( !FlatUIUtils.getUIBoolean( KEY_USE_SAFE_TRIANGLE, true )) + return; + + // handle menu selection change later, but only once in case of temporary changes + // e.g. moving mouse from one menu item to another one, fires two events: + // 1. old menu item is removed from menu selection + // 2. new menu item is added to menu selection + synchronized( this ) { + if( changePending ) + return; + changePending = true; + } + + EventQueue.invokeLater( () -> { + synchronized( this ) { + changePending = false; + } + menuSelectionChanged(); + } ); + } + + private void menuSelectionChanged() { + MenuElement[] path = MenuSelectionManager.defaultManager().getSelectedPath(); + +/*debug + System.out.println( "--- " + path.length ); + for( int i = 0; i < path.length; i++ ) + System.out.println( " " + i + ": " + path[i].getClass().getName() ); +debug*/ + + // find submenu in menu selection + int subMenuIndex = findSubMenu( path ); + + // uninstall if there is no submenu in selection + if( subMenuIndex < 0 || subMenuIndex != path.length - 1 ) { + uninstallEventQueue(); + return; + } + + // get current mouse location + PointerInfo pointerInfo = MouseInfo.getPointerInfo(); + Point mouseLocation = (pointerInfo != null) ? pointerInfo.getLocation() : new Point(); + mouseX = mouseLocation.x; + mouseY = mouseLocation.y; + + // get invoker screen bounds + JPopupMenu popup = (JPopupMenu) path[subMenuIndex]; + Component invoker = popup.getInvoker(); + invokerBounds = (invoker != null) + ? new Rectangle( invoker.getLocationOnScreen(), invoker.getSize() ) + : null; + + // check whether mouse location is within invoker + if( invokerBounds != null && !invokerBounds.contains( mouseX, mouseY ) ) { + uninstallEventQueue(); + return; + } + + // compute top/bottom target locations + Point popupLocation = popup.getLocationOnScreen(); + Dimension popupSize = popup.getSize(); + targetX = (mouseX < popupLocation.x + (popupSize.width / 2)) + ? popupLocation.x + : popupLocation.x + popupSize.width; + targetTopY = popupLocation.y; + targetBottomY = popupLocation.y + popupSize.height; + + // install own event queue to supress mouse events when mouse is moved within safe triangle + if( subMenuEventQueue == null ) + subMenuEventQueue = new SubMenuEventQueue(); + + // create safe triangle painter + if( safeTrianglePainter == null && UIManager.getBoolean( KEY_SHOW_SAFE_TRIANGLE ) ) + safeTrianglePainter = new SafeTrianglePainter( popup ); + } + + private void uninstallEventQueue() { + if( subMenuEventQueue != null ) { + subMenuEventQueue.uninstall(); + subMenuEventQueue = null; + } + + if( safeTrianglePainter != null ) { + safeTrianglePainter.uninstall(); + safeTrianglePainter = null; + } + } + + private int findSubMenu( MenuElement[] path ) { + for( int i = path.length - 1; i >= 1; i-- ) { + if( path[i] instanceof JPopupMenu && + path[i - 1] instanceof JMenu && + !((JMenu)path[i - 1]).isTopLevelMenu() ) + return i; + } + return -1; + } + + private Polygon createSafeTriangle() { + return new Polygon( + new int[] { mouseX, targetX, targetX }, + new int[] { mouseY, targetTopY, targetBottomY }, + 3 ); + } + + //---- class SubMenuEventQueue -------------------------------------------- + + private class SubMenuEventQueue + extends EventQueue + { + private Timer mouseUpdateTimer; + private Timer timeoutTimer; + + private int newMouseX; + private int newMouseY; + private AWTEvent lastMouseEvent; + + SubMenuEventQueue() { + // timer used to slightly delay update of mouse location used for safe triangle + mouseUpdateTimer = new Timer( 50, e -> { + mouseX = newMouseX; + mouseY = newMouseY; + + if( safeTrianglePainter != null ) + safeTrianglePainter.repaint(); + } ); + mouseUpdateTimer.setRepeats( false ); + + // timer used to timeout safe triangle when mouse stops moving + timeoutTimer = new Timer( 200, e -> { + if( invokerBounds != null && !invokerBounds.contains( newMouseX, newMouseY ) ) { + // post last mouse event, which selects menu item at mouse location + if( lastMouseEvent != null ) { + postEvent( lastMouseEvent ); + lastMouseEvent = null; + } + + uninstallEventQueue(); + return; + } + } ); + timeoutTimer.setRepeats( false ); + + Toolkit.getDefaultToolkit().getSystemEventQueue().push( this ); + } + + void uninstall() { + mouseUpdateTimer.stop(); + mouseUpdateTimer = null; + + timeoutTimer.stop(); + timeoutTimer = null; + + lastMouseEvent = null; + + super.pop(); + } + + @Override + protected void dispatchEvent( AWTEvent e ) { + int id = e.getID(); + + if( e instanceof MouseEvent && + (id == MouseEvent.MOUSE_MOVED || id == MouseEvent.MOUSE_DRAGGED) ) + { + newMouseX = ((MouseEvent)e).getXOnScreen(); + newMouseY = ((MouseEvent)e).getYOnScreen(); + + if( safeTrianglePainter != null ) + safeTrianglePainter.repaint(); + + mouseUpdateTimer.stop(); + timeoutTimer.stop(); + + // check whether mouse moved within safe triangle + if( createSafeTriangle().contains( newMouseX, newMouseY ) ) { + // update mouse location delayed (this changes the safe triangle) + mouseUpdateTimer.start(); + + timeoutTimer.start(); + + // remember last mouse event, which will be posted if the mouse stops moving + lastMouseEvent = e; + + // ignore mouse event + return; + } + + // update mouse location immediately (this changes the safe triangle) + mouseX = newMouseX; + mouseY = newMouseY; + } + + super.dispatchEvent( e ); + } + } + + //---- class SafeTrianglePainter ------------------------------------------ + + private class SafeTrianglePainter + extends JComponent + { + SafeTrianglePainter( JPopupMenu popup ) { + Window window = SwingUtilities.windowForComponent( popup.getInvoker() ); + if( window instanceof RootPaneContainer ) { + JLayeredPane layeredPane = ((RootPaneContainer)window).getLayeredPane(); + setSize( layeredPane.getSize() ); + layeredPane.add( this, new Integer( JLayeredPane.POPUP_LAYER + 1 ) ); + } + } + + void uninstall() { + Container parent = getParent(); + if( parent != null ) { + parent.remove( this ); + parent.repaint(); + } + } + + @Override + protected void paintComponent( Graphics g ) { + Point locationOnScreen = getLocationOnScreen(); + g.translate( -locationOnScreen.x, -locationOnScreen.y ); + + g.setColor( Color.red ); + ((Graphics2D)g).draw( createSafeTriangle() ); + } + } +} diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSubMenusTest.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSubMenusTest.java new file mode 100644 index 00000000..2016b53f --- /dev/null +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSubMenusTest.java @@ -0,0 +1,622 @@ +/* + * Copyright 2022 FormDev Software GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.formdev.flatlaf.testing; + +import java.awt.Component; +import java.awt.event.*; +import javax.swing.*; +import net.miginfocom.swing.*; + +/** + * @author Karl Tauber + */ +public class FlatSubMenusTest + extends FlatTestPanel +{ + public static void main( String[] args ) { + SwingUtilities.invokeLater( () -> { + FlatTestFrame frame = FlatTestFrame.create( args, "FlatSubMenusTest" ); + UIManager.put( "FlatLaf.debug.menu.showSafeTriangle", true ); + frame.applyComponentOrientationToFrame = true; + frame.showFrame( FlatSubMenusTest::new, panel -> ((FlatSubMenusTest)panel).menuBar ); + } ); + } + + FlatSubMenusTest() { + initComponents(); + } + + private void showPopupMenuButtonActionPerformed(ActionEvent e) { + Component invoker = (Component) e.getSource(); + PopupMenu popupMenu = new PopupMenu(); + popupMenu.applyComponentOrientation( getComponentOrientation() ); + popupMenu.show( invoker, 0, invoker.getHeight() ); + } + + private void initComponents() { + // JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents + JButton showPopupMenuButton = new JButton(); + menuBar = new JMenuBar(); + JMenu menu3 = new JMenu(); + JMenuItem menuItem1 = new JMenuItem(); + JMenuItem menuItem50 = new JMenuItem(); + JMenu menu4 = new JMenu(); + JMenuItem menuItem9 = new JMenuItem(); + JMenuItem menuItem10 = new JMenuItem(); + JMenuItem menuItem11 = new JMenuItem(); + JMenu menu8 = new JMenu(); + JMenu menu21 = new JMenu(); + JMenuItem menuItem81 = new JMenuItem(); + JMenuItem menuItem82 = new JMenuItem(); + JMenuItem menuItem83 = new JMenuItem(); + JMenuItem menuItem41 = new JMenuItem(); + JMenuItem menuItem42 = new JMenuItem(); + JMenuItem menuItem43 = new JMenuItem(); + JMenuItem menuItem44 = new JMenuItem(); + JMenuItem menuItem45 = new JMenuItem(); + JMenuItem menuItem46 = new JMenuItem(); + JMenuItem menuItem12 = new JMenuItem(); + JMenuItem menuItem13 = new JMenuItem(); + JMenuItem menuItem14 = new JMenuItem(); + JMenuItem menuItem2 = new JMenuItem(); + JMenu menu5 = new JMenu(); + JMenuItem menuItem15 = new JMenuItem(); + JMenuItem menuItem16 = new JMenuItem(); + JMenuItem menuItem17 = new JMenuItem(); + JMenuItem menuItem18 = new JMenuItem(); + JMenuItem menuItem19 = new JMenuItem(); + JMenuItem menuItem20 = new JMenuItem(); + JMenuItem menuItem21 = new JMenuItem(); + JMenuItem menuItem22 = new JMenuItem(); + JMenuItem menuItem23 = new JMenuItem(); + JMenuItem menuItem24 = new JMenuItem(); + JMenuItem menuItem25 = new JMenuItem(); + JMenuItem menuItem26 = new JMenuItem(); + JMenuItem menuItem27 = new JMenuItem(); + JMenuItem menuItem28 = new JMenuItem(); + JMenuItem menuItem29 = new JMenuItem(); + JMenu menu6 = new JMenu(); + JMenuItem menuItem30 = new JMenuItem(); + JMenuItem menuItem31 = new JMenuItem(); + JMenuItem menuItem32 = new JMenuItem(); + JMenu menu9 = new JMenu(); + JMenuItem menuItem47 = new JMenuItem(); + JMenuItem menuItem48 = new JMenuItem(); + JMenuItem menuItem49 = new JMenuItem(); + JMenuItem menuItem33 = new JMenuItem(); + JMenuItem menuItem34 = new JMenuItem(); + JMenuItem menuItem3 = new JMenuItem(); + JMenuItem menuItem4 = new JMenuItem(); + JMenuItem menuItem5 = new JMenuItem(); + JMenuItem menuItem6 = new JMenuItem(); + JMenuItem menuItem7 = new JMenuItem(); + JMenuItem menuItem8 = new JMenuItem(); + JMenu menu7 = new JMenu(); + JMenuItem menuItem35 = new JMenuItem(); + JMenuItem menuItem37 = new JMenuItem(); + JMenuItem menuItem36 = new JMenuItem(); + JMenuItem menuItem38 = new JMenuItem(); + JMenuItem menuItem39 = new JMenuItem(); + JMenuItem menuItem40 = new JMenuItem(); + + //======== this ======== + setLayout(new MigLayout( + "ltr,insets dialog,hidemode 3", + // columns + "[]", + // rows + "[]")); + + //---- showPopupMenuButton ---- + showPopupMenuButton.setText("show JPopupMenu"); + showPopupMenuButton.addActionListener(e -> showPopupMenuButtonActionPerformed(e)); + add(showPopupMenuButton, "cell 0 0"); + + //======== menuBar ======== + { + + //======== menu3 ======== + { + menu3.setText("main menu"); + + //---- menuItem1 ---- + menuItem1.setText("text"); + menu3.add(menuItem1); + + //---- menuItem50 ---- + menuItem50.setText("text"); + menu3.add(menuItem50); + + //======== menu4 ======== + { + menu4.setText("text"); + + //---- menuItem9 ---- + menuItem9.setText("text"); + menu4.add(menuItem9); + + //---- menuItem10 ---- + menuItem10.setText("text text"); + menu4.add(menuItem10); + + //---- menuItem11 ---- + menuItem11.setText("text"); + menu4.add(menuItem11); + + //======== menu8 ======== + { + menu8.setText("text"); + + //======== menu21 ======== + { + menu21.setText("text"); + + //---- menuItem81 ---- + menuItem81.setText("text"); + menu21.add(menuItem81); + + //---- menuItem82 ---- + menuItem82.setText("text"); + menu21.add(menuItem82); + + //---- menuItem83 ---- + menuItem83.setText("text"); + menu21.add(menuItem83); + } + menu8.add(menu21); + + //---- menuItem41 ---- + menuItem41.setText("text"); + menu8.add(menuItem41); + + //---- menuItem42 ---- + menuItem42.setText("text"); + menu8.add(menuItem42); + + //---- menuItem43 ---- + menuItem43.setText("text"); + menu8.add(menuItem43); + + //---- menuItem44 ---- + menuItem44.setText("text"); + menu8.add(menuItem44); + + //---- menuItem45 ---- + menuItem45.setText("text"); + menu8.add(menuItem45); + + //---- menuItem46 ---- + menuItem46.setText("text"); + menu8.add(menuItem46); + } + menu4.add(menu8); + + //---- menuItem12 ---- + menuItem12.setText("text"); + menu4.add(menuItem12); + + //---- menuItem13 ---- + menuItem13.setText("text"); + menu4.add(menuItem13); + + //---- menuItem14 ---- + menuItem14.setText("text"); + menu4.add(menuItem14); + } + menu3.add(menu4); + + //---- menuItem2 ---- + menuItem2.setText("text"); + menu3.add(menuItem2); + + //======== menu5 ======== + { + menu5.setText("text"); + + //---- menuItem15 ---- + menuItem15.setText("text bla bla"); + menu5.add(menuItem15); + + //---- menuItem16 ---- + menuItem16.setText("text"); + menu5.add(menuItem16); + + //---- menuItem17 ---- + menuItem17.setText("text"); + menu5.add(menuItem17); + + //---- menuItem18 ---- + menuItem18.setText("text"); + menu5.add(menuItem18); + + //---- menuItem19 ---- + menuItem19.setText("text"); + menu5.add(menuItem19); + + //---- menuItem20 ---- + menuItem20.setText("text"); + menu5.add(menuItem20); + + //---- menuItem21 ---- + menuItem21.setText("text"); + menu5.add(menuItem21); + + //---- menuItem22 ---- + menuItem22.setText("text"); + menu5.add(menuItem22); + + //---- menuItem23 ---- + menuItem23.setText("text"); + menu5.add(menuItem23); + + //---- menuItem24 ---- + menuItem24.setText("text"); + menu5.add(menuItem24); + + //---- menuItem25 ---- + menuItem25.setText("text"); + menu5.add(menuItem25); + + //---- menuItem26 ---- + menuItem26.setText("text"); + menu5.add(menuItem26); + + //---- menuItem27 ---- + menuItem27.setText("text"); + menu5.add(menuItem27); + + //---- menuItem28 ---- + menuItem28.setText("text"); + menu5.add(menuItem28); + + //---- menuItem29 ---- + menuItem29.setText("text"); + menu5.add(menuItem29); + } + menu3.add(menu5); + + //======== menu6 ======== + { + menu6.setText("text"); + + //---- menuItem30 ---- + menuItem30.setText("text o text"); + menu6.add(menuItem30); + + //---- menuItem31 ---- + menuItem31.setText("text"); + menu6.add(menuItem31); + + //---- menuItem32 ---- + menuItem32.setText("text"); + menu6.add(menuItem32); + + //======== menu9 ======== + { + menu9.setText("text"); + + //---- menuItem47 ---- + menuItem47.setText("text"); + menu9.add(menuItem47); + + //---- menuItem48 ---- + menuItem48.setText("text"); + menu9.add(menuItem48); + + //---- menuItem49 ---- + menuItem49.setText("text"); + menu9.add(menuItem49); + } + menu6.add(menu9); + + //---- menuItem33 ---- + menuItem33.setText("text"); + menu6.add(menuItem33); + + //---- menuItem34 ---- + menuItem34.setText("text"); + menu6.add(menuItem34); + } + menu3.add(menu6); + + //---- menuItem3 ---- + menuItem3.setText("text"); + menu3.add(menuItem3); + + //---- menuItem4 ---- + menuItem4.setText("longer text"); + menu3.add(menuItem4); + + //---- menuItem5 ---- + menuItem5.setText("text"); + menu3.add(menuItem5); + + //---- menuItem6 ---- + menuItem6.setText("text"); + menu3.add(menuItem6); + + //---- menuItem7 ---- + menuItem7.setText("text"); + menu3.add(menuItem7); + + //---- menuItem8 ---- + menuItem8.setText("text"); + menu3.add(menuItem8); + + //======== menu7 ======== + { + menu7.setText("text"); + + //---- menuItem35 ---- + menuItem35.setText("text abc"); + menu7.add(menuItem35); + + //---- menuItem37 ---- + menuItem37.setText("text"); + menu7.add(menuItem37); + + //---- menuItem36 ---- + menuItem36.setText("text"); + menu7.add(menuItem36); + + //---- menuItem38 ---- + menuItem38.setText("text"); + menu7.add(menuItem38); + + //---- menuItem39 ---- + menuItem39.setText("text"); + menu7.add(menuItem39); + + //---- menuItem40 ---- + menuItem40.setText("text"); + menu7.add(menuItem40); + } + menu3.add(menu7); + } + menuBar.add(menu3); + } + // JFormDesigner - End of component initialization //GEN-END:initComponents + } + + // JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables + private JMenuBar menuBar; + // JFormDesigner - End of variables declaration //GEN-END:variables + + //---- class PopupMenu ---------------------------------------------------- + + private class PopupMenu extends JPopupMenu { + private PopupMenu() { + initComponents(); + } + + private void initComponents() { + // JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents + JMenuItem menuItem54 = new JMenuItem(); + JMenuItem menuItem51 = new JMenuItem(); + JMenu menu10 = new JMenu(); + JMenuItem menuItem57 = new JMenuItem(); + JMenuItem menuItem58 = new JMenuItem(); + JMenu menu14 = new JMenu(); + JMenu menu18 = new JMenu(); + JMenu menu19 = new JMenu(); + JMenuItem menuItem76 = new JMenuItem(); + JMenuItem menuItem77 = new JMenuItem(); + JMenuItem menuItem78 = new JMenuItem(); + JMenu menu20 = new JMenu(); + JMenuItem menuItem73 = new JMenuItem(); + JMenuItem menuItem74 = new JMenuItem(); + JMenuItem menuItem75 = new JMenuItem(); + JMenuItem menuItem79 = new JMenuItem(); + JMenuItem menuItem80 = new JMenuItem(); + JMenuItem menuItem70 = new JMenuItem(); + JMenuItem menuItem71 = new JMenuItem(); + JMenuItem menuItem72 = new JMenuItem(); + JMenuItem menuItem59 = new JMenuItem(); + JMenuItem menuItem60 = new JMenuItem(); + JMenuItem menuItem52 = new JMenuItem(); + JMenu menu11 = new JMenu(); + JMenuItem menuItem61 = new JMenuItem(); + JMenuItem menuItem62 = new JMenuItem(); + JMenuItem menuItem63 = new JMenuItem(); + JMenu menu12 = new JMenu(); + JMenuItem menuItem64 = new JMenuItem(); + JMenuItem menuItem65 = new JMenuItem(); + JMenuItem menuItem66 = new JMenuItem(); + JMenuItem menuItem53 = new JMenuItem(); + JMenuItem menuItem55 = new JMenuItem(); + JMenuItem menuItem56 = new JMenuItem(); + JMenu menu13 = new JMenu(); + JMenuItem menuItem67 = new JMenuItem(); + JMenuItem menuItem68 = new JMenuItem(); + JMenuItem menuItem69 = new JMenuItem(); + + //======== this ======== + + //---- menuItem54 ---- + menuItem54.setText("text"); + add(menuItem54); + + //---- menuItem51 ---- + menuItem51.setText("text text"); + add(menuItem51); + + //======== menu10 ======== + { + menu10.setText("text"); + + //---- menuItem57 ---- + menuItem57.setText("text"); + menu10.add(menuItem57); + + //---- menuItem58 ---- + menuItem58.setText("text"); + menu10.add(menuItem58); + + //======== menu14 ======== + { + menu14.setText("text"); + + //======== menu18 ======== + { + menu18.setText("text"); + + //======== menu19 ======== + { + menu19.setText("text"); + + //---- menuItem76 ---- + menuItem76.setText("text"); + menu19.add(menuItem76); + + //---- menuItem77 ---- + menuItem77.setText("text"); + menu19.add(menuItem77); + + //---- menuItem78 ---- + menuItem78.setText("text"); + menu19.add(menuItem78); + } + menu18.add(menu19); + + //======== menu20 ======== + { + menu20.setText("text"); + + //---- menuItem73 ---- + menuItem73.setText("text"); + menu20.add(menuItem73); + + //---- menuItem74 ---- + menuItem74.setText("text"); + menu20.add(menuItem74); + + //---- menuItem75 ---- + menuItem75.setText("text"); + menu20.add(menuItem75); + } + menu18.add(menu20); + + //---- menuItem79 ---- + menuItem79.setText("text"); + menu18.add(menuItem79); + + //---- menuItem80 ---- + menuItem80.setText("text"); + menu18.add(menuItem80); + } + menu14.add(menu18); + + //---- menuItem70 ---- + menuItem70.setText("text"); + menu14.add(menuItem70); + + //---- menuItem71 ---- + menuItem71.setText("text"); + menu14.add(menuItem71); + + //---- menuItem72 ---- + menuItem72.setText("text"); + menu14.add(menuItem72); + } + menu10.add(menu14); + + //---- menuItem59 ---- + menuItem59.setText("text"); + menu10.add(menuItem59); + + //---- menuItem60 ---- + menuItem60.setText("text"); + menu10.add(menuItem60); + } + add(menu10); + + //---- menuItem52 ---- + menuItem52.setText("text"); + add(menuItem52); + + //======== menu11 ======== + { + menu11.setText("text"); + + //---- menuItem61 ---- + menuItem61.setText("text"); + menu11.add(menuItem61); + + //---- menuItem62 ---- + menuItem62.setText("text"); + menu11.add(menuItem62); + + //---- menuItem63 ---- + menuItem63.setText("text"); + menu11.add(menuItem63); + } + add(menu11); + + //======== menu12 ======== + { + menu12.setText("text"); + + //---- menuItem64 ---- + menuItem64.setText("text"); + menu12.add(menuItem64); + + //---- menuItem65 ---- + menuItem65.setText("text"); + menu12.add(menuItem65); + + //---- menuItem66 ---- + menuItem66.setText("text"); + menu12.add(menuItem66); + } + add(menu12); + + //---- menuItem53 ---- + menuItem53.setText("text"); + add(menuItem53); + + //---- menuItem55 ---- + menuItem55.setText("text"); + add(menuItem55); + + //---- menuItem56 ---- + menuItem56.setText("text"); + add(menuItem56); + + //======== menu13 ======== + { + menu13.setText("text"); + + //---- menuItem67 ---- + menuItem67.setText("text"); + menu13.add(menuItem67); + + //---- menuItem68 ---- + menuItem68.setText("text"); + menu13.add(menuItem68); + + //---- menuItem69 ---- + menuItem69.setText("text"); + menu13.add(menuItem69); + } + add(menu13); + // JFormDesigner - End of component initialization //GEN-END:initComponents + } + + // JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables + // JFormDesigner - End of variables declaration //GEN-END:variables + } +} diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSubMenusTest.jfd b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSubMenusTest.jfd new file mode 100644 index 00000000..d2e4189a --- /dev/null +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSubMenusTest.jfd @@ -0,0 +1,439 @@ +JFDML JFormDesigner: "7.0.5.0.404" Java: "17" encoding: "UTF-8" + +new FormModel { + contentType: "form/swing" + root: new FormRoot { + auxiliary() { + "JavaCodeGenerator.defaultVariableLocal": true + } + add( new FormContainer( "com.formdev.flatlaf.testing.FlatTestPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { + "$layoutConstraints": "ltr,insets dialog,hidemode 3" + "$columnConstraints": "[]" + "$rowConstraints": "[]" + } ) { + name: "this" + add( new FormComponent( "javax.swing.JButton" ) { + name: "showPopupMenuButton" + "text": "show JPopupMenu" + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "showPopupMenuButtonActionPerformed", true ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 0" + } ) + }, new FormLayoutConstraints( null ) { + "location": new java.awt.Point( 0, 0 ) + "size": new java.awt.Dimension( 390, 130 ) + } ) + add( new FormContainer( "javax.swing.JMenuBar", new FormLayoutManager( class javax.swing.JMenuBar ) ) { + name: "menuBar" + auxiliary() { + "JavaCodeGenerator.variableLocal": false + } + add( new FormContainer( "javax.swing.JMenu", new FormLayoutManager( class javax.swing.JMenu ) ) { + name: "menu3" + "text": "main menu" + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem1" + "text": "text" + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem50" + "text": "text" + } ) + add( new FormContainer( "javax.swing.JMenu", new FormLayoutManager( class javax.swing.JMenu ) ) { + name: "menu4" + "text": "text" + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem9" + "text": "text" + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem10" + "text": "text text" + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem11" + "text": "text" + } ) + add( new FormContainer( "javax.swing.JMenu", new FormLayoutManager( class javax.swing.JMenu ) ) { + name: "menu8" + "text": "text" + add( new FormContainer( "javax.swing.JMenu", new FormLayoutManager( class javax.swing.JMenu ) ) { + name: "menu21" + "text": "text" + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem81" + "text": "text" + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem82" + "text": "text" + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem83" + "text": "text" + } ) + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem41" + "text": "text" + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem42" + "text": "text" + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem43" + "text": "text" + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem44" + "text": "text" + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem45" + "text": "text" + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem46" + "text": "text" + } ) + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem12" + "text": "text" + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem13" + "text": "text" + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem14" + "text": "text" + } ) + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem2" + "text": "text" + } ) + add( new FormContainer( "javax.swing.JMenu", new FormLayoutManager( class javax.swing.JMenu ) ) { + name: "menu5" + "text": "text" + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem15" + "text": "text bla bla" + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem16" + "text": "text" + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem17" + "text": "text" + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem18" + "text": "text" + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem19" + "text": "text" + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem20" + "text": "text" + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem21" + "text": "text" + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem22" + "text": "text" + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem23" + "text": "text" + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem24" + "text": "text" + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem25" + "text": "text" + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem26" + "text": "text" + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem27" + "text": "text" + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem28" + "text": "text" + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem29" + "text": "text" + } ) + } ) + add( new FormContainer( "javax.swing.JMenu", new FormLayoutManager( class javax.swing.JMenu ) ) { + name: "menu6" + "text": "text" + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem30" + "text": "text o text" + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem31" + "text": "text" + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem32" + "text": "text" + } ) + add( new FormContainer( "javax.swing.JMenu", new FormLayoutManager( class javax.swing.JMenu ) ) { + name: "menu9" + "text": "text" + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem47" + "text": "text" + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem48" + "text": "text" + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem49" + "text": "text" + } ) + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem33" + "text": "text" + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem34" + "text": "text" + } ) + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem3" + "text": "text" + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem4" + "text": "longer text" + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem5" + "text": "text" + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem6" + "text": "text" + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem7" + "text": "text" + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem8" + "text": "text" + } ) + add( new FormContainer( "javax.swing.JMenu", new FormLayoutManager( class javax.swing.JMenu ) ) { + name: "menu7" + "text": "text" + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem35" + "text": "text abc" + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem37" + "text": "text" + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem36" + "text": "text" + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem38" + "text": "text" + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem39" + "text": "text" + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem40" + "text": "text" + } ) + } ) + } ) + }, new FormLayoutConstraints( null ) { + "location": new java.awt.Point( 0, 175 ) + } ) + add( new FormContainer( "javax.swing.JPopupMenu", new FormLayoutManager( class javax.swing.JPopupMenu ) ) { + name: "popupMenu1" + auxiliary() { + "JavaCodeGenerator.className": "PopupMenu" + } + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem54" + "text": "text" + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem51" + "text": "text text" + } ) + add( new FormContainer( "javax.swing.JMenu", new FormLayoutManager( class javax.swing.JMenu ) ) { + name: "menu10" + "text": "text" + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem57" + "text": "text" + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem58" + "text": "text" + } ) + add( new FormContainer( "javax.swing.JMenu", new FormLayoutManager( class javax.swing.JMenu ) ) { + name: "menu14" + "text": "text" + add( new FormContainer( "javax.swing.JMenu", new FormLayoutManager( class javax.swing.JMenu ) ) { + name: "menu18" + "text": "text" + add( new FormContainer( "javax.swing.JMenu", new FormLayoutManager( class javax.swing.JMenu ) ) { + name: "menu19" + "text": "text" + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem76" + "text": "text" + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem77" + "text": "text" + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem78" + "text": "text" + } ) + } ) + add( new FormContainer( "javax.swing.JMenu", new FormLayoutManager( class javax.swing.JMenu ) ) { + name: "menu20" + "text": "text" + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem73" + "text": "text" + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem74" + "text": "text" + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem75" + "text": "text" + } ) + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem79" + "text": "text" + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem80" + "text": "text" + } ) + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem70" + "text": "text" + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem71" + "text": "text" + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem72" + "text": "text" + } ) + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem59" + "text": "text" + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem60" + "text": "text" + } ) + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem52" + "text": "text" + } ) + add( new FormContainer( "javax.swing.JMenu", new FormLayoutManager( class javax.swing.JMenu ) ) { + name: "menu11" + "text": "text" + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem61" + "text": "text" + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem62" + "text": "text" + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem63" + "text": "text" + } ) + } ) + add( new FormContainer( "javax.swing.JMenu", new FormLayoutManager( class javax.swing.JMenu ) ) { + name: "menu12" + "text": "text" + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem64" + "text": "text" + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem65" + "text": "text" + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem66" + "text": "text" + } ) + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem53" + "text": "text" + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem55" + "text": "text" + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem56" + "text": "text" + } ) + add( new FormContainer( "javax.swing.JMenu", new FormLayoutManager( class javax.swing.JMenu ) ) { + name: "menu13" + "text": "text" + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem67" + "text": "text" + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem68" + "text": "text" + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem69" + "text": "text" + } ) + } ) + }, new FormLayoutConstraints( null ) { + "location": new java.awt.Point( 275, 175 ) + } ) + } +}