Conflicts:
	README.md
	src/core/Config.cpp
	src/gui/DatabaseTabWidget.cpp
	src/gui/DatabaseTabWidget.h
	src/gui/DatabaseWidget.cpp
	src/gui/DatabaseWidget.h
	src/gui/MainWindow.cpp
	src/gui/MainWindow.h
	src/gui/MainWindow.ui
	src/gui/SearchWidget.ui
	src/gui/SettingsWidget.cpp
	src/gui/SettingsWidgetGeneral.ui
This commit is contained in:
IvanF 2015-01-29 00:17:39 -08:00
commit 235baa3dcc
100 changed files with 8142 additions and 1057 deletions

19
.travis.yml Normal file
View File

@ -0,0 +1,19 @@
os:
- linux
- osx
compiler:
- gcc
- clang
language: cpp
install:
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then sudo apt-get -qq update; fi
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then sudo apt-get -qq install cmake libqt4-dev libgcrypt11-dev zlib1g-dev libxtst-dev; fi
- if [ "$TRAVIS_OS_NAME" = "osx" ]; then brew update; fi
- if [ "$TRAVIS_OS_NAME" = "osx" ]; then brew install cmake qt libgcrypt; fi
before_script: mkdir build && pushd build
script:
- cmake -DCMAKE_BUILD_TYPE=Debug -DWITH_GUI_TESTS=ON ..
- make
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then make test ARGS+="-E testgui"; fi
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then xvfb-run -a --server-args="-screen 0 800x600x24" make test ARGS+="-R testgui"; fi
- if [ "$TRAVIS_OS_NAME" = "osx" ]; then make test; fi

8
.tx/config Normal file
View File

@ -0,0 +1,8 @@
[main]
host = https://www.transifex.com
[keepassx.keepassx_ents]
source_file = share/translations/keepassx_en.ts
file_filter = share/translations/keepassx_<lang>.ts
source_lang = en
type = QT

View File

@ -32,7 +32,7 @@ include(CheckCXXSourceCompiles)
option(WITH_TESTS "Enable building of unit tests" ON)
option(WITH_GUI_TESTS "Enable building of GUI tests" OFF)
option(WITH_LTO "Enable Link Time Optimization (LTO)" OFF)
option(WITH_CXX11 "Build with the C++ 11 standard" OFF)
option(WITH_CXX11 "Build with the C++ 11 standard" ON)
set(KEEPASSX_VERSION "2.0 alpha 6")
set(KEEPASSX_VERSION_NUM "1.9.85")
@ -165,6 +165,9 @@ endif()
find_package(Qt4 4.6.0 REQUIRED ${QT_REQUIRED_MODULES})
include(${QT_USE_FILE})
# Debian sets the the build type to None for package builds.
# Make sure we don't enable asserts there.
set_property(DIRECTORY APPEND PROPERTY COMPILE_DEFINITIONS_NONE QT_NO_DEBUG)
find_package(Gcrypt REQUIRED)
if(NOT (${GCRYPT_VERSION_STRING} VERSION_LESS "1.6.0"))

View File

@ -2,7 +2,7 @@ Building:
=========
mkdir build
cd build
cmake .. [CMAKE PARAMETERS]
cmake [CMAKE PARAMETERS] ..
make [-jX]
Common cmake parameters:

137
README.md
View File

