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