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
|
extends Node
const TRANSLATIONS_PATH := "res://addons/keychain/translations"
const PROFILES_PATH := "user://shortcut_profiles"
# Change these settings
var profiles := [preload("profiles/default.tres")]
var selected_profile: ShortcutProfile = profiles[0]
var profile_index := 0
# Syntax: "action_name": InputAction.new("Action Display Name", "Group", true)
# Note that "action_name" must already exist in the Project's Input Map.
var actions := {}
# Syntax: "Group Name": InputGroup.new("Parent Group Name")
var groups := {}
var ignore_actions := []
var ignore_ui_actions := true
var changeable_types := [true, true, true, true]
var multiple_menu_accelerators := false
var config_path := "user://cache.ini"
var config_file: ConfigFile
class InputAction:
var display_name := ""
var group := ""
var global := true
func _init(_display_name := "",_group := "",_global := true):
display_name = _display_name
group = _group
global = _global
func update_node(_action: String) -> void:
pass
func handle_input(_event: InputEvent, _action: String) -> bool:
return false
# This class is useful for the accelerators of PopupMenu items
# It's possible for PopupMenu items to have multiple shortcuts by using
# set_item_shortcut(), but we have no control over the accelerator text that appears.
# Thus, we are stuck with using accelerators instead of shortcuts.
# If Godot ever receives the ability to change the accelerator text of the items,
# we could in theory remove this class.
# If you don't care about PopupMenus in the same scene as ShortcutEdit
# such as projects like Pixelorama where everything is in the same scene,
# then you can ignore this class.
class MenuInputAction:
extends InputAction
var node_path := ""
var node: PopupMenu
var menu_item_id := 0
var echo := false
func _init(
_display_name := "",
_group := "",
_global := true,
_node_path := "",
_menu_item_id := 0,
_echo := false
) -> void:
super._init(_display_name, _group, _global)
node_path = _node_path
menu_item_id = _menu_item_id
echo = _echo
func get_node(root: Node) -> void:
var temp_node = root.get_node(node_path)
if temp_node is PopupMenu:
node = node
elif temp_node is MenuButton:
node = temp_node.get_popup()
func update_node(action: String) -> void:
if !node:
return
var first_key: InputEventKey = Keychain.action_get_first_key(action)
var accel := first_key.get_keycode_with_modifiers() if first_key else 0
node.set_item_accelerator(menu_item_id, accel)
func handle_input(event: InputEvent, action: String) -> bool:
if not node:
return false
if event.is_action_pressed(action, false, true):
if event is InputEventKey:
var acc: int = node.get_item_accelerator(menu_item_id)
# If the event is the same as the menu item's accelerator, skip
if acc == event.get_keycode_with_modifiers():
return true
node.emit_signal("id_pressed", menu_item_id)
return true
if event.is_action(action, true) and echo:
if event.is_echo():
node.emit_signal("id_pressed", menu_item_id)
return true
return false
class InputGroup:
var parent_group := ""
var folded := true
var tree_item: TreeItem
func _init(_parent_group := "", _folded := true) -> void:
parent_group = _parent_group
folded = _folded
func _ready() -> void:
if !config_file:
config_file = ConfigFile.new()
if !config_path.is_empty():
config_file.load(config_path)
set_process_input(multiple_menu_accelerators)
# Load shortcut profiles
DirAccess.make_dir_recursive_absolute(PROFILES_PATH)
var profile_dir := DirAccess.open(PROFILES_PATH)
profile_dir.make_dir(PROFILES_PATH)
profile_dir.list_dir_begin() # TODOGODOT4 fill missing arguments https://github.com/godotengine/godot/pull/40547
var file_name = profile_dir.get_next()
while file_name != "":
if !profile_dir.current_is_dir():
if file_name.get_extension() == "tres":
var file = load(PROFILES_PATH.path_join(file_name))
if file is ShortcutProfile:
profiles.append(file)
file_name = profile_dir.get_next()
# If there are no profiles besides the default, create one custom
if profiles.size() == 1:
var profile := ShortcutProfile.new()
profile.name = "Custom"
profile.resource_path = PROFILES_PATH.path_join("custom.tres")
var saved := profile.save()
if saved:
profiles.append(profile)
for profile in profiles:
profile.fill_bindings()
var l18n_dir := DirAccess.open(TRANSLATIONS_PATH)
l18n_dir.list_dir_begin() # TODOGODOT4 fill missing arguments https://github.com/godotengine/godot/pull/40547
file_name = l18n_dir.get_next()
while file_name != "":
if !l18n_dir.current_is_dir():
if file_name.get_extension() == "po":
var t: Translation = load(TRANSLATIONS_PATH.path_join(file_name))
TranslationServer.add_translation(t)
file_name = l18n_dir.get_next()
profile_index = config_file.get_value("shortcuts", "shortcuts_profile", 0)
change_profile(profile_index)
for action in actions:
var input_action: InputAction = actions[action]
if input_action is MenuInputAction:
input_action.get_node(get_tree().current_scene)
func _input(event: InputEvent) -> void:
if event is InputEventMouseMotion:
return
for action in actions:
var input_action: InputAction = actions[action]
var done: bool = input_action.handle_input(event, action)
if done:
return
func change_profile(index: int) -> void:
if index >= profiles.size():
index = profiles.size() - 1
profile_index = index
selected_profile = profiles[index]
for action in selected_profile.bindings:
action_erase_events(action)
for event in selected_profile.bindings[action]:
action_add_event(action, event)
func action_add_event(action: String, event: InputEvent) -> void:
InputMap.action_add_event(action, event)
if action in actions:
actions[action].update_node(action)
func action_erase_event(action: String, event: InputEvent) -> void:
InputMap.action_erase_event(action, event)
if action in actions:
actions[action].update_node(action)
func action_erase_events(action: String) -> void:
InputMap.action_erase_events(action)
if action in actions:
actions[action].update_node(action)
func action_get_first_key(action: String) -> InputEventKey:
var first_key: InputEventKey = null
var events := InputMap.action_get_events(action)
for event in events:
if event is InputEventKey:
first_key = event
break
return first_key
|