diff --git a/cura/Machines/IntentNode.py b/cura/Machines/IntentNode.py index 8ec8dd6d6b..dbf37a341a 100644 --- a/cura/Machines/IntentNode.py +++ b/cura/Machines/IntentNode.py @@ -17,4 +17,4 @@ class IntentNode(ContainerNode): def __init__(self, container_id: str, quality: "QualityNode") -> None: super().__init__(container_id) self.quality = quality - self.intent_category = ContainerRegistry.getInstance().findContainersMetadata(id = container_id)[0].get("intent_category", "default") \ No newline at end of file + self.intent_category = ContainerRegistry.getInstance().findContainersMetadata(id = container_id)[0].get("intent_category", "default") diff --git a/cura/Machines/Models/BaseMaterialsModel.py b/cura/Machines/Models/BaseMaterialsModel.py index d62b848343..66718e8e4f 100644 --- a/cura/Machines/Models/BaseMaterialsModel.py +++ b/cura/Machines/Models/BaseMaterialsModel.py @@ -104,6 +104,8 @@ class BaseMaterialsModel(ListModel): # tree. This change may trigger an _update() call when the materials # changed for the configuration that this model is looking for. def _materialsListChanged(self, material: MaterialNode) -> None: + if self._extruder_stack is None: + return if material.variant.container_id != self._extruder_stack.variant.getId(): return global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack() diff --git a/cura/Machines/Models/IntentCategoryModel.py b/cura/Machines/Models/IntentCategoryModel.py index 9a520a2d31..5211889801 100644 --- a/cura/Machines/Models/IntentCategoryModel.py +++ b/cura/Machines/Models/IntentCategoryModel.py @@ -5,9 +5,11 @@ from PyQt5.QtCore import Qt import collections from typing import TYPE_CHECKING +from cura.Machines.Models.IntentModel import IntentModel from cura.Settings.IntentManager import IntentManager from UM.Qt.ListModel import ListModel from UM.Settings.ContainerRegistry import ContainerRegistry #To update the list if anything changes. +from PyQt5.QtCore import pyqtProperty, pyqtSignal if TYPE_CHECKING: from UM.Settings.ContainerRegistry import ContainerInterface @@ -15,12 +17,14 @@ if TYPE_CHECKING: from UM.i18n import i18nCatalog catalog = i18nCatalog("cura") + ## Lists the intent categories that are available for the current printer # configuration. class IntentCategoryModel(ListModel): NameRole = Qt.UserRole + 1 IntentCategoryRole = Qt.UserRole + 2 WeightRole = Qt.UserRole + 3 + QualitiesRole = Qt.UserRole + 4 #Translations to user-visible string. Ordered by weight. #TODO: Create a solution for this name and weight to be used dynamically. @@ -29,6 +33,8 @@ class IntentCategoryModel(ListModel): name_translation["engineering"] = catalog.i18nc("@label", "Engineering") name_translation["smooth"] = catalog.i18nc("@label", "Smooth") + modelUpdated = pyqtSignal() + ## Creates a new model for a certain intent category. # \param The category to list the intent profiles for. def __init__(self, intent_category: str) -> None: @@ -38,6 +44,7 @@ class IntentCategoryModel(ListModel): self.addRoleName(self.NameRole, "name") self.addRoleName(self.IntentCategoryRole, "intent_category") self.addRoleName(self.WeightRole, "weight") + self.addRoleName(self.QualitiesRole, "qualities") ContainerRegistry.getInstance().containerAdded.connect(self._onContainerChange) ContainerRegistry.getInstance().containerRemoved.connect(self._onContainerChange) @@ -55,9 +62,13 @@ class IntentCategoryModel(ListModel): available_categories = IntentManager.getInstance().currentAvailableIntentCategories() result = [] for category in available_categories: + qualities = IntentModel() + qualities.setIntentCategory(category) result.append({ "name": self.name_translation.get(category, catalog.i18nc("@label", "Unknown")), "intent_category": category, - "weight": list(self.name_translation.keys()).index(category) + "weight": list(self.name_translation.keys()).index(category), + "qualities": qualities }) - self.setItems(result) \ No newline at end of file + result.sort(key = lambda k: k["weight"]) + self.setItems(result) diff --git a/cura/Machines/Models/IntentModel.py b/cura/Machines/Models/IntentModel.py index c61d0bfcca..61fdecb559 100644 --- a/cura/Machines/Models/IntentModel.py +++ b/cura/Machines/Models/IntentModel.py @@ -6,8 +6,11 @@ from typing import Optional, List, Dict, Any from PyQt5.QtCore import Qt, QObject, pyqtProperty, pyqtSignal from UM.Qt.ListModel import ListModel +from UM.Settings.ContainerRegistry import ContainerRegistry +from UM.Settings.SettingFunction import SettingFunction from cura.Machines.ContainerTree import ContainerTree +from cura.Settings.ExtruderManager import ExtruderManager from cura.Settings.IntentManager import IntentManager import cura.CuraApplication @@ -15,18 +18,26 @@ import cura.CuraApplication class IntentModel(ListModel): NameRole = Qt.UserRole + 1 QualityTypeRole = Qt.UserRole + 2 + LayerHeightRole = Qt.UserRole + 3 + AvailableRole = Qt.UserRole + 4 + IntentRole = Qt.UserRole + 5 def __init__(self, parent: Optional[QObject] = None) -> None: super().__init__(parent) self.addRoleName(self.NameRole, "name") self.addRoleName(self.QualityTypeRole, "quality_type") + self.addRoleName(self.LayerHeightRole, "layer_height") + self.addRoleName(self.AvailableRole, "available") + self.addRoleName(self.IntentRole, "intent_category") self._intent_category = "engineering" machine_manager = cura.CuraApplication.CuraApplication.getInstance().getMachineManager() machine_manager.globalContainerChanged.connect(self._update) - machine_manager.activeStackChanged.connect(self._update) + ContainerRegistry.getInstance().containerAdded.connect(self._onChanged) + ContainerRegistry.getInstance().containerRemoved.connect(self._onChanged) + self._layer_height_unit = "" # This is cached self._update() intentCategoryChanged = pyqtSignal() @@ -41,6 +52,10 @@ class IntentModel(ListModel): def intentCategory(self) -> str: return self._intent_category + def _onChanged(self, container): + if container.getMetaDataEntry("type") == "intent": + self._update() + def _update(self) -> None: new_items = [] # type: List[Dict[str, Any]] global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack() @@ -49,11 +64,76 @@ class IntentModel(ListModel): return quality_groups = ContainerTree.getInstance().getCurrentQualityGroups() - for intent_category, quality_type in IntentManager.getInstance().getCurrentAvailableIntents(): - if intent_category == self._intent_category: - new_items.append({"name": quality_groups[quality_type].name, "quality_type": quality_type}) - if self._intent_category == "default": #For Default we always list all quality types. We can't filter on available profiles since the empty intent is not a specific quality type. - for quality_type in quality_groups.keys(): - new_items.append({"name": quality_groups[quality_type].name, "quality_type": quality_type}) + container_tree = ContainerTree.getInstance() + machine_node = container_tree.machines[global_stack.definition.getId()] + active_extruder = ExtruderManager.getInstance().getActiveExtruderStack() + if not active_extruder: + return + active_variant_name = active_extruder.variant.getMetaDataEntry("name") + active_variant_node = machine_node.variants[active_variant_name] + active_material_node = active_variant_node.materials[active_extruder.material.getMetaDataEntry("base_file")] + layer_heights_added = [] + for quality_id, quality_node in active_material_node.qualities.items(): + quality_group = quality_groups[quality_node.quality_type] + layer_height = self._fetchLayerHeight(quality_group) + for intent_id, intent_node in quality_node.intents.items(): + if intent_node.intent_category != self._intent_category: + continue + layer_heights_added.append(layer_height) + new_items.append({"name": quality_group.name, + "quality_type": quality_group.quality_type, + "layer_height": layer_height, + "available": quality_group.is_available, + "intent_category": self._intent_category + }) + + # Now that we added all intents that we found something for, ensure that we set add ticks (and layer_heights) + # for all groups that we don't have anything for (and set it to not available) + for quality_tuple, quality_group in quality_groups.items(): + # Add the intents that are of the correct category + if quality_tuple[0] != self._intent_category: + layer_height = self._fetchLayerHeight(quality_group) + if layer_height not in layer_heights_added: + new_items.append({"name": "Unavailable", + "quality_type": "", + "layer_height": layer_height, + "intent_category": self._intent_category, + "available": False}) + layer_heights_added.append(layer_height) + + new_items = sorted(new_items, key=lambda x: x["layer_height"]) self.setItems(new_items) + + #TODO: Copied this from QualityProfilesDropdownMenuModel for the moment. This code duplication should be fixed. + def _fetchLayerHeight(self, quality_group) -> float: + global_stack = cura.CuraApplication.CuraApplication.getInstance().getMachineManager().activeMachine + if not self._layer_height_unit: + unit = global_stack.definition.getProperty("layer_height", "unit") + if not unit: + unit = "" + self._layer_height_unit = unit + + default_layer_height = global_stack.definition.getProperty("layer_height", "value") + + # Get layer_height from the quality profile for the GlobalStack + if quality_group.node_for_global is None: + return float(default_layer_height) + container = quality_group.node_for_global.getContainer() + + layer_height = default_layer_height + if container and container.hasProperty("layer_height", "value"): + layer_height = container.getProperty("layer_height", "value") + else: + # Look for layer_height in the GlobalStack from material -> definition + container = global_stack.definition + if container and container.hasProperty("layer_height", "value"): + layer_height = container.getProperty("layer_height", "value") + + if isinstance(layer_height, SettingFunction): + layer_height = layer_height(global_stack) + + return float(layer_height) + + def __repr__(self): + return str(self.items) diff --git a/cura/Machines/QualityNode.py b/cura/Machines/QualityNode.py index 451c8babfb..7980f4ed63 100644 --- a/cura/Machines/QualityNode.py +++ b/cura/Machines/QualityNode.py @@ -31,8 +31,8 @@ class QualityNode(ContainerNode): # Find all intent profiles that fit the current configuration. from cura.Machines.MachineNode import MachineNode if not isinstance(self.parent, MachineNode): # Not a global profile. - for intent in container_registry.findInstanceContainersMetadata(type = "intent", definition = self.parent.variant.machine.quality_definition, variant = self.parent.variant.variant_name, material = self.parent.base_file): + for intent in container_registry.findInstanceContainersMetadata(type = "intent", definition = self.parent.variant.machine.quality_definition, variant = self.parent.variant.variant_name, material = self.parent.base_file, quality_type = self.quality_type): self.intents[intent["id"]] = IntentNode(intent["id"], quality = self) - if not self.intents: - self.intents["empty_intent"] = IntentNode("empty_intent", quality = self) + + self.intents["empty_intent"] = IntentNode("empty_intent", quality = self) # Otherwise, there are no intents for global profiles. \ No newline at end of file diff --git a/cura/Settings/CuraStackBuilder.py b/cura/Settings/CuraStackBuilder.py index 30eb0b009c..33b0fd8d2e 100644 --- a/cura/Settings/CuraStackBuilder.py +++ b/cura/Settings/CuraStackBuilder.py @@ -160,6 +160,7 @@ class CuraStackBuilder: stack.variant = variant_container stack.material = material_container stack.quality = quality_container + stack.intent = application.empty_intent_container stack.qualityChanges = application.empty_quality_changes_container stack.userChanges = user_container @@ -208,6 +209,7 @@ class CuraStackBuilder: stack.variant = variant_container stack.material = material_container stack.quality = quality_container + stack.intent = application.empty_intent_container stack.qualityChanges = application.empty_quality_changes_container stack.userChanges = user_container diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index a7447aaa1e..79417e8ed4 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -132,6 +132,7 @@ class MachineManager(QObject): activeMaterialChanged = pyqtSignal() activeVariantChanged = pyqtSignal() activeQualityChanged = pyqtSignal() + activeIntentChanged = pyqtSignal() activeStackChanged = pyqtSignal() # Emitted whenever the active stack is changed (ie: when changing between extruders, changing a profile, but not when changing a value) extruderChanged = pyqtSignal() @@ -270,6 +271,7 @@ class MachineManager(QObject): self.activeQualityChanged.emit() self.activeVariantChanged.emit() self.activeMaterialChanged.emit() + self.activeIntentChanged.emit() self.rootMaterialChanged.emit() self.numberExtrudersEnabledChanged.emit() @@ -609,6 +611,14 @@ class MachineManager(QObject): return False return Util.parseBool(global_container_stack.quality.getMetaDataEntry("is_experimental", False)) + @pyqtProperty(str, notify=activeIntentChanged) + def activeIntentCategory(self): + + if not self._active_container_stack: + return "" + intent_category = self._active_container_stack.intent.getMetaDataEntry("intent_category") + return intent_category + ## Returns whether there is anything unsupported in the current set-up. # # The current set-up signifies the global stack and all extruder stacks, diff --git a/cura/Settings/cura_empty_instance_containers.py b/cura/Settings/cura_empty_instance_containers.py index e8a6df8ff1..0ab37c5435 100644 --- a/cura/Settings/cura_empty_instance_containers.py +++ b/cura/Settings/cura_empty_instance_containers.py @@ -47,6 +47,7 @@ EMPTY_INTENT_CONTAINER_ID = "empty_intent" empty_intent_container = copy.deepcopy(empty_container) empty_intent_container.setMetaDataEntry("id", EMPTY_INTENT_CONTAINER_ID) empty_intent_container.setMetaDataEntry("type", "intent") +empty_intent_container.setMetaDataEntry("intent_category", "default") empty_intent_container.setName(catalog.i18nc("@info:No intent profile selected", "Default")) diff --git a/resources/intent/smooth.inst.cfg b/resources/intent/smooth.inst.cfg index cfaa18c2bf..2ec33da504 100644 --- a/resources/intent/smooth.inst.cfg +++ b/resources/intent/smooth.inst.cfg @@ -4,7 +4,7 @@ name = Smooth (TEST INTENT) definition = ultimaker3 [metadata] -setting_version = 8 +setting_version = 9 type = intent intent_category = smooth quality_type = draft diff --git a/resources/intent/strong.inst.cfg b/resources/intent/strong.inst.cfg index e90b73d7d8..d0354070a0 100644 --- a/resources/intent/strong.inst.cfg +++ b/resources/intent/strong.inst.cfg @@ -4,7 +4,7 @@ name = Strong (TEST INTENT) definition = ultimaker3 [metadata] -setting_version = 8 +setting_version = 9 type = intent intent_category = engineering quality_type = draft diff --git a/resources/qml/LabelBar.qml b/resources/qml/LabelBar.qml new file mode 100644 index 0000000000..9a870811ca --- /dev/null +++ b/resources/qml/LabelBar.qml @@ -0,0 +1,65 @@ +// 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.Layouts 1.3 + +import UM 1.2 as UM + +// The labelBar shows a set of labels that are evenly spaced from oneother. +// The first item is aligned to the left, the last is aligned to the right. +// It's intended to be used together with RadioCheckBar. As such, it needs +// to know what the used itemSize is, so it can ensure the labels are aligned correctly. +Item +{ + id: base + property var model: null + property string modelKey: "" + property int itemSize: 14 + height: childrenRect.height + RowLayout + { + anchors.left: parent.left + anchors.right: parent.right + spacing: 0 + Repeater + { + id: repeater + model: base.model + + Item + { + Layout.fillWidth: true + Layout.maximumWidth: Math.round(index + 1 === repeater.count || repeater.count <= 1 ? itemSize : base.width / (repeater.count - 1)) + height: label.height + + Label + { + id: label + text: model[modelKey] + color: UM.Theme.getColor("text") + font: UM.Theme.getFont("default") + renderType: Text.NativeRendering + height: contentHeight + anchors + { + // Some magic to ensure that the items are aligned properly. + // We want the following: + // First item should be aligned to the left, no margin. + // Last item should be aligned to the right, no margin. + // The middle item(s) should be aligned to the center of the "item" it's showing (hence half the itemsize as offset). + // We want the center of the label to align with the center of the item, so we negatively offset by half the contentWidth + right: index + 1 === repeater.count ? parent.right: undefined + left: index + 1 === repeater.count || index === 0 ? undefined: parent.left + leftMargin: Math.round((itemSize - contentWidth) * 0.5) + + // For some reason, the last label in the row gets misaligned with Qt 5.10. This lines seems to + // fix it. + verticalCenter: parent.verticalCenter + } + } + } + } + } +} diff --git a/resources/qml/Menus/BuildplateMenu.qml b/resources/qml/Menus/BuildplateMenu.qml deleted file mode 100644 index b924aa0879..0000000000 --- a/resources/qml/Menus/BuildplateMenu.qml +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) 2018 Ultimaker B.V. -// Cura is released under the terms of the LGPLv3 or higher. - -import QtQuick 2.7 -import QtQuick.Controls 1.4 - -import UM 1.2 as UM -import Cura 1.0 as Cura - -Menu -{ - id: menu - title: "Build plate" - - property var buildPlateModel: CuraApplication.getBuildPlateModel() - - Instantiator - { - model: menu.buildPlateModel - - MenuItem { - text: model.name - checkable: true - checked: model.name == Cura.MachineManager.globalVariantName - exclusiveGroup: group - onTriggered: { - Cura.MachineManager.setGlobalVariant(model.container_node); - } - } - - onObjectAdded: menu.insertItem(index, object) - onObjectRemoved: menu.removeItem(object) - } - - ExclusiveGroup { id: group } -} diff --git a/resources/qml/Menus/ConfigurationMenu/ConfigurationMenu.qml b/resources/qml/Menus/ConfigurationMenu/ConfigurationMenu.qml index 77164429b3..959d498054 100644 --- a/resources/qml/Menus/ConfigurationMenu/ConfigurationMenu.qml +++ b/resources/qml/Menus/ConfigurationMenu/ConfigurationMenu.qml @@ -1,8 +1,8 @@ // Copyright (c) 2018 Ultimaker B.V. // Cura is released under the terms of the LGPLv3 or higher. -import QtQuick 2.7 -import QtQuick.Controls 2.0 +import QtQuick 2.10 +import QtQuick.Controls 2.3 import QtQuick.Controls.Styles 1.4 import UM 1.2 as UM @@ -99,12 +99,14 @@ Cura.ExpandablePopup left: extruderIcon.right leftMargin: UM.Theme.getSize("default_margin").width top: typeAndBrandNameLabel.bottom + right: parent.right + rightMargin: UM.Theme.getSize("default_margin").width } } } } - //Placeholder text if there is a configuration to select but no materials (so we can't show the materials per extruder). + // Placeholder text if there is a configuration to select but no materials (so we can't show the materials per extruder). Label { text: catalog.i18nc("@label", "Select configuration") diff --git a/resources/qml/Menus/IntentMenu.qml b/resources/qml/Menus/IntentMenu.qml deleted file mode 100644 index 8aba7cbde6..0000000000 --- a/resources/qml/Menus/IntentMenu.qml +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) 2019 Ultimaker B.V. -// Cura is released under the terms of the LGPLv3 or higher. - -import QtQuick 2.7 -import QtQuick.Controls 1.4 - -import UM 1.2 as UM -import Cura 1.6 as Cura - -Menu -{ - id: menu - title: "Intent" - - property int extruderIndex: 0 - - Cura.IntentCategoryModel - { - id: intentCategoryModel - } - - Instantiator - { - model: intentCategoryModel - - MenuItem //Section header. - { - text: model.name - enabled: false - checked: false - - property var per_category_intents: Cura.IntentModel - { - id: intentModel - intentCategory: model.intent_category - } - - property var intent_instantiator: Instantiator - { - model: intentModel - MenuItem - { - text: model.name - checkable: true - checked: false - Binding on checked - { - when: Cura.MachineManager.activeStack != null - value: Cura.MachineManager.activeStack.intent.metaData["intent_category"] == intentModel.intentCategory && Cura.MachineManager.activeStack.quality.metaData["quality_type"] == model.quality_type - } - exclusiveGroup: group - onTriggered: Cura.IntentManager.selectIntent(intentModel.intentCategory, model.quality_type) - } - - onObjectAdded: menu.insertItem(index, object) - onObjectRemoved: menu.removeItem(object) - } - } - - onObjectAdded: menu.insertItem(index, object) - onObjectRemoved: menu.removeItem(object) - } - ExclusiveGroup { id: group } -} diff --git a/resources/qml/Menus/ProfileMenu.qml b/resources/qml/Menus/ProfileMenu.qml deleted file mode 100644 index 68260f2502..0000000000 --- a/resources/qml/Menus/ProfileMenu.qml +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright (c) 2018 Ultimaker B.V. -// Cura is released under the terms of the LGPLv3 or higher. - -import QtQuick 2.7 -import QtQuick.Controls 1.4 - -import UM 1.2 as UM -import Cura 1.0 as Cura - -Menu -{ - id: menu - - Instantiator - { - model: Cura.QualityProfilesDropDownMenuModel - - MenuItem - { - text: - { - var full_text = (model.layer_height != "") ? model.name + " - " + model.layer_height + model.layer_height_unit : model.name - full_text += model.is_experimental ? " - " + catalog.i18nc("@label", "Experimental") : "" - return full_text - } - checkable: true - checked: Cura.MachineManager.activeQualityOrQualityChangesName == model.name - exclusiveGroup: group - onTriggered: Cura.MachineManager.setQualityGroup(model.quality_group) - visible: model.available - } - - onObjectAdded: menu.insertItem(index, object) - onObjectRemoved: menu.removeItem(object) - } - - MenuSeparator - { - id: customSeparator - visible: Cura.CustomQualityProfilesDropDownMenuModel.count > 0 - } - - Instantiator - { - id: customProfileInstantiator - model: Cura.CustomQualityProfilesDropDownMenuModel - - Connections - { - target: Cura.CustomQualityProfilesDropDownMenuModel - onModelReset: customSeparator.visible = Cura.CustomQualityProfilesDropDownMenuModel.count > 0 - } - - MenuItem - { - text: model.name - checkable: true - checked: Cura.MachineManager.activeQualityOrQualityChangesName == model.name - exclusiveGroup: group - onTriggered: Cura.MachineManager.setQualityChangesGroup(model.quality_changes_group) - } - - onObjectAdded: - { - customSeparator.visible = model.count > 0; - menu.insertItem(index, object); - } - onObjectRemoved: - { - customSeparator.visible = model.count > 0; - menu.removeItem(object); - } - } - - ExclusiveGroup { id: group; } - - MenuSeparator { id: profileMenuSeparator } - - MenuItem { action: Cura.Actions.addProfile } - MenuItem { action: Cura.Actions.updateProfile } - MenuItem { action: Cura.Actions.resetProfile } - MenuSeparator { } - MenuItem { action: Cura.Actions.manageProfiles } -} diff --git a/resources/qml/Menus/SettingsMenu.qml b/resources/qml/Menus/SettingsMenu.qml index 2845101dbf..17a4f6734a 100644 --- a/resources/qml/Menus/SettingsMenu.qml +++ b/resources/qml/Menus/SettingsMenu.qml @@ -57,14 +57,6 @@ Menu onObjectRemoved: base.removeItem(object) } - // TODO Only show in dev mode. Remove check when feature ready - BuildplateMenu - { - title: catalog.i18nc("@title:menu", "&Build plate") - visible: CuraSDKVersion == "dev" && Cura.MachineManager.hasVariantBuildplates - } - ProfileMenu { title: catalog.i18nc("@title:settings", "&Profile") } - MenuSeparator { } MenuItem { action: Cura.Actions.configureSettingVisibility } diff --git a/resources/qml/PrintSetupSelector/Custom/CustomPrintSetup.qml b/resources/qml/PrintSetupSelector/Custom/CustomPrintSetup.qml index e6a35455f2..682aefa821 100644 --- a/resources/qml/PrintSetupSelector/Custom/CustomPrintSetup.qml +++ b/resources/qml/PrintSetupSelector/Custom/CustomPrintSetup.qml @@ -1,12 +1,12 @@ // Copyright (c) 2018 Ultimaker B.V. // Cura is released under the terms of the LGPLv3 or higher. -import QtQuick 2.7 -import QtQuick.Controls 2.0 -import QtQuick.Controls 1.1 as OldControls +import QtQuick 2.10 +import QtQuick.Controls 2.3 +import QtQuick.Controls 1.4 as OldControls import UM 1.3 as UM -import Cura 1.0 as Cura +import Cura 1.6 as Cura Item @@ -18,18 +18,6 @@ Item property var extrudersModel: CuraApplication.getExtrudersModel() - // Profile selector row - GlobalProfileSelector - { - id: globalProfileRow - anchors - { - top: parent.top - left: parent.left - right: parent.right - margins: parent.padding - } - } Item { id: intent @@ -37,7 +25,7 @@ Item anchors { - top: globalProfileRow.bottom + top: parent.top topMargin: UM.Theme.getSize("default_margin").height left: parent.left leftMargin: parent.padding @@ -47,7 +35,7 @@ Item Label { - id: intentLabel + id: profileLabel anchors { top: parent.top @@ -55,25 +43,130 @@ Item left: parent.left right: intentSelection.left } - text: catalog.i18nc("@label", "Intent") + text: catalog.i18nc("@label", "Profile") font: UM.Theme.getFont("medium") + renderType: Text.NativeRendering color: UM.Theme.getColor("text") verticalAlignment: Text.AlignVCenter } - OldControls.ToolButton + + Button { id: intentSelection - text: Cura.MachineManager.activeStack != null ? Cura.MachineManager.activeStack.intent.name : "" - tooltip: text - height: UM.Theme.getSize("print_setup_big_item").height - width: UM.Theme.getSize("print_setup_big_item").width - anchors.right: parent.right - style: UM.Theme.styles.print_setup_header_button - activeFocusOnPress: true + onClicked: menu.opened ? menu.close() : menu.open() + text: generateActiveQualityText() - menu: Cura.IntentMenu { extruderIndex: Cura.ExtruderManager.activeExtruderIndex } + anchors.right: parent.right + width: UM.Theme.getSize("print_setup_big_item").width + height: textLabel.contentHeight + 2 * UM.Theme.getSize("narrow_margin").height + hoverEnabled: true + + baselineOffset: null // If we don't do this, there is a binding loop. WHich is a bit weird, since we override the contentItem anyway... + + contentItem: Label + { + id: textLabel + text: intentSelection.text + anchors.left: parent.left + anchors.leftMargin: UM.Theme.getSize("default_margin").width + anchors.verticalCenter: intentSelection.verticalCenter + height: contentHeight + verticalAlignment: Text.AlignVCenter + renderType: Text.NativeRendering + } + + background: Rectangle + { + id: backgroundItem + border.color: intentSelection.hovered ? UM.Theme.getColor("setting_control_border_highlight") : UM.Theme.getColor("setting_control_border") + border.width: UM.Theme.getSize("default_lining").width + radius: UM.Theme.getSize("default_radius").width + color: UM.Theme.getColor("main_background") + } + + function generateActiveQualityText() + { + + var result = "" + if(Cura.MachineManager.activeIntentCategory != "default") + { + result += Cura.MachineManager.activeIntentCategory + " - " + } + + result += Cura.MachineManager.activeQualityOrQualityChangesName + if (Cura.MachineManager.isActiveQualityExperimental) + { + result += " (Experimental)" + } + + if (Cura.MachineManager.isActiveQualitySupported) + { + if (Cura.MachineManager.activeQualityLayerHeight > 0) + { + result += " " + result += " - " + result += Cura.MachineManager.activeQualityLayerHeight + "mm" + result += "" + } + } + + return result + } + + UM.SimpleButton + { + id: customisedSettings + + visible: Cura.MachineManager.hasUserSettings + width: UM.Theme.getSize("print_setup_icon").width + height: UM.Theme.getSize("print_setup_icon").height + + anchors.verticalCenter: parent.verticalCenter + anchors.right: downArrow.left + anchors.rightMargin: UM.Theme.getSize("default_margin").width + + color: hovered ? UM.Theme.getColor("setting_control_button_hover") : UM.Theme.getColor("setting_control_button"); + iconSource: UM.Theme.getIcon("star") + + onClicked: + { + forceActiveFocus(); + Cura.Actions.manageProfiles.trigger() + } + onEntered: + { + var content = catalog.i18nc("@tooltip", "Some setting/override values are different from the values stored in the profile.\n\nClick to open the profile manager.") + base.showTooltip(intent, Qt.point(-UM.Theme.getSize("default_margin").width, 0), content) + } + onExited: base.hideTooltip() + } + UM.RecolorImage + { + id: downArrow + + + source: UM.Theme.getIcon("arrow_bottom") + width: UM.Theme.getSize("standard_arrow").width + height: UM.Theme.getSize("standard_arrow").height + + anchors + { + right: parent.right + verticalCenter: parent.verticalCenter + rightMargin: UM.Theme.getSize("default_margin").width + } + + color: UM.Theme.getColor("setting_control_button") + } } + QualitiesWithIntentMenu + { + id: menu + y: intentSelection.y + intentSelection.height + x: intentSelection.x + width: intentSelection.width + } } UM.TabRow @@ -143,7 +236,7 @@ Item { anchors { - top: tabBar.visible ? tabBar.bottom : globalProfileRow.bottom + top: tabBar.visible ? tabBar.bottom : intent.bottom topMargin: -UM.Theme.getSize("default_lining").width left: parent.left leftMargin: parent.padding diff --git a/resources/qml/PrintSetupSelector/Custom/GlobalProfileSelector.qml b/resources/qml/PrintSetupSelector/Custom/GlobalProfileSelector.qml deleted file mode 100644 index 32c07a52a6..0000000000 --- a/resources/qml/PrintSetupSelector/Custom/GlobalProfileSelector.qml +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright (c) 2018 Ultimaker B.V. -// Cura is released under the terms of the LGPLv3 or higher. - -import QtQuick 2.7 -import QtQuick.Controls 1.1 -import QtQuick.Controls.Styles 1.1 -import QtQuick.Layouts 1.2 - -import UM 1.2 as UM -import Cura 1.0 as Cura - -Item -{ - id: globalProfileRow - height: childrenRect.height - - Label - { - id: globalProfileLabel - anchors - { - top: parent.top - bottom: parent.bottom - left: parent.left - right: globalProfileSelection.left - } - text: catalog.i18nc("@label", "Profile") - font: UM.Theme.getFont("medium") - color: UM.Theme.getColor("text") - verticalAlignment: Text.AlignVCenter - } - - ToolButton - { - id: globalProfileSelection - - text: generateActiveQualityText() - width: UM.Theme.getSize("print_setup_big_item").width - height: UM.Theme.getSize("print_setup_big_item").height - anchors - { - top: parent.top - right: parent.right - } - tooltip: Cura.MachineManager.activeQualityOrQualityChangesName - style: UM.Theme.styles.print_setup_header_button - activeFocusOnPress: true - menu: Cura.ProfileMenu { } - - function generateActiveQualityText() - { - var result = Cura.MachineManager.activeQualityOrQualityChangesName - if (Cura.MachineManager.isActiveQualityExperimental) - { - result += " (Experimental)" - } - - if (Cura.MachineManager.isActiveQualitySupported) - { - if (Cura.MachineManager.activeQualityLayerHeight > 0) - { - result += " " - result += " - " - result += Cura.MachineManager.activeQualityLayerHeight + "mm" - result += "" - } - } - - return result - } - - UM.SimpleButton - { - id: customisedSettings - - visible: Cura.MachineManager.hasUserSettings - width: UM.Theme.getSize("print_setup_icon").width - height: UM.Theme.getSize("print_setup_icon").height - - anchors.verticalCenter: parent.verticalCenter - anchors.right: parent.right - anchors.rightMargin: Math.round(UM.Theme.getSize("setting_preferences_button_margin").width - UM.Theme.getSize("thick_margin").width) - - color: hovered ? UM.Theme.getColor("setting_control_button_hover") : UM.Theme.getColor("setting_control_button"); - iconSource: UM.Theme.getIcon("star") - - onClicked: - { - forceActiveFocus(); - Cura.Actions.manageProfiles.trigger() - } - onEntered: - { - var content = catalog.i18nc("@tooltip","Some setting/override values are different from the values stored in the profile.\n\nClick to open the profile manager.") - base.showTooltip(globalProfileRow, Qt.point(-UM.Theme.getSize("default_margin").width, 0), content) - } - onExited: base.hideTooltip() - } - } -} \ No newline at end of file diff --git a/resources/qml/PrintSetupSelector/Custom/MenuButton.qml b/resources/qml/PrintSetupSelector/Custom/MenuButton.qml new file mode 100644 index 0000000000..29436da9cf --- /dev/null +++ b/resources/qml/PrintSetupSelector/Custom/MenuButton.qml @@ -0,0 +1,52 @@ +// 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 UM 1.2 as UM +import Cura 1.6 as Cura + +Button +{ + // This is a work around for a qml issue. Since the default button uses a private implementation for contentItem + // (the so called IconText), which handles the mnemonic conversion (aka; ensuring that &Button) text property + // is rendered with the B underlined. Since we're also forced to mix controls 1.0 and 2.0 actions together, + // we need a special property for the text of the label if we do want it to be rendered correclty, but don't want + // another shortcut to be added (which will cause for "QQuickAction::event: Ambiguous shortcut overload: " to + // happen. + property string labelText: "" + id: button + hoverEnabled: true + + background: Rectangle + { + id: backgroundRectangle + border.width: 1 + border.color: button.checked ? UM.Theme.getColor("setting_control_border_highlight") : "transparent" + color: button.hovered ? UM.Theme.getColor("action_button_hovered") : "transparent" + radius: UM.Theme.getSize("action_button_radius").width + } + + // Workarround to ensure that the mnemonic highlighting happens correctly + function replaceText(txt) + { + var index = txt.indexOf("&") + if(index >= 0) + { + txt = txt.replace(txt.substr(index, 2), ("" + txt.substr(index + 1, 1) + "")) + } + return txt + } + + contentItem: Label + { + id: textLabel + text: button.text != "" ? replaceText(button.text) : replaceText(button.labelText) + height: contentHeight + verticalAlignment: Text.AlignVCenter + anchors.left: button.left + anchors.leftMargin: UM.Theme.getSize("wide_margin").width + renderType: Text.NativeRendering + } +} \ No newline at end of file diff --git a/resources/qml/PrintSetupSelector/Custom/QualitiesWithIntentMenu.qml b/resources/qml/PrintSetupSelector/Custom/QualitiesWithIntentMenu.qml new file mode 100644 index 0000000000..96c8431112 --- /dev/null +++ b/resources/qml/PrintSetupSelector/Custom/QualitiesWithIntentMenu.qml @@ -0,0 +1,228 @@ +// 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 UM 1.2 as UM +import Cura 1.6 as Cura + +Popup +{ + id: popup + implicitWidth: 400 + property var dataModel: Cura.IntentCategoryModel {} + + property int defaultMargin: UM.Theme.getSize("default_margin").width + property color backgroundColor: UM.Theme.getColor("main_background") + property color borderColor: UM.Theme.getColor("lining") + + topPadding: UM.Theme.getSize("narrow_margin").height + rightPadding: UM.Theme.getSize("default_lining").width + leftPadding: UM.Theme.getSize("default_lining").width + + padding: 0 + closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent + background: Cura.RoundedRectangle + { + color: backgroundColor + border.width: UM.Theme.getSize("default_lining").width + border.color: borderColor + cornerSide: Cura.RoundedRectangle.Direction.Down + } + + ButtonGroup + { + id: buttonGroup + exclusive: true + onClicked: popup.visible = false + } + + contentItem: Column + { + // This repeater adds the intent labels + Repeater + { + model: dataModel + delegate: Item + { + // We need to set it like that, otherwise we'd have to set the sub model with model: model.qualities + // Which obviously won't work due to naming conflicts. + property variant subItemModel: model.qualities + + height: childrenRect.height + anchors + { + left: parent.left + right: parent.right + } + + Label + { + id: headerLabel + text: model.name + renderType: Text.NativeRendering + height: visible ? contentHeight: 0 + enabled: false + visible: qualitiesList.visibleChildren.length > 0 + anchors.left: parent.left + anchors.leftMargin: UM.Theme.getSize("default_margin").width + } + + Column + { + id: qualitiesList + anchors.top: headerLabel.bottom + anchors.left: parent.left + anchors.right: parent.right + + // We set it by means of a binding, since then we can use the when condition, which we need to + // prevent a binding loop. + Binding + { + target: parent + property: "height" + value: parent.childrenRect.height + when: parent.visibleChildren.length > 0 + } + + // Add the qualities that belong to the intent + Repeater + { + visible: false + model: subItemModel + MenuButton + { + id: button + + onClicked: Cura.IntentManager.selectIntent(model.intent_category, model.quality_type) + + width: parent.width + checkable: true + visible: model.available + text: model.name + " - " + model.layer_height + " mm" + checked: + { + if(Cura.MachineManager.hasCustomQuality) + { + // When user created profile is active, no quality tickbox should be active. + return false + } + return Cura.MachineManager.activeQualityType == model.quality_type && Cura.MachineManager.activeIntentCategory == model.intent_category + } + ButtonGroup.group: buttonGroup + + } + } + } + } + } + + Rectangle + { + height: 1 + anchors.left: parent.left + anchors.right: parent.right + color: borderColor + } + MenuButton + { + labelText: Cura.Actions.addProfile.text + + anchors.left: parent.left + anchors.right: parent.right + + enabled: Cura.Actions.addProfile.enabled + onClicked: + { + Cura.Actions.addProfile.trigger() + popup.visible = false + } + } + MenuButton + { + labelText: Cura.Actions.updateProfile.text + anchors.left: parent.left + anchors.right: parent.right + + enabled: Cura.Actions.updateProfile.enabled + + onClicked: + { + popup.visible = false + Cura.Actions.updateProfile.trigger() + } + } + MenuButton + { + text: catalog.i18nc("@action:button", "Discard current changes") + + anchors.left: parent.left + anchors.right: parent.right + + enabled: Cura.MachineManager.hasUserSettings + + onClicked: + { + popup.visible = false + Cura.ContainerManager.clearUserContainers() + } + } + Rectangle + { + height: 1 + anchors.left: parent.left + anchors.right: parent.right + color: borderColor + } + + MenuButton + { + id: manageProfilesButton + text: Cura.Actions.manageProfiles.text + anchors + { + left: parent.left + right: parent.right + } + + height: textLabel.contentHeight + 2 * UM.Theme.getSize("narrow_margin").height + + contentItem: Item + { + width: manageProfilesButton.width + Label + { + id: textLabel + text: manageProfilesButton.text + height: contentHeight + anchors.left: parent.left + anchors.leftMargin: UM.Theme.getSize("default_margin").width + UM.Theme.getSize("narrow_margin").width + verticalAlignment: Text.AlignVCenter + renderType: Text.NativeRendering + } + Label + { + id: shortcutLabel + text: Cura.Actions.manageProfiles.shortcut + height: contentHeight + anchors.right: parent.right + anchors.rightMargin: UM.Theme.getSize("default_margin").width + verticalAlignment: Text.AlignVCenter + renderType: Text.NativeRendering + } + } + onClicked: + { + popup.visible = false + Cura.Actions.manageProfiles.trigger() + } + } + // spacer + Item + { + width: 2 + height: UM.Theme.getSize("default_radius").width + } + } +} diff --git a/resources/qml/PrintSetupSelector/PrintSetupSelectorHeader.qml b/resources/qml/PrintSetupSelector/PrintSetupSelectorHeader.qml index 96b244d803..5628867922 100644 --- a/resources/qml/PrintSetupSelector/PrintSetupSelectorHeader.qml +++ b/resources/qml/PrintSetupSelector/PrintSetupSelectorHeader.qml @@ -1,7 +1,7 @@ // Copyright (c) 2018 Ultimaker B.V. // Cura is released under the terms of the LGPLv3 or higher. -import QtQuick 2.7 +import QtQuick 2.10 import QtQuick.Controls 2.3 import QtQuick.Layouts 1.3 @@ -20,10 +20,16 @@ RowLayout { if (Cura.MachineManager.activeStack) { - var text = Cura.MachineManager.activeQualityOrQualityChangesName + var text = "" + if(Cura.MachineManager.activeIntentCategory != "default") + { + text += Cura.MachineManager.activeIntentCategory + " - " + } + + text += Cura.MachineManager.activeQualityOrQualityChangesName if (!Cura.MachineManager.hasNotSupportedQuality) { - text += " " + layerHeight.properties.value + "mm" + text += " - " + layerHeight.properties.value + "mm" text += Cura.MachineManager.isActiveQualityExperimental ? " - " + catalog.i18nc("@label", "Experimental") : "" } return text diff --git a/resources/qml/PrintSetupSelector/Recommended/RecommendedPrintSetup.qml b/resources/qml/PrintSetupSelector/Recommended/RecommendedPrintSetup.qml index 6885f8c041..a180ad6324 100644 --- a/resources/qml/PrintSetupSelector/Recommended/RecommendedPrintSetup.qml +++ b/resources/qml/PrintSetupSelector/Recommended/RecommendedPrintSetup.qml @@ -1,7 +1,7 @@ // Copyright (c) 2018 Ultimaker B.V. // Cura is released under the terms of the LGPLv3 or higher. -import QtQuick 2.7 +import QtQuick 2.10 import QtQuick.Controls 1.4 import QtQuick.Controls.Styles 1.4 @@ -27,7 +27,6 @@ Item Column { - width: parent.width - 2 * parent.padding spacing: UM.Theme.getSize("wide_margin").height anchors diff --git a/resources/qml/PrintSetupSelector/Recommended/RecommendedQualityProfileSelector.qml b/resources/qml/PrintSetupSelector/Recommended/RecommendedQualityProfileSelector.qml index 0486f5d2d7..68a3e4811d 100644 --- a/resources/qml/PrintSetupSelector/Recommended/RecommendedQualityProfileSelector.qml +++ b/resources/qml/PrintSetupSelector/Recommended/RecommendedQualityProfileSelector.qml @@ -1,17 +1,14 @@ // Copyright (c) 2018 Ultimaker B.V. // Cura is released under the terms of the LGPLv3 or higher. -import QtQuick 2.7 +import QtQuick 2.10 import QtQuick.Controls 1.4 +import QtQuick.Controls 2.3 as Controls2 import QtQuick.Controls.Styles 1.4 import UM 1.2 as UM -import Cura 1.0 as Cura +import Cura 1.6 as Cura - -// -// Quality profile -// Item { id: qualityRow @@ -20,436 +17,111 @@ Item property real labelColumnWidth: Math.round(width / 3) property real settingsColumnWidth: width - labelColumnWidth - Timer - { - id: qualitySliderChangeTimer - interval: 50 - running: false - repeat: false - onTriggered: - { - var item = Cura.QualityProfilesDropDownMenuModel.getItem(qualitySlider.value); - Cura.MachineManager.activeQualityGroup = item.quality_group; - } - } - - Component.onCompleted: qualityModel.update() - - Connections - { - target: Cura.QualityProfilesDropDownMenuModel - onItemsChanged: qualityModel.update() - } - - Connections { - target: base - onVisibleChanged: - { - // update needs to be called when the widgets are visible, otherwise the step width calculation - // will fail because the width of an invisible item is 0. - if (visible) - { - qualityModel.update(); - } - } - } - - ListModel - { - id: qualityModel - - property var totalTicks: 0 - property var availableTotalTicks: 0 - property var existingQualityProfile: 0 - - property var qualitySliderActiveIndex: 0 - property var qualitySliderStepWidth: 0 - property var qualitySliderAvailableMin: 0 - property var qualitySliderAvailableMax: 0 - property var qualitySliderMarginRight: 0 - - function update () - { - reset() - - var availableMin = -1 - var availableMax = -1 - - for (var i = 0; i < Cura.QualityProfilesDropDownMenuModel.rowCount(); i++) - { - var qualityItem = Cura.QualityProfilesDropDownMenuModel.getItem(i) - - // Add each quality item to the UI quality model - qualityModel.append(qualityItem) - - // Set selected value - if (Cura.MachineManager.activeQualityType == qualityItem.quality_type) - { - // set to -1 when switching to user created profile so all ticks are clickable - if (Cura.MachineManager.hasCustomQuality) - { - qualityModel.qualitySliderActiveIndex = -1 - } - else - { - qualityModel.qualitySliderActiveIndex = i - } - - qualityModel.existingQualityProfile = 1 - } - - // Set min available - if (qualityItem.available && availableMin == -1) - { - availableMin = i - } - - // Set max available - if (qualityItem.available) - { - availableMax = i - } - } - - // Set total available ticks for active slider part - if (availableMin != -1) - { - qualityModel.availableTotalTicks = availableMax - availableMin + 1 - } - - // Calculate slider values - calculateSliderStepWidth(qualityModel.totalTicks) - calculateSliderMargins(availableMin, availableMax, qualityModel.totalTicks) - - qualityModel.qualitySliderAvailableMin = availableMin - qualityModel.qualitySliderAvailableMax = availableMax - } - - function calculateSliderStepWidth (totalTicks) - { - // Do not use Math.round otherwise the tickmarks won't be aligned - qualityModel.qualitySliderStepWidth = totalTicks != 0 ? - ((settingsColumnWidth - UM.Theme.getSize("print_setup_slider_handle").width) / (totalTicks)) : 0 - } - - function calculateSliderMargins (availableMin, availableMax, totalTicks) - { - if (availableMin == -1 || (availableMin == 0 && availableMax == 0)) - { - // Do not use Math.round otherwise the tickmarks won't be aligned - qualityModel.qualitySliderMarginRight = settingsColumnWidth / 2 - } - else if (availableMin == availableMax) - { - // Do not use Math.round otherwise the tickmarks won't be aligned - qualityModel.qualitySliderMarginRight = (totalTicks - availableMin) * qualitySliderStepWidth - } - else - { - // Do not use Math.round otherwise the tickmarks won't be aligned - qualityModel.qualitySliderMarginRight = (totalTicks - availableMax) * qualitySliderStepWidth - } - } - - function reset () { - qualityModel.clear() - qualityModel.availableTotalTicks = 0 - qualityModel.existingQualityProfile = 0 - - // check, the ticks count cannot be less than zero - qualityModel.totalTicks = Math.max(0, Cura.QualityProfilesDropDownMenuModel.rowCount() - 1) - } - } - // Here are the elements that are shown in the left column - Item - { - id: titleRow - width: labelColumnWidth - height: childrenRect.height - Cura.IconWithText + Column + { + anchors { - id: qualityRowTitle - source: UM.Theme.getIcon("category_layer_height") - text: catalog.i18nc("@label", "Layer Height") - font: UM.Theme.getFont("medium") - anchors.left: parent.left - anchors.right: customisedSettings.left + left: parent.left + right: parent.right } - UM.SimpleButton - { - id: customisedSettings + spacing: UM.Theme.getSize("default_margin").height - visible: Cura.SimpleModeSettingsManager.isProfileCustomized || Cura.MachineManager.hasCustomQuality - height: visible ? UM.Theme.getSize("print_setup_icon").height : 0 - width: height + Controls2.ButtonGroup + { + id: activeProfileButtonGroup + exclusive: true + onClicked: Cura.IntentManager.selectIntent(button.modelData.intent_category, button.modelData.quality_type) + } + + Item + { + height: childrenRect.height anchors { + left: parent.left right: parent.right - rightMargin: UM.Theme.getSize("default_margin").width - leftMargin: UM.Theme.getSize("default_margin").width - verticalCenter: parent.verticalCenter + } + Cura.IconWithText + { + id: profileLabel + source: UM.Theme.getIcon("category_layer_height") + text: catalog.i18nc("@label", "Profiles") + font: UM.Theme.getFont("medium") + width: labelColumnWidth } - color: hovered ? UM.Theme.getColor("setting_control_button_hover") : UM.Theme.getColor("setting_control_button") - iconSource: UM.Theme.getIcon("reset") + Cura.LabelBar + { + id: labelbar + anchors + { + left: profileLabel.right + right: parent.right + } - onClicked: - { - // if the current profile is user-created, switch to a built-in quality - Cura.MachineManager.resetToUseDefaultQuality() + model: Cura.QualityProfilesDropDownMenuModel + modelKey: "layer_height" } - onEntered: - { - var tooltipContent = catalog.i18nc("@tooltip","You have modified some profile settings. If you want to change these go to custom mode.") - base.showTooltip(qualityRow, Qt.point(-UM.Theme.getSize("thick_margin").width, 0), tooltipContent) - } - onExited: base.hideTooltip() } - } - // Show titles for the each quality slider ticks - Item - { - anchors.left: speedSlider.left - anchors.top: speedSlider.bottom - height: childrenRect.height Repeater { - model: qualityModel - - Label + model: Cura.IntentCategoryModel {} + Item { - anchors.verticalCenter: parent.verticalCenter - anchors.top: parent.top - // The height has to be set manually, otherwise it's not automatically calculated in the repeater - height: UM.Theme.getSize("default_margin").height - color: (Cura.MachineManager.activeMachine != null && Cura.QualityProfilesDropDownMenuModel.getItem(index).available) ? UM.Theme.getColor("quality_slider_available") : UM.Theme.getColor("quality_slider_unavailable") - text: + anchors { - var result = "" - if(Cura.MachineManager.activeMachine != null) - { - result = Cura.QualityProfilesDropDownMenuModel.getItem(index).layer_height + left: parent.left + right: parent.right + } + height: intentCategoryLabel.height - if(result == undefined) + Label + { + id: intentCategoryLabel + text: model.name + width: labelColumnWidth - UM.Theme.getSize("section_icon").width + anchors.left: parent.left + anchors.leftMargin: UM.Theme.getSize("section_icon").width + UM.Theme.getSize("narrow_margin").width + font: UM.Theme.getFont("medium") + color: UM.Theme.getColor("text") + renderType: Text.NativeRendering + elide: Text.ElideRight + } + + Cura.RadioCheckbar + { + anchors + { + left: intentCategoryLabel.right + right: parent.right + } + dataModel: model["qualities"] + buttonGroup: activeProfileButtonGroup + + function checkedFunction(modelItem) + { + if(Cura.MachineManager.hasCustomQuality) { - result = ""; + // When user created profile is active, no quality tickbox should be active. + return false } - else + + if(modelItem === null) { - result = Number(Math.round(result + "e+2") + "e-2"); //Round to 2 decimals. Javascript makes this difficult... - if (result == undefined || result != result) //Parse failure. - { - result = ""; - } + return false } + return Cura.MachineManager.activeQualityType == modelItem.quality_type && Cura.MachineManager.activeIntentCategory == modelItem.intent_category } - return result - } - x: - { - // Make sure the text aligns correctly with each tick - if (qualityModel.totalTicks == 0) - { - // If there is only one tick, align it centrally - return Math.round(((settingsColumnWidth) - width) / 2) - } - else if (index == 0) - { - return Math.round(settingsColumnWidth / qualityModel.totalTicks) * index - } - else if (index == qualityModel.totalTicks) - { - return Math.round(settingsColumnWidth / qualityModel.totalTicks) * index - width - } - else - { - return Math.round((settingsColumnWidth / qualityModel.totalTicks) * index - (width / 2)) - } - } - font: UM.Theme.getFont("default") - } - } - } - - // Print speed slider - // Two sliders are created, one at the bottom with the unavailable qualities - // and the other at the top with the available quality profiles and so the handle to select them. - Item - { - id: speedSlider - height: childrenRect.height - - anchors - { - left: titleRow.right - right: parent.right - verticalCenter: titleRow.verticalCenter - } - - // Draw unavailable slider - Slider - { - id: unavailableSlider - - width: parent.width - height: qualitySlider.height // Same height as the slider that is on top - updateValueWhileDragging : false - tickmarksEnabled: true - - minimumValue: 0 - // maximumValue must be greater than minimumValue to be able to see the handle. While the value is strictly - // speaking not always correct, it seems to have the correct behavior (switching from 0 available to 1 available) - maximumValue: qualityModel.totalTicks - stepSize: 1 - - style: SliderStyle - { - //Draw Unvailable line - groove: Item - { - Rectangle - { - height: UM.Theme.getSize("print_setup_slider_groove").height - width: control.width - UM.Theme.getSize("print_setup_slider_handle").width - anchors.horizontalCenter: parent.horizontalCenter - anchors.verticalCenter: parent.verticalCenter - color: UM.Theme.getColor("quality_slider_unavailable") - } - } - - handle: Item {} - - tickmarks: Repeater - { - id: qualityRepeater - model: qualityModel.totalTicks > 0 ? qualityModel : 0 - - Rectangle - { - color: Cura.QualityProfilesDropDownMenuModel.getItem(index).available ? UM.Theme.getColor("quality_slider_available") : UM.Theme.getColor("quality_slider_unavailable") - implicitWidth: UM.Theme.getSize("print_setup_slider_tickmarks").width - implicitHeight: UM.Theme.getSize("print_setup_slider_tickmarks").height - anchors.verticalCenter: parent.verticalCenter - - // Do not use Math.round otherwise the tickmarks won't be aligned - x: ((UM.Theme.getSize("print_setup_slider_handle").width / 2) - (implicitWidth / 2) + (qualityModel.qualitySliderStepWidth * index)) - radius: Math.round(implicitWidth / 2) - } + isCheckedFunction: checkedFunction } } - // Create a mouse area on top of the unavailable profiles to show a specific tooltip - MouseArea - { - anchors.fill: parent - hoverEnabled: true - enabled: !Cura.MachineManager.hasCustomQuality - onEntered: - { - var tooltipContent = catalog.i18nc("@tooltip", "This quality profile is not available for your current material and nozzle configuration. Please change these to enable this quality profile.") - base.showTooltip(qualityRow, Qt.point(-UM.Theme.getSize("thick_margin").width, customisedSettings.height), tooltipContent) - } - onExited: base.hideTooltip() - } - } - - // Draw available slider - Slider - { - id: qualitySlider - - width: qualityModel.qualitySliderStepWidth * (qualityModel.availableTotalTicks - 1) + UM.Theme.getSize("print_setup_slider_handle").width - height: UM.Theme.getSize("print_setup_slider_handle").height // The handle is the widest element of the slider - enabled: qualityModel.totalTicks > 0 && !Cura.SimpleModeSettingsManager.isProfileCustomized - visible: qualityModel.availableTotalTicks > 0 - updateValueWhileDragging : false - - anchors - { - right: parent.right - rightMargin: qualityModel.qualitySliderMarginRight - } - - minimumValue: qualityModel.qualitySliderAvailableMin >= 0 ? qualityModel.qualitySliderAvailableMin : 0 - // maximumValue must be greater than minimumValue to be able to see the handle. While the value is strictly - // speaking not always correct, it seems to have the correct behavior (switching from 0 available to 1 available) - maximumValue: qualityModel.qualitySliderAvailableMax >= 1 ? qualityModel.qualitySliderAvailableMax : 1 - stepSize: 1 - - value: qualityModel.qualitySliderActiveIndex - - style: SliderStyle - { - // Draw Available line - groove: Item - { - Rectangle - { - height: UM.Theme.getSize("print_setup_slider_groove").height - width: control.width - UM.Theme.getSize("print_setup_slider_handle").width - anchors.verticalCenter: parent.verticalCenter - - // Do not use Math.round otherwise the tickmarks won't be aligned - x: UM.Theme.getSize("print_setup_slider_handle").width / 2 - color: UM.Theme.getColor("quality_slider_available") - } - } - - handle: Rectangle - { - id: qualityhandleButton - color: UM.Theme.getColor("primary") - implicitWidth: UM.Theme.getSize("print_setup_slider_handle").width - implicitHeight: implicitWidth - radius: Math.round(implicitWidth / 2) - visible: !Cura.SimpleModeSettingsManager.isProfileCustomized && !Cura.MachineManager.hasCustomQuality && qualityModel.existingQualityProfile - } - } - - onValueChanged: - { - // only change if an active machine is set and the slider is visible at all. - if (Cura.MachineManager.activeMachine != null && visible) - { - // prevent updating during view initializing. Trigger only if the value changed by user - if (qualitySlider.value != qualityModel.qualitySliderActiveIndex && qualityModel.qualitySliderActiveIndex != -1) - { - // start updating with short delay - qualitySliderChangeTimer.start() - } - } - } - - // This mouse area is only used to capture the onHover state and don't propagate it to the unavailable mouse area - MouseArea - { - anchors.fill: parent - hoverEnabled: true - acceptedButtons: Qt.NoButton - enabled: !Cura.MachineManager.hasCustomQuality - } - } - - // This mouse area will only take the mouse events and show a tooltip when the profile in use is - // a user created profile - MouseArea - { - anchors.fill: parent - hoverEnabled: true - visible: Cura.MachineManager.hasCustomQuality - - onEntered: - { - var tooltipContent = catalog.i18nc("@tooltip", "A custom profile is currently active. To enable the quality slider, choose a default quality profile in Custom tab") - base.showTooltip(qualityRow, Qt.point(-UM.Theme.getSize("thick_margin").width, customisedSettings.height), tooltipContent) - } - onExited: base.hideTooltip() } } } \ No newline at end of file diff --git a/resources/qml/RadioCheckbar.qml b/resources/qml/RadioCheckbar.qml new file mode 100644 index 0000000000..3c767a6201 --- /dev/null +++ b/resources/qml/RadioCheckbar.qml @@ -0,0 +1,153 @@ +// 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.Layouts 1.3 +import UM 1.1 as UM + +Item +{ + id: base + property ButtonGroup buttonGroup: null + + property color activeColor: UM.Theme.getColor("primary") + property color inactiveColor: UM.Theme.getColor("slider_groove") + property color defaultItemColor: UM.Theme.getColor("small_button_active") + property int checkboxSize: UM.Theme.getSize("radio_button").height * 0.75 + property int inactiveMarkerSize: 2 * barSize + property int barSize: UM.Theme.getSize("slider_groove_radius").height + property var isCheckedFunction // Function that accepts the modelItem and returns if the item should be active. + + implicitWidth: 200 + implicitHeight: checkboxSize + + property var dataModel: null + + // The horizontal inactive bar that sits behind the buttons + Rectangle + { + id: inactiveLine + color: inactiveColor + + height: barSize + + anchors + { + left: buttonBar.left + right: buttonBar.right + leftMargin: (checkboxSize - inactiveMarkerSize) / 2 + rightMargin: (checkboxSize - inactiveMarkerSize) / 2 + verticalCenter: parent.verticalCenter + } + } + + + RowLayout + { + id: buttonBar + anchors.top: parent.top + height: checkboxSize + width: parent.width + spacing: 0 + + Repeater + { + id: repeater + model: base.dataModel + height: checkboxSize + Item + { + Layout.fillWidth: true + Layout.fillHeight: true + // The last item of the repeater needs to be shorter, as we don't need another part to fit + // the horizontal bar. The others should essentially not be limited. + Layout.maximumWidth: index + 1 === repeater.count ? activeComponent.width: 200000000 + + property bool isEnabled: model.available + // The horizontal bar between the checkable options. + // Note that the horizontal bar points towards the previous item. + Rectangle + { + property Item previousItem: repeater.itemAt(index - 1) + + height: barSize + width: buttonBar.width / (repeater.count - 1) - activeComponent.width - 2 + color: defaultItemColor + + anchors + { + right: activeComponent.left + verticalCenter: parent.verticalCenter + } + visible: previousItem !== null && previousItem.isEnabled && isEnabled + } + Loader + { + id: activeComponent + sourceComponent: isEnabled? checkboxComponent : disabledComponent + width: checkboxSize + + property var modelItem: model + } + } + } + } + + Component + { + id: disabledComponent + Item + { + height: checkboxSize + width: checkboxSize + + Rectangle + { + // This can (and should) be done wiht a verticalCenter. For some reason it does work in QtCreator + // but not when using the exact same QML in Cura. + anchors.verticalCenter: parent.verticalCenter + anchors.horizontalCenter: parent.horizontalCenter + height: inactiveMarkerSize + width: inactiveMarkerSize + radius: width / 2 + color: inactiveColor + } + } + } + + Component + { + id: checkboxComponent + CheckBox + { + id: checkbox + ButtonGroup.group: buttonGroup + width: checkboxSize + height: checkboxSize + property var modelData: modelItem + + checked: isCheckedFunction(modelItem) + indicator: Rectangle + { + height: checkboxSize + width: checkboxSize + radius: width / 2 + + border.color: defaultItemColor + + Rectangle + { + anchors + { + margins: 3 + fill: parent + } + radius: width / 2 + color: activeColor + visible: checkbox.checked + } + } + } + } +} diff --git a/resources/qml/Settings/SettingComboBox.qml b/resources/qml/Settings/SettingComboBox.qml index 0b7f494a7d..cbabb3ffd4 100644 --- a/resources/qml/Settings/SettingComboBox.qml +++ b/resources/qml/Settings/SettingComboBox.qml @@ -20,7 +20,6 @@ SettingItem textRole: "value" anchors.fill: parent - highlighted: base.hovered onActivated: { diff --git a/resources/qml/Widgets/ComboBox.qml b/resources/qml/Widgets/ComboBox.qml index d1edcca69c..5a1ff16b95 100644 --- a/resources/qml/Widgets/ComboBox.qml +++ b/resources/qml/Widgets/ComboBox.qml @@ -14,40 +14,34 @@ import Cura 1.1 as Cura ComboBox { id: control - property bool highlighted: False + + states: [ + State + { + name: "disabled" + when: !control.enabled + PropertyChanges { target: backgroundRectangle.border; color: UM.Theme.getColor("setting_control_disabled_border")} + PropertyChanges { target: backgroundRectangle; color: UM.Theme.getColor("setting_control_disabled")} + PropertyChanges { target: contentLabel; color: UM.Theme.getColor("setting_control_disabled_text")} + }, + State + { + name: "highlighted" + when: control.hovered || control.activeFocus + PropertyChanges { target: backgroundRectangle.border; color: UM.Theme.getColor("setting_control_border_highlight") } + PropertyChanges { target: backgroundRectangle; color: UM.Theme.getColor("setting_control_highlight")} + } + ] + background: Rectangle { - color: - { - if (!enabled) - { - return UM.Theme.getColor("setting_control_disabled") - } - - if (control.hovered || control.activeFocus || control.highlighted) - { - return UM.Theme.getColor("setting_control_highlight") - } - - return UM.Theme.getColor("setting_control") - } + id: backgroundRectangle + color: UM.Theme.getColor("setting_control") radius: UM.Theme.getSize("setting_control_radius").width border.width: UM.Theme.getSize("default_lining").width - border.color: - { - if (!enabled) - { - return UM.Theme.getColor("setting_control_disabled_border") - } + border.color: UM.Theme.getColor("setting_control_border") - if (control.hovered || control.activeFocus || control.highlighted) - { - return UM.Theme.getColor("setting_control_border_highlight") - } - - return UM.Theme.getColor("setting_control_border") - } } indicator: UM.RecolorImage @@ -67,6 +61,7 @@ ComboBox contentItem: Label { + id: contentLabel anchors.left: parent.left anchors.leftMargin: UM.Theme.getSize("setting_unit_margin").width anchors.verticalCenter: parent.verticalCenter @@ -76,7 +71,7 @@ ComboBox textFormat: Text.PlainText renderType: Text.NativeRendering font: UM.Theme.getFont("default") - color: !enabled ? UM.Theme.getColor("setting_control_disabled_text") : UM.Theme.getColor("setting_control_text") + color: UM.Theme.getColor("setting_control_text") elide: Text.ElideRight verticalAlignment: Text.AlignVCenter } diff --git a/tests/Machines/TestQualityNode.py b/tests/Machines/TestQualityNode.py index 64685689c2..54266cb6ad 100644 --- a/tests/Machines/TestQualityNode.py +++ b/tests/Machines/TestQualityNode.py @@ -44,6 +44,7 @@ def test_qualityNode_machine_1(container_registry): with patch("UM.Settings.ContainerRegistry.ContainerRegistry.getInstance", MagicMock(return_value=container_registry)): node = QualityNode("quality_1", material_node) - assert len(node.intents) == 2 + assert len(node.intents) == 3 assert "intent_3" in node.intents - assert "intent_4" in node.intents \ No newline at end of file + assert "intent_4" in node.intents + assert "empty_intent" in node.intents \ No newline at end of file diff --git a/tests/Settings/TestCuraStackBuilder.py b/tests/Settings/TestCuraStackBuilder.py index 9f2db2cf19..7a1e05296c 100644 --- a/tests/Settings/TestCuraStackBuilder.py +++ b/tests/Settings/TestCuraStackBuilder.py @@ -28,6 +28,14 @@ def quality_container(): return container +@pytest.fixture +def intent_container(): + container = InstanceContainer(container_id="intent container") + container.setMetaDataEntry("type", "intent") + + return container + + @pytest.fixture def quality_changes_container(): container = InstanceContainer(container_id="quality changes container") @@ -44,7 +52,8 @@ def test_createMachineWithUnknownDefinition(application, container_registry): assert mocked_config_error.addFaultyContainers.called_with("NOPE") -def test_createMachine(application, container_registry, definition_container, global_variant, material_instance_container, quality_container, quality_changes_container): +def test_createMachine(application, container_registry, definition_container, global_variant, material_instance_container, + quality_container, intent_container, quality_changes_container): variant_manager = MagicMock(name = "Variant Manager") quality_manager = MagicMock(name = "Quality Manager") global_variant_node = MagicMock( name = "global variant node") @@ -61,6 +70,7 @@ def test_createMachine(application, container_registry, definition_container, gl application.getQualityManager = MagicMock(return_value = quality_manager) application.empty_material_container = material_instance_container application.empty_quality_container = quality_container + application.empty_intent_container = intent_container application.empty_quality_changes_container = quality_changes_container application.empty_variant_container = global_variant @@ -83,9 +93,11 @@ def test_createMachine(application, container_registry, definition_container, gl assert machine.variant == global_variant -def test_createExtruderStack(application, definition_container, global_variant, material_instance_container, quality_container, quality_changes_container): +def test_createExtruderStack(application, definition_container, global_variant, material_instance_container, + quality_container, intent_container, quality_changes_container): application.empty_material_container = material_instance_container application.empty_quality_container = quality_container + application.empty_intent_container = intent_container application.empty_quality_changes_container = quality_changes_container with patch("cura.CuraApplication.CuraApplication.getInstance", MagicMock(return_value=application)): extruder_stack = CuraStackBuilder.createExtruderStack("Whatever", definition_container, "meh", 0, global_variant, material_instance_container, quality_container)