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