Line data Source code
1 : // SPDX-FileCopyrightText: 2016 Anne Jan Brouwer
2 : // SPDX-License-Identifier: GPL-3.0-or-later
3 : #include "mainwindow.h"
4 :
5 : #ifdef QT_DEBUG
6 : #include "debughelper.h"
7 : #endif
8 :
9 : #include "configdialog.h"
10 : #include "filecontent.h"
11 : #include "passworddialog.h"
12 : #include "qpushbuttonasqrcode.h"
13 : #include "qpushbuttonshowpassword.h"
14 : #include "qpushbuttonwithclipboard.h"
15 : #include "qtpass.h"
16 : #include "qtpasssettings.h"
17 : #include "trayicon.h"
18 : #include "ui_mainwindow.h"
19 : #include "usersdialog.h"
20 : #include "util.h"
21 : #include <QCloseEvent>
22 : #include <QDesktopServices>
23 : #include <QDialog>
24 : #include <QDirIterator>
25 : #include <QFileInfo>
26 : #include <QInputDialog>
27 : #include <QLabel>
28 : #include <QMenu>
29 : #include <QMessageBox>
30 : #include <QShortcut>
31 : #include <QTimer>
32 : #include <utility>
33 :
34 : /**
35 : * @brief MainWindow::MainWindow handles all of the main functionality and also
36 : * the main window.
37 : * @param searchText for searching from cli
38 : * @param parent pointer
39 : */
40 0 : MainWindow::MainWindow(const QString &searchText, QWidget *parent)
41 0 : : QMainWindow(parent), ui(new Ui::MainWindow), keygen(nullptr),
42 0 : tray(nullptr) {
43 : #ifdef __APPLE__
44 : // extra treatment for mac os
45 : // see http://doc.qt.io/qt-5/qkeysequence.html#qt_set_sequence_auto_mnemonic
46 : qt_set_sequence_auto_mnemonic(true);
47 : #endif
48 0 : ui->setupUi(this);
49 :
50 0 : m_qtPass = new QtPass(this);
51 :
52 : // register shortcut ctrl/cmd + Q to close the main window
53 0 : new QShortcut(QKeySequence(Qt::CTRL | Qt::Key_Q), this, SLOT(close()));
54 : // register shortcut ctrl/cmd + C to copy the currently selected password
55 0 : new QShortcut(QKeySequence(QKeySequence::StandardKey::Copy), this,
56 0 : SLOT(copyPasswordFromTreeview()));
57 :
58 0 : model.setNameFilters(QStringList() << "*.gpg");
59 0 : model.setNameFilterDisables(false);
60 :
61 : /*
62 : * I added this to solve Windows bug but now on GNU/Linux the main folder,
63 : * if hidden, disappear
64 : *
65 : * model.setFilter(QDir::NoDot);
66 : */
67 :
68 0 : QString passStore = QtPassSettings::getPassStore(Util::findPasswordStore());
69 :
70 0 : QModelIndex rootDir = model.setRootPath(passStore);
71 0 : model.fetchMore(rootDir);
72 :
73 0 : proxyModel.setModelAndStore(&model, passStore);
74 0 : selectionModel.reset(new QItemSelectionModel(&proxyModel));
75 :
76 0 : ui->treeView->setModel(&proxyModel);
77 0 : ui->treeView->setRootIndex(proxyModel.mapFromSource(rootDir));
78 0 : ui->treeView->setColumnHidden(1, true);
79 0 : ui->treeView->setColumnHidden(2, true);
80 0 : ui->treeView->setColumnHidden(3, true);
81 0 : ui->treeView->setHeaderHidden(true);
82 0 : ui->treeView->setIndentation(15);
83 0 : ui->treeView->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
84 0 : ui->treeView->setContextMenuPolicy(Qt::CustomContextMenu);
85 0 : ui->treeView->header()->setSectionResizeMode(0, QHeaderView::Stretch);
86 0 : ui->treeView->sortByColumn(0, Qt::AscendingOrder);
87 0 : connect(ui->treeView, &QWidget::customContextMenuRequested, this,
88 0 : &MainWindow::showContextMenu);
89 0 : connect(ui->treeView, &DeselectableTreeView::emptyClicked, this,
90 0 : &MainWindow::deselect);
91 :
92 0 : if (QtPassSettings::isUseMonospace()) {
93 0 : QFont monospace("Monospace");
94 0 : monospace.setStyleHint(QFont::Monospace);
95 0 : ui->textBrowser->setFont(monospace);
96 0 : }
97 0 : if (QtPassSettings::isNoLineWrapping()) {
98 0 : ui->textBrowser->setLineWrapMode(QTextBrowser::NoWrap);
99 : }
100 0 : ui->textBrowser->setOpenExternalLinks(true);
101 0 : ui->textBrowser->setContextMenuPolicy(Qt::CustomContextMenu);
102 0 : connect(ui->textBrowser, &QWidget::customContextMenuRequested, this,
103 0 : &MainWindow::showBrowserContextMenu);
104 :
105 0 : updateProfileBox();
106 :
107 0 : QtPassSettings::getPass()->updateEnv();
108 0 : clearPanelTimer.setInterval(MS_PER_SECOND *
109 0 : QtPassSettings::getAutoclearPanelSeconds());
110 0 : clearPanelTimer.setSingleShot(true);
111 0 : connect(&clearPanelTimer, &QTimer::timeout, this,
112 0 : [this]() -> void { clearPanel(); });
113 :
114 0 : searchTimer.setInterval(350);
115 0 : searchTimer.setSingleShot(true);
116 :
117 0 : connect(&searchTimer, &QTimer::timeout, this, &MainWindow::onTimeoutSearch);
118 :
119 0 : initToolBarButtons();
120 0 : initStatusBar();
121 :
122 0 : ui->lineEdit->setClearButtonEnabled(true);
123 :
124 0 : setUiElementsEnabled(true);
125 :
126 : QTimer::singleShot(10, this, SLOT(focusInput()));
127 :
128 0 : ui->lineEdit->setText(searchText);
129 :
130 0 : if (!m_qtPass->init()) {
131 : // no working config so this should just quit
132 0 : QApplication::quit();
133 : }
134 0 : }
135 :
136 0 : 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 0 : void MainWindow::focusInput() {
145 0 : ui->lineEdit->selectAll();
146 0 : ui->lineEdit->setFocus();
147 0 : }
148 :
149 : /**
150 : * @brief MainWindow::changeEvent sets focus to the search box
151 : * @param event
152 : */
153 0 : void MainWindow::changeEvent(QEvent *event) {
154 0 : QWidget::changeEvent(event);
155 0 : if (event->type() == QEvent::ActivationChange) {
156 0 : if (isActiveWindow()) {
157 0 : focusInput();
158 : }
159 : }
160 0 : }
161 :
162 : /**
163 : * @brief MainWindow::initToolBarButtons init main ToolBar and connect actions
164 : */
165 0 : void MainWindow::initToolBarButtons() {
166 0 : connect(ui->actionAddPassword, &QAction::triggered, this,
167 0 : &MainWindow::addPassword);
168 0 : connect(ui->actionAddFolder, &QAction::triggered, this,
169 0 : &MainWindow::addFolder);
170 0 : connect(ui->actionEdit, &QAction::triggered, this, &MainWindow::onEdit);
171 0 : connect(ui->actionDelete, &QAction::triggered, this, &MainWindow::onDelete);
172 0 : connect(ui->actionPush, &QAction::triggered, this, &MainWindow::onPush);
173 0 : connect(ui->actionUpdate, &QAction::triggered, this, &MainWindow::onUpdate);
174 0 : connect(ui->actionUsers, &QAction::triggered, this, &MainWindow::onUsers);
175 0 : connect(ui->actionConfig, &QAction::triggered, this, &MainWindow::onConfig);
176 0 : connect(ui->actionOtp, &QAction::triggered, this, &MainWindow::onOtp);
177 :
178 0 : ui->actionAddPassword->setIcon(
179 0 : QIcon::fromTheme("document-new", QIcon(":/icons/document-new.svg")));
180 0 : ui->actionAddFolder->setIcon(
181 0 : QIcon::fromTheme("folder-new", QIcon(":/icons/folder-new.svg")));
182 0 : ui->actionEdit->setIcon(QIcon::fromTheme(
183 0 : "document-properties", QIcon(":/icons/document-properties.svg")));
184 0 : ui->actionDelete->setIcon(
185 0 : QIcon::fromTheme("edit-delete", QIcon(":/icons/edit-delete.svg")));
186 0 : ui->actionPush->setIcon(
187 0 : QIcon::fromTheme("go-up", QIcon(":/icons/go-top.svg")));
188 0 : ui->actionUpdate->setIcon(
189 0 : QIcon::fromTheme("go-down", QIcon(":/icons/go-bottom.svg")));
190 0 : ui->actionUsers->setIcon(QIcon::fromTheme(
191 0 : "x-office-address-book", QIcon(":/icons/x-office-address-book.svg")));
192 0 : ui->actionConfig->setIcon(QIcon::fromTheme(
193 0 : "applications-system", QIcon(":/icons/applications-system.svg")));
194 0 : }
195 :
196 : /**
197 : * @brief MainWindow::initStatusBar init statusBar with default message and logo
198 : */
199 0 : void MainWindow::initStatusBar() {
200 0 : ui->statusBar->showMessage(tr("Welcome to QtPass %1").arg(VERSION), 2000);
201 :
202 0 : QPixmap logo = QPixmap::fromImage(QImage(":/artwork/icon.svg"))
203 0 : .scaledToHeight(statusBar()->height());
204 0 : auto *logoApp = new QLabel(statusBar());
205 0 : logoApp->setPixmap(logo);
206 0 : statusBar()->addPermanentWidget(logoApp);
207 0 : }
208 :
209 0 : auto MainWindow::getCurrentTreeViewIndex() -> QModelIndex {
210 0 : return ui->treeView->currentIndex();
211 : }
212 :
213 0 : void MainWindow::cleanKeygenDialog() {
214 0 : this->keygen->close();
215 0 : this->keygen = nullptr;
216 0 : }
217 :
218 0 : void MainWindow::flashText(const QString &text, const bool isError,
219 : const bool isHtml) {
220 0 : if (isError) {
221 0 : ui->textBrowser->setTextColor(Qt::red);
222 : }
223 :
224 0 : if (isHtml) {
225 : QString _text = text;
226 0 : if (!ui->textBrowser->toPlainText().isEmpty()) {
227 0 : _text = ui->textBrowser->toHtml() + _text;
228 : }
229 0 : ui->textBrowser->setHtml(_text);
230 : } else {
231 0 : ui->textBrowser->setText(text);
232 0 : ui->textBrowser->setTextColor(Qt::black);
233 : }
234 0 : }
235 :
236 : /**
237 : * @brief MainWindow::config pops up the configuration screen and handles all
238 : * inter-window communication
239 : */
240 0 : void MainWindow::config() {
241 0 : QScopedPointer<ConfigDialog> d(new ConfigDialog(this));
242 0 : d->setModal(true);
243 : // Automatically default to pass if it's available
244 0 : if (m_qtPass->isFreshStart() &&
245 0 : QFile(QtPassSettings::getPassExecutable()).exists()) {
246 0 : QtPassSettings::setUsePass(true);
247 : }
248 :
249 0 : if (m_qtPass->isFreshStart()) {
250 0 : d->wizard(); // does shit
251 : }
252 0 : if (d->exec()) {
253 0 : if (d->result() == QDialog::Accepted) {
254 : // Update the textBrowser font
255 0 : if (QtPassSettings::isUseMonospace()) {
256 0 : QFont monospace("Monospace");
257 0 : monospace.setStyleHint(QFont::Monospace);
258 0 : ui->textBrowser->setFont(monospace);
259 0 : } else {
260 0 : ui->textBrowser->setFont(QFont());
261 : }
262 : // Update the textBrowser line wrap mode
263 0 : if (QtPassSettings::isNoLineWrapping()) {
264 0 : ui->textBrowser->setLineWrapMode(QTextBrowser::NoWrap);
265 : } else {
266 0 : ui->textBrowser->setLineWrapMode(QTextBrowser::WidgetWidth);
267 : }
268 :
269 0 : if (QtPassSettings::isAlwaysOnTop()) {
270 : Qt::WindowFlags flags = windowFlags();
271 0 : this->setWindowFlags(flags | Qt::WindowStaysOnTopHint);
272 : } else {
273 0 : this->setWindowFlags(Qt::Window);
274 : }
275 0 : this->show();
276 :
277 0 : updateProfileBox();
278 0 : ui->treeView->setRootIndex(proxyModel.mapFromSource(
279 0 : model.setRootPath(QtPassSettings::getPassStore())));
280 :
281 0 : if (m_qtPass->isFreshStart() && Util::checkConfig()) {
282 0 : config();
283 : }
284 0 : QtPassSettings::getPass()->updateEnv();
285 0 : clearPanelTimer.setInterval(MS_PER_SECOND *
286 0 : QtPassSettings::getAutoclearPanelSeconds());
287 0 : m_qtPass->setClipboardTimer();
288 :
289 0 : updateGitButtonVisibility();
290 0 : updateOtpButtonVisibility();
291 0 : if (QtPassSettings::isUseTrayIcon() && tray == nullptr) {
292 0 : initTrayIcon();
293 0 : } else if (!QtPassSettings::isUseTrayIcon() && tray != nullptr) {
294 0 : destroyTrayIcon();
295 : }
296 : }
297 :
298 0 : m_qtPass->setFreshStart(false);
299 : }
300 0 : }
301 :
302 : /**
303 : * @brief MainWindow::onUpdate do a git pull
304 : */
305 0 : void MainWindow::onUpdate(bool block) {
306 0 : ui->statusBar->showMessage(tr("Updating password-store"), 2000);
307 0 : if (block) {
308 0 : QtPassSettings::getPass()->GitPull_b();
309 : } else {
310 0 : QtPassSettings::getPass()->GitPull();
311 : }
312 0 : }
313 :
314 : /**
315 : * @brief MainWindow::onPush do a git push
316 : */
317 0 : void MainWindow::onPush() {
318 0 : if (QtPassSettings::isUseGit()) {
319 0 : ui->statusBar->showMessage(tr("Updating password-store"), 2000);
320 0 : QtPassSettings::getPass()->GitPush();
321 : }
322 0 : }
323 :
324 : /**
325 : * @brief MainWindow::getFile get the selected file path
326 : * @param index
327 : * @param forPass returns relative path without '.gpg' extension
328 : * @return path
329 : * @return
330 : */
331 0 : auto MainWindow::getFile(const QModelIndex &index, bool forPass) -> QString {
332 0 : if (!index.isValid() ||
333 0 : !model.fileInfo(proxyModel.mapToSource(index)).isFile()) {
334 0 : return {};
335 : }
336 0 : QString filePath = model.filePath(proxyModel.mapToSource(index));
337 0 : if (forPass) {
338 0 : filePath = QDir(QtPassSettings::getPassStore()).relativeFilePath(filePath);
339 0 : filePath.replace(Util::endsWithGpg(), "");
340 : }
341 : return filePath;
342 : }
343 :
344 : /**
345 : * @brief MainWindow::on_treeView_clicked read the selected password file
346 : * @param index
347 : */
348 0 : void MainWindow::on_treeView_clicked(const QModelIndex &index) {
349 0 : bool cleared = ui->treeView->currentIndex().flags() == Qt::NoItemFlags;
350 : currentDir =
351 0 : Util::getDir(ui->treeView->currentIndex(), false, model, proxyModel);
352 : // TODO(bezet): "Could not decrypt";
353 0 : m_qtPass->clearClippedText();
354 0 : QString file = getFile(index, true);
355 0 : ui->passwordName->setText(getFile(index, true));
356 0 : if (!file.isEmpty() && !cleared) {
357 0 : QtPassSettings::getPass()->Show(file);
358 : } else {
359 0 : clearPanel(false);
360 0 : ui->actionEdit->setEnabled(false);
361 0 : ui->actionDelete->setEnabled(true);
362 : }
363 0 : }
364 :
365 : /**
366 : * @brief MainWindow::on_treeView_doubleClicked when doubleclicked on
367 : * TreeViewItem, open the edit Window
368 : * @param index
369 : */
370 0 : void MainWindow::on_treeView_doubleClicked(const QModelIndex &index) {
371 : QFileInfo fileOrFolder =
372 0 : model.fileInfo(proxyModel.mapToSource(ui->treeView->currentIndex()));
373 :
374 0 : if (fileOrFolder.isFile()) {
375 0 : editPassword(getFile(index, true));
376 : }
377 0 : }
378 :
379 : /**
380 : * @brief MainWindow::deselect clear the selection, password and copy buffer
381 : */
382 0 : void MainWindow::deselect() {
383 0 : currentDir = "";
384 0 : m_qtPass->clearClipboard();
385 0 : ui->treeView->clearSelection();
386 0 : ui->actionEdit->setEnabled(false);
387 0 : ui->actionDelete->setEnabled(false);
388 0 : ui->passwordName->setText("");
389 0 : clearPanel(false);
390 0 : }
391 :
392 0 : void MainWindow::executeWrapperStarted() {
393 0 : clearTemplateWidgets();
394 0 : ui->textBrowser->clear();
395 0 : setUiElementsEnabled(false);
396 0 : clearPanelTimer.stop();
397 0 : }
398 :
399 0 : void MainWindow::passShowHandler(const QString &p_output) {
400 0 : QStringList templ = QtPassSettings::isUseTemplate()
401 0 : ? QtPassSettings::getPassTemplate().split("\n")
402 0 : : QStringList();
403 : bool allFields =
404 0 : QtPassSettings::isUseTemplate() && QtPassSettings::isTemplateAllFields();
405 0 : FileContent fileContent = FileContent::parse(p_output, templ, allFields);
406 : QString output = p_output;
407 0 : QString password = fileContent.getPassword();
408 :
409 : // set clipped text
410 0 : m_qtPass->setClippedText(password, p_output);
411 :
412 : // first clear the current view:
413 0 : clearTemplateWidgets();
414 :
415 : // show what is needed:
416 0 : if (QtPassSettings::isHideContent()) {
417 0 : output = "***" + tr("Content hidden") + "***";
418 0 : } else if (!QtPassSettings::isDisplayAsIs()) {
419 0 : if (!password.isEmpty()) {
420 : // set the password, it is hidden if needed in addToGridLayout
421 0 : addToGridLayout(0, tr("Password"), password);
422 : }
423 :
424 0 : NamedValues namedValues = fileContent.getNamedValues();
425 0 : for (int j = 0; j < namedValues.length(); ++j) {
426 : const NamedValue &nv = namedValues.at(j);
427 0 : addToGridLayout(j + 1, nv.name, nv.value);
428 : }
429 0 : if (ui->gridLayout->count() == 0) {
430 0 : ui->verticalLayoutPassword->setSpacing(0);
431 : } else {
432 0 : ui->verticalLayoutPassword->setSpacing(6);
433 : }
434 :
435 0 : output = fileContent.getRemainingDataForDisplay();
436 : }
437 :
438 0 : if (QtPassSettings::isUseAutoclearPanel()) {
439 0 : clearPanelTimer.start();
440 : }
441 :
442 0 : emit passShowHandlerFinished(output);
443 0 : setUiElementsEnabled(true);
444 0 : }
445 :
446 0 : void MainWindow::passOtpHandler(const QString &p_output) {
447 0 : if (!p_output.isEmpty()) {
448 0 : addToGridLayout(ui->gridLayout->count() + 1, tr("OTP Code"), p_output);
449 0 : m_qtPass->copyTextToClipboard(p_output);
450 0 : showStatusMessage(tr("OTP code copied to clipboard"));
451 : } else {
452 0 : flashText(tr("No OTP code found in this password entry"), true);
453 : }
454 0 : if (QtPassSettings::isUseAutoclearPanel()) {
455 0 : clearPanelTimer.start();
456 : }
457 0 : setUiElementsEnabled(true);
458 0 : }
459 :
460 : /**
461 : * @brief MainWindow::clearPanel hide the information from shoulder surfers
462 : */
463 0 : void MainWindow::clearPanel(bool notify) {
464 0 : while (ui->gridLayout->count() > 0) {
465 0 : QLayoutItem *item = ui->gridLayout->takeAt(0);
466 0 : delete item->widget();
467 0 : delete item;
468 : }
469 0 : if (notify) {
470 0 : QString output = "***" + tr("Password and Content hidden") + "***";
471 0 : ui->textBrowser->setHtml(output);
472 : } else {
473 0 : ui->textBrowser->setHtml("");
474 : }
475 0 : }
476 :
477 : /**
478 : * @brief MainWindow::setUiElementsEnabled enable or disable the relevant UI
479 : * elements
480 : * @param state
481 : */
482 0 : void MainWindow::setUiElementsEnabled(bool state) {
483 0 : ui->treeView->setEnabled(state);
484 0 : ui->lineEdit->setEnabled(state);
485 0 : ui->lineEdit->installEventFilter(this);
486 0 : ui->actionAddPassword->setEnabled(state);
487 0 : ui->actionAddFolder->setEnabled(state);
488 0 : ui->actionUsers->setEnabled(state);
489 0 : ui->actionConfig->setEnabled(state);
490 : // is a file selected?
491 0 : state &= ui->treeView->currentIndex().isValid();
492 0 : ui->actionDelete->setEnabled(state);
493 0 : ui->actionEdit->setEnabled(state);
494 0 : updateGitButtonVisibility();
495 0 : updateOtpButtonVisibility();
496 0 : }
497 :
498 0 : void MainWindow::restoreWindow() {
499 0 : QByteArray geometry = QtPassSettings::getGeometry(saveGeometry());
500 0 : restoreGeometry(geometry);
501 0 : QByteArray savestate = QtPassSettings::getSavestate(saveState());
502 0 : restoreState(savestate);
503 0 : QPoint position = QtPassSettings::getPos(pos());
504 0 : move(position);
505 0 : QSize newSize = QtPassSettings::getSize(size());
506 0 : resize(newSize);
507 0 : if (QtPassSettings::isMaximized(isMaximized())) {
508 0 : showMaximized();
509 : }
510 :
511 0 : if (QtPassSettings::isAlwaysOnTop()) {
512 : Qt::WindowFlags flags = windowFlags();
513 0 : setWindowFlags(flags | Qt::WindowStaysOnTopHint);
514 0 : show();
515 : }
516 :
517 0 : if (QtPassSettings::isUseTrayIcon() && tray == nullptr) {
518 0 : initTrayIcon();
519 0 : if (QtPassSettings::isStartMinimized()) {
520 : // since we are still in constructor, can't directly hide
521 0 : QTimer::singleShot(10, this, SLOT(hide()));
522 : }
523 0 : } else if (!QtPassSettings::isUseTrayIcon() && tray != nullptr) {
524 0 : destroyTrayIcon();
525 : }
526 0 : }
527 :
528 : /**
529 : * @brief MainWindow::on_configButton_clicked run Mainwindow::config
530 : */
531 0 : void MainWindow::onConfig() { config(); }
532 :
533 : /**
534 : * @brief Executes when the string in the search box changes, collapses the
535 : * TreeView
536 : * @param arg1
537 : */
538 0 : void MainWindow::on_lineEdit_textChanged(const QString &arg1) {
539 0 : ui->statusBar->showMessage(tr("Looking for: %1").arg(arg1), 1000);
540 0 : ui->treeView->expandAll();
541 0 : clearPanel(false);
542 0 : ui->passwordName->setText("");
543 0 : ui->actionEdit->setEnabled(false);
544 0 : ui->actionDelete->setEnabled(false);
545 0 : searchTimer.start();
546 0 : }
547 :
548 : /**
549 : * @brief MainWindow::onTimeoutSearch Fired when search is finished or too much
550 : * time from two keypresses is elapsed
551 : */
552 0 : void MainWindow::onTimeoutSearch() {
553 0 : QString query = ui->lineEdit->text();
554 :
555 0 : if (query.isEmpty()) {
556 0 : ui->treeView->collapseAll();
557 0 : deselect();
558 : }
559 :
560 0 : query.replace(QStringLiteral(" "), ".*");
561 0 : QRegularExpression regExp(query, QRegularExpression::CaseInsensitiveOption);
562 0 : proxyModel.setFilterRegularExpression(regExp);
563 0 : ui->treeView->setRootIndex(proxyModel.mapFromSource(
564 0 : model.setRootPath(QtPassSettings::getPassStore())));
565 :
566 0 : if (proxyModel.rowCount() > 0 && !query.isEmpty()) {
567 0 : selectFirstFile();
568 : } else {
569 0 : ui->actionEdit->setEnabled(false);
570 0 : ui->actionDelete->setEnabled(false);
571 : }
572 0 : }
573 :
574 : /**
575 : * @brief MainWindow::on_lineEdit_returnPressed get searching
576 : *
577 : * Select the first possible file in the tree
578 : */
579 0 : void MainWindow::on_lineEdit_returnPressed() {
580 : #ifdef QT_DEBUG
581 : dbg() << "on_lineEdit_returnPressed" << proxyModel.rowCount();
582 : #endif
583 :
584 0 : if (proxyModel.rowCount() > 0) {
585 0 : selectFirstFile();
586 0 : on_treeView_clicked(ui->treeView->currentIndex());
587 : }
588 0 : }
589 :
590 : /**
591 : * @brief MainWindow::selectFirstFile select the first possible file in the
592 : * tree
593 : */
594 0 : void MainWindow::selectFirstFile() {
595 0 : QModelIndex index = proxyModel.mapFromSource(
596 0 : model.setRootPath(QtPassSettings::getPassStore()));
597 0 : index = firstFile(index);
598 0 : ui->treeView->setCurrentIndex(index);
599 0 : }
600 :
601 : /**
602 : * @brief MainWindow::firstFile return location of first possible file
603 : * @param parentIndex
604 : * @return QModelIndex
605 : */
606 0 : auto MainWindow::firstFile(QModelIndex parentIndex) -> QModelIndex {
607 0 : QModelIndex index = parentIndex;
608 0 : int numRows = proxyModel.rowCount(parentIndex);
609 0 : for (int row = 0; row < numRows; ++row) {
610 0 : index = proxyModel.index(row, 0, parentIndex);
611 0 : if (model.fileInfo(proxyModel.mapToSource(index)).isFile()) {
612 0 : return index;
613 : }
614 0 : if (proxyModel.hasChildren(index)) {
615 0 : return firstFile(index);
616 : }
617 : }
618 0 : return index;
619 : }
620 :
621 : /**
622 : * @brief MainWindow::setPassword open passworddialog
623 : * @param file which pgp file
624 : * @param isNew insert (not update)
625 : */
626 0 : void MainWindow::setPassword(const QString &file, bool isNew) {
627 0 : PasswordDialog d(file, isNew, this);
628 :
629 0 : if (!d.exec()) {
630 0 : ui->treeView->setFocus();
631 : }
632 0 : }
633 :
634 : /**
635 : * @brief MainWindow::addPassword add a new password by showing a
636 : * number of dialogs.
637 : */
638 0 : void MainWindow::addPassword() {
639 : bool ok;
640 : QString dir =
641 0 : Util::getDir(ui->treeView->currentIndex(), true, model, proxyModel);
642 : QString file =
643 0 : QInputDialog::getText(this, tr("New file"),
644 0 : tr("New password file: \n(Will be placed in %1 )")
645 0 : .arg(QtPassSettings::getPassStore() +
646 0 : Util::getDir(ui->treeView->currentIndex(),
647 0 : true, model, proxyModel)),
648 0 : QLineEdit::Normal, "", &ok);
649 0 : if (!ok || file.isEmpty()) {
650 : return;
651 : }
652 0 : file = dir + file;
653 0 : setPassword(file);
654 : }
655 :
656 : /**
657 : * @brief MainWindow::onDelete remove password, if you are
658 : * sure.
659 : */
660 0 : void MainWindow::onDelete() {
661 0 : QModelIndex currentIndex = ui->treeView->currentIndex();
662 : if (!currentIndex.isValid()) {
663 : // This fixes https://github.com/IJHack/QtPass/issues/556
664 : // Otherwise the entire password directory would be deleted if
665 : // nothing is selected in the tree view.
666 0 : return;
667 : }
668 :
669 : QFileInfo fileOrFolder =
670 0 : model.fileInfo(proxyModel.mapToSource(ui->treeView->currentIndex()));
671 0 : QString file = "";
672 : bool isDir = false;
673 :
674 0 : if (fileOrFolder.isFile()) {
675 0 : file = getFile(ui->treeView->currentIndex(), true);
676 : } else {
677 0 : file = Util::getDir(ui->treeView->currentIndex(), true, model, proxyModel);
678 : isDir = true;
679 : }
680 :
681 : QString dirMessage = tr(" and the whole content?");
682 0 : if (isDir) {
683 0 : QDirIterator it(model.rootPath() + QDir::separator() + file,
684 0 : QDirIterator::Subdirectories);
685 : bool okDir = true;
686 0 : while (it.hasNext() && okDir) {
687 0 : it.next();
688 0 : if (QFileInfo(it.filePath()).isFile()) {
689 0 : if (QFileInfo(it.filePath()).suffix() != "gpg") {
690 : okDir = false;
691 0 : dirMessage = tr(" and the whole content? <br><strong>Attention: "
692 : "there are unexpected files in the given folder, "
693 : "check them before continue.</strong>");
694 : }
695 : }
696 : }
697 0 : }
698 :
699 0 : if (QMessageBox::question(
700 0 : this, isDir ? tr("Delete folder?") : tr("Delete password?"),
701 0 : tr("Are you sure you want to delete %1%2?")
702 0 : .arg(QDir::separator() + file, isDir ? dirMessage : "?"),
703 : QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) {
704 : return;
705 : }
706 :
707 0 : QtPassSettings::getPass()->Remove(file, isDir);
708 0 : }
709 :
710 : /**
711 : * @brief MainWindow::onOTP try and generate (selected) OTP code.
712 : */
713 0 : void MainWindow::onOtp() {
714 0 : QString file = getFile(ui->treeView->currentIndex(), true);
715 0 : if (!file.isEmpty()) {
716 0 : if (QtPassSettings::isUseOtp()) {
717 0 : setUiElementsEnabled(false);
718 0 : QtPassSettings::getPass()->OtpGenerate(file);
719 : }
720 : } else {
721 0 : flashText(tr("No password selected for OTP generation"), true);
722 : }
723 0 : }
724 :
725 : /**
726 : * @brief MainWindow::onEdit try and edit (selected) password.
727 : */
728 0 : void MainWindow::onEdit() {
729 0 : QString file = getFile(ui->treeView->currentIndex(), true);
730 0 : editPassword(file);
731 0 : }
732 :
733 : /**
734 : * @brief MainWindow::userDialog see MainWindow::onUsers()
735 : * @param dir folder to edit users for.
736 : */
737 0 : void MainWindow::userDialog(const QString &dir) {
738 0 : if (!dir.isEmpty()) {
739 0 : currentDir = dir;
740 : }
741 0 : onUsers();
742 0 : }
743 :
744 : /**
745 : * @brief MainWindow::onUsers edit users for the current
746 : * folder,
747 : * gets lists and opens UserDialog.
748 : */
749 0 : void MainWindow::onUsers() {
750 : QString dir =
751 : currentDir.isEmpty()
752 0 : ? Util::getDir(ui->treeView->currentIndex(), false, model, proxyModel)
753 0 : : currentDir;
754 :
755 0 : UsersDialog d(dir, this);
756 0 : if (!d.exec()) {
757 0 : ui->treeView->setFocus();
758 : }
759 0 : }
760 :
761 : /**
762 : * @brief MainWindow::messageAvailable we have some text/message/search to do.
763 : * @param message
764 : */
765 0 : void MainWindow::messageAvailable(const QString &message) {
766 0 : if (message.isEmpty()) {
767 0 : focusInput();
768 : } else {
769 0 : ui->treeView->expandAll();
770 0 : ui->lineEdit->setText(message);
771 0 : on_lineEdit_returnPressed();
772 : }
773 0 : show();
774 0 : raise();
775 0 : }
776 :
777 : /**
778 : * @brief MainWindow::generateKeyPair internal gpg keypair generator . .
779 : * @param batch
780 : * @param keygenWindow
781 : */
782 0 : void MainWindow::generateKeyPair(const QString &batch, QDialog *keygenWindow) {
783 0 : keygen = keygenWindow;
784 0 : emit generateGPGKeyPair(batch);
785 0 : }
786 :
787 : /**
788 : * @brief MainWindow::updateProfileBox update the list of profiles, optionally
789 : * select a more appropriate one to view too
790 : */
791 0 : void MainWindow::updateProfileBox() {
792 : QHash<QString, QHash<QString, QString>> profiles =
793 0 : QtPassSettings::getProfiles();
794 :
795 : if (profiles.isEmpty()) {
796 0 : ui->profileWidget->hide();
797 : } else {
798 0 : ui->profileWidget->show();
799 0 : ui->profileBox->setEnabled(profiles.size() > 1);
800 0 : ui->profileBox->clear();
801 0 : QHashIterator<QString, QHash<QString, QString>> i(profiles);
802 0 : while (i.hasNext()) {
803 : i.next();
804 0 : if (!i.key().isEmpty()) {
805 0 : ui->profileBox->addItem(i.key());
806 : }
807 : }
808 0 : ui->profileBox->model()->sort(0);
809 : }
810 0 : int index = ui->profileBox->findText(QtPassSettings::getProfile());
811 0 : if (index != -1) { // -1 for not found
812 0 : ui->profileBox->setCurrentIndex(index);
813 : }
814 0 : }
815 :
816 : /**
817 : * @brief MainWindow::on_profileBox_currentIndexChanged make sure we show the
818 : * correct "profile"
819 : * @param name
820 : */
821 : #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
822 : void MainWindow::on_profileBox_currentIndexChanged(QString name) {
823 : #else
824 0 : void MainWindow::on_profileBox_currentTextChanged(const QString &name) {
825 : #endif
826 0 : if (m_qtPass->isFreshStart() || name == QtPassSettings::getProfile()) {
827 : return;
828 : }
829 :
830 0 : ui->lineEdit->clear();
831 :
832 0 : QtPassSettings::setProfile(name);
833 :
834 0 : QtPassSettings::setPassStore(
835 0 : QtPassSettings::getProfiles().value(name).value("path"));
836 0 : QtPassSettings::setPassSigningKey(
837 0 : QtPassSettings::getProfiles().value(name).value("signingKey"));
838 0 : ui->statusBar->showMessage(tr("Profile changed to %1").arg(name), 2000);
839 :
840 0 : QtPassSettings::getPass()->updateEnv();
841 :
842 0 : ui->treeView->selectionModel()->clear();
843 0 : ui->treeView->setRootIndex(proxyModel.mapFromSource(
844 0 : model.setRootPath(QtPassSettings::getPassStore())));
845 :
846 0 : ui->actionEdit->setEnabled(false);
847 0 : ui->actionDelete->setEnabled(false);
848 : }
849 :
850 : /**
851 : * @brief MainWindow::initTrayIcon show a nice tray icon on systems that
852 : * support
853 : * it
854 : */
855 0 : void MainWindow::initTrayIcon() {
856 0 : this->tray = new TrayIcon(this);
857 : // Setup tray icon
858 :
859 : if (tray == nullptr) {
860 : #ifdef QT_DEBUG
861 : dbg() << "Allocating tray icon failed.";
862 : #endif
863 : }
864 :
865 0 : if (!tray->getIsAllocated()) {
866 0 : destroyTrayIcon();
867 : }
868 0 : }
869 :
870 : /**
871 : * @brief MainWindow::destroyTrayIcon remove that pesky tray icon
872 : */
873 0 : void MainWindow::destroyTrayIcon() {
874 0 : delete this->tray;
875 0 : tray = nullptr;
876 0 : }
877 :
878 : /**
879 : * @brief MainWindow::closeEvent hide or quit
880 : * @param event
881 : */
882 0 : void MainWindow::closeEvent(QCloseEvent *event) {
883 0 : if (QtPassSettings::isHideOnClose()) {
884 0 : this->hide();
885 : event->ignore();
886 : } else {
887 0 : m_qtPass->clearClipboard();
888 :
889 0 : QtPassSettings::setGeometry(saveGeometry());
890 0 : QtPassSettings::setSavestate(saveState());
891 0 : QtPassSettings::setMaximized(isMaximized());
892 0 : if (!isMaximized()) {
893 0 : QtPassSettings::setPos(pos());
894 0 : QtPassSettings::setSize(size());
895 : }
896 : event->accept();
897 : }
898 0 : }
899 :
900 : /**
901 : * @brief MainWindow::eventFilter filter out some events and focus the
902 : * treeview
903 : * @param obj
904 : * @param event
905 : * @return
906 : */
907 0 : auto MainWindow::eventFilter(QObject *obj, QEvent *event) -> bool {
908 0 : if (obj == ui->lineEdit && event->type() == QEvent::KeyPress) {
909 0 : auto *key = dynamic_cast<QKeyEvent *>(event);
910 0 : if (key != nullptr && key->key() == Qt::Key_Down) {
911 0 : ui->treeView->setFocus();
912 : }
913 : }
914 0 : return QObject::eventFilter(obj, event);
915 : }
916 :
917 : /**
918 : * @brief MainWindow::keyPressEvent did anyone press return, enter or escape?
919 : * @param event
920 : */
921 0 : void MainWindow::keyPressEvent(QKeyEvent *event) {
922 0 : switch (event->key()) {
923 0 : case Qt::Key_Delete:
924 0 : onDelete();
925 0 : break;
926 0 : case Qt::Key_Return:
927 : case Qt::Key_Enter:
928 0 : if (proxyModel.rowCount() > 0) {
929 0 : on_treeView_clicked(ui->treeView->currentIndex());
930 : }
931 : break;
932 0 : case Qt::Key_Escape:
933 0 : ui->lineEdit->clear();
934 0 : break;
935 : default:
936 : break;
937 : }
938 0 : }
939 :
940 : /**
941 : * @brief MainWindow::showContextMenu show us the (file or folder) context
942 : * menu
943 : * @param pos
944 : */
945 0 : void MainWindow::showContextMenu(const QPoint &pos) {
946 0 : QModelIndex index = ui->treeView->indexAt(pos);
947 : bool selected = true;
948 : if (!index.isValid()) {
949 0 : ui->treeView->clearSelection();
950 0 : ui->actionDelete->setEnabled(false);
951 0 : ui->actionEdit->setEnabled(false);
952 0 : currentDir = "";
953 : selected = false;
954 : }
955 :
956 0 : ui->treeView->setCurrentIndex(index);
957 :
958 0 : QPoint globalPos = ui->treeView->viewport()->mapToGlobal(pos);
959 :
960 : QFileInfo fileOrFolder =
961 0 : model.fileInfo(proxyModel.mapToSource(ui->treeView->currentIndex()));
962 :
963 0 : QMenu contextMenu;
964 0 : if (!selected || fileOrFolder.isDir()) {
965 : QAction *openFolder =
966 0 : contextMenu.addAction(tr("Open folder with file manager"));
967 0 : QAction *addFolder = contextMenu.addAction(tr("Add folder"));
968 0 : QAction *addPassword = contextMenu.addAction(tr("Add password"));
969 0 : QAction *users = contextMenu.addAction(tr("Users"));
970 0 : connect(openFolder, &QAction::triggered, this, &MainWindow::openFolder);
971 0 : connect(addFolder, &QAction::triggered, this, &MainWindow::addFolder);
972 0 : connect(addPassword, &QAction::triggered, this, &MainWindow::addPassword);
973 0 : connect(users, &QAction::triggered, this, &MainWindow::onUsers);
974 0 : } else if (fileOrFolder.isFile()) {
975 0 : QAction *edit = contextMenu.addAction(tr("Edit"));
976 0 : connect(edit, &QAction::triggered, this, &MainWindow::onEdit);
977 : }
978 0 : if (selected) {
979 : // if (useClipboard != CLIPBOARD_NEVER) {
980 : // contextMenu.addSeparator();
981 : // QAction* copyItem = contextMenu.addAction(tr("Copy Password"));
982 : // if (getClippedPassword().length() == 0) copyItem->setEnabled(false);
983 : // connect(copyItem, SIGNAL(triggered()), this,
984 : // SLOT(copyPasswordToClipboard()));
985 : // }
986 0 : contextMenu.addSeparator();
987 0 : if (fileOrFolder.isDir()) {
988 0 : QAction *renameFolder = contextMenu.addAction(tr("Rename folder"));
989 0 : connect(renameFolder, &QAction::triggered, this,
990 0 : &MainWindow::renameFolder);
991 0 : } else if (fileOrFolder.isFile()) {
992 0 : QAction *renamePassword = contextMenu.addAction(tr("Rename password"));
993 0 : connect(renamePassword, &QAction::triggered, this,
994 0 : &MainWindow::renamePassword);
995 : }
996 0 : QAction *deleteItem = contextMenu.addAction(tr("Delete"));
997 0 : connect(deleteItem, &QAction::triggered, this, &MainWindow::onDelete);
998 : }
999 0 : contextMenu.exec(globalPos);
1000 0 : }
1001 :
1002 : /**
1003 : * @brief MainWindow::showBrowserContextMenu show us the context menu in
1004 : * password window
1005 : * @param pos
1006 : */
1007 0 : void MainWindow::showBrowserContextMenu(const QPoint &pos) {
1008 0 : QMenu *contextMenu = ui->textBrowser->createStandardContextMenu(pos);
1009 0 : QPoint globalPos = ui->textBrowser->viewport()->mapToGlobal(pos);
1010 :
1011 0 : contextMenu->exec(globalPos);
1012 0 : delete contextMenu;
1013 0 : }
1014 :
1015 : /**
1016 : * @brief MainWindow::openFolder open the folder in the default file manager
1017 : */
1018 0 : void MainWindow::openFolder() {
1019 : QString dir =
1020 0 : Util::getDir(ui->treeView->currentIndex(), false, model, proxyModel);
1021 :
1022 0 : QString path = QDir::toNativeSeparators(dir);
1023 0 : QDesktopServices::openUrl(QUrl::fromLocalFile(path));
1024 0 : }
1025 :
1026 : /**
1027 : * @brief MainWindow::addFolder add a new folder to store passwords in
1028 : */
1029 0 : void MainWindow::addFolder() {
1030 : bool ok;
1031 : QString dir =
1032 0 : Util::getDir(ui->treeView->currentIndex(), false, model, proxyModel);
1033 : QString newdir =
1034 0 : QInputDialog::getText(this, tr("New file"),
1035 0 : tr("New Folder: \n(Will be placed in %1 )")
1036 0 : .arg(QtPassSettings::getPassStore() +
1037 0 : Util::getDir(ui->treeView->currentIndex(),
1038 0 : true, model, proxyModel)),
1039 0 : QLineEdit::Normal, "", &ok);
1040 0 : if (!ok || newdir.isEmpty()) {
1041 0 : return;
1042 : }
1043 : newdir.prepend(dir);
1044 : // dbg()<< newdir;
1045 0 : if (!QDir().mkdir(newdir)) {
1046 0 : QMessageBox::warning(this, tr("Error"),
1047 0 : tr("Failed to create folder: %1").arg(newdir));
1048 0 : return;
1049 : }
1050 0 : if (QtPassSettings::isAddGPGId(true)) {
1051 0 : QString gpgIdFile = newdir + "/.gpg-id";
1052 0 : QFile gpgId(gpgIdFile);
1053 0 : if (!gpgId.open(QIODevice::WriteOnly)) {
1054 0 : QMessageBox::warning(
1055 0 : this, tr("Error"),
1056 0 : tr("Failed to create .gpg-id file in: %1").arg(newdir));
1057 : return;
1058 : }
1059 0 : QList<UserInfo> users = QtPassSettings::getPass()->listKeys("", true);
1060 0 : for (const UserInfo &user : users) {
1061 0 : if (user.enabled) {
1062 0 : gpgId.write((user.key_id + "\n").toUtf8());
1063 : }
1064 : }
1065 0 : gpgId.close();
1066 0 : }
1067 : }
1068 :
1069 : /**
1070 : * @brief MainWindow::renameFolder rename an existing folder
1071 : */
1072 0 : void MainWindow::renameFolder() {
1073 : bool ok;
1074 : QString srcDir = QDir::cleanPath(
1075 0 : Util::getDir(ui->treeView->currentIndex(), false, model, proxyModel));
1076 0 : QString srcDirName = QDir(srcDir).dirName();
1077 : QString newName =
1078 0 : QInputDialog::getText(this, tr("Rename file"), tr("Rename Folder To: "),
1079 0 : QLineEdit::Normal, srcDirName, &ok);
1080 0 : if (!ok || newName.isEmpty()) {
1081 : return;
1082 : }
1083 : QString destDir = srcDir;
1084 0 : destDir.replace(srcDir.lastIndexOf(srcDirName), srcDirName.length(), newName);
1085 0 : QtPassSettings::getPass()->Move(srcDir, destDir);
1086 : }
1087 :
1088 : /**
1089 : * @brief MainWindow::editPassword read password and open edit window via
1090 : * MainWindow::onEdit()
1091 : */
1092 0 : void MainWindow::editPassword(const QString &file) {
1093 0 : if (!file.isEmpty()) {
1094 0 : if (QtPassSettings::isUseGit() && QtPassSettings::isAutoPull()) {
1095 0 : onUpdate(true);
1096 : }
1097 0 : setPassword(file, false);
1098 : }
1099 0 : }
1100 :
1101 : /**
1102 : * @brief MainWindow::renamePassword rename an existing password
1103 : */
1104 0 : void MainWindow::renamePassword() {
1105 : bool ok;
1106 0 : QString file = getFile(ui->treeView->currentIndex(), false);
1107 0 : QString filePath = QFileInfo(file).path();
1108 0 : QString fileName = QFileInfo(file).fileName();
1109 0 : if (fileName.endsWith(".gpg", Qt::CaseInsensitive)) {
1110 0 : fileName.chop(4);
1111 : }
1112 :
1113 : QString newName =
1114 0 : QInputDialog::getText(this, tr("Rename file"), tr("Rename File To: "),
1115 0 : QLineEdit::Normal, fileName, &ok);
1116 0 : if (!ok || newName.isEmpty()) {
1117 : return;
1118 : }
1119 0 : QString newFile = QDir(filePath).filePath(newName);
1120 0 : QtPassSettings::getPass()->Move(file, newFile);
1121 : }
1122 :
1123 : /**
1124 : * @brief MainWindow::clearTemplateWidgets empty the template widget fields in
1125 : * the UI
1126 : */
1127 0 : void MainWindow::clearTemplateWidgets() {
1128 0 : while (ui->gridLayout->count() > 0) {
1129 0 : QLayoutItem *item = ui->gridLayout->takeAt(0);
1130 0 : delete item->widget();
1131 0 : delete item;
1132 : }
1133 0 : ui->verticalLayoutPassword->setSpacing(0);
1134 0 : }
1135 :
1136 0 : void MainWindow::copyPasswordFromTreeview() {
1137 : QFileInfo fileOrFolder =
1138 0 : model.fileInfo(proxyModel.mapToSource(ui->treeView->currentIndex()));
1139 :
1140 0 : if (fileOrFolder.isFile()) {
1141 0 : QString file = getFile(ui->treeView->currentIndex(), true);
1142 0 : connect(QtPassSettings::getPass(), &Pass::finishedShow, this,
1143 0 : &MainWindow::passwordFromFileToClipboard);
1144 0 : QtPassSettings::getPass()->Show(file);
1145 : }
1146 0 : }
1147 :
1148 0 : void MainWindow::passwordFromFileToClipboard(const QString &text) {
1149 0 : QStringList tokens = text.split('\n');
1150 0 : m_qtPass->copyTextToClipboard(tokens[0]);
1151 0 : }
1152 :
1153 : /**
1154 : * @brief MainWindow::addToGridLayout add a field to the template grid
1155 : * @param position
1156 : * @param field
1157 : * @param value
1158 : */
1159 0 : void MainWindow::addToGridLayout(int position, const QString &field,
1160 : const QString &value) {
1161 : QString trimmedField = field.trimmed();
1162 : QString trimmedValue = value.trimmed();
1163 :
1164 : // Combine the Copy button and the line edit in one widget
1165 0 : auto *frame = new QFrame();
1166 0 : QLayout *ly = new QHBoxLayout();
1167 0 : ly->setContentsMargins(5, 2, 2, 2);
1168 0 : ly->setSpacing(0);
1169 0 : frame->setLayout(ly);
1170 0 : if (QtPassSettings::getClipBoardType() != Enums::CLIPBOARD_NEVER) {
1171 0 : auto *fieldLabel = new QPushButtonWithClipboard(trimmedValue, this);
1172 0 : connect(fieldLabel, &QPushButtonWithClipboard::clicked, m_qtPass,
1173 0 : &QtPass::copyTextToClipboard);
1174 :
1175 0 : fieldLabel->setStyleSheet(
1176 0 : "border-style: none ; background: transparent; padding: 0; margin: 0;");
1177 0 : frame->layout()->addWidget(fieldLabel);
1178 : }
1179 :
1180 0 : if (QtPassSettings::isUseQrencode()) {
1181 0 : auto *qrbutton = new QPushButtonAsQRCode(trimmedValue, this);
1182 0 : connect(qrbutton, &QPushButtonAsQRCode::clicked, m_qtPass,
1183 0 : &QtPass::showTextAsQRCode);
1184 0 : qrbutton->setStyleSheet(
1185 0 : "border-style: none ; background: transparent; padding: 0; margin: 0;");
1186 0 : frame->layout()->addWidget(qrbutton);
1187 : }
1188 :
1189 : // set the echo mode to password, if the field is "password"
1190 : const QString lineStyle =
1191 0 : QtPassSettings::isUseMonospace()
1192 : ? "border-style: none; background: transparent; font-family: "
1193 : "monospace;"
1194 0 : : "border-style: none; background: transparent;";
1195 :
1196 0 : if (QtPassSettings::isHidePassword() && trimmedField == tr("Password")) {
1197 0 : auto *line = new QLineEdit();
1198 0 : line->setObjectName(trimmedField);
1199 0 : line->setText(trimmedValue);
1200 0 : line->setReadOnly(true);
1201 0 : line->setStyleSheet(lineStyle);
1202 0 : line->setContentsMargins(0, 0, 0, 0);
1203 0 : line->setEchoMode(QLineEdit::Password);
1204 0 : auto *showButton = new QPushButtonShowPassword(line, this);
1205 0 : showButton->setStyleSheet(
1206 0 : "border-style: none ; background: transparent; padding: 0; margin: 0;");
1207 0 : showButton->setContentsMargins(0, 0, 0, 0);
1208 0 : frame->layout()->addWidget(showButton);
1209 0 : frame->layout()->addWidget(line);
1210 : } else {
1211 0 : auto *line = new QTextBrowser();
1212 0 : line->setOpenExternalLinks(true);
1213 0 : line->setOpenLinks(true);
1214 0 : line->setMaximumHeight(26);
1215 0 : line->setMinimumHeight(26);
1216 0 : line->setSizePolicy(
1217 : QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum));
1218 0 : line->setObjectName(trimmedField);
1219 0 : trimmedValue.replace(Util::protocolRegex(), R"(<a href="\1">\1</a>)");
1220 0 : line->setText(trimmedValue);
1221 0 : line->setReadOnly(true);
1222 0 : line->setStyleSheet(lineStyle);
1223 0 : line->setContentsMargins(0, 0, 0, 0);
1224 0 : frame->layout()->addWidget(line);
1225 : }
1226 :
1227 0 : frame->setStyleSheet(
1228 0 : ".QFrame{border: 1px solid lightgrey; border-radius: 5px;}");
1229 :
1230 : // set into the layout
1231 0 : ui->gridLayout->addWidget(new QLabel(trimmedField), position, 0);
1232 0 : ui->gridLayout->addWidget(frame, position, 1);
1233 0 : }
1234 :
1235 : /**
1236 : * @brief Displays message in status bar
1237 : *
1238 : * @param msg text to be displayed
1239 : * @param timeout time for which msg shall be visible
1240 : */
1241 0 : void MainWindow::showStatusMessage(const QString &msg, int timeout) {
1242 0 : ui->statusBar->showMessage(msg, timeout);
1243 0 : }
1244 :
1245 : /**
1246 : * @brief MainWindow::startReencryptPath disable ui elements and treeview
1247 : */
1248 0 : void MainWindow::startReencryptPath() {
1249 0 : setUiElementsEnabled(false);
1250 0 : ui->treeView->setDisabled(true);
1251 0 : }
1252 :
1253 : /**
1254 : * @brief MainWindow::endReencryptPath re-enable ui elements
1255 : */
1256 0 : void MainWindow::endReencryptPath() { setUiElementsEnabled(true); }
1257 :
1258 0 : void MainWindow::updateGitButtonVisibility() {
1259 0 : if (!QtPassSettings::isUseGit() ||
1260 0 : (QtPassSettings::getGitExecutable().isEmpty() &&
1261 0 : QtPassSettings::getPassExecutable().isEmpty())) {
1262 0 : enableGitButtons(false);
1263 : } else {
1264 0 : enableGitButtons(true);
1265 : }
1266 0 : }
1267 :
1268 0 : void MainWindow::updateOtpButtonVisibility() {
1269 : #if defined(Q_OS_WIN) || defined(__APPLE__)
1270 : ui->actionOtp->setVisible(false);
1271 : #endif
1272 0 : if (!QtPassSettings::isUseOtp()) {
1273 0 : ui->actionOtp->setEnabled(false);
1274 : } else {
1275 0 : ui->actionOtp->setEnabled(true);
1276 : }
1277 0 : }
1278 :
1279 0 : void MainWindow::enableGitButtons(const bool &state) {
1280 : // Following GNOME guidelines is preferable disable buttons instead of hide
1281 0 : ui->actionPush->setEnabled(state);
1282 0 : ui->actionUpdate->setEnabled(state);
1283 0 : }
1284 :
1285 : /**
1286 : * @brief MainWindow::critical critical message popup wrapper.
1287 : * @param title
1288 : * @param msg
1289 : */
1290 0 : void MainWindow::critical(const QString &title, const QString &msg) {
1291 0 : QMessageBox::critical(this, title, msg);
1292 0 : }
|