diff --git a/plugins/LocalFileProvider/LocalFileProvider.py b/plugins/LocalFileProvider/LocalFileProvider.py deleted file mode 100644 index 7917b649c2..0000000000 --- a/plugins/LocalFileProvider/LocalFileProvider.py +++ /dev/null @@ -1,47 +0,0 @@ -# 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 deleted file mode 100644 index f78fe4b991..0000000000 --- a/plugins/LocalFileProvider/OpenLocalFile.qml +++ /dev/null @@ -1,171 +0,0 @@ -// 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 deleted file mode 100644 index 63845d7820..0000000000 --- a/plugins/LocalFileProvider/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -# 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 deleted file mode 100644 index cd4d77eb98..0000000000 --- a/plugins/LocalFileProvider/plugin.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "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/plugins/PrepareStage/PrepareMenu.qml b/plugins/PrepareStage/PrepareMenu.qml index d1bb47cb35..87d7c5f35c 100644 --- a/plugins/PrepareStage/PrepareMenu.qml +++ b/plugins/PrepareStage/PrepareMenu.qml @@ -1,4 +1,4 @@ -// Copyright (c) 2021 Ultimaker B.V. +// Copyright (c) 2018 Ultimaker B.V. // Cura is released under the terms of the LGPLv3 or higher. import QtQuick 2.7 @@ -96,7 +96,7 @@ Item id: openFileButton height: UM.Theme.getSize("stage_menu").height width: UM.Theme.getSize("stage_menu").height - onClicked: CuraApplication.getFileProviderModel().triggerFirst() + onClicked: Cura.Actions.open.trigger() hoverEnabled: true contentItem: Item diff --git a/resources/qml/Actions.qml b/resources/qml/Actions.qml index 5ad5337e78..c62b0cb89a 100644 --- a/resources/qml/Actions.qml +++ b/resources/qml/Actions.qml @@ -1,4 +1,4 @@ -// Copyright (c) 2021 Ultimaker B.V. +// Copyright (c) 2018 Ultimaker B.V. // Cura is released under the terms of the LGPLv3 or higher. pragma Singleton @@ -12,6 +12,7 @@ import Cura 1.0 as Cura Item { property alias newProject: newProjectAction; + property alias open: openAction; property alias quit: quitAction; property alias undo: undoAction; @@ -412,6 +413,14 @@ Item onTriggered: CuraApplication.resetAll(); } + Action + { + id: openAction; + text: catalog.i18nc("@action:inmenu menubar:file","&Open File(s)..."); + iconName: "document-open"; + shortcut: StandardKey.Open; + } + Action { id: newProjectAction diff --git a/resources/qml/Cura.qml b/resources/qml/Cura.qml index 1a42f693a9..bb7b5ac19c 100644 --- a/resources/qml/Cura.qml +++ b/resources/qml/Cura.qml @@ -619,6 +619,114 @@ 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 @@ -627,6 +735,51 @@ 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 diff --git a/resources/qml/Menus/FileMenu.qml b/resources/qml/Menus/FileMenu.qml index 918b96c727..94fc2358e1 100644 --- a/resources/qml/Menus/FileMenu.qml +++ b/resources/qml/Menus/FileMenu.qml @@ -22,7 +22,7 @@ Menu MenuItem { id: openMenu - onTriggered: CuraApplication.getFileProviderModel().triggerFirst() + action: Cura.Actions.open visible: (base.fileProviderModel.count == 1) } diff --git a/resources/qml/Menus/OpenFilesMenu.qml b/resources/qml/Menus/OpenFilesMenu.qml index 20d6fe453c..60fb507b34 100644 --- a/resources/qml/Menus/OpenFilesMenu.qml +++ b/resources/qml/Menus/OpenFilesMenu.qml @@ -1,4 +1,4 @@ -// Copyright (c) 2021 Ultimaker B.V. +// Copyright (c) 2020 Ultimaker B.V. // Cura is released under the terms of the LGPLv3 or higher. import QtQuick 2.2 @@ -21,8 +21,21 @@ Menu model: CuraApplication.getFileProviderModel() MenuItem { - text: model.displayText - onTriggered: CuraApplication.getFileProviderModel().trigger(model.name) + text: + { + return model.displayText; + } + onTriggered: + { + if (model.index == 0) // The 0th element is the "From Disk" option, which should activate the open local file dialog + { + Cura.Actions.open.trigger() + } + else + { + CuraApplication.getFileProviderModel().trigger(model.name); + } + } shortcut: model.shortcut } onObjectAdded: openFilesMenu.insertItem(index, object)