• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

IJHack / QtPass / 28631140713

03 Jul 2026 12:52AM UTC coverage: 56.387%. First build
28631140713

Pull #1602

github

web-flow
Merge 7a7073f99 into 5c4ff9a04
Pull Request #1602: fix: stop ConfigDialog from silently corrupting saved settings

24 of 30 new or added lines in 2 files covered. (80.0%)

3792 of 6725 relevant lines covered (56.39%)

30.41 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

45.75
/src/configdialog.cpp
1
// SPDX-FileCopyrightText: 2014 Anne Jan Brouwer
2
// SPDX-License-Identifier: GPL-3.0-or-later
3
#include "configdialog.h"
4
#include "appsettings.h"
5
#include "keygendialog.h"
6
#include "mainwindow.h"
7
#include "profileinit.h"
8
#include "qtpasssettings.h"
9
#include "sshauthsock.h"
10
#include "ui_configdialog.h"
11
#include "usersdialog.h"
12
#include "util.h"
13
#include <QClipboard>
14
#include <QDir>
15
#include <QFileDialog>
16
#include <QMessageBox>
17
#include <QPushButton>
18
#include <QSystemTrayIcon>
19
#include <QTableWidgetItem>
20
#include <utility>
21
#ifdef Q_OS_WIN
22
#include <windows.h>
23
#endif
24

25
#ifdef QT_DEBUG
26
#include "debughelper.h"
27
#endif
28

29
/**
30
 * @brief ConfigDialog::ConfigDialog this sets up the configuration screen.
31
 * @param parent
32
 */
33
ConfigDialog::ConfigDialog(MainWindow *parent)
16✔
34
    : QDialog(parent), ui(new Ui::ConfigDialog) {
16✔
35
  mainWindow = parent;
16✔
36
  ui->setupUi(this);
16✔
37

38
  // Restore dialog state
39
  QByteArray savedGeometry = QtPassSettings::getDialogGeometry("configDialog");
32✔
40
  bool hasSavedGeometry = !savedGeometry.isEmpty();
41
  if (hasSavedGeometry) {
16✔
42
    restoreGeometry(savedGeometry);
×
43
  }
44
  if (QtPassSettings::isDialogMaximized("configDialog")) {
32✔
45
    showMaximized();
×
46
  } else if (!hasSavedGeometry) {
47
    // Let window manager handle positioning for first launch
48
  }
49

50
  const AppSettings s = QtPassSettings::load();
16✔
51
  applySettings(s);
16✔
52

53
  if (!QSystemTrayIcon::isSystemTrayAvailable()) {
16✔
54
    ui->checkBoxUseTrayIcon->setEnabled(false);
16✔
55
    ui->checkBoxUseTrayIcon->setToolTip(tr("System tray is not available"));
32✔
56
    ui->checkBoxHideOnClose->setEnabled(false);
16✔
57
    ui->checkBoxStartMinimized->setEnabled(false);
16✔
58
  }
59

60
#if defined(Q_OS_WIN)
61
  ui->checkBoxUseOtp->hide();
62
  ui->checkBoxUseQrencode->hide();
63
  ui->label_10->hide();
64
#endif
65

66
  if (!isPassOtpAvailable()) {
16✔
67
    ui->checkBoxUseOtp->setEnabled(false);
16✔
68
    ui->checkBoxUseOtp->setToolTip(
16✔
69
        tr("Pass OTP extension needs to be installed"));
16✔
70
  }
71

72
  if (!isQrencodeAvailable()) {
16✔
73
    ui->checkBoxUseQrencode->setEnabled(false);
16✔
74
    ui->checkBoxUseQrencode->setToolTip(tr("qrencode needs to be installed"));
32✔
75
  }
76

77
  setProfiles(QtPassSettings::getProfiles(), QtPassSettings::getProfile());
32✔
78

79
  ui->profileTable->verticalHeader()->hide();
16✔
80
  ui->profileTable->horizontalHeader()->setSectionResizeMode(
16✔
81
      1, QHeaderView::Stretch);
82
  ui->label->setText(ui->label->text() + VERSION);
32✔
83
  ui->comboBoxClipboard->clear();
16✔
84

85
  ui->comboBoxClipboard->addItem(tr("No Clipboard"));
32✔
86
  ui->comboBoxClipboard->addItem(tr("Always copy to clipboard"));
32✔
87
  ui->comboBoxClipboard->addItem(tr("On-demand copy to clipboard"));
32✔
88

89
  int currentIndex = static_cast<int>(s.clipBoardType);
16✔
90
  ui->comboBoxClipboard->setCurrentIndex(currentIndex);
16✔
91
  on_comboBoxClipboard_activated(currentIndex);
16✔
92

93
  QClipboard *clip = QApplication::clipboard();
16✔
94
  if (!clip->supportsSelection()) {
16✔
95
    useSelection(false);
16✔
96
    ui->checkBoxSelection->setVisible(false);
16✔
97
  } else {
98
    useSelection(s.useSelection);
×
99
  }
100

101
  if (!Util::configIsValid(s)) {
16✔
102
    // Show Programs tab, which is likely
103
    // what the user needs to fix now.
104
    ui->tabWidget->setCurrentIndex(1);
16✔
105
  }
106

107
  connect(ui->profileTable, &QTableWidget::itemChanged, this,
16✔
108
          &ConfigDialog::onProfileTableItemChanged);
16✔
109
  connect(ui->profileTable, &QTableWidget::itemSelectionChanged, this,
16✔
110
          &ConfigDialog::onProfileTableSelectionChanged);
16✔
111
  connect(this, &ConfigDialog::accepted, this, &ConfigDialog::on_accepted);
16✔
112
}
32✔
113

114
void ConfigDialog::applySettings(const AppSettings &settings) {
16✔
115
  ui->passPath->setText(settings.passExecutable);
16✔
116
  setGitPath(settings.gitExecutable);
16✔
117
  ui->gpgPath->setText(settings.gpgExecutable);
16✔
118
  ui->storePath->setText(settings.passStore);
16✔
119
  ui->sshAuthSockOverride->setText(settings.sshAuthSockOverride);
16✔
120

121
  ui->spinBoxAutoclearSeconds->setValue(settings.autoclearSeconds);
16✔
122
  ui->spinBoxAutoclearPanelSeconds->setValue(settings.autoclearPanelSeconds);
16✔
123
  ui->checkBoxHidePassword->setChecked(settings.hidePassword);
16✔
124
  ui->checkBoxHideContent->setChecked(settings.hideContent);
16✔
125
  ui->checkBoxUseMonospace->setChecked(settings.useMonospace);
16✔
126
  ui->checkBoxDisplayAsIs->setChecked(settings.displayAsIs);
16✔
127
  ui->checkBoxNoLineWrapping->setChecked(settings.noLineWrapping);
16✔
128
  ui->checkBoxAddGPGId->setChecked(settings.addGPGId);
16✔
129
  ui->checkBoxHideOnClose->setChecked(settings.hideOnClose);
16✔
130
  ui->checkBoxStartMinimized->setChecked(settings.startMinimized);
16✔
131

132
  ui->checkBoxAvoidCapitals->setChecked(settings.avoidCapitals);
16✔
133
  ui->checkBoxAvoidNumbers->setChecked(settings.avoidNumbers);
16✔
134
  ui->checkBoxLessRandom->setChecked(settings.lessRandom);
16✔
135
  ui->checkBoxUseSymbols->setChecked(settings.useSymbols);
16✔
136
  ui->plainTextEditTemplate->setPlainText(settings.passTemplate);
16✔
137
  ui->checkBoxTemplateAllFields->setChecked(settings.templateAllFields);
16✔
138
  ui->checkBoxShowProcessOutput->setChecked(settings.showProcessOutput);
16✔
139
  ui->checkBoxAutoPull->setChecked(settings.autoPull);
16✔
140
  ui->checkBoxAutoPush->setChecked(settings.autoPush);
16✔
141
  ui->checkBoxAlwaysOnTop->setChecked(settings.alwaysOnTop);
16✔
142

143
  // Dependent helpers: set after the plain values above so the enable/disable
144
  // logic they trigger sees the final widget states.
145
  usePass(settings.usePass);
16✔
146
  useAutoclear(settings.useAutoclear);
16✔
147
  useAutoclearPanel(settings.useAutoclearPanel);
16✔
148
  useTrayIcon(settings.useTrayIcon);
16✔
149
  useGit(settings.useGit);
16✔
150
  useOtp(settings.useOtp);
16✔
151
  useGrepSearch(settings.useGrepSearch);
16✔
152
  useQrencode(settings.useQrencode);
16✔
153
  // Restore the persisted pwgen path and password-generation settings before
154
  // toggling usePwgen (which is gated on a non-empty pwgen path). Without this
155
  // the Password-tab widgets keep their .ui defaults and readSettings() writes
156
  // those defaults back over the user's saved configuration on every OK.
157
  setPwgenPath(settings.pwgenExecutable);
16✔
158
  setPasswordConfiguration(settings.passwordConfiguration);
16✔
159
  usePwgen(settings.usePwgen);
16✔
160
  useTemplate(settings.useTemplate);
16✔
161
}
16✔
162

