diff options
Diffstat (limited to 'xbmc/interfaces/legacy/WindowXML.cpp')
-rw-r--r-- | xbmc/interfaces/legacy/WindowXML.cpp | 527 |
1 files changed, 527 insertions, 0 deletions
diff --git a/xbmc/interfaces/legacy/WindowXML.cpp b/xbmc/interfaces/legacy/WindowXML.cpp new file mode 100644 index 0000000..51a4fbf --- /dev/null +++ b/xbmc/interfaces/legacy/WindowXML.cpp @@ -0,0 +1,527 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "WindowXML.h" + +#include "ServiceBroker.h" +#include "WindowException.h" +#include "WindowInterceptor.h" +#include "addons/Addon.h" +#include "addons/Skin.h" +#include "addons/addoninfo/AddonInfo.h" +#include "addons/addoninfo/AddonType.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/TextureManager.h" +#include "utils/FileUtils.h" +#include "utils/StringUtils.h" +#include "utils/URIUtils.h" + +#include <mutex> + +// These #defs are for WindowXML +#define CONTROL_BTNVIEWASICONS 2 +#define CONTROL_BTNSORTBY 3 +#define CONTROL_BTNSORTASC 4 +#define CONTROL_LABELFILES 12 + +#define A(x) interceptor->x + +namespace XBMCAddon +{ + namespace xbmcgui + { + template class Interceptor<CGUIMediaWindow>; + + /** + * This class extends the Interceptor<CGUIMediaWindow> in order to + * add behavior for a few more virtual functions that were unnecessary + * in the Window or WindowDialog. + */ +#define checkedb(methcall) ( window.isNotNull() ? xwin-> methcall : false ) +#define checkedv(methcall) { if (window.isNotNull()) xwin-> methcall ; } + + + //! @todo This should be done with template specialization + class WindowXMLInterceptor : public InterceptorDialog<CGUIMediaWindow> + { + WindowXML* xwin; + public: + WindowXMLInterceptor(WindowXML* _window, int windowid,const char* xmlfile) : + InterceptorDialog<CGUIMediaWindow>("CGUIMediaWindow",_window,windowid,xmlfile), xwin(_window) + { } + + void AllocResources(bool forceLoad = false) override + { XBMC_TRACE; if(up()) CGUIMediaWindow::AllocResources(forceLoad); else checkedv(AllocResources(forceLoad)); } + void FreeResources(bool forceUnLoad = false) override + { XBMC_TRACE; if(up()) CGUIMediaWindow::FreeResources(forceUnLoad); else checkedv(FreeResources(forceUnLoad)); } + bool OnClick(int iItem, const std::string &player = "") override { XBMC_TRACE; return up() ? CGUIMediaWindow::OnClick(iItem, player) : checkedb(OnClick(iItem)); } + + void Process(unsigned int currentTime, CDirtyRegionList &dirtyregions) override + { XBMC_TRACE; if(up()) CGUIMediaWindow::Process(currentTime,dirtyregions); else checkedv(Process(currentTime,dirtyregions)); } + + // this is a hack to SKIP the CGUIMediaWindow + bool OnAction(const CAction &action) override + { XBMC_TRACE; return up() ? CGUIWindow::OnAction(action) : checkedb(OnAction(action)); } + + protected: + // CGUIWindow + bool LoadXML(const std::string &strPath, const std::string &strPathLower) override + { XBMC_TRACE; return up() ? CGUIMediaWindow::LoadXML(strPath,strPathLower) : xwin->LoadXML(strPath,strPathLower); } + + // CGUIMediaWindow + void GetContextButtons(int itemNumber, CContextButtons &buttons) override + { XBMC_TRACE; if (up()) CGUIMediaWindow::GetContextButtons(itemNumber,buttons); else xwin->GetContextButtons(itemNumber,buttons); } + bool Update(const std::string &strPath, bool) override + { XBMC_TRACE; return up() ? CGUIMediaWindow::Update(strPath) : xwin->Update(strPath); } + void SetupShares() override { XBMC_TRACE; if(up()) CGUIMediaWindow::SetupShares(); else checkedv(SetupShares()); } + + friend class WindowXML; + friend class WindowXMLDialog; + + }; + + WindowXML::~WindowXML() { XBMC_TRACE; deallocating(); } + + WindowXML::WindowXML(const String& xmlFilename, + const String& scriptPath, + const String& defaultSkin, + const String& defaultRes, + bool isMedia) : + Window(true) + { + XBMC_TRACE; + RESOLUTION_INFO res; + std::string strSkinPath = g_SkinInfo->GetSkinPath(xmlFilename, &res); + m_isMedia = isMedia; + + if (!CFileUtils::Exists(strSkinPath)) + { + std::string str("none"); + ADDON::AddonInfoPtr addonInfo = + std::make_shared<ADDON::CAddonInfo>(str, ADDON::AddonType::SKIN); + ADDON::CSkinInfo::TranslateResolution(defaultRes, res); + + // Check for the matching folder for the skin in the fallback skins folder + std::string fallbackPath = URIUtils::AddFileToFolder(scriptPath, "resources", "skins"); + std::string basePath = URIUtils::AddFileToFolder(fallbackPath, g_SkinInfo->ID()); + + strSkinPath = g_SkinInfo->GetSkinPath(xmlFilename, &res, basePath); + + // Check for the matching folder for the skin in the fallback skins folder (if it exists) + if (CFileUtils::Exists(basePath)) + { + addonInfo->SetPath(basePath); + std::shared_ptr<ADDON::CSkinInfo> skinInfo = std::make_shared<ADDON::CSkinInfo>(addonInfo, res); + skinInfo->Start(); + strSkinPath = skinInfo->GetSkinPath(xmlFilename, &res); + } + + if (!CFileUtils::Exists(strSkinPath)) + { + // Finally fallback to the DefaultSkin as it didn't exist in either the XBMC Skin folder or the fallback skin folder + addonInfo->SetPath(URIUtils::AddFileToFolder(fallbackPath, defaultSkin)); + std::shared_ptr<ADDON::CSkinInfo> skinInfo = std::make_shared<ADDON::CSkinInfo>(addonInfo, res); + + skinInfo->Start(); + strSkinPath = skinInfo->GetSkinPath(xmlFilename, &res); + if (!CFileUtils::Exists(strSkinPath)) + throw WindowException("XML File for Window is missing"); + } + } + + m_scriptPath = scriptPath; +// sXMLFileName = strSkinPath; + + interceptor = new WindowXMLInterceptor(this, lockingGetNextAvailableWindowId(),strSkinPath.c_str()); + setWindow(interceptor); + interceptor->SetCoordsRes(res); + } + + int WindowXML::lockingGetNextAvailableWindowId() + { + XBMC_TRACE; + std::unique_lock<CCriticalSection> lock(CServiceBroker::GetWinSystem()->GetGfxContext()); + return getNextAvailableWindowId(); + } + + void WindowXML::addItem(const Alternative<String, const ListItem*>& item, int position) + { + XBMC_TRACE; + // item could be deleted if the reference count is 0. + // so I MAY need to check prior to using a Ref just in + // case this object is managed by Python. I'm not sure + // though. + AddonClass::Ref<ListItem> ritem = item.which() == XBMCAddon::first ? ListItem::fromString(item.former()) : AddonClass::Ref<ListItem>(item.later()); + + // Tells the window to add the item to FileItem vector + { + XBMCAddonUtils::GuiLock lock(languageHook, false); + + //---------------------------------------------------- + // Former AddItem call + //AddItem(ritem->item, pos); + { + CFileItemPtr& fileItem = ritem->item; + if (position == INT_MAX || position > A(m_vecItems)->Size()) + { + A(m_vecItems)->Add(fileItem); + } + else if (position < -1 && !(position*-1 < A(m_vecItems)->Size())) + { + A(m_vecItems)->AddFront(fileItem,0); + } + else + { + A(m_vecItems)->AddFront(fileItem,position); + } + A(m_viewControl).SetItems(*(A(m_vecItems))); + } + //---------------------------------------------------- + } + } + + void WindowXML::addItems(const std::vector<Alternative<String, const XBMCAddon::xbmcgui::ListItem* > > & items) + { + XBMC_TRACE; + XBMCAddonUtils::GuiLock lock(languageHook, false); + for (auto item : items) + { + AddonClass::Ref<ListItem> ritem = item.which() == XBMCAddon::first ? ListItem::fromString(item.former()) : AddonClass::Ref<ListItem>(item.later()); + CFileItemPtr& fileItem = ritem->item; + A(m_vecItems)->Add(fileItem); + } + A(m_viewControl).SetItems(*(A(m_vecItems))); + } + + + void WindowXML::removeItem(int position) + { + XBMC_TRACE; + // Tells the window to remove the item at the specified position from the FileItem vector + XBMCAddonUtils::GuiLock lock(languageHook, false); + A(m_vecItems)->Remove(position); + A(m_viewControl).SetItems(*(A(m_vecItems))); + } + + int WindowXML::getCurrentListPosition() + { + XBMC_TRACE; + XBMCAddonUtils::GuiLock lock(languageHook, false); + int listPos = A(m_viewControl).GetSelectedItem(); + return listPos; + } + + void WindowXML::setCurrentListPosition(int position) + { + XBMC_TRACE; + XBMCAddonUtils::GuiLock lock(languageHook, false); + A(m_viewControl).SetSelectedItem(position); + } + + ListItem* WindowXML::getListItem(int position) + { + XBMCAddonUtils::GuiLock lock(languageHook, false); + //CFileItemPtr fi = pwx->GetListItem(listPos); + CFileItemPtr fi; + { + if (position < 0 || position >= A(m_vecItems)->Size()) + return new ListItem(); + fi = A(m_vecItems)->Get(position); + } + + if (fi == NULL) + { + throw WindowException("Index out of range (%i)",position); + } + + ListItem* sListItem = new ListItem(); + sListItem->item = fi; + + // let's hope someone reference counts this. + return sListItem; + } + + int WindowXML::getListSize() + { + XBMC_TRACE; + return A(m_vecItems)->Size(); + } + + void WindowXML::clearList() + { + XBMC_TRACE; + XBMCAddonUtils::GuiLock lock(languageHook, false); + A(ClearFileItems()); + + A(m_viewControl).SetItems(*(A(m_vecItems))); + } + + void WindowXML::setContainerProperty(const String& key, const String& value) + { + XBMC_TRACE; + A(m_vecItems)->SetProperty(key, value); + } + + void WindowXML::setContent(const String& value) + { + XBMC_TRACE; + XBMCAddonUtils::GuiLock lock(languageHook, false); + A(m_vecItems)->SetContent(value); + } + + int WindowXML::getCurrentContainerId() + { + XBMC_TRACE; + XBMCAddonUtils::GuiLock lock(languageHook, false); + return A(m_viewControl.GetCurrentControl()); + } + + bool WindowXML::OnAction(const CAction &action) + { + XBMC_TRACE; + // do the base class window first, and the call to python after this + bool ret = ref(window)->OnAction(action); // we don't currently want the mediawindow actions here + // look at the WindowXMLInterceptor onAction, it skips + // the CGUIMediaWindow::OnAction and calls directly to + // CGUIWindow::OnAction + AddonClass::Ref<Action> inf(new Action(action)); + invokeCallback(new CallbackFunction<WindowXML,AddonClass::Ref<Action> >(this,&WindowXML::onAction,inf.get())); + PulseActionEvent(); + return ret; + } + + bool WindowXML::OnMessage(CGUIMessage& message) + { +#ifdef ENABLE_XBMC_TRACE_API + XBMC_TRACE; + CLog::Log(LOGDEBUG, "{}Message id:{}", _tg.getSpaces(), (int)message.GetMessage()); +#endif + + //! @todo We shouldn't be dropping down to CGUIWindow in any of this ideally. + //! We have to make up our minds about what python should be doing and + //! what this side of things should be doing + switch (message.GetMessage()) + { + case GUI_MSG_WINDOW_DEINIT: + { + return ref(window)->OnMessage(message); + } + break; + + case GUI_MSG_WINDOW_INIT: + { + ref(window)->OnMessage(message); + invokeCallback(new CallbackFunction<WindowXML>(this,&WindowXML::onInit)); + PulseActionEvent(); + return true; + } + break; + + case GUI_MSG_FOCUSED: + { + if (A(m_viewControl).HasControl(message.GetControlId()) && + A(m_viewControl).GetCurrentControl() != message.GetControlId()) + { + A(m_viewControl).SetFocused(); + return true; + } + // check if our focused control is one of our category buttons + int iControl=message.GetControlId(); + + invokeCallback(new CallbackFunction<WindowXML,int>(this,&WindowXML::onFocus,iControl)); + PulseActionEvent(); + } + break; + + case GUI_MSG_NOTIFY_ALL: + // most messages from GUI_MSG_NOTIFY_ALL break container content, whitelist working ones. + if (message.GetParam1() == GUI_MSG_PAGE_CHANGE || message.GetParam1() == GUI_MSG_WINDOW_RESIZE) + return A(CGUIMediaWindow::OnMessage(message)); + return true; + + case GUI_MSG_CLICKED: + { + int iControl=message.GetSenderId(); + // Handle Sort/View internally. Scripters shouldn't use ID 2, 3 or 4. + if (iControl == CONTROL_BTNSORTASC) // sort asc + { + CLog::Log(LOGINFO, "WindowXML: Internal asc/dsc button not implemented"); + /*if (m_guiState.get()) + m_guiState->SetNextSortOrder(); + UpdateFileList();*/ + return true; + } + else if (iControl == CONTROL_BTNSORTBY) // sort by + { + CLog::Log(LOGINFO, "WindowXML: Internal sort button not implemented"); + /*if (m_guiState.get()) + m_guiState->SetNextSortMethod(); + UpdateFileList();*/ + return true; + } + + if(iControl && iControl != interceptor->GetID()) // pCallbackWindow && != this->GetID()) + { + CGUIControl* controlClicked = interceptor->GetControl(iControl); + + // The old python way used to check list AND SELECITEM method + // or if its a button, radiobutton. + // Its done this way for now to allow other controls without a + // python version like togglebutton to still raise a onAction event + if (controlClicked) // Will get problems if we the id is not on the window + // and we try to do GetControlType on it. So check to make sure it exists + { + if ((controlClicked->IsContainer() && (message.GetParam1() == ACTION_SELECT_ITEM || message.GetParam1() == ACTION_MOUSE_LEFT_CLICK)) || !controlClicked->IsContainer()) + { + invokeCallback(new CallbackFunction<WindowXML,int>(this,&WindowXML::onClick,iControl)); + PulseActionEvent(); + return true; + } + else if (controlClicked->IsContainer() && message.GetParam1() == ACTION_MOUSE_DOUBLE_CLICK) + { + invokeCallback(new CallbackFunction<WindowXML,int>(this,&WindowXML::onDoubleClick,iControl)); + PulseActionEvent(); + return true; + } + else if (controlClicked->IsContainer() && message.GetParam1() == ACTION_MOUSE_RIGHT_CLICK) + { + AddonClass::Ref<Action> inf(new Action(CAction(ACTION_CONTEXT_MENU))); + invokeCallback(new CallbackFunction<WindowXML,AddonClass::Ref<Action> >(this,&WindowXML::onAction,inf.get())); + PulseActionEvent(); + return true; + } + // the core context menu can lead to all sort of issues right now when used with WindowXMLs, so lets intercept the corresponding message + else if (controlClicked->IsContainer() && message.GetParam1() == ACTION_CONTEXT_MENU) + return true; + } + } + } + break; + } + + return A(CGUIMediaWindow::OnMessage(message)); + } + + void WindowXML::AllocResources(bool forceLoad /*= false */) + { + XBMC_TRACE; + std::string tmpDir = URIUtils::GetDirectory(ref(window)->GetProperty("xmlfile").asString()); + std::string fallbackMediaPath; + URIUtils::GetParentPath(tmpDir, fallbackMediaPath); + URIUtils::RemoveSlashAtEnd(fallbackMediaPath); + m_mediaDir = fallbackMediaPath; + + //CLog::Log(LOGDEBUG, "CGUIPythonWindowXML::AllocResources called: {}", fallbackMediaPath); + CServiceBroker::GetGUI()->GetTextureManager().AddTexturePath(m_mediaDir); + ref(window)->AllocResources(forceLoad); + CServiceBroker::GetGUI()->GetTextureManager().RemoveTexturePath(m_mediaDir); + } + + void WindowXML::FreeResources(bool forceUnLoad /*= false */) + { + XBMC_TRACE; + + ref(window)->FreeResources(forceUnLoad); + } + + void WindowXML::Process(unsigned int currentTime, CDirtyRegionList ®ions) + { + XBMC_TRACE; + CServiceBroker::GetGUI()->GetTextureManager().AddTexturePath(m_mediaDir); + ref(window)->Process(currentTime, regions); + CServiceBroker::GetGUI()->GetTextureManager().RemoveTexturePath(m_mediaDir); + } + + bool WindowXML::OnClick(int iItem) + { + XBMC_TRACE; + // Hook Over calling CGUIMediaWindow::OnClick(iItem) results in it trying to PLAY the file item + // which if its not media is BAD and 99 out of 100 times undesirable. + return false; + } + + bool WindowXML::OnDoubleClick(int iItem) + { + XBMC_TRACE; + return false; + } + + void WindowXML::GetContextButtons(int itemNumber, CContextButtons &buttons) + { + XBMC_TRACE; + // maybe on day we can make an easy way to do this context menu + // with out this method overriding the MediaWindow version, it will display 'Add to Favorites' + } + + bool WindowXML::LoadXML(const String &strPath, const String &strLowerPath) + { + XBMC_TRACE; + return A(CGUIWindow::LoadXML(strPath, strLowerPath)); + } + + void WindowXML::SetupShares() + { + XBMC_TRACE; + } + + bool WindowXML::Update(const String &strPath) + { + XBMC_TRACE; + return true; + } + + WindowXMLDialog::WindowXMLDialog(const String& xmlFilename, const String& scriptPath, + const String& defaultSkin, + const String& defaultRes) : + WindowXML(xmlFilename, scriptPath, defaultSkin, defaultRes), + WindowDialogMixin(this) + { XBMC_TRACE; } + + WindowXMLDialog::~WindowXMLDialog() { XBMC_TRACE; deallocating(); } + + bool WindowXMLDialog::OnMessage(CGUIMessage &message) + { + XBMC_TRACE; + if (message.GetMessage() == GUI_MSG_WINDOW_DEINIT) + return A(CGUIWindow::OnMessage(message)); + + return WindowXML::OnMessage(message); + } + + bool WindowXMLDialog::OnAction(const CAction &action) + { + XBMC_TRACE; + return WindowDialogMixin::OnAction(action) ? true : WindowXML::OnAction(action); + } + + void WindowXMLDialog::OnDeinitWindow(int nextWindowID) + { + XBMC_TRACE; + CServiceBroker::GetGUI()->GetWindowManager().RemoveDialog(interceptor->GetID()); + WindowXML::OnDeinitWindow(nextWindowID); + } + + bool WindowXMLDialog::LoadXML(const String &strPath, const String &strLowerPath) + { + XBMC_TRACE; + if (WindowXML::LoadXML(strPath, strLowerPath)) + { + // Set the render order to the dialog's default in case it's not specified in the skin xml + // because this dialog is mapped to CGUIMediaWindow instead of CGUIDialog. + // This must be done here, because the render order will be reset before loading the skin xml. + if (ref(window)->GetRenderOrder() == RENDER_ORDER_WINDOW) + window->SetRenderOrder(RENDER_ORDER_DIALOG); + return true; + } + return false; + } + + } + +} |