diff --git a/meshroom/ui/qml/GraphEditor/NodeEditor.qml b/meshroom/ui/qml/GraphEditor/NodeEditor.qml index c92c3e3db1..7158edef24 100644 --- a/meshroom/ui/qml/GraphEditor/NodeEditor.qml +++ b/meshroom/ui/qml/GraphEditor/NodeEditor.qml @@ -275,7 +275,7 @@ Panel { toggle: true // Enable toggling the actual text field by the search button Layout.minimumWidth: searchBar.width maxWidth: 150 - enabled: tabBar.currentIndex === 0 || tabBar.currentIndex === 5 + enabled: tabBar.currentIndex === 0 || tabBar.currentIndex === 6 } MaterialToolButton { @@ -540,6 +540,19 @@ Panel { } } + Loader { + active: (tabBar.currentIndex === 4) + Layout.fillHeight: true + Layout.fillWidth: true + sourceComponent: NodeFileBrowser { + id: nodeFileBrowser + + Layout.fillHeight: true + Layout.fillWidth: true + node: root.node + } + } + NodeDocumentation { id: nodeDocumentation @@ -580,7 +593,7 @@ Panel { property bool isBackdropNode: root.node !== null && root.node.isBackdropNode // The indices of the tab bar which can be shown for incomputable nodes - readonly property var nonComputableTabIndices: [0, 4, 5] + readonly property var nonComputableTabIndices: [0, 5, 6] Layout.fillWidth: true width: childrenRect.width @@ -625,6 +638,13 @@ Panel { leftPadding: 8 rightPadding: leftPadding } + TabButton { + visible: tabBar.isComputableType + width: !visible ? 0 : tabBar.width / tabBar.count + text: "Files" + leftPadding: 8 + rightPadding: leftPadding + } TabButton { text: "Documentation" leftPadding: 8 @@ -643,7 +663,7 @@ Panel { if ((root.node && !root.node.isComputableType) && (nonComputableTabIndices.indexOf(tabBar.currentIndex) === -1)) { if (root.node.isBackdropNode) { // Backdrop nodes can only show the Documentation & Notes tabs - tabBar.currentIndex = 4 // Notes tab + tabBar.currentIndex = 5 // Documentation tab } else { tabBar.currentIndex = 0 } diff --git a/meshroom/ui/qml/GraphEditor/NodeFileBrowser.qml b/meshroom/ui/qml/GraphEditor/NodeFileBrowser.qml new file mode 100644 index 0000000000..0a78f01ecf --- /dev/null +++ b/meshroom/ui/qml/GraphEditor/NodeFileBrowser.qml @@ -0,0 +1,180 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Qt.labs.folderlistmodel + +import Controls 1.0 +import MaterialIcons 2.2 +import Utils 1.0 + +/** + * NodeFileBrowser displays the cache folder of a Node as a navigable file browser. + */ +FocusScope { + id: root + + property variant node: null + + // The root folder URL (node's internal cache folder) + readonly property url rootFolderUrl: node ? Filepath.stringToUrl(node.internalFolder) : "" + // Currently displayed folder URL + property url currentFolder: rootFolderUrl + + // Reset to root folder when node changes + onRootFolderUrlChanged: { + root.currentFolder = root.rootFolderUrl + } + + SystemPalette { id: activePalette } + + FolderListModel { + id: folderModel + folder: root.currentFolder + showFiles: true + showDirs: true + showDirsFirst: true + showHidden: false + sortField: FolderListModel.Name + nameFilters: ["*"] + } + + ColumnLayout { + anchors.fill: parent + spacing: 0 + + // Toolbar: navigate up button, current path label, open-in-OS button + ToolBar { + Layout.fillWidth: true + + RowLayout { + anchors.fill: parent + spacing: 2 + + // Navigate up button + MaterialToolButton { + text: MaterialIcons.arrow_upward + font.pointSize: 11 + padding: 4 + enabled: root.currentFolder.toString() !== root.rootFolderUrl.toString() + ToolTip.text: "Go to parent folder" + ToolTip.visible: hovered + onClicked: { + root.currentFolder = Filepath.stringToUrl(Filepath.dirname(Filepath.urlToString(root.currentFolder))) + } + } + + // Current folder path label + Label { + id: pathLabel + Layout.fillWidth: true + elide: Text.ElideLeft + text: root.node ? Filepath.urlToString(root.currentFolder) : "" + ToolTip.text: text + ToolTip.visible: hovered && truncated + font.pointSize: 8 + verticalAlignment: Text.AlignVCenter + } + + // Open current folder in OS file manager + MaterialToolButton { + text: MaterialIcons.folder_open + font.pointSize: 11 + padding: 4 + enabled: root.node !== null + ToolTip.text: "Open folder in file manager" + ToolTip.visible: hovered + onClicked: Qt.openUrlExternally(root.currentFolder) + } + } + } + + // File list + ListView { + id: fileListView + Layout.fillWidth: true + Layout.fillHeight: true + clip: true + focus: true + model: folderModel + keyNavigationEnabled: true + highlightFollowsCurrentItem: true + + ScrollBar.vertical: ScrollBar { policy: ScrollBar.AsNeeded } + + // Placeholder when folder is empty or does not exist + Label { + anchors.centerIn: parent + visible: folderModel.count === 0 && root.node !== null + color: Qt.lighter(activePalette.mid, 1.2) + text: "No files found" + } + + delegate: ItemDelegate { + id: delegateItem + width: fileListView.width + height: 24 + padding: 0 + leftPadding: 6 + + // fileIsDir is a FolderListModel role available in the delegate context + readonly property bool isDir: fileIsDir + readonly property string itemFilePath: filePath + + RowLayout { + anchors.fill: parent + anchors.leftMargin: 6 + spacing: 6 + + // File/folder icon + MaterialLabel { + text: delegateItem.isDir ? MaterialIcons.folder : MaterialIcons.insert_drive_file + color: delegateItem.isDir ? "#e8a000" : activePalette.text + font.pointSize: 10 + Layout.alignment: Qt.AlignVCenter + } + + // File/folder name + Label { + Layout.fillWidth: true + // fileName is a FolderListModel role available in the delegate context + text: fileName + elide: Text.ElideRight + font.pointSize: 8 + verticalAlignment: Text.AlignVCenter + } + + // File size (only for files, fileSize role from FolderListModel) + Label { + visible: !delegateItem.isDir + text: { + if (fileSize < 0) + return "" + if (fileSize < 1024) + return fileSize + " B" + if (fileSize < 1024 * 1024) + return (fileSize / 1024).toFixed(1) + " KB" + if (fileSize < 1024 * 1024 * 1024) + return (fileSize / (1024 * 1024)).toFixed(1) + " MB" + return (fileSize / (1024 * 1024 * 1024)).toFixed(2) + " GB" + } + color: activePalette.mid + font.pointSize: 7 + rightPadding: 8 + verticalAlignment: Text.AlignVCenter + } + } + + highlighted: fileListView.currentIndex === index + + onDoubleClicked: { + if (delegateItem.isDir) { + // fileURL is a FolderListModel role providing the URL + root.currentFolder = fileURL + } else { + Qt.openUrlExternally(fileURL) + } + } + } + } + } +} diff --git a/meshroom/ui/qml/GraphEditor/qmldir b/meshroom/ui/qml/GraphEditor/qmldir index 97b068201b..9e41965691 100644 --- a/meshroom/ui/qml/GraphEditor/qmldir +++ b/meshroom/ui/qml/GraphEditor/qmldir @@ -13,4 +13,5 @@ CompatibilityBadge 1.0 CompatibilityBadge.qml CompatibilityManager 1.0 CompatibilityManager.qml singleton GraphEditorSettings 1.0 GraphEditorSettings.qml TaskManager 1.0 TaskManager.qml -ScriptEditor 1.0 ScriptEditor.qml \ No newline at end of file +ScriptEditor 1.0 ScriptEditor.qml +NodeFileBrowser 1.0 NodeFileBrowser.qml \ No newline at end of file