The Battle for Wesnoth  1.19.4+dev
hotkey_item.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2024
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 #define GETTEXT_DOMAIN "wesnoth-lib"
17 
18 #include "hotkey/hotkey_item.hpp"
19 
20 #include "config.hpp"
21 #include "game_config_view.hpp"
23 #include "key.hpp"
24 #include "log.hpp"
25 #include "sdl/input.hpp" // for sdl::get_mods
27 
28 #include <boost/algorithm/string.hpp>
29 
30 #include <functional>
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 {
40 namespace
41 {
42 hotkey_list hotkeys_;
43 game_config_view default_hotkey_cfg_;
44 
45 const int TOUCH_MOUSE_INDEX = 255;
46 }; // namespace
47 
48 const std::string hotkey_base::get_name() const
49 {
50  std::string ret = "";
51 
52  if(mod_ & KMOD_CTRL) {
53  ret += "ctrl";
54  }
55 
56  ret += (!ret.empty() && !boost::algorithm::ends_with(ret, "+") ? "+" : "");
57  if(mod_ & KMOD_ALT) {
58 #ifdef __APPLE__
59  ret += "opt";
60 #else
61  ret += "alt";
62 #endif
63  }
64 
65  ret += (!ret.empty() && !boost::algorithm::ends_with(ret, "+") ? "+" : "");
66  if(mod_ & KMOD_SHIFT) {
67  ret += "shift";
68  }
69 
70  ret += (!ret.empty() && !boost::algorithm::ends_with(ret, "+") ? "+" : "");
71  if(mod_ & KMOD_GUI) {
72 #ifdef __APPLE__
73  ret += "cmd";
74 #else
75  ret += "win";
76 #endif
77  }
78 
79  ret += (!ret.empty() && !boost::algorithm::ends_with(ret, "+") ? "+" : "");
80  return ret += get_name_helper();
81 }
82 
84 {
85  if(other == nullptr) {
86  return false;
87  }
88 
89  const hk_scopes scopematch =
91  hotkey::get_hotkey_command(other->get_command()).scope;
92 
93  if(scopematch.none()) {
94  return false;
95  }
96 
97  return mod_ == other->mod_ && bindings_equal_helper(other);
98 }
99 
100 bool hotkey_base::matches(const SDL_Event& event) const
101 {
103  return false;
104  }
105 
106  return matches_helper(event);
107 }
108 
110 {
111  item["command"] = get_command();
112  item["disabled"] = is_disabled();
113 
114  item["shift"] = !!(mod_ & KMOD_SHIFT);
115  item["ctrl"] = !!(mod_ & KMOD_CTRL);
116  item["cmd"] = !!(mod_ & KMOD_GUI);
117  item["alt"] = !!(mod_ & KMOD_ALT);
118 
119  save_helper(item);
120 }
121 
122 hotkey_ptr create_hotkey(const std::string& id, const SDL_Event& event)
123 {
124  hotkey_ptr base = std::make_shared<hotkey_void>();
125  const hotkey_command& command = get_hotkey_command(id);
126  unsigned mods = sdl::get_mods();
127 
128  switch(event.type) {
129  case SDL_KEYUP: {
130  if(mods & KMOD_CTRL || mods & KMOD_ALT || mods & KMOD_GUI || CKey::is_uncomposable(event.key)
131  || command.toggle) {
132  auto keyboard = std::make_shared<hotkey_keyboard>();
133  base = std::dynamic_pointer_cast<hotkey_base>(keyboard);
134  SDL_Keycode code;
135  code = event.key.keysym.sym;
136  keyboard->set_keycode(code);
137  keyboard->set_text(SDL_GetKeyName(event.key.keysym.sym));
138  }
139  } break;
140 
141  case SDL_TEXTINPUT: {
142  if(command.toggle) {
143  return nullptr;
144  }
145  auto keyboard = std::make_shared<hotkey_keyboard>();
146  base = std::dynamic_pointer_cast<hotkey_base>(keyboard);
147  std::string text = std::string(event.text.text);
148  keyboard->set_text(text);
149  if(text == ":" || text == "`") {
150  mods = mods & ~KMOD_SHIFT;
151  }
152  } break;
153 
154  case SDL_MOUSEBUTTONUP: {
155  auto mouse = std::make_shared<hotkey_mouse>();
156  base = std::dynamic_pointer_cast<hotkey_base>(mouse);
157  mouse->set_button(event.button.button);
158  break;
159  }
160 
161  default:
162  ERR_G << "Trying to bind an unknown event type:" << event.type;
163  break;
164  }
165 
166  base->set_mods(mods);
167  base->set_command(id);
168  base->unset_default();
169 
170  return base;
171 }
172 
174 {
175  hotkey_ptr base = std::make_shared<hotkey_void>();
176 
177  const config::attribute_value& mouse_cfg = cfg["mouse"];
178  if(!mouse_cfg.empty()) {
179  auto mouse = std::make_shared<hotkey_mouse>();
180  base = std::dynamic_pointer_cast<hotkey_base>(mouse);
181  if(mouse_cfg.to_int() == TOUCH_MOUSE_INDEX) {
182  mouse->set_button(TOUCH_MOUSE_INDEX);
183  } else {
184  mouse->set_button(cfg["button"].to_int());
185  }
186  }
187 
188  const std::string& key_cfg = cfg["key"];
189  if(!key_cfg.empty()) {
190  auto keyboard = std::make_shared<hotkey_keyboard>();
191  base = std::dynamic_pointer_cast<hotkey_base>(keyboard);
192 
193  SDL_Keycode keycode = SDL_GetKeyFromName(key_cfg.c_str());
194  if(keycode == SDLK_UNKNOWN) {
195  ERR_G << "Unknown key: " << key_cfg;
196  }
197  keyboard->set_text(key_cfg);
198  keyboard->set_keycode(keycode);
199  }
200 
201  if(base == hotkey_ptr()) {
202  return base;
203  }
204 
205  unsigned int mods = 0;
206 
207  if(cfg["shift"].to_bool())
208  mods |= KMOD_SHIFT;
209  if(cfg["ctrl"].to_bool())
210  mods |= KMOD_CTRL;
211  if(cfg["cmd"].to_bool())
212  mods |= KMOD_GUI;
213  if(cfg["alt"].to_bool())
214  mods |= KMOD_ALT;
215 
216  base->set_mods(mods);
217  base->set_command(cfg["command"].str());
218 
219  cfg["disabled"].to_bool() ? base->disable() : base->enable();
220 
221  return base;
222 }
223 
224 bool hotkey_mouse::matches_helper(const SDL_Event& event) const
225 {
226  if(event.type != SDL_MOUSEBUTTONUP && event.type != SDL_MOUSEBUTTONDOWN && event.type != SDL_FINGERDOWN
227  && event.type != SDL_FINGERUP) {
228  return false;
229  }
230 
231  unsigned mods = sdl::get_mods();
232  if((mods != mod_)) {
233  return false;
234  }
235 
236  if(event.button.which == SDL_TOUCH_MOUSEID) {
237  return button_ == TOUCH_MOUSE_INDEX;
238  }
239 
240  return event.button.button == button_;
241 }
242 
243 const std::string hotkey_mouse::get_name_helper() const
244 {
245  return "mouse " + std::to_string(button_);
246 }
247 
249 {
250  item["mouse"] = 0;
251  if(button_ != 0) {
252  item["button"] = button_;
253  }
254 }
255 
256 void hotkey_keyboard::set_text(const std::string& text)
257 {
258  text_ = text;
259  boost::algorithm::to_lower(text_);
260 }
261 
262 const std::string hotkey_keyboard::get_name_helper() const
263 {
264  return text_;
265 }
266 
267 bool hotkey_keyboard::matches_helper(const SDL_Event& event) const
268 {
269  unsigned mods = sdl::get_mods();
270  const hotkey_command& command = get_hotkey_command(get_command());
271 
272  if((event.type == SDL_KEYDOWN || event.type == SDL_KEYUP)
273  && (mods & KMOD_CTRL || mods & KMOD_ALT || mods & KMOD_GUI || command.toggle || CKey::is_uncomposable(event.key))
274  ) {
275  return event.key.keysym.sym == keycode_ && mods == mod_;
276  }
277 
278  if(event.type == SDL_TEXTINPUT && !command.toggle) {
279  std::string text = std::string(event.text.text);
280  boost::algorithm::to_lower(text);
281  if(text == ":" || text == "`") {
282  mods = mods & ~KMOD_SHIFT;
283  }
284  return text_ == text && utf8::size(std::string(event.text.text)) == 1 && mods == mod_;
285  }
286 
287  return false;
288 }
289 
291 {
292  auto other_m = std::dynamic_pointer_cast<hotkey_mouse>(other);
293  if(other_m == nullptr) {
294  return false;
295  }
296 
297  return button_ == other_m->button_;
298 }
299 
301 {
302  if(!text_.empty()) {
303  item["key"] = text_;
304  }
305 }
306 
307 bool has_hotkey_item(const std::string& command)
308 {
309  for(const hotkey_ptr& item : hotkeys_) {
310  if(item->get_command() == command) {
311  return true;
312  }
313  }
314  return false;
315 }
316 
318 {
319  auto other_k = std::dynamic_pointer_cast<hotkey_keyboard>(other);
320  if(other_k == nullptr) {
321  return false;
322  }
323 
324  return text_ == other_k->text_;
325 }
326 
328 {
329  if(!hotkeys_.empty()) {
330  hotkeys_.erase(std::remove(hotkeys_.begin(), hotkeys_.end(), item));
331  }
332 }
333 
335 {
336  if(item) {
337  auto iter = std::find_if(hotkeys_.begin(), hotkeys_.end(),
338  [&item](const hotkey::hotkey_ptr& hk) { return hk->bindings_equal(item); });
339 
340  if(iter != hotkeys_.end()) {
341  iter->swap(item);
342  } else {
343  hotkeys_.push_back(std::move(item));
344  }
345  }
346 }
347 
348 void clear_hotkeys(const std::string& command)
349 {
350  for(hotkey::hotkey_ptr& item : hotkeys_) {
351  if(item->get_command() == command) {
352  if(item->is_default()) {
353  item->disable();
354  } else {
355  item->clear();
356  }
357  }
358  }
359 }
360 
362 {
363  hotkeys_.clear();
364 }
365 
366 const hotkey_ptr get_hotkey(const SDL_Event& event)
367 {
368  for(const hotkey_ptr& item : hotkeys_) {
369  if(item->matches(event)) {
370  return item;
371  }
372  }
373  return std::make_shared<hotkey_void>();
374 }
375 
377 {
378  hotkey_list new_hotkeys;
379  for(const config& hk : cfg.child_range("hotkey")) {
380  if(hotkey_ptr item = load_from_config(hk); !item->null()) {
381  new_hotkeys.push_back(std::move(item));
382  }
383  }
384 
385  default_hotkey_cfg_ = cfg;
386  hotkeys_.swap(new_hotkeys);
387 }
388 
390 {
391  for(const config& hk : cfg.child_range("hotkey")) {
392  if(hotkey_ptr item = load_from_config(hk); !item->null()) {
393  item->unset_default();
394  add_hotkey(item);
395  }
396  }
397 }
398 
400 {
401  hotkeys_.clear();
402 
403  if(!default_hotkey_cfg_.child_range("hotkey").empty()) {
404  load_default_hotkeys(default_hotkey_cfg_);
405  } else {
406  ERR_G << "no default hotkeys set yet; all hotkeys are now unassigned!";
407  }
408 }
409 
411 {
412  return hotkeys_;
413 }
414 
416 {
417  cfg.clear_children("hotkey");
418 
419  for(hotkey_ptr& item : hotkeys_) {
420  if((!item->is_default() && item->active()) || (item->is_default() && item->is_disabled())) {
421  item->save(cfg.add_child("hotkey"));
422  }
423  }
424 }
425 
426 std::string get_names(const std::string& id)
427 {
428  // Names are used in places like the hot-key preferences menu
429  std::vector<std::string> names;
430  for(const hotkey::hotkey_ptr& item : hotkeys_) {
431  if(item->get_command() == id && !item->null() && !item->is_disabled()) {
432  names.push_back(item->get_name());
433  }
434  }
435 
436  // These are hard-coded, non-rebindable hotkeys
437  if(id == "quit") {
438  names.push_back("escape");
439  } else if(id == "quit-to-desktop") {
440 #ifdef __APPLE__
441  names.push_back("cmd+q");
442 #else
443  names.push_back("alt+F4");
444 #endif
445  }
446 
447  return boost::algorithm::join(names, ", ");
448 }
449 
450 bool is_hotkeyable_event(const SDL_Event& event)
451 {
452  if(event.type == SDL_JOYBUTTONUP || event.type == SDL_JOYHATMOTION || event.type == SDL_MOUSEBUTTONUP) {
453  return true;
454  }
455 
456  unsigned mods = sdl::get_mods();
457 
458  if(mods & KMOD_CTRL || mods & KMOD_ALT || mods & KMOD_GUI) {
459  return event.type == SDL_KEYUP;
460  } else {
461  return event.type == SDL_TEXTINPUT || event.type == SDL_KEYUP;
462  }
463 }
464 
465 } // namespace hotkey
std::vector< std::string > names
Definition: build_info.cpp:67
static bool is_uncomposable(const SDL_KeyboardEvent &event)
Definition: key.cpp:28
Variant for storing WML attributes.
bool empty() const
Tests for an attribute that either was never set or was set to "".
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:163
void clear_children(T... keys)
Definition: config.hpp:607
config & add_child(config_key_type key)
Definition: config.cpp:439
A class grating read only view to a vector of config objects, viewed as one config with all children ...
config_array_view child_range(config_key_type key) const
bool is_disabled() const
virtual void save_helper(config &cfg) const =0
const std::string & get_command() const
Returns the string name of the HOTKEY_COMMAND.
Definition: hotkey_item.hpp:63
void save(config &cfg) const
Save the hotkey into the configuration object.
bool active() 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.
bool matches(const SDL_Event &event) const
Used to evaluate whether:
virtual bool bindings_equal(hotkey_ptr other)
Checks whether the hotkey bindings and scope are equal.
Definition: hotkey_item.cpp:83
virtual const std::string get_name_helper() const =0
This is invoked by hotkey_base::get_name and must be implemented by subclasses.
const std::string get_name() const
Return "name" of hotkey.
Definition: hotkey_item.cpp:48
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_text(const std::string &text)
virtual void save_helper(config &cfg) const
virtual const std::string get_name_helper() const
This is invoked by hotkey_base::get_name and must be implemented by subclasses.
virtual bool bindings_equal_helper(hotkey_ptr other) const
This is invoked by hotkey_base::bindings_equal as a helper for the concrete classes.
virtual bool matches_helper(const SDL_Event &event) const
This is invoked by hotkey_base::matches as a helper for the concrete classes.
virtual const std::string get_name_helper() const
This is invoked by hotkey_base::get_name and must be implemented by subclasses.
virtual void save_helper(config &cfg) const
virtual bool matches_helper(const SDL_Event &event) const
This is invoked by hotkey_base::matches as a helper for the concrete classes.
virtual bool bindings_equal_helper(hotkey_ptr other) const
This is invoked by hotkey_base::bindings_equal as a helper for the concrete classes.
Definitions for the interface to Wesnoth Markup Language (WML).
#define ERR_G
Definition: hotkey_item.cpp:33
static lg::log_domain log_config("config")
Contains functions for cleanly handling SDL input.
Standard logging facilities (interface).
void remove()
Removes a tip.
Definition: tooltip.cpp:95
std::pair< std::string, unsigned > item
Definition: help_impl.hpp:411
Keyboard shortcuts for game actions.
const hotkey_command & get_hotkey_command(const std::string &command)
returns the hotkey_command with the given name
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.
void save_hotkeys(config &cfg)
Save the non-default hotkeys to the config.
std::shared_ptr< class hotkey_base > hotkey_ptr
Definition: hotkey_item.hpp:27
bool has_hotkey_item(const std::string &command)
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...
void clear_hotkeys(const std::string &command)
Unset the command bindings for all hotkeys matching the command.
void reset_default_hotkeys()
Reset all hotkeys to the defaults.
hotkey_ptr load_from_config(const config &cfg)
Create and instantiate a hotkey from a config element.
hotkey_ptr create_hotkey(const std::string &id, const SDL_Event &event)
Create a new hotkey item for a command from an SDL_Event.
std::bitset< SCOPE_COUNT > hk_scopes
void del_hotkey(hotkey_ptr item)
Remove a hotkey from the list of hotkeys.
std::vector< hotkey::hotkey_ptr > hotkey_list
Definition: hotkey_item.hpp:31
void load_custom_hotkeys(const game_config_view &cfg)
Registers all hotkeys present in this config, overwriting any matching default hotkeys.
bool is_scope_active(scope s)
scope
Available hotkey scopes.
void load_default_hotkeys(const game_config_view &cfg)
Registers all hotkeys present in this config.
void add_hotkey(hotkey_ptr item)
Add a hotkey to the list of hotkeys.
bool is_hotkeyable_event(const SDL_Event &event)
unsigned get_mods()
Returns a bitmask of active modifier keys (ctrl, shift, alt, gui).
Definition: input.cpp:61
std::size_t size(const std::string &str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:85
std::string join(const T &v, const std::string &s=",")
Generates a new string joining container items in a list.
This file contains object "key", which is used to store information about keys while annotation parsi...
Stores all information related to functions that can be bound to hotkeys.
bool toggle
Toggle hotkeys have some restrictions on what can be bound to them.
hk_scopes scope
The visibility scope of the command.