diff --git a/platform/platform-impl/resources/intellij.platform.ide.impl.xml b/platform/platform-impl/resources/intellij.platform.ide.impl.xml
index a314589fce31..a39c3bb42ba8 100644
--- a/platform/platform-impl/resources/intellij.platform.ide.impl.xml
+++ b/platform/platform-impl/resources/intellij.platform.ide.impl.xml
@@ -70,5 +70,11 @@
defaultValue="60"
description="The interval between too-small window size checks on Windows and attempts to restore the size. Set to zero to disable"
/>
+
diff --git a/platform/platform-impl/src/com/intellij/ide/IdeEventQueue.kt b/platform/platform-impl/src/com/intellij/ide/IdeEventQueue.kt
index 36a0cb535df0..cfdaacdde143 100644
--- a/platform/platform-impl/src/com/intellij/ide/IdeEventQueue.kt
+++ b/platform/platform-impl/src/com/intellij/ide/IdeEventQueue.kt
@@ -1287,7 +1287,9 @@ private class WindowsAltSuppressor : IdeEventQueue.NonLockedEventDispatcher {
if (uiSettings == null ||
!SystemInfoRt.isWindows ||
!Registry.`is`("actionSystem.win.suppressAlt", true) ||
- !(uiSettings.hideToolStripes || uiSettings.presentationMode)) {
+ // Need to handle Alt to show hidden tool stripes by double Alt or to focus the main menu
+ !(uiSettings.hideToolStripes || uiSettings.presentationMode) &&
+ !Registry.`is`("ide.windows.main.menu.focus.on.alt", false)) {
return false
}
diff --git a/platform/platform-impl/src/com/intellij/openapi/wm/impl/customFrameDecorations/header/toolbar/ExpandableMenu.kt b/platform/platform-impl/src/com/intellij/openapi/wm/impl/customFrameDecorations/header/toolbar/ExpandableMenu.kt
index 60866eeb41b5..7d9e4b04f9bc 100644
--- a/platform/platform-impl/src/com/intellij/openapi/wm/impl/customFrameDecorations/header/toolbar/ExpandableMenu.kt
+++ b/platform/platform-impl/src/com/intellij/openapi/wm/impl/customFrameDecorations/header/toolbar/ExpandableMenu.kt
@@ -30,6 +30,8 @@ import java.awt.event.ComponentAdapter
import java.awt.event.ComponentEvent
import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent
+import javax.accessibility.AccessibleContext
+import javax.accessibility.AccessibleState
import javax.swing.JComponent
import javax.swing.JFrame
import javax.swing.JLayeredPane
@@ -102,7 +104,7 @@ internal class ExpandableMenu(
ideMenuHelper.updateUI()
}
- fun switchState(actionMenuToShow: ActionMenu? = null, itemInd: Int = 0) {
+ fun switchState(actionMenuToShow: ActionMenu? = null, itemInd: Int = 0, selectOnlyHeaderMenu: Boolean = false) {
if (isShowing() && actionMenuToShow == null) {
hideExpandedMenuBar()
return
@@ -130,11 +132,11 @@ internal class ExpandableMenu(
// The first menu usage has no selection in the menu. Fix it by invokeLater
ApplicationManager.getApplication().invokeLater {
- selectMenu(actionMenu = actionMenuToShow, itemInd = itemInd)
+ selectMenu(actionMenu = actionMenuToShow, itemInd = itemInd, selectOnlyHeaderMenu = selectOnlyHeaderMenu)
}
}
- private fun selectMenu(actionMenu: ActionMenu? = null, itemInd: Int) {
+ private fun selectMenu(actionMenu: ActionMenu? = null, itemInd: Int, selectOnlyHeaderMenu: Boolean = false) {
var menu = ideMenu.getMenu(itemInd)
if (actionMenu != null) {
for (m in ideMenu.rootMenuItems) {
@@ -148,8 +150,11 @@ internal class ExpandableMenu(
menu ?: return
val subElements = menu.popupMenu.subElements
- if (subElements.isEmpty()) {
+ if (subElements.isEmpty() || selectOnlyHeaderMenu) {
MenuSelectionManager.defaultManager().selectedPath = arrayOf(ideMenu, menu)
+ ApplicationManager.getApplication().invokeLater {
+ menu.accessibleContext.firePropertyChange(AccessibleContext.ACCESSIBLE_STATE_PROPERTY, null, AccessibleState.SELECTED)
+ }
}
else {
MenuSelectionManager.defaultManager().selectedPath = arrayOf(ideMenu, menu, menu.popupMenu, subElements[0])
diff --git a/platform/platform-impl/src/com/intellij/platform/ide/menu/JMenuBasedIdeMenuBarHelper.kt b/platform/platform-impl/src/com/intellij/platform/ide/menu/JMenuBasedIdeMenuBarHelper.kt
index b85a45fd9108..ea42750add39 100644
--- a/platform/platform-impl/src/com/intellij/platform/ide/menu/JMenuBasedIdeMenuBarHelper.kt
+++ b/platform/platform-impl/src/com/intellij/platform/ide/menu/JMenuBasedIdeMenuBarHelper.kt
@@ -5,13 +5,42 @@ import com.intellij.ide.ui.UISettings
import com.intellij.openapi.actionSystem.ActionGroup
import com.intellij.openapi.actionSystem.ActionPlaces
import com.intellij.openapi.actionSystem.impl.ActionMenu
+import com.intellij.openapi.application.ApplicationManager
+import com.intellij.openapi.util.SystemInfoRt
+import com.intellij.openapi.util.registry.Registry
import com.intellij.openapi.wm.impl.IdeFrameDecorator
import com.intellij.openapi.wm.impl.headertoolbar.MainMenuWithButton
import com.intellij.openapi.wm.impl.headertoolbar.MergedMainMenu
+import com.intellij.ui.AppUIUtil
import com.intellij.util.concurrency.ThreadingAssertions
+import com.intellij.util.ui.accessibility.ScreenReader
+import kotlinx.coroutines.job
+import java.awt.AWTEvent
+import java.awt.KeyEventPostProcessor
+import java.awt.KeyboardFocusManager
+import java.awt.Toolkit
+import java.awt.event.AWTEventListener
+import java.awt.event.InputEvent
+import java.awt.event.KeyEvent
+import java.awt.event.MouseEvent
+import javax.accessibility.AccessibleContext
+import javax.accessibility.AccessibleState
+import javax.swing.MenuElement
import javax.swing.MenuSelectionManager
+import javax.swing.SwingUtilities
internal class JMenuBasedIdeMenuBarHelper(flavor: IdeMenuFlavor, menuBar: IdeJMenuBar.JMenuBarImpl) : IdeMenuBarHelper(flavor, menuBar) {
+ init {
+ if (WinAltKeyProcessor.isEnabled()) {
+ WinAltKeyProcessor.register(this)
+ menuBar.coroutineScope.coroutineContext.job.invokeOnCompletion {
+ ApplicationManager.getApplication().invokeLater {
+ WinAltKeyProcessor.unregister(this)
+ }
+ }
+ }
+ }
+
override fun isUpdateForbidden() = MenuSelectionManager.defaultManager().selectedPath.isNotEmpty()
override suspend fun doUpdateVisibleActions(newVisibleActions: List, forceRebuild: Boolean) {
@@ -68,4 +97,125 @@ internal class JMenuBasedIdeMenuBarHelper(flavor: IdeMenuFlavor, menuBar: IdeJMe
menuBar.frame.validate()
}
}
-}
\ No newline at end of file
+
+ internal fun activateMenu() {
+ val menuBarComponent = menuBar.component
+ if (menuBarComponent is MergedMainMenu) {
+ val mainMenuWithButton = menuBarComponent.parent as? MainMenuWithButton
+ if (mainMenuWithButton?.mainMenuButton?.button?.isShowing == true) {
+ mainMenuWithButton.mainMenuButton.expandableMenu?.switchState(selectOnlyHeaderMenu = true)
+ return
+ }
+ }
+
+ val ideMenuBar = menuBarComponent as? IdeJMenuBar ?: return
+ val firstMenu = ideMenuBar.rootMenuItems.firstOrNull() ?: return
+ MenuSelectionManager.defaultManager().selectedPath = arrayOf(menuBarComponent, firstMenu)
+ ApplicationManager.getApplication().invokeLater {
+ firstMenu.accessibleContext.firePropertyChange(AccessibleContext.ACCESSIBLE_STATE_PROPERTY, null, AccessibleState.SELECTED)
+ }
+ }
+
+ internal fun shouldProcessAltRelease(e: KeyEvent): Boolean {
+ val menuBarComponent = menuBar.component
+ val visibleComponent = if (menuBarComponent.isShowing) menuBarComponent
+ else {
+ val button = ((menuBarComponent as? MergedMainMenu)?.parent as? MainMenuWithButton)?.mainMenuButton?.button
+ if (button?.isShowing == true) button else return false
+ }
+
+ val window = SwingUtilities.getWindowAncestor(visibleComponent) ?: return false
+ if (SwingUtilities.getWindowAncestor(e.component) != window && e.component != window) return false
+ if (MenuSelectionManager.defaultManager().selectedPath.isNotEmpty()) return false
+ if (ScreenReader.isActive() && AppUIUtil.isInFullScreen(window)) return false
+
+ return true
+ }
+
+}
+
+internal object WinAltKeyProcessor : KeyEventPostProcessor, AWTEventListener {
+ private val helpers = mutableListOf()
+ private var registered = false
+
+ private var altPressed = false
+ private var altPressedOnly = false
+
+ @JvmStatic
+ fun isEnabled(): Boolean = SystemInfoRt.isWindows && Registry.`is`("ide.windows.main.menu.focus.on.alt", false)
+
+ fun register(helper: JMenuBasedIdeMenuBarHelper) {
+ helpers.add(helper)
+ if (!registered) {
+ KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventPostProcessor(this)
+ Toolkit.getDefaultToolkit().addAWTEventListener(this, AWTEvent.MOUSE_EVENT_MASK)
+ registered = true
+ }
+ }
+
+ fun unregister(helper: JMenuBasedIdeMenuBarHelper) {
+ helpers.remove(helper)
+ if (helpers.isEmpty() && registered) {
+ KeyboardFocusManager.getCurrentKeyboardFocusManager().removeKeyEventPostProcessor(this)
+ Toolkit.getDefaultToolkit().removeAWTEventListener(this)
+ registered = false
+ }
+ }
+
+ override fun eventDispatched(event: AWTEvent) {
+ if (!isEnabled()) {
+ return
+ }
+
+ // Prevent focusing the menu when hold Alt + mouse click shortcuts are used
+ if (event is MouseEvent && event.id == MouseEvent.MOUSE_PRESSED && altPressed) {
+ altPressedOnly = false
+ }
+ }
+
+ override fun postProcessKeyEvent(e: KeyEvent): Boolean {
+ if (!isEnabled()) {
+ return false
+ }
+
+ if (e.keyCode != KeyEvent.VK_ALT) {
+ if (altPressed && e.id == KeyEvent.KEY_PRESSED) {
+ altPressedOnly = false
+ }
+ return false
+ }
+
+ val settings = UISettings.instanceOrNull
+ // To avoid conflicts with the double Alt shortcut to show hidden tool stripes
+ if (settings == null || settings.hideToolStripes || settings.presentationMode) {
+ return false
+ }
+
+ when (e.id) {
+ KeyEvent.KEY_PRESSED -> {
+ if (!altPressed) {
+ altPressed = true
+ altPressedOnly = true
+ }
+ }
+ KeyEvent.KEY_RELEASED -> {
+ if (altPressed && altPressedOnly
+ && e.modifiersEx and (InputEvent.SHIFT_DOWN_MASK or InputEvent.CTRL_DOWN_MASK or InputEvent.META_DOWN_MASK) == 0) {
+ for (helper in helpers) {
+ if (helper.shouldProcessAltRelease(e)) {
+ ApplicationManager.getApplication().invokeLater {
+ helper.activateMenu()
+ }
+ break
+ }
+ }
+ }
+
+ altPressed = false
+ altPressedOnly = false
+ }
+ }
+
+ return false
+ }
+}