blob: 7be0993de405e28a6609f6c3aaf52bf03a685b54 [file] [log] [blame]
/*
* Copyright 2000-2010 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.openapi.keymap;
import com.intellij.icons.AllIcons;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.InvalidDataException;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.util.registry.RegistryValue;
import com.intellij.openapi.util.registry.RegistryValueListener;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.containers.ContainerUtil;
import org.intellij.lang.annotations.JdkConstants;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.util.HashSet;
import java.util.Set;
import java.util.StringTokenizer;
public class KeymapUtil {
@NonNls private static final String APPLE_LAF_AQUA_LOOK_AND_FEEL_CLASS_NAME = "apple.laf.AquaLookAndFeel";
@NonNls private static final String GET_KEY_MODIFIERS_TEXT_METHOD = "getKeyModifiersText";
@NonNls private static final String CANCEL_KEY_TEXT = "Cancel";
@NonNls private static final String BREAK_KEY_TEXT = "Break";
@NonNls private static final String SHIFT = "shift";
@NonNls private static final String CONTROL = "control";
@NonNls private static final String CTRL = "ctrl";
@NonNls private static final String META = "meta";
@NonNls private static final String ALT = "alt";
@NonNls private static final String ALT_GRAPH = "altGraph";
@NonNls private static final String DOUBLE_CLICK = "doubleClick";
private static final Set<Integer> ourTooltipKeys = new HashSet<Integer>();
private static final Set<Integer> ourOtherTooltipKeys = new HashSet<Integer>();
private static RegistryValue ourTooltipKeysProperty;
private KeymapUtil() {
}
public static String getShortcutText(@NotNull Shortcut shortcut) {
String s = "";
if (shortcut instanceof KeyboardShortcut) {
KeyboardShortcut keyboardShortcut = (KeyboardShortcut)shortcut;
String acceleratorText = getKeystrokeText(keyboardShortcut.getFirstKeyStroke());
if (!acceleratorText.isEmpty()) {
s = acceleratorText;
}
acceleratorText = getKeystrokeText(keyboardShortcut.getSecondKeyStroke());
if (!acceleratorText.isEmpty()) {
s += ", " + acceleratorText;
}
}
else if (shortcut instanceof MouseShortcut) {
MouseShortcut mouseShortcut = (MouseShortcut)shortcut;
s = getMouseShortcutText(mouseShortcut.getButton(), mouseShortcut.getModifiers(), mouseShortcut.getClickCount());
}
else if (shortcut instanceof KeyboardModifierGestureShortcut) {
final KeyboardModifierGestureShortcut gestureShortcut = (KeyboardModifierGestureShortcut)shortcut;
s = gestureShortcut.getType() == KeyboardGestureAction.ModifierType.dblClick ? "Press, release and hold " : "Hold ";
s += getKeystrokeText(gestureShortcut.getStroke());
}
else {
throw new IllegalArgumentException("unknown shortcut class: " + shortcut.getClass().getCanonicalName());
}
return s;
}
public static Icon getShortcutIcon(Shortcut shortcut) {
if (shortcut instanceof KeyboardShortcut) {
return AllIcons.General.KeyboardShortcut;
}
else if (shortcut instanceof MouseShortcut) {
return AllIcons.General.MouseShortcut;
}
else {
throw new IllegalArgumentException("unknown shortcut class: " + shortcut);
}
}
/**
* @param button target mouse button
* @param modifiers modifiers used within the target click
* @param clickCount target clicks count
* @return string representation of passed mouse shortcut.
*/
public static String getMouseShortcutText(int button, @JdkConstants.InputEventMask int modifiers, int clickCount) {
if (clickCount < 3) {
return KeyMapBundle.message("mouse." + (clickCount == 1? "" : "double.") + "click.shortcut.text", getModifiersText(mapNewModifiers(modifiers)), button);
} else {
throw new IllegalStateException("unknown clickCount: " + clickCount);
}
}
@JdkConstants.InputEventMask
private static int mapNewModifiers(@JdkConstants.InputEventMask int modifiers) {
if ((modifiers & InputEvent.SHIFT_DOWN_MASK) != 0) {
modifiers |= InputEvent.SHIFT_MASK;
}
if ((modifiers & InputEvent.ALT_DOWN_MASK) != 0) {
modifiers |= InputEvent.ALT_MASK;
}
if ((modifiers & InputEvent.ALT_GRAPH_DOWN_MASK) != 0) {
modifiers |= InputEvent.ALT_GRAPH_MASK;
}
if ((modifiers & InputEvent.CTRL_DOWN_MASK) != 0) {
modifiers |= InputEvent.CTRL_MASK;
}
if ((modifiers & InputEvent.META_DOWN_MASK) != 0) {
modifiers |= InputEvent.META_MASK;
}
return modifiers;
}
public static String getKeystrokeText(KeyStroke accelerator) {
if (accelerator == null) return "";
if (SystemInfo.isMac) {
return MacKeymapUtil.getKeyStrokeText(accelerator);
}
String acceleratorText = "";
int modifiers = accelerator.getModifiers();
if (modifiers > 0) {
acceleratorText = getModifiersText(modifiers);
}
final int code = accelerator.getKeyCode();
String keyText = SystemInfo.isMac ? MacKeymapUtil.getKeyText(code) : KeyEvent.getKeyText(code);
// [vova] this is dirty fix for bug #35092
if(CANCEL_KEY_TEXT.equals(keyText)){
keyText = BREAK_KEY_TEXT;
}
acceleratorText += keyText;
return acceleratorText.trim();
}
private static String getModifiersText(@JdkConstants.InputEventMask int modifiers) {
if (SystemInfo.isMac) {
//try {
// Class appleLaf = Class.forName(APPLE_LAF_AQUA_LOOK_AND_FEEL_CLASS_NAME);
// Method getModifiers = appleLaf.getMethod(GET_KEY_MODIFIERS_TEXT_METHOD, int.class, boolean.class);
// return (String)getModifiers.invoke(appleLaf, modifiers, Boolean.FALSE);
//}
//catch (Exception e) {
// if (SystemInfo.isMacOSLeopard) {
// return getKeyModifiersTextForMacOSLeopard(modifiers);
// }
//
// // OK do nothing here.
//}
return MacKeymapUtil.getModifiersText(modifiers);
}
final String keyModifiersText = KeyEvent.getKeyModifiersText(modifiers);
if (keyModifiersText.isEmpty()) {
return keyModifiersText;
}
else {
return keyModifiersText + "+";
}
}
@NotNull
public static String getFirstKeyboardShortcutText(@NotNull String actionId) {
Shortcut[] shortcuts = KeymapManager.getInstance().getActiveKeymap().getShortcuts(actionId);
KeyboardShortcut shortcut = ContainerUtil.findInstance(shortcuts, KeyboardShortcut.class);
return shortcut == null? "" : getShortcutText(shortcut);
}
@NotNull
public static String getFirstKeyboardShortcutText(@NotNull AnAction action) {
Shortcut[] shortcuts = action.getShortcutSet().getShortcuts();
KeyboardShortcut shortcut = ContainerUtil.findInstance(shortcuts, KeyboardShortcut.class);
return shortcut == null ? "" : getShortcutText(shortcut);
}
public static String getShortcutsText(Shortcut[] shortcuts) {
if (shortcuts.length == 0) {
return "";
}
StringBuilder buffer = new StringBuilder();
for (int i = 0; i < shortcuts.length; i++) {
Shortcut shortcut = shortcuts[i];
if (i > 0) {
buffer.append(' ');
}
buffer.append(getShortcutText(shortcut));
}
return buffer.toString();
}
/**
* Factory method. It parses passed string and creates <code>MouseShortcut</code>.
*
* @param keystrokeString target keystroke
* @return shortcut for the given keystroke
* @throws InvalidDataException if <code>keystrokeString</code> doesn't represent valid <code>MouseShortcut</code>.
*/
public static MouseShortcut parseMouseShortcut(String keystrokeString) throws InvalidDataException {
int button = -1;
int modifiers = 0;
int clickCount = 1;
for (StringTokenizer tokenizer = new StringTokenizer(keystrokeString); tokenizer.hasMoreTokens();) {
String token = tokenizer.nextToken();
if (SHIFT.equals(token)) {
modifiers |= InputEvent.SHIFT_DOWN_MASK;
}
else if (CONTROL.equals(token) || CTRL.equals(token)) {
modifiers |= InputEvent.CTRL_DOWN_MASK;
}
else if (META.equals(token)) {
modifiers |= InputEvent.META_DOWN_MASK;
}
else if (ALT.equals(token)) {
modifiers |= InputEvent.ALT_DOWN_MASK;
}
else if (ALT_GRAPH.equals(token)) {
modifiers |= InputEvent.ALT_GRAPH_DOWN_MASK;
}
else if (token.startsWith("button") && token.length() > 6) {
try {
button = Integer.parseInt(token.substring(6));
}
catch (NumberFormatException e) {
throw new InvalidDataException("unparseable token: " + token);
}
}
else if (DOUBLE_CLICK.equals(token)) {
clickCount = 2;
}
else {
throw new InvalidDataException("unknown token: " + token);
}
}
return new MouseShortcut(button, modifiers, clickCount);
}
public static String getKeyModifiersTextForMacOSLeopard(@JdkConstants.InputEventMask int modifiers) {
StringBuilder buf = new StringBuilder();
if ((modifiers & InputEvent.META_MASK) != 0) {
buf.append("\u2318");
}
if ((modifiers & InputEvent.CTRL_MASK) != 0) {
buf.append(Toolkit.getProperty("AWT.control", "Ctrl"));
}
if ((modifiers & InputEvent.ALT_MASK) != 0) {
buf.append("\2325");
}
if ((modifiers & InputEvent.SHIFT_MASK) != 0) {
buf.append(Toolkit.getProperty("AWT.shift", "Shift"));
}
if ((modifiers & InputEvent.ALT_GRAPH_MASK) != 0) {
buf.append(Toolkit.getProperty("AWT.altGraph", "Alt Graph"));
}
if ((modifiers & InputEvent.BUTTON1_MASK) != 0) {
buf.append(Toolkit.getProperty("AWT.button1", "Button1"));
}
return buf.toString();
}
public static boolean isTooltipRequest(KeyEvent keyEvent) {
if (ourTooltipKeysProperty == null) {
ourTooltipKeysProperty = Registry.get("ide.forcedShowTooltip");
ourTooltipKeysProperty.addListener(new RegistryValueListener.Adapter() {
@Override
public void afterValueChanged(RegistryValue value) {
updateTooltipRequestKey(value);
}
}, Disposer.get("ui"));
updateTooltipRequestKey(ourTooltipKeysProperty);
}
if (keyEvent.getID() != KeyEvent.KEY_PRESSED) return false;
for (Integer each : ourTooltipKeys) {
if ((keyEvent.getModifiers() & each.intValue()) == 0) return false;
}
for (Integer each : ourOtherTooltipKeys) {
if ((keyEvent.getModifiers() & each.intValue()) > 0) return false;
}
final int code = keyEvent.getKeyCode();
return code == KeyEvent.VK_META || code == KeyEvent.VK_CONTROL || code == KeyEvent.VK_SHIFT || code == KeyEvent.VK_ALT;
}
private static void updateTooltipRequestKey(RegistryValue value) {
final String text = value.asString();
ourTooltipKeys.clear();
ourOtherTooltipKeys.clear();
processKey(text.contains("meta"), InputEvent.META_MASK);
processKey(text.contains("control") || text.contains("ctrl"), InputEvent.CTRL_MASK);
processKey(text.contains("shift"), InputEvent.SHIFT_MASK);
processKey(text.contains("alt"), InputEvent.ALT_MASK);
}
private static void processKey(boolean condition, int value) {
if (condition) {
ourTooltipKeys.add(value);
} else {
ourOtherTooltipKeys.add(value);
}
}
public static boolean isEmacsKeymap() {
return isEmacsKeymap(KeymapManager.getInstance().getActiveKeymap());
}
public static boolean isEmacsKeymap(@Nullable Keymap keymap) {
for (; keymap != null; keymap = keymap.getParent()) {
if ("Emacs".equalsIgnoreCase(keymap.getName())) {
return true;
}
}
return false;
}
@Nullable
public static KeyStroke getKeyStroke(@NotNull final ShortcutSet shortcutSet) {
final Shortcut[] shortcuts = shortcutSet.getShortcuts();
if (shortcuts.length == 0 || !(shortcuts[0] instanceof KeyboardShortcut)) return null;
final KeyboardShortcut shortcut = (KeyboardShortcut)shortcuts[0];
if (shortcut.getSecondKeyStroke() != null) {
return null;
}
return shortcut.getFirstKeyStroke();
}
@NotNull
public static String createTooltipText(@Nullable String name, @NotNull AnAction action) {
String toolTipText = name == null ? "" : name;
while (StringUtil.endsWithChar(toolTipText, '.')) {
toolTipText = toolTipText.substring(0, toolTipText.length() - 1);
}
String shortcutsText = getFirstKeyboardShortcutText(action);
if (!shortcutsText.isEmpty()) {
toolTipText += " (" + shortcutsText + ")";
}
return toolTipText;
}
}