163
auto ConfigDialog::readSettings() -> AppSettings {
×
164
  // Start from the persisted settings so keys this dialog does not own
165
  // (window geometry, WebDAV, profiles, etc.) survive the save.
166
  AppSettings settings = QtPassSettings::load();
×
167

168
  settings.passExecutable = ui->passPath->text();
×
169
  settings.gitExecutable = ui->gitPath->text();
×
170
  settings.gpgExecutable = ui->gpgPath->text();
×
171
  settings.sshAuthSockOverride = ui->sshAuthSockOverride->text().trimmed();
×
172
  settings.passStore = Util::normalizeFolderPath(ui->storePath->text());
×
173
  settings.usePass = ui->radioButtonPass->isChecked();
×
174
  settings.clipBoardType =
×
175
      static_cast<Enums::clipBoardType>(ui->comboBoxClipboard->currentIndex());
×
176
  settings.useSelection = ui->checkBoxSelection->isChecked();
×
177
  settings.useAutoclear = ui->checkBoxAutoclear->isChecked();
×
178
  settings.autoclearSeconds = ui->spinBoxAutoclearSeconds->value();
×
179
  settings.useAutoclearPanel = ui->checkBoxAutoclearPanel->isChecked();
×
180
  settings.autoclearPanelSeconds = ui->spinBoxAutoclearPanelSeconds->value();
×
181
  settings.hidePassword = ui->checkBoxHidePassword->isChecked();
×
182
  settings.hideContent = ui->checkBoxHideContent->isChecked();
×
183
  settings.useMonospace = ui->checkBoxUseMonospace->isChecked();
×
184
  settings.displayAsIs = ui->checkBoxDisplayAsIs->isChecked();
×
185
  settings.noLineWrapping = ui->checkBoxNoLineWrapping->isChecked();
×
186
  settings.addGPGId = ui->checkBoxAddGPGId->isChecked();
×
187
  // Only overwrite these environment-gated preferences when their control is
188
  // enabled. When disabled (e.g. no system tray available) keep the persisted
189
  // value loaded above rather than clobbering it to false, so the preference
190
  // survives until the user can change it on a capable environment.
191
  if (ui->checkBoxUseTrayIcon->isEnabled()) {
×
192
    settings.useTrayIcon = ui->checkBoxUseTrayIcon->isChecked();
×
193
  }
194
  if (ui->checkBoxHideOnClose->isEnabled()) {
×
195
    settings.hideOnClose = ui->checkBoxHideOnClose->isChecked();
×
196
  }
197
  if (ui->checkBoxStartMinimized->isEnabled()) {
×
198
    settings.startMinimized = ui->checkBoxStartMinimized->isChecked();
×
199
  }
200
  settings.useGit = ui->checkBoxUseGit->isChecked();
×
201
  settings.useOtp = ui->checkBoxUseOtp->isChecked();
×
202
  settings.useGrepSearch = ui->checkBoxUseGrepSearch->isChecked();
×
203
  settings.useQrencode = ui->checkBoxUseQrencode->isChecked();
×
204
  settings.pwgenExecutable = ui->pwgenPath->text();
×
205
  settings.usePwgen = ui->checkBoxUsePwgen->isChecked();
×
206
  settings.avoidCapitals = ui->checkBoxAvoidCapitals->isChecked();
×
207
  settings.avoidNumbers = ui->checkBoxAvoidNumbers->isChecked();
×
208
  settings.lessRandom = ui->checkBoxLessRandom->isChecked();
×
209
  settings.useSymbols = ui->checkBoxUseSymbols->isChecked();
×
210
  settings.passwordConfiguration = getPasswordConfiguration();
×
211
  settings.useTemplate = ui->checkBoxUseTemplate->isChecked();
×
212
  settings.passTemplate = ui->plainTextEditTemplate->toPlainText();
×
213
  settings.templateAllFields = ui->checkBoxTemplateAllFields->isChecked();
×
214
  settings.showProcessOutput = ui->checkBoxShowProcessOutput->isChecked();
×
215
  settings.alwaysOnTop = ui->checkBoxAlwaysOnTop->isChecked();
×
216
  settings.version = VERSION;
×
217

218
  return settings;
×
219
}
×
220

221
/**
222
 * @brief ConfigDialog::~ConfigDialog config destructor.
223
 */
224
ConfigDialog::~ConfigDialog() = default;
16✔
225

226
/**
227
 * @brief ConfigDialog::setGitPath set the git executable path.
228
 * Make sure the checkBoxUseGit is updated.
229
 * @param path
230
 */
231
void ConfigDialog::setGitPath(const QString &path) {
16✔
232
  ui->gitPath->setText(path);
16✔
233
  ui->checkBoxUseGit->setEnabled(!path.isEmpty());
16✔
234
  if (path.isEmpty()) {
16✔
235
    useGit(false);
×
236
  }
237
}
16✔
238

239
/**
240
 * @brief ConfigDialog::usePass set whether or not we want to use pass.
241
 * Update radio buttons accordingly.
242
 * @param usePass
243
 */
244
void ConfigDialog::usePass(bool usePass) {
16✔
245
  ui->radioButtonNative->setChecked(!usePass);
16✔
246
  ui->radioButtonPass->setChecked(usePass);
16✔
247
  setGroupBoxState();
16✔
248
}
16✔
249

250
/**
251
 * @brief Validates the configuration table fields and enables or disables the
252
 * OK button accordingly.
253
 * @example
254
 * ConfigDialog dialog;
255
 * QTableWidgetItem *item = dialog.findChild<QTableWidgetItem*>();
256
 * dialog.validate(item);
257
 *
258
 * @param QTableWidgetItem *item - The table item to validate; if null,
259
 * validates all relevant items in the profile table.
260
 * @return void - This function does not return a value.
261
 */
262
void ConfigDialog::validate(QTableWidgetItem *item) {
34✔
263
  // Update the required-field marker(s): all cells when no specific item is
264
  // given, otherwise just the one that changed.
265
  if (item == nullptr) {
34✔
266
    for (int i = 0; i < ui->profileTable->rowCount(); i++) {
6✔
267
      for (int j = 0; j < ui->profileTable->columnCount(); j++) {
20✔
268
        QTableWidgetItem *_item = ui->profileTable->item(i, j);
15✔
269

270
        if (!_item)
15✔
271
          continue;
×
272
        if (_item->text().isEmpty() && j != 2) {
30✔
273
          _item->setBackground(Qt::red);
×
274
          _item->setToolTip(tr("This field is required"));
×
275
        } else {
276
          _item->setBackground(QBrush());
15✔
277
          _item->setToolTip(QString());
30✔
278
        }
279
      }
280
    }
281
  } else {
282
    if (item->text().isEmpty() && item->column() != 2) {
66✔
283
      item->setBackground(Qt::red);
×
284
      item->setToolTip(tr("This field is required"));
×
285
    } else {
286
      item->setBackground(QBrush());
33✔
287
      item->setToolTip(QString());
66✔
288
    }
289
  }
290

291
  // Enable OK only when every required cell across all rows is filled. Deriving
292
  // this from a single changed item would re-enable OK while another row still
293
  // has an empty required field, letting an empty-named profile be saved (and
294
  // then silently dropped, losing that profile's path and signing key).
295
  bool status = true;
296
  for (int i = 0; i < ui->profileTable->rowCount() && status; i++) {
204✔
297
    for (int j = 0; j < ui->profileTable->columnCount(); j++) {
680✔
298
      QTableWidgetItem *_item = ui->profileTable->item(i, j);
510✔
299
      if (_item && _item->text().isEmpty() && j != 2) {
1,011✔
300
        status = false;
301
        break;
302
      }
303
    }
304
  }
305

306
  ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(status);
34✔
307
}
34✔
308

