diff options
Diffstat (limited to 'qml/pages')
| -rw-r--r-- | qml/pages/AboutPage.qml | 91 | ||||
| -rw-r--r-- | qml/pages/MainPage.qml | 241 | ||||
| -rw-r--r-- | qml/pages/ProfileEditor.qml | 346 | ||||
| -rw-r--r-- | qml/pages/ProfilesPage.qml | 129 | ||||
| -rw-r--r-- | qml/pages/SettingsEditor.qml | 90 |
5 files changed, 897 insertions, 0 deletions
diff --git a/qml/pages/AboutPage.qml b/qml/pages/AboutPage.qml new file mode 100644 index 0000000..7326fe4 --- /dev/null +++ b/qml/pages/AboutPage.qml @@ -0,0 +1,91 @@ +import QtQuick 2.6 +import Sailfish.Silica 1.0 + +Page { + id: aboutPage + SilicaFlickable { + anchors.fill: parent + contentHeight: column.height + contentWidth: parent.width + Column { + id: column + width: aboutPage.width + spacing: Theme.paddingLarge + bottomPadding: Theme.paddingLarge + + PageHeader { + title: qsTr("About PassFish") + } + Label { + width: parent.width - 2*Theme.horizontalPageMargin + x: Theme.horizontalPageMargin + wrapMode: Text.WordWrap + textFormat: Text.RichText + onLinkActivated : Qt.openUrlExternally(link) + color: Theme.highlightColor + text: qsTr(" +<p> + PassFish is a native re-implementation of <a href=\"https://passwordmaker.org/\">PasswordMaker</a> for Sailfish OS. + It aims to be mostly compatible to the <a href=\"https://sourceforge.net/projects/passwordmaker/files/Javascript%20Edition/\">Javascript Edition</a>. +</p> +<p> + All credit for the development of the PasswordMaker Pro algorithm goes to the original authors of PasswordMaker Pro, Miquel Burns and Eric H. Jung. +</p> +<p> + As PassFish is not merely a port, but a full reimplementation from scratch, compatibility is not guaranteed. While the original source code was + used as a guideline during implementation, the underlying technology is vastly different. Common use cases are tested by integration tests, but some + edge cases might have been missed. In case you encounter such an issue, please report it on the + <a href=\"https://github.com/soulsource/passfish/issues\">github issue tracker</a> of the project. +</p> +<p> + Speaking of integration tests: The hash algorithms were not re-implemented for this project in order to reduce the risk of introducing bugs. Instead the + QCryptographicHash API is used where possible, and where not, the implementation from the RustCrypto Hashes repository is utilized (see list of dependencies below). +</p> +<p> + This program consists of two parts: The application itself (\"<a href=\"https://github.com/soulsource/passfish\">PassFish</a>\"), and a Rust crate that contains the + implementation of the business logic (\"<a href=\"https://github.com/soulsource/passwordmaker-rs\"><nobr>passwordmaker‑rs</nobr></a>\"). + This is important, as the two parts use different licenses. PassFish is published under the GPLv3 license, while <nobr>passwordmaker‑rs</nobr> is published under LGPLv3. Please check the linked github pages for more details. +</p> +<p> + This program utilises several third party libraries. This list is kept up-to-date to the best of my knowledge. Only direct dependencies are listed, + for transitive dependencies please see the linked websites. Similarly, the source code for those third-party dependencies that are published under an + open source license can be found on the linked websites. To my knowledge the only non-open-source dependency is Silica. The listed licenses are just those + used by <i>this</i> project, most libraries are available under multiple licenses. Please see the libraries' websites for details.<br> + These libraries are linked dynamically: + <ul> + <li><a href=\"https://www.qt.io/\">Qt Quick</a>: Used under <a href=\"https://www.gnu.org/licenses/lgpl-3.0.en.html\">LGPLv3</a></li> + <li><a href=\"https://sailfishos.org/develop/docs/silica/\">Sailfish Silica</a>: Proprietary, <a href=\"https://www.gnu.org/licenses/gpl-3.0\">GPL</a> system library exception</li> + <li><a href=\"https://github.com/sailfishos/libsailfishapp\">LibSailfishApp</a>: Used under LGPL 2.1</li> + </ul> + These libraries and their dependencies are linked statically: + <ul> + <li><a href=\"https://crates.io/crates/libc\">libc Rust bindings</a>: Used under MIT license</li> + <li><a href=\"https://crates.io/crates/serde\">serde</a>: Used under MIT license</li> + <li><a href=\"https://crates.io/crates/toml\">toml-rs</a>: Used under MIT license</li> + <li><a href=\"https://crates.io/crates/dirs\">dirs</a>: Used under MIT license</li> + <li><a href=\"https://crates.io/crates/ripemd\">RustCrypto: RIPEMD</a>: Used under MIT license</li> + </ul> + The <nobr>passwordmaker‑rs</nobr> library has the following statically linked runtime dependencies: + <ul> + <li><a href=\"https://crates.io/crates/unicode-segmentation\">unicode-segmentation</a>: Used under MIT license</li> + </ul> + While it is not a runtime dependency, the code generator for the Rust Qt bindings is worth noting: + <ul> + <li><a href=\"https://invent.kde.org/sdk/rust-qt-binding-generator\">Rust Qt Binding Generator</a></li> + </ul> + PassFish uses a modified version, which can be found in the <a href=\"https://invent.kde.org/soulsource/rust-qt-binding-generator/-/tree/mockall_support\">mockall_support</a> branch. +</p> +<p> + 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 3 of the License, or (at your option) any later version. + 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 <a href=\"http://www.gnu.org/licenses/\">http://www.gnu.org/licenses/</a>. +</p> + ") + } + } + + VerticalScrollDecorator { flickable: aboutPage} + } +} diff --git a/qml/pages/MainPage.qml b/qml/pages/MainPage.qml new file mode 100644 index 0000000..b860e67 --- /dev/null +++ b/qml/pages/MainPage.qml @@ -0,0 +1,241 @@ +import QtQuick 2.6 +import Sailfish.Silica 1.0 +import "../components" + +Page { + id: mainPage + + readonly property var isBackground : Qt.application.state + + onIsBackgroundChanged: { + if(isBackground === Qt.ApplicationInactive) { + mainFlickable.restart_timers(); + } + } + + SilicaFlickable { + id: mainFlickable + anchors.fill: parent + contentHeight: column.height + + function store_settings_with_error_message() { + var worked = passwordmaker.store_settings(); + if(!worked) { + storeFailureNotice.show(); + } + } + function restart_timers() { + if(autoClearMasterPasswordTimer.enabled && passwordmaker.master_password.length > 0) { + autoClearMasterPasswordTimer.restart(); + } + if(autoClearGeneratedPasswordTimer.enabled && (passwordmaker.used_text.length > 0 || passwordmaker.url.length > 0)) { + autoClearGeneratedPasswordTimer.restart(); + } + } + + PullDownMenu { + MenuItem { + text: qsTr("App Settings") + onClicked: { + var pg = pageStack.animatorPush(Qt.resolvedUrl("SettingsEditor.qml"), + { + clear_generated_password : passwordmaker.settings.clear_generated_password_seconds, + clear_master_password : passwordmaker.settings.clear_master_password_seconds, + hide_generated_password : passwordmaker.settings.hide_generated_password + }); + pg.pageCompleted.connect(function(pg) { + pg.accepted.connect(function() { + passwordmaker.settings.clear_generated_password_seconds + =pg.clear_generated_password; + passwordmaker.settings.clear_master_password_seconds + =pg.clear_master_password; + passwordmaker.settings.hide_generated_password + =pg.hide_generated_password; + + mainFlickable.store_settings_with_error_message() + }) + }) + } + } + MenuItem { + text: qsTr("About") + onClicked: pageStack.animatorPush(Qt.resolvedUrl("AboutPage.qml")) + } + } + VerticalScrollDecorator {} + Column { + id: column + + width: mainPage.width + spacing: Theme.paddingLarge + PageHeader { + title: qsTr("PassFish") + } + + + ValueButton { + id: profileButton + label: qsTr("Profile") + value: passwordmaker.profiles.current_profile_name + + onClicked: pageStack.animatorPush(Qt.resolvedUrl("ProfilesPage.qml")) + } + TextField { + id: url + width: parent.width + + text: passwordmaker.url + inputMethodHints: Qt.ImhUrlCharactersOnly | Qt.ImhNoPredictiveText | Qt.ImhNoAutoUppercase + + label: qsTr("URL") + placeholderText: qsTr("URL") + EnterKey.iconSource: "image://theme/icon-m-enter-next" + EnterKey.onClicked: masterPassword.focus = true + Binding { + target: passwordmaker + property: "url" + value: url.text + } + } + PasswordField { + id: masterPassword + width: parent.width + + text: passwordmaker.master_password + + label: qsTr("Master Password") + placeholderText: qsTr("Master Password") + EnterKey.iconSource: "image://theme/icon-m-enter-close" + EnterKey.onClicked: focus = false + Binding { + target: passwordmaker + property: "master_password" + value: masterPassword.text + } + } + TextField { + id: usedText + width: parent.width + + inputMethodHints: Qt.ImhNoPredictiveText | Qt.ImhNoAutoUppercase + + label: qsTr("Used Text") + onFocusChanged: if(focus) text = passwordmaker.used_text + onPlaceholderTextChanged: if(!focus) text = "" + placeholderText: passwordmaker.used_text === "" ? qsTr("Used Text") : passwordmaker.used_text + EnterKey.iconSource: "image://theme/icon-m-enter-close" + EnterKey.onClicked: focus = false + Binding { + target: passwordmaker + property: "used_text" + value: usedText.text + when: usedText.focus + } + } + Separator { + width: parent.width + horizontalAlignment:Qt.AlignHCenter + color: Theme.secondaryColor + } + Row{ + width: parent.width + PasswordField { + id: generatedPassword + width: parent.width - copyToClipboard.width + + + function password_text_from_generator_state(g, t, e) { + switch(g){ + case 0 : + return e === TextInput.Normal ? t : t.replace(/./g, passwordCharacter); + case 1 : + return qsTr("Generating"); + case 2 : + return qsTr("Missing text to use"); + case 3 : + return qsTr("Missing master password"); + case 4 : + return qsTr("Error in profile character list"); + default: + return ""; + } + } + + passwordEchoMode: passwordmaker.settings.hide_generated_password + ? TextInput.Password : TextInput.Normal + showEchoModeToggle: passwordmaker.settings.hide_generated_password + readOnly: true + focusOnClick: true + label: qsTr("Generated Password") + hideLabelOnEmptyField: false + placeholderText: + password_text_from_generator_state( + passwordmaker.generator_state + , passwordmaker.generated_password + , echoMode) + + onFocusChanged: if(focus) text = passwordmaker.generated_password; else text = ""; + placeholderColor: color + + BusyIndicator { + id: busy + parent: null + size: BusyIndicatorSize.Small + running: passwordmaker.generator_state === 1 + } + states: State { + when: passwordmaker.generator_state === 1 + PropertyChanges { + target: generatedPassword + rightItem: busy + } + } + } + IconButton{ + id: copyToClipboard + enabled: passwordmaker.generator_state === 0 && passwordmaker.generated_password.length > 0 + icon.source: "image://theme/icon-m-clipboard" + onClicked: Clipboard.text = passwordmaker.generated_password; + } + } + Timer { + id: autoClearMasterPasswordTimer + property bool enabled: typeof passwordmaker.settings.clear_master_password_seconds !== 'undefined' + interval: passwordmaker.settings.clear_master_password_seconds*1000 || 10000000 + running: false + onTriggered: if(enabled) + passwordmaker.master_password = ""; + } + Timer { + id: autoClearGeneratedPasswordTimer + property bool enabled: typeof passwordmaker.settings.clear_generated_password_seconds !== 'undefined' + interval: passwordmaker.settings.clear_generated_password_seconds*1000 || 10000000 + running: false + onTriggered: if(enabled){ + passwordmaker.used_text = ""; + passwordmaker.url = ""; + } + } + } + NoticeOptional { + id: storeFailureNotice + text: qsTr("Saving settings failed.") + useNotificationFallback: true + } + + Binding{ + target: autoClearMasterPasswordTimer + property: "running" + value: false + when: !autoClearMasterPasswordTimer.enabled || isBackground === Qt.ApplicationActive + } + Binding{ + target: autoClearGeneratedPasswordTimer + property: "running" + value: false + when: !autoClearGeneratedPasswordTimer.enabled || isBackground === Qt.ApplicationActive + } + } + readonly property int current_profile_index : passwordmaker.profiles.current_profile + onCurrent_profile_indexChanged: {mainFlickable.store_settings_with_error_message()} +} diff --git a/qml/pages/ProfileEditor.qml b/qml/pages/ProfileEditor.qml new file mode 100644 index 0000000..5dd28e0 --- /dev/null +++ b/qml/pages/ProfileEditor.qml @@ -0,0 +1,346 @@ +import QtQuick 2.6 +import Sailfish.Silica 1.0 +import "../components" +import PWM 1.0 + +Dialog { + id: profileEditor + + property alias profileName: profileNameField.text + property alias useProtocol: protocolField.checked + property alias useSubdomain: subdomainField.checked + property alias useDomain: domainField.checked + property alias usePortPath: portPathField.checked + property alias useUserInfo: userInfoField.checked + property alias useDefaultFallbackForProtocol : useDefaultFallbackForProtocolField.checked + property alias passwordLength: passwordLengthSlider.value + property alias hashAlgorithm: hashAlgorithmComboBox.currentIndex + property alias useLeet : useLeetComboBox.currentIndex + property alias leetLevel : leetLevelSlider.value + property alias characters : charactersField.text + property alias username : usernameField.text + property alias modifier : modifierField.text + property alias prefix : prefixField.text + property alias suffix : suffixField.text + + canAccept: { + profileNameField.acceptableInput + && charactersField.acceptableInput + && urlPartsColumn.anySelected + } + + onAcceptBlocked: { + if(!urlPartsColumn.anySelected) { + urlNotice.show(); + } + else if(!profileNameField.acceptableInput) { + nameNotice.show(); + } + else if(!charactersField.acceptableInput) { + charactersNotice.show(); + } + } + + + SilicaFlickable { + anchors.fill: parent + contentHeight: column.height + + VerticalScrollDecorator {} + + Column { + id: column + width: parent.width + bottomPadding: Theme.paddingLarge + DialogHeader { + title: qsTr("Edit Profile") + } + TextField { + id: profileNameField + width: parent.width + errorHighlight: !acceptableInput + + //description doesn't work on Sailfish 3. Use label instead if unavailable. + readonly property bool descriptionAvailable : typeof(description) !== "undefined" + label: !descriptionAvailable && errorHighlight ? qsTr("Required Field") : qsTr("Profile Name") + hideLabelOnEmptyField: descriptionAvailable + placeholderText: qsTr("Profile Name") + + Binding { + target: profileNameField + property: "description" + value: profileNameField.errorHighlight ? qsTr("Required Field") : "" + when: profileNameField.descriptionAvailable + } + + validator: RegExpValidator{regExp: /.+/} + //It doesn't make much sense to send the focus to the unrelated and rarely used fields waaaaay at the bottom and skip most relevant fields. Rather close the keyboard. + EnterKey.iconSource: "image://theme/icon-m-enter-close" + EnterKey.onClicked: focus = false + } + NoticeOptional { + id: nameNotice + text: qsTr("Profile name required.") + useNotificationFallback: false + } + Column { + id: urlPartsColumn + width: parent.width + topPadding: Theme.paddingLarge + bottomPadding: Theme.paddingLarge + + property bool anySelected : (useProtocol || useSubdomain || useDomain || usePortPath || useUserInfo) + + TextSwitch { + id: protocolField + text: qsTr("Use Protocol") + description: qsTr("Include URL protocol (e.g. \"http://\")") + palette.highlightColor : down || urlPartsColumn.anySelected ? Theme.highlightColor : Theme.errorColor + highlighted: down || !urlPartsColumn.anySelected + } + TextSwitch { + id: useDefaultFallbackForProtocolField + visible: protocolField.checked + text: qsTr("Use \"undefined\" if protocol is missing") + description: qsTr("Enable to mimic weird behaviour of PasswordMaker Pro.") + palette.highlightColor : down ? Theme.highlightColor : Theme.errorColor + highlighted: down + } + TextSwitch { + id: userInfoField + text: qsTr("Use Userinfo") + description: qsTr("Include userinfo (e.g \"jane_doe:12345\"") + palette.highlightColor : down || urlPartsColumn.anySelected ? Theme.highlightColor : Theme.errorColor + highlighted: down || !urlPartsColumn.anySelected + } + TextSwitch { + id: subdomainField + text: qsTr("Use Subomain(s)") + description: qsTr("Include URL subdomain(s) (e.g. \"www.\")") + palette.highlightColor : down || urlPartsColumn.anySelected ? Theme.highlightColor : Theme.errorColor + highlighted: down || !urlPartsColumn.anySelected + } + TextSwitch { + id: domainField + text: qsTr("Use Domain") + description: qsTr("Include URL domain (e.g. \"example.com\")") + palette.highlightColor : down || urlPartsColumn.anySelected ? Theme.highlightColor : Theme.errorColor + highlighted: down || !urlPartsColumn.anySelected + } + TextSwitch { + id: portPathField + text: qsTr("Use Port/Path") + description: qsTr("Include port and path (e.g \":8080/file\")") + palette.highlightColor : down || urlPartsColumn.anySelected ? Theme.highlightColor : Theme.errorColor + highlighted: down || !urlPartsColumn.anySelected + + } + NoticeOptional { + id: urlNotice + text: qsTr("At least one URL part required.") + useNotificationFallback: false + } + } + Column { + width: parent.width + topPadding: Theme.paddingLarge + bottomPadding: Theme.paddingLarge + Slider { + id: passwordLengthSlider + minimumValue: 1 + maximumValue: 50 + stepSize: 1 + width: parent.width + valueText : value + label: qsTr("Password Length") + } + ComboBox { + id: hashAlgorithmComboBox + label: qsTr("Hash Algorithm") + menu: ContextMenu { + MenuItem { text: "MD4" } + MenuItem { text: "HMAC-MD4" } + MenuItem { text: "MD5" } + MenuItem { text: qsTr("MD5 Version 0.6") } + MenuItem { text: "HMAC-MD5" } + MenuItem { text: qsTr("HMAC-MD5 Version 0.6") } + MenuItem { text: "SHA-1" } + MenuItem { text: "HMAC-SHA-1" } + MenuItem { text: "SHA-256" } + MenuItem { text: "HMAC-SHA-256" } + MenuItem { text: "RIPEMD-160" } + MenuItem { text: "HMAC-RIPEMD-160" } + } + } + ComboBox { + id: useLeetComboBox + label: qsTr("Use L33t") + menu: ContextMenu { + MenuItem { text: qsTr("not at all") } + MenuItem { text: qsTr("before generating") } + MenuItem { text: qsTr("after generating") } + MenuItem { text: qsTr("before and after generating") } + } + } + Slider { + id: leetLevelSlider + minimumValue: 1 + maximumValue: 9 + stepSize: 1 + width: parent.width + valueText: value + visible: useLeetComboBox.currentIndex > 0 + label: qsTr("Leet Level") + } + } + Column { + enabled: hashAlgorithmComboBox.currentIndex != 3 && hashAlgorithmComboBox.currentIndex != 5 + width: parent.width + topPadding: Theme.paddingLarge + bottomPadding: Theme.paddingLarge + ListModel { + id: defaultCharacterValues + ListElement { + name: qsTr("Default Characters") + chars: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789`~!@#$%^&*()_-+={}|[]\\:\";'<>?,./" + userFacing: true + } + ListElement { + name: qsTr("Alphanumeric") + chars: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" + userFacing: true + } + ListElement { + name: qsTr("Letters only") + chars: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + userFacing: true + } + ListElement { + name: qsTr("Numbers only") + chars: "0123456789" + userFacing: true + } + ListElement { + name: qsTr("Special only") + chars: "`~!@#$%^&*()_-+={}|[]\\:\";'<>?,./" + userFacing: true + } + ListElement { + name: qsTr("Hex") + chars: "0123456789abcdef" + userFacing: true + } + ListElement { + name: qsTr("Custom") + chars: "" + userFacing: false + } + } + ComboBox { + id: defaultCharactersMenu + + function matchIndex(text) { + for(var i=0; i < defaultCharacterValues.count - 1;++i) + if(defaultCharacterValues.get(i).chars === text) + return i; + return defaultCharacterValues.count - 1; + } + + Binding { + target: defaultCharactersMenu + property: "currentIndex" + value: defaultCharactersMenu.matchIndex(charactersField.text) + } + + label: qsTr("Characters Preset") + menu: ContextMenu { + Repeater { + model: defaultCharacterValues + MenuItem { + text: name + visible: userFacing + onClicked: if(userFacing) charactersField.text = chars + } + } + } + } + TextField { + id: charactersField + width: parent.width + errorHighlight: !acceptableInput + + //description doesn't work on Sailfish 3. Use label instead if unavailable. + readonly property bool descriptionAvailable : typeof(description) !== "undefined" + label: !descriptionAvailable && errorHighlight ? qsTr("Need at least 2 characters.") : qsTr("Characters") + hideLabelOnEmptyField: descriptionAvailable + placeholderText: qsTr("Characters") + validator: GraphemeCountValidator { minGraphemeCount: 2 } + + //The text fields below are conceptually different. Close the keyboard. + EnterKey.iconSource: "image://theme/icon-m-enter-close" + EnterKey.onClicked: focus = false + + Binding { + target: charactersField + property: "description" + value: charactersField.errorHighlight ? qsTr("Need at least 2 characters.") : "" + when: charactersField.descriptionAvailable + } + states: State{ + name: "locked" + when: hashAlgorithmComboBox.currentIndex === 3 || hashAlgorithmComboBox.currentIndex === 5 + PropertyChanges { + target: charactersField + text: "0123456789abcdef" + + } + } + + } + NoticeOptional { + id: charactersNotice + text: qsTr("Need at least 2 characters.") + useNotificationFallback: false + } + } + Column { + width: parent.width + topPadding: Theme.paddingLarge + bottomPadding: Theme.paddingLarge + TextField { + id: usernameField + width: parent.width + label: qsTr("Username") + placeholderText: qsTr("Username") + EnterKey.iconSource: "image://theme/icon-m-enter-next" + EnterKey.onClicked: modifierField.focus = true + } + TextField { + id: modifierField + width: parent.width + label: qsTr("Modifier") + placeholderText: qsTr("Modifier") + EnterKey.iconSource: "image://theme/icon-m-enter-next" + EnterKey.onClicked: prefixField.focus = true + } + TextField { + id: prefixField + width: parent.width + label: qsTr("Prefix") + placeholderText: qsTr("Prefix") + EnterKey.iconSource: "image://theme/icon-m-enter-next" + EnterKey.onClicked: suffixField.focus = true + } + TextField { + id: suffixField + width: parent.width + label: qsTr("Suffix") + placeholderText: qsTr("Suffix") + //There are many non-keyboard-input switches on this page. Do not let the user confirm using keyboard. + EnterKey.iconSource: "image://theme/icon-m-enter-close" + EnterKey.onClicked: focus = false + } + } + } + } +} diff --git a/qml/pages/ProfilesPage.qml b/qml/pages/ProfilesPage.qml new file mode 100644 index 0000000..a2df8af --- /dev/null +++ b/qml/pages/ProfilesPage.qml @@ -0,0 +1,129 @@ +import QtQuick 2.6 +import Sailfish.Silica 1.0 +import "../components" + +Page { + id: profilesSelector + SilicaListView { + id : profilesView + anchors.fill: parent + model : passwordmaker.profiles + + function store_profile_with_error_message() { + var worked = profilesView.model.store(); + if(!worked) { + storeFailureNotice.show(); + } + } + + PullDownMenu { + MenuItem { + text: qsTr("Add Profile") + onClicked: { + profilesView.model.insertRows(profilesView.model.rowCount(),1); + profilesView.store_profile_with_error_message(); + } + } + } + header: PageHeader { + title: qsTr("Select/Edit Profiles") + } + delegate: ListItem { + id: delegate + width: parent.width + ListView.onAdd: AddAnimation { + target: delegate + } + ListView.onRemove: RemoveAnimation { + target: delegate + } + Label { + x: Theme.horizontalPageMargin + text: name + anchors.verticalCenter: parent.verticalCenter + color: index === profilesView.model.current_profile ? Theme.highlightColor : Theme.primaryColor + } + menu: ContextMenu { + MenuItem { + text: qsTr("Edit") + onClicked: { + var pg = pageStack.animatorPush(Qt.resolvedUrl("ProfileEditor.qml"), + { + profileName : name, + useProtocol : use_protocol, + useSubdomain : use_subdomains, + useDomain : use_domain, + usePortPath : use_port_path, + passwordLength : password_length, + hashAlgorithm : hash_algorithm, + useLeet : use_leet, + leetLevel : leet_level > 0 ? leet_level : 1, + characters : characters, + username : username, + modifier : modifier, + prefix : prefix, + suffix : suffix, + useUserInfo : use_user_info, + useDefaultFallbackForProtocol : use_undefined_as_protocol_fallback + }); + pg.pageCompleted.connect(function(pg) { + pg.accepted.connect(function() { + name = pg.profileName; + use_protocol = pg.useProtocol; + use_subdomains = pg.useSubdomain; + use_domain = pg.useDomain; + use_port_path = pg.usePortPath; + password_length = pg.passwordLength; + hash_algorithm = pg.hashAlgorithm; + use_leet = pg.useLeet; + leet_level = pg.leetLevel; + characters = pg.characters; + username = pg.username; + modifier = pg.modifier; + prefix = pg.prefix; + suffix = pg.suffix; + use_user_info = pg.useUserInfo; + use_undefined_as_protocol_fallback = pg.useDefaultFallbackForProtocol; + + if(index === profilesView.model.current_profile) + passwordmaker.profile_changed(); + + profilesView.store_profile_with_error_message(); + }) + }) + } + } + MenuItem { + text: qsTr("Remove") + enabled: profilesView.count > 1 + onClicked: { + delegate.remorseDelete(function() { + var bWasCurrentProfile = index === profilesView.model.current_profile; + + profilesView.model.removeRows(index,1); + profilesView.store_profile_with_error_message(); + + if(bWasCurrentProfile) + passwordmaker.profile_changed(); + }) + } + } + } + onClicked: { + if(profilesView.model.current_profile !== index) + { + profilesView.model.current_profile = index; + passwordmaker.profile_changed(); + } + pageContainer.navigateBack(PageStackAction.Animated); + } + } + NoticeOptional { + id: storeFailureNotice + text: qsTr("Saving profiles failed.") + useNotificationFallback: true + } + + VerticalScrollDecorator {} + } +} diff --git a/qml/pages/SettingsEditor.qml b/qml/pages/SettingsEditor.qml new file mode 100644 index 0000000..9baa51c --- /dev/null +++ b/qml/pages/SettingsEditor.qml @@ -0,0 +1,90 @@ +import QtQuick 2.6 +import Sailfish.Silica 1.0 + +Dialog { + id: settingsEditor + + property var clear_generated_password; + property var clear_master_password; + property alias hide_generated_password: hide_generated_passwordBox.checked; + + onAccepted: { + clear_generated_password = + clear_generated_passwordBox.checked + ? clear_generated_password_time.sliderValue * 60 + : null; + clear_master_password = + clear_master_passwordBox.checked + ? clear_master_password_time.sliderValue * 60 + : null; + } + + + SilicaFlickable { + anchors.fill: parent + contentHeight: column.height + + VerticalScrollDecorator {} + + Column { + id: column + width: parent.width + bottomPadding: Theme.paddingLarge + DialogHeader { + title: qsTr("Edit Settings") + } + TextSwitch { + id: hide_generated_passwordBox + text: qsTr("Hide Generated Password") + palette.highlightColor : Theme.highlightColor + highlighted: down + } + TextSwitch { + id: clear_generated_passwordBox + text: qsTr("Auto-clear generated password") + palette.highlightColor : Theme.highlightColor + highlighted: down + checked: typeof clear_generated_password !== 'undefined' + } + Slider { + id: clear_generated_password_time + minimumValue: 1 + maximumValue: 15 + stepSize: 1 + width: parent.width + valueText: value + " min" + visible: clear_generated_passwordBox.checked + label: qsTr("Auto-clear generated pass timeout") + value: clear_generated_password/60 || 1 + } + TextSwitch { + id: clear_master_passwordBox + text: qsTr("Auto-clear master password") + palette.highlightColor : Theme.highlightColor + highlighted: down + checked: typeof clear_master_password !== 'undefined' + } + Slider { + id: clear_master_password_time + minimumValue: clear_generated_passwordBox.checked + ? clear_generated_password_time.sliderValue + : 1 + maximumValue: 30 + stepSize: 1 + width: parent.width + valueText: sliderValue + " min" + visible: clear_master_passwordBox.checked + label: qsTr("Auto-clear master pass timeout") + value: clear_master_password/60 || 5 + } + Label { + topPadding: Theme.paddingLarge + width: parent.width - 2*Theme.horizontalPageMargin + x: Theme.horizontalPageMargin + wrapMode: Text.WordWrap + color: Theme.highlightColor + text: qsTr("Profiles can be edited directly in the profiles selector.") + } + } + } +} |
