/*
* Copyright 2003-2017 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 jetbrains.mps.nodeEditor.keymaps;
import gnu.trove.TIntObjectHashMap;
import gnu.trove.TObjectProcedure;
import jetbrains.mps.nodeEditor.EditorComponent;
import jetbrains.mps.openapi.editor.EditorContext;
import jetbrains.mps.openapi.editor.cells.EditorCell;
import jetbrains.mps.openapi.editor.cells.KeyMap;
import jetbrains.mps.openapi.editor.cells.KeyMap.ActionKey;
import jetbrains.mps.openapi.editor.cells.KeyMapAction;
import jetbrains.mps.util.Pair;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.JViewport;
import javax.swing.KeyStroke;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
/**
* User: shatalin
* Date: 2/5/13
*/
public class AWTKeymapHandler extends KeymapHandler<KeyEvent> {
private static final Logger LOG = LogManager.getLogger(AWTKeymapHandler.class);
private static TIntObjectHashMap<String> ourJavaKeyCodesMap = new TIntObjectHashMap<String>();
static {
for (Field field : KeyEvent.class.getDeclaredFields()) {
String name = field.getName();
if (name.startsWith("VK_")) {
if (name.equals("VK_CONTROL") ||
name.equals("VK_ALT") ||
name.equals("VK_SHIFT")) {
continue;
}
try {
int value = field.getInt(null);
ourJavaKeyCodesMap.put(value, name);
} catch (IllegalAccessException e) {
LOG.error(null, e);
}
}
}
} // static init
public static List<String> getValidKeyCodes() {
final List<String> result = new ArrayList<String>(ourJavaKeyCodesMap.size() + 5);
result.add(KeyMap.KEY_CODE_DIGIT);
result.add(KeyMap.KEY_CODE_LETTER);
result.add(KeyMap.KEY_CODE_LETTER_OR_DIGIT);
result.add(KeyMap.KEY_CODE_SPACE);
result.add(KeyMap.KEY_CODE_CHAR);
ourJavaKeyCodesMap.forEachValue(new TObjectProcedure<String>() {
@Override
public boolean execute(String value) {
result.add(value);
return true;
}
});
Collections.sort(result);
return result;
}
public static List<String> getValidModifiers() {
final List<String> result = new ArrayList<String>(8);
result.add(KeyMap.KEY_MODIFIERS_NONE);
result.add(KeyMap.KEY_MODIFIERS_ANY);
result.add(KeyMap.KEY_MODIFIERS_CTRL);
result.add(KeyMap.KEY_MODIFIERS_ALT);
result.add(KeyMap.KEY_MODIFIERS_SHIFT);
result.add(KeyMap.KEY_MODIFIERS_CTRL_ALT);
result.add(KeyMap.KEY_MODIFIERS_CTRL_SHIFT);
result.add(KeyMap.KEY_MODIFIERS_CTRL_ALT_SHIFT);
result.add(KeyMap.KEY_MODIFIERS_ALT_SHIFT);
Collections.sort(result);
return result;
}
private static List<String> modifiersForEvent(KeyEvent event) {
List<String> modifiers = new LinkedList<String>();
if (event.getModifiers() == 0) {
modifiers.add(KeyMap.KEY_MODIFIERS_NONE);
} else if (event.isControlDown() && !event.isAltDown() && !event.isShiftDown()) {
modifiers.add(KeyMap.KEY_MODIFIERS_CTRL);
} else if (!event.isControlDown() && event.isAltDown() && !event.isShiftDown()) {
modifiers.add(KeyMap.KEY_MODIFIERS_ALT);
} else if (!event.isControlDown() && !event.isAltDown() && event.isShiftDown()) {
modifiers.add(KeyMap.KEY_MODIFIERS_SHIFT);
} else if (event.isControlDown() && event.isAltDown() && !event.isShiftDown()) {
modifiers.add(KeyMap.KEY_MODIFIERS_CTRL_ALT);
} else if (event.isControlDown() && !event.isAltDown() && event.isShiftDown()) {
modifiers.add(KeyMap.KEY_MODIFIERS_CTRL_SHIFT);
} else if (event.isControlDown() && event.isAltDown() && event.isShiftDown()) {
modifiers.add(KeyMap.KEY_MODIFIERS_CTRL_ALT_SHIFT);
} else if (!event.isControlDown() && event.isAltDown() && event.isShiftDown()) {
modifiers.add(KeyMap.KEY_MODIFIERS_ALT_SHIFT);
}
modifiers.add(KeyMap.KEY_MODIFIERS_ANY);
return modifiers;
}
private static List<String> keyCodesForEvent(KeyEvent event) {
List<String> keyCodes = new LinkedList<String>();
keyCodes.add("");
int keyCode = event.getKeyCode();
if (keyCode != KeyEvent.VK_CONTROL &&
keyCode != KeyEvent.VK_ALT &&
keyCode != KeyEvent.VK_SHIFT &&
keyCode != KeyEvent.VK_UNDEFINED) {
String keyCodeName = ourJavaKeyCodesMap.get(keyCode);
assert keyCodeName != null;
keyCodes.add(keyCodeName);
}
// todo: the "keychar" testing in the "key pressed" event is not very reliable
// todo: the "key typed" event should be handled instead
if (event.isControlDown() || event.isAltDown()) {
// ignore keychar
return keyCodes; //TODO why?!
}
char keyChar = event.getKeyChar();
if (keyChar == KeyEvent.CHAR_UNDEFINED) {
return keyCodes;
}
keyCodes.add("" + keyChar);
if (!Character.isSpaceChar(keyChar) && !Character.isWhitespace(keyChar) &&
keyChar != KeyEvent.VK_DELETE &&
keyChar != KeyEvent.VK_ESCAPE &&
keyChar != KeyEvent.VK_BACK_SPACE) {
keyCodes.add(KeyMap.KEY_CODE_CHAR);
}
if (Character.isDigit(keyChar)) {
keyCodes.add(KeyMap.KEY_CODE_DIGIT);
keyCodes.add(KeyMap.KEY_CODE_LETTER_OR_DIGIT);
} else if (Character.isLetter(keyChar)) {
keyCodes.add(KeyMap.KEY_CODE_LETTER);
keyCodes.add(KeyMap.KEY_CODE_LETTER_OR_DIGIT);
} else if (Character.isLetterOrDigit(keyChar)) {
keyCodes.add(KeyMap.KEY_CODE_LETTER_OR_DIGIT);
} else if (Character.isSpaceChar(keyChar) || Character.isWhitespace(keyChar)) {
keyCodes.add(KeyMap.KEY_CODE_SPACE);
}
return keyCodes;
}
@Override
public Collection<ActionKey> getActionKeys(KeyEvent event) {
List<ActionKey> keys = new LinkedList<ActionKey>();
List<String> modifiers = modifiersForEvent(event);
List<String> keyCodes = keyCodesForEvent(event);
if (modifiers.size() > 0 && keyCodes.size() > 0) {
for (String modifier : modifiers) {
for (String keyCode : keyCodes) {
ActionKey actionKey = new ActionKey(modifier, keyCode, event.getID() == KeyEvent.KEY_TYPED);
keys.add(actionKey);
}
}
}
return keys;
}
@Override
public void showActionsMenu(Collection<Pair<KeyMapAction, EditorCell>> actionsInfo, final EditorContext editorContext, EditorCell selectedCell) {
JPopupMenu menu = new JPopupMenu();
int index = 1;
for (Pair<KeyMapAction, EditorCell> actionAndContextCell : actionsInfo) {
final KeyMapAction action = actionAndContextCell.o1;
final EditorCell contextCell = actionAndContextCell.o2;
char acc = 0;
if (1 <= index && index <= 9) {
acc = (char) ('0' + index);
} else if (index == 10) {
acc = '0';
} else if (10 < index && index - 11 < ('Z' - 'A')) {
acc = (char) ('A' + index - 11);
}
JMenuItem menuItem = new JMenuItem(action.getDescriptionText());
if (acc != 0) {
menuItem.setAccelerator(KeyStroke.getKeyStroke(acc));
}
ActionListener actionListener = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
executeAction(action, contextCell, editorContext);
}
};
menuItem.addActionListener(actionListener);
menu.add(menuItem);
index++;
}
EditorComponent component = ((jetbrains.mps.nodeEditor.EditorContext) editorContext).getNodeEditorComponent();
int x = selectedCell.getX();
int y = selectedCell.getY() + selectedCell.getHeight();
if (component.getParent() instanceof JViewport) {
JViewport viewport = (JViewport) component.getParent();
Rectangle vr = viewport.getViewRect();
x = Math.max(vr.x, x);
y = Math.max(vr.y, y);
}
menu.show(component, x, y);
}
}