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

IJHack / QtPass / 24842587247

23 Apr 2026 03:03PM UTC coverage: 27.095% (+0.02%) from 27.079%
24842587247

Pull #1140

github

web-flow
Merge bd78953d0 into 6fc8c668f
Pull Request #1140: feat: Store git options per profile (#112)

44 of 165 new or added lines in 2 files covered. (26.67%)

38 existing lines in 2 files now uncovered.

1636 of 6038 relevant lines covered (27.1%)

29.39 hits per line

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

0.0
/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 "keygendialog.h"
5
#include "mainwindow.h"
6
#include "profileinit.h"
7
#include "qtpasssettings.h"
8
#include "ui_configdialog.h"
9
#include "usersdialog.h"
10
#include "util.h"
11
#include <QClipboard>
12
#include <QDir>
13
#include <QFileDialog>
14
#include <QMessageBox>
15
#include <QPushButton>
16
#include <QSystemTrayIcon>
17
#include <QTableWidgetItem>
18
#include <utility>
19
#ifdef Q_OS_WIN
20
#include <windows.h>
21
#endif
22

23
#ifdef QT_DEBUG
24
#include "debughelper.h"
25
#endif
26

27
/**
28
 * @brief ConfigDialog::ConfigDialog this sets up the configuration screen.
29
 * @param parent
30
 */
31
ConfigDialog::ConfigDialog(MainWindow *parent)
×
NEW
32
    : QDialog(parent), ui(new Ui::ConfigDialog), populating(false) {
×
33
  mainWindow = parent;
×
34
  ui->setupUi(this);
×
35

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

48
  ui->passPath->setText(QtPassSettings::getPassExecutable());
×
49
  setGitPath(QtPassSettings::getGitExecutable());
×
50
  ui->gpgPath->setText(QtPassSettings::getGpgExecutable());
×
51
  ui->storePath->setText(QtPassSettings::getPassStore());
×
52

53
  ui->spinBoxAutoclearSeconds->setValue(QtPassSettings::getAutoclearSeconds());
×
54
  ui->spinBoxAutoclearPanelSeconds->setValue(
×
55
      QtPassSettings::getAutoclearPanelSeconds());
×
56
  ui->checkBoxHidePassword->setChecked(QtPassSettings::isHidePassword());
×
57
  ui->checkBoxHideContent->setChecked(QtPassSettings::isHideContent());
×
58
  ui->checkBoxUseMonospace->setChecked(QtPassSettings::isUseMonospace());
×
59
  ui->checkBoxDisplayAsIs->setChecked(QtPassSettings::isDisplayAsIs());
×
60
  ui->checkBoxNoLineWrapping->setChecked(QtPassSettings::isNoLineWrapping());
×
61
  ui->checkBoxAddGPGId->setChecked(QtPassSettings::isAddGPGId(true));
×
62

63
  if (QSystemTrayIcon::isSystemTrayAvailable()) {
×
64
    ui->checkBoxHideOnClose->setChecked(QtPassSettings::isHideOnClose());
×
65
    ui->checkBoxStartMinimized->setChecked(QtPassSettings::isStartMinimized());
×
66
  } else {
67
    ui->checkBoxUseTrayIcon->setEnabled(false);
×
68
    ui->checkBoxUseTrayIcon->setToolTip(tr("System tray is not available"));
×
69
    ui->checkBoxHideOnClose->setEnabled(false);
×
70
    ui->checkBoxStartMinimized->setEnabled(false);
×
71
  }
72

73
  ui->checkBoxAvoidCapitals->setChecked(QtPassSettings::isAvoidCapitals());
×
74
  ui->checkBoxAvoidNumbers->setChecked(QtPassSettings::isAvoidNumbers());
×
75
  ui->checkBoxLessRandom->setChecked(QtPassSettings::isLessRandom());
×
76
  ui->checkBoxUseSymbols->setChecked(QtPassSettings::isUseSymbols());
×
77
  ui->plainTextEditTemplate->setPlainText(QtPassSettings::getPassTemplate());
×
78
  ui->checkBoxTemplateAllFields->setChecked(
×
79
      QtPassSettings::isTemplateAllFields());
×
80
  ui->checkBoxAutoPull->setChecked(QtPassSettings::isAutoPull());
×
81
  ui->checkBoxAutoPush->setChecked(QtPassSettings::isAutoPush());
×
82
  ui->checkBoxAlwaysOnTop->setChecked(QtPassSettings::isAlwaysOnTop());
×
83

84
#if defined(Q_OS_WIN)
85
  ui->checkBoxUseOtp->hide();
86
  ui->checkBoxUseQrencode->hide();
87
  ui->label_10->hide();
88
#endif
89

90
  if (!isPassOtpAvailable()) {
×
91
    ui->checkBoxUseOtp->setEnabled(false);
×
92
    ui->checkBoxUseOtp->setToolTip(
×
93
        tr("Pass OTP extension needs to be installed"));
×
94
  }
95

96
  if (!isQrencodeAvailable()) {
×
97
    ui->checkBoxUseQrencode->setEnabled(false);
×
98
    ui->checkBoxUseQrencode->setToolTip(tr("qrencode needs to be installed"));
×
99
  }
100

101
  // Initialize dialogGitSettings from existing profiles
102
  QHash<QString, QHash<QString, QString>> existingProfiles =
NEW
103
      QtPassSettings::getProfiles();
×
NEW
104
  for (auto it = existingProfiles.constBegin(); it != existingProfiles.constEnd();
×
105
       ++it) {
NEW
106
    QHash<QString, QString> gitSettings;
×
NEW
107
    gitSettings["useGit"] = it.value().value("useGit");
×
NEW
108
    gitSettings["autoPush"] = it.value().value("autoPush");
×
NEW
109
    gitSettings["autoPull"] = it.value().value("autoPull");
×
NEW
110
    dialogGitSettings[it.key()] = gitSettings;
×
NEW
111
  }
×
112

NEW
113
  setProfiles(existingProfiles, QtPassSettings::getProfile());
×
114
  setPwgenPath(QtPassSettings::getPwgenExecutable());
×
115
  setPasswordConfiguration(QtPassSettings::getPasswordConfiguration());
×
116

117
  usePass(QtPassSettings::isUsePass());
×
118
  useAutoclear(QtPassSettings::isUseAutoclear());
×
119
  useAutoclearPanel(QtPassSettings::isUseAutoclearPanel());
×
120
  useTrayIcon(QtPassSettings::isUseTrayIcon());
×
121
  useGit(QtPassSettings::isUseGit());
×
122

123
  useOtp(QtPassSettings::isUseOtp());
×
124
  useGrepSearch(QtPassSettings::isUseGrepSearch());
×
125
  useQrencode(QtPassSettings::isUseQrencode());
×
126

127
  usePwgen(QtPassSettings::isUsePwgen());
×
128
  useTemplate(QtPassSettings::isUseTemplate());
×
129

130
  ui->profileTable->verticalHeader()->hide();
×
131
  ui->profileTable->horizontalHeader()->setSectionResizeMode(
×
132
      1, QHeaderView::Stretch);
133
  ui->label->setText(ui->label->text() + VERSION);
×
134
  ui->comboBoxClipboard->clear();
×
135

136
  ui->comboBoxClipboard->addItem(tr("No Clipboard"));
×
137
  ui->comboBoxClipboard->addItem(tr("Always copy to clipboard"));
×
138
  ui->comboBoxClipboard->addItem(tr("On-demand copy to clipboard"));
×
139

140
  int currentIndex = QtPassSettings::getClipBoardTypeRaw();
×
141
  ui->comboBoxClipboard->setCurrentIndex(currentIndex);
×
142
  on_comboBoxClipboard_activated(currentIndex);
×
143

144
  QClipboard *clip = QApplication::clipboard();
×
145
  if (!clip->supportsSelection()) {
×
146
    useSelection(false);
×
147
    ui->checkBoxSelection->setVisible(false);
×
148
  } else {
149
    useSelection(QtPassSettings::isUseSelection());
×
150
  }
151

152
  if (!Util::configIsValid()) {
×
153
    // Show Programs tab, which is likely
154
    // what the user needs to fix now.
155
    ui->tabWidget->setCurrentIndex(1);
×
156
  }
157

158
  connect(ui->profileTable, &QTableWidget::itemChanged, this,
×
159
          &ConfigDialog::onProfileTableItemChanged);
×
NEW
160
  connect(ui->profileTable, &QTableWidget::itemSelectionChanged, this,
×
NEW
161
          &ConfigDialog::onProfileTableSelectionChanged);
×
162
  connect(this, &ConfigDialog::accepted, this, &ConfigDialog::on_accepted);
×
163
}
×
164

