View Javadoc
1   /*******************************************************************************
2    * Copyhacked (H) 2012-2025.
3    * This program and the accompanying materials
4    * are made available under no term at all, use it like
5    * you want, but share and discuss it
6    * every time possible with every body.
7    * 
8    * Contributors:
9    *      ron190 at ymail dot com - initial implementation
10   ******************************************************************************/
11  package com.jsql.view.swing.action;
12  
13  import com.jsql.view.swing.menubar.AppMenubar;
14  import com.jsql.view.swing.util.MediatorHelper;
15  
16  import javax.swing.*;
17  import java.awt.*;
18  import java.awt.event.ActionEvent;
19  import java.util.HashSet;
20  import java.util.Set;
21  
22  /**
23   * Keyword shortcut definition. <br>
24   * - ctrl TAB: switch to next tab, <br>
25   * - ctrl shift TAB: switch to previous tab, <br>
26   * - ctrl W: delete tab
27   */
28  public final class HotkeyUtil {
29      
30      private static final String STR_CTRL_TAB = "ctrl TAB";
31      private static final String STR_CTRL_SHIFT_TAB = "ctrl shift TAB";
32      private static final String STR_SELECT_TAB = "actionString-selectTab";
33      
34      private HotkeyUtil() {
35          // Utility class
36      }
37      
38      /**
39       * Select all textfield content when focused.
40       */
41      public static void addTextFieldShortcutSelectAll() {
42          KeyboardFocusManager.getCurrentKeyboardFocusManager().addPropertyChangeListener(
43              "permanentFocusOwner",
44              propertyChangeEvent -> {
45                  if (propertyChangeEvent.getNewValue() instanceof JTextField) {
46                      SwingUtilities.invokeLater(() -> {
47                          JTextField textField = (JTextField) propertyChangeEvent.getNewValue();
48                          textField.selectAll();
49                      });
50                  }
51              }
52          );
53      }
54      
55      /**
56       * Add action to a single tabbedpane (ctrl-tab, ctrl-shift-tab).
57       */
58      public static void addShortcut(JTabbedPane tabbedPane) {
59          var ctrlTab = KeyStroke.getKeyStroke(HotkeyUtil.STR_CTRL_TAB);
60          var ctrlShiftTab = KeyStroke.getKeyStroke(HotkeyUtil.STR_CTRL_SHIFT_TAB);
61  
62          // Remove ctrl-tab from default focus traversal
63          Set<AWTKeyStroke> forwardKeys = new HashSet<>(tabbedPane.getFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS));
64          forwardKeys.remove(ctrlTab);
65          tabbedPane.setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, forwardKeys);
66  
67          // Remove ctrl-shift-tab from default focus traversal
68          Set<AWTKeyStroke> backwardKeys = new HashSet<>(tabbedPane.getFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS));
69          backwardKeys.remove(ctrlShiftTab);
70          tabbedPane.setFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, backwardKeys);
71  
72          // Add keys to the tab's input map
73          var inputMap = tabbedPane.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
74          inputMap.put(ctrlTab, "navigateNext");
75          inputMap.put(ctrlShiftTab, "navigatePrevious");
76      }
77      
78      /**
79       * Add action to global root (ctrl-tab, ctrl-shift-tab, ctrl-W).
80       */
81      public static void addShortcut(JRootPane rootPane, final JTabbedPane valuesTabbedPane) {
82          Action closeTab = new ActionCloseTabResult();
83          Action nextTab = new AbstractAction() {
84              @Override
85              public void actionPerformed(ActionEvent e) {
86                  if (valuesTabbedPane.getTabCount() > 0) {
87                      int selectedIndex = valuesTabbedPane.getSelectedIndex();
88                      if (selectedIndex + 1 < valuesTabbedPane.getTabCount()) {
89                          valuesTabbedPane.setSelectedIndex(selectedIndex + 1);
90                      } else {
91                          valuesTabbedPane.setSelectedIndex(0);
92                      }
93                  }
94              }
95          };
96          Action previousTab = new AbstractAction() {
97              @Override
98              public void actionPerformed(ActionEvent e) {
99                  if (valuesTabbedPane.getTabCount() > 0) {
100                     int selectedIndex = valuesTabbedPane.getSelectedIndex();
101                     if (selectedIndex - 1 > -1) {
102                         valuesTabbedPane.setSelectedIndex(selectedIndex - 1);
103                     } else {
104                         valuesTabbedPane.setSelectedIndex(valuesTabbedPane.getTabCount() - 1);
105                     }
106                 }
107             }
108         };
109         
110         Set<AWTKeyStroke> forwardKeys = new HashSet<>(rootPane.getFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS));
111         forwardKeys.remove(KeyStroke.getKeyStroke(HotkeyUtil.STR_CTRL_TAB));
112         rootPane.setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, forwardKeys);
113         
114         Set<AWTKeyStroke> backwardKeys = new HashSet<>(rootPane.getFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS));
115         backwardKeys.remove(KeyStroke.getKeyStroke(HotkeyUtil.STR_CTRL_SHIFT_TAB));
116         rootPane.setFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, backwardKeys);
117         
118         var inputMap = rootPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
119         var actionMap = rootPane.getActionMap();
120 
121         inputMap.put(KeyStroke.getKeyStroke("ctrl W"), "actionString-closeTab");
122         actionMap.put("actionString-closeTab", closeTab);
123         
124         inputMap.put(KeyStroke.getKeyStroke(HotkeyUtil.STR_CTRL_TAB), "actionString-nextTab");
125         actionMap.put("actionString-nextTab", nextTab);
126 
127         inputMap.put(KeyStroke.getKeyStroke(HotkeyUtil.STR_CTRL_SHIFT_TAB), "actionString-previousTab");
128         actionMap.put("actionString-previousTab", previousTab);
129 
130         int tabCount = MediatorHelper.tabManagersCards().getComponentCount();
131         
132         for (var currentTab = 1 ; currentTab <= tabCount ; currentTab++) {
133             inputMap.put(KeyStroke.getKeyStroke("ctrl "+ currentTab), HotkeyUtil.STR_SELECT_TAB + currentTab);
134             inputMap.put(KeyStroke.getKeyStroke("ctrl NUMPAD"+ currentTab), HotkeyUtil.STR_SELECT_TAB + currentTab);
135             
136             final int currentTabFinal = currentTab;
137             actionMap.put(HotkeyUtil.STR_SELECT_TAB + currentTab, new AbstractAction() {
138                 @Override
139                 public void actionPerformed(ActionEvent e) {
140                     MediatorHelper.frame().getTabManagers().setSelectedIndex(currentTabFinal - 1);
141                 }
142             });
143         }
144         
145         inputMap.put(KeyStroke.getKeyStroke("ctrl S"), "actionString-saveTab");
146         actionMap.put("actionString-saveTab", new ActionSaveTab());
147     }
148 
149     /**
150      * Create Alt shortcut to display menubar ; remove menubar when focus is set to a component.
151      * @param appMenubar The menubar to display
152      */
153     public static void addShortcut(final AppMenubar appMenubar) {
154         KeyboardFocusManager.getCurrentKeyboardFocusManager().addPropertyChangeListener(  // Hide Menubar when focusing any component
155             "permanentFocusOwner",
156             propertyChangeEvent -> SwingUtilities.invokeLater(() -> {
157                 if (
158                     // Fix #40924: NullPointerException on MediatorGui.panelAddressBar()
159                     MediatorHelper.panelAddressBar() != null
160                     && MediatorHelper.panelAddressBar().isAdvanceActivated()
161                 ) {
162                     appMenubar.setVisible(false);
163                 }
164             })
165         );
166         KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(  // Show/Hide the Menubar with Alt key (not Alt Graph)
167             new AltKeyEventDispatcher()
168         );
169     }
170 }