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