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

IJHack / QtPass / 24588315042

17 Apr 2026 09:53PM UTC coverage: 21.778% (-0.04%) from 21.818%
24588315042

push

github

web-flow
fix: clear stale selection and update proxy filter on profile change (#1033)

When switching profiles or accepting config changes:

- `proxyModel.store` was never updated, so the filter (StoreModel::showThis)
  kept using the old profile's path — causing Util::getDir to compute
  `../../old-profile/subdir/` relative paths when adding new entries.
- Qt's delayed layout re-evaluation (doDelayedItemsLayout, triggered by
  setRootIndex) could reinstate a stale currentIndex, compounding the issue.
- Decrypted content, currentDir, clipboard text, passwordName, and the panel
  were not cleared, leaving stale data from the previous profile visible.

Fix:
- Add StoreModel::setStore() to update the filter path without reinitialising
  the source model.
- Call proxyModel.setStore() before mapFromSource so filtering is correct
  when the new root is mapped.
- Call deselect() to clear all stale UI state (currentDir, clipboard, panel,
  passwordName, edit/delete actions).
- Call ui->treeView->setCurrentIndex(QModelIndex()) after setRootIndex so
  the view's own currentIndexSet state is also cleared.
- Cache QtPassSettings::getPassStore() in a local variable to avoid
  redundant settings reads.
- Apply the same fixes to config() where the same staleness issues exist.

Closes #248

0 of 16 new or added lines in 2 files covered. (0.0%)

10 existing lines in 2 files now uncovered.

1176 of 5400 relevant lines covered (21.78%)

8.66 hits per line

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

0.0
/src/mainwindow.cpp
1
// SPDX-FileCopyrightText: 2014 Anne Jan Brouwer
2
// SPDX-License-Identifier: GPL-3.0-or-later
3
#include "mainwindow.h"
4

5
#ifdef QT_DEBUG
6
#include "debughelper.h"
7
#endif
8

9
#include "configdialog.h"
10
#include "filecontent.h"
11
#include "passworddialog.h"
12
#include "qpushbuttonasqrcode.h"
13
#include "qpushbuttonshowpassword.h"
14
#include "qpushbuttonwithclipboard.h"
15
#include "qtpass.h"
16
#include "qtpasssettings.h"
17
#include "trayicon.h"
18
#include "ui_mainwindow.h"
19
#include "usersdialog.h"
20
#include "util.h"
21
#include <QCloseEvent>
22
#include <QDesktopServices>
23
#include <QDialog>
24
#include <QDirIterator>
25
#include <QFileInfo>
26
#include <QInputDialog>
27
#include <QLabel>
28
#include <QMenu>
29
#include <QMessageBox>
30
#include <QShortcut>
31
#include <QTimer>
32
#include <utility>
33

34
/**
35
 * @brief MainWindow::MainWindow handles all of the main functionality and also
36
 * the main window.
37
 * @param searchText for searching from cli
38
 * @param parent pointer
39
 */
40
MainWindow::MainWindow(const QString &searchText, QWidget *parent)
×
41
    : QMainWindow(parent), ui(new Ui::MainWindow), keygen(nullptr),
×
42
      tray(nullptr) {
×
43
#ifdef __APPLE__
44
  // extra treatment for mac os
45
  // see http://doc.qt.io/qt-5/qkeysequence.html#qt_set_sequence_auto_mnemonic
46
  qt_set_sequence_auto_mnemonic(true);
47
#endif
48
  ui->setupUi(this);
×
49

50
  m_qtPass = new QtPass(this);
×
51

52
  // register shortcut ctrl/cmd + Q to close the main window
53
  new QShortcut(QKeySequence(Qt::CTRL | Qt::Key_Q), this, SLOT(close()));
×
54
  // register shortcut ctrl/cmd + C to copy the currently selected password
55
  new QShortcut(QKeySequence(QKeySequence::StandardKey::Copy), this,
×
56
                SLOT(copyPasswordFromTreeview()));
×
57

58
  model.setNameFilters(QStringList() << "*.gpg");
×
59
  model.setNameFilterDisables(false);
×
60

61
  /*
62
   * I added this to solve Windows bug but now on GNU/Linux the main folder,
63
   * if hidden, disappear
64
   *
65
   * model.setFilter(QDir::NoDot);
66
   */
67

68
  QString passStore = QtPassSettings::getPassStore(Util::findPasswordStore());
×
69

70
  QModelIndex rootDir = model.setRootPath(passStore);
×
71
  model.fetchMore(rootDir);
×
72

73
  proxyModel.setModelAndStore(&model, passStore);
×
74
  selectionModel.reset(new QItemSelectionModel(&proxyModel));
×
75

76
  ui->treeView->setModel(&proxyModel);
×
77
  ui->treeView->setRootIndex(proxyModel.mapFromSource(rootDir));
×
78
  ui->treeView->setColumnHidden(1, true);
×
79
  ui->treeView->setColumnHidden(2, true);
×
80
  ui->treeView->setColumnHidden(3, true);
×
81
  ui->treeView->setHeaderHidden(true);
×
82
  ui->treeView->setIndentation(15);
×
83
  ui->treeView->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
×
84
  ui->treeView->setContextMenuPolicy(Qt::CustomContextMenu);
×
85
  ui->treeView->header()->setSectionResizeMode(0, QHeaderView::Stretch);
×
86
  ui->treeView->sortByColumn(0, Qt::AscendingOrder);
×
87
  connect(ui->treeView, &QWidget::customContextMenuRequested, this,
×
88
          &MainWindow::showContextMenu);
×
89
  connect(ui->treeView, &DeselectableTreeView::emptyClicked, this,
×
90
          &MainWindow::deselect);
×
91

92
  if (QtPassSettings::isUseMonospace()) {
×
93
    QFont monospace("Monospace");
×
94
    monospace.setStyleHint(QFont::Monospace);
×
95
    ui->textBrowser->setFont(monospace);
×
96
  }
×
97
  if (QtPassSettings::isNoLineWrapping()) {
×
98
    ui->textBrowser->setLineWrapMode(QTextBrowser::NoWrap);
×
99
  }
100
  ui->textBrowser->setOpenExternalLinks(true);
×
101
  ui->textBrowser->setContextMenuPolicy(Qt::CustomContextMenu);
×
102
  connect(ui->textBrowser, &QWidget::customContextMenuRequested, this,
×
103
          &MainWindow::showBrowserContextMenu);
×
104

105
  updateProfileBox();
×
106

107
  QtPassSettings::getPass()->updateEnv();
×
108
  clearPanelTimer.setInterval(MS_PER_SECOND *
×
109
                              QtPassSettings::getAutoclearPanelSeconds());
×
110
  clearPanelTimer.setSingleShot(true);
×
111
  connect(&clearPanelTimer, &QTimer::timeout, this, [this]() { clearPanel(); });
×
112

113
  searchTimer.setInterval(350);
×
114
  searchTimer.setSingleShot(true);
×
115

116
  connect(&searchTimer, &QTimer::timeout, this, &MainWindow::onTimeoutSearch);
×
117

118
  initToolBarButtons();
×
119
  initStatusBar();
×
120

121
  ui->lineEdit->setClearButtonEnabled(true);
×
122

123
  setUiElementsEnabled(true);
×
124

125
  QTimer::singleShot(10, this, SLOT(focusInput()));
126

127
  ui->lineEdit->setText(searchText);
×
128

129
  if (!m_qtPass->init()) {
×
130
    // no working config so this should just quit
131
    QApplication::quit();
×
132
  }
133
}
×
134

135
MainWindow::~MainWindow() { delete m_qtPass; }
×
136

