2 * Copyright (C) 2005-2013 Team XBMC
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)
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.
15 * You should have received a copy of the GNU General Public License
16 * along with XBMC; see the file COPYING. If not, write to
17 * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
18 * http://www.gnu.org/copyleft/gpl.html
23 #include "WindowInterceptor.h"
24 #include "guilib/GUIButtonControl.h"
25 #include "guilib/GUIEditControl.h"
26 #include "guilib/GUICheckMarkControl.h"
27 #include "guilib/GUIRadioButtonControl.h"
28 #include "guilib/GUIWindowManager.h"
29 #include "settings/Settings.h"
30 #include "Application.h"
31 #include "ApplicationMessenger.h"
32 #include "utils/Variant.h"
34 #define ACTIVE_WINDOW g_windowManager.GetActiveWindow()
40 XbmcThreads::ThreadLocal<ref> InterceptorBase::upcallTls;
43 * Used in add/remove control. It only locks if it's given a
44 * non-NULL CCriticalSection. It's given a NULL CCriticalSection
45 * when a function higher in the call stack already has a
49 CCriticalSection* lock;
51 inline MaybeLock(CCriticalSection* p_lock) : lock(p_lock) { if (lock) lock->lock(); }
52 inline ~MaybeLock() { if (lock) lock->unlock(); }
55 class SingleLockWithDelayGuard
58 CCriticalSection& lock;
60 inline SingleLockWithDelayGuard(CCriticalSection& ccrit, LanguageHook* lh) : dcg(lh), lock(ccrit) { lock.lock(); }
61 inline ~SingleLockWithDelayGuard() { lock.unlock(); }
65 * Explicit template instantiation
67 template class Interceptor<CGUIWindow>;
70 * This interceptor is a simple, non-callbackable (is that a word?)
71 * Interceptor to satisfy the Window requirements for upcalling
72 * for the purposes of instantiating a Window instance from
73 * an already existing window.
75 class ProxyExistingWindowInterceptor : public InterceptorBase
77 CGUIWindow* cguiwindow;
80 inline ProxyExistingWindowInterceptor(CGUIWindow* window) :
81 cguiwindow(window) { TRACE; }
83 virtual CGUIWindow* get();
86 CGUIWindow* ProxyExistingWindowInterceptor::get() { TRACE; return cguiwindow; }
88 Window::Window(const char* classname) throw (WindowException):
89 AddonCallback(classname), isDisposed(false), window(NULL), iWindowId(-1),
90 iOldWindowId(0), iCurrentControlId(3000), bModal(false), m_actionEvent(true),
91 canPulse(true), existingWindow(false), destroyAfterDeInit(false)
97 * This just creates a default window.
99 Window::Window(int existingWindowId) throw (WindowException) :
100 AddonCallback("Window"), isDisposed(false), window(NULL), iWindowId(-1),
101 iOldWindowId(0), iCurrentControlId(3000), bModal(false), m_actionEvent(true),
102 canPulse(false), existingWindow(true), destroyAfterDeInit(false)
105 SingleLockWithDelayGuard gslock(g_graphicsContext,languageHook);
107 if (existingWindowId == -1)
109 // in this case just do the other constructor.
111 existingWindow = false;
113 setWindow(new Interceptor<CGUIWindow>("CGUIWindow",this,getNextAvailalbeWindowId()));
117 // user specified window id, use this one if it exists
118 // It is not possible to capture key presses or button presses
119 CGUIWindow* pWindow = g_windowManager.GetWindow(existingWindowId);
121 throw WindowException("Window id does not exist");
123 setWindow(new ProxyExistingWindowInterceptor(pWindow));
134 void Window::deallocating()
136 AddonCallback::deallocating();
141 void Window::dispose()
145 // this is called from non-scripting-language callstacks. Don't use the delayed call guard.
146 CSingleLock lock(g_graphicsContext);
151 // no callbacks are possible any longer
152 // - this will be handled by the parent constructor
154 // first change to an existing window
157 if (ACTIVE_WINDOW == iWindowId && !g_application.m_bStop)
159 if(g_windowManager.GetWindow(iOldWindowId))
161 g_windowManager.ActivateWindow(iOldWindowId);
163 // old window does not exist anymore, switch to home
164 else g_windowManager.ActivateWindow(WINDOW_HOME);
171 // This is an existing window, so no resources are free'd. Note that
172 // THIS WILL FAIL for any controls newly created by python - they will
173 // remain after the script ends. Ideally this would be remedied by
174 // a flag in Control that specifies that it was python created - any python
175 // created controls could then be removed + free'd from the window.
176 // how this works with controlgroups though could be a bit tricky.
179 // and free our list of controls
180 std::vector<AddonClass::Ref<Control> >::iterator it = vecControls.begin();
181 while (it != vecControls.end())
183 AddonClass::Ref<Control> pControl = *it;
184 // initialize control to zero
185 pControl->pGUIControl = NULL;
186 pControl->iControlId = 0;
187 pControl->iParentId = 0;
195 if (g_windowManager.IsWindowVisible(ref(window)->GetID()))
197 destroyAfterDeInit = true;
201 g_windowManager.Delete(ref(window)->GetID());
209 void Window::setWindow(InterceptorBase* _window)
213 iWindowId = _window->get()->GetID();
216 g_windowManager.Add(window->get());
219 int Window::getNextAvailalbeWindowId() throw (WindowException)
222 // window id's 13000 - 13100 are reserved for python
223 // get first window id that is not in use
224 int id = WINDOW_PYTHON_START;
225 // if window 13099 is in use it means python can't create more windows
226 if (g_windowManager.GetWindow(WINDOW_PYTHON_END))
227 throw WindowException("maximum number of windows reached");
229 while(id < WINDOW_PYTHON_END && g_windowManager.GetWindow(id) != NULL) id++;
233 void Window::popActiveWindowId()
236 if (iOldWindowId != iWindowId &&
237 iWindowId != ACTIVE_WINDOW)
238 iOldWindowId = ACTIVE_WINDOW;
241 // Internal helper method
242 /* Searches for a control in Window->vecControls
243 * If we can't find any but the window has the controlId (in case of a not python window)
244 * we create a new control with basic functionality
246 Control* Window::GetControlById(int iControlId, CCriticalSection* gc) throw (WindowException)
250 // find in window vector first!!!
251 // this saves us from creating a complete new control
252 std::vector<AddonClass::Ref<Control> >::iterator it = vecControls.begin();
253 while (it != vecControls.end())
255 AddonClass::Ref<Control> control = (*it);
256 if (control->iControlId == iControlId)
258 return control.get();
262 // lock xbmc GUI before accessing data from it
265 // check if control exists
266 CGUIControl* pGUIControl = (CGUIControl*)ref(window)->GetControl(iControlId);
269 // control does not exist.
270 throw WindowException("Non-Existent Control %d",iControlId);
273 // allocate a new control with a new reference
276 Control* pControl = NULL;
278 // TODO: Yuck! Should probably be done with a Factory pattern
279 switch(pGUIControl->GetControlType())
281 case CGUIControl::GUICONTROL_BUTTON:
282 pControl = new ControlButton();
284 li = ((CGUIButtonControl *)pGUIControl)->GetLabelInfo();
286 // note: conversion from infocolors -> plain colors here
287 ((ControlButton*)pControl)->disabledColor = li.disabledColor;
288 ((ControlButton*)pControl)->focusedColor = li.focusedColor;
289 ((ControlButton*)pControl)->textColor = li.textColor;
290 ((ControlButton*)pControl)->shadowColor = li.shadowColor;
291 if (li.font) ((ControlButton*)pControl)->strFont = li.font->GetFontName();
292 ((ControlButton*)pControl)->align = li.align;
294 case CGUIControl::GUICONTROL_CHECKMARK:
295 pControl = new ControlCheckMark();
297 li = ((CGUICheckMarkControl *)pGUIControl)->GetLabelInfo();
299 // note: conversion to plain colors from infocolors.
300 ((ControlCheckMark*)pControl)->disabledColor = li.disabledColor;
301 //((ControlCheckMark*)pControl)->shadowColor = li.shadowColor;
302 ((ControlCheckMark*)pControl)->textColor = li.textColor;
303 if (li.font) ((ControlCheckMark*)pControl)->strFont = li.font->GetFontName();
304 ((ControlCheckMark*)pControl)->align = li.align;
306 case CGUIControl::GUICONTROL_LABEL:
307 pControl = new ControlLabel();
309 case CGUIControl::GUICONTROL_SPIN:
310 pControl = new ControlSpin();
312 case CGUIControl::GUICONTROL_FADELABEL:
313 pControl = new ControlFadeLabel();
315 case CGUIControl::GUICONTROL_TEXTBOX:
316 pControl = new ControlTextBox();
318 case CGUIControl::GUICONTROL_IMAGE:
319 pControl = new ControlImage();
321 case CGUIControl::GUICONTROL_PROGRESS:
322 pControl = new ControlProgress();
324 case CGUIControl::GUICONTROL_SLIDER:
325 pControl = new ControlSlider();
327 case CGUIControl::GUICONTAINER_LIST:
328 case CGUIControl::GUICONTAINER_WRAPLIST:
329 case CGUIControl::GUICONTAINER_FIXEDLIST:
330 case CGUIControl::GUICONTAINER_PANEL:
331 pControl = new ControlList();
332 // create a python spin control
333 ((ControlList*)pControl)->pControlSpin = new ControlSpin();
335 case CGUIControl::GUICONTROL_GROUP:
336 pControl = new ControlGroup();
338 case CGUIControl::GUICONTROL_RADIO:
339 pControl = new ControlRadioButton();
341 li = ((CGUIRadioButtonControl *)pGUIControl)->GetLabelInfo();
343 // note: conversion from infocolors -> plain colors here
344 ((ControlRadioButton*)pControl)->disabledColor = li.disabledColor;
345 ((ControlRadioButton*)pControl)->focusedColor = li.focusedColor;
346 ((ControlRadioButton*)pControl)->textColor = li.textColor;
347 ((ControlRadioButton*)pControl)->shadowColor = li.shadowColor;
348 if (li.font) ((ControlRadioButton*)pControl)->strFont = li.font->GetFontName();
349 ((ControlRadioButton*)pControl)->align = li.align;
351 case CGUIControl::GUICONTROL_EDIT:
352 pControl = new ControlEdit();
354 li = ((CGUIEditControl *)pGUIControl)->GetLabelInfo();
356 // note: conversion from infocolors -> plain colors here
357 ((ControlEdit*)pControl)->disabledColor = li.disabledColor;
358 ((ControlEdit*)pControl)->textColor = li.textColor;
359 if (li.font) ((ControlEdit*)pControl)->strFont = li.font->GetFontName();
360 ((ControlButton*)pControl)->align = li.align;
368 throw WindowException("Unknown control type for python");
370 // we have a valid control here, fill in all the 'Control' data
371 pControl->pGUIControl = pGUIControl;
372 pControl->iControlId = pGUIControl->GetID();
373 pControl->iParentId = iWindowId;
374 pControl->dwHeight = (int)pGUIControl->GetHeight();
375 pControl->dwWidth = (int)pGUIControl->GetWidth();
376 pControl->dwPosX = (int)pGUIControl->GetXPosition();
377 pControl->dwPosY = (int)pGUIControl->GetYPosition();
378 pControl->iControlUp = pGUIControl->GetControlIdUp();
379 pControl->iControlDown = pGUIControl->GetControlIdDown();
380 pControl->iControlLeft = pGUIControl->GetControlIdLeft();
381 pControl->iControlRight = pGUIControl->GetControlIdRight();
383 // It got this far so means the control isn't actually in the vector of controls
384 // so lets add it to save doing all that next time
385 vecControls.push_back(AddonClass::Ref<Control>(pControl));
387 // return the control with increased reference (+1)
391 void Window::PulseActionEvent()
398 bool Window::WaitForActionEvent(unsigned int milliseconds)
401 // DO NOT MAKE THIS A DELAYED CALL!!!!
402 bool ret = languageHook == NULL ? m_actionEvent.WaitMSec(milliseconds) : languageHook->WaitForEvent(m_actionEvent,milliseconds);
404 m_actionEvent.Reset();
408 bool Window::OnAction(const CAction &action)
411 // do the base class window first, and the call to python after this
412 bool ret = ref(window)->OnAction(action);
414 // workaround - for scripts which try to access the active control (focused) when there is none.
415 // for example - the case when the mouse enters the screen.
416 CGUIControl *pControl = ref(window)->GetFocusedControl();
417 if (action.IsMouse() && !pControl)
420 AddonClass::Ref<Action> inf(new Action(action));
421 invokeCallback(new CallbackFunction<Window,AddonClass::Ref<Action> >(this,&Window::onAction,inf.get()));
427 bool Window::OnBack(int actionID)
429 // we are always a Python window ... keep that in mind when reviewing the old code
433 void Window::OnDeinitWindow(int nextWindowID /*= 0*/)
435 // NOTE!: This handle child classes correctly. XML windows will call
436 // the OnDeinitWindow from CGUIMediaWindow while non-XML classes will
437 // call the OnDeinitWindow on CGUIWindow
438 ref(window)->OnDeinitWindow(nextWindowID);
439 if (destroyAfterDeInit)
440 g_windowManager.Delete(window->get()->GetID());
443 void Window::onAction(Action* action)
446 // default onAction behavior
447 if(action->id == ACTION_PREVIOUS_MENU || action->id == ACTION_NAV_BACK)
451 bool Window::OnMessage(CGUIMessage& message)
454 switch (message.GetMessage())
456 case GUI_MSG_WINDOW_DEINIT:
458 g_windowManager.ShowOverlay(ref(window)->OVERLAY_STATE_SHOWN);
462 case GUI_MSG_WINDOW_INIT:
464 ref(window)->OnMessage(message);
465 g_windowManager.ShowOverlay(ref(window)->OVERLAY_STATE_HIDDEN);
470 case GUI_MSG_CLICKED:
472 int iControl=message.GetSenderId();
473 AddonClass::Ref<Control> inf;
474 // find python control object with same iControl
475 std::vector<AddonClass::Ref<Control> >::iterator it = vecControls.begin();
476 while (it != vecControls.end())
478 AddonClass::Ref<Control> pControl = (*it);
479 if (pControl->iControlId == iControl)
481 inf = pControl.get();
487 // did we find our control?
490 // currently we only accept messages from a button or controllist with a select action
491 if (inf->canAcceptMessages(message.GetParam1()))
493 invokeCallback(new CallbackFunction<Window,AddonClass::Ref<Control> >(this,&Window::onControl,inf.get()));
496 // return true here as we are handling the event
500 // if we get here, we didn't add the action
505 return ref(window)->OnMessage(message);
508 void Window::onControl(Control* action) { TRACE; /* do nothing by default */ }
509 void Window::onClick(int controlId) { TRACE; /* do nothing by default */ }
510 void Window::onFocus(int controlId) { TRACE; /* do nothing by default */ }
511 void Window::onInit() { TRACE; /* do nothing by default */ }
516 DelayedCallGuard dcguard(languageHook);
519 std::vector<CStdString> params;
520 CApplicationMessenger::Get().ActivateWindow(iWindowId, params, false);
523 void Window::setFocus(Control* pControl) throw (WindowException)
527 throw WindowException("Object should be of type Control");
529 CGUIMessage msg = CGUIMessage(GUI_MSG_SETFOCUS,pControl->iParentId, pControl->iControlId);
530 g_windowManager.SendThreadMessage(msg, pControl->iParentId);
533 void Window::setFocusId(int iControlId)
536 CGUIMessage msg = CGUIMessage(GUI_MSG_SETFOCUS,iWindowId,iControlId);
537 g_windowManager.SendThreadMessage(msg, iWindowId);
540 Control* Window::getFocus() throw (WindowException)
543 SingleLockWithDelayGuard gslock(g_graphicsContext,languageHook);
545 int iControlId = ref(window)->GetFocusedControlID();
547 throw WindowException("No control in this window has focus");
548 // Sine I'm already holding the lock theres no reason to give it to GetFocusedControlID
549 return GetControlById(iControlId,NULL);
552 long Window::getFocusId() throw (WindowException)
555 SingleLockWithDelayGuard gslock(g_graphicsContext,languageHook);
556 int iControlId = ref(window)->GetFocusedControlID();
558 throw WindowException("No control in this window has focus");
559 return (long)iControlId;
562 void Window::removeControl(Control* pControl) throw (WindowException)
565 DelayedCallGuard dg(languageHook);
566 doRemoveControl(pControl,&g_graphicsContext,true);
569 void Window::doRemoveControl(Control* pControl, CCriticalSection* gcontext, bool wait) throw (WindowException)
572 // type checking, object should be of type Control
574 throw WindowException("Object should be of type Control");
577 MaybeLock mlock(gcontext);
578 if(!ref(window)->GetControl(pControl->iControlId))
579 throw WindowException("Control does not exist in window");
582 // delete control from vecControls in window object
583 std::vector<AddonClass::Ref<Control> >::iterator it = vecControls.begin();
584 while (it != vecControls.end())
586 AddonClass::Ref<Control> control = (*it);
587 if (control->iControlId == pControl->iControlId)
589 it = vecControls.erase(it);
593 CGUIMessage msg(GUI_MSG_REMOVE_CONTROL, 0, 0);
594 msg.SetPointer(pControl->pGUIControl);
595 CApplicationMessenger::Get().SendGUIMessage(msg, iWindowId, wait);
597 // initialize control to zero
598 pControl->pGUIControl = NULL;
599 pControl->iControlId = 0;
600 pControl->iParentId = 0;
603 void Window::removeControls(std::vector<Control*> pControls) throw (WindowException)
606 DelayedCallGuard dg(languageHook);
607 int count = 1; int size = pControls.size();
608 for (std::vector<Control*>::iterator iter = pControls.begin(); iter != pControls.end(); count++, iter++)
609 doRemoveControl(*iter,NULL, count == size);
612 long Window::getHeight()
615 return g_graphicsContext.GetHeight();
618 long Window::getWidth()
621 return g_graphicsContext.GetWidth();
624 long Window::getResolution()
627 return (long)g_graphicsContext.GetVideoResolution();
630 void Window::setCoordinateResolution(long res) throw (WindowException)
633 if (res < RES_HDTV_1080i || res > RES_AUTORES)
634 throw WindowException("Invalid resolution.");
636 SingleLockWithDelayGuard gslock(g_graphicsContext,languageHook);
637 ref(window)->SetCoordsRes(g_settings.m_ResInfo[res]);
640 void Window::setProperty(const char* key, const String& value)
643 SingleLockWithDelayGuard gslock(g_graphicsContext,languageHook);
644 CStdString lowerKey = key;
646 ref(window)->SetProperty(lowerKey.ToLower(), value);
649 String Window::getProperty(const char* key)
652 SingleLockWithDelayGuard gslock(g_graphicsContext,languageHook);
653 CStdString lowerKey = key;
654 std::string value = ref(window)->GetProperty(lowerKey.ToLower()).asString();
655 return value.c_str();
658 void Window::clearProperty(const char* key)
662 SingleLockWithDelayGuard gslock(g_graphicsContext,languageHook);
664 CStdString lowerKey = key;
665 ref(window)->SetProperty(lowerKey.ToLower(), "");
668 void Window::clearProperties()
671 SingleLockWithDelayGuard gslock(g_graphicsContext,languageHook);
672 ref(window)->ClearProperties();
683 std::vector<CStdString> params;
684 CApplicationMessenger::Get().ActivateWindow(iOldWindowId, params, false);
689 void Window::doModal()
696 if(iWindowId != ACTIVE_WINDOW)
699 while (bModal && !g_application.m_bStop)
701 // TODO: garbear added this code to the pythin window.cpp class and
702 // commented in XBPyThread.cpp. I'm not sure how to handle this
703 // in this native implementation.
704 // // Check if XBPyThread::stop() raised a SystemExit exception
705 // if (PyThreadState_Get()->async_exc == PyExc_SystemExit)
707 // CLog::Log(LOGDEBUG, "PYTHON: doModal() encountered a SystemExit exception, closing window and returning");
708 // Window_Close(self, NULL);
711 languageHook->MakePendingCalls(); // MakePendingCalls
717 DelayedCallGuard dcguard(languageHook);
718 stillWaiting = WaitForActionEvent(100) ? false : true;
720 languageHook->MakePendingCalls();
721 } while (stillWaiting);
726 void Window::addControl(Control* pControl) throw (WindowException)
729 DelayedCallGuard dg(languageHook);
730 doAddControl(pControl,&g_graphicsContext,true);
733 void Window::doAddControl(Control* pControl, CCriticalSection* gcontext, bool wait) throw (WindowException)
737 throw WindowException("NULL Control passed to WindowBase::addControl");
739 if(pControl->iControlId != 0)
740 throw WindowException("Control is already used");
742 // lock xbmc GUI before accessing data from it
743 pControl->iParentId = iWindowId;
746 MaybeLock mlock(gcontext);
747 // assign control id, if id is already in use, try next id
748 do pControl->iControlId = ++iCurrentControlId;
749 while (ref(window)->GetControl(pControl->iControlId));
754 // set default navigation for control
755 pControl->iControlUp = pControl->iControlId;
756 pControl->iControlDown = pControl->iControlId;
757 pControl->iControlLeft = pControl->iControlId;
758 pControl->iControlRight = pControl->iControlId;
760 pControl->pGUIControl->SetNavigation(pControl->iControlUp,
761 pControl->iControlDown, pControl->iControlLeft, pControl->iControlRight);
763 // add control to list and allocate recources for the control
764 vecControls.push_back(AddonClass::Ref<Control>(pControl));
765 pControl->pGUIControl->AllocResources();
767 // This calls the CGUIWindow parent class to do the final add
768 CGUIMessage msg(GUI_MSG_ADD_CONTROL, 0, 0);
769 msg.SetPointer(pControl->pGUIControl);
770 CApplicationMessenger::Get().SendGUIMessage(msg, iWindowId, wait);
773 void Window::addControls(std::vector<Control*> pControls) throw (WindowException)
776 SingleLockWithDelayGuard gslock(g_graphicsContext,languageHook);
777 int count = 1; int size = pControls.size();
778 for (std::vector<Control*>::iterator iter = pControls.begin(); iter != pControls.end(); count++, iter++)
779 doAddControl(*iter,NULL, count == size);
782 Control* Window::getControl(int iControlId) throw (WindowException)
785 DelayedCallGuard dg(languageHook);
786 return GetControlById(iControlId,&g_graphicsContext);
789 void Action::setFromCAction(const CAction& action)
793 buttonCode = action.GetButtonCode();
794 fAmount1 = action.GetAmount(0);
795 fAmount2 = action.GetAmount(1);
796 fRepeat = action.GetRepeat();
797 strAction = action.GetName();