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 "Window.h"
22 
23 #include "GUI/GUIScriptInterface.h"
24 #include "Interface.h"
25 #include "ScrollBar.h"
26 
27 namespace GemRB {
28 
Window(const Region & frame,WindowManager & mgr)29 Window::Window(const Region& frame, WindowManager& mgr)
30 	: ScrollView(frame), manager(mgr)
31 {
32 	focusView = NULL;
33 	trackingView = NULL;
34 	hoverView = NULL;
35 	lastMouseMoveTime = GetTicks();
36 
37 	SetFlags(DestroyOnClose, OP_OR);
38 	// default ingame windows to frameless
39 	if (core->HasCurrentArea()) {
40 		SetFlags(Borderless, OP_OR);
41 	}
42 	RecreateBuffer();
43 }
44 
Close()45 void Window::Close()
46 {
47 	// fire the onclose handler prior to actually invalidating the window
48 	if (eventHandlers[Closed]) {
49 		eventHandlers[Closed](this);
50 	}
51 
52 	if (flags&DestroyOnClose) {
53 		ClearScriptingRefs();
54 		manager.CloseWindow(this);
55 	} else {
56 		// somebody wants to keep a handle to this window around to display it later
57 		manager.OrderBack(this);
58 		SetVisible(false);
59 	}
60 
61 	trackingView = NULL;
62 	hoverView = NULL;
63 }
64 
FocusLost()65 void Window::FocusLost()
66 {
67 	if (eventHandlers[LostFocus]) {
68 		eventHandlers[LostFocus](this);
69 	}
70 }
71 
FocusGained()72 void Window::FocusGained()
73 {
74 	if (eventHandlers[GainedFocus]) {
75 		eventHandlers[GainedFocus](this);
76 	}
77 }
78 
HasFocus() const79 bool Window::HasFocus() const
80 {
81 	return manager.GetFocusWindow() == this;
82 }
83 
DisplayModal(WindowManager::ModalShadow shadow)84 bool Window::DisplayModal(WindowManager::ModalShadow shadow)
85 {
86 	return manager.PresentModalWindow(this, shadow);
87 }
88 
89 /** Add a Control in the Window */
SubviewAdded(View * view,View *)90 void Window::SubviewAdded(View* view, View* /*parent*/)
91 {
92 	Control* ctrl = dynamic_cast<Control*>(view);
93 	if (ctrl) {
94 		Controls.insert(ctrl);
95 	}
96 
97 	// MoviePlayer at least relies on this
98 	if (focusView == NULL) {
99 		TrySetFocus(view);
100 	}
101 }
102 
SubviewRemoved(View * subview,View * parent)103 void Window::SubviewRemoved(View* subview, View* parent)
104 {
105 	Control* ctrl = dynamic_cast<Control*>(subview);
106 	if (ctrl) {
107 		Controls.erase(ctrl);
108 	}
109 
110 	if (subview->ContainsView(trackingView)) {
111 		trackingView = NULL;
112 		drag = NULL;
113 	}
114 
115 	if (subview->ContainsView(hoverView)) {
116 		hoverView = parent;
117 	}
118 
119 	if (subview->ContainsView(focusView)) {
120 		focusView->DidUnFocus();
121 		focusView = NULL;
122 		for (std::set<Control *>::iterator c = Controls.begin(); c != Controls.end(); ++c) {
123 			Control* ctrl = *c;
124 			if (TrySetFocus(ctrl) == ctrl) {
125 				break;
126 			}
127 		}
128 	}
129 }
130 
SizeChanged(const Size &)131 void Window::SizeChanged(const Size& /*oldSize*/)
132 {
133 	RecreateBuffer();
134 }
135 
FlagsChanged(unsigned int oldflags)136 void Window::FlagsChanged(unsigned int oldflags)
137 {
138 	if ((flags&AlphaChannel) != (oldflags&AlphaChannel)) {
139 		RecreateBuffer();
140 	}
141 
142 	if ((flags&View::Invisible) && focusView) {
143 		focusView->DidUnFocus();
144 	} else if ((oldflags&View::Invisible) && focusView) {
145 		focusView->DidFocus();
146 	}
147 }
148 
RecreateBuffer()149 void Window::RecreateBuffer()
150 {
151 	Video* video = core->GetVideoDriver();
152 
153 	Video::BufferFormat fmt = (flags&AlphaChannel) ? Video::BufferFormat::DISPLAY_ALPHA : Video::BufferFormat::DISPLAY;
154 	backBuffer = video->CreateBuffer(frame, fmt);
155 
156 	// the entire window must be invalidated, because the new buffer is blank
157 	// TODO: we *could* optimize this to instead blit the old buffer to the new one
158 	MarkDirty();
159 }
160 
DrawWithoutComposition()161 const VideoBufferPtr& Window::DrawWithoutComposition()
162 {
163 	View::Draw();
164 
165 	core->GetVideoDriver()->PopDrawingBuffer();
166 	return backBuffer;
167 }
168 
WillDraw(const Region &,const Region &)169 void Window::WillDraw(const Region& /*drawFrame*/, const Region& /*clip*/)
170 {
171 	backBuffer->SetOrigin(frame.Origin());
172 	core->GetVideoDriver()->PushDrawingBuffer(backBuffer);
173 }
174 
DidDraw(const Region &,const Region &)175 void Window::DidDraw(const Region& /*drawFrame*/, const Region& /*clip*/)
176 {
177 	if (!core->InDebugMode(ID_WINDOWS)) return;
178 
179 	Video* video = core->GetVideoDriver();
180 	video->SetScreenClip(nullptr);
181 
182 	auto lock = manager.DrawHUD();
183 
184 	if (focusView) {
185 		Region r = focusView->ConvertRegionToScreen(Region(Point(), focusView->Dimensions()));
186 		video->DrawRect(r, ColorWhite, false);
187 	}
188 
189 	if (hoverView) {
190 		Region r = hoverView->ConvertRegionToScreen(Region(Point(), hoverView->Dimensions()));
191 		r.ExpandAllSides(-5);
192 		video->DrawRect(r, ColorBlue, false);
193 	}
194 
195 	if (trackingView) {
196 		Region r = trackingView->ConvertRegionToScreen(Region(Point(), trackingView->Dimensions()));
197 		r.ExpandAllSides(-10);
198 		video->DrawRect(r, ColorRed, false);
199 	}
200 }
201 
Focus()202 void Window::Focus()
203 {
204 	manager.FocusWindow(this);
205 }
206 
SetFocused(View * ctrl)207 void Window::SetFocused(View* ctrl)
208 {
209 	TrySetFocus(ctrl);
210 }
211 
TooltipText() const212 String Window::TooltipText() const
213 {
214 	if (hoverView) {
215 		return hoverView->TooltipText();
216 	}
217 	return ScrollView::TooltipText();
218 }
219 
Cursor() const220 Holder<Sprite2D> Window::Cursor() const
221 {
222 	if (drag) {
223 		return drag->cursor;
224 	}
225 
226 	Holder<Sprite2D> cursor = ScrollView::Cursor();
227 	if (cursor == NULL && hoverView) {
228 		cursor = hoverView->Cursor();
229 	}
230 	return cursor;
231 }
232 
IsDisabledCursor() const233 bool Window::IsDisabledCursor() const
234 {
235 	bool isDisabled = ScrollView::IsDisabledCursor();
236 	if (hoverView) {
237 		// if either the window or view is in a disabled state the cursor will be
238 		isDisabled = isDisabled || hoverView->IsDisabledCursor();
239 	}
240 	return isDisabled;
241 }
242 
SetPosition(WindowPosition pos)243 void Window::SetPosition(WindowPosition pos)
244 {
245 	// start at top left
246 	Region newFrame(Point(), frame.Dimensions());
247 	Size screen = manager.ScreenSize();
248 
249 	// adjust horizontal
250 	if ((pos&PosHmid) == PosHmid) {
251 		newFrame.x = (screen.w / 2) - (newFrame.w) / 2;
252 	} else if (pos&PosRight) {
253 		newFrame.x = screen.w - newFrame.w;
254 	}
255 
256 	// adjust vertical
257 	if ((pos&PosVmid) == PosVmid) {
258 		newFrame.y = (screen.h / 2) - (newFrame.h) / 2;
259 	} else if (pos&PosBottom) {
260 		newFrame.y = screen.h - newFrame.h;
261 	}
262 	SetFrame(newFrame);
263 }
264 
RedrawControls(const char * VarName,unsigned int Sum)265 void Window::RedrawControls(const char* VarName, unsigned int Sum)
266 {
267 	for (std::set<Control *>::iterator c = Controls.begin(); c != Controls.end(); ++c) {
268 		Control* ctrl = *c;
269 		ctrl->UpdateState( VarName, Sum);
270 	}
271 }
272 
TrySetFocus(View * target)273 View* Window::TrySetFocus(View* target)
274 {
275 	View* newFocus = focusView;
276 	if (target && !target->CanLockFocus()) {
277 		// target wont accept focus so dont bother unfocusing current
278 	} else if (focusView && !focusView->CanUnlockFocus()) {
279 		// current focus unwilling to reliquish
280 	} else {
281 		if (focusView)
282 			focusView->DidUnFocus();
283 
284 		newFocus = target;
285 
286 		if (newFocus)
287 			newFocus->DidFocus();
288 	}
289 	focusView = newFocus;
290 
291 	return newFocus;
292 }
293 
IsDragable() const294 bool Window::IsDragable() const
295 {
296 	if (trackingView != this)
297 	{
298 		return false;
299 	}
300 
301 	return (flags&Draggable) ||
302 	       (EventMgr::ModState(GEM_MOD_CTRL) && EventMgr::MouseButtonState(GEM_MB_ACTION));
303 }
304 
HitTest(const Point & p) const305 bool Window::HitTest(const Point& p) const
306 {
307 	bool hit = View::HitTest(p);
308 	if (hit == false){
309 		// check the control list. we could make View::HitTest optionally recursive, but this is cheaper
310 		for (std::set<Control *>::iterator c = Controls.begin(); c != Controls.end(); ++c) {
311 			Control* ctrl = *c;
312 			if (ctrl->IsVisible() && ctrl->View::HitTest(ctrl->ConvertPointFromWindow(p))) {
313 				hit = true;
314 				break;
315 			}
316 		}
317 	}
318 	return hit;
319 }
320 
SetAction(Responder handler,const ActionKey & key)321 void Window::SetAction(Responder handler, const ActionKey& key)
322 {
323 	eventHandlers[key.Value()] = std::move(handler);
324 }
325 
PerformAction(const ActionKey & key)326 bool Window::PerformAction(const ActionKey& key)
327 {
328 	auto& handler = eventHandlers[key.Value()];
329 	if (handler) {
330 		(handler)(this);
331 		return true;
332 	}
333 	return false;
334 }
335 
SupportsAction(const ActionKey & key)336 bool Window::SupportsAction(const ActionKey& key)
337 {
338 	return eventHandlers[key.Value()];
339 }
340 
DispatchMouseMotion(View * target,const MouseEvent & me)341 void Window::DispatchMouseMotion(View* target, const MouseEvent& me)
342 {
343 	if (hoverView && target != hoverView) {
344 			hoverView->MouseLeave(me, drag.get());
345 	}
346 
347 	if (target && target != hoverView) {
348 		// must create the drag event before calling MouseEnter
349 		target->MouseEnter(me, drag.get());
350 	}
351 
352 	if (trackingView && Distance(dragOrigin, me.Pos()) > EventMgr::mouseDragRadius) {
353 		// tracking will eat this event
354 		if (me.buttonStates) {
355 			trackingView->MouseDrag(me);
356 			if (trackingView == target && drag == nullptr) {
357 				drag = trackingView->DragOperation();
358 			}
359 		} else {
360 			trackingView = NULL;
361 		}
362 	} else if (target) {
363 		target->MouseOver(me);
364 	}
365 	hoverView = target;
366 }
367 
DispatchMouseDown(View * target,const MouseEvent & me,unsigned short mod)368 void Window::DispatchMouseDown(View* target, const MouseEvent& me, unsigned short mod)
369 {
370 	assert(target);
371 
372 	if (me.button == GEM_MB_ACTION
373 		&& !(Flags() & View::IgnoreEvents)
374 	) {
375 		Focus();
376 	}
377 
378 	TrySetFocus(target);
379 	target->MouseDown(me, mod);
380 	trackingView = target; // all views track the mouse within their bounds
381 	dragOrigin = me.Pos();
382 	assert(me.buttonStates);
383 }
384 
DispatchMouseUp(View * target,const MouseEvent & me,unsigned short mod)385 void Window::DispatchMouseUp(View* target, const MouseEvent& me, unsigned short mod)
386 {
387 	assert(target);
388 
389 	if (drag && drag->dragView != target && target->AcceptsDragOperation(*drag)) {
390 			drag->dropView = target;
391 			target->CompleteDragOperation(*drag);
392 	} else if (trackingView) {
393 		if (trackingView == target || trackingView->TracksMouseDown()) {
394 			trackingView->MouseUp(me, mod);
395 		}
396 	} else if (target) {
397 		target->MouseUp(me, mod);
398 	}
399 	drag = NULL;
400 	trackingView = NULL;
401 }
402 
DispatchTouchDown(View * target,const TouchEvent & te,unsigned short mod)403 void Window::DispatchTouchDown(View* target, const TouchEvent& te, unsigned short mod)
404 {
405 	assert(target);
406 
407 	if (te.numFingers == 1
408 		&& !(Flags() & View::IgnoreEvents)) {
409 		Focus();
410 	}
411 
412 	TrySetFocus(target);
413 	target->TouchDown(te, mod);
414 	trackingView = target; // all views track the mouse within their bounds
415 }
416 
DispatchTouchUp(View * target,const TouchEvent & te,unsigned short mod)417 void Window::DispatchTouchUp(View* target, const TouchEvent& te, unsigned short mod)
418 {
419 	assert(target);
420 
421 	if (drag && te.numFingers == 1) {
422 		if (target->AcceptsDragOperation(*drag) && drag->dragView != target) {
423 			drag->dropView = target;
424 			target->CompleteDragOperation(*drag);
425 		}
426 	} else if (trackingView) {
427 		if (trackingView == target || trackingView->TracksMouseDown())
428 			trackingView->TouchUp(te, mod);
429 	} else if (target) {
430 		target->TouchUp(te, mod);
431 	}
432 	drag = NULL;
433 	trackingView = NULL;
434 }
435 
DispatchTouchGesture(View * target,const GestureEvent & gesture)436 void Window::DispatchTouchGesture(View* target, const GestureEvent& gesture)
437 {
438 	// FIXME: this is incomplete
439 	// this should be a bit closer to DispatchMouseMotion
440 	// drag and drop for example wont function
441 
442 	//trackingView = target;
443 	target->TouchGesture(gesture);
444 }
445 
DispatchKey(View * keyView,const Event & event)446 bool Window::DispatchKey(View* keyView, const Event& event)
447 {
448 	// hotkeys first
449 	std::map<KeyboardKey, EventMgr::EventCallback>::iterator it = HotKeys.find(event.keyboard.keycode);
450 	if (it != HotKeys.end()) {
451 		return (it->second)(event);
452 	}
453 
454 	// try the keyView view first, if it fails have the window itself try
455 	bool handled = false;
456 	if (keyView) {
457 		handled = (event.type == Event::KeyDown)
458 		? keyView->KeyPress(event.keyboard, event.mod)
459 		: keyView->KeyRelease(event.keyboard, event.mod);
460 	}
461 
462 	if (!handled) {
463 		// FIXME: using OnKeyPress to avoid eventProxy from eating esc key
464 		// would be better if we could delegate proxies for different event classes (keys, mouse, etc)
465 		handled = (event.type == Event::KeyDown)
466 		? OnKeyPress(event.keyboard, event.mod)
467 		: OnKeyRelease(event.keyboard, event.mod);
468 	}
469 	return handled;
470 }
471 
DispatchEvent(const Event & event)472 bool Window::DispatchEvent(const Event& event)
473 {
474 	View* target = NULL;
475 
476 	if (event.type == Event::TextInput) {
477 		focusView->TextInput(event.text);
478 		return true;
479 	}
480 
481 	if (!event.isScreen) { // key events
482 		return DispatchKey(focusView, event);
483 	}
484 
485 	if (event.type == Event::TouchGesture) {
486 		if (trackingView) {
487 			DispatchTouchGesture(trackingView, event.gesture);
488 		}
489 		return true;
490 	}
491 
492 	Point screenPos = event.mouse.Pos();
493 	if (!frame.PointInside(screenPos) && trackingView == nullptr) {
494 		// this can hapen if the window is modal since it will absorb all events
495 		// the window manager maybe shouldnt dispatch the events in this case
496 		// but this is a public function and its possible to post a phoney event from anywhere anyway
497 		return true;
498 	}
499 
500 	target = SubviewAt(ConvertPointFromScreen(screenPos), false, true);
501 	assert(target == nullptr || target->IsVisible());
502 
503 	if (IsDragable() && target == nullptr) {
504 		target = this;
505 	}
506 
507 	// special event handling
508 	switch (event.type) {
509 		case Event::MouseScroll:
510 			// retarget if NULL or disabled
511 			{
512 				Point delta = event.mouse.Delta();
513 				if (target == nullptr || target->IsDisabled()) {
514 					target = this;
515 				}
516 
517 				target->MouseWheelScroll(delta);
518 				return true;
519 			}
520 		case Event::MouseMove:
521 			// allows NULL and disabled targets
522 			if (target == this) {
523 				// skip the usual dispatch
524 				// this is so that we can move windows that otherwise ignore events
525 				OnMouseDrag(event.mouse);
526 			} else {
527 				DispatchMouseMotion(target, event.mouse);
528 			}
529 			return true;
530 		default:
531 			if (target == NULL) {
532 				target = this;
533 			} else if (target->IsDisabled()) {
534 				return true; // we still absorb the event
535 			}
536 			break;
537 	}
538 
539 	assert(target);
540 	// basic event handling
541 	switch (event.type) {
542 		case Event::MouseDown:
543 			DispatchMouseDown(target, event.mouse, event.mod);
544 			break;
545 		case Event::MouseUp:
546 			DispatchMouseUp(target, event.mouse, event.mod);
547 			break;
548 		case Event::TouchDown:
549 			DispatchTouchDown(target, event.touch, event.mod);
550 			break;
551 		case Event::TouchUp:
552 			DispatchTouchUp(target, event.touch, event.mod);
553 			break;
554 		default:
555 			assert(false); // others should be handled above
556 	}
557 	// absorb other screen events i guess
558 	return true;
559 }
560 
InActionHandler() const561 bool Window::InActionHandler() const
562 {
563 	for (std::set<Control *>::iterator c = Controls.begin(); c != Controls.end(); ++c) {
564 		Control* ctrl = *c;
565 		if (ctrl->IsExecutingResponseHandler()) {
566 			return true;
567 		}
568 	}
569 
570 	return executingResponseHandler;
571 }
572 
RegisterHotKeyCallback(EventMgr::EventCallback cb,KeyboardKey key)573 bool Window::RegisterHotKeyCallback(EventMgr::EventCallback cb, KeyboardKey key)
574 {
575 	if (key < ' ') { // allowing certain non printables (eg 'F' keys)
576 		return false;
577 	}
578 
579 	std::map<KeyboardKey, EventMgr::EventCallback>::iterator it;
580 	it = HotKeys.find(key);
581 	if (it != HotKeys.end()) {
582 		// something already registered
583 		HotKeys.erase(it);
584 	}
585 
586 	HotKeys[key] = cb;
587 	return true;
588 }
589 
UnRegisterHotKeyCallback(EventMgr::EventCallback cb,KeyboardKey key)590 bool Window::UnRegisterHotKeyCallback(EventMgr::EventCallback cb, KeyboardKey key)
591 {
592 	KeyMap::iterator it = HotKeys.find(key);
593 	if (it != HotKeys.end() && FunctionTargetsEqual(it->second, cb)) {
594 		HotKeys.erase(it);
595 		return true;
596 	}
597 	return false;
598 }
599 
OnMouseDrag(const MouseEvent & me)600 bool Window::OnMouseDrag(const MouseEvent& me)
601 {
602 	assert(me.buttonStates);
603 	// dragging the window to a new position. only happens with left mouse.
604 	if (IsDragable()) {
605 		Point newOrigin = frame.Origin() - me.Delta();
606 		SetFrameOrigin(newOrigin);
607 	} else {
608 		ScrollView::OnMouseDrag(me);
609 	}
610 	return true;
611 }
612 
OnMouseLeave(const MouseEvent & me,const DragOp *)613 void Window::OnMouseLeave(const MouseEvent& me, const DragOp*)
614 {
615 	DispatchMouseMotion(NULL, me);
616 }
617 
OnKeyPress(const KeyboardEvent & key,unsigned short mod)618 bool Window::OnKeyPress(const KeyboardEvent& key, unsigned short mod)
619 {
620 	if (Flags() & View::IgnoreEvents) {
621 		return false;
622 	}
623 	switch (key.keycode) {
624 		case GEM_ESCAPE:
625 			Close();
626 			return true;
627 	}
628 	return ScrollView::OnKeyPress(key, mod);
629 }
630 
OnControllerButtonDown(const ControllerEvent & ce)631 bool Window::OnControllerButtonDown(const ControllerEvent& ce)
632 {
633 	if (ce.button == CONTROLLER_BUTTON_BACK) {
634 		Close();
635 		return true;
636 	}
637 
638 	return View::OnControllerButtonDown(ce);
639 }
640 
CreateScriptingRef(ScriptingId id,ResRef group)641 ViewScriptingRef* Window::CreateScriptingRef(ScriptingId id, ResRef group)
642 {
643 	return new WindowScriptingRef(this, id, group);
644 }
645 
646 }
647