2 * Copyright (C) 2005-2013 Team XBMC
5 * This Program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2, or (at your option)
10 * This Program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with XBMC; see the file COPYING. If not, see
17 * <http://www.gnu.org/licenses/>.
21 #include "GUIWindowMusicPlaylist.h"
22 #include "dialogs/GUIDialogSmartPlaylistEditor.h"
24 #include "playlists/PlayListM3U.h"
25 #include "Application.h"
26 #include "PlayListPlayer.h"
27 #include "PartyModeManager.h"
28 #include "utils/LabelFormatter.h"
29 #include "music/tags/MusicInfoTag.h"
30 #include "guilib/GUIWindowManager.h"
31 #include "guilib/GUIKeyboardFactory.h"
32 #include "guilib/Key.h"
33 #include "GUIUserMessages.h"
34 #include "filesystem/FavouritesDirectory.h"
35 #include "profiles/ProfilesManager.h"
36 #include "settings/MediaSettings.h"
37 #include "settings/Settings.h"
38 #include "guilib/LocalizeStrings.h"
39 #include "utils/StringUtils.h"
40 #include "utils/log.h"
41 #include "utils/URIUtils.h"
43 using namespace PLAYLIST;
45 #define CONTROL_BTNVIEWASICONS 2
46 #define CONTROL_BTNSORTBY 3
47 #define CONTROL_BTNSORTASC 4
48 #define CONTROL_LABELFILES 12
50 #define CONTROL_BTNSHUFFLE 20
51 #define CONTROL_BTNSAVE 21
52 #define CONTROL_BTNCLEAR 22
54 #define CONTROL_BTNPLAY 23
55 #define CONTROL_BTNNEXT 24
56 #define CONTROL_BTNPREVIOUS 25
57 #define CONTROL_BTNREPEAT 26
59 CGUIWindowMusicPlayList::CGUIWindowMusicPlayList(void)
60 : CGUIWindowMusicBase(WINDOW_MUSIC_PLAYLIST, "MyMusicPlaylist.xml")
62 m_musicInfoLoader.SetObserver(this);
66 CGUIWindowMusicPlayList::~CGUIWindowMusicPlayList(void)
70 bool CGUIWindowMusicPlayList::OnMessage(CGUIMessage& message)
72 switch ( message.GetMessage() )
74 case GUI_MSG_PLAYLISTPLAYER_REPEAT:
80 case GUI_MSG_PLAYLISTPLAYER_RANDOM:
81 case GUI_MSG_PLAYLIST_CHANGED:
83 // global playlist changed outside playlist window
87 if (m_viewControl.HasControl(m_iLastControl) && m_vecItems->Size() <= 0)
89 m_iLastControl = CONTROL_BTNVIEWASICONS;
90 SET_CONTROL_FOCUS(m_iLastControl, 0);
96 case GUI_MSG_WINDOW_DEINIT:
98 if (m_musicInfoLoader.IsLoading())
99 m_musicInfoLoader.StopThread();
105 case GUI_MSG_WINDOW_INIT:
107 // Setup item cache for tagloader
108 m_musicInfoLoader.UseCacheOnHD("special://temp/MusicPlaylist.fi");
110 m_vecItems->SetPath("playlistmusic://");
112 // updatebuttons is called in here
113 if (!CGUIWindowMusicBase::OnMessage(message))
116 if (m_vecItems->Size() <= 0)
118 m_iLastControl = CONTROL_BTNVIEWASICONS;
119 SET_CONTROL_FOCUS(m_iLastControl, 0);
122 if (g_application.m_pPlayer->IsPlayingAudio() && g_playlistPlayer.GetCurrentPlaylist() == PLAYLIST_MUSIC)
124 int iSong = g_playlistPlayer.GetCurrentSong();
125 if (iSong >= 0 && iSong <= m_vecItems->Size())
126 m_viewControl.SetSelectedItem(iSong);
133 case GUI_MSG_CLICKED:
135 int iControl = message.GetSenderId();
136 if (iControl == CONTROL_BTNSHUFFLE)
138 if (!g_partyModeManager.IsEnabled())
140 g_playlistPlayer.SetShuffle(PLAYLIST_MUSIC, !(g_playlistPlayer.IsShuffled(PLAYLIST_MUSIC)));
141 CMediaSettings::Get().SetMusicPlaylistShuffled(g_playlistPlayer.IsShuffled(PLAYLIST_MUSIC));
142 CSettings::Get().Save();
147 else if (iControl == CONTROL_BTNSAVE)
149 if (m_musicInfoLoader.IsLoading()) // needed since we destroy m_vecitems to save memory
150 m_musicInfoLoader.StopThread();
154 else if (iControl == CONTROL_BTNCLEAR)
156 if (m_musicInfoLoader.IsLoading())
157 m_musicInfoLoader.StopThread();
161 else if (iControl == CONTROL_BTNPLAY)
163 m_guiState->SetPlaylistDirectory("playlistmusic://");
164 g_playlistPlayer.SetCurrentPlaylist(PLAYLIST_MUSIC);
165 g_playlistPlayer.Reset();
166 g_playlistPlayer.Play(m_viewControl.GetSelectedItem());
169 else if (iControl == CONTROL_BTNNEXT)
171 g_playlistPlayer.SetCurrentPlaylist(PLAYLIST_MUSIC);
172 g_playlistPlayer.PlayNext();
174 else if (iControl == CONTROL_BTNPREVIOUS)
176 g_playlistPlayer.SetCurrentPlaylist(PLAYLIST_MUSIC);
177 g_playlistPlayer.PlayPrevious();
179 else if (iControl == CONTROL_BTNREPEAT)
181 // increment repeat state
182 PLAYLIST::REPEAT_STATE state = g_playlistPlayer.GetRepeat(PLAYLIST_MUSIC);
183 if (state == PLAYLIST::REPEAT_NONE)
184 g_playlistPlayer.SetRepeat(PLAYLIST_MUSIC, PLAYLIST::REPEAT_ALL);
185 else if (state == PLAYLIST::REPEAT_ALL)
186 g_playlistPlayer.SetRepeat(PLAYLIST_MUSIC, PLAYLIST::REPEAT_ONE);
188 g_playlistPlayer.SetRepeat(PLAYLIST_MUSIC, PLAYLIST::REPEAT_NONE);
191 CMediaSettings::Get().SetMusicPlaylistRepeat(g_playlistPlayer.GetRepeat(PLAYLIST_MUSIC) == PLAYLIST::REPEAT_ALL);
192 CSettings::Get().Save();
196 else if (m_viewControl.HasControl(iControl))
198 int iAction = message.GetParam1();
199 int iItem = m_viewControl.GetSelectedItem();
200 if (iAction == ACTION_DELETE_ITEM || iAction == ACTION_MOUSE_MIDDLE_CLICK)
202 RemovePlayListItem(iItem);
210 return CGUIWindowMusicBase::OnMessage(message);
213 bool CGUIWindowMusicPlayList::OnAction(const CAction &action)
215 if (action.GetID() == ACTION_PARENT_DIR)
217 // Playlist has no parent dirs
220 if (action.GetID() == ACTION_SHOW_PLAYLIST)
222 g_windowManager.PreviousWindow();
225 if ((action.GetID() == ACTION_MOVE_ITEM_UP) || (action.GetID() == ACTION_MOVE_ITEM_DOWN))
228 int iFocusedControl = GetFocusedControlID();
229 if (m_viewControl.HasControl(iFocusedControl))
230 iItem = m_viewControl.GetSelectedItem();
231 OnMove(iItem, action.GetID());
234 return CGUIWindowMusicBase::OnAction(action);
237 bool CGUIWindowMusicPlayList::OnBack(int actionID)
239 if (actionID == ACTION_NAV_BACK)
240 return CGUIWindow::OnBack(actionID); // base class goes up a folder, but none to go up
241 return CGUIWindowMusicBase::OnBack(actionID);
244 bool CGUIWindowMusicPlayList::MoveCurrentPlayListItem(int iItem, int iAction, bool bUpdate /* = true */)
246 int iSelected = iItem;
247 int iNew = iSelected;
248 if (iAction == ACTION_MOVE_ITEM_UP)
253 // is the currently playing item affected?
254 bool bFixCurrentSong = false;
255 if ((g_playlistPlayer.GetCurrentPlaylist() == PLAYLIST_MUSIC) && (g_application.m_pPlayer->IsPlayingAudio()) &&
256 ((g_playlistPlayer.GetCurrentSong() == iSelected) || (g_playlistPlayer.GetCurrentSong() == iNew)))
257 bFixCurrentSong = true;
259 CPlayList& playlist = g_playlistPlayer.GetPlaylist(PLAYLIST_MUSIC);
260 if (playlist.Swap(iSelected, iNew))
262 // Correct the current playing song in playlistplayer
265 int iCurrentSong = g_playlistPlayer.GetCurrentSong();
266 if (iSelected == iCurrentSong)
268 else if (iNew == iCurrentSong)
269 iCurrentSong = iSelected;
270 g_playlistPlayer.SetCurrentSong(iCurrentSong);
281 void CGUIWindowMusicPlayList::SavePlayList()
283 CStdString strNewFileName;
284 if (CGUIKeyboardFactory::ShowAndGetInput(strNewFileName, g_localizeStrings.Get(16012), false))
287 CStdString strFolder = URIUtils::AddFileToFolder(CSettings::Get().GetString("system.playlistspath"), "music");
288 strNewFileName = CUtil::MakeLegalFileName(strNewFileName);
289 strNewFileName += ".m3u";
290 CStdString strPath = URIUtils::AddFileToFolder(strFolder, strNewFileName);
293 int iItem = m_viewControl.GetSelectedItem();
294 CStdString strSelectedItem = "";
295 if (iItem >= 0 && iItem < m_vecItems->Size())
297 CFileItemPtr pItem = m_vecItems->Get(iItem);
298 if (!pItem->IsParentFolder())
300 GetDirectoryHistoryString(pItem.get(), strSelectedItem);
304 CStdString strOldDirectory = m_vecItems->GetPath();
305 m_history.SetSelectedItem(strSelectedItem, strOldDirectory);
307 CPlayListM3U playlist;
308 for (int i = 0; i < (int)m_vecItems->Size(); ++i)
310 CFileItemPtr pItem = m_vecItems->Get(i);
312 // Musicdatabase items should contain the real path instead of a musicdb url
313 // otherwise the user can't save and reuse the playlist when the musicdb gets deleted
314 if (pItem->IsMusicDb())
315 pItem->SetPath(pItem->GetMusicInfoTag()->GetURL());
319 CLog::Log(LOGDEBUG, "Saving music playlist: [%s]", strPath.c_str());
320 playlist.Save(strPath);
321 Refresh(); // need to update
325 void CGUIWindowMusicPlayList::ClearPlayList()
328 g_playlistPlayer.ClearPlaylist(PLAYLIST_MUSIC);
329 if (g_playlistPlayer.GetCurrentPlaylist() == PLAYLIST_MUSIC)
331 g_playlistPlayer.Reset();
332 g_playlistPlayer.SetCurrentPlaylist(PLAYLIST_NONE);
335 SET_CONTROL_FOCUS(CONTROL_BTNVIEWASICONS, 0);
338 void CGUIWindowMusicPlayList::RemovePlayListItem(int iItem)
340 if (iItem < 0 || iItem > m_vecItems->Size()) return;
342 // The current playing song can't be removed
343 if (g_playlistPlayer.GetCurrentPlaylist() == PLAYLIST_MUSIC && g_application.m_pPlayer->IsPlayingAudio()
344 && g_playlistPlayer.GetCurrentSong() == iItem)
347 g_playlistPlayer.Remove(PLAYLIST_MUSIC, iItem);
351 if (m_vecItems->Size() <= 0)
353 SET_CONTROL_FOCUS(CONTROL_BTNVIEWASICONS, 0);
357 m_viewControl.SetSelectedItem(iItem);
360 g_partyModeManager.OnSongChange();
363 void CGUIWindowMusicPlayList::UpdateButtons()
365 CGUIWindowMusicBase::UpdateButtons();
367 // Update playlist buttons
368 if (m_vecItems->Size() && !g_partyModeManager.IsEnabled())
370 CONTROL_ENABLE(CONTROL_BTNSHUFFLE);
371 CONTROL_ENABLE(CONTROL_BTNSAVE);
372 CONTROL_ENABLE(CONTROL_BTNCLEAR);
373 CONTROL_ENABLE(CONTROL_BTNREPEAT);
374 CONTROL_ENABLE(CONTROL_BTNPLAY);
376 if (g_application.m_pPlayer->IsPlayingAudio() && g_playlistPlayer.GetCurrentPlaylist() == PLAYLIST_MUSIC)
378 CONTROL_ENABLE(CONTROL_BTNNEXT);
379 CONTROL_ENABLE(CONTROL_BTNPREVIOUS);
383 CONTROL_DISABLE(CONTROL_BTNNEXT);
384 CONTROL_DISABLE(CONTROL_BTNPREVIOUS);
389 // disable buttons if party mode is enabled too
390 CONTROL_DISABLE(CONTROL_BTNSHUFFLE);
391 CONTROL_DISABLE(CONTROL_BTNSAVE);
392 CONTROL_DISABLE(CONTROL_BTNCLEAR);
393 CONTROL_DISABLE(CONTROL_BTNREPEAT);
394 CONTROL_DISABLE(CONTROL_BTNPLAY);
395 CONTROL_DISABLE(CONTROL_BTNNEXT);
396 CONTROL_DISABLE(CONTROL_BTNPREVIOUS);
400 CONTROL_DESELECT(CONTROL_BTNSHUFFLE);
401 if (g_playlistPlayer.IsShuffled(PLAYLIST_MUSIC))
402 CONTROL_SELECT(CONTROL_BTNSHUFFLE);
404 // update repeat button
405 int iRepeat = 595 + g_playlistPlayer.GetRepeat(PLAYLIST_MUSIC);
406 SET_CONTROL_LABEL(CONTROL_BTNREPEAT, g_localizeStrings.Get(iRepeat));
408 // Update object count label
409 CStdString items = StringUtils::Format("%i %s", m_vecItems->GetObjectCount(), g_localizeStrings.Get(127).c_str());
410 SET_CONTROL_LABEL(CONTROL_LABELFILES, items);
415 bool CGUIWindowMusicPlayList::OnPlayMedia(int iItem)
417 if (g_partyModeManager.IsEnabled())
418 g_partyModeManager.Play(iItem);
421 int iPlaylist=m_guiState->GetPlaylist();
422 if (iPlaylist!=PLAYLIST_NONE)
424 if (m_guiState.get())
425 m_guiState->SetPlaylistDirectory(m_vecItems->GetPath());
427 g_playlistPlayer.SetCurrentPlaylist( iPlaylist );
428 g_playlistPlayer.Play( iItem );
432 // Reset Playlistplayer, playback started now does
433 // not use the playlistplayer.
434 CFileItemPtr pItem=m_vecItems->Get(iItem);
435 g_playlistPlayer.Reset();
436 g_playlistPlayer.SetCurrentPlaylist(PLAYLIST_NONE);
437 g_application.PlayFile(*pItem);
444 void CGUIWindowMusicPlayList::OnItemLoaded(CFileItem* pItem)
446 if (pItem->HasMusicInfoTag() && pItem->GetMusicInfoTag()->Loaded())
447 { // set label 1+2 from tags
448 if (m_guiState.get()) m_hideExtensions = m_guiState->HideExtensions();
449 CStdString strTrackLeft=CSettings::Get().GetString("musicfiles.nowplayingtrackformat");
450 if (strTrackLeft.IsEmpty())
451 strTrackLeft = CSettings::Get().GetString("musicfiles.trackformat");
452 CStdString strTrackRight=CSettings::Get().GetString("musicfiles.nowplayingtrackformatright");
453 if (strTrackRight.IsEmpty())
454 strTrackRight = CSettings::Get().GetString("musicfiles.trackformatright");
455 CLabelFormatter formatter(strTrackLeft, strTrackRight);
456 formatter.FormatLabels(pItem);
457 } // if (pItem->m_musicInfoTag.Loaded())
460 // Our tag may have a duration even if its not loaded
461 if (pItem->HasMusicInfoTag() && pItem->GetMusicInfoTag()->GetDuration())
463 int nDuration = pItem->GetMusicInfoTag()->GetDuration();
465 pItem->SetLabel2(StringUtils::SecondsToTimeString(nDuration));
467 else if (pItem->GetLabel() == "") // pls labels come in preformatted
469 // FIXME: get the position of the item in the playlist
470 // currently it is hacked into m_iprogramCount
472 // No music info and it's not CDDA so we'll just show the filename
474 str = CUtil::GetTitleFromPath(pItem->GetPath());
475 str = StringUtils::Format("%02.2i. %s ", pItem->m_iprogramCount, str.c_str());
476 pItem->SetLabel(str);
481 bool CGUIWindowMusicPlayList::Update(const CStdString& strDirectory, bool updateFilterPath /* = true */)
483 if (m_musicInfoLoader.IsLoading())
484 m_musicInfoLoader.StopThread();
486 if (!CGUIWindowMusicBase::Update(strDirectory, updateFilterPath))
489 if (m_vecItems->GetContent().IsEmpty())
490 m_vecItems->SetContent("songs");
492 m_musicInfoLoader.Load(*m_vecItems);
496 void CGUIWindowMusicPlayList::GetContextButtons(int itemNumber, CContextButtons &buttons)
498 // is this playlist playing?
499 int itemPlaying = g_playlistPlayer.GetCurrentSong();
501 if (itemNumber >= 0 && itemNumber < m_vecItems->Size())
504 item = m_vecItems->Get(itemNumber);
506 if (m_movingFrom >= 0)
508 // we can move the item to any position not where we are, and any position not above currently
509 // playing item in party mode
510 if (itemNumber != m_movingFrom && (!g_partyModeManager.IsEnabled() || itemNumber > itemPlaying))
511 buttons.Add(CONTEXT_BUTTON_MOVE_HERE, 13252); // move item here
512 buttons.Add(CONTEXT_BUTTON_CANCEL_MOVE, 13253);
515 { // aren't in a move
516 // check what players we have, if we have multiple display play with option
517 VECPLAYERCORES vecCores;
518 CPlayerCoreFactory::Get().GetPlayers(*item, vecCores);
519 if (vecCores.size() > 1)
520 buttons.Add(CONTEXT_BUTTON_PLAY_WITH, 15213); // Play With...
522 buttons.Add(CONTEXT_BUTTON_SONG_INFO, 658); // Song Info
523 if (XFILE::CFavouritesDirectory::IsFavourite(item.get(), GetID()))
524 buttons.Add(CONTEXT_BUTTON_ADD_FAVOURITE, 14077); // Remove Favourite
526 buttons.Add(CONTEXT_BUTTON_ADD_FAVOURITE, 14076); // Add To Favourites;
527 if (itemNumber > (g_partyModeManager.IsEnabled() ? 1 : 0))
528 buttons.Add(CONTEXT_BUTTON_MOVE_ITEM_UP, 13332);
529 if (itemNumber + 1 < m_vecItems->Size())
530 buttons.Add(CONTEXT_BUTTON_MOVE_ITEM_DOWN, 13333);
531 if (!g_partyModeManager.IsEnabled() || itemNumber != itemPlaying)
532 buttons.Add(CONTEXT_BUTTON_MOVE_ITEM, 13251);
533 if (itemNumber != itemPlaying)
534 buttons.Add(CONTEXT_BUTTON_DELETE, 1210); // Remove
538 if (g_partyModeManager.IsEnabled())
540 buttons.Add(CONTEXT_BUTTON_EDIT_PARTYMODE, 21439);
541 buttons.Add(CONTEXT_BUTTON_CANCEL_PARTYMODE, 588); // cancel party mode
545 bool CGUIWindowMusicPlayList::OnContextButton(int itemNumber, CONTEXT_BUTTON button)
549 case CONTEXT_BUTTON_PLAY_WITH:
552 if (itemNumber >= 0 && itemNumber < m_vecItems->Size())
553 item = m_vecItems->Get(itemNumber);
557 VECPLAYERCORES vecCores;
558 CPlayerCoreFactory::Get().GetPlayers(*item, vecCores);
559 g_application.m_eForcedNextPlayer = CPlayerCoreFactory::Get().SelectPlayerDialog(vecCores);
560 if( g_application.m_eForcedNextPlayer != EPC_NONE )
564 case CONTEXT_BUTTON_MOVE_ITEM:
565 m_movingFrom = itemNumber;
568 case CONTEXT_BUTTON_MOVE_HERE:
569 MoveItem(m_movingFrom, itemNumber);
573 case CONTEXT_BUTTON_CANCEL_MOVE:
577 case CONTEXT_BUTTON_MOVE_ITEM_UP:
578 OnMove(itemNumber, ACTION_MOVE_ITEM_UP);
581 case CONTEXT_BUTTON_MOVE_ITEM_DOWN:
582 OnMove(itemNumber, ACTION_MOVE_ITEM_DOWN);
585 case CONTEXT_BUTTON_DELETE:
586 RemovePlayListItem(itemNumber);
589 case CONTEXT_BUTTON_ADD_FAVOURITE:
591 CFileItemPtr item = m_vecItems->Get(itemNumber);
592 XFILE::CFavouritesDirectory::AddOrRemove(item.get(), GetID());
596 case CONTEXT_BUTTON_CANCEL_PARTYMODE:
597 g_partyModeManager.Disable();
600 case CONTEXT_BUTTON_EDIT_PARTYMODE:
602 CStdString playlist = CProfilesManager::Get().GetUserDataItem("PartyMode.xsp");
603 if (CGUIDialogSmartPlaylistEditor::EditPlaylist(playlist))
606 g_partyModeManager.Disable();
607 g_partyModeManager.Enable();
615 return CGUIWindowMusicBase::OnContextButton(itemNumber, button);
619 void CGUIWindowMusicPlayList::OnMove(int iItem, int iAction)
621 if (iItem < 0 || iItem >= m_vecItems->Size()) return;
623 bool bRestart = m_musicInfoLoader.IsLoading();
625 m_musicInfoLoader.StopThread();
627 MoveCurrentPlayListItem(iItem, iAction);
630 m_musicInfoLoader.Load(*m_vecItems);
633 void CGUIWindowMusicPlayList::MoveItem(int iStart, int iDest)
635 if (iStart < 0 || iStart >= m_vecItems->Size()) return;
636 if (iDest < 0 || iDest >= m_vecItems->Size()) return;
638 // default to move up
639 int iAction = ACTION_MOVE_ITEM_UP;
641 // are we moving down?
644 iAction = ACTION_MOVE_ITEM_DOWN;
648 bool bRestart = m_musicInfoLoader.IsLoading();
650 m_musicInfoLoader.StopThread();
652 // keep swapping until you get to the destination or you
653 // hit the currently playing song
657 // try to swap adjacent items
658 if (MoveCurrentPlayListItem(i, iAction, false))
659 i = i + (1 * iDirection);
660 // we hit currently playing song, so abort
667 m_musicInfoLoader.Load(*m_vecItems);
670 void CGUIWindowMusicPlayList::MarkPlaying()
673 for (int i = 0; i < m_vecItems->Size(); i++)
674 m_vecItems->Get(i)->Select(false);
676 // mark the currently playing item
677 if ((g_playlistPlayer.GetCurrentPlaylist() == PLAYLIST_MUSIC) && (g_application.m_pPlayer->IsPlayingAudio()))
679 int iSong = g_playlistPlayer.GetCurrentSong();
680 if (iSong >= 0 && iSong <= m_vecItems->Size())
681 m_vecItems->Get(iSong)->Select(true);