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

IJHack / QtPass / 24859741708

23 Apr 2026 09:27PM UTC coverage: 26.745% (-0.2%) from 26.966%
24859741708

Pull #1144

github

web-flow
Merge a09256da5 into 2984cc30d
Pull Request #1144: feat: add Share submenu for folders

0 of 41 new or added lines in 1 file covered. (0.0%)

372 existing lines in 3 files now uncovered.

1636 of 6117 relevant lines covered (26.75%)

28.97 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 <QApplication>
22
#include <QCloseEvent>
23
#include <QDesktopServices>
24
#include <QDialog>
25
#include <QDirIterator>
26
#include <QFile>
27
#include <QFileInfo>
28
#include <QInputDialog>
29
#include <QLabel>
30
#include <QMenu>
31
#include <QMessageBox>
32
#include <QShortcut>
33
#include <QTimer>
34
#include <QTreeWidget>
35
#include <utility>
36

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

UNCOV
53
  m_qtPass = new QtPass(this);
×
54

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

61
  model.setNameFilters(QStringList() << "*.gpg");
×
UNCOV
62
  model.setNameFilterDisables(false);
×
63

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

UNCOV
71
  QString passStore = QtPassSettings::getPassStore(Util::findPasswordStore());
×
72

73
  QModelIndex rootDir = model.setRootPath(passStore);
×
UNCOV
74
  model.fetchMore(rootDir);
×
75

76
  proxyModel.setModelAndStore(&model, passStore);
×
UNCOV
77
  selectionModel.reset(new QItemSelectionModel(&proxyModel));
×
78

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

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

UNCOV
108
  updateProfileBox();
×
109

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

116
  searchTimer.setInterval(350);
×
UNCOV
117
  searchTimer.setSingleShot(true);
×
118

UNCOV
119
  connect(&searchTimer, &QTimer::timeout, this, &MainWindow::onTimeoutSearch);
×
120

121
  initToolBarButtons();
×
UNCOV
122
  initStatusBar();
×
123

124
  ui->lineEdit->setClearButtonEnabled(true);
×
UNCOV
125
  updateGrepButtonVisibility();
×
126

UNCOV
127
  setUiElementsEnabled(true);
×
128

129
  QTimer::singleShot(10, this, SLOT(focusInput()));
130

UNCOV
131
  ui->lineEdit->setText(searchText);
×
132

UNCOV
133
  if (!m_qtPass->init()) {
×
134
    // no working config so this should just quit
UNCOV
135
    QApplication::quit();
×
136
  }
UNCOV
137
}
×
138

UNCOV
139
MainWindow::~MainWindow() { delete m_qtPass; }
×
140

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

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

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

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

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

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

212
auto MainWindow::getCurrentTreeViewIndex() -> QModelIndex {
×
UNCOV
213
  return ui->treeView->currentIndex();
×
214
}
215

216
void MainWindow::cleanKeygenDialog() {
×
217
  this->keygen->close();
×
218
  this->keygen = nullptr;
×
UNCOV
219
}
×
220

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

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

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

265
  if (QtPassSettings::isNoLineWrapping()) {
×
UNCOV
266
    ui->textBrowser->setLineWrapMode(QTextBrowser::NoWrap);
×
267
  } else {
UNCOV
268
    ui->textBrowser->setLineWrapMode(QTextBrowser::WidgetWidth);
×
269
  }
UNCOV
270
}
×
271

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

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

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

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

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

323
      updateGitButtonVisibility();
×
324
      updateOtpButtonVisibility();
×
325
      updateGrepButtonVisibility();
×
326
      if (QtPassSettings::isUseTrayIcon() && tray == nullptr) {
×
327
        initTrayIcon();
×
328
      } else if (!QtPassSettings::isUseTrayIcon() && tray != nullptr) {
×
UNCOV
329
        destroyTrayIcon();
×
330
      }
331
    }
332

UNCOV
333
    m_qtPass->setFreshStart(false);
×
334
  }
UNCOV
335
}
×
336

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

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

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

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

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

409
  if (fileOrFolder.isFile()) {
×
UNCOV
410
    editPassword(getFile(index, true));
×
411
  }
UNCOV
412
}
×
413

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

427
void MainWindow::executeWrapperStarted() {
×
428
  clearTemplateWidgets();
×
429
  ui->textBrowser->clear();
×
430
  setUiElementsEnabled(false);
×
431
  clearPanelTimer.stop();
×
UNCOV
432
}
×
433

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

454
  // set clipped text
UNCOV
455
  m_qtPass->setClippedText(password, p_output);
×
456

457
  // first clear the current view:
UNCOV
458
  clearTemplateWidgets();
×
459

460
  // show what is needed:
461
  if (QtPassSettings::isHideContent()) {
×
462
    output = "***" + tr("Content hidden") + "***";
×
463
  } else if (!QtPassSettings::isDisplayAsIs()) {
×
UNCOV
464
    if (!password.isEmpty()) {
×
465
      // set the password, it is hidden if needed in addToGridLayout
UNCOV
466
      addToGridLayout(0, tr("Password"), password);
×
467
    }
468

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

UNCOV
480
    output = fileContent.getRemainingDataForDisplay();
×
481
  }
482

483
  if (QtPassSettings::isUseAutoclearPanel()) {
×
UNCOV
484
    clearPanelTimer.start();
×
485
  }
486

487
  emit passShowHandlerFinished(output);
×
488
  setUiElementsEnabled(true);
×
UNCOV
489
}
×
490

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

515
/**
516
 * @brief MainWindow::clearPanel hide the information from shoulder surfers
517
 */
518
void MainWindow::clearPanel(bool notify) {
×
519
  while (ui->gridLayout->count() > 0) {
×
520
    QLayoutItem *item = ui->gridLayout->takeAt(0);
×
521
    delete item->widget();
×
UNCOV
522
    delete item;
×
523
  }
524
  const bool grepWasVisible = ui->grepResultsList->isVisible();
×
525
  ui->grepResultsList->clear();
×
526
  if (grepWasVisible) {
×
527
    ui->grepResultsList->setVisible(false);
×
528
    ui->treeView->setVisible(true);
×
529
    if (m_grepMode) {
×
530
      m_grepMode = false;
×
531
      ui->grepButton->blockSignals(true);
×
532
      ui->grepButton->setChecked(false);
×
533
      ui->grepButton->blockSignals(false);
×
534
      ui->lineEdit->blockSignals(true);
×
535
      ui->lineEdit->clear();
×
536
      ui->lineEdit->blockSignals(false);
×
UNCOV
537
      ui->lineEdit->setPlaceholderText(tr("Search Password"));
×
538
    }
539
  }
540
  if (notify) {
×
541
    QString output = "***" + tr("Password and Content hidden") + "***";
×
UNCOV
542
    ui->textBrowser->setHtml(output);
×
543
  } else {
UNCOV
544
    ui->textBrowser->setHtml("");
×
545
  }
UNCOV
546
}
×
547

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

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

UNCOV
591
  if (QtPassSettings::isAlwaysOnTop()) {
×
592
    Qt::WindowFlags flags = windowFlags();
593
    setWindowFlags(flags | Qt::WindowStaysOnTopHint);
×
UNCOV
594
    show();
×
595
  }
596

