diff --git a/kded/ui/dialogdsl.cpp b/kded/ui/dialogdsl.cpp index 124b84d..a28b618 100644 --- a/kded/ui/dialogdsl.cpp +++ b/kded/ui/dialogdsl.cpp @@ -1,158 +1,159 @@ /* * Copyright 2017 by Ivan Cukic * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "dialogdsl.h" #include namespace DialogDsl { Key::Key(const QByteArray &key, const QString &translation) : QByteArray(key) , m_translation(translation) { } QString Key::translation() const { return m_translation; } DialogModule::DialogModule(bool valid) : m_isValid(valid) { } void DialogModule::setIsValid(bool valid) { if (valid == m_isValid) return; m_isValid = valid; emit isValidChanged(valid); } bool DialogModule::shouldBeShown() const { return true; } void DialogModule::aboutToBeShown() { } void DialogModule::aboutToBeHidden() { } bool DialogModule::isValid() const { return m_isValid; } void DialogModule::init(const PlasmaVault::Vault::Payload &payload) { + Q_UNUSED(payload); } CompoundDialogModule::CompoundDialogModule(const step &children) : DialogDsl::DialogModule(false) { auto layout = new QVBoxLayout(this); setLayout(layout); for (const auto& childFactory: children) { auto child = childFactory(); child->setParent(this); child->setSizePolicy( QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred)); m_children << child; if (!child->isValid()) { m_invalidChildren << child; } connect(child, &DialogModule::isValidChanged, this, [this, child] (bool valid) { if (valid) { m_invalidChildren.remove(child); } else { m_invalidChildren << child; } setIsValid(m_invalidChildren.isEmpty()); }); connect(child, &DialogModule::requestCancellation, this, &DialogModule::requestCancellation); layout->addWidget(child); } setIsValid(m_invalidChildren.isEmpty()); auto spacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Expanding); layout->addSpacerItem(spacer); } PlasmaVault::Vault::Payload CompoundDialogModule::fields() const { PlasmaVault::Vault::Payload result; for (const auto& child: m_children) { result.unite(child->fields()); } return result; } void CompoundDialogModule::init(const PlasmaVault::Vault::Payload &payload) { for (const auto& child: m_children) { child->init(payload); } } } // namespace DialogDsl diff --git a/kded/ui/directorypairchooserwidget.cpp b/kded/ui/directorypairchooserwidget.cpp index 110c991..818ee9e 100644 --- a/kded/ui/directorypairchooserwidget.cpp +++ b/kded/ui/directorypairchooserwidget.cpp @@ -1,182 +1,216 @@ /* * Copyright 2017 by Ivan Cukic * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "directorypairchooserwidget.h" #include "ui_directorypairchooserwidget.h" #include "vault.h" #include "backend_p.h" #include class DirectoryPairChooserWidget::Private { public: Ui::DirectoryPairChooserWidget ui; const DirectoryPairChooserWidget::Flags flags; DirectoryPairChooserWidget *const q; class DirectoryValidator { public: - bool requireNew; + bool requireEmptyDirectory; bool requireExisting; bool valid = false; + QString defaultPath; std::function update; - DirectoryValidator(bool requireNew, bool requireExisting, + KMessageWidget* widget = nullptr; + + DirectoryValidator(bool requireEmptyDirectory, bool requireExisting, + QString defaultPath, std::function update) - : requireNew(requireNew) + : requireEmptyDirectory(requireEmptyDirectory) , requireExisting(requireExisting) - , valid(!requireNew && !requireExisting) + , valid(!requireEmptyDirectory && !requireExisting) + , defaultPath(defaultPath) , update(update) { } bool isValid(const QUrl &url) const { - if (url.isEmpty()) return false; + if (url.isEmpty()) { + widget->hide(); + return false; + } - const bool directoryExists - = PlasmaVault::Backend::directoryExists(url.toLocalFile()); + if (url.toLocalFile() == defaultPath) { + widget->hide(); + return true; + } + + QDir dir(url.toLocalFile()); - if (requireNew && directoryExists) { + if (!dir.exists()) { + widget->setText(i18n("The specified path does not exist")); + widget->show(); return false; } - if (requireExisting && !directoryExists) { + if (requireEmptyDirectory && !dir.isEmpty()) { + widget->setText(i18n("The specified directory is not empty")); + widget->show(); return false; } + if (requireExisting && dir.isEmpty()) { + widget->setText(i18n("The specified directory is empty")); + widget->show(); + return false; + } + + widget->hide(); return true; } void updateFor(const QUrl &url) { bool newValid = isValid(url); if (valid != newValid) { valid = newValid; update(); } } }; DirectoryValidator deviceValidator; DirectoryValidator mountPointValidator; bool allValid; void updateValidity() { bool newAllValid = deviceValidator.valid && mountPointValidator.valid; if (allValid != newAllValid) { allValid = newAllValid; q->setIsValid(allValid); } } Private(DirectoryPairChooserWidget *parent, DirectoryPairChooserWidget::Flags flags) : flags(flags) , q(parent) , deviceValidator( - flags & RequireNewDevice, + flags & RequireEmptyDevice, flags & RequireExistingDevice, + nullptr, [&] { updateValidity(); } ) , mountPointValidator( - flags & RequireNewMountPoint, + flags & RequireEmptyMountPoint, flags & RequireExistingMountPoint, + nullptr, [&] { updateValidity(); } ) , allValid(deviceValidator.valid && mountPointValidator.valid) { } }; DirectoryPairChooserWidget::DirectoryPairChooserWidget( DirectoryPairChooserWidget::Flags flags) : DialogDsl::DialogModule(false), d(new Private(this, flags)) { d->ui.setupUi(this); + d->deviceValidator.widget = d->ui.messageDevice; + d->mountPointValidator.widget = d->ui.messageMountPoint; + if (!(flags & DirectoryPairChooserWidget::ShowDevicePicker)) { d->ui.editDevice->setVisible(false); d->ui.labelDevice->setVisible(false); } if (!(flags & DirectoryPairChooserWidget::ShowMountPointPicker)) { d->ui.editMountPoint->setVisible(false); d->ui.labelMountPoint->setVisible(false); } + d->ui.messageDevice->hide(); + d->ui.messageMountPoint->hide(); + connect(d->ui.editDevice, &KUrlRequester::textEdited, this, [&] () { d->deviceValidator.updateFor(d->ui.editDevice->url()); }); connect(d->ui.editMountPoint, &KUrlRequester::textEdited, this, [&] () { d->mountPointValidator.updateFor(d->ui.editMountPoint->url()); }); } DirectoryPairChooserWidget::~DirectoryPairChooserWidget() { } PlasmaVault::Vault::Payload DirectoryPairChooserWidget::fields() const { return { { KEY_DEVICE, d->ui.editDevice->url().toLocalFile() }, { KEY_MOUNT_POINT, d->ui.editMountPoint->url().toLocalFile() } }; } void DirectoryPairChooserWidget::init( const PlasmaVault::Vault::Payload &payload) { if (d->flags & DirectoryPairChooserWidget::AutoFillPaths) { const QString basePath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/plasma-vault"); const auto name = payload[KEY_NAME].toString(); Q_ASSERT(!name.isEmpty()); QString path = QString("%1/%2.enc").arg(basePath).arg(name); int index = 1; while (QDir(path).exists()) { path = QString("%1/%2_%3.enc").arg(basePath).arg(name).arg(index++); } d->ui.editDevice->setText(path); d->ui.editMountPoint->setText(QDir::homePath() + QStringLiteral("/Vaults/") + name); + + d->deviceValidator.defaultPath = path; + d->mountPointValidator.defaultPath = QDir::homePath() + QStringLiteral("/Vaults/") + name; } d->deviceValidator.updateFor(d->ui.editDevice->url()); d->mountPointValidator.updateFor(d->ui.editMountPoint->url()); setIsValid(d->allValid); } diff --git a/kded/ui/directorypairchooserwidget.h b/kded/ui/directorypairchooserwidget.h index f9252b6..31b09a2 100644 --- a/kded/ui/directorypairchooserwidget.h +++ b/kded/ui/directorypairchooserwidget.h @@ -1,64 +1,64 @@ /* * Copyright 2017 by Ivan Cukic * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef PLASMAVAULT_KDED_UI_DIRECTORY_PAIR_CHOOSER_WIDGET_H #define PLASMAVAULT_KDED_UI_DIRECTORY_PAIR_CHOOSER_WIDGET_H #include "dialogdsl.h" class DirectoryPairChooserWidget: public DialogDsl::DialogModule { Q_OBJECT public: enum Flag { ShowDevicePicker = 1, ShowMountPointPicker = 2, - RequireNewDevice = 4, + RequireEmptyDevice = 4, RequireExistingDevice = 8, - RequireNewMountPoint = 16, + RequireEmptyMountPoint = 16, RequireExistingMountPoint = 32, AutoFillPaths = 64 }; Q_DECLARE_FLAGS(Flags, Flag) DirectoryPairChooserWidget(Flags flags); ~DirectoryPairChooserWidget(); PlasmaVault::Vault::Payload fields() const override; void init(const PlasmaVault::Vault::Payload &payload) override; private: class Private; QScopedPointer d; }; inline DialogDsl::ModuleFactory directoryPairChooser(DirectoryPairChooserWidget::Flags flags) { return [=] { return new DirectoryPairChooserWidget(flags); }; } Q_DECLARE_OPERATORS_FOR_FLAGS(DirectoryPairChooserWidget::Flags) #endif // include guard diff --git a/kded/ui/directorypairchooserwidget.ui b/kded/ui/directorypairchooserwidget.ui index d72b5d0..c52b27c 100644 --- a/kded/ui/directorypairchooserwidget.ui +++ b/kded/ui/directorypairchooserwidget.ui @@ -1,53 +1,103 @@ DirectoryPairChooserWidget 0 0 653 - 82 + 166 Encrypted data location KFile::Directory|KFile::LocalOnly - + Mount point - + KFile::Directory|KFile::LocalOnly + + + + + 0 + 0 + + + + false + + + KMessageWidget::Error + + + + + + + + 0 + 0 + + + + false + + + KMessageWidget::Error + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + KUrlRequester QWidget
kurlrequester.h
+ + KMessageWidget + QFrame +
kmessagewidget.h
+
diff --git a/kded/ui/namechooserwidget.cpp b/kded/ui/namechooserwidget.cpp index e7a0f73..29e49a0 100644 --- a/kded/ui/namechooserwidget.cpp +++ b/kded/ui/namechooserwidget.cpp @@ -1,77 +1,78 @@ /* * Copyright 2017 by Ivan Cukic * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "namechooserwidget.h" #include "ui_namechooserwidget.h" #include "vault.h" class NameChooserWidget::Private { public: Ui::NameChooserWidget ui; NameChooserWidget *const q; Private(NameChooserWidget *parent) : q(parent) { } }; NameChooserWidget::NameChooserWidget() : DialogDsl::DialogModule(false), d(new Private(this)) { d->ui.setupUi(this); connect(d->ui.editVaultName, &QLineEdit::textChanged, this, [this] (const QString &text) { + Q_UNUSED(text); setIsValid(!d->ui.editVaultName->text().isEmpty()); }); } NameChooserWidget::~NameChooserWidget() { } PlasmaVault::Vault::Payload NameChooserWidget::fields() const { return { { KEY_NAME, d->ui.editVaultName->text() } }; } void NameChooserWidget::init( const PlasmaVault::Vault::Payload &payload) { const auto name = payload[KEY_NAME].toString(); d->ui.editVaultName->setText(name); setIsValid(!d->ui.editVaultName->text().isEmpty()); } diff --git a/kded/ui/vaultcreationwizard.cpp b/kded/ui/vaultcreationwizard.cpp index d5810a8..1e26c78 100644 --- a/kded/ui/vaultcreationwizard.cpp +++ b/kded/ui/vaultcreationwizard.cpp @@ -1,323 +1,323 @@ /* * Copyright 2017 by Ivan Cukic * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "vaultcreationwizard.h" #include "ui_vaultcreationwizard.h" #include #include #include #include #include "dialogdsl.h" #include "vault.h" using namespace DialogDsl; using namespace DialogDsl::operators; #include "backendchooserwidget.h" #include "activitieslinkingwidget.h" #include "cryfscypherchooserwidget.h" #include "directorypairchooserwidget.h" #include "noticewidget.h" #include "passwordchooserwidget.h" #include "offlineonlywidget.h" class VaultCreationWizard::Private { public: VaultCreationWizard *const q; Ui::VaultCreationWizard ui; QPushButton *buttonPrevious; QPushButton *buttonNext; QPushButton *buttonCreate; QStackedLayout *layout; inline void buttonNextSetEnabled(bool enabled) { buttonNext->setEnabled(enabled); buttonCreate->setEnabled(enabled); } QVector currentStepModules; steps currentSteps; BackendChooserWidget *firstStepModule = nullptr; DialogDsl::DialogModule *currentModule = nullptr; Logic logic { { "encfs" / i18n("EncFS"), { step { notice("encfs-message", i18n("Security notice:\n\ According to a security audit by Taylor Hornby (Defuse Security),\n\ the current implementation of Encfs is vulnerable or potentially vulnerable\n\ to multiple types of attacks.\n\ For example, an attacker with read/write access\n\ to encrypted data might lower the decryption complexity\n\ for subsequently encrypted data without this being noticed by a legitimate user,\n\ or might use timing analysis to deduce information.\n\

\n\ This means that you should not synchronize\n\ the encrypted data to a cloud storage service,\n\ or use it in other circumstances where the attacker\n\ can frequently access the encrypted data.\n\

\n\ See defuse.ca/audits/encfs.htm for more information.")) }, step { passwordChooser() }, step { directoryPairChooser( DirectoryPairChooserWidget::AutoFillPaths | DirectoryPairChooserWidget::ShowMountPointPicker | - DirectoryPairChooserWidget::RequireNewMountPoint + DirectoryPairChooserWidget::RequireEmptyMountPoint ) }, step { activitiesChooser(), offlineOnlyChooser() } } }, { "cryfs" / i18n("CryFS"), { step { notice("cryfs-message", i18n("Security notice:\n\ CryFS encrypts your files, so you can safely store them anywhere.\n\ It works well together with cloud services like Dropbox, iCloud, OneDrive and others.\n\

\n\ Unlike some other file-system overlay solutions,\n\ it does not expose the directory structure,\n\ the number of files nor the file sizes\n\ through the encrypted data format.\n\

\n\ One important thing to note is that,\n\ while CryFS is considered safe,\n\ there is no independent security audit\n\ which confirms this.")) }, step { passwordChooser() }, step { directoryPairChooser( DirectoryPairChooserWidget::AutoFillPaths | DirectoryPairChooserWidget::ShowDevicePicker | DirectoryPairChooserWidget::ShowMountPointPicker | - DirectoryPairChooserWidget::RequireNewDevice | - DirectoryPairChooserWidget::RequireNewMountPoint + DirectoryPairChooserWidget::RequireEmptyDevice | + DirectoryPairChooserWidget::RequireEmptyMountPoint ) }, step { cryfsCypherChooser(), activitiesChooser(), offlineOnlyChooser() } } } }; // to suggest the highest priority to the user as a starting value QMap priorities = { { "encfs", 1 }, { "cryfs", 2 } }; template QPushButton *addDialogButton(const QString &icon, const QString &title, ClickHandler clickHandler) { auto button = new QPushButton(QIcon::fromTheme(icon), title); ui.buttons->addButton(button, QDialogButtonBox::ActionRole); QObject::connect(button, &QPushButton::clicked, q, clickHandler); return button; } Private(VaultCreationWizard *parent) : q(parent) { ui.setupUi(parent); ui.message->hide(); layout = new QStackedLayout(); layout->setContentsMargins(0, 0, 0, 0); ui.container->setLayout(layout); // The dialog buttons do not have previous/next by default // so we need to create them buttonPrevious = addDialogButton("go-previous", i18n("Previous"), [this] { previousStep(); }); buttonNext = addDialogButton("go-next", i18n("Next"), [this] { nextStep(); }); buttonCreate = addDialogButton("dialog-ok-apply", i18n("Create"), [this] { createVault(); }); // The 'Create' button should be hidden by default buttonCreate->hide(); buttonPrevious->setEnabled(false); buttonNextSetEnabled(false); // Loading the fist page of the wizard firstStepModule = new BackendChooserWidget(); setCurrentModule(firstStepModule); layout->addWidget(firstStepModule); // Loading the backends to the combo box for (const auto& key: logic.keys()) { firstStepModule->addItem(key, key.translation(), priorities.value(key)); } firstStepModule->checkBackendAvailable(); } void setCurrentModule(DialogDsl::DialogModule *module) { // If there is a current module already, disconnect it if (currentModule) { currentModule->aboutToBeHidden(); currentModule->disconnect(); } // The current module needs to be changed currentModule = module; currentModule->aboutToBeShown(); QObject::connect( currentModule, &DialogModule::isValidChanged, q, [&] (bool valid) { buttonNextSetEnabled(valid); }); // Lets update the button states // 1. next/create button is enabled only if the current // module is in the valid state buttonNextSetEnabled(currentModule->isValid()); // 2. previous button is enabled only if we are not on // the first page buttonPrevious->setEnabled(currentStepModules.size() > 0); // 3. If we have loaded the last page, we want to show the // 'Create' button instead of 'Next' if (!currentSteps.isEmpty() && currentStepModules.size() == currentSteps.size()) { buttonNext->hide(); buttonCreate->show(); } else { buttonNext->show(); buttonCreate->hide(); } // Calling to initialize the module -- we are passing all the // previously collected data to it auto collectedPayload = firstStepModule->fields(); for (const auto* module: currentStepModules) { collectedPayload.unite(module->fields()); } currentModule->init(collectedPayload); } void previousStep() { if (currentStepModules.isEmpty()) return; // We want to kill the current module, and move to the previous one currentStepModules.takeLast(); currentModule->deleteLater();; if (currentStepModules.size()) { setCurrentModule(currentStepModules.last()); } else { setCurrentModule(firstStepModule); } if (!currentModule->shouldBeShown()) { previousStep(); } } void nextStep() { // If the step modules are empty, this means that we // have just started - the user chose the backend // and we need to load the vault creation steps if (currentStepModules.isEmpty()) { const auto &fields = firstStepModule->fields(); currentSteps = logic[fields[KEY_BACKEND].toByteArray()]; } // Loading the modulws that we need to show now auto subModules = currentSteps[currentStepModules.size()]; // If there is only one module on the current page, // lets not complicate things by creating the compound module DialogModule *stepWidget = (subModules.size() == 1) ? subModules.first()() : new CompoundDialogModule(subModules); // Adding the widget to the list and the layout currentStepModules << stepWidget; layout->addWidget(stepWidget); layout->setCurrentWidget(stepWidget); // Set the newly added module to be the current setCurrentModule(stepWidget); if (!currentModule->shouldBeShown()) { nextStep(); } } void createVault() { auto collectedPayload = firstStepModule->fields(); for (const auto* module: currentStepModules) { collectedPayload.unite(module->fields()); } const auto name = collectedPayload[KEY_NAME].toString(); const PlasmaVault::Device device(collectedPayload[KEY_DEVICE].toString()); const PlasmaVault::MountPoint mountPoint(collectedPayload[KEY_MOUNT_POINT].toString()); auto vault = new PlasmaVault::Vault(device, q); auto future = vault->create(name, mountPoint, collectedPayload); auto result = AsynQt::await(future); if (result) { emit q->createdVault(vault); q->QDialog::accept(); } else { ui.message->setText(result.error().message()); ui.message->setMessageType(KMessageWidget::Error); ui.message->show(); vault->scheduleDeletion(); } } }; VaultCreationWizard::VaultCreationWizard(QWidget *parent) : QDialog(parent) , d(new Private(this)) { setWindowTitle(i18nc("@title:window", "Create a New Vault")); } VaultCreationWizard::~VaultCreationWizard() { } diff --git a/kded/ui/vaultimportingwizard.cpp b/kded/ui/vaultimportingwizard.cpp index b39185b..58f215d 100644 --- a/kded/ui/vaultimportingwizard.cpp +++ b/kded/ui/vaultimportingwizard.cpp @@ -1,290 +1,290 @@ /* * Copyright 2017 by Ivan Cukic * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "vaultimportingwizard.h" #include "ui_vaultimportingwizard.h" #include #include #include #include #include "dialogdsl.h" #include "vault.h" using namespace DialogDsl; using namespace DialogDsl::operators; #include "backendchooserwidget.h" #include "activitieslinkingwidget.h" #include "cryfscypherchooserwidget.h" #include "directorypairchooserwidget.h" #include "noticewidget.h" #include "passwordchooserwidget.h" #include "offlineonlywidget.h" class VaultImportingWizard::Private { public: VaultImportingWizard *const q; Ui::VaultImportingWizard ui; QPushButton *buttonPrevious; QPushButton *buttonNext; QPushButton *buttonImport; QStackedLayout *layout; inline void buttonNextSetEnabled(bool enabled) { buttonNext->setEnabled(enabled); buttonImport->setEnabled(enabled); } QVector currentStepModules; steps currentSteps; BackendChooserWidget *firstStepModule = nullptr; DialogDsl::DialogModule *currentModule = nullptr; Logic logic { { "encfs" / i18n("EncFS"), { step { directoryPairChooser( DirectoryPairChooserWidget::ShowDevicePicker | DirectoryPairChooserWidget::ShowMountPointPicker | DirectoryPairChooserWidget::RequireExistingDevice | - DirectoryPairChooserWidget::RequireNewMountPoint + DirectoryPairChooserWidget::RequireEmptyMountPoint ) }, step { passwordChooser() }, step { activitiesChooser(), offlineOnlyChooser() } } }, { "cryfs" / i18n("CryFS"), { step { directoryPairChooser( DirectoryPairChooserWidget::ShowDevicePicker | DirectoryPairChooserWidget::ShowMountPointPicker | DirectoryPairChooserWidget::RequireExistingDevice | - DirectoryPairChooserWidget::RequireNewMountPoint + DirectoryPairChooserWidget::RequireEmptyMountPoint ) }, step { passwordChooser() }, step { activitiesChooser(), offlineOnlyChooser() } } } }; // to suggest the highest priority to the user as a starting value QMap priorities = { { "encfs", 1 }, { "cryfs", 2 } }; template QPushButton *addDialogButton(const QString &icon, const QString &title, ClickHandler clickHandler) { auto button = new QPushButton(QIcon::fromTheme(icon), title); ui.buttons->addButton(button, QDialogButtonBox::ActionRole); QObject::connect(button, &QPushButton::clicked, q, clickHandler); return button; } Private(VaultImportingWizard *parent) : q(parent) { ui.setupUi(parent); ui.message->hide(); layout = new QStackedLayout(); layout->setContentsMargins(0, 0, 0, 0); ui.container->setLayout(layout); // The dialog buttons do not have previous/next by default // so we need to create them buttonPrevious = addDialogButton("go-previous", i18n("Previous"), [this] { previousStep(); }); buttonNext = addDialogButton("go-next", i18n("Next"), [this] { nextStep(); }); buttonImport = addDialogButton("dialog-ok-apply", i18n("Import"), [this] { importVault(); }); // The 'Import' button should be hidden by default buttonImport->hide(); buttonPrevious->setEnabled(false); buttonNextSetEnabled(false); // Loading the fist page of the wizard firstStepModule = new BackendChooserWidget(); setCurrentModule(firstStepModule); layout->addWidget(firstStepModule); // Loading the backends to the combo box for (const auto& key: logic.keys()) { firstStepModule->addItem(key, key.translation(), priorities.value(key)); } firstStepModule->checkBackendAvailable(); } void setCurrentModule(DialogDsl::DialogModule *module) { // If there is a current module already, disconnect it if (currentModule) { currentModule->aboutToBeHidden(); currentModule->disconnect(); } // The current module needs to be changed currentModule = module; currentModule->aboutToBeShown(); QObject::connect( currentModule, &DialogModule::isValidChanged, q, [&] (bool valid) { buttonNextSetEnabled(valid); }); // Lets update the button states // 1. next/Import button is enabled only if the current // module is in the valid state buttonNextSetEnabled(currentModule->isValid()); // 2. previous button is enabled only if we are not on // the first page buttonPrevious->setEnabled(currentStepModules.size() > 0); // 3. If we have loaded the last page, we want to show the // 'Import' button instead of 'Next' if (!currentSteps.isEmpty() && currentStepModules.size() == currentSteps.size()) { buttonNext->hide(); buttonImport->show(); } else { buttonNext->show(); buttonImport->hide(); } // Calling to initialize the module -- we are passing all the // previously collected data to it auto collectedPayload = firstStepModule->fields(); for (const auto* module: currentStepModules) { collectedPayload.unite(module->fields()); } currentModule->init(collectedPayload); } void previousStep() { if (currentStepModules.isEmpty()) return; // We want to kill the current module, and move to the previous one currentStepModules.takeLast(); currentModule->deleteLater();; if (currentStepModules.size()) { setCurrentModule(currentStepModules.last()); } else { setCurrentModule(firstStepModule); } if (!currentModule->shouldBeShown()) { previousStep(); } } void nextStep() { // If the step modules are empty, this means that we // have just started - the user chose the backend // and we need to load the vault creation steps if (currentStepModules.isEmpty()) { const auto &fields = firstStepModule->fields(); currentSteps = logic[fields[KEY_BACKEND].toByteArray()]; } // Loading the modulws that we need to show now auto subModules = currentSteps[currentStepModules.size()]; // If there is only one module on the current page, // lets not complicate things by creating the compound module DialogModule *stepWidget = (subModules.size() == 1) ? subModules.first()() : new CompoundDialogModule(subModules); // Adding the widget to the list and the layout currentStepModules << stepWidget; layout->addWidget(stepWidget); layout->setCurrentWidget(stepWidget); // Set the newly added module to be the current setCurrentModule(stepWidget); if (!currentModule->shouldBeShown()) { nextStep(); } } void importVault() { auto collectedPayload = firstStepModule->fields(); for (const auto* module: currentStepModules) { collectedPayload.unite(module->fields()); } const auto name = collectedPayload[KEY_NAME].toString(); const PlasmaVault::Device device(collectedPayload[KEY_DEVICE].toString()); const PlasmaVault::MountPoint mountPoint(collectedPayload[KEY_MOUNT_POINT].toString()); auto vault = new PlasmaVault::Vault(device, q); auto future = vault->import(name, mountPoint, collectedPayload); auto result = AsynQt::await(future); if (result) { emit q->importedVault(vault); q->QDialog::accept(); } else { ui.message->setText(result.error().message()); ui.message->setMessageType(KMessageWidget::Error); ui.message->show(); vault->scheduleDeletion(); } } }; VaultImportingWizard::VaultImportingWizard(QWidget *parent) : QDialog(parent) , d(new Private(this)) { setWindowTitle(i18nc("@title:window", "Import an Existing Vault")); } VaultImportingWizard::~VaultImportingWizard() { }