add mp3 and ogg torrent url info to JamendoAlbum
[amarok.git] / src / covermanager.cpp
blobecaaa0d6f527172f17e8216188396dc974f04ddf
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 "browserToolBar.h"
10 #include "debug.h"
11 #include "querybuilder.h"
12 #include "config-amarok.h"
13 #include "pixmapviewer.h"
14 #include "playlist/PlaylistModel.h"
16 #include "TheInstances.h"
18 #include <k3listview.h>
19 #include <k3multipledrag.h>
20 #include <k3urldrag.h>
21 #include <KApplication>
22 #include <KConfig>
23 #include <KCursor>
24 #include <KFileDialog>
25 #include <KIconLoader>
26 #include <KIO/NetAccess>
27 #include <KLineEdit>
28 #include <KLocale>
29 #include <KMenu> //showCoverMenu()
30 #include <KMessageBox> //showCoverMenu()
31 #include <KPushButton>
32 #include <KSqueezedTextLabel> //status label
33 #include <KStandardDirs> //KGlobal::dirs()
34 #include <KStatusBar>
35 #include <KToolBar>
36 #include <KUrl>
37 #include <KVBox>
38 #include <KWindowSystem>
40 #include <QDesktopWidget> //ctor: desktop size
41 #include <QFile>
42 #include <QFontMetrics> //paintItem()
43 #include <QImage>
44 #include <QLabel>
45 #include <QLayout>
46 #include <QObject> //used to delete all cover fetchers
47 #include <QPainter> //paintItem()
48 #include <QPalette> //paintItem()
49 #include <QPixmap>
50 #include <QPoint>
51 #include <QProgressBar>
52 #include <q3progressdialog.h>
53 #include <QRect>
54 #include <QStringList>
55 #include <QToolTip>
56 #include <QTimer> //search filter timer
57 //Added by qt3to4:
58 #include <Q3ValueList>
59 #include <Q3PtrList>
60 #include <QDropEvent>
61 #include <QToolButton>
64 static QString artistToSelectInInitFunction;
65 CoverManager *CoverManager::s_instance = 0;
67 class ArtistItem : public K3ListViewItem
69 public:
70 ArtistItem(Q3ListView *view, Q3ListViewItem *item, const QString &text)
71 : K3ListViewItem(view, item, text) {}
72 protected:
73 int compare( Q3ListViewItem* i, int col, bool ascending ) const
75 Q_UNUSED(col);
76 Q_UNUSED(ascending);
78 QString a = text(0);
79 QString b = i->text(0);
81 if ( a.startsWith( "the ", Qt::CaseInsensitive ) )
82 Amarok::manipulateThe( a, true );
83 if ( b.startsWith( "the ", Qt::CaseInsensitive ) )
84 Amarok::manipulateThe( b, true );
86 return QString::localeAwareCompare( a.toLower(), b.toLower() );
90 CoverManager::CoverManager()
91 : QSplitter( 0 )
92 , m_timer( new QTimer( this ) ) //search filter timer
93 , m_fetchCounter( 0 )
94 , m_fetchingCovers( 0 )
95 , m_coversFetched( 0 )
96 , m_coverErrors( 0 )
98 DEBUG_BLOCK
100 setObjectName( "TheCoverManager" );
102 s_instance = this;
104 // Sets caption and icon correctly (needed e.g. for GNOME)
105 kapp->setTopWidget( this );
106 setWindowTitle( KDialog::makeStandardCaption( i18n("Cover Manager") ) );
107 setAttribute( Qt::WA_DeleteOnClose );
108 setContentsMargins( 4, 4, 4, 4 );
110 //artist listview
111 m_artistView = new K3ListView( this );
112 m_artistView->addColumn(i18n( "Albums By" ));
113 m_artistView->setFullWidth( true );
114 m_artistView->setSorting( 0 );
115 m_artistView->setMinimumWidth( 180 );
116 ArtistItem *item = 0;
118 //load artists from the collection db
119 const QStringList artists = CollectionDB::instance()->artistList( false, false );
120 foreach( QString artist, artists )
122 item = new ArtistItem( m_artistView, item, artist );
123 item->setPixmap( 0, SmallIcon( Amarok::icon( "artist" ) ) );
125 m_artistView->sort();
127 m_artistView->setSorting( -1 );
128 ArtistItem *last = static_cast<ArtistItem *>(m_artistView->lastItem());
129 item = new ArtistItem( m_artistView, 0, i18n( "All Albums" ) );
130 item->setPixmap( 0, SmallIcon( Amarok::icon( "album" ) ) );
132 QueryBuilder qb;
133 qb.addReturnValue( QueryBuilder::tabAlbum, QueryBuilder::valName );
134 qb.groupBy( QueryBuilder::tabAlbum, QueryBuilder::valName );
135 qb.setOptions( QueryBuilder::optOnlyCompilations );
136 qb.setLimit( 0, 1 );
137 if ( qb.run().count() ) {
138 item = new ArtistItem( m_artistView, last, i18n( "Various Artists" ) );
139 item->setPixmap( 0, SmallIcon("personal") );
142 KVBox *vbox = new KVBox( this );
143 KHBox *hbox = new KHBox( vbox );
145 vbox->setSpacing( 4 );
146 hbox->setSpacing( 4 );
148 { //<Search LineEdit>
149 KHBox *searchBox = new KHBox( hbox );
150 KToolBar* searchToolBar = new Browser::ToolBar( searchBox );
151 QToolButton *button = new QToolButton( searchToolBar );
152 button->setIcon( KIcon( "edit-clear-locationbar") );
153 m_searchEdit = new KLineEdit( searchToolBar );
154 m_searchEdit->setClickMessage( i18n( "Enter search terms here" ) );
155 m_searchEdit->setFrame( QFrame::Sunken );
157 m_searchEdit->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Minimum);
158 connect( button, SIGNAL(clicked()), m_searchEdit, SLOT(clear()) );
160 button->setToolTip( i18n( "Clear search field" ) );
161 m_searchEdit->setToolTip( i18n( "Enter space-separated terms to search in the albums" ) );
163 hbox->setStretchFactor( searchBox, 1 );
164 } //</Search LineEdit>
166 // view menu
167 m_viewMenu = new KMenu( this );
168 m_selectAllAlbums = m_viewMenu->addAction( i18n("All Albums"), this, SLOT( slotShowAllAlbums() ) );
169 m_selectAlbumsWithCover = m_viewMenu->addAction( i18n("Albums With Cover"), this, SLOT( slotShowAlbumsWithCover() ) );
170 m_selectAlbumsWithoutCover = m_viewMenu->addAction( i18n("Albums Without Cover"), this, SLOT( slotShowAlbumsWithoutCover() ) );
172 QActionGroup *viewGroup = new QActionGroup( this );
173 viewGroup->setExclusive( true );
174 viewGroup->addAction( m_selectAllAlbums );
175 viewGroup->addAction( m_selectAlbumsWithCover );
176 viewGroup->addAction( m_selectAlbumsWithoutCover );
177 m_selectAllAlbums->setChecked( true );
179 // amazon locale menu
180 QString locale = AmarokConfig::amazonLocale();
181 m_currentLocale = CoverFetcher::localeStringToID( locale );
183 QAction *a;
184 QActionGroup *localeGroup = new QActionGroup( this );
185 localeGroup->setExclusive( true );
187 m_amazonLocaleMenu = new KMenu( this );
189 a = m_amazonLocaleMenu->addAction( i18n("International"), this, SLOT( slotSetLocaleIntl() ) );
190 if( m_currentLocale == CoverFetcher::International ) a->setChecked( true );
191 localeGroup->addAction( a );
193 a = m_amazonLocaleMenu->addAction( i18n("Canada"), this, SLOT( slotSetLocaleCa() ) );
194 if( m_currentLocale == CoverFetcher::Canada ) a->setChecked( true );
195 localeGroup->addAction( a );
197 a = m_amazonLocaleMenu->addAction( i18n("France"), this, SLOT( slotSetLocaleFr() ) );
198 if( m_currentLocale == CoverFetcher::France ) a->setChecked( true );
199 localeGroup->addAction( a );
201 a = m_amazonLocaleMenu->addAction( i18n("Germany"), this, SLOT( slotSetLocaleDe() ) );
202 if( m_currentLocale == CoverFetcher::Germany ) a->setChecked( true );
203 localeGroup->addAction( a );
205 a = m_amazonLocaleMenu->addAction( i18n("Japan"), this, SLOT( slotSetLocaleJp() ) );
206 if( m_currentLocale == CoverFetcher::Japan ) a->setChecked( true );
207 localeGroup->addAction( a );
209 a = m_amazonLocaleMenu->addAction( i18n("United Kingdom"), this, SLOT( slotSetLocaleUk() ) );
210 if( m_currentLocale == CoverFetcher::UK ) a->setChecked( true );
211 localeGroup->addAction( a );
213 KToolBar* toolBar = new KToolBar( hbox );
214 toolBar->setToolButtonStyle( Qt::ToolButtonTextBesideIcon );
215 //TODO port to kde4
216 // toolBar->setFrameShape( QFrame::NoFrame );
218 QAction* viewMenuAction = new QAction( KIcon( "view_choose" ), i18n( "View" ), this );
219 viewMenuAction->setMenu( m_viewMenu );
220 toolBar->addAction( viewMenuAction );
223 QAction* localeMenuAction = new QAction( KIcon( "babelfish" ), i18n( "Amazon Locale" ), this );
224 localeMenuAction->setMenu( m_amazonLocaleMenu );
225 toolBar->addAction( localeMenuAction );
228 //fetch missing covers button
229 m_fetchButton = new KPushButton( KGuiItem( i18n("Fetch Missing Covers"), Amarok::icon( "download" ) ), hbox );
230 connect( m_fetchButton, SIGNAL(clicked()), SLOT(fetchMissingCovers()) );
233 //cover view
234 m_coverView = new CoverView( vbox );
236 //status bar
237 KStatusBar *m_statusBar = new KStatusBar( vbox );
239 m_statusLabel = new KSqueezedTextLabel( m_statusBar );
240 m_statusLabel->setIndent( 3 );
241 m_progressBox = new KHBox( m_statusBar );
243 m_statusBar->addWidget( m_statusLabel, 4 );
244 m_statusBar->addPermanentWidget( m_progressBox, 1 );
246 KPushButton *stopButton = new KPushButton( KGuiItem(i18n("Abort"), "stop"), m_progressBox );
247 connect( stopButton, SIGNAL(clicked()), SLOT(stopFetching()) );
248 m_progress = new QProgressBar( m_progressBox );
249 m_progress->setTextVisible( true );
251 const int h = m_statusLabel->height() + 3;
252 m_statusLabel->setFixedHeight( h );
253 m_progressBox->setFixedHeight( h );
254 m_progressBox->hide();
257 // signals and slots connections
258 connect( m_artistView, SIGNAL(selectionChanged( Q3ListViewItem* ) ),
259 SLOT(slotArtistSelected( Q3ListViewItem* )) );
260 connect( m_coverView, SIGNAL(contextMenuRequested( Q3IconViewItem*, const QPoint& )),
261 SLOT(showCoverMenu( Q3IconViewItem*, const QPoint& )) );
262 connect( m_coverView, SIGNAL(executed( Q3IconViewItem* )),
263 SLOT(coverItemExecuted( Q3IconViewItem* )) );
264 connect( m_timer, SIGNAL(timeout()),
265 SLOT(slotSetFilter()) );
266 connect( m_searchEdit, SIGNAL(textChanged( const QString& )),
267 SLOT(slotSetFilterTimeout()) );
269 connect( CollectionDB::instance(), SIGNAL(coverFetched( const QString&, const QString& )),
270 SLOT(coverFetched( const QString&, const QString& )) );
271 connect( CollectionDB::instance(), SIGNAL(coverRemoved( const QString&, const QString& )),
272 SLOT(coverRemoved( const QString&, const QString& )) );
273 connect( CollectionDB::instance(), SIGNAL(coverFetcherError( const QString& )),
274 SLOT(coverFetcherError()) );
276 m_currentView = AllAlbums;
278 QSize size = QApplication::desktop()->screenGeometry( this ).size() / 1.5;
279 QSize sz = Amarok::config( "Cover Manager" ).readEntry( "Window Size", size );
280 resize( sz.width(), sz.height() );
282 show();
284 QTimer::singleShot( 0, this, SLOT(init()) );
288 CoverManager::~CoverManager()
290 DEBUG_BLOCK
292 Amarok::config( "Cover Manager" ).writeEntry( "Window Size", size() );
294 s_instance = 0;
298 void CoverManager::init()
300 DEBUG_BLOCK
302 Q3ListViewItem *item = 0;
304 if ( !artistToSelectInInitFunction.isEmpty() )
305 for ( item = m_artistView->firstChild(); item; item = item->nextSibling() )
306 if ( item->text( 0 ) == artistToSelectInInitFunction )
307 break;
309 if ( item == 0 )
310 item = m_artistView->firstChild();
312 m_artistView->setSelected( item, true );
316 CoverViewDialog::CoverViewDialog( const QString& artist, const QString& album, QWidget *parent )
317 : QDialog( parent, 0, false, Qt::WType_TopLevel | Qt::WNoAutoErase )
318 , m_pixmap( CollectionDB::instance()->albumImage( artist, album, false, 0 ) )
320 setAttribute( Qt::WA_DeleteOnClose );
321 #ifdef Q_WS_X11
322 KWindowSystem::setType( winId(), NET::Utility );
323 #endif
324 kapp->setTopWidget( this );
325 setWindowTitle( KDialog::makeStandardCaption( i18n("%1 - %2", artist, album ) ) );
327 m_layout = new QHBoxLayout( this );
328 m_pixmapViewer = new PixmapViewer( this, m_pixmap );
329 m_layout->addWidget( m_pixmapViewer );
331 setFixedSize( m_pixmapViewer->maximalSize() );
335 void CoverManager::viewCover( const QString& artist, const QString& album, QWidget *parent ) //static
337 //QDialog means "escape" works as expected
338 QDialog *dialog = new CoverViewDialog( artist, album, parent );
339 dialog->show();
343 QString CoverManager::amazonTld() //static
345 if( AmarokConfig::amazonLocale() == "us" )
346 return "com";
347 else if( AmarokConfig::amazonLocale()== "jp" )
348 return "co.jp";
349 else if( AmarokConfig::amazonLocale() == "uk" )
350 return "co.uk";
351 else if( AmarokConfig::amazonLocale() == "ca" )
352 return "ca";
353 else
354 return AmarokConfig::amazonLocale();
358 void CoverManager::fetchMissingCovers() //SLOT
360 DEBUG_BLOCK
362 for ( Q3IconViewItem *item = m_coverView->firstItem(); item; item = item->nextItem() ) {
363 CoverViewItem *coverItem = static_cast<CoverViewItem*>( item );
364 if( !coverItem->hasCover() ) {
365 m_fetchCovers += coverItem->artist() + " @@@ " + coverItem->album();
366 m_fetchingCovers++;
370 if( !m_fetchCounter ) //loop isn't started yet
371 fetchCoversLoop();
373 updateStatusBar();
374 m_fetchButton->setEnabled( false );
379 void CoverManager::fetchCoversLoop() //SLOT
381 if( (int)m_fetchCounter < m_fetchCovers.count() )
383 //get artist and album from keyword
384 const QStringList values = m_fetchCovers[m_fetchCounter].split( " @@@ ", QString::KeepEmptyParts );
386 if( values.count() > 1 )
387 CollectionDB::instance()->fetchCover( this, values[0], values[1], m_fetchCovers.count() != 1); //edit mode when fetching 1 cover
389 m_fetchCounter++;
391 // Wait 1 second, since amazon caps the number of accesses per client
392 QTimer::singleShot( 1000, this, SLOT( fetchCoversLoop() ) );
394 else {
395 m_fetchCovers.clear();
396 m_fetchCounter = 0;
402 void CoverManager::showOnce( const QString &artist )
404 if ( !s_instance ) {
405 artistToSelectInInitFunction = artist;
406 new CoverManager(); //shows itself
408 else {
409 s_instance->activateWindow();
410 s_instance->raise();
414 void CoverManager::slotArtistSelected( Q3ListViewItem *item ) //SLOT
416 if( item->depth() ) //album item
417 return;
419 QString artist = item->text(0);
421 if( artist.endsWith( ", The" ) )
422 Amarok::manipulateThe( artist, false );
424 m_coverView->clear();
425 m_coverItems.clear();
427 // reset current view mode state to "AllAlbum" which is the default on artist change in left panel
428 m_currentView = AllAlbums;
429 m_selectAllAlbums->trigger();
430 m_selectAllAlbums->setChecked( true );
432 Q3ProgressDialog progress( this, 0, true );
433 progress.setLabelText( i18n("Loading Thumbnails...") );
434 progress.QDialog::setWindowTitle( i18n("...") );
436 //NOTE we MUST show the dialog, otherwise the closeEvents get processed
437 // in the processEvents() calls below, GRUMBLE! Qt sux0rs
438 progress.show();
439 progress.repaint(); //ensures the dialog isn't blank
441 //this is an extra processEvent call for the sake of init() and aesthetics
442 //it isn't necessary
443 kapp->processEvents();
445 //this can be a bit slow
446 QApplication::setOverrideCursor( Qt::WaitCursor );
447 QueryBuilder qb;
448 QStringList albums;
450 qb.addReturnValue( QueryBuilder::tabArtist, QueryBuilder::valName );
451 qb.addReturnValue( QueryBuilder::tabAlbum, QueryBuilder::valName );
453 qb.excludeMatch( QueryBuilder::tabAlbum, i18n( "Unknown" ) );
454 qb.sortBy( QueryBuilder::tabAlbum, QueryBuilder::valName );
455 qb.setOptions( QueryBuilder::optRemoveDuplicates );
456 qb.setOptions( QueryBuilder::optNoCompilations );
458 if ( item != m_artistView->firstChild() )
459 qb.addMatch( QueryBuilder::tabArtist, artist );
461 albums = qb.run();
463 //also retrieve compilations when we're showing all items (first treenode) or
464 //"Various Artists" (last treenode)
465 if ( item == m_artistView->firstChild() || item == m_artistView->lastChild() )
467 QStringList cl;
469 qb.clear();
470 qb.addReturnValue( QueryBuilder::tabAlbum, QueryBuilder::valName );
472 qb.excludeMatch( QueryBuilder::tabAlbum, i18n( "Unknown" ) );
473 qb.sortBy( QueryBuilder::tabAlbum, QueryBuilder::valName );
474 qb.setOptions( QueryBuilder::optRemoveDuplicates );
475 qb.setOptions( QueryBuilder::optOnlyCompilations );
476 cl = qb.run();
478 for( int i = 0; i < cl.count(); i++ ) {
479 albums.append( i18n( "Various Artists" ) );
480 albums.append( cl[ i ] );
484 QApplication::restoreOverrideCursor();
486 progress.setTotalSteps( (albums.count()/2) + (albums.count()/10) );
488 //insert the covers first because the list view is soooo paint-happy
489 //doing it in the second loop looks really bad, unfortunately
490 //this is the slowest step in the bit that we can't process events
491 uint x = 0;
492 oldForeach( albums )
494 const QString artist = *it;
495 const QString album = *(++it);
496 m_coverItems.append( new CoverViewItem( m_coverView, m_coverView->lastItem(), artist, album ) );
498 if ( ++x % 50 == 0 ) {
499 progress.setProgress( x / 5 ); // we do it less often due to bug in Qt, ask Max
500 kapp->processEvents(); // QProgressDialog also calls this, but not always due to Qt bug!
502 //only worth testing for after processEvents() is called
503 if( progress.wasCancelled() )
504 break;
508 //now, load the thumbnails
509 for( Q3IconViewItem *item = m_coverView->firstItem(); item; item = item->nextItem() ) {
510 progress.setProgress( progress.progress() + 1 );
511 kapp->processEvents();
513 if( progress.wasCancelled() )
514 break;
516 static_cast<CoverViewItem*>(item)->loadCover();
519 updateStatusBar();
522 void CoverManager::showCoverMenu( Q3IconViewItem *item, const QPoint &p ) //SLOT
524 #define item static_cast<CoverViewItem*>(item)
525 if( !item ) return;
527 KMenu menu;
529 menu.addTitle( i18n( "Cover Image" ) );
531 Q3PtrList<CoverViewItem> selected = selectedItems();
532 const int nSelected = selected.count();
534 QAction* fetchSelectedAction = new QAction( KIcon( Amarok::icon( "download" ) )
535 , ( nSelected == 1 ? i18n( "&Fetch From amazon.%1", CoverManager::amazonTld() )
536 : i18n( "&Fetch Selected Covers" ) )
537 , &menu );
538 connect( fetchSelectedAction, SIGNAL( triggered() ), this, SLOT( fetchSelectedCovers() ) );
540 QAction* setCustomAction = new QAction( KIcon( Amarok::icon( "files" ) )
541 , i18np( "Set &Custom Cover", "Set &Custom Cover for Selected Albums", nSelected )
542 , &menu );
543 connect( setCustomAction, SIGNAL( triggered() ), this, SLOT( setCustomSelectedCovers() ) );
544 QAction* unsetAction = new QAction( KIcon( Amarok::icon( "remove" ) ), i18np( "&Unset Cover", "&Unset Selected Covers", nSelected ), &menu );
545 connect( unsetAction, SIGNAL( triggered() ), this, SLOT ( deleteSelectedCovers() ) );
547 QAction* playAlbumAction = new QAction( KIcon( Amarok::icon( "add_playlist" ) )
548 , i18np( "&Append to Playlist", "&Append Selected Albums to Playlist", nSelected )
549 , &menu );
551 connect( playAlbumAction, SIGNAL( triggered() ), this, SLOT( playSelectedAlbums() ) );
553 if( nSelected > 1 ) {
554 menu.addAction( fetchSelectedAction );
555 menu.addAction( setCustomAction );
556 menu.addAction( playAlbumAction );
557 menu.addAction( unsetAction );
559 else {
560 QAction* viewAction = new QAction( KIcon( Amarok::icon( "zoom" ) ), i18n( "&Show Fullsize" ), &menu );
561 connect( viewAction, SIGNAL( triggered() ), this, SLOT( viewSelectedCover() ) );
562 viewAction ->setEnabled( item->hasCover() );
563 unsetAction->setEnabled( item->canRemoveCover() );
564 menu.addAction( viewAction );
565 menu.addAction( fetchSelectedAction );
566 menu.addAction( setCustomAction );
567 menu.addAction( playAlbumAction );
568 menu.addSeparator();
570 menu.addAction( unsetAction );
573 menu.exec( p );
575 #undef item
578 void CoverManager::viewSelectedCover()
580 CoverViewItem* item = selectedItems().first();
581 viewCover( item->artist(), item->album(), this );
584 void CoverManager::coverItemExecuted( Q3IconViewItem *item ) //SLOT
586 #define item static_cast<CoverViewItem*>(item)
588 if( !item ) return;
590 item->setSelected( true );
591 if ( item->hasCover() )
592 viewCover( item->artist(), item->album(), this );
593 else
594 fetchSelectedCovers();
596 #undef item
600 void CoverManager::slotSetFilter() //SLOT
602 m_filter = m_searchEdit->text();
604 m_coverView->selectAll( false);
605 Q3IconViewItem *item = m_coverView->firstItem();
606 while ( item )
608 Q3IconViewItem *tmp = item->nextItem();
609 m_coverView->takeItem( item );
610 item = tmp;
613 m_coverView->setAutoArrange( false );
614 for( Q3IconViewItem *item = m_coverItems.first(); item; item = m_coverItems.next() )
616 CoverViewItem *coverItem = static_cast<CoverViewItem*>(item);
617 if( coverItem->album().contains( m_filter, Qt::CaseInsensitive ) || coverItem->artist().contains( m_filter, Qt::CaseInsensitive ) )
618 m_coverView->insertItem( item, m_coverView->lastItem() );
620 m_coverView->setAutoArrange( true );
622 m_coverView->arrangeItemsInGrid();
623 updateStatusBar();
627 void CoverManager::slotSetFilterTimeout() //SLOT
629 if ( m_timer->isActive() ) m_timer->stop();
630 m_timer->setSingleShot( true );
631 m_timer->start( 180 );
634 void CoverManager::changeView( int id ) //SLOT
636 if( m_currentView == id ) return;
638 //clear the iconview without deleting items
639 m_coverView->selectAll( false);
640 Q3IconViewItem *item = m_coverView->firstItem();
641 while ( item ) {
642 Q3IconViewItem *tmp = item->nextItem();
643 m_coverView->takeItem( item );
644 item = tmp;
647 m_coverView->setAutoArrange(false );
648 for( Q3IconViewItem *item = m_coverItems.first(); item; item = m_coverItems.next() ) {
649 bool show = false;
650 CoverViewItem *coverItem = static_cast<CoverViewItem*>(item);
651 if( !m_filter.isEmpty() ) {
652 if( !coverItem->album().contains( m_filter, Qt::CaseInsensitive ) && !coverItem->artist().contains( m_filter, Qt::CaseInsensitive ) )
653 continue;
656 if( id == AllAlbums ) //show all albums
657 show = true;
658 else if( id == AlbumsWithCover && coverItem->hasCover() ) //show only albums with cover
659 show = true;
660 else if( id == AlbumsWithoutCover && !coverItem->hasCover() ) //show only albums without cover
661 show = true;
663 if( show ) m_coverView->insertItem( item, m_coverView->lastItem() );
665 m_coverView->setAutoArrange( true );
667 m_coverView->arrangeItemsInGrid();
668 m_currentView = id;
671 void CoverManager::changeLocale( int id ) //SLOT
673 QString locale = CoverFetcher::localeIDToString( id );
674 AmarokConfig::setAmazonLocale( locale );
675 m_currentLocale = id;
679 void CoverManager::coverFetched( const QString &artist, const QString &album ) //SLOT
681 loadCover( artist, album );
682 m_coversFetched++;
683 updateStatusBar();
687 void CoverManager::coverRemoved( const QString &artist, const QString &album ) //SLOT
689 loadCover( artist, album );
690 m_coversFetched--;
691 updateStatusBar();
695 void CoverManager::coverFetcherError()
697 DEBUG_FUNC_INFO
699 m_coverErrors++;
700 updateStatusBar();
704 void CoverManager::stopFetching()
706 Debug::Block block( __PRETTY_FUNCTION__ );
708 m_fetchCovers.clear();
709 m_fetchCounter = 0;
711 //delete all cover fetchers
712 QList<CoverFetcher *> list = qFindChildren<CoverFetcher*>(this);
713 foreach( CoverFetcher *cf, list )
714 cf->deleteLater();
716 m_fetchingCovers = 0;
717 updateStatusBar();
720 // PRIVATE
722 void CoverManager::loadCover( const QString &artist, const QString &album )
724 for( Q3IconViewItem *item = m_coverItems.first(); item; item = m_coverItems.next() )
726 CoverViewItem *coverItem = static_cast<CoverViewItem*>(item);
727 if ( album == coverItem->album() && ( artist == coverItem->artist() || ( artist.isEmpty() && coverItem->artist().isEmpty() ) ) )
729 coverItem->loadCover();
730 return;
735 void CoverManager::setCustomSelectedCovers()
737 //function assumes something is selected
738 Q3PtrList<CoverViewItem> selected = selectedItems();
739 CoverViewItem* first = selected.getFirst();
741 QString artist_id; artist_id.setNum( CollectionDB::instance()->artistID( first->artist() ) );
742 QString album_id; album_id.setNum( CollectionDB::instance()->albumID( first->album() ) );
743 QStringList values = CollectionDB::instance()->albumTracks( artist_id, album_id );
745 QString startPath = ":homedir";
746 if ( !values.isEmpty() ) {
747 KUrl url;
748 url.setPath( values.first() );
749 startPath = url.directory();
751 KUrl file = KFileDialog::getImageOpenUrl( startPath, this, i18n( "Select Cover Image File" ) );
752 if ( !file.isEmpty() ) {
753 kapp->processEvents(); //it may takes a while so process pending events
754 QString tmpFile;
755 QImage image = CollectionDB::fetchImage(file, tmpFile);
756 for ( CoverViewItem* item = selected.first(); item; item = selected.next() ) {
757 CollectionDB::instance()->setAlbumImage( item->artist(), item->album(), image );
758 item->loadCover();
760 KIO::NetAccess::removeTempFile( tmpFile );
764 void CoverManager::fetchSelectedCovers()
766 Q3PtrList<CoverViewItem> selected = selectedItems();
767 for ( CoverViewItem* item = selected.first(); item; item = selected.next() )
768 m_fetchCovers += item->artist() + " @@@ " + item->album();
770 m_fetchingCovers += selected.count();
772 if( !m_fetchCounter ) //loop isn't started yet
773 fetchCoversLoop();
775 updateStatusBar();
779 void CoverManager::deleteSelectedCovers()
781 Q3PtrList<CoverViewItem> selected = selectedItems();
783 int button = KMessageBox::warningContinueCancel( this,
784 i18np( "Are you sure you want to remove this cover from the Collection?",
785 "Are you sure you want to delete these %1 covers from the Collection?",
786 selected.count() ),
787 QString(),
788 KStandardGuiItem::del() );
790 if ( button == KMessageBox::Continue ) {
791 for ( CoverViewItem* item = selected.first(); item; item = selected.next() ) {
792 kapp->processEvents();
793 if ( CollectionDB::instance()->removeAlbumImage( item->artist(), item->album() ) ) //delete selected cover
794 coverRemoved( item->artist(), item->album() );
800 void CoverManager::playSelectedAlbums()
802 Q3PtrList<CoverViewItem> selected = selectedItems();
803 QString artist_id, album_id;
804 for ( CoverViewItem* item = selected.first(); item; item = selected.next() )
806 artist_id.setNum( CollectionDB::instance()->artistID( item->artist() ) );
807 album_id.setNum( CollectionDB::instance()->albumID( item->album() ) );
808 The::playlistModel()->insertMedia( CollectionDB::instance()->albumTracks( artist_id, album_id ), Playlist::Append );
812 Q3PtrList<CoverViewItem> CoverManager::selectedItems()
814 Q3PtrList<CoverViewItem> selectedItems;
815 for ( Q3IconViewItem* item = m_coverView->firstItem(); item; item = item->nextItem() )
816 if ( item->isSelected() )
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 QStringList values = m_fetchCovers[0].split( " @@@ ", QString::KeepEmptyParts ); //get artist and album name
850 if ( values.count() >= 2 )
852 if( values[0].isEmpty() )
853 text = i18n( "Fetching cover for %1..." , values[1] );
854 else
855 text = i18n( "Fetching cover for %1 - %2...", values[0], values[1] );
858 else if( m_fetchingCovers ) {
859 text = i18np( "Fetching 1 cover: ", "Fetching <b>%1</b> covers... : ", m_fetchingCovers );
860 if( m_coversFetched )
861 text += i18np( "1 fetched", "%1 fetched", m_coversFetched );
862 if( m_coverErrors ) {
863 if( m_coversFetched ) text += i18n(" - ");
864 text += i18np( "1 not found", "%1 not found", m_coverErrors );
866 if( m_coversFetched + m_coverErrors == 0 )
867 text += i18n( "Connecting..." );
870 else {
871 m_coversFetched = 0;
872 m_coverErrors = 0;
874 uint totalCounter = 0, missingCounter = 0;
876 if( m_progressBox->isVisible() )
877 m_progressBox->hide();
879 //album info
880 for( Q3IconViewItem *item = m_coverView->firstItem(); item; item = item->nextItem() ) {
881 totalCounter++;
882 if( !static_cast<CoverViewItem*>( item )->hasCover() )
883 missingCounter++; //counter for albums without cover
886 if( !m_filter.isEmpty() )
887 text = i18np( "1 result for \"%1\"", "%1 results for \"%1\"", totalCounter, m_filter );
888 else if( m_artistView->selectedItem() ) {
889 text = i18np( "1 album", "%1 albums", totalCounter );
890 if( m_artistView->selectedItem() != m_artistView->firstChild() ) //showing albums by an artist
892 QString artist = m_artistView->selectedItem()->text(0);
893 if( artist.endsWith( ", The" ) )
894 Amarok::manipulateThe( artist, false );
895 text += i18n( " by " ) + artist;
899 if( missingCounter )
900 text += i18n(" - ( <b>%1</b> without cover )", missingCounter );
902 m_fetchButton->setEnabled( missingCounter );
905 m_statusLabel->setText( text );
908 void CoverManager::setStatusText( QString text )
910 m_oldStatusText = m_statusLabel->text();
911 m_statusLabel->setText( text );
914 //////////////////////////////////////////////////////////////////////
915 // CLASS CoverView
916 /////////////////////////////////////////////////////////////////////
918 CoverView::CoverView( QWidget *parent, const char *name, Qt::WFlags f )
919 : K3IconView( parent, name, f )
921 Debug::Block block( __PRETTY_FUNCTION__ );
923 setArrangement( Q3IconView::LeftToRight );
924 setResizeMode( Q3IconView::Adjust );
925 setSelectionMode( Q3IconView::Extended );
926 arrangeItemsInGrid();
927 setAutoArrange( true );
928 setItemsMovable( false );
930 // as long as QIconView only shows tooltips when the cursor is over the
931 // icon (and not the text), we have to create our own tooltips
932 setShowToolTips( false );
934 connect( this, SIGNAL( onItem( Q3IconViewItem * ) ), SLOT( setStatusText( Q3IconViewItem * ) ) );
935 connect( this, SIGNAL( onViewport() ), CoverManager::instance(), SLOT( updateStatusBar() ) );
939 Q3DragObject *CoverView::dragObject()
941 CoverViewItem *item = static_cast<CoverViewItem*>( currentItem() );
942 if( !item )
943 return 0;
945 const QString sql = "SELECT tags.url FROM tags, album WHERE album.name %1 AND tags.album = album.id ORDER BY tags.track;";
946 const QStringList values = CollectionDB::instance()->query( sql.arg( CollectionDB::likeCondition( item->album() ) ) );
948 KUrl::List urls;
949 for( QStringList::ConstIterator it = values.begin(), end = values.end(); it != end; ++it )
950 urls += *it;
952 QString imagePath = CollectionDB::instance()->albumImage( item->artist(), item->album(), false, 1 );
953 K3MultipleDrag *drag = new K3MultipleDrag( this );
954 drag->setPixmap( item->coverPixmap() );
955 drag->addDragObject( new Q3IconDrag( this ) );
956 drag->addDragObject( new Q3ImageDrag( QImage( imagePath ) ) );
957 drag->addDragObject( new K3URLDrag( urls ) );
959 return drag;
962 void CoverView::setStatusText( Q3IconViewItem *item )
964 #define item static_cast<CoverViewItem *>( item )
965 if ( !item )
966 return;
968 bool sampler = false;
969 //compilations have valDummy for artist. see QueryBuilder::addReturnValue(..) for explanation
970 //FIXME: Don't rely on other independent code, use an sql query
971 if( item->artist().isEmpty() ) sampler = true;
973 QString tipContent = i18n( "%1 - %2", sampler ? i18n("Various Artists") : item->artist() , item->album() );
975 CoverManager::instance()->setStatusText( tipContent );
977 #undef item
980 //////////////////////////////////////////////////////////////////////
981 // CLASS CoverViewItem
982 /////////////////////////////////////////////////////////////////////
984 CoverViewItem::CoverViewItem( Q3IconView *parent, Q3IconViewItem *after, const QString &artist, const QString &album )
985 : K3IconViewItem( parent, after, album )
986 , m_artist( artist )
987 , m_album( album )
988 , m_coverImagePath( CollectionDB::instance()->albumImage( m_artist, m_album, false, 0, &m_embedded ) )
989 , m_coverPixmap( )
991 setDragEnabled( true );
992 setDropEnabled( true );
993 calcRect();
996 bool CoverViewItem::hasCover() const
998 return !m_coverImagePath.endsWith( "nocover.png" ) && QFile::exists( m_coverImagePath );
1001 void CoverViewItem::loadCover()
1003 m_coverImagePath = CollectionDB::instance()->albumImage( m_artist, m_album, false, 1, &m_embedded );
1004 m_coverPixmap = QPixmap( m_coverImagePath ); //create the scaled cover
1006 repaint();
1010 void CoverViewItem::calcRect( const QString& )
1012 int thumbWidth = AmarokConfig::coverPreviewSize();
1014 QFontMetrics fm = iconView()->fontMetrics();
1015 QRect itemPixmapRect( 5, 1, thumbWidth, thumbWidth );
1016 QRect itemRect = rect();
1017 itemRect.setWidth( thumbWidth + 10 );
1018 itemRect.setHeight( thumbWidth + fm.lineSpacing() + 2 );
1019 QRect itemTextRect( 0, thumbWidth+2, itemRect.width(), fm.lineSpacing() );
1021 setPixmapRect( itemPixmapRect );
1022 setTextRect( itemTextRect );
1023 setItemRect( itemRect );
1027 void CoverViewItem::paintItem(QPainter* p, const QColorGroup& cg)
1029 Q_UNUSED( cg ); // FIXME is this correct (cg replaced by palette() )?
1030 QPalette palette = KApplication::palette();
1031 QRect itemRect = rect();
1033 p->save();
1034 p->translate( itemRect.x(), itemRect.y() );
1036 // draw the border
1037 p->setPen( palette.mid().color() );
1038 p->drawRect( 0, 0, itemRect.width(), pixmapRect().height()+2 );
1040 // draw the cover image
1041 if( !m_coverPixmap.isNull() )
1042 p->drawPixmap( pixmapRect().x() + (pixmapRect().width() - m_coverPixmap.width())/2,
1043 pixmapRect().y() + (pixmapRect().height() - m_coverPixmap.height())/2, m_coverPixmap );
1045 //justify the album name
1046 QString str = text();
1047 QFontMetrics fm = p->fontMetrics();
1048 str = fm.elidedText( str, Qt::ElideRight, textRect().width() );
1050 p->setPen( palette.text().color() );
1051 p->drawText( textRect(), Qt::AlignCenter, str );
1053 if( isSelected() ) {
1054 p->setPen( palette.highlight().color() );
1055 p->drawRect( pixmapRect() );
1056 p->drawRect( pixmapRect().left()+1, pixmapRect().top()+1, pixmapRect().width()-2, pixmapRect().height()-2);
1057 p->drawRect( pixmapRect().left()+2, pixmapRect().top()+2, pixmapRect().width()-4, pixmapRect().height()-4);
1060 p->restore();
1064 void CoverViewItem::dropped( QDropEvent *e, const Q3ValueList<Q3IconDragItem> & )
1066 if( Q3ImageDrag::canDecode( e ) ) {
1067 if( hasCover() ) {
1068 KGuiItem continueButton = KStandardGuiItem::cont();
1069 continueButton.setText( i18n("&Overwrite") );
1070 int button = KMessageBox::warningContinueCancel( iconView(),
1071 i18n( "Are you sure you want to overwrite this cover?"),
1072 i18n("Overwrite Confirmation"),
1073 continueButton );
1074 if( button == KMessageBox::Cancel )
1075 return;
1078 QImage img;
1079 Q3ImageDrag::decode( e, img );
1080 CollectionDB::instance()->setAlbumImage( artist(), album(), img );
1081 m_coverImagePath = CollectionDB::instance()->albumImage( m_artist, m_album, false, 0 );
1082 loadCover();
1087 void CoverViewItem::dragEntered()
1089 setSelected( true );
1093 void CoverViewItem::dragLeft()
1095 setSelected( false );
1098 #include "covermanager.moc"