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

IJHack / QtPass / 24609334109

18 Apr 2026 04:50PM UTC coverage: 22.734% (+0.8%) from 21.908%
24609334109

Pull #1037

github

web-flow
Merge f564bc578 into 68ca2a489
Pull Request #1037: feat: implement pass grep content search (#109)

76 of 192 new or added lines in 7 files covered. (39.58%)

401 existing lines in 9 files now uncovered.

1299 of 5714 relevant lines covered (22.73%)

8.6 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 <QTreeWidget>
33
#include <utility>
34

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

51
  m_qtPass = new QtPass(this);
×
52

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

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

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

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

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

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

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

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

106
  updateProfileBox();
×
107

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

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

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

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

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

124
  setUiElementsEnabled(true);
×
125

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

330
    m_qtPass->setFreshStart(false);
×
331
  }
332
}
×
333

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

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

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

376
/**
377
 * @brief MainWindow::on_treeView_clicked read the selected password file
378
 * @param index
379
 */
380
void MainWindow::on_treeView_clicked(const QModelIndex &index) {
×
381
  bool cleared = ui->treeView->currentIndex().flags() == Qt::NoItemFlags;
×
382
  currentDir =
383
      Util::getDir(ui->treeView->currentIndex(), false, model, proxyModel);
×
384
  // Clear any previously cached clipped text before showing new password
385
  m_qtPass->clearClippedText();
×
386
  QString file = getFile(index, true);
×
NEW
387
  ui->passwordName->setText(file);
×
388
  if (!file.isEmpty() && !cleared) {
×
389
    QtPassSettings::getPass()->Show(file);
×
390
  } else {
391
    clearPanel(false);
×
392
    ui->actionEdit->setEnabled(false);
×
393
    ui->actionDelete->setEnabled(true);
×
394
  }
395
  // Reset filter after index has been consumed to avoid stale proxy index
NEW
396
  if (!m_grepMode && !ui->lineEdit->text().isEmpty()) {
×
NEW
397
    searchTimer.stop();
×
NEW
398
    ui->lineEdit->blockSignals(true);
×
NEW
399
    ui->lineEdit->clear();
×
NEW
400
    ui->lineEdit->blockSignals(false);
×
NEW
401
    proxyModel.setFilterRegularExpression(QRegularExpression());
×
402
  }
UNCOV
403
}
×
404

405
/**
406
 * @brief MainWindow::on_treeView_doubleClicked when doubleclicked on
407
 * TreeViewItem, open the edit Window
408
 * @param index
409
 */
410
void MainWindow::on_treeView_doubleClicked(const QModelIndex &index) {
×
411
  QFileInfo fileOrFolder =
412
      model.fileInfo(proxyModel.mapToSource(ui->treeView->currentIndex()));
×
413

414
  if (fileOrFolder.isFile()) {
×
415
    editPassword(getFile(index, true));
×
416
  }
417
}
×
418

419
/**
420
 * @brief MainWindow::deselect clear the selection, password and copy buffer
421
 */
422
void MainWindow::deselect() {
×
423
  currentDir = "";
×
424
  m_qtPass->clearClipboard();
×
425
  ui->treeView->clearSelection();
×
426
  ui->actionEdit->setEnabled(false);
×
427
  ui->actionDelete->setEnabled(false);
×
428
  ui->passwordName->setText("");
×
429
  clearPanel(false);
×
430
}
×
431

432
void MainWindow::executeWrapperStarted() {
×
433
  clearTemplateWidgets();
×
434
  ui->textBrowser->clear();
×
435
  setUiElementsEnabled(false);
×
436
  clearPanelTimer.stop();
×
437
}
×
438

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

459
  // set clipped text
460
  m_qtPass->setClippedText(password, p_output);
×
461

462
  // first clear the current view:
463
  clearTemplateWidgets();
×
464

465
  // show what is needed:
466
  if (QtPassSettings::isHideContent()) {
×
467
    output = "***" + tr("Content hidden") + "***";
×
468
  } else if (!QtPassSettings::isDisplayAsIs()) {
×
469
    if (!password.isEmpty()) {
×
470
      // set the password, it is hidden if needed in addToGridLayout
471
      addToGridLayout(0, tr("Password"), password);
×
472
    }
473

474
    NamedValues namedValues = fileContent.getNamedValues();
×
475
    for (int j = 0; j < namedValues.length(); ++j) {
×
476
      const NamedValue &nv = namedValues.at(j);
477
      addToGridLayout(j + 1, nv.name, nv.value);
×
478
    }
479
    if (ui->gridLayout->count() == 0) {
×
480
      ui->verticalLayoutPassword->setSpacing(0);
×
481
    } else {
482
      ui->verticalLayoutPassword->setSpacing(6);
×
483
    }
484

485
    output = fileContent.getRemainingDataForDisplay();
×
486
  }
487

488
  if (QtPassSettings::isUseAutoclearPanel()) {
×
489
    clearPanelTimer.start();
×
490
  }
491

492
  emit passShowHandlerFinished(output);
×
493
  setUiElementsEnabled(true);
×
494
}
×
495

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

520
/**
521
 * @brief MainWindow::clearPanel hide the information from shoulder surfers
522
 */
523
void MainWindow::clearPanel(bool notify) {
×
524
  while (ui->gridLayout->count() > 0) {
×
525
    QLayoutItem *item = ui->gridLayout->takeAt(0);
×
526
    delete item->widget();
×
527
    delete item;
×
528
  }
NEW
529
  if (ui->grepResultsList->isVisible()) {
×
NEW
530
    ui->grepResultsList->clear();
×
NEW
531
    ui->grepResultsList->setVisible(false);
×
NEW
532
    ui->treeView->setVisible(true);
×
533
  }
534
  if (notify) {
×
535
    QString output = "***" + tr("Password and Content hidden") + "***";
×
536
    ui->textBrowser->setHtml(output);
×
537
  } else {
538
    ui->textBrowser->setHtml("");
×
539
  }
540
}
×
541

542
/**
543
 * @brief MainWindow::setUiElementsEnabled enable or disable the relevant UI
544
 * elements
545
 * @param state
546
 */
