changed: Add logic to properly handle subtitles for stacked files
[vuplus_xbmc] / xbmc / music / windows / GUIWindowMusicBase.cpp
1 /*
2  *      Copyright (C) 2005-2013 Team XBMC
3  *      http://xbmc.org
4  *
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)
8  *  any later version.
9  *
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.
14  *
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/>.
18  *
19  */
20
21 #include "threads/SystemClock.h"
22 #include "system.h"
23 #include "GUIUserMessages.h"
24 #include "GUIWindowMusicBase.h"
25 #include "music/dialogs/GUIDialogMusicInfo.h"
26 #include "filesystem/ZipManager.h"
27 #ifdef HAS_FILESYSTEM_DAAP
28 #include "filesystem/DAAPDirectory.h"
29 #endif
30 #include "playlists/PlayListFactory.h"
31 #include "Util.h"
32 #include "playlists/PlayListM3U.h"
33 #include "Application.h"
34 #include "PlayListPlayer.h"
35 #include "filesystem/DirectoryCache.h"
36 #ifdef HAS_CDDA_RIPPER
37 #include "cdrip/CDDARipper.h"
38 #endif
39 #include "GUIPassword.h"
40 #include "dialogs/GUIDialogMediaSource.h"
41 #include "PartyModeManager.h"
42 #include "GUIInfoManager.h"
43 #include "filesystem/MusicDatabaseDirectory.h"
44 #include "music/dialogs/GUIDialogSongInfo.h"
45 #include "addons/GUIDialogAddonInfo.h"
46 #include "dialogs/GUIDialogSmartPlaylistEditor.h"
47 #include "music/tags/MusicInfoTag.h"
48 #include "guilib/GUIWindowManager.h"
49 #include "guilib/Key.h"
50 #include "dialogs/GUIDialogOK.h"
51 #include "dialogs/GUIDialogYesNo.h"
52 #include "guilib/GUIKeyboardFactory.h"
53 #include "dialogs/GUIDialogProgress.h"
54 #include "FileItem.h"
55 #include "filesystem/File.h"
56 #include "profiles/ProfilesManager.h"
57 #include "storage/MediaManager.h"
58 #include "settings/AdvancedSettings.h"
59 #include "settings/MediaSettings.h"
60 #include "settings/Settings.h"
61 #include "guilib/LocalizeStrings.h"
62 #include "utils/TimeUtils.h"
63 #include "utils/log.h"
64 #include "utils/URIUtils.h"
65 #include "video/VideoInfoTag.h"
66 #include "utils/StringUtils.h"
67 #include "URL.h"
68 #include "music/infoscanner/MusicInfoScanner.h"
69 #include "cores/IPlayer.h"
70
71 using namespace std;
72 using namespace XFILE;
73 using namespace MUSICDATABASEDIRECTORY;
74 using namespace PLAYLIST;
75 using namespace MUSIC_GRABBER;
76 using namespace MUSIC_INFO;
77
78 #define CONTROL_BTNVIEWASICONS  2
79 #define CONTROL_BTNSORTBY       3
80 #define CONTROL_BTNSORTASC      4
81 #define CONTROL_BTNTYPE         5
82
83 CGUIWindowMusicBase::CGUIWindowMusicBase(int id, const CStdString &xmlFile)
84     : CGUIMediaWindow(id, xmlFile)
85 {
86   m_dlgProgress = NULL;
87 }
88
89 CGUIWindowMusicBase::~CGUIWindowMusicBase ()
90 {
91 }
92
93 bool CGUIWindowMusicBase::OnBack(int actionID)
94 {
95   if (!g_application.IsMusicScanning())
96   {
97     CUtil::RemoveTempFiles();
98   }
99   return CGUIMediaWindow::OnBack(actionID);
100 }
101
102 /*!
103  \brief Handle messages on window.
104  \param message GUI Message that can be reacted on.
105  \return if a message can't be processed, return \e false
106
107  On these messages this class reacts.\n
108  When retrieving...
109   - #GUI_MSG_WINDOW_DEINIT\n
110    ...the last focused control is saved to m_iLastControl.
111   - #GUI_MSG_WINDOW_INIT\n
112    ...the musicdatabase is opend and the music extensions and shares are set.
113    The last focused control is set.
114   - #GUI_MSG_CLICKED\n
115    ... the base class reacts on the following controls:\n
116     Buttons:\n
117     - #CONTROL_BTNVIEWASICONS - switch between list, thumb and with large items
118     - #CONTROL_BTNTYPE - switch between music windows
119     - #CONTROL_BTNSEARCH - Search for items\n
120     Other Controls:
121     - The container controls\n
122      Have the following actions in message them clicking on them.
123      - #ACTION_QUEUE_ITEM - add selected item to playlist
124      - #ACTION_SHOW_INFO - retrieve album info from the internet
125      - #ACTION_SELECT_ITEM - Item has been selected. Overwrite OnClick() to react on it
126  */
127 bool CGUIWindowMusicBase::OnMessage(CGUIMessage& message)
128 {
129   switch ( message.GetMessage() )
130   {
131   case GUI_MSG_WINDOW_DEINIT:
132     {
133       m_musicdatabase.Close();
134     }
135     break;
136
137   case GUI_MSG_WINDOW_INIT:
138     {
139       m_dlgProgress = (CGUIDialogProgress*)g_windowManager.GetWindow(WINDOW_DIALOG_PROGRESS);
140
141       m_musicdatabase.Open();
142
143       if (!CGUIMediaWindow::OnMessage(message))
144         return false;
145
146       // save current window, unless the current window is the music playlist window
147       if (GetID() != WINDOW_MUSIC_PLAYLIST &&
148           CSettings::Get().GetInt("mymusic.startwindow") != GetID())
149       {
150         CSettings::Get().SetInt("mymusic.startwindow", GetID());
151         CSettings::Get().Save();
152       }
153
154       return true;
155     }
156     break;
157
158   // update the display
159   case GUI_MSG_SCAN_FINISHED:
160   case GUI_MSG_REFRESH_THUMBS:
161     Refresh();
162     break;
163
164   case GUI_MSG_CLICKED:
165     {
166       int iControl = message.GetSenderId();
167       if (iControl == CONTROL_BTNTYPE)
168       {
169         CGUIMessage msg(GUI_MSG_ITEM_SELECTED, GetID(), CONTROL_BTNTYPE);
170         g_windowManager.SendMessage(msg);
171
172         int nWindow = WINDOW_MUSIC_FILES + msg.GetParam1();
173
174         if (nWindow == GetID())
175           return true;
176
177         CSettings::Get().SetInt("mymusic.startwindow", nWindow);
178         CSettings::Get().Save();
179         g_windowManager.ChangeActiveWindow(nWindow);
180
181         CGUIMessage msg2(GUI_MSG_SETFOCUS, CSettings::Get().GetInt("mymusic.startwindow"), CONTROL_BTNTYPE);
182         g_windowManager.SendMessage(msg2);
183
184         return true;
185       }
186       else if (m_viewControl.HasControl(iControl))  // list/thumb control
187       {
188         int iItem = m_viewControl.GetSelectedItem();
189         int iAction = message.GetParam1();
190
191         // iItem is checked for validity inside these routines
192         if (iAction == ACTION_QUEUE_ITEM || iAction == ACTION_MOUSE_MIDDLE_CLICK)
193         {
194           OnQueueItem(iItem);
195         }
196         else if (iAction == ACTION_SHOW_INFO)
197         {
198           OnInfo(iItem);
199         }
200         else if (iAction == ACTION_DELETE_ITEM)
201         {
202           // is delete allowed?
203           // must be at the playlists directory
204           if (m_vecItems->GetPath().Equals("special://musicplaylists/"))
205             OnDeleteItem(iItem);
206
207           // or be at the files window and have file deletion enabled
208           else if (GetID() == WINDOW_MUSIC_FILES &&
209                    CSettings::Get().GetBool("filelists.allowfiledeletion"))
210           {
211             OnDeleteItem(iItem);
212           }
213
214           else
215             return false;
216         }
217         // use play button to add folders of items to temp playlist
218         else if (iAction == ACTION_PLAYER_PLAY)
219         {
220           // if playback is paused or playback speed != 1, return
221           if (g_application.m_pPlayer->IsPlayingAudio())
222           {
223             if (g_application.m_pPlayer->IsPausedPlayback())
224               return false;
225             if (g_application.m_pPlayer->GetPlaySpeed() != 1)
226               return false;
227           }
228
229           // not playing audio, or playback speed == 1
230           PlayItem(iItem);
231
232           return true;
233         }
234       }
235     }
236   }
237   return CGUIMediaWindow::OnMessage(message);
238 }
239
240 bool CGUIWindowMusicBase::OnAction(const CAction &action)
241 {
242   if (action.GetID() == ACTION_SHOW_PLAYLIST)
243   {
244     if (g_playlistPlayer.GetCurrentPlaylist() == PLAYLIST_MUSIC ||
245         g_playlistPlayer.GetPlaylist(PLAYLIST_MUSIC).size() > 0)
246     {
247       g_windowManager.ActivateWindow(WINDOW_MUSIC_PLAYLIST);
248       return true;
249     }
250   }
251
252   return CGUIMediaWindow::OnAction(action);
253 }
254
255 void CGUIWindowMusicBase::OnInfoAll(int iItem, bool bCurrent, bool refresh)
256 {
257   CMusicDatabaseDirectory dir;
258   CStdString strPath = m_vecItems->GetPath();
259   if (bCurrent)
260     strPath = m_vecItems->Get(iItem)->GetPath();
261
262   if (dir.HasAlbumInfo(strPath) ||
263       CMusicDatabaseDirectory::GetDirectoryChildType(strPath) == 
264       MUSICDATABASEDIRECTORY::NODE_TYPE_ALBUM)
265     g_application.StartMusicAlbumScan(strPath,refresh);
266   else
267     g_application.StartMusicArtistScan(strPath,refresh);
268 }
269
270 /// \brief Retrieves music info for albums from allmusic.com and displays them in CGUIDialogMusicInfo
271 /// \param iItem Item in list/thumb control
272 void CGUIWindowMusicBase::OnInfo(int iItem, bool bShowInfo)
273 {
274   if ( iItem < 0 || iItem >= m_vecItems->Size() )
275     return;
276
277   CFileItemPtr item = m_vecItems->Get(iItem);
278
279   if (item->IsVideoDb())
280   { // music video
281     OnContextButton(iItem, CONTEXT_BUTTON_INFO);
282     return;
283   }
284
285   if (!m_vecItems->IsPlugin() && (item->IsPlugin() || item->IsScript()))
286   {
287     CGUIDialogAddonInfo::ShowForItem(item);
288     return;
289   }
290
291   OnInfo(item.get(), bShowInfo);
292 }
293
294 void CGUIWindowMusicBase::OnInfo(CFileItem *pItem, bool bShowInfo)
295 {
296   if ((pItem->IsMusicDb() && !pItem->HasMusicInfoTag()) || pItem->IsParentFolder() ||
297        URIUtils::IsSpecial(pItem->GetPath()) || StringUtils::StartsWithNoCase(pItem->GetPath(), "musicsearch://"))
298     return; // nothing to do
299
300   if (!pItem->m_bIsFolder)
301   { // song lookup
302     ShowSongInfo(pItem);
303     return;
304   }
305
306   // this function called from outside this window - make sure the database is open
307   m_musicdatabase.Open();
308
309   // we have a folder
310   if (pItem->IsMusicDb())
311   {
312     CQueryParams params;
313     CDirectoryNode::GetDatabaseInfo(pItem->GetPath(), params);
314     if (params.GetAlbumId() == -1)
315       ShowArtistInfo(pItem);
316     else
317       ShowAlbumInfo(pItem);
318
319     if (m_dlgProgress && bShowInfo)
320       m_dlgProgress->Close();
321     return;
322   }
323
324   CFileItemList items;
325   GetDirectory(pItem->GetPath(), items);
326
327   // show dialog box indicating we're searching the album name
328   if (m_dlgProgress && bShowInfo)
329   {
330     m_dlgProgress->SetHeading(185);
331     m_dlgProgress->SetLine(0, 501);
332     m_dlgProgress->SetLine(1, "");
333     m_dlgProgress->SetLine(2, "");
334     m_dlgProgress->StartModal();
335     m_dlgProgress->Progress();
336     if (m_dlgProgress->IsCanceled())
337     {
338       return;
339     }
340   }
341
342   // check the first song we find in the folder, and grab its album info
343   for (int i = 0; i < items.Size(); i++)
344   {
345     CFileItemPtr pItem = items[i];
346     pItem->LoadMusicTag();
347     if (pItem->HasMusicInfoTag() && pItem->GetMusicInfoTag()->Loaded() &&
348        !pItem->GetMusicInfoTag()->GetAlbum().empty())
349     {
350       if (m_dlgProgress && bShowInfo)
351         m_dlgProgress->Close();
352
353       if (!ShowAlbumInfo(pItem.get())) // Something went wrong, so bail for the rest
354         break;
355     }
356   }
357
358   CLog::Log(LOGINFO, "%s called on a folder containing no songs with tag info - nothing can be done", __FUNCTION__);
359   if (m_dlgProgress && bShowInfo)
360       m_dlgProgress->Close();
361 }
362
363 void CGUIWindowMusicBase::ShowArtistInfo(const CFileItem *pItem, bool bShowInfo /* = true */)
364 {
365   CQueryParams params;
366   CDirectoryNode::GetDatabaseInfo(pItem->GetPath(), params);
367   CMusicArtistInfo artistInfo;
368   while (1)
369   {
370     // Check if we have the information in the database first
371     if (!m_musicdatabase.HasArtistInfo(params.GetArtistId()) ||
372         !m_musicdatabase.GetArtistInfo(params.GetArtistId(), artistInfo.GetArtist()))
373     {
374       if (!CProfilesManager::Get().GetCurrentProfile().canWriteDatabases() && !g_passwordManager.bMasterUser)
375         break; // should display a dialog saying no permissions
376
377       if (g_application.IsMusicScanning())
378       {
379         CGUIDialogOK::ShowAndGetInput(189, 14057, 0, 0);
380         break;
381       }
382
383       // show dialog box indicating we're searching the album
384       if (m_dlgProgress && bShowInfo)
385       {
386         m_dlgProgress->SetHeading(21889);
387         m_dlgProgress->SetLine(0, pItem->GetMusicInfoTag()->GetArtist());
388         m_dlgProgress->SetLine(1, "");
389         m_dlgProgress->SetLine(2, "");
390         m_dlgProgress->StartModal();
391       }
392
393       CMusicInfoScanner scanner;
394       if (scanner.UpdateDatabaseArtistInfo(pItem->GetPath(), artistInfo, bShowInfo) != INFO_ADDED || !artistInfo.Loaded())
395       {
396         CGUIDialogOK::ShowAndGetInput(21889, 0, 20199, 0);
397         break;
398       }
399     }
400
401     if (m_dlgProgress)
402       m_dlgProgress->Close();
403
404     CGUIDialogMusicInfo *pDlgArtistInfo = (CGUIDialogMusicInfo*)g_windowManager.GetWindow(WINDOW_DIALOG_MUSIC_INFO);
405     if (pDlgArtistInfo)
406     {
407       CStdString strPath;
408       m_musicdatabase.GetArtistPath(params.GetArtistId(), strPath);
409       pDlgArtistInfo->SetArtist(artistInfo.GetArtist(), strPath);
410       pDlgArtistInfo->DoModal();
411
412       if (pDlgArtistInfo->NeedRefresh())
413       {
414         m_musicdatabase.DeleteArtistInfo(params.GetArtistId());
415         continue;
416       } 
417       else if (pDlgArtistInfo->HasUpdatedThumb()) 
418       {
419         Update(m_vecItems->GetPath());
420       }
421     }
422     break;
423   }
424   if (m_dlgProgress)
425     m_dlgProgress->Close();
426 }
427
428 bool CGUIWindowMusicBase::ShowAlbumInfo(const CFileItem *pItem, bool bShowInfo /* = true */)
429 {
430   CQueryParams params;
431   CDirectoryNode::GetDatabaseInfo(pItem->GetPath(), params);
432   CMusicAlbumInfo albumInfo;
433   while (1)
434   {
435     if (!m_musicdatabase.HasAlbumInfo(params.GetAlbumId()) || 
436         !m_musicdatabase.GetAlbumInfo(params.GetAlbumId(), albumInfo.GetAlbum(), &albumInfo.GetAlbum().songs))
437     {
438       if (!CProfilesManager::Get().GetCurrentProfile().canWriteDatabases() && !g_passwordManager.bMasterUser)
439       {
440         // TODO: should display a dialog saying no permissions
441         if (m_dlgProgress)
442           m_dlgProgress->Close();
443         return false;
444       }
445
446       if (g_application.IsMusicScanning())
447       {
448         CGUIDialogOK::ShowAndGetInput(189, 14057, 0, 0);
449         if (m_dlgProgress)
450           m_dlgProgress->Close();
451         return false;
452       }
453
454       // show dialog box indicating we're searching the album
455       if (m_dlgProgress && bShowInfo)
456       {
457         m_dlgProgress->SetHeading(185);
458         m_dlgProgress->SetLine(0, pItem->GetMusicInfoTag()->GetAlbum());
459         m_dlgProgress->SetLine(1, StringUtils::Join(pItem->GetMusicInfoTag()->GetAlbumArtist(), g_advancedSettings.m_musicItemSeparator));
460         m_dlgProgress->SetLine(2, "");
461         m_dlgProgress->StartModal();
462       }
463
464       CMusicInfoScanner scanner;
465       if (scanner.UpdateDatabaseAlbumInfo(pItem->GetPath(), albumInfo, bShowInfo) != INFO_ADDED || !albumInfo.Loaded())
466       {
467         CGUIDialogOK::ShowAndGetInput(185, 0, 500, 0);
468         if (m_dlgProgress)
469           m_dlgProgress->Close();
470         return false;
471       }
472     }
473
474     if (m_dlgProgress)
475       m_dlgProgress->Close();
476
477     CGUIDialogMusicInfo *pDlgAlbumInfo = (CGUIDialogMusicInfo*)g_windowManager.GetWindow(WINDOW_DIALOG_MUSIC_INFO);
478     if (pDlgAlbumInfo)
479     {
480       CStdString strPath;
481       m_musicdatabase.GetAlbumPath(params.GetAlbumId(), strPath);
482       pDlgAlbumInfo->SetAlbum(albumInfo.GetAlbum(), strPath);
483       pDlgAlbumInfo->DoModal();
484
485       if (pDlgAlbumInfo->NeedRefresh())
486       {
487         m_musicdatabase.DeleteAlbumInfo(params.GetAlbumId());
488         continue;
489       }
490       else if (pDlgAlbumInfo->HasUpdatedThumb())
491       {
492         UpdateThumb(albumInfo.GetAlbum(), strPath);
493       }
494     }
495     break;
496   }
497   if (m_dlgProgress)
498     m_dlgProgress->Close();
499   return true;
500 }
501
502 void CGUIWindowMusicBase::ShowSongInfo(CFileItem* pItem)
503 {
504   CGUIDialogSongInfo *dialog = (CGUIDialogSongInfo *)g_windowManager.GetWindow(WINDOW_DIALOG_SONG_INFO);
505   if (dialog)
506   {
507     if (!pItem->IsMusicDb())
508       pItem->LoadMusicTag();
509     if (!pItem->HasMusicInfoTag())
510       return;
511
512     dialog->SetSong(pItem);
513     dialog->DoModal(GetID());
514     if (dialog->NeedsUpdate())
515       Refresh(true); // update our file list
516   }
517 }
518
519 /*
520 /// \brief Can be overwritten to implement an own tag filling function.
521 /// \param items File items to fill
522 void CGUIWindowMusicBase::OnRetrieveMusicInfo(CFileItemList& items)
523 {
524 }
525 */
526
527 /// \brief Retrieve tag information for \e m_vecItems
528 void CGUIWindowMusicBase::RetrieveMusicInfo()
529 {
530   unsigned int startTick = XbmcThreads::SystemClockMillis();
531
532   OnRetrieveMusicInfo(*m_vecItems);
533
534   CLog::Log(LOGDEBUG, "RetrieveMusicInfo() took %u msec",
535             XbmcThreads::SystemClockMillis() - startTick);
536 }
537
538 /// \brief Add selected list/thumb control item to playlist and start playing
539 /// \param iItem Selected Item in list/thumb control
540 void CGUIWindowMusicBase::OnQueueItem(int iItem)
541 {
542   // don't re-queue items from playlist window
543   if ( iItem < 0 || iItem >= m_vecItems->Size() || GetID() == WINDOW_MUSIC_PLAYLIST) return ;
544
545   int iOldSize=g_playlistPlayer.GetPlaylist(PLAYLIST_MUSIC).size();
546
547   // add item 2 playlist (make a copy as we alter the queuing state)
548   CFileItemPtr item(new CFileItem(*m_vecItems->Get(iItem)));
549
550   if (item->IsRAR() || item->IsZIP())
551     return;
552
553   //  Allow queuing of unqueueable items
554   //  when we try to queue them directly
555   if (!item->CanQueue())
556     item->SetCanQueue(true);
557
558   CLog::Log(LOGDEBUG, "Adding file %s%s to music playlist", item->GetPath().c_str(), item->m_bIsFolder ? " (folder) " : "");
559   CFileItemList queuedItems;
560   AddItemToPlayList(item, queuedItems);
561
562   // select next item
563   m_viewControl.SetSelectedItem(iItem + 1);
564
565   // if party mode, add items but DONT start playing
566   if (g_partyModeManager.IsEnabled())
567   {
568     g_partyModeManager.AddUserSongs(queuedItems, false);
569     return;
570   }
571
572   g_playlistPlayer.Add(PLAYLIST_MUSIC, queuedItems);
573   if (g_playlistPlayer.GetPlaylist(PLAYLIST_MUSIC).size() && !g_application.m_pPlayer->IsPlayingAudio())
574   {
575     if (m_guiState.get())
576       m_guiState->SetPlaylistDirectory("playlistmusic://");
577
578     g_playlistPlayer.Reset();
579     g_playlistPlayer.SetCurrentPlaylist(PLAYLIST_MUSIC);
580     g_playlistPlayer.Play(iOldSize); // start playing at the first new item
581   }
582 }
583
584 /// \brief Add unique file and folders and its subfolders to playlist
585 /// \param pItem The file item to add
586 void CGUIWindowMusicBase::AddItemToPlayList(const CFileItemPtr &pItem, CFileItemList &queuedItems)
587 {
588   if (!pItem->CanQueue() || pItem->IsRAR() || pItem->IsZIP() || pItem->IsParentFolder()) // no zip/rar enques thank you!
589     return;
590
591   // fast lookup is needed here
592   queuedItems.SetFastLookup(true);
593
594   if (pItem->IsMusicDb() && pItem->m_bIsFolder && !pItem->IsParentFolder())
595   { // we have a music database folder, just grab the "all" item underneath it
596     CMusicDatabaseDirectory dir;
597     if (!dir.ContainsSongs(pItem->GetPath()))
598     { // grab the ALL item in this category
599       // Genres will still require 2 lookups, and queuing the entire Genre folder
600       // will require 3 lookups (genre, artist, album)
601       CMusicDbUrl musicUrl;
602       if (musicUrl.FromString(pItem->GetPath()))
603       {
604         musicUrl.AppendPath("-1/");
605         CFileItemPtr item(new CFileItem(musicUrl.ToString(), true));
606         item->SetCanQueue(true); // workaround for CanQueue() check above
607         AddItemToPlayList(item, queuedItems);
608       }
609       return;
610     }
611   }
612   if (pItem->m_bIsFolder || (g_windowManager.GetActiveWindow() == WINDOW_MUSIC_NAV && pItem->IsPlayList()))
613   {
614     // Check if we add a locked share
615     if ( pItem->m_bIsShareOrDrive )
616     {
617       CFileItem item = *pItem;
618       if ( !g_passwordManager.IsItemUnlocked( &item, "music" ) )
619         return ;
620     }
621
622     // recursive
623     CFileItemList items;
624     GetDirectory(pItem->GetPath(), items);
625     //OnRetrieveMusicInfo(items);
626     FormatAndSort(items);
627     for (int i = 0; i < items.Size(); ++i)
628       AddItemToPlayList(items[i], queuedItems);
629   }
630   else
631   {
632     if (pItem->IsPlayList())
633     {
634       auto_ptr<CPlayList> pPlayList (CPlayListFactory::Create(*pItem));
635       if (pPlayList.get())
636       {
637         // load it
638         if (!pPlayList->Load(pItem->GetPath()))
639         {
640           CGUIDialogOK::ShowAndGetInput(6, 0, 477, 0);
641           return; //hmmm unable to load playlist?
642         }
643
644         CPlayList playlist = *pPlayList;
645         for (int i = 0; i < (int)playlist.size(); ++i)
646         {
647           AddItemToPlayList(playlist[i], queuedItems);
648         }
649         return;
650       }
651     }
652     else if(pItem->IsInternetStream())
653     { // just queue the internet stream, it will be expanded on play
654       queuedItems.Add(pItem);
655     }
656     else if (pItem->IsPlugin() && pItem->GetProperty("isplayable") == "true")
657     {
658       // python files can be played
659       queuedItems.Add(pItem);
660     }
661     else if (!pItem->IsNFO() && pItem->IsAudio())
662     {
663       CFileItemPtr itemCheck = queuedItems.Get(pItem->GetPath());
664       if (!itemCheck || itemCheck->m_lStartOffset != pItem->m_lStartOffset)
665       { // add item
666         CFileItemPtr item(new CFileItem(*pItem));
667         m_musicdatabase.SetPropertiesForFileItem(*item);
668         queuedItems.Add(item);
669       }
670     }
671   }
672 }
673
674 void CGUIWindowMusicBase::UpdateButtons()
675 {
676   // Update window selection control
677
678   // Remove labels from the window selection
679   CGUIMessage msg(GUI_MSG_LABEL_RESET, GetID(), CONTROL_BTNTYPE);
680   g_windowManager.SendMessage(msg);
681
682   // Add labels to the window selection
683   CGUIMessage msg2(GUI_MSG_LABEL_ADD, GetID(), CONTROL_BTNTYPE);
684   msg2.SetLabel(g_localizeStrings.Get(744)); // Files
685   g_windowManager.SendMessage(msg2);
686
687   msg2.SetLabel(g_localizeStrings.Get(15100)); // Library
688   g_windowManager.SendMessage(msg2);
689
690   // Select the current window as default item
691   CONTROL_SELECT_ITEM(CONTROL_BTNTYPE, CSettings::Get().GetInt("mymusic.startwindow") - WINDOW_MUSIC_FILES);
692
693   CGUIMediaWindow::UpdateButtons();
694 }
695
696 void CGUIWindowMusicBase::GetContextButtons(int itemNumber, CContextButtons &buttons)
697 {
698   CFileItemPtr item;
699   if (itemNumber >= 0 && itemNumber < m_vecItems->Size())
700     item = m_vecItems->Get(itemNumber);
701
702   if (item && !item->GetProperty("pluginreplacecontextitems").asBoolean())
703   {
704     if (item && !item->IsParentFolder())
705     {
706       if (!m_vecItems->IsPlugin() && (item->IsPlugin() || item->IsScript()))
707         buttons.Add(CONTEXT_BUTTON_INFO,24003); // Add-on info
708       if (item->CanQueue() && !item->IsAddonsPath() && !item->IsScript())
709       {
710         buttons.Add(CONTEXT_BUTTON_QUEUE_ITEM, 13347); //queue
711
712         // allow a folder to be ad-hoc queued and played by the default player
713         if (item->m_bIsFolder || (item->IsPlayList() &&
714            !g_advancedSettings.m_playlistAsFolders))
715         {
716           buttons.Add(CONTEXT_BUTTON_PLAY_ITEM, 208); // Play
717         }
718         else
719         { // check what players we have, if we have multiple display play with option
720           VECPLAYERCORES vecCores;
721           CPlayerCoreFactory::Get().GetPlayers(*item, vecCores);
722           if (vecCores.size() >= 1)
723             buttons.Add(CONTEXT_BUTTON_PLAY_WITH, 15213); // Play With...
724         }
725         if (item->IsSmartPlayList())
726         {
727             buttons.Add(CONTEXT_BUTTON_PLAY_PARTYMODE, 15216); // Play in Partymode
728         }
729
730         if (item->IsSmartPlayList() || m_vecItems->IsSmartPlayList())
731           buttons.Add(CONTEXT_BUTTON_EDIT_SMART_PLAYLIST, 586);
732         else if (item->IsPlayList() || m_vecItems->IsPlayList())
733           buttons.Add(CONTEXT_BUTTON_EDIT, 586);
734       }
735     }
736   }
737   CGUIMediaWindow::GetContextButtons(itemNumber, buttons);
738 }
739
740 void CGUIWindowMusicBase::GetNonContextButtons(CContextButtons &buttons)
741 {
742   if (!m_vecItems->IsVirtualDirectoryRoot())
743     buttons.Add(CONTEXT_BUTTON_GOTO_ROOT, 20128);
744   buttons.Add(CONTEXT_BUTTON_SETTINGS, 5);
745 }
746
747 bool CGUIWindowMusicBase::OnContextButton(int itemNumber, CONTEXT_BUTTON button)
748 {
749   CFileItemPtr item;
750   if (itemNumber >= 0 && itemNumber < m_vecItems->Size())
751     item = m_vecItems->Get(itemNumber);
752
753   switch (button)
754   {
755   case CONTEXT_BUTTON_QUEUE_ITEM:
756     OnQueueItem(itemNumber);
757     return true;
758
759   case CONTEXT_BUTTON_INFO:
760     OnInfo(itemNumber);
761     return true;
762
763   case CONTEXT_BUTTON_SONG_INFO:
764     {
765       ShowSongInfo(item.get());
766       return true;
767     }
768
769   case CONTEXT_BUTTON_EDIT:
770     {
771       CStdString playlist = item->IsPlayList() ? item->GetPath() : m_vecItems->GetPath(); // save path as activatewindow will destroy our items
772       g_windowManager.ActivateWindow(WINDOW_MUSIC_PLAYLIST_EDITOR, playlist);
773       // need to update
774       m_vecItems->RemoveDiscCache(GetID());
775       return true;
776     }
777
778   case CONTEXT_BUTTON_EDIT_SMART_PLAYLIST:
779     {
780       CStdString playlist = item->IsSmartPlayList() ? item->GetPath() : m_vecItems->GetPath(); // save path as activatewindow will destroy our items
781       if (CGUIDialogSmartPlaylistEditor::EditPlaylist(playlist, "music"))
782         Refresh(true); // need to update
783       return true;
784     }
785
786   case CONTEXT_BUTTON_PLAY_ITEM:
787     PlayItem(itemNumber);
788     return true;
789
790   case CONTEXT_BUTTON_PLAY_WITH:
791     {
792       VECPLAYERCORES vecCores;  // base class?
793       CPlayerCoreFactory::Get().GetPlayers(*item, vecCores);
794       g_application.m_eForcedNextPlayer = CPlayerCoreFactory::Get().SelectPlayerDialog(vecCores);
795       if( g_application.m_eForcedNextPlayer != EPC_NONE )
796         OnClick(itemNumber);
797       return true;
798     }
799
800   case CONTEXT_BUTTON_PLAY_PARTYMODE:
801     g_partyModeManager.Enable(PARTYMODECONTEXT_MUSIC, item->GetPath());
802     return true;
803
804   case CONTEXT_BUTTON_STOP_SCANNING:
805     {
806       g_application.StopMusicScan();
807       return true;
808     }
809
810   case CONTEXT_BUTTON_GOTO_ROOT:
811     Update("");
812     return true;
813
814   case CONTEXT_BUTTON_SETTINGS:
815     g_windowManager.ActivateWindow(WINDOW_SETTINGS_MYMUSIC);
816     return true;
817   default:
818     break;
819   }
820
821   return CGUIMediaWindow::OnContextButton(itemNumber, button);
822 }
823
824 void CGUIWindowMusicBase::OnRipCD()
825 {
826   if(g_mediaManager.IsAudio())
827   {
828     if (!g_application.CurrentFileItem().IsCDDA())
829     {
830 #ifdef HAS_CDDA_RIPPER
831       CCDDARipper::GetInstance().RipCD();
832 #endif
833     }
834     else
835       CGUIDialogOK::ShowAndGetInput(257, 20099, 0, 0);
836   }
837 }
838
839 void CGUIWindowMusicBase::OnRipTrack(int iItem)
840 {
841   if(g_mediaManager.IsAudio())
842   {
843     if (!g_application.CurrentFileItem().IsCDDA())
844     {
845 #ifdef HAS_CDDA_RIPPER
846       CFileItemPtr item = m_vecItems->Get(iItem);
847       CCDDARipper::GetInstance().RipTrack(item.get());
848 #endif
849     }
850     else
851       CGUIDialogOK::ShowAndGetInput(257, 20099, 0, 0);
852   }
853 }
854
855 void CGUIWindowMusicBase::PlayItem(int iItem)
856 {
857   // restrictions should be placed in the appropiate window code
858   // only call the base code if the item passes since this clears
859   // the current playlist
860
861   const CFileItemPtr pItem = m_vecItems->Get(iItem);
862
863   // special case for DAAP playlist folders
864   bool bIsDAAPplaylist = false;
865 #ifdef HAS_FILESYSTEM_DAAP
866   if (pItem->IsDAAP() && pItem->m_bIsFolder)
867   {
868     CDAAPDirectory dirDAAP;
869     if (dirDAAP.GetCurrLevel(pItem->GetPath()) == 0)
870       bIsDAAPplaylist = true;
871   }
872 #endif
873   // if its a folder, build a playlist
874   if ((pItem->m_bIsFolder && !pItem->IsPlugin()) || (g_windowManager.GetActiveWindow() == WINDOW_MUSIC_NAV && pItem->IsPlayList()))
875   {
876     // make a copy so that we can alter the queue state
877     CFileItemPtr item(new CFileItem(*m_vecItems->Get(iItem)));
878
879     //  Allow queuing of unqueueable items
880     //  when we try to queue them directly
881     if (!item->CanQueue())
882       item->SetCanQueue(true);
883
884     // skip ".."
885     if (item->IsParentFolder())
886       return;
887
888     CFileItemList queuedItems;
889     AddItemToPlayList(item, queuedItems);
890     if (g_partyModeManager.IsEnabled())
891     {
892       g_partyModeManager.AddUserSongs(queuedItems, true);
893       return;
894     }
895
896     /*
897     CStdString strPlayListDirectory = m_vecItems->GetPath();
898     URIUtils::RemoveSlashAtEnd(strPlayListDirectory);
899     */
900
901     g_playlistPlayer.ClearPlaylist(PLAYLIST_MUSIC);
902     g_playlistPlayer.Reset();
903     g_playlistPlayer.Add(PLAYLIST_MUSIC, queuedItems);
904     g_playlistPlayer.SetCurrentPlaylist(PLAYLIST_MUSIC);
905
906     // activate the playlist window if its not activated yet
907     if (bIsDAAPplaylist && GetID() == g_windowManager.GetActiveWindow())
908       g_windowManager.ActivateWindow(WINDOW_MUSIC_PLAYLIST);
909
910     // play!
911     g_playlistPlayer.Play();
912   }
913   else if (pItem->IsPlayList())
914   {
915     // load the playlist the old way
916     LoadPlayList(pItem->GetPath());
917   }
918   else
919   {
920     // just a single item, play it
921     // TODO: Add music-specific code for single playback of an item here (See OnClick in MediaWindow, and OnPlayMedia below)
922     OnClick(iItem);
923   }
924 }
925
926 void CGUIWindowMusicBase::LoadPlayList(const CStdString& strPlayList)
927 {
928   // if partymode is active, we disable it
929   if (g_partyModeManager.IsEnabled())
930     g_partyModeManager.Disable();
931
932   // load a playlist like .m3u, .pls
933   // first get correct factory to load playlist
934   auto_ptr<CPlayList> pPlayList (CPlayListFactory::Create(strPlayList));
935   if (pPlayList.get())
936   {
937     // load it
938     if (!pPlayList->Load(strPlayList))
939     {
940       CGUIDialogOK::ShowAndGetInput(6, 0, 477, 0);
941       return; //hmmm unable to load playlist?
942     }
943   }
944
945   int iSize = pPlayList->size();
946   if (g_application.ProcessAndStartPlaylist(strPlayList, *pPlayList, PLAYLIST_MUSIC))
947   {
948     if (m_guiState.get())
949       m_guiState->SetPlaylistDirectory("playlistmusic://");
950     // activate the playlist window if its not activated yet
951     if (GetID() == g_windowManager.GetActiveWindow() && iSize > 1)
952     {
953       g_windowManager.ActivateWindow(WINDOW_MUSIC_PLAYLIST);
954     }
955   }
956 }
957
958 bool CGUIWindowMusicBase::OnPlayMedia(int iItem)
959 {
960   CFileItemPtr pItem = m_vecItems->Get(iItem);
961
962   // party mode
963   if (g_partyModeManager.IsEnabled())
964   {
965     CPlayList playlistTemp;
966     playlistTemp.Add(pItem);
967     g_partyModeManager.AddUserSongs(playlistTemp, true);
968     return true;
969   }
970   else if (!pItem->IsPlayList() && !pItem->IsInternetStream())
971   { // single music file - if we get here then we have autoplaynextitem turned off or queuebydefault
972     // turned on, but we still want to use the playlist player in order to handle more queued items
973     // following etc.
974     // Karaoke items also can be added in runtime (while the song is played), so it should be queued too.
975     if ( (CSettings::Get().GetBool("musicplayer.queuebydefault") && g_windowManager.GetActiveWindow() != WINDOW_MUSIC_PLAYLIST_EDITOR)
976        || pItem->IsKaraoke() )
977     {
978       // TODO: Should the playlist be cleared if nothing is already playing?
979       OnQueueItem(iItem);
980       return true;
981     }
982     g_playlistPlayer.Reset();
983     g_playlistPlayer.ClearPlaylist(PLAYLIST_MUSIC);
984     g_playlistPlayer.Add(PLAYLIST_MUSIC, pItem);
985     g_playlistPlayer.SetCurrentPlaylist(PLAYLIST_MUSIC);
986     g_playlistPlayer.Play();
987     return true;
988   }
989   return CGUIMediaWindow::OnPlayMedia(iItem);
990 }
991
992 void CGUIWindowMusicBase::UpdateThumb(const CAlbum &album, const CStdString &path)
993 {
994   // check user permissions
995   bool saveDb = album.idAlbum != -1;
996   bool saveDirThumb = true;
997   if (!CProfilesManager::Get().GetCurrentProfile().canWriteDatabases() && !g_passwordManager.bMasterUser)
998   {
999     saveDb = false;
1000     saveDirThumb = false;
1001   }
1002
1003   CStdString albumThumb = m_musicdatabase.GetArtForItem(album.idAlbum, "album", "thumb");
1004
1005   // Update the thumb in the music database (songs + albums)
1006   CStdString albumPath(path);
1007   if (saveDb && CFile::Exists(albumThumb))
1008     m_musicdatabase.SaveAlbumThumb(album.idAlbum, albumThumb);
1009
1010   // Update currently playing song if it's from the same album.  This is necessary as when the album
1011   // first gets it's cover, the info manager's item doesn't have the updated information (so will be
1012   // sending a blank thumb to the skin.)
1013   if (g_application.m_pPlayer->IsPlayingAudio())
1014   {
1015     const CMusicInfoTag* tag=g_infoManager.GetCurrentSongTag();
1016     if (tag)
1017     {
1018       // really, this may not be enough as it is to reliably update this item.  eg think of various artists albums
1019       // that aren't tagged as such (and aren't yet scanned).  But we probably can't do anything better than this
1020       // in that case
1021       if (album.strAlbum == tag->GetAlbum() && (album.artist == tag->GetAlbumArtist() ||
1022                                                 album.artist == tag->GetArtist()))
1023       {
1024         g_infoManager.SetCurrentAlbumThumb(albumThumb);
1025       }
1026     }
1027   }
1028
1029   // Save this thumb as the directory thumb if it's the only album in the folder (files view nicety)
1030   // We do this by grabbing all the songs in the folder, and checking to see whether they come
1031   // from the same album.
1032   if (saveDirThumb && CFile::Exists(albumThumb) && !albumPath.empty() && !URIUtils::IsCDDA(albumPath))
1033   {
1034     CFileItemList items;
1035     GetDirectory(albumPath, items);
1036     OnRetrieveMusicInfo(items);
1037     VECALBUMS albums;
1038     CMusicInfoScanner::FileItemsToAlbums(items, albums);
1039     if (albums.size() == 1)
1040     { // set as folder thumb as well
1041       CMusicThumbLoader loader;
1042       loader.SetCachedImage(items, "thumb", albumPath);
1043     }
1044   }
1045
1046   // update the file listing - we have to update the whole lot, as it's likely that
1047   // more than just our thumbnaias changed
1048   // TODO: Ideally this would only be done when needed - at the moment we appear to be
1049   //       doing this for every lookup, possibly twice (see ShowAlbumInfo)
1050   Refresh(true);
1051
1052   //  Do we have to autoswitch to the thumb control?
1053   m_guiState.reset(CGUIViewState::GetViewState(GetID(), *m_vecItems));
1054   UpdateButtons();
1055 }
1056
1057 void CGUIWindowMusicBase::OnRetrieveMusicInfo(CFileItemList& items)
1058 {
1059   if (items.GetFolderCount()==items.Size() || items.IsMusicDb() ||
1060      (!CSettings::Get().GetBool("musicfiles.usetags") && !items.IsCDDA()))
1061   {
1062     return;
1063   }
1064   // Start the music info loader thread
1065   m_musicInfoLoader.SetProgressCallback(m_dlgProgress);
1066   m_musicInfoLoader.Load(items);
1067
1068   bool bShowProgress=!g_windowManager.HasModalDialog();
1069   bool bProgressVisible=false;
1070
1071   unsigned int tick=XbmcThreads::SystemClockMillis();
1072
1073   while (m_musicInfoLoader.IsLoading())
1074   {
1075     if (bShowProgress)
1076     { // Do we have to init a progress dialog?
1077       unsigned int elapsed=XbmcThreads::SystemClockMillis()-tick;
1078
1079       if (!bProgressVisible && elapsed>1500 && m_dlgProgress)
1080       { // tag loading takes more then 1.5 secs, show a progress dialog
1081         CURL url(items.GetPath());
1082         CStdString strStrippedPath = url.GetWithoutUserDetails();
1083         m_dlgProgress->SetHeading(189);
1084         m_dlgProgress->SetLine(0, 505);
1085         m_dlgProgress->SetLine(1, "");
1086         m_dlgProgress->SetLine(2, strStrippedPath );
1087         m_dlgProgress->StartModal();
1088         m_dlgProgress->ShowProgressBar(true);
1089         bProgressVisible = true;
1090       }
1091
1092       if (bProgressVisible && m_dlgProgress && !m_dlgProgress->IsCanceled())
1093       { // keep GUI alive
1094         m_dlgProgress->Progress();
1095       }
1096     } // if (bShowProgress)
1097     Sleep(1);
1098   } // while (m_musicInfoLoader.IsLoading())
1099
1100   if (bProgressVisible && m_dlgProgress)
1101     m_dlgProgress->Close();
1102 }
1103
1104 bool CGUIWindowMusicBase::GetDirectory(const CStdString &strDirectory, CFileItemList &items)
1105 {
1106   items.SetArt("thumb", "");
1107   bool bResult = CGUIMediaWindow::GetDirectory(strDirectory, items);
1108   if (bResult)
1109   {
1110     CMusicThumbLoader loader;
1111     loader.FillThumb(items);
1112   }
1113
1114   // add in the "New Playlist" item if we're in the playlists folder
1115   if ((items.GetPath() == "special://musicplaylists/") && !items.Contains("newplaylist://"))
1116   {
1117     CFileItemPtr newPlaylist(new CFileItem(CProfilesManager::Get().GetUserDataItem("PartyMode.xsp"),false));
1118     newPlaylist->SetLabel(g_localizeStrings.Get(16035));
1119     newPlaylist->SetLabelPreformated(true);
1120     newPlaylist->m_bIsFolder = true;
1121     items.Add(newPlaylist);
1122
1123     newPlaylist.reset(new CFileItem("newplaylist://", false));
1124     newPlaylist->SetLabel(g_localizeStrings.Get(525));
1125     newPlaylist->SetLabelPreformated(true);
1126     newPlaylist->SetSpecialSort(SortSpecialOnBottom);
1127     newPlaylist->SetCanQueue(false);
1128     items.Add(newPlaylist);
1129
1130     newPlaylist.reset(new CFileItem("newsmartplaylist://music", false));
1131     newPlaylist->SetLabel(g_localizeStrings.Get(21437));
1132     newPlaylist->SetLabelPreformated(true);
1133     newPlaylist->SetSpecialSort(SortSpecialOnBottom);
1134     newPlaylist->SetCanQueue(false);
1135     items.Add(newPlaylist);
1136   }
1137
1138   return bResult;
1139 }
1140
1141 bool CGUIWindowMusicBase::CheckFilterAdvanced(CFileItemList &items) const
1142 {
1143   CStdString content = items.GetContent();
1144   if ((items.IsMusicDb() || CanContainFilter(m_strFilterPath)) &&
1145       (content.Equals("artists") || content.Equals("albums") || content.Equals("songs")))
1146     return true;
1147
1148   return false;
1149 }
1150
1151 bool CGUIWindowMusicBase::CanContainFilter(const CStdString &strDirectory) const
1152 {
1153   return StringUtils::StartsWithNoCase(strDirectory, "musicdb://");
1154 }
1155
1156 void CGUIWindowMusicBase::OnInitWindow()
1157 {
1158   CGUIMediaWindow::OnInitWindow();
1159   if (CMediaSettings::Get().GetMusicNeedsUpdate() == 35 && !g_application.IsMusicScanning() &&
1160       g_infoManager.GetLibraryBool(LIBRARY_HAS_MUSIC))
1161   {
1162     // rescan of music library required
1163     if (CGUIDialogYesNo::ShowAndGetInput(799, 800, 801, -1))
1164     {
1165       int flags = CMusicInfoScanner::SCAN_RESCAN;
1166       if (CSettings::Get().GetBool("musiclibrary.downloadinfo"))
1167         flags |= CMusicInfoScanner::SCAN_ONLINE;
1168       if (CSettings::Get().GetBool("musiclibrary.backgroundupdate"))
1169         flags |= CMusicInfoScanner::SCAN_BACKGROUND;
1170       g_application.StartMusicScan("", flags);
1171       CMediaSettings::Get().SetMusicNeedsUpdate(0); // once is enough (user may interrupt, but that's up to them)
1172       CSettings::Get().Save();
1173     }
1174   }
1175 }
1176
1177 CStdString CGUIWindowMusicBase::GetStartFolder(const CStdString &dir)
1178 {
1179   if (dir.Equals("Plugins") || dir.Equals("Addons"))
1180     return "addons://sources/audio/";
1181   else if (dir.Equals("$PLAYLISTS") || dir.Equals("Playlists"))
1182     return "special://musicplaylists/";
1183   return CGUIMediaWindow::GetStartFolder(dir);
1184 }