597
  if (QtPassSettings::isUseTrayIcon() && tray == nullptr) {
×
598
    initTrayIcon();
×
UNCOV
599
    if (QtPassSettings::isStartMinimized()) {
×
600
      // since we are still in constructor, can't directly hide
UNCOV
601
      QTimer::singleShot(10, this, SLOT(hide()));
×
602
    }
603
  } else if (!QtPassSettings::isUseTrayIcon() && tray != nullptr) {
×
UNCOV
604
    destroyTrayIcon();
×
605
  }
UNCOV
606
}
×
607

608
/**
609
 * @brief MainWindow::on_configButton_clicked run Mainwindow::config
610
 */
UNCOV
611
void MainWindow::onConfig() { config(); }
×
612

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

630
/**
631
 * @brief MainWindow::onTimeoutSearch Fired when search is finished or too much
632
 * time from two keypresses is elapsed
633
 */
634
void MainWindow::onTimeoutSearch() {
×
UNCOV
635
  QString query = ui->lineEdit->text();
×
636

637
  if (query.isEmpty()) {
×
638
    ui->treeView->collapseAll();
×
UNCOV
639
    deselect();
×
640
  }
641

642
  query.replace(QStringLiteral(" "), ".*");
×
643
  QRegularExpression regExp(query, QRegularExpression::CaseInsensitiveOption);
×
644
  proxyModel.setFilterRegularExpression(regExp);
×
645
  ui->treeView->setRootIndex(proxyModel.mapFromSource(
×
UNCOV
646
      model.setRootPath(QtPassSettings::getPassStore())));
×
647

648
  if (proxyModel.rowCount() > 0 && !query.isEmpty()) {
×
UNCOV
649
    selectFirstFile();
×
650
  } else {
651
    ui->actionEdit->setEnabled(false);
×
UNCOV
652
    ui->actionDelete->setEnabled(false);
×
653
  }
UNCOV
654
}
×
655

656
/**
657
 * @brief MainWindow::on_lineEdit_returnPressed get searching
658
 *
659
 * Select the first possible file in the tree
660
 */
UNCOV
661
void MainWindow::on_lineEdit_returnPressed() {
×
662
#ifdef QT_DEBUG
663
  dbg() << "on_lineEdit_returnPressed" << proxyModel.rowCount();
664
#endif
665

666
  if (m_grepMode) {
×
667
    const QString query = ui->lineEdit->text();
×
668
    if (!query.isEmpty()) {
×
669
      m_grepCancelled = false;
×
670
      ui->grepResultsList->clear();
×
671
      ui->statusBar->showMessage(tr("Searching…"));
×
672
      if (!m_grepBusy) {
×
673
        m_grepBusy = true;
×
UNCOV
674
        QApplication::setOverrideCursor(Qt::WaitCursor);
×
675
      }
UNCOV
676
      QtPassSettings::getPass()->Grep(query, ui->grepCaseButton->isChecked());
×
677
    } else {
678
      m_grepCancelled = true;
×
679
      if (m_grepBusy) {
×
680
        m_grepBusy = false;
×
UNCOV
681
        QApplication::restoreOverrideCursor();
×
682
      }
683
      ui->grepResultsList->clear();
×
684
      ui->grepResultsList->setVisible(false);
×
UNCOV
685
      ui->treeView->setVisible(true);
×
686
    }
687
    return;
688
  }
689

690
  if (proxyModel.rowCount() > 0) {
×
691
    selectFirstFile();
×
UNCOV
692
    on_treeView_clicked(ui->treeView->currentIndex());
×
693
  }
694
}
695

696
/**
697
 * @brief Toggle grep (content search) mode.
698
 */
699
void MainWindow::on_grepButton_toggled(bool checked) {
×
700
  m_grepMode = checked;
×
701
  if (checked) {
×
702
    ui->lineEdit->setPlaceholderText(tr("Search content (regex)"));
×
703
    ui->lineEdit->clear();
×
704
    searchTimer.stop();
×
705
    proxyModel.setFilterRegularExpression(QRegularExpression());
×
706
    ui->treeView->setRootIndex(proxyModel.mapFromSource(
×
707
        model.setRootPath(QtPassSettings::getPassStore())));
×
UNCOV
708
    ui->grepResultsList->setVisible(false);
×
709
    // Keep treeView visible until results arrive
710
  } else {
711
    if (m_grepBusy) {
×
712
      m_grepBusy = false;
×
713
      m_grepCancelled = true;
×
UNCOV
714
      QApplication::restoreOverrideCursor();
×
715
    }
716
    searchTimer.stop();
×
717
    ui->lineEdit->blockSignals(true);
×
718
    ui->lineEdit->clear();
×
719
    ui->lineEdit->blockSignals(false);
×
720
    ui->lineEdit->setPlaceholderText(tr("Search Password"));
×
721
    ui->grepResultsList->clear();
×
722
    ui->grepResultsList->setVisible(false);
×
723
    ui->treeView->setVisible(true);
×
724
    proxyModel.setFilterRegularExpression(QRegularExpression());
×
725
    ui->treeView->setRootIndex(proxyModel.mapFromSource(
×
UNCOV
726
        model.setRootPath(QtPassSettings::getPassStore())));
×
727
  }
UNCOV
728
}
×
729

730
/**
731
 * @brief Display grep results in grepResultsList.
732
 */
UNCOV
733
void MainWindow::onGrepFinished(
×
734
    const QList<QPair<QString, QStringList>> &results) {
735
  if (m_grepBusy) {
×
736
    m_grepBusy = false;
×
UNCOV
737
    QApplication::restoreOverrideCursor();
×
738
  }
739
  if (m_grepCancelled) {
×
740
    m_grepCancelled = false;
×
UNCOV
741
    return;
×
742
  }
743
  setUiElementsEnabled(true);
×
UNCOV
744
  if (!m_grepMode)
×
745
    return;
746
  ui->grepResultsList->clear();
×
747
  if (results.isEmpty()) {
×
748
    ui->statusBar->showMessage(tr("No matches found."), 3000);
×
749
    ui->grepResultsList->setVisible(false);
×
750
    ui->treeView->setVisible(true);
×
UNCOV
751
    return;
×
752
  }
UNCOV
753
  const bool hideContent = QtPassSettings::isHideContent();
×
754
  int totalLines = 0;
755
  for (const auto &pair : results) {
×
756
    QTreeWidgetItem *entryItem = new QTreeWidgetItem(ui->grepResultsList);
×
757
    entryItem->setText(0, pair.first);
×
758
    entryItem->setData(0, Qt::UserRole, pair.first);
×
759
    for (const QString &line : pair.second) {
×
760
      QTreeWidgetItem *lineItem = new QTreeWidgetItem(entryItem);
×
UNCOV
761
      lineItem->setText(0, hideContent ? "***" + tr("Content hidden") + "***"
×
762
                                       : line);
763
      lineItem->setData(0, Qt::UserRole, pair.first);
×
UNCOV
764
      ++totalLines;
×
765
    }
766
  }
767
  ui->grepResultsList->expandAll();
×
768
  ui->treeView->setVisible(false);
×
769
  ui->grepResultsList->setVisible(true);
×
770
  ui->statusBar->showMessage(
×
771
      tr("Found %n match(es)", nullptr, totalLines) + " " +
×
UNCOV
772
          tr("in %n entr(ies).", nullptr, results.size()),
×
773
      3000);
774
  if (QtPassSettings::isUseAutoclearPanel())
×
UNCOV
775
    clearPanelTimer.start();
×
776
}
777

