passwordmanager/qml/main.qml

472 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.Controls.Material
import QtQuick.Controls.Universal
import QtQuick.Layouts 1.2
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
Material.theme: nativeInterface.darkModeEnabled ? Material.Dark : Material.Light
Universal.theme: nativeInterface.darkModeEnabled ? Universal.Dark : Universal.Light
globalDrawer: Kirigami.GlobalDrawer {
id: leftMenu
property bool showNoPasswordWarning: nativeInterface.fileOpen
&& !nativeInterface.passwordSet
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")
2023-03-04 17:27:38 +01:00
icon.name: "document-new"
onTriggered: fileDialog.createNew()
2018-09-10 20:43:58 +02:00
shortcut: StandardKey.New
},
Kirigami.Action {
text: qsTr("Open existing file")
2023-03-04 17:27:38 +01:00
icon.name: "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 ...")
2023-03-04 17:27:38 +01:00
icon.name: "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
2023-03-04 17:27:38 +01:00
icon.name: "document-save"
onTriggered: nativeInterface.save()
2018-09-10 20:43:58 +02:00
shortcut: StandardKey.Save
},
2019-06-24 18:51:47 +02:00
Kirigami.Action {
text: qsTr("Save as")
enabled: nativeInterface.fileOpen
2023-03-04 17:27:38 +01:00
icon.name: "document-save-as"
2019-06-24 18:51:47 +02:00
onTriggered: fileDialog.saveAs()
shortcut: StandardKey.SaveAs
},
Kirigami.Action {
2018-09-10 19:57:58 +02:00
text: nativeInterface.passwordSet ? qsTr("Change password") : qsTr(
"Add password")
enabled: nativeInterface.fileOpen
2023-03-04 17:27:38 +01:00
icon.name: "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
2023-03-04 17:27:38 +01:00
icon.name: "document-properties"
2018-12-21 01:14:41 +01:00
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
2023-03-04 17:27:38 +01:00
icon.name: "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
2023-03-04 17:27:38 +01:00
icon.name: "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
2023-03-04 17:27:38 +01:00
icon.name: "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
2023-03-04 17:27:38 +01:00
icon.name: "edit-redo"
shortcut: StandardKey.Redo
onTriggered: nativeInterface.redo()
},
Kirigami.Action {
text: qsTr("Close file")
enabled: nativeInterface.fileOpen
2023-03-04 17:27:38 +01:00
icon.name: "document-close"
2018-09-10 20:43:58 +02:00
shortcut: StandardKey.Close
onTriggered: nativeInterface.close()
},
Kirigami.Action {
separator: true
},
Kirigami.Action {
text: qsTr("About")
icon.name: "help-about"
shortcut: "Ctrl+?"
onTriggered: {
leftMenu.resetMenu()
aboutDialog.open()
}
2015-04-22 19:30:09 +02:00
}
]
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")
contentItem: Controls.TextArea {
2018-12-21 01:14:41 +01:00
id: fileSummaryLabel
readOnly: true
2018-12-21 01:14:41 +01:00
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
}
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
}
}
contentItem: 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
function onEntryFilterChanged(newFilter) {
if (filterTextField.text !== newFilter) {
filterTextField.text = newFilter
}
}
function onFileError(errorMessage, retryAction) {
2019-06-24 18:51:47 +02:00
var retryMethod = null
if (retryAction === "load" || retryAction === "save") {
retryMethod = retryAction
}
if (retryMethod) {
showPassiveNotification(errorMessage)
} else {
showPassiveNotification(errorMessage, 2500, qsTr("Retry"),
function () {
2019-06-24 18:51:47 +02:00
nativeInterface[retryMethod]()
})
}
2018-06-14 23:46:18 +02:00
}
function onSettingsError(errorMessage) {
showPassiveNotification(errorMessage)
}
function onPasswordRequired(filePath) {
2018-06-14 23:46:18 +02:00
enterPasswordDialog.askForExistingPassword(
qsTr("Password required to open %1").arg(
nativeInterface.filePath))
leftMenu.resetMenu()
}
function onFileOpenChanged(fileOpen) {
2018-06-14 23:46:18 +02:00
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
}
function onFileSaved() {
2018-06-14 23:46:18 +02:00
showPassiveNotification(qsTr("%1 saved").arg(
nativeInterface.fileName))
}
function onNewNotification(message) {
2018-09-04 00:52:43 +02:00
showPassiveNotification(message)
}
function onCurrentAccountChanged() {
// remove the fields page if the current account has been removed
if (!nativeInterface.hasCurrentAccount) {
pageStack.pop(lastEntriesPage)
}
}
function onEntryAboutToBeRemoved(removedIndex) {
// 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
}
}
}
function onHasEntryFilterChanged(hasEntryFilter) {
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)
2019-06-24 18:51:47 +02:00
onTriggered: {
nativeInterface.clear()
nativeInterface.filePath = filePath
nativeInterface.load()
}
2018-06-16 15:07:46 +02:00
}
}
2018-09-16 19:16:12 +02:00
Component {
id: clearRecentFilesActionComponent
Kirigami.Action {
text: qsTr("Clear recently opened files")
2023-03-04 17:27:38 +01:00
icon.name: "edit-clear"
2018-09-16 19:16:12 +02:00
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
}