165
/**
166
 * @brief ConfigDialog::~ConfigDialog config destructor, makes sure the
167
 * mainWindow knows about git, gpg and pass executables.
168
 */
169
ConfigDialog::~ConfigDialog() {
×
170
  QtPassSettings::setGitExecutable(ui->gitPath->text());
×
171
  QtPassSettings::setGpgExecutable(ui->gpgPath->text());
×
172
  QtPassSettings::setPassExecutable(ui->passPath->text());
×
173
}
×
174

175
/**
176
 * @brief ConfigDialog::setGitPath set the git executable path.
177
 * Make sure the checkBoxUseGit is updated.
178
 * @param path
179
 */
180
void ConfigDialog::setGitPath(const QString &path) {
×
181
  ui->gitPath->setText(path);
×
182
  ui->checkBoxUseGit->setEnabled(!path.isEmpty());
×
183
  if (path.isEmpty()) {
×
184
    useGit(false);
×
185
  }
186
}
×
187

188
/**
189
 * @brief ConfigDialog::usePass set wether or not we want to use pass.
190
 * Update radio buttons accordingly.
191
 * @param usePass
192
 */
193
void ConfigDialog::usePass(bool usePass) {
×
194
  ui->radioButtonNative->setChecked(!usePass);
×
195
  ui->radioButtonPass->setChecked(usePass);
×
196
  setGroupBoxState();
×
197
}
×
198

199
/**
200
 * @brief Validates the configuration table fields and enables or disables the
201
 * OK button accordingly.
202
 * @example
203
 * ConfigDialog dialog;
204
 * QTableWidgetItem *item = dialog.findChild<QTableWidgetItem*>();
205
 * dialog.validate(item);
206
 *
207
 * @param QTableWidgetItem *item - The table item to validate; if null,
208
 * validates all relevant items in the profile table.
209
 * @return void - This function does not return a value.
210
 */
211
void ConfigDialog::validate(QTableWidgetItem *item) {
×
212
  bool status = true;
213

214
  if (item == nullptr) {
×
215
    for (int i = 0; i < ui->profileTable->rowCount(); i++) {
×
216
      for (int j = 0; j < ui->profileTable->columnCount(); j++) {
×
217
        QTableWidgetItem *_item = ui->profileTable->item(i, j);
×
218

219
        if (_item->text().isEmpty() && j != 2) {
×
220
          _item->setBackground(Qt::red);
×
221
          _item->setToolTip(tr("This field is required"));
×
222
          status = false;
223
          break;
224
        } else {
225
          _item->setBackground(QBrush());
×
226
          _item->setToolTip(QString());
×
227
        }
228
      }
229

230
      if (!status) {
231
        break;
232
      }
233
    }
234
  } else {
235
    if (item->text().isEmpty() && item->column() != 2) {
×
236
      item->setBackground(Qt::red);
×
237
      item->setToolTip(tr("This field is required"));
×
238
      status = false;
239
    } else {
240
      item->setBackground(QBrush());
×
241
      item->setToolTip(QString());
×
242
    }
243
  }
244

245
  ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(status);
×
246
}
×
247

248
/**
249
 * @brief Saves the configuration dialog settings to persistent application
250
 * settings.
251
 * @example
252
 * ConfigDialog dialog;
253
 * dialog.on_accepted();
254
 * // Expected output: All UI-selected configuration values are stored via
255
 * QtPassSettings.
256
 *
257
 * @return void - This method does not return a value.
258
 */
259
void ConfigDialog::on_accepted() {
×
260
  QtPassSettings::setPassExecutable(ui->passPath->text());
×
261
  QtPassSettings::setGitExecutable(ui->gitPath->text());
×
262
  QtPassSettings::setGpgExecutable(ui->gpgPath->text());
×
263
  QtPassSettings::setPassStore(
×
264
      Util::normalizeFolderPath(ui->storePath->text()));
×
265
  QtPassSettings::setUsePass(ui->radioButtonPass->isChecked());
×
266
  QtPassSettings::setClipBoardType(ui->comboBoxClipboard->currentIndex());
×
267
  QtPassSettings::setUseSelection(ui->checkBoxSelection->isChecked());
×
268
  QtPassSettings::setUseAutoclear(ui->checkBoxAutoclear->isChecked());
×
269
  QtPassSettings::setAutoclearSeconds(ui->spinBoxAutoclearSeconds->value());
×
270
  QtPassSettings::setUseAutoclearPanel(ui->checkBoxAutoclearPanel->isChecked());
×
271
  QtPassSettings::setAutoclearPanelSeconds(
×
272
      ui->spinBoxAutoclearPanelSeconds->value());
×
273
  QtPassSettings::setHidePassword(ui->checkBoxHidePassword->isChecked());
×
274
  QtPassSettings::setHideContent(ui->checkBoxHideContent->isChecked());
×
275
  QtPassSettings::setUseMonospace(ui->checkBoxUseMonospace->isChecked());
×
276
  QtPassSettings::setDisplayAsIs(ui->checkBoxDisplayAsIs->isChecked());
×
277
  QtPassSettings::setNoLineWrapping(ui->checkBoxNoLineWrapping->isChecked());
×
278
  QtPassSettings::setAddGPGId(ui->checkBoxAddGPGId->isChecked());
×
279
  QtPassSettings::setUseTrayIcon(ui->checkBoxUseTrayIcon->isEnabled() &&
×
280
                                 ui->checkBoxUseTrayIcon->isChecked());
×
281
  QtPassSettings::setHideOnClose(ui->checkBoxHideOnClose->isEnabled() &&
×
282
                                 ui->checkBoxHideOnClose->isChecked());
×
283
  QtPassSettings::setStartMinimized(ui->checkBoxStartMinimized->isEnabled() &&
×
284
                                    ui->checkBoxStartMinimized->isChecked());
×
285
  QHash<QString, QHash<QString, QString>> existingProfiles =
286
      QtPassSettings::getProfiles();
×
287
  QtPassSettings::setProfiles(getProfiles());
×
288
  QtPassSettings::setUseGit(ui->checkBoxUseGit->isChecked());
×
289
  QtPassSettings::setUseOtp(ui->checkBoxUseOtp->isChecked());
×
290
  QtPassSettings::setUseGrepSearch(ui->checkBoxUseGrepSearch->isChecked());
×
291
  QtPassSettings::setUseQrencode(ui->checkBoxUseQrencode->isChecked());
×
292
  QtPassSettings::setPwgenExecutable(ui->pwgenPath->text());
×
293
  QtPassSettings::setUsePwgen(ui->checkBoxUsePwgen->isChecked());
×
294
  QtPassSettings::setAvoidCapitals(ui->checkBoxAvoidCapitals->isChecked());
×
295
  QtPassSettings::setAvoidNumbers(ui->checkBoxAvoidNumbers->isChecked());
×
296
  QtPassSettings::setLessRandom(ui->checkBoxLessRandom->isChecked());
×
297
  QtPassSettings::setUseSymbols(ui->checkBoxUseSymbols->isChecked());
×
298
  QtPassSettings::setPasswordConfiguration(getPasswordConfiguration());
×
299
  QtPassSettings::setUseTemplate(ui->checkBoxUseTemplate->isChecked());
×
300
  QtPassSettings::setPassTemplate(ui->plainTextEditTemplate->toPlainText());
×
301
  QtPassSettings::setTemplateAllFields(
×
302
      ui->checkBoxTemplateAllFields->isChecked());
×
303
  QtPassSettings::setAutoPush(ui->checkBoxAutoPush->isChecked());
×
304
  QtPassSettings::setAutoPull(ui->checkBoxAutoPull->isChecked());
×
305
  QtPassSettings::setAlwaysOnTop(ui->checkBoxAlwaysOnTop->isChecked());
×
306

307
  QtPassSettings::setVersion(VERSION);
×
308

309
  // Initialize new profiles that need pass/git initialization
310
  initializeNewProfiles(existingProfiles);
×
311
}
×
312

