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

0001 /**
0002  * \file batchimportdialog.cpp
0003  * Batch import dialog.
0004  *
0005  * \b Project: Kid3
0006  * \author Urs Fleisch
0007  * \date 2 Jan 2013
0008  *
0009  * Copyright (C) 2013-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 "batchimportdialog.h"
0028 #include <QHBoxLayout>
0029 #include <QVBoxLayout>
0030 #include <QSplitter>
0031 #include <QTextEdit>
0032 #include <QComboBox>
0033 #include <QPushButton>
0034 #include <QLabel>
0035 #include <QString>
0036 #include <QTableView>
0037 #include <QHeaderView>
0038 #include "batchimporter.h"
0039 #include "batchimportsourcedialog.h"
0040 #include "batchimportconfig.h"
0041 #include "batchimportsourcesmodel.h"
0042 #include "serverimporter.h"
0043 #include "contexthelp.h"
0044 #include "abstractlistedit.h"
0045 
0046 namespace {
0047 
0048 /**
0049  * Widget to edit a list of import sources.
0050  */
0051 class BatchImportSourceListEdit : public AbstractListEdit {
0052 public:
0053   /**
0054    * Constructor.
0055    *
0056    * @param model item model
0057    * @param parent parent widget
0058    */
0059   explicit BatchImportSourceListEdit(BatchImportSourcesModel* model,
0060                                      QWidget* parent = nullptr);
0061 
0062   /**
0063    * Destructor.
0064    */
0065   ~BatchImportSourceListEdit() override = default;
0066 
0067   /**
0068    * Set names of import servers.
0069    * @param servers server names
0070    */
0071   void setServerNames(const QStringList& servers) {
0072     m_serverNames = servers;
0073   }
0074 
0075   /**
0076    * Add a new item.
0077    */
0078   void addItem() override;
0079 
0080   /**
0081    * Edit the selected item.
0082    */
0083   void editItem() override;
0084 
0085 private:
0086   Q_DISABLE_COPY(BatchImportSourceListEdit)
0087 
0088   QTableView* m_tableView;
0089   QStringList m_serverNames;
0090 };
0091 
0092 /**
0093  * Constructor.
0094  *
0095  * @param model item model, e.g. a QStringListModel
0096  * @param parent parent widget
0097  */
0098 BatchImportSourceListEdit::BatchImportSourceListEdit(
0099     BatchImportSourcesModel* model, QWidget* parent)
0100   : AbstractListEdit(m_tableView = new QTableView, model, parent)
0101 {
0102   setObjectName(QLatin1String("BatchImportSourceListEdit"));
0103   m_tableView->setSelectionBehavior(QAbstractItemView::SelectRows);
0104   m_tableView->horizontalHeader()->setSectionResizeMode(
0105         QHeaderView::ResizeToContents);
0106 }
0107 
0108 /**
0109  * Add a new item.
0110  */
0111 void BatchImportSourceListEdit::addItem()
0112 {
0113   auto dialog = new BatchImportSourceDialog(this);
0114   dialog->setServerNames(m_serverNames);
0115   if (dialog->exec() == QDialog::Accepted) {
0116     BatchImportProfile::Source source;
0117     dialog->getSource(source);
0118     if (auto model =
0119         qobject_cast<BatchImportSourcesModel*>(getItemView()->model())) {
0120       int row = model->rowCount();
0121       model->insertRow(row);
0122       model->setBatchImportSource(row, source);
0123     }
0124   }
0125 }
0126 
0127 /**
0128  * Edit the selected item.
0129  */
0130 void BatchImportSourceListEdit::editItem()
0131 {
0132   if (QModelIndex index = getItemView()->currentIndex(); index.isValid()) {
0133     if (auto model =
0134         qobject_cast<BatchImportSourcesModel*>(getItemView()->model())) {
0135       BatchImportProfile::Source source;
0136       model->getBatchImportSource(index.row(), source);
0137       auto dialog = new BatchImportSourceDialog(this);
0138       dialog->setServerNames(m_serverNames);
0139       dialog->setSource(source);
0140       if (dialog->exec() == QDialog::Accepted) {
0141         dialog->getSource(source);
0142         model->setBatchImportSource(index.row(), source);
0143       }
0144     }
0145   }
0146 }
0147 
0148 }
0149 
0150 /**
0151  * Constructor.
0152  *
0153  * @param importers server importers
0154  * @param parent parent widget
0155  */
0156 BatchImportDialog::BatchImportDialog(const QList<ServerImporter*>& importers,
0157                                      QWidget* parent) :
0158   QDialog(parent), m_importers(importers), m_profileIdx(-1),
0159   m_isAbortButton(false)
0160 {
0161   setObjectName(QLatin1String("BatchImportDialog"));
0162   setWindowTitle(tr("Automatic Import"));
0163   setSizeGripEnabled(true);
0164 
0165   auto vlayout = new QVBoxLayout(this);
0166   auto splitter = new QSplitter(Qt::Vertical);
0167 
0168   m_edit = new QTextEdit(this);
0169   m_edit->setReadOnly(true);
0170   m_edit->setAcceptRichText(false);
0171   splitter->addWidget(m_edit);
0172 
0173   auto profileWidget = new QWidget;
0174   auto profileLayout = new QVBoxLayout(profileWidget);
0175   profileLayout->setContentsMargins(0, 0, 0, 0);
0176 
0177   auto destLayout = new QHBoxLayout;
0178   auto destLabel = new QLabel(tr("D&estination:"));
0179   destLayout->addWidget(destLabel);
0180   m_destComboBox = new QComboBox;
0181   m_destComboBox->setEditable(false);
0182   const QList<QPair<Frame::TagVersion, QString> > tagVersions =
0183       Frame::availableTagVersions();
0184   for (auto it = tagVersions.constBegin(); it != tagVersions.constEnd(); ++it) {
0185     m_destComboBox->addItem(it->second, it->first);
0186   }
0187   destLabel->setBuddy(m_destComboBox);
0188   destLayout->addWidget(m_destComboBox);
0189   destLayout->addStretch();
0190   profileLayout->addLayout(destLayout);
0191 
0192   auto nameLayout = new QHBoxLayout;
0193   auto profileLabel = new QLabel(tr("&Profile:"));
0194   nameLayout->addWidget(profileLabel);
0195   m_profileComboBox = new QComboBox;
0196   m_profileComboBox->setEditable(true);
0197   connect(m_profileComboBox, static_cast<void (QComboBox::*)(int)>(
0198             &QComboBox::activated),
0199           this, &BatchImportDialog::changeProfile);
0200   connect(m_profileComboBox, &QComboBox::editTextChanged,
0201           this, &BatchImportDialog::changeProfileName);
0202   profileLabel->setBuddy(m_profileComboBox);
0203   nameLayout->addWidget(m_profileComboBox, 1);
0204   auto profileAddButton = new QPushButton(tr("Add"));
0205   connect(profileAddButton, &QAbstractButton::clicked,
0206           this, &BatchImportDialog::addProfile);
0207   nameLayout->addWidget(profileAddButton);
0208   auto profileRemoveButton = new QPushButton(tr("Remove"));
0209   connect(profileRemoveButton, &QAbstractButton::clicked,
0210           this, &BatchImportDialog::removeProfile);
0211   nameLayout->addWidget(profileRemoveButton);
0212   profileLayout->addLayout(nameLayout);
0213 
0214   QStringList servers;
0215   servers.reserve(m_importers.size());
0216   const auto sis = m_importers;
0217   for (const ServerImporter* si : sis) {
0218     servers.append(QString::fromLatin1(si->name()));
0219   }
0220   m_profileModel = new BatchImportSourcesModel(this);
0221   auto profileListEdit =
0222       new BatchImportSourceListEdit(m_profileModel, this);
0223   profileListEdit->setServerNames(servers);
0224   profileLayout->addWidget(profileListEdit);
0225   splitter->addWidget(profileWidget);
0226   vlayout->addWidget(splitter);
0227 
0228   auto hlayout = new QHBoxLayout;
0229   auto helpButton = new QPushButton(tr("&Help"), this);
0230   helpButton->setAutoDefault(false);
0231   hlayout->addWidget(helpButton);
0232   connect(helpButton, &QAbstractButton::clicked,
0233           this, &BatchImportDialog::showHelp);
0234 
0235   auto saveButton = new QPushButton(tr("&Save Settings"), this);
0236   saveButton->setAutoDefault(false);
0237   hlayout->addWidget(saveButton);
0238   connect(saveButton, &QAbstractButton::clicked,
0239           this, &BatchImportDialog::saveConfig);
0240   hlayout->addStretch();
0241 
0242   m_startAbortButton = new QPushButton(this);
0243   setAbortButton(false);
0244   auto closeButton = new QPushButton(tr("&Close"), this);
0245   m_startAbortButton->setAutoDefault(false);
0246   m_startAbortButton->setDefault(true);
0247   closeButton->setAutoDefault(false);
0248   hlayout->addWidget(m_startAbortButton);
0249   hlayout->addWidget(closeButton);
0250   connect(m_startAbortButton, &QAbstractButton::clicked,
0251           this, &BatchImportDialog::startOrAbortImport);
0252   connect(closeButton, &QAbstractButton::clicked, this, &QDialog::reject);
0253   connect(this, &QDialog::rejected, this, &BatchImportDialog::abort);
0254 
0255   vlayout->addLayout(hlayout);
0256 }
0257 
0258 /**
0259  * Start or abort batch import.
0260  */
0261 void BatchImportDialog::startOrAbortImport()
0262 {
0263   if (m_isAbortButton) {
0264     emit abort();
0265   } else {
0266     setProfileFromGuiControls();
0267     if (m_profileIdx >= 0 && m_profileIdx < m_profiles.size()) {
0268       m_edit->clear();
0269       m_currentProfile = m_profiles.at(m_profileIdx);
0270       emit start(
0271             m_currentProfile,
0272             Frame::tagVersionCast(
0273               m_destComboBox->itemData(m_destComboBox->currentIndex()).toInt()));
0274     }
0275   }
0276 }
0277 
0278 /**
0279  * Add a new profile to the list of profiles.
0280  */
0281 void BatchImportDialog::addNewProfile()
0282 {
0283   BatchImportProfile profile;
0284   profile.setName(tr("New"));
0285   m_profiles.append(profile);
0286   m_profileIdx = m_profiles.size() - 1;
0287 }
0288 
0289 /**
0290  * Add a new profile.
0291  */
0292 void BatchImportDialog::addProfile()
0293 {
0294   setProfileFromGuiControls();
0295   // First search for an existing empty profile.
0296   for (int index = 0; index < m_profiles.size(); ++index) {
0297     if (m_profiles.at(index).getSources().isEmpty()) {
0298       m_profileIdx = index;
0299       setGuiControlsFromProfile();
0300       return;
0301     }
0302   }
0303 
0304   addNewProfile();
0305   setGuiControlsFromProfile();
0306 }
0307 
0308 /**
0309  * Remove the selected profile.
0310  */
0311 void BatchImportDialog::removeProfile()
0312 {
0313   if (int index = m_profileComboBox->currentIndex();
0314       index >= 0 && index < m_profiles.size()) {
0315     m_profiles.removeAt(index);
0316     if (m_profileIdx >= m_profiles.size())
0317       m_profileIdx = m_profiles.size() - 1;
0318     setGuiControlsFromProfile();
0319   }
0320 }
0321 
0322 /**
0323  * Switch to different profile.
0324  * @param index combo box index to set
0325  */
0326 void BatchImportDialog::changeProfile(int index)
0327 {
0328   setProfileFromGuiControls();
0329   m_profileIdx = index;
0330   setGuiControlsFromProfile();
0331 }
0332 
0333 /**
0334  * Change name of current profile.
0335  * @param name profile name
0336  */
0337 void BatchImportDialog::changeProfileName(const QString& name)
0338 {
0339   if (int index = m_profileComboBox->currentIndex();
0340       index >= 0 && index < m_profiles.size()) {
0341     m_profiles[index].setName(name);
0342     m_profileComboBox->setItemText(index, name);
0343   }
0344 }
0345 
0346 /**
0347  * Update profile from GUI controls.
0348  */
0349 void BatchImportDialog::setProfileFromGuiControls()
0350 {
0351   QList<BatchImportProfile::Source> sources =
0352       m_profileModel->getBatchImportSources();
0353   if (m_profiles.isEmpty() && !sources.isEmpty()) {
0354     addNewProfile();
0355     m_profileComboBox->setEditText(m_profiles.at(0).getName());
0356   }
0357   if (m_profileIdx >= 0 && m_profileIdx < m_profiles.size()) {
0358     BatchImportProfile& profile = m_profiles[m_profileIdx]; // clazy:exclude=detaching-member
0359     profile.setSources(sources);
0360   }
0361 }
0362 
0363 /**
0364  * Update GUI controls from profiles.
0365  */
0366 void BatchImportDialog::setGuiControlsFromProfile()
0367 {
0368   if (m_profiles.isEmpty()) {
0369     m_profileIdx = -1;
0370     m_profileComboBox->clear();
0371     m_profileModel->setBatchImportSources(QList<BatchImportProfile::Source>());
0372     return;
0373   }
0374 
0375   if (m_profileIdx < 0 || m_profileIdx >= m_profiles.size())
0376     m_profileIdx = 0;
0377   m_profileModel->setBatchImportSources(m_profiles.at(m_profileIdx).getSources());
0378   if (m_profileComboBox->count() == m_profiles.size()) {
0379     m_profileComboBox->setItemText(m_profileIdx,
0380                                    m_profiles.at(m_profileIdx).getName());
0381   } else {
0382     m_profileComboBox->clear();
0383     const auto profiles = m_profiles;
0384     for (const BatchImportProfile& profile : profiles) {
0385       m_profileComboBox->addItem(profile.getName());
0386     }
0387   }
0388   m_profileComboBox->setCurrentIndex(m_profileIdx);
0389 }
0390 
0391 /**
0392  * Set the filter combo box and line edit from the configuration.
0393  */
0394 void BatchImportDialog::setProfileFromConfig()
0395 {
0396   const BatchImportConfig& batchImportCfg = BatchImportConfig::instance();
0397   const QStringList names = batchImportCfg.profileNames();
0398   const QStringList sources = batchImportCfg.profileSources();
0399 
0400   m_profiles.clear();
0401   for (auto namesIt = names.constBegin(), sourcesIt = sources.constBegin();
0402        namesIt != names.constEnd() && sourcesIt != sources.constEnd();
0403        ++namesIt, ++sourcesIt) {
0404     BatchImportProfile profile;
0405     profile.setName(*namesIt);
0406     profile.setSourcesFromString(*sourcesIt);
0407     m_profiles.append(profile);
0408   }
0409   m_profileIdx = batchImportCfg.profileIndex();
0410   setGuiControlsFromProfile();
0411 }
0412 
0413 /**
0414  * Read the local settings from the configuration.
0415  */
0416 void BatchImportDialog::readConfig()
0417 {
0418   m_edit->clear();
0419   setAbortButton(false);
0420 
0421   const BatchImportConfig& batchImportCfg = BatchImportConfig::instance();
0422   Frame::TagVersion importDest = batchImportCfg.importDest();
0423   int index = m_destComboBox->findData(importDest);
0424   m_destComboBox->setCurrentIndex(index);
0425 
0426   setProfileFromConfig();
0427 
0428   if (!batchImportCfg.windowGeometry().isEmpty()) {
0429     restoreGeometry(batchImportCfg.windowGeometry());
0430   }
0431 }
0432 
0433 /**
0434  * Save the local settings to the configuration.
0435  */
0436 void BatchImportDialog::saveConfig()
0437 {
0438   BatchImportConfig& batchImportCfg = BatchImportConfig::instance();
0439   batchImportCfg.setImportDest(Frame::tagVersionCast(
0440     m_destComboBox->itemData(m_destComboBox->currentIndex()).toInt()));
0441 
0442   QStringList names, sources;
0443   setProfileFromGuiControls();
0444   names.reserve(m_profiles.size());
0445   sources.reserve(m_profiles.size());
0446   const auto profiles = m_profiles;
0447   for (const BatchImportProfile& profile : profiles) {
0448     names.append(profile.getName());
0449     sources.append(profile.getSourcesAsString());
0450   }
0451   batchImportCfg.setProfileNames(names);
0452   batchImportCfg.setProfileSources(sources);
0453   batchImportCfg.setProfileIndex(m_profileComboBox->currentIndex());
0454   batchImportCfg.setWindowGeometry(saveGeometry());
0455 }
0456 
0457 /**
0458  * Show help.
0459  */
0460 void BatchImportDialog::showHelp()
0461 {
0462   ContextHelp::displayHelp(QLatin1String("batch-import"));
0463 }
0464 
0465 /**
0466  * Show information about import event.
0467  * @param type import event type, enum BatchImporter::ImportEventType
0468  * @param text text to display
0469  */
0470 void BatchImportDialog::showImportEvent(int type, const QString& text)
0471 {
0472   QString eventText;
0473   switch (type) {
0474   case BatchImporter::ReadingDirectory:
0475     setAbortButton(true);
0476     eventText = tr("Reading Folder");
0477     break;
0478   case BatchImporter::Started:
0479     setAbortButton(true);
0480     eventText = tr("Started");
0481     break;
0482   case BatchImporter::SourceSelected:
0483     eventText = tr("Source");
0484     break;
0485   case BatchImporter::QueryingAlbumList:
0486     eventText = tr("Querying");
0487     break;
0488   case BatchImporter::FetchingTrackList:
0489   case BatchImporter::FetchingCoverArt:
0490     eventText = tr("Fetching");
0491     break;
0492   case BatchImporter::TrackListReceived:
0493     eventText = tr("Data received");
0494     break;
0495   case BatchImporter::CoverArtReceived:
0496     eventText = tr("Cover");
0497     break;
0498   case BatchImporter::Finished:
0499     setAbortButton(false);
0500     eventText = tr("Finished");
0501     break;
0502   case BatchImporter::Aborted:
0503     setAbortButton(false);
0504     eventText = tr("Aborted");
0505     break;
0506   case BatchImporter::Error:
0507     eventText = tr("Error");
0508   }
0509   if (!text.isEmpty()) {
0510     eventText += QLatin1String(": ");
0511     eventText += text;
0512   }
0513   m_edit->append(eventText);
0514 }
0515 
0516 /**
0517  * Set button to Start or Abort.
0518  * @param enableAbort true to set Abort button
0519  */
0520 void BatchImportDialog::setAbortButton(bool enableAbort)
0521 {
0522   m_isAbortButton = enableAbort;
0523   m_startAbortButton->setText(m_isAbortButton ? tr("A&bort") : tr("S&tart"));
0524 }