diff --git a/CHANGELOG.md b/CHANGELOG.md index 207b98bd..2eb363e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ FlatLaf Change Log - Menus: On Windows, pressing F10 now activates the menu bar without showing a menu popup (as usual on Windows platform). On other platforms the first menu popup is shown. +- Menus: On Windows, releasing Alt key now activates the menu bar (as + usual on Windows platform). (issue #43) - Menus: Fixed inconsistent left padding in menu items. (issue #3) - Menus: Fixed: Setting `iconTextGap` property on a menu item did increase left and right margins. (issue #54) diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/MnemonicHandler.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/MnemonicHandler.java index 5e4d98b8..d3f6e936 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/MnemonicHandler.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/MnemonicHandler.java @@ -28,7 +28,10 @@ import java.awt.event.WindowEvent; import java.awt.event.WindowListener; import java.lang.ref.WeakReference; import javax.swing.AbstractButton; +import javax.swing.JFrame; import javax.swing.JLabel; +import javax.swing.JMenu; +import javax.swing.JMenuBar; import javax.swing.JRootPane; import javax.swing.JTabbedPane; import javax.swing.MenuElement; @@ -74,6 +77,9 @@ class MnemonicHandler showMnemonics( shouldShowMnemonics( e ) && e.isControlDown() && e.isAltDown(), e.getComponent() ); } else { // Alt key must be pressed on Windows and Linux + if( SystemInfo.IS_WINDOWS ) + return processKeyEventOnWindows( e ); + if( keyCode == KeyEvent.VK_ALT ) showMnemonics( shouldShowMnemonics( e ), e.getComponent() ); } @@ -86,10 +92,77 @@ class MnemonicHandler MenuSelectionManager.defaultManager().getSelectedPath().length > 0; } + private int altPressedEventCount; + private boolean selectMenuOnAltReleased; + + /** + * Special Alt key behavior on Windows. + * + * Press-and-release Alt key selects first menu (if available) and moves focus + * temporary to menu bar. If menu bar has focus (some menu is selected), + * pressing Alt key unselects menu and moves focus back to permanent focus owner. + */ + private boolean processKeyEventOnWindows( KeyEvent e ) { + if( e.getKeyCode() != KeyEvent.VK_ALT ) { + selectMenuOnAltReleased = false; + return false; + } + + if( e.getID() == KeyEvent.KEY_PRESSED ) { + altPressedEventCount++; + + if( altPressedEventCount == 1 && !e.isConsumed() ) { + MenuSelectionManager menuSelectionManager = MenuSelectionManager.defaultManager(); + selectMenuOnAltReleased = (menuSelectionManager.getSelectedPath().length == 0); + + // if menu is selected when Alt key is pressed then clear menu selection + if( !selectMenuOnAltReleased ) + menuSelectionManager.clearSelectedPath(); + } + + // show mnemonics + showMnemonics( shouldShowMnemonics( e ), e.getComponent() ); + + // avoid that the system menu of the window gets focus + e.consume(); + return true; + + } else if( e.getID() == KeyEvent.KEY_RELEASED ) { + altPressedEventCount = 0; + + boolean mnemonicsShown = false; + if( selectMenuOnAltReleased && !e.isConsumed() ) { + MenuSelectionManager menuSelectionManager = MenuSelectionManager.defaultManager(); + if( menuSelectionManager.getSelectedPath().length == 0 ) { + // get menu bar and first menu + Component c = e.getComponent(); + JRootPane rootPane = SwingUtilities.getRootPane( c ); + Window window = (rootPane != null) ? SwingUtilities.getWindowAncestor( rootPane ) : null; + JMenuBar menuBar = (rootPane != null) ? rootPane.getJMenuBar() : null; + if( menuBar == null && window instanceof JFrame ) + menuBar = ((JFrame)window).getJMenuBar(); + JMenu firstMenu = (menuBar != null) ? menuBar.getMenu( 0 ) : null; + + // select first menu and show mnemonics + if( firstMenu != null ) { + menuSelectionManager.setSelectedPath( new MenuElement[] { menuBar, firstMenu } ); + showMnemonics( true, c ); + mnemonicsShown = true; + } + } + } + + // hide mnemonics + if( !mnemonicsShown ) + showMnemonics( shouldShowMnemonics( e ), e.getComponent() ); + } + return false; + } + @Override public void stateChanged( ChangeEvent e ) { MenuElement[] selectedPath = MenuSelectionManager.defaultManager().getSelectedPath(); - if( selectedPath.length == 0 ) { + if( selectedPath.length == 0 && altPressedEventCount == 0 ) { // hide mnemonics when menu selection was canceled showMnemonics( false, null ); } @@ -126,7 +199,7 @@ class MnemonicHandler // use invokeLater() to avoid that the listener is removed // while the listener queue is iterated to fire this event EventQueue.invokeLater( () -> { - showMnemonics( false, c ); + showMnemonics( false, null ); } ); } }; diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatMnemonicsTest.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatMnemonicsTest.java index 19665adb..4c756b0a 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatMnemonicsTest.java +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatMnemonicsTest.java @@ -56,6 +56,10 @@ public class FlatMnemonicsTest SwingUtilities.windowForComponent( this ).repaint(); } + private void openDialog() { + JOptionPane.showMessageDialog( this, new FlatMnemonicsTest() ); + } + private void initComponents() { // JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents JLabel label1 = new JLabel(); @@ -77,6 +81,7 @@ public class FlatMnemonicsTest JPanel panel3 = new JPanel(); JLabel label5 = new JLabel(); alwaysShowMnemonicsCheckBox = new JCheckBox(); + JButton button2 = new JButton(); menuBar = new JMenuBar(); JMenu fileMenu = new JMenu(); JMenuItem newMenuItem = new JMenuItem(); @@ -114,7 +119,7 @@ public class FlatMnemonicsTest // columns "[fill]" + "[150,fill]para" + - "[grow,fill]", + "[300,grow,fill]", // rows "[]" + "[]" + @@ -238,6 +243,11 @@ public class FlatMnemonicsTest alwaysShowMnemonicsCheckBox.addActionListener(e -> alwaysShowMnemonicsChanged()); add(alwaysShowMnemonicsCheckBox, "cell 0 7 2 1,alignx left,growx 0"); + //---- button2 ---- + button2.setText("Open Dialog"); + button2.addActionListener(e -> openDialog()); + add(button2, "cell 2 7,alignx left,growx 0"); + //======== menuBar ======== { diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatMnemonicsTest.jfd b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatMnemonicsTest.jfd index 8558674d..231dcf17 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatMnemonicsTest.jfd +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatMnemonicsTest.jfd @@ -8,7 +8,7 @@ new FormModel { } add( new FormContainer( "com.formdev.flatlaf.testing.FlatTestPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { "$layoutConstraints": "ltr,insets dialog,hidemode 3" - "$columnConstraints": "[fill][150,fill]para[grow,fill]" + "$columnConstraints": "[fill][150,fill]para[300,grow,fill]" "$rowConstraints": "[][][][][]para[][]para[]" } ) { name: "this" @@ -141,6 +141,13 @@ new FormModel { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { "value": "cell 0 7 2 1,alignx left,growx 0" } ) + add( new FormComponent( "javax.swing.JButton" ) { + name: "button2" + "text": "Open Dialog" + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "openDialog", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 2 7,alignx left,growx 0" + } ) }, new FormLayoutConstraints( null ) { "location": new java.awt.Point( 0, 0 ) "size": new java.awt.Dimension( 790, 380 )