313
/**
314
 * @brief Automatically detects required external binaries in the system PATH
315
 * and updates the dialog fields.
316
 * @example
317
 * ConfigDialog configDialog;
318
 * configDialog.on_autodetectButton_clicked();
319
 *
320
 * @return void - This function does not return a value.
321
 */
322
void ConfigDialog::on_autodetectButton_clicked() {
×
323
  QString pass = Util::findBinaryInPath("pass");
×
324
  if (!pass.isEmpty()) {
×
325
    ui->passPath->setText(pass);
×
326
  }
327
  usePass(!pass.isEmpty());
×
328
  QString gpg = Util::findBinaryInPath("gpg2");
×
329
  if (gpg.isEmpty()) {
×
330
    gpg = Util::findBinaryInPath("gpg");
×
331
  }
332
  if (!gpg.isEmpty()) {
×
333
    ui->gpgPath->setText(gpg);
×
334
  }
335
  QString git = Util::findBinaryInPath("git");
×
336
  if (!git.isEmpty()) {
×
337
    ui->gitPath->setText(git);
×
338
  }
339
  QString pwgen = Util::findBinaryInPath("pwgen");
×
340
  if (!pwgen.isEmpty()) {
×
341
    ui->pwgenPath->setText(pwgen);
×
342
  }
343
}
×
344

345
/**
346
 * @brief ConfigDialog::on_radioButtonNative_clicked wrapper for
347
 * ConfigDialog::setGroupBoxState()
348
 */
349
void ConfigDialog::on_radioButtonNative_clicked() { setGroupBoxState(); }
×
350

351
/**
352
 * @brief ConfigDialog::on_radioButtonPass_clicked wrapper for
353
 * ConfigDialog::setGroupBoxState()
354
 */
355
void ConfigDialog::on_radioButtonPass_clicked() { setGroupBoxState(); }
×
356

357
/**
358
 * @brief ConfigDialog::getSecretKeys get list of secret/private keys
359
 * @return QStringList keys
360
 */
361
auto ConfigDialog::getSecretKeys() -> QStringList {
×
362
  QList<UserInfo> keys = QtPassSettings::getPass()->listKeys("", true);
×
363
  QStringList names;
×
364

365
  if (keys.empty()) {
×
366
    return names;
367
  }
368

369
  foreach (const UserInfo &sec, keys)
×
370
    names << sec.name;
×
371

372
  return names;
×
373
}
374

375
/**
376
 * @brief ConfigDialog::setGroupBoxState update checkboxes.
377
 */
378
void ConfigDialog::setGroupBoxState() {
×
379
  bool state = ui->radioButtonPass->isChecked();
×
380
  ui->groupBoxNative->setEnabled(!state);
×
381
  ui->groupBoxPass->setEnabled(state);
×
382
  if (state) {
×
383
    // pass mode: disable all password generation controls
384
    ui->spinBoxPasswordLength->setEnabled(false);
×
385
    ui->checkBoxUsePwgen->setEnabled(false);
×
386
    ui->checkBoxAvoidCapitals->setEnabled(false);
×
387
    ui->checkBoxUseSymbols->setEnabled(false);
×
388
    ui->checkBoxLessRandom->setEnabled(false);
×
389
    ui->checkBoxAvoidNumbers->setEnabled(false);
×
390
    ui->labelPasswordChars->setEnabled(false);
×
391
    ui->passwordCharTemplateSelector->setEnabled(false);
×
392
    ui->lineEditPasswordChars->setEnabled(false);
×
393
  } else {
394
    // native mode: restore pwgen/charset state from existing handlers
395
    ui->spinBoxPasswordLength->setEnabled(true);
×
396
    ui->checkBoxUsePwgen->setEnabled(!ui->pwgenPath->text().isEmpty());
×
397
    on_checkBoxUsePwgen_clicked();
×
398
  }
399
}
×
400

401
/**
402
 * @brief ConfigDialog::selectExecutable pop-up to choose an executable.
403
 * @return
404
 */
405
auto ConfigDialog::selectExecutable() -> QString {
×
406
  QFileDialog dialog(this);
×
407
  dialog.setFileMode(QFileDialog::ExistingFile);
×
408
  dialog.setOption(QFileDialog::ReadOnly);
×
409
  if (dialog.exec()) {
×
410
    return dialog.selectedFiles().constFirst();
×
411
  }
412

413
  return {};
414
}
×
415

416
/**
417
 * @brief ConfigDialog::selectFolder pop-up to choose a folder.
418
 * @return
419
 */
420
auto ConfigDialog::selectFolder() -> QString {
×
421
  QFileDialog dialog(this);
×
422
  dialog.setFileMode(QFileDialog::Directory);
×
423
  dialog.setFilter(QDir::NoFilter);
×
424
  dialog.setOption(QFileDialog::ShowDirsOnly);
×
425
  if (dialog.exec()) {
×
426
    return dialog.selectedFiles().constFirst();
×
427
  }
428

429
  return {};
430
}
×
431

432
/**
433
 * @brief ConfigDialog::on_toolButtonGit_clicked get git application.
434
 * Enable checkboxes if found.
435
 */
436
void ConfigDialog::on_toolButtonGit_clicked() {
×
437
  QString git = selectExecutable();
×
438
  bool state = !git.isEmpty();
×
439
  if (state) {
×
440
    ui->gitPath->setText(git);
×
441
  } else {
442
    useGit(false);
×
443
  }
444

445
  ui->checkBoxUseGit->setEnabled(state);
×
446
}
×
447

448
/**
449
 * @brief ConfigDialog::on_toolButtonGpg_clicked get gpg application.
450
 */
451
void ConfigDialog::on_toolButtonGpg_clicked() {
×
452
  QString gpg = selectExecutable();
×
453
  if (!gpg.isEmpty()) {
×
454
    ui->gpgPath->setText(gpg);
×
455
  }
456
}
×
457

458
/**
459
 * @brief ConfigDialog::on_pushButtonGenerateKey_clicked open keygen dialog.
460
 */
461
void ConfigDialog::on_pushButtonGenerateKey_clicked() {
×
462
  KeygenDialog d(this);
×
463
  d.exec();
×
464
}
×
465

466
/**
467
 * @brief ConfigDialog::on_toolButtonPass_clicked get pass application.
468
 */
469
void ConfigDialog::on_toolButtonPass_clicked() {
×
470
  QString pass = selectExecutable();
×
471
  if (!pass.isEmpty()) {
×
472
    ui->passPath->setText(pass);
×
473
  }
474
}
×
475

476
/**
477
 * @brief ConfigDialog::on_toolButtonStore_clicked get .password-store
478
 * location.
479
 */
