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