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 }