/* * This file is part of WebLookAndFeel library. * * WebLookAndFeel library is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * WebLookAndFeel library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with WebLookAndFeel library. If not, see <http://www.gnu.org/licenses/>. */ package com.alee.managers.hotkey; import com.alee.api.annotations.NotNull; import com.alee.api.jdk.BiConsumer; import com.alee.api.jdk.BiPredicate; import com.alee.api.jdk.Function; import com.alee.managers.tooltip.TooltipManager; import com.alee.managers.tooltip.TooltipWay; import com.alee.utils.*; import com.alee.utils.compare.Filter; import com.alee.utils.swing.WeakComponentDataList; import javax.swing.*; import java.awt.*; import java.awt.event.AWTEventListener; import java.awt.event.KeyEvent; import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.List; /** * This manager allows you to quickly register global hotkeys (like accelerators on menu items in menubar menus) for any Swing component. * Additionally you can specify a component which will limit hotkey events to its area (meaning that hotkey event will occur only if this * component or any of its children is focused when hotkey pressed). * * TooltipManager is integrated with this manager to automatically show component hotkeys in its tooltip if needed/allowed by tooltip and * hotkey settings. * * All hotkeys are stored into WeakHashMap so hotkeys will be removed as soon as the component for which hotkey is registered gets * finalized. HotkeyInfo also keeps a weak reference to both top and hotkey components. * * @author Mikle Garin * @see <a href="https://github.com/mgarin/weblaf/wiki/How-to-use-HotkeyManager">How to use HotkeyManager</a> * @deprecated Requires a rework to make use of the common Swing action map but in a more convenient fashion */ @Deprecated public final class HotkeyManager { /** * Separator used between multiple hotkeys displayed in a single line. */ private static final String HOTKEYS_SEPARATOR = ", "; /** * HotkeyInfo text provider. */ private static final Function<HotkeyInfo, String> HOTKEY_TEXT_PROVIDER = new Function<HotkeyInfo, String> () { @Override public String apply ( final HotkeyInfo hotkeyInfo ) { return hotkeyInfo.getHotkeyData ().toString (); } }; /** * Displayed hotkeys filter. */ private static final Filter<HotkeyInfo> HOTKEY_DISPLAY_FILTER = new Filter<HotkeyInfo> () { @Override public boolean accept ( final HotkeyInfo object ) { return !object.isHidden (); } }; /** * Global hotkeys block flag. */ private static boolean hotkeysEnabled = true; /** * Pass focus to fired hotkey component. */ private static boolean transferFocus = false; /** * Added hotkeys. */ private static final WeakComponentDataList<JComponent, HotkeyInfo> hotkeys = new WeakComponentDataList<JComponent, HotkeyInfo> ( "HotkeyManager.HotkeyInfo", 20 ); /** * Global hotkeys list. */ private static final List<HotkeyInfo> globalHotkeys = new ArrayList<HotkeyInfo> ( 2 ); /** * Conditions for top components which might. * todo Get rid of this and make hotkeys use Swing mapping instead of global listening */ @Deprecated private static final WeakComponentDataList<JComponent, HotkeyCondition> containerConditions = new WeakComponentDataList<JComponent, HotkeyCondition> ( "HotkeyManager.HotkeyCondition", 5 ); /** * Initialization mark. */ private static boolean initialized = false; /** * Initializes hotkey manager. */ public static synchronized void initialize () { if ( !initialized ) { initialized = true; // XStream aliases XmlUtils.processAnnotations ( HotkeyData.class ); // AWT hotkeys listener Toolkit.getDefaultToolkit ().addAWTEventListener ( new AWTEventListener () { @Override public void eventDispatched ( @NotNull final AWTEvent event ) { if ( hotkeysEnabled && event instanceof KeyEvent ) { final KeyEvent e = ( KeyEvent ) event; if ( !e.isConsumed () && e.getID () == KeyEvent.KEY_PRESSED ) { if ( hotkeyForEventExists ( e ) ) { processHotkeys ( e ); } } } } }, AWTEvent.KEY_EVENT_MASK ); } } /** * Returns whether at least one hotkey for the specified key event exists or not. * todo Might need a rework since events like Ctrl+Alt+A won't trigger Ctrl+A hotkey * * @param keyEvent key event to search hotkeys for * @return true if at least one hotkey for the specified key event exists, false otherwise */ private static boolean hotkeyForEventExists ( @NotNull final KeyEvent keyEvent ) { boolean exists = false; for ( final HotkeyInfo hotkeyInfo : globalHotkeys ) { if ( hotkeyInfo.getHotkeyData ().isTriggered ( keyEvent ) ) { exists = true; break; } } if ( !exists ) { exists = hotkeys.anyDataMatch ( new BiPredicate<JComponent, HotkeyInfo> () { @Override public boolean test ( @NotNull final JComponent component, @NotNull final HotkeyInfo hotkeyInfo ) { return hotkeyInfo.getHotkeyData ().isTriggered ( keyEvent ); } } ); } return exists; } /** * Processes all available registered hotkeys. * * @param e key event */ private static void processHotkeys ( @NotNull final KeyEvent e ) { for ( final HotkeyInfo hotkeyInfo : globalHotkeys ) { processHotkey ( e, hotkeyInfo ); } hotkeys.forEachData ( new BiConsumer<JComponent, HotkeyInfo> () { @Override public void accept ( @NotNull final JComponent component, @NotNull final HotkeyInfo hotkeyInfo ) { processHotkey ( e, hotkeyInfo ); } } ); } /** * Processes single hotkey. * * @param e key event * @param hotkeyInfo hotkey information */ private static void processHotkey ( @NotNull final KeyEvent e, @NotNull final HotkeyInfo hotkeyInfo ) { // Specified components final Component forComponent = hotkeyInfo.getForComponent (); // If there is no pointed components - hotkey will be global if ( forComponent == null ) { // Checking hotkey if ( hotkeyInfo.getHotkeyData ().isTriggered ( e ) && hotkeyInfo.getAction () != null ) { // Performing hotkey action invokeLater ( hotkeyInfo.getAction (), e ); } } else { // Finding top component Component topComponent = hotkeyInfo.getTopComponent (); topComponent = topComponent != null ? topComponent : CoreSwingUtils.getWindowAncestor ( forComponent ); // Checking if componen or one of its children has focus if ( SwingUtils.hasFocusOwner ( topComponent ) ) { // Checking hotkey if ( hotkeyInfo.getHotkeyData ().isTriggered ( e ) && hotkeyInfo.getAction () != null ) { // Checking that hotkey meets parent containers conditions if ( meetsParentConditions ( forComponent ) ) { // Transferring focus to hotkey component if ( transferFocus ) { forComponent.requestFocusInWindow (); } // Performing hotkey action invokeLater ( hotkeyInfo.getAction (), e ); } } } } } /** * Will invoke {@link HotkeyRunnable} later in EDT in case it is called from non-EDT thread. * todo This shouldn't be needed anymore after {@link HotkeyManager} rework * * @param runnable hotkey runnable * @param e key event */ private static void invokeLater ( final HotkeyRunnable runnable, final KeyEvent e ) { if ( SwingUtilities.isEventDispatchThread () ) { runnable.run ( e ); } else { CoreSwingUtils.invokeLater ( new Runnable () { @Override public void run () { runnable.run ( e ); } } ); } } private static boolean meetsParentConditions ( final Component forComponent ) { return containerConditions.allDataMatch ( new BiPredicate<JComponent, HotkeyCondition> () { @Override public boolean test ( final JComponent component, final HotkeyCondition hotkeyCondition ) { return !component.isAncestorOf ( forComponent ) || hotkeyCondition.checkCondition ( forComponent ); } } ); } /** * Hotkey register methods */ public static HotkeyInfo registerHotkey ( final HotkeyData hotkeyData, final HotkeyRunnable action ) { final HotkeyInfo hotkeyInfo = new HotkeyInfo (); hotkeyInfo.setHidden ( true ); hotkeyInfo.setHotkeyData ( hotkeyData ); hotkeyInfo.setAction ( action ); cacheHotkey ( hotkeyInfo ); return hotkeyInfo; } public static HotkeyInfo registerHotkey ( final JComponent forComponent, final HotkeyData hotkeyData, final HotkeyRunnable action ) { return registerHotkey ( null, forComponent, hotkeyData, action ); } public static HotkeyInfo registerHotkey ( final JComponent forComponent, final HotkeyData hotkeyData, final HotkeyRunnable action, final boolean hidden ) { return registerHotkey ( null, forComponent, hotkeyData, action, hidden ); } public static HotkeyInfo registerHotkey ( final Component topComponent, final JComponent forComponent, final HotkeyData hotkeyData, final HotkeyRunnable action ) { return registerHotkey ( topComponent, forComponent, hotkeyData, action, false ); } public static HotkeyInfo registerHotkey ( final Component topComponent, final JComponent forComponent, final HotkeyData hotkeyData, final HotkeyRunnable action, final TooltipWay tooltipWay ) { return registerHotkey ( topComponent, forComponent, hotkeyData, action, false, tooltipWay ); } public static HotkeyInfo registerHotkey ( final Component topComponent, final JComponent forComponent, final HotkeyData hotkeyData, final HotkeyRunnable action, final boolean hidden ) { return registerHotkey ( topComponent, forComponent, hotkeyData, action, hidden, null ); } public static HotkeyInfo registerHotkey ( final Component topComponent, final JComponent forComponent, final HotkeyData hotkeyData, final HotkeyRunnable action, final boolean hidden, final TooltipWay tooltipWay ) { final HotkeyInfo hotkeyInfo = new HotkeyInfo (); hotkeyInfo.setHidden ( hidden ); hotkeyInfo.setTopComponent ( topComponent ); hotkeyInfo.setForComponent ( forComponent ); hotkeyInfo.setHotkeyData ( hotkeyData ); hotkeyInfo.setHotkeyDisplayWay ( tooltipWay ); hotkeyInfo.setAction ( action ); cacheHotkey ( hotkeyInfo ); return hotkeyInfo; } /** * Button-specific hotkey register methods */ public static HotkeyInfo registerHotkey ( final AbstractButton forComponent, final HotkeyData hotkeyData ) { return registerHotkey ( null, forComponent, hotkeyData ); } public static HotkeyInfo registerHotkey ( final AbstractButton forComponent, final HotkeyData hotkeyData, final boolean hidden ) { return registerHotkey ( null, forComponent, hotkeyData, hidden ); } public static HotkeyInfo registerHotkey ( final AbstractButton forComponent, final HotkeyData hotkeyData, final TooltipWay tooltipWay ) { return registerHotkey ( null, forComponent, hotkeyData, tooltipWay ); } public static HotkeyInfo registerHotkey ( final Component topComponent, final AbstractButton forComponent, final HotkeyData hotkeyData ) { return registerHotkey ( topComponent, forComponent, hotkeyData, false ); } public static HotkeyInfo registerHotkey ( final Component topComponent, final AbstractButton forComponent, final HotkeyData hotkeyData, final TooltipWay tooltipWay ) { return registerHotkey ( topComponent, forComponent, hotkeyData, createAction ( forComponent ), false, tooltipWay ); } public static HotkeyInfo registerHotkey ( final Component topComponent, final AbstractButton forComponent, final HotkeyData hotkeyData, final boolean hidden ) { return registerHotkey ( topComponent, forComponent, hotkeyData, createAction ( forComponent ), hidden, null ); } private static HotkeyRunnable createAction ( final AbstractButton forComponent ) { return new ButtonHotkeyRunnable ( forComponent ); } /** * Sets top component additional hotkey trigger condition */ public static void addContainerHotkeyCondition ( final JComponent container, final HotkeyCondition hotkeyCondition ) { containerConditions.add ( container, hotkeyCondition ); } public static void removeContainerHotkeyCondition ( final JComponent container, final HotkeyCondition hotkeyCondition ) { containerConditions.remove ( container, hotkeyCondition ); } public static void removeContainerHotkeyConditions ( final JComponent container, final List<HotkeyCondition> hotkeyConditions ) { for ( final HotkeyCondition hotkeyCondition : hotkeyConditions ) { containerConditions.remove ( container, hotkeyCondition ); } } public static void removeContainerHotkeyConditions ( final JComponent container ) { containerConditions.clear ( container ); } public static List<HotkeyCondition> getContainerHotkeyConditions ( final JComponent container ) { final List<HotkeyCondition> list = containerConditions.get ( container ); return list != null ? CollectionUtils.copy ( list ) : new ArrayList<HotkeyCondition> (); } /** * Hotkey removal methods */ public static void unregisterHotkey ( final HotkeyInfo hotkeyInfo ) { clearHotkeyCache ( hotkeyInfo ); } public static void unregisterHotkeys ( final JComponent component ) { clearHotkeysCache ( component ); } /** * Hotkeys cache methods */ private static void cacheHotkey ( final HotkeyInfo hotkeyInfo ) { final JComponent forComponent = hotkeyInfo.getForComponent (); if ( forComponent != null ) { // Component hotkey hotkeys.add ( forComponent, hotkeyInfo ); } else { // Caching global hotkey if ( !globalHotkeys.contains ( hotkeyInfo ) ) { globalHotkeys.add ( hotkeyInfo ); } } } private static void clearHotkeyCache ( final HotkeyInfo hotkeyInfo ) { if ( hotkeyInfo != null ) { final JComponent forComponent = hotkeyInfo.getForComponent (); if ( forComponent != null ) { // Clearing component hotkey cache hotkeys.remove ( forComponent, hotkeyInfo ); } else { // Clearing global hotkey cache globalHotkeys.remove ( hotkeyInfo ); } } } private static void clearHotkeysCache ( final JComponent component ) { hotkeys.clear ( component ); } public static List<HotkeyInfo> getComponentHotkeys ( final JComponent component ) { final List<HotkeyInfo> list = hotkeys.get ( component ); return list != null ? CollectionUtils.copy ( list ) : new ArrayList<HotkeyInfo> (); } /** * Shows all visible components hotkeys */ public static void showComponentHotkeys () { // Hiding all tooltips TooltipManager.hideAllTooltips (); // Displaying one-time tips with hotkeys for ( final Window window : Window.getWindows () ) { showComponentHotkeys ( window ); } } public static void showComponentHotkeys ( final Component component ) { // Hiding all tooltips TooltipManager.hideAllTooltips (); // Displaying one-time tips with hotkeys showComponentHotkeys ( CoreSwingUtils.getNonNullWindowAncestor ( component ) ); } private static void showComponentHotkeys ( final Window window ) { final LinkedHashSet<Component> shown = new LinkedHashSet<Component> (); hotkeys.forEachData ( new BiConsumer<JComponent, HotkeyInfo> () { @Override public void accept ( final JComponent component, final HotkeyInfo hotkeyInfo ) { if ( !hotkeyInfo.isHidden () ) { final JComponent forComponent = hotkeyInfo.getForComponent (); if ( forComponent != null && !shown.contains ( forComponent ) && forComponent.isVisible () && forComponent.isShowing () && CoreSwingUtils.getWindowAncestor ( forComponent ) == window ) { final String hotkey = HotkeyManager.getComponentHotkeysString ( forComponent ); final TooltipWay displayWay = hotkeyInfo.getHotkeyDisplayWay (); TooltipManager.showOneTimeTooltip ( forComponent, null, hotkey, displayWay ); shown.add ( forComponent ); } } } } ); } /** * Installs "show all hotkeys" action on window or component */ public static void installShowAllHotkeysAction ( final JComponent topComponent ) { installShowAllHotkeysAction ( topComponent, Hotkey.F1 ); } public static void installShowAllHotkeysAction ( final JComponent topComponent, final HotkeyData hotkeyData ) { HotkeyManager.registerHotkey ( topComponent, topComponent, hotkeyData, new HotkeyRunnable () { @Override public void run ( @NotNull final KeyEvent e ) { HotkeyManager.showComponentHotkeys ( topComponent ); } }, true ); } /** * All component hotkeys list */ public static String getComponentHotkeysString ( final JComponent component ) { final List<HotkeyInfo> keys = hotkeys.get ( component ); return TextUtils.listToString ( keys, HOTKEYS_SEPARATOR, HOTKEY_TEXT_PROVIDER, HOTKEY_DISPLAY_FILTER ); } /** * Global hotkey block */ public static void disableHotkeys () { hotkeysEnabled = false; } public static void enableHotkeys () { hotkeysEnabled = true; } /** * Should transfer focus to fired hotkey component or not */ public static boolean isTransferFocus () { return transferFocus; } public static void setTransferFocus ( final boolean transferFocus ) { HotkeyManager.transferFocus = transferFocus; } }