778
/**
779
 * @brief Navigate to the password entry when a grep result is clicked.
780
 */
UNCOV
781
void MainWindow::on_grepResultsList_itemClicked(QTreeWidgetItem *item,
×
782
                                                int /*column*/) {
783
  const QString entry = item->data(0, Qt::UserRole).toString();
×
UNCOV
784
  if (entry.isEmpty())
×
785
    return;
786
  const QString fullPath = QDir::cleanPath(
787
      QDir(QtPassSettings::getPassStore()).filePath(entry + ".gpg"));
×
UNCOV
788
  QModelIndex srcIndex = model.index(fullPath);
×
789
  if (!srcIndex.isValid())
790
    return;
UNCOV
791
  QModelIndex proxyIndex = proxyModel.mapFromSource(srcIndex);
×
792
  if (!proxyIndex.isValid())
793
    return;
794
  ui->treeView->setCurrentIndex(proxyIndex);
×
795
  on_treeView_clicked(proxyIndex);
×
796
  if (QtPassSettings::isHideContent() || QtPassSettings::isUseAutoclearPanel())
×
797
    ui->grepResultsList->clear();
×
798
  ui->grepResultsList->setVisible(false);
×
799
  ui->treeView->setVisible(true);
×
800
  ui->treeView->scrollTo(proxyIndex);
×
UNCOV
801
  ui->treeView->setFocus();
×
802
}
803

804
/**
805
 * @brief MainWindow::selectFirstFile select the first possible file in the
806
 * tree
807
 */
808
void MainWindow::selectFirstFile() {
×
809
  QModelIndex index = proxyModel.mapFromSource(
×
810
      model.setRootPath(QtPassSettings::getPassStore()));
×
811
  index = firstFile(index);
×
812
  ui->treeView->setCurrentIndex(index);
×
UNCOV
813
}
×
814

815
/**
816
 * @brief MainWindow::firstFile return location of first possible file
817
 * @param parentIndex
818
 * @return QModelIndex
819
 */
820
auto MainWindow::firstFile(QModelIndex parentIndex) -> QModelIndex {
×
821
  QModelIndex index = parentIndex;
×
822
  int numRows = proxyModel.rowCount(parentIndex);
×
823
  for (int row = 0; row < numRows; ++row) {
×
824
    index = proxyModel.index(row, 0, parentIndex);
×
825
    if (model.fileInfo(proxyModel.mapToSource(index)).isFile()) {
×
UNCOV
826
      return index;
×
827
    }
828
    if (proxyModel.hasChildren(index)) {
×
UNCOV
829
      return firstFile(index);
×
830
    }
831
  }
UNCOV
832
  return index;
×
833
}
834

835
/**
836
 * @brief MainWindow::setPassword open passworddialog
837
 * @param file which pgp file
838
 * @param isNew insert (not update)
839
 */
840
void MainWindow::setPassword(const QString &file, bool isNew) {
×
UNCOV
841
  PasswordDialog d(file, isNew, this);
×
842

843
  if (isNew) {
×
UNCOV
844
    QString storePath = QtPassSettings::getPassStore();
×
845
    QString folder =
846
        Util::getDir(ui->treeView->currentIndex(), false, model, proxyModel);
×
847
    if (folder.isEmpty()) {
×
UNCOV
848
      folder = storePath;
×
849
    }
UNCOV
850
    QHash<QString, QStringList> templates = Util::readTemplates(storePath);
×
851
    if (!templates.isEmpty()) {
852
      QString defaultTemplate = Util::getFolderTemplate(folder, storePath);
×
853
      d.setAvailableTemplates(templates, defaultTemplate);
×
854
      new QShortcut(QKeySequence(Qt::CTRL | Qt::Key_T), &d,
×
UNCOV
855
                    [&d]() { d.cycleTemplate(); });
×
856
    }
UNCOV
857
  }
×
858

859
  if (!d.exec()) {
×
UNCOV
860
    ui->treeView->setFocus();
×
861
  }
UNCOV
862
}
×
863

864
/**
865
 * @brief MainWindow::addPassword add a new password by showing a
866
 * number of dialogs.
867
 */
UNCOV
868
void MainWindow::addPassword() {
×
869
  bool ok;
870
  QString dir =
UNCOV
871
      Util::getDir(ui->treeView->currentIndex(), true, model, proxyModel);
×
872
  QString file =
873
      QInputDialog::getText(this, tr("New file"),
×
874
                            tr("New password file: \n(Will be placed in %1 )")
×
875
                                .arg(QtPassSettings::getPassStore() +
×
UNCOV
876
                                     Util::getDir(ui->treeView->currentIndex(),
×
877
                                                  true, model, proxyModel)),
878
                            QLineEdit::Normal, "", &ok);
×
UNCOV
879
  if (!ok || file.isEmpty()) {
×
880
    return;
881
  }
882
  file = dir + file;
×
UNCOV
883
  setPassword(file);
×
884
}
885

886
/**
887
 * @brief MainWindow::onDelete remove password, if you are
888
 * sure.
889
 */