480
void ConfigDialog::on_toolButtonStore_clicked() {
×
481
  QString store = selectFolder();
×
482
  if (!store.isEmpty()) {
×
483
    ui->storePath->setText(store);
×
484
  }
485
}
×
486

487
/**
488
 * @brief ConfigDialog::on_comboBoxClipboard_activated show and hide options.
489
 * @param index of selectbox (0 = no clipboard).
490
 */
491
void ConfigDialog::on_comboBoxClipboard_activated(int index) {
×
492
  bool state = index > 0;
×
493

494
  ui->checkBoxSelection->setEnabled(state);
×
495
  ui->checkBoxAutoclear->setEnabled(state);
×
496
  ui->checkBoxHidePassword->setEnabled(state);
×
497
  ui->checkBoxHideContent->setEnabled(state);
×
498
  if (state) {
×
499
    ui->spinBoxAutoclearSeconds->setEnabled(ui->checkBoxAutoclear->isChecked());
×
500
    ui->labelSeconds->setEnabled(ui->checkBoxAutoclear->isChecked());
×
501
  } else {
502
    ui->spinBoxAutoclearSeconds->setEnabled(false);
×
503
    ui->labelSeconds->setEnabled(false);
×
504
  }
505
}
×
506

507
/**
508
 * @brief ConfigDialog::on_checkBoxAutoclearPanel_clicked enable and disable
509
 * options based on autoclear use.
510
 */
511
void ConfigDialog::on_checkBoxAutoclearPanel_clicked() {
×
512
  bool state = ui->checkBoxAutoclearPanel->isChecked();
×
513
  ui->spinBoxAutoclearPanelSeconds->setEnabled(state);
×
514
  ui->labelPanelSeconds->setEnabled(state);
×
515
}
×
516

517
/**
518
 * @brief ConfigDialog::useSelection set the clipboard type use from
519
 * MainWindow.
520
 * @param useSelection
521
 */
522
void ConfigDialog::useSelection(bool useSelection) {
×
523
  ui->checkBoxSelection->setChecked(useSelection);
×
524
  on_checkBoxSelection_clicked();
×
525
}
×
526

527
/**
528
 * @brief ConfigDialog::useAutoclear set the clipboard autoclear use from
529
 * MainWindow.
530
 * @param useAutoclear
531
 */
532
void ConfigDialog::useAutoclear(bool useAutoclear) {
×
533
  ui->checkBoxAutoclear->setChecked(useAutoclear);
×
534
  on_checkBoxAutoclear_clicked();
×
535
}
×
536

537
/**
538
 * @brief ConfigDialog::useAutoclearPanel set the panel autoclear use from
539
 * MainWindow.
540
 * @param useAutoclearPanel
541
 */
542
void ConfigDialog::useAutoclearPanel(bool useAutoclearPanel) {
×
543
  ui->checkBoxAutoclearPanel->setChecked(useAutoclearPanel);
×
544
  on_checkBoxAutoclearPanel_clicked();
×
545
}
×
546

547
/**
548
 * @brief ConfigDialog::on_checkBoxSelection_clicked checkbox clicked, update
549
 * state via ConfigDialog::on_comboBoxClipboard_activated
550
 */
551
void ConfigDialog::on_checkBoxSelection_clicked() {
×
552
  on_comboBoxClipboard_activated(ui->comboBoxClipboard->currentIndex());
×
553
}
×
554

555
/**
556
 * @brief ConfigDialog::on_checkBoxAutoclear_clicked checkbox clicked, update
557
 * state via ConfigDialog::on_comboBoxClipboard_activated
558
 */
559
void ConfigDialog::on_checkBoxAutoclear_clicked() {
×
560
  on_comboBoxClipboard_activated(ui->comboBoxClipboard->currentIndex());
×
561
}
×
562

563
/**
564
 * @brief ConfigDialog::genKey tunnel function to make MainWindow generate a
565
 * gpg key pair.
566
 * @param batch
567
 * @param dialog
568
 */
569
void ConfigDialog::genKey(const QString &batch, QDialog *dialog) {
×
570
  mainWindow->generateKeyPair(batch, dialog);
×
571
}
×
572

573
/**
574
 * @brief ConfigDialog::setProfiles set the profiles and chosen profile from
575
 * MainWindow.
576
 * @param profiles
577
 * @param profile
578
 */
579
void ConfigDialog::setProfiles(QHash<QString, QHash<QString, QString>> profiles,
×
580
                               const QString &currentProfile) {
581
  if (profiles.contains("")) {
×
582
    profiles.remove("");
×
583
    // remove weird "" key value pairs
584
  }
585

NEW
586
  populating = true;
×
587
  ui->profileTable->setRowCount(profiles.count());
×
588
  QHashIterator<QString, QHash<QString, QString>> i(profiles);
×
589
  int n = 0;
590
  while (i.hasNext()) {
×
591
    i.next();
592
    if (!i.value().isEmpty() && !i.key().isEmpty()) {
×
593
      ui->profileTable->setItem(n, 0, new QTableWidgetItem(i.key()));
×
594
      ui->profileTable->setItem(n, 1,
×
595
                                new QTableWidgetItem(i.value().value("path")));
×
596
      ui->profileTable->setItem(
×
597
          n, 2, new QTableWidgetItem(i.value().value("signingKey")));
×
598
      if (i.key() == currentProfile) {
×
599
        ui->profileTable->selectRow(n);
×
600
        // Load git settings for current profile
NEW
601
        currentProfileName = currentProfile;
×
NEW
602
        loadGitSettingsForProfile(currentProfile, profiles);
×
603
      }
604
    }
605
    ++n;
×
606
  }
NEW
607
  populating = false;
×
608

609
  // Validate all items now that population is complete
NEW
610
  validate();
×
611
  // Update status for the selected profile if any
NEW
612
  if (!currentProfile.isEmpty()) {
×
NEW
613
    for (int row = 0; row < ui->profileTable->rowCount(); ++row) {
×
NEW
614
      QTableWidgetItem *nameItem = ui->profileTable->item(row, 0);
×
NEW
615
      if (nameItem && nameItem->text() == currentProfile) {
×
NEW
616
        updateProfileStatus(row);
×
617
        break;
618
      }
619
    }
620
  }
NEW
621
}
×
622

623
/**
624
 * @brief Load git settings for a specific profile.
625
 * @param profileName The profile name.
626
 * @param profiles The profiles hash containing git settings.
627
 */
NEW
628
void ConfigDialog::loadGitSettingsForProfile(
×
629
    const QString &profileName,
630
    const QHash<QString, QHash<QString, QString>> &profiles) {
631
  // Load from dialogGitSettings first, fall back to profiles if not present
NEW
632
  QString useGitStr;
×
NEW
633
  QString autoPushStr;
×
NEW
634
  QString autoPullStr;
×
635

NEW
636
  if (dialogGitSettings.contains(profileName)) {
×
637
    const QHash<QString, QString> &gitSettings =
NEW
638
        dialogGitSettings.value(profileName);
×
NEW
639
    useGitStr = gitSettings.value("useGit");
×
NEW
640
    autoPushStr = gitSettings.value("autoPush");
×
NEW
641
    autoPullStr = gitSettings.value("autoPull");
×
NEW
642
  } else if (profiles.contains(profileName)) {
×
NEW
643
    const QHash<QString, QString> &profile = profiles.value(profileName);
×
NEW
644
    useGitStr = profile.value("useGit");
×
NEW
645
    autoPushStr = profile.value("autoPush");
×
NEW
646
    autoPullStr = profile.value("autoPull");
×
NEW
647
  }
×
648

649
  // Load profile-specific git settings if set, otherwise use global settings
NEW
650
  if (!useGitStr.isEmpty()) {
×
NEW
651
    useGit(useGitStr == "true");
×
NEW
652
    ui->checkBoxAutoPush->setEnabled(ui->checkBoxUseGit->isChecked());
×
NEW
653
    ui->checkBoxAutoPull->setEnabled(ui->checkBoxUseGit->isChecked());
×
NEW
654
    if (autoPushStr == "true" || autoPushStr == "false") {
×
NEW
655
      ui->checkBoxAutoPush->setChecked(autoPushStr == "true");
×
656
    }
NEW
657
    if (autoPullStr == "true" || autoPullStr == "false") {
×
NEW
658
      ui->checkBoxAutoPull->setChecked(autoPullStr == "true");
×
659
    }
660
  }
661
  // If not set (empty), leave global settings as-is for migration
UNCOV
662
}
×
663

