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