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

0001 /****************************************************************************************
0002  * Copyright (c) 2004 Mark Kretschmann <kretschmann@kde.org>                            *
0003  * Copyright (c) 2004 Pierpaolo Di Panfilo <pippo_dp@libero.it>                         *
0004  * Copyright (c) 2005-2006 Alexandre Pereira de Oliveira <aleprj@gmail.com>             *
0005  * Copyright (c) 2008 Téo Mrnjavac <teo@kde.org>                                        *
0006  * Copyright (c) 2008 Leo Franchi <lfranchi@kde.org>                                    *
0007  * Copyright (c) 2009 Daniel Dewald <Daniel.Dewald@time-shift.de>                       *
0008  * Copyright (c) 2009 Pierre Dumuid <pmdumuid@gmail.com>                                *
0009  * Copyright (c) 2011 Ralf Engels <ralf-engels@gmx.de>                                  *
0010  *                                                                                      *
0011  * This program is free software; you can redistribute it and/or modify it under        *
0012  * the terms of the GNU General Public License as published by the Free Software        *
0013  * Foundation; either version 2 of the License, or (at your option) any later           *
0014  * version.                                                                             *
0015  *                                                                                      *
0016  * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
0017  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
0018  * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
0019  *                                                                                      *
0020  * You should have received a copy of the GNU General Public License along with         *
0021  * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
0022  ****************************************************************************************/
0023 
0024 #define DEBUG_PREFIX "TagDialog"
0025 
0026 #include "TagDialog.h"
0027 
0028 #include "MainWindow.h"
0029 #include "SvgHandler.h"
0030 #include "core/collections/QueryMaker.h"
0031 #include "core/logger/Logger.h"
0032 #include "core/meta/Statistics.h"
0033 #include "core/meta/TrackEditor.h"
0034 #include "core/meta/support/MetaUtility.h"
0035 #include "core/support/Amarok.h"
0036 #include "core/support/Components.h"
0037 #include "core/support/Debug.h"
0038 #include "core-impl/collections/support/CollectionManager.h"
0039 #include "covermanager/CoverFetchingActions.h"
0040 #include "dialogs/MusicBrainzTagger.h"
0041 #include "widgets/CoverLabel.h"
0042 #include "widgets/FilenameLayoutWidget.h"
0043 #include "ui_TagDialogBase.h" // needs to be after including CoverLabel, silly
0044 #include "TagGuesserDialog.h"
0045 
0046 #include <QLineEdit>
0047 #include <QMenu>
0048 #include <QTimer>
0049 
0050 #include <KRun>
0051 
0052 #include <thread>
0053 
0054 
0055 namespace Meta {
0056 namespace Field {
0057     const QString LABELS = "labels";
0058     const QString LYRICS = "lyrics";
0059     const QString TYPE = "type";
0060     const QString COLLECTION = "collection";
0061     const QString NOTE = "note";
0062 }
0063 }
0064 
0065 TagDialog::TagDialog( const Meta::TrackList &tracks, QWidget *parent )
0066     : QDialog( parent )
0067     , m_perTrack( true )
0068     , m_currentTrackNum( 0 )
0069     , m_changed( false )
0070     , m_queryMaker( nullptr )
0071     , ui( new Ui::TagDialogBase() )
0072 {
0073     DEBUG_BLOCK
0074 
0075     foreach( Meta::TrackPtr track, tracks )
0076         addTrack( track );
0077 
0078     ui->setupUi( this );
0079     resize( minimumSizeHint() );
0080     initUi();
0081     setCurrentTrack( 0 );
0082 }
0083 
0084 TagDialog::TagDialog( Meta::TrackPtr track, QWidget *parent )
0085     : QDialog( parent )
0086     , m_perTrack( true )
0087     , m_currentTrackNum( 0 )
0088     , m_changed( false )
0089     , m_queryMaker( nullptr )
0090     , ui( new Ui::TagDialogBase() )
0091 {
0092     DEBUG_BLOCK
0093 
0094     addTrack( track );
0095     ui->setupUi( this );
0096     resize( minimumSizeHint() );
0097     initUi();
0098     setCurrentTrack( 0 );
0099 
0100     QTimer::singleShot( 0, this, &TagDialog::show );
0101 }
0102 
0103 TagDialog::TagDialog( Collections::QueryMaker *qm )
0104     : QDialog( The::mainWindow() )
0105     , m_perTrack( true )
0106     , m_currentTrackNum( 0 )
0107     , m_changed( false )
0108     , m_queryMaker( qm )
0109     , ui( new Ui::TagDialogBase() )
0110 {
0111     DEBUG_BLOCK
0112 
0113     ui->setupUi( this );
0114     resize( minimumSizeHint() );
0115 
0116     qm->setQueryType( Collections::QueryMaker::Track );
0117     connect( qm, &Collections::QueryMaker::newArtistsReady, this, &TagDialog::artistsReady, Qt::QueuedConnection );
0118     connect( qm, &Collections::QueryMaker::newTracksReady, this, &TagDialog::tracksReady, Qt::QueuedConnection );
0119     connect( qm, &Collections::QueryMaker::newAlbumsReady, this, &TagDialog::albumsReady, Qt::QueuedConnection );
0120     connect( qm, &Collections::QueryMaker::newComposersReady, this, &TagDialog::composersReady, Qt::QueuedConnection );
0121     connect( qm, &Collections::QueryMaker::newGenresReady, this, &TagDialog::genresReady, Qt::QueuedConnection );
0122     connect( qm, &Collections::QueryMaker::newLabelsReady, this, &TagDialog::labelsReady, Qt::QueuedConnection );
0123     connect( qm, &Collections::QueryMaker::queryDone, this, &TagDialog::queryDone, Qt::QueuedConnection );
0124     qm->run();
0125 }
0126 
0127 TagDialog::~TagDialog()
0128 {
0129     DEBUG_BLOCK
0130 
0131     Amarok::config( "TagDialog" ).writeEntry( "CurrentTab", ui->qTabWidget->currentIndex() );
0132 
0133     if( m_currentAlbum )
0134         unsubscribeFrom( m_currentAlbum );
0135 
0136     // kRichTextEdit creates a signal during deletion which causes getTagsFromUi to access deleted objects BUG: 428769
0137     ui->kRichTextEdit_lyrics->disconnect();
0138 
0139     delete ui;
0140 }
0141 
0142 void
0143 TagDialog::metadataChanged( const Meta::AlbumPtr &album )
0144 {
0145     if( m_currentAlbum )
0146         return;
0147 
0148     // If the metadata of the current album has changed, reload the cover
0149     if( album == m_currentAlbum )
0150         updateCover();
0151 
0152     // TODO: if the lyrics changed: should we show a warning and ask the user
0153     // if he wants to use the new lyrics?
0154 }
0155 
0156 
0157 ////////////////////////////////////////////////////////////////////////////////
0158 // PRIVATE SLOTS
0159 ////////////////////////////////////////////////////////////////////////////////
0160 
0161 void
0162 TagDialog::addTrack( Meta::TrackPtr &track )
0163 {
0164     if( !m_tracks.contains( track ) )
0165     {
0166         m_tracks.append( track );
0167         m_storedTags.insert( track, getTagsFromTrack( track ) );
0168     }
0169 }
0170 
0171 void
0172 TagDialog::tracksReady( const Meta::TrackList &tracks )
0173 {
0174     foreach( Meta::TrackPtr track, tracks )
0175         addTrack( track );
0176 }
0177 
0178 void
0179 TagDialog::queryDone()
0180 {
0181     delete m_queryMaker;
0182     if( !m_tracks.isEmpty() )
0183     {
0184         initUi();
0185         setCurrentTrack( 0 );
0186 
0187         QTimer::singleShot( 0, this, &TagDialog::show );
0188     }
0189     else
0190     {
0191         deleteLater();
0192     }
0193 }
0194 
0195 void
0196 TagDialog::albumsReady( const Meta::AlbumList &albums )
0197 {
0198     foreach( const Meta::AlbumPtr &album, albums )
0199     {
0200         if( !album->name().isEmpty() )
0201             m_albums << album->name();
0202 
0203         if( album->hasAlbumArtist() && !album->albumArtist()->name().isEmpty() )
0204             m_albumArtists << album->albumArtist()->name();
0205     }
0206 }
0207 
0208 void
0209 TagDialog::artistsReady( const Meta::ArtistList &artists )
0210 {
0211     foreach( const Meta::ArtistPtr &artist, artists )
0212     {
0213         if( !artist->name().isEmpty() )
0214             m_artists << artist->name();
0215     }
0216 }
0217 
0218 void
0219 TagDialog::composersReady( const Meta::ComposerList &composers )
0220 {
0221     foreach( const Meta::ComposerPtr &composer, composers )
0222     {
0223         if( !composer->name().isEmpty() )
0224             m_composers << composer->name();
0225     }
0226 }
0227 
0228 void
0229 TagDialog::genresReady( const Meta::GenreList &genres )
0230 {
0231     foreach( const Meta::GenrePtr &genre, genres )
0232     {
0233         if( !genre->name().isEmpty() )  // Where the heck do the empty genres come from?
0234             m_genres << genre->name();
0235     }
0236 }
0237 
0238 
0239 void
0240 TagDialog::labelsReady( const Meta::LabelList &labels )
0241 {
0242     foreach( const Meta::LabelPtr &label, labels )
0243     {
0244         if( !label->name().isEmpty() )
0245             m_allLabels << label->name();
0246     }
0247 }
0248 
0249 void
0250 TagDialog::dataQueryDone()
0251 {
0252     // basically we want to ignore the fact that the fields are being
0253     // edited because we do it not the user, so it results in empty
0254     // tags being saved to files---data loss is BAD!
0255     bool oldChanged = m_changed;
0256 
0257     //we simply clear the completion data of all comboboxes
0258     //then load the current track again. that's more work than necessary
0259     //but the performance impact should be negligible
0260     // we do this because if we insert items and the contents of the textbox
0261     // are not in the list, it clears the textbox. which is bad --lfranchi 2.22.09
0262     QString saveText( ui->kComboBox_artist->lineEdit()->text() );
0263     QStringList artists = m_artists.values();
0264     artists.sort();
0265     ui->kComboBox_artist->clear();
0266     ui->kComboBox_artist->insertItems( 0, artists );
0267     ui->kComboBox_artist->completionObject()->setItems( artists );
0268     ui->kComboBox_artist->lineEdit()->setText( saveText );
0269 
0270     saveText = ui->kComboBox_album->lineEdit()->text();
0271     QStringList albums = m_albums.values();
0272     albums.sort();
0273     ui->kComboBox_album->clear();
0274     ui->kComboBox_album->insertItems( 0, albums );
0275     ui->kComboBox_album->completionObject()->setItems( albums );
0276     ui->kComboBox_album->lineEdit()->setText( saveText );
0277 
0278     saveText = ui->kComboBox_albumArtist->lineEdit()->text();
0279     QStringList albumArtists = m_albumArtists.values();
0280     albumArtists.sort();
0281     ui->kComboBox_albumArtist->clear();
0282     ui->kComboBox_albumArtist->insertItems( 0, albumArtists );
0283     ui->kComboBox_albumArtist->completionObject()->setItems( albumArtists );
0284     ui->kComboBox_albumArtist->lineEdit()->setText( saveText );
0285 
0286     saveText = ui->kComboBox_composer->lineEdit()->text();
0287     QStringList composers = m_composers.values();
0288     composers.sort();
0289     ui->kComboBox_composer->clear();
0290     ui->kComboBox_composer->insertItems( 0, composers );
0291     ui->kComboBox_composer->completionObject()->setItems( composers );
0292     ui->kComboBox_composer->lineEdit()->setText( saveText );
0293 
0294     saveText = ui->kComboBox_genre->lineEdit()->text();
0295     QStringList genres = m_genres.values();
0296     genres.sort();
0297     ui->kComboBox_genre->clear();
0298     ui->kComboBox_genre->insertItems( 0, genres );
0299     ui->kComboBox_genre->completionObject()->setItems( genres );
0300     ui->kComboBox_genre->lineEdit()->setText( saveText );
0301 
0302     saveText = ui->kComboBox_label->lineEdit()->text();
0303     QStringList labels = m_allLabels.values();
0304     labels.sort();
0305     ui->kComboBox_label->clear();
0306     ui->kComboBox_label->insertItems( 0, labels );
0307     ui->kComboBox_label->completionObject()->setItems( labels );
0308     ui->kComboBox_label->lineEdit()->setText( saveText );
0309 
0310     m_changed = oldChanged;
0311 }
0312 
0313 void
0314 TagDialog::removeLabelPressed() //SLOT
0315 {
0316     if( ui->labelsList->selectionModel()->hasSelection() )
0317     {
0318         QModelIndexList idxList = ui->labelsList->selectionModel()->selectedRows();
0319         QStringList selection;
0320 
0321         for( int x = 0; x < idxList.size(); ++x )
0322         {
0323             QString label = idxList.at(x).data( Qt::DisplayRole ).toString();
0324             selection.append( label );
0325         }
0326 
0327         m_labelModel->removeLabels( selection );
0328 
0329         ui->labelsList->selectionModel()->reset();
0330         labelSelected();
0331 
0332         checkChanged();
0333     }
0334 }
0335 
0336 void
0337 TagDialog::addLabelPressed() //SLOT
0338 {
0339     QString label = ui->kComboBox_label->currentText();
0340 
0341     if( !label.isEmpty() )
0342     {
0343         m_labelModel->addLabel( label );
0344         ui->kComboBox_label->setCurrentIndex( -1 );
0345         ui->kComboBox_label->completionObject()->insertItems( QStringList( label ) );
0346 
0347         if ( !ui->kComboBox_label->contains( label ) )
0348             ui->kComboBox_label->addItem( label );
0349 
0350         checkChanged();
0351     }
0352 }
0353 
0354 void
0355 TagDialog::cancelPressed() //SLOT
0356 {
0357     QApplication::restoreOverrideCursor(); // restore the cursor before closing the dialog (The musicbrainz dialog might have set it)
0358     reject();
0359 }
0360 
0361 
0362 void
0363 TagDialog::accept() //SLOT
0364 {
0365     ui->pushButton_ok->setEnabled( false ); //visual feedback
0366     saveTags();
0367 
0368     QDialog::accept();
0369 }
0370 
0371 
0372 inline void
0373 TagDialog::openPressed() //SLOT
0374 {
0375     new KRun( QUrl::fromLocalFile(m_path), this );
0376 }
0377 
0378 
0379 inline void
0380 TagDialog::previousTrack() //SLOT
0381 {
0382     setCurrentTrack( m_currentTrackNum - 1 );
0383 }
0384 
0385 
0386 inline void
0387 TagDialog::nextTrack() //SLOT
0388 {
0389     setCurrentTrack( m_currentTrackNum + 1 );
0390 }
0391 
0392 inline void
0393 TagDialog::perTrack( bool enabled ) //SLOT
0394 {
0395     if( enabled == m_perTrack )
0396         return;
0397 
0398     setTagsToTrack();
0399     setPerTrack( enabled );
0400     setTagsToUi();
0401 }
0402 
0403 
0404 void
0405 TagDialog::checkChanged() //SLOT
0406 {
0407     QVariantMap oldTags;
0408     if( m_perTrack )
0409         oldTags = m_storedTags.value( m_currentTrack );
0410     else
0411         oldTags = getTagsFromMultipleTracks();
0412     QVariantMap newTags = getTagsFromUi( oldTags );
0413 
0414     ui->pushButton_ok->setEnabled( m_changed || !newTags.isEmpty() );
0415 }
0416 
0417 inline void
0418 TagDialog::labelModified() //SLOT
0419 {
0420     ui->addButton->setEnabled( ui->kComboBox_label->currentText().length()>0 );
0421 }
0422 
0423 inline void
0424 TagDialog::labelSelected() //SLOT
0425 {
0426     ui->removeButton->setEnabled( ui->labelsList->selectionModel()->hasSelection() );
0427 }
0428 
0429 //creates a QDialog and executes the FilenameLayoutWidget. Grabs a filename scheme, extracts tags (via TagGuesser) from filename and fills the appropriate fields on TagDialog.
0430 void
0431 TagDialog::guessFromFilename() //SLOT
0432 {
0433     TagGuesserDialog dialog( m_currentTrack->playableUrl().path(), this );
0434 
0435     if( dialog.exec() == QDialog::Accepted )
0436     {
0437         dialog.onAccept();
0438 
0439         int cur = 0;
0440 
0441         QMap<qint64,QString> tags = dialog.guessedTags();
0442 
0443         if( !tags.isEmpty() )
0444         {
0445 
0446             if( tags.contains( Meta::valTitle ) )
0447                 ui->kLineEdit_title->setText( tags[Meta::valTitle] );
0448 
0449             if( tags.contains( Meta::valArtist ) )
0450             {
0451                 cur = ui->kComboBox_artist->currentIndex();
0452                 ui->kComboBox_artist->setItemText( cur, tags[Meta::valArtist] );
0453             }
0454 
0455             if( tags.contains( Meta::valAlbum ) )
0456             {
0457                 cur = ui->kComboBox_album->currentIndex();
0458                 ui->kComboBox_album->setItemText( cur, tags[Meta::valAlbum] );
0459             }
0460 
0461             if( tags.contains( Meta::valAlbumArtist ) )
0462             {
0463                 cur = ui->kComboBox_albumArtist->currentIndex();
0464                 ui->kComboBox_albumArtist->setItemText( cur, tags[Meta::valAlbumArtist] );
0465             }
0466 
0467             if( tags.contains( Meta::valTrackNr ) )
0468                 ui->qSpinBox_track->setValue( tags[Meta::valTrackNr].toInt() );
0469 
0470             if( tags.contains( Meta::valComment ) )
0471                 ui->qPlainTextEdit_comment->setPlainText( tags[Meta::valComment] );
0472 
0473             if( tags.contains( Meta::valYear ) )
0474                 ui->qSpinBox_year->setValue( tags[Meta::valYear].toInt() );
0475 
0476             if( tags.contains( Meta::valComposer ) )
0477             {
0478                 cur = ui->kComboBox_composer->currentIndex();
0479                 ui->kComboBox_composer->setItemText( cur, tags[Meta::valComposer] );
0480             }
0481 
0482             if( tags.contains( Meta::valGenre ) )
0483             {
0484                 cur = ui->kComboBox_genre->currentIndex();
0485                 ui->kComboBox_genre->setItemText( cur, tags[Meta::valGenre] );
0486             }
0487 
0488             if( tags.contains( Meta::valDiscNr ) )
0489             {
0490                 ui->qSpinBox_discNumber->setValue( tags[Meta::valDiscNr].toInt() );
0491             }
0492         }
0493         else
0494         {
0495             debug() << "guessing tags from filename failed" << Qt::endl;
0496         }
0497     }
0498 }
0499 
0500 ////////////////////////////////////////////////////////////////////////////////
0501 // PRIVATE
0502 ////////////////////////////////////////////////////////////////////////////////
0503 
0504 void TagDialog::initUi()
0505 {
0506     DEBUG_BLOCK
0507     // delete itself when closing
0508     setAttribute( Qt::WA_DeleteOnClose );
0509 
0510     KConfigGroup config = Amarok::config( "TagDialog" );
0511 
0512     ui->qTabWidget->addTab( ui->summaryTab   , i18n( "Summary" ) );
0513     ui->qTabWidget->addTab( ui->tagsTab      , i18n( "Tags" ) );
0514     ui->qTabWidget->addTab( ui->lyricsTab    , i18n( "Lyrics" ) );
0515     ui->qTabWidget->addTab( ui->labelsTab , i18n( "Labels" ) );
0516 
0517     ui->kComboBox_label->completionObject()->setIgnoreCase( true );
0518     ui->kComboBox_label->setCompletionMode( KCompletion::CompletionPopup );
0519 
0520     m_labelModel = new LabelListModel( QStringList(), this );
0521     ui->labelsList->setModel( m_labelModel );
0522     ui->labelsTab->setEnabled( true );
0523 
0524     ui->qTabWidget->setCurrentIndex( config.readEntry( "CurrentTab", 0 ) );
0525 
0526     ui->kComboBox_artist->completionObject()->setIgnoreCase( true );
0527     ui->kComboBox_artist->setCompletionMode( KCompletion::CompletionPopup );
0528 
0529     ui->kComboBox_album->completionObject()->setIgnoreCase( true );
0530     ui->kComboBox_album->setCompletionMode( KCompletion::CompletionPopup );
0531 
0532     ui->kComboBox_albumArtist->completionObject()->setIgnoreCase( true );
0533     ui->kComboBox_albumArtist->setCompletionMode( KCompletion::CompletionPopup );
0534 
0535     ui->kComboBox_composer->completionObject()->setIgnoreCase( true );
0536     ui->kComboBox_composer->setCompletionMode( KCompletion::CompletionPopup );
0537 
0538     ui->kComboBox_genre->completionObject()->setIgnoreCase( true );
0539     ui->kComboBox_genre->setCompletionMode( KCompletion::CompletionPopup );
0540 
0541     ui->kComboBox_label->completionObject()->setIgnoreCase( true );
0542     ui->kComboBox_label->setCompletionMode( KCompletion::CompletionPopup );
0543 
0544     ui->addButton->setEnabled( false );
0545     ui->removeButton->setEnabled( false );
0546 
0547     // set an icon for the open-in-konqui button
0548     ui->pushButton_open->setIcon( QIcon::fromTheme( QStringLiteral("folder-amarok") ) );
0549 
0550     connect( ui->pushButton_guessTags, &QAbstractButton::clicked, this, &TagDialog::guessFromFilename );
0551 
0552     // Connects for modification check
0553     // only set to overwrite-on-save if the text has changed
0554     connect( ui->kLineEdit_title,       &QLineEdit::textChanged, this, &TagDialog::checkChanged );
0555     connect( ui->kComboBox_composer,    QOverload<int>::of(&QComboBox::activated), this, &TagDialog::checkChanged );
0556     connect( ui->kComboBox_composer,    &QComboBox::editTextChanged, this, &TagDialog::checkChanged );
0557     connect( ui->kComboBox_artist,      QOverload<int>::of(&QComboBox::activated), this, &TagDialog::checkChanged );
0558     connect( ui->kComboBox_artist,      &QComboBox::editTextChanged, this, &TagDialog::checkChanged );
0559     connect( ui->kComboBox_album,       QOverload<int>::of(&QComboBox::activated), this, &TagDialog::checkChanged );
0560     connect( ui->kComboBox_album,       &QComboBox::editTextChanged, this, &TagDialog::checkChanged );
0561     connect( ui->kComboBox_albumArtist, QOverload<int>::of(&QComboBox::activated), this, &TagDialog::checkChanged );
0562     connect( ui->kComboBox_albumArtist, &QComboBox::editTextChanged, this, &TagDialog::checkChanged );
0563     connect( ui->kComboBox_genre,       QOverload<int>::of(&QComboBox::activated), this, &TagDialog::checkChanged );
0564     connect( ui->kComboBox_genre,       &QComboBox::editTextChanged, this, &TagDialog::checkChanged );
0565     connect( ui->kLineEdit_Bpm,         &QLineEdit::textChanged, this, &TagDialog::checkChanged );
0566     connect( ui->ratingWidget,          QOverload<int>::of(&KRatingWidget::ratingChanged), this, &TagDialog::checkChanged );
0567     connect( ui->qSpinBox_track,        QOverload<int>::of(&QSpinBox::valueChanged), this, &TagDialog::checkChanged );
0568     connect( ui->qSpinBox_year,         QOverload<int>::of(&QSpinBox::valueChanged), this, &TagDialog::checkChanged );
0569     connect( ui->qSpinBox_score,        QOverload<int>::of(&QSpinBox::valueChanged), this, &TagDialog::checkChanged );
0570     connect( ui->qPlainTextEdit_comment, &QPlainTextEdit::textChanged, this, &TagDialog::checkChanged );
0571     connect( ui->kRichTextEdit_lyrics,  &QTextEdit::textChanged, this, &TagDialog::checkChanged );
0572     connect( ui->qSpinBox_discNumber,   QOverload<int>::of(&QSpinBox::valueChanged), this, &TagDialog::checkChanged );
0573 
0574     connect( ui->pushButton_cancel,   &QAbstractButton::clicked, this, &TagDialog::cancelPressed );
0575     connect( ui->pushButton_ok,       &QAbstractButton::clicked, this, &TagDialog::accept );
0576     connect( ui->pushButton_open,     &QAbstractButton::clicked, this, &TagDialog::openPressed );
0577     connect( ui->pushButton_previous, &QAbstractButton::clicked, this, &TagDialog::previousTrack );
0578     connect( ui->pushButton_next,     &QAbstractButton::clicked, this, &TagDialog::nextTrack );
0579     connect( ui->checkBox_perTrack,   &QCheckBox::toggled, this, &TagDialog::perTrack );
0580 
0581     connect( ui->addButton,           &QAbstractButton::clicked, this, &TagDialog::addLabelPressed );
0582     connect( ui->removeButton,        &QAbstractButton::clicked, this, &TagDialog::removeLabelPressed );
0583     connect( ui->kComboBox_label,     QOverload<int>::of(&KComboBox::activated), this, &TagDialog::labelModified );
0584     connect( ui->kComboBox_label,     &QComboBox::editTextChanged, this, &TagDialog::labelModified );
0585     connect( ui->kComboBox_label,     QOverload<const QString&>::of(&KComboBox::returnPressed), this, &TagDialog::addLabelPressed );
0586     connect( ui->kComboBox_label,     QOverload<const QString&>::of(&KComboBox::returnPressed), this, &TagDialog::checkChanged );
0587     connect( ui->labelsList,          &QListView::pressed, this, &TagDialog::labelSelected );
0588 
0589     ui->pixmap_cover->setContextMenuPolicy( Qt::CustomContextMenu );
0590     connect( ui->pixmap_cover, &CoverLabel::customContextMenuRequested, this, &TagDialog::showCoverMenu );
0591 
0592     connect( ui->pushButton_musicbrainz, &QAbstractButton::clicked, this, &TagDialog::musicbrainzTagger );
0593 
0594     if( m_tracks.count() > 1 )
0595         setPerTrack( false );
0596     else
0597         setPerTrack( true );
0598 
0599     ui->pushButton_ok->setEnabled( false );
0600 
0601     startDataQueries();
0602 }
0603 
0604 void
0605 TagDialog::setCurrentTrack( int num )
0606 {
0607     if( num < 0 || num >= m_tracks.count() )
0608         return;
0609 
0610     if( m_currentTrack ) // even in multiple tracks mode we don't want to write back
0611         setTagsToTrack();
0612 
0613     // there is a logical problem here.
0614     // if the track itself changes (e.g. because it get's a new album)
0615     // then we don't re-subscribe
0616     if( m_currentAlbum )
0617         unsubscribeFrom( m_currentAlbum );
0618 
0619     m_currentTrack = m_tracks.at( num );
0620     m_currentAlbum = m_currentTrack->album();
0621     m_currentTrackNum = num;
0622 
0623     if( m_currentAlbum )
0624         subscribeTo( m_currentAlbum );
0625 
0626     setControlsAccessability();
0627     updateButtons();
0628     setTagsToUi();
0629 }
0630 
0631 void
0632 TagDialog::startDataQuery( Collections::QueryMaker::QueryType type, const QMetaMethod &signal,
0633                            const QMetaMethod &slot )
0634 {
0635     Collections::QueryMaker *qm = CollectionManager::instance()->queryMaker();
0636     qm->setQueryType( type );
0637 
0638     connect( qm, &Collections::QueryMaker::queryDone, this, &TagDialog::dataQueryDone, Qt::QueuedConnection );
0639     connect( qm, signal, this, slot, Qt::QueuedConnection );
0640 
0641     qm->setAutoDelete( true );
0642     qm->run();
0643 }
0644 
0645 void
0646 TagDialog::startDataQueries()
0647 {
0648     startDataQuery( Collections::QueryMaker::Artist,
0649                     QMetaMethod::fromSignal(&Collections::QueryMaker::newArtistsReady),
0650                     QMetaMethod::fromSignal(&TagDialog::artistsReady) );
0651     startDataQuery( Collections::QueryMaker::Album,
0652                     QMetaMethod::fromSignal(&Collections::QueryMaker::newAlbumsReady),
0653                     QMetaMethod::fromSignal(&TagDialog::albumsReady) );
0654     startDataQuery( Collections::QueryMaker::Composer,
0655                     QMetaMethod::fromSignal(&Collections::QueryMaker::newComposersReady),
0656                     QMetaMethod::fromSignal(&TagDialog::composersReady) );
0657     startDataQuery( Collections::QueryMaker::Genre,
0658                     QMetaMethod::fromSignal(&Collections::QueryMaker::newGenresReady),
0659                     QMetaMethod::fromSignal(&TagDialog::genresReady) );
0660     startDataQuery( Collections::QueryMaker::Label,
0661                     QMetaMethod::fromSignal(&Collections::QueryMaker::newLabelsReady),
0662                     QMetaMethod::fromSignal(&TagDialog::labelsReady) );
0663 }
0664 
0665 
0666 inline const QString
0667 TagDialog::unknownSafe( const QString &s ) const
0668 {
0669     return ( s.isNull() || s.isEmpty() || s == "?" || s == "-" )
0670            ? i18nc( "The value for this tag is not known", "Unknown" )
0671            : s;
0672 }
0673 
0674 inline const QString
0675 TagDialog::unknownSafe( int i ) const
0676 {
0677     return ( i == 0 )
0678            ? i18nc( "The value for this tag is not known", "Unknown" )
0679            : QString::number( i );
0680 }
0681 
0682 void
0683 TagDialog::showCoverMenu( const QPoint &pos )
0684 {
0685     if( !m_currentAlbum )
0686         return; // TODO: warning or something?
0687 
0688     QAction *displayCoverAction = new DisplayCoverAction( this, m_currentAlbum );
0689     QAction *unsetCoverAction   = new UnsetCoverAction( this, m_currentAlbum );
0690 
0691     if( !m_currentAlbum->hasImage() )
0692     {
0693         displayCoverAction->setEnabled( false );
0694         unsetCoverAction->setEnabled( false );
0695     }
0696 
0697     QMenu *menu = new QMenu( this );
0698     menu->addAction( displayCoverAction );
0699     menu->addAction( new FetchCoverAction( this, m_currentAlbum ) );
0700     menu->addAction( new SetCustomCoverAction( this, m_currentAlbum ) );
0701     menu->addAction( unsetCoverAction );
0702 
0703     menu->exec( ui->pixmap_cover->mapToGlobal(pos) );
0704     delete menu;
0705 }
0706 
0707 void
0708 TagDialog::setTagsToUi( const QVariantMap &tags )
0709 {
0710     bool oldChanged = m_changed;
0711 
0712     // -- the windows title
0713     if( m_perTrack )
0714     {
0715         setWindowTitle( i18n("Track Details: %1 by %2",
0716                                                            m_currentTrack->name(),  m_currentTrack->artist() ? m_currentTrack->artist()->name() : QString() ) );
0717 
0718     }
0719     else
0720     {
0721         setWindowTitle( i18ncp( "The amount of tracks being edited", "1 Track", "Information for %1 Tracks", m_tracks.count() ) );
0722 
0723     }
0724 
0725     // -- the title in the summary tab
0726 
0727     if( m_perTrack )
0728     {
0729         QString niceTitle;
0730 
0731         const QFontMetrics fnt =  ui->trackArtistAlbumLabel->fontMetrics();
0732         const int len = ui->trackArtistAlbumLabel->width();
0733         QString curTrackAlbName;
0734         QString curArtistName;
0735 
0736         QString curTrackName = fnt.elidedText( m_currentTrack->name().toHtmlEscaped(), Qt::ElideRight, len );
0737         QString curTrackPretName = fnt.elidedText( m_currentTrack->prettyName().toHtmlEscaped(), Qt::ElideRight, len );
0738 
0739         if( m_currentAlbum )
0740             curTrackAlbName = fnt.elidedText( m_currentAlbum->name().toHtmlEscaped(), Qt::ElideRight, len );
0741         if( m_currentTrack->artist() )
0742             curArtistName = fnt.elidedText( m_currentTrack->artist()->name().toHtmlEscaped(), Qt::ElideRight, len );
0743 
0744 
0745         if( m_currentAlbum && m_currentAlbum->name().isEmpty() )
0746         {
0747             if( !m_currentTrack->name().isEmpty() )
0748             {
0749                 if( !m_currentTrack->artist()->name().isEmpty() )
0750                     niceTitle = i18n( "<b>%1</b> by <b>%2</b>", curTrackName,  curArtistName );
0751                 else
0752                     niceTitle = i18n( "<b>%1</b>", curTrackName );
0753             }
0754             else
0755                 niceTitle = curTrackPretName;
0756         }
0757         else if( m_currentAlbum )
0758             niceTitle = i18n( "<b>%1</b> by <b>%2</b> on <b>%3</b>" , curTrackName, curArtistName, curTrackAlbName );
0759         else if( m_currentTrack->artist() )
0760             niceTitle = i18n( "<b>%1</b> by <b>%2</b>" , curTrackName, curArtistName );
0761         else
0762             niceTitle = i18n( "<b>%1</b>" , curTrackName );
0763 
0764         ui->trackArtistAlbumLabel->setText( niceTitle );
0765     }
0766     else
0767     {
0768         ui->trackArtistAlbumLabel->setText( i18np( "Editing 1 file", "Editing %1 files", m_tracks.count() ) );
0769 
0770     }
0771 
0772     // -- the rest
0773 
0774     ui->kLineEdit_title->setText( tags.value( Meta::Field::TITLE ).toString() );
0775     selectOrInsertText( tags.value( Meta::Field::ARTIST ).toString(), ui->kComboBox_artist );
0776     selectOrInsertText( tags.value( Meta::Field::ALBUM ).toString(), ui->kComboBox_album );
0777     selectOrInsertText( tags.value( Meta::Field::ALBUMARTIST ).toString(), ui->kComboBox_albumArtist );
0778     selectOrInsertText( tags.value( Meta::Field::COMPOSER ).toString(), ui->kComboBox_composer );
0779     ui->qPlainTextEdit_comment->setPlainText( tags.value( Meta::Field::COMMENT ).toString() );
0780     selectOrInsertText( tags.value( Meta::Field::GENRE ).toString(), ui->kComboBox_genre );
0781     ui->qSpinBox_track->setValue( tags.value( Meta::Field::TRACKNUMBER ).toInt() );
0782     ui->qSpinBox_discNumber->setValue( tags.value( Meta::Field::DISCNUMBER ).toInt() );
0783     ui->qSpinBox_year->setValue( tags.value( Meta::Field::YEAR ).toInt() );
0784     ui->kLineEdit_Bpm->setText( tags.value( Meta::Field::BPM ).toString() );
0785 
0786     ui->qLabel_length->setText( unknownSafe( Meta::msToPrettyTime( tags.value( Meta::Field::LENGTH ).toLongLong() ) ) );
0787     ui->qLabel_bitrate->setText( Meta::prettyBitrate( tags.value( Meta::Field::BITRATE ).toInt() ) );
0788     ui->qLabel_samplerate->setText( unknownSafe( tags.value( Meta::Field::SAMPLERATE ).toInt() ) );
0789     ui->qLabel_size->setText( Meta::prettyFilesize( tags.value( Meta::Field::FILESIZE ).toLongLong() ) );
0790     ui->qLabel_format->setText( unknownSafe( tags.value( Meta::Field::TYPE ).toString() ) );
0791 
0792     ui->qSpinBox_score->setValue( tags.value( Meta::Field::SCORE ).toInt() );
0793     ui->ratingWidget->setRating( tags.value( Meta::Field::RATING ).toInt() );
0794     ui->ratingWidget->setMaxRating( 10 );
0795     int playcount = tags.value( Meta::Field::PLAYCOUNT ).toInt();
0796     ui->qLabel_playcount->setText( unknownSafe( playcount ) );
0797 
0798     QDateTime firstPlayed = tags.value( Meta::Field::FIRST_PLAYED ).toDateTime();
0799     ui->qLabel_firstPlayed->setText( Amarok::verboseTimeSince( firstPlayed ) );
0800 
0801     QDateTime lastPlayed = tags.value( Meta::Field::LAST_PLAYED ).toDateTime();
0802     ui->qLabel_lastPlayed->setText( Amarok::verboseTimeSince( lastPlayed ) );
0803 
0804     ui->qLabel_collection->setText( tags.contains( Meta::Field::COLLECTION ) ?
0805                                     tags.value( Meta::Field::COLLECTION ).toString() :
0806                                     i18nc( "The collection this track is part of", "None") );
0807 
0808     // special handling - we want to hide this if empty
0809     if( tags.contains( Meta::Field::NOTE ) )
0810     {
0811         ui->noteLabel->show();
0812         ui->qLabel_note->setText( tags.value( Meta::Field::NOTE ).toString() );
0813         ui->qLabel_note->show();
0814     }
0815     else
0816     {
0817         ui->noteLabel->hide();
0818         ui->qLabel_note->hide();
0819     }
0820 
0821     ui->kRichTextEdit_lyrics->setTextOrHtml( tags.value( Meta::Field::LYRICS ).toString() );
0822 
0823     m_labelModel->setLabels( tags.value( Meta::Field::LABELS ).toStringList() );
0824     ui->labelsList->update();
0825 
0826     updateCover();
0827 
0828     setControlsAccessability();
0829 
0830     // If it's a local file, write the directory to m_path, else disable the "open in konqui" button
0831     QString urlString = tags.value( Meta::Field::URL ).toString();
0832     QUrl url = QUrl::fromUserInput( urlString );
0833     //QUrl::PreferLocalFile will give localpath or proper url for remote.
0834     ui->kLineEdit_location->setText( url.toDisplayString( QUrl::PreferLocalFile ) );
0835     if( url.isLocalFile() )
0836     {
0837         ui->locationLabel->show();
0838         ui->kLineEdit_location->show();
0839         QFileInfo fi( urlString );
0840         m_path = fi.isDir() ? urlString : url.adjusted(QUrl::RemoveFilename).path();
0841         ui->pushButton_open->setEnabled( true );
0842     }
0843     else
0844     {
0845         m_path.clear();
0846         ui->pushButton_open->setEnabled( false );
0847     }
0848 
0849     m_changed = oldChanged;
0850     ui->pushButton_ok->setEnabled( m_changed );
0851 }
0852 
0853 void
0854 TagDialog::setTagsToUi()
0855 {
0856     if( m_perTrack )
0857         setTagsToUi( m_storedTags.value( m_currentTrack ) );
0858     else
0859         setTagsToUi( getTagsFromMultipleTracks() );
0860 }
0861 
0862 
0863 QVariantMap
0864 TagDialog::getTagsFromUi( const QVariantMap &tags ) const
0865 {
0866     QVariantMap map;
0867 
0868     if( ui->kLineEdit_title->text() != tags.value( Meta::Field::TITLE ).toString() )
0869         map.insert( Meta::Field::TITLE, ui->kLineEdit_title->text() );
0870     if( ui->kComboBox_artist->currentText() != tags.value( Meta::Field::ARTIST ).toString() )
0871         map.insert( Meta::Field::ARTIST, ui->kComboBox_artist->currentText() );
0872     if( ui->kComboBox_album->currentText() != tags.value( Meta::Field::ALBUM ).toString() )
0873         map.insert( Meta::Field::ALBUM, ui->kComboBox_album->currentText() );
0874     if( ui->kComboBox_albumArtist->currentText() != tags.value( Meta::Field::ALBUMARTIST ).toString() )
0875         map.insert( Meta::Field::ALBUMARTIST, ui->kComboBox_albumArtist->currentText() );
0876     if( ui->kComboBox_composer->currentText() != tags.value( Meta::Field::COMPOSER ).toString() )
0877         map.insert( Meta::Field::COMPOSER, ui->kComboBox_composer->currentText() );
0878     if( ui->qPlainTextEdit_comment->toPlainText() != tags.value( Meta::Field::COMMENT ).toString() )
0879         map.insert( Meta::Field::COMMENT, ui->qPlainTextEdit_comment->toPlainText() );
0880     if( ui->kComboBox_genre->currentText() != tags.value( Meta::Field::GENRE ).toString() )
0881         map.insert( Meta::Field::GENRE, ui->kComboBox_genre->currentText() );
0882     if( ui->qSpinBox_track->value() != tags.value( Meta::Field::TRACKNUMBER ).toInt() )
0883         map.insert( Meta::Field::TRACKNUMBER, ui->qSpinBox_track->value() );
0884     if( ui->qSpinBox_discNumber->value() != tags.value( Meta::Field::DISCNUMBER ).toInt() )
0885         map.insert( Meta::Field::DISCNUMBER, ui->qSpinBox_discNumber->value() );
0886     if( ui->kLineEdit_Bpm->text().toDouble() != tags.value( Meta::Field::BPM ).toReal() )
0887         map.insert( Meta::Field::BPM, ui->kLineEdit_Bpm->text() );
0888     if( ui->qSpinBox_year->value() != tags.value( Meta::Field::YEAR ).toInt() )
0889         map.insert( Meta::Field::YEAR, ui->qSpinBox_year->value() );
0890 
0891     if( ui->qSpinBox_score->value() != tags.value( Meta::Field::SCORE ).toInt() )
0892         map.insert( Meta::Field::SCORE, ui->qSpinBox_score->value() );
0893 
0894     if( ui->ratingWidget->rating() != tags.value( Meta::Field::RATING ).toUInt() )
0895         map.insert( Meta::Field::RATING, ui->ratingWidget->rating() );
0896 
0897     if( !m_tracks.count() || m_perTrack )
0898     { //ignore these on MultipleTracksMode
0899         if ( ui->kRichTextEdit_lyrics->textOrHtml() != tags.value( Meta::Field::LYRICS ).toString() )
0900             map.insert( Meta::Field::LYRICS, ui->kRichTextEdit_lyrics->textOrHtml() );
0901     }
0902 
0903     QStringList uiLabelsList = m_labelModel->labels();
0904     QSet<QString> uiLabels(uiLabelsList.begin(), uiLabelsList.end());
0905     QStringList oldLabelsList = tags.value( Meta::Field::LABELS ).toStringList();
0906     QSet<QString> oldLabels(oldLabelsList.begin(), oldLabelsList.end());
0907     if( uiLabels != oldLabels )
0908         map.insert( Meta::Field::LABELS, QVariant( uiLabels.values() ) );
0909 
0910     return map;
0911 }
0912 
0913 QVariantMap
0914 TagDialog::getTagsFromTrack( const Meta::TrackPtr &track ) const
0915 {
0916     QVariantMap map;
0917     if( !track )
0918         return map;
0919 
0920     // get the shared pointers now to ensure that they don't get freed
0921     Meta::AlbumPtr album = track->album();
0922     Meta::ArtistPtr artist = track->artist();
0923     Meta::GenrePtr genre = track->genre();
0924     Meta::ComposerPtr composer = track->composer();
0925     Meta::YearPtr year = track->year();
0926 
0927     if( !track->name().isEmpty() )
0928         map.insert( Meta::Field::TITLE, track->name() );
0929     if( artist && !artist->name().isEmpty() )
0930         map.insert( Meta::Field::ARTIST, artist->name() );
0931     if( album && !track->album()->name().isEmpty() )
0932     {
0933         map.insert( Meta::Field::ALBUM, album->name() );
0934         if( album->hasAlbumArtist() && !album->albumArtist()->name().isEmpty() )
0935             map.insert( Meta::Field::ALBUMARTIST, album->albumArtist()->name() );
0936     }
0937     if( composer && !composer->name().isEmpty() )
0938         map.insert( Meta::Field::COMPOSER, composer->name() );
0939     if( !track->comment().isEmpty() )
0940         map.insert( Meta::Field::COMMENT, track->comment() );
0941     if( genre && !genre->name().isEmpty() )
0942         map.insert( Meta::Field::GENRE, genre->name() );
0943     if( track->trackNumber() )
0944         map.insert( Meta::Field::TRACKNUMBER, track->trackNumber() );
0945     if( track->discNumber() )
0946         map.insert( Meta::Field::DISCNUMBER, track->discNumber() );
0947     if( year && year->year() )
0948         map.insert( Meta::Field::YEAR, year->year() );
0949     if( track->bpm() > 0.0)
0950         map.insert( Meta::Field::BPM, track->bpm() );
0951     if( track->length() )
0952         map.insert( Meta::Field::LENGTH, track->length() );
0953     if( track->bitrate() )
0954         map.insert( Meta::Field::BITRATE, track->bitrate() );
0955     if( track->sampleRate() )
0956         map.insert( Meta::Field::SAMPLERATE, track->sampleRate() );
0957     if( track->filesize() )
0958         map.insert( Meta::Field::FILESIZE, track->filesize() );
0959 
0960     Meta::ConstStatisticsPtr statistics = track->statistics();
0961     map.insert( Meta::Field::SCORE, statistics->score() );
0962     map.insert( Meta::Field::RATING, statistics->rating() );
0963     map.insert( Meta::Field::PLAYCOUNT, statistics->playCount() );
0964     map.insert( Meta::Field::FIRST_PLAYED, statistics->firstPlayed() );
0965     map.insert( Meta::Field::LAST_PLAYED, statistics->lastPlayed() );
0966     map.insert( Meta::Field::URL, track->prettyUrl() );
0967 
0968     map.insert( Meta::Field::TYPE, track->type() );
0969 
0970     if( track->inCollection() )
0971         map.insert( Meta::Field::COLLECTION, track->collection()->prettyName() );
0972 
0973     if( !track->notPlayableReason().isEmpty() )
0974         map.insert( Meta::Field::NOTE, i18n( "The track is not playable. %1",
0975                                              track->notPlayableReason() ) );
0976 
0977     QStringList labelNames;
0978     foreach( const Meta::LabelPtr &label, track->labels() )
0979     {
0980         labelNames << label->name();
0981     }
0982     map.insert( Meta::Field::LABELS, labelNames );
0983 
0984     map.insert( Meta::Field::LYRICS, track->cachedLyrics() );
0985 
0986     return map;
0987 }
0988 
0989 QVariantMap
0990 TagDialog::getTagsFromMultipleTracks() const
0991 {
0992     QVariantMap map;
0993     if( m_tracks.isEmpty() )
0994         return map;
0995 
0996     //Check which fields are the same for all selected tracks
0997     QSet<QString> mismatchingTags;
0998 
0999     Meta::TrackPtr first = m_tracks.first();
1000     map = getTagsFromTrack( first );
1001 
1002     QString directory = first->playableUrl().adjusted(QUrl::RemoveFilename).path();
1003     int scoreCount = 0;
1004     double scoreSum = map.value( Meta::Field::SCORE ).toDouble();
1005     if( map.value( Meta::Field::SCORE ).toDouble() )
1006         scoreCount++;
1007 
1008     int ratingCount = 0;
1009     int ratingSum = map.value( Meta::Field::RATING ).toInt();
1010     if( map.value( Meta::Field::RATING ).toInt() )
1011         ratingCount++;
1012 
1013     QDateTime firstPlayed = first->statistics()->firstPlayed();
1014     QDateTime lastPlayed = first->statistics()->lastPlayed();
1015 
1016     qint64 length = first->length();
1017     qint64 size = first->filesize();
1018     QStringList validLabels = map.value( Meta::Field::LABELS ).toStringList();
1019 
1020     for( int i = 1; i < m_tracks.count(); i++ )
1021     {
1022         Meta::TrackPtr track = m_tracks[i];
1023         QVariantMap tags = m_storedTags.value( track );
1024 
1025         // -- figure out which tags do not match.
1026 
1027         // - occur not in every file
1028         QStringList mapkeys=map.keys();
1029         QStringList tagskeys=tags.keys();
1030         QSet<QString> mapKeysSet(mapkeys.begin(), mapkeys.end());
1031         QSet<QString> tagsKeysSet(tagskeys.begin(), tagskeys.end());
1032         mismatchingTags |= mapKeysSet - tagsKeysSet;
1033         mismatchingTags |= tagsKeysSet - mapKeysSet;
1034 
1035         // - not the same in every file
1036         foreach( const QString &key, (mapKeysSet & tagsKeysSet) )
1037         {
1038             if( map.value( key ) != tags.value( key ) )
1039                 mismatchingTags.insert( key );
1040         }
1041 
1042         // -- special handling for values
1043 
1044         // go up in the directories until we find a common one
1045         QString newDirectory = track->playableUrl().adjusted(QUrl::RemoveFilename).path();
1046 
1047         while( newDirectory != directory )
1048         {
1049             if( newDirectory.length() > directory.length() )
1050             {
1051                 QDir up( newDirectory ); up.cdUp();
1052                 QString d = up.path();
1053                 if( d == newDirectory ) // nothing changed
1054                 {
1055                     directory.clear();
1056                     break;
1057                 }
1058                 newDirectory = d;
1059             }
1060             else
1061             {
1062                 QDir up( directory ); up.cdUp();
1063                 QString d = up.path();
1064                 if( d == directory ) // nothing changed
1065                 {
1066                     directory.clear();
1067                     break;
1068                 }
1069                 directory = d;
1070             }
1071         }
1072         if( !track->playableUrl().isLocalFile() )
1073             directory.clear();
1074 
1075         // score and rating (unrated if rating == 0)
1076         scoreSum += tags.value( Meta::Field::SCORE ).toDouble();
1077         if( tags.value( Meta::Field::SCORE ).toDouble() )
1078             scoreCount++;
1079 
1080         ratingSum += tags.value( Meta::Field::RATING ).toInt();
1081         if( tags.value( Meta::Field::RATING ).toInt() )
1082             ratingCount++;
1083 
1084         Meta::StatisticsPtr statistics = track->statistics();
1085         if( statistics->firstPlayed().isValid() &&
1086             (!firstPlayed.isValid() || statistics->firstPlayed() < firstPlayed) )
1087             firstPlayed = statistics->firstPlayed();
1088 
1089         if( statistics->lastPlayed().isValid() &&
1090             (!lastPlayed.isValid() || statistics->lastPlayed() > lastPlayed) )
1091             lastPlayed = statistics->lastPlayed();
1092 
1093         length += track->length();
1094         size += track->filesize();
1095 
1096         // Only show labels present in all of the tracks
1097         QStringList labels = tags.value( Meta::Field::LABELS ).toStringList();
1098         for ( int x = 0; x < validLabels.count(); x++ )
1099         {
1100             if ( !labels.contains( validLabels.at( x ) ) )
1101                 validLabels.removeAt( x );
1102         }
1103 
1104     }
1105 
1106     foreach( const QString &key, mismatchingTags )
1107         map.remove( key );
1108 
1109     map.insert( Meta::Field::URL, directory );
1110     if( scoreCount > 0 )
1111         map.insert( Meta::Field::SCORE, scoreSum / scoreCount );
1112     if( ratingCount > 0 )
1113         // the extra fuzz is for emulating rounding to nearest integer
1114         map.insert( Meta::Field::RATING, ( ratingSum + ratingCount / 2 ) / ratingCount );
1115 
1116     map.insert( Meta::Field::FIRST_PLAYED, firstPlayed );
1117     map.insert( Meta::Field::LAST_PLAYED, lastPlayed );
1118 
1119     map.insert( Meta::Field::LENGTH, length );
1120     map.insert( Meta::Field::FILESIZE, size );
1121 
1122     map.insert( Meta::Field::LABELS, validLabels );
1123 
1124     return map;
1125 }
1126 
1127 void
1128 TagDialog::setTagsToTrack( const Meta::TrackPtr &track, const QVariantMap &tags )
1129 {
1130     foreach( const QString &key, tags.keys() )
1131     {
1132         m_storedTags[ track ].insert( key, tags.value( key ) );
1133     }
1134 }
1135 
1136 void
1137 TagDialog::setTagsToMultipleTracks( QVariantMap tags )
1138 {
1139     tags.remove( Meta::Field::LABELS );
1140 
1141     foreach( const Meta::TrackPtr &track, m_tracks )
1142     {
1143         setTagsToTrack( track, tags );
1144     }
1145 }
1146 
1147 void
1148 TagDialog::setTagsToTrack()
1149 {
1150     QVariantMap oldTags;
1151     if( m_perTrack )
1152         oldTags = m_storedTags.value( m_currentTrack );
1153     else
1154         oldTags = getTagsFromMultipleTracks();
1155     QVariantMap newTags = getTagsFromUi( oldTags );
1156 
1157     if( !newTags.isEmpty() )
1158     {
1159         m_changed = true;
1160         if( m_perTrack )
1161             setTagsToTrack( m_currentTrack, newTags );
1162         else
1163         {
1164             setTagsToMultipleTracks( newTags );
1165 
1166             // -- special handling for labels
1167             if( newTags.contains( Meta::Field::LABELS ) )
1168             {
1169                 // determine the differences
1170                 QStringList oldTagsList = oldTags.value( Meta::Field::LABELS ).toStringList();
1171                 QSet<QString> oldLabelsSet(oldTagsList.begin(), oldTagsList.end());
1172                 QStringList newTagsList = newTags.value( Meta::Field::LABELS ).toStringList();
1173                 QSet<QString> newLabelsSet(newTagsList.begin(), newTagsList.end());
1174 
1175                 QSet<QString> labelsToRemove = oldLabelsSet - newLabelsSet;
1176                 QSet<QString> labelsToAdd = newLabelsSet - oldLabelsSet;
1177 
1178                 // apply the differences for each track
1179                 foreach( const Meta::TrackPtr &track, m_tracks )
1180                 {
1181                     QStringList labelsList = m_storedTags[track].value( Meta::Field::LABELS ).toStringList();
1182                     QSet<QString> labelsSet(labelsList.begin(), labelsList.end());
1183                     labelsSet += labelsToAdd;
1184                     labelsSet -= labelsToRemove;
1185 
1186                     m_storedTags[ track ].insert( Meta::Field::LABELS, QVariant( labelsSet.values() ) );
1187                 }
1188             }
1189         }
1190     }
1191 }
1192 
1193 
1194 void
1195 TagDialog::setPerTrack( bool isEnabled )
1196 {
1197     debug() << "setPerTrack" << m_tracks.count() << isEnabled;
1198     if( m_tracks.count() < 2 )
1199         isEnabled = true;
1200 
1201     /* force an update so that we can use this function in the initialization
1202     if( m_perTrack == isEnabled )
1203         return;
1204     */
1205 
1206     m_perTrack = isEnabled;
1207 
1208     setControlsAccessability();
1209     updateButtons();
1210 }
1211 
1212 
1213 void
1214 TagDialog::updateButtons()
1215 {
1216     ui->pushButton_ok->setEnabled( m_changed );
1217 
1218     ui->checkBox_perTrack->setVisible( m_tracks.count() > 1 );
1219     ui->pushButton_previous->setVisible( m_tracks.count() > 1 );
1220     ui->pushButton_next->setVisible( m_tracks.count() > 1 );
1221 
1222     ui->checkBox_perTrack->setChecked( m_perTrack );
1223     ui->pushButton_previous->setEnabled( m_perTrack && m_currentTrackNum > 0 );
1224     ui->pushButton_next->setEnabled( m_perTrack && m_currentTrackNum < m_tracks.count()-1 );
1225 }
1226 
1227 void
1228 TagDialog::updateCover()
1229 {
1230     DEBUG_BLOCK
1231 
1232     if( !m_currentTrack )
1233         return;
1234 
1235     // -- get the album
1236     Meta::AlbumPtr album = m_currentAlbum;
1237     if( !m_perTrack )
1238     {
1239         foreach( Meta::TrackPtr track, m_tracks )
1240         {
1241             if( track->album() != album )
1242                 album = nullptr;
1243         }
1244     }
1245 
1246     // -- set the ui
1247     const int s = 100; // Image preview size
1248     ui->pixmap_cover->setMinimumSize( s, s );
1249     ui->pixmap_cover->setMaximumSize( s, s );
1250 
1251     if( !album )
1252     {
1253         ui->pixmap_cover->setVisible( false );
1254     }
1255     else
1256     {
1257         ui->pixmap_cover->setVisible( true );
1258         ui->pixmap_cover->setPixmap( The::svgHandler()->imageWithBorder( album, s ) );
1259         QString artist = m_currentTrack->artist() ? m_currentTrack->artist()->name() : QString();
1260         ui->pixmap_cover->setInformation( artist, album->name() );
1261     }
1262 }
1263 
1264 
1265 void
1266 TagDialog::setControlsAccessability()
1267 {
1268     bool editable = m_currentTrack ? bool( m_currentTrack->editor() ) : true;
1269 
1270     ui->qTabWidget->setTabEnabled( ui->qTabWidget->indexOf(ui->lyricsTab),
1271                                    m_perTrack );
1272 
1273     ui->kLineEdit_title->setEnabled( m_perTrack && editable );
1274     ui->kLineEdit_title->setClearButtonEnabled( m_perTrack && editable );
1275 
1276 #define enableOrDisable( X ) \
1277     ui->X->setEnabled( editable ); \
1278     qobject_cast<QLineEdit*>(ui->X->lineEdit())->setClearButtonEnabled( editable )
1279 
1280     enableOrDisable( kComboBox_artist );
1281     enableOrDisable( kComboBox_albumArtist );
1282     enableOrDisable( kComboBox_composer );
1283     enableOrDisable( kComboBox_album );
1284     enableOrDisable( kComboBox_genre );
1285 
1286 #undef enableOrDisable
1287 
1288     ui->qSpinBox_track->setEnabled( m_perTrack && editable );
1289     ui->qSpinBox_discNumber->setEnabled( editable );
1290     ui->qSpinBox_year->setEnabled( editable );
1291     ui->kLineEdit_Bpm->setEnabled( editable );
1292     ui->kLineEdit_Bpm->setClearButtonEnabled( editable );
1293 
1294     ui->qPlainTextEdit_comment->setEnabled( editable );
1295     ui->pushButton_guessTags->setEnabled( m_perTrack && editable );
1296     ui->pushButton_musicbrainz->setEnabled( editable );
1297 }
1298 
1299 void
1300 TagDialog::saveTags()
1301 {
1302     setTagsToTrack();
1303 
1304     for( auto &track : m_tracks )
1305     {
1306         QVariantMap data = m_storedTags[ track ];
1307         //there is really no need to write to the file if only info m_stored in the db has changed
1308         if( !data.isEmpty() )
1309         {
1310             debug() << "File info changed....";
1311 
1312             auto lambda = [=] () mutable
1313             {
1314                 if( data.contains( Meta::Field::SCORE ) )
1315                     track->statistics()->setScore( data.value( Meta::Field::SCORE ).toInt() );
1316                 if( data.contains( Meta::Field::RATING ) )
1317                     track->statistics()->setRating( data.value( Meta::Field::RATING ).toInt() );
1318                 if( data.contains( Meta::Field::LYRICS ) )
1319                     track->setCachedLyrics( data.value( Meta::Field::LYRICS ).toString() );
1320 
1321                 QStringList labels = data.value( Meta::Field::LABELS ).toStringList();
1322                 QHash<QString, Meta::LabelPtr> labelMap;
1323                 for( const auto &label : track->labels() )
1324                     labelMap.insert( label->name(), label );
1325 
1326                 // labels to remove
1327                 QStringList labelmapkeys=labelMap.keys();
1328                 QSet<QString> labelMapKeysSet(labelmapkeys.begin(), labelmapkeys.end());
1329                 QSet<QString> labelsSet(labels.begin(), labels.end());
1330                 for( const auto &label : labelMapKeysSet - labelsSet )
1331                     track->removeLabel( labelMap.value( label ) );
1332 
1333                 // labels to add
1334                 for( const auto &label : labelsSet - labelMapKeysSet )
1335                     track->addLabel( label );
1336 
1337                 Meta::TrackEditorPtr ec = track->editor();
1338                 if( !ec )
1339                 {
1340                     debug() << "Track" << track->prettyUrl() << "does not have Meta::TrackEditor. Skipping.";
1341                     return;
1342                 }
1343 
1344                 ec->beginUpdate();
1345 
1346                 if( data.contains( Meta::Field::TITLE ) )
1347                     ec->setTitle( data.value( Meta::Field::TITLE ).toString() );
1348                 if( data.contains( Meta::Field::COMMENT ) )
1349                     ec->setComment( data.value( Meta::Field::COMMENT ).toString() );
1350                 if( data.contains( Meta::Field::ARTIST ) )
1351                     ec->setArtist( data.value( Meta::Field::ARTIST ).toString() );
1352                 if( data.contains( Meta::Field::ALBUM ) )
1353                     ec->setAlbum( data.value( Meta::Field::ALBUM ).toString() );
1354                 if( data.contains( Meta::Field::GENRE ) )
1355                     ec->setGenre( data.value( Meta::Field::GENRE ).toString() );
1356                 if( data.contains( Meta::Field::COMPOSER ) )
1357                     ec->setComposer( data.value( Meta::Field::COMPOSER ).toString() );
1358                 if( data.contains( Meta::Field::YEAR ) )
1359                     ec->setYear( data.value( Meta::Field::YEAR ).toInt() );
1360                 if( data.contains( Meta::Field::TRACKNUMBER ) )
1361                     ec->setTrackNumber( data.value( Meta::Field::TRACKNUMBER ).toInt() );
1362                 if( data.contains( Meta::Field::DISCNUMBER ) )
1363                     ec->setDiscNumber( data.value( Meta::Field::DISCNUMBER ).toInt() );
1364                 if( data.contains( Meta::Field::BPM ) )
1365                     ec->setBpm( data.value( Meta::Field::BPM ).toDouble() );
1366                 if( data.contains( Meta::Field::ALBUMARTIST ) )
1367                     ec->setAlbumArtist( data.value( Meta::Field::ALBUMARTIST ).toString() );
1368 
1369                 ec->endUpdate();
1370                 // note: the track should by itself Q_EMIT a collectionUpdated signal if needed
1371             };
1372             std::thread thread( lambda );
1373             thread.detach();
1374         }
1375     }
1376 }
1377 
1378 void
1379 TagDialog::selectOrInsertText( const QString &text, QComboBox *comboBox )
1380 {
1381     int index = comboBox->findText( text );
1382     if( index == -1 )
1383     {
1384         comboBox->insertItem( 0, text );    //insert at the beginning
1385         comboBox->setCurrentIndex( 0 );
1386     }
1387     else
1388     {
1389         comboBox->setCurrentIndex( index );
1390     }
1391 }
1392 
1393 void
1394 TagDialog::musicbrainzTagger()
1395 {
1396     DEBUG_BLOCK
1397 
1398     MusicBrainzTagger *dialog = new MusicBrainzTagger( m_tracks, this );
1399     dialog->setWindowTitle( i18n( "MusicBrainz Tagger" ) );
1400     connect( dialog, &MusicBrainzTagger::sendResult,
1401              this, &TagDialog::musicbrainzTaggerResult );
1402     dialog->show();
1403 }
1404 
1405 void
1406 TagDialog::musicbrainzTaggerResult( const QMap<Meta::TrackPtr, QVariantMap> &result )
1407 {
1408     if( result.isEmpty() )
1409         return;
1410 
1411     foreach( Meta::TrackPtr track, result.keys() )
1412     {
1413         setTagsToTrack( track, result.value( track ) );
1414     }
1415     m_changed = true;
1416 
1417     if( m_perTrack )
1418         setTagsToUi( m_storedTags.value( m_currentTrack ) );
1419     else
1420         setTagsToUi( getTagsFromMultipleTracks() );
1421 }