309
/**
310
 * @brief Saves the configuration dialog settings to persistent application
311
 * settings.
312
 * @example
313
 * ConfigDialog dialog;
314
 * dialog.on_accepted();
315
 * // Expected output: All UI-selected configuration values are stored via
316
 * QtPassSettings.
317
 *
318
 * @return void - This method does not return a value.
319
 */
320
void ConfigDialog::on_accepted() {
×
321
  const QString sshAuthSockOverride = ui->sshAuthSockOverride->text().trimmed();
×
322
  if (!sshAuthSockOverride.isEmpty()) {
×
323
    QString reason;
×
324
    switch (SshAuthSock::overrideStatus(sshAuthSockOverride)) {
×
325
    case SshAuthSock::OverrideStatus::Valid:
326
      break;
327
    case SshAuthSock::OverrideStatus::DoesNotExist:
×
328
      reason = tr("The path does not exist.");
×
329
      break;
×
330
    case SshAuthSock::OverrideStatus::NotReadable:
×
331
      reason = tr("The path is not readable.");
×
332
      break;
×
333
    case SshAuthSock::OverrideStatus::NotUnixDomainSocket:
×
334
      reason = tr("The path is not a Unix domain socket.");
×
335
      break;
×
336
    }
337
    if (!reason.isEmpty()) {
×
338
      QMessageBox::warning(
×
339
          this, tr("Potentially invalid SSH_AUTH_SOCK override"),
×
340
          tr("The SSH_AUTH_SOCK override value may be invalid.\n\n%1\n\n"
×
341
             "The value will still be saved as entered.")
342
              .arg(reason));
×
343
    }
344
  }
345

346
  const QHash<QString, QHash<QString, QString>> existingProfiles =
347
      QtPassSettings::getProfiles();
×
348

349
  // Persist via the facade, which also invalidates the cached Pass backend so
350
  // a changed "use pass" mode takes effect.
351
  QtPassSettings::save(readSettings());
×
352

353
  // Profiles are not part of AppSettings yet, so persist them separately.
354
  QtPassSettings::setProfiles(getProfiles());
×
355

356
  // Initialize new profiles that need pass/git initialization
357
  initializeNewProfiles(existingProfiles);
×
358
}
×
359

360
/**
361
 * @brief Automatically detects required external binaries in the system PATH
362
 * and updates the dialog fields.
363
 * @example
364
 * ConfigDialog configDialog;
365
 * configDialog.on_autodetectButton_clicked();
366
 *
367
 * @return void - This function does not return a value.
368
 */
369
void ConfigDialog::on_autodetectButton_clicked() {
×
370
  QString pass = Util::findBinaryInPath("pass");
×
371
  if (!pass.isEmpty()) {
×
372
    ui->passPath->setText(pass);
×
373
  }
374
  usePass(!pass.isEmpty());
×
375
  QString gpg = Util::findBinaryInPath("gpg2");
×
376
  if (gpg.isEmpty()) {
×
377
    gpg = Util::findBinaryInPath("gpg");
×
378
  }
379
  if (!gpg.isEmpty()) {
×
380
    ui->gpgPath->setText(gpg);
×
381
  }
382
  QString git = Util::findBinaryInPath("git");
×
383
  if (!git.isEmpty()) {
×
384
    ui->gitPath->setText(git);
×
385
  }
386
  QString pwgen = Util::findBinaryInPath("pwgen");
×
387
  if (!pwgen.isEmpty()) {
×
388
    ui->pwgenPath->setText(pwgen);
×
389
  }
390
}
×
391

392
/**
393
 * @brief ConfigDialog::on_radioButtonNative_clicked wrapper for
394
 * ConfigDialog::setGroupBoxState()
395
 */
396
void ConfigDialog::on_radioButtonNative_clicked() { setGroupBoxState(); }
×
397

398
/**
399
 * @brief ConfigDialog::on_radioButtonPass_clicked wrapper for
400
 * ConfigDialog::setGroupBoxState()
401
 */
402
void ConfigDialog::on_radioButtonPass_clicked() { setGroupBoxState(); }
×
403

404
/**
405
 * @brief ConfigDialog::getSecretKeys get list of secret/private keys
406
 * @return QStringList keys
407
 */
408
auto ConfigDialog::getSecretKeys() -> QStringList {
×
409
  QList<UserInfo> keys = QtPassSettings::getPass()->listKeys("", true);
×
410
  QStringList names;
×
411

412
  if (keys.empty()) {
×
413
    return names;
414
  }
415

416
  foreach (const UserInfo &sec, keys)
×
417
    names << sec.name;
×
418

419
  return names;
×
420
}
421

422
/**
423
 * @brief ConfigDialog::setGroupBoxState update checkboxes.
424
 */
425
void ConfigDialog::setGroupBoxState() {
16✔
426
  bool state = ui->radioButtonPass->isChecked();
16✔
427
  ui->groupBoxNative->setEnabled(!state);
16✔
428
  ui->groupBoxPass->setEnabled(state);
16✔
429
  if (state) {
16✔
430
    // pass mode: disable all password generation controls
431
    ui->spinBoxPasswordLength->setEnabled(false);
×
432
    ui->checkBoxUsePwgen->setEnabled(false);
×
433
    ui->checkBoxAvoidCapitals->setEnabled(false);
×
434
    ui->checkBoxUseSymbols->setEnabled(false);
×
435
    ui->checkBoxLessRandom->setEnabled(false);
×
436
    ui->checkBoxAvoidNumbers->setEnabled(false);
×
437
    ui->labelPasswordChars->setEnabled(false);
×
438
    ui->passwordCharTemplateSelector->setEnabled(false);
×
439
    ui->lineEditPasswordChars->setEnabled(false);
×
440
  } else {
441
    // native mode: restore pwgen/charset state from existing handlers
442
    ui->spinBoxPasswordLength->setEnabled(true);
16✔
443
    ui->checkBoxUsePwgen->setEnabled(!ui->pwgenPath->text().isEmpty());
16✔
444
    on_checkBoxUsePwgen_clicked();
16✔
445
  }
446
}
16✔
447

448
/**
449
 * @brief ConfigDialog::selectExecutable pop-up to choose an executable.
450
 * @return
451
 */
452
auto ConfigDialog::selectExecutable() -> QString {
×
453
  QFileDialog dialog(this);
×
454
  dialog.setFileMode(QFileDialog::ExistingFile);
×
455
  dialog.setOption(QFileDialog::ReadOnly);
×
456
  if (dialog.exec()) {
×
457
    return dialog.selectedFiles().constFirst();
×
458
  }
459

460
  return {};
461
}
×
462

463
/**
464
 * @brief ConfigDialog::selectFolder pop-up to choose a folder.
465
 * @return
466
 */
467
auto ConfigDialog::selectFolder() -> QString {
×
468
  QFileDialog dialog(this);
×
469
  dialog.setFileMode(QFileDialog::Directory);
×
470
  dialog.setFilter(QDir::NoFilter);
×
471
  dialog.setOption(QFileDialog::ShowDirsOnly);
×
472
  if (dialog.exec()) {
×
473
    return dialog.selectedFiles().constFirst();
×
474
  }
475

476
  return {};
477
}
×
478

479
/**
480
 * @brief ConfigDialog::on_toolButtonGit_clicked get git application.
481
 * Enable checkboxes if found.
482
 */
