18#include "ui_mainwindow.h"
21#include <QApplication>
23#include <QDesktopServices>
25#include <QDirIterator>
27#include <QInputDialog>
43 : QMainWindow(parent), ui(new
Ui::
MainWindow), keygen(nullptr),
48 qt_set_sequence_auto_mnemonic(
true);
52 m_qtPass =
new QtPass(
this);
55 new QShortcut(QKeySequence(Qt::CTRL | Qt::Key_Q),
this, SLOT(close()));
57 new QShortcut(QKeySequence(QKeySequence::StandardKey::Copy),
this,
58 SLOT(copyPasswordFromTreeview()));
60 model.setNameFilters(QStringList() <<
"*.gpg");
61 model.setNameFilterDisables(
false);
72 QModelIndex rootDir = model.setRootPath(passStore);
73 model.fetchMore(rootDir);
75 proxyModel.setModelAndStore(&model, passStore);
76 selectionModel.reset(
new QItemSelectionModel(&proxyModel));
78 ui->treeView->setModel(&proxyModel);
79 ui->treeView->setRootIndex(proxyModel.mapFromSource(rootDir));
80 ui->treeView->setColumnHidden(1,
true);
81 ui->treeView->setColumnHidden(2,
true);
82 ui->treeView->setColumnHidden(3,
true);
83 ui->treeView->setHeaderHidden(
true);
84 ui->treeView->setIndentation(15);
85 ui->treeView->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
86 ui->treeView->setContextMenuPolicy(Qt::CustomContextMenu);
87 ui->treeView->header()->setSectionResizeMode(0, QHeaderView::Stretch);
88 ui->treeView->sortByColumn(0, Qt::AscendingOrder);
89 connect(ui->treeView, &QWidget::customContextMenuRequested,
this,
90 &MainWindow::showContextMenu);
95 QFont monospace(
"Monospace");
96 monospace.setStyleHint(QFont::Monospace);
97 ui->textBrowser->setFont(monospace);
100 ui->textBrowser->setLineWrapMode(QTextBrowser::NoWrap);
102 ui->textBrowser->setOpenExternalLinks(
true);
103 ui->textBrowser->setContextMenuPolicy(Qt::CustomContextMenu);
104 connect(ui->textBrowser, &QWidget::customContextMenuRequested,
this,
105 &MainWindow::showBrowserContextMenu);
112 clearPanelTimer.setSingleShot(
true);
113 connect(&clearPanelTimer, &QTimer::timeout,
this, [
this]() { clearPanel(); });
115 searchTimer.setInterval(350);
116 searchTimer.setSingleShot(
true);
118 connect(&searchTimer, &QTimer::timeout,
this, &MainWindow::onTimeoutSearch);
120 initToolBarButtons();
123 ui->lineEdit->setClearButtonEnabled(
true);
124 updateGrepButtonVisibility();
128 QTimer::singleShot(10,
this, SLOT(focusInput()));
130 ui->lineEdit->setText(searchText);
132 if (!m_qtPass->init()) {
134 QApplication::quit();
146void MainWindow::focusInput() {
147 ui->lineEdit->selectAll();
148 ui->lineEdit->setFocus();
156 QWidget::changeEvent(event);
157 if (event->type() == QEvent::ActivationChange) {
158 if (isActiveWindow()) {
167void MainWindow::initToolBarButtons() {
168 connect(ui->actionAddPassword, &QAction::triggered,
this,
169 &MainWindow::addPassword);
170 connect(ui->actionAddFolder, &QAction::triggered,
this,
171 &MainWindow::addFolder);
172 connect(ui->actionEdit, &QAction::triggered,
this, &MainWindow::onEdit);
173 connect(ui->actionDelete, &QAction::triggered,
this, &MainWindow::onDelete);
175 connect(ui->actionUpdate, &QAction::triggered,
this, &MainWindow::onUpdate);
176 connect(ui->actionUsers, &QAction::triggered,
this, &MainWindow::onUsers);
177 connect(ui->actionConfig, &QAction::triggered,
this, &MainWindow::onConfig);
178 connect(ui->actionOtp, &QAction::triggered,
this, &MainWindow::onOtp);
180 ui->actionAddPassword->setIcon(
181 QIcon::fromTheme(
"document-new", QIcon(
":/icons/document-new.svg")));
182 ui->actionAddFolder->setIcon(
183 QIcon::fromTheme(
"folder-new", QIcon(
":/icons/folder-new.svg")));
184 ui->actionEdit->setIcon(QIcon::fromTheme(
185 "document-properties", QIcon(
":/icons/document-properties.svg")));
186 ui->actionDelete->setIcon(
187 QIcon::fromTheme(
"edit-delete", QIcon(
":/icons/edit-delete.svg")));
188 ui->actionPush->setIcon(
189 QIcon::fromTheme(
"go-up", QIcon(
":/icons/go-top.svg")));
190 ui->actionUpdate->setIcon(
191 QIcon::fromTheme(
"go-down", QIcon(
":/icons/go-bottom.svg")));
192 ui->actionUsers->setIcon(QIcon::fromTheme(
193 "x-office-address-book", QIcon(
":/icons/x-office-address-book.svg")));
194 ui->actionConfig->setIcon(QIcon::fromTheme(
195 "applications-system", QIcon(
":/icons/applications-system.svg")));
201void MainWindow::initStatusBar() {
202 ui->statusBar->showMessage(tr(
"Welcome to QtPass %1").arg(VERSION), 2000);
204 QPixmap logo = QPixmap::fromImage(QImage(
":/artwork/icon.svg"))
205 .scaledToHeight(statusBar()->height());
206 auto *logoApp =
new QLabel(statusBar());
207 logoApp->setPixmap(logo);
208 statusBar()->addPermanentWidget(logoApp);
212 return ui->treeView->currentIndex();
216 this->keygen->close();
217 this->keygen =
nullptr;
237 ui->textBrowser->setTextColor(Qt::red);
241 QString _text = text;
242 if (!ui->textBrowser->toPlainText().isEmpty()) {
243 _text = ui->textBrowser->toHtml() + _text;
245 ui->textBrowser->setHtml(_text);
247 ui->textBrowser->setText(text);
255void MainWindow::applyTextBrowserSettings() {
257 QFont monospace(
"Monospace");
258 monospace.setStyleHint(QFont::Monospace);
259 ui->textBrowser->setFont(monospace);
261 ui->textBrowser->setFont(QFont());
265 ui->textBrowser->setLineWrapMode(QTextBrowser::NoWrap);
267 ui->textBrowser->setLineWrapMode(QTextBrowser::WidgetWidth);
271void MainWindow::applyWindowFlagsSettings() {
273 Qt::WindowFlags flags = windowFlags();
274 this->setWindowFlags(flags | Qt::WindowStaysOnTopHint);
276 this->setWindowFlags(Qt::Window);
293 if (m_qtPass->isFreshStart() &&
298 if (m_qtPass->isFreshStart()) {
302 if (d->result() == QDialog::Accepted) {
303 applyTextBrowserSettings();
304 applyWindowFlagsSettings();
308 proxyModel.setStore(passStore);
309 ui->treeView->setRootIndex(
310 proxyModel.mapFromSource(model.setRootPath(passStore)));
312 ui->treeView->setCurrentIndex(QModelIndex());
320 m_qtPass->setClipboardTimer();
322 updateGitButtonVisibility();
323 updateOtpButtonVisibility();
324 updateGrepButtonVisibility();
332 m_qtPass->setFreshStart(
false);
339void MainWindow::onUpdate(
bool block) {
340 ui->statusBar->showMessage(tr(
"Updating password-store"), 2000);
353 ui->statusBar->showMessage(tr(
"Updating password-store"), 2000);
365auto MainWindow::getFile(
const QModelIndex &index,
bool forPass) -> QString {
366 if (!index.isValid() ||
367 !model.fileInfo(proxyModel.mapToSource(index)).isFile()) {
370 QString filePath = model.filePath(proxyModel.mapToSource(index));
383 bool cleared = ui->treeView->currentIndex().flags() == Qt::NoItemFlags;
385 Util::getDir(ui->treeView->currentIndex(),
false, model, proxyModel);
387 m_qtPass->clearClippedText();
388 QString file = getFile(index,
true);
389 ui->passwordName->setText(file);
390 if (!file.isEmpty() && !cleared) {
394 ui->actionEdit->setEnabled(
false);
395 ui->actionDelete->setEnabled(
true);
404void MainWindow::on_treeView_doubleClicked(
const QModelIndex &index) {
405 QFileInfo fileOrFolder =
406 model.fileInfo(proxyModel.mapToSource(ui->treeView->currentIndex()));
408 if (fileOrFolder.isFile()) {
409 editPassword(getFile(index,
true));
418 m_qtPass->clearClipboard();
419 ui->treeView->clearSelection();
420 ui->actionEdit->setEnabled(
false);
421 ui->actionDelete->setEnabled(
false);
422 ui->passwordName->setText(
"");
427 clearTemplateWidgets();
428 ui->textBrowser->clear();
430 clearPanelTimer.stop();
450 QString output = p_output;
454 m_qtPass->setClippedText(password, p_output);
457 clearTemplateWidgets();
461 output =
"***" + tr(
"Content hidden") +
"***";
463 if (!password.isEmpty()) {
465 addToGridLayout(0, tr(
"Password"), password);
469 for (
int j = 0; j < namedValues.length(); ++j) {
471 addToGridLayout(j + 1, nv.
name, nv.
value);
473 if (ui->gridLayout->count() == 0) {
474 ui->verticalLayoutPassword->setSpacing(0);
476 ui->verticalLayoutPassword->setSpacing(6);
483 clearPanelTimer.start();
501 if (!p_output.isEmpty()) {
502 addToGridLayout(ui->gridLayout->count() + 1, tr(
"OTP Code"), p_output);
503 m_qtPass->copyTextToClipboard(p_output);
506 flashText(tr(
"No OTP code found in this password entry"),
true);
509 clearPanelTimer.start();
517void MainWindow::clearPanel(
bool notify) {
518 while (ui->gridLayout->count() > 0) {
519 QLayoutItem *item = ui->gridLayout->takeAt(0);
520 delete item->widget();
523 const bool grepWasVisible = ui->grepResultsList->isVisible();
524 ui->grepResultsList->clear();
525 if (grepWasVisible) {
526 ui->grepResultsList->setVisible(
false);
527 ui->treeView->setVisible(
true);
530 ui->grepButton->blockSignals(
true);
531 ui->grepButton->setChecked(
false);
532 ui->grepButton->blockSignals(
false);
533 ui->lineEdit->blockSignals(
true);
534 ui->lineEdit->clear();
535 ui->lineEdit->blockSignals(
false);
536 ui->lineEdit->setPlaceholderText(tr(
"Search Password"));
540 QString output =
"***" + tr(
"Password and Content hidden") +
"***";
541 ui->textBrowser->setHtml(output);
543 ui->textBrowser->setHtml(
"");
553 ui->treeView->setEnabled(state);
554 ui->lineEdit->setEnabled(state);
555 ui->lineEdit->installEventFilter(
this);
556 ui->actionAddPassword->setEnabled(state);
557 ui->actionAddFolder->setEnabled(state);
558 ui->actionUsers->setEnabled(state);
559 ui->actionConfig->setEnabled(state);
561 state &= ui->treeView->currentIndex().isValid();
562 ui->actionDelete->setEnabled(state);
563 ui->actionEdit->setEnabled(state);
564 updateGitButtonVisibility();
565 updateOtpButtonVisibility();
579 restoreGeometry(geometry);
581 restoreState(savestate);
591 Qt::WindowFlags flags = windowFlags();
592 setWindowFlags(flags | Qt::WindowStaysOnTopHint);
600 QTimer::singleShot(10,
this, SLOT(hide()));
610void MainWindow::onConfig() {
config(); }
617void MainWindow::on_lineEdit_textChanged(
const QString &arg1) {
620 ui->statusBar->showMessage(tr(
"Looking for: %1").arg(arg1), 1000);
621 ui->treeView->expandAll();
623 ui->passwordName->setText(
"");
624 ui->actionEdit->setEnabled(
false);
625 ui->actionDelete->setEnabled(
false);
633void MainWindow::onTimeoutSearch() {
634 QString query = ui->lineEdit->text();
636 if (query.isEmpty()) {
637 ui->treeView->collapseAll();
641 query.replace(QStringLiteral(
" "),
".*");
642 QRegularExpression regExp(query, QRegularExpression::CaseInsensitiveOption);
643 proxyModel.setFilterRegularExpression(regExp);
644 ui->treeView->setRootIndex(proxyModel.mapFromSource(
647 if (proxyModel.rowCount() > 0 && !query.isEmpty()) {
650 ui->actionEdit->setEnabled(
false);
651 ui->actionDelete->setEnabled(
false);
660void MainWindow::on_lineEdit_returnPressed() {
662 dbg() <<
"on_lineEdit_returnPressed" << proxyModel.rowCount();
666 const QString query = ui->lineEdit->text();
667 if (!query.isEmpty()) {
668 m_grepCancelled =
false;
669 ui->grepResultsList->clear();
670 ui->statusBar->showMessage(tr(
"Searching…"));
673 QApplication::setOverrideCursor(Qt::WaitCursor);
677 m_grepCancelled =
true;
680 QApplication::restoreOverrideCursor();
682 ui->grepResultsList->clear();
683 ui->grepResultsList->setVisible(
false);
684 ui->treeView->setVisible(
true);
689 if (proxyModel.rowCount() > 0) {
698void MainWindow::on_grepButton_toggled(
bool checked) {
699 m_grepMode = checked;
701 ui->lineEdit->setPlaceholderText(tr(
"Search content (regex)"));
702 ui->lineEdit->clear();
704 proxyModel.setFilterRegularExpression(QRegularExpression());
705 ui->treeView->setRootIndex(proxyModel.mapFromSource(
707 ui->grepResultsList->setVisible(
false);
712 m_grepCancelled =
true;
713 QApplication::restoreOverrideCursor();
716 ui->lineEdit->blockSignals(
true);
717 ui->lineEdit->clear();
718 ui->lineEdit->blockSignals(
false);
719 ui->lineEdit->setPlaceholderText(tr(
"Search Password"));
720 ui->grepResultsList->clear();
721 ui->grepResultsList->setVisible(
false);
722 ui->treeView->setVisible(
true);
723 proxyModel.setFilterRegularExpression(QRegularExpression());
724 ui->treeView->setRootIndex(proxyModel.mapFromSource(
733 const QList<QPair<QString, QStringList>> &results) {
736 QApplication::restoreOverrideCursor();
738 if (m_grepCancelled) {
739 m_grepCancelled =
false;
745 ui->grepResultsList->clear();
746 if (results.isEmpty()) {
747 ui->statusBar->showMessage(tr(
"No matches found."), 3000);
748 ui->grepResultsList->setVisible(
false);
749 ui->treeView->setVisible(
true);
754 for (
const auto &pair : results) {
755 QTreeWidgetItem *entryItem =
new QTreeWidgetItem(ui->grepResultsList);
756 entryItem->setText(0, pair.first);
757 entryItem->setData(0, Qt::UserRole, pair.first);
758 for (
const QString &line : pair.second) {
759 QTreeWidgetItem *lineItem =
new QTreeWidgetItem(entryItem);
760 lineItem->setText(0, hideContent ?
"***" + tr(
"Content hidden") +
"***"
762 lineItem->setData(0, Qt::UserRole, pair.first);
766 ui->grepResultsList->expandAll();
767 ui->treeView->setVisible(
false);
768 ui->grepResultsList->setVisible(
true);
769 ui->statusBar->showMessage(
770 tr(
"Found %n match(es) in %1 entr(ies).",
nullptr, totalLines)
771 .arg(results.size()),
774 clearPanelTimer.start();
780void MainWindow::on_grepResultsList_itemClicked(QTreeWidgetItem *item,
782 const QString entry = item->data(0, Qt::UserRole).toString();
785 const QString fullPath = QDir::cleanPath(
787 QModelIndex srcIndex = model.index(fullPath);
788 if (!srcIndex.isValid())
790 QModelIndex proxyIndex = proxyModel.mapFromSource(srcIndex);
791 if (!proxyIndex.isValid())
793 ui->treeView->setCurrentIndex(proxyIndex);
796 ui->grepResultsList->clear();
797 ui->grepResultsList->setVisible(
false);
798 ui->treeView->setVisible(
true);
799 ui->treeView->scrollTo(proxyIndex);
800 ui->treeView->setFocus();
807void MainWindow::selectFirstFile() {
808 QModelIndex index = proxyModel.mapFromSource(
810 index = firstFile(index);
811 ui->treeView->setCurrentIndex(index);
819auto MainWindow::firstFile(QModelIndex parentIndex) -> QModelIndex {
820 QModelIndex index = parentIndex;
821 int numRows = proxyModel.rowCount(parentIndex);
822 for (
int row = 0; row < numRows; ++row) {
823 index = proxyModel.index(row, 0, parentIndex);
824 if (model.fileInfo(proxyModel.mapToSource(index)).isFile()) {
827 if (proxyModel.hasChildren(index)) {
828 return firstFile(index);
839void MainWindow::setPassword(
const QString &file,
bool isNew) {
840 PasswordDialog d(file, isNew,
this);
843 ui->treeView->setFocus();
851void MainWindow::addPassword() {
854 Util::getDir(ui->treeView->currentIndex(),
true, model, proxyModel);
856 QInputDialog::getText(
this, tr(
"New file"),
857 tr(
"New password file: \n(Will be placed in %1 )")
860 true, model, proxyModel)),
861 QLineEdit::Normal,
"", &ok);
862 if (!ok || file.isEmpty()) {
873void MainWindow::onDelete() {
874 QModelIndex currentIndex = ui->treeView->currentIndex();
875 if (!currentIndex.isValid()) {
882 QFileInfo fileOrFolder =
883 model.fileInfo(proxyModel.mapToSource(ui->treeView->currentIndex()));
887 if (fileOrFolder.isFile()) {
888 file = getFile(ui->treeView->currentIndex(),
true);
890 file =
Util::getDir(ui->treeView->currentIndex(),
true, model, proxyModel);
894 QString dirMessage = tr(
" and the whole content?");
896 QDirIterator it(model.rootPath() + QDir::separator() + file,
897 QDirIterator::Subdirectories);
899 while (it.hasNext() && okDir) {
901 if (QFileInfo(it.filePath()).isFile()) {
902 if (QFileInfo(it.filePath()).suffix() !=
"gpg") {
904 dirMessage = tr(
" and the whole content? <br><strong>Attention: "
905 "there are unexpected files in the given folder, "
906 "check them before continue.</strong>");
912 if (QMessageBox::question(
913 this, isDir ? tr(
"Delete folder?") : tr(
"Delete password?"),
914 tr(
"Are you sure you want to delete %1%2?")
915 .arg(QDir::separator() + file, isDir ? dirMessage :
"?"),
916 QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) {
926void MainWindow::onOtp() {
927 QString file = getFile(ui->treeView->currentIndex(),
true);
928 if (!file.isEmpty()) {
934 flashText(tr(
"No password selected for OTP generation"),
true);
941void MainWindow::onEdit() {
942 QString file = getFile(ui->treeView->currentIndex(),
true);
951 if (!dir.isEmpty()) {
962void MainWindow::onUsers() {
965 ?
Util::getDir(ui->treeView->currentIndex(),
false, model, proxyModel)
970 ui->treeView->setFocus();
979 if (message.isEmpty()) {
982 ui->treeView->expandAll();
983 ui->lineEdit->setText(message);
984 on_lineEdit_returnPressed();
996 keygen = keygenWindow;
1004void MainWindow::updateProfileBox() {
1005 QHash<QString, QHash<QString, QString>> profiles =
1008 if (profiles.isEmpty()) {
1009 ui->profileWidget->hide();
1011 ui->profileWidget->show();
1012 ui->profileBox->setEnabled(profiles.size() > 1);
1013 ui->profileBox->clear();
1014 QHashIterator<QString, QHash<QString, QString>> i(profiles);
1015 while (i.hasNext()) {
1017 if (!i.key().isEmpty()) {
1018 ui->profileBox->addItem(i.key());
1021 ui->profileBox->model()->sort(0);
1025 ui->profileBox->setCurrentIndex(index);
1034#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
1035void MainWindow::on_profileBox_currentIndexChanged(QString name) {
1048void MainWindow::on_profileBox_currentTextChanged(
const QString &name) {
1054 ui->lineEdit->clear();
1062 ui->statusBar->showMessage(tr(
"Profile changed to %1").arg(name), 2000);
1067 proxyModel.setStore(passStore);
1068 ui->treeView->setRootIndex(
1069 proxyModel.mapFromSource(model.setRootPath(passStore)));
1071 ui->treeView->setCurrentIndex(QModelIndex());
1079void MainWindow::initTrayIcon() {
1080 this->tray =
new TrayIcon(
this);
1083 if (tray ==
nullptr) {
1085 dbg() <<
"Allocating tray icon failed.";
1089 if (!tray->getIsAllocated()) {
1097void MainWindow::destroyTrayIcon() {
1111 m_qtPass->clearClipboard();
1116 if (!isMaximized()) {
1132 if (obj == ui->lineEdit && event->type() == QEvent::KeyPress) {
1133 auto *key =
dynamic_cast<QKeyEvent *
>(event);
1134 if (key !=
nullptr && key->key() == Qt::Key_Down) {
1135 ui->treeView->setFocus();
1138 return QObject::eventFilter(obj, event);
1146 switch (event->key()) {
1147 case Qt::Key_Delete:
1150 case Qt::Key_Return:
1152 if (proxyModel.rowCount() > 0) {
1156 case Qt::Key_Escape:
1157 ui->lineEdit->clear();
1169void MainWindow::showContextMenu(
const QPoint &pos) {
1170 QModelIndex index = ui->treeView->indexAt(pos);
1171 bool selected =
true;
1172 if (!index.isValid()) {
1173 ui->treeView->clearSelection();
1174 ui->actionDelete->setEnabled(
false);
1175 ui->actionEdit->setEnabled(
false);
1180 ui->treeView->setCurrentIndex(index);
1182 QPoint globalPos = ui->treeView->viewport()->mapToGlobal(pos);
1184 QFileInfo fileOrFolder =
1185 model.fileInfo(proxyModel.mapToSource(ui->treeView->currentIndex()));
1188 if (!selected || fileOrFolder.isDir()) {
1189 QAction *openFolder =
1190 contextMenu.addAction(tr(
"Open folder with file manager"));
1191 QAction *addFolder = contextMenu.addAction(tr(
"Add folder"));
1192 QAction *addPassword = contextMenu.addAction(tr(
"Add password"));
1193 QAction *users = contextMenu.addAction(tr(
"Users"));
1194 connect(openFolder, &QAction::triggered,
this, &MainWindow::openFolder);
1195 connect(addFolder, &QAction::triggered,
this, &MainWindow::addFolder);
1196 connect(addPassword, &QAction::triggered,
this, &MainWindow::addPassword);
1197 connect(users, &QAction::triggered,
this, &MainWindow::onUsers);
1198 }
else if (fileOrFolder.isFile()) {
1199 QAction *edit = contextMenu.addAction(tr(
"Edit"));
1200 connect(edit, &QAction::triggered,
this, &MainWindow::onEdit);
1203 contextMenu.addSeparator();
1204 if (fileOrFolder.isDir()) {
1205 QAction *renameFolder = contextMenu.addAction(tr(
"Rename folder"));
1206 connect(renameFolder, &QAction::triggered,
this,
1207 &MainWindow::renameFolder);
1208 }
else if (fileOrFolder.isFile()) {
1209 QAction *renamePassword = contextMenu.addAction(tr(
"Rename password"));
1210 connect(renamePassword, &QAction::triggered,
this,
1211 &MainWindow::renamePassword);
1213 QAction *deleteItem = contextMenu.addAction(tr(
"Delete"));
1214 connect(deleteItem, &QAction::triggered,
this, &MainWindow::onDelete);
1215 if (fileOrFolder.isDir()) {
1216 QString dirPath = QDir::cleanPath(
1217 Util::getDir(ui->treeView->currentIndex(),
false, model, proxyModel));
1218 QAction *reencrypt = contextMenu.addAction(tr(
"Re-encrypt"));
1219 connect(reencrypt, &QAction::triggered,
this,
1220 [
this, dirPath]() { reencryptPath(dirPath); });
1223 contextMenu.exec(globalPos);
1231void MainWindow::showBrowserContextMenu(
const QPoint &pos) {
1232 QMenu *contextMenu = ui->textBrowser->createStandardContextMenu(pos);
1233 QPoint globalPos = ui->textBrowser->viewport()->mapToGlobal(pos);
1235 contextMenu->exec(globalPos);
1242void MainWindow::openFolder() {
1244 Util::getDir(ui->treeView->currentIndex(),
false, model, proxyModel);
1246 QString path = QDir::toNativeSeparators(dir);
1247 QDesktopServices::openUrl(QUrl::fromLocalFile(path));
1253void MainWindow::addFolder() {
1256 Util::getDir(ui->treeView->currentIndex(),
false, model, proxyModel);
1258 QInputDialog::getText(
this, tr(
"New file"),
1259 tr(
"New Folder: \n(Will be placed in %1 )")
1262 true, model, proxyModel)),
1263 QLineEdit::Normal,
"", &ok);
1264 if (!ok || newdir.isEmpty()) {
1267 newdir.prepend(dir);
1268 if (!QDir().mkdir(newdir)) {
1269 QMessageBox::warning(
this, tr(
"Error"),
1270 tr(
"Failed to create folder: %1").arg(newdir));
1274 QString gpgIdFile = newdir +
"/.gpg-id";
1275 QFile gpgId(gpgIdFile);
1276 if (!gpgId.open(QIODevice::WriteOnly)) {
1277 QMessageBox::warning(
1279 tr(
"Failed to create .gpg-id file in: %1").arg(newdir));
1283 for (
const UserInfo &user : users) {
1285 gpgId.write((user.key_id +
"\n").toUtf8());
1295void MainWindow::renameFolder() {
1297 QString srcDir = QDir::cleanPath(
1298 Util::getDir(ui->treeView->currentIndex(),
false, model, proxyModel));
1299 QString srcDirName = QDir(srcDir).dirName();
1301 QInputDialog::getText(
this, tr(
"Rename file"), tr(
"Rename Folder To: "),
1302 QLineEdit::Normal, srcDirName, &ok);
1303 if (!ok || newName.isEmpty()) {
1306 QString destDir = srcDir;
1307 destDir.replace(srcDir.lastIndexOf(srcDirName), srcDirName.length(), newName);
1315void MainWindow::editPassword(
const QString &file) {
1316 if (!file.isEmpty()) {
1320 setPassword(file,
false);
1327void MainWindow::renamePassword() {
1329 QString file = getFile(ui->treeView->currentIndex(),
false);
1330 QString filePath = QFileInfo(file).path();
1331 QString fileName = QFileInfo(file).fileName();
1332 if (fileName.endsWith(
".gpg", Qt::CaseInsensitive)) {
1337 QInputDialog::getText(
this, tr(
"Rename file"), tr(
"Rename File To: "),
1338 QLineEdit::Normal, fileName, &ok);
1339 if (!ok || newName.isEmpty()) {
1342 QString newFile = QDir(filePath).filePath(newName);
1350void MainWindow::clearTemplateWidgets() {
1351 while (ui->gridLayout->count() > 0) {
1352 QLayoutItem *item = ui->gridLayout->takeAt(0);
1353 delete item->widget();
1356 ui->verticalLayoutPassword->setSpacing(0);
1367void MainWindow::copyPasswordFromTreeview() {
1368 QFileInfo fileOrFolder =
1369 model.fileInfo(proxyModel.mapToSource(ui->treeView->currentIndex()));
1371 if (fileOrFolder.isFile()) {
1372 QString file = getFile(ui->treeView->currentIndex(),
true);
1375 &MainWindow::passwordFromFileToClipboard);
1377 &MainWindow::passwordFromFileToClipboard);
1382void MainWindow::passwordFromFileToClipboard(
const QString &text) {
1383 QStringList tokens = text.split(
'\n');
1384 m_qtPass->copyTextToClipboard(tokens[0]);
1393void MainWindow::addToGridLayout(
int position,
const QString &field,
1394 const QString &value) {
1395 QString trimmedField = field.trimmed();
1396 QString trimmedValue = value.trimmed();
1398 const QString buttonStyle =
1399 "border-style: none; background: transparent; padding: 0; margin: 0; "
1400 "icon-size: 16px; color: inherit;";
1403 auto *frame =
new QFrame();
1404 QLayout *ly =
new QHBoxLayout();
1405 ly->setContentsMargins(5, 2, 2, 2);
1407 frame->setLayout(ly);
1409 auto *fieldLabel =
new QPushButtonWithClipboard(trimmedValue,
this);
1413 fieldLabel->setStyleSheet(buttonStyle);
1414 frame->layout()->addWidget(fieldLabel);
1418 auto *qrbutton =
new QPushButtonAsQRCode(trimmedValue,
this);
1421 qrbutton->setStyleSheet(buttonStyle);
1422 frame->layout()->addWidget(qrbutton);
1426 const QString lineStyle =
1428 ?
"border-style: none; background: transparent; font-family: "
1430 :
"border-style: none; background: transparent;";
1433 auto *line =
new QLineEdit();
1434 line->setObjectName(trimmedField);
1435 line->setText(trimmedValue);
1436 line->setReadOnly(
true);
1437 line->setStyleSheet(lineStyle);
1438 line->setContentsMargins(0, 0, 0, 0);
1439 line->setEchoMode(QLineEdit::Password);
1440 auto *showButton =
new QPushButtonShowPassword(line,
this);
1441 showButton->setStyleSheet(buttonStyle);
1442 showButton->setContentsMargins(0, 0, 0, 0);
1443 frame->layout()->addWidget(showButton);
1444 frame->layout()->addWidget(line);
1446 auto *line =
new QTextBrowser();
1447 line->setOpenExternalLinks(
true);
1448 line->setOpenLinks(
true);
1449 line->setMaximumHeight(26);
1450 line->setMinimumHeight(26);
1451 line->setSizePolicy(
1452 QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum));
1453 line->setObjectName(trimmedField);
1455 line->setText(trimmedValue);
1456 line->setReadOnly(true);
1457 line->setStyleSheet(lineStyle);
1458 line->setContentsMargins(0, 0, 0, 0);
1459 frame->layout()->addWidget(line);
1462 frame->setStyleSheet(
1463 ".QFrame{border: 1px solid lightgrey; border-radius: 5px;}");
1466 ui->gridLayout->addWidget(
new QLabel(trimmedField), position, 0);
1467 ui->gridLayout->addWidget(frame, position, 1);
1477 ui->statusBar->showMessage(msg, timeout);
1484void MainWindow::reencryptPath(
const QString &dir) {
1486 if (!checkDir.exists()) {
1487 QMessageBox::critical(
this, tr(
"Error"),
1488 tr(
"Directory does not exist: %1").arg(dir));
1492 int ret = QMessageBox::question(
1493 this, tr(
"Re-encrypt passwords"),
1494 tr(
"Re-encrypt all passwords in %1?\n\n"
1495 "This will re-encrypt ALL password files in this folder "
1496 "using the current recipients defined in .gpg-id.\n\n"
1497 "This may rewrite many files and cannot be undone easily.\n\n"
1499 .arg(QDir(dir).dirName()),
1500 QMessageBox::Yes | QMessageBox::No);
1502 if (ret != QMessageBox::Yes)
1507 ui->treeView->setDisabled(
true);
1510 QDir::cleanPath(QDir(dir).absolutePath()));
1518 ui->treeView->setDisabled(
true);
1526void MainWindow::updateGitButtonVisibility() {
1530 enableGitButtons(
false);
1532 enableGitButtons(
true);
1536void MainWindow::updateOtpButtonVisibility() {
1537#if defined(Q_OS_WIN) || defined(__APPLE__)
1538 ui->actionOtp->setVisible(
false);
1541 ui->actionOtp->setEnabled(
false);
1543 ui->actionOtp->setEnabled(
true);
1547void MainWindow::updateGrepButtonVisibility() {
1549 ui->grepButton->setVisible(enabled);
1550 ui->grepCaseButton->setVisible(enabled);
1551 if (!enabled && m_grepMode) {
1552 ui->grepButton->setChecked(
false);
1556void MainWindow::enableGitButtons(
const bool &state) {
1558 ui->actionPush->setEnabled(state);
1559 ui->actionUpdate->setEnabled(state);
1568 QMessageBox::critical(
this, title, msg);
The ConfigDialog handles the configuration interface.
void emptyClicked()
emptyClicked event
auto getNamedValues() const -> NamedValues
Gets named value pairs from the parsed file.
auto getRemainingDataForDisplay() const -> QString
Gets remaining data for display (excludes hidden fields like OTP).
auto getPassword() const -> QString
Gets the password from the parsed file.
static auto parse(const QString &fileContent, const QStringList &templateFields, bool allFields) -> FileContent
parse parses the given fileContent in a FileContent object. The password is accessible through getPas...
void startReencryptPath()
MainWindow::startReencryptPath disable ui elements and treeview.
void closeEvent(QCloseEvent *event) override
MainWindow::closeEvent hide or quit.
void passShowHandler(const QString &)
void endReencryptPath()
MainWindow::endReencryptPath re-enable ui elements.
void onGrepFinished(const QList< QPair< QString, QStringList > > &results)
Display grep results in grepResultsList.
void executeWrapperStarted()
void generateKeyPair(const QString &, QDialog *)
MainWindow::generateKeyPair internal gpg keypair generator . .
void changeEvent(QEvent *event) override
MainWindow::changeEvent sets focus to the search box.
void critical(const QString &, const QString &)
MainWindow::critical critical message popup wrapper.
void messageAvailable(const QString &message)
MainWindow::messageAvailable we have some text/message/search to do.
MainWindow(const QString &searchText=QString(), QWidget *parent=nullptr)
MainWindow::MainWindow handles all of the main functionality and also the main window.
void keyPressEvent(QKeyEvent *event) override
MainWindow::keyPressEvent did anyone press return, enter or escape?
void onPush()
MainWindow::onPush do a git push.
void showStatusMessage(const QString &msg, int timeout=2000)
Displays message in status bar.
void passOtpHandler(const QString &)
void passShowHandlerFinished(const QString &output)
void generateGPGKeyPair(const QString &batch)
auto eventFilter(QObject *obj, QEvent *event) -> bool override
MainWindow::eventFilter filter out some events and focus the treeview.
void flashText(const QString &text, const bool isError, const bool isHtml=false)
void userDialog(const QString &="")
MainWindow::userDialog see MainWindow::onUsers().
void setUiElementsEnabled(bool state)
MainWindow::setUiElementsEnabled enable or disable the relevant UI elements.
auto getCurrentTreeViewIndex() -> QModelIndex
void deselect()
MainWindow::deselect clear the selection, password and copy buffer.
void on_treeView_clicked(const QModelIndex &index)
MainWindow::on_treeView_clicked read the selected password file.
The NamedValues class is mostly a list of NamedValue but also has a method to take a specific NamedVa...
void finishedShow(const QString &)
Emitted when show finishes.
void copyTextToClipboard(const QString &text)
MainWindow::copyTextToClipboard copies text to your clipboard.
void showTextAsQRCode(const QString &text)
displays the text as qrcode
static void setMaximized(const bool &maximized)
Save maximized state.
static auto isStartMinimized(const bool &defaultValue=QVariant().toBool()) -> bool
Check whether application should start minimized.
static auto isUseOtp(const bool &defaultValue=QVariant().toBool()) -> bool
Check whether OTP support is enabled.
static void setProfile(const QString &profile)
Save active profile name.
static auto isNoLineWrapping(const bool &defaultValue=QVariant().toBool()) -> bool
Get whether to disable line wrapping.
static void setPassStore(const QString &passStore)
Save password store path.
static auto isHideContent(const bool &defaultValue=QVariant().toBool()) -> bool
Get whether to hide content (password + username).
static auto getSize(const QSize &defaultValue=QVariant().toSize()) -> QSize
Get saved window size.
static auto getPass() -> Pass *
Get currently active pass backend instance.
static auto isUseQrencode(const bool &defaultValue=QVariant().toBool()) -> bool
Check whether qrencode support is enabled.
static auto isUseAutoclearPanel(const bool &defaultValue=QVariant().toBool()) -> bool
Get whether to use panel autoclear.
static auto isAutoPull(const bool &defaultValue=QVariant().toBool()) -> bool
Check whether automatic pull is enabled.
static auto isUseGit(const bool &defaultValue=QVariant().toBool()) -> bool
Check whether Git integration is enabled.
static auto getClipBoardType(const Enums::clipBoardType &defaultValue=Enums::CLIPBOARD_NEVER) -> Enums::clipBoardType
Get clipboard type as enum.
static auto isTemplateAllFields(const bool &defaultValue=QVariant().toBool()) -> bool
Check whether template applies to all fields.
static void setPassSigningKey(const QString &passSigningKey)
Save GPG signing key.
static auto getPassStore(const QString &defaultValue=QVariant().toString()) -> QString
Get password store directory path.
static auto isUseTemplate(const bool &defaultValue=QVariant().toBool()) -> bool
Check whether template usage is enabled.
static auto isUseTrayIcon(const bool &defaultValue=QVariant().toBool()) -> bool
Check whether tray icon support is enabled.
static auto isAddGPGId(const bool &defaultValue=QVariant().toBool()) -> bool
Get whether to auto-add GPG ID when receiving files.
static auto isUseGrepSearch(const bool &defaultValue=false) -> bool
Check whether content search (pass grep) is enabled.
static void setPos(const QPoint &pos)
Save window position.
static auto getPassTemplate(const QString &defaultValue=QVariant().toString()) -> QString
Get pass entry template.
static auto isMaximized(const bool &defaultValue=QVariant().toBool()) -> bool
Get maximized state.
static auto getProfile(const QString &defaultValue=QVariant().toString()) -> QString
Get active profile name.
static auto isUseMonospace(const bool &defaultValue=QVariant().toBool()) -> bool
Get whether to use monospace font.
static void setUsePass(const bool &usePass)
Save use pass setting.
static auto getAutoclearPanelSeconds(const int &defaultValue=QVariant().toInt()) -> int
Get panel autoclear delay in seconds.
static void setSavestate(const QByteArray &saveState)
Save window state.
static auto isAlwaysOnTop(const bool &defaultValue=QVariant().toBool()) -> bool
Check whether main window should stay always on top.
static auto isHidePassword(const bool &defaultValue=QVariant().toBool()) -> bool
Get whether to hide password in UI.
static auto getPassExecutable(const QString &defaultValue=QVariant().toString()) -> QString
Get pass executable path.
static auto getPos(const QPoint &defaultValue=QVariant().toPoint()) -> QPoint
Get saved window position.
static auto getImitatePass() -> ImitatePass *
Get imitate pass backend instance.
static auto isHideOnClose(const bool &defaultValue=QVariant().toBool()) -> bool
Check whether closing the window hides the application.
static auto getProfiles() -> QHash< QString, QHash< QString, QString > >
Get all configured profiles.
static void setSize(const QSize &size)
Save window size.
static void setGeometry(const QByteArray &geometry)
Save window geometry.
static auto getGitExecutable(const QString &defaultValue=QVariant().toString()) -> QString
Get git executable path.
static auto getGeometry(const QByteArray &defaultValue=QVariant().toByteArray()) -> QByteArray
Get saved window geometry.
static auto isDisplayAsIs(const bool &defaultValue=QVariant().toBool()) -> bool
Get whether to display password as-is (no modification).
static auto getSavestate(const QByteArray &defaultValue=QVariant().toByteArray()) -> QByteArray
Get saved window state.
Dialog for selecting GPG recipients for password encryption.
static auto protocolRegex() -> const QRegularExpression &
Returns a regex to match URL protocols.
static auto endsWithGpg() -> const QRegularExpression &
Returns a regex to match .gpg file extensions.
static auto findPasswordStore() -> QString
Locate the password store directory.
static auto getDir(const QModelIndex &index, bool forPass, const QFileSystemModel &model, const StoreModel &storeModel) -> QString
Get the selected folder path, either relative to the configured pass store or absolute.
static auto configIsValid() -> bool
Verify that the required configuration is complete.
Debug utilities for QtPass.
#define dbg()
Simple debug macro that includes file and line number.
constexpr int MS_PER_SECOND