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