From 73e66bad2670c67403f9470835884a3069e97ce6 Mon Sep 17 00:00:00 2001 From: James Wrigley Date: Sat, 23 Aug 2014 21:48:24 +1200 Subject: [PATCH 01/33] Added new exit dialog. --- buttleofx/MainWindow.qml | 35 +++-------- buttleofx/gui/dialogs/ExitDialog.qml | 90 ++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+), 27 deletions(-) create mode 100644 buttleofx/gui/dialogs/ExitDialog.qml diff --git a/buttleofx/MainWindow.qml b/buttleofx/MainWindow.qml index df988b6d..e0af916d 100644 --- a/buttleofx/MainWindow.qml +++ b/buttleofx/MainWindow.qml @@ -1,10 +1,10 @@ -import QtQuick 2.0 -import QtQuick.Controls 1.0 -import QtQuick.Layouts 1.0 import QtQml 2.1 +import QtQuick 2.0 import QuickMamba 1.0 -import QtQuick.Dialogs 1.1 import QtQuick.Window 2.1 +import QtQuick.Dialogs 1.1 +import QtQuick.Layouts 1.0 +import QtQuick.Controls 1.0 import QtQuick.LocalStorage 2.0 import "gui/graph/qml" @@ -13,6 +13,7 @@ import "gui/paramEditor/qml" import "gui/browser/qml" import "gui/plugin/qml" import "gui/shortcut/qml" +import "gui/dialogs" ApplicationWindow { property var settingsDatabase: getInitializedDatabase() @@ -65,7 +66,7 @@ ApplicationWindow { property variant view3: [player, browser, advancedParamEditor, graphEditor] property string urlOfFileToSave: _buttleData.urlOfFileToSave - + width: 1200 height: 800 id: mainWindowQML @@ -209,28 +210,8 @@ ApplicationWindow { } } - MessageDialog { + ExitDialog { id: closeButtle - title: "Save the graph?" - icon: StandardIcon.Warning - modality: Qt.WindowStaysOnTopHint && Qt.WindowModal - text: urlOfFileToSave == "" ? "Save graph changes before closing ?" : "Save " + _buttleData.getFileName(urlOfFileToSave) + " changes before closing ?" - detailedText: "If you don't save the graph, unsaved modifications will be lost. " - standardButtons: StandardButton.Yes | StandardButton.No | StandardButton.Abort - Component.onCompleted: visible = false - - onYes: { - if(urlOfFileToSave!="") { - _buttleData.saveData(urlOfFileToSave) - } else { - finderSaveGraph.open() - finderSaveGraph.close() - finderSaveGraph.open() - } - } - onNo: { - Qt.quit() - } } menuBar: MenuBar { @@ -288,7 +269,7 @@ ApplicationWindow { if (!_buttleData.graphCanBeSaved) { Qt.quit() } else { - closeButtle.open() + closeButtle.visible = true } } } diff --git a/buttleofx/gui/dialogs/ExitDialog.qml b/buttleofx/gui/dialogs/ExitDialog.qml new file mode 100644 index 00000000..9bfa68c0 --- /dev/null +++ b/buttleofx/gui/dialogs/ExitDialog.qml @@ -0,0 +1,90 @@ +import QtQuick 2.1 +import QtQuick.Window 2.1 +import QtQuick.Layouts 1.0 +import QtQuick.Controls 1.0 +import QtQuick.Controls.Styles 1.0 + +Window { + id: exitDialog + width: 425 + height: 100 + title: "Save Changes?" + color: "#141414" + flags: Qt.Dialog + modality: Qt.WindowModal + visible: false + + Component { + id: buttonStyle + + ButtonStyle { + background: Rectangle { + radius: 6 + implicitWidth: 100 + implicitHeight: 25 + + border.color: control.hovered ? "#00B2A1" : "#9F9C99" + border.width: control.hovered ? 3 : 2 + + gradient: Gradient { + GradientStop { position: 0; color: control.pressed ? "#EFEBE7" : "#EFEBE7" } + GradientStop { position: .5; color: control.pressed ? "#D9D9D9" : "#EFEBE7" } + GradientStop { position: 0; color: control.pressed ? "#EFEBE7" : "#EFEBE7" } + } + } + } + } + + ColumnLayout { + anchors.centerIn: parent + spacing: 15 + + RowLayout { + spacing: 20 + + Image { + source: "/home/james/git/ButtleOFX/buttleofx/gui/img/icons/logo_icon.png" + } + + Text { + text: "Do you want to save before exiting?
If you don't, all unsaved changes will be lost" + color: "#FEFEFE" + } + } + + RowLayout { + anchors.horizontalCenter: parent.horizontalCenter + spacing: 6 + + Button { + id: saveButton + text: "Save" + style: buttonStyle + + onClicked: { + if (urlOfFileToSave != "") { + _buttleData.saveData(urlOfFileToSave) + } else { + finderSaveGraph.open() + finderSaveGraph.close() + finderSaveGraph.open() + } + } + } + + Button { + id: discardButton + text: "Discard" + style: buttonStyle + onClicked: Qt.quit() + } + + Button { + id: abortButton + text: "Abort" + style: buttonStyle + onClicked: exitDialog.visible = false + } + } + } +} From b4edbc8d967ce5f6fd51dc30ace3bbbabf5db4ab Mon Sep 17 00:00:00 2001 From: James Wrigley Date: Sun, 24 Aug 2014 15:35:41 +1200 Subject: [PATCH 02/33] Some code cleanups. --- buttleofx/MainWindow.qml | 64 +++++++++++++++++++--------------------- 1 file changed, 31 insertions(+), 33 deletions(-) diff --git a/buttleofx/MainWindow.qml b/buttleofx/MainWindow.qml index e0af916d..a8e3878d 100644 --- a/buttleofx/MainWindow.qml +++ b/buttleofx/MainWindow.qml @@ -66,7 +66,7 @@ ApplicationWindow { property variant view3: [player, browser, advancedParamEditor, graphEditor] property string urlOfFileToSave: _buttleData.urlOfFileToSave - + width: 1200 height: 800 id: mainWindowQML @@ -83,7 +83,6 @@ ApplicationWindow { player.changeViewer(1) } if ((event.key == Qt.Key_2) && (event.modifiers & Qt.KeypadModifier)) { - player.changeViewer(2) } if ((event.key == Qt.Key_3) && (event.modifiers & Qt.KeypadModifier)) { @@ -282,7 +281,7 @@ ApplicationWindow { id: undoRedoStack title: "Undo/Redo stack" - property variant undoRedoList:_buttleData.graphCanBeSaved ? _buttleManager.undoRedoStack:_buttleManager.undoRedoStack + property variant undoRedoList: _buttleData.graphCanBeSaved ? _buttleManager.undoRedoStack:_buttleManager.undoRedoStack Instantiator { model: undoRedoStack.undoRedoList @@ -394,9 +393,10 @@ ApplicationWindow { Instantiator { model: _buttleData.getMenu(1,"") + Menu { id: firstMenu - title:object + title: object __parentContentItem: nodesMenu.__contentItem // To remove warning Instantiator { @@ -426,11 +426,11 @@ ApplicationWindow { } Instantiator { - model: _buttleData.getMenu(2,firstMenu.title) + model: _buttleData.getMenu(2, firstMenu.title) Menu { id: secondMenu - title:object + title: object __parentContentItem: nodesMenu.__contentItem // To remove warning Instantiator { @@ -460,11 +460,11 @@ ApplicationWindow { } Instantiator { - model: _buttleData.getMenu(3,secondMenu.title) + model: _buttleData.getMenu(3, secondMenu.title) Menu { id: thirdMenu - title:object + title: object __parentContentItem: nodesMenu.__contentItem // To remove warning Instantiator { @@ -494,10 +494,10 @@ ApplicationWindow { } Instantiator { - model: _buttleData.getMenu(4,thirdMenu.title) + model: _buttleData.getMenu(4, thirdMenu.title) Menu { - id:fourthMenu + id: fourthMenu title: object __parentContentItem: nodesMenu.__contentItem // To remove warning @@ -528,7 +528,7 @@ ApplicationWindow { } Instantiator { - model: _buttleData.getMenu(5,fourthMenu.title) + model: _buttleData.getMenu(5, fourthMenu.title) Menu { id: fifthMenu @@ -601,7 +601,6 @@ ApplicationWindow { } } - Menu { title: "View" @@ -616,7 +615,7 @@ ApplicationWindow { browserView.checked = false advancedView.checked = false selectedView = 1 - saveSetting("view",selectedView) + saveSetting("view", selectedView) lastSelectedDefaultView = view1 topLeftView.visible = true bottomLeftView.visible = true @@ -637,7 +636,7 @@ ApplicationWindow { defaultView.checked = false advancedView.checked = false selectedView = 2 - saveSetting("view",selectedView) + saveSetting("view", selectedView) lastSelectedDefaultView = view2 topLeftView.visible = true bottomLeftView.visible = true @@ -658,9 +657,9 @@ ApplicationWindow { defaultView.checked = false browserView.checked = false selectedView = 3 - saveSetting("view",selectedView) + saveSetting("view", selectedView) lastSelectedDefaultView = view3 - topLeftView.visible=true + topLeftView.visible = true bottomLeftView.visible = true topRightView.visible = true bottomRightView.visible = false @@ -674,29 +673,29 @@ ApplicationWindow { MenuItem { text: "Browser" checkable: true - checked: browser.parent.visible==true ? true : false - onTriggered: browser.parent.visible == false ? browser.parent.visible=true : browser.parent.visible=false + checked: browser.parent.visible == true ? true : false + onTriggered: browser.parent.visible == false ? browser.parent.visible = true : browser.parent.visible = false } MenuItem { text: "Viewer" checkable: true checked: player.parent.visible==true ? true : false - onTriggered: player.parent.visible == false ? player.parent.visible=true : player.parent.visible=false + onTriggered: player.parent.visible == false ? player.parent.visible = true : player.parent.visible = false } MenuItem { text: "Graph" checkable: true checked: graphEditor.parent.visible==true ? true : false - onTriggered: graphEditor.parent.visible == false ? graphEditor.parent.visible=true : graphEditor.parent.visible=false + onTriggered: graphEditor.parent.visible == false ? graphEditor.parent.visible = true : graphEditor.parent.visible = false } MenuItem { text: "Parameters" checkable: true checked: paramEditor.parent.visible==true ? true : false - onTriggered: paramEditor.parent.visible == false ? paramEditor.parent.visible=true : paramEditor.parent.visible=false + onTriggered: paramEditor.parent.visible == false ? paramEditor.parent.visible = true : paramEditor.parent.visible = false } */ } @@ -713,7 +712,6 @@ ApplicationWindow { } */ - // This rectangle represents the zone under the menu, it allows to define the anchors.fill and margins for the SplitterRow Rectangle { id: modulsContainer @@ -777,7 +775,7 @@ ApplicationWindow { view2[1] break case 3: - if(advancedParamEditor.displayGraph) + if (advancedParamEditor.displayGraph) view3[3] else view3[1] @@ -831,7 +829,7 @@ ApplicationWindow { implicitWidth: parent.width implicitHeight: topRightView.visible ? 0.5 * parent.height : parent.height z: -1 - visible: selectedView ==3 ? false : true + visible: selectedView == 3 ? false : true children: switch (selectedView) { @@ -857,7 +855,7 @@ ApplicationWindow { id: subviews visible: false - property variant parentBeforeFullscreen : null + property variant parentBeforeFullscreen: null Player { id: player @@ -874,7 +872,7 @@ ApplicationWindow { } } onButtonFullscreenClicked: - if (parent != fullscreenContent){ + if (parent != fullscreenContent) { subviews.parentBeforeFullscreen = parent fullscreenWindow.visibility = Window.FullScreen fullscreenContent.children = player @@ -886,8 +884,8 @@ ApplicationWindow { anchors.fill: parent onButtonCloseClicked: { - if (parent!=fullscreenContent) { - selectedView=-1 + if (parent != fullscreenContent) { + selectedView = -1 parent.visible = false } else { fullscreenWindow.visibility = Window.Hidden @@ -895,7 +893,7 @@ ApplicationWindow { } } onButtonFullscreenClicked: - if (parent != fullscreenContent){ + if (parent != fullscreenContent) { subviews.parentBeforeFullscreen = parent fullscreenWindow.visibility = Window.FullScreen fullscreenContent.children = graphEditor @@ -909,7 +907,7 @@ ApplicationWindow { currentParamNode: _buttleData.currentParamNodeWrapper ? _buttleData.currentParamNodeWrapper : null onButtonCloseClicked: { - if (parent!=fullscreenContent) { + if (parent != fullscreenContent) { selectedView =- 1 parent.visible = false } else { @@ -918,7 +916,7 @@ ApplicationWindow { } } onButtonFullscreenClicked: - if (parent != fullscreenContent){ + if (parent != fullscreenContent) { subviews.parentBeforeFullscreen = parent fullscreenWindow.visibility = Window.FullScreen fullscreenContent.children = paramEditor @@ -939,7 +937,7 @@ ApplicationWindow { } } onButtonFullscreenClicked: - if (parent != fullscreenContent){ + if (parent != fullscreenContent) { subviews.parentBeforeFullscreen = parent fullscreenWindow.visibility = Window.FullScreen fullscreenContent.children = advancedParamEditor @@ -960,7 +958,7 @@ ApplicationWindow { } } onButtonFullscreenClicked: - if (parent != fullscreenContent){ + if (parent != fullscreenContent) { subviews.parentBeforeFullscreen = parent fullscreenWindow.visibility = Window.FullScreen fullscreenContent.children = browser From 0089ca5c2137fac5947904aab63855f4e0f85c10 Mon Sep 17 00:00:00 2001 From: James Wrigley Date: Sun, 31 Aug 2014 22:51:00 +1200 Subject: [PATCH 03/33] Moar cleanups. --- buttleofx/MainWindow.qml | 55 ++++++++++++++++++++++++---------------- 1 file changed, 33 insertions(+), 22 deletions(-) diff --git a/buttleofx/MainWindow.qml b/buttleofx/MainWindow.qml index a8e3878d..ca4625d2 100644 --- a/buttleofx/MainWindow.qml +++ b/buttleofx/MainWindow.qml @@ -70,14 +70,13 @@ ApplicationWindow { width: 1200 height: 800 id: mainWindowQML - title:"ButtleOFX" + title: "ButtleOFX" // TopFocusHandler { // anchors.fill: parent // } Keys.onPressed: { - // Viewer if ((event.key == Qt.Key_1) && (event.modifiers & Qt.KeypadModifier)) { player.changeViewer(1) @@ -117,7 +116,7 @@ ApplicationWindow { } } - property bool aNodeIsSelected:true + property bool aNodeIsSelected: true // Window of hint for plugins PluginWindow { @@ -150,7 +149,7 @@ ApplicationWindow { FileDialog { id: finderSaveGraph title: "Save the graph" - nameFilters: [ "All files (*)" ] + nameFilters: [ "All files (*)" ] selectedNameFilter: "All files (*)" onAccepted: { @@ -167,15 +166,15 @@ ApplicationWindow { title:"Save the graph?" icon: StandardIcon.Warning modality: Qt.WindowStaysOnTopHint && Qt.WindowModal - text: urlOfFileToSave == "" ? "Save graph changes before closing ?" : "Save " + _buttleData.getFileName(urlOfFileToSave) + " changes before closing ?" - detailedText: "If you don't save the graph, unsaved modifications will be lost. " + text: urlOfFileToSave == "" ? "Save graph changes before closing?" : "Save " + _buttleData.getFileName(urlOfFileToSave) + " changes before closing?" + detailedText: "If you don't save the graph, unsaved modifications will be lost." standardButtons: StandardButton.Yes | StandardButton.No | StandardButton.Abort Component.onCompleted: visible = false onYes: { - if(urlOfFileToSave!="") { + if (urlOfFileToSave != "") { _buttleData.saveData(urlOfFileToSave) - } else{ + } else { finderSaveGraph.open() } } @@ -190,13 +189,13 @@ ApplicationWindow { title: "Save the graph?" icon: StandardIcon.Warning modality: Qt.WindowStaysOnTopHint && Qt.WindowModal - text: urlOfFileToSave == "" ? "Save graph changes before closing ?" : "Save " + _buttleData.getFileName(urlOfFileToSave) + " changes before closing ?" - detailedText: "If you don't save the graph, unsaved modifications will be lost. " + text: urlOfFileToSave == "" ? "Save graph changes before closing?" : "Save " + _buttleData.getFileName(urlOfFileToSave) + " changes before closing?" + detailedText: "If you don't save the graph, unsaved modifications will be lost." standardButtons: StandardButton.Yes | StandardButton.No | StandardButton.Abort Component.onCompleted: visible = false onYes: { - if (urlOfFileToSave!="") { + if (urlOfFileToSave != "") { _buttleData.saveData(urlOfFileToSave) _buttleData.newData() } else { @@ -281,7 +280,7 @@ ApplicationWindow { id: undoRedoStack title: "Undo/Redo stack" - property variant undoRedoList: _buttleData.graphCanBeSaved ? _buttleManager.undoRedoStack:_buttleManager.undoRedoStack + property variant undoRedoList: _buttleData.graphCanBeSaved ? _buttleManager.undoRedoStack : _buttleManager.undoRedoStack Instantiator { model: undoRedoStack.undoRedoList @@ -392,7 +391,7 @@ ApplicationWindow { title: "Nodes" Instantiator { - model: _buttleData.getMenu(1,"") + model: _buttleData.getMenu(1, "") Menu { id: firstMenu @@ -673,29 +672,41 @@ ApplicationWindow { MenuItem { text: "Browser" checkable: true - checked: browser.parent.visible == true ? true : false - onTriggered: browser.parent.visible == false ? browser.parent.visible = true : browser.parent.visible = false + checked: browser.parent.visible + + onTriggered: { + browser.parent.visible = !browser.parent.visible + } } MenuItem { text: "Viewer" checkable: true - checked: player.parent.visible==true ? true : false - onTriggered: player.parent.visible == false ? player.parent.visible = true : player.parent.visible = false + checked: player.parent.visible + + onTriggered: { + player.parent.visible = !player.parent.visible + } } MenuItem { text: "Graph" checkable: true - checked: graphEditor.parent.visible==true ? true : false - onTriggered: graphEditor.parent.visible == false ? graphEditor.parent.visible = true : graphEditor.parent.visible = false + checked: graphEditor.parent.visible + + onTriggered: { + graphEditor.parent.visible = !graphEditor.parent.visible + } } MenuItem { text: "Parameters" checkable: true - checked: paramEditor.parent.visible==true ? true : false - onTriggered: paramEditor.parent.visible == false ? paramEditor.parent.visible = true : paramEditor.parent.visible = false + checked: paramEditor.parent.visible + + onTriggered: { + paramEditor.parent.visible = !paramEditor.parent.visible + } } */ } @@ -829,7 +840,7 @@ ApplicationWindow { implicitWidth: parent.width implicitHeight: topRightView.visible ? 0.5 * parent.height : parent.height z: -1 - visible: selectedView == 3 ? false : true + visible: !selectedView children: switch (selectedView) { From 1c044a2525232a6352bb2c2e92acfaab174a3551 Mon Sep 17 00:00:00 2001 From: James Wrigley Date: Wed, 3 Sep 2014 21:43:10 +1200 Subject: [PATCH 04/33] Initial commit for a new file viewer dialog, not properly hooked up yet because it's completely non-functional. Changed the exit dialog to use signals instead of global variables and switched to using relative paths for the ButtleOFX logo. --- buttleofx/MainWindow.qml | 14 ++ buttleofx/gui/dialogs/ExitDialog.qml | 18 +-- buttleofx/gui/dialogs/FileViewerDialog.qml | 173 +++++++++++++++++++++ 3 files changed, 195 insertions(+), 10 deletions(-) create mode 100644 buttleofx/gui/dialogs/FileViewerDialog.qml diff --git a/buttleofx/MainWindow.qml b/buttleofx/MainWindow.qml index ca4625d2..7605c111 100644 --- a/buttleofx/MainWindow.qml +++ b/buttleofx/MainWindow.qml @@ -210,6 +210,20 @@ ApplicationWindow { ExitDialog { id: closeButtle + + onSaveButtonClicked: { + if (urlOfFileToSave != "") { + _buttleData.saveData(urlOfFileToSave) + } else { + closeButtle.visible = false + finderSaveGraph.open() +// finderSaveGraph.close() +// finderSaveGraph.open() + } +// Qt.quit() + } + onDiscardButtonClicked: Qt.quit() + onAbortButtonClicked: closeButtle.visible = false } menuBar: MenuBar { diff --git a/buttleofx/gui/dialogs/ExitDialog.qml b/buttleofx/gui/dialogs/ExitDialog.qml index 9bfa68c0..74bfe3fe 100644 --- a/buttleofx/gui/dialogs/ExitDialog.qml +++ b/buttleofx/gui/dialogs/ExitDialog.qml @@ -14,6 +14,10 @@ Window { modality: Qt.WindowModal visible: false + signal saveButtonClicked + signal discardButtonClicked + signal abortButtonClicked + Component { id: buttonStyle @@ -43,7 +47,7 @@ Window { spacing: 20 Image { - source: "/home/james/git/ButtleOFX/buttleofx/gui/img/icons/logo_icon.png" + source: "../img/icons/logo_icon.png" } Text { @@ -62,13 +66,7 @@ Window { style: buttonStyle onClicked: { - if (urlOfFileToSave != "") { - _buttleData.saveData(urlOfFileToSave) - } else { - finderSaveGraph.open() - finderSaveGraph.close() - finderSaveGraph.open() - } + exitDialog.saveButtonClicked() } } @@ -76,14 +74,14 @@ Window { id: discardButton text: "Discard" style: buttonStyle - onClicked: Qt.quit() + onClicked: exitDialog.discardButtonClicked() } Button { id: abortButton text: "Abort" style: buttonStyle - onClicked: exitDialog.visible = false + onClicked: exitDialog.abortButtonClicked() } } } diff --git a/buttleofx/gui/dialogs/FileViewerDialog.qml b/buttleofx/gui/dialogs/FileViewerDialog.qml new file mode 100644 index 00000000..fdbee7d9 --- /dev/null +++ b/buttleofx/gui/dialogs/FileViewerDialog.qml @@ -0,0 +1,173 @@ +import QtQuick 2.1 +import QtQuick.Window 2.1 +import QtQuick.Layouts 1.0 +import QtQuick.Controls 1.0 +import QtQuick.Controls.Styles 1.0 +import Qt.labs.folderlistmodel 2.1 + +Window { + id: mainWindow + width: 630 + height: 380 + flags: Qt.Dialog + color: "#141414" + + FolderListModel { + id: folderModel + showDirsFirst: true + folder: "/home/james/" + } + + ColumnLayout { + anchors.fill: parent + anchors.margins: 4 + + RowLayout { + id: headerBar + anchors.top: parent.top + anchors.leftMargin: parent.spacing + anchors.rightMargin: parent.spacing + + Button { + id: parentFolderButton + width: 15 + height: 15 + + iconSource: + if (hovered) { + "../img/buttons/browser/parent_hover.png" + } else { + "../img/buttons/browser/parent.png" + } + + style: + ButtonStyle { + background: Rectangle { + anchors.fill: parent + color: "transparent" + } + } + + onClicked: { folderModel.folder = folderModel.parentFolder } + } + + Rectangle { + Layout.fillWidth: true + height: 24 + color: "black" + radius: 5 + border.color: "grey" + + TextInput { + x: 5 + y: 4 + + readOnly: true + selectByMouse: true + Layout.fillWidth: true + + color: "white" + text: folderModel.folder + selectionColor: "#00b2a1" + } + } + } + + ScrollView { + anchors.top: headerBar.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: bottomRow.top + anchors.bottomMargin: 4 + + GridView { + id: folderView + model: folderModel + cellWidth: 100 + cellHeight: 100 + highlightFollowsCurrentItem: false + + highlight: Rectangle { + width: folderView.cellWidth + height: folderView.cellHeight + color: "#00b2a1" + radius: 4 + + x: folderView.currentItem.x + y: folderView.currentItem.y + Behavior on x { SmoothedAnimation { duration: -1; velocity: -1 } } + Behavior on y { SmoothedAnimation { duration: -1; velocity: -1 } } + } + + delegate: Rectangle { + width: folderView.cellWidth + height: folderView.cellHeight + color: "transparent" + + Column { + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + + Image { + anchors.horizontalCenter: parent.horizontalCenter + width: 55 + height: 55 + source: folderModel.isFolder(index) ? "../img/buttons/browser/folder-icon.png" : "../img/buttons/browser/file-icon.png" + } + Text { + width: folderView.cellWidth - 10 + horizontalAlignment: Text.AlignHCenter + wrapMode: Text.WrapAnywhere + text: fileName + color: "white" + } + } + + MouseArea { + anchors.fill: parent + onClicked: folderView.currentIndex = index + + onDoubleClicked: { + if (folderModel.isFolder(index)) { + folderModel.folder = folderModel.get(index, "filePath") + } + } + } + } + } + } + + RowLayout { + id: bottomRow + Layout.fillWidth: true + Layout.fillHeight: true + anchors.bottom: parent.bottom + + TextField { + Layout.fillWidth: true + } + + Button { + text: "Save" + + style: + ButtonStyle { + background: Rectangle { + radius: 6 + implicitWidth: 100 + implicitHeight: 25 + + border.color: control.hovered ? "#00B2A1" : "#9F9C99" + border.width: control.hovered ? 3 : 2 + + gradient: Gradient { + GradientStop { position: 0; color: control.pressed ? "#EFEBE7" : "#EFEBE7" } + GradientStop { position: .5; color: control.pressed ? "#D9D9D9" : "#EFEBE7" } + GradientStop { position: 0; color: control.pressed ? "#EFEBE7" : "#EFEBE7" } + } + } + } + } + } + } +} From 83e5d21c50e84f74f94b0fc9e41358660e75529d Mon Sep 17 00:00:00 2001 From: James Wrigley Date: Wed, 3 Sep 2014 22:59:50 +1200 Subject: [PATCH 05/33] Added support for image previews (when possible). --- buttleofx/gui/dialogs/FileViewerDialog.qml | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/buttleofx/gui/dialogs/FileViewerDialog.qml b/buttleofx/gui/dialogs/FileViewerDialog.qml index fdbee7d9..da037e06 100644 --- a/buttleofx/gui/dialogs/FileViewerDialog.qml +++ b/buttleofx/gui/dialogs/FileViewerDialog.qml @@ -112,7 +112,21 @@ Window { anchors.horizontalCenter: parent.horizontalCenter width: 55 height: 55 - source: folderModel.isFolder(index) ? "../img/buttons/browser/folder-icon.png" : "../img/buttons/browser/file-icon.png" + asynchronous: true + fillMode: Image.PreserveAspectFit + sourceSize.width: width + sourceSize.height: height + property variant supportedFormats: ["bmp", "gif", "jpg", "jpeg", "png", "pbm", "pgm", "ppm", "xbm", "xpm"] + + source: { + if (folderModel.isFolder(index)) { + "../img/buttons/browser/folder-icon.png" + } else if (supportedFormats.indexOf(folderModel.get(index, "fileSuffix").toLowerCase()) != -1) { + folderModel.get(index, "filePath") + } else { + "../img/buttons/browser/file-icon.png" + } + } } Text { width: folderView.cellWidth - 10 From 97d2151cabf4ce5e615ff2be69275c7cb8267d7c Mon Sep 17 00:00:00 2001 From: James Wrigley Date: Thu, 4 Sep 2014 10:29:28 +1200 Subject: [PATCH 06/33] Minor fix to remove the 'file://' prefix from the path bar. --- buttleofx/gui/dialogs/FileViewerDialog.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buttleofx/gui/dialogs/FileViewerDialog.qml b/buttleofx/gui/dialogs/FileViewerDialog.qml index da037e06..60173d46 100644 --- a/buttleofx/gui/dialogs/FileViewerDialog.qml +++ b/buttleofx/gui/dialogs/FileViewerDialog.qml @@ -67,7 +67,7 @@ Window { Layout.fillWidth: true color: "white" - text: folderModel.folder + text: folderModel.folder.toString().substring(7) selectionColor: "#00b2a1" } } From c789763a61dfa00e6a46a567061864e70d5f5d13 Mon Sep 17 00:00:00 2001 From: James Wrigley Date: Thu, 4 Sep 2014 22:13:53 +1200 Subject: [PATCH 07/33] Hooked up the new file dialog to open when saving a new graph. --- buttleofx/MainWindow.qml | 37 ++++++++++++++-------- buttleofx/gui/dialogs/FileViewerDialog.qml | 10 +++++- 2 files changed, 32 insertions(+), 15 deletions(-) diff --git a/buttleofx/MainWindow.qml b/buttleofx/MainWindow.qml index 7605c111..a393f6c8 100644 --- a/buttleofx/MainWindow.qml +++ b/buttleofx/MainWindow.qml @@ -146,19 +146,28 @@ ApplicationWindow { } } - FileDialog { + FileViewerDialog { id: finderSaveGraph title: "Save the graph" - nameFilters: [ "All files (*)" ] - selectedNameFilter: "All files (*)" + buttonText: "Save" + property bool quit: false - onAccepted: { - if (finderSaveGraph.fileUrl) { - _buttleData.saveData(finderSaveGraph.fileUrl) + onButtonClicked: { + if (finderSaveGraph.entryBarText != "") { + _buttleData.urlOfFileToSave = (finderSaveGraph.currentFolder + "/" + finderSaveGraph.entryBarText).substring(7) + + if (_buttleData.urlOfFileToSave.substr(-5) != ".bofx") { + _buttleData.urlOfFileToSave += ".bofx" + } + + _buttleData.saveData(_buttleData.urlOfFileToSave) + finderSaveGraph.visible = false + + if (quit) { + Qt.quit() + } } } - - selectExisting: false } MessageDialog { @@ -175,7 +184,8 @@ ApplicationWindow { if (urlOfFileToSave != "") { _buttleData.saveData(urlOfFileToSave) } else { - finderSaveGraph.open() + finderSaveGraph.quit = false + finderSaveGraph.visible = true } } onNo: { @@ -199,7 +209,8 @@ ApplicationWindow { _buttleData.saveData(urlOfFileToSave) _buttleData.newData() } else { - finderSaveGraph.open() + finderSaveGraph.quit = false + finderSaveGraph.visible = true _buttleData.newData() } } @@ -216,11 +227,9 @@ ApplicationWindow { _buttleData.saveData(urlOfFileToSave) } else { closeButtle.visible = false - finderSaveGraph.open() -// finderSaveGraph.close() -// finderSaveGraph.open() + finderSaveGraph.quit = true + finderSaveGraph.visible = true } -// Qt.quit() } onDiscardButtonClicked: Qt.quit() onAbortButtonClicked: closeButtle.visible = false diff --git a/buttleofx/gui/dialogs/FileViewerDialog.qml b/buttleofx/gui/dialogs/FileViewerDialog.qml index 60173d46..a1b4ab38 100644 --- a/buttleofx/gui/dialogs/FileViewerDialog.qml +++ b/buttleofx/gui/dialogs/FileViewerDialog.qml @@ -11,6 +11,12 @@ Window { height: 380 flags: Qt.Dialog color: "#141414" + visible: false + property string buttonText: "" + property string currentFolder: folderModel.folder + property string entryBarText: entryBar.text + + signal buttonClicked FolderListModel { id: folderModel @@ -158,11 +164,12 @@ Window { anchors.bottom: parent.bottom TextField { + id: entryBar Layout.fillWidth: true } Button { - text: "Save" + text: buttonText style: ButtonStyle { @@ -181,6 +188,7 @@ Window { } } } + onClicked: mainWindow.buttonClicked() } } } From 8a7e56236f6e9095d5d048f8ad946129dfa7b614 Mon Sep 17 00:00:00 2001 From: James Wrigley Date: Thu, 4 Sep 2014 23:33:30 +1200 Subject: [PATCH 08/33] Fixed buttleData.saveData(), previously it would save in the current directory if a QUrl was passed to it. Also removed file extension checks elsewhere, since this is covered in saveData(). --- buttleofx/MainWindow.qml | 4 ---- buttleofx/data/buttleData.py | 6 +++--- buttleofx/main.py | 2 -- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/buttleofx/MainWindow.qml b/buttleofx/MainWindow.qml index a393f6c8..838ceead 100644 --- a/buttleofx/MainWindow.qml +++ b/buttleofx/MainWindow.qml @@ -156,10 +156,6 @@ ApplicationWindow { if (finderSaveGraph.entryBarText != "") { _buttleData.urlOfFileToSave = (finderSaveGraph.currentFolder + "/" + finderSaveGraph.entryBarText).substring(7) - if (_buttleData.urlOfFileToSave.substr(-5) != ".bofx") { - _buttleData.urlOfFileToSave += ".bofx" - } - _buttleData.saveData(_buttleData.urlOfFileToSave) finderSaveGraph.visible = false diff --git a/buttleofx/data/buttleData.py b/buttleofx/data/buttleData.py index 1cb7af8d..2270c7fb 100644 --- a/buttleofx/data/buttleData.py +++ b/buttleofx/data/buttleData.py @@ -649,11 +649,11 @@ def saveData(self, url): """ Saves all data in a json file """ + # If called from Python, it could be a str or a QUrl if isinstance(url, str): - # If called from Python, it could be a str or a QUrl. - filepath = QtCore.QUrl.fromLocalFile(url).toLocalFile() + filepath = url else: - filepath = QtCore.QUrl(url).toLocalFile() + filepath = url.toString() if not filepath.lower().endswith(".bofx"): filepath = filepath + ".bofx" diff --git a/buttleofx/main.py b/buttleofx/main.py index 3748201b..2b79db96 100644 --- a/buttleofx/main.py +++ b/buttleofx/main.py @@ -109,8 +109,6 @@ def eventFilter(self, receiver, event): # This project has never been saved, so ask the user on which file to save. dialog = QtWidgets.QFileDialog() fileToSave = dialog.getSaveFileName(None, "Save the graph", browser.getFirstFolder())[0] - if not (fileToSave.endswith(".bofx")): - fileToSave += ".bofx" buttleData.urlOfFileToSave = fileToSave buttleData.saveData(fileToSave) # Close the application From 1b9ca13ced12e0c0182173bc3a02610b1d4c4658 Mon Sep 17 00:00:00 2001 From: James Wrigley Date: Fri, 5 Sep 2014 22:47:00 +1200 Subject: [PATCH 09/33] The QUrl stuff was messing things up. Note: with this change, we'll have to stick to passing in absolute paths to buttleData.loadData(). --- buttleofx/data/buttleData.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/buttleofx/data/buttleData.py b/buttleofx/data/buttleData.py index 2270c7fb..4547c103 100644 --- a/buttleofx/data/buttleData.py +++ b/buttleofx/data/buttleData.py @@ -524,12 +524,9 @@ def loadData(self, url='buttleofx/backup/data.bofx'): """ Loads all data from a Json file (the default Json file if no url is given) """ - - filepath = QtCore.QUrl(url).toLocalFile() - self.newData() - with open(filepath, 'r') as f: + with open(url, 'r') as f: read_data = f.read() decoded = json.loads(read_data, object_hook=_decode_dict) @@ -563,7 +560,7 @@ def loadData(self, url='buttleofx/backup/data.bofx'): f.closed - self.urlOfFileToSave = filepath + self.urlOfFileToSave = url @QtCore.pyqtSlot() def newData(self): From 30b3108cce516e0ca849ed20eb72ad2a768ca180 Mon Sep 17 00:00:00 2001 From: James Wrigley Date: Fri, 5 Sep 2014 22:48:35 +1200 Subject: [PATCH 10/33] The file dialog now displays the filename of the selected file in the text entry bar. --- buttleofx/gui/dialogs/FileViewerDialog.qml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/buttleofx/gui/dialogs/FileViewerDialog.qml b/buttleofx/gui/dialogs/FileViewerDialog.qml index a1b4ab38..2cfff5bb 100644 --- a/buttleofx/gui/dialogs/FileViewerDialog.qml +++ b/buttleofx/gui/dialogs/FileViewerDialog.qml @@ -145,7 +145,14 @@ Window { MouseArea { anchors.fill: parent - onClicked: folderView.currentIndex = index + onClicked: { + folderView.currentIndex = index + if (!folderModel.isFolder(index)) { + entryBar.text = folderModel.get(index, "fileName") + } else { + entryBar.text = "" + } + } onDoubleClicked: { if (folderModel.isFolder(index)) { From bed97967c26ee4a47967de5466954e93ddb2db31 Mon Sep 17 00:00:00 2001 From: James Wrigley Date: Fri, 5 Sep 2014 22:50:50 +1200 Subject: [PATCH 11/33] Switched finderLoadGraph to be a FileViewerDialog. The whole open/save workflow is a bit buggy still, will need a bit more work. --- buttleofx/MainWindow.qml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/buttleofx/MainWindow.qml b/buttleofx/MainWindow.qml index 838ceead..cad0dc42 100644 --- a/buttleofx/MainWindow.qml +++ b/buttleofx/MainWindow.qml @@ -133,15 +133,15 @@ ApplicationWindow { title: "Shortcuts" } - FileDialog { + FileViewerDialog { id: finderLoadGraph title: "Open a graph" - nameFilters: [ "All files (*)" ] - selectedNameFilter: "All files (*)" + buttonText: "Open" - onAccepted: { - if (finderLoadGraph.fileUrl) { - _buttleData.loadData(finderLoadGraph.fileUrl) + onButtonClicked: { + if (finderLoadGraph.entryBarText != "") { + _buttleData.loadData((finderLoadGraph.currentFolder + "/" + finderLoadGraph.entryBarText).substring(7)) + finderLoadGraph.visible = false } } } @@ -185,7 +185,7 @@ ApplicationWindow { } } onNo: { - finderLoadGraph.open() + finderLoadGraph.visible = true } onRejected: {} } @@ -254,7 +254,7 @@ ApplicationWindow { onTriggered: { if (!_buttleData.graphCanBeSaved) { - finderLoadGraph.open() + finderLoadGraph.visible = true } else { openGraph.open() openGraph.close() @@ -273,7 +273,7 @@ ApplicationWindow { MenuItem { text: "Save As" shortcut: "Ctrl+Shift+S" - onTriggered: finderSaveGraph.open() + onTriggered: finderSaveGraph.visible = true } MenuSeparator { } From 6bca292266ceccc37655b6c5da41ab7ea002e13e Mon Sep 17 00:00:00 2001 From: James Wrigley Date: Sat, 6 Sep 2014 21:39:51 +1200 Subject: [PATCH 12/33] General improvements to the file dialog: It now opens at the users home dir first by means of the new _buttleData.homeDir property and FileViewerDialog.folderModelFolder property, replaced FileViewerDialog.currentFolder with FileViewerDialog.currentFile, and changed the way the URL bar text is assigned. --- buttleofx/MainWindow.qml | 8 +++++-- buttleofx/data/buttleData.py | 4 ++++ buttleofx/gui/dialogs/FileViewerDialog.qml | 28 +++++++++++++++------- 3 files changed, 29 insertions(+), 11 deletions(-) diff --git a/buttleofx/MainWindow.qml b/buttleofx/MainWindow.qml index cad0dc42..04deedf9 100644 --- a/buttleofx/MainWindow.qml +++ b/buttleofx/MainWindow.qml @@ -135,12 +135,14 @@ ApplicationWindow { FileViewerDialog { id: finderLoadGraph + visible: false title: "Open a graph" buttonText: "Open" + folderModelFolder: _buttleData.homeDir onButtonClicked: { if (finderLoadGraph.entryBarText != "") { - _buttleData.loadData((finderLoadGraph.currentFolder + "/" + finderLoadGraph.entryBarText).substring(7)) + _buttleData.loadData(finderLoadGraph.currentFile) finderLoadGraph.visible = false } } @@ -148,13 +150,15 @@ ApplicationWindow { FileViewerDialog { id: finderSaveGraph + visible: false title: "Save the graph" buttonText: "Save" + folderModelFolder: _buttleData.homeDir property bool quit: false onButtonClicked: { if (finderSaveGraph.entryBarText != "") { - _buttleData.urlOfFileToSave = (finderSaveGraph.currentFolder + "/" + finderSaveGraph.entryBarText).substring(7) + _buttleData.urlOfFileToSave = finderSaveGraph.currentFile _buttleData.saveData(_buttleData.urlOfFileToSave) finderSaveGraph.visible = false diff --git a/buttleofx/data/buttleData.py b/buttleofx/data/buttleData.py index 4547c103..a11870e5 100644 --- a/buttleofx/data/buttleData.py +++ b/buttleofx/data/buttleData.py @@ -736,6 +736,9 @@ def zoom(self, width, height, nodeWidth, zoomCoeff, graphPreviousWidth, graphPre def getButtlePath(self): return self._buttlePath + def getHomeDir(self): + return os.path.expanduser("~") + def getCurrentConnectionWrapper(self): """ Returns the current currentConnectionWrapper. @@ -1036,6 +1039,7 @@ def graphCanBeSaved(self): # filePath buttlePath = QtCore.pyqtProperty(str, getButtlePath, constant=True) + homeDir = QtCore.pyqtProperty(str, getHomeDir, constant=True) # Current param, view, and selected node currentParamNodeChanged = QtCore.pyqtSignal() diff --git a/buttleofx/gui/dialogs/FileViewerDialog.qml b/buttleofx/gui/dialogs/FileViewerDialog.qml index 2cfff5bb..d29af0f3 100644 --- a/buttleofx/gui/dialogs/FileViewerDialog.qml +++ b/buttleofx/gui/dialogs/FileViewerDialog.qml @@ -6,22 +6,28 @@ import QtQuick.Controls.Styles 1.0 import Qt.labs.folderlistmodel 2.1 Window { - id: mainWindow + id: fileViewerWindow width: 630 height: 380 flags: Qt.Dialog color: "#141414" - visible: false - property string buttonText: "" - property string currentFolder: folderModel.folder + + property string buttonText + property string folderModelFolder property string entryBarText: entryBar.text + property string currentFile: (folderModel.folder + "/" + entryBarText).substring(7) // We use .substring() to remove the 'file://' prefix signal buttonClicked + // The way the URL bar is updated is a bit kludgy because if we do simple bindings + // the bar will be empty the first time it's started. Hence the Component.onCompleted() call + // and the assignments every time the parentFolderButton is pressed or the current folder is changed. + Component.onCompleted: urlBar.text = folderModelFolder + FolderListModel { id: folderModel showDirsFirst: true - folder: "/home/james/" + folder: folderModelFolder } ColumnLayout { @@ -54,7 +60,10 @@ Window { } } - onClicked: { folderModel.folder = folderModel.parentFolder } + onClicked: { + folderModel.folder = folderModel.parentFolder + urlBar.text = folderModel.folder.toString().substring(7) + } } Rectangle { @@ -65,6 +74,7 @@ Window { border.color: "grey" TextInput { + id: urlBar x: 5 y: 4 @@ -73,7 +83,6 @@ Window { Layout.fillWidth: true color: "white" - text: folderModel.folder.toString().substring(7) selectionColor: "#00b2a1" } } @@ -156,7 +165,8 @@ Window { onDoubleClicked: { if (folderModel.isFolder(index)) { - folderModel.folder = folderModel.get(index, "filePath") + folderModel.folder = folderModel.get(index, "filePath") + urlBar.text = folderModel.get(index, "filePath") } } } @@ -195,7 +205,7 @@ Window { } } } - onClicked: mainWindow.buttonClicked() + onClicked: fileViewerWindow.buttonClicked() } } } From a03e85951cdaf3e57a2b3eaf02d441a9d3cd1d9e Mon Sep 17 00:00:00 2001 From: James Wrigley Date: Sun, 7 Sep 2014 07:44:00 +1200 Subject: [PATCH 13/33] Moved visible property from ExitDialog.qml to the dialogs themselves in MainWindow.qml, and exposed the text of the dialog as a property so it can be modified more easily. --- buttleofx/MainWindow.qml | 2 ++ buttleofx/gui/dialogs/ExitDialog.qml | 5 +++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/buttleofx/MainWindow.qml b/buttleofx/MainWindow.qml index 04deedf9..08f83f9b 100644 --- a/buttleofx/MainWindow.qml +++ b/buttleofx/MainWindow.qml @@ -221,6 +221,8 @@ ApplicationWindow { ExitDialog { id: closeButtle + visible: false + dialogText: "Do you want to save before exiting?
If you don't, all unsaved changes will be lost" onSaveButtonClicked: { if (urlOfFileToSave != "") { diff --git a/buttleofx/gui/dialogs/ExitDialog.qml b/buttleofx/gui/dialogs/ExitDialog.qml index 74bfe3fe..c07237e2 100644 --- a/buttleofx/gui/dialogs/ExitDialog.qml +++ b/buttleofx/gui/dialogs/ExitDialog.qml @@ -12,7 +12,8 @@ Window { color: "#141414" flags: Qt.Dialog modality: Qt.WindowModal - visible: false + + property string dialogText signal saveButtonClicked signal discardButtonClicked @@ -51,7 +52,7 @@ Window { } Text { - text: "Do you want to save before exiting?
If you don't, all unsaved changes will be lost" + text: dialogText color: "#FEFEFE" } } From 7789f0958465ea9adf949debd63bcc3333bed2b6 Mon Sep 17 00:00:00 2001 From: James Wrigley Date: Mon, 8 Sep 2014 21:21:57 +1200 Subject: [PATCH 14/33] Replaced the openGraph dialog with an ExitDialog, removed the abortButtonClicked signal from ExitDialog.qml since it's always handled the same way (hide the ExitDialog), and set the ExitDialog to hide itself automatically when a button is clicked instead of having to do it manually when instanced. Fixed up the interaction between the ExitDialog's and finderSaveGraph, we now pass a bool in signals from the ExitDialog's to trigger finderSaveGraph.show(). The bool indicates whether to create a new graph or quit the application. --- buttleofx/MainWindow.qml | 73 +++++++++++++--------------- buttleofx/gui/dialogs/ExitDialog.qml | 9 ++-- 2 files changed, 41 insertions(+), 41 deletions(-) diff --git a/buttleofx/MainWindow.qml b/buttleofx/MainWindow.qml index 08f83f9b..efdd572b 100644 --- a/buttleofx/MainWindow.qml +++ b/buttleofx/MainWindow.qml @@ -154,7 +154,13 @@ ApplicationWindow { title: "Save the graph" buttonText: "Save" folderModelFolder: _buttleData.homeDir - property bool quit: false + + property bool quit + + function show(doQuit) { + quit = doQuit + finderSaveGraph.visible = true + } onButtonClicked: { if (finderSaveGraph.entryBarText != "") { @@ -163,24 +169,23 @@ ApplicationWindow { _buttleData.saveData(_buttleData.urlOfFileToSave) finderSaveGraph.visible = false + // There are currently only two callers of finderSaveGraph, openGraph and newGraph, + // so we can get away with this simple for/else statement. if (quit) { Qt.quit() + } else { + _buttleData.newData() } } } } - MessageDialog { + ExitDialog { id: openGraph - title:"Save the graph?" - icon: StandardIcon.Warning - modality: Qt.WindowStaysOnTopHint && Qt.WindowModal - text: urlOfFileToSave == "" ? "Save graph changes before closing?" : "Save " + _buttleData.getFileName(urlOfFileToSave) + " changes before closing?" - detailedText: "If you don't save the graph, unsaved modifications will be lost." - standardButtons: StandardButton.Yes | StandardButton.No | StandardButton.Abort - Component.onCompleted: visible = false - - onYes: { + visible: false + dialogText: "Do you want to save before closing this file?
If you don't, all unsaved changes will be lost" + + onSaveButtonClicked: { if (urlOfFileToSave != "") { _buttleData.saveData(urlOfFileToSave) } else { @@ -188,35 +193,28 @@ ApplicationWindow { finderSaveGraph.visible = true } } - onNo: { + onDiscardButtonClicked: { finderLoadGraph.visible = true } - onRejected: {} } - MessageDialog { + ExitDialog { id: newGraph - title: "Save the graph?" - icon: StandardIcon.Warning - modality: Qt.WindowStaysOnTopHint && Qt.WindowModal - text: urlOfFileToSave == "" ? "Save graph changes before closing?" : "Save " + _buttleData.getFileName(urlOfFileToSave) + " changes before closing?" - detailedText: "If you don't save the graph, unsaved modifications will be lost." - standardButtons: StandardButton.Yes | StandardButton.No | StandardButton.Abort - Component.onCompleted: visible = false - - onYes: { + visible: false + dialogText: "Do you want to save before closing this file?
If you don't, all unsaved changes will be lost" + + signal showDialog(bool quit) + + Component.onCompleted: newGraph.showDialog.connect(finderSaveGraph.show) + + onSaveButtonClicked: { if (urlOfFileToSave != "") { _buttleData.saveData(urlOfFileToSave) - _buttleData.newData() } else { - finderSaveGraph.quit = false - finderSaveGraph.visible = true - _buttleData.newData() + newGraph.showDialog(false) } } - onNo: { - _buttleData.newData() - } + onDiscardButtonClicked: _buttleData.newData() } ExitDialog { @@ -224,17 +222,18 @@ ApplicationWindow { visible: false dialogText: "Do you want to save before exiting?
If you don't, all unsaved changes will be lost" + signal showDialog(bool quit) + + Component.onCompleted: closeButtle.showDialog.connect(finderSaveGraph.show) + onSaveButtonClicked: { if (urlOfFileToSave != "") { _buttleData.saveData(urlOfFileToSave) } else { - closeButtle.visible = false - finderSaveGraph.quit = true - finderSaveGraph.visible = true + closeButtle.showDialog(true) } } onDiscardButtonClicked: Qt.quit() - onAbortButtonClicked: closeButtle.visible = false } menuBar: MenuBar { @@ -249,7 +248,7 @@ ApplicationWindow { if (!_buttleData.graphCanBeSaved) { _buttleData.newData() } else { - newGraph.open() + newGraph.visible = true } } } @@ -262,9 +261,7 @@ ApplicationWindow { if (!_buttleData.graphCanBeSaved) { finderLoadGraph.visible = true } else { - openGraph.open() - openGraph.close() - openGraph.open() + openGraph.visible = true } } } diff --git a/buttleofx/gui/dialogs/ExitDialog.qml b/buttleofx/gui/dialogs/ExitDialog.qml index c07237e2..5517212f 100644 --- a/buttleofx/gui/dialogs/ExitDialog.qml +++ b/buttleofx/gui/dialogs/ExitDialog.qml @@ -17,7 +17,6 @@ Window { signal saveButtonClicked signal discardButtonClicked - signal abortButtonClicked Component { id: buttonStyle @@ -68,6 +67,7 @@ Window { onClicked: { exitDialog.saveButtonClicked() + exitDialog.visible = false } } @@ -75,14 +75,17 @@ Window { id: discardButton text: "Discard" style: buttonStyle - onClicked: exitDialog.discardButtonClicked() + onClicked: { + exitDialog.discardButtonClicked() + exitDialog.visible = false + } } Button { id: abortButton text: "Abort" style: buttonStyle - onClicked: exitDialog.abortButtonClicked() + onClicked: exitDialog.visible = false } } } From 425aa8b7a1dbd2cb85c90d9bd8825de3a637b4af Mon Sep 17 00:00:00 2001 From: James Wrigley Date: Tue, 9 Sep 2014 19:34:43 +1200 Subject: [PATCH 15/33] Added comment, and switched to using a FileViewerDialog for openGraph. --- buttleofx/MainWindow.qml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/buttleofx/MainWindow.qml b/buttleofx/MainWindow.qml index efdd572b..7b434ca0 100644 --- a/buttleofx/MainWindow.qml +++ b/buttleofx/MainWindow.qml @@ -157,6 +157,7 @@ ApplicationWindow { property bool quit + // This receives a bool that tells us whether to close ButtleOFX or to create a new graph function show(doQuit) { quit = doQuit finderSaveGraph.visible = true @@ -185,12 +186,15 @@ ApplicationWindow { visible: false dialogText: "Do you want to save before closing this file?
If you don't, all unsaved changes will be lost" + signal showDialog(bool quit) + + Component.onCompleted: openGraph.showDialog.connect(finderSaveGraph.show) + onSaveButtonClicked: { if (urlOfFileToSave != "") { _buttleData.saveData(urlOfFileToSave) } else { - finderSaveGraph.quit = false - finderSaveGraph.visible = true + openGraph.showDialog(true) } } onDiscardButtonClicked: { From 605e16e5e2b680ec6b59d75b87ce23de1b83abc6 Mon Sep 17 00:00:00 2001 From: James Wrigley Date: Sat, 13 Sep 2014 21:13:14 +1200 Subject: [PATCH 16/33] Reverted 1b9ca13. --- buttleofx/data/buttleData.py | 5 +++-- buttleofx/gui/dialogs/FileViewerDialog.qml | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/buttleofx/data/buttleData.py b/buttleofx/data/buttleData.py index a11870e5..2f32b4c5 100644 --- a/buttleofx/data/buttleData.py +++ b/buttleofx/data/buttleData.py @@ -524,9 +524,10 @@ def loadData(self, url='buttleofx/backup/data.bofx'): """ Loads all data from a Json file (the default Json file if no url is given) """ + filepath = QtCore.QUrl(url).toLocalFile() self.newData() - with open(url, 'r') as f: + with open(filepath, 'r') as f: read_data = f.read() decoded = json.loads(read_data, object_hook=_decode_dict) @@ -560,7 +561,7 @@ def loadData(self, url='buttleofx/backup/data.bofx'): f.closed - self.urlOfFileToSave = url + self.urlOfFileToSave = filepath @QtCore.pyqtSlot() def newData(self): diff --git a/buttleofx/gui/dialogs/FileViewerDialog.qml b/buttleofx/gui/dialogs/FileViewerDialog.qml index d29af0f3..9d06c2d6 100644 --- a/buttleofx/gui/dialogs/FileViewerDialog.qml +++ b/buttleofx/gui/dialogs/FileViewerDialog.qml @@ -15,7 +15,7 @@ Window { property string buttonText property string folderModelFolder property string entryBarText: entryBar.text - property string currentFile: (folderModel.folder + "/" + entryBarText).substring(7) // We use .substring() to remove the 'file://' prefix + property string currentFile: folderModel.folder + "/" + entryBarText signal buttonClicked From ece3a338aa524cf6d6c979ab311799eadf1eb060 Mon Sep 17 00:00:00 2001 From: James Wrigley Date: Sat, 13 Sep 2014 21:56:09 +1200 Subject: [PATCH 17/33] Rewrote the showDialog signals to send a string describing the action to be taken, and set buttleData.saveData() to use .toLocalFile() to turn URL's into proper paths. --- buttleofx/MainWindow.qml | 33 ++++++++++++++++++--------------- buttleofx/data/buttleData.py | 2 +- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/buttleofx/MainWindow.qml b/buttleofx/MainWindow.qml index 7b434ca0..7d344c10 100644 --- a/buttleofx/MainWindow.qml +++ b/buttleofx/MainWindow.qml @@ -155,11 +155,13 @@ ApplicationWindow { buttonText: "Save" folderModelFolder: _buttleData.homeDir - property bool quit + // Acceptable values are the verb parts of the callers: 'open', 'new', and 'close' + property string action - // This receives a bool that tells us whether to close ButtleOFX or to create a new graph - function show(doQuit) { - quit = doQuit + // This initializer function takes in the action being done by the user so we know + // what to do when called. + function show(doAction) { + action = doAction finderSaveGraph.visible = true } @@ -170,12 +172,13 @@ ApplicationWindow { _buttleData.saveData(_buttleData.urlOfFileToSave) finderSaveGraph.visible = false - // There are currently only two callers of finderSaveGraph, openGraph and newGraph, - // so we can get away with this simple for/else statement. - if (quit) { - Qt.quit() - } else { + if (action == "open") { + _buttleData.newData() // Todo: Only clear the graph if the user actually decides to save + finderLoadGraph.visible = true + } else if (action == "new") { _buttleData.newData() + } else if (action == "close") { + Qt.quit() } } } @@ -186,7 +189,7 @@ ApplicationWindow { visible: false dialogText: "Do you want to save before closing this file?
If you don't, all unsaved changes will be lost" - signal showDialog(bool quit) + signal showDialog(string quit) Component.onCompleted: openGraph.showDialog.connect(finderSaveGraph.show) @@ -194,7 +197,7 @@ ApplicationWindow { if (urlOfFileToSave != "") { _buttleData.saveData(urlOfFileToSave) } else { - openGraph.showDialog(true) + openGraph.showDialog("open") } } onDiscardButtonClicked: { @@ -207,7 +210,7 @@ ApplicationWindow { visible: false dialogText: "Do you want to save before closing this file?
If you don't, all unsaved changes will be lost" - signal showDialog(bool quit) + signal showDialog(string quit) Component.onCompleted: newGraph.showDialog.connect(finderSaveGraph.show) @@ -215,7 +218,7 @@ ApplicationWindow { if (urlOfFileToSave != "") { _buttleData.saveData(urlOfFileToSave) } else { - newGraph.showDialog(false) + newGraph.showDialog("new") } } onDiscardButtonClicked: _buttleData.newData() @@ -226,7 +229,7 @@ ApplicationWindow { visible: false dialogText: "Do you want to save before exiting?
If you don't, all unsaved changes will be lost" - signal showDialog(bool quit) + signal showDialog(string quit) Component.onCompleted: closeButtle.showDialog.connect(finderSaveGraph.show) @@ -234,7 +237,7 @@ ApplicationWindow { if (urlOfFileToSave != "") { _buttleData.saveData(urlOfFileToSave) } else { - closeButtle.showDialog(true) + closeButtle.showDialog("close") } } onDiscardButtonClicked: Qt.quit() diff --git a/buttleofx/data/buttleData.py b/buttleofx/data/buttleData.py index 2f32b4c5..43b68acc 100644 --- a/buttleofx/data/buttleData.py +++ b/buttleofx/data/buttleData.py @@ -651,7 +651,7 @@ def saveData(self, url): if isinstance(url, str): filepath = url else: - filepath = url.toString() + filepath = url.toLocalFile() if not filepath.lower().endswith(".bofx"): filepath = filepath + ".bofx" From 6cc09b4e4f034bcc3e5290211f912a7b1a5eb3c8 Mon Sep 17 00:00:00 2001 From: James Wrigley Date: Mon, 22 Sep 2014 21:30:28 +1200 Subject: [PATCH 18/33] Some cleanups for the dialogs. Of particular note, the message in ExitDialog.qml is initialized to a default value, and we now send the filepath of the selected file in FileViewerDialog.qml with the buttonClicked() signal (cleaner since we don't need the extra property). --- buttleofx/MainWindow.qml | 8 ++++---- buttleofx/gui/dialogs/ExitDialog.qml | 7 +++---- buttleofx/gui/dialogs/FileViewerDialog.qml | 14 +++++++------- 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/buttleofx/MainWindow.qml b/buttleofx/MainWindow.qml index 7d344c10..83509ccb 100644 --- a/buttleofx/MainWindow.qml +++ b/buttleofx/MainWindow.qml @@ -142,7 +142,7 @@ ApplicationWindow { onButtonClicked: { if (finderLoadGraph.entryBarText != "") { - _buttleData.loadData(finderLoadGraph.currentFile) + _buttleData.loadData(currentFile) finderLoadGraph.visible = false } } @@ -155,7 +155,8 @@ ApplicationWindow { buttonText: "Save" folderModelFolder: _buttleData.homeDir - // Acceptable values are the verb parts of the callers: 'open', 'new', and 'close' + // Acceptable values are the verb parts of the callers ID's, i.e. 'open', + // 'new', and 'close' property string action // This initializer function takes in the action being done by the user so we know @@ -167,7 +168,7 @@ ApplicationWindow { onButtonClicked: { if (finderSaveGraph.entryBarText != "") { - _buttleData.urlOfFileToSave = finderSaveGraph.currentFile + _buttleData.urlOfFileToSave = currentFile _buttleData.saveData(_buttleData.urlOfFileToSave) finderSaveGraph.visible = false @@ -227,7 +228,6 @@ ApplicationWindow { ExitDialog { id: closeButtle visible: false - dialogText: "Do you want to save before exiting?
If you don't, all unsaved changes will be lost" signal showDialog(string quit) diff --git a/buttleofx/gui/dialogs/ExitDialog.qml b/buttleofx/gui/dialogs/ExitDialog.qml index 5517212f..b41e1ce9 100644 --- a/buttleofx/gui/dialogs/ExitDialog.qml +++ b/buttleofx/gui/dialogs/ExitDialog.qml @@ -13,7 +13,7 @@ Window { flags: Qt.Dialog modality: Qt.WindowModal - property string dialogText + property string dialogText: "Do you want to save before exiting?
If you don't, all unsaved changes will be lost." signal saveButtonClicked signal discardButtonClicked @@ -46,9 +46,7 @@ Window { RowLayout { spacing: 20 - Image { - source: "../img/icons/logo_icon.png" - } + Image { source: "../img/icons/logo_icon.png" } Text { text: dialogText @@ -75,6 +73,7 @@ Window { id: discardButton text: "Discard" style: buttonStyle + onClicked: { exitDialog.discardButtonClicked() exitDialog.visible = false diff --git a/buttleofx/gui/dialogs/FileViewerDialog.qml b/buttleofx/gui/dialogs/FileViewerDialog.qml index 9d06c2d6..70fa1e79 100644 --- a/buttleofx/gui/dialogs/FileViewerDialog.qml +++ b/buttleofx/gui/dialogs/FileViewerDialog.qml @@ -9,15 +9,14 @@ Window { id: fileViewerWindow width: 630 height: 380 - flags: Qt.Dialog color: "#141414" + flags: Qt.Dialog property string buttonText property string folderModelFolder property string entryBarText: entryBar.text - property string currentFile: folderModel.folder + "/" + entryBarText - signal buttonClicked + signal buttonClicked(string currentFile) // The way the URL bar is updated is a bit kludgy because if we do simple bindings // the bar will be empty the first time it's started. Hence the Component.onCompleted() call @@ -143,17 +142,19 @@ Window { } } } + Text { + text: fileName + color: "white" width: folderView.cellWidth - 10 horizontalAlignment: Text.AlignHCenter wrapMode: Text.WrapAnywhere - text: fileName - color: "white" } } MouseArea { anchors.fill: parent + onClicked: { folderView.currentIndex = index if (!folderModel.isFolder(index)) { @@ -162,7 +163,6 @@ Window { entryBar.text = "" } } - onDoubleClicked: { if (folderModel.isFolder(index)) { folderModel.folder = folderModel.get(index, "filePath") @@ -205,7 +205,7 @@ Window { } } } - onClicked: fileViewerWindow.buttonClicked() + onClicked: fileViewerWindow.buttonClicked(folderModel.folder + "/" + entryBarText) } } } From 6639c29b8b5b9fa24b1ca122e517557543f438b7 Mon Sep 17 00:00:00 2001 From: James Wrigley Date: Fri, 3 Oct 2014 21:02:33 +1300 Subject: [PATCH 19/33] Simplified the way we assign the current folder to the URL bar. Todo: fix it so that the current URL is displayed when the dialog is launched from the file menu, which is broken with this commit. On the upside, it now displays properly when the dialog is launched when the user hits the close button or hits Alt + F4 (haven't committed that bit of the code yet). --- buttleofx/gui/dialogs/FileViewerDialog.qml | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/buttleofx/gui/dialogs/FileViewerDialog.qml b/buttleofx/gui/dialogs/FileViewerDialog.qml index 70fa1e79..0f1088b1 100644 --- a/buttleofx/gui/dialogs/FileViewerDialog.qml +++ b/buttleofx/gui/dialogs/FileViewerDialog.qml @@ -18,11 +18,6 @@ Window { signal buttonClicked(string currentFile) - // The way the URL bar is updated is a bit kludgy because if we do simple bindings - // the bar will be empty the first time it's started. Hence the Component.onCompleted() call - // and the assignments every time the parentFolderButton is pressed or the current folder is changed. - Component.onCompleted: urlBar.text = folderModelFolder - FolderListModel { id: folderModel showDirsFirst: true @@ -61,7 +56,6 @@ Window { onClicked: { folderModel.folder = folderModel.parentFolder - urlBar.text = folderModel.folder.toString().substring(7) } } @@ -77,6 +71,7 @@ Window { x: 5 y: 4 + text: folderModel.folder.toString().substring(7) readOnly: true selectByMouse: true Layout.fillWidth: true @@ -166,7 +161,6 @@ Window { onDoubleClicked: { if (folderModel.isFolder(index)) { folderModel.folder = folderModel.get(index, "filePath") - urlBar.text = folderModel.get(index, "filePath") } } } From 396aad9ad9d2074ceb783f2e36f2fd49d6faf36c Mon Sep 17 00:00:00 2001 From: James Wrigley Date: Sun, 5 Oct 2014 07:41:05 +1300 Subject: [PATCH 20/33] And at long last, a fix for the bug that started it all, #72 :P This just replaces the QMessageDialog's for the close event with our own. Todo: figure out how to make the dialogs modal. --- buttleofx/main.py | 74 ++++++++++++++++++++++++++--------------------- 1 file changed, 41 insertions(+), 33 deletions(-) diff --git a/buttleofx/main.py b/buttleofx/main.py index 2b79db96..65c93b86 100644 --- a/buttleofx/main.py +++ b/buttleofx/main.py @@ -74,49 +74,57 @@ class EventFilter(QtCore.QObject): - def eventFilter(self, receiver, event): - buttleData = ButtleDataSingleton().get() - browser = FileModelBrowserSingleton().get() + def __init__(self, app, engine): + self.mainApp = app + self.mainEngine = engine + self.buttleData = ButtleDataSingleton().get() + super(EventFilter, self).__init__() + + def onSaveDialogButtonClicked(self, fileToSave): + self.buttleData.urlOfFileToSave = fileToSave + self.buttleData.saveData(QtCore.QUrl(self.buttleData.urlOfFileToSave)) + QtCore.QCoreApplication.quit() + + def onExitDialogDiscardButtonClicked(self): + QtCore.QCoreApplication.quit() + + def onExitDialogSaveButtonClicked(self): + if self.buttleData.urlOfFileToSave: + self.buttleData.saveData(self.buttleData.urlOfFileToSave) + QtCore.QCoreApplication.quit() + else: + saveDialogComponent = QtQml.QQmlComponent(self.mainEngine) + saveDialogComponent.loadUrl(QtCore.QUrl("ButtleOFX/buttleofx/gui/dialogs/FileViewerDialog.qml")) + + saveDialog = saveDialogComponent.create() + QtQml.QQmlProperty.write(saveDialog, "title", "Save the graph") + QtQml.QQmlProperty.write(saveDialog, "buttonText", "Save") + QtQml.QQmlProperty.write(saveDialog, "folderModelFolder", self.buttleData.getHomeDir()) + + saveDialog.buttonClicked.connect(self.onSaveDialogButtonClicked) + saveDialog.show() + def eventFilter(self, receiver, event): if event.type() == QtCore.QEvent.KeyPress: - # If alt f4 event ignored + # If Alt F4 event ignored if event.modifiers() == QtCore.Qt.AltModifier and event.key() == QtCore.Qt.Key_F4: event.ignore() if event.type() != QtCore.QEvent.Close: return super(EventFilter, self).eventFilter(receiver, event) if not isinstance(receiver, QtQuick.QQuickWindow) or not receiver.title() == "ButtleOFX": return False - if not buttleData.graphCanBeSaved: + if not self.buttleData.graphCanBeSaved: return False - msgBox = QtWidgets.QMessageBox() - msgBox.setText("Save graph changes before closing ?") - msgBox.setModal(True) - msgBox.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) - msgBox.setInformativeText("If you don't save the graph, unsaved modifications will be lost.") - msgBox.setStandardButtons( - QtWidgets.QMessageBox.Save | QtWidgets.QMessageBox.Discard | QtWidgets.QMessageBox.Abort) - msgBox.setDefaultButton(QtWidgets.QMessageBox.Save) - ret = msgBox.exec_() - - if ret == QtWidgets.QMessageBox.Save: - if buttleData.urlOfFileToSave: - # Save on the already existing file - buttleData.saveData(buttleData.urlOfFileToSave) - # Close the application - return super(EventFilter, self).eventFilter(receiver, event) - - # This project has never been saved, so ask the user on which file to save. - dialog = QtWidgets.QFileDialog() - fileToSave = dialog.getSaveFileName(None, "Save the graph", browser.getFirstFolder())[0] - buttleData.urlOfFileToSave = fileToSave - buttleData.saveData(fileToSave) - # Close the application - return super(EventFilter, self).eventFilter(receiver, event) + exitDialogComponent = QtQml.QQmlComponent(self.mainEngine) + # Note that we give the path relative to run_buttleofx.sh, because that becomes the PWD when this is run. + # We also do this for the saveDialogComponent in self.onExitDialogSaveButtonClicked(). + exitDialogComponent.loadUrl(QtCore.QUrl("ButtleOFX/buttleofx/gui/dialogs/ExitDialog.qml")) - if ret == QtWidgets.QMessageBox.Discard: - # Close the application - return super(EventFilter, self).eventFilter(receiver, event) + exitDialog = exitDialogComponent.create() + exitDialog.saveButtonClicked.connect(self.onExitDialogSaveButtonClicked) + exitDialog.discardButtonClicked.connect(self.onExitDialogDiscardButtonClicked) + exitDialog.show() # Don't call the parent class, so we don't close the application return True @@ -281,7 +289,7 @@ def main(argv, app): print("Watch directory:", parentDir) qic.addFilesFromDirectory(parentDir, recursive=True) - aFilter = EventFilter() + aFilter = EventFilter(app, engine) app.installEventFilter(aFilter) topLevel.show() From b1aab846f410f6796ce7f1fd7a2a3cdca895c134 Mon Sep 17 00:00:00 2001 From: James Wrigley Date: Mon, 13 Oct 2014 20:58:42 +1300 Subject: [PATCH 21/33] Simplified the ridiculous interation between dialogs, fixed a TODO so that the graph is only cleared when necessary, and made some wee cleanups. --- buttleofx/MainWindow.qml | 28 ++++++++-------------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/buttleofx/MainWindow.qml b/buttleofx/MainWindow.qml index 83509ccb..3c7b3dd5 100644 --- a/buttleofx/MainWindow.qml +++ b/buttleofx/MainWindow.qml @@ -122,9 +122,9 @@ ApplicationWindow { PluginWindow { id: doc title: "Plugin's Documentation" - selectedNodeLabel: _buttleData.currentSelectedNodeWrappers.count!=0 ? _buttleData.currentSelectedNodeWrappers.get(0).name : "" - selectedNodeDoc: _buttleData.currentSelectedNodeWrappers.count!=0 ? _buttleData.currentSelectedNodeWrappers.get(0).pluginDoc : "" - selectedNodeGroup: _buttleData.currentSelectedNodeWrappers.count!=0 ? _buttleData.currentSelectedNodeWrappers.get(0).pluginGroup : "" + selectedNodeLabel: _buttleData.currentSelectedNodeWrappers.count != 0 ? _buttleData.currentSelectedNodeWrappers.get(0).name : "" + selectedNodeDoc: _buttleData.currentSelectedNodeWrappers.count != 0 ? _buttleData.currentSelectedNodeWrappers.get(0).pluginDoc : "" + selectedNodeGroup: _buttleData.currentSelectedNodeWrappers.count != 0 ? _buttleData.currentSelectedNodeWrappers.get(0).pluginGroup : "" } // Window of shortcuts @@ -142,6 +142,7 @@ ApplicationWindow { onButtonClicked: { if (finderLoadGraph.entryBarText != "") { + _buttleData.newData() _buttleData.loadData(currentFile) finderLoadGraph.visible = false } @@ -169,12 +170,11 @@ ApplicationWindow { onButtonClicked: { if (finderSaveGraph.entryBarText != "") { _buttleData.urlOfFileToSave = currentFile - _buttleData.saveData(_buttleData.urlOfFileToSave) + finderSaveGraph.visible = false if (action == "open") { - _buttleData.newData() // Todo: Only clear the graph if the user actually decides to save finderLoadGraph.visible = true } else if (action == "new") { _buttleData.newData() @@ -190,15 +190,11 @@ ApplicationWindow { visible: false dialogText: "Do you want to save before closing this file?
If you don't, all unsaved changes will be lost" - signal showDialog(string quit) - - Component.onCompleted: openGraph.showDialog.connect(finderSaveGraph.show) - onSaveButtonClicked: { if (urlOfFileToSave != "") { _buttleData.saveData(urlOfFileToSave) } else { - openGraph.showDialog("open") + finderSaveGraph.show("open") } } onDiscardButtonClicked: { @@ -211,15 +207,11 @@ ApplicationWindow { visible: false dialogText: "Do you want to save before closing this file?
If you don't, all unsaved changes will be lost" - signal showDialog(string quit) - - Component.onCompleted: newGraph.showDialog.connect(finderSaveGraph.show) - onSaveButtonClicked: { if (urlOfFileToSave != "") { _buttleData.saveData(urlOfFileToSave) } else { - newGraph.showDialog("new") + finderSaveGraph.show("new") } } onDiscardButtonClicked: _buttleData.newData() @@ -229,15 +221,11 @@ ApplicationWindow { id: closeButtle visible: false - signal showDialog(string quit) - - Component.onCompleted: closeButtle.showDialog.connect(finderSaveGraph.show) - onSaveButtonClicked: { if (urlOfFileToSave != "") { _buttleData.saveData(urlOfFileToSave) } else { - closeButtle.showDialog("close") + finderSaveGraph.show("close") } } onDiscardButtonClicked: Qt.quit() From 747ad26711340793df66202945753a7f04383a52 Mon Sep 17 00:00:00 2001 From: James Wrigley Date: Mon, 13 Oct 2014 21:47:50 +1300 Subject: [PATCH 22/33] Replaced the QtQuick dialogs with our own. --- buttleofx/gui/graph/qml/FinderLoadGraph.qml | 18 ----- buttleofx/gui/graph/qml/FinderSaveGraph.qml | 19 ----- buttleofx/gui/graph/qml/Tools.qml | 85 ++++++++++++++++++--- 3 files changed, 75 insertions(+), 47 deletions(-) delete mode 100644 buttleofx/gui/graph/qml/FinderLoadGraph.qml delete mode 100644 buttleofx/gui/graph/qml/FinderSaveGraph.qml diff --git a/buttleofx/gui/graph/qml/FinderLoadGraph.qml b/buttleofx/gui/graph/qml/FinderLoadGraph.qml deleted file mode 100644 index 5462f2f6..00000000 --- a/buttleofx/gui/graph/qml/FinderLoadGraph.qml +++ /dev/null @@ -1,18 +0,0 @@ -import QtQuick 2.0 -import QtQuick.Dialogs 1.0 - -FileDialog { - signal getFileUrl(string fileurl) - - id: finderLoadGraph - title: "Open a graph" - folder: _buttleData.buttlePath - nameFilters: ["ButtleOFX Graph files (*.bofx)", "All files (*)"] - selectedNameFilter: "All files (*)" - - onAccepted: { - console.log(finderLoadGraph.fileUrl) - _buttleData.loadData(finderLoadGraph.fileUrl) - getFileUrl(finderLoadGraph.fileUrl) - } -} diff --git a/buttleofx/gui/graph/qml/FinderSaveGraph.qml b/buttleofx/gui/graph/qml/FinderSaveGraph.qml deleted file mode 100644 index 34a964f7..00000000 --- a/buttleofx/gui/graph/qml/FinderSaveGraph.qml +++ /dev/null @@ -1,19 +0,0 @@ -import QtQuick 2.0 -import QtQuick.Dialogs 1.0 - -FileDialog { - signal getFileUrl(string fileurl) - - id: finderSaveGraph - title: "Save the graph" - folder: _buttleData.buttlePath - nameFilters: ["ButtleOFX Graph files (*.bofx)", "All files (*)"] - selectedNameFilter: "All files (*)" - - onAccepted: { - _buttleData.saveData(finderSaveGraph.fileUrl) - getFileUrl(finderSaveGraph.fileUrl) - } - - selectExisting: false -} diff --git a/buttleofx/gui/graph/qml/Tools.qml b/buttleofx/gui/graph/qml/Tools.qml index 4c6d22f0..f14029cd 100644 --- a/buttleofx/gui/graph/qml/Tools.qml +++ b/buttleofx/gui/graph/qml/Tools.qml @@ -1,7 +1,7 @@ import QtQuick 2.0 -import QtQuick.Dialogs 1.0 import "../../plugin/qml" +import "../../dialogs" Rectangle { id: tools @@ -13,6 +13,72 @@ Rectangle { signal clickCreationNode(string nodeType) + FileViewerDialog { + id: finderLoadGraph + visible: false + title: "Open a graph" + buttonText: "Open" + folderModelFolder: _buttleData.homeDir + + onButtonClicked: { + if (finderLoadGraph.entryBarText != "") { + _buttleData.newData() + _buttleData.loadData(currentFile) + finderLoadGraph.visible = false + } + } + } + + FileViewerDialog { + id: finderSaveGraph + visible: false + title: "Save the graph" + buttonText: "Save" + folderModelFolder: _buttleData.homeDir + + // Acceptable values are the verb parts of the callers ID's, i.e. 'open' + // and 'save' (in which case we do no additional work). + property string action + + // This initializer function takes in the action being done by the user so we know + // what to do when called. + function show(doAction) { + action = doAction + finderSaveGraph.visible = true + } + + onButtonClicked: { + if (finderSaveGraph.entryBarText != "") { + _buttleData.urlOfFileToSave = currentFile + _buttleData.saveData(_buttleData.urlOfFileToSave) + + finderSaveGraph.visible = false + + if (action == "open") { + finderLoadGraph.visible = true + } + } + } + } + + ExitDialog { + id: openGraph + visible: false + dialogText: "Do you want to save before closing this file?
If you don't, all unsaved changes will be lost" + + onSaveButtonClicked: { + if (urlOfFileToSave != "") { + _buttleData.saveData(urlOfFileToSave) + finderLoadGraph.visible = true + } else { + finderSaveGraph.show("open") + } + } + onDiscardButtonClicked: { + finderLoadGraph.visible = true + } + } + gradient: Gradient { GradientStop { position: 0.0; color: gradian2 } GradientStop { position: 0.85; color: gradian2 } @@ -39,13 +105,13 @@ Rectangle { locked: false onClicked: { - if (pluginVisible==true){ - pluginVisible=false + if (pluginVisible == true){ + pluginVisible = false } else { - pluginVisible=true + pluginVisible = true } - editNode=false + editNode = false } } @@ -62,11 +128,9 @@ Rectangle { editNode = false if (!_buttleData.graphCanBeSaved) { - finderLoadGraph.open() + finderLoadGraph.visible = true } else { - openGraph.open() - openGraph.close() - openGraph.open() + openGraph.visible = true } } } @@ -82,10 +146,11 @@ Rectangle { onClicked: { pluginVisible = false editNode = false + if (urlOfFileToSave != "") { _buttleData.saveData(urlOfFileToSave) } else { - finderSaveGraph.open() + finderSaveGraph.show("save") } } } From 00b83ac73776ca6f563ddc27ec69fb09fd3b2e6a Mon Sep 17 00:00:00 2001 From: James Wrigley Date: Mon, 13 Oct 2014 21:51:31 +1300 Subject: [PATCH 23/33] Removed modality property. --- buttleofx/gui/dialogs/ExitDialog.qml | 1 - 1 file changed, 1 deletion(-) diff --git a/buttleofx/gui/dialogs/ExitDialog.qml b/buttleofx/gui/dialogs/ExitDialog.qml index b41e1ce9..9f8e9aa3 100644 --- a/buttleofx/gui/dialogs/ExitDialog.qml +++ b/buttleofx/gui/dialogs/ExitDialog.qml @@ -11,7 +11,6 @@ Window { title: "Save Changes?" color: "#141414" flags: Qt.Dialog - modality: Qt.WindowModal property string dialogText: "Do you want to save before exiting?
If you don't, all unsaved changes will be lost." From 8353dbe1430d5048c1f12873108d792f710cff2e Mon Sep 17 00:00:00 2001 From: James Wrigley Date: Thu, 22 Jan 2015 21:37:35 +1300 Subject: [PATCH 24/33] Whitespace cleanup to satisfy flake. --- buttleofx/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buttleofx/main.py b/buttleofx/main.py index 0521e507..c8d81dec 100644 --- a/buttleofx/main.py +++ b/buttleofx/main.py @@ -124,7 +124,7 @@ def eventFilter(self, receiver, event): exitDialog = exitDialogComponent.create() exitDialog.saveButtonClicked.connect(self.onExitDialogSaveButtonClicked) exitDialog.discardButtonClicked.connect(self.onExitDialogDiscardButtonClicked) - exitDialog.show() + exitDialog.show() # Don't call the parent class, so we don't close the application return True From 1c9b9b3f29b7c1e3ae9498a1434b1733ac602959 Mon Sep 17 00:00:00 2001 From: James Wrigley Date: Fri, 23 Jan 2015 08:30:38 +1300 Subject: [PATCH 25/33] Fixed bug that would hide the graph widget on startup. Seems to have been introduced by mistake in 0089ca5. --- buttleofx/MainWindow.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buttleofx/MainWindow.qml b/buttleofx/MainWindow.qml index 3c7b3dd5..028b10e7 100644 --- a/buttleofx/MainWindow.qml +++ b/buttleofx/MainWindow.qml @@ -857,7 +857,7 @@ ApplicationWindow { implicitWidth: parent.width implicitHeight: topRightView.visible ? 0.5 * parent.height : parent.height z: -1 - visible: !selectedView + visible: selectedView != 3 children: switch (selectedView) { From 3b363f4a0244b62260955e351fc9484f0a69a523 Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 13 Jul 2015 21:04:03 +0200 Subject: [PATCH 26/33] Features: - Exit dialog custom (also triggered by alt+f4) - Use buttle browser for save/open a graph --- blackMosquito.png | Bin 1450 -> 57745 bytes buttleofx/MainWindow.qml | 92 +++------------ buttleofx/data/buttleData.py | 30 +++-- .../gui/browser_v2/actions/browserAction.py | 9 +- buttleofx/gui/browser_v2/browserModel.py | 3 +- buttleofx/gui/browser_v2/qml/Browser.qml | 34 +++++- buttleofx/gui/browser_v2/qml/FileWindow.qml | 61 +++++----- buttleofx/gui/browser_v2/qml/NavBar.qml | 57 ++++----- buttleofx/gui/dialogs/BrowserDialog.qml | 40 +++++++ buttleofx/gui/dialogs/BrowserOpenDialog.qml | 16 +++ buttleofx/gui/dialogs/BrowserSaveDialog.qml | 111 ++++++++++++++++++ buttleofx/gui/dialogs/ExitDialog.qml | 33 +++++- buttleofx/gui/graph/qml/Tools.qml | 44 +------ buttleofx/gui/img/icons/cropped-buttle.png | Bin 0 -> 6271 bytes buttleofx/main.py | 30 ++--- 15 files changed, 338 insertions(+), 222 deletions(-) create mode 100644 buttleofx/gui/dialogs/BrowserDialog.qml create mode 100644 buttleofx/gui/dialogs/BrowserOpenDialog.qml create mode 100644 buttleofx/gui/dialogs/BrowserSaveDialog.qml create mode 100644 buttleofx/gui/img/icons/cropped-buttle.png diff --git a/blackMosquito.png b/blackMosquito.png index 6882107146effda55d353d79d7b384f00c2d2ebe..16c5d2a660e98d32b9673907e2982fdedf738060 100644 GIT binary patch literal 57745 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 Ef 0 && visitedFolderListIndex > 0) { + -- visitedFolderListIndex + bModel.currentPath = visitedFolderList.get(visitedFolderListIndex).url + } + } + // Recently visited folder stack ListModel { id: visitedFolderList @@ -32,9 +45,10 @@ Rectangle { spacing: 0 Tab { - Layout.fillWidth: true id: tabBar + Layout.fillWidth: true name: "Browser" + visible: root.showTab onCloseClicked: root.buttonCloseClicked(true) onFullscreenClicked: root.buttonFullscreenClicked(true) } @@ -44,9 +58,10 @@ Rectangle { Layout.fillWidth: true Layout.preferredHeight: childrenRect.height - property var model: _browser + property var model: root.bModel property alias visitedFolderList: visitedFolderList property alias visitedFolderListIndex: root.visitedFolderListIndex + onPushVisitedFolder: { root.pushVisitedFolder(path) } @@ -54,10 +69,8 @@ Rectangle { Rectangle { id: separator - Layout.fillWidth: true Layout.preferredHeight: 1 - color: "#00b2a1" } @@ -67,7 +80,8 @@ Rectangle { Layout.fillWidth: true Layout.fillHeight: true - property var model: _browser + property var model: root.bModel + property var bAction: root.bAction property alias visitedFolderList: visitedFolderList property alias visitedFolderListIndex: root.visitedFolderListIndex @@ -76,4 +90,14 @@ Rectangle { } } } + + Keys.onPressed: { + if ((event.modifiers & Qt.ControlModifier) && (event.key == Qt.Key_L)){ + navBar.toggleUrlEdit() + } + + if (event.key == Qt.Key_Backspace){ + popVisitedFolder() + } + } } diff --git a/buttleofx/gui/browser_v2/qml/FileWindow.qml b/buttleofx/gui/browser_v2/qml/FileWindow.qml index 8d52b73d..bb1c5008 100644 --- a/buttleofx/gui/browser_v2/qml/FileWindow.qml +++ b/buttleofx/gui/browser_v2/qml/FileWindow.qml @@ -9,8 +9,9 @@ Rectangle { id: root color: "transparent" - //TODO: think about standalone - function handleGraphViewerClick(pathImg){ + // defaults slots + function onItemClickedSlot(pathImg){ + // handleGraphViewerClick // We come to the temporary viewer player.changeViewer(11) @@ -28,7 +29,9 @@ Rectangle { _buttleData.currentViewerIndex = 10 // We assign to the viewer the 10th view _buttleEvent.emitViewerChangedSignal() } - function handleGraphViewerDoubleClick(browserItem){ + + function onItemDoubleClickedSlot(absolutePath){ + // handleGraphViewerDoubleClick _buttleData.currentGraphWrapper = _buttleData.graphWrapper _buttleData.currentGraphIsGraph() @@ -40,12 +43,14 @@ Rectangle { _buttleData.currentViewerNodeWrapper = player.lastNodeWrapper player.changeViewer(player.lastView) } - _buttleManager.nodeManager.dropFile(browserItem.path, 10, 10) + _buttleManager.nodeManager.dropFile(absolutePath, 10, 10) } + signal itemClicked(string absolutePath, string pathImg, bool isFolder, bool isSupported) + signal itemDoubleClicked(string absolutePath, string pathImg, bool isFolder, bool isSupported) signal pushVisitedFolder(string path) - Keys.onEscapePressed: root.model.unselectAllItems() + Keys.onEscapePressed: root.model.unselectAllItems() MouseArea { anchors.fill: parent acceptedButtons: Qt.LeftButton | Qt.RightButton @@ -76,7 +81,7 @@ Rectangle { iconName: "edit-select-all" shortcut: StandardKey.SelectAll onTriggered: { - _browser.selectAllItems() + root.model.selectAllItems() } } @@ -85,7 +90,7 @@ Rectangle { iconName: "reload" shortcut: StandardKey.Refresh onTriggered: { - _browser.refresh() + root.model.refresh() } } MenuSeparator{} @@ -103,8 +108,8 @@ Rectangle { iconName: "folder-new" shortcut: StandardKey.New onTriggered: { - _browserAction.handleNew("Folder") - _browser.refresh() + root.bAction.handleNew("Folder") + root.model.refresh() } } MenuItem{ @@ -113,8 +118,8 @@ Rectangle { iconName: "document-new" shortcut: StandardKey.UnknownKey onTriggered: { - _browserAction.handleNew("File") - _browser.refresh() + root.bAction.handleNew("File") + root.model.refresh() } } MenuSeparator{} @@ -124,7 +129,7 @@ Rectangle { shortcut: StandardKey.Copy iconName: "edit-copy" onTriggered: { - _browserAction.handleCopy() + root.bAction.handleCopy() } } MenuItem{ @@ -133,21 +138,21 @@ Rectangle { iconName: "edit-cut" shortcut: StandardKey.Cut onTriggered: { - _browserAction.handleMove() + root.bAction.handleMove() } } MenuItem{ text:"Paste" iconName: "edit-paste" shortcut: StandardKey.Paste - enabled: _browserAction.isCache + enabled: root.bAction.isCache onTriggered: { var destination="" - if(_browser.selectedItems.count == 1 && _browser.selectedItems.get(0).isFolder()) - destination = _browser.selectedItems.get(0).path + if(root.model.selectedItems.count == 1 && root.model.selectedItems.get(0).isFolder()) + destination = root.model.selectedItems.get(0).path - _browserAction.handlePaste(destination) - _browser.refresh() + root.bAction.handlePaste(destination) + root.model.refresh() } } @@ -157,8 +162,8 @@ Rectangle { iconName: "edit-delete" shortcut: StandardKey.Deletes onTriggered: { - _browserAction.handleDelete() - _browser.refresh() + root.bAction.handleDelete() + root.model.refresh() } } @@ -323,12 +328,7 @@ Rectangle { } else if(mouse.button == Qt.LeftButton){ - if(!model.object.isFolder()){ - if (model.object.isSupported()){ - handleGraphViewerClick(model.object.path) - } - } - + root.itemClicked(model.object.path, model.object.path, model.object.isFolder(), model.object.isSupported()) if ((mouse.modifiers & Qt.ShiftModifier)) root.model.selectItemTo(index) else if ((mouse.modifiers & Qt.ControlModifier)) @@ -338,17 +338,15 @@ Rectangle { } } onDoubleClicked: { + root.itemDoubleClicked(model.object.path, model.object.path, model.object.isFolder(), model.object.isSupported()) + + // we ensure this behavior by default if (model.object.isFolder()) { pushVisitedFolder(model.object.path) root.model.currentPath = model.object.path } - - // If it's an image, we create a node - else if (model.object.isSupported()) - handleGraphViewerDoubleClick(model.object) } } - } } @@ -464,4 +462,3 @@ Rectangle { // } // } } - diff --git a/buttleofx/gui/browser_v2/qml/NavBar.qml b/buttleofx/gui/browser_v2/qml/NavBar.qml index bbe98787..44fb866b 100644 --- a/buttleofx/gui/browser_v2/qml/NavBar.qml +++ b/buttleofx/gui/browser_v2/qml/NavBar.qml @@ -7,12 +7,22 @@ import QtQuick.Controls.Styles 1.0 Rectangle { id: root color: "#2E2E2E" - clip: true signal pushVisitedFolder(string path) property alias searchLayout: searchLayoutRectangle + function toggleUrlEdit(visibility){ + visibility = visibility !== undefined ? visibility : breadCrum.visible + breadCrum.visible = !visibility + textEditContainer.visible = visibility + + if(visibility) + texteditPath.forceActiveFocus() + } + + Component.onCompleted: toggleUrlEdit(true) + QtObject { id: m; property bool searchEnabled: false @@ -43,26 +53,17 @@ Rectangle { Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter tooltip: "Previous" - iconSource: - if (hovered) - "img/previous_hover.png" - else - "img/previous.png" + iconSource: hovered ? "img/previous_hover.png" : "img/previous.png" style: - ButtonStyle { - background: Rectangle { - anchors.fill: parent - color: "transparent" + ButtonStyle { + background: Rectangle { + anchors.fill: parent + color: "transparent" + } } - } - onClicked: { - if (visitedFolderList.count > 0 && visitedFolderListIndex > 0) { - -- visitedFolderListIndex - model.currentPath = visitedFolderList.get(visitedFolderListIndex).url - } - } + onClicked: popVisitedFolder() } Button { @@ -260,7 +261,8 @@ Rectangle { } Keys.onReleased: { - if(event.key === Qt.Key_Shift || event.key === Qt.Key_Alt) + if(event.key === Qt.Key_Shift || event.key === Qt.Key_Alt || event.key === Qt.Key_Control + || (event.key === Qt.Key_Control && event.key === Qt.Key_L)) return root.model.currentPath = texteditPath.text graySuggestion.fill() @@ -311,7 +313,6 @@ Rectangle { } function show() { - console.log(root.model.listFolderNavBar.count) if(!root.model.listFolderNavBar.count) return this.__popup(0, 0) @@ -330,16 +331,10 @@ Rectangle { clip: true MouseArea { - anchors.fill: parent - propagateComposedEvents: true - onDoubleClicked: { - if (breadCrum.visible) - breadCrum.visible = false - - if (!textEditContainer.visible){ - textEditContainer.visible = true - texteditPath.forceActiveFocus() - } + anchors.fill: parent + propagateComposedEvents: true + onDoubleClicked: { + root.toggleUrlEdit() } } delegate: component @@ -513,7 +508,7 @@ Rectangle { anchors.fill: parent anchors.margins: 2 - placeholderText: "Enter your search ..." + placeholderText: "Search ..." style: TextFieldStyle { selectionColor: "#00b2a1" @@ -525,7 +520,7 @@ Rectangle { } } onAccepted: { - _browser.loadData(text.trim()) + root.model.loadData(text.trim()) } onFocusChanged: { diff --git a/buttleofx/gui/dialogs/BrowserDialog.qml b/buttleofx/gui/dialogs/BrowserDialog.qml new file mode 100644 index 00000000..122d0224 --- /dev/null +++ b/buttleofx/gui/dialogs/BrowserDialog.qml @@ -0,0 +1,40 @@ +import QtQuick 2.2 +import QtQuick.Layouts 1.1 +import QtQml 2.1 +import QtQuick 2.0 +import QtQuick.Window 2.1 +import QtQuick.Dialogs 1.1 +import QtQuick.Controls 1.0 +import QtQuick.Controls.Styles 1.0 + + +import "../browser_v2/qml/" + +// common part of open and save browserDialog (Abstract behavior) +// the connecitons are done in 'subclasses' + +Window{ + id: finderBrowser + title: 'Finder Browser' + width: 630 + height: 380 + visible: false + flags: "Dialog" + modality: "ApplicationModal" + property alias browser: browser + property bool showTab: false + + Browser { + id: browser + bModel: _browserDialog + bAction: _browserActionDialog + anchors.fill: parent + showTab: finderBrowser.showTab + } + onVisibleChanged:{ + if(visible == false) + browser.bModel.stopLoading() + else + browser.navBar.toggleUrlEdit(true) + } +} diff --git a/buttleofx/gui/dialogs/BrowserOpenDialog.qml b/buttleofx/gui/dialogs/BrowserOpenDialog.qml new file mode 100644 index 00000000..d73d6ec0 --- /dev/null +++ b/buttleofx/gui/dialogs/BrowserOpenDialog.qml @@ -0,0 +1,16 @@ +import QtQuick 2.0 + +BrowserDialog{ + id: root + title: 'Open Graph' + + Connections{ + target: root.browser.fileWindow + onItemDoubleClicked:{ + if (!isFolder && absolutePath != "") { + _buttleData.loadData(absolutePath) + root.visible = false + } + } + } +} diff --git a/buttleofx/gui/dialogs/BrowserSaveDialog.qml b/buttleofx/gui/dialogs/BrowserSaveDialog.qml new file mode 100644 index 00000000..f2492bcd --- /dev/null +++ b/buttleofx/gui/dialogs/BrowserSaveDialog.qml @@ -0,0 +1,111 @@ +import QtQuick 2.2 +import QtQuick.Layouts 1.1 +import QtQml 2.1 +import QtQuick 2.0 +import QtQuick.Window 2.1 +import QtQuick.Dialogs 1.1 +import QtQuick.Controls 1.0 +import QtQuick.Controls.Styles 1.0 + + +import "../browser_v2/qml/" + +// BrowserSave dialog: used to save a comp. +// the connecitons are done MainWindow.qml (i.e the Component which use this dialog) + +BrowserDialog{ + id: root + title: 'Save the Graph' + signal saveButtonClicked(string absoluteFilePath) + property variant idBrowserOpenDialog: undefined + property string action + + function show(doAction) { + root.action = doAction + root.visible = true + } + + + onSaveButtonClicked: { + if (absoluteFilePath != "") { + _buttleData.urlOfFileToSave = absoluteFilePath + _buttleData.saveData(_buttleData.urlOfFileToSave) + + root.visible = false + + if (root.action == "open" && idBrowserOpenDialog) + idBrowserOpenDialog.visible = true + else if (root.action == "new") + _buttleData.newData() + else if (root.action == "close") + Qt.quit() + } + } + + RowLayout { + id: bottomRow + Layout.fillWidth: true + Layout.fillHeight: true + anchors.bottom: parent.bottom + width: root.width - 8 + spacing: 0 + + TextField { + id: entryBar + Layout.fillWidth: true + style: + TextFieldStyle { + selectionColor: "#00b2a1" + textColor: "#2E2E2E" + background: Rectangle { + id: entryStyle + color: "#DDDDDD" + border.color: "#00b2a1" + radius: 3 + border.width: 1 + states: [ + State { + name: "out" + when: !entryBar.focus + PropertyChanges { + target: entryStyle + border.width: 0 + } + } + ] + } + } + } + + Button{ + text: "Save" + onClicked: saveButtonClicked(entryBar.text) + + style: ButtonStyle { + background: Rectangle { + id: buttonRectangle + radius: 3 + implicitWidth: 100 + implicitHeight: 25 + + border.color: "#9F9C99"; + border.width: 1; + + gradient: Gradient { + GradientStop { position: 0; color: control.pressed ? "#EFEBE7" : "#EFEBE7" } + GradientStop { position: .5; color: control.pressed ? "#D9D9D9" : "#EFEBE7" } + GradientStop { position: 0; color: control.pressed ? "#EFEBE7" : "#EFEBE7" } + } + } + } + } + } + + Connections{ + target: root.browser.fileWindow + onItemClicked: { + entryBar.text = absolutePath + (isFolder ? '/buttleSave_now.bofx' : '') + } + } + Component.onCompleted: entryBar.text = root.browser.bModel.currentPath + 'buttleSave_now.bofx' +} diff --git a/buttleofx/gui/dialogs/ExitDialog.qml b/buttleofx/gui/dialogs/ExitDialog.qml index 9f8e9aa3..5dff85de 100644 --- a/buttleofx/gui/dialogs/ExitDialog.qml +++ b/buttleofx/gui/dialogs/ExitDialog.qml @@ -6,11 +6,12 @@ import QtQuick.Controls.Styles 1.0 Window { id: exitDialog - width: 425 - height: 100 + width: 470 + height: 120 title: "Save Changes?" - color: "#141414" + color: "#272727" flags: Qt.Dialog + modality: "ApplicationModal" property string dialogText: "Do you want to save before exiting?
If you don't, all unsaved changes will be lost." @@ -22,18 +23,37 @@ Window { ButtonStyle { background: Rectangle { + id: buttonRectangle radius: 6 implicitWidth: 100 implicitHeight: 25 - border.color: control.hovered ? "#00B2A1" : "#9F9C99" - border.width: control.hovered ? 3 : 2 + border.color: "#9F9C99"; + border.width: 1; + opacity: 0.7 gradient: Gradient { GradientStop { position: 0; color: control.pressed ? "#EFEBE7" : "#EFEBE7" } GradientStop { position: .5; color: control.pressed ? "#D9D9D9" : "#EFEBE7" } GradientStop { position: 0; color: control.pressed ? "#EFEBE7" : "#EFEBE7" } } + + states: + State { + name: "mouse-over"; + when: control.hovered + PropertyChanges { + target: buttonRectangle; + border.color: "#00B2A1"; + opacity: 1} + } + transitions: Transition { + NumberAnimation { + properties: "opacity, border.width"; + easing.type: Easing.InOutQuad; + duration: 200 + } + } } } } @@ -45,7 +65,7 @@ Window { RowLayout { spacing: 20 - Image { source: "../img/icons/logo_icon.png" } + Image { source: "../img/icons/cropped-buttle.png" } Text { text: dialogText @@ -61,6 +81,7 @@ Window { id: saveButton text: "Save" style: buttonStyle + isDefault: true onClicked: { exitDialog.saveButtonClicked() diff --git a/buttleofx/gui/graph/qml/Tools.qml b/buttleofx/gui/graph/qml/Tools.qml index 2b541b6d..3ed7baad 100644 --- a/buttleofx/gui/graph/qml/Tools.qml +++ b/buttleofx/gui/graph/qml/Tools.qml @@ -13,52 +13,12 @@ Rectangle { signal clickCreationNode(string nodeType) - FileViewerDialog { + BrowserOpenDialog{ id: finderLoadGraph - visible: false - title: "Open a graph" - buttonText: "Open" - folderModelFolder: _buttleData.homeDir - - onButtonClicked: { - if (finderLoadGraph.entryBarText != "") { - _buttleData.newData() - _buttleData.loadData(currentFile) - finderLoadGraph.visible = false - } - } } - FileViewerDialog { + BrowserSaveDialog{ id: finderSaveGraph - visible: false - title: "Save the graph" - buttonText: "Save" - folderModelFolder: _buttleData.homeDir - - // Acceptable values are the verb parts of the callers ID's, i.e. 'open' - // and 'save' (in which case we do no additional work). - property string action - - // This initializer function takes in the action being done by the user so we know - // what to do when called. - function show(doAction) { - action = doAction - finderSaveGraph.visible = true - } - - onButtonClicked: { - if (finderSaveGraph.entryBarText != "") { - _buttleData.urlOfFileToSave = currentFile - _buttleData.saveData(_buttleData.urlOfFileToSave) - - finderSaveGraph.visible = false - - if (action == "open") { - finderLoadGraph.visible = true - } - } - } } ExitDialog { diff --git a/buttleofx/gui/img/icons/cropped-buttle.png b/buttleofx/gui/img/icons/cropped-buttle.png new file mode 100644 index 0000000000000000000000000000000000000000..20e4a95a2402ce3dbbb5dfb309b7d5a6e3dfb159 GIT binary patch literal 6271 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 literal 0 HcmV?d00001 diff --git a/buttleofx/main.py b/buttleofx/main.py index 9bfa3740..7e444880 100644 --- a/buttleofx/main.py +++ b/buttleofx/main.py @@ -38,8 +38,8 @@ from buttleofx.event import globalButtleEvent from buttleofx.manager import globalButtleManager from buttleofx.core.undo_redo.manageTools import globalCommandManager -from buttleofx.gui.browser_v2.browserModel import BrowserModel, globalBrowserModel -from buttleofx.gui.browser_v2.actions.browserAction import globalBrowserAction +from buttleofx.gui.browser_v2.browserModel import BrowserModel, globalBrowser, globalBrowserDialog +from buttleofx.gui.browser_v2.actions.browserAction import globalBrowserAction, globalBrowserActionDialog from buttleofx.gui.browser_v2.actions.browserAction import globalActionManager from PyQt5 import QtCore, QtGui, QtQml, QtQuick, QtWidgets @@ -100,14 +100,9 @@ def onExitDialogSaveButtonClicked(self): QtCore.QCoreApplication.quit() else: saveDialogComponent = QtQml.QQmlComponent(self.mainEngine) - saveDialogComponent.loadUrl(QtCore.QUrl("ButtleOFX/buttleofx/gui/dialogs/FileViewerDialog.qml")) - + saveDialogComponent.loadUrl(QtCore.QUrl(os.path.dirname(os.path.abspath(__file__)) + '/gui/dialogs/BrowserSaveDialog.qml')) saveDialog = saveDialogComponent.create() - QtQml.QQmlProperty.write(saveDialog, "title", "Save the graph") - QtQml.QQmlProperty.write(saveDialog, "buttonText", "Save") - QtQml.QQmlProperty.write(saveDialog, "folderModelFolder", self.buttleData.getHomeDir()) - - saveDialog.buttonClicked.connect(self.onSaveDialogButtonClicked) + saveDialog.saveButtonClicked.connect(self.onSaveDialogButtonClicked) saveDialog.show() def eventFilter(self, receiver, event): @@ -124,10 +119,7 @@ def eventFilter(self, receiver, event): return False exitDialogComponent = QtQml.QQmlComponent(self.mainEngine) - # Note that we give the path relative to run_buttleofx.sh, because that becomes the PWD when this is run. - # We also do this for the saveDialogComponent in self.onExitDialogSaveButtonClicked(). - exitDialogComponent.loadUrl(QtCore.QUrl("ButtleOFX/buttleofx/gui/dialogs/ExitDialog.qml")) - + exitDialogComponent.loadUrl(QtCore.QUrl(os.path.dirname(os.path.abspath(__file__)) + '/gui/dialogs/ExitDialog.qml')) exitDialog = exitDialogComponent.create() exitDialog.saveButtonClicked.connect(self.onExitDialogSaveButtonClicked) exitDialog.discardButtonClicked.connect(self.onExitDialogDiscardButtonClicked) @@ -247,15 +239,17 @@ def main(argv, app): parser.add_argument('folder', nargs='?', help='Folder to browse') args = parser.parse_args() - globalBrowserModel.setCurrentPath(os.path.abspath(args.folder) if args.folder else globalBrowserModel.getHomePath()) + globalBrowser.setCurrentPath(os.path.abspath(args.folder) if args.folder else globalBrowser.getHomePath()) # Expose data to QML rc = engine.rootContext() rc.setContextProperty("_buttleApp", app) rc.setContextProperty("_buttleData", globalButtleData) rc.setContextProperty("_buttleManager", buttleManager) rc.setContextProperty("_buttleEvent", globalButtleEvent) - rc.setContextProperty("_browser", globalBrowserModel) + rc.setContextProperty("_browser", globalBrowser) + rc.setContextProperty("_browserDialog", globalBrowserDialog) rc.setContextProperty("_browserAction", globalBrowserAction) + rc.setContextProperty("_browserActionDialog", globalBrowserActionDialog) rc.setContextProperty("_actionManager", globalActionManager) iconPath = os.path.join(currentFilePath, "../blackMosquito.png") @@ -296,9 +290,11 @@ def main(argv, app): aFilter = EventFilter(app, engine) app.installEventFilter(aFilter) - globalBrowserModel.loadData() + + globalBrowser.loadData() + globalBrowserDialog.loadData() with globalActionManager: topLevelItem.show() exitCode = app.exec_() - sys.exit(exitCode) + sys.exit(exitCode) \ No newline at end of file From fc763e20747b82715bac65deccc51f817321ea3b Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 13 Jul 2015 21:04:59 +0200 Subject: [PATCH 27/33] browser: fix bug recurse search with qt async connections the bItems list is not ensured to be filled immediatly before the recurse process --- buttleofx/gui/browser_v2/browserModel.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/buttleofx/gui/browser_v2/browserModel.py b/buttleofx/gui/browser_v2/browserModel.py index a818c6a1..4b55b33a 100644 --- a/buttleofx/gui/browser_v2/browserModel.py +++ b/buttleofx/gui/browser_v2/browserModel.py @@ -42,6 +42,7 @@ def __init__(self, path=op.expanduser("~/"), sync=False, showSeq=True, hideDotFi """ QtCore.QObject.__init__(self, parent) self._currentPath = path + self._bufferBrowserItems = [] # used when recursive process: fix async signal connection when add object self._browserItems = [] # used only in python side self._browserItemsModel = QObjectListModel(self) # used for UI self._filter = filterFiles @@ -98,6 +99,7 @@ def updateItems(self, recursivePattern): return self.clearItemsSync.emit() + self._bufferBrowserItems.clear() detectOption = sequenceParser.eDetectionDefaultWithDotFile if self._hideDotFiles: detectOption = sequenceParser.eDetectionDefault @@ -146,6 +148,7 @@ def pushBrowserItems(self, allItems, toModel=True): if not self._isSync: itemToAdd.moveToThread(self.thread()) self.addItemSync.emit(itemToAdd, toModel) + self._bufferBrowserItems.append(itemToAdd) @QtCore.pyqtSlot(object, bool) def onAddItemSync(self, bItem, toModel=True): @@ -174,9 +177,8 @@ def searchRecursively(self, pattern, modelRequester): if modelRequester.getParallelThread().isStopped(): return - listToBrowse = self._browserItems + listToBrowse = self._bufferBrowserItems if self == modelRequester: - listToBrowse = self._browserItems.copy() # copy: _browserItems deleted line after modelRequester.clearItemsSync.emit() for bItem in listToBrowse: # 1st pass, all files in current dir @@ -418,4 +420,4 @@ def selectItemTo(self, index): globalBrowser = BrowserModel() -globalBrowserDialog = BrowserModel() +globalBrowserDialog = BrowserModel() \ No newline at end of file From 28014675d6648c8b72f7d8e913dd3643ae8c29ae Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 18 Jul 2015 16:14:01 +0200 Subject: [PATCH 28/33] bModel doc --- buttleofx/gui/browser_v2/browserModel.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/buttleofx/gui/browser_v2/browserModel.py b/buttleofx/gui/browser_v2/browserModel.py index 4b55b33a..9fda596f 100644 --- a/buttleofx/gui/browser_v2/browserModel.py +++ b/buttleofx/gui/browser_v2/browserModel.py @@ -171,13 +171,17 @@ def onClearItemsSync(self): @QtCore.pyqtSlot(str, object) def searchRecursively(self, pattern, modelRequester): """ - Process a recursive search. Disable thumbnail build for sub BrowserModel + Process a recursive search. Disable thumbnail build for sub BrowserModel. + + :param pattern: user input search pattern + :param modelRequester: main model which starts the recursive search """ logging.debug("Start recursive search: %s", self._currentPath) if modelRequester.getParallelThread().isStopped(): return listToBrowse = self._bufferBrowserItems + # Clear on the first level if self == modelRequester: modelRequester.clearItemsSync.emit() @@ -185,6 +189,7 @@ def searchRecursively(self, pattern, modelRequester): logging.debug("processing %s" % bItem.getPath()) if pattern.lower() in bItem.getName().lower(): bItem.moveToThread(modelRequester.thread()) + # Build thumbnails manually only on matching files bItem.startBuildThumbnail() modelRequester.addItemSync.emit(bItem, True) @@ -192,8 +197,11 @@ def searchRecursively(self, pattern, modelRequester): if not modelRequester._isSync and modelRequester.getParallelThread().isStopped(): return if bItem.isFolder(): + # Do not compute thumbnails on all elements, but only manually on matching files. recursiveModel = BrowserModel(bItem.getPath(), True, self._showSeq, self._hideDotFiles, self._filter, buildThumbnail=False) + # Browse items for this folder and fill model recursiveModel.loadData() + # Launch a search on all sub directories recursiveModel.searchRecursively(pattern.lower(), modelRequester) def getFilter(self): @@ -252,7 +260,7 @@ def onSortBrowserItems(self): self._browserItems.sort(key=lambda it: (it.getType(), it.getWeight()), reverse=rev) # sort by folder, file - self._browserItems.sort(key=lambda it: (it.getType())) + self._browserItems.sort(key=lambda it: (it.getType())) # TODO: check needed def searchIndexItem(self, bItem): """ From 832ee97899c4fac1f89fa94e4e3789b992d5d8e3 Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 18 Jul 2015 16:15:56 +0200 Subject: [PATCH 29/33] new style parent constructor --- buttleofx/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buttleofx/main.py b/buttleofx/main.py index 7e444880..38f71ccc 100644 --- a/buttleofx/main.py +++ b/buttleofx/main.py @@ -81,10 +81,10 @@ class EventFilter(QtCore.QObject): def __init__(self, app, engine): + QtCore.QObject.__init(self) self.mainApp = app self.mainEngine = engine self.buttleData = globalButtleData - super(EventFilter, self).__init__() def onSaveDialogButtonClicked(self, fileToSave): self.buttleData.urlOfFileToSave = fileToSave From 77f9a6cdbadb9bc0e4c6dd5a9064ac47427b1ff6 Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 18 Jul 2015 17:36:39 +0200 Subject: [PATCH 30/33] fix new style super constructor --- buttleofx/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buttleofx/main.py b/buttleofx/main.py index 38f71ccc..4e75ed01 100644 --- a/buttleofx/main.py +++ b/buttleofx/main.py @@ -81,7 +81,7 @@ class EventFilter(QtCore.QObject): def __init__(self, app, engine): - QtCore.QObject.__init(self) + QtCore.QObject.__init__(self) self.mainApp = app self.mainEngine = engine self.buttleData = globalButtleData From f51624dcbcee74057a2444186f67493b5f10adb7 Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 18 Jul 2015 17:37:17 +0200 Subject: [PATCH 31/33] set three views mode - Browser Mode - Quick graph mode - Graph mode --- buttleofx/MainWindow.qml | 85 +++++++++++++++++----------------------- 1 file changed, 36 insertions(+), 49 deletions(-) diff --git a/buttleofx/MainWindow.qml b/buttleofx/MainWindow.qml index beff2242..fbff9c39 100644 --- a/buttleofx/MainWindow.qml +++ b/buttleofx/MainWindow.qml @@ -65,11 +65,10 @@ ApplicationWindow { property int selectedView: getSetting("view", 3) - property variant lastSelectedView: selectedView == 1 ? view1: (selectedView == 2 ? view2 : (selectedView == 3 ? view3: view4)) - property variant view1: [browser, paramEditor, player, graphEditor] - property variant view2: [player, paramEditor, browser, graphEditor] - property variant view3: [player, browser, advancedParamEditor, graphEditor] - property variant view4: view2 //just use player and browser (ie index 0&&2) + property variant lastSelectedView: selectedView == 1 ? browserView: (selectedView == 2 ? quickGraphView: graphView ) + property variant browserView: [browser, null, player, null] //mapped to 1 in sql table + property variant quickGraphView: [player, browser, advancedParamEditor, graphEditor] //mapped to 2 in sql table + property variant graphView: [player, paramEditor, browser, graphEditor] //mapped to 3 in sql table property string urlOfFileToSave: _buttleData.urlOfFileToSave @@ -578,50 +577,32 @@ ApplicationWindow { title: "View" MenuItem { - id: defaultView - text: "Default" + id: browserViewMenu + text: "Browser Mode" checkable: true - checked: selectedView == 1 + checked: lastSelectedView === browserView onTriggered: { selectedView = 1 - saveSetting("view",selectedView) - lastSelectedView = view1 + saveSetting("view", selectedView) + lastSelectedView = browserView topLeftView.visible = true - bottomLeftView.visible = true + bottomLeftView.visible = false topRightView.visible = true - bottomRightView.visible = true - rightColumn.width = 0.7 * mainWindowQML.width + bottomRightView.visible = false + rightColumn.width = 0.5 * mainWindowQML.width } } MenuItem { - id: browserView - text: "Browser Mode" + id: quickGraphViewMenu + text: "Quick Graph" checkable: true - checked: selectedView == 2 - + checked: lastSelectedView === quickGraphView onTriggered: { selectedView = 2 - saveSetting("view",selectedView) - lastSelectedView = view2 - topLeftView.visible = true - bottomLeftView.visible = true - topRightView.visible = true - bottomRightView.visible = true - rightColumn.width = 0.7 * mainWindowQML.width - } - } - - MenuItem { - id: advancedView - text: "Quick Mode" - checkable: true - checked: selectedView == 3 - onTriggered: { - selectedView = 3 - saveSetting("view",selectedView) - lastSelectedView = view3 + saveSetting("view", selectedView) + lastSelectedView = quickGraphView topLeftView.visible=true bottomLeftView.visible = true topRightView.visible = true @@ -631,19 +612,19 @@ ApplicationWindow { } MenuItem { - id: simpleView - text: "Simple view" + id: graphViewMenu + text: "Graph" checkable: true - checked: selectedView == 4 + checked: lastSelectedView === graphView onTriggered: { - selectedView = 4 - saveSetting("view",selectedView) - lastSelectedView = view4 + selectedView = 3 + saveSetting("view", selectedView) + lastSelectedView = graphView topLeftView.visible=true topRightView.visible = true - bottomLeftView.visible = false - bottomRightView.visible = false + bottomLeftView.visible = true + bottomRightView.visible = true rightColumn.width = 0.7 * mainWindowQML.width } } @@ -716,6 +697,7 @@ ApplicationWindow { orientation: Qt.Vertical Layout.fillWidth: true Layout.minimumWidth: (topRightView.visible == true || bottomRightView.visible == true) ? 0 : parent.width + width: mainWindowQML.width // will be overrided by right column width Rectangle { id: topLeftView @@ -735,8 +717,8 @@ ApplicationWindow { implicitWidth: parent.width implicitHeight: topLeftView.visible ? 0.5 * parent.height : parent.height z: -1 - children: selectedView != 3 ? lastSelectedView[1]: (advancedParamEditor.displayGraph? view3[3] : view3[1]) - visible: selectedView != 4 + children: lastSelectedView !== quickGraphView ? lastSelectedView[1]: (advancedParamEditor.displayGraph? quickGraphView[3] : quickGraphView[1]) + visible: lastSelectedView !== browserView // not visible if browserView selected } } @@ -747,7 +729,13 @@ ApplicationWindow { orientation: Qt.Vertical Layout.fillWidth: true Layout.minimumWidth: (topLeftView.visible == true || bottomLeftView.visible == true) ? 0 : parent.width - width: selectedView == 3 ? 0.3 * mainWindowQML.width : 0.7 * mainWindowQML.width + + width: if(lastSelectedView === browserView) + 0.5 * mainWindowQML.width + else if(lastSelectedView === quickGraphView) + 0.3 * mainWindowQML.width + else + 0.7 * mainWindowQML.width Rectangle { id: topRightView @@ -767,9 +755,8 @@ ApplicationWindow { implicitWidth: parent.width implicitHeight: topRightView.visible ? 0.5 * parent.height : parent.height z: -1 - visible: selectedView == 1 || selectedView == 2 children: lastSelectedView[3] - + visible: lastSelectedView !== browserView // not visible if browserView selected } } } From 2216250a24815e5b0dd8d7195eee259a1c164a61 Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 18 Jul 2015 17:42:06 +0200 Subject: [PATCH 32/33] fix view visible quickGraph --- buttleofx/MainWindow.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buttleofx/MainWindow.qml b/buttleofx/MainWindow.qml index fbff9c39..e344f045 100644 --- a/buttleofx/MainWindow.qml +++ b/buttleofx/MainWindow.qml @@ -756,7 +756,7 @@ ApplicationWindow { implicitHeight: topRightView.visible ? 0.5 * parent.height : parent.height z: -1 children: lastSelectedView[3] - visible: lastSelectedView !== browserView // not visible if browserView selected + visible: lastSelectedView !== browserView && lastSelectedView !== quickGraphView // not visible if browserView or quickGraphView selected } } } From 340556b1e73c24474d88a5e369501ea3801ef8dd Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 18 Jul 2015 20:37:52 +0200 Subject: [PATCH 33/33] clean views --- buttleofx/MainWindow.qml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/buttleofx/MainWindow.qml b/buttleofx/MainWindow.qml index e344f045..93ff2ccf 100644 --- a/buttleofx/MainWindow.qml +++ b/buttleofx/MainWindow.qml @@ -66,9 +66,12 @@ ApplicationWindow { property int selectedView: getSetting("view", 3) property variant lastSelectedView: selectedView == 1 ? browserView: (selectedView == 2 ? quickGraphView: graphView ) - property variant browserView: [browser, null, player, null] //mapped to 1 in sql table - property variant quickGraphView: [player, browser, advancedParamEditor, graphEditor] //mapped to 2 in sql table - property variant graphView: [player, paramEditor, browser, graphEditor] //mapped to 3 in sql table + + // mapped to int in save settings sql table (i.e selectedView) + // the order follows the layout (topLeft, bottomLeft, topRight, bottomRight) + property variant browserView: [browser, null, player, null] //mapped to 1 + property variant quickGraphView: [player, browser, advancedParamEditor, graphEditor] //mapped to 2 + property variant graphView: [player, paramEditor, browser, graphEditor] //mapped to 3 property string urlOfFileToSave: _buttleData.urlOfFileToSave