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  break;
161  }
162 
163  default:
164  ERR_G << "Trying to bind an unknown event type:" << event.type;
165  break;
166  }
167 
168  base->set_mods(mods);
169  base->set_command(id);
170  base->unset_default();
171 
172  return base;
173 }
174 
176 {
177  hotkey_ptr base = std::make_shared<hotkey_void>();
178 
179  const config::attribute_value& mouse_cfg = cfg["mouse"];
180  if(!mouse_cfg.empty()) {
181  auto mouse = std::make_shared<hotkey_mouse>();
182  base = std::dynamic_pointer_cast<hotkey_base>(mouse);
183  if(mouse_cfg.to_int() == TOUCH_MOUSE_INDEX) {
184  mouse->set_button(TOUCH_MOUSE_INDEX);
185  } else {
186  mouse->set_button(cfg["button"].to_int());
187  }
188  }
189 
190  const std::string& key_cfg = cfg["key"];
191  if(!key_cfg.empty()) {
192  auto keyboard = std::make_shared<hotkey_keyboard>();
193  base = std::dynamic_pointer_cast<hotkey_base>(keyboard);
194 
195  SDL_Keycode keycode = SDL_GetKeyFromName(key_cfg.c_str());
196  if(keycode == SDLK_UNKNOWN) {
197  ERR_G << "Unknown key: " << key_cfg;
198  }
199  keyboard->set_text(key_cfg);
200  keyboard->set_keycode(keycode);
201  }
202 
203  if(base == hotkey_ptr()) {
204  return base;
205  }
206 
207  unsigned int mods = 0;
208 
209  if(cfg["shift"].to_bool())
210  mods |= KMOD_SHIFT;
211  if(cfg["ctrl"].to_bool())
212  mods |= KMOD_CTRL;
213  if(cfg["cmd"].to_bool())
214  mods |= KMOD_GUI;
215  if(cfg["alt"].to_bool())
216  mods |= KMOD_ALT;
217 
218  base->set_mods(mods);
219  base->set_command(cfg["command"].str());
220 
221  cfg["disabled"].to_bool() ? base->disable() : base->enable();
222 
223  return base;
224 }
225 
226 bool hotkey_mouse::matches_helper(const SDL_Event& event) const
227 {
228  if(event.type != SDL_MOUSEBUTTONUP && event.type != SDL_MOUSEBUTTONDOWN && event.type != SDL_FINGERDOWN
229  && event.type != SDL_FINGERUP) {
230  return false;
231  }
232 
233  unsigned mods = sdl::get_mods();
234  if((mods != mod_)) {
235  return false;
236  }
237 
238  if(events::is_touch(event.button)) {
239  return button_ == TOUCH_MOUSE_INDEX;
240  }
241 
242  return event.button.button == button_;
243 }
244 
245 const std::string hotkey_mouse::get_name_helper() const
246 {
247  return "mouse " + std::to_string(button_);
248 }
249 
251 {
252  item["mouse"] = 0;
253  if(button_ != 0) {
254  item["button"] = button_;
255  }
256 }
257 
258 void hotkey_keyboard::set_text(const std::string& text)
259 {
260  text_ = text;
261  boost::algorithm::to_lower(text_);
262 }
263 
264 const std::string hotkey_keyboard::get_name_helper() const
265 {
266  return text_;
267 }
268 
269 bool hotkey_keyboard::matches_helper(const SDL_Event& event) const
270 {
271  unsigned mods = sdl::get_mods();
272  const hotkey_command& command = get_hotkey_command(get_command());
273 
274  if((event.type == SDL_KEYDOWN || event.type == SDL_KEYUP)
275  && (mods & KMOD_CTRL || mods & KMOD_ALT || mods & KMOD_GUI || command.toggle || CKey::is_uncomposable(event.key))
276  ) {
277  return event.key.keysym.sym == keycode_ && mods == mod_;
278  }
279 
280  if(event.type == SDL_TEXTINPUT && !command.toggle) {
281  std::string text = std::string(event.text.text);
282  boost::algorithm::to_lower(text);
283  if(text == ":" || text == "`") {
284  mods = mods & ~KMOD_SHIFT;
285  }
286  return text_ == text && utf8::size(std::string(event.text.text)) == 1 && mods == mod_;
287  }
288 
289  return false;
290 }
291 
293 {
294  auto other_m = std::dynamic_pointer_cast<hotkey_mouse>(other);
295  if(other_m == nullptr) {
296  return false;
297  }
298 
299  return button_ == other_m->button_;
300 }
301 
303 {
304  if(!text_.empty()) {
305  item["key"] = text_;
306  }
307 }
308 
309 bool has_hotkey_item(const std::string& command)
310 {
311  for(const hotkey_ptr& item : hotkeys_) {
312  if(item->get_command() == command) {
313  return true;
314  }
315  }
316  return false;
317 }
318 
320 {
321  auto other_k = std::dynamic_pointer_cast<hotkey_keyboard>(other);
322  if(other_k == nullptr) {
323  return false;
324  }
325 
326  return text_ == other_k->text_;
327 }
328 
330 {
331  if(item) {
332  auto iter = std::find_if(hotkeys_.begin(), hotkeys_.end(),
333  [&item](const hotkey::hotkey_ptr& hk) { return hk->bindings_equal(item); });
334 
335  if(iter != hotkeys_.end()) {
336  iter->swap(item);
337  } else {
338  hotkeys_.push_back(std::move(item));
339  }
340  }
341 }
342 
343 void clear_hotkeys(const std::string& command)
344 {
345  for(hotkey::hotkey_ptr& item : hotkeys_) {
346  if(item->get_command() == command) {
347  if(item->is_default()) {
348  item->disable();
349  } else {
350  item->clear();
351  }
352  }
353  }
354 }
355 
357 {
358  hotkeys_.clear();
359 }
360 
361 const hotkey_ptr get_hotkey(const SDL_Event& event)
362 {
363  for(const hotkey_ptr& item : hotkeys_) {
364  if(item->matches(event)) {
365  return item;
366  }
367  }
368  return std::make_shared<hotkey_void>();
369 }
370 
372 {
373  hotkey_list new_hotkeys;
374  for(const config& hk : cfg.child_range("hotkey")) {
375  if(hotkey_ptr item = load_from_config(hk); !item->null()) {
376  new_hotkeys.push_back(std::move(item));
377  }
378  }
379 
380  default_hotkey_cfg_ = cfg;
381  hotkeys_.swap(new_hotkeys);
382 }
383 
385 {
386  for(const config& hk : cfg.child_range("hotkey")) {
387  if(hotkey_ptr item = load_from_config(hk); !item->null()) {
388  item->unset_default();
389  add_hotkey(item);
390  }
391  }
392 }
393 
395 {
396  hotkeys_.clear();
397 
398  if(!default_hotkey_cfg_.child_range("hotkey").empty()) {
399  load_default_hotkeys(default_hotkey_cfg_);
400  } else {
401  ERR_G << "no default hotkeys set yet; all hotkeys are now unassigned!";
402  }
403 }
404 
406 {
407  return hotkeys_;
408 }
409 
411 {
412  cfg.clear_children("hotkey");
413 
414  for(hotkey_ptr& item : hotkeys_) {
415  if((!item->is_default() && item->active()) || (item->is_default() && item->is_disabled())) {
416  item->save(cfg.add_child("hotkey"));
417  }
418  }
419 }
420 
421 std::string get_names(const std::string& id)
422 {
423  // Names are used in places like the hot-key preferences menu
424  std::vector<std::string> names;
425  for(const hotkey::hotkey_ptr& item : hotkeys_) {
426  if(item->get_command() == id && !item->null() && !item->is_disabled()) {
427  names.push_back(item->get_name());
428  }
429  }
430 
431  // These are hard-coded, non-rebindable hotkeys
432  if(id == "quit") {
433  names.push_back("escape");
434  } else if(id == "quit-to-desktop") {
435 #ifdef __APPLE__
436  names.push_back("cmd+q");
437 #else
438  names.push_back("alt+F4");
439 #endif
440  }
441 
442  return boost::algorithm::join(names, ", ");
443 }
444 
445 bool is_hotkeyable_event(const SDL_Event& event)
446 {
447  if(event.type == SDL_JOYBUTTONUP || event.type == SDL_JOYHATMOTION || event.type == SDL_MOUSEBUTTONUP) {
448  return true;
449  }
450 
451  unsigned mods = sdl::get_mods();
452 
453  if(mods & KMOD_CTRL || mods & KMOD_ALT || mods & KMOD_GUI) {
454  return event.type == SDL_KEYUP;
455  } else {
456  return event.type == SDL_TEXTINPUT || event.type == SDL_KEYUP;
457  }
458 }
459 
460 } // 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
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 ...
config_array_view child_range(config_key_type key) const
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).
#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.