890
void MainWindow::onDelete() {
×
UNCOV
891
  QModelIndex currentIndex = ui->treeView->currentIndex();
×
892
  if (!currentIndex.isValid()) {
893
    // This fixes https://github.com/IJHack/QtPass/issues/556
894
    // Otherwise the entire password directory would be deleted if
895
    // nothing is selected in the tree view.
UNCOV
896
    return;
×
897
  }
898

899
  QFileInfo fileOrFolder =
900
      model.fileInfo(proxyModel.mapToSource(ui->treeView->currentIndex()));
×
UNCOV
901
  QString file = "";
×
902
  bool isDir = false;
903

904
  if (fileOrFolder.isFile()) {
×
UNCOV
905
    file = getFile(ui->treeView->currentIndex(), true);
×
906
  } else {
UNCOV
907
    file = Util::getDir(ui->treeView->currentIndex(), true, model, proxyModel);
×
908
    isDir = true;
909
  }
910

911
  QString dirMessage = tr(" and the whole content?");
912
  if (isDir) {
×
913
    QDirIterator it(model.rootPath() + QDir::separator() + file,
×
UNCOV
914
                    QDirIterator::Subdirectories);
×
915
    bool okDir = true;
916
    while (it.hasNext() && okDir) {
×
917
      it.next();
×
918
      if (QFileInfo(it.filePath()).isFile()) {
×
UNCOV
919
        if (QFileInfo(it.filePath()).suffix() != "gpg") {
×
920
          okDir = false;
UNCOV
921
          dirMessage = tr(" and the whole content? <br><strong>Attention: "
×
922
                          "there are unexpected files in the given folder, "
923
                          "check them before continue.</strong>");
924
        }
925
      }
926
    }
UNCOV
927
  }
×
928

929
  if (QMessageBox::question(
×
930
          this, isDir ? tr("Delete folder?") : tr("Delete password?"),
×
931
          tr("Are you sure you want to delete %1%2?")
×
UNCOV
932
              .arg(QDir::separator() + file, isDir ? dirMessage : "?"),
×
933
          QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) {
934
    return;
935
  }
936

937
  QtPassSettings::getPass()->Remove(file, isDir);
×
UNCOV
938
}
×
939

940
/**
941
 * @brief MainWindow::onOTP try and generate (selected) OTP code.
942
 */
943
void MainWindow::onOtp() {
×
944
  QString file = getFile(ui->treeView->currentIndex(), true);
×
945
  if (!file.isEmpty()) {
×
946
    if (QtPassSettings::isUseOtp()) {
×
947
      setUiElementsEnabled(false);
×
UNCOV
948
      QtPassSettings::getPass()->OtpGenerate(file);
×
949
    }
950
  } else {
UNCOV
951
    flashText(tr("No password selected for OTP generation"), true);
×
952
  }
UNCOV
953
}
×
954

955
/**
956
 * @brief MainWindow::onEdit try and edit (selected) password.
957
 */
958
void MainWindow::onEdit() {
×
959
  QString file = getFile(ui->treeView->currentIndex(), true);
×
960
  editPassword(file);
×
UNCOV
961
}
×
962

963
/**
964
 * @brief MainWindow::userDialog see MainWindow::onUsers()
965
 * @param dir folder to edit users for.
966
 */
967
void MainWindow::userDialog(const QString &dir) {
×
968
  if (!dir.isEmpty()) {
×
UNCOV
969
    currentDir = dir;
×
970
  }
971
  onUsers();
×
UNCOV
972
}
×
973

974
/**
975
 * @brief MainWindow::onUsers edit users for the current
976
 * folder,
977
 * gets lists and opens UserDialog.
978
 */
UNCOV
979
void MainWindow::onUsers() {
×
980
  QString dir =
981
      currentDir.isEmpty()
982
          ? Util::getDir(ui->treeView->currentIndex(), false, model, proxyModel)
×
UNCOV
983
          : currentDir;
×
984

985
  UsersDialog d(dir, this);
×
986
  if (!d.exec()) {
×
UNCOV
987
    ui->treeView->setFocus();
×
988
  }
UNCOV
989
}
×
990

991
/**
992
 * @brief MainWindow::messageAvailable we have some text/message/search to do.
993
 * @param message
994
 */
995
void MainWindow::messageAvailable(const QString &message) {
×
996
  if (message.isEmpty()) {
×
UNCOV
997
    focusInput();
×
998
  } else {
999
    ui->treeView->expandAll();
×
1000
    ui->lineEdit->setText(message);
×
UNCOV
1001
    on_lineEdit_returnPressed();
×
1002
  }
1003
  show();
×
1004
  raise();
×
UNCOV
1005
}
×
1006

1007
/**
1008
 * @brief MainWindow::generateKeyPair internal gpg keypair generator . .
1009
 * @param batch
1010
 * @param keygenWindow
1011
 */
1012
void MainWindow::generateKeyPair(const QString &batch, QDialog *keygenWindow) {
×
1013
  keygen = keygenWindow;
×
1014
  emit generateGPGKeyPair(batch);
×
UNCOV
1015
}
×
1016

1017
/**
1018
 * @brief MainWindow::updateProfileBox update the list of profiles, optionally
1019
 * select a more appropriate one to view too
1020
 */
UNCOV
1021
void MainWindow::updateProfileBox() {
×
1022
  QHash<QString, QHash<QString, QString>> profiles =
UNCOV
1023
      QtPassSettings::getProfiles();
×
1024

1025
  if (profiles.isEmpty()) {
UNCOV
1026
    ui->profileWidget->hide();
×
1027
  } else {
1028
    ui->profileWidget->show();
×
1029
    ui->profileBox->setEnabled(profiles.size() > 1);
×
1030
    ui->profileBox->clear();
×
1031
    QHashIterator<QString, QHash<QString, QString>> i(profiles);
×
UNCOV
1032
    while (i.hasNext()) {
×
1033
      i.next();
1034
      if (!i.key().isEmpty()) {
×
UNCOV
1035
        ui->profileBox->addItem(i.key());
×
1036
      }
1037
    }
UNCOV
1038
    ui->profileBox->model()->sort(0);
×
1039
  }
1040
  int index = ui->profileBox->findText(QtPassSettings::getProfile());
×
1041
  if (index != -1) { //  -1 for not found
×
UNCOV
1042
    ui->profileBox->setCurrentIndex(index);
×
1043
  }
UNCOV
1044
}
×
1045

1046
/**
1047
 * @brief MainWindow::on_profileBox_currentIndexChanged make sure we show the
1048
 * correct "profile"
1049
 * @param name
1050
 */
1051
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
1052
void MainWindow::on_profileBox_currentIndexChanged(QString name) {
1053
#else
1054
/**
1055
 * @brief Handles changes to the selected profile in the profile combo box.
1056
 * @details Ignores the event during a fresh start or when the selected profile
1057
 * matches the current profile. Otherwise, it clears the password field, updates
1058
 * the active profile and related settings, refreshes the environment, and
1059
 * resets the tree view and action states to reflect the newly selected profile.
1060
 *
1061
 * @param name - The newly selected profile name.
1062
 * @return void - This function does not return a value.
1063
 *
1064
 */
UNCOV
1065
void MainWindow::on_profileBox_currentTextChanged(const QString &name) {
×
1066
#endif
1067
  if (m_qtPass->isFreshStart() || name == QtPassSettings::getProfile()) {
×
UNCOV
1068
    return;
×
1069
  }
1070

UNCOV
1071
  ui->lineEdit->clear();
×
1072

UNCOV
1073
  QtPassSettings::setProfile(name);
×
1074

1075
  QtPassSettings::setPassStore(
×
1076
      QtPassSettings::getProfiles().value(name).value("path"));
×
1077
  QtPassSettings::setPassSigningKey(
×
1078
      QtPassSettings::getProfiles().value(name).value("signingKey"));
×
UNCOV
1079
  ui->statusBar->showMessage(tr("Profile changed to %1").arg(name), 2000);
×
1080

UNCOV
1081
  QtPassSettings::getPass()->updateEnv();
×
1082

1083
  const QString passStore = QtPassSettings::getPassStore();
×
1084
  proxyModel.setStore(passStore);
×
1085
  ui->treeView->setRootIndex(
×
1086
      proxyModel.mapFromSource(model.setRootPath(passStore)));
×
1087
  deselect();
×
UNCOV
1088
  ui->treeView->setCurrentIndex(QModelIndex());
×
1089
}
1090

1091
/**
1092
 * @brief MainWindow::initTrayIcon show a nice tray icon on systems that
1093
 * support
1094
 * it
1095
 */
1096
void MainWindow::initTrayIcon() {
×
UNCOV
1097
  this->tray = new TrayIcon(this);
×
1098
  // Setup tray icon
1099

1100
  if (tray == nullptr) {
1101
#ifdef QT_DEBUG
1102
    dbg() << "Allocating tray icon failed.";
1103
#endif
1104
  }
1105

1106
  if (!tray->getIsAllocated()) {
×
UNCOV
1107
    destroyTrayIcon();
×
1108
  }
UNCOV
1109
}
×
1110

1111
/**
1112
 * @brief MainWindow::destroyTrayIcon remove that pesky tray icon
1113
 */
1114
void MainWindow::destroyTrayIcon() {
×
1115
  delete this->tray;
×
1116
  tray = nullptr;
×
UNCOV
1117
}
×
1118

1119
/**
1120
 * @brief MainWindow::closeEvent hide or quit
1121
 * @param event
1122
 */
1123
void MainWindow::closeEvent(QCloseEvent *event) {
×
1124
  if (QtPassSettings::isHideOnClose()) {
×
UNCOV
1125
    this->hide();
×
1126
    event->ignore();
1127
  } else {
UNCOV
1128
    m_qtPass->clearClipboard();
×
1129

1130
    QtPassSettings::setGeometry(saveGeometry());
×
1131
    QtPassSettings::setSavestate(saveState());
×
1132
    QtPassSettings::setMaximized(isMaximized());
×
1133
    if (!isMaximized()) {
×
1134
      QtPassSettings::setPos(pos());
×
UNCOV
1135
      QtPassSettings::setSize(size());
×
1136
    }
1137
    event->accept();
1138
  }
UNCOV
1139
}
×
1140

1141
/**
1142
 * @brief MainWindow::eventFilter filter out some events and focus the
1143
 * treeview
1144
 * @param obj
1145
 * @param event
1146
 * @return
1147
 */
1148
auto MainWindow::eventFilter(QObject *obj, QEvent *event) -> bool {
×
1149
  if (obj == ui->lineEdit && event->type() == QEvent::KeyPress) {
×
1150
    auto *key = dynamic_cast<QKeyEvent *>(event);
×
1151
    if (key != nullptr && key->key() == Qt::Key_Down) {
×
UNCOV
1152
      ui->treeView->setFocus();
×
1153
    }
1154
  }
UNCOV
1155
  return QObject::eventFilter(obj, event);
×
1156
}
1157

1158
/**
1159
 * @brief MainWindow::keyPressEvent did anyone press return, enter or escape?
1160
 * @param event
1161
 */
1162
void MainWindow::keyPressEvent(QKeyEvent *event) {
×
1163
  switch (event->key()) {
×
1164
  case Qt::Key_Delete:
×
1165
    onDelete();
×
1166
    break;
×
UNCOV
1167
  case Qt::Key_Return:
×
1168
  case Qt::Key_Enter:
1169
    if (proxyModel.rowCount() > 0) {
×
UNCOV
1170
      on_treeView_clicked(ui->treeView->currentIndex());
×
1171
    }
1172
    break;
1173
  case Qt::Key_Escape:
×
1174
    ui->lineEdit->clear();
×
UNCOV
1175
    break;
×
1176
  default:
1177
    break;
1178
  }
UNCOV
1179
}
×
1180

1181
/**
1182
 * @brief MainWindow::showContextMenu show us the (file or folder) context
1183
 * menu
1184
 * @param pos
1185
 */
1186
void MainWindow::showContextMenu(const QPoint &pos) {
×
UNCOV
1187
  QModelIndex index = ui->treeView->indexAt(pos);
×
1188
  bool selected = true;
1189
  if (!index.isValid()) {
1190
    ui->treeView->clearSelection();
×
1191
    ui->actionDelete->setEnabled(false);
×
1192
    ui->actionEdit->setEnabled(false);
×
UNCOV
1193
    currentDir = "";
×
1194
    selected = false;
1195
  }
1196

UNCOV
1197
  ui->treeView->setCurrentIndex(index);
×
1198

UNCOV
1199
  QPoint globalPos = ui->treeView->viewport()->mapToGlobal(pos);
×
1200

1201
  QFileInfo fileOrFolder =
UNCOV
1202
      model.fileInfo(proxyModel.mapToSource(ui->treeView->currentIndex()));
×
1203

1204
  QMenu contextMenu;
×
UNCOV
1205
  if (!selected || fileOrFolder.isDir()) {
×
1206
    QAction *openFolder =
1207
        contextMenu.addAction(tr("Open folder with file manager"));
×
1208
    QAction *addFolder = contextMenu.addAction(tr("Add folder"));
×
1209
    QAction *addPassword = contextMenu.addAction(tr("Add password"));
×
1210
    QAction *users = contextMenu.addAction(tr("Users"));
×
1211
    connect(openFolder, &QAction::triggered, this, &MainWindow::openFolder);
×
1212
    connect(addFolder, &QAction::triggered, this, &MainWindow::addFolder);
×
1213
    connect(addPassword, &QAction::triggered, this, &MainWindow::addPassword);
×
1214
    connect(users, &QAction::triggered, this, &MainWindow::onUsers);
×
1215
  } else if (fileOrFolder.isFile()) {
×
1216
    QAction *edit = contextMenu.addAction(tr("Edit"));
×
UNCOV
1217
    connect(edit, &QAction::triggered, this, &MainWindow::onEdit);
×
1218
  }
1219
  if (selected) {
×
1220
    contextMenu.addSeparator();
×
1221
    if (fileOrFolder.isDir()) {
×
1222
      QAction *renameFolder = contextMenu.addAction(tr("Rename folder"));
×
1223
      connect(renameFolder, &QAction::triggered, this,
×
1224
              &MainWindow::renameFolder);
×
1225
    } else if (fileOrFolder.isFile()) {
×
1226
      QAction *renamePassword = contextMenu.addAction(tr("Rename password"));
×
1227
      connect(renamePassword, &QAction::triggered, this,
×
UNCOV
1228
              &MainWindow::renamePassword);
×
1229
    }
1230
    QAction *deleteItem = contextMenu.addAction(tr("Delete"));
×
1231
    connect(deleteItem, &QAction::triggered, this, &MainWindow::onDelete);
×
UNCOV
1232
    if (fileOrFolder.isDir()) {
×
1233
      QString dirPath = QDir::cleanPath(
NEW
1234
          Util::getDir(ui->treeView->currentIndex(), false, model, proxyModel));
×
1235

NEW
1236
      QMenu *shareMenu = new QMenu(tr("Share"), &contextMenu);
×
NEW
1237
      contextMenu.addMenu(shareMenu);
×
1238

1239
      QString gpgIdPath = dirPath;
1240
      bool gpgIdExists = false;
NEW
1241
      QString storeRoot = QtPassSettings::getPassStore();
×
NEW
1242
      while (QDir::cleanPath(gpgIdPath) != QDir::cleanPath(storeRoot)) {
×
NEW
1243
        if (QFile(gpgIdPath + "/.gpg-id").exists()) {
×
1244
          gpgIdExists = true;
NEW
1245
          break;
×
1246
        }
NEW
1247
        QDir d(gpgIdPath);
×
NEW
1248
        if (!d.cdUp()) {
×
1249
          break;
1250
        }
1251
        gpgIdPath = d.path();
×
1252
      }
×
1253

NEW
1254
      bool gpgAvailable = !QtPassSettings::getGpgExecutable().isEmpty() ||
×
NEW
1255
                          !QtPassSettings::getPassExecutable().isEmpty();
×
1256

NEW
1257
      QAction *reencrypt = shareMenu->addAction(tr("Re-encrypt all passwords"));
×
NEW
1258
      reencrypt->setEnabled(gpgIdExists && gpgAvailable);
×
NEW
1259
      connect(reencrypt, &QAction::triggered, this,
×
NEW
1260
              [this, dirPath]() { reencryptPath(dirPath); });
×
1261

NEW
1262
      QAction *exportKey = shareMenu->addAction(tr("Export my public key..."));
×
NEW
1263
      exportKey->setEnabled(gpgAvailable);
×
NEW
1264
      connect(exportKey, &QAction::triggered, this,
×
NEW
1265
              &MainWindow::exportPublicKey);
×
1266

1267
      QAction *addRecipientAction =
UNCOV
1268
          shareMenu->addAction(tr("Add recipient..."));
×
1269
      addRecipientAction->setEnabled(gpgIdExists && gpgAvailable);
×
1270
      connect(addRecipientAction, &QAction::triggered, this,
×
UNCOV
1271
              [this, dirPath]() { addRecipient(dirPath); });
×
1272

UNCOV
1273
      QAction *shareHelp = shareMenu->addAction(tr("What is this?"));
×
UNCOV
1274
      connect(shareHelp, &QAction::triggered, this, &MainWindow::showShareHelp);
×
1275
    }
1276
  }
1277
  contextMenu.exec(globalPos);
×
1278
}
×
1279

1280
/**
1281
 * @brief MainWindow::showBrowserContextMenu show us the context menu in
1282
 * password window
1283
 * @param pos
1284
 */
UNCOV
1285
void MainWindow::showBrowserContextMenu(const QPoint &pos) {
×
UNCOV
1286
  QMenu *contextMenu = ui->textBrowser->createStandardContextMenu(pos);
×
UNCOV
1287
  QPoint globalPos = ui->textBrowser->viewport()->mapToGlobal(pos);
×
1288

UNCOV
1289
  contextMenu->exec(globalPos);
×
1290
  delete contextMenu;
×
UNCOV
1291
}
×
1292

1293
/**
1294
 * @brief MainWindow::openFolder open the folder in the default file manager
1295
 */
UNCOV
1296
void MainWindow::openFolder() {
×
1297
  QString dir =
UNCOV
1298
      Util::getDir(ui->treeView->currentIndex(), false, model, proxyModel);
×
1299

UNCOV
1300
  QString path = QDir::toNativeSeparators(dir);
×
UNCOV
1301
  QDesktopServices::openUrl(QUrl::fromLocalFile(path));
×
1302
}
×
1303

1304
/**
1305
 * @brief MainWindow::addFolder add a new folder to store passwords in
1306
 */
1307
void MainWindow::addFolder() {
×
1308
  bool ok;
1309
  QString dir =
1310
      Util::getDir(ui->treeView->currentIndex(), false, model, proxyModel);
×
1311
  QString newdir =
UNCOV
1312
      QInputDialog::getText(this, tr("New file"),
×
UNCOV
1313
                            tr("New Folder: \n(Will be placed in %1 )")
×
1314
                                .arg(QtPassSettings::getPassStore() +
×
1315
                                     Util::getDir(ui->treeView->currentIndex(),
×
1316
                                                  true, model, proxyModel)),
1317
                            QLineEdit::Normal, "", &ok);
×
UNCOV
1318
  if (!ok || newdir.isEmpty()) {
×
1319
    return;
1320
  }
1321
  newdir.prepend(dir);
1322
  if (!QDir().mkdir(newdir)) {
×
1323
    QMessageBox::warning(this, tr("Error"),
×
1324
                         tr("Failed to create folder: %1").arg(newdir));
×
1325
    return;
×
1326
  }
UNCOV
1327
  if (QtPassSettings::isAddGPGId(true)) {
×
1328
    QString gpgIdFile = newdir + "/.gpg-id";
×
1329
    QFile gpgId(gpgIdFile);
×
1330
    if (!gpgId.open(QIODevice::WriteOnly)) {
×
1331
      QMessageBox::warning(
×
UNCOV
1332
          this, tr("Error"),
×
UNCOV
1333
          tr("Failed to create .gpg-id file in: %1").arg(newdir));
×
1334
      return;
1335
    }
UNCOV
1336
    QList<UserInfo> users = QtPassSettings::getPass()->listKeys("", true);
×
UNCOV
1337
    for (const UserInfo &user : users) {
×
UNCOV
1338
      if (user.enabled) {
×
UNCOV
1339
        gpgId.write((user.key_id + "\n").toUtf8());
×
1340
      }
1341
    }
UNCOV
1342
    gpgId.close();
×
UNCOV
1343
  }
×
1344
}
1345

1346
/**
1347
 * @brief MainWindow::renameFolder rename an existing folder
1348
 */
1349
void MainWindow::renameFolder() {
×
1350
  bool ok;
1351
  QString srcDir = QDir::cleanPath(
UNCOV
1352
      Util::getDir(ui->treeView->currentIndex(), false, model, proxyModel));
×
1353
  QString srcDirName = QDir(srcDir).dirName();
×
1354
  QString newName =
UNCOV
1355
      QInputDialog::getText(this, tr("Rename file"), tr("Rename Folder To: "),
×
UNCOV
1356
                            QLineEdit::Normal, srcDirName, &ok);
×
UNCOV
1357
  if (!ok || newName.isEmpty()) {
×
1358
    return;
1359
  }
1360
  QString destDir = srcDir;
1361
  destDir.replace(srcDir.lastIndexOf(srcDirName), srcDirName.length(), newName);
×
1362
  QtPassSettings::getPass()->Move(srcDir, destDir);
×
1363
}
1364

1365
/**
1366
 * @brief MainWindow::editPassword read password and open edit window via
1367
 * MainWindow::onEdit()
1368
 */
UNCOV
1369
void MainWindow::editPassword(const QString &file) {
×
UNCOV
1370
  if (!file.isEmpty()) {
×
UNCOV
1371
    if (QtPassSettings::isUseGit() && QtPassSettings::isAutoPull()) {
×
UNCOV
1372
      onUpdate(true);
×
1373
    }
UNCOV
1374
    setPassword(file, false);
×
1375
  }
1376
}
×
1377

1378
/**
1379
 * @brief MainWindow::renamePassword rename an existing password
1380
 */
UNCOV
1381
void MainWindow::renamePassword() {
×
1382
  bool ok;
1383
  QString file = getFile(ui->treeView->currentIndex(), false);
×
1384
  QString filePath = QFileInfo(file).path();
×
1385
  QString fileName = QFileInfo(file).fileName();
×
UNCOV
1386
  if (fileName.endsWith(".gpg", Qt::CaseInsensitive)) {
×
UNCOV
1387
    fileName.chop(4);
×
1388
  }
1389

1390
  QString newName =
UNCOV
1391
      QInputDialog::getText(this, tr("Rename file"), tr("Rename File To: "),
×
UNCOV
1392
                            QLineEdit::Normal, fileName, &ok);
×
UNCOV
1393
  if (!ok || newName.isEmpty()) {
×
1394
    return;
1395
  }
1396
  QString newFile = QDir(filePath).filePath(newName);
×
1397
  QtPassSettings::getPass()->Move(file, newFile);
×
1398
}
1399

1400
/**
1401
 * @brief MainWindow::clearTemplateWidgets empty the template widget fields in
1402
 * the UI
1403
 */
UNCOV
1404
void MainWindow::clearTemplateWidgets() {
×
UNCOV
1405
  while (ui->gridLayout->count() > 0) {
×
UNCOV
1406
    QLayoutItem *item = ui->gridLayout->takeAt(0);
×
UNCOV
1407
    delete item->widget();
×
UNCOV
1408
    delete item;
×
1409
  }
UNCOV
1410
  ui->verticalLayoutPassword->setSpacing(0);
×
UNCOV
1411
}
×
1412

1413
/**
1414
 * @brief Copies the password of the selected file from the tree view to the
1415
 * clipboard.
1416
 * @example
1417
 * MainWindow::copyPasswordFromTreeview();
1418
 *
1419
 * @return void - This function does not return a value.
1420
 */
UNCOV
1421
void MainWindow::copyPasswordFromTreeview() {
×
1422
  QFileInfo fileOrFolder =
1423
      model.fileInfo(proxyModel.mapToSource(ui->treeView->currentIndex()));
×
1424

UNCOV
1425
  if (fileOrFolder.isFile()) {
×
1426
    QString file = getFile(ui->treeView->currentIndex(), true);
×
1427
    // Disconnect any previous connection to avoid accumulation
1428
    disconnect(QtPassSettings::getPass(), &Pass::finishedShow, this,
×
1429
               &MainWindow::passwordFromFileToClipboard);
1430
    connect(QtPassSettings::getPass(), &Pass::finishedShow, this,
×
1431
            &MainWindow::passwordFromFileToClipboard);
×
UNCOV
1432
    QtPassSettings::getPass()->Show(file);
×
1433
  }
UNCOV
1434
}
×
1435

UNCOV
1436
void MainWindow::passwordFromFileToClipboard(const QString &text) {
×
UNCOV
1437
  QStringList tokens = text.split('\n');
×
UNCOV
1438
  m_qtPass->copyTextToClipboard(tokens[0]);
×
1439
}
×
1440

1441
/**
1442
 * @brief MainWindow::addToGridLayout add a field to the template grid
1443
 * @param position
1444
 * @param field
1445
 * @param value
1446
 */
UNCOV
1447
void MainWindow::addToGridLayout(int position, const QString &field,
×
1448
                                 const QString &value) {
1449
  QString trimmedField = field.trimmed();
1450
  QString trimmedValue = value.trimmed();
1451

1452
  const QString buttonStyle =
1453
      "border-style: none; background: transparent; padding: 0; margin: 0; "
1454
      "icon-size: 16px; color: inherit;";
×
1455

1456
  // Combine the Copy button and the line edit in one widget
1457
  auto *frame = new QFrame();
×
UNCOV
1458
  QLayout *ly = new QHBoxLayout();
×
1459
  ly->setContentsMargins(5, 2, 2, 2);
×
1460
  ly->setSpacing(0);
×
UNCOV
1461
  frame->setLayout(ly);
×
UNCOV
1462
  if (QtPassSettings::getClipBoardType() != Enums::CLIPBOARD_NEVER) {
×
1463
    auto *fieldLabel = new QPushButtonWithClipboard(trimmedValue, this);
×
1464
    connect(fieldLabel, &QPushButtonWithClipboard::clicked, m_qtPass,
×
1465
            &QtPass::copyTextToClipboard);
×
1466

1467
    fieldLabel->setStyleSheet(buttonStyle);
×
1468
    frame->layout()->addWidget(fieldLabel);
×
1469
  }
1470

UNCOV
1471
  if (QtPassSettings::isUseQrencode()) {
×
UNCOV
1472
    auto *qrbutton = new QPushButtonAsQRCode(trimmedValue, this);
×
1473
    connect(qrbutton, &QPushButtonAsQRCode::clicked, m_qtPass,
×
UNCOV
1474
            &QtPass::showTextAsQRCode);
×
UNCOV
1475
    qrbutton->setStyleSheet(buttonStyle);
×
1476
    frame->layout()->addWidget(qrbutton);
×
1477
  }
1478

1479
  // set the echo mode to password, if the field is "password"
1480
  const QString lineStyle =
1481
      QtPassSettings::isUseMonospace()
×
1482
          ? "border-style: none; background: transparent; font-family: "
1483
            "monospace;"
1484
          : "border-style: none; background: transparent;";
×
1485

1486
  if (QtPassSettings::isHidePassword() && trimmedField == tr("Password")) {
×
1487
    auto *line = new QLineEdit();
×
1488
    line->setObjectName(trimmedField);
×
1489
    line->setText(trimmedValue);
×
1490
    line->setReadOnly(true);
×
UNCOV
1491
    line->setStyleSheet(lineStyle);
×
1492
    line->setContentsMargins(0, 0, 0, 0);
×
1493
    line->setEchoMode(QLineEdit::Password);
×
1494
    auto *showButton = new QPushButtonShowPassword(line, this);
×
1495
    showButton->setStyleSheet(buttonStyle);
×
1496
    showButton->setContentsMargins(0, 0, 0, 0);
×
1497
    frame->layout()->addWidget(showButton);
×
UNCOV
1498
    frame->layout()->addWidget(line);
×
1499
  } else {
1500
    auto *line = new QTextBrowser();
×
1501
    line->setOpenExternalLinks(true);
×
1502
    line->setOpenLinks(true);
×
1503
    line->setMaximumHeight(26);
×
1504
    line->setMinimumHeight(26);
×
1505
    line->setSizePolicy(
×
1506
        QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum));
UNCOV
1507
    line->setObjectName(trimmedField);
×
1508
    trimmedValue.replace(Util::protocolRegex(), R"(<a href="\1">\1</a>)");
×
UNCOV
1509
    line->setText(trimmedValue);
×
UNCOV
1510
    line->setReadOnly(true);
×
UNCOV
1511
    line->setStyleSheet(lineStyle);
×
1512
    line->setContentsMargins(0, 0, 0, 0);
×
1513
    frame->layout()->addWidget(line);
×
1514
  }
1515

UNCOV
1516
  frame->setStyleSheet(
×
1517
      ".QFrame{border: 1px solid lightgrey; border-radius: 5px;}");
1518

1519
  // set into the layout
UNCOV
1520
  ui->gridLayout->addWidget(new QLabel(trimmedField), position, 0);
×
UNCOV
1521
  ui->gridLayout->addWidget(frame, position, 1);
×
1522
}
×
1523

1524
/**
1525
 * @brief Displays message in status bar
1526
 *
1527
 * @param msg     text to be displayed
1528
 * @param timeout time for which msg shall be visible
1529
 */
1530
void MainWindow::showStatusMessage(const QString &msg, int timeout) {
×
1531
  ui->statusBar->showMessage(msg, timeout);
×
1532
}
×
1533

1534
/**
1535
 * @brief MainWindow::reencryptPath re-encrypt all passwords in a directory
1536
 * @param dir Directory path to re-encrypt
1537
 */
1538
void MainWindow::reencryptPath(const QString &dir) {
×
1539
  QDir checkDir(dir);
×
1540
  if (!checkDir.exists()) {
×
UNCOV
1541
    QMessageBox::critical(this, tr("Error"),
×
UNCOV
1542
                          tr("Directory does not exist: %1").arg(dir));
×
UNCOV
1543
    return;
×
1544
  }
1545

UNCOV
1546
  int ret = QMessageBox::question(
×
UNCOV
1547
      this, tr("Re-encrypt passwords"),
×
1548
      tr("Re-encrypt all passwords in %1?\n\n"
×
1549
         "This will re-encrypt ALL password files in this folder "
1550
         "using the current recipients defined in .gpg-id.\n\n"
1551
         "This may rewrite many files and cannot be undone easily.\n\n"
1552
         "Continue?")
1553
          .arg(QDir(dir).dirName()),
×
1554
      QMessageBox::Yes | QMessageBox::No);
1555

1556
  if (ret != QMessageBox::Yes)
×
1557
    return;
1558

1559
  // Prevent double execution - use same method as startReencryptPath
UNCOV
1560
  setUiElementsEnabled(false);
×
UNCOV
1561
  ui->treeView->setDisabled(true);
×
1562

1563
  QtPassSettings::getImitatePass()->reencryptPath(
×
1564
      QDir::cleanPath(QDir(dir).absolutePath()));
×
1565
}
×
1566

1567
/**
1568
 * @brief MainWindow::startReencryptPath disable ui elements and treeview
1569
 */
1570
void MainWindow::startReencryptPath() {
×
UNCOV
1571
  setUiElementsEnabled(false);
×
NEW
1572
  ui->treeView->setDisabled(true);
×
NEW
1573
}
×
1574

1575
/**
1576
 * @brief MainWindow::endReencryptPath re-enable ui elements
1577
 */
NEW
1578
void MainWindow::endReencryptPath() { setUiElementsEnabled(true); }
×
1579

1580
/**
1581
 * @brief MainWindow::exportPublicKey export current user's public key
1582
 * Shows instructions to export key via command line
1583
 * TODO: Wire to real gpg export via Executor - see issue #422
1584
 */
NEW
1585
void MainWindow::exportPublicKey() {
×
NEW
1586
  QString identity = QtPassSettings::getPassSigningKey();
×
NEW
1587
  if (identity.isEmpty()) {
×
NEW
1588
    identity = QStringLiteral("<your-key-id>").toHtmlEscaped();
×
1589
  }
NEW
1590
  QMessageBox::information(
×
NEW
1591
      this, tr("Export Public Key"),
×
NEW
1592
      tr("<h3>Export Your Public Key</h3>"
×
1593
         "<p>To export your public GPG key, run this in terminal:</p>"
1594
         "<pre>gpg --armor --export --output my_key.asc %1</pre>"
1595
         "<p>Then send the file to your teammates.</p>"
1596
         "<p>Your key ID: You can find it in QtPass Settings &gt; GPG "
1597
         "keys.</p>")
NEW
1598
          .arg(identity));
×
NEW
1599
}
×
1600

1601
/**
1602
 * @brief MainWindow::addRecipient add a new recipient's public key
1603
 * @param dir Directory path
1604
 */
NEW
1605
void MainWindow::addRecipient(const QString &dir) {
×
NEW
1606
  QString gpgIdPath = Pass::getGpgIdPath(dir);
×
NEW
1607
  QString message;
×
NEW
1608
  if (QFile::exists(gpgIdPath)) {
×
NEW
1609
    message = tr("<h3>Add Recipient</h3>"
×
1610
                 "<p>To add a teammate's public key:</p>"
1611
                 "<ol>"
1612
                 "<li>Save their public key as a .asc file</li>"
1613
                 "<li>Copy the key text</li>"
1614
                 "<li>Open %1</li>"
1615
                 "<li>Add the new key ID to the file</li>"
1616
                 "<li>Re-encrypt passwords to share with them</li>"
1617
                 "</ol>"
1618
                 "<p>Use the full fingerprint to ensure accuracy.</p>")
NEW
1619
                  .arg(gpgIdPath);
×
1620
  } else {
NEW
1621
    message = tr("<h3>Add Recipient</h3>"
×
1622
                 "<p>To add a teammate's public key:</p>"
1623
                 "<ol>"
1624
                 "<li>Save their public key as a .asc file</li>"
1625
                 "<li>Copy the key text</li>"
1626
                 "<li>Create %1 if it does not exist yet</li>"
1627
                 "<li>Add the new key ID to the file</li>"
1628
                 "<li>Re-encrypt passwords to share with them</li>"
1629
                 "</ol>"
1630
                 "<p>Use the full fingerprint to ensure accuracy.</p>")
NEW
1631
                  .arg(gpgIdPath);
×
1632
  }
NEW
1633
  QMessageBox::information(this, tr("Add Recipient"), message);
×
NEW
1634
}
×
1635

1636
/**
1637
 * @brief MainWindow::showShareHelp show help about GPG sharing
1638
 */
1639
void MainWindow::showShareHelp() {
×
UNCOV
1640
  QMessageBox::information(
×
1641
      this, tr("Sharing Passwords with GPG"),
×
UNCOV
1642
      tr("<h3>Sharing Passwords with GPG</h3>"
×
1643
         "<p>To share passwords with other users:</p>"
1644
         "<ol>"
1645
         "<li><b>Export your public key</b> and send it to teammates</li>"
1646
         "<li><b>Import teammates' public keys</b> to their own folders</li>"
1647
         "<li><b>Re-encrypt passwords</b> so all recipients can decrypt "
1648
         "them</li>"
1649
         "</ol>"
1650
         "<p>Only people who have a matching secret key can decrypt the "
1651
         "passwords.</p>"
1652
         "<p><b>Tip:</b> Use the same GPG key for all shared folders.</p>"
1653
         "<p>See the FAQ for more details.</p>"));
1654
}
×
1655

1656
void MainWindow::updateGitButtonVisibility() {
×
1657
  if (!QtPassSettings::isUseGit() ||
×
1658
      (QtPassSettings::getGitExecutable().isEmpty() &&
×
1659
       QtPassSettings::getPassExecutable().isEmpty())) {
×
1660
    enableGitButtons(false);
×
1661
  } else {
UNCOV
1662
    enableGitButtons(true);
×
1663
  }
UNCOV
1664
}
×
1665

UNCOV
1666
void MainWindow::updateOtpButtonVisibility() {
×
1667
#if defined(Q_OS_WIN) || defined(__APPLE__)
1668
  ui->actionOtp->setVisible(false);
1669
#endif
UNCOV
1670
  if (!QtPassSettings::isUseOtp()) {
×
UNCOV
1671
    ui->actionOtp->setEnabled(false);
×
1672
  } else {
UNCOV
1673
    ui->actionOtp->setEnabled(true);
×
1674
  }
UNCOV
1675
}
×
1676

1677
void MainWindow::updateGrepButtonVisibility() {
×
1678
  const bool enabled = QtPassSettings::isUseGrepSearch();
×
UNCOV
1679
  ui->grepButton->setVisible(enabled);
×
UNCOV
1680
  ui->grepCaseButton->setVisible(enabled);
×
UNCOV
1681
  if (!enabled && m_grepMode) {
×
UNCOV
1682
    ui->grepButton->setChecked(false);
×
1683
  }
UNCOV
1684
}
×
1685

UNCOV
1686
void MainWindow::enableGitButtons(const bool &state) {
×
1687
  // Following GNOME guidelines is preferable disable buttons instead of hide
UNCOV
1688
  ui->actionPush->setEnabled(state);
×
UNCOV
1689
  ui->actionUpdate->setEnabled(state);
×
UNCOV
1690
}
×
1691

1692
/**
1693
 * @brief MainWindow::critical critical message popup wrapper.
1694
 * @param title
1695
 * @param msg
1696
 */
UNCOV
1697
void MainWindow::critical(const QString &title, const QString &msg) {
×
UNCOV
1698
  QMessageBox::critical(this, title, msg);
×
UNCOV
1699
}
×
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