1// Aseprite UI Library
2// Copyright (C) 2020 Igara Studio S.A.
3// Copyright (C) 2001-2018 David Capello
4//
5// This file is released under the terms of the MIT license.
6// Read LICENSE.txt for more information.
7
8#ifdef HAVE_CONFIG_H
9#include "config.h"
10#endif
11
12#include "ui/accelerator.h"
13
14#include "base/debug.h"
15#include "base/replace_string.h"
16#include "base/split_string.h"
17#include "base/string.h"
18#include "os/system.h"
19
20#include <cctype>
21#include <cstdlib>
22#include <string>
23#include <vector>
24
25#include <algorithm>
26
27namespace ui {
28
29#ifdef _WIN32
30 const char* kWinKeyName = "Win";
31#else
32 const char* kWinKeyName = "Super";
33#endif
34
35namespace {
36
37const char* scancode_to_string[] = { // Same order that os::KeyScancode
38 nullptr,
39 "A",
40 "B",
41 "C",
42 "D",
43 "E",
44 "F",
45 "G",
46 "H",
47 "I",
48 "J",
49 "K",
50 "L",
51 "M",
52 "N",
53 "O",
54 "P",
55 "Q",
56 "R",
57 "S",
58 "T",
59 "U",
60 "V",
61 "W",
62 "X",
63 "Y",
64 "Z",
65 "0",
66 "1",
67 "2",
68 "3",
69 "4",
70 "5",
71 "6",
72 "7",
73 "8",
74 "9",
75 "0 Pad",
76 "1 Pad",
77 "2 Pad",
78 "3 Pad",
79 "4 Pad",
80 "5 Pad",
81 "6 Pad",
82 "7 Pad",
83 "8 Pad",
84 "9 Pad",
85 "F1",
86 "F2",
87 "F3",
88 "F4",
89 "F5",
90 "F6",
91 "F7",
92 "F8",
93 "F9",
94 "F10",
95 "F11",
96 "F12",
97 "Esc",
98 "~",
99 "-",
100 "=",
101 "Backspace",
102 "Tab",
103 "[",
104 "]",
105 "Enter",
106 ";",
107 "\'",
108 "\\",
109 "KEY_BACKSLASH2",
110 ",",
111 ".",
112 "/",
113 "Space",
114 "Ins",
115 "Del",
116 "Home",
117 "End",
118 "PgUp",
119 "PgDn",
120 "Left",
121 "Right",
122 "Up",
123 "Down",
124 "/ Pad",
125 "* Pad",
126 "- Pad",
127 "+ Pad",
128 "Del Pad",
129 "Enter Pad",
130 "PrtScr",
131 "Pause",
132 "KEY_ABNT_C1",
133 "Yen",
134 "Kana",
135 "KEY_CONVERT",
136 "KEY_NOCONVERT",
137 "KEY_AT",
138 "KEY_CIRCUMFLEX",
139 "KEY_COLON2",
140 "Kanji",
141};
142int scancode_to_string_size =
143 sizeof(scancode_to_string) / sizeof(scancode_to_string[0]);
144
145} // anonymous namespace
146
147Accelerator::Accelerator()
148 : m_modifiers(kKeyNoneModifier)
149 , m_scancode(kKeyNil)
150 , m_unicodeChar(0)
151{
152}
153
154Accelerator::Accelerator(KeyModifiers modifiers, KeyScancode scancode, int unicodeChar)
155 : m_modifiers(modifiers)
156 , m_scancode(scancode)
157 , m_unicodeChar(unicodeChar)
158{
159}
160
161Accelerator::Accelerator(const std::string& str)
162 : m_modifiers(kKeyNoneModifier)
163 , m_scancode(kKeyNil)
164 , m_unicodeChar(0)
165{
166 // Special case: plus sign
167 if (str == "+") {
168 m_unicodeChar = '+';
169 return;
170 }
171
172 std::size_t i, j;
173 for (i=0; i<str.size(); i=j+1) {
174 // i+1 because the first character can be '+' sign
175 for (j=i+1; j<str.size() && str[j] != '+'; ++j)
176 ;
177 std::string tok = base::string_to_lower(str.substr(i, j - i));
178
179 if (m_scancode == kKeySpace) {
180 m_modifiers = (KeyModifiers)((int)m_modifiers | (int)kKeySpaceModifier);
181 m_scancode = kKeyNil;
182 }
183
184 // Modifiers
185 if (tok == "shift") {
186 m_modifiers = (KeyModifiers)((int)m_modifiers | (int)kKeyShiftModifier);
187 }
188 else if (tok == "alt") {
189 m_modifiers = (KeyModifiers)((int)m_modifiers | (int)kKeyAltModifier);
190 }
191 else if (tok == "ctrl") {
192 m_modifiers = (KeyModifiers)((int)m_modifiers | (int)kKeyCtrlModifier);
193 }
194 else if (tok == "cmd") {
195 m_modifiers = (KeyModifiers)((int)m_modifiers | (int)kKeyCmdModifier);
196 }
197 else if (tok == base::string_to_lower(kWinKeyName)) {
198 m_modifiers = (KeyModifiers)((int)m_modifiers | (int)kKeyWinModifier);
199 }
200 // Word with one character
201 else if (base::utf8_length(tok) == 1) {
202 std::wstring wstr = base::from_utf8(tok);
203 if (wstr.size() != 1) {
204 ASSERT(false && "Something wrong converting utf-8 to wchar string");
205 continue;
206 }
207 m_unicodeChar = std::tolower(wstr[0]);
208 m_scancode = kKeyNil;
209 }
210 // F1, F2, ..., F11, F12
211 else if (tok[0] == 'f' && (tok.size() <= 3)) {
212 int num = std::strtol(tok.c_str()+1, nullptr, 10);
213 if ((num >= 1) && (num <= 12))
214 m_scancode = (KeyScancode)((int)kKeyF1 + num - 1);
215 }
216 else if ((tok == "escape") || (tok == "esc"))
217 m_scancode = kKeyEsc;
218 else if (tok == "backspace")
219 m_scancode = kKeyBackspace;
220 else if (tok == "tab")
221 m_scancode = kKeyTab;
222 else if (tok == "enter")
223 m_scancode = kKeyEnter;
224 else if (tok == "space")
225 m_scancode = kKeySpace;
226 else if ((tok == "insert") || (tok == "ins"))
227 m_scancode = kKeyInsert;
228 else if ((tok == "delete") || (tok == "del"))
229 m_scancode = kKeyDel;
230 else if (tok == "home")
231 m_scancode = kKeyHome;
232 else if (tok == "end")
233 m_scancode = kKeyEnd;
234 else if ((tok == "page up") || (tok == "pgup"))
235 m_scancode = kKeyPageUp;
236 else if ((tok == "page down") || (tok == "pgdn"))
237 m_scancode = kKeyPageDown;
238 else if (tok == "left")
239 m_scancode = kKeyLeft;
240 else if (tok == "right")
241 m_scancode = kKeyRight;
242 else if (tok == "up")
243 m_scancode = kKeyUp;
244 else if (tok == "down")
245 m_scancode = kKeyDown;
246 else if (tok == "0 pad")
247 m_scancode = kKey0Pad;
248 else if (tok == "1 pad")
249 m_scancode = kKey1Pad;
250 else if (tok == "2 pad")
251 m_scancode = kKey2Pad;
252 else if (tok == "3 pad")
253 m_scancode = kKey3Pad;
254 else if (tok == "4 pad")
255 m_scancode = kKey4Pad;
256 else if (tok == "5 pad")
257 m_scancode = kKey5Pad;
258 else if (tok == "6 pad")
259 m_scancode = kKey6Pad;
260 else if (tok == "7 pad")
261 m_scancode = kKey7Pad;
262 else if (tok == "8 pad")
263 m_scancode = kKey8Pad;
264 else if (tok == "9 pad")
265 m_scancode = kKey9Pad;
266 else if (tok == "/ pad" || tok == "slash pad")
267 m_scancode = kKeySlashPad;
268 else if (tok == "* pad" || tok == "asterisk pad" || tok == "asterisk")
269 m_scancode = kKeyAsterisk;
270 else if (tok == "- pad" || tok == "minus pad")
271 m_scancode = kKeyMinusPad;
272 else if (tok == "+ pad" || tok == "plus pad")
273 m_scancode = kKeyPlusPad;
274 else if (tok == "del pad" || tok == "delete pad")
275 m_scancode = kKeyDelPad;
276 else if (tok == "enter pad")
277 m_scancode = kKeyEnterPad;
278 }
279}
280
281bool Accelerator::operator==(const Accelerator& other) const
282{
283 // TODO improve this, avoid conversion to std::string
284 return toString() == other.toString();
285}
286
287bool Accelerator::isEmpty() const
288{
289 return
290 (m_modifiers == kKeyNoneModifier &&
291 m_scancode == kKeyNil &&
292 m_unicodeChar == 0);
293}
294
295std::string Accelerator::toString() const
296{
297 std::string buf;
298
299 // Shifts
300 if (m_modifiers & kKeyCtrlModifier) buf += "Ctrl+";
301 if (m_modifiers & kKeyCmdModifier) buf += "Cmd+";
302 if (m_modifiers & kKeyAltModifier) buf += "Alt+";
303 if (m_modifiers & kKeyShiftModifier) buf += "Shift+";
304 if ((m_modifiers & kKeySpaceModifier) &&
305 (m_scancode != kKeySpace) &&
306 (m_unicodeChar != ' ')) {
307 buf += "Space+";
308 }
309 if (m_modifiers & kKeyWinModifier) {
310 buf += kWinKeyName;
311 buf += "+";
312 }
313
314 // Key
315 if (m_unicodeChar) {
316 std::wstring wideUnicodeChar;
317 wideUnicodeChar.push_back((wchar_t)std::toupper(m_unicodeChar));
318 buf += base::to_utf8(wideUnicodeChar);
319 }
320 else if (m_scancode > 0 &&
321 m_scancode < scancode_to_string_size &&
322 scancode_to_string[m_scancode])
323 buf += scancode_to_string[m_scancode];
324 else if (!buf.empty() && buf[buf.size()-1] == '+')
325 buf.erase(buf.size()-1);
326
327 return buf;
328}
329
330bool Accelerator::isPressed(KeyModifiers modifiers, KeyScancode scancode, int unicodeChar) const
331{
332 return ((scancode && *this == Accelerator(modifiers, scancode, 0)) ||
333 (unicodeChar && *this == Accelerator(modifiers, kKeyNil, unicodeChar)));
334}
335
336bool Accelerator::isPressed() const
337{
338 os::System* sys = os::instance();
339 if (!sys)
340 return false;
341
342 KeyModifiers pressedModifiers = sys->keyModifiers();
343
344 // Check if this shortcut is only
345 if (m_scancode == kKeyNil && m_unicodeChar == 0)
346 return (m_modifiers == pressedModifiers);
347
348 // Compare with all pressed scancodes
349 for (int s=int(kKeyNil); s<int(kKeyFirstModifierScancode); ++s) {
350 if (sys->isKeyPressed(KeyScancode(s)) &&
351 isPressed(pressedModifiers,
352 KeyScancode(s),
353 sys->getUnicodeFromScancode(KeyScancode(s))))
354 return true;
355 }
356
357 return false;
358}
359
360bool Accelerator::isLooselyPressed() const
361{
362 os::System* sys = os::instance();
363 if (!sys)
364 return false;
365
366 KeyModifiers pressedModifiers = sys->keyModifiers();
367
368 if ((m_modifiers & pressedModifiers) != m_modifiers)
369 return false;
370
371 // Check if this shortcut is only
372 if (m_scancode == kKeyNil && m_unicodeChar == 0)
373 return true;
374
375 // Compare with all pressed scancodes
376 for (int s=int(kKeyNil); s<int(kKeyFirstModifierScancode); ++s) {
377 if (sys->isKeyPressed(KeyScancode(s)) &&
378 isPressed(m_modifiers, // Use same modifiers (we've already compared the modifiers)
379 KeyScancode(s),
380 sys->getUnicodeFromScancode(KeyScancode(s))))
381 return true;
382 }
383
384 return false;
385}
386
387//////////////////////////////////////////////////////////////////////
388// Accelerators
389
390bool Accelerators::has(const Accelerator& accel) const
391{
392 return (std::find(begin(), end(), accel) != end());
393}
394
395void Accelerators::add(const Accelerator& accel)
396{
397 if (!has(accel))
398 m_list.push_back(accel);
399}
400
401void Accelerators::remove(const Accelerator& accel)
402{
403 auto it = std::find(begin(), end(), accel);
404 if (it != end())
405 m_list.erase(it);
406}
407
408} // namespace ui
409