summaryrefslogtreecommitdiffstats
path: root/xbmc/music/windows
diff options
context:
space:
mode:
Diffstat (limited to 'xbmc/music/windows')
-rw-r--r--xbmc/music/windows/CMakeLists.txt15
-rw-r--r--xbmc/music/windows/GUIWindowMusicBase.cpp1097
-rw-r--r--xbmc/music/windows/GUIWindowMusicBase.h106
-rw-r--r--xbmc/music/windows/GUIWindowMusicNav.cpp944
-rw-r--r--xbmc/music/windows/GUIWindowMusicNav.h50
-rw-r--r--xbmc/music/windows/GUIWindowMusicPlaylist.cpp715
-rw-r--r--xbmc/music/windows/GUIWindowMusicPlaylist.h44
-rw-r--r--xbmc/music/windows/GUIWindowMusicPlaylistEditor.cpp450
-rw-r--r--xbmc/music/windows/GUIWindowMusicPlaylistEditor.h57
-rw-r--r--xbmc/music/windows/GUIWindowVisualisation.cpp233
-rw-r--r--xbmc/music/windows/GUIWindowVisualisation.h31
-rw-r--r--xbmc/music/windows/MusicFileItemListModifier.cpp114
-rw-r--r--xbmc/music/windows/MusicFileItemListModifier.h24
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);
+};