pythonregexpyside2qvalidator

QValidator for ASCII characters stays invalid


I am trying to build a lineEdit that checks for non-ascii characters. I tested the RegEx using regex101 and it worked as expected.[:ascii:] seems to be a PCRE and should therefore work with the QtRegExp, right? However, my QValidator subclass always returns QValidator.State.Invalid (unless the lineEdit is empty when it returns QValidator.State.Intermediate) even when the the lineEdit clearly contains ASCII chars only.

Here is the code for the validator:

class ASCIIValidator (QtGui.QRegExpValidator):
    def __init__(self):
        super(ASCIIValidator, self).__init__()
        self.ASCII_REGEXP = QtCore.QRegExp()
        self.ASCII_REGEXP.setPattern(r'^[[:ascii:]]+$')
        self.ASCII_REGEXP.setCaseSensitivity(QtCore.Qt.CaseInsensitive)
        self.setRegExp(self.ASCII_REGEXP)

And here is my custom lineEdit:

class ULineEdit(QtWidgets.QLineEdit):
    focusChange = QtCore.Signal(bool)
    validated = QtCore.Signal(QtGui.QValidator.State)

    """Custom lineedit"""
    def __init__(self,
            defaultText: str = "",
            validators: list = [],
            completer: QtWidgets.QCompleter = None):

        super(ULineEdit, self).__init__()
        self.setText(defaultText)
        if completer is not None and isinstance(completer, QtWidgets.QCompleter):
            self.setCompleter(completer)
        self.validators = validators
        self.setSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Fixed)

        # Signals
        self.textChanged.connect(self.validateText)

    def addValidator(self, validator: QtGui.QValidator):
        if isinstance(validator, QtGui.QValidator):
            self.validators.append(validator)
            return
        elif isinstance(validator, list):
            self.validators.extend(validator)
            return

    def validateText(self):
        """Check validators and set the style"""
        if len(self.validators) > 0:
            for val in self.validators:
                testResult = val.validate(self.text(), 0)[0] #validate() returns a tuple
                invalidTests = []
                intermedTests = []
                acceptedTests = []
                print(testResult)
                if testResult == QtGui.QValidator.Invalid:
                    invalidTests.append(testResult)
                elif testResult == QtGui.QValidator.Intermediate:
                    intermedTests.append(testResult)
                elif testResult == QtGui.QValidator.Acceptable:
                    acceptedTests.append(testResult)
            if len(invalidTests) > 0:
                self.setStyleSheet(INVALID_STYLESHEET)
                self.validated.emit(QtGui.QValidator.Invalid)
                return QtGui.QValidator.Invalid
            
            if len(intermedTests) > 0: 
                self.setStyleSheet(LINEEDIT_STYLESHEET)
                self.validated.emit(QtGui.QValidator.Intermediate)
                return QtGui.QValidator.Intermediate

            if len(acceptedTests) > 0:
                self.setStyleSheet(VALID_STYLESHEET)
                self.validated.emit(QtGui.QValidator.Acceptable)
                return QtGui.QValidator.Acceptable

Solution

  • One option is to use the range of ASCII characters to validate whether or not it is as pointed out in this answer:

    import sys
    
    from PySide2 import QtCore, QtGui, QtWidgets
    
    
    class ASCIIValidator(QtGui.QRegExpValidator):
        def __init__(self, parent=None):
            super().__init__(parent)
            pattern = r"[^\x00-\x7F]+$"
            regex = QtCore.QRegExp(pattern)
            regex.setCaseSensitivity(QtCore.Qt.CaseInsensitive)
            self.setRegExp(regex)
    
    
    class JoinValidator(QtGui.QValidator):
        def __init__(self, parent=None):
            super().__init__(parent)
            self._validators = []
    
        @property
        def validators(self):
            return self._validators
    
        def add_validator(self, validator):
            self.validators.append(validator)
    
        def validate(self, _input, pos):
            texts = []
            positions = []
            final_state = QtGui.QValidator.Acceptable
            for validator in self.validators:
                state, new_input, new_pos = validator.validate(_input, pos)
                texts.append(new_input)
                positions.append(new_pos)
                if state == QtGui.QValidator.Invalid:
                    final_state = QtGui.QValidator.Invalid
                elif (
                    state == QtGui.QValidator.Intermediate
                    and final_state != QtGui.QValidator.Invalid
                ):
                    final_state = QtGui.QValidator.Intermediate
            new_pos = min(positions)
            new_input = min(texts, key=len)
            return final_state, new_input, new_pos
    
    
    class Widget(QtWidgets.QWidget):
        def __init__(self, parent=None):
            super().__init__(parent)
    
            self.lineedit = QtWidgets.QLineEdit()
    
            lay = QtWidgets.QVBoxLayout(self)
            lay.addWidget(self.lineedit)
    
            self.validator = JoinValidator()
            self.validator.add_validator(ASCIIValidator())
    
            self.lineedit.textChanged.connect(self.handle_text_changed)
    
        def handle_text_changed(self):
            state, _, _ = self.validator.validate(
                self.lineedit.text(), self.lineedit.cursorPosition()
            )
            print(state)
    
    
    if __name__ == "__main__":
        import sys
    
        app = QtWidgets.QApplication(sys.argv)
    
        w = Widget()
        w.show()
    
        sys.exit(app.exec_())