Merge branch 'db/javac-ast'
[idea/community.git] / platform / platform-api / src / com / intellij / openapi / MnemonicWrapper.java
1 /*
2  * Copyright 2000-2015 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.openapi;
17
18 import com.intellij.Patches;
19 import com.intellij.ide.ui.UISettings;
20 import com.intellij.openapi.util.SystemInfo;
21 import com.intellij.openapi.util.registry.Registry;
22 import com.intellij.util.ui.UIUtil;
23
24 import javax.swing.*;
25 import java.awt.Component;
26 import java.awt.event.InputEvent;
27 import java.awt.event.KeyEvent;
28 import java.beans.PropertyChangeEvent;
29 import java.beans.PropertyChangeListener;
30 import java.lang.reflect.Method;
31
32 /**
33  * @author Sergey.Malenkov
34  */
35 abstract class MnemonicWrapper<T extends Component> implements Runnable, PropertyChangeListener {
36   public static MnemonicWrapper getWrapper(Component component) {
37     if (component == null || component.getClass().getName().equals("com.intellij.openapi.wm.impl.StripeButton")) {
38       return null;
39     }
40     for (PropertyChangeListener listener : component.getPropertyChangeListeners()) {
41       if (listener instanceof MnemonicWrapper) {
42         MnemonicWrapper wrapper = (MnemonicWrapper)listener;
43         wrapper.run(); // update mnemonics immediately
44         return wrapper;
45       }
46     }
47     if (component instanceof JMenuItem) {
48       return null; // TODO: new MenuWrapper((JMenuItem)component);
49     }
50     if (component instanceof AbstractButton) {
51       return new ButtonWrapper((AbstractButton)component);
52     }
53     if (component instanceof JLabel) {
54       return new LabelWrapper((JLabel)component);
55     }
56     return null;
57   }
58
59   final T myComponent; // direct access from inner classes
60   private final String myTextProperty;
61   private final String myCodeProperty;
62   private final String myIndexProperty;
63   private int myCode;
64   private int myIndex;
65   private boolean myFocusable;
66   private boolean myEvent;
67   private Runnable myRunnable;
68
69   private MnemonicWrapper(T component, String text, String code, String index) {
70     myComponent = component;
71     myTextProperty = text;
72     myCodeProperty = code;
73     myIndexProperty = index;
74     if (!updateText()) {
75       // assume that it is already set
76       myCode = getMnemonicCode();
77       myIndex = getMnemonicIndex();
78     }
79     myFocusable = isFocusable();
80     myComponent.addPropertyChangeListener(this);
81     run(); // update mnemonics immediately
82   }
83
84   @Override
85   public final void run() {
86     boolean disabled = isDisabled();
87     try {
88       myEvent = true;
89       setMnemonicCode(disabled ? KeyEvent.VK_UNDEFINED : myCode);
90       setMnemonicIndex(disabled ? -1 : myIndex);
91       Component component = getFocusableComponent();
92       if (component != null) {
93         component.setFocusable(disabled || myFocusable);
94       }
95     }
96     finally {
97       myEvent = false;
98       myRunnable = null;
99     }
100   }
101
102   @Override
103   public final void propertyChange(PropertyChangeEvent event) {
104     if (!myEvent) {
105       String property = event.getPropertyName();
106       if (myTextProperty.equals(property)) {
107         if (updateText()) {
108           updateRequest();
109         }
110       }
111       else if (myCodeProperty.equals(property)) {
112         myCode = getMnemonicCode();
113         updateRequest();
114       }
115       else if (myIndexProperty.equals(property)) {
116         myIndex = getMnemonicIndex();
117         updateRequest();
118       }
119       else if ("focusable".equals(property) || "labelFor".equals(property)) {
120         myFocusable = isFocusable();
121         updateRequest();
122       }
123     }
124   }
125
126   private boolean updateText() {
127     String text = getText();
128     if (text != null) {
129       int code = KeyEvent.VK_UNDEFINED;
130       int index = -1;
131       int length = text.length();
132       StringBuilder sb = new StringBuilder(length);
133       for (int i = 0; i < length; i++) {
134         char ch = text.charAt(i);
135         if (ch != UIUtil.MNEMONIC) {
136           sb.append(ch);
137         }
138         else if (i + 1 < length) {
139           code = getExtendedKeyCodeForChar(text.charAt(i + 1));
140           index = sb.length();
141         }
142       }
143       if (code != KeyEvent.VK_UNDEFINED) {
144         try {
145           myEvent = true;
146           setText(sb.toString());
147         }
148         finally {
149           myEvent = false;
150         }
151         myCode = code;
152         myIndex = index;
153         return true;
154       }
155     }
156     return false;
157   }
158
159   private void updateRequest() {
160     if (myRunnable == null) {
161       myRunnable = this; // run once
162       SwingUtilities.invokeLater(this);
163     }
164   }
165
166   private boolean isFocusable() {
167     Component component = getFocusableComponent();
168     return component == null || component.isFocusable();
169   }
170
171   Component getFocusableComponent() {
172     return myComponent;
173   }
174
175   boolean isDisabled() {
176     return UISettings.getShadowInstance().DISABLE_MNEMONICS_IN_CONTROLS;
177   }
178
179   abstract String getText();
180
181   abstract void setText(String text);
182
183   abstract int getMnemonicCode();
184
185   abstract void setMnemonicCode(int code);
186
187   abstract int getMnemonicIndex();
188
189   abstract void setMnemonicIndex(int index);
190
191   static KeyStroke fixMacKeyStroke(KeyStroke stroke, InputMap map, int code, boolean onKeyRelease, String action) {
192     if (stroke != null && code != stroke.getKeyCode()) {
193       map.remove(stroke);
194       stroke = null;
195     }
196     if (stroke == null && code != KeyEvent.VK_UNDEFINED) {
197       stroke = KeyStroke.getKeyStroke(code, InputEvent.ALT_MASK | InputEvent.ALT_DOWN_MASK, onKeyRelease);
198       map.put(stroke, action);
199     }
200     return stroke;
201   }
202
203   // TODO: HACK because of Java7 required:
204   // replace later with KeyEvent.getExtendedKeyCodeForChar(ch)
205   private static int getExtendedKeyCodeForChar(int ch) {
206     //noinspection ConstantConditions
207     assert Patches.USE_REFLECTION_TO_ACCESS_JDK7;
208     try {
209       Method method = KeyEvent.class.getMethod("getExtendedKeyCodeForChar", int.class);
210       if (!method.isAccessible()) {
211         method.setAccessible(true);
212       }
213       return (Integer)method.invoke(KeyEvent.class, ch);
214     }
215     catch (Exception exception) {
216       if (ch >= 'a' && ch <= 'z') {
217         ch -= ('a' - 'A');
218       }
219       return ch;
220     }
221   }
222
223   private static class MenuWrapper extends ButtonWrapper {
224     private MenuWrapper(AbstractButton component) {
225       super(component);
226     }
227
228     @Override
229     boolean isDisabled() {
230       return UISettings.getShadowInstance().DISABLE_MNEMONICS;
231     }
232   }
233
234   private static class ButtonWrapper extends MnemonicWrapper<AbstractButton> {
235     private KeyStroke myStrokePressed;
236     private KeyStroke myStrokeReleased;
237
238     private ButtonWrapper(AbstractButton component) {
239       super(component, "text", "mnemonic", "displayedMnemonicIndex");
240     }
241
242     @Override
243     String getText() {
244       return myComponent.getText();
245     }
246
247     @Override
248     void setText(String text) {
249       myComponent.setText(text);
250     }
251
252     @Override
253     int getMnemonicCode() {
254       return myComponent.getMnemonic();
255     }
256
257     @Override
258     void setMnemonicCode(int code) {
259       myComponent.setMnemonic(code);
260       if (SystemInfo.isMac && Registry.is("ide.mac.alt.mnemonic.without.ctrl")) {
261         InputMap map = myComponent.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
262         if (map != null) {
263           myStrokePressed = fixMacKeyStroke(myStrokePressed, map, code, false, "pressed");
264           myStrokeReleased = fixMacKeyStroke(myStrokeReleased, map, code, true, "released");
265         }
266       }
267     }
268
269     @Override
270     int getMnemonicIndex() {
271       return myComponent.getDisplayedMnemonicIndex();
272     }
273
274     @Override
275     void setMnemonicIndex(int index) {
276       myComponent.setDisplayedMnemonicIndex(index);
277     }
278   }
279
280   private static class LabelWrapper extends MnemonicWrapper<JLabel> {
281     private KeyStroke myStrokeRelease;
282
283     private LabelWrapper(JLabel component) {
284       super(component, "text", "displayedMnemonic", "displayedMnemonicIndex");
285     }
286
287     @Override
288     String getText() {
289       return myComponent.getText();
290     }
291
292     @Override
293     void setText(String text) {
294       myComponent.setText(text);
295     }
296
297     @Override
298     int getMnemonicCode() {
299       return myComponent.getDisplayedMnemonic();
300     }
301
302     @Override
303     void setMnemonicCode(int code) {
304       myComponent.setDisplayedMnemonic(code);
305       if (SystemInfo.isMac && Registry.is("ide.mac.alt.mnemonic.without.ctrl")) {
306         InputMap map = myComponent.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
307         if (map != null) {
308           myStrokeRelease = fixMacKeyStroke(myStrokeRelease, map, code, true, "release");
309         }
310       }
311     }
312
313     @Override
314     int getMnemonicIndex() {
315       return myComponent.getDisplayedMnemonicIndex();
316     }
317
318     @Override
319     void setMnemonicIndex(int index) {
320       myComponent.setDisplayedMnemonicIndex(index);
321     }
322
323     @Override
324     Component getFocusableComponent() {
325       return myComponent.getLabelFor();
326     }
327   }
328 }