QtPass Developer documentation
QtPass test coverage
Current view: top level - src/firstrunwizard.cpp (source / functions) Coverage Total Hit
Test: QtPass Lines: 100.0 % 312 312
Test Date: 2026-11-14 13:19:18 Functions: 100.0 % 32 32

            Line data    Source code
       1              : // SPDX-FileCopyrightText: 2026 Anne Jan Brouwer
       2              : // SPDX-License-Identifier: GPL-3.0-or-later
       3              : /**
       4              :  * @file
       5              :  * @brief Implements FirstRunWizard and its pages; the store is created and
       6              :  * initialised in FirstRunWizard::initialiseStore() on Finish.
       7              :  */
       8              : #include "firstrunwizard.h"
       9              : #include "executor.h"
      10              : #include "gpgkeystate.h"
      11              : #include "imitatepass.h"
      12              : #include "keygendialog.h"
      13              : #include "passbackendfactory.h"
      14              : #include "profileinit.h"
      15              : #include "qtpasssettings.h"
      16              : #include "util.h"
      17              : #include <QCheckBox>
      18              : #include <QDateTime>
      19              : #include <QDir>
      20              : #include <QFile>
      21              : #include <QFileDialog>
      22              : #include <QFileInfo>
      23              : #include <QFormLayout>
      24              : #include <QHBoxLayout>
      25              : #include <QLabel>
      26              : #include <QLineEdit>
      27              : #include <QListWidget>
      28              : #include <QMessageBox>
      29              : #include <QPushButton>
      30              : #include <QSystemTrayIcon>
      31              : #include <QToolButton>
      32              : #include <QVBoxLayout>
      33              : #include <functional>
      34              : #ifdef Q_OS_WIN
      35              : #include <windows.h>
      36              : #endif
      37              : 
      38              : namespace {
      39              : 
      40              : /// A line edit with a "…" button that opens a file or folder picker.
      41           92 : auto pathRow(QLineEdit *edit, QWidget *parent,
      42              :              const std::function<QString()> &pick) -> QWidget * {
      43           92 :   auto *row = new QWidget(parent);
      44           92 :   auto *layout = new QHBoxLayout(row);
      45           92 :   layout->setContentsMargins(0, 0, 0, 0);
      46           92 :   layout->addWidget(edit);
      47           92 :   auto *browse = new QToolButton(row);
      48          184 :   browse->setText(QStringLiteral("…"));
      49           92 :   browse->setToolTip(FirstRunWizard::tr("Browse"));
      50           92 :   layout->addWidget(browse);
      51          184 :   QObject::connect(browse, &QToolButton::clicked, edit, [edit, pick]() {
      52            3 :     const QString chosen = pick();
      53            3 :     if (!chosen.isEmpty()) {
      54            2 :       edit->setText(chosen);
      55            2 :       emit edit->textEdited(chosen);
      56              :     }
      57            3 :   });
      58           92 :   return row;
      59              : }
      60              : 
      61          184 : auto runnable(const QString &exe) -> bool {
      62          184 :   return Executor::parseWslCommand(exe).has_value() ||
      63          184 :          (!exe.isEmpty() && QFileInfo(exe).isExecutable());
      64              : }
      65              : 
      66              : } // namespace
      67              : 
      68              : // --------------------------------------------------------------------------
      69              : 
      70           23 : FirstRunWizard::FirstRunWizard(QWidget *parent)
      71           23 :     : QWizard(parent), m_settings(QtPassSettings::load()) {
      72           23 :   setWindowTitle(tr("Welcome to QtPass"));
      73           23 :   setWizardStyle(QWizard::ModernStyle);
      74           23 :   setOption(QWizard::NoBackButtonOnStartPage);
      75           23 :   setOption(QWizard::HaveHelpButton, false);
      76              : 
      77           23 :   auto *intro = new QWizardPage(this);
      78           23 :   intro->setTitle(tr("Welcome to QtPass"));
      79           23 :   auto *introLayout = new QVBoxLayout(intro);
      80              :   auto *introText = new QLabel(
      81           23 :       tr("QtPass is a graphical front-end for <i>pass</i>, the standard Unix "
      82              :          "password manager: every password is a file encrypted with GnuPG, "
      83              :          "kept in a folder you can put under Git.<br><br>The next pages find "
      84              :          "GnuPG, make sure you have a key to encrypt to and pick the folder "
      85              :          "your passwords live in. Nothing is written until you press "
      86              :          "Finish."),
      87           23 :       intro);
      88           23 :   introText->setWordWrap(true);
      89           23 :   introText->setTextFormat(Qt::RichText);
      90           23 :   introLayout->addWidget(introText);
      91           23 :   setPage(IntroPage, intro);
      92           23 :   setPage(ProgramsPage, new ProgramsWizardPage(this));
      93           23 :   setPage(KeyPage, new KeyWizardPage(this));
      94           23 :   setPage(StorePage, new StoreWizardPage(this));
      95           23 :   setPage(DonePage, new DoneWizardPage(this));
      96           23 :   setStartId(IntroPage);
      97              :   resize(640, 480);
      98           23 : }
      99              : 
     100           20 : auto FirstRunWizard::secretKeys(const QString &gpgExecutable)
     101              :     -> QList<UserInfo> {
     102           20 :   if (!runnable(gpgExecutable)) {
     103            2 :     return {};
     104              :   }
     105           18 :   QString out;
     106          108 :   const int rc = Executor::executeBlocking(
     107              :       gpgExecutable,
     108           18 :       {QStringLiteral("--no-tty"), QStringLiteral("--with-colons"),
     109           18 :        QStringLiteral("--with-fingerprint"),
     110           18 :        QStringLiteral("--list-secret-keys")},
     111              :       &out);
     112           18 :   if (rc != 0) {
     113            2 :     return {};
     114              :   }
     115           16 :   return parseGpgColonOutput(out, true);
     116              : }
     117              : 
     118          155 : auto FirstRunWizard::isStore(const QString &path) -> bool {
     119          155 :   return !path.isEmpty() &&
     120          465 :          QFile::exists(QDir(path).filePath(QStringLiteral(".gpg-id")));
     121              : }
     122              : 
     123            5 : auto FirstRunWizard::initialiseStore(const QString &store) -> bool {
     124            5 :   QList<UserInfo> recipients;
     125           10 :   for (const UserInfo &key : std::as_const(m_keys)) {
     126            5 :     if (key.enabled) {
     127              :       recipients.append(key);
     128              :     }
     129              :   }
     130            5 :   const bool created = !QDir(store).exists();
     131            5 :   if (created && !Util::mkpathForStore(store)) {
     132            2 :     QMessageBox::warning(
     133            1 :         this, tr("Error"),
     134            2 :         tr("Failed to create password-store at: %1").arg(store));
     135            1 :     return false;
     136              :   }
     137              : #ifdef Q_OS_WIN
     138              :   // Only a folder made here is hidden, like ~/.password-store on Unix; a
     139              :   // folder the user picked keeps its attributes.
     140              :   if (created) {
     141              :     SetFileAttributes(store.toStdWString().c_str(), FILE_ATTRIBUTE_HIDDEN);
     142              :   }
     143              : #endif
     144            4 :   QString note;
     145            4 :   if (!ProfileInit::initialise(store, recipients, m_settings, m_settings.useGit,
     146              :                                &note)) {
     147              :     // ProfileInit writes .gpg-id before it signs and commits; take it back so
     148              :     // a second Finish runs the whole initialisation again instead of finding
     149              :     // a store that looks finished.
     150            2 :     QFile::remove(QDir(store).filePath(QStringLiteral(".gpg-id")));
     151            2 :     QFile::remove(QDir(store).filePath(QStringLiteral(".gpg-id.sig")));
     152            1 :     QMessageBox::warning(this, tr("Password store not initialised"), note);
     153            1 :     return false;
     154              :   }
     155            3 :   if (!note.isEmpty()) {
     156              :     // ProfileInit's own note talks of switching profiles; on the first run
     157              :     // the folder simply becomes the store.
     158            2 :     QMessageBox::information(
     159            1 :         this, tr("Password store"),
     160            1 :         tr("%1 already contains encrypted files; they were not "
     161              :            "re-encrypted to the ticked keys. Open Users after the start "
     162              :            "to do that.")
     163            2 :             .arg(QDir::toNativeSeparators(store)));
     164              :   }
     165              :   return true;
     166              : }
     167              : 
     168            2 : auto FirstRunWizard::initialiseGit(const QString &store) -> bool {
     169            2 :   QString note;
     170            2 :   if (ProfileInit::initGit(store, m_settings, &note)) {
     171              :     return true;
     172              :   }
     173              :   // Take the repository back too, so a second Finish retries the whole thing
     174              :   // instead of skipping a .git that never got its first commit.
     175            2 :   QDir(QDir(store).filePath(QStringLiteral(".git"))).removeRecursively();
     176            1 :   QMessageBox::warning(this, tr("Password store not initialised"), note);
     177            1 :   return false;
     178              : }
     179              : 
     180            8 : void FirstRunWizard::accept() {
     181              :   // The Finish button validates the page before calling this; a programmatic
     182              :   // accept() gets the same treatment so the last page's choices are in.
     183            8 :   if (currentPage() != nullptr && !currentPage()->validatePage()) {
     184            3 :     return;
     185              :   }
     186            8 :   const QString store = QDir::cleanPath(m_settings.passStore);
     187              :   const bool needsGit =
     188           11 :       m_settings.useGit && !QDir(store).exists(QStringLiteral(".git"));
     189            8 :   const bool ready = !isStore(store) ? initialiseStore(store)
     190            3 :                      : needsGit      ? initialiseGit(store)
     191              :                                      : true;
     192            7 :   if (!ready) {
     193              :     return;
     194              :   }
     195            5 :   m_settings.passStore = Util::normalizeFolderPath(store);
     196            5 :   QtPassSettings::save(m_settings);
     197              :   // The backend cached the old paths at construction.
     198            5 :   PassBackendFactory::invalidate();
     199            5 :   QWizard::accept();
     200              : }
     201              : 
     202              : // --------------------------------------------------------------------------
     203              : 
     204           23 : ProgramsWizardPage::ProgramsWizardPage(FirstRunWizard *wizard)
     205           23 :     : QWizardPage(wizard), m_wizard(wizard), m_gpg(new QLineEdit(this)),
     206           23 :       m_git(new QLineEdit(this)), m_pass(new QLineEdit(this)),
     207           46 :       m_usePass(new QCheckBox(tr("Use the pass command-line tool"), this)),
     208           46 :       m_gpgStatus(new QLabel(this)) {
     209           46 :   setTitle(tr("Programs"));
     210           23 :   setSubTitle(tr("GnuPG does the encrypting. pass and Git are optional; "
     211              :                  "QtPass can do their work itself."));
     212           23 :   auto *layout = new QFormLayout(this);
     213            2 :   auto pickExe = [this]() {
     214            2 :     return QFileDialog::getOpenFileName(this, tr("Select executable"),
     215            4 :                                         QString(), tr("All files (*)"));
     216              :   };
     217           69 :   layout->addRow(tr("GnuPG"), pathRow(m_gpg, this, pickExe));
     218           23 :   m_gpgStatus->setWordWrap(true);
     219           46 :   layout->addRow(QString(), m_gpgStatus);
     220           69 :   layout->addRow(tr("Git"), pathRow(m_git, this, pickExe));
     221           69 :   layout->addRow(tr("pass"), pathRow(m_pass, this, pickExe));
     222           23 :   m_usePass->setToolTip(tr("Run the pass script for every operation instead "
     223              :                            "of calling gpg and git directly"));
     224           23 :   layout->addRow(QString(), m_usePass);
     225           92 :   for (QLineEdit *edit : {m_gpg, m_git, m_pass}) {
     226           69 :     connect(edit, &QLineEdit::textEdited, this,
     227          138 :             &ProgramsWizardPage::updateStatus);
     228              :   }
     229           23 : }
     230              : 
     231           12 : void ProgramsWizardPage::initializePage() {
     232           12 :   AppSettings &s = m_wizard->m_settings;
     233              :   // QtPassSettings::initExecutables() fills empty paths from PATH only; a
     234              :   // stored path whose binary has moved would be shown as an error here, so
     235              :   // look it up again in that case.
     236           48 :   auto detect = [](QString &stored, const QStringList &names) {
     237           48 :     if (runnable(stored)) {
     238              :       return;
     239              :     }
     240           70 :     for (const QString &name : names) {
     241           37 :       const QString found = Util::findBinaryInPath(name);
     242           37 :       if (!found.isEmpty()) {
     243            1 :         stored = found;
     244              :         return;
     245              :       }
     246              :     }
     247              :   };
     248           48 :   detect(s.gpgExecutable, {QStringLiteral("gpg2"), QStringLiteral("gpg")});
     249           36 :   detect(s.gitExecutable, {QStringLiteral("git")});
     250           36 :   detect(s.passExecutable, {QStringLiteral("pass")});
     251           36 :   detect(s.pwgenExecutable, {QStringLiteral("pwgen")});
     252           12 :   m_gpg->setText(s.gpgExecutable);
     253           12 :   m_git->setText(s.gitExecutable);
     254           12 :   m_pass->setText(s.passExecutable);
     255           12 :   m_usePass->setChecked(s.usePass || runnable(s.passExecutable));
     256           12 :   updateStatus();
     257           12 : }
     258              : 
     259           15 : void ProgramsWizardPage::updateStatus() {
     260           30 :   const QString gpg = m_gpg->text().trimmed();
     261           15 :   if (gpg.isEmpty()) {
     262            4 :     m_gpgStatus->setText(tr("GnuPG was not found. Install it (gpg or gpg2) "
     263              :                             "and enter its location here."));
     264           13 :   } else if (!runnable(gpg)) {
     265            2 :     m_gpgStatus->setText(tr("%1 is not an executable file.").arg(gpg));
     266              :   } else {
     267           24 :     m_gpgStatus->setText(QString());
     268              :   }
     269           30 :   m_usePass->setEnabled(runnable(m_pass->text().trimmed()));
     270           15 :   if (!m_usePass->isEnabled()) {
     271           15 :     m_usePass->setChecked(false);
     272              :   }
     273           15 :   emit completeChanged();
     274           15 : }
     275              : 
     276           58 : auto ProgramsWizardPage::isComplete() const -> bool {
     277          174 :   return runnable(m_gpg->text().trimmed());
     278              : }
     279              : 
     280           11 : auto ProgramsWizardPage::validatePage() -> bool {
     281           11 :   AppSettings &s = m_wizard->m_settings;
     282           11 :   s.gpgExecutable = m_gpg->text().trimmed();
     283           11 :   s.gitExecutable = m_git->text().trimmed();
     284           11 :   s.passExecutable = m_pass->text().trimmed();
     285           11 :   s.usePass = m_usePass->isChecked();
     286           11 :   return true;
     287              : }
     288              : 
     289              : // --------------------------------------------------------------------------
     290              : 
     291           23 : KeyWizardPage::KeyWizardPage(FirstRunWizard *wizard)
     292           23 :     : QWizardPage(wizard), m_wizard(wizard), m_list(new QListWidget(this)),
     293           46 :       m_generate(new QPushButton(tr("Generate a new key pair…"), this)),
     294           46 :       m_status(new QLabel(this)) {
     295           46 :   setTitle(tr("Your key"));
     296           23 :   setSubTitle(tr("Passwords are encrypted to GnuPG keys. Tick the keys that "
     297              :                  "should be able to open a new store; you need at least one "
     298              :                  "with its secret half on this machine."));
     299           23 :   auto *layout = new QVBoxLayout(this);
     300           23 :   layout->addWidget(m_list);
     301           23 :   m_status->setWordWrap(true);
     302           23 :   layout->addWidget(m_status);
     303           23 :   auto *buttons = new QHBoxLayout;
     304           23 :   buttons->addStretch();
     305           23 :   buttons->addWidget(m_generate);
     306           23 :   layout->addLayout(buttons);
     307           23 :   connect(m_generate, &QPushButton::clicked, this, &KeyWizardPage::generate);
     308           23 :   connect(m_list, &QListWidget::itemChanged, this,
     309           60 :           [this]() { emit completeChanged(); });
     310           23 : }
     311              : 
     312           15 : void KeyWizardPage::initializePage() { reload(); }
     313              : 
     314           16 : void KeyWizardPage::reload() {
     315           16 :   m_wizard->m_keys =
     316           16 :       FirstRunWizard::secretKeys(m_wizard->m_settings.gpgExecutable);
     317           16 :   m_list->clear();
     318           16 :   const QDateTime now = QDateTime::currentDateTime();
     319           49 :   for (UserInfo &key : m_wizard->m_keys) {
     320              :     // An expired or otherwise unusable key would make every encryption to
     321              :     // the new store fail; list it, but do not tick it.
     322           17 :     QString why;
     323           18 :     if (key.expiry.isValid() && key.expiry < now) {
     324            1 :       why = tr("expired");
     325              :     } else if (!key.isValid()) {
     326            1 :       why = tr("not usable");
     327              :     }
     328           17 :     key.enabled = why.isEmpty();
     329           34 :     QString label = QStringLiteral("%1\n%2").arg(key.name, key.key_id);
     330           17 :     if (!why.isEmpty()) {
     331            4 :       label += QStringLiteral(" (%1)").arg(why);
     332              :     }
     333           17 :     auto *item = new QListWidgetItem(label, m_list);
     334           34 :     item->setFlags(key.enabled ? (item->flags() | Qt::ItemIsUserCheckable)
     335              :                                : (item->flags() & ~Qt::ItemIsEnabled));
     336           19 :     item->setCheckState(key.enabled ? Qt::Checked : Qt::Unchecked);
     337              :   }
     338           32 :   m_status->setText(m_wizard->m_keys.isEmpty()
     339           16 :                         ? tr("GnuPG has no secret key yet. Generate one here, "
     340              :                              "or import your existing key with gpg first.")
     341              :                         : QString());
     342           16 :   emit completeChanged();
     343           16 : }
     344              : 
     345            1 : void KeyWizardPage::generate() {
     346              :   // A backend of the wizard's own, set up from its copy of the settings, so
     347              :   // the chosen gpg is used and nothing is written before Finish. Each Pass
     348              :   // owns its Executor; the shared backend is not involved.
     349            1 :   ImitatePass pass;
     350            1 :   pass.init(m_wizard->m_settings);
     351            1 :   KeygenDialog d(m_wizard->m_settings.gpgExecutable, &pass, this);
     352            1 :   d.exec();
     353            1 :   reload();
     354            1 : }
     355              : 
     356          132 : auto KeyWizardPage::isComplete() const -> bool { return true; }
     357              : 
     358           16 : auto KeyWizardPage::validatePage() -> bool {
     359           38 :   for (int i = 0; i < m_list->count() && i < m_wizard->m_keys.size(); ++i) {
     360           22 :     m_wizard->m_keys[i].enabled = m_list->item(i)->checkState() == Qt::Checked;
     361              :   }
     362           16 :   return true;
     363              : }
     364              : 
     365              : // --------------------------------------------------------------------------
     366              : 
     367           23 : StoreWizardPage::StoreWizardPage(FirstRunWizard *wizard)
     368           23 :     : QWizardPage(wizard), m_wizard(wizard), m_path(new QLineEdit(this)),
     369           46 :       m_useGit(new QCheckBox(tr("Keep the store under Git"), this)),
     370           46 :       m_status(new QLabel(this)) {
     371           46 :   setTitle(tr("Password store"));
     372           23 :   setSubTitle(tr("The folder your passwords live in. An existing store is "
     373              :                  "used as it is; an empty or missing folder is set up for "
     374              :                  "the keys you ticked."));
     375           23 :   auto *layout = new QFormLayout(this);
     376           69 :   layout->addRow(tr("Folder"), pathRow(m_path, this, [this]() {
     377              :                    return QFileDialog::getExistingDirectory(
     378            1 :                        this, tr("Choose the password store folder"),
     379            3 :                        m_path->text());
     380              :                  }));
     381           23 :   m_status->setWordWrap(true);
     382           23 :   layout->addRow(QString(), m_status);
     383           23 :   layout->addRow(QString(), m_useGit);
     384           23 :   connect(m_path, &QLineEdit::textEdited, this, &StoreWizardPage::updateStatus);
     385           23 : }
     386              : 
     387           18 : void StoreWizardPage::initializePage() {
     388           18 :   QString path = m_wizard->m_settings.passStore;
     389           18 :   if (path.isEmpty()) {
     390           10 :     path = Util::findPasswordStore();
     391              :   }
     392           18 :   m_path->setText(QDir::toNativeSeparators(QDir::cleanPath(path)));
     393           18 :   const QString clean = QDir::cleanPath(path);
     394           36 :   const bool repository = QDir(clean).exists(QStringLiteral(".git"));
     395           18 :   bool haveGit = runnable(m_wizard->m_settings.gitExecutable);
     396              :   // A first commit needs a name and an e-mail; without them Finish would
     397              :   // fail at the very end. Ask git now and say what to do instead.
     398              :   const bool identity =
     399           26 :       haveGit && (repository || ProfileInit::gitIdentityConfigured(
     400            8 :                                     QDir::homePath(), m_wizard->m_settings));
     401           18 :   m_useGit->setToolTip(
     402           18 :       haveGit && !identity
     403           18 :           ? tr("Git has no name and e-mail to commit with yet. Run\n"
     404              :                "git config --global user.name \"Your Name\"\n"
     405              :                "git config --global user.email you@example.org\n"
     406              :                "and turn Git on in Settings afterwards.")
     407              :           : tr("Every change becomes a commit; a folder that is no "
     408              :                "repository yet gets one"));
     409           18 :   haveGit = haveGit && identity;
     410           18 :   m_useGit->setEnabled(haveGit);
     411              :   // On for a repository or a store still to be made, off for an existing
     412              :   // store that is no repository: turning Git on there would make every
     413              :   // remove fail in a folder git knows nothing about.
     414           40 :   m_useGit->setChecked(haveGit && (m_wizard->m_settings.useGit || repository ||
     415            4 :                                    !FirstRunWizard::isStore(clean)));
     416           18 :   updateStatus();
     417           18 : }
     418              : 
     419           82 : auto StoreWizardPage::recipients() const -> int {
     420              :   int ticked = 0;
     421          190 :   for (const UserInfo &key : std::as_const(m_wizard->m_keys)) {
     422          158 :     ticked += key.enabled ? 1 : 0;
     423              :   }
     424           82 :   return ticked;
     425              : }
     426              : 
     427           26 : void StoreWizardPage::updateStatus() {
     428           52 :   const QString path = QDir::cleanPath(m_path->text().trimmed());
     429           26 :   QString text;
     430           26 :   if (path.isEmpty()) {
     431            1 :     text = tr("Enter a folder.");
     432           25 :   } else if (FirstRunWizard::isStore(path)) {
     433              :     const int entries = static_cast<int>(
     434           18 :         Util::regularFilesUnder(path, {QStringLiteral("*.gpg")}).size());
     435            6 :     text = tr("An existing password store with %n entries.", nullptr, entries);
     436           19 :   } else if (QDir(path).exists()) {
     437            6 :     text = QDir(path).isEmpty()
     438            6 :                ? tr("An empty folder; it will be set up as a password store.")
     439              :                : tr("This folder is not a password store yet; a .gpg-id for "
     440              :                     "the ticked keys will be written into it.");
     441              :   } else {
     442           16 :     text = tr("The folder does not exist yet; it will be created.");
     443              :   }
     444           26 :   if (!path.isEmpty() && !FirstRunWizard::isStore(path) && recipients() == 0) {
     445            6 :     text += QLatin1Char(' ') +
     446           12 :             tr("Go back and tick at least one key to encrypt it to.");
     447              :   }
     448           26 :   m_status->setText(text);
     449           26 :   emit completeChanged();
     450           26 : }
     451              : 
     452           87 : auto StoreWizardPage::isComplete() const -> bool {
     453          261 :   const QString path = QDir::cleanPath(m_path->text().trimmed());
     454           87 :   if (path.isEmpty()) {
     455              :     return false;
     456              :   }
     457           84 :   return FirstRunWizard::isStore(path) || recipients() > 0;
     458              : }
     459              : 
     460           10 : auto StoreWizardPage::validatePage() -> bool {
     461           10 :   m_wizard->m_settings.passStore =
     462           20 :       QDir::fromNativeSeparators(m_path->text().trimmed());
     463           10 :   m_wizard->m_settings.useGit = m_useGit->isEnabled() && m_useGit->isChecked();
     464           10 :   return true;
     465              : }
     466              : 
     467              : // --------------------------------------------------------------------------
     468              : 
     469           23 : DoneWizardPage::DoneWizardPage(FirstRunWizard *wizard)
     470           23 :     : QWizardPage(wizard), m_wizard(wizard), m_summary(new QLabel(this)),
     471           46 :       m_hidePassword(new QCheckBox(tr("Hide passwords until asked"), this)),
     472           69 :       m_trayIcon(new QCheckBox(tr("Show an icon in the system tray"), this)) {
     473           46 :   setTitle(tr("Ready"));
     474           23 :   setSubTitle(tr("Everything else can be changed later in Settings."));
     475           23 :   auto *layout = new QVBoxLayout(this);
     476           23 :   m_summary->setWordWrap(true);
     477           23 :   m_summary->setTextFormat(Qt::RichText);
     478           23 :   layout->addWidget(m_summary);
     479           23 :   layout->addSpacing(12);
     480           23 :   m_hidePassword->setToolTip(
     481           23 :       tr("Show the password line as dots; Show password reveals it"));
     482           23 :   layout->addWidget(m_hidePassword);
     483           23 :   layout->addWidget(m_trayIcon);
     484           23 :   layout->addStretch();
     485           23 :   setFinalPage(true);
     486           23 : }
     487              : 
     488            9 : void DoneWizardPage::initializePage() {
     489            9 :   const AppSettings &s = m_wizard->m_settings;
     490            9 :   const QString store = QDir::toNativeSeparators(QDir::cleanPath(s.passStore));
     491            9 :   QStringList lines;
     492           18 :   lines << tr("Store: %1").arg(store.toHtmlEscaped());
     493            9 :   if (FirstRunWizard::isStore(s.passStore)) {
     494            3 :     lines << tr("It is already a password store and is used as it is.");
     495            6 :   } else if (s.useGit) {
     496            2 :     lines << tr("It will be set up for the ticked keys and put under Git.");
     497              :   } else {
     498            4 :     lines << tr("It will be set up for the ticked keys.");
     499              :   }
     500           18 :   lines << tr("GnuPG: %1").arg(s.gpgExecutable.toHtmlEscaped());
     501            9 :   lines << (s.usePass ? tr("Operations run through pass.")
     502              :                       : tr("Operations run through gpg and git directly."));
     503           18 :   m_summary->setText(lines.join(QStringLiteral("<br>")));
     504            9 :   m_hidePassword->setChecked(true);
     505            9 :   const bool tray = QSystemTrayIcon::isSystemTrayAvailable();
     506            9 :   m_trayIcon->setVisible(tray);
     507            9 :   m_trayIcon->setChecked(tray && s.useTrayIcon);
     508            9 : }
     509              : 
     510           13 : auto DoneWizardPage::validatePage() -> bool {
     511           13 :   m_wizard->m_settings.hidePassword = m_hidePassword->isChecked();
     512           13 :   m_wizard->m_settings.useTrayIcon =
     513           13 :       m_trayIcon->isVisible() && m_trayIcon->isChecked();
     514           13 :   return true;
     515              : }
        

Generated by: LCOV version 2.0-1