1 /* GemRB - Infinity Engine Emulator
2 * Copyright (C) 2003 The GemRB Project
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
8
9 * This program 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 this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 *
18 *
19 */
20
21 #include "GUI/Control.h"
22 #include "GUI/GUIScriptInterface.h"
23 #include "GUI/Window.h"
24
25 #include "ie_cursors.h"
26 #include "ControlAnimation.h"
27 #include "Interface.h"
28 #include "Sprite2D.h"
29 #include "Variables.h"
30
31 #include <cstdio>
32 #include <cstring>
33
34 namespace GemRB {
35
36 unsigned int Control::ActionRepeatDelay = 250;
37
38 const Control::ValueRange Control::MaxValueRange = std::make_pair(0, std::numeric_limits<ieDword>::max());
39
Control(const Region & frame)40 Control::Control(const Region& frame)
41 : View(frame) // dont pass superview to View constructor
42 {
43 VarName[0] = 0;
44 SetValueRange(MaxValueRange);
45
46 animation = NULL;
47 ControlType = IE_GUI_INVALID;
48
49 actionTimer = NULL;
50 repeatDelay = 0;
51 }
52
~Control()53 Control::~Control()
54 {
55 ClearActionTimer();
56
57 delete animation;
58 }
59
IsOpaque() const60 bool Control::IsOpaque() const
61 {
62 return AnimPicture && AnimPicture->HasTransparency() == false;
63 }
64
SetText(const String * string)65 void Control::SetText(const String* string)
66 {
67 SetText((string) ? *string : L"");
68 }
69
SetAction(ControlEventHandler handler,Control::Action type,EventButton button,Event::EventMods mod,short count)70 void Control::SetAction(ControlEventHandler handler, Control::Action type, EventButton button,
71 Event::EventMods mod, short count)
72 {
73 ControlActionKey key(type, mod, button, count);
74 return SetAction(std::move(handler), key);
75 }
76
SetAction(Responder handler,const ActionKey & key)77 void Control::SetAction(Responder handler, const ActionKey& key)
78 {
79 if (handler) {
80 actions[key] = handler;
81 } else {
82 // delete the entry if there is one instead of setting it to NULL
83 ActionIterator it = actions.find(key);
84 if (it != actions.end()) {
85 actions.erase(it);
86 }
87 }
88 }
89
SetActionInterval(unsigned int interval)90 void Control::SetActionInterval(unsigned int interval)
91 {
92 repeatDelay = interval;
93 if (actionTimer) {
94 actionTimer->SetInverval(repeatDelay);
95 }
96 }
97
SupportsAction(const ActionKey & key)98 bool Control::SupportsAction(const ActionKey& key)
99 {
100 return actions.count(key);
101 }
102
PerformAction()103 bool Control::PerformAction()
104 {
105 return PerformAction(ACTION_DEFAULT);
106 }
107
PerformAction(const ActionKey & key)108 bool Control::PerformAction(const ActionKey& key)
109 {
110 if (IsDisabled()) {
111 return false;
112 }
113
114 ActionIterator it = actions.find(key);
115 if (it != actions.end()) {
116 if (!window) {
117 Log(WARNING, "Control", "Executing event handler for a control with no window. This most likely indicates a programming or scripting error.");
118 }
119
120 (it->second)(this);
121 return true;
122 }
123 return false;
124 }
125
FlagsChanged(unsigned int)126 void Control::FlagsChanged(unsigned int /*oldflags*/)
127 {
128 if (actionTimer && (flags&Disabled)) {
129 ClearActionTimer();
130 }
131 }
132
UpdateState(const char * varname,unsigned int val)133 void Control::UpdateState(const char* varname, unsigned int val)
134 {
135 if (strnicmp(VarName, varname, MAX_VARIABLE_LENGTH-1) == 0) {
136 UpdateState(val);
137 }
138 }
139
SetFocus()140 void Control::SetFocus()
141 {
142 window->SetFocused(this);
143 MarkDirty();
144 }
145
IsFocused()146 bool Control::IsFocused()
147 {
148 return window->FocusedView() == this;
149 }
150
SetValue(ieDword val)151 void Control::SetValue(ieDword val)
152 {
153 ieDword oldVal = Value;
154 Value = Clamp(val, range.first, range.second);
155
156 if (VarName[0] != 0) {
157 // set this even when the value doesn't change
158 // if a radio is clicked, then one of its siblings, the siblings value wont change
159 // but we expect the dictionary to reflect the selected value
160 core->GetDictionary()->SetAt(VarName, Value);
161 }
162
163 if (oldVal != Value) {
164 PerformAction(ValueChange);
165 MarkDirty();
166 }
167 }
168
SetValueRange(ValueRange r)169 void Control::SetValueRange(ValueRange r)
170 {
171 range = r;
172 if (Value != CTL_INVALID_VALUE) {
173 SetValue(Value); // update the value if it falls outside the range
174 }
175 }
176
SetValueRange(ieDword min,ieDword max)177 void Control::SetValueRange(ieDword min, ieDword max)
178 {
179 SetValueRange(ValueRange(min, max));
180 }
181
SetAnimPicture(Holder<Sprite2D> newpic)182 void Control::SetAnimPicture(Holder<Sprite2D> newpic)
183 {
184 AnimPicture = newpic;
185 MarkDirty();
186 }
187
ClearActionTimer()188 void Control::ClearActionTimer()
189 {
190 if (actionTimer) {
191 actionTimer->Invalidate();
192 actionTimer = NULL;
193 }
194 }
195
StartActionTimer(const ControlEventHandler & action,unsigned int delay)196 Timer* Control::StartActionTimer(const ControlEventHandler& action, unsigned int delay)
197 {
198 EventHandler h = [this, action] () {
199 // update the timer to use the actual repeatDelay
200 SetActionInterval(repeatDelay);
201
202 if (VarName[0] != 0) {
203 ieDword val = GetValue();
204 core->GetDictionary()->SetAt(VarName, val);
205 window->RedrawControls(VarName, val);
206 }
207
208 return action(this);
209 };
210 // always start the timer with ActionRepeatDelay
211 // this way we have consistent behavior for the initial delay prior to switching to a faster delay
212 return &core->SetTimer(h, (delay) ? delay : ActionRepeatDelay);
213 }
214
HitTest(const Point & p) const215 bool Control::HitTest(const Point& p) const
216 {
217 if (!(flags & (IgnoreEvents | Invisible))) {
218 return View::HitTest(p);
219 }
220 return false;
221 }
222
DragOperation()223 View::UniqueDragOp Control::DragOperation()
224 {
225 if (actionTimer) {
226 return nullptr;
227 }
228
229 ActionKey key(Action::DragDropCreate);
230
231 if (SupportsAction(key)) {
232 // we have to use a timer so that the dragop is set before the callback is called
233 EventHandler h = [this, key] () {
234 return actions[key](this);
235 };
236
237 actionTimer = &core->SetTimer(h, 0, 0);
238 }
239 return std::unique_ptr<ControlDragOp>(new ControlDragOp(this));
240 }
241
AcceptsDragOperation(const DragOp & dop) const242 bool Control::AcceptsDragOperation(const DragOp& dop) const
243 {
244 const ControlDragOp* cdop = dynamic_cast<const ControlDragOp*>(&dop);
245 if (cdop) {
246 assert(cdop->dragView != this);
247 // if 2 controls share the same VarName we assume they are swappable...
248 return (strnicmp(VarName, cdop->Source()->VarName, MAX_VARIABLE_LENGTH-1) == 0);
249 }
250
251 return View::AcceptsDragOperation(dop);
252 }
253
DragCursor() const254 Holder<Sprite2D> Control::DragCursor() const
255 {
256 if (core->InDebugMode(ID_VIEWS)) {
257 return core->Cursors[IE_CURSOR_SWAP];
258 }
259 return nullptr;
260 }
261
OnMouseUp(const MouseEvent & me,unsigned short mod)262 bool Control::OnMouseUp(const MouseEvent& me, unsigned short mod)
263 {
264 ControlActionKey key(Click, mod, me.button, me.repeats);
265 if (SupportsAction(key)) {
266 PerformAction(key);
267 ClearActionTimer();
268 } else if (me.repeats > 1) {
269 // also try a single-click in case there is no doubleclick handler
270 // and there is never a triple+ click handler
271 MouseEvent me2(me);
272 me2.repeats = 1;
273 OnMouseUp(me2, mod);
274 }
275 return true; // always handled
276 }
277
OnMouseDown(const MouseEvent & me,unsigned short mod)278 bool Control::OnMouseDown(const MouseEvent& me, unsigned short mod)
279 {
280 ControlActionKey key(Click, mod, me.button, me.repeats);
281 if (repeatDelay && SupportsAction(key)) {
282 actionTimer = StartActionTimer(actions[key]);
283 }
284 return true; // always handled
285 }
286
OnMouseEnter(const MouseEvent &,const DragOp *)287 void Control::OnMouseEnter(const MouseEvent& /*me*/, const DragOp*)
288 {
289 PerformAction(HoverBegin);
290 }
291
OnMouseLeave(const MouseEvent &,const DragOp *)292 void Control::OnMouseLeave(const MouseEvent& /*me*/, const DragOp*)
293 {
294 PerformAction(HoverEnd);
295 }
296
OnTouchDown(const TouchEvent &,unsigned short)297 bool Control::OnTouchDown(const TouchEvent& /*te*/, unsigned short /*mod*/)
298 {
299 ControlEventHandler cb = METHOD_CALLBACK(&Control::HandleTouchActionTimer, this);
300 actionTimer = StartActionTimer(cb, 500); // TODO: this time value should be configurable
301 return true; // always handled
302 }
303
OnTouchUp(const TouchEvent & te,unsigned short mod)304 bool Control::OnTouchUp(const TouchEvent& te, unsigned short mod)
305 {
306 if (actionTimer) {
307 // touch up before timer triggered
308 // send the touch down+up events
309 ClearActionTimer();
310 View::OnTouchDown(te, mod);
311 View::OnTouchUp(te, mod);
312 return true;
313 }
314 return false; // touch was already handled as a long press
315 }
316
OnKeyPress(const KeyboardEvent & key,unsigned short mod)317 bool Control::OnKeyPress(const KeyboardEvent& key, unsigned short mod)
318 {
319 if (key.keycode == GEM_RETURN) {
320 return PerformAction();
321 }
322
323 return View::OnKeyPress(key, mod);
324 }
325
HandleTouchActionTimer(const Control * ctrl)326 void Control::HandleTouchActionTimer(const Control* ctrl)
327 {
328 assert(ctrl == this);
329 assert(actionTimer);
330
331 ClearActionTimer();
332
333 // long press action (GEM_MB_MENU)
334 // NOTE: we could save the mod value from OnTouchDown to support modifiers to the touch, but we don't have a use ATM
335 ControlActionKey key(Click, 0, GEM_MB_MENU, 1);
336 PerformAction(key);
337 }
338
CreateScriptingRef(ScriptingId id,ResRef group)339 ViewScriptingRef* Control::CreateScriptingRef(ScriptingId id, ResRef group)
340 {
341 return new ControlScriptingRef(this, id, group);
342 }
343
344 }
345