diff --git a/plugins/LocalFileProvider/LocalFileProvider.py b/plugins/LocalFileProvider/LocalFileProvider.py new file mode 100644 index 0000000000..7917b649c2 --- /dev/null +++ b/plugins/LocalFileProvider/LocalFileProvider.py @@ -0,0 +1,47 @@ +# Copyright (c) 2021 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + +import os.path + +from UM.FileProvider import FileProvider # The plug-in type we're going to implement. +from UM.i18n import i18nCatalog +from UM.Logger import Logger +from UM.PluginRegistry import PluginRegistry # To get resources from the plug-in folder. +from cura.CuraApplication import CuraApplication # To create QML elements. + +i18n_catalog = i18nCatalog("cura") + + +class LocalFileProvider(FileProvider): + """ + Allows the user to open files from their local file system. + + These files will then be interpreted through the file handlers. + """ + + def __init__(self): + super().__init__() + self.menu_item_display_text = i18n_catalog.i18nc("@menu Open files from local disk", "Local disk") + self.shortcut = "Ctrl+O" + + self._dialog = None # Lazy-load this QML element. + + def _load_file_dialog(self): + """ + Loads the file dialog QML element into the QML context so that it can be shown. + :return: + """ + plugin_path = PluginRegistry.getInstance().getPluginPath(self.getPluginId()) + if plugin_path is None: + plugin_path = os.path.dirname(__file__) + path = os.path.join(plugin_path, "OpenLocalFile.qml") + self._dialog = CuraApplication.getInstance().createQmlComponent(path) + if self._dialog is None: + Logger.log("e", "Unable to create open file dialogue.") + + def run(self): + if self._dialog is None: + self._load_file_dialog() + if self._dialog is None: + return # Will already have logged an error in _load_file_dialog. + self._dialog.show() \ No newline at end of file diff --git a/plugins/LocalFileProvider/OpenLocalFile.qml b/plugins/LocalFileProvider/OpenLocalFile.qml new file mode 100644 index 0000000000..f78fe4b991 --- /dev/null +++ b/plugins/LocalFileProvider/OpenLocalFile.qml @@ -0,0 +1,171 @@ +// Copyright (c) 2021 Ultimaker B.V. +// Cura is released under the terms of the LGPLv3 or higher. + +import QtQuick 2.7 +import QtQuick.Dialogs 1.2 + +import UM 1.3 as UM +import Cura 1.1 as Cura + +Item +{ + id: base + + function show() + { + openDialog.visible = true; + } + + UM.I18nCatalog + { + id: catalog + name: "cura" + } + + FileDialog + { + id: openDialog; + + //: File open dialog title + title: catalog.i18nc("@title:window","Open file(s)") + modality: Qt.WindowModal + selectMultiple: true + nameFilters: UM.MeshFileHandler.supportedReadFileTypes; + folder: + { + //Because several implementations of the file dialog only update the folder when it is explicitly set. + folder = CuraApplication.getDefaultPath("dialog_load_path"); + return CuraApplication.getDefaultPath("dialog_load_path"); + } + onAccepted: + { + // Because several implementations of the file dialog only update the folder + // when it is explicitly set. + var f = folder; + folder = f; + + CuraApplication.setDefaultPath("dialog_load_path", folder); + + handleOpenFileUrls(fileUrls); + } + + // Yeah... I know... it is a mess to put all those things here. + // There are lots of user interactions in this part of the logic, such as showing a warning dialog here and there, + // etc. This means it will come back and forth from time to time between QML and Python. So, separating the logic + // and view here may require more effort but make things more difficult to understand. + function handleOpenFileUrls(fileUrlList) + { + // look for valid project files + var projectFileUrlList = []; + var hasGcode = false; + var nonGcodeFileList = []; + for (var i in fileUrlList) + { + var endsWithG = /\.g$/; + var endsWithGcode = /\.gcode$/; + if (endsWithG.test(fileUrlList[i]) || endsWithGcode.test(fileUrlList[i])) + { + continue; + } + else if (CuraApplication.checkIsValidProjectFile(fileUrlList[i])) + { + projectFileUrlList.push(fileUrlList[i]); + } + nonGcodeFileList.push(fileUrlList[i]); + } + hasGcode = nonGcodeFileList.length < fileUrlList.length; + + // show a warning if selected multiple files together with Gcode + var hasProjectFile = projectFileUrlList.length > 0; + var selectedMultipleFiles = fileUrlList.length > 1; + if (selectedMultipleFiles && hasGcode) + { + infoMultipleFilesWithGcodeDialog.selectedMultipleFiles = selectedMultipleFiles; + infoMultipleFilesWithGcodeDialog.hasProjectFile = hasProjectFile; + infoMultipleFilesWithGcodeDialog.fileUrls = nonGcodeFileList.slice(); + infoMultipleFilesWithGcodeDialog.projectFileUrlList = projectFileUrlList.slice(); + infoMultipleFilesWithGcodeDialog.open(); + } + else + { + handleOpenFiles(selectedMultipleFiles, hasProjectFile, fileUrlList, projectFileUrlList); + } + } + + function handleOpenFiles(selectedMultipleFiles, hasProjectFile, fileUrlList, projectFileUrlList) + { + // we only allow opening one project file + if (selectedMultipleFiles && hasProjectFile) + { + openFilesIncludingProjectsDialog.fileUrls = fileUrlList.slice(); + openFilesIncludingProjectsDialog.show(); + return; + } + + if (hasProjectFile) + { + var projectFile = projectFileUrlList[0]; + + // check preference + var choice = UM.Preferences.getValue("cura/choice_on_open_project"); + if (choice == "open_as_project") + { + openFilesIncludingProjectsDialog.loadProjectFile(projectFile); + } + else if (choice == "open_as_model") + { + openFilesIncludingProjectsDialog.loadModelFiles([projectFile].slice()); + } + else // always ask + { + // ask whether to open as project or as models + askOpenAsProjectOrModelsDialog.fileUrl = projectFile; + askOpenAsProjectOrModelsDialog.show(); + } + } + else + { + openFilesIncludingProjectsDialog.loadModelFiles(fileUrlList.slice()); + } + } + } + + MessageDialog + { + id: infoMultipleFilesWithGcodeDialog + title: catalog.i18nc("@title:window", "Open File(s)") + icon: StandardIcon.Information + standardButtons: StandardButton.Ok + text: catalog.i18nc("@text:window", "We have found one or more G-Code files within the files you have selected. You can only open one G-Code file at a time. If you want to open a G-Code file, please just select only one.") + + property var selectedMultipleFiles + property var hasProjectFile + property var fileUrls + property var projectFileUrlList + + onAccepted: + { + openDialog.handleOpenFiles(selectedMultipleFiles, hasProjectFile, fileUrls, projectFileUrlList); + } + } + + Cura.AskOpenAsProjectOrModelsDialog + { + id: askOpenAsProjectOrModelsDialog + } + + Connections + { + target: CuraApplication + onOpenProjectFile: + { + askOpenAsProjectOrModelsDialog.fileUrl = project_file; + askOpenAsProjectOrModelsDialog.show(); + } + } + + Cura.OpenFilesIncludingProjectsDialog + { + id: openFilesIncludingProjectsDialog + } +} \ No newline at end of file diff --git a/plugins/LocalFileProvider/__init__.py b/plugins/LocalFileProvider/__init__.py new file mode 100644 index 0000000000..63845d7820 --- /dev/null +++ b/plugins/LocalFileProvider/__init__.py @@ -0,0 +1,11 @@ +# Copyright (c) 2021 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + +from . import LocalFileProvider + + +def getMetaData(): + return {} + +def register(app): + return { "file_provider": LocalFileProvider.LocalFileProvider() } diff --git a/plugins/LocalFileProvider/plugin.json b/plugins/LocalFileProvider/plugin.json new file mode 100644 index 0000000000..cd4d77eb98 --- /dev/null +++ b/plugins/LocalFileProvider/plugin.json @@ -0,0 +1,8 @@ +{ + "name": "Local File Provider", + "description": "Enables opening files from the local file system.", + "author": "Ultimaker B.V.", + "version": "1.0.0", + "api": "7.4.0", + "i18n-catalog": "cura" +} \ No newline at end of file diff --git a/resources/qml/Cura.qml b/resources/qml/Cura.qml index bb7b5ac19c..ea615b3c17 100644 --- a/resources/qml/Cura.qml +++ b/resources/qml/Cura.qml @@ -619,114 +619,6 @@ UM.MainWindow onTriggered: base.exitFullscreen() } - FileDialog - { - id: openDialog; - - //: File open dialog title - title: catalog.i18nc("@title:window","Open file(s)") - modality: Qt.WindowModal - selectMultiple: true - nameFilters: UM.MeshFileHandler.supportedReadFileTypes; - folder: - { - //Because several implementations of the file dialog only update the folder when it is explicitly set. - folder = CuraApplication.getDefaultPath("dialog_load_path"); - return CuraApplication.getDefaultPath("dialog_load_path"); - } - onAccepted: - { - // Because several implementations of the file dialog only update the folder - // when it is explicitly set. - var f = folder; - folder = f; - - CuraApplication.setDefaultPath("dialog_load_path", folder); - - handleOpenFileUrls(fileUrls); - } - - // Yeah... I know... it is a mess to put all those things here. - // There are lots of user interactions in this part of the logic, such as showing a warning dialog here and there, - // etc. This means it will come back and forth from time to time between QML and Python. So, separating the logic - // and view here may require more effort but make things more difficult to understand. - function handleOpenFileUrls(fileUrlList) - { - // look for valid project files - var projectFileUrlList = []; - var hasGcode = false; - var nonGcodeFileList = []; - for (var i in fileUrlList) - { - var endsWithG = /\.g$/; - var endsWithGcode = /\.gcode$/; - if (endsWithG.test(fileUrlList[i]) || endsWithGcode.test(fileUrlList[i])) - { - continue; - } - else if (CuraApplication.checkIsValidProjectFile(fileUrlList[i])) - { - projectFileUrlList.push(fileUrlList[i]); - } - nonGcodeFileList.push(fileUrlList[i]); - } - hasGcode = nonGcodeFileList.length < fileUrlList.length; - - // show a warning if selected multiple files together with Gcode - var hasProjectFile = projectFileUrlList.length > 0; - var selectedMultipleFiles = fileUrlList.length > 1; - if (selectedMultipleFiles && hasGcode) - { - infoMultipleFilesWithGcodeDialog.selectedMultipleFiles = selectedMultipleFiles; - infoMultipleFilesWithGcodeDialog.hasProjectFile = hasProjectFile; - infoMultipleFilesWithGcodeDialog.fileUrls = nonGcodeFileList.slice(); - infoMultipleFilesWithGcodeDialog.projectFileUrlList = projectFileUrlList.slice(); - infoMultipleFilesWithGcodeDialog.open(); - } - else - { - handleOpenFiles(selectedMultipleFiles, hasProjectFile, fileUrlList, projectFileUrlList); - } - } - - function handleOpenFiles(selectedMultipleFiles, hasProjectFile, fileUrlList, projectFileUrlList) - { - // we only allow opening one project file - if (selectedMultipleFiles && hasProjectFile) - { - openFilesIncludingProjectsDialog.fileUrls = fileUrlList.slice(); - openFilesIncludingProjectsDialog.show(); - return; - } - - if (hasProjectFile) - { - var projectFile = projectFileUrlList[0]; - - // check preference - var choice = UM.Preferences.getValue("cura/choice_on_open_project"); - if (choice == "open_as_project") - { - openFilesIncludingProjectsDialog.loadProjectFile(projectFile); - } - else if (choice == "open_as_model") - { - openFilesIncludingProjectsDialog.loadModelFiles([projectFile].slice()); - } - else // always ask - { - // ask whether to open as project or as models - askOpenAsProjectOrModelsDialog.fileUrl = projectFile; - askOpenAsProjectOrModelsDialog.show(); - } - } - else - { - openFilesIncludingProjectsDialog.loadModelFiles(fileUrlList.slice()); - } - } - } - MessageDialog { id: packageInstallDialog @@ -735,51 +627,12 @@ UM.MainWindow modality: Qt.ApplicationModal } - MessageDialog - { - id: infoMultipleFilesWithGcodeDialog - title: catalog.i18nc("@title:window", "Open File(s)") - icon: StandardIcon.Information - standardButtons: StandardButton.Ok - text: catalog.i18nc("@text:window", "We have found one or more G-Code files within the files you have selected. You can only open one G-Code file at a time. If you want to open a G-Code file, please just select only one.") - - property var selectedMultipleFiles - property var hasProjectFile - property var fileUrls - property var projectFileUrlList - - onAccepted: - { - openDialog.handleOpenFiles(selectedMultipleFiles, hasProjectFile, fileUrls, projectFileUrlList); - } - } - Connections { target: Cura.Actions.open onTriggered: openDialog.open() } - OpenFilesIncludingProjectsDialog - { - id: openFilesIncludingProjectsDialog - } - - AskOpenAsProjectOrModelsDialog - { - id: askOpenAsProjectOrModelsDialog - } - - Connections - { - target: CuraApplication - onOpenProjectFile: - { - askOpenAsProjectOrModelsDialog.fileUrl = project_file; - askOpenAsProjectOrModelsDialog.show(); - } - } - Connections { target: Cura.Actions.showProfileFolder