1 /****************************************************************************************
2  * Copyright (c) 2004 Pierpaolo Di Panfilo <pippo_dp@libero.it>                         *
3  * Copyright (c) 2005 Isaiah Damron <xepo@trifault.net>                                 *
4  * Copyright (c) 2007 Dan Meltzer <parallelgrapefruit@gmail.com>                        *
5  * Copyright (c) 2008 Seb Ruiz <ruiz@kde.org>                                           *
6  *                                                                                      *
7  * This program is free software; you can redistribute it and/or modify it under        *
8  * the terms of the GNU General Public License as published by the Free Software        *
9  * Foundation; either version 2 of the License, or (at your option) any later           *
10  * version.                                                                             *
11  *                                                                                      *
12  * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
13  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
14  * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
15  *                                                                                      *
16  * You should have received a copy of the GNU General Public License along with         *
17  * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
18  ****************************************************************************************/
19 
20 #define DEBUG_PREFIX "CoverManager"
21 
22 #include "CoverManager.h"
23 
24 #include "amarokconfig.h"
25 #include <config.h>
26 #include "core/capabilities/ActionsCapability.h"
27 #include "core/collections/Collection.h"
28 #include "core/collections/QueryMaker.h"
29 #include "core/meta/Meta.h"
30 #include "core/support/Amarok.h"
31 #include "core/support/Debug.h"
32 #include "core-impl/collections/support/CollectionManager.h"
33 #include "covermanager/CoverFetchingActions.h"
34 #include "covermanager/CoverViewDialog.h"
35 #include "playlist/PlaylistController.h"
36 #include "statusbar/CompoundProgressBar.h"
37 #include "widgets/LineEdit.h"
38 #include "widgets/PixmapViewer.h"
39 
40 #include <QAction>
41 #include <QApplication>
42 #include <QDesktopWidget>
43 #include <QDialogButtonBox>
44 #include <QMenu>    //showCoverMenu()
45 #include <QProgressBar>
46 #include <QPushButton>
47 #include <QSplitter>
48 #include <QStatusBar>
49 #include <QStringList>
50 #include <QTimer>    //search filter timer
51 #include <QToolButton>
52 #include <QTreeWidget>
53 #include <QTreeWidgetItem>
54 #include <QVBoxLayout>
55 
56 #include <KConfigGroup>
57 #include <KIconLoader>
58 #include <KLocalizedString>
59 #include <KSqueezedTextLabel> //status label
60 #include <KToolBar>
61 
62 static QString artistToSelectInInitFunction;
63 CoverManager *CoverManager::s_instance = nullptr;
64 
65 class ArtistItem : public QTreeWidgetItem
66 {
67     public:
ArtistItem(QTreeWidget * parent,Meta::ArtistPtr artist)68         ArtistItem( QTreeWidget *parent, Meta::ArtistPtr artist )
69             : QTreeWidgetItem( parent )
70             , m_artist( artist )
71         {
72             setText( 0, artist->prettyName() );
73         }
74 
ArtistItem(const QString & text,QTreeWidget * parent=nullptr)75         ArtistItem( const QString &text, QTreeWidget *parent = nullptr )
76             : QTreeWidgetItem( parent )
77             , m_artist( 0 )
78         {
79             setText( 0, text );
80         }
81 
artist() const82         Meta::ArtistPtr artist() const { return m_artist; }
83 
84     private:
85         Meta::ArtistPtr m_artist;
86 };
87 
88 
CoverManager(QWidget * parent)89 CoverManager::CoverManager( QWidget *parent )
90         : QDialog( parent )
91         , m_currentView( AllAlbums )
92         , m_timer( new QTimer( this ) )    //search filter timer
93         , m_fetchingCovers( false )
94         , m_coversFetched( 0 )
95         , m_coverErrors( 0 )
96         , m_isLoadingCancelled( false )
97 {
98     DEBUG_BLOCK
99 
100     setObjectName( "TheCoverManager" );
101 
102     s_instance = this;
103 
104     // Sets caption and icon correctly (needed e.g. for GNOME)
105     //kapp->setTopWidget( this );
106 
107     QDialogButtonBox *buttonBox = new QDialogButtonBox( QDialogButtonBox::Close, this );
108     QVBoxLayout *mainLayout = new QVBoxLayout(this);
109     connect(buttonBox, &QDialogButtonBox::accepted, this, &CoverManager::accept);
110     connect(buttonBox, &QDialogButtonBox::rejected, this, &CoverManager::reject);
111     setWindowTitle( i18n("Cover Manager") );
112     setAttribute( Qt::WA_DeleteOnClose );
113 
114     // TODO: There is no hidden signal in QDialog. Needs porting to QT5.
115 //     connect( this, &CoverManager::hidden, this, &CoverManager::delayedDestruct );
116     connect( buttonBox->button(QDialogButtonBox::Close), &QAbstractButton::clicked, this, &CoverManager::delayedDestruct );
117 
118     m_splitter = new QSplitter( this );
119     mainLayout->addWidget(m_splitter);
120     mainLayout->addWidget(buttonBox);
121 
122     //artist listview
123     m_artistView = new QTreeWidget( m_splitter );
124     m_artistView->setHeaderLabel( i18n( "Albums By" ) );
125     m_artistView->setSortingEnabled( false );
126     m_artistView->setTextElideMode( Qt::ElideRight );
127     m_artistView->setMinimumWidth( 200 );
128     m_artistView->setColumnCount( 1 );
129     m_artistView->setAlternatingRowColors( true );
130     m_artistView->setUniformRowHeights( true );
131     m_artistView->setSelectionMode( QAbstractItemView::ExtendedSelection );
132 
133     ArtistItem *item = 0;
134     item = new ArtistItem( i18n( "All Artists" ) );
135     item->setIcon(0, SmallIcon( "media-optical-audio-amarok" ) );
136     m_items.append( item );
137 
138     Collections::Collection *coll = CollectionManager::instance()->primaryCollection();
139     Collections::QueryMaker *qm = coll->queryMaker();
140     qm->setAutoDelete( true );
141     qm->setQueryType( Collections::QueryMaker::Artist );
142     qm->setAlbumQueryMode( Collections::QueryMaker::OnlyNormalAlbums );
143     qm->orderBy( Meta::valArtist );
144 
145     connect( qm, &Collections::QueryMaker::newArtistsReady,
146              this, &CoverManager::slotArtistQueryResult );
147 
148     connect( qm, &Collections::QueryMaker::queryDone, this, &CoverManager::slotContinueConstruction );
149 
150     qm->run();
151 }
152 
153 void
slotArtistQueryResult(Meta::ArtistList artists)154 CoverManager::slotArtistQueryResult( Meta::ArtistList artists ) //SLOT
155 {
156     DEBUG_BLOCK
157     foreach( Meta::ArtistPtr artist, artists )
158         m_artistList << artist;
159 }
160 
161 void
slotContinueConstruction()162 CoverManager::slotContinueConstruction() //SLOT
163 {
164     DEBUG_BLOCK
165     foreach( Meta::ArtistPtr artist, m_artistList )
166     {
167         ArtistItem* item = new ArtistItem( m_artistView, artist );
168         item->setIcon( 0, SmallIcon( "view-media-artist-amarok" ) );
169         m_items.append( item );
170     }
171     m_artistView->insertTopLevelItems( 0, m_items );
172 
173     BoxWidget *vbox = new BoxWidget( true, m_splitter );
174     BoxWidget *hbox = new BoxWidget( false, vbox );
175     vbox->layout()->setSpacing( 4 );
176     hbox->layout()->setSpacing( 4 );
177 
178     { //<Search LineEdit>
179         m_searchEdit = new Amarok::LineEdit( hbox );
180         m_searchEdit->setPlaceholderText( i18n( "Enter search terms here" ) );
181         m_searchEdit->setFrame( true );
182 
183         m_searchEdit->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Minimum);
184         m_searchEdit->setClearButtonEnabled( true );
185 
186         static_cast<QHBoxLayout*>( hbox->layout() )->setStretchFactor( m_searchEdit, 1 );
187     } //</Search LineEdit>
188 
189     // view menu
190     m_viewButton = new QPushButton( hbox );
191 
192     m_viewMenu = new QMenu( m_viewButton );
193     m_selectAllAlbums          = m_viewMenu->addAction( i18n("All Albums"),           this, &CoverManager::slotShowAllAlbums );
194     m_selectAlbumsWithCover    = m_viewMenu->addAction( i18n("Albums With Cover"),    this, &CoverManager::slotShowAlbumsWithCover );
195     m_selectAlbumsWithoutCover = m_viewMenu->addAction( i18n("Albums Without Cover"), this, &CoverManager::slotShowAlbumsWithoutCover );
196 
197     QActionGroup *viewGroup = new QActionGroup( m_viewButton );
198     viewGroup->setExclusive( true );
199     viewGroup->addAction( m_selectAllAlbums );
200     viewGroup->addAction( m_selectAlbumsWithCover );
201     viewGroup->addAction( m_selectAlbumsWithoutCover );
202 
203     m_viewButton->setMenu( m_viewMenu );
204     m_viewButton->setIcon( QIcon::fromTheme( QStringLiteral("filename-album-amarok") ) );
205     connect( m_viewMenu, &QMenu::triggered, this, &CoverManager::slotAlbumFilterTriggered );
206 
207     //fetch missing covers button
208     m_fetchButton = new QPushButton( QIcon( "get-hot-new-stuff-amarok" ), i18n("Fetch Missing Covers"), hbox );
209     connect( m_fetchButton, &QAbstractButton::clicked, this, &CoverManager::fetchMissingCovers );
210 
211     m_selectAllAlbums->setChecked( true );
212     m_selectAllAlbums->trigger();
213 
214     //cover view
215     m_coverView = new CoverView( vbox );
216     m_coverViewSpacer = new CoverView( vbox );
217     m_coverViewSpacer->hide();
218 
219     //status bar
220     QStatusBar *statusBar = new QStatusBar( vbox );
221 
222     m_statusLabel = new KSqueezedTextLabel( statusBar );
223     m_statusLabel->setIndent( 3 );
224     m_progress = new CompoundProgressBar( statusBar );
225 
226     statusBar->addWidget( m_statusLabel, 4 );
227     statusBar->addPermanentWidget( m_progress, 1 );
228 
229     connect( m_progress, &CompoundProgressBar::allDone, this, &CoverManager::progressAllDone );
230 
231     QSize size = QApplication::desktop()->screenGeometry( this ).size() / 1.5;
232     QSize sz = Amarok::config( "Cover Manager" ).readEntry( "Window Size", size );
233     resize( sz.width(), sz.height() );
234 
235     m_splitter->setStretchFactor( m_splitter->indexOf( m_artistView ), 1 );
236     m_splitter->setStretchFactor( m_splitter->indexOf( vbox ), 4 );
237 
238     m_fetcher = The::coverFetcher();
239 
240     QTreeWidgetItem *item = 0;
241     int i = 0;
242     if ( !artistToSelectInInitFunction.isEmpty() )
243     {
244         for( item = m_artistView->invisibleRootItem()->child( 0 );
245              i < m_artistView->invisibleRootItem()->childCount();
246              item = m_artistView->invisibleRootItem()->child( i++ ) )
247         {
248             if ( item->text( 0 ) == artistToSelectInInitFunction )
249                 break;
250         }
251     }
252 
253     // signals and slots connections
254     connect( m_artistView, &QTreeWidget::itemSelectionChanged,
255              this, &CoverManager::slotArtistSelected );
256     connect( m_coverView, &CoverView::itemActivated,
257              this, &CoverManager::coverItemClicked );
258     connect( m_timer, &QTimer::timeout,
259              this, &CoverManager::slotSetFilter );
260     connect( m_searchEdit, &Amarok::LineEdit::textChanged,
261              this, &CoverManager::slotSetFilterTimeout );
262 
263     if( item == 0 )
264         item = m_artistView->invisibleRootItem()->child( 0 );
265 
266     item->setSelected( true );
267     show();
268 }
269 
~CoverManager()270 CoverManager::~CoverManager()
271 {
272     Amarok::config( "Cover Manager" ).writeEntry( "Window Size", size() );
273     qDeleteAll( m_coverItems );
274     delete m_coverView;
275     m_coverView = 0;
276     s_instance = nullptr;
277 }
278 
279 void
viewCover(const Meta::AlbumPtr & album,QWidget * parent)280 CoverManager::viewCover( const Meta::AlbumPtr &album, QWidget *parent ) //static
281 {
282     //QDialog means "escape" works as expected
283     QDialog *dialog = new CoverViewDialog( album, parent );
284     dialog->show();
285 }
286 
287 void
metadataChanged(const Meta::AlbumPtr & album)288 CoverManager::metadataChanged(const Meta::AlbumPtr &album )
289 {
290     const QString albumName = album->name();
291     foreach( CoverViewItem *item, m_coverItems )
292     {
293         if( albumName == item->albumPtr()->name() )
294             item->loadCover();
295     }
296     updateStatusBar();
297 }
298 
299 void
fetchMissingCovers()300 CoverManager::fetchMissingCovers() //SLOT
301 {
302     m_fetchCovers.clear();
303     for( int i = 0, coverCount = m_coverView->count(); i < coverCount; ++i )
304     {
305         QListWidgetItem *item = m_coverView->item( i );
306         CoverViewItem *coverItem = static_cast<CoverViewItem*>( item );
307         if( !coverItem->hasCover() )
308             m_fetchCovers += coverItem->albumPtr();
309     }
310 
311     debug() << QString( "Fetching %1 missing covers" ).arg( m_fetchCovers.size() );
312 
313     ProgressBar *fetchProgressBar = new ProgressBar( this );
314     fetchProgressBar->setDescription( i18n( "Fetching" ) );
315     fetchProgressBar->setMaximum( m_fetchCovers.size() );
316     m_progress->addProgressBar( fetchProgressBar, m_fetcher );
317     m_progress->show();
318 
319     m_fetcher->queueAlbums( m_fetchCovers );
320     m_fetchingCovers = true;
321 
322     updateStatusBar();
323     m_fetchButton->setEnabled( false );
324     connect( m_fetcher, &CoverFetcher::finishedSingle, this, &CoverManager::updateFetchingProgress );
325 }
326 
327 void
showOnce(const QString & artist,QWidget * parent)328 CoverManager::showOnce( const QString &artist, QWidget* parent )
329 {
330     if( !s_instance )
331     {
332         artistToSelectInInitFunction = artist;
333         new CoverManager( parent );
334     }
335     else
336     {
337         s_instance->activateWindow();
338         s_instance->raise();
339     }
340 }
341 
342 void
slotArtistSelected()343 CoverManager::slotArtistSelected() //SLOT
344 {
345     DEBUG_BLOCK
346 
347     // delete cover items before clearing cover view
348     qDeleteAll( m_coverItems );
349     m_coverItems.clear();
350     m_coverView->clear();
351 
352     //this can be a bit slow
353     QApplication::setOverrideCursor( Qt::WaitCursor );
354 
355     Collections::Collection *coll = CollectionManager::instance()->primaryCollection();
356 
357     Collections::QueryMaker *qm = coll->queryMaker();
358     qm->setAutoDelete( true );
359     qm->setQueryType( Collections::QueryMaker::Album );
360     qm->orderBy( Meta::valAlbum );
361 
362     qm->beginOr();
363     const QList< QTreeWidgetItem* > items = m_artistView->selectedItems();
364     foreach( const QTreeWidgetItem *item, items )
365     {
366         const ArtistItem *artistItem = static_cast< const ArtistItem* >( item );
367         if( artistItem != m_artistView->invisibleRootItem()->child( 0 ) )
368             qm->addFilter( Meta::valArtist, artistItem->artist()->name(), true, true );
369         else
370             qm->excludeFilter( Meta::valAlbum, QString(), true, true );
371     }
372     qm->endAndOr();
373 
374     // do not show albums with no name, i.e. tracks not belonging to any album
375     qm->beginAnd();
376     qm->excludeFilter( Meta::valAlbum, QString(), true, true );
377     qm->endAndOr();
378 
379     connect( qm, &Collections::QueryMaker::newAlbumsReady,
380              this, &CoverManager::slotAlbumQueryResult );
381 
382     connect( qm, &Collections::QueryMaker::queryDone, this, &CoverManager::slotArtistQueryDone );
383 
384     qm->run();
385 }
386 
387 void
slotAlbumQueryResult(const Meta::AlbumList & albums)388 CoverManager::slotAlbumQueryResult( const Meta::AlbumList &albums ) //SLOT
389 {
390     m_albumList = albums;
391 }
392 
393 void
slotAlbumFilterTriggered(QAction * action)394 CoverManager::slotAlbumFilterTriggered( QAction *action ) //SLOT
395 {
396     m_viewButton->setText( action->text() );
397 }
398 
399 void
slotArtistQueryDone()400 CoverManager::slotArtistQueryDone() //SLOT
401 {
402     DEBUG_BLOCK
403 
404     QApplication::restoreOverrideCursor();
405 
406     const int albumCount = m_albumList.count();
407 
408     ProgressBar *coverLoadProgressBar = new ProgressBar( this );
409     coverLoadProgressBar->setDescription( i18n( "Loading" ) );
410     coverLoadProgressBar->setMaximum( albumCount );
411     connect( coverLoadProgressBar, &ProgressBar::cancelled,
412              this, &CoverManager::cancelCoverViewLoading );
413 
414     m_progress->addProgressBar( coverLoadProgressBar, m_coverView );
415     m_progress->show();
416 
417     uint x = 0;
418     debug() << "Loading covers for selected artist(s)";
419 
420     //the process events calls below causes massive flickering in the m_albumList
421     //so we hide this view and only show it when all items has been inserted. This
422     //also provides quite a massive speed improvement when loading covers.
423     m_coverView->hide();
424     m_coverViewSpacer->show();
425     foreach( const Meta::AlbumPtr &album, m_albumList )
426     {
427         qApp->processEvents( QEventLoop::ExcludeSocketNotifiers );
428         if( isHidden() )
429         {
430             m_progress->endProgressOperation( m_coverView );
431             return;
432         }
433         /*
434          * Loading is stopped if cancelled by the user, or the number of albums
435          * has changed. The latter occurs when the artist selection changes.
436          */
437         if( m_isLoadingCancelled || albumCount != m_albumList.count() )
438         {
439             m_isLoadingCancelled = false;
440             break;
441         }
442 
443         CoverViewItem *item = new CoverViewItem( m_coverView, album );
444         m_coverItems.append( item );
445 
446         if( ++x % 10 == 0 )
447         {
448             m_progress->setProgress( m_coverView, x );
449         }
450     }
451     m_progress->endProgressOperation( m_coverView );
452 
453     // makes sure View is retained when artist selection changes
454     changeView( m_currentView, true );
455 
456     m_coverViewSpacer->hide();
457     m_coverView->show();
458     updateStatusBar();
459 }
460 
461 void
cancelCoverViewLoading()462 CoverManager::cancelCoverViewLoading()
463 {
464     m_isLoadingCancelled = true;
465 }
466 
467 // called when a cover item is clicked
468 void
coverItemClicked(QListWidgetItem * item)469 CoverManager::coverItemClicked( QListWidgetItem *item ) //SLOT
470 {
471     #define item static_cast<CoverViewItem*>(item)
472 
473     if( !item ) return;
474 
475     item->setSelected( true );
476     if ( item->hasCover() )
477         viewCover( item->albumPtr(), this );
478     else
479         m_fetcher->manualFetch( item->albumPtr() );
480 
481     #undef item
482 }
483 
484 
485 void
slotSetFilter()486 CoverManager::slotSetFilter() //SLOT
487 {
488     m_filter = m_searchEdit->text();
489 
490     m_coverView->clearSelection();
491     uint i = 0;
492     QListWidgetItem *item = m_coverView->item( i );
493     while ( item )
494     {
495         QListWidgetItem *tmp = m_coverView->item( i + 1 );
496         m_coverView->takeItem( i );
497         item = tmp;
498     }
499 
500     foreach( QListWidgetItem *item, m_coverItems )
501     {
502         CoverViewItem *coverItem = static_cast<CoverViewItem*>(item);
503         if( coverItem->album().contains( m_filter, Qt::CaseInsensitive ) || coverItem->artist().contains( m_filter, Qt::CaseInsensitive ) )
504             m_coverView->insertItem( m_coverView->count() -  1, item );
505     }
506 
507     // makes sure View is retained when filter text has changed
508     changeView( m_currentView, true );
509     updateStatusBar();
510 }
511 
512 
513 void
slotSetFilterTimeout()514 CoverManager::slotSetFilterTimeout() //SLOT
515 {
516     if ( m_timer->isActive() ) m_timer->stop();
517     m_timer->setSingleShot( true );
518     m_timer->start( 180 );
519 }
520 
521 void
changeView(CoverManager::View id,bool force)522 CoverManager::changeView( CoverManager::View id, bool force ) //SLOT
523 {
524     if( !force && m_currentView == id )
525         return;
526 
527     //clear the iconview without deleting items
528     m_coverView->clearSelection();
529     int itemsCount = m_coverView->count();
530     while( itemsCount-- > 0 )
531        m_coverView->takeItem( 0 );
532 
533     foreach( QListWidgetItem *item, m_coverItems )
534     {
535         bool show = false;
536         CoverViewItem *coverItem = static_cast<CoverViewItem*>(item);
537         if( !m_filter.isEmpty() )
538         {
539             if( !coverItem->album().contains( m_filter, Qt::CaseInsensitive ) &&
540                 !coverItem->artist().contains( m_filter, Qt::CaseInsensitive ) )
541                 continue;
542         }
543 
544         if( id == AllAlbums )    //show all albums
545             show = true;
546         else if( id == AlbumsWithCover && coverItem->hasCover() )    //show only albums with cover
547             show = true;
548         else if( id == AlbumsWithoutCover && !coverItem->hasCover() )//show only albums without cover
549             show = true;
550 
551         if( show )
552             m_coverView->insertItem( m_coverView->count() - 1, item );
553     }
554     m_currentView = id;
555 }
556 
557 void
updateFetchingProgress(int state)558 CoverManager::updateFetchingProgress( int state )
559 {
560     switch( static_cast< CoverFetcher::FinishState >( state ) )
561     {
562     case CoverFetcher::Success:
563         m_coversFetched++;
564         break;
565 
566     case CoverFetcher::Cancelled:
567     case CoverFetcher::Error:
568     case CoverFetcher::NotFound:
569     default:
570         m_coverErrors++;
571         break;
572     }
573     m_progress->incrementProgress( m_fetcher );
574     updateStatusBar();
575 }
576 
577 void
stopFetching()578 CoverManager::stopFetching()
579 {
580     DEBUG_FUNC_INFO
581 
582     m_fetchCovers.clear();
583     m_fetchingCovers = false;
584     m_progress->endProgressOperation( m_fetcher );
585     updateStatusBar();
586 }
587 
588 void
loadCover(const QString & artist,const QString & album)589 CoverManager::loadCover( const QString &artist, const QString &album )
590 {
591     foreach( QListWidgetItem *item, m_coverItems )
592     {
593         CoverViewItem *coverItem = static_cast<CoverViewItem*>(item);
594         if ( album == coverItem->album() && ( artist == coverItem->artist() || ( artist.isEmpty() && coverItem->artist().isEmpty() ) ) )
595         {
596             coverItem->loadCover();
597             return;
598         }
599     }
600 }
601 
602 void
progressAllDone()603 CoverManager::progressAllDone()
604 {
605     m_progress->hide();
606 }
607 
608 void
updateStatusBar()609 CoverManager::updateStatusBar()
610 {
611     QString text;
612 
613     //cover fetching info
614     if( m_fetchingCovers )
615     {
616         //update the status text
617         if( m_coversFetched + m_coverErrors >= m_fetchCovers.size() )
618         {
619             //fetching finished
620             text = i18nc( "The fetching is done.", "Finished." );
621             if( m_coverErrors )
622                 text += i18np( " Cover not found", " <b>%1</b> covers not found", m_coverErrors );
623             //reset counters
624             m_coversFetched = 0;
625             m_coverErrors = 0;
626             m_fetchCovers.clear();
627             m_fetchingCovers = false;
628             m_progress->endProgressOperation( m_fetcher );
629 
630             disconnect( m_fetcher, &CoverFetcher::finishedSingle, this, &CoverManager::updateFetchingProgress );
631             QTimer::singleShot( 2000, this, &CoverManager::updateStatusBar );
632         }
633 
634         if( m_fetchCovers.size() == 1 )
635         {
636             foreach( Meta::AlbumPtr album, m_fetchCovers )
637             {
638                 if( album->hasAlbumArtist() && !album->albumArtist()->prettyName().isEmpty() )
639                 {
640                     text = i18n( "Fetching cover for %1 - %2...",
641                                  album->albumArtist()->prettyName(),
642                                  album->prettyName() );
643                 }
644                 else
645                 {
646                     text = i18n( "Fetching cover for %1..." , album->prettyName() );
647                 }
648             }
649         }
650         else
651         {
652             text = i18np( "Fetching 1 cover: ", "Fetching <b>%1</b> covers... : ", m_fetchCovers.size() );
653             if( m_coversFetched )
654                 text += i18np( "1 fetched", "%1 fetched", m_coversFetched );
655             if( m_coverErrors )
656             {
657                 if( m_coversFetched )
658                     text += i18n(" - ");
659                 text += i18np( "1 not found", "%1 not found", m_coverErrors );
660             }
661             if( m_coversFetched + m_coverErrors == 0 )
662                 text += i18n( "Connecting..." );
663         }
664     }
665     else
666     {
667         m_coversFetched = 0;
668         m_coverErrors = 0;
669 
670         uint totalCounter = 0, missingCounter = 0;
671 
672         //album info
673         for( int i = 0, coverCount = m_coverView->count(); i < coverCount; ++i )
674         {
675             totalCounter++;
676             QListWidgetItem *item = m_coverView->item( i );
677             if( !static_cast<CoverViewItem*>( item )->hasCover() )
678                 missingCounter++;    //counter for albums without cover
679         }
680 
681         const QList< QTreeWidgetItem* > selected = m_artistView->selectedItems();
682 
683         if( !m_filter.isEmpty() )
684         {
685             text = i18np( "1 result for \"%2\"", "%1 results for \"%2\"", totalCounter, m_filter );
686         }
687         else if( selected.count() > 0 )
688         {
689             text = i18np( "1 album", "%1 albums", totalCounter );
690 
691             // showing albums by selected artist(s)
692             if( selected.first() != m_artistView->invisibleRootItem()->child( 0 ) )
693             {
694                 QStringList artists;
695                 foreach( const QTreeWidgetItem *item, selected )
696                 {
697                     QString artist = item->text( 0 );
698                     Amarok::manipulateThe( artist, false );
699                     artists.append( artist );
700                 }
701                 text = i18n( "%1 by %2", text, artists.join( i18nc("Separator for artists", ", ")) );
702             }
703         }
704 
705         if( missingCounter )
706             text = i18np("%2 - ( <b>1</b> without cover )", "%2 - ( <b>%1</b> without cover )",
707                          missingCounter, text );
708 
709         m_fetchButton->setEnabled( missingCounter );
710     }
711 
712     m_statusLabel->setText( text );
713 }
714 
715 void
delayedDestruct()716 CoverManager::delayedDestruct()
717 {
718     if ( isVisible() )
719         hide();
720 
721     deleteLater();
722 }
723 
724 void
setStatusText(const QString & text)725 CoverManager::setStatusText( const QString &text )
726 {
727     m_oldStatusText = m_statusLabel->text();
728     m_statusLabel->setText( text );
729 }
730 
731 //////////////////////////////////////////////////////////////////////
732 //    CLASS CoverView
733 /////////////////////////////////////////////////////////////////////
734 
CoverView(QWidget * parent,const char * name,Qt::WindowFlags f)735 CoverView::CoverView( QWidget *parent, const char *name, Qt::WindowFlags f )
736     : QListWidget( parent )
737 {
738     DEBUG_BLOCK
739 
740     setObjectName( name );
741     setWindowFlags( f );
742     setViewMode( QListView::IconMode );
743     setMovement( QListView::Static );
744     setResizeMode( QListView::Adjust );
745     setSelectionMode( QAbstractItemView::ExtendedSelection );
746     setWrapping( true );
747     setWordWrap( true );
748     setIconSize( QSize(100, 100) );
749     setGridSize( QSize(120, 160) );
750     setTextElideMode( Qt::ElideRight );
751     setContextMenuPolicy( Qt::DefaultContextMenu );
752     setMouseTracking( true ); // required for setting status text when itemEntered signal is emitted
753 
754     connect( this, &CoverView::itemEntered, this, &CoverView::setStatusText );
755     connect( this, &CoverView::viewportEntered, CoverManager::instance(), &CoverManager::updateStatusBar );
756 }
757 
758 void
contextMenuEvent(QContextMenuEvent * event)759 CoverView::contextMenuEvent( QContextMenuEvent *event )
760 {
761     QList<QListWidgetItem*> items = selectedItems();
762     const int itemsCount = items.count();
763 
764     QMenu menu;
765     menu.addSection( i18n( "Cover Image" ) );
766 
767     if( itemsCount == 1 )
768     {
769         // only one item selected: get all custom actions this album is capable of.
770         CoverViewItem *item = dynamic_cast<CoverViewItem*>( items.first() );
771         QList<QAction *> actions;
772         Meta::AlbumPtr album = item->albumPtr();
773         if( album )
774         {
775             QScopedPointer<Capabilities::ActionsCapability> ac( album->create<Capabilities::ActionsCapability>() );
776             if( ac )
777             {
778                 actions = ac->actions();
779                 foreach( QAction *action, actions )
780                     menu.addAction( action );
781             }
782         }
783         menu.exec( event->globalPos() );
784     }
785     else if( itemsCount > 1 )
786     {
787         // multiple albums selected: only unset cover and fetch cover actions
788         // make sense here, and perhaps (un)setting compilation flag (TODO).
789 
790         Meta::AlbumList unsetAlbums;
791         Meta::AlbumList fetchAlbums;
792 
793         foreach( QListWidgetItem *item, items )
794         {
795             CoverViewItem *cvItem = dynamic_cast<CoverViewItem*>(item);
796             Meta::AlbumPtr album = cvItem->albumPtr();
797             if( album )
798             {
799                 QScopedPointer<Capabilities::ActionsCapability> ac( album->create<Capabilities::ActionsCapability>() );
800                 if( ac )
801                 {
802                     QList<QAction *> actions = ac->actions();
803                     foreach( QAction *action, actions )
804                     {
805                         if( qobject_cast<FetchCoverAction*>(action) )
806                             fetchAlbums << album;
807                         else if( qobject_cast<UnsetCoverAction*>(action) )
808                             unsetAlbums << album;
809                     }
810                 }
811             }
812         }
813 
814         if( itemsCount == fetchAlbums.count() )
815         {
816             FetchCoverAction *fetchAction = new FetchCoverAction( this, fetchAlbums );
817             menu.addAction( fetchAction );
818         }
819         if( itemsCount == unsetAlbums.count() )
820         {
821             UnsetCoverAction *unsetAction = new UnsetCoverAction( this, unsetAlbums );
822             menu.addAction( unsetAction );
823         }
824         menu.exec( event->globalPos() );
825     }
826     else
827         QListView::contextMenuEvent( event );
828 
829     // TODO: Play, Load and Append to playlist actions
830 }
831 
832 void
setStatusText(QListWidgetItem * item)833 CoverView::setStatusText( QListWidgetItem *item )
834 {
835     #define itemmacro static_cast<CoverViewItem *>( item )
836     if ( !itemmacro )
837         return;
838 
839     const QString artist = itemmacro->albumPtr()->isCompilation() ? i18n( "Various Artists" ) : itemmacro->artist();
840     const QString tipContent = i18n( "%1 - %2", artist , itemmacro->album() );
841     CoverManager::instance()->setStatusText( tipContent );
842     #undef item
843 }
844 
845 //////////////////////////////////////////////////////////////////////
846 //    CLASS CoverViewItem
847 /////////////////////////////////////////////////////////////////////
848 
CoverViewItem(QListWidget * parent,Meta::AlbumPtr album)849 CoverViewItem::CoverViewItem( QListWidget *parent, Meta::AlbumPtr album )
850     : QListWidgetItem( parent )
851     , m_albumPtr( album)
852 {
853     m_album = album->prettyName();
854     if( album->hasAlbumArtist() )
855         m_artist = album->albumArtist()->prettyName();
856     else
857         m_artist = i18n( "No Artist" );
858     setText( album->prettyName() );
859 
860     loadCover();
861 
862     CoverManager::instance()->subscribeTo( album );
863 }
864 
~CoverViewItem()865 CoverViewItem::~CoverViewItem()
866 {}
867 
868 bool
hasCover() const869 CoverViewItem::hasCover() const
870 {
871     return albumPtr()->hasImage();
872 }
873 
874 void
loadCover()875 CoverViewItem::loadCover()
876 {
877     const bool isSuppressing = m_albumPtr->suppressImageAutoFetch();
878     m_albumPtr->setSuppressImageAutoFetch( true );
879     setIcon( QPixmap::fromImage( m_albumPtr->image( 100 ) ) );
880     m_albumPtr->setSuppressImageAutoFetch( isSuppressing );
881 }
882 
883 void
dragEntered()884 CoverViewItem::dragEntered()
885 {
886     setSelected( true );
887 }
888 
889 
890 void
dragLeft()891 CoverViewItem::dragLeft()
892 {
893     setSelected( false );
894 }
895 
896 
897