1 #include "library/dlgtrackinfo.h"
2 
3 #include <QDesktopServices>
4 #include <QStringBuilder>
5 #include <QtDebug>
6 
7 #include "library/coverartcache.h"
8 #include "library/coverartutils.h"
9 #include "library/dlgtagfetcher.h"
10 #include "library/trackmodel.h"
11 #include "moc_dlgtrackinfo.cpp"
12 #include "preferences/colorpalettesettings.h"
13 #include "sources/soundsourceproxy.h"
14 #include "track/beatfactory.h"
15 #include "track/beatutils.h"
16 #include "track/keyfactory.h"
17 #include "track/keyutils.h"
18 #include "track/track.h"
19 #include "util/color/colorpalette.h"
20 #include "util/compatibility.h"
21 #include "util/datetime.h"
22 #include "util/desktophelper.h"
23 #include "util/duration.h"
24 #include "widget/wcoverartlabel.h"
25 #include "widget/wstarrating.h"
26 
27 namespace {
28 
29 constexpr double kBpmTabRounding = 1 / 12.0;
30 constexpr int kFilterLength = 80;
31 constexpr int kMinBpm = 30;
32 // Maximum allowed interval between beats (calculated from kMinBpm).
33 const mixxx::Duration kMaxInterval = mixxx::Duration::fromMillis(
34         static_cast<qint64>(1000.0 * (60.0 / kMinBpm)));
35 
36 } // namespace
37 
DlgTrackInfo(const TrackModel * trackModel)38 DlgTrackInfo::DlgTrackInfo(
39         const TrackModel* trackModel)
40         // No parent because otherwise it inherits the style parent's
41         // style which can make it unreadable. Bug #673411
42         : QDialog(nullptr),
43           m_pTrackModel(trackModel),
44           m_tapFilter(this, kFilterLength, kMaxInterval),
45           m_dLastTapedBpm(-1.),
46           m_pWCoverArtLabel(make_parented<WCoverArtLabel>(this)),
47           m_pWStarRating(make_parented<WStarRating>(nullptr, this)) {
48     init();
49 }
50 
~DlgTrackInfo()51 DlgTrackInfo::~DlgTrackInfo() {
52     unloadTrack(false);
53 }
54 
init()55 void DlgTrackInfo::init() {
56     setupUi(this);
57 
58     coverLayout->setAlignment(Qt::AlignRight | Qt::AlignTop);
59     coverLayout->setSpacing(0);
60     coverLayout->setContentsMargins(0, 0, 0, 0);
61     coverLayout->insertWidget(0, m_pWCoverArtLabel.get());
62 
63     starsLayout->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
64     starsLayout->setSpacing(0);
65     starsLayout->setContentsMargins(0, 0, 0, 0);
66     starsLayout->insertWidget(0, m_pWStarRating.get());
67     // This is necessary to pass on mouseMove events to WStarRating
68     m_pWStarRating->setMouseTracking(true);
69 
70     if (m_pTrackModel) {
71         connect(btnNext,
72                 &QPushButton::clicked,
73                 this,
74                 &DlgTrackInfo::slotNextButton);
75         connect(btnPrev,
76                 &QPushButton::clicked,
77                 this,
78                 &DlgTrackInfo::slotPrevButton);
79     } else {
80         btnNext->hide();
81         btnPrev->hide();
82     }
83 
84     connect(btnApply,
85             &QPushButton::clicked,
86             this,
87             &DlgTrackInfo::slotApply);
88 
89     connect(btnOK,
90             &QPushButton::clicked,
91             this,
92             &DlgTrackInfo::slotOk);
93 
94     connect(btnCancel,
95             &QPushButton::clicked,
96             this,
97             &DlgTrackInfo::slotCancel);
98 
99     connect(bpmDouble,
100             &QPushButton::clicked,
101             this,
102             &DlgTrackInfo::slotBpmDouble);
103     connect(bpmHalve,
104             &QPushButton::clicked,
105             this,
106             &DlgTrackInfo::slotBpmHalve);
107     connect(bpmTwoThirds,
108             &QPushButton::clicked,
109             this,
110             &DlgTrackInfo::slotBpmTwoThirds);
111     connect(bpmThreeFourth,
112             &QPushButton::clicked,
113             this,
114             &DlgTrackInfo::slotBpmThreeFourth);
115     connect(bpmFourThirds,
116             &QPushButton::clicked,
117             this,
118             &DlgTrackInfo::slotBpmFourThirds);
119     connect(bpmThreeHalves,
120             &QPushButton::clicked,
121             this,
122             &DlgTrackInfo::slotBpmThreeHalves);
123     connect(bpmClear,
124             &QPushButton::clicked,
125             this,
126             &DlgTrackInfo::slotBpmClear);
127 
128     connect(bpmConst,
129             &QCheckBox::stateChanged,
130             this,
131             &DlgTrackInfo::slotBpmConstChanged);
132 
133     connect(spinBpm,
134             QOverload<double>::of(&QDoubleSpinBox::valueChanged),
135             this,
136             &DlgTrackInfo::slotSpinBpmValueChanged);
137 
138     connect(txtKey,
139             &QLineEdit::editingFinished,
140             this,
141             &DlgTrackInfo::slotKeyTextChanged);
142 
143     connect(bpmTap,
144             &QPushButton::pressed,
145             &m_tapFilter,
146             &TapFilter::tap);
147     connect(&m_tapFilter,
148             &TapFilter::tapped,
149             this,
150             &DlgTrackInfo::slotBpmTap);
151 
152     connect(btnImportMetadataFromFile,
153             &QPushButton::clicked,
154             this,
155             &DlgTrackInfo::slotImportMetadataFromFile);
156 
157     connect(btnImportMetadataFromMusicBrainz,
158             &QPushButton::clicked,
159             this,
160             &DlgTrackInfo::slotImportMetadataFromMusicBrainz);
161 
162     connect(btnOpenFileBrowser,
163             &QPushButton::clicked,
164             this,
165             &DlgTrackInfo::slotOpenInFileBrowser);
166 
167     CoverArtCache* pCache = CoverArtCache::instance();
168     if (pCache) {
169         connect(pCache,
170                 &CoverArtCache::coverFound,
171                 this,
172                 &DlgTrackInfo::slotCoverFound);
173     }
174     connect(m_pWCoverArtLabel.get(),
175             &WCoverArtLabel::coverInfoSelected,
176             this,
177             &DlgTrackInfo::slotCoverInfoSelected);
178     connect(m_pWCoverArtLabel.get(),
179             &WCoverArtLabel::reloadCoverArt,
180             this,
181             &DlgTrackInfo::slotReloadCoverArt);
182 }
183 
slotOk()184 void DlgTrackInfo::slotOk() {
185     unloadTrack(true);
186     accept();
187 }
188 
slotApply()189 void DlgTrackInfo::slotApply() {
190     saveTrack();
191 }
192 
slotCancel()193 void DlgTrackInfo::slotCancel() {
194     unloadTrack(false);
195     reject();
196 }
197 
trackUpdated()198 void DlgTrackInfo::trackUpdated() {
199 }
200 
slotNextButton()201 void DlgTrackInfo::slotNextButton() {
202     loadNextTrack();
203 }
204 
slotPrevButton()205 void DlgTrackInfo::slotPrevButton() {
206     loadPrevTrack();
207 }
208 
slotNextDlgTagFetcher()209 void DlgTrackInfo::slotNextDlgTagFetcher() {
210     loadNextTrack();
211     // Do not load track back into DlgTagFetcher since
212     // it will cause a reload of the same track.
213 }
214 
slotPrevDlgTagFetcher()215 void DlgTrackInfo::slotPrevDlgTagFetcher() {
216     loadPrevTrack();
217 }
218 
loadNextTrack()219 void DlgTrackInfo::loadNextTrack() {
220     auto nextRow = m_currentTrackIndex.sibling(
221             m_currentTrackIndex.row() + 1, m_currentTrackIndex.column());
222     if (nextRow.isValid()) {
223         loadTrack(nextRow);
224         emit next();
225     }
226 }
227 
loadPrevTrack()228 void DlgTrackInfo::loadPrevTrack() {
229     QModelIndex prevRow = m_currentTrackIndex.sibling(
230             m_currentTrackIndex.row() - 1, m_currentTrackIndex.column());
231     if (prevRow.isValid()) {
232         loadTrack(prevRow);
233         emit previous();
234     }
235 }
236 
populateFields(const Track & track)237 void DlgTrackInfo::populateFields(const Track& track) {
238     setWindowTitle(track.getInfo());
239 
240     // Editable fields
241     txtTrackName->setText(track.getTitle());
242     txtArtist->setText(track.getArtist());
243     txtAlbum->setText(track.getAlbum());
244     txtAlbumArtist->setText(track.getAlbumArtist());
245     txtGenre->setText(track.getGenre());
246     txtComposer->setText(track.getComposer());
247     txtGrouping->setText(track.getGrouping());
248     txtYear->setText(track.getYear());
249     txtTrackNumber->setText(track.getTrackNumber());
250     txtComment->setPlainText(track.getComment());
251 
252     // Non-editable fields
253     txtDuration->setText(track.getDurationText(mixxx::Duration::Precision::SECONDS));
254     txtDateAdded->setText(mixxx::displayLocalDateTime(track.getDateAdded()));
255     txtLocation->setText(QDir::toNativeSeparators(track.getLocation()));
256     txtType->setText(track.getType());
257     txtBitrate->setText(
258             track.getBitrateText() +
259             QChar(' ') +
260             tr(mixxx::audio::Bitrate::unit()));
261     txtBpm->setText(track.getBpmText());
262     m_keysClone = track.getKeys();
263     txtKey->setText(KeyUtils::getGlobalKeyText(m_keysClone));
264     const mixxx::ReplayGain replayGain(track.getReplayGain());
265     txtReplayGain->setText(mixxx::ReplayGain::ratioToString(replayGain.getRatio()));
266 
267     reloadTrackBeats(track);
268 
269     m_loadedCoverInfo = track.getCoverInfoWithLocation();
270     m_pWCoverArtLabel->setCoverArt(m_loadedCoverInfo, QPixmap());
271     CoverArtCache::requestCover(this, m_loadedCoverInfo);
272     m_pWStarRating->slotTrackLoaded(m_pLoadedTrack);
273 }
274 
reloadTrackBeats(const Track & track)275 void DlgTrackInfo::reloadTrackBeats(const Track& track) {
276     m_pBeatsClone = track.getBeats();
277     if (m_pBeatsClone) {
278         spinBpm->setValue(m_pBeatsClone->getBpm());
279     } else {
280         spinBpm->setValue(0.0);
281     }
282     m_trackHasBeatMap = m_pBeatsClone &&
283             !(m_pBeatsClone->getCapabilities() & mixxx::Beats::BEATSCAP_SETBPM);
284     bpmConst->setChecked(!m_trackHasBeatMap);
285     bpmConst->setEnabled(m_trackHasBeatMap); // We cannot make turn a BeatGrid to a BeatMap
286     spinBpm->setEnabled(!m_trackHasBeatMap); // We cannot change bpm continuously or tab them
287     bpmTap->setEnabled(!m_trackHasBeatMap);  // when we have a beatmap
288 
289     if (track.isBpmLocked()) {
290         tabBPM->setEnabled(false);
291     } else {
292         tabBPM->setEnabled(true);
293     }
294 }
295 
loadTrackInternal(const TrackPointer & pTrack)296 void DlgTrackInfo::loadTrackInternal(const TrackPointer& pTrack) {
297     clear();
298 
299     if (!pTrack) {
300         return;
301     }
302 
303     m_pLoadedTrack = pTrack;
304 
305     populateFields(*m_pLoadedTrack);
306     m_pWCoverArtLabel->loadTrack(m_pLoadedTrack);
307 
308     // We already listen to changed() so we don't need to listen to individual
309     // signals such as cuesUpdates, coverArtUpdated(), etc.
310     connect(pTrack.get(),
311             &Track::changed,
312             this,
313             &DlgTrackInfo::slotTrackChanged);
314 }
315 
loadTrack(TrackPointer pTrack)316 void DlgTrackInfo::loadTrack(TrackPointer pTrack) {
317     VERIFY_OR_DEBUG_ASSERT(!m_pTrackModel) {
318         return;
319     }
320     loadTrackInternal(pTrack);
321     if (m_pDlgTagFetcher && m_pLoadedTrack) {
322         m_pDlgTagFetcher->loadTrack(m_pLoadedTrack);
323     }
324 }
325 
loadTrack(const QModelIndex & index)326 void DlgTrackInfo::loadTrack(const QModelIndex& index) {
327     VERIFY_OR_DEBUG_ASSERT(m_pTrackModel) {
328         return;
329     }
330     TrackPointer pTrack = m_pTrackModel->getTrack(index);
331     m_currentTrackIndex = index;
332     loadTrackInternal(pTrack);
333     if (m_pDlgTagFetcher && m_currentTrackIndex.isValid()) {
334         m_pDlgTagFetcher->loadTrack(m_currentTrackIndex);
335     }
336 }
337 
slotCoverFound(const QObject * pRequestor,const CoverInfo & coverInfo,const QPixmap & pixmap,quint16 requestedHash,bool coverInfoUpdated)338 void DlgTrackInfo::slotCoverFound(
339         const QObject* pRequestor,
340         const CoverInfo& coverInfo,
341         const QPixmap& pixmap,
342         quint16 requestedHash,
343         bool coverInfoUpdated) {
344     Q_UNUSED(requestedHash);
345     Q_UNUSED(coverInfoUpdated);
346     if (pRequestor == this &&
347             m_pLoadedTrack &&
348             m_loadedCoverInfo.trackLocation == coverInfo.trackLocation) {
349         m_loadedCoverInfo = coverInfo;
350         m_pWCoverArtLabel->setCoverArt(coverInfo, pixmap);
351     }
352 }
353 
slotReloadCoverArt()354 void DlgTrackInfo::slotReloadCoverArt() {
355     VERIFY_OR_DEBUG_ASSERT(m_pLoadedTrack) {
356         return;
357     }
358     slotCoverInfoSelected(
359             CoverInfoGuesser().guessCoverInfoForTrack(
360                     *m_pLoadedTrack));
361 }
362 
slotCoverInfoSelected(const CoverInfoRelative & coverInfo)363 void DlgTrackInfo::slotCoverInfoSelected(const CoverInfoRelative& coverInfo) {
364     qDebug() << "DlgTrackInfo::slotCoverInfoSelected" << coverInfo;
365     VERIFY_OR_DEBUG_ASSERT(m_pLoadedTrack) {
366         return;
367     }
368     m_loadedCoverInfo = CoverInfo(coverInfo, m_pLoadedTrack->getLocation());
369     CoverArtCache::requestCover(this, m_loadedCoverInfo);
370 }
371 
slotOpenInFileBrowser()372 void DlgTrackInfo::slotOpenInFileBrowser() {
373     if (!m_pLoadedTrack) {
374         return;
375     }
376 
377     mixxx::DesktopHelper::openInFileBrowser(QStringList(m_pLoadedTrack->getLocation()));
378 }
379 
saveTrack()380 void DlgTrackInfo::saveTrack() {
381     if (!m_pLoadedTrack) {
382         return;
383     }
384 
385     // First, disconnect the track changed signal. Otherwise we signal ourselves
386     // and repopulate all these fields.
387     disconnect(m_pLoadedTrack.get(),
388             &Track::changed,
389             this,
390             &DlgTrackInfo::slotTrackChanged);
391 
392     m_pLoadedTrack->setTitle(txtTrackName->text());
393     m_pLoadedTrack->setArtist(txtArtist->text());
394     m_pLoadedTrack->setAlbum(txtAlbum->text());
395     m_pLoadedTrack->setAlbumArtist(txtAlbumArtist->text());
396     m_pLoadedTrack->setGenre(txtGenre->text());
397     m_pLoadedTrack->setComposer(txtComposer->text());
398     m_pLoadedTrack->setGrouping(txtGrouping->text());
399     m_pLoadedTrack->setYear(txtYear->text());
400     m_pLoadedTrack->setTrackNumber(txtTrackNumber->text());
401     m_pLoadedTrack->setComment(txtComment->toPlainText());
402 
403     m_pLoadedTrack->trySetBeats(m_pBeatsClone);
404     reloadTrackBeats(*m_pLoadedTrack);
405 
406     // If the user is editing the key and hits enter to close DlgTrackInfo, the
407     // editingFinished signal will not fire in time. Run the key text changed
408     // handler now to see if the key was edited. If the key was unchanged or
409     // invalid then the change will be rejected.
410     slotKeyTextChanged();
411 
412     m_pLoadedTrack->setKeys(m_keysClone);
413     m_pLoadedTrack->setCoverInfo(m_loadedCoverInfo);
414 
415     // Reconnect changed signals now.
416     connect(m_pLoadedTrack.get(),
417             &Track::changed,
418             this,
419             &DlgTrackInfo::slotTrackChanged);
420 }
421 
unloadTrack(bool save)422 void DlgTrackInfo::unloadTrack(bool save) {
423     if (!m_pLoadedTrack) {
424         return;
425     }
426 
427     if (save) {
428         saveTrack();
429     }
430 
431     clear();
432 }
433 
clear()434 void DlgTrackInfo::clear() {
435     if (m_pLoadedTrack) {
436         disconnect(m_pLoadedTrack.get(),
437                 &Track::changed,
438                 this,
439                 &DlgTrackInfo::slotTrackChanged);
440         m_pLoadedTrack.reset();
441     }
442 
443     txtTrackName->setText("");
444     txtArtist->setText("");
445     txtAlbum->setText("");
446     txtAlbumArtist->setText("");
447     txtGenre->setText("");
448     txtComposer->setText("");
449     txtGrouping->setText("");
450     txtYear->setText("");
451     txtTrackNumber->setText("");
452     txtComment->setPlainText("");
453     spinBpm->setValue(0.0);
454     m_pBeatsClone.clear();
455     m_keysClone = Keys();
456 
457     txtDuration->setText("");
458     txtType->setText("");
459     txtLocation->setText("");
460     txtBitrate->setText("");
461     txtBpm->setText("");
462     txtKey->setText("");
463     txtReplayGain->setText("");
464 
465     m_loadedCoverInfo = CoverInfo();
466     m_pWCoverArtLabel->setCoverArt(m_loadedCoverInfo, QPixmap());
467 }
468 
slotBpmDouble()469 void DlgTrackInfo::slotBpmDouble() {
470     m_pBeatsClone = m_pBeatsClone->scale(mixxx::Beats::DOUBLE);
471     // read back the actual value
472     double newValue = m_pBeatsClone->getBpm();
473     spinBpm->setValue(newValue);
474 }
475 
slotBpmHalve()476 void DlgTrackInfo::slotBpmHalve() {
477     m_pBeatsClone = m_pBeatsClone->scale(mixxx::Beats::HALVE);
478     // read back the actual value
479     double newValue = m_pBeatsClone->getBpm();
480     spinBpm->setValue(newValue);
481 }
482 
slotBpmTwoThirds()483 void DlgTrackInfo::slotBpmTwoThirds() {
484     m_pBeatsClone = m_pBeatsClone->scale(mixxx::Beats::TWOTHIRDS);
485     // read back the actual value
486     double newValue = m_pBeatsClone->getBpm();
487     spinBpm->setValue(newValue);
488 }
489 
slotBpmThreeFourth()490 void DlgTrackInfo::slotBpmThreeFourth() {
491     m_pBeatsClone = m_pBeatsClone->scale(mixxx::Beats::THREEFOURTHS);
492     // read back the actual value
493     double newValue = m_pBeatsClone->getBpm();
494     spinBpm->setValue(newValue);
495 }
496 
slotBpmFourThirds()497 void DlgTrackInfo::slotBpmFourThirds() {
498     m_pBeatsClone = m_pBeatsClone->scale(mixxx::Beats::FOURTHIRDS);
499     // read back the actual value
500     double newValue = m_pBeatsClone->getBpm();
501     spinBpm->setValue(newValue);
502 }
503 
slotBpmThreeHalves()504 void DlgTrackInfo::slotBpmThreeHalves() {
505     m_pBeatsClone = m_pBeatsClone->scale(mixxx::Beats::THREEHALVES);
506     // read back the actual value
507     double newValue = m_pBeatsClone->getBpm();
508     spinBpm->setValue(newValue);
509 }
510 
slotBpmClear()511 void DlgTrackInfo::slotBpmClear() {
512     spinBpm->setValue(0);
513     m_pBeatsClone.clear();
514 
515     bpmConst->setChecked(true);
516     bpmConst->setEnabled(m_trackHasBeatMap);
517     spinBpm->setEnabled(true);
518     bpmTap->setEnabled(true);
519 }
520 
slotBpmConstChanged(int state)521 void DlgTrackInfo::slotBpmConstChanged(int state) {
522     if (state != Qt::Unchecked) {
523         // const beatgrid requested
524         if (spinBpm->value() > 0) {
525             // Since the user is not satisfied with the beat map,
526             // it is hard to predict a fitting beat. We know that we
527             // cannot use the first beat, since it is out of sync in
528             // almost all cases.
529             // The cue point should be set on a beat, so this seems
530             // to be a good alternative
531             CuePosition cue = m_pLoadedTrack->getCuePoint();
532             m_pBeatsClone = BeatFactory::makeBeatGrid(
533                     m_pLoadedTrack->getSampleRate(), spinBpm->value(), cue.getPosition());
534         } else {
535             m_pBeatsClone.clear();
536         }
537         spinBpm->setEnabled(true);
538         bpmTap->setEnabled(true);
539     } else {
540         // try to reload BeatMap from the Track
541         reloadTrackBeats(*m_pLoadedTrack);
542     }
543 }
544 
slotBpmTap(double averageLength,int numSamples)545 void DlgTrackInfo::slotBpmTap(double averageLength, int numSamples) {
546     Q_UNUSED(numSamples);
547     if (averageLength == 0) {
548         return;
549     }
550     double averageBpm = 60.0 * 1000.0 / averageLength;
551     averageBpm = BeatUtils::roundBpmWithinRange(averageBpm - kBpmTabRounding,
552             averageBpm,
553             averageBpm + kBpmTabRounding);
554     if (averageBpm != m_dLastTapedBpm) {
555         m_dLastTapedBpm = averageBpm;
556         spinBpm->setValue(averageBpm);
557     }
558 }
559 
slotSpinBpmValueChanged(double value)560 void DlgTrackInfo::slotSpinBpmValueChanged(double value) {
561     if (value <= 0) {
562         m_pBeatsClone.clear();
563         return;
564     }
565 
566     if (!m_pBeatsClone) {
567         CuePosition cue = m_pLoadedTrack->getCuePoint();
568         m_pBeatsClone = BeatFactory::makeBeatGrid(
569                 m_pLoadedTrack->getSampleRate(), value, cue.getPosition());
570     }
571 
572     double oldValue = m_pBeatsClone->getBpm();
573     if (oldValue == value) {
574         return;
575     }
576 
577     if (m_pBeatsClone->getCapabilities() & mixxx::Beats::BEATSCAP_SETBPM) {
578         m_pBeatsClone = m_pBeatsClone->setBpm(value);
579     }
580 
581     // read back the actual value
582     double newValue = m_pBeatsClone->getBpm();
583     spinBpm->setValue(newValue);
584 }
585 
slotKeyTextChanged()586 void DlgTrackInfo::slotKeyTextChanged() {
587     // Try to parse the user's input as a key.
588     const QString newKeyText = txtKey->text();
589     Keys newKeys = KeyFactory::makeBasicKeysFromText(newKeyText,
590             mixxx::track::io::key::USER);
591     const mixxx::track::io::key::ChromaticKey globalKey(newKeys.getGlobalKey());
592 
593     // If the new key string is invalid and not empty them reject the new key.
594     if (globalKey == mixxx::track::io::key::INVALID && !newKeyText.isEmpty()) {
595         txtKey->setText(KeyUtils::getGlobalKeyText(m_keysClone));
596         return;
597     }
598 
599     // If the new key is the same as the old key, reject the change.
600     if (globalKey == m_keysClone.getGlobalKey()) {
601         return;
602     }
603 
604     // Otherwise, accept.
605     m_keysClone = newKeys;
606 }
607 
slotImportMetadataFromFile()608 void DlgTrackInfo::slotImportMetadataFromFile() {
609     if (m_pLoadedTrack) {
610         // Allocate a temporary track object for reading the metadata.
611         // We cannot reuse m_pLoadedTrack, because it might already been
612         // modified and we want to read fresh metadata directly from the
613         // file. Otherwise the changes in m_pLoadedTrack would be lost.
614         TrackPointer pTrack = SoundSourceProxy::importTemporaryTrack(
615                 m_pLoadedTrack->getFileInfo(),
616                 m_pLoadedTrack->getSecurityToken());
617         DEBUG_ASSERT(pTrack);
618         populateFields(*pTrack);
619     }
620 }
621 
slotTrackChanged(TrackId trackId)622 void DlgTrackInfo::slotTrackChanged(TrackId trackId) {
623     if (m_pLoadedTrack && m_pLoadedTrack->getId() == trackId) {
624         populateFields(*m_pLoadedTrack);
625     }
626 }
627 
slotImportMetadataFromMusicBrainz()628 void DlgTrackInfo::slotImportMetadataFromMusicBrainz() {
629     if (!m_pDlgTagFetcher) {
630         m_pDlgTagFetcher = std::make_unique<DlgTagFetcher>(
631                 m_pTrackModel);
632         connect(m_pDlgTagFetcher.get(),
633                 &QDialog::finished,
634                 [this]() {
635                     if (m_pDlgTagFetcher.get() == sender()) {
636                         m_pDlgTagFetcher.release()->deleteLater();
637                     }
638                 });
639         if (m_pTrackModel) {
640             connect(m_pDlgTagFetcher.get(),
641                     &DlgTagFetcher::next,
642                     this,
643                     &DlgTrackInfo::slotNextDlgTagFetcher);
644 
645             connect(m_pDlgTagFetcher.get(),
646                     &DlgTagFetcher::previous,
647                     this,
648                     &DlgTrackInfo::slotPrevDlgTagFetcher);
649         }
650     }
651     if (m_pTrackModel) {
652         DEBUG_ASSERT(m_currentTrackIndex.isValid());
653         m_pDlgTagFetcher->loadTrack(m_currentTrackIndex);
654     } else {
655         DEBUG_ASSERT(m_pLoadedTrack);
656         m_pDlgTagFetcher->loadTrack(m_pLoadedTrack);
657     }
658     m_pDlgTagFetcher->show();
659 }
660