Fix list index out of bounds crash on loading of .pls playlists, thanks to Tim Beaule...
[amarok.git] / src / CoverManager.cpp
blobf85c977e6c792aa76da10437e4438684d40a0e19
1 // (c) Pierpaolo Di Panfilo 2004
2 // (c) 2005 Isaiah Damron <xepo@trifault.net>
3 // See COPYING file for licensing information
5 #include "CoverManager.h"
7 #include "amarok.h"
8 #include "amarokconfig.h"
9 #include "collection/BlockingQuery.h"
10 #include "collection/Collection.h"
11 #include "CollectionManager.h"
12 #include "browserToolBar.h"
13 #include "debug.h"
14 #include "meta/Meta.h"
15 #include "QueryMaker.h"
16 #include "config-amarok.h"
17 #include "PixmapViewer.h"
18 #include "playlist/PlaylistModel.h"
20 #include "TheInstances.h"
22 #include <k3multipledrag.h>
23 #include <k3urldrag.h>
24 #include <KApplication>
25 #include <KConfig>
26 #include <KCursor>
27 #include <KFileDialog>
28 #include <KIconLoader>
29 #include <KIO/NetAccess>
30 #include <KLineEdit>
31 #include <KLocale>
32 #include <KMenu> //showCoverMenu()
33 #include <KMessageBox> //showCoverMenu()
34 #include <KPushButton>
35 #include <KSqueezedTextLabel> //status label
36 #include <KStandardDirs> //KGlobal::dirs()
37 #include <KStatusBar>
38 #include <KToolBar>
39 #include <KUrl>
40 #include <KVBox>
41 #include <KWindowSystem>
43 #include <QDesktopWidget> //ctor: desktop size
44 #include <QFile>
45 #include <QFontMetrics> //paintItem()
46 #include <QImage>
47 #include <QLabel>
48 #include <QLayout>
49 #include <QObject> //used to delete all cover fetchers
50 #include <QPainter> //paintItem()
51 #include <QPalette> //paintItem()
52 #include <QPixmap>
53 #include <QPoint>
54 #include <QProgressBar>
55 #include <QProgressDialog>
56 #include <QRect>
57 #include <QStringList>
58 #include <QToolTip>
59 #include <QTimer> //search filter timer
60 //Added by qt3to4:
61 #include <QDropEvent>
62 #include <QToolButton>
63 #include <QTreeWidget>
64 #include <QTreeWidgetItem>
66 static QString artistToSelectInInitFunction;
67 CoverManager *CoverManager::s_instance = 0;
69 class ArtistItem : public QTreeWidgetItem
71 public:
72 ArtistItem(QTreeWidget *parent, Meta::ArtistPtr artist )
73 : QTreeWidgetItem( parent )
74 , m_artist( artist )
75 { setText( 0, artist->prettyName() ); }
76 ArtistItem(const QString &text, QTreeWidget *parent = 0 )
77 : QTreeWidgetItem( parent )
78 , m_artist( 0 ) { setText( 0, text ); }
80 Meta::ArtistPtr artist() const { return m_artist; }
82 private:
83 Meta::ArtistPtr m_artist;
86 CoverManager::CoverManager()
87 : QSplitter( 0 )
88 , m_timer( new QTimer( this ) ) //search filter timer
89 , m_fetchingCovers( 0 )
90 , m_coversFetched( 0 )
91 , m_coverErrors( 0 )
93 DEBUG_BLOCK
95 setObjectName( "TheCoverManager" );
97 s_instance = this;
99 // Sets caption and icon correctly (needed e.g. for GNOME)
100 kapp->setTopWidget( this );
101 setWindowTitle( KDialog::makeStandardCaption( i18n("Cover Manager") ) );
102 setAttribute( Qt::WA_DeleteOnClose );
103 setContentsMargins( 4, 4, 4, 4 );
105 //artist listview
106 m_artistView = new QTreeWidget( this );
107 m_artistView->setHeaderLabel( i18n( "Albums By" ) );
108 m_artistView->setSortingEnabled( false );
109 m_artistView->setTextElideMode( Qt::ElideRight );
110 m_artistView->setMinimumWidth( 140 );
111 m_artistView->setColumnCount( 1 );
113 setSizes( QList<int>() << 120 << width() - 120 );
115 ArtistItem *item = 0;
116 QList<QTreeWidgetItem*> items;
117 item = new ArtistItem( i18n( "All Artists" ) );
118 item->setIcon(0, SmallIcon( Amarok::icon( "album" ) ) );
119 items.append( item );
121 Collection *coll;
122 foreach( coll, CollectionManager::instance()->collections() )
123 if( coll->collectionId() == "localCollection" )
124 break;
125 QueryMaker *qm = coll->queryMaker();
126 qm->startArtistQuery();
127 BlockingQuery bq( qm );
128 bq.startQuery();
129 Meta::ArtistList artists = bq.artists( coll->collectionId() );
130 foreach( Meta::ArtistPtr artist, artists )
132 item = new ArtistItem( m_artistView, artist );
133 item->setIcon( 0, SmallIcon( Amarok::icon( "artist" ) ) );
134 items.append( item );
136 m_artistView->insertTopLevelItems( 0, items );
140 //TODO: Port
141 // ArtistItem *last = static_cast<ArtistItem *>(m_artistView->item( m_artistView->count() - 1));
142 // QueryBuilder qb;
143 // qb.addReturnValue( QueryBuilder::tabAlbum, QueryBuilder::valName );
144 // qb.groupBy( QueryBuilder::tabAlbum, QueryBuilder::valName );
145 // qb.setOptions( QueryBuilder::optOnlyCompilations );
146 // qb.setLimit( 0, 1 );
147 // if ( qb.run().count() ) {
148 // item = new ArtistItem( m_artistView, last, i18n( "Various Artists" ) );
149 // item->setPixmap( 0, SmallIcon("personal") );
150 // }
152 KVBox *vbox = new KVBox( this );
153 KHBox *hbox = new KHBox( vbox );
155 vbox->setSpacing( 4 );
156 hbox->setSpacing( 4 );
158 { //<Search LineEdit>
159 // KHBox *searchBox = new KHBox( hbox );
160 m_searchEdit = new KLineEdit( hbox );
161 m_searchEdit->setClickMessage( i18n( "Enter search terms here" ) );
162 m_searchEdit->setFrame( QFrame::Sunken );
164 m_searchEdit->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Minimum);
165 m_searchEdit->setClearButtonShown( true );
167 m_searchEdit->setToolTip( i18n( "Enter space-separated terms to search in the albums" ) );
169 hbox->setStretchFactor( m_searchEdit, 1 );
170 } //</Search LineEdit>
172 // view menu
173 m_viewMenu = new KMenu( this );
174 m_selectAllAlbums = m_viewMenu->addAction( i18n("All Albums"), this, SLOT( slotShowAllAlbums() ) );
175 m_selectAlbumsWithCover = m_viewMenu->addAction( i18n("Albums With Cover"), this, SLOT( slotShowAlbumsWithCover() ) );
176 m_selectAlbumsWithoutCover = m_viewMenu->addAction( i18n("Albums Without Cover"), this, SLOT( slotShowAlbumsWithoutCover() ) );
178 QActionGroup *viewGroup = new QActionGroup( this );
179 viewGroup->setExclusive( true );
180 viewGroup->addAction( m_selectAllAlbums );
181 viewGroup->addAction( m_selectAlbumsWithCover );
182 viewGroup->addAction( m_selectAlbumsWithoutCover );
183 m_selectAllAlbums->setChecked( true );
185 // amazon locale menu
186 QString locale = AmarokConfig::amazonLocale();
187 m_currentLocale = CoverFetcher::localeStringToID( locale );
189 QAction *a;
190 QActionGroup *localeGroup = new QActionGroup( this );
191 localeGroup->setExclusive( true );
193 m_amazonLocaleMenu = new KMenu( this );
195 a = m_amazonLocaleMenu->addAction( i18n("International"), this, SLOT( slotSetLocaleIntl() ) );
196 if( m_currentLocale == CoverFetcher::International ) a->setChecked( true );
197 localeGroup->addAction( a );
199 a = m_amazonLocaleMenu->addAction( i18n("Canada"), this, SLOT( slotSetLocaleCa() ) );
200 if( m_currentLocale == CoverFetcher::Canada ) a->setChecked( true );
201 localeGroup->addAction( a );
203 a = m_amazonLocaleMenu->addAction( i18n("France"), this, SLOT( slotSetLocaleFr() ) );
204 if( m_currentLocale == CoverFetcher::France ) a->setChecked( true );
205 localeGroup->addAction( a );
207 a = m_amazonLocaleMenu->addAction( i18n("Germany"), this, SLOT( slotSetLocaleDe() ) );
208 if( m_currentLocale == CoverFetcher::Germany ) a->setChecked( true );
209 localeGroup->addAction( a );
211 a = m_amazonLocaleMenu->addAction( i18n("Japan"), this, SLOT( slotSetLocaleJp() ) );
212 if( m_currentLocale == CoverFetcher::Japan ) a->setChecked( true );
213 localeGroup->addAction( a );
215 a = m_amazonLocaleMenu->addAction( i18n("United Kingdom"), this, SLOT( slotSetLocaleUk() ) );
216 if( m_currentLocale == CoverFetcher::UK ) a->setChecked( true );
217 localeGroup->addAction( a );
219 KToolBar* toolBar = new KToolBar( hbox );
220 toolBar->setToolButtonStyle( Qt::ToolButtonTextBesideIcon );
222 QAction* viewMenuAction = new QAction( KIcon( Amarok::icon( "view_choose" ) ), i18nc( "@title buttontext for popup-menu", "View" ), this );
223 viewMenuAction->setMenu( m_viewMenu );
224 toolBar->addAction( viewMenuAction );
227 QAction* localeMenuAction = new QAction( KIcon( Amarok::icon( "babelfish" ) ), i18n( "Amazon Locale" ), this );
228 localeMenuAction->setMenu( m_amazonLocaleMenu );
229 toolBar->addAction( localeMenuAction );
232 //fetch missing covers button
233 m_fetchButton = new KPushButton( KGuiItem( i18n("Fetch Missing Covers"), Amarok::icon( "download" ) ), hbox );
234 connect( m_fetchButton, SIGNAL(clicked()), SLOT(fetchMissingCovers()) );
237 //cover view
238 m_coverView = new CoverView( vbox );
240 //status bar
241 KStatusBar *m_statusBar = new KStatusBar( vbox );
243 m_statusLabel = new KSqueezedTextLabel( m_statusBar );
244 m_statusLabel->setIndent( 3 );
245 m_progressBox = new KHBox( m_statusBar );
247 m_statusBar->addWidget( m_statusLabel, 4 );
248 m_statusBar->addPermanentWidget( m_progressBox, 1 );
250 KPushButton *stopButton = new KPushButton( KGuiItem(i18n("Abort"), "stop"), m_progressBox );
251 connect( stopButton, SIGNAL(clicked()), SLOT(stopFetching()) );
252 m_progress = new QProgressBar( m_progressBox );
253 m_progress->setTextVisible( true );
255 const int h = m_statusLabel->height() + 3;
256 m_statusLabel->setFixedHeight( h );
257 m_progressBox->setFixedHeight( h );
258 m_progressBox->hide();
261 // signals and slots connections
262 connect( m_artistView, SIGNAL(itemSelectionChanged() ),
263 SLOT( slotArtistSelected() ) );
264 /* connect( m_coverView, SIGNAL(contextMenuRequested( Q3IconViewItem*, const QPoint& )),
265 SLOT(showCoverMenu( Q3IconViewItem*, const QPoint& )) );*/
266 connect( m_coverView, SIGNAL(itemActivated( QListWidgetItem* )),
267 SLOT(coverItemExecuted( QListWidgetItem* )) );
268 connect( m_timer, SIGNAL(timeout()),
269 SLOT(slotSetFilter()) );
270 connect( m_searchEdit, SIGNAL(textChanged( const QString& )),
271 SLOT(slotSetFilterTimeout()) );
273 m_currentView = AllAlbums;
275 QSize size = QApplication::desktop()->screenGeometry( this ).size() / 1.5;
276 QSize sz = Amarok::config( "Cover Manager" ).readEntry( "Window Size", size );
277 resize( sz.width(), sz.height() );
279 show();
281 m_fetcher = The::coverFetcher();
283 QTimer::singleShot( 0, this, SLOT(init()) );
287 CoverManager::~CoverManager()
289 DEBUG_BLOCK
291 Amarok::config( "Cover Manager" ).writeEntry( "Window Size", size() );
293 s_instance = 0;
297 void CoverManager::init()
299 DEBUG_BLOCK
301 QTreeWidgetItem *item = 0;
303 int i = 0;
304 if ( !artistToSelectInInitFunction.isEmpty() )
305 for( item = m_artistView->invisibleRootItem()->child( 0 );
306 i < m_artistView->invisibleRootItem()->childCount();
307 item = m_artistView->invisibleRootItem()->child( i++ ) )
308 if ( item->text( 0 ) == artistToSelectInInitFunction )
309 break;
311 if ( item == 0 )
312 item = m_artistView->invisibleRootItem()->child( 0 );
314 item->setSelected( true );
318 CoverViewDialog::CoverViewDialog( Meta::AlbumPtr album, QWidget *parent )
319 : QDialog( parent, Qt::WType_TopLevel | Qt::WNoAutoErase )
321 m_pixmap = album->image( 0 ); // full sized image
322 setAttribute( Qt::WA_DeleteOnClose );
324 #ifdef Q_WS_X11
325 KWindowSystem::setType( winId(), NET::Utility );
326 #endif
328 kapp->setTopWidget( this );
329 setWindowTitle( KDialog::makeStandardCaption( i18n("%1 - %2",
330 album->albumArtist()->prettyName(), album->prettyName() ) ) );
332 m_layout = new QHBoxLayout( this );
333 m_layout->setSizeConstraint( QLayout::SetFixedSize );
334 m_pixmapViewer = new PixmapViewer( this, m_pixmap );
335 m_layout->addWidget( m_pixmapViewer );
339 void CoverManager::viewCover( Meta::AlbumPtr album, QWidget *parent ) //static
341 //QDialog means "escape" works as expected
342 QDialog *dialog = new CoverViewDialog( album, parent );
343 dialog->show();
347 QString CoverManager::amazonTld() //static
349 if( AmarokConfig::amazonLocale() == "us" )
350 return "com";
351 else if( AmarokConfig::amazonLocale()== "jp" )
352 return "co.jp";
353 else if( AmarokConfig::amazonLocale() == "uk" )
354 return "co.uk";
355 else if( AmarokConfig::amazonLocale() == "ca" )
356 return "ca";
357 else
358 return AmarokConfig::amazonLocale();
361 void
362 CoverManager::metadataChanged( Meta::Album* album )
364 DEBUG_BLOCK
366 ArtistItem *selectedItem = static_cast<ArtistItem*>(m_artistView->selectedItems().first());
367 if( selectedItem->text( 0 ) != i18n( "All Artists" ) )
369 if ( album->albumArtist() != selectedItem->artist() )
370 return;
372 else
374 foreach( CoverViewItem *item, m_coverItems )
376 if( album->name() == item->albumPtr()->name() )
377 item->loadCover();
379 // Update have/missing count.
380 updateStatusBar();
384 void CoverManager::fetchMissingCovers() //SLOT
386 DEBUG_BLOCK
388 int i = 0;
389 for ( QListWidgetItem *item = m_coverView->item( i );
390 i < m_coverView->count();
391 item = m_coverView->item( i++ ) )
393 CoverViewItem *coverItem = static_cast<CoverViewItem*>( item );
394 if( !coverItem->hasCover() ) {
395 m_fetchCovers += coverItem->albumPtr();
399 m_fetcher->queueAlbums( m_fetchCovers );
401 updateStatusBar();
402 m_fetchButton->setEnabled( false );
406 void CoverManager::showOnce( const QString &artist )
408 if ( !s_instance ) {
409 artistToSelectInInitFunction = artist;
410 new CoverManager(); //shows itself
412 else {
413 s_instance->activateWindow();
414 s_instance->raise();
418 void CoverManager::slotArtistSelected() //SLOT
420 QTreeWidgetItem *item = m_artistView->selectedItems().first();
421 ArtistItem *artistItem = static_cast< ArtistItem* >(item);
422 Meta::ArtistPtr artist = artistItem->artist();
424 //TODO: port?
425 // if( artist->prettyName().endsWith( ", The" ) )
426 // Amarok::manipulateThe( artist->prettyName(), false );
428 m_coverView->clear();
429 m_coverItems.clear();
431 // reset current view mode state to "AllAlbum" which is the default on artist change in left panel
432 m_currentView = AllAlbums;
433 m_selectAllAlbums->trigger();
434 m_selectAllAlbums->setChecked( true );
436 QProgressDialog progress( this );
437 progress.setLabelText( i18n("Loading Thumbnails...") );
438 progress.setWindowModality( Qt::WindowModal );
440 //this is an extra processEvent call for the sake of init() and aesthetics
441 //it isn't necessary
442 kapp->processEvents();
444 //this can be a bit slow
445 QApplication::setOverrideCursor( Qt::WaitCursor );
447 Meta::AlbumList albums;
449 Collection *coll;
450 foreach( coll, CollectionManager::instance()->collections() )
451 if( coll->collectionId() == "localCollection" )
452 break;
453 QueryMaker *qm = coll->queryMaker();
454 if ( item != m_artistView->invisibleRootItem()->child( 0 ) )
455 qm->addMatch( artist );
457 qm->startAlbumQuery();
458 BlockingQuery bq( qm );
459 bq.startQuery();
460 albums = bq.albums( coll->collectionId() );
463 //TODO: Port 2.0
464 //also retrieve compilations when we're showing all items (first treenode) or
465 //"Various Artists" (last treenode)
466 // if ( item == m_artistView->firstChild() || item == m_artistView->lastChild() )
467 // {
468 // QStringList cl;
470 // qb.clear();
471 // qb.addReturnValue( QueryBuilder::tabAlbum, QueryBuilder::valName );
473 // qb.excludeMatch( QueryBuilder::tabAlbum, i18n( "Unknown" ) );
474 // qb.sortBy( QueryBuilder::tabAlbum, QueryBuilder::valName );
475 // qb.setOptions( QueryBuilder::optRemoveDuplicates );
476 // qb.setOptions( QueryBuilder::optOnlyCompilations );
477 // cl = qb.run();
479 // for( int i = 0; i < cl.count(); i++ ) {
480 // albums.append( i18n( "Various Artists" ) );
481 // albums.append( cl[ i ] );
482 // }
483 // }
485 QApplication::restoreOverrideCursor();
487 progress.setMaximum( albums.count() );
489 //insert the covers first because the list view is soooo paint-happy
490 //doing it in the second loop looks really bad, unfortunately
491 //this is the slowest step in the bit that we can't process events
492 uint x = 0;
493 foreach( Meta::AlbumPtr album, albums )
495 m_coverItems.append( new CoverViewItem( m_coverView, album ) );
497 if ( ++x % 50 == 0 ) {
498 progress.setValue( x );
499 kapp->processEvents(); // QProgressDialog also calls this, but not always due to Qt bug!
501 //only worth testing for after processEvents() is called
502 if( progress.wasCanceled() )
503 break;
507 //now, load the thumbnails
508 QList<QListWidgetItem*> items;
509 int i = 0;
510 for ( QListWidgetItem *item = m_coverView->item( i );
511 i < m_coverView->count();
512 item = m_coverView->item( i++ ) )
514 progress.setValue( progress.value() + 1 );
515 kapp->processEvents();
517 if( progress.wasCanceled() )
518 break;
520 static_cast<CoverViewItem*>(item)->loadCover();
523 updateStatusBar();
526 void CoverManager::showCoverMenu( QListWidgetItem *item, const QPoint &p ) //SLOT
528 Q_UNUSED( item );
529 Q_UNUSED( p );
530 //TODO: PORT
531 #if 0
532 #define item static_cast<CoverViewItem*>(item)
533 if( !item ) return;
535 KMenu menu;
537 menu.addTitle( i18n( "Cover Image" ) );
539 QList<CoverViewItem*> selected = selectedItems();
540 const int nSelected = selected.count();
542 QAction* fetchSelectedAction = new QAction( KIcon( Amarok::icon( "download" ) )
543 , ( nSelected == 1 ? i18n( "&Fetch From amazon.%1", CoverManager::amazonTld() )
544 : i18n( "&Fetch Selected Covers" ) )
545 , &menu );
546 connect( fetchSelectedAction, SIGNAL( triggered() ), this, SLOT( fetchSelectedCovers() ) );
548 QAction* setCustomAction = new QAction( KIcon( Amarok::icon( "files" ) )
549 , i18np( "Set &Custom Cover", "Set &Custom Cover for Selected Albums", nSelected )
550 , &menu );
551 connect( setCustomAction, SIGNAL( triggered() ), this, SLOT( setCustomSelectedCovers() ) );
552 QAction* unsetAction = new QAction( KIcon( Amarok::icon( "remove" ) ), i18np( "&Unset Cover", "&Unset Selected Covers", nSelected ), &menu );
553 connect( unsetAction, SIGNAL( triggered() ), this, SLOT ( deleteSelectedCovers() ) );
555 QAction* playAlbumAction = new QAction( KIcon( Amarok::icon( "add_playlist" ) )
556 , i18np( "&Append to Playlist", "&Append Selected Albums to Playlist", nSelected )
557 , &menu );
559 connect( playAlbumAction, SIGNAL( triggered() ), this, SLOT( playSelectedAlbums() ) );
561 if( nSelected > 1 ) {
562 menu.addAction( fetchSelectedAction );
563 menu.addAction( setCustomAction );
564 menu.addAction( playAlbumAction );
565 menu.addAction( unsetAction );
567 else {
568 QAction* viewAction = new QAction( KIcon( Amarok::icon( "zoom" ) ), i18n( "&Show Fullsize" ), &menu );
569 connect( viewAction, SIGNAL( triggered() ), this, SLOT( viewSelectedCover() ) );
570 viewAction ->setEnabled( item->hasCover() );
571 unsetAction->setEnabled( item->canRemoveCover() );
572 menu.addAction( viewAction );
573 menu.addAction( fetchSelectedAction );
574 menu.addAction( setCustomAction );
575 menu.addAction( playAlbumAction );
576 menu.addSeparator();
578 menu.addAction( unsetAction );
581 menu.exec( p );
583 #undef item
584 #endif
587 void CoverManager::viewSelectedCover()
589 CoverViewItem* item = selectedItems().first();
590 viewCover( item->albumPtr(), this );
593 void CoverManager::coverItemExecuted( QListWidgetItem *item ) //SLOT
595 #define item static_cast<CoverViewItem*>(item)
597 if( !item ) return;
599 item->setSelected( true );
600 if ( item->hasCover() )
601 viewCover( item->albumPtr(), this );
602 else
603 m_fetcher->manualFetch( item->albumPtr() );
605 #undef item
609 void CoverManager::slotSetFilter() //SLOT
611 m_filter = m_searchEdit->text();
613 m_coverView->clearSelection();
614 uint i = 0;
615 QListWidgetItem *item = m_coverView->item( i );
616 while ( item )
618 QListWidgetItem *tmp = m_coverView->item( i + 1 );
619 m_coverView->takeItem( i );
620 item = tmp;
623 // m_coverView->setAutoArrange( false );
624 foreach( QListWidgetItem *item, m_coverItems )
626 CoverViewItem *coverItem = static_cast<CoverViewItem*>(item);
627 if( coverItem->album().contains( m_filter, Qt::CaseInsensitive ) || coverItem->artist().contains( m_filter, Qt::CaseInsensitive ) )
628 m_coverView->insertItem( m_coverView->count() - 1, item );
630 // m_coverView->setAutoArrange( true );
632 // m_coverView->arrangeItemsInGrid();
633 updateStatusBar();
637 void CoverManager::slotSetFilterTimeout() //SLOT
639 if ( m_timer->isActive() ) m_timer->stop();
640 m_timer->setSingleShot( true );
641 m_timer->start( 180 );
644 void CoverManager::changeView( int id ) //SLOT
646 if( m_currentView == id ) return;
648 //clear the iconview without deleting items
649 m_coverView->clearSelection();
650 QListWidgetItem *item = m_coverView->item( 0 );
651 uint i = 0;
652 while ( item ) {
653 m_coverView->takeItem( i );
654 i++;
657 // m_coverView->setAutoArrange(false );
658 foreach( QListWidgetItem *item, m_coverItems )
660 bool show = false;
661 CoverViewItem *coverItem = static_cast<CoverViewItem*>(item);
662 if( !m_filter.isEmpty() ) {
663 if( !coverItem->album().contains( m_filter, Qt::CaseInsensitive ) && !coverItem->artist().contains( m_filter, Qt::CaseInsensitive ) )
664 continue;
667 if( id == AllAlbums ) //show all albums
668 show = true;
669 else if( id == AlbumsWithCover && coverItem->hasCover() ) //show only albums with cover
670 show = true;
671 else if( id == AlbumsWithoutCover && !coverItem->hasCover() ) //show only albums without cover
672 show = true;
674 if( show ) m_coverView->insertItem( m_coverView->count() - 1, item );
676 // m_coverView->setAutoArrange( true );
678 // m_coverView->arrangeItemsInGrid();
679 m_currentView = id;
682 void CoverManager::changeLocale( int id ) //SLOT
684 QString locale = CoverFetcher::localeIDToString( id );
685 AmarokConfig::setAmazonLocale( locale );
686 m_currentLocale = id;
690 void CoverManager::coverFetched( const QString &artist, const QString &album ) //SLOT
692 loadCover( artist, album );
693 m_coversFetched++;
694 updateStatusBar();
698 void CoverManager::coverRemoved( const QString &artist, const QString &album ) //SLOT
700 loadCover( artist, album );
701 m_coversFetched--;
702 updateStatusBar();
706 void CoverManager::coverFetcherError()
708 DEBUG_FUNC_INFO
710 m_coverErrors++;
711 updateStatusBar();
715 void CoverManager::stopFetching()
717 Debug::Block block( __PRETTY_FUNCTION__ );
718 updateStatusBar();
721 // PRIVATE
723 void CoverManager::loadCover( const QString &artist, const QString &album )
725 foreach( QListWidgetItem *item, m_coverItems )
727 CoverViewItem *coverItem = static_cast<CoverViewItem*>(item);
728 if ( album == coverItem->album() && ( artist == coverItem->artist() || ( artist.isEmpty() && coverItem->artist().isEmpty() ) ) )
730 coverItem->loadCover();
731 return;
736 void CoverManager::setCustomSelectedCovers()
738 //function assumes something is selected
739 CoverViewItem* first = selectedItems().first();
741 Meta::TrackPtr track = first->albumPtr()->tracks().first();
742 QString startPath;
743 if( track )
745 KUrl url = track->playableUrl();
746 startPath = url.directory();
748 KUrl file = KFileDialog::getImageOpenUrl( startPath, this, i18n( "Select Cover Image File" ) );
749 if ( !file.isEmpty() ) {
750 kapp->processEvents(); //it may takes a while so process pending events
751 QString tmpFile;
752 QImage image( file.fileName() );
753 foreach( CoverViewItem *item, selectedItems() )
755 item->albumPtr()->setImage( image );
756 item->loadCover();
758 KIO::NetAccess::removeTempFile( tmpFile );
762 void CoverManager::fetchSelectedCovers()
764 foreach( CoverViewItem *item, selectedItems() )
765 m_fetchCovers += item->albumPtr();
767 m_fetchingCovers += selectedItems().count();
769 m_fetcher->queueAlbums( m_fetchCovers );
771 updateStatusBar();
775 void CoverManager::deleteSelectedCovers()
777 QList<CoverViewItem*> selected = selectedItems();
779 int button = KMessageBox::warningContinueCancel( this,
780 i18np( "Are you sure you want to remove this cover from the Collection?",
781 "Are you sure you want to delete these %1 covers from the Collection?",
782 selected.count() ),
783 QString(),
784 KStandardGuiItem::del() );
786 if ( button == KMessageBox::Continue ) {
787 foreach( CoverViewItem *item, selected ) {
788 kapp->processEvents();
789 if( item->albumPtr()->canUpdateImage() )
791 item->albumPtr()->setImage( QImage() );
792 coverRemoved( item->artist(), item->album() );
799 void CoverManager::playSelectedAlbums()
801 Collection *coll;
802 foreach( coll, CollectionManager::instance()->collections() )
803 if( coll->collectionId() == "localCollection" )
804 break;
805 QueryMaker *qm = coll->queryMaker();
806 foreach( CoverViewItem *item, selectedItems() )
808 qm->addMatch( item->albumPtr() );
810 The::playlistModel()->insertOptioned( qm, Playlist::Append );
813 QList<CoverViewItem*> CoverManager::selectedItems()
815 QList<CoverViewItem*> selectedItems;
816 foreach( QListWidgetItem *item, m_coverView->selectedItems() )
817 selectedItems.append( static_cast<CoverViewItem*>(item) );
819 return selectedItems;
823 void CoverManager::updateStatusBar()
825 QString text;
827 //cover fetching info
828 if( m_fetchingCovers ) {
829 //update the progress bar
830 m_progress->setMaximum( m_fetchingCovers );
831 m_progress->setValue( m_coversFetched + m_coverErrors );
832 if( m_progressBox->isHidden() )
833 m_progressBox->show();
835 //update the status text
836 if( m_coversFetched + m_coverErrors >= m_progress->value() ) {
837 //fetching finished
838 text = i18n( "Finished." );
839 if( m_coverErrors )
840 text += i18np( " Cover not found", " <b>%1</b> covers not found", m_coverErrors );
841 //reset counters
842 m_fetchingCovers = 0;
843 m_coversFetched = 0;
844 m_coverErrors = 0;
845 QTimer::singleShot( 2000, this, SLOT( updateStatusBar() ) );
848 if( m_fetchingCovers == 1 ) {
849 foreach( Meta::AlbumPtr album, m_fetchCovers )
851 if( album->albumArtist()->prettyName().isEmpty() )
852 text = i18n( "Fetching cover for %1..." , album->prettyName() );
853 else
854 text = i18n( "Fetching cover for %1 - %2...",
855 album->albumArtist()->prettyName(),
856 album->prettyName() );
859 else if( m_fetchingCovers ) {
860 text = i18np( "Fetching 1 cover: ", "Fetching <b>%1</b> covers... : ", m_fetchingCovers );
861 if( m_coversFetched )
862 text += i18np( "1 fetched", "%1 fetched", m_coversFetched );
863 if( m_coverErrors ) {
864 if( m_coversFetched ) text += i18n(" - ");
865 text += i18np( "1 not found", "%1 not found", m_coverErrors );
867 if( m_coversFetched + m_coverErrors == 0 )
868 text += i18n( "Connecting..." );
871 else {
872 m_coversFetched = 0;
873 m_coverErrors = 0;
875 uint totalCounter = 0, missingCounter = 0;
877 if( m_progressBox->isVisible() )
878 m_progressBox->hide();
880 //album info
881 int i = 0;
882 for( QListWidgetItem *item = m_coverView->item( i );
883 i < m_coverView->count();
884 item = m_coverView->item( i++ ) )
886 totalCounter++;
887 if( !static_cast<CoverViewItem*>( item )->hasCover() )
889 missingCounter++; //counter for albums without cover
893 if( !m_filter.isEmpty() )
894 text = i18np( "1 result for \"%2\"", "%1 results for \"%2\"", totalCounter, m_filter );
895 else if( m_artistView->selectedItems().count() > 0 ) {
896 text = i18np( "1 album", "%1 albums", totalCounter );
897 if( m_artistView->selectedItems().first() != m_artistView->invisibleRootItem()->child( 0 ) ) //showing albums by an artist
899 QString artist = m_artistView->selectedItems().first()->text(0);
900 if( artist.endsWith( ", The" ) )
901 Amarok::manipulateThe( artist, false );
902 text += i18n( " by " ) + artist;
906 if( missingCounter )
907 text += i18n(" - ( <b>%1</b> without cover )", missingCounter );
909 m_fetchButton->setEnabled( missingCounter );
912 m_statusLabel->setText( text );
915 void CoverManager::setStatusText( QString text )
917 m_oldStatusText = m_statusLabel->text();
918 m_statusLabel->setText( text );
920 //////////////////////////////////////////////////////////////////////
921 // CLASS CoverView
922 /////////////////////////////////////////////////////////////////////
924 CoverView::CoverView( QWidget *parent, const char *name, Qt::WFlags f )
925 : QListWidget( parent )
927 Debug::Block block( __PRETTY_FUNCTION__ );
929 setObjectName( name );
930 setWindowFlags( f );
931 setViewMode( QListView::IconMode );
932 setMovement( QListView::Static );
933 setResizeMode( QListView::Adjust );
934 setSelectionMode( QAbstractItemView::ExtendedSelection );
935 setWrapping( true );
936 setSpacing( 4 );
937 setWordWrap( true );
938 setIconSize( QSize(100,100) );
939 setTextElideMode( Qt::ElideRight );
940 setMouseTracking( true );
942 // as long as QIconView only shows tooltips when the cursor is over the
943 // icon (and not the text), we have to create our own tooltips
944 // setShowToolTips( false );
946 connect( this, SIGNAL( itemEntered( QListWidgetItem * ) ), SLOT( setStatusText( QListWidgetItem * ) ) );
947 connect( this, SIGNAL( viewportEntered() ), CoverManager::instance(), SLOT( updateStatusBar() ) );
951 //TODO: PORT 2.0
952 // Q3DragObject *CoverView::dragObject()
953 // {
954 // CoverViewItem *item = static_cast<CoverViewItem*>( currentItem() );
955 // if( !item )
956 // return 0;
958 // const QString sql = "SELECT tags.url FROM tags, album WHERE album.name %1 AND tags.album = album.id ORDER BY tags.track;";
959 // const QStringList values = CollectionDB::instance()->query( sql.arg( CollectionDB::likeCondition( item->album() ) ) );
961 // KUrl::List urls;
962 // for( QStringList::ConstIterator it = values.begin(), end = values.end(); it != end; ++it )
963 // urls += *it;
965 // QString imagePath = CollectionDB::instance()->albumImage( item->artist(), item->album(), false, 1 );
966 // K3MultipleDrag *drag = new K3MultipleDrag( this );
967 // drag->setPixmap( item->coverPixmap() );
968 // drag->addDragObject( new Q3IconDrag( this ) );
969 // drag->addDragObject( new Q3ImageDrag( QImage( imagePath ) ) );
970 // drag->addDragObject( new K3URLDrag( urls ) );
972 // return drag;
973 // }
975 void CoverView::setStatusText( QListWidgetItem *item )
977 #define item static_cast<CoverViewItem *>( item )
978 if ( !item )
979 return;
981 bool sampler = false;
982 //compilations have valDummy for artist. see QueryBuilder::addReturnValue(..) for explanation
983 //FIXME: Don't rely on other independent code, use an sql query
984 if( item->artist().isEmpty() ) sampler = true;
986 QString tipContent = i18n( "%1 - %2", sampler ? i18n("Various Artists") : item->artist() , item->album() );
988 CoverManager::instance()->setStatusText( tipContent );
990 #undef item
993 //////////////////////////////////////////////////////////////////////
994 // CLASS CoverViewItem
995 /////////////////////////////////////////////////////////////////////
997 CoverViewItem::CoverViewItem( QListWidget *parent, Meta::AlbumPtr album )
998 : QListWidgetItem( parent )
999 , m_parent( parent )
1000 , m_albumPtr( album)
1001 , m_coverPixmap( )
1003 m_album = album->prettyName();
1004 m_artist = album->albumArtist()->prettyName();
1005 setText( album->prettyName() );
1006 setIcon( album->image( 100 ) );
1007 album->subscribe( qobject_cast<CoverManager*>(parent->parent()->parent()) );
1008 // setDragEnabled( true );
1009 // setDropEnabled( true );
1010 calcRect();
1013 CoverViewItem::~CoverViewItem()
1015 m_albumPtr->unsubscribe( qobject_cast<CoverManager*>( m_parent->parent()->parent()) );
1017 bool CoverViewItem::hasCover() const
1019 return albumPtr()->hasImage();
1022 void CoverViewItem::loadCover()
1024 m_coverPixmap = m_albumPtr->image(); //create the scaled cover
1025 setIcon( m_coverPixmap );
1027 // repaint();
1031 void CoverViewItem::calcRect( const QString& )
1033 #if 0
1034 int thumbWidth = AmarokConfig::coverPreviewSize();
1036 QFontMetrics fm = listWidget()->fontMetrics();
1037 QRect itemPixmapRect( 5, 1, thumbWidth, thumbWidth );
1038 QRect itemRect = rect();
1039 itemRect.setWidth( thumbWidth + 10 );
1040 itemRect.setHeight( thumbWidth + fm.lineSpacing() + 2 );
1041 QRect itemTextRect( 0, thumbWidth+2, itemRect.width(), fm.lineSpacing() );
1043 setPixmapRect( itemPixmapRect );
1044 setTextRect( itemTextRect );
1045 setItemRect( itemRect );
1046 #endif
1050 //TODO: Port
1051 // void CoverViewItem::dropped( QDropEvent *e, const Q3ValueList<Q3IconDragItem> & )
1052 // {
1053 // if( Q3ImageDrag::canDecode( e ) ) {
1054 // if( hasCover() ) {
1055 // KGuiItem continueButton = KStandardGuiItem::cont();
1056 // continueButton.setText( i18n("&Overwrite") );
1057 // int button = KMessageBox::warningContinueCancel( iconView(),
1058 // i18n( "Are you sure you want to overwrite this cover?"),
1059 // i18n("Overwrite Confirmation"),
1060 // continueButton );
1061 // if( button == KMessageBox::Cancel )
1062 // return;
1063 // }
1065 // QImage img;
1066 // Q3ImageDrag::decode( e, img );
1067 // m_albumPtr->setImage( img );
1068 // loadCover();
1069 // }
1070 // }
1073 void CoverViewItem::dragEntered()
1075 setSelected( true );
1079 void CoverViewItem::dragLeft()
1081 setSelected( false );
1084 #include "CoverManager.moc"