diff --git a/CMakeLists.txt b/CMakeLists.txt index 0d5fa81..38a08ec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -61,12 +61,11 @@ set(WIDGETS_UI_FILES ) set(QML_HEADER_FILES - quickgui/applicationinfo.h - quickgui/applicationpaths.h + quickgui/controller.h quickgui/initiatequick.h ) set(QML_SRC_FILES - quickgui/applicationinfo.cpp + quickgui/controller.cpp quickgui/initiatequick.cpp resources/icons.qrc resources/qml.qrc diff --git a/qml/BasicDialog.qml b/qml/BasicDialog.qml new file mode 100644 index 0000000..025f29a --- /dev/null +++ b/qml/BasicDialog.qml @@ -0,0 +1,12 @@ +import QtQuick 2.7 +import QtQuick.Controls 2.1 as Controls +import org.kde.kirigami 2.4 as Kirigami + +Controls.Dialog { + modal: true + focus: true + parent: applicationWindow().overlay + x: (parent.width - width) / 2 + y: (parent.height - height) / 2 + width: Math.min(parent.width, Kirigami.Units.gridUnit * 30) +} diff --git a/qml/EntriesPage.qml b/qml/EntriesPage.qml new file mode 100644 index 0000000..a72f880 --- /dev/null +++ b/qml/EntriesPage.qml @@ -0,0 +1,146 @@ +import QtQuick 2.4 +import QtQuick.Layouts 1.2 +import QtQml.Models 2.2 +import QtQuick.Controls 2.0 as Controls +import org.kde.kirigami 2.4 as Kirigami + +Kirigami.ScrollablePage { + id: page + + property alias model: delegateModel.model + property alias rootIndex: delegateModel.rootIndex + + Layout.fillWidth: true + title: "?" + actions { + main: Kirigami.Action { + iconName: "list-add" + text: qsTr("Add account") + onTriggered: { + model.setInsertTypeToAccount() + model.insertRows(model.rowCount(rootIndex), 1, rootIndex) + } + } + left: Kirigami.Action { + iconName: "edit-paste" + text: qsTr("Paste account") + onTriggered: { + + + // TODO + } + } + right: Kirigami.Action { + iconName: "folder-add" + text: qsTr("Add category") + onTriggered: { + model.setInsertTypeToNode() + model.insertRows(model.rowCount(rootIndex), 1, rootIndex) + } + } + } + onBackRequested: { + if (fieldsSheet.sheetOpen) { + event.accepted = true + fieldsSheet.close() + } + } + background: Rectangle { + color: Kirigami.Theme.backgroundColor + } + + // "sheet" to display field model + Kirigami.OverlaySheet { + id: fieldsSheet + parent: applicationWindow().overlay + header: Kirigami.Heading { + text: qsTr("Edit account ") + nativeInterface.currentAccountName + } + ListView { + id: fieldsListView + implicitWidth: Kirigami.Units.gridUnit * 30 + model: nativeInterface.fieldModel + delegate: RowLayout { + Controls.TextField { + text: key ? key : "" + onEditingFinished: fieldsListView.model.setData( + fieldsListView.model.index(index, + 0), text) + } + Controls.TextField { + text: value ? value : "" + echoMode: isPassword ? TextInput.PasswordEchoOnEdit : TextInput.Normal + onEditingFinished: fieldsListView.model.setData( + fieldsListView.model.index(index, + 1), text) + } + } + } + } + + // list view to display one hierarchy level of entry model + ListView { + id: listView + model: DelegateModel { + id: delegateModel + + function isNode(rowNumber) { + return model.isNode(model.index(rowNumber, 0, rootIndex)) + } + + function handleEntryClicked(rowNumber, entryName) { + var modelIndex = model.index(rowNumber, 0, rootIndex) + if (model.isNode(modelIndex)) { + root.pushStackEntry(model, modelIndex) + } else { + nativeInterface.currentAccountIndex = modelIndex + fieldsSheet.open() + } + } + + delegate: Kirigami.SwipeListItem { + id: listItem + contentItem: RowLayout { + Kirigami.Icon { + width: Kirigami.Units.iconSizes.smallMedium + height: Kirigami.Units.iconSizes.smallMedium + Layout.fillHeight: true + source: delegateModel.isNode( + index) ? "folder-symbolic" : "story-editor" + } + Controls.Label { + Layout.fillWidth: true + Layout.fillHeight: true + height: Math.max(implicitHeight, + Kirigami.Units.iconSizes.smallMedium) + anchors.verticalCenter: parent.verticalCenter + text: name + + MouseArea { + anchors.fill: parent + onClicked: delegateModel.handleEntryClicked(index, + name) + } + } + } + actions: [ + Kirigami.Action { + iconName: "edit-cut" + text: qsTr("Cut") + onTriggered: showPassiveNotification(text + " " + name) + }, + Kirigami.Action { + iconName: "edit-delete" + text: qsTr("Delete") + onTriggered: showPassiveNotification(text + " " + name) + }, + Kirigami.Action { + iconName: "edit-rename" + text: qsTr("Rename") + onTriggered: showPassiveNotification(text + " " + name) + } + ] + } + } + } +} diff --git a/qml/PasswordDialog.qml b/qml/PasswordDialog.qml new file mode 100644 index 0000000..6e6be2c --- /dev/null +++ b/qml/PasswordDialog.qml @@ -0,0 +1,89 @@ +import QtQuick 2.7 +import QtQuick.Controls 2.1 as Controls +import QtQuick.Layouts 1.2 +import org.kde.kirigami 2.4 as Kirigami + +BasicDialog { + id: passwordDialog + property alias instruction: instructionLabel.text + property alias password: passwordTextField.text + property bool newPassword: false + readonly property bool canAccept: !newPassword + || showCharactersCheckBox.checked + || passwordTextField.text === repeatPasswordTextField.text + + standardButtons: canAccept ? Controls.Dialog.Ok + | Controls.Dialog.Cancel : Controls.Dialog.Cancel + title: qsTr("Enter password") + + ColumnLayout { + Controls.Label { + id: instructionLabel + Layout.preferredWidth: passwordDialog.availableWidth + wrapMode: Controls.Label.Wrap + } + + Controls.TextField { + id: passwordTextField + Layout.preferredWidth: passwordDialog.availableWidth + echoMode: showCharactersCheckBox.checked ? TextInput.Normal : TextInput.Password + placeholderText: qsTr("enter password here, leave empty for no encryption") + background: Rectangle { + border.color: "#5d5e6d" + } + } + Controls.TextField { + id: repeatPasswordTextField + Layout.preferredWidth: passwordDialog.availableWidth + visible: passwordDialog.newPassword + && !showCharactersCheckBox.checked + echoMode: TextInput.Password + placeholderText: qsTr("repeat password") + background: Rectangle { + border.color: passwordDialog.canAccept ? "#089900" : "#ff0000" + } + } + Controls.CheckBox { + id: showCharactersCheckBox + text: qsTr("Show characters") + checked: false + } + } + + onAccepted: { + nativeInterface.password = password + if (newPassword) { + showPassiveNotification( + qsTr("The new password will be used when saving next time.")) + } else { + nativeInterface.load() + } + } + + onRejected: { + if (newPassword) { + showPassiveNotification( + qsTr("You aborted. The password has not been altered.")) + } + } + + function clear() { + passwordTextField.text = "" + repeatPasswordTextField.text = "" + } + + function askForPassword(instruction, newPassword) { + this.newPassword = newPassword + this.instruction = instruction + this.clear() + this.open() + } + + function askForExistingPassword(instruction) { + this.askForPassword(instruction, false) + } + + function askForNewPassword(instruction) { + this.askForPassword(instruction, true) + } +} diff --git a/qml/banner.png b/qml/banner.png new file mode 100644 index 0000000..fa628c3 Binary files /dev/null and b/qml/banner.png differ diff --git a/qml/main.qml b/qml/main.qml index 17f9a05..4a4914b 100644 --- a/qml/main.qml +++ b/qml/main.qml @@ -1,176 +1,154 @@ -import QtQuick 2.2 -import QtQuick.Controls 1.2 -import QtQuick.Controls.Styles 1.2 -import martchus.passwordmanager 2.0 +import QtQuick 2.7 +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 -import "./pages" as Pages -import "./touch" as Touch - -ApplicationWindow { +Kirigami.ApplicationWindow { id: root - width: 700 - height: 800 - title: qsTr("Password Manager") - visible: true - property string statusBarMessage - - property Component startPage: Pages.StartPage { - onUpdateStatusBar: statusBarMessage = message - onNextPage: if (!isLocked) { - pageView.push(accountsPage) - clearSearchBox() - } + function clearStack() { + pageStack.pop(root.pageStack.initialPage, Controls.StackView.Immediate) } - property Component accountsPage: Pages.AccountsPage { - onUpdateStatusBar: statusBarMessage = message - onNextPage: if (!isLocked) { - pageView.push(fieldsPage) - } - onPreviousPage: { - pageView.pop() + + function pushStackEntry(entryModel, rootIndex) { + var title = entryModel.data(rootIndex) + var entriesComponent = Qt.createComponent("EntriesPage.qml") + if (entriesComponent.status !== Component.Ready) { + var errorMessage = "Unable to load EntriesPage.qml: " + entriesComponent.errorString() + showPassiveNotification(errorMessage) + console.error(errorMessage) + return } + var entriesPage = entriesComponent.createObject(root, { + model: entryModel, + rootIndex: rootIndex, + title: title + }) + pageStack.push(entriesPage) } - property Component fieldsPage: Pages.FieldsPage { - onUpdateStatusBar: statusBarMessage = message - onNextPage: if (!isLocked) { - pageView.pop() - } - onPreviousPage: { - pageView.pop() + + PasswordDialog { + id: enterPasswordDialog + } + + FileDialog { + id: fileDialog + title: selectExisting ? qsTr("Select an existing file") : qsTr( + "Select path for new file") + onAccepted: { + if (fileUrls.length < 1) { + return + } + nativeInterface.filePath = fileUrls[0] + if (selectExisting) { + nativeInterface.load() + } else { + nativeInterface.create() + } + } + onRejected: { + showPassiveNotification("Canceled file selection") + } + + function openExisting() { + this.selectExisting = true + this.open() + } + function createNew() { + this.selectExisting = false + this.open() } } - StackView { - id: pageView - anchors.fill: parent - focus: true - Keys.onReleased: { - if (event.key === Qt.Key_Back || - (event.key === Qt.Key_Left && (event.modifiers & Qt.AltModifier))) { - if (pageView.depth > 1) { - event.accepted = true - if (!currentItem.isLocked) - currentItem.previousPage() - } else { - if (!currentItem.hasNoSearchText) { - event.accepted = true - currentItem.clearSearchBox() + Connections { + target: nativeInterface + onFileError: { + showPassiveNotification(errorMessage) + } + onPasswordRequired: { + enterPasswordDialog.askForExistingPassword( + qsTr("Password required to open ") + filePath) + leftMenu.resetMenu() + } + onFileOpenChanged: { + clearStack() + if (!fileOpen) { + return + } + var entryModel = nativeInterface.entryModel + var rootIndex = entryModel.index(0, 0) + pushStackEntry(entryModel, rootIndex) + } + } + + header: Kirigami.ApplicationHeader { + } + globalDrawer: Kirigami.GlobalDrawer { + id: leftMenu + title: qsTr("Password manager") + titleIcon: "passwordmanager" + bannerImageSource: "banner.png" + actions: [ + Kirigami.Action { + text: qsTr("Create new file") + iconName: "document-new" + onTriggered: fileDialog.createNew() + }, + Kirigami.Action { + text: qsTr("Open existing file") + iconName: "document-open" + onTriggered: fileDialog.openExisting() + }, + Kirigami.Action { + text: "Save modifications" + enabled: nativeInterface.fileOpen + iconName: "document-save" + onTriggered: nativeInterface.save() + }, + Kirigami.Action { + text: "Change password" + enabled: nativeInterface.fileOpen + iconName: "dialog-password" + onTriggered: enterPasswordDialog.askForNewPassword( + "Change password for " + nativeInterface.filePath) + }, + Kirigami.Action { + text: "Close file" + enabled: nativeInterface.fileOpen + iconName: "document-close" + onTriggered: nativeInterface.close() + } + ] + } + contextDrawer: Kirigami.ContextDrawer { + id: contextDrawer + } + pageStack.initialPage: mainPageComponent + + // main app content + Component { + id: mainPageComponent + + RowLayout { + spacing: 5 + + Controls.Label { + text: { + if (nativeInterface.fileOpen) { + return qsTr("The file %1 has been opened.").arg( + nativeInterface.filePath) + } else { + return qsTr("No file has been opened.") } } } } - initialItem: startPage - delegate: StackViewDelegate { - pushTransition: StackViewTransition { - function transitionFinished(properties) { - properties.exitItem.opacity = 1 - } - PropertyAnimation { - target: enterItem - property: "x" - from: target.width - to: 0 - duration: 500 - easing.type: Easing.OutSine - } - PropertyAnimation { - target: exitItem - property: "x" - from: 0 - to: -target.width - duration: 500 - easing.type: Easing.OutSine - } - } - popTransition: StackViewTransition { - function transitionFinished(properties) - { - properties.exitItem.opacity = 1 - } - PropertyAnimation { - target: enterItem - property: "x" - from: -target.width - to: 0 - duration: 500 - easing.type: Easing.OutSine - } - PropertyAnimation { - target: exitItem - property: "x" - from: 0 - to: target.width - duration: 500 - easing.type: Easing.OutSine - } - } - property Component replaceTransition: pushTransition - } } - statusBar: StatusBar { - id: statusbar - width: parent.width - opacity: label.text !== "" ? 1 : 0 - property real statusBarHeight: 65 * ApplicationInfo.ratio - height: label.text !== "" ? statusBarHeight : 0 - Behavior on height { NumberAnimation {easing.type: Easing.OutSine}} - Behavior on opacity { NumberAnimation {}} - style: StatusBarStyle { - padding { left: 0; right: 0 ; top: 0 ; bottom: 0} - property Component background: Rectangle { - implicitHeight: 65 * ApplicationInfo.ratio - implicitWidth: root.width - color: ApplicationInfo.colors.smokeGray - Rectangle { - width: parent.width - height: 1 - color: Qt.darker(parent.color, 1.5) - } - Rectangle { - y: 1 - width: parent.width - height: 1 - color: "white" - } - } - } - Touch.TouchLabel { - id: label - y: 32 * ApplicationInfo.ratio - height / 2 - width: parent.width // The text will only wrap if an explicit width has been set - text: statusBarMessage - textFormat: Text.RichText - onLinkActivated: Qt.openUrlExternally(link) - wrapMode: Text.Wrap - pixelSize: 18 - letterSpacing: -0.15 - color: ApplicationInfo.colors.mediumGray - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignHCenter - function decreaseFontSizeOnNarrowScreen() { - if (label.implicitHeight > statusbar.statusBarHeight) { - pixelSize = Math.floor(pixelSize * statusbar.statusBarHeight / label.implicitHeight) - } - } - onTextChanged: { - if (text === "") { - pixelSize = 18 - } else { - decreaseFontSizeOnNarrowScreen() - } - } - onWidthChanged: decreaseFontSizeOnNarrowScreen() - } - } - - menuBar: MenuBar { - Menu { - title: qsTr("Application") - MenuItem { - text: qsTr("Exit") - onTriggered: Qt.quit(); - } + Component.onCompleted: { + // load file if one has been specified via CLI argument + if (nativeInterface.filePath.length) { + nativeInterface.load() } } } diff --git a/qml/pages/AccountsPage.qml b/qml/pages/AccountsPage.qml deleted file mode 100644 index d52fd95..0000000 --- a/qml/pages/AccountsPage.qml +++ /dev/null @@ -1,13 +0,0 @@ -import QtQuick 2.2 -import QtQuick.Controls 1.2 -import QtQuick.Controls.Styles 1.2 -import martchus.passwordmanager 2.0 - -BasicPage { - id: accountsPage - title1: qsTr("Accounts") - - pageComponent: Item { - - } -} diff --git a/qml/pages/BasicPage.qml b/qml/pages/BasicPage.qml deleted file mode 100644 index 1657398..0000000 --- a/qml/pages/BasicPage.qml +++ /dev/null @@ -1,182 +0,0 @@ -import QtQuick 2.2 -import QtQuick.Controls 1.2 -import QtQuick.Controls.Styles 1.2 -import QtQuick.Layouts 1.1 -import martchus.passwordmanager 2.0 - -import "../touch" as Touch - -Item { - id: page - signal updateStatusBar(string message) - signal nextPage - signal previousPage - signal clearSearchBox - - property Component pageComponent - property bool isLocked: Stack.status !== Stack.Active - property string title1 - property string title2 - property string title3 - - property alias searchText: searchField.text - property alias hasNoSearchText: searchField.isEmpty - signal searchBoxReturn - property string statusBarMessageDefault - - property alias topRect: topRect - property color topRectColor: ApplicationInfo.colors.yetAnotherBlue - - property bool searchFieldVisisble: false - - Binding { - target: ApplicationInfo - property: "isPortraitMode" - value: page.height > page.width - when: !ApplicationInfo.isMobile - } - - Binding { - target: ApplicationInfo - property: "applicationWidth" - value: page.width - } - - Rectangle { - id: topRect - z: 2 // so flickable doesn't draw on top - anchors.top: parent.top - height: 80 * ApplicationInfo.ratio - width: parent.width - color: page.Stack.index !== 0 && mouseBack.pressed ? - Qt.lighter(topRectColor, 1.2) : topRectColor - Rectangle { - color: Qt.lighter(parent.color, 1.2) - height: 1 - anchors.bottom: parent.bottom - anchors.bottomMargin: 1 - width: parent.width - } - Rectangle { - z: 2 // so flickable doesn't draw on top - height: 1 - width: parent.width - color: Qt.darker(ApplicationInfo.colors.blue, 1.6) - anchors.bottom: parent.bottom - } - - RowLayout { - id: titleRow - anchors.left: parent.left - anchors.right: parent.right - spacing: 0 - anchors.verticalCenter: parent.verticalCenter - Separator { - Layout.fillWidth: false - Layout.preferredWidth: ApplicationInfo.hMargin - } - Image { - source: ApplicationInfo.imagePath("backarrow.png") - Layout.preferredWidth: 22 * ApplicationInfo.ratio - Layout.preferredHeight: 35 * ApplicationInfo.ratio - visible: page.Stack.index > 0 - Accessible.role: Accessible.Button - Accessible.name: qsTr("Back") - function accessiblePressAction () { backPressed() } - } - Rectangle { - opacity: 0 - Layout.preferredWidth: 20 * ApplicationInfo.ratio - Layout.fillHeight: true - visible: page.Stack.index > 0 - } - Touch.TouchLabel { - id: t1 - text: title1 + " " - color: ApplicationInfo.colors.white - pixelSize: 30 - font.weight: Font.Bold - Layout.maximumWidth: ApplicationInfo.applicationWidth - t3.implicitWidth - 2 * ApplicationInfo.hMargin - 5 * ApplicationInfo.ratio - 42 * ApplicationInfo.ratio - Layout.alignment: Qt.AlignBaseline - } - Touch.TouchLabel { - text: "- " + title2 - color: ApplicationInfo.colors.white - visible: title2 !== "" - pixelSize: 22 - letterSpacing: -0.15 - Layout.alignment: Qt.AlignBaseline - Layout.maximumWidth: freeSpace > implicitWidth ? freeSpace : 0 - property real freeSpace: ApplicationInfo.applicationWidth - t1.width - t3.implicitWidth - 2 * ApplicationInfo.hMargin - 5 * ApplicationInfo.ratio - 42 * ApplicationInfo.ratio - } - Item { - Layout.fillWidth: true - height: 0 - } - Touch.TouchLabel { - id: t3 - text: title3 - color: ApplicationInfo.colors.white - visible: title3 !== "" - pixelSize: 22 - letterSpacing: -0.15 - Layout.alignment: Qt.AlignBaseline - } - Separator { - Layout.fillWidth: false - Layout.preferredWidth: ApplicationInfo.hMargin - } - } - Rectangle { - width: parent.width - height: 5 - anchors.top: parent.bottom - gradient: Gradient { - GradientStop {position: 0 ; color: "#40000000"} - GradientStop {position: 1 ; color: "#00000000"} - } - } - MouseArea { - id: mouseBack - anchors.fill: parent - onClicked: backPressed() - } - } - - function backPressed() { - if (!isLocked) page.previousPage() - } - - Touch.TouchTextField { - id: searchField - z: 2 - visible: searchFieldVisisble - anchors.right: topRect.right - anchors.top: topRect.top - anchors.bottom: topRect.bottom - width: ApplicationInfo.isPortraitMode ? - parent.width - t1.implicitWidth - ApplicationInfo.ratio * 50 : - parent.width/2.5 - anchors.leftMargin: topRect.width/2 - anchors.rightMargin: 20 * ApplicationInfo.ratio - anchors.margins: 12 * ApplicationInfo.ratio - - placeholderText: qsTr("filter") - Layout.fillWidth: true - Keys.onReturnPressed: page.searchBoxReturn() - Keys.onEnterPressed: page.searchBoxReturn() - onClearButtonClicked: page.clearSearchBox() - } - - Loader { - sourceComponent: pageComponent - anchors.top: topRect.bottom - anchors.bottom: parent.bottom - width: parent.width - Rectangle { - z: -1 - anchors.fill: parent - color: ApplicationInfo.colors.white - } - } -} diff --git a/qml/pages/FieldsPage.qml b/qml/pages/FieldsPage.qml deleted file mode 100644 index 66dfb90..0000000 --- a/qml/pages/FieldsPage.qml +++ /dev/null @@ -1,14 +0,0 @@ -import QtQuick 2.2 -import QtQuick.Controls 1.2 -import QtQuick.Controls.Styles 1.2 -import martchus.passwordmanager 2.0 - -BasicPage { - id : fieldsPage - title1: ApplicationInfo.currentAccountName - title3: qsTr("Fields") - - pageComponent: Item { - - } -} diff --git a/qml/pages/Separator.qml b/qml/pages/Separator.qml deleted file mode 100644 index a0c4838..0000000 --- a/qml/pages/Separator.qml +++ /dev/null @@ -1,12 +0,0 @@ -import QtQuick 2.1 -import QtQuick.Layouts 1.0 -import martchus.passwordmanager 2.0 - -Item { - implicitHeight: ApplicationInfo.hMargin - implicitWidth: ApplicationInfo.hMargin - Layout.minimumHeight: 0 - Layout.minimumWidth: 0 - Layout.fillHeight: true - Layout.fillWidth: true -} diff --git a/qml/pages/StartPage.qml b/qml/pages/StartPage.qml deleted file mode 100644 index 3e106cd..0000000 --- a/qml/pages/StartPage.qml +++ /dev/null @@ -1,66 +0,0 @@ -import QtQuick 2.2 -import QtQuick.Layouts 1.1 -import QtQuick.Controls 1.2 -import QtQuick.Controls.Styles 1.2 -import QtQuick.Dialogs 1.2 -import martchus.passwordmanager 2.0 - -import "../touch" as Touch - -BasicPage { - id: startPage - title1: qsTr("Password Manager") - - searchFieldVisisble: true - - FileDialog { - id: fileDialog - title: qsTr("Select a password file") - selectExisting: true - onAccepted: { - ApplicationInfo.currentFile = fileUrl - nextPage() - } - } - - ColumnLayout { - anchors.top: topRect.bottom - anchors.left: parent.left - anchors.right: parent.right - anchors.margins: 5 - - Touch.TouchButton { - text: qsTr("Create a new password file") - Layout.fillWidth: true - } - - Touch.TouchButton { - text: qsTr("Open an existing password file") - onClicked: { - ApplicationInfo.currentFile = "./test" - nextPage() // fileDialog.visible = true - } - Layout.fillWidth: true - } - - Touch.TouchLabel { - text: qsTr("Recently opened files") - } - - Touch.TouchLabel { - text: qsTr("Test 1") - //Layout.fillWidth: true - color: ApplicationInfo.colors.lightGray - } - Touch.TouchLabel { - text: qsTr("Test 2") - //Layout.fillWidth: true - color: ApplicationInfo.colors.lightGray - } - Touch.TouchLabel { - text: qsTr("Test 3") - //Layout.fillWidth: true - color: ApplicationInfo.colors.lightGray - } - } -} diff --git a/qml/touch/ListViewDelegate.qml b/qml/touch/ListViewDelegate.qml deleted file mode 100644 index 6acef66..0000000 --- a/qml/touch/ListViewDelegate.qml +++ /dev/null @@ -1,52 +0,0 @@ -import QtQuick 2.1 -import QtQuick.Layouts 1.0 -import martchus.passwordmanager 2.0 - -Rectangle { - id: rect - height: ApplicationInfo.constants.rowDelegateHeight - width: parent.width - signal clicked - signal deleteEntry - - color: mouseNext.pressed ? ApplicationInfo.colors.smokeGray : ApplicationInfo.colors.white - - GridLayout { - id: _grid - anchors.fill: parent - flow: Qt.LeftToRight - rowSpacing: 4 * ApplicationInfo.ratio - columnSpacing: 0 - columns: 2 - Rectangle { - Layout.preferredWidth: ApplicationInfo.hMargin - Layout.fillHeight: true - opacity: 0 - } - Loader { - // sourceComponent: - Layout.fillHeight: true - Layout.fillWidth: true - } - Rectangle { - id: separator - Layout.fillWidth: true - Layout.fillHeight: true - Layout.columnSpan: 2 - } - } - Rectangle { - z: 1 - height: 1 - anchors.bottom: parent.bottom - width: parent.width - color: ApplicationInfo.colors.paleGray - } - MouseArea { - id: mouseNext - anchors.left: parent.left - width: parent.width - 80 * ApplicationInfo.ratio - ApplicationInfo.hMargin - height: parent.height - onClicked: rect.clicked() - } -} diff --git a/qml/touch/TouchButton.qml b/qml/touch/TouchButton.qml deleted file mode 100644 index 206d5fa..0000000 --- a/qml/touch/TouchButton.qml +++ /dev/null @@ -1,43 +0,0 @@ -import QtQuick 2.1 -import QtQuick.Controls 1.1 -import QtQuick.Controls.Styles 1.1 -import QtQuick.Layouts 1.0 -import martchus.passwordmanager 2.0 - -Button { - id: button - property int pixelSize: 34 - property real letterSpacing: -0.25 - property string text: "" - - style: ButtonStyle { - label: Label { - id: label - font.family: "Open Sans" - font.pixelSize: pixelSize * ApplicationInfo.ratio * 1.1 // increasing fonts - font.letterSpacing: letterSpacing * ApplicationInfo.ratio - color: ApplicationInfo.colors.doubleDarkGray - verticalAlignment: Text.AlignBottom - horizontalAlignment: Text.AlignLeft - elide: Text.ElideRight - linkColor: ApplicationInfo.colors.blue - renderType: ApplicationInfo.isMobile ? Text.QtRendering : Text.NativeRendering - text: button.text - - Text { - id: text - visible: false - font.family: label.font.family - font.pixelSize: label.font.pixelSize - font.letterSpacing: label.font.letterSpacing - wrapMode: label.wrapMode - elide: label.elide - text: label.text - } - } - } - - property int maximumWidth: (ApplicationInfo.constants.isMobile ? ApplicationInfo.applicationWidth : 1120) / 4 - Layout.minimumWidth: Math.min(Layout.maximumWidth, implicitWidth + 1) - -} diff --git a/qml/touch/TouchLabel.qml b/qml/touch/TouchLabel.qml deleted file mode 100644 index f098f4a..0000000 --- a/qml/touch/TouchLabel.qml +++ /dev/null @@ -1,35 +0,0 @@ -import QtQuick 2.1 -import QtQuick.Controls 1.0 -import QtQuick.Layouts 1.0 -import martchus.passwordmanager 2.0 - -Label { - id: label - property int pixelSize: 34 - property real letterSpacing: -0.25 - font.family: "Open Sans" - font.pixelSize: pixelSize * ApplicationInfo.ratio * 1.1 // increasing fonts - font.letterSpacing: letterSpacing * ApplicationInfo.ratio - color: ApplicationInfo.colors.doubleDarkGray - verticalAlignment: Text.AlignBottom - horizontalAlignment: Text.AlignLeft - elide: Text.ElideRight - linkColor: ApplicationInfo.colors.blue - renderType: ApplicationInfo.isMobile ? Text.QtRendering : Text.NativeRendering - function expectedTextWidth(value) - { - text.text = value - return text.width - } - property int maximumWidth: (ApplicationInfo.constants.isMobile ? ApplicationInfo.applicationWidth : 1120) / 4 - Layout.minimumWidth: Math.min(Layout.maximumWidth, implicitWidth + 1) - Text { - id: text - visible: false - font.family: label.font.family - font.pixelSize: label.font.pixelSize - font.letterSpacing: label.font.letterSpacing - wrapMode: label.wrapMode - elide: label.elide - } -} diff --git a/qml/touch/TouchScrollView.qml b/qml/touch/TouchScrollView.qml deleted file mode 100644 index 7979a8c..0000000 --- a/qml/touch/TouchScrollView.qml +++ /dev/null @@ -1,18 +0,0 @@ -import QtQuick 2.1 -import QtQuick.Controls 1.0 -import QtQuick.Controls.Styles 1.0 -import martchus.passwordmanager 2.0 - -ScrollView { - frameVisible: false - style: ScrollViewStyle { - property int handleWidth: 20 * ApplicationInfo.ratio - transientScrollBars: true - padding{ top: 4 ; bottom: 4 ; right: 4} - property bool hovered: false - } - Rectangle { - anchors.fill: parent - color: ApplicationInfo.colors.white - } -} diff --git a/qml/touch/TouchSlider.qml b/qml/touch/TouchSlider.qml deleted file mode 100644 index ed5d10b..0000000 --- a/qml/touch/TouchSlider.qml +++ /dev/null @@ -1,42 +0,0 @@ -import QtQuick 2.1 -import QtQuick.Controls 1.0 -import QtQuick.Controls.Styles 1.0 -import martchus.passwordmanager 2.0 - -Slider { - id: slider - property real sliderHandleHeight: 0. - property real sliderHandleWidth: 0. - implicitHeight: sliderHandleHeight + ApplicationInfo.ratio * 25 - style: SliderStyle { - groove: Rectangle { - Rectangle { - id: beforeHandle - width: styleData.handlePosition - height: 20 * ApplicationInfo.ratio - color: ApplicationInfo.colors.blue - radius: 90 - z: -1 - } - Rectangle { - id: afterHandle - anchors.left: beforeHandle.right - anchors.right: parent.right - height: 20 * ApplicationInfo.ratio - color: ApplicationInfo.colors.darkGray - radius: 90 - z: -1 - } - } - handle: Item { - width: sliderHandleWidth - height: sliderHandleHeight - Image { - anchors.centerIn: parent - source: ApplicationInfo.imagePath(control.pressed ? "Pointer_pressed.png" : "Pointer.png") - width: sliderHandleWidth + 16 * ApplicationInfo.ratio - height: sliderHandleHeight + 16 * ApplicationInfo.ratio - } - } - } -} diff --git a/qml/touch/TouchTextField.qml b/qml/touch/TouchTextField.qml deleted file mode 100644 index d88a91c..0000000 --- a/qml/touch/TouchTextField.qml +++ /dev/null @@ -1,75 +0,0 @@ -import QtQuick 2.1 -import QtQuick.Controls 1.0 -import QtQuick.Controls.Styles 1.0 -import martchus.passwordmanager 2.0 - -TextField { - id: textfield - signal clearButtonClicked - implicitWidth: parent.width - property bool isEmpty: true - style: TextFieldStyle { - renderType: ApplicationInfo.isMobile ? Text.QtRendering : Text.NativeRendering - background: Rectangle { - radius: 8 - border.width: 1 - border.color: Qt.darker(ApplicationInfo.colors.blue, 1.6) - color: ApplicationInfo.colors.white - gradient: Gradient { - GradientStop { position: 0 ; color: "#ddd"} - GradientStop { position: 0.05 ; color: "#fff"} - } - implicitHeight: 60 * ApplicationInfo.ratio - opacity: 1 - } - padding.left : (12 + 50) * ApplicationInfo.ratio - padding.right: (12 + 50) * ApplicationInfo.ratio - font.pixelSize: 28 * ApplicationInfo.ratio - font.family: "Open Sans" - font.letterSpacing: -0.25 * ApplicationInfo.ratio - selectedTextColor : ApplicationInfo.colors.lightGray - selectionColor : ApplicationInfo.colors.darkBlue - textColor : ApplicationInfo.colors.mediumGray - } - - onClearButtonClicked: text = "" - - Item { - id: item - anchors.left: parent.left - anchors.top: parent.top - height: parent.height - width: parent.height - Image { - opacity: 0.9 - anchors.centerIn: item - height: iconSize - width: iconSize - source: ApplicationInfo.imagePath("magnifier.png") - property int iconSize: 50 * ApplicationInfo.ratio - } - } - - onTextChanged: isEmpty = (text === "") - inputMethodHints: Qt.ImhNoPredictiveText - MouseArea { - z: 2 - opacity: !textfield.isEmpty ? 1 : 0 - Behavior on opacity {NumberAnimation{}} - anchors.right: parent.right - anchors.rightMargin: 4 * ApplicationInfo.ratio - anchors.top: parent.top - height: parent.height - width: parent.height - Image { - anchors.centerIn: parent - source: ApplicationInfo.imagePath("clear.png") - property int iconSize: 40 * ApplicationInfo.ratio - opacity: parent.pressed ? 1 : 0.9 - width: iconSize - height: iconSize - } - onClicked: textfield.clearButtonClicked() - } -} - diff --git a/quickgui/applicationinfo.cpp b/quickgui/applicationinfo.cpp deleted file mode 100644 index 4bd5d4f..0000000 --- a/quickgui/applicationinfo.cpp +++ /dev/null @@ -1,93 +0,0 @@ -#include "./applicationinfo.h" - -#include - -#include -#include -#include -#include -#include -#include -#include - -using namespace Io; - -namespace QtGui { - -ApplicationInfo::ApplicationInfo() -{ - m_colors = new QQmlPropertyMap(this); - m_colors->insert(QLatin1String("white"), QVariant("#ffffff")); - m_colors->insert(QLatin1String("smokeGray"), QVariant("#eeeeee")); - m_colors->insert(QLatin1String("paleGray"), QVariant("#d7d6d5")); - m_colors->insert(QLatin1String("lightGray"), QVariant("#aeadac")); - m_colors->insert(QLatin1String("darkGray"), QVariant("#35322f")); - m_colors->insert(QLatin1String("mediumGray"), QVariant("#5d5b59")); - m_colors->insert(QLatin1String("doubleDarkGray"), QVariant("#1e1b18")); - m_colors->insert(QLatin1String("blue"), QVariant("#14aaff")); - m_colors->insert(QLatin1String("yetAnotherBlue"), QVariant("#428bca")); - m_colors->insert(QLatin1String("darkBlue"), QVariant("#14148c")); - m_colors->insert(QLatin1String("darkYellow"), QVariant("#dfdc00")); - m_colors->insert(QLatin1String("darkYellow"), QVariant("#eb881c")); - m_colors->insert(QLatin1String("almostBlack"), QVariant("#222222")); - - m_constants = new QQmlPropertyMap(this); - m_constants->insert(QLatin1String("isMobile"), QVariant(isMobile())); - - QRect rect = QGuiApplication::primaryScreen()->geometry(); - m_ratio = isMobile() ? qMin(qMax(rect.width(), rect.height()) / 1136., qMin(rect.width(), rect.height()) / 640.) : .5; - m_sliderHandleWidth = sizeWithRatio(70); - m_sliderHandleHeight = sizeWithRatio(87); - m_sliderGapWidth = sizeWithRatio(100); - m_isPortraitMode = isMobile() ? rect.height() > rect.width() : false; - m_hMargin = m_isPortraitMode ? 20 * ratio() : 50 * ratio(); - m_applicationWidth = isMobile() ? rect.width() : 1120; - - m_constants->insert(QLatin1String("rowDelegateHeight"), QVariant(sizeWithRatio(118))); - - m_fieldModel = new FieldModel(this); - - if (isMobile()) { - connect(QGuiApplication::primaryScreen(), &QScreen::orientationChanged, this, &ApplicationInfo::notifyPortraitMode); - } -} - -void ApplicationInfo::setApplicationWidth(const int newWidth) -{ - if (newWidth != m_applicationWidth) { - m_applicationWidth = newWidth; - emit applicationWidthChanged(); - } -} - -QString ApplicationInfo::imagePath(const QString image) -{ - return QStringLiteral("qrc:/qml/images/%1").arg(image); -} - -void ApplicationInfo::notifyPortraitMode(Qt::ScreenOrientation orientation) -{ - switch (orientation) { - case Qt::LandscapeOrientation: - case Qt::InvertedLandscapeOrientation: - setIsPortraitMode(false); - break; - case Qt::PortraitOrientation: - case Qt::InvertedPortraitOrientation: - setIsPortraitMode(true); - break; - default: - break; - } -} - -void ApplicationInfo::setIsPortraitMode(const bool newMode) -{ - if (m_isPortraitMode != newMode) { - m_isPortraitMode = newMode; - m_hMargin = m_isPortraitMode ? 20 * ratio() : 50 * ratio(); - emit portraitModeChanged(); - emit hMarginChanged(); - } -} -} // namespace QtGui diff --git a/quickgui/applicationinfo.h b/quickgui/applicationinfo.h deleted file mode 100644 index 9f923fc..0000000 --- a/quickgui/applicationinfo.h +++ /dev/null @@ -1,176 +0,0 @@ -#ifndef APPLICATIONINFO_H -#define APPLICATIONINFO_H - -#include "../model/fieldmodel.h" - -#include -#include - -namespace QtGui { - -class ApplicationInfo : public QObject { - Q_OBJECT - Q_PROPERTY(int applicationWidth READ applicationWidth WRITE setApplicationWidth NOTIFY applicationWidthChanged) - Q_PROPERTY(bool isMobile READ isMobile CONSTANT) - Q_PROPERTY(QObject *colors READ colors CONSTANT) - Q_PROPERTY(QObject *constants READ constants CONSTANT) - Q_PROPERTY(bool isPortraitMode READ isPortraitMode WRITE setIsPortraitMode NOTIFY portraitModeChanged) - Q_PROPERTY(const QString ¤fFile READ currentFile WRITE setCurrentFile NOTIFY currentFileChanged) - Q_PROPERTY(FieldModel *fieldModel READ fieldModel) - Q_PROPERTY(Io::AccountEntry *currentAccountEntry READ currentAccountEntry WRITE setCurrentAccountEntry) - Q_PROPERTY(QString currentAccountName READ currentAccountName) - Q_PROPERTY(qreal ratio READ ratio CONSTANT) - Q_PROPERTY(qreal hMargin READ hMargin NOTIFY hMarginChanged) - Q_PROPERTY(qreal sliderHandleWidth READ sliderHandleWidth CONSTANT) - Q_PROPERTY(qreal sliderHandleHeight READ sliderHandleHeight CONSTANT) - Q_PROPERTY(qreal sliderGapWidth READ sliderGapWidth CONSTANT) - -public: - ApplicationInfo(); - - static constexpr bool isMobile(); - QQmlPropertyMap *colors() const; - QQmlPropertyMap *constants() const; - - int applicationWidth() const; - void setApplicationWidth(const int newWidth); - - bool isPortraitMode() const; - void setIsPortraitMode(const bool newMode); - - const QString ¤tFile() const; - void setCurrentFile(const QString ¤tFile); - FieldModel *fieldModel(); - Io::AccountEntry *currentAccountEntry(); - QString currentAccountName() const; - void setCurrentAccountEntry(Io::AccountEntry *entry); - - qreal hMargin() const; - qreal ratio() const; - qreal sliderHandleHeight(); - qreal sliderGapWidth(); - qreal sliderHandleWidth(); - - Q_INVOKABLE QString imagePath(const QString image); - -protected Q_SLOTS: - void notifyPortraitMode(Qt::ScreenOrientation); - -protected: - qreal sizeWithRatio(const qreal height); - -Q_SIGNALS: - void currentFileChanged(); - void applicationWidthChanged(); - void portraitModeChanged(); - void hMarginChanged(); - -private: - int m_applicationWidth; - QQmlPropertyMap *m_colors; - QQmlPropertyMap *m_constants; - bool m_isPortraitMode; - QString m_currentFile; - FieldModel *m_fieldModel; - qreal m_ratio; - qreal m_hMargin; - qreal m_sliderHandleHeight, m_sliderHandleWidth, m_sliderGapWidth; -}; - -constexpr bool ApplicationInfo::isMobile() -{ -#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) || defined(Q_OS_BLACKBERRY) - return true; -#else - return false; -#endif -} - -inline QQmlPropertyMap *ApplicationInfo::colors() const -{ - return m_colors; -} - -inline QQmlPropertyMap *ApplicationInfo::constants() const -{ - return m_constants; -} - -inline int ApplicationInfo::applicationWidth() const -{ - return m_applicationWidth; -} - -inline bool ApplicationInfo::isPortraitMode() const -{ - return m_isPortraitMode; -} - -inline const QString &ApplicationInfo::currentFile() const -{ - return m_currentFile; -} - -inline void ApplicationInfo::setCurrentFile(const QString ¤tFile) -{ - if (m_currentFile != currentFile) { - m_currentFile = currentFile; - emit currentFileChanged(); - } -} - -inline FieldModel *ApplicationInfo::fieldModel() -{ - return m_fieldModel; -} - -inline Io::AccountEntry *ApplicationInfo::currentAccountEntry() -{ - return m_fieldModel->accountEntry(); -} - -inline QString ApplicationInfo::currentAccountName() const -{ - if (m_fieldModel->accountEntry()) { - return QString::fromStdString(m_fieldModel->accountEntry()->label()); - } - return QString(); -} - -inline void ApplicationInfo::setCurrentAccountEntry(Io::AccountEntry *entry) -{ - m_fieldModel->setAccountEntry(entry); -} - -inline qreal ApplicationInfo::hMargin() const -{ - return m_hMargin; -} - -inline qreal ApplicationInfo::ratio() const -{ - return m_ratio; -} - -inline qreal ApplicationInfo::sliderHandleHeight() -{ - return m_sliderHandleHeight; -} - -inline qreal ApplicationInfo::sliderGapWidth() -{ - return m_sliderGapWidth; -} - -inline qreal ApplicationInfo::sliderHandleWidth() -{ - return m_sliderHandleWidth; -} - -inline qreal ApplicationInfo::sizeWithRatio(const qreal height) -{ - return ratio() * height; -} -} // namespace QtGui - -#endif // APPLICATIONINFO_H diff --git a/quickgui/applicationpaths.h b/quickgui/applicationpaths.h deleted file mode 100644 index 4c03c6a..0000000 --- a/quickgui/applicationpaths.h +++ /dev/null @@ -1,43 +0,0 @@ -#ifndef APPLICATIONPATHS_H -#define APPLICATIONPATHS_H - -#include -#include -#include - -namespace QtGui { - -class ApplicationPaths { -public: - static QString settingsPath(); - static QString dowloadedFilesPath(); - -protected: - static QString path(QStandardPaths::StandardLocation location); -}; - -inline QString ApplicationPaths::settingsPath() -{ - return path(QStandardPaths::DataLocation); -} - -inline QString ApplicationPaths::dowloadedFilesPath() -{ - return path(QStandardPaths::CacheLocation); -} - -inline QString ApplicationPaths::path(QStandardPaths::StandardLocation location) -{ - QString path = QStandardPaths::standardLocations(location).value(0); - QDir dir(path); - if (!dir.exists()) { - dir.mkpath(path); - } - if (!path.isEmpty() && !path.endsWith("/")) { - path += "/"; - } - return path; -} -} // namespace QtGui - -#endif // APPLICATIONPATHS_H diff --git a/quickgui/controller.cpp b/quickgui/controller.cpp new file mode 100644 index 0000000..dbbccfc --- /dev/null +++ b/quickgui/controller.cpp @@ -0,0 +1,145 @@ +#include "./controller.h" + +#include +#include + +#include + +#include + +#include +#include +#include +#include + +#include + +using namespace std; +using namespace Io; +using namespace IoUtilities; +using namespace Dialogs; + +namespace QtGui { + +Controller::Controller(const QString &filePath, QObject *parent) + : QObject(parent) + , m_fileOpen(false) + , m_fileModified(false) +{ + setFilePath(filePath); + m_entryFilterModel.setSourceModel(&m_entryModel); +} + +void Controller::setFilePath(const QString &filePath) +{ + if (m_filePath == filePath) { + return; + } + m_file.clear(); + m_file.setPath(filePath.toLocal8Bit().data()); + emit filePathChanged(m_filePath = filePath); +} + +void Controller::setPassword(const QString &password) +{ + if (m_password == password) { + return; + } + m_file.setPassword(password.toUtf8().data()); + emit passwordChanged(m_password = password); +} + +void Controller::load() +{ + resetFileStatus(); + try { + m_file.load(); + m_entryModel.setRootEntry(m_file.rootEntry()); + setFileOpen(true); + updateWindowTitle(); + } catch (const CryptoException &e) { + if (m_file.isEncryptionUsed() && m_password.isEmpty()) { + emit passwordRequired(m_filePath); + } else { + emit fileError(tr("A crypto error occured when opening the file: ") + QString::fromLocal8Bit(e.what())); + } + } catch (const runtime_error &e) { + emit fileError(tr("A parsing error occured when opening the file: ") + QString::fromLocal8Bit(e.what())); + } catch (...) { + emitIoError(tr("loading")); + } +} + +void Controller::create() +{ + resetFileStatus(); + try { + m_file.create(); + m_entryModel.setRootEntry(m_file.rootEntry()); + setFileOpen(true); + updateWindowTitle(); + } catch (...) { + emitIoError(tr("creating")); + } +} + +void Controller::close() +{ + try { + m_file.close(); + resetFileStatus(); + } catch (...) { + emitIoError(tr("closing")); + } +} + +void Controller::save() +{ + try { + if (!m_password.isEmpty()) { + const auto passwordUtf8(m_password.toUtf8()); + m_file.setPassword(string(passwordUtf8.data(), static_cast(passwordUtf8.size()))); + } else { + m_file.clearPassword(); + } + m_file.save(!m_password.isEmpty()); + } catch (const CryptoException &e) { + emit fileError(tr("A crypto error occured when saving the file: ") + QString::fromLocal8Bit(e.what())); + } catch (const runtime_error &e) { + emit fileError(tr("An internal error occured when saving the file: ") + QString::fromLocal8Bit(e.what())); + } catch (...) { + emitIoError(tr("saving")); + } +} + +void Controller::resetFileStatus() +{ + setFileOpen(false); + m_entryModel.reset(); + m_fieldModel.reset(); +} + +void Controller::updateWindowTitle() +{ + if (m_fileOpen) { + const QFileInfo file(m_filePath); + emit windowTitleChanged(m_windowTitle = file.fileName() % QStringLiteral(" - ") % file.dir().path()); + } else { + emit windowTitleChanged(tr("No file opened.")); + } +} + +void Controller::setFileOpen(bool fileOpen) +{ + if (fileOpen != m_fileOpen) { + emit fileOpenChanged(m_fileOpen = fileOpen); + } +} + +void Controller::emitIoError(const QString &when) +{ + const auto *const msg = catchIoFailure(); + emit fileError(tr("An IO error occured when %1 the file: ").arg(when) + QString::fromLocal8Bit(msg)); +} + +} // namespace QtGui diff --git a/quickgui/controller.h b/quickgui/controller.h new file mode 100644 index 0000000..910c96d --- /dev/null +++ b/quickgui/controller.h @@ -0,0 +1,130 @@ +#ifndef QT_QUICK_GUI_CONTROLLER_H +#define QT_QUICK_GUI_CONTROLLER_H + +#include "../model/entryfiltermodel.h" +#include "../model/entrymodel.h" +#include "../model/fieldmodel.h" + +#include + +#include + +namespace QtGui { + +class Controller : public QObject { + Q_OBJECT + Q_PROPERTY(QString filePath READ filePath WRITE setFilePath NOTIFY filePathChanged) + Q_PROPERTY(QString password READ password WRITE setPassword NOTIFY passwordChanged) + Q_PROPERTY(QString windowTitle READ windowTitle NOTIFY windowTitleChanged) + Q_PROPERTY(bool fileOpen READ isFileOpen NOTIFY fileOpenChanged) + Q_PROPERTY(EntryModel *entryModel READ entryModel NOTIFY entryModelChanged) + Q_PROPERTY(EntryFilterModel *entryFilterModel READ entryFilterModel NOTIFY entryFilterModelChanged) + Q_PROPERTY(FieldModel *fieldModel READ fieldModel NOTIFY fieldModelChanged) + Q_PROPERTY(QModelIndex currentAccountIndex READ currentAccountIndex WRITE setCurrentAccountIndex NOTIFY currentAccountChanged) + Q_PROPERTY(QString currentAccountName READ currentAccountName NOTIFY currentAccountChanged) + +public: + explicit Controller(const QString &filePath = QString(), QObject *parent = nullptr); + + const QString &filePath() const; + void setFilePath(const QString &filePath); + const QString &password() const; + void setPassword(const QString &password); + const QString &windowTitle() const; + bool isFileOpen() const; + EntryModel *entryModel(); + EntryFilterModel *entryFilterModel(); + FieldModel *fieldModel(); + QModelIndex currentAccountIndex() const; + void setCurrentAccountIndex(const QModelIndex &accountIndex); + QString currentAccountName() const; + +public slots: + void load(); + void create(); + void close(); + void save(); + +signals: + void filePathChanged(const QString &newFilePath); + void passwordChanged(const QString &newPassword); + void passwordRequired(const QString &filePath); + void windowTitleChanged(const QString &windowTitle); + void fileOpenChanged(bool fileOpen); + void fileError(const QString &errorMessage); + void entryModelChanged(); + void entryFilterModelChanged(); + void fieldModelChanged(); + void currentAccountChanged(); + +private: + void resetFileStatus(); + void updateWindowTitle(); + void setFileOpen(bool fileOpen); + void emitIoError(const QString &when); + + QString m_filePath; + QString m_password; + QString m_windowTitle; + Io::PasswordFile m_file; + EntryModel m_entryModel; + EntryFilterModel m_entryFilterModel; + FieldModel m_fieldModel; + bool m_fileOpen; + bool m_fileModified; +}; + +inline const QString &Controller::filePath() const +{ + return m_filePath; +} + +inline const QString &Controller::password() const +{ + return m_password; +} + +inline const QString &Controller::windowTitle() const +{ + return m_windowTitle; +} + +inline bool Controller::isFileOpen() const +{ + return m_fileOpen; +} + +inline EntryModel *Controller::entryModel() +{ + return &m_entryModel; +} + +inline EntryFilterModel *Controller::entryFilterModel() +{ + return &m_entryFilterModel; +} + +inline FieldModel *Controller::fieldModel() +{ + return &m_fieldModel; +} + +inline QModelIndex Controller::currentAccountIndex() const +{ + return m_fieldModel.accountEntry() ? m_entryModel.index(const_cast(m_fieldModel.accountEntry())) : QModelIndex(); +} + +inline void Controller::setCurrentAccountIndex(const QModelIndex &accountIndex) +{ + m_fieldModel.setAccountEntry(m_entryModel.isNode(accountIndex) ? nullptr : static_cast(m_entryModel.entry(accountIndex))); + emit currentAccountChanged(); +} + +inline QString Controller::currentAccountName() const +{ + return m_fieldModel.accountEntry() ? QString::fromStdString(m_fieldModel.accountEntry()->label()) : QStringLiteral("?"); +} + +} // namespace QtGui + +#endif // QT_QUICK_GUI_CONTROLLER_H diff --git a/quickgui/initiatequick.cpp b/quickgui/initiatequick.cpp index bbd506b..39fbfe0 100644 --- a/quickgui/initiatequick.cpp +++ b/quickgui/initiatequick.cpp @@ -1,37 +1,25 @@ #include "./initiatequick.h" -#include "./applicationinfo.h" - -#include "../model/entryfiltermodel.h" -#include "../model/entrymodel.h" -#include "../model/fieldmodel.h" +#include "./controller.h" #include "resources/config.h" #include #include -#if defined(GUI_QTWIDGETS) -#include -#else -#include -#endif #include #include #include #include #include +#ifdef PASSWORD_MANAGER_GUI_QTWIDGETS +#include +#endif + using namespace ApplicationUtilities; namespace QtGui { -static QObject *applicationInfo(QQmlEngine *engine, QJSEngine *scriptEngine) -{ - Q_UNUSED(engine) - Q_UNUSED(scriptEngine) - return new ApplicationInfo(); -} - int runQuickGui(int argc, char *argv[], const QtConfigArguments &qtConfigArgs, const QString &file) { // init application @@ -40,7 +28,7 @@ int runQuickGui(int argc, char *argv[], const QtConfigArguments &qtConfigArgs, c #endif SET_QT_APPLICATION_INFO; QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling); -#if defined(GUI_QTWIDGETS) +#ifdef PASSWORD_MANAGER_GUI_QTWIDGETS QApplication a(argc, argv); #else QGuiApplication a(argc, argv); @@ -64,16 +52,19 @@ int runQuickGui(int argc, char *argv[], const QtConfigArguments &qtConfigArgs, c }; // init Quick GUI - qmlRegisterSingletonType("martchus.passwordmanager", 2, 0, "ApplicationInfo", applicationInfo); - qmlRegisterType("martchus.passwordmanager", 2, 0, "EntryFilterModel"); - qmlRegisterType("martchus.passwordmanager", 2, 0, "EntryModel"); - qmlRegisterType("martchus.passwordmanager", 2, 0, "FieldModel"); - QQmlApplicationEngine engine(QUrl("qrc:/qml/main.qml")); - engine.rootContext()->setContextProperty(QStringLiteral("userPaths"), userPaths); - engine.rootContext()->setContextProperty(QStringLiteral("file"), file); + //qmlRegisterType("martchus.passwordmanager", 2, 0, "EntryFilterModel"); + //qmlRegisterType("martchus.passwordmanager", 2, 0, "EntryModel"); + //qmlRegisterType("martchus.passwordmanager", 2, 0, "FieldModel"); + //qmlRegisterType("martchus.passwordmanager", 2, 1, "AccountEntry"); - // start event loop - int res = a.exec(); - return res; + QQmlApplicationEngine engine; + Controller controller(file); + QQmlContext *const context(engine.rootContext()); + context->setContextProperty(QStringLiteral("userPaths"), userPaths); + context->setContextProperty(QStringLiteral("nativeInterface"), &controller); + engine.load(QUrl(QStringLiteral("qrc:/qml/main.qml"))); + + // run event loop + return a.exec(); } } // namespace QtGui diff --git a/resources/qml.qrc b/resources/qml.qrc index d6f0d1e..de3b773 100644 --- a/resources/qml.qrc +++ b/resources/qml.qrc @@ -1,20 +1,9 @@ ../qml/main.qml - ../qml/pages/AccountsPage.qml - ../qml/pages/BasicPage.qml - ../qml/pages/FieldsPage.qml - ../qml/pages/StartPage.qml - ../qml/pages/Separator.qml - ../qml/touch/TouchButton.qml - ../qml/touch/TouchLabel.qml - ../qml/touch/TouchTextField.qml - ../qml/touch/TouchSlider.qml - ../qml/touch/TouchScrollView.qml - ../qml/touch/ListViewDelegate.qml - ../qml/images/clear.png - ../qml/images/magnifier.png - ../qml/images/backarrow.png - ../qml/images/close.png + ../qml/BasicDialog.qml + ../qml/PasswordDialog.qml + ../qml/EntriesPage.qml + ../qml/banner.png diff --git a/qml/images/backarrow.png b/resources/quick-gui/backarrow.png similarity index 100% rename from qml/images/backarrow.png rename to resources/quick-gui/backarrow.png diff --git a/qml/images/clear.png b/resources/quick-gui/clear.png similarity index 100% rename from qml/images/clear.png rename to resources/quick-gui/clear.png diff --git a/qml/images/close.png b/resources/quick-gui/close.png similarity index 100% rename from qml/images/close.png rename to resources/quick-gui/close.png diff --git a/qml/images/magnifier.png b/resources/quick-gui/magnifier.png similarity index 100% rename from qml/images/magnifier.png rename to resources/quick-gui/magnifier.png diff --git a/resources/quick-gui/matrix.jpg b/resources/quick-gui/matrix.jpg new file mode 100644 index 0000000..93183c6 Binary files /dev/null and b/resources/quick-gui/matrix.jpg differ diff --git a/testfiles/test.pwmgr b/testfiles/test.pwmgr new file mode 100644 index 0000000..5e66664 Binary files /dev/null and b/testfiles/test.pwmgr differ