483
void ConfigDialog::on_toolButtonGit_clicked() {
×
484
  QString git = selectExecutable();
×
485
  bool state = !git.isEmpty();
×
486
  if (state) {
×
487
    ui->gitPath->setText(git);
×
488
  } else {
489
    useGit(false);
×
490
  }
491

492
  ui->checkBoxUseGit->setEnabled(state);
×
493
}
×
494

495
/**
496
 * @brief ConfigDialog::on_toolButtonGpg_clicked get gpg application.
497
 */
498
void ConfigDialog::on_toolButtonGpg_clicked() {
×
499
  QString gpg = selectExecutable();
×
500
  if (!gpg.isEmpty()) {
×
501
    ui->gpgPath->setText(gpg);
×
502
  }
503
}
×
504

505
/**
506
 * @brief ConfigDialog::on_pushButtonGenerateKey_clicked open keygen dialog.
507
 */
508
void ConfigDialog::on_pushButtonGenerateKey_clicked() {
×
509
  KeygenDialog d(ui->gpgPath->text(), this);
×
510
  d.exec();
×
511
}
×
512

513
/**
514
 * @brief ConfigDialog::on_toolButtonPass_clicked get pass application.
515
 */
516
void ConfigDialog::on_toolButtonPass_clicked() {
×
517
  QString pass = selectExecutable();
×
518
  if (!pass.isEmpty()) {
×
519
    ui->passPath->setText(pass);
×
520
  }
521
}
×
522

523
/**
524
 * @brief ConfigDialog::on_toolButtonStore_clicked get .password-store
525
 * location.
526
 */
527
void ConfigDialog::on_toolButtonStore_clicked() {
×
528
  QString store = selectFolder();
×
529
  if (!store.isEmpty()) {
×
530
    ui->storePath->setText(store);
×
531
  }
532
}
×
533

534
/**
535
 * @brief ConfigDialog::on_comboBoxClipboard_activated show and hide options.
536
 * @param index of selectbox (0 = no clipboard).
537
 */
538
void ConfigDialog::on_comboBoxClipboard_activated(int index) {
52✔
539
  bool state = index > 0;
52✔
540

541
  ui->checkBoxSelection->setEnabled(state);
52✔
542
  ui->checkBoxAutoclear->setEnabled(state);
52✔
543
  ui->checkBoxHidePassword->setEnabled(state);
52✔
544
  ui->checkBoxHideContent->setEnabled(state);
52✔
545
  if (state) {
52✔
546
    ui->spinBoxAutoclearSeconds->setEnabled(ui->checkBoxAutoclear->isChecked());
36✔
547
    ui->labelSeconds->setEnabled(ui->checkBoxAutoclear->isChecked());
36✔
548
  } else {
549
    ui->spinBoxAutoclearSeconds->setEnabled(false);
16✔
550
    ui->labelSeconds->setEnabled(false);
16✔
551
  }
552
}
52✔
553

554
/**
555
 * @brief ConfigDialog::on_checkBoxAutoclearPanel_clicked enable and disable
556
 * options based on autoclear use.
557
 */
558
void ConfigDialog::on_checkBoxAutoclearPanel_clicked() {
18✔
559
  bool state = ui->checkBoxAutoclearPanel->isChecked();
18✔
560
  ui->spinBoxAutoclearPanelSeconds->setEnabled(state);
18✔
561
  ui->labelPanelSeconds->setEnabled(state);
18✔
562
}
18✔
563

564
/**
565
 * @brief ConfigDialog::useSelection set the clipboard type use from
566
 * MainWindow.
567
 * @param useSelection
568
 */
569
void ConfigDialog::useSelection(bool useSelection) {
18✔
570
  ui->checkBoxSelection->setChecked(useSelection);
18✔
571
  on_checkBoxSelection_clicked();
18✔
572
}
18✔
573

574
/**
575
 * @brief ConfigDialog::useAutoclear set the clipboard autoclear use from
576
 * MainWindow.
577
 * @param useAutoclear
578
 */
579
void ConfigDialog::useAutoclear(bool useAutoclear) {
18✔
580
  ui->checkBoxAutoclear->setChecked(useAutoclear);
18✔
581
  on_checkBoxAutoclear_clicked();
18✔
582
}
18✔
583

584
/**
585
 * @brief ConfigDialog::useAutoclearPanel set the panel autoclear use from
586
 * MainWindow.
587
 * @param useAutoclearPanel
588
 */
589
void ConfigDialog::useAutoclearPanel(bool useAutoclearPanel) {
18✔
590
  ui->checkBoxAutoclearPanel->setChecked(useAutoclearPanel);
18✔
591
  on_checkBoxAutoclearPanel_clicked();
18✔
592
}
18✔
593

594
/**
595
 * @brief ConfigDialog::on_checkBoxSelection_clicked checkbox clicked, update
596
 * state via ConfigDialog::on_comboBoxClipboard_activated
597
 */
598
void ConfigDialog::on_checkBoxSelection_clicked() {
18✔
599
  on_comboBoxClipboard_activated(ui->comboBoxClipboard->currentIndex());
18✔
600
}
18✔
601

602
/**
603
 * @brief ConfigDialog::on_checkBoxAutoclear_clicked checkbox clicked, update
604
 * state via ConfigDialog::on_comboBoxClipboard_activated
605
 */
606
void ConfigDialog::on_checkBoxAutoclear_clicked() {
18✔
607
  on_comboBoxClipboard_activated(ui->comboBoxClipboard->currentIndex());
18✔
608
}
18✔
609

610
/**
611
 * @brief ConfigDialog::genKey tunnel function to make MainWindow generate a
612
 * gpg key pair.
613
 * @param batch
614
 * @param dialog
615
 */
616
void ConfigDialog::genKey(const QString &batch, QDialog *dialog) {
×
617
  mainWindow->generateKeyPair(batch, dialog);
×
618
}
×
619

620
/**
621
 * @brief ConfigDialog::setProfiles set the profiles and chosen profile from
622
 * MainWindow.
623
 * @param profiles
624
 * @param profile
625
 */
626
void ConfigDialog::setProfiles(QHash<QString, QHash<QString, QString>> profiles,
16✔
627
                               const QString &currentProfile) {
628
  if (profiles.contains("")) {
16✔
629
    profiles.remove("");
×
630
    // remove weird "" key value pairs
631
  }
632

633
  // Cache profiles for use in onProfileTableSelectionChanged
634
  m_profiles = profiles;
16✔
635

636
  // Populate with sorting disabled: writing into the sort column re-sorts the
637
  // table between setItem calls, so the path/signingKey cells would land in a
638
  // different profile's row — corrupting the display and destroying data on
639
  // save. Restore the previous sort state and locate the current row by item
640
  // pointer afterwards.
641
  bool sortingEnabled = ui->profileTable->isSortingEnabled();
16✔
642
  ui->profileTable->setSortingEnabled(false);
16✔
643

644
  ui->profileTable->setRowCount(static_cast<int>(profiles.count()));
32✔
645
  QHashIterator<QString, QHash<QString, QString>> i(profiles);
16✔
646
  int n = 0;
647
  QTableWidgetItem *currentItem = nullptr;
648
  while (i.hasNext()) {
16✔
649
    i.next();
650
    if (!i.value().isEmpty() && !i.key().isEmpty()) {
19✔
651
      auto *nameItem = new QTableWidgetItem(i.key());
19✔
652
      ui->profileTable->setItem(n, 0, nameItem);
19✔
653
      ui->profileTable->setItem(n, 1,
19✔
654
                                new QTableWidgetItem(i.value().value("path")));
38✔
655
      ui->profileTable->setItem(
19✔
656
          n, 2, new QTableWidgetItem(i.value().value("signingKey")));
38✔
657
      if (i.key() == currentProfile) {
19✔
658
        currentItem = nameItem;
659
      }
660
    }
661
    ++n;
19✔
662
  }
663

664
  ui->profileTable->setSortingEnabled(sortingEnabled);
16✔
665

666
  if (currentItem != nullptr) {
16✔
NEW
667
    ui->profileTable->selectRow(ui->profileTable->row(currentItem));
×
668
    // Load git settings for current profile
NEW
669
    loadGitSettingsForProfile(currentProfile, m_profiles);
×
670
  }
671
}
16✔
672

