2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
11 #include "ServiceBroker.h"
12 #include "WindowException.h"
13 #include "WindowInterceptor.h"
14 #include "addons/Addon.h"
15 #include "addons/Skin.h"
16 #include "addons/addoninfo/AddonInfo.h"
17 #include "addons/addoninfo/AddonType.h"
18 #include "guilib/GUIComponent.h"
19 #include "guilib/GUIWindowManager.h"
20 #include "guilib/TextureManager.h"
21 #include "utils/FileUtils.h"
22 #include "utils/StringUtils.h"
23 #include "utils/URIUtils.h"
27 // These #defs are for WindowXML
28 #define CONTROL_BTNVIEWASICONS 2
29 #define CONTROL_BTNSORTBY 3
30 #define CONTROL_BTNSORTASC 4
31 #define CONTROL_LABELFILES 12
33 #define A(x) interceptor->x
39 template class Interceptor
<CGUIMediaWindow
>;
42 * This class extends the Interceptor<CGUIMediaWindow> in order to
43 * add behavior for a few more virtual functions that were unnecessary
44 * in the Window or WindowDialog.
46 #define checkedb(methcall) ( window.isNotNull() ? xwin-> methcall : false )
47 #define checkedv(methcall) { if (window.isNotNull()) xwin-> methcall ; }
50 //! @todo This should be done with template specialization
51 class WindowXMLInterceptor
: public InterceptorDialog
<CGUIMediaWindow
>
55 WindowXMLInterceptor(WindowXML
* _window
, int windowid
,const char* xmlfile
) :
56 InterceptorDialog
<CGUIMediaWindow
>("CGUIMediaWindow",_window
,windowid
,xmlfile
), xwin(_window
)
59 void AllocResources(bool forceLoad
= false) override
60 { XBMC_TRACE
; if(up()) CGUIMediaWindow::AllocResources(forceLoad
); else checkedv(AllocResources(forceLoad
)); }
61 void FreeResources(bool forceUnLoad
= false) override
62 { XBMC_TRACE
; if(up()) CGUIMediaWindow::FreeResources(forceUnLoad
); else checkedv(FreeResources(forceUnLoad
)); }
63 bool OnClick(int iItem
, const std::string
&player
= "") override
{ XBMC_TRACE
; return up() ? CGUIMediaWindow::OnClick(iItem
, player
) : checkedb(OnClick(iItem
)); }
65 void Process(unsigned int currentTime
, CDirtyRegionList
&dirtyregions
) override
66 { XBMC_TRACE
; if(up()) CGUIMediaWindow::Process(currentTime
,dirtyregions
); else checkedv(Process(currentTime
,dirtyregions
)); }
68 // this is a hack to SKIP the CGUIMediaWindow
69 bool OnAction(const CAction
&action
) override
70 { XBMC_TRACE
; return up() ? CGUIWindow::OnAction(action
) : checkedb(OnAction(action
)); }
74 bool LoadXML(const std::string
&strPath
, const std::string
&strPathLower
) override
75 { XBMC_TRACE
; return up() ? CGUIMediaWindow::LoadXML(strPath
,strPathLower
) : xwin
->LoadXML(strPath
,strPathLower
); }
78 void GetContextButtons(int itemNumber
, CContextButtons
&buttons
) override
79 { XBMC_TRACE
; if (up()) CGUIMediaWindow::GetContextButtons(itemNumber
,buttons
); else xwin
->GetContextButtons(itemNumber
,buttons
); }
80 bool Update(const std::string
&strPath
, bool) override
81 { XBMC_TRACE
; return up() ? CGUIMediaWindow::Update(strPath
) : xwin
->Update(strPath
); }
82 void SetupShares() override
{ XBMC_TRACE
; if(up()) CGUIMediaWindow::SetupShares(); else checkedv(SetupShares()); }
84 friend class WindowXML
;
85 friend class WindowXMLDialog
;
89 WindowXML::~WindowXML() { XBMC_TRACE
; deallocating(); }
91 WindowXML::WindowXML(const String
& xmlFilename
,
92 const String
& scriptPath
,
93 const String
& defaultSkin
,
94 const String
& defaultRes
,
100 std::string strSkinPath
= g_SkinInfo
->GetSkinPath(xmlFilename
, &res
);
103 if (!CFileUtils::Exists(strSkinPath
))
105 std::string
str("none");
106 ADDON::AddonInfoPtr addonInfo
=
107 std::make_shared
<ADDON::CAddonInfo
>(str
, ADDON::AddonType::SKIN
);
108 ADDON::CSkinInfo::TranslateResolution(defaultRes
, res
);
110 // Check for the matching folder for the skin in the fallback skins folder
111 std::string fallbackPath
= URIUtils::AddFileToFolder(scriptPath
, "resources", "skins");
112 std::string basePath
= URIUtils::AddFileToFolder(fallbackPath
, g_SkinInfo
->ID());
114 strSkinPath
= g_SkinInfo
->GetSkinPath(xmlFilename
, &res
, basePath
);
116 // Check for the matching folder for the skin in the fallback skins folder (if it exists)
117 if (CFileUtils::Exists(basePath
))
119 addonInfo
->SetPath(basePath
);
120 std::shared_ptr
<ADDON::CSkinInfo
> skinInfo
= std::make_shared
<ADDON::CSkinInfo
>(addonInfo
, res
);
122 strSkinPath
= skinInfo
->GetSkinPath(xmlFilename
, &res
);
125 if (!CFileUtils::Exists(strSkinPath
))
127 // Finally fallback to the DefaultSkin as it didn't exist in either the XBMC Skin folder or the fallback skin folder
128 addonInfo
->SetPath(URIUtils::AddFileToFolder(fallbackPath
, defaultSkin
));
129 std::shared_ptr
<ADDON::CSkinInfo
> skinInfo
= std::make_shared
<ADDON::CSkinInfo
>(addonInfo
, res
);
132 strSkinPath
= skinInfo
->GetSkinPath(xmlFilename
, &res
);
133 if (!CFileUtils::Exists(strSkinPath
))
134 throw WindowException("XML File for Window is missing");
138 m_scriptPath
= scriptPath
;
139 // sXMLFileName = strSkinPath;
141 interceptor
= new WindowXMLInterceptor(this, lockingGetNextAvailableWindowId(),strSkinPath
.c_str());
142 setWindow(interceptor
);
143 interceptor
->SetCoordsRes(res
);
146 int WindowXML::lockingGetNextAvailableWindowId()
149 std::unique_lock
<CCriticalSection
> lock(CServiceBroker::GetWinSystem()->GetGfxContext());
150 return getNextAvailableWindowId();
153 void WindowXML::addItem(const Alternative
<String
, const ListItem
*>& item
, int position
)
156 // item could be deleted if the reference count is 0.
157 // so I MAY need to check prior to using a Ref just in
158 // case this object is managed by Python. I'm not sure
160 AddonClass::Ref
<ListItem
> ritem
= item
.which() == XBMCAddon::first
? ListItem::fromString(item
.former()) : AddonClass::Ref
<ListItem
>(item
.later());
162 // Tells the window to add the item to FileItem vector
164 XBMCAddonUtils::GuiLock
lock(languageHook
, false);
166 //----------------------------------------------------
167 // Former AddItem call
168 //AddItem(ritem->item, pos);
170 CFileItemPtr
& fileItem
= ritem
->item
;
171 if (position
== INT_MAX
|| position
> A(m_vecItems
)->Size())
173 A(m_vecItems
)->Add(fileItem
);
175 else if (position
< -1 && !(position
*-1 < A(m_vecItems
)->Size()))
177 A(m_vecItems
)->AddFront(fileItem
,0);
181 A(m_vecItems
)->AddFront(fileItem
,position
);
183 A(m_viewControl
).SetItems(*(A(m_vecItems
)));
185 //----------------------------------------------------
189 void WindowXML::addItems(const std::vector
<Alternative
<String
, const XBMCAddon::xbmcgui::ListItem
* > > & items
)
192 XBMCAddonUtils::GuiLock
lock(languageHook
, false);
193 for (auto item
: items
)
195 AddonClass::Ref
<ListItem
> ritem
= item
.which() == XBMCAddon::first
? ListItem::fromString(item
.former()) : AddonClass::Ref
<ListItem
>(item
.later());
196 CFileItemPtr
& fileItem
= ritem
->item
;
197 A(m_vecItems
)->Add(fileItem
);
199 A(m_viewControl
).SetItems(*(A(m_vecItems
)));
203 void WindowXML::removeItem(int position
)
206 // Tells the window to remove the item at the specified position from the FileItem vector
207 XBMCAddonUtils::GuiLock
lock(languageHook
, false);
208 A(m_vecItems
)->Remove(position
);
209 A(m_viewControl
).SetItems(*(A(m_vecItems
)));
212 int WindowXML::getCurrentListPosition()
215 XBMCAddonUtils::GuiLock
lock(languageHook
, false);
216 int listPos
= A(m_viewControl
).GetSelectedItem();
220 void WindowXML::setCurrentListPosition(int position
)
223 XBMCAddonUtils::GuiLock
lock(languageHook
, false);
224 A(m_viewControl
).SetSelectedItem(position
);
227 ListItem
* WindowXML::getListItem(int position
)
229 XBMCAddonUtils::GuiLock
lock(languageHook
, false);
230 //CFileItemPtr fi = pwx->GetListItem(listPos);
233 if (position
< 0 || position
>= A(m_vecItems
)->Size())
234 return new ListItem();
235 fi
= A(m_vecItems
)->Get(position
);
240 throw WindowException("Index out of range (%i)",position
);
243 ListItem
* sListItem
= new ListItem();
244 sListItem
->item
= fi
;
246 // let's hope someone reference counts this.
250 int WindowXML::getListSize()
253 return A(m_vecItems
)->Size();
256 void WindowXML::clearList()
259 XBMCAddonUtils::GuiLock
lock(languageHook
, false);
262 A(m_viewControl
).SetItems(*(A(m_vecItems
)));
265 void WindowXML::setContainerProperty(const String
& key
, const String
& value
)
268 A(m_vecItems
)->SetProperty(key
, value
);
271 void WindowXML::setContent(const String
& value
)
274 XBMCAddonUtils::GuiLock
lock(languageHook
, false);
275 A(m_vecItems
)->SetContent(value
);
278 int WindowXML::getCurrentContainerId()
281 XBMCAddonUtils::GuiLock
lock(languageHook
, false);
282 return A(m_viewControl
.GetCurrentControl());
285 bool WindowXML::OnAction(const CAction
&action
)
288 // do the base class window first, and the call to python after this
289 bool ret
= ref(window
)->OnAction(action
); // we don't currently want the mediawindow actions here
290 // look at the WindowXMLInterceptor onAction, it skips
291 // the CGUIMediaWindow::OnAction and calls directly to
292 // CGUIWindow::OnAction
293 AddonClass::Ref
<Action
> inf(new Action(action
));
294 invokeCallback(new CallbackFunction
<WindowXML
,AddonClass::Ref
<Action
> >(this,&WindowXML::onAction
,inf
.get()));
299 bool WindowXML::OnMessage(CGUIMessage
& message
)
301 #ifdef ENABLE_XBMC_TRACE_API
303 CLog::Log(LOGDEBUG
, "{}Message id:{}", _tg
.getSpaces(), (int)message
.GetMessage());
306 //! @todo We shouldn't be dropping down to CGUIWindow in any of this ideally.
307 //! We have to make up our minds about what python should be doing and
308 //! what this side of things should be doing
309 switch (message
.GetMessage())
311 case GUI_MSG_WINDOW_DEINIT
:
313 return ref(window
)->OnMessage(message
);
317 case GUI_MSG_WINDOW_INIT
:
319 ref(window
)->OnMessage(message
);
320 invokeCallback(new CallbackFunction
<WindowXML
>(this,&WindowXML::onInit
));
326 case GUI_MSG_FOCUSED
:
328 if (A(m_viewControl
).HasControl(message
.GetControlId()) &&
329 A(m_viewControl
).GetCurrentControl() != message
.GetControlId())
331 A(m_viewControl
).SetFocused();
334 // check if our focused control is one of our category buttons
335 int iControl
=message
.GetControlId();
337 invokeCallback(new CallbackFunction
<WindowXML
,int>(this,&WindowXML::onFocus
,iControl
));
342 case GUI_MSG_NOTIFY_ALL
:
343 // most messages from GUI_MSG_NOTIFY_ALL break container content, whitelist working ones.
344 if (message
.GetParam1() == GUI_MSG_PAGE_CHANGE
|| message
.GetParam1() == GUI_MSG_WINDOW_RESIZE
)
345 return A(CGUIMediaWindow::OnMessage(message
));
348 case GUI_MSG_CLICKED
:
350 int iControl
=message
.GetSenderId();
351 // Handle Sort/View internally. Scripters shouldn't use ID 2, 3 or 4.
352 if (iControl
== CONTROL_BTNSORTASC
) // sort asc
354 CLog::Log(LOGINFO
, "WindowXML: Internal asc/dsc button not implemented");
355 /*if (m_guiState.get())
356 m_guiState->SetNextSortOrder();
360 else if (iControl
== CONTROL_BTNSORTBY
) // sort by
362 CLog::Log(LOGINFO
, "WindowXML: Internal sort button not implemented");
363 /*if (m_guiState.get())
364 m_guiState->SetNextSortMethod();
369 if(iControl
&& iControl
!= interceptor
->GetID()) // pCallbackWindow && != this->GetID())
371 CGUIControl
* controlClicked
= interceptor
->GetControl(iControl
);
373 // The old python way used to check list AND SELECITEM method
374 // or if its a button, radiobutton.
375 // Its done this way for now to allow other controls without a
376 // python version like togglebutton to still raise a onAction event
377 if (controlClicked
) // Will get problems if we the id is not on the window
378 // and we try to do GetControlType on it. So check to make sure it exists
380 if ((controlClicked
->IsContainer() && (message
.GetParam1() == ACTION_SELECT_ITEM
|| message
.GetParam1() == ACTION_MOUSE_LEFT_CLICK
)) || !controlClicked
->IsContainer())
382 invokeCallback(new CallbackFunction
<WindowXML
,int>(this,&WindowXML::onClick
,iControl
));
386 else if (controlClicked
->IsContainer() && message
.GetParam1() == ACTION_MOUSE_DOUBLE_CLICK
)
388 invokeCallback(new CallbackFunction
<WindowXML
,int>(this,&WindowXML::onDoubleClick
,iControl
));
392 else if (controlClicked
->IsContainer() && message
.GetParam1() == ACTION_MOUSE_RIGHT_CLICK
)
394 AddonClass::Ref
<Action
> inf(new Action(CAction(ACTION_CONTEXT_MENU
)));
395 invokeCallback(new CallbackFunction
<WindowXML
,AddonClass::Ref
<Action
> >(this,&WindowXML::onAction
,inf
.get()));
399 // the core context menu can lead to all sort of issues right now when used with WindowXMLs, so lets intercept the corresponding message
400 else if (controlClicked
->IsContainer() && message
.GetParam1() == ACTION_CONTEXT_MENU
)
408 return A(CGUIMediaWindow::OnMessage(message
));
411 void WindowXML::AllocResources(bool forceLoad
/*= false */)
414 std::string tmpDir
= URIUtils::GetDirectory(ref(window
)->GetProperty("xmlfile").asString());
415 std::string fallbackMediaPath
;
416 URIUtils::GetParentPath(tmpDir
, fallbackMediaPath
);
417 URIUtils::RemoveSlashAtEnd(fallbackMediaPath
);
418 m_mediaDir
= fallbackMediaPath
;
420 //CLog::Log(LOGDEBUG, "CGUIPythonWindowXML::AllocResources called: {}", fallbackMediaPath);
421 CServiceBroker::GetGUI()->GetTextureManager().AddTexturePath(m_mediaDir
);
422 ref(window
)->AllocResources(forceLoad
);
423 CServiceBroker::GetGUI()->GetTextureManager().RemoveTexturePath(m_mediaDir
);
426 void WindowXML::FreeResources(bool forceUnLoad
/*= false */)
430 ref(window
)->FreeResources(forceUnLoad
);
433 void WindowXML::Process(unsigned int currentTime
, CDirtyRegionList
®ions
)
436 CServiceBroker::GetGUI()->GetTextureManager().AddTexturePath(m_mediaDir
);
437 ref(window
)->Process(currentTime
, regions
);
438 CServiceBroker::GetGUI()->GetTextureManager().RemoveTexturePath(m_mediaDir
);
441 bool WindowXML::OnClick(int iItem
)
444 // Hook Over calling CGUIMediaWindow::OnClick(iItem) results in it trying to PLAY the file item
445 // which if its not media is BAD and 99 out of 100 times undesirable.
449 bool WindowXML::OnDoubleClick(int iItem
)
455 void WindowXML::GetContextButtons(int itemNumber
, CContextButtons
&buttons
)
458 // maybe on day we can make an easy way to do this context menu
459 // with out this method overriding the MediaWindow version, it will display 'Add to Favorites'
462 bool WindowXML::LoadXML(const String
&strPath
, const String
&strLowerPath
)
465 return A(CGUIWindow::LoadXML(strPath
, strLowerPath
));
468 void WindowXML::SetupShares()
473 bool WindowXML::Update(const String
&strPath
)
479 WindowXMLDialog::WindowXMLDialog(const String
& xmlFilename
, const String
& scriptPath
,
480 const String
& defaultSkin
,
481 const String
& defaultRes
) :
482 WindowXML(xmlFilename
, scriptPath
, defaultSkin
, defaultRes
),
483 WindowDialogMixin(this)
486 WindowXMLDialog::~WindowXMLDialog() { XBMC_TRACE
; deallocating(); }
488 bool WindowXMLDialog::OnMessage(CGUIMessage
&message
)
491 if (message
.GetMessage() == GUI_MSG_WINDOW_DEINIT
)
492 return A(CGUIWindow::OnMessage(message
));
494 return WindowXML::OnMessage(message
);
497 bool WindowXMLDialog::OnAction(const CAction
&action
)
500 return WindowDialogMixin::OnAction(action
) ? true : WindowXML::OnAction(action
);
503 void WindowXMLDialog::OnDeinitWindow(int nextWindowID
)
506 CServiceBroker::GetGUI()->GetWindowManager().RemoveDialog(interceptor
->GetID());
507 WindowXML::OnDeinitWindow(nextWindowID
);
510 bool WindowXMLDialog::LoadXML(const String
&strPath
, const String
&strLowerPath
)
513 if (WindowXML::LoadXML(strPath
, strLowerPath
))
515 // Set the render order to the dialog's default in case it's not specified in the skin xml
516 // because this dialog is mapped to CGUIMediaWindow instead of CGUIDialog.
517 // This must be done here, because the render order will be reset before loading the skin xml.
518 if (ref(window
)->GetRenderOrder() == RENDER_ORDER_WINDOW
)
519 window
->SetRenderOrder(RENDER_ORDER_DIALOG
);