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