Compare commits

...

158 Commits

Author SHA1 Message Date
Martchus 03eeecac9c Update translations 2024-06-01 23:40:37 +02:00
Martchus dff8cca664 Make column sizes in table view configurable 2024-06-01 23:37:38 +02:00
Martchus 297f34d5c6 Fix clazy warning about calling `front()` on temporary 2024-06-01 23:37:38 +02:00
Martchus 8955d5c27b Use `ANDROID_PKGCONFIG` environment variable in configuration example for Android 2024-05-30 18:48:38 +02:00
Martchus 9574ed8b0e Update server domain 2024-05-30 18:47:20 +02:00
Martchus c5a658e2c2 Update translations 2024-05-21 20:38:52 +02:00
Martchus 760f764b13 Re-evaluate default icon theme on palette changes 2024-05-21 20:38:32 +02:00
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
Martchus 68ac92d5db Use ninja in build example 2020-10-10 01:05:32 +02:00
raj292 9b0d4dc092
Add link to wikipedia on README.md 2020-10-01 10:25:32 +02:00
Martchus c3f07d08b6 Use setFilterRegularExpression() in Qt >= 5.12
The issue mentioned in the last commit was actually just about case
sensitivity and using QRegularExpression::CaseInsensitiveOption fixes it.
2020-09-18 17:12:01 +02:00
Martchus 85a816e444 Don't use setFilterRegularExpression() in Qt 5
Otherwise showing child nodes when their parent matches does not work with
Qt 5. Interestingly it *does* work with Qt 6. That's especially strange
because the code of the filter model responsible for showing child nodes
when their parents match should work completely independent of the
particular filter being used.
2020-09-15 18:14:13 +02:00
Martchus aeff15682f Remove overspecification of Qt version in documentation and comments 2020-09-04 01:21:28 +02:00
Martchus 5e8c230cfb Support Qt 6 (commit 174154b) 2020-09-04 00:54:34 +02:00
Martchus ce0df2d8a3 Improve icon
* Add 256x256 application icon
* Remove PNG_ICON_NO_CROP which is no longer required with c++utilities 5.6
2020-08-11 23:24:21 +02:00
Martchus 5a00643e4e Fix typo in German translation 2020-05-03 23:05:35 +02:00
Martchus 4ccc8058b2 Update translations 2020-05-03 23:05:22 +02:00
Martchus a290294c25 Show password prompt in any error case in Qt Quick GUI
It might happen that an empty/incorrect password does not lead to
a crypto error. Then the data is nevertheless wrongly decrypted and
trying again with the correct password might help.

Not using PasswordFile::isEncryptionUsed() here like in the Qt Widgets
GUI for better/easier integration with Android's storage access
framework.
2020-03-29 00:55:57 +01:00
Martchus 9ff25a27b7 Avoid bad contrast in password dialog of Quick GUI with dark themes 2020-03-29 00:03:18 +01:00
Martchus b98483ca34 Avoid using previous password when creating new file in Qt Quick GUI 2020-03-28 23:27:47 +01:00
Martchus 1c6230dbeb Fix showing IO error in Qt Quick GUI
The order of the catch handlers must be adjusted because
std::ios_base::failure is derived from std::runtime_error.
2020-03-28 23:16:12 +01:00
Martchus c12f63dac1 Use Q_SIGNALS instead of signals 2020-03-09 18:46:47 +01:00
Martchus 53d3e71d36 Don't use lower-case Qt macros 2020-03-08 14:09:56 +01:00
Martchus 9736a0fa61 Update translations 2020-02-14 17:38:25 +01:00
Martchus 78e4afb387 Improve consistency of error messages 2020-02-14 17:38:17 +01:00
Marius Kittler 98514ab8ca Update version 2019-11-07 19:09:35 +01:00
Marius Kittler b7cb0d85c3 Add licensing info for Windows distribution 2019-11-07 19:08:52 +01:00
Martchus 7ac57bb5a1 Use aboutToQuit() signal for cleanup-code
As recommended by the Qt documentation
2019-09-14 19:38:06 +02:00
Marius Kittler b6acb55176 Remove unused StringVector 2019-09-04 18:35:59 +02:00
Martchus b220301bb6 Fix typo in initiategui.cpp filename 2019-08-22 01:11:17 +02:00
Martchus cddfebab8b Improve Android build
Use new features in qtutilities to prevent duplication of
version and other meta-info in manifest file.
2019-08-22 01:10:35 +02:00
Martchus 73748692b7 Prevent split screen warning under Android 2019-07-22 21:11:33 +02:00
Martchus 53aa56108c Don't abuse organization domain for website in AboutDialog 2019-07-20 20:16:44 +02:00
Martchus ad5ef353ff Use viewport to map context menu position 2019-07-20 18:44:20 +02:00
Martchus 31de4955e6 Don't use QCursor::pos() to position context menus
This won't work under Wayland.
2019-07-20 18:23:43 +02:00
Martchus 318542c658 Handle that QClipboard::mimeData() might return nullptr in Wayland 2019-07-20 18:14:41 +02:00
Martchus d7e7f9ecfa Set project() on top-level
See https://github.com/Martchus/cpp-utilities/pull/15
2019-07-20 18:10:54 +02:00
Martchus e21c97a37e Update translations 2019-07-07 15:25:01 +02:00
Martchus e820ebe71f Fix lupdate problem for real
The enum class still seems to be the cause of the
problem so this still needs to be worked around.
2019-07-07 15:24:51 +02:00
Martchus e9196404d6 Add comment for lupdate again
It still seems to be required for some reason
2019-07-07 11:51:11 +02:00
Martchus e1afd5504e Update version to 4.1.0 2019-07-06 17:24:24 +02:00
Martchus c92efb3c88 Update Android build instructions 2019-07-06 17:19:54 +02:00
Marius Kittler 0e2af9ff2e Add "Save as" option in Quick GUI 2019-07-06 17:19:11 +02:00
Martchus dd77f56a40 Adapt Android specific code to c++utilities v5 2019-07-03 00:38:29 +02:00
Martchus 27f90b7008 Ensure Qt specific config is included 2019-06-16 15:56:34 +02:00
Martchus d606b8a6cb Adapt to changes in c++utilities 2019-06-12 21:02:57 +02:00
Martchus f16dbc06d1 Adapt to changes in c++utilities 2019-06-10 22:44:59 +02:00
Marius Kittler f863fb474c Adapt to c++utilities v5 2019-05-04 22:25:56 +02:00
Martchus f113197b62 Update version to 4.0.2 2019-04-21 22:28:03 +02:00
Martchus 8e1ee51e82 Adjust QTableView row height to fit content 2019-03-15 23:09:31 +01:00
Martchus 443e443214 Update README.md
* Cover current problems with CMake and latest Android NDK
* Add download section from Tag Editor
* Improve command for keystore creation
2019-03-03 22:32:57 +01:00
Martchus d196d4c983 Remove superseded test code 2019-03-03 01:35:44 +01:00
Martchus e5121aa8fc Fix verison in AndroidManifest.xml 2019-03-01 15:39:50 +01:00
Marius Kittler 37f9a65c97 Apply cmake-format 2019-02-06 17:58:29 +01:00
Martchus 481cc92d28 Update translations 2019-02-05 00:32:14 +01:00
Martchus b6952c3772 Make "Change password for" translatable 2019-02-05 00:32:06 +01:00
Martchus daa9251b77 Fix ugly bullet points under Android on PasswordEchoOnEdit 2019-02-05 00:31:27 +01:00
Martchus be2da373b8 Add debug printing for Android
Under Android we can not just use Gammaray so let's print
resources and QIcon settings for debug builds.
2019-02-05 00:30:34 +01:00
Martchus 065bded841 Ensure Kirigami2 does not mess with our settings 2019-02-05 00:29:21 +01:00
Martchus f577b5acd7 Remove ugly icon and bold font for encryption note 2019-02-05 00:28:35 +01:00
Martchus 3126bdef08 Document 2nd CMake invocation for using NDK toolchain file 2019-02-05 00:28:11 +01:00
Martchus 9002602895 Quick GUI: Show buttons to copy when editing field 2019-02-05 00:27:18 +01:00
Marius Kittler dcb2253903 Increment patch version 4.0.0 -> 4.0.1 2019-01-21 18:16:17 +01:00
Marius Kittler 5492e67496 Quick GUI: Enable wrapping in details dialog 2019-01-21 18:15:44 +01:00
Marius Kittler 5d36b93b69 Quick GUI: Prevent context menu on last, empty row 2019-01-21 18:15:21 +01:00
Marius Kittler 7fee73cf29 Quick GUI: Make '+' to append field clickable 2019-01-21 18:14:48 +01:00
Martchus 5b9e63785a Always show debug output when opening native fd 2019-01-12 02:51:13 +01:00
54 changed files with 14094 additions and 1755 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,98 +1,57 @@
cmake_minimum_required(VERSION 3.1.0 FATAL_ERROR)
cmake_minimum_required(VERSION 3.17.0 FATAL_ERROR)
# meta data
set(META_PROJECT_NAME passwordmanager)
# set meta data
project(passwordmanager)
set(META_PROJECT_NAME ${PROJECT_NAME})
set(META_PROJECT_VARNAME password_manager)
set(META_PROJECT_TYPE application)
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 0)
set(META_VERSION_PATCH 0)
set(META_APP_VERSION ${META_VERSION_MAJOR}.${META_VERSION_MINOR}.${META_VERSION_PATCH})
set(PNG_ICON_NO_CROP ON)
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
)
set(SRC_FILES
cli/cli.cpp
main.cpp
)
set(HEADER_FILES cli/cli.h model/entryfiltermodel.h model/entrymodel.h model/fieldmodel.h)
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(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/initiatequi.cpp
gui/initiategui.cpp
gui/mainwindow.cpp
gui/passwordgeneratordialog.cpp
gui/stacksupport.cpp
gui/undocommands.cpp
resources/icons.qrc
)
set(WIDGETS_UI_FILES
gui/mainwindow.ui
gui/passwordgeneratordialog.ui
)
resources/icons.qrc)
set(WIDGETS_UI_FILES gui/mainwindow.ui gui/passwordgeneratordialog.ui)
set(QML_HEADER_FILES
quickgui/controller.h
quickgui/initiatequick.h
)
set(QML_SRC_FILES
quickgui/controller.cpp
quickgui/initiatequick.cpp
resources/icons.qrc
resources/qml.qrc
)
if(ANDROID)
list(APPEND QML_HEADER_FILES
quickgui/android.h
)
list(APPEND QML_SRC_FILES
quickgui/android.cpp
)
endif()
set(QML_HEADER_FILES quickgui/controller.h quickgui/initiatequick.h)
set(QML_SRC_FILES quickgui/controller.cpp quickgui/initiatequick.cpp resources/icons.qrc resources/qml.qrc)
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
)
set(TS_FILES translations/${META_PROJECT_NAME}_de_DE.ts translations/${META_PROJECT_NAME}_en_US.ts)
set(ICON_FILES
resources/icons/hicolor/scalable/apps/${META_PROJECT_NAME}.svg
)
set(ICON_FILES resources/icons/hicolor/scalable/apps/${META_PROJECT_NAME}.svg)
set(DOC_FILES
README.md
)
set(DOC_FILES README.md)
set(REQUIRED_ICONS
application-exit
@ -138,6 +97,7 @@ set(REQUIRED_ICONS
insert-text
list-add
list-remove
password-copy
password-generate
password-show-off
password-show-on
@ -145,66 +105,118 @@ set(REQUIRED_ICONS
preferences-desktop-locale
qtcreator
search
story-editor
view-list-details-symbolic
system-file-manager
system-run
system-search
window-close
)
username-copy
window-close)
# find c++utilities
find_package(c++utilities 4.10.0 REQUIRED)
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 5.11.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 4.0.0 REQUIRED)
find_package(passwordfile${CONFIGURATION_PACKAGE_SUFFIX} 5.0.0 REQUIRED)
use_password_file()
# require qt 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)
# 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)
option(ENABLE_UNDO_SUPPORT_FOR_QUICK_GUI "enables with undo/redo support for the Qt Quick GUI (requires Qt Widgets)" ON)
if(ENABLE_UNDO_SUPPORT_FOR_QUICK_GUI)
if (ENABLE_UNDO_SUPPORT_FOR_QUICK_GUI)
list(APPEND ADDITIONAL_QT_MODULES Widgets)
list(APPEND META_PUBLIC_COMPILE_DEFINITIONS ${META_PROJECT_VARNAME_UPPER}_ENABLE_UNDO_SUPPORT_FOR_QUICK_GUI)
list(APPEND QML_HEADER_FILES
gui/stacksupport.h
gui/undocommands.h
)
list(APPEND QML_SRC_FILES
gui/stacksupport.cpp
gui/undocommands.cpp
)
endif()
endif()
list(APPEND QML_HEADER_FILES gui/stacksupport.h gui/undocommands.h)
list(APPEND QML_SRC_FILES gui/stacksupport.cpp gui/undocommands.cpp)
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)
endif()
# 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)
if (WIDGETS_GUI OR QUICK_GUI)
include(QtGuiConfig)
include(QtConfig)
endif()
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

