Index: binaries/data/config/default.cfg
===================================================================
--- binaries/data/config/default.cfg
+++ binaries/data/config/default.cfg
@@ -186,8 +186,8 @@
reset = "R" ; Reset camera rotation to default.
follow = "F" ; Follow the first unit in the selection
rallypointfocus = unused ; Focus the camera on the rally point of the selected building
-zoom.in = Plus, Equals, NumPlus ; Zoom camera in (continuous control)
-zoom.out = Minus, Underscore, NumMinus ; Zoom camera out (continuous control)
+zoom.in = Plus, NumPlus ; Zoom camera in (continuous control)
+zoom.out = Minus, NumMinus ; Zoom camera out (continuous control)
zoom.wheel.in = WheelUp ; Zoom camera in (stepped control)
zoom.wheel.out = WheelDown ; Zoom camera out (stepped control)
rotate.up = "Ctrl+UpArrow", "Ctrl+W" ; Rotate camera to look upwards
@@ -312,8 +312,8 @@
; Overlays
showstatusbars = Tab ; Toggle display of status bars
devcommands.toggle = "Alt+D" ; Toggle developer commands panel
-highlightguarding = PgDn ; Toggle highlight of guarding units
-highlightguarded = PgUp ; Toggle highlight of guarded units
+highlightguarding = PageDown ; Toggle highlight of guarding units
+highlightguarded = PageUp ; Toggle highlight of guarded units
diplomacycolors = "Alt+X" ; Toggle diplomacy colors
toggleattackrange = "Alt+C" ; Toggle display of attack range overlays of selected defensive structures
toggleaurasrange = "Alt+V" ; Toggle display of aura range overlays of selected units and structures
Index: binaries/data/config/keys.txt
===================================================================
--- binaries/data/config/keys.txt
+++ binaries/data/config/keys.txt
@@ -1,107 +1,38 @@
## This file documents keynames that can be used in .cfg files for specifying hotkeys
+## Note: all are case-insensitive.
## Note: the keynames are not actually configured or implemented here
-## Also note: if your keyboard supports keys that do not appear here (such as the French é letter),
-## you should be able to use it directly.
+## Note: those names map to 'scancodes', i.e. positions on a classic QWERTY keyboard.
+## The in-game hotkey editor will show you what that corresponds to.
-Backspace, BkSp
-Tab
-Clear
-Return, Ret
-Pause
+
+MouseLeft
+MouseRight
+MouseMiddle
+MouseX1
+MouseX2
+WheelUp
+WheelDown
+WheelLeft
+WheelRight
+
+A-Z
+0-9
+Return, Break
Escape, Esc
-Space, Spc
-!, Exclaim
-", DoubleQuote
-#, Hash
-$, Dollar
-&, Ampersand
-', SingleQuote
-(, LeftParen
-), RightParen
-*, Asterisk
-+, Plus
-,, Comma
+Backspace, Del
+Tab
+Space
-, Minus
-., Period
-/, ForwardSlash
-0
-1
-2
-3
-4
-5
-6
-7
-8
-9
-:, Colon
-;, Semicolon
-<, LessThan
-=, Equals
->, GreaterThan
-?, Question
-@, At
+=, Plus, Equals
[, LeftBracket
-\, BackSlash
], RightBracket
-^, Caret
-_, Underscore
+\\, BackSlash ## You do need the espacing in the first case.
+;, Semicolon
+', Quote ## Maps to the quote/doubleQuote key on QWERTY.
`, BackQuote
-A
-B
-C
-D
-E
-F
-G
-H
-I
-J
-K
-L
-M
-N
-O
-P
-Q
-R
-S
-T
-U
-V
-W
-X
-Y
-Z
-Delete, Del
-Numpad 0, Num0
-Numpad 1, Num1
-Numpad 2, Num2
-Numpad 3, Num3
-Numpad 4, Num4
-Numpad 5, Num5
-Numpad 6, Num6
-Numpad 7, Num7
-Numpad 8, Num8
-Numpad 9, Num9
-Numpad ., NumPoint
-Numpad /, NumDivide
-Numpad *, NumMultiply
-Numpad -, NumMinus
-Numpad +, NumPlus
-Numpad Enter, NumEnter
-Numpad =, NumEquals
-
-Arrow Up, UpArrow
-Arrow Down, DownArrow
-Arrow Right, RightArrow
-Arrow Left, LeftArrow
-Insert, Ins
-Home
-End
-Page Up, PgUp
-Page Down, PgDn
-
+,, Comma
+., Period
+/, ForwardSlash
F1
F2
F3
@@ -114,41 +45,155 @@
F10
F11
F12
+PrintScreen
+ScrollLock
+Pause
+Insert
+Home
+PageUp
+Delete
+End
+PageDown
+Right, RightArrow
+Left, LeftArrow
+Down, DownArrow
+Up, UpArrow
+Numlock
+Keypad /
+Keypad *
+Keypad -, NumMinus
+Keypad +, NumPlus
+Keypad Enter
+Keypad 1
+Keypad 2
+Keypad 3
+Keypad 4
+Keypad 5
+Keypad 6
+Keypad 7
+Keypad 8
+Keypad 9
+Keypad 0
+Keypad .
+Application
+Power
+Keypad =
F13
F14
F15
-
-Num Lock, NumLock
-Caps Lock, CapsLock
-Scroll Lock, ScrlLock
-Right Shift, RightShift
-Left Shift, LeftShift
-Shift, AnyShift
-Right Ctrl, RightCtrl
-Left Ctrl, LeftCtrl
-Ctrl, AnyCtrl
-Right Alt, RightAlt
-Left Alt, LeftAlt
-Alt, AnyAlt
-Left Super, LeftWin
-Right Super, RightWin
-Super, AnyWindows ## Windows key, also Command/meta key on Macs
-Alt Gr, AltGr
-Compose
-
+F16
+F17
+F18
+F19
+F20
+F21
+F22
+F23
+F24
+Execute
Help
-Print Screen, PrtSc
-SysRq
-Break
Menu
-Power
-Euro
+Select
+Stop
+Again
Undo
-Left Mouse Button, MouseLeft
-Right Mouse Button, MouseRight
-Middle Mouse Button, MouseMiddle
-Mouse Wheel Up, WheelUp
-Mouse Wheel Down, WheelDown
-MouseButtonX, MouseNX # where X is a number 1-255, for extra mouse buttons
-## (note that some mice start numbering their buttons at higher numbers
-## so the first extra button on your mouse might be #8 here)
+Cut
+Copy
+Paste
+Find
+Mute
+VolumeUp
+VolumeDown
+Keypad ,
+AltErase
+SysReq
+Cancel
+Clear
+Prior
+Return
+Separator
+Out
+Oper
+Clear / Again
+CrSel
+ExSel
+Keypad 00
+Keypad 000
+ThousandsSeparator
+DecimalSeparator
+CurrencyUnit
+CurrencySubUnit
+Keypad (
+Keypad )
+Keypad {
+Keypad }
+Keypad Tab
+Keypad Backspace
+Keypad A
+Keypad B
+Keypad C
+Keypad D
+Keypad E
+Keypad F
+Keypad XOR
+Keypad ^
+Keypad %
+Keypad <
+Keypad >
+Keypad &
+Keypad &&
+Keypad |
+Keypad ||
+Keypad :
+Keypad #
+Keypad Space
+Keypad @
+Keypad !
+Keypad MemStore
+Keypad MemRecall
+Keypad MemClear
+Keypad MemAdd
+Keypad MemSubtract
+Keypad MemMultiply
+Keypad MemDivide
+Keypad +/-
+Keypad Clear
+Keypad ClearEntry
+Keypad Binary
+Keypad Octal
+Keypad Decimal
+Keypad Hexadecimal
+Left Ctrl, Ctrl
+Left Shift, Shift
+Left Alt, Alt
+Left GUI, Super
+Right Ctrl, Ctrl
+Right Shift, Shift
+Right Alt, Alt
+Right GUI, Super
+ModeSwitch
+AudioNext
+AudioPrev
+AudioStop
+AudioPlay
+AudioMute
+MediaSelect
+WWW
+Mail
+Calculator
+Computer
+AC Search
+AC Home
+AC Back
+AC Forward
+AC Stop
+AC Refresh
+AC Bookmarks
+BrightnessDown
+BrightnessUp
+DisplaySwitch
+KBDIllumToggle
+KBDIllumDown
+KBDIllumUp
+Eject
+Sleep
Index: binaries/data/mods/public/gui/hotkeys/HotkeyPicker.js
===================================================================
--- /dev/null
+++ binaries/data/mods/public/gui/hotkeys/HotkeyPicker.js
@@ -0,0 +1,107 @@
+/**
+ * Handle the interface to pick a hotkey combination.
+ * The player must keep a key combination for 2s in the input field for it to be registered.
+ */
+class HotkeyPicker
+{
+ constructor(onClose, name, combinations)
+ {
+ this.name = name;
+ this.combinations = combinations;
+ this.window = Engine.GetGUIObjectByName("hotkeyPicker");
+ this.window.hidden = false;
+
+ this.enteringInput = -1;
+
+ Engine.GetGUIObjectByName("hotkeyPickerTitle").caption = translate(this.name);
+
+ this.setupCombinations();
+ this.render();
+
+ Engine.GetGUIObjectByName("hotkeyPickerReset").onPress = () => {
+ // This is a bit "using a bazooka to kill a fly"
+ Engine.ConfigDB_RemoveValue("user", "hotkey." + this.name);
+ Engine.ConfigDB_WriteFile("user", "config/user.cfg");
+ Engine.ReloadHotkeys();
+ let data = Engine.GetHotkeyMap();
+ this.combinations = data[this.name];
+ this.setupCombinations();
+ this.render();
+ };
+ Engine.GetGUIObjectByName("hotkeyPickerCancel").onPress = () => {
+ onClose(this, false);
+ };
+ Engine.GetGUIObjectByName("hotkeyPickerSave").onPress = () => {
+ onClose(this, true);
+ };
+ }
+
+ setupCombinations()
+ {
+ for (let i = 0; i < 4; ++i)
+ {
+ let s = Engine.GetGUIObjectByName("combination[" + i + "]").size;
+ s.top = +i * 60 + 90;
+ s.bottom = +i * 60 + 120;
+ Engine.GetGUIObjectByName("combination[" + i + "]").size = s;
+ Engine.GetGUIObjectByName("combNb[" + i + "]").caption = sprintf(translate("#%i"), i);
+
+ if (i == this.combinations.length)
+ this.combinations.push([]);
+
+ let input = Engine.GetGUIObjectByName("combMapping[" + i + "]");
+
+ let picker = Engine.GetGUIObjectByName("picker[" + i + "]");
+ Engine.GetGUIObjectByName("combMappingBtn[" + i + "]").onPress = () => {
+ this.enteringInput = i;
+ picker.focus();
+ this.render();
+ };
+
+ picker.onKeyChange = keys => {
+ input.caption = (keys.length ?
+ formatHotkeyCombination(keys) + translate(" (hold to register)") :
+ translate("Enter new Hotkey, hold to register."));
+ };
+
+ Engine.GetGUIObjectByName("deleteComb[" + i + "]").onPress = (j => () => {
+ this.combinations[j] = [];
+ this.render();
+ })(i);
+
+ picker.onCombination = (j => keys => {
+ this.combinations[j] = keys;
+ this.enteringInput = -1;
+ picker.blur();
+
+ this.render();
+ })(i);
+ }
+ }
+
+ close()
+ {
+ this.window.hidden = true;
+ for (let i = 0; i < 4; ++i)
+ Engine.GetGUIObjectByName("picker[" + i + "]").blur();
+ }
+
+ render()
+ {
+ for (let i = 0; i < 4; ++i)
+ {
+ let input = Engine.GetGUIObjectByName("combMapping[" + i + "]");
+ if (i == this.enteringInput)
+ input.caption = translate("Enter new Hotkey, hold to register.");
+ else
+ input.caption = formatHotkeyCombination(this.combinations[i]) || "(unused)";
+ Engine.GetGUIObjectByName("conflicts[" + i + "]").caption = "";
+
+ let conflicts = (Engine.GetConflicts(this.combinations[i]) || [])
+ .filter(name => name != this.name).map(translate);
+ if (conflicts.length)
+ Engine.GetGUIObjectByName("conflicts[" + i + "]").caption =
+ coloredText(translate("May conflict with: "), "255 153 0") + conflicts.join(", ");
+ }
+ }
+}
Index: binaries/data/mods/public/gui/hotkeys/HotkeysPage.js
===================================================================
--- /dev/null
+++ binaries/data/mods/public/gui/hotkeys/HotkeysPage.js
@@ -0,0 +1,190 @@
+class HotkeysPage
+{
+ constructor()
+ {
+ g_ScancodesMap = Engine.GetScancodeKeyNames();
+
+ Engine.GetGUIObjectByName("hotkeyList").onMouseLeftDoubleClickItem = () => {
+ let idx = Engine.GetGUIObjectByName("hotkeyList").selected;
+ let picker = new HotkeyPicker(
+ this.onHotkeyPicked.bind(this),
+ Engine.GetGUIObjectByName("hotkeyList").list_data[idx],
+ this.hotkeys[Engine.GetGUIObjectByName("hotkeyList").list_data[idx]]
+ );
+ };
+ Engine.GetGUIObjectByName("hotkeyFilter").onSelectionChange = () => this.setupHotkeyList();
+
+ Engine.GetGUIObjectByName("hotkeyTextFilter").onTextEdit = () => this.setupHotkeyList();
+
+ Engine.GetGUIObjectByName("hotkeyClose").onPress = () => Engine.PopGuiPage();
+ Engine.GetGUIObjectByName("hotkeyReset").onPress = () => this.resetUserHotkeys();
+ Engine.GetGUIObjectByName("hotkeySave").onPress = () => {
+ this.saveUserHotkeys();
+ };
+
+ this.setupHotkeyData();
+ this.setupFilters();
+ this.setupHotkeyList();
+ }
+
+ setupFilters()
+ {
+ let dropdown = Engine.GetGUIObjectByName("hotkeyFilter");
+ let names = [];
+ for (let cat in this.categories)
+ names.push(this.categories[cat].label);
+ dropdown.list = [translate("All Hotkeys")].concat(names);
+ dropdown.list_data = [-1].concat(Object.keys(this.categories));
+ dropdown.selected = 0;
+ }
+
+ setupHotkeyList()
+ {
+ let hotkeyList = Engine.GetGUIObjectByName("hotkeyList");
+ hotkeyList.selected = -1;
+ let textFilter = Engine.GetGUIObjectByName("hotkeyTextFilter").caption;
+ let dropdown = Engine.GetGUIObjectByName("hotkeyFilter");
+ if (dropdown.selected && dropdown.selected !== 0)
+ {
+ let category = this.categories[dropdown.list_data[dropdown.selected]];
+ // This is inefficient but it seems fast enough.
+ let hotkeys = category.hotkeys.filter(x => translate(x[0]).indexOf(textFilter) !== -1);
+ hotkeyList.list_name = hotkeys.map(x => translate(x[0]));
+ hotkeyList.list_mapping = hotkeys.map(x => this.formatHotkeyCombinations(x[1]));
+ hotkeyList.list = hotkeys.map(() => 0);
+ hotkeyList.list_data = hotkeys.map(x => x[0]);
+ }
+ else
+ {
+ // TODO SM62+ : refactor using flat()
+ let flattened = [];
+ for (let cat in this.categories)
+ flattened = flattened.concat(this.categories[cat].hotkeys);
+ flattened = flattened.filter(x => translate(x[0]).indexOf(textFilter) !== -1);
+ hotkeyList.list_name = flattened.map(x => translate(x[0]));
+ hotkeyList.list_mapping = flattened.map(x => this.formatHotkeyCombinations(x[1]));
+ hotkeyList.list = flattened.map(() => 0);
+ hotkeyList.list_data = flattened.map(x => x[0]);
+ }
+ }
+
+ formatHotkeyCombinations(combinations)
+ {
+ return combinations.map(formatHotkeyCombination).join(", ");
+ }
+
+ formatHotkeyCombinationsScancode(combinations)
+ {
+ return combinations.map(formatHotkeyCombinationScancode).join(", ");
+ }
+
+ onHotkeyPicked(picker, success)
+ {
+ picker.close();
+ if (!success)
+ return;
+
+ // Remove empty combinations which the picker added.
+ picker.combinations = picker.combinations.filter(x => x.length);
+
+ this.hotkeys[picker.name] = picker.combinations;
+ // Have to find the correct line.
+ let panel = Engine.GetGUIObjectByName("hotkeyList");
+ for (let cat in this.categories)
+ {
+ let idx = this.categories[cat].hotkeys.findIndex(([name, _]) => name == picker.name);
+ if (idx === -1)
+ continue;
+ this.categories[cat].hotkeys[idx][1] = picker.combinations;
+ }
+
+ this.setupHotkeyList();
+ }
+
+ setupHotkeyData()
+ {
+ let hotkeydata = Engine.GetHotkeyMap();
+ this.hotkeys = hotkeydata;
+
+ let categories = {
+ "other": {
+ "label": translate("Other hotkeys"),
+ "hotkeys": []
+ }
+ };
+ let n_categories = 1;
+ for (let hotkeyName in this.hotkeys)
+ {
+ let category = "other";
+ let firstdot = hotkeyName.indexOf('.');
+ if (firstdot !== -1)
+ category = hotkeyName.substr(0, firstdot);
+ if (!(category in categories))
+ {
+ if (n_categories > 18)
+ category = "other";
+ categories[category] = {
+ "label": category,
+ "hotkeys": []
+ };
+ }
+ categories[category].hotkeys.push([hotkeyName, this.hotkeys[hotkeyName]]);
+ }
+ // Remove categories that are too small to deserve a tab.
+ for (let cat of Object.keys(categories))
+ if (categories[cat].hotkeys.length < 3)
+ {
+ categories.other.hotkeys = categories.other.hotkeys.concat(categories[cat].hotkeys);
+ delete categories[cat];
+ }
+ for (let cat in categories)
+ categories[cat].hotkeys = categories[cat].hotkeys.sort();
+
+ this.categories = categories;
+ }
+
+ resetUserHotkeys()
+ {
+ messageBox(
+ 400, 200,
+ translate("Reset all hotkeys to default values?"),
+ translate("Confirmation"),
+ [translate("No"), translate("Yes")],
+ [
+ () => {},
+ () => {
+ for (let cat in this.categories)
+ this.categories[cat].hotkeys.forEach(([name, _]) => {
+ Engine.ConfigDB_RemoveValue("user", "hotkey." + name);
+ });
+ Engine.ConfigDB_WriteFile("user", "config/user.cfg");
+ Engine.ReloadHotkeys();
+ this.setupHotkeyData();
+ this.setupHotkeyList();
+ }
+ ]);
+ }
+
+ saveUserHotkeys()
+ {
+ for (let cat in this.categories)
+ {
+ this.categories[cat].hotkeys.forEach(([name, mapping]) => {
+ let keymap = mapping.map(comb => comb.sort(hotkeySort).join("+"));
+ Engine.ConfigDB_RemoveValue("user", "hotkey." + name);
+ let val = Engine.ConfigDB_GetValue("user", "hotkey." + name);
+ // val is a string here so I join above.
+ if (keymap.join(", ") !== val)
+ Engine.ConfigDB_CreateValues("user", "hotkey." + name, keymap);
+ });
+ }
+ Engine.ConfigDB_WriteFile("user", "config/user.cfg");
+ Engine.ReloadHotkeys();
+ }
+}
+
+
+function init(data)
+{
+ let hotkeyPage = new HotkeysPage(data);
+}
Index: binaries/data/mods/public/gui/hotkeys/hotkeys.xml
===================================================================
--- /dev/null
+++ binaries/data/mods/public/gui/hotkeys/hotkeys.xml
@@ -0,0 +1,112 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Hotkey
+
+
+
+ Click on any mapping to modify it.\n You may have up to 4 different hotkeys.
+
+
+
+
+
+
+
+
+
+ Click to set the hotkey
+
+
+ Click to delete the hotkey
+
+
+
+
+
+
+ Reset
+ Resets user settings to their game default
+
+
+
+ Save and close
+ Saves changes and go back to the hotkey window.
+
+
+
+ Cancel
+ Unsaved changes will be lost
+
+
+
+
Index: binaries/data/mods/public/gui/hotkeys/page_hotkeys.xml
===================================================================
--- /dev/null
+++ binaries/data/mods/public/gui/hotkeys/page_hotkeys.xml
@@ -0,0 +1,14 @@
+
+
+ common/modern/setup.xml
+ common/modern/styles.xml
+ common/modern/sprites.xml
+
+ common/setup.xml
+ common/sprites.xml
+ common/styles.xml
+
+ common/global.xml
+
+ hotkeys/hotkeys.xml
+
Index: binaries/data/mods/public/gui/hotkeys/utils.js
===================================================================
--- /dev/null
+++ binaries/data/mods/public/gui/hotkeys/utils.js
@@ -0,0 +1,21 @@
+/**
+ * Holds a map of scancode name -> user keyboard name
+ */
+var g_ScancodesMap = {};
+
+function hotkeySort(a, b)
+{
+ const specialKeys = ["Shift", "Alt", "Ctrl", "Super"];
+ // Quick hack to put those first.
+ if (specialKeys.indexOf(a) !== -1)
+ a = ' ' + a;
+ if (specialKeys.indexOf(b) !== -1)
+ b = ' ' + b;
+ return a.localeCompare(b, Engine.GetCurrentLocale().substr(0, 2), { "numeric": true });
+}
+
+function formatHotkeyCombination(comb)
+{
+ let str = comb.sort(hotkeySort).map(hk => g_ScancodesMap[hk]).join("+");
+ return str.replace('\\', '\\\\').replace('[', '\\[');
+}
Index: binaries/data/mods/public/gui/options/options.xml
===================================================================
--- binaries/data/mods/public/gui/options/options.xml
+++ binaries/data/mods/public/gui/options/options.xml
@@ -58,7 +58,7 @@
Close
- Unsaved changes affect this session only
+ Unsaved changes will be lost
closePage();
Index: binaries/data/mods/public/gui/pregame/MainMenuItems.js
===================================================================
--- binaries/data/mods/public/gui/pregame/MainMenuItems.js
+++ binaries/data/mods/public/gui/pregame/MainMenuItems.js
@@ -148,6 +148,13 @@
fireConfigChangeHandlers);
}
},
+ {
+ "caption": translate("Hotkeys"),
+ "tooltip": translate("Adjust hotkeys."),
+ "onPress": () => {
+ Engine.PushGuiPage("hotkeys/page_hotkeys.xml");
+ }
+ },
{
"caption": translate("Language"),
"tooltip": translate("Choose the language of the game."),
Index: binaries/data/mods/public/gui/session/MenuButtons.js
===================================================================
--- binaries/data/mods/public/gui/session/MenuButtons.js
+++ binaries/data/mods/public/gui/session/MenuButtons.js
@@ -174,6 +174,27 @@
}
};
+MenuButtons.prototype.Hotkeys = class
+{
+ constructor(button, pauseControl)
+ {
+ this.button = button;
+ this.button.caption = translate("Hotkeys");
+ this.pauseControl = pauseControl;
+ }
+
+ onPress()
+ {
+ closeOpenDialogs();
+ this.pauseControl.implicitPause();
+
+ Engine.PushGuiPage(
+ "hotkeys/page_hotkeys.xml",
+ {},
+ () => { resumeGame(); });
+ }
+};
+
MenuButtons.prototype.Pause = class
{
constructor(button, pauseControl, playerViewControl)
Index: source/gui/CGUI.cpp
===================================================================
--- source/gui/CGUI.cpp
+++ source/gui/CGUI.cpp
@@ -150,24 +150,22 @@
m_MousePos = CPos((float)ev->ev.button.x / g_GuiScale, (float)ev->ev.button.y / g_GuiScale);
}
+ // Allow the focused object to pre-empt regular GUI events.
+ if (GetFocusedObject())
+ ret = GetFocusedObject()->PreemptEvent(ev);
+
// Only one object can be hovered
- IGUIObject* pNearest = nullptr;
+ // pNearest will after this point at the hovered object, possibly nullptr
+ IGUIObject* pNearest = FindObjectUnderMouse();
- // TODO Gee: (2004-09-08) Big TODO, don't do the below if the SDL_Event is something like a keypress!
+ if (ret == IN_PASS)
{
- PROFILE("mouse events");
- // TODO Gee: Optimizations needed!
- // these two recursive function are quite overhead heavy.
-
- // pNearest will after this point at the hovered object, possibly nullptr
- pNearest = FindObjectUnderMouse();
-
- // Now we'll call UpdateMouseOver on *all* objects,
- // we'll input the one hovered, and they will each
- // update their own data and send messages accordingly
- m_BaseObject.RecurseObject(&IGUIObject::IsHiddenOrGhost, &IGUIObject::UpdateMouseOver, static_cast(pNearest));
-
- if (ev->ev.type == SDL_MOUSEBUTTONDOWN)
+ if (ev->ev.type == SDL_MOUSEMOTION)
+ // Now we'll call UpdateMouseOver on *all* objects,
+ // we'll input the one hovered, and they will each
+ // update their own data and send messages accordingly
+ m_BaseObject.RecurseObject(&IGUIObject::IsHiddenOrGhost, &IGUIObject::UpdateMouseOver, static_cast(pNearest));
+ else if (ev->ev.type == SDL_MOUSEBUTTONDOWN)
{
switch (ev->ev.button.button)
{
@@ -252,19 +250,13 @@
if (ev->ev.type == SDL_MOUSEBUTTONDOWN || ev->ev.type == SDL_MOUSEBUTTONUP)
m_MousePos = oldMousePos;
- // Handle keys for input boxes
- if (GetFocusedObject())
+ // Let GUI items handle keys after everything else, e.g. for input boxes.
+ if (ret == IN_PASS && GetFocusedObject())
{
- if ((ev->ev.type == SDL_KEYDOWN &&
- ev->ev.key.keysym.sym != SDLK_ESCAPE &&
- !g_keys[SDLK_LCTRL] && !g_keys[SDLK_RCTRL] &&
- !g_keys[SDLK_LALT] && !g_keys[SDLK_RALT]) ||
- ev->ev.type == SDL_HOTKEYDOWN ||
- ev->ev.type == SDL_TEXTINPUT ||
- ev->ev.type == SDL_TEXTEDITING)
- {
- ret = GetFocusedObject()->ManuallyHandleEvent(ev);
- }
+ if (ev->ev.type == SDL_KEYUP || ev->ev.type == SDL_KEYDOWN ||
+ ev->ev.type == SDL_HOTKEYUP || ev->ev.type == SDL_HOTKEYDOWN ||
+ ev->ev.type == SDL_TEXTINPUT || ev->ev.type == SDL_TEXTEDITING)
+ ret = GetFocusedObject()->ManuallyHandleKeys(ev);
// else will return IN_PASS because we never used the button.
}
@@ -273,6 +265,7 @@
void CGUI::TickObjects()
{
+ m_BaseObject.RecurseObject(&IGUIObject::IsHiddenOrGhost, &IGUIObject::Tick);
SendEventToAll(EventNameTick);
m_Tooltip.Update(FindObjectUnderMouse(), m_MousePos, *this);
}
Index: source/gui/GUIObjectTypes.h
===================================================================
--- source/gui/GUIObjectTypes.h
+++ source/gui/GUIObjectTypes.h
@@ -21,6 +21,7 @@
#include "gui/ObjectTypes/CChart.h"
#include "gui/ObjectTypes/CCheckBox.h"
#include "gui/ObjectTypes/CDropDown.h"
+#include "gui/ObjectTypes/CHotkeyPicker.h"
#include "gui/ObjectTypes/CImage.h"
#include "gui/ObjectTypes/CInput.h"
#include "gui/ObjectTypes/CList.h"
@@ -39,6 +40,7 @@
AddObjectType("checkbox", &CCheckBox::ConstructObject);
AddObjectType("dropdown", &CDropDown::ConstructObject);
AddObjectType("empty", &CGUIDummyObject::ConstructObject);
+ AddObjectType("hotkeypicker", &CHotkeyPicker::ConstructObject);
AddObjectType("image", &CImage::ConstructObject);
AddObjectType("input", &CInput::ConstructObject);
AddObjectType("list", &CList::ConstructObject);
Index: source/gui/ObjectBases/IGUIObject.h
===================================================================
--- source/gui/ObjectBases/IGUIObject.h
+++ source/gui/ObjectBases/IGUIObject.h
@@ -255,6 +255,12 @@
//@{
public:
+
+ /**
+ * Called on every GUI tick unless the object or one of its parent is hidden/ghost.
+ */
+ virtual void Tick() {};
+
/**
* This function is called with different messages
* for instance when the mouse enters the object.
@@ -290,7 +296,17 @@
virtual void Draw() = 0;
/**
- * Some objects need to handle the SDL_Event_ manually.
+ * Some objects need to be able to pre-emptively process SDL_Event_.
+ *
+ * Only the object with focus will have this function called.
+ *
+ * Returns either IN_PASS or IN_HANDLED. If IN_HANDLED, then
+ * the event won't be passed on and processed by other handlers.
+ */
+ virtual InReaction PreemptEvent(const SDL_Event_* UNUSED(ev)) { return IN_PASS; }
+
+ /**
+ * Some objects need to handle the text-related SDL_Event_ manually.
* For instance the input box.
*
* Only the object with focus will have this function called.
@@ -299,7 +315,7 @@
* the key won't be passed on and processed by other handlers.
* This is used for keys that the GUI uses.
*/
- virtual InReaction ManuallyHandleEvent(const SDL_Event_* UNUSED(ev)) { return IN_PASS; }
+ virtual InReaction ManuallyHandleKeys(const SDL_Event_* UNUSED(ev)) { return IN_PASS; }
/**
* Loads a style.
Index: source/gui/ObjectTypes/CDropDown.h
===================================================================
--- source/gui/ObjectTypes/CDropDown.h
+++ source/gui/ObjectTypes/CDropDown.h
@@ -58,7 +58,7 @@
/**
* Handle events manually to catch keyboard inputting.
*/
- virtual InReaction ManuallyHandleEvent(const SDL_Event_* ev);
+ virtual InReaction ManuallyHandleKeys(const SDL_Event_* ev);
/**
* Draws the Button
Index: source/gui/ObjectTypes/CDropDown.cpp
===================================================================
--- source/gui/ObjectTypes/CDropDown.cpp
+++ source/gui/ObjectTypes/CDropDown.cpp
@@ -273,7 +273,7 @@
SetupText();
}
-InReaction CDropDown::ManuallyHandleEvent(const SDL_Event_* ev)
+InReaction CDropDown::ManuallyHandleKeys(const SDL_Event_* ev)
{
InReaction result = IN_PASS;
bool update_highlight = false;
@@ -298,7 +298,7 @@
if (!m_Open)
return IN_PASS;
// Set current selected item to highlighted, before
- // then really processing these in CList::ManuallyHandleEvent()
+ // then really processing these in CList::ManuallyHandleKeys()
SetSetting("selected", m_ElementHighlight, true);
update_highlight = true;
break;
@@ -356,7 +356,7 @@
}
}
- if (CList::ManuallyHandleEvent(ev) == IN_HANDLED)
+ if (CList::ManuallyHandleKeys(ev) == IN_HANDLED)
result = IN_HANDLED;
if (update_highlight)
Index: source/gui/ObjectTypes/CHotkeyPicker.h
===================================================================
--- /dev/null
+++ source/gui/ObjectTypes/CHotkeyPicker.h
@@ -0,0 +1,78 @@
+/* Copyright (C) 2020 Wildfire Games.
+ * This file is part of 0 A.D.
+ *
+ * 0 A.D. 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, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 0 A.D. 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. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with 0 A.D. If not, see .
+ */
+
+#ifndef INCLUDED_CHOTKEYPICKER
+#define INCLUDED_CHOTKEYPICKER
+
+#include "lib/external_libraries/libsdl.h"
+#include "ps/CStr.h"
+
+class ScriptInterface;
+
+/**
+ * When in focus, returns all currently pressed keys.
+ * After a set time without changes, it will trigger a "combination" event.
+ *
+ * Used to create new hotkey combinations in-game. Mostly custom.
+ * This object does not draw anything.
+ *
+ * NB: because of how input is handled, mouse clicks
+ */
+class CHotkeyPicker : public IGUIObject
+{
+ GUI_OBJECT(CHotkeyPicker)
+
+ friend class ScriptInterface;
+public:
+ CHotkeyPicker(CGUI& pGUI);
+ virtual ~CHotkeyPicker();
+
+ // Do nothing.
+ virtual void Draw() {};
+
+ // Checks if the timer has passed and we need to fire a "combination" event.
+ virtual void Tick();
+
+ // React to blur/focus.
+ virtual void HandleMessage(SGUIMessage& Message);
+
+ // Pre-empt events: this is our sole purpose.
+ virtual InReaction PreemptEvent(const SDL_Event_* ev);
+protected:
+ // Fire an event with m_KeysPressed as argument.
+ void FireEvent(const CStr& event);
+
+ // Time without changes until a "combination" event is sent.
+ float m_TimeToCombination;
+ // Time of the last registered key change.
+ double m_LastKeyChange;
+
+ // Keep track of which keys we are pressing, and precompute their name for JS code.
+ struct Key
+ {
+ // The scancode is used for fast comparisons.
+ SDL_Scancode code;
+ // This is the name ultimately stored in the config file.
+ CStr scancodeName;
+ };
+ std::vector m_KeysPressed;
+
+ static const CStr EventNameCombination;
+ static const CStr EventNameKeyChange;
+};
+
+#endif // INCLUDED_CHOTKEYPICKER
Index: source/gui/ObjectTypes/CHotkeyPicker.cpp
===================================================================
--- /dev/null
+++ source/gui/ObjectTypes/CHotkeyPicker.cpp
@@ -0,0 +1,191 @@
+/* Copyright (C) 2020 Wildfire Games.
+ * This file is part of 0 A.D.
+ *
+ * 0 A.D. 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, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 0 A.D. 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. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with 0 A.D. If not, see .
+ */
+
+#include "precompiled.h"
+
+#include "CHotkeyPicker.h"
+
+#include "lib/timer.h"
+#include "ps/Hotkey.h"
+#include "ps/KeyName.h"
+#include "scriptinterface/ScriptConversions.h"
+
+const CStr CHotkeyPicker::EventNameCombination = "Combination";
+const CStr CHotkeyPicker::EventNameKeyChange = "KeyChange";
+
+// Don't send the scancode, JS doesn't care.
+template<> void ScriptInterface::ToJSVal(JSContext *cx, JS::MutableHandleValue ret, const CHotkeyPicker::Key& val)
+{
+ ScriptInterface::ToJSVal(cx, ret, val.scancodeName);
+}
+
+// Unused, but JSVAL_VECTOR requires it.
+template<> bool ScriptInterface::FromJSVal(JSContext*, const JS::HandleValue, CHotkeyPicker::Key&)
+{
+ LOGWARNING("FromJSVal: Not implemented");
+ return false;
+}
+
+JSVAL_VECTOR(CHotkeyPicker::Key);
+
+CHotkeyPicker::CHotkeyPicker(CGUI& pGUI) : IGUIObject(pGUI), m_TimeToCombination(1.f)
+{
+ RegisterSetting("time_to_combination", m_TimeToCombination);
+ // 8 keys at the same time is probably more than we'll ever need.
+ m_KeysPressed.reserve(8);
+}
+
+CHotkeyPicker::~CHotkeyPicker()
+{
+}
+
+void CHotkeyPicker::FireEvent(const CStr& event)
+{
+ JSContext* cx = m_pGUI.GetScriptInterface()->GetContext();
+ JSAutoRequest rq(cx);
+
+ JS::AutoValueArray<1> args(cx);
+ JS::RootedValue keys(cx);
+ m_pGUI.GetScriptInterface()->ToJSVal(cx, &keys, m_KeysPressed);
+ args[0].set(keys);
+ ScriptEvent(event, args);
+}
+
+void CHotkeyPicker::Tick()
+{
+ if (m_KeysPressed.size() == 0)
+ return;
+
+ double time = timer_Time();
+ if (time - m_LastKeyChange < m_TimeToCombination)
+ return;
+
+ FireEvent(EventNameCombination);
+
+ return;
+}
+
+void CHotkeyPicker::HandleMessage(SGUIMessage& Message)
+{
+ IGUIObject::HandleMessage(Message);
+ switch (Message.type)
+ {
+ case GUIM_GOT_FOCUS:
+ case GUIM_LOST_FOCUS:
+ {
+ m_KeysPressed.clear();
+ m_LastKeyChange = timer_Time();
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+InReaction CHotkeyPicker::PreemptEvent(const SDL_Event_* ev)
+{
+ switch (ev->ev.type)
+ {
+ // Handle the same mouse events that hotkeys handle
+ case SDL_MOUSEBUTTONDOWN:
+ case SDL_MOUSEBUTTONUP:
+ case SDL_MOUSEWHEEL:
+ {
+ SDL_Scancode scancode;
+
+ if (ev->ev.type != SDL_MOUSEWHEEL)
+ {
+ // Wait a little bit -> this gets triggered when clicking on a button,
+ // but after the button click is processed, thus immediately triggering...
+ if (timer_Time()-m_LastKeyChange < 0.2)
+ return IN_HANDLED;
+ // This is from hotkeyHandler - not sure what it does in all honesty.
+ if(ev->ev.button.button >= SDL_BUTTON_X1)
+ scancode = static_cast(MOUSE_BASE + (int)ev->ev.button.button + 2);
+ else
+ scancode = static_cast(MOUSE_BASE + (int)ev->ev.button.button);
+ }
+ else
+ {
+ if (ev->ev.wheel.y > 0)
+ scancode = static_cast(MOUSE_WHEELUP);
+ else if (ev->ev.wheel.y < 0)
+ scancode = static_cast(MOUSE_WHEELDOWN);
+ else if (ev->ev.wheel.x > 0)
+ scancode = static_cast(MOUSE_X2);
+ else if (ev->ev.wheel.x < 0)
+ scancode = static_cast(MOUSE_X1);
+ else
+ return IN_HANDLED;
+ }
+ // Don't handle keys and mouse together except for modifiers.
+ std::remove_if(m_KeysPressed.begin(), m_KeysPressed.end(), [](const Key& k) { return k.code < UNIFIED_SHIFT || k.code >= UNIFIED_LAST; } );
+ m_KeysPressed.emplace_back(Key{scancode, FindScancodeName(scancode)});
+ // For mouse events, assume we immediately want to return.
+ FireEvent(EventNameCombination);
+
+ return IN_HANDLED;
+ }
+ case SDL_KEYDOWN:
+ case SDL_KEYUP:
+ {
+ SDL_Scancode scancode = ev->ev.key.keysym.scancode;
+
+ // Don't handle caps-lock, it doesn't really work in-game and it's a weird hotkey.
+ if (scancode == SDL_SCANCODE_CAPSLOCK)
+ return IN_PASS;
+
+ if (scancode == SDL_SCANCODE_LSHIFT || scancode == SDL_SCANCODE_RSHIFT)
+ scancode = static_cast(UNIFIED_SHIFT);
+ else if (scancode == SDL_SCANCODE_LCTRL || scancode == SDL_SCANCODE_RCTRL)
+ scancode = static_cast(UNIFIED_CTRL);
+ else if (scancode == SDL_SCANCODE_LALT || scancode == SDL_SCANCODE_RALT)
+ scancode = static_cast(UNIFIED_ALT);
+ else if (scancode == SDL_SCANCODE_LGUI || scancode == SDL_SCANCODE_RGUI)
+ scancode = static_cast(UNIFIED_SUPER);
+
+ if (ev->ev.type == SDL_KEYDOWN)
+ {
+ std::vector::const_iterator it = \
+ std::find_if(m_KeysPressed.begin(), m_KeysPressed.end(), [&scancode](Key& k) { return k.code == scancode; });
+ // Can happen if multiple keys are mapped the same.
+ if (it != m_KeysPressed.end())
+ return IN_HANDLED;
+ m_KeysPressed.emplace_back(Key{scancode, FindScancodeName(scancode)});
+ }
+ else
+ {
+ std::vector::const_iterator it = \
+ std::find_if(m_KeysPressed.begin(), m_KeysPressed.end(), [&scancode](Key& k) { return k.code == scancode; });
+ // Might happen if a key was down before this object is created.
+ if (it == m_KeysPressed.end())
+ return IN_HANDLED;
+ m_KeysPressed.erase(it);
+ }
+
+ FireEvent(EventNameKeyChange);
+
+ // Register after-JS in case this takes a while (probably not but it doesn't hurt).
+ m_LastKeyChange = timer_Time();
+ return IN_HANDLED;
+ }
+ default:
+ {
+ return IN_PASS;
+ }
+ }
+}
Index: source/gui/ObjectTypes/CInput.h
===================================================================
--- source/gui/ObjectTypes/CInput.h
+++ source/gui/ObjectTypes/CInput.h
@@ -64,7 +64,7 @@
/**
* Handle events manually to catch keyboard inputting.
*/
- virtual InReaction ManuallyHandleEvent(const SDL_Event_* ev);
+ virtual InReaction ManuallyHandleKeys(const SDL_Event_* ev);
/**
* Handle events manually to catch keys which change the text.
@@ -77,7 +77,7 @@
virtual void ManuallyImmutableHandleKeyDownEvent(const SDL_Keycode keyCode);
/**
- * Handle hotkey events (called by ManuallyHandleEvent)
+ * Handle hotkey events (called by ManuallyHandleKeys)
*/
virtual InReaction ManuallyHandleHotkeyEvent(const SDL_Event_* ev);
Index: source/gui/ObjectTypes/CInput.cpp
===================================================================
--- source/gui/ObjectTypes/CInput.cpp
+++ source/gui/ObjectTypes/CInput.cpp
@@ -114,7 +114,7 @@
m_iComposedPos = 0;
}
-InReaction CInput::ManuallyHandleEvent(const SDL_Event_* ev)
+InReaction CInput::ManuallyHandleKeys(const SDL_Event_* ev)
{
ENSURE(m_iBufferPos != -1);
@@ -1117,7 +1117,7 @@
evt.ev.edit.length = 0;
evt.ev.edit.start = 0;
evt.ev.edit.text[0] = 0;
- ManuallyHandleEvent(&evt);
+ ManuallyHandleKeys(&evt);
}
SDL_StopTextInput();
Index: source/gui/ObjectTypes/CList.h
===================================================================
--- source/gui/ObjectTypes/CList.h
+++ source/gui/ObjectTypes/CList.h
@@ -73,7 +73,7 @@
/**
* Handle events manually to catch keyboard inputting.
*/
- virtual InReaction ManuallyHandleEvent(const SDL_Event_* ev);
+ virtual InReaction ManuallyHandleKeys(const SDL_Event_* ev);
/**
* Draws the List box
Index: source/gui/ObjectTypes/CList.cpp
===================================================================
--- source/gui/ObjectTypes/CList.cpp
+++ source/gui/ObjectTypes/CList.cpp
@@ -251,7 +251,7 @@
IGUITextOwner::HandleMessage(Message);
}
-InReaction CList::ManuallyHandleEvent(const SDL_Event_* ev)
+InReaction CList::ManuallyHandleKeys(const SDL_Event_* ev)
{
InReaction result = IN_PASS;
Index: source/gui/Scripting/ScriptFunctions.cpp
===================================================================
--- source/gui/Scripting/ScriptFunctions.cpp
+++ source/gui/Scripting/ScriptFunctions.cpp
@@ -30,6 +30,7 @@
#include "ps/scripting/JSInterface_Console.h"
#include "ps/scripting/JSInterface_Debug.h"
#include "ps/scripting/JSInterface_Game.h"
+#include "ps/scripting/JSInterface_Hotkey.h"
#include "ps/scripting/JSInterface_Main.h"
#include "ps/scripting/JSInterface_Mod.h"
#include "ps/scripting/JSInterface_ModIo.h"
@@ -59,6 +60,7 @@
JSI_GUIManager::RegisterScriptFunctions(scriptInterface);
JSI_Game::RegisterScriptFunctions(scriptInterface);
JSI_GameView::RegisterScriptFunctions(scriptInterface);
+ JSI_Hotkey::RegisterScriptFunctions(scriptInterface);
JSI_L10n::RegisterScriptFunctions(scriptInterface);
JSI_Lobby::RegisterScriptFunctions(scriptInterface);
JSI_Main::RegisterScriptFunctions(scriptInterface);
Index: source/gui/tests/test_GuiManager.h
===================================================================
--- source/gui/tests/test_GuiManager.h
+++ source/gui/tests/test_GuiManager.h
@@ -73,7 +73,7 @@
// Press 'a'.
SDL_Event_ hotkeyNotification;
hotkeyNotification.ev.type = SDL_KEYDOWN;
- hotkeyNotification.ev.key.keysym.sym = SDLK_a;
+ hotkeyNotification.ev.key.keysym.scancode = SDL_SCANCODE_A;
hotkeyNotification.ev.key.repeat = 0;
// Init input and poll the event.
Index: source/ps/ConfigDB.h
===================================================================
--- source/ps/ConfigDB.h
+++ source/ps/ConfigDB.h
@@ -110,6 +110,8 @@
void SetValueBool(EConfigNamespace ns, const CStr& name, const bool value);
+ void SetValueList(EConfigNamespace ns, const CStr& name, std::vector values);
+
/**
* Remove a config value in the specified namespace.
*/
Index: source/ps/ConfigDB.cpp
===================================================================
--- source/ps/ConfigDB.cpp
+++ source/ps/ConfigDB.cpp
@@ -219,6 +219,18 @@
SetValueString(ns, name, valueString);
}
+void CConfigDB::SetValueList(EConfigNamespace ns, const CStr& name, std::vector values)
+{
+ CHECK_NS(;);
+
+ std::lock_guard s(cfgdb_mutex);
+ TConfigMap::iterator it = m_Map[ns].find(name);
+ if (it == m_Map[ns].end())
+ it = m_Map[ns].insert(m_Map[ns].begin(), make_pair(name, CConfigValueSet(1)));
+
+ it->second = values;
+}
+
void CConfigDB::RemoveValue(EConfigNamespace ns, const CStr& name)
{
CHECK_NS(;);
Index: source/ps/Hotkey.h
===================================================================
--- source/ps/Hotkey.h
+++ source/ps/Hotkey.h
@@ -33,15 +33,45 @@
#include "CStr.h"
#include "lib/input.h"
-#include "lib/external_libraries/libsdl.h" // see note below
-// note: we need the real SDL header - it defines SDL_USEREVENT, which is
-// required for our HOTKEY event type definition. this is OK since
-// hotkey.h is not included from any headers.
+#include
-const uint SDL_HOTKEYPRESS = SDL_USEREVENT;
-const uint SDL_HOTKEYDOWN = SDL_USEREVENT + 1;
-const uint SDL_HOTKEYUP = SDL_USEREVENT + 2;
+// SDL_Scancode is an enum, we'll use an explicit int to avoid including SDL in this header.
+using SDL_Scancode_ = int;
+
+// 0x8000 is SDL_USEREVENT, this is static_asserted in Hotkey.cpp
+// We do this to avoid including SDL in this header.
+const uint SDL_USEREVENT_ = 0x8000;
+const uint SDL_HOTKEYPRESS = SDL_USEREVENT_;
+const uint SDL_HOTKEYDOWN = SDL_USEREVENT_ + 1;
+const uint SDL_HOTKEYUP = SDL_USEREVENT_ + 2;
+
+struct SKey
+{
+ SDL_Scancode_ code; // scancode or MOUSE_ or UNIFIED_ value
+ bool negated; // whether the key must be pressed (false) or unpressed (true)
+
+ bool operator<(const SKey& o) const { return code < o.code && negated < o.negated; }
+ bool operator==(const SKey& o) const { return code == o.code && negated == o.negated; }
+};
+
+// Hotkey data associated with an externally-specified 'primary' keycode
+struct SHotkeyMapping
+{
+ CStr name; // name of the hotkey
+ bool negated; // whether the primary key must be pressed (false) or unpressed (true)
+ std::vector requires; // list of non-primary keys that must also be active
+};
+
+typedef std::vector KeyMapping;
+
+// A mapping of scancodes onto the hotkeys that are associated with that key.
+// (A hotkey triggered by a combination of multiple keys will be in this map
+// multiple times.)
+extern std::unordered_map g_HotkeyMap;
+
+// The current pressed status of hotkeys
+extern std::unordered_map g_HotkeyStatus;
extern void LoadHotkeys();
extern void UnloadHotkeys();
Index: source/ps/Hotkey.cpp
===================================================================
--- source/ps/Hotkey.cpp
+++ source/ps/Hotkey.cpp
@@ -20,6 +20,7 @@
#include
+#include "lib/external_libraries/libsdl.h"
#include "ps/CConsole.h"
#include "ps/CLogger.h"
#include "ps/CStr.h"
@@ -29,29 +30,11 @@
static bool unified[UNIFIED_LAST - UNIFIED_SHIFT];
-struct SKey
-{
- SDL_Keycode code; // keycode or MOUSE_ or UNIFIED_ value
- bool negated; // whether the key must be pressed (false) or unpressed (true)
-};
+std::unordered_map g_HotkeyMap;
+std::unordered_map g_HotkeyStatus;
-// Hotkey data associated with an externally-specified 'primary' keycode
-struct SHotkeyMapping
-{
- CStr name; // name of the hotkey
- bool negated; // whether the primary key must be pressed (false) or unpressed (true)
- std::vector requires; // list of non-primary keys that must also be active
-};
-
-typedef std::vector KeyMapping;
-
-// A mapping of keycodes onto the hotkeys that are associated with that key.
-// (A hotkey triggered by a combination of multiple keys will be in this map
-// multiple times.)
-static std::map g_HotkeyMap;
-
-// The current pressed status of hotkeys
-std::map g_HotkeyStatus;
+static_assert(std::is_integral::type>::value, "SDL_Scancode is not an integral enum.");
+static_assert(SDL_USEREVENT_ == SDL_USEREVENT, "SDL_USEREVENT_ is not the same type as the real SDL_USEREVENT");
// Look up each key binding in the config file and set the mappings for
// all key combinations that trigger it.
@@ -74,16 +57,14 @@
for (tokenizer::iterator it = tok.begin(); it != tok.end(); ++it)
{
// Attempt decode as key name
- int mapping = FindKeyCode(*it);
- if (!mapping)
- mapping = SDL_GetKeyFromName(it->c_str());
- if (!mapping)
+ SDL_Scancode scancode = FindScancode(it->c_str());
+ if (!scancode)
{
LOGWARNING("Hotkey mapping used invalid key '%s'", hotkey.c_str());
continue;
}
- SKey key = { (SDL_Keycode)mapping, false };
+ SKey key = { scancode, false };
keyCombination.push_back(key);
}
@@ -107,8 +88,6 @@
void LoadHotkeys()
{
- InitKeyNameMap();
-
LoadConfigBindings();
// Set up the state of the hotkeys given no key is down.
@@ -146,7 +125,7 @@
else if ((int)key.code < MOUSE_LAST && (int)key.code > MOUSE_BASE && g_mouse_buttons[key.code - MOUSE_BASE] == key.negated)
return false;
// Modifier keycodes are between the normal keys and the mouse 'keys'
- else if ((int)key.code < UNIFIED_LAST && (int)key.code > SDL_SCANCODE_TO_KEYCODE(SDL_NUM_SCANCODES) && unified[key.code - UNIFIED_SHIFT] == key.negated)
+ else if ((int)key.code < UNIFIED_LAST && (int)key.code > SDL_NUM_SCANCODES && unified[key.code - UNIFIED_SHIFT] == key.negated)
return false;
else
return true;
@@ -163,13 +142,13 @@
InReaction HotkeyInputHandler(const SDL_Event_* ev)
{
- int keycode = 0;
+ int scancode = SDL_SCANCODE_UNKNOWN;
switch(ev->ev.type)
{
case SDL_KEYDOWN:
case SDL_KEYUP:
- keycode = (int)ev->ev.key.keysym.sym;
+ scancode = ev->ev.key.keysym.scancode;
break;
case SDL_MOUSEBUTTONDOWN:
@@ -177,30 +156,30 @@
// Mousewheel events are no longer buttons, but we want to maintain the order
// expected by g_mouse_buttons for compatibility
if (ev->ev.button.button >= SDL_BUTTON_X1)
- keycode = MOUSE_BASE + (int)ev->ev.button.button + 2;
+ scancode = MOUSE_BASE + (int)ev->ev.button.button + 2;
else
- keycode = MOUSE_BASE + (int)ev->ev.button.button;
+ scancode = MOUSE_BASE + (int)ev->ev.button.button;
break;
case SDL_MOUSEWHEEL:
if (ev->ev.wheel.y > 0)
{
- keycode = MOUSE_WHEELUP;
+ scancode = MOUSE_WHEELUP;
break;
}
else if (ev->ev.wheel.y < 0)
{
- keycode = MOUSE_WHEELDOWN;
+ scancode = MOUSE_WHEELDOWN;
break;
}
else if (ev->ev.wheel.x > 0)
{
- keycode = MOUSE_X2;
+ scancode = MOUSE_X2;
break;
}
else if (ev->ev.wheel.x < 0)
{
- keycode = MOUSE_X1;
+ scancode = MOUSE_X1;
break;
}
return IN_PASS;
@@ -219,33 +198,33 @@
if (phantom.ev.type == SDL_KEYDOWN)
phantom.ev.key.repeat = ev->ev.type == SDL_KEYDOWN ? ev->ev.key.repeat : 0;
- if ((keycode == SDLK_LSHIFT) || (keycode == SDLK_RSHIFT))
+ if (scancode == SDL_SCANCODE_LSHIFT || scancode == SDL_SCANCODE_RSHIFT)
{
- phantom.ev.key.keysym.sym = (SDL_Keycode)UNIFIED_SHIFT;
+ phantom.ev.key.keysym.scancode = static_cast(UNIFIED_SHIFT);
unified[0] = (phantom.ev.type == SDL_KEYDOWN);
HotkeyInputHandler(&phantom);
}
- else if ((keycode == SDLK_LCTRL) || (keycode == SDLK_RCTRL))
+ else if (scancode == SDL_SCANCODE_LCTRL || scancode == SDL_SCANCODE_RCTRL)
{
- phantom.ev.key.keysym.sym = (SDL_Keycode)UNIFIED_CTRL;
+ phantom.ev.key.keysym.scancode = static_cast(UNIFIED_CTRL);
unified[1] = (phantom.ev.type == SDL_KEYDOWN);
HotkeyInputHandler(&phantom);
}
- else if ((keycode == SDLK_LALT) || (keycode == SDLK_RALT))
+ else if (scancode == SDL_SCANCODE_LALT || scancode == SDL_SCANCODE_RALT)
{
- phantom.ev.key.keysym.sym = (SDL_Keycode)UNIFIED_ALT;
+ phantom.ev.key.keysym.scancode = static_cast(UNIFIED_ALT);
unified[2] = (phantom.ev.type == SDL_KEYDOWN);
HotkeyInputHandler(&phantom);
}
- else if ((keycode == SDLK_LGUI) || (keycode == SDLK_RGUI))
+ else if (scancode == SDL_SCANCODE_LGUI || scancode == SDL_SCANCODE_RGUI)
{
- phantom.ev.key.keysym.sym = (SDL_Keycode)UNIFIED_SUPER;
+ phantom.ev.key.keysym.scancode = static_cast(UNIFIED_SUPER);
unified[3] = (phantom.ev.type == SDL_KEYDOWN);
HotkeyInputHandler(&phantom);
}
// Check whether we have any hotkeys registered for this particular keycode
- if (g_HotkeyMap.find(keycode) == g_HotkeyMap.end())
+ if (g_HotkeyMap.find(scancode) == g_HotkeyMap.end())
return (IN_PASS);
// Inhibit the dispatch of hotkey events caused by real keys (not fake mouse button
@@ -253,7 +232,7 @@
bool consoleCapture = false;
- if (g_Console && g_Console->IsActive() && keycode < SDL_SCANCODE_TO_KEYCODE(SDL_NUM_SCANCODES))
+ if (g_Console && g_Console->IsActive() && scancode < SDL_NUM_SCANCODES)
consoleCapture = true;
// Here's an interesting bit:
@@ -273,7 +252,7 @@
std::vector closestMapNames;
size_t closestMapMatch = 0;
- for (const SHotkeyMapping& hotkey : g_HotkeyMap[keycode])
+ for (const SHotkeyMapping& hotkey : g_HotkeyMap[scancode])
{
// If a key has been pressed, and this event triggers on its release, skip it.
// Similarly, if the key's been released and the event triggers on a keypress, skip it.
@@ -329,7 +308,7 @@
// -- KEYUP SECTION --
- for (const SHotkeyMapping& hotkey : g_HotkeyMap[keycode])
+ for (const SHotkeyMapping& hotkey : g_HotkeyMap[scancode])
{
// If it's a keydown event, won't cause HotKeyUps in anything that doesn't
// use this key negated => skip them
Index: source/ps/KeyName.h
===================================================================
--- source/ps/KeyName.h
+++ source/ps/KeyName.h
@@ -23,14 +23,16 @@
class CStr8;
-extern void InitKeyNameMap();
-extern CStr8 FindKeyName(int keycode);
-extern int FindKeyCode(const CStr8& keyname);
+extern SDL_Scancode FindScancode(const CStr& keyname);
+// Map a scancode to a locale-independent scancode name.
+extern CStr8 FindScancodeName(SDL_Scancode scancode);
+// Map a scancode to a locale-dependent key name (to show the user).
+extern CStr8 FindKeyName(SDL_Scancode scancode);
enum {
// Start sequential IDs in the right place
- // Pick a code which is greater than any keycodes used by SDL itself
- EXTRA_KEYS_BASE = SDL_SCANCODE_TO_KEYCODE(SDL_NUM_SCANCODES),
+ // Pick a code which is greater than any scancodes used by SDL itself
+ EXTRA_KEYS_BASE = SDL_NUM_SCANCODES,
// 'Keycodes' for the unified modifier keys
UNIFIED_SHIFT,
UNIFIED_CTRL,
Index: source/ps/KeyName.cpp
===================================================================
--- source/ps/KeyName.cpp
+++ source/ps/KeyName.cpp
@@ -24,215 +24,111 @@
#include "lib/external_libraries/libsdl.h"
#include "ps/CStr.h"
-#include