Merge pull request #3819 from arnova/subtitles_for_stacks
[vuplus_xbmc] / xbmc / interfaces / legacy / WindowXML.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 "WindowXML.h"
22
23 #include "WindowInterceptor.h"
24 #include "guilib/GUIWindowManager.h"
25 #include "guilib/TextureManager.h"
26 #include "settings/Settings.h"
27 #include "addons/Skin.h"
28 #include "filesystem/File.h"
29 #include "utils/URIUtils.h"
30 #include "utils/StringUtils.h"
31 #include "addons/Addon.h"
32
33 // These #defs are for WindowXML
34 #define CONTROL_BTNVIEWASICONS  2
35 #define CONTROL_BTNSORTBY       3
36 #define CONTROL_BTNSORTASC      4
37 #define CONTROL_LABELFILES      12
38
39 #define A(x) interceptor->x
40
41 namespace XBMCAddon
42 {
43   namespace xbmcgui
44   {
45     template class Interceptor<CGUIMediaWindow>;
46
47     /**
48      * This class extends the Interceptor<CGUIMediaWindow> in order to 
49      *  add behavior for a few more virtual functions that were unneccessary
50      *  in the Window or WindowDialog.
51      */
52 #define checkedb(methcall) ( window.isNotNull() ? xwin-> methcall : false )
53 #define checkedv(methcall) { if (window.isNotNull()) xwin-> methcall ; }
54
55
56     // TODO: This should be done with template specialization
57     class WindowXMLInterceptor : public InterceptorDialog<CGUIMediaWindow>
58     {
59       WindowXML* xwin;
60     public:
61       WindowXMLInterceptor(WindowXML* _window, int windowid,const char* xmlfile) :
62         InterceptorDialog<CGUIMediaWindow>("CGUIMediaWindow",_window,windowid,xmlfile), xwin(_window) 
63       { }
64
65       virtual void AllocResources(bool forceLoad = false)
66       { XBMC_TRACE; if(up()) CGUIMediaWindow::AllocResources(forceLoad); else checkedv(AllocResources(forceLoad)); }
67       virtual  void FreeResources(bool forceUnLoad = false)
68       { XBMC_TRACE; if(up()) CGUIMediaWindow::FreeResources(forceUnLoad); else checkedv(FreeResources(forceUnLoad)); }
69       virtual bool OnClick(int iItem) { XBMC_TRACE; return up() ? CGUIMediaWindow::OnClick(iItem) : checkedb(OnClick(iItem)); }
70
71       virtual void Process(unsigned int currentTime, CDirtyRegionList &dirtyregions)
72       { XBMC_TRACE; if(up()) CGUIMediaWindow::Process(currentTime,dirtyregions); else checkedv(Process(currentTime,dirtyregions)); }
73
74       // this is a hack to SKIP the CGUIMediaWindow
75       virtual bool OnAction(const CAction &action) 
76       { XBMC_TRACE; return up() ? CGUIWindow::OnAction(action) : checkedb(OnAction(action)); }
77
78     protected:
79       // CGUIWindow
80       virtual bool LoadXML(const CStdString &strPath, const CStdString &strPathLower)
81       { XBMC_TRACE; return up() ? CGUIMediaWindow::LoadXML(strPath,strPathLower) : xwin->LoadXML(strPath,strPathLower); }
82
83       // CGUIMediaWindow
84       virtual void GetContextButtons(int itemNumber, CContextButtons &buttons)
85       { XBMC_TRACE; if (up()) CGUIMediaWindow::GetContextButtons(itemNumber,buttons); else xwin->GetContextButtons(itemNumber,buttons); }
86       virtual bool Update(const CStdString &strPath)
87       { XBMC_TRACE; return up() ? CGUIMediaWindow::Update(strPath) : xwin->Update(strPath); }
88       virtual void SetupShares() { XBMC_TRACE; if(up()) CGUIMediaWindow::SetupShares(); else checkedv(SetupShares()); }
89
90       friend class WindowXML;
91       friend class WindowXMLDialog;
92
93     };
94
95     WindowXML::~WindowXML() { XBMC_TRACE; deallocating();  }
96
97     WindowXML::WindowXML(const String& xmlFilename,
98                          const String& scriptPath,
99                          const String& defaultSkin,
100                          const String& defaultRes) throw(WindowException) :
101       Window(true)
102     {
103       XBMC_TRACE;
104       RESOLUTION_INFO res;
105       CStdString strSkinPath = g_SkinInfo->GetSkinPath(xmlFilename, &res);
106
107       if (!XFILE::CFile::Exists(strSkinPath))
108       {
109         CStdString str("none");
110         ADDON::AddonProps props(str, ADDON::ADDON_SKIN, "", "");
111         ADDON::CSkinInfo::TranslateResolution(defaultRes, res);
112
113         // Check for the matching folder for the skin in the fallback skins folder
114         CStdString fallbackPath = URIUtils::AddFileToFolder(scriptPath, "resources");
115         fallbackPath = URIUtils::AddFileToFolder(fallbackPath, "skins");
116         CStdString basePath = URIUtils::AddFileToFolder(fallbackPath, g_SkinInfo->ID());
117
118         strSkinPath = g_SkinInfo->GetSkinPath(xmlFilename, &res, basePath);
119
120         // Check for the matching folder for the skin in the fallback skins folder (if it exists)
121         if (XFILE::CFile::Exists(basePath))
122         {
123           props.path = basePath;
124           ADDON::CSkinInfo skinInfo(props, res);
125           skinInfo.Start();
126           strSkinPath = skinInfo.GetSkinPath(xmlFilename, &res);
127         }
128
129         if (!XFILE::CFile::Exists(strSkinPath))
130         {
131           // Finally fallback to the DefaultSkin as it didn't exist in either the XBMC Skin folder or the fallback skin folder
132           props.path = URIUtils::AddFileToFolder(fallbackPath, defaultSkin);
133           ADDON::CSkinInfo skinInfo(props, res);
134
135           skinInfo.Start();
136           strSkinPath = skinInfo.GetSkinPath(xmlFilename, &res);
137           if (!XFILE::CFile::Exists(strSkinPath))
138             throw WindowException("XML File for Window is missing");
139         }
140       }
141
142       m_scriptPath = scriptPath;
143 //      sXMLFileName = strSkinPath;
144
145       interceptor = new WindowXMLInterceptor(this, lockingGetNextAvailalbeWindowId(),strSkinPath.c_str());
146       setWindow(interceptor);
147       interceptor->SetCoordsRes(res);
148     }
149
150     int WindowXML::lockingGetNextAvailalbeWindowId() throw (WindowException)
151     {
152       XBMC_TRACE;
153       CSingleLock lock(g_graphicsContext);
154       return getNextAvailalbeWindowId();
155     }
156
157     void WindowXML::addItem(const Alternative<String, const ListItem*>& item, int position)
158     {
159       XBMC_TRACE;
160       // item could be deleted if the reference count is 0.
161       //   so I MAY need to check prior to using a Ref just in
162       //   case this object is managed by Python. I'm not sure
163       //   though.
164       AddonClass::Ref<ListItem> ritem = item.which() == XBMCAddon::first ? ListItem::fromString(item.former()) : AddonClass::Ref<ListItem>(item.later());
165
166       // Tells the window to add the item to FileItem vector
167       {
168         LOCKGUI;
169
170         //----------------------------------------------------
171         // Former AddItem call
172         //AddItem(ritem->item, pos);
173         {
174           CFileItemPtr& fileItem = ritem->item;
175           if (position == INT_MAX || position > A(m_vecItems)->Size())
176           {
177             A(m_vecItems)->Add(fileItem);
178           }
179           else if (position <  -1 &&  !(position*-1 < A(m_vecItems)->Size()))
180           {
181             A(m_vecItems)->AddFront(fileItem,0);
182           }
183           else
184           {
185             A(m_vecItems)->AddFront(fileItem,position);
186           }
187           A(m_viewControl).SetItems(*(A(m_vecItems)));
188           A(UpdateButtons());
189         }
190         //----------------------------------------------------
191       }
192     }
193
194     void WindowXML::removeItem(int position)
195     {
196       XBMC_TRACE;
197       // Tells the window to remove the item at the specified position from the FileItem vector
198       LOCKGUI;
199       A(m_vecItems)->Remove(position);
200       A(m_viewControl).SetItems(*(A(m_vecItems)));
201       A(UpdateButtons());
202     }
203
204     int WindowXML::getCurrentListPosition()
205     {
206       XBMC_TRACE;
207       LOCKGUI;
208       int listPos = A(m_viewControl).GetSelectedItem();
209       return listPos;
210     }
211
212     void WindowXML::setCurrentListPosition(int position)
213     {
214       XBMC_TRACE;
215       LOCKGUI;
216       A(m_viewControl).SetSelectedItem(position);
217     }
218
219     ListItem* WindowXML::getListItem(int position) throw (WindowException)
220     {
221       LOCKGUI;
222       //CFileItemPtr fi = pwx->GetListItem(listPos);
223       CFileItemPtr fi;
224       {
225         if (position < 0 || position >= A(m_vecItems)->Size()) 
226           return new ListItem();
227         fi = A(m_vecItems)->Get(position);
228       }
229
230       if (fi == NULL)
231       {
232         XBMCAddonUtils::guiUnlock();
233         throw WindowException("Index out of range (%i)",position);
234       }
235
236       ListItem* sListItem = new ListItem();
237       sListItem->item = fi;
238
239       // let's hope someone reference counts this.
240       return sListItem;
241     }
242
243     int WindowXML::getListSize()
244     {
245       XBMC_TRACE;
246       return A(m_vecItems)->Size();
247     }
248
249     void WindowXML::clearList()
250     {
251       XBMC_TRACE;
252       A(ClearFileItems());
253
254       A(m_viewControl).SetItems(*(A(m_vecItems)));
255       A(UpdateButtons());
256     }
257
258     void WindowXML::setProperty(const String& key, const String& value)
259     {
260       XBMC_TRACE;
261       A(m_vecItems)->SetProperty(key, value);
262     }
263
264     bool WindowXML::OnAction(const CAction &action)
265     {
266       XBMC_TRACE;
267       // do the base class window first, and the call to python after this
268       bool ret = ref(window)->OnAction(action);  // we don't currently want the mediawindow actions here
269                                                  //  look at the WindowXMLInterceptor onAction, it skips
270                                                  //  the CGUIMediaWindow::OnAction and calls directly to
271                                                  //  CGUIWindow::OnAction
272       AddonClass::Ref<Action> inf(new Action(action));
273       invokeCallback(new CallbackFunction<WindowXML,AddonClass::Ref<Action> >(this,&WindowXML::onAction,inf.get()));
274       PulseActionEvent();
275       return ret;
276     }
277
278     bool WindowXML::OnMessage(CGUIMessage& message)
279     {
280 #ifdef ENABLE_XBMC_TRACE_API
281       XBMC_TRACE;
282       CLog::Log(LOGDEBUG,"%sMessage id:%d",_tg.getSpaces(),(int)message.GetMessage());
283 #endif
284
285       // TODO: We shouldn't be dropping down to CGUIWindow in any of this ideally.
286       //       We have to make up our minds about what python should be doing and
287       //       what this side of things should be doing
288       switch (message.GetMessage())
289       {
290       case GUI_MSG_WINDOW_DEINIT:
291         {
292           return ref(window)->OnMessage(message);
293         }
294         break;
295
296       case GUI_MSG_WINDOW_INIT:
297         {
298           ref(window)->OnMessage(message);
299           invokeCallback(new CallbackFunction<WindowXML>(this,&WindowXML::onInit));
300           PulseActionEvent();
301           return true;
302         }
303         break;
304
305       case GUI_MSG_FOCUSED:
306         {
307           if (A(m_viewControl).HasControl(message.GetControlId()) && 
308               A(m_viewControl).GetCurrentControl() != (int)message.GetControlId())
309           {
310             A(m_viewControl).SetFocused();
311             return true;
312           }
313           // check if our focused control is one of our category buttons
314           int iControl=message.GetControlId();
315
316           invokeCallback(new CallbackFunction<WindowXML,int>(this,&WindowXML::onFocus,iControl));
317           PulseActionEvent();
318         }
319         break;
320
321       case GUI_MSG_CLICKED:
322         {
323           int iControl=message.GetSenderId();
324           // Handle Sort/View internally. Scripters shouldn't use ID 2, 3 or 4.
325           if (iControl == CONTROL_BTNSORTASC) // sort asc
326           {
327             CLog::Log(LOGINFO, "WindowXML: Internal asc/dsc button not implemented");
328             /*if (m_guiState.get())
329               m_guiState->SetNextSortOrder();
330               UpdateFileList();*/
331             return true;
332           }
333           else if (iControl == CONTROL_BTNSORTBY) // sort by
334           {
335             CLog::Log(LOGINFO, "WindowXML: Internal sort button not implemented");
336             /*if (m_guiState.get())
337               m_guiState->SetNextSortMethod();
338               UpdateFileList();*/
339             return true;
340           }
341
342           if(iControl && iControl != (int)interceptor->GetID()) // pCallbackWindow &&  != this->GetID())
343           {
344             CGUIControl* controlClicked = (CGUIControl*)interceptor->GetControl(iControl);
345
346             // The old python way used to check list AND SELECITEM method 
347             //   or if its a button, checkmark.
348             // Its done this way for now to allow other controls without a 
349             //  python version like togglebutton to still raise a onAction event
350             if (controlClicked) // Will get problems if we the id is not on the window 
351                                 //   and we try to do GetControlType on it. So check to make sure it exists
352             {
353               if ((controlClicked->IsContainer() && (message.GetParam1() == ACTION_SELECT_ITEM || message.GetParam1() == ACTION_MOUSE_LEFT_CLICK)) || !controlClicked->IsContainer())
354               {
355                 invokeCallback(new CallbackFunction<WindowXML,int>(this,&WindowXML::onClick,iControl));
356                 PulseActionEvent();
357                 return true;
358               }
359               else if (controlClicked->IsContainer() && message.GetParam1() == ACTION_MOUSE_DOUBLE_CLICK)
360               {
361                 invokeCallback(new CallbackFunction<WindowXML,int>(this,&WindowXML::onDoubleClick,iControl));
362                 PulseActionEvent();
363                 return true;
364               }
365               else if (controlClicked->IsContainer() && message.GetParam1() == ACTION_MOUSE_RIGHT_CLICK)
366               {
367                 AddonClass::Ref<Action> inf(new Action(CAction(ACTION_CONTEXT_MENU)));
368                 invokeCallback(new CallbackFunction<WindowXML,AddonClass::Ref<Action> >(this,&WindowXML::onAction,inf.get()));
369                 PulseActionEvent();
370                 return true;
371               }
372             }
373           }
374         }
375         break;
376       }
377
378       return A(CGUIMediaWindow::OnMessage(message));
379     }
380
381     void WindowXML::AllocResources(bool forceLoad /*= FALSE */)
382     {
383       XBMC_TRACE;
384       CStdString tmpDir = URIUtils::GetDirectory(ref(window)->GetProperty("xmlfile").asString());
385       CStdString fallbackMediaPath;
386       URIUtils::GetParentPath(tmpDir, fallbackMediaPath);
387       URIUtils::RemoveSlashAtEnd(fallbackMediaPath);
388       m_mediaDir = fallbackMediaPath;
389
390       //CLog::Log(LOGDEBUG, "CGUIPythonWindowXML::AllocResources called: %s", fallbackMediaPath.c_str());
391       g_TextureManager.AddTexturePath(m_mediaDir);
392       ref(window)->AllocResources(forceLoad);
393       g_TextureManager.RemoveTexturePath(m_mediaDir);
394     }
395
396     void WindowXML::FreeResources(bool forceUnLoad /*= FALSE */)
397     {
398       XBMC_TRACE;
399
400       ref(window)->FreeResources(forceUnLoad);
401     }
402
403     void WindowXML::Process(unsigned int currentTime, CDirtyRegionList &regions)
404     {
405       XBMC_TRACE;
406       g_TextureManager.AddTexturePath(m_mediaDir);
407       ref(window)->Process(currentTime, regions);
408       g_TextureManager.RemoveTexturePath(m_mediaDir);
409     }
410
411     bool WindowXML::OnClick(int iItem) 
412     {
413       XBMC_TRACE;
414       // Hook Over calling  CGUIMediaWindow::OnClick(iItem) results in it trying to PLAY the file item
415       // which if its not media is BAD and 99 out of 100 times undesireable.
416       return false;
417     }
418
419     bool WindowXML::OnDoubleClick(int iItem)
420     {
421       XBMC_TRACE;
422       return false;
423     }
424
425     void WindowXML::GetContextButtons(int itemNumber, CContextButtons &buttons)
426     {
427       XBMC_TRACE;
428       // maybe on day we can make an easy way to do this context menu
429       // with out this method overriding the MediaWindow version, it will display 'Add to Favorites'
430     }
431
432     bool WindowXML::LoadXML(const String &strPath, const String &strLowerPath)
433     {
434       XBMC_TRACE;
435       // load our window
436       XFILE::CFile file;
437       std::string strPathLower = strPath;
438       StringUtils::ToLower(strPathLower);
439       if (!file.Open(strPath) && !file.Open(strPathLower) && !file.Open(strLowerPath))
440       {
441         // fail - can't load the file
442         CLog::Log(LOGERROR, "%s: Unable to load skin file %s", __FUNCTION__, strPath.c_str());
443         return false;
444       }
445
446       CStdString xml;
447       char *buffer = new char[(unsigned int)file.GetLength()+1];
448       if(buffer == NULL)
449         return false;
450       int size = file.Read(buffer, file.GetLength());
451       if (size > 0)
452       {
453         buffer[size] = 0;
454         xml = buffer;
455       }
456       delete[] buffer;
457
458       CXBMCTinyXML xmlDoc;
459       xmlDoc.Parse(xml);
460
461       if (xmlDoc.Error())
462         return false;
463
464       return interceptor->Load(xmlDoc.RootElement());
465     }
466
467     void WindowXML::SetupShares()
468     {
469       XBMC_TRACE;
470       A(UpdateButtons());
471     }
472
473     bool WindowXML::Update(const String &strPath)
474     {
475       XBMC_TRACE;
476       return true;
477     }
478
479     WindowXMLDialog::WindowXMLDialog(const String& xmlFilename, const String& scriptPath,
480                                      const String& defaultSkin,
481                                      const String& defaultRes) throw(WindowException) :
482       WindowXML(xmlFilename, scriptPath, defaultSkin, defaultRes),
483       WindowDialogMixin(this)
484     { XBMC_TRACE; }
485
486     WindowXMLDialog::~WindowXMLDialog() { XBMC_TRACE; deallocating(); }
487
488     bool WindowXMLDialog::OnMessage(CGUIMessage &message)
489     {
490       XBMC_TRACE;
491       if (message.GetMessage() == GUI_MSG_WINDOW_DEINIT)
492       {
493         CGUIWindow *pWindow = g_windowManager.GetWindow(g_windowManager.GetActiveWindow());
494         if (pWindow)
495           g_windowManager.ShowOverlay(pWindow->GetOverlayState());
496         return A(CGUIWindow::OnMessage(message));
497       }
498       return WindowXML::OnMessage(message);
499     }
500
501     bool WindowXMLDialog::OnAction(const CAction &action)
502     {
503       XBMC_TRACE;
504       return WindowDialogMixin::OnAction(action) ? true : WindowXML::OnAction(action);
505     }
506     
507     void WindowXMLDialog::OnDeinitWindow(int nextWindowID)
508     {
509       XBMC_TRACE;
510       g_windowManager.RemoveDialog(interceptor->GetID());
511       WindowXML::OnDeinitWindow(nextWindowID);
512     }
513   
514   }
515
516 }