664
/**
665
 * @brief ConfigDialog::getProfiles return profile list.
666
 * @return
667
 */
668
auto ConfigDialog::getProfiles() -> QHash<QString, QHash<QString, QString>> {
×
669
  // Save current profile's Git settings before serializing
NEW
670
  saveCurrentGitSettings();
×
671

672
  // Fetch existing profiles to fall back to if not in dialogGitSettings
673
  QHash<QString, QHash<QString, QString>> existingProfiles =
NEW
674
      QtPassSettings::getProfiles();
×
675

UNCOV
676
  QHash<QString, QHash<QString, QString>> profiles;
×
677
  // Check?
678
  for (int i = 0; i < ui->profileTable->rowCount(); ++i) {
×
679
    QHash<QString, QString> profile;
×
680
    QTableWidgetItem *pathItem = ui->profileTable->item(i, 1);
×
681
    if (nullptr != pathItem) {
×
682
      QTableWidgetItem *item = ui->profileTable->item(i, 0);
×
683
      if (item == nullptr) {
×
UNCOV
684
        continue;
×
685
      }
686
      profile["path"] = pathItem->text();
×
687
      QTableWidgetItem *signingKeyItem = ui->profileTable->item(i, 2);
×
688
      if (nullptr != signingKeyItem) {
×
689
        profile["signingKey"] = signingKeyItem->text();
×
690
      }
691

NEW
692
      QString profileName = item->text();
×
693

694
      // Serialize Git settings from dialogGitSettings for all profiles
NEW
695
      if (dialogGitSettings.contains(profileName)) {
×
696
        const QHash<QString, QString> &gitSettings =
NEW
697
            dialogGitSettings.value(profileName);
×
NEW
698
        if (gitSettings.contains("useGit")) {
×
NEW
699
          profile["useGit"] = gitSettings.value("useGit");
×
700
        }
NEW
701
        if (gitSettings.contains("autoPush")) {
×
NEW
702
          profile["autoPush"] = gitSettings.value("autoPush");
×
703
        }
NEW
704
        if (gitSettings.contains("autoPull")) {
×
NEW
705
          profile["autoPull"] = gitSettings.value("autoPull");
×
706
        }
NEW
707
      } else if (existingProfiles.contains(profileName)) {
×
708
        // Fall back to existing git settings if not in dialogGitSettings
709
        const QHash<QString, QString> &existing =
NEW
710
            existingProfiles.value(profileName);
×
NEW
711
        if (existing.contains("useGit")) {
×
NEW
712
          profile["useGit"] = existing.value("useGit");
×
713
        }
NEW
714
        if (existing.contains("autoPush")) {
×
NEW
715
          profile["autoPush"] = existing.value("autoPush");
×
716
        }
NEW
717
        if (existing.contains("autoPull")) {
×
NEW
718
          profile["autoPull"] = existing.value("autoPull");
×
719
        }
NEW
720
      }
×
721
      profiles.insert(profileName, profile);
722
    }
723
  }
×
724
  return profiles;
×
725
}
×
726

727
/**
728
 * @brief Initialize new profiles that need pass/git initialization.
729
 * @param existingProfiles The profiles that existed before the dialog was
730
 * opened.
731
 */
732
void ConfigDialog::initializeNewProfiles(
×
733
    const QHash<QString, QHash<QString, QString>> &existingProfiles) {
734
  QHash<QString, QHash<QString, QString>> newProfiles = getProfiles();
×
735

736
  // Collect keys and sort for deterministic iteration
737
  QStringList keys = newProfiles.keys();
×
738
  keys.sort();
739

740
  for (const QString &name : keys) {
×
741
    const QString &path = newProfiles.value(name).value("path");
×
742

743
    // Skip if already existed before (check by name and path)
744
    if (existingProfiles.contains(name) &&
×
745
        existingProfiles.value(name).value("path") == path) {
×
746
      continue;
×
747
    }
748

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

770
    // Now check if initialization is needed (directory exists with no .gpg-id)
771
    if (!ProfileInit::needsInit(cleanPath)) {
×
772
      continue;
×
773
    }
774

775
    // Temporarily switch the active store so pass/git init operate on
776
    // the new profile's directory rather than the currently-saved one.
777
    const QString prevStore = QtPassSettings::getPassStore();
×
778
    QtPassSettings::setPassStore(cleanPath);
×
779

780
    // Show user selection dialog for GPG recipients
781
    // UsersDialog will run pass init when accepted
UNCOV
782
    UsersDialog usersDialog(cleanPath, this);
×
UNCOV
783
    usersDialog.setWindowTitle(tr("Select recipients for %1").arg(name));
×
UNCOV
784
    const int result = usersDialog.exec();
×
785

786
    if (result == QDialog::Accepted && ui->checkBoxUseGit->isChecked()) {
×
NEW
787
      QtPassSettings::getPass()->GitInit();
×
788
    }
789

790
    // Restore previous store setting
NEW
791
    QtPassSettings::setPassStore(prevStore);
×
NEW
792
  }
×
NEW
793
}
×
794

795
/**
796
 * @brief ConfigDialog::on_addButton_clicked add a profile row.
797
 */
UNCOV
798
void ConfigDialog::on_addButton_clicked() {
×
799
  // Save current profile's Git settings before switching
UNCOV
800
  saveCurrentGitSettings();
×
801

802
  bool sortingEnabled = ui->profileTable->isSortingEnabled();
×
803
  ui->profileTable->setSortingEnabled(false);
×
804

NEW
805
  int n = ui->profileTable->rowCount();
×
NEW
806
  ui->profileTable->insertRow(n);
×
807

808
  // Generate a unique identifier for the new profile
809
  static int profileCounter = 0;
NEW
810
  QString uniqueKey = QString("__new_profile_%1__").arg(profileCounter++);
×
811

812
  // Set populating flag to prevent itemChanged from triggering during setup
NEW
813
  populating = true;
×
NEW
814
  ui->profileTable->setItem(n, 0, new QTableWidgetItem(tr("New Profile")));
×
NEW
815
  ui->profileTable->setItem(n, 1, new QTableWidgetItem(ui->storePath->text()));
×
UNCOV
816
  ui->profileTable->setItem(n, 2, new QTableWidgetItem());
×
UNCOV
817
  populating = false;
×
818

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

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

825
  // Initialize git settings for the new profile with current UI state
826
  // Use unique key instead of translated string
827
  QHash<QString, QString> gitSettings;
×
828
  gitSettings["useGit"] = ui->checkBoxUseGit->isChecked() ? "true" : "false";
×
UNCOV
829
  gitSettings["autoPush"] =
×
830
      ui->checkBoxAutoPush->isChecked() ? "true" : "false";
×
UNCOV
831
  gitSettings["autoPull"] =
×
832
      ui->checkBoxAutoPull->isChecked() ? "true" : "false";
×
833
  dialogGitSettings[uniqueKey] = gitSettings;
×
834
  currentProfileName = uniqueKey;
×
835

836
  ui->profileTable->editItem(ui->profileTable->item(currentRow, 0));
×
837
  ui->profileTable->item(currentRow, 0)->setSelected(true);
×
838

839
  validate();
×
840
  updateProfileStatus(currentRow);
×
841
}
×
842

