Not crap after all...
[amarok.git] / src / covermanager.cpp
blob357b9e425e9eadf797bbf889916d20c4c70f3435
1 // (c) Pierpaolo Di Panfilo 2004
2 // (c) 2005 Isaiah Damron <xepo@trifault.net>
3 // See COPYING file for licensing information
5 #include "amarok.h"
6 #include "amarokconfig.h"
7 #include "browserToolBar.h"
8 #include "debug.h"
9 #include "collectionbrowser.h" //manipulateThe()
10 #include "querybuilder.h"
11 #include "config-amarok.h"
12 #include "coverfetcher.h"
13 #include "covermanager.h"
14 #include "pixmapviewer.h"
15 #include "playlist.h"
17 #include <qdesktopwidget.h> //ctor: desktop size
18 #include <QFile>
19 #include <QFontMetrics> //paintItem()
20 #include <QImage>
21 #include <QLabel>
22 #include <QLayout>
23 #include <QObject> //used to delete all cover fetchers
24 #include <QPainter> //paintItem()
25 #include <QPalette> //paintItem()
26 #include <QPixmap>
27 #include <qpoint.h>
28 #include <QProgressBar>
29 #include <q3progressdialog.h>
30 #include <QRect>
31 #include <QStringList>
32 #include <QToolTip>
33 #include <QTimer> //search filter timer
34 #include <kvbox.h>
35 //Added by qt3to4:
36 #include <Q3ValueList>
37 #include <Q3PtrList>
38 #include <QDropEvent>
39 #include <QToolButton>
41 #include <kapplication.h>
42 #include <kconfig.h>
43 #include <kcursor.h>
44 #include <kfiledialog.h>
45 #include <kiconloader.h>
46 #include <k3listview.h>
47 #include <klocale.h>
48 #include <kmessagebox.h> //showCoverMenu()
49 #include <k3multipledrag.h>
50 #include <kio/netaccess.h>
51 #include <kmenu.h> //showCoverMenu()
52 #include <kpushbutton.h>
53 #include <ksqueezedtextlabel.h> //status label
54 #include <kstandarddirs.h> //KGlobal::dirs()
55 #include <kstatusbar.h>
56 #include <ktoolbar.h>
57 //clear filter button
58 #include <kurl.h>
59 #include <k3urldrag.h>
60 #include <kwindowsystem.h>
61 #include <klineedit.h>
63 static QString artistToSelectInInitFunction;
64 CoverManager *CoverManager::s_instance = 0;
66 class ArtistItem : public K3ListViewItem
68 public:
69 ArtistItem(Q3ListView *view, Q3ListViewItem *item, const QString &text)
70 : K3ListViewItem(view, item, text) {}
71 protected:
72 int compare( Q3ListViewItem* i, int col, bool ascending ) const
74 Q_UNUSED(col);
75 Q_UNUSED(ascending);
77 QString a = text(0);
78 QString b = i->text(0);
80 if ( a.startsWith( "the ", Qt::CaseInsensitive ) )
81 CollectionView::manipulateThe( a, true );
82 if ( b.startsWith( "the ", Qt::CaseInsensitive ) )
83 CollectionView::manipulateThe( b, true );
85 return QString::localeAwareCompare( a.toLower(), b.toLower() );
89 CoverManager::CoverManager()
90 : QSplitter( 0, "TheCoverManager" )
91 , m_timer( new QTimer( this ) ) //search filter timer
92 , m_fetchCounter( 0 )
93 , m_fetchingCovers( 0 )
94 , m_coversFetched( 0 )
95 , m_coverErrors( 0 )
97 DEBUG_BLOCK
99 s_instance = this;
101 // Sets caption and icon correctly (needed e.g. for GNOME)
102 kapp->setTopWidget( this );
103 setCaption( KDialog::makeStandardCaption( i18n("Cover Manager") ) );
104 setAttribute( Qt::WA_DeleteOnClose );
105 setMargin( 4 );
107 //artist listview
108 m_artistView = new K3ListView( this );
109 m_artistView->addColumn(i18n( "Albums By" ));
110 m_artistView->setFullWidth( true );
111 m_artistView->setSorting( 0 );
112 m_artistView->setMinimumWidth( 180 );
113 ArtistItem *item = 0;
115 //load artists from the collection db
116 const QStringList artists = CollectionDB::instance()->artistList( false, false );
117 foreach( QString artist, artists )
119 item = new ArtistItem( m_artistView, item, artist );
120 item->setPixmap( 0, SmallIcon( Amarok::icon( "artist" ) ) );
122 m_artistView->sort();
124 m_artistView->setSorting( -1 );
125 ArtistItem *last = static_cast<ArtistItem *>(m_artistView->lastItem());
126 item = new ArtistItem( m_artistView, 0, i18n( "All Albums" ) );
127 item->setPixmap( 0, SmallIcon( Amarok::icon( "album" ) ) );
129 QueryBuilder qb;
130 qb.addReturnValue( QueryBuilder::tabAlbum, QueryBuilder::valName );
131 qb.groupBy( QueryBuilder::tabAlbum, QueryBuilder::valName );
132 qb.setOptions( QueryBuilder::optOnlyCompilations );
133 qb.setLimit( 0, 1 );
134 if ( qb.run().count() ) {
135 item = new ArtistItem( m_artistView, last, i18n( "Various Artists" ) );
136 item->setPixmap( 0, SmallIcon("personal") );
139 KVBox *vbox = new KVBox( this );
140 KHBox *hbox = new KHBox( vbox );
142 vbox->setSpacing( 4 );
143 hbox->setSpacing( 4 );
145 { //<Search LineEdit>
146 KHBox *searchBox = new KHBox( hbox );
147 KToolBar* searchToolBar = new Browser::ToolBar( searchBox );
148 QToolButton *button = new QToolButton( searchToolBar );
149 button->setIcon( KIcon( "locationbar-erase") );
150 m_searchEdit = new KLineEdit( searchToolBar );
151 m_searchEdit->setClickMessage( i18n( "Enter search terms here" ) );
152 m_searchEdit->setFrame( QFrame::Sunken );
154 m_searchEdit->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Minimum);
155 connect( button, SIGNAL(clicked()), m_searchEdit, SLOT(clear()) );
157 button->setToolTip( i18n( "Clear search field" ) );
158 m_searchEdit->setToolTip( i18n( "Enter space-separated terms to search in the albums" ) );
160 hbox->setStretchFactor( searchBox, 1 );
161 } //</Search LineEdit>
163 // view menu
164 m_viewMenu = new KMenu( this );
165 m_viewMenu->insertItem( i18n("All Albums"), AllAlbums );
166 m_viewMenu->insertItem( i18n("Albums With Cover"), AlbumsWithCover );
167 m_viewMenu->insertItem( i18n("Albums Without Cover"), AlbumsWithoutCover );
168 m_viewMenu->setItemChecked( AllAlbums, true );
169 connect( m_viewMenu, SIGNAL( activated(int) ), SLOT( changeView(int) ) );
171 // amazon locale menu
172 m_amazonLocaleMenu = new KMenu( this );
173 m_amazonLocaleMenu->insertItem( i18n("International"), CoverFetcher::International );
174 m_amazonLocaleMenu->insertItem( i18n("Canada"), CoverFetcher::Canada );
175 m_amazonLocaleMenu->insertItem( i18n("France"), CoverFetcher::France );
176 m_amazonLocaleMenu->insertItem( i18n("Germany"), CoverFetcher::Germany );
177 m_amazonLocaleMenu->insertItem( i18n("Japan"), CoverFetcher::Japan);
178 m_amazonLocaleMenu->insertItem( i18n("United Kingdom"), CoverFetcher::UK );
179 connect( m_amazonLocaleMenu, SIGNAL( activated(int) ), SLOT( changeLocale(int) ) );
181 KToolBar* toolBar = new KToolBar( hbox );
182 toolBar->setToolButtonStyle( Qt::ToolButtonTextBesideIcon );
183 //TODO port to kde4
184 // toolBar->setFrameShape( QFrame::NoFrame );
186 QAction* viewMenuAction = new QAction( KIcon( "view_choose" ), i18n( "View" ), this );
187 viewMenuAction->setMenu( m_viewMenu );
188 toolBar->addAction( viewMenuAction );
191 QAction* localeMenuAction = new QAction( KIcon( "babelfish" ), i18n( "Amazon Locale" ), this );
192 localeMenuAction->setMenu( m_amazonLocaleMenu );
193 toolBar->addAction( localeMenuAction );
195 QString locale = AmarokConfig::amazonLocale();
196 m_currentLocale = CoverFetcher::localeStringToID( locale );
197 m_amazonLocaleMenu->setItemChecked( m_currentLocale, true );
199 //fetch missing covers button
200 m_fetchButton = new KPushButton( KGuiItem( i18n("Fetch Missing Covers"), Amarok::icon( "download" ) ), hbox );
201 connect( m_fetchButton, SIGNAL(clicked()), SLOT(fetchMissingCovers()) );
204 //cover view
205 m_coverView = new CoverView( vbox );
207 //status bar
208 KStatusBar *m_statusBar = new KStatusBar( vbox );
209 m_statusBar->addWidget( m_statusLabel = new KSqueezedTextLabel( m_statusBar ), 4 );
210 m_statusLabel->setIndent( 3 );
211 m_statusBar->addWidget( m_progressBox = new KHBox( m_statusBar ), 1, true );
212 KPushButton *stopButton = new KPushButton( KGuiItem(i18n("Abort"), "stop"), m_progressBox );
213 connect( stopButton, SIGNAL(clicked()), SLOT(stopFetching()) );
214 m_progress = new QProgressBar( m_progressBox );
215 m_progress->setTextVisible( true );
217 const int h = m_statusLabel->height() + 3;
218 m_statusLabel->setFixedHeight( h );
219 m_progressBox->setFixedHeight( h );
220 m_progressBox->hide();
223 // signals and slots connections
224 connect( m_artistView, SIGNAL(selectionChanged( Q3ListViewItem* ) ),
225 SLOT(slotArtistSelected( Q3ListViewItem* )) );
226 connect( m_coverView, SIGNAL(contextMenuRequested( Q3IconViewItem*, const QPoint& )),
227 SLOT(showCoverMenu( Q3IconViewItem*, const QPoint& )) );
228 connect( m_coverView, SIGNAL(executed( Q3IconViewItem* )),
229 SLOT(coverItemExecuted( Q3IconViewItem* )) );
230 connect( m_timer, SIGNAL(timeout()),
231 SLOT(slotSetFilter()) );
232 connect( m_searchEdit, SIGNAL(textChanged( const QString& )),
233 SLOT(slotSetFilterTimeout()) );
235 connect( CollectionDB::instance(), SIGNAL(coverFetched( const QString&, const QString& )),
236 SLOT(coverFetched( const QString&, const QString& )) );
237 connect( CollectionDB::instance(), SIGNAL(coverRemoved( const QString&, const QString& )),
238 SLOT(coverRemoved( const QString&, const QString& )) );
239 connect( CollectionDB::instance(), SIGNAL(coverFetcherError( const QString& )),
240 SLOT(coverFetcherError()) );
242 m_currentView = AllAlbums;
244 QSize size = QApplication::desktop()->screenGeometry( this ).size() / 1.5;
245 QSize sz = Amarok::config( "Cover Manager" ).readEntry( "Window Size", size );
246 resize( sz.width(), sz.height() );
248 show();
250 QTimer::singleShot( 0, this, SLOT(init()) );
254 CoverManager::~CoverManager()
256 DEBUG_BLOCK
258 Amarok::config( "Cover Manager" ).writeEntry( "Window Size", size() );
260 s_instance = 0;
264 void CoverManager::init()
266 DEBUG_BLOCK
268 Q3ListViewItem *item = 0;
270 if ( !artistToSelectInInitFunction.isEmpty() )
271 for ( item = m_artistView->firstChild(); item; item = item->nextSibling() )
272 if ( item->text( 0 ) == artistToSelectInInitFunction )
273 break;
275 if ( item == 0 )
276 item = m_artistView->firstChild();
278 m_artistView->setSelected( item, true );
282 CoverViewDialog::CoverViewDialog( const QString& artist, const QString& album, QWidget *parent )
283 : QDialog( parent, 0, false, Qt::WType_TopLevel | Qt::WNoAutoErase )
284 , m_pixmap( CollectionDB::instance()->albumImage( artist, album, false, 0 ) )
286 setAttribute( Qt::WA_DeleteOnClose );
287 #ifdef Q_WS_X11
288 KWindowSystem::setType( winId(), NET::Utility );
289 #endif
290 kapp->setTopWidget( this );
291 setCaption( KDialog::makeStandardCaption( i18n("%1 - %2", artist, album ) ) );
293 m_layout = new QHBoxLayout( this );
294 m_layout->setAutoAdd( true );
295 m_pixmapViewer = new PixmapViewer( this, m_pixmap );
297 setFixedSize( m_pixmapViewer->maximalSize() );
301 void CoverManager::viewCover( const QString& artist, const QString& album, QWidget *parent ) //static
303 //QDialog means "escape" works as expected
304 QDialog *dialog = new CoverViewDialog( artist, album, parent );
305 dialog->show();
309 QString CoverManager::amazonTld() //static
311 if (AmarokConfig::amazonLocale() == "us")
312 return "com";
313 else if (AmarokConfig::amazonLocale()== "jp")
314 return "co.jp";
315 else if (AmarokConfig::amazonLocale() == "uk")
316 return "co.uk";
317 else if (AmarokConfig::amazonLocale() == "ca")
318 return "ca";
319 else
320 return AmarokConfig::amazonLocale();
324 void CoverManager::fetchMissingCovers() //SLOT
326 DEBUG_BLOCK
328 for ( Q3IconViewItem *item = m_coverView->firstItem(); item; item = item->nextItem() ) {
329 CoverViewItem *coverItem = static_cast<CoverViewItem*>( item );
330 if( !coverItem->hasCover() ) {
331 m_fetchCovers += coverItem->artist() + " @@@ " + coverItem->album();
332 m_fetchingCovers++;
336 if( !m_fetchCounter ) //loop isn't started yet
337 fetchCoversLoop();
339 updateStatusBar();
340 m_fetchButton->setEnabled( false );
345 void CoverManager::fetchCoversLoop() //SLOT
348 if( m_fetchCounter < m_fetchCovers.count() ) {
349 //get artist and album from keyword
350 const QStringList values = QStringList::split( " @@@ ", m_fetchCovers[m_fetchCounter], true );
352 if( values.count() > 1 )
353 CollectionDB::instance()->fetchCover( this, values[0], values[1], m_fetchCovers.count() != 1); //edit mode when fetching 1 cover
355 m_fetchCounter++;
357 // Wait 1 second, since amazon caps the number of accesses per client
358 QTimer::singleShot( 1000, this, SLOT( fetchCoversLoop() ) );
360 else {
361 m_fetchCovers.clear();
362 m_fetchCounter = 0;
368 void CoverManager::showOnce( const QString &artist )
370 if ( !s_instance ) {
371 artistToSelectInInitFunction = artist;
372 new CoverManager(); //shows itself
374 else {
375 s_instance->setActiveWindow();
376 s_instance->raise();
380 void CoverManager::slotArtistSelected( Q3ListViewItem *item ) //SLOT
382 if( item->depth() ) //album item
383 return;
385 QString artist = item->text(0);
387 if( artist.endsWith( ", The" ) )
388 CollectionView::instance()->manipulateThe( artist, false );
390 m_coverView->clear();
391 m_coverItems.clear();
393 // reset current view mode state to "AllAlbum" which is the default on artist change in left panel
394 m_currentView = AllAlbums;
395 m_viewMenu->setItemChecked( AllAlbums, true );
396 m_viewMenu->setItemChecked( AlbumsWithCover, false );
397 m_viewMenu->setItemChecked( AlbumsWithoutCover, false );
399 Q3ProgressDialog progress( this, 0, true );
400 progress.setLabelText( i18n("Loading Thumbnails...") );
401 progress.QDialog::setCaption( i18n("...") );
403 //NOTE we MUST show the dialog, otherwise the closeEvents get processed
404 // in the processEvents() calls below, GRUMBLE! Qt sux0rs
405 progress.show();
406 progress.repaint(); //ensures the dialog isn't blank
408 //this is an extra processEvent call for the sake of init() and aesthetics
409 //it isn't necessary
410 kapp->processEvents();
412 //this can be a bit slow
413 QApplication::setOverrideCursor( Qt::WaitCursor );
414 QueryBuilder qb;
415 QStringList albums;
417 qb.addReturnValue( QueryBuilder::tabArtist, QueryBuilder::valName );
418 qb.addReturnValue( QueryBuilder::tabAlbum, QueryBuilder::valName );
420 qb.excludeMatch( QueryBuilder::tabAlbum, i18n( "Unknown" ) );
421 qb.sortBy( QueryBuilder::tabAlbum, QueryBuilder::valName );
422 qb.setOptions( QueryBuilder::optRemoveDuplicates );
423 qb.setOptions( QueryBuilder::optNoCompilations );
425 if ( item != m_artistView->firstChild() )
426 qb.addMatch( QueryBuilder::tabArtist, artist );
428 albums = qb.run();
430 //also retrieve compilations when we're showing all items (first treenode) or
431 //"Various Artists" (last treenode)
432 if ( item == m_artistView->firstChild() || item == m_artistView->lastChild() )
434 QStringList cl;
436 qb.clear();
437 qb.addReturnValue( QueryBuilder::tabAlbum, QueryBuilder::valName );
439 qb.excludeMatch( QueryBuilder::tabAlbum, i18n( "Unknown" ) );
440 qb.sortBy( QueryBuilder::tabAlbum, QueryBuilder::valName );
441 qb.setOptions( QueryBuilder::optRemoveDuplicates );
442 qb.setOptions( QueryBuilder::optOnlyCompilations );
443 cl = qb.run();
445 for ( uint i = 0; i < cl.count(); i++ ) {
446 albums.append( i18n( "Various Artists" ) );
447 albums.append( cl[ i ] );
451 QApplication::restoreOverrideCursor();
453 progress.setTotalSteps( (albums.count()/2) + (albums.count()/10) );
455 //insert the covers first because the list view is soooo paint-happy
456 //doing it in the second loop looks really bad, unfortunately
457 //this is the slowest step in the bit that we can't process events
458 uint x = 0;
459 oldForeach( albums )
461 const QString artist = *it;
462 const QString album = *(++it);
463 m_coverItems.append( new CoverViewItem( m_coverView, m_coverView->lastItem(), artist, album ) );
465 if ( ++x % 50 == 0 ) {
466 progress.setProgress( x / 5 ); // we do it less often due to bug in Qt, ask Max
467 kapp->processEvents(); // QProgressDialog also calls this, but not always due to Qt bug!
469 //only worth testing for after processEvents() is called
470 if( progress.wasCancelled() )
471 break;
475 //now, load the thumbnails
476 for( Q3IconViewItem *item = m_coverView->firstItem(); item; item = item->nextItem() ) {
477 progress.setProgress( progress.progress() + 1 );
478 kapp->processEvents();
480 if( progress.wasCancelled() )
481 break;
483 static_cast<CoverViewItem*>(item)->loadCover();
486 updateStatusBar();
489 void CoverManager::showCoverMenu( Q3IconViewItem *item, const QPoint &p ) //SLOT
491 #define item static_cast<CoverViewItem*>(item)
492 if( !item ) return;
494 KMenu menu;
496 menu.addTitle( i18n( "Cover Image" ) );
498 Q3PtrList<CoverViewItem> selected = selectedItems();
499 const int nSelected = selected.count();
501 QAction* fetchSelectedAction = new QAction( KIcon( Amarok::icon( "download" ) )
502 , i18np( "&Fetch From amazon.%1", "&Fetch Selected Covers", nSelected, CoverManager::amazonTld() )
503 , &menu );
504 connect( fetchSelectedAction, SIGNAL( triggered() ), this, SLOT( fetchSelectedCovers() ) );
506 QAction* setCustomAction = new QAction( KIcon( Amarok::icon( "files" ) )
507 , i18np( "Set &Custom Cover", "Set &Custom Cover for Selected Albums", nSelected )
508 , &menu );
509 connect( setCustomAction, SIGNAL( triggered() ), this, SLOT( setCustomSelectedCovers() ) );
510 QAction* unsetAction = new QAction( KIcon( Amarok::icon( "remove" ) ), i18np( "&Unset Cover", "&Unset Selected Covers", nSelected ), &menu );
511 connect( unsetAction, SIGNAL( triggered() ), this, SLOT ( deleteSelectedCovers() ) );
513 QAction* playAlbumAction = new QAction( KIcon( Amarok::icon( "add_playlist" ) )
514 , i18np( "&Append to Playlist", "&Append Selected Albums to Playlist", nSelected )
515 , &menu );
517 connect( playAlbumAction, SIGNAL( triggered() ), this, SLOT( playSelectedAlbums() ) );
519 if( nSelected > 1 ) {
520 menu.addAction( fetchSelectedAction );
521 menu.addAction( setCustomAction );
522 menu.addAction( playAlbumAction );
523 menu.addAction( unsetAction );
525 else {
526 QAction* viewAction = new QAction( KIcon( Amarok::icon( "zoom" ) ), i18n( "&Show Fullsize" ), &menu );
527 connect( viewAction, SIGNAL( triggered() ), this, SLOT( viewSelectedCover() ) );
528 viewAction ->setEnabled( item->hasCover() );
529 unsetAction->setEnabled( item->canRemoveCover() );
530 menu.addAction( viewAction );
531 menu.addAction( fetchSelectedAction );
532 menu.addAction( setCustomAction );
533 menu.addAction( playAlbumAction );
534 menu.addSeparator();
536 menu.addAction( unsetAction );
539 menu.exec( p );
541 #undef item
544 void CoverManager::viewSelectedCover()
546 CoverViewItem* item = selectedItems().first();
547 viewCover( item->artist(), item->album(), this );
550 void CoverManager::coverItemExecuted( Q3IconViewItem *item ) //SLOT
552 #define item static_cast<CoverViewItem*>(item)
554 if( !item ) return;
556 item->setSelected( true );
557 if ( item->hasCover() )
558 viewCover( item->artist(), item->album(), this );
559 else
560 fetchSelectedCovers();
562 #undef item
566 void CoverManager::slotSetFilter() //SLOT
568 m_filter = m_searchEdit->text();
570 m_coverView->selectAll( false);
571 Q3IconViewItem *item = m_coverView->firstItem();
572 while ( item )
574 Q3IconViewItem *tmp = item->nextItem();
575 m_coverView->takeItem( item );
576 item = tmp;
579 m_coverView->setAutoArrange( false );
580 for( Q3IconViewItem *item = m_coverItems.first(); item; item = m_coverItems.next() )
582 CoverViewItem *coverItem = static_cast<CoverViewItem*>(item);
583 if( coverItem->album().contains( m_filter, false ) || coverItem->artist().contains( m_filter, false ) )
584 m_coverView->insertItem( item, m_coverView->lastItem() );
586 m_coverView->setAutoArrange( true );
588 m_coverView->arrangeItemsInGrid();
589 updateStatusBar();
593 void CoverManager::slotSetFilterTimeout() //SLOT
595 if ( m_timer->isActive() ) m_timer->stop();
596 m_timer->start( 180, true );
600 void CoverManager::changeView( int id ) //SLOT
602 if( m_currentView == id ) return;
604 //clear the iconview without deleting items
605 m_coverView->selectAll( false);
606 Q3IconViewItem *item = m_coverView->firstItem();
607 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() ) {
615 bool show = false;
616 CoverViewItem *coverItem = static_cast<CoverViewItem*>(item);
617 if( !m_filter.isEmpty() ) {
618 if( !coverItem->album().contains( m_filter, false ) && !coverItem->artist().contains( m_filter, false ) )
619 continue;
622 if( id == AllAlbums ) //show all albums
623 show = true;
624 else if( id == AlbumsWithCover && coverItem->hasCover() ) //show only albums with cover
625 show = true;
626 else if( id == AlbumsWithoutCover && !coverItem->hasCover() ) //show only albums without cover
627 show = true;
629 if( show ) m_coverView->insertItem( item, m_coverView->lastItem() );
631 m_coverView->setAutoArrange( true );
633 m_viewMenu->setItemChecked( m_currentView, false );
634 m_viewMenu->setItemChecked( id, true );
636 m_coverView->arrangeItemsInGrid();
637 m_currentView = id;
640 void CoverManager::changeLocale( int id ) //SLOT
642 QString locale = CoverFetcher::localeIDToString( id );
643 AmarokConfig::setAmazonLocale( locale );
644 m_amazonLocaleMenu->setItemChecked( m_currentLocale, false );
645 m_amazonLocaleMenu->setItemChecked( id, true );
646 m_currentLocale = id;
650 void CoverManager::coverFetched( const QString &artist, const QString &album ) //SLOT
652 loadCover( artist, album );
653 m_coversFetched++;
654 updateStatusBar();
658 void CoverManager::coverRemoved( const QString &artist, const QString &album ) //SLOT
660 loadCover( artist, album );
661 m_coversFetched--;
662 updateStatusBar();
666 void CoverManager::coverFetcherError()
668 DEBUG_FUNC_INFO
670 m_coverErrors++;
671 updateStatusBar();
675 void CoverManager::stopFetching()
677 Debug::Block block( __PRETTY_FUNCTION__ );
679 m_fetchCovers.clear();
680 m_fetchCounter = 0;
682 //delete all cover fetchers
683 QObjectList list = queryList( "CoverFetcher" );
684 for( int i = 0; i < list.size(); ++i )
685 list.at(i)->deleteLater();
687 m_fetchingCovers = 0;
688 updateStatusBar();
691 // PRIVATE
693 void CoverManager::loadCover( const QString &artist, const QString &album )
695 for( Q3IconViewItem *item = m_coverItems.first(); item; item = m_coverItems.next() )
697 CoverViewItem *coverItem = static_cast<CoverViewItem*>(item);
698 if ( album == coverItem->album() && ( artist == coverItem->artist() || ( artist.isEmpty() && coverItem->artist().isEmpty() ) ) )
700 coverItem->loadCover();
701 return;
706 void CoverManager::setCustomSelectedCovers()
708 //function assumes something is selected
709 Q3PtrList<CoverViewItem> selected = selectedItems();
710 CoverViewItem* first = selected.getFirst();
712 QString artist_id; artist_id.setNum( CollectionDB::instance()->artistID( first->artist() ) );
713 QString album_id; album_id.setNum( CollectionDB::instance()->albumID( first->album() ) );
714 QStringList values = CollectionDB::instance()->albumTracks( artist_id, album_id );
716 QString startPath = ":homedir";
717 if ( !values.isEmpty() ) {
718 KUrl url;
719 url.setPath( values.first() );
720 startPath = url.directory();
722 KUrl file = KFileDialog::getImageOpenUrl( startPath, this, i18n( "Select Cover Image File" ) );
723 if ( !file.isEmpty() ) {
724 kapp->processEvents(); //it may takes a while so process pending events
725 QString tmpFile;
726 QImage image = CollectionDB::fetchImage(file, tmpFile);
727 for ( CoverViewItem* item = selected.first(); item; item = selected.next() ) {
728 CollectionDB::instance()->setAlbumImage( item->artist(), item->album(), image );
729 item->loadCover();
731 KIO::NetAccess::removeTempFile( tmpFile );
735 void CoverManager::fetchSelectedCovers()
737 Q3PtrList<CoverViewItem> selected = selectedItems();
738 for ( CoverViewItem* item = selected.first(); item; item = selected.next() )
739 m_fetchCovers += item->artist() + " @@@ " + item->album();
741 m_fetchingCovers += selected.count();
743 if( !m_fetchCounter ) //loop isn't started yet
744 fetchCoversLoop();
746 updateStatusBar();
750 void CoverManager::deleteSelectedCovers()
752 Q3PtrList<CoverViewItem> selected = selectedItems();
754 int button = KMessageBox::warningContinueCancel( this,
755 i18np( "Are you sure you want to remove this cover from the Collection?",
756 "Are you sure you want to delete these %1 covers from the Collection?",
757 selected.count() ),
758 QString(),
759 KStandardGuiItem::del() );
761 if ( button == KMessageBox::Continue ) {
762 for ( CoverViewItem* item = selected.first(); item; item = selected.next() ) {
763 kapp->processEvents();
764 if ( CollectionDB::instance()->removeAlbumImage( item->artist(), item->album() ) ) //delete selected cover
765 coverRemoved( item->artist(), item->album() );
771 void CoverManager::playSelectedAlbums()
773 Q3PtrList<CoverViewItem> selected = selectedItems();
774 QString artist_id, album_id;
775 for ( CoverViewItem* item = selected.first(); item; item = selected.next() )
777 artist_id.setNum( CollectionDB::instance()->artistID( item->artist() ) );
778 album_id.setNum( CollectionDB::instance()->albumID( item->album() ) );
779 Playlist::instance()->insertMedia( CollectionDB::instance()->albumTracks( artist_id, album_id ), Playlist::Append );
783 Q3PtrList<CoverViewItem> CoverManager::selectedItems()
785 Q3PtrList<CoverViewItem> selectedItems;
786 for ( Q3IconViewItem* item = m_coverView->firstItem(); item; item = item->nextItem() )
787 if ( item->isSelected() )
788 selectedItems.append( static_cast<CoverViewItem*>(item) );
790 return selectedItems;
794 void CoverManager::updateStatusBar()
796 QString text;
798 //cover fetching info
799 if( m_fetchingCovers ) {
800 //update the progress bar
801 m_progress->setMaximum( m_fetchingCovers );
802 m_progress->setValue( m_coversFetched + m_coverErrors );
803 if( m_progressBox->isHidden() )
804 m_progressBox->show();
806 //update the status text
807 if( m_coversFetched + m_coverErrors >= m_progress->value() ) {
808 //fetching finished
809 text = i18n( "Finished." );
810 if( m_coverErrors )
811 text += i18np( " Cover not found", " <b>%1</b> covers not found", m_coverErrors );
812 //reset counters
813 m_fetchingCovers = 0;
814 m_coversFetched = 0;
815 m_coverErrors = 0;
816 QTimer::singleShot( 2000, this, SLOT( updateStatusBar() ) );
819 if( m_fetchingCovers == 1 ) {
820 QStringList values = QStringList::split( " @@@ ", m_fetchCovers[0], true ); //get artist and album name
821 if ( values.count() >= 2 )
823 if( values[0].isEmpty() )
824 text = i18n( "Fetching cover for %1..." , values[1] );
825 else
826 text = i18n( "Fetching cover for %1 - %2...", values[0], values[1] );
829 else if( m_fetchingCovers ) {
830 text = i18np( "Fetching 1 cover: ", "Fetching <b>%1</b> covers... : ", m_fetchingCovers );
831 if( m_coversFetched )
832 text += i18np( "1 fetched", "%1 fetched", m_coversFetched );
833 if( m_coverErrors ) {
834 if( m_coversFetched ) text += i18n(" - ");
835 text += i18np( "1 not found", "%1 not found", m_coverErrors );
837 if( m_coversFetched + m_coverErrors == 0 )
838 text += i18n( "Connecting..." );
841 else {
842 m_coversFetched = 0;
843 m_coverErrors = 0;
845 uint totalCounter = 0, missingCounter = 0;
847 if( m_progressBox->isShown() )
848 m_progressBox->hide();
850 //album info
851 for( Q3IconViewItem *item = m_coverView->firstItem(); item; item = item->nextItem() ) {
852 totalCounter++;
853 if( !static_cast<CoverViewItem*>( item )->hasCover() )
854 missingCounter++; //counter for albums without cover
857 if( !m_filter.isEmpty() )
858 text = i18np( "1 result for \"%1\"", "%1 results for \"%1\"", totalCounter, m_filter );
859 else if( m_artistView->selectedItem() ) {
860 text = i18np( "1 album", "%1 albums", totalCounter );
861 if( m_artistView->selectedItem() != m_artistView->firstChild() ) //showing albums by an artist
863 QString artist = m_artistView->selectedItem()->text(0);
864 if( artist.endsWith( ", The" ) )
865 CollectionView::instance()->manipulateThe( artist, false );
866 text += i18n( " by " ) + artist;
870 if( missingCounter )
871 text += i18n(" - ( <b>%1</b> without cover )", missingCounter );
873 m_fetchButton->setEnabled( missingCounter );
876 m_statusLabel->setText( text );
879 void CoverManager::setStatusText( QString text )
881 m_oldStatusText = m_statusLabel->text();
882 m_statusLabel->setText( text );
885 //////////////////////////////////////////////////////////////////////
886 // CLASS CoverView
887 /////////////////////////////////////////////////////////////////////
889 CoverView::CoverView( QWidget *parent, const char *name, Qt::WFlags f )
890 : K3IconView( parent, name, f )
892 Debug::Block block( __PRETTY_FUNCTION__ );
894 setArrangement( Q3IconView::LeftToRight );
895 setResizeMode( Q3IconView::Adjust );
896 setSelectionMode( Q3IconView::Extended );
897 arrangeItemsInGrid();
898 setAutoArrange( true );
899 setItemsMovable( false );
901 // as long as QIconView only shows tooltips when the cursor is over the
902 // icon (and not the text), we have to create our own tooltips
903 setShowToolTips( false );
905 connect( this, SIGNAL( onItem( Q3IconViewItem * ) ), SLOT( setStatusText( Q3IconViewItem * ) ) );
906 connect( this, SIGNAL( onViewport() ), CoverManager::instance(), SLOT( updateStatusBar() ) );
910 Q3DragObject *CoverView::dragObject()
912 CoverViewItem *item = static_cast<CoverViewItem*>( currentItem() );
913 if( !item )
914 return 0;
916 const QString sql = "SELECT tags.url FROM tags, album WHERE album.name %1 AND tags.album = album.id ORDER BY tags.track;";
917 const QStringList values = CollectionDB::instance()->query( sql.arg( CollectionDB::likeCondition( item->album() ) ) );
919 KUrl::List urls;
920 for( QStringList::ConstIterator it = values.begin(), end = values.end(); it != end; ++it )
921 urls += *it;
923 QString imagePath = CollectionDB::instance()->albumImage( item->artist(), item->album(), false, 1 );
924 K3MultipleDrag *drag = new K3MultipleDrag( this );
925 drag->setPixmap( item->coverPixmap() );
926 drag->addDragObject( new Q3IconDrag( this ) );
927 drag->addDragObject( new Q3ImageDrag( QImage( imagePath ) ) );
928 drag->addDragObject( new K3URLDrag( urls ) );
930 return drag;
933 void CoverView::setStatusText( Q3IconViewItem *item )
935 #define item static_cast<CoverViewItem *>( item )
936 if ( !item )
937 return;
939 bool sampler = false;
940 //compilations have valDummy for artist. see QueryBuilder::addReturnValue(..) for explanation
941 //FIXME: Don't rely on other independent code, use an sql query
942 if( item->artist().isEmpty() ) sampler = true;
944 QString tipContent = i18n( "%1 - %2", sampler ? i18n("Various Artists") : item->artist() , item->album() );
946 CoverManager::instance()->setStatusText( tipContent );
948 #undef item
951 //////////////////////////////////////////////////////////////////////
952 // CLASS CoverViewItem
953 /////////////////////////////////////////////////////////////////////
955 CoverViewItem::CoverViewItem( Q3IconView *parent, Q3IconViewItem *after, const QString &artist, const QString &album )
956 : K3IconViewItem( parent, after, album )
957 , m_artist( artist )
958 , m_album( album )
959 , m_coverImagePath( CollectionDB::instance()->albumImage( m_artist, m_album, false, 0, &m_embedded ) )
960 , m_coverPixmap( )
962 setDragEnabled( true );
963 setDropEnabled( true );
964 calcRect();
967 bool CoverViewItem::hasCover() const
969 return !m_coverImagePath.endsWith( "nocover.png" ) && QFile::exists( m_coverImagePath );
972 void CoverViewItem::loadCover()
974 m_coverImagePath = CollectionDB::instance()->albumImage( m_artist, m_album, false, 1, &m_embedded );
975 m_coverPixmap = QPixmap( m_coverImagePath ); //create the scaled cover
977 repaint();
981 void CoverViewItem::calcRect( const QString& )
983 int thumbWidth = AmarokConfig::coverPreviewSize();
985 QFontMetrics fm = iconView()->fontMetrics();
986 QRect itemPixmapRect( 5, 1, thumbWidth, thumbWidth );
987 QRect itemRect = rect();
988 itemRect.setWidth( thumbWidth + 10 );
989 itemRect.setHeight( thumbWidth + fm.lineSpacing() + 2 );
990 QRect itemTextRect( 0, thumbWidth+2, itemRect.width(), fm.lineSpacing() );
992 setPixmapRect( itemPixmapRect );
993 setTextRect( itemTextRect );
994 setItemRect( itemRect );
998 void CoverViewItem::paintItem(QPainter* p, const QColorGroup& cg)
1000 QRect itemRect = rect();
1002 p->save();
1003 p->translate( itemRect.x(), itemRect.y() );
1005 // draw the border
1006 p->setPen( cg.mid() );
1007 p->drawRect( 0, 0, itemRect.width(), pixmapRect().height()+2 );
1009 // draw the cover image
1010 if( !m_coverPixmap.isNull() )
1011 p->drawPixmap( pixmapRect().x() + (pixmapRect().width() - m_coverPixmap.width())/2,
1012 pixmapRect().y() + (pixmapRect().height() - m_coverPixmap.height())/2, m_coverPixmap );
1014 //justify the album name
1015 QString str = text();
1016 QFontMetrics fm = p->fontMetrics();
1017 int nameWidth = fm.width( str );
1018 str = fm.elidedText( str, Qt::ElideRight, textRect().width() );
1020 p->setPen( cg.text() );
1021 p->drawText( textRect(), Qt::AlignCenter, str );
1023 if( isSelected() ) {
1024 p->setPen( cg.highlight() );
1025 p->drawRect( pixmapRect() );
1026 p->drawRect( pixmapRect().left()+1, pixmapRect().top()+1, pixmapRect().width()-2, pixmapRect().height()-2);
1027 p->drawRect( pixmapRect().left()+2, pixmapRect().top()+2, pixmapRect().width()-4, pixmapRect().height()-4);
1030 p->restore();
1034 void CoverViewItem::dropped( QDropEvent *e, const Q3ValueList<Q3IconDragItem> & )
1036 if( Q3ImageDrag::canDecode( e ) ) {
1037 if( hasCover() ) {
1038 KGuiItem continueButton = KStandardGuiItem::cont();
1039 continueButton.setText( i18n("&Overwrite") );
1040 int button = KMessageBox::warningContinueCancel( iconView(),
1041 i18n( "Are you sure you want to overwrite this cover?"),
1042 i18n("Overwrite Confirmation"),
1043 continueButton );
1044 if( button == KMessageBox::Cancel )
1045 return;
1048 QImage img;
1049 Q3ImageDrag::decode( e, img );
1050 CollectionDB::instance()->setAlbumImage( artist(), album(), img );
1051 m_coverImagePath = CollectionDB::instance()->albumImage( m_artist, m_album, false, 0 );
1052 loadCover();
1057 void CoverViewItem::dragEntered()
1059 setSelected( true );
1063 void CoverViewItem::dragLeft()
1065 setSelected( false );
1068 #include "covermanager.moc"