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) 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/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/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/FileMenu.qml b/resources/qml/Menus/FileMenu.qml index b9845678d6..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,6 +23,13 @@ Menu { id: openMenu action: Cura.Actions.open + visible: (base.fileProviderModel.count == 1) + } + + OpenFilesMenu + { + id: openFilesMenu + visible: (base.fileProviderModel.count > 1) } RecentFilesMenu { } diff --git a/resources/qml/Menus/OpenFilesMenu.qml b/resources/qml/Menus/OpenFilesMenu.qml new file mode 100644 index 0000000000..3c2b64ee62 --- /dev/null +++ b/resources/qml/Menus/OpenFilesMenu.qml @@ -0,0 +1,46 @@ +// Copyright (c) 2020 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: openFilesMenu + title: catalog.i18nc("@title:menu menubar:file", "Open File(s)...") + iconName: "document-open-recent"; + + Instantiator + { + id: fileProviders + 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); + } + } + // 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) + } +} 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) diff --git a/resources/qml/TableView.qml b/resources/qml/TableView.qml new file mode 100644 index 0000000000..dd80304b83 --- /dev/null +++ b/resources/qml/TableView.qml @@ -0,0 +1,68 @@ +// Copyright (C) 2021 Ultimaker B.V. +// Cura is released under the terms of the LGPLv3 or higher. + +import QtQuick 2.10 +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 + + +OldControls.TableView +{ + itemDelegate: Item + { + height: tableCellLabel.implicitHeight + + 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("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 + 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") + } + + // The little rectangle between the vertical and horizontal scrollbars + corner: Rectangle + { + color: UM.Theme.getColor("main_background") + } + + // Override the control arrows + incrementControl: Item { } + decrementControl: Item { } + } +} \ No newline at end of file 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] } }