2 * This file is part of OpenTTD.
3 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
8 /** @file hotkeys.cpp Implementation of hotkey related functions */
14 #include "string_func.h"
15 #include "window_gui.h"
17 #include "safeguards.h"
22 * List of all HotkeyLists.
23 * This is a pointer to ensure initialisation order with the various static HotkeyList instances.
25 static std::vector
<HotkeyList
*> *_hotkey_lists
= nullptr;
27 /** String representation of a keycode */
29 const char *name
; ///< Name of the keycode
30 WindowKeyCodes keycode
; ///< The keycode
33 /** Array of non-standard keycodes that can be used in the hotkeys config file. */
34 static const KeycodeNames _keycode_to_name
[] = {
39 {"GLOBAL", WKC_GLOBAL_HOTKEY
},
41 {"BACKSPACE", WKC_BACKSPACE
},
44 {"PAGEUP", WKC_PAGEUP
},
45 {"PAGEDOWN", WKC_PAGEDOWN
},
48 {"RETURN", WKC_RETURN
},
62 {"BACKQUOTE", WKC_BACKQUOTE
},
64 {"NUM_DIV", WKC_NUM_DIV
},
65 {"NUM_MUL", WKC_NUM_MUL
},
66 {"NUM_MINUS", WKC_NUM_MINUS
},
67 {"NUM_PLUS", WKC_NUM_PLUS
},
68 {"NUM_ENTER", WKC_NUM_ENTER
},
69 {"NUM_DOT", WKC_NUM_DECIMAL
},
74 {"\\", WKC_BACKSLASH
},
76 {"'", WKC_SINGLEQUOTE
},
78 {"COMMA", WKC_COMMA
}, // legacy variant, should be below ","
84 * Try to parse a single part of a keycode.
85 * @param start Start of the string to parse.
86 * @param end End of the string to parse.
87 * @return A keycode if a match is found or 0.
89 static uint16
ParseCode(const char *start
, const char *end
)
92 while (start
< end
&& *start
== ' ') start
++;
93 while (end
> start
&& *end
== ' ') end
--;
94 for (uint i
= 0; i
< lengthof(_keycode_to_name
); i
++) {
95 if (strlen(_keycode_to_name
[i
].name
) == (size_t)(end
- start
) && strncasecmp(start
, _keycode_to_name
[i
].name
, end
- start
) == 0) {
96 return _keycode_to_name
[i
].keycode
;
99 if (end
- start
== 1) {
100 if (*start
>= 'a' && *start
<= 'z') return *start
- ('a'-'A');
101 /* Ignore invalid keycodes */
102 if (*(const uint8
*)start
< 128) return *start
;
108 * Parse a string representation of a keycode.
109 * @param start Start of the input.
110 * @param end End of the input.
111 * @return A valid keycode or 0.
113 static uint16
ParseKeycode(const char *start
, const char *end
)
115 assert(start
<= end
);
118 const char *cur
= start
;
119 while (*cur
!= '+' && cur
!= end
) cur
++;
120 uint16 code
= ParseCode(start
, cur
);
121 if (code
== 0) return 0;
122 if (code
& WKC_SPECIAL_KEYS
) {
123 /* Some completely wrong keycode we don't support. */
124 if (code
& ~WKC_SPECIAL_KEYS
) return 0;
127 /* Ignore the code if it has more then 1 letter. */
128 if (keycode
& ~WKC_SPECIAL_KEYS
) return 0;
131 if (cur
== end
) break;
139 * Parse a string to the keycodes it represents
140 * @param hotkey The hotkey object to add the keycodes to
141 * @param value The string to parse
143 static void ParseHotkeys(Hotkey
*hotkey
, const char *value
)
145 const char *start
= value
;
146 while (*start
!= '\0') {
147 const char *end
= start
;
148 while (*end
!= '\0' && *end
!= ',') end
++;
149 uint16 keycode
= ParseKeycode(start
, end
);
150 if (keycode
!= 0) hotkey
->AddKeycode(keycode
);
151 start
= (*end
== ',') ? end
+ 1: end
;
156 * Convert a hotkey to it's string representation so it can be written to the
157 * config file. Separate parts of the keycode (like "CTRL" and "F1" are split
159 * @param keycode The keycode to convert to a string.
160 * @return A string representation of this keycode.
161 * @note The return value is a static buffer, stredup the result before calling
162 * this function again.
164 static const char *KeycodeToString(uint16 keycode
)
169 if (keycode
& WKC_GLOBAL_HOTKEY
) {
170 strecat(buf
, "GLOBAL", lastof(buf
));
173 if (keycode
& WKC_SHIFT
) {
174 if (!first
) strecat(buf
, "+", lastof(buf
));
175 strecat(buf
, "SHIFT", lastof(buf
));
178 if (keycode
& WKC_CTRL
) {
179 if (!first
) strecat(buf
, "+", lastof(buf
));
180 strecat(buf
, "CTRL", lastof(buf
));
183 if (keycode
& WKC_ALT
) {
184 if (!first
) strecat(buf
, "+", lastof(buf
));
185 strecat(buf
, "ALT", lastof(buf
));
188 if (keycode
& WKC_META
) {
189 if (!first
) strecat(buf
, "+", lastof(buf
));
190 strecat(buf
, "META", lastof(buf
));
193 if (!first
) strecat(buf
, "+", lastof(buf
));
194 keycode
= keycode
& ~WKC_SPECIAL_KEYS
;
196 for (uint i
= 0; i
< lengthof(_keycode_to_name
); i
++) {
197 if (_keycode_to_name
[i
].keycode
== keycode
) {
198 strecat(buf
, _keycode_to_name
[i
].name
, lastof(buf
));
202 assert(keycode
< 128);
206 strecat(buf
, key
, lastof(buf
));
211 * Convert all keycodes attached to a hotkey to a single string. If multiple
212 * keycodes are attached to the hotkey they are split by a comma.
213 * @param hotkey The keycodes of this hotkey need to be converted to a string.
214 * @return A string representation of all keycodes.
215 * @note The return value is a static buffer, stredup the result before calling
216 * this function again.
218 const char *SaveKeycodes(const Hotkey
*hotkey
)
220 static char buf
[128];
222 for (uint i
= 0; i
< hotkey
->keycodes
.size(); i
++) {
223 const char *str
= KeycodeToString(hotkey
->keycodes
[i
]);
224 if (i
> 0) strecat(buf
, ",", lastof(buf
));
225 strecat(buf
, str
, lastof(buf
));
231 * Create a new Hotkey object with a single default keycode.
232 * @param default_keycode The default keycode for this hotkey.
233 * @param name The name of this hotkey.
234 * @param num Number of this hotkey, should be unique within the hotkey list.
236 Hotkey::Hotkey(uint16 default_keycode
, const char *name
, int num
) :
240 if (default_keycode
!= 0) this->AddKeycode(default_keycode
);
244 * Create a new Hotkey object with multiple default keycodes.
245 * @param default_keycodes An array of default keycodes terminated with 0.
246 * @param name The name of this hotkey.
247 * @param num Number of this hotkey, should be unique within the hotkey list.
249 Hotkey::Hotkey(const uint16
*default_keycodes
, const char *name
, int num
) :
253 const uint16
*keycode
= default_keycodes
;
254 while (*keycode
!= 0) {
255 this->AddKeycode(*keycode
);
261 * Add a keycode to this hotkey, from now that keycode will be matched
262 * in addition to any previously added keycodes.
263 * @param keycode The keycode to add.
265 void Hotkey::AddKeycode(uint16 keycode
)
267 include(this->keycodes
, keycode
);
270 HotkeyList::HotkeyList(const char *ini_group
, Hotkey
*items
, GlobalHotkeyHandlerFunc global_hotkey_handler
) :
271 global_hotkey_handler(global_hotkey_handler
), ini_group(ini_group
), items(items
)
273 if (_hotkey_lists
== nullptr) _hotkey_lists
= new std::vector
<HotkeyList
*>();
274 _hotkey_lists
->push_back(this);
277 HotkeyList::~HotkeyList()
279 _hotkey_lists
->erase(std::find(_hotkey_lists
->begin(), _hotkey_lists
->end(), this));
283 * Load HotkeyList from IniFile.
284 * @param ini IniFile to load from.
286 void HotkeyList::Load(IniFile
*ini
)
288 IniGroup
*group
= ini
->GetGroup(this->ini_group
);
289 for (Hotkey
*hotkey
= this->items
; hotkey
->name
!= nullptr; ++hotkey
) {
290 IniItem
*item
= group
->GetItem(hotkey
->name
, false);
291 if (item
!= nullptr) {
292 hotkey
->keycodes
.clear();
293 if (item
->value
!= nullptr) ParseHotkeys(hotkey
, item
->value
);
299 * Save HotkeyList to IniFile.
300 * @param ini IniFile to save to.
302 void HotkeyList::Save(IniFile
*ini
) const
304 IniGroup
*group
= ini
->GetGroup(this->ini_group
);
305 for (const Hotkey
*hotkey
= this->items
; hotkey
->name
!= nullptr; ++hotkey
) {
306 IniItem
*item
= group
->GetItem(hotkey
->name
, true);
307 item
->SetValue(SaveKeycodes(hotkey
));
312 * Check if a keycode is bound to something.
313 * @param keycode The keycode that was pressed
314 * @param global_only Limit the search to hotkeys defined as 'global'.
315 * @return The number of the matching hotkey or -1.
317 int HotkeyList::CheckMatch(uint16 keycode
, bool global_only
) const
319 for (const Hotkey
*list
= this->items
; list
->name
!= nullptr; ++list
) {
320 auto begin
= list
->keycodes
.begin();
321 auto end
= list
->keycodes
.end();
322 if (std::find(begin
, end
, keycode
| WKC_GLOBAL_HOTKEY
) != end
|| (!global_only
&& std::find(begin
, end
, keycode
) != end
)) {
330 static void SaveLoadHotkeys(bool save
)
332 IniFile
*ini
= new IniFile();
333 ini
->LoadFromDisk(_hotkeys_file
, NO_DIRECTORY
);
335 for (HotkeyList
*list
: *_hotkey_lists
) {
343 if (save
) ini
->SaveToDisk(_hotkeys_file
);
348 /** Load the hotkeys from the config file */
349 void LoadHotkeysFromConfig()
351 SaveLoadHotkeys(false);
354 /** Save the hotkeys to the config file */
355 void SaveHotkeysToConfig()
357 SaveLoadHotkeys(true);
360 void HandleGlobalHotkeys(WChar key
, uint16 keycode
)
362 for (HotkeyList
*list
: *_hotkey_lists
) {
363 if (list
->global_hotkey_handler
== nullptr) continue;
365 int hotkey
= list
->CheckMatch(keycode
, true);
366 if (hotkey
>= 0 && (list
->global_hotkey_handler(hotkey
) == ES_HANDLED
)) return;