-1

I am trying to make a QDoubleSpinbox that allows for entering custom metric distances with units, e.g. "3.123 mm", "2.1 um", "10.567 m".

Is there any way to convince QDoubleSpinbox to make the suffix editable?

knipknap
  • 5,934
  • 7
  • 39
  • 43

1 Answers1

0

I figured it out. Can't use suffix(), but there is another way. Here you go:

import re
from PySide.QtGui import QApplication, QDoubleSpinBox, QValidator, QAbstractSpinBox

_value_split_re = re.compile(r'^([\d\.]+)\s*(\S*)$')
def _parse_value(value):
    if not isinstance(value, str):
        return value, None
    value, unit = _value_split_re.match(value).groups()
    return float(value), unit

class DistanceSpinBox(QDoubleSpinBox):
    def __init__(self, unit, allowed_units, parent=None):
        self.unit = unit
        self.allowed_units = allowed_units
        super().__init__(parent)
        self.setStepType(QAbstractSpinBox.AdaptiveDecimalStepType)
        #self.setKeyboardTracking(False)

    def textFromValue(self, value):
        decimals = self.decimals()
        fmt = f"{{:.{decimals}f}} {self.unit}"
        return fmt.format(value)

    def valueFromText(self, text):
        try:
            value, unit = _parse_value(text)
        except AttributeError:
            return .0

        if unit in self.allowed_units:
            self.unit = unit
            self.valueChanged.emit(value)

        return value

    def validate(self, text, pos):
        return QValidator.Acceptable, text, pos

if __name__ == '__main__':
    import sys
    app = QApplication(sys.argv)
    spin_box = DistanceSpinBox('mm', ('nm', 'mm', 'cm', 'in', 'ft'))
    spin_box.show()
    sys.exit(app.exec_())

Alternatively, here is a link to the full version that includes some checks.

knipknap
  • 5,934
  • 7
  • 39
  • 43
  • This doesn't seem valid, especially considering that you're trying to "validate" values by using a simple `split()`, which doesn't evaluate missing spaces, special characters, etc. While I can understand the need for a "generic" numeric field that could have variable units, proper UX implementation requires thorough thinking, and often results in choosing a completely different approach: for instance, a separate "field" that allows to select the unit. Also, links to repositories are generally discouraged, since their contents will likely change in the future, making them completely unreliable. – musicamante Aug 25 '23 at 23:58
  • Sure, the point wasn't to provide a "bells and whistles included" solution, but to show the concept. – knipknap Aug 27 '23 at 07:32
  • I actually was able to simplify it, the issue you mentioned is now fixed. As for separating the unit into a separate widget in the UI, I would really dislike this approach as a user. – knipknap Aug 27 '23 at 09:24
  • I understand that it may seem awkward, but it could still be done by properly embedding both controls by making them appearing "as one". Note that your approach still has issues, most notably due to the *always acceptable* validation (so the user could write *anything*). Also, you should not emit the `valueChanged` signal in `valueFromText()`, as it's responsibility of the spinbox to do it when it gets an appropriate (and changed) return value from `valueFromText()`: in your case, the signal will be emitted twice, which may cause issues. I understand that you didn't mean to show a fully and » – musicamante Aug 27 '23 at 15:13
  • » perfect fail-proof example, but, still, while the concept is acceptable in principle, its implementation has important issues. – musicamante Aug 27 '23 at 15:15
  • Unfortunately the valueChanged() is needed, because QSpinbox assumes nothing changed if you only changed the unit and not the value. Sure, I could make sure to emit it only if the unit changed and the value didn't. In any case, this code is working perfectly in my app, so... good enough for me! – knipknap Aug 28 '23 at 16:19