1 /*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9 #include "GUIWindow.h"
10
11 #include "Application.h"
12 #include "GUIAudioManager.h"
13 #include "GUIComponent.h"
14 #include "GUIControlFactory.h"
15 #include "GUIControlGroup.h"
16 #include "GUIControlProfiler.h"
17 #include "GUIInfoManager.h"
18 #include "GUIWindowManager.h"
19 #include "ServiceBroker.h"
20 #include "addons/Skin.h"
21 #include "input/Key.h"
22 #include "input/WindowTranslator.h"
23 #include "messaging/ApplicationMessenger.h"
24 #include "settings/AdvancedSettings.h"
25 #include "settings/SettingsComponent.h"
26 #include "threads/SingleLock.h"
27 #include "utils/Color.h"
28 #include "utils/StringUtils.h"
29 #include "utils/TimeUtils.h"
30 #include "utils/Variant.h"
31 #include "utils/XMLUtils.h"
32 #include "utils/log.h"
33
34 using namespace KODI::MESSAGING;
35
operator ()(const std::string & s1,const std::string & s2) const36 bool CGUIWindow::icompare::operator()(const std::string &s1, const std::string &s2) const
37 {
38 return StringUtils::CompareNoCase(s1, s2) < 0;
39 }
40
CGUIWindow(int id,const std::string & xmlFile)41 CGUIWindow::CGUIWindow(int id, const std::string &xmlFile)
42 {
43 CGUIWindow::SetID(id);
44 SetProperty("xmlfile", xmlFile);
45 m_lastControlID = 0;
46 m_needsScaling = true;
47 m_windowLoaded = false;
48 m_loadType = LOAD_EVERY_TIME;
49 m_closing = false;
50 m_active = false;
51 m_renderOrder = RENDER_ORDER_WINDOW;
52 m_dynamicResourceAlloc = true;
53 m_previousWindow = WINDOW_INVALID;
54 m_animationsEnabled = true;
55 m_manualRunActions = false;
56 m_exclusiveMouseControl = 0;
57 m_clearBackground = 0xff000000; // opaque black -> always clear
58 m_windowXMLRootElement = nullptr;
59 m_menuControlID = 0;
60 m_menuLastFocusedControlID = 0;
61 m_custom = false;
62 }
63
~CGUIWindow()64 CGUIWindow::~CGUIWindow()
65 {
66 delete m_windowXMLRootElement;
67 }
68
Load(const std::string & strFileName,bool bContainsPath)69 bool CGUIWindow::Load(const std::string& strFileName, bool bContainsPath)
70 {
71 if (m_windowLoaded || !g_SkinInfo)
72 return true; // no point loading if it's already there
73
74 #ifdef _DEBUG
75 int64_t start;
76 start = CurrentHostCounter();
77 #endif
78
79 const char* strLoadType;
80 switch (m_loadType)
81 {
82 case LOAD_ON_GUI_INIT:
83 strLoadType = "LOAD_ON_GUI_INIT";
84 break;
85 case KEEP_IN_MEMORY:
86 strLoadType = "KEEP_IN_MEMORY";
87 break;
88 case LOAD_EVERY_TIME:
89 default:
90 strLoadType = "LOAD_EVERY_TIME";
91 break;
92 }
93 CLog::Log(LOGINFO, "Loading skin file: %s, load type: %s", strFileName.c_str(), strLoadType);
94
95 // Find appropriate skin folder + resolution to load from
96 std::string strPath;
97 std::string strLowerPath;
98 if (bContainsPath)
99 strPath = strFileName;
100 else
101 {
102 // FIXME: strLowerPath needs to eventually go since resToUse can get incorrectly overridden
103 std::string strFileNameLower = strFileName;
104 StringUtils::ToLower(strFileNameLower);
105 strLowerPath = g_SkinInfo->GetSkinPath(strFileNameLower, &m_coordsRes);
106 strPath = g_SkinInfo->GetSkinPath(strFileName, &m_coordsRes);
107 }
108
109 bool ret = LoadXML(strPath, strLowerPath);
110 if (ret)
111 {
112 m_windowLoaded = true;
113 OnWindowLoaded();
114
115 #ifdef _DEBUG
116 int64_t end, freq;
117 end = CurrentHostCounter();
118 freq = CurrentHostFrequency();
119 CLog::Log(LOGDEBUG, "Skin file %s loaded in %.2fms", strPath.c_str(), 1000.f * (end - start) / freq);
120 #endif
121 }
122
123 return ret;
124 }
125
LoadXML(const std::string & strPath,const std::string & strLowerPath)126 bool CGUIWindow::LoadXML(const std::string &strPath, const std::string &strLowerPath)
127 {
128 // load window xml if we don't have it stored yet
129 if (!m_windowXMLRootElement)
130 {
131 CXBMCTinyXML xmlDoc;
132 std::string strPathLower = strPath;
133 StringUtils::ToLower(strPathLower);
134 if (!xmlDoc.LoadFile(strPath) && !xmlDoc.LoadFile(strPathLower) && !xmlDoc.LoadFile(strLowerPath))
135 {
136 CLog::Log(LOGERROR, "Unable to load window XML: %s. Line %d\n%s", strPath.c_str(), xmlDoc.ErrorRow(), xmlDoc.ErrorDesc());
137 SetID(WINDOW_INVALID);
138 return false;
139 }
140
141 // xml need a <window> root element
142 if (!StringUtils::EqualsNoCase(xmlDoc.RootElement()->Value(), "window"))
143 {
144 CLog::Log(LOGERROR, "XML file %s does not contain a <window> root element", GetProperty("xmlfile").c_str());
145 return false;
146 }
147
148 // store XML for further processing if window's load type is LOAD_EVERY_TIME or a reload is needed
149 m_windowXMLRootElement = static_cast<TiXmlElement*>(xmlDoc.RootElement()->Clone());
150 }
151 else
152 CLog::Log(LOGDEBUG, "Using already stored xml root node for %s", strPath.c_str());
153
154 return Load(Prepare(m_windowXMLRootElement).get());
155 }
156
Prepare(TiXmlElement * pRootElement)157 std::unique_ptr<TiXmlElement> CGUIWindow::Prepare(TiXmlElement *pRootElement)
158 {
159 if (!pRootElement)
160 return nullptr;
161
162 // clone the root element as we will manipulate it
163 auto preparedRoot = std::unique_ptr<TiXmlElement>(static_cast<TiXmlElement*>(pRootElement->Clone()));
164
165 // Resolve any includes, constants, expressions that may be present
166 // and save include's conditions to the given map
167 g_SkinInfo->ResolveIncludes(preparedRoot.get(), &m_xmlIncludeConditions);
168
169 return preparedRoot;
170 }
171
Load(TiXmlElement * pRootElement)172 bool CGUIWindow::Load(TiXmlElement *pRootElement)
173 {
174 if (!pRootElement)
175 return false;
176
177 // set the scaling resolution so that any control creation or initialisation can
178 // be done with respect to the correct aspect ratio
179 CServiceBroker::GetWinSystem()->GetGfxContext().SetScalingResolution(m_coordsRes, m_needsScaling);
180
181 // now load in the skin file
182 SetDefaults();
183
184 CGUIControlFactory::GetInfoColor(pRootElement, "backgroundcolor", m_clearBackground, GetID());
185 CGUIControlFactory::GetActions(pRootElement, "onload", m_loadActions);
186 CGUIControlFactory::GetActions(pRootElement, "onunload", m_unloadActions);
187 CRect parentRect(0, 0, static_cast<float>(m_coordsRes.iWidth), static_cast<float>(m_coordsRes.iHeight));
188 CGUIControlFactory::GetHitRect(pRootElement, m_hitRect, parentRect);
189
190 TiXmlElement *pChild = pRootElement->FirstChildElement();
191 while (pChild)
192 {
193 std::string strValue = pChild->Value();
194 if (strValue == "previouswindow" && pChild->FirstChild())
195 {
196 m_previousWindow = CWindowTranslator::TranslateWindow(pChild->FirstChild()->Value());
197 }
198 else if (strValue == "defaultcontrol" && pChild->FirstChild())
199 {
200 const char *always = pChild->Attribute("always");
201 if (always && StringUtils::EqualsNoCase(always, "true"))
202 m_defaultAlways = true;
203 m_defaultControl = atoi(pChild->FirstChild()->Value());
204 }
205 else if(strValue == "menucontrol" && pChild->FirstChild())
206 {
207 m_menuControlID = atoi(pChild->FirstChild()->Value());
208 }
209 else if (strValue == "visible" && pChild->FirstChild())
210 {
211 std::string condition;
212 CGUIControlFactory::GetConditionalVisibility(pRootElement, condition);
213 m_visibleCondition = CServiceBroker::GetGUI()->GetInfoManager().Register(condition, GetID());
214 }
215 else if (strValue == "animation" && pChild->FirstChild())
216 {
217 CRect rect(0, 0, static_cast<float>(m_coordsRes.iWidth), static_cast<float>(m_coordsRes.iHeight));
218 CAnimation anim;
219 anim.Create(pChild, rect, GetID());
220 m_animations.push_back(anim);
221 }
222 else if (strValue == "zorder" && pChild->FirstChild())
223 {
224 m_renderOrder = atoi(pChild->FirstChild()->Value());
225 }
226 else if (strValue == "coordinates")
227 {
228 XMLUtils::GetFloat(pChild, "posx", m_posX);
229 XMLUtils::GetFloat(pChild, "posy", m_posY);
230 XMLUtils::GetFloat(pChild, "left", m_posX);
231 XMLUtils::GetFloat(pChild, "top", m_posY);
232
233 TiXmlElement *originElement = pChild->FirstChildElement("origin");
234 while (originElement)
235 {
236 COrigin origin;
237 origin.x = CGUIControlFactory::ParsePosition(originElement->Attribute("x"), static_cast<float>(m_coordsRes.iWidth));
238 origin.y = CGUIControlFactory::ParsePosition(originElement->Attribute("y"), static_cast<float>(m_coordsRes.iHeight));
239 if (originElement->FirstChild())
240 origin.condition = CServiceBroker::GetGUI()->GetInfoManager().Register(originElement->FirstChild()->Value(), GetID());
241 m_origins.push_back(origin);
242 originElement = originElement->NextSiblingElement("origin");
243 }
244 }
245 else if (strValue == "camera")
246 { // z is fixed
247 m_camera.x = CGUIControlFactory::ParsePosition(pChild->Attribute("x"), static_cast<float>(m_coordsRes.iWidth));
248 m_camera.y = CGUIControlFactory::ParsePosition(pChild->Attribute("y"), static_cast<float>(m_coordsRes.iHeight));
249 m_hasCamera = true;
250 }
251 else if (strValue == "depth" && pChild->FirstChild())
252 {
253 float stereo = static_cast<float>(atof(pChild->FirstChild()->Value()));
254 m_stereo = std::max(-1.f, std::min(1.f, stereo));
255 }
256 else if (strValue == "controls")
257 {
258 TiXmlElement *pControl = pChild->FirstChildElement();
259 while (pControl)
260 {
261 if (StringUtils::EqualsNoCase(pControl->Value(), "control"))
262 {
263 LoadControl(pControl, nullptr, CRect(0, 0, static_cast<float>(m_coordsRes.iWidth), static_cast<float>(m_coordsRes.iHeight)));
264 }
265 pControl = pControl->NextSiblingElement();
266 }
267 }
268
269 pChild = pChild->NextSiblingElement();
270 }
271
272 return true;
273 }
274
LoadControl(TiXmlElement * pControl,CGUIControlGroup * pGroup,const CRect & rect)275 void CGUIWindow::LoadControl(TiXmlElement* pControl, CGUIControlGroup *pGroup, const CRect &rect)
276 {
277 // get control type
278 CGUIControlFactory factory;
279
280 CGUIControl* pGUIControl = factory.Create(GetID(), rect, pControl);
281 if (pGUIControl)
282 {
283 float maxX = pGUIControl->GetXPosition() + pGUIControl->GetWidth();
284 if (maxX > m_width)
285 {
286 m_width = maxX;
287 }
288
289 float maxY = pGUIControl->GetYPosition() + pGUIControl->GetHeight();
290 if (maxY > m_height)
291 {
292 m_height = maxY;
293 }
294 // if we are in a group, add to the group, else add to our window
295 if (pGroup)
296 pGroup->AddControl(pGUIControl);
297 else
298 AddControl(pGUIControl);
299 // if the new control is a group, then add it's controls
300 if (pGUIControl->IsGroup())
301 {
302 CGUIControlGroup *grp = static_cast<CGUIControlGroup*>(pGUIControl);
303 TiXmlElement *pSubControl = pControl->FirstChildElement("control");
304 CRect grpRect(grp->GetXPosition(), grp->GetYPosition(),
305 grp->GetXPosition() + grp->GetWidth(), grp->GetYPosition() + grp->GetHeight());
306 while (pSubControl)
307 {
308 LoadControl(pSubControl, grp, grpRect);
309 pSubControl = pSubControl->NextSiblingElement("control");
310 }
311 }
312 }
313 }
314
OnWindowLoaded()315 void CGUIWindow::OnWindowLoaded()
316 {
317 DynamicResourceAlloc(true);
318 }
319
CenterWindow()320 void CGUIWindow::CenterWindow()
321 {
322 m_posX = (m_coordsRes.iWidth - GetWidth()) / 2;
323 m_posY = (m_coordsRes.iHeight - GetHeight()) / 2;
324 }
325
DoProcess(unsigned int currentTime,CDirtyRegionList & dirtyregions)326 void CGUIWindow::DoProcess(unsigned int currentTime, CDirtyRegionList &dirtyregions)
327 {
328 if (!IsControlDirty() && CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_guiSmartRedraw)
329 return;
330
331 CServiceBroker::GetWinSystem()->GetGfxContext().SetRenderingResolution(m_coordsRes, m_needsScaling);
332 CServiceBroker::GetWinSystem()->GetGfxContext().AddGUITransform();
333 CGUIControlGroup::DoProcess(currentTime, dirtyregions);
334 CServiceBroker::GetWinSystem()->GetGfxContext().RemoveTransform();
335
336 // check if currently focused control can have it
337 // and fallback to default control if not
338 CGUIControl* focusedControl = GetFocusedControl();
339 if (focusedControl && !focusedControl->CanFocus())
340 SET_CONTROL_FOCUS(m_defaultControl, 0);
341 }
342
DoRender()343 void CGUIWindow::DoRender()
344 {
345 // If we're rendering from a different thread, then we should wait for the main
346 // app thread to finish AllocResources(), as dynamic resources (images in particular)
347 // will try and be allocated from 2 different threads, which causes nasty things
348 // to occur.
349 if (!m_bAllocated) return;
350
351 CServiceBroker::GetWinSystem()->GetGfxContext().SetRenderingResolution(m_coordsRes, m_needsScaling);
352
353 CServiceBroker::GetWinSystem()->GetGfxContext().AddGUITransform();
354 CGUIControlGroup::DoRender();
355 CServiceBroker::GetWinSystem()->GetGfxContext().RemoveTransform();
356
357 if (CGUIControlProfiler::IsRunning()) CGUIControlProfiler::Instance().EndFrame();
358 }
359
AfterRender()360 void CGUIWindow::AfterRender()
361 {
362 // Check to see if we should close at this point
363 // We check after the controls have finished rendering, as we may have to close due to
364 // the controls rendering after the window has finished it's animation
365 // we call the base class instead of this class so that we can find the change
366 if (m_closing && !CGUIControlGroup::IsAnimating(ANIM_TYPE_WINDOW_CLOSE))
367 Close(true);
368
369 }
370
Close_Internal(bool forceClose,int nextWindowID,bool enableSound)371 void CGUIWindow::Close_Internal(bool forceClose /*= false*/, int nextWindowID /*= 0*/, bool enableSound /*= true*/)
372 {
373 CSingleLock lock(CServiceBroker::GetWinSystem()->GetGfxContext());
374
375 if (!m_active)
376 return;
377
378 forceClose |= (nextWindowID == WINDOW_FULLSCREEN_VIDEO ||
379 nextWindowID == WINDOW_FULLSCREEN_GAME);
380
381 if (!forceClose && HasAnimation(ANIM_TYPE_WINDOW_CLOSE))
382 {
383 if (!m_closing)
384 {
385 if (enableSound && IsSoundEnabled())
386 CServiceBroker::GetGUI()->GetAudioManager().PlayWindowSound(GetID(), SOUND_DEINIT);
387
388 // Perform the window out effect
389 QueueAnimation(ANIM_TYPE_WINDOW_CLOSE);
390 m_closing = true;
391 }
392 return;
393 }
394
395 m_closing = false;
396 CGUIMessage msg(GUI_MSG_WINDOW_DEINIT, 0, 0, nextWindowID);
397 OnMessage(msg);
398 }
399
Close(bool forceClose,int nextWindowID,bool enableSound,bool bWait)400 void CGUIWindow::Close(bool forceClose /*= false*/, int nextWindowID /*= 0*/, bool enableSound /*= true*/, bool bWait /* = true */)
401 {
402 if (!g_application.IsCurrentThread())
403 {
404 // make sure graphics lock is not held
405 CSingleExit leaveIt(CServiceBroker::GetWinSystem()->GetGfxContext());
406 int param2 = (forceClose ? 0x01 : 0) | (enableSound ? 0x02 : 0);
407 if (bWait)
408 CApplicationMessenger::GetInstance().SendMsg(TMSG_GUI_WINDOW_CLOSE, nextWindowID, param2, static_cast<void*>(this));
409 else
410 CApplicationMessenger::GetInstance().PostMsg(TMSG_GUI_WINDOW_CLOSE, nextWindowID, param2, static_cast<void*>(this));
411 }
412 else
413 Close_Internal(forceClose, nextWindowID, enableSound);
414 }
415
OnAction(const CAction & action)416 bool CGUIWindow::OnAction(const CAction &action)
417 {
418 if (action.IsMouse() || action.IsGesture())
419 return EVENT_RESULT_UNHANDLED != OnMouseAction(action);
420
421 CGUIControl *focusedControl = GetFocusedControl();
422 if (focusedControl)
423 {
424 if (focusedControl->OnAction(action))
425 return true;
426 }
427 else
428 {
429 // no control has focus?
430 // set focus to the default control then
431 CGUIMessage msg(GUI_MSG_SETFOCUS, GetID(), m_defaultControl);
432 OnMessage(msg);
433 }
434
435 // default implementations
436 switch(action.GetID())
437 {
438 case ACTION_NAV_BACK:
439 case ACTION_PREVIOUS_MENU:
440 return OnBack(action.GetID());
441 case ACTION_SHOW_INFO:
442 return OnInfo(action.GetID());
443 case ACTION_MENU:
444 if (m_menuControlID > 0)
445 {
446 CGUIControl *menu = GetControl(m_menuControlID);
447 if (menu)
448 {
449 int focusControlId;
450 if (!menu->HasFocus())
451 {
452 // focus the menu control
453 focusControlId = m_menuControlID;
454 // To support a toggle behaviour we store the last focused control id
455 // to restore (focus) this control if the menu control has the focus
456 // while you press the menu button again.
457 m_menuLastFocusedControlID = GetFocusedControlID();
458 }
459 else
460 {
461 // restore the last focused control or if not exists use the default control
462 focusControlId = m_menuLastFocusedControlID > 0 ? m_menuLastFocusedControlID : m_defaultControl;
463 }
464
465 CGUIMessage msg = CGUIMessage(GUI_MSG_SETFOCUS, GetID(), focusControlId);
466 return OnMessage(msg);
467 }
468 }
469 break;
470 }
471
472 return false;
473 }
474
GetPosition() const475 CPoint CGUIWindow::GetPosition() const
476 {
477 for (unsigned int i = 0; i < m_origins.size(); i++)
478 {
479 // no condition implies true
480 if (!m_origins[i].condition || m_origins[i].condition->Get())
481 { // found origin
482 return CPoint(m_origins[i].x, m_origins[i].y);
483 }
484 }
485 return CGUIControlGroup::GetPosition();
486 }
487
488 // OnMouseAction - called by OnAction()
OnMouseAction(const CAction & action)489 EVENT_RESULT CGUIWindow::OnMouseAction(const CAction &action)
490 {
491 CServiceBroker::GetWinSystem()->GetGfxContext().SetScalingResolution(m_coordsRes, m_needsScaling);
492 CPoint mousePoint(action.GetAmount(0), action.GetAmount(1));
493 CServiceBroker::GetWinSystem()->GetGfxContext().InvertFinalCoords(mousePoint.x, mousePoint.y);
494
495 // create the mouse event
496 CMouseEvent event(action.GetID(), action.GetHoldTime(), action.GetAmount(2), action.GetAmount(3));
497 if (m_exclusiveMouseControl)
498 {
499 CGUIControl *child = GetControl(m_exclusiveMouseControl);
500 if (child)
501 {
502 CPoint renderPos = child->GetRenderPosition() - CPoint(child->GetXPosition(), child->GetYPosition());
503 return child->OnMouseEvent(mousePoint - renderPos, event);
504 }
505 }
506
507 UnfocusFromPoint(mousePoint);
508
509 return SendMouseEvent(mousePoint, event);
510 }
511
OnMouseEvent(const CPoint & point,const CMouseEvent & event)512 EVENT_RESULT CGUIWindow::OnMouseEvent(const CPoint &point, const CMouseEvent &event)
513 {
514 if (event.m_id == ACTION_MOUSE_RIGHT_CLICK)
515 { // no control found to absorb this click - go to previous menu
516 return OnAction(CAction(ACTION_PREVIOUS_MENU)) ? EVENT_RESULT_HANDLED : EVENT_RESULT_UNHANDLED;
517 }
518 return EVENT_RESULT_UNHANDLED;
519 }
520
521 /// \brief Called on window open.
522 /// * Restores the control state(s)
523 /// * Sets initial visibility of controls
524 /// * Queue WindowOpen animation
525 /// * Set overlay state
526 /// Override this function and do any window-specific initialisation such
527 /// as filling control contents and setting control focus before
528 /// calling the base method.
OnInitWindow()529 void CGUIWindow::OnInitWindow()
530 {
531 // Play the window specific init sound
532 if (IsSoundEnabled())
533 CServiceBroker::GetGUI()->GetAudioManager().PlayWindowSound(GetID(), SOUND_INIT);
534
535 // set our rendered state
536 m_hasProcessed = false;
537 m_closing = false;
538 m_active = true;
539 ResetAnimations(); // we need to reset our animations as those windows that don't dynamically allocate
540 // need their anims reset. An alternative solution is turning off all non-dynamic
541 // allocation (which in some respects may be nicer, but it kills hdd spindown and the like)
542
543 // set our initial control visibility before restoring control state and
544 // focusing the default control, and again afterward to make sure that
545 // any controls that depend on the state of the focused control (and or on
546 // control states) are active.
547 SetInitialVisibility();
548 RestoreControlStates();
549 SetInitialVisibility();
550 QueueAnimation(ANIM_TYPE_WINDOW_OPEN);
551
552 if (!m_manualRunActions)
553 {
554 RunLoadActions();
555 }
556 }
557
558 // Called on window close.
559 // * Saves control state(s)
560 // Override this function and call the base class before doing any dynamic memory freeing
OnDeinitWindow(int nextWindowID)561 void CGUIWindow::OnDeinitWindow(int nextWindowID)
562 {
563 if (!m_manualRunActions)
564 {
565 RunUnloadActions();
566 }
567
568 SaveControlStates();
569 m_active = false;
570 }
571
OnMessage(CGUIMessage & message)572 bool CGUIWindow::OnMessage(CGUIMessage& message)
573 {
574 switch ( message.GetMessage() )
575 {
576 case GUI_MSG_WINDOW_LOAD:
577 {
578 Initialize();
579 return true;
580 }
581 break;
582
583 case GUI_MSG_WINDOW_INIT:
584 {
585 CLog::Log(LOGDEBUG, "------ Window Init (%s) ------", GetProperty("xmlfile").c_str());
586 if (m_dynamicResourceAlloc || !m_bAllocated) AllocResources(false);
587 OnInitWindow();
588 return true;
589 }
590 break;
591
592 case GUI_MSG_WINDOW_DEINIT:
593 {
594 CLog::Log(LOGDEBUG, "------ Window Deinit (%s) ------", GetProperty("xmlfile").c_str());
595 OnDeinitWindow(message.GetParam1());
596 // now free the window
597 if (m_dynamicResourceAlloc) FreeResources();
598 return true;
599 }
600 break;
601
602 case GUI_MSG_UNFOCUS_ALL:
603 {
604 //unfocus the current focused control in this window
605 CGUIControl *control = GetFocusedControl();
606 if(control)
607 {
608 //tell focused control that it has lost the focus
609 CGUIMessage msgLostFocus(GUI_MSG_LOSTFOCUS, GetID(), control->GetID(), control->GetID());
610 control->OnMessage(msgLostFocus);
611 CLog::Log(LOGDEBUG, "Unfocus WindowID: %i, ControlID: %i",GetID(), control->GetID());
612 }
613 return true;
614 }
615
616 case GUI_MSG_FOCUSED:
617 { // a control has been focused
618 if (HasID(message.GetSenderId()))
619 {
620 m_focusedControl = message.GetControlId();
621 return true;
622 }
623 break;
624 }
625 case GUI_MSG_LOSTFOCUS:
626 {
627 // nothing to do at the window level when we lose focus
628 return true;
629 }
630 case GUI_MSG_MOVE:
631 {
632 if (HasID(message.GetSenderId()))
633 return OnMove(message.GetControlId(), message.GetParam1());
634 break;
635 }
636 case GUI_MSG_SETFOCUS:
637 {
638 // CLog::Log(LOGDEBUG,"set focus to control:%i window:%i (%i)", message.GetControlId(),message.GetSenderId(), GetID());
639 if ( message.GetControlId() )
640 {
641 // first unfocus the current control
642 CGUIControl *control = GetFocusedControl();
643 if (control)
644 {
645 CGUIMessage msgLostFocus(GUI_MSG_LOSTFOCUS, GetID(), control->GetID(), message.GetControlId());
646 control->OnMessage(msgLostFocus);
647 }
648
649 // get the control to focus
650 CGUIControl* pFocusedControl = GetFirstFocusableControl(message.GetControlId());
651 if (!pFocusedControl) pFocusedControl = GetControl(message.GetControlId());
652
653 // and focus it
654 if (pFocusedControl)
655 return pFocusedControl->OnMessage(message);
656 }
657 return true;
658 }
659 break;
660 case GUI_MSG_EXCLUSIVE_MOUSE:
661 {
662 m_exclusiveMouseControl = message.GetSenderId();
663 return true;
664 }
665 break;
666 case GUI_MSG_GESTURE_NOTIFY:
667 {
668 CAction action(ACTION_GESTURE_NOTIFY, 0, static_cast<float>(message.GetParam1()), static_cast<float>(message.GetParam2()), 0, 0);
669 EVENT_RESULT result = OnMouseAction(action);
670 auto res = new int(result);
671 message.SetPointer(static_cast<void*>(res));
672 return result != EVENT_RESULT_UNHANDLED;
673 }
674 case GUI_MSG_ADD_CONTROL:
675 {
676 if (message.GetPointer())
677 {
678 CGUIControl *control = static_cast<CGUIControl*>(message.GetPointer());
679 control->AllocResources();
680 AddControl(control);
681 }
682 return true;
683 }
684 case GUI_MSG_REMOVE_CONTROL:
685 {
686 if (message.GetPointer())
687 {
688 CGUIControl *control = static_cast<CGUIControl*>(message.GetPointer());
689 RemoveControl(control);
690 control->FreeResources(true);
691 delete control;
692 }
693 return true;
694 }
695 case GUI_MSG_NOTIFY_ALL:
696 {
697 // only process those notifications that come from this window, or those intended for every window
698 if (HasID(message.GetSenderId()) || !message.GetSenderId())
699 {
700 if (message.GetParam1() == GUI_MSG_PAGE_CHANGE ||
701 message.GetParam1() == GUI_MSG_REFRESH_THUMBS ||
702 message.GetParam1() == GUI_MSG_REFRESH_LIST ||
703 message.GetParam1() == GUI_MSG_WINDOW_RESIZE)
704 { // alter the message accordingly, and send to all controls
705 for (iControls it = m_children.begin(); it != m_children.end(); ++it)
706 {
707 CGUIControl *control = *it;
708 CGUIMessage msg(message.GetParam1(), message.GetControlId(), control->GetID(), message.GetParam2());
709 control->OnMessage(msg);
710 }
711 }
712 else if (message.GetParam1() == GUI_MSG_STATE_CHANGED)
713 MarkDirtyRegion(DIRTY_STATE_CHILD); //Don't force an dirtyRect, we don't know if / what has changed.
714 }
715 }
716 break;
717 }
718
719 return CGUIControlGroup::OnMessage(message);
720 }
721
NeedLoad() const722 bool CGUIWindow::NeedLoad() const
723 {
724 return !m_windowLoaded || CServiceBroker::GetGUI()->GetInfoManager().ConditionsChangedValues(m_xmlIncludeConditions);
725 }
726
AllocResources(bool forceLoad)727 void CGUIWindow::AllocResources(bool forceLoad /*= false */)
728 {
729 CSingleLock lock(CServiceBroker::GetWinSystem()->GetGfxContext());
730
731 #ifdef _DEBUG
732 int64_t start;
733 start = CurrentHostCounter();
734 #endif
735 // use forceLoad to determine if window needs (re)loading
736 forceLoad |= NeedLoad() || (m_loadType == LOAD_EVERY_TIME);
737
738 // if window is loaded and load is forced we have to free window resources first
739 if (m_windowLoaded && forceLoad)
740 FreeResources(true);
741
742 if (forceLoad)
743 {
744 std::string xmlFile = GetProperty("xmlfile").asString();
745 if (xmlFile.size())
746 {
747 bool bHasPath =
748 xmlFile.find('\\') != std::string::npos || xmlFile.find('/') != std::string::npos;
749 Load(xmlFile, bHasPath);
750 }
751 }
752
753 #ifdef _DEBUG
754 int64_t slend;
755 slend = CurrentHostCounter();
756 #endif
757
758 // and now allocate resources
759 CGUIControlGroup::AllocResources();
760
761 #ifdef _DEBUG
762 int64_t end, freq;
763 end = CurrentHostCounter();
764 freq = CurrentHostFrequency();
765 if (forceLoad)
766 CLog::Log(LOGDEBUG,"Alloc resources: %.2fms (%.2f ms skin load)", 1000.f * (end - start) / freq, 1000.f * (slend - start) / freq);
767 else
768 {
769 CLog::Log(LOGDEBUG,"Window %s was already loaded", GetProperty("xmlfile").c_str());
770 CLog::Log(LOGDEBUG,"Alloc resources: %.2fms", 1000.f * (end - start) / freq);
771 }
772 #endif
773 m_bAllocated = true;
774 }
775
FreeResources(bool forceUnload)776 void CGUIWindow::FreeResources(bool forceUnload /*= false */)
777 {
778 m_bAllocated = false;
779 CGUIControlGroup::FreeResources();
780 //CServiceBroker::GetGUI()->GetTextureManager().Dump();
781 // unload the skin
782 if (m_loadType == LOAD_EVERY_TIME || forceUnload) ClearAll();
783 if (forceUnload)
784 {
785 delete m_windowXMLRootElement;
786 m_windowXMLRootElement = nullptr;
787 m_xmlIncludeConditions.clear();
788 }
789 }
790
DynamicResourceAlloc(bool bOnOff)791 void CGUIWindow::DynamicResourceAlloc(bool bOnOff)
792 {
793 m_dynamicResourceAlloc = bOnOff;
794 CGUIControlGroup::DynamicResourceAlloc(bOnOff);
795 }
796
ClearAll()797 void CGUIWindow::ClearAll()
798 {
799 OnWindowUnload();
800 CGUIControlGroup::ClearAll();
801 m_windowLoaded = false;
802 m_dynamicResourceAlloc = true;
803 m_visibleCondition.reset();
804 }
805
Initialize()806 bool CGUIWindow::Initialize()
807 {
808 if (!CServiceBroker::GetGUI()->GetWindowManager().Initialized())
809 return false;
810 if (!NeedLoad())
811 return true;
812 if (g_application.IsCurrentThread())
813 AllocResources(false);
814 else
815 {
816 // if not app thread, send gui msg via app messenger
817 // and wait for results, so windowLoaded flag would be updated
818 CGUIMessage msg(GUI_MSG_WINDOW_LOAD, 0, 0);
819 CApplicationMessenger::GetInstance().SendGUIMessage(msg, GetID(), true);
820 }
821 return m_windowLoaded;
822 }
823
SetInitialVisibility()824 void CGUIWindow::SetInitialVisibility()
825 {
826 // reset our info manager caches
827 CGUIInfoManager& infoMgr = CServiceBroker::GetGUI()->GetInfoManager();
828 infoMgr.ResetCache();
829 infoMgr.GetInfoProviders().GetGUIControlsInfoProvider().ResetContainerMovingCache();
830 CGUIControlGroup::SetInitialVisibility();
831 }
832
IsActive() const833 bool CGUIWindow::IsActive() const
834 {
835 return CServiceBroker::GetGUI()->GetWindowManager().IsWindowActive(GetID());
836 }
837
CheckAnimation(ANIMATION_TYPE animType)838 bool CGUIWindow::CheckAnimation(ANIMATION_TYPE animType)
839 {
840 // special cases first
841 if (animType == ANIM_TYPE_WINDOW_CLOSE)
842 {
843 if (!m_bAllocated || !HasProcessed()) // can't process an animation if we aren't allocated or haven't processed
844 return false;
845 // make sure we update our visibility prior to queuing the window close anim
846 for (unsigned int i = 0; i < m_children.size(); i++)
847 m_children[i]->UpdateVisibility(nullptr);
848 }
849 return true;
850 }
851
IsAnimating(ANIMATION_TYPE animType)852 bool CGUIWindow::IsAnimating(ANIMATION_TYPE animType)
853 {
854 if (!m_animationsEnabled)
855 return false;
856 if (animType == ANIM_TYPE_WINDOW_CLOSE)
857 return m_closing;
858 return CGUIControlGroup::IsAnimating(animType);
859 }
860
Animate(unsigned int currentTime)861 bool CGUIWindow::Animate(unsigned int currentTime)
862 {
863 if (m_animationsEnabled)
864 return CGUIControlGroup::Animate(currentTime);
865 else
866 {
867 m_transform.Reset();
868 return false;
869 }
870 }
871
DisableAnimations()872 void CGUIWindow::DisableAnimations()
873 {
874 m_animationsEnabled = false;
875 }
876
877 // returns true if the control group with id groupID has controlID as
878 // its focused control
ControlGroupHasFocus(int groupID,int controlID)879 bool CGUIWindow::ControlGroupHasFocus(int groupID, int controlID)
880 {
881 // 1. Run through and get control with groupID (assume unique)
882 // 2. Get it's selected item.
883 CGUIControl *group = GetFirstFocusableControl(groupID);
884 if (!group) group = GetControl(groupID);
885
886 if (group && group->IsGroup())
887 {
888 if (controlID == 0)
889 { // just want to know if the group is focused
890 return group->HasFocus();
891 }
892 else
893 {
894 CGUIMessage message(GUI_MSG_ITEM_SELECTED, GetID(), group->GetID());
895 group->OnMessage(message);
896 return (controlID == message.GetParam1());
897 }
898 }
899 return false;
900 }
901
SaveControlStates()902 void CGUIWindow::SaveControlStates()
903 {
904 ResetControlStates();
905 if (!m_defaultAlways)
906 m_lastControlID = GetFocusedControlID();
907 for (iControls it = m_children.begin(); it != m_children.end(); ++it)
908 (*it)->SaveStates(m_controlStates);
909 }
910
RestoreControlStates()911 void CGUIWindow::RestoreControlStates()
912 {
913 for (const auto& it : m_controlStates)
914 {
915 CGUIMessage message(GUI_MSG_ITEM_SELECT, GetID(), it.m_id, it.m_data);
916 OnMessage(message);
917 }
918 int focusControl = (!m_defaultAlways && m_lastControlID) ? m_lastControlID : m_defaultControl;
919 SET_CONTROL_FOCUS(focusControl, 0);
920 }
921
ResetControlStates()922 void CGUIWindow::ResetControlStates()
923 {
924 m_lastControlID = 0;
925 m_focusedControl = 0;
926 m_controlStates.clear();
927 }
928
OnBack(int actionID)929 bool CGUIWindow::OnBack(int actionID)
930 {
931 CServiceBroker::GetGUI()->GetWindowManager().PreviousWindow();
932 return true;
933 }
934
OnMove(int fromControl,int moveAction)935 bool CGUIWindow::OnMove(int fromControl, int moveAction)
936 {
937 const CGUIControl *control = GetFirstFocusableControl(fromControl);
938 if (!control) control = GetControl(fromControl);
939 if (!control)
940 { // no current control??
941 CLog::Log(LOGERROR, "Unable to find control %i in window %u",
942 fromControl, GetID());
943 return false;
944 }
945 std::vector<int> moveHistory;
946 int nextControl = fromControl;
947 while (control)
948 { // grab the next control direction
949 moveHistory.push_back(nextControl);
950 CGUIAction action = control->GetAction(moveAction);
951 action.ExecuteActions(nextControl, GetParentID());
952 nextControl = action.GetNavigation();
953 if (!nextControl) // 0 isn't valid control id
954 return false;
955 // check our history - if the nextControl is in it, we can't focus it
956 for (unsigned int i = 0; i < moveHistory.size(); i++)
957 {
958 if (nextControl == moveHistory[i])
959 return false; // no control to focus so do nothing
960 }
961 control = GetFirstFocusableControl(nextControl);
962 if (control)
963 break; // found a focusable control
964 control = GetControl(nextControl); // grab the next control and try again
965 }
966 if (!control)
967 return false; // no control to focus
968 // if we get here we have our new control so focus it (and unfocus the current control)
969 SET_CONTROL_FOCUS(nextControl, 0);
970 return true;
971 }
972
SetDefaults()973 void CGUIWindow::SetDefaults()
974 {
975 m_renderOrder = RENDER_ORDER_WINDOW;
976 m_defaultAlways = false;
977 m_defaultControl = 0;
978 m_posX = m_posY = m_width = m_height = 0;
979 m_previousWindow = WINDOW_INVALID;
980 m_animations.clear();
981 m_origins.clear();
982 m_hasCamera = false;
983 m_stereo = 0.f;
984 m_animationsEnabled = true;
985 m_clearBackground = 0xff000000; // opaque black -> clear
986 m_hitRect.SetRect(0, 0, static_cast<float>(m_coordsRes.iWidth), static_cast<float>(m_coordsRes.iHeight));
987 m_menuControlID = 0;
988 m_menuLastFocusedControlID = 0;
989 }
990
GetScaledBounds() const991 CRect CGUIWindow::GetScaledBounds() const
992 {
993 CSingleLock lock(CServiceBroker::GetWinSystem()->GetGfxContext());
994 CServiceBroker::GetWinSystem()->GetGfxContext().SetScalingResolution(m_coordsRes, m_needsScaling);
995 CPoint pos(GetPosition());
996 CRect rect(pos.x, pos.y, pos.x + m_width, pos.y + m_height);
997 float z = 0;
998 CServiceBroker::GetWinSystem()->GetGfxContext().ScaleFinalCoords(rect.x1, rect.y1, z);
999 CServiceBroker::GetWinSystem()->GetGfxContext().ScaleFinalCoords(rect.x2, rect.y2, z);
1000 return rect;
1001 }
1002
OnEditChanged(int id,std::string & text)1003 void CGUIWindow::OnEditChanged(int id, std::string &text)
1004 {
1005 CGUIMessage msg(GUI_MSG_ITEM_SELECTED, GetID(), id);
1006 OnMessage(msg);
1007 text = msg.GetLabel();
1008 }
1009
SendMessage(int message,int id,int param1,int param2)1010 bool CGUIWindow::SendMessage(int message, int id, int param1 /* = 0*/, int param2 /* = 0*/)
1011 {
1012 CGUIMessage msg(message, GetID(), id, param1, param2);
1013 return OnMessage(msg);
1014 }
1015 #ifdef _DEBUG
DumpTextureUse()1016 void CGUIWindow::DumpTextureUse()
1017 {
1018 CLog::Log(LOGDEBUG, "%s for window %u", __FUNCTION__, GetID());
1019 CGUIControlGroup::DumpTextureUse();
1020 }
1021 #endif
1022
SetProperty(const std::string & strKey,const CVariant & value)1023 void CGUIWindow::SetProperty(const std::string &strKey, const CVariant &value)
1024 {
1025 CSingleLock lock(*this);
1026 m_mapProperties[strKey] = value;
1027 }
1028
GetProperty(const std::string & strKey) const1029 CVariant CGUIWindow::GetProperty(const std::string &strKey) const
1030 {
1031 CSingleLock lock(const_cast<CGUIWindow&>(*this));
1032 std::map<std::string, CVariant, icompare>::const_iterator iter = m_mapProperties.find(strKey);
1033 if (iter == m_mapProperties.end())
1034 return CVariant(CVariant::VariantTypeNull);
1035
1036 return iter->second;
1037 }
1038
ClearProperties()1039 void CGUIWindow::ClearProperties()
1040 {
1041 CSingleLock lock(*this);
1042 m_mapProperties.clear();
1043 }
1044
SetRunActionsManually()1045 void CGUIWindow::SetRunActionsManually()
1046 {
1047 m_manualRunActions = true;
1048 }
1049
RunLoadActions() const1050 void CGUIWindow::RunLoadActions() const
1051 {
1052 m_loadActions.ExecuteActions(GetID(), GetParentID());
1053 }
1054
RunUnloadActions() const1055 void CGUIWindow::RunUnloadActions() const
1056 {
1057 m_unloadActions.ExecuteActions(GetID(), GetParentID());
1058 }
1059
ClearBackground()1060 void CGUIWindow::ClearBackground()
1061 {
1062 m_clearBackground.Update();
1063 UTILS::Color color = m_clearBackground;
1064 if (color)
1065 CServiceBroker::GetWinSystem()->GetGfxContext().Clear(color);
1066 }
1067
SetID(int id)1068 void CGUIWindow::SetID(int id)
1069 {
1070 CGUIControlGroup::SetID(id);
1071 m_idRange.clear();
1072 m_idRange.push_back(id);
1073 }
1074
HasID(int controlID) const1075 bool CGUIWindow::HasID(int controlID) const
1076 {
1077 for (const auto& it : m_idRange)
1078 {
1079 if (controlID == it)
1080 return true;
1081 }
1082 return false;
1083 }
1084