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

0001 /****************************************************************************************
0002  * Copyright (c) 2004 Mark Kretschmann <kretschmann@kde.org>                            *
0003  * Copyright (c) 2004 Stefan Bogner <bochi@online.ms>                                   *
0004  * Copyright (c) 2004 Max Howell <max.howell@methylblue.com>                            *
0005  * Copyright (c) 2007 Dan Meltzer <parallelgrapefruit@gmail.com>                        *
0006  * Copyright (c) 2009 Martin Sandsmark <sandsmark@samfundet.no>                         *
0007  *                                                                                      *
0008  * This program is free software; you can redistribute it and/or modify it under        *
0009  * the terms of the GNU General Public License as published by the Free Software        *
0010  * Foundation; either version 2 of the License, or (at your option) any later           *
0011  * version.                                                                             *
0012  *                                                                                      *
0013  * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
0014  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
0015  * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
0016  *                                                                                      *
0017  * You should have received a copy of the GNU General Public License along with         *
0018  * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
0019  ****************************************************************************************/
0020 
0021 #define DEBUG_PREFIX "CoverFoundDialog"
0022 
0023 #include "CoverFoundDialog.h"
0024 
0025 #include "SvgHandler.h"
0026 #include "core/support/Amarok.h"
0027 #include "core/support/Debug.h"
0028 #include "covermanager/CoverViewDialog.h"
0029 #include "statusbar/KJobProgressBar.h"
0030 #include "widgets/AlbumBreadcrumbWidget.h"
0031 #include "widgets/PixmapViewer.h"
0032 
0033 #include <QCloseEvent>
0034 #include <QDir>
0035 #include <QFileDialog>
0036 #include <QFontDatabase>
0037 #include <QFormLayout>
0038 #include <QGridLayout>
0039 #include <QHeaderView>
0040 #include <QLineEdit>
0041 #include <QListWidget>
0042 #include <QMenu>
0043 #include <QMimeDatabase>
0044 #include <QMimeType>
0045 #include <QPushButton>
0046 #include <QScrollArea>
0047 #include <QSplitter>
0048 #include <QStandardPaths>
0049 #include <QTabWidget>
0050 
0051 #include <KComboBox>
0052 #include <KConfigGroup>
0053 #include <KLineEdit>
0054 #include <KLocalizedString>
0055 #include <KMessageBox>
0056 #include <KWindowConfig>
0057 
0058 CoverFoundDialog::CoverFoundDialog( const CoverFetchUnit::Ptr &unit,
0059                                     const CoverFetch::Metadata &data,
0060                                     QWidget *parent )
0061     : QDialog( parent )
0062     , m_album( unit->album() )
0063     , m_isSorted( false )
0064     , m_sortEnabled( false )
0065     , m_unit( unit )
0066     , m_queryPage( 0 )
0067 {
0068     DEBUG_BLOCK
0069 
0070     setLayout( new QVBoxLayout );
0071 
0072     QSplitter *splitter = new QSplitter( this );
0073     layout()->addWidget( splitter );
0074 
0075     QDialogButtonBox *buttonBox = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this );
0076     QPushButton *clearButton = buttonBox->addButton( QStringLiteral( "clear" ), QDialogButtonBox::ActionRole ); // clear icon view
0077     layout()->addWidget( buttonBox );
0078 
0079     connect( clearButton, &QAbstractButton::clicked, this, &CoverFoundDialog::clearView );
0080     connect( buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept );
0081     connect( buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject );
0082 
0083     m_sideBar = new CoverFoundSideBar( m_album, splitter );
0084 
0085     BoxWidget *vbox = new BoxWidget( true, splitter );
0086     vbox->layout()->setSpacing( 4 );
0087 
0088     BoxWidget *breadcrumbBox = new BoxWidget( false, vbox );
0089     QLabel *breadcrumbLabel = new QLabel( i18n( "Finding cover for" ), breadcrumbBox );
0090     AlbumBreadcrumbWidget *breadcrumb = new AlbumBreadcrumbWidget( m_album, breadcrumbBox );
0091 
0092     QFont breadcrumbLabelFont;
0093     breadcrumbLabelFont.setBold( true );
0094     breadcrumbLabel->setFont( breadcrumbLabelFont );
0095     breadcrumbLabel->setIndent( 4 );
0096 
0097     connect( breadcrumb, &AlbumBreadcrumbWidget::artistClicked, this, &CoverFoundDialog::addToCustomSearch );
0098     connect( breadcrumb, &AlbumBreadcrumbWidget::albumClicked, this, &CoverFoundDialog::addToCustomSearch );
0099 
0100     BoxWidget *searchBox = new BoxWidget( false, vbox );
0101 
0102     QStringList completionNames;
0103     QString firstRunQuery( m_album->name() );
0104     completionNames << firstRunQuery;
0105     if( m_album->hasAlbumArtist() )
0106     {
0107         const QString &name = m_album->albumArtist()->name();
0108         completionNames << name;
0109         firstRunQuery += ' ' + name;
0110     }
0111     m_query = firstRunQuery;
0112     m_album->setSuppressImageAutoFetch( true );
0113 
0114     m_search = new KComboBox( searchBox );
0115     m_search->setEditable( true ); // creates a QLineEdit for the combobox
0116     m_search->setTrapReturnKey( true );
0117     m_search->setInsertPolicy( KComboBox::NoInsert ); // insertion is handled by us
0118     m_search->setCompletionMode( KCompletion::CompletionPopup );
0119     m_search->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Fixed );
0120     m_search->lineEdit()->setPlaceholderText( i18n( "Enter Custom Search" ) );
0121     m_search->completionObject()->setOrder( KCompletion::Insertion );
0122     m_search->completionObject()->setIgnoreCase( true );
0123     m_search->completionObject()->setItems( completionNames );
0124     m_search->insertItem( 0, KStandardGuiItem::find().icon(), QString() );
0125     m_search->insertSeparator( 1 );
0126     m_search->insertItem( 2, QIcon::fromTheme("filename-album-amarok"), m_album->name() );
0127     if( m_album->hasAlbumArtist() )
0128         m_search->insertItem( 3, QIcon::fromTheme("filename-artist-amarok"), m_album->albumArtist()->name() );
0129 
0130     auto findItem = KStandardGuiItem::find();
0131     m_searchButton = new QPushButton( findItem.icon(), findItem.text(), searchBox );
0132     auto configureItem = KStandardGuiItem::configure();
0133     QPushButton *sourceButton = new QPushButton( configureItem.icon(), configureItem.text(), searchBox );
0134     updateSearchButton( firstRunQuery );
0135 
0136     QMenu *sourceMenu = new QMenu( sourceButton );
0137     QAction *lastFmAct = new QAction( i18n( "Last.fm" ), sourceMenu );
0138     QAction *googleAct = new QAction( i18n( "Google" ), sourceMenu );
0139     QAction *discogsAct = new QAction( i18n( "Discogs" ), sourceMenu );
0140     // TODO: currently broken, re-enable after adjusting to current API/returned doc
0141     googleAct->setEnabled( false );
0142     // TODO: currently broken, re-enable after adjusting to current API/returned doc
0143     discogsAct->setEnabled( false );
0144     lastFmAct->setCheckable( true );
0145     googleAct->setCheckable( true );
0146     discogsAct->setCheckable( true );
0147     connect( lastFmAct, &QAction::triggered, this, &CoverFoundDialog::selectLastFm );
0148     connect( googleAct, &QAction::triggered, this, &CoverFoundDialog::selectGoogle );
0149     connect( discogsAct, &QAction::triggered, this, &CoverFoundDialog::selectDiscogs );
0150 
0151     m_sortAction = new QAction( i18n( "Sort by size" ), sourceMenu );
0152     m_sortAction->setCheckable( true );
0153     connect( m_sortAction, &QAction::triggered, this, &CoverFoundDialog::sortingTriggered );
0154 
0155     QActionGroup *ag = new QActionGroup( sourceButton );
0156     ag->addAction( lastFmAct );
0157     ag->addAction( googleAct );
0158     ag->addAction( discogsAct );
0159     sourceMenu->addActions( ag->actions() );
0160     sourceMenu->addSeparator();
0161     sourceMenu->addAction( m_sortAction );
0162     sourceButton->setMenu( sourceMenu );
0163 
0164     connect( m_search, QOverload<const QString&>::of(&KComboBox::returnPressed),
0165              this, &CoverFoundDialog::insertComboText );
0166     connect( m_search, QOverload<const QString&>::of(&KComboBox::returnPressed),
0167              this, &CoverFoundDialog::processQuery );
0168     connect( m_search, QOverload<const QString&>::of(&KComboBox::returnPressed),
0169              this, &CoverFoundDialog::updateSearchButton );
0170     connect( m_search, &KComboBox::editTextChanged, this, &CoverFoundDialog::updateSearchButton );
0171 
0172     sourceMenu->addAction( m_sortAction );
0173     sourceButton->setMenu( sourceMenu );
0174 
0175     connect( m_search, QOverload<const QString&>::of(&KComboBox::returnPressed),
0176              this, &CoverFoundDialog::insertComboText );
0177     connect( m_search, QOverload<const QString&>::of(&KComboBox::returnPressed),
0178              this, &CoverFoundDialog::processQuery );
0179     connect( m_search, QOverload<const QString&>::of(&KComboBox::returnPressed),
0180              this, &CoverFoundDialog::updateSearchButton );
0181     connect( m_search, &KComboBox::editTextChanged, this, &CoverFoundDialog::updateSearchButton );
0182     connect( dynamic_cast<KLineEdit*>(m_search->lineEdit()), &KLineEdit::clearButtonClicked,
0183              this, &CoverFoundDialog::clearQueryButtonClicked);
0184     connect( m_searchButton, &QPushButton::clicked, this, &CoverFoundDialog::processCurrentQuery );
0185 
0186     m_view = new QListWidget( vbox );
0187     m_view->setAcceptDrops( false );
0188     m_view->setContextMenuPolicy( Qt::CustomContextMenu );
0189     m_view->setDragDropMode( QAbstractItemView::NoDragDrop );
0190     m_view->setDragEnabled( false );
0191     m_view->setDropIndicatorShown( false );
0192     m_view->setMovement( QListView::Static );
0193     m_view->setGridSize( QSize( 140, 150 ) );
0194     m_view->setIconSize( QSize( 120, 120 ) );
0195     m_view->setSpacing( 4 );
0196     m_view->setViewMode( QListView::IconMode );
0197     m_view->setResizeMode( QListView::Adjust );
0198 
0199     connect( m_view, &QListWidget::currentItemChanged,
0200              this, &CoverFoundDialog::currentItemChanged );
0201     connect( m_view, &QListWidget::itemDoubleClicked,
0202              this, &CoverFoundDialog::itemDoubleClicked );
0203     connect( m_view, &QListWidget::customContextMenuRequested,
0204              this, &CoverFoundDialog::itemMenuRequested );
0205 
0206     splitter->addWidget( m_sideBar );
0207     splitter->addWidget( vbox );
0208 //     setMainWidget( splitter );
0209 
0210     const KConfigGroup config = Amarok::config( "Cover Fetcher" );
0211     const QString source = config.readEntry( "Interactive Image Source", "LastFm" );
0212     m_sortEnabled = config.readEntry( "Sort by Size", false );
0213     m_sortAction->setChecked( m_sortEnabled );
0214     m_isSorted = m_sortEnabled;
0215     KWindowConfig::restoreWindowSize( windowHandle(), config ); // call this after setMainWidget()
0216 
0217     if( source == "LastFm" )
0218         lastFmAct->setChecked( true );
0219     else if( source == "Discogs" )
0220         discogsAct->setChecked( true );
0221     else
0222         googleAct->setChecked( true );
0223 
0224     typedef CoverFetchArtPayload CFAP;
0225     const CFAP *payload = dynamic_cast< const CFAP* >( unit->payload() );
0226     if( !m_album->hasImage() )
0227         m_sideBar->setPixmap( QPixmap::fromImage( m_album->image(190 ) ) );
0228     else if( payload )
0229         add( m_album->image(), data, payload->imageSize() );
0230     else
0231         add( m_album->image(), data );
0232     m_view->setCurrentItem( m_view->item( 0 ) );
0233     updateGui();
0234     
0235     connect( The::networkAccessManager(), &NetworkAccessManagerProxy::requestRedirectedReply,
0236              this, &CoverFoundDialog::fetchRequestRedirected );
0237 }
0238 
0239 CoverFoundDialog::~CoverFoundDialog()
0240 {
0241     m_album->setSuppressImageAutoFetch( false );
0242 
0243     const QList<QListWidgetItem*> &viewItems = m_view->findItems( QChar('*'), Qt::MatchWildcard );
0244     qDeleteAll( viewItems );
0245     delete m_dialog.data();
0246 }
0247 
0248 void CoverFoundDialog::hideEvent( QHideEvent *event )
0249 {
0250     KConfigGroup config = Amarok::config( "Cover Fetcher" );
0251     config.writeEntry( "geometry", saveGeometry() );
0252     event->accept();
0253 }
0254 
0255 void CoverFoundDialog::add( const QImage &cover,
0256                             const CoverFetch::Metadata &metadata,
0257                             const CoverFetch::ImageSize imageSize )
0258 {
0259     if( cover.isNull() )
0260         return;
0261 
0262     if( !contains( metadata ) )
0263     {
0264         CoverFoundItem *item = new CoverFoundItem( cover, metadata, imageSize );
0265         addToView( item );
0266     }
0267 }
0268 
0269 void CoverFoundDialog::addToView( CoverFoundItem *item )
0270 {
0271     const CoverFetch::Metadata &metadata = item->metadata();
0272 
0273     if( m_sortEnabled && metadata.contains( "width" ) && metadata.contains( "height" ) )
0274     {
0275         if( m_isSorted )
0276         {
0277             const int size = metadata.value( "width" ).toInt() * metadata.value( "height" ).toInt();
0278             QList< int >::iterator i = std::lower_bound( m_sortSizes.begin(), m_sortSizes.end(), size );
0279             m_sortSizes.insert( i, size );
0280             const int index = m_sortSizes.count() - m_sortSizes.indexOf( size ) - 1;
0281             m_view->insertItem( index, item );
0282         }
0283         else
0284         {
0285             m_view->addItem( item );
0286             sortCoversBySize();
0287         }
0288     }
0289     else
0290     {
0291         m_view->addItem( item );
0292     }
0293     updateGui();
0294 }
0295 
0296 bool CoverFoundDialog::contains( const CoverFetch::Metadata &metadata ) const
0297 {
0298     for( int i = 0, count = m_view->count(); i < count; ++i )
0299     {
0300         CoverFoundItem *item = static_cast<CoverFoundItem*>( m_view->item(i) );
0301         if( item->metadata() == metadata )
0302             return true;
0303     }
0304     return false;
0305 }
0306 
0307 void CoverFoundDialog::addToCustomSearch( const QString &text )
0308 {
0309     const QString &query = m_search->currentText();
0310     if( !text.isEmpty() && !query.contains( text ) )
0311     {
0312         QStringList q;
0313         if( !query.isEmpty() )
0314             q << query;
0315         q << text;
0316         const QString result = q.join( QLatin1Char( ' ' ) );
0317         qobject_cast<QLineEdit*>( m_search->lineEdit() )->setText( result );
0318     }
0319 }
0320 
0321 void CoverFoundDialog::clearQueryButtonClicked()
0322 {
0323     m_query.clear();
0324     m_queryPage = 0;
0325     updateGui();
0326 }
0327 
0328 void CoverFoundDialog::clearView()
0329 {
0330     m_view->clear();
0331     m_sideBar->clear();
0332     m_sortSizes.clear();
0333     updateGui();
0334 }
0335 
0336 void CoverFoundDialog::insertComboText( const QString &text )
0337 {
0338     if( text.isEmpty() )
0339         return;
0340 
0341     if( m_search->contains( text ) )
0342     {
0343         m_search->setCurrentIndex( m_search->findText( text ) );
0344         return;
0345     }
0346     m_search->completionObject()->addItem( text );
0347     m_search->insertItem( 0, KStandardGuiItem::find().icon(), text );
0348     m_search->setCurrentIndex( 0 );
0349 }
0350 
0351 void CoverFoundDialog::currentItemChanged( QListWidgetItem *current, QListWidgetItem *previous )
0352 {
0353     Q_UNUSED( previous )
0354     if( !current )
0355         return;
0356     CoverFoundItem *it = static_cast< CoverFoundItem* >( current );
0357     QImage image = it->hasBigPix() ? it->bigPix() : it->thumb();
0358     m_image = image;
0359     m_sideBar->setPixmap( QPixmap::fromImage(image), it->metadata() );
0360 }
0361 
0362 void CoverFoundDialog::itemDoubleClicked( QListWidgetItem *item )
0363 {
0364     Q_UNUSED( item )
0365     slotButtonClicked( QDialog::Accepted );
0366 }
0367 
0368 void CoverFoundDialog::itemMenuRequested( const QPoint &pos )
0369 {
0370     const QPoint globalPos = m_view->mapToGlobal( pos );
0371     QModelIndex index = m_view->indexAt( pos );
0372 
0373     if( !index.isValid() )
0374         return;
0375 
0376     CoverFoundItem *item = static_cast< CoverFoundItem* >( m_view->item( index.row() ) );
0377     item->setSelected( true );
0378 
0379     QMenu menu( this );
0380     QAction *display = new QAction( QIcon::fromTheme(QStringLiteral("zoom-original")), i18n("Display Cover"), &menu );
0381     connect( display, &QAction::triggered, this, &CoverFoundDialog::display );
0382 
0383     QAction *save = new QAction( QIcon::fromTheme(QStringLiteral("document-save")), i18n("Save As"), &menu );
0384     connect( save, &QAction::triggered, this, &CoverFoundDialog::saveAs );
0385 
0386     menu.addAction( display );
0387     menu.addAction( save );
0388     menu.exec( globalPos );
0389 }
0390 
0391 void CoverFoundDialog::saveAs()
0392 {
0393     CoverFoundItem *item = static_cast< CoverFoundItem* >( m_view->currentItem() );
0394     if( !item->hasBigPix() && !fetchBigPix() )
0395         return;
0396 
0397     Meta::TrackList tracks = m_album->tracks();
0398     if( tracks.isEmpty() )
0399     {
0400         warning() << "no tracks associated with album" << m_album->name();
0401         return;
0402     }
0403 
0404     QFileDialog dlg;
0405     QWidget::setWindowTitle( i18n("Cover Image Save Location") );
0406     dlg.setFileMode( QFileDialog::AnyFile );
0407     dlg.setSupportedSchemes( QStringList( QStringLiteral( "file" ) ) );
0408     dlg.setAcceptMode( QFileDialog::AcceptSave );
0409 
0410     QUrl selectedUrl;
0411     selectedUrl.setPath( "cover.jpg" );
0412     dlg.selectUrl( selectedUrl );
0413 
0414     QStringList supportedMimeTypes;
0415     supportedMimeTypes << "image/jpeg";
0416     supportedMimeTypes << "image/png";
0417     dlg.setMimeTypeFilters( supportedMimeTypes );
0418 
0419     QUrl saveUrl;
0420     int res = dlg.exec();
0421     switch( res )
0422     {
0423     case QDialog::Accepted:
0424         saveUrl = dlg.selectedUrls().value( 0 );
0425         break;
0426     case QDialog::Rejected:
0427         return;
0428     }
0429 
0430     QFile saveFile( saveUrl.path() );
0431     if( !saveFile.open( QFile::WriteOnly ) )
0432     {
0433         KMessageBox::detailedError( this,
0434                                     i18n("Sorry, the cover could not be saved."),
0435                                     saveFile.errorString() );
0436         return;
0437     }
0438 
0439     const QImage &image = item->bigPix();
0440     QMimeDatabase db;
0441     const QString &ext = db.suffixForFileName( saveUrl.path() ).toLower();
0442     bool ok;
0443     if( ext == "jpg" || ext == "jpeg" )
0444         ok = image.save( &saveFile, "JPG" );
0445     else if( ext == "png" )
0446         ok = image.save( &saveFile, "PNG" );
0447     else
0448         ok = image.save( &saveFile );
0449 
0450     if( !ok )
0451     {
0452         KMessageBox::detailedError( this,
0453                                     i18n("Sorry, the cover could not be saved."),
0454                                     saveFile.errorString() );
0455         saveFile.remove();
0456     }
0457 }
0458 
0459 void CoverFoundDialog::slotButtonClicked( int button )
0460 {
0461     if( button == QDialog::Accepted )
0462     {
0463         CoverFoundItem *item = dynamic_cast< CoverFoundItem* >( m_view->currentItem() );
0464         if( !item )
0465         {
0466             reject();
0467             return;
0468         }
0469 
0470         bool gotBigPix( true );
0471         if( !item->hasBigPix() )
0472             gotBigPix = fetchBigPix();
0473 
0474         if( gotBigPix )
0475         {
0476             m_image = item->bigPix();
0477             accept();
0478         }
0479     }
0480 }
0481 
0482 void CoverFoundDialog::fetchRequestRedirected( QNetworkReply *oldReply,
0483                                                QNetworkReply *newReply )
0484 {
0485     QUrl oldUrl = oldReply->request().url();
0486     QUrl newUrl = newReply->request().url();
0487 
0488     // Since we were redirected we have to check if the redirect
0489     // was for one of our URLs and if the new URL is not handled
0490     // already.
0491     if( m_urls.contains( oldUrl ) && !m_urls.contains( newUrl ) )
0492     {
0493         // Get the unit for the old URL.
0494         CoverFoundItem *item = m_urls.value( oldUrl );
0495 
0496         // Add the unit with the new URL and remove the old one.
0497         m_urls.insert( newUrl, item );
0498         m_urls.remove( oldUrl );
0499     }
0500 }
0501 
0502 void CoverFoundDialog::handleFetchResult( const QUrl &url, const QByteArray &data,
0503                                           const NetworkAccessManagerProxy::Error &e )
0504 {
0505     CoverFoundItem *item = m_urls.take( url );
0506     QImage image;
0507     if( item && e.code == QNetworkReply::NoError && image.loadFromData( data ) )
0508     {
0509         item->setBigPix( image );
0510         m_sideBar->setPixmap( QPixmap::fromImage( image ) );
0511         if( m_dialog )
0512             m_dialog->accept();
0513     }
0514     else
0515     {
0516         QStringList errors;
0517         errors << e.description;
0518         KMessageBox::errorList( this, i18n("Sorry, the cover image could not be retrieved."), errors );
0519         if( m_dialog )
0520             m_dialog->reject();
0521     }
0522 }
0523 
0524 bool CoverFoundDialog::fetchBigPix()
0525 {
0526     DEBUG_BLOCK
0527     CoverFoundItem *item = static_cast< CoverFoundItem* >( m_view->currentItem() );
0528     const QUrl url( item->metadata().value( "normalarturl" ) );
0529     if( !url.isValid() )
0530         return false;
0531 
0532     QNetworkReply *reply = The::networkAccessManager()->getData( url, this, &CoverFoundDialog::handleFetchResult );
0533     m_urls.insert( url, item );
0534 
0535     if( !m_dialog )
0536     {
0537         m_dialog = new QProgressDialog( this );
0538         m_dialog->setWindowTitle( i18n( "Fetching Large Cover" ) );
0539         m_dialog->setLabelText( i18n( "Download Progress" ) );
0540         m_dialog->setModal( true );
0541         m_dialog->setCancelButton( new QPushButton( i18n( "Cancel" ) ) );
0542         m_dialog->setAutoClose( false );
0543         m_dialog->setAutoReset( true );
0544         m_dialog->setMinimumWidth( 300 );
0545         connect( reply, &QNetworkReply::downloadProgress,
0546                  this, &CoverFoundDialog::downloadProgressed );
0547     }
0548     int result = m_dialog->exec();
0549     bool success = (result == QDialog::Accepted) && !m_dialog->wasCanceled();
0550     The::networkAccessManager()->abortGet( url );
0551     if( !success )
0552         m_urls.remove( url );
0553     m_dialog->deleteLater();
0554     return success;
0555 }
0556 
0557 void CoverFoundDialog::downloadProgressed( qint64 bytesReceived, qint64 bytesTotal )
0558 {
0559     if( m_dialog )
0560     {
0561         m_dialog->setRange( 0, bytesTotal );
0562         m_dialog->setValue( bytesReceived );
0563     }
0564 }
0565 
0566 void CoverFoundDialog::display()
0567 {
0568     CoverFoundItem *item = static_cast< CoverFoundItem* >( m_view->currentItem() );
0569     const bool success = item->hasBigPix() ? true : fetchBigPix();
0570     if( !success )
0571         return;
0572 
0573     const QImage &image = item->hasBigPix() ? item->bigPix() : item->thumb();
0574     CoverViewDialog *dlg = new CoverViewDialog( image, this );
0575     dlg->show();
0576     dlg->raise();
0577     dlg->activateWindow();
0578 }
0579 
0580 void CoverFoundDialog::processCurrentQuery()
0581 {
0582     const QString text = m_search->currentText();
0583     processQuery( text );
0584 }
0585 
0586 void CoverFoundDialog::processQuery( const QString &input )
0587 {
0588     const bool inputEmpty( input.isEmpty() );
0589     const bool mQueryEmpty( m_query.isEmpty() );
0590 
0591     QString q;
0592     if( inputEmpty && !mQueryEmpty )
0593     {
0594         q = m_query;
0595     }
0596     else if( !inputEmpty || !mQueryEmpty )
0597     {
0598         q = input;
0599         if( m_query != input )
0600         {
0601             m_query = input;
0602             m_queryPage = 1;
0603         }
0604     }
0605 
0606     if( !q.isEmpty() )
0607     {
0608         Q_EMIT newCustomQuery( m_album, q, m_queryPage );
0609         updateSearchButton( q );
0610         m_queryPage++;
0611     }
0612 }
0613 
0614 void CoverFoundDialog::selectDiscogs()
0615 {
0616     KConfigGroup config = Amarok::config( "Cover Fetcher" );
0617     config.writeEntry( "Interactive Image Source", "Discogs" );
0618     m_sortAction->setEnabled( true );
0619     m_queryPage = 0;
0620     processCurrentQuery();
0621     debug() << "Select Discogs as source";
0622 }
0623 
0624 void CoverFoundDialog::selectLastFm()
0625 {
0626     KConfigGroup config = Amarok::config( "Cover Fetcher" );
0627     config.writeEntry( "Interactive Image Source", "LastFm" );
0628     m_sortAction->setEnabled( false );
0629     m_queryPage = 0;
0630     processCurrentQuery();
0631     debug() << "Select Last.fm as source";
0632 }
0633 
0634 void CoverFoundDialog::selectGoogle()
0635 {
0636     KConfigGroup config = Amarok::config( "Cover Fetcher" );
0637     config.writeEntry( "Interactive Image Source", "Google" );
0638     m_sortAction->setEnabled( true );
0639     m_queryPage = 0;
0640     processCurrentQuery();
0641     debug() << "Select Google as source";
0642 }
0643 
0644 void CoverFoundDialog::setQueryPage( int page )
0645 {
0646     m_queryPage = page;
0647 }
0648 
0649 void CoverFoundDialog::sortingTriggered( bool checked )
0650 {
0651     KConfigGroup config = Amarok::config( "Cover Fetcher" );
0652     config.writeEntry( "Sort by Size", checked );
0653     m_sortEnabled = checked;
0654     m_isSorted = false;
0655     if( m_sortEnabled )
0656         sortCoversBySize();
0657     debug() << "Enable sorting by size:" << checked;
0658 }
0659 
0660 void CoverFoundDialog::sortCoversBySize()
0661 {
0662     DEBUG_BLOCK
0663 
0664     m_sortSizes.clear();
0665     QList< QListWidgetItem* > viewItems = m_view->findItems( QChar('*'), Qt::MatchWildcard );
0666     QMultiMap<int, CoverFoundItem*> sortItems;
0667 
0668     // get a list of cover items sorted (automatically by qmap) by size
0669     foreach( QListWidgetItem *viewItem, viewItems  )
0670     {
0671         CoverFoundItem *coverItem = dynamic_cast<CoverFoundItem*>( viewItem );
0672         const CoverFetch::Metadata &meta = coverItem->metadata();
0673         const int itemSize = meta.value( "width" ).toInt() * meta.value( "height" ).toInt();
0674         sortItems.insert( itemSize, coverItem );
0675         m_sortSizes << itemSize;
0676     }
0677 
0678     // take items from the view and insert into a temp list in the sorted order
0679     QList<CoverFoundItem*> coverItems = sortItems.values();
0680     QList<CoverFoundItem*> tempItems;
0681     for( int i = 0, count = sortItems.count(); i < count; ++i )
0682     {
0683         CoverFoundItem *item = coverItems.value( i );
0684         const int itemRow = m_view->row( item );
0685         QListWidgetItem *itemFromRow = m_view->takeItem( itemRow );
0686         if( itemFromRow )
0687             tempItems << dynamic_cast<CoverFoundItem*>( itemFromRow );
0688     }
0689 
0690     // add the items back to the view in descending order
0691     foreach( CoverFoundItem* item, tempItems )
0692         m_view->insertItem( 0, item );
0693 
0694     m_isSorted = true;
0695 }
0696 
0697 void CoverFoundDialog::updateSearchButton( const QString &text )
0698 {
0699     const bool isNewSearch = ( text != m_query ) ? true : false;
0700     m_searchButton->setText( isNewSearch ? KStandardGuiItem::find().text() : KStandardGuiItem::cont().text() );
0701     m_searchButton->setIcon( isNewSearch ? KStandardGuiItem::find().icon() : KStandardGuiItem::cont().icon() );
0702     m_searchButton->setToolTip( isNewSearch ? i18n( "Search" ) : i18n( "Search For More Results" ) );
0703 }
0704 
0705 void CoverFoundDialog::updateGui()
0706 {
0707     updateTitle();
0708 
0709     if( !m_search->hasFocus() )
0710         findChild<QDialogButtonBox*>()->button( QDialogButtonBox::Ok )->setFocus();
0711     update();
0712 }
0713 
0714 void CoverFoundDialog::updateTitle()
0715 {
0716     const int itemCount = m_view->count();
0717     const QString caption = ( itemCount == 0 )
0718                           ? i18n( "No Images Found" )
0719                           : i18np( "1 Image Found", "%1 Images Found", itemCount );
0720     setWindowTitle( caption );
0721 }
0722 
0723 CoverFoundSideBar::CoverFoundSideBar( const Meta::AlbumPtr &album, QWidget *parent )
0724     : BoxWidget( true, parent )
0725     , m_album( album )
0726 {
0727     m_cover = new QLabel( this );
0728     m_tabs  = new QTabWidget( this );
0729     m_notes = new QLabel;
0730     QScrollArea *metaArea = new QScrollArea;
0731     m_metaTable = new QWidget( metaArea );
0732     m_metaTable->setLayout( new QFormLayout );
0733     m_metaTable->setMinimumSize( QSize( 150, 200 ) );
0734     metaArea->setFrameShape( QFrame::NoFrame );
0735     metaArea->setWidget( m_metaTable );
0736     m_notes->setAlignment( Qt::AlignLeft | Qt::AlignTop );
0737     m_notes->setMargin( 4 );
0738     m_notes->setOpenExternalLinks( true );
0739     m_notes->setTextFormat( Qt::RichText );
0740     m_notes->setTextInteractionFlags( Qt::TextBrowserInteraction );
0741     m_notes->setWordWrap( true );
0742     m_cover->setAlignment( Qt::AlignCenter );
0743     m_tabs->addTab( metaArea, i18n( "Information" ) );
0744     m_tabs->addTab( m_notes, i18n( "Notes" ) );
0745     setMaximumWidth( 200 );
0746     setPixmap( QPixmap::fromImage( m_album->image( 190 ) ) );
0747     clear();
0748 }
0749 
0750 CoverFoundSideBar::~CoverFoundSideBar()
0751 {
0752 }
0753 
0754 void CoverFoundSideBar::clear()
0755 {
0756     clearMetaTable();
0757     m_notes->clear();
0758     m_metadata.clear();
0759 }
0760 
0761 void CoverFoundSideBar::setPixmap( const QPixmap &pixmap, const CoverFetch::Metadata &metadata )
0762 {
0763     m_metadata = metadata;
0764     updateNotes();
0765     setPixmap( pixmap );
0766 }
0767 
0768 void CoverFoundSideBar::setPixmap( const QPixmap &pixmap )
0769 {
0770     m_pixmap = pixmap;
0771     QPixmap scaledPix = pixmap.scaled( QSize( 190, 190 ), Qt::KeepAspectRatio );
0772     QPixmap prettyPix = The::svgHandler()->addBordersToPixmap( scaledPix, 5, QString(), true );
0773     m_cover->setPixmap( prettyPix );
0774     updateMetaTable();
0775 }
0776 
0777 void CoverFoundSideBar::updateNotes()
0778 {
0779     bool enableNotes( false );
0780     if( m_metadata.contains( "notes" ) )
0781     {
0782         const QString notes = m_metadata.value( "notes" );
0783         if( !notes.isEmpty() )
0784         {
0785             m_notes->setText( notes );
0786             enableNotes = true;
0787         }
0788         else
0789             enableNotes = false;
0790     }
0791     else
0792     {
0793         m_notes->clear();
0794         enableNotes = false;
0795     }
0796     m_tabs->setTabEnabled( m_tabs->indexOf( m_notes ), enableNotes );
0797 }
0798 
0799 void CoverFoundSideBar::updateMetaTable()
0800 {
0801     clearMetaTable();
0802 
0803     QFormLayout *layout = static_cast< QFormLayout* >( m_metaTable->layout() );
0804     layout->setSizeConstraint( QLayout::SetMinAndMaxSize );
0805 
0806     CoverFetch::Metadata::const_iterator mit = m_metadata.constBegin();
0807     while( mit != m_metadata.constEnd() )
0808     {
0809         const QString &value = mit.value();
0810         if( !value.isEmpty() )
0811         {
0812             const QString &tag = mit.key();
0813             QString name;
0814 
0815             #define TAGHAS(s) (tag.compare(QLatin1String(s)) == 0)
0816             if( TAGHAS("artist") )        name = i18nc( "@item::intable", "Artist" );
0817             else if( TAGHAS("country") )  name = i18nc( "@item::intable", "Country" );
0818             else if( TAGHAS("date") )     name = i18nc( "@item::intable", "Date" );
0819             else if( TAGHAS("format") )   name = i18nc( "@item::intable File Format", "Format" );
0820             else if( TAGHAS("height") )   name = i18nc( "@item::intable Image Height", "Height" );
0821             else if( TAGHAS("name") )     name = i18nc( "@item::intable Album Title", "Title" );
0822             else if( TAGHAS("type") )     name = i18nc( "@item::intable Release Type", "Type" );
0823             else if( TAGHAS("released") ) name = i18nc( "@item::intable Release Date", "Released" );
0824             else if( TAGHAS("size") )     name = i18nc( "@item::intable File Size", "Size" );
0825             else if( TAGHAS("source") )   name = i18nc( "@item::intable Cover Provider", "Source" );
0826             else if( TAGHAS("title") )    name = i18nc( "@item::intable Album Title", "Title" );
0827             else if( TAGHAS("width") )    name = i18nc( "@item::intable Image Width", "Width" );
0828             #undef TAGHAS
0829 
0830             if( !name.isEmpty() )
0831             {
0832                 QLabel *label = new QLabel( value, nullptr );
0833                 label->setToolTip( value );
0834                 layout->addRow( QStringLiteral("<b>%1:</b>").arg(name), label );
0835             }
0836         }
0837         ++mit;
0838     }
0839 
0840     QString refUrl;
0841 
0842     const QString source = m_metadata.value( "source" );
0843     if( source == "Last.fm" || source == "Discogs" )
0844     {
0845         refUrl = m_metadata.value( "releaseurl" );
0846     }
0847     else if( source == "Google" )
0848     {
0849         refUrl = m_metadata.value( "imgrefurl" );
0850     }
0851 
0852     if( !refUrl.isEmpty() )
0853     {
0854         QFont font;
0855         QFontMetrics qfm( font );
0856         const QString &toolUrl = refUrl;
0857         const QString &tooltip = qfm.elidedText( toolUrl, Qt::ElideMiddle, 350 );
0858         const QString &decoded = QUrl::fromPercentEncoding( refUrl.toLocal8Bit() );
0859         const QString &url     = QString( "<a href=\"%1\">%2</a>" )
0860                                     .arg( decoded,
0861                                           i18nc("@item::intable URL", "link") );
0862 
0863         QLabel *label = new QLabel( url, nullptr );
0864         label->setOpenExternalLinks( true );
0865         label->setTextInteractionFlags( Qt::TextBrowserInteraction );
0866         label->setToolTip( tooltip );
0867         layout->addRow( QString( "<b>%1:</b>" ).arg( i18nc("@item::intable", "URL") ), label );
0868     }
0869 }
0870 
0871 void CoverFoundSideBar::clearMetaTable()
0872 {
0873     QFormLayout *layout = static_cast< QFormLayout* >( m_metaTable->layout() );
0874     int count = layout->count();
0875     while( --count >= 0 )
0876     {
0877         QLayoutItem *child = layout->itemAt( 0 );
0878         layout->removeItem( child );
0879         delete child->widget();
0880         delete child;
0881     }
0882 }
0883 
0884 CoverFoundItem::CoverFoundItem( const QImage &cover,
0885                                 const CoverFetch::Metadata &data,
0886                                 const CoverFetch::ImageSize imageSize,
0887                                 QListWidget *parent )
0888     : QListWidgetItem( parent )
0889     , m_metadata( data )
0890 {
0891     switch( imageSize )
0892     {
0893     default:
0894     case CoverFetch::NormalSize:
0895         m_bigPix = cover;
0896         break;
0897     case CoverFetch::ThumbSize:
0898         m_thumb = cover;
0899         break;
0900     }
0901 
0902     QPixmap scaledPix = QPixmap::fromImage(cover.scaled( QSize( 120, 120 ), Qt::KeepAspectRatio ));
0903     QPixmap prettyPix = The::svgHandler()->addBordersToPixmap( scaledPix, 5, QString(), true );
0904     setSizeHint( QSize( 140, 150 ) );
0905     setIcon( prettyPix );
0906     setCaption();
0907     setFont( QFontDatabase::systemFont( QFontDatabase::SmallestReadableFont ) );
0908     setTextAlignment( Qt::AlignHCenter | Qt::AlignTop );
0909 }
0910 
0911 CoverFoundItem::~CoverFoundItem()
0912 {
0913 }
0914 
0915 bool CoverFoundItem::operator==( const CoverFoundItem &other ) const
0916 {
0917     return m_metadata == other.m_metadata;
0918 }
0919 
0920 bool CoverFoundItem::operator!=( const CoverFoundItem &other ) const
0921 {
0922     return !( *this == other );
0923 }
0924 
0925 void CoverFoundItem::setCaption()
0926 {
0927     QStringList captions;
0928     const QString &width = m_metadata.value( QLatin1String("width") );
0929     const QString &height = m_metadata.value( QLatin1String("height") );
0930     if( !width.isEmpty() && !height.isEmpty() )
0931         captions << QString( "%1 x %2" ).arg( width, height );
0932 
0933     int size = m_metadata.value( QLatin1String("size") ).toInt();
0934     if( size )
0935     {
0936         const QString source = m_metadata.value( QLatin1String("source") );
0937 
0938         captions << ( QString::number( size ) + QLatin1Char('k') );
0939     }
0940 
0941     if( !captions.isEmpty() )
0942         setText( captions.join( QLatin1String( " - " ) ) );
0943 }
0944