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"
19 std::string _hotkeys_file
;
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
},
71 {"/", WKC_SLASH
}, /* deprecated, use SLASH */
72 {"SEMICOLON", WKC_SEMICOLON
},
73 {";", WKC_SEMICOLON
}, /* deprecated, use SEMICOLON */
74 {"EQUALS", WKC_EQUALS
},
75 {"=", WKC_EQUALS
}, /* deprecated, use EQUALS */
76 {"L_BRACKET", WKC_L_BRACKET
},
77 {"[", WKC_L_BRACKET
}, /* deprecated, use L_BRACKET */
78 {"BACKSLASH", WKC_BACKSLASH
},
79 {"\\", WKC_BACKSLASH
}, /* deprecated, use BACKSLASH */
80 {"R_BRACKET", WKC_R_BRACKET
},
81 {"]", WKC_R_BRACKET
}, /* deprecated, use R_BRACKET */
82 {"SINGLEQUOTE", WKC_SINGLEQUOTE
},
83 {"'", WKC_SINGLEQUOTE
}, /* deprecated, use SINGLEQUOTE */
85 {"PERIOD", WKC_PERIOD
},
86 {".", WKC_PERIOD
}, /* deprecated, use PERIOD */
88 {"-", WKC_MINUS
}, /* deprecated, use MINUS */
92 * Try to parse a single part of a keycode.
93 * @param start Start of the string to parse.
94 * @param end End of the string to parse.
95 * @return A keycode if a match is found or 0.
97 static uint16
ParseCode(const char *start
, const char *end
)
100 while (start
< end
&& *start
== ' ') start
++;
101 while (end
> start
&& *end
== ' ') end
--;
102 for (uint i
= 0; i
< lengthof(_keycode_to_name
); i
++) {
103 if (strlen(_keycode_to_name
[i
].name
) == (size_t)(end
- start
) && strncasecmp(start
, _keycode_to_name
[i
].name
, end
- start
) == 0) {
104 return _keycode_to_name
[i
].keycode
;
107 if (end
- start
== 1) {
108 if (*start
>= 'a' && *start
<= 'z') return *start
- ('a'-'A');
109 /* Ignore invalid keycodes */
110 if (*(const uint8
*)start
< 128) return *start
;
116 * Parse a string representation of a keycode.
117 * @param start Start of the input.
118 * @param end End of the input.
119 * @return A valid keycode or 0.
121 static uint16
ParseKeycode(const char *start
, const char *end
)
123 assert(start
<= end
);
126 const char *cur
= start
;
127 while (*cur
!= '+' && cur
!= end
) cur
++;
128 uint16 code
= ParseCode(start
, cur
);
129 if (code
== 0) return 0;
130 if (code
& WKC_SPECIAL_KEYS
) {
131 /* Some completely wrong keycode we don't support. */
132 if (code
& ~WKC_SPECIAL_KEYS
) return 0;
135 /* Ignore the code if it has more then 1 letter. */
136 if (keycode
& ~WKC_SPECIAL_KEYS
) return 0;
139 if (cur
== end
) break;
147 * Parse a string to the keycodes it represents
148 * @param hotkey The hotkey object to add the keycodes to
149 * @param value The string to parse
151 static void ParseHotkeys(Hotkey
*hotkey
, const char *value
)
153 const char *start
= value
;
154 while (*start
!= '\0') {
155 const char *end
= start
;
156 while (*end
!= '\0' && *end
!= ',') end
++;
157 uint16 keycode
= ParseKeycode(start
, end
);
158 if (keycode
!= 0) hotkey
->AddKeycode(keycode
);
159 start
= (*end
== ',') ? end
+ 1: end
;
164 * Convert a hotkey to it's string representation so it can be written to the
165 * config file. Separate parts of the keycode (like "CTRL" and "F1" are split
167 * @param keycode The keycode to convert to a string.
168 * @return A string representation of this keycode.
169 * @note The return value is a static buffer, stredup the result before calling
170 * this function again.
172 static const char *KeycodeToString(uint16 keycode
)
177 if (keycode
& WKC_GLOBAL_HOTKEY
) {
178 strecat(buf
, "GLOBAL", lastof(buf
));
181 if (keycode
& WKC_SHIFT
) {
182 if (!first
) strecat(buf
, "+", lastof(buf
));
183 strecat(buf
, "SHIFT", lastof(buf
));
186 if (keycode
& WKC_CTRL
) {
187 if (!first
) strecat(buf
, "+", lastof(buf
));
188 strecat(buf
, "CTRL", lastof(buf
));
191 if (keycode
& WKC_ALT
) {
192 if (!first
) strecat(buf
, "+", lastof(buf
));
193 strecat(buf
, "ALT", lastof(buf
));
196 if (keycode
& WKC_META
) {
197 if (!first
) strecat(buf
, "+", lastof(buf
));
198 strecat(buf
, "META", lastof(buf
));
201 if (!first
) strecat(buf
, "+", lastof(buf
));
202 keycode
= keycode
& ~WKC_SPECIAL_KEYS
;
204 for (uint i
= 0; i
< lengthof(_keycode_to_name
); i
++) {
205 if (_keycode_to_name
[i
].keycode
== keycode
) {
206 strecat(buf
, _keycode_to_name
[i
].name
, lastof(buf
));
210 assert(keycode
< 128);
214 strecat(buf
, key
, lastof(buf
));
219 * Convert all keycodes attached to a hotkey to a single string. If multiple
220 * keycodes are attached to the hotkey they are split by a comma.
221 * @param hotkey The keycodes of this hotkey need to be converted to a string.
222 * @return A string representation of all keycodes.
223 * @note The return value is a static buffer, stredup the result before calling
224 * this function again.
226 const char *SaveKeycodes(const Hotkey
*hotkey
)
228 static char buf
[128];
230 for (uint i
= 0; i
< hotkey
->keycodes
.size(); i
++) {
231 const char *str
= KeycodeToString(hotkey
->keycodes
[i
]);
232 if (i
> 0) strecat(buf
, ",", lastof(buf
));
233 strecat(buf
, str
, lastof(buf
));
239 * Create a new Hotkey object with a single default keycode.
240 * @param default_keycode The default keycode for this hotkey.
241 * @param name The name of this hotkey.
242 * @param num Number of this hotkey, should be unique within the hotkey list.
244 Hotkey::Hotkey(uint16 default_keycode
, const char *name
, int num
) :
248 if (default_keycode
!= 0) this->AddKeycode(default_keycode
);
252 * Create a new Hotkey object with multiple default keycodes.
253 * @param default_keycodes An array of default keycodes terminated with 0.
254 * @param name The name of this hotkey.
255 * @param num Number of this hotkey, should be unique within the hotkey list.
257 Hotkey::Hotkey(const uint16
*default_keycodes
, const char *name
, int num
) :
261 const uint16
*keycode
= default_keycodes
;
262 while (*keycode
!= 0) {
263 this->AddKeycode(*keycode
);
269 * Add a keycode to this hotkey, from now that keycode will be matched
270 * in addition to any previously added keycodes.
271 * @param keycode The keycode to add.
273 void Hotkey::AddKeycode(uint16 keycode
)
275 include(this->keycodes
, keycode
);
278 HotkeyList::HotkeyList(const char *ini_group
, Hotkey
*items
, GlobalHotkeyHandlerFunc global_hotkey_handler
) :
279 global_hotkey_handler(global_hotkey_handler
), ini_group(ini_group
), items(items
)
281 if (_hotkey_lists
== nullptr) _hotkey_lists
= new std::vector
<HotkeyList
*>();
282 _hotkey_lists
->push_back(this);
285 HotkeyList::~HotkeyList()
287 _hotkey_lists
->erase(std::find(_hotkey_lists
->begin(), _hotkey_lists
->end(), this));
291 * Load HotkeyList from IniFile.
292 * @param ini IniFile to load from.
294 void HotkeyList::Load(IniFile
*ini
)
296 IniGroup
*group
= ini
->GetGroup(this->ini_group
);
297 for (Hotkey
*hotkey
= this->items
; hotkey
->name
!= nullptr; ++hotkey
) {
298 IniItem
*item
= group
->GetItem(hotkey
->name
, false);
299 if (item
!= nullptr) {
300 hotkey
->keycodes
.clear();
301 if (item
->value
.has_value()) ParseHotkeys(hotkey
, item
->value
->c_str());
307 * Save HotkeyList to IniFile.
308 * @param ini IniFile to save to.
310 void HotkeyList::Save(IniFile
*ini
) const
312 IniGroup
*group
= ini
->GetGroup(this->ini_group
);
313 for (const Hotkey
*hotkey
= this->items
; hotkey
->name
!= nullptr; ++hotkey
) {
314 IniItem
*item
= group
->GetItem(hotkey
->name
, true);
315 item
->SetValue(SaveKeycodes(hotkey
));
320 * Check if a keycode is bound to something.
321 * @param keycode The keycode that was pressed
322 * @param global_only Limit the search to hotkeys defined as 'global'.
323 * @return The number of the matching hotkey or -1.
325 int HotkeyList::CheckMatch(uint16 keycode
, bool global_only
) const
327 for (const Hotkey
*list
= this->items
; list
->name
!= nullptr; ++list
) {
328 auto begin
= list
->keycodes
.begin();
329 auto end
= list
->keycodes
.end();
330 if (std::find(begin
, end
, keycode
| WKC_GLOBAL_HOTKEY
) != end
|| (!global_only
&& std::find(begin
, end
, keycode
) != end
)) {
338 static void SaveLoadHotkeys(bool save
)
340 IniFile
*ini
= new IniFile();
341 ini
->LoadFromDisk(_hotkeys_file
, NO_DIRECTORY
);
343 for (HotkeyList
*list
: *_hotkey_lists
) {
351 if (save
) ini
->SaveToDisk(_hotkeys_file
);
356 /** Load the hotkeys from the config file */
357 void LoadHotkeysFromConfig()
359 SaveLoadHotkeys(false);
362 /** Save the hotkeys to the config file */
363 void SaveHotkeysToConfig()
365 SaveLoadHotkeys(true);
368 void HandleGlobalHotkeys(WChar key
, uint16 keycode
)
370 for (HotkeyList
*list
: *_hotkey_lists
) {
371 if (list
->global_hotkey_handler
== nullptr) continue;
373 int hotkey
= list
->CheckMatch(keycode
, true);
374 if (hotkey
>= 0 && (list
->global_hotkey_handler(hotkey
) == ES_HANDLED
)) return;