1 /****************************************************************************************
2 * Copyright (c) 2004 Mark Kretschmann <kretschmann@kde.org> *
3 * Copyright (c) 2004 Stefan Bogner <bochi@online.ms> *
4 * Copyright (c) 2004 Max Howell <max.howell@methylblue.com> *
5 * Copyright (c) 2007 Dan Meltzer <parallelgrapefruit@gmail.com> *
6 * Copyright (c) 2009 Martin Sandsmark <sandsmark@samfundet.no> *
7 * *
8 * This program is free software; you can redistribute it and/or modify it under *
9 * the terms of the GNU General Public License as published by the Free Software *
10 * Foundation; either version 2 of the License, or (at your option) any later *
11 * version. *
12 * *
13 * This program is distributed in the hope that it will be useful, but WITHOUT ANY *
14 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A *
15 * PARTICULAR PURPOSE. See the GNU General Public License for more details. *
16 * *
17 * You should have received a copy of the GNU General Public License along with *
18 * this program. If not, see <http://www.gnu.org/licenses/>. *
19 ****************************************************************************************/
20
21 #define DEBUG_PREFIX "CoverFoundDialog"
22
23 #include "CoverFoundDialog.h"
24
25 #include "SvgHandler.h"
26 #include "core/support/Amarok.h"
27 #include "core/support/Debug.h"
28 #include "covermanager/CoverViewDialog.h"
29 #include "statusbar/KJobProgressBar.h"
30 #include "widgets/AlbumBreadcrumbWidget.h"
31 #include "widgets/PixmapViewer.h"
32
33 #include <QCloseEvent>
34 #include <QDir>
35 #include <QFileDialog>
36 #include <QFontDatabase>
37 #include <QFormLayout>
38 #include <QGridLayout>
39 #include <QHeaderView>
40 #include <QLineEdit>
41 #include <QListWidget>
42 #include <QMenu>
43 #include <QMimeDatabase>
44 #include <QMimeType>
45 #include <QPushButton>
46 #include <QScrollArea>
47 #include <QSplitter>
48 #include <QStandardPaths>
49 #include <QTabWidget>
50
51 #include <KComboBox>
52 #include <KConfigGroup>
53 #include <KLineEdit>
54 #include <KLocalizedString>
55 #include <KMessageBox>
56 #include <KWindowConfig>
57
CoverFoundDialog(const CoverFetchUnit::Ptr & unit,const CoverFetch::Metadata & data,QWidget * parent)58 CoverFoundDialog::CoverFoundDialog( const CoverFetchUnit::Ptr &unit,
59 const CoverFetch::Metadata &data,
60 QWidget *parent )
61 : QDialog( parent )
62 , m_album( unit->album() )
63 , m_isSorted( false )
64 , m_sortEnabled( false )
65 , m_unit( unit )
66 , m_queryPage( 0 )
67 {
68 DEBUG_BLOCK
69
70 setLayout( new QVBoxLayout );
71
72 QSplitter *splitter = new QSplitter( this );
73 layout()->addWidget( splitter );
74
75 QDialogButtonBox *buttonBox = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this );
76 QPushButton *clearButton = buttonBox->addButton( QStringLiteral( "clear" ), QDialogButtonBox::ActionRole ); // clear icon view
77 layout()->addWidget( buttonBox );
78
79 connect( clearButton, &QAbstractButton::clicked, this, &CoverFoundDialog::clearView );
80 connect( buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept );
81 connect( buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject );
82
83 m_sideBar = new CoverFoundSideBar( m_album, splitter );
84
85 BoxWidget *vbox = new BoxWidget( true, splitter );
86 vbox->layout()->setSpacing( 4 );
87
88 BoxWidget *breadcrumbBox = new BoxWidget( false, vbox );
89 QLabel *breadcrumbLabel = new QLabel( i18n( "Finding cover for" ), breadcrumbBox );
90 AlbumBreadcrumbWidget *breadcrumb = new AlbumBreadcrumbWidget( m_album, breadcrumbBox );
91
92 QFont breadcrumbLabelFont;
93 breadcrumbLabelFont.setBold( true );
94 breadcrumbLabel->setFont( breadcrumbLabelFont );
95 breadcrumbLabel->setIndent( 4 );
96
97 connect( breadcrumb, &AlbumBreadcrumbWidget::artistClicked, this, &CoverFoundDialog::addToCustomSearch );
98 connect( breadcrumb, &AlbumBreadcrumbWidget::albumClicked, this, &CoverFoundDialog::addToCustomSearch );
99
100 BoxWidget *searchBox = new BoxWidget( false, vbox );
101
102 QStringList completionNames;
103 QString firstRunQuery( m_album->name() );
104 completionNames << firstRunQuery;
105 if( m_album->hasAlbumArtist() )
106 {
107 const QString &name = m_album->albumArtist()->name();
108 completionNames << name;
109 firstRunQuery += ' ' + name;
110 }
111 m_query = firstRunQuery;
112 m_album->setSuppressImageAutoFetch( true );
113
114 m_search = new KComboBox( searchBox );
115 m_search->setEditable( true ); // creates a QLineEdit for the combobox
116 m_search->setTrapReturnKey( true );
117 m_search->setInsertPolicy( KComboBox::NoInsert ); // insertion is handled by us
118 m_search->setCompletionMode( KCompletion::CompletionPopup );
119 m_search->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Fixed );
120 m_search->lineEdit()->setPlaceholderText( i18n( "Enter Custom Search" ) );
121 m_search->completionObject()->setOrder( KCompletion::Insertion );
122 m_search->completionObject()->setIgnoreCase( true );
123 m_search->completionObject()->setItems( completionNames );
124 m_search->insertItem( 0, KStandardGuiItem::find().icon(), QString() );
125 m_search->insertSeparator( 1 );
126 m_search->insertItem( 2, QIcon::fromTheme("filename-album-amarok"), m_album->name() );
127 if( m_album->hasAlbumArtist() )
128 m_search->insertItem( 3, QIcon::fromTheme("filename-artist-amarok"), m_album->albumArtist()->name() );
129
130 auto findItem = KStandardGuiItem::find();
131 m_searchButton = new QPushButton( findItem.icon(), findItem.text(), searchBox );
132 auto configureItem = KStandardGuiItem::configure();
133 QPushButton *sourceButton = new QPushButton( configureItem.icon(), configureItem.text(), searchBox );
134 updateSearchButton( firstRunQuery );
135
136 QMenu *sourceMenu = new QMenu( sourceButton );
137 QAction *lastFmAct = new QAction( i18n( "Last.fm" ), sourceMenu );
138 QAction *googleAct = new QAction( i18n( "Google" ), sourceMenu );
139 QAction *discogsAct = new QAction( i18n( "Discogs" ), sourceMenu );
140 // TODO: currently broken, re-enable after adjusting to current API/returned doc
141 googleAct->setEnabled( false );
142 // TODO: currently broken, re-enable after adjusting to current API/returned doc
143 discogsAct->setEnabled( false );
144 lastFmAct->setCheckable( true );
145 googleAct->setCheckable( true );
146 discogsAct->setCheckable( true );
147 connect( lastFmAct, &QAction::triggered, this, &CoverFoundDialog::selectLastFm );
148 connect( googleAct, &QAction::triggered, this, &CoverFoundDialog::selectGoogle );
149 connect( discogsAct, &QAction::triggered, this, &CoverFoundDialog::selectDiscogs );
150
151 m_sortAction = new QAction( i18n( "Sort by size" ), sourceMenu );
152 m_sortAction->setCheckable( true );
153 connect( m_sortAction, &QAction::triggered, this, &CoverFoundDialog::sortingTriggered );
154
155 QActionGroup *ag = new QActionGroup( sourceButton );
156 ag->addAction( lastFmAct );
157 ag->addAction( googleAct );
158 ag->addAction( discogsAct );
159 sourceMenu->addActions( ag->actions() );
160 sourceMenu->addSeparator();
161 sourceMenu->addAction( m_sortAction );
162 sourceButton->setMenu( sourceMenu );
163
164 connect( m_search, QOverload<const QString&>::of(&KComboBox::returnPressed),
165 this, &CoverFoundDialog::insertComboText );
166 connect( m_search, QOverload<const QString&>::of(&KComboBox::returnPressed),
167 this, &CoverFoundDialog::processQuery );
168 connect( m_search, QOverload<const QString&>::of(&KComboBox::returnPressed),
169 this, &CoverFoundDialog::updateSearchButton );
170 connect( m_search, &KComboBox::editTextChanged, this, &CoverFoundDialog::updateSearchButton );
171
172 sourceMenu->addAction( m_sortAction );
173 sourceButton->setMenu( sourceMenu );
174
175 connect( m_search, QOverload<const QString&>::of(&KComboBox::returnPressed),
176 this, &CoverFoundDialog::insertComboText );
177 connect( m_search, QOverload<const QString&>::of(&KComboBox::returnPressed),
178 this, &CoverFoundDialog::processQuery );
179 connect( m_search, QOverload<const QString&>::of(&KComboBox::returnPressed),
180 this, &CoverFoundDialog::updateSearchButton );
181 connect( m_search, &KComboBox::editTextChanged, this, &CoverFoundDialog::updateSearchButton );
182 connect( dynamic_cast<KLineEdit*>(m_search->lineEdit()), &KLineEdit::clearButtonClicked,
183 this, &CoverFoundDialog::clearQueryButtonClicked);
184 connect( m_searchButton, &QPushButton::clicked, this, &CoverFoundDialog::processCurrentQuery );
185
186 m_view = new QListWidget( vbox );
187 m_view->setAcceptDrops( false );
188 m_view->setContextMenuPolicy( Qt::CustomContextMenu );
189 m_view->setDragDropMode( QAbstractItemView::NoDragDrop );
190 m_view->setDragEnabled( false );
191 m_view->setDropIndicatorShown( false );
192 m_view->setMovement( QListView::Static );
193 m_view->setGridSize( QSize( 140, 150 ) );
194 m_view->setIconSize( QSize( 120, 120 ) );
195 m_view->setSpacing( 4 );
196 m_view->setViewMode( QListView::IconMode );
197 m_view->setResizeMode( QListView::Adjust );
198
199 connect( m_view, &QListWidget::currentItemChanged,
200 this, &CoverFoundDialog::currentItemChanged );
201 connect( m_view, &QListWidget::itemDoubleClicked,
202 this, &CoverFoundDialog::itemDoubleClicked );
203 connect( m_view, &QListWidget::customContextMenuRequested,
204 this, &CoverFoundDialog::itemMenuRequested );
205
206 splitter->addWidget( m_sideBar );
207 splitter->addWidget( vbox );
208 // setMainWidget( splitter );
209
210 const KConfigGroup config = Amarok::config( "Cover Fetcher" );
211 const QString source = config.readEntry( "Interactive Image Source", "LastFm" );
212 m_sortEnabled = config.readEntry( "Sort by Size", false );
213 m_sortAction->setChecked( m_sortEnabled );
214 m_isSorted = m_sortEnabled;
215 KWindowConfig::restoreWindowSize( windowHandle(), config ); // call this after setMainWidget()
216
217 if( source == "LastFm" )
218 lastFmAct->setChecked( true );
219 else if( source == "Discogs" )
220 discogsAct->setChecked( true );
221 else
222 googleAct->setChecked( true );
223
224 typedef CoverFetchArtPayload CFAP;
225 const CFAP *payload = dynamic_cast< const CFAP* >( unit->payload() );
226 if( !m_album->hasImage() )
227 m_sideBar->setPixmap( QPixmap::fromImage( m_album->image(190 ) ) );
228 else if( payload )
229 add( m_album->image(), data, payload->imageSize() );
230 else
231 add( m_album->image(), data );
232 m_view->setCurrentItem( m_view->item( 0 ) );
233 updateGui();
234
235 connect( The::networkAccessManager(), &NetworkAccessManagerProxy::requestRedirectedReply,
236 this, &CoverFoundDialog::fetchRequestRedirected );
237 }
238
~CoverFoundDialog()239 CoverFoundDialog::~CoverFoundDialog()
240 {
241 m_album->setSuppressImageAutoFetch( false );
242
243 const QList<QListWidgetItem*> &viewItems = m_view->findItems( QChar('*'), Qt::MatchWildcard );
244 qDeleteAll( viewItems );
245 delete m_dialog.data();
246 }
247
hideEvent(QHideEvent * event)248 void CoverFoundDialog::hideEvent( QHideEvent *event )
249 {
250 KConfigGroup config = Amarok::config( "Cover Fetcher" );
251 config.writeEntry( "geometry", saveGeometry() );
252 event->accept();
253 }
254
add(const QImage & cover,const CoverFetch::Metadata & metadata,const CoverFetch::ImageSize imageSize)255 void CoverFoundDialog::add( const QImage &cover,
256 const CoverFetch::Metadata &metadata,
257 const CoverFetch::ImageSize imageSize )
258 {
259 if( cover.isNull() )
260 return;
261
262 if( !contains( metadata ) )
263 {
264 CoverFoundItem *item = new CoverFoundItem( cover, metadata, imageSize );
265 addToView( item );
266 }
267 }
268
addToView(CoverFoundItem * item)269 void CoverFoundDialog::addToView( CoverFoundItem *item )
270 {
271 const CoverFetch::Metadata &metadata = item->metadata();
272
273 if( m_sortEnabled && metadata.contains( "width" ) && metadata.contains( "height" ) )
274 {
275 if( m_isSorted )
276 {
277 const int size = metadata.value( "width" ).toInt() * metadata.value( "height" ).toInt();
278 QList< int >::iterator i = qLowerBound( m_sortSizes.begin(), m_sortSizes.end(), size );
279 m_sortSizes.insert( i, size );
280 const int index = m_sortSizes.count() - m_sortSizes.indexOf( size ) - 1;
281 m_view->insertItem( index, item );
282 }
283 else
284 {
285 m_view->addItem( item );
286 sortCoversBySize();
287 }
288 }
289 else
290 {
291 m_view->addItem( item );
292 }
293 updateGui();
294 }
295
contains(const CoverFetch::Metadata & metadata) const296 bool CoverFoundDialog::contains( const CoverFetch::Metadata &metadata ) const
297 {
298 for( int i = 0, count = m_view->count(); i < count; ++i )
299 {
300 CoverFoundItem *item = static_cast<CoverFoundItem*>( m_view->item(i) );
301 if( item->metadata() == metadata )
302 return true;
303 }
304 return false;
305 }
306
addToCustomSearch(const QString & text)307 void CoverFoundDialog::addToCustomSearch( const QString &text )
308 {
309 const QString &query = m_search->currentText();
310 if( !text.isEmpty() && !query.contains( text ) )
311 {
312 QStringList q;
313 if( !query.isEmpty() )
314 q << query;
315 q << text;
316 const QString result = q.join( QLatin1Char( ' ' ) );
317 qobject_cast<QLineEdit*>( m_search->lineEdit() )->setText( result );
318 }
319 }
320
clearQueryButtonClicked()321 void CoverFoundDialog::clearQueryButtonClicked()
322 {
323 m_query.clear();
324 m_queryPage = 0;
325 updateGui();
326 }
327
clearView()328 void CoverFoundDialog::clearView()
329 {
330 m_view->clear();
331 m_sideBar->clear();
332 m_sortSizes.clear();
333 updateGui();
334 }
335
insertComboText(const QString & text)336 void CoverFoundDialog::insertComboText( const QString &text )
337 {
338 if( text.isEmpty() )
339 return;
340
341 if( m_search->contains( text ) )
342 {
343 m_search->setCurrentIndex( m_search->findText( text ) );
344 return;
345 }
346 m_search->completionObject()->addItem( text );
347 m_search->insertItem( 0, KStandardGuiItem::find().icon(), text );
348 m_search->setCurrentIndex( 0 );
349 }
350
currentItemChanged(QListWidgetItem * current,QListWidgetItem * previous)351 void CoverFoundDialog::currentItemChanged( QListWidgetItem *current, QListWidgetItem *previous )
352 {
353 Q_UNUSED( previous )
354 if( !current )
355 return;
356 CoverFoundItem *it = static_cast< CoverFoundItem* >( current );
357 QImage image = it->hasBigPix() ? it->bigPix() : it->thumb();
358 m_image = image;
359 m_sideBar->setPixmap( QPixmap::fromImage(image), it->metadata() );
360 }
361
itemDoubleClicked(QListWidgetItem * item)362 void CoverFoundDialog::itemDoubleClicked( QListWidgetItem *item )
363 {
364 Q_UNUSED( item )
365 slotButtonClicked( QDialog::Accepted );
366 }
367
itemMenuRequested(const QPoint & pos)368 void CoverFoundDialog::itemMenuRequested( const QPoint &pos )
369 {
370 const QPoint globalPos = m_view->mapToGlobal( pos );
371 QModelIndex index = m_view->indexAt( pos );
372
373 if( !index.isValid() )
374 return;
375
376 CoverFoundItem *item = static_cast< CoverFoundItem* >( m_view->item( index.row() ) );
377 item->setSelected( true );
378
379 QMenu menu( this );
380 QAction *display = new QAction( QIcon::fromTheme(QStringLiteral("zoom-original")), i18n("Display Cover"), &menu );
381 connect( display, &QAction::triggered, this, &CoverFoundDialog::display );
382
383 QAction *save = new QAction( QIcon::fromTheme(QStringLiteral("document-save")), i18n("Save As"), &menu );
384 connect( save, &QAction::triggered, this, &CoverFoundDialog::saveAs );
385
386 menu.addAction( display );
387 menu.addAction( save );
388 menu.exec( globalPos );
389 }
390
saveAs()391 void CoverFoundDialog::saveAs()
392 {
393 CoverFoundItem *item = static_cast< CoverFoundItem* >( m_view->currentItem() );
394 if( !item->hasBigPix() && !fetchBigPix() )
395 return;
396
397 Meta::TrackList tracks = m_album->tracks();
398 if( tracks.isEmpty() )
399 {
400 warning() << "no tracks associated with album" << m_album->name();
401 return;
402 }
403
404 QFileDialog dlg;
405 QWidget::setWindowTitle( i18n("Cover Image Save Location") );
406 dlg.setFileMode( QFileDialog::AnyFile );
407 dlg.setSupportedSchemes( QStringList( QStringLiteral( "file" ) ) );
408 dlg.setAcceptMode( QFileDialog::AcceptSave );
409
410 QUrl selectedUrl;
411 selectedUrl.setPath( "cover.jpg" );
412 dlg.selectUrl( selectedUrl );
413
414 QStringList supportedMimeTypes;
415 supportedMimeTypes << "image/jpeg";
416 supportedMimeTypes << "image/png";
417 dlg.setMimeTypeFilters( supportedMimeTypes );
418
419 QUrl saveUrl;
420 int res = dlg.exec();
421 switch( res )
422 {
423 case QDialog::Accepted:
424 saveUrl = dlg.selectedUrls().value( 0 );
425 break;
426 case QDialog::Rejected:
427 return;
428 }
429
430 QFile saveFile( saveUrl.path() );
431 if( !saveFile.open( QFile::WriteOnly ) )
432 {
433 KMessageBox::detailedError( this,
434 i18n("Sorry, the cover could not be saved."),
435 saveFile.errorString() );
436 return;
437 }
438
439 const QImage &image = item->bigPix();
440 QMimeDatabase db;
441 const QString &ext = db.suffixForFileName( saveUrl.path() ).toLower();
442 bool ok;
443 if( ext == "jpg" || ext == "jpeg" )
444 ok = image.save( &saveFile, "JPG" );
445 else if( ext == "png" )
446 ok = image.save( &saveFile, "PNG" );
447 else
448 ok = image.save( &saveFile );
449
450 if( !ok )
451 {
452 KMessageBox::detailedError( this,
453 i18n("Sorry, the cover could not be saved."),
454 saveFile.errorString() );
455 saveFile.remove();
456 }
457 }
458
slotButtonClicked(int button)459 void CoverFoundDialog::slotButtonClicked( int button )
460 {
461 if( button == QDialog::Accepted )
462 {
463 CoverFoundItem *item = dynamic_cast< CoverFoundItem* >( m_view->currentItem() );
464 if( !item )
465 {
466 reject();
467 return;
468 }
469
470 bool gotBigPix( true );
471 if( !item->hasBigPix() )
472 gotBigPix = fetchBigPix();
473
474 if( gotBigPix )
475 {
476 m_image = item->bigPix();
477 accept();
478 }
479 }
480 }
481
fetchRequestRedirected(QNetworkReply * oldReply,QNetworkReply * newReply)482 void CoverFoundDialog::fetchRequestRedirected( QNetworkReply *oldReply,
483 QNetworkReply *newReply )
484 {
485 QUrl oldUrl = oldReply->request().url();
486 QUrl newUrl = newReply->request().url();
487
488 // Since we were redirected we have to check if the redirect
489 // was for one of our URLs and if the new URL is not handled
490 // already.
491 if( m_urls.contains( oldUrl ) && !m_urls.contains( newUrl ) )
492 {
493 // Get the unit for the old URL.
494 CoverFoundItem *item = m_urls.value( oldUrl );
495
496 // Add the unit with the new URL and remove the old one.
497 m_urls.insert( newUrl, item );
498 m_urls.remove( oldUrl );
499 }
500 }
501
handleFetchResult(const QUrl & url,const QByteArray & data,const NetworkAccessManagerProxy::Error & e)502 void CoverFoundDialog::handleFetchResult( const QUrl &url, const QByteArray &data,
503 const NetworkAccessManagerProxy::Error &e )
504 {
505 CoverFoundItem *item = m_urls.take( url );
506 QImage image;
507 if( item && e.code == QNetworkReply::NoError && image.loadFromData( data ) )
508 {
509 item->setBigPix( image );
510 m_sideBar->setPixmap( QPixmap::fromImage( image ) );
511 if( m_dialog )
512 m_dialog->accept();
513 }
514 else
515 {
516 QStringList errors;
517 errors << e.description;
518 KMessageBox::errorList( this, i18n("Sorry, the cover image could not be retrieved."), errors );
519 if( m_dialog )
520 m_dialog->reject();
521 }
522 }
523
fetchBigPix()524 bool CoverFoundDialog::fetchBigPix()
525 {
526 DEBUG_BLOCK
527 CoverFoundItem *item = static_cast< CoverFoundItem* >( m_view->currentItem() );
528 const QUrl url( item->metadata().value( "normalarturl" ) );
529 if( !url.isValid() )
530 return false;
531
532 QNetworkReply *reply = The::networkAccessManager()->getData( url, this, &CoverFoundDialog::handleFetchResult );
533 m_urls.insert( url, item );
534
535 if( !m_dialog )
536 {
537 m_dialog = new QProgressDialog( this );
538 m_dialog->setWindowTitle( i18n( "Fetching Large Cover" ) );
539 m_dialog->setLabelText( i18n( "Download Progress" ) );
540 m_dialog->setModal( true );
541 m_dialog->setCancelButton( new QPushButton( i18n( "Cancel" ) ) );
542 m_dialog->setAutoClose( false );
543 m_dialog->setAutoReset( true );
544 m_dialog->setMinimumWidth( 300 );
545 connect( reply, &QNetworkReply::downloadProgress,
546 this, &CoverFoundDialog::downloadProgressed );
547 }
548 int result = m_dialog->exec();
549 bool success = (result == QDialog::Accepted) && !m_dialog->wasCanceled();
550 The::networkAccessManager()->abortGet( url );
551 if( !success )
552 m_urls.remove( url );
553 m_dialog->deleteLater();
554 return success;
555 }
556
downloadProgressed(qint64 bytesReceived,qint64 bytesTotal)557 void CoverFoundDialog::downloadProgressed( qint64 bytesReceived, qint64 bytesTotal )
558 {
559 if( m_dialog )
560 {
561 m_dialog->setRange( 0, bytesTotal );
562 m_dialog->setValue( bytesReceived );
563 }
564 }
565
display()566 void CoverFoundDialog::display()
567 {
568 CoverFoundItem *item = static_cast< CoverFoundItem* >( m_view->currentItem() );
569 const bool success = item->hasBigPix() ? true : fetchBigPix();
570 if( !success )
571 return;
572
573 const QImage &image = item->hasBigPix() ? item->bigPix() : item->thumb();
574 CoverViewDialog *dlg = new CoverViewDialog( image, this );
575 dlg->show();
576 dlg->raise();
577 dlg->activateWindow();
578 }
579
processCurrentQuery()580 void CoverFoundDialog::processCurrentQuery()
581 {
582 const QString text = m_search->currentText();
583 processQuery( text );
584 }
585
processQuery(const QString & input)586 void CoverFoundDialog::processQuery( const QString &input )
587 {
588 const bool inputEmpty( input.isEmpty() );
589 const bool mQueryEmpty( m_query.isEmpty() );
590
591 QString q;
592 if( inputEmpty && !mQueryEmpty )
593 {
594 q = m_query;
595 }
596 else if( !inputEmpty || !mQueryEmpty )
597 {
598 q = input;
599 if( m_query != input )
600 {
601 m_query = input;
602 m_queryPage = 1;
603 }
604 }
605
606 if( !q.isEmpty() )
607 {
608 Q_EMIT newCustomQuery( m_album, q, m_queryPage );
609 updateSearchButton( q );
610 m_queryPage++;
611 }
612 }
613
selectDiscogs()614 void CoverFoundDialog::selectDiscogs()
615 {
616 KConfigGroup config = Amarok::config( "Cover Fetcher" );
617 config.writeEntry( "Interactive Image Source", "Discogs" );
618 m_sortAction->setEnabled( true );
619 m_queryPage = 0;
620 processCurrentQuery();
621 debug() << "Select Discogs as source";
622 }
623
selectLastFm()624 void CoverFoundDialog::selectLastFm()
625 {
626 KConfigGroup config = Amarok::config( "Cover Fetcher" );
627 config.writeEntry( "Interactive Image Source", "LastFm" );
628 m_sortAction->setEnabled( false );
629 m_queryPage = 0;
630 processCurrentQuery();
631 debug() << "Select Last.fm as source";
632 }
633
selectGoogle()634 void CoverFoundDialog::selectGoogle()
635 {
636 KConfigGroup config = Amarok::config( "Cover Fetcher" );
637 config.writeEntry( "Interactive Image Source", "Google" );
638 m_sortAction->setEnabled( true );
639 m_queryPage = 0;
640 processCurrentQuery();
641 debug() << "Select Google as source";
642 }
643
setQueryPage(int page)644 void CoverFoundDialog::setQueryPage( int page )
645 {
646 m_queryPage = page;
647 }
648
sortingTriggered(bool checked)649 void CoverFoundDialog::sortingTriggered( bool checked )
650 {
651 KConfigGroup config = Amarok::config( "Cover Fetcher" );
652 config.writeEntry( "Sort by Size", checked );
653 m_sortEnabled = checked;
654 m_isSorted = false;
655 if( m_sortEnabled )
656 sortCoversBySize();
657 debug() << "Enable sorting by size:" << checked;
658 }
659
sortCoversBySize()660 void CoverFoundDialog::sortCoversBySize()
661 {
662 DEBUG_BLOCK
663
664 m_sortSizes.clear();
665 QList< QListWidgetItem* > viewItems = m_view->findItems( QChar('*'), Qt::MatchWildcard );
666 QMultiMap<int, CoverFoundItem*> sortItems;
667
668 // get a list of cover items sorted (automatically by qmap) by size
669 foreach( QListWidgetItem *viewItem, viewItems )
670 {
671 CoverFoundItem *coverItem = dynamic_cast<CoverFoundItem*>( viewItem );
672 const CoverFetch::Metadata &meta = coverItem->metadata();
673 const int itemSize = meta.value( "width" ).toInt() * meta.value( "height" ).toInt();
674 sortItems.insert( itemSize, coverItem );
675 m_sortSizes << itemSize;
676 }
677
678 // take items from the view and insert into a temp list in the sorted order
679 QList<CoverFoundItem*> coverItems = sortItems.values();
680 QList<CoverFoundItem*> tempItems;
681 for( int i = 0, count = sortItems.count(); i < count; ++i )
682 {
683 CoverFoundItem *item = coverItems.value( i );
684 const int itemRow = m_view->row( item );
685 QListWidgetItem *itemFromRow = m_view->takeItem( itemRow );
686 if( itemFromRow )
687 tempItems << dynamic_cast<CoverFoundItem*>( itemFromRow );
688 }
689
690 // add the items back to the view in descending order
691 foreach( CoverFoundItem* item, tempItems )
692 m_view->insertItem( 0, item );
693
694 m_isSorted = true;
695 }
696
updateSearchButton(const QString & text)697 void CoverFoundDialog::updateSearchButton( const QString &text )
698 {
699 const bool isNewSearch = ( text != m_query ) ? true : false;
700 m_searchButton->setText( isNewSearch ? KStandardGuiItem::find().text() : KStandardGuiItem::cont().text() );
701 m_searchButton->setIcon( isNewSearch ? KStandardGuiItem::find().icon() : KStandardGuiItem::cont().icon() );
702 m_searchButton->setToolTip( isNewSearch ? i18n( "Search" ) : i18n( "Search For More Results" ) );
703 }
704
updateGui()705 void CoverFoundDialog::updateGui()
706 {
707 updateTitle();
708
709 if( !m_search->hasFocus() )
710 findChild<QDialogButtonBox*>()->button( QDialogButtonBox::Ok )->setFocus();
711 update();
712 }
713
updateTitle()714 void CoverFoundDialog::updateTitle()
715 {
716 const int itemCount = m_view->count();
717 const QString caption = ( itemCount == 0 )
718 ? i18n( "No Images Found" )
719 : i18np( "1 Image Found", "%1 Images Found", itemCount );
720 setWindowTitle( caption );
721 }
722
CoverFoundSideBar(const Meta::AlbumPtr & album,QWidget * parent)723 CoverFoundSideBar::CoverFoundSideBar( const Meta::AlbumPtr &album, QWidget *parent )
724 : BoxWidget( true, parent )
725 , m_album( album )
726 {
727 m_cover = new QLabel( this );
728 m_tabs = new QTabWidget( this );
729 m_notes = new QLabel;
730 QScrollArea *metaArea = new QScrollArea;
731 m_metaTable = new QWidget( metaArea );
732 m_metaTable->setLayout( new QFormLayout );
733 m_metaTable->setMinimumSize( QSize( 150, 200 ) );
734 metaArea->setFrameShape( QFrame::NoFrame );
735 metaArea->setWidget( m_metaTable );
736 m_notes->setAlignment( Qt::AlignLeft | Qt::AlignTop );
737 m_notes->setMargin( 4 );
738 m_notes->setOpenExternalLinks( true );
739 m_notes->setTextFormat( Qt::RichText );
740 m_notes->setTextInteractionFlags( Qt::TextBrowserInteraction );
741 m_notes->setWordWrap( true );
742 m_cover->setAlignment( Qt::AlignCenter );
743 m_tabs->addTab( metaArea, i18n( "Information" ) );
744 m_tabs->addTab( m_notes, i18n( "Notes" ) );
745 setMaximumWidth( 200 );
746 setPixmap( QPixmap::fromImage( m_album->image( 190 ) ) );
747 clear();
748 }
749
~CoverFoundSideBar()750 CoverFoundSideBar::~CoverFoundSideBar()
751 {
752 }
753
clear()754 void CoverFoundSideBar::clear()
755 {
756 clearMetaTable();
757 m_notes->clear();
758 m_metadata.clear();
759 }
760
setPixmap(const QPixmap & pixmap,const CoverFetch::Metadata & metadata)761 void CoverFoundSideBar::setPixmap( const QPixmap &pixmap, const CoverFetch::Metadata &metadata )
762 {
763 m_metadata = metadata;
764 updateNotes();
765 setPixmap( pixmap );
766 }
767
setPixmap(const QPixmap & pixmap)768 void CoverFoundSideBar::setPixmap( const QPixmap &pixmap )
769 {
770 m_pixmap = pixmap;
771 QPixmap scaledPix = pixmap.scaled( QSize( 190, 190 ), Qt::KeepAspectRatio );
772 QPixmap prettyPix = The::svgHandler()->addBordersToPixmap( scaledPix, 5, QString(), true );
773 m_cover->setPixmap( prettyPix );
774 updateMetaTable();
775 }
776
updateNotes()777 void CoverFoundSideBar::updateNotes()
778 {
779 bool enableNotes( false );
780 if( m_metadata.contains( "notes" ) )
781 {
782 const QString notes = m_metadata.value( "notes" );
783 if( !notes.isEmpty() )
784 {
785 m_notes->setText( notes );
786 enableNotes = true;
787 }
788 else
789 enableNotes = false;
790 }
791 else
792 {
793 m_notes->clear();
794 enableNotes = false;
795 }
796 m_tabs->setTabEnabled( m_tabs->indexOf( m_notes ), enableNotes );
797 }
798
updateMetaTable()799 void CoverFoundSideBar::updateMetaTable()
800 {
801 clearMetaTable();
802
803 QFormLayout *layout = static_cast< QFormLayout* >( m_metaTable->layout() );
804 layout->setSizeConstraint( QLayout::SetMinAndMaxSize );
805
806 CoverFetch::Metadata::const_iterator mit = m_metadata.constBegin();
807 while( mit != m_metadata.constEnd() )
808 {
809 const QString &value = mit.value();
810 if( !value.isEmpty() )
811 {
812 const QString &tag = mit.key();
813 QString name;
814
815 #define TAGHAS(s) (tag.compare(QLatin1String(s)) == 0)
816 if( TAGHAS("artist") ) name = i18nc( "@item::intable", "Artist" );
817 else if( TAGHAS("country") ) name = i18nc( "@item::intable", "Country" );
818 else if( TAGHAS("date") ) name = i18nc( "@item::intable", "Date" );
819 else if( TAGHAS("format") ) name = i18nc( "@item::intable File Format", "Format" );
820 else if( TAGHAS("height") ) name = i18nc( "@item::intable Image Height", "Height" );
821 else if( TAGHAS("name") ) name = i18nc( "@item::intable Album Title", "Title" );
822 else if( TAGHAS("type") ) name = i18nc( "@item::intable Release Type", "Type" );
823 else if( TAGHAS("released") ) name = i18nc( "@item::intable Release Date", "Released" );
824 else if( TAGHAS("size") ) name = i18nc( "@item::intable File Size", "Size" );
825 else if( TAGHAS("source") ) name = i18nc( "@item::intable Cover Provider", "Source" );
826 else if( TAGHAS("title") ) name = i18nc( "@item::intable Album Title", "Title" );
827 else if( TAGHAS("width") ) name = i18nc( "@item::intable Image Width", "Width" );
828 #undef TAGHAS
829
830 if( !name.isEmpty() )
831 {
832 QLabel *label = new QLabel( value, 0 );
833 label->setToolTip( value );
834 layout->addRow( QStringLiteral("<b>%1:</b>").arg(name), label );
835 }
836 }
837 ++mit;
838 }
839
840 QString refUrl;
841
842 const QString source = m_metadata.value( "source" );
843 if( source == "Last.fm" || source == "Discogs" )
844 {
845 refUrl = m_metadata.value( "releaseurl" );
846 }
847 else if( source == "Google" )
848 {
849 refUrl = m_metadata.value( "imgrefurl" );
850 }
851
852 if( !refUrl.isEmpty() )
853 {
854 QFont font;
855 QFontMetrics qfm( font );
856 const QString &toolUrl = refUrl;
857 const QString &tooltip = qfm.elidedText( toolUrl, Qt::ElideMiddle, 350 );
858 const QString &decoded = QUrl::fromPercentEncoding( refUrl.toLocal8Bit() );
859 const QString &url = QString( "<a href=\"%1\">%2</a>" )
860 .arg( decoded,
861 i18nc("@item::intable URL", "link") );
862
863 QLabel *label = new QLabel( url, nullptr );
864 label->setOpenExternalLinks( true );
865 label->setTextInteractionFlags( Qt::TextBrowserInteraction );
866 label->setToolTip( tooltip );
867 layout->addRow( QString( "<b>%1:</b>" ).arg( i18nc("@item::intable", "URL") ), label );
868 }
869 }
870
clearMetaTable()871 void CoverFoundSideBar::clearMetaTable()
872 {
873 QFormLayout *layout = static_cast< QFormLayout* >( m_metaTable->layout() );
874 int count = layout->count();
875 while( --count >= 0 )
876 {
877 QLayoutItem *child = layout->itemAt( 0 );
878 layout->removeItem( child );
879 delete child->widget();
880 delete child;
881 }
882 }
883
CoverFoundItem(const QImage & cover,const CoverFetch::Metadata & data,const CoverFetch::ImageSize imageSize,QListWidget * parent)884 CoverFoundItem::CoverFoundItem( const QImage &cover,
885 const CoverFetch::Metadata &data,
886 const CoverFetch::ImageSize imageSize,
887 QListWidget *parent )
888 : QListWidgetItem( parent )
889 , m_metadata( data )
890 {
891 switch( imageSize )
892 {
893 default:
894 case CoverFetch::NormalSize:
895 m_bigPix = cover;
896 break;
897 case CoverFetch::ThumbSize:
898 m_thumb = cover;
899 break;
900 }
901
902 QPixmap scaledPix = QPixmap::fromImage(cover.scaled( QSize( 120, 120 ), Qt::KeepAspectRatio ));
903 QPixmap prettyPix = The::svgHandler()->addBordersToPixmap( scaledPix, 5, QString(), true );
904 setSizeHint( QSize( 140, 150 ) );
905 setIcon( prettyPix );
906 setCaption();
907 setFont( QFontDatabase::systemFont( QFontDatabase::SmallestReadableFont ) );
908 setTextAlignment( Qt::AlignHCenter | Qt::AlignTop );
909 }
910
~CoverFoundItem()911 CoverFoundItem::~CoverFoundItem()
912 {
913 }
914
operator ==(const CoverFoundItem & other) const915 bool CoverFoundItem::operator==( const CoverFoundItem &other ) const
916 {
917 return m_metadata == other.m_metadata;
918 }
919
operator !=(const CoverFoundItem & other) const920 bool CoverFoundItem::operator!=( const CoverFoundItem &other ) const
921 {
922 return !( *this == other );
923 }
924
setCaption()925 void CoverFoundItem::setCaption()
926 {
927 QStringList captions;
928 const QString &width = m_metadata.value( QLatin1String("width") );
929 const QString &height = m_metadata.value( QLatin1String("height") );
930 if( !width.isEmpty() && !height.isEmpty() )
931 captions << QString( "%1 x %2" ).arg( width, height );
932
933 int size = m_metadata.value( QLatin1String("size") ).toInt();
934 if( size )
935 {
936 const QString source = m_metadata.value( QLatin1String("source") );
937
938 captions << ( QString::number( size ) + QLatin1Char('k') );
939 }
940
941 if( !captions.isEmpty() )
942 setText( captions.join( QLatin1String( " - " ) ) );
943 }
944
945