Page MenuHome GnuPG

No OneTemporary

diff --git a/src/models/storemodel.cpp b/src/models/storemodel.cpp
index a3b25f9..e7bcf4b 100644
--- a/src/models/storemodel.cpp
+++ b/src/models/storemodel.cpp
@@ -1,422 +1,421 @@
/*
SPDX-FileCopyrightText: 2014-2023 Anne Jan Brouwer <brouwer@annejan.com>
SPDX-FileCopyrightText: 2018 Claudio Maradonna <penguyman@stronzi.org>
SPDX-FileCopyrightText: 2019 Maciej S. Szmigiero <mail@maciej.szmigiero.name>
SPDX-FileCopyrightText: 2023 g10 Code GmbH
SPDX-FileContributor: Sune Stolborg Vuorela <sune@vuorela.dk>
SPDX-License-Identifier: GPL-3.0-or-later
*/
#include "storemodel.h"
#include "addfileinfoproxy.h"
#include "job/filereencryptjob.h"
#include "rootfolderconfig.h"
#include "rootfoldersmanager.h"
#include "util.h"
#include <KLocalizedString>
#include <KSelectionProxyModel>
#include <QDir>
#include <QFileSystemModel>
#include <QItemSelectionModel>
#include <QMessageBox>
#include <QMimeData>
#include <QRegularExpression>
#include <qstringliteral.h>
static const QString mimeType = QStringLiteral("application/vnd+gnupgpass.dragAndDropInfoPasswordStore");
/// \brief holds values to share beetween drag and drop on the passwordstorage view.
struct DragAndDropInfoPasswordStore {
bool isDir = false;
bool isFile = false;
QString path;
};
QDataStream &operator<<(QDataStream &out, const DragAndDropInfoPasswordStore &dragAndDropInfoPasswordStore)
{
out << dragAndDropInfoPasswordStore.isDir << dragAndDropInfoPasswordStore.isFile << dragAndDropInfoPasswordStore.path;
return out;
}
QDataStream &operator>>(QDataStream &in, DragAndDropInfoPasswordStore &dragAndDropInfoPasswordStore)
{
in >> dragAndDropInfoPasswordStore.isDir >> dragAndDropInfoPasswordStore.isFile >> dragAndDropInfoPasswordStore.path;
return in;
}
StoreModel::StoreModel(QObject *parent)
: QSortFilterProxyModel(parent)
, m_fileSystemModel(new QFileSystemModel(this))
, m_addRoleModel(new AddFileInfoProxy(this))
, m_itemSelectionModel(new QItemSelectionModel(m_addRoleModel, this))
, m_selectionProxyModel(new KSelectionProxyModel(m_itemSelectionModel, this))
{
m_fileSystemModel->setNameFilters({QStringLiteral("*.gpg")});
m_fileSystemModel->setNameFilterDisables(false);
m_addRoleModel->setSourceModel(m_fileSystemModel);
m_selectionProxyModel->setFilterBehavior(KSelectionProxyModel::SubTrees);
m_selectionProxyModel->setSourceModel(m_addRoleModel);
setObjectName(QStringLiteral("StoreModel"));
setRecursiveFilteringEnabled(true);
setSourceModel(m_selectionProxyModel);
#if QT_VERSION > QT_VERSION_CHECK(6, 0, 0)
setAutoAcceptChildRows(true);
#endif
connect(m_fileSystemModel, &QFileSystemModel::directoryLoaded, this, &StoreModel::directoryLoaded);
}
int StoreModel::columnCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return 1;
}
RootFoldersManager *StoreModel::rootFoldersManager() const
{
return m_rootFoldersManager;
}
void StoreModel::setRootFoldersManager(RootFoldersManager *rootFoldersManager)
{
if (m_rootFoldersManager) {
disconnect(m_rootFoldersManager, &RootFoldersManager::rootFoldersChanged, this, &StoreModel::updateRootFolders);
}
m_rootFoldersManager = rootFoldersManager;
if (m_rootFoldersManager) {
updateRootFolders();
connect(m_rootFoldersManager, &RootFoldersManager::rootFoldersChanged, this, &StoreModel::updateRootFolders);
}
}
void StoreModel::updateRootFolders()
{
m_itemSelectionModel->clear();
for (const auto &rootFolder : m_rootFoldersManager->rootFolders()) {
QDir dir(rootFolder->path());
if (!dir.exists()) {
dir.mkpath(QStringLiteral("."));
}
QModelIndex rootDirIndex = m_fileSystemModel->setRootPath(rootFolder->path());
m_fileSystemModel->fetchMore(rootDirIndex);
m_itemSelectionModel->select(m_addRoleModel->mapFromSource(rootDirIndex), QItemSelectionModel::Select);
}
- m_selectionProxyModel->setFilterBehavior(m_rootFoldersManager->rootFolders().count() > 1 ? KSelectionProxyModel::SubTrees
- : KSelectionProxyModel::SubTreesWithoutRoots);
+ Q_EMIT rootFoldersSizeChanged();
}
QVariant StoreModel::data(const QModelIndex &index, int role) const
{
Q_ASSERT(checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid));
const auto initialValue = QSortFilterProxyModel::data(index, role);
if (index.column() == 0 && !index.parent().isValid() && m_rootFoldersManager) {
if (role == Qt::DisplayRole && m_rootFoldersManager->rootFolders().count() > 1) {
const auto uuid = initialValue.toString();
if (!uuid.isEmpty()) {
return m_rootFoldersManager->rootFolderName(uuid);
}
} else if (role == Qt::DecorationRole) {
return QIcon::fromTheme(QStringLiteral("folder-root-symbolic"));
}
}
if (role == Qt::ToolTipRole) {
const auto fileInfo = index.data(AddFileInfoProxy::FileInfoRole).value<QFileInfo>();
const bool isDir = fileInfo.isDir();
if (isDir) {
const auto recipients = recipientsForFile(fileInfo);
QString tooltip = i18nc("@info:tooltip", "<p>This directory is encrypted for the following users:</p><ul>");
for (const auto &recipient : recipients) {
tooltip += QStringLiteral("<li>") + QString::fromUtf8(recipient) + QStringLiteral("</li>");
}
tooltip += QStringLiteral("</ul>");
return tooltip;
}
} else if (role == Qt::DisplayRole) {
QString name = initialValue.toString();
name.replace(Util::endsWithGpg(), QString{});
return name;
}
return initialValue;
}
Qt::DropActions StoreModel::supportedDropActions() const
{
return Qt::CopyAction | Qt::MoveAction;
}
Qt::DropActions StoreModel::supportedDragActions() const
{
return Qt::CopyAction | Qt::MoveAction;
}
Qt::ItemFlags StoreModel::flags(const QModelIndex &index) const
{
Qt::ItemFlags defaultFlags = QSortFilterProxyModel::flags(index);
if (index.isValid()) {
return Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | defaultFlags;
}
return Qt::ItemIsDropEnabled | defaultFlags;
}
QStringList StoreModel::mimeTypes() const
{
QStringList types;
types << mimeType;
return types;
}
QMimeData *StoreModel::mimeData(const QModelIndexList &indexes) const
{
DragAndDropInfoPasswordStore info;
QByteArray encodedData;
// only use the first, otherwise we should enable multiselection
QModelIndex index = indexes.at(0);
if (index.isValid()) {
auto fileInfo = index.data(AddFileInfoProxy::FileInfoRole).value<QFileInfo>();
info.isDir = fileInfo.isDir();
info.isFile = fileInfo.isFile();
info.path = fileInfo.absoluteFilePath();
QDataStream stream(&encodedData, QIODevice::WriteOnly);
stream << info;
}
auto *mimeData = new QMimeData();
mimeData->setData(mimeType, encodedData);
return mimeData;
}
bool StoreModel::canDropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) const
{
#ifdef QT_DEBUG
qDebug() << action << row;
#else
Q_UNUSED(action)
Q_UNUSED(row)
#endif
if (!sourceModel()) {
return false;
}
const QModelIndex idx = index(parent.row(), parent.column(), parent.parent());
QByteArray encodedData = data->data(mimeType);
QDataStream stream(&encodedData, QIODevice::ReadOnly);
DragAndDropInfoPasswordStore info;
stream >> info;
if (!data->hasFormat(mimeType))
return false;
if (column > 0) {
return false;
}
auto fileInfo = idx.data(AddFileInfoProxy::FileInfoRole).value<QFileInfo>();
const auto rootDir = fileInfo.absoluteFilePath().isEmpty();
// you can drop a folder on a folder
if ((fileInfo.isDir() || rootDir) && info.isDir) {
return true;
}
// you can drop a file on a folder
if ((fileInfo.isDir() || rootDir) && info.isFile) {
return true;
}
// you can drop a file on a file
if (fileInfo.isFile() && info.isFile) {
return true;
}
return false;
}
bool StoreModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
{
if (!canDropMimeData(data, action, row, column, parent))
return false;
if (action == Qt::IgnoreAction) {
return true;
}
QByteArray encodedData = data->data(mimeType);
QDataStream stream(&encodedData, QIODevice::ReadOnly);
DragAndDropInfoPasswordStore info;
stream >> info;
QModelIndex destIndex = this->index(parent.row(), parent.column(), parent.parent());
QFileInfo destFileinfo = destIndex.data(AddFileInfoProxy::FileInfoRole).value<QFileInfo>();
if (destFileinfo.absoluteFilePath().isEmpty()) {
auto rootFolder = m_rootFoldersManager->rootFolders().constFirst();
destFileinfo = QFileInfo(rootFolder->path());
}
QFileInfo srcFileInfo = QFileInfo(info.path);
QString cleanedSrc = QDir::cleanPath(srcFileInfo.absoluteFilePath());
QString cleanedDest = QDir::cleanPath(destFileinfo.absoluteFilePath());
if (info.isDir) {
// dropped dir onto dir
if (destFileinfo.isDir()) {
QDir destDir = QDir(cleanedDest).filePath(srcFileInfo.fileName());
QString cleanedDestDir = QDir::cleanPath(destDir.absolutePath());
if (action == Qt::MoveAction) {
move(cleanedSrc, cleanedDestDir);
} else if (action == Qt::CopyAction) {
copy(cleanedSrc, cleanedDestDir);
}
}
} else if (info.isFile) {
// dropped file onto a directory
if (destFileinfo.isDir()) {
if (action == Qt::MoveAction) {
move(cleanedSrc, cleanedDest);
} else if (action == Qt::CopyAction) {
copy(cleanedSrc, cleanedDest);
}
} else if (destFileinfo.isFile()) {
// dropped file onto a file
int answer = QMessageBox::question(nullptr,
i18n("Force overwrite?"),
i18nc("Overwrite DestinationFile with SourceFile", "Overwrite %1 with %2?", cleanedDest, cleanedSrc),
QMessageBox::Yes | QMessageBox::No);
bool force = answer == QMessageBox::Yes;
if (action == Qt::MoveAction) {
move(cleanedSrc, cleanedDest, force);
} else if (action == Qt::CopyAction) {
copy(cleanedSrc, cleanedDest, force);
}
}
}
return true;
}
bool StoreModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const
{
/* matches logic in QFileSystemModelSorter::compareNodes() */
#ifndef Q_OS_MAC
if (source_left.column() == 0 || source_left.column() == 1) {
bool leftD = source_left.data(AddFileInfoProxy::FileInfoRole).value<QFileInfo>().isDir();
bool rightD = source_right.data(AddFileInfoProxy::FileInfoRole).value<QFileInfo>().isDir();
if (leftD ^ rightD)
return leftD;
}
#endif
return QSortFilterProxyModel::lessThan(source_left, source_right);
}
QList<QByteArray> StoreModel::recipientsForFile(const QFileInfo &fileInfo) const
{
QDir gpgIdPath(fileInfo.isDir() ? fileInfo.absoluteFilePath() : fileInfo.absoluteDir());
bool found = false;
QString rootDir;
while (gpgIdPath.exists()) {
bool isInRootFolder = false;
const auto rootFolders = m_rootFoldersManager->rootFolders();
for (const auto &rootFolder : rootFolders) {
auto rootFolderPath = rootFolder->path();
rootFolderPath.chop(1); // remove / at end
if (gpgIdPath.absolutePath().startsWith(rootFolderPath)) {
isInRootFolder = true;
rootDir = rootFolder->path();
}
}
if (!isInRootFolder) {
break;
}
if (QFile(gpgIdPath.absoluteFilePath(QStringLiteral(".gpg-id"))).exists()) {
found = true;
break;
}
if (!gpgIdPath.cdUp())
break;
}
QFile gpgId(found ? gpgIdPath.absoluteFilePath(QStringLiteral(".gpg-id")) : rootDir + QStringLiteral(".gpg-id"));
if (!gpgId.open(QIODevice::ReadOnly | QIODevice::Text)) {
return {};
}
QList<QByteArray> recipients;
while (!gpgId.atEnd()) {
const auto recipient(gpgId.readLine().trimmed());
if (!recipient.isEmpty()) {
recipients += recipient;
}
}
std::sort(recipients.begin(), recipients.end());
return recipients;
}
void StoreModel::move(const QString &source, const QString &destination, const bool force)
{
QFileInfo srcFileInfo(source);
QFileInfo destFileInfo(destination);
QString destFile;
QString srcFileBaseName = srcFileInfo.fileName();
if (srcFileInfo.isFile()) {
if (destFileInfo.isFile()) {
if (!force) {
return;
}
} else if (destFileInfo.isDir()) {
destFile = QDir(destination).filePath(srcFileBaseName);
} else {
destFile = destination;
}
if (destFile.endsWith(QStringLiteral(".gpg"), Qt::CaseInsensitive))
destFile.chop(4); // make sure suffix is lowercase
destFile.append(QStringLiteral(".gpg"));
} else if (srcFileInfo.isDir()) {
if (destFileInfo.isDir()) {
destFile = QDir(destination).filePath(srcFileBaseName);
} else if (destFileInfo.isFile()) {
return;
} else {
destFile = destination;
}
} else {
return;
}
QDir qDir;
if (force) {
qDir.remove(destFile);
}
qDir.rename(source, destFile);
}
void StoreModel::copy(const QString &source, const QString &destination, const bool force)
{
QDir qDir;
if (force) {
qDir.remove(destination);
}
if (!QFile::copy(source, destination)) {
Q_EMIT errorOccurred(QStringLiteral("Failed to copy file"));
}
const QFileInfo destinationInfo(destination);
const QFileInfo sourceInfo(source);
if (destinationInfo.isDir()) {
const auto recipients = recipientsForFile(destinationInfo);
auto fileReencryptJob = new FileReencryptJob(destinationInfo.absoluteFilePath() + u'/' + sourceInfo.fileName(), recipients);
fileReencryptJob->start();
} else if (destinationInfo.isFile()) {
const auto recipients = recipientsForFile(destinationInfo);
auto fileReencryptJob = new FileReencryptJob(destinationInfo.absoluteFilePath(), recipients);
fileReencryptJob->start();
}
}
diff --git a/src/models/storemodel.h b/src/models/storemodel.h
index 695d30a..ffbeb19 100644
--- a/src/models/storemodel.h
+++ b/src/models/storemodel.h
@@ -1,73 +1,74 @@
/*
SPDX-FileCopyrightText: 2014-2023 Anne Jan Brouwer <brouwer@annejan.com>
SPDX-FileCopyrightText: 2018 Claudio Maradonna <penguyman@stronzi.org>
SPDX-FileCopyrightText: 2019 Maciej S. Szmigiero <mail@maciej.szmigiero.name>
SPDX-FileCopyrightText: 2023 g10 Code GmbH
SPDX-FileContributor: Sune Stolborg Vuorela <sune@vuorela.dk>
SPDX-License-Identifier: GPL-3.0-or-later
*/
#ifndef STOREMODEL_H_
#define STOREMODEL_H_
#include <QSortFilterProxyModel>
class QFileInfo;
class QFileSystemModel;
class QItemSelectionModel;
class KSelectionProxyModel;
class AddFileInfoProxy;
class Pass;
class RootFoldersManager;
class RootFolderConfig;
/// \brief The QSortFilterProxyModel for handling filesystem searches.
///
/// This model support drag and drop.
class StoreModel : public QSortFilterProxyModel
{
Q_OBJECT
public:
explicit StoreModel(QObject *parent = nullptr);
RootFoldersManager *rootFoldersManager() const;
void setRootFoldersManager(RootFoldersManager *rootFoldersManager);
/// Overriden to remove .gpg at the end of the file name.
QVariant data(const QModelIndex &index, int role) const override;
bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const override;
int columnCount(const QModelIndex &parent) const override;
Qt::DropActions supportedDropActions() const override;
Qt::DropActions supportedDragActions() const override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
QStringList mimeTypes() const override;
QMimeData *mimeData(const QModelIndexList &indexes) const override;
bool canDropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) const override;
bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override;
/// \return The recipients for a file
/// This list is created from the .gpg-id file in the same directory or parent
/// directory and is sorted.
QList<QByteArray> recipientsForFile(const QFileInfo &fileInfo) const;
void move(const QString &source, const QString &destination, const bool force = false);
void copy(const QString &source, const QString &destination, const bool force = false);
Q_SIGNALS:
void errorOccurred(const QString &errorText);
void directoryLoaded(const QString &path);
+ void rootFoldersSizeChanged();
private:
void updateRootFolders();
private:
QFileSystemModel *const m_fileSystemModel;
AddFileInfoProxy *const m_addRoleModel;
QItemSelectionModel *const m_itemSelectionModel;
KSelectionProxyModel *const m_selectionProxyModel;
RootFoldersManager *m_rootFoldersManager = nullptr;
};
#endif // STOREMODEL_H_
diff --git a/src/widgets/mainwindow.cpp b/src/widgets/mainwindow.cpp
index 5226e2d..9de1e0f 100644
--- a/src/widgets/mainwindow.cpp
+++ b/src/widgets/mainwindow.cpp
@@ -1,811 +1,824 @@
/*
SPDX-FileCopyrightText: 2014-2023 Anne Jan Brouwer <brouwer@annejan.com>
SPDX-FileCopyrightText: 2016-2017 tezeb <tezeb+github@outoftheblue.pl>
SPDX-FileCopyrightText: 2018 Lukas Vogel <lukedirtwalker@gmail.com>
SPDX-FileCopyrightText: 2018 Claudio Maradonna <penguyman@stronzi.org>
SPDX-FileCopyrightText: 2019 Maciej S. Szmigiero <mail@maciej.szmigiero.name>
SPDX-FileCopyrightText: 2023 g10 Code GmbH
SPDX-FileContributor: Sune Stolborg Vuorela <sune@vuorela.dk>
SPDX-License-Identifier: GPL-3.0-or-later
*/
#include "mainwindow.h"
#include <gpgpass_version.h>
#include "clipboardhelper.h"
#include "conf/configuredialog.h"
#include "job/directoryreencryptjob.h"
#include "job/filedecryptjob.h"
#include "job/fileencryptjob.h"
#include "models/addfileinfoproxy.h"
#include "passentry.h"
#include "rootfoldersmanager.h"
#include "setupwidget.h"
#include "ui_mainwindow.h"
#include "usersdialog.h"
#include "util.h"
#include "widgets/passwordeditorwidget.h"
#include "widgets/passwordviewerwidget.h"
#include <KLocalizedString>
#include <KMessageBox>
#include <KMessageWidget>
#include <KStandardGuiItem>
#include <QCloseEvent>
#include <QComboBox>
#include <QDebug>
#include <QDirIterator>
#include <QFileInfo>
#include <QInputDialog>
#include <QMenu>
#include <QMessageBox>
#include <QShortcut>
enum class StackLayer {
WelcomePage = 0,
PasswordViewer = 1,
ConfigurationPage = 2,
PasswordEditor = 3,
};
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)
, ui(new Ui::MainWindow)
, m_clipboardHelper(new ClipboardHelper(this))
, m_passwordViewer(new PasswordViewerWidget(m_clipboardHelper, this))
, m_passwordEditor(new PasswordEditorWidget(m_clipboardHelper, this))
, m_rootFoldersManager(new RootFoldersManager(this))
{
#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);
ui->stackedWidget->addWidget(m_passwordViewer);
auto setupWidget = new SetupWidget(this);
ui->stackedWidget->addWidget(setupWidget);
ui->stackedWidget->addWidget(m_passwordEditor);
connect(setupWidget, &SetupWidget::setupComplete, this, &MainWindow::slotSetupFinished);
ui->actionEdit->setCheckable(true);
ui->separator->setFixedHeight(1);
ui->separator->setFrameStyle(QFrame::HLine);
ui->lineEditWrapper->setContentsMargins(style()->pixelMetric(QStyle::PM_LayoutLeftMargin),
style()->pixelMetric(QStyle::PM_LayoutTopMargin),
style()->pixelMetric(QStyle::PM_LayoutRightMargin),
style()->pixelMetric(QStyle::PM_LayoutBottomMargin));
m_errorMessage = new KMessageWidget();
m_errorMessage->setMessageType(KMessageWidget::Error);
#if QT_VERSION > QT_VERSION_CHECK(6, 0, 0)
m_errorMessage->setPosition(KMessageWidget::Position::Header);
#endif
m_errorMessage->hide();
ui->messagesArea->addWidget(m_errorMessage);
m_storeModel.setRootFoldersManager(m_rootFoldersManager);
connect(&m_storeModel, &StoreModel::errorOccurred, this, [this](auto str) {
m_errorMessage->setText(str);
m_errorMessage->animatedShow();
setUiElementsEnabled(true);
});
+ connect(&m_storeModel, &StoreModel::rootFoldersSizeChanged, this, &MainWindow::updateRootIndex);
+
connect(m_passwordViewer, &PasswordViewerWidget::loaded, this, [this] {
setUiElementsEnabled(true);
});
}
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 = false;
// register shortcut ctrl/cmd + Q to close the main window
new QShortcut(QKeySequence(Qt::CTRL | Qt::Key_Q), this, SLOT(close()));
ui->treeView->setModel(&m_storeModel);
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);
searchTimer.setInterval(350);
searchTimer.setSingleShot(true);
connect(&searchTimer, &QTimer::timeout, this, &MainWindow::onTimeoutSearch);
initToolBarButtons();
ui->lineEdit->setClearButtonEnabled(true);
setUiElementsEnabled(true);
QTimer::singleShot(10, this, SLOT(focusInput()));
verifyInitialized();
+
+ updateRootIndex();
}
QMainWindow::setVisible(visible);
}
MainWindow::~MainWindow() = default;
+void MainWindow::updateRootIndex()
+{
+ if (m_rootFoldersManager->rootFolders().count() == 1) {
+ ui->treeView->setRootIndex(m_storeModel.index(0, 0));
+ } else {
+ ui->treeView->setRootIndex({});
+ }
+}
+
/**
* @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::toggled, 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, [this] {
openConfig(ConfigureDialog::Page::None);
});
connect(ui->treeView, &QTreeView::clicked, this, &MainWindow::selectTreeItem);
connect(ui->treeView, &QTreeView::doubleClicked, this, &MainWindow::editTreeItem);
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("configure-symbolic")));
}
const QModelIndex MainWindow::getCurrentTreeViewIndex()
{
return ui->treeView->currentIndex();
}
void MainWindow::openConfig(ConfigureDialog::Page page)
{
QScopedPointer<ConfigureDialog> dialog(new ConfigureDialog(m_rootFoldersManager, this));
dialog->setModal(true);
dialog->openPage(page);
if (dialog->exec()) {
if (dialog->result() == QDialog::Accepted) {
this->show();
m_passwordViewer->setPanelTimer();
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->actionEdit->setChecked(false);
if (!file.isEmpty() && QFileInfo(file).isFile() && !cleared) {
m_selectedFile = file;
m_passwordViewer->setFileName(ui->treeView->selectionModel()->currentIndex().data().toString(), file);
ui->stackedWidget->setCurrentIndex((int)StackLayer::PasswordViewer);
} else {
m_passwordViewer->clear();
ui->actionEdit->setEnabled(false);
ui->actionDelete->setEnabled(index.parent().isValid() || m_rootFoldersManager->rootFolders().count() == 1);
ui->stackedWidget->setCurrentIndex((int)StackLayer::WelcomePage);
}
}
/**
* @brief MainWindow::on_treeView_doubleClicked when doubleclicked on
* TreeViewItem, open the edit Window
* @param index
*/
void MainWindow::editTreeItem(const QModelIndex &index)
{
QFileInfo fileInfo{index.data(QFileSystemModel::Roles::FilePathRole).toString()};
if (!fileInfo.isFile()) {
return;
}
auto decryptJob = new FileDecryptJob(fileInfo.absoluteFilePath());
connect(decryptJob, &FileDecryptJob::finished, this, [this, decryptJob](KJob *) {
if (decryptJob->error() != KJob::NoError) {
m_errorMessage->setText(decryptJob->errorText());
m_errorMessage->animatedShow();
return;
}
switchToPasswordEditor(decryptJob->filePath(), decryptJob->content());
});
decryptJob->start();
}
void MainWindow::switchToPasswordEditor(const QString &filePath, const QString &content)
{
PassEntry entry;
const auto name = QFileInfo(filePath).baseName();
if (!content.isEmpty()) {
entry = PassEntry(name, content);
}
m_passwordEditor->setPassEntry(entry);
ui->actionEdit->setChecked(true);
ui->stackedWidget->setCurrentIndex((int)StackLayer::PasswordEditor);
connect(m_passwordEditor, &PasswordEditorWidget::save, this, [this, filePath](const QString &content) {
const QFileInfo fileInfo(filePath);
const auto recipients = m_storeModel.recipientsForFile(fileInfo);
auto encryptJob = new FileEncryptJob(fileInfo.absoluteFilePath(), content.toUtf8(), recipients);
connect(encryptJob, &FileDecryptJob::finished, this, [encryptJob, this](KJob *) {
if (encryptJob->error() != KJob::NoError) {
m_errorMessage->setText(encryptJob->errorText());
m_errorMessage->animatedShow();
return;
}
ui->treeView->setFocus();
ui->actionEdit->setChecked(false);
selectTreeItem(getCurrentTreeViewIndex());
});
encryptJob->start();
});
connect(m_passwordEditor, &PasswordEditorWidget::editorClosed, this, [this]() {
selectTreeItem(getCurrentTreeViewIndex());
ui->actionEdit->setChecked(false);
});
}
/**
* @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);
m_passwordViewer->clear();
ui->stackedWidget->setCurrentIndex((int)StackLayer::WelcomePage);
}
/**
* @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 Executes when the string in the search box changes, collapses the
* TreeView
* @param arg1
*/
void MainWindow::filterList(const QString &arg1)
{
ui->treeView->expandAll();
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);
m_storeModel.setFilterRegularExpression(regExp);
if (m_storeModel.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 (m_storeModel.rowCount() > 0) {
selectFirstFile();
selectTreeItem(ui->treeView->currentIndex());
}
}
/**
* @brief MainWindow::selectFirstFile select the first possible file in the
* tree
*/
void MainWindow::selectFirstFile()
{
auto model = ui->treeView->model();
auto index = firstFile(model->index(0, 0));
ui->treeView->selectionModel()->select(index, QItemSelectionModel::ClearAndSelect);
}
/**
* @brief MainWindow::firstFile return location of first possible file
* @param parentIndex
* @return QModelIndex
*/
QModelIndex MainWindow::firstFile(QModelIndex parentIndex)
{
auto model = parentIndex.model();
int numRows = model->rowCount(parentIndex);
for (int row = 0; row < numRows; ++row) {
auto index = model->index(row, 0, parentIndex);
if (index.data(AddFileInfoProxy::FileInfoRole).value<QFileInfo>().isFile())
return index;
if (model->hasChildren(index))
return firstFile(index);
}
return parentIndex;
}
QString MainWindow::fallbackStore()
{
const auto rootFolders = m_rootFoldersManager->rootFolders();
if (rootFolders.isEmpty()) {
QMessageBox::critical(this, i18nc("@title:dialog", "No password store found"), i18nc("@info", "Please add a password store first."));
return {};
}
return rootFolders[0]->path();
}
/**
* @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 = fallbackStore();
if (dir.isEmpty()) {
return;
}
}
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"));
switchToPasswordEditor(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 remove %1 and the whole content?", file);
QDirIterator it(file, QDirIterator::Subdirectories);
while (it.hasNext()) {
it.next();
if (auto fi = it.fileInfo(); fi.isFile()) {
if (fi.suffix() != QStringLiteral("gpg")) {
message += QStringLiteral("<br><strong>")
+ i18nc("extra warning during certain folder deletions",
"Attention: "
"there are unexpected files in the given folder, "
"check them before continue")
+ QStringLiteral("</strong>");
break;
}
}
}
} else {
message = i18nc("deleting a file; placeholder is file name", "Are you sure you want to remove %1?", file);
}
if (KMessageBox::warningTwoActions(this,
message,
isDir ? i18n("Remove folder?") : i18n("Remove password?"),
KStandardGuiItem::remove(),
KStandardGuiItem::cancel())
!= KMessageBox::PrimaryAction)
return;
if (!isDir) {
QFile(file).remove();
} else {
QDir dir(file);
dir.removeRecursively();
}
}
/**
* @brief MainWindow::onEdit try and edit (selected) password.
*/
void MainWindow::onEdit(bool edit)
{
if (edit) {
editTreeItem(ui->treeView->currentIndex());
} else {
selectTreeItem(getCurrentTreeViewIndex());
ui->actionEdit->setChecked(false);
}
}
/**
* @brief MainWindow::userDialog see MainWindow::onUsers()
* @param dir folder to edit users for.
*/
void MainWindow::userDialog(QString dir)
{
if (dir.isEmpty()) {
dir = fallbackStore();
if (dir.isEmpty()) {
return;
}
}
QFileInfo fi(dir);
if (!fi.isDir()) {
dir = fi.absolutePath();
}
const auto recipients = m_storeModel.recipientsForFile(QFileInfo(dir));
auto usersDialog = new UsersDialog(recipients, this);
usersDialog->setAttribute(Qt::WA_DeleteOnClose);
connect(usersDialog, &UsersDialog::save, this, [dir, this, usersDialog](const QList<QByteArray> &recipients) {
auto reencryptJob = new DirectoryReencryptJob(m_storeModel, recipients, dir);
connect(reencryptJob, &DirectoryReencryptJob::result, this, [this, usersDialog](KJob *job) {
if (job->error() != KJob::NoError) {
usersDialog->showError(job->errorText());
return;
}
usersDialog->close();
setUiElementsEnabled(true);
ui->treeView->setFocus();
verifyInitialized();
});
// statusBar()->showMessage(i18n("Re-encrypting folders"), 3000);
setUiElementsEnabled(false);
ui->treeView->setDisabled(true);
reencryptJob->start();
});
usersDialog->show();
}
/**
* @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 = fallbackStore();
if (dir.isEmpty()) {
return;
}
} else {
QFileInfo fi(dir);
if (!fi.isDir()) {
dir = fi.absolutePath();
}
dir = Util::normalizeFolderPath(dir);
}
userDialog(dir);
}
void MainWindow::slotSetupFinished(const QString &location, const QByteArray &keyId)
{
const QString gpgIdFile = location + QStringLiteral(".gpg-id");
QFile gpgId(gpgIdFile);
if (!gpgId.open(QIODevice::WriteOnly | QIODevice::Text)) {
KMessageBox::error(this, i18n("Unable to write user configuration at \"%1\". Error: %2", gpgIdFile, gpgId.errorString()));
return;
}
gpgId.write(keyId);
gpgId.close();
m_rootFoldersManager->addRootFolder(i18nc("Default store name", "Local Store"), location);
m_rootFoldersManager->save();
ui->lineEdit->clear();
m_passwordViewer->clear();
ui->treeView->selectionModel()->clear();
ui->actionEdit->setEnabled(false);
ui->actionDelete->setEnabled(false);
ui->stackedWidget->setCurrentIndex((int)StackLayer::WelcomePage);
verifyInitialized();
}
void MainWindow::verifyInitialized()
{
auto alreadyConfigured = !m_rootFoldersManager->rootFolders().isEmpty();
ui->sidebar->setVisible(alreadyConfigured);
ui->stackedWidget->setCurrentIndex(alreadyConfigured ? (int)StackLayer::WelcomePage : (int)StackLayer::ConfigurationPage);
ui->actionAddFolder->setEnabled(alreadyConfigured);
ui->actionAddPassword->setEnabled(alreadyConfigured);
ui->actionDelete->setEnabled(ui->actionDelete->isEnabled() && alreadyConfigured);
ui->actionEdit->setEnabled(ui->actionEdit->isEnabled() && alreadyConfigured);
}
/**
* @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<QKeyEvent *>(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 (m_storeModel.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 = ui->treeView->currentIndex().data(AddFileInfoProxy::FileInfoRole).value<QFileInfo>();
QMenu contextMenu;
if (!selected || fileOrFolder.isDir()) {
QAction *addFolderAction = contextMenu.addAction(i18nc("@action:inmenu", "Add Folder"));
addFolderAction->setIcon(QIcon::fromTheme(QStringLiteral("folder-add-symbolic")));
connect(addFolderAction, &QAction::triggered, this, &MainWindow::addFolder);
QAction *addPasswordAction = contextMenu.addAction(i18nc("@action:inmenu", "Add Password"));
addPasswordAction->setIcon(QIcon::fromTheme(QStringLiteral("lock-symbolic")));
connect(addPasswordAction, &QAction::triggered, this, &MainWindow::addPassword);
QAction *usersAction = contextMenu.addAction(i18nc("@action:inmenu", "Configure Users"));
usersAction->setIcon(QIcon::fromTheme(QStringLiteral("system-users-symbolic")));
connect(usersAction, &QAction::triggered, this, &MainWindow::onUsers);
} else if (fileOrFolder.isFile()) {
QAction *edit = contextMenu.addAction(i18nc("@action:inmenu", "Edit Password"));
edit->setIcon(QIcon::fromTheme(QStringLiteral("document-edit")));
connect(edit, &QAction::triggered, this, [this] {
onEdit(true);
});
}
if (selected && (index.parent().isValid() || m_rootFoldersManager->rootFolders().count() == 1)) {
contextMenu.addSeparator();
if (fileOrFolder.isDir()) {
QAction *renameFolderAction = contextMenu.addAction(i18nc("@action:inmenu", "Rename Folder"));
connect(renameFolderAction, &QAction::triggered, this, &MainWindow::renameFolder);
renameFolderAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-rename-symbolic")));
} else if (fileOrFolder.isFile()) {
QAction *renamePasswordAction = contextMenu.addAction(i18nc("@action:inmenu", "Rename Password"));
renamePasswordAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-rename-symbolic")));
connect(renamePasswordAction, &QAction::triggered, this, &MainWindow::renamePassword);
}
QAction *deleteItem =
contextMenu.addAction(fileOrFolder.isFile() ? i18nc("@action:inmenu", "Delete Password") : i18nc("@action:inmenu", "Delete Folder"));
deleteItem->setIcon(QIcon::fromTheme(QStringLiteral("delete-symbolic")));
connect(deleteItem, &QAction::triggered, this, &MainWindow::onDelete);
}
if (!index.parent().isValid() && m_rootFoldersManager->rootFolders().count() != 1) {
contextMenu.addSeparator();
const auto configureAction = contextMenu.addAction(i18nc("@action:inmenu", "Configure Password Stores"));
configureAction->setIcon(QIcon::fromTheme(QStringLiteral("configure-symbolic")));
connect(configureAction, &QAction::triggered, this, [this] {
openConfig(ConfigureDialog::Page::PasswordsStore);
});
}
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 = fallbackStore();
if (dir.isEmpty()) {
return;
}
}
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_storeModel.move(srcDir, destDir);
}
/**
* @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_storeModel.move(file, newFile);
}
diff --git a/src/widgets/mainwindow.h b/src/widgets/mainwindow.h
index 81b825a..ce0562b 100644
--- a/src/widgets/mainwindow.h
+++ b/src/widgets/mainwindow.h
@@ -1,127 +1,128 @@
/*
SPDX-FileCopyrightText: 2014-2023 Anne Jan Brouwer <brouwer@annejan.com>
SPDX-FileCopyrightText: 2016-2017 tezeb <tezeb+github@outoftheblue.pl>
SPDX-FileCopyrightText: 2018 Lukas Vogel <lukedirtwalker@gmail.com>
SPDX-FileCopyrightText: 2018 Claudio Maradonna <penguyman@stronzi.org>
SPDX-FileCopyrightText: 2019 Maciej S. Szmigiero <mail@maciej.szmigiero.name>
SPDX-FileCopyrightText: 2023 g10 Code GmbH
SPDX-FileContributor: Sune Stolborg Vuorela <sune@vuorela.dk>
SPDX-License-Identifier: GPL-3.0-or-later
*/
#ifndef MAINWINDOW_H_
#define MAINWINDOW_H_
#include "conf/configuredialog.h"
#include "models/storemodel.h"
#include <KSelectionProxyModel>
#include <QFileSystemModel>
#include <QItemSelectionModel>
#include <QMainWindow>
#include <QProcess>
#include <QTimer>
#ifdef __APPLE__
// http://doc.qt.io/qt-5/qkeysequence.html#qt_set_sequence_auto_mnemonic
void qt_set_sequence_auto_mnemonic(bool b);
#endif
namespace Ui
{
class MainWindow;
}
class QComboBox;
class ClipboardHelper;
class KMessageWidget;
class AddFileInfoProxy;
class KJob;
class PasswordViewerWidget;
class PasswordEditorWidget;
class RootFoldersManager;
/*!
\class MainWindow
\brief The MainWindow class does way too much, not only is it a switchboard,
configuration handler and more, it's also the process-manager.
This class could really do with an overhaul.
*/
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow();
void restoreWindow();
void userDialog(QString = {});
/// Open the configuration dialog
void openConfig(ConfigureDialog::Page page = ConfigureDialog::Page::None);
void setUiElementsEnabled(bool state);
const QModelIndex getCurrentTreeViewIndex();
void setVisible(bool visible) override;
protected:
void closeEvent(QCloseEvent *event) override;
void keyPressEvent(QKeyEvent *event) override;
void changeEvent(QEvent *event) override;
bool eventFilter(QObject *obj, QEvent *event) override;
public Q_SLOTS:
void deselect();
void selectTreeItem(const QModelIndex &index);
private Q_SLOTS:
void addPassword();
void addFolder();
void onEdit(bool edit);
void onDelete();
void onUsers();
void editTreeItem(const QModelIndex &index);
void switchToPasswordEditor(const QString &filePath, const QString &content = {});
void filterList(const QString &arg1);
void selectFromSearch();
void showContextMenu(const QPoint &pos);
void renameFolder();
void renamePassword();
void focusInput();
void onTimeoutSearch();
void verifyInitialized();
void slotSetupFinished(const QString &location, const QByteArray &keyId);
+ void updateRootIndex();
private:
QString fallbackStore();
QScopedPointer<Ui::MainWindow> ui;
ClipboardHelper *const m_clipboardHelper;
PasswordViewerWidget *const m_passwordViewer;
PasswordEditorWidget *const m_passwordEditor;
RootFoldersManager *const m_rootFoldersManager;
StoreModel m_storeModel;
QTimer searchTimer;
KMessageWidget *m_notInitialized;
KMessageWidget *m_errorMessage;
QString m_selectedFile;
bool firstShow = true;
void initToolBarButtons();
void updateText();
void selectFirstFile();
QModelIndex firstFile(QModelIndex parentIndex);
void setPassword(QString, bool isNew = true);
void initTrayIcon();
void destroyTrayIcon();
void reencryptPath(QString dir);
};
#endif // MAINWINDOW_H_

File Metadata

Mime Type
text/x-diff
Expires
Mon, Dec 23, 1:47 PM (1 d, 51 m)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
cd/d4/77fe0b8bd877ef4f87d9e6cb31c0

Event Timeline