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 textField) {
46                      SwingUtilities.invokeLater(textField::selectAll);
47                  }
48              }
49          );
50      }
51      
52      /**
53       * Add action to a single tabbedpane (ctrl-tab, ctrl-shift-tab).
54       */
55      public static void addShortcut(JTabbedPane tabbedPane) {
56          var ctrlTab = KeyStroke.getKeyStroke(HotkeyUtil.STR_CTRL_TAB);
57          var ctrlShiftTab = KeyStroke.getKeyStroke(HotkeyUtil.STR_CTRL_SHIFT_TAB);
58  
59          // Remove ctrl-tab from default focus traversal
60          Set<AWTKeyStroke> forwardKeys = new HashSet<>(tabbedPane.getFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS));
61          forwardKeys.remove(ctrlTab);
62          tabbedPane.setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, forwardKeys);
63  
64          // Remove ctrl-shift-tab from default focus traversal
65          Set<AWTKeyStroke> backwardKeys = new HashSet<>(tabbedPane.getFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS));
66          backwardKeys.remove(ctrlShiftTab);
67          tabbedPane.setFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, backwardKeys);
68  
69          // Add keys to the tab's input map
70          var inputMap = tabbedPane.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
71          inputMap.put(ctrlTab, "navigateNext");
72          inputMap.put(ctrlShiftTab, "navigatePrevious");
73      }
74      
75      /**
76       * Add action to global root (ctrl-tab, ctrl-shift-tab, ctrl-W).
77       */
78      public static void addShortcut(JRootPane rootPane, final JTabbedPane valuesTabbedPane) {
79          Action closeTab = new ActionCloseTabResult();
80          Action nextTab = new AbstractAction() {
81              @Override
82              public void actionPerformed(ActionEvent e) {
83                  if (valuesTabbedPane.getTabCount() > 0) {
84                      int selectedIndex = valuesTabbedPane.getSelectedIndex();
85                      if (selectedIndex + 1 < valuesTabbedPane.getTabCount()) {
86                          valuesTabbedPane.setSelectedIndex(selectedIndex + 1);
87                      } else {
88                          valuesTabbedPane.setSelectedIndex(0);
89                      }
90                  }
91              }
92          };
93          Action previousTab = new AbstractAction() {
94              @Override
95              public void actionPerformed(ActionEvent e) {
96                  if (valuesTabbedPane.getTabCount() > 0) {
97                      int selectedIndex = valuesTabbedPane.getSelectedIndex();
98                      if (selectedIndex - 1 > -1) {
99                          valuesTabbedPane.setSelectedIndex(selectedIndex - 1);
100                     } else {
101                         valuesTabbedPane.setSelectedIndex(valuesTabbedPane.getTabCount() - 1);
102                     }
103                 }
104             }
105         };
106         
107         Set<AWTKeyStroke> forwardKeys = new HashSet<>(rootPane.getFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS));
108         forwardKeys.remove(KeyStroke.getKeyStroke(HotkeyUtil.STR_CTRL_TAB));
109         rootPane.setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, forwardKeys);
110         
111         Set<AWTKeyStroke> backwardKeys = new HashSet<>(rootPane.getFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS));
112         backwardKeys.remove(KeyStroke.getKeyStroke(HotkeyUtil.STR_CTRL_SHIFT_TAB));
113         rootPane.setFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, backwardKeys);
114         
115         var inputMap = rootPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
116         var actionMap = rootPane.getActionMap();
117 
118         inputMap.put(KeyStroke.getKeyStroke("ctrl W"), "actionString-closeTab");
119         actionMap.put("actionString-closeTab", closeTab);
120         
121         inputMap.put(KeyStroke.getKeyStroke(HotkeyUtil.STR_CTRL_TAB), "actionString-nextTab");
122         actionMap.put("actionString-nextTab", nextTab);
123 
124         inputMap.put(KeyStroke.getKeyStroke(HotkeyUtil.STR_CTRL_SHIFT_TAB), "actionString-previousTab");
125         actionMap.put("actionString-previousTab", previousTab);
126 
127         int tabCount = MediatorHelper.tabManagersCards().getComponentCount();
128         
129         for (var currentTab = 1 ; currentTab <= tabCount ; currentTab++) {
130             inputMap.put(KeyStroke.getKeyStroke("ctrl "+ currentTab), HotkeyUtil.STR_SELECT_TAB + currentTab);
131             inputMap.put(KeyStroke.getKeyStroke("ctrl NUMPAD"+ currentTab), HotkeyUtil.STR_SELECT_TAB + currentTab);
132             
133             final int currentTabFinal = currentTab;
134             actionMap.put(HotkeyUtil.STR_SELECT_TAB + currentTab, new AbstractAction() {
135                 @Override
136                 public void actionPerformed(ActionEvent e) {
137                     MediatorHelper.frame().getTabManagers().setSelectedIndex(currentTabFinal - 1);
138                 }
139             });
140         }
141         
142         inputMap.put(KeyStroke.getKeyStroke("ctrl S"), "actionString-saveTab");
143         actionMap.put("actionString-saveTab", new ActionSaveTab());
144     }
145 
146     /**
147      * Create Alt shortcut to display menubar ; remove menubar when focus is set to a component.
148      * @param appMenubar The menubar to display
149      */
150     public static void addShortcut(final AppMenubar appMenubar) {
151         KeyboardFocusManager.getCurrentKeyboardFocusManager().addPropertyChangeListener(  // Hide Menubar when focusing any component
152             "permanentFocusOwner",
153             propertyChangeEvent -> SwingUtilities.invokeLater(() -> {
154                 if (
155                     // Fix #40924: NullPointerException on MediatorGui.panelAddressBar()
156                     MediatorHelper.panelAddressBar() != null
157                     && MediatorHelper.panelAddressBar().isAdvanceActivated()
158                 ) {
159                     appMenubar.setVisible(false);
160                 }
161             })
162         );
163         KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(  // Show/Hide the Menubar with Alt key (not Alt Graph)
164             new AltKeyEventDispatcher()
165         );
166     }
167 }