843
/**
844
 * @brief ConfigDialog::on_profileTable_cellDoubleClicked open folder browser
845
 * for path column (column 1).
846
 */
NEW
847
void ConfigDialog::on_profileTable_cellDoubleClicked(int row, int column) {
×
NEW
848
  if (column == 1) {
×
NEW
849
    QString dir = selectFolder();
×
850
    if (!dir.isEmpty()) {
×
NEW
851
      ui->profileTable->item(row, 1)->setText(dir);
×
852
    }
853
  }
NEW
854
}
×
855

856
/**
857
 * @brief ConfigDialog::on_deleteButton_clicked remove a profile row.
858
 */
859
void ConfigDialog::on_deleteButton_clicked() {
×
860
  QSet<int> selectedRows; //  we use a set to prevent doubles
UNCOV
861
  QList<QTableWidgetItem *> itemList = ui->profileTable->selectedItems();
×
UNCOV
862
  if (itemList.count() == 0) {
×
863
    QMessageBox::warning(this, tr("No profile selected"),
×
UNCOV
864
                         tr("No profile selected to delete"));
×
865
    return;
866
  }
867

868
  // Collect profile names to delete from dialogGitSettings
NEW
869
  QStringList profilesToDelete;
×
870
  QTableWidgetItem *item;
NEW
871
  foreach (item, itemList) {
×
NEW
872
    selectedRows.insert(item->row());
×
NEW
873
    QTableWidgetItem *nameItem = ui->profileTable->item(item->row(), 0);
×
NEW
874
    if (nameItem != nullptr) {
×
NEW
875
      profilesToDelete.append(nameItem->text());
×
876
    }
877
  }
878

879
  // get a list, and sort it big to small
UNCOV
880
  QList<int> rows = selectedRows.values();
×
881
  std::sort(rows.begin(), rows.end());
×
882
  // now actually do the removing:
883
  foreach (int row, rows)
×
884
    ui->profileTable->removeRow(row);
×
885

886
  // Remove deleted profiles from dialogGitSettings
UNCOV
887
  foreach (const QString &profileName, profilesToDelete) {
×
888
    dialogGitSettings.remove(profileName);
×
889
  }
890

891
  // Clear currentProfileName if it was deleted
892
  if (profilesToDelete.contains(currentProfileName)) {
×
UNCOV
893
    currentProfileName.clear();
×
894
  }
895

896
  if (ui->profileTable->rowCount() < 1) {
×
897
    ui->deleteButton->setEnabled(false);
×
898
  }
899

900
  validate();
×
901
  updateProfileStatus(-1);
×
902
}
903

904
/**
905
 * @brief ConfigDialog::criticalMessage wrapper for showing critical messages
906
 * in a popup.
907
 * @param title
908
 * @param text
909
 */
910
void ConfigDialog::criticalMessage(const QString &title, const QString &text) {
×
911
  QMessageBox::critical(this, title, text, QMessageBox::Ok, QMessageBox::Ok);
×
912
}
×
913

914
/**
915
 * @brief Checks whether the qrencode executable is available on the system.
916
 * @example
917
 * bool result = ConfigDialog::isQrencodeAvailable();
918
 * std::cout << result << std::endl; // Expected output: true if qrencode is
919
 * found, otherwise false
920
 *
921
 * @return bool - True if qrencode is available; otherwise false. On Windows,
922
 * always returns false.
923
 */
924
auto ConfigDialog::isQrencodeAvailable() -> bool {
×
925
#ifdef Q_OS_WIN
926
  return false;
927
#else
928
  QProcess which;
×
929
  which.start("which", QStringList() << "qrencode");
×
930
  which.waitForFinished();
×
931
  QtPassSettings::setQrencodeExecutable(
×
932
      which.readAllStandardOutput().trimmed());
×
933
  return which.exitCode() == 0;
×
934
#endif
935
}
×
936

937
auto ConfigDialog::isPassOtpAvailable() -> bool {
×
938
#ifdef Q_OS_WIN
939
  return false;
940
#else
941
  QProcess pass;
×
942
  pass.start(QtPassSettings::getPassExecutable(), QStringList() << "otp"
×
943
                                                                << "--help");
×
944
  pass.waitForFinished(2000);
×
945
  return pass.exitCode() == 0;
×
946
#endif
947
}
×
948

949
/**
950
 * @brief ConfigDialog::wizard first-time use wizard.
951
 */
952
void ConfigDialog::wizard() {
×
953
  (void)Util::configIsValid();
×
954
  on_autodetectButton_clicked();
×
955

956
  if (!checkGpgExistence()) {
×
957
    return;
958
  }
959
  if (!checkSecretKeys()) {
×
960
    return;
961
  }
962
  if (!checkPasswordStore()) {
×
963
    return;
964
  }
965
  handleGpgIdFile();
×
966

967
  ui->checkBoxHidePassword->setCheckState(Qt::Checked);
×
968
}
969

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

1010
/**
1011
 * @brief Checks whether secret keys are available and, if needed, prompts the
1012
 * user to generate them.
1013
 * @example
1014
 * bool result = ConfigDialog.checkSecretKeys();
1015
 * std::cout << result << std::endl; // Expected output: true if keys are
1016
 * present or key generation dialog is accepted, false otherwise
1017
 *
1018
 * @return bool - Returns true when secret keys are already available or the key
1019
 * generation dialog is accepted; false if the dialog is rejected.
1020
 */
1021
auto ConfigDialog::checkSecretKeys() -> bool {
×
1022
  QString gpg = ui->gpgPath->text();
×
1023
  QStringList names = getSecretKeys();
×
1024

1025
#ifdef QT_DEBUG
1026
  dbg() << names;
1027
#endif
1028

1029
  if ((gpg.startsWith("wsl ") || QFile(gpg).exists()) && names.empty()) {
×
1030
    KeygenDialog d(this);
×
1031
    return d.exec();
×
1032
  }
×
1033
  return true;
1034
}
1035

1036
/**
1037
 * @brief Checks whether the password-store path exists and prompts the user to
1038
 * create it if it does not.
1039
 * @example
1040
 * bool result = ConfigDialog::checkPasswordStore();
1041
 * std::cout << std::boolalpha << result << std::endl; // Expected output: true
1042
 * if the store exists or is created successfully, false if creation fails
1043
 *
1044
 * @return bool - True if the password-store exists or is successfully created,
1045
 * false if creation fails.
1046
 */