547
void MainWindow::setUiElementsEnabled(bool state) {
×
548
  ui->treeView->setEnabled(state);
×
549
  ui->lineEdit->setEnabled(state);
×
550
  ui->lineEdit->installEventFilter(this);
×
551
  ui->actionAddPassword->setEnabled(state);
×
552
  ui->actionAddFolder->setEnabled(state);
×
553
  ui->actionUsers->setEnabled(state);
×
554
  ui->actionConfig->setEnabled(state);
×
555
  // is a file selected?
556
  state &= ui->treeView->currentIndex().isValid();
×
557
  ui->actionDelete->setEnabled(state);
×
558
  ui->actionEdit->setEnabled(state);
×
559
  updateGitButtonVisibility();
×
560
  updateOtpButtonVisibility();
×
561
}
×
562

563
/**
564
 * @brief Restores the main window geometry, state, position, size, and
565
 * tray/icon settings from saved application settings.
566
 * @example
567
 * MainWindow window;
568
 * window.restoreWindow();
569
 *
570
 * @return void - This function does not return a value.
571
 */
572
void MainWindow::restoreWindow() {
×
573
  QByteArray geometry = QtPassSettings::getGeometry(saveGeometry());
×
574
  restoreGeometry(geometry);
×
575
  QByteArray savestate = QtPassSettings::getSavestate(saveState());
×
576
  restoreState(savestate);
×
577
  QPoint position = QtPassSettings::getPos(pos());
×
578
  move(position);
×
579
  QSize newSize = QtPassSettings::getSize(size());
×
580
  resize(newSize);
×
581
  if (QtPassSettings::isMaximized(isMaximized())) {
×
582
    showMaximized();
×
583
  }
584

585
  if (QtPassSettings::isAlwaysOnTop()) {
×
586
    Qt::WindowFlags flags = windowFlags();
587
    setWindowFlags(flags | Qt::WindowStaysOnTopHint);
×
588
    show();
×
589
  }
590

591
  if (QtPassSettings::isUseTrayIcon() && tray == nullptr) {
×
592
    initTrayIcon();
×
593
    if (QtPassSettings::isStartMinimized()) {
×
594
      // since we are still in constructor, can't directly hide
595
      QTimer::singleShot(10, this, SLOT(hide()));
×
596
    }
597
  } else if (!QtPassSettings::isUseTrayIcon() && tray != nullptr) {
×
598
    destroyTrayIcon();
×
599
  }
600
}
×
601

602
/**
603
 * @brief MainWindow::on_configButton_clicked run Mainwindow::config
604
 */
605
void MainWindow::onConfig() { config(); }
×
606

607
/**
608
 * @brief Executes when the string in the search box changes, collapses the
609
 * TreeView
610
 * @param arg1
611
 */
612
void MainWindow::on_lineEdit_textChanged(const QString &arg1) {
×
NEW
613
  if (m_grepMode)
×
614
    return;
615
  ui->statusBar->showMessage(tr("Looking for: %1").arg(arg1), 1000);
×
616
  ui->treeView->expandAll();
×
617
  clearPanel(false);
×
618
  ui->passwordName->setText("");
×
619
  ui->actionEdit->setEnabled(false);
×
620
  ui->actionDelete->setEnabled(false);
×
621
  searchTimer.start();
×
622
}
623

624
/**
625
 * @brief MainWindow::onTimeoutSearch Fired when search is finished or too much
626
 * time from two keypresses is elapsed
627
 */
628
void MainWindow::onTimeoutSearch() {
×
629
  QString query = ui->lineEdit->text();
×
630

631
  if (query.isEmpty()) {
×
632
    ui->treeView->collapseAll();
×
633
    deselect();
×
634
  }
635

636
  query.replace(QStringLiteral(" "), ".*");
×
637
  QRegularExpression regExp(query, QRegularExpression::CaseInsensitiveOption);
×
638
  proxyModel.setFilterRegularExpression(regExp);
×
639
  ui->treeView->setRootIndex(proxyModel.mapFromSource(
×
640
      model.setRootPath(QtPassSettings::getPassStore())));
×
641

642
  if (proxyModel.rowCount() > 0 && !query.isEmpty()) {
×
643
    selectFirstFile();
×
644
  } else {
645
    ui->actionEdit->setEnabled(false);
×
646
    ui->actionDelete->setEnabled(false);
×
647
  }
648
}
×
649

650
/**
651
 * @brief MainWindow::on_lineEdit_returnPressed get searching
652
 *
653
 * Select the first possible file in the tree
654
 */
655
void MainWindow::on_lineEdit_returnPressed() {
×
656
#ifdef QT_DEBUG
657
  dbg() << "on_lineEdit_returnPressed" << proxyModel.rowCount();
658
#endif
659

NEW
660
  if (m_grepMode) {
×
NEW
661
    const QString query = ui->lineEdit->text().trimmed();
×
NEW
662
    if (!query.isEmpty())
×
NEW
663
      QtPassSettings::getPass()->Grep(query, ui->grepCaseButton->isChecked());
×
664
    return;
665
  }
666

667
  if (proxyModel.rowCount() > 0) {
×
668
    selectFirstFile();
×
669
    on_treeView_clicked(ui->treeView->currentIndex());
×
670
  }
671
}
672

673
/**
674
 * @brief Toggle grep (content search) mode.
675
 */
NEW
676
void MainWindow::on_grepButton_toggled(bool checked) {
×
NEW
677
  m_grepMode = checked;
×
NEW
678
  if (checked) {
×
NEW
679
    ui->lineEdit->setPlaceholderText(tr("Search content (regex)"));
×
NEW
680
    ui->lineEdit->clear();
×
NEW
681
    searchTimer.stop();
×
NEW
682
    proxyModel.setFilterRegularExpression(QRegularExpression());
×
NEW
683
    ui->grepResultsList->setVisible(false);
×
684
    // Keep treeView visible until results arrive
685
  } else {
NEW
686
    searchTimer.stop();
×
NEW
687
    ui->lineEdit->blockSignals(true);
×
NEW
688
    ui->lineEdit->clear();
×
NEW
689
    ui->lineEdit->blockSignals(false);
×
NEW
690
    ui->lineEdit->setPlaceholderText(tr("Search Password"));
×
NEW
691
    ui->grepResultsList->clear();
×
NEW
692
    ui->grepResultsList->setVisible(false);
×
NEW
693
    ui->treeView->setVisible(true);
×
NEW
694
    proxyModel.setFilterRegularExpression(QRegularExpression());
×
NEW
695
    ui->treeView->setRootIndex(proxyModel.mapFromSource(
×
NEW
696
        model.setRootPath(QtPassSettings::getPassStore())));
×
697
  }
NEW
698
}
×
699

