diff --git a/meshroom/ui/app.py b/meshroom/ui/app.py index 75a9dcb47c..633665f0ca 100644 --- a/meshroom/ui/app.py +++ b/meshroom/ui/app.py @@ -250,12 +250,12 @@ def addRecentProjectFile(self, projectFile): # add the new value in the first place projects.insert(0, projectFileNorm) - # keep only the 10 first elements + # keep only the 20 first elements projects = projects[0:20] settings = QSettings() settings.beginGroup("RecentFiles") - size = settings.beginWriteArray("Projects") + settings.beginWriteArray("Projects") for i, p in enumerate(projects): settings.setArrayIndex(i) settings.setValue("filepath", p) @@ -292,7 +292,7 @@ def removeRecentProjectFile(self, projectFile): settings = QSettings() settings.beginGroup("RecentFiles") - size = settings.beginWriteArray("Projects") + settings.beginWriteArray("Projects") for i, p in enumerate(projects): settings.setArrayIndex(i) settings.setValue("filepath", p) @@ -301,6 +301,86 @@ def removeRecentProjectFile(self, projectFile): self.recentProjectFilesChanged.emit() + def _recentImportedImagesFolders(self): + folders = [] + settings = QSettings() + settings.beginGroup("RecentFiles") + size = settings.beginReadArray("ImagesFolders") + for i in range(size): + settings.setArrayIndex(i) + f = settings.value("path") + if f: + folders.append(f) + settings.endArray() + return folders + + @Slot(QUrl) + def addRecentImportedImagesFolder(self, imagesFolder): + if isinstance(imagesFolder, QUrl): + folderPath = imagesFolder.toLocalFile() + if not folderPath: + folderPath = imagesFolder.toString() + else: + raise TypeError("Unexpected data type: {}".format(imagesFolder.__class__)) + + folders = self._recentImportedImagesFolders() + + # remove duplicates while preserving order + from collections import OrderedDict + uniqueFolders = OrderedDict.fromkeys(folders) + folders = list(uniqueFolders) + # remove previous usage of the value + if folderPath in uniqueFolders: + folders.remove(folderPath) + # add the new value in the first place + folders.insert(0, folderPath) + + # keep only the first three elements to have a backup if one of the folders goes missing + folders = folders[0:3] + + settings = QSettings() + settings.beginGroup("RecentFiles") + settings.beginWriteArray("ImagesFolders") + for i, p in enumerate(folders): + settings.setArrayIndex(i) + settings.setValue("path", p) + settings.endArray() + settings.sync() + + self.recentImportedImagesFoldersChanged.emit() + + @Slot(QUrl) + def removeRecentImportedImagesFolder(self, imagesFolder): + if isinstance(imagesFolder, QUrl): + folderPath = imagesFolder.toLocalFile() + if not folderPath: + folderPath = imagesFolder.toString() + else: + raise TypeError("Unexpected data type: {}".format(imagesFolder.__class__)) + + folders = self._recentImportedImagesFolders() + + # remove duplicates while preserving order + from collections import OrderedDict + uniqueFolders = OrderedDict.fromkeys(folders) + folders = list(uniqueFolders) + # remove previous usage of the value + if folderPath not in uniqueFolders: + return + + folders.remove(folderPath) + + settings = QSettings() + settings.beginGroup("RecentFiles") + settings.beginWriteArray("ImagesFolders") + for i, f in enumerate(folders): + settings.setArrayIndex(i) + settings.setValue("path", f) + settings.endArray() + settings.sync() + + self.recentImportedImagesFoldersChanged.emit() + @Slot(str, result=str) def markdownToHtml(self, md): """ @@ -358,7 +438,9 @@ def _default8bitViewerEnabled(self): licensesModel = Property("QVariantList", _licensesModel, constant=True) pipelineTemplateFilesChanged = Signal() recentProjectFilesChanged = Signal() + recentImportedImagesFoldersChanged = Signal() pipelineTemplateFiles = Property("QVariantList", _pipelineTemplateFiles, notify=pipelineTemplateFilesChanged) pipelineTemplateNames = Property("QVariantList", _pipelineTemplateNames, notify=pipelineTemplateFilesChanged) recentProjectFiles = Property("QVariantList", _recentProjectFiles, notify=recentProjectFilesChanged) + recentImportedImagesFolders = Property("QVariantList", _recentImportedImagesFolders, notify=recentImportedImagesFoldersChanged) default8bitViewerEnabled = Property(bool, _default8bitViewerEnabled, constant=True) diff --git a/meshroom/ui/qml/ImageGallery/ImageGallery.qml b/meshroom/ui/qml/ImageGallery/ImageGallery.qml index 4858486241..0318a93c18 100644 --- a/meshroom/ui/qml/ImageGallery/ImageGallery.qml +++ b/meshroom/ui/qml/ImageGallery/ImageGallery.qml @@ -23,6 +23,7 @@ Panel { readonly property string currentItemSource: grid.currentItem ? grid.currentItem.source : "" readonly property var currentItemMetadata: grid.currentItem ? grid.currentItem.metadata : undefined readonly property int centerViewId: (_reconstruction && _reconstruction.sfmTransform) ? parseInt(_reconstruction.sfmTransform.attribute("transformation").value) : 0 + readonly property alias galleryGrid: grid property int defaultCellSize: 160 property bool readOnly: false diff --git a/meshroom/ui/qml/WorkspaceView.qml b/meshroom/ui/qml/WorkspaceView.qml index 284c7c6df5..dab4181554 100644 --- a/meshroom/ui/qml/WorkspaceView.qml +++ b/meshroom/ui/qml/WorkspaceView.qml @@ -24,6 +24,7 @@ Item { property bool readOnly: false property alias panel3dViewer: panel3dViewerLoader.item readonly property Viewer2D viewer2D: viewer2D + readonly property alias imageGallery: imageGallery implicitWidth: 300 implicitHeight: 400 diff --git a/meshroom/ui/qml/main.qml b/meshroom/ui/qml/main.qml index d05d45b08e..283f115bcf 100644 --- a/meshroom/ui/qml/main.qml +++ b/meshroom/ui/qml/main.qml @@ -46,6 +46,19 @@ 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' @@ -328,6 +341,8 @@ ApplicationWindow { nameFilters: [] onAccepted: { _reconstruction.importImagesUrls(importImagesDialog.fileUrls) + imagesFolder = Filepath.dirname(importImagesDialog.fileUrls[0]) + MeshroomApp.addRecentImportedImagesFolder(imagesFolder) } } @@ -475,15 +490,27 @@ ApplicationWindow { // Utility functions for elements in the menubar - function initFileDialogFolder(dialog) { - if(_reconstruction.graph && _reconstruction.graph.filepath) { - dialog.folder = Filepath.stringToUrl(Filepath.dirname(_reconstruction.graph.filepath)); + function initFileDialogFolder(dialog, importImages = false) { + let folder = ""; + + if (imagesFolder.toString() === "" && workspaceView.imageGallery.galleryGrid.itemAtIndex(0) !== null) { + imagesFolder = Filepath.stringToUrl(Filepath.dirname(workspaceView.imageGallery.galleryGrid.itemAtIndex(0).source)); + } + + if (_reconstruction.graph && _reconstruction.graph.filepath) { + folder = Filepath.stringToUrl(Filepath.dirname(_reconstruction.graph.filepath)); } else { var projects = MeshroomApp.recentProjectFiles; if (projects.length > 0 && Filepath.exists(projects[0])) { - dialog.folder = Filepath.stringToUrl(Filepath.dirname(projects[0])); + folder = Filepath.stringToUrl(Filepath.dirname(projects[0])); } } + + if (importImages && imagesFolder.toString() !== "" && Filepath.exists(imagesFolder)) { + folder = imagesFolder; + } + + dialog.folder = folder; } header: MenuBar { @@ -606,7 +633,7 @@ ApplicationWindow { text: "Import Images" shortcut: "Ctrl+I" onTriggered: { - initFileDialogFolder(importImagesDialog); + initFileDialogFolder(importImagesDialog, true); importImagesDialog.open(); } }