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