Merge pull request #26359 from garbear/fix-settings-crop
[xbmc.git] / xbmc / addons / gui / GUIWindowAddonBrowser.cpp
blobcfef6eb0b7540766b821f26c1c03d6ff0ff8bfa2
1 /*
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.
7 */
9 #include "GUIWindowAddonBrowser.h"
11 #include "ContextMenuManager.h"
12 #include "FileItem.h"
13 #include "FileItemList.h"
14 #include "GUIDialogAddonInfo.h"
15 #include "GUIUserMessages.h"
16 #include "LangInfo.h"
17 #include "ServiceBroker.h"
18 #include "URL.h"
19 #include "addons/AddonInstaller.h"
20 #include "addons/AddonManager.h"
21 #include "addons/AddonSystemSettings.h"
22 #include "addons/IAddon.h"
23 #include "addons/RepositoryUpdater.h"
24 #include "addons/addoninfo/AddonType.h"
25 #include "dialogs/GUIDialogBusy.h"
26 #include "dialogs/GUIDialogFileBrowser.h"
27 #include "dialogs/GUIDialogSelect.h"
28 #include "dialogs/GUIDialogYesNo.h"
29 #include "filesystem/AddonsDirectory.h"
30 #include "guilib/GUIComponent.h"
31 #include "guilib/GUIWindowManager.h"
32 #include "guilib/LocalizeStrings.h"
33 #include "input/actions/ActionIDs.h"
34 #include "messaging/helpers/DialogHelper.h"
35 #include "platform/Platform.h"
36 #include "settings/MediaSourceSettings.h"
37 #include "settings/Settings.h"
38 #include "settings/SettingsComponent.h"
39 #include "storage/MediaManager.h"
40 #include "threads/IRunnable.h"
41 #include "utils/StringUtils.h"
42 #include "utils/Variant.h"
44 #include <utility>
46 #define CONTROL_SETTINGS 5
47 #define CONTROL_FOREIGNFILTER 7
48 #define CONTROL_BROKENFILTER 8
49 #define CONTROL_CHECK_FOR_UPDATES 9
51 using namespace ADDON;
52 using namespace XFILE;
54 CGUIWindowAddonBrowser::CGUIWindowAddonBrowser(void)
55 : CGUIMediaWindow(WINDOW_ADDON_BROWSER, "AddonBrowser.xml")
59 CGUIWindowAddonBrowser::~CGUIWindowAddonBrowser() = default;
61 bool CGUIWindowAddonBrowser::OnMessage(CGUIMessage& message)
63 switch (message.GetMessage())
65 case GUI_MSG_WINDOW_DEINIT:
67 CServiceBroker::GetRepositoryUpdater().Events().Unsubscribe(this);
68 CServiceBroker::GetAddonMgr().Events().Unsubscribe(this);
70 if (m_thumbLoader.IsLoading())
71 m_thumbLoader.StopThread();
73 break;
74 case GUI_MSG_WINDOW_INIT:
76 CServiceBroker::GetRepositoryUpdater().Events().Subscribe(this,
77 &CGUIWindowAddonBrowser::OnEvent);
78 CServiceBroker::GetAddonMgr().Events().Subscribe(this, &CGUIWindowAddonBrowser::OnEvent);
80 SetProperties();
82 break;
83 case GUI_MSG_CLICKED:
85 int iControl = message.GetSenderId();
86 if (iControl == CONTROL_FOREIGNFILTER)
88 const std::shared_ptr<CSettings> settings =
89 CServiceBroker::GetSettingsComponent()->GetSettings();
90 settings->ToggleBool(CSettings::SETTING_GENERAL_ADDONFOREIGNFILTER);
91 settings->Save();
92 Refresh();
93 return true;
95 else if (iControl == CONTROL_BROKENFILTER)
97 const std::shared_ptr<CSettings> settings =
98 CServiceBroker::GetSettingsComponent()->GetSettings();
99 settings->ToggleBool(CSettings::SETTING_GENERAL_ADDONBROKENFILTER);
100 settings->Save();
101 Refresh();
102 return true;
104 else if (iControl == CONTROL_CHECK_FOR_UPDATES)
106 CServiceBroker::GetRepositoryUpdater().CheckForUpdates(true);
107 return true;
109 else if (iControl == CONTROL_SETTINGS)
111 CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_SETTINGS_SYSTEM,
112 "addons");
113 return true;
115 else if (m_viewControl.HasControl(iControl)) // list/thumb control
117 // get selected item
118 int iItem = m_viewControl.GetSelectedItem();
119 int iAction = message.GetParam1();
121 // iItem is checked for validity inside these routines
122 if (iAction == ACTION_SHOW_INFO)
124 if (!m_vecItems->Get(iItem)->GetProperty("Addon.ID").empty())
125 return CGUIDialogAddonInfo::ShowForItem((*m_vecItems)[iItem]);
126 return false;
130 break;
131 case GUI_MSG_NOTIFY_ALL:
133 if (message.GetParam1() == GUI_MSG_UPDATE_ITEM && IsActive() &&
134 message.GetNumStringParams() == 1)
135 { // update this item
136 for (int i = 0; i < m_vecItems->Size(); ++i)
138 CFileItemPtr item = m_vecItems->Get(i);
139 if (item->GetProperty("Addon.ID") == message.GetStringParam())
141 UpdateStatus(item);
142 FormatAndSort(*m_vecItems);
143 return true;
147 else if (message.GetParam1() == GUI_MSG_UPDATE && IsActive())
148 SetProperties();
150 break;
151 default:
152 break;
154 return CGUIMediaWindow::OnMessage(message);
157 void CGUIWindowAddonBrowser::SetProperties()
159 auto lastUpdated = CServiceBroker::GetRepositoryUpdater().LastUpdated();
160 SetProperty("Updated", lastUpdated.IsValid() ? lastUpdated.GetAsLocalizedDateTime()
161 : g_localizeStrings.Get(21337));
164 class UpdateAddons : public IRunnable
166 void Run() override
168 for (const auto& addon : CServiceBroker::GetAddonMgr().GetAvailableUpdates())
169 CAddonInstaller::GetInstance().InstallOrUpdate(addon->ID(), BackgroundJob::CHOICE_YES,
170 ModalJob::CHOICE_NO);
174 class UpdateAllowedAddons : public IRunnable
176 void Run() override
178 for (const auto& addon : CServiceBroker::GetAddonMgr().GetAvailableUpdates())
179 if (CServiceBroker::GetAddonMgr().IsAutoUpdateable(addon->ID()))
180 CAddonInstaller::GetInstance().InstallOrUpdate(addon->ID(), BackgroundJob::CHOICE_YES,
181 ModalJob::CHOICE_NO);
185 void CGUIWindowAddonBrowser::OnEvent(const ADDON::CRepositoryUpdater::RepositoryUpdated& event)
187 CGUIMessage msg(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE);
188 CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg);
191 void CGUIWindowAddonBrowser::OnEvent(const ADDON::AddonEvent& event)
193 CGUIMessage msg(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE);
194 CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg);
197 void CGUIWindowAddonBrowser::InstallFromZip()
199 using namespace KODI::MESSAGING::HELPERS;
201 if (!CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
202 CSettings::SETTING_ADDONS_ALLOW_UNKNOWN_SOURCES))
204 if (ShowYesNoDialogText(13106, 36617, 186, 10004) == DialogResponse::CHOICE_YES)
205 CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(
206 WINDOW_SETTINGS_SYSTEM, CSettings::SETTING_ADDONS_ALLOW_UNKNOWN_SOURCES);
208 else
210 // pop up filebrowser to grab an installed folder
211 std::vector<CMediaSource> shares = *CMediaSourceSettings::GetInstance().GetSources("files");
212 CServiceBroker::GetMediaManager().GetLocalDrives(shares);
213 CServiceBroker::GetMediaManager().GetNetworkLocations(shares);
214 std::string path;
215 if (CGUIDialogFileBrowser::ShowAndGetFile(shares, "*.zip", g_localizeStrings.Get(24041), path))
217 CAddonInstaller::GetInstance().InstallFromZip(path);
222 bool CGUIWindowAddonBrowser::OnClick(int iItem, const std::string& player)
224 CFileItemPtr item = m_vecItems->Get(iItem);
225 if (item->GetPath() == "addons://install/")
227 InstallFromZip();
228 return true;
230 if (item->GetPath() == "addons://update_all/")
232 UpdateAddons updater;
233 CGUIDialogBusy::Wait(&updater, 100, true);
234 return true;
236 if (item->GetPath() == "addons://update_allowed/")
238 UpdateAllowedAddons updater;
239 CGUIDialogBusy::Wait(&updater, 100, true);
240 return true;
242 if (!item->m_bIsFolder)
244 // cancel a downloading job
245 if (item->HasProperty("Addon.Downloading"))
247 if (CGUIDialogYesNo::ShowAndGetInput(CVariant{24000}, item->GetProperty("Addon.Name"),
248 CVariant{24066}, CVariant{""}))
250 if (CAddonInstaller::GetInstance().Cancel(item->GetProperty("Addon.ID").asString()))
251 Refresh();
253 return true;
256 CGUIDialogAddonInfo::ShowForItem(item);
257 return true;
259 if (item->IsPath("addons://search/"))
261 Update(item->GetPath());
262 return true;
265 return CGUIMediaWindow::OnClick(iItem, player);
268 void CGUIWindowAddonBrowser::UpdateButtons()
270 const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings();
271 SET_CONTROL_SELECTED(GetID(), CONTROL_FOREIGNFILTER,
272 settings->GetBool(CSettings::SETTING_GENERAL_ADDONFOREIGNFILTER));
273 SET_CONTROL_SELECTED(GetID(), CONTROL_BROKENFILTER,
274 settings->GetBool(CSettings::SETTING_GENERAL_ADDONBROKENFILTER));
275 CONTROL_ENABLE(CONTROL_CHECK_FOR_UPDATES);
276 CONTROL_ENABLE(CONTROL_SETTINGS);
278 bool allowFilter = CAddonsDirectory::IsRepoDirectory(CURL(m_vecItems->GetPath()));
279 CONTROL_ENABLE_ON_CONDITION(CONTROL_FOREIGNFILTER, allowFilter);
280 CONTROL_ENABLE_ON_CONDITION(CONTROL_BROKENFILTER, allowFilter);
282 CGUIMediaWindow::UpdateButtons();
285 static bool IsForeign(const std::string& languages)
287 if (languages.empty())
288 return false;
290 for (const auto& lang : StringUtils::Split(languages, " "))
292 if (lang == "en" || lang == g_langInfo.GetLocale().GetLanguageCode() ||
293 lang == g_langInfo.GetLocale().ToShortString())
294 return false;
296 // for backwards compatibility
297 if (lang == "no" && g_langInfo.GetLocale().ToShortString() == "nb_NO")
298 return false;
300 return true;
303 bool CGUIWindowAddonBrowser::GetDirectory(const std::string& strDirectory, CFileItemList& items)
305 bool result = CGUIMediaWindow::GetDirectory(strDirectory, items);
307 if (result && CAddonsDirectory::IsRepoDirectory(CURL(strDirectory)))
309 const std::shared_ptr<CSettings> settings =
310 CServiceBroker::GetSettingsComponent()->GetSettings();
311 if (settings->GetBool(CSettings::SETTING_GENERAL_ADDONFOREIGNFILTER))
313 int i = 0;
314 while (i < items.Size())
316 auto prop = items[i]->GetProperty("Addon.Language");
317 if (!prop.isNull() && IsForeign(prop.asString()))
318 items.Remove(i);
319 else
320 ++i;
323 if (settings->GetBool(CSettings::SETTING_GENERAL_ADDONBROKENFILTER))
325 for (int i = items.Size() - 1; i >= 0; i--)
327 if (items[i]->GetAddonInfo() &&
328 items[i]->GetAddonInfo()->LifecycleState() == AddonLifecycleState::BROKEN)
330 //check if it's installed
331 AddonPtr addon;
332 if (!CServiceBroker::GetAddonMgr().GetAddon(items[i]->GetProperty("Addon.ID").asString(),
333 addon, OnlyEnabled::CHOICE_YES))
334 items.Remove(i);
340 for (int i = 0; i < items.Size(); ++i)
341 UpdateStatus(items[i]);
343 return result;
346 void CGUIWindowAddonBrowser::UpdateStatus(const CFileItemPtr& item)
348 if (!item || item->m_bIsFolder)
349 return;
351 unsigned int percent;
352 bool downloadFinshed;
353 if (CAddonInstaller::GetInstance().GetProgress(item->GetProperty("Addon.ID").asString(), percent,
354 downloadFinshed))
356 std::string progress = StringUtils::Format(
357 !downloadFinshed ? g_localizeStrings.Get(24042) : g_localizeStrings.Get(24044), percent);
358 item->SetProperty("Addon.Status", progress);
359 item->SetProperty("Addon.Downloading", true);
361 else
362 item->ClearProperty("Addon.Downloading");
365 bool CGUIWindowAddonBrowser::Update(const std::string& strDirectory,
366 bool updateFilterPath /* = true */)
368 if (m_thumbLoader.IsLoading())
369 m_thumbLoader.StopThread();
371 if (!CGUIMediaWindow::Update(strDirectory, updateFilterPath))
372 return false;
374 m_thumbLoader.Load(*m_vecItems);
376 return true;
379 int CGUIWindowAddonBrowser::SelectAddonID(AddonType type,
380 std::string& addonID,
381 bool showNone /* = false */,
382 bool showDetails /* = true */,
383 bool showInstalled /* = true */,
384 bool showInstallable /*= false */,
385 bool showMore /* = true */)
387 std::vector<AddonType> types;
388 types.push_back(type);
389 return SelectAddonID(types, addonID, showNone, showDetails, showInstalled, showInstallable,
390 showMore);
393 int CGUIWindowAddonBrowser::SelectAddonID(AddonType type,
394 std::vector<std::string>& addonIDs,
395 bool showNone /* = false */,
396 bool showDetails /* = true */,
397 bool multipleSelection /* = true */,
398 bool showInstalled /* = true */,
399 bool showInstallable /* = false */,
400 bool showMore /* = true */)
402 std::vector<AddonType> types;
403 types.push_back(type);
404 return SelectAddonID(types, addonIDs, showNone, showDetails, multipleSelection, showInstalled,
405 showInstallable, showMore);
408 int CGUIWindowAddonBrowser::SelectAddonID(const std::vector<AddonType>& types,
409 std::string& addonID,
410 bool showNone /* = false */,
411 bool showDetails /* = true */,
412 bool showInstalled /* = true */,
413 bool showInstallable /* = false */,
414 bool showMore /* = true */)
416 std::vector<std::string> addonIDs;
417 if (!addonID.empty())
418 addonIDs.push_back(addonID);
419 int retval = SelectAddonID(types, addonIDs, showNone, showDetails, false, showInstalled,
420 showInstallable, showMore);
421 if (!addonIDs.empty())
422 addonID = addonIDs.at(0);
423 else
424 addonID = "";
425 return retval;
428 int CGUIWindowAddonBrowser::SelectAddonID(const std::vector<AddonType>& types,
429 std::vector<std::string>& addonIDs,
430 bool showNone /* = false */,
431 bool showDetails /* = true */,
432 bool multipleSelection /* = true */,
433 bool showInstalled /* = true */,
434 bool showInstallable /* = false */,
435 bool showMore /* = true */)
437 // if we shouldn't show neither installed nor installable addons the list will be empty
438 if (!showInstalled && !showInstallable)
439 return -1;
441 // can't show the "Get More" button if we already show installable addons
442 if (showInstallable)
443 showMore = false;
445 CGUIDialogSelect* dialog =
446 CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSelect>(
447 WINDOW_DIALOG_SELECT);
448 if (!dialog)
449 return -1;
451 // get rid of any invalid addon types
452 std::vector<AddonType> validTypes(types.size());
453 std::copy_if(types.begin(), types.end(), validTypes.begin(),
454 [](AddonType type) { return type != AddonType::UNKNOWN; });
456 if (validTypes.empty())
457 return -1;
459 // get all addons to show
460 VECADDONS addons;
461 if (showInstalled)
463 for (std::vector<AddonType>::const_iterator type = validTypes.begin(); type != validTypes.end();
464 ++type)
466 VECADDONS typeAddons;
467 if (*type == AddonType::AUDIO)
468 CAddonsDirectory::GetScriptsAndPlugins("audio", typeAddons);
469 else if (*type == AddonType::EXECUTABLE)
470 CAddonsDirectory::GetScriptsAndPlugins("executable", typeAddons);
471 else if (*type == AddonType::IMAGE)
472 CAddonsDirectory::GetScriptsAndPlugins("image", typeAddons);
473 else if (*type == AddonType::VIDEO)
474 CAddonsDirectory::GetScriptsAndPlugins("video", typeAddons);
475 else if (*type == AddonType::GAME)
476 CAddonsDirectory::GetScriptsAndPlugins("game", typeAddons);
477 else
478 CServiceBroker::GetAddonMgr().GetAddons(typeAddons, *type);
480 addons.insert(addons.end(), typeAddons.begin(), typeAddons.end());
484 if (showInstallable || showMore)
486 VECADDONS installableAddons;
487 if (CServiceBroker::GetAddonMgr().GetInstallableAddons(installableAddons))
489 for (auto addon = installableAddons.begin(); addon != installableAddons.end();)
491 AddonPtr pAddon = *addon;
493 // check if the addon matches one of the provided addon types
494 bool matchesType = false;
495 for (std::vector<AddonType>::const_iterator type = validTypes.begin();
496 type != validTypes.end(); ++type)
498 if (pAddon->HasType(*type))
500 matchesType = true;
501 break;
505 if (matchesType)
507 ++addon;
508 continue;
511 addon = installableAddons.erase(addon);
514 if (showInstallable)
515 addons.insert(addons.end(), installableAddons.begin(), installableAddons.end());
516 else if (showMore)
517 showMore = !installableAddons.empty();
521 if (addons.empty() && !showNone)
522 return -1;
524 // turn the addons into items
525 std::map<std::string, AddonPtr> addonMap;
526 CFileItemList items;
527 for (const auto& addon : addons)
529 const CFileItemPtr item(CAddonsDirectory::FileItemFromAddon(addon, addon->ID()));
531 // Game controllers don't have specific summaries
532 if (addon->Type() != AddonType::GAME_CONTROLLER)
533 item->SetLabel2(addon->Summary());
535 if (!items.Contains(item->GetPath()))
537 items.Add(item);
538 addonMap.insert(std::make_pair(item->GetPath(), addon));
542 if (items.IsEmpty() && !showNone)
543 return -1;
545 std::string heading;
546 for (std::vector<AddonType>::const_iterator type = validTypes.begin(); type != validTypes.end();
547 ++type)
549 if (!heading.empty())
550 heading += ", ";
551 heading += CAddonInfo::TranslateType(*type, true);
554 dialog->SetHeading(CVariant{std::move(heading)});
555 dialog->Reset();
556 dialog->SetUseDetails(showDetails);
558 if (multipleSelection)
560 showNone = false;
561 showMore = false;
562 dialog->EnableButton(true, 186);
564 else if (showMore)
565 dialog->EnableButton(true, 21452);
567 if (showNone)
569 CFileItemPtr item(new CFileItem("", false));
570 item->SetLabel(g_localizeStrings.Get(231));
571 item->SetLabel2(g_localizeStrings.Get(24040));
572 item->SetArt("icon", "DefaultAddonNone.png");
573 item->SetSpecialSort(SortSpecialOnTop);
574 items.Add(item);
576 items.Sort(SortByLabel, SortOrderAscending);
578 if (!addonIDs.empty())
580 for (std::vector<std::string>::const_iterator it = addonIDs.begin(); it != addonIDs.end(); ++it)
582 CFileItemPtr item = items.Get(*it);
583 if (item)
584 item->Select(true);
587 dialog->SetItems(items);
588 dialog->SetMultiSelection(multipleSelection);
589 dialog->Open();
591 // if the "Get More" button has been pressed and we haven't shown the
592 // installable addons so far show a list of installable addons
593 if (showMore && dialog->IsButtonPressed())
594 return SelectAddonID(types, addonIDs, showNone, showDetails, multipleSelection, false, true,
595 false);
597 if (!dialog->IsConfirmed())
598 return 0;
600 addonIDs.clear();
601 for (int i : dialog->GetSelectedItems())
603 const CFileItemPtr& item = items.Get(i);
605 // check if one of the selected addons needs to be installed
606 if (showInstallable)
608 std::map<std::string, AddonPtr>::const_iterator itAddon = addonMap.find(item->GetPath());
609 if (itAddon != addonMap.end())
611 const AddonPtr& addon = itAddon->second;
613 // if the addon isn't installed we need to install it
614 if (!CServiceBroker::GetAddonMgr().IsAddonInstalled(addon->ID()))
616 AddonPtr installedAddon;
617 if (!CAddonInstaller::GetInstance().InstallModal(addon->ID(), installedAddon,
618 InstallModalPrompt::CHOICE_NO))
619 continue;
622 // if the addon is disabled we need to enable it
623 if (CServiceBroker::GetAddonMgr().IsAddonDisabled(addon->ID()))
624 CServiceBroker::GetAddonMgr().EnableAddon(addon->ID());
628 addonIDs.push_back(item->GetPath());
630 return 1;
633 std::string CGUIWindowAddonBrowser::GetStartFolder(const std::string& dir)
635 if (StringUtils::StartsWith(dir, "addons://"))
637 if (StringUtils::StartsWith(dir, "addons://default_binary_addons_source/"))
639 const bool all = CServiceBroker::GetPlatform().SupportsUserInstalledBinaryAddons();
640 std::string startDir = dir;
641 StringUtils::Replace(startDir, "/default_binary_addons_source/", all ? "/all/" : "/user/");
642 return startDir;
644 else
645 return dir;
648 return CGUIMediaWindow::GetStartFolder(dir);