1 | /**************************************************************************/ |
2 | /* event_listener_line_edit.cpp */ |
3 | /**************************************************************************/ |
4 | /* This file is part of: */ |
5 | /* GODOT ENGINE */ |
6 | /* https://godotengine.org */ |
7 | /**************************************************************************/ |
8 | /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ |
9 | /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ |
10 | /* */ |
11 | /* Permission is hereby granted, free of charge, to any person obtaining */ |
12 | /* a copy of this software and associated documentation files (the */ |
13 | /* "Software"), to deal in the Software without restriction, including */ |
14 | /* without limitation the rights to use, copy, modify, merge, publish, */ |
15 | /* distribute, sublicense, and/or sell copies of the Software, and to */ |
16 | /* permit persons to whom the Software is furnished to do so, subject to */ |
17 | /* the following conditions: */ |
18 | /* */ |
19 | /* The above copyright notice and this permission notice shall be */ |
20 | /* included in all copies or substantial portions of the Software. */ |
21 | /* */ |
22 | /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ |
23 | /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ |
24 | /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ |
25 | /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ |
26 | /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ |
27 | /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ |
28 | /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ |
29 | /**************************************************************************/ |
30 | |
31 | #include "editor/event_listener_line_edit.h" |
32 | |
33 | #include "core/input/input_map.h" |
34 | |
35 | // Maps to 2*axis if value is neg, or 2*axis+1 if value is pos. |
36 | static const char *_joy_axis_descriptions[(size_t)JoyAxis::MAX * 2] = { |
37 | TTRC("Left Stick Left, Joystick 0 Left" ), |
38 | TTRC("Left Stick Right, Joystick 0 Right" ), |
39 | TTRC("Left Stick Up, Joystick 0 Up" ), |
40 | TTRC("Left Stick Down, Joystick 0 Down" ), |
41 | TTRC("Right Stick Left, Joystick 1 Left" ), |
42 | TTRC("Right Stick Right, Joystick 1 Right" ), |
43 | TTRC("Right Stick Up, Joystick 1 Up" ), |
44 | TTRC("Right Stick Down, Joystick 1 Down" ), |
45 | TTRC("Joystick 2 Left" ), |
46 | TTRC("Left Trigger, Sony L2, Xbox LT, Joystick 2 Right" ), |
47 | TTRC("Joystick 2 Up" ), |
48 | TTRC("Right Trigger, Sony R2, Xbox RT, Joystick 2 Down" ), |
49 | TTRC("Joystick 3 Left" ), |
50 | TTRC("Joystick 3 Right" ), |
51 | TTRC("Joystick 3 Up" ), |
52 | TTRC("Joystick 3 Down" ), |
53 | TTRC("Joystick 4 Left" ), |
54 | TTRC("Joystick 4 Right" ), |
55 | TTRC("Joystick 4 Up" ), |
56 | TTRC("Joystick 4 Down" ), |
57 | }; |
58 | |
59 | String EventListenerLineEdit::get_event_text(const Ref<InputEvent> &p_event, bool p_include_device) { |
60 | ERR_FAIL_COND_V_MSG(p_event.is_null(), String(), "Provided event is not a valid instance of InputEvent" ); |
61 | |
62 | String text; |
63 | Ref<InputEventKey> key = p_event; |
64 | if (key.is_valid()) { |
65 | String mods_text = key->InputEventWithModifiers::as_text(); |
66 | mods_text = mods_text.is_empty() ? mods_text : mods_text + "+" ; |
67 | if (key->is_command_or_control_autoremap()) { |
68 | if (OS::get_singleton()->has_feature("macos" ) || OS::get_singleton()->has_feature("web_macos" ) || OS::get_singleton()->has_feature("web_ios" )) { |
69 | mods_text = mods_text.replace("Command" , "Command/Ctrl" ); |
70 | } else { |
71 | mods_text = mods_text.replace("Ctrl" , "Command/Ctrl" ); |
72 | } |
73 | } |
74 | |
75 | if (key->get_keycode() != Key::NONE) { |
76 | text += mods_text + keycode_get_string(key->get_keycode()); |
77 | } |
78 | if (key->get_physical_keycode() != Key::NONE) { |
79 | if (!text.is_empty()) { |
80 | text += " " + TTR("or" ) + " " ; |
81 | } |
82 | text += mods_text + keycode_get_string(key->get_physical_keycode()) + " (" + TTR("Physical" ) + ")" ; |
83 | } |
84 | if (key->get_key_label() != Key::NONE) { |
85 | if (!text.is_empty()) { |
86 | text += " " + TTR("or" ) + " " ; |
87 | } |
88 | text += mods_text + keycode_get_string(key->get_key_label()) + " (" + TTR("Unicode" ) + ")" ; |
89 | } |
90 | |
91 | if (text.is_empty()) { |
92 | text = "(" + TTR("Unset" ) + ")" ; |
93 | } |
94 | } else { |
95 | text = p_event->as_text(); |
96 | } |
97 | |
98 | Ref<InputEventMouse> mouse = p_event; |
99 | Ref<InputEventJoypadMotion> jp_motion = p_event; |
100 | Ref<InputEventJoypadButton> jp_button = p_event; |
101 | if (jp_motion.is_valid()) { |
102 | // Joypad motion events will display slightly differently than what the event->as_text() provides. See #43660. |
103 | String desc = TTR("Unknown Joypad Axis" ); |
104 | if (jp_motion->get_axis() < JoyAxis::MAX) { |
105 | desc = TTR(_joy_axis_descriptions[2 * (size_t)jp_motion->get_axis() + (jp_motion->get_axis_value() < 0 ? 0 : 1)]); |
106 | } |
107 | |
108 | // TRANSLATORS: %d is the axis number, the first %s is either "-" or "+", and the second %s is the description of the axis. |
109 | text = vformat(TTR("Joypad Axis %d %s (%s)" ), (int64_t)jp_motion->get_axis(), jp_motion->get_axis_value() < 0 ? "-" : "+" , desc); |
110 | } |
111 | if (p_include_device && (mouse.is_valid() || jp_button.is_valid() || jp_motion.is_valid())) { |
112 | String device_string = get_device_string(p_event->get_device()); |
113 | text += vformat(" - %s" , device_string); |
114 | } |
115 | |
116 | return text; |
117 | } |
118 | |
119 | String EventListenerLineEdit::get_device_string(int p_device) { |
120 | if (p_device == InputMap::ALL_DEVICES) { |
121 | return TTR("All Devices" ); |
122 | } |
123 | return TTR("Device" ) + " " + itos(p_device); |
124 | } |
125 | |
126 | bool EventListenerLineEdit::_is_event_allowed(const Ref<InputEvent> &p_event) const { |
127 | const Ref<InputEventMouseButton> mb = p_event; |
128 | const Ref<InputEventKey> k = p_event; |
129 | const Ref<InputEventJoypadButton> jb = p_event; |
130 | const Ref<InputEventJoypadMotion> jm = p_event; |
131 | |
132 | return (mb.is_valid() && (allowed_input_types & INPUT_MOUSE_BUTTON)) || |
133 | (k.is_valid() && (allowed_input_types & INPUT_KEY)) || |
134 | (jb.is_valid() && (allowed_input_types & INPUT_JOY_BUTTON)) || |
135 | (jm.is_valid() && (allowed_input_types & INPUT_JOY_MOTION)); |
136 | } |
137 | |
138 | void EventListenerLineEdit::gui_input(const Ref<InputEvent> &p_event) { |
139 | const Ref<InputEventMouseMotion> mm = p_event; |
140 | if (mm.is_valid()) { |
141 | LineEdit::gui_input(p_event); |
142 | return; |
143 | } |
144 | |
145 | // Allow mouse button click on the clear button without being treated as an event. |
146 | const Ref<InputEventMouseButton> b = p_event; |
147 | if (b.is_valid() && _is_over_clear_button(b->get_position())) { |
148 | LineEdit::gui_input(p_event); |
149 | return; |
150 | } |
151 | |
152 | // First event will be an event which is used to focus this control - i.e. a mouse click, or a tab press. |
153 | // Ignore the first one so that clicking into the LineEdit does not override the current event. |
154 | // Ignore is reset to true when the control is unfocused. |
155 | // This class also specially handles grab_focus() calls. |
156 | if (ignore_next_event) { |
157 | ignore_next_event = false; |
158 | return; |
159 | } |
160 | |
161 | accept_event(); |
162 | if (!p_event->is_pressed() || p_event->is_echo() || p_event->is_match(event) || !_is_event_allowed(p_event)) { |
163 | return; |
164 | } |
165 | |
166 | event = p_event; |
167 | set_text(get_event_text(event, false)); |
168 | emit_signal("event_changed" , event); |
169 | } |
170 | |
171 | void EventListenerLineEdit::_on_text_changed(const String &p_text) { |
172 | if (p_text.is_empty()) { |
173 | clear_event(); |
174 | } |
175 | } |
176 | |
177 | void EventListenerLineEdit::_on_focus() { |
178 | set_placeholder(TTR("Listening for input..." )); |
179 | } |
180 | |
181 | void EventListenerLineEdit::_on_unfocus() { |
182 | ignore_next_event = true; |
183 | set_placeholder(TTR("Filter by event..." )); |
184 | } |
185 | |
186 | Ref<InputEvent> EventListenerLineEdit::get_event() const { |
187 | return event; |
188 | } |
189 | |
190 | void EventListenerLineEdit::clear_event() { |
191 | if (event.is_valid()) { |
192 | event = Ref<InputEvent>(); |
193 | set_text("" ); |
194 | emit_signal("event_changed" , event); |
195 | } |
196 | } |
197 | |
198 | void EventListenerLineEdit::set_allowed_input_types(int p_type_masks) { |
199 | allowed_input_types = p_type_masks; |
200 | } |
201 | |
202 | int EventListenerLineEdit::get_allowed_input_types() const { |
203 | return allowed_input_types; |
204 | } |
205 | |
206 | void EventListenerLineEdit::grab_focus() { |
207 | // If we grab focus through code, we don't need to ignore the first event! |
208 | ignore_next_event = false; |
209 | Control::grab_focus(); |
210 | } |
211 | |
212 | void EventListenerLineEdit::_notification(int p_what) { |
213 | switch (p_what) { |
214 | case NOTIFICATION_ENTER_TREE: { |
215 | connect("text_changed" , callable_mp(this, &EventListenerLineEdit::_on_text_changed)); |
216 | connect("focus_entered" , callable_mp(this, &EventListenerLineEdit::_on_focus)); |
217 | connect("focus_exited" , callable_mp(this, &EventListenerLineEdit::_on_unfocus)); |
218 | set_right_icon(get_editor_theme_icon(SNAME("Keyboard" ))); |
219 | set_clear_button_enabled(true); |
220 | } break; |
221 | } |
222 | } |
223 | |
224 | void EventListenerLineEdit::_bind_methods() { |
225 | ADD_SIGNAL(MethodInfo("event_changed" , PropertyInfo(Variant::OBJECT, "event" , PROPERTY_HINT_RESOURCE_TYPE, "InputEvent" ))); |
226 | } |
227 | |
228 | EventListenerLineEdit::EventListenerLineEdit() { |
229 | set_caret_blink_enabled(false); |
230 | set_placeholder(TTR("Filter by event..." )); |
231 | } |
232 | |