2 * Copyright 2000-2015 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package com.intellij.openapi;
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;
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;
33 * @author Sergey.Malenkov
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")) {
40 for (PropertyChangeListener listener : component.getPropertyChangeListeners()) {
41 if (listener instanceof MnemonicWrapper) {
42 MnemonicWrapper wrapper = (MnemonicWrapper)listener;
43 wrapper.run(); // update mnemonics immediately
47 if (component instanceof JMenuItem) {
48 return null; // TODO: new MenuWrapper((JMenuItem)component);
50 if (component instanceof AbstractButton) {
51 return new ButtonWrapper((AbstractButton)component);
53 if (component instanceof JLabel) {
54 return new LabelWrapper((JLabel)component);
59 final T myComponent; // direct access from inner classes
60 private final String myTextProperty;
61 private final String myCodeProperty;
62 private final String myIndexProperty;
65 private boolean myFocusable;
66 private boolean myEvent;
67 private Runnable myRunnable;
69 private MnemonicWrapper(T component, String text, String code, String index) {
70 myComponent = component;
71 myTextProperty = text;
72 myCodeProperty = code;
73 myIndexProperty = index;
75 // assume that it is already set
76 myCode = getMnemonicCode();
77 myIndex = getMnemonicIndex();
79 myFocusable = isFocusable();
80 myComponent.addPropertyChangeListener(this);
81 run(); // update mnemonics immediately
85 public final void run() {
86 boolean disabled = isDisabled();
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);
103 public final void propertyChange(PropertyChangeEvent event) {
105 String property = event.getPropertyName();
106 if (myTextProperty.equals(property)) {
111 else if (myCodeProperty.equals(property)) {
112 myCode = getMnemonicCode();
115 else if (myIndexProperty.equals(property)) {
116 myIndex = getMnemonicIndex();
119 else if ("focusable".equals(property) || "labelFor".equals(property)) {
120 myFocusable = isFocusable();
126 private boolean updateText() {
127 String text = getText();
129 int code = KeyEvent.VK_UNDEFINED;
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) {
138 else if (i + 1 < length) {
139 code = getExtendedKeyCodeForChar(text.charAt(i + 1));
143 if (code != KeyEvent.VK_UNDEFINED) {
146 setText(sb.toString());
159 private void updateRequest() {
160 if (myRunnable == null) {
161 myRunnable = this; // run once
162 SwingUtilities.invokeLater(this);
166 private boolean isFocusable() {
167 Component component = getFocusableComponent();
168 return component == null || component.isFocusable();
171 Component getFocusableComponent() {
175 boolean isDisabled() {
176 return UISettings.getShadowInstance().DISABLE_MNEMONICS_IN_CONTROLS;
179 abstract String getText();
181 abstract void setText(String text);
183 abstract int getMnemonicCode();
185 abstract void setMnemonicCode(int code);
187 abstract int getMnemonicIndex();
189 abstract void setMnemonicIndex(int index);
191 static KeyStroke fixMacKeyStroke(KeyStroke stroke, InputMap map, int code, boolean onKeyRelease, String action) {
192 if (stroke != null && code != stroke.getKeyCode()) {
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);
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;
209 Method method = KeyEvent.class.getMethod("getExtendedKeyCodeForChar", int.class);
210 if (!method.isAccessible()) {
211 method.setAccessible(true);
213 return (Integer)method.invoke(KeyEvent.class, ch);
215 catch (Exception exception) {
216 if (ch >= 'a' && ch <= 'z') {
223 private static class MenuWrapper extends ButtonWrapper {
224 private MenuWrapper(AbstractButton component) {
229 boolean isDisabled() {
230 return UISettings.getShadowInstance().DISABLE_MNEMONICS;
234 private static class ButtonWrapper extends MnemonicWrapper<AbstractButton> {
235 private KeyStroke myStrokePressed;
236 private KeyStroke myStrokeReleased;
238 private ButtonWrapper(AbstractButton component) {
239 super(component, "text", "mnemonic", "displayedMnemonicIndex");
244 return myComponent.getText();
248 void setText(String text) {
249 myComponent.setText(text);
253 int getMnemonicCode() {
254 return myComponent.getMnemonic();
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);
263 myStrokePressed = fixMacKeyStroke(myStrokePressed, map, code, false, "pressed");
264 myStrokeReleased = fixMacKeyStroke(myStrokeReleased, map, code, true, "released");
270 int getMnemonicIndex() {
271 return myComponent.getDisplayedMnemonicIndex();
275 void setMnemonicIndex(int index) {
276 myComponent.setDisplayedMnemonicIndex(index);
280 private static class LabelWrapper extends MnemonicWrapper<JLabel> {
281 private KeyStroke myStrokeRelease;
283 private LabelWrapper(JLabel component) {
284 super(component, "text", "displayedMnemonic", "displayedMnemonicIndex");
289 return myComponent.getText();
293 void setText(String text) {
294 myComponent.setText(text);
298 int getMnemonicCode() {
299 return myComponent.getDisplayedMnemonic();
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);
308 myStrokeRelease = fixMacKeyStroke(myStrokeRelease, map, code, true, "release");
314 int getMnemonicIndex() {
315 return myComponent.getDisplayedMnemonicIndex();
319 void setMnemonicIndex(int index) {
320 myComponent.setDisplayedMnemonicIndex(index);
324 Component getFocusableComponent() {
325 return myComponent.getLabelFor();