673
/**
674
 * @brief Load git settings for a specific profile.
675
 * @param profileName The profile name.
676
 * @param profiles The profiles hash containing git settings.
677
 */
678
void ConfigDialog::loadGitSettingsForProfile(
1✔
679
    const QString &profileName,
680
    const QHash<QString, QHash<QString, QString>> &profiles) {
681
  if (profiles.contains(profileName)) {
1✔
682
    const QHash<QString, QString> &profile = profiles.value(profileName);
×
683
    QString useGitStr = profile.value("useGit");
×
684
    QString autoPushStr = profile.value("autoPush");
×
685
    QString autoPullStr = profile.value("autoPull");
×
686

687
    // Load profile-specific git settings if set, otherwise use global settings
688
    if (!useGitStr.isEmpty()) {
×
689
      useGit(useGitStr == "true");
×
690
      ui->checkBoxAutoPush->setEnabled(ui->checkBoxUseGit->isChecked());
×
691
      ui->checkBoxAutoPull->setEnabled(ui->checkBoxUseGit->isChecked());
×
692
      if (autoPushStr == "true" || autoPushStr == "false") {
×
693
        ui->checkBoxAutoPush->setChecked(autoPushStr == "true");
×
694
      }
695
      if (autoPullStr == "true" || autoPullStr == "false") {
×
696
        ui->checkBoxAutoPull->setChecked(autoPullStr == "true");
×
697
      }
698
    }
699
    // If not set (empty), leave global settings as-is for migration
700
  }
×
701
}
1✔
702

703
/**
704
 * @brief ConfigDialog::getProfiles return profile list.
705
 * @return
706
 */
707
auto ConfigDialog::getProfiles() -> QHash<QString, QHash<QString, QString>> {
×
708
  // Get currently selected profile name
709
  QList<QTableWidgetItem *> selected = ui->profileTable->selectedItems();
×
710
  QString selectedProfile;
×
711
  if (!selected.isEmpty()) {
×
712
    selectedProfile =
713
        ui->profileTable->item(selected.first()->row(), 0)->text();
×
714
  }
715

716
  // Use cached m_profiles to preserve git settings for non-selected profiles
717
  QHash<QString, QHash<QString, QString>> existingProfiles = m_profiles;
718

719
  QHash<QString, QHash<QString, QString>> profiles;
×
720
  // Check?
721
  for (int i = 0; i < ui->profileTable->rowCount(); ++i) {
×
722
    QHash<QString, QString> profile;
×
723
    QTableWidgetItem *pathItem = ui->profileTable->item(i, 1);
×
724
    if (nullptr != pathItem) {
×
725
      QTableWidgetItem *item = ui->profileTable->item(i, 0);
×
726
      if (item == nullptr) {
×
727
        continue;
728
      }
729
      profile["path"] = pathItem->text();
×
730
      QTableWidgetItem *signingKeyItem = ui->profileTable->item(i, 2);
×
731
      if (nullptr != signingKeyItem) {
×
732
        profile["signingKey"] = signingKeyItem->text();
×
733
      }
734

735
      // Only update git settings for the currently selected profile
736
      // Preserve existing git settings for other profiles
737
      if (item->text() == selectedProfile) {
×
738
        profile["useGit"] = ui->checkBoxUseGit->isChecked() ? "true" : "false";
×
739
        profile["autoPush"] =
×
740
            ui->checkBoxAutoPush->isChecked() ? "true" : "false";
×
741
        profile["autoPull"] =
×
742
            ui->checkBoxAutoPull->isChecked() ? "true" : "false";
×
743
      } else if (existingProfiles.contains(item->text())) {
×
744
        // Preserve existing git settings for non-selected profiles
745
        const QHash<QString, QString> &existing =
746
            existingProfiles.value(item->text());
×
747
        if (existing.contains("useGit")) {
×
748
          profile["useGit"] = existing.value("useGit");
×
749
        }
750
        if (existing.contains("autoPush")) {
×
751
          profile["autoPush"] = existing.value("autoPush");
×
752
        }
753
        if (existing.contains("autoPull")) {
×
754
          profile["autoPull"] = existing.value("autoPull");
×
755
        }
756
      }
×
757
      profiles.insert(item->text(), profile);
×
758
    }
759
  }
×
760
  // Update cache with current in-dialog state
761
  m_profiles = profiles;
×
762
  return profiles;
×
763
}
×
764

765
/**
766
 * @brief Initialize new profiles that need pass/git initialization.
767
 * @param existingProfiles The profiles that existed before the dialog was
768
 * opened.
769
 */
