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  // TODO: add joystick support back
211 #if 0
212  const std::string& joystick_cfg = cfg["joystick"];
213  if (!joystick_cfg.empty()) {
214  joystick_ = cfg["joystick"].to_int();
215  }
216  const std::string& hat = cfg["hat"];
217  if (!hat.empty()) {
218  hat_ = cfg["hat"].to_int();
219  value_ = cfg["value"].to_int();
220  }
221 
222  const std::string& button = cfg["button"];
223  if (!button.empty()) {
224  button_ = cfg["button"].to_int();
225  }
226 #endif
227 
228  const std::string& key_cfg = cfg["key"];
229  if (!key_cfg.empty()) {
230  auto keyboard = std::make_shared<hotkey_keyboard>();
231  base = std::dynamic_pointer_cast<hotkey_base>(keyboard);
232 
233  SDL_Keycode keycode = SDL_GetKeyFromName(key_cfg.c_str());
234  if (keycode == SDLK_UNKNOWN) {
235  ERR_G<< "Unknown key: " << key_cfg << "\n";
236  }
237  keyboard->set_text(key_cfg);
238  keyboard->set_keycode(keycode);
239  }
240 
241  if (base == hotkey_ptr()) {
242  return base;
243  }
244 
245  unsigned int mods = 0;
246 
247  if (cfg["shift"].to_bool())
248  mods |= KMOD_SHIFT;
249  if (cfg["ctrl"].to_bool())
250  mods |= KMOD_CTRL;
251  if (cfg["cmd"].to_bool())
252  mods |= KMOD_GUI;
253  if (cfg["alt"].to_bool())
254  mods |= KMOD_ALT;
255 
256  base->set_mods(mods);
257  base->set_command(cfg["command"].str());
258 
259  cfg["disabled"].to_bool() ? base->disable() : base->enable();
260 
261  return base;
262 }
263 
264 bool hotkey_mouse::matches_helper(const SDL_Event &event) const
265 {
266  if (event.type != SDL_MOUSEBUTTONUP
267  && event.type != SDL_MOUSEBUTTONDOWN
268  && event.type != SDL_FINGERDOWN
269  && event.type != SDL_FINGERUP) {
270  return false;
271  }
272 
273  unsigned int mods = sdl_get_mods();
274  if ((mods != mod_)) {
275  return false;
276  }
277 
278  if (event.button.which == SDL_TOUCH_MOUSEID) {
279  return button_ == TOUCH_MOUSE_INDEX;
280  }
281 
282  return event.button.button == button_;
283 }
284 
285 const std::string hotkey_mouse::get_name_helper() const
286 {
287  return "mouse " + std::to_string(button_);
288 }
289 
291 {
292  item["mouse"] = 0;
293  if (button_ != 0) {
294  item["button"] = button_;
295  }
296 }
297 
298 const std::string hotkey_keyboard::get_name_helper() const
299 {
300  return text_;
301 }
302 
303 bool hotkey_keyboard::matches_helper(const SDL_Event &event) const
304 {
305  unsigned int mods = sdl_get_mods();
306  const hotkey_command& command = get_hotkey_command(get_command());
307 
308  if ((event.type == SDL_KEYDOWN || event.type == SDL_KEYUP) &&
309  (mods & KMOD_CTRL || mods & KMOD_ALT || mods & KMOD_GUI ||
310  command.toggle || CKey::is_uncomposable(event.key))) {
311  return event.key.keysym.sym == keycode_ && mods == mod_;
312  }
313 
314  if (event.type == SDL_TEXTINPUT && !command.toggle) {
315  std::string text = std::string(event.text.text);
316  boost::algorithm::to_lower(text);
317  if(text == ":" || text == "`") {
318  mods = mods & ~KMOD_SHIFT;
319  }
320  return text_ == text && utf8::size(std::string(event.text.text)) == 1 && mods == mod_;
321  }
322 
323  return false;
324 }
325 
327 {
328  hotkey_mouse_ptr other_m = std::dynamic_pointer_cast<hotkey_mouse>(other);
329 
330  if (other_m == hotkey_mouse_ptr()) {
331  return false;
332  }
333 
334  return button_ == other_m->button_;
335 }
336 
338 {
339  if(!text_.empty()) {
340  item["key"] = text_;
341  }
342 }
343 
344 bool has_hotkey_item(const std::string& command)
345 {
346  for (hotkey_ptr item : hotkeys_) {
347  if (item->get_command() == command) {
348  return true;
349  }
350 
351  }
352  return false;
353 }
354 
356 {
357  hotkey_keyboard_ptr other_k = std::dynamic_pointer_cast<hotkey_keyboard>(
358  other);
359  if (other_k == hotkey_keyboard_ptr()) {
360  return false;
361  }
362 
363  return text_ == other_k->text_;
364 }
365 
367 {
368  if (!hotkeys_.empty()) {
369  hotkeys_.erase(std::remove(hotkeys_.begin(), hotkeys_.end(), item));
370  }
371 }
372 
374 {
375 
376  if (item == hotkey_ptr()) {
377  return;
378  }
379 
380  scope_changer scope_ch;
381  set_active_scopes(hotkey::get_hotkey_command(item->get_command()).scope);
382 
383  if (!hotkeys_.empty()) {
384  hotkeys_.erase(
385  std::remove_if(hotkeys_.begin(), hotkeys_.end(), [item](const hotkey::hotkey_ptr& hk) { return hk->bindings_equal(item); }),
386  hotkeys_.end());
387  }
388 
389  hotkeys_.push_back(item);
390 
391 }
392 
393 void clear_hotkeys(const std::string& command)
394 {
395  for (hotkey::hotkey_ptr item : hotkeys_) {
396  if (item->get_command() == command)
397  {
398  if (item->is_default())
399  item->disable();
400  else
401  item->clear();
402  }
403  }
404 }
405 
407 {
408  hotkeys_.clear();
409 }
410 
411 const hotkey_ptr get_hotkey(const SDL_Event &event)
412 {
413  for (hotkey_ptr item : hotkeys_) {
414  if (item->matches(event)) {
415  return item;
416  }
417  }
418  return hotkey_ptr(new hotkey_void());
419 }
420 
421 void load_hotkeys(const config& cfg, bool set_as_default)
422 {
423  for (const config &hk : cfg.child_range("hotkey")) {
424 
426  if (!set_as_default) {
427  item->unset_default();
428  }
429 
430  if (!item->null()) {
431  add_hotkey(item);
432  }
433  }
434 
435  if (set_as_default) {
436  default_hotkey_cfg_ = cfg;
437  }
438 }
439 
441 {
442  hotkeys_.clear();
443 
444  if (!default_hotkey_cfg_.empty()) {
445  load_hotkeys(default_hotkey_cfg_, true);
446  } else {
447  ERR_G<< "no default hotkeys set yet; all hotkeys are now unassigned!" << std::endl;
448  }
449 }
450 
452 {
453  return hotkeys_;
454 }
455 
457 {
458  cfg.clear_children("hotkey");
459 
460  for (hotkey_ptr item : hotkeys_) {
461  if ((!item->is_default() && item->active()) ||
462  (item->is_default() && item->is_disabled())) {
463  item->save(cfg.add_child("hotkey"));
464  }
465  }
466 }
467 
468 std::string get_names(const std::string& id)
469 {
470  // Names are used in places like the hot-key preferences menu
471  std::vector<std::string> names;
472  for (const hotkey::hotkey_ptr item : hotkeys_) {
473  if (item->get_command() == id && !item->null() && !item->is_disabled()) {
474  names.push_back(item->get_name());
475  }
476  }
477 
478  // These are hard-coded, non-rebindable hotkeys
479  if (id == "quit") {
480  names.push_back("escape");
481  }
482  else if (id == "quit-to-desktop") {
483 #ifdef __APPLE__
484  names.push_back("cmd+q");
485 #else
486  names.push_back("alt+F4");
487 #endif
488  }
489 
490  return boost::algorithm::join(names, ", ");
491 }
492 
493 bool is_hotkeyable_event(const SDL_Event &event) {
494 
495  if (event.type == SDL_JOYBUTTONUP ||
496  event.type == SDL_JOYHATMOTION ||
497  event.type == SDL_MOUSEBUTTONUP) {
498  return true;
499  }
500 
501  unsigned mods = sdl_get_mods();
502 
503  if (mods & KMOD_CTRL || mods & KMOD_ALT || mods & KMOD_GUI) {
504  return event.type == SDL_KEYUP;
505  } else {
506  return event.type == SDL_TEXTINPUT || event.type == SDL_KEYUP;
507  }
508 }
509 
510 }
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:509
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:92
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