1047
auto ConfigDialog::checkPasswordStore() -> bool {
×
1048
  QString passStore = ui->storePath->text();
×
1049

1050
  if (!QFile(passStore).exists()) {
×
1051
    if (QMessageBox::question(
×
1052
            this, tr("Create password-store?"),
×
1053
            tr("Would you like to create a password-store at %1?")
×
1054
                .arg(passStore),
×
1055
            QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
1056
      if (!QDir().mkdir(passStore)) {
×
1057
        QMessageBox::warning(
×
1058
            this, tr("Error"),
×
1059
            tr("Failed to create password-store at: %1").arg(passStore));
×
1060
        return false;
×
1061
      }
1062
#ifdef Q_OS_WIN
1063
      SetFileAttributes(passStore.toStdWString().c_str(),
1064
                        FILE_ATTRIBUTE_HIDDEN);
1065
#endif
1066
      if (ui->checkBoxUseGit->isChecked()) {
×
1067
        emit mainWindow->passGitInitNeeded();
×
1068
      }
1069
      mainWindow->userDialog(passStore);
×
1070
    }
1071
  }
1072
  return true;
1073
}
1074

1075
/**
1076
 * @brief Handles selection and validation of the password store's .gpg-id file.
1077
 * @example
1078
 * ConfigDialog dialog;
1079
 * dialog.handleGpgIdFile();
1080
 *
1081
 * @return void - This method does not return a value; it updates the UI flow
1082
 * and may prompt the user to choose a valid password store.
1083
 */
1084
void ConfigDialog::handleGpgIdFile() {
×
1085
  QString passStore = ui->storePath->text();
×
1086
  if (!QFile(QDir(passStore).filePath(".gpg-id")).exists()) {
×
1087
#ifdef QT_DEBUG
1088
    dbg() << ".gpg-id file does not exist";
1089
#endif
1090
    criticalMessage(tr("Password store not initialised"),
×
1091
                    tr("The folder %1 doesn't seem to be a password store or "
×
1092
                       "is not yet initialised.")
1093
                        .arg(passStore));
×
1094

1095
    while (!QFile(passStore).exists()) {
×
1096
      on_toolButtonStore_clicked();
×
1097
      if (passStore == ui->storePath->text()) {
×
1098
        return;
1099
      }
1100
      passStore = ui->storePath->text();
×
1101
    }
1102
    if (!QFile(passStore + ".gpg-id").exists()) {
×
1103
#ifdef QT_DEBUG
1104
      dbg() << ".gpg-id file still does not exist :/";
1105
#endif
1106
      mainWindow->userDialog(passStore);
×
1107
    }
1108
  }
1109
}
1110

1111
/**
1112
 * @brief ConfigDialog::useTrayIcon set preference for using trayicon.
1113
 * Enable or disable related checkboxes accordingly.
1114
 * @param useSystray
1115
 */
1116
void ConfigDialog::useTrayIcon(bool useSystray) {
×
1117
  if (QSystemTrayIcon::isSystemTrayAvailable()) {
×
1118
    ui->checkBoxUseTrayIcon->setChecked(useSystray);
×
1119
    ui->checkBoxHideOnClose->setEnabled(useSystray);
×
1120
    ui->checkBoxStartMinimized->setEnabled(useSystray);
×
1121

1122
    if (!useSystray) {
×
1123
      ui->checkBoxHideOnClose->setChecked(false);
×
1124
      ui->checkBoxStartMinimized->setChecked(false);
×
1125
    }
1126
  }
1127
}
×
1128

1129
/**
1130
 * @brief ConfigDialog::on_checkBoxUseTrayIcon_clicked enable and disable
1131
 * related checkboxes.
1132
 */
1133
void ConfigDialog::on_checkBoxUseTrayIcon_clicked() {
×
1134
  bool state = ui->checkBoxUseTrayIcon->isChecked();
×
1135
  ui->checkBoxHideOnClose->setEnabled(state);
×
1136
  ui->checkBoxStartMinimized->setEnabled(state);
×
1137
}
×
1138

1139
/**
1140
 * @brief ConfigDialog::closeEvent close this window.
1141
 * @param event
1142
 */
1143
void ConfigDialog::closeEvent(QCloseEvent *event) {
×
1144
  QtPassSettings::setDialogGeometry("configDialog", saveGeometry());
×
1145
  if (!isMaximized()) {
×
1146
    QtPassSettings::setDialogPos("configDialog", pos());
×
1147
    QtPassSettings::setDialogSize("configDialog", size());
×
1148
  }
1149
  QtPassSettings::setDialogMaximized("configDialog", isMaximized());
×
1150
  event->accept();
1151
}
×
1152

1153
/**
1154
 * @brief ConfigDialog::useGit set preference for using git.
1155
 * @param useGit
1156
 */
1157
void ConfigDialog::useGit(bool useGit) {
×
1158
  ui->checkBoxUseGit->setChecked(useGit);
×
1159
  on_checkBoxUseGit_clicked();
×
1160
}
×
1161

1162
/**
1163
 * @brief ConfigDialog::useOtp set preference for using otp plugin.
1164
 * @param useOtp
1165
 */
1166
void ConfigDialog::useOtp(bool useOtp) {
×
1167
  ui->checkBoxUseOtp->setChecked(useOtp);
×
1168
}
×
1169

1170
void ConfigDialog::useGrepSearch(bool useGrepSearch) {
×
1171
  ui->checkBoxUseGrepSearch->setChecked(useGrepSearch);
×
1172
}
×
1173

1174
/**
1175
 * @brief ConfigDialog::useQrencode set preference for using qrencode plugin.
1176
 * @param useQrencode
1177
 */
1178
void ConfigDialog::useQrencode(bool useQrencode) {
×
1179
  ui->checkBoxUseQrencode->setChecked(useQrencode);
×
1180
}
×
1181

1182
/**
1183
 * @brief ConfigDialog::on_checkBoxUseGit_clicked enable or disable related
1184
 * checkboxes.
1185
 */
1186
void ConfigDialog::on_checkBoxUseGit_clicked() {
×
1187
  ui->checkBoxAddGPGId->setEnabled(ui->checkBoxUseGit->isChecked());
×
1188
  ui->checkBoxAutoPull->setEnabled(ui->checkBoxUseGit->isChecked());
×
1189
  ui->checkBoxAutoPush->setEnabled(ui->checkBoxUseGit->isChecked());
×
1190
}
×
1191

1192
/**
1193
 * @brief ConfigDialog::on_toolButtonPwgen_clicked enable or disable related
1194
 * options in the interface.
1195
 */
1196
void ConfigDialog::on_toolButtonPwgen_clicked() {
×
1197
  QString pwgen = selectExecutable();
×
1198
  if (!pwgen.isEmpty()) {
×
1199
    ui->pwgenPath->setText(pwgen);
×
1200
    ui->checkBoxUsePwgen->setEnabled(true);
×
1201
  } else {
1202
    ui->checkBoxUsePwgen->setEnabled(false);
×
1203
    ui->checkBoxUsePwgen->setChecked(false);
×
1204
  }
1205
}
×
1206

1207
/**
1208
 * @brief ConfigDialog::setPwgenPath set pwgen executable path.
1209
 * Enable or disable related options in the interface.
1210
 * @param pwgen
1211
 */
1212
void ConfigDialog::setPwgenPath(const QString &pwgen) {
×
1213
  ui->pwgenPath->setText(pwgen);
×
1214
  if (pwgen.isEmpty()) {
×
1215
    ui->checkBoxUsePwgen->setChecked(false);
×
1216
    ui->checkBoxUsePwgen->setEnabled(false);
×
1217
  }
1218
  on_checkBoxUsePwgen_clicked();
×
1219
}
×
1220

1221
/**
1222
 * @brief ConfigDialog::on_checkBoxUsePwgen_clicked enable or disable related
1223
 * options in the interface.
1224
 */
1225
void ConfigDialog::on_checkBoxUsePwgen_clicked() {
×
1226
  if (ui->radioButtonPass->isChecked())
×
1227
    return;
1228
  bool usePwgen = ui->checkBoxUsePwgen->isChecked();
×
1229
  ui->checkBoxAvoidCapitals->setEnabled(usePwgen);
×
1230
  ui->checkBoxAvoidNumbers->setEnabled(usePwgen);
×
1231
  ui->checkBoxLessRandom->setEnabled(usePwgen);
×
1232
  ui->checkBoxUseSymbols->setEnabled(usePwgen);
×
1233
  ui->lineEditPasswordChars->setEnabled(!usePwgen);
×
1234
  ui->labelPasswordChars->setEnabled(!usePwgen);
×
1235
  ui->passwordCharTemplateSelector->setEnabled(!usePwgen);
×
1236
}
1237

1238
/**
1239
 * @brief ConfigDialog::usePwgen set preference for using pwgen (can be
1240
 * overruled by empty pwgenPath).
1241
 * enable or disable related options in the interface via
1242
 * ConfigDialog::on_checkBoxUsePwgen_clicked
1243
 * @param usePwgen
1244
 */
1245
void ConfigDialog::usePwgen(bool usePwgen) {
×
1246
  if (ui->pwgenPath->text().isEmpty()) {
×
1247
    usePwgen = false;
1248
  }
1249
  ui->checkBoxUsePwgen->setChecked(usePwgen);
×
1250
  on_checkBoxUsePwgen_clicked();
×
1251
}
×
1252

1253
void ConfigDialog::setPasswordConfiguration(
×
1254
    const PasswordConfiguration &config) {
1255
  ui->spinBoxPasswordLength->setValue(config.length);
×
1256
  ui->passwordCharTemplateSelector->setCurrentIndex(config.selected);
×
1257
  if (config.selected != PasswordConfiguration::CUSTOM) {
×
1258
    ui->lineEditPasswordChars->setEnabled(false);
×
1259
  }
1260
  ui->lineEditPasswordChars->setText(config.Characters[config.selected]);
×
1261
}
×
1262

1263
auto ConfigDialog::getPasswordConfiguration() -> PasswordConfiguration {
×
1264
  PasswordConfiguration config;
×
1265
  config.length = ui->spinBoxPasswordLength->value();
×
1266
  config.selected = static_cast<PasswordConfiguration::characterSet>(
×
1267
      ui->passwordCharTemplateSelector->currentIndex());
×
1268
  config.Characters[PasswordConfiguration::CUSTOM] =
1269
      ui->lineEditPasswordChars->text();
×
1270
  return config;
×
1271
}
×
1272

1273
/**
1274
 * @brief ConfigDialog::on_passwordCharTemplateSelector_activated sets the
1275
 * passwordChar Template
1276
 * combo box to the desired entry
1277
 * @param entry of
1278
 */
1279
void ConfigDialog::on_passwordCharTemplateSelector_activated(int index) {
×
NEW
1280
  ui->lineEditPasswordChars->setText(
×
NEW
1281
      QtPassSettings::getPasswordConfiguration().Characters[index]);
×
NEW
1282
  if (index == PasswordConfiguration::CUSTOM) {
×
NEW
1283
    ui->lineEditPasswordChars->setEnabled(true);
×
1284
  } else {
NEW
1285
    ui->lineEditPasswordChars->setEnabled(false);
×
1286
  }
NEW
1287
}
×
1288

1289
/**
1290
 * @brief ConfigDialog::on_checkBoxUseTemplate_clicked enable or disable the
1291
 * template field and options.
1292
 */
NEW
1293
void ConfigDialog::on_checkBoxUseTemplate_clicked() {
×
NEW
1294
  ui->plainTextEditTemplate->setEnabled(ui->checkBoxUseTemplate->isChecked());
×
NEW
1295
  ui->checkBoxTemplateAllFields->setEnabled(
×
NEW
1296
      ui->checkBoxUseTemplate->isChecked());
×
NEW
1297
}
×
1298

NEW
1299
void ConfigDialog::onProfileTableItemChanged(QTableWidgetItem *item) {
×
1300
  // Skip processing during programmatic population
1301
  if (populating) {
×
1302
    return;
1303
  }
1304

1305
  // If profile name changed (column 0), update dialogGitSettings key
NEW
1306
  if (item != nullptr && item->column() == 0) {
×
NEW
1307
    QString newName = item->text();
×
1308
    // If currentProfileName was set and is different, rename in
1309
    // dialogGitSettings
NEW
1310
    if (!currentProfileName.isEmpty() && currentProfileName != newName) {
×
NEW
1311
      if (dialogGitSettings.contains(currentProfileName)) {
×
1312
        QHash<QString, QString> settings =
1313
            dialogGitSettings.take(currentProfileName);
NEW
1314
        dialogGitSettings[newName] = settings;
×
NEW
1315
      }
×
NEW
1316
      currentProfileName = newName;
×
1317
    }
1318
  }
1319

NEW
1320
  validate(item);
×
NEW
1321
  updateProfileStatus(item ? item->row() : -1);
×
1322
}
1323

1324
/**
1325
 * @brief Save the current profile's Git checkbox states to dialogGitSettings.
1326
 */
NEW
1327
void ConfigDialog::saveCurrentGitSettings() {
×
NEW
1328
  if (currentProfileName.isEmpty()) {
×
NEW
1329
    return;
×
1330
  }
1331

NEW
1332
  QHash<QString, QString> gitSettings;
×
NEW
1333
  gitSettings["useGit"] = ui->checkBoxUseGit->isChecked() ? "true" : "false";
×
NEW
1334
  gitSettings["autoPush"] =
×
NEW
1335
      ui->checkBoxAutoPush->isChecked() ? "true" : "false";
×
NEW
1336
  gitSettings["autoPull"] =
×
NEW
1337
      ui->checkBoxAutoPull->isChecked() ? "true" : "false";
×
NEW
1338
  dialogGitSettings[currentProfileName] = gitSettings;
×
1339
}
×
1340

UNCOV
1341
void ConfigDialog::onProfileTableSelectionChanged() {
×
1342
  // Save current profile's Git settings before switching
UNCOV
1343
  saveCurrentGitSettings();
×
1344

UNCOV
1345
  QList<QTableWidgetItem *> selected = ui->profileTable->selectedItems();
×
UNCOV
1346
  if (selected.isEmpty()) {
×
1347
    return;
1348
  }
1349
  QTableWidgetItem *nameItem =
1350
      ui->profileTable->item(selected.first()->row(), 0);
×
1351
  if (nameItem == nullptr) {
×
1352
    return;
1353
  }
1354
  QString profileName = nameItem->text();
×
1355
  currentProfileName = profileName;
×
1356
  loadGitSettingsForProfile(profileName);
×
1357
}
1358

1359
/**
1360
 * @brief Update status bar with profile preview for given row.
1361
 * @param row The row index to preview, or -1 to clear.
1362
 */
1363
void ConfigDialog::updateProfileStatus(int row) {
×
1364
  if (row < 0 || row >= ui->profileTable->rowCount()) {
×
1365
    ui->statusLabel->setText(QString());
×
1366
    return;
×
1367
  }
1368

1369
  QTableWidgetItem *nameItem = ui->profileTable->item(row, 0);
×
1370
  QTableWidgetItem *pathItem = ui->profileTable->item(row, 1);
×
1371

1372
  QString statusMessage;
×
1373
  if (nameItem && !nameItem->text().isEmpty() && pathItem &&
×
1374
      !pathItem->text().isEmpty()) {
×
1375
    QDir dir(QDir::cleanPath(pathItem->text()));
×
1376
    if (!dir.exists()) {
×
1377
      statusMessage = tr("New profile: %1 at %2")
×
1378
                          .arg(nameItem->text())
×
1379
                          .arg(QDir::cleanPath(pathItem->text()));
×
1380
    } else {
1381
      statusMessage = tr("Profile: %1 at %2")
×
1382
                          .arg(nameItem->text())
×
1383
                          .arg(QDir::cleanPath(pathItem->text()));
×
1384
    }
1385
  } else {
×
1386
    statusMessage = tr("Fill in all required fields");
×
1387
  }
1388

1389
  ui->statusLabel->setText(statusMessage);
×
1390
}
1391

1392
/**
1393
 * @brief ConfigDialog::useTemplate set preference for using templates.
1394
 * @param useTemplate
1395
 */
1396
void ConfigDialog::useTemplate(bool useTemplate) {
×
1397
  ui->checkBoxUseTemplate->setChecked(useTemplate);
×
1398
  on_checkBoxUseTemplate_clicked();
×
1399
}
×
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