File size: 9,777 Bytes
b6a38d7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
if FirstLoad then
	MouseButtonImagesInText = {
		-- Add project specific images
		--[[["MouseL"] = "UI/Infopanel/left_click.tga",
		["MouseR"] = "UI/Infopanel/right_click.tga",
		["MouseM"] = "UI/Infopanel/middle_click.tga",
		["MouseX1"] = "UI/Infopanel/button_4.tga",
		["MouseX2"] = "UI/Infopanel/button_5.tga",
		["MouseWheelFwd"] = "UI/Infopanel/scroll_up.tga",
		["MouseWheelBack"] = "UI/Infopanel/scroll_down.tga",]]
	}

	MouseButtonNames = {
		MouseL = T(344793107847, "Left Mouse Button"),
		MouseR = T(620781110653, "Right Mouse Button"),
		MouseM = T(814937893055, "Middle Mouse Button"),
		MouseX1 = T(404129049676, "Mouse Button 4"),
		MouseX2 = T(640322216255, "Mouse Button 5"),
		MouseWheelFwd = T(286518835802, "Mouse Wheel Forward"),
		MouseWheelBack = T(889465032724, "Mouse Wheel Back"),
	}
	
	ForbiddenShortcutKeys = {
		Lwin = true,
		Rwin = true,
		Menu = true,
		MouseL = true,
		MouseR = true,
		MouseM = true,
		Enter = true,
	}
	
	NonBindableKeys = {}
end

function GatherNonBindableKeys()
	local nonBindableKeys = {}
	for _, action in ipairs(XShortcutsTarget:GetActions()) do
		if action.ActionMode ~= "Editor" and not action.ActionBindable then
			table.insert(nonBindableKeys, action)
		end
	end
	return nonBindableKeys
end

TFormat.GamepadShortcutName = function(context_obj, shortcut)
	if not shortcut or shortcut == "" then
		return T(879415238341, "<negative>Unassigned</negative>")
	end
	local buttons = SplitShortcut(shortcut)
	for i, button in ipairs(buttons) do
		buttons[i] = const.TagLookupTable[button] or GetPlatformSpecificImageTag(button) or "?"
	end
	return Untranslated(table.concat(buttons))
end

EmShortcutNames = false
ShortcutIconScale = " 1000"