185
README.md
View File

@ -1,10 +1,10 @@
# Password Manager
A simple password manager with Qt 5 GUI and command-line interface using AES-256-CBC encryption via OpenSSL.
A simple [password manager](https://en.wikipedia.org/wiki/Password_manager) with Qt GUI and command-line interface using AES-256-CBC encryption via OpenSSL.
## 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 with Qt 5 GUI and command-line interface using AES-256
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
@ -38,125 +38,142 @@ Note that some of the mentioned points are actually implemented the underlying l
[c++utilities](http://github.com/Martchus/cpp-utilities), [qtutilities](http://github.com/Martchus/qtutilities)
and [passwordfile](http://github.com/Martchus/passwordfile).
## Download / binary repository
I currently provide packages for Arch Linux and Windows. Sources for those packages can be found in a
separate [repository](https://github.com/Martchus/PKGBUILDs). For binaries checkout the release section
on GitHub or my [website](http://martchus.no-ip.biz/website/page.php?name=programming).
## Download
### Source
See the release section on GitHub.
### Packages and binaries
* 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)
* there is also a [binary repository](https://martchus.dyn.f3l.de/repo/arch/ownstuff)
* Tumbleweed, Leap, Fedora
* 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
* 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)
## 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: core gui widgets
* When building with support for the experimental Qt Quick GUI, the following Qt/KDE modules are required: core gui qml quick quickcontrols2 kirigami
* 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 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) g++ or clang, the required Qt 5 modules and CMake. 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
make install -j$(nproc)
"$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
#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.keystore -alias keystore_alias -keyalg RSA -validity 999999
keytool -importkeystore -srckeystore keystore_alias.keystore -destkeystore keystore_alias.keystore -deststoretype pkcs12 # FIXME: make this in one step
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_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_PKGCONFIG" \
-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"
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" \
-Diconv_DYNAMIC_INCLUDE_DIR="$other_libs_include" \
-Diconv_STATIC_INCLUDE_DIR="$other_libs_include" \
-Dcrypto_DYNAMIC_INCLUDE_DIR="$other_libs_include" \
-Dcrypto_STATIC_INCLUDE_DIR="$other_libs_include" \
-Dboost_iostreams_DYNAMIC_INCLUDE_DIR="$other_libs_include" \
-Dboost_iostreams_STATIC_INCLUDE_DIR="$other_libs_include" \
-DCLANG_FORMAT_ENABLED=ON \
-DUSE_NATIVE_FILE_BUFFER=ON \
-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" \
$SOURCES/subdirs/$_reponame
make passwordmanager_apk -j$(nproc)
make passwordmanager_deploy_apk # install app on USB-connected phone
# 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 5, 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).
* 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.
### 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`
### Building without Qt 5 GUI
### 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.
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,50 +0,0 @@
<?xml version="1.0"?>
<manifest package="org.martchus.passwordmanager" xmlns:android="http://schemas.android.com/apk/res/android" android:versionName="4.0.0" android:versionCode="4" android:installLocation="auto">
<application android:icon="@mipmap/ic_launcher" android:name="org.qtproject.qt5.android.bindings.QtApplication" android:label="@string/app_name">
<activity
android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation"
android:name="org.martchus.passwordmanager.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>

View File

@ -1,24 +1,25 @@
buildscript {
repositories {
google()
jcenter()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.2.0'
classpath 'com.android.tools.build:gradle:7.4.1'
}
}
repositories {
google()
jcenter()
mavenCentral()
}
apply plugin: 'com.android.application'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:support-v4:27.1.0'
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
implementation "androidx.documentfile:documentfile:1.0.1"
implementation 'androidx.core:core:1.10.1'
}
android {
@ -26,7 +27,7 @@ android {
* The following variables:
* - androidBuildToolsVersion,
* - androidCompileSdkVersion
* - qt5AndroidDir - holds the path to qt android files
* - qtAndroidDir - holds the path to qt android files
* needed to build any Qt application
* on Android.
*
@ -35,24 +36,48 @@ android {
* Changing them manually might break the compilation!
*******************************************************/
compileSdkVersion androidCompileSdkVersion.toInteger()
compileSdkVersion androidCompileSdkVersion
buildToolsVersion androidBuildToolsVersion
ndkVersion androidNdkVersion
// Extract native libraries from the APK
packagingOptions.jniLibs.useLegacyPackaging true
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']
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

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

View File

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

View File

@ -7,21 +7,21 @@ 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_PICK_DIR = 1;
private final int REQUEST_CODE_PICK_EXISTING_FILE = 2;
private final int REQUEST_CODE_PICK_NEW_FILE = 3;
private final int REQUEST_CODE_OPEN_EXISTING_FILE = 1;
private final int REQUEST_CODE_CREATE_NEW_FILE = 2;
private final int REQUEST_CODE_SAVE_FILE_AS = 3;
/*!
* \brief Shows the native Android file dialog. Results are handled in onActivityResult().
*/
public boolean showAndroidFileDialog(boolean existing) {
public boolean showAndroidFileDialog(boolean existing, boolean createNew) {
String action = existing ? Intent.ACTION_OPEN_DOCUMENT : Intent.ACTION_CREATE_DOCUMENT;
int requestCode = existing ? REQUEST_CODE_PICK_EXISTING_FILE : REQUEST_CODE_PICK_NEW_FILE;
int requestCode = existing ? REQUEST_CODE_OPEN_EXISTING_FILE : (createNew ? REQUEST_CODE_CREATE_NEW_FILE : REQUEST_CODE_SAVE_FILE_AS);
Intent intent = new Intent(action);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("*/*");
@ -51,9 +51,12 @@ public class Activity extends QtActivity {
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case REQUEST_CODE_PICK_EXISTING_FILE:
case REQUEST_CODE_PICK_NEW_FILE:
boolean existingFile = requestCode == REQUEST_CODE_PICK_EXISTING_FILE;
case REQUEST_CODE_OPEN_EXISTING_FILE:
case REQUEST_CODE_CREATE_NEW_FILE:
case REQUEST_CODE_SAVE_FILE_AS:
boolean createNew = requestCode == REQUEST_CODE_CREATE_NEW_FILE;
boolean existingFile = requestCode == REQUEST_CODE_OPEN_EXISTING_FILE;
boolean saveAs = requestCode == REQUEST_CODE_SAVE_FILE_AS;
if (resultCode != RESULT_OK) {
onAndroidFileDialogRejected();
@ -65,7 +68,7 @@ public class Activity extends QtActivity {
try {
DocumentFile file = DocumentFile.fromSingleUri(this, uri);
ParcelFileDescriptor fd = getContentResolver().openFileDescriptor(file.getUri(), existingFile ? "r" : "wt");
onAndroidFileDialogAcceptedDescriptor(file.getUri().toString(), file.getName(), fd.detachFd(), existingFile);
onAndroidFileDialogAcceptedDescriptor(file.getUri().toString(), file.getName(), fd.detachFd(), existingFile, createNew);
} catch (FileNotFoundException e) {
onAndroidError("Failed to find selected file.");
}
@ -74,7 +77,7 @@ public class Activity extends QtActivity {
String fileName = data.getDataString();
if (fileName != null) {
onAndroidFileDialogAccepted(fileName, existingFile);
onAndroidFileDialogAccepted(fileName, existingFile, createNew);
return;
}
onAndroidError("Failed to read result from Android's file dialog.");
@ -85,7 +88,7 @@ public class Activity extends QtActivity {
}
public static native void onAndroidError(String message);
public static native void onAndroidFileDialogAccepted(String fileName, boolean existing);
public static native void onAndroidFileDialogAcceptedDescriptor(String nativeUrl, String fileName, int fileDescriptor, boolean existing);
public static native void onAndroidFileDialogAccepted(String fileName, boolean existing, boolean createNew);
public static native void onAndroidFileDialogAcceptedDescriptor(String nativeUrl, String fileName, int fileDescriptor, boolean existing, boolean createNew);
public static native void onAndroidFileDialogRejected();
}

View File

@ -5,25 +5,25 @@
#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>
#include <c++utilities/io/ansiescapecodes.h>
#include <c++utilities/io/catchiofailure.h>
#if defined(PLATFORM_UNIX)
#include <unistd.h>
#endif
#include <algorithm>
#include <cstdlib>
#include <functional>
#include <stdexcept>
using namespace std;
using namespace std::placeholders;
using namespace ApplicationUtilities;
using namespace ConversionUtilities;
using namespace EscapeCodes;
using namespace IoUtilities;
using namespace CppUtilities;
using namespace CppUtilities::EscapeCodes;
using namespace Io;
namespace Cli {
@ -92,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!")) {
@ -178,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();
@ -216,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) {
@ -237,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 {
@ -255,15 +262,14 @@ 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 occurred when opening file \"" << file << "\"" << endl;
throw;
} catch (...) {
const char *what = catchIoFailure();
m_o << "IO error occured when opening file \"" << file << "\"" << endl;
throw ios_base::failure(what);
}
} catch (const std::exception &e) {
if (*e.what() != 0) {
@ -307,15 +313,14 @@ 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 occurred when saving file \"" << m_file.path() << "\"" << endl;
throw;
} catch (...) {
const char *what = catchIoFailure();
m_o << "IO error occured when saving file \"" << m_file.path() << "\"" << endl;
throw ios_base::failure(what);
}
} catch (const exception &e) {
if (*e.what() != 0) {
@ -327,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;
@ -341,10 +346,9 @@ void InteractiveCli::createFile(const string &file)
m_file.generateRootEntry();
m_currentEntry = m_file.rootEntry();
m_o << "file \"" << file << "\" created and opened" << endl;
} catch (...) {
const char *what = catchIoFailure();
m_o << "IO error occured when creating file \"" << file << "\"" << endl;
throw ios_base::failure(what);
} catch (const std::ios_base::failure &) {
m_o << "IO error occurred when creating file \"" << file << "\"" << endl;
throw;
}
} catch (const exception &e) {
if (*e.what() != 0) {
@ -398,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;
@ -413,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()) {
@ -456,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;
@ -479,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;
@ -499,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 {
@ -519,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 {
@ -553,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;
@ -577,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;
@ -587,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) {
@ -649,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;
@ -659,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;
@ -736,7 +740,7 @@ void InteractiveCli::quit()
}
}
string InteractiveCli::askForPassphrase(bool confirm)
std::string InteractiveCli::askForPassphrase(bool confirm)
{
if (confirm) {
m_o << "enter new passphrase: ";
@ -744,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()) {
@ -757,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,11 +12,7 @@
#include <istream>
#include <ostream>
#include <string>
#include <vector>
namespace ApplicationUtilities {
using StringVector = std::vector<std::string>;
}
#include <string_view>
namespace Io {
class Entry;
@ -27,7 +23,7 @@ namespace Cli {
class InputMuter {
public:
InputMuter();
explicit InputMuter();
~InputMuter();
private:
@ -43,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;
};

74
gui/initiategui.cpp Normal file
View File

@ -0,0 +1,74 @@
#include "./initiategui.h"
#include "./gui/mainwindow.h"
#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>
#include <qtutilities/settingsdialog/qtsettings.h>
#include <passwordfile/util/openssl.h>
#include <QFile>
#include <QMessageBox>
#include <QSettings>
using namespace CppUtilities;
using namespace QtUtilities;
using namespace Util;
namespace QtGui {
int runWidgetsGui(int argc, char *argv[], const QtConfigArguments &qtConfigArgs, const QString &file)
{
SET_QT_APPLICATION_INFO;
// init OpenSSL
OpenSsl::init();
// init application
auto application = QApplication(argc, argv);
QObject::connect(&application, &QCoreApplication::aboutToQuit, &OpenSsl::clean);
// restore Qt 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
qtConfigArgs.applySettings(qtSettings.hasCustomFont());
LOAD_QT_TRANSLATIONS;
// init widgets GUI
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
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

@ -5,13 +5,13 @@
QT_FORWARD_DECLARE_CLASS(QString)
namespace ApplicationUtilities {
namespace CppUtilities {
class QtConfigArguments;
}
namespace QtGui {
int runWidgetsGui(int argc, char *argv[], const ApplicationUtilities::QtConfigArguments &qtConfigArgs, const QString &file);
int runWidgetsGui(int argc, char *argv[], const CppUtilities::QtConfigArguments &qtConfigArgs, const QString &file);
}
#endif // INITIATE_H

View File

@ -1,51 +0,0 @@
#include "./initiategui.h"
#include "./gui/mainwindow.h"
#include "resources/config.h"
#include <qtutilities/resources/importplugin.h>
#include <qtutilities/resources/qtconfigarguments.h>
#include <qtutilities/resources/resources.h>
#include <qtutilities/settingsdialog/qtsettings.h>
#include <QApplication>
#include <QFile>
#include <QSettings>
using namespace ApplicationUtilities;
using namespace Dialogs;
namespace QtGui {
int runWidgetsGui(int argc, char *argv[], const QtConfigArguments &qtConfigArgs, const QString &file)
{
SET_QT_APPLICATION_INFO;
// init application
QApplication a(argc, argv);
// 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);
qtSettings.apply();
// apply settings specified via command line args
qtConfigArgs.applySettings(qtSettings.hasCustomFont());
LOAD_QT_TRANSLATIONS;
// init widgets GUI
MainWindow w(settings, &qtSettings);
w.show();
if (!file.isEmpty()) {
w.openFile(file);
}
// start event loop
int res = a.exec();
// save Qt settings
qtSettings.save(settings);
return res;
}
} // namespace QtGui

View File

@ -22,7 +22,6 @@
#include <qtutilities/settingsdialog/settingsdialog.h>
#include <c++utilities/conversion/stringconversion.h>
#include <c++utilities/io/catchiofailure.h>
#include <c++utilities/io/path.h>
#include <QActionGroup>
@ -43,10 +42,9 @@
#include <stdexcept>
using namespace std;
using namespace IoUtilities;
using namespace CppUtilities;
using namespace QtUtilities;
using namespace Io;
using namespace Dialogs;
using namespace MiscUtils;
namespace QtGui {
@ -108,10 +106,38 @@ 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 Updates the columns sizing according to the checked action/option.
*/
void MainWindow::updateColumnSizing()
{
auto *const header = m_ui->tableView->horizontalHeader();
if (m_ui->actionColumnWidthCustom->isChecked()) {
header->setStretchLastSection(true);
header->setCascadingSectionResizes(true);
header->setSectionResizeMode(QHeaderView::Interactive);
} else {
header->setSectionResizeMode(QHeaderView::Stretch);
}
}
/*!
* \brief Constructs a new main window.
*/
MainWindow::MainWindow(QSettings &settings, Dialogs::QtSettings *qtSettings, QWidget *parent)
MainWindow::MainWindow(QSettings &settings, QtUtilities::QtSettings *qtSettings, QWidget *parent)
: QMainWindow(parent)
, m_ui(new Ui::MainWindow)
, m_openFlags(PasswordFileOpenFlags::None)
@ -122,12 +148,9 @@ MainWindow::MainWindow(QSettings &settings, Dialogs::QtSettings *qtSettings, QWi
, 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;
@ -164,7 +187,18 @@ MainWindow::MainWindow(QSettings &settings, Dialogs::QtSettings *qtSettings, QWi
m_ui->treeView->setFrameShape(QFrame::StyledPanel);
m_ui->tableView->setFrameShape(QFrame::StyledPanel);
#endif
m_ui->tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
// setup column sizing
auto *const columnSizingGroup = new QActionGroup(this);
columnSizingGroup->addAction(m_ui->actionColumnWidthAuto);
columnSizingGroup->addAction(m_ui->actionColumnWidthCustom);
if (settings.value(QStringLiteral("interactivecolumns")).toBool()) {
m_ui->actionColumnWidthCustom->setChecked(true);
} else {
m_ui->actionColumnWidthAuto->setChecked(true);
}
updateColumnSizing();
// splitter sizes
m_ui->splitter->setSizes(QList<int>() << 100 << 800);
@ -217,6 +251,7 @@ MainWindow::MainWindow(QSettings &settings, Dialogs::QtSettings *qtSettings, QWi
connect(m_undoStack, &QUndoStack::canRedoChanged, m_ui->actionRedo, &QAction::setEnabled);
// -> view
connect(passwordVisibilityGroup, &QActionGroup::triggered, this, &MainWindow::setPasswordVisibility);
connect(columnSizingGroup, &QActionGroup::triggered, this, &MainWindow::updateColumnSizing);
connect(m_ui->actionShowUndoStack, &QAction::triggered, this, &MainWindow::showUndoView);
// -> models
connect(m_ui->treeView->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &MainWindow::accountSelected);
@ -229,7 +264,7 @@ MainWindow::MainWindow(QSettings &settings, Dialogs::QtSettings *qtSettings, QWi
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();
@ -242,6 +277,20 @@ MainWindow::~MainWindow()
{
}
bool MainWindow::event(QEvent *event)
{
switch (event->type()) {
case QEvent::PaletteChange:
if (m_qtSettings) {
m_qtSettings->reevaluatePaletteAndDefaultIconTheme();
}
updateStyleSheet();
break;
default:;
}
return QMainWindow::event(event);
}
bool MainWindow::eventFilter(QObject *obj, QEvent *event)
{
if (obj == m_undoView) {
@ -259,7 +308,8 @@ bool MainWindow::eventFilter(QObject *obj, QEvent *event)
QString data;
const QMimeData *mimeData = dropEvent->mimeData();
if (mimeData->hasUrls()) {
const QUrl url = mimeData->urls().front();
const auto urls = mimeData->urls();
const auto &url = urls.front();
if (url.scheme() == QLatin1String("file")) {
data = url.path();
}
@ -274,7 +324,7 @@ bool MainWindow::eventFilter(QObject *obj, QEvent *event)
}
return true;
}
FALLTHROUGH;
[[fallthrough]];
default:;
}
}
@ -288,16 +338,18 @@ void MainWindow::closeEvent(QCloseEvent *event)
event->ignore();
return;
}
// close undow view
if (m_undoView) {
m_undoView->close();
}
// save settings
m_settings.beginGroup(QStringLiteral("mainwindow"));
m_settings.setValue(QStringLiteral("geometry"), saveGeometry());
m_settings.setValue(QStringLiteral("state"), saveState());
m_settings.setValue(QStringLiteral("recententries"), m_recentMgr->save());
m_settings.setValue(QStringLiteral("accountfilter"), m_ui->accountFilterLineEdit->text());
m_settings.setValue(QStringLiteral("geometry"), QVariant(saveGeometry()));
m_settings.setValue(QStringLiteral("state"), QVariant(saveState()));
m_settings.setValue(QStringLiteral("recententries"), QVariant(m_recentMgr->save()));
m_settings.setValue(QStringLiteral("accountfilter"), QVariant(m_ui->accountFilterLineEdit->text()));
m_settings.setValue(QStringLiteral("alwayscreatebackup"), m_ui->actionAlwaysCreateBackup->isChecked());
QString pwVisibility;
if (m_ui->actionShowAlways->isChecked()) {
@ -307,8 +359,12 @@ void MainWindow::closeEvent(QCloseEvent *event)
} else {
pwVisibility = QStringLiteral("editing");
}
m_settings.setValue(QStringLiteral("pwvisibility"), pwVisibility);
m_settings.setValue(QStringLiteral("pwvisibility"), QVariant(pwVisibility));
m_settings.setValue(QStringLiteral("interactivecolumns"), m_ui->actionColumnWidthCustom->isChecked());
m_settings.endGroup();
if (m_qtSettings) {
m_qtSettings->save(m_settings);
}
}
void MainWindow::timerEvent(QTimerEvent *event)
@ -329,8 +385,8 @@ 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(); });
}
//connect(m_settingsDlg, &SettingsDialog::applied, this, &MainWindow::settingsAccepted);
}
if (m_settingsDlg->isHidden()) {
m_settingsDlg->showNormal();
@ -345,7 +401,7 @@ void MainWindow::showSettingsDialog()
void MainWindow::showAboutDialog()
{
if (!m_aboutDlg) {
m_aboutDlg = new AboutDialog(this, tr("A simple password store using AES-256-CBC encryption via OpenSSL."),
m_aboutDlg = new AboutDialog(this, QStringLiteral(APP_URL), tr("A simple password store using AES-256-CBC encryption via OpenSSL."),
QImage(":/icons/hicolor/128x128/apps/passwordmanager.png"));
}
m_aboutDlg->show();
@ -414,8 +470,6 @@ void MainWindow::showUndoView()
*/
bool MainWindow::openFile(const QString &path, PasswordFileOpenFlags openFlags)
{
using namespace Dialogs;
// close previous file
if (m_file.hasRootEntry() && !closeFile()) {
return false;
@ -425,17 +479,15 @@ bool MainWindow::openFile(const QString &path, PasswordFileOpenFlags openFlags)
m_file.setPath(path.toStdString());
try {
m_file.open(m_openFlags = openFlags);
} catch (...) {
// catch std::ios_base::failure
const char *const ioError = catchIoFailure();
} catch (const std::ios_base::failure &failure) {
// try read-only
if (!(m_openFlags & PasswordFileOpenFlags::ReadOnly)) {
return openFile(path, m_openFlags | PasswordFileOpenFlags::ReadOnly);
}
// show error message
const QString errmsg = tr("An IO error occured when opening the specified file \"%1\".\n\n(%2)").arg(path, QString::fromLocal8Bit(ioError));
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;
@ -480,9 +532,9 @@ bool MainWindow::openFile(const QString &path, PasswordFileOpenFlags openFlags)
m_file.load();
} catch (const CryptoException &e) {
msg = tr("The file couldn't be decrypted.\nOpenSSL error queue: %1").arg(QString::fromLocal8Bit(e.what()));
} catch (...) {
} catch (const std::ios_base::failure &failure) {
try {
msg = QString::fromLocal8Bit(catchIoFailure());
msg = QString::fromLocal8Bit(failure.what());
} catch (const runtime_error &e) {
msg = tr("Unable to parse the file. %1").arg(QString::fromLocal8Bit(e.what()));
}
@ -544,9 +596,9 @@ void MainWindow::createFile(const QString &path, const QString &password)
try {
m_openFlags = PasswordFileOpenFlags::Default;
m_file.create();
} catch (...) {
catchIoFailure();
QMessageBox::critical(this, QApplication::applicationName(), tr("The file <i>%1</i> couldn't be created.").arg(path));
} catch (const std::ios_base::failure &failure) {
QMessageBox::critical(this, QApplication::applicationName(),
tr("The file <i>%1</i> couldn't be created: %2").arg(path, QString::fromLocal8Bit(failure.what())));
return;
}
m_file.generateRootEntry();
@ -599,21 +651,21 @@ void MainWindow::updateUiStatus()
*/
void MainWindow::updateWindowTitle()
{
Dialogs::DocumentStatus docStatus;
DocumentStatus docStatus;
if (m_file.hasRootEntry()) {
if (m_somethingChanged) {
docStatus = Dialogs::DocumentStatus::Unsaved;
docStatus = DocumentStatus::Unsaved;
} else {
docStatus = Dialogs::DocumentStatus::Saved;
docStatus = DocumentStatus::Saved;
}
} else {
docStatus = Dialogs::DocumentStatus::NoDocument;
docStatus = DocumentStatus::NoDocument;
}
auto documentPath(QString::fromStdString(m_file.path()));
if (m_openFlags & PasswordFileOpenFlags::ReadOnly) {
documentPath += tr(" [read-only]");
}
setWindowTitle(Dialogs::generateWindowTitle(docStatus, documentPath));
setWindowTitle(generateWindowTitle(docStatus, documentPath));
}
void MainWindow::applyDefaultExpanding(const QModelIndex &parent)
@ -707,7 +759,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;
@ -742,8 +794,8 @@ bool MainWindow::askForCreatingFile()
try {
m_file.create();
updateWindowTitle();
} catch (...) {
QMessageBox::critical(this, QApplication::applicationName(), QString::fromLocal8Bit(catchIoFailure()));
} catch (const std::ios_base::failure &failure) {
QMessageBox::critical(this, QApplication::applicationName(), QString::fromLocal8Bit(failure.what()));
return false;
}
}
@ -820,7 +872,6 @@ bool MainWindow::closeFile()
*/
bool MainWindow::saveFile()
{
using namespace Dialogs;
if (showNoFileOpened()) {
return false;
}
@ -830,9 +881,8 @@ bool MainWindow::saveFile()
if (m_ui->actionAlwaysCreateBackup->isChecked()) {
try {
m_file.doBackup();
} catch (...) {
const QString message(
tr("The backup file couldn't be created because in IO error occured: %1").arg(QString::fromLocal8Bit(catchIoFailure())));
} catch (const std::ios_base::failure &failure) {
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;
@ -865,8 +915,8 @@ bool MainWindow::saveFile()
m_file.save(saveOptions());
} catch (const CryptoException &ex) {
msg = tr("The password list couldn't be saved due to encryption failure.\nOpenSSL error queue: %1").arg(QString::fromLocal8Bit(ex.what()));
} catch (...) {
msg = QString::fromLocal8Bit(catchIoFailure());
} catch (const std::ios_base::failure &failure) {
msg = QString::fromLocal8Bit(failure.what());
}
// show status
if (!msg.isEmpty()) {
@ -900,8 +950,8 @@ void MainWindow::exportFile()
QString errmsg;
try {
m_file.exportToTextfile(targetPath.toStdString());
} catch (...) {
errmsg = tr("The password list couldn't be exported. %1").arg(QString::fromLocal8Bit(catchIoFailure()));
} catch (const std::ios_base::failure &failure) {
errmsg = tr("An IO error occurred when exporting the password list: %1").arg(QString::fromLocal8Bit(failure.what()));
}
if (errmsg.isEmpty()) {
m_ui->statusBar->showMessage(tr("The password list has been exported."), 5000);
@ -925,7 +975,7 @@ void MainWindow::showContainingDirectory()
}
const QFileInfo file(QString::fromStdString(m_file.path()));
if (file.dir().exists()) {
DesktopUtils::openLocalFileOrDir(file.dir().absolutePath());
openLocalFileOrDir(file.dir().absolutePath());
}
}
@ -977,7 +1027,7 @@ void MainWindow::addEntry(EntryType type)
QMessageBox::warning(this, QApplication::applicationName(), tr("Unable to create new entry."));
return;
}
m_entryModel->setData(m_entryModel->index(row, 0, selected), text, Qt::DisplayRole);
m_entryModel->setData(m_entryModel->index(row, 0, selected), QVariant(text), Qt::DisplayRole);
setSomethingChanged(true);
}
@ -1006,7 +1056,13 @@ void MainWindow::removeEntry()
*/
void MainWindow::applyFilter(const QString &filterText)
{
m_entryFilterModel->setFilterRegExp(filterText);
m_entryFilterModel->
#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0))
setFilterRegularExpression(QRegularExpression(filterText, QRegularExpression::CaseInsensitiveOption))
#else
setFilterRegExp(filterText)
#endif
;
if (filterText.isEmpty()) {
applyDefaultExpanding(QModelIndex());
} else {
@ -1022,6 +1078,7 @@ void MainWindow::accountSelected(const QModelIndex &selected, const QModelIndex
if (Entry *entry = m_entryModel->entry(m_entryFilterModel->mapToSource(selected))) {
if (entry->type() == EntryType::Account) {
m_fieldModel->setAccountEntry(static_cast<AccountEntry *>(entry));
m_ui->tableView->resizeRowsToContents();
return;
}
}
@ -1133,7 +1190,6 @@ void MainWindow::setPasswordVisibility(QAction *selectedAction)
*/
void MainWindow::changePassword()
{
using namespace Dialogs;
if (showNoFileOpened()) {
return;
}
@ -1162,7 +1218,7 @@ void MainWindow::changePassword()
/*!
* \brief Shows the tree view context menu.
*/
void MainWindow::showTreeViewContextMenu()
void MainWindow::showTreeViewContextMenu(const QPoint &pos)
{
if (!m_file.hasRootEntry()) {
return;
@ -1190,13 +1246,13 @@ void MainWindow::showTreeViewContextMenu()
std::bind(&EntryModel::setData, m_entryModel, std::cref(selected), QVariant(!nodeEntry->isExpandedByDefault()), DefaultExpandedRole));
contextMenu.addAction(action);
}
contextMenu.exec(QCursor::pos());
contextMenu.exec(m_ui->treeView->viewport()->mapToGlobal(pos));
}
/*!
* \brief Shows the table view context menu.
*/
void MainWindow::showTableViewContextMenu()
void MainWindow::showTableViewContextMenu(const QPoint &pos)
{
// check whether there is a selection at all
const QModelIndexList selectedIndexes = m_ui->tableView->selectionModel()->selectedIndexes();
@ -1219,7 +1275,7 @@ void MainWindow::showTableViewContextMenu()
if (const Field *field = m_fieldModel->field(static_cast<size_t>(index.row()))) {
if (url.isEmpty() && field->type() != FieldType::Password) {
for (const string &protocol : protocols) {
if (ConversionUtilities::startsWith(field->value(), protocol)) {
if (startsWith(field->value(), protocol)) {
url = QString::fromUtf8(field->value().data());
}
}
@ -1261,7 +1317,8 @@ void MainWindow::showTableViewContextMenu()
contextMenu.addSeparator();
contextMenu.addAction(QIcon::fromTheme(QStringLiteral("edit-copy")), tr("Copy"), this, &MainWindow::copyFields);
contextMenu.addAction(QIcon::fromTheme(QStringLiteral("edit-copy")), tr("Copy for 5 seconds"), this, &MainWindow::copyFieldsForXMilliSeconds);
if (QApplication::clipboard()->mimeData()->hasText()) {
const auto *const mimeData = QGuiApplication::clipboard()->mimeData();
if (mimeData && mimeData->hasText()) {
contextMenu.addAction(QIcon::fromTheme(QStringLiteral("edit-paste")), tr("Paste"), this, &MainWindow::insertFieldsFromClipboard);
}
// -> insert open URL
@ -1271,7 +1328,7 @@ void MainWindow::showTableViewContextMenu()
contextMenu.addAction(openUrlAction);
}
contextMenu.exec(QCursor::pos());
contextMenu.exec(m_ui->tableView->viewport()->mapToGlobal(pos));
}
void MainWindow::showFileDetails()

View File

@ -19,20 +19,19 @@ QT_FORWARD_DECLARE_CLASS(QUndoStack)
QT_FORWARD_DECLARE_CLASS(QUndoView)
QT_FORWARD_DECLARE_CLASS(QSettings)
#define PASSWORD_MANAGER_ENUM_CLASS enum class
namespace Io {
DECLARE_ENUM_CLASS(EntryType, int);
DECLARE_ENUM_CLASS(FieldType, int);
PASSWORD_MANAGER_ENUM_CLASS EntryType : int;
PASSWORD_MANAGER_ENUM_CLASS FieldType : int;
} // namespace Io
#undef PASSWORD_MANAGER_ENUM_CLASS
namespace MiscUtils {
namespace QtUtilities {
class RecentMenuManager;
}
namespace Dialogs {
class AboutDialog;
class SettingsDialog;
class QtSettings;
} // namespace Dialogs
} // namespace QtUtilities
namespace QtGui {
@ -48,13 +47,14 @@ class MainWindow : public QMainWindow {
Q_OBJECT
public:
explicit MainWindow(QSettings &settings, Dialogs::QtSettings *qtSettings = nullptr, QWidget *parent = nullptr);
explicit MainWindow(QSettings &settings, QtUtilities::QtSettings *qtSettings = nullptr, QWidget *parent = nullptr);
~MainWindow() override;
public slots:
QString selectedFieldsString() const;
public Q_SLOTS:
// file management
bool openFile(const QString &path);
bool openFile(const QString &path, Io::PasswordFileOpenFlags openFlags);
void createFile(const QString &path, const QString &password);
void createFile(const QString &path);
bool createFile();
@ -70,12 +70,16 @@ public slots:
void showPassowrdGeneratorDialog();
void showUndoView();
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;
private slots:
private Q_SLOTS:
// file management
bool showFile();
// account/categories management
@ -92,20 +96,21 @@ private 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();
void insertFieldsFromClipboard();
// showing context menus
void showTreeViewContextMenu();
void showTableViewContextMenu();
void showTreeViewContextMenu(const QPoint &pos);
void showTableViewContextMenu(const QPoint &pos);
// other
void showFileDetails();
void showContainingDirectory();
void clearClipboard();
void setSomethingChanged();
void setSomethingChanged(bool somethingChanged);
void updateStyleSheet();
void updateColumnSizing();
private:
// showing conditional messages/prompts
@ -129,11 +134,11 @@ private:
Io::PasswordFileOpenFlags m_openFlags;
bool m_dontUpdateSelection;
int m_clearClipboardTimer;
MiscUtils::RecentMenuManager *m_recentMgr;
Dialogs::AboutDialog *m_aboutDlg;
QtUtilities::RecentMenuManager *m_recentMgr;
QtUtilities::AboutDialog *m_aboutDlg;
QSettings &m_settings;
Dialogs::QtSettings *m_qtSettings;
Dialogs::SettingsDialog *m_settingsDlg;
QtUtilities::QtSettings *m_qtSettings;
QtUtilities::SettingsDialog *m_settingsDlg;
};
/*!

View File

@ -76,7 +76,7 @@
<number>0</number>
</property>
<item>
<widget class="Widgets::ClearLineEdit" name="accountFilterLineEdit">
<widget class="QtUtilities::ClearLineEdit" name="accountFilterLineEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
@ -249,7 +249,15 @@
<addaction name="actionShowOnlyWhenEditing"/>
<addaction name="actionHideAlways"/>
</widget>
<widget class="QMenu" name="menuColumnWiths">
<property name="title">
<string>Column withs</string>
</property>
<addaction name="actionColumnWidthAuto"/>
<addaction name="actionColumnWidthCustom"/>
</widget>
<addaction name="menuPassword_visibility"/>
<addaction name="menuColumnWiths"/>
<addaction name="actionShowUndoStack"/>
</widget>
<addaction name="menuProgramm"/>
@ -569,11 +577,27 @@
<string>Details ...</string>
</property>
</action>
<action name="actionColumnWidthAuto">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Auto</string>
</property>
</action>
<action name="actionColumnWidthCustom">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Custom</string>
</property>
</action>
</widget>
<layoutdefault spacing="6" margin="11"/>
<customwidgets>
<customwidget>
<class>Widgets::ClearLineEdit</class>
<class>QtUtilities::ClearLineEdit</class>
<extends>QLineEdit</extends>
<header location="global">qtutilities/widgets/clearlineedit.h</header>
</customwidget>

View File

@ -19,7 +19,7 @@
using namespace std;
using namespace Io;
using namespace Util;
using namespace Dialogs;
using namespace QtUtilities;
namespace QtGui {
@ -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
@ -7,14 +8,15 @@
#endif
#include "resources/config.h"
#include <passwordfile/util/openssl.h>
#include "resources/qtconfig.h"
#include <c++utilities/application/argumentparser.h>
#include <c++utilities/application/commandlineutils.h>
#include <c++utilities/application/failure.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>
ENABLE_QT_RESOURCES_OF_STATIC_DEPENDENCIES
@ -22,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 ApplicationUtilities;
using namespace Util;
using namespace CppUtilities;
#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);
@ -56,68 +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.parseArgsOrExit(argc, argv);
#endif
parser.parseArgs(argc, argv);
// init OpenSSL
OpenSsl::init();
#ifndef PASSWORD_MANAGER_FORCE_GUI
// start either interactive CLI or GUI
// run CLI if CLI-argument is present
if (cliArg.isPresent()) {
Cli::InteractiveCli cli;
if (fileArg.isPresent()) {
cli.run(fileArg.firstValue());
} else {
cli.run();
}
} 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());
return Cli::InteractiveCli().run(fileArg.isPresent() ? std::string(fileArg.firstValue()) : std::string());
}
// 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
// clean OpenSSL
OpenSsl::clean();
return returnCode;
}

View File

@ -7,8 +7,6 @@
#include <passwordfile/io/entry.h>
#include <passwordfile/io/parsingexception.h>
#include <c++utilities/io/catchiofailure.h>
#include <QBuffer>
#include <QDebug>
#include <QIcon>
@ -96,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) {
@ -127,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;
@ -245,9 +243,8 @@ 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());
} catch (...) {
IoUtilities::catchIoFailure();
return QByteArray(str.data(), static_cast<QByteArray::size_type>(str.size()));
} catch (const std::ios_base::failure &) {
return false;
}
}
@ -304,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);
@ -318,9 +318,8 @@ bool EntryModel::setData(const QModelIndex &index, const QVariant &value, int ro
} catch (const Io::ParsingException &parsingError) {
cerr << "EntryModel::setData: parsing exception: " << parsingError.what() << endl;
return false;
} catch (...) {
const char *const errorMessage(IoUtilities::catchIoFailure());
cerr << "EntryModel::setData: IO exception: " << errorMessage << endl;
} catch (const std::ios_base::failure &failure) {
cerr << "EntryModel::setData: IO exception: " << failure.what() << endl;
return false;
}
}
@ -379,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:;
}
}
@ -453,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 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();
@ -509,7 +522,7 @@ QMimeData *EntryModel::mimeData(const QModelIndexList &indexes) const
if (types.isEmpty()) {
return nullptr;
}
QMimeData *const data = new QMimeData();
auto *const data = new QMimeData();
QStringList plainTextParts;
plainTextParts.reserve(indexes.size());
QByteArray encoded;

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 {
@ -23,6 +35,7 @@ Kirigami.ScrollablePage {
property alias isPassword: fieldIsPasswordCheckBox.checked
title: qsTr("Edit field of %1").arg(nativeInterface.currentAccountName)
standardButtons: Controls.Dialog.Ok | Controls.Dialog.Cancel
onAccepted: {
var column0 = fieldsListView.model.index(entryIndex, 0)
var column1 = fieldsListView.model.index(entryIndex, 1)
@ -31,21 +44,55 @@ Kirigami.ScrollablePage {
fieldsListView.model.setData(column0, isPassword ? 1 : 0,
0x0100 + 1)
}
contentItem: ColumnLayout {
GridLayout {
Layout.preferredWidth: fieldDialog.availableWidth
columns: 2
columnSpacing: 0
ColumnLayout {
Controls.TextField {
id: fieldNameEdit
Layout.preferredWidth: fieldDialog.availableWidth
text: fieldDialog.fieldName
Keys.onPressed: fieldDialog.acceptOnReturn(event)
}
Controls.TextField {
id: fieldValueEdit
Layout.preferredWidth: fieldDialog.availableWidth
text: fieldDialog.fieldValue
echoMode: fieldDialog.isPassword
&& !showCharactersCheckBox.checked ? TextInput.PasswordEchoOnEdit : TextInput.Normal
Keys.onPressed: fieldDialog.acceptOnReturn(event)
Controls.TextField {
id: fieldNameEdit
Layout.fillWidth: true
text: fieldDialog.fieldName
Keys.onPressed: (event) => fieldDialog.acceptOnReturn(event)
}
Controls.RoundButton {
flat: true
icon.name: "username-copy"
Layout.preferredWidth: height
onClicked: {
nativeInterface.copyToClipboard(fieldNameEdit.text)
showPassiveNotification(qsTr("Copied field name"))
}
}
Controls.TextField {
id: fieldValueEdit
property bool hideCharacters: fieldDialog.isPassword
&& !showCharactersCheckBox.checked
Layout.fillWidth: true
// ensure height is always the same, regardless of echo mode (under Android the
// bullet points for PasswordEchoOnEdit have a different size causing a different
// height)
Layout.preferredHeight: fieldNameEdit.height
text: fieldDialog.fieldValue
echoMode: hideCharacters ? TextInput.PasswordEchoOnEdit : TextInput.Normal
// fix ugly bullet points under Android
font.pointSize: hideCharacters ? fieldNameEdit.font.pointSize
* 0.5 : fieldNameEdit.font.pointSize
Keys.onPressed: (event) => fieldDialog.acceptOnReturn(event)
}
Controls.RoundButton {
flat: true
icon.name: "password-copy"
Layout.preferredWidth: height
onClicked: {
nativeInterface.copyToClipboard(fieldValueEdit.text)
showPassiveNotification(
fieldDialog.isPassword ? qsTr("Copied password") : qsTr(
"Copied value"))
}
}
}
RowLayout {
Layout.preferredWidth: fieldDialog.availableWidth
@ -71,160 +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
}
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) {
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,22 +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")
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")
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 {
@ -90,14 +92,8 @@ Kirigami.ApplicationWindow {
Item {
Layout.preferredWidth: 2
}
Kirigami.Icon {
source: "emblem-warning"
width: Kirigami.Units.iconSizes.small
height: Kirigami.Units.iconSizes.small
}
Controls.Label {
text: qsTr("No password set\nFile will be saved unencrypted!")
font.bold: true
}
}
Item {
@ -108,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
@ -130,23 +126,31 @@ 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
icon.name: "document-save-as"
onTriggered: fileDialog.saveAs()
shortcut: StandardKey.SaveAs
},
Kirigami.Action {
text: nativeInterface.passwordSet ? qsTr("Change password") : qsTr(
"Add password")
enabled: nativeInterface.fileOpen
iconName: "document-encrypt"
icon.name: "document-encrypt"
onTriggered: enterPasswordDialog.askForNewPassword(
"Change password for " + nativeInterface.filePath)
qsTr("Change password for %1").arg(
nativeInterface.filePath))
shortcut: "Ctrl+P"
},
Kirigami.Action {
text: qsTr("Details")
enabled: nativeInterface.fileOpen
iconName: "document-properties"
icon.name: "document-properties"
onTriggered: {
leftMenu.resetMenu()
fileSummaryDialog.show()
@ -158,7 +162,7 @@ Kirigami.ApplicationWindow {
"Adjust search")
enabled: nativeInterface.fileOpen
visible: nativeInterface.filterAsDialog
iconName: "search"
icon.name: "search"
onTriggered: {
leftMenu.resetMenu()
filterDialog.open()
@ -170,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 = ""
@ -182,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()
},
@ -191,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
@ -238,11 +243,13 @@ 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
width: fileSummaryDialog.availableWidth
}
function show() {
@ -253,32 +260,6 @@ Kirigami.ApplicationWindow {
FileDialog {
id: fileDialog
title: selectExisting ? qsTr("Select an existing file") : qsTr(
"Select path for new file")
onAccepted: {
if (fileUrls.length < 1) {
return
}
nativeInterface.handleFileSelectionAccepted(fileUrls[0],
this.selectExisting)
}
onRejected: nativeInterface.handleFileSelectionCanceled()
function show() {
if (nativeInterface.showNativeFileDialog(this.selectExisting)) {
return
}
// fallback to the Qt Quick file dialog if a native implementation is not available
this.open()
}
function openExisting() {
this.selectExisting = true
this.show()
}
function createNew() {
this.selectExisting = false
this.show()
}
}
BasicDialog {
@ -310,8 +291,7 @@ Kirigami.ApplicationWindow {
Controls.DialogButtonBox.buttonRole: Controls.DialogButtonBox.RejectRole
}
}
ColumnLayout {
contentItem: ColumnLayout {
Controls.TextField {
id: filterDialogTextField
Layout.preferredWidth: filterDialog.availableWidth
@ -322,28 +302,35 @@ Kirigami.ApplicationWindow {
Connections {
target: nativeInterface
onEntryFilterChanged: {
function onEntryFilterChanged(newFilter) {
if (filterTextField.text !== newFilter) {
filterTextField.text = newFilter
}
}
onFileError: {
if (retryAction.length === 0) {
function onFileError(errorMessage, retryAction) {
var retryMethod = null
if (retryAction === "load" || retryAction === "save") {
retryMethod = retryAction
}
if (retryMethod) {
showPassiveNotification(errorMessage)
} else {
showPassiveNotification(errorMessage, 2500, qsTr("Retry"),
function () {
nativeInterface[retryAction]()
nativeInterface[retryMethod]()
})
}
}
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(
@ -355,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)
@ -386,7 +373,7 @@ Kirigami.ApplicationWindow {
}
}
}
onHasEntryFilterChanged: {
function onHasEntryFilterChanged(hasEntryFilter) {
if (nativeInterface.fileOpen) {
pageStack.clear()
initStack()
@ -399,7 +386,11 @@ Kirigami.ApplicationWindow {
Kirigami.Action {
property string filePath
text: filePath.substring(filePath.lastIndexOf('/') + 1)
onTriggered: nativeInterface.load(filePath)
onTriggered: {
nativeInterface.clear()
nativeInterface.filePath = filePath
nativeInterface.load()
}
}
}
@ -407,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,18 +5,17 @@
#include <c++utilities/conversion/stringbuilder.h>
#include <QAndroidJniObject>
#include <QColor>
#include <QCoreApplication>
#include <QJniObject>
#include <QMessageLogContext>
#include <QMetaObject>
#include <QtAndroid>
#include <android/log.h>
#include <jni.h>
using namespace ConversionUtilities;
using namespace CppUtilities;
namespace QtGui {
@ -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);
@ -50,15 +49,16 @@ void registerControllerForAndroid(Controller *controller)
controllerForAndroid = controller;
}
bool showAndroidFileDialog(bool existing)
bool showAndroidFileDialog(bool existing, bool createNew)
{
return QtAndroid::androidActivity().callMethod<jboolean>("showAndroidFileDialog", "(Z)Z", existing);
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,20 +102,21 @@ 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)
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(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)
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(int, fileHandle), Q_ARG(bool, existing));
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));
}
static void onAndroidFileDialogRejected(JNIEnv *, jobject)
@ -144,8 +145,8 @@ JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *)
// register native methods
static const JNINativeMethod methods[] = {
{ "onAndroidError", "(Ljava/lang/String;)V", reinterpret_cast<void *>(onAndroidError) },
{ "onAndroidFileDialogAccepted", "(Ljava/lang/String;Z)V", reinterpret_cast<void *>(onAndroidFileDialogAccepted) },
{ "onAndroidFileDialogAcceptedDescriptor", "(Ljava/lang/String;Ljava/lang/String;IZ)V",
{ "onAndroidFileDialogAccepted", "(Ljava/lang/String;ZZ)V", reinterpret_cast<void *>(onAndroidFileDialogAccepted) },
{ "onAndroidFileDialogAcceptedDescriptor", "(Ljava/lang/String;Ljava/lang/String;IZZ)V",
reinterpret_cast<void *>(onAndroidFileDialogAcceptedDescriptor) },
{ "onAndroidFileDialogRejected", "()V", reinterpret_cast<void *>(onAndroidFileDialogRejected) },
};

View File

@ -12,7 +12,7 @@ class Controller;
void applyThemingForAndroid();
void registerControllerForAndroid(Controller *controller);
bool showAndroidFileDialog(bool existing);
bool showAndroidFileDialog(bool existing, bool createNew);
int openFileDescriptorFromAndroidContentUrl(const QString &url, const QString &mode);
void writeToAndroidLog(QtMsgType type, const QMessageLogContext &context, const QString &msg);
void setupAndroidSpecifics();

View File

@ -4,16 +4,17 @@
#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/catchiofailure.h>
#include <c++utilities/io/nativefilestream.h>
#include <c++utilities/io/path.h>
#ifndef QT_NO_CLIPBOARD
#include <QClipboard>
#endif
#ifdef DEBUG_BUILD
#if defined(CPP_UTILITIES_DEBUG_BUILD) || (defined(Q_OS_ANDROID) && defined(CPP_UTILITIES_USE_NATIVE_FILE_BUFFER))
#include <QDebug>
#endif
#include <QDir>
@ -28,8 +29,8 @@
using namespace std;
using namespace Io;
using namespace IoUtilities;
using namespace Dialogs;
using namespace CppUtilities;
using namespace QtUtilities;
namespace QtGui {
@ -42,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);
@ -74,29 +76,25 @@ 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 (filePath.startsWith(QLatin1String("//"))) {
while (actualFilePath.startsWith(QLatin1String("//"))) {
actualFilePath = actualFilePath.mid(1);
}
// skip if this path is already set
if (m_filePath == actualFilePath) {
return;
}
// assign full file path and file name
m_file.clear();
m_file.setPath(filePath.toLocal8Bit().data());
m_fileName = QString::fromLocal8Bit(IoUtilities::fileName(m_file.path()).data());
emit filePathChanged(m_filePath = filePath);
// clear password so we don't use the password from the previous file
m_password.clear();
m_filePath = actualFilePath.toString();
m_file.setPath(m_filePath.toLocal8Bit().toStdString());
const auto fileName = CppUtilities::fileName(m_file.path());
m_fileName = QString::fromLocal8Bit(fileName.data(), static_cast<int>(fileName.size()));
emit filePathChanged(m_filePath);
// handle recent files
if (m_filePath.isEmpty()) {
return;
}
const auto index = m_recentFiles.indexOf(m_filePath);
if (!index) {
return;
@ -126,20 +124,18 @@ void Controller::init()
if (!m_filePath.isEmpty()) {
load();
}
if (const auto error = QtUtilities::errorMessageForSettings(m_settings); !error.isEmpty()) {
emit settingsError(error);
}
}
void Controller::load(const QString &filePath)
void Controller::load()
{
if (!filePath.isEmpty()) {
setFilePath(filePath);
}
resetFileStatus();
try {
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);
@ -150,33 +146,30 @@ void Controller::load(const QString &filePath)
} 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 (const runtime_error &e) {
emit fileError(tr("A parsing error occured when opening the file: ") + QString::fromLocal8Bit(e.what()), QStringLiteral("load"));
} catch (...) {
emitIoError(tr("loading"));
if ((m_file.saveOptions() & PasswordFileSaveFlags::Encryption) && m_password.isEmpty()) {
emit passwordRequired(m_filePath);
} else {
// clear password since the password which has been provided might not have been correct (although there was no CryptoException)
clearPassword();
emitFileError(tr("loading"));
}
}
}
void Controller::create(const QString &filePath)
void Controller::create()
{
if (!filePath.isEmpty()) {
setFilePath(filePath);
}
resetFileStatus();
try {
if (filePath.isEmpty()) {
m_file.clear();
}
m_file.create();
} catch (...) {
emitIoError(tr("creating"));
emitFileError(tr("creating"));
}
m_file.generateRootEntry();
m_entryModel.setRootEntry(m_file.rootEntry());
m_password.clear(); // avoid using the password of previously opened file
setFileOpen(true);
updateWindowTitle();
}
@ -187,13 +180,24 @@ void Controller::close()
m_file.close();
resetFileStatus();
} catch (...) {
emitIoError(tr("closing"));
emitFileError(tr("closing"));
}
}
void Controller::clear()
{
try {
m_file.close();
} catch (...) {
emitFileError(tr("closing"));
}
m_file.clear();
resetFileStatus();
}
PasswordFileSaveFlags Controller::prepareSaving()
{
auto flags = PasswordFileSaveFlags::Compression | PasswordFileSaveFlags::PasswordHashing;
auto flags = PasswordFileSaveFlags::Compression | PasswordFileSaveFlags::PasswordHashing | PasswordFileSaveFlags::AllowToCreateNewFile;
if (!m_password.isEmpty()) {
flags |= PasswordFileSaveFlags::Encryption;
const auto passwordUtf8(m_password.toUtf8());
@ -214,14 +218,14 @@ void Controller::save()
m_file.close();
// open new file descriptor to replace existing file and allow writing
IF_DEBUG_BUILD(qDebug() << "Opening new fd for saving, native url: " << m_nativeUrl;)
qDebug() << "Opening new fd for saving, native url: " << m_nativeUrl;
const auto newFileDescriptor = openFileDescriptorFromAndroidContentUrl(m_nativeUrl, QStringLiteral("wt"));
if (newFileDescriptor < 0) {
emit fileError(tr("Unable to open file descriptor for saving the file."));
emit fileError(tr("Unable to open file descriptor for saving the file."), QStringLiteral("save"));
return;
}
m_file.fileStream().openFromFileDescriptor(newFileDescriptor, ios_base::out | ios_base::trunc | ios_base::binary);
m_file.fileStream().open(newFileDescriptor, ios_base::out | ios_base::trunc | ios_base::binary);
m_file.write(flags);
} else {
#endif
@ -232,11 +236,9 @@ 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"));
} catch (const runtime_error &e) {
emit fileError(tr("An internal 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 (...) {
emitIoError(tr("saving"));
emitFileError(tr("saving"));
}
}
@ -244,42 +246,69 @@ void Controller::save()
* \brief Shows a native file dialog if supported; otherwise returns false.
* \remarks If supported, this method will load/create the selected file (according to \a existing).
*/
bool Controller::showNativeFileDialog(bool existing)
bool Controller::showNativeFileDialog(bool existing, bool createNew)
{
#if defined(Q_OS_ANDROID) && defined(CPP_UTILITIES_USE_NATIVE_FILE_BUFFER)
if (!m_useNativeFileDialog) {
return false;
}
return showAndroidFileDialog(existing);
return showAndroidFileDialog(existing, createNew);
#else
Q_UNUSED(existing)
Q_UNUSED(createNew)
return false;
#endif
}
void Controller::handleFileSelectionAccepted(const QString &filePath, bool existing)
void Controller::handleFileSelectionAccepted(const QString &filePath, const QString &nativeUrl, bool existing, bool createNew)
{
m_nativeUrl.clear();
m_nativeUrl = nativeUrl;
// assign the "ordinary" file path if one has been passed; otherwise the caller is responsible for handling this
const auto saveAs = !existing && !createNew;
if (!filePath.isEmpty()) {
// clear leftovers from possibly previously opened file unless we want to save the current file under a different location
if (!saveAs) {
m_file.clear();
}
setFilePath(filePath);
}
cout << "path is still " << m_file.path() << " (2)" << endl;
if (!saveAs) {
resetFileStatus();
}
if (existing) {
load(filePath);
} else {
create(filePath);
load();
} else if (createNew) {
create();
} else if (saveAs) {
save();
}
}
#if defined(Q_OS_ANDROID) && defined(CPP_UTILITIES_USE_NATIVE_FILE_BUFFER)
void Controller::handleFileSelectionAcceptedDescriptor(const QString &nativeUrl, const QString &fileName, int fileDescriptor, bool existing)
void Controller::handleFileSelectionAcceptedDescriptor(
const QString &nativeUrl, const QString &fileName, int fileDescriptor, bool existing, bool createNew)
{
qDebug() << "Opening file descriptor for native url: " << nativeUrl;
qDebug() << "(existing: " << existing << ", create new: " << createNew << ")";
try {
// clear leftovers from possibly previously opened file unless we want to save the current file under a different location
if (existing || createNew) {
m_file.clear();
}
m_file.setPath(fileName.toStdString());
m_file.fileStream().openFromFileDescriptor(fileDescriptor, ios_base::in | ios_base::binary);
m_file.fileStream().open(fileDescriptor, ios_base::in | ios_base::binary);
m_file.opened();
} catch (...) {
emitIoError(tr("opening from native file descriptor"));
emitFileError(existing ? QStringLiteral("load") : (createNew ? QStringLiteral("create") : QStringLiteral("save")));
}
emit filePathChanged(m_filePath = m_fileName = fileName);
handleFileSelectionAccepted(QString(), existing);
m_nativeUrl = nativeUrl;
handleFileSelectionAccepted(QString(), nativeUrl, existing, createNew);
}
#endif
@ -423,16 +452,20 @@ void Controller::setFileOpen(bool fileOpen)
}
}
void Controller::emitIoError(const QString &when)
void Controller::emitFileError(const QString &when)
{
try {
const auto *const msg = catchIoFailure();
emit fileError(tr("An IO error occured when %1 the file %2: ").arg(when, m_filePath) + QString::fromLocal8Bit(msg), QStringLiteral("load"));
throw;
} catch (const std::ios_base::failure &failure) {
emit fileError(
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 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"));
}
}
@ -456,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

@ -29,11 +29,11 @@ class Controller : public QObject {
Q_PROPERTY(EntryModel *entryModel READ entryModel NOTIFY entryModelChanged)
Q_PROPERTY(EntryFilterModel *entryFilterModel READ entryFilterModel NOTIFY entryFilterModelChanged)
Q_PROPERTY(FieldModel *fieldModel READ fieldModel NOTIFY fieldModelChanged)
Q_PROPERTY(Io::AccountEntry *currentAccount READ currentAccount WRITE setCurrentAccount NOTIFY currentAccountChanged)
//Q_PROPERTY(Io::AccountEntry *currentAccount READ currentAccount WRITE setCurrentAccount NOTIFY currentAccountChanged)
Q_PROPERTY(QModelIndex currentAccountIndex READ currentAccountIndex WRITE setCurrentAccountIndex NOTIFY currentAccountChanged)
Q_PROPERTY(QString currentAccountName READ currentAccountName NOTIFY currentAccountChanged)
Q_PROPERTY(bool hasCurrentAccount READ hasCurrentAccount NOTIFY currentAccountChanged)
Q_PROPERTY(QList<QPersistentModelIndex> cutEntries READ cutEntries WRITE setCutEntries NOTIFY cutEntriesChanged)
//Q_PROPERTY(QList<QPersistentModelIndex> cutEntries READ cutEntries WRITE setCutEntries NOTIFY cutEntriesChanged)
Q_PROPERTY(bool canPaste READ canPaste NOTIFY cutEntriesChanged)
Q_PROPERTY(QStringList recentFiles READ recentFiles RESET clearRecentFiles NOTIFY recentFilesChanged)
Q_PROPERTY(bool useNativeFileDialog READ useNativeFileDialog WRITE setUseNativeFileDialog NOTIFY useNativeFileDialogChanged)
@ -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,31 +86,34 @@ public:
QUndoStack *undoStack();
QString undoText() const;
QString redoText() const;
Io::PasswordFileSaveFlags prepareSaving();
bool isDarkModeEnabled() const;
void setDarkModeEnabled(bool darkModeEnabled);
public slots:
public Q_SLOTS:
void init();
void load(const QString &filePath = QString());
void create(const QString &filePath = QString());
void load();
void create();
void close();
void clear();
void save();
bool showNativeFileDialog(bool existing);
void handleFileSelectionAccepted(const QString &filePath, bool existing);
bool showNativeFileDialog(bool existing, bool createNew);
void handleFileSelectionAccepted(const QString &filePath, const QString &nativeUrl, bool existing, bool createNew);
#if defined(Q_OS_ANDROID) && defined(CPP_UTILITIES_USE_NATIVE_FILE_BUFFER)
void handleFileSelectionAcceptedDescriptor(const QString &nativeUrl, const QString &fileName, int fileDescriptor, bool existing);
void handleFileSelectionAcceptedDescriptor(const QString &nativeUrl, const QString &fileName, int fileDescriptor, bool existing, bool createNew);
#endif
void handleFileSelectionCanceled();
void undo();
void redo();
Io::PasswordFileSaveFlags prepareSaving();
QString computeFileSummary();
signals:
Q_SIGNALS:
void filePathChanged(const QString &newFilePath);
void passwordChanged(const QString &newPassword);
void passwordRequired(const QString &filePath);
void windowTitleChanged(const QString &windowTitle);
void fileOpenChanged(bool fileOpen);
void fileError(const QString &errorMessage, const QString &retryAction = QString());
void fileError(const QString &errorMessage, const QString &retryAction);
void fileSaved();
void entryModelChanged();
void entryFilterModelChanged();
@ -127,8 +131,10 @@ 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 slots:
private Q_SLOTS:
void handleEntriesRemoved(const QModelIndex &parentIndex, int first, int last);
void handleRecentFilesChanged();
@ -136,7 +142,7 @@ private:
void resetFileStatus();
void updateWindowTitle();
void setFileOpen(bool fileOpen);
void emitIoError(const QString &when);
void emitFileError(const QString &when);
QModelIndex ensureSourceEntryIndex(const QModelIndex &entryIndexMaybeFromFilterModel) const;
QSettings &m_settings;
@ -158,6 +164,7 @@ private:
bool m_fileModified;
bool m_useNativeFileDialog;
bool m_filterAsDialog;
bool m_darkModeEnabled;
};
inline QModelIndex Controller::ensureSourceEntryIndex(const QModelIndex &entryIndexMaybeFromFilterModel) const
@ -295,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
@ -335,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

@ -5,26 +5,37 @@
#endif
#include "resources/config.h"
#include "resources/qtconfig.h"
// 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 <QGuiApplication>
#include <passwordfile/util/openssl.h>
#include <QDebug>
#include <QIcon>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QSettings>
#include <QTextCodec>
#include <QtQml>
#ifdef PASSWORD_MANAGER_GUI_QTWIDGETS
#include <QApplication>
#endif
#include <cstdlib>
using namespace ApplicationUtilities;
using namespace CppUtilities;
using namespace Util;
namespace QtGui {
@ -35,44 +46,75 @@ int runQuickGui(int argc, char *argv[], const QtConfigArguments &qtConfigArgs, c
setupAndroidSpecifics();
#endif
// init OpenSSL
OpenSsl::init();
// init application
SET_QT_APPLICATION_INFO;
QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#ifdef PASSWORD_MANAGER_GUI_QTWIDGETS
QApplication a(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 a(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"));
}
// 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"), &a);
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
return a.exec();
// 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

@ -5,13 +5,13 @@
QT_FORWARD_DECLARE_CLASS(QString)
namespace ApplicationUtilities {
namespace CppUtilities {
class QtConfigArguments;
}
namespace QtGui {
int runQuickGui(int argc, char *argv[], const ApplicationUtilities::QtConfigArguments &qtConfigArgs, const QString &file);
int runQuickGui(int argc, char *argv[], const CppUtilities::QtConfigArguments &qtConfigArgs, const QString &file);
}
#endif // QT_QUICK_GUI_INITIATE_H

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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

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

View File

@ -1,37 +0,0 @@
#include "./testroutines.h"
#include <c++utilities/io/binaryreader.h>
#include <c++utilities/io/binarywriter.h>
#include <iostream>
#include <string>
#include <sstream>
using namespace std;
using namespace IoUtilities;
namespace Testroutines {
void lengthPrefixedString()
{
stringstream stream;
BinaryReader reader(&stream);
BinaryWriter writer(&stream);
string string1("jöalfj32öl4fj34 f234ölf3je frasdölkajwe fqwöejkfwöfklja sdefölasje fasef jasöefjas efajs eflasje faöslefj asöflej asefölajsefl öasejföaslefja söef jaseö flajseflas jeföaslefj aslefjaweöflja4 rfq34jqlök4jfq ljase öfaje4fqp 34f89uj <pfj apefjawepfoi jaefoaje föasdjfaösefj a4jfase9fau sejfpas");
string string2;
string string3("asdfalsjd23öl4j3");
writer.writeLengthPrefixedString(string1);
writer.writeLengthPrefixedString(string2);
writer.writeLengthPrefixedString(string3);
if(reader.readLengthPrefixedString() == string1
&& reader.readLengthPrefixedString() == string2
&& reader.readLengthPrefixedString() == string3) {
cout << "test sucessfull" << endl;
} else {
cout << "test failed" << endl;
}
}
}

View File

@ -1,10 +0,0 @@
#ifndef TESTROUTINES_H
#define TESTROUTINES_H
namespace Testroutines {
void lengthPrefixedString();
}
#endif // TESTROUTINES_H