File indexing completed on 2024-05-05 04:48:22

0001 /****************************************************************************************
0002  * Copyright (c) 2004 Pierpaolo Di Panfilo <pippo_dp@libero.it>                         *
0003  * Copyright (c) 2005 Isaiah Damron <xepo@trifault.net>                                 *
0004  * Copyright (c) 2007 Dan Meltzer <parallelgrapefruit@gmail.com>                        *
0005  * Copyright (c) 2008 Seb Ruiz <ruiz@kde.org>                                           *
0006  *                                                                                      *
0007  * This program is free software; you can redistribute it and/or modify it under        *
0008  * the terms of the GNU General Public License as published by the Free Software        *
0009  * Foundation; either version 2 of the License, or (at your option) any later           *
0010  * version.                                                                             *
0011  *                                                                                      *
0012  * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
0013  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
0014  * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
0015  *                                                                                      *
0016  * You should have received a copy of the GNU General Public License along with         *
0017  * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
0018  ****************************************************************************************/
0019 
0020 #define DEBUG_PREFIX "CoverManager"
0021 
0022 #include "CoverManager.h"
0023 
0024 #include "amarokconfig.h"
0025 #include <config.h>
0026 #include "core/capabilities/ActionsCapability.h"
0027 #include "core/collections/Collection.h"
0028 #include "core/collections/QueryMaker.h"
0029 #include "core/meta/Meta.h"
0030 #include "core/support/Amarok.h"
0031 #include "core/support/Debug.h"
0032 #include "core-impl/collections/support/CollectionManager.h"
0033 #include "covermanager/CoverFetchingActions.h"
0034 #include "covermanager/CoverViewDialog.h"
0035 #include "playlist/PlaylistController.h"
0036 #include "statusbar/CompoundProgressBar.h"
0037 #include "widgets/LineEdit.h"
0038 #include "widgets/PixmapViewer.h"
0039 
0040 #include <QAction>
0041 #include <QApplication>
0042 #include <QDesktopWidget>
0043 #include <QDialogButtonBox>
0044 #include <QMenu>    //showCoverMenu()
0045 #include <QProgressBar>
0046 #include <QPushButton>
0047 #include <QSplitter>
0048 #include <QStatusBar>
0049 #include <QStringList>
0050 #include <QTimer>    //search filter timer
0051 #include <QToolButton>
0052 #include <QTreeWidget>
0053 #include <QTreeWidgetItem>
0054 #include <QVBoxLayout>
0055 
0056 #include <KConfigGroup>
0057 #include <KIconLoader>
0058 #include <KLocalizedString>
0059 #include <KSqueezedTextLabel> //status label
0060 #include <KToolBar>
0061 
0062 static QString artistToSelectInInitFunction;
0063 CoverManager *CoverManager::s_instance = nullptr;
0064 
0065 class ArtistItem : public QTreeWidgetItem
0066 {
0067     public:
0068         ArtistItem( QTreeWidget *parent, Meta::ArtistPtr artist )
0069             : QTreeWidgetItem( parent )
0070             , m_artist( artist )
0071         {
0072             setText( 0, artist->prettyName() );
0073         }
0074 
0075         ArtistItem( const QString &text, QTreeWidget *parent = nullptr )
0076             : QTreeWidgetItem( parent )
0077             , m_artist( nullptr )
0078         {
0079             setText( 0, text );
0080         }
0081 
0082         Meta::ArtistPtr artist() const { return m_artist; }
0083 
0084     private:
0085         Meta::ArtistPtr m_artist;
0086 };
0087 
0088 
0089 CoverManager::CoverManager( QWidget *parent )
0090         : QDialog( parent )
0091         , m_currentView( AllAlbums )
0092         , m_timer( new QTimer( this ) )    //search filter timer
0093         , m_fetchingCovers( false )
0094         , m_coversFetched( 0 )
0095         , m_coverErrors( 0 )
0096         , m_isLoadingCancelled( false )
0097 {
0098     DEBUG_BLOCK
0099 
0100     setObjectName( "TheCoverManager" );
0101 
0102     s_instance = this;
0103 
0104     // Sets caption and icon correctly (needed e.g. for GNOME)
0105     //kapp->setTopWidget( this );
0106 
0107     QDialogButtonBox *buttonBox = new QDialogButtonBox( QDialogButtonBox::Close, this );
0108     QVBoxLayout *mainLayout = new QVBoxLayout(this);
0109     connect(buttonBox, &QDialogButtonBox::accepted, this, &CoverManager::accept);
0110     connect(buttonBox, &QDialogButtonBox::rejected, this, &CoverManager::reject);
0111     setWindowTitle( i18n("Cover Manager") );
0112     setAttribute( Qt::WA_DeleteOnClose );
0113 
0114     // TODO: There is no hidden signal in QDialog. Needs porting to QT5.
0115 //     connect( this, &CoverManager::hidden, this, &CoverManager::delayedDestruct );
0116     connect( buttonBox->button(QDialogButtonBox::Close), &QAbstractButton::clicked, this, &CoverManager::delayedDestruct );
0117 
0118     m_splitter = new QSplitter( this );
0119     mainLayout->addWidget(m_splitter);
0120     mainLayout->addWidget(buttonBox);
0121 
0122     //artist listview
0123     m_artistView = new QTreeWidget( m_splitter );
0124     m_artistView->setHeaderLabel( i18n( "Albums By" ) );
0125     m_artistView->setSortingEnabled( false );
0126     m_artistView->setTextElideMode( Qt::ElideRight );
0127     m_artistView->setMinimumWidth( 200 );
0128     m_artistView->setColumnCount( 1 );
0129     m_artistView->setAlternatingRowColors( true );
0130     m_artistView->setUniformRowHeights( true );
0131     m_artistView->setSelectionMode( QAbstractItemView::ExtendedSelection );
0132 
0133     ArtistItem *item = nullptr;
0134     item = new ArtistItem( i18n( "All Artists" ) );
0135     item->setIcon(0, QIcon::fromTheme( "media-optical-audio-amarok" ) );
0136     m_items.append( item );
0137 
0138     Collections::Collection *coll = CollectionManager::instance()->primaryCollection();
0139     Collections::QueryMaker *qm = coll->queryMaker();
0140     qm->setAutoDelete( true );
0141     qm->setQueryType( Collections::QueryMaker::Artist );
0142     qm->setAlbumQueryMode( Collections::QueryMaker::OnlyNormalAlbums );
0143     qm->orderBy( Meta::valArtist );
0144 
0145     connect( qm, &Collections::QueryMaker::newArtistsReady,
0146              this, &CoverManager::slotArtistQueryResult );
0147 
0148     connect( qm, &Collections::QueryMaker::queryDone, this, &CoverManager::slotContinueConstruction );
0149 
0150     qm->run();
0151 }
0152 
0153 void
0154 CoverManager::slotArtistQueryResult( Meta::ArtistList artists ) //SLOT
0155 {
0156     DEBUG_BLOCK
0157     foreach( Meta::ArtistPtr artist, artists )
0158         m_artistList << artist;
0159 }
0160 
0161 void
0162 CoverManager::slotContinueConstruction() //SLOT
0163 {
0164     DEBUG_BLOCK
0165     foreach( Meta::ArtistPtr artist, m_artistList )
0166     {
0167         ArtistItem* item = new ArtistItem( m_artistView, artist );
0168         item->setIcon( 0, QIcon::fromTheme( "view-media-artist-amarok" ) );
0169         m_items.append( item );
0170     }
0171     m_artistView->insertTopLevelItems( 0, m_items );
0172 
0173     BoxWidget *vbox = new BoxWidget( true, m_splitter );
0174     BoxWidget *hbox = new BoxWidget( false, vbox );
0175     vbox->layout()->setSpacing( 4 );
0176     hbox->layout()->setSpacing( 4 );
0177 
0178     { //<Search LineEdit>
0179         m_searchEdit = new Amarok::LineEdit( hbox );
0180         m_searchEdit->setPlaceholderText( i18n( "Enter search terms here" ) );
0181         m_searchEdit->setFrame( true );
0182 
0183         m_searchEdit->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Minimum);
0184         m_searchEdit->setClearButtonEnabled( true );
0185 
0186         static_cast<QHBoxLayout*>( hbox->layout() )->setStretchFactor( m_searchEdit, 1 );
0187     } //</Search LineEdit>
0188 
0189     // view menu
0190     m_viewButton = new QPushButton( hbox );
0191 
0192     m_viewMenu = new QMenu( m_viewButton );
0193     m_selectAllAlbums          = m_viewMenu->addAction( i18n("All Albums"),           this, &CoverManager::slotShowAllAlbums );
0194     m_selectAlbumsWithCover    = m_viewMenu->addAction( i18n("Albums With Cover"),    this, &CoverManager::slotShowAlbumsWithCover );
0195     m_selectAlbumsWithoutCover = m_viewMenu->addAction( i18n("Albums Without Cover"), this, &CoverManager::slotShowAlbumsWithoutCover );
0196 
0197     QActionGroup *viewGroup = new QActionGroup( m_viewButton );
0198     viewGroup->setExclusive( true );
0199     viewGroup->addAction( m_selectAllAlbums );
0200     viewGroup->addAction( m_selectAlbumsWithCover );
0201     viewGroup->addAction( m_selectAlbumsWithoutCover );
0202 
0203     m_viewButton->setMenu( m_viewMenu );
0204     m_viewButton->setIcon( QIcon::fromTheme( QStringLiteral("filename-album-amarok") ) );
0205     connect( m_viewMenu, &QMenu::triggered, this, &CoverManager::slotAlbumFilterTriggered );
0206 
0207     //fetch missing covers button
0208     m_fetchButton = new QPushButton( QIcon( "get-hot-new-stuff-amarok" ), i18n("Fetch Missing Covers"), hbox );
0209     connect( m_fetchButton, &QAbstractButton::clicked, this, &CoverManager::fetchMissingCovers );
0210 
0211     m_selectAllAlbums->setChecked( true );
0212     m_selectAllAlbums->trigger();
0213 
0214     //cover view
0215     m_coverView = new CoverView( vbox );
0216     m_coverViewSpacer = new CoverView( vbox );
0217     m_coverViewSpacer->hide();
0218 
0219     //status bar
0220     QStatusBar *statusBar = new QStatusBar( vbox );
0221 
0222     m_statusLabel = new KSqueezedTextLabel( statusBar );
0223     m_statusLabel->setIndent( 3 );
0224     m_progress = new CompoundProgressBar( statusBar );
0225 
0226     statusBar->addWidget( m_statusLabel, 4 );
0227     statusBar->addPermanentWidget( m_progress, 1 );
0228 
0229     connect( m_progress, &CompoundProgressBar::allDone, this, &CoverManager::progressAllDone );
0230 
0231     QSize size = QApplication::desktop()->screenGeometry( this ).size() / 1.5;
0232     QSize sz = Amarok::config( "Cover Manager" ).readEntry( "Window Size", size );
0233     resize( sz.width(), sz.height() );
0234 
0235     m_splitter->setStretchFactor( m_splitter->indexOf( m_artistView ), 1 );
0236     m_splitter->setStretchFactor( m_splitter->indexOf( vbox ), 4 );
0237 
0238     m_fetcher = The::coverFetcher();
0239 
0240     QTreeWidgetItem *item = nullptr;
0241     int i = 0;
0242     if ( !artistToSelectInInitFunction.isEmpty() )
0243     {
0244         for( item = m_artistView->invisibleRootItem()->child( 0 );
0245              i < m_artistView->invisibleRootItem()->childCount();
0246              item = m_artistView->invisibleRootItem()->child( i++ ) )
0247         {
0248             if ( item->text( 0 ) == artistToSelectInInitFunction )
0249                 break;
0250         }
0251     }
0252 
0253     // signals and slots connections
0254     connect( m_artistView, &QTreeWidget::itemSelectionChanged,
0255              this, &CoverManager::slotArtistSelected );
0256     connect( m_coverView, &CoverView::itemActivated,
0257              this, &CoverManager::coverItemClicked );
0258     connect( m_timer, &QTimer::timeout,
0259              this, &CoverManager::slotSetFilter );
0260     connect( m_searchEdit, &Amarok::LineEdit::textChanged,
0261              this, &CoverManager::slotSetFilterTimeout );
0262 
0263     if( item == nullptr )
0264         item = m_artistView->invisibleRootItem()->child( 0 );
0265 
0266     item->setSelected( true );
0267     show();
0268 }
0269 
0270 CoverManager::~CoverManager()
0271 {
0272     Amarok::config( "Cover Manager" ).writeEntry( "Window Size", size() );
0273     qDeleteAll( m_coverItems );
0274     delete m_coverView;
0275     m_coverView = nullptr;
0276     s_instance = nullptr;
0277 }
0278 
0279 void
0280 CoverManager::viewCover( const Meta::AlbumPtr &album, QWidget *parent ) //static
0281 {
0282     //QDialog means "escape" works as expected
0283     QDialog *dialog = new CoverViewDialog( album, parent );
0284     dialog->show();
0285 }
0286 
0287 void
0288 CoverManager::metadataChanged(const Meta::AlbumPtr &album )
0289 {
0290     const QString albumName = album->name();
0291     foreach( CoverViewItem *item, m_coverItems )
0292     {
0293         if( albumName == item->albumPtr()->name() )
0294             item->loadCover();
0295     }
0296     updateStatusBar();
0297 }
0298 
0299 void
0300 CoverManager::fetchMissingCovers() //SLOT
0301 {
0302     m_fetchCovers.clear();
0303     for( int i = 0, coverCount = m_coverView->count(); i < coverCount; ++i )
0304     {
0305         QListWidgetItem *item = m_coverView->item( i );
0306         CoverViewItem *coverItem = static_cast<CoverViewItem*>( item );
0307         if( !coverItem->hasCover() )
0308             m_fetchCovers += coverItem->albumPtr();
0309     }
0310 
0311     debug() << QString( "Fetching %1 missing covers" ).arg( m_fetchCovers.size() );
0312 
0313     ProgressBar *fetchProgressBar = new ProgressBar( this );
0314     fetchProgressBar->setDescription( i18n( "Fetching" ) );
0315     fetchProgressBar->setMaximum( m_fetchCovers.size() );
0316     m_progress->addProgressBar( fetchProgressBar, m_fetcher );
0317     m_progress->show();
0318 
0319     m_fetcher->queueAlbums( m_fetchCovers );
0320     m_fetchingCovers = true;
0321 
0322     updateStatusBar();
0323     m_fetchButton->setEnabled( false );
0324     connect( m_fetcher, &CoverFetcher::finishedSingle, this, &CoverManager::updateFetchingProgress );
0325 }
0326 
0327 void
0328 CoverManager::showOnce( const QString &artist, QWidget* parent )
0329 {
0330     if( !s_instance )
0331     {
0332         artistToSelectInInitFunction = artist;
0333         new CoverManager( parent );
0334     }
0335     else
0336     {
0337         s_instance->activateWindow();
0338         s_instance->raise();
0339     }
0340 }
0341 
0342 void
0343 CoverManager::slotArtistSelected() //SLOT
0344 {
0345     DEBUG_BLOCK
0346 
0347     // delete cover items before clearing cover view
0348     qDeleteAll( m_coverItems );
0349     m_coverItems.clear();
0350     m_coverView->clear();
0351 
0352     //this can be a bit slow
0353     QApplication::setOverrideCursor( Qt::WaitCursor );
0354 
0355     Collections::Collection *coll = CollectionManager::instance()->primaryCollection();
0356 
0357     Collections::QueryMaker *qm = coll->queryMaker();
0358     qm->setAutoDelete( true );
0359     qm->setQueryType( Collections::QueryMaker::Album );
0360     qm->orderBy( Meta::valAlbum );
0361 
0362     qm->beginOr();
0363     const QList< QTreeWidgetItem* > items = m_artistView->selectedItems();
0364     foreach( const QTreeWidgetItem *item, items )
0365     {
0366         const ArtistItem *artistItem = static_cast< const ArtistItem* >( item );
0367         if( artistItem != m_artistView->invisibleRootItem()->child( 0 ) )
0368             qm->addFilter( Meta::valArtist, artistItem->artist()->name(), true, true );
0369         else
0370             qm->excludeFilter( Meta::valAlbum, QString(), true, true );
0371     }
0372     qm->endAndOr();
0373 
0374     // do not show albums with no name, i.e. tracks not belonging to any album
0375     qm->beginAnd();
0376     qm->excludeFilter( Meta::valAlbum, QString(), true, true );
0377     qm->endAndOr();
0378 
0379     connect( qm, &Collections::QueryMaker::newAlbumsReady,
0380              this, &CoverManager::slotAlbumQueryResult );
0381 
0382     connect( qm, &Collections::QueryMaker::queryDone, this, &CoverManager::slotArtistQueryDone );
0383 
0384     qm->run();
0385 }
0386 
0387 void
0388 CoverManager::slotAlbumQueryResult( const Meta::AlbumList &albums ) //SLOT
0389 {
0390     m_albumList = albums;
0391 }
0392 
0393 void
0394 CoverManager::slotAlbumFilterTriggered( QAction *action ) //SLOT
0395 {
0396     m_viewButton->setText( action->text() );
0397 }
0398 
0399 void
0400 CoverManager::slotArtistQueryDone() //SLOT
0401 {
0402     DEBUG_BLOCK
0403 
0404     QApplication::restoreOverrideCursor();
0405 
0406     const int albumCount = m_albumList.count();
0407 
0408     ProgressBar *coverLoadProgressBar = new ProgressBar( this );
0409     coverLoadProgressBar->setDescription( i18n( "Loading" ) );
0410     coverLoadProgressBar->setMaximum( albumCount );
0411     connect( coverLoadProgressBar, &ProgressBar::cancelled,
0412              this, &CoverManager::cancelCoverViewLoading );
0413 
0414     m_progress->addProgressBar( coverLoadProgressBar, m_coverView );
0415     m_progress->show();
0416 
0417     uint x = 0;
0418     debug() << "Loading covers for selected artist(s)";
0419 
0420     //the process events calls below causes massive flickering in the m_albumList
0421     //so we hide this view and only show it when all items has been inserted. This
0422     //also provides quite a massive speed improvement when loading covers.
0423     m_coverView->hide();
0424     m_coverViewSpacer->show();
0425     foreach( const Meta::AlbumPtr &album, m_albumList )
0426     {
0427         qApp->processEvents( QEventLoop::ExcludeSocketNotifiers );
0428         if( isHidden() )
0429         {
0430             m_progress->endProgressOperation( m_coverView );
0431             return;
0432         }
0433         /*
0434          * Loading is stopped if cancelled by the user, or the number of albums
0435          * has changed. The latter occurs when the artist selection changes.
0436          */
0437         if( m_isLoadingCancelled || albumCount != m_albumList.count() )
0438         {
0439             m_isLoadingCancelled = false;
0440             break;
0441         }
0442 
0443         CoverViewItem *item = new CoverViewItem( m_coverView, album );
0444         m_coverItems.append( item );
0445 
0446         if( ++x % 10 == 0 )
0447         {
0448             m_progress->setProgress( m_coverView, x );
0449         }
0450     }
0451     m_progress->endProgressOperation( m_coverView );
0452 
0453     // makes sure View is retained when artist selection changes
0454     changeView( m_currentView, true );
0455 
0456     m_coverViewSpacer->hide();
0457     m_coverView->show();
0458     updateStatusBar();
0459 }
0460 
0461 void
0462 CoverManager::cancelCoverViewLoading()
0463 {
0464     m_isLoadingCancelled = true;
0465 }
0466 
0467 // called when a cover item is clicked
0468 void
0469 CoverManager::coverItemClicked( QListWidgetItem *item ) //SLOT
0470 {
0471     #define item static_cast<CoverViewItem*>(item)
0472 
0473     if( !item ) return;
0474 
0475     item->setSelected( true );
0476     if ( item->hasCover() )
0477         viewCover( item->albumPtr(), this );
0478     else
0479         m_fetcher->manualFetch( item->albumPtr() );
0480 
0481     #undef item
0482 }
0483 
0484 
0485 void
0486 CoverManager::slotSetFilter() //SLOT
0487 {
0488     m_filter = m_searchEdit->text();
0489 
0490     m_coverView->clearSelection();
0491     uint i = 0;
0492     QListWidgetItem *item = m_coverView->item( i );
0493     while ( item )
0494     {
0495         QListWidgetItem *tmp = m_coverView->item( i + 1 );
0496         m_coverView->takeItem( i );
0497         item = tmp;
0498     }
0499 
0500     foreach( QListWidgetItem *item, m_coverItems )
0501     {
0502         CoverViewItem *coverItem = static_cast<CoverViewItem*>(item);
0503         if( coverItem->album().contains( m_filter, Qt::CaseInsensitive ) || coverItem->artist().contains( m_filter, Qt::CaseInsensitive ) )
0504             m_coverView->insertItem( m_coverView->count() -  1, item );
0505     }
0506 
0507     // makes sure View is retained when filter text has changed
0508     changeView( m_currentView, true );
0509     updateStatusBar();
0510 }
0511 
0512 
0513 void
0514 CoverManager::slotSetFilterTimeout() //SLOT
0515 {
0516     if ( m_timer->isActive() ) m_timer->stop();
0517     m_timer->setSingleShot( true );
0518     m_timer->start( 180 );
0519 }
0520 
0521 void
0522 CoverManager::changeView( CoverManager::View id, bool force ) //SLOT
0523 {
0524     if( !force && m_currentView == id )
0525         return;
0526 
0527     //clear the iconview without deleting items
0528     m_coverView->clearSelection();
0529     int itemsCount = m_coverView->count();
0530     while( itemsCount-- > 0 )
0531        m_coverView->takeItem( 0 );
0532 
0533     foreach( QListWidgetItem *item, m_coverItems )
0534     {
0535         bool show = false;
0536         CoverViewItem *coverItem = static_cast<CoverViewItem*>(item);
0537         if( !m_filter.isEmpty() )
0538         {
0539             if( !coverItem->album().contains( m_filter, Qt::CaseInsensitive ) &&
0540                 !coverItem->artist().contains( m_filter, Qt::CaseInsensitive ) )
0541                 continue;
0542         }
0543 
0544         if( id == AllAlbums )    //show all albums
0545             show = true;
0546         else if( id == AlbumsWithCover && coverItem->hasCover() )    //show only albums with cover
0547             show = true;
0548         else if( id == AlbumsWithoutCover && !coverItem->hasCover() )//show only albums without cover
0549             show = true;
0550 
0551         if( show )
0552             m_coverView->insertItem( m_coverView->count() - 1, item );
0553     }
0554     m_currentView = id;
0555 }
0556 
0557 void
0558 CoverManager::updateFetchingProgress( int state )
0559 {
0560     switch( static_cast< CoverFetcher::FinishState >( state ) )
0561     {
0562     case CoverFetcher::Success:
0563         m_coversFetched++;
0564         break;
0565 
0566     case CoverFetcher::Cancelled:
0567     case CoverFetcher::Error:
0568     case CoverFetcher::NotFound:
0569     default:
0570         m_coverErrors++;
0571         break;
0572     }
0573     m_progress->incrementProgress( m_fetcher );
0574     updateStatusBar();
0575 }
0576 
0577 void
0578 CoverManager::stopFetching()
0579 {
0580     DEBUG_FUNC_INFO
0581 
0582     m_fetchCovers.clear();
0583     m_fetchingCovers = false;
0584     m_progress->endProgressOperation( m_fetcher );
0585     updateStatusBar();
0586 }
0587 
0588 void
0589 CoverManager::loadCover( const QString &artist, const QString &album )
0590 {
0591     foreach( QListWidgetItem *item, m_coverItems )
0592     {
0593         CoverViewItem *coverItem = static_cast<CoverViewItem*>(item);
0594         if ( album == coverItem->album() && ( artist == coverItem->artist() || ( artist.isEmpty() && coverItem->artist().isEmpty() ) ) )
0595         {
0596             coverItem->loadCover();
0597             return;
0598         }
0599     }
0600 }
0601 
0602 void
0603 CoverManager::progressAllDone()
0604 {
0605     m_progress->hide();
0606 }
0607 
0608 void
0609 CoverManager::updateStatusBar()
0610 {
0611     QString text;
0612 
0613     //cover fetching info
0614     if( m_fetchingCovers )
0615     {
0616         //update the status text
0617         if( m_coversFetched + m_coverErrors >= m_fetchCovers.size() )
0618         {
0619             //fetching finished
0620             text = i18nc( "The fetching is done.", "Finished." );
0621             if( m_coverErrors )
0622                 text += i18np( " Cover not found", " <b>%1</b> covers not found", m_coverErrors );
0623             //reset counters
0624             m_coversFetched = 0;
0625             m_coverErrors = 0;
0626             m_fetchCovers.clear();
0627             m_fetchingCovers = false;
0628             m_progress->endProgressOperation( m_fetcher );
0629 
0630             disconnect( m_fetcher, &CoverFetcher::finishedSingle, this, &CoverManager::updateFetchingProgress );
0631             QTimer::singleShot( 2000, this, &CoverManager::updateStatusBar );
0632         }
0633 
0634         if( m_fetchCovers.size() == 1 )
0635         {
0636             foreach( Meta::AlbumPtr album, m_fetchCovers )
0637             {
0638                 if( album->hasAlbumArtist() && !album->albumArtist()->prettyName().isEmpty() )
0639                 {
0640                     text = i18n( "Fetching cover for %1 - %2...",
0641                                  album->albumArtist()->prettyName(),
0642                                  album->prettyName() );
0643                 }
0644                 else
0645                 {
0646                     text = i18n( "Fetching cover for %1..." , album->prettyName() );
0647                 }
0648             }
0649         }
0650         else
0651         {
0652             text = i18np( "Fetching 1 cover: ", "Fetching <b>%1</b> covers... : ", m_fetchCovers.size() );
0653             if( m_coversFetched )
0654                 text += i18np( "1 fetched", "%1 fetched", m_coversFetched );
0655             if( m_coverErrors )
0656             {
0657                 if( m_coversFetched )
0658                     text += i18n(" - ");
0659                 text += i18np( "1 not found", "%1 not found", m_coverErrors );
0660             }
0661             if( m_coversFetched + m_coverErrors == 0 )
0662                 text += i18n( "Connecting..." );
0663         }
0664     }
0665     else
0666     {
0667         m_coversFetched = 0;
0668         m_coverErrors = 0;
0669 
0670         uint totalCounter = 0, missingCounter = 0;
0671 
0672         //album info
0673         for( int i = 0, coverCount = m_coverView->count(); i < coverCount; ++i )
0674         {
0675             totalCounter++;
0676             QListWidgetItem *item = m_coverView->item( i );
0677             if( !static_cast<CoverViewItem*>( item )->hasCover() )
0678                 missingCounter++;    //counter for albums without cover
0679         }
0680 
0681         const QList< QTreeWidgetItem* > selected = m_artistView->selectedItems();
0682 
0683         if( !m_filter.isEmpty() )
0684         {
0685             text = i18np( "1 result for \"%2\"", "%1 results for \"%2\"", totalCounter, m_filter );
0686         }
0687         else if( selected.count() > 0 )
0688         {
0689             text = i18np( "1 album", "%1 albums", totalCounter );
0690 
0691             // showing albums by selected artist(s)
0692             if( selected.first() != m_artistView->invisibleRootItem()->child( 0 ) )
0693             {
0694                 QStringList artists;
0695                 foreach( const QTreeWidgetItem *item, selected )
0696                 {
0697                     QString artist = item->text( 0 );
0698                     Amarok::manipulateThe( artist, false );
0699                     artists.append( artist );
0700                 }
0701                 text = i18n( "%1 by %2", text, artists.join( i18nc("Separator for artists", ", ")) );
0702             }
0703         }
0704 
0705         if( missingCounter )
0706             text = i18np("%2 - ( <b>1</b> without cover )", "%2 - ( <b>%1</b> without cover )",
0707                          missingCounter, text );
0708 
0709         m_fetchButton->setEnabled( missingCounter );
0710     }
0711 
0712     m_statusLabel->setText( text );
0713 }
0714 
0715 void
0716 CoverManager::delayedDestruct()
0717 {
0718     if ( isVisible() )
0719         hide();
0720 
0721     deleteLater();
0722 }
0723 
0724 void
0725 CoverManager::setStatusText( const QString &text )
0726 {
0727     m_oldStatusText = m_statusLabel->text();
0728     m_statusLabel->setText( text );
0729 }
0730 
0731 //////////////////////////////////////////////////////////////////////
0732 //    CLASS CoverView
0733 /////////////////////////////////////////////////////////////////////
0734 
0735 CoverView::CoverView( QWidget *parent, const char *name, Qt::WindowFlags f )
0736     : QListWidget( parent )
0737 {
0738     DEBUG_BLOCK
0739 
0740     setObjectName( name );
0741     setWindowFlags( f );
0742     setViewMode( QListView::IconMode );
0743     setMovement( QListView::Static );
0744     setResizeMode( QListView::Adjust );
0745     setSelectionMode( QAbstractItemView::ExtendedSelection );
0746     setWrapping( true );
0747     setWordWrap( true );
0748     setIconSize( QSize(100, 100) );
0749     setGridSize( QSize(120, 160) );
0750     setTextElideMode( Qt::ElideRight );
0751     setContextMenuPolicy( Qt::DefaultContextMenu );
0752     setMouseTracking( true ); // required for setting status text when itemEntered signal is emitted
0753 
0754     connect( this, &CoverView::itemEntered, this, &CoverView::setStatusText );
0755     connect( this, &CoverView::viewportEntered, CoverManager::instance(), &CoverManager::updateStatusBar );
0756 }
0757 
0758 void
0759 CoverView::contextMenuEvent( QContextMenuEvent *event )
0760 {
0761     QList<QListWidgetItem*> items = selectedItems();
0762     const int itemsCount = items.count();
0763 
0764     QMenu menu;
0765     menu.addSection( i18n( "Cover Image" ) );
0766 
0767     if( itemsCount == 1 )
0768     {
0769         // only one item selected: get all custom actions this album is capable of.
0770         CoverViewItem *item = dynamic_cast<CoverViewItem*>( items.first() );
0771         QList<QAction *> actions;
0772         Meta::AlbumPtr album = item->albumPtr();
0773         if( album )
0774         {
0775             QScopedPointer<Capabilities::ActionsCapability> ac( album->create<Capabilities::ActionsCapability>() );
0776             if( ac )
0777             {
0778                 actions = ac->actions();
0779                 foreach( QAction *action, actions )
0780                     menu.addAction( action );
0781             }
0782         }
0783         menu.exec( event->globalPos() );
0784     }
0785     else if( itemsCount > 1 )
0786     {
0787         // multiple albums selected: only unset cover and fetch cover actions
0788         // make sense here, and perhaps (un)setting compilation flag (TODO).
0789 
0790         Meta::AlbumList unsetAlbums;
0791         Meta::AlbumList fetchAlbums;
0792 
0793         foreach( QListWidgetItem *item, items )
0794         {
0795             CoverViewItem *cvItem = dynamic_cast<CoverViewItem*>(item);
0796             Meta::AlbumPtr album = cvItem->albumPtr();
0797             if( album )
0798             {
0799                 QScopedPointer<Capabilities::ActionsCapability> ac( album->create<Capabilities::ActionsCapability>() );
0800                 if( ac )
0801                 {
0802                     QList<QAction *> actions = ac->actions();
0803                     foreach( QAction *action, actions )
0804                     {
0805                         if( qobject_cast<FetchCoverAction*>(action) )
0806                             fetchAlbums << album;
0807                         else if( qobject_cast<UnsetCoverAction*>(action) )
0808                             unsetAlbums << album;
0809                     }
0810                 }
0811             }
0812         }
0813 
0814         if( itemsCount == fetchAlbums.count() )
0815         {
0816             FetchCoverAction *fetchAction = new FetchCoverAction( this, fetchAlbums );
0817             menu.addAction( fetchAction );
0818         }
0819         if( itemsCount == unsetAlbums.count() )
0820         {
0821             UnsetCoverAction *unsetAction = new UnsetCoverAction( this, unsetAlbums );
0822             menu.addAction( unsetAction );
0823         }
0824         menu.exec( event->globalPos() );
0825     }
0826     else
0827         QListView::contextMenuEvent( event );
0828 
0829     // TODO: Play, Load and Append to playlist actions
0830 }
0831 
0832 void
0833 CoverView::setStatusText( QListWidgetItem *item )
0834 {
0835     #define itemmacro static_cast<CoverViewItem *>( item )
0836     if ( !itemmacro )
0837         return;
0838 
0839     const QString artist = itemmacro->albumPtr()->isCompilation() ? i18n( "Various Artists" ) : itemmacro->artist();
0840     const QString tipContent = i18n( "%1 - %2", artist , itemmacro->album() );
0841     CoverManager::instance()->setStatusText( tipContent );
0842     #undef item
0843 }
0844 
0845 //////////////////////////////////////////////////////////////////////
0846 //    CLASS CoverViewItem
0847 /////////////////////////////////////////////////////////////////////
0848 
0849 CoverViewItem::CoverViewItem( QListWidget *parent, Meta::AlbumPtr album )
0850     : QListWidgetItem( parent )
0851     , m_albumPtr( album)
0852 {
0853     m_album = album->prettyName();
0854     if( album->hasAlbumArtist() )
0855         m_artist = album->albumArtist()->prettyName();
0856     else
0857         m_artist = i18n( "No Artist" );
0858     setText( album->prettyName() );
0859 
0860     loadCover();
0861 
0862     CoverManager::instance()->subscribeTo( album );
0863 }
0864 
0865 CoverViewItem::~CoverViewItem()
0866 {}
0867 
0868 bool
0869 CoverViewItem::hasCover() const
0870 {
0871     return albumPtr()->hasImage();
0872 }
0873 
0874 void
0875 CoverViewItem::loadCover()
0876 {
0877     const bool isSuppressing = m_albumPtr->suppressImageAutoFetch();
0878     m_albumPtr->setSuppressImageAutoFetch( true );
0879     setIcon( QPixmap::fromImage( m_albumPtr->image( 100 ) ) );
0880     m_albumPtr->setSuppressImageAutoFetch( isSuppressing );
0881 }
0882 
0883 void
0884 CoverViewItem::dragEntered()
0885 {
0886     setSelected( true );
0887 }
0888 
0889 
0890 void
0891 CoverViewItem::dragLeft()
0892 {
0893     setSelected( false );
0894 }
0895 
0896