diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..15af85bf --- /dev/null +++ b/.travis.yml @@ -0,0 +1,19 @@ +os: + - linux + - osx +compiler: + - gcc + - clang +language: cpp +install: + - if [ "$TRAVIS_OS_NAME" = "linux" ]; then sudo apt-get -qq update; fi + - if [ "$TRAVIS_OS_NAME" = "linux" ]; then sudo apt-get -qq install cmake libqt4-dev libgcrypt11-dev zlib1g-dev libxtst-dev; fi + - if [ "$TRAVIS_OS_NAME" = "osx" ]; then brew update; fi + - if [ "$TRAVIS_OS_NAME" = "osx" ]; then brew install cmake qt libgcrypt; fi +before_script: mkdir build && pushd build +script: + - cmake -DCMAKE_BUILD_TYPE=Debug -DWITH_GUI_TESTS=ON .. + - make + - if [ "$TRAVIS_OS_NAME" = "linux" ]; then make test ARGS+="-E testgui"; fi + - if [ "$TRAVIS_OS_NAME" = "linux" ]; then xvfb-run -a --server-args="-screen 0 800x600x24" make test ARGS+="-R testgui"; fi + - if [ "$TRAVIS_OS_NAME" = "osx" ]; then make test; fi diff --git a/.tx/config b/.tx/config new file mode 100644 index 00000000..015acf4b --- /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/CMakeLists.txt b/CMakeLists.txt index ad5c9c00..28deb054 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,7 +32,7 @@ include(CheckCXXSourceCompiles) option(WITH_TESTS "Enable building of unit tests" ON) option(WITH_GUI_TESTS "Enable building of GUI tests" OFF) option(WITH_LTO "Enable Link Time Optimization (LTO)" OFF) -option(WITH_CXX11 "Build with the C++ 11 standard" OFF) +option(WITH_CXX11 "Build with the C++ 11 standard" ON) set(KEEPASSX_VERSION "2.0 alpha 6") set(KEEPASSX_VERSION_NUM "1.9.85") @@ -165,6 +165,9 @@ endif() find_package(Qt4 4.6.0 REQUIRED ${QT_REQUIRED_MODULES}) include(${QT_USE_FILE}) +# Debian sets the the build type to None for package builds. +# Make sure we don't enable asserts there. +set_property(DIRECTORY APPEND PROPERTY COMPILE_DEFINITIONS_NONE QT_NO_DEBUG) find_package(Gcrypt REQUIRED) if(NOT (${GCRYPT_VERSION_STRING} VERSION_LESS "1.6.0")) diff --git a/INSTALL b/INSTALL index 028ccff2..bde991bb 100644 --- a/INSTALL +++ b/INSTALL @@ -2,7 +2,7 @@ Building: ========= mkdir build cd build -cmake .. [CMAKE PARAMETERS] +cmake [CMAKE PARAMETERS] .. make [-jX] Common cmake parameters: diff --git a/README.md b/README.md index d7483c6c..cc0e0a8b 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,128 @@ -# KeePassX + keepasshttp + autotype +# KeePassX + keepasshttp -This code extends the brilliant [KeePassX](https://www.keepassx.org/) program -which accesses [KeePass](http://keepass.info/) password databases. +## About -I have merged the latest version of the code with Francois Ferrand's -[keepassx-http](https://gitorious.org/keepassx/keepassx-http/) repository. -This adds support for the [keepasshttp](https://github.com/pfn/keepasshttp/) -protocol, enabling automatic form-filling in web browsers. This is accomplished -via a compatible browser plugin such as -[PassIFox](https://passifox.appspot.com/passifox.xpi) for Mozilla Firefox and -[chromeIPass](https://chrome.google.com/webstore/detail/chromeipass/ompiailgknfdndiefoaoiligalphfdae) -for Google Chrome. +KeePassX is an application for people with extremely high demands on secure personal data management. +It has a light interface, is cross platform and published under the terms of the GNU General Public License. -I have also added global autotype for OSX machines and added a few other minor -tweaks and bugfixes. +KeePassX saves many different information e.g. user names, passwords, urls, attachments and comments in one single database. +For a better management user-defined titles and icons can be specified for each single entry. +Furthermore the entries are sorted in groups, which are customizable as well. The integrated search function allows to search in a single group or the complete database. +KeePassX offers a little utility for secure password generation. The password generator is very customizable, fast and easy to use. +Especially someone who generates passwords frequently will appreciate this feature. +The complete database is always encrypted with the AES (aka Rijndael) encryption algorithm using a 256 bit key. +Therefore the saved information can be considered as quite safe. KeePassX uses a database format that is compatible with [KeePass Password Safe](http://keepass.info/). +This makes the use of that application even more favorable. + +## Install + +KeePassX can be downloaded and installed using an assortment of installers available on the main [KeePassX website](http://www.keepassx.org). +KeePassX can also be installed from the official repositories of many Linux repositories. +If you wish to build KeePassX from source, rather than rely on the pre-compiled binaries, you may wish to read up on the _From Source_ section. + +### Debian + +To install KeePassX from the Debian repository: + +```bash +sudo apt-get install keepassx +``` + +### Red Hat + +Install KeePassX from the Red Hat (or CentOS) repository: + +```bash +sudo yum install keepassx +``` + +### Windows / Mac OS X + +Download the installer from the KeePassX [download](https://www.keepassx.org/downloads) page. +Once downloaded, double click on the file to execute the installer. + +### From Source + +#### Build Dependencies + +The following tools must exist within your PATH: + +* make +* cmake (>= 2.6.4) +* g++ or clang++ + +The following libraries are required: + +* Qt 4 (>= 4.6) +* libgcrypt +* zlib + +On Debian you can install them with: + +```bash +sudo apt-get install build-essential cmake libqt4-dev libgcrypt11-dev zlib1g-dev +``` + +#### Build Steps + +To compile from source: + +```bash +mkdir build +cd build +cmake .. +make [-jX] +``` + +You will have the compiled KeePassX binary inside the `./build/src/` directory. + +To install this binary execute the following: + +```bash +sudo make install +``` + +More detailed instructions available in the INSTALL file. + +## Contribute + +Coordination of work between developers is handled through the [KeePassX development](https://www.keepassx.org/dev/) site. +Requests for enhancements, or reports of bugs encountered, can also be reported through the KeePassX development site. +However, members of the open-source community are encouraged to submit pull requests directly through GitHub. + +### Clone Repository + +Clone the repository to a suitable location where you can extend and build this project. + +```bash +git clone https://github.com/keepassx/keepassx.git +``` + +**Note:** This will clone the entire contents of the repository at the HEAD revision. + +To update the project from within the project's folder you can run the following command: + +```bash +git pull +``` + +### Feature Requests + +We're always looking for suggestions to improve our application. If you have a suggestion for improving an existing feature, +or would like to suggest a completely new feature for KeePassX, please file a ticket on the [KeePassX development](https://www.keepassx.org/dev/) site. + +### Bug Reports + +Our software isn't always perfect, but we strive to always improve our work. You may file bug reports on the [KeePassX development](https://www.keepassx.org/dev/) site. + +### Pull Requests + +Along with our desire to hear your feedback and suggestions, we're also interested in accepting direct assistance in the form of code. + +Issue merge requests against our [GitHub repository](https://github.com/keepassx/keepassx). + +### Translations + +Translations are managed on [Transifex](https://www.transifex.com/projects/p/keepassx/) which offers a web interface. +Please join an existing language team or request a new one if there is none. diff --git a/share/CMakeLists.txt b/share/CMakeLists.txt index 7069c6c4..0e2b7fa9 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 00000000..b1aa8785 --- /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 00000000..5433c3ce --- /dev/null +++ b/share/translations/keepassx_de.ts @@ -0,0 +1,1177 @@ + + + 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. + KeePassX ist unter der GNU General Public License (GPL) version 2 (version 3) veröffentlicht. + + + + AutoType + + Auto-Type - KeePassX + Auto-Type - KeePassX + + + Couldn't find an entry that matches the window title. + Konnte dem Fenstertitel keinen passenden Eintrag zuordnen. + + + + AutoTypeAssociationsModel + + Window + Fenster + + + Sequence + Reihenfolge + + + Default sequence + Standardreihenfolge + + + + AutoTypeSelectDialog + + Auto-Type - KeePassX + Auto-Type - KeePassX + + + Select entry to Auto-Type: + Wählen Sie einen Eintrag für Auto-Type: + + + + ChangeMasterKeyWidget + + Password + Passwort + + + Enter password: + Passwort eingeben: + + + Repeat password: + Passwort wiederholen: + + + Key file + Schlüsseldatei + + + Browse + Durchsuchen + + + Create + Erstellen + + + Key files + Schlüsseldateien + + + All files + Alle Dateien + + + Create Key File... + Erzeuge eine Schlüsseldatei... + + + Error + Fehler + + + Unable to create Key File : + Erzeugen der Schlüsseldatei nicht möglich: + + + Select a key file + Schlüsseldatei auswählen + + + Question + Frage + + + Do you really want to use an empty string as password? + Wollen Sie wirklich eine leere Zeichenkette als Passwort verwenden? + + + Different passwords supplied. + Unterschiedliche Passwörter eingegeben. + + + + DatabaseOpenWidget + + Enter master key + Hauptschlüssel eingeben + + + Key File: + Schlüsseldatei: + + + Password: + Passwort: + + + Browse + Durchsuchen + + + Error + Fehler + + + Unable to open the database. + Öffnen der Datenbank nicht möglich. + + + Can't open key file + Schlüsseldatein kann nicht geöffnet werden + + + All files + Alle Dateien + + + Key files + Schlüsseldateien + + + Select key file + Schlüsseldatei auswählen + + + + DatabaseSettingsWidget + + Database name: + Datenbankname: + + + Database description: + Datenbankbeschreibung: + + + Transform rounds: + Verschlüsselungsdurchläufe: + + + Default username: + Standardbenutzername: + + + Use recycle bin: + Verwende Papierkorb: + + + MiB + MiB + + + Benchmark + Benchmark + + + Max. history items: + Max Einträge im Verlauf: + + + Max. history size: + Max. Verlaufsgröße: + + + + DatabaseTabWidget + + Root + Root + + + KeePass 2 Database + KeePass 2 Datenbank + + + All files + Alle Dateien + + + Open database + Datenbank öffnen + + + Warning + Warnung + + + File not found! + Datei nicht gefunden! + + + Open KeePass 1 database + KeePass 1 Datenbank öffnen + + + KeePass 1 database + KeePass 1 Datenbank + + + All files (*) + Alle Dateien (*) + + + Close? + Schließen? + + + "%1" is in edit mode. +Close anyway? + "%1" wird bearbeitet. +Trotzdem schließen? + + + Save changes? + Änderungen speichern? + + + "%1" was modified. +Save changes? + "%1" wurde geändert. +Änderungen speichern? + + + Error + Fehler + + + Writing the database failed. + Schreiben der Datenbank fehlgeschlagen. + + + Save database as + Datenbank speichern unter + + + New database + Neue Datenbank + + + locked + gesperrt + + + + DatabaseWidget + + Change master key + Hauptschlüssel ändern + + + Delete entry? + Eintrag löschen? + + + Do you really want to delete the entry "%1" for good? + Wollen Sie den Eintrag "%1" wirklich löschen? + + + Delete entries? + Einträge löschen? + + + Do you really want to delete %1 entries for good? + Wollen Sie die Einträge "%1" wirklich löschen? + + + Move entries to recycle bin? + Einträge in den Papierkorb verschieben? + + + Do you really want to move %n entry(s) to the recycle bin? + Wollen Sie wirklich %n Eintrag in den Papierkorb verschieben?Wollen Sie wirklich %n Einträge in den Papierkorb verschieben? + + + Delete group? + Gruppe löschen? + + + Do you really want to delete the group "%1" for good? + Wollen Sie die Gruppe "%1" wirklich löschen? + + + Current group + Aktuelle Gruppe + + + + EditEntryWidget + + Entry + Eintrag + + + Advanced + Fortgeschritten + + + Icon + Symbol + + + Auto-Type + Auto-Type + + + Properties + Eigenschaften + + + History + Verlauf + + + Entry history + Eintragsverlauf + + + Add entry + Eintrag hinzufügen + + + Edit entry + Eintrag bearbeiten + + + Error + Fehler + + + Different passwords supplied. + Unterschiedliche Passwörter eingegeben. + + + New attribute + Neue Eigenschaft + + + Select file + Datei wählen + + + Unable to open file + Öffnen der Datei nicht möglich + + + Save attachment + Anhang speichern + + + Unable to save the attachment: + + Speichern des Anhangs nicht möglich: + + + Tomorrow + Morgen + + + %n week(s) + %n Woche%n Wochen + + + %n month(s) + %n Monat%n Monaten + + + 1 year + 1 Jahr + + + + EditEntryWidgetAdvanced + + Additional attributes + Zusätzliche Eigenschaften + + + Add + Hinzufügen + + + Edit + Bearbeiten + + + Remove + Entfernen + + + Attachments + Anhänge + + + Save + Speichern + + + + EditEntryWidgetAutoType + + Enable Auto-Type for this entry + Auto-Type für diesen Eintrag aktivieren + + + Inherit default Auto-Type sequence from the group + Standard-Auto-Type-Sequenz von der Gruppe erben + + + Use custom Auto-Type sequence: + Benutzerdefinierte Auto-Type-Sequenz benutzen: + + + + + + + + + - + - + + + Window title: + Fenstertitel: + + + Use default sequence + Standardsequenz benutzen + + + Set custom sequence: + Benutzerdefinierte Sequenz verwenden: + + + + EditEntryWidgetHistory + + Show + Anzeigen + + + Restore + Wiederherstellen + + + Delete + Löschen + + + Delete all + Alle löschen + + + + EditEntryWidgetMain + + Title: + Titel: + + + Username: + Benutzername: + + + Password: + Passwort: + + + Repeat: + Wiederholen: + + + Gen. + Gen. + + + URL: + URL: + + + Expires + Erlischt + + + Presets + Vorgaben + + + Notes: + Notizen: + + + + EditGroupWidget + + Group + Gruppe + + + Icon + Symbol + + + Properties + Eigenschaften + + + Add group + Gruppe hinzufügen + + + Edit group + Gruppe bearbeiten + + + Enable + Aktivieren + + + Disable + Deaktivieren + + + Inherit from parent group (%1) + Von der übergeordneten Gruppe (%1) erben + + + + EditGroupWidgetMain + + Name + Name + + + Notes + Notizen + + + Expires + Erlischt + + + Search + Suche + + + Auto-type + Auto-type + + + + EditWidgetIcons + + Use default icon + Standardsymbol verwenden + + + Use custom icon + Benutzerdefiniertes Symbol verwenden + + + Add custom icon + Benutzerdefiniertes Symbol hinzufügen + + + Delete custom icon + Benutzerdefiniertes Symbol löschen + + + Images + Bilder + + + All files + Alle Dateien + + + Select Image + Bild auswählen + + + Can't delete icon! + Symbol kann nicht gelöscht werden! + + + Can't delete icon. Still used by %n item(s). + Symbol kann nicht gelöscht werden. Es wird von %n Eintrag verwendet.Symbol kann nicht gelöscht werden. Es wird von %n Einträgen verwendet. + + + + EditWidgetProperties + + Created: + Erstellt: + + + Modified: + Bearbeitet: + + + Accessed: + Zugegriffen: + + + Uuid: + Uuid: + + + + EntryAttributesModel + + Name + Name + + + + EntryHistoryModel + + Last modified + Zuletzt geändert + + + Title + Titel + + + Username + Benutzername + + + URL + URL + + + + EntryModel + + Group + Gruppe + + + Title + Titel + + + Username + Benutzername + + + URL + URL + + + + Group + + Recycle Bin + Papierkorb + + + + KeePass1OpenWidget + + Import KeePass1 database + KeePass 1 Datenbank importieren + + + Error + Fehler + + + Unable to open the database. + Öffnen der Datenbank nicht möglich. + + + + KeePass1Reader + + Unable to read keyfile. + Lesen der Schlüsseldatei nicht möglich. + + + Not a KeePass database. + Keine KeePass-Datenbank. + + + Unsupported encryption algorithm. + Nicht unterstützter Verschlüsselungsalgorithmus. + + + Unsupported KeePass database version. + Nicht unterstützte KeePass-Datenbankversion. + + + Root + Root + + + + KeePass2Reader + + Not a KeePass database. + Keine KeePass-Datenbank. + + + Unsupported KeePass database version. + Nicht unterstützte KeePass-Datenbankversion. + + + Wrong key or database file is corrupt. + Falscher Schlüssel oder die Datei ist beschädigt. + + + + MainWindow + + Database + Datenbank + + + Recent databases + Aktuelle Datenbanken + + + Help + Hilfe + + + Entries + Einträge + + + Copy attribute to clipboard + Eingenschaft in die Zwischenablage kopieren + + + Groups + Gruppen + + + Extras + Extras + + + View + Ansicht + + + Quit + Beenden + + + About + Über + + + Open database + Datenbank öffnen + + + Save database + Datenbank speichern + + + Close database + Datenbank schließen + + + New database + Neue Datenbank + + + Add new entry + Neuen Eintrag hinzufügen + + + View/Edit entry + Eintrag anzeigen/bearbeiten + + + Delete entry + Eintrag löschen + + + Add new group + Neue Gruppe hinzufügen + + + Edit group + Gruppe bearbeiten + + + Delete group + Gruppe löschen + + + Save database as + Datenbank speichern als + + + Change master key + Hauptschlüssel ändern + + + Database settings + Datenbankeinstellungen + + + Import KeePass 1 database + KeePass 1 Datenbank importieren + + + Clone entry + Eintrag klonen + + + Find + Suchen + + + Username + Benutzername + + + Copy username to clipboard + Benutzername in die Zwischenablage kopieren + + + Password + Passwort + + + Copy password to clipboard + Passwort in die Zwischenablage kopieren + + + Settings + Einstellungen + + + Perform Auto-Type + Auto-Type ausführen + + + Open URL + URL öffnen + + + Lock databases + Datenbank sperren + + + Title + Titel + + + URL + URL + + + Notes + Notizen + + + Show toolbar + Symbolleiste anzeigen + + + read-only + Nur Lesezugriff + + + + PasswordGeneratorWidget + + Password: + Passwort: + + + Length: + Länge: + + + Character Types + Zeichenarten + + + Upper Case Letters + Großbuchstaben + + + Lower Case Letters + Kleinbuchstaben + + + Numbers + Zahlen + + + Special Characters + Sonderzeichen + + + Exclude look-alike characters + Gleich aussehende Zeichen ausschließen + + + Ensure that the password contains characters from every group + Sicher stellen, dass das Passwort Zeichen aller Gruppen enthält + + + Accept + Akzeptieren + + + + QCommandLineParser + + Displays version information. + Versionsinformationen anzeigen. + + + Displays this help. + Zeigt diese Hilfe an. + + + Unknown option '%1'. + Unbekannte Option '%1'. + + + Unknown options: %1. + Unbekannte Optionen: '%1'. + + + Missing value after '%1'. + Fehlender Wert nach '%1'. + + + Unexpected value after '%1'. + Unerwarteter Wert nach '%1'. + + + [options] + [Optionen] + + + Usage: %1 + Verwendung: %1 + + + Options: + Optionen: + + + Arguments: + Argumente: + + + + QSaveFile + + Existing file %1 is not writable + Bestehende Datei(en) %1 ist nicht schreibbar + + + Writing canceled by application + Schreiben von der Applikation abgebrochen + + + Partial write. Partition full? + Unvollständiger Schreibvorgang. Partition voll? + + + + QtIOCompressor + + Internal zlib error when compressing: + Interner Fehler in zlib beim komprimieren: + + + Error writing to underlying device: + Fehler beim Schreiben auf das zugrunde liegende Gerät: + + + Error opening underlying device: + Fehler beim Öffnen des zugrunde liegenden Gerätes: + + + Error reading data from underlying device: + Fehler beim Lesen von Daten auf dem zugrunde liegenden Gerät: + + + Internal zlib error when decompressing: + Interner Fehler in zlib beim dekomprimieren: + + + + QtIOCompressor::open + + The gzip format not supported in this version of zlib. + Das gzip-Format wird von dieser zlib Version nicht unterstützt. + + + Internal zlib error: + Interner Fehler in zlib: + + + + SearchWidget + + Find: + Suchen nach: + + + Case sensitive + Groß-/Kleinschreibung unterscheiden + + + Current group + Aktuelle Gruppe + + + Root group + Root-Gruppe + + + + SettingsWidget + + Application Settings + Anwendungseinstellungen + + + General + Allgemein + + + Security + Sicherheit + + + + SettingsWidgetGeneral + + Remember last databases + Letzte Datenbank merken + + + Open previous databases on startup + Letzte Datenbank beim Starten öffnen + + + Mark as modified on expanded state changes + Als erweiterte Zustandsänderungen makieren + + + Automatically save on exit + Automatisch speichern beim Schließen + + + Automatically save after every change + Automatisch nach jeder Änderung speichern + + + Minimize when copying to clipboard + Minimieren beim Kopieren in die Zwischenablage + + + Use group icon on entry creation + Gruppensymbol für das Erstellen neuer Einträge verwenden + + + Global Auto-Type shortcut + Globale Tastenkombination für Auto-Type + + + Use entry title to match windows for global auto-type + Verwende den Eintragstitel für entsprechende Fenster für den globale Auto-Typ + + + + SettingsWidgetSecurity + + Clear clipboard after + Zwischenablage leeren nach + + + sec + sek + + + Lock databases after inactivity of + Datenbank sperren nach einer Inaktivität von + + + Show passwords in cleartext by default + Passwort standartmäßig in Klartext anzeigen + + + Always ask before performing auto-type + Immer vor einem Auto-type fragen + + + + UnlockDatabaseWidget + + Unlock database + Datenbank entsperren + + + Error + Fehler + + + Wrong key. + Falscher Schlüssel. + + + + WelcomeWidget + + Welcome! + Willkommen! + + + + main + + KeePassX - cross-platform password manager + KeePassX - plattformübergreifender Passwortmanager + + + filename of the password database to open (*.kdbx) + Dateiname für die zu öffnende Passwortdatenbank (*.kdbx) + + + path to a custom config file + Pfad zu einer benutzerdefinierten Konfigurationsdatei + + + password of the database (DANGEROUS!) + Passwort der Datenbank (GEFÄHRLICH!) + + + key file of the database + Schlüsseldatei der Datenbank + + + \ 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 00000000..a7b5b109 --- /dev/null +++ b/share/translations/keepassx_en.ts @@ -0,0 +1,1216 @@ + + + + + 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. + + + + + Main + + Fatal error while testing the cryptographic functions. + + + + KeePassX - Error + + + + + 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 + + + + Toggle window + + + + + 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 + + + + Language + + + + Show a system tray icon + + + + Hide window to system tray when minimized + + + + + 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 00000000..006f6f6e --- /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/keepassx_it.ts b/share/translations/keepassx_it.ts new file mode 100644 index 00000000..4f91b75a --- /dev/null +++ b/share/translations/keepassx_it.ts @@ -0,0 +1,1179 @@ + + + AboutDialog + + About KeePassX + A proposito di KeePassX + + + KeePassX is distributed under the term of the GNU General Public License (GPL) version 2 or (at your option) version 3. + KeePassX è distribuito sotto i termini della licenza +GNU General Public License (GPL) versione 2 o, a tua scelta, della versione 3. + + + + AutoType + + Auto-Type - KeePassX + Auto-Type - KeePassX + + + Couldn't find an entry that matches the window title. + Impossibile trovare una voce che corrisponda al titolo della finestra + + + + AutoTypeAssociationsModel + + Window + Finestra + + + Sequence + Sequenza + + + Default sequence + Sequenza predefinita + + + + AutoTypeSelectDialog + + Auto-Type - KeePassX + Auto-Type - KeePassX + + + Select entry to Auto-Type: + Selezionare una voce per Auto-Type: + + + + ChangeMasterKeyWidget + + Password + Password + + + Enter password: + Inserire password: + + + Repeat password: + Ripetere password: + + + Key file + File chiave + + + Browse + Sfogliare + + + Create + Creare + + + Key files + File chiave + + + All files + Tutti i file + + + Create Key File... + Creare file chiave... + + + Error + Errore + + + Unable to create Key File : + Impossibile creare file chiave: + + + Select a key file + Selezionare file chiave + + + Question + Domanda + + + Do you really want to use an empty string as password? + Vuoi veramente usare una stringa vuota come password? + + + Different passwords supplied. + Sono state fornite password differenti. + + + + DatabaseOpenWidget + + Enter master key + Inserire password + + + Key File: + File Chiave: + + + Password: + Password: + + + Browse + Sfogliare + + + Error + Errore + + + Unable to open the database. + Impossibile aprire il database. + + + Can't open key file + Impossibile aprire il file chiave + + + All files + Tutti i file + + + Key files + File chiave + + + Select key file + Selezionare file chiave + + + + DatabaseSettingsWidget + + Database name: + Nome database: + + + Database description: + Descrizione database: + + + Transform rounds: + Round di trasformazione: + + + Default username: + Nome utente predefinito: + + + Use recycle bin: + Utilizzare cestino: + + + MiB + MiB + + + Benchmark + Benchmark + + + Max. history items: + Max. oggetti nella cronologia: + + + Max. history size: + Max. grandezza della cronologia: + + + + DatabaseTabWidget + + Root + Root + + + KeePass 2 Database + Database KeePass 2 + + + All files + Tutti i file + + + Open database + Aprire database + + + Warning + Avviso + + + File not found! + File non trovato! + + + Open KeePass 1 database + Aprire database KeePass 1 + + + KeePass 1 database + Database KeePass 1 + + + All files (*) + Tutti i file (*) + + + Close? + Chiudere? + + + "%1" is in edit mode. +Close anyway? + "%1" è in modalità modifica. +Chiudere comunque? + + + Save changes? + Salvare modifiche? + + + "%1" was modified. +Save changes? + "%1" è stata modificata. +Salvare le modifiche? + + + Error + Errore + + + Writing the database failed. + Scrittura del database fallita. + + + Save database as + Salvare database come + + + New database + Nuovo database + + + locked + bloccato + + + + DatabaseWidget + + Change master key + Cambiare password principale + + + Delete entry? + Eliminare voce? + + + Do you really want to delete the entry "%1" for good? + Vuoi veramente eliminare la voce "%1"? + + + Delete entries? + Eliminare voci? + + + Do you really want to delete %1 entries for good? + Vuoi veramente eliminare %1 voci? + + + Move entries to recycle bin? + Muovere le voci nel cestino? + + + Do you really want to move %n entry(s) to the recycle bin? + Vuoi veramente spostare %n voce(i) nel cestino?Vuoi veramente spostare %n voce(i) nel cestino? + + + Delete group? + Eliminare gruppo? + + + Do you really want to delete the group "%1" for good? + Vuoi veramente eliminare il gruppo "%1"? + + + Current group + Gruppo corrente + + + + EditEntryWidget + + Entry + Voce + + + Advanced + Avanzate + + + Icon + Icona + + + Auto-Type + Auto-Type + + + Properties + Proprietà + + + History + Cronologia + + + Entry history + Cronologia voce + + + Add entry + Aggiungere voce + + + Edit entry + Modificare voce + + + Error + Errore + + + Different passwords supplied. + Sono state immesse password differenti. + + + New attribute + Nuovo attributo + + + Select file + Selezionare file + + + Unable to open file + Impossibile aprire il file + + + Save attachment + Salvare l'allegato + + + Unable to save the attachment: + + Impossibile salvare l'allegato + + + + Tomorrow + Domani + + + %n week(s) + %n settimana(e)%n settimana(e) + + + %n month(s) + %n mese(i)%n mese(i) + + + 1 year + 1 anno + + + + EditEntryWidgetAdvanced + + Additional attributes + Attributi addizionali + + + Add + Aggiungere + + + Edit + Modificare + + + Remove + Rimuovere + + + Attachments + Allegati + + + Save + Salvare + + + + EditEntryWidgetAutoType + + Enable Auto-Type for this entry + Abilitare Auto-Type per questa voce + + + Inherit default Auto-Type sequence from the group + Ereditare la sequenza predefinita di Auto-Type dal gruppo + + + Use custom Auto-Type sequence: + Usare sequenza personalizzata di Auto-Type: + + + + + + + + + - + - + + + Window title: + Titolo finestra: + + + Use default sequence + Usare sequenza predefinita + + + Set custom sequence: + Impostare sequenza personalizzata: + + + + EditEntryWidgetHistory + + Show + Mostrare + + + Restore + Ripristinare + + + Delete + Eliminare + + + Delete all + Eliminare tutti + + + + EditEntryWidgetMain + + Title: + Titolo: + + + Username: + Nome utente: + + + Password: + Password: + + + Repeat: + Ripetere: + + + Gen. + Gen. + + + URL: + URL: + + + Expires + Scade: + + + Presets + Programmare + + + Notes: + Note: + + + + EditGroupWidget + + Group + Gruppo + + + Icon + Icona + + + Properties + Proprietà + + + Add group + Aggiungere gruppo + + + Edit group + Modificare gruppo + + + Enable + Abilitare + + + Disable + Disabilitare + + + Inherit from parent group (%1) + Ereditare dal gruppo genitore (%1) + + + + EditGroupWidgetMain + + Name + Nome + + + Notes + Note + + + Expires + Scade + + + Search + Cercare + + + Auto-type + Auto-Type + + + + EditWidgetIcons + + Use default icon + Usare icona predefinita + + + Use custom icon + Usare icona personalizzata + + + Add custom icon + Aggiungere icona personalizzata + + + Delete custom icon + Rimuovere icona personalizzata + + + Images + Immagini + + + All files + Tutti i file + + + Select Image + Selezionare Immagine + + + Can't delete icon! + Impossibile eliminare icona! + + + Can't delete icon. Still used by %n item(s). + Impossibile eliminare l'icona in quanto è in uso da %n voce(i).Impossibile eliminare l'icona in quanto è in uso da %n voce(i). + + + + EditWidgetProperties + + Created: + Creato: + + + Modified: + Modificato: + + + Accessed: + Accesso: + + + Uuid: + Uuid: + + + + EntryAttributesModel + + Name + Nome + + + + EntryHistoryModel + + Last modified + Ultima modifica + + + Title + Titolo + + + Username + Nome utente + + + URL + URL + + + + EntryModel + + Group + Gruppo + + + Title + Titolo + + + Username + Nome Utente + + + URL + URL + + + + Group + + Recycle Bin + Cestino (Gruppo) + + + + KeePass1OpenWidget + + Import KeePass1 database + Importare database KeePass1 + + + Error + Errore + + + Unable to open the database. + Impossibile aprire il database. + + + + KeePass1Reader + + Unable to read keyfile. + Impossibile leggere il file chiave. + + + Not a KeePass database. + Non è un database KeePass. + + + Unsupported encryption algorithm. + Algoritmo di cifratura non supportato. + + + Unsupported KeePass database version. + Versione database non supportata + + + Root + Root (KeePass1Reader) + + + + KeePass2Reader + + Not a KeePass database. + Non è un database KeePass. + + + Unsupported KeePass database version. + Versione database non supportata + + + Wrong key or database file is corrupt. + Password errata o database corrotto. + + + + MainWindow + + Database + Database + + + Recent databases + Database recenti + + + Help + Aiuto + + + Entries + Voci + + + Copy attribute to clipboard + Copiare attributi negli appunti + + + Groups + Gruppi + + + Extras + Extra + + + View + Visualizzare + + + Quit + Uscire + + + About + A Proposito + + + Open database + Aprire database + + + Save database + Salvare database + + + Close database + Chiudere database + + + New database + Nuovo database + + + Add new entry + Aggiungere nuova voce + + + View/Edit entry + Visualizzare/Modificare voce + + + Delete entry + Eliminare voce + + + Add new group + Aggiungere nuovo gruppo + + + Edit group + Modificare gruppo + + + Delete group + Eliminare gruppo + + + Save database as + Salvare database come + + + Change master key + Cambiare password principale + + + Database settings + Impostazioni database + + + Import KeePass 1 database + Importare database KeePass 1 + + + Clone entry + Clona voce + + + Find + Trovare + + + Username + Nome Utente + + + Copy username to clipboard + Copiare nome utente negli appunti + + + Password + Password + + + Copy password to clipboard + Copiare password negli appunti + + + Settings + Impostazioni + + + Perform Auto-Type + Eseguire Auto-Type + + + Open URL + Aprire URL + + + Lock databases + Bloccare database + + + Title + Titolo + + + URL + URL + + + Notes + Note + + + Show toolbar + Mostrare barra degli strumenti + + + read-only + sola lettura + + + + PasswordGeneratorWidget + + Password: + Password: + + + Length: + Lunghezza: + + + Character Types + Tipi di carattere + + + Upper Case Letters + Lettere maiuscole + + + Lower Case Letters + Lettere minuscole + + + Numbers + Numeri + + + Special Characters + Caratteri speciali + + + Exclude look-alike characters + Escludere caratteri simili + + + Ensure that the password contains characters from every group + Assicurare che la password contenga caratteri di ogni gruppo + + + Accept + Accettare + + + + QCommandLineParser + + Displays version information. + Mostrare informazioni sulla versione. + + + Displays this help. + Mostrare questo aiuto. + + + Unknown option '%1'. + Opzione sconosciuta '%1'. + + + Unknown options: %1. + Opzioni sconosciute '%1'. + + + Missing value after '%1'. + Manca valore dopo '%1'. + + + Unexpected value after '%1'. + Valore inaspettato dopo '%1'. + + + [options] + [opzioni] + + + Usage: %1 + Uso: %1 + + + Options: + Opzioni: + + + Arguments: + Argomenti: + + + + QSaveFile + + Existing file %1 is not writable + Il file esistente %1 non è scrivibile + + + Writing canceled by application + Scrittura cancellata dall'applicazione + + + Partial write. Partition full? + Scrittura parziale. Partizione piena? + + + + QtIOCompressor + + Internal zlib error when compressing: + Errore interno di zlib durante la compressione: + + + Error writing to underlying device: + Errore durante la scrittura nel dispositivo: + + + Error opening underlying device: + Errore durante l'apertura dal dispositivo: + + + Error reading data from underlying device: + Errore durante la lettura dal dispositivo: + + + Internal zlib error when decompressing: + Errore interno di zlib durante la decompressione: + + + + QtIOCompressor::open + + The gzip format not supported in this version of zlib. + Formato gzip non supportato da questa versione di zlib. + + + Internal zlib error: + Errore interno di zlib: + + + + SearchWidget + + Find: + Trovare: + + + Case sensitive + Case sensitive + + + Current group + Gruppo corrente + + + Root group + Gruppo radice + + + + SettingsWidget + + Application Settings + Impostazioni applicazione + + + General + Generale + + + Security + Sicurezza + + + + SettingsWidgetGeneral + + Remember last databases + Ricordare ultimo database + + + Open previous databases on startup + Aprire precedente database all'avvio + + + Mark as modified on expanded state changes + Marcare come modificata quando la voce viene espansa + + + Automatically save on exit + Salvare automaticamente all'uscita + + + Automatically save after every change + Salvare automaticamente dopo ogni modifica + + + Minimize when copying to clipboard + Minimizzare quando si copia negli appunti + + + Use group icon on entry creation + Usare l'icona del gruppo alla creazione di una voce + + + Global Auto-Type shortcut + Scorciatoia Auto-Type globale + + + Use entry title to match windows for global auto-type + Utilizzare il titolo della voce per abbinare la finestra per auto-type globale + + + + SettingsWidgetSecurity + + Clear clipboard after + Pulire appunti dopo + + + sec + sec + + + Lock databases after inactivity of + Bloccare database dopo un'inattività di + + + Show passwords in cleartext by default + Mostrare la password in chiaro in maniera predefinita + + + Always ask before performing auto-type + Chiedere sempre prima di eseguire auto-type + + + + UnlockDatabaseWidget + + Unlock database + Sbloccare database + + + Error + Errore + + + Wrong key. + Password errata. + + + + WelcomeWidget + + Welcome! + Benvenuto/a! + + + + main + + KeePassX - cross-platform password manager + KeePassX - gestore di password cross-platform + + + filename of the password database to open (*.kdbx) + nome del file del database da aprire (*.kdbx) + + + path to a custom config file + percorso ad un file di configurazione personalizzato + + + password of the database (DANGEROUS!) + password del database (PERICOLOSO!) + + + key file of the database + file chiave del database + + + \ No newline at end of file diff --git a/share/translations/keepassx_nl_NL.ts b/share/translations/keepassx_nl_NL.ts new file mode 100644 index 00000000..aa6320ee --- /dev/null +++ b/share/translations/keepassx_nl_NL.ts @@ -0,0 +1,1178 @@ + + + AboutDialog + + About KeePassX + Over KeePassX + + + KeePassX is distributed under the term of the GNU General Public License (GPL) version 2 or (at your option) version 3. + KeePassX wordt verspreid onder de bepalingen van de GNU General Public License (GPL) versie 2 of (als u wenst) versie 3. + + + + AutoType + + Auto-Type - KeePassX + Auto-typen - KeePassX + + + Couldn't find an entry that matches the window title. + Kon geen element vinden dat overeenkomt met de venstertitel. + + + + AutoTypeAssociationsModel + + Window + Venster + + + Sequence + Volgorde + + + Default sequence + Standaardvolgorde + + + + AutoTypeSelectDialog + + Auto-Type - KeePassX + Auto-typen - KeePassX + + + Select entry to Auto-Type: + Kies element om automatisch te typen: + + + + ChangeMasterKeyWidget + + Password + Wachtwoord + + + Enter password: + Geef wachtwoord: + + + Repeat password: + Herhaal wachtwoord: + + + Key file + Sleutelbestand + + + Browse + Bladeren + + + Create + Aanmaken + + + Key files + Sleutelbestanden + + + All files + Alle bestanden + + + Create Key File... + Genereer sleutelbestand... + + + Error + Fout + + + Unable to create Key File : + Niet mogelijk om sleutelbestand aan te maken: + + + Select a key file + Kies een sleutelbestand + + + Question + Vraag + + + Do you really want to use an empty string as password? + Weet u zeker dat u een leeg veld als wachtwoord wilt gebruiken? + + + Different passwords supplied. + Verschillende wachtwoorden opgegeven. + + + + DatabaseOpenWidget + + Enter master key + Geef hoofdsleutel + + + Key File: + Sleutelbestand: + + + Password: + Wachtwoord: + + + Browse + Bladeren + + + Error + Fout + + + Unable to open the database. + Niet mogelijk om de database te openen. + + + Can't open key file + Niet mogelijk om het sleutelbestand te openen + + + All files + Alle bestanden + + + Key files + Sleutelbestanden + + + Select key file + Kies sleutelbestand + + + + DatabaseSettingsWidget + + Database name: + Naam van de database: + + + Database description: + Beschrijving van de database: + + + Transform rounds: + Transformatierondes: + + + Default username: + Standaard gebruikersnaam: + + + Use recycle bin: + Gebruik prullenbak: + + + MiB + MiB + + + Benchmark + Test + + + Max. history items: + Max. items in geschiedenis: + + + Max. history size: + Max. grootte geschiedenis: + + + + DatabaseTabWidget + + Root + Alles + + + KeePass 2 Database + KeePass 2 Database + + + All files + Alle bestanden + + + Open database + Open database + + + Warning + Waarschuwing + + + File not found! + Bestand niet gevonden! + + + Open KeePass 1 database + Open KeePass 1 database + + + KeePass 1 database + KeePass 1 database + + + All files (*) + Alle bestanden (*) + + + Close? + Sluiten? + + + "%1" is in edit mode. +Close anyway? + "%1" is in bewerkmodus. +Toch sluiten? + + + Save changes? + Wijzigingen opslaan? + + + "%1" was modified. +Save changes? + "%1" is gewijzigd. +Opslaan? + + + Error + Fout + + + Writing the database failed. + Opslaan van de database is mislukt. + + + Save database as + Database opslaan als + + + New database + Nieuwe database + + + locked + vergrendeld + + + + DatabaseWidget + + Change master key + Wijzig hoofdsleutel + + + Delete entry? + Element verwijderen? + + + Do you really want to delete the entry "%1" for good? + Weet u zeker dat u het element "%1" wilt verwijderen? + + + Delete entries? + Elementen wissen? + + + Do you really want to delete %1 entries for good? + Weet u zeker dat u %1 elementen wilt wissen? + + + Move entries to recycle bin? + Elementen naar de prullenbak verplaatsen? + + + Do you really want to move %n entry(s) to the recycle bin? + Weet u zeker dat u %n element naar de prullenbak wilt verplaatsen?Weet u zeker dat u %n elementen naar de prullenbak wilt verplaatsen? + + + Delete group? + Groep verwijderen? + + + Do you really want to delete the group "%1" for good? + Weet u zeker dat u de groep "%1" wilt verwijderen? + + + Current group + Huidige groep + + + + EditEntryWidget + + Entry + Element + + + Advanced + Geavanceerd + + + Icon + Icoon + + + Auto-Type + Auto-typen - KeePassX + + + Properties + Eigenschappen + + + History + Geschiedenis + + + Entry history + Geschiedenis van element + + + Add entry + Element toevoegen + + + Edit entry + Element wijzigen + + + Error + Fout + + + Different passwords supplied. + Verschillende wachtwoorden opgegeven. + + + New attribute + Nieuwe eigenschap + + + Select file + Kies bestand + + + Unable to open file + Niet mogelijk om bestand te openen + + + Save attachment + Bijlage opslaan + + + Unable to save the attachment: + + Niet mogelijk om de bijlage op te slaan: + + + + Tomorrow + Morgen + + + %n week(s) + %n week%n weken + + + %n month(s) + %n maand%n maanden + + + 1 year + 1 jaar + + + + EditEntryWidgetAdvanced + + Additional attributes + Extra eigenschappen + + + Add + Toevoegen + + + Edit + Wijzigen + + + Remove + Verwijderen + + + Attachments + Bijlagen + + + Save + Opslaan + + + + EditEntryWidgetAutoType + + Enable Auto-Type for this entry + Auto-typen inschakelen voor dit element + + + Inherit default Auto-Type sequence from the group + Erf standaard auto-typevolgorde van de groep + + + Use custom Auto-Type sequence: + Gebruik aangepaste auto-typevolgorde: + + + + + + + + + - + - + + + Window title: + Venstertitel: + + + Use default sequence + Gebruik standaardvolgorde + + + Set custom sequence: + Aangepaste volgorde: + + + + EditEntryWidgetHistory + + Show + Tonen + + + Restore + Herstellen + + + Delete + Verwijderen + + + Delete all + Alles verwijderen + + + + EditEntryWidgetMain + + Title: + Titel: + + + Username: + Gebruikersnaam: + + + Password: + Wachtwoord: + + + Repeat: + Herhalen: + + + Gen. + Gen. + + + URL: + URL: + + + Expires + Verloopt + + + Presets + Ingebouwd + + + Notes: + Opmerkingen: + + + + EditGroupWidget + + Group + Groep + + + Icon + Icoon + + + Properties + Eigenschappen + + + Add group + Groep toevoegen + + + Edit group + Groep wijzigen + + + Enable + Inschakelen + + + Disable + Uitschakelen + + + Inherit from parent group (%1) + Erf van bovenliggende groep (%1) + + + + EditGroupWidgetMain + + Name + Naam + + + Notes + Opmerkingen + + + Expires + Verloopt + + + Search + Zoeken + + + Auto-type + Auto-typen + + + + EditWidgetIcons + + Use default icon + Gebruik standaardicoon + + + Use custom icon + Gebruik aangepast icoon + + + Add custom icon + Voeg icoon toe + + + Delete custom icon + Verwijder icoon + + + Images + Afbeeldingen + + + All files + Alle bestanden + + + Select Image + Kies afbeelding + + + Can't delete icon! + Kan icoon niet verwijderen! + + + Can't delete icon. Still used by %n item(s). + Kan icoon niet verwijderen. Het wordt nog gebruikt door %n element.Kan icoon niet verwijderen. Het wordt nog gebruikt door %n elementen. + + + + EditWidgetProperties + + Created: + Aangemaakt: + + + Modified: + Gewijzigd: + + + Accessed: + Gelezen: + + + Uuid: + Uuid: + + + + EntryAttributesModel + + Name + Naam + + + + EntryHistoryModel + + Last modified + Laatst gewijzigd + + + Title + Titel + + + Username + Gebruikersnaam + + + URL + URL + + + + EntryModel + + Group + Groep + + + Title + Titel + + + Username + Gebruikersnaam + + + URL + URL + + + + Group + + Recycle Bin + Prullenbak + + + + KeePass1OpenWidget + + Import KeePass1 database + Importeer Keepass 1-database + + + Error + Fout + + + Unable to open the database. + Niet mogelijk om de database te openen. + + + + KeePass1Reader + + Unable to read keyfile. + Niet mogelijk om sleutelbestand te lezen + + + Not a KeePass database. + Geen Keepass-database + + + Unsupported encryption algorithm. + Niet-ondersteund encryptie-algoritme + + + Unsupported KeePass database version. + Niet-ondersteunde versie van Keepass-database + + + Root + Alles + + + + KeePass2Reader + + Not a KeePass database. + Geen Keepass-database. + + + Unsupported KeePass database version. + Niet-ondersteunde versie van Keepass-database. + + + Wrong key or database file is corrupt. + Verkeerde sleutel of corrupte database. + + + + MainWindow + + Database + Database + + + Recent databases + Recente databases + + + Help + Help + + + Entries + Elementen + + + Copy attribute to clipboard + Kopieer eigenschap naar klembord + + + Groups + Groepen + + + Extras + Extra's + + + View + Beeld + + + Quit + Afsluiten + + + About + Over + + + Open database + Open database + + + Save database + Sla database op + + + Close database + Sluit database + + + New database + Nieuwe database + + + Add new entry + Voeg element toe + + + View/Edit entry + Bekijk/bewerk element + + + Delete entry + Verwijder element + + + Add new group + Voeg groep toe + + + Edit group + Bewerk groep + + + Delete group + Verwijder groep + + + Save database as + Database opslaan als + + + Change master key + Hoofdsleutel wijzigen + + + Database settings + Database-instellingen + + + Import KeePass 1 database + Importeer Keepass 1-database + + + Clone entry + Element klonen + + + Find + Vind + + + Username + Gebruikersnaam + + + Copy username to clipboard + Kopieer gebruikersnaam naar klembord + + + Password + Wachtwoord + + + Copy password to clipboard + Kopieer wachtwoord naar klembord + + + Settings + Instellingen + + + Perform Auto-Type + Voer auto-typen uit + + + Open URL + Open URL + + + Lock databases + Vergrendel databases + + + Title + Titel + + + URL + URL + + + Notes + Opmerkingen + + + Show toolbar + Werkbalk weergeven + + + read-only + alleen-lezen + + + + PasswordGeneratorWidget + + Password: + Wachtwoord: + + + Length: + Lengte: + + + Character Types + Tekens + + + Upper Case Letters + Hoofdletters + + + Lower Case Letters + Kleine letters + + + Numbers + Cijfers + + + Special Characters + Speciale tekens + + + Exclude look-alike characters + Geen op elkaar lijkende tekens + + + Ensure that the password contains characters from every group + Zorg dat het wachtwoord tekens uit iedere groep bevat + + + Accept + Accepteren + + + + QCommandLineParser + + Displays version information. + Toont versie-informatie. + + + Displays this help. + Toont deze helptekst. + + + Unknown option '%1'. + Onbekende optie '%1'. + + + Unknown options: %1. + Onbekende opties: %1. + + + Missing value after '%1'. + Ontbrekende waarde na '%1'. + + + Unexpected value after '%1'. + Onverwachte waarde na '%1'. + + + [options] + [opties] + + + Usage: %1 + Gebruik: %1 + + + Options: + Opties: + + + Arguments: + Argumenten: + + + + QSaveFile + + Existing file %1 is not writable + Bestaand bestand %1 is niet schrijfbaar + + + Writing canceled by application + Schrijven afgebroken door programma + + + Partial write. Partition full? + Slechts deels geschreven. Is de schijf vol? + + + + QtIOCompressor + + Internal zlib error when compressing: + Interne fout in zlib bij inpakken: + + + Error writing to underlying device: + Fout bij schrijven naar onderliggend apparaat: + + + Error opening underlying device: + Fout bij openen van onderliggend apparaat: + + + Error reading data from underlying device: + Fout bij lezen van gegevens van onderliggend apparaat: + + + Internal zlib error when decompressing: + Interne fout in zlib bij uitpakken: + + + + QtIOCompressor::open + + The gzip format not supported in this version of zlib. + Gzip wordt niet ondersteund in deze versie van zlib. + + + Internal zlib error: + Interne fout in zlib: + + + + SearchWidget + + Find: + Vind: + + + Case sensitive + Hoofdlettergevoelig + + + Current group + Huidige groep + + + Root group + Hoofdgroep + + + + SettingsWidget + + Application Settings + Programma-instellingen + + + General + Algemeen + + + Security + Beveiliging + + + + SettingsWidgetGeneral + + Remember last databases + Onthoud laatste databases + + + Open previous databases on startup + Open vorige databases bij starten + + + Mark as modified on expanded state changes + Markeer database als gewijzigd bij wijzigen van de status + + + Automatically save on exit + Automatisch opslaan bij afsluiten + + + Automatically save after every change + Automatisch opslaan na iedere wijziging + + + Minimize when copying to clipboard + Minimaliseer bij kopieeren naar klembord + + + Use group icon on entry creation + Gebruik icoon van de groep voor nieuwe elementen + + + Global Auto-Type shortcut + Globale sneltoets voor auto-typen + + + Use entry title to match windows for global auto-type + Gebruik naam van element als vensternaam voor auto-typen + + + + SettingsWidgetSecurity + + Clear clipboard after + Leeg klembord na + + + sec + sec + + + Lock databases after inactivity of + Vergrendel databases na inactiviteit van + + + Show passwords in cleartext by default + Laat wachtwoorden standaard zien + + + Always ask before performing auto-type + Altijd vragen alvorens auto-type uit te voeren + + + + UnlockDatabaseWidget + + Unlock database + Database ontgrendelen + + + Error + Fout + + + Wrong key. + Verkeerd wachtwoord + + + + WelcomeWidget + + Welcome! + Welkom! + + + + main + + KeePassX - cross-platform password manager + KeepassX - multi-platform wachtwoordbeheerder + + + filename of the password database to open (*.kdbx) + bestandsnaam van de te openen wachtwoorddatabase (*.kdbx) + + + path to a custom config file + pad naar een configuratiebestand + + + password of the database (DANGEROUS!) + wachtwoord van de database (GEVAARLIJK!) + + + key file of the database + sleutelbestand van de database + + + \ No newline at end of file diff --git a/share/translations/keepassx_sv.ts b/share/translations/keepassx_sv.ts new file mode 100644 index 00000000..2a3ba790 --- /dev/null +++ b/share/translations/keepassx_sv.ts @@ -0,0 +1,1178 @@ + + + AboutDialog + + About KeePassX + Om KeePassX + + + KeePassX is distributed under the term of the GNU General Public License (GPL) version 2 or (at your option) version 3. + Keepassx distribueras enligt villkoren i GNU General Public License (GPL) version 2 eller (om du vill) version 3. + + + + AutoType + + Auto-Type - KeePassX + Auto-skriv - KeePassX + + + Couldn't find an entry that matches the window title. + Kunde inte hitta en post som matchar fönstertiteln. + + + + AutoTypeAssociationsModel + + Window + Fönster + + + Sequence + Sekvens + + + Default sequence + Standard sekvens + + + + AutoTypeSelectDialog + + Auto-Type - KeePassX + Auto-skriv - KeePassX + + + Select entry to Auto-Type: + Välj post att auto-skriva + + + + ChangeMasterKeyWidget + + Password + Lösenord + + + Enter password: + Ange lösenord: + + + Repeat password: + Repetera lösenord: + + + Key file + Nyckel-fil + + + Browse + Bläddra + + + Create + Skapa + + + Key files + Nyckel-filer + + + All files + Alla filer + + + Create Key File... + Skapa nyckel-fil... + + + Error + Fel + + + Unable to create Key File : + Kunde inte skapa nyckel-fil + + + Select a key file + Välj nyckel-fil + + + Question + Fråga + + + Do you really want to use an empty string as password? + Vill du verkligen vill använda en tom sträng som lösenord? + + + Different passwords supplied. + Olika lösenord angivna + + + + DatabaseOpenWidget + + Enter master key + Ange huvud lösenord + + + Key File: + Nyckel-fil: + + + Password: + Lösenord: + + + Browse + Bläddra + + + Error + Fel + + + Unable to open the database. + Kunde inte öppna databas. + + + Can't open key file + Kan inte öppna nyckel-fil + + + All files + Alla filer + + + Key files + Nyckel-filer + + + Select key file + Välj nyckel-fil + + + + DatabaseSettingsWidget + + Database name: + Databasnamn: + + + Database description: + Databasbeskrivning: + + + Transform rounds: + Transformerings varv: + + + Default username: + Standard användarnamn: + + + Use recycle bin: + Använd papperskorg: + + + MiB + MiB + + + Benchmark + Benchmark + + + Max. history items: + Maxantal historik poster: + + + Max. history size: + Maximal historik storlek: + + + + DatabaseTabWidget + + Root + Root + + + KeePass 2 Database + KeePass 2 Databas + + + All files + Alla filer + + + Open database + Öppna databas + + + Warning + Varning + + + File not found! + Filen kunde inte hittas! + + + Open KeePass 1 database + Öppna KeePass 1 databas + + + KeePass 1 database + KeePass 1 databas + + + All files (*) + Alla filer (*) + + + Close? + Stäng? + + + "%1" is in edit mode. +Close anyway? + "%1" är i redigerar-läge. +Stäng ändå? + + + Save changes? + Spara ändringar? + + + "%1" was modified. +Save changes? + "%1" har ändrats. +Spara ändringarna? + + + Error + Fel + + + Writing the database failed. + Kunde inte skriva till databasen. + + + Save database as + Spara databas som + + + New database + Ny databas + + + locked + låst + + + + DatabaseWidget + + Change master key + Ändra huvud lösenord + + + Delete entry? + Ta bort post? + + + Do you really want to delete the entry "%1" for good? + Vill du verkligen ta bort "%1" för gott? + + + Delete entries? + Ta bort poster? + + + Do you really want to delete %1 entries for good? + Vill du verkligen ta bort %1 poser för gott? + + + Move entries to recycle bin? + Lägg poster i papperskorgen? + + + Do you really want to move %n entry(s) to the recycle bin? + Vill du verkligen flytta %n post till papperskorgen?Vill du verkligen flytta %n poster till papperskorgen? + + + Delete group? + Ta bort grupp? + + + Do you really want to delete the group "%1" for good? + Vill du verkligen ta bort gruppen "%1" för gott? + + + Current group + Nuvarande grupp + + + + EditEntryWidget + + Entry + Post + + + Advanced + Avancerat + + + Icon + Ikon + + + Auto-Type + Auto-skriv + + + Properties + Egenskaper + + + History + Historik + + + Entry history + Posthistork + + + Add entry + Lägg till post + + + Edit entry + Ändra post + + + Error + Fel + + + Different passwords supplied. + Olika lösenord angivna + + + New attribute + Nytt attribut + + + Select file + Välj fil + + + Unable to open file + Kunde inte öppna filen. + + + Save attachment + Spara bifogad fil + + + Unable to save the attachment: + + Kunde inte spara bifogad fil: + + + + Tomorrow + Imorgon + + + %n week(s) + %n vecka%n veckor + + + %n month(s) + %n månad%n månader + + + 1 year + 1 år + + + + EditEntryWidgetAdvanced + + Additional attributes + Ytterligare attribut + + + Add + Lägg till + + + Edit + Ändra + + + Remove + Ta bort + + + Attachments + Bilagor + + + Save + Spara + + + + EditEntryWidgetAutoType + + Enable Auto-Type for this entry + Slå på auto-skriv för denna post + + + Inherit default Auto-Type sequence from the group + Ärv standard auto-skriv sekvens för grupp + + + Use custom Auto-Type sequence: + Använd egen auto-skriv sekvens: + + + + + + + + + - + - + + + Window title: + Fönster titel: + + + Use default sequence + Använd standard sekvens + + + Set custom sequence: + Egen sekvens: + + + + EditEntryWidgetHistory + + Show + Visa + + + Restore + Återställ + + + Delete + Ta bort + + + Delete all + Ta bort alla + + + + EditEntryWidgetMain + + Title: + Titel: + + + Username: + Användarnamn: + + + Password: + Lösenord: + + + Repeat: + Repetera: + + + Gen. + Gen. + + + URL: + URL: + + + Expires + Går ut + + + Presets + Förinställningar + + + Notes: + Anteckningar: + + + + EditGroupWidget + + Group + Grupp + + + Icon + Ikon + + + Properties + Egenskaper + + + Add group + Lägg till grupp + + + Edit group + Ändra grupp + + + Enable + Slå på + + + Disable + Stäng av + + + Inherit from parent group (%1) + Ärv från förälder grupp (%1) + + + + EditGroupWidgetMain + + Name + Namn + + + Notes + Anteckningar + + + Expires + Går ut + + + Search + Sök + + + Auto-type + Auto-skriv + + + + EditWidgetIcons + + Use default icon + Använd standard ikon + + + Use custom icon + Använd egen ikon + + + Add custom icon + Lägg till egen ikon + + + Delete custom icon + Ta bort egen ikon + + + Images + Bilder + + + All files + Alla filer + + + Select Image + Välj bild + + + Can't delete icon! + Kan inte ta bort ikon! + + + Can't delete icon. Still used by %n item(s). + Kan inte ta bort ikonen. Den används fortfarande av %n postKan inte ta bort ikonen. Den används fortfarande av %n poster + + + + EditWidgetProperties + + Created: + Skapad: + + + Modified: + Ändrad: + + + Accessed: + Läst: + + + Uuid: + UUID: + + + + EntryAttributesModel + + Name + Namn + + + + EntryHistoryModel + + Last modified + Senast ändrad + + + Title + Titel + + + Username + Användarnamn + + + URL + URL + + + + EntryModel + + Group + Grupp + + + Title + Titel + + + Username + Användarnamn + + + URL + URL + + + + Group + + Recycle Bin + Papperskorg + + + + KeePass1OpenWidget + + Import KeePass1 database + Importera KeePass1 databas + + + Error + Fel + + + Unable to open the database. + Kunde inte öppna databas. + + + + KeePass1Reader + + Unable to read keyfile. + Kunde inte läsa nyckel-filen. + + + Not a KeePass database. + Inte en KeePass databas + + + Unsupported encryption algorithm. + Krypteringsalgoritnmen stöds ej + + + Unsupported KeePass database version. + KeePass databas versionen stöds ej. + + + Root + Root + + + + KeePass2Reader + + Not a KeePass database. + Inte en KeePass databas. + + + Unsupported KeePass database version. + KeePass databas versionen stöds ej. + + + Wrong key or database file is corrupt. + Fel lösenord eller korrupt databas-fil + + + + MainWindow + + Database + Databas + + + Recent databases + Senast använda databser + + + Help + Hjälp + + + Entries + Poster + + + Copy attribute to clipboard + Kopiera attribut + + + Groups + Grupper + + + Extras + Extra + + + View + Vy + + + Quit + Avsluta + + + About + Om + + + Open database + Öppna databas + + + Save database + Spara databas + + + Close database + Stäng databas + + + New database + Ny databas + + + Add new entry + Lägg till ny post + + + View/Edit entry + Visa/ändra post + + + Delete entry + Ta bort post + + + Add new group + Lägg till ny grupp + + + Edit group + Ändra grupp + + + Delete group + Ta bort grupp + + + Save database as + Spara databas som + + + Change master key + Ändra huvud lösenord + + + Database settings + Databasinställningar + + + Import KeePass 1 database + Importera KeePass1 databas + + + Clone entry + Klona post + + + Find + Sök + + + Username + Användarnamn + + + Copy username to clipboard + Kopiera användarnamn + + + Password + Lösenord + + + Copy password to clipboard + Kopiera lösenord + + + Settings + Inställningar + + + Perform Auto-Type + Utför auto-skriv + + + Open URL + Öppna URL + + + Lock databases + Lås databaser + + + Title + Titel + + + URL + URL + + + Notes + Anteckningar + + + Show toolbar + Visa verktygsfält + + + read-only + läs bara + + + + PasswordGeneratorWidget + + Password: + Lösenord: + + + Length: + Längd: + + + Character Types + Teckentyper + + + Upper Case Letters + Versaler + + + Lower Case Letters + Gemener + + + Numbers + Siffror + + + Special Characters + Specialtecken + + + Exclude look-alike characters + Uteslut liknande tecken + + + Ensure that the password contains characters from every group + Säkerställ att lösenordet innehåller tecken från varje grupp + + + Accept + Acceptera + + + + QCommandLineParser + + Displays version information. + Visar versionsinformation. + + + Displays this help. + Visa denna hjälp. + + + Unknown option '%1'. + Okänt alternativ: '%1' + + + Unknown options: %1. + Okända alternativ: '%1' + + + Missing value after '%1'. + Saknar värde efter '%1' + + + Unexpected value after '%1'. + Oväntat värde efter '%1' + + + [options] + [alternativ] + + + Usage: %1 + Användning: %1 + + + Options: + Alternativ: + + + Arguments: + Argument: + + + + QSaveFile + + Existing file %1 is not writable + Den existerande filen %1 är inte skrivbar + + + Writing canceled by application + Skrivning avbruten av applikation + + + Partial write. Partition full? + Delvis skrivet. Är partitionen full? + + + + QtIOCompressor + + Internal zlib error when compressing: + Internt zlib fel vid komprimering: + + + Error writing to underlying device: + Fel vid skrivning till underliggande enhet: + + + Error opening underlying device: + Fel vid öppning av underliggande enhet: + + + Error reading data from underlying device: + Fel vid läsning från underliggande enhet: + + + Internal zlib error when decompressing: + Internt zlib fel vid extrahering: + + + + QtIOCompressor::open + + The gzip format not supported in this version of zlib. + Gzip formatet stöds inte av denna version av zlib. + + + Internal zlib error: + Internt zlib fel: + + + + SearchWidget + + Find: + Sök: + + + Case sensitive + Skiftlägeskänslig + + + Current group + Nuvarande grupp + + + Root group + Root grupp + + + + SettingsWidget + + Application Settings + Applikationsinställningar + + + General + Allmän + + + Security + Säkerhet + + + + SettingsWidgetGeneral + + Remember last databases + Komihåg senaste databasen + + + Open previous databases on startup + Öppna senaste databasen är programmet startar + + + Mark as modified on expanded state changes + Markera som ändrad när utökat läge ändras + + + Automatically save on exit + Spara automatiskt är applikationen anslutas + + + Automatically save after every change + Spara automatiskt efter varje ändring + + + Minimize when copying to clipboard + Minimera vid kopiering + + + Use group icon on entry creation + Använd gruppens ikon för nya poster + + + Global Auto-Type shortcut + Globalt auto-skriv kortkommando + + + Use entry title to match windows for global auto-type + Använda postens titel till matchning med fönster för globalt auto-skriv + + + + SettingsWidgetSecurity + + Clear clipboard after + Rensa urklipp efter + + + sec + sek + + + Lock databases after inactivity of + Lås databaser efter inaktivitet i + + + Show passwords in cleartext by default + Visa lösenord i klartext som standard + + + Always ask before performing auto-type + Fråga alltid innan auto-skriv utförs + + + + UnlockDatabaseWidget + + Unlock database + Lås upp databas + + + Error + Fel + + + Wrong key. + Fel lösenord + + + + WelcomeWidget + + Welcome! + Välkommen! + + + + main + + KeePassX - cross-platform password manager + KeePassX - plattformsoberoende lösenordshanterare + + + filename of the password database to open (*.kdbx) + namn på databas fil att öppna (*.kdbx) + + + path to a custom config file + Sökväg till egen konfigurations-fil + + + password of the database (DANGEROUS!) + lösenord för databasen (FARLIGT!) + + + key file of the database + nyckel-fil för databas + + + \ No newline at end of file diff --git a/share/translations/update.sh b/share/translations/update.sh new file mode 100755 index 00000000..6828dc82 --- /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 4b4b2483..5fc05bbc 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -35,6 +35,7 @@ set(keepassx_SOURCES core/Entry.cpp core/EntryAttachments.cpp core/EntryAttributes.cpp + core/EntrySearcher.cpp core/FilePath.cpp core/Global.h core/Group.cpp @@ -47,7 +48,9 @@ set(keepassx_SOURCES core/SignalMultiplexer.cpp core/TimeDelta.cpp core/TimeInfo.cpp + core/ToDbExporter.cpp core/Tools.cpp + core/Translator.cpp core/Uuid.cpp core/qcommandlineoption.cpp core/qcommandlineparser.cpp @@ -73,6 +76,7 @@ set(keepassx_SOURCES gui/DatabaseSettingsWidget.cpp gui/DatabaseTabWidget.cpp gui/DatabaseWidget.cpp + gui/DatabaseWidgetStateSync.cpp gui/DialogyWidget.cpp gui/DragTabBar.cpp gui/EditWidget.cpp @@ -162,6 +166,7 @@ set(keepassx_MOC gui/DatabaseSettingsWidget.h gui/DatabaseTabWidget.h gui/DatabaseWidget.h + gui/DatabaseWidgetStateSync.h gui/DialogyWidget.h gui/DragTabBar.h gui/EditWidget.h diff --git a/src/autotype/AutoType.cpp b/src/autotype/AutoType.cpp index 80b81082..aac0c0cf 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/autotype/x11/AutoTypeX11.cpp b/src/autotype/x11/AutoTypeX11.cpp index 06a1e329..843fbfa4 100644 --- a/src/autotype/x11/AutoTypeX11.cpp +++ b/src/autotype/x11/AutoTypeX11.cpp @@ -209,23 +209,26 @@ QString AutoTypePlatformX11::windowTitle(Window window, bool useBlacklist) unsigned long after; unsigned char* data = Q_NULLPTR; + // the window manager spec says we should read _NET_WM_NAME first, then fall back to WM_NAME + int retVal = XGetWindowProperty(m_dpy, window, m_atomNetWmName, 0, 1000, false, m_atomUtf8String, &type, &format, &nitems, &after, &data); - if (retVal != 0 && data) { + if ((retVal == 0) && data) { title = QString::fromUtf8(reinterpret_cast(data)); } else { XTextProperty textProp; retVal = XGetTextProperty(m_dpy, window, &textProp, m_atomWmName); - if (retVal != 0 && textProp.value) { + if ((retVal != 0) && textProp.value) { char** textList = Q_NULLPTR; int count; if (textProp.encoding == m_atomUtf8String) { title = QString::fromUtf8(reinterpret_cast(textProp.value)); } - else if (XmbTextPropertyToTextList(m_dpy, &textProp, &textList, &count) == 0 && textList && count > 0) { + else if ((XmbTextPropertyToTextList(m_dpy, &textProp, &textList, &count) == 0) + && textList && (count > 0)) { title = QString::fromLocal8Bit(textList[0]); } else if (textProp.encoding == m_atomString) { diff --git a/src/config-keepassx.h.cmake b/src/config-keepassx.h.cmake index 9a3f4951..805700a9 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 418c5e76..48ea9f33 100644 --- a/src/core/Config.cpp +++ b/src/core/Config.cpp @@ -71,7 +71,8 @@ Config::Config(QObject* parent) userPath += "/keepassx/"; #else userPath = QDir::fromNativeSeparators(QDesktopServices::storageLocation(QDesktopServices::DataLocation)); - // storageLocation() appends the application name ("/keepassx/") to the end + // storageLocation() appends the application name ("/keepassx") to the end + userPath += "/"; #endif userPath += "keepassx2.ini"; @@ -89,19 +90,21 @@ void Config::init(const QString& fileName) m_defaults.insert("RememberLastDatabases", true); m_defaults.insert("OpenPreviousDatabasesOnStartup", true); - m_defaults.insert("ModifiedOnExpandedStateChanges", true); m_defaults.insert("AutoSaveAfterEveryChange", false); m_defaults.insert("AutoSaveOnExit", false); m_defaults.insert("ShowToolbar", true); m_defaults.insert("MinimizeOnCopy", false); m_defaults.insert("UseGroupIconOnEntryCreation", false); - m_defaults.insert("ReloadBehavior", 0 /*always ask*/); + m_defaults.insert("AutoTypeEntryTitleMatch", true); m_defaults.insert("security/clearclipboard", true); m_defaults.insert("security/clearclipboardtimeout", 10); m_defaults.insert("security/lockdatabaseidle", false); m_defaults.insert("security/lockdatabaseidlesec", 10); m_defaults.insert("security/passwordscleartext", false); - m_defaults.insert("security/autotypeask", false); + m_defaults.insert("security/autotypeask", true); + m_defaults.insert("GUI/Language", "system"); + m_defaults.insert("GUI/ShowTrayIcon", false); + m_defaults.insert("GUI/MinimizeToTray", false); } Config* Config::instance() @@ -113,7 +116,7 @@ Config* Config::instance() return m_instance; } -void Config::createConfigFromFile(QString file) +void Config::createConfigFromFile(const QString& file) { Q_ASSERT(!m_instance); m_instance = new Config(file, qApp); diff --git a/src/core/Config.h b/src/core/Config.h index ee308268..ca0f74cb 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 55f54328..4f977915 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 c2c2938c..ae07ed45 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/EntryAttachments.cpp b/src/core/EntryAttachments.cpp index e39bf418..7bd080bf 100644 --- a/src/core/EntryAttachments.cpp +++ b/src/core/EntryAttachments.cpp @@ -27,6 +27,11 @@ QList EntryAttachments::keys() const return m_attachments.keys(); } +bool EntryAttachments::hasKey(const QString& key) const +{ + return m_attachments.keys().contains(key); +} + QList EntryAttachments::values() const { return m_attachments.values(); diff --git a/src/core/EntryAttachments.h b/src/core/EntryAttachments.h index b482f42e..3446b315 100644 --- a/src/core/EntryAttachments.h +++ b/src/core/EntryAttachments.h @@ -30,6 +30,7 @@ class EntryAttachments : public QObject public: explicit EntryAttachments(QObject* parent = Q_NULLPTR); QList keys() const; + bool hasKey(const QString& key) const; QList values() const; QByteArray value(const QString& key) const; void set(const QString& key, const QByteArray& value); diff --git a/src/core/EntryAttributes.cpp b/src/core/EntryAttributes.cpp index 08fcf42f..08fd2f9c 100644 --- a/src/core/EntryAttributes.cpp +++ b/src/core/EntryAttributes.cpp @@ -36,6 +36,11 @@ QList EntryAttributes::keys() const return m_attributes.keys(); } +bool EntryAttributes::hasKey(const QString& key) const +{ + return m_attributes.keys().contains(key); +} + QList EntryAttributes::customKeys() { QList customKeys; diff --git a/src/core/EntryAttributes.h b/src/core/EntryAttributes.h index 2decc8a3..0c6591a2 100644 --- a/src/core/EntryAttributes.h +++ b/src/core/EntryAttributes.h @@ -32,6 +32,7 @@ class EntryAttributes : public QObject public: explicit EntryAttributes(QObject* parent = Q_NULLPTR); QList keys() const; + bool hasKey(const QString& key) const; QList customKeys(); QString value(const QString& key) const; bool contains(const QString& key) const; diff --git a/src/core/EntrySearcher.cpp b/src/core/EntrySearcher.cpp new file mode 100644 index 00000000..82a553e3 --- /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 00000000..246538cb --- /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 00000000..dedb1c8a --- /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 4c04b484..e96b857b 100644 --- a/src/core/Group.cpp +++ b/src/core/Group.cpp @@ -248,9 +248,7 @@ void Group::setExpanded(bool expanded) if (m_data.isExpanded != expanded) { m_data.isExpanded = expanded; updateTimeinfo(); - if (config()->get("ModifiedOnExpandedStateChanges").toBool()) { - Q_EMIT modified(); - } + Q_EMIT modified(); } } @@ -500,22 +498,6 @@ void Group::copyDataFrom(const Group* other) m_lastTopVisibleEntry = other->m_lastTopVisibleEntry; } -Database* Group::exportToDb() -{ - Q_ASSERT(database()); - - Database* db = new Database(); - Group* clonedGroup = clone(Entry::CloneNewUuid | Entry::CloneIncludeHistory); - clonedGroup->setParent(db->rootGroup()); - - QSet customIcons = customIconsRecursive(); - db->metadata()->copyCustomIcons(customIcons, database()->metadata()); - - db->copyAttributesFrom(database()); - - return db; -} - void Group::addEntry(Entry* entry) { Q_ASSERT(entry); @@ -612,33 +594,6 @@ void Group::recCreateDelObjects() } } -QList 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 4e08f5b3..7391f886 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 00000000..1f76fb74 --- /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 00000000..58c5efeb --- /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 00000000..bc4d2b62 --- /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 00000000..4bc4fca3 --- /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/crypto/Crypto.cpp b/src/crypto/Crypto.cpp index 1e28002b..13c3c20e 100644 --- a/src/crypto/Crypto.cpp +++ b/src/crypto/Crypto.cpp @@ -21,7 +21,12 @@ #include +#include "config-keepassx.h" +#include "crypto/CryptoHash.h" +#include "crypto/SymmetricCipher.h" + bool Crypto::m_initalized(false); +QString Crypto::m_errorStr; #if !defined(GCRYPT_VERSION_NUMBER) || (GCRYPT_VERSION_NUMBER < 0x010600) static int gcry_qt_mutex_init(void** p_sys) @@ -64,11 +69,11 @@ Crypto::Crypto() { } -void Crypto::init() +bool Crypto::init() { if (m_initalized) { qWarning("Crypto::init: already initalized"); - return; + return true; } // libgcrypt >= 1.6 doesn't allow custom thread callbacks anymore. @@ -78,7 +83,19 @@ void Crypto::init() gcry_check_version(0); gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0); + if (!checkAlgorithms()) { + return false; + } + + // has to be set before testing Crypto classes m_initalized = true; + + if (!selfTest()) { + m_initalized = false; + return false; + } + + return true; } bool Crypto::initalized() @@ -86,7 +103,89 @@ bool Crypto::initalized() return m_initalized; } -bool Crypto::selfTest() +QString Crypto::errorString() +{ + return m_errorStr; +} + +bool Crypto::backendSelfTest() { return (gcry_control(GCRYCTL_SELFTEST) == 0); } + +bool Crypto::checkAlgorithms() +{ + if (gcry_cipher_algo_info(GCRY_CIPHER_AES256, GCRYCTL_TEST_ALGO, Q_NULLPTR, Q_NULLPTR) != 0) { + m_errorStr = "GCRY_CIPHER_AES256 not found."; + qWarning("Crypto::checkAlgorithms: %s", qPrintable(m_errorStr)); + return false; + } + if (gcry_cipher_algo_info(GCRY_CIPHER_TWOFISH, GCRYCTL_TEST_ALGO, Q_NULLPTR, Q_NULLPTR) != 0) { + m_errorStr = "GCRY_CIPHER_TWOFISH not found."; + qWarning("Crypto::checkAlgorithms: %s", qPrintable(m_errorStr)); + return false; + } +#ifdef GCRYPT_HAS_SALSA20 + if (gcry_cipher_algo_info(GCRY_CIPHER_SALSA20, GCRYCTL_TEST_ALGO, Q_NULLPTR, Q_NULLPTR) != 0) { + m_errorStr = "GCRY_CIPHER_SALSA20 not found."; + qWarning("Crypto::checkAlgorithms: %s", qPrintable(m_errorStr)); + return false; + } +#endif + if (gcry_md_test_algo(GCRY_MD_SHA256) != 0) { + m_errorStr = "GCRY_MD_SHA256 not found."; + qWarning("Crypto::checkAlgorithms: %s", qPrintable(m_errorStr)); + return false; + } + + return true; +} + +bool Crypto::selfTest() +{ + QByteArray sha256Test = CryptoHash::hash("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", + CryptoHash::Sha256); + + if (sha256Test != QByteArray::fromHex("248D6A61D20638B8E5C026930C3E6039A33CE45964FF2167F6ECEDD419DB06C1")) { + m_errorStr = "SHA-256 mismatch."; + qWarning("Crypto::selfTest: %s", qPrintable(m_errorStr)); + return false; + } + + QByteArray key = QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4"); + QByteArray iv = QByteArray::fromHex("000102030405060708090a0b0c0d0e0f"); + QByteArray plainText = QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172a"); + plainText.append(QByteArray::fromHex("ae2d8a571e03ac9c9eb76fac45af8e51")); + QByteArray cipherText = QByteArray::fromHex("f58c4c04d6e5f1ba779eabfb5f7bfbd6"); + cipherText.append(QByteArray::fromHex("9cfc4e967edb808d679f777bc6702c7d")); + + SymmetricCipher aes256Encrypt(SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Encrypt, key, iv); + if (aes256Encrypt.process(plainText) != cipherText) { + m_errorStr = "AES-256 encryption mismatch."; + qWarning("Crypto::selfTest: %s", qPrintable(m_errorStr)); + return false; + } + + SymmetricCipher aes256Descrypt(SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Decrypt, key, iv); + if (aes256Descrypt.process(cipherText) != plainText) { + m_errorStr = "AES-256 decryption mismatch."; + qWarning("Crypto::selfTest: %s", qPrintable(m_errorStr)); + return false; + } + + QByteArray salsa20Key = QByteArray::fromHex("F3F4F5F6F7F8F9FAFBFCFDFEFF000102030405060708090A0B0C0D0E0F101112"); + QByteArray salsa20iv = QByteArray::fromHex("0000000000000000"); + QByteArray salsa20Plain = QByteArray::fromHex("00000000000000000000000000000000"); + QByteArray salsa20Cipher = QByteArray::fromHex("B4C0AFA503BE7FC29A62058166D56F8F"); + + SymmetricCipher salsa20Stream(SymmetricCipher::Salsa20, SymmetricCipher::Stream, + SymmetricCipher::Encrypt, salsa20Key, salsa20iv); + + if (salsa20Stream.process(salsa20Plain) != salsa20Cipher) { + m_errorStr = "Salsa20 stream cipher mismatch."; + qWarning("Crypto::selfTest: %s", qPrintable(m_errorStr)); + return false; + } + + return true; +} diff --git a/src/crypto/Crypto.h b/src/crypto/Crypto.h index 63f11777..9926f14b 100644 --- a/src/crypto/Crypto.h +++ b/src/crypto/Crypto.h @@ -18,18 +18,25 @@ #ifndef KEEPASSX_CRYPTO_H #define KEEPASSX_CRYPTO_H +#include + #include "core/Global.h" class Crypto { public: - static void init(); + static bool init(); static bool initalized(); - static bool selfTest(); + static bool backendSelfTest(); + static QString errorString(); private: Crypto(); + static bool checkAlgorithms(); + static bool selfTest(); + static bool m_initalized; + static QString m_errorStr; }; #endif // KEEPASSX_CRYPTO_H diff --git a/src/format/KeePass2XmlReader.cpp b/src/format/KeePass2XmlReader.cpp index 383269f6..d1737d52 100644 --- a/src/format/KeePass2XmlReader.cpp +++ b/src/format/KeePass2XmlReader.cpp @@ -35,9 +35,15 @@ KeePass2XmlReader::KeePass2XmlReader() , m_db(Q_NULLPTR) , m_meta(Q_NULLPTR) , m_error(false) + , m_strictMode(false) { } +void KeePass2XmlReader::setStrictMode(bool strictMode) +{ + m_strictMode = strictMode; +} + void KeePass2XmlReader::readDatabase(QIODevice* device, Database* db, KeePass2RandomStream* randomStream) { m_error = false; @@ -493,7 +499,12 @@ Group* KeePass2XmlReader::parseGroup() if (m_xml.name() == "UUID") { Uuid uuid = readUuid(); if (uuid.isNull()) { - raiseError("Null group uuid"); + if (m_strictMode) { + raiseError("Null group uuid"); + } + else { + group->setUuid(Uuid::random()); + } } else { group->setUuid(uuid); @@ -508,7 +519,9 @@ Group* KeePass2XmlReader::parseGroup() else if (m_xml.name() == "IconID") { int iconId = readNumber(); if (iconId < 0) { - raiseError("Invalid group icon number"); + if (m_strictMode) { + raiseError("Invalid group icon number"); + } } else { if (iconId >= DatabaseIcons::IconCount) { @@ -584,6 +597,10 @@ Group* KeePass2XmlReader::parseGroup() } } + if (group->uuid().isNull() && !m_strictMode) { + group->setUuid(Uuid::random()); + } + if (!group->uuid().isNull()) { Group* tmpGroup = group; group = getGroup(tmpGroup->uuid()); @@ -630,7 +647,9 @@ void KeePass2XmlReader::parseDeletedObject() if (m_xml.name() == "UUID") { Uuid uuid = readUuid(); if (uuid.isNull()) { - raiseError("Null DeleteObject uuid"); + if (m_strictMode) { + raiseError("Null DeleteObject uuid"); + } } else { delObj.uuid = uuid; @@ -647,7 +666,7 @@ void KeePass2XmlReader::parseDeletedObject() if (!delObj.uuid.isNull() && !delObj.deletionTime.isNull()) { m_db->addDeletedObject(delObj); } - else { + else if (m_strictMode) { raiseError("Missing DeletedObject uuid or time"); } } @@ -665,7 +684,12 @@ Entry* KeePass2XmlReader::parseEntry(bool history) if (m_xml.name() == "UUID") { Uuid uuid = readUuid(); if (uuid.isNull()) { - raiseError("Null entry uuid"); + if (m_strictMode) { + raiseError("Null entry uuid"); + } + else { + entry->setUuid(Uuid::random()); + } } else { entry->setUuid(uuid); @@ -674,7 +698,9 @@ Entry* KeePass2XmlReader::parseEntry(bool history) else if (m_xml.name() == "IconID") { int iconId = readNumber(); if (iconId < 0) { - raiseError("Invalud entry icon number"); + if (m_strictMode) { + raiseError("Invalud entry icon number"); + } } else { entry->setIcon(iconId); @@ -726,6 +752,10 @@ Entry* KeePass2XmlReader::parseEntry(bool history) } } + if (entry->uuid().isNull() && !m_strictMode) { + entry->setUuid(Uuid::random()); + } + if (!entry->uuid().isNull()) { if (history) { entry->setUpdateTimeinfo(false); @@ -795,7 +825,13 @@ void KeePass2XmlReader::parseEntryString(Entry* entry) } if (keySet && valueSet) { - entry->attributes()->set(key, value, protect); + // the default attributes are always there so additionally check if it's empty + if (entry->attributes()->hasKey(key) && !entry->attributes()->value(key).isEmpty()) { + raiseError("Duplicate custom attribute found"); + } + else { + entry->attributes()->set(key, value, protect); + } } else { raiseError("Entry string key or value missing"); @@ -844,7 +880,12 @@ QPair KeePass2XmlReader::parseEntryBinary(Entry* entry) } if (keySet && valueSet) { - entry->attachments()->set(key, value); + if (entry->attachments()->hasKey(key)) { + raiseError("Duplicate attachment found"); + } + else { + entry->attachments()->set(key, value); + } } else { raiseError("Entry binary key or value missing"); @@ -986,7 +1027,12 @@ QDateTime KeePass2XmlReader::readDateTime() QDateTime dt = QDateTime::fromString(str, Qt::ISODate); if (!dt.isValid()) { - raiseError("Invalid date time value"); + if (m_strictMode) { + raiseError("Invalid date time value"); + } + else { + dt = Tools::currentDateTimeUtc(); + } } return dt; @@ -1001,7 +1047,9 @@ QColor KeePass2XmlReader::readColor() } if (colorStr.length() != 7 || colorStr[0] != '#') { - raiseError("Invalid color value"); + if (m_strictMode) { + raiseError("Invalid color value"); + } return QColor(); } @@ -1011,7 +1059,9 @@ QColor KeePass2XmlReader::readColor() bool ok; int rgbPart = rgbPartStr.toInt(&ok, 16); if (!ok || rgbPart > 255) { - raiseError("Invalid color rgb part"); + if (m_strictMode) { + raiseError("Invalid color rgb part"); + } return QColor(); } @@ -1043,7 +1093,9 @@ Uuid KeePass2XmlReader::readUuid() { QByteArray uuidBin = readBinary(); if (uuidBin.length() != Uuid::Length) { - raiseError("Invalid uuid value"); + if (m_strictMode) { + raiseError("Invalid uuid value"); + } return Uuid(); } else { diff --git a/src/format/KeePass2XmlReader.h b/src/format/KeePass2XmlReader.h index 4520d42b..ca311b0e 100644 --- a/src/format/KeePass2XmlReader.h +++ b/src/format/KeePass2XmlReader.h @@ -47,6 +47,7 @@ public: bool hasError(); QString errorString(); QByteArray headerHash(); + void setStrictMode(bool strictMode); private: bool parseKeePassFile(); @@ -95,6 +96,7 @@ private: QByteArray m_headerHash; bool m_error; QString m_errorStr; + bool m_strictMode; }; #endif // KEEPASSX_KEEPASS2XMLREADER_H diff --git a/src/gui/Clipboard.cpp b/src/gui/Clipboard.cpp index eb77d2b3..7d8f71fa 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 bc2a19d3..8b6ea69f 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 8d9610d9..bb1bea94 100644 --- a/src/gui/DatabaseOpenWidget.cpp +++ b/src/gui/DatabaseOpenWidget.cpp @@ -87,7 +87,7 @@ void DatabaseOpenWidget::enterKey(const QString& pw, const QString& keyFile) m_ui->editPassword->setText(pw); } if (!keyFile.isEmpty()) { - m_ui->checkKeyFile->setText(keyFile); + m_ui->comboKeyFile->setEditText(keyFile); } openDatabase(); @@ -129,8 +129,8 @@ void DatabaseOpenWidget::openDatabase(const CompositeKey& masterKey) Q_EMIT editFinished(true); } else { - MessageBox::warning(this, tr("Error"), tr("Unable to open the database.\n%1") - .arg(reader.errorString())); + MessageBox::warning(this, tr("Error"), tr("Unable to open the database.").append("\n") + .append(reader.errorString())); m_ui->editPassword->clear(); } } @@ -150,7 +150,7 @@ CompositeKey DatabaseOpenWidget::databaseKey() QString keyFilename = m_ui->comboKeyFile->currentText(); QString errorMsg; if (!key.load(keyFilename, &errorMsg)) { - MessageBox::warning(this, tr("Error"), tr("Can't open key file:\n%1").arg(errorMsg)); + MessageBox::warning(this, tr("Error"), tr("Can't open key file").append(":\n").append(errorMsg)); return CompositeKey(); } masterKey.addKey(key); diff --git a/src/gui/DatabaseTabWidget.cpp b/src/gui/DatabaseTabWidget.cpp index 4effe5f4..fa978b4f 100644 --- a/src/gui/DatabaseTabWidget.cpp +++ b/src/gui/DatabaseTabWidget.cpp @@ -19,9 +19,6 @@ #include #include -#include -#include -#include #include "autotype/AutoType.h" #include "core/Config.h" @@ -30,6 +27,7 @@ #include "core/Metadata.h" #include "core/qsavefile.h" #include "gui/DatabaseWidget.h" +#include "gui/DatabaseWidgetStateSync.h" #include "gui/DragTabBar.h" #include "gui/FileDialog.h" #include "gui/MessageBox.h" @@ -48,16 +46,17 @@ DatabaseManagerStruct::DatabaseManagerStruct() const int DatabaseTabWidget::LastDatabasesCount = 5; DatabaseTabWidget::DatabaseTabWidget(QWidget* parent) - : QTabWidget(parent), - m_fileWatcher(new QFileSystemWatcher(this)) + : QTabWidget(parent) + , m_dbWidgetSateSync(new DatabaseWidgetStateSync(this)) { DragTabBar* tabBar = new DragTabBar(this); tabBar->setDrawBase(false); setTabBar(tabBar); connect(this, SIGNAL(tabCloseRequested(int)), SLOT(closeDatabase(int))); + connect(this, SIGNAL(currentChanged(int)), SLOT(emitActivateDatabaseChanged())); + connect(this, SIGNAL(activateDatabaseChanged(DatabaseWidget*)), m_dbWidgetSateSync, SLOT(setActive(DatabaseWidget*))); connect(autoType(), SIGNAL(globalShortcutTriggered()), SLOT(performGlobalAutoType())); - connect(m_fileWatcher, SIGNAL(fileChanged(QString)), SLOT(fileChanged(QString))); } DatabaseTabWidget::~DatabaseTabWidget() @@ -71,7 +70,16 @@ DatabaseTabWidget::~DatabaseTabWidget() void DatabaseTabWidget::toggleTabbar() { - tabBar()->setVisible(count() > 1); + if (count() > 1) { + if (!tabBar()->isVisible()) { + tabBar()->show(); + } + } + else { + if (tabBar()->isVisible()) { + tabBar()->hide(); + } + } } void DatabaseTabWidget::newDatabase() @@ -97,7 +105,7 @@ void DatabaseTabWidget::openDatabase() } void DatabaseTabWidget::openDatabase(const QString& fileName, const QString& pw, - const QString& keyFile, const CompositeKey& key, int index) + const QString& keyFile) { QFileInfo fileInfo(fileName); QString canonicalFilePath = fileInfo.canonicalFilePath(); @@ -141,17 +149,12 @@ void DatabaseTabWidget::openDatabase(const QString& fileName, const QString& pw, dbStruct.filePath = fileInfo.absoluteFilePath(); dbStruct.canonicalFilePath = canonicalFilePath; dbStruct.fileName = fileInfo.fileName(); - dbStruct.lastModified = fileInfo.lastModified(); - insertDatabase(db, dbStruct, index); - m_fileWatcher->addPath(dbStruct.filePath); + insertDatabase(db, dbStruct); - updateRecentDatabases(dbStruct.filePath); + updateLastDatabases(dbStruct.filePath); - if (!key.isEmpty()) { - dbStruct.dbWidget->switchToOpenDatabase(dbStruct.filePath, key); - } - else if (!pw.isNull() || !keyFile.isEmpty()) { + if (!pw.isNull() || !keyFile.isEmpty()) { dbStruct.dbWidget->switchToOpenDatabase(dbStruct.filePath, pw, keyFile); } else { @@ -178,121 +181,6 @@ void DatabaseTabWidget::importKeePass1Database() dbStruct.dbWidget->switchToImportKeepass1(fileName); } -void DatabaseTabWidget::fileChanged(const QString &fileName) -{ - const bool wasEmpty = m_changedFiles.isEmpty(); - m_changedFiles.insert(fileName); - bool found = false; - Q_FOREACH (QString f, m_fileWatcher->files()) { - if (f == fileName) { - found = true; - break; - } - } - if (!found) m_fileWatcher->addPath(fileName); - if (wasEmpty && !m_changedFiles.isEmpty()) - QTimer::singleShot(200, this, SLOT(checkReloadDatabases())); -} - -void DatabaseTabWidget::expectFileChange(const DatabaseManagerStruct& dbStruct) -{ - if (dbStruct.filePath.isEmpty()) - return; - m_expectedFileChanges.insert(dbStruct.filePath); -} - -void DatabaseTabWidget::unexpectFileChange(DatabaseManagerStruct& dbStruct) -{ - if (dbStruct.filePath.isEmpty()) - return; - m_expectedFileChanges.remove(dbStruct.filePath); - dbStruct.lastModified = QFileInfo(dbStruct.filePath).lastModified(); -} - -void DatabaseTabWidget::checkReloadDatabases() -{ - QSet changedFiles; - - changedFiles = m_changedFiles.subtract(m_expectedFileChanges); - m_changedFiles.clear(); - - if (changedFiles.isEmpty()) - return; - - Q_FOREACH (DatabaseManagerStruct dbStruct, m_dbList) { - QString filePath = dbStruct.filePath; - Database * db = dbStruct.dbWidget->database(); - - if (!changedFiles.contains(filePath)) - continue; - - QFileInfo fi(filePath); - QDateTime lastModified = fi.lastModified(); - if (dbStruct.lastModified == lastModified) - continue; - - DatabaseWidget::Mode mode = dbStruct.dbWidget->currentMode(); - if (mode == DatabaseWidget::None || mode == DatabaseWidget::LockedMode || !db->hasKey()) - continue; - - ReloadBehavior reloadBehavior = ReloadBehavior(config()->get("ReloadBehavior").toInt()); - if ( (reloadBehavior == AlwaysAsk) - || (reloadBehavior == ReloadUnmodified && (mode == DatabaseWidget::EditMode - || mode == DatabaseWidget::OpenMode)) - || (reloadBehavior == ReloadUnmodified && dbStruct.modified)) { - int res = QMessageBox::warning(this, fi.exists() ? tr("Database file changed") : tr("Database file removed"), - tr("Do you want to discard your changes and reload?"), - QMessageBox::Yes|QMessageBox::No); - if (res == QMessageBox::No) - continue; - } - - if (fi.exists()) { - //Ignore/cancel all edits - dbStruct.dbWidget->switchToView(false); - dbStruct.modified = false; - - //Save current group/entry - Uuid currentGroup; - if (Group* group = dbStruct.dbWidget->currentGroup()) - currentGroup = group->uuid(); - Uuid currentEntry; - if (Entry* entry = dbStruct.dbWidget->entryView()->currentEntry()) - currentEntry = entry->uuid(); - QString searchText = dbStruct.dbWidget->searchText(); - bool caseSensitive = dbStruct.dbWidget->caseSensitiveSearch(); - bool allGroups = dbStruct.dbWidget->isAllGroupsSearch(); - - //Reload updated db - CompositeKey key = db->key(); - int tabPos = databaseIndex(db); - closeDatabase(db); - openDatabase(filePath, QString(), QString(), key, tabPos); - - //Restore current group/entry - dbStruct = indexDatabaseManagerStruct(count() - 1); - if (dbStruct.dbWidget && dbStruct.dbWidget->currentMode() == DatabaseWidget::ViewMode) { - Database * db = dbStruct.dbWidget->database(); - if (!currentGroup.isNull()) - if (Group* group = db->resolveGroup(currentGroup)) - dbStruct.dbWidget->groupView()->setCurrentGroup(group); - if (!searchText.isEmpty()) - dbStruct.dbWidget->search(searchText, caseSensitive, allGroups); - if (!currentEntry.isNull()) - if (Entry* entry = db->resolveEntry(currentEntry)) - dbStruct.dbWidget->entryView()->setCurrentEntry(entry); - } - } else { - //Ignore/cancel all edits - dbStruct.dbWidget->switchToView(false); - dbStruct.modified = false; - - //Close database - closeDatabase(dbStruct.dbWidget->database()); - } - } -} - bool DatabaseTabWidget::closeDatabase(Database* db) { Q_ASSERT(db); @@ -305,8 +193,7 @@ bool DatabaseTabWidget::closeDatabase(Database* db) if (dbName.right(1) == "*") { dbName.chop(1); } - if ((dbStruct.dbWidget->currentMode() == DatabaseWidget::EditMode || - dbStruct.dbWidget->currentMode() == DatabaseWidget::OpenMode) && db->hasKey()) { + if (dbStruct.dbWidget->isInEditMode() && db->hasKey()) { QMessageBox::StandardButton result = MessageBox::question( this, tr("Close?"), @@ -348,7 +235,6 @@ void DatabaseTabWidget::deleteDatabase(Database* db) int index = databaseIndex(db); - m_fileWatcher->removePath(dbStruct.filePath); removeTab(index); toggleTabbar(); m_dbList.remove(db); @@ -362,13 +248,6 @@ void DatabaseTabWidget::deleteDatabase(Database* db) bool DatabaseTabWidget::closeAllDatabases() { - QStringList reloadDatabases; - if (config()->get("AutoReopenLastDatabases", false).toBool()) { - for (int i = 0; i < count(); i ++) - reloadDatabases << indexDatabaseManagerStruct(i).filePath; - } - config()->set("LastOpenDatabases", reloadDatabases); - while (!m_dbList.isEmpty()) { if (!closeDatabase()) { return false; @@ -384,16 +263,12 @@ void DatabaseTabWidget::saveDatabase(Database* db) if (dbStruct.saveToFilename) { bool result = false; - expectFileChange(dbStruct); - QSaveFile saveFile(dbStruct.filePath); if (saveFile.open(QIODevice::WriteOnly)) { m_writer.writeDatabase(&saveFile, db); result = saveFile.commit(); } - unexpectFileChange(dbStruct); - if (result) { dbStruct.modified = false; updateTabName(db); @@ -411,12 +286,12 @@ void DatabaseTabWidget::saveDatabase(Database* db) void DatabaseTabWidget::saveDatabaseAs(Database* db) { DatabaseManagerStruct& dbStruct = m_dbList[db]; - QString oldFilePath; + QString oldFileName; if (dbStruct.saveToFilename) { - oldFilePath = dbStruct.filePath; + oldFileName = dbStruct.filePath; } QString fileName = fileDialog()->getSaveFileName(this, tr("Save database as"), - oldFilePath, tr("KeePass 2 Database").append(" (*.kdbx)")); + oldFileName, tr("KeePass 2 Database").append(" (*.kdbx)")); if (!fileName.isEmpty()) { bool result = false; @@ -427,18 +302,15 @@ void DatabaseTabWidget::saveDatabaseAs(Database* db) } if (result) { - m_fileWatcher->removePath(oldFilePath); dbStruct.modified = false; dbStruct.saveToFilename = true; QFileInfo fileInfo(fileName); dbStruct.filePath = fileInfo.absoluteFilePath(); dbStruct.canonicalFilePath = fileInfo.canonicalFilePath(); dbStruct.fileName = fileInfo.fileName(); - dbStruct.lastModified = fileInfo.lastModified(); dbStruct.dbWidget->updateFilename(dbStruct.filePath); updateTabName(db); - updateRecentDatabases(dbStruct.filePath); - m_fileWatcher->addPath(dbStruct.filePath); + updateLastDatabases(dbStruct.filePath); } else { MessageBox::critical(this, tr("Error"), tr("Writing the database failed.") + "\n\n" @@ -609,13 +481,14 @@ Database* DatabaseTabWidget::databaseFromDatabaseWidget(DatabaseWidget* dbWidget return Q_NULLPTR; } -void DatabaseTabWidget::insertDatabase(Database* db, const DatabaseManagerStruct& dbStruct, int index) +void DatabaseTabWidget::insertDatabase(Database* db, const DatabaseManagerStruct& dbStruct) { m_dbList.insert(db, dbStruct); - index = insertTab(index, dbStruct.dbWidget, ""); + addTab(dbStruct.dbWidget, ""); toggleTabbar(); updateTabName(db); + int index = databaseIndex(db); setCurrentIndex(index); connectDatabase(db); connect(dbStruct.dbWidget, SIGNAL(closeRequest()), SLOT(closeDatabaseFromSender())); @@ -634,7 +507,7 @@ DatabaseWidget* DatabaseTabWidget::currentDatabaseWidget() } } -bool DatabaseTabWidget::hasLockableDatabases() +bool DatabaseTabWidget::hasLockableDatabases() const { QHashIterator i(m_dbList); while (i.hasNext()) { @@ -687,7 +560,7 @@ void DatabaseTabWidget::modified() } } -void DatabaseTabWidget::updateRecentDatabases(const QString& filename) +void DatabaseTabWidget::updateLastDatabases(const QString& filename) { if (!config()->get("RememberLastDatabases").toBool()) { config()->set("LastDatabases", QVariant()); @@ -719,6 +592,11 @@ void DatabaseTabWidget::changeDatabase(Database* newDb) connectDatabase(newDb, oldDb); } +void DatabaseTabWidget::emitActivateDatabaseChanged() +{ + Q_EMIT activateDatabaseChanged(currentDatabaseWidget()); +} + void DatabaseTabWidget::connectDatabase(Database* newDb, Database* oldDb) { if (oldDb) { diff --git a/src/gui/DatabaseTabWidget.h b/src/gui/DatabaseTabWidget.h index 8fda1094..25d34f30 100644 --- a/src/gui/DatabaseTabWidget.h +++ b/src/gui/DatabaseTabWidget.h @@ -18,18 +18,16 @@ #ifndef KEEPASSX_DATABASETABWIDGET_H #define KEEPASSX_DATABASETABWIDGET_H -#include #include #include -#include #include "format/KeePass2Writer.h" #include "gui/DatabaseWidget.h" class DatabaseWidget; +class DatabaseWidgetStateSync; class DatabaseOpenWidget; class QFile; -class QFileSystemWatcher; struct DatabaseManagerStruct { @@ -42,7 +40,6 @@ struct DatabaseManagerStruct bool saveToFilename; bool modified; bool readOnly; - QDateTime lastModified; }; Q_DECLARE_TYPEINFO(DatabaseManagerStruct, Q_MOVABLE_TYPE); @@ -55,25 +52,16 @@ public: explicit DatabaseTabWidget(QWidget* parent = Q_NULLPTR); ~DatabaseTabWidget(); void openDatabase(const QString& fileName, const QString& pw = QString(), - const QString& keyFile = QString(), const CompositeKey& key = CompositeKey(), - int index = -1); + const QString& keyFile = QString()); DatabaseWidget* currentDatabaseWidget(); - bool hasLockableDatabases(); + bool hasLockableDatabases() const; static const int LastDatabasesCount; - enum ReloadBehavior { - AlwaysAsk, - ReloadUnmodified, - IgnoreAll - }; - public Q_SLOTS: void newDatabase(); void openDatabase(); void importKeePass1Database(); - void fileChanged(const QString& fileName); - void checkReloadDatabases(); void saveDatabase(int index = -1); void saveDatabaseAs(int index = -1); bool closeDatabase(int index = -1); @@ -88,6 +76,7 @@ public Q_SLOTS: Q_SIGNALS: void tabNameChanged(); void databaseWithFileClosed(QString filePath); + void activateDatabaseChanged(DatabaseWidget* dbWidget); private Q_SLOTS: void updateTabName(Database* db); @@ -96,6 +85,7 @@ private Q_SLOTS: void modified(); void toggleTabbar(); void changeDatabase(Database* newDb); + void emitActivateDatabaseChanged(); private: void saveDatabase(Database* db); @@ -106,17 +96,13 @@ private: Database* indexDatabase(int index); DatabaseManagerStruct indexDatabaseManagerStruct(int index); Database* databaseFromDatabaseWidget(DatabaseWidget* dbWidget); - void insertDatabase(Database* db, const DatabaseManagerStruct& dbStruct, int index = -1); - void updateRecentDatabases(const QString& filename); + void insertDatabase(Database* db, const DatabaseManagerStruct& dbStruct); + void updateLastDatabases(const QString& filename); void connectDatabase(Database* newDb, Database* oldDb = Q_NULLPTR); - void expectFileChange(const DatabaseManagerStruct& dbStruct); - void unexpectFileChange(DatabaseManagerStruct& dbStruct); KeePass2Writer m_writer; QHash m_dbList; - QSet m_changedFiles; - QSet m_expectedFileChanges; - QFileSystemWatcher* m_fileWatcher; + DatabaseWidgetStateSync* m_dbWidgetSateSync; }; #endif // KEEPASSX_DATABASETABWIDGET_H diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp index f2688ff5..eccf49de 100644 --- a/src/gui/DatabaseWidget.cpp +++ b/src/gui/DatabaseWidget.cpp @@ -28,6 +28,7 @@ #include "autotype/AutoType.h" #include "core/Config.h" +#include "core/EntrySearcher.h" #include "core/FilePath.h" #include "core/Group.h" #include "core/Metadata.h" @@ -52,8 +53,6 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent) , m_newGroup(Q_NULLPTR) , m_newEntry(Q_NULLPTR) , m_newParent(Q_NULLPTR) - , m_searchAllGroups(false) - , m_searchSensitivity(Qt::CaseInsensitive) { m_searchUi->setupUi(m_searchWidget); @@ -62,12 +61,13 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent) m_mainWidget = new QWidget(this); QLayout* layout = new QHBoxLayout(m_mainWidget); - QSplitter* splitter = new QSplitter(m_mainWidget); + m_splitter = new QSplitter(m_mainWidget); + m_splitter->setChildrenCollapsible(false); - QWidget* rightHandSideWidget = new QWidget(splitter); + QWidget* rightHandSideWidget = new QWidget(m_splitter); m_searchWidget->setParent(rightHandSideWidget); - m_groupView = new GroupView(db, splitter); + m_groupView = new GroupView(db, m_splitter); m_groupView->setObjectName("groupView"); m_groupView->setContextMenuPolicy(Qt::CustomContextMenu); connect(m_groupView, SIGNAL(customContextMenuRequested(QPoint)), @@ -80,22 +80,13 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent) connect(m_entryView, SIGNAL(customContextMenuRequested(QPoint)), SLOT(emitEntryContextMenuRequested(QPoint))); - QSizePolicy policy; - policy = m_groupView->sizePolicy(); - policy.setHorizontalStretch(30); - m_groupView->setSizePolicy(policy); - policy = rightHandSideWidget->sizePolicy(); - policy.setHorizontalStretch(70); - rightHandSideWidget->setSizePolicy(policy); - QAction* closeAction = new QAction(m_searchWidget); QIcon closeIcon = filePath()->icon("actions", "dialog-close"); closeAction->setIcon(closeIcon); m_searchUi->closeSearchButton->setDefaultAction(closeAction); m_searchUi->closeSearchButton->setShortcut(Qt::Key_Escape); - int iconsize = style()->pixelMetric(QStyle::PM_SmallIconSize); - m_searchUi->closeSearchButton->setIconSize(QSize(iconsize,iconsize)); m_searchWidget->hide(); + m_searchUi->caseSensitiveCheckBox->setVisible(false); QVBoxLayout* vLayout = new QVBoxLayout(rightHandSideWidget); vLayout->setMargin(0); @@ -104,10 +95,17 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent) rightHandSideWidget->setLayout(vLayout); - splitter->addWidget(m_groupView); - splitter->addWidget(rightHandSideWidget); + setTabOrder(m_searchUi->searchRootRadioButton, m_entryView); + setTabOrder(m_entryView, m_groupView); + setTabOrder(m_groupView, m_searchWidget); - layout->addWidget(splitter); + m_splitter->addWidget(m_groupView); + m_splitter->addWidget(rightHandSideWidget); + + m_splitter->setStretchFactor(0, 30); + m_splitter->setStretchFactor(1, 70); + + layout->addWidget(m_splitter); m_mainWidget->setLayout(layout); m_editEntryWidget = new EditEntryWidget(); @@ -139,6 +137,8 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent) addWidget(m_keepass1OpenWidget); addWidget(m_unlockDatabaseWidget); + connect(m_splitter, SIGNAL(splitterMoved(int,int)), SIGNAL(splitterSizesChanged())); + connect(m_entryView->header(), SIGNAL(sectionResized(int,int,int)), SIGNAL(entryColumnSizesChanged())); connect(m_groupView, SIGNAL(groupChanged(Group*)), this, SLOT(clearLastGroup(Group*))); connect(m_groupView, SIGNAL(groupChanged(Group*)), SIGNAL(groupChanged())); connect(m_groupView, SIGNAL(groupChanged(Group*)), m_entryView, SLOT(setGroup(Group*))); @@ -155,7 +155,11 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent) connect(m_keepass1OpenWidget, SIGNAL(editFinished(bool)), SLOT(openDatabase(bool))); connect(m_unlockDatabaseWidget, SIGNAL(editFinished(bool)), SLOT(unlockDatabase(bool))); connect(this, SIGNAL(currentChanged(int)), this, SLOT(emitCurrentModeChanged())); - connect(m_searchUi->searchResults, SIGNAL(linkActivated(QString)), this, SLOT(onLinkActivated(QString))); + connect(m_searchUi->searchEdit, SIGNAL(textChanged(QString)), this, SLOT(startSearchTimer())); + connect(m_searchUi->caseSensitiveCheckBox, SIGNAL(toggled(bool)), this, SLOT(startSearch())); + connect(m_searchUi->searchCurrentRadioButton, SIGNAL(toggled(bool)), this, SLOT(startSearch())); + connect(m_searchUi->searchRootRadioButton, SIGNAL(toggled(bool)), this, SLOT(startSearch())); + connect(m_searchUi->searchEdit, SIGNAL(returnPressed()), m_entryView, SLOT(setFocus())); connect(m_searchTimer, SIGNAL(timeout()), this, SLOT(search())); connect(closeAction, SIGNAL(triggered()), this, SLOT(closeSearch())); @@ -166,7 +170,7 @@ DatabaseWidget::~DatabaseWidget() { } -DatabaseWidget::Mode DatabaseWidget::currentMode() +DatabaseWidget::Mode DatabaseWidget::currentMode() const { if (currentWidget() == Q_NULLPTR) { return DatabaseWidget::None; @@ -185,21 +189,56 @@ DatabaseWidget::Mode DatabaseWidget::currentMode() } } +bool DatabaseWidget::isInEditMode() const +{ + if (currentMode() == DatabaseWidget::LockedMode) { + return m_widgetBeforeLock != Q_NULLPTR + && m_widgetBeforeLock != m_mainWidget + && m_widgetBeforeLock != m_unlockDatabaseWidget; + } + else { + return currentMode() == DatabaseWidget::EditMode; + } +} + +QList 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; @@ -296,8 +335,7 @@ void DatabaseWidget::deleteEntries() if (selected.size() > 1) { QMessageBox::StandardButton result = MessageBox::question( this, tr("Move entries to recycle bin?"), - tr("Do you really want to move %1 entries to the recycle bin?") - .arg(selected.size()), + tr("Do you really want to move %n entry(s) to the recycle bin?", 0, selected.size()), QMessageBox::Yes | QMessageBox::No); if (result == QMessageBox::No) { return; @@ -429,7 +467,7 @@ void DatabaseWidget::createGroup() void DatabaseWidget::deleteGroup() { Group* currentGroup = m_groupView->currentGroup(); - if (!currentGroup || !canDeleteCurrentGoup()) { + if (!currentGroup || !canDeleteCurrentGroup()) { Q_ASSERT(false); return; } @@ -597,7 +635,7 @@ void DatabaseWidget::unlockDatabase(bool accepted) Q_ASSERT(accepted); Q_UNUSED(accepted); - setCurrentWidget(widgetBeforeLock); + setCurrentWidget(m_widgetBeforeLock); Q_EMIT unlockedDatabase(); } @@ -660,13 +698,6 @@ void DatabaseWidget::switchToOpenDatabase(const QString& fileName, const QString m_databaseOpenWidget->enterKey(password, keyFile); } -void DatabaseWidget::switchToOpenDatabase(const QString &fileName, const CompositeKey& masterKey) -{ - updateFilename(fileName); - switchToOpenDatabase(fileName); - m_databaseOpenWidget->enterKey(masterKey); -} - void DatabaseWidget::switchToImportKeepass1(const QString& fileName) { updateFilename(fileName); @@ -674,117 +705,100 @@ void DatabaseWidget::switchToImportKeepass1(const QString& fileName) setCurrentWidget(m_keepass1OpenWidget); } -void DatabaseWidget::search(const QString& searchString, bool caseSensitive, bool allGroups) +void DatabaseWidget::openSearch() { - m_searchSensitivity = caseSensitive ? Qt::CaseSensitive - : Qt::CaseInsensitive; - m_searchAllGroups = allGroups; - search(searchString); -} + if (isInSearchMode()) { + m_searchUi->searchEdit->selectAll(); -void DatabaseWidget::search(const QString& text) -{ - if (text.isEmpty()) { - if (m_entryView->inEntryListMode()) - closeSearch(); - } - else if (m_entryView->inEntryListMode()) { - m_searchText = text; - startSearchTimer(); + if (!m_searchUi->searchEdit->hasFocus()) { + m_searchUi->searchEdit->setFocus(); + // make sure the search action is checked again + emitCurrentModeChanged(); + } } else { - showSearch(text); - } -} - -bool DatabaseWidget::caseSensitiveSearch() const -{ - return m_searchSensitivity == Qt::CaseSensitive; -} - -void DatabaseWidget::setCaseSensitiveSearch(bool caseSensitive) -{ - if (caseSensitive != caseSensitiveSearch()) { - m_searchSensitivity = caseSensitive ? Qt::CaseSensitive - : Qt::CaseInsensitive; - if (m_entryView->inEntryListMode()) - startSearchTimer(); - } -} - -bool DatabaseWidget::isAllGroupsSearch() const -{ - return m_searchAllGroups; -} - -bool DatabaseWidget::canChooseSearchScope() const -{ - return currentGroup() != m_db->rootGroup(); -} - -Group*DatabaseWidget::currentGroup() const -{ - return m_entryView->inEntryListMode() ? m_lastGroup - : m_groupView->currentGroup(); -} - -void DatabaseWidget::setAllGroupsSearch(bool allGroups) -{ - if (allGroups != isAllGroupsSearch()) { - m_searchAllGroups = allGroups; - if (m_entryView->inEntryListMode()) - startSearchTimer(); + showSearch(); } } void DatabaseWidget::closeSearch() { Q_ASSERT(m_lastGroup); - m_searchTimer->stop(); + + Q_EMIT listModeAboutToActivate(); + m_groupView->setCurrentGroup(m_lastGroup); + m_searchTimer->stop(); + + Q_EMIT listModeActivated(); } -void DatabaseWidget::showSearch(const QString & searchString) +void DatabaseWidget::showSearch() { - m_searchText = searchString; + Q_EMIT searchModeAboutToActivate(); + + m_searchUi->searchEdit->blockSignals(true); + m_searchUi->searchEdit->clear(); + m_searchUi->searchEdit->blockSignals(false); + + m_searchUi->searchCurrentRadioButton->blockSignals(true); + m_searchUi->searchRootRadioButton->blockSignals(true); + m_searchUi->searchRootRadioButton->setChecked(true); + m_searchUi->searchCurrentRadioButton->blockSignals(false); + m_searchUi->searchRootRadioButton->blockSignals(false); + m_lastGroup = m_groupView->currentGroup(); Q_ASSERT(m_lastGroup); + + if (m_lastGroup == m_db->rootGroup()) { + m_searchUi->optionsWidget->hide(); + m_searchUi->searchCurrentRadioButton->hide(); + m_searchUi->searchRootRadioButton->hide(); + } + else { + m_searchUi->optionsWidget->show(); + m_searchUi->searchCurrentRadioButton->show(); + m_searchUi->searchRootRadioButton->show(); + m_searchUi->searchCurrentRadioButton->setText(tr("Current group") + .append(" (") + .append(m_lastGroup->name()) + .append(")")); + } m_groupView->setCurrentIndex(QModelIndex()); m_searchWidget->show(); search(); -} + m_searchUi->searchEdit->setFocus(); -void DatabaseWidget::onLinkActivated(const QString& link) -{ - if (link == "searchAll") - setAllGroupsSearch(true); - else if (link == "searchCurrent") - setAllGroupsSearch(false); + Q_EMIT searchModeActivated(); } void DatabaseWidget::search() { Q_ASSERT(m_lastGroup); - Group* searchGroup = m_searchAllGroups ? m_db->rootGroup() - : m_lastGroup; - QList searchResult = searchGroup->search(m_searchText, m_searchSensitivity); - - QString message; - switch(searchResult.count()) { - case 0: message = tr("No result found"); break; - case 1: message = tr("1 result found"); break; - default: message = tr("%1 results found").arg(searchResult.count()); break; + Group* searchGroup; + if (m_searchUi->searchCurrentRadioButton->isChecked()) { + searchGroup = m_lastGroup; } - if (searchGroup != m_db->rootGroup()) - message += tr(" in \"%1\". Search all groups...").arg(searchGroup->name()); - else if (m_lastGroup != m_db->rootGroup()) - message += tr(". Search in \"%1\"...").arg(m_lastGroup->name()); - else - message += tr("."); - m_searchUi->searchResults->setText(message); + else if (m_searchUi->searchRootRadioButton->isChecked()) { + searchGroup = m_db->rootGroup(); + } + else { + Q_ASSERT(false); + return; + } + + Qt::CaseSensitivity sensitivity; + if (m_searchUi->caseSensitiveCheckBox->isChecked()) { + sensitivity = Qt::CaseSensitive; + } + else { + sensitivity = Qt::CaseInsensitive; + } + + QList searchResult = EntrySearcher().search(m_searchUi->searchEdit->text(), searchGroup, sensitivity); m_entryView->setEntryList(searchResult); } @@ -799,9 +813,6 @@ void DatabaseWidget::startSearchTimer() void DatabaseWidget::startSearch() { - if (!isInSearchMode()) - return; - if (!m_searchTimer->isActive()) { m_searchTimer->stop(); } @@ -818,12 +829,12 @@ void DatabaseWidget::emitEntryContextMenuRequested(const QPoint& pos) Q_EMIT entryContextMenuRequested(m_entryView->viewport()->mapToGlobal(pos)); } -bool DatabaseWidget::dbHasKey() +bool DatabaseWidget::dbHasKey() const { return m_db->hasKey(); } -bool DatabaseWidget::canDeleteCurrentGoup() +bool DatabaseWidget::canDeleteCurrentGroup() const { bool isRootGroup = m_db->rootGroup() == m_groupView->currentGroup(); bool isRecycleBin = m_db->metadata()->recycleBin() == m_groupView->currentGroup(); @@ -835,11 +846,6 @@ bool DatabaseWidget::isInSearchMode() const return m_entryView->inEntryListMode(); } -QString DatabaseWidget::searchText() const -{ - return m_entryView->inEntryListMode() ? m_searchText : QString(); -} - void DatabaseWidget::clearLastGroup(Group* group) { if (group) { @@ -852,7 +858,7 @@ void DatabaseWidget::lock() { Q_ASSERT(currentMode() != DatabaseWidget::LockedMode); - widgetBeforeLock = currentWidget(); + m_widgetBeforeLock = currentWidget(); m_unlockDatabaseWidget->load(m_filename, m_db); setCurrentWidget(m_unlockDatabaseWidget); } @@ -861,3 +867,23 @@ void DatabaseWidget::updateFilename(const QString& fileName) { m_filename = fileName; } + +int DatabaseWidget::numberOfSelectedEntries() const +{ + return m_entryView->numberOfSelectedEntries(); +} + +QStringList DatabaseWidget::customEntryAttributes() const +{ + Entry* entry = m_entryView->currentEntry(); + if (!entry) { + return QStringList(); + } + + return entry->attributes()->customKeys(); +} + +bool DatabaseWidget::isGroupSelected() const +{ + return m_groupView->currentGroup() != Q_NULLPTR; +} diff --git a/src/gui/DatabaseWidget.h b/src/gui/DatabaseWidget.h index c0f10452..e906a795 100644 --- a/src/gui/DatabaseWidget.h +++ b/src/gui/DatabaseWidget.h @@ -38,8 +38,8 @@ class GroupView; class KeePass1OpenWidget; class QFile; class QMenu; +class QSplitter; class UnlockDatabaseWidget; -class CompositeKey; namespace Ui { class SearchWidget; @@ -61,24 +61,24 @@ public: explicit DatabaseWidget(Database* db, QWidget* parent = Q_NULLPTR); ~DatabaseWidget(); - GroupView* groupView(); - EntryView* entryView(); Database* database(); - bool dbHasKey(); - bool canDeleteCurrentGoup(); + bool dbHasKey() const; + bool canDeleteCurrentGroup() const; bool isInSearchMode() const; - QString searchText() const; - bool caseSensitiveSearch() const; - bool isAllGroupsSearch() const; - bool canChooseSearchScope() const; - Group* currentGroup() const; int addWidget(QWidget* w); void setCurrentIndex(int index); void setCurrentWidget(QWidget* widget); - DatabaseWidget::Mode currentMode(); + DatabaseWidget::Mode currentMode() const; void lock(); void updateFilename(const QString& filename); - void search(const QString & searchString, bool caseSensitive, bool allGroups); + int numberOfSelectedEntries() const; + QStringList customEntryAttributes() const; + bool isGroupSelected() const; + bool isInEditMode() const; + QList splitterSizes() const; + void setSplitterSizes(const QList& sizes); + QList entryHeaderViewSizes() const; + void setEntryViewHeaderSizes(const QList& sizes); Q_SIGNALS: void closeRequest(); @@ -89,6 +89,12 @@ Q_SIGNALS: void groupContextMenuRequested(const QPoint& globalPos); void entryContextMenuRequested(const QPoint& globalPos); void unlockedDatabase(); + void listModeAboutToActivate(); + void listModeActivated(); + void searchModeAboutToActivate(); + void searchModeActivated(); + void splitterSizesChanged(); + void entryColumnSizesChanged(); public Q_SLOTS: void createEntry(); @@ -111,24 +117,19 @@ public Q_SLOTS: void switchToDatabaseSettings(); void switchToOpenDatabase(const QString& fileName); void switchToOpenDatabase(const QString& fileName, const QString& password, const QString& keyFile); - void switchToOpenDatabase(const QString &fileName, const CompositeKey &masterKey); void switchToImportKeepass1(const QString& fileName); - void switchToView(bool accepted); - void search(const QString & searchString); - void setCaseSensitiveSearch(bool caseSensitive); - void setAllGroupsSearch(bool allGroups); - void emitGroupContextMenuRequested(const QPoint& pos); - void emitEntryContextMenuRequested(const QPoint& pos); + void openSearch(); private Q_SLOTS: void entryActivationSignalReceived(Entry* entry, EntryModel::ModelColumn column); - void onLinkActivated(const QString& link); - void showSearch(const QString & searchString = QString()); void switchBackToEntryEdit(); + void switchToView(bool accepted); void switchToHistoryView(Entry* entry); void switchToEntryEdit(Entry* entry); void switchToEntryEdit(Entry* entry, bool create); void switchToGroupEdit(Group* entry, bool create); + void emitGroupContextMenuRequested(const QPoint& pos); + void emitEntryContextMenuRequested(const QPoint& pos); void updateMasterKey(bool accepted); void openDatabase(bool accepted); void unlockDatabase(bool accepted); @@ -137,6 +138,7 @@ private Q_SLOTS: void search(); void startSearch(); void startSearchTimer(); + void showSearch(); void closeSearch(); private: @@ -155,6 +157,7 @@ private: DatabaseOpenWidget* m_databaseOpenWidget; KeePass1OpenWidget* m_keepass1OpenWidget; UnlockDatabaseWidget* m_unlockDatabaseWidget; + QSplitter* m_splitter; GroupView* m_groupView; EntryView* m_entryView; Group* m_newGroup; @@ -162,11 +165,8 @@ private: Group* m_newParent; Group* m_lastGroup; QTimer* m_searchTimer; - QWidget* widgetBeforeLock; + QWidget* m_widgetBeforeLock; QString m_filename; - QString m_searchText; - bool m_searchAllGroups; - Qt::CaseSensitivity m_searchSensitivity; }; #endif // KEEPASSX_DATABASEWIDGET_H diff --git a/src/gui/DatabaseWidgetStateSync.cpp b/src/gui/DatabaseWidgetStateSync.cpp new file mode 100644 index 00000000..66b8492e --- /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 00000000..f6a87cd9 --- /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 348ee576..aa849180 100644 --- a/src/gui/EditWidgetIcons.cpp +++ b/src/gui/EditWidgetIcons.cpp @@ -271,8 +271,7 @@ void EditWidgetIcons::removeCustomIcon() } else { MessageBox::information(this, tr("Can't delete icon!"), - tr("Can't delete icon. Still used by %1 items.") - .arg(iconUsedCount)); + tr("Can't delete icon. Still used by %n item(s).", 0, iconUsedCount)); } } } diff --git a/src/gui/KeePass1OpenWidget.cpp b/src/gui/KeePass1OpenWidget.cpp index 5f23d807..96ddf13f 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 2caade09..d48d0950 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -20,79 +20,34 @@ #include #include -#include #include "autotype/AutoType.h" #include "core/Config.h" -#include "core/Database.h" -#include "core/Entry.h" #include "core/FilePath.h" #include "core/InactivityTimer.h" #include "core/Metadata.h" #include "gui/AboutDialog.h" #include "gui/DatabaseWidget.h" -#include "gui/entry/EntryView.h" -#include "gui/group/GroupView.h" - -#include "http/Service.h" -#include "http/HttpSettings.h" -#include "http/OptionDialog.h" -#include "gui/SettingsWidget.h" -#include "gui/qocoa/qsearchfield.h" - -class HttpPlugin: public ISettingsPage { -public: - HttpPlugin(DatabaseTabWidget * tabWidget) { - m_service = new Service(tabWidget); - } - virtual ~HttpPlugin() { - //delete m_service; - } - virtual QString name() { - return QObject::tr("Http"); - } - virtual QWidget * createWidget() { - OptionDialog * dlg = new OptionDialog(); - QObject::connect(dlg, SIGNAL(removeSharedEncryptionKeys()), m_service, SLOT(removeSharedEncryptionKeys())); - QObject::connect(dlg, SIGNAL(removeStoredPermissions()), m_service, SLOT(removeStoredPermissions())); - return dlg; - } - virtual void loadSettings(QWidget * widget) { - qobject_cast(widget)->loadSettings(); - } - virtual void saveSettings(QWidget * widget) { - qobject_cast(widget)->saveSettings(); - if (HttpSettings::isEnabled()) - m_service->start(); - else - m_service->stop(); - } -private: - Service *m_service; -}; const QString MainWindow::BaseWindowTitle = "KeePassX"; MainWindow::MainWindow() : m_ui(new Ui::MainWindow()) + , m_trayIcon(Q_NULLPTR) { m_ui->setupUi(this); - restoreGeometry(config()->get("window/Geometry").toByteArray()); - m_ui->settingsWidget->addSettingsPage(new HttpPlugin(m_ui->tabWidget)); + m_countDefaultAttributes = m_ui->menuEntryCopyAttribute->actions().size(); + + restoreGeometry(config()->get("GUI/MainWindowGeometry").toByteArray()); setWindowIcon(filePath()->applicationIcon()); QAction* toggleViewAction = m_ui->toolBar->toggleViewAction(); toggleViewAction->setText(tr("Show toolbar")); m_ui->menuView->addAction(toggleViewAction); - int toolbarIconSize = config()->get("ToolbarIconSize", 20).toInt(); - setToolbarIconSize(toolbarIconSize); bool showToolbar = config()->get("ShowToolbar").toBool(); m_ui->toolBar->setVisible(showToolbar); connect(m_ui->toolBar, SIGNAL(visibilityChanged(bool)), this, SLOT(saveToolbarState(bool))); - connect(m_ui->actionToolbarIconSize16, SIGNAL(triggered()), this, SLOT(setToolbarIconSize16())); - connect(m_ui->actionToolbarIconSize22, SIGNAL(triggered()), this, SLOT(setToolbarIconSize22())); - connect(m_ui->actionToolbarIconSize28, SIGNAL(triggered()), this, SLOT(setToolbarIconSize28())); m_clearHistoryAction = new QAction("Clear history", m_ui->menuFile); m_lastDatabasesActions = new QActionGroup(m_ui->menuRecentDatabases); @@ -124,19 +79,13 @@ MainWindow::MainWindow() setShortcut(m_ui->actionDatabaseClose, QKeySequence::Close, Qt::CTRL + Qt::Key_W); m_ui->actionLockDatabases->setShortcut(Qt::CTRL + Qt::Key_L); setShortcut(m_ui->actionQuit, QKeySequence::Quit, Qt::CTRL + Qt::Key_Q); - //TODO: do not register shortcut on Q_OS_MAC, if this is done automatically?? - const QKeySequence seq = !QKeySequence::keyBindings(QKeySequence::Find).isEmpty() - ? QKeySequence::Find - : QKeySequence(Qt::CTRL + Qt::Key_F); - connect(new QShortcut(seq, this), SIGNAL(activated()), m_ui->searchField, SLOT(setFocus())); - m_ui->searchField->setContentsMargins(5,0,5,0); + setShortcut(m_ui->actionSearch, QKeySequence::Find, Qt::CTRL + Qt::Key_F); m_ui->actionEntryNew->setShortcut(Qt::CTRL + Qt::Key_N); m_ui->actionEntryEdit->setShortcut(Qt::CTRL + Qt::Key_E); m_ui->actionEntryDelete->setShortcut(Qt::CTRL + Qt::Key_D); m_ui->actionEntryClone->setShortcut(Qt::CTRL + Qt::Key_K); m_ui->actionEntryCopyUsername->setShortcut(Qt::CTRL + Qt::Key_B); m_ui->actionEntryCopyPassword->setShortcut(Qt::CTRL + Qt::Key_C); - m_ui->actionEntryCopyURL->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_U); setShortcut(m_ui->actionEntryAutoType, QKeySequence::Paste, Qt::CTRL + Qt::Key_V); m_ui->actionEntryOpenUrl->setShortcut(Qt::CTRL + Qt::Key_U); @@ -170,6 +119,8 @@ MainWindow::MainWindow() m_ui->actionAbout->setIcon(filePath()->icon("actions", "help-about")); + m_ui->actionSearch->setIcon(filePath()->icon("actions", "system-search")); + m_actionMultiplexer.connect(SIGNAL(currentModeChanged(DatabaseWidget::Mode)), this, SLOT(setMenuActionState(DatabaseWidget::Mode))); m_actionMultiplexer.connect(SIGNAL(groupChanged()), @@ -249,25 +200,10 @@ MainWindow::MainWindow() connect(m_ui->actionAbout, SIGNAL(triggered()), SLOT(showAboutDialog())); - m_ui->searchField->setPlaceholderText(tr("Type to search")); - m_ui->searchField->setEnabled(false); - m_ui->toolBar->addWidget(m_ui->mySpacer); - m_ui->toolBar->addWidget(m_ui->searchField); - m_actionMultiplexer.connect(m_ui->searchField, SIGNAL(textChanged(QString)), - SLOT(search(QString))); - QMenu* searchMenu = new QMenu(this); - searchMenu->addAction(m_ui->actionFindCaseSensitive); - searchMenu->addSeparator(); - searchMenu->addAction(m_ui->actionFindCurrentGroup); - searchMenu->addAction(m_ui->actionFindRootGroup); - m_ui->searchField->setMenu(searchMenu); - QActionGroup* group = new QActionGroup(this); - group->addAction(m_ui->actionFindCurrentGroup); - group->addAction(m_ui->actionFindRootGroup); - m_actionMultiplexer.connect(m_ui->actionFindCaseSensitive, SIGNAL(toggled(bool)), - SLOT(setCaseSensitiveSearch(bool))); - m_actionMultiplexer.connect(m_ui->actionFindRootGroup, SIGNAL(toggled(bool)), - SLOT(setAllGroupsSearch(bool))); + m_actionMultiplexer.connect(m_ui->actionSearch, SIGNAL(triggered()), + SLOT(openSearch())); + + updateTrayIcon(); } MainWindow::~MainWindow() @@ -294,17 +230,16 @@ void MainWindow::updateCopyAttributesMenu() return; } - Entry* entry = dbWidget->entryView()->currentEntry(); - if (!entry || !dbWidget->entryView()->isSingleEntrySelected()) { + if (dbWidget->numberOfSelectedEntries() != 1) { return; } QList actions = m_ui->menuEntryCopyAttribute->actions(); - for (int i = EntryAttributes::DefaultAttributes.size() + 1; i < actions.size(); i++) { + for (int i = m_countDefaultAttributes; i < actions.size(); i++) { delete actions[i]; } - Q_FOREACH (const QString& key, entry->attributes()->customKeys()) { + Q_FOREACH (const QString& key, dbWidget->customEntryAttributes()) { QAction* action = m_ui->menuEntryCopyAttribute->addAction(key); m_copyAdditionalAttributeActions->addAction(action); } @@ -320,40 +255,11 @@ void MainWindow::clearLastDatabases() config()->set("LastDatabases", QVariant()); } -void MainWindow::changeEvent(QEvent *e) -{ - QMainWindow::changeEvent(e); - if (e->type() == QEvent::ActivationChange) { - if (isActiveWindow()) - m_ui->tabWidget->checkReloadDatabases(); - } -} - void MainWindow::openDatabase(const QString& fileName, const QString& pw, const QString& keyFile) { m_ui->tabWidget->openDatabase(fileName, pw, keyFile); } -void MainWindow::updateSearchField(DatabaseWidget* dbWidget) -{ - bool enabled = dbWidget != NULL; - - m_ui->actionFindCaseSensitive->setChecked(enabled && dbWidget->caseSensitiveSearch()); - - m_ui->actionFindCurrentGroup->setEnabled(enabled && dbWidget->canChooseSearchScope()); - m_ui->actionFindRootGroup->setEnabled(enabled && dbWidget->canChooseSearchScope()); - if (enabled && dbWidget->isAllGroupsSearch()) - m_ui->actionFindRootGroup->setChecked(true); - else - m_ui->actionFindCurrentGroup->setChecked(true); - - m_ui->searchField->setEnabled(enabled); - if (enabled && dbWidget->isInSearchMode()) - m_ui->searchField->setText(dbWidget->searchText()); - else - m_ui->searchField->clear(); -} - void MainWindow::setMenuActionState(DatabaseWidget::Mode mode) { bool inDatabaseTabWidget = (m_ui->stackedWidget->currentIndex() == 0); @@ -370,9 +276,9 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode) switch (mode) { case DatabaseWidget::ViewMode: { bool inSearch = dbWidget->isInSearchMode(); - bool singleEntrySelected = dbWidget->entryView()->isSingleEntrySelected(); - bool entriesSelected = !dbWidget->entryView()->selectionModel()->selectedRows().isEmpty(); - bool groupSelected = dbWidget->groupView()->currentGroup(); + bool singleEntrySelected = dbWidget->numberOfSelectedEntries() == 1; + bool entriesSelected = dbWidget->numberOfSelectedEntries() > 0; + bool groupSelected = dbWidget->isGroupSelected(); m_ui->actionEntryNew->setEnabled(!inSearch); m_ui->actionEntryClone->setEnabled(singleEntrySelected && !inSearch); @@ -388,8 +294,9 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode) m_ui->actionEntryOpenUrl->setEnabled(singleEntrySelected); m_ui->actionGroupNew->setEnabled(groupSelected); m_ui->actionGroupEdit->setEnabled(groupSelected); - m_ui->actionGroupDelete->setEnabled(groupSelected && dbWidget->canDeleteCurrentGoup()); - updateSearchField(dbWidget); + m_ui->actionGroupDelete->setEnabled(groupSelected && dbWidget->canDeleteCurrentGroup()); + // TODO: get checked state from db widget + m_ui->actionSearch->setEnabled(true); m_ui->actionChangeMasterKey->setEnabled(true); m_ui->actionChangeDatabaseSettings->setEnabled(true); m_ui->actionDatabaseSave->setEnabled(true); @@ -398,7 +305,6 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode) } case DatabaseWidget::EditMode: case DatabaseWidget::LockedMode: - case DatabaseWidget::OpenMode: Q_FOREACH (QAction* action, m_ui->menuEntries->actions()) { action->setEnabled(false); } @@ -406,9 +312,14 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode) Q_FOREACH (QAction* action, m_ui->menuGroups->actions()) { action->setEnabled(false); } + m_ui->actionEntryCopyTitle->setEnabled(false); + m_ui->actionEntryCopyUsername->setEnabled(false); + m_ui->actionEntryCopyPassword->setEnabled(false); + m_ui->actionEntryCopyURL->setEnabled(false); + m_ui->actionEntryCopyNotes->setEnabled(false); m_ui->menuEntryCopyAttribute->setEnabled(false); - updateSearchField(); + m_ui->actionSearch->setEnabled(false); m_ui->actionChangeMasterKey->setEnabled(false); m_ui->actionChangeDatabaseSettings->setEnabled(false); m_ui->actionDatabaseSave->setEnabled(false); @@ -427,9 +338,14 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode) Q_FOREACH (QAction* action, m_ui->menuGroups->actions()) { action->setEnabled(false); } + m_ui->actionEntryCopyTitle->setEnabled(false); + m_ui->actionEntryCopyUsername->setEnabled(false); + m_ui->actionEntryCopyPassword->setEnabled(false); + m_ui->actionEntryCopyURL->setEnabled(false); + m_ui->actionEntryCopyNotes->setEnabled(false); m_ui->menuEntryCopyAttribute->setEnabled(false); - updateSearchField(); + m_ui->actionSearch->setEnabled(false); m_ui->actionChangeMasterKey->setEnabled(false); m_ui->actionChangeDatabaseSettings->setEnabled(false); m_ui->actionDatabaseSave->setEnabled(false); @@ -513,15 +429,29 @@ void MainWindow::closeEvent(QCloseEvent* event) saveWindowInformation(); event->accept(); + QApplication::quit(); } else { event->ignore(); } } +void MainWindow::changeEvent(QEvent *event) +{ + if ((event->type() == QEvent::WindowStateChange) && isMinimized() + && isTrayIconEnabled() && config()->get("GUI/MinimizeToTray").toBool()) + { + event->ignore(); + hide(); + } + else { + QMainWindow::changeEvent(event); + } +} + void MainWindow::saveWindowInformation() { - config()->set("window/Geometry", saveGeometry()); + config()->set("GUI/MainWindowGeometry", saveGeometry()); } bool MainWindow::saveLastDatabases() @@ -551,6 +481,35 @@ bool MainWindow::saveLastDatabases() return accept; } +void MainWindow::updateTrayIcon() +{ + if (isTrayIconEnabled()) { + if (!m_trayIcon) { + m_trayIcon = new QSystemTrayIcon(filePath()->applicationIcon(), this); + + QMenu* menu = new QMenu(this); + + QAction* actionToggle = new QAction(tr("Toggle window"), menu); + menu->addAction(actionToggle); + + menu->addAction(m_ui->actionQuit); + + connect(m_trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), + SLOT(trayIconTriggered(QSystemTrayIcon::ActivationReason))); + connect(actionToggle, SIGNAL(triggered()), SLOT(toggleWindow())); + + m_trayIcon->setContextMenu(menu); + m_trayIcon->show(); + } + } + else { + if (m_trayIcon) { + delete m_trayIcon; + m_trayIcon = Q_NULLPTR; + } + } +} + void MainWindow::showEntryContextMenu(const QPoint& globalPos) { m_ui->menuEntries->popup(globalPos); @@ -566,30 +525,6 @@ void MainWindow::saveToolbarState(bool value) config()->set("ShowToolbar", value); } -void MainWindow::setToolbarIconSize(int size) -{ - config()->set("ToolbarIconSize", size); - m_ui->toolBar->setIconSize(QSize(size, size)); - m_ui->actionToolbarIconSize16->setChecked(size == 16); - m_ui->actionToolbarIconSize22->setChecked(size == 22); - m_ui->actionToolbarIconSize28->setChecked(size == 28); -} - -void MainWindow::setToolbarIconSize16() -{ - setToolbarIconSize(16); -} - -void MainWindow::setToolbarIconSize22() -{ - setToolbarIconSize(22); -} - -void MainWindow::setToolbarIconSize28() -{ - setToolbarIconSize(28); -} - void MainWindow::setShortcut(QAction* action, QKeySequence::StandardKey standard, int fallback) { if (!QKeySequence::keyBindings(standard).isEmpty()) { @@ -619,4 +554,31 @@ void MainWindow::applySettingsChanges() else { m_inactivityTimer->deactivate(); } + + updateTrayIcon(); +} + +void MainWindow::trayIconTriggered(QSystemTrayIcon::ActivationReason reason) +{ + if (reason == QSystemTrayIcon::Trigger) { + toggleWindow(); + } +} + +void MainWindow::toggleWindow() +{ + if (QApplication::activeWindow() == this) { + hide(); + } + else { + show(); + raise(); + activateWindow(); + } +} + +bool MainWindow::isTrayIconEnabled() const +{ + return config()->get("GUI/ShowTrayIcon").toBool() + && QSystemTrayIcon::isSystemTrayAvailable(); } diff --git a/src/gui/MainWindow.h b/src/gui/MainWindow.h index 23b8cc48..b966703d 100644 --- a/src/gui/MainWindow.h +++ b/src/gui/MainWindow.h @@ -20,6 +20,7 @@ #include #include +#include #include "core/SignalMultiplexer.h" #include "gui/DatabaseWidget.h" @@ -43,8 +44,8 @@ public Q_SLOTS: const QString& keyFile = QString()); protected: - void changeEvent(QEvent *e); - void closeEvent(QCloseEvent* event) Q_DECL_OVERRIDE; + void closeEvent(QCloseEvent* event) Q_DECL_OVERRIDE; + void changeEvent(QEvent* event) Q_DECL_OVERRIDE; private Q_SLOTS: void setMenuActionState(DatabaseWidget::Mode mode = DatabaseWidget::None); @@ -62,19 +63,18 @@ private Q_SLOTS: void saveToolbarState(bool value); void rememberOpenDatabases(const QString& filePath); void applySettingsChanges(); - void setToolbarIconSize(int size); - void setToolbarIconSize16(); - void setToolbarIconSize22(); - void setToolbarIconSize28(); + void trayIconTriggered(QSystemTrayIcon::ActivationReason reason); + void toggleWindow(); private: - void updateSearchField(DatabaseWidget* dbWidget = NULL); static void setShortcut(QAction* action, QKeySequence::StandardKey standard, int fallback = 0); static const QString BaseWindowTitle; void saveWindowInformation(); bool saveLastDatabases(); + void updateTrayIcon(); + bool isTrayIconEnabled() const; const QScopedPointer m_ui; SignalMultiplexer m_actionMultiplexer; @@ -83,6 +83,8 @@ private: QActionGroup* m_copyAdditionalAttributeActions; QStringList m_openDatabases; InactivityTimer* m_inactivityTimer; + int m_countDefaultAttributes; + QSystemTrayIcon* m_trayIcon; Q_DISABLE_COPY(MainWindow) }; diff --git a/src/gui/MainWindow.ui b/src/gui/MainWindow.ui index 7585a754..09e1c412 100644 --- a/src/gui/MainWindow.ui +++ b/src/gui/MainWindow.ui @@ -70,7 +70,7 @@ 0 0 800 - 24 + 20 @@ -148,15 +148,6 @@ View - - - Toolbar &Icon Size - - - - - - @@ -186,17 +177,7 @@ - - - - 0 - 0 - - - - - - + @@ -322,6 +303,14 @@ Clone entry + + + false + + + Find + + false @@ -397,54 +386,6 @@ Notes - - - true - - - &16x16 - - - - - true - - - &22x22 - - - - - true - - - 2&8x28 - - - - - true - - - Case Sensitive - - - - - true - - - Current Group - - - - - true - - - Root Group - - @@ -465,12 +406,6 @@
gui/WelcomeWidget.h
1
- - QSearchField - QWidget -
gui/qocoa/qsearchfield.h
- 1 -
diff --git a/src/gui/PasswordComboBox.cpp b/src/gui/PasswordComboBox.cpp index af8e9949..f11311a9 100644 --- a/src/gui/PasswordComboBox.cpp +++ b/src/gui/PasswordComboBox.cpp @@ -45,7 +45,13 @@ void PasswordComboBox::setEcho(bool echo) // add fake item to show visual indication that a popup is available addItem(""); - setStyleSheet("QComboBox { font-family: monospace; }"); +#ifdef Q_OS_MAC + // Qt on Mac OS doesn't seem to know the generic monospace family (tested with 4.8.6) + setStyleSheet("QComboBox { font-family: monospace,Menlo,Monaco; }"); +#else + setStyleSheet("QComboBox { font-family: monospace,Courier; }"); +#endif + } else { // clear items so the combobox indicates that no popup menu is available diff --git a/src/gui/PasswordEdit.cpp b/src/gui/PasswordEdit.cpp index 85326961..b68eef68 100644 --- a/src/gui/PasswordEdit.cpp +++ b/src/gui/PasswordEdit.cpp @@ -56,7 +56,12 @@ void PasswordEdit::updateStylesheet() QString stylesheet("QLineEdit { "); if (echoMode() == QLineEdit::Normal) { +#ifdef Q_OS_MAC + // Qt on Mac OS doesn't seem to know the generic monospace family (tested with 4.8.6) + stylesheet.append("font-family: monospace,Menlo,Monaco; "); +#else stylesheet.append("font-family: monospace; "); +#endif } if (m_basePasswordEdit && !passwordsEqual()) { diff --git a/src/gui/PasswordGeneratorWidget.ui b/src/gui/PasswordGeneratorWidget.ui index 342f191a..5c75ef9e 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 9f334807..ce4845dc 100644 --- a/src/gui/SearchWidget.ui +++ b/src/gui/SearchWidget.ui @@ -7,31 +7,119 @@ 0 0 630 - 16 + 87 - + 0 - + + 0 + + + 0 + + + 0 + + + + + + + Qt::ClickFocus + true - + + + Find: + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Case sensitive + + + + + + + Current group + + + false + + + + + + + Root group + + + true + + + + + + + Qt::Horizontal + + + + 255 + 1 + + + + + + + + + + LineEdit + QLineEdit +
gui/LineEdit.h
+
+
closeSearchButton + searchEdit + caseSensitiveCheckBox + searchCurrentRadioButton + searchRootRadioButton diff --git a/src/gui/SettingsWidget.cpp b/src/gui/SettingsWidget.cpp index d3cc0477..33789ae7 100644 --- a/src/gui/SettingsWidget.cpp +++ b/src/gui/SettingsWidget.cpp @@ -21,27 +21,7 @@ #include "autotype/AutoType.h" #include "core/Config.h" - -class SettingsWidget::ExtraPage -{ -public: - ExtraPage(ISettingsPage* page, QWidget* widget): settingsPage(page), widget(widget) - {} - - void loadSettings() const - { - settingsPage->loadSettings(widget); - } - - void saveSettings() const - { - settingsPage->saveSettings(widget); - } - -private: - QSharedPointer settingsPage; - QWidget* widget; -}; +#include "core/Translator.h" SettingsWidget::SettingsWidget(QWidget* parent) : EditWidget(parent) @@ -67,6 +47,8 @@ SettingsWidget::SettingsWidget(QWidget* parent) connect(m_generalUi->autoSaveAfterEveryChangeCheckBox, SIGNAL(toggled(bool)), this, SLOT(enableAutoSaveOnExit(bool))); + connect(m_generalUi->systrayShowCheckBox, SIGNAL(toggled(bool)), + m_generalUi->systrayMinimizeToTrayCheckBox, SLOT(setEnabled(bool))); connect(m_secUi->clearClipboardCheckBox, SIGNAL(toggled(bool)), m_secUi->clearClipboardSpinBox, SLOT(setEnabled(bool))); @@ -78,25 +60,29 @@ SettingsWidget::~SettingsWidget() { } -void SettingsWidget::addSettingsPage(ISettingsPage *page) -{ - QWidget * widget = page->createWidget(); - widget->setParent(this); - m_extraPages.append(ExtraPage(page, widget)); - add(page->name(), widget); -} - void SettingsWidget::loadSettings() { m_generalUi->rememberLastDatabasesCheckBox->setChecked(config()->get("RememberLastDatabases").toBool()); m_generalUi->openPreviousDatabasesOnStartupCheckBox->setChecked( config()->get("OpenPreviousDatabasesOnStartup").toBool()); - m_generalUi->modifiedExpandedChangedCheckBox->setChecked(config()->get("ModifiedOnExpandedStateChanges").toBool()); m_generalUi->autoSaveAfterEveryChangeCheckBox->setChecked(config()->get("AutoSaveAfterEveryChange").toBool()); m_generalUi->autoSaveOnExitCheckBox->setChecked(config()->get("AutoSaveOnExit").toBool()); m_generalUi->minimizeOnCopyCheckBox->setChecked(config()->get("MinimizeOnCopy").toBool()); m_generalUi->useGroupIconOnEntryCreationCheckBox->setChecked(config()->get("UseGroupIconOnEntryCreation").toBool()); - m_generalUi->reloadBehavior->setCurrentIndex(config()->get("ReloadBehavior").toInt()); + m_generalUi->autoTypeEntryTitleMatchCheckBox->setChecked(config()->get("AutoTypeEntryTitleMatch").toBool()); + + m_generalUi->languageComboBox->clear(); + QList > languages = Translator::availableLanguages(); + for (int i = 0; i < languages.size(); i++) { + m_generalUi->languageComboBox->addItem(languages[i].second, languages[i].first); + } + int defaultIndex = m_generalUi->languageComboBox->findData(config()->get("GUI/Language")); + if (defaultIndex > 0) { + m_generalUi->languageComboBox->setCurrentIndex(defaultIndex); + } + + m_generalUi->systrayShowCheckBox->setChecked(config()->get("GUI/ShowTrayIcon").toBool()); + m_generalUi->systrayMinimizeToTrayCheckBox->setChecked(config()->get("GUI/MinimizeToTray").toBool()); if (autoType()->isAvailable()) { m_globalAutoTypeKey = static_cast(config()->get("GlobalAutoTypeKey").toInt()); @@ -115,8 +101,7 @@ void SettingsWidget::loadSettings() m_secUi->passwordCleartextCheckBox->setChecked(config()->get("security/passwordscleartext").toBool()); m_secUi->autoTypeAskCheckBox->setChecked(config()->get("security/autotypeask").toBool()); - Q_FOREACH (const ExtraPage& page, m_extraPages) - page.loadSettings(); + setCurrentRow(0); } @@ -125,15 +110,20 @@ void SettingsWidget::saveSettings() config()->set("RememberLastDatabases", m_generalUi->rememberLastDatabasesCheckBox->isChecked()); config()->set("OpenPreviousDatabasesOnStartup", m_generalUi->openPreviousDatabasesOnStartupCheckBox->isChecked()); - config()->set("ModifiedOnExpandedStateChanges", - m_generalUi->modifiedExpandedChangedCheckBox->isChecked()); config()->set("AutoSaveAfterEveryChange", m_generalUi->autoSaveAfterEveryChangeCheckBox->isChecked()); config()->set("AutoSaveOnExit", m_generalUi->autoSaveOnExitCheckBox->isChecked()); config()->set("MinimizeOnCopy", m_generalUi->minimizeOnCopyCheckBox->isChecked()); config()->set("UseGroupIconOnEntryCreation", m_generalUi->useGroupIconOnEntryCreationCheckBox->isChecked()); - config()->set("ReloadBehavior", m_generalUi->reloadBehavior->currentIndex()); + config()->set("AutoTypeEntryTitleMatch", + m_generalUi->autoTypeEntryTitleMatchCheckBox->isChecked()); + int currentLangIndex = m_generalUi->languageComboBox->currentIndex(); + config()->set("GUI/Language", m_generalUi->languageComboBox->itemData(currentLangIndex).toString()); + + config()->set("GUI/ShowTrayIcon", m_generalUi->systrayShowCheckBox->isChecked()); + config()->set("GUI/MinimizeToTray", m_generalUi->systrayMinimizeToTrayCheckBox->isChecked()); + if (autoType()->isAvailable()) { config()->set("GlobalAutoTypeKey", m_generalUi->autoTypeShortcutWidget->key()); config()->set("GlobalAutoTypeModifiers", @@ -148,8 +138,6 @@ void SettingsWidget::saveSettings() config()->set("security/passwordscleartext", m_secUi->passwordCleartextCheckBox->isChecked()); config()->set("security/autotypeask", m_secUi->autoTypeAskCheckBox->isChecked()); - Q_FOREACH (const ExtraPage& page, m_extraPages) - page.saveSettings(); Q_EMIT editFinished(true); } diff --git a/src/gui/SettingsWidget.h b/src/gui/SettingsWidget.h index a53fd30b..58983ebb 100644 --- a/src/gui/SettingsWidget.h +++ b/src/gui/SettingsWidget.h @@ -59,8 +59,6 @@ private: const QScopedPointer m_generalUi; Qt::Key m_globalAutoTypeKey; Qt::KeyboardModifiers m_globalAutoTypeModifiers; - class ExtraPage; - QList m_extraPages; }; #endif // KEEPASSX_SETTINGSWIDGET_H diff --git a/src/gui/SettingsWidgetGeneral.ui b/src/gui/SettingsWidgetGeneral.ui index a86ee8b4..04c603a3 100644 --- a/src/gui/SettingsWidgetGeneral.ui +++ b/src/gui/SettingsWidgetGeneral.ui @@ -6,55 +6,24 @@ 0 0 - 481 - 185 + 456 + 313 + + QFormLayout::AllNonFixedFieldsGrow + - Remember recent databases + Remember last databases true - - - - Mark as modified on expanded state changes - - - true - - - - - - - Automatically save after every change - - - - - - - Automatically save on exit - - - - - - - Global Auto-Type shortcut - - - - - - @@ -62,46 +31,78 @@ - - + + - When database files are externally modified + Automatically save on exit - - - - - Always ask - - - - - Reload unmodified databases - - - - - Ignore modifications - - + + + + 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 + + + + + + + Language + + + + + + + + + + Show a system tray icon + + + + + + + false + + + Hide window to system tray when minimized + + + @@ -114,7 +115,6 @@ rememberLastDatabasesCheckBox openPreviousDatabasesOnStartupCheckBox - modifiedExpandedChangedCheckBox autoSaveOnExitCheckBox autoSaveAfterEveryChangeCheckBox minimizeOnCopyCheckBox diff --git a/src/gui/entry/EditEntryWidget.cpp b/src/gui/entry/EditEntryWidget.cpp index 8d711db3..37d19b57 100644 --- a/src/gui/entry/EditEntryWidget.cpp +++ b/src/gui/entry/EditEntryWidget.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include "core/Config.h" #include "core/Database.h" @@ -107,7 +108,11 @@ void EditEntryWidget::setupAdvanced() m_attachmentsModel->setEntryAttachments(m_entryAttachments); m_advancedUi->attachmentsView->setModel(m_attachmentsModel); + connect(m_advancedUi->attachmentsView->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), + SLOT(updateAttachmentButtonsEnabled(QModelIndex))); + connect(m_advancedUi->attachmentsView, SIGNAL(doubleClicked(QModelIndex)), SLOT(openAttachment(QModelIndex))); connect(m_advancedUi->saveAttachmentButton, SIGNAL(clicked()), SLOT(saveCurrentAttachment())); + connect(m_advancedUi->openAttachmentButton, SIGNAL(clicked()), SLOT(openCurrentAttachment())); connect(m_advancedUi->addAttachmentButton, SIGNAL(clicked()), SLOT(insertAttachment())); connect(m_advancedUi->removeAttachmentButton, SIGNAL(clicked()), SLOT(removeCurrentAttachment())); @@ -232,6 +237,15 @@ void EditEntryWidget::useExpiryPreset(QAction* action) m_mainUi->expireDatePicker->setDateTime(expiryDateTime); } +void EditEntryWidget::updateAttachmentButtonsEnabled(const QModelIndex& current) +{ + bool enable = current.isValid(); + + m_advancedUi->saveAttachmentButton->setEnabled(enable); + m_advancedUi->openAttachmentButton->setEnabled(enable); + m_advancedUi->removeAttachmentButton->setEnabled(enable && !m_history); +} + QString EditEntryWidget::entryTitle() const { if (m_entry) { @@ -281,7 +295,7 @@ void EditEntryWidget::setForms(const Entry* entry, bool restore) m_mainUi->tooglePasswordGeneratorButton->setChecked(false); m_mainUi->passwordGenerator->reset(); m_advancedUi->addAttachmentButton->setEnabled(!m_history); - m_advancedUi->removeAttachmentButton->setEnabled(!m_history); + updateAttachmentButtonsEnabled(m_advancedUi->attachmentsView->currentIndex()); m_advancedUi->addAttributeButton->setEnabled(!m_history); m_advancedUi->editAttributeButton->setEnabled(false); m_advancedUi->removeAttributeButton->setEnabled(false); @@ -591,14 +605,14 @@ void EditEntryWidget::insertAttachment() QFile file(filename); if (!file.open(QIODevice::ReadOnly)) { MessageBox::warning(this, tr("Error"), - tr("Unable to open file:\n").append(file.errorString())); + tr("Unable to open file").append(":\n").append(file.errorString())); return; } QByteArray data; if (!Tools::readAllFromDevice(&file, data)) { MessageBox::warning(this, tr("Error"), - tr("Unable to open file:\n").append(file.errorString())); + tr("Unable to open file").append(":\n").append(file.errorString())); return; } @@ -637,6 +651,42 @@ void EditEntryWidget::saveCurrentAttachment() } } +void EditEntryWidget::openAttachment(const QModelIndex& index) +{ + if (!index.isValid()) { + Q_ASSERT(false); + return; + } + + QString filename = m_attachmentsModel->keyByIndex(index); + QByteArray attachmentData = m_entryAttachments->value(filename); + + // tmp file will be removed once the database (or the application) has been closed + QString tmpFileTemplate = QDir::temp().absoluteFilePath(QString("XXXXXX.").append(filename)); + QTemporaryFile* file = new QTemporaryFile(tmpFileTemplate, this); + + if (!file->open()) { + MessageBox::warning(this, tr("Error"), + tr("Unable to save the attachment:\n").append(file->errorString())); + return; + } + + if (file->write(attachmentData) != attachmentData.size()) { + MessageBox::warning(this, tr("Error"), + tr("Unable to save the attachment:\n").append(file->errorString())); + return; + } + + QDesktopServices::openUrl(QUrl::fromLocalFile(file->fileName())); +} + +void EditEntryWidget::openCurrentAttachment() +{ + QModelIndex index = m_advancedUi->attachmentsView->currentIndex(); + + openAttachment(index); +} + void EditEntryWidget::removeCurrentAttachment() { Q_ASSERT(!m_history); @@ -784,13 +834,13 @@ QMenu* EditEntryWidget::createPresetsMenu() QMenu* expirePresetsMenu = new QMenu(this); expirePresetsMenu->addAction(tr("Tomorrow"))->setData(QVariant::fromValue(TimeDelta::fromDays(1))); expirePresetsMenu->addSeparator(); - expirePresetsMenu->addAction(tr("1 week"))->setData(QVariant::fromValue(TimeDelta::fromDays(7))); - expirePresetsMenu->addAction(tr("2 weeks"))->setData(QVariant::fromValue(TimeDelta::fromDays(14))); - expirePresetsMenu->addAction(tr("3 weeks"))->setData(QVariant::fromValue(TimeDelta::fromDays(21))); + expirePresetsMenu->addAction(tr("%n week(s)", 0, 1))->setData(QVariant::fromValue(TimeDelta::fromDays(7))); + expirePresetsMenu->addAction(tr("%n week(s)", 0, 2))->setData(QVariant::fromValue(TimeDelta::fromDays(14))); + expirePresetsMenu->addAction(tr("%n week(s)", 0, 3))->setData(QVariant::fromValue(TimeDelta::fromDays(21))); expirePresetsMenu->addSeparator(); - expirePresetsMenu->addAction(tr("1 month"))->setData(QVariant::fromValue(TimeDelta::fromMonths(1))); - expirePresetsMenu->addAction(tr("3 months"))->setData(QVariant::fromValue(TimeDelta::fromMonths(3))); - expirePresetsMenu->addAction(tr("6 months"))->setData(QVariant::fromValue(TimeDelta::fromMonths(6))); + expirePresetsMenu->addAction(tr("%n month(s)", 0, 1))->setData(QVariant::fromValue(TimeDelta::fromMonths(1))); + expirePresetsMenu->addAction(tr("%n month(s)", 0, 3))->setData(QVariant::fromValue(TimeDelta::fromMonths(3))); + expirePresetsMenu->addAction(tr("%n month(s)", 0, 6))->setData(QVariant::fromValue(TimeDelta::fromMonths(6))); expirePresetsMenu->addSeparator(); expirePresetsMenu->addAction(tr("1 year"))->setData(QVariant::fromValue(TimeDelta::fromYears(1))); return expirePresetsMenu; diff --git a/src/gui/entry/EditEntryWidget.h b/src/gui/entry/EditEntryWidget.h index 4e3d82ef..14434762 100644 --- a/src/gui/entry/EditEntryWidget.h +++ b/src/gui/entry/EditEntryWidget.h @@ -76,6 +76,8 @@ private Q_SLOTS: void updateCurrentAttribute(); void insertAttachment(); void saveCurrentAttachment(); + void openAttachment(const QModelIndex& index); + void openCurrentAttachment(); void removeCurrentAttachment(); void updateAutoTypeEnabled(); void insertAutoTypeAssoc(); @@ -91,6 +93,7 @@ private Q_SLOTS: void histEntryActivated(const QModelIndex& index); void updateHistoryButtons(const QModelIndex& current, const QModelIndex& previous); void useExpiryPreset(QAction* action); + void updateAttachmentButtonsEnabled(const QModelIndex& current); private: void setupMain(); diff --git a/src/gui/entry/EditEntryWidgetAdvanced.ui b/src/gui/entry/EditEntryWidgetAdvanced.ui index 7cf0eb4d..589bd320 100644 --- a/src/gui/entry/EditEntryWidgetAdvanced.ui +++ b/src/gui/entry/EditEntryWidgetAdvanced.ui @@ -7,7 +7,7 @@ 0 0 400 - 299 + 315 @@ -119,13 +119,29 @@ + + false + Remove + + + + false + + + Open + + + + + false + Save diff --git a/src/gui/entry/EntryView.cpp b/src/gui/entry/EntryView.cpp index f71f80bc..cd2c6fba 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 b5f056aa..c11d0417 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/http/Service.cpp b/src/http/Service.cpp index 525b2877..1c054760 100644 --- a/src/http/Service.cpp +++ b/src/http/Service.cpp @@ -25,6 +25,7 @@ #include "core/Database.h" #include "core/Entry.h" #include "core/Group.h" +#include "core/EntrySearcher.h" #include "core/Metadata.h" #include "core/Uuid.h" #include "core/PasswordGenerator.h" @@ -190,7 +191,7 @@ QList Service::searchEntries(Database* db, const QString& hostname) { QList entries; if (Group* rootGroup = db->rootGroup()) - Q_FOREACH (Entry* entry, rootGroup->search(hostname, Qt::CaseInsensitive)) { + Q_FOREACH (Entry* entry, EntrySearcher().search(hostname, rootGroup, Qt::CaseInsensitive)) { QString title = entry->title(); QString url = entry->url(); diff --git a/src/main.cpp b/src/main.cpp index abe7ceb4..2bdef5bd 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -21,9 +21,11 @@ #include "core/Config.h" #include "core/qcommandlineparser.h" #include "core/Tools.h" +#include "core/Translator.h" #include "crypto/Crypto.h" #include "gui/Application.h" #include "gui/MainWindow.h" +#include "gui/MessageBox.h" int main(int argc, char** argv) { @@ -37,7 +39,16 @@ int main(int argc, char** argv) // don't set organizationName as that changes the return value of // QDesktopServices::storageLocation(QDesktopServices::DataLocation) - Crypto::init(); + QApplication::setQuitOnLastWindowClosed(false); + + if (!Crypto::init()) { + QString error = QCoreApplication::translate("Main", + "Fatal error while testing the cryptographic functions."); + error.append("\n"); + error.append(Crypto::errorString()); + MessageBox::critical(Q_NULLPTR, QCoreApplication::translate("Main", "KeePassX - Error"), error); + return 1; + } QCommandLineParser parser; parser.setApplicationDescription(QCoreApplication::translate("main", "KeePassX - cross-platform password manager")); @@ -66,6 +77,8 @@ int main(int argc, char** argv) Config::createConfigFromFile(parser.value(configOption)); } + Translator::installTranslator(); + #ifdef Q_OS_MAC // Don't show menu icons on OSX QApplication::setAttribute(Qt::AA_DontShowIconsInMenus); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 8df0050a..c094f823 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 47ac0909..818f57c9 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(); - + QVERIFY(Crypto::init()); + Config::createTempFileInstance(); AutoType::createTestInstance(); + config()->set("AutoTypeEntryTitleMatch", false); + config()->set("security/autotypeask", false); QPluginLoader loader(filePath()->pluginPath("keepassx-autotype-test")); loader.setLoadHints(QLibrary::ResolveAllSymbolsHint); @@ -54,12 +59,24 @@ void TestAutoType::init() m_test->clearActions(); m_db = new Database(); + m_dbList.clear(); + m_dbList.append(m_db); m_group = new Group(); m_db->setRootGroup(m_group); - m_entry = new Entry(); - m_entry->setGroup(m_group); - m_entry->setUsername("myuser"); - m_entry->setPassword("mypass"); + + m_entry1 = new Entry(); + m_entry1->setGroup(m_group); + m_entry1->setUsername("myuser"); + m_entry1->setPassword("mypass"); + AutoTypeAssociations::Association association; + association.window = "custom window"; + association.sequence = "{username}association{password}"; + m_entry1->autoTypeAssociations()->add(association); + + m_entry2 = new Entry(); + m_entry2->setGroup(m_group); + m_entry2->setPassword("myuser"); + m_entry2->setTitle("entry title"); } void TestAutoType::cleanup() @@ -77,7 +94,7 @@ void TestAutoType::testInternal() void TestAutoType::testAutoTypeWithoutSequence() { - m_autoType->performAutoType(m_entry, Q_NULLPTR); + m_autoType->performAutoType(m_entry1, Q_NULLPTR); QCOMPARE(m_test->actionCount(), 14); QCOMPARE(m_test->actionChars(), @@ -88,42 +105,54 @@ void TestAutoType::testAutoTypeWithoutSequence() void TestAutoType::testAutoTypeWithSequence() { - m_autoType->performAutoType(m_entry, Q_NULLPTR, "{Username}abc{PaSsWoRd}"); + m_autoType->performAutoType(m_entry1, Q_NULLPTR, "{Username}abc{PaSsWoRd}"); QCOMPARE(m_test->actionCount(), 15); QCOMPARE(m_test->actionChars(), QString("%1abc%2") - .arg(m_entry->username()) - .arg(m_entry->password())); + .arg(m_entry1->username()) + .arg(m_entry1->password())); } void TestAutoType::testGlobalAutoTypeWithNoMatch() { - QList 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 fba7fde1..d46a5596 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 4f258a17..eb26ca83 100644 --- a/tests/TestCryptoHash.cpp +++ b/tests/TestCryptoHash.cpp @@ -23,15 +23,17 @@ #include "crypto/Crypto.h" #include "crypto/CryptoHash.h" +QTEST_GUILESS_MAIN(TestCryptoHash) + void TestCryptoHash::initTestCase() { - Crypto::init(); + QVERIFY(Crypto::init()); } void TestCryptoHash::test() { // TODO: move somewhere else - QVERIFY(Crypto::selfTest()); + QVERIFY(Crypto::backendSelfTest()); CryptoHash cryptoHash1(CryptoHash::Sha256); QCOMPARE(cryptoHash1.result(), @@ -47,5 +49,3 @@ void TestCryptoHash::test() QCOMPARE(cryptoHash3.result(), QByteArray::fromHex("0b56e5f65263e747af4a833bd7dd7ad26a64d7a4de7c68e52364893dca0766b4")); } - -QTEST_GUILESS_MAIN(TestCryptoHash) diff --git a/tests/TestDeletedObjects.cpp b/tests/TestDeletedObjects.cpp index 914096ce..cf9e3d10 100644 --- a/tests/TestDeletedObjects.cpp +++ b/tests/TestDeletedObjects.cpp @@ -26,9 +26,11 @@ #include "format/KeePass2XmlReader.h" #include "config-keepassx-tests.h" +QTEST_GUILESS_MAIN(TestDeletedObjects) + void TestDeletedObjects::initTestCase() { - Crypto::init(); + QVERIFY(Crypto::init()); } void TestDeletedObjects::createAndDelete(Database* db, int delObjectsSize) @@ -88,6 +90,7 @@ void TestDeletedObjects::createAndDelete(Database* db, int delObjectsSize) void TestDeletedObjects::testDeletedObjectsFromFile() { KeePass2XmlReader reader; + reader.setStrictMode(true); QString xmlFile = QString(KEEPASSX_TEST_DATA_DIR).append("/NewDatabase.xml"); Database* db = reader.readDatabase(xmlFile); @@ -158,5 +161,3 @@ void TestDeletedObjects::testDatabaseChange() delete db; delete db2; } - -QTEST_GUILESS_MAIN(TestDeletedObjects) diff --git a/tests/TestEntry.cpp b/tests/TestEntry.cpp index 15f398f1..477e83b8 100644 --- a/tests/TestEntry.cpp +++ b/tests/TestEntry.cpp @@ -23,9 +23,11 @@ #include "core/Entry.h" #include "crypto/Crypto.h" +QTEST_GUILESS_MAIN(TestEntry) + void TestEntry::initTestCase() { - Crypto::init(); + QVERIFY(Crypto::init()); } void TestEntry::testHistoryItemDeletion() @@ -121,5 +123,3 @@ void TestEntry::testClone() QCOMPARE(entryCloneHistory->historyItems().first()->title(), QString("Original Title")); QCOMPARE(entryCloneHistory->timeInfo().creationTime(), entryOrg->timeInfo().creationTime()); } - -QTEST_GUILESS_MAIN(TestEntry) diff --git a/tests/TestEntryModel.cpp b/tests/TestEntryModel.cpp index fab63db6..7ba886bc 100644 --- a/tests/TestEntryModel.cpp +++ b/tests/TestEntryModel.cpp @@ -33,10 +33,12 @@ #include "gui/entry/EntryAttachmentsModel.h" #include "gui/entry/EntryAttributesModel.h" +QTEST_GUILESS_MAIN(TestEntryModel) + void TestEntryModel::initTestCase() { qRegisterMetaType("QModelIndex"); - Crypto::init(); + QVERIFY(Crypto::init()); } void TestEntryModel::test() @@ -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 00000000..9f7ca139 --- /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 00000000..7c45451d --- /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 00000000..d703e02f --- /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() +{ + QVERIFY(Crypto::init()); +} + +void TestExporter::testToDbExporter() +{ + QImage iconImage(1, 1, QImage::Format_RGB32); + iconImage.setPixel(0, 0, qRgb(1, 2, 3)); + Uuid iconUuid = Uuid::random(); + + QImage iconUnusedImage(1, 1, QImage::Format_RGB32); + iconUnusedImage.setPixel(0, 0, qRgb(1, 2, 3)); + Uuid iconUnusedUuid = Uuid::random(); + + Database* dbOrg = new Database(); + Group* groupOrg = new Group(); + groupOrg->setParent(dbOrg->rootGroup()); + groupOrg->setName("GTEST"); + Entry* entryOrg = new Entry(); + entryOrg->setGroup(groupOrg); + entryOrg->setTitle("ETEST"); + dbOrg->metadata()->addCustomIcon(iconUuid, iconImage); + dbOrg->metadata()->addCustomIcon(iconUnusedUuid, iconUnusedImage); + entryOrg->setIcon(iconUuid); + entryOrg->beginUpdate(); + entryOrg->setIcon(Entry::DefaultIconNumber); + entryOrg->endUpdate(); + + Database* dbExp = ToDbExporter().exportGroup(groupOrg); + + QCOMPARE(dbExp->rootGroup()->children().size(), 1); + Group* groupExp = dbExp->rootGroup()->children().first(); + QVERIFY(groupExp != groupOrg); + QCOMPARE(groupExp->name(), groupOrg->name()); + QCOMPARE(groupExp->entries().size(), 1); + + Entry* entryExp = groupExp->entries().first(); + QCOMPARE(entryExp->title(), entryOrg->title()); + QCOMPARE(dbExp->metadata()->customIcons().size(), 1); + QVERIFY(dbExp->metadata()->containsCustomIcon(iconUuid)); + QCOMPARE(entryExp->iconNumber(), entryOrg->iconNumber()); + + QCOMPARE(entryExp->historyItems().size(), 1); + QCOMPARE(entryExp->historyItems().first()->iconUuid(), iconUuid); + + delete dbOrg; + delete dbExp; +} + + + diff --git a/tests/TestExporter.h b/tests/TestExporter.h new file mode 100644 index 00000000..15f9a7c3 --- /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 86b55b70..507cf155 100644 --- a/tests/TestGroup.cpp +++ b/tests/TestGroup.cpp @@ -27,11 +27,13 @@ #include "core/Metadata.h" #include "crypto/Crypto.h" +QTEST_GUILESS_MAIN(TestGroup) + void TestGroup::initTestCase() { qRegisterMetaType("Entry*"); qRegisterMetaType("Group*"); - Crypto::init(); + QVERIFY(Crypto::init()); } void TestGroup::testParenting() @@ -334,102 +336,6 @@ void TestGroup::testCopyCustomIcon() delete dbTarget; } -void TestGroup::testSearch() -{ - Group* groupRoot = new Group(); - Group* group1 = new Group(); - Group* group2 = new Group(); - Group* group3 = new Group(); - - group1->setParent(groupRoot); - group2->setParent(groupRoot); - group3->setParent(groupRoot); - - Group* group11 = new Group(); - - group11->setParent(group1); - - Group* group21 = new Group(); - Group* group211 = new Group(); - Group* group2111 = new Group(); - - group21->setParent(group2); - group211->setParent(group21); - group2111->setParent(group211); - - group1->setSearchingEnabled(Group::Disable); - group11->setSearchingEnabled(Group::Enable); - - Entry* eRoot = new Entry(); - eRoot->setNotes("test search term test"); - eRoot->setGroup(groupRoot); - - Entry* eRoot2 = new Entry(); - eRoot2->setNotes("test term test"); - eRoot2->setGroup(groupRoot); - - Entry* e1 = new Entry(); - e1->setNotes("test search term test"); - e1->setGroup(group1); - - Entry* e2111 = new Entry(); - e2111->setNotes("test search term test"); - e2111->setGroup(group2111); - - Entry* e2111b = new Entry(); - e2111b->setNotes("test search test"); - e2111b->setGroup(group2111); - - Entry* e3 = new Entry(); - e3->setNotes("test search term test"); - e3->setGroup(group3); - - Entry* e3b = new Entry(); - e3b->setNotes("test search test"); - e3b->setGroup(group3); - - QList 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 895c2cc5..c612a3ac 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 a16386c7..32a4b8e9 100644 --- a/tests/TestGroupModel.cpp +++ b/tests/TestGroupModel.cpp @@ -27,10 +27,12 @@ #include "crypto/Crypto.h" #include "gui/group/GroupModel.h" +QTEST_GUILESS_MAIN(TestGroupModel) + void TestGroupModel::initTestCase() { qRegisterMetaType("QModelIndex"); - Crypto::init(); + QVERIFY(Crypto::init()); } void TestGroupModel::test() @@ -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 ab7d386a..09179fef 100644 --- a/tests/TestHashedBlockStream.cpp +++ b/tests/TestHashedBlockStream.cpp @@ -24,9 +24,11 @@ #include "crypto/Crypto.h" #include "streams/HashedBlockStream.h" +QTEST_GUILESS_MAIN(TestHashedBlockStream) + void TestHashedBlockStream::initTestCase() { - Crypto::init(); + QVERIFY(Crypto::init()); } void TestHashedBlockStream::testWriteRead() @@ -69,5 +71,3 @@ void TestHashedBlockStream::testWriteRead() buffer.reset(); buffer.buffer().clear(); } - -QTEST_GUILESS_MAIN(TestHashedBlockStream) diff --git a/tests/TestKeePass1Reader.cpp b/tests/TestKeePass1Reader.cpp index 3ec4e783..249a3657 100644 --- a/tests/TestKeePass1Reader.cpp +++ b/tests/TestKeePass1Reader.cpp @@ -33,9 +33,11 @@ #include "keys/FileKey.h" #include "keys/PasswordKey.h" +QTEST_GUILESS_MAIN(TestKeePass1Reader) + void TestKeePass1Reader::initTestCase() { - Crypto::init(); + QVERIFY(Crypto::init()); QString filename = QString(KEEPASSX_TEST_DATA_DIR).append("/basic.kdb"); @@ -292,5 +294,3 @@ void TestKeePass1Reader::reopenDatabase(Database* db, const QString& password, c QVERIFY(!reader.hasError()); delete newDb; } - -QTEST_GUILESS_MAIN(TestKeePass1Reader) diff --git a/tests/TestKeePass2RandomStream.cpp b/tests/TestKeePass2RandomStream.cpp index 74a15406..7963e9af 100644 --- a/tests/TestKeePass2RandomStream.cpp +++ b/tests/TestKeePass2RandomStream.cpp @@ -26,9 +26,11 @@ #include "format/KeePass2.h" #include "format/KeePass2RandomStream.h" +QTEST_GUILESS_MAIN(TestKeePass2RandomStream) + void TestKeePass2RandomStream::initTestCase() { - Crypto::init(); + QVERIFY(Crypto::init()); } void TestKeePass2RandomStream::test() @@ -77,5 +79,3 @@ void TestKeePass2RandomStream::test() QCOMPARE(cipherData, cipherDataEncrypt); QCOMPARE(randomStreamData, cipherData); } - -QTEST_GUILESS_MAIN(TestKeePass2RandomStream) diff --git a/tests/TestKeePass2Reader.cpp b/tests/TestKeePass2Reader.cpp index 6b1ee1e9..d6cb70c7 100644 --- a/tests/TestKeePass2Reader.cpp +++ b/tests/TestKeePass2Reader.cpp @@ -28,9 +28,11 @@ #include "format/KeePass2Reader.h" #include "keys/PasswordKey.h" +QTEST_GUILESS_MAIN(TestKeePass2Reader) + void TestKeePass2Reader::initTestCase() { - Crypto::init(); + QVERIFY(Crypto::init()); } void TestKeePass2Reader::testNonAscii() @@ -154,5 +156,3 @@ void TestKeePass2Reader::testFormat300() delete db; } - -QTEST_GUILESS_MAIN(TestKeePass2Reader) diff --git a/tests/TestKeePass2Writer.cpp b/tests/TestKeePass2Writer.cpp index bbc4992a..cf4ab1c5 100644 --- a/tests/TestKeePass2Writer.cpp +++ b/tests/TestKeePass2Writer.cpp @@ -29,9 +29,11 @@ #include "format/KeePass2Writer.h" #include "keys/PasswordKey.h" +QTEST_GUILESS_MAIN(TestKeePass2Writer) + void TestKeePass2Writer::initTestCase() { - Crypto::init(); + QVERIFY(Crypto::init()); CompositeKey key; key.addKey(PasswordKey("test")); @@ -104,5 +106,3 @@ void TestKeePass2Writer::cleanupTestCase() delete m_dbOrg; delete m_dbTest; } - -QTEST_GUILESS_MAIN(TestKeePass2Writer) diff --git a/tests/TestKeePass2XmlReader.cpp b/tests/TestKeePass2XmlReader.cpp index ca57db95..e88e990a 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) @@ -66,9 +68,10 @@ QDateTime TestKeePass2XmlReader::genDT(int year, int month, int day, int hour, i void TestKeePass2XmlReader::initTestCase() { - Crypto::init(); + QVERIFY(Crypto::init()); KeePass2XmlReader reader; + reader.setStrictMode(true); QString xmlFile = QString(KEEPASSX_TEST_DATA_DIR).append("/NewDatabase.xml"); m_db = reader.readDatabase(xmlFile); QVERIFY(m_db); @@ -355,28 +358,44 @@ void TestKeePass2XmlReader::testDeletedObjects() void TestKeePass2XmlReader::testBroken() { QFETCH(QString, baseName); + QFETCH(bool, strictMode); + QFETCH(bool, expectError); KeePass2XmlReader reader; + reader.setStrictMode(strictMode); QString xmlFile = QString("%1/%2.xml").arg(KEEPASSX_TEST_DATA_DIR, baseName); QVERIFY(QFile::exists(xmlFile)); QScopedPointer db(reader.readDatabase(xmlFile)); - QVERIFY(reader.hasError()); + if (reader.hasError()) { + qWarning("Reader error: %s", qPrintable(reader.errorString())); + } + QCOMPARE(reader.hasError(), expectError); } void TestKeePass2XmlReader::testBroken_data() { QTest::addColumn("baseName"); + QTest::addColumn("strictMode"); + QTest::addColumn("expectError"); - QTest::newRow("BrokenNoGroupUuid") << "BrokenNoGroupUuid"; - QTest::newRow("BrokenNoEntryUuid") << "BrokenNoEntryUuid"; - QTest::newRow("BrokenNoRootGroup") << "BrokenNoRootGroup"; - QTest::newRow("BrokenTwoRoots") << "BrokenTwoRoots"; - QTest::newRow("BrokenTwoRootGroups") << "BrokenTwoRootGroups"; + // testfile strict? error? + QTest::newRow("BrokenNoGroupUuid (strict)") << "BrokenNoGroupUuid" << true << true; + QTest::newRow("BrokenNoGroupUuid (not strict)") << "BrokenNoGroupUuid" << false << false; + QTest::newRow("BrokenNoEntryUuid (strict)") << "BrokenNoEntryUuid" << true << true; + QTest::newRow("BrokenNoEntryUuid (not strict)") << "BrokenNoEntryUuid" << false << false; + QTest::newRow("BrokenNoRootGroup (strict)") << "BrokenNoRootGroup" << true << true; + QTest::newRow("BrokenNoRootGroup (not strict)") << "BrokenNoRootGroup" << false << true; + QTest::newRow("BrokenTwoRoots (strict)") << "BrokenTwoRoots" << true << true; + QTest::newRow("BrokenTwoRoots (not strict)") << "BrokenTwoRoots" << false << true; + QTest::newRow("BrokenTwoRootGroups (strict)") << "BrokenTwoRootGroups" << true << true; + QTest::newRow("BrokenTwoRootGroups (not strict)") << "BrokenTwoRootGroups" << false << true; + QTest::newRow("BrokenGroupReference (strict)") << "BrokenGroupReference" << true << false; + QTest::newRow("BrokenGroupReference (not strict)") << "BrokenGroupReference" << false << false; + QTest::newRow("BrokenDeletedObjects (strict)") << "BrokenDeletedObjects" << true << true; + QTest::newRow("BrokenDeletedObjects (not strict)") << "BrokenDeletedObjects" << false << false; } void TestKeePass2XmlReader::cleanupTestCase() { delete m_db; } - -QTEST_GUILESS_MAIN(TestKeePass2XmlReader) diff --git a/tests/TestKeys.cpp b/tests/TestKeys.cpp index ec9a35b4..d6758d65 100644 --- a/tests/TestKeys.cpp +++ b/tests/TestKeys.cpp @@ -31,9 +31,11 @@ #include "keys/FileKey.h" #include "keys/PasswordKey.h" +QTEST_GUILESS_MAIN(TestKeys) + void TestKeys::initTestCase() { - Crypto::init(); + QVERIFY(Crypto::init()); } void TestKeys::testComposite() @@ -184,5 +186,3 @@ void TestKeys::benchmarkTransformKey() compositeKey.transform(seed, 1e6); } } - -QTEST_GUILESS_MAIN(TestKeys) diff --git a/tests/TestModified.cpp b/tests/TestModified.cpp index 864ea1c3..e275e837 100644 --- a/tests/TestModified.cpp +++ b/tests/TestModified.cpp @@ -27,9 +27,11 @@ #include "core/Tools.h" #include "crypto/Crypto.h" +QTEST_GUILESS_MAIN(TestModified) + void TestModified::initTestCase() { - Crypto::init(); + QVERIFY(Crypto::init()); } void TestModified::testSignals() @@ -466,5 +468,3 @@ void TestModified::testHistoryItem() delete db; } - -QTEST_GUILESS_MAIN(TestModified) diff --git a/tests/TestQCommandLineParser.cpp b/tests/TestQCommandLineParser.cpp index d487862c..4e2c6350 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 bccee0ec..443db529 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 8ac570e1..40ab702d 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 b47a0057..6d4e94f6 100644 --- a/tests/TestSymmetricCipher.cpp +++ b/tests/TestSymmetricCipher.cpp @@ -25,9 +25,11 @@ #include "crypto/SymmetricCipher.h" #include "streams/SymmetricCipherStream.h" +QTEST_GUILESS_MAIN(TestSymmetricCipher) + void TestSymmetricCipher::initTestCase() { - Crypto::init(); + QVERIFY(Crypto::init()); } void TestSymmetricCipher::testAes256CbcEncryption() @@ -192,5 +194,3 @@ void TestSymmetricCipher::testPadding() QByteArray decrypted = streamDec.readAll(); QCOMPARE(decrypted, plainText); } - -QTEST_GUILESS_MAIN(TestSymmetricCipher) diff --git a/tests/TestWildcardMatcher.cpp b/tests/TestWildcardMatcher.cpp index e06125b8..dc9991db 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/data/BrokenDeletedObjects.xml b/tests/data/BrokenDeletedObjects.xml new file mode 100644 index 00000000..89506aa6 --- /dev/null +++ b/tests/data/BrokenDeletedObjects.xml @@ -0,0 +1,27 @@ + + + + + lmU+9n0aeESKZvcEze+bRg== + Test + + AaUYVdXsI02h4T1RiAlgtg== + + Title + Sample Entry 1 + + + + + + + 2010-08-25T16:14:12Z + + + + 5K/bzWCSmkCv5OZxYl4N/w== + + + + + diff --git a/tests/data/BrokenGroupReference.xml b/tests/data/BrokenGroupReference.xml new file mode 100644 index 00000000..b3207e1a --- /dev/null +++ b/tests/data/BrokenGroupReference.xml @@ -0,0 +1,20 @@ + + + + True + 6w7wZdhAp0qVlXjkemuCYw== + + + + lmU+9n0aeESKZvcEze+bRg== + Test + + AaUYVdXsI02h4T1RiAlgtg== + + Title + Sample Entry 1 + + + + + diff --git a/tests/data/BrokenNoEntryUuid.xml b/tests/data/BrokenNoEntryUuid.xml index 38ab96f2..595f836f 100644 --- a/tests/data/BrokenNoEntryUuid.xml +++ b/tests/data/BrokenNoEntryUuid.xml @@ -9,6 +9,7 @@ Title Sample Entry 1 + diff --git a/tests/gui/TestGui.cpp b/tests/gui/TestGui.cpp index a4d04c5d..26c8be07 100644 --- a/tests/gui/TestGui.cpp +++ b/tests/gui/TestGui.cpp @@ -51,7 +51,7 @@ void TestGui::initTestCase() { - Crypto::init(); + QVERIFY(Crypto::init()); Config::createTempFileInstance(); m_mainWindow = new MainWindow(); m_tabWidget = m_mainWindow->findChild("tabWidget"); @@ -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()); @@ -170,20 +170,35 @@ void TestGui::testSearch() QVERIFY(searchAction->isEnabled()); QToolBar* toolBar = m_mainWindow->findChild("toolBar"); QWidget* searchActionWidget = toolBar->widgetForAction(searchAction); - QVERIFY(searchActionWidget->isEnabled()); - QTest::mouseClick(searchActionWidget, Qt::LeftButton); - EntryView* entryView = m_dbWidget->findChild("entryView"); QLineEdit* searchEdit = m_dbWidget->findChild("searchEdit"); QToolButton* clearSearch = m_dbWidget->findChild("clearButton"); + QVERIFY(!searchEdit->hasFocus()); + + // Enter search + QTest::mouseClick(searchActionWidget, Qt::LeftButton); + QTRY_VERIFY(searchEdit->hasFocus()); + // Search for "ZZZ" QTest::keyClicks(searchEdit, "ZZZ"); - QTRY_COMPARE(entryView->model()->rowCount(), 0); - + // Escape + QTest::keyClick(m_mainWindow, Qt::Key_Escape); + QTRY_VERIFY(!searchEdit->hasFocus()); + // Enter search again + QTest::mouseClick(searchActionWidget, Qt::LeftButton); + QTRY_VERIFY(searchEdit->hasFocus()); + // Input and clear + QTest::keyClicks(searchEdit, "ZZZ"); + QTRY_COMPARE(searchEdit->text(), QString("ZZZ")); QTest::mouseClick(clearSearch, Qt::LeftButton); + QTRY_COMPARE(searchEdit->text(), QString("")); + // Triggering search should select the existing text + QTest::keyClicks(searchEdit, "ZZZ"); + QTest::mouseClick(searchActionWidget, Qt::LeftButton); + QTRY_VERIFY(searchEdit->hasFocus()); + // Search for "some" QTest::keyClicks(searchEdit, "some"); - QTRY_COMPARE(entryView->model()->rowCount(), 4); clickIndex(entryView->model()->index(0, 1), entryView, Qt::LeftButton); @@ -237,8 +252,8 @@ void TestGui::testSearch() void TestGui::testDeleteEntry() { - GroupView* groupView = m_dbWidget->groupView(); - EntryView* entryView = m_dbWidget->entryView(); + GroupView* groupView = m_dbWidget->findChild("groupView"); + 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 +289,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 +307,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 +329,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 +468,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; diff --git a/tests/gui/TestGuiPixmaps.cpp b/tests/gui/TestGuiPixmaps.cpp index 401f68bc..87e3f248 100644 --- a/tests/gui/TestGuiPixmaps.cpp +++ b/tests/gui/TestGuiPixmaps.cpp @@ -29,7 +29,7 @@ void TestGuiPixmaps::initTestCase() { - Crypto::init(); + QVERIFY(Crypto::init()); } void TestGuiPixmaps::testDatabaseIcons() diff --git a/utils/kdbx-extract.cpp b/utils/kdbx-extract.cpp index beee71dc..f5d2a19a 100644 --- a/utils/kdbx-extract.cpp +++ b/utils/kdbx-extract.cpp @@ -38,7 +38,9 @@ int main(int argc, char **argv) return 1; } - Crypto::init(); + if (!Crypto::init()) { + qFatal("Fatal error while testing the cryptographic functions:\n%s", qPrintable(Crypto::errorString())); + } CompositeKey key; if (QFile::exists(app.arguments().at(1))) {