mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2025-03-28 00:48:33 -04:00
Merge branch 'develop' into feature/yubikey
This commit is contained in:
commit
37c7318097
8
.gitattributes
vendored
8
.gitattributes
vendored
@ -1 +1,9 @@
|
||||
src/version.h.cmake export-subst
|
||||
.gitattributes export-ignore
|
||||
.gitignore export-ignore
|
||||
.github export-ignore
|
||||
.travis.yml export-ignore
|
||||
.tx export-ignore
|
||||
snapcraft.yaml export-ignore
|
||||
make_release.sh export-ignore
|
||||
AppImage-Recipe.sh export-ignore
|
||||
|
89
.github/CONTRIBUTING.md
vendored
89
.github/CONTRIBUTING.md
vendored
@ -1,31 +1,32 @@
|
||||
# Contributing to KeePassX Reboot
|
||||
# Contributing to KeePassXC
|
||||
|
||||
:+1::tada: First off, thanks for taking the time to contribute! :tada::+1:
|
||||
|
||||
The following is a set of guidelines for contributing to KeePassX Reboot on GitHub.
|
||||
The following is a set of guidelines for contributing to KeePassXC on GitHub.
|
||||
These are just guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request.
|
||||
|
||||
#### Table Of Contents
|
||||
#### Table of contents
|
||||
|
||||
[What should I know before I get started?](#what-should-i-know-before-i-get-started)
|
||||
* [Open Source Contribution Policy](#open-source-contribution-policy)
|
||||
|
||||
[How Can I Contribute?](#how-can-i-contribute)
|
||||
* [Feature Requests](#feature-requests)
|
||||
* [Bug Reports](#bug-reports)
|
||||
* [Your First Code Contribution](#your-first-code-contribution)
|
||||
* [Pull Requests](#pull-requests)
|
||||
[How can I contribute?](#how-can-i-contribute)
|
||||
* [Feature requests](#feature-requests)
|
||||
* [Bug reports](#bug-reports)
|
||||
* [Discuss with the team](#discuss-with-the-team)
|
||||
* [Your first code contribution](#your-first-code-contribution)
|
||||
* [Pull requests](#pull-requests)
|
||||
* [Translations](#translations)
|
||||
|
||||
[Styleguides](#styleguides)
|
||||
* [Git Branch Strategy](#git_branch_strategy)
|
||||
* [Git Commit Messages](#git-commit-messages)
|
||||
* [Coding Styleguide](#coding-styleguide)
|
||||
* [Git branch strategy](#git-branch-strategy)
|
||||
* [Git commit messages](#git-commit-messages)
|
||||
* [Coding styleguide](#coding-styleguide)
|
||||
|
||||
|
||||
## What should I know before I get started?
|
||||
### Open Source Contribution Policy
|
||||
[Version 0.3, 2015–11–18](https://medium.com/@jmaynard/a-contribution-policy-for-open-source-that-works-bfc4600c9d83#.i9ntbhmad)
|
||||
**Source**: [Version 0.3, 2015–11–18](https://medium.com/@jmaynard/a-contribution-policy-for-open-source-that-works-bfc4600c9d83#.i9ntbhmad)
|
||||
|
||||
#### Policy
|
||||
|
||||
@ -49,35 +50,35 @@ If we reject your contribution, it means only that we do not consider it suitabl
|
||||
* 0.3, 2011–11–19: Added “irrevocably” to “we can use” and changed “it” to “your contribution” in the “if rejected” section. Thanks to Patrick Maupin.
|
||||
|
||||
|
||||
## How Can I Contribute?
|
||||
### Feature Requests
|
||||
## How can I contribute?
|
||||
### Feature requests
|
||||
|
||||
We're always looking for suggestions to improve our application. If you have a suggestion for improving an existing feature, or would like to suggest a completely new feature for KeePassX Reboot, please use the Issues section or our [Google Groups](https://groups.google.com/forum/#!forum/keepassx-reboot) forum.
|
||||
We're always looking for suggestions to improve our application. If you have a suggestion to improve an existing feature, or would like to suggest a completely new feature for KeePassXC, please use the [issue tracker on GitHub][issues-section]. For more general discussion, try using our [Google Groups][google-groups] forum.
|
||||
|
||||
### Bug Reports
|
||||
### Bug reports
|
||||
|
||||
Our software isn't always perfect, but we strive to always improve our work. You may file bug reports in the Issues section.
|
||||
Our software isn't always perfect, but we strive to always improve our work. You may file bug reports in the issue tracker.
|
||||
|
||||
Before submitting a Bug Report, check if the problem has already been reported. Please refrain from opening a duplicate issue. If you want to highlight a deficiency on an existing issue, simply add a comment.
|
||||
Before submitting a bug report, check if the problem has already been reported. Please refrain from opening a duplicate issue. If you want to add further information to an existing issue, simply add a comment on that issue.
|
||||
|
||||
### Discuss with the Team
|
||||
### Discuss with the team
|
||||
|
||||
You can talk to the KeePassX Reboot Team about Bugs, new feature, Issue and PullRequests at our [Google Groups](https://groups.google.com/forum/#!forum/keepassx-reboot) forum
|
||||
As with feature requests, you can talk to the KeePassXC team about bugs, new features, other issues and pull requests on the dedicated issue tracker, using the [Google Groups][google-groups] forum, or in the IRC channel on Freenode (`#keepassxc-dev` on `irc.freenode.net`, or use a [webchat link](https://webchat.freenode.net/?channels=%23keepassxc-dev)).
|
||||
|
||||
### Your First Code Contribution
|
||||
### Your first code contribution
|
||||
|
||||
Unsure where to begin contributing to KeePassX Reboot? You can start by looking through these `beginner` and `help-wanted` issues:
|
||||
Unsure where to begin contributing to KeePassXC? You can start by looking through these `beginner` and `help-wanted` issues:
|
||||
|
||||
* [Beginner issues][beginner] - issues which should only require a few lines of code, and a test or two.
|
||||
* [Help wanted issues][help-wanted] - issues which should be a bit more involved than `beginner` issues.
|
||||
* [Beginner issues][beginner] – issues which should only require a few lines of code, and a test or two.
|
||||
* ['Help wanted' issues][help-wanted] – issues which should be a bit more involved than `beginner` issues.
|
||||
|
||||
Both issue lists are sorted by total number of comments. While not perfect, number of comments is a reasonable proxy for impact a given change will have.
|
||||
Both issue lists are sorted by total number of comments. While not perfect, looking at the number of comments on an issue can give a general idea of how much an impact a given change will have.
|
||||
|
||||
### Pull Requests
|
||||
### Pull requests
|
||||
|
||||
Along with our desire to hear your feedback and suggestions, we're also interested in accepting direct assistance in the form of code.
|
||||
|
||||
All pull requests must comply with the above requirements and with the [Styleguides](#styleguides).
|
||||
All pull requests must comply with the above requirements and with the [styleguides](#styleguides).
|
||||
|
||||
### Translations
|
||||
|
||||
@ -86,19 +87,20 @@ Please join an existing language team or request a new one if there is none.
|
||||
|
||||
## Styleguides
|
||||
|
||||
### Git Branch Strategy
|
||||
### Git branch strategy
|
||||
|
||||
The Branch Strategy is based on [git-flow-lite](http://nvie.com/posts/a-successful-git-branching-model/).
|
||||
|
||||
* **master** -> always points to the last release published
|
||||
* **develop** -> points to the next planned release, tested and reviewed code
|
||||
* **feature/**[name] -> points to brand new feature in codebase, candidate for merge into develop (subject to rebase)
|
||||
* **master** – points to the latest public release
|
||||
* **develop** – points to the development of the next release, contains tested and reviewed code
|
||||
* **feature/**[name] – points to a branch with a new feature, one which is candidate for merge into develop (subject to rebase)
|
||||
* **hotfix/**[id]-[description] – points to a branch with a fix for a particular issue ID
|
||||
|
||||
|
||||
### Git Commit Messages
|
||||
### Git commit messages
|
||||
|
||||
* Use the present tense ("Add feature" not "Added feature")
|
||||
* Use the imperative mood ("Move cursor to..." not "Moves cursor to...")
|
||||
* Use the imperative mood ("Move cursor to…" not "Moves cursor to…")
|
||||
* Limit the first line to 72 characters or less
|
||||
* Reference issues and pull requests liberally
|
||||
* When only changing documentation, include `[ci skip]` in the commit description
|
||||
@ -114,21 +116,21 @@ The Branch Strategy is based on [git-flow-lite](http://nvie.com/posts/a-successf
|
||||
* :lock: `:lock:` when dealing with security
|
||||
|
||||
|
||||
### Coding Styleguide
|
||||
### Coding styleguide
|
||||
|
||||
This project follows the [Qt Coding Style](https://wiki.qt.io/Qt_Coding_Style). All submissions are expected to follow this style.
|
||||
|
||||
In particular Code must follow the following specific rules:
|
||||
In particular, code must stick to the following rules:
|
||||
|
||||
#### Naming Convention
|
||||
#### Naming convention
|
||||
`lowerCamelCase`
|
||||
|
||||
For names made of only one word, the fist letter is lowercase.
|
||||
For names made of multiple concatenated words, the first letter is lowercase and each subsequent concatenated word is capitalized.
|
||||
For names made of only one word, the first letter should be lowercase.
|
||||
For names made of multiple concatenated words, the first letter of the whole is lowercase, and the first letter of each subsequent word is capitalized.
|
||||
|
||||
#### Indention
|
||||
For C++ files (.cpp .h): 4 spaces
|
||||
For Qt-UI files (.ui): 2 spaces
|
||||
For **C++ files** (*.cpp .h*): 4 spaces
|
||||
For **Qt-UI files** (*.ui*): 2 spaces
|
||||
|
||||
#### Pointers
|
||||
```c
|
||||
@ -165,9 +167,8 @@ Use prefix: `m_*`
|
||||
|
||||
Example: `m_variable`
|
||||
|
||||
#### GUI Widget names
|
||||
Widget names must be related to the desired program behaviour.
|
||||
Preferably end the name with the Widget Classname
|
||||
#### GUI widget names
|
||||
Widget names must be related to the desired program behavior, and preferably end with the widget's classname.
|
||||
|
||||
Example: `<widget class="QCheckBox" name="rememberCheckBox">`
|
||||
|
||||
@ -175,3 +176,5 @@ Example: `<widget class="QCheckBox" name="rememberCheckBox">`
|
||||
|
||||
[beginner]:https://github.com/keepassxreboot/keepassx/issues?q=is%3Aopen+is%3Aissue+label%3Abeginner+label%3A%22help+wanted%22+sort%3Acomments-desc
|
||||
[help-wanted]:https://github.com/keepassxreboot/keepassx/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22+sort%3Acomments-desc
|
||||
[issues-section]:https://github.com/keepassxreboot/keepassxc/issues
|
||||
[google-groups]:https://groups.google.com/forum/#!forum/keepassx-reboot
|
||||
|
19
.travis.yml
19
.travis.yml
@ -10,12 +10,11 @@ os:
|
||||
# Define clang compiler without any frills
|
||||
compiler:
|
||||
- clang
|
||||
- gcc
|
||||
|
||||
# Define gcc compile with deploy option (only for master/develop merges)
|
||||
matrix:
|
||||
include:
|
||||
- compiler: gcc
|
||||
env: DEPLOY=1
|
||||
env:
|
||||
- CONFIG=Release
|
||||
- CONFIG=Debug
|
||||
|
||||
git:
|
||||
depth: 3
|
||||
@ -33,14 +32,14 @@ before_script:
|
||||
- mkdir build && pushd build
|
||||
|
||||
script:
|
||||
- cmake -DCMAKE_BUILD_TYPE=Release -DWITH_GUI_TESTS=ON -DWITH_XC_HTTP=ON -DWITH_XC_AUTOTYPE=ON -DWITH_XC_YUBIKEY=ON $CMAKE_ARGS ..
|
||||
- cmake -DCMAKE_BUILD_TYPE=${CONFIG} -DWITH_GUI_TESTS=ON -DWITH_XC_HTTP=ON -DWITH_XC_AUTOTYPE=ON -DWITH_XC_YUBIKEY=ON $CMAKE_ARGS ..
|
||||
- make -j2
|
||||
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then make test ARGS+="-E testgui --output-on-failure"; fi
|
||||
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then xvfb-run -a --server-args="-screen 0 800x600x24" make test ARGS+="-R testgui --output-on-failure"; fi
|
||||
- if [ "$TRAVIS_OS_NAME" = "osx" ]; then make test ARGS+="--output-on-failure"; fi
|
||||
|
||||
# Generate snapcraft build when merging into master/develop branches
|
||||
after_success:
|
||||
- popd
|
||||
- "[[ $DEPLOY = 1 ]] && [[ $TRAVIS_BRANCH =~ (master|develop) ]] && [[ $TRAVIS_PULL_REQUEST = false ]] \
|
||||
&& docker run -v $(pwd):/cwd snapcore/snapcraft sh -c 'cd /cwd && apt update && snapcraft'"
|
||||
#after_success:
|
||||
# - popd
|
||||
# - "[[ $DEPLOY = 1 ]] && [[ $CONFIG = Release ]] && [[ $TRAVIS_BRANCH =~ (master|develop) ]] && [[ $TRAVIS_PULL_REQUEST = false ]] \
|
||||
# && docker run -v $(pwd):/cwd snapcore/snapcraft sh -c 'cd /cwd && apt update && snapcraft'"
|
||||
|
92
AppImage-Recipe.sh
Executable file
92
AppImage-Recipe.sh
Executable file
@ -0,0 +1,92 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# KeePassXC AppImage Recipe
|
||||
# Copyright (C) 2017 KeePassXC team <https://keepassxc.org/>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 2 or (at your option)
|
||||
# version 3 of the License.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
if [ "$1" == "" ] || [ "$2" == "" ]; then
|
||||
echo "Usage: $(basename $0) APP_NAME RELEASE_VERSION" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -f CHANGELOG ]; then
|
||||
echo "This recipe must not be run from the sources root." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -d ../bin-release ]; then
|
||||
echo "../bin-release does not exist." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
APP="$1"
|
||||
LOWERAPP="$(echo "$APP" | tr '[:upper:]' '[:lower:]')"
|
||||
VERSION="$2"
|
||||
|
||||
mkdir -p $APP.AppDir
|
||||
wget -q https://github.com/probonopd/AppImages/raw/master/functions.sh -O ./functions.sh
|
||||
. ./functions.sh
|
||||
|
||||
LIB_DIR=./usr/lib
|
||||
if [ -d ./usr/lib/x86_64-linux-gnu ]; then
|
||||
LIB_DIR=./usr/lib/x86_64-linux-gnu
|
||||
fi
|
||||
|
||||
cd $APP.AppDir
|
||||
cp -a ../../bin-release/* .
|
||||
cp -a ./usr/local/* ./usr
|
||||
rm -R ./usr/local
|
||||
rmdir ./opt 2> /dev/null
|
||||
patch_strings_in_file /usr/local ././
|
||||
patch_strings_in_file /usr ./
|
||||
|
||||
# bundle Qt platform plugins and themes
|
||||
QXCB_PLUGIN="$(find /usr/lib -name 'libqxcb.so' 2> /dev/null)"
|
||||
if [ "$QXCB_PLUGIN" == "" ]; then
|
||||
QXCB_PLUGIN="$(find /opt/qt*/plugins -name 'libqxcb.so' 2> /dev/null)"
|
||||
fi
|
||||
QT_PLUGIN_PATH="$(dirname $(dirname $QXCB_PLUGIN))"
|
||||
mkdir -p ".${QT_PLUGIN_PATH}/platforms"
|
||||
cp "$QXCB_PLUGIN" ".${QT_PLUGIN_PATH}/platforms/"
|
||||
|
||||
get_apprun
|
||||
copy_deps
|
||||
delete_blacklisted
|
||||
|
||||
# remove dbus and systemd libs as they are not blacklisted
|
||||
find . -name libdbus-1.so.3 -exec rm {} \;
|
||||
find . -name libsystemd.so.0 -exec rm {} \;
|
||||
|
||||
get_desktop
|
||||
get_icon
|
||||
cat << EOF > ./usr/bin/keepassxc_env
|
||||
#!/usr/bin/env bash
|
||||
#export QT_QPA_PLATFORMTHEME=gtk2
|
||||
export LD_LIBRARY_PATH="../opt/qt58/lib:\${LD_LIBRARY_PATH}"
|
||||
export QT_PLUGIN_PATH="..${QT_PLUGIN_PATH}"
|
||||
exec keepassxc "\$@"
|
||||
EOF
|
||||
chmod +x ./usr/bin/keepassxc_env
|
||||
sed -i 's/Exec=keepassxc/Exec=keepassxc_env/' keepassxc.desktop
|
||||
get_desktopintegration $LOWERAPP
|
||||
|
||||
GLIBC_NEEDED=$(glibc_needed)
|
||||
|
||||
cd ..
|
||||
|
||||
generate_type2_appimage
|
||||
|
||||
mv ../out/*.AppImage ..
|
||||
rmdir ../out > /dev/null 2>&1
|
38
CHANGELOG
38
CHANGELOG
@ -1,3 +1,41 @@
|
||||
2.1.1 (2017-02-06)
|
||||
=========================
|
||||
|
||||
- Enabled HTTP plugin build; plugin is disabled by default and limited to localhost [#147]
|
||||
- Escape HTML in dialog boxes [#247]
|
||||
- Corrected crashes in favicon download and password generator [#233, #226]
|
||||
- Increase font size of password meter [#228]
|
||||
- Fixed compatibility with Qt 5.8 [#211]
|
||||
- Use consistent button heights in password generator [#229]
|
||||
|
||||
2.1.0 (2017-01-22)
|
||||
=========================
|
||||
|
||||
- Show unlock dialog when using autotype on a closed database [#10, #89]
|
||||
- Show different tray icon when database is locked [#37, #46]
|
||||
- Support autotype on Windows and OS X [#42, #60, #63]
|
||||
- Add delay feature to autotype [#76, #77]
|
||||
- Add password strength meter [#84, #92]
|
||||
- Add option for automatically locking the database when minimizing
|
||||
the window [#57]
|
||||
- Add feature to download favicons and use them as entry icons [#30]
|
||||
- Automatically reload and merge database when the file changed on
|
||||
disk [#22, #33, #93]
|
||||
- Add tool for merging two databases [#22, #47, #143]
|
||||
- Add --pw-stdin commandline option to unlock the database by providing
|
||||
a password on STDIN [#54]
|
||||
- Add utility script for reading the database password from KWallet [#55]
|
||||
- Fix some KeePassHTTP settings not being remembered [#34, #65]
|
||||
- Make search box persistent [#15, #67, #157]
|
||||
- Enhance search feature by scoping the search to selected group [#16, #118]
|
||||
- Improve interaction between search field and entry list [#131, #141]
|
||||
- Add stand-alone password-generator [#18, #92]
|
||||
- Don't require password repetition when password is visible [#27, #92]
|
||||
- Add support for entry attributes in autotype sequences [#107]
|
||||
- Always focus password field when opening the database unlock widget [#116, #117]
|
||||
- Fix compilation errors on various platforms [#53, #126, #130]
|
||||
- Restructure and improve kdbx-extract utility [#160]
|
||||
|
||||
2.0.3 (2016-09-04)
|
||||
=========================
|
||||
|
||||
|
@ -38,8 +38,8 @@ option(WITH_XC_AUTOTYPE "Include Autotype." OFF)
|
||||
option(WITH_XC_HTTP "Include KeePassHTTP." OFF)
|
||||
option(WITH_XC_YUBIKEY "Include Yubikey support." OFF)
|
||||
|
||||
set(KEEPASSXC_VERSION "2.1.0")
|
||||
set(KEEPASSXC_VERSION_NUM "2.1.0")
|
||||
set(KEEPASSXC_VERSION "2.1.1")
|
||||
set(KEEPASSXC_VERSION_NUM "2.1.1")
|
||||
|
||||
if("${CMAKE_C_COMPILER}" MATCHES "clang$" OR "${CMAKE_C_COMPILER_ID}" STREQUAL "Clang")
|
||||
set(CMAKE_COMPILER_IS_CLANG 1)
|
||||
@ -172,9 +172,13 @@ set(CMAKE_AUTOMOC ON)
|
||||
# Make sure we don't enable asserts there.
|
||||
set_property(DIRECTORY APPEND PROPERTY COMPILE_DEFINITIONS_NONE QT_NO_DEBUG)
|
||||
|
||||
find_package(LibGPGError REQUIRED)
|
||||
|
||||
find_package(Gcrypt 1.6.0 REQUIRED)
|
||||
|
||||
find_package(LibMicroHTTPD REQUIRED)
|
||||
if (WITH_XC_HTTP)
|
||||
find_package(LibMicroHTTPD REQUIRED)
|
||||
endif(WITH_XC_HTTP)
|
||||
|
||||
find_package(ZLIB REQUIRED)
|
||||
|
||||
|
6
COPYING
6
COPYING
@ -198,3 +198,9 @@ Files: src/zxcvbn/zxcvbn.*
|
||||
Copyright: 2015, Tony Evans
|
||||
2016, KeePassXC Team
|
||||
License: BSD 3-clause
|
||||
|
||||
Files: src/gui/KMessageWidget.h
|
||||
src/gui/KMessageWidget.cpp
|
||||
Copyright: 2011 Aurélien Gâteau <agateau@kde.org>
|
||||
2014 Dominik Haumann <dhaumann@kde.org>
|
||||
License: LGPL-2.1
|
||||
|
54
Dockerfile
Normal file
54
Dockerfile
Normal file
@ -0,0 +1,54 @@
|
||||
# KeePassXC Linux Release Build Dockerfile
|
||||
# Copyright (C) 2017 KeePassXC team <https://keepassxc.org/>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 2 or (at your option)
|
||||
# version 3 of the License.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
FROM ubuntu:14.04
|
||||
|
||||
RUN set -x \
|
||||
&& apt-get update \
|
||||
&& apt-get install --yes software-properties-common
|
||||
|
||||
RUN set -x \
|
||||
&& add-apt-repository --yes ppa:beineri/opt-qt58-trusty
|
||||
|
||||
RUN set -x \
|
||||
&& apt-get update \
|
||||
&& apt-get install --yes \
|
||||
g++ \
|
||||
cmake \
|
||||
libgcrypt20-dev \
|
||||
qt58base \
|
||||
qt58tools \
|
||||
qt58x11extras \
|
||||
libmicrohttpd-dev \
|
||||
libxi-dev \
|
||||
libxtst-dev \
|
||||
zlib1g-dev \
|
||||
wget \
|
||||
file \
|
||||
fuse \
|
||||
python
|
||||
|
||||
RUN set -x \
|
||||
&& apt-get install --yes mesa-common-dev
|
||||
|
||||
VOLUME /keepassxc/src
|
||||
VOLUME /keepassxc/out
|
||||
WORKDIR /keepassxc
|
||||
|
||||
ENV CMAKE_PREFIX_PATH=/opt/qt58/lib/cmake
|
||||
ENV LD_LIBRARY_PATH=/opt/qt58/lib
|
||||
RUN set -x \
|
||||
&& echo /opt/qt58/lib > /etc/ld.so.conf.d/qt58.conf
|
64
README.md
64
README.md
@ -3,57 +3,63 @@
|
||||
[](https://travis-ci.org/keepassxreboot/keepassxc) [](https://coveralls.io/github/keepassxreboot/keepassxc)
|
||||
|
||||
## About
|
||||
|
||||
Fork of [KeePassX](https://www.keepassx.org/) that [aims to incorporate stalled Pull Requests, features, and bug fixes that are not being incorporated into the main KeePassX baseline](https://github.com/keepassxreboot/keepassx/issues/43).
|
||||
KeePassXC is a fork of [KeePassX](https://www.keepassx.org/) that [aims to incorporate stalled pull requests, features, and bug fixes that have never made it into the main KeePassX repository](https://github.com/keepassxreboot/keepassx/issues/43).
|
||||
|
||||
|
||||
#### Additional Reboot Features
|
||||
- keepasshttp support for use with [PassIFox](https://addons.mozilla.org/en-us/firefox/addon/passifox/) for Mozilla Firefox and [chromeIPass](https://chrome.google.com/webstore/detail/chromeipass/ompiailgknfdndiefoaoiligalphfdae) for Google Chrome.
|
||||
## Additional features compared to KeePassX
|
||||
- Autotype on all three major platforms (Linux, Windows, OS X)
|
||||
- Stand-alone password generator
|
||||
- Password strength meter
|
||||
- Use website's favicons as entry icons
|
||||
- Merging of databases
|
||||
- Automatic reload when the database changed on disk
|
||||
- KeePassHTTP support for use with [PassIFox](https://addons.mozilla.org/en-us/firefox/addon/passifox/) in Mozilla Firefox and [chromeIPass](https://chrome.google.com/webstore/detail/chromeipass/ompiailgknfdndiefoaoiligalphfdae) in Google Chrome or Chromium.
|
||||
|
||||
KeePassHttp implementation has been forked from jdachtera's repository, which in turn was based on code from code with Francois Ferrand's [keepassx-http](https://gitorious.org/keepassx/keepassx-http/source/master) repository.
|
||||
|
||||
This is a rebuild from [denk-mal's keepasshttp](https://github.com/denk-mal/keepassx.git) that brings it forward to Qt5 and KeePassX v2.x.
|
||||
For a full list of features and changes, read the [CHANGELOG](CHANGELOG) document.
|
||||
|
||||
### Note about KeePassHTTP
|
||||
KeePassHTTP is not a highly secure protocol and has certain flaw which allow an attacker to decrypt your passwords when they manage to intercept communication between a KeePassHTTP server and PassIFox/chromeIPass over a network connection (see [here](https://github.com/pfn/keepasshttp/issues/258) and [here](https://github.com/keepassxreboot/keepassxc/issues/147)). KeePassXC therefore strictly limits communication between itself and the browser plugin to your local computer. As long as your computer is not compromised, your passwords are fairly safe that way, but still use it at your own risk!
|
||||
|
||||
### Installation
|
||||
Pre-compiled binaries can be found on the [downloads page](https://keepassxc.org/download). Additionally, individual Linux distributions may ship their own versions, so please check out your distribution's package list to see if KeePassXC is available.
|
||||
|
||||
Right now KeePassXC does not have a precompiled executable or an installation package.<br/>
|
||||
So you must install it from its source code.
|
||||
### Building KeePassXC yourself
|
||||
|
||||
**More detailed instructions are available in the INSTALL file or at the [Wiki page](https://github.com/keepassxreboot/keepassx/wiki/Install-Instruction-from-Source).**
|
||||
*More detailed instructions are available in the INSTALL file or on the [Wiki page](https://github.com/keepassxreboot/keepassx/wiki/Install-Instruction-from-Source).*
|
||||
|
||||
First you must download the KeePassXC source code as ZIP file or with Git.
|
||||
First, you must download the KeePassXC [source tarball](https://keepassxc.org/download#source) or check out the latest version from our [Git repository](https://github.com/keepassxreboot/keepassxc).
|
||||
|
||||
Generally you can build and install KeePassXC with the following commands from a Terminal in the KeePassXC folder
|
||||
```
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DWITH_TESTS=OFF ..
|
||||
make
|
||||
sudo make install
|
||||
```
|
||||
|
||||
|
||||
### Clone Repository
|
||||
|
||||
Clone the repository to a suitable location where you can extend and build this project.
|
||||
To clone the project from Git, `cd` to a suitable location and run
|
||||
|
||||
```bash
|
||||
git clone https://github.com/keepassxreboot/keepassxc.git
|
||||
```
|
||||
|
||||
**Note:** This will clone the entire contents of the repository at the HEAD revision.
|
||||
This will clone the entire contents of the repository and check out the current `develop` branch.
|
||||
|
||||
To update the project from within the project's folder you can run the following command:
|
||||
To update the project from within the project's folder, you can run the following command:
|
||||
|
||||
```bash
|
||||
git pull
|
||||
```
|
||||
|
||||
Once you have downloaded the source code, you can `cd` into the source code directory and build and install KeePassXC with
|
||||
|
||||
```
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DWITH_TESTS=OFF ..
|
||||
make -j8
|
||||
sudo make install
|
||||
```
|
||||
|
||||
To enable autotype, add `-DWITH_XC_AUTOTYPE=ON` to the `cmake` command. KeePassHTTP support is compiled in by adding `-DWITH_XC_HTTP=ON`. If these options are not specified, KeePassXC will be built without these plugins.
|
||||
|
||||
|
||||
### Contributing
|
||||
|
||||
We're always looking for suggestions to improve our application. If you have a suggestion for improving an existing feature,
|
||||
or would like to suggest a completely new feature for KeePassX Reboot, please use the [Issues](https://github.com/keepassxreboot/keepassxc/issues) section or our [Google Groups](https://groups.google.com/forum/#!forum/keepassx-reboot) forum.
|
||||
We are always looking for suggestions how to improve our application. If you find any bugs or have an idea for a new feature, please let us know by opening a report in our [issue tracker](https://github.com/keepassxreboot/keepassxc/issues) on GitHub or write to our [Google Groups](https://groups.google.com/forum/#!forum/keepassx-reboot) forum.
|
||||
|
||||
Please review the [CONTRIBUTING](.github/CONTRIBUTING.md) document for further information.
|
||||
You can of course also directly contribute your own code. We are happy to accept your pull requests.
|
||||
|
||||
Please read the [CONTRIBUTING](.github/CONTRIBUTING.md) document for further information.
|
||||
|
9
cmake/FindLibGPGError.cmake
Normal file
9
cmake/FindLibGPGError.cmake
Normal file
@ -0,0 +1,9 @@
|
||||
|
||||
find_path(GPGERROR_INCLUDE_DIR gpg-error.h)
|
||||
|
||||
find_library(GPGERROR_LIBRARIES gpg-error)
|
||||
|
||||
mark_as_advanced(GPGERROR_LIBRARIES GPGERROR_INCLUDE_DIR)
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(LibGPGError DEFAULT_MSG GPGERROR_LIBRARIES GPGERROR_INCLUDE_DIR)
|
683
release-tool
Executable file
683
release-tool
Executable file
@ -0,0 +1,683 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# KeePassXC Release Preparation Helper
|
||||
# Copyright (C) 2017 KeePassXC team <https://keepassxc.org/>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 2 or (at your option)
|
||||
# version 3 of the License.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
printf "\e[1m\e[32mKeePassXC\e[0m Release Preparation Helper\n"
|
||||
printf "Copyright (C) 2017 KeePassXC Team <https://keepassxc.org/>\n\n"
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# global default values
|
||||
# -----------------------------------------------------------------------
|
||||
RELEASE_NAME=""
|
||||
APP_NAME="KeePassXC"
|
||||
SRC_DIR="."
|
||||
GPG_KEY="CFB4C2166397D0D2"
|
||||
GPG_GIT_KEY=""
|
||||
OUTPUT_DIR="release"
|
||||
SOURCE_BRANCH=""
|
||||
TARGET_BRANCH="master"
|
||||
TAG_NAME=""
|
||||
DOCKER_IMAGE=""
|
||||
DOCKER_CONTAINER_NAME="keepassxc-build-container"
|
||||
CMAKE_OPTIONS=""
|
||||
COMPILER="g++"
|
||||
MAKE_OPTIONS="-j8"
|
||||
BUILD_PLUGINS="autotype http"
|
||||
INSTALL_PREFIX="/usr/local"
|
||||
BUILD_SOURCE_TARBALL=true
|
||||
ORIG_BRANCH=""
|
||||
ORIG_CWD="$(pwd)"
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# helper functions
|
||||
# -----------------------------------------------------------------------
|
||||
printUsage() {
|
||||
local cmd
|
||||
if [ "" == "$1" ] || [ "help" == "$1" ]; then
|
||||
cmd="COMMAND"
|
||||
elif [ "merge" == "$1" ] || [ "build" == "$1" ] || [ "sign" == "$1" ]; then
|
||||
cmd="$1"
|
||||
else
|
||||
logError "Unknown command: '$1'\n"
|
||||
cmd="COMMAND"
|
||||
fi
|
||||
|
||||
printf "\e[1mUsage:\e[0m $(basename $0) $cmd [options]\n"
|
||||
|
||||
if [ "COMMAND" == "$cmd" ]; then
|
||||
cat << EOF
|
||||
|
||||
Commands:
|
||||
merge Merge release branch into main branch and create release tags
|
||||
build Build and package binary release from sources
|
||||
sign Sign previously compiled release packages
|
||||
help Show help for the given command
|
||||
EOF
|
||||
elif [ "merge" == "$cmd" ]; then
|
||||
cat << EOF
|
||||
|
||||
Merge release branch into main branch and create release tags
|
||||
|
||||
Options:
|
||||
-v, --version Release version number or name (required)
|
||||
-a, --app-name Application name (default: '${APP_NAME}')
|
||||
-s, --source-dir Source directory (default: '${SRC_DIR}')
|
||||
-g, --gpg-key GPG key used to sign the merge commit and release tag,
|
||||
leave empty to let Git choose your default key
|
||||
(default: '${GPG_GIT_KEY}')
|
||||
-r, --release-branch Source release branch to merge from (default: 'release/VERSION')
|
||||
--target-branch Target branch to merge to (default: '${TARGET_BRANCH}')
|
||||
-t, --tag-name Override release tag name (defaults to version number)
|
||||
-h, --help Show this help
|
||||
EOF
|
||||
elif [ "build" == "$cmd" ]; then
|
||||
cat << EOF
|
||||
|
||||
Build and package binary release from sources
|
||||
|
||||
Options:
|
||||
-v, --version Release version number or name (required)
|
||||
-a, --app-name Application name (default: '${APP_NAME}')
|
||||
-s, --source-dir Source directory (default: '${SRC_DIR}')
|
||||
-o, --output-dir Output directory where to build the release
|
||||
(default: '${OUTPUT_DIR}')
|
||||
-t, --tag-name Release tag to check out (defaults to version number)
|
||||
-b, --build Build sources after exporting release
|
||||
-d, --docker-image Use the specified Docker image to compile the application.
|
||||
The image must have all required build dependencies installed.
|
||||
This option has no effect if --build is not set.
|
||||
--container-name Docker container name (default: '${DOCKER_CONTAINER_NAME}')
|
||||
The container must not exist already
|
||||
-c, --cmake-options Additional CMake options for compiling the sources
|
||||
--compiler Compiler to use (default: '${COMPILER}')
|
||||
-m, --make-options Make options for compiling sources (default: '${MAKE_OPTIONS}')
|
||||
-i, --install-prefix Install prefix (default: '${INSTALL_PREFIX}')
|
||||
-p, --plugins Space-separated list of plugins to build
|
||||
(default: ${BUILD_PLUGINS})
|
||||
-n, --no-source-tarball Don't build source tarball
|
||||
-h, --help Show this help
|
||||
EOF
|
||||
elif [ "sign" == "$cmd" ]; then
|
||||
cat << EOF
|
||||
|
||||
Sign previously compiled release packages
|
||||
|
||||
Options:
|
||||
-f, --files Files to sign (required)
|
||||
-g, --gpg-key GPG key used to sign the files (default: '${GPG_KEY}')
|
||||
-h, --help Show this help
|
||||
EOF
|
||||
fi
|
||||
}
|
||||
|
||||
logInfo() {
|
||||
printf "\e[1m[ \e[34mINFO\e[39m ]\e[0m $1\n"
|
||||
}
|
||||
|
||||
logError() {
|
||||
printf "\e[1m[ \e[31mERROR\e[39m ]\e[0m $1\n" >&2
|
||||
}
|
||||
|
||||
init() {
|
||||
ORIG_CWD="$(pwd)"
|
||||
cd "$SRC_DIR" > /dev/null 2>&1
|
||||
ORIG_BRANCH="$(git rev-parse --abbrev-ref HEAD 2> /dev/null)"
|
||||
cd "$ORIG_CWD"
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
logInfo "Checking out original branch..."
|
||||
if [ "" != "$ORIG_BRANCH" ]; then
|
||||
git checkout "$ORIG_BRANCH" > /dev/null 2>&1
|
||||
fi
|
||||
logInfo "Leaving source directory..."
|
||||
cd "$ORIG_CWD"
|
||||
}
|
||||
|
||||
exitError() {
|
||||
logError "$1"
|
||||
cleanup
|
||||
exit 1
|
||||
}
|
||||
|
||||
exitTrap() {
|
||||
exitError "Existing upon user request..."
|
||||
}
|
||||
|
||||
checkSourceDirExists() {
|
||||
if [ ! -d "$SRC_DIR" ]; then
|
||||
exitError "Source directory '${SRC_DIR}' does not exist!"
|
||||
fi
|
||||
}
|
||||
|
||||
checkOutputDirDoesNotExist() {
|
||||
if [ -e "$OUTPUT_DIR" ]; then
|
||||
exitError "Output directory '$OUTPUT_DIR' already exists. Please choose a different location!"
|
||||
fi
|
||||
}
|
||||
|
||||
checkGitRepository() {
|
||||
if [ ! -d .git ] || [ ! -f CHANGELOG ]; then
|
||||
exitError "Source directory is not a valid Git repository!"
|
||||
fi
|
||||
}
|
||||
|
||||
checkTagExists() {
|
||||
git tag | grep -q "$TAG_NAME"
|
||||
if [ $? -ne 0 ]; then
|
||||
exitError "Tag '${TAG_NAME}' does not exist!"
|
||||
fi
|
||||
}
|
||||
|
||||
checkReleaseDoesNotExist() {
|
||||
git tag | grep -q "$TAG_NAME"
|
||||
if [ $? -eq 0 ]; then
|
||||
exitError "Release '$RELEASE_NAME' (tag: '$TAG_NAME') already exists!"
|
||||
fi
|
||||
}
|
||||
|
||||
checkWorkingTreeClean() {
|
||||
git diff-index --quiet HEAD --
|
||||
if [ $? -ne 0 ]; then
|
||||
exitError "Current working tree is not clean! Please commit or unstage any changes."
|
||||
fi
|
||||
}
|
||||
|
||||
checkSourceBranchExists() {
|
||||
git rev-parse "$SOURCE_BRANCH" > /dev/null 2>&1
|
||||
if [ $? -ne 0 ]; then
|
||||
exitError "Source branch '$SOURCE_BRANCH' does not exist!"
|
||||
fi
|
||||
}
|
||||
|
||||
checkTargetBranchExists() {
|
||||
git rev-parse "$TARGET_BRANCH" > /dev/null 2>&1
|
||||
if [ $? -ne 0 ]; then
|
||||
exitError "Target branch '$TARGET_BRANCH' does not exist!"
|
||||
fi
|
||||
}
|
||||
|
||||
checkVersionInCMake() {
|
||||
local app_name_upper="$(echo "$APP_NAME" | tr '[:lower:]' '[:upper:]')"
|
||||
|
||||
grep -q "${app_name_upper}_VERSION \"${RELEASE_NAME}\"" CMakeLists.txt
|
||||
if [ $? -ne 0 ]; then
|
||||
exitError "${app_name_upper}_VERSION version not updated to '${RELEASE_NAME}' in CMakeLists.txt!"
|
||||
fi
|
||||
|
||||
grep -q "${app_name_upper}_VERSION_NUM \"${RELEASE_NAME}\"" CMakeLists.txt
|
||||
if [ $? -ne 0 ]; then
|
||||
exitError "${app_name_upper}_VERSION_NUM version not updated to '${RELEASE_NAME}' in CMakeLists.txt!"
|
||||
fi
|
||||
}
|
||||
|
||||
checkChangeLog() {
|
||||
if [ ! -f CHANGELOG ]; then
|
||||
exitError "No CHANGELOG file found!"
|
||||
fi
|
||||
|
||||
grep -qPzo "${RELEASE_NAME} \(\d{4}-\d{2}-\d{2}\)\n=+\n" CHANGELOG
|
||||
if [ $? -ne 0 ]; then
|
||||
exitError "CHANGELOG does not contain any information about the '${RELEASE_NAME}' release!"
|
||||
fi
|
||||
}
|
||||
|
||||
checkTransifexCommandExists() {
|
||||
command -v tx > /dev/null
|
||||
if [ 0 -ne $? ]; then
|
||||
exitError "Transifex tool 'tx' not installed! Please install it using 'pip install transifex-client'"
|
||||
fi
|
||||
}
|
||||
|
||||
# re-implement realpath for OS X (thanks mschrag)
|
||||
# https://superuser.com/questions/205127/
|
||||
if ! $(command -v realpath > /dev/null); then
|
||||
realpath() {
|
||||
pushd . > /dev/null
|
||||
if [ -d "$1" ]; then
|
||||
cd "$1"
|
||||
dirs -l +0
|
||||
else
|
||||
cd "$(dirname "$1")"
|
||||
cur_dir=$(dirs -l +0)
|
||||
|
||||
if [ "$cur_dir" == "/" ]; then
|
||||
echo "$cur_dir$(basename "$1")"
|
||||
else
|
||||
echo "$cur_dir/$(basename "$1")"
|
||||
fi
|
||||
fi
|
||||
popd > /dev/null
|
||||
}
|
||||
fi
|
||||
|
||||
|
||||
trap exitTrap SIGINT SIGTERM
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# merge command
|
||||
# -----------------------------------------------------------------------
|
||||
merge() {
|
||||
while [ $# -ge 1 ]; do
|
||||
local arg="$1"
|
||||
case "$arg" in
|
||||
-v|--version)
|
||||
RELEASE_NAME="$2"
|
||||
shift ;;
|
||||
|
||||
-a|--app-name)
|
||||
APP_NAME="$2"
|
||||
shift ;;
|
||||
|
||||
-s|--source-dir)
|
||||
SRC_DIR="$2"
|
||||
shift ;;
|
||||
|
||||
-g|--gpg-key)
|
||||
GPG_GIT_KEY="$2"
|
||||
shift ;;
|
||||
|
||||
-r|--release-branch)
|
||||
SOURCE_BRANCH="$2"
|
||||
shift ;;
|
||||
|
||||
--target-branch)
|
||||
TARGET_BRANCH="$2"
|
||||
shift ;;
|
||||
|
||||
-t|--tag-name)
|
||||
TAG_NAME="$2"
|
||||
shift ;;
|
||||
|
||||
-h|--help)
|
||||
printUsage "merge"
|
||||
exit ;;
|
||||
|
||||
*)
|
||||
logError "Unknown option '$arg'\n"
|
||||
printUsage "merge"
|
||||
exit 1 ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
if [ "" == "$RELEASE_NAME" ]; then
|
||||
logError "Missing arguments, --version is required!\n"
|
||||
printUsage "merge"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "" == "$TAG_NAME" ]; then
|
||||
TAG_NAME="$RELEASE_NAME"
|
||||
fi
|
||||
|
||||
if [ "" == "$SOURCE_BRANCH" ]; then
|
||||
SOURCE_BRANCH="release/${RELEASE_NAME}"
|
||||
fi
|
||||
|
||||
init
|
||||
|
||||
SRC_DIR="$(realpath "$SRC_DIR")"
|
||||
|
||||
logInfo "Performing basic checks..."
|
||||
|
||||
checkSourceDirExists
|
||||
|
||||
logInfo "Changing to source directory..."
|
||||
cd "${SRC_DIR}"
|
||||
|
||||
checkTransifexCommandExists
|
||||
checkGitRepository
|
||||
checkReleaseDoesNotExist
|
||||
checkWorkingTreeClean
|
||||
checkSourceBranchExists
|
||||
checkTargetBranchExists
|
||||
|
||||
logInfo "Checking out source branch '${SOURCE_BRANCH}'..."
|
||||
git checkout "$SOURCE_BRANCH"
|
||||
|
||||
checkVersionInCMake
|
||||
checkChangeLog
|
||||
|
||||
logInfo "All checks pass, getting our hands dirty now!"
|
||||
|
||||
logInfo "Updating language files..."
|
||||
./share/translations/update.sh
|
||||
if [ 0 -ne $? ]; then
|
||||
exitError "Updating translations failed!"
|
||||
fi
|
||||
git diff-index --quiet HEAD --
|
||||
if [ $? -ne 0 ]; then
|
||||
git add ./share/translations/*
|
||||
logInfo "Committing changes..."
|
||||
if [ "" == "$GPG_GIT_KEY" ]; then
|
||||
git commit -m "Update translations"
|
||||
else
|
||||
git commit -m "Update translations" -S"$GPG_GIT_KEY"
|
||||
fi
|
||||
fi
|
||||
|
||||
CHANGELOG=$(grep -Pzo "(?<=${RELEASE_NAME} \(\d{4}-\d{2}-\d{2}\)\n)=+\n\n?(?:.|\n)+?\n(?=\n)" \
|
||||
CHANGELOG | grep -Pzo '(?<=\n\n)(.|\n)+' | tr -d \\0)
|
||||
COMMIT_MSG="Release ${RELEASE_NAME}"
|
||||
|
||||
logInfo "Checking out target branch '${TARGET_BRANCH}'..."
|
||||
git checkout "$TARGET_BRANCH"
|
||||
|
||||
logInfo "Merging '${SOURCE_BRANCH}' into '${TARGET_BRANCH}'..."
|
||||
|
||||
git merge "$SOURCE_BRANCH" --no-ff -m "$COMMIT_MSG" -m "${CHANGELOG}" "$SOURCE_BRANCH" -S"$GPG_GIT_KEY"
|
||||
|
||||
logInfo "Creating tag '${TAG_NAME}'..."
|
||||
if [ "" == "$GPG_GIT_KEY" ]; then
|
||||
git tag -a "$TAG_NAME" -m "$COMMIT_MSG" -m "${CHANGELOG}" -s
|
||||
else
|
||||
git tag -a "$TAG_NAME" -m "$COMMIT_MSG" -m "${CHANGELOG}" -s -u "$GPG_GIT_KEY"
|
||||
fi
|
||||
|
||||
cleanup
|
||||
|
||||
logInfo "All done!"
|
||||
logInfo "Please merge the release branch back into the develop branch now and then push your changes."
|
||||
logInfo "Don't forget to also push the tags using \e[1mgit push --tags\e[0m."
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# build command
|
||||
# -----------------------------------------------------------------------
|
||||
build() {
|
||||
while [ $# -ge 1 ]; do
|
||||
local arg="$1"
|
||||
case "$arg" in
|
||||
-v|--version)
|
||||
RELEASE_NAME="$2"
|
||||
shift ;;
|
||||
|
||||
-a|--app-name)
|
||||
APP_NAME="$2"
|
||||
shift ;;
|
||||
|
||||
-s|--source-dir)
|
||||
SRC_DIR="$2"
|
||||
shift ;;
|
||||
|
||||
-o|--output-dir)
|
||||
OUTPUT_DIR="$2"
|
||||
shift ;;
|
||||
|
||||
-t|--tag-name)
|
||||
TAG_NAME="$2"
|
||||
shift ;;
|
||||
|
||||
-d|--docker-image)
|
||||
DOCKER_IMAGE="$2"
|
||||
shift ;;
|
||||
|
||||
--container-name)
|
||||
DOCKER_CONTAINER_NAME="$2"
|
||||
shift ;;
|
||||
|
||||
-c|--cmake-options)
|
||||
CMAKE_OPTIONS="$2"
|
||||
shift ;;
|
||||
|
||||
--compiler)
|
||||
COMPILER="$2"
|
||||
shift ;;
|
||||
|
||||
-m|--make-options)
|
||||
MAKE_OPTIONS="$2"
|
||||
shift ;;
|
||||
|
||||
-i|--install-prefix)
|
||||
INSTALL_PREFIX="$2"
|
||||
shift ;;
|
||||
|
||||
-p|--plugins)
|
||||
BUILD_PLUGINS="$2"
|
||||
shift ;;
|
||||
|
||||
-n|--no-source-tarball)
|
||||
BUILD_SOURCE_TARBALL=false ;;
|
||||
|
||||
-h|--help)
|
||||
printUsage "build"
|
||||
exit ;;
|
||||
|
||||
*)
|
||||
logError "Unknown option '$arg'\n"
|
||||
printUsage "build"
|
||||
exit 1 ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
if [ "" == "$RELEASE_NAME" ]; then
|
||||
logError "Missing arguments, --version is required!\n"
|
||||
printUsage "build"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "" == "$TAG_NAME" ]; then
|
||||
TAG_NAME="$RELEASE_NAME"
|
||||
fi
|
||||
|
||||
init
|
||||
|
||||
SRC_DIR="$(realpath "$SRC_DIR")"
|
||||
OUTPUT_DIR="$(realpath "$OUTPUT_DIR")"
|
||||
|
||||
logInfo "Performing basic checks..."
|
||||
|
||||
checkSourceDirExists
|
||||
|
||||
logInfo "Changing to source directory..."
|
||||
cd "${SRC_DIR}"
|
||||
|
||||
checkTagExists
|
||||
checkGitRepository
|
||||
checkWorkingTreeClean
|
||||
checkOutputDirDoesNotExist
|
||||
|
||||
logInfo "All checks pass, getting our hands dirty now!"
|
||||
|
||||
logInfo "Checking out release tag '${TAG_NAME}'..."
|
||||
git checkout "$TAG_NAME"
|
||||
|
||||
logInfo "Creating output directory..."
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
exitError "Failed to create output directory!"
|
||||
fi
|
||||
|
||||
if $BUILD_SOURCE_TARBALL; then
|
||||
logInfo "Creating source tarball..."
|
||||
local app_name_lower="$(echo "$APP_NAME" | tr '[:upper:]' '[:lower:]')"
|
||||
TARBALL_NAME="${app_name_lower}-${RELEASE_NAME}-src.tar.xz"
|
||||
git archive --format=tar "$TAG_NAME" --prefix="${app_name_lower}-${RELEASE_NAME}/" \
|
||||
| xz -6 > "${OUTPUT_DIR}/${TARBALL_NAME}"
|
||||
fi
|
||||
|
||||
logInfo "Creating build directory..."
|
||||
mkdir -p "${OUTPUT_DIR}/build-release"
|
||||
cd "${OUTPUT_DIR}/build-release"
|
||||
|
||||
logInfo "Configuring sources..."
|
||||
for p in $BUILD_PLUGINS; do
|
||||
CMAKE_OPTIONS="${CMAKE_OPTIONS} -DWITH_XC_$(echo $p | tr '[:lower:]' '[:upper:]')=On"
|
||||
done
|
||||
|
||||
if [ "$COMPILER" == "g++" ]; then
|
||||
export CC=gcc
|
||||
elif [ "$COMPILER" == "clang++" ]; then
|
||||
export CC=clang
|
||||
fi
|
||||
export CXX="$COMPILER"
|
||||
|
||||
if [ "" == "$DOCKER_IMAGE" ]; then
|
||||
if [ "$(uname -s)" == "Darwin" ]; then
|
||||
# Building on OS X
|
||||
local qt_vers="$(ls /usr/local/Cellar/qt5 2> /dev/null | sort -r | head -n1)"
|
||||
if [ "" == "$qt_vers" ]; then
|
||||
exitError "Couldn't find Qt5! Please make sure it is available in '/usr/local/Cellar/qt5'."
|
||||
fi
|
||||
export MACOSX_DEPLOYMENT_TARGET=10.7
|
||||
|
||||
logInfo "Configuring build..."
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX="${INSTALL_PREFIX}" \
|
||||
-DCMAKE_OSX_ARCHITECTURES=x86_64 -DWITH_CXX11=OFF \
|
||||
-DCMAKE_PREFIX_PATH="/usr/local/Cellar/qt5/${qt_vers}/lib/cmake" \
|
||||
-DQT_BINARY_DIR="/usr/local/Cellar/qt5/${qt_vers}/bin" $CMAKE_OPTIONS "$SRC_DIR"
|
||||
|
||||
logInfo "Compiling and packaging sources..."
|
||||
make $MAKE_OPTIONS package
|
||||
|
||||
mv "./${APP_NAME}-${RELEASE_NAME}.dmg" ../
|
||||
elif [ "$(uname -o)" == "Msys" ]; then
|
||||
# Building on Windows with Msys
|
||||
logInfo "Configuring build..."
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DWITH_TESTS=Off -G"MSYS Makefiles" \
|
||||
-DCMAKE_INSTALL_PREFIX="${INSTALL_PREFIX}" $CMAKE_OPTIONS "$SRC_DIR"
|
||||
|
||||
logInfo "Compiling and packaging sources..."
|
||||
make $MAKE_OPTIONS package
|
||||
|
||||
mv "./${APP_NAME}-${RELEASE_NAME}-"*.{exe,zip} ../
|
||||
else
|
||||
mkdir -p "${OUTPUT_DIR}/bin-release"
|
||||
|
||||
# Building on Linux without Docker container
|
||||
logInfo "Configuring build..."
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DWITH_TESTS=Off $CMAKE_OPTIONS \
|
||||
-DCMAKE_INSTALL_PREFIX="${INSTALL_PREFIX}" "$SRC_DIR"
|
||||
|
||||
logInfo "Compiling sources..."
|
||||
make $MAKE_OPTIONS
|
||||
|
||||
logInfo "Installing to bin dir..."
|
||||
make DESTDIR="${OUTPUT_DIR}/bin-release" install/strip
|
||||
|
||||
logInfo "Creating AppImage..."
|
||||
${SRC_DIR}/AppImage-Recipe.sh "$APP_NAME" "$RELEASE_NAME"
|
||||
fi
|
||||
else
|
||||
mkdir -p "${OUTPUT_DIR}/bin-release"
|
||||
|
||||
logInfo "Launching Docker container to compile sources..."
|
||||
|
||||
docker run --name "$DOCKER_CONTAINER_NAME" --rm \
|
||||
--cap-add SYS_ADMIN --device /dev/fuse \
|
||||
-e "CC=${CC}" -e "CXX=${CXX}" \
|
||||
-v "$(realpath "$SRC_DIR"):/keepassxc/src:ro" \
|
||||
-v "$(realpath "$OUTPUT_DIR"):/keepassxc/out:rw" \
|
||||
"$DOCKER_IMAGE" \
|
||||
bash -c "cd /keepassxc/out/build-release && \
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DWITH_TESTS=Off $CMAKE_OPTIONS \
|
||||
-DCMAKE_INSTALL_PREFIX=\"${INSTALL_PREFIX}\" /keepassxc/src && \
|
||||
make $MAKE_OPTIONS && make DESTDIR=/keepassxc/out/bin-release install/strip && \
|
||||
/keepassxc/src/AppImage-Recipe.sh "$APP_NAME" "$RELEASE_NAME""
|
||||
|
||||
if [ 0 -ne $? ]; then
|
||||
exitError "Docker build failed!"
|
||||
fi
|
||||
|
||||
logInfo "Build finished, Docker container terminated."
|
||||
fi
|
||||
|
||||
cleanup
|
||||
|
||||
logInfo "All done!"
|
||||
}
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# sign command
|
||||
# -----------------------------------------------------------------------
|
||||
sign() {
|
||||
SIGN_FILES=()
|
||||
|
||||
while [ $# -ge 1 ]; do
|
||||
local arg="$1"
|
||||
case "$arg" in
|
||||
-f|--files)
|
||||
while [ "${2:0:1}" != "-" ] && [ $# -ge 2 ]; do
|
||||
SIGN_FILES+=("$2")
|
||||
shift
|
||||
done ;;
|
||||
|
||||
-g|--gpg-key)
|
||||
GPG_KEY="$2"
|
||||
shift ;;
|
||||
|
||||
-h|--help)
|
||||
printUsage "sign"
|
||||
exit ;;
|
||||
|
||||
*)
|
||||
logError "Unknown option '$arg'\n"
|
||||
printUsage "sign"
|
||||
exit 1 ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
if [ -z "$SIGN_FILES" ]; then
|
||||
logError "Missing arguments, --files is required!\n"
|
||||
printUsage "sign"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
for f in "${SIGN_FILES[@]}"; do
|
||||
if [ ! -f "$f" ]; then
|
||||
exitError "File '${f}' does not exist!"
|
||||
fi
|
||||
|
||||
logInfo "Signing file '${f}'..."
|
||||
gpg --output "${f}.sig" --armor --local-user "$GPG_KEY" --detach-sig "$f"
|
||||
|
||||
if [ 0 -ne $? ]; then
|
||||
exitError "Signing failed!"
|
||||
fi
|
||||
|
||||
logInfo "Creating digest for file '${f}'..."
|
||||
sha256sum "$f" > "${f}.DIGEST"
|
||||
done
|
||||
|
||||
logInfo "All done!"
|
||||
}
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# parse global command line
|
||||
# -----------------------------------------------------------------------
|
||||
MODE="$1"
|
||||
shift
|
||||
if [ "" == "$MODE" ]; then
|
||||
logError "Missing arguments!\n"
|
||||
printUsage
|
||||
exit 1
|
||||
elif [ "help" == "$MODE" ]; then
|
||||
printUsage "$1"
|
||||
exit
|
||||
elif [ "merge" == "$MODE" ] || [ "build" == "$MODE" ] || [ "sign" == "$MODE" ]; then
|
||||
$MODE "$@"
|
||||
else
|
||||
printUsage "$MODE"
|
||||
fi
|
BIN
share/icons/application/22x22/actions/document-new.png
Normal file
BIN
share/icons/application/22x22/actions/document-new.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -19,6 +19,11 @@
|
||||
<source>KeePassXC is distributed under the term of the GNU General Public License (GPL) version 2 or (at your option) version 3.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Extensions:
|
||||
</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>AccessControlDialog</name>
|
||||
@ -475,6 +480,46 @@ Do you want to save it anyway?</source>
|
||||
<source>No Results</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Execute command?</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Do you really want to execute the following command?<br><br>%1<br></source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Remember my choice</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Autoreload Request</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>The database file has changed. Do you want to load the changes?</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Merge Request</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>The database file has changed and you have unsaved changes.Do you want to merge your changes?</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Autoreload Failed</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Could not parse or unlock the new database file while attempting to autoreload this database.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Could not open the new database file while attempting to autoreload this database.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>EditEntryWidget</name>
|
||||
@ -669,10 +714,6 @@ Do you want to save it anyway?</source>
|
||||
<source>Repeat:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Gen.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>URL:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
@ -830,6 +871,13 @@ Do you want to save it anyway?</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Entry</name>
|
||||
<message>
|
||||
<source> - Clone</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>EntryAttributesModel</name>
|
||||
<message>
|
||||
@ -1226,6 +1274,10 @@ This is a one-way migration. You won't be able to open the imported databas
|
||||
<source>Re&pair database</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Password Generator</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>OptionDialog</name>
|
||||
@ -1299,14 +1351,6 @@ Only entries with the same scheme (http://, https://, ftp://, ...) are returned<
|
||||
<source>Automatic creates or updates are not supported for string fields!</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>HTTP Host:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Default host: localhost</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>HTTP Port:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
@ -1315,11 +1359,6 @@ Only entries with the same scheme (http://, https://, ftp://, ...) are returned<
|
||||
<source>Default port: 19455</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Enable KeepassXC Http protocol
|
||||
This is required for accessing your databases from ChromeIPass or PassIFox</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Re&quest to unlock the database if it is locked</source>
|
||||
<translation type="unfinished"></translation>
|
||||
@ -1328,6 +1367,24 @@ This is required for accessing your databases from ChromeIPass or PassIFox</sour
|
||||
<source>Sort &matching entries by title</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Enable KeepassXC HTTP protocol
|
||||
This is required for accessing your databases from ChromeIPass or PassIFox</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>KeePassXC will listen to this port on 127.0.0.1</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot bind to privileged ports</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot bind to privileged ports below 1024!
|
||||
Using default port 19455.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>PasswordGeneratorWidget</name>
|
||||
@ -1335,10 +1392,6 @@ This is required for accessing your databases from ChromeIPass or PassIFox</sour
|
||||
<source>Password:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Length:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Character Types</source>
|
||||
<translation type="unfinished"></translation>
|
||||
@ -1364,11 +1417,63 @@ This is required for accessing your databases from ChromeIPass or PassIFox</sour
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Ensure that the password contains characters from every group</source>
|
||||
<source>Accept</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Accept</source>
|
||||
<source>%p%</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>strength</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>entropy</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>&Length:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Pick characters from every group</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Generate</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Close</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Apply</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Entropy: %1 bit</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Password Quality: %1</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Poor</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Weak</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Good</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Excellent</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
@ -1415,16 +1520,20 @@ This is required for accessing your databases from ChromeIPass or PassIFox</sour
|
||||
</context>
|
||||
<context>
|
||||
<name>SearchWidget</name>
|
||||
<message>
|
||||
<source>Find:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Case Sensitive</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Search Current Group</source>
|
||||
<source>Search</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Find</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Clear</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
@ -1536,10 +1645,6 @@ give it a unique name to identify and accept it.</source>
|
||||
<source>Remember last databases</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Open previous databases on startup</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Automatically save on exit</source>
|
||||
<translation type="unfinished"></translation>
|
||||
@ -1581,11 +1686,19 @@ give it a unique name to identify and accept it.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Hide window to system tray instead of App Exit</source>
|
||||
<source>Load previous databases on startup</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Hide window to system tray on App start</source>
|
||||
<source>Automatically reload the database when modified externally</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Hide window to system tray instead of app exit</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Minimize window at application startup</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
@ -1615,6 +1728,10 @@ give it a unique name to identify and accept it.</source>
|
||||
<source>Lock databases after minimizing the window</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Don't require password repeat when it is visible</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>UnlockDatabaseWidget</name>
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
BIN
share/windows/installer-header.bmp
Normal file
BIN
share/windows/installer-header.bmp
Normal file
Binary file not shown.
After Width: | Height: | Size: 25 KiB |
BIN
share/windows/installer-wizard.bmp
Normal file
BIN
share/windows/installer-wizard.bmp
Normal file
Binary file not shown.
After Width: | Height: | Size: 151 KiB |
@ -1,10 +1,11 @@
|
||||
name: keepassxc
|
||||
version: develop
|
||||
version: 2.1.0
|
||||
grade: stable
|
||||
summary: community driven port of the windows application “Keepass Password Safe”
|
||||
description: |
|
||||
KeePassXC is an application for people with extremly high demands on secure
|
||||
KeePassXC is an application for people with extremely high demands on secure
|
||||
personal data management. It has a light interface, is cross platform and
|
||||
published under the terms of the GNU General Public License.
|
||||
is published under the terms of the GNU General Public License.
|
||||
confinement: strict
|
||||
|
||||
apps:
|
||||
@ -19,10 +20,10 @@ parts:
|
||||
configflags:
|
||||
- -DCMAKE_BUILD_TYPE=Release
|
||||
- -DWITH_TESTS=OFF
|
||||
- -DWITH_XC_AUTOTYPE=ON
|
||||
build-packages:
|
||||
- g++
|
||||
- libgcrypt20-dev
|
||||
- libmicrohttpd-dev
|
||||
- libqt5x11extras5-dev
|
||||
- qtbase5-dev
|
||||
- qttools5-dev
|
||||
|
@ -86,9 +86,11 @@ set(keepassx_SOURCES
|
||||
gui/FileDialog.cpp
|
||||
gui/IconModels.cpp
|
||||
gui/KeePass1OpenWidget.cpp
|
||||
gui/KMessageWidget.cpp
|
||||
gui/LineEdit.cpp
|
||||
gui/MainWindow.cpp
|
||||
gui/MessageBox.cpp
|
||||
gui/MessageWidget.cpp
|
||||
gui/PasswordEdit.cpp
|
||||
gui/PasswordGeneratorWidget.cpp
|
||||
gui/PasswordComboBox.cpp
|
||||
@ -226,6 +228,7 @@ target_link_libraries(${PROGNAME}
|
||||
Qt5::Widgets
|
||||
Qt5::Network
|
||||
${GCRYPT_LIBRARIES}
|
||||
${GPGERROR_LIBRARIES}
|
||||
${ZLIB_LIBRARIES})
|
||||
|
||||
if(YUBIKEY_FOUND)
|
||||
@ -269,9 +272,25 @@ if(APPLE)
|
||||
endif()
|
||||
|
||||
if(MINGW)
|
||||
set(CPACK_GENERATOR "ZIP")
|
||||
string(REPLACE "AMD" "Win" OUTPUT_FILE_POSTFIX "${CMAKE_HOST_SYSTEM_PROCESSOR}")
|
||||
set(CPACK_GENERATOR "ZIP;NSIS")
|
||||
set(CPACK_STRIP_FILES ON)
|
||||
set(CPACK_PACKAGE_FILE_NAME "${PROGNAME}-${KEEPASSXC_VERSION_NUM}")
|
||||
set(CPACK_PACKAGE_FILE_NAME "${PROGNAME}-${KEEPASSXC_VERSION}-${OUTPUT_FILE_POSTFIX}")
|
||||
set(CPACK_PACKAGE_INSTALL_DIRECTORY ${PROGNAME})
|
||||
set(CPACK_PACKAGE_VERSION ${KEEPASSXC_VERSION})
|
||||
set(CPACK_PACKAGE_VENDOR "${PROGNAME} Team")
|
||||
string(REGEX REPLACE "/" "\\\\\\\\" CPACK_PACKAGE_ICON "${CMAKE_SOURCE_DIR}/share/windows/installer-header.bmp")
|
||||
set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_SOURCE_DIR}/LICENSE.GPL-2")
|
||||
set(CPACK_NSIS_MUI_ICON "${CMAKE_SOURCE_DIR}/share/windows/keepassxc.ico")
|
||||
set(CPACK_NSIS_MUI_UNIICON "${CPACK_NSIS_MUI_ICON}")
|
||||
set(CPACK_NSIS_INSTALLED_ICON_NAME "\\\\${PROGNAME}.exe")
|
||||
string(REGEX REPLACE "/" "\\\\\\\\" CPACK_NSIS_MUI_WELCOMEFINISHPAGE_BITMAP "${CMAKE_SOURCE_DIR}/share/windows/installer-wizard.bmp")
|
||||
set(CPACK_NSIS_MUI_UNWELCOMEFINISHPAGE_BITMAP "${CPACK_NSIS_MUI_WELCOMEFINISHPAGE_BITMAP}")
|
||||
set(CPACK_NSIS_CREATE_ICONS_EXTRA "CreateShortCut '$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\${PROGNAME}.lnk' '$INSTDIR\\\\${PROGNAME}.exe'")
|
||||
set(CPACK_NSIS_DELETE_ICONS_EXTRA "Delete '$SMPROGRAMS\\\\$START_MENU\\\\${PROGNAME}.lnk'")
|
||||
set(CPACK_NSIS_URL_INFO_ABOUT "https://keepassxc.org")
|
||||
set(CPACK_NSIS_PACKAGE_NAME "${PROGNAME} v${KEEPASSXC_VERSION}")
|
||||
set(CPACK_NSIS_MUI_FINISHPAGE_RUN "../${PROGNAME}.exe")
|
||||
include(CPack)
|
||||
|
||||
install(CODE "
|
||||
@ -279,5 +298,9 @@ if(MINGW)
|
||||
" COMPONENT Runtime)
|
||||
|
||||
include(DeployQt4)
|
||||
install_qt4_executable(${PROGNAME}.exe "qjpeg;qgif;qico;qtaccessiblewidgets")
|
||||
install_qt4_executable(${PROGNAME}.exe)
|
||||
add_custom_command(TARGET ${PROGNAME} POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${Qt5Core_DIR}/../../../share/qt5/plugins/platforms/qwindows$<$<CONFIG:Debug>:d>.dll
|
||||
$<TARGET_FILE_DIR:${PROGNAME}>)
|
||||
install(FILES $<TARGET_FILE_DIR:${PROGNAME}>/qwindows$<$<CONFIG:Debug>:d>.dll DESTINATION "platforms")
|
||||
endif()
|
||||
|
@ -396,6 +396,9 @@ QList<AutoTypeAction*> AutoType::createActionFromTemplate(const QString& tmpl, c
|
||||
else if (tmplName.compare("enter",Qt::CaseInsensitive)==0) {
|
||||
list.append(new AutoTypeKey(Qt::Key_Enter));
|
||||
}
|
||||
else if (tmplName.compare("space",Qt::CaseInsensitive)==0) {
|
||||
list.append(new AutoTypeKey(Qt::Key_Space));
|
||||
}
|
||||
else if (tmplName.compare("up",Qt::CaseInsensitive)==0) {
|
||||
list.append(new AutoTypeKey(Qt::Key_Up));
|
||||
}
|
||||
|
@ -98,7 +98,9 @@ QString AutoTypePlatformMac::activeWindowTitle()
|
||||
if (windowLayer(window) == 0) {
|
||||
// First toplevel window in list (front to back order)
|
||||
title = windowTitle(window);
|
||||
break;
|
||||
if (!title.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -435,6 +435,8 @@ KeySym AutoTypePlatformX11::keyToKeySym(Qt::Key key)
|
||||
return XK_Tab;
|
||||
case Qt::Key_Enter:
|
||||
return XK_Return;
|
||||
case Qt::Key_Space:
|
||||
return XK_space;
|
||||
case Qt::Key_Up:
|
||||
return XK_Up;
|
||||
case Qt::Key_Down:
|
||||
|
@ -353,6 +353,12 @@ void Entry::setTitle(const QString& title)
|
||||
|
||||
void Entry::setUrl(const QString& url)
|
||||
{
|
||||
bool remove = url != m_attributes->value(EntryAttributes::URLKey) &&
|
||||
(m_attributes->value(EntryAttributes::RememberCmdExecAttr) == "1" ||
|
||||
m_attributes->value(EntryAttributes::RememberCmdExecAttr) == "0");
|
||||
if (remove) {
|
||||
m_attributes->remove(EntryAttributes::RememberCmdExecAttr);
|
||||
}
|
||||
m_attributes->set(EntryAttributes::URLKey, url, m_attributes->isProtected(EntryAttributes::URLKey));
|
||||
}
|
||||
|
||||
@ -508,7 +514,8 @@ Entry* Entry::clone(CloneFlags flags) const
|
||||
entry->m_data.timeInfo.setLocationChanged(now);
|
||||
}
|
||||
|
||||
|
||||
if (flags & CloneRenameTitle)
|
||||
entry->setTitle(entry->title() + tr(" - Clone"));
|
||||
|
||||
return entry;
|
||||
}
|
||||
@ -639,12 +646,17 @@ QString Entry::resolvePlaceholder(const QString& str) const
|
||||
const QList<QString> keyList = attributes()->keys();
|
||||
for (const QString& key : keyList) {
|
||||
Qt::CaseSensitivity cs = Qt::CaseInsensitive;
|
||||
QString k = key;
|
||||
|
||||
if (!EntryAttributes::isDefaultAttribute(key)) {
|
||||
cs = Qt::CaseSensitive;
|
||||
k.prepend("{S:");
|
||||
} else {
|
||||
k.prepend("{");
|
||||
}
|
||||
|
||||
QString k = key;
|
||||
k.prepend("{").append("}");
|
||||
|
||||
k.append("}");
|
||||
if (result.compare(k,cs)==0) {
|
||||
result.replace(result,attributes()->value(key));
|
||||
break;
|
||||
|
@ -115,7 +115,8 @@ public:
|
||||
CloneNoFlags = 0,
|
||||
CloneNewUuid = 1, // generate a random uuid for the clone
|
||||
CloneResetTimeInfo = 2, // set all TimeInfo attributes to the current time
|
||||
CloneIncludeHistory = 4 // clone the history items
|
||||
CloneIncludeHistory = 4, // clone the history items
|
||||
CloneRenameTitle = 8 // add "-Clone" after the original title
|
||||
};
|
||||
Q_DECLARE_FLAGS(CloneFlags, CloneFlag)
|
||||
|
||||
|
@ -24,6 +24,7 @@ const QString EntryAttributes::URLKey = "URL";
|
||||
const QString EntryAttributes::NotesKey = "Notes";
|
||||
const QStringList EntryAttributes::DefaultAttributes(QStringList() << TitleKey << UserNameKey
|
||||
<< PasswordKey << URLKey << NotesKey);
|
||||
const QString EntryAttributes::RememberCmdExecAttr = "_EXEC_CMD";
|
||||
|
||||
EntryAttributes::EntryAttributes(QObject* parent)
|
||||
: QObject(parent)
|
||||
|
@ -52,6 +52,7 @@ public:
|
||||
static const QString URLKey;
|
||||
static const QString NotesKey;
|
||||
static const QStringList DefaultAttributes;
|
||||
static const QString RememberCmdExecAttr;
|
||||
static bool isDefaultAttribute(const QString& key);
|
||||
|
||||
Q_SIGNALS:
|
||||
|
@ -42,7 +42,11 @@ QList<Entry*> EntrySearcher::searchEntries(const QString& searchTerm, const Grou
|
||||
const QList<Group*> children = group->children();
|
||||
for (Group* childGroup : children) {
|
||||
if (childGroup->searchingEnabled() != Group::Disable) {
|
||||
searchResult.append(searchEntries(searchTerm, childGroup, caseSensitivity));
|
||||
if (matchGroup(searchTerm, childGroup, caseSensitivity)) {
|
||||
searchResult.append(childGroup->entriesRecursive());
|
||||
} else {
|
||||
searchResult.append(searchEntries(searchTerm, childGroup, caseSensitivity));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -69,3 +73,21 @@ bool EntrySearcher::wordMatch(const QString& word, Entry* entry, Qt::CaseSensiti
|
||||
entry->url().contains(word, caseSensitivity) ||
|
||||
entry->notes().contains(word, caseSensitivity);
|
||||
}
|
||||
|
||||
bool EntrySearcher::matchGroup(const QString& searchTerm, const Group* group, Qt::CaseSensitivity caseSensitivity)
|
||||
{
|
||||
const QStringList wordList = searchTerm.split(QRegExp("\\s"), QString::SkipEmptyParts);
|
||||
for (const QString& word : wordList) {
|
||||
if (!wordMatch(word, group, caseSensitivity)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EntrySearcher::wordMatch(const QString& word, const Group* group, Qt::CaseSensitivity caseSensitivity)
|
||||
{
|
||||
return group->name().contains(word, caseSensitivity) ||
|
||||
group->notes().contains(word, caseSensitivity);
|
||||
}
|
||||
|
@ -33,6 +33,8 @@ private:
|
||||
QList<Entry*> searchEntries(const QString& searchTerm, const Group* group, Qt::CaseSensitivity caseSensitivity);
|
||||
QList<Entry*> matchEntry(const QString& searchTerm, Entry* entry, Qt::CaseSensitivity caseSensitivity);
|
||||
bool wordMatch(const QString& word, Entry* entry, Qt::CaseSensitivity caseSensitivity);
|
||||
bool matchGroup(const QString& searchTerm, const Group* group, Qt::CaseSensitivity caseSensitivity);
|
||||
bool wordMatch(const QString& word, const Group* group, Qt::CaseSensitivity caseSensitivity);
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_ENTRYSEARCHER_H
|
||||
|
@ -211,6 +211,9 @@ FilePath::FilePath()
|
||||
else if (testSetDir(appDirPath + "/share")) {
|
||||
}
|
||||
#endif
|
||||
// Last ditch test when running in the build directory (mainly for travis tests)
|
||||
else if (testSetDir(QString(KEEPASSX_SOURCE_DIR) + "/share")) {
|
||||
}
|
||||
|
||||
if (m_dataPath.isEmpty()) {
|
||||
qWarning("FilePath::DataPath: can't find data dir");
|
||||
|
@ -83,3 +83,23 @@ QString SymmetricCipher::errorString() const
|
||||
{
|
||||
return m_backend->errorString();
|
||||
}
|
||||
|
||||
SymmetricCipher::Algorithm SymmetricCipher::cipherToAlgorithm(Uuid cipher)
|
||||
{
|
||||
if (cipher == KeePass2::CIPHER_AES) {
|
||||
return SymmetricCipher::Aes256;
|
||||
}
|
||||
else {
|
||||
return SymmetricCipher::Twofish;
|
||||
}
|
||||
}
|
||||
|
||||
Uuid SymmetricCipher::algorithmToCipher(SymmetricCipher::Algorithm algo)
|
||||
{
|
||||
switch (algo) {
|
||||
case SymmetricCipher::Aes256:
|
||||
return KeePass2::CIPHER_AES;
|
||||
default:
|
||||
return KeePass2::CIPHER_TWOFISH;
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,7 @@
|
||||
#include <QString>
|
||||
|
||||
#include "crypto/SymmetricCipherBackend.h"
|
||||
#include "format/KeePass2.h"
|
||||
|
||||
class SymmetricCipher
|
||||
{
|
||||
@ -71,6 +72,9 @@ public:
|
||||
int blockSize() const;
|
||||
QString errorString() const;
|
||||
|
||||
static SymmetricCipher::Algorithm cipherToAlgorithm(Uuid cipher);
|
||||
static Uuid algorithmToCipher(SymmetricCipher::Algorithm algo);
|
||||
|
||||
private:
|
||||
static SymmetricCipherBackend* createBackend(SymmetricCipher::Algorithm algo, SymmetricCipher::Mode mode,
|
||||
SymmetricCipher::Direction direction);
|
||||
|
@ -33,6 +33,7 @@ namespace KeePass2
|
||||
const QSysInfo::Endian BYTEORDER = QSysInfo::LittleEndian;
|
||||
|
||||
const Uuid CIPHER_AES = Uuid(QByteArray::fromHex("31c1f2e6bf714350be5805216afc5aff"));
|
||||
const Uuid CIPHER_TWOFISH = Uuid(QByteArray::fromHex("ad68f29f576f4bb9a36ad47af965346c"));
|
||||
|
||||
const QByteArray INNER_STREAM_SALSA20_IV("\xE8\x30\x09\x4B\x97\x20\x5D\x2A");
|
||||
|
||||
|
@ -124,7 +124,7 @@ Database* KeePass2Reader::readDatabase(QIODevice* device, const CompositeKey& ke
|
||||
hash.addData(m_db->transformedMasterKey());
|
||||
QByteArray finalKey = hash.result();
|
||||
|
||||
SymmetricCipherStream cipherStream(m_device, SymmetricCipher::Aes256,
|
||||
SymmetricCipherStream cipherStream(m_device, SymmetricCipher::cipherToAlgorithm(m_db->cipher()),
|
||||
SymmetricCipher::Cbc, SymmetricCipher::Decrypt);
|
||||
if (!cipherStream.init(finalKey, m_encryptionIV)) {
|
||||
raiseError(cipherStream.errorString());
|
||||
@ -336,7 +336,7 @@ void KeePass2Reader::setCipher(const QByteArray& data)
|
||||
else {
|
||||
Uuid uuid(data);
|
||||
|
||||
if (uuid != KeePass2::CIPHER_AES) {
|
||||
if (uuid != KeePass2::CIPHER_AES && uuid != KeePass2::CIPHER_TWOFISH) {
|
||||
raiseError("Unsupported cipher");
|
||||
}
|
||||
else {
|
||||
|
@ -93,8 +93,8 @@ void KeePass2Writer::writeDatabase(QIODevice* device, Database* db)
|
||||
QByteArray headerHash = CryptoHash::hash(header.data(), CryptoHash::Sha256);
|
||||
CHECK_RETURN(writeData(header.data()));
|
||||
|
||||
SymmetricCipherStream cipherStream(device, SymmetricCipher::Aes256, SymmetricCipher::Cbc,
|
||||
SymmetricCipher::Encrypt);
|
||||
SymmetricCipherStream cipherStream(device, SymmetricCipher::cipherToAlgorithm(db->cipher()),
|
||||
SymmetricCipher::Cbc, SymmetricCipher::Encrypt);
|
||||
cipherStream.init(finalKey, encryptionIV);
|
||||
if (!cipherStream.open(QIODevice::WriteOnly)) {
|
||||
raiseError(cipherStream.errorString());
|
||||
|
@ -388,7 +388,7 @@ void KeePass2XmlReader::parseBinaries()
|
||||
QString id = attr.value("ID").toString();
|
||||
|
||||
QByteArray data;
|
||||
if (attr.value("Compressed").compare("True", Qt::CaseInsensitive) == 0) {
|
||||
if (attr.value("Compressed").compare(QLatin1String("True"), Qt::CaseInsensitive) == 0) {
|
||||
data = readCompressedBinary();
|
||||
}
|
||||
else {
|
||||
|
@ -17,12 +17,20 @@
|
||||
*/
|
||||
|
||||
#include "Application.h"
|
||||
#include "MainWindow.h"
|
||||
|
||||
#include <QAbstractNativeEventFilter>
|
||||
#include <QFileOpenEvent>
|
||||
#include <QSocketNotifier>
|
||||
|
||||
#include "autotype/AutoType.h"
|
||||
|
||||
#if defined(Q_OS_UNIX)
|
||||
#include <signal.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/socket.h>
|
||||
#endif
|
||||
|
||||
#if defined(Q_OS_UNIX) && !defined(Q_OS_OSX)
|
||||
class XcbEventFilter : public QAbstractNativeEventFilter
|
||||
{
|
||||
@ -65,12 +73,18 @@ public:
|
||||
Application::Application(int& argc, char** argv)
|
||||
: QApplication(argc, argv)
|
||||
, m_mainWindow(nullptr)
|
||||
#ifdef Q_OS_UNIX
|
||||
, m_unixSignalNotifier(nullptr)
|
||||
#endif
|
||||
{
|
||||
#if defined(Q_OS_UNIX) && !defined(Q_OS_OSX)
|
||||
installNativeEventFilter(new XcbEventFilter());
|
||||
#elif defined(Q_OS_WIN)
|
||||
installNativeEventFilter(new WinEventFilter());
|
||||
#endif
|
||||
#if defined(Q_OS_UNIX)
|
||||
registerUnixSignals();
|
||||
#endif
|
||||
}
|
||||
|
||||
void Application::setMainWindow(QWidget* mainWindow)
|
||||
@ -98,3 +112,57 @@ bool Application::event(QEvent* event)
|
||||
|
||||
return QApplication::event(event);
|
||||
}
|
||||
|
||||
#if defined(Q_OS_UNIX)
|
||||
int Application::unixSignalSocket[2];
|
||||
|
||||
void Application::registerUnixSignals()
|
||||
{
|
||||
int result = ::socketpair(AF_UNIX, SOCK_STREAM, 0, unixSignalSocket);
|
||||
Q_ASSERT(0 == result);
|
||||
if (0 != result) {
|
||||
// do not register handles when socket creation failed, otherwise
|
||||
// application will be unresponsive to signals such as SIGINT or SIGTERM
|
||||
return;
|
||||
}
|
||||
|
||||
QVector<int> const handledSignals = { SIGQUIT, SIGINT, SIGTERM, SIGHUP };
|
||||
for (auto s: handledSignals) {
|
||||
struct sigaction sigAction;
|
||||
|
||||
sigAction.sa_handler = handleUnixSignal;
|
||||
sigemptyset(&sigAction.sa_mask);
|
||||
sigAction.sa_flags = 0 | SA_RESTART;
|
||||
sigaction(s, &sigAction, nullptr);
|
||||
}
|
||||
|
||||
m_unixSignalNotifier = new QSocketNotifier(unixSignalSocket[1], QSocketNotifier::Read, this);
|
||||
connect(m_unixSignalNotifier, SIGNAL(activated(int)), this, SLOT(quitBySignal()));
|
||||
}
|
||||
|
||||
void Application::handleUnixSignal(int sig)
|
||||
{
|
||||
switch (sig) {
|
||||
case SIGQUIT:
|
||||
case SIGINT:
|
||||
case SIGTERM:
|
||||
{
|
||||
char buf = 0;
|
||||
::write(unixSignalSocket[0], &buf, sizeof(buf));
|
||||
return;
|
||||
}
|
||||
case SIGHUP:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void Application::quitBySignal()
|
||||
{
|
||||
m_unixSignalNotifier->setEnabled(false);
|
||||
char buf;
|
||||
::read(unixSignalSocket[1], &buf, sizeof(buf));
|
||||
|
||||
if (nullptr != m_mainWindow)
|
||||
static_cast<MainWindow*>(m_mainWindow)->appExit();
|
||||
}
|
||||
#endif
|
||||
|
@ -21,6 +21,8 @@
|
||||
|
||||
#include <QApplication>
|
||||
|
||||
class QSocketNotifier;
|
||||
|
||||
class Application : public QApplication
|
||||
{
|
||||
Q_OBJECT
|
||||
@ -34,8 +36,23 @@ public:
|
||||
Q_SIGNALS:
|
||||
void openFile(const QString& filename);
|
||||
|
||||
private Q_SLOTS:
|
||||
#if defined(Q_OS_UNIX)
|
||||
void quitBySignal();
|
||||
#endif
|
||||
|
||||
private:
|
||||
QWidget* m_mainWindow;
|
||||
|
||||
#if defined(Q_OS_UNIX)
|
||||
/**
|
||||
* Register Unix signals such as SIGINT and SIGTERM for clean shutdown.
|
||||
*/
|
||||
void registerUnixSignals();
|
||||
QSocketNotifier* m_unixSignalNotifier;
|
||||
static void handleUnixSignal(int sig);
|
||||
static int unixSignalSocket[2];
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_APPLICATION_H
|
||||
|
@ -34,6 +34,8 @@ ChangeMasterKeyWidget::ChangeMasterKeyWidget(QWidget* parent)
|
||||
{
|
||||
m_ui->setupUi(this);
|
||||
|
||||
m_ui->messageWidget->setHidden(true);
|
||||
|
||||
connect(m_ui->buttonBox, SIGNAL(accepted()), SLOT(generateKey()));
|
||||
connect(m_ui->buttonBox, SIGNAL(rejected()), SLOT(reject()));
|
||||
m_ui->togglePasswordButton->setIcon(filePath()->onOffIcon("actions", "password-show"));
|
||||
@ -60,7 +62,7 @@ void ChangeMasterKeyWidget::createKeyFile()
|
||||
QString errorMsg;
|
||||
bool created = FileKey::create(fileName, &errorMsg);
|
||||
if (!created) {
|
||||
MessageBox::warning(this, tr("Error"), tr("Unable to create Key File : ") + errorMsg);
|
||||
m_ui->messageWidget->showMessage(tr("Unable to create Key File : ").append(errorMsg), MessageWidget::Error);
|
||||
}
|
||||
else {
|
||||
m_ui->keyFileCombo->setEditText(fileName);
|
||||
@ -125,7 +127,7 @@ void ChangeMasterKeyWidget::generateKey()
|
||||
m_key.addKey(PasswordKey(m_ui->enterPasswordEdit->text()));
|
||||
}
|
||||
else {
|
||||
MessageBox::warning(this, tr("Error"), tr("Different passwords supplied."));
|
||||
m_ui->messageWidget->showMessage(tr("Different passwords supplied."), MessageWidget::Error);
|
||||
m_ui->enterPasswordEdit->setText("");
|
||||
m_ui->repeatPasswordEdit->setText("");
|
||||
return;
|
||||
@ -134,10 +136,10 @@ void ChangeMasterKeyWidget::generateKey()
|
||||
if (m_ui->keyFileGroup->isChecked()) {
|
||||
FileKey fileKey;
|
||||
QString errorMsg;
|
||||
if (!fileKey.load(m_ui->keyFileCombo->currentText(), &errorMsg)) {
|
||||
MessageBox::critical(this, tr("Failed to set key file"),
|
||||
tr("Failed to set %1 as the Key file:\n%2")
|
||||
.arg(m_ui->keyFileCombo->currentText(), errorMsg));
|
||||
QString fileKeyName = m_ui->keyFileCombo->currentText();
|
||||
if (!fileKey.load(fileKeyName, &errorMsg)) {
|
||||
m_ui->messageWidget->showMessage(
|
||||
tr("Failed to set %1 as the Key file:\n%2").arg(fileKeyName, errorMsg), MessageWidget::Error);
|
||||
return;
|
||||
}
|
||||
m_key.addKey(fileKey);
|
||||
@ -151,6 +153,7 @@ void ChangeMasterKeyWidget::generateKey()
|
||||
m_key.addChallengeResponseKey(key);
|
||||
}
|
||||
|
||||
m_ui->messageWidget->hideMessage();
|
||||
Q_EMIT editFinished(true);
|
||||
}
|
||||
|
||||
|
@ -7,10 +7,13 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>438</width>
|
||||
<height>256</height>
|
||||
<height>342</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="MessageWidget" name="messageWidget" native="true"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="headlineLabel"/>
|
||||
</item>
|
||||
@ -176,6 +179,12 @@
|
||||
<extends>QLineEdit</extends>
|
||||
<header>gui/PasswordEdit.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>MessageWidget</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>gui/MessageWidget.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<tabstops>
|
||||
<tabstop>passwordGroup</tabstop>
|
||||
|
@ -40,6 +40,8 @@ DatabaseOpenWidget::DatabaseOpenWidget(QWidget* parent)
|
||||
{
|
||||
m_ui->setupUi(this);
|
||||
|
||||
m_ui->messageWidget->setHidden(true);
|
||||
|
||||
QFont font = m_ui->labelHeadline->font();
|
||||
font.setBold(true);
|
||||
font.setPointSize(font.pointSize() + 2);
|
||||
@ -127,8 +129,8 @@ void DatabaseOpenWidget::openDatabase()
|
||||
|
||||
QFile file(m_filename);
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
MessageBox::warning(this, tr("Error"), tr("Unable to open the database.").append("\n")
|
||||
.append(file.errorString()));
|
||||
m_ui->messageWidget->showMessage(
|
||||
tr("Unable to open the database.").append("\n").append(file.errorString()), MessageWidget::Error);
|
||||
return;
|
||||
}
|
||||
if (m_db) {
|
||||
@ -139,11 +141,14 @@ void DatabaseOpenWidget::openDatabase()
|
||||
QApplication::restoreOverrideCursor();
|
||||
|
||||
if (m_db) {
|
||||
if (m_ui->messageWidget->isVisible()) {
|
||||
m_ui->messageWidget->animatedHide();
|
||||
}
|
||||
Q_EMIT editFinished(true);
|
||||
}
|
||||
else {
|
||||
MessageBox::warning(this, tr("Error"), tr("Unable to open the database.").append("\n")
|
||||
.append(reader.errorString()));
|
||||
m_ui->messageWidget->showMessage(tr("Unable to open the database.")
|
||||
.append("\n").append(reader.errorString()), MessageWidget::Error);
|
||||
m_ui->editPassword->clear();
|
||||
}
|
||||
}
|
||||
@ -163,7 +168,8 @@ CompositeKey DatabaseOpenWidget::databaseKey()
|
||||
QString keyFilename = m_ui->comboKeyFile->currentText();
|
||||
QString errorMsg;
|
||||
if (!key.load(keyFilename, &errorMsg)) {
|
||||
MessageBox::warning(this, tr("Error"), tr("Can't open key file").append(":\n").append(errorMsg));
|
||||
m_ui->messageWidget->showMessage(tr("Can't open key file").append(":\n")
|
||||
.append(errorMsg), MessageWidget::Error);
|
||||
return CompositeKey();
|
||||
}
|
||||
masterKey.addKey(key);
|
||||
|
@ -10,10 +10,13 @@
|
||||
<height>250</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout" stretch="1,0,0,1,0,0,3">
|
||||
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,1,0,0,1,0,0,3">
|
||||
<property name="spacing">
|
||||
<number>8</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="MessageWidget" name="messageWidget" native="true"/>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_2">
|
||||
<property name="orientation">
|
||||
@ -161,6 +164,12 @@
|
||||
<extends>QLineEdit</extends>
|
||||
<header>gui/PasswordEdit.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>MessageWidget</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>gui/MessageWidget.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<tabstops>
|
||||
<tabstop>checkPassword</tabstop>
|
||||
|
@ -21,6 +21,8 @@
|
||||
#include "core/Database.h"
|
||||
#include "core/Group.h"
|
||||
#include "core/Metadata.h"
|
||||
#include "crypto/SymmetricCipher.h"
|
||||
#include "format/KeePass2.h"
|
||||
#include "keys/CompositeKey.h"
|
||||
|
||||
DatabaseSettingsWidget::DatabaseSettingsWidget(QWidget* parent)
|
||||
@ -53,6 +55,7 @@ void DatabaseSettingsWidget::load(Database* db)
|
||||
m_ui->dbDescriptionEdit->setText(meta->description());
|
||||
m_ui->recycleBinEnabledCheckBox->setChecked(meta->recycleBinEnabled());
|
||||
m_ui->defaultUsernameEdit->setText(meta->defaultUserName());
|
||||
m_ui->AlgorithmComboBox->setCurrentIndex(SymmetricCipher::cipherToAlgorithm(m_db->cipher()));
|
||||
m_ui->transformRoundsSpinBox->setValue(m_db->transformRounds());
|
||||
if (meta->historyMaxItems() > -1) {
|
||||
m_ui->historyMaxItemsSpinBox->setValue(meta->historyMaxItems());
|
||||
@ -82,6 +85,8 @@ void DatabaseSettingsWidget::save()
|
||||
meta->setName(m_ui->dbNameEdit->text());
|
||||
meta->setDescription(m_ui->dbDescriptionEdit->text());
|
||||
meta->setDefaultUserName(m_ui->defaultUsernameEdit->text());
|
||||
m_db->setCipher(SymmetricCipher::algorithmToCipher(static_cast<SymmetricCipher::Algorithm>
|
||||
(m_ui->AlgorithmComboBox->currentIndex())));
|
||||
meta->setRecycleBinEnabled(m_ui->recycleBinEnabledCheckBox->isChecked());
|
||||
if (static_cast<quint64>(m_ui->transformRoundsSpinBox->value()) != m_db->transformRounds()) {
|
||||
QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
|
||||
|
@ -49,35 +49,7 @@
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="dbDescriptionEdit"/>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="transformRoundsLabel">
|
||||
<property name="text">
|
||||
<string>Transform rounds:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="defaultUsernameLabel">
|
||||
<property name="text">
|
||||
<string>Default username:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QLineEdit" name="defaultUsernameEdit">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Use recycle bin:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="1">
|
||||
<item row="9" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QSpinBox" name="historyMaxSizeSpinBox">
|
||||
@ -100,7 +72,7 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<item row="8" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QSpinBox" name="historyMaxItemsSpinBox">
|
||||
@ -117,7 +89,7 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<item row="5" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
<widget class="QSpinBox" name="transformRoundsSpinBox">
|
||||
@ -144,23 +116,72 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<item row="8" column="0">
|
||||
<widget class="QCheckBox" name="historyMaxItemsCheckBox">
|
||||
<property name="text">
|
||||
<string>Max. history items:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0">
|
||||
<item row="9" column="0">
|
||||
<widget class="QCheckBox" name="historyMaxSizeCheckBox">
|
||||
<property name="text">
|
||||
<string>Max. history size:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="transformRoundsLabel">
|
||||
<property name="text">
|
||||
<string>Transform rounds:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="1">
|
||||
<widget class="QCheckBox" name="recycleBinEnabledCheckBox"/>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="defaultUsernameLabel">
|
||||
<property name="text">
|
||||
<string>Default username:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Use recycle bin:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<widget class="QLineEdit" name="defaultUsernameEdit">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="AlgorithmLabel">
|
||||
<property name="text">
|
||||
<string>Algorithm:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QComboBox" name="AlgorithmComboBox">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>AES: 256 Bit (default)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Twofish: 256 Bit</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
|
@ -116,7 +116,7 @@ void DatabaseTabWidget::openDatabase(const QString& fileName, const QString& pw,
|
||||
QFileInfo fileInfo(fileName);
|
||||
QString canonicalFilePath = fileInfo.canonicalFilePath();
|
||||
if (canonicalFilePath.isEmpty()) {
|
||||
MessageBox::warning(this, tr("Warning"), tr("File not found!"));
|
||||
Q_EMIT messageGlobal(tr("File not found!"), MessageWidget::Error);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -136,8 +136,9 @@ void DatabaseTabWidget::openDatabase(const QString& fileName, const QString& pw,
|
||||
QFile file(fileName);
|
||||
if (!file.open(QIODevice::ReadWrite)) {
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
MessageBox::warning(this, tr("Error"), tr("Unable to open the database.").append("\n")
|
||||
.append(file.errorString()));
|
||||
// can't open
|
||||
Q_EMIT messageGlobal(
|
||||
tr("Unable to open the database.").append("\n").append(file.errorString()), MessageWidget::Error);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
@ -184,6 +185,10 @@ void DatabaseTabWidget::openDatabase(const QString& fileName, const QString& pw,
|
||||
|
||||
insertDatabase(db, dbStruct);
|
||||
|
||||
if (dbStruct.readOnly) {
|
||||
Q_EMIT messageTab(tr("File opened in read only mode."), MessageWidget::Warning);
|
||||
}
|
||||
|
||||
updateLastDatabases(dbStruct.filePath);
|
||||
|
||||
if (!pw.isNull() || !keyFile.isEmpty()) {
|
||||
@ -192,6 +197,7 @@ void DatabaseTabWidget::openDatabase(const QString& fileName, const QString& pw,
|
||||
else {
|
||||
dbStruct.dbWidget->switchToOpenDatabase(dbStruct.filePath);
|
||||
}
|
||||
Q_EMIT messageDismissGlobal();
|
||||
}
|
||||
|
||||
void DatabaseTabWidget::mergeDatabase()
|
||||
@ -246,7 +252,7 @@ bool DatabaseTabWidget::closeDatabase(Database* db)
|
||||
QMessageBox::StandardButton result =
|
||||
MessageBox::question(
|
||||
this, tr("Close?"),
|
||||
tr("\"%1\" is in edit mode.\nDiscard changes and close anyway?").arg(dbName),
|
||||
tr("\"%1\" is in edit mode.\nDiscard changes and close anyway?").arg(dbName.toHtmlEscaped()),
|
||||
QMessageBox::Discard | QMessageBox::Cancel, QMessageBox::Cancel);
|
||||
if (result == QMessageBox::Cancel) {
|
||||
return false;
|
||||
@ -262,7 +268,7 @@ bool DatabaseTabWidget::closeDatabase(Database* db)
|
||||
QMessageBox::StandardButton result =
|
||||
MessageBox::question(
|
||||
this, tr("Save changes?"),
|
||||
tr("\"%1\" was modified.\nSave changes?").arg(dbName),
|
||||
tr("\"%1\" was modified.\nSave changes?").arg(dbName.toHtmlEscaped()),
|
||||
QMessageBox::Yes | QMessageBox::Discard | QMessageBox::Cancel, QMessageBox::Yes);
|
||||
if (result == QMessageBox::Yes) {
|
||||
if (!saveDatabase(db)) {
|
||||
@ -322,8 +328,8 @@ bool DatabaseTabWidget::saveDatabase(Database* db)
|
||||
// write the database to the file
|
||||
m_writer.writeDatabase(&saveFile, db);
|
||||
if (m_writer.hasError()) {
|
||||
MessageBox::critical(this, tr("Error"), tr("Writing the database failed.") + "\n\n"
|
||||
+ m_writer.errorString());
|
||||
Q_EMIT messageTab(tr("Writing the database failed.").append("\n")
|
||||
.append(m_writer.errorString()), MessageWidget::Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -332,17 +338,18 @@ bool DatabaseTabWidget::saveDatabase(Database* db)
|
||||
dbStruct.modified = false;
|
||||
dbStruct.dbWidget->databaseSaved();
|
||||
updateTabName(db);
|
||||
Q_EMIT messageDismissTab();
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
MessageBox::critical(this, tr("Error"), tr("Writing the database failed.") + "\n\n"
|
||||
+ saveFile.errorString());
|
||||
Q_EMIT messageTab(tr("Writing the database failed.").append("\n")
|
||||
.append(saveFile.errorString()), MessageWidget::Error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
MessageBox::critical(this, tr("Error"), tr("Writing the database failed.") + "\n\n"
|
||||
+ saveFile.errorString());
|
||||
Q_EMIT messageTab(tr("Writing the database failed.").append("\n")
|
||||
.append(saveFile.errorString()), MessageWidget::Error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -486,8 +493,9 @@ void DatabaseTabWidget::exportToCsv()
|
||||
|
||||
CsvExporter csvExporter;
|
||||
if (!csvExporter.exportDatabase(fileName, db)) {
|
||||
MessageBox::critical(this, tr("Error"), tr("Writing the CSV file failed.") + "\n\n"
|
||||
+ csvExporter.errorString());
|
||||
Q_EMIT messageGlobal(
|
||||
tr("Writing the CSV file failed.").append("\n")
|
||||
.append(csvExporter.errorString()), MessageWidget::Error);
|
||||
}
|
||||
}
|
||||
|
||||
@ -716,6 +724,7 @@ void DatabaseTabWidget::lockDatabases()
|
||||
}
|
||||
else if (result == QMessageBox::Discard) {
|
||||
m_dbList[db].modified = false;
|
||||
m_dbList[db].dbWidget->databaseSaved();
|
||||
}
|
||||
else if (result == QMessageBox::Cancel) {
|
||||
continue;
|
||||
|
@ -23,12 +23,14 @@
|
||||
|
||||
#include "format/KeePass2Writer.h"
|
||||
#include "gui/DatabaseWidget.h"
|
||||
#include "gui/MessageWidget.h"
|
||||
|
||||
class DatabaseWidget;
|
||||
class DatabaseWidgetStateSync;
|
||||
class DatabaseOpenWidget;
|
||||
class QFile;
|
||||
class QLockFile;
|
||||
class MessageWidget;
|
||||
|
||||
struct DatabaseManagerStruct
|
||||
{
|
||||
@ -84,6 +86,10 @@ Q_SIGNALS:
|
||||
void activateDatabaseChanged(DatabaseWidget* dbWidget);
|
||||
void databaseLocked(DatabaseWidget* dbWidget);
|
||||
void databaseUnlocked(DatabaseWidget* dbWidget);
|
||||
void messageGlobal(const QString&, MessageWidget::MessageType type);
|
||||
void messageTab(const QString&, MessageWidget::MessageType type);
|
||||
void messageDismissGlobal();
|
||||
void messageDismissTab();
|
||||
|
||||
private Q_SLOTS:
|
||||
void updateTabName(Database* db);
|
||||
|
@ -19,6 +19,7 @@
|
||||
|
||||
#include <QAction>
|
||||
#include <QDesktopServices>
|
||||
#include <QCheckBox>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QFile>
|
||||
@ -59,7 +60,14 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent)
|
||||
, m_newParent(nullptr)
|
||||
{
|
||||
m_mainWidget = new QWidget(this);
|
||||
QLayout* layout = new QHBoxLayout(m_mainWidget);
|
||||
|
||||
m_messageWidget = new MessageWidget(this);
|
||||
m_messageWidget->setHidden(true);
|
||||
|
||||
QVBoxLayout* mainLayout = new QVBoxLayout();
|
||||
QLayout* layout = new QHBoxLayout();
|
||||
mainLayout->addWidget(m_messageWidget);
|
||||
mainLayout->addLayout(layout);
|
||||
m_splitter = new QSplitter(m_mainWidget);
|
||||
m_splitter->setChildrenCollapsible(false);
|
||||
|
||||
@ -104,7 +112,7 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent)
|
||||
m_splitter->setStretchFactor(1, 70);
|
||||
|
||||
layout->addWidget(m_splitter);
|
||||
m_mainWidget->setLayout(layout);
|
||||
m_mainWidget->setLayout(mainLayout);
|
||||
|
||||
m_editEntryWidget = new EditEntryWidget();
|
||||
m_editEntryWidget->setObjectName("editEntryWidget");
|
||||
@ -312,8 +320,10 @@ void DatabaseWidget::cloneEntry()
|
||||
return;
|
||||
}
|
||||
|
||||
Entry* entry = currentEntry->clone(Entry::CloneNewUuid | Entry::CloneResetTimeInfo);
|
||||
Entry* entry = currentEntry->clone(Entry::CloneNewUuid | Entry::CloneResetTimeInfo | Entry::CloneRenameTitle);
|
||||
entry->setGroup(currentEntry->group());
|
||||
if (isInSearchMode())
|
||||
search(m_lastSearchText);
|
||||
m_entryView->setFocus();
|
||||
m_entryView->setCurrentEntry(entry);
|
||||
}
|
||||
@ -341,7 +351,7 @@ void DatabaseWidget::deleteEntries()
|
||||
result = MessageBox::question(
|
||||
this, tr("Delete entry?"),
|
||||
tr("Do you really want to delete the entry \"%1\" for good?")
|
||||
.arg(selectedEntries.first()->title()),
|
||||
.arg(selectedEntries.first()->title().toHtmlEscaped()),
|
||||
QMessageBox::Yes | QMessageBox::No);
|
||||
}
|
||||
else {
|
||||
@ -365,7 +375,7 @@ void DatabaseWidget::deleteEntries()
|
||||
result = MessageBox::question(
|
||||
this, tr("Move entry to recycle bin?"),
|
||||
tr("Do you really want to move entry \"%1\" to the recycle bin?")
|
||||
.arg(selectedEntries.first()->title()),
|
||||
.arg(selectedEntries.first()->title().toHtmlEscaped()),
|
||||
QMessageBox::Yes | QMessageBox::No);
|
||||
}
|
||||
else {
|
||||
@ -494,8 +504,46 @@ void DatabaseWidget::openUrlForEntry(Entry* entry)
|
||||
}
|
||||
|
||||
if (urlString.startsWith("cmd://")) {
|
||||
// check if decision to execute command was stored
|
||||
if (entry->attributes()->hasKey(EntryAttributes::RememberCmdExecAttr)) {
|
||||
if (entry->attributes()->value(EntryAttributes::RememberCmdExecAttr) == "1") {
|
||||
QProcess::startDetached(urlString.mid(6));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// otherwise ask user
|
||||
if (urlString.length() > 6) {
|
||||
QProcess::startDetached(urlString.mid(6));
|
||||
QString cmdTruncated = urlString.mid(6);
|
||||
if (cmdTruncated.length() > 400)
|
||||
cmdTruncated = cmdTruncated.left(400) + " […]";
|
||||
QMessageBox msgbox(QMessageBox::Icon::Question,
|
||||
tr("Execute command?"),
|
||||
tr("Do you really want to execute the following command?<br><br>%1<br>")
|
||||
.arg(cmdTruncated.toHtmlEscaped()),
|
||||
QMessageBox::Yes | QMessageBox::No,
|
||||
this
|
||||
);
|
||||
msgbox.setDefaultButton(QMessageBox::No);
|
||||
|
||||
QCheckBox* checkbox = new QCheckBox(tr("Remember my choice"), &msgbox);
|
||||
msgbox.setCheckBox(checkbox);
|
||||
bool remember = false;
|
||||
QObject::connect(checkbox, &QCheckBox::stateChanged, [&](int state) {
|
||||
if (static_cast<Qt::CheckState>(state) == Qt::CheckState::Checked) {
|
||||
remember = true;
|
||||
}
|
||||
});
|
||||
|
||||
int result = msgbox.exec();
|
||||
if (result == QMessageBox::Yes) {
|
||||
QProcess::startDetached(urlString.mid(6));
|
||||
}
|
||||
|
||||
if (remember) {
|
||||
entry->attributes()->set(EntryAttributes::RememberCmdExecAttr,
|
||||
result == QMessageBox::Yes ? "1" : "0");
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
@ -532,7 +580,7 @@ void DatabaseWidget::deleteGroup()
|
||||
QMessageBox::StandardButton result = MessageBox::question(
|
||||
this, tr("Delete group?"),
|
||||
tr("Do you really want to delete the group \"%1\" for good?")
|
||||
.arg(currentGroup->name()),
|
||||
.arg(currentGroup->name().toHtmlEscaped()),
|
||||
QMessageBox::Yes | QMessageBox::No);
|
||||
if (result == QMessageBox::Yes) {
|
||||
delete currentGroup;
|
||||
@ -648,7 +696,7 @@ void DatabaseWidget::updateMasterKey(bool accepted)
|
||||
QApplication::restoreOverrideCursor();
|
||||
|
||||
if (!result) {
|
||||
MessageBox::critical(this, tr("Error"), tr("Unable to calculate master key"));
|
||||
m_messageWidget->showMessage(tr("Unable to calculate master key"), MessageWidget::Error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -688,14 +736,14 @@ void DatabaseWidget::mergeDatabase(bool accepted)
|
||||
{
|
||||
if (accepted) {
|
||||
if (!m_db) {
|
||||
MessageBox::critical(this, tr("Error"), tr("No current database."));
|
||||
m_messageWidget->showMessage(tr("No current database."), MessageWidget::Error);
|
||||
return;
|
||||
}
|
||||
|
||||
Database* srcDb = static_cast<DatabaseOpenWidget*>(sender())->database();
|
||||
|
||||
if (!srcDb) {
|
||||
MessageBox::critical(this, tr("Error"), tr("No source database, nothing to do."));
|
||||
m_messageWidget->showMessage(tr("No source database, nothing to do."), MessageWidget::Error);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -722,15 +770,10 @@ void DatabaseWidget::unlockDatabase(bool accepted)
|
||||
|
||||
replaceDatabase(db);
|
||||
|
||||
const QList<Group*> groups = m_db->rootGroup()->groupsRecursive(true);
|
||||
for (Group* group : groups) {
|
||||
if (group->uuid() == m_groupBeforeLock) {
|
||||
m_groupView->setCurrentGroup(group);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
restoreGroupEntryFocus(m_groupBeforeLock, m_entryBeforeLock);
|
||||
m_groupBeforeLock = Uuid();
|
||||
m_entryBeforeLock = Uuid();
|
||||
|
||||
setCurrentWidget(m_mainWidget);
|
||||
m_unlockDatabaseWidget->clearForms();
|
||||
Q_EMIT unlockedDatabase();
|
||||
@ -755,7 +798,7 @@ void DatabaseWidget::entryActivationSignalReceived(Entry* entry, EntryModel::Mod
|
||||
void DatabaseWidget::switchToEntryEdit()
|
||||
{
|
||||
Entry* entry = m_entryView->currentEntry();
|
||||
Q_ASSERT(entry);
|
||||
|
||||
if (!entry) {
|
||||
return;
|
||||
}
|
||||
@ -766,7 +809,7 @@ void DatabaseWidget::switchToEntryEdit()
|
||||
void DatabaseWidget::switchToGroupEdit()
|
||||
{
|
||||
Group* group = m_groupView->currentGroup();
|
||||
Q_ASSERT(group);
|
||||
|
||||
if (!group) {
|
||||
return;
|
||||
}
|
||||
@ -943,6 +986,10 @@ void DatabaseWidget::lock()
|
||||
m_groupBeforeLock = m_db->rootGroup()->uuid();
|
||||
}
|
||||
|
||||
if (m_entryView->currentEntry()) {
|
||||
m_entryBeforeLock = m_entryView->currentEntry()->uuid();
|
||||
}
|
||||
|
||||
clearAllWidgets();
|
||||
m_unlockDatabaseWidget->load(m_filename);
|
||||
setCurrentWidget(m_unlockDatabaseWidget);
|
||||
@ -1028,18 +1075,33 @@ void DatabaseWidget::reloadDatabaseFile()
|
||||
}
|
||||
}
|
||||
|
||||
Uuid groupBeforeReload;
|
||||
if (m_groupView && m_groupView->currentGroup()) {
|
||||
groupBeforeReload = m_groupView->currentGroup()->uuid();
|
||||
}
|
||||
else {
|
||||
groupBeforeReload = m_db->rootGroup()->uuid();
|
||||
}
|
||||
|
||||
Uuid entryBeforeReload;
|
||||
if (m_entryView && m_entryView->currentEntry()) {
|
||||
entryBeforeReload = m_entryView->currentEntry()->uuid();
|
||||
}
|
||||
|
||||
replaceDatabase(db);
|
||||
restoreGroupEntryFocus(groupBeforeReload, entryBeforeReload);
|
||||
|
||||
}
|
||||
else {
|
||||
MessageBox::critical(this, tr("Autoreload Failed"),
|
||||
tr("Could not parse or unlock the new database file while attempting"
|
||||
" to autoreload this database."));
|
||||
m_messageWidget->showMessage(
|
||||
tr("Could not parse or unlock the new database file while attempting"
|
||||
" to autoreload this database."), MessageWidget::Error);
|
||||
}
|
||||
}
|
||||
else {
|
||||
MessageBox::critical(this, tr("Autoreload Failed"),
|
||||
tr("Could not open the new database file while attempting to autoreload"
|
||||
" this database."));
|
||||
m_messageWidget->showMessage(
|
||||
tr("Could not open the new database file while attempting to autoreload this database."),
|
||||
MessageWidget::Error);
|
||||
}
|
||||
|
||||
// Rewatch the database file
|
||||
@ -1061,6 +1123,35 @@ QStringList DatabaseWidget::customEntryAttributes() const
|
||||
return entry->attributes()->customKeys();
|
||||
}
|
||||
|
||||
/*
|
||||
* Restores the focus on the group and entry that was focused
|
||||
* before the database was locked or reloaded.
|
||||
*/
|
||||
void DatabaseWidget::restoreGroupEntryFocus(Uuid groupUuid, Uuid entryUuid)
|
||||
{
|
||||
Group* restoredGroup = nullptr;
|
||||
const QList<Group*> groups = m_db->rootGroup()->groupsRecursive(true);
|
||||
for (Group* group : groups) {
|
||||
if (group->uuid() == groupUuid) {
|
||||
restoredGroup = group;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (restoredGroup != nullptr) {
|
||||
m_groupView->setCurrentGroup(restoredGroup);
|
||||
|
||||
const QList<Entry*> entries = restoredGroup->entries();
|
||||
for (Entry* entry : entries) {
|
||||
if (entry->uuid() == entryUuid) {
|
||||
m_entryView->setCurrentEntry(entry);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool DatabaseWidget::isGroupSelected() const
|
||||
{
|
||||
return m_groupView->currentGroup() != nullptr;
|
||||
@ -1136,3 +1227,15 @@ void DatabaseWidget::closeUnlockDialog()
|
||||
{
|
||||
m_unlockDatabaseDialog->close();
|
||||
}
|
||||
|
||||
void DatabaseWidget::showMessage(const QString& text, MessageWidget::MessageType type)
|
||||
{
|
||||
m_messageWidget->showMessage(text, type);
|
||||
}
|
||||
|
||||
void DatabaseWidget::hideMessage()
|
||||
{
|
||||
if (m_messageWidget->isVisible()) {
|
||||
m_messageWidget->animatedHide();
|
||||
}
|
||||
}
|
||||
|
@ -26,6 +26,7 @@
|
||||
#include "core/Uuid.h"
|
||||
|
||||
#include "gui/entry/EntryModel.h"
|
||||
#include "gui/MessageWidget.h"
|
||||
|
||||
class ChangeMasterKeyWidget;
|
||||
class DatabaseOpenWidget;
|
||||
@ -43,9 +44,14 @@ class QMenu;
|
||||
class QSplitter;
|
||||
class QLabel;
|
||||
class UnlockDatabaseWidget;
|
||||
class MessageWidget;
|
||||
class UnlockDatabaseDialog;
|
||||
class QFileSystemWatcher;
|
||||
|
||||
namespace Ui {
|
||||
class SearchWidget;
|
||||
}
|
||||
|
||||
class DatabaseWidget : public QStackedWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
@ -145,6 +151,8 @@ public Q_SLOTS:
|
||||
void search(const QString& searchtext);
|
||||
void setSearchCaseSensitive(bool state);
|
||||
void endSearch();
|
||||
void showMessage(const QString& text, MessageWidget::MessageType type);
|
||||
void hideMessage();
|
||||
|
||||
private Q_SLOTS:
|
||||
void entryActivationSignalReceived(Entry* entry, EntryModel::ModelColumn column);
|
||||
@ -163,6 +171,7 @@ private Q_SLOTS:
|
||||
// Database autoreload slots
|
||||
void onWatchedFileChanged();
|
||||
void reloadDatabaseFile();
|
||||
void restoreGroupEntryFocus(Uuid groupUuid, Uuid EntryUuid);
|
||||
|
||||
private:
|
||||
void setClipboardTextAndMinimize(const QString& text);
|
||||
@ -190,6 +199,8 @@ private:
|
||||
Group* m_newParent;
|
||||
QString m_filename;
|
||||
Uuid m_groupBeforeLock;
|
||||
Uuid m_entryBeforeLock;
|
||||
MessageWidget* m_messageWidget;
|
||||
|
||||
// Search state
|
||||
QString m_lastSearchText;
|
||||
|
@ -149,3 +149,4 @@ QVariant DatabaseWidgetStateSync::intListToVariant(const QList<int>& list)
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -25,6 +25,8 @@ EditWidget::EditWidget(QWidget* parent)
|
||||
m_ui->setupUi(this);
|
||||
setReadOnly(false);
|
||||
|
||||
m_ui->messageWidget->setHidden(true);
|
||||
|
||||
QFont headerLabelFont = m_ui->headerLabel->font();
|
||||
headerLabelFont.setBold(true);
|
||||
headerLabelFont.setPointSize(headerLabelFont.pointSize() + 2);
|
||||
@ -86,3 +88,15 @@ bool EditWidget::readOnly() const
|
||||
{
|
||||
return m_readOnly;
|
||||
}
|
||||
|
||||
void EditWidget::showMessage(const QString& text, MessageWidget::MessageType type)
|
||||
{
|
||||
m_ui->messageWidget->showMessage(text, type);
|
||||
}
|
||||
|
||||
void EditWidget::hideMessage()
|
||||
{
|
||||
if (m_ui->messageWidget->isVisible()) {
|
||||
m_ui->messageWidget->animatedHide();
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,7 @@
|
||||
#include <QScopedPointer>
|
||||
|
||||
#include "gui/DialogyWidget.h"
|
||||
#include "gui/MessageWidget.h"
|
||||
|
||||
class QLabel;
|
||||
|
||||
@ -48,6 +49,10 @@ Q_SIGNALS:
|
||||
void accepted();
|
||||
void rejected();
|
||||
|
||||
protected Q_SLOTS:
|
||||
void showMessage(const QString& text, MessageWidget::MessageType type);
|
||||
void hideMessage();
|
||||
|
||||
private:
|
||||
const QScopedPointer<Ui::EditWidget> m_ui;
|
||||
bool m_readOnly;
|
||||
|
@ -11,6 +11,9 @@
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="MessageWidget" name="messageWidget" native="true"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="headerLabel">
|
||||
<property name="text">
|
||||
@ -58,6 +61,12 @@
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>MessageWidget</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>gui/MessageWidget.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>CategoryListWidget</class>
|
||||
<extends>QListWidget</extends>
|
||||
|
@ -158,12 +158,11 @@ void EditWidgetIcons::fetchFavicon(QUrl url)
|
||||
|
||||
void EditWidgetIcons::fetchFaviconFromGoogle(QString domain)
|
||||
{
|
||||
if (m_fallbackToGoogle) {
|
||||
if (m_fallbackToGoogle) {
|
||||
abortFaviconDownload();
|
||||
m_fallbackToGoogle = false;
|
||||
fetchFavicon(QUrl("http://www.google.com/s2/favicons?domain=" + domain));
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
abortFaviconDownload();
|
||||
MessageBox::warning(this, tr("Error"), tr("Unable to fetch favicon."));
|
||||
}
|
||||
@ -190,6 +189,10 @@ void EditWidgetIcons::abortFaviconDownload(bool clearRedirect)
|
||||
|
||||
void EditWidgetIcons::onRequestFinished(QNetworkReply *reply)
|
||||
{
|
||||
if (m_database == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!reply->error()) {
|
||||
QImage image;
|
||||
image.loadFromData(reply->readAll());
|
||||
@ -244,7 +247,7 @@ void EditWidgetIcons::addCustomIcon()
|
||||
m_ui->customIconsView->setCurrentIndex(index);
|
||||
}
|
||||
else {
|
||||
MessageBox::critical(this, tr("Error"), tr("Can't read icon"));
|
||||
Q_EMIT messageEditEntry(tr("Can't read icon"), MessageWidget::Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -298,9 +301,8 @@ void EditWidgetIcons::removeCustomIcon()
|
||||
}
|
||||
}
|
||||
else {
|
||||
MessageBox::information(this, tr("Can't delete icon!"),
|
||||
tr("Can't delete icon. Still used by %1 items.")
|
||||
.arg(iconUsedCount));
|
||||
Q_EMIT messageEditEntry(
|
||||
tr("Can't delete icon. Still used by %1 items.").arg(iconUsedCount), MessageWidget::Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -26,6 +26,7 @@
|
||||
|
||||
#include "core/Global.h"
|
||||
#include "core/Uuid.h"
|
||||
#include "gui/MessageWidget.h"
|
||||
|
||||
class Database;
|
||||
class DefaultIconModel;
|
||||
@ -58,6 +59,10 @@ public:
|
||||
public Q_SLOTS:
|
||||
void setUrl(const QString &url);
|
||||
|
||||
Q_SIGNALS:
|
||||
void messageEditEntry(QString, MessageWidget::MessageType);
|
||||
void messageEditEntryDismiss();
|
||||
|
||||
private Q_SLOTS:
|
||||
void downloadFavicon();
|
||||
void fetchFavicon(QUrl url);
|
||||
|
480
src/gui/KMessageWidget.cpp
Normal file
480
src/gui/KMessageWidget.cpp
Normal file
@ -0,0 +1,480 @@
|
||||
/* This file is part of the KDE libraries
|
||||
*
|
||||
* Copyright (c) 2011 Aurélien Gâteau <agateau@kde.org>
|
||||
* Copyright (c) 2014 Dominik Haumann <dhaumann@kde.org>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
* 02110-1301 USA
|
||||
*/
|
||||
#include "KMessageWidget.h"
|
||||
|
||||
#include <QAction>
|
||||
#include <QEvent>
|
||||
#include <QGridLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QPainter>
|
||||
#include <QShowEvent>
|
||||
#include <QTimeLine>
|
||||
#include <QToolButton>
|
||||
#include <QStyle>
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// KMessageWidgetPrivate
|
||||
//---------------------------------------------------------------------
|
||||
class KMessageWidgetPrivate
|
||||
{
|
||||
public:
|
||||
void init(KMessageWidget *);
|
||||
|
||||
KMessageWidget *q;
|
||||
QFrame *content;
|
||||
QLabel *iconLabel;
|
||||
QLabel *textLabel;
|
||||
QToolButton *closeButton;
|
||||
QTimeLine *timeLine;
|
||||
QIcon icon;
|
||||
|
||||
KMessageWidget::MessageType messageType;
|
||||
bool wordWrap;
|
||||
QList<QToolButton *> buttons;
|
||||
QPixmap contentSnapShot;
|
||||
|
||||
void createLayout();
|
||||
void updateSnapShot();
|
||||
void updateLayout();
|
||||
void slotTimeLineChanged(qreal);
|
||||
void slotTimeLineFinished();
|
||||
|
||||
int bestContentHeight() const;
|
||||
};
|
||||
|
||||
void KMessageWidgetPrivate::init(KMessageWidget *q_ptr)
|
||||
{
|
||||
q = q_ptr;
|
||||
|
||||
q->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
|
||||
|
||||
timeLine = new QTimeLine(500, q);
|
||||
QObject::connect(timeLine, SIGNAL(valueChanged(qreal)), q, SLOT(slotTimeLineChanged(qreal)));
|
||||
QObject::connect(timeLine, SIGNAL(finished()), q, SLOT(slotTimeLineFinished()));
|
||||
|
||||
content = new QFrame(q);
|
||||
content->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
||||
|
||||
wordWrap = false;
|
||||
|
||||
iconLabel = new QLabel(content);
|
||||
iconLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||
iconLabel->hide();
|
||||
|
||||
textLabel = new QLabel(content);
|
||||
textLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
||||
textLabel->setTextInteractionFlags(Qt::TextBrowserInteraction);
|
||||
QObject::connect(textLabel, SIGNAL(linkActivated(QString)), q, SIGNAL(linkActivated(QString)));
|
||||
QObject::connect(textLabel, SIGNAL(linkHovered(QString)), q, SIGNAL(linkHovered(QString)));
|
||||
|
||||
QAction *closeAction = new QAction(q);
|
||||
closeAction->setText(KMessageWidget::tr("&Close"));
|
||||
closeAction->setToolTip(KMessageWidget::tr("Close message"));
|
||||
closeAction->setIcon(q->style()->standardIcon(QStyle::SP_DialogCloseButton));
|
||||
|
||||
QObject::connect(closeAction, SIGNAL(triggered(bool)), q, SLOT(animatedHide()));
|
||||
|
||||
closeButton = new QToolButton(content);
|
||||
closeButton->setAutoRaise(true);
|
||||
closeButton->setDefaultAction(closeAction);
|
||||
|
||||
q->setMessageType(KMessageWidget::Information);
|
||||
}
|
||||
|
||||
void KMessageWidgetPrivate::createLayout()
|
||||
{
|
||||
delete content->layout();
|
||||
|
||||
content->resize(q->size());
|
||||
|
||||
qDeleteAll(buttons);
|
||||
buttons.clear();
|
||||
|
||||
Q_FOREACH (QAction *action, q->actions()) {
|
||||
QToolButton *button = new QToolButton(content);
|
||||
button->setDefaultAction(action);
|
||||
button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
|
||||
buttons.append(button);
|
||||
}
|
||||
|
||||
// AutoRaise reduces visual clutter, but we don't want to turn it on if
|
||||
// there are other buttons, otherwise the close button will look different
|
||||
// from the others.
|
||||
closeButton->setAutoRaise(buttons.isEmpty());
|
||||
|
||||
if (wordWrap) {
|
||||
QGridLayout *layout = new QGridLayout(content);
|
||||
// Set alignment to make sure icon does not move down if text wraps
|
||||
layout->addWidget(iconLabel, 0, 0, 1, 1, Qt::AlignHCenter | Qt::AlignTop);
|
||||
layout->addWidget(textLabel, 0, 1);
|
||||
|
||||
QHBoxLayout *buttonLayout = new QHBoxLayout;
|
||||
buttonLayout->addStretch();
|
||||
Q_FOREACH (QToolButton *button, buttons) {
|
||||
// For some reason, calling show() is necessary if wordwrap is true,
|
||||
// otherwise the buttons do not show up. It is not needed if
|
||||
// wordwrap is false.
|
||||
button->show();
|
||||
buttonLayout->addWidget(button);
|
||||
}
|
||||
buttonLayout->addWidget(closeButton);
|
||||
layout->addItem(buttonLayout, 1, 0, 1, 2);
|
||||
} else {
|
||||
QHBoxLayout *layout = new QHBoxLayout(content);
|
||||
layout->addWidget(iconLabel);
|
||||
layout->addWidget(textLabel);
|
||||
|
||||
Q_FOREACH (QToolButton *button, buttons) {
|
||||
layout->addWidget(button);
|
||||
}
|
||||
|
||||
layout->addWidget(closeButton);
|
||||
};
|
||||
|
||||
if (q->isVisible()) {
|
||||
q->setFixedHeight(content->sizeHint().height());
|
||||
}
|
||||
q->updateGeometry();
|
||||
}
|
||||
|
||||
void KMessageWidgetPrivate::updateLayout()
|
||||
{
|
||||
if (content->layout()) {
|
||||
createLayout();
|
||||
}
|
||||
}
|
||||
|
||||
void KMessageWidgetPrivate::updateSnapShot()
|
||||
{
|
||||
// Attention: updateSnapShot calls QWidget::render(), which causes the whole
|
||||
// window layouts to be activated. Calling this method from resizeEvent()
|
||||
// can lead to infinite recursion, see:
|
||||
// https://bugs.kde.org/show_bug.cgi?id=311336
|
||||
contentSnapShot = QPixmap(content->size() * q->devicePixelRatio());
|
||||
contentSnapShot.setDevicePixelRatio(q->devicePixelRatio());
|
||||
contentSnapShot.fill(Qt::transparent);
|
||||
content->render(&contentSnapShot, QPoint(), QRegion(), QWidget::DrawChildren);
|
||||
}
|
||||
|
||||
void KMessageWidgetPrivate::slotTimeLineChanged(qreal value)
|
||||
{
|
||||
q->setFixedHeight(qMin(value * 2, qreal(1.0)) * content->height());
|
||||
q->update();
|
||||
}
|
||||
|
||||
void KMessageWidgetPrivate::slotTimeLineFinished()
|
||||
{
|
||||
if (timeLine->direction() == QTimeLine::Forward) {
|
||||
// Show
|
||||
// We set the whole geometry here, because it may be wrong if a
|
||||
// KMessageWidget is shown right when the toplevel window is created.
|
||||
content->setGeometry(0, 0, q->width(), bestContentHeight());
|
||||
|
||||
// notify about finished animation
|
||||
emit q->showAnimationFinished();
|
||||
} else {
|
||||
// hide and notify about finished animation
|
||||
q->hide();
|
||||
emit q->hideAnimationFinished();
|
||||
}
|
||||
}
|
||||
|
||||
int KMessageWidgetPrivate::bestContentHeight() const
|
||||
{
|
||||
int height = content->heightForWidth(q->width());
|
||||
if (height == -1) {
|
||||
height = content->sizeHint().height();
|
||||
}
|
||||
return height;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// KMessageWidget
|
||||
//---------------------------------------------------------------------
|
||||
KMessageWidget::KMessageWidget(QWidget *parent)
|
||||
: QFrame(parent)
|
||||
, d(new KMessageWidgetPrivate)
|
||||
{
|
||||
d->init(this);
|
||||
}
|
||||
|
||||
KMessageWidget::KMessageWidget(const QString &text, QWidget *parent)
|
||||
: QFrame(parent)
|
||||
, d(new KMessageWidgetPrivate)
|
||||
{
|
||||
d->init(this);
|
||||
setText(text);
|
||||
}
|
||||
|
||||
KMessageWidget::~KMessageWidget()
|
||||
{
|
||||
delete d;
|
||||
}
|
||||
|
||||
QString KMessageWidget::text() const
|
||||
{
|
||||
return d->textLabel->text();
|
||||
}
|
||||
|
||||
void KMessageWidget::setText(const QString &text)
|
||||
{
|
||||
d->textLabel->setText(text);
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
KMessageWidget::MessageType KMessageWidget::messageType() const
|
||||
{
|
||||
return d->messageType;
|
||||
}
|
||||
|
||||
static QColor darkShade(QColor c)
|
||||
{
|
||||
qreal contrast = 0.7; // taken from kcolorscheme for the dark shade
|
||||
|
||||
qreal darkAmount;
|
||||
if (c.lightnessF() < 0.006) { /* too dark */
|
||||
darkAmount = 0.02 + 0.40 * contrast;
|
||||
} else if (c.lightnessF() > 0.93) { /* too bright */
|
||||
darkAmount = -0.06 - 0.60 * contrast;
|
||||
} else {
|
||||
darkAmount = (-c.lightnessF()) * (0.55 + contrast * 0.35);
|
||||
}
|
||||
|
||||
qreal v = c.lightnessF() + darkAmount;
|
||||
v = v > 0.0 ? (v < 1.0 ? v : 1.0) : 0.0;
|
||||
c.setHsvF(c.hslHueF(), c.hslSaturationF(), v);
|
||||
return c;
|
||||
}
|
||||
|
||||
void KMessageWidget::setMessageType(KMessageWidget::MessageType type)
|
||||
{
|
||||
d->messageType = type;
|
||||
QColor bg0, bg1, bg2, border, fg;
|
||||
switch (type) {
|
||||
case Positive:
|
||||
bg1.setRgb(0, 110, 40); // values taken from kcolorscheme.cpp (Positive)
|
||||
break;
|
||||
case Information:
|
||||
bg1 = palette().highlight().color();
|
||||
break;
|
||||
case Warning:
|
||||
bg1.setRgb(176, 128, 0); // values taken from kcolorscheme.cpp (Neutral)
|
||||
break;
|
||||
case Error:
|
||||
bg1.setRgb(191, 3, 3); // values taken from kcolorscheme.cpp (Negative)
|
||||
break;
|
||||
}
|
||||
|
||||
// Colors
|
||||
fg = palette().highlightedText().color();
|
||||
bg0 = bg1.lighter(110);
|
||||
bg2 = bg1.darker(110);
|
||||
border = darkShade(bg1);
|
||||
|
||||
d->content->setStyleSheet(
|
||||
QString(QLatin1String(".QFrame {"
|
||||
"background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,"
|
||||
" stop: 0 %1,"
|
||||
" stop: 0.1 %2,"
|
||||
" stop: 1.0 %3);"
|
||||
"border-radius: 5px;"
|
||||
"border: 1px solid %4;"
|
||||
"margin: %5px;"
|
||||
"}"
|
||||
".QLabel { color: %6; }"
|
||||
))
|
||||
.arg(bg0.name())
|
||||
.arg(bg1.name())
|
||||
.arg(bg2.name())
|
||||
.arg(border.name())
|
||||
// DefaultFrameWidth returns the size of the external margin + border width. We know our border is 1px, so we subtract this from the frame normal QStyle FrameWidth to get our margin
|
||||
.arg(style()->pixelMetric(QStyle::PM_DefaultFrameWidth, 0, this) - 1)
|
||||
.arg(fg.name())
|
||||
);
|
||||
}
|
||||
|
||||
QSize KMessageWidget::sizeHint() const
|
||||
{
|
||||
ensurePolished();
|
||||
return d->content->sizeHint();
|
||||
}
|
||||
|
||||
QSize KMessageWidget::minimumSizeHint() const
|
||||
{
|
||||
ensurePolished();
|
||||
return d->content->minimumSizeHint();
|
||||
}
|
||||
|
||||
bool KMessageWidget::event(QEvent *event)
|
||||
{
|
||||
if (event->type() == QEvent::Polish && !d->content->layout()) {
|
||||
d->createLayout();
|
||||
}
|
||||
return QFrame::event(event);
|
||||
}
|
||||
|
||||
void KMessageWidget::resizeEvent(QResizeEvent *event)
|
||||
{
|
||||
QFrame::resizeEvent(event);
|
||||
|
||||
if (d->timeLine->state() == QTimeLine::NotRunning) {
|
||||
d->content->resize(width(), d->bestContentHeight());
|
||||
}
|
||||
}
|
||||
|
||||
int KMessageWidget::heightForWidth(int width) const
|
||||
{
|
||||
ensurePolished();
|
||||
return d->content->heightForWidth(width);
|
||||
}
|
||||
|
||||
void KMessageWidget::paintEvent(QPaintEvent *event)
|
||||
{
|
||||
QFrame::paintEvent(event);
|
||||
if (d->timeLine->state() == QTimeLine::Running) {
|
||||
QPainter painter(this);
|
||||
painter.setOpacity(d->timeLine->currentValue() * d->timeLine->currentValue());
|
||||
painter.drawPixmap(0, 0, d->contentSnapShot);
|
||||
}
|
||||
}
|
||||
|
||||
bool KMessageWidget::wordWrap() const
|
||||
{
|
||||
return d->wordWrap;
|
||||
}
|
||||
|
||||
void KMessageWidget::setWordWrap(bool wordWrap)
|
||||
{
|
||||
d->wordWrap = wordWrap;
|
||||
d->textLabel->setWordWrap(wordWrap);
|
||||
QSizePolicy policy = sizePolicy();
|
||||
policy.setHeightForWidth(wordWrap);
|
||||
setSizePolicy(policy);
|
||||
d->updateLayout();
|
||||
// Without this, when user does wordWrap -> !wordWrap -> wordWrap, a minimum
|
||||
// height is set, causing the widget to be too high.
|
||||
// Mostly visible in test programs.
|
||||
if (wordWrap) {
|
||||
setMinimumHeight(0);
|
||||
}
|
||||
}
|
||||
|
||||
bool KMessageWidget::isCloseButtonVisible() const
|
||||
{
|
||||
return d->closeButton->isVisible();
|
||||
}
|
||||
|
||||
void KMessageWidget::setCloseButtonVisible(bool show)
|
||||
{
|
||||
d->closeButton->setVisible(show);
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void KMessageWidget::addAction(QAction *action)
|
||||
{
|
||||
QFrame::addAction(action);
|
||||
d->updateLayout();
|
||||
}
|
||||
|
||||
void KMessageWidget::removeAction(QAction *action)
|
||||
{
|
||||
QFrame::removeAction(action);
|
||||
d->updateLayout();
|
||||
}
|
||||
|
||||
void KMessageWidget::animatedShow()
|
||||
{
|
||||
if (!style()->styleHint(QStyle::SH_Widget_Animate, 0, this)) {
|
||||
show();
|
||||
emit showAnimationFinished();
|
||||
return;
|
||||
}
|
||||
|
||||
if (isVisible()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QFrame::show();
|
||||
setFixedHeight(0);
|
||||
int wantedHeight = d->bestContentHeight();
|
||||
d->content->setGeometry(0, -wantedHeight, width(), wantedHeight);
|
||||
|
||||
d->updateSnapShot();
|
||||
|
||||
d->timeLine->setDirection(QTimeLine::Forward);
|
||||
if (d->timeLine->state() == QTimeLine::NotRunning) {
|
||||
d->timeLine->start();
|
||||
}
|
||||
}
|
||||
|
||||
void KMessageWidget::animatedHide()
|
||||
{
|
||||
if (!style()->styleHint(QStyle::SH_Widget_Animate, 0, this)) {
|
||||
hide();
|
||||
emit hideAnimationFinished();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isVisible()) {
|
||||
hide();
|
||||
return;
|
||||
}
|
||||
|
||||
d->content->move(0, -d->content->height());
|
||||
d->updateSnapShot();
|
||||
|
||||
d->timeLine->setDirection(QTimeLine::Backward);
|
||||
if (d->timeLine->state() == QTimeLine::NotRunning) {
|
||||
d->timeLine->start();
|
||||
}
|
||||
}
|
||||
|
||||
bool KMessageWidget::isHideAnimationRunning() const
|
||||
{
|
||||
return (d->timeLine->direction() == QTimeLine::Backward)
|
||||
&& (d->timeLine->state() == QTimeLine::Running);
|
||||
}
|
||||
|
||||
bool KMessageWidget::isShowAnimationRunning() const
|
||||
{
|
||||
return (d->timeLine->direction() == QTimeLine::Forward)
|
||||
&& (d->timeLine->state() == QTimeLine::Running);
|
||||
}
|
||||
|
||||
QIcon KMessageWidget::icon() const
|
||||
{
|
||||
return d->icon;
|
||||
}
|
||||
|
||||
void KMessageWidget::setIcon(const QIcon &icon)
|
||||
{
|
||||
d->icon = icon;
|
||||
if (d->icon.isNull()) {
|
||||
d->iconLabel->hide();
|
||||
} else {
|
||||
const int size = style()->pixelMetric(QStyle::PM_ToolBarIconSize);
|
||||
d->iconLabel->setPixmap(d->icon.pixmap(size));
|
||||
d->iconLabel->show();
|
||||
}
|
||||
}
|
||||
|
||||
#include "moc_KMessageWidget.cpp"
|
342
src/gui/KMessageWidget.h
Normal file
342
src/gui/KMessageWidget.h
Normal file
@ -0,0 +1,342 @@
|
||||
/* This file is part of the KDE libraries
|
||||
*
|
||||
* Copyright (c) 2011 Aurélien Gâteau <agateau@kde.org>
|
||||
* Copyright (c) 2014 Dominik Haumann <dhaumann@kde.org>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
* 02110-1301 USA
|
||||
*/
|
||||
#ifndef KMESSAGEWIDGET_H
|
||||
#define KMESSAGEWIDGET_H
|
||||
|
||||
#include <QFrame>
|
||||
|
||||
class KMessageWidgetPrivate;
|
||||
|
||||
/**
|
||||
* @short A widget to provide feedback or propose opportunistic interactions.
|
||||
*
|
||||
* KMessageWidget can be used to provide inline positive or negative
|
||||
* feedback, or to implement opportunistic interactions.
|
||||
*
|
||||
* As a feedback widget, KMessageWidget provides a less intrusive alternative
|
||||
* to "OK Only" message boxes. If you want to avoid a modal KMessageBox,
|
||||
* consider using KMessageWidget instead.
|
||||
*
|
||||
* Examples of KMessageWidget look as follows, all of them having an icon set
|
||||
* with setIcon(), and the first three show a close button:
|
||||
*
|
||||
* \image html kmessagewidget.png "KMessageWidget with different message types"
|
||||
*
|
||||
* <b>Negative feedback</b>
|
||||
*
|
||||
* The KMessageWidget can be used as a secondary indicator of failure: the
|
||||
* first indicator is usually the fact the action the user expected to happen
|
||||
* did not happen.
|
||||
*
|
||||
* Example: User fills a form, clicks "Submit".
|
||||
*
|
||||
* @li Expected feedback: form closes
|
||||
* @li First indicator of failure: form stays there
|
||||
* @li Second indicator of failure: a KMessageWidget appears on top of the
|
||||
* form, explaining the error condition
|
||||
*
|
||||
* When used to provide negative feedback, KMessageWidget should be placed
|
||||
* close to its context. In the case of a form, it should appear on top of the
|
||||
* form entries.
|
||||
*
|
||||
* KMessageWidget should get inserted in the existing layout. Space should not
|
||||
* be reserved for it, otherwise it becomes "dead space", ignored by the user.
|
||||
* KMessageWidget should also not appear as an overlay to prevent blocking
|
||||
* access to elements the user needs to interact with to fix the failure.
|
||||
*
|
||||
* <b>Positive feedback</b>
|
||||
*
|
||||
* KMessageWidget can be used for positive feedback but it shouldn't be
|
||||
* overused. It is often enough to provide feedback by simply showing the
|
||||
* results of an action.
|
||||
*
|
||||
* Examples of acceptable uses:
|
||||
*
|
||||
* @li Confirm success of "critical" transactions
|
||||
* @li Indicate completion of background tasks
|
||||
*
|
||||
* Example of unadapted uses:
|
||||
*
|
||||
* @li Indicate successful saving of a file
|
||||
* @li Indicate a file has been successfully removed
|
||||
*
|
||||
* <b>Opportunistic interaction</b>
|
||||
*
|
||||
* Opportunistic interaction is the situation where the application suggests to
|
||||
* the user an action he could be interested in perform, either based on an
|
||||
* action the user just triggered or an event which the application noticed.
|
||||
*
|
||||
* Example of acceptable uses:
|
||||
*
|
||||
* @li A browser can propose remembering a recently entered password
|
||||
* @li A music collection can propose ripping a CD which just got inserted
|
||||
* @li A chat application may notify the user a "special friend" just connected
|
||||
*
|
||||
* @author Aurélien Gâteau <agateau@kde.org>
|
||||
* @since 4.7
|
||||
*/
|
||||
class KMessageWidget : public QFrame
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_ENUMS(MessageType)
|
||||
|
||||
Q_PROPERTY(QString text READ text WRITE setText)
|
||||
Q_PROPERTY(bool wordWrap READ wordWrap WRITE setWordWrap)
|
||||
Q_PROPERTY(bool closeButtonVisible READ isCloseButtonVisible WRITE setCloseButtonVisible)
|
||||
Q_PROPERTY(MessageType messageType READ messageType WRITE setMessageType)
|
||||
Q_PROPERTY(QIcon icon READ icon WRITE setIcon)
|
||||
public:
|
||||
|
||||
/**
|
||||
* Available message types.
|
||||
* The background colors are chosen depending on the message type.
|
||||
*/
|
||||
enum MessageType {
|
||||
Positive,
|
||||
Information,
|
||||
Warning,
|
||||
Error
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructs a KMessageWidget with the specified @p parent.
|
||||
*/
|
||||
explicit KMessageWidget(QWidget *parent = 0);
|
||||
|
||||
/**
|
||||
* Constructs a KMessageWidget with the specified @p parent and
|
||||
* contents @p text.
|
||||
*/
|
||||
explicit KMessageWidget(const QString &text, QWidget *parent = 0);
|
||||
|
||||
/**
|
||||
* Destructor.
|
||||
*/
|
||||
~KMessageWidget();
|
||||
|
||||
/**
|
||||
* Get the text of this message widget.
|
||||
* @see setText()
|
||||
*/
|
||||
QString text() const;
|
||||
|
||||
/**
|
||||
* Check whether word wrap is enabled.
|
||||
*
|
||||
* If word wrap is enabled, the message widget wraps the displayed text
|
||||
* as required to the available width of the widget. This is useful to
|
||||
* avoid breaking widget layouts.
|
||||
*
|
||||
* @see setWordWrap()
|
||||
*/
|
||||
bool wordWrap() const;
|
||||
|
||||
/**
|
||||
* Check whether the close button is visible.
|
||||
*
|
||||
* @see setCloseButtonVisible()
|
||||
*/
|
||||
bool isCloseButtonVisible() const;
|
||||
|
||||
/**
|
||||
* Get the type of this message.
|
||||
* By default, the type is set to KMessageWidget::Information.
|
||||
*
|
||||
* @see KMessageWidget::MessageType, setMessageType()
|
||||
*/
|
||||
MessageType messageType() const;
|
||||
|
||||
/**
|
||||
* Add @p action to the message widget.
|
||||
* For each action a button is added to the message widget in the
|
||||
* order the actions were added.
|
||||
*
|
||||
* @param action the action to add
|
||||
* @see removeAction(), QWidget::actions()
|
||||
*/
|
||||
void addAction(QAction *action);
|
||||
|
||||
/**
|
||||
* Remove @p action from the message widget.
|
||||
*
|
||||
* @param action the action to remove
|
||||
* @see KMessageWidget::MessageType, addAction(), setMessageType()
|
||||
*/
|
||||
void removeAction(QAction *action);
|
||||
|
||||
/**
|
||||
* Returns the preferred size of the message widget.
|
||||
*/
|
||||
QSize sizeHint() const Q_DECL_OVERRIDE;
|
||||
|
||||
/**
|
||||
* Returns the minimum size of the message widget.
|
||||
*/
|
||||
QSize minimumSizeHint() const Q_DECL_OVERRIDE;
|
||||
|
||||
/**
|
||||
* Returns the required height for @p width.
|
||||
* @param width the width in pixels
|
||||
*/
|
||||
int heightForWidth(int width) const Q_DECL_OVERRIDE;
|
||||
|
||||
/**
|
||||
* The icon shown on the left of the text. By default, no icon is shown.
|
||||
* @since 4.11
|
||||
*/
|
||||
QIcon icon() const;
|
||||
|
||||
/**
|
||||
* Check whether the hide animation started by calling animatedHide()
|
||||
* is still running. If animations are disabled, this function always
|
||||
* returns @e false.
|
||||
*
|
||||
* @see animatedHide(), hideAnimationFinished()
|
||||
* @since 5.0
|
||||
*/
|
||||
bool isHideAnimationRunning() const;
|
||||
|
||||
/**
|
||||
* Check whether the show animation started by calling animatedShow()
|
||||
* is still running. If animations are disabled, this function always
|
||||
* returns @e false.
|
||||
*
|
||||
* @see animatedShow(), showAnimationFinished()
|
||||
* @since 5.0
|
||||
*/
|
||||
bool isShowAnimationRunning() const;
|
||||
|
||||
public Q_SLOTS:
|
||||
/**
|
||||
* Set the text of the message widget to @p text.
|
||||
* If the message widget is already visible, the text changes on the fly.
|
||||
*
|
||||
* @param text the text to display, rich text is allowed
|
||||
* @see text()
|
||||
*/
|
||||
void setText(const QString &text);
|
||||
|
||||
/**
|
||||
* Set word wrap to @p wordWrap. If word wrap is enabled, the text()
|
||||
* of the message widget is wrapped to fit the available width.
|
||||
* If word wrap is disabled, the message widget's minimum size is
|
||||
* such that the entire text fits.
|
||||
*
|
||||
* @param wordWrap disable/enable word wrap
|
||||
* @see wordWrap()
|
||||
*/
|
||||
void setWordWrap(bool wordWrap);
|
||||
|
||||
/**
|
||||
* Set the visibility of the close button. If @p visible is @e true,
|
||||
* a close button is shown that calls animatedHide() if clicked.
|
||||
*
|
||||
* @see closeButtonVisible(), animatedHide()
|
||||
*/
|
||||
void setCloseButtonVisible(bool visible);
|
||||
|
||||
/**
|
||||
* Set the message type to @p type.
|
||||
* By default, the message type is set to KMessageWidget::Information.
|
||||
*
|
||||
* @see messageType(), KMessageWidget::MessageType
|
||||
*/
|
||||
void setMessageType(KMessageWidget::MessageType type);
|
||||
|
||||
/**
|
||||
* Show the widget using an animation.
|
||||
*/
|
||||
void animatedShow();
|
||||
|
||||
/**
|
||||
* Hide the widget using an animation.
|
||||
*/
|
||||
void animatedHide();
|
||||
|
||||
/**
|
||||
* Define an icon to be shown on the left of the text
|
||||
* @since 4.11
|
||||
*/
|
||||
void setIcon(const QIcon &icon);
|
||||
|
||||
Q_SIGNALS:
|
||||
/**
|
||||
* This signal is emitted when the user clicks a link in the text label.
|
||||
* The URL referred to by the href anchor is passed in contents.
|
||||
* @param contents text of the href anchor
|
||||
* @see QLabel::linkActivated()
|
||||
* @since 4.10
|
||||
*/
|
||||
void linkActivated(const QString &contents);
|
||||
|
||||
/**
|
||||
* This signal is emitted when the user hovers over a link in the text label.
|
||||
* The URL referred to by the href anchor is passed in contents.
|
||||
* @param contents text of the href anchor
|
||||
* @see QLabel::linkHovered()
|
||||
* @since 4.11
|
||||
*/
|
||||
void linkHovered(const QString &contents);
|
||||
|
||||
/**
|
||||
* This signal is emitted when the hide animation is finished, started by
|
||||
* calling animatedHide(). If animations are disabled, this signal is
|
||||
* emitted immediately after the message widget got hidden.
|
||||
*
|
||||
* @note This signal is @e not emitted if the widget was hidden by
|
||||
* calling hide(), so this signal is only useful in conjunction
|
||||
* with animatedHide().
|
||||
*
|
||||
* @see animatedHide()
|
||||
* @since 5.0
|
||||
*/
|
||||
void hideAnimationFinished();
|
||||
|
||||
/**
|
||||
* This signal is emitted when the show animation is finished, started by
|
||||
* calling animatedShow(). If animations are disabled, this signal is
|
||||
* emitted immediately after the message widget got shown.
|
||||
*
|
||||
* @note This signal is @e not emitted if the widget was shown by
|
||||
* calling show(), so this signal is only useful in conjunction
|
||||
* with animatedShow().
|
||||
*
|
||||
* @see animatedShow()
|
||||
* @since 5.0
|
||||
*/
|
||||
void showAnimationFinished();
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) Q_DECL_OVERRIDE;
|
||||
|
||||
bool event(QEvent *event) Q_DECL_OVERRIDE;
|
||||
|
||||
void resizeEvent(QResizeEvent *event) Q_DECL_OVERRIDE;
|
||||
|
||||
private:
|
||||
KMessageWidgetPrivate *const d;
|
||||
friend class KMessageWidgetPrivate;
|
||||
|
||||
Q_PRIVATE_SLOT(d, void slotTimeLineChanged(qreal))
|
||||
Q_PRIVATE_SLOT(d, void slotTimeLineFinished())
|
||||
};
|
||||
|
||||
#endif /* KMESSAGEWIDGET_H */
|
@ -49,8 +49,8 @@ void KeePass1OpenWidget::openDatabase()
|
||||
|
||||
QFile file(m_filename);
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
MessageBox::warning(this, tr("Error"), tr("Unable to open the database.").append("\n")
|
||||
.append(file.errorString()));
|
||||
m_ui->messageWidget->showMessage( tr("Unable to open the database.").append("\n")
|
||||
.append(file.errorString()), MessageWidget::Error);
|
||||
return;
|
||||
}
|
||||
if (m_db) {
|
||||
@ -65,8 +65,9 @@ void KeePass1OpenWidget::openDatabase()
|
||||
Q_EMIT editFinished(true);
|
||||
}
|
||||
else {
|
||||
MessageBox::warning(this, tr("Error"), tr("Unable to open the database.").append("\n")
|
||||
.append(reader.errorString()));
|
||||
m_ui->messageWidget->showMessage(tr("Unable to open the database.").append("\n")
|
||||
.append(reader.errorString()), MessageWidget::Error);
|
||||
|
||||
m_ui->editPassword->clear();
|
||||
}
|
||||
}
|
||||
|
@ -104,6 +104,7 @@ MainWindow::MainWindow()
|
||||
#endif
|
||||
|
||||
setWindowIcon(filePath()->applicationIcon());
|
||||
m_ui->globalMessageWidget->setHidden(true);
|
||||
QAction* toggleViewAction = m_ui->toolBar->toggleViewAction();
|
||||
toggleViewAction->setText(tr("Show toolbar"));
|
||||
m_ui->menuView->addAction(toggleViewAction);
|
||||
@ -137,13 +138,14 @@ MainWindow::MainWindow()
|
||||
this, SLOT(lockDatabasesAfterInactivity()));
|
||||
applySettingsChanges();
|
||||
|
||||
setShortcut(m_ui->actionDatabaseNew, QKeySequence::New, Qt::CTRL + Qt::Key_N);
|
||||
setShortcut(m_ui->actionDatabaseOpen, QKeySequence::Open, Qt::CTRL + Qt::Key_O);
|
||||
setShortcut(m_ui->actionDatabaseSave, QKeySequence::Save, Qt::CTRL + Qt::Key_S);
|
||||
setShortcut(m_ui->actionDatabaseSaveAs, QKeySequence::SaveAs);
|
||||
setShortcut(m_ui->actionDatabaseClose, QKeySequence::Close, Qt::CTRL + Qt::Key_W);
|
||||
m_ui->actionLockDatabases->setShortcut(Qt::CTRL + Qt::Key_L);
|
||||
setShortcut(m_ui->actionQuit, QKeySequence::Quit, Qt::CTRL + Qt::Key_Q);
|
||||
m_ui->actionEntryNew->setShortcut(Qt::CTRL + Qt::Key_N);
|
||||
m_ui->actionEntryNew->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_N);
|
||||
m_ui->actionEntryEdit->setShortcut(Qt::CTRL + Qt::Key_E);
|
||||
m_ui->actionEntryDelete->setShortcut(Qt::CTRL + Qt::Key_D);
|
||||
m_ui->actionEntryClone->setShortcut(Qt::CTRL + Qt::Key_K);
|
||||
@ -275,8 +277,18 @@ MainWindow::MainWindow()
|
||||
connect(m_ui->actionPasswordGenerator, SIGNAL(toggled(bool)), SLOT(switchToPasswordGen(bool)));
|
||||
connect(m_ui->passwordGeneratorWidget, SIGNAL(dialogTerminated()), SLOT(closePasswordGen()));
|
||||
|
||||
connect(m_ui->welcomeWidget, SIGNAL(newDatabase()), SLOT(switchToNewDatabase()));
|
||||
connect(m_ui->welcomeWidget, SIGNAL(openDatabase()), SLOT(switchToOpenDatabase()));
|
||||
connect(m_ui->welcomeWidget, SIGNAL(openDatabaseFile(QString)), SLOT(switchToDatabaseFile(QString)));
|
||||
connect(m_ui->welcomeWidget, SIGNAL(importKeePass1Database()), SLOT(switchToKeePass1Database()));
|
||||
|
||||
connect(m_ui->actionAbout, SIGNAL(triggered()), SLOT(showAboutDialog()));
|
||||
|
||||
connect(m_ui->tabWidget, SIGNAL(messageGlobal(QString,MessageWidget::MessageType)), this, SLOT(displayGlobalMessage(QString, MessageWidget::MessageType)));
|
||||
connect(m_ui->tabWidget, SIGNAL(messageDismissGlobal()), this, SLOT(hideGlobalMessage()));
|
||||
connect(m_ui->tabWidget, SIGNAL(messageTab(QString,MessageWidget::MessageType)), this, SLOT(displayTabMessage(QString, MessageWidget::MessageType)));
|
||||
connect(m_ui->tabWidget, SIGNAL(messageDismissTab()), this, SLOT(hideTabMessage()));
|
||||
|
||||
updateTrayIcon();
|
||||
}
|
||||
|
||||
@ -364,7 +376,7 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode)
|
||||
bool groupSelected = dbWidget->isGroupSelected();
|
||||
|
||||
m_ui->actionEntryNew->setEnabled(!inSearch);
|
||||
m_ui->actionEntryClone->setEnabled(singleEntrySelected && !inSearch);
|
||||
m_ui->actionEntryClone->setEnabled(singleEntrySelected);
|
||||
m_ui->actionEntryEdit->setEnabled(singleEntrySelected);
|
||||
m_ui->actionEntryDelete->setEnabled(entriesSelected);
|
||||
m_ui->actionEntryCopyTitle->setEnabled(singleEntrySelected && dbWidget->currentEntryHasTitle());
|
||||
@ -530,6 +542,30 @@ void MainWindow::closePasswordGen()
|
||||
switchToPasswordGen(false);
|
||||
}
|
||||
|
||||
void MainWindow::switchToNewDatabase()
|
||||
{
|
||||
m_ui->tabWidget->newDatabase();
|
||||
switchToDatabases();
|
||||
}
|
||||
|
||||
void MainWindow::switchToOpenDatabase()
|
||||
{
|
||||
m_ui->tabWidget->openDatabase();
|
||||
switchToDatabases();
|
||||
}
|
||||
|
||||
void MainWindow::switchToDatabaseFile(QString file)
|
||||
{
|
||||
m_ui->tabWidget->openDatabase(file);
|
||||
switchToDatabases();
|
||||
}
|
||||
|
||||
void MainWindow::switchToKeePass1Database()
|
||||
{
|
||||
m_ui->tabWidget->importKeePass1Database();
|
||||
switchToDatabases();
|
||||
}
|
||||
|
||||
void MainWindow::databaseStatusChanged(DatabaseWidget *)
|
||||
{
|
||||
updateTrayIcon();
|
||||
@ -755,7 +791,7 @@ void MainWindow::repairDatabase()
|
||||
if (fileName.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
QScopedPointer<QDialog> dialog(new QDialog(this));
|
||||
DatabaseRepairWidget* dbRepairWidget = new DatabaseRepairWidget(dialog.data());
|
||||
connect(dbRepairWidget, SIGNAL(success()), dialog.data(), SLOT(accept()));
|
||||
@ -770,8 +806,9 @@ void MainWindow::repairDatabase()
|
||||
KeePass2Writer writer;
|
||||
writer.writeDatabase(saveFileName, dbRepairWidget->database());
|
||||
if (writer.hasError()) {
|
||||
QMessageBox::critical(this, tr("Error"),
|
||||
tr("Writing the database failed.").append("\n\n").append(writer.errorString()));
|
||||
displayGlobalMessage(
|
||||
tr("Writing the database failed.").append("\n").append(writer.errorString()),
|
||||
MessageWidget::Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -787,3 +824,26 @@ bool MainWindow::isTrayIconEnabled() const
|
||||
&& QSystemTrayIcon::isSystemTrayAvailable();
|
||||
#endif
|
||||
}
|
||||
|
||||
void MainWindow::displayGlobalMessage(const QString& text, MessageWidget::MessageType type)
|
||||
{
|
||||
m_ui->globalMessageWidget->showMessage(text, type);
|
||||
}
|
||||
|
||||
void MainWindow::displayTabMessage(const QString& text, MessageWidget::MessageType type)
|
||||
{
|
||||
m_ui->tabWidget->currentDatabaseWidget()->showMessage(text, type);
|
||||
}
|
||||
|
||||
void MainWindow::hideGlobalMessage()
|
||||
{
|
||||
m_ui->globalMessageWidget->hideMessage();
|
||||
}
|
||||
|
||||
void MainWindow::hideTabMessage()
|
||||
{
|
||||
if (m_ui->stackedWidget->currentIndex() == 0) {
|
||||
m_ui->tabWidget->currentDatabaseWidget()->hideMessage();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -42,6 +42,7 @@ public:
|
||||
public Q_SLOTS:
|
||||
void openDatabase(const QString& fileName, const QString& pw = QString(),
|
||||
const QString& keyFile = QString());
|
||||
void appExit();
|
||||
|
||||
protected:
|
||||
void closeEvent(QCloseEvent* event) override;
|
||||
@ -54,6 +55,10 @@ private Q_SLOTS:
|
||||
void switchToDatabases();
|
||||
void switchToSettings();
|
||||
void switchToPasswordGen(bool enabled);
|
||||
void switchToNewDatabase();
|
||||
void switchToOpenDatabase();
|
||||
void switchToDatabaseFile(QString file);
|
||||
void switchToKeePass1Database();
|
||||
void closePasswordGen();
|
||||
void databaseStatusChanged(DatabaseWidget *dbWidget);
|
||||
void databaseTabChanged(int tabIndex);
|
||||
@ -68,9 +73,12 @@ private Q_SLOTS:
|
||||
void applySettingsChanges();
|
||||
void trayIconTriggered(QSystemTrayIcon::ActivationReason reason);
|
||||
void toggleWindow();
|
||||
void appExit();
|
||||
void lockDatabasesAfterInactivity();
|
||||
void repairDatabase();
|
||||
void displayGlobalMessage(const QString& text, MessageWidget::MessageType type);
|
||||
void displayTabMessage(const QString& text, MessageWidget::MessageType type);
|
||||
void hideGlobalMessage();
|
||||
void hideTabMessage();
|
||||
|
||||
private:
|
||||
static void setShortcut(QAction* action, QKeySequence::StandardKey standard, int fallback = 0);
|
||||
|
@ -2,6 +2,9 @@
|
||||
<ui version="4.0">
|
||||
<class>MainWindow</class>
|
||||
<widget class="QMainWindow" name="MainWindow">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
@ -14,6 +17,9 @@
|
||||
<string notr="true">KeePassXC</string>
|
||||
</property>
|
||||
<widget class="QWidget" name="centralwidget">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
@ -27,8 +33,24 @@
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="MessageWidget" name="globalMessageWidget" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QStackedWidget" name="stackedWidget">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="currentIndex">
|
||||
<number>2</number>
|
||||
</property>
|
||||
@ -83,7 +105,45 @@
|
||||
<widget class="QWidget" name="pageWelcome">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_5">
|
||||
<item>
|
||||
<widget class="WelcomeWidget" name="welcomeWidget" native="true"/>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::MinimumExpanding</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>50</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="WelcomeWidget" name="welcomeWidget" native="true">
|
||||
<zorder>horizontalSpacer_2</zorder>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::MinimumExpanding</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>50</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
@ -104,7 +164,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>800</width>
|
||||
<height>26</height>
|
||||
<height>29</height>
|
||||
</rect>
|
||||
</property>
|
||||
<widget class="QMenu" name="menuFile">
|
||||
@ -155,15 +215,15 @@
|
||||
<addaction name="actionEntryCopyNotes"/>
|
||||
<addaction name="separator"/>
|
||||
</widget>
|
||||
<addaction name="actionEntryNew"/>
|
||||
<addaction name="actionEntryClone"/>
|
||||
<addaction name="actionEntryEdit"/>
|
||||
<addaction name="actionEntryDelete"/>
|
||||
<addaction name="actionEntryCopyUsername"/>
|
||||
<addaction name="actionEntryCopyPassword"/>
|
||||
<addaction name="menuEntryCopyAttribute"/>
|
||||
<addaction name="actionEntryAutoType"/>
|
||||
<addaction name="actionEntryOpenUrl"/>
|
||||
<addaction name="actionEntryEdit"/>
|
||||
<addaction name="actionEntryClone"/>
|
||||
<addaction name="actionEntryDelete"/>
|
||||
<addaction name="actionEntryNew"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuGroups">
|
||||
<property name="title">
|
||||
@ -203,6 +263,7 @@
|
||||
<attribute name="toolBarBreak">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<addaction name="actionDatabaseNew"/>
|
||||
<addaction name="actionDatabaseOpen"/>
|
||||
<addaction name="actionDatabaseSave"/>
|
||||
<addaction name="separator"/>
|
||||
@ -254,9 +315,9 @@
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionDatabaseMerge">
|
||||
<property name="text">
|
||||
<string>Merge from KeePassX database</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Merge from KeePassX database</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionEntryNew">
|
||||
<property name="enabled">
|
||||
@ -452,6 +513,12 @@
|
||||
</action>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>MessageWidget</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>gui/MessageWidget.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>DatabaseTabWidget</class>
|
||||
<extends>QTabWidget</extends>
|
||||
|
36
src/gui/MessageWidget.cpp
Normal file
36
src/gui/MessageWidget.cpp
Normal file
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Pedro Alves <devel@pgalves.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "MessageWidget.h"
|
||||
|
||||
MessageWidget::MessageWidget(QWidget* parent)
|
||||
:KMessageWidget(parent)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void MessageWidget::showMessage(const QString& text, MessageWidget::MessageType type)
|
||||
{
|
||||
setMessageType(type);
|
||||
setText(text);
|
||||
animatedShow();
|
||||
}
|
||||
|
||||
void MessageWidget::hideMessage()
|
||||
{
|
||||
animatedHide();
|
||||
}
|
36
src/gui/MessageWidget.h
Normal file
36
src/gui/MessageWidget.h
Normal file
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Pedro Alves <devel@pgalves.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MESSAGEWIDGET_H
|
||||
#define MESSAGEWIDGET_H
|
||||
|
||||
#include "gui/KMessageWidget.h"
|
||||
|
||||
class MessageWidget : public KMessageWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit MessageWidget(QWidget* parent = 0);
|
||||
|
||||
public Q_SLOTS:
|
||||
void showMessage(const QString& text, MessageWidget::MessageType type);
|
||||
void hideMessage();
|
||||
|
||||
};
|
||||
|
||||
#endif // MESSAGEWIDGET_H
|
@ -45,11 +45,15 @@ PasswordGeneratorWidget::PasswordGeneratorWidget(QWidget* parent)
|
||||
|
||||
connect(m_ui->optionButtons, SIGNAL(buttonClicked(int)), SLOT(updateGenerator()));
|
||||
|
||||
// set font size of password quality and entropy labels dynamically to 80% of the default font size
|
||||
// set font size of password quality and entropy labels dynamically to 80% of
|
||||
// the default font size, but make it no smaller than 8pt
|
||||
QFont defaultFont;
|
||||
defaultFont.setPointSize(static_cast<int>(defaultFont.pointSize() * 0.8f));
|
||||
m_ui->entropyLabel->setFont(defaultFont);
|
||||
m_ui->strengthLabel->setFont(defaultFont);
|
||||
int smallerSize = static_cast<int>(defaultFont.pointSize() * 0.8f);
|
||||
if (smallerSize >= 8) {
|
||||
defaultFont.setPointSize(smallerSize);
|
||||
m_ui->entropyLabel->setFont(defaultFont);
|
||||
m_ui->strengthLabel->setFont(defaultFont);
|
||||
}
|
||||
|
||||
loadSettings();
|
||||
reset();
|
||||
@ -132,8 +136,10 @@ void PasswordGeneratorWidget::updatePasswordStrength(const QString& password)
|
||||
|
||||
void PasswordGeneratorWidget::generatePassword()
|
||||
{
|
||||
QString password = m_generator->generatePassword();
|
||||
m_ui->editNewPassword->setText(password);
|
||||
if (m_generator->isValid()) {
|
||||
QString password = m_generator->generatePassword();
|
||||
m_ui->editNewPassword->setText(password);
|
||||
}
|
||||
}
|
||||
|
||||
void PasswordGeneratorWidget::applyPassword()
|
||||
@ -279,5 +285,11 @@ void PasswordGeneratorWidget::updateGenerator()
|
||||
m_generator->setCharClasses(classes);
|
||||
m_generator->setFlags(flags);
|
||||
|
||||
if (m_generator->isValid()) {
|
||||
m_ui->buttonGenerate->setEnabled(true);
|
||||
} else {
|
||||
m_ui->buttonGenerate->setEnabled(false);
|
||||
}
|
||||
|
||||
regeneratePassword();
|
||||
}
|
||||
|
@ -104,11 +104,6 @@ QProgressBar::chunk {
|
||||
<height>30</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>8</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>strength</string>
|
||||
</property>
|
||||
@ -144,11 +139,6 @@ QProgressBar::chunk {
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>8</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>entropy</string>
|
||||
</property>
|
||||
@ -295,6 +285,12 @@ QProgressBar::chunk {
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="checkBoxNumbers">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>26</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::StrongFocus</enum>
|
||||
</property>
|
||||
@ -314,6 +310,12 @@ QProgressBar::chunk {
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="checkBoxSpecialChars">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>26</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::StrongFocus</enum>
|
||||
</property>
|
||||
|
@ -34,12 +34,14 @@ SearchWidget::SearchWidget(QWidget *parent)
|
||||
m_searchTimer->setSingleShot(true);
|
||||
|
||||
connect(m_ui->searchEdit, SIGNAL(textChanged(QString)), SLOT(startSearchTimer()));
|
||||
connect(m_ui->searchEdit, SIGNAL(returnPressed()), SLOT(startSearch()));
|
||||
connect(m_ui->searchIcon, SIGNAL(triggered(QAction*)), m_ui->searchEdit, SLOT(setFocus()));
|
||||
connect(m_ui->searchIcon, SIGNAL(pressed()), m_ui->searchEdit, SLOT(setFocus()));
|
||||
connect(m_ui->clearIcon, SIGNAL(pressed()), m_ui->searchEdit, SLOT(clear()));
|
||||
connect(m_ui->clearIcon, SIGNAL(pressed()), m_ui->searchEdit, SLOT(setFocus()));
|
||||
connect(m_searchTimer, SIGNAL(timeout()), this, SLOT(startSearch()));
|
||||
connect(this, SIGNAL(escapePressed()), m_ui->searchEdit, SLOT(clear()));
|
||||
|
||||
new QShortcut(Qt::CTRL + Qt::Key_F, m_ui->searchEdit, SLOT(setFocus()), nullptr, Qt::ApplicationShortcut);
|
||||
new QShortcut(Qt::CTRL + Qt::Key_F, this, SLOT(searchFocus()), nullptr, Qt::ApplicationShortcut);
|
||||
new QShortcut(Qt::Key_Escape, m_ui->searchEdit, SLOT(clear()), nullptr, Qt::ApplicationShortcut);
|
||||
|
||||
m_ui->searchEdit->installEventFilter(this);
|
||||
|
||||
@ -51,6 +53,9 @@ SearchWidget::SearchWidget(QWidget *parent)
|
||||
m_ui->searchIcon->setIcon(filePath()->icon("actions", "system-search"));
|
||||
m_ui->searchIcon->setMenu(searchMenu);
|
||||
m_ui->searchIcon->setPopupMode(QToolButton::MenuButtonPopup);
|
||||
|
||||
m_ui->clearIcon->setIcon(filePath()->icon("actions", "edit-clear-locationbar-rtl"));
|
||||
m_ui->clearIcon->setEnabled(false);
|
||||
}
|
||||
|
||||
SearchWidget::~SearchWidget()
|
||||
@ -67,23 +72,24 @@ bool SearchWidget::eventFilter(QObject *obj, QEvent *event)
|
||||
return true;
|
||||
}
|
||||
else if (keyEvent->matches(QKeySequence::Copy)) {
|
||||
// If Control+C is pressed in the search edit when no
|
||||
// text is selected, copy the password of the current
|
||||
// entry.
|
||||
// If Control+C is pressed in the search edit when no text
|
||||
// is selected, copy the password of the current entry
|
||||
if (!m_ui->searchEdit->hasSelectedText()) {
|
||||
emit copyPressed();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (keyEvent->matches(QKeySequence::MoveToNextLine)) {
|
||||
// If Down is pressed at EOL in the search edit, move
|
||||
// the focus to the entry view.
|
||||
QLineEdit* searchEdit = m_ui->searchEdit;
|
||||
if (!searchEdit->hasSelectedText() &&
|
||||
searchEdit->cursorPosition() == searchEdit->text().length()) {
|
||||
if (m_ui->searchEdit->cursorPosition() == m_ui->searchEdit->text().length()) {
|
||||
// If down is pressed at EOL, move the focus to the entry view
|
||||
emit downPressed();
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
// Otherwise move the cursor to EOL
|
||||
m_ui->searchEdit->setCursorPosition(m_ui->searchEdit->text().length());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -96,6 +102,7 @@ void SearchWidget::connectSignals(SignalMultiplexer& mx)
|
||||
mx.connect(this, SIGNAL(caseSensitiveChanged(bool)), SLOT(setSearchCaseSensitive(bool)));
|
||||
mx.connect(this, SIGNAL(copyPressed()), SLOT(copyPassword()));
|
||||
mx.connect(this, SIGNAL(downPressed()), SLOT(setFocus()));
|
||||
mx.connect(m_ui->searchEdit, SIGNAL(returnPressed()), SLOT(switchToEntryEdit()));
|
||||
}
|
||||
|
||||
void SearchWidget::databaseChanged(DatabaseWidget *dbWidget)
|
||||
@ -125,6 +132,9 @@ void SearchWidget::startSearch()
|
||||
m_searchTimer->stop();
|
||||
}
|
||||
|
||||
bool hasText = m_ui->searchEdit->text().length() > 0;
|
||||
m_ui->clearIcon->setEnabled(hasText);
|
||||
|
||||
search(m_ui->searchEdit->text());
|
||||
}
|
||||
|
||||
@ -138,3 +148,9 @@ void SearchWidget::setCaseSensitive(bool state)
|
||||
m_actionCaseSensitive->setChecked(state);
|
||||
updateCaseSensitive();
|
||||
}
|
||||
|
||||
void SearchWidget::searchFocus()
|
||||
{
|
||||
m_ui->searchEdit->setFocus();
|
||||
m_ui->searchEdit->selectAll();
|
||||
}
|
||||
|
@ -48,6 +48,7 @@ signals:
|
||||
void escapePressed();
|
||||
void copyPressed();
|
||||
void downPressed();
|
||||
void enterPressed();
|
||||
|
||||
public slots:
|
||||
void databaseChanged(DatabaseWidget* dbWidget);
|
||||
@ -56,6 +57,7 @@ private slots:
|
||||
void startSearchTimer();
|
||||
void startSearch();
|
||||
void updateCaseSensitive();
|
||||
void searchFocus();
|
||||
|
||||
private:
|
||||
const QScopedPointer<Ui::SearchWidget> m_ui;
|
||||
|
@ -10,45 +10,69 @@
|
||||
<height>34</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
<number>7</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QToolButton" name="searchIcon">
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::ClickFocus</enum>
|
||||
</property>
|
||||
<property name="toolButtonStyle">
|
||||
<enum>Qt::ToolButtonIconOnly</enum>
|
||||
</property>
|
||||
<property name="autoRaise">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Find:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
<item>
|
||||
<widget class="QToolButton" name="searchIcon">
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::ClickFocus</enum>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Search</string>
|
||||
</property>
|
||||
<property name="toolButtonStyle">
|
||||
<enum>Qt::ToolButtonIconOnly</enum>
|
||||
</property>
|
||||
<property name="autoRaise">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="searchEdit"/>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="searchEdit">
|
||||
<property name="styleSheet">
|
||||
<string notr="true">padding:3px</string>
|
||||
</property>
|
||||
<property name="placeholderText">
|
||||
<string>Find</string>
|
||||
</property>
|
||||
<property name="clearButtonEnabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="clearIcon">
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::ClickFocus</enum>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Clear</string>
|
||||
</property>
|
||||
<property name="toolButtonStyle">
|
||||
<enum>Qt::ToolButtonIconOnly</enum>
|
||||
</property>
|
||||
<property name="autoRaise">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
|
@ -18,13 +18,51 @@
|
||||
#include "WelcomeWidget.h"
|
||||
#include "ui_WelcomeWidget.h"
|
||||
|
||||
#include "config-keepassx.h"
|
||||
#include "core/FilePath.h"
|
||||
#include "core/Config.h"
|
||||
|
||||
WelcomeWidget::WelcomeWidget(QWidget* parent)
|
||||
: QWidget(parent)
|
||||
, m_ui(new Ui::WelcomeWidget())
|
||||
{
|
||||
m_ui->setupUi(this);
|
||||
|
||||
m_ui->welcomeLabel->setText(m_ui->welcomeLabel->text() + " " + KEEPASSX_VERSION);
|
||||
QFont welcomeLabelFont = m_ui->welcomeLabel->font();
|
||||
welcomeLabelFont.setBold(true);
|
||||
welcomeLabelFont.setPointSize(welcomeLabelFont.pointSize() + 4);
|
||||
m_ui->welcomeLabel->setFont(welcomeLabelFont);
|
||||
|
||||
m_ui->iconLabel->setPixmap(filePath()->applicationIcon().pixmap(64));
|
||||
|
||||
m_ui->recentListWidget->clear();
|
||||
const QStringList lastDatabases = config()->get("LastDatabases", QVariant()).toStringList();
|
||||
for (const QString& database : lastDatabases) {
|
||||
QListWidgetItem *itm = new QListWidgetItem;
|
||||
itm->setText(database);
|
||||
m_ui->recentListWidget->addItem(itm);
|
||||
}
|
||||
bool recent_visibility = (m_ui->recentListWidget->count() > 0);
|
||||
m_ui->startLabel->setVisible(!recent_visibility);
|
||||
m_ui->recentListWidget->setVisible(recent_visibility);
|
||||
m_ui->recentLabel->setVisible(recent_visibility);
|
||||
|
||||
connect(m_ui->buttonNewDatabase, SIGNAL(clicked()), SIGNAL(newDatabase()));
|
||||
connect(m_ui->buttonOpenDatabase, SIGNAL(clicked()), SIGNAL(openDatabase()));
|
||||
connect(m_ui->buttonImportKeePass1, SIGNAL(clicked()), SIGNAL(importKeePass1Database()));
|
||||
connect(m_ui->recentListWidget, SIGNAL(itemDoubleClicked(QListWidgetItem*)), this,
|
||||
SLOT(openDatabaseFromFile(QListWidgetItem*)));
|
||||
}
|
||||
|
||||
WelcomeWidget::~WelcomeWidget()
|
||||
{
|
||||
}
|
||||
|
||||
void WelcomeWidget::openDatabaseFromFile(QListWidgetItem* item)
|
||||
{
|
||||
if (item->text().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Q_EMIT openDatabaseFile(item->text());
|
||||
}
|
@ -19,6 +19,7 @@
|
||||
#define KEEPASSX_WELCOMEWIDGET_H
|
||||
|
||||
#include <QWidget>
|
||||
#include <QListWidgetItem>
|
||||
|
||||
namespace Ui {
|
||||
class WelcomeWidget;
|
||||
@ -32,6 +33,15 @@ public:
|
||||
explicit WelcomeWidget(QWidget* parent = nullptr);
|
||||
~WelcomeWidget();
|
||||
|
||||
Q_SIGNALS:
|
||||
void newDatabase();
|
||||
void openDatabase();
|
||||
void openDatabaseFile(QString);
|
||||
void importKeePass1Database();
|
||||
|
||||
private Q_SLOTS:
|
||||
void openDatabaseFromFile(QListWidgetItem* item);
|
||||
|
||||
private:
|
||||
const QScopedPointer<Ui::WelcomeWidget> m_ui;
|
||||
};
|
||||
|
@ -2,17 +2,176 @@
|
||||
<ui version="4.0">
|
||||
<class>WelcomeWidget</class>
|
||||
<widget class="QWidget" name="WelcomeWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>450</width>
|
||||
<height>419</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>450</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="labelWelcome">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QLabel" name="iconLabel">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="welcomeLabel">
|
||||
<property name="text">
|
||||
<string>Welcome!</string>
|
||||
<string>Welcome to KeePassXC</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="startLabel">
|
||||
<property name="text">
|
||||
<string>Start storing your passwords securely in a KeePassXC database</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="buttonNewDatabase">
|
||||
<property name="text">
|
||||
<string>Create new database</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="buttonOpenDatabase">
|
||||
<property name="text">
|
||||
<string>Open existing database</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="buttonImportKeePass1">
|
||||
<property name="text">
|
||||
<string>Import from KeePass 1</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Minimum</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>5</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="recentLabel">
|
||||
<property name="text">
|
||||
<string>Recent databases</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QListWidget" name="recentListWidget">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>110</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
|
@ -77,6 +77,9 @@ EditEntryWidget::EditEntryWidget(QWidget* parent)
|
||||
|
||||
connect(this, SIGNAL(accepted()), SLOT(saveEntry()));
|
||||
connect(this, SIGNAL(rejected()), SLOT(cancel()));
|
||||
|
||||
connect(m_iconsWidget, SIGNAL(messageEditEntry(QString, MessageWidget::MessageType)), SLOT(showMessage(QString, MessageWidget::MessageType)));
|
||||
connect(m_iconsWidget, SIGNAL(messageEditEntryDismiss()), SLOT(hideMessage()));
|
||||
}
|
||||
|
||||
EditEntryWidget::~EditEntryWidget()
|
||||
@ -89,6 +92,7 @@ void EditEntryWidget::setupMain()
|
||||
add(tr("Entry"), m_mainWidget);
|
||||
|
||||
m_mainUi->togglePasswordButton->setIcon(filePath()->onOffIcon("actions", "password-show"));
|
||||
m_mainUi->togglePasswordGeneratorButton->setIcon(filePath()->icon("actions", "password-generator", false));
|
||||
connect(m_mainUi->togglePasswordButton, SIGNAL(toggled(bool)), m_mainUi->passwordEdit, SLOT(setShowPassword(bool)));
|
||||
connect(m_mainUi->togglePasswordGeneratorButton, SIGNAL(toggled(bool)), SLOT(togglePasswordGeneratorButton(bool)));
|
||||
connect(m_mainUi->expireCheck, SIGNAL(toggled(bool)), m_mainUi->expireDatePicker, SLOT(setEnabled(bool)));
|
||||
@ -271,14 +275,15 @@ void EditEntryWidget::loadEntry(Entry* entry, bool create, bool history, const Q
|
||||
m_history = history;
|
||||
|
||||
if (history) {
|
||||
setHeadline(QString("%1 > %2").arg(parentName, tr("Entry history")));
|
||||
setHeadline(QString("%1 > %2").arg(parentName.toHtmlEscaped(), tr("Entry history")));
|
||||
}
|
||||
else {
|
||||
if (create) {
|
||||
setHeadline(QString("%1 > %2").arg(parentName, tr("Add entry")));
|
||||
setHeadline(QString("%1 > %2").arg(parentName.toHtmlEscaped(), tr("Add entry")));
|
||||
}
|
||||
else {
|
||||
setHeadline(QString("%1 > %2 > %3").arg(parentName, entry->title(), tr("Edit entry")));
|
||||
setHeadline(QString("%1 > %2 > %3").arg(parentName.toHtmlEscaped(),
|
||||
entry->title().toHtmlEscaped(), tr("Edit entry")));
|
||||
}
|
||||
}
|
||||
|
||||
@ -393,12 +398,13 @@ void EditEntryWidget::saveEntry()
|
||||
{
|
||||
if (m_history) {
|
||||
clear();
|
||||
hideMessage();
|
||||
Q_EMIT editFinished(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!passwordsEqual()) {
|
||||
MessageBox::warning(this, tr("Error"), tr("Different passwords supplied."));
|
||||
showMessage(tr("Different passwords supplied."), MessageWidget::Error);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -433,6 +439,9 @@ void EditEntryWidget::saveEntry()
|
||||
|
||||
void EditEntryWidget::updateEntryData(Entry* entry) const
|
||||
{
|
||||
entry->attributes()->copyCustomKeysFrom(m_entryAttributes);
|
||||
entry->attachments()->copyDataFrom(m_entryAttachments);
|
||||
|
||||
entry->setTitle(m_mainUi->titleEdit->text());
|
||||
entry->setUsername(m_mainUi->usernameEdit->text());
|
||||
entry->setUrl(m_mainUi->urlEdit->text());
|
||||
@ -442,9 +451,6 @@ void EditEntryWidget::updateEntryData(Entry* entry) const
|
||||
|
||||
entry->setNotes(m_mainUi->notesEdit->toPlainText());
|
||||
|
||||
entry->attributes()->copyCustomKeysFrom(m_entryAttributes);
|
||||
entry->attachments()->copyDataFrom(m_entryAttachments);
|
||||
|
||||
IconStruct iconStruct = m_iconsWidget->state();
|
||||
|
||||
if (iconStruct.number < 0) {
|
||||
@ -472,6 +478,7 @@ void EditEntryWidget::cancel()
|
||||
{
|
||||
if (m_history) {
|
||||
clear();
|
||||
hideMessage();
|
||||
Q_EMIT editFinished(false);
|
||||
return;
|
||||
}
|
||||
@ -495,6 +502,7 @@ void EditEntryWidget::clear()
|
||||
m_autoTypeAssoc->clear();
|
||||
m_historyModel->clear();
|
||||
m_iconsWidget->reset();
|
||||
hideMessage();
|
||||
}
|
||||
|
||||
bool EditEntryWidget::hasBeenModified() const
|
||||
@ -628,15 +636,13 @@ void EditEntryWidget::insertAttachment()
|
||||
|
||||
QFile file(filename);
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
MessageBox::warning(this, tr("Error"),
|
||||
tr("Unable to open file").append(":\n").append(file.errorString()));
|
||||
showMessage(tr("Unable to open file").append(":\n").append(file.errorString()), MessageWidget::Error);
|
||||
return;
|
||||
}
|
||||
|
||||
QByteArray data;
|
||||
if (!Tools::readAllFromDevice(&file, data)) {
|
||||
MessageBox::warning(this, tr("Error"),
|
||||
tr("Unable to open file").append(":\n").append(file.errorString()));
|
||||
showMessage(tr("Unable to open file").append(":\n").append(file.errorString()), MessageWidget::Error);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -663,13 +669,11 @@ void EditEntryWidget::saveCurrentAttachment()
|
||||
|
||||
QFile file(savePath);
|
||||
if (!file.open(QIODevice::WriteOnly)) {
|
||||
MessageBox::warning(this, tr("Error"),
|
||||
tr("Unable to save the attachment:\n").append(file.errorString()));
|
||||
showMessage(tr("Unable to save the attachment:\n").append(file.errorString()), MessageWidget::Error);
|
||||
return;
|
||||
}
|
||||
if (file.write(attachmentData) != attachmentData.size()) {
|
||||
MessageBox::warning(this, tr("Error"),
|
||||
tr("Unable to save the attachment:\n").append(file.errorString()));
|
||||
showMessage(tr("Unable to save the attachment:\n").append(file.errorString()), MessageWidget::Error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -690,20 +694,17 @@ void EditEntryWidget::openAttachment(const QModelIndex& index)
|
||||
QTemporaryFile* file = new QTemporaryFile(tmpFileTemplate, this);
|
||||
|
||||
if (!file->open()) {
|
||||
MessageBox::warning(this, tr("Error"),
|
||||
tr("Unable to save the attachment:\n").append(file->errorString()));
|
||||
showMessage(tr("Unable to save the attachment:\n").append(file->errorString()), MessageWidget::Error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (file->write(attachmentData) != attachmentData.size()) {
|
||||
MessageBox::warning(this, tr("Error"),
|
||||
tr("Unable to save the attachment:\n").append(file->errorString()));
|
||||
showMessage(tr("Unable to save the attachment:\n").append(file->errorString()), MessageWidget::Error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!file->flush()) {
|
||||
MessageBox::warning(this, tr("Error"),
|
||||
tr("Unable to save the attachment:\n").append(file->errorString()));
|
||||
showMessage(tr("Unable to save the attachment:\n").append(file->errorString()), MessageWidget::Error);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -77,9 +77,6 @@
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="togglePasswordGeneratorButton">
|
||||
<property name="text">
|
||||
<string>Generate</string>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
|
@ -43,6 +43,9 @@ EditGroupWidget::EditGroupWidget(QWidget* parent)
|
||||
|
||||
connect(this, SIGNAL(accepted()), SLOT(save()));
|
||||
connect(this, SIGNAL(rejected()), SLOT(cancel()));
|
||||
|
||||
connect(m_editGroupWidgetIcons, SIGNAL(messageEditEntry(QString, MessageWidget::MessageType)), SLOT(showMessage(QString, MessageWidget::MessageType)));
|
||||
connect(m_editGroupWidgetIcons, SIGNAL(messageEditEntryDismiss()), SLOT(hideMessage()));
|
||||
}
|
||||
|
||||
EditGroupWidget::~EditGroupWidget()
|
||||
|
@ -45,6 +45,8 @@ public:
|
||||
|
||||
Q_SIGNALS:
|
||||
void editFinished(bool accepted);
|
||||
void messageEditEntry(QString, MessageWidget::MessageType);
|
||||
void messageEditEntryDismiss();
|
||||
|
||||
private Q_SLOTS:
|
||||
void save();
|
||||
|
@ -18,7 +18,7 @@ PasswordGenerator HttpSettings::m_generator;
|
||||
|
||||
bool HttpSettings::isEnabled()
|
||||
{
|
||||
return config()->get("Http/Enabled", true).toBool();
|
||||
return config()->get("Http/Enabled", false).toBool();
|
||||
}
|
||||
|
||||
void HttpSettings::setEnabled(bool enabled)
|
||||
@ -126,18 +126,6 @@ void HttpSettings::setSupportKphFields(bool supportKphFields)
|
||||
config()->set("Http/SupportKphFields", supportKphFields);
|
||||
}
|
||||
|
||||
QString HttpSettings::httpHost()
|
||||
{
|
||||
static const QString host = "localhost";
|
||||
|
||||
return config()->get("Http/Host", host).toString().toUtf8();
|
||||
}
|
||||
|
||||
void HttpSettings::setHttpHost(QString host)
|
||||
{
|
||||
config()->set("Http/Host", host);
|
||||
}
|
||||
|
||||
int HttpSettings::httpPort()
|
||||
{
|
||||
static const int PORT = 19455;
|
||||
|
@ -42,8 +42,6 @@ public:
|
||||
static void setSearchInAllDatabases(bool searchInAllDatabases);
|
||||
static bool supportKphFields();
|
||||
static void setSupportKphFields(bool supportKphFields);
|
||||
static QString httpHost();
|
||||
static void setHttpHost(QString host);
|
||||
static int httpPort();
|
||||
static void setHttpPort(int port);
|
||||
|
||||
|
@ -15,6 +15,8 @@
|
||||
#include "ui_OptionDialog.h"
|
||||
#include "HttpSettings.h"
|
||||
|
||||
#include <QMessageBox>
|
||||
|
||||
OptionDialog::OptionDialog(QWidget *parent) :
|
||||
QWidget(parent),
|
||||
ui(new Ui::OptionDialog())
|
||||
@ -41,7 +43,6 @@ void OptionDialog::loadSettings()
|
||||
ui->sortByUsername->setChecked(true);
|
||||
else
|
||||
ui->sortByTitle->setChecked(true);
|
||||
ui->httpHost->setText(settings.httpHost());
|
||||
ui->httpPort->setText(QString::number(settings.httpPort()));
|
||||
|
||||
/*
|
||||
@ -70,8 +71,14 @@ void OptionDialog::saveSettings()
|
||||
settings.setUnlockDatabase(ui->unlockDatabase->isChecked());
|
||||
settings.setMatchUrlScheme(ui->matchUrlScheme->isChecked());
|
||||
settings.setSortByUsername(ui->sortByUsername->isChecked());
|
||||
settings.setHttpHost(ui->httpHost->text());
|
||||
settings.setHttpPort(ui->httpPort->text().toInt());
|
||||
|
||||
int port = ui->httpPort->text().toInt();
|
||||
if (port < 1024) {
|
||||
QMessageBox::warning(this, tr("Cannot bind to privileged ports"),
|
||||
tr("Cannot bind to privileged ports below 1024!\nUsing default port 19455."));
|
||||
port = 19455;
|
||||
}
|
||||
settings.setHttpPort(port);
|
||||
|
||||
/*
|
||||
settings.setPasswordUseLowercase(ui->checkBoxLower->isChecked());
|
||||
|
@ -7,7 +7,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>605</width>
|
||||
<height>389</height>
|
||||
<height>429</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
@ -17,7 +17,7 @@
|
||||
<item>
|
||||
<widget class="QCheckBox" name="enableHttpServer">
|
||||
<property name="text">
|
||||
<string>Enable KeepassXC Http protocol
|
||||
<string>Enable KeepassXC HTTP protocol
|
||||
This is required for accessing your databases from ChromeIPass or PassIFox</string>
|
||||
</property>
|
||||
</widget>
|
||||
@ -28,7 +28,7 @@ This is required for accessing your databases from ChromeIPass or PassIFox</stri
|
||||
<enum>QTabWidget::Rounded</enum>
|
||||
</property>
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
<number>2</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="tab">
|
||||
<attribute name="title">
|
||||
@ -201,32 +201,41 @@ Only entries with the same scheme (http://, https://, ftp://, ...) are returned<
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_1">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>HTTP Host:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="httpHost">
|
||||
<property name="placeholderText">
|
||||
<string>Default host: localhost</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
<spacer name="verticalSpacer_4">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="httpPort">
|
||||
<property name="inputMask">
|
||||
<string notr="true">d0000</string>
|
||||
</property>
|
||||
<property name="placeholderText">
|
||||
<string>Default port: 19455</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>KeePassXC will listen to this port on 127.0.0.1</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
@ -237,15 +246,8 @@ Only entries with the same scheme (http://, https://, ftp://, ...) are returned<
|
||||
<property name="text">
|
||||
<string>HTTP Port:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="httpPort">
|
||||
<property name="inputMask">
|
||||
<string notr="true">d0000</string>
|
||||
</property>
|
||||
<property name="placeholderText">
|
||||
<string>Default port: 19455</string>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -331,68 +331,34 @@ void Server::start(void)
|
||||
if (m_started)
|
||||
return;
|
||||
|
||||
bool nohost = true;
|
||||
// local loopback hardcoded, since KeePassHTTP handshake
|
||||
// is not safe against interception
|
||||
QHostAddress address("127.0.0.1");
|
||||
int port = HttpSettings::httpPort();
|
||||
|
||||
void* addrx = NULL;
|
||||
unsigned int flags = MHD_USE_SELECT_INTERNALLY;
|
||||
|
||||
QHostInfo info = QHostInfo::fromName(HttpSettings::httpHost());
|
||||
if (!info.addresses().isEmpty()) {
|
||||
void* addrx = NULL;
|
||||
unsigned int flags = MHD_USE_SELECT_INTERNALLY;
|
||||
QHostAddress address = info.addresses().first();
|
||||
struct sockaddr_in *addr = static_cast<struct sockaddr_in*>(calloc(1, sizeof(struct sockaddr_in)));
|
||||
addrx = static_cast<void*>(addr);
|
||||
addr->sin_family = AF_INET;
|
||||
addr->sin_port = htons(port);
|
||||
addr->sin_addr.s_addr = htonl(address.toIPv4Address());
|
||||
|
||||
if (address.protocol() == QAbstractSocket::IPv4Protocol) {
|
||||
struct sockaddr_in *addr = static_cast<struct sockaddr_in*>(calloc(1, sizeof(struct sockaddr_in)));
|
||||
addrx = static_cast<void*>(addr);
|
||||
addr->sin_family = AF_INET;
|
||||
addr->sin_port = htons(HttpSettings::httpPort());
|
||||
addr->sin_addr.s_addr = htonl(address.toIPv4Address());
|
||||
nohost = false;
|
||||
} else {
|
||||
struct sockaddr_in6 *addr = static_cast<struct sockaddr_in6*>(calloc(1, sizeof(struct sockaddr_in6)));
|
||||
addrx = static_cast<void*>(addr);
|
||||
addr->sin6_family = AF_INET6;
|
||||
addr->sin6_port = htons(HttpSettings::httpPort());
|
||||
memcpy(&addr->sin6_addr, address.toIPv6Address().c, 16);
|
||||
nohost = false;
|
||||
flags |= MHD_USE_IPv6;
|
||||
}
|
||||
|
||||
if (nohost) {
|
||||
qWarning("HTTPPlugin: Faled to get configured host!");
|
||||
} else {
|
||||
if (NULL == (daemon = MHD_start_daemon(flags, port, NULL, NULL,
|
||||
&this->request_handler_wrapper, this,
|
||||
MHD_OPTION_NOTIFY_COMPLETED,
|
||||
this->request_completed, NULL,
|
||||
MHD_OPTION_SOCK_ADDR,
|
||||
addrx,
|
||||
MHD_OPTION_END))) {
|
||||
nohost = true;
|
||||
qWarning("HTTPPlugin: Failed to bind to configured host!");
|
||||
} else {
|
||||
nohost = false;
|
||||
//qWarning("HTTPPlugin: Binded to configured host.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (addrx != NULL)
|
||||
free(addrx);
|
||||
if (NULL == (daemon = MHD_start_daemon(flags, port, NULL, NULL,
|
||||
&this->request_handler_wrapper, this,
|
||||
MHD_OPTION_NOTIFY_COMPLETED,
|
||||
this->request_completed, NULL,
|
||||
MHD_OPTION_SOCK_ADDR,
|
||||
addrx,
|
||||
MHD_OPTION_END))) {
|
||||
qWarning("HTTPPlugin: Failed to bind to localhost!");
|
||||
} else {
|
||||
m_started = true;
|
||||
}
|
||||
|
||||
if (nohost) {
|
||||
if (NULL == (daemon = MHD_start_daemon(MHD_USE_SELECT_INTERNALLY, port, NULL, NULL,
|
||||
&this->request_handler_wrapper, this,
|
||||
MHD_OPTION_NOTIFY_COMPLETED,
|
||||
this->request_completed, NULL,
|
||||
MHD_OPTION_END))) {
|
||||
qWarning("HTTPPlugin: Fatal! Failed to bind to both configured and default hosts!");
|
||||
} else {
|
||||
qWarning("HTTPPlugin: Bound to fallback address 0.0.0.0/:::!");
|
||||
}
|
||||
}
|
||||
|
||||
m_started = true;
|
||||
if (addrx != NULL)
|
||||
free(addrx);
|
||||
}
|
||||
|
||||
|
||||
|
@ -480,7 +480,8 @@ void Service::updateEntry(const QString &, const QString &uuid, const QString &l
|
||||
//ShowNotification(QString("%0: You have an entry change prompt waiting, click to activate").arg(requestId));
|
||||
if ( HttpSettings::alwaysAllowUpdate()
|
||||
|| QMessageBox::warning(0, tr("KeePassXC: Update Entry"),
|
||||
tr("Do you want to update the information in %1 - %2?").arg(QUrl(url).host()).arg(u),
|
||||
tr("Do you want to update the information in %1 - %2?")
|
||||
.arg(QUrl(url).host().toHtmlEscaped()).arg(u.toHtmlEscaped()),
|
||||
QMessageBox::Yes|QMessageBox::No) == QMessageBox::Yes ) {
|
||||
entry->beginUpdate();
|
||||
entry->setUsername(login);
|
||||
|
10
src/main.cpp
10
src/main.cpp
@ -28,6 +28,16 @@
|
||||
#include "gui/MainWindow.h"
|
||||
#include "gui/MessageBox.h"
|
||||
|
||||
#ifdef QT_STATIC
|
||||
#include <QtPlugin>
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin)
|
||||
#elif Q_OS_LINUX
|
||||
Q_IMPORT_PLUGIN(QXcbIntegrationPlugin)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
#ifdef QT_NO_DEBUG
|
||||
|
@ -92,6 +92,7 @@ set(TEST_LIBRARIES
|
||||
Qt5::Widgets
|
||||
Qt5::Test
|
||||
${GCRYPT_LIBRARIES}
|
||||
${GPGERROR_LIBRARIES}
|
||||
${ZLIB_LIBRARIES}
|
||||
)
|
||||
|
||||
|
@ -96,10 +96,10 @@ void TestAutoType::init()
|
||||
m_entry4->setPassword("custom_attr");
|
||||
m_entry4->attributes()->set("CUSTOM","Attribute",false);
|
||||
association.window = "//^CustomAttr1$//";
|
||||
association.sequence = "{PASSWORD}:{CUSTOM}";
|
||||
association.sequence = "{PASSWORD}:{S:CUSTOM}";
|
||||
m_entry4->autoTypeAssociations()->add(association);
|
||||
association.window = "//^CustomAttr2$//";
|
||||
association.sequence = "{CuStOm}";
|
||||
association.sequence = "{S:CuStOm}";
|
||||
m_entry4->autoTypeAssociations()->add(association);
|
||||
association.window = "//^CustomAttr3$//";
|
||||
association.sequence = "{PaSSworD}";
|
||||
|
@ -123,6 +123,127 @@ void TestSymmetricCipher::testAes256CbcDecryption()
|
||||
plainText);
|
||||
}
|
||||
|
||||
void TestSymmetricCipher::testTwofish256CbcEncryption()
|
||||
{
|
||||
// NIST MCT Known-Answer Tests (cbc_e_m.txt)
|
||||
// https://www.schneier.com/code/twofish-kat.zip
|
||||
|
||||
QVector<QByteArray> keys {
|
||||
QByteArray::fromHex("0000000000000000000000000000000000000000000000000000000000000000"),
|
||||
QByteArray::fromHex("D0A260EB41755B19374BABF259A79DB3EA7162E65490B03B1AE4871FB35EF23B"),
|
||||
QByteArray::fromHex("8D55E4849A4DED08D89881E6708EDD26BEEE942073DFB3790B2798B240ACD74A"),
|
||||
QByteArray::fromHex("606EFDC2066A837AF0430EBE4CF1F21071CCB236C33B4B9D82404FDB05C74621"),
|
||||
QByteArray::fromHex("B119AA9485CEEEB4CC778AF21121E54DE4BDBA3498C61C8FD9004AA0C71909C3")
|
||||
};
|
||||
QVector<QByteArray> ivs {
|
||||
QByteArray::fromHex("00000000000000000000000000000000"),
|
||||
QByteArray::fromHex("EA7162E65490B03B1AE4871FB35EF23B"),
|
||||
QByteArray::fromHex("549FF6C6274F034211C31FADF3F22571"),
|
||||
QByteArray::fromHex("CF222616B0E4F8E48967D769456B916B"),
|
||||
QByteArray::fromHex("957108025BFD57125B40057BC2DE4FE2")
|
||||
};
|
||||
QVector<QByteArray> plainTexts {
|
||||
QByteArray::fromHex("00000000000000000000000000000000"),
|
||||
QByteArray::fromHex("D0A260EB41755B19374BABF259A79DB3"),
|
||||
QByteArray::fromHex("5DF7846FDB38B611EFD32A1429294095"),
|
||||
QByteArray::fromHex("ED3B19469C276E7228DB8F583C7F2F36"),
|
||||
QByteArray::fromHex("D177575683A46DCE3C34844C5DD0175D")
|
||||
};
|
||||
QVector<QByteArray> cipherTexts {
|
||||
QByteArray::fromHex("EA7162E65490B03B1AE4871FB35EF23B"),
|
||||
QByteArray::fromHex("549FF6C6274F034211C31FADF3F22571"),
|
||||
QByteArray::fromHex("CF222616B0E4F8E48967D769456B916B"),
|
||||
QByteArray::fromHex("957108025BFD57125B40057BC2DE4FE2"),
|
||||
QByteArray::fromHex("6F725C5950133F82EF021A94CADC8508")
|
||||
};
|
||||
|
||||
SymmetricCipher cipher(SymmetricCipher::Twofish, SymmetricCipher::Cbc, SymmetricCipher::Encrypt);
|
||||
bool ok;
|
||||
|
||||
for (int i = 0; i < keys.size(); ++i) {
|
||||
cipher.init(keys[i], ivs[i]);
|
||||
QByteArray ptNext = plainTexts[i];
|
||||
QByteArray ctPrev = ivs[i];
|
||||
QByteArray ctCur;
|
||||
QCOMPARE(cipher.blockSize(), 16);
|
||||
for (int j = 0; j < 5000; ++j) {
|
||||
ctCur = cipher.process(ptNext, &ok);
|
||||
if (!ok)
|
||||
break;
|
||||
ptNext = ctPrev;
|
||||
ctPrev = ctCur;
|
||||
|
||||
ctCur = cipher.process(ptNext, &ok);
|
||||
if (!ok)
|
||||
break;
|
||||
ptNext = ctPrev;
|
||||
ctPrev = ctCur;
|
||||
}
|
||||
|
||||
QVERIFY(ok);
|
||||
QCOMPARE(ctCur, cipherTexts[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void TestSymmetricCipher::testTwofish256CbcDecryption()
|
||||
{
|
||||
// NIST MCT Known-Answer Tests (cbc_d_m.txt)
|
||||
// https://www.schneier.com/code/twofish-kat.zip
|
||||
|
||||
QVector<QByteArray> keys {
|
||||
QByteArray::fromHex("0000000000000000000000000000000000000000000000000000000000000000"),
|
||||
QByteArray::fromHex("1B1FE8F5A911CD4C0D800EDCE8ED0A942CBA6271A1044F90C30BA8FE91E1C163"),
|
||||
QByteArray::fromHex("EBA31FF8D2A24FDD769A937353E23257294A33394E4D17A668060AD8230811A1"),
|
||||
QByteArray::fromHex("1DCF1915C389AB273F80F897BF008F058ED89F58A95C1BE523C4B11295ED2D0F"),
|
||||
QByteArray::fromHex("491B9A66D3ED4EF19F02180289D5B1A1C2596AE568540A95DC5244198A9B8869")
|
||||
};
|
||||
QVector<QByteArray> ivs {
|
||||
QByteArray::fromHex("00000000000000000000000000000000"),
|
||||
QByteArray::fromHex("1B1FE8F5A911CD4C0D800EDCE8ED0A94"),
|
||||
QByteArray::fromHex("F0BCF70D7BB382917B1A9DAFBB0F38C3"),
|
||||
QByteArray::fromHex("F66C06ED112BE4FA491A6BE4ECE2BD52"),
|
||||
QByteArray::fromHex("54D483731064E5D6A082E09536D53EA4")
|
||||
};
|
||||
QVector<QByteArray> plainTexts {
|
||||
QByteArray::fromHex("2CBA6271A1044F90C30BA8FE91E1C163"),
|
||||
QByteArray::fromHex("05F05148EF495836AB0DA226B2E9D0C2"),
|
||||
QByteArray::fromHex("A792AC61E7110C434BC2BBCAB6E53CAE"),
|
||||
QByteArray::fromHex("4C81F5BDC1081170FF96F50B1F76A566"),
|
||||
QByteArray::fromHex("BD959F5B787037631A37051EA5F369F8")
|
||||
};
|
||||
QVector<QByteArray> cipherTexts {
|
||||
QByteArray::fromHex("00000000000000000000000000000000"),
|
||||
QByteArray::fromHex("2CBA6271A1044F90C30BA8FE91E1C163"),
|
||||
QByteArray::fromHex("05F05148EF495836AB0DA226B2E9D0C2"),
|
||||
QByteArray::fromHex("A792AC61E7110C434BC2BBCAB6E53CAE"),
|
||||
QByteArray::fromHex("4C81F5BDC1081170FF96F50B1F76A566")
|
||||
};
|
||||
|
||||
SymmetricCipher cipher(SymmetricCipher::Twofish, SymmetricCipher::Cbc, SymmetricCipher::Decrypt);
|
||||
bool ok;
|
||||
|
||||
for (int i = 0; i < keys.size(); ++i) {
|
||||
cipher.init(keys[i], ivs[i]);
|
||||
QByteArray ctNext = cipherTexts[i];
|
||||
QByteArray ptCur;
|
||||
QCOMPARE(cipher.blockSize(), 16);
|
||||
for (int j = 0; j < 5000; ++j) {
|
||||
ptCur = cipher.process(ctNext, &ok);
|
||||
if (!ok)
|
||||
break;
|
||||
ctNext = ptCur;
|
||||
|
||||
ptCur = cipher.process(ctNext, &ok);
|
||||
if (!ok)
|
||||
break;
|
||||
ctNext = ptCur;
|
||||
}
|
||||
|
||||
QVERIFY(ok);
|
||||
QCOMPARE(ptCur, plainTexts[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void TestSymmetricCipher::testSalsa20()
|
||||
{
|
||||
// http://www.ecrypt.eu.org/stream/svn/viewcvs.cgi/ecrypt/trunk/submissions/salsa20/full/verified.test-vectors?logsort=rev&rev=210&view=markup
|
||||
|
@ -28,6 +28,8 @@ private Q_SLOTS:
|
||||
void initTestCase();
|
||||
void testAes256CbcEncryption();
|
||||
void testAes256CbcDecryption();
|
||||
void testTwofish256CbcEncryption();
|
||||
void testTwofish256CbcDecryption();
|
||||
void testSalsa20();
|
||||
void testPadding();
|
||||
void testStreamReset();
|
||||
|
@ -401,7 +401,13 @@ void TestGui::testSearch()
|
||||
QTRY_COMPARE(searchTextEdit->text(), QString("ZZZ"));
|
||||
QTRY_VERIFY(m_dbWidget->isInSearchMode());
|
||||
QTRY_COMPARE(entryView->model()->rowCount(), 0);
|
||||
// Press the search clear button
|
||||
QToolButton* clearButton = searchWidget->findChild<QToolButton*>("clearIcon");
|
||||
QTest::mouseClick(clearButton, Qt::LeftButton);
|
||||
QTRY_VERIFY(searchTextEdit->text().isEmpty());
|
||||
QTRY_VERIFY(searchTextEdit->hasFocus());
|
||||
// Escape clears searchedit and retains focus
|
||||
QTest::keyClicks(searchTextEdit, "ZZZ");
|
||||
QTest::keyClick(searchTextEdit, Qt::Key_Escape);
|
||||
QTRY_VERIFY(searchTextEdit->text().isEmpty());
|
||||
QTRY_VERIFY(searchTextEdit->hasFocus());
|
||||
@ -413,19 +419,26 @@ void TestGui::testSearch()
|
||||
// Search for "someTHING"
|
||||
QTest::keyClicks(searchTextEdit, "THING");
|
||||
QTRY_COMPARE(entryView->model()->rowCount(), 2);
|
||||
// Press Down to focus on the entry view if at EOL
|
||||
// Press Down to focus on the entry view
|
||||
QTest::keyClick(searchTextEdit, Qt::Key_Right, Qt::ControlModifier);
|
||||
QTRY_VERIFY(searchTextEdit->hasFocus());
|
||||
QTest::keyClick(searchTextEdit, Qt::Key_Down);
|
||||
QTRY_VERIFY(entryView->hasFocus());
|
||||
// Test clipboard
|
||||
// Restore focus and search text selection
|
||||
QTest::keyClick(m_mainWindow, Qt::Key_F, Qt::ControlModifier);
|
||||
QTRY_COMPARE(searchTextEdit->selectedText(), QString("someTHING"));
|
||||
// Ensure Down focuses on entry view when search text is selected
|
||||
QTest::keyClick(searchTextEdit, Qt::Key_Down);
|
||||
QTRY_VERIFY(entryView->hasFocus());
|
||||
// Refocus back to search edit
|
||||
QTest::mouseClick(searchTextEdit, Qt::LeftButton);
|
||||
QTRY_VERIFY(searchTextEdit->hasFocus());
|
||||
// Test password copy
|
||||
QClipboard *clipboard = QApplication::clipboard();
|
||||
QTest::keyClick(entryView, Qt::Key_C, Qt::ControlModifier);
|
||||
QTest::keyClick(searchTextEdit, Qt::Key_C, Qt::ControlModifier);
|
||||
QModelIndex searchedItem = entryView->model()->index(0, 1);
|
||||
Entry* searchedEntry = entryView->entryFromIndex(searchedItem);
|
||||
QTRY_COMPARE(searchedEntry->password(), clipboard->text());
|
||||
// Restore focus
|
||||
QTest::mouseClick(searchTextEdit, Qt::LeftButton);
|
||||
|
||||
// Test case sensitive search
|
||||
searchWidget->setCaseSensitive(true);
|
||||
@ -445,16 +458,14 @@ void TestGui::testSearch()
|
||||
QCOMPARE(groupView->currentGroup(), m_db->rootGroup());
|
||||
|
||||
// Try to edit the first entry from the search view
|
||||
// Refocus back to search edit
|
||||
QTest::mouseClick(searchTextEdit, Qt::LeftButton);
|
||||
QTRY_VERIFY(searchTextEdit->hasFocus());
|
||||
QVERIFY(m_dbWidget->isInSearchMode());
|
||||
|
||||
QModelIndex item = entryView->model()->index(0, 1);
|
||||
Entry* entry = entryView->entryFromIndex(item);
|
||||
QVERIFY(m_dbWidget->isInSearchMode());
|
||||
clickIndex(item, entryView, Qt::LeftButton);
|
||||
QAction* entryEditAction = m_mainWindow->findChild<QAction*>("actionEntryEdit");
|
||||
QVERIFY(entryEditAction->isEnabled());
|
||||
QWidget* entryEditWidget = toolBar->widgetForAction(entryEditAction);
|
||||
QVERIFY(entryEditWidget->isVisible());
|
||||
QVERIFY(entryEditWidget->isEnabled());
|
||||
QTest::mouseClick(entryEditWidget, Qt::LeftButton);
|
||||
QTest::keyClick(searchTextEdit, Qt::Key_Return);
|
||||
QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::EditMode);
|
||||
|
||||
// Perform the edit and save it
|
||||
@ -465,13 +476,12 @@ void TestGui::testSearch()
|
||||
QDialogButtonBox* editEntryWidgetButtonBox = editEntryWidget->findChild<QDialogButtonBox*>("buttonBox");
|
||||
QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton);
|
||||
|
||||
// Confirm the edit was made and we are back in view mode
|
||||
// Confirm the edit was made and we are back in search mode
|
||||
QTRY_VERIFY(m_dbWidget->isInSearchMode());
|
||||
QCOMPARE(entry->title(), origTitle.append("_edited"));
|
||||
|
||||
// Cancel search, should return to normal view
|
||||
QTest::mouseClick(searchTextEdit, Qt::LeftButton);
|
||||
QTest::keyClick(searchTextEdit, Qt::Key_Escape);
|
||||
QTest::keyClick(m_mainWindow, Qt::Key_Escape);
|
||||
QTRY_COMPARE(m_dbWidget->currentMode(), DatabaseWidget::ViewMode);
|
||||
}
|
||||
|
||||
@ -556,7 +566,7 @@ void TestGui::testCloneEntry()
|
||||
QCOMPARE(entryView->model()->rowCount(), 2);
|
||||
Entry* entryClone = entryView->entryFromIndex(entryView->model()->index(1, 1));
|
||||
QVERIFY(entryOrg->uuid() != entryClone->uuid());
|
||||
QCOMPARE(entryClone->title(), entryOrg->title());
|
||||
QCOMPARE(entryClone->title(), entryOrg->title() + QString(" - Clone"));
|
||||
}
|
||||
|
||||
void TestGui::testDragAndDropEntry()
|
||||
|
@ -20,6 +20,7 @@ target_link_libraries(kdbx-extract
|
||||
keepassx_core
|
||||
Qt5::Core
|
||||
${GCRYPT_LIBRARIES}
|
||||
${GPGERROR_LIBRARIES}
|
||||
${ZLIB_LIBRARIES})
|
||||
|
||||
add_executable(kdbx-merge kdbx-merge.cpp)
|
||||
@ -27,6 +28,7 @@ target_link_libraries(kdbx-merge
|
||||
keepassx_core
|
||||
Qt5::Core
|
||||
${GCRYPT_LIBRARIES}
|
||||
${GPGERROR_LIBRARIES}
|
||||
${ZLIB_LIBRARIES})
|
||||
|
||||
|
||||
|
@ -101,9 +101,10 @@ int main(int argc, char **argv)
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (line[0])
|
||||
if (line[0]) {
|
||||
calculate(line,advanced);
|
||||
printf("> ");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
|
Loading…
x
Reference in New Issue
Block a user