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 + } +}