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 }