summaryrefslogtreecommitdiffstats
path: root/xbmc/favourites
diff options
context:
space:
mode:
Diffstat (limited to 'xbmc/favourites')
-rw-r--r--xbmc/favourites/CMakeLists.txt17
-rw-r--r--xbmc/favourites/ContextMenus.cpp80
-rw-r--r--xbmc/favourites/ContextMenus.h76
-rw-r--r--xbmc/favourites/FavouritesService.cpp273
-rw-r--r--xbmc/favourites/FavouritesService.h56
-rw-r--r--xbmc/favourites/FavouritesURL.cpp205
-rw-r--r--xbmc/favourites/FavouritesURL.h66
-rw-r--r--xbmc/favourites/FavouritesUtils.cpp124
-rw-r--r--xbmc/favourites/FavouritesUtils.h24
-rw-r--r--xbmc/favourites/GUIDialogFavourites.cpp213
-rw-r--r--xbmc/favourites/GUIDialogFavourites.h43
-rw-r--r--xbmc/favourites/GUIViewStateFavourites.cpp28
-rw-r--r--xbmc/favourites/GUIViewStateFavourites.h24
-rw-r--r--xbmc/favourites/GUIWindowFavourites.cpp189
-rw-r--r--xbmc/favourites/GUIWindowFavourites.h33
15 files changed, 1451 insertions, 0 deletions
diff --git a/xbmc/favourites/CMakeLists.txt b/xbmc/favourites/CMakeLists.txt
new file mode 100644
index 0000000..beee8bd
--- /dev/null
+++ b/xbmc/favourites/CMakeLists.txt
@@ -0,0 +1,17 @@
+set(SOURCES ContextMenus.cpp
+ GUIDialogFavourites.cpp
+ GUIViewStateFavourites.cpp
+ GUIWindowFavourites.cpp
+ FavouritesService.cpp
+ FavouritesURL.cpp
+ FavouritesUtils.cpp)
+
+set(HEADERS ContextMenus.h
+ GUIDialogFavourites.h
+ GUIViewStateFavourites.h
+ GUIWindowFavourites.h
+ FavouritesService.h
+ FavouritesURL.h
+ FavouritesUtils.h)
+
+core_add_library(favourites)
diff --git a/xbmc/favourites/ContextMenus.cpp b/xbmc/favourites/ContextMenus.cpp
new file mode 100644
index 0000000..821841f
--- /dev/null
+++ b/xbmc/favourites/ContextMenus.cpp
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2016-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 "ContextMenus.h"
+
+#include "FileItem.h"
+#include "ServiceBroker.h"
+#include "favourites/FavouritesService.h"
+#include "favourites/FavouritesUtils.h"
+#include "utils/URIUtils.h"
+
+namespace CONTEXTMENU
+{
+ bool CFavouriteContextMenuAction::IsVisible(const CFileItem& item) const
+ {
+ return URIUtils::IsProtocol(item.GetPath(), "favourites");
+ }
+
+ bool CFavouriteContextMenuAction::Execute(const std::shared_ptr<CFileItem>& item) const
+ {
+ CFileItemList items;
+ CServiceBroker::GetFavouritesService().GetAll(items);
+ for (const auto& favourite : items)
+ {
+ if (favourite->GetPath() == item->GetPath())
+ {
+ if (DoExecute(items, favourite))
+ return CServiceBroker::GetFavouritesService().Save(items);
+ }
+ }
+ return false;
+ }
+
+ bool CMoveUpFavourite::DoExecute(CFileItemList& items,
+ const std::shared_ptr<CFileItem>& item) const
+ {
+ return FAVOURITES_UTILS::MoveItem(items, item, -1);
+ }
+
+ bool CMoveUpFavourite::IsVisible(const CFileItem& item) const
+ {
+ return CFavouriteContextMenuAction::IsVisible(item) &&
+ FAVOURITES_UTILS::ShouldEnableMoveItems();
+ }
+
+ bool CMoveDownFavourite::DoExecute(CFileItemList& items,
+ const std::shared_ptr<CFileItem>& item) const
+ {
+ return FAVOURITES_UTILS::MoveItem(items, item, +1);
+ }
+
+ bool CMoveDownFavourite::IsVisible(const CFileItem& item) const
+ {
+ return CFavouriteContextMenuAction::IsVisible(item) &&
+ FAVOURITES_UTILS::ShouldEnableMoveItems();
+ }
+
+ bool CRemoveFavourite::DoExecute(CFileItemList& items,
+ const std::shared_ptr<CFileItem>& item) const
+ {
+ return FAVOURITES_UTILS::RemoveItem(items, item);
+ }
+
+ bool CRenameFavourite::DoExecute(CFileItemList&, const std::shared_ptr<CFileItem>& item) const
+ {
+ return FAVOURITES_UTILS::ChooseAndSetNewName(*item);
+ }
+
+ bool CChooseThumbnailForFavourite::DoExecute(CFileItemList&,
+ const std::shared_ptr<CFileItem>& item) const
+ {
+ return FAVOURITES_UTILS::ChooseAndSetNewThumbnail(*item);
+ }
+
+} // namespace CONTEXTMENU
diff --git a/xbmc/favourites/ContextMenus.h b/xbmc/favourites/ContextMenus.h
new file mode 100644
index 0000000..fe1edc8
--- /dev/null
+++ b/xbmc/favourites/ContextMenus.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2016-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.
+ */
+
+#pragma once
+
+#include "ContextMenuItem.h"
+
+#include <memory>
+
+class CFileItemList;
+
+namespace CONTEXTMENU
+{
+
+class CFavouriteContextMenuAction : public CStaticContextMenuAction
+{
+public:
+ explicit CFavouriteContextMenuAction(uint32_t label) : CStaticContextMenuAction(label) {}
+ bool IsVisible(const CFileItem& item) const override;
+ bool Execute(const std::shared_ptr<CFileItem>& item) const override;
+
+protected:
+ ~CFavouriteContextMenuAction() override = default;
+ virtual bool DoExecute(CFileItemList& items, const std::shared_ptr<CFileItem>& item) const = 0;
+};
+
+class CMoveUpFavourite : public CFavouriteContextMenuAction
+{
+public:
+ CMoveUpFavourite() : CFavouriteContextMenuAction(13332) {} // Move up
+ bool IsVisible(const CFileItem& item) const override;
+
+protected:
+ bool DoExecute(CFileItemList& items, const std::shared_ptr<CFileItem>& item) const override;
+};
+
+class CMoveDownFavourite : public CFavouriteContextMenuAction
+{
+public:
+ CMoveDownFavourite() : CFavouriteContextMenuAction(13333) {} // Move down
+ bool IsVisible(const CFileItem& item) const override;
+
+protected:
+ bool DoExecute(CFileItemList& items, const std::shared_ptr<CFileItem>& item) const override;
+};
+
+class CRemoveFavourite : public CFavouriteContextMenuAction
+{
+public:
+ CRemoveFavourite() : CFavouriteContextMenuAction(15015) {} // Remove
+protected:
+ bool DoExecute(CFileItemList& items, const std::shared_ptr<CFileItem>& item) const override;
+};
+
+class CRenameFavourite : public CFavouriteContextMenuAction
+{
+public:
+ CRenameFavourite() : CFavouriteContextMenuAction(118) {} // Rename
+protected:
+ bool DoExecute(CFileItemList& items, const std::shared_ptr<CFileItem>& item) const override;
+};
+
+class CChooseThumbnailForFavourite : public CFavouriteContextMenuAction
+{
+public:
+ CChooseThumbnailForFavourite() : CFavouriteContextMenuAction(20019) {} // Choose thumbnail
+protected:
+ bool DoExecute(CFileItemList& items, const std::shared_ptr<CFileItem>& item) const override;
+};
+
+}
diff --git a/xbmc/favourites/FavouritesService.cpp b/xbmc/favourites/FavouritesService.cpp
new file mode 100644
index 0000000..8659445
--- /dev/null
+++ b/xbmc/favourites/FavouritesService.cpp
@@ -0,0 +1,273 @@
+/*
+ * 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 "FavouritesService.h"
+
+#include "FileItem.h"
+#include "GUIPassword.h"
+#include "ServiceBroker.h"
+#include "Util.h"
+#include "favourites/FavouritesURL.h"
+#include "profiles/ProfileManager.h"
+#include "settings/SettingsComponent.h"
+#include "utils/ContentUtils.h"
+#include "utils/FileUtils.h"
+#include "utils/URIUtils.h"
+#include "utils/XBMCTinyXML.h"
+#include "utils/log.h"
+
+#include <mutex>
+
+namespace
+{
+bool IsMediasourceOfFavItemUnlocked(const std::shared_ptr<CFileItem>& item)
+{
+ if (!item)
+ {
+ CLog::Log(LOGERROR, "{}: No item passed (nullptr).", __func__);
+ return true;
+ }
+
+ if (!item->IsFavourite())
+ {
+ CLog::Log(LOGERROR, "{}: Wrong item passed (not a favourite).", __func__);
+ return true;
+ }
+
+ const auto settingsComponent = CServiceBroker::GetSettingsComponent();
+ if (!settingsComponent)
+ {
+ CLog::Log(LOGERROR, "{}: returned nullptr.", __func__);
+ return true;
+ }
+
+ const auto profileManager = settingsComponent->GetProfileManager();
+ if (!profileManager)
+ {
+ CLog::Log(LOGERROR, "{}: returned nullptr.", __func__);
+ return true;
+ }
+
+ const CFavouritesURL url(item->GetPath());
+ if (!url.IsValid())
+ {
+ CLog::Log(LOGERROR, "{}: Invalid exec string (syntax error).", __func__);
+ return true;
+ }
+
+ const CFavouritesURL::Action action = url.GetAction();
+
+ if (action != CFavouritesURL::Action::PLAY_MEDIA &&
+ action != CFavouritesURL::Action::SHOW_PICTURE)
+ return true;
+
+ const CFileItem itemToCheck(url.GetTarget(), url.IsDir());
+
+ if (action == CFavouritesURL::Action::PLAY_MEDIA)
+ {
+ if (itemToCheck.IsVideo())
+ {
+ if (!profileManager->GetCurrentProfile().videoLocked())
+ return g_passwordManager.IsMediaFileUnlocked("video", itemToCheck.GetPath());
+
+ return false;
+ }
+ else if (itemToCheck.IsAudio())
+ {
+ if (!profileManager->GetCurrentProfile().musicLocked())
+ return g_passwordManager.IsMediaFileUnlocked("music", itemToCheck.GetPath());
+
+ return false;
+ }
+ }
+ else if (action == CFavouritesURL::Action::SHOW_PICTURE && itemToCheck.IsPicture())
+ {
+ if (!profileManager->GetCurrentProfile().picturesLocked())
+ return g_passwordManager.IsMediaFileUnlocked("pictures", itemToCheck.GetPath());
+
+ return false;
+ }
+
+ return true;
+}
+
+bool LoadFromFile(const std::string& strPath, CFileItemList& items)
+{
+ CXBMCTinyXML doc;
+ if (!doc.LoadFile(strPath))
+ {
+ CLog::Log(LOGERROR, "Unable to load {} (row {} column {})", strPath, doc.Row(), doc.Column());
+ return false;
+ }
+ TiXmlElement *root = doc.RootElement();
+ if (!root || strcmp(root->Value(), "favourites"))
+ {
+ CLog::Log(LOGERROR, "Favourites.xml doesn't contain the <favourites> root element");
+ return false;
+ }
+
+ TiXmlElement *favourite = root->FirstChildElement("favourite");
+ while (favourite)
+ {
+ // format:
+ // <favourite name="Cool Video" thumb="foo.jpg">PlayMedia(c:\videos\cool_video.avi)</favourite>
+ // <favourite name="My Album" thumb="bar.tbn">ActivateWindow(MyMusic,c:\music\my album)</favourite>
+ // <favourite name="Apple Movie Trailers" thumb="path_to_thumb.png">RunScript(special://xbmc/scripts/apple movie trailers/default.py)</favourite>
+ const char *name = favourite->Attribute("name");
+ const char *thumb = favourite->Attribute("thumb");
+ if (name && favourite->FirstChild())
+ {
+ const std::string favURL(
+ CFavouritesURL(CExecString(favourite->FirstChild()->Value())).GetURL());
+ if (!items.Contains(favURL))
+ {
+ const CFileItemPtr item(std::make_shared<CFileItem>(name));
+ item->SetPath(favURL);
+ if (thumb)
+ item->SetArt("thumb", thumb);
+ items.Add(item);
+ }
+ }
+ favourite = favourite->NextSiblingElement("favourite");
+ }
+ return true;
+}
+} // unnamed namespace
+
+CFavouritesService::CFavouritesService(std::string userDataFolder) : m_favourites("favourites://")
+{
+ ReInit(std::move(userDataFolder));
+}
+
+void CFavouritesService::ReInit(std::string userDataFolder)
+{
+ m_userDataFolder = std::move(userDataFolder);
+ m_favourites.Clear();
+ m_favourites.SetContent("favourites");
+
+ std::string favourites = "special://xbmc/system/favourites.xml";
+ if (CFileUtils::Exists(favourites))
+ LoadFromFile(favourites, m_favourites);
+ else
+ CLog::Log(LOGDEBUG, "CFavourites::Load - no system favourites found, skipping");
+
+ favourites = URIUtils::AddFileToFolder(m_userDataFolder, "favourites.xml");
+ if (CFileUtils::Exists(favourites))
+ LoadFromFile(favourites, m_favourites);
+ else
+ CLog::Log(LOGDEBUG, "CFavourites::Load - no userdata favourites found, skipping");
+}
+
+bool CFavouritesService::Persist()
+{
+ CXBMCTinyXML doc;
+ TiXmlElement xmlRootElement("favourites");
+ TiXmlNode *rootNode = doc.InsertEndChild(xmlRootElement);
+ if (!rootNode)
+ return false;
+
+ for (const auto& item : m_favourites)
+ {
+ TiXmlElement favNode("favourite");
+ favNode.SetAttribute("name", item->GetLabel().c_str());
+ if (item->HasArt("thumb"))
+ favNode.SetAttribute("thumb", item->GetArt("thumb").c_str());
+
+ TiXmlText execute(CFavouritesURL(item->GetPath()).GetExecString());
+ favNode.InsertEndChild(execute);
+ rootNode->InsertEndChild(favNode);
+ }
+
+ auto path = URIUtils::AddFileToFolder(m_userDataFolder, "favourites.xml");
+ return doc.SaveFile(path);
+}
+
+bool CFavouritesService::Save(const CFileItemList& items)
+{
+ {
+ std::unique_lock<CCriticalSection> lock(m_criticalSection);
+ m_favourites.Clear();
+ m_favourites.Copy(items);
+ Persist();
+ }
+ OnUpdated();
+ return true;
+}
+
+void CFavouritesService::OnUpdated()
+{
+ m_events.Publish(FavouritesUpdated{});
+}
+
+std::string CFavouritesService::GetFavouritesUrl(const CFileItem& item, int contextWindow) const
+{
+ return CFavouritesURL(item, contextWindow).GetURL();
+}
+
+bool CFavouritesService::AddOrRemove(const CFileItem& item, int contextWindow)
+{
+ auto favUrl = GetFavouritesUrl(item, contextWindow);
+ {
+ std::unique_lock<CCriticalSection> lock(m_criticalSection);
+ CFileItemPtr match = m_favourites.Get(favUrl);
+ if (match)
+ { // remove the item
+ m_favourites.Remove(match.get());
+ }
+ else
+ { // create our new favourite item
+ const CFileItemPtr favourite(std::make_shared<CFileItem>(item.GetLabel()));
+ if (item.GetLabel().empty())
+ favourite->SetLabel(CUtil::GetTitleFromPath(item.GetPath(), item.m_bIsFolder));
+ favourite->SetArt("thumb", ContentUtils::GetPreferredArtImage(item));
+ favourite->SetPath(favUrl);
+ m_favourites.Add(favourite);
+ }
+ Persist();
+ }
+ OnUpdated();
+ return true;
+}
+
+bool CFavouritesService::IsFavourited(const CFileItem& item, int contextWindow) const
+{
+ std::unique_lock<CCriticalSection> lock(m_criticalSection);
+ return m_favourites.Contains(GetFavouritesUrl(item, contextWindow));
+}
+
+void CFavouritesService::GetAll(CFileItemList& items) const
+{
+ std::unique_lock<CCriticalSection> lock(m_criticalSection);
+ items.Clear();
+ if (g_passwordManager.IsMasterLockUnlocked(false)) // don't prompt
+ {
+ items.Copy(m_favourites, true); // copy items
+ }
+ else
+ {
+ for (const auto& fav : m_favourites)
+ {
+ if (IsMediasourceOfFavItemUnlocked(fav))
+ items.Add(fav);
+ }
+ }
+
+ int index = 0;
+ for (const auto& item : items)
+ {
+ const CFavouritesURL favURL(item->GetPath());
+ item->SetProperty("favourite.action", favURL.GetActionLabel());
+ item->SetProperty("favourite.provider", favURL.GetProviderLabel());
+ item->SetProperty("favourite.index", index++);
+ }
+}
+
+void CFavouritesService::RefreshFavourites()
+{
+ m_events.Publish(FavouritesUpdated{});
+}
diff --git a/xbmc/favourites/FavouritesService.h b/xbmc/favourites/FavouritesService.h
new file mode 100644
index 0000000..5a16459
--- /dev/null
+++ b/xbmc/favourites/FavouritesService.h
@@ -0,0 +1,56 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "FileItem.h"
+#include "threads/CriticalSection.h"
+#include "utils/EventStream.h"
+
+#include <string>
+#include <vector>
+
+class CFavouritesService
+{
+public:
+ explicit CFavouritesService(std::string userDataFolder);
+ virtual ~CFavouritesService() = default;
+
+ /** For profiles*/
+ void ReInit(std::string userDataFolder);
+
+ bool IsFavourited(const CFileItem& item, int contextWindow) const;
+ void GetAll(CFileItemList& items) const;
+ bool AddOrRemove(const CFileItem& item, int contextWindow);
+ bool Save(const CFileItemList& items);
+
+ /*! \brief Refresh favourites for directory providers, e.g. the GUI needs to be updated
+ */
+ void RefreshFavourites();
+
+ struct FavouritesUpdated { };
+
+ CEventStream<FavouritesUpdated>& Events() { return m_events; }
+
+private:
+ CFavouritesService() = delete;
+ CFavouritesService(const CFavouritesService&) = delete;
+ CFavouritesService& operator=(const CFavouritesService&) = delete;
+ CFavouritesService(CFavouritesService&&) = delete;
+ CFavouritesService& operator=(CFavouritesService&&) = delete;
+
+ void OnUpdated();
+ bool Persist();
+ std::string GetFavouritesUrl(const CFileItem &item, int contextWindow) const;
+
+ std::string m_userDataFolder;
+ CFileItemList m_favourites;
+ CEventSource<FavouritesUpdated> m_events;
+ mutable CCriticalSection m_criticalSection;
+};
+
diff --git a/xbmc/favourites/FavouritesURL.cpp b/xbmc/favourites/FavouritesURL.cpp
new file mode 100644
index 0000000..a277256
--- /dev/null
+++ b/xbmc/favourites/FavouritesURL.cpp
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2022 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 "FavouritesURL.h"
+
+#include "FileItem.h"
+#include "ServiceBroker.h"
+#include "URL.h"
+#include "addons/AddonManager.h"
+#include "addons/IAddon.h"
+#include "guilib/LocalizeStrings.h"
+#include "input/WindowTranslator.h"
+#include "utils/ExecString.h"
+#include "utils/StringUtils.h"
+#include "utils/SystemInfo.h"
+#include "utils/URIUtils.h"
+#include "utils/log.h"
+
+#include <vector>
+
+namespace
+{
+std::string GetActionString(CFavouritesURL::Action action)
+{
+ switch (action)
+ {
+ case CFavouritesURL::Action::ACTIVATE_WINDOW:
+ return "activatewindow";
+ case CFavouritesURL::Action::PLAY_MEDIA:
+ return "playmedia";
+ case CFavouritesURL::Action::SHOW_PICTURE:
+ return "showpicture";
+ case CFavouritesURL::Action::RUN_SCRIPT:
+ return "runscript";
+ case CFavouritesURL::Action::RUN_ADDON:
+ return "runaddon";
+ case CFavouritesURL::Action::START_ANDROID_ACTIVITY:
+ return "startandroidactivity";
+ default:
+ return {};
+ }
+}
+
+CFavouritesURL::Action GetActionId(const std::string& actionString)
+{
+ if (actionString == "activatewindow")
+ return CFavouritesURL::Action::ACTIVATE_WINDOW;
+ else if (actionString == "playmedia")
+ return CFavouritesURL::Action::PLAY_MEDIA;
+ else if (actionString == "showpicture")
+ return CFavouritesURL::Action::SHOW_PICTURE;
+ else if (actionString == "runscript")
+ return CFavouritesURL::Action::RUN_SCRIPT;
+ else if (actionString == "runaddon")
+ return CFavouritesURL::Action::RUN_ADDON;
+ else if (actionString == "startandroidactivity")
+ return CFavouritesURL::Action::START_ANDROID_ACTIVITY;
+ else
+ return CFavouritesURL::Action::UNKNOWN;
+}
+} // namespace
+
+CFavouritesURL::CFavouritesURL(const std::string& favouritesURL)
+{
+ const CURL url(favouritesURL);
+ if (url.GetProtocol() != "favourites")
+ {
+ CLog::LogF(LOGERROR, "Invalid protocol");
+ return;
+ }
+
+ m_exec = CExecString(CURL::Decode(url.GetHostName()));
+ if (m_exec.IsValid())
+ m_valid = Parse(GetActionId(m_exec.GetFunction()), m_exec.GetParams());
+}
+
+CFavouritesURL::CFavouritesURL(const CExecString& execString) : m_exec(execString)
+{
+ if (m_exec.IsValid())
+ m_valid = Parse(GetActionId(m_exec.GetFunction()), m_exec.GetParams());
+}
+
+CFavouritesURL::CFavouritesURL(Action action, const std::vector<std::string>& params)
+ : m_exec(GetActionString(action), params)
+{
+ if (m_exec.IsValid())
+ m_valid = Parse(action, params);
+}
+
+CFavouritesURL::CFavouritesURL(const CFileItem& item, int contextWindow)
+ : m_exec(item, std::to_string(contextWindow))
+{
+ if (m_exec.IsValid())
+ m_valid = Parse(GetActionId(m_exec.GetFunction()), m_exec.GetParams());
+}
+
+bool CFavouritesURL::Parse(CFavouritesURL::Action action, const std::vector<std::string>& params)
+{
+ m_action = action;
+
+ bool pathIsAddonID = false;
+
+ switch (action)
+ {
+ case Action::ACTIVATE_WINDOW:
+ if (params.empty())
+ {
+ CLog::LogF(LOGERROR, "Missing parameter");
+ return false;
+ }
+
+ // mandatory: window name/id
+ m_windowId = CWindowTranslator::TranslateWindow(params[0]);
+
+ if (params.size() > 1)
+ {
+ // optional: target url/path
+ m_target = StringUtils::DeParamify(params[1]);
+ }
+ m_actionLabel =
+ StringUtils::Format(g_localizeStrings.Get(15220), // Show content in '<windowname>'
+ g_localizeStrings.Get(m_windowId));
+ break;
+ case Action::PLAY_MEDIA:
+ if (params.empty())
+ {
+ CLog::LogF(LOGERROR, "Missing parameter");
+ return false;
+ }
+ m_target = StringUtils::DeParamify(params[0]);
+ m_actionLabel = g_localizeStrings.Get(15218); // Play media
+ break;
+ case Action::SHOW_PICTURE:
+ if (params.empty())
+ {
+ CLog::LogF(LOGERROR, "Missing parameter");
+ return false;
+ }
+ m_target = StringUtils::DeParamify(params[0]);
+ m_actionLabel = g_localizeStrings.Get(15219); // Show picture
+ break;
+ case Action::RUN_SCRIPT:
+ if (params.empty())
+ {
+ CLog::LogF(LOGERROR, "Missing parameter");
+ return false;
+ }
+ m_target = StringUtils::DeParamify(params[0]);
+ m_actionLabel = g_localizeStrings.Get(15221); // Execute script
+ pathIsAddonID = true;
+ break;
+ case Action::RUN_ADDON:
+ if (params.empty())
+ {
+ CLog::LogF(LOGERROR, "Missing parameter");
+ return false;
+ }
+ m_target = StringUtils::DeParamify(params[0]);
+ m_actionLabel = g_localizeStrings.Get(15223); // Execute add-on
+ pathIsAddonID = true;
+ break;
+ case Action::START_ANDROID_ACTIVITY:
+ if (params.empty())
+ {
+ CLog::LogF(LOGERROR, "Missing parameter");
+ return false;
+ }
+ m_target = StringUtils::DeParamify(params[0]);
+ m_actionLabel = g_localizeStrings.Get(15222); // Execute Android app
+ break;
+ default:
+ if (params.empty())
+ {
+ CLog::LogF(LOGERROR, "Missing parameter");
+ return false;
+ }
+ m_action = CFavouritesURL::Action::UNKNOWN;
+ m_target = StringUtils::DeParamify(params[0]);
+ m_actionLabel = g_localizeStrings.Get(15224); // Other / Unknown
+ break;
+ }
+
+ m_path = StringUtils::Format("favourites://{}", CURL::Encode(GetExecString()));
+ m_isDir = URIUtils::HasSlashAtEnd(m_target, true);
+
+ if (pathIsAddonID || URIUtils::IsPlugin(m_target))
+ {
+ // get the add-on name
+ const std::string plugin = pathIsAddonID ? m_target : CURL(m_target).GetHostName();
+
+ ADDON::AddonPtr addon;
+ CServiceBroker::GetAddonMgr().GetAddon(plugin, addon, ADDON::OnlyEnabled::CHOICE_NO);
+ if (addon)
+ m_providerLabel = addon->Name();
+ }
+ if (m_providerLabel.empty())
+ m_providerLabel = CSysInfo::GetAppName();
+
+ return true;
+}
diff --git a/xbmc/favourites/FavouritesURL.h b/xbmc/favourites/FavouritesURL.h
new file mode 100644
index 0000000..7261484
--- /dev/null
+++ b/xbmc/favourites/FavouritesURL.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2012-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.
+ */
+
+#pragma once
+
+#include "utils/ExecString.h"
+
+#include <string>
+#include <vector>
+
+class CFileItem;
+
+class CFavouritesURL
+{
+public:
+ enum class Action
+ {
+ UNKNOWN,
+ ACTIVATE_WINDOW,
+ PLAY_MEDIA,
+ SHOW_PICTURE,
+ RUN_SCRIPT,
+ RUN_ADDON,
+ START_ANDROID_ACTIVITY,
+ };
+
+ explicit CFavouritesURL(const std::string& favouritesURL);
+ explicit CFavouritesURL(const CExecString& execString);
+ CFavouritesURL(Action action, const std::vector<std::string>& params);
+ CFavouritesURL(const CFileItem& item, int contextWindow);
+
+ virtual ~CFavouritesURL() = default;
+
+ std::string GetURL() const { return m_path; }
+
+ bool IsValid() const { return m_valid && m_exec.IsValid(); }
+
+ bool IsDir() const { return m_isDir; }
+
+ std::string GetExecString() const { return m_exec.GetExecString(); }
+ Action GetAction() const { return m_action; }
+ std::vector<std::string> GetParams() const { return m_exec.GetParams(); }
+ std::string GetTarget() const { return m_target; }
+ int GetWindowID() const { return m_windowId; }
+ std::string GetActionLabel() const { return m_actionLabel; }
+ std::string GetProviderLabel() const { return m_providerLabel; }
+
+private:
+ bool Parse(CFavouritesURL::Action action, const std::vector<std::string>& params);
+
+ CExecString m_exec;
+
+ bool m_valid{false};
+ std::string m_path;
+ Action m_action{Action::UNKNOWN};
+ std::string m_target;
+ int m_windowId{-1};
+ bool m_isDir{false};
+ std::string m_actionLabel;
+ std::string m_providerLabel;
+};
diff --git a/xbmc/favourites/FavouritesUtils.cpp b/xbmc/favourites/FavouritesUtils.cpp
new file mode 100644
index 0000000..9a055c5
--- /dev/null
+++ b/xbmc/favourites/FavouritesUtils.cpp
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2023 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 "FavouritesUtils.h"
+
+#include "FileItem.h"
+#include "ServiceBroker.h"
+#include "dialogs/GUIDialogFileBrowser.h"
+#include "favourites/GUIWindowFavourites.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUIKeyboardFactory.h"
+#include "guilib/GUIWindowManager.h"
+#include "guilib/LocalizeStrings.h"
+#include "storage/MediaManager.h"
+#include "utils/Variant.h"
+#include "view/GUIViewState.h"
+
+#include <string>
+
+namespace FAVOURITES_UTILS
+{
+bool ChooseAndSetNewName(CFileItem& item)
+{
+ std::string label = item.GetLabel();
+ if (CGUIKeyboardFactory::ShowAndGetInput(label, CVariant{g_localizeStrings.Get(16008)},
+ false)) // Enter new title
+ {
+ item.SetLabel(label);
+ return true;
+ }
+ return false;
+}
+
+bool ChooseAndSetNewThumbnail(CFileItem& item)
+{
+ CFileItemList prefilledItems;
+ if (item.HasArt("thumb"))
+ {
+ const auto current = std::make_shared<CFileItem>("thumb://Current", false);
+ current->SetArt("thumb", item.GetArt("thumb"));
+ current->SetLabel(g_localizeStrings.Get(20016)); // Current thumb
+ prefilledItems.Add(current);
+ }
+
+ const auto none = std::make_shared<CFileItem>("thumb://None", false);
+ none->SetArt("icon", item.GetArt("icon"));
+ none->SetLabel(g_localizeStrings.Get(20018)); // No thumb
+ prefilledItems.Add(none);
+
+ std::string thumb;
+ VECSOURCES sources;
+ CServiceBroker::GetMediaManager().GetLocalDrives(sources);
+ if (CGUIDialogFileBrowser::ShowAndGetImage(prefilledItems, sources, g_localizeStrings.Get(1030),
+ thumb)) // Browse for image
+ {
+ item.SetArt("thumb", thumb);
+ return true;
+ }
+ return false;
+}
+
+bool MoveItem(CFileItemList& items, const std::shared_ptr<CFileItem>& item, int amount)
+{
+ if (items.Size() < 2 || amount == 0)
+ return false;
+
+ int itemPos = -1;
+ for (const auto& i : items)
+ {
+ itemPos++;
+
+ if (i->GetPath() == item->GetPath())
+ break;
+ }
+
+ if (itemPos < 0 || itemPos >= items.Size())
+ return false;
+
+ int nextItem = (itemPos + amount) % items.Size();
+ if (nextItem < 0)
+ {
+ const auto itemToAdd(item);
+ items.Remove(itemPos);
+ items.Add(itemToAdd);
+ }
+ else if (nextItem == 0)
+ {
+ const auto itemToAdd(item);
+ items.Remove(itemPos);
+ items.AddFront(itemToAdd, 0);
+ }
+ else
+ {
+ items.Swap(itemPos, nextItem);
+ }
+ return true;
+}
+
+bool RemoveItem(CFileItemList& items, const std::shared_ptr<CFileItem>& item)
+{
+ int iBefore = items.Size();
+ items.Remove(item.get());
+ return items.Size() == iBefore - 1;
+}
+
+bool ShouldEnableMoveItems()
+{
+ auto& mgr = CServiceBroker::GetGUI()->GetWindowManager();
+ CGUIWindowFavourites* window = mgr.GetWindow<CGUIWindowFavourites>(WINDOW_FAVOURITES);
+ if (window && window->IsActive())
+ {
+ const CGUIViewState* state = window->GetViewState();
+ if (state && state->GetSortMethod().sortBy != SortByUserPreference)
+ return false; // in favs window, allow move only if current sort method is by user preference
+ }
+ return true;
+}
+
+} // namespace FAVOURITES_UTILS
diff --git a/xbmc/favourites/FavouritesUtils.h b/xbmc/favourites/FavouritesUtils.h
new file mode 100644
index 0000000..0388b4d
--- /dev/null
+++ b/xbmc/favourites/FavouritesUtils.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+#pragma once
+
+#include <memory>
+
+class CFileItem;
+class CFileItemList;
+
+namespace FAVOURITES_UTILS
+{
+bool ChooseAndSetNewName(CFileItem& item);
+bool ChooseAndSetNewThumbnail(CFileItem& item);
+bool MoveItem(CFileItemList& items, const std::shared_ptr<CFileItem>& item, int amount);
+bool RemoveItem(CFileItemList& items, const std::shared_ptr<CFileItem>& item);
+bool ShouldEnableMoveItems();
+
+} // namespace FAVOURITES_UTILS
diff --git a/xbmc/favourites/GUIDialogFavourites.cpp b/xbmc/favourites/GUIDialogFavourites.cpp
new file mode 100644
index 0000000..7af6d29
--- /dev/null
+++ b/xbmc/favourites/GUIDialogFavourites.cpp
@@ -0,0 +1,213 @@
+/*
+ * 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 "GUIDialogFavourites.h"
+
+#include "ContextMenuManager.h"
+#include "ServiceBroker.h"
+#include "dialogs/GUIDialogContextMenu.h"
+#include "favourites/FavouritesURL.h"
+#include "favourites/FavouritesUtils.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUIMessage.h"
+#include "guilib/GUIWindowManager.h"
+#include "input/actions/ActionIDs.h"
+
+#define FAVOURITES_LIST 450
+
+CGUIDialogFavourites::CGUIDialogFavourites() :
+ CGUIDialog(WINDOW_DIALOG_FAVOURITES, "DialogFavourites.xml"),
+ m_favouritesService(CServiceBroker::GetFavouritesService())
+{
+ m_favourites = new CFileItemList;
+ m_loadType = KEEP_IN_MEMORY;
+}
+
+CGUIDialogFavourites::~CGUIDialogFavourites(void)
+{
+ delete m_favourites;
+}
+
+bool CGUIDialogFavourites::OnMessage(CGUIMessage &message)
+{
+ if (message.GetMessage() == GUI_MSG_CLICKED)
+ {
+ if (message.GetSenderId() == FAVOURITES_LIST)
+ {
+ int item = GetSelectedItem();
+ int action = message.GetParam1();
+ if (action == ACTION_SELECT_ITEM || action == ACTION_MOUSE_LEFT_CLICK)
+ OnClick(item);
+ else if (action == ACTION_MOVE_ITEM_UP)
+ OnMoveItem(item, -1);
+ else if (action == ACTION_MOVE_ITEM_DOWN)
+ OnMoveItem(item, 1);
+ else if (action == ACTION_CONTEXT_MENU || action == ACTION_MOUSE_RIGHT_CLICK)
+ OnPopupMenu(item);
+ else if (action == ACTION_DELETE_ITEM)
+ OnDelete(item);
+ else
+ return false;
+ return true;
+ }
+ }
+ else if (message.GetMessage() == GUI_MSG_WINDOW_DEINIT)
+ {
+ CGUIDialog::OnMessage(message);
+ // clear our favourites
+ CGUIMessage message(GUI_MSG_LABEL_RESET, GetID(), FAVOURITES_LIST);
+ OnMessage(message);
+ m_favourites->Clear();
+ return true;
+ }
+ return CGUIDialog::OnMessage(message);
+}
+
+void CGUIDialogFavourites::OnInitWindow()
+{
+ m_favouritesService.GetAll(*m_favourites);
+ UpdateList();
+ CGUIWindow::OnInitWindow();
+}
+
+int CGUIDialogFavourites::GetSelectedItem()
+{
+ CGUIMessage message(GUI_MSG_ITEM_SELECTED, GetID(), FAVOURITES_LIST);
+ OnMessage(message);
+ return message.GetParam1();
+}
+
+void CGUIDialogFavourites::OnClick(int item)
+{
+ if (item < 0 || item >= m_favourites->Size())
+ return;
+
+ CGUIMessage message(GUI_MSG_EXECUTE, 0, GetID());
+ message.SetStringParam(CFavouritesURL(*(*m_favourites)[item], GetID()).GetExecString());
+
+ Close();
+
+ CServiceBroker::GetGUI()->GetWindowManager().SendMessage(message);
+}
+
+void CGUIDialogFavourites::OnPopupMenu(int item)
+{
+ if (item < 0 || item >= m_favourites->Size())
+ return;
+
+ // highlight the item
+ (*m_favourites)[item]->Select(true);
+
+ CContextButtons choices;
+ if (m_favourites->Size() > 1)
+ {
+ choices.Add(1, 13332); // Move up
+ choices.Add(2, 13333); // Move down
+ }
+ choices.Add(3, 20019); // Choose thumbnail
+ choices.Add(4, 118); // Rename
+ choices.Add(5, 15015); // Remove
+
+ CFileItemPtr itemPtr = m_favourites->Get(item);
+
+ //temporary workaround until the context menu ids are removed
+ const int addonItemOffset = 10000;
+
+ auto addonItems = CServiceBroker::GetContextMenuManager().GetAddonItems(*itemPtr);
+
+ for (size_t i = 0; i < addonItems.size(); ++i)
+ choices.Add(addonItemOffset + i, addonItems[i]->GetLabel(*itemPtr));
+
+ int button = CGUIDialogContextMenu::ShowAndGetChoice(choices);
+
+ // unhighlight the item
+ (*m_favourites)[item]->Select(false);
+
+ if (button == 1)
+ OnMoveItem(item, -1);
+ else if (button == 2)
+ OnMoveItem(item, +1);
+ else if (button == 3)
+ OnSetThumb(item);
+ else if (button == 4)
+ OnRename(item);
+ else if (button == 5)
+ OnDelete(item);
+ else if (button >= addonItemOffset)
+ CONTEXTMENU::LoopFrom(*addonItems.at(button - addonItemOffset), itemPtr);
+}
+
+void CGUIDialogFavourites::OnMoveItem(int item, int amount)
+{
+ if (item < 0 || item >= m_favourites->Size() || m_favourites->Size() <= 1 || 0 == amount) return;
+
+ int nextItem = (item + amount) % m_favourites->Size();
+ if (nextItem < 0) nextItem += m_favourites->Size();
+
+ m_favourites->Swap(item, nextItem);
+ m_favouritesService.Save(*m_favourites);
+
+ CGUIMessage message(GUI_MSG_ITEM_SELECT, GetID(), FAVOURITES_LIST, nextItem);
+ OnMessage(message);
+
+ UpdateList();
+}
+
+void CGUIDialogFavourites::OnDelete(int item)
+{
+ if (item < 0 || item >= m_favourites->Size())
+ return;
+ m_favourites->Remove(item);
+ m_favouritesService.Save(*m_favourites);
+
+ CGUIMessage message(GUI_MSG_ITEM_SELECT, GetID(), FAVOURITES_LIST, item < m_favourites->Size() ? item : item - 1);
+ OnMessage(message);
+
+ UpdateList();
+}
+
+void CGUIDialogFavourites::OnRename(int item)
+{
+ if (item < 0 || item >= m_favourites->Size())
+ return;
+
+ if (FAVOURITES_UTILS::ChooseAndSetNewName(*(*m_favourites)[item]))
+ {
+ m_favouritesService.Save(*m_favourites);
+ UpdateList();
+ }
+}
+
+void CGUIDialogFavourites::OnSetThumb(int item)
+{
+ if (item < 0 || item >= m_favourites->Size())
+ return;
+
+ if (FAVOURITES_UTILS::ChooseAndSetNewThumbnail(*(*m_favourites)[item]))
+ {
+ m_favouritesService.Save(*m_favourites);
+ UpdateList();
+ }
+}
+
+void CGUIDialogFavourites::UpdateList()
+{
+ int currentItem = GetSelectedItem();
+ CGUIMessage message(GUI_MSG_LABEL_BIND, GetID(), FAVOURITES_LIST, currentItem >= 0 ? currentItem : 0, 0, m_favourites);
+ OnMessage(message);
+}
+
+CFileItemPtr CGUIDialogFavourites::GetCurrentListItem(int offset)
+{
+ int currentItem = GetSelectedItem();
+ if (currentItem < 0 || !m_favourites->Size()) return CFileItemPtr();
+
+ int item = (currentItem + offset) % m_favourites->Size();
+ if (item < 0) item += m_favourites->Size();
+ return (*m_favourites)[item];
+}
diff --git a/xbmc/favourites/GUIDialogFavourites.h b/xbmc/favourites/GUIDialogFavourites.h
new file mode 100644
index 0000000..72d26bd
--- /dev/null
+++ b/xbmc/favourites/GUIDialogFavourites.h
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "favourites/FavouritesService.h"
+#include "guilib/GUIDialog.h"
+
+class CFileItem;
+class CFileItemList;
+
+class CGUIDialogFavourites :
+ public CGUIDialog
+{
+public:
+ CGUIDialogFavourites(void);
+ ~CGUIDialogFavourites(void) override;
+ bool OnMessage(CGUIMessage &message) override;
+ void OnInitWindow() override;
+
+ CFileItemPtr GetCurrentListItem(int offset = 0) override;
+
+ bool HasListItems() const override { return true; }
+
+protected:
+ int GetSelectedItem();
+ void OnClick(int item);
+ void OnPopupMenu(int item);
+ void OnMoveItem(int item, int amount);
+ void OnDelete(int item);
+ void OnRename(int item);
+ void OnSetThumb(int item);
+ void UpdateList();
+
+private:
+ CFileItemList* m_favourites;
+ CFavouritesService& m_favouritesService;
+};
diff --git a/xbmc/favourites/GUIViewStateFavourites.cpp b/xbmc/favourites/GUIViewStateFavourites.cpp
new file mode 100644
index 0000000..7bb5ff1
--- /dev/null
+++ b/xbmc/favourites/GUIViewStateFavourites.cpp
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2022 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 "GUIViewStateFavourites.h"
+
+#include "FileItem.h"
+#include "guilib/WindowIDs.h"
+
+CGUIViewStateFavourites::CGUIViewStateFavourites(const CFileItemList& items) : CGUIViewState(items)
+{
+ AddSortMethod(SortByUserPreference, 19349,
+ LABEL_MASKS("%L", "", "%L", "")); // Label, empty | Label, empty
+ AddSortMethod(SortByLabel, 551, LABEL_MASKS("%L", "", "%L", "")); // Label, empty | Label, empty
+
+ SetSortMethod(SortByUserPreference);
+
+ LoadViewState(items.GetPath(), WINDOW_FAVOURITES);
+}
+
+void CGUIViewStateFavourites::SaveViewState()
+{
+ SaveViewToDb(m_items.GetPath(), WINDOW_FAVOURITES);
+}
diff --git a/xbmc/favourites/GUIViewStateFavourites.h b/xbmc/favourites/GUIViewStateFavourites.h
new file mode 100644
index 0000000..aad4444
--- /dev/null
+++ b/xbmc/favourites/GUIViewStateFavourites.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#pragma once
+
+#include "view/GUIViewState.h"
+
+class CFileItemList;
+
+class CGUIViewStateFavourites : public CGUIViewState
+{
+public:
+ CGUIViewStateFavourites(const CFileItemList& items);
+ ~CGUIViewStateFavourites() override = default;
+
+protected:
+ void SaveViewState() override;
+ bool HideParentDirItems() override { return true; };
+};
diff --git a/xbmc/favourites/GUIWindowFavourites.cpp b/xbmc/favourites/GUIWindowFavourites.cpp
new file mode 100644
index 0000000..0d43b59
--- /dev/null
+++ b/xbmc/favourites/GUIWindowFavourites.cpp
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2022 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 "GUIWindowFavourites.h"
+
+#include "FileItem.h"
+#include "ServiceBroker.h"
+#include "favourites/FavouritesURL.h"
+#include "favourites/FavouritesUtils.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUIMessage.h"
+#include "guilib/GUIWindowManager.h"
+#include "input/actions/Action.h"
+#include "input/actions/ActionIDs.h"
+#include "messaging/ApplicationMessenger.h"
+#include "utils/PlayerUtils.h"
+#include "utils/StringUtils.h"
+
+CGUIWindowFavourites::CGUIWindowFavourites()
+ : CGUIMediaWindow(WINDOW_FAVOURITES, "MyFavourites.xml")
+{
+ m_loadType = KEEP_IN_MEMORY;
+ CServiceBroker::GetFavouritesService().Events().Subscribe(
+ this, &CGUIWindowFavourites::OnFavouritesEvent);
+}
+
+CGUIWindowFavourites::~CGUIWindowFavourites()
+{
+ CServiceBroker::GetFavouritesService().Events().Unsubscribe(this);
+}
+
+void CGUIWindowFavourites::OnFavouritesEvent(const CFavouritesService::FavouritesUpdated& event)
+{
+ CGUIMessage m(GUI_MSG_REFRESH_LIST, GetID(), 0, 0);
+ CServiceBroker::GetAppMessenger()->SendGUIMessage(m);
+}
+
+namespace
+{
+bool ExecuteAction(const std::string& execute)
+{
+ if (!execute.empty())
+ {
+ CGUIMessage message(GUI_MSG_EXECUTE, 0, 0);
+ message.SetStringParam(execute);
+ CServiceBroker::GetGUI()->GetWindowManager().SendMessage(message);
+ return true;
+ }
+ return false;
+}
+} // namespace
+
+bool CGUIWindowFavourites::OnSelect(int item)
+{
+ if (item < 0 || item >= m_vecItems->Size())
+ return false;
+
+ return ExecuteAction(CFavouritesURL(*(*m_vecItems)[item], GetID()).GetExecString());
+}
+
+bool CGUIWindowFavourites::OnAction(const CAction& action)
+{
+ const int selectedItem = m_viewControl.GetSelectedItem();
+ if (selectedItem < 0 || selectedItem >= m_vecItems->Size())
+ return false;
+
+ if (action.GetID() == ACTION_PLAYER_PLAY)
+ {
+ const CFavouritesURL favURL((*m_vecItems)[selectedItem]->GetPath());
+ if (!favURL.IsValid())
+ return false;
+
+ // If action is playmedia, just play it
+ if (favURL.GetAction() == CFavouritesURL::Action::PLAY_MEDIA)
+ return ExecuteAction(favURL.GetExecString());
+
+ // Resolve and check the target
+ const auto item = std::make_shared<CFileItem>(favURL.GetTarget(), favURL.IsDir());
+ if (CPlayerUtils::IsItemPlayable(*item))
+ {
+ CFavouritesURL target(*item, {});
+ if (target.GetAction() == CFavouritesURL::Action::PLAY_MEDIA)
+ {
+ return ExecuteAction(target.GetExecString());
+ }
+ else
+ {
+ // build and execute a playmedia execute string
+ target = CFavouritesURL(CFavouritesURL::Action::PLAY_MEDIA,
+ {StringUtils::Paramify(item->GetPath())});
+ return ExecuteAction(target.GetExecString());
+ }
+ }
+ return false;
+ }
+ else if (action.GetID() == ACTION_MOVE_ITEM_UP)
+ {
+ if (FAVOURITES_UTILS::ShouldEnableMoveItems())
+ return MoveItem(selectedItem, -1);
+ }
+ else if (action.GetID() == ACTION_MOVE_ITEM_DOWN)
+ {
+ if (FAVOURITES_UTILS::ShouldEnableMoveItems())
+ return MoveItem(selectedItem, +1);
+ }
+ else if (action.GetID() == ACTION_DELETE_ITEM)
+ {
+ return RemoveItem(selectedItem);
+ }
+
+ return CGUIMediaWindow::OnAction(action);
+}
+
+bool CGUIWindowFavourites::OnMessage(CGUIMessage& message)
+{
+ bool ret = false;
+
+ switch (message.GetMessage())
+ {
+ case GUI_MSG_REFRESH_LIST:
+ {
+ const int size = m_vecItems->Size();
+ int selected = m_viewControl.GetSelectedItem();
+ if (m_vecItems->Size() > 0 && selected == size - 1)
+ --selected; // remove of last item, select the new last item after refresh
+
+ Refresh(true);
+
+ if (m_vecItems->Size() < size)
+ {
+ // item removed. select item after the removed item
+ m_viewControl.SetSelectedItem(selected);
+ }
+
+ ret = true;
+ break;
+ }
+ }
+
+ return ret || CGUIMediaWindow::OnMessage(message);
+}
+
+bool CGUIWindowFavourites::Update(const std::string& strDirectory,
+ bool updateFilterPath /* = true */)
+{
+ std::string directory = strDirectory;
+ if (directory.empty())
+ directory = "favourites://";
+
+ return CGUIMediaWindow::Update(directory, updateFilterPath);
+}
+
+bool CGUIWindowFavourites::MoveItem(int item, int amount)
+{
+ if (item < 0 || item >= m_vecItems->Size() || m_vecItems->Size() < 2 || amount == 0)
+ return false;
+
+ if (FAVOURITES_UTILS::MoveItem(*m_vecItems, (*m_vecItems)[item], amount) &&
+ CServiceBroker::GetFavouritesService().Save(*m_vecItems))
+ {
+ int selected = item + amount;
+ if (selected >= m_vecItems->Size())
+ selected = 0;
+ else if (selected < 0)
+ selected = m_vecItems->Size() - 1;
+
+ m_viewControl.SetSelectedItem(selected);
+ return true;
+ }
+
+ return false;
+}
+
+bool CGUIWindowFavourites::RemoveItem(int item)
+{
+ if (item < 0 || item >= m_vecItems->Size())
+ return false;
+
+ if (FAVOURITES_UTILS::RemoveItem(*m_vecItems, (*m_vecItems)[item]) &&
+ CServiceBroker::GetFavouritesService().Save(*m_vecItems))
+ return true;
+
+ return false;
+}
diff --git a/xbmc/favourites/GUIWindowFavourites.h b/xbmc/favourites/GUIWindowFavourites.h
new file mode 100644
index 0000000..ec4ea32
--- /dev/null
+++ b/xbmc/favourites/GUIWindowFavourites.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#pragma once
+
+#include "favourites/FavouritesService.h"
+#include "windows/GUIMediaWindow.h"
+
+class CGUIWindowFavourites : public CGUIMediaWindow
+{
+public:
+ CGUIWindowFavourites();
+ ~CGUIWindowFavourites() override;
+
+protected:
+ std::string GetRootPath() const override { return "favourites://"; }
+
+ bool OnSelect(int item) override;
+ bool OnAction(const CAction& action) override;
+ bool OnMessage(CGUIMessage& message) override;
+
+ bool Update(const std::string& strDirectory, bool updateFilterPath = true) override;
+
+private:
+ void OnFavouritesEvent(const CFavouritesService::FavouritesUpdated& event);
+ bool MoveItem(int item, int amount);
+ bool RemoveItem(int item);
+};