passwordmanager/qml/main.qml

478 lines
16 KiB
QML
Raw Normal View History

import QtQuick 2.7
import QtQuick.Templates 2.0 as T2
import QtQuick.Controls 2.1 as Controls
import QtQuick.Layouts 1.2
import QtQuick.Dialogs 1.3
import org.kde.kirigami 2.4 as Kirigami
2015-04-22 19:30:09 +02:00
Kirigami.ApplicationWindow {
2015-04-22 19:30:09 +02:00
id: root
property var fieldsPage: undefined
property var lastEntriesPage: undefined
globalDrawer: Kirigami.GlobalDrawer {
id: leftMenu
property bool showNoPasswordWarning: nativeInterface.fileOpen
&& !nativeInterface.passwordSet
2018-12-03 18:11:42 +01:00
title: app.applicationName
titleIcon: "qrc://icons/hicolor/scalable/apps/passwordmanager.svg"
2018-09-10 19:57:58 +02:00
visible: true
resetMenuOnTriggered: false
2018-06-14 23:19:28 +02:00
topContent: ColumnLayout {
Layout.fillWidth: true
Item {
Layout.preferredHeight: 4
}
Item {
Layout.fillWidth: true
Layout.preferredHeight: filterTextField.implicitHeight
enabled: nativeInterface.fileOpen
visible: !nativeInterface.filterAsDialog
Controls.TextField {
id: filterTextField
anchors.fill: parent
placeholderText: qsTr("Filter")
onTextChanged: nativeInterface.entryFilter = text
}
Kirigami.Icon {
source: "edit-clear"
anchors.right: parent.right
anchors.rightMargin: 6
anchors.verticalCenter: parent.verticalCenter
width: Kirigami.Units.iconSizes.small
height: Kirigami.Units.iconSizes.small
visible: filterTextField.text.length !== 0
MouseArea {
anchors.fill: parent
onClicked: filterTextField.text = ""
}
}
}
2018-06-14 23:19:28 +02:00
Controls.MenuSeparator {
padding: 0
topPadding: 8
bottomPadding: 0
Layout.fillWidth: true
}
Controls.Label {
id: fileNameLabel
2018-06-14 23:19:28 +02:00
padding: 8
wrapMode: Controls.Label.Wrap
fontSizeMode: Text.HorizontalFit
minimumPixelSize: 10
font.pixelSize: 20
Layout.fillWidth: true
2018-06-14 23:46:18 +02:00
text: nativeInterface.fileOpen ? nativeInterface.fileName : qsTr(
"No file opened")
MouseArea {
id: fileNameMouseArea
anchors.fill: parent
hoverEnabled: true
}
Controls.ToolTip {
z: 1000
text: nativeInterface.filePath
visible: text ? fileNameMouseArea.containsMouse : false
delay: Qt.styleHints.mousePressAndHoldInterval
onAboutToShow: {
x = fileNameMouseArea.mouseX + 10
y = fileNameMouseArea.mouseY + 10
}
}
2018-06-14 23:19:28 +02:00
}
RowLayout {
visible: leftMenu.showNoPasswordWarning
Item {
Layout.preferredWidth: 2
}
Controls.Label {
text: qsTr("No password set\nFile will be saved unencrypted!")
}
}
Item {
visible: leftMenu.showNoPasswordWarning
height: 4
}
2018-06-14 23:19:28 +02:00
}
actions: [
Kirigami.Action {
text: qsTr("Create new file")
iconName: "document-new"
onTriggered: fileDialog.createNew()
2018-09-10 20:43:58 +02:00
shortcut: StandardKey.New
},
Kirigami.Action {
text: qsTr("Open existing file")
iconName: "document-open"
onTriggered: fileDialog.openExisting()
2018-09-10 20:43:58 +02:00
shortcut: StandardKey.Open
},
2018-06-16 15:07:46 +02:00
Kirigami.Action {
id: recentlyOpenedAction
2018-06-16 15:07:46 +02:00
text: qsTr("Recently opened ...")
iconName: "document-open-recent"
2018-09-16 19:16:12 +02:00
children: createRecentlyOpenedActions(
nativeInterface.recentFiles)
visible: nativeInterface.recentFiles.length > 0
2018-09-10 20:43:58 +02:00
shortcut: "Ctrl+R"
2018-06-16 15:07:46 +02:00
},
Kirigami.Action {
2018-09-10 19:57:58 +02:00
text: qsTr("Save modifications")
enabled: nativeInterface.fileOpen
iconName: "document-save"
onTriggered: nativeInterface.save()
2018-09-10 20:43:58 +02:00
shortcut: StandardKey.Save
},
Kirigami.Action {
2018-09-10 19:57:58 +02:00
text: nativeInterface.passwordSet ? qsTr("Change password") : qsTr(
"Add password")
enabled: nativeInterface.fileOpen
2018-09-09 00:57:02 +02:00
iconName: "document-encrypt"
onTriggered: enterPasswordDialog.askForNewPassword(
qsTr("Change password for %1").arg(
nativeInterface.filePath))
2018-09-10 20:43:58 +02:00
shortcut: "Ctrl+P"
},
2018-12-21 01:14:41 +01:00
Kirigami.Action {
text: qsTr("Details")
enabled: nativeInterface.fileOpen
iconName: "document-properties"
onTriggered: {
leftMenu.resetMenu()
fileSummaryDialog.show()
}
shortcut: "Ctrl+I"
},
Kirigami.Action {
text: nativeInterface.entryFilter.length === 0 ? qsTr("Search") : qsTr(
"Adjust search")
enabled: nativeInterface.fileOpen
visible: nativeInterface.filterAsDialog
iconName: "search"
onTriggered: {
leftMenu.resetMenu()
filterDialog.open()
}
shortcut: "Ctrl+F"
},
Kirigami.Action {
text: qsTr("Clear search")
enabled: nativeInterface.fileOpen
visible: nativeInterface.filterAsDialog
&& nativeInterface.entryFilter.length > 0
iconName: "edit-clear"
onTriggered: {
leftMenu.resetMenu()
nativeInterface.entryFilter = ""
}
shortcut: "Ctrl+Shift+F"
},
Kirigami.Action {
text: qsTr("Undo \"%1\"").arg(nativeInterface.undoText)
visible: nativeInterface.undoText.length !== 0
&& nativeInterface.entryFilter.length === 0
enabled: visible
iconName: "edit-undo"
shortcut: StandardKey.Undo
onTriggered: nativeInterface.undo()
},
Kirigami.Action {
text: qsTr("Redo \"%1\"").arg(nativeInterface.redoText)
visible: nativeInterface.redoText.length !== 0
&& nativeInterface.entryFilter.length === 0
enabled: visible
iconName: "edit-redo"
shortcut: StandardKey.Redo
onTriggered: nativeInterface.redo()
},
Kirigami.Action {
text: qsTr("Close file")
enabled: nativeInterface.fileOpen
iconName: "document-close"
2018-09-10 20:43:58 +02:00
shortcut: StandardKey.Close
onTriggered: nativeInterface.close()
2015-04-22 19:30:09 +02:00
}
]
2018-12-03 18:11:42 +01:00
onBannerClicked: {
leftMenu.resetMenu()
aboutDialog.open()
}
2018-09-04 00:52:43 +02:00
Controls.Switch {
text: qsTr("Use native file dialog")
checked: nativeInterface.useNativeFileDialog
visible: nativeInterface.supportsNativeFileDialog
onCheckedChanged: nativeInterface.useNativeFileDialog = checked
}
2015-04-22 19:30:09 +02:00
}
2018-06-12 22:02:37 +02:00
contextDrawer: Kirigami.ContextDrawer {
id: contextDrawer
}
Component.onCompleted: nativeInterface.init()
2018-06-14 23:46:18 +02:00
AboutDialog {
2018-12-03 18:11:42 +01:00
id: aboutDialog
}
2018-06-14 23:46:18 +02:00
PasswordDialog {
id: enterPasswordDialog
2018-09-10 19:57:58 +02:00
onAboutToShow: leftMenu.close()
onRejected: {
if (!nativeInterface.fileOpen) {
leftMenu.open()
}
}
2018-06-14 23:46:18 +02:00
}
2018-12-21 01:14:41 +01:00
BasicDialog {
id: fileSummaryDialog
standardButtons: Controls.Dialog.Ok
title: qsTr("File details")
Controls.Label {
id: fileSummaryLabel
text: "No file summary available"
textFormat: Text.RichText
wrapMode: Text.Wrap
width: fileSummaryDialog.availableWidth
2018-12-21 01:14:41 +01:00
}
function show() {
fileSummaryLabel.text = nativeInterface.computeFileSummary()
this.open()
}
}
2018-06-14 23:46:18 +02:00
FileDialog {
id: fileDialog
title: selectExisting ? qsTr("Select an existing file") : qsTr(
"Select path for new file")
onAccepted: {
if (fileUrls.length < 1) {
return
}
2018-09-04 00:52:43 +02:00
nativeInterface.handleFileSelectionAccepted(fileUrls[0],
this.selectExisting)
2018-06-14 23:46:18 +02:00
}
2018-09-04 00:52:43 +02:00
onRejected: nativeInterface.handleFileSelectionCanceled()
2018-06-14 23:46:18 +02:00
2018-09-04 00:52:43 +02:00
function show() {
if (nativeInterface.showNativeFileDialog(this.selectExisting)) {
return
}
// fallback to the Qt Quick file dialog if a native implementation is not available
this.open()
}
2018-06-14 23:46:18 +02:00
function openExisting() {
this.selectExisting = true
2018-09-04 00:52:43 +02:00
this.show()
2018-06-14 23:46:18 +02:00
}
function createNew() {
this.selectExisting = false
2018-09-04 00:52:43 +02:00
this.show()
2018-06-14 23:46:18 +02:00
}
}
BasicDialog {
id: filterDialog
title: qsTr("Search for categories and accounts")
onAccepted: nativeInterface.entryFilter = filterDialogTextField.text
onReset: {
nativeInterface.entryFilter = ""
filterDialog.close()
}
onVisibleChanged: {
if (visible) {
filterDialogTextField.forceActiveFocus()
}
}
footer: Controls.DialogButtonBox {
Controls.Button {
text: qsTr("Apply search term")
Controls.DialogButtonBox.buttonRole: Controls.DialogButtonBox.AcceptRole
enabled: filterDialogTextField.text.length > 0
}
Controls.Button {
text: qsTr("Clear search")
Controls.DialogButtonBox.buttonRole: Controls.DialogButtonBox.ResetRole
enabled: nativeInterface.entryFilter
}
Controls.Button {
text: qsTr("Quit dialog")
Controls.DialogButtonBox.buttonRole: Controls.DialogButtonBox.RejectRole
}
}
ColumnLayout {
Controls.TextField {
id: filterDialogTextField
Layout.preferredWidth: filterDialog.availableWidth
Keys.onPressed: filterDialog.acceptOnReturn(event)
}
}
}
2018-06-14 23:46:18 +02:00
Connections {
target: nativeInterface
onEntryFilterChanged: {
if (filterTextField.text !== newFilter) {
filterTextField.text = newFilter
}
}
2018-06-14 23:46:18 +02:00
onFileError: {
if (retryAction.length === 0) {
showPassiveNotification(errorMessage)
} else {
showPassiveNotification(errorMessage, 2500, qsTr("Retry"),
function () {
nativeInterface[retryAction]()
})
}
2018-06-14 23:46:18 +02:00
}
onPasswordRequired: {
enterPasswordDialog.askForExistingPassword(
qsTr("Password required to open %1").arg(
nativeInterface.filePath))
leftMenu.resetMenu()
}
onFileOpenChanged: {
clearStack()
if (!nativeInterface.fileOpen) {
showPassiveNotification(qsTr("%1 closed").arg(
nativeInterface.fileName))
return
}
initStack()
2018-06-14 23:46:18 +02:00
showPassiveNotification(qsTr("%1 opened").arg(
nativeInterface.fileName))
2018-09-10 19:57:58 +02:00
leftMenu.close()
2018-06-14 23:46:18 +02:00
}
onFileSaved: {
showPassiveNotification(qsTr("%1 saved").arg(
nativeInterface.fileName))
}
2018-09-04 00:52:43 +02:00
onNewNotification: {
showPassiveNotification(message)
}
onCurrentAccountChanged: {
// remove the fields page if the current account has been removed
if (!nativeInterface.hasCurrentAccount) {
pageStack.pop(lastEntriesPage)
}
}
onEntryAboutToBeRemoved: {
// get the filter entry index
if (nativeInterface.hasEntryFilter) {
removedIndex = nativeInterface.filterEntryIndex(removedIndex)
}
// remove all possibly open stack pages of the removed entry and its children
for (var i = pageStack.depth - 1; i >= 0; --i) {
var stackPage = pageStack.get(i)
if (!stackPage) {
continue
}
if (stackPage.rootIndex === removedIndex) {
pageStack.pop(lastEntriesPage = pageStack.get(i - 1))
return
}
}
}
onHasEntryFilterChanged: {
if (nativeInterface.fileOpen) {
pageStack.clear()
initStack()
}
}
2018-06-14 23:46:18 +02:00
}
2018-06-16 15:07:46 +02:00
Component {
2018-06-16 15:25:57 +02:00
id: fileActionComponent
2018-06-16 15:07:46 +02:00
Kirigami.Action {
property string filePath
text: filePath.substring(filePath.lastIndexOf('/') + 1)
onTriggered: nativeInterface.load(filePath)
}
}
2018-09-16 19:16:12 +02:00
Component {
id: clearRecentFilesActionComponent
Kirigami.Action {
text: qsTr("Clear recently opened files")
iconName: "edit-clear"
onTriggered: {
nativeInterface.clearRecentFiles()
leftMenu.resetMenu()
}
}
}
Component {
id: entriesComponent
EntriesPage {
main: root
}
}
2018-09-09 01:49:32 +02:00
Component {
id: fieldsComponent
FieldsPage {
main: root
}
}
2018-09-10 20:43:58 +02:00
Shortcut {
sequence: "Ctrl+M"
onActivated: leftMenu.visible = !leftMenu.visible
}
function initStack() {
var entryModel = nativeInterface.hasEntryFilter ? nativeInterface.entryFilterModel : nativeInterface.entryModel
var rootIndex = entryModel.index(0, 0)
pushStackEntry(entryModel, rootIndex)
}
2018-06-14 23:46:18 +02:00
function clearStack() {
pageStack.pop(lastEntriesPage = root.pageStack.initialPage,
Controls.StackView.Immediate)
2018-06-14 23:46:18 +02:00
}
function pushStackEntry(entryModel, rootIndex) {
pageStack.push(lastEntriesPage = entriesComponent.createObject(root, {
"entryModel": entryModel,
"rootIndex": rootIndex
}))
2018-06-14 23:46:18 +02:00
}
2018-06-16 15:07:46 +02:00
2018-09-09 01:49:32 +02:00
function pushAccountEdit() {
// lazy-initialize fieldsPage
if (!fieldsPage) {
fieldsPage = fieldsComponent.createObject(root)
}
// remove fieldsPage if already shown to prevent warning
if (pageStack.get(pageStack.depth - 1) === fieldsPage) {
pageStack.pop(lastEntriesPage)
}
pageStack.push(fieldsPage)
2018-09-09 01:49:32 +02:00
}
2018-06-16 15:25:57 +02:00
function createFileActions(files) {
return files.map(function (filePath) {
return this.createObject(root, {
2018-09-04 00:52:43 +02:00
"filePath": filePath
2018-06-16 15:25:57 +02:00
})
}, fileActionComponent)
2018-06-16 15:07:46 +02:00
}
2018-09-16 19:16:12 +02:00
function createRecentlyOpenedActions(files) {
var actions = createFileActions(files)
actions.push(clearRecentFilesActionComponent.createObject(root))
return actions
}
2015-04-22 19:30:09 +02:00
}