137
/**
138
 * @brief MainWindow::focusInput selects any text (if applicable) in the search
139
 * box and sets focus to it. Allows for easy searching, called at application
140
 * start and when receiving empty message in MainWindow::messageAvailable when
141
 * compiled with SINGLE_APP=1 (default).
142
 */
143
void MainWindow::focusInput() {
×
144
  ui->lineEdit->selectAll();
×
145
  ui->lineEdit->setFocus();
×
146
}
×
147

148
/**
149
 * @brief MainWindow::changeEvent sets focus to the search box
150
 * @param event
151
 */
152
void MainWindow::changeEvent(QEvent *event) {
×
153
  QWidget::changeEvent(event);
×
154
  if (event->type() == QEvent::ActivationChange) {
×
155
    if (isActiveWindow()) {
×
156
      focusInput();
×
157
    }
158
  }
159
}
×
160

161
/**
162
 * @brief MainWindow::initToolBarButtons init main ToolBar and connect actions
163
 */
164
void MainWindow::initToolBarButtons() {
×
165
  connect(ui->actionAddPassword, &QAction::triggered, this,
×
166
          &MainWindow::addPassword);
×
167
  connect(ui->actionAddFolder, &QAction::triggered, this,
×
168
          &MainWindow::addFolder);
×
169
  connect(ui->actionEdit, &QAction::triggered, this, &MainWindow::onEdit);
×
170
  connect(ui->actionDelete, &QAction::triggered, this, &MainWindow::onDelete);
×
171
  connect(ui->actionPush, &QAction::triggered, this, &MainWindow::onPush);
×
172
  connect(ui->actionUpdate, &QAction::triggered, this, &MainWindow::onUpdate);
×
173
  connect(ui->actionUsers, &QAction::triggered, this, &MainWindow::onUsers);
×
174
  connect(ui->actionConfig, &QAction::triggered, this, &MainWindow::onConfig);
×
175
  connect(ui->actionOtp, &QAction::triggered, this, &MainWindow::onOtp);
×
176

177
  ui->actionAddPassword->setIcon(
×
178
      QIcon::fromTheme("document-new", QIcon(":/icons/document-new.svg")));
×
179
  ui->actionAddFolder->setIcon(
×
180
      QIcon::fromTheme("folder-new", QIcon(":/icons/folder-new.svg")));
×
181
  ui->actionEdit->setIcon(QIcon::fromTheme(
×
182
      "document-properties", QIcon(":/icons/document-properties.svg")));
×
183
  ui->actionDelete->setIcon(
×
184
      QIcon::fromTheme("edit-delete", QIcon(":/icons/edit-delete.svg")));
×
185
  ui->actionPush->setIcon(
×
186
      QIcon::fromTheme("go-up", QIcon(":/icons/go-top.svg")));
×
187
  ui->actionUpdate->setIcon(
×
188
      QIcon::fromTheme("go-down", QIcon(":/icons/go-bottom.svg")));
×
189
  ui->actionUsers->setIcon(QIcon::fromTheme(
×
190
      "x-office-address-book", QIcon(":/icons/x-office-address-book.svg")));
×
191
  ui->actionConfig->setIcon(QIcon::fromTheme(
×
192
      "applications-system", QIcon(":/icons/applications-system.svg")));
×
193
}
×
194

195
/**
196
 * @brief MainWindow::initStatusBar init statusBar with default message and logo
197
 */
198
void MainWindow::initStatusBar() {
×
199
  ui->statusBar->showMessage(tr("Welcome to QtPass %1").arg(VERSION), 2000);
×
200

201
  QPixmap logo = QPixmap::fromImage(QImage(":/artwork/icon.svg"))
×
202
                     .scaledToHeight(statusBar()->height());
×
203
  auto *logoApp = new QLabel(statusBar());
×
204
  logoApp->setPixmap(logo);
×
205
  statusBar()->addPermanentWidget(logoApp);
×
206
}
×
207

208
auto MainWindow::getCurrentTreeViewIndex() -> QModelIndex {
×
209
  return ui->treeView->currentIndex();
×
210
}
211

212
void MainWindow::cleanKeygenDialog() {
×
213
  this->keygen->close();
×
214
  this->keygen = nullptr;
×
215
}
×
216

217
/**
218
 * @brief Displays the given text in the main window text browser, optionally
219
 * marking it as an error and/or rendering it as HTML.
220
 * @example
221
 * MainWindow window;
222
 * window.flashText("Operation completed.", false, false);
223
 *
224
 * @param const QString &text - The text content to display.
225
 * @param const bool isError - If true, sets the text color to red before
226
 * displaying the text.
227
 * @param const bool isHtml - If true, treats the text as HTML and appends it to
228
 * the existing HTML content.
229
 * @return void - No return value.
230
 */
231
void MainWindow::flashText(const QString &text, const bool isError,
×
232
                           const bool isHtml) {
233
  if (isError) {
×
234
    ui->textBrowser->setTextColor(Qt::red);
×
235
  }
236

237
  if (isHtml) {
×
238
    QString _text = text;
239
    if (!ui->textBrowser->toPlainText().isEmpty()) {
×
240
      _text = ui->textBrowser->toHtml() + _text;
×
241
    }
242
    ui->textBrowser->setHtml(_text);
×
243
  } else {
244
    ui->textBrowser->setText(text);
×
245
  }
246
}
×
247

248
/**
249
 * @brief MainWindow::config pops up the configuration screen and handles all
250
 * inter-window communication
251
 */
252
void MainWindow::applyTextBrowserSettings() {
×
253
  if (QtPassSettings::isUseMonospace()) {
×
254
    QFont monospace("Monospace");
×
255
    monospace.setStyleHint(QFont::Monospace);
×
256
    ui->textBrowser->setFont(monospace);
×
257
  } else {
×
258
    ui->textBrowser->setFont(QFont());
×
259
  }
260

261
  if (QtPassSettings::isNoLineWrapping()) {
×
262
    ui->textBrowser->setLineWrapMode(QTextBrowser::NoWrap);
×
263
  } else {
264
    ui->textBrowser->setLineWrapMode(QTextBrowser::WidgetWidth);
×
265
  }
266
}
×
267

268
void MainWindow::applyWindowFlagsSettings() {
×
269
  if (QtPassSettings::isAlwaysOnTop()) {
×
270
    Qt::WindowFlags flags = windowFlags();
271
    this->setWindowFlags(flags | Qt::WindowStaysOnTopHint);
×
272
  } else {
273
    this->setWindowFlags(Qt::Window);
×
274
  }
275
  this->show();
×
276
}
×
277

278
/**
279
 * @brief Opens and processes the application configuration dialog, then applies
280
 * any accepted settings.
281
 * @example
282
 * config();
283
 *
284
 * @return void - This function does not return a value.
285
 */
286
void MainWindow::config() {
×
287
  QScopedPointer<ConfigDialog> d(new ConfigDialog(this));
×
288
  d->setModal(true);
×
289
  // Automatically default to pass if it's available
290
  if (m_qtPass->isFreshStart() &&
×
291
      QFile(QtPassSettings::getPassExecutable()).exists()) {
×
292
    QtPassSettings::setUsePass(true);
×
293
  }
294

295
  if (m_qtPass->isFreshStart()) {
×
296
    d->wizard(); //  does shit
×
297
  }
298
  if (d->exec()) {
×
299
    if (d->result() == QDialog::Accepted) {
×
300
      applyTextBrowserSettings();
×
301
      applyWindowFlagsSettings();
×
302

303
      updateProfileBox();
×
NEW
304
      const QString passStore = QtPassSettings::getPassStore();
×
NEW
305
      proxyModel.setStore(passStore);
×
NEW
306
      ui->treeView->setRootIndex(
×
NEW
307
          proxyModel.mapFromSource(model.setRootPath(passStore)));
×
NEW
308
      deselect();
×
NEW
309
      ui->treeView->setCurrentIndex(QModelIndex());
×
310

311
      if (m_qtPass->isFreshStart() && !Util::configIsValid()) {
×
312
        config();
×
313
      }
314
      QtPassSettings::getPass()->updateEnv();
×
315
      clearPanelTimer.setInterval(MS_PER_SECOND *
×
316
                                  QtPassSettings::getAutoclearPanelSeconds());
×
317
      m_qtPass->setClipboardTimer();
×
318

319
      updateGitButtonVisibility();
×
320
      updateOtpButtonVisibility();
×
321
      if (QtPassSettings::isUseTrayIcon() && tray == nullptr) {
×
322
        initTrayIcon();
×
323
      } else if (!QtPassSettings::isUseTrayIcon() && tray != nullptr) {
×
324
        destroyTrayIcon();
×
325
      }
326
    }
327

328
    m_qtPass->setFreshStart(false);
×
329
  }
330
}
×
331

332
/**
333
 * @brief MainWindow::onUpdate do a git pull
334
 */
335
void MainWindow::onUpdate(bool block) {
×
336
  ui->statusBar->showMessage(tr("Updating password-store"), 2000);
×
337
  if (block) {
×
338
    QtPassSettings::getPass()->GitPull_b();
×
339
  } else {
340
    QtPassSettings::getPass()->GitPull();
×
341
  }
342
}
×
343

344
/**
345
 * @brief MainWindow::onPush do a git push
346
 */
347
void MainWindow::onPush() {
×
348
  if (QtPassSettings::isUseGit()) {
×
349
    ui->statusBar->showMessage(tr("Updating password-store"), 2000);
×
350
    QtPassSettings::getPass()->GitPush();
×
351
  }
352
}
×
353

354
/**
355
 * @brief MainWindow::getFile get the selected file path
356
 * @param index
357
 * @param forPass returns relative path without '.gpg' extension
358
 * @return path
359
 * @return
360
 */
361
auto MainWindow::getFile(const QModelIndex &index, bool forPass) -> QString {
×
362
  if (!index.isValid() ||
×
363
      !model.fileInfo(proxyModel.mapToSource(index)).isFile()) {
×
364
    return {};
365
  }
366
  QString filePath = model.filePath(proxyModel.mapToSource(index));
×
367
  if (forPass) {
×
368
    filePath = QDir(QtPassSettings::getPassStore()).relativeFilePath(filePath);
×
369
    filePath.replace(Util::endsWithGpg(), "");
×
370
  }
371
  return filePath;
372
}
373

374
/**
375
 * @brief MainWindow::on_treeView_clicked read the selected password file
376
 * @param index
377
 */
378
void MainWindow::on_treeView_clicked(const QModelIndex &index) {
×
379
  bool cleared = ui->treeView->currentIndex().flags() == Qt::NoItemFlags;
×
380
  currentDir =
381
      Util::getDir(ui->treeView->currentIndex(), false, model, proxyModel);
×
382
  // Clear any previously cached clipped text before showing new password
383
  m_qtPass->clearClippedText();
×
384
  QString file = getFile(index, true);
×
385
  ui->passwordName->setText(getFile(index, true));
×
386
  if (!file.isEmpty() && !cleared) {
×
387
    QtPassSettings::getPass()->Show(file);
×
388
  } else {
389
    clearPanel(false);
×
390
    ui->actionEdit->setEnabled(false);
×
391
    ui->actionDelete->setEnabled(true);
×
392
  }
393
}
×
394

395
/**
396
 * @brief MainWindow::on_treeView_doubleClicked when doubleclicked on
397
 * TreeViewItem, open the edit Window
398
 * @param index
399
 */
400
void MainWindow::on_treeView_doubleClicked(const QModelIndex &index) {
×
401
  QFileInfo fileOrFolder =
402
      model.fileInfo(proxyModel.mapToSource(ui->treeView->currentIndex()));
×
403

404
  if (fileOrFolder.isFile()) {
×
405
    editPassword(getFile(index, true));
×
406
  }
407
}
×
408

409
/**
410
 * @brief MainWindow::deselect clear the selection, password and copy buffer
411
 */
412
void MainWindow::deselect() {
×
413
  currentDir = "";
×
414
  m_qtPass->clearClipboard();
×
415
  ui->treeView->clearSelection();
×
416
  ui->actionEdit->setEnabled(false);
×
417
  ui->actionDelete->setEnabled(false);
×
418
  ui->passwordName->setText("");
×
419
  clearPanel(false);
×
420
}
×
421

422
void MainWindow::executeWrapperStarted() {
×
423
  clearTemplateWidgets();
×
424
  ui->textBrowser->clear();
×
425
  setUiElementsEnabled(false);
×
426
  clearPanelTimer.stop();
×
427
}
×
428

429
/**
430
 * @brief Handles displaying parsed password entry content in the main window.
431
 * @example
432
 * void result = MainWindow::passShowHandler(p_output);
433
 * // Updates the UI with parsed fields and emits
434
 * passShowHandlerFinished(output)
435
 *
436
 * @param p_output - The raw output text containing the password entry data.
437
 * @return void - This function does not return a value.
438
 */
439
void MainWindow::passShowHandler(const QString &p_output) {
×
440
  QStringList templ = QtPassSettings::isUseTemplate()
×
441
                          ? QtPassSettings::getPassTemplate().split("\n")
×
442
                          : QStringList();
×
443
  bool allFields =
444
      QtPassSettings::isUseTemplate() && QtPassSettings::isTemplateAllFields();
×
445
  FileContent fileContent = FileContent::parse(p_output, templ, allFields);
×
446
  QString output = p_output;
447
  QString password = fileContent.getPassword();
×
448

449
  // set clipped text
450
  m_qtPass->setClippedText(password, p_output);
×
451

452
  // first clear the current view:
453
  clearTemplateWidgets();
×
454

455
  // show what is needed:
456
  if (QtPassSettings::isHideContent()) {
×
457
    output = "***" + tr("Content hidden") + "***";
×
458
  } else if (!QtPassSettings::isDisplayAsIs()) {
×
459
    if (!password.isEmpty()) {
×
460
      // set the password, it is hidden if needed in addToGridLayout
461
      addToGridLayout(0, tr("Password"), password);
×
462
    }
463

464
    NamedValues namedValues = fileContent.getNamedValues();
×
465
    for (int j = 0; j < namedValues.length(); ++j) {
×
466
      const NamedValue &nv = namedValues.at(j);
467
      addToGridLayout(j + 1, nv.name, nv.value);
×
468
    }
469
    if (ui->gridLayout->count() == 0) {
×
470
      ui->verticalLayoutPassword->setSpacing(0);
×
471
    } else {
472
      ui->verticalLayoutPassword->setSpacing(6);
×
473
    }
474

475
    output = fileContent.getRemainingDataForDisplay();
×
476
  }
477

478
  if (QtPassSettings::isUseAutoclearPanel()) {
×
479
    clearPanelTimer.start();
×
480
  }
481

482
  emit passShowHandlerFinished(output);
×
483
  setUiElementsEnabled(true);
×
484
}
×
485

486
/**
487
 * @brief Handles the OTP output by displaying it, copying it to the clipboard,
488
 * and updating the UI state.
489
 * @example
490
 * void MainWindow::passOtpHandler(const QString &p_output);
491
 *
492
 * @param const QString &p_output - The OTP code text to process; if empty, an
493
 * error message is shown instead.
494
 * @return void - This function does not return a value.
495
 */
496
void MainWindow::passOtpHandler(const QString &p_output) {
×
497
  if (!p_output.isEmpty()) {
×
498
    addToGridLayout(ui->gridLayout->count() + 1, tr("OTP Code"), p_output);
×
499
    m_qtPass->copyTextToClipboard(p_output);
×
500
    showStatusMessage(tr("OTP code copied to clipboard"));
×
501
  } else {
502
    flashText(tr("No OTP code found in this password entry"), true);
×
503
  }
504
  if (QtPassSettings::isUseAutoclearPanel()) {
×
505
    clearPanelTimer.start();
×
506
  }
507
  setUiElementsEnabled(true);
×
508
}
×
509

510
/**
511
 * @brief MainWindow::clearPanel hide the information from shoulder surfers
512
 */
513
void MainWindow::clearPanel(bool notify) {
×
514
  while (ui->gridLayout->count() > 0) {
×
515
    QLayoutItem *item = ui->gridLayout->takeAt(0);
×
516
    delete item->widget();
×
517
    delete item;
×
518
  }
519
  if (notify) {
×
520
    QString output = "***" + tr("Password and Content hidden") + "***";
×
521
    ui->textBrowser->setHtml(output);
×
522
  } else {
523
    ui->textBrowser->setHtml("");
×
524
  }
525
}
×
526

527
/**
528
 * @brief MainWindow::setUiElementsEnabled enable or disable the relevant UI
529
 * elements
530
 * @param state
531
 */
532
void MainWindow::setUiElementsEnabled(bool state) {
×
533
  ui->treeView->setEnabled(state);
×
534
  ui->lineEdit->setEnabled(state);
×
535
  ui->lineEdit->installEventFilter(this);
×
536
  ui->actionAddPassword->setEnabled(state);
×
537
  ui->actionAddFolder->setEnabled(state);
×
538
  ui->actionUsers->setEnabled(state);
×
539
  ui->actionConfig->setEnabled(state);
×
540
  // is a file selected?
541
  state &= ui->treeView->currentIndex().isValid();
×
542
  ui->actionDelete->setEnabled(state);
×
543
  ui->actionEdit->setEnabled(state);
×
544
  updateGitButtonVisibility();
×
545
  updateOtpButtonVisibility();
×
546
}
×
547

548
/**
549
 * @brief Restores the main window geometry, state, position, size, and
550
 * tray/icon settings from saved application settings.
551
 * @example
552
 * MainWindow window;
553
 * window.restoreWindow();
554
 *
555
 * @return void - This function does not return a value.
556
 */
557
void MainWindow::restoreWindow() {
×
558
  QByteArray geometry = QtPassSettings::getGeometry(saveGeometry());
×
559
  restoreGeometry(geometry);
×
560
  QByteArray savestate = QtPassSettings::getSavestate(saveState());
×
561
  restoreState(savestate);
×
562
  QPoint position = QtPassSettings::getPos(pos());
×
563
  move(position);
×
564
  QSize newSize = QtPassSettings::getSize(size());
×
565
  resize(newSize);
×
566
  if (QtPassSettings::isMaximized(isMaximized())) {
×
567
    showMaximized();
×
568
  }
569

570
  if (QtPassSettings::isAlwaysOnTop()) {
×
571
    Qt::WindowFlags flags = windowFlags();
572
    setWindowFlags(flags | Qt::WindowStaysOnTopHint);
×
573
    show();
×
574
  }
575

576
  if (QtPassSettings::isUseTrayIcon() && tray == nullptr) {
×
577
    initTrayIcon();
×
578
    if (QtPassSettings::isStartMinimized()) {
×
579
      // since we are still in constructor, can't directly hide
580
      QTimer::singleShot(10, this, SLOT(hide()));
×
581
    }
582
  } else if (!QtPassSettings::isUseTrayIcon() && tray != nullptr) {
×
583
    destroyTrayIcon();
×
584
  }
585
}
×
586

587
/**
588
 * @brief MainWindow::on_configButton_clicked run Mainwindow::config
589
 */
590
void MainWindow::onConfig() { config(); }
×
591

592
/**
593
 * @brief Executes when the string in the search box changes, collapses the
594
 * TreeView
595
 * @param arg1
596
 */
597
void MainWindow::on_lineEdit_textChanged(const QString &arg1) {
×
598
  ui->statusBar->showMessage(tr("Looking for: %1").arg(arg1), 1000);
×
599
  ui->treeView->expandAll();
×
600
  clearPanel(false);
×
601
  ui->passwordName->setText("");
×
602
  ui->actionEdit->setEnabled(false);
×
603
  ui->actionDelete->setEnabled(false);
×
604
  searchTimer.start();
×
605
}
×
606

607
/**
608
 * @brief MainWindow::onTimeoutSearch Fired when search is finished or too much
609
 * time from two keypresses is elapsed
610
 */
611
void MainWindow::onTimeoutSearch() {
×
612
  QString query = ui->lineEdit->text();
×
613

614
  if (query.isEmpty()) {
×
615
    ui->treeView->collapseAll();
×
616
    deselect();
×
617
  }
618

619
  query.replace(QStringLiteral(" "), ".*");
×
620
  QRegularExpression regExp(query, QRegularExpression::CaseInsensitiveOption);
×
621
  proxyModel.setFilterRegularExpression(regExp);
×
622
  ui->treeView->setRootIndex(proxyModel.mapFromSource(
×
623
      model.setRootPath(QtPassSettings::getPassStore())));
×
624

625
  if (proxyModel.rowCount() > 0 && !query.isEmpty()) {
×
626
    selectFirstFile();
×
627
  } else {
628
    ui->actionEdit->setEnabled(false);
×
629
    ui->actionDelete->setEnabled(false);
×
630
  }
631
}
×
632

633
/**
634
 * @brief MainWindow::on_lineEdit_returnPressed get searching
635
 *
636
 * Select the first possible file in the tree
637
 */
638
void MainWindow::on_lineEdit_returnPressed() {
×
639
#ifdef QT_DEBUG
640
  dbg() << "on_lineEdit_returnPressed" << proxyModel.rowCount();
641
#endif
642

643
  if (proxyModel.rowCount() > 0) {
×
644
    selectFirstFile();
×
645
    on_treeView_clicked(ui->treeView->currentIndex());
×
646
  }
647
}
×
648

649
/**
650
 * @brief MainWindow::selectFirstFile select the first possible file in the
651
 * tree
652
 */
653
void MainWindow::selectFirstFile() {
×
654
  QModelIndex index = proxyModel.mapFromSource(
×
655
      model.setRootPath(QtPassSettings::getPassStore()));
×
656
  index = firstFile(index);
×
657
  ui->treeView->setCurrentIndex(index);
×
658
}
×
659

660
/**
661
 * @brief MainWindow::firstFile return location of first possible file
662
 * @param parentIndex
663
 * @return QModelIndex
664
 */
665
auto MainWindow::firstFile(QModelIndex parentIndex) -> QModelIndex {
×
666
  QModelIndex index = parentIndex;
×
667
  int numRows = proxyModel.rowCount(parentIndex);
×
668
  for (int row = 0; row < numRows; ++row) {
×
669
    index = proxyModel.index(row, 0, parentIndex);
×
670
    if (model.fileInfo(proxyModel.mapToSource(index)).isFile()) {
×
671
      return index;
×
672
    }
673
    if (proxyModel.hasChildren(index)) {
×
674
      return firstFile(index);
×
675
    }
676
  }
677
  return index;
×
678
}
679

680
/**
681
 * @brief MainWindow::setPassword open passworddialog
682
 * @param file which pgp file
683
 * @param isNew insert (not update)
684
 */
685
void MainWindow::setPassword(const QString &file, bool isNew) {
×
686
  PasswordDialog d(file, isNew, this);
×
687

688
  if (!d.exec()) {
×
689
    ui->treeView->setFocus();
×
690
  }
691
}
×
692

693
/**
694
 * @brief MainWindow::addPassword add a new password by showing a
695
 * number of dialogs.
696
 */
697
void MainWindow::addPassword() {
×
698
  bool ok;
699
  QString dir =
700
      Util::getDir(ui->treeView->currentIndex(), true, model, proxyModel);
×
701
  QString file =
702
      QInputDialog::getText(this, tr("New file"),
×
703
                            tr("New password file: \n(Will be placed in %1 )")
×
704
                                .arg(QtPassSettings::getPassStore() +
×
705
                                     Util::getDir(ui->treeView->currentIndex(),
×
706
                                                  true, model, proxyModel)),
707
                            QLineEdit::Normal, "", &ok);
×
708
  if (!ok || file.isEmpty()) {
×
709
    return;
710
  }
711
  file = dir + file;
×
712
  setPassword(file);
×
713
}
714

715
/**
716
 * @brief MainWindow::onDelete remove password, if you are
717
 * sure.
718
 */
719
void MainWindow::onDelete() {
×
720
  QModelIndex currentIndex = ui->treeView->currentIndex();
×
721
  if (!currentIndex.isValid()) {
722
    // This fixes https://github.com/IJHack/QtPass/issues/556
723
    // Otherwise the entire password directory would be deleted if
724
    // nothing is selected in the tree view.
725
    return;
×
726
  }
727

728
  QFileInfo fileOrFolder =
729
      model.fileInfo(proxyModel.mapToSource(ui->treeView->currentIndex()));
×
730
  QString file = "";
×
731
  bool isDir = false;
732

733
  if (fileOrFolder.isFile()) {
×
734
    file = getFile(ui->treeView->currentIndex(), true);
×
735
  } else {
736
    file = Util::getDir(ui->treeView->currentIndex(), true, model, proxyModel);
×
737
    isDir = true;
738
  }
739

740
  QString dirMessage = tr(" and the whole content?");
741
  if (isDir) {
×
742
    QDirIterator it(model.rootPath() + QDir::separator() + file,
×
743
                    QDirIterator::Subdirectories);
×
744
    bool okDir = true;
745
    while (it.hasNext() && okDir) {
×
746
      it.next();
×
747
      if (QFileInfo(it.filePath()).isFile()) {
×
748
        if (QFileInfo(it.filePath()).suffix() != "gpg") {
×
749
          okDir = false;
750
          dirMessage = tr(" and the whole content? <br><strong>Attention: "
×
751
                          "there are unexpected files in the given folder, "
752
                          "check them before continue.</strong>");
753
        }
754
      }
755
    }
756
  }
×
757

758
  if (QMessageBox::question(
×
759
          this, isDir ? tr("Delete folder?") : tr("Delete password?"),
×
760
          tr("Are you sure you want to delete %1%2?")
×
761
              .arg(QDir::separator() + file, isDir ? dirMessage : "?"),
×
762
          QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) {
763
    return;
764
  }
765

766
  QtPassSettings::getPass()->Remove(file, isDir);
×
767
}
×
768

769
/**
770
 * @brief MainWindow::onOTP try and generate (selected) OTP code.
771
 */
772
void MainWindow::onOtp() {
×
773
  QString file = getFile(ui->treeView->currentIndex(), true);
×
774
  if (!file.isEmpty()) {
×
775
    if (QtPassSettings::isUseOtp()) {
×
776
      setUiElementsEnabled(false);
×
777
      QtPassSettings::getPass()->OtpGenerate(file);
×
778
    }
779
  } else {
780
    flashText(tr("No password selected for OTP generation"), true);
×
781
  }
782
}
×
783

784
/**
785
 * @brief MainWindow::onEdit try and edit (selected) password.
786
 */
787
void MainWindow::onEdit() {
×
788
  QString file = getFile(ui->treeView->currentIndex(), true);
×
789
  editPassword(file);
×
790
}
×
791

792
/**
793
 * @brief MainWindow::userDialog see MainWindow::onUsers()
794
 * @param dir folder to edit users for.
795
 */
796
void MainWindow::userDialog(const QString &dir) {
×
797
  if (!dir.isEmpty()) {
×
798
    currentDir = dir;
×
799
  }
800
  onUsers();
×
801
}
×
802

803
/**
804
 * @brief MainWindow::onUsers edit users for the current
805
 * folder,
806
 * gets lists and opens UserDialog.
807
 */
808
void MainWindow::onUsers() {
×
809
  QString dir =
810
      currentDir.isEmpty()
811
          ? Util::getDir(ui->treeView->currentIndex(), false, model, proxyModel)
×
812
          : currentDir;
×
813

814
  UsersDialog d(dir, this);
×
815
  if (!d.exec()) {
×
816
    ui->treeView->setFocus();
×
817
  }
818
}
×
819

820
/**
821
 * @brief MainWindow::messageAvailable we have some text/message/search to do.
822
 * @param message
823
 */
824
void MainWindow::messageAvailable(const QString &message) {
×
825
  if (message.isEmpty()) {
×
826
    focusInput();
×
827
  } else {
828
    ui->treeView->expandAll();
×
829
    ui->lineEdit->setText(message);
×
830
    on_lineEdit_returnPressed();
×
831
  }
832
  show();
×
833
  raise();
×
834
}
×
835

836
/**
837
 * @brief MainWindow::generateKeyPair internal gpg keypair generator . .
838
 * @param batch
839
 * @param keygenWindow
840
 */
841
void MainWindow::generateKeyPair(const QString &batch, QDialog *keygenWindow) {
×
842
  keygen = keygenWindow;
×
843
  emit generateGPGKeyPair(batch);
×
844
}
×
845

846
/**
847
 * @brief MainWindow::updateProfileBox update the list of profiles, optionally
848
 * select a more appropriate one to view too
849
 */
850
void MainWindow::updateProfileBox() {
×
851
  QHash<QString, QHash<QString, QString>> profiles =
852
      QtPassSettings::getProfiles();
×
853

854
  if (profiles.isEmpty()) {
855
    ui->profileWidget->hide();
×
856
  } else {
857
    ui->profileWidget->show();
×
858
    ui->profileBox->setEnabled(profiles.size() > 1);
×
859
    ui->profileBox->clear();
×
860
    QHashIterator<QString, QHash<QString, QString>> i(profiles);
×
861
    while (i.hasNext()) {
×
862
      i.next();
863
      if (!i.key().isEmpty()) {
×
864
        ui->profileBox->addItem(i.key());
×
865
      }
866
    }
867
    ui->profileBox->model()->sort(0);
×
868
  }
869
  int index = ui->profileBox->findText(QtPassSettings::getProfile());
×
870
  if (index != -1) { //  -1 for not found
×
871
    ui->profileBox->setCurrentIndex(index);
×
872
  }
873
}
×
874

875
/**
876
 * @brief MainWindow::on_profileBox_currentIndexChanged make sure we show the
877
 * correct "profile"
878
 * @param name
879
 */
880
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
881
void MainWindow::on_profileBox_currentIndexChanged(QString name) {
882
#else
883
/**
884
 * @brief Handles changes to the selected profile in the profile combo box.
885
 * @details Ignores the event during a fresh start or when the selected profile
886
 * matches the current profile. Otherwise, it clears the password field, updates
887
 * the active profile and related settings, refreshes the environment, and
888
 * resets the tree view and action states to reflect the newly selected profile.
889
 *
890
 * @param name - The newly selected profile name.
891
 * @return void - This function does not return a value.
892
 *
893
 */
894
void MainWindow::on_profileBox_currentTextChanged(const QString &name) {
×
895
#endif
896
  if (m_qtPass->isFreshStart() || name == QtPassSettings::getProfile()) {
×
UNCOV
897
    return;
×
898
  }
899

900
  ui->lineEdit->clear();
×
901

902
  QtPassSettings::setProfile(name);
×
903

904
  QtPassSettings::setPassStore(
×
905
      QtPassSettings::getProfiles().value(name).value("path"));
×
906
  QtPassSettings::setPassSigningKey(
×
907
      QtPassSettings::getProfiles().value(name).value("signingKey"));
×
908
  ui->statusBar->showMessage(tr("Profile changed to %1").arg(name), 2000);
×
909

910
  QtPassSettings::getPass()->updateEnv();
×
911

NEW
912
  const QString passStore = QtPassSettings::getPassStore();
×
NEW
913
  proxyModel.setStore(passStore);
×
NEW
914
  ui->treeView->setRootIndex(
×
NEW
915
      proxyModel.mapFromSource(model.setRootPath(passStore)));
×
NEW
916
  deselect();
×
NEW
917
  ui->treeView->setCurrentIndex(QModelIndex());
×
918
}
919

920
/**
921
 * @brief MainWindow::initTrayIcon show a nice tray icon on systems that
922
 * support
923
 * it
924
 */
925
void MainWindow::initTrayIcon() {
×
926
  this->tray = new TrayIcon(this);
×
927
  // Setup tray icon
928

929
  if (tray == nullptr) {
930
#ifdef QT_DEBUG
931
    dbg() << "Allocating tray icon failed.";
932
#endif
933
  }
934

935
  if (!tray->getIsAllocated()) {
×
936
    destroyTrayIcon();
×
937
  }
938
}
×
939

940
/**
941
 * @brief MainWindow::destroyTrayIcon remove that pesky tray icon
942
 */
943
void MainWindow::destroyTrayIcon() {
×
944
  delete this->tray;
×
945
  tray = nullptr;
×
946
}
×
947

948
/**
949
 * @brief MainWindow::closeEvent hide or quit
950
 * @param event
951
 */
952
void MainWindow::closeEvent(QCloseEvent *event) {
×
953
  if (QtPassSettings::isHideOnClose()) {
×
954
    this->hide();
×
955
    event->ignore();
956
  } else {
957
    m_qtPass->clearClipboard();
×
958

959
    QtPassSettings::setGeometry(saveGeometry());
×
960
    QtPassSettings::setSavestate(saveState());
×
961
    QtPassSettings::setMaximized(isMaximized());
×
962
    if (!isMaximized()) {
×
963
      QtPassSettings::setPos(pos());
×
964
      QtPassSettings::setSize(size());
×
965
    }
966
    event->accept();
967
  }
968
}
×
969

970
/**
971
 * @brief MainWindow::eventFilter filter out some events and focus the
972
 * treeview
973
 * @param obj
974
 * @param event
975
 * @return
976
 */
977
auto MainWindow::eventFilter(QObject *obj, QEvent *event) -> bool {
×
978
  if (obj == ui->lineEdit && event->type() == QEvent::KeyPress) {
×
979
    auto *key = dynamic_cast<QKeyEvent *>(event);
×
980
    if (key != nullptr && key->key() == Qt::Key_Down) {
×
981
      ui->treeView->setFocus();
×
982
    }
983
  }
984
  return QObject::eventFilter(obj, event);
×
985
}
986

987
/**
988
 * @brief MainWindow::keyPressEvent did anyone press return, enter or escape?
989
 * @param event
990
 */
991
void MainWindow::keyPressEvent(QKeyEvent *event) {
×
992
  switch (event->key()) {
×
993
  case Qt::Key_Delete:
×
994
    onDelete();
×
995
    break;
×
996
  case Qt::Key_Return:
×
997
  case Qt::Key_Enter:
998
    if (proxyModel.rowCount() > 0) {
×
999
      on_treeView_clicked(ui->treeView->currentIndex());
×
1000
    }
1001
    break;
1002
  case Qt::Key_Escape:
×
1003
    ui->lineEdit->clear();
×
1004
    break;
×
1005
  default:
1006
    break;
1007
  }
1008
}
×
1009

1010
/**
1011
 * @brief MainWindow::showContextMenu show us the (file or folder) context
1012
 * menu
1013
 * @param pos
1014
 */
1015
void MainWindow::showContextMenu(const QPoint &pos) {
×
1016
  QModelIndex index = ui->treeView->indexAt(pos);
×
1017
  bool selected = true;
1018
  if (!index.isValid()) {
1019
    ui->treeView->clearSelection();
×
1020
    ui->actionDelete->setEnabled(false);
×
1021
    ui->actionEdit->setEnabled(false);
×
1022
    currentDir = "";
×
1023
    selected = false;
1024
  }
1025

1026
  ui->treeView->setCurrentIndex(index);
×
1027

1028
  QPoint globalPos = ui->treeView->viewport()->mapToGlobal(pos);
×
1029

1030
  QFileInfo fileOrFolder =
1031
      model.fileInfo(proxyModel.mapToSource(ui->treeView->currentIndex()));
×
1032

1033
  QMenu contextMenu;
×
1034
  if (!selected || fileOrFolder.isDir()) {
×
1035
    QAction *openFolder =
1036
        contextMenu.addAction(tr("Open folder with file manager"));
×
1037
    QAction *addFolder = contextMenu.addAction(tr("Add folder"));
×
1038
    QAction *addPassword = contextMenu.addAction(tr("Add password"));
×
1039
    QAction *users = contextMenu.addAction(tr("Users"));
×
1040
    connect(openFolder, &QAction::triggered, this, &MainWindow::openFolder);
×
1041
    connect(addFolder, &QAction::triggered, this, &MainWindow::addFolder);
×
1042
    connect(addPassword, &QAction::triggered, this, &MainWindow::addPassword);
×
1043
    connect(users, &QAction::triggered, this, &MainWindow::onUsers);
×
1044
  } else if (fileOrFolder.isFile()) {
×
1045
    QAction *edit = contextMenu.addAction(tr("Edit"));
×
1046
    connect(edit, &QAction::triggered, this, &MainWindow::onEdit);
×
1047
  }
1048
  if (selected) {
×
1049
    contextMenu.addSeparator();
×
1050
    if (fileOrFolder.isDir()) {
×
1051
      QAction *renameFolder = contextMenu.addAction(tr("Rename folder"));
×
1052
      connect(renameFolder, &QAction::triggered, this,
×
1053
              &MainWindow::renameFolder);
×
1054
    } else if (fileOrFolder.isFile()) {
×
1055
      QAction *renamePassword = contextMenu.addAction(tr("Rename password"));
×
1056
      connect(renamePassword, &QAction::triggered, this,
×
1057
              &MainWindow::renamePassword);
×
1058
    }
1059
    QAction *deleteItem = contextMenu.addAction(tr("Delete"));
×
1060
    connect(deleteItem, &QAction::triggered, this, &MainWindow::onDelete);
×
1061
    if (fileOrFolder.isDir()) {
×
1062
      QString dirPath = QDir::cleanPath(
1063
          Util::getDir(ui->treeView->currentIndex(), false, model, proxyModel));
×
1064
      QAction *reencrypt = contextMenu.addAction(tr("Re-encrypt"));
×
1065
      connect(reencrypt, &QAction::triggered, this,
×
1066
              [this, dirPath]() { reencryptPath(dirPath); });
×
1067
    }
1068
  }
1069
  contextMenu.exec(globalPos);
×
1070
}
×
1071

1072
/**
1073
 * @brief MainWindow::showBrowserContextMenu show us the context menu in
1074
 * password window
1075
 * @param pos
1076
 */
1077
void MainWindow::showBrowserContextMenu(const QPoint &pos) {
×
1078
  QMenu *contextMenu = ui->textBrowser->createStandardContextMenu(pos);
×
1079
  QPoint globalPos = ui->textBrowser->viewport()->mapToGlobal(pos);
×
1080

1081
  contextMenu->exec(globalPos);
×
1082
  delete contextMenu;
×
1083
}
×
1084

1085
/**
1086
 * @brief MainWindow::openFolder open the folder in the default file manager
1087
 */
1088
void MainWindow::openFolder() {
×
1089
  QString dir =
1090
      Util::getDir(ui->treeView->currentIndex(), false, model, proxyModel);
×
1091

1092
  QString path = QDir::toNativeSeparators(dir);
×
1093
  QDesktopServices::openUrl(QUrl::fromLocalFile(path));
×
1094
}
×
1095

1096
/**
1097
 * @brief MainWindow::addFolder add a new folder to store passwords in
1098
 */
1099
void MainWindow::addFolder() {
×
1100
  bool ok;
1101
  QString dir =
1102
      Util::getDir(ui->treeView->currentIndex(), false, model, proxyModel);
×
1103
  QString newdir =
1104
      QInputDialog::getText(this, tr("New file"),
×
1105
                            tr("New Folder: \n(Will be placed in %1 )")
×
1106
                                .arg(QtPassSettings::getPassStore() +
×
1107
                                     Util::getDir(ui->treeView->currentIndex(),
×
1108
                                                  true, model, proxyModel)),
1109
                            QLineEdit::Normal, "", &ok);
×
1110
  if (!ok || newdir.isEmpty()) {
×
1111
    return;
1112
  }
1113
  newdir.prepend(dir);
1114
  if (!QDir().mkdir(newdir)) {
×
1115
    QMessageBox::warning(this, tr("Error"),
×
1116
                         tr("Failed to create folder: %1").arg(newdir));
×
1117
    return;
×
1118
  }
1119
  if (QtPassSettings::isAddGPGId(true)) {
×
1120
    QString gpgIdFile = newdir + "/.gpg-id";
×
1121
    QFile gpgId(gpgIdFile);
×
1122
    if (!gpgId.open(QIODevice::WriteOnly)) {
×
1123
      QMessageBox::warning(
×
1124
          this, tr("Error"),
×
1125
          tr("Failed to create .gpg-id file in: %1").arg(newdir));
×
1126
      return;
1127
    }
1128
    QList<UserInfo> users = QtPassSettings::getPass()->listKeys("", true);
×
1129
    for (const UserInfo &user : users) {
×
1130
      if (user.enabled) {
×
1131
        gpgId.write((user.key_id + "\n").toUtf8());
×
1132
      }
1133
    }
1134
    gpgId.close();
×
1135
  }
×
1136
}
1137

1138
/**
1139
 * @brief MainWindow::renameFolder rename an existing folder
1140
 */
1141
void MainWindow::renameFolder() {
×
1142
  bool ok;
1143
  QString srcDir = QDir::cleanPath(
1144
      Util::getDir(ui->treeView->currentIndex(), false, model, proxyModel));
×
1145
  QString srcDirName = QDir(srcDir).dirName();
×
1146
  QString newName =
1147
      QInputDialog::getText(this, tr("Rename file"), tr("Rename Folder To: "),
×
1148
                            QLineEdit::Normal, srcDirName, &ok);
×
1149
  if (!ok || newName.isEmpty()) {
×
1150
    return;
1151
  }
1152
  QString destDir = srcDir;
1153
  destDir.replace(srcDir.lastIndexOf(srcDirName), srcDirName.length(), newName);
×
1154
  QtPassSettings::getPass()->Move(srcDir, destDir);
×
1155
}
1156

1157
/**
1158
 * @brief MainWindow::editPassword read password and open edit window via
1159
 * MainWindow::onEdit()
1160
 */
1161
void MainWindow::editPassword(const QString &file) {
×
1162
  if (!file.isEmpty()) {
×
1163
    if (QtPassSettings::isUseGit() && QtPassSettings::isAutoPull()) {
×
1164
      onUpdate(true);
×
1165
    }
1166
    setPassword(file, false);
×
1167
  }
1168
}
×
1169

1170
/**
1171
 * @brief MainWindow::renamePassword rename an existing password
1172
 */
1173
void MainWindow::renamePassword() {
×
1174
  bool ok;
1175
  QString file = getFile(ui->treeView->currentIndex(), false);
×
1176
  QString filePath = QFileInfo(file).path();
×
1177
  QString fileName = QFileInfo(file).fileName();
×
1178
  if (fileName.endsWith(".gpg", Qt::CaseInsensitive)) {
×
1179
    fileName.chop(4);
×
1180
  }
1181

1182
  QString newName =
1183
      QInputDialog::getText(this, tr("Rename file"), tr("Rename File To: "),
×
1184
                            QLineEdit::Normal, fileName, &ok);
×
1185
  if (!ok || newName.isEmpty()) {
×
1186
    return;
1187
  }
1188
  QString newFile = QDir(filePath).filePath(newName);
×
1189
  QtPassSettings::getPass()->Move(file, newFile);
×
1190
}
1191

1192
/**
1193
 * @brief MainWindow::clearTemplateWidgets empty the template widget fields in
1194
 * the UI
1195
 */
1196
void MainWindow::clearTemplateWidgets() {
×
1197
  while (ui->gridLayout->count() > 0) {
×
1198
    QLayoutItem *item = ui->gridLayout->takeAt(0);
×
1199
    delete item->widget();
×
1200
    delete item;
×
1201
  }
1202
  ui->verticalLayoutPassword->setSpacing(0);
×
1203
}
×
1204

1205
/**
1206
 * @brief Copies the password of the selected file from the tree view to the
1207
 * clipboard.
1208
 * @example
1209
 * MainWindow::copyPasswordFromTreeview();
1210
 *
1211
 * @return void - This function does not return a value.
1212
 */
1213
void MainWindow::copyPasswordFromTreeview() {
×
1214
  QFileInfo fileOrFolder =
1215
      model.fileInfo(proxyModel.mapToSource(ui->treeView->currentIndex()));
×
1216

1217
  if (fileOrFolder.isFile()) {
×
1218
    QString file = getFile(ui->treeView->currentIndex(), true);
×
1219
    // Disconnect any previous connection to avoid accumulation
1220
    disconnect(QtPassSettings::getPass(), &Pass::finishedShow, this,
×
1221
               &MainWindow::passwordFromFileToClipboard);
1222
    connect(QtPassSettings::getPass(), &Pass::finishedShow, this,
×
1223
            &MainWindow::passwordFromFileToClipboard);
×
1224
    QtPassSettings::getPass()->Show(file);
×
1225
  }
1226
}
×
1227

1228
void MainWindow::passwordFromFileToClipboard(const QString &text) {
×
1229
  QStringList tokens = text.split('\n');
×
1230
  m_qtPass->copyTextToClipboard(tokens[0]);
×
1231
}
×
1232

1233
/**
1234
 * @brief MainWindow::addToGridLayout add a field to the template grid
1235
 * @param position
1236
 * @param field
1237
 * @param value
1238
 */
1239
void MainWindow::addToGridLayout(int position, const QString &field,
×
1240
                                 const QString &value) {
1241
  QString trimmedField = field.trimmed();
1242
  QString trimmedValue = value.trimmed();
1243

1244
  const QString buttonStyle =
1245
      "border-style: none; background: transparent; padding: 0; margin: 0; "
1246
      "icon-size: 16px; color: inherit;";
×
1247

1248
  // Combine the Copy button and the line edit in one widget
1249
  auto *frame = new QFrame();
×
1250
  QLayout *ly = new QHBoxLayout();
×
1251
  ly->setContentsMargins(5, 2, 2, 2);
×
1252
  ly->setSpacing(0);
×
1253
  frame->setLayout(ly);
×
1254
  if (QtPassSettings::getClipBoardType() != Enums::CLIPBOARD_NEVER) {
×
1255
    auto *fieldLabel = new QPushButtonWithClipboard(trimmedValue, this);
×
1256
    connect(fieldLabel, &QPushButtonWithClipboard::clicked, m_qtPass,
×
1257
            &QtPass::copyTextToClipboard);
×
1258

1259
    fieldLabel->setStyleSheet(buttonStyle);
×
1260
    frame->layout()->addWidget(fieldLabel);
×
1261
  }
1262

1263
  if (QtPassSettings::isUseQrencode()) {
×
1264
    auto *qrbutton = new QPushButtonAsQRCode(trimmedValue, this);
×
1265
    connect(qrbutton, &QPushButtonAsQRCode::clicked, m_qtPass,
×
1266
            &QtPass::showTextAsQRCode);
×
1267
    qrbutton->setStyleSheet(buttonStyle);
×
1268
    frame->layout()->addWidget(qrbutton);
×
1269
  }
1270

1271
  // set the echo mode to password, if the field is "password"
1272
  const QString lineStyle =
1273
      QtPassSettings::isUseMonospace()
×
1274
          ? "border-style: none; background: transparent; font-family: "
1275
            "monospace;"
1276
          : "border-style: none; background: transparent;";
×
1277

1278
  if (QtPassSettings::isHidePassword() && trimmedField == tr("Password")) {
×
1279
    auto *line = new QLineEdit();
×
1280
    line->setObjectName(trimmedField);
×
1281
    line->setText(trimmedValue);
×
1282
    line->setReadOnly(true);
×
1283
    line->setStyleSheet(lineStyle);
×
1284
    line->setContentsMargins(0, 0, 0, 0);
×
1285
    line->setEchoMode(QLineEdit::Password);
×
1286
    auto *showButton = new QPushButtonShowPassword(line, this);
×
1287
    showButton->setStyleSheet(buttonStyle);
×
1288
    showButton->setContentsMargins(0, 0, 0, 0);
×
1289
    frame->layout()->addWidget(showButton);
×
1290
    frame->layout()->addWidget(line);
×
1291
  } else {
1292
    auto *line = new QTextBrowser();
×
1293
    line->setOpenExternalLinks(true);
×
1294
    line->setOpenLinks(true);
×
1295
    line->setMaximumHeight(26);
×
1296
    line->setMinimumHeight(26);
×
1297
    line->setSizePolicy(
×
1298
        QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum));
1299
    line->setObjectName(trimmedField);
×
1300
    trimmedValue.replace(Util::protocolRegex(), R"(<a href="\1">\1</a>)");
×
1301
    line->setText(trimmedValue);
×
1302
    line->setReadOnly(true);
×
1303
    line->setStyleSheet(lineStyle);
×
1304
    line->setContentsMargins(0, 0, 0, 0);
×
1305
    frame->layout()->addWidget(line);
×
1306
  }
1307

1308
  frame->setStyleSheet(
×
1309
      ".QFrame{border: 1px solid lightgrey; border-radius: 5px;}");
1310

1311
  // set into the layout
1312
  ui->gridLayout->addWidget(new QLabel(trimmedField), position, 0);
×
1313
  ui->gridLayout->addWidget(frame, position, 1);
×
1314
}
×
1315

1316
/**
1317
 * @brief Displays message in status bar
1318
 *
1319
 * @param msg     text to be displayed
1320
 * @param timeout time for which msg shall be visible
1321
 */
1322
void MainWindow::showStatusMessage(const QString &msg, int timeout) {
×
1323
  ui->statusBar->showMessage(msg, timeout);
×
1324
}
×
1325

1326
/**
1327
 * @brief MainWindow::reencryptPath re-encrypt all passwords in a directory
1328
 * @param dir Directory path to re-encrypt
1329
 */
1330
void MainWindow::reencryptPath(const QString &dir) {
×
1331
  QDir checkDir(dir);
×
1332
  if (!checkDir.exists()) {
×
1333
    QMessageBox::critical(this, tr("Error"),
×
1334
                          tr("Directory does not exist: %1").arg(dir));
×
1335
    return;
×
1336
  }
1337

1338
  int ret = QMessageBox::question(
×
1339
      this, tr("Re-encrypt passwords"),
×
1340
      tr("Re-encrypt all passwords in %1?\n\n"
×
1341
         "This will re-encrypt ALL password files in this folder "
1342
         "using the current recipients defined in .gpg-id.\n\n"
1343
         "This may rewrite many files and cannot be undone easily.\n\n"
1344
         "Continue?")
1345
          .arg(QDir(dir).dirName()),
×
1346
      QMessageBox::Yes | QMessageBox::No);
1347

1348
  if (ret != QMessageBox::Yes)
×
1349
    return;
1350

1351
  // Prevent double execution - use same method as startReencryptPath
1352
  setUiElementsEnabled(false);
×
1353
  ui->treeView->setDisabled(true);
×
1354

1355
  QtPassSettings::getImitatePass()->reencryptPath(
×
1356
      QDir::cleanPath(QDir(dir).absolutePath()));
×
1357
}
×
1358

1359
/**
1360
 * @brief MainWindow::startReencryptPath disable ui elements and treeview
1361
 */
1362
void MainWindow::startReencryptPath() {
×
1363
  setUiElementsEnabled(false);
×
1364
  ui->treeView->setDisabled(true);
×
1365
}
×
1366

1367
/**
1368
 * @brief MainWindow::endReencryptPath re-enable ui elements
1369
 */
1370
void MainWindow::endReencryptPath() { setUiElementsEnabled(true); }
×
1371

1372
void MainWindow::updateGitButtonVisibility() {
×
1373
  if (!QtPassSettings::isUseGit() ||
×
1374
      (QtPassSettings::getGitExecutable().isEmpty() &&
×
1375
       QtPassSettings::getPassExecutable().isEmpty())) {
×
1376
    enableGitButtons(false);
×
1377
  } else {
1378
    enableGitButtons(true);
×
1379
  }
1380
}
×
1381

1382
void MainWindow::updateOtpButtonVisibility() {
×
1383
#if defined(Q_OS_WIN) || defined(__APPLE__)
1384
  ui->actionOtp->setVisible(false);
1385
#endif
1386
  if (!QtPassSettings::isUseOtp()) {
×
1387
    ui->actionOtp->setEnabled(false);
×
1388
  } else {
1389
    ui->actionOtp->setEnabled(true);
×
1390
  }
1391
}
×
1392

1393
void MainWindow::enableGitButtons(const bool &state) {
×
1394
  // Following GNOME guidelines is preferable disable buttons instead of hide
1395
  ui->actionPush->setEnabled(state);
×
1396
  ui->actionUpdate->setEnabled(state);
×
1397
}
×
1398

1399
/**
1400
 * @brief MainWindow::critical critical message popup wrapper.
1401
 * @param title
1402
 * @param msg
1403
 */
1404
void MainWindow::critical(const QString &title, const QString &msg) {
×
1405
  QMessageBox::critical(this, title, msg);
×
1406
}
×
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