Index: binaries/data/config/default.cfg =================================================================== --- binaries/data/config/default.cfg +++ binaries/data/config/default.cfg @@ -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,7 +1,7 @@ ## This file documents keynames that can be used in .cfg files for specifying hotkeys ## 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 @@ -19,7 +19,7 @@ (, LeftParen ), RightParen *, Asterisk -+, Plus ++, Plus ## This maps to the same key as = ,, Comma -, Minus ., Period @@ -34,7 +34,6 @@ 7 8 9 -:, Colon ;, Semicolon <, LessThan =, Equals @@ -92,15 +91,15 @@ Numpad Enter, NumEnter Numpad =, NumEquals -Arrow Up, UpArrow -Arrow Down, DownArrow -Arrow Right, RightArrow -Arrow Left, LeftArrow +UpArrow +DownArrow +RightArrow +LeftArrow Insert, Ins Home End -Page Up, PgUp -Page Down, PgDn +PageUp +PageDown F1 F2 @@ -149,6 +148,5 @@ 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) +Mouse Wheel Left, MouseX1 +Mouse Wheel Right, MouseX2 Index: binaries/data/mods/public/gui/hotkeys/HotkeyPicker.js =================================================================== --- /dev/null +++ binaries/data/mods/public/gui/hotkeys/HotkeyPicker.js @@ -0,0 +1,106 @@ +/** + * 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); + + Engine.GetGUIObjectByName("hotkeyPickerDesc").caption = '' + + `Click on any mapping to modify it.`; + + this.setupCombinations(); + + 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(); + }; + Engine.GetGUIObjectByName("hotkeyPickerCancel").onPress = () => { + onClose(this, false); + } + Engine.GetGUIObjectByName("hotkeyPickerAccept").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 = translate(`#${i+1}`); + + if (i == this.combinations.length) + this.combinations.push([]); + + let input = Engine.GetGUIObjectByName("combMapping[" + i + "]"); + input.caption = formatHotkeyCombination(this.combinations[i]); + + let picker = Engine.GetGUIObjectByName("picker[" + i + "]"); + Engine.GetGUIObjectByName("combMappingBtn[" + i + "]").onPress = () => { + if (this.enteringInput) + { + + } + input.caption = translate("Enter new Hotkey, hold to register."); + this.enteringInput = i; + picker.focus(); + }; + + 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 = (i => () => { + this.combinations[i] = []; + input.caption = ""; + })(i); + + Engine.GetGUIObjectByName("conflicts[" + i + "]").caption = ""; + + picker.onCombination = (i => keys => { + this.combinations[i] = keys; + input.caption = formatHotkeyCombination(this.combinations[i]); + picker.blur(); + + let conflicts = (Engine.GetConflicts(keys) || []) + .filter(name => name != this.name).map(translate); + if (conflicts.length) + Engine.GetGUIObjectByName("conflicts[" + i + "]").caption = + translate(`${coloredText("Conflicts with", "255 153 0")}: ${conflicts.join(", ")}`); + })(i); + } + } + + close() + { + this.window.hidden = true; + for (let i = 0; i < 4; ++i) + Engine.GetGUIObjectByName("picker[" + i + "]").blur(); + } + + renderButtons() + { + Engine.GetGUIObjectByName("hotkeyPickerAccept").enabled = !!this.combination; + Engine.GetGUIObjectByName("hotkeyPickerRepick").enabled = !!this.combination; + } +} 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; + 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("hotkeyCancel").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,103 @@ + + + + +