Compare commits

...

92 Commits

Author SHA1 Message Date
Martchus cabeca7756 Don't build the Qt Quick GUI by default for Windows and Apple platforms
It uses Kirigami which is probably not present/wanted on those platforms.
2024-04-13 16:50:59 +02:00
Martchus 06ae52d0d8 Update translations 2024-04-08 12:40:05 +02:00
Martchus 6ffa54088a Ensure desktop file name is set when initializing GUIs 2024-04-08 12:39:46 +02:00
Martchus 1b6b645f7a Apply cmake-format 2024-04-08 12:39:05 +02:00
Martchus 1ce919397b Bump patch version 2024-04-08 12:38:47 +02:00
Martchus 3c706b614e Clarify condition in Qt version check for Quick GUI 2024-04-02 15:19:49 +02:00
Martchus e095dc904f Update release date 2024-04-02 11:09:47 +02:00
Martchus a37ee658a6 Update translations 2024-04-01 15:41:34 +02:00
Martchus 9653b4c0d5 Bump minor version after all the Android and Qt Quick GUI related changes 2024-04-01 15:41:09 +02:00
Martchus 613d62d042 Elide entry names in entries/fields list items 2024-04-01 15:32:47 +02:00
Martchus 8319d4f485 Extend Android-related remarks in README 2024-04-01 14:55:58 +02:00
Martchus c2fc538e2c Avoid hidden swipe actions in mobile/tablet mode as those are broken
When clicking on an action the action is not triggered but the whole list
item enters a broken state instead. This can also be reproduced on the
desktop by setting `QT_QUICK_CONTROLS_MOBILE=1`.
2024-04-01 14:40:19 +02:00
Martchus f20c7b5d3d Avoid using `story-editor` icon as it is not actually dark in breeze-dark 2024-04-01 14:24:28 +02:00
Martchus b2d85c7d53 Refactor various aspects of the code
* Prefer using auto
* Reduce nesting in certain places
* Simplify code in main function
2024-04-01 13:59:22 +02:00
Martchus 0f4c30c14f Format Android-specific files (also when not building for Android) 2024-03-31 23:04:39 +02:00
Martchus 21a16e0be0 Add approach for handling darkmode changes dynamically
This seems to work for plain QCC2 parts but unfortunately doesn't help with
Kirigami (see added not). Hence this is disabled for now.
2024-03-31 22:53:32 +02:00
Martchus ba9a6e26de Clean code for initializing Qt Quick GUI 2024-03-31 22:46:01 +02:00
Martchus 9ebffe6b16 Exit application when the main QML file cannot be loaded 2024-03-31 22:43:48 +02:00
Martchus 64a2738827 Update translations 2024-03-31 13:50:00 +02:00
Martchus a098b40dab Apply clang-format 2024-03-31 13:49:44 +02:00
Martchus dbc2e9a4e2 Select correct icon theme for Qt Quick GUI depending on darkmode setting 2024-03-31 13:49:34 +02:00
Martchus f960aa80a1 Remove Android-specific debugging code for Qt Quick app 2024-03-31 13:44:48 +02:00
Martchus b950451807 Avoid explicit color definitions in Qt Quick GUI
Those might break dark mode and using custom color palettes in general.
2024-03-31 12:57:35 +02:00
Martchus 20aea51fa1 Update translations 2024-03-26 22:54:47 +01:00
Martchus 5706086456 Streamline setup of Qt Quick GUI with Qt Widgets GUI 2024-03-26 22:54:10 +01:00
Martchus b8d2422669 Bundle icons and translations when building for Android 2024-03-26 22:52:21 +01:00
Martchus 7a08ee7d00 Adapt README to latest changes 2024-03-26 22:50:20 +01:00
Martchus 7c03835fc3 Improve documentation of keystore handling 2024-03-26 21:58:35 +01:00
Martchus 0791964c6f Apply clang-format and cmake-format 2024-03-25 18:34:22 +01:00
Martchus f6096f7232 Avoid showing title/icon in Qt Quick GUI
It doesn't look as good in the KF6-based version anymore and cannot be made
clickable anyway.
2024-03-25 18:34:22 +01:00
Martchus 79a0ef031c Improve specifying dependencies for Qt Quick GUI
* Remove QuickControls2 as the CMake module from qtutilities take care of
  it
* Replace Kirigami2 with Kirigami because the former is deprecated
2024-03-25 18:34:22 +01:00
Martchus cb6fe128c4 Set app domain for the sake of displaying the URL in the Qt Quick GUI 2024-03-25 18:34:22 +01:00
Martchus eed9d596ba Simplify code for about dialog of Qt Quick GUI 2024-03-25 18:34:22 +01:00
Martchus cd23eb4add Avoid warning about missing version when building Qt Quick GUI 2024-03-25 18:34:22 +01:00
Martchus 4891ee0b32 Update translations 2024-03-25 18:34:22 +01:00
Martchus 6972256727 Improve Qt Quick GUI after porting to Qt 6
* Avoid use of deprecated QML features
* Fix problems when moving items by making the delegates own items
* Improve code in models, especially functions for moving rows
* Fix sizing of dialogs
* Replace code relying on `bannerClicked` event which has been removed in
  KF6
* Use the native file dialog if supported (and otherwise not); this should
  give the desired behavior under each platform out of the box
* Update versioning requirements in README
2024-03-24 22:26:44 +01:00
Martchus 4bf6a91d72 Port Qt Quick GUI for Android to Qt 6 2024-03-17 00:34:00 +01:00
Martchus 430e17416a Update copyright date 2024-02-24 21:16:53 +01:00
Martchus 8f8eda8755 Mention key used for signing binaries in README 2024-02-07 19:27:58 +01:00
Martchus fcbc3cb4a4 Add tab-stops explicitly where default order is wrong 2024-01-07 23:41:39 +01:00
Martchus 7b242732e8 Bump patch version 2024-01-07 23:40:42 +01:00
Martchus 343121ae90 Update release date 2023-11-21 21:53:07 +01:00
Martchus a8023d255a State minimum required Windows 10 version 2023-11-18 21:42:07 +01:00
Martchus 5ebf64d1de Enable creating a backup file by default 2023-11-18 21:13:57 +01:00
Martchus 9aa8d9c9ac Bump patch version 2023-11-18 21:13:36 +01:00
Martchus 1993046e96 Avoid CMake deprecation warning by bumping version 2023-07-23 21:06:56 +02:00
Martchus a93e04fd14 Update release date 2023-04-04 21:22:05 +02:00
Martchus ef6b6085b1 Update translations 2023-03-30 00:05:08 +02:00
Martchus 5acf8b0cc0 Apply Qt settings immediately 2023-03-30 00:04:39 +02:00
Martchus 0ead95e749 Update style sheet on palette changes accordingly 2023-03-30 00:04:17 +02:00
Martchus a45734de1d Remove accidentally hard-coded font 2023-03-30 00:02:01 +02:00
Martchus e407f0b455 Bump patch release 2023-03-30 00:00:49 +02:00
Martchus 5ef3ac49bf Update release date 2023-03-07 20:09:00 +01:00
Martchus 991873c149 Update translations 2023-03-04 18:29:59 +01:00
Martchus 34e7fb123a Use `pubsetbuf` only with `libstdc++`
This usage of the function seems only to work as intended with that
standard lib. With `libc++` and the MSVC standard lib the call has no
effect.
2023-02-28 21:04:00 +01:00
Martchus 140d2e1782 Update translations 2023-02-18 19:39:51 +01:00
Martchus e8c5fdfb56 Consider all QML files for translations
Ensure QML files not used in the current build (as they're
Qt-version-specific and a different Qt version is used) are still
considered when generating the XML files for translations. Otherwise they
will be showing as vanished or obsoleted when they shouldn't.
2023-02-18 19:39:37 +01:00
Martchus a829ca0e65 Handle errors when restoring/saving settings 2023-02-18 19:19:59 +01:00
Martchus 05e0ad5696 Port Qt Quick GUI to Qt 6 and KF6 2023-01-31 17:37:18 +01:00
Martchus e1f753cd89 Remove unused QML import 2023-01-31 16:07:56 +01:00
Martchus 25ef1dbe91 Allow adding additional QML import paths via environment variable
This is useful to use a custom build of kirigami.
2023-01-31 16:02:00 +01:00
Martchus a8364be203 Avoid use of deprecated `QString::count()` 2023-01-31 15:35:59 +01:00
Martchus b32c29c29f Bump patch version 2023-01-31 15:35:59 +01:00
Martchus 3a1495ce53 Update copyright notice 2023-01-17 18:38:00 +01:00
Martchus 9e944618b7 Update release date 2022-10-12 23:28:09 +02:00
Martchus 16876710e0 Add release date 2022-10-12 23:27:53 +02:00
Martchus 9c3a236672 Fix typo in README 2022-08-28 00:19:48 +02:00
Martchus eb1b73f4be Allow portable configuration via `QtUtilities::getSettings()`
See https://github.com/Martchus/tageditor/issues/88
2022-08-20 16:29:56 +02:00
Martchus 217942e946 Avoid getter being mismarked as slot in MainWindow 2022-08-20 16:22:23 +02:00
Martchus 3f7ea3095e Document QT_PACKAGE_PREFIX 2022-05-16 21:08:28 +02:00
Martchus 46d35530c4 Update download instructions 2022-05-16 20:46:59 +02:00
Martchus 24300cd8da Add stalebot config 2022-04-12 01:08:16 +02:00
Martchus 37ee70dfa4 Add copyright notice 2022-04-05 20:19:34 +02:00
Martchus 2b155c925f Increment patch version 2022-03-15 21:42:47 +01:00
Martchus 7cf5972d27 Clarify that license is "GPL-2-or-later" 2022-03-15 21:41:30 +01:00
Martchus d020aaab63 Update license info for Windows distribution 2022-03-15 00:57:15 +01:00
Martchus eb6fc17f6b Adapt C++ code of Qt Quick GUI to Qt 6 2022-01-29 19:45:14 +01:00
Martchus 16b1051c68 Fix warnings when building against Qt 6 2022-01-29 19:44:19 +01:00
Martchus 418104b6cb Add OBS repository URL as download page doesn't contain all versions 2022-01-22 17:51:43 +01:00
Martchus cfb4423f79 Fix warning about possible null pointer deref 2021-12-11 23:48:59 +01:00
Martchus 2428c3e656 Update/improve build instructions 2021-12-05 18:36:52 +01:00
Martchus d97d291b16 Add notes about compatibility of Windows builds 2021-10-05 18:18:20 +02:00
Martchus 7e9f319d2d Update translations 2021-08-22 00:05:33 +02:00
Martchus 54e21607df Apply clang-format and cmake-format 2021-08-22 00:05:07 +02:00
Martchus 6684f1b627 Fix warning about duplicated branch 2021-08-22 00:03:12 +02:00
Martchus 641e02f64c Fix shadowing warning 2021-08-22 00:02:48 +02:00
Martchus 3a01f2de31 Fix typos found via `codespell --skip .git -w` 2021-07-03 19:46:22 +02:00
Martchus acbfd6ec6b Fix warnings 2021-03-20 21:57:47 +01:00
Martchus 4c85e7710b Improve download section of README 2020-12-05 21:23:36 +01:00
Martchus bdefea2e59 Update RPM download section 2020-12-05 21:10:00 +01:00
Martchus 79038808ce Fix compilation with Qt 6 (beta1) 2020-10-23 17:01:34 +02:00
Martchus 04bf32cf6b Allow configuring package suffix for qtutilities individually 2020-10-23 17:01:11 +02:00
47 changed files with 7875 additions and 2982 deletions

19
.github/stale.yml vendored Normal file
View File

@ -0,0 +1,19 @@
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 60
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 7
# Issues with these labels will never be considered stale
exemptLabels:
- pinned
- security
- feature request
- enhancement
# Label to use when marking an issue as stale
staleLabel: stale
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: false

3
.gitignore vendored
View File

@ -42,3 +42,6 @@ Makefile*
# clang-format
/.clang-format
# Android-specific
/android/AndroidManifest.xml

View File

