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"
8 #include "amarokconfig.h"
9 #include "browserToolBar.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>
24 #include <KFileDialog>
25 #include <KIconLoader>
26 #include <KIO/NetAccess>
29 #include <KMenu> //showCoverMenu()
30 #include <KMessageBox> //showCoverMenu()
31 #include <KPushButton>
32 #include <KSqueezedTextLabel> //status label
33 #include <KStandardDirs> //KGlobal::dirs()
38 #include <KWindowSystem>
40 #include <QDesktopWidget> //ctor: desktop size
42 #include <QFontMetrics> //paintItem()
46 #include <QObject> //used to delete all cover fetchers
47 #include <QPainter> //paintItem()
48 #include <QPalette> //paintItem()
51 #include <QProgressBar>
52 #include <q3progressdialog.h>
54 #include <QStringList>
56 #include <QTimer> //search filter timer
58 #include <Q3ValueList>
61 #include <QToolButton>
64 static QString artistToSelectInInitFunction
;
65 CoverManager
*CoverManager::s_instance
= 0;
67 class ArtistItem
: public K3ListViewItem
70 ArtistItem(Q3ListView
*view
, Q3ListViewItem
*item
, const QString
&text
)
71 : K3ListViewItem(view
, item
, text
) {}
73 int compare( Q3ListViewItem
* i
, int col
, bool ascending
) const
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()
92 , m_timer( new QTimer( this ) ) //search filter timer
94 , m_fetchingCovers( 0 )
95 , m_coversFetched( 0 )
100 setObjectName( "TheCoverManager" );
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 );
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" ) ) );
133 qb
.addReturnValue( QueryBuilder::tabAlbum
, QueryBuilder::valName
);
134 qb
.groupBy( QueryBuilder::tabAlbum
, QueryBuilder::valName
);
135 qb
.setOptions( QueryBuilder::optOnlyCompilations
);
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>
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
);
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
);
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()) );
234 m_coverView
= new CoverView( vbox
);
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() );
284 QTimer::singleShot( 0, this, SLOT(init()) );
288 CoverManager::~CoverManager()
292 Amarok::config( "Cover Manager" ).writeEntry( "Window Size", size() );
298 void CoverManager::init()
302 Q3ListViewItem
*item
= 0;
304 if ( !artistToSelectInInitFunction
.isEmpty() )
305 for ( item
= m_artistView
->firstChild(); item
; item
= item
->nextSibling() )
306 if ( item
->text( 0 ) == artistToSelectInInitFunction
)
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
);
322 KWindowSystem::setType( winId(), NET::Utility
);
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
);
343 QString
CoverManager::amazonTld() //static
345 if( AmarokConfig::amazonLocale() == "us" )
347 else if( AmarokConfig::amazonLocale()== "jp" )
349 else if( AmarokConfig::amazonLocale() == "uk" )
351 else if( AmarokConfig::amazonLocale() == "ca" )
354 return AmarokConfig::amazonLocale();
358 void CoverManager::fetchMissingCovers() //SLOT
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();
370 if( !m_fetchCounter
) //loop isn't started yet
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
391 // Wait 1 second, since amazon caps the number of accesses per client
392 QTimer::singleShot( 1000, this, SLOT( fetchCoversLoop() ) );
395 m_fetchCovers
.clear();
402 void CoverManager::showOnce( const QString
&artist
)
405 artistToSelectInInitFunction
= artist
;
406 new CoverManager(); //shows itself
409 s_instance
->activateWindow();
414 void CoverManager::slotArtistSelected( Q3ListViewItem
*item
) //SLOT
416 if( item
->depth() ) //album item
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
439 progress
.repaint(); //ensures the dialog isn't blank
441 //this is an extra processEvent call for the sake of init() and aesthetics
443 kapp
->processEvents();
445 //this can be a bit slow
446 QApplication::setOverrideCursor( Qt::WaitCursor
);
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
);
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() )
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
);
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
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() )
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() )
516 static_cast<CoverViewItem
*>(item
)->loadCover();
522 void CoverManager::showCoverMenu( Q3IconViewItem
*item
, const QPoint
&p
) //SLOT
524 #define item static_cast<CoverViewItem*>(item)
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" ) )
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
)
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
)
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
);
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
);
570 menu
.addAction( unsetAction
);
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)
590 item
->setSelected( true );
591 if ( item
->hasCover() )
592 viewCover( item
->artist(), item
->album(), this );
594 fetchSelectedCovers();
600 void CoverManager::slotSetFilter() //SLOT
602 m_filter
= m_searchEdit
->text();
604 m_coverView
->selectAll( false);
605 Q3IconViewItem
*item
= m_coverView
->firstItem();
608 Q3IconViewItem
*tmp
= item
->nextItem();
609 m_coverView
->takeItem( item
);
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();
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();
642 Q3IconViewItem
*tmp
= item
->nextItem();
643 m_coverView
->takeItem( item
);
647 m_coverView
->setAutoArrange(false );
648 for( Q3IconViewItem
*item
= m_coverItems
.first(); item
; item
= m_coverItems
.next() ) {
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
) )
656 if( id
== AllAlbums
) //show all albums
658 else if( id
== AlbumsWithCover
&& coverItem
->hasCover() ) //show only albums with cover
660 else if( id
== AlbumsWithoutCover
&& !coverItem
->hasCover() ) //show only albums without cover
663 if( show
) m_coverView
->insertItem( item
, m_coverView
->lastItem() );
665 m_coverView
->setAutoArrange( true );
667 m_coverView
->arrangeItemsInGrid();
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
);
687 void CoverManager::coverRemoved( const QString
&artist
, const QString
&album
) //SLOT
689 loadCover( artist
, album
);
695 void CoverManager::coverFetcherError()
704 void CoverManager::stopFetching()
706 Debug::Block
block( __PRETTY_FUNCTION__
);
708 m_fetchCovers
.clear();
711 //delete all cover fetchers
712 QList
<CoverFetcher
*> list
= qFindChildren
<CoverFetcher
*>(this);
713 foreach( CoverFetcher
*cf
, list
)
716 m_fetchingCovers
= 0;
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();
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() ) {
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
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
);
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
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?",
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()
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() ) {
838 text
= i18n( "Finished." );
840 text
+= i18np( " Cover not found", " <b>%1</b> covers not found", m_coverErrors
);
842 m_fetchingCovers
= 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] );
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..." );
874 uint totalCounter
= 0, missingCounter
= 0;
876 if( m_progressBox
->isVisible() )
877 m_progressBox
->hide();
880 for( Q3IconViewItem
*item
= m_coverView
->firstItem(); item
; item
= item
->nextItem() ) {
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
;
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 //////////////////////////////////////////////////////////////////////
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() );
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() ) ) );
949 for( QStringList::ConstIterator it
= values
.begin(), end
= values
.end(); it
!= end
; ++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
) );
962 void CoverView::setStatusText( Q3IconViewItem
*item
)
964 #define item static_cast<CoverViewItem *>( item )
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
);
980 //////////////////////////////////////////////////////////////////////
981 // CLASS CoverViewItem
982 /////////////////////////////////////////////////////////////////////
984 CoverViewItem::CoverViewItem( Q3IconView
*parent
, Q3IconViewItem
*after
, const QString
&artist
, const QString
&album
)
985 : K3IconViewItem( parent
, after
, album
)
988 , m_coverImagePath( CollectionDB::instance()->albumImage( m_artist
, m_album
, false, 0, &m_embedded
) )
991 setDragEnabled( true );
992 setDropEnabled( true );
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
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();
1034 p
->translate( itemRect
.x(), itemRect
.y() );
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);
1064 void CoverViewItem::dropped( QDropEvent
*e
, const Q3ValueList
<Q3IconDragItem
> & )
1066 if( Q3ImageDrag::canDecode( e
) ) {
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"),
1074 if( button
== KMessageBox::Cancel
)
1079 Q3ImageDrag::decode( e
, img
);
1080 CollectionDB::instance()->setAlbumImage( artist(), album(), img
);
1081 m_coverImagePath
= CollectionDB::instance()->albumImage( m_artist
, m_album
, false, 0 );
1087 void CoverViewItem::dragEntered()
1089 setSelected( true );
1093 void CoverViewItem::dragLeft()
1095 setSelected( false );
1098 #include "covermanager.moc"