1 /**
2  * \file importdialog.cpp
3  * Import dialog.
4  *
5  * \b Project: Kid3
6  * \author Urs Fleisch
7  * \date 17 Sep 2003
8  *
9  * Copyright (C) 2003-2018  Urs Fleisch
10  *
11  * This file is part of Kid3.
12  *
13  * Kid3 is free software; you can redistribute it and/or modify
14  * it under the terms of the GNU General Public License as published by
15  * the Free Software Foundation; either version 2 of the License, or
16  * (at your option) any later version.
17  *
18  * Kid3 is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21  * GNU General Public License for more details.
22  *
23  * You should have received a copy of the GNU General Public License
24  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
25  */
26 
27 #include "importdialog.h"
28 #include <QLayout>
29 #include <QPushButton>
30 #include <QToolButton>
31 #include <QLabel>
32 #include <QCheckBox>
33 #include <QSpinBox>
34 #include <QString>
35 #include <QVBoxLayout>
36 #include <QHBoxLayout>
37 #include <QComboBox>
38 #include <QLineEdit>
39 #include <QBitArray>
40 #include <QToolTip>
41 #include <QTableView>
42 #include <QHeaderView>
43 #include <QList>
44 #include <QGridLayout>
45 #include <QGroupBox>
46 #include <QDir>
47 #include <QMenu>
48 #include <QCoreApplication>
49 #include "config.h"
50 #include "importconfig.h"
51 #include "genres.h"
52 #include "serverimporter.h"
53 #include "servertrackimporter.h"
54 #include "serverimportdialog.h"
55 #include "servertrackimportdialog.h"
56 #include "textimportdialog.h"
57 #include "tagimportdialog.h"
58 #include "contexthelp.h"
59 #include "taggedfile.h"
60 #include "trackdata.h"
61 #include "trackdatamodel.h"
62 #include "frameitemdelegate.h"
63 #include "trackdatamatcher.h"
64 #include "iplatformtools.h"
65 
66 namespace {
67 
68 /**
69  * Get list of frame types whose visibility can be changed using a context menu.
70  * @return list of frame types of Frame::Type or
71  *         TrackDataModel::TrackProperties.
72  */
checkableFrameTypes()73 QList<int> checkableFrameTypes() {
74   return QList<int>()
75       << TrackDataModel::FT_FileName << TrackDataModel::FT_FilePath;
76 }
77 
78 }
79 
80 /**
81  * Constructor.
82  *
83  * @param platformTools platform tools
84  * @param parent        parent widget
85  * @param caption       dialog title
86  * @param genreModel    genre model
87  * @param trackDataModel track data to be filled with imported values,
88  *                      is passed with durations of files set
89  * @param importers     server importers
90  * @param trackImporters server track importers
91  */
ImportDialog(IPlatformTools * platformTools,QWidget * parent,QString & caption,TrackDataModel * trackDataModel,GenreModel * genreModel,const QList<ServerImporter * > & importers,const QList<ServerTrackImporter * > & trackImporters)92 ImportDialog::ImportDialog(IPlatformTools* platformTools,
93                            QWidget* parent, QString& caption,
94                            TrackDataModel* trackDataModel,
95                            GenreModel* genreModel,
96                            const QList<ServerImporter*>& importers,
97                            const QList<ServerTrackImporter*>& trackImporters)
98   : QDialog(parent), m_platformTools(platformTools),
99     m_autoStartSubDialog(-1), m_columnVisibility(0ULL),
100     m_trackDataModel(trackDataModel), m_importers(importers),
101     m_trackImporters(trackImporters)
102 {
103   setObjectName(QLatin1String("ImportDialog"));
104   setModal(false);
105   setWindowTitle(caption);
106   setSizeGripEnabled(true);
107 
108   auto vlayout = new QVBoxLayout(this);
109 
110   m_trackDataTable = new QTableView(this);
111   m_trackDataTable->setModel(m_trackDataModel);
112   m_trackDataTable->resizeColumnsToContents();
113   m_trackDataTable->setItemDelegateForColumn(
114         m_trackDataModel->columnForFrameType(Frame::FT_Genre),
115         new FrameItemDelegate(genreModel, this));
116   m_trackDataTable->verticalHeader()->setSectionsMovable(true);
117   m_trackDataTable->horizontalHeader()->setSectionsMovable(true);
118   m_trackDataTable->horizontalHeader()->setContextMenuPolicy(
119         Qt::CustomContextMenu);
120   connect(m_trackDataTable->verticalHeader(), &QHeaderView::sectionMoved,
121           this, &ImportDialog::moveTableRow);
122   connect(m_trackDataTable->horizontalHeader(),
123           &QWidget::customContextMenuRequested,
124       this, &ImportDialog::showTableHeaderContextMenu);
125   vlayout->addWidget(m_trackDataTable);
126 
127   auto accuracyLayout = new QHBoxLayout;
128   QLabel* accuracyLabel = new QLabel(tr("Accuracy:"));
129   accuracyLayout->addWidget(accuracyLabel);
130   m_accuracyPercentLabel = new QLabel(QLatin1String("-"));
131 #if QT_VERSION >= 0x050b00
132   m_accuracyPercentLabel->setMinimumWidth(
133         m_accuracyPercentLabel->fontMetrics().horizontalAdvance(QLatin1String("100%")));
134 #else
135   m_accuracyPercentLabel->setMinimumWidth(
136         m_accuracyPercentLabel->fontMetrics().width(QLatin1String("100%")));
137 #endif
138   accuracyLayout->addWidget(m_accuracyPercentLabel);
139   QLabel* coverArtLabel = new QLabel(tr("Cover Art:"));
140   accuracyLayout->addWidget(coverArtLabel);
141   m_coverArtUrlLabel = new QLabel(QLatin1String(" -"));
142   m_coverArtUrlLabel->setSizePolicy(QSizePolicy::Ignored,
143                                     QSizePolicy::Preferred);
144   accuracyLayout->addWidget(m_coverArtUrlLabel, 1);
145   vlayout->addLayout(accuracyLayout);
146 
147   auto butlayout = new QHBoxLayout;
148   QPushButton* fileButton = new QPushButton(tr("From F&ile/Clipboard..."));
149   fileButton->setAutoDefault(false);
150   butlayout->addWidget(fileButton);
151   QPushButton* tagsButton = new QPushButton(tr("From T&ags..."));
152   tagsButton->setAutoDefault(false);
153   butlayout->addWidget(tagsButton);
154   QPushButton* serverButton = new QPushButton(tr("&From Server..."));
155   serverButton->setAutoDefault(false);
156   butlayout->addWidget(serverButton);
157   m_serverComboBox = new QComboBox;
158   m_serverComboBox->setEditable(false);
159   const auto sis = m_importers;
160   for (const ServerImporter* si : sis) {
161     m_serverComboBox->addItem(QCoreApplication::translate("@default", si->name()));
162   }
163   const auto stis = m_trackImporters;
164   for (const ServerTrackImporter* si : stis) {
165     m_serverComboBox->addItem(QCoreApplication::translate("@default", si->name()));
166   }
167   butlayout->addWidget(m_serverComboBox);
168   if (m_serverComboBox->count() == 0) {
169     serverButton->hide();
170     m_serverComboBox->hide();
171   }
172   auto butspacer = new QSpacerItem(16, 0, QSizePolicy::Expanding,
173                                          QSizePolicy::Minimum);
174   butlayout->addItem(butspacer);
175   QLabel* destLabel = new QLabel;
176   destLabel->setText(tr("D&estination:"));
177   butlayout->addWidget(destLabel);
178   m_destComboBox = new QComboBox;
179   m_destComboBox->setEditable(false);
180   const QList<QPair<Frame::TagVersion, QString> > tagVersions =
181       Frame::availableTagVersions();
182   for (auto it = tagVersions.constBegin(); it != tagVersions.constEnd(); ++it) {
183     m_destComboBox->addItem(it->second, it->first);
184   }
185   destLabel->setBuddy(m_destComboBox);
186   butlayout->addWidget(m_destComboBox);
187   auto revertButton = new QToolButton;
188   revertButton->setIcon(
189         m_platformTools->iconFromTheme(QLatin1String("document-revert")));
190   revertButton->setToolTip(tr("Revert"));
191   revertButton->setShortcut(QKeySequence::Undo);
192   connect(revertButton, &QAbstractButton::clicked,
193           this, &ImportDialog::changeTagDestination);
194   butlayout->addWidget(revertButton);
195   vlayout->addLayout(butlayout);
196 
197   auto matchLayout = new QHBoxLayout;
198   m_mismatchCheckBox = new QCheckBox(
199     tr("Check maximum allowable time &difference (sec):"));
200   matchLayout->addWidget(m_mismatchCheckBox);
201   m_maxDiffSpinBox = new QSpinBox;
202   m_maxDiffSpinBox->setMaximum(9999);
203   matchLayout->addWidget(m_maxDiffSpinBox);
204   auto matchSpacer = new QSpacerItem(16, 0, QSizePolicy::Expanding,
205                                              QSizePolicy::Minimum);
206   matchLayout->addItem(matchSpacer);
207   QLabel* matchLabel = new QLabel(tr("Match with:"));
208   matchLayout->addWidget(matchLabel);
209   QPushButton* lengthButton = new QPushButton(tr("&Length"));
210   lengthButton->setAutoDefault(false);
211   matchLayout->addWidget(lengthButton);
212   QPushButton* trackButton = new QPushButton(tr("T&rack"));
213   trackButton->setAutoDefault(false);
214   matchLayout->addWidget(trackButton);
215   QPushButton* titleButton = new QPushButton(tr("&Title"));
216   titleButton->setAutoDefault(false);
217   matchLayout->addWidget(titleButton);
218   vlayout->addLayout(matchLayout);
219 
220   connect(fileButton, &QAbstractButton::clicked,
221           this, &ImportDialog::fromText);
222   connect(tagsButton, &QAbstractButton::clicked,
223           this, &ImportDialog::fromTags);
224   connect(serverButton, &QAbstractButton::clicked,
225           this, &ImportDialog::fromServer);
226   connect(m_serverComboBox, static_cast<void (QComboBox::*)(int)>(
227             &QComboBox::activated), this, &ImportDialog::fromServer);
228   connect(lengthButton, &QAbstractButton::clicked,
229           this, &ImportDialog::matchWithLength);
230   connect(trackButton, &QAbstractButton::clicked,
231           this, &ImportDialog::matchWithTrack);
232   connect(titleButton, &QAbstractButton::clicked,
233           this, &ImportDialog::matchWithTitle);
234   connect(m_mismatchCheckBox, &QAbstractButton::toggled,
235           this, &ImportDialog::showPreview);
236   connect(m_maxDiffSpinBox, static_cast<void (QSpinBox::*)(int)>(
237             &QSpinBox::valueChanged), this, &ImportDialog::maxDiffChanged);
238   connect(this, &QDialog::finished, this, &ImportDialog::hideSubdialogs);
239 
240   auto hlayout = new QHBoxLayout;
241   auto hspacer = new QSpacerItem(16, 0, QSizePolicy::Expanding,
242                                          QSizePolicy::Minimum);
243   QPushButton* helpButton = new QPushButton(tr("&Help"), this);
244   helpButton->setAutoDefault(false);
245   QPushButton* saveButton = new QPushButton(tr("&Save Settings"), this);
246   saveButton->setAutoDefault(false);
247   QPushButton* okButton = new QPushButton(tr("&OK"), this);
248   okButton->setAutoDefault(false);
249   QPushButton* cancelButton = new QPushButton(tr("&Cancel"), this);
250   cancelButton->setAutoDefault(false);
251   hlayout->addWidget(helpButton);
252   hlayout->addWidget(saveButton);
253   hlayout->addItem(hspacer);
254   hlayout->addWidget(okButton);
255   hlayout->addWidget(cancelButton);
256   connect(helpButton, &QAbstractButton::clicked,
257           this, &ImportDialog::showHelp);
258   connect(saveButton, &QAbstractButton::clicked,
259           this, &ImportDialog::saveConfig);
260   connect(okButton, &QAbstractButton::clicked, this, &QDialog::accept);
261   connect(cancelButton, &QAbstractButton::clicked, this, &QDialog::reject);
262   vlayout->addLayout(hlayout);
263 }
264 
265 /**
266  * Destructor.
267  */
~ImportDialog()268 ImportDialog::~ImportDialog()
269 {
270   // Must not be inline because of forwared declared QScopedPointer.
271 }
272 
273 /**
274  * Import from server and preview in table.
275  */
fromServer()276 void ImportDialog::fromServer()
277 {
278   if (m_serverComboBox)
279     displayServerImportDialog(m_serverComboBox->currentIndex());
280 }
281 
282 /**
283  * Import from text.
284  */
fromText()285 void ImportDialog::fromText()
286 {
287   if (!m_textImportDialog) {
288     m_textImportDialog.reset(new TextImportDialog(
289           m_platformTools, this, m_trackDataModel));
290     connect(m_textImportDialog.data(), &TextImportDialog::trackDataUpdated,
291             this, &ImportDialog::showPreview);
292   }
293   m_textImportDialog->clear();
294   m_textImportDialog->show();
295 }
296 
297 /**
298  * Import from tags.
299  */
fromTags()300 void ImportDialog::fromTags()
301 {
302   if (!m_tagImportDialog) {
303     m_tagImportDialog.reset(new TagImportDialog(this, m_trackDataModel));
304     connect(m_tagImportDialog.data(), &TagImportDialog::trackDataUpdated,
305             this, &ImportDialog::showPreview);
306   }
307   m_tagImportDialog->clear();
308   m_tagImportDialog->show();
309 }
310 
311 /**
312  * Display server import dialog.
313  *
314  * @param importerIdx importer index, if invalid but not negative the
315  *                    MusicBrainz Fingerprint dialog is displayed
316  */
displayServerImportDialog(int importerIdx)317 void ImportDialog::displayServerImportDialog(int importerIdx)
318 {
319   if (importerIdx >= 0) {
320     if (importerIdx < m_importers.size()) {
321       displayServerImportDialog(m_importers.at(importerIdx));
322     } else if (importerIdx - m_importers.size() < m_trackImporters.size()) {
323       displayServerTrackImportDialog(
324             m_trackImporters.at(importerIdx - m_importers.size()));
325     }
326   }
327 }
328 
329 /**
330  * Display server import dialog.
331  *
332  * @param source import source
333  */
displayServerImportDialog(ServerImporter * source)334 void ImportDialog::displayServerImportDialog(ServerImporter* source)
335 {
336   if (!m_serverImportDialog) {
337     m_serverImportDialog.reset(new ServerImportDialog(this));
338     connect(m_serverImportDialog.data(), &ServerImportDialog::trackDataUpdated,
339             this, &ImportDialog::showPreview);
340     connect(m_serverImportDialog.data(), &QDialog::accepted,
341             this, &ImportDialog::onServerImportDialogClosed);
342   }
343   m_serverImportDialog->setImportSource(source);
344   m_serverImportDialog->setArtistAlbum(
345         m_trackDataModel->trackData().getArtist(),
346         m_trackDataModel->trackData().getAlbum());
347   m_serverImportDialog->show();
348 }
349 
350 /**
351  * Import from track server and preview in table.
352  *
353  * @param source import source
354  */
displayServerTrackImportDialog(ServerTrackImporter * source)355 void ImportDialog::displayServerTrackImportDialog(ServerTrackImporter* source)
356 {
357   if (!m_serverTrackImportDialog) {
358     m_serverTrackImportDialog.reset(new ServerTrackImportDialog(this, m_trackDataModel));
359     connect(m_serverTrackImportDialog.data(), &ServerTrackImportDialog::trackDataUpdated,
360             this, &ImportDialog::showPreview);
361   }
362   m_serverTrackImportDialog->setImportSource(source);
363   m_serverTrackImportDialog->initTable();
364   m_serverTrackImportDialog->exec();
365 }
366 
367 /**
368  * Hide subdialogs.
369  */
hideSubdialogs()370 void ImportDialog::hideSubdialogs()
371 {
372   if (m_serverImportDialog)
373     m_serverImportDialog->hide();
374   if (m_textImportDialog)
375     m_textImportDialog->hide();
376   if (m_tagImportDialog)
377     m_tagImportDialog->hide();
378 }
379 
380 /**
381  * Shows the dialog as a modeless dialog.
382  *
383  * @param importerIndex index of importer to use, -1 for none
384  */
showWithSubDialog(int importerIndex)385 void ImportDialog::showWithSubDialog(int importerIndex)
386 {
387   m_autoStartSubDialog = importerIndex;
388 
389   if (importerIndex >= 0 && importerIndex < m_serverComboBox->count()) {
390     m_serverComboBox->setCurrentIndex(importerIndex);
391   }
392 
393   show();
394   if (m_autoStartSubDialog >= 0) {
395     displayServerImportDialog(m_autoStartSubDialog);
396   }
397 }
398 
399 /**
400  * Clear dialog data.
401  */
clear()402 void ImportDialog::clear()
403 {
404   const ImportConfig& importCfg = ImportConfig::instance();
405   m_serverComboBox->setCurrentIndex(importCfg.importServer());
406   Frame::TagVersion importDest = importCfg.importDest();
407   int index = m_destComboBox->findData(importDest);
408   m_destComboBox->setCurrentIndex(index);
409 
410   if (!m_trackDataModel->trackData().isTagSupported(
411         Frame::tagNumberFromMask(importDest))) {
412     index = m_destComboBox->findData(Frame::TagV2);
413     m_destComboBox->setCurrentIndex(index);
414     changeTagDestination();
415   }
416 
417   m_mismatchCheckBox->setChecked(importCfg.enableTimeDifferenceCheck());
418   m_maxDiffSpinBox->setValue(importCfg.maxTimeDifference());
419   m_columnVisibility = importCfg.importVisibleColumns();
420 
421   const auto frameTypes = checkableFrameTypes();
422   for (int frameType : frameTypes) {
423     if (frameType < 64) {
424       int column = m_trackDataModel->columnForFrameType(frameType);
425       if (column != -1) {
426         m_trackDataTable->setColumnHidden(
427               column, (m_columnVisibility & (1ULL << frameType)) == 0ULL);
428       }
429     }
430   }
431 
432   if (!importCfg.importWindowGeometry().isEmpty()) {
433     restoreGeometry(importCfg.importWindowGeometry());
434   }
435 
436   showPreview();
437 }
438 
439 /**
440  * Show fields to import in text as preview in table.
441  */
showPreview()442 void ImportDialog::showPreview()
443 {
444   // make time difference check
445   bool diffCheckEnable;
446   int maxDiff;
447   getTimeDifferenceCheck(diffCheckEnable, maxDiff);
448   m_trackDataModel->setTimeDifferenceCheck(diffCheckEnable, maxDiff);
449   m_trackDataTable->scrollToTop();
450   m_trackDataTable->resizeColumnsToContents();
451   m_trackDataTable->resizeRowsToContents();
452 
453   int accuracy = m_trackDataModel->calculateAccuracy();
454   m_accuracyPercentLabel->setText(accuracy >= 0 && accuracy <= 100
455                                   ? QString::number(accuracy) + QLatin1Char('%')
456                                   : QLatin1String("-"));
457   QUrl coverArtUrl = m_trackDataModel->getTrackData().getCoverArtUrl();
458   m_coverArtUrlLabel->setText(coverArtUrl.isEmpty() ? QLatin1String("-")
459                                                     : coverArtUrl.toString());
460 }
461 
462 /**
463  * Called when server import dialog is closed.
464  */
onServerImportDialogClosed()465 void ImportDialog::onServerImportDialogClosed()
466 {
467   // This is used to prevent that the import dialog is brought behind the
468   // main window when the server import dialog is closed, which happened
469   // with Qt 5 on Mac OS X.
470   show();
471   raise();
472   activateWindow();
473 }
474 
475 /**
476  * Get import destination.
477  *
478  * @return TagV1, TagV2 or TagV2V1 for ID3v1, ID3v2 or both.
479  */
getDestination() const480 Frame::TagVersion ImportDialog::getDestination() const
481 {
482   return Frame::tagVersionCast(
483     m_destComboBox->itemData(m_destComboBox->currentIndex()).toInt());
484 }
485 
486 /**
487  * Show help.
488  */
showHelp()489 void ImportDialog::showHelp()
490 {
491   ContextHelp::displayHelp(QLatin1String("import"));
492 }
493 
494 /**
495  * Save the local settings to the configuration.
496  */
saveConfig()497 void ImportDialog::saveConfig()
498 {
499   ImportConfig& importCfg = ImportConfig::instance();
500   importCfg.setImportDest(Frame::tagVersionCast(
501     m_destComboBox->itemData(m_destComboBox->currentIndex()).toInt()));
502 
503   importCfg.setImportServer(m_serverComboBox->currentIndex());
504   bool enable;
505   int maxDiff;
506   getTimeDifferenceCheck(enable, maxDiff);
507   importCfg.setEnableTimeDifferenceCheck(enable);
508   importCfg.setMaxTimeDifference(maxDiff);
509   importCfg.setImportVisibleColumns(m_columnVisibility);
510 
511   importCfg.setImportWindowGeometry(saveGeometry());
512 }
513 
514 /**
515  * Get time difference check configuration.
516  *
517  * @param enable  true if check is enabled
518  * @param maxDiff maximum allowed time difference
519  */
getTimeDifferenceCheck(bool & enable,int & maxDiff) const520 void ImportDialog::getTimeDifferenceCheck(bool& enable, int& maxDiff) const
521 {
522   enable = m_mismatchCheckBox->isChecked();
523   maxDiff = m_maxDiffSpinBox->value();
524 }
525 
526 /**
527  * Called when the maximum time difference value is changed.
528  */
maxDiffChanged()529 void ImportDialog::maxDiffChanged() {
530   if (m_mismatchCheckBox->isChecked()) {
531     showPreview();
532   }
533 }
534 
535 /**
536  * Move a table row.
537  *
538  * The first parameter @a section is not used.
539  * @param fromIndex index of position moved from
540  * @param fromIndex index of position moved to
541  */
moveTableRow(int,int fromIndex,int toIndex)542 void ImportDialog::moveTableRow(int, int fromIndex, int toIndex) {
543   auto vHeader = qobject_cast<QHeaderView*>(sender());
544   if (vHeader) {
545     // revert movement, but avoid recursion
546     disconnect(vHeader, &QHeaderView::sectionMoved, nullptr, nullptr);
547     vHeader->moveSection(toIndex, fromIndex);
548     connect(vHeader, &QHeaderView::sectionMoved,
549             this, &ImportDialog::moveTableRow);
550   }
551 
552   // Allow dragging multiple rows when pressing Ctrl by adding selected rows.
553   ImportTrackDataVector trackDataVector(m_trackDataModel->getTrackData());
554   auto numTracks = static_cast<int>(trackDataVector.size());
555 
556   int diff = toIndex - fromIndex;
557   QList<int> fromList;
558   if (fromIndex >= 0 && fromIndex < numTracks &&
559       toIndex >= 0 && toIndex < numTracks) {
560     fromList.append(fromIndex);
561   }
562   const QModelIndexList selectedRows =
563       m_trackDataTable->selectionModel()->selectedRows();
564   for (const QModelIndex& index : selectedRows) {
565     int from = index.row();
566     int to = from + diff;
567     if (!fromList.contains(from) &&
568         from >= 0 && from < numTracks &&
569         to >= 0 && to < numTracks) {
570       fromList.append(from);
571     }
572   }
573   std::sort(fromList.begin(), fromList.end());
574 
575   for (auto it = fromList.constBegin(); it != fromList.constEnd(); ++it) {
576     fromIndex = *it;
577     toIndex = fromIndex + diff;
578     // swap elements but keep file durations and names
579     ImportTrackData fromData(trackDataVector[fromIndex]);
580     ImportTrackData toData(trackDataVector[toIndex]);
581     trackDataVector[fromIndex].setFrameCollection(toData.getFrameCollection());
582     trackDataVector[toIndex].setFrameCollection(fromData.getFrameCollection());
583     trackDataVector[fromIndex].setImportDuration(toData.getImportDuration());
584     trackDataVector[toIndex].setImportDuration(fromData.getImportDuration());
585   }
586   if (!fromList.isEmpty()) {
587     m_trackDataModel->setTrackData(trackDataVector);
588     // redisplay the table
589     showPreview();
590   }
591 }
592 
593 /**
594  * Called when the destination combo box value is changed.
595  */
changeTagDestination()596 void ImportDialog::changeTagDestination()
597 {
598   ImportTrackDataVector trackDataVector(m_trackDataModel->getTrackData());
599   trackDataVector.readTags(getDestination());
600   m_trackDataModel->setTrackData(trackDataVector);
601   showPreview();
602 }
603 
604 /**
605  * Match import data with length.
606  */
matchWithLength()607 void ImportDialog::matchWithLength()
608 {
609   bool diffCheckEnable;
610   int maxDiff;
611   getTimeDifferenceCheck(diffCheckEnable, maxDiff);
612   if (TrackDataMatcher::matchWithLength(m_trackDataModel, diffCheckEnable, maxDiff))
613     showPreview();
614 }
615 
616 /**
617  * Match import data with track number.
618  */
matchWithTrack()619 void ImportDialog::matchWithTrack()
620 {
621   if (TrackDataMatcher::matchWithTrack(m_trackDataModel))
622     showPreview();
623 }
624 
625 /**
626  * Match import data with title.
627  */
matchWithTitle()628 void ImportDialog::matchWithTitle()
629 {
630   if (TrackDataMatcher::matchWithTitle(m_trackDataModel))
631     showPreview();
632 }
633 
634 /**
635  * Display custom context menu for horizontal table header.
636  *
637  * @param pos position where context menu is drawn on screen
638  */
showTableHeaderContextMenu(const QPoint & pos)639 void ImportDialog::showTableHeaderContextMenu(const QPoint& pos)
640 {
641   if (QWidget* widget = qobject_cast<QWidget*>(sender())) {
642     QMenu menu(widget);
643     const auto frameTypes = checkableFrameTypes();
644     for (int frameType : frameTypes) {
645       int column = m_trackDataModel->columnForFrameType(frameType);
646       if (column != -1) {
647         auto action = new QAction(&menu);
648         action->setText(
649               m_trackDataModel->headerData(column, Qt::Horizontal).toString());
650         action->setData(frameType);
651         action->setCheckable(true);
652         action->setChecked((m_columnVisibility & (1ULL << frameType)) != 0ULL);
653         connect(action, &QAction::triggered,
654                 this, &ImportDialog::toggleTableColumnVisibility);
655         menu.addAction(action);
656       }
657     }
658     menu.setMouseTracking(true);
659     menu.exec(widget->mapToGlobal(pos));
660   }
661 }
662 
663 /**
664  * Toggle visibility of table column.
665  *
666  * @param visible true to make column visible
667  */
toggleTableColumnVisibility(bool visible)668 void ImportDialog::toggleTableColumnVisibility(bool visible)
669 {
670   if (auto action = qobject_cast<QAction*>(sender())) {
671     bool ok;
672     int frameType = action->data().toInt(&ok);
673     if (ok && frameType < 64) {
674       if (visible) {
675         m_columnVisibility |= 1ULL << frameType;
676       } else {
677         m_columnVisibility &= ~(1ULL << frameType);
678       }
679       int column = m_trackDataModel->columnForFrameType(frameType);
680       if (column != -1) {
681         m_trackDataTable->setColumnHidden(column, !visible);
682       }
683     }
684     if (visible) {
685       m_trackDataTable->resizeColumnsToContents();
686     }
687   }
688 }
689