QtPass 1.4.0
Multi-platform GUI for pass, the standard unix password manager.
Loading...
Searching...
No Matches
mainwindow.cpp
Go to the documentation of this file.
1#include "mainwindow.h"
2
3#ifdef QT_DEBUG
4#include "debughelper.h"
5#endif
6
7#include "configdialog.h"
8#include "filecontent.h"
9#include "passworddialog.h"
10#include "qpushbuttonasqrcode.h"
13#include "qtpass.h"
14#include "qtpasssettings.h"
15#include "trayicon.h"
16#include "ui_mainwindow.h"
17#include "usersdialog.h"
18#include "util.h"
19#include <QCloseEvent>
20#include <QDesktopServices>
21#include <QDialog>
22#include <QFileInfo>
23#include <QInputDialog>
24#include <QLabel>
25#include <QMenu>
26#include <QMessageBox>
27#include <QShortcut>
28#include <QTimer>
29
36MainWindow::MainWindow(const QString &searchText, QWidget *parent)
37 : QMainWindow(parent), ui(new Ui::MainWindow), keygen(nullptr),
38 tray(nullptr) {
39#ifdef __APPLE__
40 // extra treatment for mac os
41 // see http://doc.qt.io/qt-5/qkeysequence.html#qt_set_sequence_auto_mnemonic
42 qt_set_sequence_auto_mnemonic(true);
43#endif
44 ui->setupUi(this);
45
46 m_qtPass = new QtPass(this);
47
48 // register shortcut ctrl/cmd + Q to close the main window
49 new QShortcut(QKeySequence(Qt::CTRL | Qt::Key_Q), this, SLOT(close()));
50 // register shortcut ctrl/cmd + C to copy the currently selected password
51 new QShortcut(QKeySequence(QKeySequence::StandardKey::Copy), this,
52 SLOT(copyPasswordFromTreeview()));
53
54 model.setNameFilters(QStringList() << "*.gpg");
55 model.setNameFilterDisables(false);
56
57 /*
58 * I added this to solve Windows bug but now on GNU/Linux the main folder,
59 * if hidden, disappear
60 *
61 * model.setFilter(QDir::NoDot);
62 */
63
65
66 QModelIndex rootDir = model.setRootPath(passStore);
67 model.fetchMore(rootDir);
68
69 proxyModel.setModelAndStore(&model, passStore);
70 selectionModel.reset(new QItemSelectionModel(&proxyModel));
71
72 ui->treeView->setModel(&proxyModel);
73 ui->treeView->setRootIndex(proxyModel.mapFromSource(rootDir));
74 ui->treeView->setColumnHidden(1, true);
75 ui->treeView->setColumnHidden(2, true);
76 ui->treeView->setColumnHidden(3, true);
77 ui->treeView->setHeaderHidden(true);
78 ui->treeView->setIndentation(15);
79 ui->treeView->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
80 ui->treeView->setContextMenuPolicy(Qt::CustomContextMenu);
81 ui->treeView->header()->setSectionResizeMode(0, QHeaderView::Stretch);
82 ui->treeView->sortByColumn(0, Qt::AscendingOrder);
83 connect(ui->treeView, &QWidget::customContextMenuRequested, this,
84 &MainWindow::showContextMenu);
85 connect(ui->treeView, &DeselectableTreeView::emptyClicked, this,
87
89 ui->textBrowser->setFont(QFont(QStringLiteral("Monospace")));
90 }
92 ui->textBrowser->setLineWrapMode(QTextBrowser::NoWrap);
93 }
94 ui->textBrowser->setOpenExternalLinks(true);
95 ui->textBrowser->setContextMenuPolicy(Qt::CustomContextMenu);
96 connect(ui->textBrowser, &QWidget::customContextMenuRequested, this,
97 &MainWindow::showBrowserContextMenu);
98
99 updateProfileBox();
100
102 clearPanelTimer.setInterval(1000 *
104 clearPanelTimer.setSingleShot(true);
105 connect(&clearPanelTimer, SIGNAL(timeout()), this, SLOT(clearPanel()));
106
107 searchTimer.setInterval(350);
108 searchTimer.setSingleShot(true);
109
110 connect(&searchTimer, &QTimer::timeout, this, &MainWindow::onTimeoutSearch);
111
112 initToolBarButtons();
113 initStatusBar();
114
115 ui->lineEdit->setClearButtonEnabled(true);
116
118
119 QTimer::singleShot(10, this, SLOT(focusInput()));
120
121 ui->lineEdit->setText(searchText);
122
123 if (!m_qtPass->init())
124 // no working config so this should just quit
125 QApplication::quit();
126}
127
128MainWindow::~MainWindow() { delete m_qtPass; }
129
136void MainWindow::focusInput() {
137 ui->lineEdit->selectAll();
138 ui->lineEdit->setFocus();
139}
140
145void MainWindow::changeEvent(QEvent *event) {
146 QWidget::changeEvent(event);
147 if (event->type() == QEvent::ActivationChange) {
148 if (isActiveWindow()) {
149 focusInput();
150 }
151 }
152}
153
157void MainWindow::initToolBarButtons() {
158 connect(ui->actionAddPassword, &QAction::triggered, this,
159 &MainWindow::addPassword);
160 connect(ui->actionAddFolder, &QAction::triggered, this,
161 &MainWindow::addFolder);
162 connect(ui->actionEdit, &QAction::triggered, this, &MainWindow::onEdit);
163 connect(ui->actionDelete, &QAction::triggered, this, &MainWindow::onDelete);
164 connect(ui->actionPush, &QAction::triggered, this, &MainWindow::onPush);
165 connect(ui->actionUpdate, &QAction::triggered, this, &MainWindow::onUpdate);
166 connect(ui->actionUsers, &QAction::triggered, this, &MainWindow::onUsers);
167 connect(ui->actionConfig, &QAction::triggered, this, &MainWindow::onConfig);
168 connect(ui->actionOtp, &QAction::triggered, this, &MainWindow::onOtp);
169
170 ui->actionAddPassword->setIcon(
171 QIcon::fromTheme("document-new", QIcon(":/icons/document-new.svg")));
172 ui->actionAddFolder->setIcon(
173 QIcon::fromTheme("folder-new", QIcon(":/icons/folder-new.svg")));
174 ui->actionEdit->setIcon(QIcon::fromTheme(
175 "document-properties", QIcon(":/icons/document-properties.svg")));
176 ui->actionDelete->setIcon(
177 QIcon::fromTheme("edit-delete", QIcon(":/icons/edit-delete.svg")));
178 ui->actionPush->setIcon(
179 QIcon::fromTheme("go-up", QIcon(":/icons/go-top.svg")));
180 ui->actionUpdate->setIcon(
181 QIcon::fromTheme("go-down", QIcon(":/icons/go-bottom.svg")));
182 ui->actionUsers->setIcon(QIcon::fromTheme(
183 "x-office-address-book", QIcon(":/icons/x-office-address-book.svg")));
184 ui->actionConfig->setIcon(QIcon::fromTheme(
185 "applications-system", QIcon(":/icons/applications-system.svg")));
186}
187
191void MainWindow::initStatusBar() {
192 ui->statusBar->showMessage(tr("Welcome to QtPass %1").arg(VERSION), 2000);
193
194 QPixmap logo = QPixmap::fromImage(QImage(":/artwork/icon.svg"))
195 .scaledToHeight(statusBar()->height());
196 QLabel *logoApp = new QLabel(statusBar());
197 logoApp->setPixmap(logo);
198 statusBar()->addPermanentWidget(logoApp);
199}
200
202 return ui->treeView->currentIndex();
203}
204
206 this->keygen->close();
207 this->keygen = nullptr;
208}
209
210void MainWindow::flashText(const QString &text, const bool isError,
211 const bool isHtml) {
212 if (isError)
213 ui->textBrowser->setTextColor(Qt::red);
214
215 if (isHtml) {
216 QString _text = text;
217 if (!ui->textBrowser->toPlainText().isEmpty())
218 _text = ui->textBrowser->toHtml() + _text;
219 ui->textBrowser->setHtml(_text);
220 } else {
221 ui->textBrowser->setText(text);
222 ui->textBrowser->setTextColor(Qt::black);
223 }
224}
225
231 QScopedPointer<ConfigDialog> d(new ConfigDialog(this));
232 d->setModal(true);
233 // Automatically default to pass if it's available
234 if (m_qtPass->isFreshStart() &&
235 QFile(QtPassSettings::getPassExecutable()).exists()) {
237 }
238
239 if (m_qtPass->isFreshStart())
240 d->wizard(); // does shit
241 if (d->exec()) {
242 if (d->result() == QDialog::Accepted) {
243 // Update the textBrowser font
245 ui->textBrowser->setFont(QFont(QStringLiteral("Monospace")));
246 } else {
247 ui->textBrowser->setFont(QFont());
248 }
249 // Update the textBrowser line wrap mode
251 ui->textBrowser->setLineWrapMode(QTextBrowser::NoWrap);
252 } else {
253 ui->textBrowser->setLineWrapMode(QTextBrowser::WidgetWidth);
254 }
255
257 Qt::WindowFlags flags = windowFlags();
258 this->setWindowFlags(flags | Qt::WindowStaysOnTopHint);
259 } else {
260 this->setWindowFlags(Qt::Window);
261 }
262 this->show();
263
264 updateProfileBox();
265 ui->treeView->setRootIndex(proxyModel.mapFromSource(
266 model.setRootPath(QtPassSettings::getPassStore())));
267
268 if (m_qtPass->isFreshStart() && Util::checkConfig())
269 config();
271 clearPanelTimer.setInterval(1000 *
273 m_qtPass->setClipboardTimer();
274
275 updateGitButtonVisibility();
276 updateOtpButtonVisibility();
277 if (QtPassSettings::isUseTrayIcon() && tray == nullptr)
278 initTrayIcon();
279 else if (!QtPassSettings::isUseTrayIcon() && tray != nullptr) {
280 destroyTrayIcon();
281 }
282 }
283
284 m_qtPass->setFreshStart(false);
285 }
286}
287
291void MainWindow::onUpdate(bool block) {
292 ui->statusBar->showMessage(tr("Updating password-store"), 2000);
293 if (block)
295 else
297}
298
304 ui->statusBar->showMessage(tr("Updating password-store"), 2000);
306 }
307}
308
316QString MainWindow::getFile(const QModelIndex &index, bool forPass) {
317 if (!index.isValid() ||
318 !model.fileInfo(proxyModel.mapToSource(index)).isFile())
319 return QString();
320 QString filePath = model.filePath(proxyModel.mapToSource(index));
321 if (forPass) {
322 filePath = QDir(QtPassSettings::getPassStore()).relativeFilePath(filePath);
323 filePath.replace(Util::endsWithGpg(), "");
324 }
325 return filePath;
326}
327
332void MainWindow::on_treeView_clicked(const QModelIndex &index) {
333 bool cleared = ui->treeView->currentIndex().flags() == Qt::NoItemFlags;
334 currentDir =
335 Util::getDir(ui->treeView->currentIndex(), false, model, proxyModel);
336 // TODO(bezet): "Could not decrypt";
337 m_qtPass->clearClippedText();
338 QString file = getFile(index, true);
339 ui->passwordName->setText(getFile(index, true));
340 if (!file.isEmpty() && !cleared) {
342 } else {
343 clearPanel(false);
344 ui->actionEdit->setEnabled(false);
345 ui->actionDelete->setEnabled(true);
346 }
347}
348
354void MainWindow::on_treeView_doubleClicked(const QModelIndex &index) {
355 QFileInfo fileOrFolder =
356 model.fileInfo(proxyModel.mapToSource(ui->treeView->currentIndex()));
357
358 if (fileOrFolder.isFile()) {
359 editPassword(getFile(index, true));
360 }
361}
362
367 currentDir = "";
368 m_qtPass->clearClipboard();
369 ui->treeView->clearSelection();
370 ui->actionEdit->setEnabled(false);
371 ui->actionDelete->setEnabled(false);
372 ui->passwordName->setText("");
373 clearPanel(false);
374}
375
377 clearTemplateWidgets();
378 ui->textBrowser->clear();
380 clearPanelTimer.stop();
381}
382
383void MainWindow::passShowHandler(const QString &p_output) {
384 QStringList templ = QtPassSettings::isUseTemplate()
385 ? QtPassSettings::getPassTemplate().split("\n")
386 : QStringList();
387 bool allFields =
389 FileContent fileContent = FileContent::parse(p_output, templ, allFields);
390 QString output = p_output;
391 QString password = fileContent.getPassword();
392
393 // set clipped text
394 m_qtPass->setClippedText(password, p_output);
395
396 // first clear the current view:
397 clearTemplateWidgets();
398
399 // show what is needed:
401 output = "***" + tr("Content hidden") + "***";
402 } else if (!QtPassSettings::isDisplayAsIs()) {
403 if (!password.isEmpty()) {
404 // set the password, it is hidden if needed in addToGridLayout
405 addToGridLayout(0, tr("Password"), password);
406 }
407
408 NamedValues namedValues = fileContent.getNamedValues();
409 for (int j = 0; j < namedValues.length(); ++j) {
410 NamedValue nv = namedValues.at(j);
411 addToGridLayout(j + 1, nv.name, nv.value);
412 }
413 if (ui->gridLayout->count() == 0)
414 ui->verticalLayoutPassword->setSpacing(0);
415 else
416 ui->verticalLayoutPassword->setSpacing(6);
417
418 output = fileContent.getRemainingDataForDisplay();
419 }
420
422 clearPanelTimer.start();
423 }
424
425 emit passShowHandlerFinished(output);
427}
428
429void MainWindow::passOtpHandler(const QString &p_output) {
430 if (!p_output.isEmpty()) {
431 addToGridLayout(ui->gridLayout->count() + 1, tr("OTP Code"), p_output);
432 m_qtPass->copyTextToClipboard(p_output);
433 }
435 clearPanelTimer.start();
436 }
438}
439
443void MainWindow::clearPanel(bool notify) {
444 while (ui->gridLayout->count() > 0) {
445 QLayoutItem *item = ui->gridLayout->takeAt(0);
446 delete item->widget();
447 delete item;
448 }
449 if (notify) {
450 QString output = "***" + tr("Password and Content hidden") + "***";
451 ui->textBrowser->setHtml(output);
452 } else {
453 ui->textBrowser->setHtml("");
454 }
455}
456
463 ui->treeView->setEnabled(state);
464 ui->lineEdit->setEnabled(state);
465 ui->lineEdit->installEventFilter(this);
466 ui->actionAddPassword->setEnabled(state);
467 ui->actionAddFolder->setEnabled(state);
468 ui->actionUsers->setEnabled(state);
469 ui->actionConfig->setEnabled(state);
470 // is a file selected?
471 state &= ui->treeView->currentIndex().isValid();
472 ui->actionDelete->setEnabled(state);
473 ui->actionEdit->setEnabled(state);
474 updateGitButtonVisibility();
475 updateOtpButtonVisibility();
476}
477
479 QByteArray geometry = QtPassSettings::getGeometry(saveGeometry());
480 restoreGeometry(geometry);
481 QByteArray savestate = QtPassSettings::getSavestate(saveState());
482 restoreState(savestate);
483 QPoint position = QtPassSettings::getPos(pos());
484 move(position);
485 QSize newSize = QtPassSettings::getSize(size());
486 resize(newSize);
487 if (QtPassSettings::isMaximized(isMaximized())) {
488 showMaximized();
489 }
490
492 Qt::WindowFlags flags = windowFlags();
493 setWindowFlags(flags | Qt::WindowStaysOnTopHint);
494 show();
495 }
496
497 if (QtPassSettings::isUseTrayIcon() && tray == nullptr) {
498 initTrayIcon();
500 // since we are still in constructor, can't directly hide
501 QTimer::singleShot(10, this, SLOT(hide()));
502 }
503 } else if (!QtPassSettings::isUseTrayIcon() && tray != nullptr) {
504 destroyTrayIcon();
505 }
506}
507
511void MainWindow::onConfig() { config(); }
512
518void MainWindow::on_lineEdit_textChanged(const QString &arg1) {
519 ui->statusBar->showMessage(tr("Looking for: %1").arg(arg1), 1000);
520 ui->treeView->expandAll();
521 clearPanel(false);
522 ui->passwordName->setText("");
523 ui->actionEdit->setEnabled(false);
524 ui->actionDelete->setEnabled(false);
525 searchTimer.start();
526}
527
532void MainWindow::onTimeoutSearch() {
533 QString query = ui->lineEdit->text();
534
535 if (query.isEmpty()) {
536 ui->treeView->collapseAll();
537 deselect();
538 }
539
540 query.replace(QStringLiteral(" "), ".*");
541 QRegularExpression regExp(query, QRegularExpression::CaseInsensitiveOption);
542 proxyModel.setFilterRegularExpression(regExp);
543 ui->treeView->setRootIndex(proxyModel.mapFromSource(
544 model.setRootPath(QtPassSettings::getPassStore())));
545
546 if (proxyModel.rowCount() > 0 && !query.isEmpty()) {
547 selectFirstFile();
548 } else {
549 ui->actionEdit->setEnabled(false);
550 ui->actionDelete->setEnabled(false);
551 }
552}
553
559void MainWindow::on_lineEdit_returnPressed() {
560#ifdef QT_DEBUG
561 dbg() << "on_lineEdit_returnPressed" << proxyModel.rowCount();
562#endif
563
564 if (proxyModel.rowCount() > 0) {
565 selectFirstFile();
566 on_treeView_clicked(ui->treeView->currentIndex());
567 }
568}
569
574void MainWindow::selectFirstFile() {
575 QModelIndex index = proxyModel.mapFromSource(
576 model.setRootPath(QtPassSettings::getPassStore()));
577 index = firstFile(index);
578 ui->treeView->setCurrentIndex(index);
579}
580
586QModelIndex MainWindow::firstFile(QModelIndex parentIndex) {
587 QModelIndex index = parentIndex;
588 int numRows = proxyModel.rowCount(parentIndex);
589 for (int row = 0; row < numRows; ++row) {
590 index = proxyModel.index(row, 0, parentIndex);
591 if (model.fileInfo(proxyModel.mapToSource(index)).isFile())
592 return index;
593 if (proxyModel.hasChildren(index))
594 return firstFile(index);
595 }
596 return index;
597}
598
604void MainWindow::setPassword(QString file, bool isNew) {
605 PasswordDialog d(file, isNew, this);
606
607 if (!d.exec()) {
608 ui->treeView->setFocus();
609 }
610}
611
616void MainWindow::addPassword() {
617 bool ok;
618 QString dir =
619 Util::getDir(ui->treeView->currentIndex(), true, model, proxyModel);
620 QString file =
621 QInputDialog::getText(this, tr("New file"),
622 tr("New password file: \n(Will be placed in %1 )")
624 Util::getDir(ui->treeView->currentIndex(),
625 true, model, proxyModel)),
626 QLineEdit::Normal, "", &ok);
627 if (!ok || file.isEmpty())
628 return;
629 file = dir + file;
630 setPassword(file);
631}
632
637void MainWindow::onDelete() {
638 QModelIndex currentIndex = ui->treeView->currentIndex();
639 if (!currentIndex.isValid()) {
640 // This fixes https://github.com/IJHack/QtPass/issues/556
641 // Otherwise the entire password directory would be deleted if
642 // nothing is selected in the tree view.
643 return;
644 }
645
646 QFileInfo fileOrFolder =
647 model.fileInfo(proxyModel.mapToSource(ui->treeView->currentIndex()));
648 QString file = "";
649 bool isDir = false;
650
651 if (fileOrFolder.isFile()) {
652 file = getFile(ui->treeView->currentIndex(), true);
653 } else {
654 file = Util::getDir(ui->treeView->currentIndex(), true, model, proxyModel);
655 isDir = true;
656 }
657
658 QString dirMessage = tr(" and the whole content?");
659 if (isDir) {
660 QDirIterator it(model.rootPath() + QDir::separator() + file,
661 QDirIterator::Subdirectories);
662 bool okDir = true;
663 while (it.hasNext() && okDir) {
664 it.next();
665 if (QFileInfo(it.filePath()).isFile()) {
666 if (QFileInfo(it.filePath()).suffix() != "gpg") {
667 okDir = false;
668 dirMessage = tr(" and the whole content? <br><strong>Attention: "
669 "there are unexpected files in the given folder, "
670 "check them before continue.</strong>");
671 }
672 }
673 }
674 }
675
676 if (QMessageBox::question(
677 this, isDir ? tr("Delete folder?") : tr("Delete password?"),
678 tr("Are you sure you want to delete %1%2?")
679 .arg(QDir::separator() + file, isDir ? dirMessage : "?"),
680 QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes)
681 return;
682
683 QtPassSettings::getPass()->Remove(file, isDir);
684}
685
689void MainWindow::onOtp() {
690 QString file = getFile(ui->treeView->currentIndex(), true);
691 if (!file.isEmpty()) {
694 }
695}
696
700void MainWindow::onEdit() {
701 QString file = getFile(ui->treeView->currentIndex(), true);
702 editPassword(file);
703}
704
709void MainWindow::userDialog(QString dir) {
710 if (!dir.isEmpty())
711 currentDir = dir;
712 onUsers();
713}
714
720void MainWindow::onUsers() {
721 QString dir =
722 currentDir.isEmpty()
723 ? Util::getDir(ui->treeView->currentIndex(), false, model, proxyModel)
724 : currentDir;
725
726 UsersDialog d(dir, this);
727 if (!d.exec()) {
728 ui->treeView->setFocus();
729 }
730}
731
736void MainWindow::messageAvailable(QString message) {
737 if (message.isEmpty()) {
738 focusInput();
739 } else {
740 ui->treeView->expandAll();
741 ui->lineEdit->setText(message);
742 on_lineEdit_returnPressed();
743 }
744 show();
745 raise();
746}
747
753void MainWindow::generateKeyPair(QString batch, QDialog *keygenWindow) {
754 keygen = keygenWindow;
755 emit generateGPGKeyPair(batch);
756}
757
762void MainWindow::updateProfileBox() {
763 QHash<QString, QHash<QString, QString>> profiles =
765
766 if (profiles.isEmpty()) {
767 ui->profileWidget->hide();
768 } else {
769 ui->profileWidget->show();
770 ui->profileBox->setEnabled(profiles.size() > 1);
771 ui->profileBox->clear();
772 QHashIterator<QString, QHash<QString, QString>> i(profiles);
773 while (i.hasNext()) {
774 i.next();
775 if (!i.key().isEmpty())
776 ui->profileBox->addItem(i.key());
777 }
778 ui->profileBox->model()->sort(0);
779 }
780 int index = ui->profileBox->findText(QtPassSettings::getProfile());
781 if (index != -1) // -1 for not found
782 ui->profileBox->setCurrentIndex(index);
783}
784
790void MainWindow::on_profileBox_currentIndexChanged(QString name) {
791 if (m_qtPass->isFreshStart() || name == QtPassSettings::getProfile())
792 return;
793
794 ui->lineEdit->clear();
795
797
799 QtPassSettings::getProfiles().value(name).value("path"));
801 QtPassSettings::getProfiles().value(name).value("signingKey"));
802 ui->statusBar->showMessage(tr("Profile changed to %1").arg(name), 2000);
803
805
806 ui->treeView->selectionModel()->clear();
807 ui->treeView->setRootIndex(proxyModel.mapFromSource(
808 model.setRootPath(QtPassSettings::getPassStore())));
809
810 ui->actionEdit->setEnabled(false);
811 ui->actionDelete->setEnabled(false);
812}
813
819void MainWindow::initTrayIcon() {
820 this->tray = new TrayIcon(this);
821 // Setup tray icon
822
823 if (tray == nullptr) {
824#ifdef QT_DEBUG
825 dbg() << "Allocating tray icon failed.";
826#endif
827 }
828
829 if (!tray->getIsAllocated()) {
830 destroyTrayIcon();
831 }
832}
833
837void MainWindow::destroyTrayIcon() {
838 delete this->tray;
839 tray = nullptr;
840}
841
846void MainWindow::closeEvent(QCloseEvent *event) {
848 this->hide();
849 event->ignore();
850 } else {
851 m_qtPass->clearClipboard();
852
853 QtPassSettings::setGeometry(saveGeometry());
854 QtPassSettings::setSavestate(saveState());
855 QtPassSettings::setMaximized(isMaximized());
856 if (!isMaximized()) {
859 }
860 event->accept();
861 }
862}
863
871bool MainWindow::eventFilter(QObject *obj, QEvent *event) {
872 if (obj == ui->lineEdit && event->type() == QEvent::KeyPress) {
873 auto *key = dynamic_cast<QKeyEvent *>(event);
874 if (key != NULL && key->key() == Qt::Key_Down) {
875 ui->treeView->setFocus();
876 }
877 }
878 return QObject::eventFilter(obj, event);
879}
880
885void MainWindow::keyPressEvent(QKeyEvent *event) {
886 switch (event->key()) {
887 case Qt::Key_Delete:
888 onDelete();
889 break;
890 case Qt::Key_Return:
891 case Qt::Key_Enter:
892 if (proxyModel.rowCount() > 0)
893 on_treeView_clicked(ui->treeView->currentIndex());
894 break;
895 case Qt::Key_Escape:
896 ui->lineEdit->clear();
897 break;
898 default:
899 break;
900 }
901}
902
908void MainWindow::showContextMenu(const QPoint &pos) {
909 QModelIndex index = ui->treeView->indexAt(pos);
910 bool selected = true;
911 if (!index.isValid()) {
912 ui->treeView->clearSelection();
913 ui->actionDelete->setEnabled(false);
914 ui->actionEdit->setEnabled(false);
915 currentDir = "";
916 selected = false;
917 }
918
919 ui->treeView->setCurrentIndex(index);
920
921 QPoint globalPos = ui->treeView->viewport()->mapToGlobal(pos);
922
923 QFileInfo fileOrFolder =
924 model.fileInfo(proxyModel.mapToSource(ui->treeView->currentIndex()));
925
926 QMenu contextMenu;
927 if (!selected || fileOrFolder.isDir()) {
928 QAction *openFolder =
929 contextMenu.addAction(tr("Open folder with file manager"));
930 QAction *addFolder = contextMenu.addAction(tr("Add folder"));
931 QAction *addPassword = contextMenu.addAction(tr("Add password"));
932 QAction *users = contextMenu.addAction(tr("Users"));
933 connect(openFolder, &QAction::triggered, this, &MainWindow::openFolder);
934 connect(addFolder, &QAction::triggered, this, &MainWindow::addFolder);
935 connect(addPassword, &QAction::triggered, this, &MainWindow::addPassword);
936 connect(users, &QAction::triggered, this, &MainWindow::onUsers);
937 } else if (fileOrFolder.isFile()) {
938 QAction *edit = contextMenu.addAction(tr("Edit"));
939 connect(edit, &QAction::triggered, this, &MainWindow::onEdit);
940 }
941 if (selected) {
942 // if (useClipboard != CLIPBOARD_NEVER) {
943 // contextMenu.addSeparator();
944 // QAction* copyItem = contextMenu.addAction(tr("Copy Password"));
945 // if (getClippedPassword().length() == 0) copyItem->setEnabled(false);
946 // connect(copyItem, SIGNAL(triggered()), this,
947 // SLOT(copyPasswordToClipboard()));
948 // }
949 contextMenu.addSeparator();
950 if (fileOrFolder.isDir()) {
951 QAction *renameFolder = contextMenu.addAction(tr("Rename folder"));
952 connect(renameFolder, &QAction::triggered, this,
953 &MainWindow::renameFolder);
954 } else if (fileOrFolder.isFile()) {
955 QAction *renamePassword = contextMenu.addAction(tr("Rename password"));
956 connect(renamePassword, &QAction::triggered, this,
957 &MainWindow::renamePassword);
958 }
959 QAction *deleteItem = contextMenu.addAction(tr("Delete"));
960 connect(deleteItem, &QAction::triggered, this, &MainWindow::onDelete);
961 }
962 contextMenu.exec(globalPos);
963}
964
970void MainWindow::showBrowserContextMenu(const QPoint &pos) {
971 QMenu *contextMenu = ui->textBrowser->createStandardContextMenu(pos);
972 QPoint globalPos = ui->textBrowser->viewport()->mapToGlobal(pos);
973
974 contextMenu->exec(globalPos);
975 delete contextMenu;
976}
977
981void MainWindow::openFolder() {
982 QString dir =
983 Util::getDir(ui->treeView->currentIndex(), false, model, proxyModel);
984
985 QString path = QDir::toNativeSeparators(dir);
986 QDesktopServices::openUrl(QUrl::fromLocalFile(path));
987}
988
992void MainWindow::addFolder() {
993 bool ok;
994 QString dir =
995 Util::getDir(ui->treeView->currentIndex(), false, model, proxyModel);
996 QString newdir =
997 QInputDialog::getText(this, tr("New file"),
998 tr("New Folder: \n(Will be placed in %1 )")
1000 Util::getDir(ui->treeView->currentIndex(),
1001 true, model, proxyModel)),
1002 QLineEdit::Normal, "", &ok);
1003 if (!ok || newdir.isEmpty())
1004 return;
1005 newdir.prepend(dir);
1006 // dbg()<< newdir;
1007 QDir().mkdir(newdir);
1008}
1009
1013void MainWindow::renameFolder() {
1014 bool ok;
1015 QString srcDir = QDir::cleanPath(
1016 Util::getDir(ui->treeView->currentIndex(), false, model, proxyModel));
1017 QString srcDirName = QDir(srcDir).dirName();
1018 QString newName =
1019 QInputDialog::getText(this, tr("Rename file"), tr("Rename Folder To: "),
1020 QLineEdit::Normal, srcDirName, &ok);
1021 if (!ok || newName.isEmpty())
1022 return;
1023 QString destDir = srcDir;
1024 destDir.replace(srcDir.lastIndexOf(srcDirName), srcDirName.length(), newName);
1025 QtPassSettings::getPass()->Move(srcDir, destDir);
1026}
1027
1032void MainWindow::editPassword(const QString &file) {
1033 if (!file.isEmpty()) {
1035 onUpdate(true);
1036 setPassword(file, false);
1037 }
1038}
1039
1043void MainWindow::renamePassword() {
1044 bool ok;
1045 QString file = getFile(ui->treeView->currentIndex(), false);
1046 QString filePath = QFileInfo(file).path();
1047 QString fileName = QFileInfo(file).fileName();
1048 if (fileName.endsWith(".gpg", Qt::CaseInsensitive))
1049 fileName.chop(4);
1050
1051 QString newName =
1052 QInputDialog::getText(this, tr("Rename file"), tr("Rename File To: "),
1053 QLineEdit::Normal, fileName, &ok);
1054 if (!ok || newName.isEmpty())
1055 return;
1056 QString newFile = QDir(filePath).filePath(newName);
1057 QtPassSettings::getPass()->Move(file, newFile);
1058}
1059
1064void MainWindow::clearTemplateWidgets() {
1065 while (ui->gridLayout->count() > 0) {
1066 QLayoutItem *item = ui->gridLayout->takeAt(0);
1067 delete item->widget();
1068 delete item;
1069 }
1070 ui->verticalLayoutPassword->setSpacing(0);
1071}
1072
1073void MainWindow::copyPasswordFromTreeview() {
1074 QFileInfo fileOrFolder =
1075 model.fileInfo(proxyModel.mapToSource(ui->treeView->currentIndex()));
1076
1077 if (fileOrFolder.isFile()) {
1078 QString file = getFile(ui->treeView->currentIndex(), true);
1080 &MainWindow::passwordFromFileToClipboard);
1082 }
1083}
1084
1085void MainWindow::passwordFromFileToClipboard(const QString &text) {
1086 QStringList tokens = text.split('\n');
1087 m_qtPass->copyTextToClipboard(tokens[0]);
1088}
1089
1096void MainWindow::addToGridLayout(int position, const QString &field,
1097 const QString &value) {
1098 QString trimmedField = field.trimmed();
1099 QString trimmedValue = value.trimmed();
1100
1101 // Combine the Copy button and the line edit in one widget
1102 QFrame *frame = new QFrame();
1103 QLayout *ly = new QHBoxLayout();
1104 ly->setContentsMargins(5, 2, 2, 2);
1105 ly->setSpacing(0);
1106 frame->setLayout(ly);
1108 auto *fieldLabel = new QPushButtonWithClipboard(trimmedValue, this);
1109 connect(fieldLabel, &QPushButtonWithClipboard::clicked, m_qtPass,
1111
1112 fieldLabel->setStyleSheet(
1113 "border-style: none ; background: transparent; padding: 0; margin: 0;");
1114 frame->layout()->addWidget(fieldLabel);
1115 }
1116
1118 QPushButtonAsQRCode *qrbutton = new QPushButtonAsQRCode(trimmedValue, this);
1119 connect(qrbutton, &QPushButtonAsQRCode::clicked, m_qtPass,
1121 qrbutton->setStyleSheet(
1122 "border-style: none ; background: transparent; padding: 0; margin: 0;");
1123 frame->layout()->addWidget(qrbutton);
1124 }
1125
1126 // set the echo mode to password, if the field is "password"
1127 const QString lineStyle =
1129 ? "border-style: none; background: transparent; font-family: "
1130 "monospace;"
1131 : "border-style: none; background: transparent;";
1132
1133 if (QtPassSettings::isHidePassword() && trimmedField == tr("Password")) {
1134
1135 auto *line = new QLineEdit();
1136 line->setObjectName(trimmedField);
1137 line->setText(trimmedValue);
1138 line->setReadOnly(true);
1139 line->setStyleSheet(lineStyle);
1140 line->setContentsMargins(0, 0, 0, 0);
1141 line->setEchoMode(QLineEdit::Password);
1142 QPushButtonShowPassword *showButton =
1143 new QPushButtonShowPassword(line, this);
1144 showButton->setStyleSheet(
1145 "border-style: none ; background: transparent; padding: 0; margin: 0;");
1146 showButton->setContentsMargins(0, 0, 0, 0);
1147 frame->layout()->addWidget(showButton);
1148 frame->layout()->addWidget(line);
1149 } else {
1150 auto *line = new QTextBrowser();
1151 line->setOpenExternalLinks(true);
1152 line->setOpenLinks(true);
1153 line->setMaximumHeight(26);
1154 line->setMinimumHeight(26);
1155 line->setSizePolicy(
1156 QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum));
1157 line->setObjectName(trimmedField);
1158 trimmedValue.replace(Util::protocolRegex(), R"(<a href="\1">\1</a>)");
1159 line->setText(trimmedValue);
1160 line->setReadOnly(true);
1161 line->setStyleSheet(lineStyle);
1162 line->setContentsMargins(0, 0, 0, 0);
1163 frame->layout()->addWidget(line);
1164 }
1165
1166 frame->setStyleSheet(
1167 ".QFrame{border: 1px solid lightgrey; border-radius: 5px;}");
1168
1169 // set into the layout
1170 ui->gridLayout->addWidget(new QLabel(trimmedField), position, 0);
1171 ui->gridLayout->addWidget(frame, position, 1);
1172}
1173
1180void MainWindow::showStatusMessage(QString msg, int timeout) {
1181 ui->statusBar->showMessage(msg, timeout);
1182}
1183
1188 setUiElementsEnabled(false);
1189 ui->treeView->setDisabled(true);
1190}
1191
1196
1197void MainWindow::updateGitButtonVisibility() {
1198 if (!QtPassSettings::isUseGit() ||
1199 (QtPassSettings::getGitExecutable().isEmpty() &&
1200 QtPassSettings::getPassExecutable().isEmpty())) {
1201 enableGitButtons(false);
1202 } else {
1203 enableGitButtons(true);
1204 }
1205}
1206
1207void MainWindow::updateOtpButtonVisibility() {
1208#if defined(Q_OS_WIN) || defined(__APPLE__)
1209 ui->actionOtp->setVisible(false);
1210#endif
1212 ui->actionOtp->setEnabled(false);
1213 else
1214 ui->actionOtp->setEnabled(true);
1215}
1216
1217void MainWindow::enableGitButtons(const bool &state) {
1218 // Following GNOME guidelines is preferable disable buttons instead of hide
1219 ui->actionPush->setEnabled(state);
1220 ui->actionUpdate->setEnabled(state);
1221}
1222
1228void MainWindow::critical(QString title, QString msg) {
1229 QMessageBox::critical(this, title, msg);
1230}
The ConfigDialog handles the configuration interface.
Definition: configdialog.h:24
void emptyClicked()
emptyClicked event
QString getRemainingDataForDisplay() const
Definition: filecontent.cpp:42
QString getPassword() const
Definition: filecontent.cpp:36
NamedValues getNamedValues() const
Definition: filecontent.cpp:38
static FileContent parse(const QString &fileContent, const QStringList &templateFields, bool allFields)
parse parses the given fileContent in a FileContent object. The password is accessible through getPas...
Definition: filecontent.cpp:7
The MainWindow class does way too much, not only is it a switchboard, configuration handler and more,...
Definition: mainwindow.h:37
void startReencryptPath()
MainWindow::startReencryptPath disable ui elements and treeview.
void passShowHandler(const QString &)
Definition: mainwindow.cpp:383
void endReencryptPath()
MainWindow::endReencryptPath re-enable ui elements.
void executeWrapperStarted()
Definition: mainwindow.cpp:376
void showStatusMessage(QString msg, int timeout=2000)
Displays message in status bar.
void closeEvent(QCloseEvent *event)
MainWindow::closeEvent hide or quit.
Definition: mainwindow.cpp:846
MainWindow(const QString &searchText=QString(), QWidget *parent=nullptr)
MainWindow::MainWindow handles all of the main functionality and also the main window.
Definition: mainwindow.cpp:36
void onPush()
MainWindow::onPush do a git push.
Definition: mainwindow.cpp:302
void critical(QString, QString)
MainWindow::critical critical message popup wrapper.
void generateGPGKeyPair(QString batch)
void passOtpHandler(const QString &)
Definition: mainwindow.cpp:429
void restoreWindow()
Definition: mainwindow.cpp:478
void keyPressEvent(QKeyEvent *event)
MainWindow::keyPressEvent did anyone press return, enter or escape?
Definition: mainwindow.cpp:885
bool eventFilter(QObject *obj, QEvent *event)
MainWindow::eventFilter filter out some events and focus the treeview.
Definition: mainwindow.cpp:871
const QModelIndex getCurrentTreeViewIndex()
Definition: mainwindow.cpp:201
void flashText(const QString &text, const bool isError, const bool isHtml=false)
Definition: mainwindow.cpp:210
void setUiElementsEnabled(bool state)
MainWindow::setUiElementsEnabled enable or disable the relevant UI elements.
Definition: mainwindow.cpp:462
void changeEvent(QEvent *event)
MainWindow::changeEvent sets focus to the search box.
Definition: mainwindow.cpp:145
void cleanKeygenDialog()
Definition: mainwindow.cpp:205
void deselect()
MainWindow::deselect clear the selection, password and copy buffer.
Definition: mainwindow.cpp:366
void on_treeView_clicked(const QModelIndex &index)
MainWindow::on_treeView_clicked read the selected password file.
Definition: mainwindow.cpp:332
void generateKeyPair(QString, QDialog *)
MainWindow::generateKeyPair internal gpg keypair generator . .
Definition: mainwindow.cpp:753
void userDialog(QString="")
MainWindow::userDialog see MainWindow::onUsers()
Definition: mainwindow.cpp:709
void config()
MainWindow::config pops up the configuration screen and handles all inter-window communication.
Definition: mainwindow.cpp:230
void messageAvailable(QString message)
MainWindow::messageAvailable we have some text/message/search to do.
Definition: mainwindow.cpp:736
void passShowHandlerFinished(QString output)
The NamedValues class is mostly a list of NamedValue but also has a method to take a specific NamedVa...
Definition: filecontent.h:17
void finishedShow(const QString &)
virtual void GitPull()=0
virtual void Remove(QString file, bool isDir)=0
virtual void Show(QString file)=0
virtual void GitPull_b()=0
virtual void GitPush()=0
virtual void Move(const QString srcDir, const QString dest, const bool force=false)=0
virtual void OtpGenerate(QString file)=0
void updateEnv()
Pass::updateEnv update the execution environment (used when switching profiles)
Definition: pass.cpp:248
PasswordDialog Handles the inserting and editing of passwords.
Stylish widget to display the field as QR Code.
void clicked(QString)
Stylish widget to allow copying of password and account details.
Definition: qtpass.h:10
void clearClippedText()
Definition: qtpass.cpp:357
void setClipboardTimer()
Definition: qtpass.cpp:359
void clearClipboard()
MainWindow::clearClipboard remove clipboard contents.
Definition: qtpass.cpp:366
bool init()
QtPass::init make sure we are ready to go as soon as possible.
Definition: qtpass.cpp:61
void setFreshStart(const bool &fs)
Definition: qtpass.h:22
void setClippedText(const QString &, const QString &p_output=QString())
Definition: qtpass.cpp:349
bool isFreshStart()
Definition: qtpass.h:21
void copyTextToClipboard(const QString &text)
MainWindow::copyTextToClipboard copies text to your clipboard.
Definition: qtpass.cpp:391
void showTextAsQRCode(const QString &text)
displays the text as qrcode
Definition: qtpass.cpp:410
static void setMaximized(const bool &maximized)
static bool isUseAutoclearPanel(const bool &defaultValue=QVariant().toBool())
static bool isUseTemplate(const bool &defaultValue=QVariant().toBool())
static void setProfile(const QString &profile)
static bool isTemplateAllFields(const bool &defaultValue=QVariant().toBool())
static int getAutoclearPanelSeconds(const int &defaultValue=QVariant().toInt())
static bool isUseOtp(const bool &defaultValue=QVariant().toBool())
static void setPassStore(const QString &passStore)
static bool isUseGit(const bool &defaultValue=QVariant().toBool())
static bool isAlwaysOnTop(const bool &defaultValue=QVariant().toBool())
static QString getPassTemplate(const QString &defaultValue=QVariant().toString())
static QString getProfile(const QString &defaultValue=QVariant().toString())
static bool isHideOnClose(const bool &defaultValue=QVariant().toBool())
static QByteArray getGeometry(const QByteArray &defaultValue=QVariant().toByteArray())
static void setPassSigningKey(const QString &passSigningKey)
static bool isAutoPull(const bool &defaultValue=QVariant().toBool())
static QByteArray getSavestate(const QByteArray &defaultValue=QVariant().toByteArray())
static bool isUseTrayIcon(const bool &defaultValue=QVariant().toBool())
static bool isDisplayAsIs(const bool &defaultValue=QVariant().toBool())
static QHash< QString, QHash< QString, QString > > getProfiles()
static bool isStartMinimized(const bool &defaultValue=QVariant().toBool())
static void setPos(const QPoint &pos)
static Pass * getPass()
static Enums::clipBoardType getClipBoardType(const Enums::clipBoardType &defaultvalue=Enums::CLIPBOARD_NEVER)
static void setUsePass(const bool &usePass)
static bool isUseMonospace(const bool &defaultValue=QVariant().toBool())
static bool isUseQrencode(const bool &defaultValue=QVariant().toBool())
static bool isHideContent(const bool &defaultValue=QVariant().toBool())
static QString getPassStore(const QString &defaultValue=QVariant().toString())
static void setSavestate(const QByteArray &saveState)
static bool isNoLineWrapping(const bool &defaultValue=QVariant().toBool())
static QPoint getPos(const QPoint &defaultValue=QVariant().toPoint())
static QSize getSize(const QSize &defaultValue=QVariant().toSize())
static void setSize(const QSize &size)
static bool isMaximized(const bool &defaultValue=QVariant().toBool())
static void setGeometry(const QByteArray &geometry)
static QString getPassExecutable(const QString &defaultValue=QVariant().toString())
static QString getGitExecutable(const QString &defaultValue=QVariant().toString())
static bool isHidePassword(const bool &defaultValue=QVariant().toBool())
void setModelAndStore(QFileSystemModel *sourceModel, QString passStore)
StoreModel::setModelAndStore update the source model and store.
Definition: storemodel.cpp:84
Handles the systemtray icon and menu.
Definition: trayicon.h:14
bool getIsAllocated()
TrayIcon::getIsAllocated return if TrayIcon is allocated.
Definition: trayicon.cpp:57
Handles listing and editing of GPG users.
Definition: usersdialog.h:23
static bool checkConfig()
Util::checkConfig do we have prequisite settings?
Definition: util.cpp:142
static QString findPasswordStore()
Util::findPasswordStore look for common .password-store folder location.
Definition: util.cpp:56
static QString getDir(const QModelIndex &index, bool forPass, const QFileSystemModel &model, const StoreModel &storeModel)
Util::getDir get selectd folder path.
Definition: util.cpp:160
static const QRegularExpression & protocolRegex()
Definition: util.cpp:203
static const QRegularExpression & endsWithGpg()
Definition: util.cpp:198
#define dbg()
Definition: debughelper.h:7
@ CLIPBOARD_NEVER
Definition: enums.h:11
Definition: configdialog.h:9
QString name
Definition: filecontent.h:9
QString value
Definition: filecontent.h:10