From 430550452f80310435ba9859993f82456a24b7b8 Mon Sep 17 00:00:00 2001 From: Kostas Karmas Date: Tue, 15 Dec 2020 18:31:15 +0100 Subject: [PATCH 01/15] Add a new file menu option when there are multiple file providers CURA-7868 --- resources/qml/Menus/FileMenu.qml | 5 +++ resources/qml/Menus/OpenFilesMenu.qml | 45 +++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 resources/qml/Menus/OpenFilesMenu.qml diff --git a/resources/qml/Menus/FileMenu.qml b/resources/qml/Menus/FileMenu.qml index b9845678d6..24c081502a 100644 --- a/resources/qml/Menus/FileMenu.qml +++ b/resources/qml/Menus/FileMenu.qml @@ -22,6 +22,11 @@ Menu { id: openMenu action: Cura.Actions.open + visible: CuraApplication.fileProviders.length > 0 // DEBUG: It's > 0 so that both options are visible for debugging purposes + } + + OpenFilesMenu { + visible: CuraApplication.fileProviders.length > 0 // DEBUG: It's > 0 so that both options are visible for debugging purposes } RecentFilesMenu { } diff --git a/resources/qml/Menus/OpenFilesMenu.qml b/resources/qml/Menus/OpenFilesMenu.qml new file mode 100644 index 0000000000..a3afa9a598 --- /dev/null +++ b/resources/qml/Menus/OpenFilesMenu.qml @@ -0,0 +1,45 @@ +// Copyright (c) 2016 Ultimaker B.V. +// Cura is released under the terms of the LGPLv3 or higher. + +import QtQuick 2.2 +import QtQuick.Controls 1.1 + +import UM 1.6 as UM +import Cura 1.0 as Cura + +import "../Dialogs" + +Menu +{ + id: menu + title: catalog.i18nc("@title:menu menubar:file", "Open File(s)...") + iconName: "document-open-recent"; + + + Instantiator + { + id: fileProviders + model: UM.FileProviderModel { } + MenuItem + { + 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 + { + fileProviders.model.trigger(model.name); + } + } + shortcut: model.shortcut + } + onObjectAdded: menu.insertItem(index, object) + onObjectRemoved: menu.removeItem(object) + } +} From 43615a57b61c2be31a439025f4bf6279b45a63a0 Mon Sep 17 00:00:00 2001 From: Kostas Karmas Date: Fri, 18 Dec 2020 14:34:29 +0100 Subject: [PATCH 02/15] Change client scope in the Account CURA-7868 --- cura/API/Account.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cura/API/Account.py b/cura/API/Account.py index 15bccb71e1..d5ef2bfcb9 100644 --- a/cura/API/Account.py +++ b/cura/API/Account.py @@ -81,7 +81,8 @@ class Account(QObject): CLIENT_ID="um----------------------------ultimaker_cura", CLIENT_SCOPES="account.user.read drive.backup.read drive.backup.write packages.download " "packages.rating.read packages.rating.write connect.cluster.read connect.cluster.write " - "cura.printjob.read cura.printjob.write cura.mesh.read cura.mesh.write", + "library.project.read library.project.write cura.printjob.read cura.printjob.write " + "cura.mesh.read cura.mesh.write", AUTH_DATA_PREFERENCE_KEY="general/ultimaker_auth_data", AUTH_SUCCESS_REDIRECT="{}/app/auth-success".format(self._oauth_root), AUTH_FAILED_REDIRECT="{}/app/auth-error".format(self._oauth_root) From 71994eaaf90ca6302cbbc860ebb1366bf710b8fe Mon Sep 17 00:00:00 2001 From: Kostas Karmas Date: Wed, 23 Dec 2020 17:13:14 +0100 Subject: [PATCH 03/15] Change the Open File(s) option according to the file providers count When there is only one file provider (i.e. the local file provider), the Open File(s) will be a simple item in the File menu. When there are more than one file providers, the Open File(s) will become a submenu in the File menu, which will contain all the file providers as submenu items. CURA-7868 --- cura/CuraApplication.py | 11 +++++++++++ resources/qml/Menus/FileMenu.qml | 18 +++++++++++++++--- resources/qml/Menus/OpenFilesMenu.qml | 11 +++++------ 3 files changed, 31 insertions(+), 9 deletions(-) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index f0c69d5a61..ee347e7a4d 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -30,6 +30,7 @@ from UM.Operations.SetTransformOperation import SetTransformOperation from UM.Platform import Platform from UM.PluginError import PluginNotFoundError from UM.Preferences import Preferences +from UM.Qt.Bindings.FileProviderModel import FileProviderModel from UM.Qt.QtApplication import QtApplication # The class we're inheriting from. from UM.Resources import Resources from UM.Scene.Camera import Camera @@ -822,6 +823,9 @@ class CuraApplication(QtApplication): self._add_printer_pages_model_without_cancel.initialize(cancellable = False) self._whats_new_pages_model.initialize() + # Initialize the FileProviderModel + self._file_provider_model.initialize(self._onFileProviderEnabledChanged) + # Detect in which mode to run and execute that mode if self._is_headless: self.runWithoutGUI() @@ -1051,6 +1055,13 @@ class CuraApplication(QtApplication): self._simple_mode_settings_manager = SimpleModeSettingsManager() return self._simple_mode_settings_manager + @pyqtSlot(result = QObject) + def getFileProviderModel(self) -> FileProviderModel: + return self._file_provider_model + + def _onFileProviderEnabledChanged(self): + self._file_provider_model.update() + def event(self, event): """Handle Qt events""" diff --git a/resources/qml/Menus/FileMenu.qml b/resources/qml/Menus/FileMenu.qml index 24c081502a..a1bd246763 100644 --- a/resources/qml/Menus/FileMenu.qml +++ b/resources/qml/Menus/FileMenu.qml @@ -22,11 +22,23 @@ Menu { id: openMenu action: Cura.Actions.open - visible: CuraApplication.fileProviders.length > 0 // DEBUG: It's > 0 so that both options are visible for debugging purposes + visible: (CuraApplication.getFileProviderModel().count == 1) } - OpenFilesMenu { - visible: CuraApplication.fileProviders.length > 0 // DEBUG: It's > 0 so that both options are visible for debugging purposes + OpenFilesMenu + { + id: openFilesMenu + visible: (CuraApplication.getFileProviderModel().count > 1) + } + + Connections + { + target: CuraApplication.getFileProviderModel() + onItemsChanged: + { + openMenu.visible = (CuraApplication.getFileProviderModel().count == 1) // 1 because the open local files menu should always exist in the model + openFilesMenu.visible = (CuraApplication.getFileProviderModel().count > 1) + } } RecentFilesMenu { } diff --git a/resources/qml/Menus/OpenFilesMenu.qml b/resources/qml/Menus/OpenFilesMenu.qml index a3afa9a598..226ea70680 100644 --- a/resources/qml/Menus/OpenFilesMenu.qml +++ b/resources/qml/Menus/OpenFilesMenu.qml @@ -11,15 +11,14 @@ import "../Dialogs" Menu { - id: menu + id: openFilesMenu title: catalog.i18nc("@title:menu menubar:file", "Open File(s)...") iconName: "document-open-recent"; - Instantiator { id: fileProviders - model: UM.FileProviderModel { } + model: CuraApplication.getFileProviderModel() MenuItem { text: @@ -34,12 +33,12 @@ Menu } else { - fileProviders.model.trigger(model.name); + CuraApplication.getFileProviderModel().trigger(model.name); } } shortcut: model.shortcut } - onObjectAdded: menu.insertItem(index, object) - onObjectRemoved: menu.removeItem(object) + onObjectAdded: openFilesMenu.insertItem(index, object) + onObjectRemoved: openFilesMenu.removeItem(object) } } From 70550594cd113bee4af662c1dd980738ed3dbe0d Mon Sep 17 00:00:00 2001 From: Kostas Karmas Date: Mon, 4 Jan 2021 15:07:53 +0100 Subject: [PATCH 04/15] Connect the visibility of the components through their properties As Ghostkeeper suspected correctly in the review comment https://github.com/Ultimaker/Cura/pull/9012#discussion_r549707433 the binding wasn't working because the model was being retrieved using a function (CuraApplication.getFileProviderModel()). Separating this model into a variable allows us to properly bind the "visible" properties of the menu items with the count property of the model without a problem. CURA-7868 --- resources/qml/Menus/FileMenu.qml | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/resources/qml/Menus/FileMenu.qml b/resources/qml/Menus/FileMenu.qml index a1bd246763..94fc2358e1 100644 --- a/resources/qml/Menus/FileMenu.qml +++ b/resources/qml/Menus/FileMenu.qml @@ -11,6 +11,7 @@ Menu { id: base title: catalog.i18nc("@title:menu menubar:toplevel", "&File") + property var fileProviderModel: CuraApplication.getFileProviderModel() MenuItem { @@ -22,23 +23,13 @@ Menu { id: openMenu action: Cura.Actions.open - visible: (CuraApplication.getFileProviderModel().count == 1) + visible: (base.fileProviderModel.count == 1) } OpenFilesMenu { id: openFilesMenu - visible: (CuraApplication.getFileProviderModel().count > 1) - } - - Connections - { - target: CuraApplication.getFileProviderModel() - onItemsChanged: - { - openMenu.visible = (CuraApplication.getFileProviderModel().count == 1) // 1 because the open local files menu should always exist in the model - openFilesMenu.visible = (CuraApplication.getFileProviderModel().count > 1) - } + visible: (base.fileProviderModel.count > 1) } RecentFilesMenu { } From e5038ab46dc6b5d43a3a9d575f74bbf3b21ee6f0 Mon Sep 17 00:00:00 2001 From: Kostas Karmas Date: Mon, 4 Jan 2021 15:12:53 +0100 Subject: [PATCH 05/15] Update year in the copyright comment. CURA-7868 --- resources/qml/Menus/OpenFilesMenu.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/qml/Menus/OpenFilesMenu.qml b/resources/qml/Menus/OpenFilesMenu.qml index 226ea70680..60fb507b34 100644 --- a/resources/qml/Menus/OpenFilesMenu.qml +++ b/resources/qml/Menus/OpenFilesMenu.qml @@ -1,4 +1,4 @@ -// Copyright (c) 2016 Ultimaker B.V. +// Copyright (c) 2020 Ultimaker B.V. // Cura is released under the terms of the LGPLv3 or higher. import QtQuick 2.2 From 4b375ce2fe70815ba2e07024c132b2bd11e1c226 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 4 Jan 2021 15:19:17 +0100 Subject: [PATCH 06/15] Remove unused hasMesh signal This is logic that shouldn't be in QML anyway. It's not used by anything at this point. Let's remove it. Contributes to issue CURA-7868. --- resources/qml/Cura.qml | 11 +---------- .../Dialogs/AskOpenAsProjectOrModelsDialog.qml | 8 +------- .../Dialogs/OpenFilesIncludingProjectsDialog.qml | 16 ++++++++-------- resources/qml/Menus/RecentFilesMenu.qml | 10 ++-------- 4 files changed, 12 insertions(+), 33 deletions(-) diff --git a/resources/qml/Cura.qml b/resources/qml/Cura.qml index 9f24d91caf..bb7b5ac19c 100644 --- a/resources/qml/Cura.qml +++ b/resources/qml/Cura.qml @@ -1,4 +1,4 @@ -// Copyright (c) 2020 Ultimaker B.V. +// Copyright (c) 2021 Ultimaker B.V. // Cura is released under the terms of the LGPLv3 or higher. import QtQuick 2.7 @@ -149,15 +149,6 @@ UM.MainWindow id: backgroundItem anchors.fill: parent - signal hasMesh(string name) //this signal sends the filebase name so it can be used for the JobSpecs.qml - function getMeshName(path) - { - //takes the path the complete path of the meshname and returns only the filebase - var fileName = path.slice(path.lastIndexOf("/") + 1) - var fileBase = fileName.slice(0, fileName.indexOf(".")) - return fileBase - } - //DeleteSelection on the keypress backspace event Keys.onPressed: { diff --git a/resources/qml/Dialogs/AskOpenAsProjectOrModelsDialog.qml b/resources/qml/Dialogs/AskOpenAsProjectOrModelsDialog.qml index 8cdaeea5fa..68f58616f1 100644 --- a/resources/qml/Dialogs/AskOpenAsProjectOrModelsDialog.qml +++ b/resources/qml/Dialogs/AskOpenAsProjectOrModelsDialog.qml @@ -1,4 +1,4 @@ -// Copyright (c) 2015 Ultimaker B.V. +// Copyright (c) 2021 Ultimaker B.V. // Cura is released under the terms of the LGPLv3 or higher. import QtQuick 2.2 @@ -32,30 +32,24 @@ UM.Dialog // load the entire project function loadProjectFile() { - // update preference if (rememberChoiceCheckBox.checked) { UM.Preferences.setValue("cura/choice_on_open_project", "open_as_project") } UM.WorkspaceFileHandler.readLocalFile(base.fileUrl) - var meshName = backgroundItem.getMeshName(base.fileUrl.toString()) - backgroundItem.hasMesh(decodeURIComponent(meshName)) base.hide() } // load the project file as separated models function loadModelFiles() { - // update preference if (rememberChoiceCheckBox.checked) { UM.Preferences.setValue("cura/choice_on_open_project", "open_as_model") } CuraApplication.readLocalFile(base.fileUrl, "open_as_model") - var meshName = backgroundItem.getMeshName(base.fileUrl.toString()) - backgroundItem.hasMesh(decodeURIComponent(meshName)) base.hide() } diff --git a/resources/qml/Dialogs/OpenFilesIncludingProjectsDialog.qml b/resources/qml/Dialogs/OpenFilesIncludingProjectsDialog.qml index 187578f12c..62cded866c 100644 --- a/resources/qml/Dialogs/OpenFilesIncludingProjectsDialog.qml +++ b/resources/qml/Dialogs/OpenFilesIncludingProjectsDialog.qml @@ -1,4 +1,4 @@ -// Copyright (c) 2017 Ultimaker B.V. +// Copyright (c) 2021 Ultimaker B.V. // Cura is released under the terms of the LGPLv3 or higher. import QtQuick 2.2 @@ -33,9 +33,6 @@ UM.Dialog function loadProjectFile(projectFile) { UM.WorkspaceFileHandler.readLocalFile(projectFile); - - var meshName = backgroundItem.getMeshName(projectFile.toString()); - backgroundItem.hasMesh(decodeURIComponent(meshName)); } function loadModelFiles(fileUrls) @@ -44,9 +41,6 @@ UM.Dialog { CuraApplication.readLocalFile(fileUrls[i], "open_as_model"); } - - var meshName = backgroundItem.getMeshName(fileUrls[0].toString()); - backgroundItem.hasMesh(decodeURIComponent(meshName)); } Column @@ -108,5 +102,11 @@ UM.Dialog } } } + + UM.I18nCatalog + { + id: catalog + name: "cura" + } } -} +} \ No newline at end of file diff --git a/resources/qml/Menus/RecentFilesMenu.qml b/resources/qml/Menus/RecentFilesMenu.qml index 9de523280c..de6d2e3817 100644 --- a/resources/qml/Menus/RecentFilesMenu.qml +++ b/resources/qml/Menus/RecentFilesMenu.qml @@ -1,4 +1,4 @@ -// Copyright (c) 2016 Ultimaker B.V. +// Copyright (c) 2021 Ultimaker B.V. // Cura is released under the terms of the LGPLv3 or higher. import QtQuick 2.2 @@ -27,13 +27,7 @@ Menu var path = decodeURIComponent(modelData.toString()) return (index + 1) + ". " + path.slice(path.lastIndexOf("/") + 1); } - onTriggered: - { - CuraApplication.readLocalFile(modelData); - - var meshName = backgroundItem.getMeshName(modelData.toString()) - backgroundItem.hasMesh(decodeURIComponent(meshName)) - } + onTriggered: CuraApplication.readLocalFile(modelData) } onObjectAdded: menu.insertItem(index, object) onObjectRemoved: menu.removeItem(object) From 00de7497a4c2986cf8fd13be8f598a0f615f3d63 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 4 Jan 2021 15:48:51 +0100 Subject: [PATCH 07/15] Move open dialogue to separate file provider plug-in We can now define plug-ins that specify where to open files from. This is one of the places where you can open files. This breaks the main button to open files in the interface. It needs to be redirected to trigger the plug-in to show the open file dialogue. Contributest o issue CURA-7868. --- .../LocalFileProvider/LocalFileProvider.py | 47 +++++ plugins/LocalFileProvider/OpenLocalFile.qml | 171 ++++++++++++++++++ plugins/LocalFileProvider/__init__.py | 11 ++ plugins/LocalFileProvider/plugin.json | 8 + resources/qml/Cura.qml | 147 --------------- 5 files changed, 237 insertions(+), 147 deletions(-) create mode 100644 plugins/LocalFileProvider/LocalFileProvider.py create mode 100644 plugins/LocalFileProvider/OpenLocalFile.qml create mode 100644 plugins/LocalFileProvider/__init__.py create mode 100644 plugins/LocalFileProvider/plugin.json 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 From de80461954fc91d32c95e1a56ef88f5c7f69796f Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 4 Jan 2021 15:54:18 +0100 Subject: [PATCH 08/15] Remove open action, fixing ambiguous overload of Ctrl+O The action was no longer in the menu, but the hotkey still functioned. Then there were two actions for Ctrl+O, which was ambiguous to Qt. Contributes to issue CURA-7868. --- resources/qml/Actions.qml | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/resources/qml/Actions.qml b/resources/qml/Actions.qml index c62b0cb89a..5ad5337e78 100644 --- a/resources/qml/Actions.qml +++ b/resources/qml/Actions.qml @@ -1,4 +1,4 @@ -// Copyright (c) 2018 Ultimaker B.V. +// Copyright (c) 2021 Ultimaker B.V. // Cura is released under the terms of the LGPLv3 or higher. pragma Singleton @@ -12,7 +12,6 @@ import Cura 1.0 as Cura Item { property alias newProject: newProjectAction; - property alias open: openAction; property alias quit: quitAction; property alias undo: undoAction; @@ -413,14 +412,6 @@ 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 From b266904d76542216b5bdbdf6c2b628b784bd7d8f Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 4 Jan 2021 17:45:16 +0100 Subject: [PATCH 09/15] Replace other references to the open action with the file provider model This adds a function 'triggerFirst' to the file provider that triggers the first file provider in the model. That should then be the local file provider, but if the plug-in is disabled for some reason it would use another plug-in. Contributes to issue CURA-7868. --- plugins/PrepareStage/PrepareMenu.qml | 4 ++-- resources/qml/Cura.qml | 6 ------ resources/qml/Menus/OpenFilesMenu.qml | 19 +++---------------- 3 files changed, 5 insertions(+), 24 deletions(-) diff --git a/plugins/PrepareStage/PrepareMenu.qml b/plugins/PrepareStage/PrepareMenu.qml index 87d7c5f35c..d1bb47cb35 100644 --- a/plugins/PrepareStage/PrepareMenu.qml +++ b/plugins/PrepareStage/PrepareMenu.qml @@ -1,4 +1,4 @@ -// Copyright (c) 2018 Ultimaker B.V. +// Copyright (c) 2021 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: Cura.Actions.open.trigger() + onClicked: CuraApplication.getFileProviderModel().triggerFirst() hoverEnabled: true contentItem: Item diff --git a/resources/qml/Cura.qml b/resources/qml/Cura.qml index ea615b3c17..1a42f693a9 100644 --- a/resources/qml/Cura.qml +++ b/resources/qml/Cura.qml @@ -627,12 +627,6 @@ UM.MainWindow modality: Qt.ApplicationModal } - Connections - { - target: Cura.Actions.open - onTriggered: openDialog.open() - } - Connections { target: Cura.Actions.showProfileFolder diff --git a/resources/qml/Menus/OpenFilesMenu.qml b/resources/qml/Menus/OpenFilesMenu.qml index 60fb507b34..20d6fe453c 100644 --- a/resources/qml/Menus/OpenFilesMenu.qml +++ b/resources/qml/Menus/OpenFilesMenu.qml @@ -1,4 +1,4 @@ -// Copyright (c) 2020 Ultimaker B.V. +// Copyright (c) 2021 Ultimaker B.V. // Cura is released under the terms of the LGPLv3 or higher. import QtQuick 2.2 @@ -21,21 +21,8 @@ Menu model: CuraApplication.getFileProviderModel() MenuItem { - 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); - } - } + text: model.displayText + onTriggered: CuraApplication.getFileProviderModel().trigger(model.name) shortcut: model.shortcut } onObjectAdded: openFilesMenu.insertItem(index, object) From 5f6b3b52c1848416c98f7d276fe02f08d067f675 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 4 Jan 2021 18:10:50 +0100 Subject: [PATCH 10/15] Don't trigger open menu upon starting Cura That would be very annoying. Contributes to issue CURA-7868. --- resources/qml/Menus/FileMenu.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/qml/Menus/FileMenu.qml b/resources/qml/Menus/FileMenu.qml index 94fc2358e1..918b96c727 100644 --- a/resources/qml/Menus/FileMenu.qml +++ b/resources/qml/Menus/FileMenu.qml @@ -22,7 +22,7 @@ Menu MenuItem { id: openMenu - action: Cura.Actions.open + onTriggered: CuraApplication.getFileProviderModel().triggerFirst() visible: (base.fileProviderModel.count == 1) } From dc02038513090941be5d665a8013e6ded8f2103a Mon Sep 17 00:00:00 2001 From: Kostas Karmas Date: Tue, 5 Jan 2021 14:47:46 +0100 Subject: [PATCH 11/15] Add a Cura-themed, re-usable TableView component CURA-7868 --- resources/qml/TableView.qml | 72 +++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 resources/qml/TableView.qml diff --git a/resources/qml/TableView.qml b/resources/qml/TableView.qml new file mode 100644 index 0000000000..4d9ca75f92 --- /dev/null +++ b/resources/qml/TableView.qml @@ -0,0 +1,72 @@ +// Copyright (C) 2021 Ultimaker B.V. +// Cura is released under the terms of the LGPLv3 or higher. + +import QtQuick 2.10 +import QtQuick.Window 2.2 +import QtQuick.Controls 1.4 as OldControls // TableView doesn't exist in the QtQuick Controls 2.x in 5.10, so use the old one +import QtQuick.Controls 2.3 +import QtQuick.Controls.Styles 1.4 + +import UM 1.2 as UM +import Cura 1.6 as Cura + + +OldControls.TableView +{ + id: tableView + + itemDelegate: Item + { + height: tableCellLabel.implicitHeight + UM.Theme.getSize("thin_margin").height + + Label + { + id: tableCellLabel + color: UM.Theme.getColor("text") + elide: Text.ElideRight + text: styleData.value + anchors.fill: parent + anchors.leftMargin: 10 * screenScaleFactor + verticalAlignment: Text.AlignVCenter + } + } + + rowDelegate: Rectangle + { + color: styleData.selected ? UM.Theme.getColor("toolbar_button_hover") : UM.Theme.getColor("main_background") + } + + // Use the old styling technique since it's the only way to make the scrollbars themed in the TableView + style: TableViewStyle + { + backgroundColor: UM.Theme.getColor("main_background") + + handle: Rectangle + { + // both implicit width and height have to be set, since the handle is used by both the horizontal and the vertical scrollbars + implicitWidth: UM.Theme.getSize("scrollbar").width + implicitHeight: UM.Theme.getSize("scrollbar").width // + radius: width / 2 + color: UM.Theme.getColor(styleData.pressed ? "scrollbar_handle_down" : styleData.hovered ? "scrollbar_handle_hover" : "scrollbar_handle") + } + + scrollBarBackground: Rectangle + { + // both implicit width and height have to be set, since the handle is used by both the horizontal and the vertical scrollbars + implicitWidth: UM.Theme.getSize("scrollbar").width + implicitHeight: UM.Theme.getSize("scrollbar").width + color: UM.Theme.getColor("main_background") + } + + corner: Rectangle // The little rectangle between the vertical and horizontal scrollbars + { + color: UM.Theme.getColor("main_background") + } + + // Override the control arrows + incrementControl: Item { } + decrementControl: Item { } + } +} + + From adcdf7bad9b202c6bad8130615cd7b07bc8db21f Mon Sep 17 00:00:00 2001 From: Kostas Karmas Date: Tue, 5 Jan 2021 14:54:28 +0100 Subject: [PATCH 12/15] Remove unnecessary QML imports CURA-7868 --- resources/qml/TableView.qml | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/resources/qml/TableView.qml b/resources/qml/TableView.qml index 4d9ca75f92..b2ebee51c9 100644 --- a/resources/qml/TableView.qml +++ b/resources/qml/TableView.qml @@ -2,19 +2,15 @@ // Cura is released under the terms of the LGPLv3 or higher. import QtQuick 2.10 -import QtQuick.Window 2.2 import QtQuick.Controls 1.4 as OldControls // TableView doesn't exist in the QtQuick Controls 2.x in 5.10, so use the old one import QtQuick.Controls 2.3 import QtQuick.Controls.Styles 1.4 import UM 1.2 as UM -import Cura 1.6 as Cura OldControls.TableView { - id: tableView - itemDelegate: Item { height: tableCellLabel.implicitHeight + UM.Theme.getSize("thin_margin").height @@ -43,22 +39,23 @@ OldControls.TableView handle: Rectangle { - // both implicit width and height have to be set, since the handle is used by both the horizontal and the vertical scrollbars + // Both implicit width and height have to be set, since the handle is used by both the horizontal and the vertical scrollbars implicitWidth: UM.Theme.getSize("scrollbar").width - implicitHeight: UM.Theme.getSize("scrollbar").width // + implicitHeight: UM.Theme.getSize("scrollbar").width radius: width / 2 - color: UM.Theme.getColor(styleData.pressed ? "scrollbar_handle_down" : styleData.hovered ? "scrollbar_handle_hover" : "scrollbar_handle") + color: UM.Theme.getColor(styleData.pressed ? "scrollbar_handle_down" : (styleData.hovered ? "scrollbar_handle_hover" : "scrollbar_handle")) } scrollBarBackground: Rectangle { - // both implicit width and height have to be set, since the handle is used by both the horizontal and the vertical scrollbars + // Both implicit width and height have to be set, since the handle is used by both the horizontal and the vertical scrollbars implicitWidth: UM.Theme.getSize("scrollbar").width implicitHeight: UM.Theme.getSize("scrollbar").width color: UM.Theme.getColor("main_background") } - corner: Rectangle // The little rectangle between the vertical and horizontal scrollbars + // The little rectangle between the vertical and horizontal scrollbars + corner: Rectangle { color: UM.Theme.getColor("main_background") } @@ -67,6 +64,4 @@ OldControls.TableView incrementControl: Item { } decrementControl: Item { } } -} - - +} \ No newline at end of file From 606ec587fe627945108d93dd23b1e39417d51904 Mon Sep 17 00:00:00 2001 From: Kostas Karmas Date: Wed, 6 Jan 2021 11:51:29 +0100 Subject: [PATCH 13/15] Change the row height in the table view CURA-7868 --- resources/qml/TableView.qml | 5 +++-- resources/themes/cura-light/theme.json | 4 +++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/resources/qml/TableView.qml b/resources/qml/TableView.qml index b2ebee51c9..dd80304b83 100644 --- a/resources/qml/TableView.qml +++ b/resources/qml/TableView.qml @@ -13,7 +13,7 @@ OldControls.TableView { itemDelegate: Item { - height: tableCellLabel.implicitHeight + UM.Theme.getSize("thin_margin").height + height: tableCellLabel.implicitHeight Label { @@ -29,7 +29,8 @@ OldControls.TableView rowDelegate: Rectangle { - color: styleData.selected ? UM.Theme.getColor("toolbar_button_hover") : UM.Theme.getColor("main_background") + color: styleData.selected ? UM.Theme.getColor("secondary") : UM.Theme.getColor("main_background") + height: UM.Theme.getSize("table_row").height } // Use the old styling technique since it's the only way to make the scrollbars themed in the TableView diff --git a/resources/themes/cura-light/theme.json b/resources/themes/cura-light/theme.json index 7bb8156458..e897969a5e 100644 --- a/resources/themes/cura-light/theme.json +++ b/resources/themes/cura-light/theme.json @@ -630,6 +630,8 @@ "monitor_external_link_icon": [1.16, 1.16], "monitor_column": [18.0, 1.0], "monitor_progress_bar": [16.5, 1.0], - "monitor_margin": [1.5, 1.5] + "monitor_margin": [1.5, 1.5], + + "table_row": [2.0, 2.0] } } From 96c4d66029011df9f70ac54ba023d3be53ea67d2 Mon Sep 17 00:00:00 2001 From: Kostas Karmas Date: Thu, 7 Jan 2021 16:35:40 +0100 Subject: [PATCH 14/15] Revert making the open file dialog a separate LocalFileProvider plugin This reverts commits 00de7497a4c2986cf8fd13be8f598a0f615f3d63 to 5f6b3b52c1848416c98f7d276fe02f08d067f675 CURA-7868 --- .../LocalFileProvider/LocalFileProvider.py | 47 ----- plugins/LocalFileProvider/OpenLocalFile.qml | 171 ------------------ plugins/LocalFileProvider/__init__.py | 11 -- plugins/LocalFileProvider/plugin.json | 8 - plugins/PrepareStage/PrepareMenu.qml | 4 +- resources/qml/Actions.qml | 11 +- resources/qml/Cura.qml | 153 ++++++++++++++++ resources/qml/Menus/FileMenu.qml | 2 +- resources/qml/Menus/OpenFilesMenu.qml | 19 +- 9 files changed, 182 insertions(+), 244 deletions(-) delete mode 100644 plugins/LocalFileProvider/LocalFileProvider.py delete mode 100644 plugins/LocalFileProvider/OpenLocalFile.qml delete mode 100644 plugins/LocalFileProvider/__init__.py delete mode 100644 plugins/LocalFileProvider/plugin.json 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) From fd3c9854406e9a655d7076307bf91d8ca4840a1f Mon Sep 17 00:00:00 2001 From: Kostas Karmas Date: Mon, 11 Jan 2021 17:50:26 +0100 Subject: [PATCH 15/15] Fix ambiguous Ctrl+O shortcut not opening the local file dialog Ctrl+O was assigned as a shortcut in two places: 1. To the "File->Open File(s)" menu item, which is visible when only the local file provider is enabled (i.e. the DF file provider is disabled) 2. To the "File->Open File(s)->From Disk" menu item, which is visible when there are more than one file providers enabled. This was creating an ambiguous shortcut, thus never opening the local file dialog. This is now fixed by disabling the shortcuts when the respective items are not visible. CURA-7868 --- resources/qml/Actions.qml | 6 +++++- resources/qml/Menus/OpenFilesMenu.qml | 4 +++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/resources/qml/Actions.qml b/resources/qml/Actions.qml index c62b0cb89a..78c4958598 100644 --- a/resources/qml/Actions.qml +++ b/resources/qml/Actions.qml @@ -416,9 +416,13 @@ Item Action { id: openAction; + property var fileProviderModel: CuraApplication.getFileProviderModel() + text: catalog.i18nc("@action:inmenu menubar:file","&Open File(s)..."); iconName: "document-open"; - shortcut: StandardKey.Open; + // Unassign the shortcut when there are more than one file providers, since then the file provider's shortcut is + // enabled instead, and Ctrl+O is assigned to the local file provider + shortcut: fileProviderModel.count == 1 ? StandardKey.Open : ""; } Action diff --git a/resources/qml/Menus/OpenFilesMenu.qml b/resources/qml/Menus/OpenFilesMenu.qml index 60fb507b34..3c2b64ee62 100644 --- a/resources/qml/Menus/OpenFilesMenu.qml +++ b/resources/qml/Menus/OpenFilesMenu.qml @@ -36,7 +36,9 @@ Menu CuraApplication.getFileProviderModel().trigger(model.name); } } - shortcut: model.shortcut + // Unassign the shortcuts when the submenu is invisible (i.e. when there is only one file provider) to avoid ambiguous shortcuts. + // When there is a signle file provider, the openAction is assigned with the Ctrl+O shortcut instead. + shortcut: openFilesMenu.visible ? model.shortcut : "" } onObjectAdded: openFilesMenu.insertItem(index, object) onObjectRemoved: openFilesMenu.removeItem(object)