700
/**
701
 * @brief Display grep results in grepResultsList.
702
 */
NEW
703
void MainWindow::onGrepFinished(
×
704
    const QList<QPair<QString, QStringList>> &results) {
NEW
705
  setUiElementsEnabled(true);
×
NEW
706
  if (!m_grepMode)
×
707
    return;
NEW
708
  ui->grepResultsList->clear();
×
NEW
709
  if (results.isEmpty()) {
×
NEW
710
    ui->statusBar->showMessage(tr("No matches found."), 3000);
×
NEW
711
    ui->grepResultsList->setVisible(false);
×
NEW
712
    ui->treeView->setVisible(true);
×
NEW
713
    return;
×
714
  }
NEW
715
  const bool hideContent = QtPassSettings::isHideContent();
×
716
  int totalLines = 0;
NEW
717
  for (const auto &pair : results) {
×
NEW
718
    QTreeWidgetItem *entryItem = new QTreeWidgetItem(ui->grepResultsList);
×
NEW
719
    entryItem->setText(0, pair.first);
×
NEW
720
    entryItem->setData(0, Qt::UserRole, pair.first);
×
NEW
721
    for (const QString &line : pair.second) {
×
NEW
722
      QTreeWidgetItem *lineItem = new QTreeWidgetItem(entryItem);
×
NEW
723
      lineItem->setText(0, hideContent ? QStringLiteral("***") : line);
×
NEW
724
      lineItem->setData(0, Qt::UserRole, pair.first);
×
NEW
725
      ++totalLines;
×
726
    }
727
  }
NEW
728
  ui->grepResultsList->expandAll();
×
NEW
729
  ui->treeView->setVisible(false);
×
NEW
730
  ui->grepResultsList->setVisible(true);
×
NEW
731
  ui->statusBar->showMessage(tr("Found %1 match(es) in %2 entr(ies).")
×
NEW
732
                                 .arg(totalLines)
×
NEW
733
                                 .arg(results.size()),
×
734
                             3000);
NEW
735
  if (QtPassSettings::isUseAutoclearPanel())
×
NEW
736
    clearPanelTimer.start();
×
737
}
738

739
/**
740
 * @brief Navigate to the password entry when a grep result is clicked.
741
 */
NEW
742
void MainWindow::on_grepResultsList_itemClicked(QTreeWidgetItem *item,
×
743
                                                int /*column*/) {
NEW
744
  const QString entry = item->data(0, Qt::UserRole).toString();
×
NEW
745
  if (entry.isEmpty())
×
746
    return;
747
  const QString fullPath = QDir::cleanPath(
NEW
748
      QDir(QtPassSettings::getPassStore()).filePath(entry + ".gpg"));
×
NEW
749
  QModelIndex srcIndex = model.index(fullPath);
×
750
  if (!srcIndex.isValid())
751
    return;
NEW
752
  QModelIndex proxyIndex = proxyModel.mapFromSource(srcIndex);
×
753
  if (!proxyIndex.isValid())
754
    return;
NEW
755
  ui->treeView->setCurrentIndex(proxyIndex);
×
NEW
756
  on_treeView_clicked(proxyIndex);
×
NEW
757
  ui->grepResultsList->setVisible(false);
×
NEW
758
  ui->treeView->setVisible(true);
×
NEW
759
  ui->treeView->scrollTo(proxyIndex);
×
NEW
760
  ui->treeView->setFocus();
×
761
}
762

763
/**
764
 * @brief MainWindow::selectFirstFile select the first possible file in the
765
 * tree
766
 */
767
void MainWindow::selectFirstFile() {
×
768
  QModelIndex index = proxyModel.mapFromSource(
×
769
      model.setRootPath(QtPassSettings::getPassStore()));
×
770
  index = firstFile(index);
×
771
  ui->treeView->setCurrentIndex(index);
×
772
}
×
773

774
/**
775
 * @brief MainWindow::firstFile return location of first possible file
776
 * @param parentIndex
777
 * @return QModelIndex
778
 */
779
auto MainWindow::firstFile(QModelIndex parentIndex) -> QModelIndex {
×
780
  QModelIndex index = parentIndex;
×
781
  int numRows = proxyModel.rowCount(parentIndex);
×
782
  for (int row = 0; row < numRows; ++row) {
×
783
    index = proxyModel.index(row, 0, parentIndex);
×
784
    if (model.fileInfo(proxyModel.mapToSource(index)).isFile()) {
×
785
      return index;
×
786
    }
787
    if (proxyModel.hasChildren(index)) {
×
788
      return firstFile(index);
×
789
    }
790
  }
791
  return index;
×
792
}
793

794
/**
795
 * @brief MainWindow::setPassword open passworddialog
796
 * @param file which pgp file
797
 * @param isNew insert (not update)
798
 */
799
void MainWindow::setPassword(const QString &file, bool isNew) {
×
800
  PasswordDialog d(file, isNew, this);
×
801

802
  if (!d.exec()) {
×
803
    ui->treeView->setFocus();
×
804
  }
805
}
×
806

807
/**
808
 * @brief MainWindow::addPassword add a new password by showing a
809
 * number of dialogs.
810
 */
811
void MainWindow::addPassword() {
×
812
  bool ok;
813
  QString dir =
814
      Util::getDir(ui->treeView->currentIndex(), true, model, proxyModel);
×
815
  QString file =
816
      QInputDialog::getText(this, tr("New file"),
×
817
                            tr("New password file: \n(Will be placed in %1 )")
×
818
                                .arg(QtPassSettings::getPassStore() +
×
819
                                     Util::getDir(ui->treeView->currentIndex(),
×
820
                                                  true, model, proxyModel)),
821
                            QLineEdit::Normal, "", &ok);
×
822
  if (!ok || file.isEmpty()) {
×
823
    return;
824
  }
825
  file = dir + file;
×
826
  setPassword(file);
×
827
}
828

