Line data Source code
1 : /* Copyright (C) 2021 Wildfire Games.
2 : * This file is part of 0 A.D.
3 : *
4 : * 0 A.D. is free software: you can redistribute it and/or modify
5 : * it under the terms of the GNU General Public License as published by
6 : * the Free Software Foundation, either version 2 of the License, or
7 : * (at your option) any later version.
8 : *
9 : * 0 A.D. is distributed in the hope that it will be useful,
10 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 : * GNU General Public License for more details.
13 : *
14 : * You should have received a copy of the GNU General Public License
15 : * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
16 : */
17 :
18 : #include "precompiled.h"
19 :
20 : #include "CHotkeyPicker.h"
21 :
22 : #include "gui/ObjectBases/IGUIObject.h"
23 : #include "lib/timer.h"
24 : #include "ps/CLogger.h"
25 : #include "ps/Hotkey.h"
26 : #include "ps/KeyName.h"
27 : #include "scriptinterface/ScriptConversions.h"
28 :
29 :
30 1 : const CStr CHotkeyPicker::EventNameCombination = "Combination";
31 1 : const CStr CHotkeyPicker::EventNameKeyChange = "KeyChange";
32 :
33 : // Don't send the scancode, JS doesn't care.
34 0 : template<> void Script::ToJSVal(const ScriptRequest& rq, JS::MutableHandleValue ret, const CHotkeyPicker::Key& val)
35 : {
36 0 : Script::ToJSVal(rq, ret, val.scancodeName);
37 0 : }
38 :
39 : // Unused, but JSVAL_VECTOR requires it.
40 0 : template<> bool Script::FromJSVal(const ScriptRequest&, const JS::HandleValue, CHotkeyPicker::Key&)
41 : {
42 0 : LOGWARNING("FromJSVal<CHotkeyPicker>: Not implemented");
43 0 : return false;
44 : }
45 :
46 0 : JSVAL_VECTOR(CHotkeyPicker::Key);
47 :
48 0 : CHotkeyPicker::CHotkeyPicker(CGUI& pGUI) : IGUIObject(pGUI), m_TimeToCombination(this, "time_to_combination", 1.f)
49 : {
50 : // 8 keys at the same time is probably more than we'll ever need.
51 0 : m_KeysPressed.reserve(8);
52 0 : }
53 :
54 0 : CHotkeyPicker::~CHotkeyPicker()
55 : {
56 0 : }
57 :
58 0 : void CHotkeyPicker::FireEvent(const CStr& event)
59 : {
60 0 : ScriptRequest rq(*m_pGUI.GetScriptInterface());
61 :
62 0 : JS::RootedValueArray<1> args(rq.cx);
63 0 : JS::RootedValue keys(rq.cx);
64 0 : Script::ToJSVal(rq, &keys, m_KeysPressed);
65 0 : args[0].set(keys);
66 0 : ScriptEvent(event, args);
67 0 : }
68 :
69 0 : void CHotkeyPicker::Tick()
70 : {
71 0 : if (m_KeysPressed.size() == 0)
72 0 : return;
73 :
74 0 : double time = timer_Time();
75 0 : if (time - m_LastKeyChange < m_TimeToCombination)
76 0 : return;
77 :
78 0 : FireEvent(EventNameCombination);
79 :
80 0 : return;
81 : }
82 :
83 0 : void CHotkeyPicker::HandleMessage(SGUIMessage& Message)
84 : {
85 0 : IGUIObject::HandleMessage(Message);
86 0 : switch (Message.type)
87 : {
88 0 : case GUIM_GOT_FOCUS:
89 : case GUIM_LOST_FOCUS:
90 : {
91 0 : m_KeysPressed.clear();
92 0 : m_LastKeyChange = timer_Time();
93 0 : break;
94 : }
95 0 : default:
96 0 : break;
97 : }
98 0 : }
99 :
100 0 : InReaction CHotkeyPicker::PreemptEvent(const SDL_Event_* ev)
101 : {
102 0 : switch (ev->ev.type)
103 : {
104 : // Handle the same mouse events that hotkeys handle
105 0 : case SDL_MOUSEBUTTONDOWN:
106 : case SDL_MOUSEBUTTONUP:
107 : case SDL_MOUSEWHEEL:
108 : {
109 : SDL_Scancode scancode;
110 :
111 0 : if (ev->ev.type != SDL_MOUSEWHEEL)
112 : {
113 : // Wait a little bit -> this gets triggered when clicking on a button,
114 : // but after the button click is processed, thus immediately triggering...
115 0 : if (timer_Time()-m_LastKeyChange < 0.2)
116 0 : return IN_HANDLED;
117 : // This is from hotkeyHandler - not sure what it does in all honesty.
118 0 : if(ev->ev.button.button >= SDL_BUTTON_X1)
119 0 : scancode = static_cast<SDL_Scancode>(MOUSE_BASE + (int)ev->ev.button.button + 2);
120 : else
121 0 : scancode = static_cast<SDL_Scancode>(MOUSE_BASE + (int)ev->ev.button.button);
122 : }
123 : else
124 : {
125 0 : if (ev->ev.wheel.y > 0)
126 0 : scancode = static_cast<SDL_Scancode>(MOUSE_WHEELUP);
127 0 : else if (ev->ev.wheel.y < 0)
128 0 : scancode = static_cast<SDL_Scancode>(MOUSE_WHEELDOWN);
129 0 : else if (ev->ev.wheel.x > 0)
130 0 : scancode = static_cast<SDL_Scancode>(MOUSE_X2);
131 0 : else if (ev->ev.wheel.x < 0)
132 0 : scancode = static_cast<SDL_Scancode>(MOUSE_X1);
133 : else
134 0 : return IN_HANDLED;
135 : }
136 : // Don't handle keys and mouse together except for modifiers.
137 0 : m_KeysPressed.erase(std::remove_if(m_KeysPressed.begin(), m_KeysPressed.end(), [](const Key& k) {
138 0 : return static_cast<int>(k.code) < UNIFIED_SHIFT || static_cast<int>(k.code) >= UNIFIED_LAST; } ), m_KeysPressed.end());
139 0 : m_KeysPressed.emplace_back(Key{scancode, FindScancodeName(scancode)});
140 : // For mouse events, assume we immediately want to return.
141 0 : FireEvent(EventNameCombination);
142 :
143 0 : return IN_HANDLED;
144 : }
145 0 : case SDL_KEYDOWN:
146 : case SDL_KEYUP:
147 : {
148 0 : SDL_Scancode scancode = ev->ev.key.keysym.scancode;
149 :
150 : // Don't handle caps-lock, it doesn't really work in-game and it's a weird hotkey.
151 0 : if (scancode == SDL_SCANCODE_CAPSLOCK)
152 0 : return IN_PASS;
153 :
154 0 : if (scancode == SDL_SCANCODE_LSHIFT || scancode == SDL_SCANCODE_RSHIFT)
155 0 : scancode = static_cast<SDL_Scancode>(UNIFIED_SHIFT);
156 0 : else if (scancode == SDL_SCANCODE_LCTRL || scancode == SDL_SCANCODE_RCTRL)
157 0 : scancode = static_cast<SDL_Scancode>(UNIFIED_CTRL);
158 0 : else if (scancode == SDL_SCANCODE_LALT || scancode == SDL_SCANCODE_RALT)
159 0 : scancode = static_cast<SDL_Scancode>(UNIFIED_ALT);
160 0 : else if (scancode == SDL_SCANCODE_LGUI || scancode == SDL_SCANCODE_RGUI)
161 0 : scancode = static_cast<SDL_Scancode>(UNIFIED_SUPER);
162 :
163 0 : if (ev->ev.type == SDL_KEYDOWN)
164 : {
165 : std::vector<Key>::const_iterator it = \
166 0 : std::find_if(m_KeysPressed.begin(), m_KeysPressed.end(), [&scancode](Key& k) { return k.code == scancode; });
167 : // Can happen if multiple keys are mapped the same.
168 0 : if (it != m_KeysPressed.end())
169 0 : return IN_HANDLED;
170 0 : m_KeysPressed.emplace_back(Key{scancode, FindScancodeName(scancode)});
171 : }
172 : else
173 : {
174 : std::vector<Key>::const_iterator it = \
175 0 : std::find_if(m_KeysPressed.begin(), m_KeysPressed.end(), [&scancode](Key& k) { return k.code == scancode; });
176 : // Might happen if a key was down before this object is created.
177 0 : if (it == m_KeysPressed.end())
178 0 : return IN_HANDLED;
179 0 : m_KeysPressed.erase(it);
180 : }
181 :
182 0 : FireEvent(EventNameKeyChange);
183 :
184 : // Register after-JS in case this takes a while (probably not but it doesn't hurt).
185 0 : m_LastKeyChange = timer_Time();
186 0 : return IN_HANDLED;
187 : }
188 0 : default:
189 : {
190 0 : return IN_PASS;
191 : }
192 : }
193 3 : }
|