diff --git a/src/configdialog.cpp b/src/configdialog.cpp index 89777b0..e018b63 100644 --- a/src/configdialog.cpp +++ b/src/configdialog.cpp @@ -1,302 +1,320 @@ /* SPDX-FileCopyrightText: 2014-2023 Anne Jan Brouwer SPDX-FileCopyrightText: 2018 Claudio Maradonna SPDX-FileCopyrightText: 2023 g10 Code GmbH SPDX-FileContributor: Sune Stolborg Vuorela SPDX-License-Identifier: GPL-3.0-or-later */ #include "configdialog.h" #include "mainwindow.h" #include "settings.h" #include "ui_configdialog.h" #include "util.h" #include #include #include #include #include #include #include /** * @brief ConfigDialog::ConfigDialog this sets up the configuration screen. * @param parent */ ConfigDialog::ConfigDialog(MainWindow *parent) : QDialog(parent) , ui(new Ui::ConfigDialog) { mainWindow = parent; ui->setupUi(this); - ui->storePath->setText(Settings::getPassStore()); + ui->profileStorePath->setText(Settings::getPassStore()); + ui->currentPathLineEdit->setText(Settings::getPassStore()); ui->spinBoxAutoclearSeconds->setValue(Settings::getAutoclearSeconds()); ui->spinBoxAutoclearPanelSeconds->setValue(Settings::getAutoclearPanelSeconds()); ui->checkBoxHideContent->setChecked(Settings::isHideContent()); ui->checkBoxDisplayAsIs->setChecked(Settings::isDisplayAsIs()); ui->checkBoxNoLineWrapping->setChecked(Settings::isNoLineWrapping()); ui->plainTextEditTemplate->setPlainText(Settings::getPassTemplate()); ui->checkBoxTemplateAllFields->setChecked(Settings::isTemplateAllFields()); setProfiles(Settings::getProfiles(), Settings::getProfile()); setPasswordConfiguration(Settings::getPasswordConfiguration()); useAutoclear(Settings::isUseAutoclear()); useAutoclearPanel(Settings::isUseAutoclearPanel()); + validateNewProfile(); + useTemplate(Settings::isUseTemplate()); ui->profileTable->verticalHeader()->hide(); ui->profileTable->horizontalHeader()->setStretchLastSection(true); ui->label->setText(ui->label->text() + QString::fromUtf8(GPGPASS_VERSION_STRING)); - connect(ui->profileTable, &QTableWidget::itemChanged, this, &ConfigDialog::onProfileTableItemChanged); + // Generic setup connect(this, &ConfigDialog::accepted, this, &ConfigDialog::on_accepted); + + // General tab connect(ui->toolButtonStore, &QAbstractButton::clicked, this, &ConfigDialog::selectStoreFolder); - connect(ui->checkBoxUseTemplate, &QAbstractButton::toggled, this, &ConfigDialog::toggleTemplateSubentries); connect(ui->checkBoxAutoclearPanel, &QAbstractButton::toggled, this, &ConfigDialog::toggleAutoClearPanelSubentries); + + // Profiles tab connect(ui->addButton, &QAbstractButton::clicked, this, &ConfigDialog::addProfile); connect(ui->deleteButton, &QAbstractButton::clicked, this, &ConfigDialog::deleteSelectedProfile); + connect(ui->createNewProfileFolder, &QAbstractButton::toggled, this, &ConfigDialog::validateNewProfile); + connect(ui->newProfileNameEdit, &QLineEdit::textChanged, this, &ConfigDialog::validateNewProfile); + connect(ui->profileStorePath, &QLineEdit::textChanged, this, &ConfigDialog::validateNewProfile); + connect(ui->profileTable, &QTableWidget::itemSelectionChanged, this, [this]() { + ui->deleteButton->setEnabled(!ui->profileTable->selectedItems().isEmpty()); + }); + + // template tab + connect(ui->checkBoxUseTemplate, &QAbstractButton::toggled, this, &ConfigDialog::toggleTemplateSubentries); } -ConfigDialog::~ConfigDialog() = default; - -void ConfigDialog::validate(QTableWidgetItem *item) +bool ConfigDialog::checkNewProfileData() { - bool status = true; - - if (item == nullptr) { - for (int i = 0; i < ui->profileTable->rowCount(); i++) { - for (int j = 0; j < ui->profileTable->columnCount(); j++) { - QTableWidgetItem *_item = ui->profileTable->item(i, j); - - if (_item->text().isEmpty()) { - _item->setBackground(Qt::red); - status = false; - break; - } - } - - if (!status) - break; - } + QString name = ui->newProfileNameEdit->text(); + if (name.isEmpty()) { + return false; + } + QString path = ui->profileStorePath->text(); + if (path.isEmpty()) { + return false; + } + auto nameItem = ui->profileTable->findItems(name, Qt::MatchExactly); + if (!nameItem.empty()) { + return false; + } + auto pathItem = ui->profileTable->findItems(path, Qt::MatchExactly); + if (!pathItem.empty()) { + return false; + } + QFileInfo fi(path); + if (fi.exists()) { + return fi.isDir(); } else { - if (item->text().isEmpty()) { - item->setBackground(Qt::red); - status = false; - } + return ui->createNewProfileFolder->isChecked(); } +} - ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(status); +void ConfigDialog::validateNewProfile() +{ + ui->addButton->setEnabled(checkNewProfileData()); } +ConfigDialog::~ConfigDialog() = default; + void ConfigDialog::on_accepted() { - Settings::setPassStore(Util::normalizeFolderPath(ui->storePath->text())); + Settings::setPassStore(Util::normalizeFolderPath(ui->currentPathLineEdit->text())); Settings::setUseAutoclear(ui->checkBoxAutoclear->isChecked()); Settings::setAutoclearSeconds(ui->spinBoxAutoclearSeconds->value()); Settings::setUseAutoclearPanel(ui->checkBoxAutoclearPanel->isChecked()); Settings::setAutoclearPanelSeconds(ui->spinBoxAutoclearPanelSeconds->value()); Settings::setHideContent(ui->checkBoxHideContent->isChecked()); Settings::setDisplayAsIs(ui->checkBoxDisplayAsIs->isChecked()); Settings::setNoLineWrapping(ui->checkBoxNoLineWrapping->isChecked()); Settings::setProfiles(getProfiles()); Settings::setPasswordConfiguration(getPasswordConfiguration()); Settings::setUseTemplate(ui->checkBoxUseTemplate->isChecked()); Settings::setPassTemplate(ui->plainTextEditTemplate->toPlainText()); Settings::setTemplateAllFields(ui->checkBoxTemplateAllFields->isChecked()); Settings::setVersion(QString::fromUtf8(GPGPASS_VERSION_STRING)); } /** * @brief ConfigDialog::selectFolder pop-up to choose a folder. * @return */ QString ConfigDialog::selectFolder() { QFileDialog dialog(this); dialog.setFileMode(QFileDialog::Directory); dialog.setFilter(QDir::NoFilter); dialog.setOption(QFileDialog::ShowDirsOnly); if (dialog.exec()) return dialog.selectedFiles().constFirst(); return QString(); } /** * @brief ConfigDialog::on_toolButtonStore_clicked get .password-store * location.s */ void ConfigDialog::selectStoreFolder() { QString store = selectFolder(); - if (!store.isEmpty()) // TODO(annejan) call check - ui->storePath->setText(store); + if (!store.isEmpty()) + ui->profileStorePath->setText(store); } /** * @brief ConfigDialog::on_checkBoxAutoclearPanel_clicked enable and disable * options based on autoclear use. */ void ConfigDialog::toggleAutoClearPanelSubentries(bool state) { ui->spinBoxAutoclearPanelSeconds->setEnabled(state); ui->labelPanelSeconds->setEnabled(state); } /** * @brief ConfigDialog::useAutoclear set the clipboard autoclear use from * MainWindow. * @param useAutoclear */ void ConfigDialog::useAutoclear(bool useAutoclear) { ui->checkBoxAutoclear->setChecked(useAutoclear); } /** * @brief ConfigDialog::useAutoclearPanel set the panel autoclear use from * MainWindow. * @param useAutoclearPanel */ void ConfigDialog::useAutoclearPanel(bool useAutoclearPanel) { ui->checkBoxAutoclearPanel->setChecked(useAutoclearPanel); toggleAutoClearPanelSubentries(useAutoclearPanel); } +static std::unique_ptr createTableWidgetItem(const QString &str) +{ + auto item = std::make_unique(str); + item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); + return item; +} + /** * @brief ConfigDialog::setProfiles set the profiles and chosen profile from * MainWindow. * @param profiles * @param profile */ void ConfigDialog::setProfiles(const QHash& profiles, const QString& profile) { ui->profileTable->clear(); ui->profileTable->setSortingEnabled(false); QHashIterator i(profiles); while (i.hasNext()) { i.next(); if (!i.value().isEmpty() && !i.key().isEmpty()) { ui->profileTable->insertRow(0); - ui->profileTable->setItem(0, 0, new QTableWidgetItem(i.key())); - ui->profileTable->setItem(0, 1, new QTableWidgetItem(i.value())); + ui->profileTable->setItem(0, 0, createTableWidgetItem(i.key()).release()); + ui->profileTable->setItem(0, 1, createTableWidgetItem(i.value()).release()); } } auto selected = ui->profileTable->findItems(profile, Qt::MatchExactly); if (!selected.empty()) { ui->profileTable->selectRow(selected.front()->row()); } ui->profileTable->setSortingEnabled(true); } /** * @brief ConfigDialog::getProfiles return profile list. * @return */ QHash ConfigDialog::getProfiles() { QHash profiles; // Check? for (int i = 0; i < ui->profileTable->rowCount(); ++i) { QTableWidgetItem *pathItem = ui->profileTable->item(i, 1); if (nullptr != pathItem) { QTableWidgetItem *item = ui->profileTable->item(i, 0); if (item == nullptr) { continue; } profiles.insert(item->text(), pathItem->text()); } } return profiles; } /** * @brief ConfigDialog::on_addButton_clicked add a profile row. */ void ConfigDialog::addProfile() { + if (!checkNewProfileData()) { + return; + } ui->profileTable->setSortingEnabled(false); int n = ui->profileTable->rowCount(); ui->profileTable->insertRow(n); - ui->profileTable->setItem(n, 0, new QTableWidgetItem()); - ui->profileTable->setItem(n, 1, new QTableWidgetItem(ui->storePath->text())); + ui->profileTable->setItem(n, 0, createTableWidgetItem(ui->newProfileNameEdit->text()).release()); + ui->profileTable->setItem(n, 1, createTableWidgetItem(ui->profileStorePath->text()).release()); ui->profileTable->selectRow(n); ui->deleteButton->setEnabled(true); ui->profileTable->setSortingEnabled(true); - - validate(); } /** * @brief ConfigDialog::on_deleteButton_clicked remove a profile row. */ void ConfigDialog::deleteSelectedProfile() { QSet selectedRows; // we use a set to prevent doubles const QList itemList = ui->profileTable->selectedItems(); if (itemList.count() == 0) { QMessageBox::warning(this, i18n("No profile selected"), i18n("No profile selected to delete")); return; } ui->profileTable->setSortingEnabled(false); for (const auto *item : itemList) selectedRows.insert(item->row()); // get a list, and sort it big to small QList rows = selectedRows.values(); std::sort(rows.begin(), rows.end()); // now actually do the removing: for (int row : qAsConst(rows)) ui->profileTable->removeRow(row); if (ui->profileTable->rowCount() < 1) ui->deleteButton->setEnabled(false); ui->profileTable->setSortingEnabled(true); - - validate(); } void ConfigDialog::setPasswordConfiguration(const PasswordConfiguration &config) { ui->spinBoxPasswordLength->setValue(config.length); ui->passwordCharTemplateSelector->setCurrentIndex(config.selected); } PasswordConfiguration ConfigDialog::getPasswordConfiguration() { PasswordConfiguration config; config.length = ui->spinBoxPasswordLength->value(); config.selected = static_cast(ui->passwordCharTemplateSelector->currentIndex()); return config; } /** * @brief ConfigDialog::on_checkBoxUseTemplate_clicked enable or disable the * template field and options. */ void ConfigDialog::toggleTemplateSubentries(bool enable) { ui->plainTextEditTemplate->setEnabled(enable); ui->checkBoxTemplateAllFields->setEnabled(enable); } -void ConfigDialog::onProfileTableItemChanged(QTableWidgetItem *item) -{ - validate(item); -} - /** * @brief ConfigDialog::useTemplate set preference for using templates. * @param useTemplate */ void ConfigDialog::useTemplate(bool useTemplate) { ui->checkBoxUseTemplate->setChecked(useTemplate); toggleTemplateSubentries(useTemplate); } diff --git a/src/configdialog.h b/src/configdialog.h index c85d18c..819a840 100644 --- a/src/configdialog.h +++ b/src/configdialog.h @@ -1,68 +1,66 @@ /* SPDX-FileCopyrightText: 2014-2023 Anne Jan Brouwer SPDX-FileCopyrightText: 2018 Claudio Maradonna SPDX-FileCopyrightText: 2023 g10 Code GmbH SPDX-FileContributor: Sune Stolborg Vuorela SPDX-License-Identifier: GPL-3.0-or-later */ #ifndef CONFIGDIALOG_H_ #define CONFIGDIALOG_H_ #include "passwordconfiguration.h" #include namespace Ui { struct UserInfo; class ConfigDialog; } // namespace Ui /*! \class ConfigDialog \brief The ConfigDialog handles the configuration interface. This class should also take the handling from the MainWindow class. */ class MainWindow; class QCloseEvent; class QTableWidgetItem; class ConfigDialog : public QDialog { Q_OBJECT public: explicit ConfigDialog(MainWindow *parent); ~ConfigDialog(); - void useAutoclear(bool useAutoclear); - void useAutoclearPanel(bool useAutoclearPanel); - QHash getProfiles(); - void setPasswordConfiguration(const PasswordConfiguration &config); - PasswordConfiguration getPasswordConfiguration(); - void useTemplate(bool useTemplate); - private Q_SLOTS: void on_accepted(); void selectStoreFolder(); void addProfile(); void deleteSelectedProfile(); void toggleAutoClearPanelSubentries(bool enable); void toggleTemplateSubentries(bool enable); - - void onProfileTableItemChanged(QTableWidgetItem *item); + void validateNewProfile(); private: + void useAutoclear(bool useAutoclear); + void useAutoclearPanel(bool useAutoclearPanel); + QHash getProfiles(); + void setPasswordConfiguration(const PasswordConfiguration &config); + PasswordConfiguration getPasswordConfiguration(); + void useTemplate(bool useTemplate); QScopedPointer ui; void setProfiles(const QHash&, const QString&); QString selectFolder(); - void validate(QTableWidgetItem *item = nullptr); + bool checkNewProfileData(); MainWindow *mainWindow; }; #endif // CONFIGDIALOG_H_ diff --git a/src/configdialog.ui b/src/configdialog.ui index 86eb08e..bc4499f 100644 --- a/src/configdialog.ui +++ b/src/configdialog.ui @@ -1,647 +1,690 @@ ConfigDialog 0 0 659 728 0 0 Configuration 6 6 6 6 0 Settings 6 6 6 6 75 true Clipboard behaviour: Autoclear after: 5 10 Seconds Qt::Horizontal 40 20 75 true Content panel behaviour: 0 Hide content Autoclear panel after: 5 10 55 0 Seconds Qt::Horizontal 40 20 Display the files content as-is No line wrapping Qt::Horizontal 40 20 75 true Password Generation: 0 Password Length: true 50 0 8 255 16 true Characters Qt::Horizontal QSizePolicy::Expanding 40 20 true Use characters: 0 0 0 Select character set for password generation All Characters Alphabetical Alphanumerical Qt::Horizontal 40 20 0 Qt::Vertical 20 40 Profiles - - - 6 - - - 6 - - - 6 - - - 6 - + 0 0 QFrame::NoFrame QFrame::Plain 0 QAbstractItemView::DoubleClicked|QAbstractItemView::EditKeyPressed|QAbstractItemView::SelectedClicked true QAbstractItemView::SingleSelection QAbstractItemView::SelectRows true Name Path - - - - - Add - - - - document-new - - - - Qt::ToolButtonFollowStyle - - - - - - - Delete - - - - edit-delete - - - - Qt::ToolButtonFollowStyle - - - - - - - Current path - - + + + + + + + Delete selected profile + + + + edit-delete + + + + Qt::ToolButtonFollowStyle + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + - - - - + + + + Qt::Vertical - - + + + + + + Name: + + + + + + + + + + Path: + + + + + + + + + + + + + + + + + + + + + Add profile + + + + document-new + + + + Qt::ToolButtonFollowStyle + + + + + + + Create folder if not exists + + + + + + + Current Path: + + + + + + + Template 6 6 6 6 Templates add extra fields in the password generation dialogue, and in the password view. Use template Show all lines beginning with a word followed by a colon as fields in password fields, not only the listed ones Show all fields templated QFrame::Plain login URL e-mail GnuPG Password Manager version true Qt::Horizontal 40 20 Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok tabWidget profileTable - addButton - deleteButton - storePath - toolButtonStore buttonBox rejected() ConfigDialog reject() 556 518 286 214 buttonBox accepted() ConfigDialog accept() 556 518 157 214