4 * This file is part of OpenTTD.
5 * 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.
6 * 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.
7 * 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/>.
10 /** @file hotkeys.cpp Implementation of hotkey related functions */
16 #include "string_func.h"
17 #include "window_gui.h"
19 #include "safeguards.h"
24 * List of all HotkeyLists.
25 * This is a pointer to ensure initialisation order with the various static HotkeyList instances.
27 static SmallVector
<HotkeyList
*, 16> *_hotkey_lists
= nullptr;
29 /** String representation of a keycode */
31 const char *name
; ///< Name of the keycode
32 WindowKeyCodes keycode
; ///< The keycode
35 /** Array of non-standard keycodes that can be used in the hotkeys config file. */
36 static const KeycodeNames _keycode_to_name
[] = {
41 {"GLOBAL", WKC_GLOBAL_HOTKEY
},
44 {"RETURN", WKC_RETURN
},
45 {"BACKQUOTE", WKC_BACKQUOTE
},
60 {"NUM_PLUS", WKC_NUM_PLUS
},
61 {"NUM_MINUS", WKC_NUM_MINUS
},
64 {"PAGE_UP", WKC_PAGEUP
},
65 {"PAGE_DOWN", WKC_PAGEDOWN
},
69 * Try to parse a single part of a keycode.
70 * @param start Start of the string to parse.
71 * @param end End of the string to parse.
72 * @return A keycode if a match is found or 0.
74 static uint16
ParseCode(const char *start
, const char *end
)
77 while (start
< end
&& *start
== ' ') start
++;
78 while (end
> start
&& *end
== ' ') end
--;
79 for (uint i
= 0; i
< lengthof(_keycode_to_name
); i
++) {
80 if (strlen(_keycode_to_name
[i
].name
) == (size_t)(end
- start
) && strncasecmp(start
, _keycode_to_name
[i
].name
, end
- start
) == 0) {
81 return _keycode_to_name
[i
].keycode
;
84 if (end
- start
== 1) {
85 if (*start
>= 'a' && *start
<= 'z') return *start
- ('a'-'A');
86 /* Ignore invalid keycodes */
87 if (*(const uint8
*)start
< 128) return *start
;
93 * Parse a string representation of a keycode.
94 * @param start Start of the input.
95 * @param end End of the input.
96 * @return A valid keycode or 0.
98 static uint16
ParseKeycode(const char *start
, const char *end
)
100 assert(start
<= end
);
103 const char *cur
= start
;
104 while (*cur
!= '+' && cur
!= end
) cur
++;
105 uint16 code
= ParseCode(start
, cur
);
106 if (code
== 0) return 0;
107 if (code
& WKC_SPECIAL_KEYS
) {
108 /* Some completely wrong keycode we don't support. */
109 if (code
& ~WKC_SPECIAL_KEYS
) return 0;
112 /* Ignore the code if it has more then 1 letter. */
113 if (keycode
& ~WKC_SPECIAL_KEYS
) return 0;
116 if (cur
== end
) break;
124 * Parse a string to the keycodes it represents
125 * @param hotkey The hotkey object to add the keycodes to
126 * @param value The string to parse
128 static void ParseHotkeys(Hotkey
*hotkey
, const char *value
)
130 const char *start
= value
;
131 while (*start
!= '\0') {
132 const char *end
= start
;
133 while (*end
!= '\0' && *end
!= ',') end
++;
134 uint16 keycode
= ParseKeycode(start
, end
);
135 if (keycode
!= 0) hotkey
->AddKeycode(keycode
);
136 start
= (*end
== ',') ? end
+ 1: end
;
141 * Convert a hotkey to it's string representation so it can be written to the
142 * config file. Separate parts of the keycode (like "CTRL" and "F1" are split
144 * @param keycode The keycode to convert to a string.
145 * @return A string representation of this keycode.
146 * @note The return value is a static buffer, stredup the result before calling
147 * this function again.
149 static const char *KeycodeToString(uint16 keycode
)
154 if (keycode
& WKC_GLOBAL_HOTKEY
) {
155 strecat(buf
, "GLOBAL", lastof(buf
));
158 if (keycode
& WKC_SHIFT
) {
159 if (!first
) strecat(buf
, "+", lastof(buf
));
160 strecat(buf
, "SHIFT", lastof(buf
));
163 if (keycode
& WKC_CTRL
) {
164 if (!first
) strecat(buf
, "+", lastof(buf
));
165 strecat(buf
, "CTRL", lastof(buf
));
168 if (keycode
& WKC_ALT
) {
169 if (!first
) strecat(buf
, "+", lastof(buf
));
170 strecat(buf
, "ALT", lastof(buf
));
173 if (keycode
& WKC_META
) {
174 if (!first
) strecat(buf
, "+", lastof(buf
));
175 strecat(buf
, "META", lastof(buf
));
178 if (!first
) strecat(buf
, "+", lastof(buf
));
179 keycode
= keycode
& ~WKC_SPECIAL_KEYS
;
181 for (uint i
= 0; i
< lengthof(_keycode_to_name
); i
++) {
182 if (_keycode_to_name
[i
].keycode
== keycode
) {
183 strecat(buf
, _keycode_to_name
[i
].name
, lastof(buf
));
187 assert(keycode
< 128);
191 strecat(buf
, key
, lastof(buf
));
196 * Convert all keycodes attached to a hotkey to a single string. If multiple
197 * keycodes are attached to the hotkey they are split by a comma.
198 * @param hotkey The keycodes of this hotkey need to be converted to a string.
199 * @return A string representation of all keycodes.
200 * @note The return value is a static buffer, stredup the result before calling
201 * this function again.
203 const char *SaveKeycodes(const Hotkey
*hotkey
)
205 static char buf
[128];
207 for (uint i
= 0; i
< hotkey
->keycodes
.Length(); i
++) {
208 const char *str
= KeycodeToString(hotkey
->keycodes
[i
]);
209 if (i
> 0) strecat(buf
, ",", lastof(buf
));
210 strecat(buf
, str
, lastof(buf
));
216 * Create a new Hotkey object with a single default keycode.
217 * @param default_keycode The default keycode for this hotkey.
218 * @param name The name of this hotkey.
219 * @param num Number of this hotkey, should be unique within the hotkey list.
221 Hotkey::Hotkey(uint16 default_keycode
, const char *name
, int num
) :
225 if (default_keycode
!= 0) this->AddKeycode(default_keycode
);
229 * Create a new Hotkey object with multiple default keycodes.
230 * @param default_keycodes An array of default keycodes terminated with 0.
231 * @param name The name of this hotkey.
232 * @param num Number of this hotkey, should be unique within the hotkey list.
234 Hotkey::Hotkey(const uint16
*default_keycodes
, const char *name
, int num
) :
238 const uint16
*keycode
= default_keycodes
;
239 while (*keycode
!= 0) {
240 this->AddKeycode(*keycode
);
246 * Add a keycode to this hotkey, from now that keycode will be matched
247 * in addition to any previously added keycodes.
248 * @param keycode The keycode to add.
250 void Hotkey::AddKeycode(uint16 keycode
)
252 this->keycodes
.Include(keycode
);
255 HotkeyList::HotkeyList(const char *ini_group
, Hotkey
*items
, GlobalHotkeyHandlerFunc global_hotkey_handler
) :
256 global_hotkey_handler(global_hotkey_handler
), ini_group(ini_group
), items(items
)
258 if (_hotkey_lists
== nullptr) _hotkey_lists
= new SmallVector
<HotkeyList
*, 16>();
259 *_hotkey_lists
->Append() = this;
262 HotkeyList::~HotkeyList()
264 _hotkey_lists
->Erase(_hotkey_lists
->Find(this));
268 * Load HotkeyList from IniFile.
269 * @param ini IniFile to load from.
271 void HotkeyList::Load(IniFile
*ini
)
273 IniGroup
*group
= ini
->GetGroup(this->ini_group
);
274 for (Hotkey
*hotkey
= this->items
; hotkey
->name
!= nullptr; ++hotkey
) {
275 IniItem
*item
= group
->GetItem(hotkey
->name
, false);
276 if (item
!= nullptr) {
277 hotkey
->keycodes
.Clear();
278 if (item
->value
!= nullptr) ParseHotkeys(hotkey
, item
->value
);
284 * Save HotkeyList to IniFile.
285 * @param ini IniFile to save to.
287 void HotkeyList::Save(IniFile
*ini
) const
289 IniGroup
*group
= ini
->GetGroup(this->ini_group
);
290 for (const Hotkey
*hotkey
= this->items
; hotkey
->name
!= nullptr; ++hotkey
) {
291 IniItem
*item
= group
->GetItem(hotkey
->name
, true);
292 item
->SetValue(SaveKeycodes(hotkey
));
297 * Check if a keycode is bound to something.
298 * @param keycode The keycode that was pressed
299 * @param global_only Limit the search to hotkeys defined as 'global'.
300 * @return The number of the matching hotkey or -1.
302 int HotkeyList::CheckMatch(uint16 keycode
, bool global_only
) const
304 for (const Hotkey
*list
= this->items
; list
->name
!= nullptr; ++list
) {
305 if (list
->keycodes
.Contains(keycode
| WKC_GLOBAL_HOTKEY
) || (!global_only
&& list
->keycodes
.Contains(keycode
))) {
313 static void SaveLoadHotkeys(bool save
)
315 IniFile
*ini
= new IniFile();
316 ini
->LoadFromDisk(_hotkeys_file
, NO_DIRECTORY
);
318 for (HotkeyList
**list
= _hotkey_lists
->Begin(); list
!= _hotkey_lists
->End(); ++list
) {
326 if (save
) ini
->SaveToDisk(_hotkeys_file
);
331 /** Load the hotkeys from the config file */
332 void LoadHotkeysFromConfig()
334 SaveLoadHotkeys(false);
337 /** Save the hotkeys to the config file */
338 void SaveHotkeysToConfig()
340 SaveLoadHotkeys(true);
343 void HandleGlobalHotkeys(WChar key
, uint16 keycode
)
345 for (HotkeyList
**list
= _hotkey_lists
->Begin(); list
!= _hotkey_lists
->End(); ++list
) {
346 if ((*list
)->global_hotkey_handler
== nullptr) continue;
348 int hotkey
= (*list
)->CheckMatch(keycode
, true);
349 if (hotkey
>= 0 && ((*list
)->global_hotkey_handler(hotkey
) == ES_HANDLED
)) return;