1 /***************************************************************************
2 * copyright : (c) 2004 Pierpaolo Di Panfilo *
3 * (c) 2004 Mark Kretschmann <markey@web.de> *
4 * (c) 2005-2007 Seb Ruiz <me@sebruiz.net> *
5 * (c) 2005 Gábor Lehel <illissius@gmail.com> *
6 * (c) 2005 Christian Muehlhaeuser <chris@chris.de> *
7 * (c) 2006 Alexandre Oliveira <aleprj@gmail.com> *
8 * (c) 2006 Adam Pigg <adam@piggz.co.uk> *
9 * See COPYING file for licensing information *
10 ***************************************************************************/
12 #define DEBUG_PREFIX "PlaylistBrowser"
14 #include "playlistbrowser.h"
16 #include "amarok.h" //actionCollection()
17 #include "browserToolBar.h"
19 #include "dynamicmode.h"
20 #include "k3bexporter.h"
22 #include "mediabrowser.h"
23 #include "playlistbrowseritem.h"
25 #include "playlist/PlaylistModel.h"
26 #include "playlistselection.h"
27 #include "podcastbundle.h"
28 #include "PodcastCollection.h"
29 #include "podcastsettings.h"
30 #include "querybuilder.h" //smart playlists
31 #include "scancontroller.h"
32 #include "smartplaylisteditor.h"
33 #include "statusbar.h"
34 #include "tagdialog.h" //showContextMenu()
35 #include "TheInstances.h"
36 #include "threadmanager.h"
37 #include "xspfplaylist.h"
38 #include "playlistmanager/PlaylistManager.h"
40 #include <k3multipledrag.h> //dragObject()
41 #include <k3urldrag.h> //dragObject()
43 #include <KActionCollection>
44 #include <KActionMenu>
45 #include <KApplication>
46 #include <KFileDialog> //openPlaylist()
47 #include <KIconLoader> //smallIcon
48 #include <KInputDialog>
49 #include <KIO/DeleteJob> //deleteSelectedPlaylists()
50 #include <KLineEdit> //rename()
53 #include <KMessageBox> //renamePlaylist(), deleteSelectedPlaylist()
55 #include <KPushButton>
56 #include <KStandardDirs> //KGlobal::dirs()
59 #include <q3header.h> //mousePressed()
61 #include <q3textstream.h> //loadPlaylists(), saveM3U(), savePLS()
62 #include <QDragEnterEvent>
63 #include <QDragLeaveEvent>
64 #include <QDragMoveEvent>
66 #include <QEvent> //customEvent()
69 #include <QLinkedList>
70 #include <QPainter> //paintCell()
71 #include <QPaintEvent>
72 #include <QPixmap> //paintCell()
73 #include <QResizeEvent>
76 #include <cstdio> //rename() in renamePlaylist()
82 findItemByPath( Q3ListView
*view
, QString name
)
84 const static QString
escaped( "\\/" );
85 const static QChar
sep( '/' );
87 debug() << "Searching " << name
;
88 QStringList path
= splitPath( name
);
90 Q3ListViewItem
*prox
= view
->firstChild();
91 Q3ListViewItem
*item
= 0;
93 foreach( QString text
, path
) {
95 text
.replace( escaped
, sep
);
97 for ( ; item
; item
= item
->nextSibling() ) {
98 if ( text
== item
->text(0) ) {
105 prox
= item
->firstChild();
111 splitPath( QString path
) {
114 const static QChar
sep( '/' );
115 int bOffset
= 0, sOffset
= 0;
117 int pos
= path
.indexOf( sep
, bOffset
);
119 while ( pos
!= -1 ) {
120 if ( pos
> sOffset
&& pos
<= (int)path
.length() ) {
121 if ( pos
> 0 && path
[pos
-1] != '\\' ) {
122 list
<< path
.mid( sOffset
, pos
- sOffset
);
127 pos
= path
.indexOf( sep
, bOffset
);
130 int length
= path
.length() - 1;
131 if ( path
.mid( sOffset
, length
- sOffset
+ 1 ).length() > 0 )
132 list
<< path
.mid( sOffset
, length
- sOffset
+ 1 );
140 fileExtension( const QString
&fileName
)
142 return Amarok::extension( fileName
);
145 PlaylistBrowser
*PlaylistBrowser::s_instance
= 0;
148 PlaylistBrowser::PlaylistBrowser( const char *name
)
150 , m_polished( false )
151 , m_playlistCategory( 0 )
152 , m_streamsCategory( 0 )
153 , m_smartCategory( 0 )
154 , m_dynamicCategory( 0 )
155 , m_podcastCategory( 0 )
157 , m_smartDefaults( 0 )
158 , m_lastfmCategory( 0 )
159 , m_shoutcastCategory( 0 )
160 , m_lastPlaylist( 0 )
161 , m_coolStreamsOpen( false )
162 , m_smartDefaultsOpen( false )
163 , m_lastfmOpen( false )
164 , m_ac( new KActionCollection( this ) )
165 , m_podcastTimer( new QTimer( this ) )
171 KVBox
*browserBox
= new KVBox( this );
172 // browserBox->setSpacing( 3 );
175 addMenuButton
= new KActionMenu( KIcon( Amarok::icon( "add_playlist" ) ), i18n("Add"), m_ac
);
176 addMenuButton
->setDelayed( false );
178 KMenu
*playlistMenu
= new KMenu( browserBox
);
179 playlistMenu
->insertItem( i18n("New..."), PLAYLIST
);
180 playlistMenu
->insertItem( i18n("Import Existing..."), PLAYLIST_IMPORT
);
181 connect( playlistMenu
, SIGNAL( activated(int) ), SLOT( slotAddPlaylistMenu(int) ) );
183 KMenu
*addMenu
= addMenuButton
->popupMenu();
184 addMenu
->insertItem( i18n("Playlist"), playlistMenu
);
185 addMenu
->insertItem( i18n("Smart Playlist..."), SMARTPLAYLIST
);
186 addMenu
->insertItem( i18n("Dynamic Playlist..."), ADDDYNAMIC
);
187 addMenu
->insertItem( i18n("Radio Stream..."), STREAM
);
188 addMenu
->insertItem( i18n("Podcast..."), PODCAST
);
189 connect( addMenu
, SIGNAL( activated(int) ), SLOT( slotAddMenu(int) ) );
191 renameButton
= new KAction( KIcon( "edit-clear" ), i18n("Rename"), m_ac
);
192 connect( renameButton
, SIGNAL( triggered( bool ) ), this, SLOT( renameSelectedItem() ) );
193 removeButton
= new KAction( KIcon( Amarok::icon( "remove" ) ), i18n("Delete"), m_ac
);
194 connect( removeButton
, SIGNAL( triggered( bool ) ), this, SLOT( removeSelectedItems() ) );
196 m_toolbar
= new Browser::ToolBar( browserBox
);
197 m_toolbar
->setToolButtonStyle( Qt::ToolButtonIconOnly
);
198 m_toolbar
->setSizePolicy( QSizePolicy::Preferred
, QSizePolicy::Preferred
);
199 m_toolbar
->addAction( addMenuButton
);
201 m_toolbar
->setToolButtonStyle( Qt::ToolButtonIconOnly
); //default appearance
202 m_toolbar
->addSeparator();
203 m_toolbar
->addAction( renameButton
);
204 m_toolbar
->addAction( removeButton
);
206 renameButton
->setEnabled( false );
207 removeButton
->setEnabled( false );
210 m_splitter
= new QSplitter( Qt::Vertical
, browserBox
);
211 m_splitter
->setChildrenCollapsible( false ); // hiding the InfoPane entirely can only be confusing
213 m_listview
= new PlaylistBrowserView( m_splitter
);
215 int sort
= Amarok::config( "PlaylistBrowser" ).readEntry( "Sorting", int(Qt::Ascending
) );
216 m_listview
->setSorting( 0, sort
== Qt::Ascending
? true : false );
218 m_podcastTimerInterval
= Amarok::config( "PlaylistBrowser" ).readEntry( "Podcast Interval", 14400000 );
219 connect( m_podcastTimer
, SIGNAL(timeout()), this, SLOT(scanPodcasts()) );
221 // signals and slots connections
222 connect( m_listview
, SIGNAL( contextMenuRequested( Q3ListViewItem
*, const QPoint
&, int ) ),
223 this, SLOT( showContextMenu( Q3ListViewItem
*, const QPoint
&, int ) ) );
224 connect( m_listview
, SIGNAL( doubleClicked( Q3ListViewItem
*, const QPoint
&, int ) ),
225 this, SLOT( invokeItem( Q3ListViewItem
*, const QPoint
&, int ) ) );
226 connect( m_listview
, SIGNAL( itemRenamed( Q3ListViewItem
*, const QString
&, int ) ),
227 this, SLOT( renamePlaylist( Q3ListViewItem
*, const QString
&, int ) ) );
228 connect( m_listview
, SIGNAL( currentChanged( Q3ListViewItem
* ) ),
229 this, SLOT( currentItemChanged( Q3ListViewItem
* ) ) );
230 connect( CollectionDB::instance(), SIGNAL( scanDone( bool ) ), SLOT( collectionScanDone() ) );
232 // setMinimumWidth( m_toolbar->sizeHint().width() );
234 m_infoPane
= new InfoPane( m_splitter
);
236 m_podcastCategory
= loadPodcasts();
241 setFocusProxy( m_listview
);
246 PlaylistBrowser::polish()
248 // we make startup faster by doing the slow bits for this
249 // only when we are shown on screen
253 Amarok::OverrideCursor cursor
;
255 // blockSignals( true );
256 // BrowserBar::instance()->restoreWidth();
257 // blockSignals( false );
261 /// Podcasting is always initialised in the ctor because of autoscanning
265 m_playlistCategory
= loadPlaylists();
266 if( !CollectionDB::instance()->isEmpty() )
268 m_smartCategory
= loadSmartPlaylists();
269 loadDefaultSmartPlaylists();
271 #define config Amarok::config( "PlaylistBrowser" )
273 m_dynamicCategory
= loadDynamics();
274 m_randomDynamic
= new DynamicEntry( m_dynamicCategory
, 0, i18n("Random Mix") );
275 m_randomDynamic
->setKept( false ); //don't save it
276 m_randomDynamic
->setCycleTracks( config
.readEntry( "Dynamic Random Remove Played", true ) );
277 m_randomDynamic
->setUpcomingCount( config
.readEntry ( "Dynamic Random Upcoming Count", 15 ) );
278 m_randomDynamic
->setPreviousCount( config
.readEntry ( "Dynamic Random Previous Count", 5 ) );
280 m_suggestedDynamic
= new DynamicEntry( m_dynamicCategory
, m_randomDynamic
, i18n("Suggested Songs" ) );
281 m_suggestedDynamic
->setKept( false ); //don't save it
282 m_suggestedDynamic
->setAppendType( DynamicMode::SUGGESTION
);
283 m_suggestedDynamic
->setCycleTracks( config
.readEntry( "Dynamic Suggest Remove Played", true ) );
284 m_suggestedDynamic
->setUpcomingCount( config
.readEntry ( "Dynamic Suggest Upcoming Count", 15 ) );
285 m_suggestedDynamic
->setPreviousCount( config
.readEntry ( "Dynamic Suggest Previous Count", 5 ) );
289 m_streamsCategory
= loadStreams();
291 m_shoutcastCategory
= new ShoutcastBrowser( m_streamsCategory
);
293 if( !AmarokConfig::scrobblerUsername().isEmpty() )
295 const bool subscriber
= Amarok::config( "Scrobbler" ).readEntry( "Subscriber", false );
296 loadLastfmStreams( subscriber
);
299 markDynamicEntries();
301 // ListView item state restoration:
302 // First we check if the number of items in the listview is the same as it was on last
303 // application exit. If true, we iterate over all items and restore their open/closed state.
304 // Note: We ignore podcast items, because they are added dynamically added to the ListView.
305 QList
<int> stateList
= Amarok::config( "PlaylistBrowser" ).readEntry( "Item State", QList
<int>() );
306 Q3ListViewItemIterator
it( m_listview
);
308 while ( it
.current() ) {
309 if( !isPodcastEpisode( it
.current() ) )
314 if ( count
== stateList
.count() ) {
316 it
= Q3ListViewItemIterator( m_listview
);
317 while ( it
.current() ) {
318 if( !isPodcastEpisode( it
.current() ) ) {
319 it
.current()->setOpen( stateList
[index
] );
326 // Set height of InfoPane
327 m_infoPane
->setStoredHeight( Amarok::config( "PlaylistBrowser" ).readEntry( "InfoPane Height", 200 ) );
331 PlaylistBrowser::~PlaylistBrowser()
339 savePodcastFolderStates( m_podcastCategory
);
342 for( uint i
=0; i
< m_dynamicEntries
.count(); i
++ )
344 Q3ListViewItem
*item
= m_dynamicEntries
.at( i
);
345 list
.append( item
->text(0) );
347 int sortorder
= m_listview
->sortOrder() == Qt::AscendingOrder
? 0 : 1;
348 Amarok::config( "PlaylistBrowser" ).writeEntry( "Sorting", sortorder
);
349 Amarok::config( "PlaylistBrowser" ).writeEntry( "Podcast Interval", m_podcastTimerInterval
);
350 Amarok::config( "PlaylistBrowser" ).writeEntry( "Podcast Folder Open", m_podcastCategory
->isOpen() );
351 Amarok::config( "PlaylistBrowser" ).writeEntry( "InfoPane Height", m_infoPane
->getHeight() );
357 PlaylistBrowser::setInfo( const QString
&title
, const QString
&info
)
359 m_infoPane
->setInfo( title
, info
);
363 PlaylistBrowser::resizeEvent( QResizeEvent
* )
365 if( m_infoPane
->findChild
<QWidget
*>( "container" )->isShown() )
366 m_infoPane
->setMaximumHeight( ( int )( m_splitter
->height() / 1.5 ) );
371 PlaylistBrowser::markDynamicEntries()
373 if( Amarok::dynamicMode() )
375 QStringList playlists
= Amarok::dynamicMode()->items();
377 for( uint i
=0; i
< playlists
.count(); i
++ )
379 PlaylistBrowserEntry
*item
= dynamic_cast<PlaylistBrowserEntry
*>( Amarok::findItemByPath( m_listview
, playlists
[i
] ) );
383 m_dynamicEntries
.append( item
);
384 if( item
->rtti() == PlaylistEntry::RTTI
)
385 static_cast<PlaylistEntry
*>( item
)->setDynamic( true );
386 if( item
->rtti() == SmartPlaylist::RTTI
)
387 static_cast<SmartPlaylist
*>( item
)->setDynamic( true );
394 *************************************************************************
396 *************************************************************************
399 QString
PlaylistBrowser::streamBrowserCache() const
401 return Amarok::saveLocation() + "streambrowser_save.xml";
405 PlaylistCategory
* PlaylistBrowser::loadStreams()
407 QFile
file( streamBrowserCache() );
409 QTextStream
stream( &file
);
410 stream
.setCodec( "UTF8" );
415 Q3ListViewItem
*after
= m_dynamicCategory
;
417 if( !file
.open( QIODevice::ReadOnly
) || !d
.setContent( stream
.read() ) )
418 { /*Couldn't open the file or it had invalid content, so let's create an empty element*/
419 return new PlaylistCategory( m_listview
, after
, i18n("Radio Streams") );
422 e
= d
.namedItem( "category" ).toElement();
423 if ( e
.attribute("formatversion") =="1.1" ) {
424 PlaylistCategory
* p
= new PlaylistCategory( m_listview
, after
, e
);
425 p
->setText(0, i18n("Radio Streams") );
428 else { // Old unversioned format
429 PlaylistCategory
* p
= new PlaylistCategory( m_listview
, after
, i18n("Radio Streams") );
430 Q3ListViewItem
*last
= 0;
431 QDomNode n
= d
.namedItem( "streambrowser" ).namedItem("stream");
432 for( ; !n
.isNull(); n
= n
.nextSibling() ) {
433 last
= new StreamEntry( p
, last
, n
.toElement() );
440 void PlaylistBrowser::loadCoolStreams()
442 QFile
file( KStandardDirs::locate( "data","amarok/data/Cool-Streams.xml" ) );
443 if( !file
.open( QIODevice::ReadOnly
) )
446 QTextStream
stream( &file
);
447 stream
.setCodec( "UTF8" );
451 if( !d
.setContent( stream
.read() ) )
453 error() << "Bad Cool Streams XML file" << endl
;
457 m_coolStreams
= new PlaylistCategory( m_streamsCategory
, 0, i18n("Cool-Streams") );
458 m_coolStreams
->setOpen( m_coolStreamsOpen
);
459 m_coolStreams
->setKept( false );
460 StreamEntry
*last
= 0;
462 QDomNode n
= d
.namedItem( "coolstreams" ).firstChild();
464 for( ; !n
.isNull(); n
= n
.nextSibling() )
466 QDomElement e
= n
.toElement();
467 QString name
= e
.attribute( "name" );
468 e
= n
.namedItem( "url" ).toElement();
469 KUrl url
= KUrl::KUrl( e
.text() );
470 last
= new StreamEntry( m_coolStreams
, last
, url
, name
);
471 last
->setKept( false );
475 void PlaylistBrowser::addStream( Q3ListViewItem
*parent
)
477 StreamEditor
dialog( this, i18n( "Radio Stream" ), QString() );
478 dialog
.setCaption( i18n( "Add Radio Stream" ) );
480 if( !parent
) parent
= static_cast<Q3ListViewItem
*>(m_streamsCategory
);
482 if( dialog
.exec() == QDialog::Accepted
)
484 new StreamEntry( parent
, 0, dialog
.url(), dialog
.name() );
485 parent
->sortChildItems( 0, true );
486 parent
->setOpen( true );
493 void PlaylistBrowser::editStreamURL( StreamEntry
*item
, const bool readonly
)
495 StreamEditor
dialog( this, item
->title(), item
->url().prettyUrl(), readonly
);
496 dialog
.setCaption( readonly
? i18n( "Radio Stream" ) : i18n( "Edit Radio Stream" ) );
498 if( dialog
.exec() == QDialog::Accepted
)
500 item
->setTitle( dialog
.name() );
501 item
->setUrl( dialog
.url() );
502 item
->setText(0, dialog
.name() );
506 void PlaylistBrowser::saveStreams()
508 QFile
file( streamBrowserCache() );
511 QDomElement streamB
= m_streamsCategory
->xml();
512 streamB
.setAttribute( "product", "Amarok" );
513 streamB
.setAttribute( "version", APP_VERSION
);
514 streamB
.setAttribute( "formatversion", "1.1" );
515 QDomNode streamsNode
= doc
.importNode( streamB
, true );
516 doc
.appendChild( streamsNode
);
518 QString
temp( doc
.toString() );
520 // Only open the file after all data is ready. If it crashes, data is not lost!
521 if ( !file
.open( QIODevice::WriteOnly
) ) return;
523 QTextStream
stream( &file
);
524 stream
.setCodec( "UTF8" );
525 stream
<< "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
530 *************************************************************************
532 *************************************************************************
535 void PlaylistBrowser::loadLastfmStreams( const bool subscriber
/*false*/ )
537 QFile
file( Amarok::saveLocation() + "lastfmbrowser_save.xml" );
539 QTextStream
stream( &file
);
540 stream
.setCodec( "UTF8" );
545 Q3ListViewItem
*after
= m_streamsCategory
;
547 if( !file
.open( QIODevice::ReadOnly
) || !d
.setContent( stream
.read() ) )
548 { /*Couldn't open the file or it had invalid content, so let's create an empty element*/
549 m_lastfmCategory
= new PlaylistCategory( m_listview
, after
, i18n("Last.fm Radio") );
552 e
= d
.namedItem( "category" ).toElement();
553 m_lastfmCategory
= new PlaylistCategory( m_listview
, after
, e
);
554 m_lastfmCategory
->setText( 0, i18n("Last.fm Radio") );
557 /// Load the default items
559 QStringList globaltags
;
560 globaltags
<< "Alternative" << "Ambient" << "Chill Out" << "Classical" << "Dance"
561 << "Electronica" << "Favorites" << "Heavy Metal" << "Hip Hop" << "Indie Rock"
562 << "Industrial" << "Japanese" << "Pop" << "Psytrance" << "Rap" << "Rock"
563 << "Soundtrack" << "Techno" << "Trance";
565 PlaylistCategory
*tagsFolder
= new PlaylistCategory( m_lastfmCategory
, 0, i18n("Global Tags") );
566 tagsFolder
->setKept( false );
567 LastFmEntry
*last
= 0;
569 foreach( QString str
, globaltags
)
571 const KUrl
url( "lastfm://globaltags/" + str
);
572 last
= new LastFmEntry( tagsFolder
, last
, url
, str
);
573 last
->setKept( false );
576 QString user
= AmarokConfig::scrobblerUsername();
577 KUrl
url( QString("lastfm://user/%1/neighbours").arg( user
) );
578 last
= new LastFmEntry( m_lastfmCategory
, tagsFolder
, url
, i18n( "Neighbor Radio" ) );
579 last
->setKept( false );
583 url
= KUrl( QString("lastfm://user/%1/personal").arg( user
) );
584 last
= new LastFmEntry( m_lastfmCategory
, last
, url
, i18n( "Personal Radio" ) );
585 last
->setKept( false );
587 url
= KUrl( QString("lastfm://user/%1/loved").arg( user
) );
588 last
= new LastFmEntry( m_lastfmCategory
, last
, url
, i18n( "Loved Radio" ) );
589 last
->setKept( false );
593 void PlaylistBrowser::addLastFmRadio( Q3ListViewItem
*parent
)
595 StreamEditor
dialog( this, i18n( "Last.fm Radio" ), QString() );
596 dialog
.setCaption( i18n( "Add Last.fm Radio" ) );
598 if( !parent
) parent
= static_cast<Q3ListViewItem
*>(m_lastfmCategory
);
600 if( dialog
.exec() == QDialog::Accepted
)
602 new LastFmEntry( parent
, 0, dialog
.url(), dialog
.name() );
603 parent
->sortChildItems( 0, true );
604 parent
->setOpen( true );
609 void PlaylistBrowser::addLastFmCustomRadio( Q3ListViewItem
*parent
)
611 QString token
= LastFm::Controller::createCustomStation();
612 if( token
.isEmpty() ) return;
613 token
.replace( "/", "%252" );
615 const QString text
= "lastfm://artistnames/" + token
;
616 const KUrl
url( text
);
618 QString name
= LastFm::Controller::stationDescription( text
);
619 name
.replace( "%252", "/" );
620 new LastFmEntry( parent
, 0, url
, name
);
624 void PlaylistBrowser::saveLastFm()
626 if ( !m_lastfmCategory
)
629 QFile
file( Amarok::saveLocation() + "lastfmbrowser_save.xml" );
632 QDomElement lastfmB
= m_lastfmCategory
->xml();
633 lastfmB
.setAttribute( "product", "Amarok" );
634 lastfmB
.setAttribute( "version", APP_VERSION
);
635 lastfmB
.setAttribute( "formatversion", "1.1" );
636 QDomNode lastfmNode
= doc
.importNode( lastfmB
, true );
637 doc
.appendChild( lastfmNode
);
639 QString
temp( doc
.toString() );
641 // Only open the file after all data is ready. If it crashes, data is not lost!
642 if ( !file
.open( QIODevice::WriteOnly
) ) return;
644 QTextStream
stream( &file
);
645 stream
.setCodec( "UTF8" );
646 stream
<< "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
652 *************************************************************************
654 *************************************************************************
657 QString
PlaylistBrowser::smartplaylistBrowserCache() const
659 return Amarok::saveLocation() + "smartplaylistbrowser_save.xml";
662 void PlaylistBrowser::addSmartPlaylist( Q3ListViewItem
*parent
) //SLOT
664 if( CollectionDB::instance()->isEmpty() || !m_smartCategory
)
667 if( !parent
) parent
= static_cast<Q3ListViewItem
*>(m_smartCategory
);
670 SmartPlaylistEditor
dialog( i18n("Untitled"), this );
671 if( dialog
.exec() == QDialog::Accepted
) {
673 PlaylistCategory
*category
= dynamic_cast<PlaylistCategory
*>(parent
);
675 return; //this should never happen, but let's make sure amarok doesn't crash
676 for( Q3ListViewItem
*item
= category
->firstChild(); item
; item
= item
->nextSibling() ) {
677 SmartPlaylist
*sp
= dynamic_cast<SmartPlaylist
*>(item
);
678 if ( sp
&& sp
->title() == dialog
.name() ) {
679 if( KMessageBox::warningContinueCancel(
681 i18n( "A Smart Playlist named \"%1\" already exists. Do you want to overwrite it?", dialog
.name() ),
682 i18n( "Overwrite Playlist?" ), KGuiItem( i18n( "Overwrite" ) ) ) ==
683 KMessageBox::Continue
)
692 new SmartPlaylist( parent
, 0, dialog
.result() );
693 parent
->sortChildItems( 0, true );
694 parent
->setOpen( true );
696 saveSmartPlaylists();
700 PlaylistCategory
* PlaylistBrowser::loadSmartPlaylists()
703 QFile
file( smartplaylistBrowserCache() );
704 QTextStream
stream( &file
);
705 stream
.setCodec( "UTF8" );
706 Q3ListViewItem
*after
= m_playlistCategory
;
711 if( !file
.open( QIODevice::ReadOnly
) || !d
.setContent( stream
.read() ) )
712 { /*Couldn't open the file or it had invalid content, so let's create an empty element*/
713 return new PlaylistCategory(m_listview
, after
, i18n("Smart Playlists") );
716 e
= d
.namedItem( "category" ).toElement();
717 QString version
= e
.attribute("formatversion");
718 float fversion
= e
.attribute("formatversion").toFloat();
719 if ( version
== "1.8" )
721 PlaylistCategory
* p
= new PlaylistCategory(m_listview
, after
, e
);
722 p
->setText( 0, i18n("Smart Playlists") );
725 else if ( fversion
> 1.0f
)
727 PlaylistCategory
* p
= new PlaylistCategory(m_listview
, after
, e
);
728 p
->setText( 0, i18n("Smart Playlists") );
729 debug() << "loading old format smart playlists, converted to new format";
730 updateSmartPlaylists( p
);
731 saveSmartPlaylists( p
);
734 else { // Old unversioned format
735 PlaylistCategory
* p
= new PlaylistCategory(m_listview
, after
, i18n("Smart Playlists") );
736 Q3ListViewItem
*last
= 0;
737 QDomNode n
= d
.namedItem( "smartplaylists" ).namedItem("smartplaylist");
738 for( ; !n
.isNull(); n
= n
.nextSibling() ) {
739 last
= new SmartPlaylist( p
, last
, n
.toElement() );
746 void PlaylistBrowser::updateSmartPlaylists( Q3ListViewItem
*p
)
751 for( Q3ListViewItem
*it
= p
->firstChild();
753 it
= it
->nextSibling() )
755 SmartPlaylist
*spl
= dynamic_cast<SmartPlaylist
*>( it
);
758 QDomElement xml
= spl
->xml();
759 QDomElement query
= xml
.namedItem( "sqlquery" ).toElement();
760 QDomElement expandBy
= xml
.namedItem( "expandby" ).toElement();
761 updateSmartPlaylistElement( query
);
762 updateSmartPlaylistElement( expandBy
);
766 updateSmartPlaylists( it
);
770 void PlaylistBrowser::updateSmartPlaylistElement( QDomElement
& query
)
772 QRegExp
limitSearch( "LIMIT.*(\\d+)\\s*,\\s*(\\d+)" );
773 QRegExp
selectFromSearch( "SELECT[^'\"]*FROM" );
774 for(QDomNode child
= query
.firstChild();
776 child
= child
.nextSibling() )
780 //HACK this should be refactored to just regenerate the SQL from the <criteria>'s
781 QDomText text
= child
.toText();
782 QString sql
= text
.data();
783 if ( selectFromSearch
.search( sql
) != -1 )
784 sql
.replace( selectFromSearch
, "SELECT (*ListOfFields*) FROM" );
785 if ( limitSearch
.search( sql
) != -1 )
786 sql
.replace( limitSearch
,
787 QString( "LIMIT %1 OFFSET %2").arg( limitSearch
.capturedTexts()[2].toInt() ).arg( limitSearch
.capturedTexts()[1].toInt() ) );
795 void PlaylistBrowser::loadDefaultSmartPlaylists()
799 const QStringList genres
= CollectionDB::instance()->query( "SELECT DISTINCT name FROM genre;" );
800 const QStringList artists
= CollectionDB::instance()->artistList();
803 SmartPlaylist
*last
= 0;
804 m_smartDefaults
= new PlaylistCategory( m_smartCategory
, 0, i18n("Collection") );
805 m_smartDefaults
->setOpen( m_smartDefaultsOpen
);
806 m_smartDefaults
->setKept( false );
807 /********** All Collection **************/
809 qb
.sortBy( QueryBuilder::tabArtist
, QueryBuilder::valName
);
810 qb
.sortBy( QueryBuilder::tabAlbum
, QueryBuilder::valName
);
811 qb
.sortBy( QueryBuilder::tabSong
, QueryBuilder::valTrack
);
813 item
= new SmartPlaylist( m_smartDefaults
, 0, i18n( "All Collection" ), qb
.query() );
814 item
->setPixmap( 0, SmallIcon( Amarok::icon( "collection" ) ) );
815 item
->setKept( false );
816 /********** Favorite Tracks **************/
819 qb
.setLimit( 0, 15 );
820 item
= new SmartPlaylist( m_smartDefaults
, item
, i18n( "Favorite Tracks" ), qb
.query() );
821 item
->setKept( false );
826 qb
.setLimit( 0, 15 );
827 foreach( QString str
, artists
) {
828 QueryBuilder
qbTemp( qb
);
829 qbTemp
.addMatch( QueryBuilder::tabArtist
, str
);
831 last
= new SmartPlaylist( item
, last
, i18n( "By %1", str
), qbTemp
.query() );
832 last
->setKept( false );
835 /********** Most Played **************/
837 qb
.sortBy( QueryBuilder::tabStats
, QueryBuilder::valPlayCounter
, true );
838 qb
.setLimit( 0, 15 );
840 item
= new SmartPlaylist( m_smartDefaults
, item
, i18n( "Most Played" ), qb
.query() );
841 item
->setKept( false );
845 qb
.sortBy( QueryBuilder::tabStats
, QueryBuilder::valPlayCounter
, true );
846 qb
.setLimit( 0, 15 );
847 foreach( QString str
, artists
) {
848 QueryBuilder
qbTemp( qb
);
849 qbTemp
.addMatch( QueryBuilder::tabArtist
, str
);
851 last
= new SmartPlaylist( item
, last
, i18n( "By %1", str
), qbTemp
.query() );
852 last
->setKept( false );
855 /********** Newest Tracks **************/
857 qb
.sortBy( QueryBuilder::tabSong
, QueryBuilder::valCreateDate
, true );
858 qb
.setLimit( 0, 15 );
860 item
= new SmartPlaylist( m_smartDefaults
, item
, i18n( "Newest Tracks" ), qb
.query() );
861 item
->setKept( false );
865 qb
.sortBy( QueryBuilder::tabSong
, QueryBuilder::valCreateDate
, true );
866 qb
.setLimit( 0, 15 );
867 foreach( QString str
, artists
) {
868 QueryBuilder
qbTemp( qb
);
869 qbTemp
.addMatch( QueryBuilder::tabArtist
, str
);
871 last
= new SmartPlaylist( item
, last
, i18n( "By %1", str
), qbTemp
.query( true ));
872 last
->setKept( false );
875 /********** Last Played **************/
877 qb
.sortBy( QueryBuilder::tabStats
, QueryBuilder::valAccessDate
, true );
878 qb
.setLimit( 0, 15 );
880 item
= new SmartPlaylist( m_smartDefaults
, item
, i18n( "Last Played" ), qb
.query( true ) );
881 item
->setKept( false );
883 /********** Never Played **************/
885 qb
.addNumericFilter( QueryBuilder::tabStats
, QueryBuilder::valPlayCounter
, "0" );
886 qb
.sortBy( QueryBuilder::tabArtist
, QueryBuilder::valName
);
887 qb
.sortBy( QueryBuilder::tabAlbum
, QueryBuilder::valName
);
888 qb
.sortBy( QueryBuilder::tabSong
, QueryBuilder::valTrack
);
890 item
= new SmartPlaylist( m_smartDefaults
, item
, i18n( "Never Played" ), qb
.query( true ) );
891 item
->setKept( false );
893 /********** Ever Played **************/
895 qb
.excludeFilter( QueryBuilder::tabStats
, QueryBuilder::valPlayCounter
, "1", QueryBuilder::modeLess
);
896 qb
.sortBy( QueryBuilder::tabArtist
, QueryBuilder::valName
);
897 qb
.sortBy( QueryBuilder::tabAlbum
, QueryBuilder::valName
);
898 qb
.sortBy( QueryBuilder::tabSong
, QueryBuilder::valTrack
);
899 qb
.sortBy( QueryBuilder::tabStats
, QueryBuilder::valScore
);
901 item
= new SmartPlaylist( m_smartDefaults
, item
, i18n( "Ever Played" ), qb
.query( true ) );
902 item
->setKept( false );
904 /********** Genres **************/
905 item
= new SmartPlaylist( m_smartDefaults
, item
, i18n( "Genres" ), QString() );
906 item
->setKept( false );
910 qb
.sortBy( QueryBuilder::tabArtist
, QueryBuilder::valName
);
911 qb
.sortBy( QueryBuilder::tabAlbum
, QueryBuilder::valName
);
912 qb
.sortBy( QueryBuilder::tabSong
, QueryBuilder::valTrack
);
913 foreach( QString str
, genres
) {
914 QueryBuilder
qbTemp( qb
);
915 qbTemp
.addMatch( QueryBuilder::tabGenre
, str
);
917 last
= new SmartPlaylist( item
, last
, i18n( "%1", str
), qbTemp
.query( true ) );
918 last
->setKept( false );
921 /********** 50 Random Tracks **************/
923 qb
.setOptions( QueryBuilder::optRandomize
);
924 qb
.setLimit( 0, 50 );
925 item
= new SmartPlaylist( m_smartDefaults
, item
, i18n( "50 Random Tracks" ), qb
.query( true ) );
926 item
->setKept( false );
929 void PlaylistBrowser::editSmartPlaylist( SmartPlaylist
* item
)
931 SmartPlaylistEditor
dialog( this, item
->xml() );
933 if( dialog
.exec() == QDialog::Accepted
)
935 item
->setXml ( dialog
.result() );
936 item
->setText( 0, dialog
.name() );
938 if( item
->isDynamic() ) // rebuild the cache if the smart playlist has changed
939 Playlist::instance()->rebuildDynamicModeCache();
943 void PlaylistBrowser::saveSmartPlaylists( PlaylistCategory
*smartCategory
)
945 QFile
file( smartplaylistBrowserCache() );
948 smartCategory
= m_smartCategory
;
950 // If the user hadn't set a collection, we didn't create the Smart Playlist Item
951 if( !smartCategory
) return;
954 QDomElement smartB
= smartCategory
->xml();
955 smartB
.setAttribute( "product", "Amarok" );
956 smartB
.setAttribute( "version", APP_VERSION
);
957 smartB
.setAttribute( "formatversion", "1.8" );
958 QDomNode smartplaylistsNode
= doc
.importNode( smartB
, true );
959 doc
.appendChild( smartplaylistsNode
);
961 QString
temp( doc
.toString() );
963 // Only open the file after all data is ready. If it crashes, data is not lost!
964 if ( !file
.open( QIODevice::WriteOnly
) ) return;
966 QTextStream
smart( &file
);
967 smart
.setEncoding( QTextStream::UnicodeUTF8
);
968 smart
<< "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
973 *************************************************************************
975 *************************************************************************
978 QString
PlaylistBrowser::dynamicBrowserCache() const
980 return Amarok::saveLocation() + "dynamicbrowser_save.xml";
983 PlaylistCategory
* PlaylistBrowser::loadDynamics()
985 QFile
file( dynamicBrowserCache() );
987 QTextStream
stream( &file
);
988 stream
.setCodec( "UTF8" );
993 PlaylistCategory
*after
= m_smartCategory
;
994 if( CollectionDB::instance()->isEmpty() || !m_smartCategory
) // incase of no collection
995 after
= m_playlistCategory
;
997 if( !file
.open( QIODevice::ReadOnly
) || !d
.setContent( stream
.read() ) )
998 { /*Couldn't open the file or it had invalid content, so let's create some defaults*/
999 PlaylistCategory
*p
= new PlaylistCategory( m_listview
, after
, i18n("Dynamic Playlists") );
1003 e
= d
.namedItem( "category" ).toElement();
1004 QString version
= e
.attribute("formatversion");
1005 if ( version
== "1.2" ) {
1006 PlaylistCategory
* p
= new PlaylistCategory( m_listview
, after
, e
);
1007 p
->setText( 0, i18n("Dynamic Playlists") );
1010 else if ( version
== "1.1" ) {
1011 // In 1.1, playlists would be referred only by its name.
1012 // TODO: We can *try* to convert by using findItem
1013 PlaylistCategory
* p
= new PlaylistCategory( m_listview
, after
, e
);
1014 p
->setText( 0, i18n("Dynamic Playlists") );
1015 fixDynamicPlaylistPath( p
);
1018 else { // Old unversioned format
1019 PlaylistCategory
* p
= new PlaylistCategory( m_listview
, after
, i18n("Dynamic Playlists") );
1020 Q3ListViewItem
*last
= 0;
1021 QDomNode n
= d
.namedItem( "dynamicbrowser" ).namedItem("dynamic");
1022 for( ; !n
.isNull(); n
= n
.nextSibling() ) {
1023 last
= new DynamicEntry( p
, last
, n
.toElement() );
1032 PlaylistBrowser::fixDynamicPlaylistPath( Q3ListViewItem
*item
)
1034 DynamicEntry
*entry
= dynamic_cast<DynamicEntry
*>( item
);
1036 QStringList names
= entry
->items();
1038 foreach( QString str
, names
) {
1039 QString path
= guessPathFromPlaylistName( str
);
1040 if ( !path
.isNull() )
1043 entry
->setItems( paths
);
1045 PlaylistCategory
*cat
= dynamic_cast<PlaylistCategory
*>( item
);
1047 Q3ListViewItem
*it
= cat
->firstChild();
1048 for( ; it
; it
= it
->nextSibling() ) {
1049 fixDynamicPlaylistPath( it
);
1055 PlaylistBrowser::guessPathFromPlaylistName( QString name
)
1057 Q3ListViewItem
*item
= m_listview
->findItem( name
, 0, Q3ListView::ExactMatch
);
1058 PlaylistBrowserEntry
*entry
= dynamic_cast<PlaylistBrowserEntry
*>( item
);
1060 return entry
->name();
1064 void PlaylistBrowser::saveDynamics()
1066 Amarok::config( "PlaylistBrowser" ).writeEntry( "Dynamic Random Remove Played", m_randomDynamic
->cycleTracks() );
1067 Amarok::config( "PlaylistBrowser" ).writeEntry( "Dynamic Random Upcoming Count", m_randomDynamic
->upcomingCount() );
1068 Amarok::config( "PlaylistBrowser" ).writeEntry( "Dynamic Random Previous Count", m_randomDynamic
->previousCount() );
1070 Amarok::config( "PlaylistBrowser" ).writeEntry( "Dynamic Suggest Remove Played", m_suggestedDynamic
->cycleTracks() );
1071 Amarok::config( "PlaylistBrowser" ).writeEntry( "Dynamic Suggest Upcoming Count", m_suggestedDynamic
->upcomingCount() );
1072 Amarok::config( "PlaylistBrowser" ).writeEntry( "Dynamic Suggest Previous Count", m_suggestedDynamic
->previousCount() );
1074 QFile
file( dynamicBrowserCache() );
1075 QTextStream
stream( &file
);
1078 QDomElement dynamicB
= m_dynamicCategory
->xml();
1079 dynamicB
.setAttribute( "product", "Amarok" );
1080 dynamicB
.setAttribute( "version", APP_VERSION
);
1081 dynamicB
.setAttribute( "formatversion", "1.2" );
1082 QDomNode dynamicsNode
= doc
.importNode( dynamicB
, true );
1083 doc
.appendChild( dynamicsNode
);
1085 QString
temp( doc
.toString() );
1087 // Only open the file after all data is ready. If it crashes, data is not lost!
1088 if ( !file
.open( QIODevice::WriteOnly
) ) return;
1090 stream
.setCodec( "UTF8" );
1091 stream
<< "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
1095 void PlaylistBrowser::loadDynamicItems()
1097 // Make sure all items are unmarked
1098 for( uint i
=0; i
< m_dynamicEntries
.count(); i
++ )
1100 Q3ListViewItem
*it
= m_dynamicEntries
.at( i
);
1103 static_cast<PlaylistBrowserEntry
*>(it
)->setDynamic( false );
1105 m_dynamicEntries
.clear(); // Don't use remove(), since we do i++, which would cause skip overs!!!
1107 // Mark appropriate items as used
1108 markDynamicEntries();
1112 *************************************************************************
1114 *************************************************************************
1117 QString
PlaylistBrowser::podcastBrowserCache() const
1119 //returns the playlists stats cache file
1120 return Amarok::saveLocation() + "podcastbrowser_save.xml";
1123 PlaylistCategory
* PlaylistBrowser::loadPodcasts()
1127 QFile
file( podcastBrowserCache() );
1128 QTextStream
stream( &file
);
1129 stream
.setCodec( "UTF8" );
1134 Q3ListViewItem
*after
= 0;
1136 if( !file
.open( QIODevice::ReadOnly
) || !d
.setContent( stream
.read() ) )
1137 { /*Couldn't open the file or it had invalid content, so let's create an empty element*/
1138 PlaylistCategory
*p
= new PlaylistCategory( m_listview
, after
, i18n("Podcasts") );
1140 loadPodcastsFromDatabase( p
);
1144 e
= d
.namedItem( "category" ).toElement();
1146 if ( e
.attribute("formatversion") == "1.1" ) {
1147 debug() << "Podcasts are being moved to the database...";
1148 m_podcastItemsToScan
.clear();
1150 PlaylistCategory
*p
= new PlaylistCategory( m_listview
, after
, e
);
1152 //delete the file, it is deprecated
1153 KIO::del( KUrl( podcastBrowserCache() ) );
1155 if( !m_podcastItemsToScan
.isEmpty() )
1156 m_podcastTimer
->start( m_podcastTimerInterval
);
1161 PlaylistCategory
*p
= new PlaylistCategory( m_listview
, after
, i18n("Podcasts") );
1166 void PlaylistBrowser::loadPodcastsFromDatabase( PlaylistCategory
*p
)
1169 if( !p
) p
= m_podcastCategory
;
1170 m_podcastItemsToScan
.clear();
1172 while( p
->firstChild() )
1173 delete p
->firstChild();
1175 QMap
<int,PlaylistCategory
*> folderMap
= loadPodcastFolders( p
);
1177 Q3ValueList
<PodcastChannelBundle
> channels
;
1179 channels
= CollectionDB::instance()->getPodcastChannels();
1181 PodcastChannel
*channel
= 0;
1183 oldForeachType( Q3ValueList
<PodcastChannelBundle
>, channels
)
1185 PlaylistCategory
*parent
= p
;
1186 const int parentId
= (*it
).parentId();
1187 if( parentId
> 0 && folderMap
.find( parentId
) != folderMap
.end() )
1188 parent
= folderMap
[parentId
];
1190 channel
= new PodcastChannel( parent
, channel
, *it
);
1192 bool hasNew
= CollectionDB::instance()->query( QString("SELECT COUNT(parent) FROM podcastepisodes WHERE ( parent='%1' AND isNew=%2 ) LIMIT 1" )
1193 .arg( (*it
).url().url(), CollectionDB::instance()->boolT() ) )
1194 .first().toInt() > 0;
1196 channel
->setNew( hasNew
);
1198 if( channel
->autoscan() )
1199 m_podcastItemsToScan
.append( channel
);
1202 if( !m_podcastItemsToScan
.isEmpty() )
1203 m_podcastTimer
->start( m_podcastTimerInterval
);
1206 QMap
<int,PlaylistCategory
*>
1207 PlaylistBrowser::loadPodcastFolders( PlaylistCategory
*p
)
1210 QString sql
= "SELECT * FROM podcastfolders ORDER BY parent ASC;";
1211 QStringList values
= CollectionDB::instance()->query( sql
);
1213 // store the folder and IDs so finding a parent is fast
1214 QMap
<int,PlaylistCategory
*> folderMap
;
1215 PlaylistCategory
*folder
= 0;
1216 oldForeach( values
)
1218 const int id
= (*it
).toInt();
1219 const QString t
= *++it
;
1220 const int parentId
= (*++it
).toInt();
1221 const bool isOpen
= ( (*++it
) == CollectionDB::instance()->boolT() ? true : false );
1223 PlaylistCategory
*parent
= p
;
1224 if( parentId
> 0 && folderMap
.find( parentId
) != folderMap
.end() )
1225 parent
= folderMap
[parentId
];
1227 folder
= new PlaylistCategory( parent
, folder
, t
, id
);
1228 folder
->setOpen( isOpen
);
1230 folderMap
[id
] = folder
;
1232 // check if the base folder exists
1233 p
->setOpen( Amarok::config( "PlaylistBrowser" ).readEntry( "Podcast Folder Open", true ) );
1238 void PlaylistBrowser::savePodcastFolderStates( PlaylistCategory
*folder
)
1240 if( !folder
) return;
1242 PlaylistCategory
*child
= static_cast<PlaylistCategory
*>(folder
->firstChild());
1245 if( isCategory( child
) )
1246 savePodcastFolderStates( child
);
1250 child
= static_cast<PlaylistCategory
*>(child
->nextSibling());
1252 if( folder
!= m_podcastCategory
)
1254 if( folder
->id() < 0 ) // probably due to a 1.3->1.4 migration
1255 { // we add the folder to the db, set the id and then update all the children
1256 int parentId
= static_cast<PlaylistCategory
*>(folder
->parent())->id();
1257 int newId
= CollectionDB::instance()->addPodcastFolder( folder
->text(0), parentId
, folder
->isOpen() );
1258 folder
->setId( newId
);
1259 PodcastChannel
*chan
= static_cast<PodcastChannel
*>(folder
->firstChild());
1262 if( isPodcastChannel( chan
) )
1263 // will update the database so child has correct parentId.
1264 chan
->setParent( folder
);
1265 chan
= static_cast<PodcastChannel
*>(chan
->nextSibling());
1270 CollectionDB::instance()->updatePodcastFolder( folder
->id(), folder
->text(0),
1271 static_cast<PlaylistCategory
*>(folder
->parent())->id(), folder
->isOpen() );
1276 void PlaylistBrowser::scanPodcasts()
1278 //don't want to restart timer unnecessarily. addPodcast will start it if it is necessary
1279 if( m_podcastItemsToScan
.isEmpty() ) return;
1281 for( uint i
=0; i
< m_podcastItemsToScan
.count(); i
++ )
1283 Q3ListViewItem
*item
= m_podcastItemsToScan
.at( i
);
1284 PodcastChannel
*pc
= static_cast<PodcastChannel
*>(item
);
1288 m_podcastTimer
->start( m_podcastTimerInterval
);
1291 void PlaylistBrowser::refreshPodcasts( Q3ListViewItem
*parent
)
1293 for( Q3ListViewItem
*child
= parent
->firstChild();
1295 child
= child
->nextSibling() )
1297 if( isPodcastChannel( child
) )
1298 static_cast<PodcastChannel
*>( child
)->rescan();
1299 else if( isCategory( child
) )
1300 refreshPodcasts( child
);
1304 void PlaylistBrowser::addPodcast( Q3ListViewItem
*parent
)
1307 const QString name
= KInputDialog::getText(i18n("Add Podcast"), i18n("Enter Podcast URL:"), QString(), &ok
, this);
1309 if( ok
&& !name
.isEmpty() )
1311 PodcastChannelProvider
* podcastChannelProvider
=
1312 static_cast<PodcastChannelProvider
*>(
1313 The::playlistManager()->playlistProvider( PlaylistManager::PodcastChannel
, i18n("Local Podcasts") )
1315 if( podcastChannelProvider
)
1316 podcastChannelProvider
->addPodcast( name
); //KUrl( name ), parent );
1318 debug() << "no PodcastCollection found";
1322 void PlaylistBrowser::configurePodcasts( Q3ListViewItem
*parent
)
1324 Q3PtrList
<PodcastChannel
> podcastChannelList
;
1325 for( Q3ListViewItem
*child
= parent
->firstChild();
1327 child
= child
->nextSibling() )
1329 if( isPodcastChannel( child
) )
1331 podcastChannelList
.append( static_cast<PodcastChannel
*>( child
) );
1334 if( !podcastChannelList
.isEmpty() )
1335 configurePodcasts( podcastChannelList
, i18nc( "Podcasts contained in %1",
1336 "All in %1", parent
->text( 0 ) ) );
1339 void PlaylistBrowser::configureSelectedPodcasts()
1341 Q3PtrList
<PodcastChannel
> selected
;
1342 Q3ListViewItemIterator
it( m_listview
, Q3ListViewItemIterator::Selected
);
1343 for( ; it
.current(); ++it
)
1345 if( isPodcastChannel( (*it
) ) )
1346 selected
.append( static_cast<PodcastChannel
*>(*it
) );
1348 if (selected
.isEmpty() )
1349 return; //shouldn't happen
1351 if( selected
.count() == 1 )
1352 selected
.getFirst()->configure();
1354 configurePodcasts( selected
, i18np("1 Podcast", "%1 Podcasts", selected
.count() ) );
1356 if( m_podcastItemsToScan
.isEmpty() )
1357 m_podcastTimer
->stop();
1359 else if( m_podcastItemsToScan
.count() == 1 )
1360 m_podcastTimer
->start( m_podcastTimerInterval
);
1361 // else timer is already running
1364 void PlaylistBrowser::configurePodcasts( Q3PtrList
<PodcastChannel
> &podcastChannelList
,
1365 const QString
&caption
)
1368 if( podcastChannelList
.isEmpty() )
1370 debug() << "BUG: podcastChannelList is empty";
1373 Q3PtrList
<PodcastSettings
> podcastSettingsList
;
1374 oldForeachType( Q3PtrList
<PodcastChannel
>, podcastChannelList
)
1376 podcastSettingsList
.append( (*it
)->getSettings() );
1378 PodcastSettingsDialog
*dialog
= new PodcastSettingsDialog( podcastSettingsList
, caption
);
1379 if( dialog
->configure() )
1381 PodcastChannel
*channel
= podcastChannelList
.first();
1382 oldForeachType( Q3PtrList
<PodcastSettings
>, podcastSettingsList
)
1384 if ( (*it
)->title() == channel
->title() )
1386 channel
->setSettings( *it
);
1389 debug() << " BUG in playlistbrowser.cpp:configurePodcasts( )";
1391 channel
= podcastChannelList
.next();
1397 PlaylistBrowser::findPodcastChannel( const KUrl
&feed
, Q3ListViewItem
*parent
) const
1399 if( !parent
) parent
= static_cast<Q3ListViewItem
*>(m_podcastCategory
);
1401 for( Q3ListViewItem
*it
= parent
->firstChild();
1403 it
= it
->nextSibling() )
1405 if( isPodcastChannel( it
) )
1407 PodcastChannel
*channel
= static_cast<PodcastChannel
*>( it
);
1408 if( channel
->url().prettyUrl() == feed
.prettyUrl() )
1413 else if( isCategory( it
) )
1415 PodcastChannel
*channel
= findPodcastChannel( feed
, it
);
1425 PlaylistBrowser::findPodcastEpisode( const KUrl
&episode
, const KUrl
&feed
) const
1427 PodcastChannel
*channel
= findPodcastChannel( feed
);
1431 if( !channel
->isPolished() )
1434 Q3ListViewItem
*child
= channel
->firstChild();
1437 #define child static_cast<PodcastEpisode*>(child)
1438 if( child
->url() == episode
)
1441 child
= child
->nextSibling();
1447 void PlaylistBrowser::addPodcast( const KUrl
& origUrl
, Q3ListViewItem
*parent
)
1449 if( !parent
) parent
= static_cast<Q3ListViewItem
*>(m_podcastCategory
);
1451 KUrl
url( origUrl
);
1452 if( url
.protocol() == "itpc" || url
.protocol() == "pcast" )
1453 url
.setProtocol( "http" );
1455 PodcastChannel
*channel
= findPodcastChannel( url
);
1458 Amarok::StatusBar::instance()->longMessage(
1459 i18n( "Already subscribed to feed %1 as %2", url
.prettyUrl(),
1461 KDE::StatusBar::Sorry
);
1465 PodcastChannel
*pc
= new PodcastChannel( parent
, 0, url
);
1467 if( m_podcastItemsToScan
.isEmpty() )
1469 m_podcastItemsToScan
.append( pc
);
1470 m_podcastTimer
->start( m_podcastTimerInterval
);
1474 m_podcastItemsToScan
.append( pc
);
1477 parent
->sortChildItems( 0, true );
1478 parent
->setOpen( true );
1481 void PlaylistBrowser::changePodcastInterval()
1483 double time
= static_cast<double>(m_podcastTimerInterval
/ ( 60 * 60 * 1000 ));
1485 double interval
= KInputDialog::getDouble( i18n("Download Interval"),
1486 i18n("Scan interval (hours):"), time
,
1487 0.5, 100.0, .5, 1, // min, max, step, base
1489 int milliseconds
= static_cast<int>(interval
*60.0*60.0*1000.0);
1492 if( milliseconds
!= m_podcastTimerInterval
)
1494 m_podcastTimerInterval
= milliseconds
;
1495 m_podcastTimer
->start( m_podcastTimerInterval
);
1500 bool PlaylistBrowser::deleteSelectedPodcastItems( const bool removeItem
, const bool silent
)
1503 Q3ListViewItemIterator
it( m_podcastCategory
, Q3ListViewItemIterator::Selected
);
1504 Q3PtrList
<PodcastEpisode
> erasedItems
;
1506 for( ; it
.current(); ++it
)
1508 if( isPodcastEpisode( *it
) )
1510 #define item static_cast<PodcastEpisode*>(*it)
1511 if( item
->isOnDisk() ) {
1512 urls
.append( item
->localUrl() );
1513 erasedItems
.append( item
);
1519 if( urls
.isEmpty() ) return false;
1522 button
= KMessageBox::warningContinueCancel( this,
1523 i18np( "<p>You have selected 1 podcast episode to be <b>irreversibly</b> deleted. ",
1524 "<p>You have selected %1 podcast episodes to be <b>irreversibly</b> deleted. ",
1525 urls
.count() ), QString(), KStandardGuiItem::del() );
1526 if( silent
|| button
!= KMessageBox::Continue
)
1529 KIO::Job
*job
= KIO::del( urls
);
1531 PodcastEpisode
*item
;
1532 for ( item
= erasedItems
.first(); item
; item
= erasedItems
.next() )
1536 CollectionDB::instance()->removePodcastEpisode( item
->dBId() );
1540 connect( job
, SIGNAL( result( KIO::Job
* ) ), item
, SLOT( isOnDisk() ) );;
1545 bool PlaylistBrowser::deletePodcasts( Q3PtrList
<PodcastChannel
> items
)
1547 if( items
.isEmpty() ) return false;
1550 oldForeachType( Q3PtrList
<PodcastChannel
>, items
)
1552 for( Q3ListViewItem
*ch
= (*it
)->firstChild(); ch
; ch
= ch
->nextSibling() )
1554 #define ch static_cast<PodcastEpisode*>(ch)
1555 if( ch
->isOnDisk() )
1557 //delete downloaded media
1558 urls
.append( ch
->localUrl() );
1561 /// we don't need to delete from the database, because removing the channel from the database
1562 /// automatically removes the children as well.
1563 m_podcastItemsToScan
.remove( static_cast<PodcastChannel
*>(*it
) );
1565 CollectionDB::instance()->removePodcastChannel( static_cast<PodcastChannel
*>(*it
)->url() );
1568 // TODO We need to check which files have been deleted successfully
1574 void PlaylistBrowser::downloadSelectedPodcasts()
1576 Q3ListViewItemIterator
it( m_listview
, Q3ListViewItemIterator::Selected
);
1578 for( ; it
.current(); ++it
)
1580 if( isPodcastEpisode( *it
) )
1582 #define item static_cast<PodcastEpisode*>(*it)
1583 if( !item
->isOnDisk() )
1584 m_podcastDownloadQueue
.append( item
);
1588 downloadPodcastQueue();
1591 void PlaylistBrowser::downloadPodcastQueue() //SLOT
1593 if( m_podcastDownloadQueue
.isEmpty() ) return;
1595 PodcastEpisode
*first
= m_podcastDownloadQueue
.first();
1596 first
->downloadMedia();
1597 m_podcastDownloadQueue
.removeFirst();
1599 connect( first
, SIGNAL( downloadFinished() ), this, SLOT( downloadPodcastQueue() ) );
1600 connect( first
, SIGNAL( downloadAborted() ), this, SLOT( abortPodcastQueue() ) );
1603 void PlaylistBrowser::abortPodcastQueue() //SLOT
1605 m_podcastDownloadQueue
.clear();
1608 void PlaylistBrowser::registerPodcastSettings( const QString
&title
, const PodcastSettings
*settings
)
1610 m_podcastSettings
.insert( title
, settings
);
1614 *************************************************************************
1616 *************************************************************************
1619 QString
PlaylistBrowser::playlistBrowserCache() const
1621 //returns the playlists stats cache file
1622 return Amarok::saveLocation() + "playlistbrowser_save.xml";
1625 PlaylistCategory
* PlaylistBrowser::loadPlaylists()
1627 QFile
file( playlistBrowserCache() );
1629 QTextStream
stream( &file
);
1630 stream
.setCodec( "UTF8" );
1635 if( !file
.open( QIODevice::ReadOnly
) || !d
.setContent( stream
.read() ) )
1636 { /*Couldn't open the file or it had invalid content, so let's create an empty element*/
1637 return new PlaylistCategory(m_listview
, 0 , i18n("Playlists") );
1640 e
= d
.namedItem( "category" ).toElement();
1641 if ( e
.attribute("formatversion") =="1.1" )
1643 PlaylistCategory
* p
= new PlaylistCategory( m_listview
, 0 , e
);
1644 p
->setText( 0, i18n("Playlists") );
1647 else { // Old unversioned format
1648 PlaylistCategory
* p
= new PlaylistCategory( m_listview
, 0 , i18n("Playlists") );
1649 Q3ListViewItem
*last
= 0;
1650 QDomNode n
= d
.namedItem( "playlistbrowser" ).namedItem("playlist");
1652 for ( ; !n
.isNull(); n
= n
.nextSibling() )
1653 last
= new PlaylistEntry( p
, last
, n
.toElement() );
1661 PlaylistBrowser::findItemInTree( const QString
&searchstring
, int c
) const
1663 QStringList list
= QStringList::split( "/", searchstring
, true );
1665 // select the 1st level
1666 QStringList::Iterator it
= list
.begin();
1667 Q3ListViewItem
*pli
= findItem (*it
, c
);
1668 if ( !pli
) return pli
;
1670 for ( ++it
; it
!= list
.end(); ++it
)
1673 Q3ListViewItemIterator
it2( pli
);
1674 for( ++it2
; it2
.current(); ++it2
)
1676 if ( *it
== (*it2
)->text(0) )
1681 // test, to not go over into the next category
1682 if ( isCategory( *it2
) && (pli
->nextSibling() == *it2
) )
1685 if ( ! it2
.current() )
1692 DynamicMode
*PlaylistBrowser::findDynamicModeByTitle( const QString
&title
)
1697 for ( Q3ListViewItem
*item
= m_dynamicCategory
->firstChild(); item
; item
= item
->nextSibling() )
1699 DynamicEntry
*entry
= dynamic_cast<DynamicEntry
*>( item
);
1700 if ( entry
&& entry
->title() == title
)
1708 PlaylistBrowser::findPlaylistEntry( const QString
&url
, Q3ListViewItem
*parent
) const
1710 if( !parent
) parent
= static_cast<Q3ListViewItem
*>(m_playlistCategory
);
1712 for( Q3ListViewItem
*it
= parent
->firstChild();
1714 it
= it
->nextSibling() )
1716 if( isPlaylist( it
) )
1718 PlaylistEntry
*pl
= static_cast<PlaylistEntry
*>( it
);
1719 debug() << pl
->url().path() << " == " << url
;
1720 if( pl
->url().path() == url
)
1726 else if( isCategory( it
) )
1728 PlaylistEntry
*pl
= findPlaylistEntry( url
, it
);
1737 int PlaylistBrowser::loadPlaylist( const QString
&playlist
, bool /*force*/ )
1742 Q3ListViewItem
*pli
= findItemInTree( playlist
, 0 );
1743 if ( ! pli
) return -1;
1745 slotDoubleClicked( pli
);
1750 void PlaylistBrowser::addPlaylist( const QString
&path
, Q3ListViewItem
*parent
, bool force
, bool imported
)
1752 // this function adds a playlist to the playlist browser
1758 if( !file
.exists() ) return;
1760 PlaylistEntry
*playlist
= findPlaylistEntry( path
);
1762 if( playlist
&& force
)
1763 playlist
->load(); //reload the playlist
1766 Q3ListViewItem
*playlistImports
= 0;
1767 //First try and find the imported folder
1768 for ( Q3ListViewItem
*it
= m_playlistCategory
->firstChild(); it
; it
= it
->nextSibling() )
1770 if ( dynamic_cast<PlaylistCategory
*>( it
) && static_cast<PlaylistCategory
*>( it
)->isFolder() &&
1771 it
->text( 0 ) == i18n( "Imported" ) )
1773 playlistImports
= it
;
1777 if ( !playlistImports
) //We didn't find the Imported folder, so create it.
1778 playlistImports
= new PlaylistCategory( m_playlistCategory
, 0, i18n("Imported") );
1779 parent
= playlistImports
;
1781 else if( !parent
) parent
= static_cast<Q3ListViewItem
*>(m_playlistCategory
);
1784 if( !m_playlistCategory
|| !m_playlistCategory
->childCount() ) { //first child
1785 removeButton
->setEnabled( true );
1786 renameButton
->setEnabled( true );
1790 auxKURL
.setPath(path
);
1791 m_lastPlaylist
= playlist
= new PlaylistEntry( parent
, 0, auxKURL
);
1794 parent
->setOpen( true );
1795 parent
->sortChildItems( 0, true );
1796 m_listview
->clearSelection();
1797 playlist
->setSelected( true );
1800 bool PlaylistBrowser::savePlaylist( const QString
&path
, const Q3ValueList
<KUrl
> &in_urls
,
1801 const Q3ValueList
<QString
> &titles
, const Q3ValueList
<int> &lengths
,
1804 if( path
.isEmpty() )
1809 if( !file
.open( QIODevice::WriteOnly
) )
1811 KMessageBox::sorry( MainWindow::self(), i18n( "Cannot write playlist (%1).").arg(path
) );
1815 QTextStream
stream( &file
);
1816 stream
<< "#EXTM3U\n";
1819 for( int i
= 0, n
= in_urls
.count(); i
< n
; ++i
)
1821 const KUrl
&url
= in_urls
[i
];
1822 if( url
.isLocalFile() && QFileInfo( url
.path() ).isDir() )
1823 urls
+= recurse( url
);
1828 for( int i
= 0, n
= urls
.count(); i
< n
; ++i
)
1830 const KUrl
&url
= urls
[i
];
1832 if( !titles
.isEmpty() && !lengths
.isEmpty() )
1834 stream
<< "#EXTINF:";
1835 stream
<< QString::number( lengths
[i
] );
1837 stream
<< titles
[i
];
1840 if (url
.protocol() == "file" ) {
1842 const QFileInfo
fi(file
);
1843 stream
<< KUrl::relativePath(fi
.path(), url
.path());
1845 stream
<< url
.path();
1847 stream
<< url
.url();
1852 file
.close(); // Flushes the file, before we read it
1853 PlaylistBrowser::instance()->addPlaylist( path
, 0, true );
1858 void PlaylistBrowser::openPlaylist( Q3ListViewItem
*parent
) //SLOT
1860 // open a file selector to add playlists to the playlist browser
1862 files
= KFileDialog::getOpenFileNames( KUrl(), "*.m3u *.pls *.xspf|" + i18n("Playlist Files"), this, i18n("Import Playlists") );
1864 const QStringList::ConstIterator end
= files
.constEnd();
1865 for( QStringList::ConstIterator it
= files
.constBegin(); it
!= end
; ++it
)
1866 addPlaylist( *it
, parent
);
1871 void PlaylistBrowser::savePlaylists()
1873 QFile
file( playlistBrowserCache() );
1876 QDomElement playlistsB
= m_playlistCategory
->xml();
1877 playlistsB
.setAttribute( "product", "Amarok" );
1878 playlistsB
.setAttribute( "version", APP_VERSION
);
1879 playlistsB
.setAttribute( "formatversion", "1.1" );
1880 QDomNode playlistsNode
= doc
.importNode( playlistsB
, true );
1881 doc
.appendChild( playlistsNode
);
1883 QString
temp( doc
.toString() );
1885 // Only open the file after all data is ready. If it crashes, data is not lost!
1886 if ( !file
.open( QIODevice::WriteOnly
) ) return;
1888 QTextStream
stream( &file
);
1889 stream
.setCodec( "UTF8" );
1890 stream
<< "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
1894 bool PlaylistBrowser::deletePlaylists( Q3PtrList
<PlaylistEntry
> items
)
1897 oldForeachType( Q3PtrList
<PlaylistEntry
>, items
)
1899 urls
.append( (*it
)->url() );
1901 if( !urls
.isEmpty() )
1902 return deletePlaylists( urls
);
1907 bool PlaylistBrowser::deletePlaylists( KUrl::List items
)
1909 if ( items
.isEmpty() ) return false;
1911 // TODO We need to check which files have been deleted successfully
1912 // Avoid deleting dirs. See bug #122480
1913 for ( KUrl::List::iterator it
= items
.begin(), end
= items
.end(); it
!= end
; ++it
) {
1914 if ( QFileInfo( (*it
).path() ).isDir() ) {
1915 it
= items
.erase( it
);
1923 void PlaylistBrowser::savePlaylist( PlaylistEntry
*item
)
1925 bool append
= false;
1927 if( item
->trackList().count() == 0 ) //the playlist hasn't been loaded so we append the dropped tracks
1930 //save the modified playlist in m3u or pls format
1931 const QString ext
= fileExtension( item
->url().path() );
1932 if( ext
.toLower() == "m3u" )
1933 saveM3U( item
, append
);
1934 else if ( ext
.toLower() == "xspf" )
1935 saveXSPF( item
, append
);
1937 savePLS( item
, append
);
1941 *************************************************************************
1943 *************************************************************************
1946 PlaylistBrowserEntry
*
1947 PlaylistBrowser::findItem( QString
&t
, int c
) const
1949 return static_cast<PlaylistBrowserEntry
*>( m_listview
->findItem( t
, c
, Q3ListView::ExactMatch
) );
1952 bool PlaylistBrowser::createPlaylist( Q3ListViewItem
*parent
, bool current
, QString title
)
1954 if( title
.isEmpty() ) title
= i18n("Untitled");
1956 const QString path
= PlaylistDialog::getSaveFileName( title
);
1957 if( path
.isEmpty() )
1961 parent
= static_cast<Q3ListViewItem
*>( m_playlistCategory
);
1965 if ( !Playlist::instance()->saveM3U( path
) ) {
1971 //Remove any items in Listview that have the same path as this one
1972 // Should only happen when overwriting a playlist
1973 Q3ListViewItem
*item
= parent
->firstChild();
1976 if( static_cast<PlaylistEntry
*>( item
)->url() == path
)
1978 Q3ListViewItem
*todelete
= item
;
1979 item
= item
->nextSibling();
1983 item
= item
->nextSibling();
1986 //Remove existing playlist if it exists
1987 if ( QFileInfo( path
).exists() )
1988 QFileInfo( path
).dir().remove( path
);
1990 m_lastPlaylist
= new PlaylistEntry( parent
, 0, path
);
1991 parent
->sortChildItems( 0, true );
1999 void PlaylistBrowser::addSelectedToPlaylist( int options
)
2001 if ( options
== -1 )
2002 options
= Playlist::Unique
| Playlist::Append
;
2006 Q3ListViewItemIterator
it( m_listview
, Q3ListViewItemIterator::Selected
);
2007 for( ; it
.current(); ++it
)
2010 if ( isPlaylist( item
) )
2011 list
<< static_cast<PlaylistEntry
*>(item
)->url();
2013 else if( isLastFm( item
) )
2014 list
<< static_cast<LastFmEntry
*>(item
)->url();
2016 else if ( isStream( item
) )
2017 list
<< static_cast<StreamEntry
*>(item
)->url();
2019 else if ( isPodcastChannel( item
) )
2021 #define channel static_cast<PodcastChannel*>(item)
2022 if( !channel
->isPolished() )
2026 Q3ListViewItem
*child
= item
->firstChild();
2029 #define child static_cast<PodcastEpisode *>(child)
2031 _list
.prepend( child
->localUrl() ):
2032 _list
.prepend( child
->url() );
2034 child
= child
->nextSibling();
2039 else if ( isPodcastEpisode( item
) )
2041 #define pod static_cast<PodcastEpisode*>(item)
2042 if( pod
->isOnDisk() )
2043 list
<< pod
->localUrl();
2049 else if ( isPlaylistTrackItem( item
) )
2050 list
<< static_cast<PlaylistTrackItem
*>(item
)->url();
2054 if( !list
.isEmpty() )
2055 The::playlistModel()->insertMedia( list
, options
);
2059 PlaylistBrowser::invokeItem( Q3ListViewItem
* i
, const QPoint
& point
, int column
) //SLOT
2064 PlaylistBrowserView
*view
= getListView();
2066 QPoint p
= mapFromGlobal( point
);
2067 if ( p
.x() > view
->header()->sectionPos( view
->header()->mapToIndex( 0 ) ) + view
->treeStepSize() * ( i
->depth() + ( view
->rootIsDecorated() ? 1 : 0) ) + view
->itemMargin()
2068 || p
.x() < view
->header()->sectionPos( view
->header()->mapToIndex( 0 ) ) )
2069 slotDoubleClicked( i
);
2072 void PlaylistBrowser::slotDoubleClicked( Q3ListViewItem
*item
) //SLOT
2075 PlaylistBrowserEntry
*entry
= dynamic_cast<PlaylistBrowserEntry
*>(item
);
2077 entry
->slotDoubleClicked();
2080 void PlaylistBrowser::collectionScanDone()
2082 if( !m_polished
|| CollectionDB::instance()->isEmpty() )
2086 else if( !m_smartCategory
)
2088 m_smartCategory
= loadSmartPlaylists();
2089 loadDefaultSmartPlaylists();
2090 m_smartCategory
->setOpen( true );
2094 void PlaylistBrowser::removeSelectedItems() //SLOT
2096 // this function remove selected playlists and tracks
2098 int playlistCount
= 0;
2100 int streamCount
= 0;
2101 int smartyCount
= 0;
2102 int dynamicCount
= 0;
2103 int podcastCount
= 0;
2104 int folderCount
= 0;
2105 int lastfmCount
= 0;
2107 Q3PtrList
<PlaylistEntry
> playlistsToDelete
;
2108 Q3PtrList
<PodcastChannel
> podcastsToDelete
;
2110 Q3PtrList
<PlaylistCategory
> playlistFoldersToDelete
;
2111 Q3PtrList
<PlaylistCategory
> podcastFoldersToDelete
;
2113 //remove currentItem, no matter if selected or not
2114 m_listview
->setSelected( m_listview
->currentItem(), true );
2116 Q3PtrList
<Q3ListViewItem
> selected
;
2117 Q3ListViewItemIterator
it( m_listview
, Q3ListViewItemIterator::Selected
);
2118 for( ; it
.current(); ++it
)
2120 if( !static_cast<PlaylistBrowserEntry
*>(*it
)->isKept() )
2123 if( isCategory( *it
) && !static_cast<PlaylistCategory
*>(*it
)->isFolder() ) //its a base category
2126 // if the playlist containing this item is already selected the current item will be skipped
2127 // it will be deleted from the parent
2128 Q3ListViewItem
*parent
= it
.current()->parent();
2130 if( parent
&& parent
->isSelected() ) //parent will remove children
2134 while( parent
->parent() && static_cast<PlaylistBrowserEntry
*>(parent
)->isKept() )
2135 parent
= parent
->parent();
2138 if( parent
&& !static_cast<PlaylistBrowserEntry
*>(parent
)->isKept() )
2141 switch( (*it
)->rtti() )
2143 case PlaylistEntry::RTTI
:
2144 playlistsToDelete
.append( static_cast<PlaylistEntry
*>(*it
) );
2146 continue; // don't add the folder to selected, else it will be deleted twice
2148 case PlaylistTrackItem::RTTI
:
2152 case LastFmEntry::RTTI
:
2156 case StreamEntry::RTTI
:
2160 case DynamicEntry::RTTI
:
2164 case SmartPlaylist::RTTI
:
2168 case PodcastChannel::RTTI
:
2170 podcastsToDelete
.append( static_cast<PodcastChannel
*>(*it
) );
2171 case PodcastEpisode::RTTI
: //episodes can't be removed
2172 continue; // don't add the folder to selected, else it will be deleted twice
2174 case PlaylistCategory::RTTI
:
2176 if( parent
== m_playlistCategory
)
2178 for( Q3ListViewItem
*ch
= (*it
)->firstChild(); ch
; ch
= ch
->nextSibling() )
2180 if( isCategory( ch
) )
2183 playlistFoldersToDelete
.append( static_cast<PlaylistCategory
*>(ch
) );
2188 playlistsToDelete
.append( static_cast<PlaylistEntry
*>(ch
) );
2191 playlistFoldersToDelete
.append( static_cast<PlaylistCategory
*>(*it
) );
2192 continue; // don't add the folder to selected, else it will be deleted twice
2194 else if( parent
== m_podcastCategory
)
2196 for( Q3ListViewItem
*ch
= (*it
)->firstChild(); ch
; ch
= ch
->nextSibling() )
2198 if( isCategory( ch
) )
2201 podcastFoldersToDelete
.append( static_cast<PlaylistCategory
*>(ch
) );
2206 podcastsToDelete
.append( static_cast<PodcastChannel
*>(ch
) );
2209 podcastFoldersToDelete
.append( static_cast<PlaylistCategory
*>(*it
) );
2210 continue; // don't add the folder to selected, else it will be deleted twice
2217 selected
.append( it
.current() );
2220 int totalCount
= playlistCount
+ smartyCount
+ dynamicCount
+
2221 streamCount
+ podcastCount
+ folderCount
+ lastfmCount
;
2223 if( selected
.isEmpty() && !totalCount
) return;
2225 QString message
= i18n( "<p>You have selected:<ul>" );
2227 if( playlistCount
) message
+= "<li>" + i18np( "1 playlist", "%1 playlists", playlistCount
) + "</li>";
2229 if( smartyCount
) message
+= "<li>" + i18np( "1 smart playlist", "%1 smart playlists", smartyCount
) + "</li>";
2231 if( dynamicCount
) message
+= "<li>" + i18np( "1 dynamic playlist", "%1 dynamic playlists", dynamicCount
) + "</li>";
2233 if( streamCount
) message
+= "<li>" + i18np( "1 stream", "%1 streams", streamCount
) + "</li>";
2235 if( podcastCount
) message
+= "<li>" + i18np( "1 podcast", "%1 podcasts", podcastCount
) + "</li>";
2237 if( folderCount
) message
+= "<li>" + i18np( "1 folder", "%1 folders", folderCount
) + "</li>";
2239 if( lastfmCount
) message
+= "<li>" + i18np( "1 last.fm stream", "%1 last.fm streams", lastfmCount
) + "</li>";
2241 message
+= i18n( "</ul><br>to be <b>irreversibly</b> deleted.</p>" );
2244 message
+= i18n( "<br><p>All downloaded podcast episodes will also be deleted.</p>" );
2246 if( totalCount
> 0 )
2248 int button
= KMessageBox::warningContinueCancel( this, message
, QString(), KStandardGuiItem::del() );
2249 if( button
!= KMessageBox::Continue
)
2253 oldForeachType( Q3PtrList
<Q3ListViewItem
>, selected
)
2255 if ( isPlaylistTrackItem( *it
) )
2257 static_cast<PlaylistEntry
*>( (*it
)->parent() )->removeTrack( (*it
) );
2260 if ( isDynamic( *it
) )
2261 static_cast<DynamicEntry
*>( *it
)->deleting();
2265 // used for deleting playlists first, then folders.
2268 if( deletePlaylists( playlistsToDelete
) )
2270 oldForeachType( Q3PtrList
<PlaylistEntry
>, playlistsToDelete
)
2272 m_dynamicEntries
.remove(*it
);
2280 if( deletePodcasts( podcastsToDelete
) )
2281 oldForeachType( Q3PtrList
<PodcastChannel
>, podcastsToDelete
)
2285 oldForeachType( Q3PtrList
<PlaylistCategory
>, playlistFoldersToDelete
)
2288 oldForeachType( Q3PtrList
<PlaylistCategory
>, podcastFoldersToDelete
)
2289 removePodcastFolder( *it
);
2291 if( playlistCount
|| trackCount
)
2294 if( streamCount
) saveStreams();
2295 if( smartyCount
) saveSmartPlaylists();
2296 if( dynamicCount
) saveDynamics();
2297 if( lastfmCount
) saveLastFm();
2300 // remove podcast folders. we need to do this recursively to ensure all children are removed from the db
2301 void PlaylistBrowser::removePodcastFolder( PlaylistCategory
*item
)
2304 if( !item
->childCount() )
2306 CollectionDB::instance()->removePodcastFolder( item
->id() );
2311 Q3ListViewItem
*child
= item
->firstChild();
2314 Q3ListViewItem
*nextChild
= 0;
2315 if( isPodcastChannel( child
) )
2317 #define child static_cast<PodcastChannel*>(child)
2318 nextChild
= child
->nextSibling();
2319 CollectionDB::instance()->removePodcastChannel( child
->url() );
2320 m_podcastItemsToScan
.remove( child
);
2323 else if( isCategory( child
) )
2325 nextChild
= child
->nextSibling();
2326 removePodcastFolder( static_cast<PlaylistCategory
*>(child
) );
2331 CollectionDB::instance()->removePodcastFolder( item
->id() );
2335 void PlaylistBrowser::renameSelectedItem() //SLOT
2337 Q3ListViewItem
*item
= m_listview
->currentItem();
2340 if( item
== m_randomDynamic
|| item
== m_suggestedDynamic
)
2343 PlaylistBrowserEntry
*entry
= dynamic_cast<PlaylistBrowserEntry
*>( item
);
2345 entry
->slotRenameItem();
2349 void PlaylistBrowser::renamePlaylist( Q3ListViewItem
* item
, const QString
& newName
, int ) //SLOT
2351 PlaylistBrowserEntry
*entry
= dynamic_cast<PlaylistBrowserEntry
*>( item
);
2353 entry
->slotPostRenameItem( newName
);
2357 void PlaylistBrowser::saveM3U( PlaylistEntry
*item
, bool append
)
2359 QFile
file( item
->url().path() );
2361 if( append
? file
.open( QIODevice::WriteOnly
| QIODevice::Append
) : file
.open( QIODevice::WriteOnly
) )
2363 QTextStream
stream( &file
);
2365 stream
<< "#EXTM3U\n";
2366 Q3PtrList
<TrackItemInfo
> trackList
= append
? item
->droppedTracks() : item
->trackList();
2367 for( TrackItemInfo
*info
= trackList
.first(); info
; info
= trackList
.next() )
2369 stream
<< "#EXTINF:";
2370 stream
<< info
->length();
2372 stream
<< info
->title();
2374 stream
<< (info
->url().protocol() == "file" ? info
->url().path() : info
->url().url());
2382 void PlaylistBrowser::saveXSPF( PlaylistEntry
*item
, bool append
)
2384 XSPFPlaylist playlist
;
2386 playlist
.setCreator( "Amarok" );
2387 playlist
.setTitle( item
->text(0) );
2391 //XSPFPlaylist has been ported to Meta::, PlaylistBrowser needs to be
2393 Q3PtrList
<TrackItemInfo
> trackList
= append
? item
->droppedTracks() : item
->trackList();
2394 for( TrackItemInfo
*info
= trackList
.first(); info
; info
= trackList
.next() )
2397 MetaBundle
b( info
->url() );
2398 track
.creator
= b
.artist();
2399 track
.title
= b
.title();
2400 track
.location
= b
.url().url();
2401 list
.append( track
);
2404 playlist
.setTrackList( list
, append
);
2406 QFile
file( item
->url().path() );
2407 file
.open( QIODevice::WriteOnly
);
2409 QTextStream
stream ( &file
);
2411 playlist
.save( stream
, 2 );
2417 void PlaylistBrowser::savePLS( PlaylistEntry
*item
, bool append
)
2419 QFile
file( item
->url().path() );
2421 if( append
? file
.open( QIODevice::WriteOnly
| QIODevice::Append
) : file
.open( QIODevice::WriteOnly
) )
2423 QTextStream
stream( &file
);
2424 Q3PtrList
<TrackItemInfo
> trackList
= append
? item
->droppedTracks() : item
->trackList();
2425 stream
<< "NumberOfEntries=" << trackList
.count() << endl
;
2427 for( TrackItemInfo
*info
= trackList
.first(); info
; info
= trackList
.next(), ++c
)
2429 stream
<< "File" << c
<< "=";
2430 stream
<< (info
->url().protocol() == "file" ? info
->url().path() : info
->url().url());
2431 stream
<< "\nTitle" << c
<< "=";
2432 stream
<< info
->title();
2433 stream
<< "\nLength" << c
<< "=";
2434 stream
<< info
->length();
2438 stream
<< "Version=2\n";
2443 #include <kdirlister.h>
2444 #include <QEventLoop>
2445 #include "playlistloader.h"
2446 //this function (C) Copyright 2003-4 Max Howell, (C) Copyright 2004 Mark Kretschmann
2447 KUrl::List
PlaylistBrowser::recurse( const KUrl
&url
)
2449 typedef QMap
<QString
, KUrl
> FileMap
;
2451 KDirLister
lister( false );
2452 lister
.setAutoUpdate( false );
2453 lister
.setAutoErrorHandlingEnabled( false, 0 );
2454 lister
.openUrl( url
);
2456 while( !lister
.isFinished() )
2457 kapp
->processEvents( QEventLoop::ExcludeUserInput
);
2459 KFileItemList items
= lister
.items(); //returns QPtrList, so we MUST only do it once!
2462 oldForeachType( KFileItemList
, items
) {
2463 if( (*it
)->isFile() ) { files
[(*it
)->name()] = (*it
)->url(); continue; }
2464 if( (*it
)->isDir() ) urls
+= recurse( (*it
)->url() );
2467 oldForeachType( FileMap
, files
)
2468 // users often have playlist files that reflect directories
2469 // higher up, or stuff in this directory. Don't add them as
2470 // it produces double entries
2471 if( !PlaylistFile::isPlaylistFile( (*it
).fileName() ) )
2478 void PlaylistBrowser::currentItemChanged( Q3ListViewItem
*item
) //SLOT
2480 // rename remove and delete buttons are disabled if there are no playlists
2481 // rename and delete buttons are disabled for track items
2483 bool enable_remove
= false;
2484 bool enable_rename
= false;
2487 goto enable_buttons
;
2489 if ( isCategory( item
) )
2491 if( static_cast<PlaylistCategory
*>(item
)->isFolder() &&
2492 static_cast<PlaylistCategory
*>(item
)->isKept() )
2493 enable_remove
= enable_rename
= true;
2495 else if ( isPodcastChannel( item
) )
2497 enable_remove
= true;
2498 enable_rename
= false;
2500 else if ( !isPodcastEpisode( item
) )
2501 enable_remove
= enable_rename
= static_cast<PlaylistCategory
*>(item
)->isKept();
2503 static_cast<PlaylistBrowserEntry
*>(item
)->updateInfo();
2507 removeButton
->setEnabled( enable_remove
);
2508 renameButton
->setEnabled( enable_rename
);
2512 void PlaylistBrowser::customEvent( QEvent
*e
)
2514 // If a playlist is found in collection folders it will be automatically added to the playlist browser
2515 // The ScanController sends a PlaylistFoundEvent when a playlist is found.
2517 ScanController::PlaylistFoundEvent
* p
= static_cast<ScanController::PlaylistFoundEvent
*>( e
);
2518 addPlaylist( p
->path(), 0, false, true );
2522 void PlaylistBrowser::slotAddMenu( int id
) //SLOT
2539 ConfigDynamic::dynamicDialog(this);
2545 void PlaylistBrowser::slotAddPlaylistMenu( int id
) //SLOT
2550 createPlaylist( 0/*base cat*/, false/*make empty*/ );
2553 case PLAYLIST_IMPORT
:
2561 ************************
2562 * Context Menu Entries
2563 ************************
2566 void PlaylistBrowser::showContextMenu( Q3ListViewItem
*item
, const QPoint
&p
, int ) //SLOT
2570 PlaylistBrowserEntry
*entry
= dynamic_cast<PlaylistBrowserEntry
*>( item
);
2572 entry
->showContextMenu( p
);
2575 /////////////////////////////////////////////////////////////////////////////
2576 // CLASS PlaylistBrowserView
2577 ////////////////////////////////////////////////////////////////////////////
2579 PlaylistBrowserView::PlaylistBrowserView( QWidget
*parent
, const char *name
)
2580 : K3ListView( parent
)
2583 setObjectName( name
);
2584 addColumn( i18n("Playlists") );
2586 setSelectionMode( Q3ListView::Extended
);
2587 setResizeMode( Q3ListView::AllColumns
);
2588 setShowSortIndicator( true );
2589 setRootIsDecorated( true );
2591 setDropVisualizer( true ); //the visualizer (a line marker) is drawn when dragging over tracks
2592 setDropHighlighter( true ); //and the highligther (a focus rect) is drawn when dragging over playlists
2593 setDropVisualizerWidth( 3 );
2594 setAcceptDrops( true );
2596 setTreeStepSize( 20 );
2598 connect( this, SIGNAL( mouseButtonPressed ( int, Q3ListViewItem
*, const QPoint
&, int ) ),
2599 this, SLOT( mousePressed( int, Q3ListViewItem
*, const QPoint
&, int ) ) );
2601 //TODO moving tracks
2602 //connect( this, SIGNAL( moved(QListViewItem *, QListViewItem *, QListViewItem * )),
2603 // this, SLOT( itemMoved(QListViewItem *, QListViewItem *, QListViewItem * )));
2606 PlaylistBrowserView::~PlaylistBrowserView() { }
2608 void PlaylistBrowserView::contentsDragEnterEvent( QDragEnterEvent
*e
)
2610 e
->accept( e
->source() == viewport() || K3URLDrag::canDecode( e
) );
2613 void PlaylistBrowserView::contentsDragMoveEvent( QDragMoveEvent
* e
)
2615 //Get the closest item _before_ the cursor
2616 const QPoint p
= contentsToViewport( e
->pos() );
2617 Q3ListViewItem
*item
= itemAt( p
);
2623 //only for track items (for playlist items we draw the highlighter)
2624 if( isPlaylistTrackItem( item
) )
2625 item
= item
->itemAbove();
2627 if( item
!= m_marker
)
2631 viewportPaintEvent( 0 );
2635 void PlaylistBrowserView::contentsDragLeaveEvent( QDragLeaveEvent
* )
2641 void PlaylistBrowserView::contentsDropEvent( QDropEvent
*e
)
2643 Q3ListViewItem
*parent
= 0;
2644 Q3ListViewItem
*after
;
2646 const QPoint p
= contentsToViewport( e
->pos() );
2647 Q3ListViewItem
*item
= itemAt( p
);
2653 if( !isPlaylist( item
) )
2654 findDrop( e
->pos(), parent
, after
);
2658 if( e
->source() == this )
2660 moveSelectedItems( item
); // D&D sucks, do it ourselves
2663 KUrl::List decodedList
;
2664 Q3ValueList
<MetaBundle
> bundles
;
2665 if( K3URLDrag::decode( e
, decodedList
) )
2667 KUrl::List::ConstIterator it
= decodedList
.begin();
2668 MetaBundle
first( *it
);
2669 const QString album
= first
.album();
2670 const QString artist
= first
.artist();
2672 int suggestion
= !album
.trimmed().isEmpty() ? 1 : !artist
.trimmed().isEmpty() ? 2 : 3;
2674 for ( ; it
!= decodedList
.end(); ++it
)
2676 if( isCategory(item
) )
2677 { // check if it is podcast category
2678 Q3ListViewItem
*cat
= item
;
2679 while( isCategory(cat
) && cat
!=PlaylistBrowser::instance()->podcastCategory() )
2680 cat
= cat
->parent();
2682 if( cat
== PlaylistBrowser::instance()->podcastCategory() )
2683 PlaylistBrowser::instance()->addPodcast(*it
, item
);
2687 QString filename
= (*it
).fileName();
2689 if( filename
.endsWith("m3u") || filename
.endsWith("pls") )
2690 PlaylistBrowser::instance()->addPlaylist( (*it
).path() );
2691 else //TODO: check canDecode ?
2694 bundles
.append( mb
);
2695 if( suggestion
== 1 && mb
.album()->toLower().trimmed() != album
.toLower().trimmed() )
2697 if( suggestion
== 2 && mb
.artist()->toLower().trimmed() != artist
.toLower().trimmed() )
2702 if( bundles
.isEmpty() ) return;
2704 if( parent
&& isPlaylist( parent
) ) {
2705 //insert the dropped tracks
2706 PlaylistEntry
*playlist
= static_cast<PlaylistEntry
*>( parent
);
2707 playlist
->insertTracks( after
, bundles
);
2709 else //dropped on a playlist item
2711 Q3ListViewItem
*parent
= item
;
2712 bool isPlaylistFolder
= false;
2716 if( parent
== PlaylistBrowser::instance()->m_playlistCategory
)
2718 isPlaylistFolder
= true;
2721 parent
= parent
->parent();
2724 if( isPlaylist( item
) ) {
2725 PlaylistEntry
*playlist
= static_cast<PlaylistEntry
*>( item
);
2726 //append the dropped tracks
2727 playlist
->insertTracks( 0, bundles
);
2729 else if( isCategory( item
) && isPlaylistFolder
)
2731 PlaylistBrowser
*pb
= PlaylistBrowser::instance();
2732 QString title
= suggestion
== 1 ? album
2733 : suggestion
== 2 ? artist
2735 if ( pb
->createPlaylist( item
, false, title
) )
2736 pb
->m_lastPlaylist
->insertTracks( 0, bundles
);
2746 void PlaylistBrowserView::eraseMarker() //SLOT
2751 if( isPlaylist( m_marker
) )
2752 spot
= drawItemHighlighter( 0, m_marker
);
2754 spot
= drawDropVisualizer( 0, 0, m_marker
);
2757 viewport()->repaint( spot
, false );
2761 void PlaylistBrowserView::viewportPaintEvent( QPaintEvent
*e
)
2763 if( e
) K3ListView::viewportPaintEvent( e
); //we call with 0 in contentsDropEvent()
2767 QPainter
painter( viewport() );
2768 if( isPlaylist( m_marker
) ) //when dragging on a playlist we draw a focus rect
2769 drawItemHighlighter( &painter
, m_marker
);
2770 else //when dragging on a track we draw a line marker
2771 painter
.fillRect( drawDropVisualizer( 0, 0, m_marker
),
2772 QBrush( colorGroup().highlight(), Qt::Dense4Pattern
) );
2776 void PlaylistBrowserView::mousePressed( int button
, Q3ListViewItem
*item
, const QPoint
&pnt
, int ) //SLOT
2778 // this function expande/collapse the playlist if the +/- symbol has been pressed
2779 // and show the save menu if the save icon has been pressed
2781 if( !item
|| button
!= Qt::LeftButton
) return;
2783 if( isPlaylist( item
) )
2785 QPoint p
= mapFromGlobal( pnt
);
2786 p
.setY( p
.y() - header()->height() );
2788 QRect itemrect
= itemRect( item
);
2790 QRect expandRect
= QRect( 4, itemrect
.y() + (item
->height()/2) - 5, 15, 15 );
2791 if( expandRect
.contains( p
) ) { //expand symbol clicked
2792 setOpen( item
, !item
->isOpen() );
2798 void PlaylistBrowserView::moveSelectedItems( Q3ListViewItem
*newParent
)
2800 if( !newParent
|| isDynamic( newParent
) || isPodcastChannel( newParent
) ||
2801 isSmartPlaylist( newParent
) || isPodcastEpisode( newParent
) )
2804 #define newParent static_cast<PlaylistBrowserEntry*>(newParent)
2805 if( !newParent
->isKept() )
2809 Q3PtrList
<Q3ListViewItem
> selected
;
2810 Q3ListViewItemIterator
it( this, Q3ListViewItemIterator::Selected
);
2811 for( ; it
.current(); ++it
)
2813 if( !(*it
)->parent() ) //must be a base category we are draggin'
2816 selected
.append( *it
);
2819 Q3ListViewItem
*after
=0;
2820 for( Q3ListViewItem
*item
= selected
.first(); item
; item
= selected
.next() )
2822 Q3ListViewItem
*itemParent
= item
->parent();
2823 if( isPlaylistTrackItem( item
) )
2825 if( isPlaylistTrackItem( newParent
) )
2827 if( !after
&& newParent
!= newParent
->parent()->firstChild() )
2828 after
= newParent
->itemAbove();
2830 newParent
= static_cast<PlaylistEntry
*>(newParent
->parent());
2832 else if( !isPlaylist( newParent
) )
2836 #define newParent static_cast<PlaylistEntry*>(newParent)
2837 newParent
->insertTracks( after
, KUrl::List( static_cast<PlaylistTrackItem
*>(item
)->url() ));
2839 #define itemParent static_cast<PlaylistEntry*>(itemParent)
2840 itemParent
->removeTrack( static_cast<PlaylistTrackItem
*>(item
) );
2844 else if( !isCategory( newParent
) )
2847 Q3ListViewItem
*base
= newParent
;
2848 while( base
->parent() )
2849 base
= base
->parent();
2851 if( base
== PlaylistBrowser::instance()->m_playlistCategory
&& isPlaylist( item
) ||
2852 base
== PlaylistBrowser::instance()->m_streamsCategory
&& isStream( item
) ||
2853 base
== PlaylistBrowser::instance()->m_smartCategory
&& isSmartPlaylist( item
) ||
2854 base
== PlaylistBrowser::instance()->m_dynamicCategory
&& isDynamic( item
) )
2856 itemParent
->takeItem( item
);
2857 newParent
->insertItem( item
);
2858 newParent
->sortChildItems( 0, true );
2860 else if( base
== PlaylistBrowser::instance()->m_podcastCategory
&& isPodcastChannel( item
) )
2862 #define item static_cast<PodcastChannel*>(item)
2863 item
->setParent( static_cast<PlaylistCategory
*>(newParent
) );
2869 void PlaylistBrowserView::rename( Q3ListViewItem
*item
, int c
)
2871 K3ListView::rename( item
, c
);
2873 QRect
rect( itemRect( item
) );
2874 int fieldX
= rect
.x() + treeStepSize() + 2;
2875 int fieldW
= rect
.width() - treeStepSize() - 2;
2877 KLineEdit
*renameEdit
= renameLineEdit();
2878 renameEdit
->setGeometry( fieldX
, rect
.y(), fieldW
, rect
.height() );
2882 void PlaylistBrowserView::keyPressEvent( QKeyEvent
*e
)
2884 switch( e
->key() ) {
2885 case Qt::Key_Space
: //load
2886 PlaylistBrowser::instance()->slotDoubleClicked( currentItem() );
2889 case Qt::ShiftModifier
+Qt::Key_Delete
: //delete
2890 case Qt::Key_Delete
: //remove
2891 PlaylistBrowser::instance()->removeSelectedItems();
2894 case Qt::Key_F2
: //rename
2895 PlaylistBrowser::instance()->renameSelectedItem();
2899 K3ListView::keyPressEvent( e
);
2905 void PlaylistBrowserView::startDrag()
2910 KUrl::List itemList
; // this is for CollectionDB::createDragPixmap()
2911 KUrl::List podList
; // used to add podcast episodes of the same channel in reverse order (usability)
2912 PodcastEpisode
*lastPodcastEpisode
= 0; // keep track of the last podcastepisode we visited.
2913 K3MultipleDrag
*drag
= new K3MultipleDrag( this );
2915 Q3ListViewItemIterator
it( this, Q3ListViewItemIterator::Selected
);
2919 for( ; it
.current(); ++it
)
2921 if( !isPodcastEpisode( *it
) && !podList
.isEmpty() )
2922 { // we left the podcast channel, so append those items we iterated over
2927 if( isPlaylist( *it
) )
2929 urls
+= static_cast<PlaylistEntry
*>(*it
)->url();
2930 itemList
+= static_cast<PlaylistEntry
*>(*it
)->url();
2931 pixText
= (*it
)->text(0);
2934 else if( isStream( *it
) )
2936 urls
+= static_cast<StreamEntry
*>(*it
)->url();
2937 itemList
+= KUrl( "stream://" );
2938 pixText
= (*it
)->text(0);
2941 else if( isLastFm( *it
) )
2943 urls
+= static_cast<LastFmEntry
*>(*it
)->url();
2944 itemList
+= static_cast<LastFmEntry
*>(*it
)->url();
2945 pixText
= (*it
)->text(0);
2948 else if( isPodcastEpisode( *it
) )
2950 if( (*it
)->parent()->isSelected() ) continue;
2951 if( !podList
.isEmpty() && lastPodcastEpisode
&& lastPodcastEpisode
->Q3ListViewItem::parent() != (*it
)->parent() )
2952 { // we moved onto a new podcast channel
2956 #define item static_cast<PodcastEpisode *>(*it)
2957 if( item
->isOnDisk() )
2959 podList
.prepend( item
->localUrl() );
2960 itemList
+= item
->url();
2964 podList
.prepend( item
->url() );
2965 itemList
+= item
->url();
2967 lastPodcastEpisode
= item
;
2968 pixText
= (*it
)->text(0);
2971 else if( isPodcastChannel( *it
) )
2973 #define item static_cast<PodcastChannel *>(*it)
2974 if( !item
->isPolished() )
2977 Q3ListViewItem
*child
= item
->firstChild();
2979 // we add the podcasts in reverse, its much nicer to add them chronologically :)
2982 PodcastEpisode
*pe
= static_cast<PodcastEpisode
*>( child
);
2983 if( pe
->isOnDisk() )
2984 tmp
.prepend( pe
->localUrl() );
2986 tmp
.prepend( pe
->url() );
2987 child
= child
->nextSibling();
2990 itemList
+= KUrl( item
->url().url() );
2991 pixText
= (*it
)->text(0);
2995 else if( isSmartPlaylist( *it
) )
2997 SmartPlaylist
*item
= static_cast<SmartPlaylist
*>( *it
);
2999 if( !item
->query().isEmpty() )
3001 Q3TextDrag
*textdrag
= new Q3TextDrag( item
->text(0) + '\n' + item
->query(), 0 );
3002 textdrag
->setSubtype( "amarok-sql" );
3003 drag
->addDragObject( textdrag
);
3005 itemList
+= KUrl( QString("smartplaylist://%1").arg( item
->text(0) ) );
3006 pixText
= (*it
)->text(0);
3009 else if( isDynamic( *it
) )
3011 DynamicEntry
*item
= static_cast<DynamicEntry
*>( *it
);
3013 // Serialize pointer to string
3014 const QString str
= QString::number( reinterpret_cast<qulonglong
>( item
) );
3016 Q3TextDrag
*textdrag
= new Q3TextDrag( str
, 0 );
3017 textdrag
->setSubtype( "dynamic" );
3018 drag
->addDragObject( textdrag
);
3019 itemList
+= KUrl( QString("dynamic://%1").arg( item
->text(0) ) );
3020 pixText
= (*it
)->text(0);
3023 else if( isPlaylistTrackItem( *it
) )
3025 if( (*it
)->parent()->isSelected() ) continue;
3026 urls
+= static_cast<PlaylistTrackItem
*>(*it
)->url();
3027 itemList
+= static_cast<PlaylistTrackItem
*>(*it
)->url();
3032 if( !podList
.isEmpty() )
3035 if( count
> 1 ) pixText
.clear();
3037 drag
->addDragObject( new K3URLDrag( urls
, viewport() ) );
3038 drag
->setPixmap( CollectionDB::createDragPixmap( itemList
, pixText
),
3039 QPoint( CollectionDB::DRAGPIXMAP_OFFSET_X
, CollectionDB::DRAGPIXMAP_OFFSET_Y
) );
3043 /////////////////////////////////////////////////////////////////////////////
3044 // CLASS PlaylistDialog
3045 ////////////////////////////////////////////////////////////////////////////
3047 QString
PlaylistDialog::getSaveFileName( const QString
&suggestion
, bool proposeOverwriting
) //static
3049 PlaylistDialog dialog
;
3050 if( !suggestion
.isEmpty() )
3052 QString path
= Amarok::saveLocation("playlists/") + "%1" + ".m3u";
3053 if( QFileInfo( path
.arg( suggestion
) ).exists() && !proposeOverwriting
)
3056 while( QFileInfo( path
.arg( i18n( "%1 (%2)", suggestion
, QString::number( n
) ) ) ).exists() )
3058 dialog
.edit
->setText( i18n( "%1 (%2)", suggestion
, QString::number( n
) ) );
3061 dialog
.edit
->setText( suggestion
);
3063 if( dialog
.exec() == Accepted
)
3064 return dialog
.result
;
3068 PlaylistDialog::PlaylistDialog()
3069 : KDialog( MainWindow::self() )
3070 , customChosen( false )
3072 setCaption( i18n( "Save Playlist" ) );
3074 setButtons( Ok
| Cancel
| User1
);
3075 setDefaultButton( Ok
);
3076 showButtonSeparator( false );
3077 setButtonGuiItem( User1
, KGuiItem( i18n( "Save to location..." ) ) ); //KIcon( Amarok::icon( "files" ) )
3080 KVBox
*vbox
= new KVBox( this );
3081 setMainWidget( vbox
);
3083 QLabel
*label
= new QLabel( i18n( "&Enter a name for the playlist:" ), vbox
);
3084 edit
= new KLineEdit( vbox
);
3086 label
->setBuddy( edit
);
3087 enableButtonOk( false );
3088 connect( edit
, SIGNAL( textChanged( const QString
& ) ),
3089 this, SLOT( slotTextChanged( const QString
& ) ) );
3090 connect( this, SIGNAL( user1Clicked() ), SLOT( slotCustomPath() ) );
3091 connect(this,SIGNAL(okClicked()),this,SLOT(slotOk()));
3094 void PlaylistDialog::slotOk()
3096 // TODO Remove this hack for 1.2. It's needed because playlists was a file once.
3097 QString folder
= Amarok::saveLocation( "playlists" );
3098 QFileInfo
info( folder
);
3099 if ( !info
.isDir() ) QFile::remove( folder
);
3101 if( !customChosen
&& !edit
->text().isEmpty() )
3102 result
= Amarok::saveLocation( "playlists/" ) + edit
->text() + ".m3u";
3104 if( !QFileInfo( result
).exists() ||
3105 KMessageBox::warningContinueCancel(
3107 i18n( "A playlist named \"%1\" already exists. Do you want to overwrite it?", edit
->text() ),
3108 i18n( "Overwrite Playlist?" ), KGuiItem( i18n( "Overwrite" ) ) ) == KMessageBox::Continue
)
3110 //KDialog::slotOk();
3111 slotButtonClicked( Ok
);
3115 void PlaylistDialog::slotTextChanged( const QString
&s
)
3117 enableButtonOk( !s
.isEmpty() );
3120 void PlaylistDialog::slotCustomPath()
3122 result
= KFileDialog::getSaveFileName( KUrl( "kfiledialog:///saveplaylists" ), "*.m3u" );
3123 if( !result
.isNull() )
3125 edit
->setText( result
);
3126 edit
->setReadOnly( true );
3127 enableButtonOk( true );
3128 customChosen
= true;
3133 InfoPane::InfoPane( QWidget
*parent
)
3136 m_storedHeight( 100 )
3138 KVBox
*container
= new KVBox( this );
3139 container
->setObjectName( "container" );
3143 KHBox
*box
= new KHBox( container
);
3144 //box->setMargin( 3 );
3145 box
->setBackgroundMode( Qt::PaletteBase
);
3147 m_infoBrowser
= new KHTMLPart( box
);
3149 container
->setFrameStyle( QFrame::StyledPanel
);
3150 container
->setMargin( 3 );
3151 container
->setBackgroundMode( Qt::PaletteBase
);
3154 m_pushButton
= new KPushButton( KGuiItem( i18n("&Show Extended Info"), "info" ), this );
3155 m_pushButton
->setToggleButton( true );
3156 m_pushButton
->setEnabled( m_enable
);
3157 connect( m_pushButton
, SIGNAL(toggled( bool )), SLOT(toggle( bool )) );
3159 //Set the height to fixed. The button shouldn't be resized.
3160 setFixedHeight( m_pushButton
->sizeHint().height() );
3163 InfoPane::~InfoPane()
3165 // Ensure the KHTMLPart dies before its KHTMLView dies,
3166 // because KHTMLPart's dtoring relies on its KHTMLView still being alive
3167 // (see bug 130494).
3168 delete m_infoBrowser
;
3172 InfoPane::getHeight()
3174 if( findChild
<QWidget
*>( "container" )->isShown() )
3176 //If the InfoPane is shown, return true height.
3177 return static_cast<QSplitter
*>( parentWidget() )->sizes().last();
3180 return m_storedHeight
;
3184 InfoPane::setStoredHeight( const int newHeight
) {
3185 m_storedHeight
= newHeight
;
3189 InfoPane::toggle( bool toggled
)
3191 QSplitter
*splitter
= static_cast<QSplitter
*>( parentWidget() );
3195 //Save the height for later
3196 setStoredHeight( splitter
->sizes().last() );
3198 //Set the height to fixed. The button shouldn't be resized.
3199 setFixedHeight( m_pushButton
->sizeHint().height() );
3201 //Now the info pane is not shown, we can disable the button if necessary
3202 m_pushButton
->setEnabled( m_enable
);
3205 setMaximumHeight( ( int )( parentWidget()->height() / 1.5 ) );
3207 //Restore the height of the InfoPane (change the splitter properties)
3208 //Done every time since the pane forgets its height if you try to resize it while the info is hidden.
3209 Q3ValueList
<int> sizes
= splitter
->sizes();
3210 const int sizeOffset
= getHeight() - sizes
.last();
3211 sizes
.first() -= sizeOffset
;
3212 sizes
.last() += sizeOffset
;
3213 splitter
->setSizes( sizes
);
3215 setMinimumHeight( 150 );
3218 findChild
<QWidget
*>( "container" )->setShown( toggled
);
3222 InfoPane::setInfo( const QString
&title
, const QString
&info
)
3224 //If the info pane is not shown, we can enable or disable the button depending on
3225 //whether there is content to show. Otherwise, just remember what we wanted to do
3226 //so we can do it later, when the user does hide the pane.
3227 m_enable
= !( info
.isEmpty() && title
.isEmpty() );
3228 if ( !findChild
<QWidget
*>("container")->isShown() )
3229 m_pushButton
->setEnabled( m_enable
);
3231 if( m_pushButton
->isOn() )
3232 toggle( !(info
.isEmpty() && title
.isEmpty()) );
3234 m_infoBrowser
->write(
3236 QString( "<div id='extended_box' class='box'>"
3237 "<div id='extended_box-header-title' class='box-header'>"
3238 "<span id='extended_box-header-title' class='box-header-title'>"
3242 "<table id='extended_box-table' class='box-body' width='100%' cellpadding='0' cellspacing='0'>"
3244 "<td id='extended_box-information-td'>"
3249 "</div>" ).arg( title
, info
) :
3253 #include "playlistbrowser.moc"