diff --git a/.tx/config b/.tx/config new file mode 100644 index 000000000..015acf4b0 --- /dev/null +++ b/.tx/config @@ -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_.ts +source_lang = en +type = QT diff --git a/share/CMakeLists.txt b/share/CMakeLists.txt index 7069c6c4b..0e2b7fa99 100644 --- a/share/CMakeLists.txt +++ b/share/CMakeLists.txt @@ -13,6 +13,8 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +add_subdirectory(translations) + file(GLOB DATABASE_ICONS icons/database/*.png) install(FILES ${DATABASE_ICONS} DESTINATION ${DATA_INSTALL_DIR}/icons/database) diff --git a/share/translations/CMakeLists.txt b/share/translations/CMakeLists.txt new file mode 100644 index 000000000..b1aa8785b --- /dev/null +++ b/share/translations/CMakeLists.txt @@ -0,0 +1,26 @@ +# Copyright (C) 2014 Felix Geyer +# +# 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 . + +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) diff --git a/share/translations/keepassx_de.ts b/share/translations/keepassx_de.ts new file mode 100644 index 000000000..fe074ae69 --- /dev/null +++ b/share/translations/keepassx_de.ts @@ -0,0 +1,1175 @@ + + + AboutDialog + + About KeePassX + Über KeePassX + + + KeePassX is distributed under the term of the GNU General Public License (GPL) version 2 or (at your option) version 3. + + + + + AutoType + + Auto-Type - KeePassX + + + + Couldn't find an entry that matches the window title. + + + + + AutoTypeAssociationsModel + + Window + + + + Sequence + + + + Default sequence + + + + + AutoTypeSelectDialog + + Auto-Type - KeePassX + + + + Select entry to Auto-Type: + + + + + ChangeMasterKeyWidget + + Password + + + + Enter password: + + + + Repeat password: + + + + Key file + + + + Browse + + + + Create + + + + Key files + + + + All files + + + + Create Key File... + + + + Error + + + + Unable to create Key File : + + + + Select a key file + + + + Question + + + + Do you really want to use an empty string as password? + + + + Different passwords supplied. + + + + + DatabaseOpenWidget + + Enter master key + + + + Key File: + + + + Password: + + + + Browse + + + + Error + + + + Unable to open the database. + + + + Can't open key file + + + + All files + + + + Key files + + + + Select key file + + + + + DatabaseSettingsWidget + + Database name: + + + + Database description: + + + + Transform rounds: + + + + Default username: + + + + Use recycle bin: + + + + MiB + + + + Benchmark + + + + Max. history items: + + + + Max. history size: + + + + + DatabaseTabWidget + + Root + + + + KeePass 2 Database + + + + All files + + + + Open database + + + + Warning + + + + File not found! + + + + Open KeePass 1 database + + + + KeePass 1 database + + + + All files (*) + + + + Close? + + + + "%1" is in edit mode. +Close anyway? + + + + Save changes? + + + + "%1" was modified. +Save changes? + + + + Error + + + + Writing the database failed. + + + + Save database as + + + + New database + + + + locked + + + + + DatabaseWidget + + Change master key + + + + Delete entry? + + + + Do you really want to delete the entry "%1" for good? + + + + Delete entries? + + + + Do you really want to delete %1 entries for good? + + + + Move entries to recycle bin? + + + + Do you really want to move %n entry(s) to the recycle bin? + + + + Delete group? + + + + Do you really want to delete the group "%1" for good? + + + + Current group + + + + + EditEntryWidget + + Entry + + + + Advanced + + + + Icon + + + + Auto-Type + + + + Properties + + + + History + + + + Entry history + + + + Add entry + + + + Edit entry + + + + Error + + + + Different passwords supplied. + + + + New attribute + + + + Select file + + + + Unable to open file + + + + Save attachment + + + + Unable to save the attachment: + + + + + Tomorrow + + + + %n week(s) + + + + %n month(s) + + + + 1 year + + + + + EditEntryWidgetAdvanced + + Additional attributes + + + + Add + + + + Edit + + + + Remove + + + + Attachments + + + + Save + + + + + EditEntryWidgetAutoType + + Enable Auto-Type for this entry + + + + Inherit default Auto-Type sequence from the group + + + + Use custom Auto-Type sequence: + + + + + + + + + - + + + + Window title: + + + + Use default sequence + + + + Set custom sequence: + + + + + EditEntryWidgetHistory + + Show + + + + Restore + + + + Delete + + + + Delete all + + + + + EditEntryWidgetMain + + Title: + + + + Username: + + + + Password: + + + + Repeat: + + + + Gen. + + + + URL: + + + + Expires + + + + Presets + + + + Notes: + + + + + EditGroupWidget + + Group + + + + Icon + + + + Properties + + + + Add group + + + + Edit group + + + + Enable + + + + Disable + + + + Inherit from parent group (%1) + + + + + EditGroupWidgetMain + + Name + + + + Notes + + + + Expires + + + + Search + + + + Auto-type + + + + + EditWidgetIcons + + Use default icon + + + + Use custom icon + + + + Add custom icon + + + + Delete custom icon + + + + Images + + + + All files + + + + Select Image + + + + Can't delete icon! + + + + Can't delete icon. Still used by %n item(s). + + + + + EditWidgetProperties + + Created: + + + + Modified: + + + + Accessed: + + + + Uuid: + + + + + EntryAttributesModel + + Name + + + + + EntryHistoryModel + + Last modified + + + + Title + + + + Username + + + + URL + + + + + EntryModel + + Group + + + + Title + + + + Username + + + + URL + + + + + Group + + Recycle Bin + + + + + KeePass1OpenWidget + + Import KeePass1 database + + + + Error + + + + Unable to open the database. + + + + + KeePass1Reader + + Unable to read keyfile. + + + + Not a KeePass database. + + + + Unsupported encryption algorithm. + + + + Unsupported KeePass database version. + + + + Root + + + + + KeePass2Reader + + Not a KeePass database. + + + + Unsupported KeePass database version. + + + + Wrong key or database file is corrupt. + + + + + MainWindow + + Database + Datenbank + + + Recent databases + + + + Help + Hilfe + + + Entries + Einträge + + + Copy attribute to clipboard + + + + Groups + Gruppen + + + Extras + Extras + + + View + Ansicht + + + Quit + + + + About + + + + Open database + + + + Save database + + + + Close database + + + + New database + + + + Add new entry + + + + View/Edit entry + + + + Delete entry + + + + Add new group + + + + Edit group + + + + Delete group + + + + Save database as + + + + Change master key + + + + Database settings + + + + Import KeePass 1 database + + + + Clone entry + + + + Find + + + + Username + + + + Copy username to clipboard + + + + Password + + + + Copy password to clipboard + + + + Settings + + + + Perform Auto-Type + + + + Open URL + + + + Lock databases + + + + Title + + + + URL + + + + Notes + + + + Show toolbar + + + + read-only + + + + + PasswordGeneratorWidget + + Password: + + + + Length: + + + + Character Types + + + + Upper Case Letters + + + + Lower Case Letters + + + + Numbers + + + + Special Characters + + + + Exclude look-alike characters + + + + Ensure that the password contains characters from every group + + + + Accept + + + + + QCommandLineParser + + Displays version information. + + + + Displays this help. + + + + Unknown option '%1'. + + + + Unknown options: %1. + + + + Missing value after '%1'. + + + + Unexpected value after '%1'. + + + + [options] + + + + Usage: %1 + + + + Options: + + + + Arguments: + + + + + QSaveFile + + Existing file %1 is not writable + + + + Writing canceled by application + + + + Partial write. Partition full? + + + + + QtIOCompressor + + Internal zlib error when compressing: + + + + Error writing to underlying device: + + + + Error opening underlying device: + + + + Error reading data from underlying device: + + + + Internal zlib error when decompressing: + + + + + QtIOCompressor::open + + The gzip format not supported in this version of zlib. + + + + Internal zlib error: + + + + + SearchWidget + + Find: + + + + Case sensitive + + + + Current group + + + + Root group + + + + + SettingsWidget + + Application Settings + + + + General + + + + Security + + + + + SettingsWidgetGeneral + + Remember last databases + + + + Open previous databases on startup + + + + Mark as modified on expanded state changes + + + + Automatically save on exit + + + + Automatically save after every change + + + + Minimize when copying to clipboard + + + + Use group icon on entry creation + + + + Global Auto-Type shortcut + + + + Use entry title to match windows for global auto-type + + + + + SettingsWidgetSecurity + + Clear clipboard after + + + + sec + + + + Lock databases after inactivity of + + + + Show passwords in cleartext by default + + + + Always ask before performing auto-type + + + + + UnlockDatabaseWidget + + Unlock database + + + + Error + + + + Wrong key. + + + + + WelcomeWidget + + Welcome! + + + + + main + + KeePassX - cross-platform password manager + + + + filename of the password database to open (*.kdbx) + + + + path to a custom config file + + + + password of the database (DANGEROUS!) + + + + key file of the database + + + + \ No newline at end of file diff --git a/share/translations/keepassx_en.ts b/share/translations/keepassx_en.ts new file mode 100644 index 000000000..eccd5575b --- /dev/null +++ b/share/translations/keepassx_en.ts @@ -0,0 +1,1189 @@ + + + + + AboutDialog + + About KeePassX + + + + KeePassX is distributed under the term of the GNU General Public License (GPL) version 2 or (at your option) version 3. + + + + + AutoType + + Auto-Type - KeePassX + + + + Couldn't find an entry that matches the window title. + + + + + AutoTypeAssociationsModel + + Window + + + + Sequence + + + + Default sequence + + + + + AutoTypeSelectDialog + + Auto-Type - KeePassX + + + + Select entry to Auto-Type: + + + + + ChangeMasterKeyWidget + + Password + + + + Enter password: + + + + Repeat password: + + + + Key file + + + + Browse + + + + Create + + + + Key files + + + + All files + + + + Create Key File... + + + + Error + + + + Unable to create Key File : + + + + Select a key file + + + + Question + + + + Do you really want to use an empty string as password? + + + + Different passwords supplied. + + + + + DatabaseOpenWidget + + Enter master key + + + + Key File: + + + + Password: + + + + Browse + + + + Error + + + + Unable to open the database. + + + + Can't open key file + + + + All files + + + + Key files + + + + Select key file + + + + + DatabaseSettingsWidget + + Database name: + + + + Database description: + + + + Transform rounds: + + + + Default username: + + + + Use recycle bin: + + + + MiB + + + + Benchmark + + + + Max. history items: + + + + Max. history size: + + + + + DatabaseTabWidget + + Root + + + + KeePass 2 Database + + + + All files + + + + Open database + + + + Warning + + + + File not found! + + + + Open KeePass 1 database + + + + KeePass 1 database + + + + All files (*) + + + + Close? + + + + "%1" is in edit mode. +Close anyway? + + + + Save changes? + + + + "%1" was modified. +Save changes? + + + + Error + + + + Writing the database failed. + + + + Save database as + + + + New database + + + + locked + + + + + DatabaseWidget + + Change master key + + + + Delete entry? + + + + Do you really want to delete the entry "%1" for good? + + + + Delete entries? + + + + Do you really want to delete %1 entries for good? + + + + Move entries to recycle bin? + + + + Do you really want to move %n entry(s) to the recycle bin? + + + + + + + Delete group? + + + + Do you really want to delete the group "%1" for good? + + + + Current group + + + + + EditEntryWidget + + Entry + + + + Advanced + + + + Icon + + + + Auto-Type + + + + Properties + + + + History + + + + Entry history + + + + Add entry + + + + Edit entry + + + + Error + + + + Different passwords supplied. + + + + New attribute + + + + Select file + + + + Unable to open file + + + + Save attachment + + + + Unable to save the attachment: + + + + + Tomorrow + + + + %n week(s) + + + + + + + %n month(s) + + + + + + + 1 year + + + + + EditEntryWidgetAdvanced + + Additional attributes + + + + Add + + + + Edit + + + + Remove + + + + Attachments + + + + Save + + + + + EditEntryWidgetAutoType + + Enable Auto-Type for this entry + + + + Inherit default Auto-Type sequence from the group + + + + Use custom Auto-Type sequence: + + + + + + + + + - + + + + Window title: + + + + Use default sequence + + + + Set custom sequence: + + + + + EditEntryWidgetHistory + + Show + + + + Restore + + + + Delete + + + + Delete all + + + + + EditEntryWidgetMain + + Title: + + + + Username: + + + + Password: + + + + Repeat: + + + + Gen. + + + + URL: + + + + Expires + + + + Presets + + + + Notes: + + + + + EditGroupWidget + + Group + + + + Icon + + + + Properties + + + + Add group + + + + Edit group + + + + Enable + + + + Disable + + + + Inherit from parent group (%1) + + + + + EditGroupWidgetMain + + Name + + + + Notes + + + + Expires + + + + Search + + + + Auto-type + + + + + EditWidgetIcons + + Use default icon + + + + Use custom icon + + + + Add custom icon + + + + Delete custom icon + + + + Images + + + + All files + + + + Select Image + + + + Can't delete icon! + + + + Can't delete icon. Still used by %n item(s). + + + + + + + + EditWidgetProperties + + Created: + + + + Modified: + + + + Accessed: + + + + Uuid: + + + + + EntryAttributesModel + + Name + + + + + EntryHistoryModel + + Last modified + + + + Title + + + + Username + + + + URL + + + + + EntryModel + + Group + + + + Title + + + + Username + + + + URL + + + + + Group + + Recycle Bin + + + + + KeePass1OpenWidget + + Import KeePass1 database + + + + Error + + + + Unable to open the database. + + + + + KeePass1Reader + + Unable to read keyfile. + + + + Not a KeePass database. + + + + Unsupported encryption algorithm. + + + + Unsupported KeePass database version. + + + + Root + + + + + KeePass2Reader + + Not a KeePass database. + + + + Unsupported KeePass database version. + + + + Wrong key or database file is corrupt. + + + + + MainWindow + + Database + + + + Recent databases + + + + Help + + + + Entries + + + + Copy attribute to clipboard + + + + Groups + + + + Extras + + + + View + + + + Quit + + + + About + + + + Open database + + + + Save database + + + + Close database + + + + New database + + + + Add new entry + + + + View/Edit entry + + + + Delete entry + + + + Add new group + + + + Edit group + + + + Delete group + + + + Save database as + + + + Change master key + + + + Database settings + + + + Import KeePass 1 database + + + + Clone entry + + + + Find + + + + Username + + + + Copy username to clipboard + + + + Password + + + + Copy password to clipboard + + + + Settings + + + + Perform Auto-Type + + + + Open URL + + + + Lock databases + + + + Title + + + + URL + + + + Notes + + + + Show toolbar + + + + read-only + + + + + PasswordGeneratorWidget + + Password: + + + + Length: + + + + Character Types + + + + Upper Case Letters + + + + Lower Case Letters + + + + Numbers + + + + Special Characters + + + + Exclude look-alike characters + + + + Ensure that the password contains characters from every group + + + + Accept + + + + + QCommandLineParser + + Displays version information. + + + + Displays this help. + + + + Unknown option '%1'. + + + + Unknown options: %1. + + + + Missing value after '%1'. + + + + Unexpected value after '%1'. + + + + [options] + + + + Usage: %1 + + + + Options: + + + + Arguments: + + + + + QSaveFile + + Existing file %1 is not writable + + + + Writing canceled by application + + + + Partial write. Partition full? + + + + + QtIOCompressor + + Internal zlib error when compressing: + + + + Error writing to underlying device: + + + + Error opening underlying device: + + + + Error reading data from underlying device: + + + + Internal zlib error when decompressing: + + + + + QtIOCompressor::open + + The gzip format not supported in this version of zlib. + + + + Internal zlib error: + + + + + SearchWidget + + Find: + + + + Case sensitive + + + + Current group + + + + Root group + + + + + SettingsWidget + + Application Settings + + + + General + + + + Security + + + + + SettingsWidgetGeneral + + Remember last databases + + + + Open previous databases on startup + + + + Mark as modified on expanded state changes + + + + Automatically save on exit + + + + Automatically save after every change + + + + Minimize when copying to clipboard + + + + Use group icon on entry creation + + + + Global Auto-Type shortcut + + + + Use entry title to match windows for global auto-type + + + + + SettingsWidgetSecurity + + Clear clipboard after + + + + sec + + + + Lock databases after inactivity of + + + + Show passwords in cleartext by default + + + + Always ask before performing auto-type + + + + + UnlockDatabaseWidget + + Unlock database + + + + Error + + + + Wrong key. + + + + + WelcomeWidget + + Welcome! + + + + + main + + KeePassX - cross-platform password manager + + + + filename of the password database to open (*.kdbx) + + + + path to a custom config file + + + + password of the database (DANGEROUS!) + + + + key file of the database + + + + diff --git a/share/translations/keepassx_en_plurals.ts b/share/translations/keepassx_en_plurals.ts new file mode 100644 index 000000000..006f6f6e4 --- /dev/null +++ b/share/translations/keepassx_en_plurals.ts @@ -0,0 +1,41 @@ + + + + + DatabaseWidget + + Do you really want to move %n entry(s) to the recycle bin? + + Do you really want to move %n entry to the recycle bin? + Do you really want to move %n entries to the recycle bin? + + + + + EditEntryWidget + + %n week(s) + + %n week + %n weeks + + + + %n month(s) + + %n month + %n months + + + + + EditWidgetIcons + + Can't delete icon. Still used by %n item(s). + + Can't delete icon. Still used by %n item. + Can't delete icon. Still used by %n items. + + + + diff --git a/share/translations/update.sh b/share/translations/update.sh new file mode 100755 index 000000000..6828dc820 --- /dev/null +++ b/share/translations/update.sh @@ -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 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d221418d3..420cc6d20 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -36,6 +36,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 @@ -48,7 +49,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 @@ -74,6 +77,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 @@ -156,6 +160,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 diff --git a/src/autotype/AutoType.cpp b/src/autotype/AutoType.cpp index 80b810821..aac0c0cf8 100644 --- a/src/autotype/AutoType.cpp +++ b/src/autotype/AutoType.cpp @@ -190,8 +190,10 @@ void AutoType::performGlobalAutoType(const QList& 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(); } diff --git a/src/config-keepassx.h.cmake b/src/config-keepassx.h.cmake index 9a3f49516..805700a9f 100644 --- a/src/config-keepassx.h.cmake +++ b/src/config-keepassx.h.cmake @@ -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}" diff --git a/src/core/Config.cpp b/src/core/Config.cpp index c48bc9553..8a455e27a 100644 --- a/src/core/Config.cpp +++ b/src/core/Config.cpp @@ -95,12 +95,14 @@ void Config::init(const QString& fileName) m_defaults.insert("ShowToolbar", true); m_defaults.insert("MinimizeOnCopy", false); m_defaults.insert("UseGroupIconOnEntryCreation", false); + 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"); } Config* Config::instance() @@ -112,7 +114,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); diff --git a/src/core/Config.h b/src/core/Config.h index ee3082685..ca0f74cb5 100644 --- a/src/core/Config.h +++ b/src/core/Config.h @@ -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: diff --git a/src/core/Entry.cpp b/src/core/Entry.cpp index 55f543285..4f977915b 100644 --- a/src/core/Entry.cpp +++ b/src/core/Entry.cpp @@ -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; diff --git a/src/core/Entry.h b/src/core/Entry.h index c2c2938c1..ae07ed453 100644 --- a/src/core/Entry.h +++ b/src/core/Entry.h @@ -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 bool set(T& property, const T& value); diff --git a/src/core/EntrySearcher.cpp b/src/core/EntrySearcher.cpp new file mode 100644 index 000000000..82a553e3a --- /dev/null +++ b/src/core/EntrySearcher.cpp @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2014 Florian Geyer + * + * 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 . + */ + +#include "EntrySearcher.h" + +#include "core/Group.h" + +QList EntrySearcher::search(const QString &searchTerm, const Group* group, Qt::CaseSensitivity caseSensitivity) +{ + if (!group->resolveSearchingEnabled()) { + return QList(); + } + + return searchEntries(searchTerm, group, caseSensitivity); +} + +QList EntrySearcher::searchEntries(const QString& searchTerm, const Group* group, Qt::CaseSensitivity caseSensitivity) +{ + QList 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 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(); + } + } + + return QList() << 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); +} diff --git a/src/core/EntrySearcher.h b/src/core/EntrySearcher.h new file mode 100644 index 000000000..246538cbe --- /dev/null +++ b/src/core/EntrySearcher.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2014 Florian Geyer + * + * 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 . + */ + +#ifndef KEEPASSX_ENTRYSEARCHER_H +#define KEEPASSX_ENTRYSEARCHER_H + +#include + + +class Group; +class Entry; + +class EntrySearcher +{ +public: + QList search(const QString& searchTerm, const Group* group, Qt::CaseSensitivity caseSensitivity); +private: + QList searchEntries(const QString& searchTerm, const Group* group, Qt::CaseSensitivity caseSensitivity); + QList matchEntry(const QString& searchTerm, Entry* entry, Qt::CaseSensitivity caseSensitivity); + bool wordMatch(const QString &word, Entry *entry, Qt::CaseSensitivity caseSensitivity); +}; + +#endif // KEEPASSX_ENTRYSEARCHER_H diff --git a/src/core/Exporter.h b/src/core/Exporter.h new file mode 100644 index 000000000..dedb1c8a5 --- /dev/null +++ b/src/core/Exporter.h @@ -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 diff --git a/src/core/Group.cpp b/src/core/Group.cpp index 4c04b4846..517f8cb0c 100644 --- a/src/core/Group.cpp +++ b/src/core/Group.cpp @@ -500,22 +500,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 customIcons = customIconsRecursive(); - db->metadata()->copyCustomIcons(customIcons, database()->metadata()); - - db->copyAttributesFrom(database()); - - return db; -} - void Group::addEntry(Entry* entry) { Q_ASSERT(entry); @@ -612,33 +596,6 @@ void Group::recCreateDelObjects() } } -QList Group::search(const QString& searchTerm, Qt::CaseSensitivity caseSensitivity, - bool resolveInherit) -{ - QList 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) { diff --git a/src/core/Group.h b/src/core/Group.h index 4e08f5b3b..7391f886d 100644 --- a/src/core/Group.h +++ b/src/core/Group.h @@ -111,10 +111,6 @@ public: */ Group* clone(Entry::CloneFlags entryFlags = Entry::CloneNewUuid | Entry::CloneResetTimeInfo) const; void copyDataFrom(const Group* other); - Database* exportToDb(); - - QList search(const QString& searchTerm, Qt::CaseSensitivity caseSensitivity, - bool resolveInherit = true); Q_SIGNALS: void dataChanged(Group* group); diff --git a/src/core/ToDbExporter.cpp b/src/core/ToDbExporter.cpp new file mode 100644 index 000000000..1f76fb744 --- /dev/null +++ b/src/core/ToDbExporter.cpp @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2014 Felix Geyer + * Copyright (C) 2014 Florian Geyer + * + * 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 . + */ + +#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 customIcons = group->customIconsRecursive(); + db->metadata()->copyCustomIcons(customIcons, oldDb->metadata()); + + db->copyAttributesFrom(oldDb); + + return db; +} diff --git a/src/core/ToDbExporter.h b/src/core/ToDbExporter.h new file mode 100644 index 000000000..58c5efeb3 --- /dev/null +++ b/src/core/ToDbExporter.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2014 Felix Geyer + * Copyright (C) 2014 Florian Geyer + * + * 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 . + */ + +#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 diff --git a/src/core/Translator.cpp b/src/core/Translator.cpp new file mode 100644 index 000000000..bc4d2b629 --- /dev/null +++ b/src/core/Translator.cpp @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2014 Felix Geyer + * + * 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 . + */ + +#include "Translator.h" + +#include +#include +#include +#include +#include +#include + +#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 > Translator::availableLanguages() +{ + QStringList paths; +#ifdef QT_DEBUG + paths.append(QString("%1/share/translations").arg(KEEPASSX_BINARY_DIR)); +#endif + paths.append(filePath()->dataPath("translations")); + + QList > languages; + languages.append(QPair("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(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; + } +} diff --git a/src/core/Translator.h b/src/core/Translator.h new file mode 100644 index 000000000..4bc4fca3e --- /dev/null +++ b/src/core/Translator.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2014 Felix Geyer + * + * 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 . + */ + +#ifndef KEEPASSX_TRANSLATOR_H +#define KEEPASSX_TRANSLATOR_H + +#include +#include + +class Translator +{ +public: + static void installTranslator(); + static QList > 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 diff --git a/src/gui/Clipboard.cpp b/src/gui/Clipboard.cpp index eb77d2b3d..7d8f71faf 100644 --- a/src/gui/Clipboard.cpp +++ b/src/gui/Clipboard.cpp @@ -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() diff --git a/src/gui/Clipboard.h b/src/gui/Clipboard.h index bc2a19d31..8b6ea69f5 100644 --- a/src/gui/Clipboard.h +++ b/src/gui/Clipboard.h @@ -43,6 +43,7 @@ private: static Clipboard* m_instance; QTimer* m_timer; + QString m_lastCopied; }; inline Clipboard* clipboard() { diff --git a/src/gui/DatabaseOpenWidget.cpp b/src/gui/DatabaseOpenWidget.cpp index 973192523..37bbce747 100644 --- a/src/gui/DatabaseOpenWidget.cpp +++ b/src/gui/DatabaseOpenWidget.cpp @@ -117,8 +117,8 @@ void DatabaseOpenWidget::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(); } } @@ -138,7 +138,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); diff --git a/src/gui/DatabaseTabWidget.cpp b/src/gui/DatabaseTabWidget.cpp index 8c2ba06d9..7f9984615 100644 --- a/src/gui/DatabaseTabWidget.cpp +++ b/src/gui/DatabaseTabWidget.cpp @@ -27,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" @@ -46,12 +47,15 @@ const int DatabaseTabWidget::LastDatabasesCount = 5; DatabaseTabWidget::DatabaseTabWidget(QWidget* parent) : 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())); } @@ -189,7 +193,7 @@ bool DatabaseTabWidget::closeDatabase(Database* db) if (dbName.right(1) == "*") { dbName.chop(1); } - if (dbStruct.dbWidget->currentMode() == DatabaseWidget::EditMode && db->hasKey()) { + if (dbStruct.dbWidget->isInEditMode() && db->hasKey()) { QMessageBox::StandardButton result = MessageBox::question( this, tr("Close?"), @@ -503,7 +507,7 @@ DatabaseWidget* DatabaseTabWidget::currentDatabaseWidget() } } -bool DatabaseTabWidget::hasLockableDatabases() +bool DatabaseTabWidget::hasLockableDatabases() const { QHashIterator i(m_dbList); while (i.hasNext()) { @@ -584,6 +588,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) { diff --git a/src/gui/DatabaseTabWidget.h b/src/gui/DatabaseTabWidget.h index 9261a065c..25d34f304 100644 --- a/src/gui/DatabaseTabWidget.h +++ b/src/gui/DatabaseTabWidget.h @@ -25,6 +25,7 @@ #include "gui/DatabaseWidget.h" class DatabaseWidget; +class DatabaseWidgetStateSync; class DatabaseOpenWidget; class QFile; @@ -53,7 +54,7 @@ public: void openDatabase(const QString& fileName, const QString& pw = QString(), const QString& keyFile = QString()); DatabaseWidget* currentDatabaseWidget(); - bool hasLockableDatabases(); + bool hasLockableDatabases() const; static const int LastDatabasesCount; @@ -75,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); @@ -83,6 +85,7 @@ private Q_SLOTS: void modified(); void toggleTabbar(); void changeDatabase(Database* newDb); + void emitActivateDatabaseChanged(); private: void saveDatabase(Database* db); @@ -99,6 +102,7 @@ private: KeePass2Writer m_writer; QHash m_dbList; + DatabaseWidgetStateSync* m_dbWidgetSateSync; }; #endif // KEEPASSX_DATABASETABWIDGET_H diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp index 1dc694f69..f22f33885 100644 --- a/src/gui/DatabaseWidget.cpp +++ b/src/gui/DatabaseWidget.cpp @@ -30,6 +30,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" @@ -63,12 +64,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)), @@ -81,14 +83,6 @@ 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); @@ -104,10 +98,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 +140,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*))); @@ -171,7 +174,7 @@ DatabaseWidget::~DatabaseWidget() { } -DatabaseWidget::Mode DatabaseWidget::currentMode() +DatabaseWidget::Mode DatabaseWidget::currentMode() const { if (currentWidget() == Q_NULLPTR) { return DatabaseWidget::None; @@ -187,21 +190,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 DatabaseWidget::splitterSizes() const +{ + return m_splitter->sizes(); +} + +void DatabaseWidget::setSplitterSizes(const QList& sizes) +{ + m_splitter->setSizes(sizes); +} + +QList DatabaseWidget::entryHeaderViewSizes() const +{ + QList 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& 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; @@ -298,8 +336,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; @@ -431,7 +468,7 @@ void DatabaseWidget::createGroup() void DatabaseWidget::deleteGroup() { Group* currentGroup = m_groupView->currentGroup(); - if (!currentGroup || !canDeleteCurrentGoup()) { + if (!currentGroup || !canDeleteCurrentGroup()) { Q_ASSERT(false); return; } @@ -601,7 +638,7 @@ void DatabaseWidget::unlockDatabase(bool accepted) Q_ASSERT(accepted); Q_UNUSED(accepted); - setCurrentWidget(widgetBeforeLock); + setCurrentWidget(m_widgetBeforeLock); Q_EMIT unlockedDatabase(); } @@ -673,8 +710,16 @@ void DatabaseWidget::switchToImportKeepass1(const QString& fileName) void DatabaseWidget::toggleSearch() { - if (m_entryView->inEntryListMode()) { - closeSearch(); + if (isInSearchMode()) { + if (m_searchUi->searchEdit->hasFocus()) { + closeSearch(); + } + else { + m_searchUi->searchEdit->selectAll(); + m_searchUi->searchEdit->setFocus(); + // make sure the search action is checked again + emitCurrentModeChanged(); + } } else { showSearch(); @@ -684,11 +729,19 @@ void DatabaseWidget::toggleSearch() void DatabaseWidget::closeSearch() { Q_ASSERT(m_lastGroup); + + Q_EMIT listModeAboutToActivate(); + m_groupView->setCurrentGroup(m_lastGroup); + m_searchTimer->stop(); + + Q_EMIT listModeActivated(); } void DatabaseWidget::showSearch() { + Q_EMIT searchModeAboutToActivate(); + m_searchUi->searchEdit->blockSignals(true); m_searchUi->searchEdit->clear(); m_searchUi->searchEdit->blockSignals(false); @@ -722,6 +775,8 @@ void DatabaseWidget::showSearch() m_searchWidget->show(); search(); m_searchUi->searchEdit->setFocus(); + + Q_EMIT searchModeActivated(); } void DatabaseWidget::search() @@ -747,8 +802,8 @@ void DatabaseWidget::search() else { sensitivity = Qt::CaseInsensitive; } - QList searchResult = searchGroup->search(m_searchUi->searchEdit->text(), sensitivity); + QList searchResult = EntrySearcher().search(m_searchUi->searchEdit->text(), searchGroup, sensitivity); m_entryView->setEntryList(searchResult); } @@ -779,19 +834,19 @@ 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(); return !isRootGroup && !isRecycleBin; } -bool DatabaseWidget::isInSearchMode() +bool DatabaseWidget::isInSearchMode() const { return m_entryView->inEntryListMode(); } @@ -808,7 +863,7 @@ void DatabaseWidget::lock() { Q_ASSERT(currentMode() != DatabaseWidget::LockedMode); - widgetBeforeLock = currentWidget(); + m_widgetBeforeLock = currentWidget(); m_unlockDatabaseWidget->load(m_filename, m_db); setCurrentWidget(m_unlockDatabaseWidget); } @@ -839,3 +894,23 @@ void DatabaseWidget::databaseModifedExternally() delete oldDb; } } + +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; +} diff --git a/src/gui/DatabaseWidget.h b/src/gui/DatabaseWidget.h index a83f3206a..9311e4022 100644 --- a/src/gui/DatabaseWidget.h +++ b/src/gui/DatabaseWidget.h @@ -39,6 +39,7 @@ class GroupView; class KeePass1OpenWidget; class QFile; class QMenu; +class QSplitter; class UnlockDatabaseWidget; namespace Ui { @@ -60,18 +61,24 @@ public: explicit DatabaseWidget(Database* db, QWidget* parent = Q_NULLPTR); ~DatabaseWidget(); - GroupView* groupView(); - EntryView* entryView(); Database* database(); - bool dbHasKey(); - bool canDeleteCurrentGoup(); - bool isInSearchMode(); + bool dbHasKey() const; + bool canDeleteCurrentGroup() const; + bool isInSearchMode() 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); + int numberOfSelectedEntries() const; + QStringList customEntryAttributes() const; + bool isGroupSelected() const; + bool isInEditMode() const; + QList splitterSizes() const; + void setSplitterSizes(const QList& sizes); + QList entryHeaderViewSizes() const; + void setEntryViewHeaderSizes(const QList& sizes); Q_SIGNALS: void closeRequest(); @@ -82,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(); @@ -106,8 +119,6 @@ public Q_SLOTS: void switchToOpenDatabase(const QString& fileName, const QString& password, const QString& keyFile); void switchToImportKeepass1(const QString& fileName); void toggleSearch(); - void emitGroupContextMenuRequested(const QPoint& pos); - void emitEntryContextMenuRequested(const QPoint& pos); private Q_SLOTS: void entryActivationSignalReceived(Entry* entry, EntryModel::ModelColumn column); @@ -117,6 +128,8 @@ private Q_SLOTS: 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 databaseModifedExternally(); @@ -145,6 +158,7 @@ private: DatabaseOpenWidget* m_databaseOpenWidget; KeePass1OpenWidget* m_keepass1OpenWidget; UnlockDatabaseWidget* m_unlockDatabaseWidget; + QSplitter* m_splitter; GroupView* m_groupView; EntryView* m_entryView; Group* m_newGroup; @@ -152,7 +166,7 @@ private: Group* m_newParent; Group* m_lastGroup; QTimer* m_searchTimer; - QWidget* widgetBeforeLock; + QWidget* m_widgetBeforeLock; QString m_filename; FileSystemWatcher m_file_watcher; }; diff --git a/src/gui/DatabaseWidgetStateSync.cpp b/src/gui/DatabaseWidgetStateSync.cpp new file mode 100644 index 000000000..66b8492e2 --- /dev/null +++ b/src/gui/DatabaseWidgetStateSync.cpp @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2014 Felix Geyer + * Copyright (C) 2014 Florian Geyer + * + * 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 . + */ + +#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 DatabaseWidgetStateSync::variantToIntList(const QVariant& variant) +{ + QVariantList list = variant.toList(); + QList 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& list) +{ + QVariantList result; + + Q_FOREACH (int value, list) { + result.append(value); + } + + return result; +} diff --git a/src/gui/DatabaseWidgetStateSync.h b/src/gui/DatabaseWidgetStateSync.h new file mode 100644 index 000000000..f6a87cd9a --- /dev/null +++ b/src/gui/DatabaseWidgetStateSync.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2014 Felix Geyer + * Copyright (C) 2014 Florian Geyer + * + * 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 . + */ + +#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 variantToIntList(const QVariant& variant); + static QVariant intListToVariant(const QList& list); + + DatabaseWidget* m_activeDbWidget; + + bool m_blockUpdates; + QList m_splitterSizes; + QList m_columnSizesList; + QList m_columnSizesSearch; +}; + +#endif // KEEPASSX_HEADERVIEWSYNC_H diff --git a/src/gui/EditWidgetIcons.cpp b/src/gui/EditWidgetIcons.cpp index 26314d360..9e8574535 100644 --- a/src/gui/EditWidgetIcons.cpp +++ b/src/gui/EditWidgetIcons.cpp @@ -194,8 +194,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)); } } } diff --git a/src/gui/KeePass1OpenWidget.cpp b/src/gui/KeePass1OpenWidget.cpp index 5f23d8070..96ddf13f2 100644 --- a/src/gui/KeePass1OpenWidget.cpp +++ b/src/gui/KeePass1OpenWidget.cpp @@ -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(); } } diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 36fb656f2..8c72bbff9 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -23,15 +23,11 @@ #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" const QString MainWindow::BaseWindowTitle = "KeePassX"; @@ -40,7 +36,9 @@ MainWindow::MainWindow() { m_ui->setupUi(this); - restoreGeometry(config()->get("window/Geometry").toByteArray()); + m_countDefaultAttributes = m_ui->menuEntryCopyAttribute->actions().size(); + + restoreGeometry(config()->get("GUI/MainWindowGeometry").toByteArray()); setWindowIcon(filePath()->applicationIcon()); QAction* toggleViewAction = m_ui->toolBar->toggleViewAction(); @@ -229,17 +227,16 @@ void MainWindow::updateCopyAttributesMenu() return; } - Entry* entry = dbWidget->entryView()->currentEntry(); - if (!entry || !dbWidget->entryView()->isSingleEntrySelected()) { + if (!dbWidget->numberOfSelectedEntries() == 1) { return; } QList actions = m_ui->menuEntryCopyAttribute->actions(); - for (int i = EntryAttributes::DefaultAttributes.size() + 1; i < actions.size(); i++) { + for (int i = m_countDefaultAttributes + 1; 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); } @@ -276,9 +273,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); @@ -294,7 +291,7 @@ 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()); + m_ui->actionGroupDelete->setEnabled(groupSelected && dbWidget->canDeleteCurrentGroup()); m_ui->actionSearch->setEnabled(true); // TODO: get checked state from db widget m_ui->actionSearch->setChecked(inSearch); @@ -313,6 +310,11 @@ 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); m_ui->actionSearch->setEnabled(false); @@ -335,6 +337,11 @@ 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); m_ui->actionSearch->setEnabled(false); @@ -430,7 +437,7 @@ void MainWindow::closeEvent(QCloseEvent* event) void MainWindow::saveWindowInformation() { - config()->set("window/Geometry", saveGeometry()); + config()->set("GUI/MainWindowGeometry", saveGeometry()); } bool MainWindow::saveLastDatabases() diff --git a/src/gui/MainWindow.h b/src/gui/MainWindow.h index 706fd2d50..e904426ae 100644 --- a/src/gui/MainWindow.h +++ b/src/gui/MainWindow.h @@ -77,6 +77,7 @@ private: QActionGroup* m_copyAdditionalAttributeActions; QStringList m_openDatabases; InactivityTimer* m_inactivityTimer; + int m_countDefaultAttributes; Q_DISABLE_COPY(MainWindow) }; diff --git a/src/gui/PasswordGeneratorWidget.ui b/src/gui/PasswordGeneratorWidget.ui index 342f191a8..5c75ef9ea 100644 --- a/src/gui/PasswordGeneratorWidget.ui +++ b/src/gui/PasswordGeneratorWidget.ui @@ -100,7 +100,7 @@ Upper Case Letters - A-Z + A-Z true @@ -116,7 +116,7 @@ Lower Case Letters - a-z + a-z true @@ -132,7 +132,7 @@ Numbers - 0-9 + 0-9 true @@ -148,7 +148,7 @@ Special Characters - /*_& ... + /*_& ... true diff --git a/src/gui/SearchWidget.ui b/src/gui/SearchWidget.ui index c3d59b8d1..ce4845dcd 100644 --- a/src/gui/SearchWidget.ui +++ b/src/gui/SearchWidget.ui @@ -11,7 +11,16 @@ - + + 0 + + + 0 + + + 0 + + 0 @@ -21,6 +30,9 @@ + + Qt::ClickFocus + true @@ -38,7 +50,16 @@ - + + 0 + + + 0 + + + 0 + + 0 diff --git a/src/gui/SettingsWidget.cpp b/src/gui/SettingsWidget.cpp index d09805939..374e52d97 100644 --- a/src/gui/SettingsWidget.cpp +++ b/src/gui/SettingsWidget.cpp @@ -21,6 +21,7 @@ #include "autotype/AutoType.h" #include "core/Config.h" +#include "core/Translator.h" SettingsWidget::SettingsWidget(QWidget* parent) : EditWidget(parent) @@ -67,6 +68,16 @@ void SettingsWidget::loadSettings() 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->autoTypeEntryTitleMatchCheckBox->setChecked(config()->get("AutoTypeEntryTitleMatch").toBool()); + + QList > 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); + } if (autoType()->isAvailable()) { m_globalAutoTypeKey = static_cast(config()->get("GlobalAutoTypeKey").toInt()); @@ -102,6 +113,10 @@ void SettingsWidget::saveSettings() config()->set("MinimizeOnCopy", m_generalUi->minimizeOnCopyCheckBox->isChecked()); config()->set("UseGroupIconOnEntryCreation", m_generalUi->useGroupIconOnEntryCreationCheckBox->isChecked()); + config()->set("AutoTypeEntryTitleMatch", + m_generalUi->autoTypeEntryTitleMatchCheckBox->isChecked()); + int currentLangIndex = m_generalUi->languageComboBox->currentIndex(); + config()->set("GUI/Language", m_generalUi->languageComboBox->itemData(currentLangIndex).toString()); if (autoType()->isAvailable()) { config()->set("GlobalAutoTypeKey", m_generalUi->autoTypeShortcutWidget->key()); config()->set("GlobalAutoTypeModifiers", diff --git a/src/gui/SettingsWidgetGeneral.ui b/src/gui/SettingsWidgetGeneral.ui index f9aa8b680..f3dc079e2 100644 --- a/src/gui/SettingsWidgetGeneral.ui +++ b/src/gui/SettingsWidgetGeneral.ui @@ -7,10 +7,13 @@ 0 0 456 - 185 + 288 + + QFormLayout::AllNonFixedFieldsGrow + @@ -21,6 +24,13 @@ + + + + Open previous databases on startup + + + @@ -31,13 +41,6 @@ - - - - Automatically save after every change - - - @@ -45,20 +48,10 @@ - - + + - Global Auto-Type shortcut - - - - - - - - - - Open previous databases on startup + Automatically save after every change @@ -76,6 +69,33 @@ + + + + Global Auto-Type shortcut + + + + + + + + + + Use entry title to match windows for global auto-type + + + + + + + Language + + + + + + diff --git a/src/gui/entry/EditEntryWidget.cpp b/src/gui/entry/EditEntryWidget.cpp index 4a6743324..465f5d401 100644 --- a/src/gui/entry/EditEntryWidget.cpp +++ b/src/gui/entry/EditEntryWidget.cpp @@ -590,14 +590,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; } @@ -783,13 +783,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; diff --git a/src/gui/entry/EntryView.cpp b/src/gui/entry/EntryView.cpp index f71f80bc6..cd2c6fbad 100644 --- a/src/gui/entry/EntryView.cpp +++ b/src/gui/entry/EntryView.cpp @@ -17,6 +17,7 @@ #include "EntryView.h" +#include #include #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& 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) diff --git a/src/gui/entry/EntryView.h b/src/gui/entry/EntryView.h index b5f056aa0..c11d0417d 100644 --- a/src/gui/entry/EntryView.h +++ b/src/gui/entry/EntryView.h @@ -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& entries); bool inEntryListMode(); + int numberOfSelectedEntries(); + void setFirstEntryActive(); public Q_SLOTS: void setGroup(Group* group); diff --git a/src/main.cpp b/src/main.cpp index abe7ceb4e..d5e64b92c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -21,6 +21,7 @@ #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" @@ -66,6 +67,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); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 8df0050aa..c094f8235 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -13,7 +13,6 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -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) diff --git a/tests/TestAutoType.cpp b/tests/TestAutoType.cpp index 47ac0909b..4f4350e71 100644 --- a/tests/TestAutoType.cpp +++ b/tests/TestAutoType.cpp @@ -21,6 +21,7 @@ #include #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(); - + 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 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 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()); + +} diff --git a/tests/TestAutoType.h b/tests/TestAutoType.h index fba7fde1c..d46a55969 100644 --- a/tests/TestAutoType.h +++ b/tests/TestAutoType.h @@ -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 m_dbList; Group* m_group; - Entry* m_entry; + Entry* m_entry1; + Entry* m_entry2; }; #endif // KEEPASSX_TESTAUTOTYPE_H diff --git a/tests/TestCryptoHash.cpp b/tests/TestCryptoHash.cpp index 4f258a179..d189d456c 100644 --- a/tests/TestCryptoHash.cpp +++ b/tests/TestCryptoHash.cpp @@ -23,6 +23,8 @@ #include "crypto/Crypto.h" #include "crypto/CryptoHash.h" +QTEST_GUILESS_MAIN(TestCryptoHash) + void TestCryptoHash::initTestCase() { Crypto::init(); @@ -47,5 +49,3 @@ void TestCryptoHash::test() QCOMPARE(cryptoHash3.result(), QByteArray::fromHex("0b56e5f65263e747af4a833bd7dd7ad26a64d7a4de7c68e52364893dca0766b4")); } - -QTEST_GUILESS_MAIN(TestCryptoHash) diff --git a/tests/TestDeletedObjects.cpp b/tests/TestDeletedObjects.cpp index 914096cec..5967e58ee 100644 --- a/tests/TestDeletedObjects.cpp +++ b/tests/TestDeletedObjects.cpp @@ -26,6 +26,8 @@ #include "format/KeePass2XmlReader.h" #include "config-keepassx-tests.h" +QTEST_GUILESS_MAIN(TestDeletedObjects) + void TestDeletedObjects::initTestCase() { Crypto::init(); @@ -158,5 +160,3 @@ void TestDeletedObjects::testDatabaseChange() delete db; delete db2; } - -QTEST_GUILESS_MAIN(TestDeletedObjects) diff --git a/tests/TestEntry.cpp b/tests/TestEntry.cpp index 15f398f18..481250a20 100644 --- a/tests/TestEntry.cpp +++ b/tests/TestEntry.cpp @@ -23,6 +23,8 @@ #include "core/Entry.h" #include "crypto/Crypto.h" +QTEST_GUILESS_MAIN(TestEntry) + void TestEntry::initTestCase() { Crypto::init(); @@ -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) diff --git a/tests/TestEntryModel.cpp b/tests/TestEntryModel.cpp index fab63db68..5347a3c52 100644 --- a/tests/TestEntryModel.cpp +++ b/tests/TestEntryModel.cpp @@ -33,6 +33,8 @@ #include "gui/entry/EntryAttachmentsModel.h" #include "gui/entry/EntryAttributesModel.h" +QTEST_GUILESS_MAIN(TestEntryModel) + void TestEntryModel::initTestCase() { qRegisterMetaType("QModelIndex"); @@ -341,5 +343,3 @@ void TestEntryModel::testDatabaseDelete() delete modelTest; delete model; } - -QTEST_GUILESS_MAIN(TestEntryModel) diff --git a/tests/TestEntrySearcher.cpp b/tests/TestEntrySearcher.cpp new file mode 100644 index 000000000..9f7ca1397 --- /dev/null +++ b/tests/TestEntrySearcher.cpp @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2014 Florian Geyer + * + * 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 . + */ + +#include "TestEntrySearcher.h" + +#include + +#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); +} diff --git a/tests/TestEntrySearcher.h b/tests/TestEntrySearcher.h new file mode 100644 index 000000000..7c45451dc --- /dev/null +++ b/tests/TestEntrySearcher.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2014 Florian Geyer + * + * 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 . + */ + + +#ifndef KEEPASSX_TESTENTRYSEARCHER_H +#define KEEPASSX_TESTENTRYSEARCHER_H + +#include + +#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 m_searchResult; +}; + +#endif // KEEPASSX_TESTENTRYSEARCHER_H diff --git a/tests/TestExporter.cpp b/tests/TestExporter.cpp new file mode 100644 index 000000000..16b4b23cb --- /dev/null +++ b/tests/TestExporter.cpp @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2014 Felix Geyer + * Copyright (C) 2014 Florian Geyer + * + * 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 . + */ + +#include "TestExporter.h" + +#include + +#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() +{ + 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; +} + + + diff --git a/tests/TestExporter.h b/tests/TestExporter.h new file mode 100644 index 000000000..15f9a7c33 --- /dev/null +++ b/tests/TestExporter.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2014 Felix Geyer + * Copyright (C) 2014 Florian Geyer + * + * 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 . + */ + +#ifndef KEEPASSX_TESTEXPORTER_H +#define KEEPASSX_TESTEXPORTER_H + +#include + +class TestExporter : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void initTestCase(); + void testToDbExporter(); +}; + +#endif // KEEPASSX_TESTEXPORTER_H diff --git a/tests/TestGroup.cpp b/tests/TestGroup.cpp index 86b55b706..32398f01f 100644 --- a/tests/TestGroup.cpp +++ b/tests/TestGroup.cpp @@ -27,6 +27,8 @@ #include "core/Metadata.h" #include "crypto/Crypto.h" +QTEST_GUILESS_MAIN(TestGroup) + void TestGroup::initTestCase() { qRegisterMetaType("Entry*"); @@ -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 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 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) diff --git a/tests/TestGroup.h b/tests/TestGroup.h index 895c2cc5a..c612a3ac6 100644 --- a/tests/TestGroup.h +++ b/tests/TestGroup.h @@ -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 diff --git a/tests/TestGroupModel.cpp b/tests/TestGroupModel.cpp index a16386c7d..fe7018e84 100644 --- a/tests/TestGroupModel.cpp +++ b/tests/TestGroupModel.cpp @@ -27,6 +27,8 @@ #include "crypto/Crypto.h" #include "gui/group/GroupModel.h" +QTEST_GUILESS_MAIN(TestGroupModel) + void TestGroupModel::initTestCase() { qRegisterMetaType("QModelIndex"); @@ -149,5 +151,3 @@ void TestGroupModel::test() delete modelTest; delete model; } - -QTEST_GUILESS_MAIN(TestGroupModel) diff --git a/tests/TestHashedBlockStream.cpp b/tests/TestHashedBlockStream.cpp index ab7d386a6..b062908f7 100644 --- a/tests/TestHashedBlockStream.cpp +++ b/tests/TestHashedBlockStream.cpp @@ -24,6 +24,8 @@ #include "crypto/Crypto.h" #include "streams/HashedBlockStream.h" +QTEST_GUILESS_MAIN(TestHashedBlockStream) + void TestHashedBlockStream::initTestCase() { Crypto::init(); @@ -69,5 +71,3 @@ void TestHashedBlockStream::testWriteRead() buffer.reset(); buffer.buffer().clear(); } - -QTEST_GUILESS_MAIN(TestHashedBlockStream) diff --git a/tests/TestKeePass1Reader.cpp b/tests/TestKeePass1Reader.cpp index 3ec4e7830..423087c91 100644 --- a/tests/TestKeePass1Reader.cpp +++ b/tests/TestKeePass1Reader.cpp @@ -33,6 +33,8 @@ #include "keys/FileKey.h" #include "keys/PasswordKey.h" +QTEST_GUILESS_MAIN(TestKeePass1Reader) + void TestKeePass1Reader::initTestCase() { Crypto::init(); @@ -292,5 +294,3 @@ void TestKeePass1Reader::reopenDatabase(Database* db, const QString& password, c QVERIFY(!reader.hasError()); delete newDb; } - -QTEST_GUILESS_MAIN(TestKeePass1Reader) diff --git a/tests/TestKeePass2RandomStream.cpp b/tests/TestKeePass2RandomStream.cpp index 74a154069..328decb30 100644 --- a/tests/TestKeePass2RandomStream.cpp +++ b/tests/TestKeePass2RandomStream.cpp @@ -26,6 +26,8 @@ #include "format/KeePass2.h" #include "format/KeePass2RandomStream.h" +QTEST_GUILESS_MAIN(TestKeePass2RandomStream) + void TestKeePass2RandomStream::initTestCase() { Crypto::init(); @@ -77,5 +79,3 @@ void TestKeePass2RandomStream::test() QCOMPARE(cipherData, cipherDataEncrypt); QCOMPARE(randomStreamData, cipherData); } - -QTEST_GUILESS_MAIN(TestKeePass2RandomStream) diff --git a/tests/TestKeePass2Reader.cpp b/tests/TestKeePass2Reader.cpp index 6b1ee1e95..3dd87c4f4 100644 --- a/tests/TestKeePass2Reader.cpp +++ b/tests/TestKeePass2Reader.cpp @@ -28,6 +28,8 @@ #include "format/KeePass2Reader.h" #include "keys/PasswordKey.h" +QTEST_GUILESS_MAIN(TestKeePass2Reader) + void TestKeePass2Reader::initTestCase() { Crypto::init(); @@ -154,5 +156,3 @@ void TestKeePass2Reader::testFormat300() delete db; } - -QTEST_GUILESS_MAIN(TestKeePass2Reader) diff --git a/tests/TestKeePass2Writer.cpp b/tests/TestKeePass2Writer.cpp index bbc4992a0..f00b38e86 100644 --- a/tests/TestKeePass2Writer.cpp +++ b/tests/TestKeePass2Writer.cpp @@ -29,6 +29,8 @@ #include "format/KeePass2Writer.h" #include "keys/PasswordKey.h" +QTEST_GUILESS_MAIN(TestKeePass2Writer) + void TestKeePass2Writer::initTestCase() { Crypto::init(); @@ -104,5 +106,3 @@ void TestKeePass2Writer::cleanupTestCase() delete m_dbOrg; delete m_dbTest; } - -QTEST_GUILESS_MAIN(TestKeePass2Writer) diff --git a/tests/TestKeePass2XmlReader.cpp b/tests/TestKeePass2XmlReader.cpp index ca57db958..d9935804d 100644 --- a/tests/TestKeePass2XmlReader.cpp +++ b/tests/TestKeePass2XmlReader.cpp @@ -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) @@ -378,5 +380,3 @@ void TestKeePass2XmlReader::cleanupTestCase() { delete m_db; } - -QTEST_GUILESS_MAIN(TestKeePass2XmlReader) diff --git a/tests/TestKeys.cpp b/tests/TestKeys.cpp index ec9a35b4b..d5cba4abc 100644 --- a/tests/TestKeys.cpp +++ b/tests/TestKeys.cpp @@ -31,6 +31,8 @@ #include "keys/FileKey.h" #include "keys/PasswordKey.h" +QTEST_GUILESS_MAIN(TestKeys) + void TestKeys::initTestCase() { Crypto::init(); @@ -184,5 +186,3 @@ void TestKeys::benchmarkTransformKey() compositeKey.transform(seed, 1e6); } } - -QTEST_GUILESS_MAIN(TestKeys) diff --git a/tests/TestModified.cpp b/tests/TestModified.cpp index 864ea1c36..85a6bf23e 100644 --- a/tests/TestModified.cpp +++ b/tests/TestModified.cpp @@ -27,6 +27,8 @@ #include "core/Tools.h" #include "crypto/Crypto.h" +QTEST_GUILESS_MAIN(TestModified) + void TestModified::initTestCase() { Crypto::init(); @@ -466,5 +468,3 @@ void TestModified::testHistoryItem() delete db; } - -QTEST_GUILESS_MAIN(TestModified) diff --git a/tests/TestQCommandLineParser.cpp b/tests/TestQCommandLineParser.cpp index d487862c0..4e2c63505 100644 --- a/tests/TestQCommandLineParser.cpp +++ b/tests/TestQCommandLineParser.cpp @@ -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) diff --git a/tests/TestQSaveFile.cpp b/tests/TestQSaveFile.cpp index bccee0ec4..443db5299 100644 --- a/tests/TestQSaveFile.cpp +++ b/tests/TestQSaveFile.cpp @@ -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) diff --git a/tests/TestRandom.cpp b/tests/TestRandom.cpp index 8ac570e1c..40ab702db 100644 --- a/tests/TestRandom.cpp +++ b/tests/TestRandom.cpp @@ -22,6 +22,8 @@ #include +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) diff --git a/tests/TestSymmetricCipher.cpp b/tests/TestSymmetricCipher.cpp index b47a00576..9f05db1bd 100644 --- a/tests/TestSymmetricCipher.cpp +++ b/tests/TestSymmetricCipher.cpp @@ -25,6 +25,8 @@ #include "crypto/SymmetricCipher.h" #include "streams/SymmetricCipherStream.h" +QTEST_GUILESS_MAIN(TestSymmetricCipher) + void TestSymmetricCipher::initTestCase() { Crypto::init(); @@ -192,5 +194,3 @@ void TestSymmetricCipher::testPadding() QByteArray decrypted = streamDec.readAll(); QCOMPARE(decrypted, plainText); } - -QTEST_GUILESS_MAIN(TestSymmetricCipher) diff --git a/tests/TestWildcardMatcher.cpp b/tests/TestWildcardMatcher.cpp index e06125b86..dc9991db9 100644 --- a/tests/TestWildcardMatcher.cpp +++ b/tests/TestWildcardMatcher.cpp @@ -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) diff --git a/tests/gui/TestGui.cpp b/tests/gui/TestGui.cpp index a4d04c5de..51dc39e5d 100644 --- a/tests/gui/TestGui.cpp +++ b/tests/gui/TestGui.cpp @@ -83,7 +83,7 @@ void TestGui::testTabs() void TestGui::testEditEntry() { - EntryView* entryView = m_dbWidget->entryView(); + EntryView* entryView = m_dbWidget->findChild("entryView"); QModelIndex item = entryView->model()->index(0, 1); QRect itemRect = entryView->visualRect(item); QTest::mouseClick(entryView->viewport(), Qt::LeftButton, Qt::NoModifier, itemRect.center()); @@ -237,8 +237,8 @@ void TestGui::testSearch() void TestGui::testDeleteEntry() { - GroupView* groupView = m_dbWidget->groupView(); - EntryView* entryView = m_dbWidget->entryView(); + GroupView* groupView = m_dbWidget->findChild("groupView"); + EntryView* entryView = m_dbWidget->findChild("entryView"); QToolBar* toolBar = m_mainWindow->findChild("toolBar"); QAction* entryDeleteAction = m_mainWindow->findChild("actionEntryDelete"); QWidget* entryDeleteWidget = toolBar->widgetForAction(entryDeleteAction); @@ -274,7 +274,7 @@ void TestGui::testDeleteEntry() void TestGui::testCloneEntry() { - EntryView* entryView = m_dbWidget->entryView(); + EntryView* entryView = m_dbWidget->findChild("entryView"); QCOMPARE(entryView->model()->rowCount(), 1); @@ -292,8 +292,8 @@ void TestGui::testCloneEntry() void TestGui::testDragAndDropEntry() { - EntryView* entryView = m_dbWidget->entryView(); - GroupView* groupView = m_dbWidget->groupView(); + EntryView* entryView = m_dbWidget->findChild("entryView"); + GroupView* groupView = m_dbWidget->findChild("groupView"); QAbstractItemModel* groupModel = groupView->model(); QModelIndex sourceIndex = entryView->model()->index(0, 1); @@ -314,7 +314,7 @@ void TestGui::testDragAndDropEntry() void TestGui::testDragAndDropGroup() { - QAbstractItemModel* groupModel = m_dbWidget->groupView()->model(); + QAbstractItemModel* groupModel = m_dbWidget->findChild("groupView")->model(); QModelIndex rootIndex = groupModel->index(0, 0); dragAndDropGroup(groupModel->index(0, 0, rootIndex), @@ -453,7 +453,7 @@ void TestGui::dragAndDropGroup(const QModelIndex& sourceIndex, const QModelIndex QVERIFY(sourceIndex.isValid()); QVERIFY(targetIndex.isValid()); - GroupModel* groupModel = qobject_cast(m_dbWidget->groupView()->model()); + GroupModel* groupModel = qobject_cast(m_dbWidget->findChild("groupView")->model()); QMimeData mimeData; QByteArray encoded;