@ -1,17 +1,128 @@
# KeePassX + keepasshttp + autotype
# KeePassX + keepasshttp
This code extends the brilliant [KeePassX](https://www.keepassx.org/) program
which accesses [KeePass](http://keepass.info/) password databases.
## About
I have merged the latest version of the code with Francois Ferrand's
[keepassx-http](https://gitorious.org/keepassx/keepassx-http/) repository.
This adds support for the [keepasshttp](https://github.com/pfn/keepasshttp/)
protocol, enabling automatic form-filling in web browsers. This is accomplished
via a compatible browser plugin such as
[PassIFox](https://passifox.appspot.com/passifox.xpi) for Mozilla Firefox and
[chromeIPass](https://chrome.google.com/webstore/detail/chromeipass/ompiailgknfdndiefoaoiligalphfdae)
for Google Chrome.
KeePassX 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.
I have also added global autotype for OSX machines and added a few other minor
tweaks and bugfixes.
KeePassX saves many different information e.g. user names, passwords, urls, attachments and comments in one single database.
For a better management user-defined titles and icons can be specified for each single entry.
Furthermore the entries are sorted in groups, which are customizable as well. The integrated search function allows to search in a single group or the complete database.
KeePassX offers a little utility for secure password generation. The password generator is very customizable, fast and easy to use.
Especially someone who generates passwords frequently will appreciate this feature.
The complete database is always encrypted with the AES (aka Rijndael) encryption algorithm using a 256 bit key.
Therefore the saved information can be considered as quite safe. KeePassX uses a database format that is compatible with [KeePass Password Safe](http://keepass.info/).
This makes the use of that application even more favorable.
## Install
KeePassX can be downloaded and installed using an assortment of installers available on the main [KeePassX website](http://www.keepassx.org).
KeePassX can also be installed from the official repositories of many Linux repositories.
If you wish to build KeePassX from source, rather than rely on the pre-compiled binaries, you may wish to read up on the _From Source_ section.
### Debian
To install KeePassX from the Debian repository:
```bash
sudo apt-get install keepassx
```
### Red Hat
Install KeePassX from the Red Hat (or CentOS) repository:
```bash
sudo yum install keepassx
```
### Windows / Mac OS X
Download the installer from the KeePassX [download](https://www.keepassx.org/downloads) page.
Once downloaded, double click on the file to execute the installer.
### From Source
#### Build Dependencies
The following tools must exist within your PATH:
* make
* cmake (>= 2.6.4)
* g++ or clang++
The following libraries are required:
* Qt 4 (>= 4.6)
* libgcrypt
* zlib
On Debian you can install them with:
```bash
sudo apt-get install build-essential cmake libqt4-dev libgcrypt11-dev zlib1g-dev
```
#### Build Steps
To compile from source:
```bash
mkdir build
cd build
cmake ..
make [-jX]
```
You will have the compiled KeePassX binary inside the `./build/src/` directory.
To install this binary execute the following:
```bash
sudo make install
```
More detailed instructions available in the INSTALL file.
## Contribute
Coordination of work between developers is handled through the [KeePassX development](https://www.keepassx.org/dev/) site.
Requests for enhancements, or reports of bugs encountered, can also be reported through the KeePassX development site.
However, members of the open-source community are encouraged to submit pull requests directly through GitHub.
### Clone Repository
Clone the repository to a suitable location where you can extend and build this project.
```bash
git clone https://github.com/keepassx/keepassx.git
```
**Note:** This will clone the entire contents of the repository at the HEAD revision.
To update the project from within the project's folder you can run the following command:
```bash
git pull
```
### 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, please file a ticket on the [KeePassX development](https://www.keepassx.org/dev/) site.
### Bug Reports
Our software isn't always perfect, but we strive to always improve our work. You may file bug reports on the [KeePassX development](https://www.keepassx.org/dev/) site.
### 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.
Issue merge requests against our [GitHub repository](https://github.com/keepassx/keepassx).
### Translations
Translations are managed on [Transifex](https://www.transifex.com/projects/p/keepassx/) which offers a web interface.
Please join an existing language team or request a new one if there is none.

View File

@ -13,6 +13,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
add_subdirectory(translations)
file(GLOB DATABASE_ICONS icons/database/*.png)
install(FILES ${DATABASE_ICONS} DESTINATION ${DATA_INSTALL_DIR}/icons/database)

View File

@ -0,0 +1,26 @@
# Copyright (C) 2014 Felix Geyer <debfx@fobos.de>
#
# 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/>.
file(GLOB TRANSLATION_FILES *.ts)
get_filename_component(TRANSLATION_EN_ABS keepassx_en.ts ABSOLUTE)
list(REMOVE_ITEM TRANSLATION_FILES keepassx_en.ts)
list(REMOVE_ITEM TRANSLATION_FILES ${TRANSLATION_EN_ABS})
message(STATUS ${TRANSLATION_FILES})
qt4_add_translation(QM_FILES ${TRANSLATION_FILES})
install(FILES ${QM_FILES} DESTINATION ${DATA_INSTALL_DIR}/translations)
add_custom_target(translations DEPENDS ${QM_FILES})
add_dependencies(${PROGNAME} translations)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.0" language="en_US">
<context>
<name>DatabaseWidget</name>
<message numerus="yes">
<source>Do you really want to move %n entry(s) to the recycle bin?</source>
<translation>
<numerusform>Do you really want to move %n entry to the recycle bin?</numerusform>
<numerusform>Do you really want to move %n entries to the recycle bin?</numerusform>
</translation>
</message>
</context>
<context>
<name>EditEntryWidget</name>
<message numerus="yes">
<source>%n week(s)</source>
<translation>
<numerusform>%n week</numerusform>
<numerusform>%n weeks</numerusform>
</translation>
</message>
<message numerus="yes">
<source>%n month(s)</source>
<translation>
<numerusform>%n month</numerusform>
<numerusform>%n months</numerusform>
</translation>
</message>
</context>
<context>
<name>EditWidgetIcons</name>
<message numerus="yes">
<source>Can&apos;t delete icon. Still used by %n item(s).</source>
<translation>
<numerusform>Can&apos;t delete icon. Still used by %n item.</numerusform>
<numerusform>Can&apos;t delete icon. Still used by %n items.</numerusform>
</translation>
</message>
</context>
</TS>

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

8
share/translations/update.sh Executable file
View File

@ -0,0 +1,8 @@
#!/bin/sh
BASEDIR=$(dirname $0)
cd $BASEDIR/../..
lupdate -no-ui-lines -disable-heuristic similartext -locations none -no-obsolete src -ts share/translations/keepassx_en.ts
lupdate -no-ui-lines -disable-heuristic similartext -locations none -pluralonly src -ts share/translations/keepassx_en_plurals.ts

View File

@ -35,6 +35,7 @@ set(keepassx_SOURCES
core/Entry.cpp
core/EntryAttachments.cpp
core/EntryAttributes.cpp
core/EntrySearcher.cpp
core/FilePath.cpp
core/Global.h
core/Group.cpp
@ -47,7 +48,9 @@ set(keepassx_SOURCES
core/SignalMultiplexer.cpp
core/TimeDelta.cpp
core/TimeInfo.cpp
core/ToDbExporter.cpp
core/Tools.cpp
core/Translator.cpp
core/Uuid.cpp
core/qcommandlineoption.cpp
core/qcommandlineparser.cpp
@ -73,6 +76,7 @@ set(keepassx_SOURCES
gui/DatabaseSettingsWidget.cpp
gui/DatabaseTabWidget.cpp
gui/DatabaseWidget.cpp
gui/DatabaseWidgetStateSync.cpp
gui/DialogyWidget.cpp
gui/DragTabBar.cpp
gui/EditWidget.cpp
@ -162,6 +166,7 @@ set(keepassx_MOC
gui/DatabaseSettingsWidget.h
gui/DatabaseTabWidget.h
gui/DatabaseWidget.h
gui/DatabaseWidgetStateSync.h
gui/DialogyWidget.h
gui/DragTabBar.h
gui/EditWidget.h

View File

@ -190,8 +190,10 @@ void AutoType::performGlobalAutoType(const QList<Database*>& dbList)
if (entryList.isEmpty()) {
m_inAutoType = false;
MessageBox::information(Q_NULLPTR, tr("Auto-Type - KeePassX"),
tr("Couldn't find an entry that matches the window title."));
QString message = tr("Couldn't find an entry that matches the window title:");
message.append("\n\n");
message.append(windowTitle);
MessageBox::information(Q_NULLPTR, tr("Auto-Type - KeePassX"), message);
}
else if ((entryList.size() == 1) && !config()->get("security/autotypeask").toBool()) {
m_inAutoType = false;
@ -503,6 +505,12 @@ QString AutoType::autoTypeSequence(const Entry* entry, const QString& windowTitl
}
}
if (!match && config()->get("AutoTypeEntryTitleMatch").toBool() && !entry->title().isEmpty()
&& windowTitle.contains(entry->title(), Qt::CaseInsensitive)) {
sequence = entry->defaultAutoTypeSequence();
match = true;
}
if (!match) {
return QString();
}

View File

@ -209,23 +209,26 @@ QString AutoTypePlatformX11::windowTitle(Window window, bool useBlacklist)
unsigned long after;
unsigned char* data = Q_NULLPTR;
// the window manager spec says we should read _NET_WM_NAME first, then fall back to WM_NAME
int retVal = XGetWindowProperty(m_dpy, window, m_atomNetWmName, 0, 1000, false, m_atomUtf8String,
&type, &format, &nitems, &after, &data);
if (retVal != 0 && data) {
if ((retVal == 0) && data) {
title = QString::fromUtf8(reinterpret_cast<char*>(data));
}
else {
XTextProperty textProp;
retVal = XGetTextProperty(m_dpy, window, &textProp, m_atomWmName);
if (retVal != 0 && textProp.value) {
if ((retVal != 0) && textProp.value) {
char** textList = Q_NULLPTR;
int count;
if (textProp.encoding == m_atomUtf8String) {
title = QString::fromUtf8(reinterpret_cast<char*>(textProp.value));
}
else if (XmbTextPropertyToTextList(m_dpy, &textProp, &textList, &count) == 0 && textList && count > 0) {
else if ((XmbTextPropertyToTextList(m_dpy, &textProp, &textList, &count) == 0)
&& textList && (count > 0)) {
title = QString::fromLocal8Bit(textList[0]);
}
else if (textProp.encoding == m_atomString) {

View File

@ -6,6 +6,7 @@
#define KEEPASSX_VERSION "${KEEPASSX_VERSION}"
#define KEEPASSX_SOURCE_DIR "${CMAKE_SOURCE_DIR}"
#define KEEPASSX_BINARY_DIR "${CMAKE_BINARY_DIR}"
#define KEEPASSX_PLUGIN_DIR "${PLUGIN_INSTALL_DIR}"

View File

@ -71,7 +71,8 @@ Config::Config(QObject* parent)
userPath += "/keepassx/";
#else
userPath = QDir::fromNativeSeparators(QDesktopServices::storageLocation(QDesktopServices::DataLocation));
// storageLocation() appends the application name ("/keepassx/") to the end
// storageLocation() appends the application name ("/keepassx") to the end
userPath += "/";
#endif
userPath += "keepassx2.ini";
@ -89,19 +90,21 @@ void Config::init(const QString& fileName)
m_defaults.insert("RememberLastDatabases", true);
m_defaults.insert("OpenPreviousDatabasesOnStartup", true);
m_defaults.insert("ModifiedOnExpandedStateChanges", true);
m_defaults.insert("AutoSaveAfterEveryChange", false);
m_defaults.insert("AutoSaveOnExit", false);
m_defaults.insert("ShowToolbar", true);
m_defaults.insert("MinimizeOnCopy", false);
m_defaults.insert("UseGroupIconOnEntryCreation", false);
m_defaults.insert("ReloadBehavior", 0 /*always ask*/);
m_defaults.insert("AutoTypeEntryTitleMatch", true);
m_defaults.insert("security/clearclipboard", true);
m_defaults.insert("security/clearclipboardtimeout", 10);
m_defaults.insert("security/lockdatabaseidle", false);
m_defaults.insert("security/lockdatabaseidlesec", 10);
m_defaults.insert("security/passwordscleartext", false);
m_defaults.insert("security/autotypeask", false);
m_defaults.insert("security/autotypeask", true);
m_defaults.insert("GUI/Language", "system");
m_defaults.insert("GUI/ShowTrayIcon", false);
m_defaults.insert("GUI/MinimizeToTray", false);
}
Config* Config::instance()
@ -113,7 +116,7 @@ Config* Config::instance()
return m_instance;
}
void Config::createConfigFromFile(QString file)
void Config::createConfigFromFile(const QString& file)
{
Q_ASSERT(!m_instance);
m_instance = new Config(file, qApp);

View File

@ -36,7 +36,7 @@ public:
void set(const QString& key, const QVariant& value);
static Config* instance();
static void createConfigFromFile(QString file);
static void createConfigFromFile(const QString& file);
static void createTempFileInstance();
private:

View File

@ -579,25 +579,6 @@ const Database* Entry::database() const
}
}
bool Entry::match(const QString& searchTerm, Qt::CaseSensitivity caseSensitivity)
{
QStringList wordList = searchTerm.split(QRegExp("\\s"), QString::SkipEmptyParts);
Q_FOREACH (const QString& word, wordList) {
if (!wordMatch(word, caseSensitivity)) {
return false;
}
}
return true;
}
bool Entry::wordMatch(const QString& word, Qt::CaseSensitivity caseSensitivity)
{
return title().contains(word, caseSensitivity) ||
username().contains(word, caseSensitivity) ||
url().contains(word, caseSensitivity) ||
notes().contains(word, caseSensitivity);
}
QString Entry::resolvePlaceholders(const QString& str) const
{
QString result = str;

View File

@ -141,7 +141,6 @@ public:
void setGroup(Group* group);
void setUpdateTimeinfo(bool value);
bool match(const QString& searchTerm, Qt::CaseSensitivity caseSensitivity);
Q_SIGNALS:
/**
@ -157,7 +156,6 @@ private Q_SLOTS:
void updateModifiedSinceBegin();
private:
bool wordMatch(const QString& word, Qt::CaseSensitivity caseSensitivity);
const Database* database() const;
template <class T> bool set(T& property, const T& value);

View File

@ -27,6 +27,11 @@ QList<QString> EntryAttachments::keys() const
return m_attachments.keys();
}
bool EntryAttachments::hasKey(const QString& key) const
{
return m_attachments.keys().contains(key);
}
QList<QByteArray> EntryAttachments::values() const
{
return m_attachments.values();

View File

@ -30,6 +30,7 @@ class EntryAttachments : public QObject
public:
explicit EntryAttachments(QObject* parent = Q_NULLPTR);
QList<QString> keys() const;
bool hasKey(const QString& key) const;
QList<QByteArray> values() const;
QByteArray value(const QString& key) const;
void set(const QString& key, const QByteArray& value);

View File

@ -36,6 +36,11 @@ QList<QString> EntryAttributes::keys() const
return m_attributes.keys();
}
bool EntryAttributes::hasKey(const QString& key) const
{
return m_attributes.keys().contains(key);
}
QList<QString> EntryAttributes::customKeys()
{
QList<QString> customKeys;

View File

@ -32,6 +32,7 @@ class EntryAttributes : public QObject
public:
explicit EntryAttributes(QObject* parent = Q_NULLPTR);
QList<QString> keys() const;
bool hasKey(const QString& key) const;
QList<QString> customKeys();
QString value(const QString& key) const;
bool contains(const QString& key) const;

View File

@ -0,0 +1,65 @@
/*
* Copyright (C) 2014 Florian Geyer <blueice@fobos.de>
*
* 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 "EntrySearcher.h"
#include "core/Group.h"
QList<Entry*> EntrySearcher::search(const QString &searchTerm, const Group* group, Qt::CaseSensitivity caseSensitivity)
{
if (!group->resolveSearchingEnabled()) {
return QList<Entry*>();
}
return searchEntries(searchTerm, group, caseSensitivity);
}
QList<Entry*> EntrySearcher::searchEntries(const QString& searchTerm, const Group* group, Qt::CaseSensitivity caseSensitivity)
{
QList<Entry*> searchResult;
Q_FOREACH (Entry* entry, group->entries()) {
searchResult.append(matchEntry(searchTerm, entry, caseSensitivity));
}
Q_FOREACH (Group* childGroup, group->children()) {
if (childGroup->searchingEnabled() != Group::Disable) {
searchResult.append(searchEntries(searchTerm, childGroup, caseSensitivity));
}
}
return searchResult;
}
QList<Entry*> EntrySearcher::matchEntry(const QString& searchTerm, Entry* entry, Qt::CaseSensitivity caseSensitivity)
{
QStringList wordList = searchTerm.split(QRegExp("\\s"), QString::SkipEmptyParts);
Q_FOREACH (const QString& word, wordList) {
if (!wordMatch(word, entry, caseSensitivity)) {
return QList<Entry*>();
}
}
return QList<Entry*>() << entry;
}
bool EntrySearcher::wordMatch(const QString& word, Entry* entry, Qt::CaseSensitivity caseSensitivity)
{
return entry->title().contains(word, caseSensitivity) ||
entry->username().contains(word, caseSensitivity) ||
entry->url().contains(word, caseSensitivity) ||
entry->notes().contains(word, caseSensitivity);
}

37
src/core/EntrySearcher.h Normal file
View File

@ -0,0 +1,37 @@
/*
* Copyright (C) 2014 Florian Geyer <debfx@fobos.de>
*
* 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 KEEPASSX_ENTRYSEARCHER_H
#define KEEPASSX_ENTRYSEARCHER_H
#include <QString>
class Group;
class Entry;
class EntrySearcher
{
public:
QList<Entry*> search(const QString& searchTerm, const Group* group, Qt::CaseSensitivity caseSensitivity);
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);
};
#endif // KEEPASSX_ENTRYSEARCHER_H

14
src/core/Exporter.h Normal file
View File

@ -0,0 +1,14 @@
#ifndef KEEPASSX_EXPORTER_H
#define KEEPASSX_EXPORTER_H
class Database;
class Group;
class Exporter
{
public:
virtual Database* exportGroup(Group* group) = 0;
virtual ~Exporter() {}
};
#endif // KEEPASSX_EXPORTER_H

View File

@ -248,9 +248,7 @@ void Group::setExpanded(bool expanded)
if (m_data.isExpanded != expanded) {
m_data.isExpanded = expanded;
updateTimeinfo();
if (config()->get("ModifiedOnExpandedStateChanges").toBool()) {
Q_EMIT modified();
}
Q_EMIT modified();
}
}
@ -500,22 +498,6 @@ void Group::copyDataFrom(const Group* other)
m_lastTopVisibleEntry = other->m_lastTopVisibleEntry;
}
Database* Group::exportToDb()
{
Q_ASSERT(database());
Database* db = new Database();
Group* clonedGroup = clone(Entry::CloneNewUuid | Entry::CloneIncludeHistory);
clonedGroup->setParent(db->rootGroup());
QSet<Uuid> customIcons = customIconsRecursive();
db->metadata()->copyCustomIcons(customIcons, database()->metadata());
db->copyAttributesFrom(database());
return db;
}
void Group::addEntry(Entry* entry)
{
Q_ASSERT(entry);
@ -612,33 +594,6 @@ void Group::recCreateDelObjects()
}
}
QList<Entry*> Group::search(const QString& searchTerm, Qt::CaseSensitivity caseSensitivity,
bool resolveInherit)
{
QList<Entry*> searchResult;
bool search;
if (resolveInherit) {
search = resolveSearchingEnabled();
}
else if (searchingEnabled() == Disable) {
search = false;
}
else {
search = true;
}
if (search) {
Q_FOREACH (Entry* entry, m_entries) {
if (entry->match(searchTerm, caseSensitivity)) {
searchResult.append(entry);
}
}
Q_FOREACH (Group* group, m_children) {
searchResult.append(group->search(searchTerm, caseSensitivity, false));
}
}
return searchResult;
}
bool Group::resolveSearchingEnabled() const
{
switch (m_data.searchingEnabled) {

View File

@ -111,10 +111,6 @@ public:
*/
Group* clone(Entry::CloneFlags entryFlags = Entry::CloneNewUuid | Entry::CloneResetTimeInfo) const;
void copyDataFrom(const Group* other);
Database* exportToDb();
QList<Entry*> search(const QString& searchTerm, Qt::CaseSensitivity caseSensitivity,
bool resolveInherit = true);
Q_SIGNALS:
void dataChanged(Group* group);

39
src/core/ToDbExporter.cpp Normal file
View File

@ -0,0 +1,39 @@
/*
* Copyright (C) 2014 Felix Geyer <debfx@fobos.de>
* Copyright (C) 2014 Florian Geyer <blueice@fobos.de>
*
* 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 "ToDbExporter.h"
#include "core/Database.h"
#include "core/Group.h"
#include "core/Metadata.h"
Database* ToDbExporter::exportGroup(Group* group)
{
Database* oldDb = group->database();
Q_ASSERT(oldDb);
Database* db = new Database();
Group* clonedGroup = group->clone(Entry::CloneNewUuid | Entry::CloneIncludeHistory);
clonedGroup->setParent(db->rootGroup());
QSet<Uuid> customIcons = group->customIconsRecursive();
db->metadata()->copyCustomIcons(customIcons, oldDb->metadata());
db->copyAttributesFrom(oldDb);
return db;
}

33
src/core/ToDbExporter.h Normal file
View File

@ -0,0 +1,33 @@
/*
* Copyright (C) 2014 Felix Geyer <debfx@fobos.de>
* Copyright (C) 2014 Florian Geyer <blueice@fobos.de>
*
* 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 KEEPASSX_TODBEXPORTER_H
#define KEEPASSX_TODBEXPORTER_H
#include "core/Exporter.h"
class Database;
class Group;
class ToDbExporter : Exporter
{
public:
Database* exportGroup(Group* group);
};
#endif // KEEPASSX_TODBEXPORTER_H

120
src/core/Translator.cpp Normal file
View File

@ -0,0 +1,120 @@
/*
* Copyright (C) 2014 Felix Geyer <debfx@fobos.de>
*
* 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 "Translator.h"
#include <QCoreApplication>
#include <QDir>
#include <QLibraryInfo>
#include <QLocale>
#include <QRegExp>
#include <QTranslator>
#include "config-keepassx.h"
#include "core/Config.h"
#include "core/FilePath.h"
void Translator::installTranslator()
{
QString language = config()->get("GUI/Language").toString();
if (language == "system" || language.isEmpty()) {
language = QLocale::system().name();
}
if (!installTranslator(language)) {
// English fallback still needs translations for plurals
if (!installTranslator("en_plurals")) {
qWarning("Couldn't load translations.");
}
}
installQtTranslator(language);
availableLanguages();
}
QList<QPair<QString, QString> > Translator::availableLanguages()
{
QStringList paths;
#ifdef QT_DEBUG
paths.append(QString("%1/share/translations").arg(KEEPASSX_BINARY_DIR));
#endif
paths.append(filePath()->dataPath("translations"));
QList<QPair<QString, QString> > languages;
languages.append(QPair<QString, QString>("system", "System default"));
QRegExp regExp("keepassx_([a-zA-Z_]+)\\.qm", Qt::CaseInsensitive, QRegExp::RegExp2);
Q_FOREACH (const QString& path, paths) {
Q_FOREACH (const QString& filename, QDir(path).entryList()) {
if (regExp.exactMatch(filename)) {
QString langcode = regExp.cap(1);
if (langcode == "en_plurals") {
langcode = "en";
}
languages.append(QPair<QString, QString>(langcode,
QLocale::languageToString(QLocale(langcode).language())));
}
}
}
return languages;
}
bool Translator::installTranslator(const QString& language)
{
QStringList paths;
#ifdef QT_DEBUG
paths.append(QString("%1/share/translations").arg(KEEPASSX_BINARY_DIR));
#endif
paths.append(filePath()->dataPath("translations"));
Q_FOREACH (const QString& path, paths) {
if (installTranslator(language, path)) {
return true;
}
}
return false;
}
bool Translator::installTranslator(const QString& language, const QString& path)
{
QTranslator* translator = new QTranslator(qApp);
if (translator->load(QString("keepassx_").append(language), path)) {
QCoreApplication::installTranslator(translator);
return true;
}
else {
delete translator;
return false;
}
}
bool Translator::installQtTranslator(const QString& language)
{
QTranslator* qtTranslator = new QTranslator(qApp);
if (qtTranslator->load(QString("%1/qt_%2").arg(QLibraryInfo::location(QLibraryInfo::TranslationsPath), language))) {
QCoreApplication::installTranslator(qtTranslator);
return true;
}
else {
delete qtTranslator;
return false;
}
}

36
src/core/Translator.h Normal file
View File

@ -0,0 +1,36 @@
/*
* Copyright (C) 2014 Felix Geyer <debfx@fobos.de>
*
* 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 KEEPASSX_TRANSLATOR_H
#define KEEPASSX_TRANSLATOR_H
#include <QPair>
#include <QString>
class Translator
{
public:
static void installTranslator();
static QList<QPair<QString, QString> > availableLanguages();
private:
static bool installTranslator(const QString& language);
static bool installTranslator(const QString& language, const QString& path);
static bool installQtTranslator(const QString& language);
};
#endif // KEEPASSX_TRANSLATOR_H

View File

@ -21,7 +21,12 @@
#include <gcrypt.h>
#include "config-keepassx.h"
#include "crypto/CryptoHash.h"
#include "crypto/SymmetricCipher.h"
bool Crypto::m_initalized(false);
QString Crypto::m_errorStr;
#if !defined(GCRYPT_VERSION_NUMBER) || (GCRYPT_VERSION_NUMBER < 0x010600)
static int gcry_qt_mutex_init(void** p_sys)
@ -64,11 +69,11 @@ Crypto::Crypto()
{
}
void Crypto::init()
bool Crypto::init()
{
if (m_initalized) {
qWarning("Crypto::init: already initalized");
return;
return true;
}
// libgcrypt >= 1.6 doesn't allow custom thread callbacks anymore.
@ -78,7 +83,19 @@ void Crypto::init()
gcry_check_version(0);
gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0);
if (!checkAlgorithms()) {
return false;
}
// has to be set before testing Crypto classes
m_initalized = true;
if (!selfTest()) {
m_initalized = false;
return false;
}
return true;
}
bool Crypto::initalized()
@ -86,7 +103,89 @@ bool Crypto::initalized()
return m_initalized;
}
bool Crypto::selfTest()
QString Crypto::errorString()
{
return m_errorStr;
}
bool Crypto::backendSelfTest()
{
return (gcry_control(GCRYCTL_SELFTEST) == 0);
}
bool Crypto::checkAlgorithms()
{
if (gcry_cipher_algo_info(GCRY_CIPHER_AES256, GCRYCTL_TEST_ALGO, Q_NULLPTR, Q_NULLPTR) != 0) {
m_errorStr = "GCRY_CIPHER_AES256 not found.";
qWarning("Crypto::checkAlgorithms: %s", qPrintable(m_errorStr));
return false;
}
if (gcry_cipher_algo_info(GCRY_CIPHER_TWOFISH, GCRYCTL_TEST_ALGO, Q_NULLPTR, Q_NULLPTR) != 0) {
m_errorStr = "GCRY_CIPHER_TWOFISH not found.";
qWarning("Crypto::checkAlgorithms: %s", qPrintable(m_errorStr));
return false;
}
#ifdef GCRYPT_HAS_SALSA20
if (gcry_cipher_algo_info(GCRY_CIPHER_SALSA20, GCRYCTL_TEST_ALGO, Q_NULLPTR, Q_NULLPTR) != 0) {
m_errorStr = "GCRY_CIPHER_SALSA20 not found.";
qWarning("Crypto::checkAlgorithms: %s", qPrintable(m_errorStr));
return false;
}
#endif
if (gcry_md_test_algo(GCRY_MD_SHA256) != 0) {
m_errorStr = "GCRY_MD_SHA256 not found.";
qWarning("Crypto::checkAlgorithms: %s", qPrintable(m_errorStr));
return false;
}
return true;
}
bool Crypto::selfTest()
{
QByteArray sha256Test = CryptoHash::hash("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
CryptoHash::Sha256);
if (sha256Test != QByteArray::fromHex("248D6A61D20638B8E5C026930C3E6039A33CE45964FF2167F6ECEDD419DB06C1")) {
m_errorStr = "SHA-256 mismatch.";
qWarning("Crypto::selfTest: %s", qPrintable(m_errorStr));
return false;
}
QByteArray key = QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4");
QByteArray iv = QByteArray::fromHex("000102030405060708090a0b0c0d0e0f");
QByteArray plainText = QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172a");
plainText.append(QByteArray::fromHex("ae2d8a571e03ac9c9eb76fac45af8e51"));
QByteArray cipherText = QByteArray::fromHex("f58c4c04d6e5f1ba779eabfb5f7bfbd6");
cipherText.append(QByteArray::fromHex("9cfc4e967edb808d679f777bc6702c7d"));
SymmetricCipher aes256Encrypt(SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Encrypt, key, iv);
if (aes256Encrypt.process(plainText) != cipherText) {
m_errorStr = "AES-256 encryption mismatch.";
qWarning("Crypto::selfTest: %s", qPrintable(m_errorStr));
return false;
}
SymmetricCipher aes256Descrypt(SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Decrypt, key, iv);
if (aes256Descrypt.process(cipherText) != plainText) {
m_errorStr = "AES-256 decryption mismatch.";
qWarning("Crypto::selfTest: %s", qPrintable(m_errorStr));
return false;
}
QByteArray salsa20Key = QByteArray::fromHex("F3F4F5F6F7F8F9FAFBFCFDFEFF000102030405060708090A0B0C0D0E0F101112");
QByteArray salsa20iv = QByteArray::fromHex("0000000000000000");
QByteArray salsa20Plain = QByteArray::fromHex("00000000000000000000000000000000");
QByteArray salsa20Cipher = QByteArray::fromHex("B4C0AFA503BE7FC29A62058166D56F8F");
SymmetricCipher salsa20Stream(SymmetricCipher::Salsa20, SymmetricCipher::Stream,
SymmetricCipher::Encrypt, salsa20Key, salsa20iv);
if (salsa20Stream.process(salsa20Plain) != salsa20Cipher) {
m_errorStr = "Salsa20 stream cipher mismatch.";
qWarning("Crypto::selfTest: %s", qPrintable(m_errorStr));
return false;
}
return true;
}

View File

@ -18,18 +18,25 @@
#ifndef KEEPASSX_CRYPTO_H
#define KEEPASSX_CRYPTO_H
#include <QString>
#include "core/Global.h"
class Crypto
{
public:
static void init();
static bool init();
static bool initalized();
static bool selfTest();
static bool backendSelfTest();
static QString errorString();
private:
Crypto();
static bool checkAlgorithms();
static bool selfTest();
static bool m_initalized;
static QString m_errorStr;
};
#endif // KEEPASSX_CRYPTO_H

View File

@ -35,9 +35,15 @@ KeePass2XmlReader::KeePass2XmlReader()
, m_db(Q_NULLPTR)
, m_meta(Q_NULLPTR)
, m_error(false)
, m_strictMode(false)
{
}
void KeePass2XmlReader::setStrictMode(bool strictMode)
{
m_strictMode = strictMode;
}
void KeePass2XmlReader::readDatabase(QIODevice* device, Database* db, KeePass2RandomStream* randomStream)
{
m_error = false;
@ -493,7 +499,12 @@ Group* KeePass2XmlReader::parseGroup()
if (m_xml.name() == "UUID") {
Uuid uuid = readUuid();
if (uuid.isNull()) {
raiseError("Null group uuid");
if (m_strictMode) {
raiseError("Null group uuid");
}
else {
group->setUuid(Uuid::random());
}
}
else {
group->setUuid(uuid);
@ -508,7 +519,9 @@ Group* KeePass2XmlReader::parseGroup()
else if (m_xml.name() == "IconID") {
int iconId = readNumber();
if (iconId < 0) {
raiseError("Invalid group icon number");
if (m_strictMode) {
raiseError("Invalid group icon number");
}
}
else {
if (iconId >= DatabaseIcons::IconCount) {
@ -584,6 +597,10 @@ Group* KeePass2XmlReader::parseGroup()
}
}
if (group->uuid().isNull() && !m_strictMode) {
group->setUuid(Uuid::random());
}
if (!group->uuid().isNull()) {
Group* tmpGroup = group;
group = getGroup(tmpGroup->uuid());
@ -630,7 +647,9 @@ void KeePass2XmlReader::parseDeletedObject()
if (m_xml.name() == "UUID") {
Uuid uuid = readUuid();
if (uuid.isNull()) {
raiseError("Null DeleteObject uuid");
if (m_strictMode) {
raiseError("Null DeleteObject uuid");
}
}
else {
delObj.uuid = uuid;
@ -647,7 +666,7 @@ void KeePass2XmlReader::parseDeletedObject()
if (!delObj.uuid.isNull() && !delObj.deletionTime.isNull()) {
m_db->addDeletedObject(delObj);
}
else {
else if (m_strictMode) {
raiseError("Missing DeletedObject uuid or time");
}
}
@ -665,7 +684,12 @@ Entry* KeePass2XmlReader::parseEntry(bool history)
if (m_xml.name() == "UUID") {
Uuid uuid = readUuid();
if (uuid.isNull()) {
raiseError("Null entry uuid");
if (m_strictMode) {
raiseError("Null entry uuid");
}
else {
entry->setUuid(Uuid::random());
}
}
else {
entry->setUuid(uuid);
@ -674,7 +698,9 @@ Entry* KeePass2XmlReader::parseEntry(bool history)
else if (m_xml.name() == "IconID") {
int iconId = readNumber();
if (iconId < 0) {
raiseError("Invalud entry icon number");
if (m_strictMode) {
raiseError("Invalud entry icon number");
}
}
else {
entry->setIcon(iconId);
@ -726,6 +752,10 @@ Entry* KeePass2XmlReader::parseEntry(bool history)
}
}
if (entry->uuid().isNull() && !m_strictMode) {
entry->setUuid(Uuid::random());
}
if (!entry->uuid().isNull()) {
if (history) {
entry->setUpdateTimeinfo(false);
@ -795,7 +825,13 @@ void KeePass2XmlReader::parseEntryString(Entry* entry)
}
if (keySet && valueSet) {
entry->attributes()->set(key, value, protect);
// the default attributes are always there so additionally check if it's empty
if (entry->attributes()->hasKey(key) && !entry->attributes()->value(key).isEmpty()) {
raiseError("Duplicate custom attribute found");
}
else {
entry->attributes()->set(key, value, protect);
}
}
else {
raiseError("Entry string key or value missing");
@ -844,7 +880,12 @@ QPair<QString, QString> KeePass2XmlReader::parseEntryBinary(Entry* entry)
}
if (keySet && valueSet) {
entry->attachments()->set(key, value);
if (entry->attachments()->hasKey(key)) {
raiseError("Duplicate attachment found");
}
else {
entry->attachments()->set(key, value);
}
}
else {
raiseError("Entry binary key or value missing");
@ -986,7 +1027,12 @@ QDateTime KeePass2XmlReader::readDateTime()
QDateTime dt = QDateTime::fromString(str, Qt::ISODate);
if (!dt.isValid()) {
raiseError("Invalid date time value");
if (m_strictMode) {
raiseError("Invalid date time value");
}
else {
dt = Tools::currentDateTimeUtc();
}
}
return dt;
@ -1001,7 +1047,9 @@ QColor KeePass2XmlReader::readColor()
}
if (colorStr.length() != 7 || colorStr[0] != '#') {
raiseError("Invalid color value");
if (m_strictMode) {
raiseError("Invalid color value");
}
return QColor();
}
@ -1011,7 +1059,9 @@ QColor KeePass2XmlReader::readColor()
bool ok;
int rgbPart = rgbPartStr.toInt(&ok, 16);
if (!ok || rgbPart > 255) {
raiseError("Invalid color rgb part");
if (m_strictMode) {
raiseError("Invalid color rgb part");
}
return QColor();
}
@ -1043,7 +1093,9 @@ Uuid KeePass2XmlReader::readUuid()
{
QByteArray uuidBin = readBinary();
if (uuidBin.length() != Uuid::Length) {
raiseError("Invalid uuid value");
if (m_strictMode) {
raiseError("Invalid uuid value");
}
return Uuid();
}
else {

View File

@ -47,6 +47,7 @@ public:
bool hasError();
QString errorString();
QByteArray headerHash();
void setStrictMode(bool strictMode);
private:
bool parseKeePassFile();
@ -95,6 +96,7 @@ private:
QByteArray m_headerHash;
bool m_error;
QString m_errorStr;
bool m_strictMode;
};
#endif // KEEPASSX_KEEPASS2XMLREADER_H

View File

@ -51,6 +51,7 @@ void Clipboard::setText(const QString& text)
if (config()->get("security/clearclipboard").toBool()) {
int timeout = config()->get("security/clearclipboardtimeout").toInt();
if (timeout > 0) {
m_lastCopied = text;
m_timer->start(timeout * 1000);
}
}
@ -65,8 +66,12 @@ void Clipboard::clearClipboard()
return;
}
clipboard->clear(QClipboard::Clipboard);
if (clipboard->supportsSelection()) {
if (clipboard->text(QClipboard::Clipboard) == m_lastCopied) {
clipboard->clear(QClipboard::Clipboard);
}
if (clipboard->supportsSelection()
&& (clipboard->text(QClipboard::Selection) == m_lastCopied)) {
clipboard->clear(QClipboard::Selection);
}
@ -74,6 +79,8 @@ void Clipboard::clearClipboard()
QDBusMessage message = QDBusMessage::createMethodCall("org.kde.klipper", "/klipper", "", "clearClipboardHistory");
QDBusConnection::sessionBus().send(message);
#endif
m_lastCopied.clear();
}
void Clipboard::cleanup()

View File

@ -43,6 +43,7 @@ private:
static Clipboard* m_instance;
QTimer* m_timer;
QString m_lastCopied;
};
inline Clipboard* clipboard() {

View File

@ -87,7 +87,7 @@ void DatabaseOpenWidget::enterKey(const QString& pw, const QString& keyFile)
m_ui->editPassword->setText(pw);
}
if (!keyFile.isEmpty()) {
m_ui->checkKeyFile->setText(keyFile);
m_ui->comboKeyFile->setEditText(keyFile);
}
openDatabase();
@ -129,8 +129,8 @@ void DatabaseOpenWidget::openDatabase(const CompositeKey& masterKey)
Q_EMIT editFinished(true);
}
else {
MessageBox::warning(this, tr("Error"), tr("Unable to open the database.\n%1")
.arg(reader.errorString()));
MessageBox::warning(this, tr("Error"), tr("Unable to open the database.").append("\n")
.append(reader.errorString()));
m_ui->editPassword->clear();
}
}
@ -150,7 +150,7 @@ 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:\n%1").arg(errorMsg));
MessageBox::warning(this, tr("Error"), tr("Can't open key file").append(":\n").append(errorMsg));
return CompositeKey();
}
masterKey.addKey(key);

View File

@ -19,9 +19,6 @@
#include <QFileInfo>
#include <QTabWidget>
#include <QtCore/QFileSystemWatcher>
#include <QtCore/QTimer>
#include <QtCore/QDebug>
#include "autotype/AutoType.h"
#include "core/Config.h"
@ -30,6 +27,7 @@
#include "core/Metadata.h"
#include "core/qsavefile.h"
#include "gui/DatabaseWidget.h"
#include "gui/DatabaseWidgetStateSync.h"
#include "gui/DragTabBar.h"
#include "gui/FileDialog.h"
#include "gui/MessageBox.h"
@ -48,16 +46,17 @@ DatabaseManagerStruct::DatabaseManagerStruct()
const int DatabaseTabWidget::LastDatabasesCount = 5;
DatabaseTabWidget::DatabaseTabWidget(QWidget* parent)
: QTabWidget(parent),
m_fileWatcher(new QFileSystemWatcher(this))
: QTabWidget(parent)
, m_dbWidgetSateSync(new DatabaseWidgetStateSync(this))
{
DragTabBar* tabBar = new DragTabBar(this);
tabBar->setDrawBase(false);
setTabBar(tabBar);
connect(this, SIGNAL(tabCloseRequested(int)), SLOT(closeDatabase(int)));
connect(this, SIGNAL(currentChanged(int)), SLOT(emitActivateDatabaseChanged()));
connect(this, SIGNAL(activateDatabaseChanged(DatabaseWidget*)), m_dbWidgetSateSync, SLOT(setActive(DatabaseWidget*)));
connect(autoType(), SIGNAL(globalShortcutTriggered()), SLOT(performGlobalAutoType()));
connect(m_fileWatcher, SIGNAL(fileChanged(QString)), SLOT(fileChanged(QString)));
}
DatabaseTabWidget::~DatabaseTabWidget()
@ -71,7 +70,16 @@ DatabaseTabWidget::~DatabaseTabWidget()
void DatabaseTabWidget::toggleTabbar()
{
tabBar()->setVisible(count() > 1);
if (count() > 1) {
if (!tabBar()->isVisible()) {
tabBar()->show();
}
}
else {
if (tabBar()->isVisible()) {
tabBar()->hide();
}
}
}
void DatabaseTabWidget::newDatabase()
@ -97,7 +105,7 @@ void DatabaseTabWidget::openDatabase()
}
void DatabaseTabWidget::openDatabase(const QString& fileName, const QString& pw,
const QString& keyFile, const CompositeKey& key, int index)
const QString& keyFile)
{
QFileInfo fileInfo(fileName);
QString canonicalFilePath = fileInfo.canonicalFilePath();
@ -141,17 +149,12 @@ void DatabaseTabWidget::openDatabase(const QString& fileName, const QString& pw,
dbStruct.filePath = fileInfo.absoluteFilePath();
dbStruct.canonicalFilePath = canonicalFilePath;
dbStruct.fileName = fileInfo.fileName();
dbStruct.lastModified = fileInfo.lastModified();
insertDatabase(db, dbStruct, index);
m_fileWatcher->addPath(dbStruct.filePath);
insertDatabase(db, dbStruct);
updateRecentDatabases(dbStruct.filePath);
updateLastDatabases(dbStruct.filePath);
if (!key.isEmpty()) {
dbStruct.dbWidget->switchToOpenDatabase(dbStruct.filePath, key);
}
else if (!pw.isNull() || !keyFile.isEmpty()) {
if (!pw.isNull() || !keyFile.isEmpty()) {
dbStruct.dbWidget->switchToOpenDatabase(dbStruct.filePath, pw, keyFile);
}
else {
@ -178,121 +181,6 @@ void DatabaseTabWidget::importKeePass1Database()
dbStruct.dbWidget->switchToImportKeepass1(fileName);
}
void DatabaseTabWidget::fileChanged(const QString &fileName)
{
const bool wasEmpty = m_changedFiles.isEmpty();
m_changedFiles.insert(fileName);
bool found = false;
Q_FOREACH (QString f, m_fileWatcher->files()) {
if (f == fileName) {
found = true;
break;
}
}
if (!found) m_fileWatcher->addPath(fileName);
if (wasEmpty && !m_changedFiles.isEmpty())
QTimer::singleShot(200, this, SLOT(checkReloadDatabases()));
}
void DatabaseTabWidget::expectFileChange(const DatabaseManagerStruct& dbStruct)
{
if (dbStruct.filePath.isEmpty())
return;
m_expectedFileChanges.insert(dbStruct.filePath);
}
void DatabaseTabWidget::unexpectFileChange(DatabaseManagerStruct& dbStruct)
{
if (dbStruct.filePath.isEmpty())
return;
m_expectedFileChanges.remove(dbStruct.filePath);
dbStruct.lastModified = QFileInfo(dbStruct.filePath).lastModified();
}
void DatabaseTabWidget::checkReloadDatabases()
{
QSet<QString> changedFiles;
changedFiles = m_changedFiles.subtract(m_expectedFileChanges);
m_changedFiles.clear();
if (changedFiles.isEmpty())
return;
Q_FOREACH (DatabaseManagerStruct dbStruct, m_dbList) {
QString filePath = dbStruct.filePath;
Database * db = dbStruct.dbWidget->database();
if (!changedFiles.contains(filePath))
continue;
QFileInfo fi(filePath);
QDateTime lastModified = fi.lastModified();
if (dbStruct.lastModified == lastModified)
continue;
DatabaseWidget::Mode mode = dbStruct.dbWidget->currentMode();
if (mode == DatabaseWidget::None || mode == DatabaseWidget::LockedMode || !db->hasKey())
continue;
ReloadBehavior reloadBehavior = ReloadBehavior(config()->get("ReloadBehavior").toInt());
if ( (reloadBehavior == AlwaysAsk)
|| (reloadBehavior == ReloadUnmodified && (mode == DatabaseWidget::EditMode
|| mode == DatabaseWidget::OpenMode))
|| (reloadBehavior == ReloadUnmodified && dbStruct.modified)) {
int res = QMessageBox::warning(this, fi.exists() ? tr("Database file changed") : tr("Database file removed"),
tr("Do you want to discard your changes and reload?"),
QMessageBox::Yes|QMessageBox::No);
if (res == QMessageBox::No)
continue;
}
if (fi.exists()) {
//Ignore/cancel all edits
dbStruct.dbWidget->switchToView(false);
dbStruct.modified = false;
//Save current group/entry
Uuid currentGroup;
if (Group* group = dbStruct.dbWidget->currentGroup())
currentGroup = group->uuid();
Uuid currentEntry;
if (Entry* entry = dbStruct.dbWidget->entryView()->currentEntry())
currentEntry = entry->uuid();
QString searchText = dbStruct.dbWidget->searchText();
bool caseSensitive = dbStruct.dbWidget->caseSensitiveSearch();
bool allGroups = dbStruct.dbWidget->isAllGroupsSearch();
//Reload updated db
CompositeKey key = db->key();
int tabPos = databaseIndex(db);
closeDatabase(db);
openDatabase(filePath, QString(), QString(), key, tabPos);
//Restore current group/entry
dbStruct = indexDatabaseManagerStruct(count() - 1);
if (dbStruct.dbWidget && dbStruct.dbWidget->currentMode() == DatabaseWidget::ViewMode) {
Database * db = dbStruct.dbWidget->database();
if (!currentGroup.isNull())
if (Group* group = db->resolveGroup(currentGroup))
dbStruct.dbWidget->groupView()->setCurrentGroup(group);
if (!searchText.isEmpty())
dbStruct.dbWidget->search(searchText, caseSensitive, allGroups);
if (!currentEntry.isNull())
if (Entry* entry = db->resolveEntry(currentEntry))
dbStruct.dbWidget->entryView()->setCurrentEntry(entry);
}
} else {
//Ignore/cancel all edits
dbStruct.dbWidget->switchToView(false);
dbStruct.modified = false;
//Close database
closeDatabase(dbStruct.dbWidget->database());
}
}
}
bool DatabaseTabWidget::closeDatabase(Database* db)
{
Q_ASSERT(db);
@ -305,8 +193,7 @@ bool DatabaseTabWidget::closeDatabase(Database* db)
if (dbName.right(1) == "*") {
dbName.chop(1);
}
if ((dbStruct.dbWidget->currentMode() == DatabaseWidget::EditMode ||
dbStruct.dbWidget->currentMode() == DatabaseWidget::OpenMode) && db->hasKey()) {
if (dbStruct.dbWidget->isInEditMode() && db->hasKey()) {
QMessageBox::StandardButton result =
MessageBox::question(
this, tr("Close?"),
@ -348,7 +235,6 @@ void DatabaseTabWidget::deleteDatabase(Database* db)
int index = databaseIndex(db);
m_fileWatcher->removePath(dbStruct.filePath);
removeTab(index);
toggleTabbar();
m_dbList.remove(db);
@ -362,13 +248,6 @@ void DatabaseTabWidget::deleteDatabase(Database* db)
bool DatabaseTabWidget::closeAllDatabases()
{
QStringList reloadDatabases;
if (config()->get("AutoReopenLastDatabases", false).toBool()) {
for (int i = 0; i < count(); i ++)
reloadDatabases << indexDatabaseManagerStruct(i).filePath;
}
config()->set("LastOpenDatabases", reloadDatabases);
while (!m_dbList.isEmpty()) {
if (!closeDatabase()) {
return false;
@ -384,16 +263,12 @@ void DatabaseTabWidget::saveDatabase(Database* db)
if (dbStruct.saveToFilename) {
bool result = false;
expectFileChange(dbStruct);
QSaveFile saveFile(dbStruct.filePath);
if (saveFile.open(QIODevice::WriteOnly)) {
m_writer.writeDatabase(&saveFile, db);
result = saveFile.commit();
}
unexpectFileChange(dbStruct);
if (result) {
dbStruct.modified = false;
updateTabName(db);
@ -411,12 +286,12 @@ void DatabaseTabWidget::saveDatabase(Database* db)
void DatabaseTabWidget::saveDatabaseAs(Database* db)
{
DatabaseManagerStruct& dbStruct = m_dbList[db];
QString oldFilePath;
QString oldFileName;
if (dbStruct.saveToFilename) {
oldFilePath = dbStruct.filePath;
oldFileName = dbStruct.filePath;
}
QString fileName = fileDialog()->getSaveFileName(this, tr("Save database as"),
oldFilePath, tr("KeePass 2 Database").append(" (*.kdbx)"));
oldFileName, tr("KeePass 2 Database").append(" (*.kdbx)"));
if (!fileName.isEmpty()) {
bool result = false;
@ -427,18 +302,15 @@ void DatabaseTabWidget::saveDatabaseAs(Database* db)
}
if (result) {
m_fileWatcher->removePath(oldFilePath);
dbStruct.modified = false;
dbStruct.saveToFilename = true;
QFileInfo fileInfo(fileName);
dbStruct.filePath = fileInfo.absoluteFilePath();
dbStruct.canonicalFilePath = fileInfo.canonicalFilePath();
dbStruct.fileName = fileInfo.fileName();
dbStruct.lastModified = fileInfo.lastModified();
dbStruct.dbWidget->updateFilename(dbStruct.filePath);
updateTabName(db);
updateRecentDatabases(dbStruct.filePath);
m_fileWatcher->addPath(dbStruct.filePath);
updateLastDatabases(dbStruct.filePath);
}
else {
MessageBox::critical(this, tr("Error"), tr("Writing the database failed.") + "\n\n"
@ -609,13 +481,14 @@ Database* DatabaseTabWidget::databaseFromDatabaseWidget(DatabaseWidget* dbWidget
return Q_NULLPTR;
}
void DatabaseTabWidget::insertDatabase(Database* db, const DatabaseManagerStruct& dbStruct, int index)
void DatabaseTabWidget::insertDatabase(Database* db, const DatabaseManagerStruct& dbStruct)
{
m_dbList.insert(db, dbStruct);
index = insertTab(index, dbStruct.dbWidget, "");
addTab(dbStruct.dbWidget, "");
toggleTabbar();
updateTabName(db);
int index = databaseIndex(db);
setCurrentIndex(index);
connectDatabase(db);
connect(dbStruct.dbWidget, SIGNAL(closeRequest()), SLOT(closeDatabaseFromSender()));
@ -634,7 +507,7 @@ DatabaseWidget* DatabaseTabWidget::currentDatabaseWidget()
}
}
bool DatabaseTabWidget::hasLockableDatabases()
bool DatabaseTabWidget::hasLockableDatabases() const
{
QHashIterator<Database*, DatabaseManagerStruct> i(m_dbList);
while (i.hasNext()) {
@ -687,7 +560,7 @@ void DatabaseTabWidget::modified()
}
}
void DatabaseTabWidget::updateRecentDatabases(const QString& filename)
void DatabaseTabWidget::updateLastDatabases(const QString& filename)
{
if (!config()->get("RememberLastDatabases").toBool()) {
config()->set("LastDatabases", QVariant());
@ -719,6 +592,11 @@ void DatabaseTabWidget::changeDatabase(Database* newDb)
connectDatabase(newDb, oldDb);
}
void DatabaseTabWidget::emitActivateDatabaseChanged()
{
Q_EMIT activateDatabaseChanged(currentDatabaseWidget());
}
void DatabaseTabWidget::connectDatabase(Database* newDb, Database* oldDb)
{
if (oldDb) {

View File

@ -18,18 +18,16 @@
#ifndef KEEPASSX_DATABASETABWIDGET_H
#define KEEPASSX_DATABASETABWIDGET_H
#include <QDateTime>
#include <QHash>
#include <QTabWidget>
#include <QSet>
#include "format/KeePass2Writer.h"
#include "gui/DatabaseWidget.h"
class DatabaseWidget;
class DatabaseWidgetStateSync;
class DatabaseOpenWidget;
class QFile;
class QFileSystemWatcher;
struct DatabaseManagerStruct
{
@ -42,7 +40,6 @@ struct DatabaseManagerStruct
bool saveToFilename;
bool modified;
bool readOnly;
QDateTime lastModified;
};
Q_DECLARE_TYPEINFO(DatabaseManagerStruct, Q_MOVABLE_TYPE);
@ -55,25 +52,16 @@ public:
explicit DatabaseTabWidget(QWidget* parent = Q_NULLPTR);
~DatabaseTabWidget();
void openDatabase(const QString& fileName, const QString& pw = QString(),
const QString& keyFile = QString(), const CompositeKey& key = CompositeKey(),
int index = -1);
const QString& keyFile = QString());
DatabaseWidget* currentDatabaseWidget();
bool hasLockableDatabases();
bool hasLockableDatabases() const;
static const int LastDatabasesCount;
enum ReloadBehavior {
AlwaysAsk,
ReloadUnmodified,
IgnoreAll
};
public Q_SLOTS:
void newDatabase();
void openDatabase();
void importKeePass1Database();
void fileChanged(const QString& fileName);
void checkReloadDatabases();
void saveDatabase(int index = -1);
void saveDatabaseAs(int index = -1);
bool closeDatabase(int index = -1);
@ -88,6 +76,7 @@ public Q_SLOTS:
Q_SIGNALS:
void tabNameChanged();
void databaseWithFileClosed(QString filePath);
void activateDatabaseChanged(DatabaseWidget* dbWidget);
private Q_SLOTS:
void updateTabName(Database* db);
@ -96,6 +85,7 @@ private Q_SLOTS:
void modified();
void toggleTabbar();
void changeDatabase(Database* newDb);
void emitActivateDatabaseChanged();
private:
void saveDatabase(Database* db);
@ -106,17 +96,13 @@ private:
Database* indexDatabase(int index);
DatabaseManagerStruct indexDatabaseManagerStruct(int index);
Database* databaseFromDatabaseWidget(DatabaseWidget* dbWidget);
void insertDatabase(Database* db, const DatabaseManagerStruct& dbStruct, int index = -1);
void updateRecentDatabases(const QString& filename);
void insertDatabase(Database* db, const DatabaseManagerStruct& dbStruct);
void updateLastDatabases(const QString& filename);
void connectDatabase(Database* newDb, Database* oldDb = Q_NULLPTR);
void expectFileChange(const DatabaseManagerStruct& dbStruct);
void unexpectFileChange(DatabaseManagerStruct& dbStruct);
KeePass2Writer m_writer;
QHash<Database*, DatabaseManagerStruct> m_dbList;
QSet<QString> m_changedFiles;
QSet<QString> m_expectedFileChanges;
QFileSystemWatcher* m_fileWatcher;
DatabaseWidgetStateSync* m_dbWidgetSateSync;
};
#endif // KEEPASSX_DATABASETABWIDGET_H

View File

@ -28,6 +28,7 @@
#include "autotype/AutoType.h"
#include "core/Config.h"
#include "core/EntrySearcher.h"
#include "core/FilePath.h"
#include "core/Group.h"
#include "core/Metadata.h"
@ -52,8 +53,6 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent)
, m_newGroup(Q_NULLPTR)
, m_newEntry(Q_NULLPTR)
, m_newParent(Q_NULLPTR)
, m_searchAllGroups(false)
, m_searchSensitivity(Qt::CaseInsensitive)
{
m_searchUi->setupUi(m_searchWidget);
@ -62,12 +61,13 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent)
m_mainWidget = new QWidget(this);
QLayout* layout = new QHBoxLayout(m_mainWidget);
QSplitter* splitter = new QSplitter(m_mainWidget);
m_splitter = new QSplitter(m_mainWidget);
m_splitter->setChildrenCollapsible(false);
QWidget* rightHandSideWidget = new QWidget(splitter);
QWidget* rightHandSideWidget = new QWidget(m_splitter);
m_searchWidget->setParent(rightHandSideWidget);
m_groupView = new GroupView(db, splitter);
m_groupView = new GroupView(db, m_splitter);
m_groupView->setObjectName("groupView");
m_groupView->setContextMenuPolicy(Qt::CustomContextMenu);
connect(m_groupView, SIGNAL(customContextMenuRequested(QPoint)),
@ -80,22 +80,13 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent)
connect(m_entryView, SIGNAL(customContextMenuRequested(QPoint)),
SLOT(emitEntryContextMenuRequested(QPoint)));
QSizePolicy policy;
policy = m_groupView->sizePolicy();
policy.setHorizontalStretch(30);
m_groupView->setSizePolicy(policy);
policy = rightHandSideWidget->sizePolicy();
policy.setHorizontalStretch(70);
rightHandSideWidget->setSizePolicy(policy);
QAction* closeAction = new QAction(m_searchWidget);
QIcon closeIcon = filePath()->icon("actions", "dialog-close");
closeAction->setIcon(closeIcon);
m_searchUi->closeSearchButton->setDefaultAction(closeAction);
m_searchUi->closeSearchButton->setShortcut(Qt::Key_Escape);
int iconsize = style()->pixelMetric(QStyle::PM_SmallIconSize);
m_searchUi->closeSearchButton->setIconSize(QSize(iconsize,iconsize));
m_searchWidget->hide();
m_searchUi->caseSensitiveCheckBox->setVisible(false);
QVBoxLayout* vLayout = new QVBoxLayout(rightHandSideWidget);
vLayout->setMargin(0);
@ -104,10 +95,17 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent)
rightHandSideWidget->setLayout(vLayout);
splitter->addWidget(m_groupView);
splitter->addWidget(rightHandSideWidget);
setTabOrder(m_searchUi->searchRootRadioButton, m_entryView);
setTabOrder(m_entryView, m_groupView);
setTabOrder(m_groupView, m_searchWidget);
layout->addWidget(splitter);
m_splitter->addWidget(m_groupView);
m_splitter->addWidget(rightHandSideWidget);
m_splitter->setStretchFactor(0, 30);
m_splitter->setStretchFactor(1, 70);
layout->addWidget(m_splitter);
m_mainWidget->setLayout(layout);
m_editEntryWidget = new EditEntryWidget();
@ -139,6 +137,8 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent)
addWidget(m_keepass1OpenWidget);
addWidget(m_unlockDatabaseWidget);
connect(m_splitter, SIGNAL(splitterMoved(int,int)), SIGNAL(splitterSizesChanged()));
connect(m_entryView->header(), SIGNAL(sectionResized(int,int,int)), SIGNAL(entryColumnSizesChanged()));
connect(m_groupView, SIGNAL(groupChanged(Group*)), this, SLOT(clearLastGroup(Group*)));
connect(m_groupView, SIGNAL(groupChanged(Group*)), SIGNAL(groupChanged()));
connect(m_groupView, SIGNAL(groupChanged(Group*)), m_entryView, SLOT(setGroup(Group*)));
@ -155,7 +155,11 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent)
connect(m_keepass1OpenWidget, SIGNAL(editFinished(bool)), SLOT(openDatabase(bool)));
connect(m_unlockDatabaseWidget, SIGNAL(editFinished(bool)), SLOT(unlockDatabase(bool)));
connect(this, SIGNAL(currentChanged(int)), this, SLOT(emitCurrentModeChanged()));
connect(m_searchUi->searchResults, SIGNAL(linkActivated(QString)), this, SLOT(onLinkActivated(QString)));
connect(m_searchUi->searchEdit, SIGNAL(textChanged(QString)), this, SLOT(startSearchTimer()));
connect(m_searchUi->caseSensitiveCheckBox, SIGNAL(toggled(bool)), this, SLOT(startSearch()));
connect(m_searchUi->searchCurrentRadioButton, SIGNAL(toggled(bool)), this, SLOT(startSearch()));
connect(m_searchUi->searchRootRadioButton, SIGNAL(toggled(bool)), this, SLOT(startSearch()));
connect(m_searchUi->searchEdit, SIGNAL(returnPressed()), m_entryView, SLOT(setFocus()));
connect(m_searchTimer, SIGNAL(timeout()), this, SLOT(search()));
connect(closeAction, SIGNAL(triggered()), this, SLOT(closeSearch()));
@ -166,7 +170,7 @@ DatabaseWidget::~DatabaseWidget()
{
}
DatabaseWidget::Mode DatabaseWidget::currentMode()
DatabaseWidget::Mode DatabaseWidget::currentMode() const
{
if (currentWidget() == Q_NULLPTR) {
return DatabaseWidget::None;
@ -185,21 +189,56 @@ DatabaseWidget::Mode DatabaseWidget::currentMode()
}
}
bool DatabaseWidget::isInEditMode() const
{
if (currentMode() == DatabaseWidget::LockedMode) {
return m_widgetBeforeLock != Q_NULLPTR
&& m_widgetBeforeLock != m_mainWidget
&& m_widgetBeforeLock != m_unlockDatabaseWidget;
}
else {
return currentMode() == DatabaseWidget::EditMode;
}
}
QList<int> DatabaseWidget::splitterSizes() const
{
return m_splitter->sizes();
}
void DatabaseWidget::setSplitterSizes(const QList<int>& sizes)
{
m_splitter->setSizes(sizes);
}
QList<int> DatabaseWidget::entryHeaderViewSizes() const
{
QList<int> sizes;
for (int i = 0; i < m_entryView->header()->count(); i++) {
sizes.append(m_entryView->header()->sectionSize(i));
}
return sizes;
}
void DatabaseWidget::setEntryViewHeaderSizes(const QList<int>& sizes)
{
if (sizes.size() != m_entryView->header()->count()) {
Q_ASSERT(false);
return;
}
for (int i = 0; i < sizes.size(); i++) {
m_entryView->header()->resizeSection(i, sizes[i]);
}
}
void DatabaseWidget::emitCurrentModeChanged()
{
Q_EMIT currentModeChanged(currentMode());
}
GroupView* DatabaseWidget::groupView()
{
return m_groupView;
}
EntryView* DatabaseWidget::entryView()
{
return m_entryView;
}
Database* DatabaseWidget::database()
{
return m_db;
@ -296,8 +335,7 @@ void DatabaseWidget::deleteEntries()
if (selected.size() > 1) {
QMessageBox::StandardButton result = MessageBox::question(
this, tr("Move entries to recycle bin?"),
tr("Do you really want to move %1 entries to the recycle bin?")
.arg(selected.size()),
tr("Do you really want to move %n entry(s) to the recycle bin?", 0, selected.size()),
QMessageBox::Yes | QMessageBox::No);
if (result == QMessageBox::No) {
return;
@ -429,7 +467,7 @@ void DatabaseWidget::createGroup()
void DatabaseWidget::deleteGroup()
{
Group* currentGroup = m_groupView->currentGroup();
if (!currentGroup || !canDeleteCurrentGoup()) {
if (!currentGroup || !canDeleteCurrentGroup()) {
Q_ASSERT(false);
return;
}
@ -597,7 +635,7 @@ void DatabaseWidget::unlockDatabase(bool accepted)
Q_ASSERT(accepted);
Q_UNUSED(accepted);
setCurrentWidget(widgetBeforeLock);
setCurrentWidget(m_widgetBeforeLock);
Q_EMIT unlockedDatabase();
}
@ -660,13 +698,6 @@ void DatabaseWidget::switchToOpenDatabase(const QString& fileName, const QString
m_databaseOpenWidget->enterKey(password, keyFile);
}
void DatabaseWidget::switchToOpenDatabase(const QString &fileName, const CompositeKey& masterKey)
{
updateFilename(fileName);
switchToOpenDatabase(fileName);
m_databaseOpenWidget->enterKey(masterKey);
}
void DatabaseWidget::switchToImportKeepass1(const QString& fileName)
{
updateFilename(fileName);
@ -674,117 +705,100 @@ void DatabaseWidget::switchToImportKeepass1(const QString& fileName)
setCurrentWidget(m_keepass1OpenWidget);
}
void DatabaseWidget::search(const QString& searchString, bool caseSensitive, bool allGroups)
void DatabaseWidget::openSearch()
{
m_searchSensitivity = caseSensitive ? Qt::CaseSensitive
: Qt::CaseInsensitive;
m_searchAllGroups = allGroups;
search(searchString);
}
if (isInSearchMode()) {
m_searchUi->searchEdit->selectAll();
void DatabaseWidget::search(const QString& text)
{
if (text.isEmpty()) {
if (m_entryView->inEntryListMode())
closeSearch();
}
else if (m_entryView->inEntryListMode()) {
m_searchText = text;
startSearchTimer();
if (!m_searchUi->searchEdit->hasFocus()) {
m_searchUi->searchEdit->setFocus();
// make sure the search action is checked again
emitCurrentModeChanged();
}
}
else {
showSearch(text);
}
}
bool DatabaseWidget::caseSensitiveSearch() const
{
return m_searchSensitivity == Qt::CaseSensitive;
}
void DatabaseWidget::setCaseSensitiveSearch(bool caseSensitive)
{
if (caseSensitive != caseSensitiveSearch()) {
m_searchSensitivity = caseSensitive ? Qt::CaseSensitive
: Qt::CaseInsensitive;
if (m_entryView->inEntryListMode())
startSearchTimer();
}
}
bool DatabaseWidget::isAllGroupsSearch() const
{
return m_searchAllGroups;
}
bool DatabaseWidget::canChooseSearchScope() const
{
return currentGroup() != m_db->rootGroup();
}
Group*DatabaseWidget::currentGroup() const
{
return m_entryView->inEntryListMode() ? m_lastGroup
: m_groupView->currentGroup();
}
void DatabaseWidget::setAllGroupsSearch(bool allGroups)
{
if (allGroups != isAllGroupsSearch()) {
m_searchAllGroups = allGroups;
if (m_entryView->inEntryListMode())
startSearchTimer();
showSearch();
}
}
void DatabaseWidget::closeSearch()
{
Q_ASSERT(m_lastGroup);
m_searchTimer->stop();
Q_EMIT listModeAboutToActivate();
m_groupView->setCurrentGroup(m_lastGroup);
m_searchTimer->stop();
Q_EMIT listModeActivated();
}
void DatabaseWidget::showSearch(const QString & searchString)
void DatabaseWidget::showSearch()
{
m_searchText = searchString;
Q_EMIT searchModeAboutToActivate();
m_searchUi->searchEdit->blockSignals(true);
m_searchUi->searchEdit->clear();
m_searchUi->searchEdit->blockSignals(false);
m_searchUi->searchCurrentRadioButton->blockSignals(true);
m_searchUi->searchRootRadioButton->blockSignals(true);
m_searchUi->searchRootRadioButton->setChecked(true);
m_searchUi->searchCurrentRadioButton->blockSignals(false);
m_searchUi->searchRootRadioButton->blockSignals(false);
m_lastGroup = m_groupView->currentGroup();
Q_ASSERT(m_lastGroup);
if (m_lastGroup == m_db->rootGroup()) {
m_searchUi->optionsWidget->hide();
m_searchUi->searchCurrentRadioButton->hide();
m_searchUi->searchRootRadioButton->hide();
}
else {
m_searchUi->optionsWidget->show();
m_searchUi->searchCurrentRadioButton->show();
m_searchUi->searchRootRadioButton->show();
m_searchUi->searchCurrentRadioButton->setText(tr("Current group")
.append(" (")
.append(m_lastGroup->name())
.append(")"));
}
m_groupView->setCurrentIndex(QModelIndex());
m_searchWidget->show();
search();
}
m_searchUi->searchEdit->setFocus();
void DatabaseWidget::onLinkActivated(const QString& link)
{
if (link == "searchAll")
setAllGroupsSearch(true);
else if (link == "searchCurrent")
setAllGroupsSearch(false);
Q_EMIT searchModeActivated();
}
void DatabaseWidget::search()
{
Q_ASSERT(m_lastGroup);
Group* searchGroup = m_searchAllGroups ? m_db->rootGroup()
: m_lastGroup;
QList<Entry*> searchResult = searchGroup->search(m_searchText, m_searchSensitivity);
QString message;
switch(searchResult.count()) {
case 0: message = tr("No result found"); break;
case 1: message = tr("1 result found"); break;
default: message = tr("%1 results found").arg(searchResult.count()); break;
Group* searchGroup;
if (m_searchUi->searchCurrentRadioButton->isChecked()) {
searchGroup = m_lastGroup;
}
if (searchGroup != m_db->rootGroup())
message += tr(" in \"%1\". <a href='searchAll'>Search all groups...</a>").arg(searchGroup->name());
else if (m_lastGroup != m_db->rootGroup())
message += tr(". <a href='searchCurrent'>Search in \"%1\"...</a>").arg(m_lastGroup->name());
else
message += tr(".");
m_searchUi->searchResults->setText(message);
else if (m_searchUi->searchRootRadioButton->isChecked()) {
searchGroup = m_db->rootGroup();
}
else {
Q_ASSERT(false);
return;
}
Qt::CaseSensitivity sensitivity;
if (m_searchUi->caseSensitiveCheckBox->isChecked()) {
sensitivity = Qt::CaseSensitive;
}
else {
sensitivity = Qt::CaseInsensitive;
}
QList<Entry*> searchResult = EntrySearcher().search(m_searchUi->searchEdit->text(), searchGroup, sensitivity);
m_entryView->setEntryList(searchResult);
}
@ -799,9 +813,6 @@ void DatabaseWidget::startSearchTimer()
void DatabaseWidget::startSearch()
{
if (!isInSearchMode())
return;
if (!m_searchTimer->isActive()) {
m_searchTimer->stop();
}
@ -818,12 +829,12 @@ void DatabaseWidget::emitEntryContextMenuRequested(const QPoint& pos)
Q_EMIT entryContextMenuRequested(m_entryView->viewport()->mapToGlobal(pos));
}
bool DatabaseWidget::dbHasKey()
bool DatabaseWidget::dbHasKey() const
{
return m_db->hasKey();
}
bool DatabaseWidget::canDeleteCurrentGoup()
bool DatabaseWidget::canDeleteCurrentGroup() const
{
bool isRootGroup = m_db->rootGroup() == m_groupView->currentGroup();
bool isRecycleBin = m_db->metadata()->recycleBin() == m_groupView->currentGroup();
@ -835,11 +846,6 @@ bool DatabaseWidget::isInSearchMode() const
return m_entryView->inEntryListMode();
}
QString DatabaseWidget::searchText() const
{
return m_entryView->inEntryListMode() ? m_searchText : QString();
}
void DatabaseWidget::clearLastGroup(Group* group)
{
if (group) {
@ -852,7 +858,7 @@ void DatabaseWidget::lock()
{
Q_ASSERT(currentMode() != DatabaseWidget::LockedMode);
widgetBeforeLock = currentWidget();
m_widgetBeforeLock = currentWidget();
m_unlockDatabaseWidget->load(m_filename, m_db);
setCurrentWidget(m_unlockDatabaseWidget);
}
@ -861,3 +867,23 @@ void DatabaseWidget::updateFilename(const QString& fileName)
{
m_filename = fileName;
}
int DatabaseWidget::numberOfSelectedEntries() const
{
return m_entryView->numberOfSelectedEntries();
}
QStringList DatabaseWidget::customEntryAttributes() const
{
Entry* entry = m_entryView->currentEntry();
if (!entry) {
return QStringList();
}
return entry->attributes()->customKeys();
}
bool DatabaseWidget::isGroupSelected() const
{
return m_groupView->currentGroup() != Q_NULLPTR;
}

View File

@ -38,8 +38,8 @@ class GroupView;
class KeePass1OpenWidget;
class QFile;
class QMenu;
class QSplitter;
class UnlockDatabaseWidget;
class CompositeKey;
namespace Ui {
class SearchWidget;
@ -61,24 +61,24 @@ public:
explicit DatabaseWidget(Database* db, QWidget* parent = Q_NULLPTR);
~DatabaseWidget();
GroupView* groupView();
EntryView* entryView();
Database* database();
bool dbHasKey();
bool canDeleteCurrentGoup();
bool dbHasKey() const;
bool canDeleteCurrentGroup() const;
bool isInSearchMode() const;
QString searchText() const;
bool caseSensitiveSearch() const;
bool isAllGroupsSearch() const;
bool canChooseSearchScope() const;
Group* currentGroup() const;
int addWidget(QWidget* w);
void setCurrentIndex(int index);
void setCurrentWidget(QWidget* widget);
DatabaseWidget::Mode currentMode();
DatabaseWidget::Mode currentMode() const;
void lock();
void updateFilename(const QString& filename);
void search(const QString & searchString, bool caseSensitive, bool allGroups);
int numberOfSelectedEntries() const;
QStringList customEntryAttributes() const;
bool isGroupSelected() const;
bool isInEditMode() const;
QList<int> splitterSizes() const;
void setSplitterSizes(const QList<int>& sizes);
QList<int> entryHeaderViewSizes() const;
void setEntryViewHeaderSizes(const QList<int>& sizes);
Q_SIGNALS:
void closeRequest();
@ -89,6 +89,12 @@ Q_SIGNALS:
void groupContextMenuRequested(const QPoint& globalPos);
void entryContextMenuRequested(const QPoint& globalPos);
void unlockedDatabase();
void listModeAboutToActivate();
void listModeActivated();
void searchModeAboutToActivate();
void searchModeActivated();
void splitterSizesChanged();
void entryColumnSizesChanged();
public Q_SLOTS:
void createEntry();
@ -111,24 +117,19 @@ public Q_SLOTS:
void switchToDatabaseSettings();
void switchToOpenDatabase(const QString& fileName);
void switchToOpenDatabase(const QString& fileName, const QString& password, const QString& keyFile);
void switchToOpenDatabase(const QString &fileName, const CompositeKey &masterKey);
void switchToImportKeepass1(const QString& fileName);
void switchToView(bool accepted);
void search(const QString & searchString);
void setCaseSensitiveSearch(bool caseSensitive);
void setAllGroupsSearch(bool allGroups);
void emitGroupContextMenuRequested(const QPoint& pos);
void emitEntryContextMenuRequested(const QPoint& pos);
void openSearch();
private Q_SLOTS:
void entryActivationSignalReceived(Entry* entry, EntryModel::ModelColumn column);
void onLinkActivated(const QString& link);
void showSearch(const QString & searchString = QString());
void switchBackToEntryEdit();
void switchToView(bool accepted);
void switchToHistoryView(Entry* entry);
void switchToEntryEdit(Entry* entry);
void switchToEntryEdit(Entry* entry, bool create);
void switchToGroupEdit(Group* entry, bool create);
void emitGroupContextMenuRequested(const QPoint& pos);
void emitEntryContextMenuRequested(const QPoint& pos);
void updateMasterKey(bool accepted);
void openDatabase(bool accepted);
void unlockDatabase(bool accepted);
@ -137,6 +138,7 @@ private Q_SLOTS:
void search();
void startSearch();
void startSearchTimer();
void showSearch();
void closeSearch();
private:
@ -155,6 +157,7 @@ private:
DatabaseOpenWidget* m_databaseOpenWidget;
KeePass1OpenWidget* m_keepass1OpenWidget;
UnlockDatabaseWidget* m_unlockDatabaseWidget;
QSplitter* m_splitter;
GroupView* m_groupView;
EntryView* m_entryView;
Group* m_newGroup;
@ -162,11 +165,8 @@ private:
Group* m_newParent;
Group* m_lastGroup;
QTimer* m_searchTimer;
QWidget* widgetBeforeLock;
QWidget* m_widgetBeforeLock;
QString m_filename;
QString m_searchText;
bool m_searchAllGroups;
Qt::CaseSensitivity m_searchSensitivity;
};
#endif // KEEPASSX_DATABASEWIDGET_H

View File

@ -0,0 +1,154 @@
/*
* Copyright (C) 2014 Felix Geyer <debfx@fobos.de>
* Copyright (C) 2014 Florian Geyer <blueice@fobos.de>
*
* 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 "DatabaseWidgetStateSync.h"
#include "core/Config.h"
DatabaseWidgetStateSync::DatabaseWidgetStateSync(QObject* parent)
: QObject(parent)
, m_activeDbWidget(Q_NULLPTR)
, m_blockUpdates(false)
{
m_splitterSizes = variantToIntList(config()->get("GUI/SplitterState"));
m_columnSizesList = variantToIntList(config()->get("GUI/EntryListColumnSizes"));
m_columnSizesSearch = variantToIntList(config()->get("GUI/EntrySearchColumnSizes"));
}
DatabaseWidgetStateSync::~DatabaseWidgetStateSync()
{
config()->set("GUI/SplitterState", intListToVariant(m_splitterSizes));
config()->set("GUI/EntryListColumnSizes", intListToVariant(m_columnSizesList));
config()->set("GUI/EntrySearchColumnSizes", intListToVariant(m_columnSizesSearch));
}
void DatabaseWidgetStateSync::setActive(DatabaseWidget* dbWidget)
{
if (m_activeDbWidget) {
disconnect(m_activeDbWidget, 0, this, 0);
}
m_activeDbWidget = dbWidget;
if (m_activeDbWidget) {
m_blockUpdates = true;
if (!m_splitterSizes.isEmpty()) {
m_activeDbWidget->setSplitterSizes(m_splitterSizes);
}
if (m_activeDbWidget->isGroupSelected()) {
restoreListView();
}
else {
restoreSearchView();
}
m_blockUpdates = false;
connect(m_activeDbWidget, SIGNAL(splitterSizesChanged()),
SLOT(updateSplitterSizes()));
connect(m_activeDbWidget, SIGNAL(entryColumnSizesChanged()),
SLOT(updateColumnSizes()));
connect(m_activeDbWidget, SIGNAL(listModeActivated()),
SLOT(restoreListView()));
connect(m_activeDbWidget, SIGNAL(searchModeActivated()),
SLOT(restoreSearchView()));
connect(m_activeDbWidget, SIGNAL(listModeAboutToActivate()),
SLOT(blockUpdates()));
connect(m_activeDbWidget, SIGNAL(searchModeAboutToActivate()),
SLOT(blockUpdates()));
}
}
void DatabaseWidgetStateSync::restoreListView()
{
if (!m_columnSizesList.isEmpty()) {
m_activeDbWidget->setEntryViewHeaderSizes(m_columnSizesList);
}
m_blockUpdates = false;
}
void DatabaseWidgetStateSync::restoreSearchView()
{
if (!m_columnSizesSearch.isEmpty()) {
m_activeDbWidget->setEntryViewHeaderSizes(m_columnSizesSearch);
}
m_blockUpdates = false;
}
void DatabaseWidgetStateSync::blockUpdates()
{
m_blockUpdates = true;
}
void DatabaseWidgetStateSync::updateSplitterSizes()
{
if (m_blockUpdates) {
return;
}
m_splitterSizes = m_activeDbWidget->splitterSizes();
}
void DatabaseWidgetStateSync::updateColumnSizes()
{
if (m_blockUpdates) {
return;
}
if (m_activeDbWidget->isGroupSelected()) {
m_columnSizesList = m_activeDbWidget->entryHeaderViewSizes();
}
else {
m_columnSizesSearch = m_activeDbWidget->entryHeaderViewSizes();
}
}
QList<int> DatabaseWidgetStateSync::variantToIntList(const QVariant& variant)
{
QVariantList list = variant.toList();
QList<int> result;
Q_FOREACH (const QVariant& var, list) {
bool ok;
int size = var.toInt(&ok);
if (ok) {
result.append(size);
}
else {
result.clear();
break;
}
}
return result;
}
QVariant DatabaseWidgetStateSync::intListToVariant(const QList<int>& list)
{
QVariantList result;
Q_FOREACH (int value, list) {
result.append(value);
}
return result;
}

View File

@ -0,0 +1,54 @@
/*
* Copyright (C) 2014 Felix Geyer <debfx@fobos.de>
* Copyright (C) 2014 Florian Geyer <blueice@fobos.de>
*
* 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 KEEPASSX_HEADERVIEWSYNC_H
#define KEEPASSX_HEADERVIEWSYNC_H
#include "gui/DatabaseWidget.h"
class DatabaseWidgetStateSync : public QObject
{
Q_OBJECT
public:
explicit DatabaseWidgetStateSync(QObject* parent = Q_NULLPTR);
~DatabaseWidgetStateSync();
public Q_SLOTS:
void setActive(DatabaseWidget* dbWidget);
void restoreListView();
void restoreSearchView();
private Q_SLOTS:
void blockUpdates();
void updateSplitterSizes();
void updateColumnSizes();
private:
static QList<int> variantToIntList(const QVariant& variant);
static QVariant intListToVariant(const QList<int>& list);
DatabaseWidget* m_activeDbWidget;
bool m_blockUpdates;
QList<int> m_splitterSizes;
QList<int> m_columnSizesList;
QList<int> m_columnSizesSearch;
};
#endif // KEEPASSX_HEADERVIEWSYNC_H

View File

@ -271,8 +271,7 @@ void EditWidgetIcons::removeCustomIcon()
}
else {
MessageBox::information(this, tr("Can't delete icon!"),
tr("Can't delete icon. Still used by %1 items.")
.arg(iconUsedCount));
tr("Can't delete icon. Still used by %n item(s).", 0, iconUsedCount));
}
}
}

View File

@ -64,8 +64,8 @@ void KeePass1OpenWidget::openDatabase()
Q_EMIT editFinished(true);
}
else {
MessageBox::warning(this, tr("Error"), tr("Unable to open the database.\n%1")
.arg(reader.errorString()));
MessageBox::warning(this, tr("Error"), tr("Unable to open the database.").append("\n")
.append(reader.errorString()));
m_ui->editPassword->clear();
}
}

View File

@ -20,79 +20,34 @@
#include <QCloseEvent>
#include <QShortcut>
#include <QtGui/QLineEdit>
#include "autotype/AutoType.h"
#include "core/Config.h"
#include "core/Database.h"
#include "core/Entry.h"
#include "core/FilePath.h"
#include "core/InactivityTimer.h"
#include "core/Metadata.h"
#include "gui/AboutDialog.h"
#include "gui/DatabaseWidget.h"
#include "gui/entry/EntryView.h"
#include "gui/group/GroupView.h"
#include "http/Service.h"
#include "http/HttpSettings.h"
#include "http/OptionDialog.h"
#include "gui/SettingsWidget.h"
#include "gui/qocoa/qsearchfield.h"
class HttpPlugin: public ISettingsPage {
public:
HttpPlugin(DatabaseTabWidget * tabWidget) {
m_service = new Service(tabWidget);
}
virtual ~HttpPlugin() {
//delete m_service;
}
virtual QString name() {
return QObject::tr("Http");
}
virtual QWidget * createWidget() {
OptionDialog * dlg = new OptionDialog();
QObject::connect(dlg, SIGNAL(removeSharedEncryptionKeys()), m_service, SLOT(removeSharedEncryptionKeys()));
QObject::connect(dlg, SIGNAL(removeStoredPermissions()), m_service, SLOT(removeStoredPermissions()));
return dlg;
}
virtual void loadSettings(QWidget * widget) {
qobject_cast<OptionDialog*>(widget)->loadSettings();
}
virtual void saveSettings(QWidget * widget) {
qobject_cast<OptionDialog*>(widget)->saveSettings();
if (HttpSettings::isEnabled())
m_service->start();
else
m_service->stop();
}
private:
Service *m_service;
};
const QString MainWindow::BaseWindowTitle = "KeePassX";
MainWindow::MainWindow()
: m_ui(new Ui::MainWindow())
, m_trayIcon(Q_NULLPTR)
{
m_ui->setupUi(this);
restoreGeometry(config()->get("window/Geometry").toByteArray());
m_ui->settingsWidget->addSettingsPage(new HttpPlugin(m_ui->tabWidget));
m_countDefaultAttributes = m_ui->menuEntryCopyAttribute->actions().size();
restoreGeometry(config()->get("GUI/MainWindowGeometry").toByteArray());
setWindowIcon(filePath()->applicationIcon());
QAction* toggleViewAction = m_ui->toolBar->toggleViewAction();
toggleViewAction->setText(tr("Show toolbar"));
m_ui->menuView->addAction(toggleViewAction);
int toolbarIconSize = config()->get("ToolbarIconSize", 20).toInt();
setToolbarIconSize(toolbarIconSize);
bool showToolbar = config()->get("ShowToolbar").toBool();
m_ui->toolBar->setVisible(showToolbar);
connect(m_ui->toolBar, SIGNAL(visibilityChanged(bool)), this, SLOT(saveToolbarState(bool)));
connect(m_ui->actionToolbarIconSize16, SIGNAL(triggered()), this, SLOT(setToolbarIconSize16()));
connect(m_ui->actionToolbarIconSize22, SIGNAL(triggered()), this, SLOT(setToolbarIconSize22()));
connect(m_ui->actionToolbarIconSize28, SIGNAL(triggered()), this, SLOT(setToolbarIconSize28()));
m_clearHistoryAction = new QAction("Clear history", m_ui->menuFile);
m_lastDatabasesActions = new QActionGroup(m_ui->menuRecentDatabases);
@ -124,19 +79,13 @@ MainWindow::MainWindow()
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);
//TODO: do not register shortcut on Q_OS_MAC, if this is done automatically??
const QKeySequence seq = !QKeySequence::keyBindings(QKeySequence::Find).isEmpty()
? QKeySequence::Find
: QKeySequence(Qt::CTRL + Qt::Key_F);
connect(new QShortcut(seq, this), SIGNAL(activated()), m_ui->searchField, SLOT(setFocus()));
m_ui->searchField->setContentsMargins(5,0,5,0);
setShortcut(m_ui->actionSearch, QKeySequence::Find, Qt::CTRL + Qt::Key_F);
m_ui->actionEntryNew->setShortcut(Qt::CTRL + 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);
m_ui->actionEntryCopyUsername->setShortcut(Qt::CTRL + Qt::Key_B);
m_ui->actionEntryCopyPassword->setShortcut(Qt::CTRL + Qt::Key_C);
m_ui->actionEntryCopyURL->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_U);
setShortcut(m_ui->actionEntryAutoType, QKeySequence::Paste, Qt::CTRL + Qt::Key_V);
m_ui->actionEntryOpenUrl->setShortcut(Qt::CTRL + Qt::Key_U);
@ -170,6 +119,8 @@ MainWindow::MainWindow()
m_ui->actionAbout->setIcon(filePath()->icon("actions", "help-about"));
m_ui->actionSearch->setIcon(filePath()->icon("actions", "system-search"));
m_actionMultiplexer.connect(SIGNAL(currentModeChanged(DatabaseWidget::Mode)),
this, SLOT(setMenuActionState(DatabaseWidget::Mode)));
m_actionMultiplexer.connect(SIGNAL(groupChanged()),
@ -249,25 +200,10 @@ MainWindow::MainWindow()
connect(m_ui->actionAbout, SIGNAL(triggered()), SLOT(showAboutDialog()));
m_ui->searchField->setPlaceholderText(tr("Type to search"));
m_ui->searchField->setEnabled(false);
m_ui->toolBar->addWidget(m_ui->mySpacer);
m_ui->toolBar->addWidget(m_ui->searchField);
m_actionMultiplexer.connect(m_ui->searchField, SIGNAL(textChanged(QString)),
SLOT(search(QString)));
QMenu* searchMenu = new QMenu(this);
searchMenu->addAction(m_ui->actionFindCaseSensitive);
searchMenu->addSeparator();
searchMenu->addAction(m_ui->actionFindCurrentGroup);
searchMenu->addAction(m_ui->actionFindRootGroup);
m_ui->searchField->setMenu(searchMenu);
QActionGroup* group = new QActionGroup(this);
group->addAction(m_ui->actionFindCurrentGroup);
group->addAction(m_ui->actionFindRootGroup);
m_actionMultiplexer.connect(m_ui->actionFindCaseSensitive, SIGNAL(toggled(bool)),
SLOT(setCaseSensitiveSearch(bool)));
m_actionMultiplexer.connect(m_ui->actionFindRootGroup, SIGNAL(toggled(bool)),
SLOT(setAllGroupsSearch(bool)));
m_actionMultiplexer.connect(m_ui->actionSearch, SIGNAL(triggered()),
SLOT(openSearch()));
updateTrayIcon();
}
MainWindow::~MainWindow()
@ -294,17 +230,16 @@ void MainWindow::updateCopyAttributesMenu()
return;
}
Entry* entry = dbWidget->entryView()->currentEntry();
if (!entry || !dbWidget->entryView()->isSingleEntrySelected()) {
if (dbWidget->numberOfSelectedEntries() != 1) {
return;
}
QList<QAction*> actions = m_ui->menuEntryCopyAttribute->actions();
for (int i = EntryAttributes::DefaultAttributes.size() + 1; i < actions.size(); i++) {
for (int i = m_countDefaultAttributes; i < actions.size(); i++) {
delete actions[i];
}
Q_FOREACH (const QString& key, entry->attributes()->customKeys()) {
Q_FOREACH (const QString& key, dbWidget->customEntryAttributes()) {
QAction* action = m_ui->menuEntryCopyAttribute->addAction(key);
m_copyAdditionalAttributeActions->addAction(action);
}
@ -320,40 +255,11 @@ void MainWindow::clearLastDatabases()
config()->set("LastDatabases", QVariant());
}
void MainWindow::changeEvent(QEvent *e)
{
QMainWindow::changeEvent(e);
if (e->type() == QEvent::ActivationChange) {
if (isActiveWindow())
m_ui->tabWidget->checkReloadDatabases();
}
}
void MainWindow::openDatabase(const QString& fileName, const QString& pw, const QString& keyFile)
{
m_ui->tabWidget->openDatabase(fileName, pw, keyFile);
}
void MainWindow::updateSearchField(DatabaseWidget* dbWidget)
{
bool enabled = dbWidget != NULL;
m_ui->actionFindCaseSensitive->setChecked(enabled && dbWidget->caseSensitiveSearch());
m_ui->actionFindCurrentGroup->setEnabled(enabled && dbWidget->canChooseSearchScope());
m_ui->actionFindRootGroup->setEnabled(enabled && dbWidget->canChooseSearchScope());
if (enabled && dbWidget->isAllGroupsSearch())
m_ui->actionFindRootGroup->setChecked(true);
else
m_ui->actionFindCurrentGroup->setChecked(true);
m_ui->searchField->setEnabled(enabled);
if (enabled && dbWidget->isInSearchMode())
m_ui->searchField->setText(dbWidget->searchText());
else
m_ui->searchField->clear();
}
void MainWindow::setMenuActionState(DatabaseWidget::Mode mode)
{
bool inDatabaseTabWidget = (m_ui->stackedWidget->currentIndex() == 0);
@ -370,9 +276,9 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode)
switch (mode) {
case DatabaseWidget::ViewMode: {
bool inSearch = dbWidget->isInSearchMode();
bool singleEntrySelected = dbWidget->entryView()->isSingleEntrySelected();
bool entriesSelected = !dbWidget->entryView()->selectionModel()->selectedRows().isEmpty();
bool groupSelected = dbWidget->groupView()->currentGroup();
bool singleEntrySelected = dbWidget->numberOfSelectedEntries() == 1;
bool entriesSelected = dbWidget->numberOfSelectedEntries() > 0;
bool groupSelected = dbWidget->isGroupSelected();
m_ui->actionEntryNew->setEnabled(!inSearch);
m_ui->actionEntryClone->setEnabled(singleEntrySelected && !inSearch);
@ -388,8 +294,9 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode)
m_ui->actionEntryOpenUrl->setEnabled(singleEntrySelected);
m_ui->actionGroupNew->setEnabled(groupSelected);
m_ui->actionGroupEdit->setEnabled(groupSelected);
m_ui->actionGroupDelete->setEnabled(groupSelected && dbWidget->canDeleteCurrentGoup());
updateSearchField(dbWidget);
m_ui->actionGroupDelete->setEnabled(groupSelected && dbWidget->canDeleteCurrentGroup());
// TODO: get checked state from db widget
m_ui->actionSearch->setEnabled(true);
m_ui->actionChangeMasterKey->setEnabled(true);
m_ui->actionChangeDatabaseSettings->setEnabled(true);
m_ui->actionDatabaseSave->setEnabled(true);
@ -398,7 +305,6 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode)
}
case DatabaseWidget::EditMode:
case DatabaseWidget::LockedMode:
case DatabaseWidget::OpenMode:
Q_FOREACH (QAction* action, m_ui->menuEntries->actions()) {
action->setEnabled(false);
}
@ -406,9 +312,14 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode)
Q_FOREACH (QAction* action, m_ui->menuGroups->actions()) {
action->setEnabled(false);
}
m_ui->actionEntryCopyTitle->setEnabled(false);
m_ui->actionEntryCopyUsername->setEnabled(false);
m_ui->actionEntryCopyPassword->setEnabled(false);
m_ui->actionEntryCopyURL->setEnabled(false);
m_ui->actionEntryCopyNotes->setEnabled(false);
m_ui->menuEntryCopyAttribute->setEnabled(false);
updateSearchField();
m_ui->actionSearch->setEnabled(false);
m_ui->actionChangeMasterKey->setEnabled(false);
m_ui->actionChangeDatabaseSettings->setEnabled(false);
m_ui->actionDatabaseSave->setEnabled(false);
@ -427,9 +338,14 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode)
Q_FOREACH (QAction* action, m_ui->menuGroups->actions()) {
action->setEnabled(false);
}
m_ui->actionEntryCopyTitle->setEnabled(false);
m_ui->actionEntryCopyUsername->setEnabled(false);
m_ui->actionEntryCopyPassword->setEnabled(false);
m_ui->actionEntryCopyURL->setEnabled(false);
m_ui->actionEntryCopyNotes->setEnabled(false);
m_ui->menuEntryCopyAttribute->setEnabled(false);
updateSearchField();
m_ui->actionSearch->setEnabled(false);
m_ui->actionChangeMasterKey->setEnabled(false);
m_ui->actionChangeDatabaseSettings->setEnabled(false);
m_ui->actionDatabaseSave->setEnabled(false);
@ -513,15 +429,29 @@ void MainWindow::closeEvent(QCloseEvent* event)
saveWindowInformation();
event->accept();
QApplication::quit();
}
else {
event->ignore();
}
}
void MainWindow::changeEvent(QEvent *event)
{
if ((event->type() == QEvent::WindowStateChange) && isMinimized()
&& isTrayIconEnabled() && config()->get("GUI/MinimizeToTray").toBool())
{
event->ignore();
hide();
}
else {
QMainWindow::changeEvent(event);
}
}
void MainWindow::saveWindowInformation()
{
config()->set("window/Geometry", saveGeometry());
config()->set("GUI/MainWindowGeometry", saveGeometry());
}
bool MainWindow::saveLastDatabases()
@ -551,6 +481,35 @@ bool MainWindow::saveLastDatabases()
return accept;
}
void MainWindow::updateTrayIcon()
{
if (isTrayIconEnabled()) {
if (!m_trayIcon) {
m_trayIcon = new QSystemTrayIcon(filePath()->applicationIcon(), this);
QMenu* menu = new QMenu(this);
QAction* actionToggle = new QAction(tr("Toggle window"), menu);
menu->addAction(actionToggle);
menu->addAction(m_ui->actionQuit);
connect(m_trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
SLOT(trayIconTriggered(QSystemTrayIcon::ActivationReason)));
connect(actionToggle, SIGNAL(triggered()), SLOT(toggleWindow()));
m_trayIcon->setContextMenu(menu);
m_trayIcon->show();
}
}
else {
if (m_trayIcon) {
delete m_trayIcon;
m_trayIcon = Q_NULLPTR;
}
}
}
void MainWindow::showEntryContextMenu(const QPoint& globalPos)
{
m_ui->menuEntries->popup(globalPos);
@ -566,30 +525,6 @@ void MainWindow::saveToolbarState(bool value)
config()->set("ShowToolbar", value);
}
void MainWindow::setToolbarIconSize(int size)
{
config()->set("ToolbarIconSize", size);
m_ui->toolBar->setIconSize(QSize(size, size));
m_ui->actionToolbarIconSize16->setChecked(size == 16);
m_ui->actionToolbarIconSize22->setChecked(size == 22);
m_ui->actionToolbarIconSize28->setChecked(size == 28);
}
void MainWindow::setToolbarIconSize16()
{
setToolbarIconSize(16);
}
void MainWindow::setToolbarIconSize22()
{
setToolbarIconSize(22);
}
void MainWindow::setToolbarIconSize28()
{
setToolbarIconSize(28);
}
void MainWindow::setShortcut(QAction* action, QKeySequence::StandardKey standard, int fallback)
{
if (!QKeySequence::keyBindings(standard).isEmpty()) {
@ -619,4 +554,31 @@ void MainWindow::applySettingsChanges()
else {
m_inactivityTimer->deactivate();
}
updateTrayIcon();
}
void MainWindow::trayIconTriggered(QSystemTrayIcon::ActivationReason reason)
{
if (reason == QSystemTrayIcon::Trigger) {
toggleWindow();
}
}
void MainWindow::toggleWindow()
{
if (QApplication::activeWindow() == this) {
hide();
}
else {
show();
raise();
activateWindow();
}
}
bool MainWindow::isTrayIconEnabled() const
{
return config()->get("GUI/ShowTrayIcon").toBool()
&& QSystemTrayIcon::isSystemTrayAvailable();
}

View File

@ -20,6 +20,7 @@
#include <QActionGroup>
#include <QMainWindow>
#include <QSystemTrayIcon>
#include "core/SignalMultiplexer.h"
#include "gui/DatabaseWidget.h"
@ -43,8 +44,8 @@ public Q_SLOTS:
const QString& keyFile = QString());
protected:
void changeEvent(QEvent *e);
void closeEvent(QCloseEvent* event) Q_DECL_OVERRIDE;
void closeEvent(QCloseEvent* event) Q_DECL_OVERRIDE;
void changeEvent(QEvent* event) Q_DECL_OVERRIDE;
private Q_SLOTS:
void setMenuActionState(DatabaseWidget::Mode mode = DatabaseWidget::None);
@ -62,19 +63,18 @@ private Q_SLOTS:
void saveToolbarState(bool value);
void rememberOpenDatabases(const QString& filePath);
void applySettingsChanges();
void setToolbarIconSize(int size);
void setToolbarIconSize16();
void setToolbarIconSize22();
void setToolbarIconSize28();
void trayIconTriggered(QSystemTrayIcon::ActivationReason reason);
void toggleWindow();
private:
void updateSearchField(DatabaseWidget* dbWidget = NULL);
static void setShortcut(QAction* action, QKeySequence::StandardKey standard, int fallback = 0);
static const QString BaseWindowTitle;
void saveWindowInformation();
bool saveLastDatabases();
void updateTrayIcon();
bool isTrayIconEnabled() const;
const QScopedPointer<Ui::MainWindow> m_ui;
SignalMultiplexer m_actionMultiplexer;
@ -83,6 +83,8 @@ private:
QActionGroup* m_copyAdditionalAttributeActions;
QStringList m_openDatabases;
InactivityTimer* m_inactivityTimer;
int m_countDefaultAttributes;
QSystemTrayIcon* m_trayIcon;
Q_DISABLE_COPY(MainWindow)
};

View File

@ -70,7 +70,7 @@
<x>0</x>
<y>0</y>
<width>800</width>
<height>24</height>
<height>20</height>
</rect>
</property>
<widget class="QMenu" name="menuFile">
@ -148,15 +148,6 @@
<property name="title">
<string>View</string>
</property>
<widget class="QMenu" name="menuToolbarIconSize">
<property name="title">
<string>Toolbar &amp;Icon Size</string>
</property>
<addaction name="actionToolbarIconSize16"/>
<addaction name="actionToolbarIconSize22"/>
<addaction name="actionToolbarIconSize28"/>
</widget>
<addaction name="menuToolbarIconSize"/>
</widget>
<addaction name="menuFile"/>
<addaction name="menuEntries"/>
@ -186,17 +177,7 @@
<addaction name="actionEntryCopyPassword"/>
<addaction name="separator"/>
<addaction name="actionLockDatabases"/>
<widget class="QWidget" name="mySpacer">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
<widget class="QWidget" name="searchPanel">
<widget class="QSearchField" name="searchField" native="true"/>
</widget>
<addaction name="actionSearch"/>
</widget>
<action name="actionQuit">
<property name="text">
@ -322,6 +303,14 @@
<string>Clone entry</string>
</property>
</action>
<action name="actionSearch">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Find</string>
</property>
</action>
<action name="actionEntryCopyUsername">
<property name="enabled">
<bool>false</bool>
@ -397,54 +386,6 @@
<string>Notes</string>
</property>
</action>
<action name="actionToolbarIconSize16">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>&amp;16x16</string>
</property>
</action>
<action name="actionToolbarIconSize22">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>&amp;22x22</string>
</property>
</action>
<action name="actionToolbarIconSize28">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>2&amp;8x28</string>
</property>
</action>
<action name="actionFindCaseSensitive">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Case Sensitive</string>
</property>
</action>
<action name="actionFindCurrentGroup">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Current Group</string>
</property>
</action>
<action name="actionFindRootGroup">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Root Group</string>
</property>
</action>
</widget>
<customwidgets>
<customwidget>
@ -465,12 +406,6 @@
<header>gui/WelcomeWidget.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>QSearchField</class>
<extends>QWidget</extends>
<header>gui/qocoa/qsearchfield.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>

View File

@ -45,7 +45,13 @@ void PasswordComboBox::setEcho(bool echo)
// add fake item to show visual indication that a popup is available
addItem("");
setStyleSheet("QComboBox { font-family: monospace; }");
#ifdef Q_OS_MAC
// Qt on Mac OS doesn't seem to know the generic monospace family (tested with 4.8.6)
setStyleSheet("QComboBox { font-family: monospace,Menlo,Monaco; }");
#else
setStyleSheet("QComboBox { font-family: monospace,Courier; }");
#endif
}
else {
// clear items so the combobox indicates that no popup menu is available

View File

@ -56,7 +56,12 @@ void PasswordEdit::updateStylesheet()
QString stylesheet("QLineEdit { ");
if (echoMode() == QLineEdit::Normal) {
#ifdef Q_OS_MAC
// Qt on Mac OS doesn't seem to know the generic monospace family (tested with 4.8.6)
stylesheet.append("font-family: monospace,Menlo,Monaco; ");
#else
stylesheet.append("font-family: monospace; ");
#endif
}
if (m_basePasswordEdit && !passwordsEqual()) {

View File

@ -100,7 +100,7 @@
<string>Upper Case Letters</string>
</property>
<property name="text">
<string>A-Z</string>
<string notr="true">A-Z</string>
</property>
<property name="checkable">
<bool>true</bool>
@ -116,7 +116,7 @@
<string>Lower Case Letters</string>
</property>
<property name="text">
<string>a-z</string>
<string notr="true">a-z</string>
</property>
<property name="checkable">
<bool>true</bool>
@ -132,7 +132,7 @@
<string>Numbers</string>
</property>
<property name="text">
<string>0-9</string>
<string notr="true">0-9</string>
</property>
<property name="checkable">
<bool>true</bool>
@ -148,7 +148,7 @@
<string>Special Characters</string>
</property>
<property name="text">
<string>/*_&amp; ...</string>
<string notr="true">/*_&amp; ...</string>
</property>
<property name="checkable">
<bool>true</bool>

View File

@ -7,31 +7,119 @@
<x>0</x>
<y>0</y>
<width>630</width>
<height>16</height>
<height>87</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout">
<property name="margin">
<property name="leftMargin">
<number>0</number>
</property>
<item row="0" column="0" colspan="2">
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item row="0" column="1">
<widget class="LineEdit" name="searchEdit"/>
</item>
<item row="0" column="0">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QToolButton" name="closeSearchButton">
<property name="focusPolicy">
<enum>Qt::ClickFocus</enum>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="searchResults"/>
<widget class="QLabel" name="label">
<property name="text">
<string>Find:</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="1">
<widget class="QWidget" name="optionsWidget" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QCheckBox" name="caseSensitiveCheckBox">
<property name="text">
<string>Case sensitive</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="searchCurrentRadioButton">
<property name="text">
<string>Current group</string>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="searchRootRadioButton">
<property name="text">
<string>Root group</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>255</width>
<height>1</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>LineEdit</class>
<extends>QLineEdit</extends>
<header>gui/LineEdit.h</header>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>closeSearchButton</tabstop>
<tabstop>searchEdit</tabstop>
<tabstop>caseSensitiveCheckBox</tabstop>
<tabstop>searchCurrentRadioButton</tabstop>
<tabstop>searchRootRadioButton</tabstop>
</tabstops>
<resources/>
<connections/>

View File

@ -21,27 +21,7 @@
#include "autotype/AutoType.h"
#include "core/Config.h"
class SettingsWidget::ExtraPage
{
public:
ExtraPage(ISettingsPage* page, QWidget* widget): settingsPage(page), widget(widget)
{}
void loadSettings() const
{
settingsPage->loadSettings(widget);
}
void saveSettings() const
{
settingsPage->saveSettings(widget);
}
private:
QSharedPointer<ISettingsPage> settingsPage;
QWidget* widget;
};
#include "core/Translator.h"
SettingsWidget::SettingsWidget(QWidget* parent)
: EditWidget(parent)
@ -67,6 +47,8 @@ SettingsWidget::SettingsWidget(QWidget* parent)
connect(m_generalUi->autoSaveAfterEveryChangeCheckBox, SIGNAL(toggled(bool)),
this, SLOT(enableAutoSaveOnExit(bool)));
connect(m_generalUi->systrayShowCheckBox, SIGNAL(toggled(bool)),
m_generalUi->systrayMinimizeToTrayCheckBox, SLOT(setEnabled(bool)));
connect(m_secUi->clearClipboardCheckBox, SIGNAL(toggled(bool)),
m_secUi->clearClipboardSpinBox, SLOT(setEnabled(bool)));
@ -78,25 +60,29 @@ SettingsWidget::~SettingsWidget()
{
}
void SettingsWidget::addSettingsPage(ISettingsPage *page)
{
QWidget * widget = page->createWidget();
widget->setParent(this);
m_extraPages.append(ExtraPage(page, widget));
add(page->name(), widget);
}
void SettingsWidget::loadSettings()
{
m_generalUi->rememberLastDatabasesCheckBox->setChecked(config()->get("RememberLastDatabases").toBool());
m_generalUi->openPreviousDatabasesOnStartupCheckBox->setChecked(
config()->get("OpenPreviousDatabasesOnStartup").toBool());
m_generalUi->modifiedExpandedChangedCheckBox->setChecked(config()->get("ModifiedOnExpandedStateChanges").toBool());
m_generalUi->autoSaveAfterEveryChangeCheckBox->setChecked(config()->get("AutoSaveAfterEveryChange").toBool());
m_generalUi->autoSaveOnExitCheckBox->setChecked(config()->get("AutoSaveOnExit").toBool());
m_generalUi->minimizeOnCopyCheckBox->setChecked(config()->get("MinimizeOnCopy").toBool());
m_generalUi->useGroupIconOnEntryCreationCheckBox->setChecked(config()->get("UseGroupIconOnEntryCreation").toBool());
m_generalUi->reloadBehavior->setCurrentIndex(config()->get("ReloadBehavior").toInt());
m_generalUi->autoTypeEntryTitleMatchCheckBox->setChecked(config()->get("AutoTypeEntryTitleMatch").toBool());
m_generalUi->languageComboBox->clear();
QList<QPair<QString, QString> > languages = Translator::availableLanguages();
for (int i = 0; i < languages.size(); i++) {
m_generalUi->languageComboBox->addItem(languages[i].second, languages[i].first);
}
int defaultIndex = m_generalUi->languageComboBox->findData(config()->get("GUI/Language"));
if (defaultIndex > 0) {
m_generalUi->languageComboBox->setCurrentIndex(defaultIndex);
}
m_generalUi->systrayShowCheckBox->setChecked(config()->get("GUI/ShowTrayIcon").toBool());
m_generalUi->systrayMinimizeToTrayCheckBox->setChecked(config()->get("GUI/MinimizeToTray").toBool());
if (autoType()->isAvailable()) {
m_globalAutoTypeKey = static_cast<Qt::Key>(config()->get("GlobalAutoTypeKey").toInt());
@ -115,8 +101,7 @@ void SettingsWidget::loadSettings()
m_secUi->passwordCleartextCheckBox->setChecked(config()->get("security/passwordscleartext").toBool());
m_secUi->autoTypeAskCheckBox->setChecked(config()->get("security/autotypeask").toBool());
Q_FOREACH (const ExtraPage& page, m_extraPages)
page.loadSettings();
setCurrentRow(0);
}
@ -125,15 +110,20 @@ void SettingsWidget::saveSettings()
config()->set("RememberLastDatabases", m_generalUi->rememberLastDatabasesCheckBox->isChecked());
config()->set("OpenPreviousDatabasesOnStartup",
m_generalUi->openPreviousDatabasesOnStartupCheckBox->isChecked());
config()->set("ModifiedOnExpandedStateChanges",
m_generalUi->modifiedExpandedChangedCheckBox->isChecked());
config()->set("AutoSaveAfterEveryChange",
m_generalUi->autoSaveAfterEveryChangeCheckBox->isChecked());
config()->set("AutoSaveOnExit", m_generalUi->autoSaveOnExitCheckBox->isChecked());
config()->set("MinimizeOnCopy", m_generalUi->minimizeOnCopyCheckBox->isChecked());
config()->set("UseGroupIconOnEntryCreation",
m_generalUi->useGroupIconOnEntryCreationCheckBox->isChecked());
config()->set("ReloadBehavior", m_generalUi->reloadBehavior->currentIndex());
config()->set("AutoTypeEntryTitleMatch",
m_generalUi->autoTypeEntryTitleMatchCheckBox->isChecked());
int currentLangIndex = m_generalUi->languageComboBox->currentIndex();
config()->set("GUI/Language", m_generalUi->languageComboBox->itemData(currentLangIndex).toString());
config()->set("GUI/ShowTrayIcon", m_generalUi->systrayShowCheckBox->isChecked());
config()->set("GUI/MinimizeToTray", m_generalUi->systrayMinimizeToTrayCheckBox->isChecked());
if (autoType()->isAvailable()) {
config()->set("GlobalAutoTypeKey", m_generalUi->autoTypeShortcutWidget->key());
config()->set("GlobalAutoTypeModifiers",
@ -148,8 +138,6 @@ void SettingsWidget::saveSettings()
config()->set("security/passwordscleartext", m_secUi->passwordCleartextCheckBox->isChecked());
config()->set("security/autotypeask", m_secUi->autoTypeAskCheckBox->isChecked());
Q_FOREACH (const ExtraPage& page, m_extraPages)
page.saveSettings();
Q_EMIT editFinished(true);
}

View File

@ -59,8 +59,6 @@ private:
const QScopedPointer<Ui::SettingsWidgetGeneral> m_generalUi;
Qt::Key m_globalAutoTypeKey;
Qt::KeyboardModifiers m_globalAutoTypeModifiers;
class ExtraPage;
QList<ExtraPage> m_extraPages;
};
#endif // KEEPASSX_SETTINGSWIDGET_H

View File

@ -6,55 +6,24 @@
<rect>
<x>0</x>
<y>0</y>
<width>481</width>
<height>185</height>
<width>456</width>
<height>313</height>
</rect>
</property>
<layout class="QFormLayout" name="formLayout">
<property name="fieldGrowthPolicy">
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
</property>
<item row="0" column="0">
<widget class="QCheckBox" name="rememberLastDatabasesCheckBox">
<property name="text">
<string>Remember recent databases</string>
<string>Remember last databases</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QCheckBox" name="modifiedExpandedChangedCheckBox">
<property name="text">
<string>Mark as modified on expanded state changes</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QCheckBox" name="autoSaveAfterEveryChangeCheckBox">
<property name="text">
<string>Automatically save after every change</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QCheckBox" name="autoSaveOnExitCheckBox">
<property name="text">
<string>Automatically save on exit</string>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="autoTypeShortcutLabel">
<property name="text">
<string>Global Auto-Type shortcut</string>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="ShortcutWidget" name="autoTypeShortcutWidget"/>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="openPreviousDatabasesOnStartupCheckBox">
<property name="text">
@ -62,46 +31,78 @@
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="label_2">
<item row="2" column="0">
<widget class="QCheckBox" name="autoSaveOnExitCheckBox">
<property name="text">
<string>When database files are externally modified</string>
<string>Automatically save on exit</string>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QComboBox" name="reloadBehavior">
<item>
<property name="text">
<string>Always ask</string>
</property>
</item>
<item>
<property name="text">
<string>Reload unmodified databases</string>
</property>
</item>
<item>
<property name="text">
<string>Ignore modifications</string>
</property>
</item>
<item row="3" column="0">
<widget class="QCheckBox" name="autoSaveAfterEveryChangeCheckBox">
<property name="text">
<string>Automatically save after every change</string>
</property>
</widget>
</item>
<item row="5" column="0">
<item row="4" column="0">
<widget class="QCheckBox" name="minimizeOnCopyCheckBox">
<property name="text">
<string>Minimize when copying to clipboard</string>
</property>
</widget>
</item>
<item row="6" column="0">
<item row="5" column="0">
<widget class="QCheckBox" name="useGroupIconOnEntryCreationCheckBox">
<property name="text">
<string>Use group icon on entry creation</string>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="autoTypeShortcutLabel">
<property name="text">
<string>Global Auto-Type shortcut</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="ShortcutWidget" name="autoTypeShortcutWidget"/>
</item>
<item row="7" column="0">
<widget class="QCheckBox" name="autoTypeEntryTitleMatchCheckBox">
<property name="text">
<string>Use entry title to match windows for global auto-type</string>
</property>
</widget>
</item>
<item row="8" column="0">
<widget class="QLabel" name="languageLabel">
<property name="text">
<string>Language</string>
</property>
</widget>
</item>
<item row="8" column="1">
<widget class="QComboBox" name="languageComboBox"/>
</item>
<item row="9" column="0">
<widget class="QCheckBox" name="systrayShowCheckBox">
<property name="text">
<string>Show a system tray icon</string>
</property>
</widget>
</item>
<item row="10" column="0">
<widget class="QCheckBox" name="systrayMinimizeToTrayCheckBox">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Hide window to system tray when minimized</string>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
@ -114,7 +115,6 @@
<tabstops>
<tabstop>rememberLastDatabasesCheckBox</tabstop>
<tabstop>openPreviousDatabasesOnStartupCheckBox</tabstop>
<tabstop>modifiedExpandedChangedCheckBox</tabstop>
<tabstop>autoSaveOnExitCheckBox</tabstop>
<tabstop>autoSaveAfterEveryChangeCheckBox</tabstop>
<tabstop>minimizeOnCopyCheckBox</tabstop>

View File

@ -25,6 +25,7 @@
#include <QStackedLayout>
#include <QMenu>
#include <QSortFilterProxyModel>
#include <QTemporaryFile>
#include "core/Config.h"
#include "core/Database.h"
@ -107,7 +108,11 @@ void EditEntryWidget::setupAdvanced()
m_attachmentsModel->setEntryAttachments(m_entryAttachments);
m_advancedUi->attachmentsView->setModel(m_attachmentsModel);
connect(m_advancedUi->attachmentsView->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)),
SLOT(updateAttachmentButtonsEnabled(QModelIndex)));
connect(m_advancedUi->attachmentsView, SIGNAL(doubleClicked(QModelIndex)), SLOT(openAttachment(QModelIndex)));
connect(m_advancedUi->saveAttachmentButton, SIGNAL(clicked()), SLOT(saveCurrentAttachment()));
connect(m_advancedUi->openAttachmentButton, SIGNAL(clicked()), SLOT(openCurrentAttachment()));
connect(m_advancedUi->addAttachmentButton, SIGNAL(clicked()), SLOT(insertAttachment()));
connect(m_advancedUi->removeAttachmentButton, SIGNAL(clicked()), SLOT(removeCurrentAttachment()));
@ -232,6 +237,15 @@ void EditEntryWidget::useExpiryPreset(QAction* action)
m_mainUi->expireDatePicker->setDateTime(expiryDateTime);
}
void EditEntryWidget::updateAttachmentButtonsEnabled(const QModelIndex& current)
{
bool enable = current.isValid();
m_advancedUi->saveAttachmentButton->setEnabled(enable);
m_advancedUi->openAttachmentButton->setEnabled(enable);
m_advancedUi->removeAttachmentButton->setEnabled(enable && !m_history);
}
QString EditEntryWidget::entryTitle() const
{
if (m_entry) {
@ -281,7 +295,7 @@ void EditEntryWidget::setForms(const Entry* entry, bool restore)
m_mainUi->tooglePasswordGeneratorButton->setChecked(false);
m_mainUi->passwordGenerator->reset();
m_advancedUi->addAttachmentButton->setEnabled(!m_history);
m_advancedUi->removeAttachmentButton->setEnabled(!m_history);
updateAttachmentButtonsEnabled(m_advancedUi->attachmentsView->currentIndex());
m_advancedUi->addAttributeButton->setEnabled(!m_history);
m_advancedUi->editAttributeButton->setEnabled(false);
m_advancedUi->removeAttributeButton->setEnabled(false);
@ -591,14 +605,14 @@ void EditEntryWidget::insertAttachment()
QFile file(filename);
if (!file.open(QIODevice::ReadOnly)) {
MessageBox::warning(this, tr("Error"),
tr("Unable to open file:\n").append(file.errorString()));
tr("Unable to open file").append(":\n").append(file.errorString()));
return;
}
QByteArray data;
if (!Tools::readAllFromDevice(&file, data)) {
MessageBox::warning(this, tr("Error"),
tr("Unable to open file:\n").append(file.errorString()));
tr("Unable to open file").append(":\n").append(file.errorString()));
return;
}
@ -637,6 +651,42 @@ void EditEntryWidget::saveCurrentAttachment()
}
}
void EditEntryWidget::openAttachment(const QModelIndex& index)
{
if (!index.isValid()) {
Q_ASSERT(false);
return;
}
QString filename = m_attachmentsModel->keyByIndex(index);
QByteArray attachmentData = m_entryAttachments->value(filename);
// tmp file will be removed once the database (or the application) has been closed
QString tmpFileTemplate = QDir::temp().absoluteFilePath(QString("XXXXXX.").append(filename));
QTemporaryFile* file = new QTemporaryFile(tmpFileTemplate, this);
if (!file->open()) {
MessageBox::warning(this, tr("Error"),
tr("Unable to save the attachment:\n").append(file->errorString()));
return;
}
if (file->write(attachmentData) != attachmentData.size()) {
MessageBox::warning(this, tr("Error"),
tr("Unable to save the attachment:\n").append(file->errorString()));
return;
}
QDesktopServices::openUrl(QUrl::fromLocalFile(file->fileName()));
}
void EditEntryWidget::openCurrentAttachment()
{
QModelIndex index = m_advancedUi->attachmentsView->currentIndex();
openAttachment(index);
}
void EditEntryWidget::removeCurrentAttachment()
{
Q_ASSERT(!m_history);
@ -784,13 +834,13 @@ QMenu* EditEntryWidget::createPresetsMenu()
QMenu* expirePresetsMenu = new QMenu(this);
expirePresetsMenu->addAction(tr("Tomorrow"))->setData(QVariant::fromValue(TimeDelta::fromDays(1)));
expirePresetsMenu->addSeparator();
expirePresetsMenu->addAction(tr("1 week"))->setData(QVariant::fromValue(TimeDelta::fromDays(7)));
expirePresetsMenu->addAction(tr("2 weeks"))->setData(QVariant::fromValue(TimeDelta::fromDays(14)));
expirePresetsMenu->addAction(tr("3 weeks"))->setData(QVariant::fromValue(TimeDelta::fromDays(21)));
expirePresetsMenu->addAction(tr("%n week(s)", 0, 1))->setData(QVariant::fromValue(TimeDelta::fromDays(7)));
expirePresetsMenu->addAction(tr("%n week(s)", 0, 2))->setData(QVariant::fromValue(TimeDelta::fromDays(14)));
expirePresetsMenu->addAction(tr("%n week(s)", 0, 3))->setData(QVariant::fromValue(TimeDelta::fromDays(21)));
expirePresetsMenu->addSeparator();
expirePresetsMenu->addAction(tr("1 month"))->setData(QVariant::fromValue(TimeDelta::fromMonths(1)));
expirePresetsMenu->addAction(tr("3 months"))->setData(QVariant::fromValue(TimeDelta::fromMonths(3)));
expirePresetsMenu->addAction(tr("6 months"))->setData(QVariant::fromValue(TimeDelta::fromMonths(6)));
expirePresetsMenu->addAction(tr("%n month(s)", 0, 1))->setData(QVariant::fromValue(TimeDelta::fromMonths(1)));
expirePresetsMenu->addAction(tr("%n month(s)", 0, 3))->setData(QVariant::fromValue(TimeDelta::fromMonths(3)));
expirePresetsMenu->addAction(tr("%n month(s)", 0, 6))->setData(QVariant::fromValue(TimeDelta::fromMonths(6)));
expirePresetsMenu->addSeparator();
expirePresetsMenu->addAction(tr("1 year"))->setData(QVariant::fromValue(TimeDelta::fromYears(1)));
return expirePresetsMenu;

View File

@ -76,6 +76,8 @@ private Q_SLOTS:
void updateCurrentAttribute();
void insertAttachment();
void saveCurrentAttachment();
void openAttachment(const QModelIndex& index);
void openCurrentAttachment();
void removeCurrentAttachment();
void updateAutoTypeEnabled();
void insertAutoTypeAssoc();
@ -91,6 +93,7 @@ private Q_SLOTS:
void histEntryActivated(const QModelIndex& index);
void updateHistoryButtons(const QModelIndex& current, const QModelIndex& previous);
void useExpiryPreset(QAction* action);
void updateAttachmentButtonsEnabled(const QModelIndex& current);
private:
void setupMain();

View File

@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>400</width>
<height>299</height>
<height>315</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
@ -119,13 +119,29 @@
</item>
<item>
<widget class="QPushButton" name="removeAttachmentButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Remove</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="openAttachmentButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Open</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="saveAttachmentButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Save</string>
</property>

View File

@ -17,6 +17,7 @@
#include "EntryView.h"
#include <QHeaderView>
#include <QKeyEvent>
#include "gui/SortFilterHideProxyModel.h"
@ -40,6 +41,7 @@ EntryView::EntryView(QWidget* parent)
setDragEnabled(true);
setSortingEnabled(true);
setSelectionMode(QAbstractItemView::ExtendedSelection);
header()->setDefaultSectionSize(150);
// QAbstractItemView::startDrag() uses this property as the default drag action
setDefaultDropAction(Qt::MoveAction);
@ -62,13 +64,24 @@ void EntryView::keyPressEvent(QKeyEvent* event)
void EntryView::setGroup(Group* group)
{
m_model->setGroup(group);
Q_EMIT entrySelectionChanged();
setFirstEntryActive();
}
void EntryView::setEntryList(const QList<Entry*>& entries)
{
m_model->setEntryList(entries);
Q_EMIT entrySelectionChanged();
setFirstEntryActive();
}
void EntryView::setFirstEntryActive()
{
if(m_model->rowCount() > 0) {
QModelIndex index = m_sortModel->mapToSource(m_sortModel->index(0, 0));
setCurrentEntry(m_model->entryFromIndex(index));
}
else {
Q_EMIT entrySelectionChanged();
}
}
bool EntryView::inEntryListMode()
@ -100,9 +113,9 @@ Entry* EntryView::currentEntry()
}
}
bool EntryView::isSingleEntrySelected()
int EntryView::numberOfSelectedEntries()
{
return (selectionModel()->selectedRows().size() == 1);
return selectionModel()->selectedRows().size();
}
void EntryView::setCurrentEntry(Entry* entry)

View File

@ -37,11 +37,12 @@ public:
explicit EntryView(QWidget* parent = Q_NULLPTR);
void setModel(QAbstractItemModel* model) Q_DECL_OVERRIDE;
Entry* currentEntry();
bool isSingleEntrySelected();
void setCurrentEntry(Entry* entry);
Entry* entryFromIndex(const QModelIndex& index);
void setEntryList(const QList<Entry*>& entries);
bool inEntryListMode();
int numberOfSelectedEntries();
void setFirstEntryActive();
public Q_SLOTS:
void setGroup(Group* group);

View File

@ -25,6 +25,7 @@
#include "core/Database.h"
#include "core/Entry.h"
#include "core/Group.h"
#include "core/EntrySearcher.h"
#include "core/Metadata.h"
#include "core/Uuid.h"
#include "core/PasswordGenerator.h"
@ -190,7 +191,7 @@ QList<Entry*> Service::searchEntries(Database* db, const QString& hostname)
{
QList<Entry*> entries;
if (Group* rootGroup = db->rootGroup())
Q_FOREACH (Entry* entry, rootGroup->search(hostname, Qt::CaseInsensitive)) {
Q_FOREACH (Entry* entry, EntrySearcher().search(hostname, rootGroup, Qt::CaseInsensitive)) {
QString title = entry->title();
QString url = entry->url();

View File

@ -21,9 +21,11 @@
#include "core/Config.h"
#include "core/qcommandlineparser.h"
#include "core/Tools.h"
#include "core/Translator.h"
#include "crypto/Crypto.h"
#include "gui/Application.h"
#include "gui/MainWindow.h"
#include "gui/MessageBox.h"
int main(int argc, char** argv)
{
@ -37,7 +39,16 @@ int main(int argc, char** argv)
// don't set organizationName as that changes the return value of
// QDesktopServices::storageLocation(QDesktopServices::DataLocation)
Crypto::init();
QApplication::setQuitOnLastWindowClosed(false);
if (!Crypto::init()) {
QString error = QCoreApplication::translate("Main",
"Fatal error while testing the cryptographic functions.");
error.append("\n");
error.append(Crypto::errorString());
MessageBox::critical(Q_NULLPTR, QCoreApplication::translate("Main", "KeePassX - Error"), error);
return 1;
}
QCommandLineParser parser;
parser.setApplicationDescription(QCoreApplication::translate("main", "KeePassX - cross-platform password manager"));
@ -66,6 +77,8 @@ int main(int argc, char** argv)
Config::createConfigFromFile(parser.value(configOption));
}
Translator::installTranslator();
#ifdef Q_OS_MAC
// Don't show menu icons on OSX
QApplication::setAttribute(Qt::AA_DontShowIconsInMenus);

View File

@ -13,7 +13,6 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
set(EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR})
include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_SOURCE_DIR}/src)
add_definitions(-DQT_TEST_LIB)
@ -165,6 +164,12 @@ add_unit_test(NAME testqcommandlineparser SOURCES TestQCommandLineParser.cpp MOC
add_unit_test(NAME testrandom SOURCES TestRandom.cpp MOCS TestRandom.h
LIBS ${TEST_LIBRARIES})
add_unit_test(NAME testentrysearcher SOURCES TestEntrySearcher.cpp MOCS TestEntrySearcher.h
LIBS ${TEST_LIBRARIES})
add_unit_test(NAME testexporter SOURCES TestExporter.cpp MOCS TestExporter.h
LIBS ${TEST_LIBRARIES})
if(WITH_GUI_TESTS)
add_subdirectory(gui)
endif(WITH_GUI_TESTS)

View File

@ -21,6 +21,7 @@
#include <QTest>
#include "tests.h"
#include "core/Config.h"
#include "core/FilePath.h"
#include "core/Entry.h"
#include "core/Group.h"
@ -30,11 +31,15 @@
#include "autotype/test/AutoTypeTestInterface.h"
#include "gui/MessageBox.h"
QTEST_GUILESS_MAIN(TestAutoType)
void TestAutoType::initTestCase()
{
Crypto::init();
QVERIFY(Crypto::init());
Config::createTempFileInstance();
AutoType::createTestInstance();
config()->set("AutoTypeEntryTitleMatch", false);
config()->set("security/autotypeask", false);
QPluginLoader loader(filePath()->pluginPath("keepassx-autotype-test"));
loader.setLoadHints(QLibrary::ResolveAllSymbolsHint);
@ -54,12 +59,24 @@ void TestAutoType::init()
m_test->clearActions();
m_db = new Database();
m_dbList.clear();
m_dbList.append(m_db);
m_group = new Group();
m_db->setRootGroup(m_group);
m_entry = new Entry();
m_entry->setGroup(m_group);
m_entry->setUsername("myuser");
m_entry->setPassword("mypass");
m_entry1 = new Entry();
m_entry1->setGroup(m_group);
m_entry1->setUsername("myuser");
m_entry1->setPassword("mypass");
AutoTypeAssociations::Association association;
association.window = "custom window";
association.sequence = "{username}association{password}";
m_entry1->autoTypeAssociations()->add(association);
m_entry2 = new Entry();
m_entry2->setGroup(m_group);
m_entry2->setPassword("myuser");
m_entry2->setTitle("entry title");
}
void TestAutoType::cleanup()
@ -77,7 +94,7 @@ void TestAutoType::testInternal()
void TestAutoType::testAutoTypeWithoutSequence()
{
m_autoType->performAutoType(m_entry, Q_NULLPTR);
m_autoType->performAutoType(m_entry1, Q_NULLPTR);
QCOMPARE(m_test->actionCount(), 14);
QCOMPARE(m_test->actionChars(),
@ -88,42 +105,54 @@ void TestAutoType::testAutoTypeWithoutSequence()
void TestAutoType::testAutoTypeWithSequence()
{
m_autoType->performAutoType(m_entry, Q_NULLPTR, "{Username}abc{PaSsWoRd}");
m_autoType->performAutoType(m_entry1, Q_NULLPTR, "{Username}abc{PaSsWoRd}");
QCOMPARE(m_test->actionCount(), 15);
QCOMPARE(m_test->actionChars(),
QString("%1abc%2")
.arg(m_entry->username())
.arg(m_entry->password()));
.arg(m_entry1->username())
.arg(m_entry1->password()));
}
void TestAutoType::testGlobalAutoTypeWithNoMatch()
{
QList<Database*> dbList;
dbList.append(m_db);
m_test->setActiveWindowTitle("nomatch");
MessageBox::setNextAnswer(QMessageBox::Ok);
m_autoType->performGlobalAutoType(dbList);
m_autoType->performGlobalAutoType(m_dbList);
QCOMPARE(m_test->actionChars(), QString());
}
void TestAutoType::testGlobalAutoTypeWithOneMatch()
{
QList<Database*> dbList;
dbList.append(m_db);
AutoTypeAssociations::Association association;
association.window = "custom window";
association.sequence = "{username}association{password}";
m_entry->autoTypeAssociations()->add(association);
m_test->setActiveWindowTitle("custom window");
m_autoType->performGlobalAutoType(dbList);
m_autoType->performGlobalAutoType(m_dbList);
QCOMPARE(m_test->actionChars(),
QString("%1association%2")
.arg(m_entry->username())
.arg(m_entry->password()));
.arg(m_entry1->username())
.arg(m_entry1->password()));
}
QTEST_GUILESS_MAIN(TestAutoType)
void TestAutoType::testGlobalAutoTypeTitleMatch()
{
config()->set("AutoTypeEntryTitleMatch", true);
m_test->setActiveWindowTitle("An Entry Title!");
m_autoType->performGlobalAutoType(m_dbList);
QCOMPARE(m_test->actionChars(),
QString("%1%2").arg(m_entry2->password(), m_test->keyToString(Qt::Key_Enter)));
}
void TestAutoType::testGlobalAutoTypeTitleMatchDisabled()
{
config()->set("AutoTypeEntryTitleMatch", false);
m_test->setActiveWindowTitle("An Entry Title!");
MessageBox::setNextAnswer(QMessageBox::Ok);
m_autoType->performGlobalAutoType(m_dbList);
QCOMPARE(m_test->actionChars(), QString());
}

View File

@ -41,14 +41,18 @@ private Q_SLOTS:
void testAutoTypeWithSequence();
void testGlobalAutoTypeWithNoMatch();
void testGlobalAutoTypeWithOneMatch();
void testGlobalAutoTypeTitleMatch();
void testGlobalAutoTypeTitleMatchDisabled();
private:
AutoTypePlatformInterface* m_platform;
AutoTypeTestInterface* m_test;
AutoType* m_autoType;
Database* m_db;
QList<Database*> m_dbList;
Group* m_group;
Entry* m_entry;
Entry* m_entry1;
Entry* m_entry2;
};
#endif // KEEPASSX_TESTAUTOTYPE_H

View File

@ -23,15 +23,17 @@
#include "crypto/Crypto.h"
#include "crypto/CryptoHash.h"
QTEST_GUILESS_MAIN(TestCryptoHash)
void TestCryptoHash::initTestCase()
{
Crypto::init();
QVERIFY(Crypto::init());
}
void TestCryptoHash::test()
{
// TODO: move somewhere else
QVERIFY(Crypto::selfTest());
QVERIFY(Crypto::backendSelfTest());
CryptoHash cryptoHash1(CryptoHash::Sha256);
QCOMPARE(cryptoHash1.result(),
@ -47,5 +49,3 @@ void TestCryptoHash::test()
QCOMPARE(cryptoHash3.result(),
QByteArray::fromHex("0b56e5f65263e747af4a833bd7dd7ad26a64d7a4de7c68e52364893dca0766b4"));
}
QTEST_GUILESS_MAIN(TestCryptoHash)

View File

@ -26,9 +26,11 @@
#include "format/KeePass2XmlReader.h"
#include "config-keepassx-tests.h"
QTEST_GUILESS_MAIN(TestDeletedObjects)
void TestDeletedObjects::initTestCase()
{
Crypto::init();
QVERIFY(Crypto::init());
}
void TestDeletedObjects::createAndDelete(Database* db, int delObjectsSize)
@ -88,6 +90,7 @@ void TestDeletedObjects::createAndDelete(Database* db, int delObjectsSize)
void TestDeletedObjects::testDeletedObjectsFromFile()
{
KeePass2XmlReader reader;
reader.setStrictMode(true);
QString xmlFile = QString(KEEPASSX_TEST_DATA_DIR).append("/NewDatabase.xml");
Database* db = reader.readDatabase(xmlFile);
@ -158,5 +161,3 @@ void TestDeletedObjects::testDatabaseChange()
delete db;
delete db2;
}
QTEST_GUILESS_MAIN(TestDeletedObjects)

View File

@ -23,9 +23,11 @@
#include "core/Entry.h"
#include "crypto/Crypto.h"
QTEST_GUILESS_MAIN(TestEntry)
void TestEntry::initTestCase()
{
Crypto::init();
QVERIFY(Crypto::init());
}
void TestEntry::testHistoryItemDeletion()
@ -121,5 +123,3 @@ void TestEntry::testClone()
QCOMPARE(entryCloneHistory->historyItems().first()->title(), QString("Original Title"));
QCOMPARE(entryCloneHistory->timeInfo().creationTime(), entryOrg->timeInfo().creationTime());
}
QTEST_GUILESS_MAIN(TestEntry)

View File

@ -33,10 +33,12 @@
#include "gui/entry/EntryAttachmentsModel.h"
#include "gui/entry/EntryAttributesModel.h"
QTEST_GUILESS_MAIN(TestEntryModel)
void TestEntryModel::initTestCase()
{
qRegisterMetaType<QModelIndex>("QModelIndex");
Crypto::init();
QVERIFY(Crypto::init());
}
void TestEntryModel::test()
@ -341,5 +343,3 @@ void TestEntryModel::testDatabaseDelete()
delete modelTest;
delete model;
}
QTEST_GUILESS_MAIN(TestEntryModel)

144
tests/TestEntrySearcher.cpp Normal file
View File

@ -0,0 +1,144 @@
/*
* Copyright (C) 2014 Florian Geyer <blueice@fobos.de>
*
* 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 "TestEntrySearcher.h"
#include <QTest>
#include "tests.h"
QTEST_GUILESS_MAIN(TestEntrySearcher)
void TestEntrySearcher::initTestCase()
{
m_groupRoot = new Group();
}
void TestEntrySearcher::cleanupTestCase()
{
delete m_groupRoot;
}
void TestEntrySearcher::testSearch()
{
Group* group1 = new Group();
Group* group2 = new Group();
Group* group3 = new Group();
group1->setParent(m_groupRoot);
group2->setParent(m_groupRoot);
group3->setParent(m_groupRoot);
Group* group11 = new Group();
group11->setParent(group1);
Group* group21 = new Group();
Group* group211 = new Group();
Group* group2111 = new Group();
group21->setParent(group2);
group211->setParent(group21);
group2111->setParent(group211);
group1->setSearchingEnabled(Group::Disable);
group11->setSearchingEnabled(Group::Enable);
Entry* eRoot = new Entry();
eRoot->setNotes("test search term test");
eRoot->setGroup(m_groupRoot);
Entry* eRoot2 = new Entry();
eRoot2->setNotes("test term test");
eRoot2->setGroup(m_groupRoot);
Entry* e1 = new Entry();
e1->setNotes("test search term test");
e1->setGroup(group1);
Entry* e11 = new Entry();
e11->setNotes("test search term test");
e11->setGroup(group11);
Entry* e2111 = new Entry();
e2111->setNotes("test search term test");
e2111->setGroup(group2111);
Entry* e2111b = new Entry();
e2111b->setNotes("test search test");
e2111b->setGroup(group2111);
Entry* e3 = new Entry();
e3->setNotes("test search term test");
e3->setGroup(group3);
Entry* e3b = new Entry();
e3b->setNotes("test search test");
e3b->setGroup(group3);
m_searchResult = m_entrySearcher.search("search term", m_groupRoot, Qt::CaseInsensitive);
QCOMPARE(m_searchResult.count(), 3);
m_searchResult = m_entrySearcher.search("search term", group211, Qt::CaseInsensitive);
QCOMPARE(m_searchResult.count(), 1);
m_searchResult = m_entrySearcher.search("search term", group11, Qt::CaseInsensitive);
QCOMPARE(m_searchResult.count(), 1);
m_searchResult = m_entrySearcher.search("search term", group1, Qt::CaseInsensitive);
QCOMPARE(m_searchResult.count(), 0);
}
void TestEntrySearcher::testAndConcatenationInSearch()
{
Entry* entry = new Entry();
entry->setNotes("abc def ghi");
entry->setTitle("jkl");
entry->setGroup(m_groupRoot);
m_searchResult = m_entrySearcher.search("", m_groupRoot, Qt::CaseInsensitive);
QCOMPARE(m_searchResult.count(), 1);
m_searchResult = m_entrySearcher.search("def", m_groupRoot, Qt::CaseInsensitive);
QCOMPARE(m_searchResult.count(), 1);
m_searchResult = m_entrySearcher.search(" abc ghi ", m_groupRoot, Qt::CaseInsensitive);
QCOMPARE(m_searchResult.count(), 1);
m_searchResult = m_entrySearcher.search("ghi ef", m_groupRoot, Qt::CaseInsensitive);
QCOMPARE(m_searchResult.count(), 1);
m_searchResult = m_entrySearcher.search("abc ef xyz", m_groupRoot, Qt::CaseInsensitive);
QCOMPARE(m_searchResult.count(), 0);
m_searchResult = m_entrySearcher.search("abc kl", m_groupRoot, Qt::CaseInsensitive);
QCOMPARE(m_searchResult.count(), 1);
}
void TestEntrySearcher::testAllAttributesAreSearched()
{
Entry* entry = new Entry();
entry->setGroup(m_groupRoot);
entry->setTitle("testTitle");
entry->setUsername("testUsername");
entry->setUrl("testUrl");
entry->setNotes("testNote");
m_searchResult = m_entrySearcher.search("testTitle testUsername testUrl testNote", m_groupRoot, Qt::CaseInsensitive);
QCOMPARE(m_searchResult.count(), 1);
}

45
tests/TestEntrySearcher.h Normal file
View File

@ -0,0 +1,45 @@
/*
* Copyright (C) 2014 Florian Geyer <blueice@fobos.de>
*
* 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 KEEPASSX_TESTENTRYSEARCHER_H
#define KEEPASSX_TESTENTRYSEARCHER_H
#include <QObject>
#include "core/EntrySearcher.h"
#include "core/Group.h"
class TestEntrySearcher : public QObject
{
Q_OBJECT
private Q_SLOTS:
void initTestCase();
void cleanupTestCase();
void testAndConcatenationInSearch();
void testSearch();
void testAllAttributesAreSearched();
private:
Group* m_groupRoot;
EntrySearcher m_entrySearcher;
QList<Entry*> m_searchResult;
};
#endif // KEEPASSX_TESTENTRYSEARCHER_H

82
tests/TestExporter.cpp Normal file
View File

@ -0,0 +1,82 @@
/*
* Copyright (C) 2014 Felix Geyer <debfx@fobos.de>
* Copyright (C) 2014 Florian Geyer <blueice@fobos.de>
*
* 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 "TestExporter.h"
#include <QTest>
#include "tests.h"
#include "core/ToDbExporter.h"
#include "core/Group.h"
#include "core/Metadata.h"
#include "crypto/Crypto.h"
QTEST_GUILESS_MAIN(TestExporter)
void TestExporter::initTestCase()
{
QVERIFY(Crypto::init());
}
void TestExporter::testToDbExporter()
{
QImage iconImage(1, 1, QImage::Format_RGB32);
iconImage.setPixel(0, 0, qRgb(1, 2, 3));
Uuid iconUuid = Uuid::random();
QImage iconUnusedImage(1, 1, QImage::Format_RGB32);
iconUnusedImage.setPixel(0, 0, qRgb(1, 2, 3));
Uuid iconUnusedUuid = Uuid::random();
Database* dbOrg = new Database();
Group* groupOrg = new Group();
groupOrg->setParent(dbOrg->rootGroup());
groupOrg->setName("GTEST");
Entry* entryOrg = new Entry();
entryOrg->setGroup(groupOrg);
entryOrg->setTitle("ETEST");
dbOrg->metadata()->addCustomIcon(iconUuid, iconImage);
dbOrg->metadata()->addCustomIcon(iconUnusedUuid, iconUnusedImage);
entryOrg->setIcon(iconUuid);
entryOrg->beginUpdate();
entryOrg->setIcon(Entry::DefaultIconNumber);
entryOrg->endUpdate();
Database* dbExp = ToDbExporter().exportGroup(groupOrg);
QCOMPARE(dbExp->rootGroup()->children().size(), 1);
Group* groupExp = dbExp->rootGroup()->children().first();
QVERIFY(groupExp != groupOrg);
QCOMPARE(groupExp->name(), groupOrg->name());
QCOMPARE(groupExp->entries().size(), 1);
Entry* entryExp = groupExp->entries().first();
QCOMPARE(entryExp->title(), entryOrg->title());
QCOMPARE(dbExp->metadata()->customIcons().size(), 1);
QVERIFY(dbExp->metadata()->containsCustomIcon(iconUuid));
QCOMPARE(entryExp->iconNumber(), entryOrg->iconNumber());
QCOMPARE(entryExp->historyItems().size(), 1);
QCOMPARE(entryExp->historyItems().first()->iconUuid(), iconUuid);
delete dbOrg;
delete dbExp;
}

33
tests/TestExporter.h Normal file
View File

@ -0,0 +1,33 @@
/*
* Copyright (C) 2014 Felix Geyer <debfx@fobos.de>
* Copyright (C) 2014 Florian Geyer <blueice@fobos.de>
*
* 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 KEEPASSX_TESTEXPORTER_H
#define KEEPASSX_TESTEXPORTER_H
#include <QObject>
class TestExporter : public QObject
{
Q_OBJECT
private Q_SLOTS:
void initTestCase();
void testToDbExporter();
};
#endif // KEEPASSX_TESTEXPORTER_H

View File

@ -27,11 +27,13 @@
#include "core/Metadata.h"
#include "crypto/Crypto.h"
QTEST_GUILESS_MAIN(TestGroup)
void TestGroup::initTestCase()
{
qRegisterMetaType<Entry*>("Entry*");
qRegisterMetaType<Group*>("Group*");
Crypto::init();
QVERIFY(Crypto::init());
}
void TestGroup::testParenting()
@ -334,102 +336,6 @@ void TestGroup::testCopyCustomIcon()
delete dbTarget;
}
void TestGroup::testSearch()
{
Group* groupRoot = new Group();
Group* group1 = new Group();
Group* group2 = new Group();
Group* group3 = new Group();
group1->setParent(groupRoot);
group2->setParent(groupRoot);
group3->setParent(groupRoot);
Group* group11 = new Group();
group11->setParent(group1);
Group* group21 = new Group();
Group* group211 = new Group();
Group* group2111 = new Group();
group21->setParent(group2);
group211->setParent(group21);
group2111->setParent(group211);
group1->setSearchingEnabled(Group::Disable);
group11->setSearchingEnabled(Group::Enable);
Entry* eRoot = new Entry();
eRoot->setNotes("test search term test");
eRoot->setGroup(groupRoot);
Entry* eRoot2 = new Entry();
eRoot2->setNotes("test term test");
eRoot2->setGroup(groupRoot);
Entry* e1 = new Entry();
e1->setNotes("test search term test");
e1->setGroup(group1);
Entry* e2111 = new Entry();
e2111->setNotes("test search term test");
e2111->setGroup(group2111);
Entry* e2111b = new Entry();
e2111b->setNotes("test search test");
e2111b->setGroup(group2111);
Entry* e3 = new Entry();
e3->setNotes("test search term test");
e3->setGroup(group3);
Entry* e3b = new Entry();
e3b->setNotes("test search test");
e3b->setGroup(group3);
QList<Entry*> searchResult;
searchResult = groupRoot->search("search term", Qt::CaseInsensitive);
QCOMPARE(searchResult.count(), 3);
searchResult = group211->search("search term", Qt::CaseInsensitive);
QCOMPARE(searchResult.count(), 1);
delete groupRoot;
}
void TestGroup::testAndConcatenationInSearch()
{
Group* group = new Group();
Entry* entry = new Entry();
entry->setNotes("abc def ghi");
entry->setTitle("jkl");
entry->setGroup(group);
QList<Entry*> searchResult;
searchResult = group->search("", Qt::CaseInsensitive);
QCOMPARE(searchResult.count(), 1);
searchResult = group->search("def", Qt::CaseInsensitive);
QCOMPARE(searchResult.count(), 1);
searchResult = group->search(" abc ghi ", Qt::CaseInsensitive);
QCOMPARE(searchResult.count(), 1);
searchResult = group->search("ghi ef", Qt::CaseInsensitive);
QCOMPARE(searchResult.count(), 1);
searchResult = group->search("abc ef xyz", Qt::CaseInsensitive);
QCOMPARE(searchResult.count(), 0);
searchResult = group->search("abc kl", Qt::CaseInsensitive);
QCOMPARE(searchResult.count(), 1);
delete group;
}
void TestGroup::testClone()
{
Database* db = new Database();
@ -536,49 +442,3 @@ void TestGroup::testCopyCustomIcons()
QCOMPARE(metaTarget->customIcon(group1Icon).pixel(0, 0), qRgb(1, 2, 3));
QCOMPARE(metaTarget->customIcon(group2Icon).pixel(0, 0), qRgb(4, 5, 6));
}
void TestGroup::testExportToDb()
{
QImage iconImage(1, 1, QImage::Format_RGB32);
iconImage.setPixel(0, 0, qRgb(1, 2, 3));
Uuid iconUuid = Uuid::random();
QImage iconUnusedImage(1, 1, QImage::Format_RGB32);
iconUnusedImage.setPixel(0, 0, qRgb(1, 2, 3));
Uuid iconUnusedUuid = Uuid::random();
Database* dbOrg = new Database();
Group* groupOrg = new Group();
groupOrg->setParent(dbOrg->rootGroup());
groupOrg->setName("GTEST");
Entry* entryOrg = new Entry();
entryOrg->setGroup(groupOrg);
entryOrg->setTitle("ETEST");
dbOrg->metadata()->addCustomIcon(iconUuid, iconImage);
dbOrg->metadata()->addCustomIcon(iconUnusedUuid, iconUnusedImage);
entryOrg->setIcon(iconUuid);
entryOrg->beginUpdate();
entryOrg->setIcon(Entry::DefaultIconNumber);
entryOrg->endUpdate();
Database* dbExp = groupOrg->exportToDb();
QCOMPARE(dbExp->rootGroup()->children().size(), 1);
Group* groupExp = dbExp->rootGroup()->children().first();
QVERIFY(groupExp != groupOrg);
QCOMPARE(groupExp->name(), groupOrg->name());
QCOMPARE(groupExp->entries().size(), 1);
Entry* entryExp = groupExp->entries().first();
QCOMPARE(entryExp->title(), entryOrg->title());
QCOMPARE(dbExp->metadata()->customIcons().size(), 1);
QVERIFY(dbExp->metadata()->containsCustomIcon(iconUuid));
QCOMPARE(entryExp->iconNumber(), entryOrg->iconNumber());
QCOMPARE(entryExp->historyItems().size(), 1);
QCOMPARE(entryExp->historyItems().first()->iconUuid(), iconUuid);
delete dbOrg;
delete dbExp;
}
QTEST_GUILESS_MAIN(TestGroup)

View File

@ -31,11 +31,8 @@ private Q_SLOTS:
void testEntries();
void testDeleteSignals();
void testCopyCustomIcon();
void testSearch();
void testAndConcatenationInSearch();
void testClone();
void testCopyCustomIcons();
void testExportToDb();
};
#endif // KEEPASSX_TESTGROUP_H

View File

@ -27,10 +27,12 @@
#include "crypto/Crypto.h"
#include "gui/group/GroupModel.h"
QTEST_GUILESS_MAIN(TestGroupModel)
void TestGroupModel::initTestCase()
{
qRegisterMetaType<QModelIndex>("QModelIndex");
Crypto::init();
QVERIFY(Crypto::init());
}
void TestGroupModel::test()
@ -149,5 +151,3 @@ void TestGroupModel::test()
delete modelTest;
delete model;
}
QTEST_GUILESS_MAIN(TestGroupModel)

View File

@ -24,9 +24,11 @@
#include "crypto/Crypto.h"
#include "streams/HashedBlockStream.h"
QTEST_GUILESS_MAIN(TestHashedBlockStream)
void TestHashedBlockStream::initTestCase()
{
Crypto::init();
QVERIFY(Crypto::init());
}
void TestHashedBlockStream::testWriteRead()
@ -69,5 +71,3 @@ void TestHashedBlockStream::testWriteRead()
buffer.reset();
buffer.buffer().clear();
}
QTEST_GUILESS_MAIN(TestHashedBlockStream)

View File

@ -33,9 +33,11 @@
#include "keys/FileKey.h"
#include "keys/PasswordKey.h"
QTEST_GUILESS_MAIN(TestKeePass1Reader)
void TestKeePass1Reader::initTestCase()
{
Crypto::init();
QVERIFY(Crypto::init());
QString filename = QString(KEEPASSX_TEST_DATA_DIR).append("/basic.kdb");
@ -292,5 +294,3 @@ void TestKeePass1Reader::reopenDatabase(Database* db, const QString& password, c
QVERIFY(!reader.hasError());
delete newDb;
}
QTEST_GUILESS_MAIN(TestKeePass1Reader)

View File

@ -26,9 +26,11 @@
#include "format/KeePass2.h"
#include "format/KeePass2RandomStream.h"
QTEST_GUILESS_MAIN(TestKeePass2RandomStream)
void TestKeePass2RandomStream::initTestCase()
{
Crypto::init();
QVERIFY(Crypto::init());
}
void TestKeePass2RandomStream::test()
@ -77,5 +79,3 @@ void TestKeePass2RandomStream::test()
QCOMPARE(cipherData, cipherDataEncrypt);
QCOMPARE(randomStreamData, cipherData);
}
QTEST_GUILESS_MAIN(TestKeePass2RandomStream)

View File

@ -28,9 +28,11 @@
#include "format/KeePass2Reader.h"
#include "keys/PasswordKey.h"
QTEST_GUILESS_MAIN(TestKeePass2Reader)
void TestKeePass2Reader::initTestCase()
{
Crypto::init();
QVERIFY(Crypto::init());
}
void TestKeePass2Reader::testNonAscii()
@ -154,5 +156,3 @@ void TestKeePass2Reader::testFormat300()
delete db;
}
QTEST_GUILESS_MAIN(TestKeePass2Reader)

View File

@ -29,9 +29,11 @@
#include "format/KeePass2Writer.h"
#include "keys/PasswordKey.h"
QTEST_GUILESS_MAIN(TestKeePass2Writer)
void TestKeePass2Writer::initTestCase()
{
Crypto::init();
QVERIFY(Crypto::init());
CompositeKey key;
key.addKey(PasswordKey("test"));
@ -104,5 +106,3 @@ void TestKeePass2Writer::cleanupTestCase()
delete m_dbOrg;
delete m_dbTest;
}
QTEST_GUILESS_MAIN(TestKeePass2Writer)

View File

@ -28,6 +28,8 @@
#include "format/KeePass2XmlReader.h"
#include "config-keepassx-tests.h"
QTEST_GUILESS_MAIN(TestKeePass2XmlReader)
namespace QTest {
template<>
char* toString(const Uuid& uuid)
@ -66,9 +68,10 @@ QDateTime TestKeePass2XmlReader::genDT(int year, int month, int day, int hour, i
void TestKeePass2XmlReader::initTestCase()
{
Crypto::init();
QVERIFY(Crypto::init());
KeePass2XmlReader reader;
reader.setStrictMode(true);
QString xmlFile = QString(KEEPASSX_TEST_DATA_DIR).append("/NewDatabase.xml");
m_db = reader.readDatabase(xmlFile);
QVERIFY(m_db);
@ -355,28 +358,44 @@ void TestKeePass2XmlReader::testDeletedObjects()
void TestKeePass2XmlReader::testBroken()
{
QFETCH(QString, baseName);
QFETCH(bool, strictMode);
QFETCH(bool, expectError);
KeePass2XmlReader reader;
reader.setStrictMode(strictMode);
QString xmlFile = QString("%1/%2.xml").arg(KEEPASSX_TEST_DATA_DIR, baseName);
QVERIFY(QFile::exists(xmlFile));
QScopedPointer<Database> db(reader.readDatabase(xmlFile));
QVERIFY(reader.hasError());
if (reader.hasError()) {
qWarning("Reader error: %s", qPrintable(reader.errorString()));
}
QCOMPARE(reader.hasError(), expectError);
}
void TestKeePass2XmlReader::testBroken_data()
{
QTest::addColumn<QString>("baseName");
QTest::addColumn<bool>("strictMode");
QTest::addColumn<bool>("expectError");
QTest::newRow("BrokenNoGroupUuid") << "BrokenNoGroupUuid";
QTest::newRow("BrokenNoEntryUuid") << "BrokenNoEntryUuid";
QTest::newRow("BrokenNoRootGroup") << "BrokenNoRootGroup";
QTest::newRow("BrokenTwoRoots") << "BrokenTwoRoots";
QTest::newRow("BrokenTwoRootGroups") << "BrokenTwoRootGroups";
// testfile strict? error?
QTest::newRow("BrokenNoGroupUuid (strict)") << "BrokenNoGroupUuid" << true << true;
QTest::newRow("BrokenNoGroupUuid (not strict)") << "BrokenNoGroupUuid" << false << false;
QTest::newRow("BrokenNoEntryUuid (strict)") << "BrokenNoEntryUuid" << true << true;
QTest::newRow("BrokenNoEntryUuid (not strict)") << "BrokenNoEntryUuid" << false << false;
QTest::newRow("BrokenNoRootGroup (strict)") << "BrokenNoRootGroup" << true << true;
QTest::newRow("BrokenNoRootGroup (not strict)") << "BrokenNoRootGroup" << false << true;
QTest::newRow("BrokenTwoRoots (strict)") << "BrokenTwoRoots" << true << true;
QTest::newRow("BrokenTwoRoots (not strict)") << "BrokenTwoRoots" << false << true;
QTest::newRow("BrokenTwoRootGroups (strict)") << "BrokenTwoRootGroups" << true << true;
QTest::newRow("BrokenTwoRootGroups (not strict)") << "BrokenTwoRootGroups" << false << true;
QTest::newRow("BrokenGroupReference (strict)") << "BrokenGroupReference" << true << false;
QTest::newRow("BrokenGroupReference (not strict)") << "BrokenGroupReference" << false << false;
QTest::newRow("BrokenDeletedObjects (strict)") << "BrokenDeletedObjects" << true << true;
QTest::newRow("BrokenDeletedObjects (not strict)") << "BrokenDeletedObjects" << false << false;
}
void TestKeePass2XmlReader::cleanupTestCase()
{
delete m_db;
}
QTEST_GUILESS_MAIN(TestKeePass2XmlReader)

View File

@ -31,9 +31,11 @@
#include "keys/FileKey.h"
#include "keys/PasswordKey.h"
QTEST_GUILESS_MAIN(TestKeys)
void TestKeys::initTestCase()
{
Crypto::init();
QVERIFY(Crypto::init());
}
void TestKeys::testComposite()
@ -184,5 +186,3 @@ void TestKeys::benchmarkTransformKey()
compositeKey.transform(seed, 1e6);
}
}
QTEST_GUILESS_MAIN(TestKeys)

View File

@ -27,9 +27,11 @@
#include "core/Tools.h"
#include "crypto/Crypto.h"
QTEST_GUILESS_MAIN(TestModified)
void TestModified::initTestCase()
{
Crypto::init();
QVERIFY(Crypto::init());
}
void TestModified::testSignals()
@ -466,5 +468,3 @@ void TestModified::testHistoryItem()
delete db;
}
QTEST_GUILESS_MAIN(TestModified)

View File

@ -46,6 +46,8 @@
#include "tests.h"
#include "core/qcommandlineparser.h"
QTEST_GUILESS_MAIN(TestQCommandLineParser)
Q_DECLARE_METATYPE(char**)
static char *empty_argv[] = { 0 };
@ -412,5 +414,3 @@ void TestQCommandLineParser::testSingleDashWordOptionModes()
QCOMPARE(parser.value(parser.optionNames().at(i)), expectedOptionValues.at(i));
QCOMPARE(parser.unknownOptionNames(), QStringList());
}
QTEST_GUILESS_MAIN(TestQCommandLineParser)

View File

@ -29,6 +29,8 @@
#include "tests.h"
#include "core/qsavefile.h"
QTEST_GUILESS_MAIN(TestQSaveFile)
class DirCleanup
{
public:
@ -154,6 +156,9 @@ void TestQSaveFile::transactionalWriteCanceled()
void TestQSaveFile::transactionalWriteErrorRenaming()
{
#ifndef Q_OS_WIN
if (::geteuid() == 0) {
QSKIP("not valid running this test as root", SkipAll);
}
const QString dir = tmpDir();
QVERIFY(!dir.isEmpty());
const QString targetFile = dir + QString::fromLatin1("/outfile");
@ -197,5 +202,3 @@ QString TestQSaveFile::tmpDir()
return dirName;
}
QTEST_GUILESS_MAIN(TestQSaveFile)

View File

@ -22,6 +22,8 @@
#include <QTest>
QTEST_GUILESS_MAIN(TestRandom)
void TestRandom::initTestCase()
{
m_backend = new RandomBackendTest();
@ -93,5 +95,3 @@ void RandomBackendTest::setNextBytes(const QByteArray& nextBytes)
m_nextBytes = nextBytes;
m_bytesIndex = 0;
}
QTEST_GUILESS_MAIN(TestRandom)

View File

@ -25,9 +25,11 @@
#include "crypto/SymmetricCipher.h"
#include "streams/SymmetricCipherStream.h"
QTEST_GUILESS_MAIN(TestSymmetricCipher)
void TestSymmetricCipher::initTestCase()
{
Crypto::init();
QVERIFY(Crypto::init());
}
void TestSymmetricCipher::testAes256CbcEncryption()
@ -192,5 +194,3 @@ void TestSymmetricCipher::testPadding()
QByteArray decrypted = streamDec.readAll();
QCOMPARE(decrypted, plainText);
}
QTEST_GUILESS_MAIN(TestSymmetricCipher)

View File

@ -22,6 +22,8 @@
#include "tests.h"
#include "autotype/WildcardMatcher.h"
QTEST_GUILESS_MAIN(TestWildcardMatcher)
const QString TestWildcardMatcher::DefaultText = QString("some text");
const QString TestWildcardMatcher::AlternativeText = QString("some other text");
@ -82,5 +84,3 @@ void TestWildcardMatcher::verifyNoMatch(QString pattern)
bool matchResult = m_matcher->match(pattern);
QVERIFY(!matchResult);
}
QTEST_GUILESS_MAIN(TestWildcardMatcher)

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<KeePassFile>
<Root>
<Group>
<UUID>lmU+9n0aeESKZvcEze+bRg==</UUID>
<Name>Test</Name>
<Entry>
<UUID>AaUYVdXsI02h4T1RiAlgtg==</UUID>
<String>
<Key>Title</Key>
<Value>Sample Entry 1</Value>
</String>
</Entry>
</Group>
<DeletedObjects>
<DeletedObject>
<UUID/>
<DeletionTime>2010-08-25T16:14:12Z</DeletionTime>
</DeletedObject>
<DeletedObject/>
<DeletedObject>
<UUID>5K/bzWCSmkCv5OZxYl4N/w==</UUID>
<DeletionTime/>
</DeletedObject>
</DeletedObjects>
</Root>
</KeePassFile>

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<KeePassFile>
<Meta>
<RecycleBinEnabled>True</RecycleBinEnabled>
<RecycleBinUUID>6w7wZdhAp0qVlXjkemuCYw==</RecycleBinUUID>
</Meta>
<Root>
<Group>
<UUID>lmU+9n0aeESKZvcEze+bRg==</UUID>
<Name>Test</Name>
<Entry>
<UUID>AaUYVdXsI02h4T1RiAlgtg==</UUID>
<String>
<Key>Title</Key>
<Value>Sample Entry 1</Value>
</String>
</Entry>
</Group>
</Root>
</KeePassFile>

View File

@ -9,6 +9,7 @@
<Key>Title</Key>
<Value>Sample Entry 1</Value>
</String>
</Entry>
</Group>
</Root>
</KeePassFile>

View File

@ -51,7 +51,7 @@
void TestGui::initTestCase()
{
Crypto::init();
QVERIFY(Crypto::init());
Config::createTempFileInstance();
m_mainWindow = new MainWindow();
m_tabWidget = m_mainWindow->findChild<DatabaseTabWidget*>("tabWidget");
@ -83,7 +83,7 @@ void TestGui::testTabs()
void TestGui::testEditEntry()
{
EntryView* entryView = m_dbWidget->entryView();
EntryView* entryView = m_dbWidget->findChild<EntryView*>("entryView");
QModelIndex item = entryView->model()->index(0, 1);
QRect itemRect = entryView->visualRect(item);
QTest::mouseClick(entryView->viewport(), Qt::LeftButton, Qt::NoModifier, itemRect.center());
@ -170,20 +170,35 @@ void TestGui::testSearch()
QVERIFY(searchAction->isEnabled());
QToolBar* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar");
QWidget* searchActionWidget = toolBar->widgetForAction(searchAction);
QVERIFY(searchActionWidget->isEnabled());
QTest::mouseClick(searchActionWidget, Qt::LeftButton);
EntryView* entryView = m_dbWidget->findChild<EntryView*>("entryView");
QLineEdit* searchEdit = m_dbWidget->findChild<QLineEdit*>("searchEdit");
QToolButton* clearSearch = m_dbWidget->findChild<QToolButton*>("clearButton");
QVERIFY(!searchEdit->hasFocus());
// Enter search
QTest::mouseClick(searchActionWidget, Qt::LeftButton);
QTRY_VERIFY(searchEdit->hasFocus());
// Search for "ZZZ"
QTest::keyClicks(searchEdit, "ZZZ");
QTRY_COMPARE(entryView->model()->rowCount(), 0);
// Escape
QTest::keyClick(m_mainWindow, Qt::Key_Escape);
QTRY_VERIFY(!searchEdit->hasFocus());
// Enter search again
QTest::mouseClick(searchActionWidget, Qt::LeftButton);
QTRY_VERIFY(searchEdit->hasFocus());
// Input and clear
QTest::keyClicks(searchEdit, "ZZZ");
QTRY_COMPARE(searchEdit->text(), QString("ZZZ"));
QTest::mouseClick(clearSearch, Qt::LeftButton);
QTRY_COMPARE(searchEdit->text(), QString(""));
// Triggering search should select the existing text
QTest::keyClicks(searchEdit, "ZZZ");
QTest::mouseClick(searchActionWidget, Qt::LeftButton);
QTRY_VERIFY(searchEdit->hasFocus());
// Search for "some"
QTest::keyClicks(searchEdit, "some");
QTRY_COMPARE(entryView->model()->rowCount(), 4);
clickIndex(entryView->model()->index(0, 1), entryView, Qt::LeftButton);
@ -237,8 +252,8 @@ void TestGui::testSearch()
void TestGui::testDeleteEntry()
{
GroupView* groupView = m_dbWidget->groupView();
EntryView* entryView = m_dbWidget->entryView();
GroupView* groupView = m_dbWidget->findChild<GroupView*>("groupView");
EntryView* entryView = m_dbWidget->findChild<EntryView*>("entryView");
QToolBar* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar");
QAction* entryDeleteAction = m_mainWindow->findChild<QAction*>("actionEntryDelete");
QWidget* entryDeleteWidget = toolBar->widgetForAction(entryDeleteAction);
@ -274,7 +289,7 @@ void TestGui::testDeleteEntry()
void TestGui::testCloneEntry()
{
EntryView* entryView = m_dbWidget->entryView();
EntryView* entryView = m_dbWidget->findChild<EntryView*>("entryView");
QCOMPARE(entryView->model()->rowCount(), 1);
@ -292,8 +307,8 @@ void TestGui::testCloneEntry()
void TestGui::testDragAndDropEntry()
{
EntryView* entryView = m_dbWidget->entryView();
GroupView* groupView = m_dbWidget->groupView();
EntryView* entryView = m_dbWidget->findChild<EntryView*>("entryView");
GroupView* groupView = m_dbWidget->findChild<GroupView*>("groupView");
QAbstractItemModel* groupModel = groupView->model();
QModelIndex sourceIndex = entryView->model()->index(0, 1);
@ -314,7 +329,7 @@ void TestGui::testDragAndDropEntry()
void TestGui::testDragAndDropGroup()
{
QAbstractItemModel* groupModel = m_dbWidget->groupView()->model();
QAbstractItemModel* groupModel = m_dbWidget->findChild<GroupView*>("groupView")->model();
QModelIndex rootIndex = groupModel->index(0, 0);
dragAndDropGroup(groupModel->index(0, 0, rootIndex),
@ -453,7 +468,7 @@ void TestGui::dragAndDropGroup(const QModelIndex& sourceIndex, const QModelIndex
QVERIFY(sourceIndex.isValid());
QVERIFY(targetIndex.isValid());
GroupModel* groupModel = qobject_cast<GroupModel*>(m_dbWidget->groupView()->model());
GroupModel* groupModel = qobject_cast<GroupModel*>(m_dbWidget->findChild<GroupView*>("groupView")->model());
QMimeData mimeData;
QByteArray encoded;

View File

@ -29,7 +29,7 @@
void TestGuiPixmaps::initTestCase()
{
Crypto::init();
QVERIFY(Crypto::init());
}
void TestGuiPixmaps::testDatabaseIcons()

View File

@ -38,7 +38,9 @@ int main(int argc, char **argv)
return 1;
}
Crypto::init();
if (!Crypto::init()) {
qFatal("Fatal error while testing the cryptographic functions:\n%s", qPrintable(Crypto::errorString()));
}
CompositeKey key;
if (QFile::exists(app.arguments().at(1))) {