0

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

2 Answers2

0

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_())
eyllanesc
  • 235,170
  • 19
  • 170
  • 241
  • Thanks for the reply. Have you tested the code? Because when I follow your implementation, I still get the behaviour described in my original post. Always returns State.Invalid unless the lineedit is empty.The concept of a JoinValidator was a good tip though! – ChemicalMoss Aug 27 '20 at 11:09
0

Have you tried with r"[^\\x00-\\x7F]+$" instead of r"[^\x00-\x7F]+$" (with double back slash) ? It works in C++.