From 37d1c0be365c6fb5b46f8ae494f7ea2c918b999e Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Thu, 27 Oct 2016 17:18:49 +0200 Subject: [PATCH 1/3] Add validator for machine names Currently this validator can't be used in QML due to a PyQt bug. I plan to call the validator manually upon receiving a signal that the text changed in a text box. Contributes to issue CURA-2692. --- cura/CuraApplication.py | 1 + cura/Settings/MachineNameValidator.py | 33 +++++++++++++++++++++++++++ cura/Settings/__init__.py | 1 + 3 files changed, 35 insertions(+) create mode 100644 cura/Settings/MachineNameValidator.py diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 3a67b9b95c..bd54209e36 100644 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -554,6 +554,7 @@ class CuraApplication(QtApplication): qmlRegisterType(cura.Settings.UserProfilesModel, "Cura", 1, 0, "UserProfilesModel") qmlRegisterType(cura.Settings.MaterialSettingsVisibilityHandler, "Cura", 1, 0, "MaterialSettingsVisibilityHandler") qmlRegisterType(cura.Settings.QualitySettingsModel, "Cura", 1, 0, "QualitySettingsModel") + qmlRegisterType(cura.Settings.MachineNameValidator, "Cura", 1, 0, "MachineNameValidator") qmlRegisterSingletonType(cura.Settings.ContainerManager, "Cura", 1, 0, "ContainerManager", cura.Settings.ContainerManager.createContainerManager) diff --git a/cura/Settings/MachineNameValidator.py b/cura/Settings/MachineNameValidator.py new file mode 100644 index 0000000000..c4f3d87582 --- /dev/null +++ b/cura/Settings/MachineNameValidator.py @@ -0,0 +1,33 @@ +# Copyright (c) 2016 Ultimaker B.V. +# Cura is released under the terms of the AGPLv3 or higher. + +import UM.Resources +import UM.Settings.ContainerRegistry +import UM.Settings.InstanceContainer + +from PyQt5.QtGui import QValidator +import os #For statvfs. +import urllib #To escape machine names for how they're saved to file. + +## Are machine names valid? +# +# Performs checks based on the length of the name. +class MachineNameValidator(QValidator): + ## Check if a specified machine name is allowed. + # + # \param name The machine name to check. + # \param position The current position of the cursor in the text box. + # \return ``QValidator.Invalid`` if it's disallowed, or + # ``QValidator.Acceptable`` if it's allowed. + def validate(self, name, position): + #Check for file name length of the current settings container (which is the longest file we're saving with the name). + try: + filename_max_length = os.statvfs(UM.Resources.getDataStoragePath()) + except AttributeError: #Doesn't support statvfs. Probably because it's not a Unix system. + filename_max_length = 255 #Assume it's Windows on NTFS. + escaped_name = urllib.parse.quote_plus(name) + current_settings_filename = escaped_name + "_current_settings." + UM.Settings.ContainerRegistry.getMimeTypeForContainer(UM.Settings.InstanceContainer).preferredSuffix + if current_settings_filename > filename_max_length: + return QValidator.Invalid + + return QValidator.Acceptable #All checks succeeded. \ No newline at end of file diff --git a/cura/Settings/__init__.py b/cura/Settings/__init__.py index a28073fa7f..0298ac886d 100644 --- a/cura/Settings/__init__.py +++ b/cura/Settings/__init__.py @@ -8,6 +8,7 @@ from .CuraContainerRegistry import CuraContainerRegistry from .ExtruderManager import ExtruderManager from .ExtrudersModel import ExtrudersModel from .MachineManager import MachineManager +from .MachineNameValidator import MachineNameValidator from .MaterialSettingsVisibilityHandler import MaterialSettingsVisibilityHandler from .SettingOverrideDecorator import SettingOverrideDecorator from .QualitySettingsModel import QualitySettingsModel From 717248bd78317ed0e9132d0fbf730cfac455b8fc Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Thu, 27 Oct 2016 21:55:48 +0200 Subject: [PATCH 2/3] Limit number of special characters There is also a more advanced attempt that allows more, but I've commented it out because it doesn't quite work yet. Special characters now always count for 12. Contributes to issue CURA-2652. --- cura/Settings/MachineNameValidator.py | 60 +++++++++++++++++++++++---- resources/qml/AddMachineDialog.qml | 8 ++++ 2 files changed, 60 insertions(+), 8 deletions(-) diff --git a/cura/Settings/MachineNameValidator.py b/cura/Settings/MachineNameValidator.py index c4f3d87582..1ab063600a 100644 --- a/cura/Settings/MachineNameValidator.py +++ b/cura/Settings/MachineNameValidator.py @@ -1,18 +1,45 @@ # Copyright (c) 2016 Ultimaker B.V. # Cura is released under the terms of the AGPLv3 or higher. +from PyQt5.QtCore import pyqtSlot, pyqtProperty, QObject, pyqtSignal, QRegExp +from PyQt5.QtGui import QValidator +import os #For statvfs. +import urllib #To escape machine names for how they're saved to file. + import UM.Resources import UM.Settings.ContainerRegistry import UM.Settings.InstanceContainer -from PyQt5.QtGui import QValidator -import os #For statvfs. -import urllib #To escape machine names for how they're saved to file. - ## Are machine names valid? # # Performs checks based on the length of the name. -class MachineNameValidator(QValidator): +class MachineNameValidator(QObject): + def __init__(self, parent = None): + super().__init__(parent) + + #Compute the validation regex for printer names. This is limited by the maximum file name length. + try: + filename_max_length = os.statvfs(UM.Resources.getDataStoragePath()).f_namemax + except AttributeError: #Doesn't support statvfs. Probably because it's not a Unix system. + filename_max_length = 255 #Assume it's Windows on NTFS. + machine_name_max_length = filename_max_length - len("_current_settings.") - len(UM.Settings.ContainerRegistry.getMimeTypeForContainer(UM.Settings.InstanceContainer).preferredSuffix) + # Characters that urllib.parse.quote_plus escapes count for 3! So now we + # must devise a regex that allows only 3 normal characters or 1 special + # character, and that up to [machine_name_max_length / 3] times. + maximum_special_characters = int(machine_name_max_length / 12) + unescaped = r"[a-zA-Z0-9_\-\.\/]" + #single_length = r"[\u0000-\u007F]" + #double_length = r"[\u0080-\u07FF]" + #triple_length = r"[\u0800-\uFFFF]" + #quadruple_length = r"[\u10000-\u10FFFF]" + #self.machine_name_regex = r"((((" + unescaped + r"{0,3}|" + single_length + r"){0,2}" \ + # + r"|" + double_length + r"){0,2}" \ + # + r"|" + triple_length + r"){0,2}" \ + # + r"|" + quadruple_length + r"){0," + str(maximum_special_characters) + r"}" + self.machine_name_regex = r"((" + unescaped + "){0,12}|.){0," + str(maximum_special_characters) + r"}" + + validationChanged = pyqtSignal() + ## Check if a specified machine name is allowed. # # \param name The machine name to check. @@ -22,12 +49,29 @@ class MachineNameValidator(QValidator): def validate(self, name, position): #Check for file name length of the current settings container (which is the longest file we're saving with the name). try: - filename_max_length = os.statvfs(UM.Resources.getDataStoragePath()) + filename_max_length = os.statvfs(UM.Resources.getDataStoragePath()).f_namemax except AttributeError: #Doesn't support statvfs. Probably because it's not a Unix system. filename_max_length = 255 #Assume it's Windows on NTFS. escaped_name = urllib.parse.quote_plus(name) current_settings_filename = escaped_name + "_current_settings." + UM.Settings.ContainerRegistry.getMimeTypeForContainer(UM.Settings.InstanceContainer).preferredSuffix - if current_settings_filename > filename_max_length: + if len(current_settings_filename) > filename_max_length: return QValidator.Invalid - return QValidator.Acceptable #All checks succeeded. \ No newline at end of file + return QValidator.Acceptable #All checks succeeded. + + ## Updates the validation state of a machine name text field. + @pyqtSlot(str) + def updateValidation(self, new_name): + is_valid = self.validate(new_name, 0) + if is_valid == QValidator.Acceptable: + print("VALID") + self.validation_regex = "^.*$" #Matches anything. + else: + print("BROKEN!") + self.validation_regex = "a^" #Never matches (unless you manage to get "a" before the start of the string... good luck). + self.validationChanged.emit() + + @pyqtProperty("QRegExp", notify=validationChanged) + def machineNameRegex(self): + print(self.machine_name_regex) + return QRegExp(self.machine_name_regex) \ No newline at end of file diff --git a/resources/qml/AddMachineDialog.qml b/resources/qml/AddMachineDialog.qml index e37629306b..80ac1213a8 100644 --- a/resources/qml/AddMachineDialog.qml +++ b/resources/qml/AddMachineDialog.qml @@ -175,6 +175,14 @@ UM.Dialog text: getMachineName() implicitWidth: UM.Theme.getSize("standard_list_input").width maximumLength: 40 + //validator: Cura.MachineNameValidator { } //TODO: Gives a segfault in PyQt5.6. For now, we must use a signal on text changed. + validator: RegExpValidator + { + regExp: { + machineName.machine_name_validator.machineNameRegex + } + } + property var machine_name_validator: Cura.MachineNameValidator { } anchors.bottom:parent.bottom } From 36fa4afc6c7e015700cc124829219200c989ad81 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 28 Oct 2016 09:55:19 +0200 Subject: [PATCH 3/3] Remove misguided attempt at representing UTF-8 encode length It was commented out anyway. I also updated the documentation there. Contributes to issue CURA-2692. --- cura/Settings/MachineNameValidator.py | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/cura/Settings/MachineNameValidator.py b/cura/Settings/MachineNameValidator.py index 1ab063600a..d859e85343 100644 --- a/cura/Settings/MachineNameValidator.py +++ b/cura/Settings/MachineNameValidator.py @@ -23,19 +23,11 @@ class MachineNameValidator(QObject): except AttributeError: #Doesn't support statvfs. Probably because it's not a Unix system. filename_max_length = 255 #Assume it's Windows on NTFS. machine_name_max_length = filename_max_length - len("_current_settings.") - len(UM.Settings.ContainerRegistry.getMimeTypeForContainer(UM.Settings.InstanceContainer).preferredSuffix) - # Characters that urllib.parse.quote_plus escapes count for 3! So now we - # must devise a regex that allows only 3 normal characters or 1 special - # character, and that up to [machine_name_max_length / 3] times. + # Characters that urllib.parse.quote_plus escapes count for 12! So now + # we must devise a regex that allows only 12 normal characters or 1 + # special character, and that up to [machine_name_max_length / 12] times. maximum_special_characters = int(machine_name_max_length / 12) unescaped = r"[a-zA-Z0-9_\-\.\/]" - #single_length = r"[\u0000-\u007F]" - #double_length = r"[\u0080-\u07FF]" - #triple_length = r"[\u0800-\uFFFF]" - #quadruple_length = r"[\u10000-\u10FFFF]" - #self.machine_name_regex = r"((((" + unescaped + r"{0,3}|" + single_length + r"){0,2}" \ - # + r"|" + double_length + r"){0,2}" \ - # + r"|" + triple_length + r"){0,2}" \ - # + r"|" + quadruple_length + r"){0," + str(maximum_special_characters) + r"}" self.machine_name_regex = r"((" + unescaped + "){0,12}|.){0," + str(maximum_special_characters) + r"}" validationChanged = pyqtSignal()