diff options
Diffstat (limited to 'xbmc/music/dialogs')
-rw-r--r-- | xbmc/music/dialogs/CMakeLists.txt | 13 | ||||
-rw-r--r-- | xbmc/music/dialogs/GUIDialogInfoProviderSettings.cpp | 479 | ||||
-rw-r--r-- | xbmc/music/dialogs/GUIDialogInfoProviderSettings.h | 92 | ||||
-rw-r--r-- | xbmc/music/dialogs/GUIDialogMusicInfo.cpp | 1043 | ||||
-rw-r--r-- | xbmc/music/dialogs/GUIDialogMusicInfo.h | 80 | ||||
-rw-r--r-- | xbmc/music/dialogs/GUIDialogMusicOSD.cpp | 91 | ||||
-rw-r--r-- | xbmc/music/dialogs/GUIDialogMusicOSD.h | 22 | ||||
-rw-r--r-- | xbmc/music/dialogs/GUIDialogSongInfo.cpp | 523 | ||||
-rw-r--r-- | xbmc/music/dialogs/GUIDialogSongInfo.h | 54 | ||||
-rw-r--r-- | xbmc/music/dialogs/GUIDialogVisualisationPresetList.cpp | 98 | ||||
-rw-r--r-- | xbmc/music/dialogs/GUIDialogVisualisationPresetList.h | 32 |
11 files changed, 2527 insertions, 0 deletions
diff --git a/xbmc/music/dialogs/CMakeLists.txt b/xbmc/music/dialogs/CMakeLists.txt new file mode 100644 index 0000000..40b6e90 --- /dev/null +++ b/xbmc/music/dialogs/CMakeLists.txt @@ -0,0 +1,13 @@ +set(SOURCES GUIDialogInfoProviderSettings.cpp + GUIDialogMusicInfo.cpp + GUIDialogMusicOSD.cpp + GUIDialogSongInfo.cpp + GUIDialogVisualisationPresetList.cpp) + +set(HEADERS GUIDialogInfoProviderSettings.h + GUIDialogMusicInfo.h + GUIDialogMusicOSD.h + GUIDialogSongInfo.h + GUIDialogVisualisationPresetList.h) + +core_add_library(music_dialogs) diff --git a/xbmc/music/dialogs/GUIDialogInfoProviderSettings.cpp b/xbmc/music/dialogs/GUIDialogInfoProviderSettings.cpp new file mode 100644 index 0000000..a9e2232 --- /dev/null +++ b/xbmc/music/dialogs/GUIDialogInfoProviderSettings.cpp @@ -0,0 +1,479 @@ +/* + * Copyright (C) 2017-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 "GUIDialogInfoProviderSettings.h" + +#include "ServiceBroker.h" +#include "Util.h" +#include "addons/AddonManager.h" +#include "addons/AddonSystemSettings.h" +#include "addons/addoninfo/AddonType.h" +#include "addons/gui/GUIDialogAddonSettings.h" +#include "addons/gui/GUIWindowAddonBrowser.h" +#include "dialogs/GUIDialogFileBrowser.h" +#include "dialogs/GUIDialogKaiToast.h" +#include "filesystem/AddonsDirectory.h" +#include "filesystem/Directory.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/LocalizeStrings.h" +#include "interfaces/builtins/Builtins.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "settings/lib/Setting.h" +#include "settings/windows/GUIControlSettings.h" +#include "storage/MediaManager.h" +#include "utils/URIUtils.h" +#include "utils/log.h" + +#include <limits.h> +#include <map> +#include <memory> +#include <string> +#include <utility> +#include <vector> + +using namespace ADDON; + +const std::string SETTING_ALBUMSCRAPER_SETTINGS = "albumscrapersettings"; +const std::string SETTING_ARTISTSCRAPER_SETTINGS = "artistscrapersettings"; +const std::string SETTING_APPLYTOITEMS = "applysettingstoitems"; + +CGUIDialogInfoProviderSettings::CGUIDialogInfoProviderSettings() + : CGUIDialogSettingsManualBase(WINDOW_DIALOG_INFOPROVIDER_SETTINGS, "DialogSettings.xml") +{ } + +bool CGUIDialogInfoProviderSettings::Show() +{ + CGUIDialogInfoProviderSettings *dialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogInfoProviderSettings>(WINDOW_DIALOG_INFOPROVIDER_SETTINGS); + if (!dialog) + return false; + + const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings(); + + dialog->m_showSingleScraper = false; + + // Get current default info provider settings from service broker + dialog->m_fetchInfo = settings->GetBool(CSettings::SETTING_MUSICLIBRARY_DOWNLOADINFO); + + ADDON::AddonPtr defaultScraper; + // Get default album scraper (when enabled - can default scraper be disabled??) + if (ADDON::CAddonSystemSettings::GetInstance().GetActive(ADDON::AddonType::SCRAPER_ALBUMS, + defaultScraper)) + { + ADDON::ScraperPtr scraper = std::dynamic_pointer_cast<ADDON::CScraper>(defaultScraper); + dialog->SetAlbumScraper(scraper); + } + + // Get default artist scraper + if (ADDON::CAddonSystemSettings::GetInstance().GetActive(ADDON::AddonType::SCRAPER_ARTISTS, + defaultScraper)) + { + ADDON::ScraperPtr scraper = std::dynamic_pointer_cast<ADDON::CScraper>(defaultScraper); + dialog->SetArtistScraper(scraper); + } + + dialog->m_strArtistInfoPath = settings->GetString(CSettings::SETTING_MUSICLIBRARY_ARTISTSFOLDER); + + dialog->Open(); + + dialog->ResetDefaults(); + return dialog->IsConfirmed(); +} + +int CGUIDialogInfoProviderSettings::Show(ADDON::ScraperPtr& scraper) +{ + CGUIDialogInfoProviderSettings *dialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogInfoProviderSettings>(WINDOW_DIALOG_INFOPROVIDER_SETTINGS); + if (!dialog || !scraper) + return -1; + if (scraper->Content() != CONTENT_ARTISTS && scraper->Content() != CONTENT_ALBUMS) + return -1; + + dialog->m_showSingleScraper = true; + dialog->m_singleScraperType = scraper->Content(); + + if (dialog->m_singleScraperType == CONTENT_ALBUMS) + dialog->SetAlbumScraper(scraper); + else + dialog->SetArtistScraper(scraper); + // toast selected but disabled scrapers + if (CServiceBroker::GetAddonMgr().IsAddonDisabled(scraper->ID())) + CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error, g_localizeStrings.Get(24024), scraper->Name(), 2000, true); + + dialog->Open(); + + bool confirmed = dialog->IsConfirmed(); + unsigned int applyToItems = dialog->m_applyToItems; + if (confirmed) + { + if (dialog->m_singleScraperType == CONTENT_ALBUMS) + scraper = dialog->GetAlbumScraper(); + else + { + scraper = dialog->GetArtistScraper(); + // Save artist information folder (here not in the caller) when applying setting as default for all artists + if (applyToItems == INFOPROVIDERAPPLYOPTIONS::INFOPROVIDER_DEFAULT) + CServiceBroker::GetSettingsComponent()->GetSettings()->SetString(CSettings::SETTING_MUSICLIBRARY_ARTISTSFOLDER, dialog->m_strArtistInfoPath); + } + if (scraper) + scraper->SetPathSettings(dialog->m_singleScraperType, ""); + } + + dialog->ResetDefaults(); + + if (confirmed) + return applyToItems; + else + return -1; +} + +void CGUIDialogInfoProviderSettings::OnInitWindow() +{ + CGUIDialogSettingsManualBase::OnInitWindow(); +} + +void CGUIDialogInfoProviderSettings::OnSettingChanged( + const std::shared_ptr<const CSetting>& setting) +{ + if (setting == nullptr) + return; + + CGUIDialogSettingsManualBase::OnSettingChanged(setting); + + const std::string &settingId = setting->GetId(); + + if (settingId == CSettings::SETTING_MUSICLIBRARY_DOWNLOADINFO) + { + m_fetchInfo = std::static_pointer_cast<const CSettingBool>(setting)->GetValue(); + SetupView(); + SetFocus(CSettings::SETTING_MUSICLIBRARY_DOWNLOADINFO); + } + else if (settingId == CSettings::SETTING_MUSICLIBRARY_ARTISTSFOLDER) + m_strArtistInfoPath = std::static_pointer_cast<const CSettingString>(setting)->GetValue(); + else if (settingId == SETTING_APPLYTOITEMS) + { + m_applyToItems = std::static_pointer_cast<const CSettingInt>(setting)->GetValue(); + SetupView(); + SetFocus(SETTING_APPLYTOITEMS); + } +} + +void CGUIDialogInfoProviderSettings::OnSettingAction(const std::shared_ptr<const CSetting>& setting) +{ + if (setting == nullptr) + return; + + CGUIDialogSettingsManualBase::OnSettingAction(setting); + + const std::string &settingId = setting->GetId(); + + if (settingId == CSettings::SETTING_MUSICLIBRARY_ALBUMSSCRAPER) + { + std::string currentScraperId; + if (m_albumscraper) + currentScraperId = m_albumscraper->ID(); + std::string selectedAddonId = currentScraperId; + + if (CGUIWindowAddonBrowser::SelectAddonID(AddonType::SCRAPER_ALBUMS, selectedAddonId, false) == + 1 && + selectedAddonId != currentScraperId) + { + AddonPtr scraperAddon; + if (CServiceBroker::GetAddonMgr().GetAddon(selectedAddonId, scraperAddon, + OnlyEnabled::CHOICE_YES)) + { + m_albumscraper = std::dynamic_pointer_cast<CScraper>(scraperAddon); + SetupView(); + SetFocus(settingId); + } + else + { + CLog::Log(LOGERROR, "{} - Could not get settings for addon: {}", __FUNCTION__, + selectedAddonId); + } + } + } + else if (settingId == CSettings::SETTING_MUSICLIBRARY_ARTISTSSCRAPER) + { + std::string currentScraperId; + if (m_artistscraper) + currentScraperId = m_artistscraper->ID(); + std::string selectedAddonId = currentScraperId; + + if (CGUIWindowAddonBrowser::SelectAddonID(AddonType::SCRAPER_ARTISTS, selectedAddonId, false) == + 1 && + selectedAddonId != currentScraperId) + { + AddonPtr scraperAddon; + if (CServiceBroker::GetAddonMgr().GetAddon(selectedAddonId, scraperAddon, + OnlyEnabled::CHOICE_YES)) + { + m_artistscraper = std::dynamic_pointer_cast<CScraper>(scraperAddon); + SetupView(); + SetFocus(settingId); + } + else + { + CLog::Log(LOGERROR, "{} - Could not get addon: {}", __FUNCTION__, selectedAddonId); + } + } + } + else if (settingId == SETTING_ALBUMSCRAPER_SETTINGS) + CGUIDialogAddonSettings::ShowForAddon(m_albumscraper, false); + else if (settingId == SETTING_ARTISTSCRAPER_SETTINGS) + CGUIDialogAddonSettings::ShowForAddon(m_artistscraper, false); + else if (settingId == CSettings::SETTING_MUSICLIBRARY_ARTISTSFOLDER) + { + VECSOURCES shares; + CServiceBroker::GetMediaManager().GetLocalDrives(shares); + CServiceBroker::GetMediaManager().GetNetworkLocations(shares); + CServiceBroker::GetMediaManager().GetRemovableDrives(shares); + std::string strDirectory = m_strArtistInfoPath; + if (!strDirectory.empty()) + { + URIUtils::AddSlashAtEnd(strDirectory); + bool bIsSource; + if (CUtil::GetMatchingSource(strDirectory, shares, bIsSource) < 0) // path is outside shares - add it as a separate one + { + CMediaSource share; + share.strName = g_localizeStrings.Get(13278); + share.strPath = strDirectory; + shares.push_back(share); + } + } + else + strDirectory = "default location"; + + if (CGUIDialogFileBrowser::ShowAndGetDirectory(shares, g_localizeStrings.Get(20223), strDirectory, true)) + { + if (!strDirectory.empty()) + { + m_strArtistInfoPath = strDirectory; + SetLabel2(CSettings::SETTING_MUSICLIBRARY_ARTISTSFOLDER, strDirectory); + SetFocus(CSettings::CSettings::SETTING_MUSICLIBRARY_ARTISTSFOLDER); + } + } + } +} + +bool CGUIDialogInfoProviderSettings::Save() +{ + if (m_showSingleScraper) + return true; //Save done by caller of ::Show + + // Save default settings for fetching additional information and art + CLog::Log(LOGINFO, "{} called", __FUNCTION__); + // Save Fetch addiitional info during update + const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings(); + settings->SetBool(CSettings::SETTING_MUSICLIBRARY_DOWNLOADINFO, m_fetchInfo); + // Save default scrapers and addon setting values + settings->SetString(CSettings::SETTING_MUSICLIBRARY_ALBUMSSCRAPER, m_albumscraper->ID()); + m_albumscraper->SaveSettings(); + settings->SetString(CSettings::SETTING_MUSICLIBRARY_ARTISTSSCRAPER, m_artistscraper->ID()); + m_artistscraper->SaveSettings(); + // Save artist information folder + settings->SetString(CSettings::SETTING_MUSICLIBRARY_ARTISTSFOLDER, m_strArtistInfoPath); + settings->Save(); + + return true; +} + +void CGUIDialogInfoProviderSettings::SetupView() +{ + CGUIDialogSettingsManualBase::SetupView(); + + SET_CONTROL_HIDDEN(CONTROL_SETTINGS_CUSTOM_BUTTON); + SET_CONTROL_LABEL(CONTROL_SETTINGS_OKAY_BUTTON, 186); + SET_CONTROL_LABEL(CONTROL_SETTINGS_CANCEL_BUTTON, 222); + + SetLabel2(CSettings::SETTING_MUSICLIBRARY_ARTISTSFOLDER, m_strArtistInfoPath); + + if (!m_showSingleScraper) + { + SetHeading(38330); + if (!m_fetchInfo) + { + ToggleState(CSettings::SETTING_MUSICLIBRARY_ALBUMSSCRAPER, false); + ToggleState(SETTING_ALBUMSCRAPER_SETTINGS, false); + ToggleState(CSettings::SETTING_MUSICLIBRARY_ARTISTSSCRAPER, false); + ToggleState(SETTING_ARTISTSCRAPER_SETTINGS, false); + ToggleState(CSettings::SETTING_MUSICLIBRARY_ARTISTSFOLDER, false); + } + else + { // Album scraper + ToggleState(CSettings::SETTING_MUSICLIBRARY_ALBUMSSCRAPER, true); + if (m_albumscraper && !CServiceBroker::GetAddonMgr().IsAddonDisabled(m_albumscraper->ID())) + { + SetLabel2(CSettings::SETTING_MUSICLIBRARY_ALBUMSSCRAPER, m_albumscraper->Name()); + if (m_albumscraper && m_albumscraper->HasSettings()) + ToggleState(SETTING_ALBUMSCRAPER_SETTINGS, true); + else + ToggleState(SETTING_ALBUMSCRAPER_SETTINGS, false); + } + else + { + SetLabel2(CSettings::SETTING_MUSICLIBRARY_ALBUMSSCRAPER, g_localizeStrings.Get(231)); //Set label2 to "None" + ToggleState(SETTING_ALBUMSCRAPER_SETTINGS, false); + } + // Artist scraper + ToggleState(CSettings::SETTING_MUSICLIBRARY_ARTISTSSCRAPER, true); + if (m_artistscraper && !CServiceBroker::GetAddonMgr().IsAddonDisabled(m_artistscraper->ID())) + { + SetLabel2(CSettings::SETTING_MUSICLIBRARY_ARTISTSSCRAPER, m_artistscraper->Name()); + if (m_artistscraper && m_artistscraper->HasSettings()) + ToggleState(SETTING_ARTISTSCRAPER_SETTINGS, true); + else + ToggleState(SETTING_ARTISTSCRAPER_SETTINGS, false); + } + else + { + SetLabel2(CSettings::SETTING_MUSICLIBRARY_ARTISTSSCRAPER, g_localizeStrings.Get(231)); //Set label2 to "None" + ToggleState(SETTING_ARTISTSCRAPER_SETTINGS, false); + } + // Artist Information Folder + ToggleState(CSettings::SETTING_MUSICLIBRARY_ARTISTSFOLDER, true); + } + } + else if (m_singleScraperType == CONTENT_ALBUMS) + { + SetHeading(38331); + // Album scraper + ToggleState(CSettings::SETTING_MUSICLIBRARY_ALBUMSSCRAPER, true); + if (m_albumscraper && !CServiceBroker::GetAddonMgr().IsAddonDisabled(m_albumscraper->ID())) + { + SetLabel2(CSettings::SETTING_MUSICLIBRARY_ALBUMSSCRAPER, m_albumscraper->Name()); + if (m_albumscraper && m_albumscraper->HasSettings()) + ToggleState(SETTING_ALBUMSCRAPER_SETTINGS, true); + else + ToggleState(SETTING_ALBUMSCRAPER_SETTINGS, false); + } + else + { + SetLabel2(CSettings::SETTING_MUSICLIBRARY_ALBUMSSCRAPER, g_localizeStrings.Get(231)); //Set label2 to "None" + ToggleState(SETTING_ALBUMSCRAPER_SETTINGS, false); + } + } + else + { + SetHeading(38332); + // Artist scraper + ToggleState(CSettings::SETTING_MUSICLIBRARY_ARTISTSSCRAPER, true); + if (m_artistscraper && !CServiceBroker::GetAddonMgr().IsAddonDisabled(m_artistscraper->ID())) + { + SetLabel2(CSettings::SETTING_MUSICLIBRARY_ARTISTSSCRAPER, m_artistscraper->Name()); + if (m_artistscraper && m_artistscraper->HasSettings()) + ToggleState(SETTING_ARTISTSCRAPER_SETTINGS, true); + else + ToggleState(SETTING_ARTISTSCRAPER_SETTINGS, false); + } + else + { + SetLabel2(CSettings::SETTING_MUSICLIBRARY_ARTISTSSCRAPER, g_localizeStrings.Get(231)); //Set label2 to "None" + ToggleState(SETTING_ARTISTSCRAPER_SETTINGS, false); + } + // Artist Information Folder when default settings + ToggleState(CSettings::SETTING_MUSICLIBRARY_ARTISTSFOLDER, m_applyToItems == INFOPROVIDER_DEFAULT); + } +} + +void CGUIDialogInfoProviderSettings::InitializeSettings() +{ + CGUIDialogSettingsManualBase::InitializeSettings(); + + std::shared_ptr<CSettingCategory> category = AddCategory("infoprovidersettings", -1); + if (category == nullptr) + { + CLog::Log(LOGERROR, "{}: unable to setup settings", __FUNCTION__); + return; + } + std::shared_ptr<CSettingGroup> group1 = AddGroup(category); + if (group1 == nullptr) + { + CLog::Log(LOGERROR, "{}: unable to setup settings", __FUNCTION__); + return; + } + + if (!m_showSingleScraper) + { + AddToggle(group1, CSettings::SETTING_MUSICLIBRARY_DOWNLOADINFO, 38333, SettingLevel::Basic, m_fetchInfo); // "Fetch additional information during scan" + } + else + { + TranslatableIntegerSettingOptions entries; + entries.clear(); + if (m_singleScraperType == CONTENT_ALBUMS) + { + entries.push_back(TranslatableIntegerSettingOption(38066, INFOPROVIDER_THISITEM)); + entries.push_back(TranslatableIntegerSettingOption(38067, INFOPROVIDER_ALLVIEW)); + } + else + { + entries.push_back(TranslatableIntegerSettingOption(38064, INFOPROVIDER_THISITEM)); + entries.push_back(TranslatableIntegerSettingOption(38065, INFOPROVIDER_ALLVIEW)); + } + entries.push_back(TranslatableIntegerSettingOption(38063, INFOPROVIDER_DEFAULT)); + AddList(group1, SETTING_APPLYTOITEMS, 38338, SettingLevel::Basic, m_applyToItems, entries, 38339); // "Apply settings to" + } + + std::shared_ptr<CSettingGroup> group = AddGroup(category, 38337); + if (group == nullptr) + { + CLog::Log(LOGERROR, "{}: unable to setup settings", __FUNCTION__); + return; + } + std::shared_ptr<CSettingAction> subsetting; + if (!m_showSingleScraper || m_singleScraperType == CONTENT_ALBUMS) + { + AddButton(group, CSettings::SETTING_MUSICLIBRARY_ALBUMSSCRAPER, 38334, SettingLevel::Basic); //Provider for album information + subsetting = AddButton(group, SETTING_ALBUMSCRAPER_SETTINGS, 10004, SettingLevel::Basic); //"settings" + if (subsetting) + subsetting->SetParent(CSettings::SETTING_MUSICLIBRARY_ALBUMSSCRAPER); + } + if (!m_showSingleScraper || m_singleScraperType == CONTENT_ARTISTS) + { + AddButton(group, CSettings::SETTING_MUSICLIBRARY_ARTISTSSCRAPER, 38335, SettingLevel::Basic); //Provider for artist information + subsetting = AddButton(group, SETTING_ARTISTSCRAPER_SETTINGS, 10004, SettingLevel::Basic); //"settings" + if (subsetting) + subsetting->SetParent(CSettings::SETTING_MUSICLIBRARY_ARTISTSSCRAPER); + + AddButton(group, CSettings::SETTING_MUSICLIBRARY_ARTISTSFOLDER, 38336, SettingLevel::Basic); + } +} + +void CGUIDialogInfoProviderSettings::SetLabel2(const std::string &settingid, const std::string &label) +{ + BaseSettingControlPtr settingControl = GetSettingControl(settingid); + if (settingControl != NULL && settingControl->GetControl() != NULL) + SET_CONTROL_LABEL2(settingControl->GetID(), label); +} + +void CGUIDialogInfoProviderSettings::ToggleState(const std::string &settingid, bool enabled) +{ + BaseSettingControlPtr settingControl = GetSettingControl(settingid); + if (settingControl != NULL && settingControl->GetControl() != NULL) + { + if (enabled) + CONTROL_ENABLE(settingControl->GetID()); + else + CONTROL_DISABLE(settingControl->GetID()); + } +} + +void CGUIDialogInfoProviderSettings::SetFocus(const std::string &settingid) +{ + BaseSettingControlPtr settingControl = GetSettingControl(settingid); + if (settingControl != NULL && settingControl->GetControl() != NULL) + SET_CONTROL_FOCUS(settingControl->GetID(), 0); +} + +void CGUIDialogInfoProviderSettings::ResetDefaults() +{ + m_showSingleScraper = false; + m_singleScraperType = CONTENT_NONE; + m_applyToItems = INFOPROVIDER_THISITEM; +} diff --git a/xbmc/music/dialogs/GUIDialogInfoProviderSettings.h b/xbmc/music/dialogs/GUIDialogInfoProviderSettings.h new file mode 100644 index 0000000..56a33b5 --- /dev/null +++ b/xbmc/music/dialogs/GUIDialogInfoProviderSettings.h @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2017-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 "addons/Addon.h" +#include "addons/Scraper.h" +#include "settings/dialogs/GUIDialogSettingsManualBase.h" + +#include <map> +#include <utility> + +class CFileItemList; + +// Enumeration of what combination of items to apply the scraper settings +enum INFOPROVIDERAPPLYOPTIONS +{ + INFOPROVIDER_DEFAULT = 0x0000, + INFOPROVIDER_ALLVIEW = 0x0001, + INFOPROVIDER_THISITEM = 0x0002 +}; + +class CGUIDialogInfoProviderSettings : public CGUIDialogSettingsManualBase +{ +public: + CGUIDialogInfoProviderSettings(); + + // specialization of CGUIWindow + bool HasListItems() const override { return true; } + + const ADDON::ScraperPtr& GetAlbumScraper() const { return m_albumscraper; } + void SetAlbumScraper(ADDON::ScraperPtr scraper) { m_albumscraper = std::move(scraper); } + const ADDON::ScraperPtr& GetArtistScraper() const { return m_artistscraper; } + void SetArtistScraper(ADDON::ScraperPtr scraper) { m_artistscraper = std::move(scraper); } + + /*! \brief Show dialog to change information provider for either artists or albums (not both). + Has a list to select how settings are to be applied - as system default, to just current item or to all the filtered items on the node. + This does not save the settings itself, that is left to the caller + \param scraper [in/out] the selected scraper addon and settings. Scraper content must be artists or albums. + \return 0 settings apply as system default, 1 to all items on node, 2 to just the selected item or -1 if dialog cancelled or error occurs + */ + static int Show(ADDON::ScraperPtr& scraper); + + /*! \brief Show dialog to change the music scraping settings including default information providers for both artists or albums. + This saves the settings when the dialog is confirmed. + \return true if the dialog is confirmed, false otherwise + */ + static bool Show(); + +protected: + // specializations of CGUIWindow + void OnInitWindow() override; + + // implementations of ISettingCallback + void OnSettingChanged(const std::shared_ptr<const CSetting>& setting) override; + void OnSettingAction(const std::shared_ptr<const CSetting>& setting) override; + + // specialization of CGUIDialogSettingsBase + bool AllowResettingSettings() const override { return false; } + bool Save() override; + void SetupView() override; + + // specialization of CGUIDialogSettingsManualBase + void InitializeSettings() override; + +private: + void SetLabel2(const std::string &settingid, const std::string &label); + void ToggleState(const std::string &settingid, bool enabled); + using CGUIDialogSettingsManualBase::SetFocus; + void SetFocus(const std::string &settingid); + void ResetDefaults(); + + /*! + * @brief The currently selected album scraper + */ + ADDON::ScraperPtr m_albumscraper; + /*! + * @brief The currently selected artist scraper + */ + ADDON::ScraperPtr m_artistscraper; + + std::string m_strArtistInfoPath; + bool m_showSingleScraper = false; + CONTENT_TYPE m_singleScraperType = CONTENT_NONE; + bool m_fetchInfo; + unsigned int m_applyToItems = INFOPROVIDER_THISITEM; +}; diff --git a/xbmc/music/dialogs/GUIDialogMusicInfo.cpp b/xbmc/music/dialogs/GUIDialogMusicInfo.cpp new file mode 100644 index 0000000..a4b282a --- /dev/null +++ b/xbmc/music/dialogs/GUIDialogMusicInfo.cpp @@ -0,0 +1,1043 @@ +/* + * 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 "GUIDialogMusicInfo.h" + +#include "FileItem.h" +#include "GUIPassword.h" +#include "GUIUserMessages.h" +#include "ServiceBroker.h" +#include "TextureCache.h" +#include "URL.h" +#include "dialogs/GUIDialogBusy.h" +#include "dialogs/GUIDialogFileBrowser.h" +#include "dialogs/GUIDialogProgress.h" +#include "filesystem/Directory.h" +#include "filesystem/MusicDatabaseDirectory/QueryParams.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/LocalizeStrings.h" +#include "input/Key.h" +#include "messaging/helpers/DialogOKHelper.h" +#include "music/MusicDatabase.h" +#include "music/MusicLibraryQueue.h" +#include "music/MusicThumbLoader.h" +#include "music/MusicUtils.h" +#include "music/dialogs/GUIDialogSongInfo.h" +#include "music/infoscanner/MusicInfoScanner.h" +#include "music/tags/MusicInfoTag.h" +#include "music/windows/GUIWindowMusicBase.h" +#include "profiles/ProfileManager.h" +#include "settings/MediaSourceSettings.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "storage/MediaManager.h" +#include "utils/FileExtensionProvider.h" +#include "utils/FileUtils.h" +#include "utils/ProgressJob.h" +#include "utils/StringUtils.h" +#include "utils/URIUtils.h" + +using namespace XFILE; +using namespace MUSIC_INFO; +using namespace MUSICDATABASEDIRECTORY; +using namespace KODI::MESSAGING; + +#define CONTROL_BTN_REFRESH 6 +#define CONTROL_USERRATING 7 +#define CONTROL_BTN_PLAY 8 +#define CONTROL_BTN_GET_THUMB 10 +#define CONTROL_ARTISTINFO 12 + +#define CONTROL_LIST 50 + +#define TIME_TO_BUSY_DIALOG 500 + +class CGetInfoJob : public CJob +{ +public: + ~CGetInfoJob(void) override = default; + + // Fetch full album/artist information including art types list + bool DoWork() override + { + CGUIDialogMusicInfo *dialog = CServiceBroker::GetGUI()->GetWindowManager(). + GetWindow<CGUIDialogMusicInfo>(WINDOW_DIALOG_MUSIC_INFO); + if (!dialog) + return false; + if (dialog->IsCancelled()) + return false; + CFileItemPtr m_item = dialog->GetCurrentListItem(); + CMusicInfoTag& tag = *m_item->GetMusicInfoTag(); + + CMusicDatabase database; + database.Open(); + // May only have partially populated music item, so fetch all artist or album data from db + if (tag.GetType() == MediaTypeArtist) + { + int artistId = tag.GetDatabaseId(); + CArtist artist; + if (!database.GetArtist(artistId, artist)) + return false; + tag.SetArtist(artist); + CMusicDatabase::SetPropertiesFromArtist(*m_item, artist); + m_item->SetLabel(artist.strArtist); + + // Get artist folder where local art could be found + // Get the *name* of the folder for this artist within the Artist Info folder (may not exist). + // If there is no Artist Info folder specified in settings this will be blank + database.GetArtistPath(artist, artist.strPath); + // Get the old location for those album artists with a unique folder (local to music files) + // If there is no folder for the artist and *only* the artist this will be blank + std::string oldartistpath; + bool oldpathfound = database.GetOldArtistPath(artist.idArtist, oldartistpath); + + // Set up path for *item folder when browsing for art, by default this is + // in the Artist Info Folder (when it exists), but could end up blank + std::string artistItemPath = artist.strPath; + if (!CDirectory::Exists(artistItemPath)) + { + // Fall back local to music files (historic location for those album artists with a unique folder) + // although there may not be such a unique folder for the arist + if (oldpathfound) + artistItemPath = oldartistpath; + else + // Fall back further to browse the Artist Info Folder itself + artistItemPath = CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_MUSICLIBRARY_ARTISTSFOLDER); + } + m_item->SetPath(artistItemPath); + + // Store info as CArtist as well as item properties + dialog->SetArtist(artist, oldartistpath); + + // Fetch artist discography as scraped from online sources, but always + // include all the albums in the music library + dialog->SetDiscography(database); + } + else + { + // tag.GetType == MediaTypeAlbum + int albumId = tag.GetDatabaseId(); + CAlbum album; + if (!database.GetAlbum(albumId, album)) + return false; + tag.SetAlbum(album); + CMusicDatabase::SetPropertiesFromAlbum(*m_item, album); + + // Get album folder where local art could be found + database.GetAlbumPath(albumId, album.strPath); + // Set up path for *item folder when browsing for art + m_item->SetPath(album.strPath); + // Store info as CAlbum as well as item properties + dialog->SetAlbum(album, album.strPath); + + // Set the list of songs and related art + dialog->SetSongs(album.songs); + } + database.Close(); + + /* + Load current art (to CGUIListItem.m_art) + For albums this includes related artist(s) art and artist fanart set as + fallback album fanart. + Clear item art first to ensure fresh not cached/partial art + */ + m_item->ClearArt(); + CMusicThumbLoader loader; + loader.LoadItem(m_item.get()); + + // Fill vector of possible art types with current art, when it exists, + // for display on the art type selection dialog + CFileItemList artlist; + MUSIC_UTILS::FillArtTypesList(*m_item, artlist); + dialog->SetArtTypeList(artlist); + if (dialog->IsCancelled()) + return false; + + // Tell waiting MusicDialog that job is complete + dialog->FetchComplete(); + + return true; + } +}; + +class CSetUserratingJob : public CJob +{ + int idAlbum; + int iUserrating; +public: + CSetUserratingJob(int albumId, int userrating) : + idAlbum(albumId), + iUserrating(userrating) + { } + + ~CSetUserratingJob(void) override = default; + + bool DoWork(void) override + { + // Asynchronously update userrating in library + CMusicDatabase db; + if (db.Open()) + { + db.SetAlbumUserrating(idAlbum, iUserrating); + db.Close(); + } + + return true; + } +}; + +class CRefreshInfoJob : public CProgressJob +{ +public: + CRefreshInfoJob(CGUIDialogProgress* progressDialog) + : CProgressJob(nullptr) + { + if (progressDialog) + SetProgressIndicators(nullptr, progressDialog); + SetAutoClose(true); + } + + ~CRefreshInfoJob(void) override = default; + + // Refresh album/artist information including art types list + bool DoWork() override + { + CGUIDialogMusicInfo *dialog = CServiceBroker::GetGUI()->GetWindowManager(). + GetWindow<CGUIDialogMusicInfo>(WINDOW_DIALOG_MUSIC_INFO); + if (!dialog) + return false; + if (dialog->IsCancelled()) + return false; + CFileItemPtr m_item = dialog->GetCurrentListItem(); + CMusicInfoTag& tag = *m_item->GetMusicInfoTag(); + CArtist& m_artist = dialog->GetArtist(); + CAlbum& m_album = dialog->GetAlbum(); + + CGUIDialogProgress* dlgProgress = GetProgressDialog(); + CMusicDatabase database; + database.Open(); + if (tag.GetType() == MediaTypeArtist) + { + ADDON::ScraperPtr scraper; + if (!database.GetScraper(m_artist.idArtist, CONTENT_ARTISTS, scraper)) + return false; + + if (dlgProgress->IsCanceled()) + return false; + database.ClearArtistLastScrapedTime(m_artist.idArtist); + + if (dlgProgress->IsCanceled()) + return false; + CMusicInfoScanner scanner; + if (scanner.UpdateArtistInfo(m_artist, scraper, true, dlgProgress) != CInfoScanner::INFO_ADDED) + return false; + else + // Tell info dialog, so can show message + dialog->SetScrapedInfo(true); + + if (dlgProgress->IsCanceled()) + return false; + //That changed DB and m_artist, now update dialog item with new info and art + tag.SetArtist(m_artist); + CMusicDatabase::SetPropertiesFromArtist(*m_item, m_artist); + + // Fetch artist discography as scraped from online sources, but always + // include all the albums in the music library + dialog->SetDiscography(database); + } + else + { + // tag.GetType == MediaTypeAlbum + ADDON::ScraperPtr scraper; + if (!database.GetScraper(m_album.idAlbum, CONTENT_ALBUMS, scraper)) + return false; + + if (dlgProgress->IsCanceled()) + return false; + database.ClearAlbumLastScrapedTime(m_album.idAlbum); + + if (dlgProgress->IsCanceled()) + return false; + CMusicInfoScanner scanner; + if (scanner.UpdateAlbumInfo(m_album, scraper, true, GetProgressDialog()) != CInfoScanner::INFO_ADDED) + return false; + else + // Tell info dialog, so can show message + dialog->SetScrapedInfo(true); + + if (dlgProgress->IsCanceled()) + return false; + //That changed DB and m_album, now update dialog item with new info and art + // Album songs are unchanged by refresh (even with Musicbrainz sync?) + tag.SetAlbum(m_album); + CMusicDatabase::SetPropertiesFromAlbum(*m_item, m_album); + + // Set the list of songs and related art + dialog->SetSongs(m_album.songs); + } + database.Close(); + + if (dlgProgress->IsCanceled()) + return false; + /* + Load current art (to CGUIListItem.m_art) + For albums this includes related artist(s) art and artist fanart set as + fallback album fanart. + Clear item art first to ensure fresh not cached/partial art + */ + m_item->ClearArt(); + CMusicThumbLoader loader; + loader.LoadItem(m_item.get()); + + if (dlgProgress->IsCanceled()) + return false; + // Fill vector of possible art types with current art, when it exists, + // for display on the art type selection dialog + CFileItemList artlist; + MUSIC_UTILS::FillArtTypesList(*m_item, artlist); + dialog->SetArtTypeList(artlist); + if (dialog->IsCancelled()) + return false; + + // Tell waiting MusicDialog that job is complete + MarkFinished(); + return true; + } +}; + +CGUIDialogMusicInfo::CGUIDialogMusicInfo(void) + : CGUIDialog(WINDOW_DIALOG_MUSIC_INFO, "DialogMusicInfo.xml"), + m_albumSongs(new CFileItemList), + m_item(new CFileItem), + m_artTypeList(new CFileItemList) +{ + m_loadType = KEEP_IN_MEMORY; +} + +CGUIDialogMusicInfo::~CGUIDialogMusicInfo(void) +{ +} + +bool CGUIDialogMusicInfo::OnMessage(CGUIMessage& message) +{ + switch ( message.GetMessage() ) + { + case GUI_MSG_WINDOW_DEINIT: + { + m_artTypeList->Clear(); + // For albums update user rating if it has changed + if (!m_bArtistInfo && m_startUserrating != m_item->GetMusicInfoTag()->GetUserrating()) + { + m_hasUpdatedUserrating = true; + + // Asynchronously update song userrating in library + CSetUserratingJob *job = new CSetUserratingJob(m_item->GetMusicInfoTag()->GetAlbumId(), + m_item->GetMusicInfoTag()->GetUserrating()); + CServiceBroker::GetJobManager()->AddJob(job, nullptr); + } + if (m_hasRefreshed || m_hasUpdatedUserrating) + { + // Send a message to all windows to tell them to update the item. + // This communicates changes to the music lib window. + // The music lib window item is updated to but changes to the rating when it is the sort + // do not show on screen until refresh() that fetches the list from scratch, sorts etc. + CGUIMessage msg(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_ITEM, 0, m_item); + CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg); + } + + CGUIMessage msg(GUI_MSG_LABEL_RESET, GetID(), CONTROL_LIST); + OnMessage(msg); + m_albumSongs->Clear(); + } + break; + + case GUI_MSG_WINDOW_INIT: + { + CGUIDialog::OnMessage(message); + Update(); + m_cancelled = false; + return true; + } + break; + + + case GUI_MSG_CLICKED: + { + int iControl = message.GetSenderId(); + if (iControl == CONTROL_USERRATING) + { + OnSetUserrating(); + } + else if (iControl == CONTROL_BTN_REFRESH) + { + RefreshInfo(); + return true; + } + else if (iControl == CONTROL_BTN_GET_THUMB) + { + OnGetArt(); + return true; + } + else if (iControl == CONTROL_ARTISTINFO) + { + if (!m_bArtistInfo) + OnArtistInfo(m_album.artistCredits[0].GetArtistId()); + return true; + } + else if (iControl == CONTROL_LIST) + { + int iAction = message.GetParam1(); + if (m_bArtistInfo && (ACTION_SELECT_ITEM == iAction || ACTION_MOUSE_LEFT_CLICK == iAction)) + { + CGUIMessage msg(GUI_MSG_ITEM_SELECTED, GetID(), iControl); + CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg); + int iItem = msg.GetParam1(); + int id = -1; + if (iItem >= 0 && iItem < m_albumSongs->Size()) + id = m_albumSongs->Get(iItem)->GetMusicInfoTag()->GetDatabaseId(); + if (id > 0) + { + OnAlbumInfo(id); + return true; + } + } + } + else if (iControl == CONTROL_BTN_PLAY) + { + if (m_album.idAlbum >= 0) + { + // Play album + const std::string path = StringUtils::Format("musicdb://albums/{}", m_album.idAlbum); + OnPlayItem(std::make_shared<CFileItem>(path, m_album)); + return true; + } + else + { + CGUIMessage msg(GUI_MSG_ITEM_SELECTED, GetID(), iControl); + CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg); + const int iItem = msg.GetParam1(); + if (iItem >= 0 && iItem < m_albumSongs->Size()) + { + // Play selected song + OnPlayItem(m_albumSongs->Get(iItem)); + return true; + } + } + return false; + } + } + break; + } + + return CGUIDialog::OnMessage(message); +} + +bool CGUIDialogMusicInfo::OnAction(const CAction &action) +{ + int userrating = m_item->GetMusicInfoTag()->GetUserrating(); + if (action.GetID() == ACTION_INCREASE_RATING) + { + SetUserrating(userrating + 1); + return true; + } + else if (action.GetID() == ACTION_DECREASE_RATING) + { + SetUserrating(userrating - 1); + return true; + } + else if (action.GetID() == ACTION_SHOW_INFO) + { + Close(); + return true; + } + return CGUIDialog::OnAction(action); +} + +bool CGUIDialogMusicInfo::SetItem(CFileItem* item) +{ + *m_item = *item; + m_event.Reset(); + m_cancelled = false; // Happens before win_init + + // In a separate job fetch info and fill list of art types. + int jobid = + CServiceBroker::GetJobManager()->AddJob(new CGetInfoJob(), nullptr, CJob::PRIORITY_LOW); + + // Wait to get all data before show, allowing user to cancel if fetch is slow + if (!CGUIDialogBusy::WaitOnEvent(m_event, TIME_TO_BUSY_DIALOG)) + { + // Cancel job still waiting in queue (unlikely) + CServiceBroker::GetJobManager()->CancelJob(jobid); + // Flag to stop job already in progress + m_cancelled = true; + return false; + } + + return true; +} + +void CGUIDialogMusicInfo::SetAlbum(const CAlbum& album, const std::string &path) +{ + m_album = album; + m_item->SetPath(album.strPath); + + m_startUserrating = m_album.iUserrating; + m_fallbackartpath.clear(); + m_bArtistInfo = false; + m_hasUpdatedUserrating = false; + m_hasRefreshed = false; +} + +void CGUIDialogMusicInfo::SetArtist(const CArtist& artist, const std::string &path) +{ + m_artist = artist; + m_fallbackartpath = path; + m_bArtistInfo = true; + m_hasRefreshed = false; +} + +void CGUIDialogMusicInfo::SetSongs(const VECSONGS &songs) const +{ + m_albumSongs->Clear(); + CMusicThumbLoader loader; + for (unsigned int i = 0; i < songs.size(); i++) + { + const CSong& song = songs[i]; + CFileItemPtr item(new CFileItem(song)); + // Load the song art and related artist(s) (that may be different from album artist) art + loader.LoadItem(item.get()); + m_albumSongs->Add(item); + } +} + +void CGUIDialogMusicInfo::SetDiscography(CMusicDatabase& database) const +{ + m_albumSongs->Clear(); + database.GetArtistDiscography(m_artist.idArtist, *m_albumSongs); + CMusicThumbLoader loader; + for (const auto& item : *m_albumSongs) + { + // Load all the album art and related artist(s) art (could be other collaborating artists) + loader.LoadItem(item.get()); + if (item->GetMusicInfoTag()->GetDatabaseId() == -1) + item->SetArt("thumb", "DefaultAlbumCover.png"); + } +} + +void CGUIDialogMusicInfo::Update() +{ + if (m_bArtistInfo) + { + SET_CONTROL_HIDDEN(CONTROL_ARTISTINFO); + SET_CONTROL_HIDDEN(CONTROL_USERRATING); + + CGUIMessage message(GUI_MSG_LABEL_BIND, GetID(), CONTROL_LIST, 0, 0, m_albumSongs.get()); + OnMessage(message); + + } + else + { + SET_CONTROL_VISIBLE(CONTROL_ARTISTINFO); + SET_CONTROL_VISIBLE(CONTROL_USERRATING); + + CGUIMessage message(GUI_MSG_LABEL_BIND, GetID(), CONTROL_LIST, 0, 0, m_albumSongs.get()); + OnMessage(message); + + } + + const std::shared_ptr<CProfileManager> profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager(); + + // Disable the Choose Art button if the user isn't allowed it + CONTROL_ENABLE_ON_CONDITION(CONTROL_BTN_GET_THUMB, + profileManager->GetCurrentProfile().canWriteDatabases() || g_passwordManager.bMasterUser); +} + +void CGUIDialogMusicInfo::SetLabel(int iControl, const std::string& strLabel) +{ + if (strLabel.empty()) + { + SET_CONTROL_LABEL(iControl, 416); + } + else + { + SET_CONTROL_LABEL(iControl, strLabel); + } +} + +void CGUIDialogMusicInfo::OnInitWindow() +{ + SET_CONTROL_LABEL(CONTROL_BTN_REFRESH, 184); + SET_CONTROL_LABEL(CONTROL_USERRATING, 38023); + SET_CONTROL_LABEL(CONTROL_BTN_GET_THUMB, 13511); + SET_CONTROL_LABEL(CONTROL_ARTISTINFO, 21891); + SET_CONTROL_LABEL(CONTROL_BTN_PLAY, 208); + + if (m_bArtistInfo) + { + SET_CONTROL_HIDDEN(CONTROL_ARTISTINFO); + SET_CONTROL_HIDDEN(CONTROL_USERRATING); + SET_CONTROL_HIDDEN(CONTROL_BTN_PLAY); + } + CGUIDialog::OnInitWindow(); +} + +void CGUIDialogMusicInfo::FetchComplete() +{ + //Trigger the event to indicate data has been fetched + m_event.Set(); +} + + +void CGUIDialogMusicInfo::RefreshInfo() +{ + // Double check we have permission (button should be hidden when not) + const std::shared_ptr<CProfileManager> profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager(); + if (!profileManager->GetCurrentProfile().canWriteDatabases() && !g_passwordManager.bMasterUser) + return; + + // Check if scanning + if (CMusicLibraryQueue::GetInstance().IsScanningLibrary()) + { + HELPERS::ShowOKDialogText(CVariant{ 189 }, CVariant{ 14057 }); + return; + } + + CGUIDialogProgress* dlgProgress = CServiceBroker::GetGUI()->GetWindowManager(). + GetWindow<CGUIDialogProgress>(WINDOW_DIALOG_PROGRESS); + if (!dlgProgress) + return; + + if (m_bArtistInfo) + { // Show dialog box indicating we're searching for the artist + dlgProgress->SetHeading(CVariant{ 21889 }); + dlgProgress->SetLine(0, CVariant{ m_artist.strArtist }); + dlgProgress->SetLine(1, CVariant{ "" }); + dlgProgress->SetLine(2, CVariant{ "" }); + } + else + { // Show dialog box indicating we're searching for the album + dlgProgress->SetHeading(CVariant{ 185 }); + dlgProgress->SetLine(0, CVariant{ m_album.strAlbum }); + dlgProgress->SetLine(1, CVariant{ m_album.strArtistDesc }); + dlgProgress->SetLine(2, CVariant{ "" }); + } + dlgProgress->Open(); + + SetScrapedInfo(false); + // Start separate job to scrape info and fill list of art types. + CServiceBroker::GetJobManager()->AddJob(new CRefreshInfoJob(dlgProgress), nullptr, + CJob::PRIORITY_HIGH); + + // Wait for refresh to complete or be canceled, but render every 10ms so that the + // pointer movements works on dialog even when job is reporting progress infrequently + if (dlgProgress) + dlgProgress->Wait(10); + + if (dlgProgress->IsCanceled()) + { + return; + } + + // Show message when scraper was unsuccessful + if (!HasScrapedInfo()) + { + if (m_bArtistInfo) + HELPERS::ShowOKDialogText(CVariant{ 21889 }, CVariant{ 20199 }); + else + HELPERS::ShowOKDialogText(CVariant{ 185 }, CVariant{ 500 }); + return; + } + + // Show new values on screen + Update(); + m_hasRefreshed = true; + + if (dlgProgress) + dlgProgress->Close(); +} + +void CGUIDialogMusicInfo::SetUserrating(int userrating) const +{ + userrating = std::max(userrating, 0); + userrating = std::min(userrating, 10); + if (userrating != m_item->GetMusicInfoTag()->GetUserrating()) + { + m_item->GetMusicInfoTag()->SetUserrating(userrating); + } +} + +void CGUIDialogMusicInfo::OnAlbumInfo(int id) +{ + // Switch to show album info for given album ID + // Close current (artist) dialog to save art changes + Close(true); + ShowForAlbum(id); +} + +void CGUIDialogMusicInfo::OnArtistInfo(int id) +{ + // Switch to show artist info for given artist ID + // Close current (album) dialog to save art and rating changes + Close(true); + ShowForArtist(id); +} + +CFileItemPtr CGUIDialogMusicInfo::GetCurrentListItem(int offset) +{ + return m_item; +} + +std::string CGUIDialogMusicInfo::GetContent() +{ + if (m_item->GetMusicInfoTag()->GetType() == MediaTypeArtist) + return "artists"; + else + return "albums"; +} + +void CGUIDialogMusicInfo::AddItemPathToFileBrowserSources(VECSOURCES &sources, const CFileItem &item) +{ + std::string itemDir; + std::string artistFolder; + + itemDir = item.GetPath(); + if (item.HasMusicInfoTag()) + { + if (item.GetMusicInfoTag()->GetType() == MediaTypeSong) + itemDir = URIUtils::GetParentPath(item.GetMusicInfoTag()->GetURL()); + + // For artist add Artist Info Folder path to browser sources + if (item.GetMusicInfoTag()->GetType() == MediaTypeArtist) + { + artistFolder = CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_MUSICLIBRARY_ARTISTSFOLDER); + if (!artistFolder.empty() && artistFolder.compare(itemDir) == 0) + itemDir.clear(); // skip *item when artist not have a unique path + } + } + // Add "*Item folder" path to file browser sources + if (!itemDir.empty() && CDirectory::Exists(itemDir)) + { + CMediaSource itemSource; + itemSource.strName = g_localizeStrings.Get(36041); + itemSource.strPath = itemDir; + sources.push_back(itemSource); + } + + // For artist add Artist Info Folder path to browser sources + if (!artistFolder.empty() && CDirectory::Exists(artistFolder)) + { + CMediaSource itemSource; + itemSource.strName = "* " + g_localizeStrings.Get(20223); + itemSource.strPath = artistFolder; + sources.push_back(itemSource); + } +} + +void CGUIDialogMusicInfo::SetArtTypeList(CFileItemList& artlist) +{ + m_artTypeList->Clear(); + m_artTypeList->Copy(artlist); +} + +/* +Allow user to choose artwork for the artist or album +For each type of art the options are: +1. Current art +2. Local art (thumb found by filename) +3. Remote art (scraped list of urls from online sources e.g. fanart.tv) +5. Embedded art (@todo) +6. None +*/ +void CGUIDialogMusicInfo::OnGetArt() +{ + std::string type = MUSIC_UTILS::ShowSelectArtTypeDialog(*m_artTypeList); + if (type.empty()) + return; // Cancelled + + CFileItemList items; + CGUIListItem::ArtMap primeArt = m_item->GetArt(); // art without fallbacks + bool bHasArt = m_item->HasArt(type); + bool bFallback(false); + if (bHasArt) + { + // Check if that type of art is actually a fallback, e.g. artist fanart + CGUIListItem::ArtMap::const_iterator i = primeArt.find(type); + bFallback = (i == primeArt.end()); + } + + // Build list of possible images of that art type + if (bHasArt) + { + // Add item for current artwork + // For album it could be a fallback from artist + CFileItemPtr item(new CFileItem("thumb://Current", false)); + item->SetArt("thumb", m_item->GetArt(type)); + item->SetArt("icon", "DefaultPicture.png"); + item->SetLabel(g_localizeStrings.Get(13512)); + items.Add(item); + } + + // Grab the thumbnails of this art type scraped from the web + std::vector<std::string> remotethumbs; + // Art type is encoded into the scraper XML as optional "aspect=" field + // Type "thumb" returns URLs for all types of art including those without aspect. + // Those URL without aspect are also returned for all other type values. + if (m_bArtistInfo) + m_artist.thumbURL.GetThumbUrls(remotethumbs, type); + else + m_album.thumbURL.GetThumbUrls(remotethumbs, type); + + for (unsigned int i = 0; i < remotethumbs.size(); ++i) + { + std::string strItemPath; + strItemPath = StringUtils::Format("thumb://Remote{}", i); + CFileItemPtr item(new CFileItem(strItemPath, false)); + item->SetArt("thumb", remotethumbs[i]); + item->SetArt("icon", "DefaultPicture.png"); + item->SetLabel(g_localizeStrings.Get(13513)); + + items.Add(item); + } + + // Local art + std::string localArt; + std::vector<std::string> paths; + if (m_bArtistInfo) + { + // Individual artist subfolder within the Artist Information Folder + paths.emplace_back(m_artist.strPath); + // Fallback local to music files (when there is a unique folder) + paths.emplace_back(m_fallbackartpath); + } + else + // Album folder, when a unique one exists, no fallback + paths.emplace_back(m_album.strPath); + for (const auto& path : paths) + { + if (!localArt.empty() && CFileUtils::Exists(localArt)) + break; + if (!path.empty()) + { + CFileItem item(path, true); + if (type == "thumb") + // Local music thumbnail images named by <musicthumbs> + localArt = item.GetUserMusicThumb(true); + else + { // Check case and ext insenitively for local images with type as name + // e.g. <arttype>.jpg + CFileItemList items; + CDirectory::GetDirectory(path, items, + CServiceBroker::GetFileExtensionProvider().GetPictureExtensions(), + DIR_FLAG_NO_FILE_DIRS | DIR_FLAG_READ_CACHE | DIR_FLAG_NO_FILE_INFO); + + for (int j = 0; j < items.Size(); j++) + { + std::string strCandidate = URIUtils::GetFileName(items[j]->GetPath()); + URIUtils::RemoveExtension(strCandidate); + if (StringUtils::EqualsNoCase(strCandidate, type)) + { + localArt = items[j]->GetPath(); + break; + } + } + } + } + } + if (!localArt.empty() && CFileUtils::Exists(localArt)) + { + CFileItemPtr item(new CFileItem("Local Art: " + localArt, false)); + item->SetArt("thumb", localArt); + item->SetLabel(g_localizeStrings.Get(13514)); // "Local art" + items.Add(item); + } + + // No art + if (bHasArt && !bFallback) + { // Actually has this type of art (not a fallback) so + // allow the user to delete it by selecting "no art". + CFileItemPtr item(new CFileItem("thumb://None", false)); + if (m_bArtistInfo) + item->SetArt("icon", "DefaultArtist.png"); + else + item->SetArt("icon", "DefaultAlbumCover.png"); + item->SetLabel(g_localizeStrings.Get(13515)); + items.Add(item); + } + + //! @todo: Add support for extracting embedded art from song files to use for album + + // Clear local images of this type from cache so user will see any recent + // local file changes immediately + for (auto& item : items) + { + // Skip images from remote sources, recache done by refresh (could be slow) + if (StringUtils::StartsWith(item->GetPath(), "thumb://Remote")) + continue; + std::string thumb(item->GetArt("thumb")); + if (thumb.empty()) + continue; + CURL url(CTextureUtils::UnwrapImageURL(thumb)); + // Skip images from remote sources (current thumb could be remote) + if (url.IsProtocol("http") || url.IsProtocol("https")) + continue; + CServiceBroker::GetTextureCache()->ClearCachedImage(thumb); + // Remove any thumbnail of local image too (created when browsing files) + std::string thumbthumb(CTextureUtils::GetWrappedThumbURL(thumb)); + CServiceBroker::GetTextureCache()->ClearCachedImage(thumbthumb); + } + + // Show list of possible art for user selection + // Note that during selection thumbs of *all* images shown are cached. When + // browsing folders there could be many irrelevant thumbs cached that are + // never used by Kodi again, but there is no obvious way to clear these + // thumbs from the cache automatically. + std::string result; + VECSOURCES sources(*CMediaSourceSettings::GetInstance().GetSources("music")); + CGUIDialogMusicInfo::AddItemPathToFileBrowserSources(sources, *m_item); + CServiceBroker::GetMediaManager().GetLocalDrives(sources); + if (CGUIDialogFileBrowser::ShowAndGetImage(items, sources, g_localizeStrings.Get(13511), result) && + result != "thumb://Current") + { + // User didn't choose the one they have. + // Overwrite with the new art or clear it + std::string newArt; + if (StringUtils::StartsWith(result, "thumb://Remote")) + { + int number = atoi(result.substr(14).c_str()); + newArt = remotethumbs[number]; + } + else if (result == "thumb://Thumb") + newArt = m_item->GetArt("thumb"); + else if (StringUtils::StartsWith(result, "Local Art: ")) + newArt = localArt; + else if (CFileUtils::Exists(result)) + newArt = result; + else // none + newArt.clear(); + + // Asynchronously update that type of art in the database and then + // refresh artist, album and fallback art of currently playing song + MUSIC_UTILS::UpdateArtJob(m_item, type, newArt); + + // Update local item and art list with current art + m_item->SetArt(type, newArt); + for (const auto& artitem : *m_artTypeList) + { + if (artitem->GetProperty("artType") == type) + { + artitem->SetArt("thumb", newArt); + break; + } + } + + // Get new artwork to show in other places e.g. on music lib window, but this does + // not update artist, album or fallback art for the currently playing song as it + // is a different item with different ID and media type + CGUIMessage msg(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_ITEM, 0, m_item); + CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg); + } + + // Re-open the art type selection dialog as we come back from + // the image selection dialog + OnGetArt(); +} + +void CGUIDialogMusicInfo::OnSetUserrating() const +{ + int userrating = MUSIC_UTILS::ShowSelectRatingDialog(m_item->GetMusicInfoTag()->GetUserrating()); + if (userrating < 0) // Nothing selected, so rating unchanged + return; + + SetUserrating(userrating); +} + + +void CGUIDialogMusicInfo::ShowForAlbum(int idAlbum) +{ + std::string path = StringUtils::Format("musicdb://albums/{}", idAlbum); + CFileItem item(path, true); // An album, but IsAlbum() not set as didn't use SetAlbum() + ShowFor(&item); +} + +void CGUIDialogMusicInfo::ShowForArtist(int idArtist) +{ + std::string path = StringUtils::Format("musicdb://artists/{}", idArtist); + CFileItem item(path, true); + ShowFor(&item); +} + +void CGUIDialogMusicInfo::ShowFor(CFileItem* pItem) +{ + if (pItem->IsParentFolder() || URIUtils::IsSpecial(pItem->GetPath()) || + StringUtils::StartsWithNoCase(pItem->GetPath(), "musicsearch://")) + return; // nothing to do + + if (!pItem->m_bIsFolder) + { // Show Song information dialog + CGUIDialogSongInfo::ShowFor(pItem); + return; + } + + CFileItem musicitem("musicdb://", true); + + // We have a folder album/artist info dialog only shown for db items + // or for music video with artist/album in music library + if (pItem->IsMusicDb()) + { + if (!pItem->HasMusicInfoTag() || pItem->GetMusicInfoTag()->GetDatabaseId() < 1) + { + // Maybe only path is set, then set MusicInfoTag + CQueryParams params; + CDirectoryNode::GetDatabaseInfo(pItem->GetPath(), params); + if (params.GetArtistId() > 0) + pItem->GetMusicInfoTag()->SetDatabaseId(params.GetArtistId(), MediaTypeArtist); + else if (params.GetAlbumId() > 0) + pItem->GetMusicInfoTag()->SetDatabaseId(params.GetAlbumId(), MediaTypeAlbum); + else + return; // nothing to do + } + musicitem.SetFromMusicInfoTag(*pItem->GetMusicInfoTag()); + } + else if (pItem->HasProperty("artist_musicid")) + { + musicitem.GetMusicInfoTag()->SetDatabaseId(pItem->GetProperty("artist_musicid").asInteger32(), + MediaTypeArtist); + } + else if (pItem->HasProperty("album_musicid")) + { + musicitem.GetMusicInfoTag()->SetDatabaseId(pItem->GetProperty("album_musicid").asInteger32(), + MediaTypeAlbum); + } + else + return; // nothing to do + + + CGUIDialogMusicInfo *pDlgMusicInfo = CServiceBroker::GetGUI()->GetWindowManager(). + GetWindow<CGUIDialogMusicInfo>(WINDOW_DIALOG_MUSIC_INFO); + if (pDlgMusicInfo) + { + if (pDlgMusicInfo->SetItem(&musicitem)) + { + pDlgMusicInfo->Open(); + if (pItem->GetMusicInfoTag()->GetType() == MediaTypeAlbum && + pDlgMusicInfo->HasUpdatedUserrating()) + { + auto window = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIWindowMusicBase>(WINDOW_MUSIC_NAV); + if (window) + window->RefreshContent("albums"); + } + } + } +} + +void CGUIDialogMusicInfo::OnPlayItem(const std::shared_ptr<CFileItem>& item) +{ + Close(true); + MUSIC_UTILS::PlayItem(item); +} diff --git a/xbmc/music/dialogs/GUIDialogMusicInfo.h b/xbmc/music/dialogs/GUIDialogMusicInfo.h new file mode 100644 index 0000000..781a7d1 --- /dev/null +++ b/xbmc/music/dialogs/GUIDialogMusicInfo.h @@ -0,0 +1,80 @@ +/* + * 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 "MediaSource.h" +#include "guilib/GUIDialog.h" +#include "music/Album.h" +#include "music/Artist.h" +#include "music/Song.h" +#include "threads/Event.h" + +#include <memory> + +class CFileItem; +class CFileItemList; + +class CGUIDialogMusicInfo : + public CGUIDialog +{ +public: + CGUIDialogMusicInfo(void); + ~CGUIDialogMusicInfo(void) override; + bool OnMessage(CGUIMessage& message) override; + bool OnAction(const CAction &action) override; + bool SetItem(CFileItem* item); + void SetAlbum(const CAlbum& album, const std::string &path); + void SetArtist(const CArtist& artist, const std::string &path); + bool HasUpdatedUserrating() const { return m_hasUpdatedUserrating; } + bool HasRefreshed() const { return m_hasRefreshed; } + + bool HasListItems() const override { return true; } + CFileItemPtr GetCurrentListItem(int offset = 0) override; + std::string GetContent(); + static void AddItemPathToFileBrowserSources(VECSOURCES &sources, const CFileItem &item); + void SetDiscography(CMusicDatabase& database) const; + void SetSongs(const VECSONGS &songs) const; + void SetArtTypeList(CFileItemList& artlist); + void SetScrapedInfo(bool bScraped) { m_scraperAddInfo = bScraped; } + CArtist& GetArtist() { return m_artist; } + CAlbum& GetAlbum() { return m_album; } + bool IsArtistInfo() const { return m_bArtistInfo; } + bool IsCancelled() const { return m_cancelled; } + bool HasScrapedInfo() const { return m_scraperAddInfo; } + void FetchComplete(); + void RefreshInfo(); + + static void ShowForAlbum(int idAlbum); + static void ShowForArtist(int idArtist); + static void ShowFor(CFileItem* pItem); +protected: + void OnInitWindow() override; + void Update(); + void SetLabel(int iControl, const std::string& strLabel); + void OnGetArt(); + void OnAlbumInfo(int id); + void OnArtistInfo(int id); + void OnSetUserrating() const; + void SetUserrating(int userrating) const; + void OnPlayItem(const std::shared_ptr<CFileItem>& item); + + CAlbum m_album; + CArtist m_artist; + int m_startUserrating = -1; + bool m_hasUpdatedUserrating = false; + bool m_hasRefreshed = false; + bool m_bArtistInfo = false; + bool m_cancelled = false; + bool m_scraperAddInfo = false; + std::unique_ptr<CFileItemList> m_albumSongs; + std::shared_ptr<CFileItem> m_item; + std::unique_ptr<CFileItemList> m_artTypeList; + CEvent m_event; + std::string m_fallbackartpath; +}; diff --git a/xbmc/music/dialogs/GUIDialogMusicOSD.cpp b/xbmc/music/dialogs/GUIDialogMusicOSD.cpp new file mode 100644 index 0000000..6cd110b --- /dev/null +++ b/xbmc/music/dialogs/GUIDialogMusicOSD.cpp @@ -0,0 +1,91 @@ +/* + * 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 "GUIDialogMusicOSD.h" + +#include "GUIUserMessages.h" +#include "ServiceBroker.h" +#include "addons/addoninfo/AddonType.h" +#include "addons/gui/GUIWindowAddonBrowser.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "input/InputManager.h" +#include "input/Key.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" + +#define CONTROL_VIS_BUTTON 500 +#define CONTROL_LOCK_BUTTON 501 + +CGUIDialogMusicOSD::CGUIDialogMusicOSD(void) + : CGUIDialog(WINDOW_DIALOG_MUSIC_OSD, "MusicOSD.xml") +{ + m_loadType = KEEP_IN_MEMORY; +} + +CGUIDialogMusicOSD::~CGUIDialogMusicOSD(void) = default; + +bool CGUIDialogMusicOSD::OnMessage(CGUIMessage &message) +{ + switch (message.GetMessage()) + { + case GUI_MSG_CLICKED: + { + unsigned int iControl = message.GetSenderId(); + if (iControl == CONTROL_VIS_BUTTON) + { + std::string addonID; + if (CGUIWindowAddonBrowser::SelectAddonID(ADDON::AddonType::VISUALIZATION, addonID, true) == + 1) + { + const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings(); + settings->SetString(CSettings::SETTING_MUSICPLAYER_VISUALISATION, addonID); + settings->Save(); + CServiceBroker::GetGUI()->GetWindowManager().SendMessage(GUI_MSG_VISUALISATION_RELOAD, 0, 0); + } + } + else if (iControl == CONTROL_LOCK_BUTTON) + { + CGUIMessage msg(GUI_MSG_VISUALISATION_ACTION, 0, 0, ACTION_VIS_PRESET_LOCK); + CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg); + } + return true; + } + break; + } + return CGUIDialog::OnMessage(message); +} + +bool CGUIDialogMusicOSD::OnAction(const CAction &action) +{ + switch (action.GetID()) + { + case ACTION_SHOW_OSD: + Close(); + return true; + default: + break; + } + + return CGUIDialog::OnAction(action); +} + +void CGUIDialogMusicOSD::FrameMove() +{ + if (m_autoClosing) + { + // check for movement of mouse or a submenu open + if (CServiceBroker::GetInputManager().IsMouseActive() || + CServiceBroker::GetGUI()->GetWindowManager().IsWindowActive(WINDOW_DIALOG_VIS_SETTINGS) || + CServiceBroker::GetGUI()->GetWindowManager().IsWindowActive(WINDOW_DIALOG_VIS_PRESET_LIST) || + CServiceBroker::GetGUI()->GetWindowManager().IsWindowActive(WINDOW_DIALOG_PVR_RADIO_RDS_INFO)) + // extend show time by original value + SetAutoClose(m_showDuration); + } + CGUIDialog::FrameMove(); +} diff --git a/xbmc/music/dialogs/GUIDialogMusicOSD.h b/xbmc/music/dialogs/GUIDialogMusicOSD.h new file mode 100644 index 0000000..99a208c --- /dev/null +++ b/xbmc/music/dialogs/GUIDialogMusicOSD.h @@ -0,0 +1,22 @@ +/* + * 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 "guilib/GUIDialog.h" + +class CGUIDialogMusicOSD : + public CGUIDialog +{ +public: + CGUIDialogMusicOSD(void); + ~CGUIDialogMusicOSD(void) override; + bool OnMessage(CGUIMessage &message) override; + bool OnAction(const CAction &action) override; + void FrameMove() override; +}; diff --git a/xbmc/music/dialogs/GUIDialogSongInfo.cpp b/xbmc/music/dialogs/GUIDialogSongInfo.cpp new file mode 100644 index 0000000..8efca94 --- /dev/null +++ b/xbmc/music/dialogs/GUIDialogSongInfo.cpp @@ -0,0 +1,523 @@ +/* + * 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 "GUIDialogSongInfo.h" + +#include "GUIDialogMusicInfo.h" +#include "GUIPassword.h" +#include "GUIUserMessages.h" +#include "ServiceBroker.h" +#include "TextureCache.h" +#include "Util.h" +#include "dialogs/GUIDialogBusy.h" +#include "dialogs/GUIDialogFileBrowser.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/LocalizeStrings.h" +#include "input/Key.h" +#include "music/MusicDatabase.h" +#include "music/MusicUtils.h" +#include "music/tags/MusicInfoTag.h" +#include "music/windows/GUIWindowMusicBase.h" +#include "profiles/ProfileManager.h" +#include "settings/MediaSourceSettings.h" +#include "settings/SettingsComponent.h" +#include "storage/MediaManager.h" +#include "utils/FileUtils.h" + +#define CONTROL_BTN_REFRESH 6 +#define CONTROL_USERRATING 7 +#define CONTROL_BTN_PLAY 8 +#define CONTROL_BTN_GET_THUMB 10 +#define CONTROL_ALBUMINFO 12 + +#define CONTROL_LIST 50 + +#define TIME_TO_BUSY_DIALOG 500 + + + +class CGetSongInfoJob : public CJob +{ +public: + ~CGetSongInfoJob(void) override = default; + + // Fetch full song information including art types list + bool DoWork() override + { + CGUIDialogSongInfo *dialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSongInfo>(WINDOW_DIALOG_SONG_INFO); + if (!dialog) + return false; + if (dialog->IsCancelled()) + return false; + CFileItemPtr m_song = dialog->GetCurrentListItem(); + + // Fetch tag data from library using filename of item path, or scanning file + // (if item does not already have this loaded) + if (!m_song->LoadMusicTag()) + { + // Stop SongInfoDialog waiting + dialog->FetchComplete(); + return false; + } + if (dialog->IsCancelled()) + return false; + // Fetch album and primary song artist data from library as properties + // and lyrics by scanning tags from file + MUSIC_INFO::CMusicInfoLoader::LoadAdditionalTagInfo(m_song.get()); + if (dialog->IsCancelled()) + return false; + + // Get album path (for use in browsing art selection) + std::string albumpath; + CMusicDatabase db; + db.Open(); + db.GetAlbumPath(m_song->GetMusicInfoTag()->GetAlbumId(), albumpath); + m_song->SetProperty("album_path", albumpath); + db.Close(); + if (dialog->IsCancelled()) + return false; + + // Load song art. + // For songs in library this includes related album and artist(s) art. + // Also fetches artist art for non library songs when artist can be found + // uniquely by name, otherwise just embedded or cached thumb is fetched. + CMusicThumbLoader loader; + loader.LoadItem(m_song.get()); + if (dialog->IsCancelled()) + return false; + + // For songs in library fill vector of possible art types, with current art when it exists + // for display on the art type selection dialog + CFileItemList artlist; + MUSIC_UTILS::FillArtTypesList(*m_song, artlist); + dialog->SetArtTypeList(artlist); + if (dialog->IsCancelled()) + return false; + + // Tell waiting SongInfoDialog that job is complete + dialog->FetchComplete(); + + return true; + } +}; + +CGUIDialogSongInfo::CGUIDialogSongInfo(void) + : CGUIDialog(WINDOW_DIALOG_SONG_INFO, "DialogMusicInfo.xml") + , m_song(new CFileItem) +{ + m_cancelled = false; + m_hasUpdatedUserrating = false; + m_startUserrating = -1; + m_artTypeList.Clear(); + m_loadType = KEEP_IN_MEMORY; +} + +CGUIDialogSongInfo::~CGUIDialogSongInfo(void) = default; + +bool CGUIDialogSongInfo::OnMessage(CGUIMessage& message) +{ + switch (message.GetMessage()) + { + case GUI_MSG_WINDOW_DEINIT: + { + m_artTypeList.Clear(); + if (m_startUserrating != m_song->GetMusicInfoTag()->GetUserrating()) + { + m_hasUpdatedUserrating = true; + + // Asynchronously update song userrating in library + MUSIC_UTILS::UpdateSongRatingJob(m_song, m_song->GetMusicInfoTag()->GetUserrating()); + + // Send a message to all windows to tell them to update the fileitem + // This communicates the rating change to the music lib window, current playlist and OSD. + // The music lib window item is updated to but changes to the rating when it is the sort + // do not show on screen until refresh() that fetches the list from scratch, sorts etc. + CGUIMessage msg(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_ITEM, 0, m_song); + CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg); + } + CGUIMessage msg(GUI_MSG_LABEL_RESET, GetID(), CONTROL_LIST); + OnMessage(msg); + break; + } + case GUI_MSG_WINDOW_INIT: + CGUIDialog::OnMessage(message); + Update(); + m_cancelled = false; + break; + + case GUI_MSG_CLICKED: + { + int iControl = message.GetSenderId(); + if (iControl == CONTROL_USERRATING) + { + OnSetUserrating(); + } + else if (iControl == CONTROL_ALBUMINFO) + { + CGUIDialogMusicInfo::ShowForAlbum(m_albumId); + return true; + } + else if (iControl == CONTROL_BTN_GET_THUMB) + { + OnGetArt(); + return true; + } + else if (iControl == CONTROL_LIST) + { + int iAction = message.GetParam1(); + if ((ACTION_SELECT_ITEM == iAction || ACTION_MOUSE_LEFT_CLICK == iAction)) + { + CGUIMessage msg(GUI_MSG_ITEM_SELECTED, GetID(), iControl); + CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg); + int iItem = msg.GetParam1(); + if (iItem < 0 || iItem >= static_cast<int>(m_song->GetMusicInfoTag()->GetContributors().size())) + break; + int idArtist = m_song->GetMusicInfoTag()->GetContributors()[iItem].GetArtistId(); + if (idArtist > 0) + CGUIDialogMusicInfo::ShowForArtist(idArtist); + return true; + } + } + else if (iControl == CONTROL_BTN_PLAY) + { + OnPlaySong(m_song); + return true; + } + return false; + } + break; + } + + return CGUIDialog::OnMessage(message); +} + +bool CGUIDialogSongInfo::OnAction(const CAction& action) +{ + int userrating = m_song->GetMusicInfoTag()->GetUserrating(); + if (action.GetID() == ACTION_INCREASE_RATING) + { + SetUserrating(userrating + 1); + return true; + } + else if (action.GetID() == ACTION_DECREASE_RATING) + { + SetUserrating(userrating - 1); + return true; + } + else if (action.GetID() == ACTION_SHOW_INFO) + { + Close(); + return true; + } + return CGUIDialog::OnAction(action); +} + +bool CGUIDialogSongInfo::OnBack(int actionID) +{ + m_cancelled = true; + return CGUIDialog::OnBack(actionID); +} + +void CGUIDialogSongInfo::FetchComplete() +{ + //Trigger the event to indicate data has been fetched + m_event.Set(); +} + +void CGUIDialogSongInfo::OnInitWindow() +{ + // Enable album info button when we know album + m_albumId = m_song->GetMusicInfoTag()->GetAlbumId(); + + CONTROL_ENABLE_ON_CONDITION(CONTROL_ALBUMINFO, m_albumId > 0); + + // Disable music user rating button for plugins as they don't have tables to save this + if (m_song->IsPlugin()) + CONTROL_DISABLE(CONTROL_USERRATING); + else + CONTROL_ENABLE(CONTROL_USERRATING); + + // Disable the Choose Art button if the user isn't allowed it + const std::shared_ptr<CProfileManager> profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager(); + CONTROL_ENABLE_ON_CONDITION(CONTROL_BTN_GET_THUMB, + profileManager->GetCurrentProfile().canWriteDatabases() || g_passwordManager.bMasterUser); + + SET_CONTROL_HIDDEN(CONTROL_BTN_REFRESH); + SET_CONTROL_LABEL(CONTROL_USERRATING, 38023); + SET_CONTROL_LABEL(CONTROL_BTN_GET_THUMB, 13511); + SET_CONTROL_LABEL(CONTROL_ALBUMINFO, 10523); + SET_CONTROL_LABEL(CONTROL_BTN_PLAY, 208); + + CGUIDialog::OnInitWindow(); +} + +void CGUIDialogSongInfo::Update() +{ + CFileItemList items; + for (const auto& contributor : m_song->GetMusicInfoTag()->GetContributors()) + { + auto item = std::make_shared<CFileItem>(contributor.GetRoleDesc()); + item->SetLabel2(contributor.GetArtist()); + item->GetMusicInfoTag()->SetDatabaseId(contributor.GetArtistId(), MediaTypeArtist); + items.Add(std::move(item)); + } + CGUIMessage message(GUI_MSG_LABEL_BIND, GetID(), CONTROL_LIST, 0, 0, &items); + OnMessage(message); +} + +void CGUIDialogSongInfo::SetUserrating(int userrating) +{ + userrating = std::max(userrating, 0); + userrating = std::min(userrating, 10); + if (userrating != m_song->GetMusicInfoTag()->GetUserrating()) + { + m_song->GetMusicInfoTag()->SetUserrating(userrating); + } +} + +bool CGUIDialogSongInfo::SetSong(CFileItem* item) +{ + *m_song = *item; + m_event.Reset(); + m_cancelled = false; // SetSong happens before win_init + // In a separate job fetch song info and fill list of art types. + int jobid = + CServiceBroker::GetJobManager()->AddJob(new CGetSongInfoJob(), nullptr, CJob::PRIORITY_LOW); + + // Wait to get all data before show, allowing user to cancel if fetch is slow + if (!CGUIDialogBusy::WaitOnEvent(m_event, TIME_TO_BUSY_DIALOG)) + { + // Cancel job still waiting in queue (unlikely) + CServiceBroker::GetJobManager()->CancelJob(jobid); + // Flag to stop job already in progress + m_cancelled = true; + return false; + } + + // Store initial userrating + m_startUserrating = m_song->GetMusicInfoTag()->GetUserrating(); + m_hasUpdatedUserrating = false; + return true; +} + +void CGUIDialogSongInfo::SetArtTypeList(CFileItemList& artlist) +{ + m_artTypeList.Copy(artlist); +} + +CFileItemPtr CGUIDialogSongInfo::GetCurrentListItem(int offset) +{ + return m_song; +} + +std::string CGUIDialogSongInfo::GetContent() +{ + return "songs"; +} + +/* + Allow user to choose artwork for the song + For each type of art the options are: + 1. Current art + 2. Local art (thumb found by filename) + 3. Embedded art (@todo) + 4. None + Note that songs are not scraped, hence there is no list of urls for possible remote art +*/ +void CGUIDialogSongInfo::OnGetArt() +{ + std::string type = MUSIC_UTILS::ShowSelectArtTypeDialog(m_artTypeList); + if (type.empty()) + return; // Cancelled + + CFileItemList items; + CGUIListItem::ArtMap primeArt = m_song->GetArt(); // Song art without fallbacks + bool bHasArt = m_song->HasArt(type); + bool bFallback(false); + if (bHasArt) + { + // Check if that type of art is actually a fallback, e.g. album thumb or artist fanart + CGUIListItem::ArtMap::const_iterator i = primeArt.find(type); + bFallback = (i == primeArt.end()); + } + + // Build list of possible images of that art type + if (bHasArt) + { + // Add item for current artwork, could a fallback from album/artist + CFileItemPtr item(new CFileItem("thumb://Current", false)); + item->SetArt("thumb", m_song->GetArt(type)); + item->SetArt("icon", "DefaultPicture.png"); + item->SetLabel(g_localizeStrings.Get(13512)); //! @todo: label fallback art so user knows? + items.Add(item); + } + else if (m_song->HasArt("thumb")) + { // For missing art of that type add the thumb (when it exists and not a fallback) + CGUIListItem::ArtMap::const_iterator i = primeArt.find("thumb"); + if (i != primeArt.end()) + { + CFileItemPtr item(new CFileItem("thumb://Thumb", false)); + item->SetArt("thumb", m_song->GetArt("thumb")); + item->SetArt("icon", "DefaultAlbumCover.png"); + item->SetLabel(g_localizeStrings.Get(21371)); + items.Add(item); + } + } + + std::string localThumb; + if (type == "thumb") + { // Local thumb type art held in <filename>.tbn (for non-library items) + localThumb = m_song->GetUserMusicThumb(true); + if (m_song->IsMusicDb()) + { + CFileItem item(m_song->GetMusicInfoTag()->GetURL(), false); + localThumb = item.GetUserMusicThumb(true); + } + if (CFileUtils::Exists(localThumb)) + { + CFileItemPtr item(new CFileItem("thumb://Local", false)); + item->SetArt("thumb", localThumb); + item->SetLabel(g_localizeStrings.Get(20017)); + items.Add(item); + } + } + + // Clear these local images from cache so user will see any recent + // local file changes immediately + for (auto& item : items) + { + std::string thumb(item->GetArt("thumb")); + if (thumb.empty()) + continue; + CServiceBroker::GetTextureCache()->ClearCachedImage(thumb); + // Remove any thumbnail of local image too (created when browsing files) + std::string thumbthumb(CTextureUtils::GetWrappedThumbURL(thumb)); + CServiceBroker::GetTextureCache()->ClearCachedImage(thumbthumb); + } + + if (bHasArt && !bFallback) + { // Actually has this type of art (not a fallback) so + // allow the user to delete it by selecting "no art". + CFileItemPtr item(new CFileItem("thumb://None", false)); + item->SetArt("thumb", "DefaultAlbumCover.png"); + item->SetLabel(g_localizeStrings.Get(13515)); + items.Add(item); + } + + //! @todo: Add support for extracting embedded art + + // Show list of possible art for user selection + std::string result; + VECSOURCES sources(*CMediaSourceSettings::GetInstance().GetSources("music")); + // Add album folder as source (could be disc set) + std::string albumpath = m_song->GetProperty("album_path").asString(); + if (!albumpath.empty()) + { + CFileItem pathItem(albumpath, true); + CGUIDialogMusicInfo::AddItemPathToFileBrowserSources(sources, pathItem); + } + else // Add parent folder of song + CGUIDialogMusicInfo::AddItemPathToFileBrowserSources(sources, *m_song); + CServiceBroker::GetMediaManager().GetLocalDrives(sources); + if (CGUIDialogFileBrowser::ShowAndGetImage(items, sources, g_localizeStrings.Get(13511), result) && + result != "thumb://Current") + { + // User didn't choose the one they have, or the fallback image. + // Overwrite with the new art or clear it + std::string newArt; + if (result == "thumb://Thumb") + newArt = m_song->GetArt("thumb"); + else if (result == "thumb://Local") + newArt = localThumb; +// else if (result == "thumb://Embedded") +// newArt = embeddedArt; + else if (CFileUtils::Exists(result)) + newArt = result; + else // none + newArt.clear(); + + // Asynchronously update that type of art in the database + MUSIC_UTILS::UpdateArtJob(m_song, type, newArt); + + // Update local song with current art + if (newArt.empty()) + { + // Remove that type of art from the song + primeArt.erase(type); + m_song->SetArt(primeArt); + } + else + // Add or modify the type of art + m_song->SetArt(type, newArt); + + // Update local art list with current art + // Show any fallback art when song art removed + if (newArt.empty() && m_song->HasArt(type)) + newArt = m_song->GetArt(type); + for (const auto& artitem : m_artTypeList) + { + if (artitem->GetProperty("artType") == type) + { + artitem->SetArt("thumb", newArt); + break; + } + } + + // Get new artwork to show in other places e.g. on music lib window, + // current playlist and player OSD. + CGUIMessage msg(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_ITEM, 0, m_song); + CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg); + + } + + // Re-open the art type selection dialog as we come back from + // the image selection dialog + OnGetArt(); +} + +void CGUIDialogSongInfo::OnSetUserrating() +{ + int userrating = MUSIC_UTILS::ShowSelectRatingDialog(m_song->GetMusicInfoTag()->GetUserrating()); + if (userrating < 0) // Nothing selected, so rating unchanged + return; + + SetUserrating(userrating); +} + +void CGUIDialogSongInfo::ShowFor(CFileItem* pItem) +{ + if (pItem->m_bIsFolder) + return; + if (!pItem->IsMusicDb()) + pItem->LoadMusicTag(); + if (!pItem->HasMusicInfoTag()) + return; + + CGUIDialogSongInfo *dialog = CServiceBroker::GetGUI()->GetWindowManager(). + GetWindow<CGUIDialogSongInfo>(WINDOW_DIALOG_SONG_INFO); + if (dialog) + { + if (dialog->SetSong(pItem)) // Fetch full song info asynchronously + { + dialog->Open(); + if (dialog->HasUpdatedUserrating()) + { + auto window = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIWindowMusicBase>(WINDOW_MUSIC_NAV); + if (window) + window->RefreshContent("songs"); + } + } + } +} + +void CGUIDialogSongInfo::OnPlaySong(const std::shared_ptr<CFileItem>& item) +{ + Close(true); + MUSIC_UTILS::PlayItem(item); +} diff --git a/xbmc/music/dialogs/GUIDialogSongInfo.h b/xbmc/music/dialogs/GUIDialogSongInfo.h new file mode 100644 index 0000000..274f1ac --- /dev/null +++ b/xbmc/music/dialogs/GUIDialogSongInfo.h @@ -0,0 +1,54 @@ +/* + * 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 "guilib/GUIDialog.h" +#include "threads/Event.h" + +#include <memory> + +class CGUIDialogSongInfo : + public CGUIDialog +{ +public: + CGUIDialogSongInfo(void); + ~CGUIDialogSongInfo(void) override; + bool OnMessage(CGUIMessage& message) override; + bool SetSong(CFileItem* item); + void SetArtTypeList(CFileItemList& artlist); + bool OnAction(const CAction& action) override; + bool OnBack(int actionID) override; + bool HasUpdatedUserrating() const { return m_hasUpdatedUserrating; } + + bool HasListItems() const override { return true; } + CFileItemPtr GetCurrentListItem(int offset = 0) override; + std::string GetContent(); + //const CFileItemList& CurrentDirectory() const { return m_artTypeList; } + bool IsCancelled() const { return m_cancelled; } + void FetchComplete(); + + static void ShowFor(CFileItem* pItem); +protected: + void OnInitWindow() override; + void Update(); + void OnGetArt(); + void SetUserrating(int userrating); + void OnSetUserrating(); + void OnPlaySong(const std::shared_ptr<CFileItem>& item); + + CFileItemPtr m_song; + CFileItemList m_artTypeList; + CEvent m_event; + int m_startUserrating; + bool m_cancelled; + bool m_hasUpdatedUserrating; + long m_albumId = -1; + +}; diff --git a/xbmc/music/dialogs/GUIDialogVisualisationPresetList.cpp b/xbmc/music/dialogs/GUIDialogVisualisationPresetList.cpp new file mode 100644 index 0000000..b5f2607 --- /dev/null +++ b/xbmc/music/dialogs/GUIDialogVisualisationPresetList.cpp @@ -0,0 +1,98 @@ +/* + * 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 "GUIDialogVisualisationPresetList.h" + +#include "FileItem.h" +#include "GUIUserMessages.h" +#include "ServiceBroker.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIVisualisationControl.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/LocalizeStrings.h" +#include "input/Key.h" +#include "utils/StringUtils.h" +#include "utils/Variant.h" + +CGUIDialogVisualisationPresetList::CGUIDialogVisualisationPresetList() + : CGUIDialogSelect(WINDOW_DIALOG_VIS_PRESET_LIST) +{ + m_loadType = KEEP_IN_MEMORY; +} + +bool CGUIDialogVisualisationPresetList::OnMessage(CGUIMessage &message) +{ + switch (message.GetMessage()) + { + case GUI_MSG_VISUALISATION_UNLOADING: + ClearVisualisation(); + break; + } + return CGUIDialogSelect::OnMessage(message); +} + +void CGUIDialogVisualisationPresetList::OnSelect(int idx) +{ + if (m_viz) + m_viz->SetPreset(idx); +} + +void CGUIDialogVisualisationPresetList::ClearVisualisation() +{ + m_viz = nullptr; + Reset(); +} + +void CGUIDialogVisualisationPresetList::SetVisualisation(CGUIVisualisationControl* vis) +{ + m_viz = vis; + Reset(); + if (!m_viz) + { // No viz, but show something if this dialog activated + SetHeading(CVariant{ 10122 }); + CFileItem item(g_localizeStrings.Get(13389)); + Add(item); + } + else + { + SetUseDetails(false); + SetMultiSelection(false); + SetHeading(CVariant{StringUtils::Format(g_localizeStrings.Get(13407), m_viz->Name())}); + std::vector<std::string> presets; + if (m_viz->GetPresetList(presets)) + { + for (const auto& preset : presets) + { + CFileItem item(preset); + item.RemoveExtension(); + Add(item); + } + SetSelected(m_viz->GetActivePreset()); + } + else + { // Viz does not have any presets + // "There are no presets available for this visualisation" + CFileItem item(g_localizeStrings.Get(13389)); + Add(item); + } + } +} + +void CGUIDialogVisualisationPresetList::OnInitWindow() +{ + CGUIMessage msg(GUI_MSG_GET_VISUALISATION, 0, 0); + CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg); + SetVisualisation(static_cast<CGUIVisualisationControl*>(msg.GetPointer())); + CGUIDialogSelect::OnInitWindow(); +} + +void CGUIDialogVisualisationPresetList::OnDeinitWindow(int nextWindowID) +{ + ClearVisualisation(); + CGUIDialogSelect::OnDeinitWindow(nextWindowID); +} diff --git a/xbmc/music/dialogs/GUIDialogVisualisationPresetList.h b/xbmc/music/dialogs/GUIDialogVisualisationPresetList.h new file mode 100644 index 0000000..c832163 --- /dev/null +++ b/xbmc/music/dialogs/GUIDialogVisualisationPresetList.h @@ -0,0 +1,32 @@ +/* + * 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 "dialogs/GUIDialogSelect.h" +#include "guilib/GUIDialog.h" + +class CGUIVisualisationControl; +class CFileItemList; + +class CGUIDialogVisualisationPresetList : public CGUIDialogSelect +{ +public: + CGUIDialogVisualisationPresetList(); + bool OnMessage(CGUIMessage &message) override; + +protected: + void OnInitWindow() override; + void OnDeinitWindow(int nextWindowID) override; + void OnSelect(int idx) override; + +private: + void ClearVisualisation(); + void SetVisualisation(CGUIVisualisationControl *addon); + CGUIVisualisationControl* m_viz = nullptr; +}; |