@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.1.0 FATAL_ERROR)
cmake_minimum_required(VERSION 3.17.0 FATAL_ERROR)
# set meta data
project(passwordmanager)
@ -9,13 +9,15 @@ set(META_APP_NAME "Password Manager")
set(META_APP_CATEGORIES "Utility;Security;")
set(META_APP_AUTHOR "Martchus")
set(META_APP_URL "https://github.com/${META_APP_AUTHOR}/${META_PROJECT_NAME}")
set(META_APP_DOMAIN "${META_APP_URL}")
set(META_APP_DESCRIPTION "A simple password store using AES-256-CBC encryption via OpenSSL")
set(META_GUI_OPTIONAL YES)
set(META_USE_QQC2 ON)
set(META_ANDROID_PACKAGE_NAME "org.martchus.passwordmanager")
set(META_VERSION_MAJOR 4)
set(META_VERSION_MINOR 1)
set(META_VERSION_PATCH 5)
set(META_VERSION_MINOR 2)
set(META_VERSION_PATCH 1)
set(META_RELEASE_DATE "2024-04-02")
# add project files
set(HEADER_FILES cli/cli.h model/entryfiltermodel.h model/entrymodel.h model/fieldmodel.h)
@ -24,13 +26,8 @@ set(SRC_FILES cli/cli.cpp main.cpp)
set(GUI_HEADER_FILES model/entryfiltermodel.cpp model/entrymodel.cpp model/fieldmodel.cpp)
set(GUI_SRC_FILES model/entryfiltermodel.cpp model/entrymodel.cpp model/fieldmodel.cpp)
set(WIDGETS_HEADER_FILES
gui/fielddelegate.h
gui/initiategui.h
gui/mainwindow.h
gui/passwordgeneratordialog.h
gui/stacksupport.h
gui/undocommands.h)
set(WIDGETS_HEADER_FILES gui/fielddelegate.h gui/initiategui.h gui/mainwindow.h gui/passwordgeneratordialog.h
gui/stacksupport.h gui/undocommands.h)
set(WIDGETS_SRC_FILES
gui/fielddelegate.cpp
gui/initiategui.cpp
@ -46,6 +43,8 @@ set(QML_SRC_FILES quickgui/controller.cpp quickgui/initiatequick.cpp resources/i
if (ANDROID)
list(APPEND QML_HEADER_FILES quickgui/android.h)
list(APPEND QML_SRC_FILES quickgui/android.cpp)
else ()
list(APPEND EXCLUDED_FILES quickgui/android.h quickgui/android.cpp)
endif ()
set(TS_FILES translations/${META_PROJECT_NAME}_de_DE.ts translations/${META_PROJECT_NAME}_en_US.ts)
@ -106,7 +105,7 @@ set(REQUIRED_ICONS
preferences-desktop-locale
qtcreator
search
story-editor
view-list-details-symbolic
system-file-manager
system-run
system-search
@ -114,27 +113,31 @@ set(REQUIRED_ICONS
window-close)
# find c++utilities
set(CONFIGURATION_PACKAGE_SUFFIX ""
set(CONFIGURATION_PACKAGE_SUFFIX
""
CACHE STRING "sets the suffix for find_package() calls to packages configured via c++utilities")
find_package(c++utilities${CONFIGURATION_PACKAGE_SUFFIX} 5.6.0 REQUIRED)
use_cpp_utilities()
# apply basic configuration
set(BUILD_QUICK_GUI_BY_DEFAULT ON)
if (WIN32 OR APPLE)
set(BUILD_QUICK_GUI_BY_DEFAULT OFF)
endif ()
option(QUICK_GUI "enables/disables building the Qt Quick GUI using Kirigami" "${BUILD_QUICK_GUI_BY_DEFAULT}")
include(BasicConfig)
# find qtutilities
find_package(qtutilities${CONFIGURATION_PACKAGE_SUFFIX} 6.0.0 REQUIRED)
set(CONFIGURATION_PACKAGE_SUFFIX_QTUTILITIES
"${CONFIGURATION_PACKAGE_SUFFIX}"
CACHE STRING "sets the suffix for qtutilities")
find_package(qtutilities${CONFIGURATION_PACKAGE_SUFFIX_QTUTILITIES} 6.14.0 REQUIRED)
use_qt_utilities()
# find passwordfile
find_package(passwordfile${CONFIGURATION_PACKAGE_SUFFIX} 5.0.0 REQUIRED)
use_password_file()
# require at least Qt 5.8 for the Qt Quick GUI
if (QUICK_GUI)
set(META_QT5_VERSION 5.8)
endif ()
# allow to enable undo support from the widgets GUI in the quick GUI as well (so the quick GUI will depend on Qt Widgets as
# well)
if (QUICK_GUI AND NOT WIDGETS_GUI)
@ -147,12 +150,44 @@ if (QUICK_GUI AND NOT WIDGETS_GUI)
endif ()
endif ()
# add further Qt/KF modules required by the Qt Quick GUI under Android
if (ANDROID AND QUICK_GUI)
list(APPEND ADDITIONAL_QT_MODULES AndroidExtras)
list(APPEND ADDITIONAL_KF_MODULES Kirigami2)
# deduce major Qt version from package prefix
if (NOT QT_PACKAGE_PREFIX)
set(MAJOR_QT_VERSION "5")
elseif (QT_PACKAGE_PREFIX MATCHES ".*Qt([0-9]+).*")
set(MAJOR_QT_VERSION "${CMAKE_MATCH_1}")
endif ()
# require Qt 6 for the Qt Quick GUI
if (QUICK_GUI AND (MAJOR_QT_VERSION VERSION_LESS 6 OR MAJOR_QT_VERSION VERSION_GREATER_EQUAL 7))
message(FATAL_ERROR "The Qt Quick GUI is only compatible with Qt 6 (but Qt ${MAJOR_QT_VERSION} was found).")
endif ()
# workaround "ld: error: undefined symbol: qt_resourceFeatureZstd" when Qt 6 is not configured with zstd support
if (MAJOR_QT_VERSION GREATER_EQUAL 6 AND NOT QT_FEATURE_zstd)
set(CMAKE_AUTORCC_OPTIONS "--no-zstd")
endif ()
# add further Qt/KF modules required by Qt Quick GUI
if (QUICK_GUI)
list(APPEND ADDITIONAL_KF_MODULES Kirigami)
endif ()
# add Qt-version-specific QML files
unset(QML_FILE)
if (MAJOR_QT_VERSION)
set(QML_FILE "resources/qml${MAJOR_QT_VERSION}.qrc")
endif ()
if (NOT QML_FILE)
message(FATAL_ERROR "Unable to add Qt-version-specific resource file for QT_PACKAGE_PREFIX \"${QT_PACKAGE_PREFIX}\".")
endif ()
message(STATUS "Adding Qt-version-specific resource file \"${QML_FILE}\" to build")
list(APPEND QML_SRC_FILES "${QML_FILE}")
# add other QML files to "EXCLUDED_FILES" so they're still considered for translations in any case
file(GLOB OTHER_QML_FILES "resources/qml*.qrc")
list(REMOVE_ITEM OTHER_QML_FILES ${QML_SRC_FILES})
list(APPEND EXCLUDED_FILES ${OTHER_QML_FILES})
# apply further configuration
if (WIDGETS_GUI OR QUICK_GUI)
include(QtGuiConfig)
@ -160,9 +195,28 @@ if (WIDGETS_GUI OR QUICK_GUI)
endif ()
include(WindowsResources)
include(AppTarget)
include(AndroidApk)
include(ShellCompletion)
include(ConfigHeader)
# configure creating an Android package using androiddeployqt
if (ANDROID)
set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android")
set_target_properties(${META_TARGET_NAME} PROPERTIES QT_ANDROID_PACKAGE_SOURCE_DIR "${ANDROID_PACKAGE_SOURCE_DIR}")
set(ANDROID_MANIFEST_PATH "${ANDROID_PACKAGE_SOURCE_DIR}/AndroidManifest.xml")
configure_file("resources/AndroidManifest.xml.in" "${ANDROID_MANIFEST_PATH}")
# bundle OpenMP (used by Kirigami) explicitly as it is otherwise not bundled
find_package(OpenMP)
if (OpenMP_CXX_FOUND)
message(STATUS "Bundling OpenMP library for Kirigami: ${OpenMP_omp_LIBRARY}")
set_target_properties(${META_TARGET_NAME} PROPERTIES QT_ANDROID_EXTRA_LIBS "${OpenMP_omp_LIBRARY}")
endif ()
set(QT_ANDROID_SIGN_APK ON)
qt_android_generate_deployment_settings(${META_TARGET_NAME})
qt_android_add_apk_target(${META_TARGET_NAME})
endif ()
# create desktop file using previously defined meta data
add_desktop_file()

File diff suppressed because it is too large Load Diff

216
README.md
View File

@ -4,7 +4,7 @@ A simple [password manager](https://en.wikipedia.org/wiki/Password_manager) with
## Features
* Cross-platform: tested under GNU/Linux, Android and Windows
* Qt Widgets GUI for desktop platforms
* Qt Quick GUI (using Qt Quick Controls 2 and Kirigami 2) for mobile platforms
* Qt Quick GUI (using Qt Quick Controls 2 and Kirigami) for mobile platforms
* Interactive command-line interface
* Simple architecture: All data is stored in ordinary files with AES-256-CBC applied. No cloud stuff. Use
eg. Syncthing for synchronization.
@ -13,7 +13,7 @@ A simple [password manager](https://en.wikipedia.org/wiki/Password_manager) with
I've mainly started this project to learn C++ and Qt programming. So beside the mentioned features this project
and the underlying libraries serve as an example project covering some interesting C++/Qt topics:
* Basic use of Qt Widgets, Qt Quick and Kirigami 2
* Basic use of Qt Widgets, Qt Quick and Kirigami
* Creating custom Qt models
* Nested model and model with multiple columns
* Support Drag & Drop in `QTreeView`
@ -22,7 +22,7 @@ and the underlying libraries serve as an example project covering some interesti
* Integration with Qt Widgets' undo/redo framework
* Filtering
* Android tweaks
* Add CMake target to invoke `androiddeployqt`
* Create APK via CMake (using `androiddeployqt`)
* Customize activity
* Customize gradle project to add additional Java dependency
* Adjust the window style of the activity
@ -46,177 +46,124 @@ See the release section on GitHub.
* Arch Linux
* for PKGBUILDs checkout [my GitHub repository](https://github.com/Martchus/PKGBUILDs) or
[the AUR](https://aur.archlinux.org/packages?SeB=m&K=Martchus)
* for a binary repository checkout [my website](http://martchus.no-ip.biz/website/page.php?name=programming)
* there is also a [binary repository](https://martchus.no-ip.biz/repo/arch/ownstuff)
* Tumbleweed, Leap, Fedora
* for RPM \*.spec files and binary repository checkout
[openSUSE Build Servide](https://build.opensuse.org/project/show/home:mkittler)
* there's also a [repo with builds of Git master](https://build.opensuse.org/project/show/home:mkittler:vcs)
* RPM \*.spec files and binaries are available via openSUSE Build Service
* remarks
* Be sure to add the repository that matches the version of your OS and to keep it
in sync when upgrading.
* The linked download pages might be incomplete, use the repositories URL for a full
list.
* latest releases: [download page](https://software.opensuse.org/download.html?project=home:mkittler&package=passwordmanager),
[repositories URL](https://download.opensuse.org/repositories/home:/mkittler),
[project page](https://build.opensuse.org/project/show/home:mkittler)
* Git master: [download page](https://software.opensuse.org/download.html?project=home:mkittler:vcs&package=passwordmanager),
[repositories URL](https://download.opensuse.org/repositories/home:/mkittler:/vcs),
[project page](https://build.opensuse.org/project/show/home:mkittler:vcs)
* Other GNU/Linux systems
* [AppImage repository for releases on the openSUSE Build Service](https://download.opensuse.org/repositories/home:/mkittler:/appimage/AppImage)
* [AppImage repository for builds from Git master the openSUSE Build Service](https://download.opensuse.org/repositories/home:/mkittler:/appimage:/vcs/AppImage/)
* for generic, self-contained binaries checkout the [release section on GitHub](https://github.com/Martchus/passwordmanager/releases)
* Requires glibc>=2.26, OpenGL and libX11
* openSUSE Leap 15, Fedora 27, Debian 10 and Ubuntu 18.04 are recent enough (be sure
the package `libopengl0` is installed on Debian/Ubuntu)
* Supports X11 and Wayland (set the environment variable `QT_QPA_PLATFORM=xcb` to disable
native Wayland support if it does not work on your system)
* Binaries are signed with the GPG key
[`B9E36A7275FC61B464B67907E06FE8F53CDC6A4C`](https://keyserver.ubuntu.com/pks/lookup?search=B9E36A7275FC61B464B67907E06FE8F53CDC6A4C&fingerprint=on&op=index).
* Windows
* for binaries checkout the [release section on GitHub](https://github.com/Martchus/tageditor/releases)
* the Qt 6 based version is stable and preferable but only supports Windows 10 version 1809 and newer
* the Qt 5 based version should still work on older versions down to Windows 7 although this is not regularly checked
* Binaries are signed with the GPG key
[`B9E36A7275FC61B464B67907E06FE8F53CDC6A4C`](https://keyserver.ubuntu.com/pks/lookup?search=B9E36A7275FC61B464B67907E06FE8F53CDC6A4C&fingerprint=on&op=index).
* for mingw-w64 PKGBUILDs checkout [my GitHub repository](https://github.com/Martchus/PKGBUILDs)
* for statically linked binaries checkout the [release section on GitHub](https://github.com/Martchus/tageditor/releases)
* [my website](http://martchus.no-ip.biz/website/page.php?name=programming) also contains an occasionally
updated archive with a dynamically linked executable
## Build instructions
The Password Manager depends on c++utilities and passwordfile. Checkout the README of c++utilities for more details. Note that this project is not built differently than any other CMake project.
The application depends on [c++utilities](https://github.com/Martchus/cpp-utilities) and
[passwordfile](https://github.com/Martchus/passwordfile) and is built the same way as these libraries.
For basic instructions checkout the README file of [c++utilities](https://github.com/Martchus/cpp-utilities).
When the Qt GUI is enabled, Qt and [qtutilities](https://github.com/Martchus/qtutilities) are required, too.
To avoid building c++utilities/passwordfile/qtutilities separately, follow the instructions under
"Building this straight". There's also documentation about
[various build variables](https://github.com/Martchus/cpp-utilities/blob/master/doc/buildvariables.md) which
can be passed to CMake to influence the build.
### Optional dependencies
* When building any Qt GUI, the library qtutilities is required.
* When building with Qt Widgets GUI support, the following Qt modules are required (version 5.6 or higher): core gui widgets
* When building with support for the experimental Qt Quick GUI, the following Qt/KDE modules are required (version 5.12 or higher): core gui qml quick quickcontrols2 kirigami
* When building with support for the experimental Qt Quick GUI, the following Qt/KDE modules are required (version 6.6 or higher): core gui qml quick quickcontrols2 kirigami
To specify the major Qt version to use, set `QT_PACKAGE_PREFIX` (e.g. add `-DQT_PACKAGE_PREFIX:STRING=Qt6`
to the CMake arguments). There's also `KF_PACKAGE_PREFIX` for KDE dependencies. Note that the Qt Quick GUI
always requires the same major Qt version as your KDE modules use.
### Building this straight
1. Install (preferably the latest version of) GCC or Clang, the required Qt modules, CMake and Ninja/Make. OpenSSL, iconv and
zlib are required as well but likely already installed.
2. Get the sources of additional dependencies and the password manager itself. For the lastest version from Git clone the following repositories:
0. Install (preferably the latest version of) the GCC toolchain or Clang, the required Qt modules, OpenSSL, iconv,
zlib, CMake and Ninja.
1. Get the sources of additional dependencies and the password manager itself. For the latest version from Git clone the following repositories:
```
cd $SOURCES
cd "$SOURCES"
git clone https://github.com/Martchus/cpp-utilities.git c++utilities
git clone https://github.com/Martchus/passwordfile.git
git clone https://github.com/Martchus/qtutilities.git # only required for Qt GUI
git clone https://github.com/Martchus/passwordmanager.git
git clone https://github.com/Martchus/subdirs.git
```
3. Build and install everything in one step:
2. Build and install everything in one step:
```
cd $BUILD_DIR
cd "$BUILD_DIR"
cmake \
-G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX="/install/prefix" \
$SOURCES/subdirs/passwordmanager
"$SOURCES/subdirs/passwordmanager"
ninja install
```
* If the install directory is not writable, do **not** conduct the build as root. Instead, set `DESTDIR` to a
writable location (e.g. `DESTDIR="temporary/install/dir" ninja install`) and move the files from there to
the desired location afterwards.
#### Concrete example of 3. for building an Android APK under Arch Linux
Create stuff for signing the package (remove `-DANDROID_APK_FORCE_DEBUG=ON` line in the CMake invocation to actually use this):
Create a key for signing the package (always required; otherwise the APK file won't install):
```
# locate keystore
keystore_dir=/path/to/keystore-dir
keystore_alias=$USER
keystore_url=$keystore_dir/$keystore_alias
# make up some password to protect the store; enter this on keytool invocation
keystore_password=<password>
# set variables for creating keystore and androiddeployqt to find it
export QT_ANDROID_KEYSTORE_PATH=/path/to/keystore-dir QT_ANDROID_KEYSTORE_ALIAS=$USER-devel QT_ANDROID_KEYSTORE_STORE_PASS=$USER-devel QT_ANDROID_KEYSTORE_KEY_PASS=$USER-devel
# create keystore (do only once)
pushd "$keystore_dir"
keytool -genkey -v -keystore "$keystore_alias" -alias "$keystore_alias" -keyalg RSA -keysize 2048 -validity 10000
mkdir -p "${QT_ANDROID_KEYSTORE_PATH%/*}"
pushd "${QT_ANDROID_KEYSTORE_PATH%/*}"
keytool -genkey -v -keystore "$QT_ANDROID_KEYSTORE_ALIAS" -alias "$QT_ANDROID_KEYSTORE_ALIAS" -keyalg RSA -keysize 2048 -validity 10000
popd
```
Build c++utilities, passwordfile, qtutilities and passwordmanager in one step to create an Android APK for arm64-v8a:
Build c++utilities, passwordfile, qtutilities and passwordmanager in one step to create an Android APK for aarch64:
```
# specify Android platform
_pkg_arch=aarch64
_android_arch=arm64-v8a
_android_arch2=arm64
_android_api_level=22
# use Java 17 (the latest Java doesn't work at this point) and avoid unwanted Java options
export PATH=/usr/lib/jvm/java-17-openjdk/bin:$PATH
export _JAVA_OPTIONS=
# set project name
_reponame=passwordmanager
_pkgname=passwordmanager
# configure and build using helpers from android-cmake package
android_arch=aarch64
build_dir=$BUILD_DIR/../manual/passwordmanager-android-$android_arch-release
source /usr/bin/android-env $android_arch
android-$android_arch-cmake -G Ninja -S . -B "$build_dir" \
-DCMAKE_FIND_ROOT_PATH="${ANDROID_PREFIX}" -DANDROID_SDK_ROOT="${ANDROID_HOME}" \
-DPKG_CONFIG_EXECUTABLE:FILEPATH=/usr/bin/android-$android_arch-pkg-config \
-DBUILTIN_ICON_THEMES='breeze;breeze-dark' -DBUILTIN_TRANSLATIONS=ON \
-DQT_PACKAGE_PREFIX:STRING=Qt6 -DKF_PACKAGE_PREFIX:STRING=KF6
cmake --build "$build_dir"
# locate SDK, NDK and further libraries
android_sdk_root=${ANDROID_SDK_ROOT:-/opt/android-sdk}
android_ndk_root=${ANDROID_NDK_ROOT:-/opt/android-ndk}
build_tools_version=$(pacman -Q android-sdk-build-tools | sed 's/.* r\(.*\)-.*/\1/')
other_libs_root=/opt/android-libs/$_pkg_arch
other_libs_include=$other_libs_root/include
root="$android_ndk_root/sysroot;$other_libs_root"
# use Java 8 which seems to be the latest version which works
export PATH=/usr/lib/jvm/java-8-openjdk/jre/bin/:$PATH
# configure with the toolchain file provided by the Android NDK (still WIP)
# note: This configuration is likely required in the future to resolve https://gitlab.kitware.com/cmake/cmake/issues/18739. But for now
# better keep using CMake's internal Android support because this config has its own pitfalls (see CMAKE_CXX_FLAGS).
cmake \
-DCMAKE_BUILD_TYPE=Release \
-DANDROID_ABI=$_android_arch \
-DANDROID_PLATFORM=$_android_api_level \
-DCMAKE_TOOLCHAIN_FILE=$android_ndk_root/build/cmake/android.toolchain.cmake \
-DCMAKE_SYSTEM_NAME=Android \
-DCMAKE_SYSTEM_VERSION=$_android_api_level \
-DCMAKE_ANDROID_ARCH_ABI=$_android_arch \
-DCMAKE_ANDROID_NDK="$android_ndk_root" \
-DCMAKE_ANDROID_SDK="$android_sdk_root" \
-DCMAKE_ANDROID_STL_TYPE=c++_shared \
-DCMAKE_INSTALL_PREFIX=$other_libs_root \
-DCMAKE_PREFIX_PATH="$root" \
-DCMAKE_FIND_ROOT_PATH="$root;$root/libs" \
-DCMAKE_CXX_FLAGS="-include $android_ndk_root/sysroot/usr/include/math.h -include $android_ndk_root/sources/cxx-stl/llvm-libc++/include/math.h -I$other_libs_include" \
-DBUILD_SHARED_LIBS=ON \
-DZLIB_LIBRARY="$android_ndk_root/platforms/android-$_android_api_level/arch-$_android_arch2/usr/lib/libz.so" \
-DCLANG_FORMAT_ENABLED=ON \
-DUSE_NATIVE_FILE_BUFFER=ON \
-DUSE_STANDARD_FILESYSTEM=OFF \
-DNO_DOXYGEN=ON \
-DWIDGETS_GUI=OFF \
-DQUICK_GUI=ON \
-DBUILTIN_ICON_THEMES=breeze \
-DBUILTIN_TRANSLATIONS=ON \
-DANDROID_APK_TOOLCHAIN_VERSION=4.9 \
-DANDROID_APK_CXX_STANDARD_LIBRARY="$android_ndk_root/platforms/android-$_android_api_level/arch-$_android_arch2/usr/lib/libstdc++.so" \
-DANDROID_APK_FORCE_DEBUG=ON \
-DANDROID_APK_KEYSTORE_URL="$keystore_url" \
-DANDROID_APK_KEYSTORE_ALIAS="$keystore_alias" \
-DANDROID_APK_KEYSTORE_PASSWORD="$keystore_password" \
-DANDROID_APK_APPLICATION_ID_SUFFIX=".unstable" \
-DANDROID_APK_APPLICATION_LABEL="Password Manager (unstable)" \
$SOURCES/subdirs/$_reponame
# configure with CMake's internal Android support
# note: Requires workaround with Android NDK r19: https://gitlab.kitware.com/cmake/cmake/issues/18739#note_498676
cmake \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_SYSTEM_NAME=Android \
-DCMAKE_SYSTEM_VERSION=$_android_api_level \
-DCMAKE_ANDROID_ARCH_ABI=$_android_arch \
-DCMAKE_ANDROID_NDK="$android_ndk_root" \
-DCMAKE_ANDROID_SDK="$android_sdk_root" \
-DCMAKE_ANDROID_STL_TYPE=c++_shared \
-DCMAKE_INSTALL_PREFIX=$other_libs_root \
-DCMAKE_PREFIX_PATH="$root" \
-DCMAKE_FIND_ROOT_PATH="$root;$root/libs" \
-DCMAKE_CXX_FLAGS="-D__ANDROID_API__=$_android_api_level" \
-DCLANG_FORMAT_ENABLED=ON \
-DBUILD_SHARED_LIBS=ON \
-DUSE_NATIVE_FILE_BUFFER=ON \
-DUSE_STANDARD_FILESYSTEM=OFF \
-DNO_DOXYGEN=ON \
-DWIDGETS_GUI=OFF \
-DQUICK_GUI=ON \
-DBUILTIN_ICON_THEMES=breeze \
-DBUILTIN_TRANSLATIONS=ON \
-DANDROID_APK_FORCE_DEBUG=ON \
-DANDROID_APK_KEYSTORE_URL="$keystore_url" \
-DANDROID_APK_KEYSTORE_ALIAS="$keystore_alias" \
-DANDROID_APK_KEYSTORE_PASSWORD="$keystore_password" \
-DANDROID_APK_APPLICATION_ID_SUFFIX=".unstable" \
-DANDROID_APK_APPLICATION_LABEL="Password Manager (unstable)" \
$SOURCES/subdirs/$_reponame
# build all binaries and make APK file using all CPU cores
make passwordmanager_apk -j$(nproc)
# install app on USB-connected phone
make passwordmanager_deploy_apk
# install the app
adb install "$build_dir/passwordmanager/android-build//build/outputs/apk/release/android-build-release-signed.apk"
```
##### Notes
* The Android packages for the dependencies Qt, iconv, OpenSSL and Kirigami 2 are provided in
* The Android packages for the dependencies Boost, Qt, iconv, OpenSSL and Kirigami are provided in
my [PKGBUILDs](http://github.com/Martchus/PKGBUILDs) repo.
* The lastest Java I was able to use was version 8 (`jdk8-openjdk` package).
### Manual deployment of Android APK file
1. Find device ID: `adb devices`
2. Install App on phone: `adb -s <DEVICE_ID> install -r $BUILD_DIR/passwordmanager_build_apk/build/outputs/apk/passwordmanager_build_apk-debug.apk`
3. View log: `adb -s <DEVICE_ID> logcat`
* The latest Java I was able to use was version 17.
* Use `QT_QUICK_CONTROLS_STYLE=Material` and `QT_QUICK_CONTROLS_MOBILE=1` to test the Qt Quick GUI like it would be shown under
Android via a normal desktop build.
### Building without Qt GUI
It is possible to build without the GUI if only the CLI is needed. In this case no Qt dependencies (including qtutilities) are required.
@ -225,3 +172,8 @@ To build without GUI, add the following parameters to the CMake call:
```
-DWIDGETS_GUI=OFF -DQUICK_GUI=OFF
```
## Copyright notice and license
Copyright © 2015-2024 Marius Kittler
All code is licensed under [GPL-2-or-later](LICENSE).

View File

@ -1,54 +0,0 @@
<?xml version="1.0"?>
<manifest package="@META_ANDROID_PACKAGE_NAME@" xmlns:android="http://schemas.android.com/apk/res/android" android:versionName="@META_APP_VERSION@" android:versionCode="@META_VERSION_MAJOR@" android:installLocation="auto">
<application
android:icon="@mipmap/ic_launcher"
android:name="org.qtproject.qt5.android.bindings.QtApplication"
android:label="@string/app_name"
android:resizeableActivity="true">
<activity
android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation"
android:name="@META_ANDROID_PACKAGE_NAME@.Activity"
android:label="@string/app_name"
android:screenOrientation="unspecified"
android:theme="@style/AppTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<meta-data android:name="android.app.lib_name" android:value="-- %%INSERT_APP_LIB_NAME%% --"/>
<meta-data android:name="android.app.qt_sources_resource_id" android:resource="@array/qt_sources"/>
<meta-data android:name="android.app.repository" android:value="default"/>
<meta-data android:name="android.app.qt_libs_resource_id" android:resource="@array/qt_libs"/>
<meta-data android:name="android.app.bundled_libs_resource_id" android:resource="@array/bundled_libs"/>
<!-- Deploy Qt libs as part of package -->
<meta-data android:name="android.app.bundle_local_qt_libs" android:value="-- %%BUNDLE_LOCAL_QT_LIBS%% --"/>
<meta-data android:name="android.app.bundled_in_lib_resource_id" android:resource="@array/bundled_in_lib"/>
<meta-data android:name="android.app.bundled_in_assets_resource_id" android:resource="@array/bundled_in_assets"/>
<!-- Run with local libs -->
<meta-data android:name="android.app.use_local_qt_libs" android:value="-- %%USE_LOCAL_QT_LIBS%% --"/>
<meta-data android:name="android.app.libs_prefix" android:value="/data/local/tmp/qt/"/>
<!--<meta-data android:name="android.app.load_local_libs" android:value="-- %%INSERT_LOCAL_LIBS%% --"/>-->
<meta-data android:name="android.app.load_local_libs" android:value="plugins/platforms/android/libqtforandroid.so:plugins/bearer/libqandroidbearer.so:lib/libQt5QuickParticles.so"/>
<meta-data android:name="android.app.load_local_jars" android:value="-- %%INSERT_LOCAL_JARS%% --"/>
<meta-data android:name="android.app.static_init_classes" android:value="-- %%INSERT_INIT_CLASSES%% --"/>
<!-- Messages maps -->
<meta-data android:value="@string/ministro_not_found_msg" android:name="android.app.ministro_not_found_msg"/>
<meta-data android:value="@string/ministro_needed_msg" android:name="android.app.ministro_needed_msg"/>
<meta-data android:value="@string/fatal_error_msg" android:name="android.app.fatal_error_msg"/>
<!-- Splash screen -->
<meta-data android:name="android.app.splash_screen_drawable" android:resource="@drawable/splash"/>
<!-- Splash screen -->
</activity>
</application>
<uses-sdk android:minSdkVersion="23" android:targetSdkVersion="23"/>
<supports-screens android:largeScreens="true" android:normalScreens="true" android:anyDensity="true" android:smallScreens="true"/>
<!-- The following comment will be replaced upon deployment with default permissions based on the dependencies of the application.
Remove the comment if you do not require these default permissions. -->
<!-- %%INSERT_PERMISSIONS -->
<!-- The following comment will be replaced upon deployment with default features based on the dependencies of the application.
Remove the comment if you do not require these default features. -->
<!-- %%INSERT_FEATURES -->
</manifest>

83
android/build.gradle Normal file
View File

@ -0,0 +1,83 @@
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.4.1'
}
}
repositories {
google()
mavenCentral()
}
apply plugin: 'com.android.application'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
implementation "androidx.documentfile:documentfile:1.0.1"
implementation 'androidx.core:core:1.10.1'
}
android {
/*******************************************************
* The following variables:
* - androidBuildToolsVersion,
* - androidCompileSdkVersion
* - qtAndroidDir - holds the path to qt android files
* needed to build any Qt application
* on Android.
*
* are defined in gradle.properties file. This file is
* updated by QtCreator and androiddeployqt tools.
* Changing them manually might break the compilation!
*******************************************************/
compileSdkVersion androidCompileSdkVersion
buildToolsVersion androidBuildToolsVersion
ndkVersion androidNdkVersion
// Extract native libraries from the APK
packagingOptions.jniLibs.useLegacyPackaging true
sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = [qtAndroidDir + '/src', 'src', 'java']
aidl.srcDirs = [qtAndroidDir + '/src', 'src', 'aidl']
res.srcDirs = [qtAndroidDir + '/res', 'res']
resources.srcDirs = ['resources']
renderscript.srcDirs = ['src']
assets.srcDirs = ['assets']
jniLibs.srcDirs = ['libs']
}
}
tasks.withType(JavaCompile) {
options.incremental = true
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
lintOptions {
abortOnError false
}
// Do not compress Qt binary resources file
aaptOptions {
noCompress 'rcc'
}
defaultConfig {
resConfig "en"
minSdkVersion qtMinSdkVersion
targetSdkVersion qtTargetSdkVersion
ndk.abiFilters = qtTargetAbiList.split(",")
}
}

View File

@ -1,63 +0,0 @@
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.2.0'
}
}
repositories {
google()
jcenter()
}
apply plugin: 'com.android.application'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:support-v4:27.1.0'
}
android {
/*******************************************************
* The following variables:
* - androidBuildToolsVersion,
* - androidCompileSdkVersion
* - qt5AndroidDir - holds the path to qt android files
* needed to build any Qt application
* on Android.
*
* are defined in gradle.properties file. This file is
* updated by QtCreator and androiddeployqt tools.
* Changing them manually might break the compilation!
*******************************************************/
compileSdkVersion androidCompileSdkVersion.toInteger()
buildToolsVersion androidBuildToolsVersion
defaultConfig {
applicationId "@META_ANDROID_PACKAGE_NAME@"
applicationIdSuffix "@ANDROID_APK_APPLICATION_ID_SUFFIX@"
}
sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = [qt5AndroidDir + '/src', 'src', 'java']
aidl.srcDirs = [qt5AndroidDir + '/src', 'src', 'aidl']
res.srcDirs = [qt5AndroidDir + '/res', 'res']
resources.srcDirs = ['src']
renderscript.srcDirs = ['src']
assets.srcDirs = ['assets']
jniLibs.srcDirs = ['libs']
}
}
lintOptions {
abortOnError false
}
}

View File

@ -0,0 +1 @@
android.useAndroidX=true

View File

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">@ANDROID_APK_APPLICATION_LABEL@</string>
</resources>

View File

@ -7,9 +7,9 @@ import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.view.Window;
import android.view.WindowManager.LayoutParams;
import android.support.v4.provider.DocumentFile;
import androidx.documentfile.provider.DocumentFile;
import java.io.FileNotFoundException;
import org.qtproject.qt5.android.bindings.QtActivity;
import org.qtproject.qt.android.bindings.QtActivity;
public class Activity extends QtActivity {
private final int REQUEST_CODE_OPEN_EXISTING_FILE = 1;

View File

@ -5,6 +5,7 @@
#include <passwordfile/io/field.h>
#include <passwordfile/io/parsingexception.h>
#include <passwordfile/io/passwordfile.h>
#include <passwordfile/util/openssl.h>
#include <c++utilities/application/commandlineutils.h>
#include <c++utilities/conversion/stringconversion.h>
@ -15,7 +16,9 @@
#endif
#include <algorithm>
#include <cstdlib>
#include <functional>
#include <stdexcept>
using namespace std;
using namespace std::placeholders;
@ -89,32 +92,39 @@ InteractiveCli::InteractiveCli()
, m_modified(false)
, m_quit(false)
{
Util::OpenSsl::init();
CMD_UTILS_START_CONSOLE;
}
void InteractiveCli::run(const string &file)
InteractiveCli::~InteractiveCli()
{
Util::OpenSsl::clean();
}
int InteractiveCli::run(string_view file)
{
if (!file.empty()) {
openFile(file, PasswordFileOpenFlags::Default);
}
string input;
auto input = std::string();
while (!m_quit) {
getline(m_i, input);
std::getline(m_i, input);
if (!input.empty()) {
processCommand(input);
}
}
return EXIT_SUCCESS;
}
void InteractiveCli::processCommand(const string &cmd)
void InteractiveCli::processCommand(const std::string &cmd)
{
#define CMD(value) !paramMissing && cmd == value
#define CMD(value) !paramMissing &&cmd == value
#define CMD2(value1, value2) !paramMissing && (cmd == value1 || cmd == value2)
#define CMD_P(value) !paramMissing && checkCommand(cmd, value, param, paramMissing)
#define CMD_P(value) !paramMissing &&checkCommand(cmd, value, param, paramMissing)
#define CMD2_P(value1, value2) !paramMissing && (checkCommand(cmd, value1, param, paramMissing) || checkCommand(cmd, value2, param, paramMissing))
string param;
bool paramMissing = false;
auto param = std::string();
auto paramMissing = false;
if (CMD2("quit", "q")) {
quit();
} else if (CMD("q!")) {
@ -175,9 +185,9 @@ void InteractiveCli::processCommand(const string &cmd)
}
}
Entry *InteractiveCli::resolvePath(const string &path)
Entry *InteractiveCli::resolvePath(const std::string &path)
{
auto parts = splitString<vector<string>>(path, "/", EmptyPartsTreat::Merge);
auto parts = splitString<std::vector<std::string>>(path, "/", EmptyPartsTreat::Merge);
bool fromRoot = path.at(0) == '/';
if (fromRoot && parts.empty()) {
return m_file.rootEntry();
@ -213,7 +223,7 @@ Entry *InteractiveCli::resolvePath(const string &path)
}
}
bool InteractiveCli::checkCommand(const string &str, const char *phrase, std::string &param, bool &paramMissing)
bool InteractiveCli::checkCommand(const std::string &str, const char *phrase, std::string &param, bool &paramMissing)
{
for (auto i = str.cbegin(), end = str.cend(); i != end; ++i, ++phrase) {
if (*phrase == 0) {
@ -234,13 +244,13 @@ bool InteractiveCli::checkCommand(const string &str, const char *phrase, std::st
return false;
}
void InteractiveCli::openFile(const string &file, PasswordFileOpenFlags openFlags)
void InteractiveCli::openFile(std::string_view file, PasswordFileOpenFlags openFlags)
{
if (m_file.isOpen()) {
m_o << "file \"" << m_file.path() << "\" currently open; close first" << endl;
return;
}
m_file.setPath(file);
m_file.setPath(std::string(file));
for (;;) {
try {
try {
@ -252,13 +262,13 @@ void InteractiveCli::openFile(const string &file, PasswordFileOpenFlags openFlag
m_currentEntry = m_file.rootEntry();
m_o << "file \"" << file << "\" opened" << endl;
} catch (const ParsingException &) {
m_o << "error occured when parsing file \"" << file << "\"" << endl;
m_o << "error occurred when parsing file \"" << file << "\"" << endl;
throw;
} catch (const CryptoException &) {
m_o << "error occured when decrypting file \"" << file << "\"" << endl;
m_o << "error occurred when decrypting file \"" << file << "\"" << endl;
throw;
} catch (const std::ios_base::failure &) {
m_o << "IO error occured when opening file \"" << file << "\"" << endl;
m_o << "IO error occurred when opening file \"" << file << "\"" << endl;
throw;
}
} catch (const std::exception &e) {
@ -303,13 +313,13 @@ void InteractiveCli::saveFile()
m_file.save(flags);
m_o << "file \"" << m_file.path() << "\" saved" << endl;
} catch (const ParsingException &) {
m_o << "error occured when parsing file \"" << m_file.path() << "\"" << endl;
m_o << "error occurred when parsing file \"" << m_file.path() << "\"" << endl;
throw;
} catch (const CryptoException &) {
m_o << "error occured when encrypting file \"" << m_file.path() << "\"" << endl;
m_o << "error occurred when encrypting file \"" << m_file.path() << "\"" << endl;
throw;
} catch (const std::ios_base::failure &) {
m_o << "IO error occured when saving file \"" << m_file.path() << "\"" << endl;
m_o << "IO error occurred when saving file \"" << m_file.path() << "\"" << endl;
throw;
}
} catch (const exception &e) {
@ -322,7 +332,7 @@ void InteractiveCli::saveFile()
m_modified = false;
}
void InteractiveCli::createFile(const string &file)
void InteractiveCli::createFile(const std::string &file)
{
if (m_file.isOpen()) {
m_o << "file \"" << m_file.path() << "\" currently open; close first" << endl;
@ -337,7 +347,7 @@ void InteractiveCli::createFile(const string &file)
m_currentEntry = m_file.rootEntry();
m_o << "file \"" << file << "\" created and opened" << endl;
} catch (const std::ios_base::failure &) {
m_o << "IO error occured when creating file \"" << file << "\"" << endl;
m_o << "IO error occurred when creating file \"" << file << "\"" << endl;
throw;
}
} catch (const exception &e) {
@ -392,7 +402,7 @@ void InteractiveCli::pwd()
m_o << joinStrings(path, "/") << endl;
}
void InteractiveCli::cd(const string &path)
void InteractiveCli::cd(const std::string &path)
{
if (!m_file.isOpen()) {
m_o << "can not change directory; no file open" << endl;
@ -407,7 +417,7 @@ void InteractiveCli::cd(const string &path)
void InteractiveCli::ls()
{
if (!m_file.isOpen()) {
m_o << "can not list any entires; no file open" << endl;
m_o << "can not list any entries; no file open" << endl;
return;
}
switch (m_currentEntry->type()) {
@ -450,7 +460,7 @@ void InteractiveCli::tree()
printEntries(m_currentEntry, 0);
}
void InteractiveCli::makeEntry(EntryType entryType, const string &label)
void InteractiveCli::makeEntry(EntryType entryType, const std::string &label)
{
if (!m_file.isOpen()) {
m_o << "can not make entry; no file open" << endl;
@ -473,7 +483,7 @@ void InteractiveCli::makeEntry(EntryType entryType, const string &label)
}
}
void InteractiveCli::removeEntry(const string &path)
void InteractiveCli::removeEntry(const std::string &path)
{
if (!m_file.isOpen()) {
m_o << "can not remove entry; no file open" << endl;
@ -493,16 +503,16 @@ void InteractiveCli::removeEntry(const string &path)
}
}
void InteractiveCli::renameEntry(const string &path)
void InteractiveCli::renameEntry(const std::string &path)
{
if (!m_file.isOpen()) {
m_o << "can not rename entry; no file open" << endl;
return;
}
if (Entry *entry = resolvePath(path)) {
string label;
auto label = std::string();
m_o << "enter new name: " << endl;
getline(m_i, label);
std::getline(m_i, label);
if (label.empty()) {
m_o << "can not rename; new name is empty" << endl;
} else {
@ -513,16 +523,16 @@ void InteractiveCli::renameEntry(const string &path)
}
}
void InteractiveCli::moveEntry(const string &path)
void InteractiveCli::moveEntry(const std::string &path)
{
if (!m_file.isOpen()) {
m_o << "can not rename entry; no file open" << endl;
return;
}
if (Entry *entry = resolvePath(path)) {
string newParentPath;
auto newParentPath = std::string();
m_o << "enter path of new parent: " << endl;
getline(m_i, newParentPath);
std::getline(m_i, newParentPath);
if (newParentPath.empty()) {
m_o << "can not move; path of new parent is empty" << endl;
} else {
@ -547,7 +557,7 @@ void InteractiveCli::moveEntry(const string &path)
}
}
void InteractiveCli::readField(const string &fieldName)
void InteractiveCli::readField(const std::string &fieldName)
{
if (!m_file.isOpen()) {
m_o << "can not read field; no file open" << endl;
@ -571,7 +581,7 @@ void InteractiveCli::readField(const string &fieldName)
}
}
void InteractiveCli::setField(bool useMuter, const string &fieldName)
void InteractiveCli::setField(bool useMuter, const std::string &fieldName)
{
if (!m_file.isOpen()) {
m_o << "can not set field; no file open" << endl;
@ -581,22 +591,22 @@ void InteractiveCli::setField(bool useMuter, const string &fieldName)
m_o << "can not set field; current entry is no account entry" << endl;
return;
}
vector<Field> &fields = static_cast<AccountEntry *>(m_currentEntry)->fields();
unsigned int valuesFound = 0;
string value;
auto &fields = static_cast<AccountEntry *>(m_currentEntry)->fields();
auto valuesFound = unsigned();
auto value = std::string();
m_o << "enter new value: ";
if (useMuter) {
InputMuter m;
getline(m_i, value);
std::getline(m_i, value);
m_o << endl << "repeat: ";
string repeat;
getline(m_i, repeat);
auto repeat = std::string();
std::getline(m_i, repeat);
if (value != repeat) {
m_o << "values do not match; field has not been altered" << endl;
return;
}
} else {
getline(m_i, value);
std::getline(m_i, value);
}
for (Field &field : fields) {
if (field.name() == fieldName) {
@ -643,7 +653,7 @@ void InteractiveCli::setField(bool useMuter, const string &fieldName)
}
}
void InteractiveCli::removeField(const string &fieldName)
void InteractiveCli::removeField(const std::string &fieldName)
{
if (!m_file.isOpen()) {
m_o << "can not remove field; no file open" << endl;
@ -653,8 +663,8 @@ void InteractiveCli::removeField(const string &fieldName)
m_o << "can not remove field; current entry is no account entry" << endl;
return;
}
vector<Field> &fields = static_cast<AccountEntry *>(m_currentEntry)->fields();
unsigned int valuesFound = 0;
auto &fields = static_cast<AccountEntry *>(m_currentEntry)->fields();
auto valuesFound = unsigned();
for (const Field &field : fields) {
if (field.name() == fieldName) {
++valuesFound;
@ -730,7 +740,7 @@ void InteractiveCli::quit()
}
}
string InteractiveCli::askForPassphrase(bool confirm)
std::string InteractiveCli::askForPassphrase(bool confirm)
{
if (confirm) {
m_o << "enter new passphrase: ";
@ -738,10 +748,10 @@ string InteractiveCli::askForPassphrase(bool confirm)
m_o << "enter passphrase: ";
}
m_o.flush();
string input1;
auto input1 = std::string();
{
InputMuter m;
getline(m_i, input1);
auto m = InputMuter();
std::getline(m_i, input1);
}
m_o << endl;
if (input1.empty()) {
@ -751,15 +761,15 @@ string InteractiveCli::askForPassphrase(bool confirm)
if (confirm) {
m_o << "confirm new passphrase: ";
m_o.flush();
string input2;
auto input2 = std::string();
{
InputMuter m;
getline(m_i, input2);
auto m = InputMuter();
std::getline(m_i, input2);
}
m_o << endl;
if (input1 != input2) {
m_o << "phrases do not match" << endl;
throw runtime_error("confirmation failed");
throw std::runtime_error("confirmation failed");
}
}
return input1;

View File

@ -12,7 +12,7 @@
#include <istream>
#include <ostream>
#include <string>
#include <vector>
#include <string_view>
namespace Io {
class Entry;
@ -23,7 +23,7 @@ namespace Cli {
class InputMuter {
public:
InputMuter();
explicit InputMuter();
~InputMuter();
private:
@ -39,9 +39,10 @@ void clearConsole();
class InteractiveCli {
public:
InteractiveCli();
void run(const std::string &file = std::string());
void openFile(const std::string &file, Io::PasswordFileOpenFlags openFlags);
explicit InteractiveCli();
~InteractiveCli();
int run(std::string_view file);
void openFile(std::string_view file, Io::PasswordFileOpenFlags openFlags);
void closeFile();
void saveFile();
void createFile(const std::string &file);

View File

@ -7,7 +7,7 @@ namespace QtGui {
class FieldDelegate : public QStyledItemDelegate {
public:
FieldDelegate(QObject *parent = nullptr);
explicit FieldDelegate(QObject *parent = nullptr);
void setEditorData(QWidget *editor, const QModelIndex &index) const override;
};

View File

@ -5,6 +5,8 @@
#include "resources/config.h"
#include "resources/qtconfig.h"
#include <QApplication> // ensure QGuiApplication is defined before resources.h for desktop file name
#include <qtutilities/resources/importplugin.h>
#include <qtutilities/resources/qtconfigarguments.h>
#include <qtutilities/resources/resources.h>
@ -12,8 +14,8 @@
#include <passwordfile/util/openssl.h>
#include <QApplication>
#include <QFile>
#include <QMessageBox>
#include <QSettings>
using namespace CppUtilities;
@ -30,18 +32,15 @@ int runWidgetsGui(int argc, char *argv[], const QtConfigArguments &qtConfigArgs,
OpenSsl::init();
// init application
QApplication application(argc, argv);
auto application = QApplication(argc, argv);
QObject::connect(&application, &QCoreApplication::aboutToQuit, &OpenSsl::clean);
// restore Qt settings
QtSettings qtSettings;
QSettings settings(QSettings::IniFormat, QSettings::UserScope, QStringLiteral(PROJECT_NAME));
// move old config to new location
const QString oldConfig
= QSettings(QSettings::IniFormat, QSettings::UserScope, QApplication::organizationName(), QApplication::applicationName()).fileName();
QFile::rename(oldConfig, settings.fileName()) || QFile::remove(oldConfig);
settings.sync();
qtSettings.restore(settings);
auto qtSettings = QtSettings();
auto settings = QtUtilities::getSettings(QStringLiteral(PROJECT_NAME));
auto settingsError = QtUtilities::errorMessageForSettings(*settings);
qtSettings.disableNotices();
qtSettings.restore(*settings);
qtSettings.apply();
// apply settings specified via command line args
@ -49,14 +48,27 @@ int runWidgetsGui(int argc, char *argv[], const QtConfigArguments &qtConfigArgs,
LOAD_QT_TRANSLATIONS;
// init widgets GUI
MainWindow w(settings, &qtSettings);
if (!settingsError.isEmpty()) {
QMessageBox::critical(nullptr, QCoreApplication::applicationName(), settingsError);
}
auto w = MainWindow(*settings, &qtSettings);
w.show();
if (!file.isEmpty()) {
w.openFile(file);
}
// start event loop
QObject::connect(&application, &QCoreApplication::aboutToQuit, &OpenSsl::clean);
return application.exec();
auto res = application.exec();
// save settings to disk
settings->sync();
if (settingsError.isEmpty()) {
settingsError = QtUtilities::errorMessageForSettings(*settings);
if (!settingsError.isEmpty()) {
QMessageBox::critical(nullptr, QCoreApplication::applicationName(), settingsError);
}
}
return res;
}
} // namespace QtGui

View File

@ -106,6 +106,19 @@ void MainWindow::setSomethingChanged(bool somethingChanged)
}
}
/*!
* \brief Updates the style sheet.
*/
void MainWindow::updateStyleSheet()
{
#ifdef Q_OS_WINDOWS
const auto p = palette();
setStyleSheet(QStringLiteral("%1 #splitter QWidget { background-color: palette(base); color: palette(text); } #splitter QWidget *, #splitter "
"QWidget * { background-color: none; } #leftWidget { border-right: 1px solid %2; }")
.arg(dialogStyleForPalette(p), windowFrameColorForPalette(p).name()));
#endif
}
/*!
* \brief Constructs a new main window.
*/
@ -120,12 +133,9 @@ MainWindow::MainWindow(QSettings &settings, QtUtilities::QtSettings *qtSettings,
, m_settingsDlg(nullptr)
{
// setup ui
updateStyleSheet();
m_ui->setupUi(this);
#ifdef Q_OS_WIN32
setStyleSheet(QStringLiteral("%1 #splitter QWidget { background-color: palette(base); color: palette(text); } #splitter QWidget *, #splitter "
"QWidget * { background-color: none; } #leftWidget { border-right: 1px solid %2; }")
.arg(dialogStyle(), windowFrameColor().name()));
#endif
// set default values
setSomethingChanged(false);
m_dontUpdateSelection = false;
@ -227,7 +237,7 @@ MainWindow::MainWindow(QSettings &settings, QtUtilities::QtSettings *qtSettings,
connect(m_ui->accountFilterLineEdit, &QLineEdit::textChanged, this, &MainWindow::applyFilter);
// setup other controls
m_ui->actionAlwaysCreateBackup->setChecked(settings.value(QStringLiteral("alwayscreatebackup"), false).toBool());
m_ui->actionAlwaysCreateBackup->setChecked(settings.value(QStringLiteral("alwayscreatebackup"), true).toBool());
m_ui->accountFilterLineEdit->setText(settings.value(QStringLiteral("accountfilter"), QString()).toString());
m_ui->centralWidget->installEventFilter(this);
settings.endGroup();
@ -240,6 +250,17 @@ MainWindow::~MainWindow()
{
}
bool MainWindow::event(QEvent *event)
{
switch (event->type()) {
case QEvent::PaletteChange:
updateStyleSheet();
break;
default:;
}
return QMainWindow::event(event);
}
bool MainWindow::eventFilter(QObject *obj, QEvent *event)
{
if (obj == m_undoView) {
@ -332,6 +353,7 @@ void MainWindow::showSettingsDialog()
if (m_qtSettings) {
m_settingsDlg->setWindowTitle(tr("Qt settings"));
m_settingsDlg->setSingleCategory(m_qtSettings->category());
connect(m_settingsDlg, &SettingsDialog::applied, this, [this] { m_qtSettings->apply(); });
}
}
if (m_settingsDlg->isHidden()) {
@ -432,7 +454,8 @@ bool MainWindow::openFile(const QString &path, PasswordFileOpenFlags openFlags)
}
// show error message
const QString errmsg = tr("An IO error occured when opening the specified file \"%1\": %2").arg(path, QString::fromLocal8Bit(failure.what()));
const QString errmsg
= tr("An IO error occurred when opening the specified file \"%1\": %2").arg(path, QString::fromLocal8Bit(failure.what()));
m_ui->statusBar->showMessage(errmsg, 5000);
QMessageBox::critical(this, QApplication::applicationName(), errmsg);
return false;
@ -704,7 +727,7 @@ void MainWindow::insertFields(const QString &fieldsString)
}();
auto row = selectedIndexes.front().row();
m_fieldModel->insertRows(row, rowValues.size(), QModelIndex());
m_fieldModel->insertRows(row, static_cast<int>(rowValues.size()), QModelIndex());
for (const auto &rowValue : rowValues) {
int col = initCol;
@ -827,7 +850,7 @@ bool MainWindow::saveFile()
try {
m_file.doBackup();
} catch (const std::ios_base::failure &failure) {
const QString message(tr("An IO error occured when making the backup file: %1").arg(QString::fromLocal8Bit(failure.what())));
const QString message(tr("An IO error occurred when making the backup file: %1").arg(QString::fromLocal8Bit(failure.what())));
QMessageBox::critical(this, QApplication::applicationName(), message);
m_ui->statusBar->showMessage(message, 7000);
return false;

View File

@ -50,6 +50,8 @@ public:
explicit MainWindow(QSettings &settings, QtUtilities::QtSettings *qtSettings = nullptr, QWidget *parent = nullptr);
~MainWindow() override;
QString selectedFieldsString() const;
public Q_SLOTS:
// file management
bool openFile(const QString &path);
@ -72,6 +74,7 @@ public:
bool openFile(const QString &path, Io::PasswordFileOpenFlags openFlags); // can not be a slot in Qt 6 without further effort, see QTBUG-86424
protected:
bool event(QEvent *event) override;
bool eventFilter(QObject *obj, QEvent *event) override;
void closeEvent(QCloseEvent *event) override;
void timerEvent(QTimerEvent *event) override;
@ -93,7 +96,6 @@ private Q_SLOTS:
void markAsNormalField();
void setFieldType(Io::FieldType fieldType);
void setPasswordVisibility(QAction *selectedAction);
QString selectedFieldsString() const;
void insertFields(const QString &fieldsString);
void copyFieldsForXMilliSeconds(int x = 5000);
void copyFields();
@ -107,6 +109,7 @@ private Q_SLOTS:
void clearClipboard();
void setSomethingChanged();
void setSomethingChanged(bool somethingChanged);
void updateStyleSheet();
private:
// showing conditional messages/prompts

View File

@ -43,11 +43,8 @@ PasswordGeneratorDialog::PasswordGeneratorDialog(QWidget *parent)
: QDialog(parent)
, m_ui(new Ui::PasswordGeneratorDialog)
{
updateStyleSheet();
m_ui->setupUi(this);
#ifdef Q_OS_WIN32
setStyleSheet(QStringLiteral("%1 QCommandLinkButton { font-size: 12pt; color: %2; font-weight: normal; }")
.arg(dialogStyle(), instructionTextColor().name()));
#endif
setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::WindowCloseButtonHint);
connect(m_ui->generatePassowordCommandLinkButton, &QCommandLinkButton::clicked, this, &PasswordGeneratorDialog::generateNewPassword);
@ -75,6 +72,17 @@ PasswordGeneratorDialog::~PasswordGeneratorDialog()
{
}
bool PasswordGeneratorDialog::event(QEvent *event)
{
switch (event->type()) {
case QEvent::PaletteChange:
updateStyleSheet();
break;
default:;
}
return QDialog::event(event);
}
/*!
* \brief Generates and shows a new password.
*/
@ -159,7 +167,18 @@ void PasswordGeneratorDialog::handleCheckedCategoriesChanged()
*/
void PasswordGeneratorDialog::handlePasswordChanged()
{
m_ui->copyPasswordCommandLinkButton->setEnabled(m_ui->passwordLineEdit->text().count() > 0);
m_ui->copyPasswordCommandLinkButton->setEnabled(m_ui->passwordLineEdit->text().size() > 0);
}
/*!
* \brief Updates the style sheet.
*/
void PasswordGeneratorDialog::updateStyleSheet()
{
#ifdef Q_OS_WINDOWS
setStyleSheet(QStringLiteral("%1 QCommandLinkButton { font-size: 12pt; color: %2; font-weight: normal; }")
.arg(dialogStyleForPalette(palette()), instructionTextColor().name()));
#endif
}
#ifndef QT_NO_CLIPBOARD

View File

@ -21,10 +21,14 @@ public:
explicit PasswordGeneratorDialog(QWidget *parent = nullptr);
~PasswordGeneratorDialog() override;
protected:
bool event(QEvent *event) override;
private Q_SLOTS:
void generateNewPassword();
void handleCheckedCategoriesChanged();
void handlePasswordChanged();
void updateStyleSheet();
#ifndef QT_NO_CLIPBOARD
void copyPassword();
#endif

View File

@ -281,22 +281,12 @@
</item>
<item>
<widget class="QPushButton" name="closePushButton">
<property name="font">
<font>
<family>Segoe UI,Sans</family>
<pointsize>9</pointsize>
<weight>50</weight>
<italic>false</italic>
<bold>false</bold>
</font>
</property>
<property name="text">
<string>Close</string>
</property>
<property name="icon">
<iconset theme="window-close">
<normaloff/>
</iconset>
<normaloff>.</normaloff>.</iconset>
</property>
<property name="checkable">
<bool>false</bool>
@ -308,6 +298,17 @@
</item>
</layout>
</widget>
<tabstops>
<tabstop>useSmallLettersCheckBox</tabstop>
<tabstop>useCapitalLettersCheckBox</tabstop>
<tabstop>useDigitsCheckBox</tabstop>
<tabstop>otherCharsLineEdit</tabstop>
<tabstop>LengthSpinBox</tabstop>
<tabstop>passwordLineEdit</tabstop>
<tabstop>generatePassowordCommandLinkButton</tabstop>
<tabstop>copyPasswordCommandLinkButton</tabstop>
<tabstop>closePushButton</tabstop>
</tabstops>
<resources>
<include location="../resources/icons.qrc"/>
</resources>

View File

@ -17,7 +17,7 @@ class StackSupport {
friend class StackAbsorper;
public:
StackSupport(QUndoStack *undoStack = nullptr);
explicit StackSupport(QUndoStack *undoStack = nullptr);
protected:
QUndoStack *undoStack();
@ -68,7 +68,7 @@ inline void StackSupport::clearUndoStack()
*/
class StackAbsorper {
public:
StackAbsorper(StackSupport *supported);
explicit StackAbsorper(StackSupport *supported);
~StackAbsorper();
QUndoStack *stack();

View File

@ -62,6 +62,15 @@ void CustomUndoCommand::undo()
* \brief Sets the value for the specified index and role in the specified field model.
*/
/// \cond
static QString getFieldName(FieldModel *model, int row, const QModelIndex &index)
{
return model->index(row, 0, index.parent()).data().toString();
}
/// \endcond
/*!
* \brief Constructs a new command.
*/
@ -75,7 +84,6 @@ FieldModelSetValueCommand::FieldModelSetValueCommand(FieldModel *model, const QM
, m_oldValue(model->data(index, role))
, m_role(role)
{
QString fieldName = model->index(m_row, 0, index.parent()).data().toString();
switch (role) {
case Qt::DisplayRole:
case Qt::EditRole:
@ -88,16 +96,16 @@ FieldModelSetValueCommand::FieldModelSetValueCommand(FieldModel *model, const QM
}
break;
case 1:
if (fieldName.isEmpty()) {
setText(QApplication::translate("undocommands", "setting value of empty field"));
} else {
if (const auto fieldName = getFieldName(model, m_row, index); !fieldName.isEmpty()) {
setText(QApplication::translate("undocommands", "setting value of »%1« field").arg(fieldName));
} else {
setText(QApplication::translate("undocommands", "setting value of empty field"));
}
break;
}
break;
case FieldTypeRole:
setText(QApplication::translate("undocommands", "setting type of »%1« field").arg(fieldName));
setText(QApplication::translate("undocommands", "setting type of »%1« field").arg(getFieldName(model, m_row, index)));
break;
default:
setText(QApplication::translate("undocommands", "setting field property in row »%1«").arg(m_row + 1));
@ -174,7 +182,7 @@ bool FieldModelRemoveRowsCommand::internalRedo()
m_model->setAccountEntry(m_account);
if (m_values.isEmpty()) {
for (int row = m_row, end = m_row + m_count; row < end; ++row) {
if (const Field *field = m_model->field(row)) {
if (const Field *const field = m_model->field(static_cast<std::size_t>(row))) {
m_values << Field(*field);
}
}
@ -186,7 +194,7 @@ bool FieldModelRemoveRowsCommand::internalUndo()
{
m_model->setAccountEntry(m_account);
bool res = m_model->insertRows(m_row, m_count, QModelIndex());
for (int row = m_row, end = m_row + m_count, value = 0, values = m_values.size(); row < end && value < values; ++row, ++value) {
for (int row = m_row, end = m_row + m_count, value = 0, values = static_cast<int>(m_values.size()); row < end && value < values; ++row, ++value) {
m_model->setData(m_model->index(row, 0), QString::fromStdString(m_values.at(value).name()), Qt::EditRole);
m_model->setData(m_model->index(row, 1), QString::fromStdString(m_values.at(value).value()), Qt::EditRole);
m_model->setData(m_model->index(row, 0), static_cast<int>(m_values.at(value).type()), FieldTypeRole);
@ -197,7 +205,7 @@ bool FieldModelRemoveRowsCommand::internalUndo()
/*!
* \brief Stores the entry path for the specified \a model and \a index in \a res.
*/
void indexToPath(EntryModel *model, const QModelIndex &index, list<string> &res)
static void indexToPath(EntryModel *model, const QModelIndex &index, list<string> &res)
{
res.clear();
if (Entry *entry = model->entry(index)) {
@ -209,7 +217,7 @@ void indexToPath(EntryModel *model, const QModelIndex &index, list<string> &res)
* \brief Fetches the entry for the specified \a model and \a path.
* \remarks The \a path will be modified. To prevent this use entryFromPathCpy().
*/
Entry *entryFromPath(EntryModel *model, list<string> &path)
static Entry *entryFromPath(EntryModel *model, list<string> &path)
{
if (NodeEntry *rootEntry = model->rootEntry()) {
return rootEntry->entryByPath(path);
@ -220,7 +228,7 @@ Entry *entryFromPath(EntryModel *model, list<string> &path)
/*!
* \brief Fetches the entry for the specified \a model and \a path.
*/
Entry *entryFromPathCpy(EntryModel *model, list<string> path)
static Entry *entryFromPathCpy(EntryModel *model, list<string> path)
{
return entryFromPath(model, path);
}
@ -316,7 +324,7 @@ EntryModelModifyRowsCommand::~EntryModelModifyRowsCommand()
*/
bool EntryModelModifyRowsCommand::insert()
{
if (Entry *parentEntry = entryFromPathCpy(m_model, m_parentPath)) {
if (Entry *const parentEntry = entryFromPathCpy(m_model, m_parentPath)) {
if (m_model->insertEntries(m_row, m_model->index(parentEntry), m_values)) {
m_values.clear();
return true;
@ -334,7 +342,7 @@ bool EntryModelModifyRowsCommand::insert()
*/
bool EntryModelModifyRowsCommand::remove()
{
if (Entry *parentEntry = entryFromPathCpy(m_model, m_parentPath)) {
if (Entry *const parentEntry = entryFromPathCpy(m_model, m_parentPath)) {
m_values = m_model->takeEntries(m_row, m_count, m_model->index(parentEntry));
return !m_values.isEmpty();
}
@ -433,29 +441,28 @@ bool EntryModelMoveRowsCommand::internalRedo()
bool EntryModelMoveRowsCommand::internalUndo()
{
if (m_count) {
Entry *sourceParentEntry = entryFromPathCpy(m_model, m_sourceParentPath);
Entry *destParentEntry = entryFromPathCpy(m_model, m_destParentPath);
if (sourceParentEntry && destParentEntry) {
int sourceRow = m_destChild;
int destChild = m_sourceRow;
// moves whithin the same parent needs special consideration
if (sourceParentEntry == destParentEntry) {
// move entry down
if (m_sourceRow < m_destChild) {
sourceRow -= m_count;
// move entry up
} else if (m_sourceRow > m_destChild) {
destChild += m_count;
// keep entry were it is
} else {
return true;
}
}
return m_model->moveRows(m_model->index(destParentEntry), sourceRow, m_count, m_model->index(sourceParentEntry), destChild);
}
return false;
if (!m_count) {
return true;
}
return true;
Entry *const sourceParentEntry = entryFromPathCpy(m_model, m_sourceParentPath);
Entry *const destParentEntry = entryFromPathCpy(m_model, m_destParentPath);
if (sourceParentEntry && destParentEntry) {
auto sourceRow = m_destChild, destChild = m_sourceRow;
// moves within the same parent needs special consideration
if (sourceParentEntry == destParentEntry) {
// move entry down
if (m_sourceRow < m_destChild) {
sourceRow -= m_count;
// move entry up
} else if (m_sourceRow > m_destChild) {
destChild += m_count;
// keep entry were it is
} else {
return true;
}
}
return m_model->moveRows(m_model->index(destParentEntry), sourceRow, m_count, m_model->index(sourceParentEntry), destChild);
}
return false;
}
} // namespace QtGui

View File

@ -1,4 +1,5 @@
#include "./cli/cli.h"
#ifdef PASSWORD_MANAGER_GUI_QTWIDGETS
#include "./gui/initiategui.h"
#endif
@ -9,13 +10,12 @@
#include "resources/config.h"
#include "resources/qtconfig.h"
#include <passwordfile/util/openssl.h>
#include <c++utilities/application/argumentparser.h>
#include <c++utilities/application/commandlineutils.h>
#include <c++utilities/misc/parseerror.h>
#if defined(PASSWORD_MANAGER_GUI_QTWIDGETS) || defined(PASSWORD_MANAGER_GUI_QTQUICK)
#define PASSWORD_MANAGER_GUI_QTWIDGETS_OR_QTQUICK
#include <QCoreApplication>
#include <QString>
#include <qtutilities/resources/qtconfigarguments.h>
@ -24,33 +24,39 @@ ENABLE_QT_RESOURCES_OF_STATIC_DEPENDENCIES
#include <c++utilities/application/fakeqtconfigarguments.h>
#endif
#include <cstdlib>
#include <iostream>
// force (preferably Qt Quick) GUI under Android
#ifdef Q_OS_ANDROID
#if defined(PASSWORD_MANAGER_GUI_QTWIDGETS) || defined(PASSWORD_MANAGER_GUI_QTQUICK)
#ifdef PASSWORD_MANAGER_GUI_QTWIDGETS_OR_QTQUICK
#define PASSWORD_MANAGER_FORCE_GUI
#else
#error "Must build at least one kind of GUI under Android."
#error "Must configure building at least one kind of GUI under Android."
#endif
#endif
using namespace std;
using namespace CppUtilities;
using namespace Util;
#ifndef PASSWORD_MANAGER_FORCE_GUI
static int fail(std::string_view error)
{
CMD_UTILS_START_CONSOLE;
std::cerr << error << std::endl;
return EXIT_FAILURE;
}
#endif
int main(int argc, char *argv[])
{
CMD_UTILS_CONVERT_ARGS_TO_UTF8;
SET_APPLICATION_INFO;
QT_CONFIG_ARGUMENTS qtConfigArgs;
int returnCode = 0;
// parse CLI arguments
auto qtConfigArgs = QT_CONFIG_ARGUMENTS();
#ifndef PASSWORD_MANAGER_FORCE_GUI
// setup argument parser
ArgumentParser parser;
// file argument
Argument fileArg("file", 'f', "specifies the file to be opened (or created when using --modify)");
auto parser = ArgumentParser();
auto fileArg = Argument("file", 'f', "specifies the file to be opened (or created when using --modify)");
fileArg.setValueNames({ "path" });
fileArg.setRequiredValueCount(1);
fileArg.setCombinable(true);
@ -58,69 +64,44 @@ int main(int argc, char *argv[])
fileArg.setImplicit(true);
qtConfigArgs.qtWidgetsGuiArg().addSubArgument(&fileArg);
qtConfigArgs.qtQuickGuiArg().addSubArgument(&fileArg);
// cli argument
Argument cliArg("interactive-cli", 'i', "starts the interactive command line interface");
auto cliArg = Argument("interactive-cli", 'i', "starts the interactive command line interface");
cliArg.setDenotesOperation(true);
cliArg.setSubArguments({ &fileArg });
// help argument
HelpArgument helpArg(parser);
auto helpArg = HelpArgument(parser);
parser.setMainArguments({ &qtConfigArgs.qtWidgetsGuiArg(), &qtConfigArgs.qtQuickGuiArg(), &cliArg, &helpArg });
// parse the specified arguments
parser.parseArgs(argc, argv);
#endif
#ifndef PASSWORD_MANAGER_FORCE_GUI
// start either interactive CLI or GUI
// run CLI if CLI-argument is present
if (cliArg.isPresent()) {
// init OpenSSL
OpenSsl::init();
return Cli::InteractiveCli().run(fileArg.isPresent() ? std::string(fileArg.firstValue()) : std::string());
}
Cli::InteractiveCli cli;
if (fileArg.isPresent()) {
cli.run(fileArg.firstValue());
} else {
cli.run();
}
// clean OpenSSL
OpenSsl::clean();
} else if (qtConfigArgs.areQtGuiArgsPresent()) {
#if defined(PASSWORD_MANAGER_GUI_QTWIDGETS) || defined(PASSWORD_MANAGER_GUI_QTQUICK)
const auto file(fileArg.isPresent() ? QString::fromLocal8Bit(fileArg.firstValue()) : QString());
// run GUI depending on which GUI-argument is present
if (qtConfigArgs.areQtGuiArgsPresent()) {
#ifdef PASSWORD_MANAGER_GUI_QTWIDGETS_OR_QTQUICK
const auto file = fileArg.isPresent() ? QString::fromLocal8Bit(fileArg.firstValue()) : QString();
#endif
if (qtConfigArgs.qtWidgetsGuiArg().isPresent()) {
#ifdef PASSWORD_MANAGER_GUI_QTWIDGETS
returnCode = QtGui::runWidgetsGui(argc, argv, qtConfigArgs, file);
return QtGui::runWidgetsGui(argc, argv, qtConfigArgs, file);
#else
CMD_UTILS_START_CONSOLE;
cerr << "The application has not been built with Qt widgets support." << endl;
return fail("The application has not been built with Qt Widgets GUI support.");
#endif
} else if (qtConfigArgs.qtQuickGuiArg().isPresent()) {
#ifdef PASSWORD_MANAGER_GUI_QTQUICK
returnCode = QtGui::runQuickGui(argc, argv, qtConfigArgs, file);
return QtGui::runQuickGui(argc, argv, qtConfigArgs, file);
#else
CMD_UTILS_START_CONSOLE;
cerr << "The application has not been built with Qt quick support." << endl;
#endif
} else {
#if defined(PASSWORD_MANAGER_GUI_QTQUICK)
returnCode = QtGui::runQuickGui(argc, argv, qtConfigArgs, file);
#elif defined(PASSWORD_MANAGER_GUI_QTWIDGETS)
returnCode = QtGui::runWidgetsGui(argc, argv, qtConfigArgs, file);
#else
CMD_UTILS_START_CONSOLE;
cerr << "See --help for usage." << endl;
return fail("The application has not been built with Qt Quick GUI support.");
#endif
}
}
return fail("See --help for usage.");
#else // PASSWORD_MANAGER_FORCE_GUI
#ifdef PASSWORD_MANAGER_GUI_QTQUICK
returnCode = QtGui::runQuickGui(argc, argv, qtConfigArgs, QString());
return QtGui::runQuickGui(argc, argv, qtConfigArgs, QString());
#else
returnCode = QtGui::runWidgetsGui(argc, argv, qtConfigArgs, QString());
return QtGui::runWidgetsGui(argc, argv, qtConfigArgs, QString());
#endif
#endif
return returnCode;
}

View File

@ -94,7 +94,7 @@ QList<Entry *> EntryModel::takeEntries(int row, int count, const QModelIndex &pa
int lastIndex = row + count - 1;
const vector<Entry *> &children = parentNodeEntry->children();
if (lastIndex < 0 || static_cast<size_t>(lastIndex) >= children.size()) {
lastIndex = children.size() - 1;
lastIndex = static_cast<int>(children.size() - 1);
}
beginRemoveRows(parent, row, lastIndex);
for (int index = lastIndex; index >= row; --index) {
@ -125,9 +125,9 @@ bool EntryModel::insertEntries(int row, const QModelIndex &parent, const QList<E
NodeEntry *const parentNodeEntry = static_cast<NodeEntry *>(parentEntry);
const vector<Entry *> &children = parentNodeEntry->children();
if (row < 0 || static_cast<size_t>(row) > children.size()) {
row = children.size();
row = static_cast<int>(children.size());
}
beginInsertRows(parent, row, row + entries.size() - 1);
beginInsertRows(parent, row, row + static_cast<int>(entries.size()) - 1);
for (Entry *const entry : entries) {
entry->setParent(parentNodeEntry, row);
++row;
@ -243,7 +243,7 @@ QVariant EntryModel::data(const QModelIndex &index, int role) const
entry->make(ss);
// FIXME: make conversion to QByteArray more efficient
const auto str(ss.str());
return QByteArray(str.data(), str.size());
return QByteArray(str.data(), static_cast<QByteArray::size_type>(str.size()));
} catch (const std::ios_base::failure &) {
return false;
}
@ -301,8 +301,11 @@ bool EntryModel::setData(const QModelIndex &index, const QVariant &value, int ro
try {
stringstream ss(stringstream::in | stringstream::out | stringstream::binary);
ss.exceptions(std::stringstream::failbit | std::stringstream::badbit);
#if defined(__GLIBCXX__) && !defined(_LIBCPP_VERSION)
ss.rdbuf()->pubsetbuf(array.data(), array.size());
#else
ss.write(array.data(), array.size());
#endif
Entry *const newEntry = Entry::parse(ss);
const int row = entry->index();
beginRemoveRows(parentIndex, row, row);
@ -375,7 +378,7 @@ int EntryModel::rowCount(const QModelIndex &parent) const
if (Entry *parentEntry = static_cast<Entry *>(parent.internalPointer())) {
switch (parentEntry->type()) {
case EntryType::Node:
return static_cast<NodeEntry *>(parentEntry)->children().size();
return static_cast<int>(static_cast<NodeEntry *>(parentEntry)->children().size());
case EntryType::Account:;
}
}
@ -449,42 +452,56 @@ bool EntryModel::moveRows(const QModelIndex &sourceParent, int sourceRow, int co
if (undoStack()) {
return push(make_unique<EntryModelMoveRowsCommand>(this, sourceParent, sourceRow, count, destinationParent, destinationChild));
}
#endif
#if CPP_UTILITIES_DEBUG_BUILD
std::cout << "sourceRow: " << sourceRow << endl;
std::cout << "destinationChild: " << destinationChild << endl;
#endif
// check validation of specified arguments: source and destination parent entries need to be node entries
if (!sourceParent.isValid() || !destinationParent.isValid() || sourceRow < 0 || count <= 0 || entry(sourceParent)->type() != EntryType::Node //
|| entry(destinationParent)->type() != EntryType::Node) {
if (sourceRow < 0 || count <= 0) {
return false;
}
const auto *const srcParentEntry = entry(sourceParent);
const auto *const destParentEntry = entry(destinationParent);
if (!srcParentEntry || !destParentEntry || srcParentEntry->type() != EntryType::Node || destParentEntry->type() != EntryType::Node) {
return false;
}
// determine the source parent entry and dest parent entry as node entries
auto *const srcParentEntry = static_cast<NodeEntry *>(sourceParent.internalPointer());
auto *const destParentEntry = static_cast<NodeEntry *>(destinationParent.internalPointer());
#if CPP_UTILITIES_DEBUG_BUILD
cout << "destinationChild: " << destinationChild << endl;
#endif
auto *const srcParentNodeEntry = static_cast<NodeEntry *>(sourceParent.internalPointer());
auto *const destParentNodeEntry = static_cast<NodeEntry *>(destinationParent.internalPointer());
// source rows must be within the valid range
if (static_cast<size_t>(sourceRow + count) > srcParentEntry->children().size()
// if source and destination parent are the same the destination child mustn't be in the source range
|| !(srcParentEntry != destParentEntry || (destinationChild < sourceRow || (sourceRow + count) < destinationChild))) {
if (static_cast<std::size_t>(sourceRow + count) > srcParentNodeEntry->children().size()) {
return false;
}
// if source and destination parent are the same the destination child mustn't be in the source range
if (srcParentNodeEntry == destParentNodeEntry) {
if (destinationChild == sourceRow) {
return true;
}
if (!(destinationChild < sourceRow || (sourceRow + count) < destinationChild)) {
return false;
}
}
// do not move a row to one of its own children! -> check before
for (int index = 0; index < count; ++index) {
Entry *const toMove = srcParentEntry->children()[static_cast<size_t>(sourceRow + index)];
Entry *const toMove = srcParentNodeEntry->children()[static_cast<std::size_t>(sourceRow + index)];
if (toMove->type() != EntryType::Node) {
continue;
}
if (toMove == destParentEntry || destParentEntry->isIndirectChildOf(static_cast<NodeEntry *>(toMove))) {
if (toMove == destParentNodeEntry || destParentNodeEntry->isIndirectChildOf(static_cast<NodeEntry *>(toMove))) {
return false;
}
}
// actually perform the move operation
beginMoveRows(sourceParent, sourceRow, sourceRow + count - 1, destinationParent, destinationChild);
if (!beginMoveRows(sourceParent, sourceRow, sourceRow + count - 1, destinationParent, destinationChild)) {
return false;
}
for (int index = 0; index < count; ++index) {
Entry *toMove = srcParentEntry->children()[static_cast<size_t>(sourceRow + index)];
if (srcParentEntry == destParentEntry && sourceRow < destinationChild) {
toMove->setParent(destParentEntry, destinationChild + index - 1);
Entry *toMove = srcParentNodeEntry->children()[static_cast<std::size_t>(sourceRow + index)];
if (srcParentNodeEntry == destParentNodeEntry && sourceRow < destinationChild) {
toMove->setParent(destParentNodeEntry, destinationChild + index - 1);
} else {
toMove->setParent(destParentEntry, destinationChild + index);
toMove->setParent(destParentNodeEntry, destinationChild + index);
}
}
endMoveRows();

View File

@ -9,11 +9,15 @@
#include <QAbstractItemModel>
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
#include <passwordfile/io/entry.h>
#else
namespace Io {
class Entry;
class NodeEntry;
enum class EntryType;
} // namespace Io
#endif
namespace QtGui {

View File

@ -89,7 +89,7 @@ inline QVariant FieldModel::passwordValue(const QModelIndex &index, int role) co
return (m_passwordVisibility == PasswordVisibility::Always || role == Qt::EditRole
|| (*m_fields)[static_cast<size_t>(index.row())].type() != FieldType::Password)
? QString::fromStdString((*m_fields)[static_cast<size_t>(index.row())].value())
: QString((*m_fields)[static_cast<size_t>(index.row())].value().size(), QChar(0x2022));
: QString(static_cast<QString::size_type>((*m_fields)[static_cast<size_t>(index.row())].value().size()), QChar(0x2022));
}
QVariant FieldModel::data(const QModelIndex &index, int role) const
@ -155,14 +155,14 @@ QVariant FieldModel::data(const QModelIndex &index, int role) const
QMap<int, QVariant> FieldModel::itemData(const QModelIndex &index) const
{
static const auto roleMap = [this, index] {
QMap<int, QVariant> roleMap;
auto res = QMap<int, QVariant>();
for (const auto role : initializer_list<int>{ Qt::EditRole, FieldTypeRole }) {
const auto variantData(data(index, role));
if (variantData.isValid()) {
roleMap.insert(role, variantData);
res.insert(role, variantData);
}
}
return roleMap;
return res;
}();
return roleMap;
}
@ -233,17 +233,15 @@ bool FieldModel::setData(const QModelIndex &index, const QVariant &value, int ro
switch (index.column()) {
case 0:
beginInsertRows(index.parent(), rowCount(), rowCount());
m_fields->emplace_back(m_accountEntry);
m_fields->back().setName(value.toString().toStdString());
m_fields->emplace_back(m_accountEntry).setName(value.toString().toStdString());
endInsertRows();
roles << Qt::DisplayRole << Qt::EditRole << IsLastRow;
roles << Qt::DisplayRole << Qt::EditRole << Key << IsLastRow;
break;
case 1:
beginInsertRows(index.parent(), rowCount(), rowCount());
m_fields->emplace_back(m_accountEntry);
m_fields->back().setValue(value.toString().toStdString());
m_fields->emplace_back(m_accountEntry).setValue(value.toString().toStdString());
endInsertRows();
roles << Qt::DisplayRole << Qt::EditRole << IsLastRow;
roles << Qt::DisplayRole << Qt::EditRole << Value << IsLastRow;
break;
default:;
}
@ -255,7 +253,7 @@ bool FieldModel::setData(const QModelIndex &index, const QVariant &value, int ro
if (roles.isEmpty()) {
return false;
}
// emit data changed signal on sucess
// emit data changed signal on success
emit dataChanged(index, index, roles);
return true;
}
@ -289,7 +287,7 @@ QVariant FieldModel::headerData(int section, Qt::Orientation orientation, int ro
int FieldModel::rowCount(const QModelIndex &parent) const
{
return (!parent.isValid() && m_fields) ? m_fields->size() + 1 : 0;
return (!parent.isValid() && m_fields) ? static_cast<int>(m_fields->size()) + 1 : 0;
}
int FieldModel::columnCount(const QModelIndex &parent) const
@ -335,25 +333,23 @@ bool FieldModel::moveRows(const QModelIndex &sourceParent, int sourceRow, int co
// validate input parameter
if (sourceParent.isValid() || destinationParent.isValid() || sourceRow < 0 || count <= 0 || destinationChild < 0
|| static_cast<size_t>(sourceRow + count) > m_fields->size() || static_cast<size_t>(destinationChild) >= m_fields->size()
|| static_cast<std::size_t>(sourceRow + count) > m_fields->size() || static_cast<std::size_t>(destinationChild) >= m_fields->size()
|| (destinationChild >= sourceRow && destinationChild < (sourceRow + count))) {
return false;
}
// begin the move
if (destinationChild > sourceRow) {
// move rows down: the third param is still counted in the initial array!
beginMoveRows(sourceParent, sourceRow, sourceRow + count - 1, destinationParent, destinationChild + count);
} else {
// move rows up
beginMoveRows(sourceParent, sourceRow, sourceRow + count - 1, destinationParent, destinationChild);
// note: When moving rows down (destinationChild > sourceRow) the third param is still counted in the initial array!
if (!beginMoveRows(sourceParent, sourceRow, sourceRow + count - 1, destinationParent,
destinationChild > sourceRow ? destinationChild + count : destinationChild)) {
return false;
}
// reserve space for temporary copies (FIXME: possible to avoid this?)
m_fields->reserve(m_fields->size() + static_cast<size_t>(count));
vector<Io::Field> tmp(static_cast<size_t>(count));
m_fields->reserve(m_fields->size() + static_cast<std::size_t>(count));
auto tmp = std::vector<Io::Field>(static_cast<std::size_t>(count));
// move rows to temporary array
move(m_fields->begin() + sourceRow, m_fields->begin() + sourceRow + count, tmp.begin());
std::move(m_fields->begin() + sourceRow, m_fields->begin() + sourceRow + count, tmp.begin());
// erase slots of rows to be moved
m_fields->erase(m_fields->begin() + sourceRow, m_fields->begin() + sourceRow + count);
// insert rows again at their new position
@ -398,10 +394,7 @@ QMimeData *FieldModel::mimeData(const QModelIndexList &indices) const
*/
const Field *FieldModel::field(size_t row) const
{
if (m_fields && row < m_fields->size()) {
return &(*m_fields)[row];
}
return nullptr;
return m_fields && row < m_fields->size() ? &(*m_fields)[row] : nullptr;
}
} // namespace QtGui

View File

@ -141,7 +141,7 @@ inline void FieldModel::setPasswordVisibility(PasswordVisibility passwordVisibil
{
m_passwordVisibility = passwordVisibility;
if (m_fields) {
emit dataChanged(index(0, 1), index(m_fields->size() - 1, 1), QVector<int>({ Qt::DisplayRole, Qt::EditRole }));
emit dataChanged(index(0, 1), index(static_cast<int>(m_fields->size() - 1), 1), QVector<int>({ Qt::DisplayRole, Qt::EditRole }));
}
}
} // namespace QtGui

View File

@ -1,15 +1,13 @@
import QtQuick 2.7
import QtQuick.Controls 2.1 as Controls
import QtQuick.Layouts 1.2
import QtQuick.Dialogs 1.3
import org.kde.kirigami 2.4 as Kirigami
BasicDialog {
id: aboutDialog
standardButtons: Controls.Dialog.Ok
padding: Kirigami.Units.largeSpacing
ColumnLayout {
contentItem: ColumnLayout {
width: aboutDialog.availableWidth
Image {
@ -44,16 +42,8 @@ BasicDialog {
Layout.fillWidth: true
text: "<a href=\"" + app.organizationDomain + "\">" + app.organizationDomain + "</a>"
horizontalAlignment: Text.AlignHCenter
onLinkActivated: openWebsite()
onLinkActivated: Qt.openUrlExternally(app.organizationDomain)
wrapMode: Text.Wrap
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: parent.openWebsite()
}
function openWebsite() {
Qt.openUrlExternally(app.organizationDomain)
}
}
Controls.Label {
Layout.fillWidth: true

View File

@ -6,9 +6,7 @@ Controls.Dialog {
modal: true
focus: true
parent: applicationWindow().overlay
//anchors.centerIn: parent // enable if requiring at least Qt 5.12 instead of setting x and y manually
x: (parent.width - width) / 2
y: (parent.height - height) / 2
anchors.centerIn: parent
width: Math.min(parent.width, Kirigami.Units.gridUnit * 30)
function acceptOnReturn(event) {

View File

@ -1,4 +1,4 @@
import QtQuick 2.4
import QtQuick 2.15
import QtQuick.Layouts 1.2
import QtQml.Models 2.2
import QtQuick.Controls 2.4 as Controls
@ -15,17 +15,17 @@ Kirigami.ScrollablePage {
var currentEntryName = entryModel.data(rootIndex)
return currentEntryName ? currentEntryName : ""
}
actions {
main: Kirigami.Action {
iconName: "list-add"
actions:[
Kirigami.Action {
icon.name: "list-add"
text: qsTr("Add account")
visible: !nativeInterface.hasEntryFilter
enabled: !nativeInterface.hasEntryFilter
onTriggered: insertEntry("Account")
shortcut: "Ctrl+A"
}
left: Kirigami.Action {
iconName: "edit-paste"
},
Kirigami.Action {
icon.name: "edit-paste"
text: qsTr("Paste account")
visible: !nativeInterface.hasEntryFilter
enabled: nativeInterface.canPaste && !nativeInterface.hasEntryFilter
@ -40,19 +40,16 @@ Kirigami.ScrollablePage {
pastedEntries.join(", ")))
}
shortcut: StandardKey.Paste
}
right: Kirigami.Action {
iconName: "folder-add"
},
Kirigami.Action {
icon.name: "folder-add"
text: qsTr("Add category")
visible: !nativeInterface.hasEntryFilter
enabled: !nativeInterface.hasEntryFilter
onTriggered: insertEntry("Node")
shortcut: "Ctrl+Shift+A"
}
}
background: Rectangle {
color: Kirigami.Theme.backgroundColor
}
]
// dialog to confirm deletion of an entry
BasicDialog {
@ -64,8 +61,7 @@ Kirigami.ScrollablePage {
standardButtons: Controls.Dialog.Ok | Controls.Dialog.Cancel
title: qsTr("Delete %1?").arg(entryDesc)
onAccepted: entryModel.removeRows(this.entryIndex, 1, rootIndex)
ColumnLayout {
contentItem: ColumnLayout {
Controls.Label {
text: " "
}
@ -109,13 +105,12 @@ Kirigami.ScrollablePage {
entryModel.removeRows(this.entryIndex, 1, rootIndex)
}
}
ColumnLayout {
contentItem: ColumnLayout {
Controls.TextField {
id: entryNameTextField
Layout.preferredWidth: renameDialog.availableWidth
placeholderText: qsTr("enter new name here")
Keys.onPressed: renameDialog.acceptOnReturn(event)
Keys.onPressed: (event) => renameDialog.acceptOnReturn(event)
}
}
@ -138,115 +133,6 @@ Kirigami.ScrollablePage {
}
}
// component representing an entry
Component {
id: listDelegateComponent
Kirigami.SwipeListItem {
id: listItem
contentItem: RowLayout {
Kirigami.ListItemDragHandle {
listItem: listItem
listView: entriesListView
enabled: !nativeInterface.hasEntryFilter
// FIXME: not sure why newIndex + 1 is required to be able to move a row at the end
onMoveRequested: entryModel.moveRows(
rootIndex, oldIndex, 1, rootIndex,
oldIndex < newIndex ? newIndex + 1 : newIndex)
}
Item {
Layout.fillWidth: true
Layout.fillHeight: true
RowLayout {
anchors.fill: parent
Kirigami.Icon {
width: Kirigami.Units.iconSizes.smallMedium
height: Kirigami.Units.iconSizes.smallMedium
Layout.fillHeight: true
source: delegateModel.isNode(
index) ? "folder-symbolic" : "story-editor"
}
Controls.Label {
Layout.fillWidth: true
Layout.fillHeight: true
text: model.name
verticalAlignment: Text.AlignVCenter
}
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: {
if (mouse.button === Qt.RightButton) {
entryContextMenu.popup()
return
}
delegateModel.handleEntryClicked(index, model.name)
}
onPressAndHold: entryContextMenu.popup()
}
Controls.Menu {
id: entryContextMenu
Controls.MenuItem {
icon.name: "edit-cut"
text: qsTr("Cut")
enabled: !nativeInterface.hasEntryFilter
onTriggered: {
nativeInterface.cutEntry(
entryModel.index(index, 0,
rootIndex))
showPassiveNotification(qsTr("Cut %1").arg(
model.name))
}
}
Controls.MenuItem {
icon.name: "edit-delete"
text: qsTr("Delete")
enabled: !nativeInterface.hasEntryFilter
onTriggered: confirmDeletionDialog.confirmDeletion(
model.name, index)
}
Controls.MenuItem {
icon.name: "edit-rename"
text: qsTr("Rename")
enabled: !nativeInterface.hasEntryFilter
onTriggered: renameDialog.renameEntry(model.name,
index)
}
}
}
}
actions: [
Kirigami.Action {
iconName: "edit-cut"
text: qsTr("Cut")
enabled: !nativeInterface.hasEntryFilter
onTriggered: {
nativeInterface.cutEntry(entryModel.index(index, 0,
rootIndex))
showPassiveNotification(text + " " + model.name)
}
shortcut: StandardKey.Cut
},
Kirigami.Action {
iconName: "edit-delete"
text: qsTr("Delete")
enabled: !nativeInterface.hasEntryFilter
onTriggered: confirmDeletionDialog.confirmDeletion(
model.name, index)
shortcut: StandardKey.Delete
},
Kirigami.Action {
iconName: "edit-rename"
text: qsTr("Rename")
enabled: !nativeInterface.hasEntryFilter
onTriggered: renameDialog.renameEntry(model.name, index)
shortcut: "F2"
}
]
}
}
// list view to display one hierarchy level of entry model
ListView {
id: entriesListView
@ -257,12 +143,16 @@ Kirigami.ScrollablePage {
easing.type: Easing.InOutQuad
}
}
reuseItems: true
model: DelegateModel {
id: delegateModel
delegate: Kirigami.DelegateRecycler {
width: parent ? parent.width : implicitWidth
sourceComponent: listDelegateComponent
delegate: EntryDelegate {
width: entriesListView.width
view: entriesListView
onMoveRequested:
(oldIndex, newIndex) => {
entryModel.moveRows(rootIndex, oldIndex, 1, rootIndex, oldIndex < newIndex ? newIndex + 1 : newIndex)
}
}
function isNode(rowNumber) {

119
qml/EntryDelegate.qml Normal file
View File

@ -0,0 +1,119 @@
import QtQuick 2.4
import QtQuick.Layouts 1.2
import QtQml.Models 2.2
import QtQuick.Controls 2.4 as Controls
import org.kde.kirigami 2.5 as Kirigami
Item {
id: delegate
property ListView view
implicitHeight : listItem.implicitHeight
signal moveRequested(int oldIndex, int newIndex)
Kirigami.SwipeListItem {
id: listItem
alwaysVisibleActions: true // default is broken in mobile/tablet mode
contentItem: RowLayout {
Kirigami.ListItemDragHandle {
listItem: listItem
listView: view
enabled: !nativeInterface.hasEntryFilter
onMoveRequested:
(oldIndex, newIndex) => delegate.moveRequested(oldIndex, newIndex)
}
Item {
Layout.fillWidth: true
Layout.fillHeight: true
RowLayout {
anchors.fill: parent
Kirigami.Icon {
width: Kirigami.Units.iconSizes.smallMedium
height: Kirigami.Units.iconSizes.smallMedium
Layout.fillHeight: true
source: delegateModel.isNode(
index) ? "folder-symbolic" : "view-list-details-symbolic"
}
Controls.Label {
Layout.fillWidth: true
Layout.fillHeight: true
Layout.maximumWidth: availableWidth - listItem.overlayWidth
elide: Text.ElideRight
text: model.name
verticalAlignment: Text.AlignVCenter
}
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: (mouse) => {
if (mouse.button === Qt.RightButton) {
entryContextMenu.popup()
return
}
delegateModel.handleEntryClicked(index, model.name)
}
onPressAndHold: entryContextMenu.popup()
}
Controls.Menu {
id: entryContextMenu
Controls.MenuItem {
icon.name: "edit-cut"
text: qsTr("Cut")
enabled: !nativeInterface.hasEntryFilter
onTriggered: {
nativeInterface.cutEntry(
entryModel.index(index, 0,
rootIndex))
showPassiveNotification(qsTr("Cut %1").arg(
model.name))
}
}
Controls.MenuItem {
icon.name: "edit-delete"
text: qsTr("Delete")
enabled: !nativeInterface.hasEntryFilter
onTriggered: confirmDeletionDialog.confirmDeletion(
model.name, index)
}
Controls.MenuItem {
icon.name: "edit-rename"
text: qsTr("Rename")
enabled: !nativeInterface.hasEntryFilter
onTriggered: renameDialog.renameEntry(model.name,
index)
}
}
}
}
actions: [
Kirigami.Action {
icon.name: "edit-cut"
text: qsTr("Cut")
enabled: !nativeInterface.hasEntryFilter
onTriggered: {
nativeInterface.cutEntry(entryModel.index(index, 0,
rootIndex))
showPassiveNotification(text + " " + model.name)
}
shortcut: StandardKey.Cut
},
Kirigami.Action {
icon.name: "edit-delete"
text: qsTr("Delete")
enabled: !nativeInterface.hasEntryFilter
onTriggered: confirmDeletionDialog.confirmDeletion(
model.name, index)
shortcut: StandardKey.Delete
},
Kirigami.Action {
icon.name: "edit-rename"
text: qsTr("Rename")
enabled: !nativeInterface.hasEntryFilter
onTriggered: renameDialog.renameEntry(model.name, index)
shortcut: "F2"
}
]
}
}

129
qml/FieldsDelegate.qml Normal file
View File

@ -0,0 +1,129 @@
import QtQuick 2.4
import QtQuick.Layouts 1.2
import QtQml.Models 2.2
import QtQuick.Controls 2.4 as Controls
import org.kde.kirigami 2.5 as Kirigami
Item {
id: delegate
property ListView view
implicitHeight : listItem.implicitHeight
signal moveRequested(int oldIndex, int newIndex)
Kirigami.SwipeListItem {
id: listItem
alwaysVisibleActions: true // default is broken in mobile/tablet mode
visible: !model.isLastRow
contentItem: RowLayout {
id: fieldRow
Kirigami.ListItemDragHandle {
listItem: listItem
listView: view
onMoveRequested: (oldIndex, newIndex) => delegate.moveRequested(oldIndex, newIndex)
}
Item {
Layout.fillWidth: true
Layout.fillHeight: true
RowLayout {
anchors.fill: parent
Controls.Label {
Layout.fillWidth: true
Layout.fillHeight: true
Layout.maximumWidth: availableWidth - listItem.overlayWidth
elide: Text.ElideRight
text: {
let pieces = []
if (model.key) {
pieces.push(model.key)
}
if (model.value) {
pieces.push(model.value)
}
return pieces.join(": ")
}
verticalAlignment: Text.AlignVCenter
}
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: (mouse) => {
if (mouse.button === Qt.RightButton) {
return fieldContextMenu.popup()
}
fieldDialog.init(model, index)
fieldDialog.open()
}
onPressAndHold: fieldContextMenu.popup()
}
Controls.Menu {
id: fieldContextMenu
Controls.MenuItem {
icon.name: !model.isPassword ? "password-show-off" : "password-show-on"
text: model.isPassword ? qsTr("Mark as normal field") : qsTr(
"Mark as password field")
onClicked: view.model.setData(
view.model.index(index,
0),
model.isPassword ? 0 : 1, 0x0100 + 1)
}
Controls.MenuItem {
icon.name: "edit-copy"
text: model.isPassword ? qsTr("Copy password") : qsTr(
"Copy value")
onClicked: showPassiveNotification(
nativeInterface.copyToClipboard(
model.actualValue) ? qsTr("Copied") : qsTr(
"Unable to access clipboard"))
}
Controls.MenuItem {
icon.name: "edit-delete"
text: qsTr("Delete field")
onClicked: view.model.removeRows(index, 1)
}
Controls.MenuItem {
icon.name: "list-add"
text: qsTr("Insert empty field after this")
onClicked: view.model.insertRows(
index + 1, 1)
}
}
}
}
actions: [
Kirigami.Action {
icon.name: !model.isPassword ? "password-show-off" : "password-show-on"
text: model.isPassword ? qsTr(
"Mark as normal field") : qsTr(
"Mark as password field")
onTriggered: view.model.setData(
view.model.index(index, 0),
model.isPassword ? 0 : 1, 0x0100 + 1)
},
Kirigami.Action {
icon.name: "edit-copy"
text: model.isPassword ? qsTr("Copy password") : qsTr(
"Copy value")
onTriggered: showPassiveNotification(
nativeInterface.copyToClipboard(
model.actualValue) ? qsTr("Copied") : qsTr(
"Unable to access clipboard"))
shortcut: StandardKey.Cut
},
Kirigami.Action {
icon.name: "edit-delete"
text: qsTr("Delete field")
onTriggered: view.model.removeRows(index, 1)
shortcut: StandardKey.Delete
},
Kirigami.Action {
icon.name: "list-add"
text: qsTr("Insert empty field after this")
enabled: !nativeInterface.hasEntryFilter
onTriggered: view.model.insertRows(index + 1, 1)
}
]
}
}

View File

@ -1,4 +1,4 @@
import QtQuick 2.4
import QtQuick 2.15
import QtQuick.Layouts 1.2
import QtQml.Models 2.2
import QtQuick.Controls 2.4 as Controls
@ -10,9 +10,21 @@ Kirigami.ScrollablePage {
Layout.fillWidth: true
title: nativeInterface.currentAccountName
background: Rectangle {
color: Kirigami.Theme.backgroundColor
}
actions:[
Kirigami.Action {
icon.name: "list-add"
text: qsTr("Add field")
visible: !nativeInterface.hasEntryFilter
enabled: !nativeInterface.hasEntryFilter
onTriggered: {
const delegateModel = fieldsListView.model
const row = delegateModel.rowCount() - 1
fieldDialog.init(delegateModel, row)
fieldDialog.open()
}
shortcut: "Ctrl+Shift+A"
}
]
// dialog to edit certain field
BasicDialog {
@ -32,8 +44,7 @@ Kirigami.ScrollablePage {
fieldsListView.model.setData(column0, isPassword ? 1 : 0,
0x0100 + 1)
}
ColumnLayout {
contentItem: ColumnLayout {
GridLayout {
Layout.preferredWidth: fieldDialog.availableWidth
columns: 2
@ -43,7 +54,7 @@ Kirigami.ScrollablePage {
id: fieldNameEdit
Layout.fillWidth: true
text: fieldDialog.fieldName
Keys.onPressed: fieldDialog.acceptOnReturn(event)
Keys.onPressed: (event) => fieldDialog.acceptOnReturn(event)
}
Controls.RoundButton {
flat: true
@ -69,7 +80,7 @@ Kirigami.ScrollablePage {
// fix ugly bullet points under Android
font.pointSize: hideCharacters ? fieldNameEdit.font.pointSize
* 0.5 : fieldNameEdit.font.pointSize
Keys.onPressed: fieldDialog.acceptOnReturn(event)
Keys.onPressed: (event) => fieldDialog.acceptOnReturn(event)
}
Controls.RoundButton {
flat: true
@ -107,169 +118,27 @@ Kirigami.ScrollablePage {
}
}
// component representing a field
Component {
id: fieldsListDelegateComponent
Kirigami.SwipeListItem {
id: fieldsListItem
contentItem: RowLayout {
id: fieldRow
readonly property bool isLast: model.isLastRow
Kirigami.ListItemDragHandle {
listItem: fieldsListItem
listView: fieldsListView
onMoveRequested: fieldsListView.model.moveRows(
fieldsListView.model.index(-1, 0),
oldIndex, 1,
fieldsListView.model.index(-1, 0),
newIndex)
visible: !fieldRow.isLast
}
Kirigami.Icon {
width: Kirigami.Units.iconSizes.smallMedium
height: width
source: "list-add"
opacity: 0.6
visible: fieldRow.isLast
MouseArea {
anchors.fill: parent
onClicked: {
fieldDialog.init(model, index)
fieldDialog.open()
}
}
}
Item {
Layout.fillWidth: true
Layout.fillHeight: true
RowLayout {
anchors.fill: parent
Controls.Label {
Layout.fillWidth: true
Layout.fillHeight: true
text: {
if (fieldRow.isLast) {
return qsTr("click to append new field")
}
var pieces = []
if (model.key) {
pieces.push(model.key)
}
if (model.value) {
pieces.push(model.value)
}
return pieces.join(": ")
}
color: fieldRow.isLast ? "gray" : palette.text
verticalAlignment: Text.AlignVCenter
}
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: {
if (mouse.button === Qt.RightButton) {
if (!fieldRow.isLast) {
fieldContextMenu.popup()
}
return
}
fieldDialog.init(model, index)
fieldDialog.open()
}
onPressAndHold: fieldContextMenu.popup()
}
Controls.Menu {
id: fieldContextMenu
Controls.MenuItem {
icon.name: !model.isPassword ? "password-show-off" : "password-show-on"
text: model.isPassword ? qsTr("Mark as normal field") : qsTr(
"Mark as password field")
onClicked: fieldsListView.model.setData(
fieldsListView.model.index(index,
0),
model.isPassword ? 0 : 1, 0x0100 + 1)
}
Controls.MenuItem {
icon.name: "edit-copy"
text: model.isPassword ? qsTr("Copy password") : qsTr(
"Copy value")
onClicked: showPassiveNotification(
nativeInterface.copyToClipboard(
model.actualValue) ? qsTr("Copied") : qsTr(
"Unable to access clipboard"))
}
Controls.MenuItem {
icon.name: "edit-delete"
text: qsTr("Delete field")
onClicked: fieldsListView.model.removeRows(index, 1)
}
Controls.MenuItem {
icon.name: "list-add"
text: qsTr("Insert empty field after this")
onClicked: fieldsListView.model.insertRows(
index + 1, 1)
}
}
}
}
actions: [
Kirigami.Action {
iconName: !model.isPassword ? "password-show-off" : "password-show-on"
text: model.isPassword ? qsTr(
"Mark as normal field") : qsTr(
"Mark as password field")
onTriggered: fieldsListView.model.setData(
fieldsListView.model.index(index, 0),
model.isPassword ? 0 : 1, 0x0100 + 1)
visible: !fieldRow.isLast
},
Kirigami.Action {
iconName: "edit-copy"
text: model.isPassword ? qsTr("Copy password") : qsTr(
"Copy value")
onTriggered: showPassiveNotification(
nativeInterface.copyToClipboard(
model.actualValue) ? qsTr("Copied") : qsTr(
"Unable to access clipboard"))
shortcut: StandardKey.Cut
visible: !fieldRow.isLast
},
Kirigami.Action {
iconName: "edit-delete"
text: qsTr("Delete field")
onTriggered: fieldsListView.model.removeRows(index, 1)
shortcut: StandardKey.Delete
visible: !fieldRow.isLast
},
Kirigami.Action {
iconName: "list-add"
text: qsTr("Insert empty field after this")
enabled: !nativeInterface.hasEntryFilter
onTriggered: fieldsListView.model.insertRows(index + 1, 1)
visible: !fieldRow.isLast
}
]
}
}
// list view to edit the currently selected account
ListView {
id: fieldsListView
implicitWidth: Kirigami.Units.gridUnit * 30
model: nativeInterface.fieldModel
reuseItems: true
moveDisplaced: Transition {
YAnimator {
duration: Kirigami.Units.longDuration
easing.type: Easing.InOutQuad
}
}
delegate: Kirigami.DelegateRecycler {
width: parent ? parent.width : implicitWidth
sourceComponent: fieldsListDelegateComponent
delegate: FieldsDelegate {
width: fieldsListView.width
view: fieldsListView
onMoveRequested:
(oldIndex, newIndex) => {
const model = fieldsListView.model
const invalidIndex = model.index(-1, 0)
model.moveRows(invalidIndex, oldIndex, 1, invalidIndex, newIndex)
}
}
}
}

39
qml/FileDialog5.qml Normal file
View File

@ -0,0 +1,39 @@
import QtQuick.Dialogs 1.3
FileDialog {
id: fileDialog
property bool createNewFile: false
title: selectExisting ? qsTr("Select an existing file") : (saveAs ? qsTr("Select path to save file") : qsTr("Select path for new file"))
onAccepted: {
if (fileUrls.length < 1) {
return
}
nativeInterface.handleFileSelectionAccepted(fileUrls[0], "",
this.selectExisting,
this.createNewFile)
}
onRejected: nativeInterface.handleFileSelectionCanceled()
function show() {
if (nativeInterface.showNativeFileDialog(this.selectExisting, this.createNewFile)) {
return
}
// fallback to the Qt Quick file dialog if a native implementation is not available
this.open()
}
function openExisting() {
this.selectExisting = true
this.createNewFile = false
this.show()
}
function createNew() {
this.selectExisting = false
this.createNewFile = true
this.show()
}
function saveAs() {
this.selectExisting = false
this.createNewFile = false
this.show()
}
}

40
qml/FileDialog6.qml Normal file
View File

@ -0,0 +1,40 @@
import QtQuick.Dialogs 6.2 as Dialogs
Dialogs.FileDialog {
id: fileDialog
property bool createNewFile: false
property bool selectExisting: fileMode !== Dialogs.FileDialog.SaveFile
title: selectExisting ? qsTr("Select an existing file") : (!createNewFile ? qsTr("Select path to save file") : qsTr("Select path for new file"))
onAccepted: {
if (selectedFiles.length < 1) {
return
}
nativeInterface.handleFileSelectionAccepted(selectedFiles[0], "",
this.selectExisting,
this.createNewFile)
}
onRejected: nativeInterface.handleFileSelectionCanceled()
function show() {
if (nativeInterface.showNativeFileDialog(this.selectExisting, this.createNewFile)) {
return
}
// fallback to the Qt Quick file dialog if a native implementation is not available
this.open()
}
function openExisting() {
this.fileMode = Dialogs.FileDialog.OpenFile
this.createNewFile = false
this.show()
}
function createNew() {
this.fileMode = Dialogs.FileDialog.SaveFile
this.createNewFile = true
this.show()
}
function saveAs() {
this.fileMode = Dialogs.FileDialog.SaveFile
this.createNewFile = false
this.show()
}
}

View File

@ -30,8 +30,7 @@ BasicDialog {
qsTr("You aborted. The password has not been altered."))
}
}
ColumnLayout {
contentItem: ColumnLayout {
Controls.Label {
id: instructionLabel
Layout.preferredWidth: passwordDialog.availableWidth
@ -42,26 +41,18 @@ BasicDialog {
id: passwordTextField
Layout.preferredWidth: passwordDialog.availableWidth
echoMode: showCharactersCheckBox.checked ? TextInput.Normal : TextInput.Password
placeholderText: qsTr("enter password here, leave empty for no encryption")
color: "#101010"
placeholderTextColor: "#505050"
background: Rectangle {
border.color: "#5d5e6d"
}
Keys.onPressed: passwordDialog.acceptOnReturn(event)
placeholderText: newPassword
? qsTr("enter password here, leave empty for no encryption")
: qsTr("enter password here")
Keys.onPressed: (event) => passwordDialog.acceptOnReturn(event)
}
Controls.TextField {
id: repeatPasswordTextField
Layout.preferredWidth: passwordDialog.availableWidth
visible: passwordDialog.newPassword
&& !showCharactersCheckBox.checked
enabled: visible && !showCharactersCheckBox.checked
echoMode: TextInput.Password
placeholderText: qsTr("repeat password")
color: "#101010"
placeholderTextColor: "#505050"
background: Rectangle {
border.color: passwordDialog.canAccept ? "#089900" : "#ff0000"
}
Keys.onPressed: passwordDialog.acceptOnReturn(event)
}
Controls.CheckBox {

View File

@ -1,8 +1,9 @@
import QtQuick 2.7
import QtQuick.Templates 2.0 as T2
import QtQuick.Controls 2.1 as Controls
import QtQuick.Controls.Material
import QtQuick.Controls.Universal
import QtQuick.Layouts 1.2
import QtQuick.Dialogs 1.3
import org.kde.kirigami 2.4 as Kirigami
Kirigami.ApplicationWindow {
@ -10,13 +11,14 @@ Kirigami.ApplicationWindow {
property var fieldsPage: undefined
property var lastEntriesPage: undefined
Material.theme: nativeInterface.darkModeEnabled ? Material.Dark : Material.Light
Universal.theme: nativeInterface.darkModeEnabled ? Universal.Dark : Universal.Light
globalDrawer: Kirigami.GlobalDrawer {
id: leftMenu
property bool showNoPasswordWarning: nativeInterface.fileOpen
&& !nativeInterface.passwordSet
title: app.applicationName
titleIcon: "qrc://icons/hicolor/scalable/apps/passwordmanager.svg"
visible: true
resetMenuOnTriggered: false
topContent: ColumnLayout {
@ -102,20 +104,20 @@ Kirigami.ApplicationWindow {
actions: [
Kirigami.Action {
text: qsTr("Create new file")
iconName: "document-new"
icon.name: "document-new"
onTriggered: fileDialog.createNew()
shortcut: StandardKey.New
},
Kirigami.Action {
text: qsTr("Open existing file")
iconName: "document-open"
icon.name: "document-open"
onTriggered: fileDialog.openExisting()
shortcut: StandardKey.Open
},
Kirigami.Action {
id: recentlyOpenedAction
text: qsTr("Recently opened ...")
iconName: "document-open-recent"
icon.name: "document-open-recent"
children: createRecentlyOpenedActions(
nativeInterface.recentFiles)
visible: nativeInterface.recentFiles.length > 0
@ -124,14 +126,14 @@ Kirigami.ApplicationWindow {
Kirigami.Action {
text: qsTr("Save modifications")
enabled: nativeInterface.fileOpen
iconName: "document-save"
icon.name: "document-save"
onTriggered: nativeInterface.save()
shortcut: StandardKey.Save
},
Kirigami.Action {
text: qsTr("Save as")
enabled: nativeInterface.fileOpen
iconName: "document-save-as"
icon.name: "document-save-as"
onTriggered: fileDialog.saveAs()
shortcut: StandardKey.SaveAs
},
@ -139,7 +141,7 @@ Kirigami.ApplicationWindow {
text: nativeInterface.passwordSet ? qsTr("Change password") : qsTr(
"Add password")
enabled: nativeInterface.fileOpen
iconName: "document-encrypt"
icon.name: "document-encrypt"
onTriggered: enterPasswordDialog.askForNewPassword(
qsTr("Change password for %1").arg(
nativeInterface.filePath))
@ -148,7 +150,7 @@ Kirigami.ApplicationWindow {
Kirigami.Action {
text: qsTr("Details")
enabled: nativeInterface.fileOpen
iconName: "document-properties"
icon.name: "document-properties"
onTriggered: {
leftMenu.resetMenu()
fileSummaryDialog.show()
@ -160,7 +162,7 @@ Kirigami.ApplicationWindow {
"Adjust search")
enabled: nativeInterface.fileOpen
visible: nativeInterface.filterAsDialog
iconName: "search"
icon.name: "search"
onTriggered: {
leftMenu.resetMenu()
filterDialog.open()
@ -172,7 +174,7 @@ Kirigami.ApplicationWindow {
enabled: nativeInterface.fileOpen
visible: nativeInterface.filterAsDialog
&& nativeInterface.entryFilter.length > 0
iconName: "edit-clear"
icon.name: "edit-clear"
onTriggered: {
leftMenu.resetMenu()
nativeInterface.entryFilter = ""
@ -184,7 +186,7 @@ Kirigami.ApplicationWindow {
visible: nativeInterface.undoText.length !== 0
&& nativeInterface.entryFilter.length === 0
enabled: visible
iconName: "edit-undo"
icon.name: "edit-undo"
shortcut: StandardKey.Undo
onTriggered: nativeInterface.undo()
},
@ -193,29 +195,30 @@ Kirigami.ApplicationWindow {
visible: nativeInterface.redoText.length !== 0
&& nativeInterface.entryFilter.length === 0
enabled: visible
iconName: "edit-redo"
icon.name: "edit-redo"
shortcut: StandardKey.Redo
onTriggered: nativeInterface.redo()
},
Kirigami.Action {
text: qsTr("Close file")
enabled: nativeInterface.fileOpen
iconName: "document-close"
icon.name: "document-close"
shortcut: StandardKey.Close
onTriggered: nativeInterface.close()
},
Kirigami.Action {
separator: true
},
Kirigami.Action {
text: qsTr("About")
icon.name: "help-about"
shortcut: "Ctrl+?"
onTriggered: {
leftMenu.resetMenu()
aboutDialog.open()
}
}
]
onBannerClicked: {
leftMenu.resetMenu()
aboutDialog.open()
}
Controls.Switch {
text: qsTr("Use native file dialog")
checked: nativeInterface.useNativeFileDialog
visible: nativeInterface.supportsNativeFileDialog
onCheckedChanged: nativeInterface.useNativeFileDialog = checked
}
}
contextDrawer: Kirigami.ContextDrawer {
id: contextDrawer
@ -240,9 +243,9 @@ Kirigami.ApplicationWindow {
id: fileSummaryDialog
standardButtons: Controls.Dialog.Ok
title: qsTr("File details")
Controls.Label {
contentItem: Controls.TextArea {
id: fileSummaryLabel
readOnly: true
text: "No file summary available"
textFormat: Text.RichText
wrapMode: Text.Wrap
@ -257,41 +260,6 @@ Kirigami.ApplicationWindow {
FileDialog {
id: fileDialog
property bool createNewFile: false
title: selectExisting ? qsTr("Select an existing file") : (saveAs ? qsTr("Select path to save file") : qsTr("Select path for new file"))
onAccepted: {
if (fileUrls.length < 1) {
return
}
nativeInterface.handleFileSelectionAccepted(fileUrls[0], "",
this.selectExisting,
this.createNewFile)
}
onRejected: nativeInterface.handleFileSelectionCanceled()
function show() {
if (nativeInterface.showNativeFileDialog(this.selectExisting,
this.createNewFile)) {
return
}
// fallback to the Qt Quick file dialog if a native implementation is not available
this.open()
}
function openExisting() {
this.selectExisting = true
this.createNewFile = false
this.show()
}
function createNew() {
this.selectExisting = false
this.createNewFile = true
this.show()
}
function saveAs() {
this.selectExisting = false
this.createNewFile = false
this.show()
}
}
BasicDialog {
@ -323,8 +291,7 @@ Kirigami.ApplicationWindow {
Controls.DialogButtonBox.buttonRole: Controls.DialogButtonBox.RejectRole
}
}
ColumnLayout {
contentItem: ColumnLayout {
Controls.TextField {
id: filterDialogTextField
Layout.preferredWidth: filterDialog.availableWidth
@ -335,12 +302,12 @@ Kirigami.ApplicationWindow {
Connections {
target: nativeInterface
onEntryFilterChanged: {
function onEntryFilterChanged(newFilter) {
if (filterTextField.text !== newFilter) {
filterTextField.text = newFilter
}
}
onFileError: {
function onFileError(errorMessage, retryAction) {
var retryMethod = null
if (retryAction === "load" || retryAction === "save") {
retryMethod = retryAction
@ -354,13 +321,16 @@ Kirigami.ApplicationWindow {
})
}
}
onPasswordRequired: {
function onSettingsError(errorMessage) {
showPassiveNotification(errorMessage)
}
function onPasswordRequired(filePath) {
enterPasswordDialog.askForExistingPassword(
qsTr("Password required to open %1").arg(
nativeInterface.filePath))
leftMenu.resetMenu()
}
onFileOpenChanged: {
function onFileOpenChanged(fileOpen) {
clearStack()
if (!nativeInterface.fileOpen) {
showPassiveNotification(qsTr("%1 closed").arg(
@ -372,20 +342,20 @@ Kirigami.ApplicationWindow {
nativeInterface.fileName))
leftMenu.close()
}
onFileSaved: {
function onFileSaved() {
showPassiveNotification(qsTr("%1 saved").arg(
nativeInterface.fileName))
}
onNewNotification: {
function onNewNotification(message) {
showPassiveNotification(message)
}
onCurrentAccountChanged: {
function onCurrentAccountChanged() {
// remove the fields page if the current account has been removed
if (!nativeInterface.hasCurrentAccount) {
pageStack.pop(lastEntriesPage)
}
}
onEntryAboutToBeRemoved: {
function onEntryAboutToBeRemoved(removedIndex) {
// get the filter entry index
if (nativeInterface.hasEntryFilter) {
removedIndex = nativeInterface.filterEntryIndex(removedIndex)
@ -403,7 +373,7 @@ Kirigami.ApplicationWindow {
}
}
}
onHasEntryFilterChanged: {
function onHasEntryFilterChanged(hasEntryFilter) {
if (nativeInterface.fileOpen) {
pageStack.clear()
initStack()
@ -428,7 +398,7 @@ Kirigami.ApplicationWindow {
id: clearRecentFilesActionComponent
Kirigami.Action {
text: qsTr("Clear recently opened files")
iconName: "edit-clear"
icon.name: "edit-clear"
onTriggered: {
nativeInterface.clearRecentFiles()
leftMenu.resetMenu()

View File

@ -5,12 +5,11 @@
#include <c++utilities/conversion/stringbuilder.h>
#include <QAndroidJniObject>
#include <QColor>
#include <QCoreApplication>
#include <QJniObject>
#include <QMessageLogContext>
#include <QMetaObject>
#include <QtAndroid>
#include <android/log.h>
@ -35,9 +34,9 @@ static Controller *controllerForAndroid = nullptr;
void applyThemingForAndroid()
{
QtAndroid::runOnAndroidThread([=]() {
QNativeInterface::QAndroidApplication::runOnAndroidMainThread([=]() {
const auto color = QColor(QLatin1String("#2c714a")).rgba();
QAndroidJniObject window = QtAndroid::androidActivity().callObjectMethod("getWindow", "()Landroid/view/Window;");
QJniObject window = QJniObject(QNativeInterface::QAndroidApplication::context()).callObjectMethod("getWindow", "()Landroid/view/Window;");
window.callMethod<void>("addFlags", "(I)V", Android::WindowManager::LayoutParams::DrawsSystemBarBackgrounds);
window.callMethod<void>("clearFlags", "(I)V", Android::WindowManager::LayoutParams::TranslucentStatus);
window.callMethod<void>("setStatusBarColor", "(I)V", color);
@ -52,13 +51,14 @@ void registerControllerForAndroid(Controller *controller)
bool showAndroidFileDialog(bool existing, bool createNew)
{
return QtAndroid::androidActivity().callMethod<jboolean>("showAndroidFileDialog", "(ZZ)Z", existing, createNew);
return QJniObject(QNativeInterface::QAndroidApplication::context()).callMethod<jboolean>("showAndroidFileDialog", "(ZZ)Z", existing, createNew);
}
int openFileDescriptorFromAndroidContentUrl(const QString &url, const QString &mode)
{
return QtAndroid::androidActivity().callMethod<jint>("openFileDescriptorFromAndroidContentUri", "(Ljava/lang/String;Ljava/lang/String;)I",
QAndroidJniObject::fromString(url).object<jstring>(), QAndroidJniObject::fromString(mode).object<jstring>());
return QJniObject(QNativeInterface::QAndroidApplication::context())
.callMethod<jint>("openFileDescriptorFromAndroidContentUri", "(Ljava/lang/String;Ljava/lang/String;)I",
QJniObject::fromString(url).object<jstring>(), QJniObject::fromString(mode).object<jstring>());
}
void writeToAndroidLog(QtMsgType type, const QMessageLogContext &context, const QString &msg)
@ -102,19 +102,20 @@ void setupAndroidSpecifics()
static void onAndroidError(JNIEnv *, jobject, jstring message)
{
QMetaObject::invokeMethod(
QtGui::controllerForAndroid, "newNotification", Qt::QueuedConnection, Q_ARG(QString, QAndroidJniObject::fromLocalRef(message).toString()));
QtGui::controllerForAndroid, "newNotification", Qt::QueuedConnection, Q_ARG(QString, QJniObject::fromLocalRef(message).toString()));
}
static void onAndroidFileDialogAccepted(JNIEnv *, jobject, jstring fileName, jboolean existing, jboolean createNew)
{
QMetaObject::invokeMethod(QtGui::controllerForAndroid, "handleFileSelectionAccepted", Qt::QueuedConnection,
Q_ARG(QString, QAndroidJniObject::fromLocalRef(fileName).toString()), Q_ARG(bool, existing), Q_ARG(bool, createNew));
Q_ARG(QString, QJniObject::fromLocalRef(fileName).toString()), Q_ARG(bool, existing), Q_ARG(bool, createNew));
}
static void onAndroidFileDialogAcceptedDescriptor(JNIEnv *, jobject, jstring nativeUrl, jstring fileName, jint fileHandle, jboolean existing, jboolean createNew)
static void onAndroidFileDialogAcceptedDescriptor(
JNIEnv *, jobject, jstring nativeUrl, jstring fileName, jint fileHandle, jboolean existing, jboolean createNew)
{
QMetaObject::invokeMethod(QtGui::controllerForAndroid, "handleFileSelectionAcceptedDescriptor", Qt::QueuedConnection,
Q_ARG(QString, QAndroidJniObject::fromLocalRef(nativeUrl).toString()), Q_ARG(QString, QAndroidJniObject::fromLocalRef(fileName).toString()),
Q_ARG(QString, QJniObject::fromLocalRef(nativeUrl).toString()), Q_ARG(QString, QJniObject::fromLocalRef(fileName).toString()),
Q_ARG(int, fileHandle), Q_ARG(bool, existing), Q_ARG(bool, createNew));
}

View File

@ -4,7 +4,9 @@
#include <passwordfile/io/cryptoexception.h>
#include <passwordfile/io/parsingexception.h>
#include <qtutilities/misc/compat.h>
#include <qtutilities/misc/dialogutils.h>
#include <qtutilities/resources/resources.h>
#include <c++utilities/io/nativefilestream.h>
#include <c++utilities/io/path.h>
@ -41,14 +43,15 @@ Controller::Controller(QSettings &settings, const QString &filePath, QObject *pa
#endif
, m_fileOpen(false)
, m_fileModified(false)
, m_useNativeFileDialog(false)
, m_useNativeFileDialog(supportsNativeFileDialog())
, m_filterAsDialog(
#ifdef Q_OS_ANDROID
true
#else
false
#endif
)
)
, m_darkModeEnabled(false)
{
m_fieldModel.setPasswordVisibility(PasswordVisibility::Never);
m_entryFilterModel.setFilterCaseSensitivity(Qt::CaseInsensitive);
@ -73,9 +76,9 @@ Controller::Controller(QSettings &settings, const QString &filePath, QObject *pa
void Controller::setFilePath(const QString &filePath)
{
// get rid of file:// prefix
QStringRef actualFilePath(&filePath);
auto actualFilePath = makeStringView(filePath);
if (filePath.startsWith(QLatin1String("file:"))) {
actualFilePath = filePath.midRef(5);
actualFilePath = midRef(filePath, 5);
}
while (actualFilePath.startsWith(QLatin1String("//"))) {
actualFilePath = actualFilePath.mid(1);
@ -121,6 +124,9 @@ void Controller::init()
if (!m_filePath.isEmpty()) {
load();
}
if (const auto error = QtUtilities::errorMessageForSettings(m_settings); !error.isEmpty()) {
emit settingsError(error);
}
}
void Controller::load()
@ -129,7 +135,7 @@ void Controller::load()
m_file.load();
m_entryModel.setRootEntry(m_file.rootEntry());
if (!m_entryModel.rootEntry()) {
emit fileError(tr("An error occured when opening the file: root element missing"), QStringLiteral("load"));
emit fileError(tr("An error occurred when opening the file: root element missing"), QStringLiteral("load"));
return;
}
setFileOpen(true);
@ -140,7 +146,7 @@ void Controller::load()
} else {
// clear password since the password which has been provided likely wasn't correct
clearPassword();
emit fileError(tr("A crypto error occured when opening the file: ") + QString::fromLocal8Bit(e.what()), QStringLiteral("load"));
emit fileError(tr("A crypto error occurred when opening the file: ") + QString::fromLocal8Bit(e.what()), QStringLiteral("load"));
}
} catch (...) {
if ((m_file.saveOptions() & PasswordFileSaveFlags::Encryption) && m_password.isEmpty()) {
@ -230,7 +236,7 @@ void Controller::save()
#endif
emit fileSaved();
} catch (const CryptoException &e) {
emit fileError(tr("A crypto error occured when saving the file: ") + QString::fromLocal8Bit(e.what()), QStringLiteral("save"));
emit fileError(tr("A crypto error occurred when saving the file: ") + QString::fromLocal8Bit(e.what()), QStringLiteral("save"));
} catch (...) {
emitFileError(tr("saving"));
}
@ -452,14 +458,14 @@ void Controller::emitFileError(const QString &when)
throw;
} catch (const std::ios_base::failure &failure) {
emit fileError(
tr("An IO error occured when %1 the file %2: ").arg(when, m_filePath) + QString::fromLocal8Bit(failure.what()), QStringLiteral("load"));
tr("An IO error occurred when %1 the file %2: ").arg(when, m_filePath) + QString::fromLocal8Bit(failure.what()), QStringLiteral("load"));
} catch (const runtime_error &e) {
emit fileError(tr("An error occured when %1 the file: ").arg(when) + QString::fromLocal8Bit(e.what()), QStringLiteral("save"));
emit fileError(tr("An error occurred when %1 the file: ").arg(when) + QString::fromLocal8Bit(e.what()), QStringLiteral("save"));
} catch (const exception &e) {
emit fileError(tr("An unknown exception occured when %1 the file %2: ").arg(when, m_filePath) + QString::fromLocal8Bit(e.what()),
emit fileError(tr("An unknown exception occurred when %1 the file %2: ").arg(when, m_filePath) + QString::fromLocal8Bit(e.what()),
QStringLiteral("load"));
} catch (...) {
emit fileError(tr("An unknown error occured when %1 the file %2.").arg(when, m_filePath), QStringLiteral("load"));
emit fileError(tr("An unknown error occurred when %1 the file %2.").arg(when, m_filePath), QStringLiteral("load"));
}
}
@ -483,11 +489,19 @@ void Controller::setUseNativeFileDialog(bool useNativeFileDialog)
void Controller::setEntryFilter(const QString &filter)
{
#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0))
const auto previousFilter(m_entryFilterModel.filterRegularExpression().pattern());
#else
const auto previousFilter(m_entryFilterModel.filterRegExp().pattern());
#endif
if (filter == previousFilter) {
return;
}
#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0))
m_entryFilterModel.setFilterRegularExpression(filter);
#else
m_entryFilterModel.setFilterRegExp(filter);
#endif
emit entryFilterChanged(filter);
if (previousFilter.isEmpty() != filter.isEmpty()) {
emit hasEntryFilterChanged(!filter.isEmpty());

View File

@ -44,6 +44,7 @@ class Controller : public QObject {
Q_PROPERTY(QUndoStack *undoStack READ undoStack NOTIFY undoStackChanged)
Q_PROPERTY(QString undoText READ undoText NOTIFY undoTextChanged)
Q_PROPERTY(QString redoText READ redoText NOTIFY redoTextChanged)
Q_PROPERTY(bool darkModeEnabled READ isDarkModeEnabled WRITE setDarkModeEnabled NOTIFY darkModeEnabledChanged)
public:
explicit Controller(QSettings &settings, const QString &filePath = QString(), QObject *parent = nullptr);
@ -85,6 +86,9 @@ public:
QUndoStack *undoStack();
QString undoText() const;
QString redoText() const;
Io::PasswordFileSaveFlags prepareSaving();
bool isDarkModeEnabled() const;
void setDarkModeEnabled(bool darkModeEnabled);
public Q_SLOTS:
void init();
@ -101,7 +105,6 @@ public Q_SLOTS:
void handleFileSelectionCanceled();
void undo();
void redo();
Io::PasswordFileSaveFlags prepareSaving();
QString computeFileSummary();
Q_SIGNALS:
@ -128,6 +131,8 @@ Q_SIGNALS:
void undoStackChanged(QUndoStack *undoStack);
void undoTextChanged(const QString &undoText);
void redoTextChanged(const QString &redoText);
void settingsError(const QString &errorMessage);
void darkModeEnabledChanged(bool darkModeEnabled);
private Q_SLOTS:
void handleEntriesRemoved(const QModelIndex &parentIndex, int first, int last);
@ -159,6 +164,7 @@ private:
bool m_fileModified;
bool m_useNativeFileDialog;
bool m_filterAsDialog;
bool m_darkModeEnabled;
};
inline QModelIndex Controller::ensureSourceEntryIndex(const QModelIndex &entryIndexMaybeFromFilterModel) const
@ -296,12 +302,20 @@ inline QModelIndex Controller::filterEntryIndex(const QModelIndex &entryIndex) c
inline QString Controller::entryFilter() const
{
#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0))
return m_entryFilterModel.filterRegularExpression().pattern();
#else
return m_entryFilterModel.filterRegExp().pattern();
#endif
}
inline bool Controller::hasEntryFilter() const
{
#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0))
return !m_entryFilterModel.filterRegularExpression().pattern().isEmpty();
#else
return !m_entryFilterModel.filterRegExp().isEmpty();
#endif
}
inline bool Controller::filterAsDialog() const
@ -336,6 +350,18 @@ inline QString Controller::redoText() const
#endif
}
inline bool Controller::isDarkModeEnabled() const
{
return m_darkModeEnabled;
}
inline void Controller::setDarkModeEnabled(bool darkModeEnabled)
{
if (darkModeEnabled != m_darkModeEnabled) {
emit darkModeEnabledChanged(m_darkModeEnabled = darkModeEnabled);
}
}
inline void Controller::undo()
{
#ifdef PASSWORD_MANAGER_UNDO_SUPPORT

View File

@ -10,26 +10,29 @@
// enable inline helper functions for Qt Quick provided by qtutilities
#define QT_UTILITIES_GUI_QTQUICK
// ensure QGuiApplication is defined before resources.h for desktop file name
#ifdef PASSWORD_MANAGER_GUI_QTWIDGETS
#include <QApplication>
using App = QApplication;
#else
#include <QGuiApplication>
using App = QGuiApplication;
#endif
#include <qtutilities/misc/desktoputils.h>
#include <qtutilities/resources/qtconfigarguments.h>
#include <qtutilities/resources/resources.h>
#include <qtutilities/settingsdialog/qtsettings.h>
#include <passwordfile/util/openssl.h>
#include <QGuiApplication>
#include <QDebug>
#include <QIcon>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QSettings>
#include <QTextCodec>
#include <QtQml>
#ifdef Q_OS_ANDROID
#include <QDebug>
#include <QDirIterator>
#endif
#ifdef PASSWORD_MANAGER_GUI_QTWIDGETS
#include <QApplication>
#endif
#include <cstdlib>
using namespace CppUtilities;
using namespace Util;
@ -43,64 +46,75 @@ int runQuickGui(int argc, char *argv[], const QtConfigArguments &qtConfigArgs, c
setupAndroidSpecifics();
#endif
// work around kirigami plugin trying to be clever
if (!qEnvironmentVariableIsSet("XDG_CURRENT_DESKTOP")) {
qputenv("XDG_CURRENT_DESKTOP", QByteArray("please don't override my settings"));
}
// init OpenSSL
OpenSsl::init();
// init application
SET_QT_APPLICATION_INFO;
QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#ifdef PASSWORD_MANAGER_GUI_QTWIDGETS
QApplication application(argc, argv);
auto application = App(argc, argv);
QObject::connect(&application, &QCoreApplication::aboutToQuit, &OpenSsl::clean);
// restore Qt settings
auto qtSettings = QtUtilities::QtSettings();
auto settings = QtUtilities::getSettings(QStringLiteral(PROJECT_NAME));
if (auto settingsError = QtUtilities::errorMessageForSettings(*settings); !settingsError.isEmpty()) {
qDebug() << settingsError;
}
qtSettings.restore(*settings);
qtSettings.apply();
// create controller and handle dark mode
// note: Not handling changes of the dark mode setting dynamically yet because it does not work with Kirigami.
// It looks like Kirigami does not follow the QCC2 theme (the Material.theme/Material.theme settings) but
// instead uses colors based on the initial palette. Not sure how to toggle Kirigami's palette in accordance
// with the QCC2 theme. Hence this code is disabled via APPLY_COLOR_SCHEME_DYNAMICALLY for now.
auto controller = Controller(*settings, file);
#ifdef APPLY_COLOR_SCHEME_DYNAMICALLY
QtUtilities::onDarkModeChanged(
[&qtSettings, &controller](bool isDarkModeEnabled) {
qtSettings.reapplyDefaultIconTheme(isDarkModeEnabled);
controller.setDarkModeEnabled(isDarkModeEnabled);
},
&controller);
#else
QGuiApplication application(argc, argv);
const auto isDarkModeEnabled = QtUtilities::isDarkModeEnabled().value_or(false);
qtSettings.reapplyDefaultIconTheme(isDarkModeEnabled);
controller.setDarkModeEnabled(isDarkModeEnabled);
#endif
// apply settings specified via command line args
qtConfigArgs.applySettings();
qtConfigArgs.applySettings(qtSettings.hasCustomFont());
qtConfigArgs.applySettingsForQuickGui();
// assume we're bundling breeze icons
if (QIcon::themeName().isEmpty()) {
QIcon::setThemeName(QStringLiteral("breeze"));
}
// log resource information
#if defined(Q_OS_ANDROID) && defined(CPP_UTILITIES_DEBUG_BUILD)
qDebug() << "Using icon theme" << QIcon::themeName();
qDebug() << "Icon theme search paths" << QIcon::themeSearchPaths();
qDebug() << "Resources:";
QDirIterator it(QStringLiteral(":/"), QDirIterator::Subdirectories);
while (it.hasNext()) {
qDebug() << it.next();
}
#endif
// load settings from configuration file
QSettings settings(QSettings::IniFormat, QSettings::UserScope, QStringLiteral(PROJECT_NAME));
// load translations
LOAD_QT_TRANSLATIONS;
// init Quick GUI
QQmlApplicationEngine engine;
Controller controller(settings, file);
// init QML engine
auto engine = QQmlApplicationEngine();
#ifdef Q_OS_ANDROID
registerControllerForAndroid(&controller);
#endif
auto *const context(engine.rootContext());
auto *const context = engine.rootContext();
context->setContextProperty(QStringLiteral("nativeInterface"), &controller);
context->setContextProperty(QStringLiteral("app"), &application);
context->setContextProperty(QStringLiteral("description"), QStringLiteral(APP_DESCRIPTION));
context->setContextProperty(QStringLiteral("dependencyVersions"), QStringList(DEPENCENCY_VERSIONS));
engine.load(QUrl(QStringLiteral("qrc:/qml/main.qml")));
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
const auto importPaths = qEnvironmentVariable(PROJECT_VARNAME_UPPER "_QML_IMPORT_PATHS").split(QChar(':'));
for (const auto &path : importPaths) {
engine.addImportPath(path);
}
#endif
// run event loop
QObject::connect(&application, &QCoreApplication::aboutToQuit, &OpenSsl::clean);
// load main QML file; run event loop or exit if it cannot be loaded
const auto mainUrl = QUrl(QStringLiteral("qrc:/qml/main.qml"));
QObject::connect(
&engine, &QQmlApplicationEngine::objectCreated, &application,
[&mainUrl](QObject *obj, const QUrl &objUrl) {
if (!obj && objUrl == mainUrl) {
QCoreApplication::exit(EXIT_FAILURE);
}
},
Qt::QueuedConnection);
engine.load(mainUrl);
return application.exec();
}
} // namespace QtGui

View File

@ -0,0 +1,50 @@
<?xml version="1.0"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="@META_ANDROID_PACKAGE_NAME@"
android:label="@META_APP_NAME@"
android:installLocation="auto"
android:versionName="@META_APP_VERSION@"
android:versionCode="@META_VERSION_MAJOR@">
<!-- %%INSERT_PERMISSIONS -->
<!-- %%INSERT_FEATURES -->
<supports-screens
android:anyDensity="true"
android:largeScreens="true"
android:normalScreens="true"
android:smallScreens="true" />
<application
android:name="org.qtproject.qt.android.bindings.QtApplication"
android:icon="@mipmap/ic_launcher"
android:hardwareAccelerated="true"
android:label="@META_APP_NAME@"
android:requestLegacyExternalStorage="true"
android:allowNativeHeapPointerTagging="false"
android:allowBackup="true"
android:fullBackupOnly="false">
<activity
android:name="org.martchus.passwordmanager.Activity"
android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation|mcc|mnc|density"
android:label="@META_APP_NAME@"
android:launchMode="singleTop"
android:screenOrientation="unspecified"
android:exported="true"
android:theme="@style/AppTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.lib_name"
android:value="-- %%INSERT_APP_LIB_NAME%% --" />
<meta-data
android:name="android.app.arguments"
android:value="-- %%INSERT_APP_ARGUMENTS%% --" />
<meta-data
android:name="android.app.extract_android_style"
android:value="minimal" />
<meta-data
android:name="android.app.splash_screen_drawable"
android:resource="@drawable/splash" />
</activity>
</application>
</manifest>

View File

@ -8,6 +8,8 @@
<file alias="AboutDialog.qml">../qml/AboutDialog.qml</file>
<file alias="PasswordDialog.qml">../qml/PasswordDialog.qml</file>
<file alias="EntriesPage.qml">../qml/EntriesPage.qml</file>
<file alias="EntryDelegate.qml">../qml/EntryDelegate.qml</file>
<file alias="FieldsPage.qml">../qml/FieldsPage.qml</file>
<file alias="FieldsDelegate.qml">../qml/FieldsDelegate.qml</file>
</qresource>
</RCC>

5
resources/qml5.qrc Normal file
View File

@ -0,0 +1,5 @@
<RCC>
<qresource prefix="/qml">
<file alias="FileDialog.qml">../qml/FileDialog5.qml</file>
</qresource>
</RCC>

5
resources/qml6.qrc Normal file
View File

@ -0,0 +1,5 @@
<RCC>
<qresource prefix="/qml">
<file alias="FileDialog.qml">../qml/FileDialog6.qml</file>
</qresource>
</RCC>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff