From 3c863fc388b0f7e813005f3b50403572d333b9e5 Mon Sep 17 00:00:00 2001 From: ChrisTerBeke Date: Mon, 4 Dec 2017 16:28:35 +0100 Subject: [PATCH] Get default settings view to work as sidebar component --- cura/CuraApplication.py | 37 +++++++++++++++----------- cura/Settings/SettingsSidebarView.py | 16 +++++++---- cura/Sidebar/SidebarController.py | 33 ++++++++++++----------- cura/Sidebar/SidebarControllerProxy.py | 36 +++++++++++++++++++++++++ cura/Sidebar/SidebarView.py | 15 +++++++++-- cura_app.py | 8 +++--- resources/qml/Cura.qml | 21 +++++++++------ resources/qml/SidebarSettings.qml | 21 +++++++++++++++ 8 files changed, 138 insertions(+), 49 deletions(-) create mode 100644 cura/Sidebar/SidebarControllerProxy.py create mode 100644 resources/qml/SidebarSettings.qml diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 6f5746b49d..d7ded98688 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -32,13 +32,11 @@ from UM.Operations.AddSceneNodeOperation import AddSceneNodeOperation from UM.Operations.RemoveSceneNodeOperation import RemoveSceneNodeOperation from UM.Operations.GroupedOperation import GroupedOperation from UM.Operations.SetTransformOperation import SetTransformOperation + from cura.Arrange import Arrange -from cura.Settings.SettingsSidebarView import SettingsSidebarView from cura.ShapeArray import ShapeArray from cura.ConvexHullDecorator import ConvexHullDecorator from cura.SetParentOperation import SetParentOperation -from cura.Sidebar.SidebarController import SidebarController -from cura.Sidebar.SidebarViewModel import SidebarViewModel from cura.SliceableObjectDecorator import SliceableObjectDecorator from cura.BlockSlicingDecorator import BlockSlicingDecorator @@ -78,6 +76,11 @@ from cura.Settings.ContainerManager import ContainerManager from cura.Settings.GlobalStack import GlobalStack from cura.Settings.ExtruderStack import ExtruderStack +from cura.Sidebar.SidebarController import SidebarController +from cura.Sidebar.SidebarControllerProxy import SidebarControllerProxy +from cura.Sidebar.SidebarViewModel import SidebarViewModel +from cura.Settings.SettingsSidebarView import SettingsSidebarView + from PyQt5.QtCore import QUrl, pyqtSignal, pyqtProperty, QEvent, Q_ENUMS from UM.FlameProfiler import pyqtSlot from PyQt5.QtGui import QColor, QIcon @@ -131,6 +134,7 @@ class CuraApplication(QtApplication): stacksValidationFinished = pyqtSignal() # Emitted whenever a validation is finished def __init__(self): + # this list of dir names will be used by UM to detect an old cura directory for dir_name in ["extruders", "machine_instances", "materials", "plugins", "quality", "user", "variants"]: Resources.addExpectedDirNameInData(dir_name) @@ -159,7 +163,6 @@ class CuraApplication(QtApplication): SettingDefinition.addSettingType("extruder", None, str, Validator) SettingDefinition.addSettingType("optional_extruder", None, str, None) - SettingDefinition.addSettingType("[int]", None, str, None) SettingFunction.registerOperator("extruderValues", ExtruderManager.getExtruderValues) @@ -184,7 +187,8 @@ class CuraApplication(QtApplication): ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.DefinitionChangesContainer) ## Initialise the version upgrade manager with Cura's storage paths. - import UM.VersionUpgradeManager #Needs to be here to prevent circular dependencies. + # Needs to be here to prevent circular dependencies. + import UM.VersionUpgradeManager UM.VersionUpgradeManager.VersionUpgradeManager.getInstance().setCurrentVersions( { @@ -323,6 +327,11 @@ class CuraApplication(QtApplication): self._need_to_show_user_agreement = not Preferences.getInstance().getValue("general/accepted_user_agreement") + # Set the active sidebar view based on user preferences + preferences.addPreference("cura/active_sidebar_view", "default") + active_sidebar_view = preferences.getValue("cura/active_sidebar_view") + self._sidebar_controller.setActiveSidebarView(active_sidebar_view) + for key in [ "dialog_load_path", # dialog_save_path is in LocalFileOutputDevicePlugin "dialog_profile_path", @@ -678,7 +687,6 @@ class CuraApplication(QtApplication): controller = self.getController() controller.setActiveView("SolidView") - controller.setCameraTool("CameraTool") controller.setSelectionTool("SelectionTool") @@ -741,6 +749,11 @@ class CuraApplication(QtApplication): self.exec_() + ## Get the SidebarController of this application. + # \returns SidebarControllers \type{SidebarController} + def getSidebarController(self) -> SidebarController: + return self._sidebar_controller + def getMachineManager(self, *args): if self._machine_manager is None: self._machine_manager = MachineManager.createMachineManager() @@ -786,14 +799,6 @@ class CuraApplication(QtApplication): def getPrintInformation(self): return self._print_information - ## Get the SidebarController of this application. - # A sidebar controller is created if it wasn't yet. - # \returns SidebarControllers \type{SidebarController} - def getSidebarController(self) -> SidebarController: - if self._sidebar_controller is None: - self._sidebar_controller = SidebarController(self) - return self._sidebar_controller - ## Registers objects for the QML engine to use. # # \param engine The QML engine. @@ -808,6 +813,7 @@ class CuraApplication(QtApplication): qmlRegisterUncreatableType(CuraApplication, "Cura", 1, 0, "ResourceTypes", "Just an Enum type") + qmlRegisterType(SidebarViewModel, "Cura", 1, 0, "SidebarViewModel") qmlRegisterType(ExtrudersModel, "Cura", 1, 0, "ExtrudersModel") qmlRegisterType(ContainerSettingsModel, "Cura", 1, 0, "ContainerSettingsModel") qmlRegisterSingletonType(ProfilesModel, "Cura", 1, 0, "ProfilesModel", ProfilesModel.createProfilesModel) @@ -819,8 +825,7 @@ class CuraApplication(QtApplication): qmlRegisterType(MachineNameValidator, "Cura", 1, 0, "MachineNameValidator") qmlRegisterType(UserChangesModel, "Cura", 1, 1, "UserChangesModel") qmlRegisterSingletonType(ContainerManager, "Cura", 1, 0, "ContainerManager", ContainerManager.createContainerManager) - qmlRegisterSingletonType(SidebarController, "Cura", 1, 0, "SidebarController", self.getSidebarController) - qmlRegisterType(SidebarViewModel, "Cura", 1, 0, "SidebarViewModel") + qmlRegisterSingletonType(SidebarControllerProxy, "Cura", 1, 0, "SidebarController", SidebarControllerProxy.createSidebarControllerProxy) # As of Qt5.7, it is necessary to get rid of any ".." in the path for the singleton to work. actions_url = QUrl.fromLocalFile(os.path.abspath(Resources.getPath(CuraApplication.ResourceTypes.QmlFiles, "Actions.qml"))) diff --git a/cura/Settings/SettingsSidebarView.py b/cura/Settings/SettingsSidebarView.py index 648e056044..b1cb88182b 100644 --- a/cura/Settings/SettingsSidebarView.py +++ b/cura/Settings/SettingsSidebarView.py @@ -1,23 +1,29 @@ # Copyright (c) 2017 Ultimaker B.V. -from PyQt5.QtCore import QObject +import os.path +from PyQt5.QtCore import QObject, QUrl + from UM.i18n import i18nCatalog from cura.Sidebar.SidebarView import SidebarView i18n_catalog = i18nCatalog("cura") class SettingsSidebarView(QObject, SidebarView): - def __init__(self): - super().__init__() + def __init__(self, parent = None): + super().__init__(parent) ## As the default sidebar is not a plugin, we have a get plugin ID method to allow the sidebar view model to get the needed data. def getPluginId(self): return "default" - ## As the default sidebar is not a plugin, we have a get meta data method here to allow the sidebar view model to get the needed data. + ## As the default sidebar is not a plugin, we have a add meta data method here to allow the sidebar view model to get the needed data. def getMetaData(self): return { "sidebar_view": { "name": i18n_catalog.i18nc("", "Print settings"), - "weight": 1 + "weight": 0 } } + + ## As the default sidebar is not a plugin, we have a get component path method here to allow the sidebar controller to get the needed data. + def getComponentPath(self): + return QUrl("SidebarSettings.qml") diff --git a/cura/Sidebar/SidebarController.py b/cura/Sidebar/SidebarController.py index 1212091b2d..d7789b9fb8 100644 --- a/cura/Sidebar/SidebarController.py +++ b/cura/Sidebar/SidebarController.py @@ -1,9 +1,7 @@ # Copyright (c) 2017 Ultimaker B.V. from UM.Logger import Logger -from UM.PluginRegistry import PluginRegistry from UM.Signal import Signal -from .SidebarView import SidebarView -from typing import Optional, Dict +from UM.PluginRegistry import PluginRegistry # The sidebar controller manages available sidebar components and decides which one to display. # The cura.qml file uses this controller to repeat over the sidebars and show the active index. @@ -14,6 +12,7 @@ class SidebarController: self._sidebar_views = {} self._active_sidebar_view = None + # Register the sidebar_view plugin type so plugins can expose custom sidebar views. PluginRegistry.addType("sidebar_view", self.addSidebarView) ## Emitted when the list of views changes. @@ -26,28 +25,32 @@ class SidebarController: def getApplication(self): return self._application + ## Get all sidebar views registered in this controller. + def getAllSidebarViews(self): + return self._sidebar_views + ## Add a sidebar view to the registry. # It get's a unique name based on the plugin ID. - def addSidebarView(self, sidebar_view: SidebarView): - name = sidebar_view.getPluginId() - if name not in self._sidebar_views: - self._sidebar_views[name] = sidebar_view + def addSidebarView(self, sidebar_view): + sidebar_view_id = sidebar_view.getPluginId() + if sidebar_view_id not in self._sidebar_views: + self._sidebar_views[sidebar_view_id] = sidebar_view self.sidebarViewsChanged.emit() ## Get a registered sidebar view by name. # The name is the ID of the plugin that registered the view. - def getSidebarView(self, name: str) -> Optional[SidebarView]: + def getSidebarView(self, name: str): try: return self._sidebar_views[name] except KeyError: Logger.log("e", "Unable to find %s in sidebar view list", name) return None - ## Change the active sidebar view to one of the registered views. - def setActiveSidebarView(self, name: str): - print("setting active sidebar view") - self.activeSidebarViewChanged.emit() + ## Get the active sidebar view. + def getActiveSidebarView(self): + return self._active_sidebar_view - ## Get all sidebar views registered in this controller. - def getAllSidebarViews(self) -> Dict[SidebarView]: - return self._sidebar_views + ## Change the active sidebar view to one of the registered views. + def setActiveSidebarView(self, sidebar_view_id: str): + self._active_sidebar_view = self._sidebar_views[sidebar_view_id] + self.activeSidebarViewChanged.emit() diff --git a/cura/Sidebar/SidebarControllerProxy.py b/cura/Sidebar/SidebarControllerProxy.py new file mode 100644 index 0000000000..1278d1cea8 --- /dev/null +++ b/cura/Sidebar/SidebarControllerProxy.py @@ -0,0 +1,36 @@ +# Copyright (c) 2017 Ultimaker B.V. +from PyQt5.QtCore import QObject, pyqtSlot, QUrl, pyqtProperty, pyqtSignal +from UM.Application import Application + + +## The sidebar controller proxy acts a proxy between the sidebar controller and the QMl context of the controller. +class SidebarControllerProxy(QObject): + + def __init__(self, parent = None): + super().__init__(parent) + self._controller = Application.getInstance().getSidebarController() + self._controller.activeSidebarViewChanged.connect(self._onActiveSidebarComponentChanged) + + ## Emitted when the active view changes. + activeSidebarViewChanged = pyqtSignal() + + @classmethod + def createSidebarControllerProxy(self, engine, script_engine): + return SidebarControllerProxy() + + @pyqtSlot() + def setActiveView(self, sidebar_view): + self._controller.setActiveSidebarView(sidebar_view) + + @pyqtProperty(QUrl, notify = activeSidebarViewChanged) + def activeComponentPath(self): + if not self._controller.getActiveSidebarView(): + return QUrl() + return self._controller.getActiveSidebarView().getComponentPath() + + @pyqtSlot(QUrl) + def getSidebarComponentPath(self, sidebar_id): + self._controller.getSidebarView(sidebar_id).getComponentPath() + + def _onActiveSidebarComponentChanged(self): + self.activeSidebarViewChanged.emit() diff --git a/cura/Sidebar/SidebarView.py b/cura/Sidebar/SidebarView.py index f5a9caba94..6b78de1f17 100644 --- a/cura/Sidebar/SidebarView.py +++ b/cura/Sidebar/SidebarView.py @@ -1,7 +1,9 @@ # Copyright (c) 2017 Ultimaker B.V. +from PyQt5.QtCore import QUrl +from UM.Logger import Logger from UM.PluginObject import PluginObject - +from UM.PluginRegistry import PluginRegistry # Abstract class for sidebar view objects. # By default the sidebar is Cura's settings, slicing and printing overview. @@ -10,4 +12,13 @@ class SidebarView(PluginObject): def __init__(self): super().__init__() - print("sidebar view hello") + self._view = None + + ## Get the path to the component QML file as QUrl + def getComponentPath(self): + try: + sidebar_component_file_path = PluginRegistry.getInstance().getMetaData(self.getPluginId())["sidebar_view"]["sidebar_component"] + return QUrl.fromLocalFile(sidebar_component_file_path) + except KeyError: + Logger.log("w", "Could not find sidebar component QML file for %s", self.getPluginId()) + return QUrl() diff --git a/cura_app.py b/cura_app.py index d725bc1200..dd795e5aa4 100755 --- a/cura_app.py +++ b/cura_app.py @@ -22,9 +22,10 @@ if Platform.isLinux(): # Needed for platform.linux_distribution, which is not av if Platform.isWindows() and hasattr(sys, "frozen"): try: del os.environ["PYTHONPATH"] - except KeyError: pass + except KeyError: + pass -#WORKAROUND: GITHUB-704 GITHUB-708 +# WORKAROUND: GITHUB-704 GITHUB-708 # It looks like setuptools creates a .pth file in # the default /usr/lib which causes the default site-packages # to be inserted into sys.path before PYTHONPATH. @@ -45,6 +46,7 @@ def exceptHook(hook_type, value, traceback): _crash_handler = CrashHandler(hook_type, value, traceback) _crash_handler.show() + sys.excepthook = exceptHook # Workaround for a race condition on certain systems where there @@ -75,7 +77,7 @@ faulthandler.enable() # Force an instance of CuraContainerRegistry to be created and reused later. cura.Settings.CuraContainerRegistry.CuraContainerRegistry.getInstance() -# This prestart up check is needed to determine if we should start the application at all. +# This pre-start up check is needed to determine if we should start the application at all. if not cura.CuraApplication.CuraApplication.preStartUp(): sys.exit(0) diff --git a/resources/qml/Cura.qml b/resources/qml/Cura.qml index e144048af7..9385b36b60 100644 --- a/resources/qml/Cura.qml +++ b/resources/qml/Cura.qml @@ -366,19 +366,24 @@ UM.MainWindow onStopMonitoringPrint: base.showPrintMonitor = false } - Sidebar + Loader { - id: sidebar; + id: sidebar anchors { - top: topbar.bottom; - bottom: parent.bottom; - right: parent.right; + top: topbar.bottom + bottom: parent.bottom + right: parent.right } - z: 1 - width: UM.Theme.getSize("sidebar").width; - monitoringPrint: base.showPrintMonitor + + width: UM.Theme.getSize("sidebar").width + + // all sidebar components will have access to the show print monitor flag + property bool showPrintMonitor: base.showPrintMonitor + + // dynamically get the component from the sidebar controller + source: Cura.SidebarController.activeComponentPath } Rectangle diff --git a/resources/qml/SidebarSettings.qml b/resources/qml/SidebarSettings.qml new file mode 100644 index 0000000000..85bee1f096 --- /dev/null +++ b/resources/qml/SidebarSettings.qml @@ -0,0 +1,21 @@ +// Copyright (c) 2017 Ultimaker B.V. +// Cura is released under the terms of the LGPLv3 or higher. + +import QtQuick 2.2 + +Sidebar +{ + id: sidebarSettings + + property bool showPrintMonitor: false + + anchors { + top: parent.top + bottom: parent.bottom + left: parent.left + right: parent.right + } + + width: parent.width + monitoringPrint: showPrintMonitor +}