1 /*
2  *  Copyright (C) 2013-2018 Team Kodi
3  *  This file is part of Kodi - https://kodi.tv
4  *
5  *  SPDX-License-Identifier: GPL-2.0-or-later
6  *  See LICENSES/README.md for more information.
7  */
8 
9 #include "ContextMenuManager.h"
10 
11 #include "ContextMenuItem.h"
12 #include "ContextMenus.h"
13 #include "ServiceBroker.h"
14 #include "addons/Addon.h"
15 #include "addons/ContextMenuAddon.h"
16 #include "addons/ContextMenus.h"
17 #include "addons/IAddon.h"
18 #include "dialogs/GUIDialogContextMenu.h"
19 #include "favourites/ContextMenus.h"
20 #include "music/ContextMenus.h"
21 #include "pvr/PVRContextMenus.h"
22 #include "utils/log.h"
23 #include "video/ContextMenus.h"
24 
25 #include <iterator>
26 
27 using namespace ADDON;
28 using namespace PVR;
29 
30 const CContextMenuItem CContextMenuManager::MAIN = CContextMenuItem::CreateGroup("", "", "kodi.core.main", "");
31 const CContextMenuItem CContextMenuManager::MANAGE = CContextMenuItem::CreateGroup("", "", "kodi.core.manage", "");
32 
33 
CContextMenuManager(CAddonMgr & addonMgr)34 CContextMenuManager::CContextMenuManager(CAddonMgr& addonMgr)
35   : m_addonMgr(addonMgr) {}
36 
~CContextMenuManager()37 CContextMenuManager::~CContextMenuManager()
38 {
39   Deinit();
40 }
41 
Deinit()42 void CContextMenuManager::Deinit()
43 {
44   CPVRContextMenuManager::GetInstance().Events().Unsubscribe(this);
45   m_addonMgr.Events().Unsubscribe(this);
46   m_items.clear();
47 }
48 
Init()49 void CContextMenuManager::Init()
50 {
51   m_addonMgr.Events().Subscribe(this, &CContextMenuManager::OnEvent);
52   CPVRContextMenuManager::GetInstance().Events().Subscribe(this, &CContextMenuManager::OnPVREvent);
53 
54   CSingleLock lock(m_criticalSection);
55   m_items = {
56       std::make_shared<CONTEXTMENU::CResume>(),
57       std::make_shared<CONTEXTMENU::CPlay>(),
58       std::make_shared<CONTEXTMENU::CPlayAndQueue>(),
59       std::make_shared<CONTEXTMENU::CPlayNext>(),
60       std::make_shared<CONTEXTMENU::CQueue>(),
61       std::make_shared<CONTEXTMENU::CAddonInfo>(),
62       std::make_shared<CONTEXTMENU::CEnableAddon>(),
63       std::make_shared<CONTEXTMENU::CDisableAddon>(),
64       std::make_shared<CONTEXTMENU::CAddonSettings>(),
65       std::make_shared<CONTEXTMENU::CCheckForUpdates>(),
66       std::make_shared<CONTEXTMENU::CEpisodeInfo>(),
67       std::make_shared<CONTEXTMENU::CMovieInfo>(),
68       std::make_shared<CONTEXTMENU::CMusicVideoInfo>(),
69       std::make_shared<CONTEXTMENU::CTVShowInfo>(),
70       std::make_shared<CONTEXTMENU::CAlbumInfo>(),
71       std::make_shared<CONTEXTMENU::CArtistInfo>(),
72       std::make_shared<CONTEXTMENU::CSongInfo>(),
73       std::make_shared<CONTEXTMENU::CMarkWatched>(),
74       std::make_shared<CONTEXTMENU::CMarkUnWatched>(),
75       std::make_shared<CONTEXTMENU::CRemoveResumePoint>(),
76       std::make_shared<CONTEXTMENU::CEjectDisk>(),
77       std::make_shared<CONTEXTMENU::CEjectDrive>(),
78       std::make_shared<CONTEXTMENU::CRemoveFavourite>(),
79       std::make_shared<CONTEXTMENU::CRenameFavourite>(),
80       std::make_shared<CONTEXTMENU::CChooseThumbnailForFavourite>(),
81       std::make_shared<CONTEXTMENU::CAddRemoveFavourite>(),
82   };
83 
84   ReloadAddonItems();
85 
86   const std::vector<std::shared_ptr<IContextMenuItem>> pvrItems(CPVRContextMenuManager::GetInstance().GetMenuItems());
87   for (const auto &item : pvrItems)
88     m_items.emplace_back(item);
89 }
90 
ReloadAddonItems()91 void CContextMenuManager::ReloadAddonItems()
92 {
93   VECADDONS addons;
94   m_addonMgr.GetAddons(addons, ADDON_CONTEXT_ITEM);
95 
96   std::vector<CContextMenuItem> addonItems;
97   for (const auto& addon : addons)
98   {
99     auto items = std::static_pointer_cast<CContextMenuAddon>(addon)->GetItems();
100     for (auto& item : items)
101     {
102       auto it = std::find(addonItems.begin(), addonItems.end(), item);
103       if (it == addonItems.end())
104         addonItems.push_back(item);
105     }
106   }
107 
108   CSingleLock lock(m_criticalSection);
109   m_addonItems = std::move(addonItems);
110 
111   CLog::Log(LOGDEBUG, "ContextMenuManager: addon menus reloaded.");
112 }
113 
OnEvent(const ADDON::AddonEvent & event)114 void CContextMenuManager::OnEvent(const ADDON::AddonEvent& event)
115 {
116   if (typeid(event) == typeid(AddonEvents::ReInstalled) ||
117       typeid(event) == typeid(AddonEvents::UnInstalled))
118   {
119     ReloadAddonItems();
120   }
121   else if (typeid(event) == typeid(AddonEvents::Enabled))
122   {
123     AddonPtr addon;
124     if (m_addonMgr.GetAddon(event.id, addon, ADDON_CONTEXT_ITEM, OnlyEnabled::YES))
125     {
126       CSingleLock lock(m_criticalSection);
127       auto items = std::static_pointer_cast<CContextMenuAddon>(addon)->GetItems();
128       for (auto& item : items)
129       {
130         auto it = std::find(m_addonItems.begin(), m_addonItems.end(), item);
131         if (it == m_addonItems.end())
132           m_addonItems.push_back(item);
133       }
134       CLog::Log(LOGDEBUG, "ContextMenuManager: loaded %s.", event.id.c_str());
135     }
136   }
137   else if (typeid(event) == typeid(AddonEvents::Disabled))
138   {
139     if (m_addonMgr.HasType(event.id, ADDON_CONTEXT_ITEM))
140     {
141       ReloadAddonItems();
142     }
143   }
144 }
145 
OnPVREvent(const PVRContextMenuEvent & event)146 void CContextMenuManager::OnPVREvent(const PVRContextMenuEvent& event)
147 {
148   switch (event.action)
149   {
150     case PVRContextMenuEventAction::ADD_ITEM:
151     {
152       CSingleLock lock(m_criticalSection);
153       m_items.emplace_back(event.item);
154       break;
155     }
156     case PVRContextMenuEventAction::REMOVE_ITEM:
157     {
158       CSingleLock lock(m_criticalSection);
159       auto it = std::find(m_items.begin(), m_items.end(), event.item);
160       if (it != m_items.end())
161         m_items.erase(it);
162       break;
163     }
164 
165     default:
166       break;
167   }
168 }
169 
IsVisible(const CContextMenuItem & menuItem,const CContextMenuItem & root,const CFileItem & fileItem) const170 bool CContextMenuManager::IsVisible(
171   const CContextMenuItem& menuItem, const CContextMenuItem& root, const CFileItem& fileItem) const
172 {
173   if (menuItem.GetLabel(fileItem).empty() || !root.IsParentOf(menuItem))
174     return false;
175 
176   if (menuItem.IsGroup())
177   {
178     CSingleLock lock(m_criticalSection);
179     return std::any_of(m_addonItems.begin(), m_addonItems.end(),
180         [&](const CContextMenuItem& other){ return menuItem.IsParentOf(other) && other.IsVisible(fileItem); });
181   }
182 
183   return menuItem.IsVisible(fileItem);
184 }
185 
GetItems(const CFileItem & fileItem,const CContextMenuItem & root) const186 ContextMenuView CContextMenuManager::GetItems(const CFileItem& fileItem, const CContextMenuItem& root /*= MAIN*/) const
187 {
188   ContextMenuView result;
189   //! @todo implement group support
190   if (&root == &MAIN)
191   {
192     CSingleLock lock(m_criticalSection);
193     std::copy_if(m_items.begin(), m_items.end(), std::back_inserter(result),
194         [&](const std::shared_ptr<IContextMenuItem>& menu){ return menu->IsVisible(fileItem); });
195   }
196   return result;
197 }
198 
GetAddonItems(const CFileItem & fileItem,const CContextMenuItem & root) const199 ContextMenuView CContextMenuManager::GetAddonItems(const CFileItem& fileItem, const CContextMenuItem& root /*= MAIN*/) const
200 {
201   ContextMenuView result;
202   {
203     CSingleLock lock(m_criticalSection);
204     for (const auto& menu : m_addonItems)
205       if (IsVisible(menu, root, fileItem))
206         result.emplace_back(new CContextMenuItem(menu));
207   }
208 
209   if (&root == &MANAGE)
210   {
211     std::sort(result.begin(), result.end(),
212         [&](const ContextMenuView::value_type& lhs, const ContextMenuView::value_type& rhs)
213         {
214           return lhs->GetLabel(fileItem) < rhs->GetLabel(fileItem);
215         }
216     );
217   }
218   return result;
219 }
220 
ShowFor(const CFileItemPtr & fileItem,const CContextMenuItem & root)221 bool CONTEXTMENU::ShowFor(const CFileItemPtr& fileItem, const CContextMenuItem& root)
222 {
223   if (!fileItem)
224     return false;
225 
226   const CContextMenuManager &contextMenuManager = CServiceBroker::GetContextMenuManager();
227 
228   auto menuItems = contextMenuManager.GetItems(*fileItem, root);
229   for (auto&& item : contextMenuManager.GetAddonItems(*fileItem, root))
230     menuItems.emplace_back(std::move(item));
231 
232   if (menuItems.empty())
233     return true;
234 
235   CContextButtons buttons;
236   buttons.reserve(menuItems.size());
237   for (size_t i = 0; i < menuItems.size(); ++i)
238     buttons.Add(i, menuItems[i]->GetLabel(*fileItem));
239 
240   int selected = CGUIDialogContextMenu::Show(buttons);
241   if (selected < 0 || selected >= static_cast<int>(menuItems.size()))
242     return false;
243 
244   return menuItems[selected]->IsGroup() ?
245          ShowFor(fileItem, static_cast<const CContextMenuItem&>(*menuItems[selected])) :
246          menuItems[selected]->Execute(fileItem);
247 }
248 
LoopFrom(const IContextMenuItem & menu,const CFileItemPtr & fileItem)249 bool CONTEXTMENU::LoopFrom(const IContextMenuItem& menu, const CFileItemPtr& fileItem)
250 {
251   if (!fileItem)
252     return false;
253   if (menu.IsGroup())
254     return ShowFor(fileItem, static_cast<const CContextMenuItem&>(menu));
255   return menu.Execute(fileItem);
256 }
257