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