770
void ConfigDialog::initializeNewProfiles(
×
771
    const QHash<QString, QHash<QString, QString>> &existingProfiles) {
772
  QHash<QString, QHash<QString, QString>> newProfiles = getProfiles();
×
773

774
  // Collect keys and sort for deterministic iteration
775
  QStringList keys = newProfiles.keys();
×
776
  keys.sort();
777

778
  for (const QString &name : keys) {
×
779
    const QString &path = newProfiles.value(name).value("path");
×
780

781
    // Skip if already existed before (check by name and path)
782
    if (existingProfiles.contains(name) &&
×
783
        existingProfiles.value(name).value("path") == path) {
×
784
      continue;
×
785
    }
786

787
    // This is a new profile - create directory if needed and initialize
788
    // Note: needsInit returns false for non-existent directories, so we
789
    // must create the directory first.
790
    QString cleanPath = QDir::cleanPath(path);
×
791
    QDir dir(cleanPath);
×
792
    if (!dir.exists()) {
×
793
      if (QMessageBox::question(
×
794
              this, tr("Create profile directory?"),
×
795
              tr("Would you like to create a password store at %1?")
×
796
                  .arg(cleanPath),
×
797
              QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) {
798
        continue;
×
799
      }
800
      if (!QDir().mkpath(cleanPath)) {
×
801
        QMessageBox::warning(
×
802
            this, tr("Error"),
×
803
            tr("Could not create profile directory: %1").arg(cleanPath));
×
804
        continue;
×
805
      }
806
    }
807

808
    // Now check if initialization is needed (directory exists with no .gpg-id)
809
    if (!ProfileInit::needsInit(cleanPath)) {
×
810
      continue;
×
811
    }
812

813
    // Temporarily switch the active store so pass/git init operate on
814
    // the new profile's directory rather than the currently-saved one.
815
    const QString prevStore = QtPassSettings::getPassStore();
×
816
    QtPassSettings::setPassStore(cleanPath);
×
817

818
    // Show user selection dialog for GPG recipients
819
    // UsersDialog will run pass init when accepted
820
    const AppSettings settings = QtPassSettings::load();
×
821
    UsersDialog usersDialog(QtPassSettings::getPass(), settings, cleanPath,
822
                            this);
×
823
    usersDialog.setWindowTitle(tr("Select recipients for %1").arg(name));
×
824
    const int result = usersDialog.exec();
×
825

826
    // Use per-profile useGit setting, falling back to global if not set
827
    QString useGitStr = newProfiles.value(name).value("useGit");
×
828
    bool useGit = useGitStr.isEmpty() ? settings.useGit : useGitStr == "true";
×
829

830
    if (result == QDialog::Accepted && useGit) {
×
831
      QtPassSettings::getPass()->GitInit();
×
832
    }
833

834
    // Restore previous store setting
835
    QtPassSettings::setPassStore(prevStore);
×
836
  }
×
837
}
×
838

839
/**
840
 * @brief ConfigDialog::on_addButton_clicked add a profile row.
841
 */
842
void ConfigDialog::on_addButton_clicked() {
1✔
843
  bool sortingEnabled = ui->profileTable->isSortingEnabled();
1✔
844
  ui->profileTable->setSortingEnabled(false);
1✔
845

846
  int n = ui->profileTable->rowCount();
1✔
847
  ui->profileTable->insertRow(n);
1✔
848
  auto *nameItem = new QTableWidgetItem(tr("New Profile"));
3✔
849
  ui->profileTable->setItem(n, 0, nameItem);
1✔
850
  ui->profileTable->setItem(n, 1, new QTableWidgetItem(ui->storePath->text()));
2✔
851
  ui->profileTable->setItem(n, 2, new QTableWidgetItem());
1✔
852

853
  ui->profileTable->setSortingEnabled(sortingEnabled);
1✔
854

855
  // Re-enabling sorting may move the new row, so locate it by item pointer
856
  // rather than the stale insertion index — item(n, 0) could now be a
857
  // different, existing profile, which we would then wrongly rename/select.
858
  int currentRow = ui->profileTable->row(nameItem);
1✔
859
  ui->profileTable->selectRow(currentRow);
1✔
860
  ui->deleteButton->setEnabled(true);
1✔
861

862
  ui->profileTable->editItem(nameItem);
1✔
863
  nameItem->setSelected(true);
1✔
864

865
  validate();
1✔
866
  updateProfileStatus(currentRow);
1✔
867
}
1✔
868

869
/**
870
 * @brief ConfigDialog::on_profileTable_cellDoubleClicked open folder browser
871
 * for path column (column 1).
872
 */
873
void ConfigDialog::on_profileTable_cellDoubleClicked(int row, int column) {
×
874
  if (column == 1) {
×
875
    QString dir = selectFolder();
×
876
    if (!dir.isEmpty()) {
×
877
      // QTableWidget emits cellDoubleClicked even for cells with no item, so
878
      // guard against a null path cell rather than dereferencing it.
NEW
879
      QTableWidgetItem *pathItem = ui->profileTable->item(row, 1);
×
NEW
880
      if (pathItem != nullptr) {
×
NEW
881
        pathItem->setText(dir);
×
882
      } else {
NEW
883
        ui->profileTable->setItem(row, 1, new QTableWidgetItem(dir));
×
884
      }
885
    }
886
  }
887
}
×
888

889
/**
890
 * @brief ConfigDialog::on_deleteButton_clicked remove a profile row.
891
 */
892
void ConfigDialog::on_deleteButton_clicked() {
×
893
  QSet<int> selectedRows; //  we use a set to prevent doubles
894
  QList<QTableWidgetItem *> itemList = ui->profileTable->selectedItems();
×
895
  if (itemList.count() == 0) {
×
896
    QMessageBox::warning(this, tr("No profile selected"),
×
897
                         tr("No profile selected to delete"));
×
898
    return;
899
  }
900
  QTableWidgetItem *item;
901
  foreach (item, itemList)
×
902
    selectedRows.insert(item->row());
×
903
  // get a list, and sort it big to small
904
  QList<int> rows = selectedRows.values();
×
905
  std::sort(rows.begin(), rows.end(), std::greater<>());
×
906
  // now actually do the removing:
907
  foreach (int row, rows)
×
908
    ui->profileTable->removeRow(row);
×
909
  if (ui->profileTable->rowCount() < 1) {
×
910
    ui->deleteButton->setEnabled(false);
×
911
  }
912

913
  validate();
×
914
  updateProfileStatus(-1);
×
915
}
916

917
/**
918
 * @brief ConfigDialog::criticalMessage wrapper for showing critical messages
919
 * in a popup.
920
 * @param title
921
 * @param text
922
 */
923
void ConfigDialog::criticalMessage(const QString &title, const QString &text) {
×
924
  QMessageBox::critical(this, title, text, QMessageBox::Ok, QMessageBox::Ok);
×
925
}
×
926

927
/**
928
 * @brief Checks whether the qrencode executable is available on the system.
929
 * @example
930
 * bool result = ConfigDialog::isQrencodeAvailable();
931
 * std::cout << result << std::endl; // Expected output: true if qrencode is
932
 * found, otherwise false
933
 *
934
 * @return bool - True if qrencode is available; otherwise false. On Windows,
935
 * always returns false.
936
 */
937
auto ConfigDialog::isQrencodeAvailable() -> bool {
16✔
938
#ifdef Q_OS_WIN
939
  return false;
940
#else
941
  QProcess which;
16✔
942
  which.start("which", QStringList() << "qrencode");
48✔
943
  if (!which.waitForFinished(2000)) {
16✔
944
    which.kill();
×
945
    which.waitForFinished(500);
×
946
    return false;
947
  }
948
  QtPassSettings::setQrencodeExecutable(
16✔
949
      which.readAllStandardOutput().trimmed());
16✔
950
  return which.exitCode() == 0;
16✔
951
#endif
952
}
16✔
953

954
auto ConfigDialog::isPassOtpAvailable() -> bool {
16✔
955
#ifdef Q_OS_WIN
956
  return false;
957
#else
958
  QProcess pass;
16✔
959
  pass.start(QtPassSettings::getPassExecutable(), QStringList() << "otp"
64✔
960
                                                                << "--help");
32✔
961
  pass.waitForFinished(2000);
16✔
962
  return pass.exitCode() == 0;
32✔
963
#endif
964
}
16✔
965

966
/**
967
 * @brief ConfigDialog::wizard first-time use wizard.
968
 */
969
void ConfigDialog::wizard() {
×
970
  (void)Util::configIsValid(QtPassSettings::load());
×
971
  on_autodetectButton_clicked();
×
972

973
  if (!checkGpgExistence()) {
×
974
    return;
975
  }
976
  if (!checkSecretKeys()) {
×
977
    return;
978
  }
979
  if (!checkPasswordStore()) {
×
980
    return;
981
  }
982
  handleGpgIdFile();
×
983

984
  ui->checkBoxHidePassword->setCheckState(Qt::Checked);
×
985
}
986

987
/**
988
 * @brief Checks whether the configured GnuPG executable exists.
989
 * @example
990
 * bool result = ConfigDialog::checkGpgExistence();
991
 * std::cout << result << std::endl; // Expected output: true if GnuPG is found,
992
 * false otherwise
993
 *
994
 * @return bool - True if the GnuPG path is valid or uses a WSL command prefix;
995
 * false if the executable cannot be found and an error message is shown.
996
 */
997
auto ConfigDialog::checkGpgExistence() -> bool {
×
998
  QString gpg = ui->gpgPath->text();
×
999
  if (!gpg.startsWith("wsl ") && !QFile(gpg).exists()) {
×
1000
    criticalMessage(
×
1001
        tr("GnuPG not found"),
×
1002
#ifdef Q_OS_WIN
1003
#ifdef WINSTORE
1004
        tr("Please install GnuPG on your system.<br>Install "
1005
           "<strong>Ubuntu</strong> from the Microsoft Store to get it.<br>"
1006
           "If you already did so, make sure you started it once and<br>"
1007
           "click \"Autodetect\" in the next dialog.")
1008
#else
1009
        tr("Please install GnuPG on your system.<br>Install "
1010
           "<strong>Ubuntu</strong> from the Microsoft Store<br>or <a "
1011
           "href=\"https://www.gnupg.org/download/#sec-1-2\">download</a> it "
1012
           "from GnuPG.org")
1013
#endif
1014
#else
1015
        tr("Please install GnuPG on your system.<br>Install "
×
1016
           "<strong>gpg</strong> using your favorite package manager<br>or "
1017
           "<a "
1018
           "href=\"https://www.gnupg.org/download/#sec-1-2\">download</a> it "
1019
           "from GnuPG.org")
1020
#endif
1021
    );
1022
    return false;
×
1023
  }
1024
  return true;
1025
}
1026

1027
/**
1028
 * @brief Checks whether secret keys are available and, if needed, prompts the
1029
 * user to generate them.
1030
 * @example
1031
 * bool result = ConfigDialog.checkSecretKeys();
1032
 * std::cout << result << std::endl; // Expected output: true if keys are
1033
 * present or key generation dialog is accepted, false otherwise
1034
 *
1035
 * @return bool - Returns true when secret keys are already available or the key
1036
 * generation dialog is accepted; false if the dialog is rejected.
1037
 */
1038
auto ConfigDialog::checkSecretKeys() -> bool {
×
1039
  QString gpg = ui->gpgPath->text();
×
1040
  QStringList names = getSecretKeys();
×
1041

1042
#ifdef QT_DEBUG
1043
  dbg() << names;
1044
#endif
1045

1046
  if ((gpg.startsWith("wsl ") || QFile(gpg).exists()) && names.empty()) {
×
1047
    KeygenDialog d(gpg, this);
×
1048
    return d.exec();
×
1049
  }
×
1050
  return true;
1051
}
1052

1053
/**
1054
 * @brief Checks whether the password-store path exists and prompts the user to
1055
 * create it if it does not.
1056
 * @example
1057
 * bool result = ConfigDialog::checkPasswordStore();
1058
 * std::cout << std::boolalpha << result << std::endl; // Expected output: true
1059
 * if the store exists or is created successfully, false if creation fails
1060
 *
1061
 * @return bool - True if the password-store exists or is successfully created,
1062
 * false if creation fails.
1063
 */
1064
auto ConfigDialog::checkPasswordStore() -> bool {
×
1065
  QString passStore = ui->storePath->text();
×
1066

1067
  if (!QFile(passStore).exists()) {
×
1068
    if (QMessageBox::question(
×
1069
            this, tr("Create password-store?"),
×
1070
            tr("Would you like to create a password-store at %1?")
×
1071
                .arg(passStore),
×
1072
            QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
1073
      if (!QDir().mkdir(passStore)) {
×
1074
        QMessageBox::warning(
×
1075
            this, tr("Error"),
×
1076
            tr("Failed to create password-store at: %1").arg(passStore));
×
1077
        return false;
×
1078
      }
1079
#ifdef Q_OS_WIN
1080
      SetFileAttributes(passStore.toStdWString().c_str(),
1081
                        FILE_ATTRIBUTE_HIDDEN);
1082
#endif
1083
      if (ui->checkBoxUseGit->isChecked()) {
×
1084
        emit mainWindow->passGitInitNeeded();
×
1085
      }
1086
      mainWindow->userDialog(passStore);
×
1087
    }
1088
  }
1089
  return true;
1090
}
1091

1092
/**
1093
 * @brief Handles selection and validation of the password store's .gpg-id file.
1094
 * @example
1095
 * ConfigDialog dialog;
1096
 * dialog.handleGpgIdFile();
1097
 *
1098
 * @return void - This method does not return a value; it updates the UI flow
1099
 * and may prompt the user to choose a valid password store.
1100
 */
1101
void ConfigDialog::handleGpgIdFile() {
×
1102
  QString passStore = ui->storePath->text();
×
1103
  if (!QFile(QDir(passStore).filePath(".gpg-id")).exists()) {
×
1104
#ifdef QT_DEBUG
1105
    dbg() << ".gpg-id file does not exist";
1106
#endif
1107
    criticalMessage(tr("Password store not initialised"),
×
1108
                    tr("The folder %1 doesn't seem to be a password store or "
×
1109
                       "is not yet initialised.")
1110
                        .arg(passStore));
×
1111

1112
    while (!QFile(passStore).exists()) {
×
1113
      on_toolButtonStore_clicked();
×
1114
      if (passStore == ui->storePath->text()) {
×
1115
        return;
1116
      }
1117
      passStore = ui->storePath->text();
×
1118
    }
1119
    if (!QFile(passStore + ".gpg-id").exists()) {
×
1120
#ifdef QT_DEBUG
1121
      dbg() << ".gpg-id file still does not exist :/";
1122
#endif
1123
      mainWindow->userDialog(passStore);
×
1124
    }
1125
  }
1126
}
1127

1128
/**
1129
 * @brief ConfigDialog::useTrayIcon set preference for using trayicon.
1130
 * Enable or disable related checkboxes accordingly.
1131
 * @param useSystray
1132
 */
1133
void ConfigDialog::useTrayIcon(bool useSystray) {
16✔
1134
  if (QSystemTrayIcon::isSystemTrayAvailable()) {
16✔
1135
    ui->checkBoxUseTrayIcon->setChecked(useSystray);
×
1136
    ui->checkBoxHideOnClose->setEnabled(useSystray);
×
1137
    ui->checkBoxStartMinimized->setEnabled(useSystray);
×
1138

1139
    if (!useSystray) {
×
1140
      ui->checkBoxHideOnClose->setChecked(false);
×
1141
      ui->checkBoxStartMinimized->setChecked(false);
×
1142
    }
1143
  }
1144
}
16✔
1145

1146
/**
1147
 * @brief ConfigDialog::on_checkBoxUseTrayIcon_clicked enable and disable
1148
 * related checkboxes.
1149
 */
1150
void ConfigDialog::on_checkBoxUseTrayIcon_clicked() {
×
1151
  bool state = ui->checkBoxUseTrayIcon->isChecked();
×
1152
  ui->checkBoxHideOnClose->setEnabled(state);
×
1153
  ui->checkBoxStartMinimized->setEnabled(state);
×
1154
}
×
1155

1156
/**
1157
 * @brief ConfigDialog::closeEvent close this window.
1158
 * @param event
1159
 */
1160
void ConfigDialog::closeEvent(QCloseEvent *event) {
×
1161
  QtPassSettings::setDialogGeometry("configDialog", saveGeometry());
×
1162
  if (!isMaximized()) {
×
1163
    QtPassSettings::setDialogPos("configDialog", pos());
×
1164
    QtPassSettings::setDialogSize("configDialog", size());
×
1165
  }
1166
  QtPassSettings::setDialogMaximized("configDialog", isMaximized());
×
1167
  event->accept();
1168
}
×
1169

1170
/**
1171
 * @brief ConfigDialog::useGit set preference for using git.
1172
 * @param useGit
1173
 */
1174
void ConfigDialog::useGit(bool useGit) {
18✔
1175
  ui->checkBoxUseGit->setChecked(useGit);
18✔
1176
  on_checkBoxUseGit_clicked();
18✔
1177
}
18✔
1178

1179
/**
1180
 * @brief ConfigDialog::useOtp set preference for using otp plugin.
1181
 * @param useOtp
1182
 */
1183
void ConfigDialog::useOtp(bool useOtp) {
18✔
1184
  ui->checkBoxUseOtp->setChecked(useOtp);
18✔
1185
}
18✔
1186

1187
void ConfigDialog::useGrepSearch(bool useGrepSearch) {
18✔
1188
  ui->checkBoxUseGrepSearch->setChecked(useGrepSearch);
18✔
1189
}
18✔
1190

1191
/**
1192
 * @brief ConfigDialog::useQrencode set preference for using qrencode plugin.
1193
 * @param useQrencode
1194
 */
1195
void ConfigDialog::useQrencode(bool useQrencode) {
18✔
1196
  ui->checkBoxUseQrencode->setChecked(useQrencode);
18✔
1197
}
18✔
1198

1199
/**
1200
 * @brief ConfigDialog::on_checkBoxUseGit_clicked enable or disable related
1201
 * checkboxes.
1202
 */
1203
void ConfigDialog::on_checkBoxUseGit_clicked() {
18✔
1204
  ui->checkBoxAddGPGId->setEnabled(ui->checkBoxUseGit->isChecked());
18✔
1205
  ui->checkBoxAutoPull->setEnabled(ui->checkBoxUseGit->isChecked());
18✔
1206
  ui->checkBoxAutoPush->setEnabled(ui->checkBoxUseGit->isChecked());
18✔
1207
}
18✔
1208

1209
/**
1210
 * @brief ConfigDialog::on_toolButtonPwgen_clicked enable or disable related
1211
 * options in the interface.
1212
 */
1213
void ConfigDialog::on_toolButtonPwgen_clicked() {
×
1214
  QString pwgen = selectExecutable();
×
1215
  if (!pwgen.isEmpty()) {
×
1216
    ui->pwgenPath->setText(pwgen);
×
1217
    ui->checkBoxUsePwgen->setEnabled(true);
×
1218
  } else {
1219
    ui->checkBoxUsePwgen->setEnabled(false);
×
1220
    ui->checkBoxUsePwgen->setChecked(false);
×
1221
  }
1222
}
×
1223

1224
/**
1225
 * @brief ConfigDialog::setPwgenPath set pwgen executable path.
1226
 * Enable or disable related options in the interface.
1227
 * @param pwgen
1228
 */
1229
void ConfigDialog::setPwgenPath(const QString &pwgen) {
18✔
1230
  ui->pwgenPath->setText(pwgen);
18✔
1231
  if (pwgen.isEmpty()) {
18✔
1232
    ui->checkBoxUsePwgen->setChecked(false);
1✔
1233
    ui->checkBoxUsePwgen->setEnabled(false);
1✔
1234
  }
1235
  on_checkBoxUsePwgen_clicked();
18✔
1236
}
18✔
1237

1238
/**
1239
 * @brief ConfigDialog::on_checkBoxUsePwgen_clicked enable or disable related
1240
 * options in the interface.
1241
 */
1242
void ConfigDialog::on_checkBoxUsePwgen_clicked() {
53✔
1243
  if (ui->radioButtonPass->isChecked())
53✔
1244
    return;
1245
  bool usePwgen = ui->checkBoxUsePwgen->isChecked();
53✔
1246
  ui->checkBoxAvoidCapitals->setEnabled(usePwgen);
53✔
1247
  ui->checkBoxAvoidNumbers->setEnabled(usePwgen);
53✔
1248
  ui->checkBoxLessRandom->setEnabled(usePwgen);
53✔
1249
  ui->checkBoxUseSymbols->setEnabled(usePwgen);
53✔
1250
  ui->lineEditPasswordChars->setEnabled(!usePwgen);
53✔
1251
  ui->labelPasswordChars->setEnabled(!usePwgen);
53✔
1252
  ui->passwordCharTemplateSelector->setEnabled(!usePwgen);
53✔
1253
}
1254

1255
/**
1256
 * @brief ConfigDialog::usePwgen set preference for using pwgen (can be
1257
 * overruled by empty pwgenPath).
1258
 * enable or disable related options in the interface via
1259
 * ConfigDialog::on_checkBoxUsePwgen_clicked
1260
 * @param usePwgen
1261
 */
1262
void ConfigDialog::usePwgen(bool usePwgen) {
19✔
1263
  if (ui->pwgenPath->text().isEmpty()) {
38✔
1264
    usePwgen = false;
1265
  }
1266
  ui->checkBoxUsePwgen->setChecked(usePwgen);
19✔
1267
  on_checkBoxUsePwgen_clicked();
19✔
1268
}
19✔
1269

1270
void ConfigDialog::setPasswordConfiguration(
20✔
1271
    const PasswordConfiguration &config) {
1272
  // Retain the custom charset so it survives even when a builtin set is the
1273
  // active selection (the line edit then shows the builtin's characters).
1274
  m_customPasswordChars = config.Characters[PasswordConfiguration::CUSTOM];
20✔
1275
  ui->spinBoxPasswordLength->setValue(config.length);
20✔
1276
  ui->passwordCharTemplateSelector->setCurrentIndex(config.selected);
20✔
1277
  if (config.selected != PasswordConfiguration::CUSTOM) {
20✔
1278
    ui->lineEditPasswordChars->setEnabled(false);
18✔
1279
  }
1280
  ui->lineEditPasswordChars->setText(config.Characters[config.selected]);
20✔
1281
}
20✔
1282

1283
auto ConfigDialog::getPasswordConfiguration() -> PasswordConfiguration {
4✔
1284
  PasswordConfiguration config;
4✔
1285
  config.length = ui->spinBoxPasswordLength->value();
4✔
1286
  config.selected = static_cast<PasswordConfiguration::characterSet>(
4✔
1287
      ui->passwordCharTemplateSelector->currentIndex());
4✔
1288
  // The line edit only holds the user's custom charset while CUSTOM is
1289
  // selected; for a builtin selection it shows that builtin's characters.
1290
  // Reading it unconditionally would overwrite the saved custom charset with a
1291
  // builtin string, so fall back to the retained custom value in that case.
1292
  if (config.selected == PasswordConfiguration::CUSTOM) {
4✔
1293
    config.Characters[PasswordConfiguration::CUSTOM] =
1294
        ui->lineEditPasswordChars->text();
4✔
1295
  } else {
1296
    config.Characters[PasswordConfiguration::CUSTOM] = m_customPasswordChars;
2✔
1297
  }
1298
  return config;
4✔
1299
}
×
1300

1301
/**
1302
 * @brief ConfigDialog::on_passwordCharTemplateSelector_activated sets the
1303
 * passwordChar Template
1304
 * combo box to the desired entry
1305
 * @param entry of
1306
 */
1307
void ConfigDialog::on_passwordCharTemplateSelector_activated(int index) {
×
1308
  ui->lineEditPasswordChars->setText(
×
1309
      QtPassSettings::getPasswordConfiguration().Characters[index]);
×
1310
  if (index == PasswordConfiguration::CUSTOM) {
×
1311
    ui->lineEditPasswordChars->setEnabled(true);
×
1312
  } else {
1313
    ui->lineEditPasswordChars->setEnabled(false);
×
1314
  }
1315
}
×
1316

1317
/**
1318
 * @brief ConfigDialog::on_checkBoxUseTemplate_clicked enable or disable the
1319
 * template field and options.
1320
 */
1321
void ConfigDialog::on_checkBoxUseTemplate_clicked() {
18✔
1322
  ui->plainTextEditTemplate->setEnabled(ui->checkBoxUseTemplate->isChecked());
18✔
1323
  ui->checkBoxTemplateAllFields->setEnabled(
18✔
1324
      ui->checkBoxUseTemplate->isChecked());
18✔
1325
}
18✔
1326

1327
void ConfigDialog::onProfileTableItemChanged(QTableWidgetItem *item) {
33✔
1328
  validate(item);
33✔
1329
  updateProfileStatus(item ? item->row() : -1);
33✔
1330
}
33✔
1331

1332
void ConfigDialog::onProfileTableSelectionChanged() {
1✔
1333
  QList<QTableWidgetItem *> selected = ui->profileTable->selectedItems();
1✔
1334
  if (selected.isEmpty()) {
1✔
1335
    return;
1336
  }
1337
  QTableWidgetItem *nameItem =
1338
      ui->profileTable->item(selected.first()->row(), 0);
1✔
1339
  if (nameItem == nullptr) {
1✔
1340
    return;
1341
  }
1342
  QString profileName = nameItem->text();
1✔
1343
  loadGitSettingsForProfile(profileName, m_profiles);
1✔
1344
}
1345

1346
/**
1347
 * @brief Update status bar with profile preview for given row.
1348
 * @param row The row index to preview, or -1 to clear.
1349
 */
1350
void ConfigDialog::updateProfileStatus(int row) {
34✔
1351
  if (row < 0 || row >= ui->profileTable->rowCount()) {
34✔
1352
    ui->statusLabel->setText(QString());
×
1353
    return;
×
1354
  }
1355

1356
  QTableWidgetItem *nameItem = ui->profileTable->item(row, 0);
34✔
1357
  QTableWidgetItem *pathItem = ui->profileTable->item(row, 1);
34✔
1358

1359
  QString statusMessage;
34✔
1360
  if (nameItem && !nameItem->text().isEmpty() && pathItem &&
99✔
1361
      !pathItem->text().isEmpty()) {
96✔
1362
    QDir dir(QDir::cleanPath(pathItem->text()));
31✔
1363
    if (!dir.exists()) {
31✔
1364
      statusMessage = tr("New profile: %1 at %2")
31✔
1365
                          .arg(nameItem->text())
62✔
1366
                          .arg(QDir::cleanPath(pathItem->text()));
93✔
1367
    } else {
1368
      statusMessage = tr("Profile: %1 at %2")
×
1369
                          .arg(nameItem->text())
×
1370
                          .arg(QDir::cleanPath(pathItem->text()));
×
1371
    }
1372
  } else {
31✔
1373
    statusMessage = tr("Fill in all required fields");
3✔
1374
  }
1375

1376
  ui->statusLabel->setText(statusMessage);
34✔
1377
}
1378

1379
/**
1380
 * @brief ConfigDialog::useTemplate set preference for using templates.
1381
 * @param useTemplate
1382
 */
1383
void ConfigDialog::useTemplate(bool useTemplate) {
18✔
1384
  ui->checkBoxUseTemplate->setChecked(useTemplate);
18✔
1385
  on_checkBoxUseTemplate_clicked();
18✔
1386
}
18✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc