The Battle for Wesnoth  1.17.0-dev
hotkey_item.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2021
3  by David White <dave@whitevine.net>
4  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
16 
17 #include "log.hpp"
18 #include "hotkey/hotkey_item.hpp"
20 #include "config.hpp"
21 
22 #define GETTEXT_DOMAIN "wesnoth-lib"
23 
24 #include <boost/algorithm/string/join.hpp>
25 #include <boost/algorithm/string.hpp>
26 #include <functional>
27 #include "game_config_view.hpp"
28 #include <SDL2/SDL.h>
29 #include <key.hpp>
31 
32 
33 static lg::log_domain log_config("config");
34 #define ERR_G LOG_STREAM(err, lg::general())
35 #define LOG_G LOG_STREAM(info, lg::general())
36 #define DBG_G LOG_STREAM(debug, lg::general())
37 #define ERR_CF LOG_STREAM(err, log_config)
38 
39 namespace hotkey {
40 
43 
44 namespace {
45  const int TOUCH_MOUSE_INDEX = 255;
46  const char TOUCH_MOUSE_STRING[] = "255";
47 };
48 
49 static unsigned int sdl_get_mods()
50 {
51  unsigned int mods;
52  mods = SDL_GetModState();
53 
54  mods &= ~KMOD_NUM;
55  mods &= ~KMOD_CAPS;
56  mods &= ~KMOD_MODE;
57 
58  // save the matching for checking right vs left keys
59  if (mods & KMOD_SHIFT)
60  mods |= KMOD_SHIFT;
61 
62  if (mods & KMOD_CTRL)
63  mods |= KMOD_CTRL;
64 
65  if (mods & KMOD_ALT)
66  mods |= KMOD_ALT;
67 
68  if (mods & KMOD_GUI)
69  mods |= KMOD_GUI;
70 
71  return mods;
72 }
73 
74 const std::string hotkey_base::get_name() const
75 {
76  std::string ret = "";
77 
78  if (mod_ & KMOD_CTRL)
79  ret += "ctrl";
80 
81  ret +=
82  (!ret.empty() && !boost::algorithm::ends_with(ret, "+") ?
83  "+" : "");
84  if (mod_ & KMOD_ALT)
85  ret += "alt";
86 
87  ret +=
88  (!ret.empty() && !boost::algorithm::ends_with(ret, "+") ?
89  "+" : "");
90  if (mod_ & KMOD_SHIFT)
91  ret += "shift";
92 
93  ret +=
94  (!ret.empty() && !boost::algorithm::ends_with(ret, "+") ?
95  "+" : "");
96  if (mod_ & KMOD_GUI)
97 #ifdef __APPLE__
98  ret += "cmd";
99 #else
100  ret += "win";
101 #endif
102 
103  ret +=
104  (!ret.empty() && !boost::algorithm::ends_with(ret, "+") ?
105  "+" : "");
106  return ret += get_name_helper();
107 }
108 
110 {
111  if (other == hotkey_ptr()) {
112  return false;
113  }
114 
116  & hotkey::get_hotkey_command(other->get_command()).scope;
117 
118  if (scopematch.none()) {
119  return false;
120  }
121 
122  return mod_ == other->mod_ && bindings_equal_helper(other);
123 }
124 
125 bool hotkey_base::matches(const SDL_Event &event) const
126 {
128  !active() || is_disabled()) {
129  return false;
130  }
131 
132  return matches_helper(event);
133 }
134 
136 {
137  item["command"] = get_command();
138  item["disabled"] = is_disabled();
139 
140  item["shift"] = !!(mod_ & KMOD_SHIFT);
141  item["ctrl"] = !!(mod_ & KMOD_CTRL);
142  item["cmd"] = !!(mod_ & KMOD_GUI);
143  item["alt"] = !!(mod_ & KMOD_ALT);
144 
145  save_helper(item);
146 }
147 
148 hotkey_ptr create_hotkey(const std::string &id, const SDL_Event &event)
149 {
150  hotkey_ptr base = std::make_shared<hotkey_void>();
151  const hotkey_command& command = get_hotkey_command(id);
152  unsigned mods = sdl_get_mods();
153 
154  switch (event.type) {
155  case SDL_KEYUP: {
156  if (mods & KMOD_CTRL || mods & KMOD_ALT || mods & KMOD_GUI || CKey::is_uncomposable(event.key) || command.toggle) {
157  auto keyboard = std::make_shared<hotkey_keyboard>();
158  base = std::dynamic_pointer_cast<hotkey_base>(keyboard);
159  SDL_Keycode code;
160  code = event.key.keysym.sym;
161  keyboard->set_keycode(code);
162  keyboard->set_text(SDL_GetKeyName(event.key.keysym.sym));
163  }
164  }
165  break;
166  case SDL_TEXTINPUT: {
167  if(command.toggle) {
168  return nullptr;
169  }
170  auto keyboard = std::make_shared<hotkey_keyboard>();
171  base = std::dynamic_pointer_cast<hotkey_base>(keyboard);
172  std::string text = std::string(event.text.text);
173  keyboard->set_text(text);
174  if(text == ":" || text == "`") {
175  mods = mods & ~KMOD_SHIFT;
176  }
177  }
178  break;
179  case SDL_MOUSEBUTTONUP: {
180  auto mouse = std::make_shared<hotkey_mouse>();
181  base = std::dynamic_pointer_cast<hotkey_base>(mouse);
182  mouse->set_button(event.button.button);
183  break;
184  }
185  default:
186  ERR_G<< "Trying to bind an unknown event type:" << event.type << "\n";
187  break;
188  }
189 
190  base->set_mods(mods);
191  base->set_command(id);
192  base->unset_default();
193 
194  return base;
195 }
196 
198 {
199  hotkey_ptr base = std::make_shared<hotkey_void>();
200 
201  const std::string& mouse_cfg = cfg["mouse"];
202  if (!mouse_cfg.empty()) {
203  auto mouse = std::make_shared<hotkey_mouse>();
204  base = std::dynamic_pointer_cast<hotkey_base>(mouse);
205  if (mouse_cfg == TOUCH_MOUSE_STRING) {
206  mouse->set_button(TOUCH_MOUSE_INDEX);
207  } else {
208  mouse->set_button(cfg["button"].to_int());
209  }
210  }
211 
212  const std::string& key_cfg = cfg["key"];
213  if (!key_cfg.empty()) {
214  auto keyboard = std::make_shared<hotkey_keyboard>();
215  base = std::dynamic_pointer_cast<hotkey_base>(keyboard);
216 
217  SDL_Keycode keycode = SDL_GetKeyFromName(key_cfg.c_str());
218  if (keycode == SDLK_UNKNOWN) {
219  ERR_G<< "Unknown key: " << key_cfg << "\n";
220  }
221  keyboard->set_text(key_cfg);
222  keyboard->set_keycode(keycode);
223  }
224 
225  if (base == hotkey_ptr()) {
226  return base;
227  }
228 
229  unsigned int mods = 0;
230 
231  if (cfg["shift"].to_bool())
232  mods |= KMOD_SHIFT;
233  if (cfg["ctrl"].to_bool())
234  mods |= KMOD_CTRL;
235  if (cfg["cmd"].to_bool())
236  mods |= KMOD_GUI;
237  if (cfg["alt"].to_bool())
238  mods |= KMOD_ALT;
239 
240  base->set_mods(mods);
241  base->set_command(cfg["command"].str());
242 
243  cfg["disabled"].to_bool() ? base->disable() : base->enable();
244 
245  return base;
246 }
247 
248 bool hotkey_mouse::matches_helper(const SDL_Event &event) const
249 {
250  if (event.type != SDL_MOUSEBUTTONUP
251  && event.type != SDL_MOUSEBUTTONDOWN
252  && event.type != SDL_FINGERDOWN
253  && event.type != SDL_FINGERUP) {
254  return false;
255  }
256 
257  unsigned int mods = sdl_get_mods();
258  if ((mods != mod_)) {
259  return false;
260  }
261 
262  if (event.button.which == SDL_TOUCH_MOUSEID) {
263  return button_ == TOUCH_MOUSE_INDEX;
264  }
265 
266  return event.button.button == button_;
267 }
268 
269 const std::string hotkey_mouse::get_name_helper() const
270 {
271  return "mouse " + std::to_string(button_);
272 }
273 
275 {
276  item["mouse"] = 0;
277  if (button_ != 0) {
278  item["button"] = button_;
279  }
280 }
281 
282 const std::string hotkey_keyboard::get_name_helper() const
283 {
284  return text_;
285 }
286 
287 bool hotkey_keyboard::matches_helper(const SDL_Event &event) const
288 {
289  unsigned int mods = sdl_get_mods();
290  const hotkey_command& command = get_hotkey_command(get_command());
291 
292  if ((event.type == SDL_KEYDOWN || event.type == SDL_KEYUP) &&
293  (mods & KMOD_CTRL || mods & KMOD_ALT || mods & KMOD_GUI ||
294  command.toggle || CKey::is_uncomposable(event.key))) {
295  return event.key.keysym.sym == keycode_ && mods == mod_;
296  }
297 
298  if (event.type == SDL_TEXTINPUT && !command.toggle) {
299  std::string text = std::string(event.text.text);
300  boost::algorithm::to_lower(text);
301  if(text == ":" || text == "`") {
302  mods = mods & ~KMOD_SHIFT;
303  }
304  return text_ == text && utf8::size(std::string(event.text.text)) == 1 && mods == mod_;
305  }
306 
307  return false;
308 }
309 
311 {
312  hotkey_mouse_ptr other_m = std::dynamic_pointer_cast<hotkey_mouse>(other);
313 
314  if (other_m == hotkey_mouse_ptr()) {
315  return false;
316  }
317 
318  return button_ == other_m->button_;
319 }
320 
322 {
323  if(!text_.empty()) {
324  item["key"] = text_;
325  }
326 }
327 
328 bool has_hotkey_item(const std::string& command)
329 {
330  for (hotkey_ptr item : hotkeys_) {
331  if (item->get_command() == command) {
332  return true;
333  }
334 
335  }
336  return false;
337 }
338 
340 {
341  hotkey_keyboard_ptr other_k = std::dynamic_pointer_cast<hotkey_keyboard>(
342  other);
343  if (other_k == hotkey_keyboard_ptr()) {
344  return false;
345  }
346 
347  return text_ == other_k->text_;
348 }
349 
351 {
352  if (!hotkeys_.empty()) {
353  hotkeys_.erase(std::remove(hotkeys_.begin(), hotkeys_.end(), item));
354  }
355 }
356 
358 {
359 
360  if (item == hotkey_ptr()) {
361  return;
362  }
363 
364  scope_changer scope_ch;
365  set_active_scopes(hotkey::get_hotkey_command(item->get_command()).scope);
366 
367  if (!hotkeys_.empty()) {
368  hotkeys_.erase(
369  std::remove_if(hotkeys_.begin(), hotkeys_.end(), [item](const hotkey::hotkey_ptr& hk) { return hk->bindings_equal(item); }),
370  hotkeys_.end());
371  }
372 
373  hotkeys_.push_back(item);
374 
375 }
376 
377 void clear_hotkeys(const std::string& command)
378 {
379  for (hotkey::hotkey_ptr item : hotkeys_) {
380  if (item->get_command() == command)
381  {
382  if (item->is_default())
383  item->disable();
384  else
385  item->clear();
386  }
387  }
388 }
389 
391 {
392  hotkeys_.clear();
393 }
394 
395 const hotkey_ptr get_hotkey(const SDL_Event &event)
396 {
397  for (hotkey_ptr item : hotkeys_) {
398  if (item->matches(event)) {
399  return item;
400  }
401  }
402  return hotkey_ptr(new hotkey_void());
403 }
404 
405 void load_hotkeys(const game_config_view& cfg, bool set_as_default)
406 {
407  for (const config &hk : cfg.child_range("hotkey")) {
408 
410  if (!set_as_default) {
411  item->unset_default();
412  }
413 
414  if (!item->null()) {
415  add_hotkey(item);
416  }
417  }
418 
419  if (set_as_default) {
420  default_hotkey_cfg_ = cfg;
421  }
422 }
423 
425 {
426  hotkeys_.clear();
427 
428  if (!default_hotkey_cfg_.child_range("hotkey").empty()) {
429  load_hotkeys(default_hotkey_cfg_, true);
430  } else {
431  ERR_G<< "no default hotkeys set yet; all hotkeys are now unassigned!" << std::endl;
432  }
433 }
434 
436 {
437  return hotkeys_;
438 }
439 
441 {
442  cfg.clear_children("hotkey");
443 
444  for (hotkey_ptr item : hotkeys_) {
445  if ((!item->is_default() && item->active()) ||
446  (item->is_default() && item->is_disabled())) {
447  item->save(cfg.add_child("hotkey"));
448  }
449  }
450 }
451 
452 std::string get_names(const std::string& id)
453 {
454  // Names are used in places like the hot-key preferences menu
455  std::vector<std::string> names;
456  for (const hotkey::hotkey_ptr& item : hotkeys_) {
457  if (item->get_command() == id && !item->null() && !item->is_disabled()) {
458  names.push_back(item->get_name());
459  }
460  }
461 
462  // These are hard-coded, non-rebindable hotkeys
463  if (id == "quit") {
464  names.push_back("escape");
465  }
466  else if (id == "quit-to-desktop") {
467 #ifdef __APPLE__
468  names.push_back("cmd+q");
469 #else
470  names.push_back("alt+F4");
471 #endif
472  }
473 
474  return boost::algorithm::join(names, ", ");
475 }
476 
477 bool is_hotkeyable_event(const SDL_Event &event) {
478 
479  if (event.type == SDL_JOYBUTTONUP ||
480  event.type == SDL_JOYHATMOTION ||
481  event.type == SDL_MOUSEBUTTONUP) {
482  return true;
483  }
484 
485  unsigned mods = sdl_get_mods();
486 
487  if (mods & KMOD_CTRL || mods & KMOD_ALT || mods & KMOD_GUI) {
488  return event.type == SDL_KEYUP;
489  } else {
490  return event.type == SDL_TEXTINPUT || event.type == SDL_KEYUP;
491  }
492 }
493 
494 }
void remove()
Removes a tip.
Definition: tooltip.cpp:175
virtual bool matches_helper(const SDL_Event &event) const
This is invoked by hotkey_base::matches as a helper for the concrete classes.
virtual void save_helper(config &cfg) const
static unsigned int sdl_get_mods()
Definition: hotkey_item.cpp:49
void clear_children(T... keys)
Definition: config.hpp:557
hotkey_ptr load_from_config(const config &cfg)
Create and instantiate a hotkey from a config element.
void save_hotkeys(config &cfg)
Save the non-default hotkeys to the config.
std::string join(const T &v, const std::string &s=",")
Generates a new string joining container items in a list.
bool is_scope_active(scope s)
scope
Available hotkey scopes.
const std::string get_name() const
Return "name" of hotkey.
Definition: hotkey_item.cpp:74
config_array_view child_range(config_key_type key) const
bool ends_with(const std::string &str, const std::string &suffix)
virtual void save_helper(config &cfg) const
virtual bool bindings_equal_helper(hotkey_ptr other) const =0
This is invoked by hotkey_base::bindings_equal as a helper for the concrete classes.
static lg::log_domain log_config("config")
void del_hotkey(hotkey_ptr item)
Remove a hotkey from the list of hotkeys.
void clear_hotkeys(const std::string &command)
Unset the command bindings for all hotkeys matching the command.
Stores all information related to functions that can be bound to hotkeys.
bool has_hotkey_item(const std::string &command)
bool matches(const SDL_Event &event) const
Used to evaluate whether:
bool is_disabled() const
const std::string & get_command() const
Returns the string name of the HOTKEY_COMMAND.
Definition: hotkey_item.hpp:70
virtual const std::string get_name_helper() const
This is invoked by hotkey_base::get_name and must be implemented by subclasses.
static bool is_uncomposable(const SDL_KeyboardEvent &event)
Definition: key.cpp:28
Definitions for the interface to Wesnoth Markup Language (WML).
virtual bool matches_helper(const SDL_Event &event) const =0
This is invoked by hotkey_base::matches as a helper for the concrete classes.
void set_active_scopes(hk_scopes s)
Keyboard shortcuts for game actions.
std::shared_ptr< hotkey_mouse > hotkey_mouse_ptr
Definition: hotkey_item.hpp:34
#define ERR_G
Definition: hotkey_item.cpp:34
const hotkey_list & get_hotkeys()
Returns the list of hotkeys.
std::string get_names(const std::string &id)
Returns a comma-separated string of hotkey names.
game_config_view default_hotkey_cfg_
Definition: hotkey_item.cpp:42
std::size_t size(const std::string &str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:87
std::bitset< SCOPE_COUNT > hk_scopes
virtual void save_helper(config &cfg) const =0
const hotkey_ptr get_hotkey(const SDL_Event &event)
Iterate through the list of hotkeys and return a hotkey that matches the SDL_Event and the current ke...
virtual const std::string get_name_helper() const
This is invoked by hotkey_base::get_name and must be implemented by subclasses.
std::shared_ptr< hotkey_keyboard > hotkey_keyboard_ptr
Definition: hotkey_item.hpp:35
virtual bool bindings_equal_helper(hotkey_ptr other) const
This is invoked by hotkey_base::bindings_equal as a helper for the concrete classes.
void add_hotkey(const hotkey_ptr item)
Add a hotkey to the list of hotkeys.
This class is used to return non-valid results in order to save other people from null checks...
virtual bool matches_helper(const SDL_Event &event) const
This is invoked by hotkey_base::matches as a helper for the concrete classes.
std::vector< hotkey::hotkey_ptr > hotkey_list
Definition: hotkey_item.hpp:37
hk_scopes scope
The visibility scope of the command.
void save(config &cfg) const
Save the hotkey into the configuration object.
bool toggle
Toggle hotkeys have some restrictions on what can be bound to them.
std::vector< std::string > names
Definition: build_info.cpp:66
virtual const std::string get_name_helper() const =0
This is invoked by hotkey_base::get_name and must be implemented by subclasses.
hotkey_list hotkeys_
Definition: hotkey_item.cpp:41
std::shared_ptr< hotkey_base > hotkey_ptr
Definition: hotkey_item.hpp:32
void reset_default_hotkeys()
Reset all hotkeys to the defaults.
config & add_child(config_key_type key)
Definition: config.cpp:514
This class is responsible for handling mouse button presses.
This is the base class for hotkey event matching.
Definition: hotkey_item.hpp:43
This class is responsible for handling keys, not modifiers.
hotkey_ptr create_hotkey(const std::string &id, const SDL_Event &event)
Create a new hotkey item for a command from an SDL_Event.
Standard logging facilities (interface).
virtual bool bindings_equal(hotkey_ptr other)
Checks whether the hotkey bindings and scope are equal.
bool is_hotkeyable_event(const SDL_Event &event)
void load_hotkeys(const game_config_view &cfg, bool set_as_default)
Iterates through all hotkeys present in the config struct and creates and adds them to the hotkey lis...
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:61
const hotkey_command & get_hotkey_command(const std::string &command)
returns the hotkey_command with the given name
virtual bool bindings_equal_helper(hotkey_ptr other) const
This is invoked by hotkey_base::bindings_equal as a helper for the concrete classes.
std::pair< std::string, unsigned > item
Definition: help_impl.hpp:410
bool active() const