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 )