mirror of
https://github.com/alicevision/Meshroom.git
synced 2025-08-04 09:18:27 +02:00
[ui] Improve separation between main
and Application
All the actions, file dialogs and message dialogs that are specific to the application are removed from `main.qml` and placed instead in `Application.qml`. Only elements and functions related to the main window remain in `main.qml`. As such, the `ensureSaved` and `ensureNotComputing` functions, used when closing the main window, remain in `main.qml`. Message dialogs that may be opened by these functions however are moved to `Application.qml` as they are application-specific and can't be opened on the homepage. Application dialogs that may need to be called when triggering the closure of the main window are aliased from `Application.qml` to remain callable through the `StackView`.
This commit is contained in:
parent
e0ec0aef55
commit
e0120fd287
2 changed files with 505 additions and 502 deletions
|
@ -16,6 +16,10 @@ import Controls 1.0
|
|||
|
||||
Page {
|
||||
id: root
|
||||
|
||||
property alias computingAtExitDialog: computingAtExitDialog
|
||||
property alias unsavedDialog: unsavedDialog
|
||||
|
||||
Settings {
|
||||
id: settings_UILayout
|
||||
category: 'UILayout'
|
||||
|
@ -26,7 +30,6 @@ Page {
|
|||
}
|
||||
|
||||
// Utility functions for elements in the menubar
|
||||
|
||||
function initFileDialogFolder(dialog, importImages = false) {
|
||||
let folder = "";
|
||||
|
||||
|
@ -50,6 +53,496 @@ Page {
|
|||
dialog.folder = folder
|
||||
}
|
||||
|
||||
function getSelectedNodesName() {
|
||||
if (!_reconstruction)
|
||||
return ""
|
||||
var nodesName = ""
|
||||
for (var i = 0; i < _reconstruction.selectedNodes.count; i++) {
|
||||
if (nodesName !== "")
|
||||
nodesName += ", "
|
||||
var node = _reconstruction.selectedNodes.at(i)
|
||||
nodesName += node.name
|
||||
}
|
||||
return nodesName
|
||||
}
|
||||
|
||||
property url imagesFolder: {
|
||||
var recentImportedImagesFolders = MeshroomApp.recentImportedImagesFolders
|
||||
if (recentImportedImagesFolders.length > 0) {
|
||||
for (var i = 0; i < recentImportedImagesFolders.length; i++) {
|
||||
if (Filepath.exists(recentImportedImagesFolders[i]))
|
||||
return Filepath.stringToUrl(recentImportedImagesFolders[i])
|
||||
else
|
||||
MeshroomApp.removeRecentImportedImagesFolder(Filepath.stringToUrl(recentImportedImagesFolders[i]))
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// File dialogs
|
||||
Platform.FileDialog {
|
||||
id: saveFileDialog
|
||||
options: Platform.FileDialog.DontUseNativeDialog
|
||||
|
||||
signal closed(var result)
|
||||
|
||||
title: "Save File"
|
||||
nameFilters: ["Meshroom Graphs (*.mg)"]
|
||||
defaultSuffix: ".mg"
|
||||
fileMode: Platform.FileDialog.SaveFile
|
||||
onAccepted: {
|
||||
_reconstruction.saveAs(currentFile)
|
||||
closed(Platform.Dialog.Accepted)
|
||||
MeshroomApp.addRecentProjectFile(currentFile.toString())
|
||||
}
|
||||
onRejected: closed(Platform.Dialog.Rejected)
|
||||
}
|
||||
|
||||
Platform.FileDialog {
|
||||
id: saveTemplateDialog
|
||||
options: Platform.FileDialog.DontUseNativeDialog
|
||||
|
||||
signal closed(var result)
|
||||
|
||||
title: "Save Template"
|
||||
nameFilters: ["Meshroom Graphs (*.mg)"]
|
||||
defaultSuffix: ".mg"
|
||||
fileMode: Platform.FileDialog.SaveFile
|
||||
onAccepted: {
|
||||
_reconstruction.saveAsTemplate(currentFile)
|
||||
closed(Platform.Dialog.Accepted)
|
||||
MeshroomApp.reloadTemplateList()
|
||||
}
|
||||
onRejected: closed(Platform.Dialog.Rejected)
|
||||
}
|
||||
|
||||
Platform.FileDialog {
|
||||
id: openFileDialog
|
||||
options: Platform.FileDialog.DontUseNativeDialog
|
||||
title: "Open File"
|
||||
nameFilters: ["Meshroom Graphs (*.mg)"]
|
||||
onAccepted: {
|
||||
if (_reconstruction.loadUrl(currentFile)) {
|
||||
MeshroomApp.addRecentProjectFile(currentFile.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Platform.FileDialog {
|
||||
id: loadTemplateDialog
|
||||
options: Platform.FileDialog.DontUseNativeDialog
|
||||
title: "Load Template"
|
||||
nameFilters: ["Meshroom Graphs (*.mg)"]
|
||||
onAccepted: {
|
||||
// Open the template as a regular file
|
||||
if (_reconstruction.loadUrl(currentFile, true, true)) {
|
||||
MeshroomApp.addRecentProjectFile(currentFile.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Platform.FileDialog {
|
||||
id: importImagesDialog
|
||||
options: Platform.FileDialog.DontUseNativeDialog
|
||||
title: "Import Images"
|
||||
fileMode: Platform.FileDialog.OpenFiles
|
||||
nameFilters: []
|
||||
onAccepted: {
|
||||
_reconstruction.importImagesUrls(currentFiles)
|
||||
imagesFolder = Filepath.dirname(currentFiles[0])
|
||||
MeshroomApp.addRecentImportedImagesFolder(imagesFolder)
|
||||
}
|
||||
}
|
||||
|
||||
Platform.FileDialog {
|
||||
id: importProjectDialog
|
||||
options: Platform.FileDialog.DontUseNativeDialog
|
||||
title: "Import Project"
|
||||
fileMode: Platform.FileDialog.OpenFile
|
||||
nameFilters: ["Meshroom Graphs (*.mg)"]
|
||||
onAccepted: {
|
||||
graphEditor.uigraph.importProject(currentFile)
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: computeManager
|
||||
|
||||
property bool warnIfUnsaved: true
|
||||
|
||||
// Evaluate if graph computation can be submitted externally
|
||||
property bool canSubmit: _reconstruction ?
|
||||
_reconstruction.canSubmit // current setup allows to compute externally
|
||||
&& _reconstruction.graph.filepath : // graph is saved on disk
|
||||
false
|
||||
|
||||
function compute(nodes, force) {
|
||||
if (!force && warnIfUnsaved && !_reconstruction.graph.filepath) {
|
||||
unsavedComputeDialog.selectedNodes = nodes;
|
||||
unsavedComputeDialog.open();
|
||||
}
|
||||
else {
|
||||
try {
|
||||
_reconstruction.execute(nodes)
|
||||
}
|
||||
catch (error) {
|
||||
const data = ErrorHandler.analyseError(error)
|
||||
if (data.context === "COMPUTATION")
|
||||
computeSubmitErrorDialog.openError(data.type, data.msg, nodes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function submit(nodes) {
|
||||
if (!canSubmit) {
|
||||
unsavedSubmitDialog.open()
|
||||
} else {
|
||||
try {
|
||||
_reconstruction.submit(nodes)
|
||||
}
|
||||
catch (error) {
|
||||
const data = ErrorHandler.analyseError(error)
|
||||
if (data.context === "SUBMITTING")
|
||||
computeSubmitErrorDialog.openError(data.type, data.msg, nodes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MessageDialog {
|
||||
id: computeSubmitErrorDialog
|
||||
|
||||
property string errorType // Used to specify signals' behavior
|
||||
property var currentNode: null
|
||||
|
||||
function openError(type, msg, node) {
|
||||
errorType = type
|
||||
switch (type) {
|
||||
case "Already Submitted": {
|
||||
this.setupPendingStatusError(msg, node)
|
||||
break
|
||||
}
|
||||
case "Compatibility Issue": {
|
||||
this.setupCompatibilityIssue(msg)
|
||||
break
|
||||
}
|
||||
default: {
|
||||
this.onlyDisplayError(msg)
|
||||
}
|
||||
}
|
||||
|
||||
this.open()
|
||||
}
|
||||
|
||||
function onlyDisplayError(msg) {
|
||||
text = msg
|
||||
|
||||
standardButtons = Dialog.Ok
|
||||
}
|
||||
|
||||
function setupPendingStatusError(msg, node) {
|
||||
currentNode = node
|
||||
text = msg + "\n\nDo you want to Clear Pending Status and Start Computing?"
|
||||
|
||||
standardButtons = (Dialog.Ok | Dialog.Cancel)
|
||||
}
|
||||
|
||||
function setupCompatibilityIssue(msg) {
|
||||
text = msg + "\n\nDo you want to open the Compatibility Manager?"
|
||||
|
||||
standardButtons = (Dialog.Ok | Dialog.Cancel)
|
||||
}
|
||||
|
||||
canCopy: false
|
||||
icon.text: MaterialIcons.warning
|
||||
parent: Overlay.overlay
|
||||
preset: "Warning"
|
||||
title: "Computation/Submitting"
|
||||
text: ""
|
||||
|
||||
onAccepted: {
|
||||
switch (errorType) {
|
||||
case "Already Submitted": {
|
||||
close()
|
||||
_reconstruction.graph.clearSubmittedNodes()
|
||||
_reconstruction.execute(currentNode)
|
||||
break
|
||||
}
|
||||
case "Compatibility Issue": {
|
||||
close()
|
||||
compatibilityManager.open()
|
||||
break
|
||||
}
|
||||
default: close()
|
||||
}
|
||||
}
|
||||
|
||||
onRejected: close()
|
||||
}
|
||||
|
||||
MessageDialog {
|
||||
id: unsavedComputeDialog
|
||||
|
||||
property var selectedNodes: null
|
||||
|
||||
canCopy: false
|
||||
icon.text: MaterialIcons.warning
|
||||
parent: Overlay.overlay
|
||||
preset: "Warning"
|
||||
title: "Unsaved Project"
|
||||
text: "Data will be computed in the default cache folder if project remains unsaved."
|
||||
detailedText: "Default cache folder: " + (_reconstruction ? _reconstruction.graph.cacheDir : "unknown")
|
||||
helperText: "Save project first?"
|
||||
standardButtons: Dialog.Discard | Dialog.Cancel | Dialog.Save
|
||||
|
||||
CheckBox {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
text: "Don't ask again for this session"
|
||||
padding: 0
|
||||
onToggled: computeManager.warnIfUnsaved = !checked
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
// Set up discard button text
|
||||
standardButton(Dialog.Discard).text = "Continue without Saving"
|
||||
}
|
||||
|
||||
onDiscarded: {
|
||||
close()
|
||||
computeManager.compute(selectedNodes, true)
|
||||
}
|
||||
|
||||
onAccepted: saveAsAction.trigger()
|
||||
}
|
||||
|
||||
MessageDialog {
|
||||
id: unsavedSubmitDialog
|
||||
|
||||
canCopy: false
|
||||
icon.text: MaterialIcons.warning
|
||||
parent: Overlay.overlay
|
||||
preset: "Warning"
|
||||
title: "Unsaved Project"
|
||||
text: "The project cannot be submitted if it remains unsaved."
|
||||
helperText: "Save the project to be able to submit it?"
|
||||
standardButtons: Dialog.Cancel | Dialog.Save
|
||||
|
||||
onDiscarded: close()
|
||||
onAccepted: saveAsAction.trigger()
|
||||
}
|
||||
|
||||
MessageDialog {
|
||||
id: fileModifiedDialog
|
||||
|
||||
canCopy: false
|
||||
icon.text: MaterialIcons.warning
|
||||
parent: Overlay.overlay
|
||||
preset: "Warning"
|
||||
title: "File Modified"
|
||||
text: "The file has been modified by another instance."
|
||||
detailedText: "Do you want to overwrite the file?"
|
||||
|
||||
// Add a reload file button next to the save button
|
||||
footer: DialogButtonBox {
|
||||
position: DialogButtonBox.Footer
|
||||
standardButtons: Dialog.Save | Dialog.Cancel
|
||||
|
||||
Button {
|
||||
text: "Reload File"
|
||||
|
||||
onClicked: {
|
||||
_reconstruction.loadUrl(_reconstruction.graph.filepath)
|
||||
fileModifiedDialog.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onAccepted: _reconstruction.save()
|
||||
onDiscarded: close()
|
||||
}
|
||||
}
|
||||
|
||||
// Message dialogs
|
||||
MessageDialog {
|
||||
id: unsavedDialog
|
||||
|
||||
property var _callback: undefined
|
||||
|
||||
title: (_reconstruction ? Filepath.basename(_reconstruction.graph.filepath) : "") || "Unsaved Project"
|
||||
preset: "Info"
|
||||
canCopy: false
|
||||
text: _reconstruction && _reconstruction.graph.filepath ? "Current project has unsaved modifications."
|
||||
: "Current project has not been saved."
|
||||
helperText: _reconstruction && _reconstruction.graph.filepath ? "Would you like to save those changes?"
|
||||
: "Would you like to save this project?"
|
||||
|
||||
standardButtons: Dialog.Save | Dialog.Cancel | Dialog.Discard
|
||||
|
||||
onDiscarded: {
|
||||
close() // BUG ? discard does not close window
|
||||
fireCallback()
|
||||
}
|
||||
|
||||
onAccepted: {
|
||||
// Save current file
|
||||
if (saveAction.enabled)
|
||||
{
|
||||
saveAction.trigger()
|
||||
fireCallback()
|
||||
}
|
||||
// Open "Save As" dialog
|
||||
else
|
||||
{
|
||||
saveFileDialog.open()
|
||||
function _callbackWrapper(rc) {
|
||||
if (rc === Platform.Dialog.Accepted)
|
||||
fireCallback()
|
||||
saveFileDialog.closed.disconnect(_callbackWrapper)
|
||||
}
|
||||
saveFileDialog.closed.disconnect(_callbackWrapper)
|
||||
}
|
||||
}
|
||||
|
||||
function fireCallback()
|
||||
{
|
||||
// Call the callback and reset it
|
||||
if (_callback)
|
||||
_callback()
|
||||
_callback = undefined
|
||||
}
|
||||
|
||||
/// Open the unsaved dialog warning with an optional
|
||||
/// callback to fire when the dialog is accepted/discarded
|
||||
function prompt(callback)
|
||||
{
|
||||
_callback = callback
|
||||
open()
|
||||
}
|
||||
}
|
||||
|
||||
MessageDialog {
|
||||
id: computingAtExitDialog
|
||||
title: "Operation in progress"
|
||||
modal: true
|
||||
canCopy: false
|
||||
Label {
|
||||
text: "Please stop any local computation before exiting Meshroom"
|
||||
}
|
||||
}
|
||||
|
||||
MessageDialog {
|
||||
// Popup displayed while the application
|
||||
// is busy building intrinsics while importing images
|
||||
id: buildingIntrinsicsDialog
|
||||
modal: true
|
||||
visible: _reconstruction ? _reconstruction.buildingIntrinsics : false
|
||||
closePolicy: Popup.NoAutoClose
|
||||
title: "Initializing Cameras"
|
||||
icon.text: MaterialIcons.camera
|
||||
icon.font.pointSize: 10
|
||||
canCopy: false
|
||||
standardButtons: Dialog.NoButton
|
||||
|
||||
detailedText: "Extracting images metadata and creating Camera intrinsics..."
|
||||
ProgressBar {
|
||||
indeterminate: true
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
|
||||
AboutDialog {
|
||||
id: aboutDialog
|
||||
}
|
||||
|
||||
DialogsFactory {
|
||||
id: dialogsFactory
|
||||
}
|
||||
|
||||
CompatibilityManager {
|
||||
id: compatibilityManager
|
||||
uigraph: _reconstruction
|
||||
}
|
||||
|
||||
|
||||
// Actions
|
||||
Action {
|
||||
id: removeAllImagesAction
|
||||
property string tooltip: "Remove all the images from the current CameraInit group"
|
||||
text: "Remove All Images"
|
||||
onTriggered: {
|
||||
_reconstruction.removeAllImages()
|
||||
_reconstruction.selectedViewId = "-1"
|
||||
}
|
||||
}
|
||||
|
||||
Action {
|
||||
id: removeImagesFromAllGroupsAction
|
||||
property string tooltip: "Remove all the images from all the CameraInit groups"
|
||||
text: "Remove Images From All CameraInit Nodes"
|
||||
onTriggered: {
|
||||
_reconstruction.removeImagesFromAllGroups()
|
||||
_reconstruction.selectedViewId = "-1"
|
||||
}
|
||||
}
|
||||
|
||||
Action {
|
||||
id: undoAction
|
||||
|
||||
property string tooltip: 'Undo "' + (_reconstruction ? _reconstruction.undoStack.undoText : "Unknown") + '"'
|
||||
text: "Undo"
|
||||
shortcut: "Ctrl+Z"
|
||||
enabled: _reconstruction ? _reconstruction.undoStack.canUndo && _reconstruction.undoStack.isUndoableIndex : false
|
||||
onTriggered: _reconstruction.undoStack.undo()
|
||||
}
|
||||
|
||||
Action {
|
||||
id: redoAction
|
||||
|
||||
property string tooltip: 'Redo "' + (_reconstruction ? _reconstruction.undoStack.redoText : "Unknown") + '"'
|
||||
text: "Redo"
|
||||
shortcut: "Ctrl+Shift+Z"
|
||||
enabled: _reconstruction ? _reconstruction.undoStack.canRedo && !_reconstruction.undoStack.lockedRedo : false
|
||||
onTriggered: _reconstruction.undoStack.redo()
|
||||
}
|
||||
|
||||
Action {
|
||||
id: cutAction
|
||||
|
||||
property string tooltip: {
|
||||
var s = "Copy selected node"
|
||||
s += (_reconstruction && _reconstruction.selectedNodes.count > 1 ? "s (" : " (") + getSelectedNodesName()
|
||||
s += ") to the clipboard and remove them from the graph"
|
||||
return s
|
||||
}
|
||||
text: "Cut Node" + (_reconstruction && _reconstruction.selectedNodes.count > 1 ? "s " : " ")
|
||||
enabled: _reconstruction ? _reconstruction.selectedNodes.count > 0 : false
|
||||
onTriggered: {
|
||||
graphEditor.copyNodes()
|
||||
graphEditor.uigraph.removeNodes(graphEditor.uigraph.selectedNodes)
|
||||
}
|
||||
}
|
||||
|
||||
Action {
|
||||
id: copyAction
|
||||
|
||||
property string tooltip: {
|
||||
var s = "Copy selected node"
|
||||
s += (_reconstruction && _reconstruction.selectedNodes.count > 1 ? "s (" : " (") + getSelectedNodesName()
|
||||
s += ") to the clipboard"
|
||||
return s
|
||||
}
|
||||
text: "Copy Node" + (_reconstruction && _reconstruction.selectedNodes.count > 1 ? "s " : " ")
|
||||
enabled: _reconstruction ? _reconstruction.selectedNodes.count > 0 : false
|
||||
onTriggered: graphEditor.copyNodes()
|
||||
}
|
||||
|
||||
Action {
|
||||
id: pasteAction
|
||||
|
||||
property string tooltip: "Paste the clipboard content to the project if it contains valid nodes"
|
||||
text: "Paste Node(s)"
|
||||
onTriggered: graphEditor.pasteNodes()
|
||||
}
|
||||
|
||||
Action {
|
||||
id: loadTemplateAction
|
||||
|
||||
|
@ -185,10 +678,10 @@ Page {
|
|||
enabled: _reconstruction ? (_reconstruction.graph && !_reconstruction.graph.filepath) || !_reconstruction.undoStack.clean : false
|
||||
onTriggered: {
|
||||
if (_reconstruction.graph.filepath) {
|
||||
// get current time date
|
||||
// Get current time date
|
||||
var date = _reconstruction.graph.getFileDateVersionFromPath(_reconstruction.graph.filepath)
|
||||
|
||||
// check if the file has been modified by another instance
|
||||
// Check if the file has been modified by another instance
|
||||
if (_reconstruction.graph.fileDateVersion !== date) {
|
||||
fileModifiedDialog.open()
|
||||
} else
|
||||
|
@ -368,7 +861,7 @@ Page {
|
|||
Action {
|
||||
text: "About Meshroom"
|
||||
onTriggered: aboutDialog.open()
|
||||
// should be StandardKey.HelpContents, but for some reason it's not stable
|
||||
// Should be StandardKey.HelpContents, but for some reason it's not stable
|
||||
// (may cause crash, requires pressing F1 twice after closing the popup)
|
||||
shortcut: "F1"
|
||||
}
|
||||
|
@ -477,8 +970,7 @@ Page {
|
|||
target: _reconstruction
|
||||
|
||||
// Bind messages to DialogsFactory
|
||||
function createDialog(func, message)
|
||||
{
|
||||
function createDialog(func, message) {
|
||||
var dialog = func(_window)
|
||||
// Set text afterwards to avoid dialog sizing issues
|
||||
dialog.title = message.title
|
||||
|
@ -487,10 +979,10 @@ Page {
|
|||
}
|
||||
|
||||
function onGraphChanged() {
|
||||
// open CompatibilityManager after file loading if any issue is detected
|
||||
// Open CompatibilityManager after file loading if any issue is detected
|
||||
if (compatibilityManager.issueCount)
|
||||
compatibilityManager.open()
|
||||
// trigger fit to visualize all nodes
|
||||
// Trigger fit to visualize all nodes
|
||||
graphEditor.fit()
|
||||
}
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ ApplicationWindow {
|
|||
}
|
||||
|
||||
onClosing: {
|
||||
// make sure document is saved before exiting application
|
||||
// Make sure document is saved before exiting application
|
||||
close.accepted = false
|
||||
if (!ensureNotComputing())
|
||||
return
|
||||
|
@ -70,19 +70,6 @@ ApplicationWindow {
|
|||
SystemPalette { id: activePalette }
|
||||
SystemPalette { id: disabledPalette; colorGroup: SystemPalette.Disabled }
|
||||
|
||||
property url imagesFolder: {
|
||||
var recentImportedImagesFolders = MeshroomApp.recentImportedImagesFolders
|
||||
if (recentImportedImagesFolders.length > 0) {
|
||||
for (var i = 0; i < recentImportedImagesFolders.length; i++) {
|
||||
if (Filepath.exists(recentImportedImagesFolders[i]))
|
||||
return Filepath.stringToUrl(recentImportedImagesFolders[i])
|
||||
else
|
||||
MeshroomApp.removeRecentImportedImagesFolder(Filepath.stringToUrl(recentImportedImagesFolders[i]))
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
Settings {
|
||||
id: settings_General
|
||||
category: 'General'
|
||||
|
@ -91,511 +78,35 @@ ApplicationWindow {
|
|||
}
|
||||
|
||||
Component.onDestruction: {
|
||||
// store main window dimensions in persisting Settings
|
||||
// Store main window dimensions in persisting Settings
|
||||
settings_General.windowWidth = _window.width
|
||||
settings_General.windowHeight = _window.height
|
||||
}
|
||||
|
||||
MessageDialog {
|
||||
id: unsavedDialog
|
||||
|
||||
property var _callback: undefined
|
||||
|
||||
title: (_reconstruction ? Filepath.basename(_reconstruction.graph.filepath) : "") || "Unsaved Project"
|
||||
preset: "Info"
|
||||
canCopy: false
|
||||
text: _reconstruction && _reconstruction.graph.filepath ? "Current project has unsaved modifications."
|
||||
: "Current project has not been saved."
|
||||
helperText: _reconstruction && _reconstruction.graph.filepath ? "Would you like to save those changes?"
|
||||
: "Would you like to save this project?"
|
||||
|
||||
standardButtons: Dialog.Save | Dialog.Cancel | Dialog.Discard
|
||||
|
||||
onDiscarded: {
|
||||
close() // BUG ? discard does not close window
|
||||
fireCallback()
|
||||
}
|
||||
|
||||
onAccepted: {
|
||||
// save current file
|
||||
if (saveAction.enabled)
|
||||
{
|
||||
saveAction.trigger()
|
||||
fireCallback()
|
||||
}
|
||||
// open "save as" dialog
|
||||
else
|
||||
{
|
||||
saveFileDialog.open()
|
||||
function _callbackWrapper(rc) {
|
||||
if (rc === Platform.Dialog.Accepted)
|
||||
fireCallback()
|
||||
saveFileDialog.closed.disconnect(_callbackWrapper)
|
||||
}
|
||||
saveFileDialog.closed.connect(_callbackWrapper)
|
||||
}
|
||||
}
|
||||
|
||||
function fireCallback()
|
||||
{
|
||||
// call the callback and reset it
|
||||
if (_callback)
|
||||
_callback()
|
||||
_callback = undefined
|
||||
}
|
||||
|
||||
/// Open the unsaved dialog warning with an optional
|
||||
/// callback to fire when the dialog is accepted/discarded
|
||||
function prompt(callback)
|
||||
{
|
||||
_callback = callback
|
||||
open()
|
||||
}
|
||||
}
|
||||
|
||||
Platform.FileDialog {
|
||||
id: saveFileDialog
|
||||
options: Platform.FileDialog.DontUseNativeDialog
|
||||
|
||||
signal closed(var result)
|
||||
|
||||
title: "Save File"
|
||||
nameFilters: ["Meshroom Graphs (*.mg)"]
|
||||
defaultSuffix: ".mg"
|
||||
fileMode: Platform.FileDialog.SaveFile
|
||||
onAccepted: {
|
||||
_reconstruction.saveAs(currentFile)
|
||||
closed(Platform.Dialog.Accepted)
|
||||
MeshroomApp.addRecentProjectFile(currentFile.toString())
|
||||
}
|
||||
onRejected: closed(Platform.Dialog.Rejected)
|
||||
}
|
||||
|
||||
Platform.FileDialog {
|
||||
id: saveTemplateDialog
|
||||
options: Platform.FileDialog.DontUseNativeDialog
|
||||
|
||||
signal closed(var result)
|
||||
|
||||
title: "Save Template"
|
||||
nameFilters: ["Meshroom Graphs (*.mg)"]
|
||||
defaultSuffix: ".mg"
|
||||
fileMode: Platform.FileDialog.SaveFile
|
||||
onAccepted: {
|
||||
_reconstruction.saveAsTemplate(currentFile)
|
||||
closed(Platform.Dialog.Accepted)
|
||||
MeshroomApp.reloadTemplateList()
|
||||
}
|
||||
onRejected: closed(Platform.Dialog.Rejected)
|
||||
}
|
||||
|
||||
Item {
|
||||
id: computeManager
|
||||
|
||||
property bool warnIfUnsaved: true
|
||||
|
||||
// evaluate if graph computation can be submitted externally
|
||||
property bool canSubmit: _reconstruction ?
|
||||
_reconstruction.canSubmit // current setup allows to compute externally
|
||||
&& _reconstruction.graph.filepath : // graph is saved on disk
|
||||
false
|
||||
|
||||
function compute(nodes, force) {
|
||||
if (!force && warnIfUnsaved && !_reconstruction.graph.filepath)
|
||||
{
|
||||
unsavedComputeDialog.selectedNodes = nodes;
|
||||
unsavedComputeDialog.open();
|
||||
}
|
||||
else {
|
||||
try {
|
||||
_reconstruction.execute(nodes)
|
||||
}
|
||||
catch (error) {
|
||||
const data = ErrorHandler.analyseError(error)
|
||||
if (data.context === "COMPUTATION")
|
||||
computeSubmitErrorDialog.openError(data.type, data.msg, nodes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function submit(nodes) {
|
||||
if (!canSubmit) {
|
||||
unsavedSubmitDialog.open()
|
||||
} else {
|
||||
try {
|
||||
_reconstruction.submit(nodes)
|
||||
}
|
||||
catch (error) {
|
||||
const data = ErrorHandler.analyseError(error)
|
||||
if (data.context === "SUBMITTING")
|
||||
computeSubmitErrorDialog.openError(data.type, data.msg, nodes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MessageDialog {
|
||||
id: computeSubmitErrorDialog
|
||||
|
||||
property string errorType // Used to specify signals' behavior
|
||||
property var currentNode: null
|
||||
|
||||
function openError(type, msg, node) {
|
||||
errorType = type
|
||||
switch (type) {
|
||||
case "Already Submitted": {
|
||||
this.setupPendingStatusError(msg, node)
|
||||
break
|
||||
}
|
||||
case "Compatibility Issue": {
|
||||
this.setupCompatibilityIssue(msg)
|
||||
break
|
||||
}
|
||||
default: {
|
||||
this.onlyDisplayError(msg)
|
||||
}
|
||||
}
|
||||
|
||||
this.open()
|
||||
}
|
||||
|
||||
function onlyDisplayError(msg) {
|
||||
text = msg
|
||||
|
||||
standardButtons = Dialog.Ok
|
||||
}
|
||||
|
||||
function setupPendingStatusError(msg, node) {
|
||||
currentNode = node
|
||||
text = msg + "\n\nDo you want to Clear Pending Status and Start Computing?"
|
||||
|
||||
standardButtons = (Dialog.Ok | Dialog.Cancel)
|
||||
}
|
||||
|
||||
function setupCompatibilityIssue(msg) {
|
||||
text = msg + "\n\nDo you want to open the Compatibility Manager?"
|
||||
|
||||
standardButtons = (Dialog.Ok | Dialog.Cancel)
|
||||
}
|
||||
|
||||
canCopy: false
|
||||
icon.text: MaterialIcons.warning
|
||||
parent: Overlay.overlay
|
||||
preset: "Warning"
|
||||
title: "Computation/Submitting"
|
||||
text: ""
|
||||
|
||||
onAccepted: {
|
||||
switch (errorType) {
|
||||
case "Already Submitted": {
|
||||
close()
|
||||
_reconstruction.graph.clearSubmittedNodes()
|
||||
_reconstruction.execute(currentNode)
|
||||
break
|
||||
}
|
||||
case "Compatibility Issue": {
|
||||
close()
|
||||
compatibilityManager.open()
|
||||
break
|
||||
}
|
||||
default: close()
|
||||
}
|
||||
}
|
||||
|
||||
onRejected: close()
|
||||
}
|
||||
|
||||
MessageDialog {
|
||||
id: unsavedComputeDialog
|
||||
|
||||
property var selectedNodes: null
|
||||
|
||||
canCopy: false
|
||||
icon.text: MaterialIcons.warning
|
||||
parent: Overlay.overlay
|
||||
preset: "Warning"
|
||||
title: "Unsaved Project"
|
||||
text: "Data will be computed in the default cache folder if project remains unsaved."
|
||||
detailedText: "Default cache folder: " + (_reconstruction ? _reconstruction.graph.cacheDir : "unknown")
|
||||
helperText: "Save project first?"
|
||||
standardButtons: Dialog.Discard | Dialog.Cancel | Dialog.Save
|
||||
|
||||
CheckBox {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
text: "Don't ask again for this session"
|
||||
padding: 0
|
||||
onToggled: computeManager.warnIfUnsaved = !checked
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
// set up discard button text
|
||||
standardButton(Dialog.Discard).text = "Continue without Saving"
|
||||
}
|
||||
|
||||
onDiscarded: {
|
||||
close()
|
||||
computeManager.compute(selectedNodes, true)
|
||||
}
|
||||
|
||||
onAccepted: saveAsAction.trigger()
|
||||
}
|
||||
|
||||
MessageDialog {
|
||||
id: unsavedSubmitDialog
|
||||
|
||||
canCopy: false
|
||||
icon.text: MaterialIcons.warning
|
||||
parent: Overlay.overlay
|
||||
preset: "Warning"
|
||||
title: "Unsaved Project"
|
||||
text: "The project cannot be submitted if it remains unsaved."
|
||||
helperText: "Save the project to be able to submit it?"
|
||||
standardButtons: Dialog.Cancel | Dialog.Save
|
||||
|
||||
onDiscarded: close()
|
||||
onAccepted: saveAsAction.trigger()
|
||||
}
|
||||
|
||||
MessageDialog {
|
||||
id: fileModifiedDialog
|
||||
|
||||
canCopy: false
|
||||
icon.text: MaterialIcons.warning
|
||||
parent: Overlay.overlay
|
||||
preset: "Warning"
|
||||
title: "File Modified"
|
||||
text: "The file has been modified by another instance."
|
||||
detailedText: "Do you want to overwrite the file?"
|
||||
|
||||
// Add a reload file button next to the save button
|
||||
footer: DialogButtonBox {
|
||||
position: DialogButtonBox.Footer
|
||||
standardButtons: Dialog.Save | Dialog.Cancel
|
||||
|
||||
Button {
|
||||
text: "Reload File"
|
||||
|
||||
onClicked: {
|
||||
_reconstruction.loadUrl(_reconstruction.graph.filepath)
|
||||
fileModifiedDialog.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onAccepted: _reconstruction.save()
|
||||
onDiscarded: close()
|
||||
}
|
||||
}
|
||||
|
||||
Platform.FileDialog {
|
||||
id: openFileDialog
|
||||
options: Platform.FileDialog.DontUseNativeDialog
|
||||
title: "Open File"
|
||||
nameFilters: ["Meshroom Graphs (*.mg)"]
|
||||
onAccepted: {
|
||||
if (_reconstruction.loadUrl(currentFile)) {
|
||||
MeshroomApp.addRecentProjectFile(currentFile.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Platform.FileDialog {
|
||||
id: loadTemplateDialog
|
||||
options: Platform.FileDialog.DontUseNativeDialog
|
||||
title: "Load Template"
|
||||
nameFilters: ["Meshroom Graphs (*.mg)"]
|
||||
onAccepted: {
|
||||
// Open the template as a regular file
|
||||
if (_reconstruction.loadUrl(currentFile, true, true)) {
|
||||
MeshroomApp.addRecentProjectFile(currentFile.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Platform.FileDialog {
|
||||
id: importImagesDialog
|
||||
options: Platform.FileDialog.DontUseNativeDialog
|
||||
title: "Import Images"
|
||||
fileMode: Platform.FileDialog.OpenFiles
|
||||
nameFilters: []
|
||||
onAccepted: {
|
||||
_reconstruction.importImagesUrls(currentFiles)
|
||||
imagesFolder = Filepath.dirname(currentFiles[0])
|
||||
MeshroomApp.addRecentImportedImagesFolder(imagesFolder)
|
||||
}
|
||||
}
|
||||
|
||||
Platform.FileDialog {
|
||||
id: importProjectDialog
|
||||
options: Platform.FileDialog.DontUseNativeDialog
|
||||
title: "Import Project"
|
||||
fileMode: Platform.FileDialog.OpenFile
|
||||
nameFilters: ["Meshroom Graphs (*.mg)"]
|
||||
onAccepted: {
|
||||
graphEditor.uigraph.importProject(currentFile)
|
||||
}
|
||||
}
|
||||
|
||||
AboutDialog {
|
||||
id: aboutDialog
|
||||
}
|
||||
|
||||
// Check if document has been saved
|
||||
function ensureSaved(callback)
|
||||
{
|
||||
var saved = _reconstruction.undoStack.clean
|
||||
if (!saved) { // If current document is modified, open "unsaved dialog"
|
||||
unsavedDialog.prompt(callback)
|
||||
mainStack.currentItem.unsavedDialog.prompt(callback)
|
||||
} else { // Otherwise, directly call the callback
|
||||
callback()
|
||||
}
|
||||
return saved
|
||||
}
|
||||
|
||||
MessageDialog {
|
||||
id: computingAtExitDialog
|
||||
title: "Operation in progress"
|
||||
modal: true
|
||||
canCopy: false
|
||||
Label {
|
||||
text: "Please stop any local computation before exiting Meshroom"
|
||||
}
|
||||
}
|
||||
|
||||
// Check and return whether no local computation is in progress
|
||||
function ensureNotComputing()
|
||||
{
|
||||
if (_reconstruction.computingLocally)
|
||||
{
|
||||
// Open a warning dialog to ask for computation to be stopped
|
||||
computingAtExitDialog.open()
|
||||
mainStack.currentItem.computingAtExitDialog.open()
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
MessageDialog {
|
||||
// Popup displayed while the application
|
||||
// is busy building intrinsics while importing images
|
||||
id: buildingIntrinsicsDialog
|
||||
modal: true
|
||||
visible: _reconstruction ? _reconstruction.buildingIntrinsics : false
|
||||
closePolicy: Popup.NoAutoClose
|
||||
title: "Initializing Cameras"
|
||||
icon.text: MaterialIcons.camera
|
||||
icon.font.pointSize: 10
|
||||
canCopy: false
|
||||
standardButtons: Dialog.NoButton
|
||||
|
||||
detailedText: "Extracting images metadata and creating Camera intrinsics..."
|
||||
ProgressBar {
|
||||
indeterminate: true
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
|
||||
DialogsFactory {
|
||||
id: dialogsFactory
|
||||
}
|
||||
|
||||
CompatibilityManager {
|
||||
id: compatibilityManager
|
||||
uigraph: _reconstruction
|
||||
}
|
||||
|
||||
Action {
|
||||
id: removeAllImagesAction
|
||||
property string tooltip: "Remove all the images from the current CameraInit group"
|
||||
text: "Remove All Images"
|
||||
onTriggered: {
|
||||
_reconstruction.removeAllImages()
|
||||
_reconstruction.selectedViewId = "-1"
|
||||
}
|
||||
}
|
||||
|
||||
Action {
|
||||
id: removeImagesFromAllGroupsAction
|
||||
property string tooltip: "Remove all the images from all the CameraInit groups"
|
||||
text: "Remove Images From All CameraInit Nodes"
|
||||
onTriggered: {
|
||||
_reconstruction.removeImagesFromAllGroups()
|
||||
_reconstruction.selectedViewId = "-1"
|
||||
}
|
||||
}
|
||||
|
||||
Action {
|
||||
id: undoAction
|
||||
|
||||
property string tooltip: 'Undo "' + (_reconstruction ? _reconstruction.undoStack.undoText : "Unknown") + '"'
|
||||
text: "Undo"
|
||||
shortcut: "Ctrl+Z"
|
||||
enabled: _reconstruction ? _reconstruction.undoStack.canUndo && _reconstruction.undoStack.isUndoableIndex : false
|
||||
onTriggered: _reconstruction.undoStack.undo()
|
||||
}
|
||||
Action {
|
||||
id: redoAction
|
||||
|
||||
property string tooltip: 'Redo "' + (_reconstruction ? _reconstruction.undoStack.redoText : "Unknown") + '"'
|
||||
text: "Redo"
|
||||
shortcut: "Ctrl+Shift+Z"
|
||||
enabled: _reconstruction ? _reconstruction.undoStack.canRedo && !_reconstruction.undoStack.lockedRedo : false
|
||||
onTriggered: _reconstruction.undoStack.redo()
|
||||
}
|
||||
|
||||
function getSelectedNodesName()
|
||||
{
|
||||
if (!_reconstruction)
|
||||
return ""
|
||||
var nodesName = ""
|
||||
for (var i = 0; i < _reconstruction.selectedNodes.count; i++)
|
||||
{
|
||||
if (nodesName !== "")
|
||||
nodesName += ", "
|
||||
var node = _reconstruction.selectedNodes.at(i)
|
||||
nodesName += node.name
|
||||
}
|
||||
return nodesName
|
||||
}
|
||||
Action {
|
||||
id: cutAction
|
||||
|
||||
property string tooltip: {
|
||||
var s = "Copy selected node"
|
||||
s += (_reconstruction && _reconstruction.selectedNodes.count > 1 ? "s (" : " (") + getSelectedNodesName()
|
||||
s += ") to the clipboard and remove them from the graph"
|
||||
return s
|
||||
}
|
||||
text: "Cut Node" + (_reconstruction && _reconstruction.selectedNodes.count > 1 ? "s " : " ")
|
||||
enabled: _reconstruction ? _reconstruction.selectedNodes.count > 0 : false
|
||||
onTriggered: {
|
||||
graphEditor.copyNodes()
|
||||
graphEditor.uigraph.removeNodes(graphEditor.uigraph.selectedNodes)
|
||||
}
|
||||
}
|
||||
|
||||
Action {
|
||||
id: copyAction
|
||||
|
||||
property string tooltip: {
|
||||
var s = "Copy selected node"
|
||||
s += (_reconstruction && _reconstruction.selectedNodes.count > 1 ? "s (" : " (") + getSelectedNodesName()
|
||||
s += ") to the clipboard"
|
||||
return s
|
||||
}
|
||||
text: "Copy Node" + (_reconstruction && _reconstruction.selectedNodes.count > 1 ? "s " : " ")
|
||||
enabled: _reconstruction ? _reconstruction.selectedNodes.count > 0 : false
|
||||
onTriggered: graphEditor.copyNodes()
|
||||
}
|
||||
|
||||
Action {
|
||||
id: pasteAction
|
||||
|
||||
property string tooltip: "Paste the clipboard content to the project if it contains valid nodes"
|
||||
text: "Paste Node(s)"
|
||||
onTriggered: graphEditor.pasteNodes()
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// TODO: uncomment for Qt6 to re-enable the alternative palette (the alternative palette and the disabled items currently cannot both be supported)
|
||||
/* Action {
|
||||
|
@ -629,4 +140,4 @@ ApplicationWindow {
|
|||
forceActiveFocus();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue