diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 18:07:22 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 18:07:22 +0000 |
commit | c04dcc2e7d834218ef2d4194331e383402495ae1 (patch) | |
tree | 7333e38d10d75386e60f336b80c2443c1166031d /xbmc/music/windows | |
parent | Initial commit. (diff) | |
download | kodi-c04dcc2e7d834218ef2d4194331e383402495ae1.tar.xz kodi-c04dcc2e7d834218ef2d4194331e383402495ae1.zip |
Adding upstream version 2:20.4+dfsg.upstream/2%20.4+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'xbmc/music/windows')
-rw-r--r-- | xbmc/music/windows/CMakeLists.txt | 15 | ||||
-rw-r--r-- | xbmc/music/windows/GUIWindowMusicBase.cpp | 1097 | ||||
-rw-r--r-- | xbmc/music/windows/GUIWindowMusicBase.h | 106 | ||||
-rw-r--r-- | xbmc/music/windows/GUIWindowMusicNav.cpp | 944 | ||||
-rw-r--r-- | xbmc/music/windows/GUIWindowMusicNav.h | 50 | ||||
-rw-r--r-- | xbmc/music/windows/GUIWindowMusicPlaylist.cpp | 715 | ||||
-rw-r--r-- | xbmc/music/windows/GUIWindowMusicPlaylist.h | 44 | ||||
-rw-r--r-- | xbmc/music/windows/GUIWindowMusicPlaylistEditor.cpp | 450 | ||||
-rw-r--r-- | xbmc/music/windows/GUIWindowMusicPlaylistEditor.h | 57 | ||||
-rw-r--r-- | xbmc/music/windows/GUIWindowVisualisation.cpp | 233 | ||||
-rw-r--r-- | xbmc/music/windows/GUIWindowVisualisation.h | 31 | ||||
-rw-r--r-- | xbmc/music/windows/MusicFileItemListModifier.cpp | 114 | ||||
-rw-r--r-- | xbmc/music/windows/MusicFileItemListModifier.h | 24 |
13 files changed, 3880 insertions, 0 deletions
diff --git a/xbmc/music/windows/CMakeLists.txt b/xbmc/music/windows/CMakeLists.txt new file mode 100644 index 0000000..031d735 --- /dev/null +++ b/xbmc/music/windows/CMakeLists.txt @@ -0,0 +1,15 @@ +set(SOURCES GUIWindowMusicBase.cpp + GUIWindowMusicNav.cpp + GUIWindowMusicPlaylist.cpp + GUIWindowMusicPlaylistEditor.cpp + GUIWindowVisualisation.cpp + MusicFileItemListModifier.cpp) + +set(HEADERS GUIWindowMusicBase.h + GUIWindowMusicNav.h + GUIWindowMusicPlaylist.h + GUIWindowMusicPlaylistEditor.h + GUIWindowVisualisation.h + MusicFileItemListModifier.h) + +core_add_library(music_windows) diff --git a/xbmc/music/windows/GUIWindowMusicBase.cpp b/xbmc/music/windows/GUIWindowMusicBase.cpp new file mode 100644 index 0000000..df45141 --- /dev/null +++ b/xbmc/music/windows/GUIWindowMusicBase.cpp @@ -0,0 +1,1097 @@ +/* + * 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 "GUIWindowMusicBase.h" + +#include "GUIUserMessages.h" +#include "PlayListPlayer.h" +#include "ServiceBroker.h" +#include "Util.h" +#include "application/Application.h" +#include "application/ApplicationComponents.h" +#include "application/ApplicationPlayer.h" +#include "dialogs/GUIDialogMediaSource.h" +#include "input/actions/Action.h" +#include "input/actions/ActionIDs.h" +#include "music/MusicDbUrl.h" +#include "music/MusicLibraryQueue.h" +#include "music/MusicUtils.h" +#include "music/dialogs/GUIDialogInfoProviderSettings.h" +#include "music/dialogs/GUIDialogMusicInfo.h" +#include "playlists/PlayList.h" +#include "playlists/PlayListFactory.h" +#ifdef HAS_CDDA_RIPPER +#include "cdrip/CDDARipper.h" +#endif +#include "Autorun.h" +#include "FileItem.h" +#include "GUIInfoManager.h" +#include "GUIPassword.h" +#include "PartyModeManager.h" +#include "URL.h" +#include "addons/gui/GUIDialogAddonInfo.h" +#include "cores/playercorefactory/PlayerCoreFactory.h" +#include "dialogs/GUIDialogProgress.h" +#include "dialogs/GUIDialogSmartPlaylistEditor.h" +#include "dialogs/GUIDialogYesNo.h" +#include "filesystem/Directory.h" +#include "filesystem/MusicDatabaseDirectory.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/LocalizeStrings.h" +#include "guilib/guiinfo/GUIInfoLabels.h" +#include "messaging/helpers/DialogHelper.h" +#include "messaging/helpers/DialogOKHelper.h" +#include "music/infoscanner/MusicInfoScanner.h" +#include "music/tags/MusicInfoTag.h" +#include "profiles/ProfileManager.h" +#include "settings/AdvancedSettings.h" +#include "settings/MediaSourceSettings.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "storage/MediaManager.h" +#include "utils/FileUtils.h" +#include "utils/StringUtils.h" +#include "utils/URIUtils.h" +#include "utils/Variant.h" +#include "utils/XTimeUtils.h" +#include "utils/log.h" +#include "video/VideoInfoTag.h" +#include "video/dialogs/GUIDialogVideoInfo.h" +#include "view/GUIViewState.h" + +#include <algorithm> + +using namespace XFILE; +using namespace MUSICDATABASEDIRECTORY; +using namespace MUSIC_GRABBER; +using namespace MUSIC_INFO; +using namespace KODI::MESSAGING; +using KODI::MESSAGING::HELPERS::DialogResponse; + +using namespace std::chrono_literals; + +#define CONTROL_BTNVIEWASICONS 2 +#define CONTROL_BTNSORTBY 3 +#define CONTROL_BTNSORTASC 4 +#define CONTROL_BTNPLAYLISTS 7 +#define CONTROL_BTNSCAN 9 +#define CONTROL_BTNRIP 11 + +CGUIWindowMusicBase::CGUIWindowMusicBase(int id, const std::string &xmlFile) + : CGUIMediaWindow(id, xmlFile.c_str()) +{ + m_dlgProgress = NULL; + m_thumbLoader.SetObserver(this); +} + +CGUIWindowMusicBase::~CGUIWindowMusicBase () = default; + +bool CGUIWindowMusicBase::OnBack(int actionID) +{ + if (!CMusicLibraryQueue::GetInstance().IsScanningLibrary()) + { + CUtil::RemoveTempFiles(); + } + return CGUIMediaWindow::OnBack(actionID); +} + +/*! + \brief Handle messages on window. + \param message GUI Message that can be reacted on. + \return if a message can't be processed, return \e false + + On these messages this class reacts.\n + When retrieving... + - #GUI_MSG_WINDOW_DEINIT\n + ...the last focused control is saved to m_iLastControl. + - #GUI_MSG_WINDOW_INIT\n + ...the musicdatabase is opend and the music extensions and shares are set. + The last focused control is set. + - #GUI_MSG_CLICKED\n + ... the base class reacts on the following controls:\n + Buttons:\n + - #CONTROL_BTNVIEWASICONS - switch between list, thumb and with large items + - #CONTROL_BTNSEARCH - Search for items\n + Other Controls: + - The container controls\n + Have the following actions in message them clicking on them. + - #ACTION_QUEUE_ITEM - add selected item to end of playlist + - #ACTION_QUEUE_ITEM_NEXT - add selected item to next pos in playlist + - #ACTION_SHOW_INFO - retrieve album info from the internet + - #ACTION_SELECT_ITEM - Item has been selected. Overwrite OnClick() to react on it + */ +bool CGUIWindowMusicBase::OnMessage(CGUIMessage& message) +{ + switch ( message.GetMessage() ) + { + case GUI_MSG_WINDOW_DEINIT: + { + if (m_thumbLoader.IsLoading()) + m_thumbLoader.StopThread(); + m_musicdatabase.Close(); + } + break; + + case GUI_MSG_WINDOW_INIT: + { + m_dlgProgress = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogProgress>(WINDOW_DIALOG_PROGRESS); + + m_musicdatabase.Open(); + + if (!CGUIMediaWindow::OnMessage(message)) + return false; + + return true; + } + break; + case GUI_MSG_DIRECTORY_SCANNED: + { + CFileItem directory(message.GetStringParam(), true); + + // Only update thumb on a local drive + if (directory.IsHD()) + { + std::string strParent; + URIUtils::GetParentPath(directory.GetPath(), strParent); + if (directory.GetPath() == m_vecItems->GetPath() || strParent == m_vecItems->GetPath()) + Refresh(); + } + } + break; + + // update the display + case GUI_MSG_SCAN_FINISHED: + case GUI_MSG_REFRESH_THUMBS: // Never called as is secondary msg sent as GUI_MSG_NOTIFY_ALL + Refresh(); + break; + + case GUI_MSG_CLICKED: + { + int iControl = message.GetSenderId(); + if (iControl == CONTROL_BTNRIP) + { + OnRipCD(); + } + else if (iControl == CONTROL_BTNPLAYLISTS) + { + if (!m_vecItems->IsPath("special://musicplaylists/")) + Update("special://musicplaylists/"); + } + else if (iControl == CONTROL_BTNSCAN) + { + OnScan(-1); + } + else if (m_viewControl.HasControl(iControl)) // list/thumb control + { + int iItem = m_viewControl.GetSelectedItem(); + int iAction = message.GetParam1(); + + // iItem is checked for validity inside these routines + if (iAction == ACTION_QUEUE_ITEM || iAction == ACTION_MOUSE_MIDDLE_CLICK) + { + OnQueueItem(iItem); + } + else if (iAction == ACTION_QUEUE_ITEM_NEXT) + { + OnQueueItem(iItem, true); + } + else if (iAction == ACTION_SHOW_INFO) + { + OnItemInfo(iItem); + } + else if (iAction == ACTION_DELETE_ITEM) + { + // is delete allowed? + // must be at the playlists directory + if (m_vecItems->IsPath("special://musicplaylists/")) + OnDeleteItem(iItem); + + else + return false; + } + // use play button to add folders of items to temp playlist + else if (iAction == ACTION_PLAYER_PLAY) + { + const auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + // if playback is paused or playback speed != 1, return + if (appPlayer->IsPlayingAudio()) + { + if (appPlayer->IsPausedPlayback()) + return false; + if (appPlayer->GetPlaySpeed() != 1) + return false; + } + + // not playing audio, or playback speed == 1 + PlayItem(iItem); + + return true; + } + } + } + break; + case GUI_MSG_NOTIFY_ALL: + { + if (message.GetParam1()==GUI_MSG_REMOVED_MEDIA) + CUtil::DeleteDirectoryCache("r-"); + } + break; + } + return CGUIMediaWindow::OnMessage(message); +} + +bool CGUIWindowMusicBase::OnAction(const CAction &action) +{ + if (action.GetID() == ACTION_SHOW_PLAYLIST) + { + if (CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist() == PLAYLIST::TYPE_MUSIC || + CServiceBroker::GetPlaylistPlayer().GetPlaylist(PLAYLIST::TYPE_MUSIC).size() > 0) + { + CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_MUSIC_PLAYLIST); + return true; + } + } + + if (action.GetID() == ACTION_SCAN_ITEM) + { + int item = m_viewControl.GetSelectedItem(); + if (item > -1 && m_vecItems->Get(item)->m_bIsFolder) + OnScan(item); + + return true; + } + + return CGUIMediaWindow::OnAction(action); +} + +void CGUIWindowMusicBase::OnItemInfoAll(const std::string& strPath, bool refresh) +{ + if (StringUtils::EqualsNoCase(m_vecItems->GetContent(), "albums")) + { + if (CMusicLibraryQueue::GetInstance().IsScanningLibrary()) + return; + + CMusicLibraryQueue::GetInstance().StartAlbumScan(strPath, refresh); + } + else if (StringUtils::EqualsNoCase(m_vecItems->GetContent(), "artists")) + { + if (CMusicLibraryQueue::GetInstance().IsScanningLibrary()) + return; + + CMusicLibraryQueue::GetInstance().StartArtistScan(strPath, refresh); + } +} + +void CGUIWindowMusicBase::OnItemInfo(int iItem) +{ + if ( iItem < 0 || iItem >= m_vecItems->Size() ) + return; + + CFileItemPtr item = m_vecItems->Get(iItem); + + // Match visibility test of CMusicInfo::IsVisible + if (item->IsVideoDb() && item->HasVideoInfoTag() && + (item->HasProperty("artist_musicid") || item->HasProperty("album_musicid"))) + { + // Music video artist or album (navigation by music > music video > artist)) + CGUIDialogMusicInfo::ShowFor(item.get()); + return; + } + + if (item->IsVideo() && item->HasVideoInfoTag() && + item->GetVideoInfoTag()->m_type == MediaTypeMusicVideo) + { // Music video on a mixed current playlist or navigation by music > music video > artist > video + CGUIDialogVideoInfo::ShowFor(*item); + return; + } + + if (!m_vecItems->IsPlugin() && (item->IsPlugin() || item->IsScript())) + { + CGUIDialogAddonInfo::ShowForItem(item); + return; + } + + // Match visibility test of CMusicInfo::IsVisible + if (item->HasMusicInfoTag() && (item->GetMusicInfoTag()->GetType() == MediaTypeSong || + item->GetMusicInfoTag()->GetType() == MediaTypeAlbum || + item->GetMusicInfoTag()->GetType() == MediaTypeArtist)) + CGUIDialogMusicInfo::ShowFor(item.get()); +} + +void CGUIWindowMusicBase::RefreshContent(const std::string& strContent) +{ + if ( CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_MUSIC_NAV && + m_vecItems->GetContent() == strContent && + m_vecItems->GetSortMethod() == SortByUserRating) + // When music library window is active and showing songs or albums sorted + // by userrating refresh the list to resort items and show new userrating + Refresh(true); +} + +/// \brief Retrieve tag information for \e m_vecItems +void CGUIWindowMusicBase::RetrieveMusicInfo() +{ + auto start = std::chrono::steady_clock::now(); + + OnRetrieveMusicInfo(*m_vecItems); + + //! @todo Scan for multitrack items here... + std::vector<std::string> itemsForRemove; + CFileItemList itemsForAdd; + for (int i = 0; i < m_vecItems->Size(); ++i) + { + CFileItemPtr pItem = (*m_vecItems)[i]; + if (pItem->m_bIsFolder || pItem->IsPlayList() || pItem->IsPicture() || pItem->IsLyrics() || pItem->IsVideo()) + continue; + + CMusicInfoTag& tag = *pItem->GetMusicInfoTag(); + if (tag.Loaded() && !tag.GetCueSheet().empty()) + pItem->LoadEmbeddedCue(); + + if (pItem->HasCueDocument() + && pItem->LoadTracksFromCueDocument(itemsForAdd)) + { + itemsForRemove.push_back(pItem->GetPath()); + } + } + for (size_t i = 0; i < itemsForRemove.size(); ++i) + { + for (int j = 0; j < m_vecItems->Size(); ++j) + { + if ((*m_vecItems)[j]->GetPath() == itemsForRemove[i]) + { + m_vecItems->Remove(j); + break; + } + } + } + m_vecItems->Append(itemsForAdd); + + auto end = std::chrono::steady_clock::now(); + auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start); + + CLog::Log(LOGDEBUG, "RetrieveMusicInfo() took {} ms", duration.count()); +} + +/// \brief Add selected list/thumb control item to playlist and start playing +/// \param iItem Selected Item in list/thumb control +void CGUIWindowMusicBase::OnQueueItem(int iItem, bool first) +{ + // don't re-queue items from playlist window + if (iItem < 0 || iItem >= m_vecItems->Size() || GetID() == WINDOW_MUSIC_PLAYLIST) + return; + + // add item 2 playlist + const auto item = m_vecItems->Get(iItem); + + if (item->IsRAR() || item->IsZIP()) + return; + + MUSIC_UTILS::QueueItem(item, first ? MUSIC_UTILS::QueuePosition::POSITION_BEGIN + : MUSIC_UTILS::QueuePosition::POSITION_END); + + // select next item + m_viewControl.SetSelectedItem(iItem + 1); +} + +void CGUIWindowMusicBase::UpdateButtons() +{ + CONTROL_ENABLE_ON_CONDITION(CONTROL_BTNRIP, CServiceBroker::GetMediaManager().IsAudio()); + + CONTROL_ENABLE_ON_CONDITION(CONTROL_BTNSCAN, + !(m_vecItems->IsVirtualDirectoryRoot() || + m_vecItems->IsMusicDb())); + + if (CMusicLibraryQueue::GetInstance().IsScanningLibrary()) + SET_CONTROL_LABEL(CONTROL_BTNSCAN, 14056); // Stop Scan + else + SET_CONTROL_LABEL(CONTROL_BTNSCAN, 102); // Scan + + CGUIMediaWindow::UpdateButtons(); +} + +void CGUIWindowMusicBase::GetContextButtons(int itemNumber, CContextButtons &buttons) +{ + CFileItemPtr item; + if (itemNumber >= 0 && itemNumber < m_vecItems->Size()) + item = m_vecItems->Get(itemNumber); + + if (item) + { + const std::shared_ptr<CProfileManager> profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager(); + + // Check for the partymode playlist item. + // When "PartyMode.xsp" not exist, only context menu button is edit + if (item->IsSmartPlayList() && + (item->GetPath() == profileManager->GetUserDataItem("PartyMode.xsp")) && + !CFileUtils::Exists(item->GetPath())) + { + buttons.Add(CONTEXT_BUTTON_EDIT_SMART_PLAYLIST, 586); + return; + } + + if (!item->IsParentFolder()) + { + //! @todo get rid of IsAddonsPath and IsScript check. CanQueue should be enough! + if (item->CanQueue() && !item->IsAddonsPath() && !item->IsScript()) + { + if (!item->m_bIsFolder && + (!item->IsPlayList() || + CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_playlistAsFolders)) + { + const CPlayerCoreFactory& playerCoreFactory = CServiceBroker::GetPlayerCoreFactory(); + + // check what players we have, if we have multiple display play with option + std::vector<std::string> players; + playerCoreFactory.GetPlayers(*item, players); + if (players.size() >= 1) + buttons.Add(CONTEXT_BUTTON_PLAY_WITH, 15213); // Play With... + } + + if (item->IsSmartPlayList()) + buttons.Add(CONTEXT_BUTTON_PLAY_PARTYMODE, 15216); // Play in Partymode + + if (item->IsSmartPlayList() || m_vecItems->IsSmartPlayList()) + buttons.Add(CONTEXT_BUTTON_EDIT_SMART_PLAYLIST, 586); + else if (item->IsPlayList() || m_vecItems->IsPlayList()) + buttons.Add(CONTEXT_BUTTON_EDIT, 586); + } +#ifdef HAS_DVD_DRIVE + // enable Rip CD Audio or Track button if we have an audio disc + if (CServiceBroker::GetMediaManager().IsDiscInDrive() && m_vecItems->IsCDDA()) + { + // those cds can also include Audio Tracks: CDExtra and MixedMode! + MEDIA_DETECT::CCdInfo* pCdInfo = CServiceBroker::GetMediaManager().GetCdInfo(); + if (pCdInfo->IsAudio(1) || pCdInfo->IsCDExtra(1) || pCdInfo->IsMixedMode(1)) + buttons.Add(CONTEXT_BUTTON_RIP_TRACK, 610); + } +#endif + } + + // enable CDDB lookup if the current dir is CDDA + if (CServiceBroker::GetMediaManager().IsDiscInDrive() && m_vecItems->IsCDDA() && + (profileManager->GetCurrentProfile().canWriteDatabases() || g_passwordManager.bMasterUser)) + { + buttons.Add(CONTEXT_BUTTON_CDDB, 16002); + } + } + CGUIMediaWindow::GetContextButtons(itemNumber, buttons); +} + +void CGUIWindowMusicBase::GetNonContextButtons(CContextButtons &buttons) +{ +} + +bool CGUIWindowMusicBase::OnContextButton(int itemNumber, CONTEXT_BUTTON button) +{ + CFileItemPtr item; + if (itemNumber >= 0 && itemNumber < m_vecItems->Size()) + item = m_vecItems->Get(itemNumber); + + if (CGUIDialogContextMenu::OnContextButton("music", item, button)) + { + if (button == CONTEXT_BUTTON_REMOVE_SOURCE) + OnRemoveSource(itemNumber); + + Update(m_vecItems->GetPath()); + return true; + } + + switch (button) + { + case CONTEXT_BUTTON_INFO: + OnItemInfo(itemNumber); + return true; + + case CONTEXT_BUTTON_EDIT: + { + std::string playlist = item->IsPlayList() ? item->GetPath() : m_vecItems->GetPath(); // save path as activatewindow will destroy our items + CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_MUSIC_PLAYLIST_EDITOR, playlist); + // need to update + m_vecItems->RemoveDiscCache(GetID()); + return true; + } + + case CONTEXT_BUTTON_EDIT_SMART_PLAYLIST: + { + std::string playlist = item->IsSmartPlayList() ? item->GetPath() : m_vecItems->GetPath(); // save path as activatewindow will destroy our items + if (CGUIDialogSmartPlaylistEditor::EditPlaylist(playlist, "music")) + Refresh(true); // need to update + return true; + } + + case CONTEXT_BUTTON_PLAY_WITH: + { + const CPlayerCoreFactory &playerCoreFactory = CServiceBroker::GetPlayerCoreFactory(); + + std::vector<std::string> players; + playerCoreFactory.GetPlayers(*item, players); + std::string player = playerCoreFactory.SelectPlayerDialog(players); + if (!player.empty()) + OnClick(itemNumber, player); + return true; + } + + case CONTEXT_BUTTON_PLAY_PARTYMODE: + g_partyModeManager.Enable(PARTYMODECONTEXT_MUSIC, item->GetPath()); + return true; + + case CONTEXT_BUTTON_RIP_CD: + OnRipCD(); + return true; + +#ifdef HAS_CDDA_RIPPER + case CONTEXT_BUTTON_CANCEL_RIP_CD: + KODI::CDRIP::CCDDARipper::GetInstance().CancelJobs(); + return true; +#endif + + case CONTEXT_BUTTON_RIP_TRACK: + OnRipTrack(itemNumber); + return true; + + case CONTEXT_BUTTON_SCAN: + // Check if scanning already and inform user + if (CMusicLibraryQueue::GetInstance().IsScanningLibrary()) + HELPERS::ShowOKDialogText(CVariant{ 189 }, CVariant{ 14057 }); + else + OnScan(itemNumber, true); + return true; + + case CONTEXT_BUTTON_CDDB: + if (m_musicdatabase.LookupCDDBInfo(true)) + Refresh(); + return true; + + default: + break; + } + + return CGUIMediaWindow::OnContextButton(itemNumber, button); +} + +bool CGUIWindowMusicBase::OnAddMediaSource() +{ + return CGUIDialogMediaSource::ShowAndAddMediaSource("music"); +} + +void CGUIWindowMusicBase::OnRipCD() +{ + if (CServiceBroker::GetMediaManager().IsAudio()) + { + if (!g_application.CurrentFileItem().IsCDDA()) + { +#ifdef HAS_CDDA_RIPPER + KODI::CDRIP::CCDDARipper::GetInstance().RipCD(); +#endif + } + else + HELPERS::ShowOKDialogText(CVariant{257}, CVariant{20099}); + } +} + +void CGUIWindowMusicBase::OnRipTrack(int iItem) +{ + if (CServiceBroker::GetMediaManager().IsAudio()) + { + if (!g_application.CurrentFileItem().IsCDDA()) + { +#ifdef HAS_CDDA_RIPPER + CFileItemPtr item = m_vecItems->Get(iItem); + KODI::CDRIP::CCDDARipper::GetInstance().RipTrack(item.get()); +#endif + } + else + HELPERS::ShowOKDialogText(CVariant{257}, CVariant{20099}); + } +} + +void CGUIWindowMusicBase::PlayItem(int iItem) +{ + // restrictions should be placed in the appropriate window code + // only call the base code if the item passes since this clears + // the current playlist + + const CFileItemPtr pItem = m_vecItems->Get(iItem); +#ifdef HAS_DVD_DRIVE + if (pItem->IsDVD()) + { + MEDIA_DETECT::CAutorun::PlayDiscAskResume(pItem->GetPath()); + return; + } +#endif + + // Check for the partymode playlist item, do nothing when "PartyMode.xsp" not exist + if (pItem->IsSmartPlayList()) + { + const std::shared_ptr<CProfileManager> profileManager = + CServiceBroker::GetSettingsComponent()->GetProfileManager(); + if ((pItem->GetPath() == profileManager->GetUserDataItem("PartyMode.xsp")) && + !CFileUtils::Exists(pItem->GetPath())) + return; + } + + // if its a folder, build a playlist + if (pItem->m_bIsFolder && !pItem->IsPlugin()) + { + // make a copy so that we can alter the queue state + CFileItemPtr item(new CFileItem(*m_vecItems->Get(iItem))); + + // Allow queuing of unqueueable items + // when we try to queue them directly + if (!item->CanQueue()) + item->SetCanQueue(true); + + // skip ".." + if (item->IsParentFolder()) + return; + + CFileItemList queuedItems; + MUSIC_UTILS::GetItemsForPlayList(item, queuedItems); + if (g_partyModeManager.IsEnabled()) + { + g_partyModeManager.AddUserSongs(queuedItems, true); + return; + } + + /* + std::string strPlayListDirectory = m_vecItems->GetPath(); + URIUtils::RemoveSlashAtEnd(strPlayListDirectory); + */ + + CServiceBroker::GetPlaylistPlayer().ClearPlaylist(PLAYLIST::TYPE_MUSIC); + CServiceBroker::GetPlaylistPlayer().Reset(); + CServiceBroker::GetPlaylistPlayer().Add(PLAYLIST::TYPE_MUSIC, queuedItems); + CServiceBroker::GetPlaylistPlayer().SetCurrentPlaylist(PLAYLIST::TYPE_MUSIC); + + // play! + CServiceBroker::GetPlaylistPlayer().Play(); + } + else if (pItem->IsPlayList()) + { + // load the playlist the old way + LoadPlayList(pItem->GetPath()); + } + else + { + // just a single item, play it + //! @todo Add music-specific code for single playback of an item here (See OnClick in MediaWindow, and OnPlayMedia below) + OnClick(iItem); + } +} + +void CGUIWindowMusicBase::LoadPlayList(const std::string& strPlayList) +{ + // if partymode is active, we disable it + if (g_partyModeManager.IsEnabled()) + g_partyModeManager.Disable(); + + // load a playlist like .m3u, .pls + // first get correct factory to load playlist + std::unique_ptr<PLAYLIST::CPlayList> pPlayList(PLAYLIST::CPlayListFactory::Create(strPlayList)); + if (pPlayList) + { + // load it + if (!pPlayList->Load(strPlayList)) + { + HELPERS::ShowOKDialogText(CVariant{6}, CVariant{477}); + return; //hmmm unable to load playlist? + } + } + + int iSize = pPlayList->size(); + if (g_application.ProcessAndStartPlaylist(strPlayList, *pPlayList, PLAYLIST::TYPE_MUSIC)) + { + if (m_guiState) + m_guiState->SetPlaylistDirectory("playlistmusic://"); + // activate the playlist window if its not activated yet + if (GetID() == CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() && iSize > 1) + { + CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_MUSIC_PLAYLIST); + } + } +} + +bool CGUIWindowMusicBase::OnPlayMedia(int iItem, const std::string &player) +{ + CFileItemPtr pItem = m_vecItems->Get(iItem); + + // party mode + if (g_partyModeManager.IsEnabled()) + { + PLAYLIST::CPlayList playlistTemp; + playlistTemp.Add(pItem); + g_partyModeManager.AddUserSongs(playlistTemp, !CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_MUSICPLAYER_QUEUEBYDEFAULT)); + return true; + } + else if (!pItem->IsPlayList() && !pItem->IsInternetStream()) + { // single music file - if we get here then we have autoplaynextitem turned off or queuebydefault + // turned on, but we still want to use the playlist player in order to handle more queued items + // following etc. + if ( (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_MUSICPLAYER_QUEUEBYDEFAULT) && CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() != WINDOW_MUSIC_PLAYLIST_EDITOR) ) + { + //! @todo Should the playlist be cleared if nothing is already playing? + OnQueueItem(iItem); + return true; + } + pItem->SetProperty("playlist_type_hint", m_guiState->GetPlaylist()); + CServiceBroker::GetPlaylistPlayer().Play(pItem, player); + return true; + } + return CGUIMediaWindow::OnPlayMedia(iItem, player); +} + +/// \brief Can be overwritten to implement an own tag filling function. +/// \param items File items to fill +void CGUIWindowMusicBase::OnRetrieveMusicInfo(CFileItemList& items) +{ + // No need to attempt to read music file tags for music videos + if (items.IsVideoDb()) + return; + if (items.GetFolderCount()==items.Size() || items.IsMusicDb() || + (!CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_MUSICFILES_USETAGS) && !items.IsCDDA())) + { + return; + } + // Start the music info loader thread + m_musicInfoLoader.SetProgressCallback(m_dlgProgress); + m_musicInfoLoader.Load(items); + + bool bShowProgress = !CServiceBroker::GetGUI()->GetWindowManager().HasModalDialog(true); + bool bProgressVisible = false; + + auto start = std::chrono::steady_clock::now(); + + while (m_musicInfoLoader.IsLoading()) + { + if (bShowProgress) + { // Do we have to init a progress dialog? + auto end = std::chrono::steady_clock::now(); + auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start); + + if (!bProgressVisible && duration.count() > 1500 && m_dlgProgress) + { // tag loading takes more then 1.5 secs, show a progress dialog + CURL url(items.GetPath()); + m_dlgProgress->SetHeading(CVariant{189}); + m_dlgProgress->SetLine(0, CVariant{505}); + m_dlgProgress->SetLine(1, CVariant{""}); + m_dlgProgress->SetLine(2, CVariant{url.GetWithoutUserDetails()}); + m_dlgProgress->Open(); + m_dlgProgress->ShowProgressBar(true); + bProgressVisible = true; + } + + if (bProgressVisible && m_dlgProgress && !m_dlgProgress->IsCanceled()) + { // keep GUI alive + m_dlgProgress->Progress(); + } + } // if (bShowProgress) + KODI::TIME::Sleep(1ms); + } // while (m_musicInfoLoader.IsLoading()) + + if (bProgressVisible && m_dlgProgress) + m_dlgProgress->Close(); +} + +bool CGUIWindowMusicBase::GetDirectory(const std::string &strDirectory, CFileItemList &items) +{ + items.ClearArt(); + bool bResult = CGUIMediaWindow::GetDirectory(strDirectory, items); + if (bResult) + { + // We want to expand disc images when browsing in file view but not on library, smartplaylist + // or node menu music windows + if (!items.GetPath().empty() && !StringUtils::StartsWithNoCase(items.GetPath(), "musicdb://") && + !StringUtils::StartsWithNoCase(items.GetPath(), "special://") && + !StringUtils::StartsWithNoCase(items.GetPath(), "library://")) + CDirectory::FilterFileDirectories(items, ".iso", true); + + CMusicThumbLoader loader; + loader.FillThumb(items); + + CQueryParams params; + CDirectoryNode::GetDatabaseInfo(items.GetPath(), params); + + // Get art for directory when album or artist + bool artfound = false; + std::vector<ArtForThumbLoader> art; + if (params.GetAlbumId() > 0) + { // Get album and related artist(s) art + artfound = m_musicdatabase.GetArtForItem(-1, params.GetAlbumId(), -1, false, art); + } + else if (params.GetArtistId() > 0) + { // get artist art + artfound = m_musicdatabase.GetArtForItem(-1, -1, params.GetArtistId(), true, art); + } + if (artfound) + { + std::string dirType = MediaTypeArtist; + if (params.GetAlbumId() > 0) + dirType = MediaTypeAlbum; + std::map<std::string, std::string> artmap; + for (auto artitem : art) + { + std::string artname; + if (dirType == artitem.mediaType) + artname = artitem.artType; + else if (artitem.prefix.empty()) + artname = artitem.mediaType + "." + artitem.artType; + else + { + if (dirType == MediaTypeAlbum) + StringUtils::Replace(artitem.prefix, "albumartist", "artist"); + artname = artitem.prefix + "." + artitem.artType; + } + artmap.insert(std::make_pair(artname, artitem.url)); + } + items.SetArt(artmap); + } + + int iWindow = GetID(); + // Add "New Playlist" items when in the playlists folder, except on playlist editor screen + if ((iWindow != WINDOW_MUSIC_PLAYLIST_EDITOR) && + (items.GetPath() == "special://musicplaylists/") && !items.Contains("newplaylist://")) + { + const std::shared_ptr<CProfileManager> profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager(); + + CFileItemPtr newPlaylist(new CFileItem(profileManager->GetUserDataItem("PartyMode.xsp"),false)); + newPlaylist->SetLabel(g_localizeStrings.Get(16035)); + newPlaylist->SetLabelPreformatted(true); + newPlaylist->SetArt("icon", "DefaultPartyMode.png"); + newPlaylist->m_bIsFolder = true; + items.Add(newPlaylist); + + newPlaylist.reset(new CFileItem("newplaylist://", false)); + newPlaylist->SetLabel(g_localizeStrings.Get(525)); + newPlaylist->SetArt("icon", "DefaultAddSource.png"); + newPlaylist->SetLabelPreformatted(true); + newPlaylist->SetSpecialSort(SortSpecialOnBottom); + newPlaylist->SetCanQueue(false); + items.Add(newPlaylist); + + newPlaylist.reset(new CFileItem("newsmartplaylist://music", false)); + newPlaylist->SetLabel(g_localizeStrings.Get(21437)); + newPlaylist->SetArt("icon", "DefaultAddSource.png"); + newPlaylist->SetLabelPreformatted(true); + newPlaylist->SetSpecialSort(SortSpecialOnBottom); + newPlaylist->SetCanQueue(false); + items.Add(newPlaylist); + } + + // check for .CUE files here. + items.FilterCueItems(); + + std::string label; + if (items.GetLabel().empty() && m_rootDir.IsSource(items.GetPath(), CMediaSourceSettings::GetInstance().GetSources("music"), &label)) + items.SetLabel(label); + } + + return bResult; +} + +bool CGUIWindowMusicBase::CheckFilterAdvanced(CFileItemList &items) const +{ + const std::string& content = items.GetContent(); + if ((items.IsMusicDb() || CanContainFilter(m_strFilterPath)) && + (StringUtils::EqualsNoCase(content, "artists") || + StringUtils::EqualsNoCase(content, "albums") || + StringUtils::EqualsNoCase(content, "songs"))) + return true; + + return false; +} + +bool CGUIWindowMusicBase::CanContainFilter(const std::string &strDirectory) const +{ + return URIUtils::IsProtocol(strDirectory, "musicdb"); +} + +bool CGUIWindowMusicBase::OnSelect(int iItem) +{ + auto item = m_vecItems->Get(iItem); + if (item->IsAudioBook()) + { + int bookmark; + if (m_musicdatabase.GetResumeBookmarkForAudioBook(*item, bookmark) && bookmark > 0) + { + // find which chapter the bookmark belongs to + auto itemIt = + std::find_if(m_vecItems->cbegin(), m_vecItems->cend(), + [&](const CFileItemPtr& item) { return bookmark < item->GetEndOffset(); }); + + if (itemIt != m_vecItems->cend()) + { + // ask the user if they want to play or resume + CContextButtons choices; + choices.Add(MUSIC_SELECT_ACTION_PLAY, 208); // 208 = Play + choices.Add(MUSIC_SELECT_ACTION_RESUME, + StringUtils::Format(g_localizeStrings.Get(12022), // 12022 = Resume from ... + (*itemIt)->GetMusicInfoTag()->GetTitle())); + + auto choice = CGUIDialogContextMenu::Show(choices); + if (choice == MUSIC_SELECT_ACTION_RESUME) + { + (*itemIt)->SetProperty("audiobook_bookmark", bookmark); + return CGUIMediaWindow::OnSelect(static_cast<int>(itemIt - m_vecItems->cbegin())); + } + else if (choice < 0) + return true; + } + } + } + + return CGUIMediaWindow::OnSelect(iItem); +} + +void CGUIWindowMusicBase::OnInitWindow() +{ + CGUIMediaWindow::OnInitWindow(); + // Prompt for rescan of library to read music file tags that were not processed by previous versions + // and accommodate any changes to the way some tags are processed + if (m_musicdatabase.GetMusicNeedsTagScan() != 0) + { + if (CServiceBroker::GetGUI() + ->GetInfoManager() + .GetInfoProviders() + .GetLibraryInfoProvider() + .GetLibraryBool(LIBRARY_HAS_MUSIC) && + !CMusicLibraryQueue::GetInstance().IsScanningLibrary()) + { + // rescan of music library required + if (CGUIDialogYesNo::ShowAndGetInput(CVariant{799}, CVariant{38060})) + { + int flags = CMusicInfoScanner::SCAN_RESCAN; + // When set to fetch information on update enquire about scraping that as well + // It may take some time, so the user may want to do it later by "Query Info For All" + if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_MUSICLIBRARY_DOWNLOADINFO)) + if (CGUIDialogYesNo::ShowAndGetInput(CVariant{799}, CVariant{38061})) + flags |= CMusicInfoScanner::SCAN_ONLINE; + + CMusicLibraryQueue::GetInstance().ScanLibrary("", flags, true); + + m_musicdatabase.SetMusicTagScanVersion(); // once is enough (user may interrupt, but that's up to them) + } + } + else + { + // no need to force a rescan if there's no music in the library or if a library scan is already active + m_musicdatabase.SetMusicTagScanVersion(); + } + } +} + +std::string CGUIWindowMusicBase::GetStartFolder(const std::string &dir) +{ + std::string lower(dir); StringUtils::ToLower(lower); + if (lower == "plugins" || lower == "addons") + return "addons://sources/audio/"; + else if (lower == "$playlists" || lower == "playlists") + return "special://musicplaylists/"; + return CGUIMediaWindow::GetStartFolder(dir); +} + +void CGUIWindowMusicBase::OnScan(int iItem, bool bPromptRescan /*= false*/) +{ + std::string strPath; + if (iItem < 0 || iItem >= m_vecItems->Size()) + strPath = m_vecItems->GetPath(); + else if (m_vecItems->Get(iItem)->m_bIsFolder) + strPath = m_vecItems->Get(iItem)->GetPath(); + else + { //! @todo MUSICDB - should we allow scanning a single item into the database? + //! This will require changes to the info scanner, which assumes we're running on a folder + strPath = m_vecItems->GetPath(); + } + // Ask for full rescan of music files when scan item from file view context menu + bool doRescan = false; + if (bPromptRescan) + doRescan = CGUIDialogYesNo::ShowAndGetInput(CVariant{ 799 }, CVariant{ 38062 }); + + DoScan(strPath, doRescan); +} + +void CGUIWindowMusicBase::DoScan(const std::string &strPath, bool bRescan /*= false*/) +{ + if (CMusicLibraryQueue::GetInstance().IsScanningLibrary()) + { + CMusicLibraryQueue::GetInstance().StopLibraryScanning(); + return; + } + + // Start background loader + int iControl=GetFocusedControlID(); + int flags = 0; + if (bRescan) + flags = CMusicInfoScanner::SCAN_RESCAN; + if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_MUSICLIBRARY_DOWNLOADINFO)) + flags |= CMusicInfoScanner::SCAN_ONLINE; + + CMusicLibraryQueue::GetInstance().ScanLibrary(strPath, flags, true); + + SET_CONTROL_FOCUS(iControl, 0); + UpdateButtons(); +} + +void CGUIWindowMusicBase::OnRemoveSource(int iItem) +{ + + //Remove music source from library, even when leaving songs + CMusicDatabase database; + database.Open(); + database.RemoveSource(m_vecItems->Get(iItem)->GetLabel()); + + bool bCanceled; + if (CGUIDialogYesNo::ShowAndGetInput(CVariant{522}, CVariant{20340}, bCanceled, CVariant{""}, CVariant{""}, CGUIDialogYesNo::NO_TIMEOUT)) + { + MAPSONGS songs; + database.RemoveSongsFromPath(m_vecItems->Get(iItem)->GetPath(), songs, false); + database.CleanupOrphanedItems(); + database.CheckArtistLinksChanged(); + CServiceBroker::GetGUI()->GetInfoManager().GetInfoProviders().GetLibraryInfoProvider().ResetLibraryBools(); + m_vecItems->RemoveDiscCache(GetID()); + } + database.Close(); +} + +void CGUIWindowMusicBase::OnPrepareFileItems(CFileItemList &items) +{ + CGUIMediaWindow::OnPrepareFileItems(items); + + if (!items.IsMusicDb() && !items.IsSmartPlayList()) + RetrieveMusicInfo(); +} + +void CGUIWindowMusicBase::OnAssignContent(const std::string& oldName, const CMediaSource& source) +{ + // Music scrapers are not source specific, so unlike video there is no content selection logic here. + // Called on having added or edited a music source, this starts scanning items into library when required + + //! @todo: do async as updating sources for all albums could be slow?? + //Store music source in the music library, even those not scanned + CMusicDatabase database; + database.Open(); + database.UpdateSource(oldName, source.strName, source.strPath, source.vecPaths); + database.Close(); + + // "Add to library" yes/no dialog with additional "settings" custom button + // "Do you want to add the media from this source to your library?" + DialogResponse rep = DialogResponse::CHOICE_CUSTOM; + while (rep == DialogResponse::CHOICE_CUSTOM) + { + rep = HELPERS::ShowYesNoCustomDialog(CVariant{20444}, CVariant{20447}, CVariant{106}, CVariant{107}, CVariant{10004}); + if (rep == DialogResponse::CHOICE_CUSTOM) + // Edit default info provider settings so can be applied during scan + CGUIDialogInfoProviderSettings::Show(); + } + if (rep == DialogResponse::CHOICE_YES) + CMusicLibraryQueue::GetInstance().ScanLibrary(source.strPath, + MUSIC_INFO::CMusicInfoScanner::SCAN_NORMAL, true); +} + diff --git a/xbmc/music/windows/GUIWindowMusicBase.h b/xbmc/music/windows/GUIWindowMusicBase.h new file mode 100644 index 0000000..85f4931 --- /dev/null +++ b/xbmc/music/windows/GUIWindowMusicBase.h @@ -0,0 +1,106 @@ +/* + * 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 + +/*! +\file GUIWindowMusicBase.h +\brief +*/ + +#include "music/MusicDatabase.h" +#include "music/MusicInfoLoader.h" +#include "music/MusicThumbLoader.h" +#include "music/infoscanner/MusicInfoScraper.h" +#include "windows/GUIMediaWindow.h" + +#include <vector> + +enum MusicSelectAction +{ + MUSIC_SELECT_ACTION_PLAY, + MUSIC_SELECT_ACTION_RESUME, +}; + +/*! + \ingroup windows + \brief The base class for music windows + + CGUIWindowMusicBase is the base class for + all music windows. + */ +class CGUIWindowMusicBase : public CGUIMediaWindow, public IBackgroundLoaderObserver +{ +public: + CGUIWindowMusicBase(int id, const std::string &xmlFile); + ~CGUIWindowMusicBase(void) override; + bool OnMessage(CGUIMessage& message) override; + bool OnAction(const CAction &action) override; + bool OnBack(int actionID) override; + + void DoScan(const std::string &strPath, bool bRescan = false); + void RefreshContent(const std::string& strContent); + + /*! \brief Once a music source is added, store source in library, and prompt + the user to scan this folder into the library + \param oldName the original music source name + \param source details of the music source (just added or edited) + */ + static void OnAssignContent(const std::string& oldName, const CMediaSource& source); + +protected: + void OnInitWindow() override; + /*! + \brief Will be called when an popup context menu has been asked for + \param itemNumber List/thumb control item that has been clicked on + */ + void GetContextButtons(int itemNumber, CContextButtons &buttons) override; + void GetNonContextButtons(CContextButtons &buttons); + bool OnContextButton(int itemNumber, CONTEXT_BUTTON button) override; + bool OnAddMediaSource() override; + /*! + \brief Overwrite to update your gui buttons (visible, enable,...) + */ + void UpdateButtons() override; + + bool GetDirectory(const std::string &strDirectory, CFileItemList &items) override; + virtual void OnRetrieveMusicInfo(CFileItemList& items); + void OnPrepareFileItems(CFileItemList& items) override; + void OnRipCD(); + std::string GetStartFolder(const std::string &dir) override; + void OnItemLoaded(CFileItem* pItem) override {} + + virtual void OnScan(int iItem, bool bPromptRescan = false); + + bool CheckFilterAdvanced(CFileItemList &items) const override; + bool CanContainFilter(const std::string &strDirectory) const override; + + bool OnSelect(int iItem) override; + + // new methods + virtual void PlayItem(int iItem); + bool OnPlayMedia(int iItem, const std::string &player = "") override; + + void RetrieveMusicInfo(); + void OnItemInfo(int iItem); + void OnItemInfoAll(const std::string& strPath, bool refresh = false); + virtual void OnQueueItem(int iItem, bool first = false); + enum ALLOW_SELECTION { SELECTION_ALLOWED = 0, SELECTION_AUTO, SELECTION_FORCED }; + + void OnRipTrack(int iItem); + void LoadPlayList(const std::string& strPlayList) override; + virtual void OnRemoveSource(int iItem); + + typedef std::vector <CFileItem*>::iterator ivecItems; ///< CFileItem* vector Iterator + CGUIDialogProgress* m_dlgProgress; ///< Progress dialog + + CMusicDatabase m_musicdatabase; + MUSIC_INFO::CMusicInfoLoader m_musicInfoLoader; + + CMusicThumbLoader m_thumbLoader; +}; diff --git a/xbmc/music/windows/GUIWindowMusicNav.cpp b/xbmc/music/windows/GUIWindowMusicNav.cpp new file mode 100644 index 0000000..f8ba5a4 --- /dev/null +++ b/xbmc/music/windows/GUIWindowMusicNav.cpp @@ -0,0 +1,944 @@ +/* + * 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 "GUIWindowMusicNav.h" + +#include "FileItem.h" +#include "GUIPassword.h" +#include "GUIUserMessages.h" +#include "PartyModeManager.h" +#include "ServiceBroker.h" +#include "URL.h" +#include "Util.h" +#include "addons/AddonSystemSettings.h" +#include "dialogs/GUIDialogYesNo.h" +#include "filesystem/MusicDatabaseDirectory.h" +#include "filesystem/VideoDatabaseDirectory.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIEditControl.h" +#include "guilib/GUIKeyboardFactory.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/LocalizeStrings.h" +#include "input/Key.h" +#include "messaging/ApplicationMessenger.h" +#include "messaging/helpers/DialogOKHelper.h" +#include "music/MusicLibraryQueue.h" +#include "music/dialogs/GUIDialogInfoProviderSettings.h" +#include "music/tags/MusicInfoTag.h" +#include "playlists/PlayList.h" +#include "playlists/PlayListFactory.h" +#include "profiles/ProfileManager.h" +#include "settings/AdvancedSettings.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "storage/MediaManager.h" +#include "utils/FileUtils.h" +#include "utils/LegacyPathTranslation.h" +#include "utils/StringUtils.h" +#include "utils/URIUtils.h" +#include "utils/Variant.h" +#include "utils/log.h" +#include "video/VideoDatabase.h" +#include "video/dialogs/GUIDialogVideoInfo.h" +#include "video/windows/GUIWindowVideoNav.h" +#include "view/GUIViewState.h" + +using namespace XFILE; +using namespace PLAYLIST; +using namespace MUSICDATABASEDIRECTORY; +using namespace KODI::MESSAGING; + +#define CONTROL_BTNVIEWASICONS 2 +#define CONTROL_BTNSORTBY 3 +#define CONTROL_BTNSORTASC 4 +#define CONTROL_BTNTYPE 5 +#define CONTROL_LABELFILES 12 + +#define CONTROL_SEARCH 8 +#define CONTROL_FILTER 15 +#define CONTROL_BTNPARTYMODE 16 +#define CONTROL_BTNMANUALINFO 17 +#define CONTROL_BTN_FILTER 19 + +#define CONTROL_UPDATE_LIBRARY 20 + +CGUIWindowMusicNav::CGUIWindowMusicNav(void) + : CGUIWindowMusicBase(WINDOW_MUSIC_NAV, "MyMusicNav.xml") +{ + m_vecItems->SetPath("?"); + m_searchWithEdit = false; +} + +CGUIWindowMusicNav::~CGUIWindowMusicNav(void) = default; + +bool CGUIWindowMusicNav::OnMessage(CGUIMessage& message) +{ + switch (message.GetMessage()) + { + case GUI_MSG_WINDOW_RESET: + m_vecItems->SetPath("?"); + break; + case GUI_MSG_WINDOW_INIT: + { + /* We don't want to show Autosourced items (ie removable pendrives, memorycards) in Library mode */ + m_rootDir.AllowNonLocalSources(false); + + // is this the first time the window is opened? + if (m_vecItems->GetPath() == "?" && message.GetStringParam().empty()) + message.SetStringParam(CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_MYMUSIC_DEFAULTLIBVIEW)); + + if (!CGUIWindowMusicBase::OnMessage(message)) + return false; + + if (message.GetStringParam(0) != "") + { + CURL url(message.GetStringParam(0)); + + int i = 0; + for (; i < m_vecItems->Size(); i++) + { + CFileItemPtr pItem = m_vecItems->Get(i); + + // skip ".." + if (pItem->IsParentFolder()) + continue; + + if (URIUtils::PathEquals(pItem->GetPath(), message.GetStringParam(0), true, true)) + { + m_viewControl.SetSelectedItem(i); + i = -1; + if (url.GetOption("showinfo") == "true") + OnItemInfo(i); + break; + } + } + } + + return true; + } + break; + + case GUI_MSG_CLICKED: + { + int iControl = message.GetSenderId(); + if (iControl == CONTROL_BTNPARTYMODE) + { + if (g_partyModeManager.IsEnabled()) + g_partyModeManager.Disable(); + else + { + if (!g_partyModeManager.Enable()) + { + SET_CONTROL_SELECTED(GetID(),CONTROL_BTNPARTYMODE,false); + return false; + } + + // Playlist directory is the root of the playlist window + if (m_guiState) + m_guiState->SetPlaylistDirectory("playlistmusic://"); + + return true; + } + UpdateButtons(); + } + else if (iControl == CONTROL_SEARCH) + { + if (m_searchWithEdit) + { + // search updated - reset timer + m_searchTimer.StartZero(); + // grab our search string + CGUIMessage selected(GUI_MSG_ITEM_SELECTED, GetID(), CONTROL_SEARCH); + OnMessage(selected); + SetProperty("search", selected.GetLabel()); + return true; + } + std::string search(GetProperty("search").asString()); + CGUIKeyboardFactory::ShowAndGetFilter(search, true); + SetProperty("search", search); + return true; + } + else if (iControl == CONTROL_UPDATE_LIBRARY) + { + if (!CMusicLibraryQueue::GetInstance().IsScanningLibrary()) + CMusicLibraryQueue::GetInstance().ScanLibrary(""); + else + CMusicLibraryQueue::GetInstance().StopLibraryScanning(); + return true; + } + } + break; + case GUI_MSG_PLAYBACK_STOPPED: + case GUI_MSG_PLAYBACK_ENDED: + case GUI_MSG_PLAYLISTPLAYER_STOPPED: + case GUI_MSG_PLAYBACK_STARTED: + { + SET_CONTROL_SELECTED(GetID(),CONTROL_BTNPARTYMODE, g_partyModeManager.IsEnabled()); + } + break; + case GUI_MSG_NOTIFY_ALL: + { + if (message.GetParam1() == GUI_MSG_SEARCH_UPDATE && IsActive()) + { + // search updated - reset timer + m_searchTimer.StartZero(); + SetProperty("search", message.GetStringParam()); + } + } + } + return CGUIWindowMusicBase::OnMessage(message); +} + +bool CGUIWindowMusicNav::OnAction(const CAction& action) +{ + if (action.GetID() == ACTION_SCAN_ITEM) + { + int item = m_viewControl.GetSelectedItem(); + CMusicDatabaseDirectory dir; + if (item > -1 && m_vecItems->Get(item)->m_bIsFolder + && (m_vecItems->Get(item)->IsAlbum()|| + dir.IsArtistDir(m_vecItems->Get(item)->GetPath()))) + { + OnContextButton(item,CONTEXT_BUTTON_INFO); + return true; + } + } + + return CGUIWindowMusicBase::OnAction(action); +} + +bool CGUIWindowMusicNav::ManageInfoProvider(const CFileItemPtr& item) +{ + CQueryParams params; + CDirectoryNode::GetDatabaseInfo(item->GetPath(), params); + // Management of Info provider only valid for specific artist or album items + if (params.GetAlbumId() == -1 && params.GetArtistId() == -1) + return false; + + // Set things up for processing artist or albums + CONTENT_TYPE content = CONTENT_ALBUMS; + int id = params.GetAlbumId(); + if (id == -1) + { + content = CONTENT_ARTISTS; + id = params.GetArtistId(); + } + + ADDON::ScraperPtr scraper; + // Get specific scraper and settings for current item or use default + if (!m_musicdatabase.GetScraper(id, content, scraper)) + { + ADDON::AddonPtr defaultScraper; + if (ADDON::CAddonSystemSettings::GetInstance().GetActive( + ADDON::ScraperTypeFromContent(content), defaultScraper)) + { + scraper = std::dynamic_pointer_cast<ADDON::CScraper>(defaultScraper); + } + } + + // Set Information provider and settings + int applyto = CGUIDialogInfoProviderSettings::Show(scraper); + if (applyto >= 0) + { + bool result = false; + CVariant msgctxt; + switch (applyto) + { + case INFOPROVIDERAPPLYOPTIONS::INFOPROVIDER_THISITEM: // Change information provider for specific item + result = m_musicdatabase.SetScraper(id, content, scraper); + break; + case INFOPROVIDERAPPLYOPTIONS::INFOPROVIDER_ALLVIEW: // Change information provider for the filtered items shown on this node + { + msgctxt = 38069; + if (content == CONTENT_ARTISTS) + msgctxt = 38068; + if (CGUIDialogYesNo::ShowAndGetInput(CVariant{ 20195 }, msgctxt)) // Change information provider, confirm for all shown + { + // Set scraper for all items on current view. + std::string strPath = "musicdb://"; + if (content == CONTENT_ARTISTS) + strPath += "artists"; + else + strPath += "albums"; + URIUtils::AddSlashAtEnd(strPath); + // Items on view could be limited by navigation criteria, smart playlist rules or a filter. + // Get these options, except ID, from item path + CURL musicUrl(item->GetPath()); //Use CURL, as CMusicDbUrl removes "filter" option + if (content == CONTENT_ARTISTS) + musicUrl.RemoveOption("artistid"); + else + musicUrl.RemoveOption("albumid"); + strPath += musicUrl.GetOptions(); + result = m_musicdatabase.SetScraperAll(strPath, scraper); + } + } + break; + case INFOPROVIDERAPPLYOPTIONS::INFOPROVIDER_DEFAULT: // Change information provider for all items + { + msgctxt = 38071; + if (content == CONTENT_ARTISTS) + msgctxt = 38070; + if (CGUIDialogYesNo::ShowAndGetInput(CVariant{20195}, msgctxt)) // Change information provider, confirm default and clear + { + // Save scraper addon default setting values + scraper->SaveSettings(); + // Set default scraper + const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings(); + if (content == CONTENT_ARTISTS) + settings->SetString(CSettings::SETTING_MUSICLIBRARY_ARTISTSSCRAPER, scraper->ID()); + else + settings->SetString(CSettings::SETTING_MUSICLIBRARY_ALBUMSSCRAPER, scraper->ID()); + settings->Save(); + // Clear all item specific settings + if (content == CONTENT_ARTISTS) + result = m_musicdatabase.SetScraperAll("musicdb://artists/", nullptr); + else + result = m_musicdatabase.SetScraperAll("musicdb://albums/", nullptr); + } + } + default: + break; + } + if (!result) + return false; + + // Refresh additional information using the new settings + if (applyto == INFOPROVIDERAPPLYOPTIONS::INFOPROVIDER_ALLVIEW || applyto == INFOPROVIDERAPPLYOPTIONS::INFOPROVIDER_DEFAULT) + { + // Change information provider, all artists or albums + if (CGUIDialogYesNo::ShowAndGetInput(CVariant{20195}, CVariant{38072})) + OnItemInfoAll(m_vecItems->GetPath(), true); + } + else + { + // Change information provider, selected artist or album + if (CGUIDialogYesNo::ShowAndGetInput(CVariant{20195}, CVariant{38073})) + { + std::string itempath = StringUtils::Format("musicdb://albums/{}/", id); + if (content == CONTENT_ARTISTS) + itempath = StringUtils::Format("musicdb://artists/{}/", id); + OnItemInfoAll(itempath, true); + } + } + } + return true; +} + +bool CGUIWindowMusicNav::OnClick(int iItem, const std::string &player /* = "" */) +{ + if (iItem < 0 || iItem >= m_vecItems->Size()) return false; + + CFileItemPtr item = m_vecItems->Get(iItem); + if (StringUtils::StartsWith(item->GetPath(), "musicsearch://")) + { + if (m_searchWithEdit) + OnSearchUpdate(); + else + { + std::string search(GetProperty("search").asString()); + CGUIKeyboardFactory::ShowAndGetFilter(search, true); + SetProperty("search", search); + } + return true; + } + if (item->IsMusicDb() && !item->m_bIsFolder) + m_musicdatabase.SetPropertiesForFileItem(*item); + + if (item->IsPlayList() && + !CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_playlistAsFolders) + { + PlayItem(iItem); + return true; + } + return CGUIWindowMusicBase::OnClick(iItem, player); +} + +bool CGUIWindowMusicNav::Update(const std::string &strDirectory, bool updateFilterPath /* = true */) +{ + if (m_thumbLoader.IsLoading()) + m_thumbLoader.StopThread(); + + if (CGUIWindowMusicBase::Update(strDirectory, updateFilterPath)) + { + m_thumbLoader.Load(*m_unfilteredItems); + return true; + } + + return false; +} + +bool CGUIWindowMusicNav::GetDirectory(const std::string &strDirectory, CFileItemList &items) +{ + if (strDirectory.empty()) + AddSearchFolder(); + + bool bResult = CGUIWindowMusicBase::GetDirectory(strDirectory, items); + if (bResult) + { + if (items.IsPlayList()) + OnRetrieveMusicInfo(items); + } + + // update our content in the info manager + if (StringUtils::StartsWithNoCase(strDirectory, "videodb://") || items.IsVideoDb()) + { + CVideoDatabaseDirectory dir; + VIDEODATABASEDIRECTORY::NODE_TYPE node = dir.GetDirectoryChildType(items.GetPath()); + if (node == VIDEODATABASEDIRECTORY::NODE_TYPE_TITLE_MUSICVIDEOS || + node == VIDEODATABASEDIRECTORY::NODE_TYPE_RECENTLY_ADDED_MUSICVIDEOS) + items.SetContent("musicvideos"); + else if (node == VIDEODATABASEDIRECTORY::NODE_TYPE_GENRE) + items.SetContent("genres"); + else if (node == VIDEODATABASEDIRECTORY::NODE_TYPE_COUNTRY) + items.SetContent("countries"); + else if (node == VIDEODATABASEDIRECTORY::NODE_TYPE_ACTOR) + items.SetContent("artists"); + else if (node == VIDEODATABASEDIRECTORY::NODE_TYPE_DIRECTOR) + items.SetContent("directors"); + else if (node == VIDEODATABASEDIRECTORY::NODE_TYPE_STUDIO) + items.SetContent("studios"); + else if (node == VIDEODATABASEDIRECTORY::NODE_TYPE_YEAR) + items.SetContent("years"); + else if (node == VIDEODATABASEDIRECTORY::NODE_TYPE_MUSICVIDEOS_ALBUM) + items.SetContent("albums"); + else if (node == VIDEODATABASEDIRECTORY::NODE_TYPE_TAGS) + items.SetContent("tags"); + else + items.SetContent(""); + } + else if (StringUtils::StartsWithNoCase(strDirectory, "musicdb://") || items.IsMusicDb()) + { + CMusicDatabaseDirectory dir; + NODE_TYPE node = dir.GetDirectoryChildType(items.GetPath()); + if (node == NODE_TYPE_ALBUM || + node == NODE_TYPE_ALBUM_RECENTLY_ADDED || + node == NODE_TYPE_ALBUM_RECENTLY_PLAYED || + node == NODE_TYPE_ALBUM_TOP100 || + node == NODE_TYPE_DISC) // ! @todo: own content type "discs"?? + items.SetContent("albums"); + else if (node == NODE_TYPE_ARTIST) + items.SetContent("artists"); + else if (node == NODE_TYPE_SONG || + node == NODE_TYPE_SONG_TOP100 || + node == NODE_TYPE_SINGLES || + node == NODE_TYPE_ALBUM_RECENTLY_ADDED_SONGS || + node == NODE_TYPE_ALBUM_RECENTLY_PLAYED_SONGS || + node == NODE_TYPE_ALBUM_TOP100_SONGS) + items.SetContent("songs"); + else if (node == NODE_TYPE_GENRE) + items.SetContent("genres"); + else if (node == NODE_TYPE_SOURCE) + items.SetContent("sources"); + else if (node == NODE_TYPE_ROLE) + items.SetContent("roles"); + else if (node == NODE_TYPE_YEAR) + items.SetContent("years"); + else + items.SetContent(""); + } + else if (items.IsPlayList()) + items.SetContent("songs"); + else if (URIUtils::PathEquals(strDirectory, "special://musicplaylists/") || + URIUtils::PathEquals(strDirectory, "library://music/playlists.xml/")) + items.SetContent("playlists"); + else if (URIUtils::PathEquals(strDirectory, "plugin://music/")) + items.SetContent("plugins"); + else if (items.IsAddonsPath()) + items.SetContent("addons"); + else if (!items.IsSourcesPath() && !items.IsVirtualDirectoryRoot() && + !items.IsLibraryFolder() && !items.IsPlugin() && !items.IsSmartPlayList()) + items.SetContent("files"); + + return bResult; +} + +void CGUIWindowMusicNav::UpdateButtons() +{ + CGUIWindowMusicBase::UpdateButtons(); + + // Update object count + int iItems = m_vecItems->Size(); + if (iItems) + { + // check for parent dir and "all" items + // should always be the first two items + for (int i = 0; i <= (iItems>=2 ? 1 : 0); i++) + { + CFileItemPtr pItem = m_vecItems->Get(i); + if (pItem->IsParentFolder()) iItems--; + if (StringUtils::StartsWith(pItem->GetPath(), "/-1/")) iItems--; + } + // or the last item + if (m_vecItems->Size() > 2 && + StringUtils::StartsWith(m_vecItems->Get(m_vecItems->Size()-1)->GetPath(), "/-1/")) + iItems--; + } + std::string items = StringUtils::Format("{} {}", iItems, g_localizeStrings.Get(127)); + SET_CONTROL_LABEL(CONTROL_LABELFILES, items); + + // set the filter label + std::string strLabel; + + // "Playlists" + if (m_vecItems->IsPath("special://musicplaylists/")) + strLabel = g_localizeStrings.Get(136); + // "{Playlist Name}" + else if (m_vecItems->IsPlayList()) + { + // get playlist name from path + std::string strDummy; + URIUtils::Split(m_vecItems->GetPath(), strDummy, strLabel); + } + // everything else is from a musicdb:// path + else + { + CMusicDatabaseDirectory dir; + dir.GetLabel(m_vecItems->GetPath(), strLabel); + } + + SET_CONTROL_LABEL(CONTROL_FILTER, strLabel); + + SET_CONTROL_SELECTED(GetID(),CONTROL_BTNPARTYMODE, g_partyModeManager.IsEnabled()); + + CONTROL_ENABLE_ON_CONDITION(CONTROL_UPDATE_LIBRARY, !m_vecItems->IsAddonsPath() && !m_vecItems->IsPlugin() && !m_vecItems->IsScript()); +} + +void CGUIWindowMusicNav::PlayItem(int iItem) +{ + // unlike additemtoplaylist, we need to check the items here + // before calling it since the current playlist will be stopped + // and cleared! + + // root is not allowed + if (m_vecItems->IsVirtualDirectoryRoot() && !m_vecItems->Get(iItem)->IsDVD()) + return; + + CGUIWindowMusicBase::PlayItem(iItem); +} + +void CGUIWindowMusicNav::OnWindowLoaded() +{ + const CGUIControl *control = GetControl(CONTROL_SEARCH); + m_searchWithEdit = (control && control->GetControlType() == CGUIControl::GUICONTROL_EDIT); + + CGUIWindowMusicBase::OnWindowLoaded(); + + if (m_searchWithEdit) + { + SendMessage(GUI_MSG_SET_TYPE, CONTROL_SEARCH, CGUIEditControl::INPUT_TYPE_SEARCH); + SET_CONTROL_LABEL2(CONTROL_SEARCH, GetProperty("search").asString()); + } +} + +void CGUIWindowMusicNav::GetContextButtons(int itemNumber, CContextButtons &buttons) +{ + CFileItemPtr item; + if (itemNumber >= 0 && itemNumber < m_vecItems->Size()) + item = m_vecItems->Get(itemNumber); + if (item) + { + const std::shared_ptr<CProfileManager> profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager(); + + // are we in the playlists location? + bool inPlaylists = m_vecItems->IsPath(CUtil::MusicPlaylistsLocation()) || + m_vecItems->IsPath("special://musicplaylists/"); + + if (m_vecItems->IsPath("sources://music/")) + { + // get the usual music shares, and anything for all media windows + CGUIDialogContextMenu::GetContextButtons("music", item, buttons); +#ifdef HAS_DVD_DRIVE + // enable Rip CD an audio disc + if (CServiceBroker::GetMediaManager().IsDiscInDrive() && item->IsCDDA()) + { + // those cds can also include Audio Tracks: CDExtra and MixedMode! + MEDIA_DETECT::CCdInfo* pCdInfo = CServiceBroker::GetMediaManager().GetCdInfo(); + if (pCdInfo->IsAudio(1) || pCdInfo->IsCDExtra(1) || pCdInfo->IsMixedMode(1)) + { + if (CServiceBroker::GetJobManager()->IsProcessing("cdrip")) + buttons.Add(CONTEXT_BUTTON_CANCEL_RIP_CD, 14100); + else + buttons.Add(CONTEXT_BUTTON_RIP_CD, 600); + } + } +#endif + // Scan button for music sources except ".." and "Add music source" items + if (!item->IsPath("add") && !item->IsParentFolder() && + (profileManager->GetCurrentProfile().canWriteDatabases() || g_passwordManager.bMasterUser)) + { + buttons.Add(CONTEXT_BUTTON_SCAN, 13352); + } + CGUIMediaWindow::GetContextButtons(itemNumber, buttons); + } + else + { + CGUIWindowMusicBase::GetContextButtons(itemNumber, buttons); + + // Scan button for real folders containing files when navigating within music sources. + // Blacklist the bespoke Kodi protocols as to many valid external protocols to whitelist + if (m_vecItems->GetContent() == "files" && // Other content not scanned to library + !inPlaylists && !m_vecItems->IsInternetStream() && // Not playlists locations or streams + !item->IsPath("add") && !item->IsParentFolder() && // Not ".." and "Add items + item->m_bIsFolder && // Folders only, but playlists can be folders too + !URIUtils::IsLibraryContent(item->GetPath()) && // database folder or .xsp files + !URIUtils::IsSpecial(item->GetPath()) && !item->IsPlugin() && !item->IsScript() && + !item->IsPlayList() && // .m3u etc. that as flagged as folders when playlistasfolders + !StringUtils::StartsWithNoCase(item->GetPath(), "addons://") && + (profileManager->GetCurrentProfile().canWriteDatabases() || + g_passwordManager.bMasterUser)) + { + buttons.Add(CONTEXT_BUTTON_SCAN, 13352); + } + + CMusicDatabaseDirectory dir; + + if (!item->IsParentFolder() && !dir.IsAllItem(item->GetPath())) + { + if (item->m_bIsFolder && !item->IsVideoDb() && + !item->IsPlugin() && !StringUtils::StartsWithNoCase(item->GetPath(), "musicsearch://")) + { + if (item->IsAlbum()) + // enable query all albums button only in album view + buttons.Add(CONTEXT_BUTTON_INFO_ALL, 20059); + else if (dir.IsArtistDir(item->GetPath())) + // enable query all artist button only in artist view + buttons.Add(CONTEXT_BUTTON_INFO_ALL, 21884); + + //Set default or clear default + NODE_TYPE nodetype = dir.GetDirectoryType(item->GetPath()); + if (!inPlaylists && + (nodetype == NODE_TYPE_ROOT || + nodetype == NODE_TYPE_OVERVIEW || + nodetype == NODE_TYPE_TOP100)) + { + const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings(); + if (!item->IsPath(settings->GetString(CSettings::SETTING_MYMUSIC_DEFAULTLIBVIEW))) + buttons.Add(CONTEXT_BUTTON_SET_DEFAULT, 13335); // set default + if (!settings->GetString(CSettings::SETTING_MYMUSIC_DEFAULTLIBVIEW).empty()) + buttons.Add(CONTEXT_BUTTON_CLEAR_DEFAULT, 13403); // clear default + } + + //Change information provider + if (StringUtils::EqualsNoCase(m_vecItems->GetContent(), "albums") || + StringUtils::EqualsNoCase(m_vecItems->GetContent(), "artists")) + { + // we allow the user to set information provider for albums and artists + buttons.Add(CONTEXT_BUTTON_SET_CONTENT, 20195); + } + } + if (item->HasMusicInfoTag() && !item->GetMusicInfoTag()->GetArtistString().empty()) + { + CVideoDatabase database; + database.Open(); + if (database.GetMatchingMusicVideo(item->GetMusicInfoTag()->GetArtistString()) > -1) + buttons.Add(CONTEXT_BUTTON_GO_TO_ARTIST, 20400); + } + if (item->HasMusicInfoTag() && !item->GetMusicInfoTag()->GetArtistString().empty() && + !item->GetMusicInfoTag()->GetAlbum().empty() && + !item->GetMusicInfoTag()->GetTitle().empty()) + { + CVideoDatabase database; + database.Open(); + if (database.GetMatchingMusicVideo(item->GetMusicInfoTag()->GetArtistString(), item->GetMusicInfoTag()->GetAlbum(), item->GetMusicInfoTag()->GetTitle()) > -1) + buttons.Add(CONTEXT_BUTTON_PLAY_OTHER, 20401); + } + if (item->HasVideoInfoTag() && !item->m_bIsFolder) + { + if ((profileManager->GetCurrentProfile().canWriteDatabases() || g_passwordManager.bMasterUser) && !item->IsPlugin()) + { + buttons.Add(CONTEXT_BUTTON_RENAME, 16105); + buttons.Add(CONTEXT_BUTTON_DELETE, 646); + } + } + if (inPlaylists && URIUtils::GetFileName(item->GetPath()) != "PartyMode.xsp" + && (item->IsPlayList() || item->IsSmartPlayList())) + buttons.Add(CONTEXT_BUTTON_DELETE, 117); + + if (!item->IsReadOnly() && CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool("filelists.allowfiledeletion")) + { + buttons.Add(CONTEXT_BUTTON_DELETE, 117); + buttons.Add(CONTEXT_BUTTON_RENAME, 118); + } + } + } + } + // noncontextual buttons + + CGUIWindowMusicBase::GetNonContextButtons(buttons); +} + +bool CGUIWindowMusicNav::OnContextButton(int itemNumber, CONTEXT_BUTTON button) +{ + CFileItemPtr item; + if (itemNumber >= 0 && itemNumber < m_vecItems->Size()) + item = m_vecItems->Get(itemNumber); + + switch (button) + { + case CONTEXT_BUTTON_INFO: + { + if (!item->IsVideoDb()) + return CGUIWindowMusicBase::OnContextButton(itemNumber,button); + + // music videos - artists + if (StringUtils::StartsWithNoCase(item->GetPath(), "videodb://musicvideos/artists/")) + { + int idArtist = m_musicdatabase.GetArtistByName(item->GetLabel()); + if (idArtist == -1) + return false; + std::string path = StringUtils::Format("musicdb://artists/{}/", idArtist); + CArtist artist; + m_musicdatabase.GetArtist(idArtist, artist, false); + *item = CFileItem(artist); + item->SetPath(path); + CGUIWindowMusicBase::OnContextButton(itemNumber,button); + Refresh(); + m_viewControl.SetSelectedItem(itemNumber); + return true; + } + + // music videos - albums + if (StringUtils::StartsWithNoCase(item->GetPath(), "videodb://musicvideos/albums/")) + { + int idAlbum = m_musicdatabase.GetAlbumByName(item->GetLabel()); + if (idAlbum == -1) + return false; + std::string path = StringUtils::Format("musicdb://albums/{}/", idAlbum); + CAlbum album; + m_musicdatabase.GetAlbum(idAlbum, album, false); + *item = CFileItem(path,album); + item->SetPath(path); + CGUIWindowMusicBase::OnContextButton(itemNumber,button); + Refresh(); + m_viewControl.SetSelectedItem(itemNumber); + return true; + } + + if (item->HasVideoInfoTag() && !item->GetVideoInfoTag()->m_strTitle.empty()) + { + CGUIDialogVideoInfo::ShowFor(*item); + Refresh(); + } + return true; + } + + case CONTEXT_BUTTON_INFO_ALL: + OnItemInfoAll(m_vecItems->GetPath()); + return true; + + case CONTEXT_BUTTON_SET_DEFAULT: + { + const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings(); + settings->SetString(CSettings::SETTING_MYMUSIC_DEFAULTLIBVIEW, item->GetPath()); + settings->Save(); + return true; + } + + case CONTEXT_BUTTON_CLEAR_DEFAULT: + { + const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings(); + settings->SetString(CSettings::SETTING_MYMUSIC_DEFAULTLIBVIEW, ""); + settings->Save(); + return true; + } + + case CONTEXT_BUTTON_GO_TO_ARTIST: + { + std::string strPath; + CVideoDatabase database; + database.Open(); + strPath = StringUtils::Format( + "videodb://musicvideos/artists/{}/", + database.GetMatchingMusicVideo(item->GetMusicInfoTag()->GetArtistString())); + CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_VIDEO_NAV,strPath); + return true; + } + + case CONTEXT_BUTTON_PLAY_OTHER: + { + CVideoDatabase database; + database.Open(); + CVideoInfoTag details; + database.GetMusicVideoInfo("", details, database.GetMatchingMusicVideo(item->GetMusicInfoTag()->GetArtistString(), item->GetMusicInfoTag()->GetAlbum(), item->GetMusicInfoTag()->GetTitle())); + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_MEDIA_PLAY, 0, 0, + static_cast<void*>(new CFileItem(details))); + return true; + } + + case CONTEXT_BUTTON_RENAME: + if (!item->IsVideoDb() && !item->IsReadOnly()) + OnRenameItem(itemNumber); + + CGUIDialogVideoInfo::UpdateVideoItemTitle(item); + CUtil::DeleteVideoDatabaseDirectoryCache(); + Refresh(); + return true; + + case CONTEXT_BUTTON_DELETE: + if (item->IsPlayList() || item->IsSmartPlayList()) + { + item->m_bIsFolder = false; + CGUIComponent *gui = CServiceBroker::GetGUI(); + if (gui && gui->ConfirmDelete(item->GetPath())) + CFileUtils::DeleteItem(item); + } + else if (!item->IsVideoDb()) + OnDeleteItem(itemNumber); + else + { + CGUIDialogVideoInfo::DeleteVideoItemFromDatabase(item); + CUtil::DeleteVideoDatabaseDirectoryCache(); + } + Refresh(); + return true; + + case CONTEXT_BUTTON_SET_CONTENT: + return ManageInfoProvider(item); + + default: + break; + } + + return CGUIWindowMusicBase::OnContextButton(itemNumber, button); +} + +bool CGUIWindowMusicNav::GetSongsFromPlayList(const std::string& strPlayList, CFileItemList &items) +{ + std::string strParentPath=m_history.GetParentPath(); + + if (m_guiState.get() && !m_guiState->HideParentDirItems()) + { + CFileItemPtr pItem(new CFileItem("..")); + pItem->SetPath(strParentPath); + items.Add(pItem); + } + + items.SetPath(strPlayList); + CLog::Log(LOGDEBUG, "CGUIWindowMusicNav, opening playlist [{}]", strPlayList); + + std::unique_ptr<CPlayList> pPlayList (CPlayListFactory::Create(strPlayList)); + if (nullptr != pPlayList) + { + // load it + if (!pPlayList->Load(strPlayList)) + { + HELPERS::ShowOKDialogText(CVariant{6}, CVariant{477}); + return false; //hmmm unable to load playlist? + } + CPlayList playlist = *pPlayList; + // convert playlist items to songs + for (int i = 0; i < playlist.size(); ++i) + { + items.Add(playlist[i]); + } + } + + return true; +} + +void CGUIWindowMusicNav::OnSearchUpdate() +{ + std::string search(CURL::Encode(GetProperty("search").asString())); + if (!search.empty()) + { + std::string path = "musicsearch://" + search + "/"; + m_history.ClearSearchHistory(); + Update(path); + } + else if (m_vecItems->IsVirtualDirectoryRoot()) + { + Update(""); + } +} + +void CGUIWindowMusicNav::FrameMove() +{ + static const int search_timeout = 2000; + // update our searching + if (m_searchTimer.IsRunning() && m_searchTimer.GetElapsedMilliseconds() > search_timeout) + { + m_searchTimer.Stop(); + OnSearchUpdate(); + } + CGUIWindowMusicBase::FrameMove(); +} + +void CGUIWindowMusicNav::AddSearchFolder() +{ + // we use a general viewstate (and not our member) here as our + // current viewstate may be specific to some other folder, and + // we know we're in the root here + CFileItemList items; + CGUIViewState* viewState = CGUIViewState::GetViewState(GetID(), items); + if (viewState) + { + // add our remove the musicsearch source + VECSOURCES &sources = viewState->GetSources(); + bool haveSearchSource = false; + bool needSearchSource = !GetProperty("search").empty() || !m_searchWithEdit; // we always need it if we don't have the edit control + for (IVECSOURCES it = sources.begin(); it != sources.end(); ++it) + { + CMediaSource& share = *it; + if (share.strPath == "musicsearch://") + { + haveSearchSource = true; + if (!needSearchSource) + { // remove it + sources.erase(it); + break; + } + } + } + if (!haveSearchSource && needSearchSource) + { + // add search share + CMediaSource share; + share.strName=g_localizeStrings.Get(137); // Search + share.strPath = "musicsearch://"; + share.m_iDriveType = CMediaSource::SOURCE_TYPE_LOCAL; + sources.push_back(share); + } + m_rootDir.SetSources(sources); + delete viewState; + } +} + +std::string CGUIWindowMusicNav::GetStartFolder(const std::string &dir) +{ + std::string lower(dir); StringUtils::ToLower(lower); + if (lower == "genres") + return "musicdb://genres/"; + else if (lower == "artists") + return "musicdb://artists/"; + else if (lower == "albums") + return "musicdb://albums/"; + else if (lower == "singles") + return "musicdb://singles/"; + else if (lower == "songs") + return "musicdb://songs/"; + else if (lower == "top100") + return "musicdb://top100/"; + else if (lower == "top100songs") + return "musicdb://top100/songs/"; + else if (lower == "top100albums") + return "musicdb://top100/albums/"; + else if (lower == "recentlyaddedalbums") + return "musicdb://recentlyaddedalbums/"; + else if (lower == "recentlyplayedalbums") + return "musicdb://recentlyplayedalbums/"; + else if (lower == "compilations") + return "musicdb://compilations/"; + else if (lower == "years") + return "musicdb://years/"; + else if (lower == "files") + return "sources://music/"; + else if (lower == "boxsets") + return "musicdb://boxsets/"; + + return CGUIWindowMusicBase::GetStartFolder(dir); +} diff --git a/xbmc/music/windows/GUIWindowMusicNav.h b/xbmc/music/windows/GUIWindowMusicNav.h new file mode 100644 index 0000000..021eaad --- /dev/null +++ b/xbmc/music/windows/GUIWindowMusicNav.h @@ -0,0 +1,50 @@ +/* + * 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 "GUIWindowMusicBase.h" +#include "utils/Stopwatch.h" + +class CFileItemList; + +class CGUIWindowMusicNav : public CGUIWindowMusicBase +{ +public: + + CGUIWindowMusicNav(void); + ~CGUIWindowMusicNav(void) override; + + bool OnMessage(CGUIMessage& message) override; + bool OnAction(const CAction& action) override; + void FrameMove() override; + +protected: + void OnItemLoaded(CFileItem* pItem) override {}; + // override base class methods + bool Update(const std::string &strDirectory, bool updateFilterPath = true) override; + bool GetDirectory(const std::string &strDirectory, CFileItemList &items) override; + void UpdateButtons() override; + void PlayItem(int iItem) override; + void OnWindowLoaded() override; + void GetContextButtons(int itemNumber, CContextButtons &buttons) override; + bool OnContextButton(int itemNumber, CONTEXT_BUTTON button) override; + bool OnClick(int iItem, const std::string &player = "") override; + std::string GetStartFolder(const std::string &url) override; + + bool GetSongsFromPlayList(const std::string& strPlayList, CFileItemList &items); + bool ManageInfoProvider(const CFileItemPtr& item); + + VECSOURCES m_shares; + + // searching + void OnSearchUpdate(); + void AddSearchFolder(); + CStopWatch m_searchTimer; ///< Timer to delay a search while more characters are entered + bool m_searchWithEdit; ///< Whether the skin supports the new edit control searching +}; diff --git a/xbmc/music/windows/GUIWindowMusicPlaylist.cpp b/xbmc/music/windows/GUIWindowMusicPlaylist.cpp new file mode 100644 index 0000000..acc46f6 --- /dev/null +++ b/xbmc/music/windows/GUIWindowMusicPlaylist.cpp @@ -0,0 +1,715 @@ +/* + * 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 "GUIWindowMusicPlaylist.h" + +#include "FileItem.h" +#include "GUIUserMessages.h" +#include "PartyModeManager.h" +#include "PlayListPlayer.h" +#include "ServiceBroker.h" +#include "Util.h" +#include "application/Application.h" +#include "application/ApplicationComponents.h" +#include "application/ApplicationPlayer.h" +#include "cores/playercorefactory/PlayerCoreFactory.h" +#include "dialogs/GUIDialogSmartPlaylistEditor.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIKeyboardFactory.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/LocalizeStrings.h" +#include "input/actions/Action.h" +#include "input/actions/ActionIDs.h" +#include "music/tags/MusicInfoTag.h" +#include "playlists/PlayListM3U.h" +#include "profiles/ProfileManager.h" +#include "settings/MediaSettings.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "utils/LabelFormatter.h" +#include "utils/StringUtils.h" +#include "utils/URIUtils.h" +#include "utils/Variant.h" +#include "utils/log.h" +#include "view/GUIViewState.h" + +#define CONTROL_BTNVIEWASICONS 2 +#define CONTROL_BTNSORTBY 3 +#define CONTROL_BTNSORTASC 4 +#define CONTROL_LABELFILES 12 + +#define CONTROL_BTNSHUFFLE 20 +#define CONTROL_BTNSAVE 21 +#define CONTROL_BTNCLEAR 22 + +#define CONTROL_BTNPLAY 23 +#define CONTROL_BTNNEXT 24 +#define CONTROL_BTNPREVIOUS 25 +#define CONTROL_BTNREPEAT 26 + +CGUIWindowMusicPlayList::CGUIWindowMusicPlayList(void) + : CGUIWindowMusicBase(WINDOW_MUSIC_PLAYLIST, "MyPlaylist.xml") +{ + m_musicInfoLoader.SetObserver(this); + m_movingFrom = -1; +} + +CGUIWindowMusicPlayList::~CGUIWindowMusicPlayList(void) = default; + +bool CGUIWindowMusicPlayList::OnMessage(CGUIMessage& message) +{ + switch ( message.GetMessage() ) + { + case GUI_MSG_PLAYLISTPLAYER_REPEAT: + { + UpdateButtons(); + } + break; + + case GUI_MSG_PLAYLISTPLAYER_RANDOM: + case GUI_MSG_PLAYLIST_CHANGED: + { + // global playlist changed outside playlist window + UpdateButtons(); + + if (m_vecItemsUpdating) + { + CLog::Log(LOGWARNING, "CGUIWindowMusicPlayList::OnMessage - updating in progress"); + return true; + } + CUpdateGuard ug(m_vecItemsUpdating); + + Refresh(true); + + if (m_viewControl.HasControl(m_iLastControl) && m_vecItems->Size() <= 0) + { + m_iLastControl = CONTROL_BTNVIEWASICONS; + SET_CONTROL_FOCUS(m_iLastControl, 0); + } + + } + break; + + case GUI_MSG_WINDOW_DEINIT: + { + if (m_musicInfoLoader.IsLoading()) + m_musicInfoLoader.StopThread(); + + m_movingFrom = -1; + } + break; + + case GUI_MSG_WINDOW_INIT: + { + // Setup item cache for tagloader + m_musicInfoLoader.UseCacheOnHD("special://temp/archive_cache/MusicPlaylist.fi"); + + m_vecItems->SetPath("playlistmusic://"); + + // updatebuttons is called in here + if (!CGUIWindowMusicBase::OnMessage(message)) + return false; + + if (m_vecItems->Size() <= 0) + { + m_iLastControl = CONTROL_BTNVIEWASICONS; + SET_CONTROL_FOCUS(m_iLastControl, 0); + } + + const auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + if (appPlayer->IsPlayingAudio() && + CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist() == PLAYLIST::TYPE_MUSIC) + { + int iSong = CServiceBroker::GetPlaylistPlayer().GetCurrentSong(); + if (iSong >= 0 && iSong <= m_vecItems->Size()) + m_viewControl.SetSelectedItem(iSong); + } + + return true; + } + break; + + case GUI_MSG_CLICKED: + { + int iControl = message.GetSenderId(); + if (iControl == CONTROL_BTNSHUFFLE) + { + if (!g_partyModeManager.IsEnabled()) + { + CServiceBroker::GetPlaylistPlayer().SetShuffle( + PLAYLIST::TYPE_MUSIC, + !(CServiceBroker::GetPlaylistPlayer().IsShuffled(PLAYLIST::TYPE_MUSIC))); + CMediaSettings::GetInstance().SetMusicPlaylistShuffled( + CServiceBroker::GetPlaylistPlayer().IsShuffled(PLAYLIST::TYPE_MUSIC)); + CServiceBroker::GetSettingsComponent()->GetSettings()->Save(); + UpdateButtons(); + Refresh(); + } + } + else if (iControl == CONTROL_BTNSAVE) + { + if (m_musicInfoLoader.IsLoading()) // needed since we destroy m_vecitems to save memory + m_musicInfoLoader.StopThread(); + + SavePlayList(); + } + else if (iControl == CONTROL_BTNCLEAR) + { + if (m_musicInfoLoader.IsLoading()) + m_musicInfoLoader.StopThread(); + + ClearPlayList(); + } + else if (iControl == CONTROL_BTNPLAY) + { + m_guiState->SetPlaylistDirectory("playlistmusic://"); + CServiceBroker::GetPlaylistPlayer().SetCurrentPlaylist(PLAYLIST::TYPE_MUSIC); + CServiceBroker::GetPlaylistPlayer().Reset(); + CServiceBroker::GetPlaylistPlayer().Play(m_viewControl.GetSelectedItem(), ""); + UpdateButtons(); + } + else if (iControl == CONTROL_BTNNEXT) + { + CServiceBroker::GetPlaylistPlayer().SetCurrentPlaylist(PLAYLIST::TYPE_MUSIC); + CServiceBroker::GetPlaylistPlayer().PlayNext(); + } + else if (iControl == CONTROL_BTNPREVIOUS) + { + CServiceBroker::GetPlaylistPlayer().SetCurrentPlaylist(PLAYLIST::TYPE_MUSIC); + CServiceBroker::GetPlaylistPlayer().PlayPrevious(); + } + else if (iControl == CONTROL_BTNREPEAT) + { + // increment repeat state + PLAYLIST::RepeatState state = + CServiceBroker::GetPlaylistPlayer().GetRepeat(PLAYLIST::TYPE_MUSIC); + if (state == PLAYLIST::RepeatState::NONE) + CServiceBroker::GetPlaylistPlayer().SetRepeat(PLAYLIST::TYPE_MUSIC, + PLAYLIST::RepeatState::ALL); + else if (state == PLAYLIST::RepeatState::ALL) + CServiceBroker::GetPlaylistPlayer().SetRepeat(PLAYLIST::TYPE_MUSIC, + PLAYLIST::RepeatState::ONE); + else + CServiceBroker::GetPlaylistPlayer().SetRepeat(PLAYLIST::TYPE_MUSIC, + PLAYLIST::RepeatState::NONE); + + // save settings + CMediaSettings::GetInstance().SetMusicPlaylistRepeat( + CServiceBroker::GetPlaylistPlayer().GetRepeat(PLAYLIST::TYPE_MUSIC) == + PLAYLIST::RepeatState::ALL); + CServiceBroker::GetSettingsComponent()->GetSettings()->Save(); + + UpdateButtons(); + } + else if (m_viewControl.HasControl(iControl)) + { + int iAction = message.GetParam1(); + int iItem = m_viewControl.GetSelectedItem(); + if (iAction == ACTION_DELETE_ITEM || iAction == ACTION_MOUSE_MIDDLE_CLICK) + { + RemovePlayListItem(iItem); + MarkPlaying(); + } + } + } + break; + + } + return CGUIWindowMusicBase::OnMessage(message); +} + +bool CGUIWindowMusicPlayList::OnAction(const CAction &action) +{ + if (action.GetID() == ACTION_PARENT_DIR) + { + // Playlist has no parent dirs + return true; + } + if (action.GetID() == ACTION_SHOW_PLAYLIST) + { + CServiceBroker::GetGUI()->GetWindowManager().PreviousWindow(); + return true; + } + if ((action.GetID() == ACTION_MOVE_ITEM_UP) || (action.GetID() == ACTION_MOVE_ITEM_DOWN)) + { + int iItem = -1; + int iFocusedControl = GetFocusedControlID(); + if (m_viewControl.HasControl(iFocusedControl)) + iItem = m_viewControl.GetSelectedItem(); + OnMove(iItem, action.GetID()); + return true; + } + return CGUIWindowMusicBase::OnAction(action); +} + +bool CGUIWindowMusicPlayList::OnBack(int actionID) +{ + CancelUpdateItems(); + + if (actionID == ACTION_NAV_BACK) + return CGUIWindow::OnBack(actionID); // base class goes up a folder, but none to go up + return CGUIWindowMusicBase::OnBack(actionID); +} + +bool CGUIWindowMusicPlayList::MoveCurrentPlayListItem(int iItem, int iAction, bool bUpdate /* = true */) +{ + int iSelected = iItem; + int iNew = iSelected; + if (iAction == ACTION_MOVE_ITEM_UP) + iNew--; + else + iNew++; + + const auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + + // is the currently playing item affected? + bool bFixCurrentSong = false; + if ((CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist() == PLAYLIST::TYPE_MUSIC) && + appPlayer->IsPlayingAudio() && + ((CServiceBroker::GetPlaylistPlayer().GetCurrentSong() == iSelected) || + (CServiceBroker::GetPlaylistPlayer().GetCurrentSong() == iNew))) + bFixCurrentSong = true; + + PLAYLIST::CPlayList& playlist = + CServiceBroker::GetPlaylistPlayer().GetPlaylist(PLAYLIST::TYPE_MUSIC); + if (playlist.Swap(iSelected, iNew)) + { + // Correct the current playing song in playlistplayer + if (bFixCurrentSong) + { + int iCurrentSong = CServiceBroker::GetPlaylistPlayer().GetCurrentSong(); + if (iSelected == iCurrentSong) + iCurrentSong = iNew; + else if (iNew == iCurrentSong) + iCurrentSong = iSelected; + CServiceBroker::GetPlaylistPlayer().SetCurrentSong(iCurrentSong); + } + + if (bUpdate) + Refresh(); + return true; + } + + return false; +} + +void CGUIWindowMusicPlayList::SavePlayList() +{ + std::string strNewFileName; + if (CGUIKeyboardFactory::ShowAndGetInput(strNewFileName, CVariant{g_localizeStrings.Get(16012)}, false)) + { + // need 2 rename it + strNewFileName = CUtil::MakeLegalFileName(strNewFileName); + strNewFileName += ".m3u"; + std::string strPath = URIUtils::AddFileToFolder( + CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_SYSTEM_PLAYLISTSPATH), + "music", + strNewFileName); + + // get selected item + int iItem = m_viewControl.GetSelectedItem(); + std::string strSelectedItem = ""; + if (iItem >= 0 && iItem < m_vecItems->Size()) + { + CFileItemPtr pItem = m_vecItems->Get(iItem); + if (!pItem->IsParentFolder()) + { + GetDirectoryHistoryString(pItem.get(), strSelectedItem); + } + } + + std::string strOldDirectory = m_vecItems->GetPath(); + m_history.SetSelectedItem(strSelectedItem, strOldDirectory); + + PLAYLIST::CPlayListM3U playlist; + for (int i = 0; i < m_vecItems->Size(); ++i) + { + CFileItemPtr pItem = m_vecItems->Get(i); + + // Musicdatabase items should contain the real path instead of a musicdb url + // otherwise the user can't save and reuse the playlist when the musicdb gets deleted + if (pItem->IsMusicDb()) + pItem->SetPath(pItem->GetMusicInfoTag()->GetURL()); + + playlist.Add(pItem); + } + CLog::Log(LOGDEBUG, "Saving music playlist: [{}]", strPath); + playlist.Save(strPath); + Refresh(); // need to update + } +} + +void CGUIWindowMusicPlayList::ClearPlayList() +{ + ClearFileItems(); + CServiceBroker::GetPlaylistPlayer().ClearPlaylist(PLAYLIST::TYPE_MUSIC); + if (CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist() == PLAYLIST::TYPE_MUSIC) + { + CServiceBroker::GetPlaylistPlayer().Reset(); + CServiceBroker::GetPlaylistPlayer().SetCurrentPlaylist(PLAYLIST::TYPE_NONE); + } + Refresh(); + SET_CONTROL_FOCUS(CONTROL_BTNVIEWASICONS, 0); +} + +void CGUIWindowMusicPlayList::RemovePlayListItem(int iItem) +{ + if (iItem < 0 || iItem > m_vecItems->Size()) return; + + const auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + // The current playing song can't be removed + if (CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist() == PLAYLIST::TYPE_MUSIC && + appPlayer->IsPlayingAudio() && CServiceBroker::GetPlaylistPlayer().GetCurrentSong() == iItem) + return ; + + CServiceBroker::GetPlaylistPlayer().Remove(PLAYLIST::TYPE_MUSIC, iItem); + + Refresh(); + + if (m_vecItems->Size() <= 0) + { + SET_CONTROL_FOCUS(CONTROL_BTNVIEWASICONS, 0); + } + else + { + m_viewControl.SetSelectedItem(iItem); + } + + g_partyModeManager.OnSongChange(); +} + +void CGUIWindowMusicPlayList::UpdateButtons() +{ + CGUIWindowMusicBase::UpdateButtons(); + + // Update playlist buttons + if (m_vecItems->Size() && !g_partyModeManager.IsEnabled()) + { + CONTROL_ENABLE(CONTROL_BTNSHUFFLE); + CONTROL_ENABLE(CONTROL_BTNSAVE); + CONTROL_ENABLE(CONTROL_BTNCLEAR); + CONTROL_ENABLE(CONTROL_BTNREPEAT); + CONTROL_ENABLE(CONTROL_BTNPLAY); + + const auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + if (appPlayer->IsPlayingAudio() && + CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist() == PLAYLIST::TYPE_MUSIC) + { + CONTROL_ENABLE(CONTROL_BTNNEXT); + CONTROL_ENABLE(CONTROL_BTNPREVIOUS); + } + else + { + CONTROL_DISABLE(CONTROL_BTNNEXT); + CONTROL_DISABLE(CONTROL_BTNPREVIOUS); + } + } + else + { + // disable buttons if party mode is enabled too + CONTROL_DISABLE(CONTROL_BTNSHUFFLE); + CONTROL_DISABLE(CONTROL_BTNSAVE); + CONTROL_DISABLE(CONTROL_BTNCLEAR); + CONTROL_DISABLE(CONTROL_BTNREPEAT); + CONTROL_DISABLE(CONTROL_BTNPLAY); + CONTROL_DISABLE(CONTROL_BTNNEXT); + CONTROL_DISABLE(CONTROL_BTNPREVIOUS); + } + + // update buttons + CONTROL_DESELECT(CONTROL_BTNSHUFFLE); + if (CServiceBroker::GetPlaylistPlayer().IsShuffled(PLAYLIST::TYPE_MUSIC)) + CONTROL_SELECT(CONTROL_BTNSHUFFLE); + + // update repeat button + int iLocalizedString; + PLAYLIST::RepeatState repState = + CServiceBroker::GetPlaylistPlayer().GetRepeat(PLAYLIST::TYPE_MUSIC); + if (repState == PLAYLIST::RepeatState::NONE) + iLocalizedString = 595; // Repeat: Off + else if (repState == PLAYLIST::RepeatState::ONE) + iLocalizedString = 596; // Repeat: One + else + iLocalizedString = 597; // Repeat: All + + SET_CONTROL_LABEL(CONTROL_BTNREPEAT, g_localizeStrings.Get(iLocalizedString)); + + // Update object count label + std::string items = + StringUtils::Format("{} {}", m_vecItems->GetObjectCount(), g_localizeStrings.Get(127)); + SET_CONTROL_LABEL(CONTROL_LABELFILES, items); + + MarkPlaying(); +} + +bool CGUIWindowMusicPlayList::OnPlayMedia(int iItem, const std::string &player) +{ + if (g_partyModeManager.IsEnabled()) + g_partyModeManager.Play(iItem); + else + { + PLAYLIST::Id playlistId = m_guiState->GetPlaylist(); + if (playlistId != PLAYLIST::TYPE_NONE) + { + if (m_guiState) + m_guiState->SetPlaylistDirectory(m_vecItems->GetPath()); + + CServiceBroker::GetPlaylistPlayer().SetCurrentPlaylist(playlistId); + CServiceBroker::GetPlaylistPlayer().Play(iItem, player); + } + else + { + // Reset Playlistplayer, playback started now does + // not use the playlistplayer. + CFileItemPtr pItem=m_vecItems->Get(iItem); + CServiceBroker::GetPlaylistPlayer().Reset(); + CServiceBroker::GetPlaylistPlayer().SetCurrentPlaylist(PLAYLIST::TYPE_NONE); + g_application.PlayFile(*pItem, player); + } + } + + return true; +} + +void CGUIWindowMusicPlayList::OnItemLoaded(CFileItem* pItem) +{ + if (pItem->HasMusicInfoTag() && pItem->GetMusicInfoTag()->Loaded()) + { // set label 1+2 from tags + const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings(); + std::string strTrack = settings->GetString(CSettings::SETTING_MUSICFILES_NOWPLAYINGTRACKFORMAT); + if (strTrack.empty()) + strTrack = settings->GetString(CSettings::SETTING_MUSICFILES_TRACKFORMAT); + CLabelFormatter formatter(strTrack, "%D"); + formatter.FormatLabels(pItem); + } // if (pItem->m_musicInfoTag.Loaded()) + else + { + // Our tag may have a duration even if its not loaded + if (pItem->HasMusicInfoTag() && pItem->GetMusicInfoTag()->GetDuration()) + { + int nDuration = pItem->GetMusicInfoTag()->GetDuration(); + if (nDuration > 0) + pItem->SetLabel2(StringUtils::SecondsToTimeString(nDuration)); + } + else if (pItem->GetLabel() == "") // pls labels come in preformatted + { + // FIXME: get the position of the item in the playlist + // currently it is hacked into m_iprogramCount + + // No music info and it's not CDDA so we'll just show the filename + std::string str; + str = CUtil::GetTitleFromPath(pItem->GetPath()); + str = StringUtils::Format("{:02}. {} ", pItem->m_iprogramCount, str); + pItem->SetLabel(str); + } + } +} + +bool CGUIWindowMusicPlayList::Update(const std::string& strDirectory, bool updateFilterPath /* = true */) +{ + if (m_musicInfoLoader.IsLoading()) + m_musicInfoLoader.StopThread(); + + if (!CGUIWindowMusicBase::Update(strDirectory, updateFilterPath)) + return false; + + if (m_vecItems->GetContent().empty()) + m_vecItems->SetContent("songs"); + + m_musicInfoLoader.Load(*m_vecItems); + return true; +} + +void CGUIWindowMusicPlayList::GetContextButtons(int itemNumber, CContextButtons &buttons) +{ + // is this playlist playing? + int itemPlaying = CServiceBroker::GetPlaylistPlayer().GetCurrentSong(); + + if (itemNumber >= 0 && itemNumber < m_vecItems->Size()) + { + CFileItemPtr item; + item = m_vecItems->Get(itemNumber); + + if (m_movingFrom >= 0) + { + // we can move the item to any position not where we are, and any position not above currently + // playing item in party mode + if (itemNumber != m_movingFrom && (!g_partyModeManager.IsEnabled() || itemNumber > itemPlaying)) + buttons.Add(CONTEXT_BUTTON_MOVE_HERE, 13252); // move item here + buttons.Add(CONTEXT_BUTTON_CANCEL_MOVE, 13253); + } + else + { + const CPlayerCoreFactory &playerCoreFactory = CServiceBroker::GetPlayerCoreFactory(); + + // aren't in a move + // check what players we have, if we have multiple display play with option + std::vector<std::string> players; + playerCoreFactory.GetPlayers(*item, players); + if (players.size() > 1) + buttons.Add(CONTEXT_BUTTON_PLAY_WITH, 15213); // Play With... + + if (itemNumber > (g_partyModeManager.IsEnabled() ? 1 : 0)) + buttons.Add(CONTEXT_BUTTON_MOVE_ITEM_UP, 13332); + if (itemNumber + 1 < m_vecItems->Size()) + buttons.Add(CONTEXT_BUTTON_MOVE_ITEM_DOWN, 13333); + if (!g_partyModeManager.IsEnabled() || itemNumber != itemPlaying) + buttons.Add(CONTEXT_BUTTON_MOVE_ITEM, 13251); + if (itemNumber != itemPlaying) + buttons.Add(CONTEXT_BUTTON_DELETE, 1210); // Remove + } + } + + if (g_partyModeManager.IsEnabled()) + { + buttons.Add(CONTEXT_BUTTON_EDIT_PARTYMODE, 21439); + buttons.Add(CONTEXT_BUTTON_CANCEL_PARTYMODE, 588); // cancel party mode + } +} + +bool CGUIWindowMusicPlayList::OnContextButton(int itemNumber, CONTEXT_BUTTON button) +{ + switch (button) + { + case CONTEXT_BUTTON_PLAY_WITH: + { + CFileItemPtr item; + if (itemNumber >= 0 && itemNumber < m_vecItems->Size()) + item = m_vecItems->Get(itemNumber); + if (!item) + break; + + const CPlayerCoreFactory &playerCoreFactory = CServiceBroker::GetPlayerCoreFactory(); + + std::vector<std::string> players; + playerCoreFactory.GetPlayers(*item, players); + std::string player = playerCoreFactory.SelectPlayerDialog(players); + if (!player.empty()) + OnClick(itemNumber, player); + return true; + } + case CONTEXT_BUTTON_MOVE_ITEM: + m_movingFrom = itemNumber; + return true; + + case CONTEXT_BUTTON_MOVE_HERE: + MoveItem(m_movingFrom, itemNumber); + m_movingFrom = -1; + return true; + + case CONTEXT_BUTTON_CANCEL_MOVE: + m_movingFrom = -1; + return true; + + case CONTEXT_BUTTON_MOVE_ITEM_UP: + OnMove(itemNumber, ACTION_MOVE_ITEM_UP); + return true; + + case CONTEXT_BUTTON_MOVE_ITEM_DOWN: + OnMove(itemNumber, ACTION_MOVE_ITEM_DOWN); + return true; + + case CONTEXT_BUTTON_DELETE: + RemovePlayListItem(itemNumber); + return true; + + case CONTEXT_BUTTON_CANCEL_PARTYMODE: + g_partyModeManager.Disable(); + return true; + + case CONTEXT_BUTTON_EDIT_PARTYMODE: + { + const std::shared_ptr<CProfileManager> profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager(); + + std::string playlist = profileManager->GetUserDataItem("PartyMode.xsp"); + if (CGUIDialogSmartPlaylistEditor::EditPlaylist(playlist)) + { + // apply new rules + g_partyModeManager.Disable(); + g_partyModeManager.Enable(); + } + return true; + } + + default: + break; + } + return CGUIWindowMusicBase::OnContextButton(itemNumber, button); +} + + +void CGUIWindowMusicPlayList::OnMove(int iItem, int iAction) +{ + if (iItem < 0 || iItem >= m_vecItems->Size()) return; + + bool bRestart = m_musicInfoLoader.IsLoading(); + if (bRestart) + m_musicInfoLoader.StopThread(); + + MoveCurrentPlayListItem(iItem, iAction); + + if (bRestart) + m_musicInfoLoader.Load(*m_vecItems); +} + +void CGUIWindowMusicPlayList::MoveItem(int iStart, int iDest) +{ + if (iStart < 0 || iStart >= m_vecItems->Size()) return; + if (iDest < 0 || iDest >= m_vecItems->Size()) return; + + // default to move up + int iAction = ACTION_MOVE_ITEM_UP; + int iDirection = -1; + // are we moving down? + if (iStart < iDest) + { + iAction = ACTION_MOVE_ITEM_DOWN; + iDirection = 1; + } + + bool bRestart = m_musicInfoLoader.IsLoading(); + if (bRestart) + m_musicInfoLoader.StopThread(); + + // keep swapping until you get to the destination or you + // hit the currently playing song + int i = iStart; + while (i != iDest) + { + // try to swap adjacent items + if (MoveCurrentPlayListItem(i, iAction, false)) + i = i + (1 * iDirection); + // we hit currently playing song, so abort + else + break; + } + Refresh(); + + if (bRestart) + m_musicInfoLoader.Load(*m_vecItems); +} + +void CGUIWindowMusicPlayList::MarkPlaying() +{ + /* // clear markings + for (int i = 0; i < m_vecItems->Size(); i++) + m_vecItems->Get(i)->Select(false); + + // mark the currently playing item + if ((CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist() == TYPE_MUSIC) && (g_application.GetAppPlayer().IsPlayingAudio())) + { + int iSong = CServiceBroker::GetPlaylistPlayer().GetCurrentSong(); + if (iSong >= 0 && iSong <= m_vecItems->Size()) + m_vecItems->Get(iSong)->Select(true); + }*/ +} + diff --git a/xbmc/music/windows/GUIWindowMusicPlaylist.h b/xbmc/music/windows/GUIWindowMusicPlaylist.h new file mode 100644 index 0000000..019a1c7 --- /dev/null +++ b/xbmc/music/windows/GUIWindowMusicPlaylist.h @@ -0,0 +1,44 @@ +/* + * 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 "GUIWindowMusicBase.h" + +class CGUIWindowMusicPlayList : public CGUIWindowMusicBase +{ +public: + CGUIWindowMusicPlayList(void); + ~CGUIWindowMusicPlayList(void) override; + + bool OnMessage(CGUIMessage& message) override; + bool OnAction(const CAction &action) override; + bool OnBack(int actionID) override; + + void RemovePlayListItem(int iItem); + void MoveItem(int iStart, int iDest); + +protected: + bool GoParentFolder() override { return false; } + void UpdateButtons() override; + void OnItemLoaded(CFileItem* pItem) override; + bool Update(const std::string& strDirectory, bool updateFilterPath = true) override; + void GetContextButtons(int itemNumber, CContextButtons &buttons) override; + bool OnContextButton(int itemNumber, CONTEXT_BUTTON button) override; + void OnMove(int iItem, int iAction); + bool OnPlayMedia(int iItem, const std::string &player = "") override; + + void SavePlayList(); + void ClearPlayList(); + void MarkPlaying(); + + bool MoveCurrentPlayListItem(int iItem, int iAction, bool bUpdate = true); + + int m_movingFrom; + VECSOURCES m_shares; +}; diff --git a/xbmc/music/windows/GUIWindowMusicPlaylistEditor.cpp b/xbmc/music/windows/GUIWindowMusicPlaylistEditor.cpp new file mode 100644 index 0000000..393923d --- /dev/null +++ b/xbmc/music/windows/GUIWindowMusicPlaylistEditor.cpp @@ -0,0 +1,450 @@ +/* + * 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 "GUIWindowMusicPlaylistEditor.h" + +#include "Autorun.h" +#include "FileItem.h" +#include "GUIUserMessages.h" +#include "ServiceBroker.h" +#include "Util.h" +#include "dialogs/GUIDialogFileBrowser.h" +#include "dialogs/GUIDialogKaiToast.h" +#include "filesystem/PlaylistFileDirectory.h" +#include "guilib/GUIKeyboardFactory.h" +#include "guilib/LocalizeStrings.h" +#include "input/Key.h" +#include "music/MusicUtils.h" +#include "playlists/PlayListM3U.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "utils/StringUtils.h" +#include "utils/URIUtils.h" +#include "utils/Variant.h" + +#define CONTROL_LABELFILES 12 + +#define CONTROL_LOAD_PLAYLIST 6 +#define CONTROL_SAVE_PLAYLIST 7 +#define CONTROL_CLEAR_PLAYLIST 8 + +#define CONTROL_LIST 50 +#define CONTROL_PLAYLIST 100 +#define CONTROL_LABEL_PLAYLIST 101 + +CGUIWindowMusicPlaylistEditor::CGUIWindowMusicPlaylistEditor(void) + : CGUIWindowMusicBase(WINDOW_MUSIC_PLAYLIST_EDITOR, "MyMusicPlaylistEditor.xml") +{ + m_playlistThumbLoader.SetObserver(this); + m_playlist = new CFileItemList; +} + +CGUIWindowMusicPlaylistEditor::~CGUIWindowMusicPlaylistEditor(void) +{ + delete m_playlist; +} + +bool CGUIWindowMusicPlaylistEditor::OnBack(int actionID) +{ + if (actionID == ACTION_NAV_BACK && !m_viewControl.HasControl(GetFocusedControlID())) + return CGUIWindow::OnBack(actionID); // base class goes up a folder, but none to go up + return CGUIWindowMusicBase::OnBack(actionID); +} + +bool CGUIWindowMusicPlaylistEditor::OnAction(const CAction &action) +{ + if (action.GetID() == ACTION_CONTEXT_MENU) + { + int iControl = GetFocusedControlID(); + if (iControl == CONTROL_PLAYLIST) + { + OnPlaylistContext(); + return true; + } + else if (iControl == CONTROL_LIST) + { + OnSourcesContext(); + return true; + } + } + return CGUIWindow::OnAction(action); +} + +bool CGUIWindowMusicPlaylistEditor::OnClick(int iItem, const std::string& player /* = "" */) +{ + if (iItem < 0 || iItem >= m_vecItems->Size()) return false; + CFileItemPtr item = m_vecItems->Get(iItem); + + // Expand .m3u files in sources list when clicked on regardless of <playlistasfolders> + if (item->IsFileFolder(EFILEFOLDER_MASK_ONBROWSE)) + return Update(item->GetPath()); + // Avoid playback (default click behaviour) of media files + if (!item->m_bIsFolder) + return false; + + return CGUIWindowMusicBase::OnClick(iItem, player); +} + +bool CGUIWindowMusicPlaylistEditor::OnMessage(CGUIMessage& message) +{ + switch ( message.GetMessage() ) + { + case GUI_MSG_WINDOW_DEINIT: + if (m_thumbLoader.IsLoading()) + m_thumbLoader.StopThread(); + if (m_playlistThumbLoader.IsLoading()) + m_playlistThumbLoader.StopThread(); + CGUIWindowMusicBase::OnMessage(message); + return true; + + case GUI_MSG_WINDOW_INIT: + { + if (m_vecItems->GetPath() == "?") + m_vecItems->SetPath(""); + CGUIWindowMusicBase::OnMessage(message); + + if (message.GetNumStringParams()) + LoadPlaylist(message.GetStringParam()); + + return true; + } + break; + + case GUI_MSG_NOTIFY_ALL: + { + if (message.GetParam1()==GUI_MSG_REMOVED_MEDIA) + DeleteRemoveableMediaDirectoryCache(); + } + break; + + case GUI_MSG_CLICKED: + { + int control = message.GetSenderId(); + if (control == CONTROL_PLAYLIST) + { + int item = GetCurrentPlaylistItem(); + int action = message.GetParam1(); + if (action == ACTION_CONTEXT_MENU || action == ACTION_MOUSE_RIGHT_CLICK) + OnPlaylistContext(); + else if (action == ACTION_QUEUE_ITEM || action == ACTION_DELETE_ITEM || + action == ACTION_MOUSE_MIDDLE_CLICK) + OnDeletePlaylistItem(item); + else if (action == ACTION_MOVE_ITEM_UP) + OnMovePlaylistItem(item, -1); + else if (action == ACTION_MOVE_ITEM_DOWN) + OnMovePlaylistItem(item, 1); + return true; + } + else if (control == CONTROL_LIST) + { + int action = message.GetParam1(); + if (action == ACTION_CONTEXT_MENU || action == ACTION_MOUSE_RIGHT_CLICK) + { + OnSourcesContext(); + return true; + } + } + else if (control == CONTROL_LOAD_PLAYLIST) + { // load a playlist + OnLoadPlaylist(); + return true; + } + else if (control == CONTROL_SAVE_PLAYLIST) + { // save the playlist + OnSavePlaylist(); + return true; + } + else if (control == CONTROL_CLEAR_PLAYLIST) + { // clear the playlist + ClearPlaylist(); + return true; + } + } + break; + } + + return CGUIWindowMusicBase::OnMessage(message); +} + +bool CGUIWindowMusicPlaylistEditor::GetDirectory(const std::string &strDirectory, CFileItemList &items) +{ + items.Clear(); + if (strDirectory.empty()) + { // root listing - list files:// and musicdb:// + CFileItemPtr files(new CFileItem("sources://music/", true)); + files->SetLabel(g_localizeStrings.Get(744)); + files->SetLabelPreformatted(true); + files->m_bIsShareOrDrive = true; + items.Add(files); + + CFileItemPtr mdb(new CFileItem("library://music/", true)); + mdb->SetLabel(g_localizeStrings.Get(14022)); + mdb->SetLabelPreformatted(true); + mdb->m_bIsShareOrDrive = true; + items.SetPath(""); + items.Add(mdb); + + CFileItemPtr vdb(new CFileItem("videodb://musicvideos/", true)); + vdb->SetLabel(g_localizeStrings.Get(20389)); + vdb->SetLabelPreformatted(true); + vdb->m_bIsShareOrDrive = true; + items.SetPath(""); + items.Add(vdb); + + return true; + } + + if (!CGUIWindowMusicBase::GetDirectory(strDirectory, items)) + return false; + + // check for .CUE files here. + items.FilterCueItems(); + + return true; +} + +void CGUIWindowMusicPlaylistEditor::OnPrepareFileItems(CFileItemList &items) +{ + CGUIWindowMusicBase::OnPrepareFileItems(items); + + RetrieveMusicInfo(); +} + +void CGUIWindowMusicPlaylistEditor::UpdateButtons() +{ + CGUIWindowMusicBase::UpdateButtons(); + + // Update object count label + std::string items = StringUtils::Format("{} {}", m_vecItems->GetObjectCount(), + g_localizeStrings.Get(127)); // " 14 Objects" + SET_CONTROL_LABEL(CONTROL_LABELFILES, items); +} + +void CGUIWindowMusicPlaylistEditor::DeleteRemoveableMediaDirectoryCache() +{ + CUtil::DeleteDirectoryCache("r-"); +} + +void CGUIWindowMusicPlaylistEditor::PlayItem(int iItem) +{ + // unlike additemtoplaylist, we need to check the items here + // before calling it since the current playlist will be stopped + // and cleared! + + // we're at the root source listing + if (m_vecItems->IsVirtualDirectoryRoot() && !m_vecItems->Get(iItem)->IsDVD()) + return; + +#ifdef HAS_DVD_DRIVE + if (m_vecItems->Get(iItem)->IsDVD()) + MEDIA_DETECT::CAutorun::PlayDiscAskResume(m_vecItems->Get(iItem)->GetPath()); + else +#endif + CGUIWindowMusicBase::PlayItem(iItem); +} + +void CGUIWindowMusicPlaylistEditor::OnQueueItem(int iItem, bool) +{ + if (iItem < 0 || iItem >= m_vecItems->Size()) + return; + + // add this item to our playlist. We make a new copy here as we may be rendering them side by side, + // and thus want a different layout for each item + CFileItemPtr item(new CFileItem(*m_vecItems->Get(iItem))); + CFileItemList newItems; + MUSIC_UTILS::GetItemsForPlayList(item, newItems); + AppendToPlaylist(newItems); +} + +bool CGUIWindowMusicPlaylistEditor::Update(const std::string &strDirectory, bool updateFilterPath /* = true */) +{ + if (m_thumbLoader.IsLoading()) + m_thumbLoader.StopThread(); + + if (!CGUIMediaWindow::Update(strDirectory, updateFilterPath)) + return false; + + m_vecItems->SetContent("files"); + m_thumbLoader.Load(*m_vecItems); + + // update our playlist control + UpdatePlaylist(); + return true; +} + +void CGUIWindowMusicPlaylistEditor::ClearPlaylist() +{ + CGUIMessage msg(GUI_MSG_LABEL_RESET, GetID(), CONTROL_PLAYLIST); + OnMessage(msg); + + m_playlist->Clear(); +} + +void CGUIWindowMusicPlaylistEditor::UpdatePlaylist() +{ + if (m_playlistThumbLoader.IsLoading()) + m_playlistThumbLoader.StopThread(); + + // deselect all items + for (int i = 0; i < m_playlist->Size(); i++) + m_playlist->Get(i)->Select(false); + + // bind them to the list + CGUIMessage msg(GUI_MSG_LABEL_BIND, GetID(), CONTROL_PLAYLIST, 0, 0, m_playlist); + OnMessage(msg); + + // indicate how many songs we have + std::string items = StringUtils::Format("{} {}", m_playlist->Size(), + g_localizeStrings.Get(134)); // "123 Songs" + SET_CONTROL_LABEL(CONTROL_LABEL_PLAYLIST, items); + + m_playlistThumbLoader.Load(*m_playlist); +} + +int CGUIWindowMusicPlaylistEditor::GetCurrentPlaylistItem() +{ + CGUIMessage msg(GUI_MSG_ITEM_SELECTED, GetID(), CONTROL_PLAYLIST); + OnMessage(msg); + int item = msg.GetParam1(); + if (item > m_playlist->Size()) + return -1; + return item; +} + +void CGUIWindowMusicPlaylistEditor::OnDeletePlaylistItem(int item) +{ + if (item < 0) return; + m_playlist->Remove(item); + UpdatePlaylist(); + // select the next item + CGUIMessage msg(GUI_MSG_ITEM_SELECT, GetID(), CONTROL_PLAYLIST, item); + OnMessage(msg); +} + +void CGUIWindowMusicPlaylistEditor::OnMovePlaylistItem(int item, int direction) +{ + if (item < 0) return; + if (item + direction >= m_playlist->Size() || item + direction < 0) + return; + m_playlist->Swap(item, item + direction); + UpdatePlaylist(); + CGUIMessage msg(GUI_MSG_ITEM_SELECT, GetID(), CONTROL_PLAYLIST, item + direction); + OnMessage(msg); +} + +void CGUIWindowMusicPlaylistEditor::OnLoadPlaylist() +{ + // Prompt user for file to load from music playlists folder + std::string playlist; + if (CGUIDialogFileBrowser::ShowAndGetFile("special://musicplaylists/", + ".m3u|.pls|.b4s|.wpl|.xspf", g_localizeStrings.Get(656), + playlist)) + LoadPlaylist(playlist); +} + +void CGUIWindowMusicPlaylistEditor::LoadPlaylist(const std::string &playlist) +{ + const CURL pathToUrl(playlist); + if (pathToUrl.IsProtocol("newplaylist")) + { + ClearPlaylist(); + m_strLoadedPlaylist.clear(); + return; + } + + XFILE::CPlaylistFileDirectory dir; + CFileItemList items; + if (dir.GetDirectory(pathToUrl, items)) + { + ClearPlaylist(); + AppendToPlaylist(items); + m_strLoadedPlaylist = playlist; + } +} + +void CGUIWindowMusicPlaylistEditor::OnSavePlaylist() +{ + // saves playlist to the playlist folder + std::string name = URIUtils::GetFileName(m_strLoadedPlaylist); + URIUtils::RemoveExtension(name); + + if (CGUIKeyboardFactory::ShowAndGetInput(name, CVariant{g_localizeStrings.Get(16012)}, false)) + { // save playlist as an .m3u + PLAYLIST::CPlayListM3U playlist; + playlist.Add(*m_playlist); + std::string path = URIUtils::AddFileToFolder( + CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_SYSTEM_PLAYLISTSPATH), + "music", + name + ".m3u"); + + playlist.Save(path); + m_strLoadedPlaylist = name; + CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, + g_localizeStrings.Get(559), // "Playlist" + g_localizeStrings.Get(35259)); // "Saved" + } +} + +void CGUIWindowMusicPlaylistEditor::AppendToPlaylist(CFileItemList &newItems) +{ + OnRetrieveMusicInfo(newItems); + FormatItemLabels(newItems, LABEL_MASKS(CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_MUSICFILES_TRACKFORMAT), "%D", "%L", "")); + m_playlist->Append(newItems); + UpdatePlaylist(); +} + +void CGUIWindowMusicPlaylistEditor::OnSourcesContext() +{ + CFileItemPtr item = GetCurrentListItem(); + CContextButtons buttons; + if (item->IsFileFolder(EFILEFOLDER_MASK_ONBROWSE)) + buttons.Add(CONTEXT_BUTTON_BROWSE_INTO, 37015); //Browse into + if (item && !item->IsParentFolder() && !m_vecItems->IsVirtualDirectoryRoot()) + buttons.Add(CONTEXT_BUTTON_QUEUE_ITEM, 15019); // Add (to playlist) + + int btnid = CGUIDialogContextMenu::ShowAndGetChoice(buttons); + if (btnid == CONTEXT_BUTTON_QUEUE_ITEM) + OnQueueItem(m_viewControl.GetSelectedItem(), false); + else if (btnid == CONTEXT_BUTTON_BROWSE_INTO) + Update(item->GetPath()); +} + +void CGUIWindowMusicPlaylistEditor::OnPlaylistContext() +{ + int item = GetCurrentPlaylistItem(); + CContextButtons buttons; + if (item > 0) + buttons.Add(CONTEXT_BUTTON_MOVE_ITEM_UP, 13332); + if (item >= 0 && item < m_playlist->Size() - 1) + buttons.Add(CONTEXT_BUTTON_MOVE_ITEM_DOWN, 13333); + if (item >= 0) + buttons.Add(CONTEXT_BUTTON_DELETE, 1210); + + int btnid = CGUIDialogContextMenu::ShowAndGetChoice(buttons); + if (btnid == CONTEXT_BUTTON_MOVE_ITEM_UP) + OnMovePlaylistItem(item, -1); + else if (btnid == CONTEXT_BUTTON_MOVE_ITEM_DOWN) + OnMovePlaylistItem(item, 1); + else if (btnid == CONTEXT_BUTTON_DELETE) + OnDeletePlaylistItem(item); +} + +bool CGUIWindowMusicPlaylistEditor::OnContextButton(int itemNumber, CONTEXT_BUTTON button) +{ + switch (button) + { + case CONTEXT_BUTTON_QUEUE_ITEM: + OnQueueItem(itemNumber); + return true; + + default: + break; + } + + return CGUIWindowMusicBase::OnContextButton(itemNumber, button); +} diff --git a/xbmc/music/windows/GUIWindowMusicPlaylistEditor.h b/xbmc/music/windows/GUIWindowMusicPlaylistEditor.h new file mode 100644 index 0000000..3859b55 --- /dev/null +++ b/xbmc/music/windows/GUIWindowMusicPlaylistEditor.h @@ -0,0 +1,57 @@ +/* + * 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 "GUIWindowMusicBase.h" + +class CFileItemList; + +class CGUIWindowMusicPlaylistEditor : public CGUIWindowMusicBase +{ +public: + CGUIWindowMusicPlaylistEditor(void); + ~CGUIWindowMusicPlaylistEditor(void) override; + + bool OnMessage(CGUIMessage& message) override; + bool OnAction(const CAction &action) override; + bool OnClick(int iItem, const std::string &player = "") override; + bool OnBack(int actionID) override; + +protected: + bool GetDirectory(const std::string &strDirectory, CFileItemList &items) override; + void UpdateButtons() override; + bool Update(const std::string &strDirectory, bool updateFilterPath = true) override; + void OnPrepareFileItems(CFileItemList &items) override; + bool OnContextButton(int itemNumber, CONTEXT_BUTTON button) override; + void OnQueueItem(int iItem, bool first = false) override; + std::string GetStartFolder(const std::string& dir) override { return ""; } + + void OnSourcesContext(); + void OnPlaylistContext(); + int GetCurrentPlaylistItem(); + void OnDeletePlaylistItem(int item); + void UpdatePlaylist(); + void ClearPlaylist(); + void OnSavePlaylist(); + void OnLoadPlaylist(); + void AppendToPlaylist(CFileItemList &newItems); + void OnMovePlaylistItem(int item, int direction); + + void LoadPlaylist(const std::string &playlist); + + // new method + void PlayItem(int iItem) override; + + void DeleteRemoveableMediaDirectoryCache(); + + CMusicThumbLoader m_playlistThumbLoader; + + CFileItemList* m_playlist; + std::string m_strLoadedPlaylist; +}; diff --git a/xbmc/music/windows/GUIWindowVisualisation.cpp b/xbmc/music/windows/GUIWindowVisualisation.cpp new file mode 100644 index 0000000..805c2df --- /dev/null +++ b/xbmc/music/windows/GUIWindowVisualisation.cpp @@ -0,0 +1,233 @@ +/* + * 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 "GUIWindowVisualisation.h" + +#include "GUIInfoManager.h" +#include "GUIUserMessages.h" +#include "ServiceBroker.h" +#include "application/ApplicationComponents.h" +#include "application/ApplicationPlayer.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIDialog.h" +#include "guilib/GUIWindowManager.h" +#include "input/Key.h" +#include "settings/AdvancedSettings.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" + +using namespace MUSIC_INFO; + +#define START_FADE_LENGTH 2.0f // 2 seconds on startup + +#define CONTROL_VIS 2 + +CGUIWindowVisualisation::CGUIWindowVisualisation(void) + : CGUIWindow(WINDOW_VISUALISATION, "MusicVisualisation.xml") +{ + m_bShowPreset = false; + m_loadType = KEEP_IN_MEMORY; +} + +bool CGUIWindowVisualisation::OnAction(const CAction &action) +{ + bool passToVis = false; + switch (action.GetID()) + { + case ACTION_VIS_PRESET_NEXT: + case ACTION_VIS_PRESET_PREV: + case ACTION_VIS_PRESET_RANDOM: + case ACTION_VIS_RATE_PRESET_PLUS: + case ACTION_VIS_RATE_PRESET_MINUS: + passToVis = true; + break; + + case ACTION_SHOW_INFO: + { + m_initTimer.Stop(); + CServiceBroker::GetSettingsComponent()->GetSettings()->SetBool(CSettings::SETTING_MYMUSIC_SONGTHUMBINVIS, + CServiceBroker::GetGUI()->GetInfoManager().GetInfoProviders().GetPlayerInfoProvider().ToggleShowInfo()); + return true; + } + break; + + case ACTION_SHOW_OSD: + CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_DIALOG_MUSIC_OSD); + return true; + + case ACTION_SHOW_GUI: + // save the settings + CServiceBroker::GetSettingsComponent()->GetSettings()->Save(); + CServiceBroker::GetGUI()->GetWindowManager().PreviousWindow(); + return true; + break; + + case ACTION_VIS_PRESET_LOCK: + { // show the locked icon + fall through so that the vis handles the locking + if (!m_bShowPreset) + { + m_lockedTimer.StartZero(); + } + passToVis = true; + } + break; + case ACTION_VIS_PRESET_SHOW: + { + if (!m_lockedTimer.IsRunning() || m_bShowPreset) + m_bShowPreset = !m_bShowPreset; + return true; + } + break; + + case ACTION_DECREASE_RATING: + case ACTION_INCREASE_RATING: + { + // actual action is taken care of in CApplication::OnAction() + m_initTimer.StartZero(); + CServiceBroker::GetGUI()->GetInfoManager().GetInfoProviders().GetPlayerInfoProvider().SetShowInfo(true); + } + break; + //! @todo These should be mapped to its own function - at the moment it's overriding + //! the global action of fastforward/rewind and OSD. +/* case KEY_BUTTON_Y: + g_application.m_CdgParser.Pause(); + return true; + break; + + case ACTION_ANALOG_FORWARD: + // calculate the speed based on the amount the button is held down + if (action.GetAmount()) + { + float AVDelay = g_application.m_CdgParser.GetAVDelay(); + g_application.m_CdgParser.SetAVDelay(AVDelay - action.GetAmount() / 4.0f); + return true; + } + break;*/ + } + + if (passToVis) + { + CGUIControl *control = GetControl(CONTROL_VIS); + if (control) + return control->OnAction(action); + } + + return CGUIWindow::OnAction(action); +} + +bool CGUIWindowVisualisation::OnMessage(CGUIMessage& message) +{ + switch ( message.GetMessage() ) + { + case GUI_MSG_GET_VISUALISATION: + case GUI_MSG_VISUALISATION_RELOAD: + case GUI_MSG_PLAYBACK_STARTED: + { + CGUIControl *control = GetControl(CONTROL_VIS); + if (control) + return control->OnMessage(message); + } + break; + case GUI_MSG_VISUALISATION_ACTION: + { + CAction action(message.GetParam1()); + return OnAction(action); + } + case GUI_MSG_WINDOW_DEINIT: + { + if (IsActive()) // save any changed settings from the OSD + CServiceBroker::GetSettingsComponent()->GetSettings()->Save(); + + // close all active modal dialogs + CServiceBroker::GetGUI()->GetWindowManager().CloseInternalModalDialogs(true); + } + break; + case GUI_MSG_WINDOW_INIT: + { + // check whether we've come back here from a window during which time we've actually + // stopped playing music + const auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + if (message.GetParam1() == WINDOW_INVALID && !appPlayer->IsPlayingAudio()) + { // why are we here if nothing is playing??? + CServiceBroker::GetGUI()->GetWindowManager().PreviousWindow(); + return true; + } + + // hide or show the preset button(s) + CGUIInfoManager& infoMgr = CServiceBroker::GetGUI()->GetInfoManager(); + infoMgr.GetInfoProviders().GetPlayerInfoProvider().SetShowInfo(true); // always show the info initially. + CGUIWindow::OnMessage(message); + if (infoMgr.GetCurrentSongTag()) + m_tag = *infoMgr.GetCurrentSongTag(); + + if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_MYMUSIC_SONGTHUMBINVIS)) + { // always on + m_initTimer.Stop(); + } + else + { + // start display init timer (fade out after 3 secs...) + m_initTimer.StartZero(); + } + return true; + } + } + return CGUIWindow::OnMessage(message); +} + +EVENT_RESULT CGUIWindowVisualisation::OnMouseEvent(const CPoint &point, const CMouseEvent &event) +{ + if (event.m_id == ACTION_MOUSE_RIGHT_CLICK) + { // no control found to absorb this click - go back to GUI + OnAction(CAction(ACTION_SHOW_GUI)); + return EVENT_RESULT_HANDLED; + } + if (event.m_id == ACTION_GESTURE_NOTIFY) + return EVENT_RESULT_UNHANDLED; + if (event.m_id != ACTION_MOUSE_MOVE || event.m_offsetX || event.m_offsetY) + { // some other mouse action has occurred - bring up the OSD + CGUIDialog *pOSD = CServiceBroker::GetGUI()->GetWindowManager().GetDialog(WINDOW_DIALOG_MUSIC_OSD); + if (pOSD) + { + pOSD->SetAutoClose(3000); + pOSD->Open(); + } + return EVENT_RESULT_HANDLED; + } + return EVENT_RESULT_UNHANDLED; +} + +void CGUIWindowVisualisation::FrameMove() +{ + CGUIInfoManager& infoMgr = CServiceBroker::GetGUI()->GetInfoManager(); + + // check for a tag change + const CMusicInfoTag* tag = infoMgr.GetCurrentSongTag(); + if (tag && *tag != m_tag) + { // need to fade in then out again + m_tag = *tag; + // fade in + m_initTimer.StartZero(); + infoMgr.GetInfoProviders().GetPlayerInfoProvider().SetShowInfo(true); + } + if (m_initTimer.IsRunning() && m_initTimer.GetElapsedSeconds() > (float)CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_songInfoDuration) + { + m_initTimer.Stop(); + if (!CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_MYMUSIC_SONGTHUMBINVIS)) + { // reached end of fade in, fade out again + infoMgr.GetInfoProviders().GetPlayerInfoProvider().SetShowInfo(false); + } + } + // show or hide the locked texture + if (m_lockedTimer.IsRunning() && m_lockedTimer.GetElapsedSeconds() > START_FADE_LENGTH) + { + m_lockedTimer.Stop(); + } + CGUIWindow::FrameMove(); +} diff --git a/xbmc/music/windows/GUIWindowVisualisation.h b/xbmc/music/windows/GUIWindowVisualisation.h new file mode 100644 index 0000000..ce9a198 --- /dev/null +++ b/xbmc/music/windows/GUIWindowVisualisation.h @@ -0,0 +1,31 @@ +/* + * 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/GUIWindow.h" +#include "music/tags/MusicInfoTag.h" +#include "utils/Stopwatch.h" + +class CGUIWindowVisualisation : + public CGUIWindow +{ +public: + CGUIWindowVisualisation(void); + bool OnMessage(CGUIMessage& message) override; + bool OnAction(const CAction &action) override; + void FrameMove() override; +protected: + EVENT_RESULT OnMouseEvent(const CPoint &point, const CMouseEvent &event) override; + + CStopWatch m_initTimer; + CStopWatch m_lockedTimer; + bool m_bShowPreset; + MUSIC_INFO::CMusicInfoTag m_tag; // current tag info, for finding when the info manager updates +}; + diff --git a/xbmc/music/windows/MusicFileItemListModifier.cpp b/xbmc/music/windows/MusicFileItemListModifier.cpp new file mode 100644 index 0000000..c78de79 --- /dev/null +++ b/xbmc/music/windows/MusicFileItemListModifier.cpp @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2016-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "MusicFileItemListModifier.h" + +#include "FileItem.h" +#include "ServiceBroker.h" +#include "filesystem/MusicDatabaseDirectory/DirectoryNode.h" +#include "guilib/LocalizeStrings.h" +#include "music/MusicDbUrl.h" +#include "settings/AdvancedSettings.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" + +using namespace XFILE::MUSICDATABASEDIRECTORY; + +bool CMusicFileItemListModifier::CanModify(const CFileItemList &items) const +{ + if (items.IsMusicDb()) + return true; + + return false; +} + +bool CMusicFileItemListModifier::Modify(CFileItemList &items) const +{ + AddQueuingFolder(items); + return true; +} + +// Add an "* All ..." folder to the CFileItemList +// depending on the child node +void CMusicFileItemListModifier::AddQueuingFolder(CFileItemList& items) +{ + if (!items.IsMusicDb()) + return; + + auto directoryNode = CDirectoryNode::ParseURL(items.GetPath()); + + CFileItemPtr pItem; + + CMusicDbUrl musicUrl; + if (!musicUrl.FromString(directoryNode->BuildPath())) + return; + + // always show "all" items by default + if (!CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_MUSICLIBRARY_SHOWALLITEMS)) + return; + + // no need for "all" item when only one item + if (items.GetObjectCount() <= 1) + return; + + auto nodeChildType = directoryNode->GetChildType(); + + // No need for "*all" when overview node and child node is "albums" or "artists" + // without options (hence all albums or artists unfiltered). + if (directoryNode->GetType() == NODE_TYPE_OVERVIEW && + (nodeChildType == NODE_TYPE_ARTIST || nodeChildType == NODE_TYPE_ALBUM) && + musicUrl.GetOptions().empty()) + return; + // Smart playlist rules on parent node do not get applied to child nodes so no "*all" + // ! @Todo: Remove this allowing "*all" once rules do get applied to child nodes. + if (directoryNode->GetType() == NODE_TYPE_OVERVIEW && + (nodeChildType == NODE_TYPE_ARTIST || nodeChildType == NODE_TYPE_ALBUM) && + musicUrl.HasOption("xsp")) + return; + + switch (nodeChildType) + { + case NODE_TYPE_ARTIST: + pItem.reset(new CFileItem(g_localizeStrings.Get(15103))); // "All Artists" + musicUrl.AppendPath("-1/"); + pItem->SetPath(musicUrl.ToString()); + break; + + // All album related nodes + case NODE_TYPE_ALBUM: + case NODE_TYPE_ALBUM_RECENTLY_PLAYED: + case NODE_TYPE_ALBUM_RECENTLY_ADDED: + case NODE_TYPE_ALBUM_TOP100: + pItem.reset(new CFileItem(g_localizeStrings.Get(15102))); // "All Albums" + musicUrl.AppendPath("-1/"); + pItem->SetPath(musicUrl.ToString()); + break; + + // Disc node + case NODE_TYPE_DISC: + pItem.reset(new CFileItem(g_localizeStrings.Get(38075))); // "All Discs" + musicUrl.AppendPath("-1/"); + pItem->SetPath(musicUrl.ToString()); + break; + + default: + break; + } + + if (pItem) + { + pItem->m_bIsFolder = true; + pItem->SetSpecialSort(CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_bMusicLibraryAllItemsOnBottom ? SortSpecialOnBottom : SortSpecialOnTop); + pItem->SetCanQueue(false); + pItem->SetLabelPreformatted(true); + if (CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_bMusicLibraryAllItemsOnBottom) + items.Add(pItem); + else + items.AddFront(pItem, (items.Size() > 0 && items[0]->IsParentFolder()) ? 1 : 0); + } +} diff --git a/xbmc/music/windows/MusicFileItemListModifier.h b/xbmc/music/windows/MusicFileItemListModifier.h new file mode 100644 index 0000000..9a2190b --- /dev/null +++ b/xbmc/music/windows/MusicFileItemListModifier.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2016-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "IFileItemListModifier.h" + +class CMusicFileItemListModifier : public IFileItemListModifier +{ +public: + CMusicFileItemListModifier() = default; + ~CMusicFileItemListModifier() override = default; + + bool CanModify(const CFileItemList &items) const override; + bool Modify(CFileItemList &items) const override; + +private: + static void AddQueuingFolder(CFileItemList & items); +}; |