Page MenuHome GnuPG

No OneTemporary

diff --git a/CMakeLists.txt b/CMakeLists.txt
index af3ae52..4152d7e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,74 +1,78 @@
cmake_minimum_required(VERSION 3.16)
set(VERSION "0.0.1")
project(gnupgpass VERSION 0.0.1)
set(QT_MIN_VERSION "5.15.0")
set(KF_MIN_VERSION "5.100.0")
if (QT_MAJOR_VERSION STREQUAL "6")
set(QT_MIN_VERSION "6.6.0")
set(KF_MIN_VERSION "5.240.0")
endif()
find_package(ECM ${KF_MIN_VERSION} REQUIRED NO_MODULE)
set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake/modules)
include(KDEInstallDirs)
include(KDECompilerSettings NO_POLICY_SCOPE)
include(KDECMakeSettings)
include(ECMMarkAsTest)
include(ECMAddTests)
include(FeatureSummary)
include(ECMAddAppIcon)
include(ECMSetupVersion)
include(KDEGitCommitHooks)
include(KDEClangFormat)
kde_configure_git_pre_commit_hook(CHECKS CLANG_FORMAT)
+set(CMAKE_CXX_STANDARD 20)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
# Generate header with version number
ecm_setup_version(${VERSION} VARIABLE_PREFIX GPGPASS VERSION_HEADER
"${CMAKE_CURRENT_BINARY_DIR}/gpgpass_version.h")
find_package(Qt${QT_MAJOR_VERSION} ${QT_MIN_VERSION} NO_MODULE COMPONENTS Core Widgets Test)
set_package_properties(Qt6 PROPERTIES
TYPE REQUIRED
PURPOSE "Basic application components"
)
include_directories(${CMAKE_BINARY_DIR})
find_package(KF${QT_MAJOR_VERSION} ${KF_MIN_VERSION} REQUIRED COMPONENTS
CoreAddons
Prison
IconThemes
I18n
WidgetsAddons
ItemModels
ItemViews
Config
KIO
+ XmlGui
)
if (QT_MAJOR_VERSION STREQUAL "6")
find_package(KF${QT_MAJOR_VERSION} ${KF_MIN_VERSION} REQUIRED COMPONENTS ColorScheme)
find_package(QGpgmeQt6 1.19 CONFIG REQUIRED)
set(KPIM_LIBKLEO_VERSION "6.1.40")
find_package(KPim6Libkleo ${KPIM_LIBKLEO_VERSION} CONFIG REQUIRED)
set(CMAKE_MODULE_PATH ${LIBKLEO_MODULE_PATH} ${CMAKE_MODULE_PATH})
find_package(LibGpgError ${GPG_ERROR_REQUIRED_VERSION} REQUIRED)
else()
find_package(QGpgme 1.19 CONFIG REQUIRED)
find_package(KPim5Libkleo ${KPIM_LIBKLEO_VERSION} CONFIG REQUIRED)
find_package(KF${QT_MAJOR_VERSION} ${KF_MIN_VERSION} REQUIRED COMPONENTS ConfigWidgets)
endif()
add_subdirectory(main)
add_subdirectory(src)
add_subdirectory(autotests)
ki18n_install(po)
install(FILES org.gnupg.gpgpass.desktop DESTINATION ${KDE_INSTALL_APPDIR})
install(FILES org.gnupg.gpgpass.metainfo.xml DESTINATION ${KDE_INSTALL_METAINFODIR})
install(FILES artwork/sc-gpgpass.svg DESTINATION ${KDE_INSTALL_FULL_ICONDIR}/hicolor/scalable/apps RENAME org.gnupg.gpgpass.svg)
feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES)
diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt
index 158cffc..e41d01c 100644
--- a/main/CMakeLists.txt
+++ b/main/CMakeLists.txt
@@ -1,20 +1,21 @@
add_executable(gpgpass)
target_link_libraries(gpgpass gpgpass_internal KF${QT_MAJOR_VERSION}::IconThemes)
if (QT_MAJOR_VERSION STREQUAL "6")
- target_link_libraries(gpgpass KF${QT_MAJOR_VERSION}::ColorScheme)
+ target_link_libraries(gpgpass KF${QT_MAJOR_VERSION}::ColorScheme KPim6::Libkleo)
endif()
+
ecm_add_app_icon(GPGPASS_ICONS ICONS ${CMAKE_SOURCE_DIR}/artwork/sc-gpgpass.svg ${CMAKE_SOURCE_DIR}/artwork/32-gpgpass.png ${CMAKE_SOURCE_DIR}/artwork/64-gpgpass.png ${CMAKE_SOURCE_DIR}/artwork/256-gpgpass.png)
-target_sources(gpgpass PRIVATE main.cpp ${GPGPASS_ICONS})
+target_sources(gpgpass PRIVATE main.cpp aboutdata.cpp ${GPGPASS_ICONS})
target_compile_definitions(gpgpass PRIVATE QT_NO_TRANSLATION)
if(WIN32)
configure_file(versioninfo.rc.in versioninfo.rc)
configure_file(gpgpass.w32-manifest.in gpgpass.w32-manifest)
target_sources(gpgpass PRIVATE
${CMAKE_CURRENT_BINARY_DIR}/versioninfo.rc
${CMAKE_CURRENT_BINARY_DIR}/gpgpass.w32-manifest
)
endif()
install(TARGETS gpgpass ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})
diff --git a/main/aboutdata.cpp b/main/aboutdata.cpp
new file mode 100644
index 0000000..376e366
--- /dev/null
+++ b/main/aboutdata.cpp
@@ -0,0 +1,90 @@
+/*
+ aboutdata.cpp
+
+ This file is part of Kleopatra, the KDE keymanager
+ SPDX-FileCopyrightText: 2001, 2002, 2004 Klarälvdalens Datakonsult AB
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#include <gpgpass_version.h>
+
+#include "aboutdata.h"
+
+#include <Libkleo/GnuPG>
+#include <libkleo_version.h>
+
+#include <QCoreApplication>
+#include <QSettings>
+#include <QThread>
+
+#include <KLazyLocalizedString>
+#include <KLocalizedString>
+#include <kcoreaddons_version.h>
+
+struct about_data {
+ const KLazyLocalizedString name;
+ const KLazyLocalizedString description;
+};
+
+static constexpr auto authors = std::to_array<about_data>({
+ {kli18n("Carl Schwan"), kli18n("Maintainer")},
+ {kli18n("Sune Vuorela"), kli18n("Developer")},
+});
+
+static constexpr auto credits = std::to_array<about_data>({
+ {kli18n("Anne Jan Brouwer"), kli18n("QtPass developer")},
+});
+
+// Extend the about data with the used GnuPG Version since this can
+// make a big difference with regards to the available features.
+static void loadBackendVersions()
+{
+#if LIBKLEO_VERSION >= QT_VERSION_CHECK(6, 3, 41)
+ auto thread = QThread::create([]() {
+ const auto backendComponents = Kleo::backendComponents();
+ if (!backendComponents.empty()) {
+ QMetaObject::invokeMethod(qApp, [backendComponents]() {
+ auto about = KAboutData::applicationData();
+ for (const auto &component : backendComponents) {
+#if KCOREADDONS_VERSION >= QT_VERSION_CHECK(6, 9, 0)
+ about.addComponent(component);
+#else
+ about.addComponent(component.name(), component.description(), component.version(), component.webAddress() /*, component.license() */);
+#endif
+ }
+ KAboutData::setApplicationData(about);
+ });
+ }
+ });
+ thread->start();
+#endif
+}
+
+AboutData::AboutData()
+ : KAboutData(QStringLiteral("gpgpass"),
+ i18n("GPGPass"),
+ QLatin1String(GPGPASS_VERSION_STRING),
+ i18nc("@info", "Password Manager using GnuPG to store your password securely."),
+ KAboutLicense::GPL,
+ i18nc("@info:credit", "\u00A9 2023-%1 g10 Code GmbH", QStringLiteral("2024")) + QLatin1Char('\n')
+ + i18nc("@info:credit", "\u00A9 2014-2023 Anne Jan Brouwer") + QLatin1Char('\n'),
+ {},
+ QStringLiteral("https://www.gnupg.com/"),
+ QStringLiteral("https://dev.gnupg.org/maniphest/task/edit/form/3/"))
+{
+ setOrganizationDomain("gnupg.org");
+ setDesktopFileName(QStringLiteral("org.gnupg.gpgpass"));
+ using ::authors;
+ using ::credits;
+
+ for (const auto &author : authors) {
+ addAuthor(author.name.toString(), author.description.toString());
+ }
+
+ for (const auto &credit : credits) {
+ addCredit(credit.name.toString(), credit.description.toString());
+ }
+
+ loadBackendVersions();
+}
diff --git a/main/aboutdata.h b/main/aboutdata.h
new file mode 100644
index 0000000..624a82d
--- /dev/null
+++ b/main/aboutdata.h
@@ -0,0 +1,18 @@
+/*
+ aboutdata.h
+
+ This file is part of Kleopatra, the KDE keymanager
+ SPDX-FileCopyrightText: 2001, 2002, 2004 Klarälvdalens Datakonsult AB
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#pragma once
+
+#include <KAboutData>
+
+class AboutData : public KAboutData
+{
+public:
+ AboutData();
+};
diff --git a/main/main.cpp b/main/main.cpp
index db9b28e..634c61e 100644
--- a/main/main.cpp
+++ b/main/main.cpp
@@ -1,62 +1,61 @@
/*
SPDX-FileCopyrightText: 2014-2023 Anne Jan Brouwer <brouwer@annejan.com>
SPDX-FileCopyrightText: 2023 g10 Code GmbH
SPDX-FileContributor: Sune Stolborg Vuorela <sune@vuorela.dk>
SPDX-License-Identifier: GPL-3.0-or-later
*/
+#include "aboutdata.h"
#include "widgets/mainwindow.h"
#include <QApplication>
#if QT_VERSION > QT_VERSION_CHECK(6, 0, 0)
#include <KColorSchemeManager>
#endif
#include <KIconLoader>
#include <KLocalizedString>
#include <gpgpass_version.h>
#include <sys/stat.h>
int main(int argc, char *argv[])
{
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
#endif
// allow darkmode
#if defined(Q_OS_WIN)
if (!qEnvironmentVariableIsSet("QT_QPA_PLATFORM")) {
qputenv("QT_QPA_PLATFORM", "windows:darkmode=1");
}
#endif
QApplication app(argc, argv);
+
KLocalizedString::setApplicationDomain("gpgpass");
- Q_INIT_RESOURCE(resources);
+ AboutData aboutData;
+ KAboutData::setApplicationData(aboutData);
- QCoreApplication::setOrganizationName(QStringLiteral("GnuPG"));
- QCoreApplication::setOrganizationDomain(QStringLiteral("gnupg.org"));
- QCoreApplication::setApplicationName(QStringLiteral("GnuPGPass"));
- QCoreApplication::setApplicationVersion(QString::fromUtf8(GPGPASS_VERSION_STRING));
+ Q_INIT_RESOURCE(resources);
#ifdef Q_OS_WINDOWS
QApplication::setWindowIcon(QIcon(QStringLiteral(":/artwork/64-gpgpass.png")));
#else
QApplication::setWindowIcon(QIcon::fromTheme(QStringLiteral("org.gnupg.gpgpass")));
#endif
- QGuiApplication::setDesktopFileName(QStringLiteral("org.gnupg.gpgpass"));
auto w = new MainWindow;
// ensure KIconThemes is loaded for rcc icons
KIconLoader::global()->hasIcon(QString{});
#if QT_VERSION > QT_VERSION_CHECK(6, 0, 0)
KColorSchemeManager m;
#endif
w->show();
return app.exec();
}
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 1f4d853..1114541 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -1,125 +1,128 @@
# let's put most of the "meat" in a static library this way, we can also unit
# test parts of it
add_library(gpgpass_internal STATIC)
target_sources(gpgpass_internal PRIVATE
../resources.qrc
clipboardhelper.cpp
clipboardhelper.h
gpgmehelpers.h
gpgmehelpers.h
passentry.cpp
passentry.h
passphrasegenerator.cpp
passphrasegenerator.h
passwordhealth.cpp
passwordhealth.h
passwordgenerator.cpp
passwordgenerator.h
rootfoldersmanager.cpp
rootfoldersmanager.h
userinfo.cpp
userinfo.h
util.cpp
util.h
job/directoryreencryptjob.cpp
job/directoryreencryptjob.h
job/fileencryptjob.cpp
job/fileencryptjob.h
job/filedecryptjob.cpp
job/filedecryptjob.h
job/filereencryptjob.cpp
job/filereencryptjob.h
conf/configuredialog.cpp
conf/configuredialog.h
conf/generalconfigurationpage.cpp
conf/generalconfigurationpage.h
conf/gpgpassconfigmodule.cpp
conf/gpgpassconfigmodule.h
conf/gpgpasspageconfigdialog.cpp
conf/gpgpasspageconfigdialog.h
conf/rootfoldersconfigurationpage.cpp
conf/rootfoldersconfigurationpage.h
conf/templateconfigurationpage.cpp
conf/templateconfigurationpage.h
models/addfileinfoproxy.cpp
models/addfileinfoproxy.h
models/storemodel.cpp
models/storemodel.h
models/userslistmodel.cpp
models/userslistmodel.h
models/rootfoldersmodel.cpp
models/rootfoldersmodel.h
widgets/adjustingscrollarea.cpp
widgets/adjustingscrollarea.h
widgets/deselectabletreeview.h
widgets/formtextinput.cpp
widgets/formtextinput.h
widgets/mainwindow.cpp
widgets/mainwindow.h
widgets/passwordeditorwidget.cpp
widgets/passwordeditorwidget.h
widgets/passwordgeneratorwidget.cpp
widgets/passwordgeneratorwidget.h
widgets/passwordviewerwidget.cpp
widgets/passwordviewerwidget.h
widgets/qpushbuttonfactory.h
widgets/qrcodepopup.cpp
widgets/qrcodepopup.h
widgets/setupwidget.cpp
widgets/setupwidget.h
widgets/usersdialog.cpp
widgets/usersdialog.h
widgets/welcomewidget.cpp
widgets/welcomewidget.h
zxcvbn/zxcvbn.c
zxcvbn/zxcvbn.h
)
kconfig_add_kcfg_files(gpgpass_internal GENERATE_MOC
config.kcfgc
rootfolderconfig.kcfgc
)
ki18n_wrap_ui(gpgpass_internal
conf/generalconfigurationpage.ui
conf/rootfoldersconfigurationpage.ui
conf/templateconfigurationpage.ui
widgets/passwordeditorwidget.ui
widgets/passwordgeneratorwidget.ui
widgets/welcomewidget.ui
widgets/mainwindow.ui
widgets/usersdialog.ui
widgets/userswidget.ui
)
target_link_libraries(gpgpass_internal
Qt::Widgets
KF${QT_MAJOR_VERSION}::CoreAddons
KF${QT_MAJOR_VERSION}::ConfigCore
KF${QT_MAJOR_VERSION}::ConfigGui
KF${QT_MAJOR_VERSION}::Prison
KF${QT_MAJOR_VERSION}::IconThemes
KF${QT_MAJOR_VERSION}::I18n
KF${QT_MAJOR_VERSION}::WidgetsAddons
KF${QT_MAJOR_VERSION}::ItemModels
KF${QT_MAJOR_VERSION}::ItemViews
KF${QT_MAJOR_VERSION}::KIOWidgets
+ KF${QT_MAJOR_VERSION}::XmlGui
KPim${QT_MAJOR_VERSION}::Libkleo
)
+install(FILES gpgpassui.rc DESTINATION ${KDE_INSTALL_KXMLGUIDIR}/gpgpass)
+
if (QT_MAJOR_VERSION STREQUAL "6")
target_link_libraries(gpgpass_internal QGpgmeQt6 LibGpgError::LibGpgError KF6::ColorScheme)
target_sources(gpgpass_internal PRIVATE
job/openpgpcertificatecreationjob.cpp
job/openpgpcertificatecreationjob.h
)
else()
target_link_libraries(gpgpass_internal QGpgme KF5::ConfigWidgets)
endif()
diff --git a/src/gpgpassui.rc b/src/gpgpassui.rc
new file mode 100644
index 0000000..312250e
--- /dev/null
+++ b/src/gpgpassui.rc
@@ -0,0 +1,30 @@
+<?xml version="1.0"?>
+<!DOCTYPE gui SYSTEM "kpartgui.dtd" >
+<gui version="1" name="gpgpass" translationDomain="gpgpass">
+ <MenuBar>
+ <Menu name="file" >
+ <Action name="file_new" />
+ <Action name="add_folder" />
+ <Separator/>
+ <Action name="gpgpass_edit" />
+ <Action name="gpgpass_delete" />
+ <Separator/>
+ <Action name="gpgpass_users" />
+ <Action name="gpgpass_users" />
+ <Action name="file_quit" />
+ </Menu>
+ <Menu name="edit" />
+ </MenuBar>
+
+ <ToolBar noMerge="1" name="mainToolBar" fullWidth="true" >
+ <text>Main Toolbar</text>
+ <Action name="add_entry" />
+ <Action name="add_folder" />
+ <Separator/>
+ <Action name="gpgpass_edit" />
+ <Action name="gpgpass_delete" />
+ <Separator/>
+ <Action name="gpgpass_users" />
+ <Action name="options_configure" />
+ </ToolBar>
+</gui>
diff --git a/src/widgets/mainwindow.cpp b/src/widgets/mainwindow.cpp
index 7a8ff81..105d342 100644
--- a/src/widgets/mainwindow.cpp
+++ b/src/widgets/mainwindow.cpp
@@ -1,853 +1,867 @@
/*
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 <KActionCollection>
#include <KLocalizedString>
#include <KMessageBox>
#include <KMessageWidget>
+#include <KStandardAction>
#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)
+ : KXmlGuiWindow(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);
+ initToolBarButtons();
+ setupGUI(ToolBar | Keys | Save | Create, QStringLiteral("gpgpassui.rc"));
+ setCentralWidget(ui->centralWidget);
+
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);
connect(m_passwordEditor, &PasswordEditorWidget::editorClosed, this, [this]() {
- ui->actionEdit->setChecked(false);
+ m_actionEdit->setChecked(false);
});
- 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);
- });
+ m_actionAddEntry = KStandardAction::openNew(this, &MainWindow::addPassword, actionCollection());
+ m_actionAddEntry->setText(i18nc("@action", "Add Entry"));
+ m_actionAddEntry->setToolTip(i18nc("@info:tooltip", "Add Password Entry"));
+
+ m_actionAddFolder = new QAction(QIcon::fromTheme(QStringLiteral("folder-new-symbolic")), i18nc("@action", "Add Folder"), this);
+ actionCollection()->addAction(QStringLiteral("add_folder"), m_actionAddFolder);
+ connect(m_actionAddFolder, &QAction::triggered, this, &MainWindow::addFolder);
+
+ m_actionDelete = new QAction(QIcon::fromTheme(QStringLiteral("edit-delete-symbolic")), i18nc("@action", "Delete"), this);
+ actionCollection()->addAction(QStringLiteral("gpgpass_delete"), m_actionDelete);
+ connect(m_actionDelete, &QAction::triggered, this, &MainWindow::onDelete);
+
+ m_actionEdit = new QAction(QIcon::fromTheme(QStringLiteral("document-properties-symbolic")), i18nc("@action", "Edit"), this);
+ m_actionEdit->setCheckable(true);
+ actionCollection()->addAction(QStringLiteral("gpgpass_edit"), m_actionEdit);
+ connect(m_actionEdit, &QAction::toggled, this, &MainWindow::onEdit);
+
+ m_actionUsers = new QAction(QIcon::fromTheme(QStringLiteral("x-office-address-book-symbolic")), i18nc("@action", "Users"), this);
+ m_actionUsers->setToolTip(i18nc("@info:tooltip", "Manage who can read password in folder"));
+ actionCollection()->addAction(QStringLiteral("gpgpass_users"), m_actionUsers);
+ connect(m_actionUsers, &QAction::triggered, this, &MainWindow::onUsers);
+ KStandardAction::quit(this, &MainWindow::close, actionCollection());
+
+ m_actionConfig = KStandardAction::preferences(
+ this,
+ [this] {
+ openConfig(ConfigureDialog::Page::None);
+ },
+ actionCollection());
+
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) {
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);
+ m_actionEdit->setChecked(false);
QFileInfo fileInfo(file);
if (!file.isEmpty() && fileInfo.isFile() && !cleared) {
m_selectedFile = file;
const auto filePath = fileInfo.absoluteFilePath();
if (m_passwordViewer->filePath() != filePath) {
m_passwordViewer->setFilePath(ui->treeView->selectionModel()->currentIndex().data().toString(), filePath);
}
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);
+ m_actionEdit->setEnabled(false);
+ m_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;
}
const auto filePath = fileInfo.absoluteFilePath();
if (m_passwordViewer->filePath() == filePath && !m_passwordViewer->rawContent().isEmpty()) {
switchToPasswordEditor(filePath, m_passwordViewer->rawContent());
} else {
auto decryptJob = new FileDecryptJob(filePath);
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)
{
// Ensure we don't trigger the actionEdit
- disconnect(ui->actionEdit, &QAction::toggled, this, &MainWindow::onEdit);
- ui->actionEdit->setChecked(true);
- connect(ui->actionEdit, &QAction::toggled, this, &MainWindow::onEdit);
+ disconnect(m_actionEdit, &QAction::toggled, this, &MainWindow::onEdit);
+ m_actionEdit->setChecked(true);
+ connect(m_actionEdit, &QAction::toggled, this, &MainWindow::onEdit);
const QFileInfo fileInfo(filePath);
const auto name = fileInfo.baseName();
const auto absoluteFilePath = fileInfo.absoluteFilePath();
PassEntry entry(name, content);
m_passwordEditor->setPassEntry(entry);
ui->stackedWidget->setCurrentIndex((int)StackLayer::PasswordEditor);
disconnect(m_passwordEditor, &PasswordEditorWidget::save, this, nullptr);
connect(m_passwordEditor, &PasswordEditorWidget::save, this, [this, name, fileInfo, absoluteFilePath](const QString &content) {
const auto recipients = m_storeModel.recipientsForFile(fileInfo);
auto encryptJob = new FileEncryptJob(absoluteFilePath, content.toUtf8(), recipients);
connect(encryptJob, &FileEncryptJob::finished, this, [absoluteFilePath, name, encryptJob, content, this](KJob *) {
if (encryptJob->error() != KJob::NoError) {
m_errorMessage->setText(encryptJob->errorText());
m_errorMessage->animatedShow();
qWarning() << encryptJob->errorText();
return;
}
ui->treeView->setFocus();
// Ensure we don't trigger the actionEdit
- disconnect(ui->actionEdit, &QAction::toggled, this, &MainWindow::onEdit);
- ui->actionEdit->setChecked(false);
- connect(ui->actionEdit, &QAction::toggled, this, &MainWindow::onEdit);
+ disconnect(m_actionEdit, &QAction::toggled, this, &MainWindow::onEdit);
+ m_actionEdit->setChecked(false);
+ connect(m_actionEdit, &QAction::toggled, this, &MainWindow::onEdit);
if (m_passwordViewer->filePath() == absoluteFilePath) {
m_passwordViewer->setRawContent(content);
} else {
// slower but insure state is correct
m_passwordViewer->setFilePath(name, absoluteFilePath);
}
ui->stackedWidget->setCurrentIndex((int)StackLayer::PasswordViewer);
auto index = m_storeModel.indexForPath(absoluteFilePath);
ui->treeView->setCurrentIndex(index);
selectTreeItem(index);
});
encryptJob->start();
});
}
/**
* @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_actionEdit->setEnabled(false);
+ m_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);
+ m_actionAddEntry->setEnabled(state);
+ m_actionAddFolder->setEnabled(state);
+ m_actionUsers->setEnabled(state);
+ m_actionConfig->setEnabled(state);
// is a file selected?
state &= ui->treeView->currentIndex().isValid();
- ui->actionDelete->setEnabled(state);
- ui->actionEdit->setEnabled(state);
+ m_actionDelete->setEnabled(state);
+ m_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);
+ m_actionEdit->setEnabled(false);
+ m_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 entry?"),
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);
+ m_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;
}
setUiElementsEnabled(true);
ui->treeView->setFocus();
verifyInitialized();
usersDialog->deleteLater();
});
// 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);
+ m_actionEdit->setEnabled(false);
+ m_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);
+ m_actionAddFolder->setEnabled(alreadyConfigured);
+ m_actionAddEntry->setEnabled(alreadyConfigured);
+ m_actionDelete->setEnabled(m_actionDelete->isEnabled() && alreadyConfigured);
+ m_actionEdit->setEnabled(m_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);
+ m_actionDelete->setEnabled(false);
+ m_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 Entry"));
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 Entry"));
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 Entry"));
renamePasswordAction->setToolTip(i18nc("@info:tooltip", "Rename Password Entry"));
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 Entry") : 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 ce0562b..c853d2a 100644
--- a/src/widgets/mainwindow.h
+++ b/src/widgets/mainwindow.h
@@ -1,128 +1,137 @@
/*
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>
+#include <KXmlGuiWindow>
+
#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
+class MainWindow : public KXmlGuiWindow
{
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;
+ QAction *m_actionAddEntry = nullptr;
+ QAction *m_actionAddFolder = nullptr;
+ QAction *m_actionEdit = nullptr;
+ QAction *m_actionDelete = nullptr;
+ QAction *m_actionUsers = nullptr;
+ QAction *m_actionConfig = nullptr;
+
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_
diff --git a/src/widgets/mainwindow.ui b/src/widgets/mainwindow.ui
index 162d323..e425d71 100644
--- a/src/widgets/mainwindow.ui
+++ b/src/widgets/mainwindow.ui
@@ -1,223 +1,141 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
- <widget class="QMainWindow" name="MainWindow">
+ <widget class="KXmlGuiWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>801</width>
<height>484</height>
</rect>
</property>
<property name="windowTitle">
<string>GnuPG Password Manager</string>
</property>
<widget class="QWidget" name="centralWidget">
<layout class="QVBoxLayout" name="gridLayout_2">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<layout class="QVBoxLayout" name="messagesArea"/>
</item>
<item>
<widget class="QSplitter" name="mainSplitter">
<property name="childrenCollapsible"><bool>false</bool></property>
<widget class="QWidget" name="sidebar">
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<enum>0</enum>
</property>
<item>
<layout class="QVBoxLayout" name="lineEditWrapper">
<item>
<widget class="QLineEdit" name="lineEdit">
<property name="minimumSize">
<size>
<width>0</width>
<height>26</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::StrongFocus</enum>
</property>
<property name="text">
<string/>
</property>
<property name="placeholderText">
<string>Search Password</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QFrame" name="separator" />
</item>
<item>
<widget class="DeselectableTreeView" name="treeView">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="acceptDrops">
<bool>true</bool>
</property>
<property name="sizeAdjustPolicy">
<enum>QAbstractScrollArea::AdjustIgnored</enum>
</property>
<property name="dragEnabled">
<bool>true</bool>
</property>
<property name="dragDropMode">
<enum>QAbstractItemView::InternalMove</enum>
</property>
<property name="sortingEnabled">
<bool>true</bool>
</property>
<property name="animated">
<bool>true</bool>
</property>
<attribute name="headerStretchLastSection">
<bool>false</bool>
</attribute>
</widget>
</item>
</layout>
</widget>
<widget class="QStackedWidget" name="stackedWidget">
<widget class="WelcomeWidget" name="welcomeWidget"/>
</widget>
</widget>
</item>
</layout>
</widget>
- <widget class="QToolBar" name="toolBar">
- <property name="contextMenuPolicy">
- <enum>Qt::PreventContextMenu</enum>
- </property>
- <property name="acceptDrops">
- <bool>false</bool>
- </property>
- <property name="windowTitle">
- <string/>
- </property>
- <property name="movable">
- <bool>false</bool>
- </property>
- <property name="toolButtonStyle">
- <enum>Qt::ToolButtonFollowStyle</enum>
- </property>
- <attribute name="toolBarArea">
- <enum>TopToolBarArea</enum>
- </attribute>
- <attribute name="toolBarBreak">
- <bool>false</bool>
- </attribute>
- <addaction name="actionAddPassword"/>
- <addaction name="actionAddFolder"/>
- <addaction name="separator"/>
- <addaction name="actionEdit"/>
- <addaction name="actionDelete"/>
- <addaction name="separator"/>
- <addaction name="actionUsers"/>
- <addaction name="actionConfig"/>
- </widget>
- <action name="actionAddPassword">
- <property name="text">
- <string>Add Entry</string>
- </property>
- <property name="toolTip">
- <string>Add password entry</string>
- </property>
- <property name="shortcut">
- <string>Ctrl+N</string>
- </property>
- </action>
- <action name="actionAddFolder">
- <property name="text">
- <string>Add Folder</string>
- </property>
- <property name="toolTip">
- <string>Add folder</string>
- </property>
- </action>
- <action name="actionEdit">
- <property name="text">
- <string>Edit</string>
- </property>
- <property name="toolTip">
- <string>Edit</string>
- </property>
- </action>
- <action name="actionDelete">
- <property name="text">
- <string>Delete</string>
- </property>
- <property name="toolTip">
- <string>Delete</string>
- </property>
- </action>
- <action name="actionUsers">
- <property name="text">
- <string>Users</string>
- </property>
- <property name="toolTip">
- <string>Manage who can read password in folder</string>
- </property>
- </action>
- <action name="actionConfig">
- <property name="text">
- <string>Config</string>
- </property>
- <property name="toolTip">
- <string>Configuration</string>
- </property>
- </action>
</widget>
<layoutdefault spacing="6" margin="11"/>
<customwidgets>
<customwidget>
<class>DeselectableTreeView</class>
<extends>QTreeView</extends>
<header>widgets/deselectabletreeview.h</header>
<slots>
<signal>signal1()</signal>
</slots>
</customwidget>
<customwidget>
<class>WelcomeWidget</class>
<extends>QWidget</extends>
<header>widgets/welcomewidget.h</header>
</customwidget>
<customwidget>
<class>KTitleWidget</class>
<extends>QWidget</extends>
<header>ktitlewidget.h</header>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>lineEdit</tabstop>
<tabstop>treeView</tabstop>
</tabstops>
<resources/>
<connections/>
<slots>
<slot>deselect()</slot>
</slots>
</ui>

File Metadata

Mime Type
text/x-diff
Expires
Fri, Mar 13, 9:55 AM (18 h, 25 m)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
02/13/f453851aa6608e11bcaf2ebae21c

Event Timeline