/*
* KeyboardShortcut.java
*
* Copyright (C) 2009-15 by RStudio, Inc.
*
* Unless you have received this program directly from RStudio pursuant
* to the terms of a commercial license agreement with RStudio, then
* this program is licensed to you under the terms of version 3 of the
* GNU Affero General Public License. This program is distributed WITHOUT
* ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
* AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
*
*/
package org.rstudio.core.client.command;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.event.dom.client.KeyCodes;
import org.rstudio.core.client.BrowseCap;
import org.rstudio.core.client.StringUtil;
import java.util.ArrayList;
import java.util.List;
public class KeyboardShortcut
{
public static class KeyCombination
{
public KeyCombination(NativeEvent event)
{
keyCode_ = event.getKeyCode();
modifiers_ = getModifierValue(event);
}
public KeyCombination(int keyCode, int modifiers)
{
keyCode_ = keyCode;
modifiers_ = modifiers;
}
public int getKeyCode()
{
return keyCode_;
}
public int getModifier()
{
return modifiers_;
}
public boolean isCtrlPressed()
{
return (modifiers_ & CTRL) == CTRL;
}
public boolean isAltPressed()
{
return (modifiers_ & ALT) == ALT;
}
public boolean isShiftPressed()
{
return (modifiers_ & SHIFT) == SHIFT;
}
public boolean isMetaPressed()
{
return (modifiers_ & META) == META;
}
@Override
public String toString()
{
return toString(false);
}
public String toString(boolean pretty)
{
if (BrowseCap.hasMetaKey() && pretty)
{
return ((modifiers_ & CTRL) == CTRL ? "⌃" : "")
+ ((modifiers_ & SHIFT) == SHIFT ? "⇧" : "")
+ ((modifiers_ & ALT) == ALT ? "⌥" : "")
+ ((modifiers_ & META) == META ? "⌘" : "")
+ getKeyName(true);
}
else
{
return ((modifiers_ & CTRL) == CTRL ? "Ctrl+" : "")
+ ((modifiers_ & SHIFT) == SHIFT ? "Shift+" : "")
+ ((modifiers_ & ALT) == ALT ? "Alt+" : "")
+ ((modifiers_ & META) == META ? "Cmd+" : "")
+ getKeyName(pretty);
}
}
private String getKeyName(boolean pretty)
{
boolean macStyle = BrowseCap.hasMetaKey() && pretty;
if (keyCode_ == KeyCodes.KEY_ENTER)
return macStyle ? "↩" : "Enter";
else if (keyCode_ == KeyCodes.KEY_LEFT)
return macStyle ? "←" : "Left";
else if (keyCode_ == KeyCodes.KEY_RIGHT)
return macStyle ? "→" : "Right";
else if (keyCode_ == KeyCodes.KEY_UP)
return macStyle ? "↑" : "Up";
else if (keyCode_ == KeyCodes.KEY_DOWN)
return macStyle ? "↓" : "Down";
else if (keyCode_ == KeyCodes.KEY_TAB)
return macStyle ? "⇥" : "Tab";
else if (keyCode_ == KeyCodes.KEY_PAGEUP)
return pretty ? "PgUp" : "PageUp";
else if (keyCode_ == KeyCodes.KEY_PAGEDOWN)
return pretty ? "PgDn" : "PageDown";
else if (keyCode_ == 8)
return macStyle ? "⌫" : "Backspace";
return KeyboardHelper.keyNameFromKeyCode(keyCode_);
}
@Override
public int hashCode()
{
int result = modifiers_;
result = (result << 8) + keyCode_;
return result;
}
@Override
public boolean equals(Object object)
{
if (object == null || !(object instanceof KeyCombination))
return false;
KeyCombination other = (KeyCombination) object;
return keyCode_ == other.keyCode_ &&
modifiers_ == other.modifiers_;
}
private final int keyCode_;
private final int modifiers_;
}
public static class KeySequence
{
public KeySequence()
{
keyCombinations_ = new ArrayList<KeyCombination>();
}
public KeySequence(int keyCode, int modifiers)
{
this();
keyCombinations_.add(new KeyCombination(keyCode, modifiers));
}
public KeySequence(List<KeyCombination> keyList)
{
keyCombinations_ = new ArrayList<KeyCombination>(keyList);
}
public static KeySequence fromShortcutString(String shortcut)
{
KeySequence sequence = new KeySequence();
if (StringUtil.isNullOrEmpty(shortcut))
return sequence;
String[] splat = shortcut.split("\\s+");
for (int i = 0; i < splat.length; i++)
{
String sc = splat[i].toLowerCase();
int modifiers = KeyboardShortcut.NONE;
if (sc.indexOf("ctrl") != -1)
modifiers |= KeyboardShortcut.CTRL;
if (sc.indexOf("alt") != -1 || sc.indexOf("option") != -1)
modifiers |= KeyboardShortcut.ALT;
if (sc.indexOf("shift") != -1)
modifiers |= KeyboardShortcut.SHIFT;
if (sc.indexOf("meta") != -1 || sc.indexOf("cmd") != -1 || sc.indexOf("command") != -1)
modifiers |= KeyboardShortcut.META;
int keyCode = 0;
if (sc.endsWith("-"))
{
keyCode = KeyboardHelper.KEY_HYPHEN;
}
else if (sc.endsWith("+"))
{
keyCode = 187;
}
else
{
String[] keySplit = sc.split("[-+]");
String keyName = keySplit[keySplit.length - 1];
keyCode = KeyboardHelper.keyCodeFromKeyName(keyName);
}
sequence.add(keyCode, modifiers);
}
return sequence;
}
public void set(KeySequence others)
{
keyCombinations_.clear();
keyCombinations_.addAll(others.keyCombinations_);
}
public void clear()
{
keyCombinations_.clear();
}
public KeySequence clone()
{
KeySequence clone = new KeySequence();
clone.keyCombinations_.addAll(keyCombinations_);
return clone;
}
public boolean isEmpty()
{
return keyCombinations_.isEmpty();
}
public int size()
{
return keyCombinations_.size();
}
public KeyCombination get(int index)
{
return keyCombinations_.get(index);
}
public void add(NativeEvent event)
{
add(new KeyCombination(event.getKeyCode(), getModifierValue(event)));
}
public void pop()
{
if (keyCombinations_ != null && keyCombinations_.size() > 0)
keyCombinations_.remove(keyCombinations_.size() - 1);
}
public void add(int keyCode, int modifiers)
{
add(new KeyCombination(keyCode, modifiers));
}
public void add(KeyCombination combination)
{
keyCombinations_.add(combination);
}
public boolean startsWith(KeySequence other, boolean strict)
{
if (other.keyCombinations_.size() > keyCombinations_.size())
return false;
if (strict && other.keyCombinations_.size() == keyCombinations_.size())
return false;
for (int i = 0; i < other.keyCombinations_.size(); i++)
if (!keyCombinations_.get(i).equals(other.keyCombinations_.get(i)))
return false;
return true;
}
@Override
public String toString()
{
return toString(false);
}
public String toString(boolean pretty)
{
if (keyCombinations_.size() == 0)
return "";
StringBuilder builder = new StringBuilder();
builder.append(keyCombinations_.get(0).toString(pretty));
for (int i = 1; i < keyCombinations_.size(); i++)
{
builder.append(" ");
builder.append(keyCombinations_.get(i).toString(pretty));
}
return builder.toString();
}
@Override
public int hashCode()
{
int code = 1;
for (int i = 0; i < keyCombinations_.size(); i++)
code += (1 << (10 + i)) + keyCombinations_.get(i).hashCode();
return code;
}
@Override
public boolean equals(Object object)
{
if (object == null || !(object instanceof KeySequence))
return false;
KeySequence other = (KeySequence) object;
if (keyCombinations_ == null || other.keyCombinations_ == null)
return false;
if (keyCombinations_.size() != other.keyCombinations_.size())
return false;
for (int i = 0; i < keyCombinations_.size(); i++)
if (!keyCombinations_.get(i).equals(other.keyCombinations_.get(i)))
return false;
return true;
}
public List<KeyCombination> getData()
{
return keyCombinations_;
}
private final List<KeyCombination> keyCombinations_;
}
public KeyboardShortcut(int keyCode)
{
this(keyCode, "");
}
public KeyboardShortcut(int keyCode, String groupName)
{
this(KeyboardShortcut.NONE, keyCode, groupName, "", "");
}
public KeyboardShortcut(int modifiers, int keyCode)
{
this(modifiers, keyCode, "", "", "");
}
public KeyboardShortcut(int modifiers, int keyCode,
String groupName, String title, String disableModes)
{
this(new KeySequence(keyCode, modifiers), groupName, title, disableModes);
}
public KeyboardShortcut(KeySequence keySequence)
{
this(keySequence, "", "", "");
}
public KeyboardShortcut(KeySequence keySequence,
String groupName, String title, String disableModes)
{
keySequence_ = keySequence;
groupName_ = groupName;
order_ = ORDER++;
title_ = title;
disableModes_ = ShortcutManager.parseDisableModes(disableModes);
}
public int getDisableModes()
{
return disableModes_;
}
public KeySequence getKeySequence()
{
return keySequence_;
}
public boolean startsWith(KeyboardShortcut other, boolean strict)
{
return getKeySequence().startsWith(other.getKeySequence(), strict);
}
public boolean startsWith(KeySequence sequence, boolean strict)
{
return getKeySequence().startsWith(sequence, strict);
}
@Override
public boolean equals(Object object)
{
if (object == null || !(object instanceof KeyboardShortcut))
return false;
KeyboardShortcut other = (KeyboardShortcut) object;
return keySequence_.equals(other.keySequence_);
}
@Override
public int hashCode()
{
return keySequence_.hashCode();
}
@Override
public String toString()
{
return keySequence_.toString(false);
}
public String toString(boolean pretty)
{
return keySequence_.toString(pretty);
}
public String getGroupName()
{
return groupName_;
}
public int getOrder()
{
return order_;
}
public String getTitle()
{
return title_;
}
public boolean isModeDisabled(int mode)
{
return (mode & disableModes_) > 0;
}
public boolean isModalShortcut()
{
return disableModes_ != MODE_NONE;
}
public static int getModifierValue(NativeEvent e)
{
int modifiers = 0;
if (e.getAltKey())
modifiers += ALT;
if (e.getCtrlKey())
modifiers += CTRL;
if (e.getMetaKey())
modifiers += META;
if (e.getShiftKey())
modifiers += SHIFT;
return modifiers;
}
private final KeySequence keySequence_;
private String groupName_;
private int order_ = 0;
private String title_ = "";
private int disableModes_ = MODE_NONE;
private static int ORDER = 0;
public static final int NONE = 0;
public static final int ALT = 1;
public static final int CTRL = 2;
public static final int META = 4;
public static final int SHIFT = 8;
public static final int MODE_NONE = 0;
public static final int MODE_DEFAULT = 1;
public static final int MODE_VIM = 2;
public static final int MODE_EMACS = 4;
}