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

IJHack / QtPass / 27699675724

17 Jun 2026 03:18PM UTC coverage: 57.104% (-0.02%) from 57.128%
27699675724

push

github

web-flow
refactor(#1511): inject Pass* into StoreModel; inject gpgExe into KeygenDialog (#1557)

* refactor(#1511): inject Pass* into StoreModel; inject gpgExe into KeygenDialog

StoreModel:
- Add Pass *m_pass member and setPass(Pass*) method; remove
  qtpasssettings.h dependency from storemodel.cpp
- executeDropAction: use store member instead of QtPassSettings::getPassStore()
- performDrop: de-static, guard on m_pass, use m_pass->Move/Copy instead of
  QtPassSettings::getPass()->Move/Copy
- MainWindow wires proxyModel.setPass(QtPassSettings::getPass()) at init

KeygenDialog:
- Constructor gains explicit QString &gpgExe parameter (mirrors ImportKeyDialog)
- m_gpgExe stored and passed to Pass::getDefaultKeyTemplate instead of
  QtPassSettings::getGpgExecutable()
- configdialog.cpp callers updated: on_pushButtonGenerateKey_clicked passes
  ui->gpgPath->text(); wizard() passes local gpg variable already in scope

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(#1557): rebind StoreModel backend after settings save; drop stale m_gpgExe member

- mainwindow.cpp config(): after accepting config dialog, call
  proxyModel.setPass(activePass) alongside the existing updateEnv() so
  StoreModel::m_pass tracks the correct backend when usePass mode
  toggles (PassBackendFactory::invalidate resets the cached pointer and
  getPass() may return the other backend on next call)
- keygendialog.h/cpp: drop m_gpgExe member; gpgExe is used only once
  (Pass::getDefaultKeyTemplate) in the constructor body so storing it
  for the dialog's lifetime is unnecessary — pass the parameter directly

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

5 of 14 new or added lines in 4 files covered. (35.71%)

1 existing line in 1 file now uncovered.

3963 of 6940 relevant lines covered (57.1%)

23.25 hits per line

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

35.8
/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)
13✔
34
    : QDialog(parent), ui(new Ui::ConfigDialog) {
13✔
35
  mainWindow = parent;
13✔
36
  ui->setupUi(this);
13✔
37

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

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

53
  if (!QSystemTrayIcon::isSystemTrayAvailable()) {
13✔
54
    ui->checkBoxUseTrayIcon->setEnabled(false);
13✔
55
    ui->checkBoxUseTrayIcon->setToolTip(tr("System tray is not available"));
26✔
56
    ui->checkBoxHideOnClose->setEnabled(false);
13✔
57
    ui->checkBoxStartMinimized->setEnabled(false);
13✔
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()) {
13✔
67
    ui->checkBoxUseOtp->setEnabled(false);
13✔
68
    ui->checkBoxUseOtp->setToolTip(
13✔
69
        tr("Pass OTP extension needs to be installed"));
13✔
70
  }
71

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

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

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

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

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

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

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

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

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

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

132
  ui->checkBoxAvoidCapitals->setChecked(settings.avoidCapitals);
13✔
133
  ui->checkBoxAvoidNumbers->setChecked(settings.avoidNumbers);
13✔
134
  ui->checkBoxLessRandom->setChecked(settings.lessRandom);
13✔
135
  ui->checkBoxUseSymbols->setChecked(settings.useSymbols);
13✔
136
  ui->plainTextEditTemplate->setPlainText(settings.passTemplate);
13✔
137
  ui->checkBoxTemplateAllFields->setChecked(settings.templateAllFields);
13✔
138
  ui->checkBoxShowProcessOutput->setChecked(settings.showProcessOutput);
13✔
139
  ui->checkBoxAutoPull->setChecked(settings.autoPull);
13✔
140
  ui->checkBoxAutoPush->setChecked(settings.autoPush);
13✔
141
  ui->checkBoxAlwaysOnTop->setChecked(settings.alwaysOnTop);
13✔
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);
13✔
146
  useAutoclear(settings.useAutoclear);
13✔
147
  useAutoclearPanel(settings.useAutoclearPanel);
13✔
148
  useTrayIcon(settings.useTrayIcon);
13✔
149
  useGit(settings.useGit);
13✔
150
  useOtp(settings.useOtp);
13✔
151
  useGrepSearch(settings.useGrepSearch);
13✔
152
  useQrencode(settings.useQrencode);
13✔
153
  usePwgen(settings.usePwgen);
13✔
154
  useTemplate(settings.useTemplate);
13✔
155
}
13✔
156

157
auto ConfigDialog::readSettings() -> AppSettings {
×
158
  // Start from the persisted settings so keys this dialog does not own
159
  // (window geometry, WebDAV, profiles, etc.) survive the save.
160
  AppSettings settings = QtPassSettings::load();
×
161

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

212
  return settings;
×
213
}
×
214

215
/**
216
 * @brief ConfigDialog::~ConfigDialog config destructor.
217
 */
218
ConfigDialog::~ConfigDialog() = default;
13✔
219

220
/**
221
 * @brief ConfigDialog::setGitPath set the git executable path.
222
 * Make sure the checkBoxUseGit is updated.
223
 * @param path
224
 */
225
void ConfigDialog::setGitPath(const QString &path) {
13✔
226
  ui->gitPath->setText(path);
13✔
227
  ui->checkBoxUseGit->setEnabled(!path.isEmpty());
13✔
228
  if (path.isEmpty()) {
13✔
229
    useGit(false);
×
230
  }
231
}
13✔
232

233
/**
234
 * @brief ConfigDialog::usePass set whether or not we want to use pass.
235
 * Update radio buttons accordingly.
236
 * @param usePass
237
 */
238
void ConfigDialog::usePass(bool usePass) {
13✔
239
  ui->radioButtonNative->setChecked(!usePass);
13✔
240
  ui->radioButtonPass->setChecked(usePass);
13✔
241
  setGroupBoxState();
13✔
242
}
13✔
243

244
/**
245
 * @brief Validates the configuration table fields and enables or disables the
246
 * OK button accordingly.
247
 * @example
248
 * ConfigDialog dialog;
249
 * QTableWidgetItem *item = dialog.findChild<QTableWidgetItem*>();
250
 * dialog.validate(item);
251
 *
252
 * @param QTableWidgetItem *item - The table item to validate; if null,
253
 * validates all relevant items in the profile table.
254
 * @return void - This function does not return a value.
255
 */
256
void ConfigDialog::validate(QTableWidgetItem *item) {
×
257
  bool status = true;
258

259
  if (item == nullptr) {
×
260
    for (int i = 0; i < ui->profileTable->rowCount(); i++) {
×
261
      for (int j = 0; j < ui->profileTable->columnCount(); j++) {
×
262
        QTableWidgetItem *_item = ui->profileTable->item(i, j);
×
263

264
        if (_item->text().isEmpty() && j != 2) {
×
265
          _item->setBackground(Qt::red);
×
266
          _item->setToolTip(tr("This field is required"));
×
267
          status = false;
268
          break;
269
        } else {
270
          _item->setBackground(QBrush());
×
271
          _item->setToolTip(QString());
×
272
        }
273
      }
274

275
      if (!status) {
276
        break;
277
      }
278
    }
279
  } else {
280
    if (item->text().isEmpty() && item->column() != 2) {
×
281
      item->setBackground(Qt::red);
×
282
      item->setToolTip(tr("This field is required"));
×
283
      status = false;
284
    } else {
285
      item->setBackground(QBrush());
×
286
      item->setToolTip(QString());
×
287
    }
288
  }
289

290
  ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(status);
×
291
}
×
292

293
/**
294
 * @brief Saves the configuration dialog settings to persistent application
295
 * settings.
296
 * @example
297
 * ConfigDialog dialog;
298
 * dialog.on_accepted();
299
 * // Expected output: All UI-selected configuration values are stored via
300
 * QtPassSettings.
301
 *
302
 * @return void - This method does not return a value.
303
 */
304
void ConfigDialog::on_accepted() {
×
305
  const QString sshAuthSockOverride = ui->sshAuthSockOverride->text().trimmed();
×
306
  if (!sshAuthSockOverride.isEmpty()) {
×
307
    QString reason;
×
308
    switch (SshAuthSock::overrideStatus(sshAuthSockOverride)) {
×
309
    case SshAuthSock::OverrideStatus::Valid:
310
      break;
311
    case SshAuthSock::OverrideStatus::DoesNotExist:
×
312
      reason = tr("The path does not exist.");
×
313
      break;
×
314
    case SshAuthSock::OverrideStatus::NotReadable:
×
315
      reason = tr("The path is not readable.");
×
316
      break;
×
317
    case SshAuthSock::OverrideStatus::NotUnixDomainSocket:
×
318
      reason = tr("The path is not a Unix domain socket.");
×
319
      break;
×
320
    }
321
    if (!reason.isEmpty()) {
×
322
      QMessageBox::warning(
×
323
          this, tr("Potentially invalid SSH_AUTH_SOCK override"),
×
324
          tr("The SSH_AUTH_SOCK override value may be invalid.\n\n%1\n\n"
×
325
             "The value will still be saved as entered.")
326
              .arg(reason));
×
327
    }
328
  }
329

330
  const QHash<QString, QHash<QString, QString>> existingProfiles =
331
      QtPassSettings::getProfiles();
×
332

333
  // Persist via the facade, which also invalidates the cached Pass backend so
334
  // a changed "use pass" mode takes effect.
335
  QtPassSettings::save(readSettings());
×
336

337
  // Profiles are not part of AppSettings yet, so persist them separately.
338
  QtPassSettings::setProfiles(getProfiles());
×
339

340
  // Initialize new profiles that need pass/git initialization
341
  initializeNewProfiles(existingProfiles);
×
342
}
×
343

344
/**
345
 * @brief Automatically detects required external binaries in the system PATH
346
 * and updates the dialog fields.
347
 * @example
348
 * ConfigDialog configDialog;
349
 * configDialog.on_autodetectButton_clicked();
350
 *
351
 * @return void - This function does not return a value.
352
 */
353
void ConfigDialog::on_autodetectButton_clicked() {
×
354
  QString pass = Util::findBinaryInPath("pass");
×
355
  if (!pass.isEmpty()) {
×
356
    ui->passPath->setText(pass);
×
357
  }
358
  usePass(!pass.isEmpty());
×
359
  QString gpg = Util::findBinaryInPath("gpg2");
×
360
  if (gpg.isEmpty()) {
×
361
    gpg = Util::findBinaryInPath("gpg");
×
362
  }
363
  if (!gpg.isEmpty()) {
×
364
    ui->gpgPath->setText(gpg);
×
365
  }
366
  QString git = Util::findBinaryInPath("git");
×
367
  if (!git.isEmpty()) {
×
368
    ui->gitPath->setText(git);
×
369
  }
370
  QString pwgen = Util::findBinaryInPath("pwgen");
×
371
  if (!pwgen.isEmpty()) {
×
372
    ui->pwgenPath->setText(pwgen);
×
373
  }
374
}
×
375

376
/**
377
 * @brief ConfigDialog::on_radioButtonNative_clicked wrapper for
378
 * ConfigDialog::setGroupBoxState()
379
 */
380
void ConfigDialog::on_radioButtonNative_clicked() { setGroupBoxState(); }
×
381

382
/**
383
 * @brief ConfigDialog::on_radioButtonPass_clicked wrapper for
384
 * ConfigDialog::setGroupBoxState()
385
 */
386
void ConfigDialog::on_radioButtonPass_clicked() { setGroupBoxState(); }
×
387

388
/**
389
 * @brief ConfigDialog::getSecretKeys get list of secret/private keys
390
 * @return QStringList keys
391
 */
392
auto ConfigDialog::getSecretKeys() -> QStringList {
×
393
  QList<UserInfo> keys = QtPassSettings::getPass()->listKeys("", true);
×
394
  QStringList names;
×
395

396
  if (keys.empty()) {
×
397
    return names;
398
  }
399

400
  foreach (const UserInfo &sec, keys)
×
401
    names << sec.name;
×
402

403
  return names;
×
404
}
405

406
/**
407
 * @brief ConfigDialog::setGroupBoxState update checkboxes.
408
 */
409
void ConfigDialog::setGroupBoxState() {
13✔
410
  bool state = ui->radioButtonPass->isChecked();
13✔
411
  ui->groupBoxNative->setEnabled(!state);
13✔
412
  ui->groupBoxPass->setEnabled(state);
13✔
413
  if (state) {
13✔
414
    // pass mode: disable all password generation controls
415
    ui->spinBoxPasswordLength->setEnabled(false);
×
416
    ui->checkBoxUsePwgen->setEnabled(false);
×
417
    ui->checkBoxAvoidCapitals->setEnabled(false);
×
418
    ui->checkBoxUseSymbols->setEnabled(false);
×
419
    ui->checkBoxLessRandom->setEnabled(false);
×
420
    ui->checkBoxAvoidNumbers->setEnabled(false);
×
421
    ui->labelPasswordChars->setEnabled(false);
×
422
    ui->passwordCharTemplateSelector->setEnabled(false);
×
423
    ui->lineEditPasswordChars->setEnabled(false);
×
424
  } else {
425
    // native mode: restore pwgen/charset state from existing handlers
426
    ui->spinBoxPasswordLength->setEnabled(true);
13✔
427
    ui->checkBoxUsePwgen->setEnabled(!ui->pwgenPath->text().isEmpty());
13✔
428
    on_checkBoxUsePwgen_clicked();
13✔
429
  }
430
}
13✔
431

432
/**
433
 * @brief ConfigDialog::selectExecutable pop-up to choose an executable.
434
 * @return
435
 */
436
auto ConfigDialog::selectExecutable() -> QString {
×
437
  QFileDialog dialog(this);
×
438
  dialog.setFileMode(QFileDialog::ExistingFile);
×
439
  dialog.setOption(QFileDialog::ReadOnly);
×
440
  if (dialog.exec()) {
×
441
    return dialog.selectedFiles().constFirst();
×
442
  }
443

444
  return {};
445
}
×
446

447
/**
448
 * @brief ConfigDialog::selectFolder pop-up to choose a folder.
449
 * @return
450
 */
451
auto ConfigDialog::selectFolder() -> QString {
×
452
  QFileDialog dialog(this);
×
453
  dialog.setFileMode(QFileDialog::Directory);
×
454
  dialog.setFilter(QDir::NoFilter);
×
455
  dialog.setOption(QFileDialog::ShowDirsOnly);
×
456
  if (dialog.exec()) {
×
457
    return dialog.selectedFiles().constFirst();
×
458
  }
459

460
  return {};
461
}
×
462

463
/**
464
 * @brief ConfigDialog::on_toolButtonGit_clicked get git application.
465
 * Enable checkboxes if found.
466
 */
467
void ConfigDialog::on_toolButtonGit_clicked() {
×
468
  QString git = selectExecutable();
×
469
  bool state = !git.isEmpty();
×
470
  if (state) {
×
471
    ui->gitPath->setText(git);
×
472
  } else {
473
    useGit(false);
×
474
  }
475

476
  ui->checkBoxUseGit->setEnabled(state);
×
477
}
×
478

479
/**
480
 * @brief ConfigDialog::on_toolButtonGpg_clicked get gpg application.
481
 */
482
void ConfigDialog::on_toolButtonGpg_clicked() {
×
483
  QString gpg = selectExecutable();
×
484
  if (!gpg.isEmpty()) {
×
485
    ui->gpgPath->setText(gpg);
×
486
  }
487
}
×
488

489
/**
490
 * @brief ConfigDialog::on_pushButtonGenerateKey_clicked open keygen dialog.
491
 */
492
void ConfigDialog::on_pushButtonGenerateKey_clicked() {
×
NEW
493
  KeygenDialog d(ui->gpgPath->text(), this);
×
494
  d.exec();
×
495
}
×
496

497
/**
498
 * @brief ConfigDialog::on_toolButtonPass_clicked get pass application.
499
 */
500
void ConfigDialog::on_toolButtonPass_clicked() {
×
501
  QString pass = selectExecutable();
×
502
  if (!pass.isEmpty()) {
×
503
    ui->passPath->setText(pass);
×
504
  }
505
}
×
506

507
/**
508
 * @brief ConfigDialog::on_toolButtonStore_clicked get .password-store
509
 * location.
510
 */
511
void ConfigDialog::on_toolButtonStore_clicked() {
×
512
  QString store = selectFolder();
×
513
  if (!store.isEmpty()) {
×
514
    ui->storePath->setText(store);
×
515
  }
516
}
×
517

518
/**
519
 * @brief ConfigDialog::on_comboBoxClipboard_activated show and hide options.
520
 * @param index of selectbox (0 = no clipboard).
521
 */
522
void ConfigDialog::on_comboBoxClipboard_activated(int index) {
43✔
523
  bool state = index > 0;
43✔
524

525
  ui->checkBoxSelection->setEnabled(state);
43✔
526
  ui->checkBoxAutoclear->setEnabled(state);
43✔
527
  ui->checkBoxHidePassword->setEnabled(state);
43✔
528
  ui->checkBoxHideContent->setEnabled(state);
43✔
529
  if (state) {
43✔
530
    ui->spinBoxAutoclearSeconds->setEnabled(ui->checkBoxAutoclear->isChecked());
30✔
531
    ui->labelSeconds->setEnabled(ui->checkBoxAutoclear->isChecked());
30✔
532
  } else {
533
    ui->spinBoxAutoclearSeconds->setEnabled(false);
13✔
534
    ui->labelSeconds->setEnabled(false);
13✔
535
  }
536
}
43✔
537

538
/**
539
 * @brief ConfigDialog::on_checkBoxAutoclearPanel_clicked enable and disable
540
 * options based on autoclear use.
541
 */
542
void ConfigDialog::on_checkBoxAutoclearPanel_clicked() {
15✔
543
  bool state = ui->checkBoxAutoclearPanel->isChecked();
15✔
544
  ui->spinBoxAutoclearPanelSeconds->setEnabled(state);
15✔
545
  ui->labelPanelSeconds->setEnabled(state);
15✔
546
}
15✔
547

548
/**
549
 * @brief ConfigDialog::useSelection set the clipboard type use from
550
 * MainWindow.
551
 * @param useSelection
552
 */
553
void ConfigDialog::useSelection(bool useSelection) {
15✔
554
  ui->checkBoxSelection->setChecked(useSelection);
15✔
555
  on_checkBoxSelection_clicked();
15✔
556
}
15✔
557

558
/**
559
 * @brief ConfigDialog::useAutoclear set the clipboard autoclear use from
560
 * MainWindow.
561
 * @param useAutoclear
562
 */
563
void ConfigDialog::useAutoclear(bool useAutoclear) {
15✔
564
  ui->checkBoxAutoclear->setChecked(useAutoclear);
15✔
565
  on_checkBoxAutoclear_clicked();
15✔
566
}
15✔
567

568
/**
569
 * @brief ConfigDialog::useAutoclearPanel set the panel autoclear use from
570
 * MainWindow.
571
 * @param useAutoclearPanel
572
 */
573
void ConfigDialog::useAutoclearPanel(bool useAutoclearPanel) {
15✔
574
  ui->checkBoxAutoclearPanel->setChecked(useAutoclearPanel);
15✔
575
  on_checkBoxAutoclearPanel_clicked();
15✔
576
}
15✔
577

578
/**
579
 * @brief ConfigDialog::on_checkBoxSelection_clicked checkbox clicked, update
580
 * state via ConfigDialog::on_comboBoxClipboard_activated
581
 */
582
void ConfigDialog::on_checkBoxSelection_clicked() {
15✔
583
  on_comboBoxClipboard_activated(ui->comboBoxClipboard->currentIndex());
15✔
584
}
15✔
585

586
/**
587
 * @brief ConfigDialog::on_checkBoxAutoclear_clicked checkbox clicked, update
588
 * state via ConfigDialog::on_comboBoxClipboard_activated
589
 */
590
void ConfigDialog::on_checkBoxAutoclear_clicked() {
15✔
591
  on_comboBoxClipboard_activated(ui->comboBoxClipboard->currentIndex());
15✔
592
}
15✔
593

594
/**
595
 * @brief ConfigDialog::genKey tunnel function to make MainWindow generate a
596
 * gpg key pair.
597
 * @param batch
598
 * @param dialog
599
 */
600
void ConfigDialog::genKey(const QString &batch, QDialog *dialog) {
×
601
  mainWindow->generateKeyPair(batch, dialog);
×
602
}
×
603

604
/**
605
 * @brief ConfigDialog::setProfiles set the profiles and chosen profile from
606
 * MainWindow.
607
 * @param profiles
608
 * @param profile
609
 */
610
void ConfigDialog::setProfiles(QHash<QString, QHash<QString, QString>> profiles,
13✔
611
                               const QString &currentProfile) {
612
  if (profiles.contains("")) {
13✔
613
    profiles.remove("");
×
614
    // remove weird "" key value pairs
615
  }
616

617
  // Cache profiles for use in onProfileTableSelectionChanged
618
  m_profiles = profiles;
13✔
619

620
  ui->profileTable->setRowCount(static_cast<int>(profiles.count()));
26✔
621
  QHashIterator<QString, QHash<QString, QString>> i(profiles);
13✔
622
  int n = 0;
623
  while (i.hasNext()) {
13✔
624
    i.next();
625
    if (!i.value().isEmpty() && !i.key().isEmpty()) {
13✔
626
      ui->profileTable->setItem(n, 0, new QTableWidgetItem(i.key()));
13✔
627
      ui->profileTable->setItem(n, 1,
13✔
628
                                new QTableWidgetItem(i.value().value("path")));
26✔
629
      ui->profileTable->setItem(
13✔
630
          n, 2, new QTableWidgetItem(i.value().value("signingKey")));
26✔
631
      if (i.key() == currentProfile) {
13✔
632
        ui->profileTable->selectRow(n);
×
633
        // Load git settings for current profile
634
        loadGitSettingsForProfile(currentProfile, m_profiles);
×
635
      }
636
    }
637
    ++n;
13✔
638
  }
639
}
13✔
640

641
/**
642
 * @brief Load git settings for a specific profile.
643
 * @param profileName The profile name.
644
 * @param profiles The profiles hash containing git settings.
645
 */
646
void ConfigDialog::loadGitSettingsForProfile(
×
647
    const QString &profileName,
648
    const QHash<QString, QHash<QString, QString>> &profiles) {
649
  if (profiles.contains(profileName)) {
×
650
    const QHash<QString, QString> &profile = profiles.value(profileName);
×
651
    QString useGitStr = profile.value("useGit");
×
652
    QString autoPushStr = profile.value("autoPush");
×
653
    QString autoPullStr = profile.value("autoPull");
×
654

655
    // Load profile-specific git settings if set, otherwise use global settings
656
    if (!useGitStr.isEmpty()) {
×
657
      useGit(useGitStr == "true");
×
658
      ui->checkBoxAutoPush->setEnabled(ui->checkBoxUseGit->isChecked());
×
659
      ui->checkBoxAutoPull->setEnabled(ui->checkBoxUseGit->isChecked());
×
660
      if (autoPushStr == "true" || autoPushStr == "false") {
×
661
        ui->checkBoxAutoPush->setChecked(autoPushStr == "true");
×
662
      }
663
      if (autoPullStr == "true" || autoPullStr == "false") {
×
664
        ui->checkBoxAutoPull->setChecked(autoPullStr == "true");
×
665
      }
666
    }
667
    // If not set (empty), leave global settings as-is for migration
668
  }
×
669
}
×
670

671
/**
672
 * @brief ConfigDialog::getProfiles return profile list.
673
 * @return
674
 */
675
auto ConfigDialog::getProfiles() -> QHash<QString, QHash<QString, QString>> {
×
676
  // Get currently selected profile name
677
  QList<QTableWidgetItem *> selected = ui->profileTable->selectedItems();
×
678
  QString selectedProfile;
×
679
  if (!selected.isEmpty()) {
×
680
    selectedProfile =
681
        ui->profileTable->item(selected.first()->row(), 0)->text();
×
682
  }
683

684
  // Use cached m_profiles to preserve git settings for non-selected profiles
685
  QHash<QString, QHash<QString, QString>> existingProfiles = m_profiles;
686

687
  QHash<QString, QHash<QString, QString>> profiles;
×
688
  // Check?
689
  for (int i = 0; i < ui->profileTable->rowCount(); ++i) {
×
690
    QHash<QString, QString> profile;
×
691
    QTableWidgetItem *pathItem = ui->profileTable->item(i, 1);
×
692
    if (nullptr != pathItem) {
×
693
      QTableWidgetItem *item = ui->profileTable->item(i, 0);
×
694
      if (item == nullptr) {
×
695
        continue;
696
      }
697
      profile["path"] = pathItem->text();
×
698
      QTableWidgetItem *signingKeyItem = ui->profileTable->item(i, 2);
×
699
      if (nullptr != signingKeyItem) {
×
700
        profile["signingKey"] = signingKeyItem->text();
×
701
      }
702

703
      // Only update git settings for the currently selected profile
704
      // Preserve existing git settings for other profiles
705
      if (item->text() == selectedProfile) {
×
706
        profile["useGit"] = ui->checkBoxUseGit->isChecked() ? "true" : "false";
×
707
        profile["autoPush"] =
×
708
            ui->checkBoxAutoPush->isChecked() ? "true" : "false";
×
709
        profile["autoPull"] =
×
710
            ui->checkBoxAutoPull->isChecked() ? "true" : "false";
×
711
      } else if (existingProfiles.contains(item->text())) {
×
712
        // Preserve existing git settings for non-selected profiles
713
        const QHash<QString, QString> &existing =
714
            existingProfiles.value(item->text());
×
715
        if (existing.contains("useGit")) {
×
716
          profile["useGit"] = existing.value("useGit");
×
717
        }
718
        if (existing.contains("autoPush")) {
×
719
          profile["autoPush"] = existing.value("autoPush");
×
720
        }
721
        if (existing.contains("autoPull")) {
×
722
          profile["autoPull"] = existing.value("autoPull");
×
723
        }
724
      }
×
725
      profiles.insert(item->text(), profile);
×
726
    }
727
  }
×
728
  // Update cache with current in-dialog state
729
  m_profiles = profiles;
×
730
  return profiles;
×
731
}
×
732

733
/**
734
 * @brief Initialize new profiles that need pass/git initialization.
735
 * @param existingProfiles The profiles that existed before the dialog was
736
 * opened.
737
 */
738
void ConfigDialog::initializeNewProfiles(
×
739
    const QHash<QString, QHash<QString, QString>> &existingProfiles) {
740
  QHash<QString, QHash<QString, QString>> newProfiles = getProfiles();
×
741

742
  // Collect keys and sort for deterministic iteration
743
  QStringList keys = newProfiles.keys();
×
744
  keys.sort();
745

746
  for (const QString &name : keys) {
×
747
    const QString &path = newProfiles.value(name).value("path");
×
748

749
    // Skip if already existed before (check by name and path)
750
    if (existingProfiles.contains(name) &&
×
751
        existingProfiles.value(name).value("path") == path) {
×
752
      continue;
×
753
    }
754

755
    // This is a new profile - create directory if needed and initialize
756
    // Note: needsInit returns false for non-existent directories, so we
757
    // must create the directory first.
758
    QString cleanPath = QDir::cleanPath(path);
×
759
    QDir dir(cleanPath);
×
760
    if (!dir.exists()) {
×
761
      if (QMessageBox::question(
×
762
              this, tr("Create profile directory?"),
×
763
              tr("Would you like to create a password store at %1?")
×
764
                  .arg(cleanPath),
×
765
              QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) {
766
        continue;
×
767
      }
768
      if (!QDir().mkpath(cleanPath)) {
×
769
        QMessageBox::warning(
×
770
            this, tr("Error"),
×
771
            tr("Could not create profile directory: %1").arg(cleanPath));
×
772
        continue;
×
773
      }
774
    }
775

776
    // Now check if initialization is needed (directory exists with no .gpg-id)
777
    if (!ProfileInit::needsInit(cleanPath)) {
×
778
      continue;
×
779
    }
780

781
    // Temporarily switch the active store so pass/git init operate on
782
    // the new profile's directory rather than the currently-saved one.
783
    const QString prevStore = QtPassSettings::getPassStore();
×
784
    QtPassSettings::setPassStore(cleanPath);
×
785

786
    // Show user selection dialog for GPG recipients
787
    // UsersDialog will run pass init when accepted
788
    UsersDialog usersDialog(cleanPath, this);
×
789
    usersDialog.setWindowTitle(tr("Select recipients for %1").arg(name));
×
790
    const int result = usersDialog.exec();
×
791

792
    // Use per-profile useGit setting, falling back to global if not set
793
    QString useGitStr = newProfiles.value(name).value("useGit");
×
794
    bool useGit =
795
        useGitStr.isEmpty() ? QtPassSettings::isUseGit() : useGitStr == "true";
×
796

797
    if (result == QDialog::Accepted && useGit) {
×
798
      QtPassSettings::getPass()->GitInit();
×
799
    }
800

801
    // Restore previous store setting
802
    QtPassSettings::setPassStore(prevStore);
×
803
  }
×
804
}
×
805

806
/**
807
 * @brief ConfigDialog::on_addButton_clicked add a profile row.
808
 */
809
void ConfigDialog::on_addButton_clicked() {
×
810
  bool sortingEnabled = ui->profileTable->isSortingEnabled();
×
811
  ui->profileTable->setSortingEnabled(false);
×
812

813
  int n = ui->profileTable->rowCount();
×
814
  ui->profileTable->insertRow(n);
×
815
  ui->profileTable->setItem(n, 0, new QTableWidgetItem(tr("New Profile")));
×
816
  ui->profileTable->setItem(n, 1, new QTableWidgetItem(ui->storePath->text()));
×
817
  ui->profileTable->setItem(n, 2, new QTableWidgetItem());
×
818

819
  ui->profileTable->setSortingEnabled(sortingEnabled);
×
820

821
  int currentRow = ui->profileTable->row(ui->profileTable->item(n, 0));
×
822
  ui->profileTable->selectRow(currentRow);
×
823
  ui->deleteButton->setEnabled(true);
×
824

825
  ui->profileTable->editItem(ui->profileTable->item(currentRow, 0));
×
826
  ui->profileTable->item(currentRow, 0)->setSelected(true);
×
827

828
  validate();
×
829
  updateProfileStatus(currentRow);
×
830
}
×
831

832
/**
833
 * @brief ConfigDialog::on_profileTable_cellDoubleClicked open folder browser
834
 * for path column (column 1).
835
 */
836
void ConfigDialog::on_profileTable_cellDoubleClicked(int row, int column) {
×
837
  if (column == 1) {
×
838
    QString dir = selectFolder();
×
839
    if (!dir.isEmpty()) {
×
840
      ui->profileTable->item(row, 1)->setText(dir);
×
841
    }
842
  }
843
}
×
844

845
/**
846
 * @brief ConfigDialog::on_deleteButton_clicked remove a profile row.
847
 */
848
void ConfigDialog::on_deleteButton_clicked() {
×
849
  QSet<int> selectedRows; //  we use a set to prevent doubles
850
  QList<QTableWidgetItem *> itemList = ui->profileTable->selectedItems();
×
851
  if (itemList.count() == 0) {
×
852
    QMessageBox::warning(this, tr("No profile selected"),
×
853
                         tr("No profile selected to delete"));
×
854
    return;
855
  }
856
  QTableWidgetItem *item;
857
  foreach (item, itemList)
×
858
    selectedRows.insert(item->row());
×
859
  // get a list, and sort it big to small
860
  QList<int> rows = selectedRows.values();
×
861
  std::sort(rows.begin(), rows.end());
×
862
  // now actually do the removing:
863
  foreach (int row, rows)
×
864
    ui->profileTable->removeRow(row);
×
865
  if (ui->profileTable->rowCount() < 1) {
×
866
    ui->deleteButton->setEnabled(false);
×
867
  }
868

869
  validate();
×
870
  updateProfileStatus(-1);
×
871
}
872

873
/**
874
 * @brief ConfigDialog::criticalMessage wrapper for showing critical messages
875
 * in a popup.
876
 * @param title
877
 * @param text
878
 */
879
void ConfigDialog::criticalMessage(const QString &title, const QString &text) {
×
880
  QMessageBox::critical(this, title, text, QMessageBox::Ok, QMessageBox::Ok);
×
881
}
×
882

883
/**
884
 * @brief Checks whether the qrencode executable is available on the system.
885
 * @example
886
 * bool result = ConfigDialog::isQrencodeAvailable();
887
 * std::cout << result << std::endl; // Expected output: true if qrencode is
888
 * found, otherwise false
889
 *
890
 * @return bool - True if qrencode is available; otherwise false. On Windows,
891
 * always returns false.
892
 */
893
auto ConfigDialog::isQrencodeAvailable() -> bool {
13✔
894
#ifdef Q_OS_WIN
895
  return false;
896
#else
897
  QProcess which;
13✔
898
  which.start("which", QStringList() << "qrencode");
39✔
899
  which.waitForFinished();
13✔
900
  QtPassSettings::setQrencodeExecutable(
13✔
901
      which.readAllStandardOutput().trimmed());
13✔
902
  return which.exitCode() == 0;
26✔
903
#endif
904
}
13✔
905

906
auto ConfigDialog::isPassOtpAvailable() -> bool {
13✔
907
#ifdef Q_OS_WIN
908
  return false;
909
#else
910
  QProcess pass;
13✔
911
  pass.start(QtPassSettings::getPassExecutable(), QStringList() << "otp"
52✔
912
                                                                << "--help");
26✔
913
  pass.waitForFinished(2000);
13✔
914
  return pass.exitCode() == 0;
26✔
915
#endif
916
}
13✔
917

918
/**
919
 * @brief ConfigDialog::wizard first-time use wizard.
920
 */
921
void ConfigDialog::wizard() {
×
922
  (void)Util::configIsValid(QtPassSettings::load());
×
923
  on_autodetectButton_clicked();
×
924

925
  if (!checkGpgExistence()) {
×
926
    return;
927
  }
928
  if (!checkSecretKeys()) {
×
929
    return;
930
  }
931
  if (!checkPasswordStore()) {
×
932
    return;
933
  }
934
  handleGpgIdFile();
×
935

936
  ui->checkBoxHidePassword->setCheckState(Qt::Checked);
×
937
}
938

939
/**
940
 * @brief Checks whether the configured GnuPG executable exists.
941
 * @example
942
 * bool result = ConfigDialog::checkGpgExistence();
943
 * std::cout << result << std::endl; // Expected output: true if GnuPG is found,
944
 * false otherwise
945
 *
946
 * @return bool - True if the GnuPG path is valid or uses a WSL command prefix;
947
 * false if the executable cannot be found and an error message is shown.
948
 */
949
auto ConfigDialog::checkGpgExistence() -> bool {
×
950
  QString gpg = ui->gpgPath->text();
×
951
  if (!gpg.startsWith("wsl ") && !QFile(gpg).exists()) {
×
952
    criticalMessage(
×
953
        tr("GnuPG not found"),
×
954
#ifdef Q_OS_WIN
955
#ifdef WINSTORE
956
        tr("Please install GnuPG on your system.<br>Install "
957
           "<strong>Ubuntu</strong> from the Microsoft Store to get it.<br>"
958
           "If you already did so, make sure you started it once and<br>"
959
           "click \"Autodetect\" in the next dialog.")
960
#else
961
        tr("Please install GnuPG on your system.<br>Install "
962
           "<strong>Ubuntu</strong> from the Microsoft Store<br>or <a "
963
           "href=\"https://www.gnupg.org/download/#sec-1-2\">download</a> it "
964
           "from GnuPG.org")
965
#endif
966
#else
967
        tr("Please install GnuPG on your system.<br>Install "
×
968
           "<strong>gpg</strong> using your favorite package manager<br>or "
969
           "<a "
970
           "href=\"https://www.gnupg.org/download/#sec-1-2\">download</a> it "
971
           "from GnuPG.org")
972
#endif
973
    );
974
    return false;
×
975
  }
976
  return true;
977
}
978

979
/**
980
 * @brief Checks whether secret keys are available and, if needed, prompts the
981
 * user to generate them.
982
 * @example
983
 * bool result = ConfigDialog.checkSecretKeys();
984
 * std::cout << result << std::endl; // Expected output: true if keys are
985
 * present or key generation dialog is accepted, false otherwise
986
 *
987
 * @return bool - Returns true when secret keys are already available or the key
988
 * generation dialog is accepted; false if the dialog is rejected.
989
 */
990
auto ConfigDialog::checkSecretKeys() -> bool {
×
991
  QString gpg = ui->gpgPath->text();
×
992
  QStringList names = getSecretKeys();
×
993

994
#ifdef QT_DEBUG
995
  dbg() << names;
996
#endif
997

998
  if ((gpg.startsWith("wsl ") || QFile(gpg).exists()) && names.empty()) {
×
NEW
999
    KeygenDialog d(gpg, this);
×
1000
    return d.exec();
×
1001
  }
×
1002
  return true;
1003
}
1004

1005
/**
1006
 * @brief Checks whether the password-store path exists and prompts the user to
1007
 * create it if it does not.
1008
 * @example
1009
 * bool result = ConfigDialog::checkPasswordStore();
1010
 * std::cout << std::boolalpha << result << std::endl; // Expected output: true
1011
 * if the store exists or is created successfully, false if creation fails
1012
 *
1013
 * @return bool - True if the password-store exists or is successfully created,
1014
 * false if creation fails.
1015
 */
1016
auto ConfigDialog::checkPasswordStore() -> bool {
×
1017
  QString passStore = ui->storePath->text();
×
1018

1019
  if (!QFile(passStore).exists()) {
×
1020
    if (QMessageBox::question(
×
1021
            this, tr("Create password-store?"),
×
1022
            tr("Would you like to create a password-store at %1?")
×
1023
                .arg(passStore),
×
1024
            QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
1025
      if (!QDir().mkdir(passStore)) {
×
1026
        QMessageBox::warning(
×
1027
            this, tr("Error"),
×
1028
            tr("Failed to create password-store at: %1").arg(passStore));
×
1029
        return false;
×
1030
      }
1031
#ifdef Q_OS_WIN
1032
      SetFileAttributes(passStore.toStdWString().c_str(),
1033
                        FILE_ATTRIBUTE_HIDDEN);
1034
#endif
1035
      if (ui->checkBoxUseGit->isChecked()) {
×
1036
        emit mainWindow->passGitInitNeeded();
×
1037
      }
1038
      mainWindow->userDialog(passStore);
×
1039
    }
1040
  }
1041
  return true;
1042
}
1043

1044
/**
1045
 * @brief Handles selection and validation of the password store's .gpg-id file.
1046
 * @example
1047
 * ConfigDialog dialog;
1048
 * dialog.handleGpgIdFile();
1049
 *
1050
 * @return void - This method does not return a value; it updates the UI flow
1051
 * and may prompt the user to choose a valid password store.
1052
 */
1053
void ConfigDialog::handleGpgIdFile() {
×
1054
  QString passStore = ui->storePath->text();
×
1055
  if (!QFile(QDir(passStore).filePath(".gpg-id")).exists()) {
×
1056
#ifdef QT_DEBUG
1057
    dbg() << ".gpg-id file does not exist";
1058
#endif
1059
    criticalMessage(tr("Password store not initialised"),
×
1060
                    tr("The folder %1 doesn't seem to be a password store or "
×
1061
                       "is not yet initialised.")
1062
                        .arg(passStore));
×
1063

1064
    while (!QFile(passStore).exists()) {
×
1065
      on_toolButtonStore_clicked();
×
1066
      if (passStore == ui->storePath->text()) {
×
1067
        return;
1068
      }
1069
      passStore = ui->storePath->text();
×
1070
    }
1071
    if (!QFile(passStore + ".gpg-id").exists()) {
×
1072
#ifdef QT_DEBUG
1073
      dbg() << ".gpg-id file still does not exist :/";
1074
#endif
1075
      mainWindow->userDialog(passStore);
×
1076
    }
1077
  }
1078
}
1079

1080
/**
1081
 * @brief ConfigDialog::useTrayIcon set preference for using trayicon.
1082
 * Enable or disable related checkboxes accordingly.
1083
 * @param useSystray
1084
 */
1085
void ConfigDialog::useTrayIcon(bool useSystray) {
13✔
1086
  if (QSystemTrayIcon::isSystemTrayAvailable()) {
13✔
1087
    ui->checkBoxUseTrayIcon->setChecked(useSystray);
×
1088
    ui->checkBoxHideOnClose->setEnabled(useSystray);
×
1089
    ui->checkBoxStartMinimized->setEnabled(useSystray);
×
1090

1091
    if (!useSystray) {
×
1092
      ui->checkBoxHideOnClose->setChecked(false);
×
1093
      ui->checkBoxStartMinimized->setChecked(false);
×
1094
    }
1095
  }
1096
}
13✔
1097

1098
/**
1099
 * @brief ConfigDialog::on_checkBoxUseTrayIcon_clicked enable and disable
1100
 * related checkboxes.
1101
 */
1102
void ConfigDialog::on_checkBoxUseTrayIcon_clicked() {
×
1103
  bool state = ui->checkBoxUseTrayIcon->isChecked();
×
1104
  ui->checkBoxHideOnClose->setEnabled(state);
×
1105
  ui->checkBoxStartMinimized->setEnabled(state);
×
1106
}
×
1107

1108
/**
1109
 * @brief ConfigDialog::closeEvent close this window.
1110
 * @param event
1111
 */
1112
void ConfigDialog::closeEvent(QCloseEvent *event) {
×
1113
  QtPassSettings::setDialogGeometry("configDialog", saveGeometry());
×
1114
  if (!isMaximized()) {
×
1115
    QtPassSettings::setDialogPos("configDialog", pos());
×
1116
    QtPassSettings::setDialogSize("configDialog", size());
×
1117
  }
1118
  QtPassSettings::setDialogMaximized("configDialog", isMaximized());
×
1119
  event->accept();
1120
}
×
1121

1122
/**
1123
 * @brief ConfigDialog::useGit set preference for using git.
1124
 * @param useGit
1125
 */
1126
void ConfigDialog::useGit(bool useGit) {
15✔
1127
  ui->checkBoxUseGit->setChecked(useGit);
15✔
1128
  on_checkBoxUseGit_clicked();
15✔
1129
}
15✔
1130

1131
/**
1132
 * @brief ConfigDialog::useOtp set preference for using otp plugin.
1133
 * @param useOtp
1134
 */
1135
void ConfigDialog::useOtp(bool useOtp) {
15✔
1136
  ui->checkBoxUseOtp->setChecked(useOtp);
15✔
1137
}
15✔
1138

1139
void ConfigDialog::useGrepSearch(bool useGrepSearch) {
15✔
1140
  ui->checkBoxUseGrepSearch->setChecked(useGrepSearch);
15✔
1141
}
15✔
1142

1143
/**
1144
 * @brief ConfigDialog::useQrencode set preference for using qrencode plugin.
1145
 * @param useQrencode
1146
 */
1147
void ConfigDialog::useQrencode(bool useQrencode) {
15✔
1148
  ui->checkBoxUseQrencode->setChecked(useQrencode);
15✔
1149
}
15✔
1150

1151
/**
1152
 * @brief ConfigDialog::on_checkBoxUseGit_clicked enable or disable related
1153
 * checkboxes.
1154
 */
1155
void ConfigDialog::on_checkBoxUseGit_clicked() {
15✔
1156
  ui->checkBoxAddGPGId->setEnabled(ui->checkBoxUseGit->isChecked());
15✔
1157
  ui->checkBoxAutoPull->setEnabled(ui->checkBoxUseGit->isChecked());
15✔
1158
  ui->checkBoxAutoPush->setEnabled(ui->checkBoxUseGit->isChecked());
15✔
1159
}
15✔
1160

1161
/**
1162
 * @brief ConfigDialog::on_toolButtonPwgen_clicked enable or disable related
1163
 * options in the interface.
1164
 */
1165
void ConfigDialog::on_toolButtonPwgen_clicked() {
×
1166
  QString pwgen = selectExecutable();
×
1167
  if (!pwgen.isEmpty()) {
×
1168
    ui->pwgenPath->setText(pwgen);
×
1169
    ui->checkBoxUsePwgen->setEnabled(true);
×
1170
  } else {
1171
    ui->checkBoxUsePwgen->setEnabled(false);
×
1172
    ui->checkBoxUsePwgen->setChecked(false);
×
1173
  }
1174
}
×
1175

1176
/**
1177
 * @brief ConfigDialog::setPwgenPath set pwgen executable path.
1178
 * Enable or disable related options in the interface.
1179
 * @param pwgen
1180
 */
1181
void ConfigDialog::setPwgenPath(const QString &pwgen) {
2✔
1182
  ui->pwgenPath->setText(pwgen);
2✔
1183
  if (pwgen.isEmpty()) {
2✔
1184
    ui->checkBoxUsePwgen->setChecked(false);
1✔
1185
    ui->checkBoxUsePwgen->setEnabled(false);
1✔
1186
  }
1187
  on_checkBoxUsePwgen_clicked();
2✔
1188
}
2✔
1189

1190
/**
1191
 * @brief ConfigDialog::on_checkBoxUsePwgen_clicked enable or disable related
1192
 * options in the interface.
1193
 */
1194
void ConfigDialog::on_checkBoxUsePwgen_clicked() {
31✔
1195
  if (ui->radioButtonPass->isChecked())
31✔
1196
    return;
1197
  bool usePwgen = ui->checkBoxUsePwgen->isChecked();
31✔
1198
  ui->checkBoxAvoidCapitals->setEnabled(usePwgen);
31✔
1199
  ui->checkBoxAvoidNumbers->setEnabled(usePwgen);
31✔
1200
  ui->checkBoxLessRandom->setEnabled(usePwgen);
31✔
1201
  ui->checkBoxUseSymbols->setEnabled(usePwgen);
31✔
1202
  ui->lineEditPasswordChars->setEnabled(!usePwgen);
31✔
1203
  ui->labelPasswordChars->setEnabled(!usePwgen);
31✔
1204
  ui->passwordCharTemplateSelector->setEnabled(!usePwgen);
31✔
1205
}
1206

1207
/**
1208
 * @brief ConfigDialog::usePwgen set preference for using pwgen (can be
1209
 * overruled by empty pwgenPath).
1210
 * enable or disable related options in the interface via
1211
 * ConfigDialog::on_checkBoxUsePwgen_clicked
1212
 * @param usePwgen
1213
 */
1214
void ConfigDialog::usePwgen(bool usePwgen) {
16✔
1215
  if (ui->pwgenPath->text().isEmpty()) {
32✔
1216
    usePwgen = false;
1217
  }
1218
  ui->checkBoxUsePwgen->setChecked(usePwgen);
16✔
1219
  on_checkBoxUsePwgen_clicked();
16✔
1220
}
16✔
1221

1222
void ConfigDialog::setPasswordConfiguration(
1✔
1223
    const PasswordConfiguration &config) {
1224
  ui->spinBoxPasswordLength->setValue(config.length);
1✔
1225
  ui->passwordCharTemplateSelector->setCurrentIndex(config.selected);
1✔
1226
  if (config.selected != PasswordConfiguration::CUSTOM) {
1✔
1227
    ui->lineEditPasswordChars->setEnabled(false);
1✔
1228
  }
1229
  ui->lineEditPasswordChars->setText(config.Characters[config.selected]);
1✔
1230
}
1✔
1231

1232
auto ConfigDialog::getPasswordConfiguration() -> PasswordConfiguration {
1✔
1233
  PasswordConfiguration config;
1✔
1234
  config.length = ui->spinBoxPasswordLength->value();
1✔
1235
  config.selected = static_cast<PasswordConfiguration::characterSet>(
1✔
1236
      ui->passwordCharTemplateSelector->currentIndex());
1✔
1237
  config.Characters[PasswordConfiguration::CUSTOM] =
1238
      ui->lineEditPasswordChars->text();
1✔
1239
  return config;
1✔
1240
}
×
1241

1242
/**
1243
 * @brief ConfigDialog::on_passwordCharTemplateSelector_activated sets the
1244
 * passwordChar Template
1245
 * combo box to the desired entry
1246
 * @param entry of
1247
 */
1248
void ConfigDialog::on_passwordCharTemplateSelector_activated(int index) {
×
1249
  ui->lineEditPasswordChars->setText(
×
1250
      QtPassSettings::getPasswordConfiguration().Characters[index]);
×
1251
  if (index == PasswordConfiguration::CUSTOM) {
×
1252
    ui->lineEditPasswordChars->setEnabled(true);
×
1253
  } else {
1254
    ui->lineEditPasswordChars->setEnabled(false);
×
1255
  }
1256
}
×
1257

1258
/**
1259
 * @brief ConfigDialog::on_checkBoxUseTemplate_clicked enable or disable the
1260
 * template field and options.
1261
 */
1262
void ConfigDialog::on_checkBoxUseTemplate_clicked() {
15✔
1263
  ui->plainTextEditTemplate->setEnabled(ui->checkBoxUseTemplate->isChecked());
15✔
1264
  ui->checkBoxTemplateAllFields->setEnabled(
15✔
1265
      ui->checkBoxUseTemplate->isChecked());
15✔
1266
}
15✔
1267

1268
void ConfigDialog::onProfileTableItemChanged(QTableWidgetItem *item) {
×
1269
  validate(item);
×
1270
  updateProfileStatus(item ? item->row() : -1);
×
1271
}
×
1272

1273
void ConfigDialog::onProfileTableSelectionChanged() {
×
1274
  QList<QTableWidgetItem *> selected = ui->profileTable->selectedItems();
×
1275
  if (selected.isEmpty()) {
×
1276
    return;
1277
  }
1278
  QTableWidgetItem *nameItem =
1279
      ui->profileTable->item(selected.first()->row(), 0);
×
1280
  if (nameItem == nullptr) {
×
1281
    return;
1282
  }
1283
  QString profileName = nameItem->text();
×
1284
  loadGitSettingsForProfile(profileName, m_profiles);
×
1285
}
1286

1287
/**
1288
 * @brief Update status bar with profile preview for given row.
1289
 * @param row The row index to preview, or -1 to clear.
1290
 */
1291
void ConfigDialog::updateProfileStatus(int row) {
×
1292
  if (row < 0 || row >= ui->profileTable->rowCount()) {
×
1293
    ui->statusLabel->setText(QString());
×
1294
    return;
×
1295
  }
1296

1297
  QTableWidgetItem *nameItem = ui->profileTable->item(row, 0);
×
1298
  QTableWidgetItem *pathItem = ui->profileTable->item(row, 1);
×
1299

1300
  QString statusMessage;
×
1301
  if (nameItem && !nameItem->text().isEmpty() && pathItem &&
×
1302
      !pathItem->text().isEmpty()) {
×
1303
    QDir dir(QDir::cleanPath(pathItem->text()));
×
1304
    if (!dir.exists()) {
×
1305
      statusMessage = tr("New profile: %1 at %2")
×
1306
                          .arg(nameItem->text())
×
1307
                          .arg(QDir::cleanPath(pathItem->text()));
×
1308
    } else {
1309
      statusMessage = tr("Profile: %1 at %2")
×
1310
                          .arg(nameItem->text())
×
1311
                          .arg(QDir::cleanPath(pathItem->text()));
×
1312
    }
1313
  } else {
×
1314
    statusMessage = tr("Fill in all required fields");
×
1315
  }
1316

1317
  ui->statusLabel->setText(statusMessage);
×
1318
}
1319

1320
/**
1321
 * @brief ConfigDialog::useTemplate set preference for using templates.
1322
 * @param useTemplate
1323
 */
1324
void ConfigDialog::useTemplate(bool useTemplate) {
15✔
1325
  ui->checkBoxUseTemplate->setChecked(useTemplate);
15✔
1326
  on_checkBoxUseTemplate_clicked();
15✔
1327
}
15✔
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