diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 49eec1d778..29febf2610 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -117,6 +117,8 @@ from cura.UI.AddPrinterPagesModel import AddPrinterPagesModel from cura.UI.WelcomePagesModel import WelcomePagesModel from cura.UI.WhatsNewPagesModel import WhatsNewPagesModel +from cura.Utils.NetworkingUtil import NetworkingUtil + from .SingleInstance import SingleInstance from .AutoSave import AutoSave from . import PlatformPhysics @@ -1028,6 +1030,8 @@ class CuraApplication(QtApplication): qmlRegisterSingletonType(SimpleModeSettingsManager, "Cura", 1, 0, "SimpleModeSettingsManager", self.getSimpleModeSettingsManager) qmlRegisterSingletonType(MachineActionManager.MachineActionManager, "Cura", 1, 0, "MachineActionManager", self.getMachineActionManager) + qmlRegisterType(NetworkingUtil, "Cura", 1, 5, "NetworkingUtil") + qmlRegisterType(WelcomePagesModel, "Cura", 1, 0, "WelcomePagesModel") qmlRegisterType(WhatsNewPagesModel, "Cura", 1, 0, "WhatsNewPagesModel") qmlRegisterType(AddPrinterPagesModel, "Cura", 1, 0, "AddPrinterPagesModel") diff --git a/cura/Machines/Models/DiscoveredPrintersModel.py b/cura/Machines/Models/DiscoveredPrintersModel.py index e639ee2d25..a2a1fac3f7 100644 --- a/cura/Machines/Models/DiscoveredPrintersModel.py +++ b/cura/Machines/Models/DiscoveredPrintersModel.py @@ -192,7 +192,7 @@ class DiscoveredPrintersModel(QObject): def discoveredPrintersByAddress(self) -> Dict[str, DiscoveredPrinter]: return self._discovered_printer_by_ip_dict - @pyqtProperty(list, notify = discoveredPrintersChanged) + @pyqtProperty("QVariantList", notify = discoveredPrintersChanged) def discoveredPrinters(self) -> List["DiscoveredPrinter"]: item_list = list( x for x in self._discovered_printer_by_ip_dict.values() if not parseBool(x.device.getProperty("temporary"))) diff --git a/cura/Machines/Models/GlobalStacksModel.py b/cura/Machines/Models/GlobalStacksModel.py index 707d71be97..9db4ffe6db 100644 --- a/cura/Machines/Models/GlobalStacksModel.py +++ b/cura/Machines/Models/GlobalStacksModel.py @@ -5,6 +5,7 @@ from PyQt5.QtCore import Qt, QTimer from UM.Qt.ListModel import ListModel from UM.i18n import i18nCatalog +from UM.Util import parseBool from cura.PrinterOutput.PrinterOutputDevice import ConnectionType from cura.Settings.CuraContainerRegistry import CuraContainerRegistry @@ -54,7 +55,6 @@ class GlobalStacksModel(ListModel): items = [] container_stacks = CuraContainerRegistry.getInstance().findContainerStacks(type = "machine") - for container_stack in container_stacks: has_remote_connection = False @@ -62,7 +62,7 @@ class GlobalStacksModel(ListModel): has_remote_connection |= connection_type in [ConnectionType.NetworkConnection.value, ConnectionType.CloudConnection.value] - if container_stack.getMetaDataEntry("hidden", False) in ["True", True]: + if parseBool(container_stack.getMetaDataEntry("hidden", False)): continue section_name = "Network enabled printers" if has_remote_connection else "Local printers" diff --git a/cura/Settings/GlobalStack.py b/cura/Settings/GlobalStack.py index 3ec73972dd..15c95c9394 100755 --- a/cura/Settings/GlobalStack.py +++ b/cura/Settings/GlobalStack.py @@ -4,6 +4,8 @@ from collections import defaultdict import threading from typing import Any, Dict, Optional, Set, TYPE_CHECKING, List +import uuid + from PyQt5.QtCore import pyqtProperty, pyqtSlot, pyqtSignal from UM.Decorators import override @@ -34,6 +36,12 @@ class GlobalStack(CuraContainerStack): self.setMetaDataEntry("type", "machine") # For backward compatibility + # TL;DR: If Cura is looking for printers that belong to the same group, it should use "group_id". + # Each GlobalStack by default belongs to a group which is identified via "group_id". This group_id is used to + # figure out which GlobalStacks are in the printer cluster for example without knowing the implementation + # details such as the um_network_key or some other identifier that's used by the underlying device plugin. + self.setMetaDataEntry("group_id", str(uuid.uuid4())) # Assign a new GlobalStack to a unique group by default + self._extruders = {} # type: Dict[str, "ExtruderStack"] # This property is used to track which settings we are calculating the "resolve" for diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index 24d63d5164..d1f5c4c884 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -797,7 +797,6 @@ class MachineManager(QObject): self.setActiveMachine(other_machine_stacks[0]["id"]) metadata = CuraContainerRegistry.getInstance().findContainerStacksMetadata(id = machine_id)[0] - network_key = metadata.get("um_network_key", None) ExtruderManager.getInstance().removeMachineExtruders(machine_id) containers = CuraContainerRegistry.getInstance().findInstanceContainersMetadata(type = "user", machine = machine_id) for container in containers: @@ -805,8 +804,9 @@ class MachineManager(QObject): CuraContainerRegistry.getInstance().removeContainer(machine_id) # If the printer that is being removed is a network printer, the hidden printers have to be also removed - if network_key: - metadata_filter = {"um_network_key": network_key} + group_id = metadata.get("group_id", None) + if group_id: + metadata_filter = {"group_id": group_id} hidden_containers = CuraContainerRegistry.getInstance().findContainerStacks(type = "machine", **metadata_filter) if hidden_containers: # This reuses the method and remove all printers recursively @@ -1360,21 +1360,24 @@ class MachineManager(QObject): # Get the definition id corresponding to this machine name machine_definition_id = CuraContainerRegistry.getInstance().findDefinitionContainers(name = machine_name)[0].getId() # Try to find a machine with the same network key - new_machine = self.getMachine(machine_definition_id, metadata_filter = {"um_network_key": self.activeMachineNetworkKey()}) + metadata_filter = {"group_id": self._global_container_stack.getMetaDataEntry("group_id"), + "um_network_key": self.activeMachineNetworkKey(), + } + new_machine = self.getMachine(machine_definition_id, metadata_filter = metadata_filter) # If there is no machine, then create a new one and set it to the non-hidden instance if not new_machine: new_machine = CuraStackBuilder.createMachine(machine_definition_id + "_sync", machine_definition_id) if not new_machine: return + new_machine.setMetaDataEntry("group_id", self._global_container_stack.getMetaDataEntry("group_id")) new_machine.setMetaDataEntry("um_network_key", self.activeMachineNetworkKey()) new_machine.setMetaDataEntry("group_name", self.activeMachineNetworkGroupName) - new_machine.setMetaDataEntry("hidden", False) new_machine.setMetaDataEntry("connection_type", self._global_container_stack.getMetaDataEntry("connection_type")) else: Logger.log("i", "Found a %s with the key %s. Let's use it!", machine_name, self.activeMachineNetworkKey()) - new_machine.setMetaDataEntry("hidden", False) # Set the current printer instance to hidden (the metadata entry must exist) + new_machine.setMetaDataEntry("hidden", False) self._global_container_stack.setMetaDataEntry("hidden", True) self.setActiveMachine(new_machine.getId()) @@ -1654,3 +1657,7 @@ class MachineManager(QObject): if results: machine_type_name = results[0]["name"] return machine_type_name + + # Gets all machines that belong to the given group_id. + def getMachinesInGroup(self, group_id: str) -> List["GlobalStack"]: + return self._container_registry.findContainerStacks(type = "machine", group_id = group_id) diff --git a/cura/UI/AddPrinterPagesModel.py b/cura/UI/AddPrinterPagesModel.py index 55bb1500ba..d40da59b2a 100644 --- a/cura/UI/AddPrinterPagesModel.py +++ b/cura/UI/AddPrinterPagesModel.py @@ -15,6 +15,7 @@ class AddPrinterPagesModel(WelcomePagesModel): "page_url": self._getBuiltinWelcomePagePath("AddNetworkOrLocalPrinterContent.qml"), "next_page_id": "machine_actions", "next_page_button_text": self._catalog.i18nc("@action:button", "Add"), + "previous_page_button_text": self._catalog.i18nc("@action:button", "Cancel"), }) self._pages.append({"id": "add_printer_by_ip", "page_url": self._getBuiltinWelcomePagePath("AddPrinterByIpContent.qml"), diff --git a/cura/UI/WelcomePagesModel.py b/cura/UI/WelcomePagesModel.py index 10ae9dabf5..c16ec3763e 100644 --- a/cura/UI/WelcomePagesModel.py +++ b/cura/UI/WelcomePagesModel.py @@ -39,6 +39,7 @@ class WelcomePagesModel(ListModel): 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 + PreviousPageButtonTextRole = Qt.UserRole + 5 # The text for the previous page button def __init__(self, application: "CuraApplication", parent: Optional["QObject"] = None) -> None: super().__init__(parent) @@ -47,6 +48,7 @@ class WelcomePagesModel(ListModel): self.addRoleName(self.PageUrlRole, "page_url") self.addRoleName(self.NextPageIdRole, "next_page_id") self.addRoleName(self.NextPageButtonTextRole, "next_page_button_text") + self.addRoleName(self.PreviousPageButtonTextRole, "previous_page_button_text") self._application = application self._catalog = i18nCatalog("cura") diff --git a/cura/Utils/NetworkingUtil.py b/cura/Utils/NetworkingUtil.py new file mode 100644 index 0000000000..b13f7903b9 --- /dev/null +++ b/cura/Utils/NetworkingUtil.py @@ -0,0 +1,44 @@ +# Copyright (c) 2019 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + +import socket +from typing import Optional + +from PyQt5.QtCore import QObject, pyqtSlot + + +# +# This is a QObject because some of the functions can be used (and are useful) in QML. +# +class NetworkingUtil(QObject): + + def __init__(self, parent: Optional["QObject"] = None) -> None: + super().__init__(parent = parent) + + # Checks if the given string is a valid IPv4 address. + @pyqtSlot(str, result = bool) + def isIPv4(self, address: str) -> bool: + try: + socket.inet_pton(socket.AF_INET, address) + result = True + except: + result = False + return result + + # Checks if the given string is a valid IPv6 address. + @pyqtSlot(str, result = bool) + def isIPv6(self, address: str) -> bool: + try: + socket.inet_pton(socket.AF_INET6, address) + result = True + except: + result = False + return result + + # Checks if the given string is a valid IPv4 or IPv6 address. + @pyqtSlot(str, result = bool) + def isValidIP(self, address: str) -> bool: + return self.isIPv4(address) or self.isIPv6(address) + + +__all__ = ["NetworkingUtil"] diff --git a/plugins/SliceInfoPlugin/MoreInfoWindow.qml b/plugins/SliceInfoPlugin/MoreInfoWindow.qml index f19e2cce19..50276ec25c 100644 --- a/plugins/SliceInfoPlugin/MoreInfoWindow.qml +++ b/plugins/SliceInfoPlugin/MoreInfoWindow.qml @@ -71,7 +71,7 @@ Window left: parent.left right: parent.right } - text: catalog.i18nc("@text:window", "Ultimaker Cura collects anonymous data in order to improve the print quality and user experience. Below is an example of all the data that is sent:") + text: catalog.i18nc("@text:window", "Ultimaker Cura collects anonymous data in order to improve the print quality and user experience. Below is an example of all the data that is shared:") wrapMode: Text.WordWrap renderType: Text.NativeRendering } @@ -106,7 +106,7 @@ Window Cura.RadioButton { id: dontSendButton - text: catalog.i18nc("@text:window", "I don't want to send this data") + text: catalog.i18nc("@text:window", "I don't want to send anonymous data") onClicked: { baseDialog.allowSendData = !checked @@ -115,7 +115,7 @@ Window Cura.RadioButton { id: allowSendButton - text: catalog.i18nc("@text:window", "Allow sending this data to Ultimaker and help us improve Cura") + text: catalog.i18nc("@text:window", "Allow sending anonymous data") onClicked: { baseDialog.allowSendData = checked diff --git a/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml b/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml index 2788a35173..ecec87ef02 100644 --- a/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml +++ b/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml @@ -1,8 +1,8 @@ -// Copyright (c) 2018 Ultimaker B.V. +// Copyright (c) 2019 Ultimaker B.V. // Cura is released under the terms of the LGPLv3 or higher. import UM 1.2 as UM -import Cura 1.0 as Cura +import Cura 1.5 as Cura import QtQuick 2.2 import QtQuick.Controls 1.1 @@ -14,9 +14,13 @@ Cura.MachineAction { id: base anchors.fill: parent; + property alias currentItemIndex: listview.currentIndex property var selectedDevice: null property bool completeProperties: true + // For validating IP addresses + property var networkingUtil: Cura.NetworkingUtil {} + function connectToPrinter() { if(base.selectedDevice && base.completeProperties) @@ -342,6 +346,17 @@ Cura.MachineAction } } + MessageDialog + { + id: invalidIPAddressMessageDialog + x: (parent.x + (parent.width) / 2) | 0 + y: (parent.y + (parent.height) / 2) | 0 + title: catalog.i18nc("@title:window", "Invalid IP address") + text: catalog.i18nc("@text", "Please enter a valid IP address.") + icon: StandardIcon.Warning + standardButtons: StandardButton.Ok + } + UM.Dialog { id: manualPrinterDialog @@ -404,6 +419,26 @@ Cura.MachineAction text: catalog.i18nc("@action:button", "OK") onClicked: { + // Validate the input first + if (!networkingUtil.isValidIP(manualPrinterDialog.addressText)) + { + invalidIPAddressMessageDialog.open() + return + } + + // if the entered IP address has already been discovered, switch the current item to that item + // and do nothing else. + for (var i = 0; i < manager.foundDevices.length; i++) + { + var device = manager.foundDevices[i] + if (device.address == manualPrinterDialog.addressText) + { + currentItemIndex = i + manualPrinterDialog.hide() + return + } + } + manager.setManualDevice(manualPrinterDialog.printerKey, manualPrinterDialog.addressText) manualPrinterDialog.hide() } diff --git a/plugins/UM3NetworkPrinting/src/DiscoverUM3Action.py b/plugins/UM3NetworkPrinting/src/DiscoverUM3Action.py index 28e7b60a0e..b67f4d7185 100644 --- a/plugins/UM3NetworkPrinting/src/DiscoverUM3Action.py +++ b/plugins/UM3NetworkPrinting/src/DiscoverUM3Action.py @@ -110,14 +110,12 @@ class DiscoverUM3Action(MachineAction): Logger.log("d", "Attempting to set the group name of the active machine to %s", group_name) global_container_stack = CuraApplication.getInstance().getGlobalContainerStack() if global_container_stack: - meta_data = global_container_stack.getMetaData() - if "group_name" in meta_data: - previous_connect_group_name = meta_data["group_name"] - global_container_stack.setMetaDataEntry("group_name", group_name) - # Find all the places where there is the same group name and change it accordingly - self._replaceContainersMetadata(key = "group_name", value = previous_connect_group_name, new_value = group_name) - else: - global_container_stack.setMetaDataEntry("group_name", group_name) + # Update a GlobalStacks in the same group with the new group name. + group_id = global_container_stack.getMetaDataEntry("group_id") + machine_manager = CuraApplication.getInstance().getMachineManager() + for machine in machine_manager.getMachinesInGroup(group_id): + machine.setMetaDataEntry("group_name", group_name) + # Set the default value for "hidden", which is used when you have a group with multiple types of printers global_container_stack.setMetaDataEntry("hidden", False) @@ -125,13 +123,6 @@ class DiscoverUM3Action(MachineAction): # Ensure that the connection states are refreshed. self._network_plugin.refreshConnections() - ## Find all container stacks that has the pair 'key = value' in its metadata and replaces the value with 'new_value' - def _replaceContainersMetadata(self, key: str, value: str, new_value: str) -> None: - machines = CuraContainerRegistry.getInstance().findContainerStacks(type="machine") - for machine in machines: - if machine.getMetaDataEntry(key) == value: - machine.setMetaDataEntry(key, new_value) - # Associates the currently active machine with the given printer device. The network connection information will be # stored into the metadata of the currently active machine. @pyqtSlot(QObject) diff --git a/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py b/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py index f820b6244c..41c76dc4c0 100644 --- a/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py +++ b/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py @@ -283,34 +283,24 @@ class UM3OutputDevicePlugin(OutputDevicePlugin): Logger.log("d", "Attempting to set the network key of the active machine to %s", printer_device.key) - global_container_stack = CuraApplication.getInstance().getGlobalContainerStack() + machine_manager = CuraApplication.getInstance().getMachineManager() + global_container_stack = machine_manager.activeMachine if not global_container_stack: return - meta_data = global_container_stack.getMetaData() + for machine in machine_manager.getMachinesInGroup(global_container_stack.getMetaDataEntry("group_id")): + machine.setMetaDataEntry("um_network_key", printer_device.key) + machine.setMetaDataEntry("group_name", printer_device.name) - if "um_network_key" in meta_data: # Global stack already had a connection, but it's changed. - old_network_key = meta_data["um_network_key"] - # Since we might have a bunch of hidden stacks, we also need to change it there. - metadata_filter = {"um_network_key": old_network_key} - containers = self._application.getContainerRegistry().findContainerStacks(type = "machine", **metadata_filter) + # Delete old authentication data. + Logger.log("d", "Removing old authentication id %s for device %s", + global_container_stack.getMetaDataEntry("network_authentication_id", None), printer_device.key) - for container in containers: - container.setMetaDataEntry("um_network_key", printer_device.key) + machine.removeMetaDataEntry("network_authentication_id") + machine.removeMetaDataEntry("network_authentication_key") - # Delete old authentication data. - Logger.log("d", "Removing old authentication id %s for device %s", - global_container_stack.getMetaDataEntry("network_authentication_id", None), printer_device.key) - - container.removeMetaDataEntry("network_authentication_id") - container.removeMetaDataEntry("network_authentication_key") - - # Ensure that these containers do know that they are configured for network connection - container.addConfiguredConnectionType(printer_device.connectionType.value) - - else: # Global stack didn't have a connection yet, configure it. - global_container_stack.setMetaDataEntry("um_network_key", printer_device.key) - global_container_stack.addConfiguredConnectionType(printer_device.connectionType.value) + # Ensure that these containers do know that they are configured for network connection + machine.addConfiguredConnectionType(printer_device.connectionType.value) self.refreshConnections() diff --git a/plugins/VersionUpgrade/VersionUpgrade40to41/VersionUpgrade40to41.py b/plugins/VersionUpgrade/VersionUpgrade40to41/VersionUpgrade40to41.py index 03272e63d5..845e9cbb8c 100644 --- a/plugins/VersionUpgrade/VersionUpgrade40to41/VersionUpgrade40to41.py +++ b/plugins/VersionUpgrade/VersionUpgrade40to41/VersionUpgrade40to41.py @@ -3,6 +3,7 @@ import configparser import io +import uuid from typing import Dict, List, Tuple from UM.VersionUpgrade import VersionUpgrade @@ -18,6 +19,7 @@ _renamed_quality_profiles = { "gmax15plus_pla_very_thick": "gmax15plus_global_very_thick" } # type: Dict[str, str] + ## Upgrades configurations from the state they were in at version 4.0 to the # state they should be in at version 4.1. class VersionUpgrade40to41(VersionUpgrade): @@ -95,6 +97,13 @@ class VersionUpgrade40to41(VersionUpgrade): if parser["containers"]["4"] in _renamed_quality_profiles: parser["containers"]["4"] = _renamed_quality_profiles[parser["containers"]["4"]] + # Assign a GlobalStack to a unique group_id. If the GlobalStack has a UM network connection, use the UM network + # key as the group_id. + if "um_network_key" in parser["metadata"]: + parser["metadata"]["group_id"] = parser["metadata"]["um_network_key"] + elif "group_id" not in parser["metadata"]: + parser["metadata"]["group_id"] = str(uuid.uuid4()) + result = io.StringIO() parser.write(result) return [filename], [result.getvalue()] diff --git a/resources/qml/Cura.qml b/resources/qml/Cura.qml index b6d62ec3f0..ab02887774 100644 --- a/resources/qml/Cura.qml +++ b/resources/qml/Cura.qml @@ -795,7 +795,6 @@ UM.MainWindow title: catalog.i18nc("@title:window", "Add Printer") model: CuraApplication.getAddPrinterPagesModel() progressBarVisible: false - hasCancelButton: true } Cura.WizardDialog @@ -804,7 +803,6 @@ UM.MainWindow title: catalog.i18nc("@title:window", "What's New") model: CuraApplication.getWhatsNewPagesModel() progressBarVisible: false - hasCancelButton: false } Connections diff --git a/resources/qml/PrinterSelector/MachineSelectorList.qml b/resources/qml/PrinterSelector/MachineSelectorList.qml index 1043339ae6..9c52c15580 100644 --- a/resources/qml/PrinterSelector/MachineSelectorList.qml +++ b/resources/qml/PrinterSelector/MachineSelectorList.qml @@ -32,16 +32,7 @@ ListView width: listView.width outputDevice: Cura.MachineManager.printerOutputDevices.length >= 1 ? Cura.MachineManager.printerOutputDevices[0] : null - checked: - { - // If the machine has a remote connection - var result = Cura.MachineManager.activeMachineId == model.id - if (Cura.MachineManager.activeMachineHasRemoteConnection) - { - result |= Cura.MachineManager.activeMachineNetworkGroupName == model.metadata["group_name"] - } - return result - } + checked: Cura.MachineManager.activeMachineId == model.id onClicked: { diff --git a/resources/qml/WelcomePages/AddNetworkOrLocalPrinterContent.qml b/resources/qml/WelcomePages/AddNetworkOrLocalPrinterContent.qml index 73ba179fef..81dd345f3f 100644 --- a/resources/qml/WelcomePages/AddNetworkOrLocalPrinterContent.qml +++ b/resources/qml/WelcomePages/AddNetworkOrLocalPrinterContent.qml @@ -98,6 +98,20 @@ Item } } + // This "Back" button only shows in the "Add Machine" dialog, which has "previous_page_button_text" set to "Cancel" + Cura.SecondaryButton + { + id: backButton + anchors.left: parent.left + anchors.bottom: parent.bottom + visible: base.currentItem.previous_page_button_text ? true : false + text: base.currentItem.previous_page_button_text ? base.currentItem.previous_page_button_text : "" + onClicked: + { + base.endWizard() + } + } + Cura.PrimaryButton { id: nextButton diff --git a/resources/qml/WelcomePages/AddNetworkPrinterScrollView.qml b/resources/qml/WelcomePages/AddNetworkPrinterScrollView.qml index 5f3ceac116..6d8f75c3f5 100644 --- a/resources/qml/WelcomePages/AddNetworkPrinterScrollView.qml +++ b/resources/qml/WelcomePages/AddNetworkPrinterScrollView.qml @@ -76,15 +76,28 @@ Item Component.onCompleted: { - // Select the first one that's not "unknown" by default. + var toSelectIndex = -1 + // Select the first one that's not "unknown" and is the host a group by default. for (var i = 0; i < count; i++) { - if (!model[i].isUnknownMachineType) + if (!model[i].isUnknownMachineType && model[i].isHostOfGroup) { - currentIndex = i + toSelectIndex = i break } } + currentIndex = toSelectIndex + } + + // CURA-6483 For some reason currentIndex can be reset to 0. This check is here to prevent automatically + // selecting an unknown or non-host printer. + onCurrentIndexChanged: + { + var item = model[currentIndex] + if (!item || item.isUnknownMachineType || !item.isHostOfGroup) + { + currentIndex = -1 + } } Component diff --git a/resources/qml/WelcomePages/AddPrinterByIpContent.qml b/resources/qml/WelcomePages/AddPrinterByIpContent.qml index de9562908c..4aec5879c1 100644 --- a/resources/qml/WelcomePages/AddPrinterByIpContent.qml +++ b/resources/qml/WelcomePages/AddPrinterByIpContent.qml @@ -6,7 +6,7 @@ import QtQuick.Controls 2.3 import QtQuick.Layouts 1.3 import UM 1.3 as UM -import Cura 1.1 as Cura +import Cura 1.5 as Cura // @@ -22,9 +22,35 @@ Item property bool hasRequestInProgress: CuraApplication.getDiscoveredPrintersModel().hasManualDeviceRequestInProgress // Indicates if a request has finished. property bool hasRequestFinished: false + property string currentRequestAddress: "" property var discoveredPrinter: null - property var isPrinterDiscovered: discoveredPrinter != null + property bool isPrinterDiscovered: discoveredPrinter != null + // A printer can only be added if it doesn't have an unknown type and it's the host of a group. + property bool canAddPrinter: isPrinterDiscovered && !discoveredPrinter.isUnknownMachineType && discoveredPrinter.isHostOfGroup + + // For validating IP address + property var networkingUtil: Cura.NetworkingUtil {} + + // CURA-6483 + // For a manually added UM printer, the UM3OutputDevicePlugin will first create a LegacyUM device for it. Later, + // when it gets more info from the printer, it will first REMOVE the LegacyUM device and then add a ClusterUM device. + // The Add-by-IP page needs to make sure that the user do not add an unknown printer or a printer that's not the + // host of a group. Because of the device list change, this page needs to react upon DiscoveredPrintersChanged so + // it has the correct information. + Connections + { + target: CuraApplication.getDiscoveredPrintersModel() + onDiscoveredPrintersChanged: + { + if (hasRequestFinished && currentRequestAddress) + { + var printer = CuraApplication.getDiscoveredPrintersModel().discoveredPrintersByAddress[currentRequestAddress] + printer = printer ? printer : null + discoveredPrinter = printer + } + } + } // Make sure to cancel the current request when this page closes. onVisibleChanged: @@ -93,17 +119,36 @@ Item anchors.verticalCenter: addPrinterButton.verticalCenter anchors.left: parent.left + signal invalidInputDetected() + + onInvalidInputDetected: invalidInputLabel.visible = true + validator: RegExpValidator { - regExp: /((^\s*((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))\s*$)|(^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$))?/ + regExp: /([a-fA-F0-9.:]+)?/ } + onTextEdited: invalidInputLabel.visible = false + placeholderText: catalog.i18nc("@text", "Place enter your printer's IP address.") enabled: { ! (addPrinterByIpScreen.hasRequestInProgress || addPrinterByIpScreen.isPrinterDiscovered) } onAccepted: addPrinterButton.clicked() } + Label + { + id: invalidInputLabel + anchors.top: hostnameField.bottom + anchors.topMargin: UM.Theme.getSize("default_margin").height + anchors.left: parent.left + visible: false + text: catalog.i18nc("@text", "Please enter a valid IP address.") + font: UM.Theme.getFont("default") + color: UM.Theme.getColor("text") + renderType: Text.NativeRendering + } + Cura.SecondaryButton { id: addPrinterButton @@ -115,14 +160,21 @@ Item onClicked: { const address = hostnameField.text + if (!networkingUtil.isValidIP(address)) + { + hostnameField.invalidInputDetected() + return + } // This address is already in the discovered printer model, no need to add a manual discovery. if (CuraApplication.getDiscoveredPrintersModel().discoveredPrintersByAddress[address]) { addPrinterByIpScreen.discoveredPrinter = CuraApplication.getDiscoveredPrintersModel().discoveredPrintersByAddress[address] + addPrinterByIpScreen.hasRequestFinished = true return } + addPrinterByIpScreen.currentRequestAddress = address CuraApplication.getDiscoveredPrintersModel().checkManualDevice(address) } busy: addPrinterByIpScreen.hasRequestInProgress @@ -161,6 +213,8 @@ Item Item { id: printerInfoLabels + anchors.left: parent.left + anchors.right: parent.right anchors.top: parent.top anchors.margins: UM.Theme.getSize("default_margin").width @@ -177,10 +231,24 @@ Item text: !addPrinterByIpScreen.isPrinterDiscovered ? "???" : addPrinterByIpScreen.discoveredPrinter.name } + Label + { + id: printerCannotBeAddedLabel + width: parent.width + anchors.top: printerNameLabel.bottom + anchors.topMargin: UM.Theme.getSize("default_margin").height + text: catalog.i18nc("@label", "This printer cannot be added because it's an unknown printer or it's not the host of a group.") + visible: addPrinterByIpScreen.hasRequestFinished && !addPrinterByIpScreen.canAddPrinter + font: UM.Theme.getFont("default_bold") + color: UM.Theme.getColor("text") + renderType: Text.NativeRendering + wrapMode: Text.WordWrap + } + GridLayout { id: printerInfoGrid - anchors.top: printerNameLabel.bottom + anchors.top: printerCannotBeAddedLabel ? printerCannotBeAddedLabel.bottom : printerNameLabel.bottom anchors.margins: UM.Theme.getSize("default_margin").width columns: 2 columnSpacing: UM.Theme.getSize("default_margin").width @@ -253,7 +321,7 @@ Item } } - Cura.PrimaryButton + Cura.SecondaryButton { id: backButton anchors.left: parent.left @@ -278,6 +346,6 @@ Item base.showNextPage() } - enabled: addPrinterByIpScreen.isPrinterDiscovered + enabled: addPrinterByIpScreen.canAddPrinter } } diff --git a/resources/qml/WelcomePages/CloudContent.qml b/resources/qml/WelcomePages/CloudContent.qml index b0b4d53cf1..e9b6df94e0 100644 --- a/resources/qml/WelcomePages/CloudContent.qml +++ b/resources/qml/WelcomePages/CloudContent.qml @@ -35,7 +35,7 @@ Item horizontalAlignment: Text.AlignHCenter text: catalog.i18nc("@label", "Ultimaker Cloud") color: UM.Theme.getColor("primary_button") - font: UM.Theme.getFont("large_bold") + font: UM.Theme.getFont("huge") renderType: Text.NativeRendering } diff --git a/resources/qml/WelcomePages/WizardDialog.qml b/resources/qml/WelcomePages/WizardDialog.qml index ce16ff1f65..c81f9daff0 100644 --- a/resources/qml/WelcomePages/WizardDialog.qml +++ b/resources/qml/WelcomePages/WizardDialog.qml @@ -31,7 +31,6 @@ Window property var model: null // Needs to be set by whoever is using this dialog. property alias progressBarVisible: wizardPanel.progressBarVisible - property alias hasCancelButton: cancelButton.visible onVisibilityChanged: { @@ -54,21 +53,4 @@ Window target: model onAllFinished: dialog.hide() } - - Cura.SecondaryButton - { - id: cancelButton - - text: catalog.i18nc("@button", "Cancel") - - visible: false - - anchors.left: parent.left - anchors.bottom: parent.bottom - anchors.margins: UM.Theme.getSize("default_margin").width - anchors.leftMargin: UM.Theme.getSize("wide_margin").width - - enabled: true - onClicked: dialog.visible = false - } }