The Battle for Wesnoth  1.19.0-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  ret += "alt";
59  }
60 
61  ret += (!ret.empty() && !boost::algorithm::ends_with(ret, "+") ? "+" : "");
62  if(mod_ & KMOD_SHIFT) {
63  ret += "shift";
64  }
65 
66  ret += (!ret.empty() && !boost::algorithm::ends_with(ret, "+") ? "+" : "");
67  if(mod_ & KMOD_GUI) {
68 #ifdef __APPLE__
69  ret += "cmd";
70 #else
71  ret += "win";
72 #endif
73  }
74 
75  ret += (!ret.empty() && !boost::algorithm::ends_with(ret, "+") ? "+" : "");
76  return ret += get_name_helper();
77 }
78 
80 {
81  if(other == nullptr) {
82  return false;
83  }
84 
85  const hk_scopes scopematch =
87  hotkey::get_hotkey_command(other->get_command()).scope;
88 
89  if(scopematch.none()) {
90  return false;
91  }
92 
93  return mod_ == other->mod_ && bindings_equal_helper(other);
94 }
95 
96 bool hotkey_base::matches(const SDL_Event& event) const
97 {
99  return false;
100  }
101 
102  return matches_helper(event);
103 }
104 
106 {
107  item["command"] = get_command();
108  item["disabled"] = is_disabled();
109 
110  item["shift"] = !!(mod_ & KMOD_SHIFT);
111  item["ctrl"] = !!(mod_ & KMOD_CTRL);
112  item["cmd"] = !!(mod_ & KMOD_GUI);
113  item["alt"] = !!(mod_ & KMOD_ALT);
114 
115  save_helper(item);
116 }
117 
118 hotkey_ptr create_hotkey(const std::string& id, const SDL_Event& event)
119 {
120  hotkey_ptr base = std::make_shared<hotkey_void>();
121  const hotkey_command& command = get_hotkey_command(id);
122  unsigned mods = sdl::get_mods();
123 
124  switch(event.type) {
125  case SDL_KEYUP: {
126  if(mods & KMOD_CTRL || mods & KMOD_ALT || mods & KMOD_GUI || CKey::is_uncomposable(event.key)
127  || command.toggle) {
128  auto keyboard = std::make_shared<hotkey_keyboard>();
129  base = std::dynamic_pointer_cast<hotkey_base>(keyboard);
130  SDL_Keycode code;
131  code = event.key.keysym.sym;
132  keyboard->set_keycode(code);
133  keyboard->set_text(SDL_GetKeyName(event.key.keysym.sym));
134  }
135  } break;
136 
137  case SDL_TEXTINPUT: {
138  if(command.toggle) {
139  return nullptr;
140  }
141  auto keyboard = std::make_shared<hotkey_keyboard>();
142  base = std::dynamic_pointer_cast<hotkey_base>(keyboard);
143  std::string text = std::string(event.text.text);
144  keyboard->set_text(text);
145  if(text == ":" || text == "`") {
146  mods = mods & ~KMOD_SHIFT;
147  }
148  } break;
149 
150  case SDL_MOUSEBUTTONUP: {
151  auto mouse = std::make_shared<hotkey_mouse>();
152  base = std::dynamic_pointer_cast<hotkey_base>(mouse);
153  mouse->set_button(event.button.button);
154  break;
155  }
156 
157  default:
158  ERR_G << "Trying to bind an unknown event type:" << event.type;
159  break;
160  }
161 
162  base->set_mods(mods);
163  base->set_command(id);
164  base->unset_default();
165 
166  return base;
167 }
168 
170 {
171  hotkey_ptr base = std::make_shared<hotkey_void>();
172 
173  const config::attribute_value& mouse_cfg = cfg["mouse"];
174  if(!mouse_cfg.empty()) {
175  auto mouse = std::make_shared<hotkey_mouse>();
176  base = std::dynamic_pointer_cast<hotkey_base>(mouse);
177  if(mouse_cfg.to_int() == TOUCH_MOUSE_INDEX) {
178  mouse->set_button(TOUCH_MOUSE_INDEX);
179  } else {
180  mouse->set_button(cfg["button"].to_int());
181  }
182  }
183 
184  const std::string& key_cfg = cfg["key"];
185  if(!key_cfg.empty()) {
186  auto keyboard = std::make_shared<hotkey_keyboard>();
187  base = std::dynamic_pointer_cast<hotkey_base>(keyboard);
188 
189  SDL_Keycode keycode = SDL_GetKeyFromName(key_cfg.c_str());
190  if(keycode == SDLK_UNKNOWN) {
191  ERR_G << "Unknown key: " << key_cfg;
192  }
193  keyboard->set_text(key_cfg);
194  keyboard->set_keycode(keycode);
195  }
196 
197  if(base == hotkey_ptr()) {
198  return base;
199  }
200 
201  unsigned int mods = 0;
202 
203  if(cfg["shift"].to_bool())
204  mods |= KMOD_SHIFT;
205  if(cfg["ctrl"].to_bool())
206  mods |= KMOD_CTRL;
207  if(cfg["cmd"].to_bool())
208  mods |= KMOD_GUI;
209  if(cfg["alt"].to_bool())
210  mods |= KMOD_ALT;
211 
212  base->set_mods(mods);
213  base->set_command(cfg["command"].str());
214 
215  cfg["disabled"].to_bool() ? base->disable() : base->enable();
216 
217  return base;
218 }
219 
220 bool hotkey_mouse::matches_helper(const SDL_Event& event) const
221 {
222  if(event.type != SDL_MOUSEBUTTONUP && event.type != SDL_MOUSEBUTTONDOWN && event.type != SDL_FINGERDOWN
223  && event.type != SDL_FINGERUP) {
224  return false;
225  }
226 
227  unsigned mods = sdl::get_mods();
228  if((mods != mod_)) {
229  return false;
230  }
231 
232  if(event.button.which == SDL_TOUCH_MOUSEID) {
233  return button_ == TOUCH_MOUSE_INDEX;
234  }
235 
236  return event.button.button == button_;
237 }
238 
239 const std::string hotkey_mouse::get_name_helper() const
240 {
241  return "mouse " + std::to_string(button_);
242 }
243 
245 {
246  item["mouse"] = 0;
247  if(button_ != 0) {
248  item["button"] = button_;
249  }
250 }
251 
252 void hotkey_keyboard::set_text(const std::string& text)
253 {
254  text_ = text;
255  boost::algorithm::to_lower(text_);
256 }
257 
258 const std::string hotkey_keyboard::get_name_helper() const
259 {
260  return text_;
261 }
262 
263 bool hotkey_keyboard::matches_helper(const SDL_Event& event) const
264 {
265  unsigned mods = sdl::get_mods();
266  const hotkey_command& command = get_hotkey_command(get_command());
267 
268  if((event.type == SDL_KEYDOWN || event.type == SDL_KEYUP)
269  && (mods & KMOD_CTRL || mods & KMOD_ALT || mods & KMOD_GUI || command.toggle || CKey::is_uncomposable(event.key))
270  ) {
271  return event.key.keysym.sym == keycode_ && mods == mod_;
272  }
273 
274  if(event.type == SDL_TEXTINPUT && !command.toggle) {
275  std::string text = std::string(event.text.text);
276  boost::algorithm::to_lower(text);
277  if(text == ":" || text == "`") {
278  mods = mods & ~KMOD_SHIFT;
279  }
280  return text_ == text && utf8::size(std::string(event.text.text)) == 1 && mods == mod_;
281  }
282 
283  return false;
284 }
285 
287 {
288  auto other_m = std::dynamic_pointer_cast<hotkey_mouse>(other);
289  if(other_m == nullptr) {
290  return false;
291  }
292 
293  return button_ == other_m->button_;
294 }
295 
297 {
298  if(!text_.empty()) {
299  item["key"] = text_;
300  }
301 }
302 
303 bool has_hotkey_item(const std::string& command)
304 {
305  for(const hotkey_ptr& item : hotkeys_) {
306  if(item->get_command() == command) {
307  return true;
308  }
309  }
310  return false;
311 }
312 
314 {
315  auto other_k = std::dynamic_pointer_cast<hotkey_keyboard>(other);
316  if(other_k == nullptr) {
317  return false;
318  }
319 
320  return text_ == other_k->text_;
321 }
322 
324 {
325  if(!hotkeys_.empty()) {
326  hotkeys_.erase(std::remove(hotkeys_.begin(), hotkeys_.end(), item));
327  }
328 }
329 
331 {
332  if(item) {
333  auto iter = std::find_if(hotkeys_.begin(), hotkeys_.end(),
334  [&item](const hotkey::hotkey_ptr& hk) { return hk->bindings_equal(item); });
335 
336  if(iter != hotkeys_.end()) {
337  iter->swap(item);
338  } else {
339  hotkeys_.push_back(std::move(item));
340  }
341  }
342 }
343 
344 void clear_hotkeys(const std::string& command)
345 {
346  for(hotkey::hotkey_ptr& item : hotkeys_) {
347  if(item->get_command() == command) {
348  if(item->is_default()) {
349  item->disable();
350  } else {
351  item->clear();
352  }
353  }
354  }
355 }
356 
358 {
359  hotkeys_.clear();
360 }
361 
362 const hotkey_ptr get_hotkey(const SDL_Event& event)
363 {
364  for(const hotkey_ptr& item : hotkeys_) {
365  if(item->matches(event)) {
366  return item;
367  }
368  }
369  return std::make_shared<hotkey_void>();
370 }
371 
373 {
374  hotkey_list new_hotkeys;
375  for(const config& hk : cfg.child_range("hotkey")) {
376  if(hotkey_ptr item = load_from_config(hk); !item->null()) {
377  new_hotkeys.push_back(std::move(item));
378  }
379  }
380 
381  default_hotkey_cfg_ = cfg;
382  hotkeys_.swap(new_hotkeys);
383 }
384 
386 {
387  for(const config& hk : cfg.child_range("hotkey")) {
388  if(hotkey_ptr item = load_from_config(hk); !item->null()) {
389  item->unset_default();
390  add_hotkey(item);
391  }
392  }
393 }
394 
396 {
397  hotkeys_.clear();
398 
399  if(!default_hotkey_cfg_.child_range("hotkey").empty()) {
400  load_default_hotkeys(default_hotkey_cfg_);
401  } else {
402  ERR_G << "no default hotkeys set yet; all hotkeys are now unassigned!";
403  }
404 }
405 
407 {
408  return hotkeys_;
409 }
410 
412 {
413  cfg.clear_children("hotkey");
414 
415  for(hotkey_ptr& item : hotkeys_) {
416  if((!item->is_default() && item->active()) || (item->is_default() && item->is_disabled())) {
417  item->save(cfg.add_child("hotkey"));
418  }
419  }
420 }
421 
422 std::string get_names(const std::string& id)
423 {
424  // Names are used in places like the hot-key preferences menu
425  std::vector<std::string> names;
426  for(const hotkey::hotkey_ptr& item : hotkeys_) {
427  if(item->get_command() == id && !item->null() && !item->is_disabled()) {
428  names.push_back(item->get_name());
429  }
430  }
431 
432  // These are hard-coded, non-rebindable hotkeys
433  if(id == "quit") {
434  names.push_back("escape");
435  } else if(id == "quit-to-desktop") {
436 #ifdef __APPLE__
437  names.push_back("cmd+q");
438 #else
439  names.push_back("alt+F4");
440 #endif
441  }
442 
443  return boost::algorithm::join(names, ", ");
444 }
445 
446 bool is_hotkeyable_event(const SDL_Event& event)
447 {
448  if(event.type == SDL_JOYBUTTONUP || event.type == SDL_JOYHATMOTION || event.type == SDL_MOUSEBUTTONUP) {
449  return true;
450  }
451 
452  unsigned mods = sdl::get_mods();
453 
454  if(mods & KMOD_CTRL || mods & KMOD_ALT || mods & KMOD_GUI) {
455  return event.type == SDL_KEYUP;
456  } else {
457  return event.type == SDL_TEXTINPUT || event.type == SDL_KEYUP;
458  }
459 }
460 
461 } // namespace hotkey
std::vector< std::string > names
Definition: build_info.cpp:66
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:159
void clear_children(T... keys)
Definition: config.hpp:642
config & add_child(config_key_type key)
Definition: config.cpp:441
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:
Definition: hotkey_item.cpp:96
virtual bool bindings_equal(hotkey_ptr other)
Checks whether the hotkey bindings and scope are equal.
Definition: hotkey_item.cpp:79
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.
#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).
bool ends_with(const std::string &str, const std::string &suffix)
void remove()
Removes a tip.
Definition: tooltip.cpp:109
std::pair< std::string, unsigned > item
Definition: help_impl.hpp:412
Keyboard shortcuts for game actions.
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.
const hotkey_command & get_hotkey_command(const std::string &command)
returns the hotkey_command with the given name
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.