diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 742ef2fc0b..0310526c2e 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -112,7 +112,9 @@ from cura.UI import CuraSplashScreen, MachineActionManager, PrintInformation from cura.UI.MachineSettingsManager import MachineSettingsManager from cura.UI.ObjectsModel import ObjectsModel from cura.UI.TextManager import TextManager +from cura.UI.AddPrinterPagesModel import AddPrinterPagesModel from cura.UI.WelcomePagesModel import WelcomePagesModel +from cura.UI.WhatsNewPagesModel import WhatsNewPagesModel from .SingleInstance import SingleInstance from .AutoSave import AutoSave @@ -217,6 +219,8 @@ class CuraApplication(QtApplication): self._discovered_printer_model = DiscoveredPrintersModel(self) self._first_start_machine_actions_model = FirstStartMachineActionsModel(self) self._welcome_pages_model = WelcomePagesModel(self) + self._add_printer_pages_model = AddPrinterPagesModel(self) + self._whats_new_pages_model = WhatsNewPagesModel(self) self._text_manager = TextManager(self) self._quality_profile_drop_down_menu_model = None @@ -762,6 +766,8 @@ class CuraApplication(QtApplication): self._output_device_manager.start() self._welcome_pages_model.initialize() + self._add_printer_pages_model.initialize() + self._whats_new_pages_model.initialize() # Detect in which mode to run and execute that mode if self._is_headless: @@ -873,6 +879,14 @@ class CuraApplication(QtApplication): def getWelcomePagesModel(self, *args) -> "WelcomePagesModel": return self._welcome_pages_model + @pyqtSlot(result = QObject) + def getAddPrinterPagesModel(self, *args) -> "AddPrinterPagesModel": + return self._add_printer_pages_model + + @pyqtSlot(result = QObject) + def getWhatsNewPagesModel(self, *args) -> "WhatsNewPagesModel": + return self._whats_new_pages_model + @pyqtSlot(result = QObject) def getMachineSettingsManager(self, *args) -> "MachineSettingsManager": return self._machine_settings_manager @@ -1014,6 +1028,8 @@ class CuraApplication(QtApplication): qmlRegisterSingletonType(MachineActionManager.MachineActionManager, "Cura", 1, 0, "MachineActionManager", self.getMachineActionManager) qmlRegisterType(WelcomePagesModel, "Cura", 1, 0, "WelcomePagesModel") + qmlRegisterType(WhatsNewPagesModel, "Cura", 1, 0, "WhatsNewPagesModel") + qmlRegisterType(AddPrinterPagesModel, "Cura", 1, 0, "AddPrinterPagesModel") qmlRegisterType(TextManager, "Cura", 1, 0, "TextManager") qmlRegisterType(NetworkMJPGImage, "Cura", 1, 0, "NetworkMJPGImage") @@ -1098,7 +1114,6 @@ class CuraApplication(QtApplication): self._camera_animation.setTarget(Selection.getSelectedObject(0).getWorldPosition()) self._camera_animation.start() - requestAddPrinter = pyqtSignal() activityChanged = pyqtSignal() sceneBoundingBoxChanged = pyqtSignal() @@ -1758,3 +1773,16 @@ class CuraApplication(QtApplication): def getSidebarCustomMenuItems(self) -> list: return self._sidebar_custom_menu_items + @pyqtSlot(result = bool) + def shouldShowWelcomeDialog(self) -> bool: + # Only show the complete flow if there is no printer yet. + return self._machine_manager.activeMachine is None + + @pyqtSlot(result = bool) + def shouldShowWhatsNewDialog(self) -> bool: + has_active_machine = self._machine_manager.activeMachine is not None + has_app_just_upgraded = self.hasJustUpdatedFromOldVersion() + + # Only show the what's new dialog if there's no machine and we have just upgraded + show_whatsnew_only = has_active_machine and has_app_just_upgraded + return show_whatsnew_only diff --git a/cura/UI/AddPrinterPagesModel.py b/cura/UI/AddPrinterPagesModel.py new file mode 100644 index 0000000000..55bb1500ba --- /dev/null +++ b/cura/UI/AddPrinterPagesModel.py @@ -0,0 +1,30 @@ +# Copyright (c) 2019 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + +from .WelcomePagesModel import WelcomePagesModel + + +# +# This Qt ListModel is more or less the same the WelcomePagesModel, except that this model is only for adding a printer, +# so only the steps for adding a printer is included. +# +class AddPrinterPagesModel(WelcomePagesModel): + + def initialize(self) -> None: + self._pages.append({"id": "add_network_or_local_printer", + "page_url": self._getBuiltinWelcomePagePath("AddNetworkOrLocalPrinterContent.qml"), + "next_page_id": "machine_actions", + "next_page_button_text": self._catalog.i18nc("@action:button", "Add"), + }) + self._pages.append({"id": "add_printer_by_ip", + "page_url": self._getBuiltinWelcomePagePath("AddPrinterByIpContent.qml"), + "next_page_id": "machine_actions", + }) + self._pages.append({"id": "machine_actions", + "page_url": self._getBuiltinWelcomePagePath("FirstStartMachineActionsContent.qml"), + "should_show_function": self.shouldShowMachineActions, + }) + self.setItems(self._pages) + + +__all__ = ["AddPrinterPagesModel"] diff --git a/cura/UI/WelcomePagesModel.py b/cura/UI/WelcomePagesModel.py index b26964bd2e..f75082e20d 100644 --- a/cura/UI/WelcomePagesModel.py +++ b/cura/UI/WelcomePagesModel.py @@ -6,6 +6,7 @@ from typing import TYPE_CHECKING, Optional, List, Dict, Any from PyQt5.QtCore import QUrl, Qt, pyqtSlot, pyqtProperty, pyqtSignal +from UM.i18n import i18nCatalog from UM.Logger import Logger from UM.Qt.ListModel import ListModel from UM.Resources import Resources @@ -23,6 +24,9 @@ if TYPE_CHECKING: # - page_url : The QUrl to the QML file that contains the content of this page # - next_page_id : (OPTIONAL) The next page ID to go to when this page finished. This is optional. If this is not # provided, it will go to the page with the current index + 1 +# - next_page_button_text: (OPTIONAL) The text to show for the "next" button, by default it's the translated text of +# "Next". Note that each step QML can decide whether to use this text or not, so it's not +# mandatory. # - should_show_function : (OPTIONAL) An optional function that returns True/False indicating if this page should be # shown. By default all pages should be shown. If a function returns False, that page will # be skipped and its next page will be shown. @@ -34,6 +38,7 @@ class WelcomePagesModel(ListModel): IdRole = Qt.UserRole + 1 # Page ID PageUrlRole = Qt.UserRole + 2 # URL to the page's QML file NextPageIdRole = Qt.UserRole + 3 # The next page ID it should go to + NextPageButtonTextRole = Qt.UserRole + 4 # The text for the next page button def __init__(self, application: "CuraApplication", parent: Optional["QObject"] = None) -> None: super().__init__(parent) @@ -41,8 +46,12 @@ class WelcomePagesModel(ListModel): self.addRoleName(self.IdRole, "id") self.addRoleName(self.PageUrlRole, "page_url") self.addRoleName(self.NextPageIdRole, "next_page_id") + self.addRoleName(self.NextPageButtonTextRole, "next_page_button_text") self._application = application + self._catalog = i18nCatalog("cura") + + self._default_next_button_text = self._catalog.i18nc("@action:button", "Next") self._pages = [] # type: List[Dict[str, Any]] @@ -137,7 +146,8 @@ class WelcomePagesModel(ListModel): page_index = self.getPageIndexById(page_id) if page_index is None: # FIXME: If we cannot find the next page, we cannot do anything here. - Logger.log("e", "Cannot find page with ID [%s]", page_index) + Logger.log("e", "Cannot find page with ID [%s], go to the next page by default", page_index) + self.goToNextPage() return if self._shouldPageBeShown(page_index): @@ -180,38 +190,48 @@ class WelcomePagesModel(ListModel): os.path.join("WelcomePages", page_filename))) def initialize(self) -> None: - # Add default welcome pages - self._pages.append({"id": "welcome", - "page_url": self._getBuiltinWelcomePagePath("WelcomeContent.qml"), - }) - self._pages.append({"id": "user_agreement", - "page_url": self._getBuiltinWelcomePagePath("UserAgreementContent.qml"), - }) - self._pages.append({"id": "whats_new", - "page_url": self._getBuiltinWelcomePagePath("WhatsNewContent.qml"), - }) - self._pages.append({"id": "data_collections", - "page_url": self._getBuiltinWelcomePagePath("DataCollectionsContent.qml"), - }) - self._pages.append({"id": "add_network_or_local_printer", - "page_url": self._getBuiltinWelcomePagePath("AddNetworkOrLocalPrinterContent.qml"), - "next_page_id": "machine_actions", - }) - self._pages.append({"id": "add_printer_by_ip", - "page_url": self._getBuiltinWelcomePagePath("AddPrinterByIpContent.qml"), - "next_page_id": "machine_actions", - }) - self._pages.append({"id": "machine_actions", - "page_url": self._getBuiltinWelcomePagePath("FirstStartMachineActionsContent.qml"), - "next_page_id": "cloud", - "should_show_function": self.shouldShowMachineActions, - }) - self._pages.append({"id": "cloud", - "page_url": self._getBuiltinWelcomePagePath("CloudContent.qml"), - }) + # All pages + all_pages_list = [{"id": "welcome", + "page_url": self._getBuiltinWelcomePagePath("WelcomeContent.qml"), + }, + {"id": "user_agreement", + "page_url": self._getBuiltinWelcomePagePath("UserAgreementContent.qml"), + }, + {"id": "whats_new", + "page_url": self._getBuiltinWelcomePagePath("WhatsNewContent.qml"), + }, + {"id": "data_collections", + "page_url": self._getBuiltinWelcomePagePath("DataCollectionsContent.qml"), + }, + {"id": "add_network_or_local_printer", + "page_url": self._getBuiltinWelcomePagePath("AddNetworkOrLocalPrinterContent.qml"), + "next_page_id": "machine_actions", + }, + {"id": "add_printer_by_ip", + "page_url": self._getBuiltinWelcomePagePath("AddPrinterByIpContent.qml"), + "next_page_id": "machine_actions", + }, + {"id": "machine_actions", + "page_url": self._getBuiltinWelcomePagePath("FirstStartMachineActionsContent.qml"), + "next_page_id": "cloud", + "should_show_function": self.shouldShowMachineActions, + }, + {"id": "cloud", + "page_url": self._getBuiltinWelcomePagePath("CloudContent.qml"), + }, + ] + self._pages = all_pages_list self.setItems(self._pages) + # For convenience, inject the default "next" button text to each item if it's not present. + def setItems(self, items: List[Dict[str, Any]]) -> None: + for item in items: + if "next_page_button_text" not in item: + item["next_page_button_text"] = self._default_next_button_text + + super().setItems(items) + # Indicates if the machine action panel should be shown by checking if there's any first start machine actions # available. def shouldShowMachineActions(self) -> bool: diff --git a/cura/UI/WhatsNewPagesModel.py b/cura/UI/WhatsNewPagesModel.py new file mode 100644 index 0000000000..5b968ae574 --- /dev/null +++ b/cura/UI/WhatsNewPagesModel.py @@ -0,0 +1,22 @@ +# Copyright (c) 2019 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + +from .WelcomePagesModel import WelcomePagesModel + + +# +# This Qt ListModel is more or less the same the WelcomePagesModel, except that this model is only for showing the +# "what's new" page. This is also used in the "Help" menu to show the changes log. +# +class WhatsNewPagesModel(WelcomePagesModel): + + def initialize(self) -> None: + self._pages = [] + self._pages.append({"id": "whats_new", + "page_url": self._getBuiltinWelcomePagePath("WhatsNewContent.qml"), + "next_page_button_text": self._catalog.i18nc("@action:button", "Close"), + }) + self.setItems(self._pages) + + +__all__ = ["WhatsNewPagesModel"] diff --git a/plugins/ChangeLogPlugin/ChangeLog.py b/plugins/ChangeLogPlugin/ChangeLog.py deleted file mode 100644 index 241d7a8953..0000000000 --- a/plugins/ChangeLogPlugin/ChangeLog.py +++ /dev/null @@ -1,65 +0,0 @@ -# Copyright (c) 2019 Ultimaker B.V. -# Cura is released under the terms of the LGPLv3 or higher. - -import os.path - -from PyQt5.QtCore import QObject - -from UM.i18n import i18nCatalog -from UM.Extension import Extension -from UM.Application import Application -from UM.PluginRegistry import PluginRegistry -from UM.Version import Version - -catalog = i18nCatalog("cura") - - -class ChangeLog(Extension, QObject): - def __init__(self, parent = None): - QObject.__init__(self, parent) - Extension.__init__(self) - self._changelog_window = None - self._changelog_context = None - version_string = Application.getInstance().getVersion() - if version_string is not "master": - self._current_app_version = Version(version_string) - else: - self._current_app_version = None - - Application.getInstance().engineCreatedSignal.connect(self._onEngineCreated) - Application.getInstance().getPreferences().addPreference("general/latest_version_changelog_shown", "2.0.0") #First version of CURA with uranium - self.setMenuName(catalog.i18nc("@item:inmenu", "Changelog")) - self.addMenuItem(catalog.i18nc("@item:inmenu", "Show Changelog"), self.showChangelog) - - def _onEngineCreated(self): - if not self._current_app_version: - return #We're on dev branch. - - if Application.getInstance().getPreferences().getValue("general/latest_version_changelog_shown") == "master": - latest_version_shown = Version("0.0.0") - else: - latest_version_shown = Version(Application.getInstance().getPreferences().getValue("general/latest_version_changelog_shown")) - - Application.getInstance().getPreferences().setValue("general/latest_version_changelog_shown", Application.getInstance().getVersion()) - - # Do not show the changelog when there is no global container stack - # This implies we are running Cura for the first time. - if not Application.getInstance().getGlobalContainerStack(): - return - - if self._current_app_version > latest_version_shown: - self.showChangelog() - - def showChangelog(self): - if not self._changelog_window: - self.createChangelogWindow() - - self._changelog_window.show() - - def hideChangelog(self): - if self._changelog_window: - self._changelog_window.hide() - - def createChangelogWindow(self): - path = os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), "ChangeLog.qml") - self._changelog_window = Application.getInstance().createQmlComponent(path, {"manager": self}) diff --git a/plugins/ChangeLogPlugin/ChangeLog.qml b/plugins/ChangeLogPlugin/ChangeLog.qml deleted file mode 100644 index 7089a56caf..0000000000 --- a/plugins/ChangeLogPlugin/ChangeLog.qml +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) 2015 Ultimaker B.V. -// Cura is released under the terms of the LGPLv3 or higher. - -import QtQuick 2.1 -import QtQuick.Controls 1.3 -import QtQuick.Layouts 1.1 -import QtQuick.Window 2.1 - -import UM 1.3 as UM -import Cura 1.0 as Cura - - -UM.Dialog -{ - id: base - minimumWidth: (UM.Theme.getSize("modal_window_minimum").width * 0.75) | 0 - minimumHeight: (UM.Theme.getSize("modal_window_minimum").height * 0.75) | 0 - width: minimumWidth - height: minimumHeight - title: catalog.i18nc("@label", "Changelog") - - TextArea - { - anchors.fill: parent - text: CuraApplication.getTextManager().getChangeLogText() - readOnly: true; - textFormat: TextEdit.RichText - } - - rightButtons: [ - Button - { - UM.I18nCatalog - { - id: catalog - name: "cura" - } - - text: catalog.i18nc("@action:button", "Close") - onClicked: base.hide() - } - ] -} diff --git a/plugins/ChangeLogPlugin/__init__.py b/plugins/ChangeLogPlugin/__init__.py deleted file mode 100644 index a5452b60c8..0000000000 --- a/plugins/ChangeLogPlugin/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -# Copyright (c) 2015 Ultimaker B.V. -# Cura is released under the terms of the LGPLv3 or higher. - -from . import ChangeLog - - -def getMetaData(): - return {} - -def register(app): - return {"extension": ChangeLog.ChangeLog()} diff --git a/plugins/ChangeLogPlugin/plugin.json b/plugins/ChangeLogPlugin/plugin.json deleted file mode 100644 index 92041d1543..0000000000 --- a/plugins/ChangeLogPlugin/plugin.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "Changelog", - "author": "Ultimaker B.V.", - "version": "1.0.1", - "description": "Shows changes since latest checked version.", - "api": "6.0", - "i18n-catalog": "cura" -} diff --git a/plugins/UltimakerMachineActions/UM2UpgradeSelectionMachineAction.qml b/plugins/UltimakerMachineActions/UM2UpgradeSelectionMachineAction.qml index 69023d3432..0aef5e08e1 100644 --- a/plugins/UltimakerMachineActions/UM2UpgradeSelectionMachineAction.qml +++ b/plugins/UltimakerMachineActions/UM2UpgradeSelectionMachineAction.qml @@ -23,11 +23,11 @@ Cura.MachineAction Label { id: pageDescription - anchors.top: pageTitle.bottom + anchors.top: parent.top anchors.topMargin: UM.Theme.getSize("default_margin").height width: parent.width wrapMode: Text.WordWrap - text: catalog.i18nc("@label","Please select any upgrades made to this Ultimaker 2.") + text: catalog.i18nc("@label", "Please select any upgrades made to this Ultimaker 2.") font: UM.Theme.getFont("medium") renderType: Text.NativeRendering } diff --git a/plugins/VersionUpgrade/VersionUpgrade40to41/VersionUpgrade40to41.py b/plugins/VersionUpgrade/VersionUpgrade40to41/VersionUpgrade40to41.py index db7d3c75da..03272e63d5 100644 --- a/plugins/VersionUpgrade/VersionUpgrade40to41/VersionUpgrade40to41.py +++ b/plugins/VersionUpgrade/VersionUpgrade40to41/VersionUpgrade40to41.py @@ -71,6 +71,11 @@ class VersionUpgrade40to41(VersionUpgrade): parser["general"]["version"] = "6" if "metadata" not in parser: parser["metadata"] = {} + + # Remove changelog plugin + if "latest_version_changelog_shown" in parser["general"]: + del parser["general"]["latest_version_changelog_shown"] + parser["metadata"]["setting_version"] = "7" result = io.StringIO() diff --git a/resources/bundled_packages/cura.json b/resources/bundled_packages/cura.json index 3dcc02a503..cf8ecfb010 100644 --- a/resources/bundled_packages/cura.json +++ b/resources/bundled_packages/cura.json @@ -33,23 +33,6 @@ } } }, - "ChangeLogPlugin": { - "package_info": { - "package_id": "ChangeLogPlugin", - "package_type": "plugin", - "display_name": "Change Log", - "description": "Shows changes since latest checked version.", - "package_version": "1.0.1", - "sdk_version": "6.0.0", - "website": "https://ultimaker.com", - "author": { - "author_id": "UltimakerPackages", - "display_name": "Ultimaker B.V.", - "email": "plugins@ultimaker.com", - "website": "https://ultimaker.com" - } - } - }, "CuraDrive": { "package_info": { "package_id": "CuraDrive", diff --git a/resources/qml/Actions.qml b/resources/qml/Actions.qml index 81c14aa01a..ce9618a560 100644 --- a/resources/qml/Actions.qml +++ b/resources/qml/Actions.qml @@ -61,6 +61,7 @@ Item property alias documentation: documentationAction; property alias showTroubleshooting: showTroubleShootingAction property alias reportBug: reportBugAction; + property alias whatsNew: whatsNewAction property alias about: aboutAction; property alias toggleFullScreen: toggleFullScreenAction; @@ -229,6 +230,12 @@ Item onTriggered: CuraActions.openBugReportPage(); } + Action + { + id: whatsNewAction; + text: catalog.i18nc("@action:inmenu menubar:help", "What's New"); + } + Action { id: aboutAction; diff --git a/resources/qml/Cura.qml b/resources/qml/Cura.qml index b6f1268e7f..f5cbe8ad53 100644 --- a/resources/qml/Cura.qml +++ b/resources/qml/Cura.qml @@ -46,7 +46,7 @@ UM.MainWindow { id: greyOutBackground anchors.fill: parent - visible: welcomeDialog.visible + visible: welcomeDialogItem.visible color: UM.Theme.getColor("window_disabled_background") opacity: 0.7 z: stageMenu.z + 1 @@ -61,9 +61,9 @@ UM.MainWindow } } - WelcomeDialog + WelcomeDialogItem { - id: welcomeDialog + id: welcomeDialogItem visible: true // True, so if somehow no preferences are found/loaded, it's shown anyway. z: greyOutBackground.z + 1 } @@ -71,29 +71,43 @@ UM.MainWindow Component.onCompleted: { CuraApplication.setMinimumWindowSize(UM.Theme.getSize("window_minimum_size")) - // Workaround silly issues with QML Action's shortcut property. - // - // Currently, there is no way to define shortcuts as "Application Shortcut". - // This means that all Actions are "Window Shortcuts". The code for this - // implements a rather naive check that just checks if any of the action's parents - // are a window. Since the "Actions" object is a singleton it has no parent by - // default. If we set its parent to something contained in this window, the - // shortcut will activate properly because one of its parents is a window. - // - // This has been fixed for QtQuick Controls 2 since the Shortcut item has a context property. - Cura.Actions.parent = backgroundItem CuraApplication.purgeWindows() + } - if (CuraApplication.needToShowUserAgreement) + Connections + { + target: CuraApplication + onInitializationFinished: { - welcomeDialog.visible = true + // Workaround silly issues with QML Action's shortcut property. + // + // Currently, there is no way to define shortcuts as "Application Shortcut". + // This means that all Actions are "Window Shortcuts". The code for this + // implements a rather naive check that just checks if any of the action's parents + // are a window. Since the "Actions" object is a singleton it has no parent by + // default. If we set its parent to something contained in this window, the + // shortcut will activate properly because one of its parents is a window. + // + // This has been fixed for QtQuick Controls 2 since the Shortcut item has a context property. + Cura.Actions.parent = backgroundItem + + if (CuraApplication.shouldShowWelcomeDialog()) + { + welcomeDialogItem.visible = true + } + else + { + welcomeDialogItem.visible = false + } + + // Reuse the welcome dialog item to show "What's New" only. + if (CuraApplication.shouldShowWhatsNewDialog()) + { + welcomeDialogItem.model = CuraApplication.getWhatsNewPagesModel() + welcomeDialogItem.progressBarVisible = false + welcomeDialogItem.visible = true + } } - else - { - welcomeDialog.visible = false - } - // TODO: While the new onboarding process contains the user-agreement, - // it should probably not entirely rely on 'needToShowUserAgreement' for show/hide. } Item @@ -731,44 +745,6 @@ UM.MainWindow } } - AddMachineDialog - { - id: addMachineDialog - onMachineAdded: - { - machineActionsWizard.firstRun = addMachineDialog.firstRun - machineActionsWizard.start(id) - } - } - - // Dialog to handle first run machine actions - UM.Wizard - { - id: machineActionsWizard; - - title: catalog.i18nc("@title:window", "Add Printer") - property var machine; - - function start(id) - { - var actions = Cura.MachineActionManager.getFirstStartActions(id) - resetPages() // Remove previous pages - - for (var i = 0; i < actions.length; i++) - { - actions[i].displayItem.reset() - machineActionsWizard.appendPage(actions[i].displayItem, catalog.i18nc("@title", actions[i].label)); - } - - //Only start if there are actions to perform. - if (actions.length > 0) - { - machineActionsWizard.currentPage = 0; - show() - } - } - } - MessageDialog { id: messageDialog @@ -812,10 +788,32 @@ UM.MainWindow } } + Cura.WizardDialog + { + id: addMachineDialog + title: catalog.i18nc("@title:window", "Add Printer") + model: CuraApplication.getAddPrinterPagesModel() + progressBarVisible: false + } + + Cura.WizardDialog + { + id: whatsNewDialog + title: catalog.i18nc("@title:window", "What's New") + model: CuraApplication.getWhatsNewPagesModel() + progressBarVisible: false + } + + Connections + { + target: Cura.Actions.whatsNew + onTriggered: whatsNewDialog.show() + } + Connections { target: Cura.Actions.addMachine - onTriggered: addMachineDialog.visible = true; + onTriggered: addMachineDialog.show() } AboutDialog @@ -829,31 +827,17 @@ UM.MainWindow onTriggered: aboutDialog.visible = true; } - Connections - { - target: CuraApplication - onRequestAddPrinter: - { - addMachineDialog.visible = true - addMachineDialog.firstRun = false - } - } - Timer { - id: startupTimer; - interval: 100; - repeat: false; - running: true; + id: startupTimer + interval: 100 + repeat: false + running: true onTriggered: { - if(!base.visible) + if (!base.visible) { - base.visible = true; - } - if(!CuraApplication.needToShowUserAgreement && Cura.MachineManager.activeMachine == null) - { - addMachineDialog.open(); + base.visible = true } } } diff --git a/resources/qml/Dialogs/AddMachineDialog.qml b/resources/qml/Dialogs/AddMachineDialog.qml deleted file mode 100644 index d3988155b7..0000000000 --- a/resources/qml/Dialogs/AddMachineDialog.qml +++ /dev/null @@ -1,321 +0,0 @@ -// Copyright (c) 2019 Ultimaker B.V. -// Cura is released under the terms of the LGPLv3 or higher. - -import QtQuick 2.2 -import QtQuick.Controls 1.1 -import QtQuick.Layouts 1.1 -import QtQuick.Window 2.1 - -import QtQuick.Controls.Styles 1.1 - -import UM 1.2 as UM -import Cura 1.0 as Cura - - -UM.Dialog -{ - id: base - title: catalog.i18nc("@title:window", "Add Printer") - property bool firstRun: false - property string preferredCategory: "Ultimaker" - property string activeCategory: preferredCategory - - minimumWidth: UM.Theme.getSize("modal_window_minimum").width - minimumHeight: UM.Theme.getSize("modal_window_minimum").height - width: minimumWidth - height: minimumHeight - - flags: - { - var window_flags = Qt.Dialog | Qt.CustomizeWindowHint | Qt.WindowTitleHint; - if (Cura.MachineManager.activeDefinitionId !== "") //Disallow closing the window if we have no active printer yet. You MUST add a printer. - { - window_flags |= Qt.WindowCloseButtonHint; - } - return window_flags; - } - - onVisibilityChanged: - { - // Reset selection and machine name - if (visible) - { - activeCategory = preferredCategory; - machineList.currentIndex = 0; - machineName.text = getMachineName(); - } - } - - signal machineAdded(string id) - - function getMachineName() - { - if (machineList.model.getItem(machineList.currentIndex) != undefined) - { - return machineList.model.getItem(machineList.currentIndex).name; - } - return ""; - } - - function getMachineMetaDataEntry(key) - { - if (machineList.model.getItem(machineList.currentIndex) != undefined) - { - return machineList.model.getItem(machineList.currentIndex).metadata[key]; - } - return ""; - } - - Label - { - id: titleLabel - - anchors - { - top: parent.top - left: parent.left - topMargin: UM.Theme.getSize("default_margin").height - } - text: catalog.i18nc("@title:tab", "Add a printer to Cura") - - font.pointSize: 18 - } - - Label - { - id: captionLabel - anchors - { - left: parent.left - top: titleLabel.bottom - topMargin: UM.Theme.getSize("default_margin").height - } - text: catalog.i18nc("@title:tab", "Select the printer you want to use from the list below.\n\nIf your printer is not in the list, use the \"Custom FFF Printer\" from the \"Custom\" category and adjust the settings to match your printer in the next dialog.") - width: parent.width - wrapMode: Text.WordWrap - } - - ScrollView - { - id: machinesHolder - - anchors - { - top: captionLabel.visible ? captionLabel.bottom : parent.top; - topMargin: captionLabel.visible ? UM.Theme.getSize("default_margin").height : 0; - bottom: addPrinterButton.top; - bottomMargin: UM.Theme.getSize("default_margin").height - } - - width: Math.round(parent.width * 0.45) - - frameVisible: true; - Rectangle - { - parent: viewport - anchors.fill: parent - color: palette.light - } - - ListView - { - id: machineList - - model: UM.DefinitionContainersModel - { - id: machineDefinitionsModel - filter: { "visible": true } - sectionProperty: "category" - preferredSectionValue: preferredCategory - } - - section.property: "section" - section.delegate: Button - { - id: machineSectionButton - text: section - width: machineList.width - style: ButtonStyle - { - background: Item - { - height: UM.Theme.getSize("standard_list_lineheight").height - width: machineList.width - } - label: Label - { - anchors.left: parent.left - anchors.leftMargin: UM.Theme.getSize("standard_arrow").width + UM.Theme.getSize("default_margin").width - text: control.text - color: palette.windowText - font.bold: true - UM.RecolorImage - { - id: downArrow - anchors.verticalCenter: parent.verticalCenter - anchors.right: parent.left - anchors.rightMargin: UM.Theme.getSize("default_margin").width - width: UM.Theme.getSize("standard_arrow").width - height: UM.Theme.getSize("standard_arrow").height - sourceSize.height: width - color: palette.windowText - source: base.activeCategory == section ? UM.Theme.getIcon("arrow_bottom") : UM.Theme.getIcon("arrow_right") - } - } - } - - onClicked: - { - base.activeCategory = section; - if (machineList.model.getItem(machineList.currentIndex).section != section) - { - // Find the first machine from this section - for(var i = 0; i < machineList.model.count; i++) - { - var item = machineList.model.getItem(i); - if (item.section == section) - { - machineList.currentIndex = i; - break; - } - } - } - machineName.text = getMachineName(); - } - } - - delegate: RadioButton - { - id: machineButton - - anchors.left: parent.left - anchors.leftMargin: UM.Theme.getSize("standard_list_lineheight").width - - opacity: 1; - height: UM.Theme.getSize("standard_list_lineheight").height; - - checked: ListView.isCurrentItem; - - exclusiveGroup: printerGroup; - - text: model.name - - onClicked: - { - ListView.view.currentIndex = index; - machineName.text = getMachineName() - } - - states: State - { - name: "collapsed"; - when: base.activeCategory != model.section; - - PropertyChanges { target: machineButton; opacity: 0; height: 0; } - } - } - } - } - - Column - { - anchors - { - top: machinesHolder.top - left: machinesHolder.right - right: parent.right - leftMargin: UM.Theme.getSize("default_margin").width - } - - spacing: UM.Theme.getSize("default_margin").height - Label - { - width: parent.width - wrapMode: Text.WordWrap - text: getMachineName() - font.pointSize: 16 - elide: Text.ElideRight - } - Grid - { - width: parent.width - columns: 2 - rowSpacing: UM.Theme.getSize("default_lining").height - columnSpacing: UM.Theme.getSize("default_margin").width - verticalItemAlignment: Grid.AlignVCenter - - Label - { - wrapMode: Text.WordWrap - text: catalog.i18nc("@label", "Manufacturer") - } - Label - { - width: Math.floor(parent.width * 0.65) - wrapMode: Text.WordWrap - text: getMachineMetaDataEntry("manufacturer") - } - Label - { - wrapMode: Text.WordWrap - text: catalog.i18nc("@label", "Author") - } - Label - { - width: Math.floor(parent.width * 0.75) - wrapMode: Text.WordWrap - text: getMachineMetaDataEntry("author") - } - Label - { - wrapMode: Text.WordWrap - text: catalog.i18nc("@label", "Printer Name") - } - TextField - { - id: machineName - text: getMachineName() - width: Math.floor(parent.width * 0.75) - maximumLength: 40 - //validator: Cura.MachineNameValidator { } //TODO: Gives a segfault in PyQt5.6. For now, we must use a signal on text changed. - validator: RegExpValidator - { - regExp: { - machineName.machine_name_validator.machineNameRegex - } - } - property var machine_name_validator: Cura.MachineNameValidator { } - } - } - } - - Button - { - id: addPrinterButton - text: catalog.i18nc("@action:button", "Add Printer") - anchors.bottom: parent.bottom - anchors.right: parent.right - onClicked: addMachine() - } - - onAccepted: addMachine() - - function addMachine() - { - base.visible = false - var item = machineList.model.getItem(machineList.currentIndex); - Cura.MachineManager.addMachine(item.id, machineName.text) - base.machineAdded(item.id) // Emit signal that the user added a machine. - } - - Item - { - UM.I18nCatalog - { - id: catalog; - name: "cura"; - } - SystemPalette { id: palette } - ExclusiveGroup { id: printerGroup; } - } -} diff --git a/resources/qml/MainWindow/ApplicationMenu.qml b/resources/qml/MainWindow/ApplicationMenu.qml index 2f18df8914..7f343eb8f4 100644 --- a/resources/qml/MainWindow/ApplicationMenu.qml +++ b/resources/qml/MainWindow/ApplicationMenu.qml @@ -101,6 +101,7 @@ Item MenuItem { action: Cura.Actions.documentation } MenuItem { action: Cura.Actions.reportBug } MenuSeparator { } + MenuItem { action: Cura.Actions.whatsNew } MenuItem { action: Cura.Actions.about } } } diff --git a/resources/qml/Preferences/MachinesPage.qml b/resources/qml/Preferences/MachinesPage.qml index 9bb17470c8..cbbb0b80bf 100644 --- a/resources/qml/Preferences/MachinesPage.qml +++ b/resources/qml/Preferences/MachinesPage.qml @@ -45,7 +45,7 @@ UM.ManagementPage { text: catalog.i18nc("@action:button", "Add"); iconName: "list-add"; - onClicked: CuraApplication.requestAddPrinter() + onClicked: Cura.Actions.addMachine.trigger() }, Button { diff --git a/resources/qml/WelcomePages/AddLocalPrinterScrollView.qml b/resources/qml/WelcomePages/AddLocalPrinterScrollView.qml index ff64cdc33e..2feff668e7 100644 --- a/resources/qml/WelcomePages/AddLocalPrinterScrollView.qml +++ b/resources/qml/WelcomePages/AddLocalPrinterScrollView.qml @@ -12,9 +12,12 @@ import Cura 1.0 as Cura // This is the scroll view widget for adding a (local) printer. This scroll view shows a list view with printers // categorized into 3 categories: "Ultimaker", "Custom", and "Other". // -ScrollView +Item { + UM.I18nCatalog { id: catalog; name: "cura" } + id: base + height: childrenRect.height // The currently selected machine item in the local machine list. property var currentItem: (machineList.currentIndex >= 0) @@ -25,12 +28,16 @@ ScrollView // By default (when this list shows up) we always expand the "Ultimaker" section. property string preferredCategory: "Ultimaker" - ScrollBar.horizontal.policy: ScrollBar.AlwaysOff - ScrollBar.vertical.policy: ScrollBar.AsNeeded property int maxItemCountAtOnce: 10 // show at max 10 items at once, otherwise you need to scroll. - height: maxItemCountAtOnce * UM.Theme.getSize("action_button").height - clip: true + // User-editable printer name + property alias printerName: printerNameTextField.text + property alias isPrinterNameValid: printerNameTextField.acceptableInput + + onCurrentItemChanged: + { + printerName = currentItem == null ? "" : currentItem.name + } function updateCurrentItemUponSectionChange() { @@ -51,103 +58,164 @@ ScrollView updateCurrentItemUponSectionChange() } - ListView + Item { - id: machineList + id: localPrinterSelectionItem + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + height: childrenRect.height - model: UM.DefinitionContainersModel + // ScrollView + ListView for selecting a local printer to add + ScrollView { - id: machineDefinitionsModel - filter: { "visible": true } - sectionProperty: "category" - preferredSectionValue: preferredCategory - } - - section.property: "section" - section.delegate: sectionHeader - delegate: machineButton - } - - Component - { - id: sectionHeader - - Button - { - id: button - width: ListView.view.width - height: UM.Theme.getSize("action_button").height - text: section - - property bool isActive: base.currentSection == section - - background: Rectangle - { - anchors.fill: parent - color: isActive ? UM.Theme.getColor("setting_control_highlight") : "transparent" - } - - contentItem: Item - { - width: childrenRect.width - height: UM.Theme.getSize("action_button").height - - UM.RecolorImage - { - id: arrow - anchors.left: parent.left - width: UM.Theme.getSize("standard_arrow").width - height: UM.Theme.getSize("standard_arrow").height - sourceSize.width: width - sourceSize.height: height - color: UM.Theme.getColor("text") - source: base.currentSection == section ? UM.Theme.getIcon("arrow_bottom") : UM.Theme.getIcon("arrow_right") - } - - Label - { - id: label - anchors.left: arrow.right - anchors.leftMargin: UM.Theme.getSize("default_margin").width - verticalAlignment: Text.AlignVCenter - text: button.text - font.bold: true - renderType: Text.NativeRendering - } - } - - onClicked: - { - base.currentSection = section - base.updateCurrentItemUponSectionChange() - } - } - } - - Component - { - id: machineButton - - Cura.RadioButton - { - id: radioButton + id: scrollView anchors.left: parent.left - anchors.leftMargin: UM.Theme.getSize("standard_list_lineheight").width anchors.right: parent.right - anchors.rightMargin: UM.Theme.getSize("default_margin").width - height: visible ? UM.Theme.getSize("standard_list_lineheight").height : 0 + anchors.top: parent.top - checked: ListView.view.currentIndex == index - onCheckedChanged: + height: maxItemCountAtOnce * UM.Theme.getSize("action_button").height + + ScrollBar.horizontal.policy: ScrollBar.AlwaysOff + ScrollBar.vertical.policy: ScrollBar.AsNeeded + + clip: true + + ListView { - if(checked) + id: machineList + + model: UM.DefinitionContainersModel { - machineList.currentIndex = index + id: machineDefinitionsModel + filter: { "visible": true } + sectionProperty: "category" + preferredSectionValue: preferredCategory + } + + section.property: "section" + section.delegate: sectionHeader + delegate: machineButton + } + + Component + { + id: sectionHeader + + Button + { + id: button + width: ListView.view.width + height: UM.Theme.getSize("action_button").height + text: section + + property bool isActive: base.currentSection == section + + background: Rectangle + { + anchors.fill: parent + color: isActive ? UM.Theme.getColor("setting_control_highlight") : "transparent" + } + + contentItem: Item + { + width: childrenRect.width + height: UM.Theme.getSize("action_button").height + + UM.RecolorImage + { + id: arrow + anchors.left: parent.left + width: UM.Theme.getSize("standard_arrow").width + height: UM.Theme.getSize("standard_arrow").height + sourceSize.width: width + sourceSize.height: height + color: UM.Theme.getColor("text") + source: base.currentSection == section ? UM.Theme.getIcon("arrow_bottom") : UM.Theme.getIcon("arrow_right") + } + + Label + { + id: label + anchors.left: arrow.right + anchors.leftMargin: UM.Theme.getSize("default_margin").width + verticalAlignment: Text.AlignVCenter + text: button.text + font: UM.Theme.getFont("default_bold") + renderType: Text.NativeRendering + } + } + + onClicked: + { + base.currentSection = section + base.updateCurrentItemUponSectionChange() + } } } - text: name - visible: base.currentSection == section - onClicked: ListView.view.currentIndex = index + + Component + { + id: machineButton + + Cura.RadioButton + { + id: radioButton + anchors.left: parent.left + anchors.leftMargin: UM.Theme.getSize("standard_list_lineheight").width + anchors.right: parent.right + anchors.rightMargin: UM.Theme.getSize("default_margin").width + height: visible ? UM.Theme.getSize("standard_list_lineheight").height : 0 + + checked: ListView.view.currentIndex == index + text: name + visible: base.currentSection == section + onClicked: ListView.view.currentIndex = index + } + } + } + } + + // Horizontal line + Rectangle + { + id: horizontalLine + anchors.top: localPrinterSelectionItem.bottom + anchors.left: parent.left + anchors.right: parent.right + height: UM.Theme.getSize("default_lining").height + color: UM.Theme.getColor("lining") + } + + // User-editable printer name row + Row + { + anchors.top: horizontalLine.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: UM.Theme.getSize("default_lining").height + anchors.leftMargin: UM.Theme.getSize("default_margin").width + + spacing: UM.Theme.getSize("default_margin").width + + Label + { + text: catalog.i18nc("@label", "Printer Name") + anchors.verticalCenter: parent.verticalCenter + font: UM.Theme.getFont("medium") + verticalAlignment: Text.AlignVCenter + renderType: Text.NativeRendering + } + + Cura.TextField + { + id: printerNameTextField + anchors.verticalCenter: parent.verticalCenter + width: (parent.width / 2) | 0 + placeholderText: catalog.i18nc("@text", "Please give your printer a name") + + // Make sure that the fill is not empty + validator: RegExpValidator { regExp: /.+/ } } } } diff --git a/resources/qml/WelcomePages/AddNetworkOrLocalPrinterContent.qml b/resources/qml/WelcomePages/AddNetworkOrLocalPrinterContent.qml index c219ba0eca..34ec3b4a24 100644 --- a/resources/qml/WelcomePages/AddNetworkOrLocalPrinterContent.qml +++ b/resources/qml/WelcomePages/AddNetworkOrLocalPrinterContent.qml @@ -118,11 +118,14 @@ Item } else { - return addLocalPrinterDropDown.contentItem.currentItem != null + // Printer name cannot be empty + const localPrinterItem = addLocalPrinterDropDown.contentItem.currentItem + const isPrinterNameValid = addLocalPrinterDropDown.contentItem.isPrinterNameValid + return localPrinterItem != null && isPrinterNameValid } } - text: catalog.i18nc("@button", "Next") + text: base.currentItem.next_page_button_text onClicked: { // Create a network printer or a local printer according to the selection @@ -139,7 +142,8 @@ Item { // Create a local printer const localPrinterItem = addLocalPrinterDropDown.contentItem.currentItem - Cura.MachineManager.addMachine(localPrinterItem.id) + const printerName = addLocalPrinterDropDown.contentItem.printerName + Cura.MachineManager.addMachine(localPrinterItem.id, printerName) base.showNextPage() } diff --git a/resources/qml/WelcomePages/DropDownWidget.qml b/resources/qml/WelcomePages/DropDownWidget.qml index bbf5344398..7251e8c520 100644 --- a/resources/qml/WelcomePages/DropDownWidget.qml +++ b/resources/qml/WelcomePages/DropDownWidget.qml @@ -60,7 +60,7 @@ Item anchors.left: header.left anchors.right: header.right // Add 2x lining, because it needs a bit of space on the top and the bottom. - height: contentLoader.height + 2 * UM.Theme.getSize("thick_lining").height + height: contentLoader.item.height + 2 * UM.Theme.getSize("thick_lining").height border.width: UM.Theme.getSize("default_lining").width border.color: UM.Theme.getColor("lining") diff --git a/resources/qml/WelcomePages/WelcomeDialog.qml b/resources/qml/WelcomePages/WelcomeDialogItem.qml similarity index 83% rename from resources/qml/WelcomePages/WelcomeDialog.qml rename to resources/qml/WelcomePages/WelcomeDialogItem.qml index c7832e1e56..7da4c6e897 100644 --- a/resources/qml/WelcomePages/WelcomeDialog.qml +++ b/resources/qml/WelcomePages/WelcomeDialogItem.qml @@ -11,7 +11,7 @@ import Cura 1.1 as Cura // -// This is a no-frame dialog that shows the welcome process. +// This is an Item that tries to mimic a dialog for showing the welcome process. // Item { @@ -26,6 +26,7 @@ Item property int shadowOffset: 1 * screenScaleFactor + property alias progressBarVisible: wizardPanel.progressBarVisible property var model: CuraApplication.getWelcomePagesModel() onVisibleChanged: @@ -38,7 +39,7 @@ Item WizardPanel { - id: stepPanel + id: wizardPanel anchors.fill: parent model: dialog.model } @@ -48,8 +49,8 @@ Item { id: shadow radius: UM.Theme.getSize("first_run_shadow_radius").width - anchors.fill: stepPanel - source: stepPanel + anchors.fill: wizardPanel + source: wizardPanel horizontalOffset: shadowOffset verticalOffset: shadowOffset color: UM.Theme.getColor("first_run_shadow") diff --git a/resources/qml/WelcomePages/WhatsNewContent.qml b/resources/qml/WelcomePages/WhatsNewContent.qml index 415acae05c..51a347779a 100644 --- a/resources/qml/WelcomePages/WhatsNewContent.qml +++ b/resources/qml/WelcomePages/WhatsNewContent.qml @@ -51,7 +51,7 @@ Item id: getStartedButton anchors.right: parent.right anchors.bottom: parent.bottom - text: catalog.i18nc("@button", "Next") + text: base.currentItem.next_page_button_text onClicked: base.showNextPage() } } diff --git a/resources/qml/WelcomePages/WizardDialog.qml b/resources/qml/WelcomePages/WizardDialog.qml new file mode 100644 index 0000000000..4a0867c9a2 --- /dev/null +++ b/resources/qml/WelcomePages/WizardDialog.qml @@ -0,0 +1,54 @@ +// Copyright (c) 2019 Ultimaker B.V. +// Cura is released under the terms of the LGPLv3 or higher. + +import QtQuick 2.10 +import QtQuick.Controls 2.3 +import QtQuick.Window 2.2 + +import UM 1.3 as UM +import Cura 1.1 as Cura + + +// +// This is a dialog for showing a set of processes that's defined in a WelcomePagesModel or some other Qt ListModel with +// a compatible interface. +// +Window +{ + UM.I18nCatalog { id: catalog; name: "cura" } + + id: dialog + + flags: Qt.Dialog + modality: Qt.ApplicationModal + + minimumWidth: 580 * screenScaleFactor + minimumHeight: 600 * screenScaleFactor + + color: UM.Theme.getColor("main_background") + + property var model: null // Needs to be set by whoever is using this dialog. + property alias progressBarVisible: wizardPanel.progressBarVisible + + onVisibilityChanged: + { + if (visible) + { + model.resetState() + } + } + + WizardPanel + { + id: wizardPanel + anchors.fill: parent + model: dialog.model + } + + // Close this dialog when there's no more page to show + Connections + { + target: model + onAllFinished: dialog.hide() + } +} diff --git a/resources/qml/WelcomePages/WizardPanel.qml b/resources/qml/WelcomePages/WizardPanel.qml index 91f351810c..d4ec116d65 100644 --- a/resources/qml/WelcomePages/WizardPanel.qml +++ b/resources/qml/WelcomePages/WizardPanel.qml @@ -25,6 +25,7 @@ Item property var progressValue: model == null ? 0 : model.currentProgress property string pageUrl: currentItem == null ? "" : currentItem.page_url + property alias progressBarVisible: progressBar.visible property alias backgroundColor: panelBackground.color signal showNextPage() @@ -44,6 +45,7 @@ Item anchors.fill: parent radius: UM.Theme.getSize("default_radius").width color: UM.Theme.getColor("main_background") + UM.ProgressBar { id: progressBar diff --git a/resources/qml/Widgets/TextField.qml b/resources/qml/Widgets/TextField.qml index 8161dfe532..c15d3d0c2d 100644 --- a/resources/qml/Widgets/TextField.qml +++ b/resources/qml/Widgets/TextField.qml @@ -17,17 +17,38 @@ TextField UM.I18nCatalog { id: catalog; name: "cura" } - property int controlWidth: UM.Theme.getSize("setting_control").width - property int controlHeight: UM.Theme.getSize("setting_control").height - hoverEnabled: true selectByMouse: true font: UM.Theme.getFont("default") renderType: Text.NativeRendering + states: [ + State + { + name: "disabled" + when: !textField.enabled + PropertyChanges { target: backgroundRectangle.border; color: UM.Theme.getColor("setting_control_disabled_border")} + PropertyChanges { target: backgroundRectangle; color: UM.Theme.getColor("setting_control_disabled")} + }, + State + { + name: "invalid" + when: !textField.acceptableInput + PropertyChanges { target: backgroundRectangle.border; color: UM.Theme.getColor("setting_validation_error")} + PropertyChanges { target: backgroundRectangle; color: UM.Theme.getColor("setting_validation_error_background")} + }, + State + { + name: "hovered" + when: textField.hovered || textField.activeFocus + PropertyChanges { target: backgroundRectangle.border; color: UM.Theme.getColor("setting_control_border_highlight") } + } + ] + background: Rectangle { - anchors.fill: parent + id: backgroundRectangle + anchors.margins: Math.round(UM.Theme.getSize("default_lining").width) radius: UM.Theme.getSize("setting_control_radius").width diff --git a/resources/qml/qmldir b/resources/qml/qmldir index b4881bde77..dcc2e410c9 100644 --- a/resources/qml/qmldir +++ b/resources/qml/qmldir @@ -20,6 +20,12 @@ CheckBoxWithTooltip 1.0 CheckBoxWithTooltip.qml ToolTip 1.0 ToolTip.qml +# Cura/WelcomePages + +WizardPanel 1.0 WizardPanel.qml +WizardDialog 1.0 WizardDialog.qml + + # Cura/Widgets CheckBox 1.0 CheckBox.qml