diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 4575296..4c41cee 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1,959 +1,960 @@ /* SPDX-FileCopyrightText: 2014-2023 Anne Jan Brouwer SPDX-FileCopyrightText: 2016-2017 tezeb SPDX-FileCopyrightText: 2018 Lukas Vogel SPDX-FileCopyrightText: 2018 Claudio Maradonna SPDX-FileCopyrightText: 2019Maciej S. Szmigiero SPDX-FileCopyrightText: 2023 g10 Code GmbH SPDX-FileContributor: Sune Stolborg Vuorela SPDX-License-Identifier: GPL-3.0-or-later */ #include "mainwindow.h" #include #include "clipboardhelper.h" #include "configdialog.h" #include "filecontent.h" #include "pass.h" #include "passworddialog.h" #include "qpushbuttonfactory.h" #include "settings.h" #include "ui_mainwindow.h" #include "usersdialog.h" #include "util.h" #include #include #include #include #include #include #include #include #include #include #include static QString directoryName(const QString &dirOrFile) { QFileInfo fi{dirOrFile}; if (fi.isDir()) { return fi.absoluteFilePath(); } else { return fi.absolutePath(); } } /** * @brief MainWindow::MainWindow handles all of the main functionality and also * the main window. * @param searchText for searching from cli * @param parent pointer */ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , m_pass{std::make_unique()} , ui(new Ui::MainWindow) , proxyModel{*m_pass} { #ifdef __APPLE__ // extra treatment for mac os // see http://doc.qt.io/qt-5/qkeysequence.html#qt_set_sequence_auto_mnemonic qt_set_sequence_auto_mnemonic(true); #endif ui->setupUi(this); m_clipboardHelper = new ClipboardHelper(this); connect(m_pass.get(), &Pass::errorString, this, [this](auto str) { flashText(str, true); setUiElementsEnabled(true); }); connect(m_pass.get(), &Pass::critical, this, &MainWindow::critical); connect(m_pass.get(), &Pass::finishedShow, this, &MainWindow::passShowHandler); connect(m_pass.get(), &Pass::finishedInsert, this, [this]() { this->selectTreeItem(this->getCurrentTreeViewIndex()); }); connect(m_pass.get(), &Pass::startReencryptPath, this, &MainWindow::startReencryptPath); connect(m_pass.get(), &Pass::endReencryptPath, this, &MainWindow::endReencryptPath); } void MainWindow::setVisible(bool visible) { // This originated in the ctor, but we want this to happen after the first start wizard has been run // so for now, moved to show() on first call if (visible && firstShow) { firstShow = true; // register shortcut ctrl/cmd + Q to close the main window new QShortcut(QKeySequence(Qt::CTRL | Qt::Key_Q), this, SLOT(close())); model.setNameFilters(QStringList() << QStringLiteral("*.gpg")); model.setNameFilterDisables(false); /* * I added this to solve Windows bug but now on GNU/Linux the main folder, * if hidden, disappear * * model.setFilter(QDir::NoDot); */ QString passStore = Settings::getPassStore(Util::findPasswordStore()); QModelIndex rootDir = model.setRootPath(passStore); model.fetchMore(rootDir); proxyModel.setSourceModel(&model); selectionModel.reset(new QItemSelectionModel(&proxyModel)); ui->treeView->setModel(&proxyModel); ui->treeView->setRootIndex(proxyModel.mapFromSource(rootDir)); ui->treeView->setColumnHidden(1, true); ui->treeView->setColumnHidden(2, true); ui->treeView->setColumnHidden(3, true); ui->treeView->setHeaderHidden(true); ui->treeView->setIndentation(15); ui->treeView->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); ui->treeView->setContextMenuPolicy(Qt::CustomContextMenu); ui->treeView->header()->setSectionResizeMode(0, QHeaderView::Stretch); ui->treeView->sortByColumn(0, Qt::AscendingOrder); connect(ui->treeView, &QWidget::customContextMenuRequested, this, &MainWindow::showContextMenu); connect(ui->treeView, &DeselectableTreeView::emptyClicked, this, &MainWindow::deselect); if (Settings::isNoLineWrapping()) { ui->textBrowser->setLineWrapMode(QTextBrowser::NoWrap); } ui->textBrowser->setOpenExternalLinks(true); ui->textBrowser->setContextMenuPolicy(Qt::DefaultContextMenu); updateProfileBox(); clearPanelTimer.setInterval(1000 * Settings::getAutoclearPanelSeconds()); clearPanelTimer.setSingleShot(true); connect(&clearPanelTimer, SIGNAL(timeout()), this, SLOT(clearPanel())); searchTimer.setInterval(350); searchTimer.setSingleShot(true); connect(&searchTimer, &QTimer::timeout, this, &MainWindow::onTimeoutSearch); initToolBarButtons(); initStatusBar(); ui->lineEdit->setClearButtonEnabled(true); setUiElementsEnabled(true); QTimer::singleShot(10, this, SLOT(focusInput())); } QMainWindow::setVisible(visible); } MainWindow::~MainWindow() = default; /** * @brief MainWindow::focusInput selects any text (if applicable) in the search * box and sets focus to it. Allows for easy searching, called at application * start and when receiving empty message in MainWindow::messageAvailable when * compiled with SINGLE_APP=1 (default). */ void MainWindow::focusInput() { ui->lineEdit->selectAll(); ui->lineEdit->setFocus(); } /** * @brief MainWindow::changeEvent sets focus to the search box * @param event */ void MainWindow::changeEvent(QEvent *event) { QWidget::changeEvent(event); if (event->type() == QEvent::ActivationChange) { if (isActiveWindow()) { focusInput(); } } } /** * @brief MainWindow::initToolBarButtons init main ToolBar and connect actions */ void MainWindow::initToolBarButtons() { connect(ui->actionAddPassword, &QAction::triggered, this, &MainWindow::addPassword); connect(ui->actionAddFolder, &QAction::triggered, this, &MainWindow::addFolder); connect(ui->actionEdit, &QAction::triggered, this, &MainWindow::onEdit); connect(ui->actionDelete, &QAction::triggered, this, &MainWindow::onDelete); connect(ui->actionUsers, &QAction::triggered, this, &MainWindow::onUsers); connect(ui->actionConfig, &QAction::triggered, this, &MainWindow::onConfig); connect(ui->treeView, &QTreeView::clicked, this, &MainWindow::selectTreeItem); connect(ui->treeView, &QTreeView::doubleClicked, this, &MainWindow::editTreeItem); connect(ui->profileBox, &QComboBox::currentTextChanged, this, &MainWindow::selectProfile); connect(ui->lineEdit, &QLineEdit::textChanged, this, &MainWindow::filterList); connect(ui->lineEdit, &QLineEdit::returnPressed, this, &MainWindow::selectFromSearch); ui->actionAddPassword->setIcon(QIcon::fromTheme(QStringLiteral("document-new"))); ui->actionAddFolder->setIcon(QIcon::fromTheme(QStringLiteral("folder-new"))); ui->actionEdit->setIcon(QIcon::fromTheme(QStringLiteral("document-properties"))); ui->actionDelete->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete"))); ui->actionUsers->setIcon(QIcon::fromTheme(QStringLiteral("x-office-address-book"))); ui->actionConfig->setIcon(QIcon::fromTheme(QStringLiteral("applications-system"))); } /** * @brief MainWindow::initStatusBar init statusBar with default message and logo */ void MainWindow::initStatusBar() { ui->statusBar->showMessage(i18nc("placeholder is version number","Welcome to GnuPG Password Manager %1", QString::fromLocal8Bit(GPGPASS_VERSION_STRING))); QPixmap logo = QPixmap(QStringLiteral(":/artwork/32-gpgpass.png")); QLabel *logoApp = new QLabel(statusBar()); logoApp->setPixmap(logo); statusBar()->addPermanentWidget(logoApp); } const QModelIndex MainWindow::getCurrentTreeViewIndex() { return ui->treeView->currentIndex(); } void MainWindow::flashText(const QString &text, const bool isError, const bool isHtml) { if (isError) ui->textBrowser->setTextColor(Qt::red); if (isHtml) { QString _text = text; if (!ui->textBrowser->toPlainText().isEmpty()) _text = ui->textBrowser->toHtml() + _text; ui->textBrowser->setHtml(_text); } else { ui->textBrowser->setText(text); ui->textBrowser->setTextColor(Qt::black); } } /** * @brief MainWindow::config pops up the configuration screen and handles all * inter-window communication */ void MainWindow::config() { QScopedPointer d(new ConfigDialog(this)); d->setModal(true); if (d->exec()) { if (d->result() == QDialog::Accepted) { // Update the textBrowser line wrap mode if (Settings::isNoLineWrapping()) { ui->textBrowser->setLineWrapMode(QTextBrowser::NoWrap); } else { ui->textBrowser->setLineWrapMode(QTextBrowser::WidgetWidth); } this->show(); updateProfileBox(); ui->treeView->setRootIndex(proxyModel.mapFromSource(model.setRootPath(Settings::getPassStore()))); clearPanelTimer.setInterval(1000 * Settings::getAutoclearPanelSeconds()); m_clipboardHelper->setClipboardTimer(); } } } /** * @brief MainWindow::on_treeView_clicked read the selected password file * @param index */ void MainWindow::selectTreeItem(const QModelIndex &index) { bool cleared = ui->treeView->currentIndex().flags() == Qt::NoItemFlags; // TODO(bezet): "Could not decrypt"; m_clipboardHelper->clearClippedText(); QString file = index.data(QFileSystemModel::FilePathRole).toString(); ui->passwordName->setText(index.data().toString()); if (!file.isEmpty() && QFileInfo(file).isFile() && !cleared) { m_pass->Show(file); } else { clearPanel(false); ui->actionEdit->setEnabled(false); ui->actionDelete->setEnabled(true); } } /** * @brief MainWindow::on_treeView_doubleClicked when doubleclicked on * TreeViewItem, open the edit Window * @param index */ void MainWindow::editTreeItem(const QModelIndex &index) { QFileInfo fileOrFolder{index.data(QFileSystemModel::Roles::FilePathRole).toString()}; if (fileOrFolder.isFile()) { editPassword(fileOrFolder.absoluteFilePath()); } } /** * @brief MainWindow::deselect clear the selection, password and copy buffer */ void MainWindow::deselect() { m_clipboardHelper->clearClipboard(); ui->treeView->clearSelection(); ui->actionEdit->setEnabled(false); ui->actionDelete->setEnabled(false); ui->passwordName->setText(QString{}); clearPanel(false); } void MainWindow::passShowHandler(const QString &p_output) { QStringList templ = Settings::isUseTemplate() ? Settings::getPassTemplate().split(QStringLiteral("\n")) : QStringList(); bool allFields = Settings::isUseTemplate() && Settings::isTemplateAllFields(); FileContent fileContent = FileContent::parse(p_output, templ, allFields); QString output = p_output; QString password = fileContent.getPassword(); // set clipped text m_clipboardHelper->setClippedText(password); // first clear the current view: clearTemplateWidgets(); // show what is needed: if (Settings::isHideContent()) { output = i18n("*** Content hidden ***"); } else if (!Settings::isDisplayAsIs()) { if (!password.isEmpty()) { // set the password, it is hidden if needed in addToGridLayout addToGridLayout(0, i18n("Password"), password); } NamedValues namedValues = fileContent.getNamedValues(); for (int j = 0; j < namedValues.length(); ++j) { NamedValue nv = namedValues.at(j); addToGridLayout(j + 1, nv.name, nv.value); } if (ui->gridLayout->count() == 0) ui->verticalLayoutPassword->setSpacing(0); else ui->verticalLayoutPassword->setSpacing(6); output = fileContent.getRemainingDataForDisplay(); } if (Settings::isUseAutoclearPanel()) { clearPanelTimer.start(); } output = output.toHtmlEscaped(); output.replace(Util::protocolRegex(), QStringLiteral(R"(\1)")); output.replace(QStringLiteral("\n"), QStringLiteral("
")); flashText(output, false, true); setUiElementsEnabled(true); } /** * @brief MainWindow::clearPanel hide the information from shoulder surfers */ void MainWindow::clearPanel(bool notify) { while (ui->gridLayout->count() > 0) { QLayoutItem *item = ui->gridLayout->takeAt(0); delete item->widget(); delete item; } if (notify) { QString output = i18n("*** Password and Content hidden ***"); ui->textBrowser->setHtml(output); } else { ui->textBrowser->setHtml(QString{}); } } /** * @brief MainWindow::setUiElementsEnabled enable or disable the relevant UI * elements * @param state */ void MainWindow::setUiElementsEnabled(bool state) { ui->treeView->setEnabled(state); ui->lineEdit->setEnabled(state); ui->lineEdit->installEventFilter(this); ui->actionAddPassword->setEnabled(state); ui->actionAddFolder->setEnabled(state); ui->actionUsers->setEnabled(state); ui->actionConfig->setEnabled(state); // is a file selected? state &= ui->treeView->currentIndex().isValid(); ui->actionDelete->setEnabled(state); ui->actionEdit->setEnabled(state); } /** * @brief MainWindow::on_configButton_clicked run Mainwindow::config */ void MainWindow::onConfig() { config(); } /** * @brief Executes when the string in the search box changes, collapses the * TreeView * @param arg1 */ void MainWindow::filterList(const QString &arg1) { ui->statusBar->showMessage(i18n("Looking for: %1", arg1), 1000); ui->treeView->expandAll(); clearPanel(false); ui->passwordName->setText(QString{}); ui->actionEdit->setEnabled(false); ui->actionDelete->setEnabled(false); searchTimer.start(); } /** * @brief MainWindow::onTimeoutSearch Fired when search is finished or too much * time from two keypresses is elapsed */ void MainWindow::onTimeoutSearch() { QString query = ui->lineEdit->text(); if (query.isEmpty()) { ui->treeView->collapseAll(); deselect(); } query.replace(QStringLiteral(" "), QStringLiteral(".*")); QRegularExpression regExp(query, QRegularExpression::CaseInsensitiveOption); proxyModel.setFilterRegularExpression(regExp); ui->treeView->setRootIndex(proxyModel.mapFromSource(model.setRootPath(Settings::getPassStore()))); if (proxyModel.rowCount() > 0 && !query.isEmpty()) { selectFirstFile(); } else { ui->actionEdit->setEnabled(false); ui->actionDelete->setEnabled(false); } } /** * @brief MainWindow::on_lineEdit_returnPressed get searching * * Select the first possible file in the tree */ void MainWindow::selectFromSearch() { if (proxyModel.rowCount() > 0) { selectFirstFile(); selectTreeItem(ui->treeView->currentIndex()); } } /** * @brief MainWindow::selectFirstFile select the first possible file in the * tree */ void MainWindow::selectFirstFile() { QModelIndex index = proxyModel.mapFromSource(model.setRootPath(Settings::getPassStore())); index = firstFile(index); ui->treeView->setCurrentIndex(index); } /** * @brief MainWindow::firstFile return location of first possible file * @param parentIndex * @return QModelIndex */ QModelIndex MainWindow::firstFile(QModelIndex parentIndex) { QModelIndex index = parentIndex; int numRows = proxyModel.rowCount(parentIndex); for (int row = 0; row < numRows; ++row) { index = proxyModel.index(row, 0, parentIndex); if (model.fileInfo(proxyModel.mapToSource(index)).isFile()) return index; if (proxyModel.hasChildren(index)) return firstFile(index); } return index; } /** * @brief MainWindow::setPassword open passworddialog * @param file which pgp file * @param isNew insert (not update) */ void MainWindow::setPassword(QString file, bool isNew) { PasswordDialog d(*m_pass, file, isNew, this); if (!d.exec()) { ui->treeView->setFocus(); } } /** * @brief MainWindow::addPassword add a new password by showing a * number of dialogs. */ void MainWindow::addPassword() { bool ok; QString dir = directoryName(ui->treeView->currentIndex().data(QFileSystemModel::Roles::FilePathRole).toString()); if (dir.isEmpty()) { dir = Settings::getPassStore(); } QString file = QInputDialog::getText(this, i18n("New file"), i18n("New password file: \n(Will be placed in %1 )", dir), QLineEdit::Normal, QString{}, &ok); if (!ok || file.isEmpty()) return; file = QDir(dir).absoluteFilePath(file + QStringLiteral(".gpg")); setPassword(file); } /** * @brief MainWindow::onDelete remove password, if you are * sure. */ void MainWindow::onDelete() { QModelIndex currentIndex = ui->treeView->currentIndex(); if (!currentIndex.isValid()) { // If not valid, we might end up passing empty string // to delete, and taht might delete unexpected things on disk return; } QFileInfo fileOrFolder = currentIndex.data(QFileSystemModel::FilePathRole).toString(); bool isDir = fileOrFolder.isDir(); QString file = fileOrFolder.absoluteFilePath(); QString message; if (isDir) { message = i18nc("deleting a folder; placeholder is folder name","Are you sure you want to delete %1 and the whole content?", file); QDirIterator it(model.rootPath() + QLatin1Char('/') + file, QDirIterator::Subdirectories); while (it.hasNext()) { it.next(); if (auto fi = it.fileInfo(); fi.isFile()) { if (fi.suffix() != QStringLiteral("gpg")) { message += QStringLiteral("
") + i18nc("extra warning during certain folder deletions","Attention: " "there are unexpected files in the given folder, " "check them before continue") + QStringLiteral(""); break; } } } } else { message = i18nc("deleting a file; placeholder is file name","Are you sure you want to delete %1?", file); } if (QMessageBox::question(this, isDir ? i18n("Delete folder?") : i18n("Delete password?"), message, QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) return; m_pass->Remove(file, isDir); } /** * @brief MainWindow::onEdit try and edit (selected) password. */ void MainWindow::onEdit() { QString file = ui->treeView->currentIndex().data(QFileSystemModel::FilePathRole).toString(); if (!file.isEmpty()) { editPassword(file); } } /** * @brief MainWindow::userDialog see MainWindow::onUsers() * @param dir folder to edit users for. */ void MainWindow::userDialog(QString dir) { QFileInfo fi(dir); if (!fi.isDir()) { dir = fi.absolutePath(); } UsersDialog d(dir, *m_pass, this); if (!d.exec()) { ui->treeView->setFocus(); } } /** * @brief MainWindow::onUsers edit users for the current * folder, * gets lists and opens UserDialog. */ void MainWindow::onUsers() { QString dir = ui->treeView->currentIndex().data(QFileSystemModel::Roles::FilePathRole).toString(); if (dir.isEmpty()) { dir = Settings::getPassStore(); } else { QFileInfo fi(dir); if (!fi.isDir()) { dir = fi.absolutePath(); } + dir = Util::normalizeFolderPath(dir); } userDialog(dir); } /** * @brief MainWindow::updateProfileBox update the list of profiles, optionally * select a more appropriate one to view too */ void MainWindow::updateProfileBox() { QHash profiles = Settings::getProfiles(); if (profiles.isEmpty()) { ui->profileWidget->hide(); } else { ui->profileWidget->show(); ui->profileBox->setEnabled(profiles.size() > 1); ui->profileBox->clear(); QHashIterator i(profiles); while (i.hasNext()) { i.next(); if (!i.key().isEmpty()) ui->profileBox->addItem(i.key()); } ui->profileBox->model()->sort(0); } int index = ui->profileBox->findText(Settings::getProfile()); if (index != -1) // -1 for not found ui->profileBox->setCurrentIndex(index); } /** * @brief MainWindow::on_profileBox_currentIndexChanged make sure we show the * correct "profile" * @param name */ void MainWindow::selectProfile(QString name) { if (name == Settings::getProfile()) return; ui->lineEdit->clear(); Settings::setProfile(name); Settings::setPassStore(Settings::getProfiles().value(name)); ui->statusBar->showMessage(i18n("Profile changed to %1", name), 2000); ui->treeView->selectionModel()->clear(); ui->treeView->setRootIndex(proxyModel.mapFromSource(model.setRootPath(Settings::getPassStore()))); ui->actionEdit->setEnabled(false); ui->actionDelete->setEnabled(false); } /** * @brief MainWindow::closeEvent hide or quit * @param event */ void MainWindow::closeEvent(QCloseEvent *event) { m_clipboardHelper->clearClipboard(); event->accept(); } /** * @brief MainWindow::eventFilter filter out some events and focus the * treeview * @param obj * @param event * @return */ bool MainWindow::eventFilter(QObject *obj, QEvent *event) { if (obj == ui->lineEdit && event->type() == QEvent::KeyPress) { auto *key = dynamic_cast(event); if (key != nullptr && key->key() == Qt::Key_Down) { ui->treeView->setFocus(); } } return QObject::eventFilter(obj, event); } /** * @brief MainWindow::keyPressEvent did anyone press return, enter or escape? * @param event */ void MainWindow::keyPressEvent(QKeyEvent *event) { switch (event->key()) { case Qt::Key_Delete: onDelete(); break; case Qt::Key_Return: case Qt::Key_Enter: if (proxyModel.rowCount() > 0) selectTreeItem(ui->treeView->currentIndex()); break; case Qt::Key_Escape: ui->lineEdit->clear(); break; default: break; } } /** * @brief MainWindow::showContextMenu show us the (file or folder) context * menu * @param pos */ void MainWindow::showContextMenu(const QPoint &pos) { QModelIndex index = ui->treeView->indexAt(pos); bool selected = true; if (!index.isValid()) { ui->treeView->clearSelection(); ui->actionDelete->setEnabled(false); ui->actionEdit->setEnabled(false); selected = false; } ui->treeView->setCurrentIndex(index); QPoint globalPos = ui->treeView->viewport()->mapToGlobal(pos); QFileInfo fileOrFolder = model.fileInfo(proxyModel.mapToSource(ui->treeView->currentIndex())); QMenu contextMenu; if (!selected || fileOrFolder.isDir()) { const QAction *addFolderAction = contextMenu.addAction(i18n("Add folder")); const QAction *addPasswordAction = contextMenu.addAction(i18n("Add password")); const QAction *usersAction = contextMenu.addAction(i18n("Users")); connect(addFolderAction, &QAction::triggered, this, &MainWindow::addFolder); connect(addPasswordAction, &QAction::triggered, this, &MainWindow::addPassword); connect(usersAction, &QAction::triggered, this, &MainWindow::onUsers); } else if (fileOrFolder.isFile()) { const QAction *edit = contextMenu.addAction(i18n("Edit")); connect(edit, &QAction::triggered, this, &MainWindow::onEdit); } if (selected) { contextMenu.addSeparator(); if (fileOrFolder.isDir()) { const QAction *renameFolderAction = contextMenu.addAction(i18n("Rename folder")); connect(renameFolderAction, &QAction::triggered, this, &MainWindow::renameFolder); } else if (fileOrFolder.isFile()) { const QAction *renamePasswordAction = contextMenu.addAction(i18n("Rename password")); connect(renamePasswordAction, &QAction::triggered, this, &MainWindow::renamePassword); } const QAction *deleteItem = contextMenu.addAction(i18n("Delete")); connect(deleteItem, &QAction::triggered, this, &MainWindow::onDelete); } contextMenu.exec(globalPos); } /** * @brief MainWindow::addFolder add a new folder to store passwords in */ void MainWindow::addFolder() { bool ok; QString dir = directoryName(ui->treeView->currentIndex().data(QFileSystemModel::FilePathRole).toString()); if (dir.isEmpty()) { dir = Settings::getPassStore(); } QString newdir = QInputDialog::getText(this, i18n("New file"), i18n("New Folder: \n(Will be placed in %1 )", dir), QLineEdit::Normal, QString{}, &ok); if (!ok || newdir.isEmpty()) return; QDir(dir).mkdir(newdir); } /** * @brief MainWindow::renameFolder rename an existing folder */ void MainWindow::renameFolder() { bool ok; QString srcDir = QDir::cleanPath(directoryName(ui->treeView->currentIndex().data(QFileSystemModel::FilePathRole).toString())); if (srcDir.isEmpty()) { return; } QString srcDirName = QDir(srcDir).dirName(); QString newName = QInputDialog::getText(this, i18n("Rename file"), i18n("Rename Folder To: "), QLineEdit::Normal, srcDirName, &ok); if (!ok || newName.isEmpty()) return; QString destDir = srcDir; destDir.replace(srcDir.lastIndexOf(srcDirName), srcDirName.length(), newName); m_pass->Move(srcDir, destDir); } /** * @brief MainWindow::editPassword read password and open edit window via * MainWindow::onEdit() */ void MainWindow::editPassword(const QString &file) { if (!file.isEmpty()) { setPassword(file, false); } } /** * @brief MainWindow::renamePassword rename an existing password */ void MainWindow::renamePassword() { bool ok; QString file = ui->treeView->currentIndex().data(QFileSystemModel::FilePathRole).toString(); QString filePath = QFileInfo(file).path(); QString fileName = QFileInfo(file).fileName(); if (fileName.endsWith(QStringLiteral(".gpg"), Qt::CaseInsensitive)) fileName.chop(4); QString newName = QInputDialog::getText(this, i18n("Rename file"), i18n("Rename File To: "), QLineEdit::Normal, fileName, &ok); if (!ok || newName.isEmpty()) return; QString newFile = QDir(filePath).filePath(newName); m_pass->Move(file, newFile); } /** * @brief MainWindow::clearTemplateWidgets empty the template widget fields in * the UI */ void MainWindow::clearTemplateWidgets() { while (ui->gridLayout->count() > 0) { QLayoutItem *item = ui->gridLayout->takeAt(0); delete item->widget(); delete item; } ui->verticalLayoutPassword->setSpacing(0); } /** * @brief MainWindow::addToGridLayout add a field to the template grid * @param position * @param field * @param value */ void MainWindow::addToGridLayout(int position, const QString &field, const QString &value) { QString trimmedField = field.trimmed(); QString trimmedValue = value.trimmed(); // Combine the Copy button and the line edit in one widget QFrame *frame = new QFrame(); QLayout *ly = new QHBoxLayout(); ly->setContentsMargins(5, 2, 2, 2); ly->setSpacing(0); frame->setLayout(ly); auto fieldLabel = createPushButton(QIcon::fromTheme(QStringLiteral("edit-copy")), m_clipboardHelper, [this, trimmedValue] { m_clipboardHelper->copyTextToClipboard(trimmedValue); }); frame->layout()->addWidget(fieldLabel.release()); auto qrButton = createPushButton(QIcon::fromTheme(QStringLiteral("view-barcode-qr")), m_clipboardHelper, [this, trimmedValue]() { auto barcode = Prison::createBarcode(Prison::QRCode); if (!barcode) { return; } barcode->setData(trimmedValue); auto image = barcode->toImage(barcode->preferredSize(window()->devicePixelRatioF())); QDialog popup(nullptr, Qt::Popup | Qt::FramelessWindowHint); QVBoxLayout *layout = new QVBoxLayout; QLabel *popupLabel = new QLabel(); layout->addWidget(popupLabel); popupLabel->setPixmap(QPixmap::fromImage(image)); popupLabel->setScaledContents(true); popupLabel->show(); popup.setLayout(layout); popup.move(QCursor::pos()); popup.exec(); }); frame->layout()->addWidget(qrButton.release()); if (trimmedField == i18n("Password")) { auto *line = new QLineEdit(); line->setObjectName(trimmedField); line->setText(trimmedValue); line->setReadOnly(true); line->setContentsMargins(0, 0, 0, 0); line->setEchoMode(QLineEdit::Password); auto icon = QIcon::fromTheme(QStringLiteral("password-show-on")); icon.addFile(QStringLiteral("password-show-off"), QSize(), QIcon::Normal, QIcon::Off); auto showButton = createPushButton(icon, line, [line]() { if (line->echoMode() == QLineEdit::Password) { line->setEchoMode(QLineEdit::Normal); } else { line->setEchoMode(QLineEdit::Password); } }); showButton->setCheckable(true); showButton->setContentsMargins(0, 0, 0, 0); frame->layout()->addWidget(showButton.release()); frame->layout()->addWidget(line); } else { auto *line = new QLabel(); line->setOpenExternalLinks(true); line->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::LinksAccessibleByMouse | Qt::LinksAccessibleByKeyboard); line->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum)); line->setObjectName(trimmedField); trimmedValue.replace(Util::protocolRegex(), QStringLiteral(R"(\1)")); line->setText(trimmedValue); line->setContentsMargins(0, 0, 0, 0); frame->layout()->addWidget(line); } // set into the layout ui->gridLayout->addWidget(new QLabel(trimmedField), position, 0); ui->gridLayout->addWidget(frame, position, 1); } /** * @brief MainWindow::startReencryptPath disable ui elements and treeview */ void MainWindow::startReencryptPath() { statusBar()->showMessage(i18n("Re-encrypting folders"), 3000); setUiElementsEnabled(false); ui->treeView->setDisabled(true); } /** * @brief MainWindow::endReencryptPath re-enable ui elements */ void MainWindow::endReencryptPath() { setUiElementsEnabled(true); } /** * @brief MainWindow::critical critical message popup wrapper. * @param title * @param msg */ void MainWindow::critical(QString title, QString msg) { QMessageBox::critical(this, title, msg); }