File indexing completed on 2024-05-05 17:43:14

0001 /*
0002  *   SPDX-FileCopyrightText: 2017 Ivan Cukic <ivan.cukic (at) kde.org>
0003  *
0004  *   SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005  */
0006 
0007 #ifndef PLASMAVAULT_KDED_UI_VAULT_WIZARD_BASE_P_H
0008 #define PLASMAVAULT_KDED_UI_VAULT_WIZARD_BASE_P_H
0009 
0010 #include <QPushButton>
0011 
0012 namespace PlasmaVault
0013 {
0014 class Vault;
0015 } // namespace PlasmaVault
0016 
0017 #define WBASE(Class) VaultWizardBase<Class, Ui::Class, Class::Private>
0018 
0019 template<typename Class, typename Ui, typename Impl>
0020 class VaultWizardBase
0021 {
0022 public:
0023     Class *const q;
0024     Ui ui;
0025 
0026     QPushButton *buttonPrevious;
0027     QPushButton *buttonNext;
0028     QStackedLayout *layout;
0029 
0030     bool lastModule = false;
0031     QString lastButtonText;
0032     inline void setLastModule(bool last)
0033     {
0034         lastModule = last;
0035         if (last) {
0036             buttonNext->setText(lastButtonText);
0037             buttonNext->setIcon(QIcon::fromTheme("dialog-ok-apply"));
0038         } else {
0039             buttonNext->setText(i18n("Next"));
0040             buttonNext->setIcon(QIcon::fromTheme("go-next"));
0041         }
0042     }
0043 
0044     QVector<DialogDsl::DialogModule *> currentStepModules;
0045     steps currentSteps;
0046     BackendChooserWidget *firstStepModule = nullptr;
0047     DialogDsl::DialogModule *currentModule = nullptr;
0048 
0049     // to suggest the highest priority to the user as a starting value
0050     QMap<QString, int> priorities = {
0051         {"gocryptfs", 1},
0052         {"encfs", 2},
0053         {"cryfs", 3},
0054     };
0055 
0056     template<typename ClickHandler>
0057     QPushButton *addDialogButton(const QString &icon, const QString &title, ClickHandler clickHandler)
0058     {
0059         auto button = new QPushButton(QIcon::fromTheme(icon), title);
0060         ui.buttons->addButton(button, QDialogButtonBox::ActionRole);
0061         QObject::connect(button, &QPushButton::clicked, q, clickHandler);
0062         return button;
0063     }
0064 
0065     Impl *self()
0066     {
0067         return static_cast<Impl *>(this);
0068     }
0069 
0070     VaultWizardBase(Class *parent)
0071         : q(parent)
0072     {
0073         ui.setupUi(parent);
0074         ui.message->hide();
0075 
0076         layout = new QStackedLayout();
0077         layout->setContentsMargins(0, 0, 0, 0);
0078         ui.container->setLayout(layout);
0079 
0080         lastButtonText = i18n("Create");
0081     }
0082 
0083     void initBase()
0084     {
0085         // The dialog buttons do not have previous/next by default
0086         // so we need to create them
0087         buttonPrevious = addDialogButton("go-previous", i18n("Previous"), [this] {
0088             previousStep();
0089         });
0090         buttonNext = addDialogButton("go-next", i18n("Next"), [this] {
0091             if (lastModule)
0092                 self()->finish();
0093             else
0094                 nextStep();
0095         });
0096 
0097         // The 'Create' button should be hidden by default
0098         buttonPrevious->setEnabled(false);
0099         buttonNext->setEnabled(false);
0100         buttonNext->setDefault(true);
0101 
0102         // Loading the fist page of the wizard
0103         firstStepModule = new BackendChooserWidget();
0104         setCurrentModule(firstStepModule);
0105         layout->addWidget(firstStepModule);
0106 
0107         // Loading the backends to the combo box
0108         for (const auto &key : self()->logic.keys()) {
0109             firstStepModule->addItem(key, key.translation(), priorities.value(key));
0110         }
0111         firstStepModule->checkBackendAvailable();
0112     }
0113 
0114     void setCurrentModule(DialogDsl::DialogModule *module)
0115     {
0116         // If there is a current module already, disconnect it
0117         if (currentModule) {
0118             currentModule->aboutToBeHidden();
0119             currentModule->disconnect();
0120         }
0121 
0122         // The current module needs to be changed
0123         currentModule = module;
0124         currentModule->aboutToBeShown();
0125 
0126         QObject::connect(currentModule, &DialogModule::isValidChanged, q, [&](bool valid) {
0127             buttonNext->setEnabled(valid);
0128         });
0129 
0130         // Lets update the button states
0131 
0132         // 1. next/create button is enabled only if the current
0133         //    module is in the valid state
0134         buttonNext->setEnabled(currentModule->isValid());
0135 
0136         // 2. previous button is enabled only if we are not on
0137         //    the first page
0138         buttonPrevious->setEnabled(currentStepModules.size() > 0);
0139 
0140         // 3. If we have loaded the last page, we want to show the
0141         //    'Create' button instead of 'Next'
0142         setLastModule(!currentSteps.isEmpty() && currentStepModules.size() == currentSteps.size());
0143 
0144         // Calling to initialize the module -- we are passing all the
0145         // previously collected data to it
0146         auto collectedPayload = firstStepModule == module ? PlasmaVault::Vault::Payload{} : firstStepModule->fields();
0147         for (const auto *module : currentStepModules) {
0148             collectedPayload.insert(module->fields());
0149         }
0150         currentModule->init(collectedPayload);
0151     }
0152 
0153     void previousStep()
0154     {
0155         if (currentStepModules.isEmpty())
0156             return;
0157 
0158         // We want to kill the current module, and move to the previous one
0159         currentStepModules.takeLast();
0160         currentModule->deleteLater();
0161         ;
0162 
0163         if (currentStepModules.size()) {
0164             setCurrentModule(currentStepModules.last());
0165         } else {
0166             setCurrentModule(firstStepModule);
0167         }
0168 
0169         if (!currentModule->shouldBeShown()) {
0170             previousStep();
0171         }
0172     }
0173 
0174     void nextStep()
0175     {
0176         // If the current module is not filled properly, do
0177         // not go to the next step
0178         if (currentModule && !currentModule->isValid()) {
0179             return;
0180         }
0181 
0182         // If the step modules are empty, this means that we
0183         // have just started - the user chose the backend
0184         // and we need to load the vault creation steps
0185         if (currentStepModules.isEmpty()) {
0186             const auto &fields = firstStepModule->fields();
0187             currentSteps = self()->logic[fields[KEY_BACKEND].toByteArray()];
0188         }
0189 
0190         // Loading the modules that we need to show now
0191         auto subModules = currentSteps[currentStepModules.size()];
0192 
0193         // If there is only one module on the current page,
0194         // lets not complicate things by creating the compound module
0195         DialogModule *stepWidget = (subModules.size() == 1) ? subModules.first()() : new CompoundDialogModule(subModules);
0196 
0197         // Adding the widget to the list and the layout
0198         currentStepModules << stepWidget;
0199         layout->addWidget(stepWidget);
0200         layout->setCurrentWidget(stepWidget);
0201 
0202         // Set the newly added module to be the current
0203         setCurrentModule(stepWidget);
0204 
0205         if (!currentModule->shouldBeShown()) {
0206             nextStep();
0207         }
0208     }
0209 };
0210 
0211 #endif // include guard