File indexing completed on 2021-12-21 13:26:59

0001 /* AUDEX CDDA EXTRACTOR
0002  * Copyright (C) 2007-2015 Marco Nelles (audex@maniatek.com)
0003  * <https://userbase.kde.org/Audex>
0004  *
0005  * This program is free software: you can redistribute it and/or modify
0006  * it under the terms of the GNU General Public License as published by
0007  * the Free Software Foundation, either version 3 of the License, or
0008  * (at your option) any later version.
0009  *
0010  * This program is distributed in the hope that it will be useful,
0011  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0012  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0013  * GNU General Public License for more details.
0014  *
0015  * You should have received a copy of the GNU General Public License
0016  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
0017  */
0018 
0019 #include "mainwindow.h"
0020 
0021 #include <QMenu>
0022 #include <QWidgetAction>
0023 
0024 MainWindow::MainWindow(QWidget *parent)
0025     : KXmlGuiWindow(parent)
0026 {
0027     profile_model = new ProfileModel(this);
0028     if (!profile_model) {
0029         qDebug() << "Unable to create ProfileModel object. Low mem?";
0030         ErrorDialog::show(this, i18n("Unable to create ProfileModel object."), i18n("Internal error. Check your hardware. If all okay please make bug report."));
0031         return;
0032     }
0033     if (profile_model->lastError().isValid()) {
0034         ErrorDialog::show(this, profile_model->lastError().message(), profile_model->lastError().details());
0035         return;
0036     }
0037 
0038     bool updated = firstStart();
0039 
0040     cdda_model = new CDDAModel(this);
0041     if (!cdda_model) {
0042         qDebug() << "Unable to create CDDAModel object. Low mem?";
0043         ErrorDialog::show(this, i18n("Unable to create CDDAModel object."), i18n("Internal error. Check your hardware. If all okay please make bug report."));
0044         return;
0045     }
0046     if (cdda_model->lastError().isValid()) {
0047         ErrorDialog::show(this, cdda_model->lastError().message(), cdda_model->lastError().details());
0048         return;
0049     }
0050 
0051     connect(cdda_model, SIGNAL(audioDiscDetected()), this, SLOT(new_audio_disc_detected()));
0052     connect(cdda_model, SIGNAL(audioDiscRemoved()), this, SLOT(audio_disc_removed()));
0053 
0054     connect(cdda_model, SIGNAL(cddbLookupStarted()), this, SLOT(cddb_lookup_start()));
0055     connect(cdda_model, SIGNAL(cddbLookupDone(const bool)), this, SLOT(cddb_lookup_done(const bool)));
0056     connect(cdda_model, SIGNAL(cddbDataModified()), this, SLOT(enable_submit()));
0057     connect(cdda_model, SIGNAL(cddbDataModified()), this, SLOT(update_layout()));
0058     connect(cdda_model, SIGNAL(cddbDataSubmited(bool)), this, SLOT(enable_submit(bool)));
0059 
0060     connect(profile_model, SIGNAL(profilesRemovedOrInserted()), this, SLOT(update_profile_action()));
0061     connect(profile_model, SIGNAL(currentProfileIndexChanged(int)), this, SLOT(update_profile_action(int)));
0062 
0063     setup_actions();
0064     setup_layout();
0065     setupGUI();
0066 
0067     enable_layout(false);
0068 
0069     if (updated) {
0070         update();
0071         resize(650, 500);
0072     }
0073 }
0074 
0075 bool MainWindow::firstStart()
0076 {
0077     if (Preferences::firstStart()) {
0078         profile_model->autoCreate();
0079         Preferences::setFirstStart(false);
0080         Preferences::self()->save();
0081         return true;
0082     }
0083 
0084     return false;
0085 }
0086 
0087 MainWindow::~MainWindow()
0088 {
0089     delete profile_model;
0090     delete cdda_model;
0091 }
0092 
0093 void MainWindow::eject()
0094 {
0095     qDebug() << "eject requested";
0096     cdda_model->eject();
0097 }
0098 
0099 void MainWindow::cddb_lookup()
0100 {
0101     cdda_model->lookupCDDB();
0102 }
0103 
0104 void MainWindow::cddb_submit()
0105 {
0106     if (!cdda_model->submitCDDB()) {
0107         ErrorDialog::show(this, cdda_model->lastError().message(), cdda_model->lastError().details());
0108     }
0109 }
0110 
0111 void MainWindow::rip()
0112 {
0113     if (cdda_model->empty()) {
0114         if (KMessageBox::warningYesNo(this, i18n("No disc information set. Do you really want to continue?"), i18n("Disc information not found"), KStandardGuiItem::yes(), KStandardGuiItem::no(), "no_disc_info_warn") == KMessageBox::No)
0115             return;
0116     }
0117 
0118     if ((profile_model->data(profile_model->index(profile_model->currentProfileRow(), PROFILE_MODEL_COLUMN_SF_INDEX)).toBool()) && (cdda_model->numOfAudioTracksInSelection() < cdda_model->numOfAudioTracks())) {
0119         if (KMessageBox::warningYesNo(this,
0120                                       i18n("Single file rip selected but not all audio tracks to rip selected. Do you really want to continue?"),
0121                                       i18n("Not all audio tracks selected for single file rip"),
0122                                       KStandardGuiItem::yes(),
0123                                       KStandardGuiItem::no(),
0124                                       "singlefile_selection_warn") == KMessageBox::No)
0125             return;
0126     }
0127 
0128     auto *dialog = new ExtractingProgressDialog(profile_model, cdda_model, this);
0129 
0130     dialog->setWindowModality(Qt::ApplicationModal);
0131 
0132     dialog->exec();
0133 
0134     delete dialog;
0135 }
0136 
0137 void MainWindow::configure()
0138 {
0139     if (KConfigDialog::showDialog("settings"))
0140         return;
0141 
0142     KConfigDialog *dialog = new KConfigDialog(this, "settings", Preferences::self());
0143 
0144     KPageWidgetItem *generalPage = dialog->addPage(new generalSettingsWidget(), i18n("General settings"));
0145     generalPage->setIcon(QIcon(QApplication::windowIcon()));
0146 
0147     KPageWidgetItem *devicePage = dialog->addPage(new deviceWidget(), i18n("Device settings"));
0148     devicePage->setIcon(QIcon::fromTheme("drive-optical"));
0149 
0150     KPageWidgetItem *profilePage = dialog->addPage(new profileWidget(profile_model), i18n("Profiles"));
0151     profilePage->setIcon(QIcon::fromTheme("document-multiple"));
0152 
0153     KService::Ptr libkcddb = KService::serviceByDesktopName("libkcddb");
0154     if (libkcddb && libkcddb->isValid()) {
0155         KCModuleInfo info(libkcddb->entryPath());
0156         if (info.service()->isValid()) {
0157             KCModule *m = KCModuleLoader::loadModule(info, KCModuleLoader::Inline);
0158             if (m) {
0159                 m->load();
0160                 auto *cfg = new KCDDB::Config();
0161                 cfg->load();
0162                 dialog->addPage(m, cfg, i18n("CDDB settings"), "text-xmcd");
0163                 connect(dialog, SIGNAL(okClicked()), m, SLOT(save()));
0164                 connect(dialog, SIGNAL(applyClicked()), m, SLOT(save()));
0165                 connect(dialog, SIGNAL(defaultClicked()), m, SLOT(defaults()));
0166             }
0167         }
0168     }
0169 
0170     KPageWidgetItem *remoteServerPage = dialog->addPage(new remoteServerSettingsWidget(), i18n("Remote Server"));
0171     remoteServerPage->setIcon(QIcon::fromTheme("network-server"));
0172 
0173     connect(dialog, SIGNAL(settingsChanged(const QString &)), this, SLOT(configuration_updated(const QString &)));
0174 
0175     dialog->exec();
0176 }
0177 
0178 void MainWindow::new_audio_disc_detected()
0179 {
0180     enable_layout(true);
0181     resizeColumns();
0182     if (Preferences::cddbLookupAuto()) {
0183         qDebug() << "Performing CDDB auto lookup";
0184         QTimer::singleShot(0, this, SLOT(cddb_lookup()));
0185     }
0186 
0187     update_layout();
0188 }
0189 
0190 void MainWindow::audio_disc_removed()
0191 {
0192     enable_layout(false);
0193 
0194     update_layout();
0195 }
0196 
0197 void MainWindow::cddb_lookup_start()
0198 {
0199 }
0200 
0201 void MainWindow::cddb_lookup_done(const bool successful)
0202 {
0203     if (!successful) {
0204         ErrorDialog::show(this, i18n("CDDB lookup failed, with the following error:\n%1", cdda_model->lastError().message()), cdda_model->lastError().details(), i18n("CDD Lookup Failure"));
0205     }
0206     update_layout();
0207     disable_submit();
0208     if (Preferences::coverLookupAuto())
0209         cdda_header_widget->googleAuto();
0210 }
0211 
0212 void MainWindow::update_layout()
0213 {
0214     if (!cdda_model->isVarious()) {
0215         cdda_tree_view->hideColumn(CDDA_MODEL_COLUMN_ARTIST_INDEX);
0216     } else {
0217         cdda_tree_view->showColumn(CDDA_MODEL_COLUMN_ARTIST_INDEX);
0218     }
0219     resizeColumns();
0220     actionCollection()->action("selectall")->setEnabled(cdda_model->selectedTracks().count() < cdda_model->numOfAudioTracks());
0221     actionCollection()->action("selectnone")->setEnabled(cdda_model->selectedTracks().count() > 0);
0222 }
0223 
0224 void MainWindow::enable_layout(bool enabled)
0225 {
0226     layout_enabled = enabled;
0227     cdda_tree_view->setEnabled(enabled);
0228     cdda_header_dock->setEnabled(enabled);
0229     cdda_header_widget->setEnabled(enabled);
0230     actionCollection()->action("profile_label")->setEnabled((profile_model->rowCount() > 0) && (enabled));
0231     profile_combobox->setEnabled((profile_model->rowCount() > 0) && (enabled));
0232     actionCollection()->action("profile")->setEnabled((profile_model->rowCount() > 0) && (enabled));
0233     actionCollection()->action("fetch")->setEnabled(enabled);
0234     if (cdda_model->isModified())
0235         actionCollection()->action("submit")->setEnabled(enabled);
0236     else
0237         actionCollection()->action("submit")->setEnabled(false);
0238     actionCollection()->action("eject")->setEnabled(enabled);
0239     actionCollection()->action("rip")->setEnabled(enabled);
0240     actionCollection()->action("splittitles")->setEnabled(enabled);
0241     actionCollection()->action("swapartistsandtitles")->setEnabled(enabled);
0242     actionCollection()->action("capitalize")->setEnabled(enabled);
0243     actionCollection()->action("autofillartists")->setEnabled(enabled);
0244     actionCollection()->action("selectall")->setEnabled(enabled);
0245     actionCollection()->action("selectnone")->setEnabled(enabled);
0246     actionCollection()->action("invertselection")->setEnabled(enabled);
0247 }
0248 
0249 void MainWindow::enable_submit(bool enabled)
0250 {
0251     actionCollection()->action("submit")->setEnabled(enabled);
0252 }
0253 
0254 void MainWindow::disable_submit()
0255 {
0256     actionCollection()->action("submit")->setEnabled(false);
0257 }
0258 
0259 void MainWindow::configuration_updated(const QString &dialog_name)
0260 {
0261     Q_UNUSED(dialog_name);
0262     Preferences::self()->save();
0263 }
0264 
0265 void MainWindow::current_profile_updated_from_ui(int row)
0266 {
0267     if (row >= 0) {
0268         profile_model->blockSignals(true);
0269         profile_model->setRowAsCurrentProfileIndex(row);
0270         profile_model->blockSignals(false);
0271     }
0272 }
0273 
0274 void MainWindow::update_profile_action(int index)
0275 {
0276     if (index == -1) {
0277         if (layout_enabled) {
0278             actionCollection()->action("profile_label")->setEnabled(false);
0279             actionCollection()->action("profile")->setEnabled(false);
0280         }
0281     } else {
0282         if (layout_enabled) {
0283             actionCollection()->action("profile_label")->setEnabled(true);
0284             actionCollection()->action("profile")->setEnabled(true);
0285         }
0286         profile_combobox->setCurrentIndex(profile_model->getRowByIndex(index));
0287     }
0288 }
0289 
0290 void MainWindow::update_profile_action()
0291 {
0292     // When the Profile model emits 'reset' the profile combo clears its current settings.
0293     // Therefore, we need to try and reset these...
0294     if (profile_combobox->currentText().isEmpty()) {
0295         profile_combobox->setCurrentIndex(profile_model->currentProfileRow());
0296     }
0297 
0298     if (layout_enabled) {
0299         actionCollection()->action("profile_label")->setEnabled(profile_model->rowCount() > 0);
0300         actionCollection()->action("profile")->setEnabled(profile_model->rowCount() > 0);
0301     }
0302 }
0303 
0304 void MainWindow::split_titles()
0305 {
0306     bool ok;
0307     QString divider = QInputDialog::getText(this, i18n("Split titles"), i18n("Please set a divider string. Be aware of empty spaces.\n\nDivider:"), QLineEdit::Normal, " - ", &ok);
0308     if (ok && !divider.isEmpty()) {
0309         cdda_model->splitTitleOfTracks(divider);
0310     }
0311 }
0312 
0313 void MainWindow::swap_artists_and_titles()
0314 {
0315     if (KMessageBox::warningYesNo(this, i18n("Do you really want to swap all artists and titles?"), i18n("Swap artists and titles"), KStandardGuiItem::yes(), KStandardGuiItem::no(), "no_swap_artists_and_titles_warn") == KMessageBox::No)
0316         return;
0317 
0318     cdda_model->swapArtistAndTitle();
0319     cdda_model->swapArtistAndTitleOfTracks();
0320 }
0321 
0322 void MainWindow::capitalize()
0323 {
0324     if (KMessageBox::warningYesNo(this, i18n("Do you really want to capitalize all artists and titles?"), i18n("Capitalize artists and titles"), KStandardGuiItem::yes(), KStandardGuiItem::no(), "no_capitalize_warn") == KMessageBox::No)
0325         return;
0326 
0327     cdda_model->capitalizeHeader();
0328     cdda_model->capitalizeTracks();
0329 }
0330 
0331 void MainWindow::auto_fill_artists()
0332 {
0333     if (KMessageBox::warningYesNo(this, i18n("Do you really want to autofill track artists?"), i18n("Autofill artists"), KStandardGuiItem::yes(), KStandardGuiItem::no(), "no_autofill_warn") == KMessageBox::No)
0334         return;
0335 
0336     cdda_model->setTitleArtistsFromHeader();
0337 }
0338 
0339 void MainWindow::toggle(const QModelIndex &idx)
0340 {
0341     if (idx.isValid() && (idx.column() == CDDA_MODEL_COLUMN_RIP_INDEX)) {
0342         cdda_model->toggle(idx.row());
0343         cdda_tree_view->update(idx);
0344     }
0345 }
0346 
0347 void MainWindow::resizeColumns()
0348 {
0349     for (int i = 0; i < CDDA_MODEL_COLUMN_COUNT; ++i)
0350         cdda_tree_view->resizeColumnToContents(i);
0351 }
0352 
0353 void MainWindow::setup_actions()
0354 {
0355     auto *ejectAction = new QAction(this);
0356     ejectAction->setText(i18n("Eject"));
0357     ejectAction->setIcon(QIcon::fromTheme("media-eject"));
0358     actionCollection()->addAction("eject", ejectAction);
0359     actionCollection()->setDefaultShortcut(ejectAction, Qt::CTRL + Qt::Key_E);
0360     connect(ejectAction, SIGNAL(triggered(bool)), this, SLOT(eject()));
0361 
0362     profile_label = new QLabel(this);
0363     profile_label->setText(i18n("Profile:"));
0364     profile_combobox = new KComboBox(this);
0365     profile_combobox->setModel(profile_model);
0366     profile_combobox->setModelColumn(1);
0367     profile_combobox->setMinimumWidth(80);
0368     profile_combobox->setMaximumWidth(220);
0369     profile_combobox->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed));
0370     profile_combobox->resize(QSize(220, profile_combobox->height()));
0371     profile_combobox->setCurrentIndex(profile_model->currentProfileRow());
0372     connect(profile_combobox, SIGNAL(currentIndexChanged(int)), this, SLOT(current_profile_updated_from_ui(int)));
0373 
0374     auto *plabelAction = new QWidgetAction(this);
0375     plabelAction->setText(i18n("&Profile:"));
0376     plabelAction->setDefaultWidget(profile_label);
0377     profile_label->setBuddy(profile_combobox);
0378     actionCollection()->addAction("profile_label", plabelAction);
0379 
0380     auto *profileAction = new QWidgetAction(this);
0381     profileAction->setText(i18n("Profile"));
0382     profileAction->setDefaultWidget(profile_combobox);
0383     actionCollection()->addAction("profile", profileAction);
0384     actionCollection()->setDefaultShortcut(profileAction, Qt::Key_F6);
0385     actionCollection()->setShortcutsConfigurable(profileAction, false);
0386     update_profile_action();
0387 
0388     auto *cddbLookupAction = new QAction(this);
0389     cddbLookupAction->setText(i18n("Fetch Info"));
0390     cddbLookupAction->setIcon(QIcon::fromTheme("view-list-text"));
0391     actionCollection()->addAction("fetch", cddbLookupAction);
0392     actionCollection()->setDefaultShortcut(cddbLookupAction, Qt::CTRL + Qt::Key_F);
0393     connect(cddbLookupAction, SIGNAL(triggered(bool)), this, SLOT(cddb_lookup()));
0394 
0395     auto *cddbSubmitAction = new QAction(this);
0396     cddbSubmitAction->setText(i18n("Submit Info"));
0397     actionCollection()->addAction("submit", cddbSubmitAction);
0398     actionCollection()->setDefaultShortcut(cddbSubmitAction, Qt::CTRL + Qt::Key_S);
0399     connect(cddbSubmitAction, SIGNAL(triggered(bool)), this, SLOT(cddb_submit()));
0400 
0401     auto *extractAction = new QAction(this);
0402     extractAction->setText(i18n("Rip..."));
0403     extractAction->setIcon(QIcon::fromTheme("media-optical-audio"));
0404     actionCollection()->addAction("rip", extractAction);
0405     actionCollection()->setDefaultShortcut(extractAction, Qt::CTRL + Qt::Key_X);
0406     connect(extractAction, SIGNAL(triggered(bool)), this, SLOT(rip()));
0407 
0408     actionCollection()->addAction("preferences", KStandardAction::preferences(this, SLOT(configure()), this));
0409 
0410     auto *splitTitlesAction = new QAction(this);
0411     splitTitlesAction->setText(i18n("Split Titles..."));
0412     actionCollection()->addAction("splittitles", splitTitlesAction);
0413     connect(splitTitlesAction, SIGNAL(triggered(bool)), this, SLOT(split_titles()));
0414 
0415     auto *swapArtistsAndTitlesAction = new QAction(this);
0416     swapArtistsAndTitlesAction->setText(i18n("Swap Artists And Titles"));
0417     actionCollection()->addAction("swapartistsandtitles", swapArtistsAndTitlesAction);
0418     connect(swapArtistsAndTitlesAction, SIGNAL(triggered(bool)), this, SLOT(swap_artists_and_titles()));
0419 
0420     auto *capitalizeAction = new QAction(this);
0421     capitalizeAction->setText(i18n("Capitalize"));
0422     actionCollection()->addAction("capitalize", capitalizeAction);
0423     connect(capitalizeAction, SIGNAL(triggered(bool)), this, SLOT(capitalize()));
0424 
0425     auto *autoFillArtistsAction = new QAction(this);
0426     autoFillArtistsAction->setText(i18n("Auto Fill Artists"));
0427     actionCollection()->addAction("autofillartists", autoFillArtistsAction);
0428     connect(autoFillArtistsAction, SIGNAL(triggered(bool)), this, SLOT(auto_fill_artists()));
0429 
0430     auto *selectAllAction = new QAction(this);
0431     selectAllAction->setText(i18n("Select All Tracks"));
0432     actionCollection()->addAction("selectall", selectAllAction);
0433     connect(selectAllAction, SIGNAL(triggered(bool)), this, SLOT(select_all()));
0434 
0435     auto *selectNoneAction = new QAction(this);
0436     selectNoneAction->setText(i18n("Deselect All Tracks"));
0437     actionCollection()->addAction("selectnone", selectNoneAction);
0438     connect(selectNoneAction, SIGNAL(triggered(bool)), this, SLOT(select_none()));
0439 
0440     auto *invertSelectionAction = new QAction(this);
0441     invertSelectionAction->setText(i18n("Invert Selection"));
0442     actionCollection()->addAction("invertselection", invertSelectionAction);
0443     connect(invertSelectionAction, SIGNAL(triggered(bool)), this, SLOT(invert_selection()));
0444 
0445     KStandardAction::quit(qApp, SLOT(quit()), actionCollection());
0446 }
0447 
0448 void MainWindow::setup_layout()
0449 {
0450     cdda_tree_view = new QTreeView(this);
0451     cdda_tree_view->setModel(cdda_model);
0452     cdda_tree_view->setAlternatingRowColors(true);
0453     cdda_tree_view->setSelectionBehavior(QAbstractItemView::SelectRows);
0454     cdda_tree_view->setEditTriggers(QAbstractItemView::EditKeyPressed | QAbstractItemView::DoubleClicked);
0455     cdda_tree_view->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
0456     cdda_tree_view->setIndentation(0);
0457     cdda_tree_view->setAllColumnsShowFocus(true);
0458     cdda_tree_view->setContextMenuPolicy(Qt::CustomContextMenu);
0459     connect(cdda_tree_view, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(cdda_context_menu(const QPoint &)));
0460     connect(cdda_tree_view, SIGNAL(clicked(const QModelIndex &)), SLOT(toggle(const QModelIndex &)));
0461     // connect(cdda_model, SIGNAL(discInfoChanged(const CDDAModel::DiscInfo)), SLOT(resizeColumns()));
0462     connect(cdda_model, SIGNAL(selectionChanged(const int)), this, SLOT(selection_changed(const int)));
0463 
0464     cdda_header_dock = new QDockWidget(this);
0465     cdda_header_dock->setObjectName("cdda_header_dock");
0466     cdda_header_dock->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable);
0467     cdda_header_dock->setAllowedAreas(Qt::AllDockWidgetAreas);
0468 
0469     setCentralWidget(cdda_tree_view);
0470     cdda_header_widget = new CDDAHeaderWidget(cdda_model, cdda_header_dock);
0471     connect(cdda_header_widget, SIGNAL(headerDataChanged()), this, SLOT(update_layout()));
0472     cdda_header_dock->setWidget(cdda_header_widget);
0473     addDockWidget(Qt::LeftDockWidgetArea, cdda_header_dock);
0474 
0475     statusBar()->hide();
0476     statusBar()->setMaximumHeight(0);
0477 }
0478 
0479 void MainWindow::select_all()
0480 {
0481     cdda_model->selectAll();
0482 }
0483 
0484 void MainWindow::select_none()
0485 {
0486     cdda_model->selectNone();
0487 }
0488 
0489 void MainWindow::invert_selection()
0490 {
0491     cdda_model->invertSelection();
0492 }
0493 
0494 void MainWindow::cdda_context_menu(const QPoint &pos)
0495 {
0496     Q_UNUSED(pos);
0497     QMenu menu(this);
0498     menu.addAction(actionCollection()->action("selectall"));
0499     menu.addAction(actionCollection()->action("selectnone"));
0500     menu.addSeparator();
0501     menu.addAction(actionCollection()->action("invertselection"));
0502     menu.exec(QCursor::pos());
0503 }
0504 
0505 void MainWindow::selection_changed(const int num_selected)
0506 {
0507     actionCollection()->action("rip")->setEnabled(num_selected > 0);
0508     actionCollection()->action("selectall")->setEnabled(num_selected < cdda_model->numOfAudioTracks());
0509     actionCollection()->action("selectnone")->setEnabled(num_selected > 0);
0510 }