Page Menu
Home
GnuPG
Search
Configure Global Search
Log In
Files
F37528976
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Size
58 KB
Subscribers
None
View Options
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
Details
Attached
Mime Type
text/x-diff
Expires
Fri, Mar 13, 9:55 AM (15 m, 44 s)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
02/13/f453851aa6608e11bcaf2ebae21c
Attached To
rGPGPASS GnuPG Password Manager
Event Timeline
Log In to Comment