TFormat.KeyboardAndMouseShortcutName = function(context_obj, shortcut, scale, unassigned_str)
	if not shortcut or shortcut == "" then
		return unassigned_str or T(879415238341, "<negative>Unassigned</negative>")
	end
	local keys = SplitShortcut(shortcut)
	scale = scale or ShortcutIconScale
	local texts = {}
	for _, key in ipairs(keys) do
		local img = MouseButtonImagesInText[key]
		if img then
			texts[#texts + 1] = Untranslated("<image " .. img .. scale .. ">")
			goto continue
		end
		local text = MouseButtonNames[key] or KeyNames[VKStrNamesInverse[key]]
		assert(text, "Unknown mouse button name")
		if not text then
			goto continue
		end
		if EmShortcutNames then
			texts[#texts + 1] = T{399372771879, "<em><name></em>", name = text}
		else
			texts[#texts + 1] = T{629765447024, "<name>", name = text}
		end
		::continue::
    end
	return table.concat(texts, "-")
end

TFormat.ShortcutName = function(context_obj, action_id, source, scale)
	local shortcuts = GetShortcuts(action_id)
	if GetUIStyleGamepad() and (not source or source == "gamepad") then
		return TFormat.GamepadShortcutName(context_obj, shortcuts and shortcuts[3])
	else
		return TFormat.KeyboardAndMouseShortcutName(context_obj, shortcuts and shortcuts[1], scale)
	end
end

TwinShortcuts = {
	DPadUp = { DPadDown = "DPadUpDown" },
	DPadDown = { DPadUp = "DPadUpDown" },
	DPadLeft = { DPadRight = "DPadLeftRight" },
	DPadRight = { DPadLeft = "DPadLeftRight" },
}

local function GetTwinShortcutIcon(shortcut1, shortcut2)
	if not shortcut1 or shortcut1 == "" then return end
	if not shortcut2 or shortcut2 == "" then return end
	local buttons1 = SplitShortcut(shortcut1)
	local buttons2 = SplitShortcut(shortcut2)
	local button_cnt = #buttons1
	if button_cnt ~= #buttons2 then return end
	for i = 1, button_cnt - 1 do
		if buttons1[i] ~= buttons2[i] then return end
	end
	local twin_button = table.get(TwinShortcuts[buttons1[button_cnt]], buttons2[button_cnt])
	if not twin_button then return end
	local twin_shortcut_icon = const.TagLookupTable[twin_button] or GetPlatformSpecificImageTag(twin_button)
	return twin_shortcut_icon, buttons1
end

TwinShortcutSeparator = T(522258393731, " / ")

--- Combines two related shortcuts into one
-- If RT+DPadUp and RT+DPadDown are passed then the return value will be RT+DPadUpDown.
-- Shortcut relations are specified in the TwinShortcuts table.
-- If two unrelated shortcuts are passed then the return value will be those shortcuts concatenated with TwinShortcutSeparator.
TFormat.TwinShortcutNames = function(context_obj, action1_id, action2_id, source, separator)
	local shortcuts1 = GetShortcuts(action1_id)
	local shortcuts2 = GetShortcuts(action2_id)
	local shortcut1, shortcut2, shortcut_name_func
	if GetUIStyleGamepad() and (not source or source == "gamepad") then
		shortcut1 = shortcuts1 and shortcuts1[3]
		shortcut2 = shortcuts2 and shortcuts2[3]
		shortcut_name_func = TFormat.GamepadShortcutName
	else
		shortcut1 = shortcuts1 and shortcuts1[1]
		shortcut2 = shortcuts2 and shortcuts2[1]
		shortcut_name_func = TFormat.KeyboardAndMouseShortcutName
	end
	local twin_shortcut_icon, buttons = GetTwinShortcutIcon(shortcut1, shortcut2)
	if twin_shortcut_icon then
		local button_cnt = #buttons
		for i = 1, button_cnt - 1 do
			local button = buttons[i]
			buttons[i] = const.TagLookupTable[button] or GetPlatformSpecificImageTag(button) or "?"
		end
		buttons[button_cnt] = twin_shortcut_icon
		return Untranslated(table.concat(buttons))
	else
		return Untranslated(table.concat({
			shortcut_name_func(context_obj, shortcut1),
			shortcut_name_func(context_obj, shortcut2)
		}, separator or TwinShortcutSeparator))
	end
end

function ShortcutKeysToText(keys)
	local texts = {}
	for k, v in ipairs(keys) do
		texts[k] = MouseButtonNames[v] or KeyNames[VKStrNamesInverse[v]]
	end
	return table.concat(texts, "-")
end

EmptyKeyImageInText = false
KeybindingImageScale = " 1100"
function KeybindingName(shortcut)
	local empty_key = EmptyKeyImageInText and Untranslated("<image " .. EmptyKeyImageInText .. KeybindingImageScale .. ">") or ""
	return TFormat.KeyboardAndMouseShortcutName(nil, shortcut, KeybindingImageScale, empty_key)
end

function RebindKeys(idx, prop_ctrl)
	CreateRealTimeThread(function(idx, prop_ctrl)
		local obj = ResolvePropObj(prop_ctrl.context)
		local prop_meta = prop_ctrl.prop_meta
		local prop_id = prop_meta.id
		local prop_name = prop_meta.name
		local dlg = CreateMessageBox(terminal.desktop, T(""), T{529975158495, "Press a key to assign to <action>...\nPress Esc to cancel.", action = prop_name})
		dlg:PreventClose()
		local shortcut, keys, last_key
		repeat
			shortcut = WaitShortcut()
			if prop_ctrl.window_state == "destroying" then
				dlg:Close()
				return
			end
			if shortcut then
				keys = SplitShortcut(shortcut)
				last_key = keys[#keys]
				if shortcut ~= "Escape" then
					Msg("OptionsChanged")
				end
			end
		until shortcut and not ForbiddenShortcutKeys[last_key] and not (prop_meta.single_key and (last_key == "Ctrl" or last_key == "Shift"))
		if prop_meta.single_key then
			shortcut = last_key
			keys = {last_key}
		end
		shortcut = last_key ~= "Escape" and shortcut
		if MouseButtonNames[last_key] and not prop_meta.mouse_bindable then
			local parent = dlg.parent
			dlg:Close()
			CreateMessageBox(parent, T(207596731516, "Conflicting controls"),
				T{301773001578, "<key> cannot be used for <action>.",
				key = MouseButtonNames[last_key] or T{last_key}, action = prop_name},
				T(325411474155, "OK"))
			return
		end
		local nonRebindableAction = table.find_value(NonBindableKeys, "ActionShortcut", last_key)
		if shortcut and nonRebindableAction and EnabledInModes(nonRebindableAction.ActionMode, prop_meta.mode) then
			local parent = dlg.parent
			dlg:Close()
			CreateMessageBox(parent, T(207596731516, "Conflicting controls"),
				T{163533339775, "<newKey> is already used by a non rebindable action <nonRebindableKey>",
				newKey = KeybindingName(last_key) or T{last_key}, nonRebindableKey = nonRebindableAction.ActionTranslate and nonRebindableAction.ActionName or ""},
				T(325411474155, "OK"))
			return
		end
		if shortcut then
			for _, ctrl in ipairs(prop_ctrl.parent) do
				local ctrl_meta = ctrl.prop_meta
				if not ctrl_meta then goto continue end
				local bindings = obj[ctrl_meta.id]
				if bindings and EnabledInModes(ctrl_meta.mode, prop_meta.mode) then
					for i = 1, #bindings do 
						if ctrl_meta.id ~= prop_id and bindings[i] == shortcut then
							local old_action = ctrl_meta.name
							local new_action = prop_name
							if dlg.window_state == "open" then
								dlg:Close()
							end
							
							local res = WaitQuestion(terminal.desktop,
								T(207596731516, "Conflicting controls"),
								T{905663676426, "Do you want to rebind <key> from <old_action> to <new_action>?",
									key = ShortcutKeysToText(keys),
									old_action = old_action,
									new_action = new_action
								},
								T(689884995409, "Yes"),
								T(782927325160, "No")
							)
							if res == "ok" then
								-- clear binding
								bindings = table.copy(bindings) --don't overwrite the default values
								bindings[1] = i ~= 1 and bindings[1] or bindings[2] or ""
								bindings[2] = ""
								obj:SetProperty(ctrl_meta.id, bindings)
								ctrl.value = bindings
								ctrl:OnPropUpdate(ctrl.context, ctrl_meta, bindings)
								break
							else
								return
							end
						end
					end
				end
				::continue::
			end
			local bindings = obj[prop_meta.id]
			bindings = bindings and table.copy(bindings) or {} --don't overwrite the default values
			idx = bindings[1] and idx or 1
			bindings[idx] = shortcut
			if #bindings > 1 and bindings[1] == bindings[2] then
				bindings[2] = ""
			end
			obj:SetProperty(prop_meta.id, bindings)
			prop_ctrl.value = bindings
			prop_ctrl:OnPropUpdate(prop_ctrl.context, prop_meta, bindings)
		end
		if dlg and dlg.window_state ~= "destroying" then
			dlg:Close()
		end
	end, idx, prop_ctrl)
end

ShouldAttachSelectionShortcutWork = return_true
ShouldBlackPlanesShortcutWork = return_true