829
/**
830
 * @brief MainWindow::onDelete remove password, if you are
831
 * sure.
832
 */
833
void MainWindow::onDelete() {
×
834
  QModelIndex currentIndex = ui->treeView->currentIndex();
×
835
  if (!currentIndex.isValid()) {
836
    // This fixes https://github.com/IJHack/QtPass/issues/556
837
    // Otherwise the entire password directory would be deleted if
838
    // nothing is selected in the tree view.
839
    return;
×
840
  }
841

842
  QFileInfo fileOrFolder =
843
      model.fileInfo(proxyModel.mapToSource(ui->treeView->currentIndex()));
×
844
  QString file = "";
×
845
  bool isDir = false;
846

847
  if (fileOrFolder.isFile()) {
×
848
    file = getFile(ui->treeView->currentIndex(), true);
×
849
  } else {
850
    file = Util::getDir(ui->treeView->currentIndex(), true, model, proxyModel);
×
851
    isDir = true;
852
  }
853

854
  QString dirMessage = tr(" and the whole content?");
855
  if (isDir) {
×
856
    QDirIterator it(model.rootPath() + QDir::separator() + file,
×
857
                    QDirIterator::Subdirectories);
×
858
    bool okDir = true;
859
    while (it.hasNext() && okDir) {
×
860
      it.next();
×
861
      if (QFileInfo(it.filePath()).isFile()) {
×
862
        if (QFileInfo(it.filePath()).suffix() != "gpg") {
×
863
          okDir = false;
864
          dirMessage = tr(" and the whole content? <br><strong>Attention: "
×
865
                          "there are unexpected files in the given folder, "
866
                          "check them before continue.</strong>");
867
        }
868
      }
869
    }
870
  }
×
871

872
  if (QMessageBox::question(
×
873
          this, isDir ? tr("Delete folder?") : tr("Delete password?"),
×
874
          tr("Are you sure you want to delete %1%2?")
×
875
              .arg(QDir::separator() + file, isDir ? dirMessage : "?"),
×
876
          QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) {
877
    return;
878
  }
879

880
  QtPassSettings::getPass()->Remove(file, isDir);
×
881
}
×
882

883
/**
884
 * @brief MainWindow::onOTP try and generate (selected) OTP code.
885
 */
886
void MainWindow::onOtp() {
×
887
  QString file = getFile(ui->treeView->currentIndex(), true);
×
888
  if (!file.isEmpty()) {
×
889
    if (QtPassSettings::isUseOtp()) {
×
890
      setUiElementsEnabled(false);
×
891
      QtPassSettings::getPass()->OtpGenerate(file);
×
892
    }
893
  } else {
894
    flashText(tr("No password selected for OTP generation"), true);
×
895
  }
896
}
×
897

898
/**
899
 * @brief MainWindow::onEdit try and edit (selected) password.
900
 */
901
void MainWindow::onEdit() {
×
902
  QString file = getFile(ui->treeView->currentIndex(), true);
×
903
  editPassword(file);
×
904
}
×
905

906
/**
907
 * @brief MainWindow::userDialog see MainWindow::onUsers()
908
 * @param dir folder to edit users for.
909
 */
910
void MainWindow::userDialog(const QString &dir) {
×
911
  if (!dir.isEmpty()) {
×
912
    currentDir = dir;
×
913
  }
914
  onUsers();
×
915
}
×
916

917
/**
918
 * @brief MainWindow::onUsers edit users for the current
919
 * folder,
920
 * gets lists and opens UserDialog.
921
 */
922
void MainWindow::onUsers() {
×
923
  QString dir =
924
      currentDir.isEmpty()
925
          ? Util::getDir(ui->treeView->currentIndex(), false, model, proxyModel)
×
926
          : currentDir;
×
927

928
  UsersDialog d(dir, this);
×
929
  if (!d.exec()) {
×
930
    ui->treeView->setFocus();
×
931
  }
932
}
×
933

934
/**
935
 * @brief MainWindow::messageAvailable we have some text/message/search to do.
936
 * @param message
937
 */
938
void MainWindow::messageAvailable(const QString &message) {
×
939
  if (message.isEmpty()) {
×
940
    focusInput();
×
941
  } else {
942
    ui->treeView->expandAll();
×
943
    ui->lineEdit->setText(message);
×
944
    on_lineEdit_returnPressed();
×
945
  }
946
  show();
×
947
  raise();
×
948
}
×
949

950
/**
951
 * @brief MainWindow::generateKeyPair internal gpg keypair generator . .
952
 * @param batch
953
 * @param keygenWindow
954
 */
955
void MainWindow::generateKeyPair(const QString &batch, QDialog *keygenWindow) {
×
956
  keygen = keygenWindow;
×
957
  emit generateGPGKeyPair(batch);
×
958
}
×
959

960
/**
961
 * @brief MainWindow::updateProfileBox update the list of profiles, optionally
962
 * select a more appropriate one to view too
963
 */
964
void MainWindow::updateProfileBox() {
×
965
  QHash<QString, QHash<QString, QString>> profiles =
966
      QtPassSettings::getProfiles();
×
967

968
  if (profiles.isEmpty()) {
969
    ui->profileWidget->hide();
×
970
  } else {
971
    ui->profileWidget->show();
×
972
    ui->profileBox->setEnabled(profiles.size() > 1);
×
973
    ui->profileBox->clear();
×
974
    QHashIterator<QString, QHash<QString, QString>> i(profiles);
×
975
    while (i.hasNext()) {
×
976
      i.next();
977
      if (!i.key().isEmpty()) {
×
978
        ui->profileBox->addItem(i.key());
×
979
      }
980
    }
981
    ui->profileBox->model()->sort(0);
×
982
  }
983
  int index = ui->profileBox->findText(QtPassSettings::getProfile());
×
984
  if (index != -1) { //  -1 for not found
×
985
    ui->profileBox->setCurrentIndex(index);
×
986
  }
987
}
×
988

989
/**
990
 * @brief MainWindow::on_profileBox_currentIndexChanged make sure we show the
991
 * correct "profile"
992
 * @param name
993
 */
994
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
995
void MainWindow::on_profileBox_currentIndexChanged(QString name) {
996
#else
997
/**
998
 * @brief Handles changes to the selected profile in the profile combo box.
999
 * @details Ignores the event during a fresh start or when the selected profile
1000
 * matches the current profile. Otherwise, it clears the password field, updates
1001
 * the active profile and related settings, refreshes the environment, and
1002
 * resets the tree view and action states to reflect the newly selected profile.
1003
 *
1004
 * @param name - The newly selected profile name.
1005
 * @return void - This function does not return a value.
1006
 *
1007
 */
1008
void MainWindow::on_profileBox_currentTextChanged(const QString &name) {
×
1009
#endif
1010
  if (m_qtPass->isFreshStart() || name == QtPassSettings::getProfile()) {
×
1011
    return;
×
1012
  }
1013

1014
  ui->lineEdit->clear();
×
1015

1016
  QtPassSettings::setProfile(name);
×
1017

1018
  QtPassSettings::setPassStore(
×
1019
      QtPassSettings::getProfiles().value(name).value("path"));
×
1020
  QtPassSettings::setPassSigningKey(
×
1021
      QtPassSettings::getProfiles().value(name).value("signingKey"));
×
1022
  ui->statusBar->showMessage(tr("Profile changed to %1").arg(name), 2000);
×
1023

1024
  QtPassSettings::getPass()->updateEnv();
×
1025

1026
  const QString passStore = QtPassSettings::getPassStore();
×
1027
  proxyModel.setStore(passStore);
×
1028
  ui->treeView->setRootIndex(
×
1029
      proxyModel.mapFromSource(model.setRootPath(passStore)));
×
1030
  deselect();
×
1031
  ui->treeView->setCurrentIndex(QModelIndex());
×
1032
}
1033

1034
/**
1035
 * @brief MainWindow::initTrayIcon show a nice tray icon on systems that
1036
 * support
1037
 * it
1038
 */
1039
void MainWindow::initTrayIcon() {
×
1040
  this->tray = new TrayIcon(this);
×
1041
  // Setup tray icon
1042

1043
  if (tray == nullptr) {
1044
#ifdef QT_DEBUG
1045
    dbg() << "Allocating tray icon failed.";
1046
#endif
1047
  }
1048

1049
  if (!tray->getIsAllocated()) {
×
1050
    destroyTrayIcon();
×
1051
  }
1052
}
×
1053

1054
/**
1055
 * @brief MainWindow::destroyTrayIcon remove that pesky tray icon
1056
 */
1057
void MainWindow::destroyTrayIcon() {
×
1058
  delete this->tray;
×
1059
  tray = nullptr;
×
1060
}
×
1061

1062
/**
1063
 * @brief MainWindow::closeEvent hide or quit
1064
 * @param event
1065
 */
1066
void MainWindow::closeEvent(QCloseEvent *event) {
×
1067
  if (QtPassSettings::isHideOnClose()) {
×
1068
    this->hide();
×
1069
    event->ignore();
1070
  } else {
1071
    m_qtPass->clearClipboard();
×
1072

1073
    QtPassSettings::setGeometry(saveGeometry());
×
1074
    QtPassSettings::setSavestate(saveState());
×
1075
    QtPassSettings::setMaximized(isMaximized());
×
1076
    if (!isMaximized()) {
×
1077
      QtPassSettings::setPos(pos());
×
1078
      QtPassSettings::setSize(size());
×
1079
    }
1080
    event->accept();
1081
  }
1082
}
×
1083

1084
/**
1085
 * @brief MainWindow::eventFilter filter out some events and focus the
1086
 * treeview
1087
 * @param obj
1088
 * @param event
1089
 * @return
1090
 */
1091
auto MainWindow::eventFilter(QObject *obj, QEvent *event) -> bool {
×
1092
  if (obj == ui->lineEdit && event->type() == QEvent::KeyPress) {
×
1093
    auto *key = dynamic_cast<QKeyEvent *>(event);
×
1094
    if (key != nullptr && key->key() == Qt::Key_Down) {
×
1095
      ui->treeView->setFocus();
×
1096
    }
1097
  }
1098
  return QObject::eventFilter(obj, event);
×
1099
}
1100

1101
/**
1102
 * @brief MainWindow::keyPressEvent did anyone press return, enter or escape?
1103
 * @param event
1104
 */
1105
void MainWindow::keyPressEvent(QKeyEvent *event) {
×
1106
  switch (event->key()) {
×
1107
  case Qt::Key_Delete:
×
1108
    onDelete();
×
1109
    break;
×
1110
  case Qt::Key_Return:
×
1111
  case Qt::Key_Enter:
1112
    if (proxyModel.rowCount() > 0) {
×
1113
      on_treeView_clicked(ui->treeView->currentIndex());
×
1114
    }
1115
    break;
1116
  case Qt::Key_Escape:
×
1117
    ui->lineEdit->clear();
×
1118
    break;
×
1119
  default:
1120
    break;
1121
  }
1122
}
×
1123

1124
/**
1125
 * @brief MainWindow::showContextMenu show us the (file or folder) context
1126
 * menu
1127
 * @param pos
1128
 */
1129
void MainWindow::showContextMenu(const QPoint &pos) {
×
1130
  QModelIndex index = ui->treeView->indexAt(pos);
×
1131
  bool selected = true;
1132
  if (!index.isValid()) {
1133
    ui->treeView->clearSelection();
×
1134
    ui->actionDelete->setEnabled(false);
×
1135
    ui->actionEdit->setEnabled(false);
×
1136
    currentDir = "";
×
1137
    selected = false;
1138
  }
1139

1140
  ui->treeView->setCurrentIndex(index);
×
1141

1142
  QPoint globalPos = ui->treeView->viewport()->mapToGlobal(pos);
×
1143

1144
  QFileInfo fileOrFolder =
1145
      model.fileInfo(proxyModel.mapToSource(ui->treeView->currentIndex()));
×
1146

1147
  QMenu contextMenu;
×
1148
  if (!selected || fileOrFolder.isDir()) {
×
1149
    QAction *openFolder =
1150
        contextMenu.addAction(tr("Open folder with file manager"));
×
1151
    QAction *addFolder = contextMenu.addAction(tr("Add folder"));
×
1152
    QAction *addPassword = contextMenu.addAction(tr("Add password"));
×
1153
    QAction *users = contextMenu.addAction(tr("Users"));
×
1154
    connect(openFolder, &QAction::triggered, this, &MainWindow::openFolder);
×
1155
    connect(addFolder, &QAction::triggered, this, &MainWindow::addFolder);
×
1156
    connect(addPassword, &QAction::triggered, this, &MainWindow::addPassword);
×
1157
    connect(users, &QAction::triggered, this, &MainWindow::onUsers);
×
1158
  } else if (fileOrFolder.isFile()) {
×
1159
    QAction *edit = contextMenu.addAction(tr("Edit"));
×
1160
    connect(edit, &QAction::triggered, this, &MainWindow::onEdit);
×
1161
  }
1162
  if (selected) {
×
1163
    contextMenu.addSeparator();
×
1164
    if (fileOrFolder.isDir()) {
×
1165
      QAction *renameFolder = contextMenu.addAction(tr("Rename folder"));
×
1166
      connect(renameFolder, &QAction::triggered, this,
×
1167
              &MainWindow::renameFolder);
×
1168
    } else if (fileOrFolder.isFile()) {
×
1169
      QAction *renamePassword = contextMenu.addAction(tr("Rename password"));
×
1170
      connect(renamePassword, &QAction::triggered, this,
×
1171
              &MainWindow::renamePassword);
×
1172
    }
1173
    QAction *deleteItem = contextMenu.addAction(tr("Delete"));
×
1174
    connect(deleteItem, &QAction::triggered, this, &MainWindow::onDelete);
×
1175
    if (fileOrFolder.isDir()) {
×
1176
      QString dirPath = QDir::cleanPath(
1177
          Util::getDir(ui->treeView->currentIndex(), false, model, proxyModel));
×
1178
      QAction *reencrypt = contextMenu.addAction(tr("Re-encrypt"));
×
1179
      connect(reencrypt, &QAction::triggered, this,
×
1180
              [this, dirPath]() { reencryptPath(dirPath); });
×
1181
    }
1182
  }
1183
  contextMenu.exec(globalPos);
×
1184
}
×
1185

1186
/**
1187
 * @brief MainWindow::showBrowserContextMenu show us the context menu in
1188
 * password window
1189
 * @param pos
1190
 */
1191
void MainWindow::showBrowserContextMenu(const QPoint &pos) {
×
1192
  QMenu *contextMenu = ui->textBrowser->createStandardContextMenu(pos);
×
1193
  QPoint globalPos = ui->textBrowser->viewport()->mapToGlobal(pos);
×
1194

1195
  contextMenu->exec(globalPos);
×
1196
  delete contextMenu;
×
1197
}
×
1198

1199
/**
1200
 * @brief MainWindow::openFolder open the folder in the default file manager
1201
 */
1202
void MainWindow::openFolder() {
×
1203
  QString dir =
1204
      Util::getDir(ui->treeView->currentIndex(), false, model, proxyModel);
×
1205

1206
  QString path = QDir::toNativeSeparators(dir);
×
1207
  QDesktopServices::openUrl(QUrl::fromLocalFile(path));
×
1208
}
×
1209

1210
/**
1211
 * @brief MainWindow::addFolder add a new folder to store passwords in
1212
 */
1213
void MainWindow::addFolder() {
×
1214
  bool ok;
1215
  QString dir =
1216
      Util::getDir(ui->treeView->currentIndex(), false, model, proxyModel);
×
1217
  QString newdir =
1218
      QInputDialog::getText(this, tr("New file"),
×
1219
                            tr("New Folder: \n(Will be placed in %1 )")
×
1220
                                .arg(QtPassSettings::getPassStore() +
×
1221
                                     Util::getDir(ui->treeView->currentIndex(),
×
1222
                                                  true, model, proxyModel)),
1223
                            QLineEdit::Normal, "", &ok);
×
1224
  if (!ok || newdir.isEmpty()) {
×
1225
    return;
1226
  }
1227
  newdir.prepend(dir);
1228
  if (!QDir().mkdir(newdir)) {
×
1229
    QMessageBox::warning(this, tr("Error"),
×
1230
                         tr("Failed to create folder: %1").arg(newdir));
×
1231
    return;
×
1232
  }
1233
  if (QtPassSettings::isAddGPGId(true)) {
×
1234
    QString gpgIdFile = newdir + "/.gpg-id";
×
1235
    QFile gpgId(gpgIdFile);
×
1236
    if (!gpgId.open(QIODevice::WriteOnly)) {
×
1237
      QMessageBox::warning(
×
1238
          this, tr("Error"),
×
1239
          tr("Failed to create .gpg-id file in: %1").arg(newdir));
×
1240
      return;
1241
    }
1242
    QList<UserInfo> users = QtPassSettings::getPass()->listKeys("", true);
×
1243
    for (const UserInfo &user : users) {
×
1244
      if (user.enabled) {
×
1245
        gpgId.write((user.key_id + "\n").toUtf8());
×
1246
      }
1247
    }
1248
    gpgId.close();
×
1249
  }
×
1250
}
1251

1252
/**
1253
 * @brief MainWindow::renameFolder rename an existing folder
1254
 */
1255
void MainWindow::renameFolder() {
×
1256
  bool ok;
1257
  QString srcDir = QDir::cleanPath(
1258
      Util::getDir(ui->treeView->currentIndex(), false, model, proxyModel));
×
1259
  QString srcDirName = QDir(srcDir).dirName();
×
1260
  QString newName =
1261
      QInputDialog::getText(this, tr("Rename file"), tr("Rename Folder To: "),
×
1262
                            QLineEdit::Normal, srcDirName, &ok);
×
1263
  if (!ok || newName.isEmpty()) {
×
1264
    return;
1265
  }
1266
  QString destDir = srcDir;
1267
  destDir.replace(srcDir.lastIndexOf(srcDirName), srcDirName.length(), newName);
×
1268
  QtPassSettings::getPass()->Move(srcDir, destDir);
×
1269
}
1270

1271
/**
1272
 * @brief MainWindow::editPassword read password and open edit window via
1273
 * MainWindow::onEdit()
1274
 */
1275
void MainWindow::editPassword(const QString &file) {
×
1276
  if (!file.isEmpty()) {
×
1277
    if (QtPassSettings::isUseGit() && QtPassSettings::isAutoPull()) {
×
1278
      onUpdate(true);
×
1279
    }
1280
    setPassword(file, false);
×
1281
  }
1282
}
×
1283

1284
/**
1285
 * @brief MainWindow::renamePassword rename an existing password
1286
 */
1287
void MainWindow::renamePassword() {
×
1288
  bool ok;
1289
  QString file = getFile(ui->treeView->currentIndex(), false);
×
1290
  QString filePath = QFileInfo(file).path();
×
1291
  QString fileName = QFileInfo(file).fileName();
×
1292
  if (fileName.endsWith(".gpg", Qt::CaseInsensitive)) {
×
1293
    fileName.chop(4);
×
1294
  }
1295

1296
  QString newName =
1297
      QInputDialog::getText(this, tr("Rename file"), tr("Rename File To: "),
×
1298
                            QLineEdit::Normal, fileName, &ok);
×
1299
  if (!ok || newName.isEmpty()) {
×
1300
    return;
1301
  }
1302
  QString newFile = QDir(filePath).filePath(newName);
×
1303
  QtPassSettings::getPass()->Move(file, newFile);
×
1304
}
1305

1306
/**
1307
 * @brief MainWindow::clearTemplateWidgets empty the template widget fields in
1308
 * the UI
1309
 */
1310
void MainWindow::clearTemplateWidgets() {
×
1311
  while (ui->gridLayout->count() > 0) {
×
1312
    QLayoutItem *item = ui->gridLayout->takeAt(0);
×
1313
    delete item->widget();
×
1314
    delete item;
×
1315
  }
1316
  ui->verticalLayoutPassword->setSpacing(0);
×
1317
}
×
1318

1319
/**
1320
 * @brief Copies the password of the selected file from the tree view to the
1321
 * clipboard.
1322
 * @example
1323
 * MainWindow::copyPasswordFromTreeview();
1324
 *
1325
 * @return void - This function does not return a value.
1326
 */
1327
void MainWindow::copyPasswordFromTreeview() {
×
1328
  QFileInfo fileOrFolder =
1329
      model.fileInfo(proxyModel.mapToSource(ui->treeView->currentIndex()));
×
1330

1331
  if (fileOrFolder.isFile()) {
×
1332
    QString file = getFile(ui->treeView->currentIndex(), true);
×
1333
    // Disconnect any previous connection to avoid accumulation
1334
    disconnect(QtPassSettings::getPass(), &Pass::finishedShow, this,
×
1335
               &MainWindow::passwordFromFileToClipboard);
1336
    connect(QtPassSettings::getPass(), &Pass::finishedShow, this,
×
1337
            &MainWindow::passwordFromFileToClipboard);
×
1338
    QtPassSettings::getPass()->Show(file);
×
1339
  }
1340
}
×
1341

1342
void MainWindow::passwordFromFileToClipboard(const QString &text) {
×
1343
  QStringList tokens = text.split('\n');
×
1344
  m_qtPass->copyTextToClipboard(tokens[0]);
×
1345
}
×
1346

1347
/**
1348
 * @brief MainWindow::addToGridLayout add a field to the template grid
1349
 * @param position
1350
 * @param field
1351
 * @param value
1352
 */
1353
void MainWindow::addToGridLayout(int position, const QString &field,
×
1354
                                 const QString &value) {
1355
  QString trimmedField = field.trimmed();
1356
  QString trimmedValue = value.trimmed();
1357

1358
  const QString buttonStyle =
1359
      "border-style: none; background: transparent; padding: 0; margin: 0; "
1360
      "icon-size: 16px; color: inherit;";
×
1361

1362
  // Combine the Copy button and the line edit in one widget
1363
  auto *frame = new QFrame();
×
1364
  QLayout *ly = new QHBoxLayout();
×
1365
  ly->setContentsMargins(5, 2, 2, 2);
×
1366
  ly->setSpacing(0);
×
1367
  frame->setLayout(ly);
×
1368
  if (QtPassSettings::getClipBoardType() != Enums::CLIPBOARD_NEVER) {
×
1369
    auto *fieldLabel = new QPushButtonWithClipboard(trimmedValue, this);
×
1370
    connect(fieldLabel, &QPushButtonWithClipboard::clicked, m_qtPass,
×
1371
            &QtPass::copyTextToClipboard);
×
1372

1373
    fieldLabel->setStyleSheet(buttonStyle);
×
1374
    frame->layout()->addWidget(fieldLabel);
×
1375
  }
1376

1377
  if (QtPassSettings::isUseQrencode()) {
×
1378
    auto *qrbutton = new QPushButtonAsQRCode(trimmedValue, this);
×
1379
    connect(qrbutton, &QPushButtonAsQRCode::clicked, m_qtPass,
×
1380
            &QtPass::showTextAsQRCode);
×
1381
    qrbutton->setStyleSheet(buttonStyle);
×
1382
    frame->layout()->addWidget(qrbutton);
×
1383
  }
1384

1385
  // set the echo mode to password, if the field is "password"
1386
  const QString lineStyle =
1387
      QtPassSettings::isUseMonospace()
×
1388
          ? "border-style: none; background: transparent; font-family: "
1389
            "monospace;"
1390
          : "border-style: none; background: transparent;";
×
1391

1392
  if (QtPassSettings::isHidePassword() && trimmedField == tr("Password")) {
×
1393
    auto *line = new QLineEdit();
×
1394
    line->setObjectName(trimmedField);
×
1395
    line->setText(trimmedValue);
×
1396
    line->setReadOnly(true);
×
1397
    line->setStyleSheet(lineStyle);
×
1398
    line->setContentsMargins(0, 0, 0, 0);
×
1399
    line->setEchoMode(QLineEdit::Password);
×
1400
    auto *showButton = new QPushButtonShowPassword(line, this);
×
1401
    showButton->setStyleSheet(buttonStyle);
×
1402
    showButton->setContentsMargins(0, 0, 0, 0);
×
1403
    frame->layout()->addWidget(showButton);
×
1404
    frame->layout()->addWidget(line);
×
1405
  } else {
1406
    auto *line = new QTextBrowser();
×
1407
    line->setOpenExternalLinks(true);
×
1408
    line->setOpenLinks(true);
×
1409
    line->setMaximumHeight(26);
×
1410
    line->setMinimumHeight(26);
×
1411
    line->setSizePolicy(
×
1412
        QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum));
1413
    line->setObjectName(trimmedField);
×
1414
    trimmedValue.replace(Util::protocolRegex(), R"(<a href="\1">\1</a>)");
×
1415
    line->setText(trimmedValue);
×
1416
    line->setReadOnly(true);
×
1417
    line->setStyleSheet(lineStyle);
×
1418
    line->setContentsMargins(0, 0, 0, 0);
×
1419
    frame->layout()->addWidget(line);
×
1420
  }
1421

1422
  frame->setStyleSheet(
×
1423
      ".QFrame{border: 1px solid lightgrey; border-radius: 5px;}");
1424

1425
  // set into the layout
1426
  ui->gridLayout->addWidget(new QLabel(trimmedField), position, 0);
×
1427
  ui->gridLayout->addWidget(frame, position, 1);
×
1428
}
×
1429

1430
/**
1431
 * @brief Displays message in status bar
1432
 *
1433
 * @param msg     text to be displayed
1434
 * @param timeout time for which msg shall be visible
1435
 */
1436
void MainWindow::showStatusMessage(const QString &msg, int timeout) {
×
1437
  ui->statusBar->showMessage(msg, timeout);
×
1438
}
×
1439

1440
/**
1441
 * @brief MainWindow::reencryptPath re-encrypt all passwords in a directory
1442
 * @param dir Directory path to re-encrypt
1443
 */
1444
void MainWindow::reencryptPath(const QString &dir) {
×
1445
  QDir checkDir(dir);
×
1446
  if (!checkDir.exists()) {
×
1447
    QMessageBox::critical(this, tr("Error"),
×
1448
                          tr("Directory does not exist: %1").arg(dir));
×
1449
    return;
×
1450
  }
1451

1452
  int ret = QMessageBox::question(
×
1453
      this, tr("Re-encrypt passwords"),
×
1454
      tr("Re-encrypt all passwords in %1?\n\n"
×
1455
         "This will re-encrypt ALL password files in this folder "
1456
         "using the current recipients defined in .gpg-id.\n\n"
1457
         "This may rewrite many files and cannot be undone easily.\n\n"
1458
         "Continue?")
1459
          .arg(QDir(dir).dirName()),
×
1460
      QMessageBox::Yes | QMessageBox::No);
1461

1462
  if (ret != QMessageBox::Yes)
×
1463
    return;
1464

1465
  // Prevent double execution - use same method as startReencryptPath
1466
  setUiElementsEnabled(false);
×
1467
  ui->treeView->setDisabled(true);
×
1468

1469
  QtPassSettings::getImitatePass()->reencryptPath(
×
1470
      QDir::cleanPath(QDir(dir).absolutePath()));
×
1471
}
×
1472

1473
/**
1474
 * @brief MainWindow::startReencryptPath disable ui elements and treeview
1475
 */
1476
void MainWindow::startReencryptPath() {
×
1477
  setUiElementsEnabled(false);
×
1478
  ui->treeView->setDisabled(true);
×
1479
}
×
1480

1481
/**
1482
 * @brief MainWindow::endReencryptPath re-enable ui elements
1483
 */
1484
void MainWindow::endReencryptPath() { setUiElementsEnabled(true); }
×
1485

1486
void MainWindow::updateGitButtonVisibility() {
×
1487
  if (!QtPassSettings::isUseGit() ||
×
1488
      (QtPassSettings::getGitExecutable().isEmpty() &&
×
1489
       QtPassSettings::getPassExecutable().isEmpty())) {
×
1490
    enableGitButtons(false);
×
1491
  } else {
1492
    enableGitButtons(true);
×
1493
  }
1494
}
×
1495

1496
void MainWindow::updateOtpButtonVisibility() {
×
1497
#if defined(Q_OS_WIN) || defined(__APPLE__)
1498
  ui->actionOtp->setVisible(false);
1499
#endif
1500
  if (!QtPassSettings::isUseOtp()) {
×
1501
    ui->actionOtp->setEnabled(false);
×
1502
  } else {
1503
    ui->actionOtp->setEnabled(true);
×
1504
  }
1505
}
×
1506

NEW
1507
void MainWindow::updateGrepButtonVisibility() {
×
NEW
1508
  const bool enabled = QtPassSettings::isUseGrepSearch();
×
NEW
1509
  ui->grepButton->setVisible(enabled);
×
NEW
1510
  ui->grepCaseButton->setVisible(enabled);
×
NEW
1511
  if (!enabled && m_grepMode) {
×
NEW
1512
    ui->grepButton->setChecked(false);
×
1513
  }
NEW
1514
}
×
1515

UNCOV
1516
void MainWindow::enableGitButtons(const bool &state) {
×
1517
  // Following GNOME guidelines is preferable disable buttons instead of hide
1518
  ui->actionPush->setEnabled(state);
×
1519
  ui->actionUpdate->setEnabled(state);
×
1520
}
×
1521

1522
/**
1523
 * @brief MainWindow::critical critical message popup wrapper.
1524
 * @param title
1525
 * @param msg
1526
 */
1527
void MainWindow::critical(const QString &title, const QString &msg) {
×
1528
  QMessageBox::critical(this, title, msg);
×
1529
}
×
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