aseprite/src/ui/accelerator.cpp
2020-04-08 17:48:06 -03:00

409 lines
9.1 KiB
C++

// Aseprite UI Library
// Copyright (C) 2020 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "ui/accelerator.h"
#include "base/debug.h"
#include "base/replace_string.h"
#include "base/split_string.h"
#include "base/string.h"
#include "os/system.h"
#include <cctype>
#include <cstdlib>
#include <string>
#include <vector>
#include <algorithm>
namespace ui {
#ifdef _WIN32
const char* kWinKeyName = "Win";
#else
const char* kWinKeyName = "Super";
#endif
namespace {
const char* scancode_to_string[] = { // Same order that os::KeyScancode
nullptr,
"A",
"B",
"C",
"D",
"E",
"F",
"G",
"H",
"I",
"J",
"K",
"L",
"M",
"N",
"O",
"P",
"Q",
"R",
"S",
"T",
"U",
"V",
"W",
"X",
"Y",
"Z",
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"0 Pad",
"1 Pad",
"2 Pad",
"3 Pad",
"4 Pad",
"5 Pad",
"6 Pad",
"7 Pad",
"8 Pad",
"9 Pad",
"F1",
"F2",
"F3",
"F4",
"F5",
"F6",
"F7",
"F8",
"F9",
"F10",
"F11",
"F12",
"Esc",
"~",
"-",
"=",
"Backspace",
"Tab",
"[",
"]",
"Enter",
";",
"\'",
"\\",
"KEY_BACKSLASH2",
",",
".",
"/",
"Space",
"Ins",
"Del",
"Home",
"End",
"PgUp",
"PgDn",
"Left",
"Right",
"Up",
"Down",
"/ Pad",
"* Pad",
"- Pad",
"+ Pad",
"Del Pad",
"Enter Pad",
"PrtScr",
"Pause",
"KEY_ABNT_C1",
"Yen",
"Kana",
"KEY_CONVERT",
"KEY_NOCONVERT",
"KEY_AT",
"KEY_CIRCUMFLEX",
"KEY_COLON2",
"Kanji",
};
int scancode_to_string_size =
sizeof(scancode_to_string) / sizeof(scancode_to_string[0]);
} // anonymous namespace
Accelerator::Accelerator()
: m_modifiers(kKeyNoneModifier)
, m_scancode(kKeyNil)
, m_unicodeChar(0)
{
}
Accelerator::Accelerator(KeyModifiers modifiers, KeyScancode scancode, int unicodeChar)
: m_modifiers(modifiers)
, m_scancode(scancode)
, m_unicodeChar(unicodeChar)
{
}
Accelerator::Accelerator(const std::string& str)
: m_modifiers(kKeyNoneModifier)
, m_scancode(kKeyNil)
, m_unicodeChar(0)
{
// Special case: plus sign
if (str == "+") {
m_unicodeChar = '+';
return;
}
std::size_t i, j;
for (i=0; i<str.size(); i=j+1) {
// i+1 because the first character can be '+' sign
for (j=i+1; j<str.size() && str[j] != '+'; ++j)
;
std::string tok = base::string_to_lower(str.substr(i, j - i));
if (m_scancode == kKeySpace) {
m_modifiers = (KeyModifiers)((int)m_modifiers | (int)kKeySpaceModifier);
m_scancode = kKeyNil;
}
// Modifiers
if (tok == "shift") {
m_modifiers = (KeyModifiers)((int)m_modifiers | (int)kKeyShiftModifier);
}
else if (tok == "alt") {
m_modifiers = (KeyModifiers)((int)m_modifiers | (int)kKeyAltModifier);
}
else if (tok == "ctrl") {
m_modifiers = (KeyModifiers)((int)m_modifiers | (int)kKeyCtrlModifier);
}
else if (tok == "cmd") {
m_modifiers = (KeyModifiers)((int)m_modifiers | (int)kKeyCmdModifier);
}
else if (tok == base::string_to_lower(kWinKeyName)) {
m_modifiers = (KeyModifiers)((int)m_modifiers | (int)kKeyWinModifier);
}
// Word with one character
else if (base::utf8_length(tok) == 1) {
std::wstring wstr = base::from_utf8(tok);
if (wstr.size() != 1) {
ASSERT(false && "Something wrong converting utf-8 to wchar string");
continue;
}
m_unicodeChar = std::tolower(wstr[0]);
m_scancode = kKeyNil;
}
// F1, F2, ..., F11, F12
else if (tok[0] == 'f' && (tok.size() <= 3)) {
int num = std::strtol(tok.c_str()+1, nullptr, 10);
if ((num >= 1) && (num <= 12))
m_scancode = (KeyScancode)((int)kKeyF1 + num - 1);
}
else if ((tok == "escape") || (tok == "esc"))
m_scancode = kKeyEsc;
else if (tok == "backspace")
m_scancode = kKeyBackspace;
else if (tok == "tab")
m_scancode = kKeyTab;
else if (tok == "enter")
m_scancode = kKeyEnter;
else if (tok == "space")
m_scancode = kKeySpace;
else if ((tok == "insert") || (tok == "ins"))
m_scancode = kKeyInsert;
else if ((tok == "delete") || (tok == "del"))
m_scancode = kKeyDel;
else if (tok == "home")
m_scancode = kKeyHome;
else if (tok == "end")
m_scancode = kKeyEnd;
else if ((tok == "page up") || (tok == "pgup"))
m_scancode = kKeyPageUp;
else if ((tok == "page down") || (tok == "pgdn"))
m_scancode = kKeyPageDown;
else if (tok == "left")
m_scancode = kKeyLeft;
else if (tok == "right")
m_scancode = kKeyRight;
else if (tok == "up")
m_scancode = kKeyUp;
else if (tok == "down")
m_scancode = kKeyDown;
else if (tok == "0 pad")
m_scancode = kKey0Pad;
else if (tok == "1 pad")
m_scancode = kKey1Pad;
else if (tok == "2 pad")
m_scancode = kKey2Pad;
else if (tok == "3 pad")
m_scancode = kKey3Pad;
else if (tok == "4 pad")
m_scancode = kKey4Pad;
else if (tok == "5 pad")
m_scancode = kKey5Pad;
else if (tok == "6 pad")
m_scancode = kKey6Pad;
else if (tok == "7 pad")
m_scancode = kKey7Pad;
else if (tok == "8 pad")
m_scancode = kKey8Pad;
else if (tok == "9 pad")
m_scancode = kKey9Pad;
else if (tok == "/ pad" || tok == "slash pad")
m_scancode = kKeySlashPad;
else if (tok == "* pad" || tok == "asterisk pad" || tok == "asterisk")
m_scancode = kKeyAsterisk;
else if (tok == "- pad" || tok == "minus pad")
m_scancode = kKeyMinusPad;
else if (tok == "+ pad" || tok == "plus pad")
m_scancode = kKeyPlusPad;
else if (tok == "del pad" || tok == "delete pad")
m_scancode = kKeyDelPad;
else if (tok == "enter pad")
m_scancode = kKeyEnterPad;
}
}
bool Accelerator::operator==(const Accelerator& other) const
{
// TODO improve this, avoid conversion to std::string
return toString() == other.toString();
}
bool Accelerator::isEmpty() const
{
return
(m_modifiers == kKeyNoneModifier &&
m_scancode == kKeyNil &&
m_unicodeChar == 0);
}
std::string Accelerator::toString() const
{
std::string buf;
// Shifts
if (m_modifiers & kKeyCtrlModifier) buf += "Ctrl+";
if (m_modifiers & kKeyCmdModifier) buf += "Cmd+";
if (m_modifiers & kKeyAltModifier) buf += "Alt+";
if (m_modifiers & kKeyShiftModifier) buf += "Shift+";
if ((m_modifiers & kKeySpaceModifier) &&
(m_scancode != kKeySpace) &&
(m_unicodeChar != ' ')) {
buf += "Space+";
}
if (m_modifiers & kKeyWinModifier) {
buf += kWinKeyName;
buf += "+";
}
// Key
if (m_unicodeChar) {
std::wstring wideUnicodeChar;
wideUnicodeChar.push_back((wchar_t)std::toupper(m_unicodeChar));
buf += base::to_utf8(wideUnicodeChar);
}
else if (m_scancode > 0 &&
m_scancode < scancode_to_string_size &&
scancode_to_string[m_scancode])
buf += scancode_to_string[m_scancode];
else if (!buf.empty() && buf[buf.size()-1] == '+')
buf.erase(buf.size()-1);
return buf;
}
bool Accelerator::isPressed(KeyModifiers modifiers, KeyScancode scancode, int unicodeChar) const
{
return ((scancode && *this == Accelerator(modifiers, scancode, 0)) ||
(unicodeChar && *this == Accelerator(modifiers, kKeyNil, unicodeChar)));
}
bool Accelerator::isPressed() const
{
os::System* sys = os::instance();
if (!sys)
return false;
KeyModifiers pressedModifiers = sys->keyModifiers();
// Check if this shortcut is only
if (m_scancode == kKeyNil && m_unicodeChar == 0)
return (m_modifiers == pressedModifiers);
// Compare with all pressed scancodes
for (int s=int(kKeyNil); s<int(kKeyFirstModifierScancode); ++s) {
if (sys->isKeyPressed(KeyScancode(s)) &&
isPressed(pressedModifiers,
KeyScancode(s),
sys->getUnicodeFromScancode(KeyScancode(s))))
return true;
}
return false;
}
bool Accelerator::isLooselyPressed() const
{
os::System* sys = os::instance();
if (!sys)
return false;
KeyModifiers pressedModifiers = sys->keyModifiers();
if ((m_modifiers & pressedModifiers) != m_modifiers)
return false;
// Check if this shortcut is only
if (m_scancode == kKeyNil && m_unicodeChar == 0)
return true;
// Compare with all pressed scancodes
for (int s=int(kKeyNil); s<int(kKeyFirstModifierScancode); ++s) {
if (sys->isKeyPressed(KeyScancode(s)) &&
isPressed(m_modifiers, // Use same modifiers (we've already compared the modifiers)
KeyScancode(s),
sys->getUnicodeFromScancode(KeyScancode(s))))
return true;
}
return false;
}
//////////////////////////////////////////////////////////////////////
// Accelerators
bool Accelerators::has(const Accelerator& accel) const
{
return (std::find(begin(), end(), accel) != end());
}
void Accelerators::add(const Accelerator& accel)
{
if (!has(accel))
m_list.push_back(accel);
}
void Accelerators::remove(const Accelerator& accel)
{
auto it = std::find(begin(), end(), accel);
if (it != end())
m_list.erase(it);
}
} // namespace ui