File indexing completed on 2025-02-23 04:34:18

0001 /**
0002  * \file importdialog.cpp
0003  * Import dialog.
0004  *
0005  * \b Project: Kid3
0006  * \author Urs Fleisch
0007  * \date 17 Sep 2003
0008  *
0009  * Copyright (C) 2003-2024  Urs Fleisch
0010  *
0011  * This file is part of Kid3.
0012  *
0013  * Kid3 is free software; you can redistribute it and/or modify
0014  * it under the terms of the GNU General Public License as published by
0015  * the Free Software Foundation; either version 2 of the License, or
0016  * (at your option) any later version.
0017  *
0018  * Kid3 is distributed in the hope that it will be useful,
0019  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0020  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0021  * GNU General Public License for more details.
0022  *
0023  * You should have received a copy of the GNU General Public License
0024  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
0025  */
0026 
0027 #include "importdialog.h"
0028 #include <QLayout>
0029 #include <QPushButton>
0030 #include <QToolButton>
0031 #include <QLabel>
0032 #include <QCheckBox>
0033 #include <QSpinBox>
0034 #include <QString>
0035 #include <QVBoxLayout>
0036 #include <QHBoxLayout>
0037 #include <QComboBox>
0038 #include <QLineEdit>
0039 #include <QBitArray>
0040 #include <QToolTip>
0041 #include <QTableView>
0042 #include <QHeaderView>
0043 #include <QList>
0044 #include <QGridLayout>
0045 #include <QGroupBox>
0046 #include <QDir>
0047 #include <QMenu>
0048 #include <QCoreApplication>
0049 #include "config.h"
0050 #include "importconfig.h"
0051 #include "genres.h"
0052 #include "serverimporter.h"
0053 #include "servertrackimporter.h"
0054 #include "serverimportdialog.h"
0055 #include "servertrackimportdialog.h"
0056 #include "textimportdialog.h"
0057 #include "tagimportdialog.h"
0058 #include "contexthelp.h"
0059 #include "taggedfile.h"
0060 #include "trackdata.h"
0061 #include "trackdatamodel.h"
0062 #include "frameitemdelegate.h"
0063 #include "trackdatamatcher.h"
0064 #include "iplatformtools.h"
0065 
0066 namespace {
0067 
0068 /**
0069  * Get list of frame types whose visibility can be changed using a context menu.
0070  * @return list of frame types of Frame::Type or
0071  *         TrackDataModel::TrackProperties.
0072  */
0073 QList<int> checkableFrameTypes() {
0074   return QList<int>()
0075       << TrackDataModel::FT_FileName << TrackDataModel::FT_FilePath;
0076 }
0077 
0078 }
0079 
0080 /**
0081  * Constructor.
0082  *
0083  * @param platformTools platform tools
0084  * @param parent        parent widget
0085  * @param caption       dialog title
0086  * @param genreModel    genre model
0087  * @param trackDataModel track data to be filled with imported values,
0088  *                      is passed with durations of files set
0089  * @param importers     server importers
0090  * @param trackImporters server track importers
0091  */
0092 ImportDialog::ImportDialog(IPlatformTools* platformTools,
0093                            QWidget* parent, const QString& caption,
0094                            TrackDataModel* trackDataModel,
0095                            GenreModel* genreModel,
0096                            const QList<ServerImporter*>& importers,
0097                            const QList<ServerTrackImporter*>& trackImporters)
0098   : QDialog(parent), m_platformTools(platformTools),
0099     m_autoStartSubDialog(-1), m_columnVisibility(0ULL),
0100     m_trackDataModel(trackDataModel), m_importers(importers),
0101     m_trackImporters(trackImporters)
0102 {
0103   setObjectName(QLatin1String("ImportDialog"));
0104   setModal(false);
0105   setWindowTitle(caption);
0106   setSizeGripEnabled(true);
0107 
0108   auto vlayout = new QVBoxLayout(this);
0109 
0110   m_trackDataTable = new QTableView(this);
0111   m_trackDataTable->setModel(m_trackDataModel);
0112   m_trackDataTable->resizeColumnsToContents();
0113   m_trackDataTable->setItemDelegateForColumn(
0114         m_trackDataModel->columnForFrameType(Frame::FT_Genre),
0115         new FrameItemDelegate(genreModel, this));
0116   m_trackDataTable->verticalHeader()->setSectionsMovable(true);
0117   m_trackDataTable->horizontalHeader()->setSectionsMovable(true);
0118   m_trackDataTable->horizontalHeader()->setContextMenuPolicy(
0119         Qt::CustomContextMenu);
0120   connect(m_trackDataTable->verticalHeader(), &QHeaderView::sectionMoved,
0121           this, &ImportDialog::moveTableRow);
0122   connect(m_trackDataTable->horizontalHeader(),
0123           &QWidget::customContextMenuRequested,
0124       this, &ImportDialog::showTableHeaderContextMenu);
0125   vlayout->addWidget(m_trackDataTable);
0126 
0127   auto accuracyLayout = new QHBoxLayout;
0128   auto accuracyLabel = new QLabel(tr("Accuracy:"));
0129   accuracyLayout->addWidget(accuracyLabel);
0130   m_accuracyPercentLabel = new QLabel(QLatin1String("-"));
0131 #if QT_VERSION >= 0x050b00
0132   m_accuracyPercentLabel->setMinimumWidth(
0133         m_accuracyPercentLabel->fontMetrics().horizontalAdvance(QLatin1String("100%")));
0134 #else
0135   m_accuracyPercentLabel->setMinimumWidth(
0136         m_accuracyPercentLabel->fontMetrics().width(QLatin1String("100%")));
0137 #endif
0138   accuracyLayout->addWidget(m_accuracyPercentLabel);
0139   auto coverArtLabel = new QLabel(tr("Cover Art:"));
0140   accuracyLayout->addWidget(coverArtLabel);
0141   m_coverArtUrlLabel = new QLabel(QLatin1String(" -"));
0142   m_coverArtUrlLabel->setSizePolicy(QSizePolicy::Ignored,
0143                                     QSizePolicy::Preferred);
0144   accuracyLayout->addWidget(m_coverArtUrlLabel, 1);
0145   vlayout->addLayout(accuracyLayout);
0146 
0147   auto butlayout = new QHBoxLayout;
0148   auto fileButton = new QPushButton(tr("From F&ile/Clipboard..."));
0149   fileButton->setAutoDefault(false);
0150   butlayout->addWidget(fileButton);
0151   auto tagsButton = new QPushButton(tr("From T&ags..."));
0152   tagsButton->setAutoDefault(false);
0153   butlayout->addWidget(tagsButton);
0154   auto serverButton = new QPushButton(tr("&From Server..."));
0155   serverButton->setAutoDefault(false);
0156   butlayout->addWidget(serverButton);
0157   m_serverComboBox = new QComboBox;
0158   m_serverComboBox->setEditable(false);
0159   const auto sis = m_importers;
0160   for (const ServerImporter* si : sis) {
0161     m_serverComboBox->addItem(QCoreApplication::translate("@default", si->name()));
0162   }
0163   const auto stis = m_trackImporters;
0164   for (const ServerTrackImporter* si : stis) {
0165     m_serverComboBox->addItem(QCoreApplication::translate("@default", si->name()));
0166   }
0167   butlayout->addWidget(m_serverComboBox);
0168   if (m_serverComboBox->count() == 0) {
0169     serverButton->hide();
0170     m_serverComboBox->hide();
0171   }
0172   auto butspacer = new QSpacerItem(16, 0, QSizePolicy::Expanding,
0173                                          QSizePolicy::Minimum);
0174   butlayout->addItem(butspacer);
0175   auto destLabel = new QLabel;
0176   destLabel->setText(tr("D&estination:"));
0177   butlayout->addWidget(destLabel);
0178   m_destComboBox = new QComboBox;
0179   m_destComboBox->setEditable(false);
0180   const QList<QPair<Frame::TagVersion, QString> > tagVersions =
0181       Frame::availableTagVersions();
0182   for (auto it = tagVersions.constBegin(); it != tagVersions.constEnd(); ++it) {
0183     m_destComboBox->addItem(it->second, it->first);
0184   }
0185   destLabel->setBuddy(m_destComboBox);
0186   butlayout->addWidget(m_destComboBox);
0187   auto revertButton = new QToolButton;
0188   revertButton->setIcon(
0189         m_platformTools->iconFromTheme(QLatin1String("document-revert")));
0190   revertButton->setToolTip(tr("Revert"));
0191   revertButton->setShortcut(QKeySequence::Undo);
0192   connect(revertButton, &QAbstractButton::clicked,
0193           this, &ImportDialog::changeTagDestination);
0194   butlayout->addWidget(revertButton);
0195   vlayout->addLayout(butlayout);
0196 
0197   auto matchLayout = new QHBoxLayout;
0198   m_mismatchCheckBox = new QCheckBox(
0199     tr("Check maximum allowable time &difference (sec):"));
0200   matchLayout->addWidget(m_mismatchCheckBox);
0201   m_maxDiffSpinBox = new QSpinBox;
0202   m_maxDiffSpinBox->setMaximum(9999);
0203   matchLayout->addWidget(m_maxDiffSpinBox);
0204   auto matchSpacer = new QSpacerItem(16, 0, QSizePolicy::Expanding,
0205                                              QSizePolicy::Minimum);
0206   matchLayout->addItem(matchSpacer);
0207   auto matchLabel = new QLabel(tr("Match with:"));
0208   matchLayout->addWidget(matchLabel);
0209   auto lengthButton = new QPushButton(tr("&Length"));
0210   lengthButton->setAutoDefault(false);
0211   matchLayout->addWidget(lengthButton);
0212   auto trackButton = new QPushButton(tr("T&rack"));
0213   trackButton->setAutoDefault(false);
0214   matchLayout->addWidget(trackButton);
0215   auto titleButton = new QPushButton(tr("&Title"));
0216   titleButton->setAutoDefault(false);
0217   matchLayout->addWidget(titleButton);
0218   vlayout->addLayout(matchLayout);
0219 
0220   connect(fileButton, &QAbstractButton::clicked,
0221           this, &ImportDialog::fromText);
0222   connect(tagsButton, &QAbstractButton::clicked,
0223           this, &ImportDialog::fromTags);
0224   connect(serverButton, &QAbstractButton::clicked,
0225           this, &ImportDialog::fromServer);
0226   connect(m_serverComboBox, static_cast<void (QComboBox::*)(int)>(
0227             &QComboBox::activated), this, &ImportDialog::fromServer);
0228   connect(lengthButton, &QAbstractButton::clicked,
0229           this, &ImportDialog::matchWithLength);
0230   connect(trackButton, &QAbstractButton::clicked,
0231           this, &ImportDialog::matchWithTrack);
0232   connect(titleButton, &QAbstractButton::clicked,
0233           this, &ImportDialog::matchWithTitle);
0234   connect(m_mismatchCheckBox, &QAbstractButton::toggled,
0235           this, &ImportDialog::showPreview);
0236   connect(m_maxDiffSpinBox, static_cast<void (QSpinBox::*)(int)>(
0237             &QSpinBox::valueChanged), this, &ImportDialog::maxDiffChanged);
0238   connect(this, &QDialog::finished, this, &ImportDialog::hideSubdialogs);
0239 
0240   auto hlayout = new QHBoxLayout;
0241   auto hspacer = new QSpacerItem(16, 0, QSizePolicy::Expanding,
0242                                          QSizePolicy::Minimum);
0243   auto helpButton = new QPushButton(tr("&Help"), this);
0244   helpButton->setAutoDefault(false);
0245   auto saveButton = new QPushButton(tr("&Save Settings"), this);
0246   saveButton->setAutoDefault(false);
0247   auto okButton = new QPushButton(tr("&OK"), this);
0248   auto cancelButton = new QPushButton(tr("&Cancel"), this);
0249   cancelButton->setAutoDefault(false);
0250   hlayout->addWidget(helpButton);
0251   hlayout->addWidget(saveButton);
0252   hlayout->addItem(hspacer);
0253   hlayout->addWidget(okButton);
0254   hlayout->addWidget(cancelButton);
0255   connect(helpButton, &QAbstractButton::clicked,
0256           this, &ImportDialog::showHelp);
0257   connect(saveButton, &QAbstractButton::clicked,
0258           this, &ImportDialog::saveConfig);
0259   connect(okButton, &QAbstractButton::clicked, this, &QDialog::accept);
0260   connect(cancelButton, &QAbstractButton::clicked, this, &QDialog::reject);
0261   vlayout->addLayout(hlayout);
0262 }
0263 
0264 /**
0265  * Destructor.
0266  */
0267 ImportDialog::~ImportDialog()
0268 {
0269   // Must not be inline because of forwared declared QScopedPointer.
0270 }
0271 
0272 /**
0273  * Import from server and preview in table.
0274  */
0275 void ImportDialog::fromServer()
0276 {
0277   if (m_serverComboBox)
0278     displayServerImportDialog(m_serverComboBox->currentIndex());
0279 }
0280 
0281 /**
0282  * Import from text.
0283  */
0284 void ImportDialog::fromText()
0285 {
0286   if (!m_textImportDialog) {
0287     m_textImportDialog.reset(new TextImportDialog(
0288           m_platformTools, this, m_trackDataModel));
0289     connect(m_textImportDialog.data(), &TextImportDialog::trackDataUpdated,
0290             this, &ImportDialog::showPreview);
0291   }
0292   m_textImportDialog->clear();
0293   m_textImportDialog->show();
0294 }
0295 
0296 /**
0297  * Import from tags.
0298  */
0299 void ImportDialog::fromTags()
0300 {
0301   if (!m_tagImportDialog) {
0302     m_tagImportDialog.reset(new TagImportDialog(this, m_trackDataModel));
0303     connect(m_tagImportDialog.data(), &TagImportDialog::trackDataUpdated,
0304             this, &ImportDialog::showPreview);
0305   }
0306   m_tagImportDialog->clear();
0307   m_tagImportDialog->show();
0308 }
0309 
0310 /**
0311  * Display server import dialog.
0312  *
0313  * @param importerIdx importer index, if invalid but not negative the
0314  *                    MusicBrainz Fingerprint dialog is displayed
0315  */
0316 void ImportDialog::displayServerImportDialog(int importerIdx)
0317 {
0318   if (importerIdx >= 0) {
0319     if (importerIdx < m_importers.size()) {
0320       displayServerImportDialog(m_importers.at(importerIdx));
0321     } else if (importerIdx - m_importers.size() < m_trackImporters.size()) {
0322       displayServerTrackImportDialog(
0323             m_trackImporters.at(importerIdx - m_importers.size()));
0324     }
0325   }
0326 }
0327 
0328 /**
0329  * Display server import dialog.
0330  *
0331  * @param source import source
0332  */
0333 void ImportDialog::displayServerImportDialog(ServerImporter* source)
0334 {
0335   if (!m_serverImportDialog) {
0336     m_serverImportDialog.reset(new ServerImportDialog(this));
0337     connect(m_serverImportDialog.data(), &ServerImportDialog::trackDataUpdated,
0338             this, &ImportDialog::showPreview);
0339     connect(m_serverImportDialog.data(), &QDialog::accepted,
0340             this, &ImportDialog::onServerImportDialogClosed);
0341   }
0342   m_serverImportDialog->setImportSource(source);
0343   m_serverImportDialog->setArtistAlbum(
0344         m_trackDataModel->trackData().getArtist(),
0345         m_trackDataModel->trackData().getAlbum());
0346   m_serverImportDialog->show();
0347 }
0348 
0349 /**
0350  * Import from track server and preview in table.
0351  *
0352  * @param source import source
0353  */
0354 void ImportDialog::displayServerTrackImportDialog(ServerTrackImporter* source)
0355 {
0356   if (!m_serverTrackImportDialog) {
0357     m_serverTrackImportDialog.reset(new ServerTrackImportDialog(this, m_trackDataModel));
0358     connect(m_serverTrackImportDialog.data(), &ServerTrackImportDialog::trackDataUpdated,
0359             this, &ImportDialog::showPreview);
0360   }
0361   m_serverTrackImportDialog->setImportSource(source);
0362   m_serverTrackImportDialog->initTable();
0363   m_serverTrackImportDialog->exec();
0364 }
0365 
0366 /**
0367  * Hide subdialogs.
0368  */
0369 void ImportDialog::hideSubdialogs()
0370 {
0371   if (m_serverImportDialog)
0372     m_serverImportDialog->hide();
0373   if (m_textImportDialog)
0374     m_textImportDialog->hide();
0375   if (m_tagImportDialog)
0376     m_tagImportDialog->hide();
0377 }
0378 
0379 /**
0380  * Shows the dialog as a modeless dialog.
0381  *
0382  * @param importerIndex index of importer to use, -1 for none
0383  */
0384 void ImportDialog::showWithSubDialog(int importerIndex)
0385 {
0386   m_autoStartSubDialog = importerIndex;
0387 
0388   if (importerIndex >= 0 && importerIndex < m_serverComboBox->count()) {
0389     m_serverComboBox->setCurrentIndex(importerIndex);
0390   }
0391 
0392   show();
0393   if (m_autoStartSubDialog >= 0) {
0394     displayServerImportDialog(m_autoStartSubDialog);
0395   }
0396 }
0397 
0398 /**
0399  * Clear dialog data.
0400  */
0401 void ImportDialog::clear()
0402 {
0403   const ImportConfig& importCfg = ImportConfig::instance();
0404   m_serverComboBox->setCurrentIndex(importCfg.importServer());
0405   Frame::TagVersion importDest = importCfg.importDest();
0406   int index = m_destComboBox->findData(importDest);
0407   m_destComboBox->setCurrentIndex(index);
0408 
0409   if (!m_trackDataModel->trackData().isTagSupported(
0410         Frame::tagNumberFromMask(importDest))) {
0411     index = m_destComboBox->findData(Frame::TagV2);
0412     m_destComboBox->setCurrentIndex(index);
0413     changeTagDestination();
0414   }
0415 
0416   m_mismatchCheckBox->setChecked(importCfg.enableTimeDifferenceCheck());
0417   m_maxDiffSpinBox->setValue(importCfg.maxTimeDifference());
0418   m_columnVisibility = importCfg.importVisibleColumns();
0419 
0420   const auto frameTypes = checkableFrameTypes();
0421   for (int frameType : frameTypes) {
0422     if (frameType < 64) {
0423       if (int column = m_trackDataModel->columnForFrameType(frameType);
0424           column != -1) {
0425         m_trackDataTable->setColumnHidden(
0426               column, (m_columnVisibility & (1ULL << frameType)) == 0ULL);
0427       }
0428     }
0429   }
0430 
0431   if (!importCfg.importWindowGeometry().isEmpty()) {
0432     restoreGeometry(importCfg.importWindowGeometry());
0433   }
0434 
0435   showPreview();
0436 }
0437 
0438 /**
0439  * Show fields to import in text as preview in table.
0440  */
0441 void ImportDialog::showPreview()
0442 {
0443   // make time difference check
0444   bool diffCheckEnable;
0445   int maxDiff;
0446   getTimeDifferenceCheck(diffCheckEnable, maxDiff);
0447   m_trackDataModel->setTimeDifferenceCheck(diffCheckEnable, maxDiff);
0448   m_trackDataTable->scrollToTop();
0449   m_trackDataTable->resizeColumnsToContents();
0450   m_trackDataTable->resizeRowsToContents();
0451 
0452   int accuracy = m_trackDataModel->calculateAccuracy();
0453   m_accuracyPercentLabel->setText(accuracy >= 0 && accuracy <= 100
0454                                   ? QString::number(accuracy) + QLatin1Char('%')
0455                                   : QLatin1String("-"));
0456   QUrl coverArtUrl = m_trackDataModel->getTrackData().getCoverArtUrl();
0457   m_coverArtUrlLabel->setText(coverArtUrl.isEmpty() ? QLatin1String("-")
0458                                                     : coverArtUrl.toString());
0459 }
0460 
0461 /**
0462  * Called when server import dialog is closed.
0463  */
0464 void ImportDialog::onServerImportDialogClosed()
0465 {
0466   // This is used to prevent that the import dialog is brought behind the
0467   // main window when the server import dialog is closed, which happened
0468   // with Qt 5 on Mac OS X.
0469   show();
0470   raise();
0471   activateWindow();
0472 }
0473 
0474 /**
0475  * Get import destination.
0476  *
0477  * @return TagV1, TagV2 or TagV2V1 for ID3v1, ID3v2 or both.
0478  */
0479 Frame::TagVersion ImportDialog::getDestination() const
0480 {
0481   return Frame::tagVersionCast(
0482     m_destComboBox->itemData(m_destComboBox->currentIndex()).toInt());
0483 }
0484 
0485 /**
0486  * Show help.
0487  */
0488 void ImportDialog::showHelp()
0489 {
0490   ContextHelp::displayHelp(QLatin1String("import"));
0491 }
0492 
0493 /**
0494  * Save the local settings to the configuration.
0495  */
0496 void ImportDialog::saveConfig()
0497 {
0498   ImportConfig& importCfg = ImportConfig::instance();
0499   importCfg.setImportDest(Frame::tagVersionCast(
0500     m_destComboBox->itemData(m_destComboBox->currentIndex()).toInt()));
0501 
0502   importCfg.setImportServer(m_serverComboBox->currentIndex());
0503   bool enable;
0504   int maxDiff;
0505   getTimeDifferenceCheck(enable, maxDiff);
0506   importCfg.setEnableTimeDifferenceCheck(enable);
0507   importCfg.setMaxTimeDifference(maxDiff);
0508   importCfg.setImportVisibleColumns(m_columnVisibility);
0509 
0510   importCfg.setImportWindowGeometry(saveGeometry());
0511 }
0512 
0513 /**
0514  * Get time difference check configuration.
0515  *
0516  * @param enable  true if check is enabled
0517  * @param maxDiff maximum allowed time difference
0518  */
0519 void ImportDialog::getTimeDifferenceCheck(bool& enable, int& maxDiff) const
0520 {
0521   enable = m_mismatchCheckBox->isChecked();
0522   maxDiff = m_maxDiffSpinBox->value();
0523 }
0524 
0525 /**
0526  * Called when the maximum time difference value is changed.
0527  */
0528 void ImportDialog::maxDiffChanged() {
0529   if (m_mismatchCheckBox->isChecked()) {
0530     showPreview();
0531   }
0532 }
0533 
0534 /**
0535  * Move a table row.
0536  *
0537  * The first parameter @a section is not used.
0538  * @param fromIndex index of position moved from
0539  * @param toIndex   index of position moved to
0540  */
0541 void ImportDialog::moveTableRow(int, int fromIndex, int toIndex) {
0542   if (auto vHeader = qobject_cast<QHeaderView*>(sender())) {
0543     // revert movement, but avoid recursion
0544     disconnect(vHeader, &QHeaderView::sectionMoved, nullptr, nullptr);
0545     vHeader->moveSection(toIndex, fromIndex);
0546     connect(vHeader, &QHeaderView::sectionMoved,
0547             this, &ImportDialog::moveTableRow);
0548   }
0549 
0550   // Allow dragging multiple rows when pressing Ctrl by adding selected rows.
0551   ImportTrackDataVector trackDataVector(m_trackDataModel->getTrackData());
0552   auto numTracks = trackDataVector.size();
0553 
0554   int diff = toIndex - fromIndex;
0555   QList<int> fromList;
0556   if (fromIndex >= 0 && fromIndex < numTracks &&
0557       toIndex >= 0 && toIndex < numTracks) {
0558     fromList.append(fromIndex);
0559   }
0560   const QModelIndexList selectedRows =
0561       m_trackDataTable->selectionModel()->selectedRows();
0562   for (const QModelIndex& index : selectedRows) {
0563     int from = index.row();
0564     int to = from + diff;
0565     if (!fromList.contains(from) &&
0566         from >= 0 && from < numTracks &&
0567         to >= 0 && to < numTracks) {
0568       fromList.append(from);
0569     }
0570   }
0571   std::sort(fromList.begin(), fromList.end());
0572 
0573   for (auto it = fromList.constBegin(); it != fromList.constEnd(); ++it) {
0574     fromIndex = *it;
0575     toIndex = fromIndex + diff;
0576     // swap elements but keep file durations and names
0577     ImportTrackData fromData(trackDataVector[fromIndex]);
0578     ImportTrackData toData(trackDataVector[toIndex]);
0579     trackDataVector[fromIndex].setFrameCollection(toData.getFrameCollection());
0580     trackDataVector[toIndex].setFrameCollection(fromData.getFrameCollection());
0581     trackDataVector[fromIndex].setImportDuration(toData.getImportDuration());
0582     trackDataVector[toIndex].setImportDuration(fromData.getImportDuration());
0583   }
0584   if (!fromList.isEmpty()) {
0585     m_trackDataModel->setTrackData(trackDataVector);
0586     // redisplay the table
0587     showPreview();
0588   }
0589 }
0590 
0591 /**
0592  * Called when the destination combo box value is changed.
0593  */
0594 void ImportDialog::changeTagDestination()
0595 {
0596   ImportTrackDataVector trackDataVector(m_trackDataModel->getTrackData());
0597   trackDataVector.readTags(getDestination());
0598   m_trackDataModel->setTrackData(trackDataVector);
0599   showPreview();
0600 }
0601 
0602 /**
0603  * Match import data with length.
0604  */
0605 void ImportDialog::matchWithLength()
0606 {
0607   bool diffCheckEnable;
0608   int maxDiff;
0609   getTimeDifferenceCheck(diffCheckEnable, maxDiff);
0610   if (TrackDataMatcher::matchWithLength(m_trackDataModel, diffCheckEnable, maxDiff))
0611     showPreview();
0612 }
0613 
0614 /**
0615  * Match import data with track number.
0616  */
0617 void ImportDialog::matchWithTrack()
0618 {
0619   if (TrackDataMatcher::matchWithTrack(m_trackDataModel))
0620     showPreview();
0621 }
0622 
0623 /**
0624  * Match import data with title.
0625  */
0626 void ImportDialog::matchWithTitle()
0627 {
0628   if (TrackDataMatcher::matchWithTitle(m_trackDataModel))
0629     showPreview();
0630 }
0631 
0632 /**
0633  * Display custom context menu for horizontal table header.
0634  *
0635  * @param pos position where context menu is drawn on screen
0636  */
0637 void ImportDialog::showTableHeaderContextMenu(const QPoint& pos)
0638 {
0639   if (QWidget* widget = qobject_cast<QWidget*>(sender())) {
0640     QMenu menu(widget);
0641     const auto frameTypes = checkableFrameTypes();
0642     for (int frameType : frameTypes) {
0643       if (int column = m_trackDataModel->columnForFrameType(frameType);
0644           column != -1) {
0645         auto action = new QAction(&menu);
0646         action->setText(
0647               m_trackDataModel->headerData(column, Qt::Horizontal).toString());
0648         action->setData(frameType);
0649         action->setCheckable(true);
0650         action->setChecked((m_columnVisibility & (1ULL << frameType)) != 0ULL);
0651         connect(action, &QAction::triggered,
0652                 this, &ImportDialog::toggleTableColumnVisibility);
0653         menu.addAction(action);
0654       }
0655     }
0656     menu.setMouseTracking(true);
0657     menu.exec(widget->mapToGlobal(pos));
0658   }
0659 }
0660 
0661 /**
0662  * Toggle visibility of table column.
0663  *
0664  * @param visible true to make column visible
0665  */
0666 void ImportDialog::toggleTableColumnVisibility(bool visible)
0667 {
0668   if (auto action = qobject_cast<QAction*>(sender())) {
0669     bool ok;
0670     if (int frameType = action->data().toInt(&ok); ok && frameType < 64) {
0671       if (visible) {
0672         m_columnVisibility |= 1ULL << frameType;
0673       } else {
0674         m_columnVisibility &= ~(1ULL << frameType);
0675       }
0676       if (int column = m_trackDataModel->columnForFrameType(frameType);
0677           column != -1) {
0678         m_trackDataTable->setColumnHidden(column, !visible);
0679       }
0680     }
0681     if (visible) {
0682       m_trackDataTable->resizeColumnsToContents();
0683     }
0684   }
0685 }