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