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