diff --git a/docs/widgets/qquantity.md b/docs/widgets/qquantity.md index 4ca21f81..ba709a23 100644 --- a/docs/widgets/qquantity.md +++ b/docs/widgets/qquantity.md @@ -28,6 +28,12 @@ w.show() app.exec() ``` +!!! note + + `QQuantity` currently supports simple units with exponents, e.g., `meters^2` or + `1/second`. However, compound units, e.g. `meter/second`, `Newton`, etc., + are not currently supported. + {{ show_widget(150) }} {{ show_members('superqt.QQuantity') }} diff --git a/src/superqt/spinbox/_quantity.py b/src/superqt/spinbox/_quantity.py index 94f73f80..9d4b5353 100644 --- a/src/superqt/spinbox/_quantity.py +++ b/src/superqt/spinbox/_quantity.py @@ -110,6 +110,19 @@ def unitRegistry(self) -> UnitRegistry: """Return the pint UnitRegistry used by this widget.""" return self._ureg + def _get_unit_options(self, units: Unit) -> list[Unit]: + if len(units.dimensionality) > 1: + raise NotImplementedError( + "QQuantity does not currently support quantities with non-simple units," + " e.g. `meter/second` or `Newton`." + ) + dims, exp = next(iter(units.dimensionality.items())) + + options = DEFAULT_OPTIONS.get(dims, []) + if exp != 1: + options = [f"({u})^{exp}" for u in options] + return [Unit(u) for u in options] + def _update_units_combo_choices(self): if self._value.dimensionless: with signals_blocked(self._units_combo): @@ -122,13 +135,7 @@ def _update_units_combo_choices(self): return units = self._value.units - dims, exp = next(iter(units.dimensionality.items())) - if exp != 1: - raise NotImplementedError("Inverse units not yet implemented") - options = [ - self._format_units(self._ureg.Unit(u)) - for u in DEFAULT_OPTIONS.get(dims, []) - ] + options = [self._format_units(u) for u in self._get_unit_options(units)] current = self._format_units(units) with signals_blocked(self._units_combo): self._units_combo.clear() @@ -202,7 +209,7 @@ def setMagnitude(self, magnitude: Number) -> None: """Set the magnitude of the current value.""" self.setValue(self._ureg.Quantity(magnitude, self._value.units)) - def setUnits(self, units: str | Unit | Quantity) -> None: + def setUnits(self, units: str | Unit | Quantity | None) -> None: """Set the units of the current value. If `units` is `None`, will convert to a dimensionless quantity. @@ -231,4 +238,4 @@ def unitsComboBox(self) -> QComboBox: def _format_units(self, u: Unit | str) -> str: if isinstance(u, str): return u - return f"{u:~}" if self._abbreviate_units else f"{u:}" + return f"{u:~P}" if self._abbreviate_units else f"{u:}" diff --git a/tests/test_quantity.py b/tests/test_quantity.py index ea610062..d3c6b105 100644 --- a/tests/test_quantity.py +++ b/tests/test_quantity.py @@ -1,3 +1,4 @@ +import pytest from pint import Quantity from superqt import QQuantity @@ -33,6 +34,29 @@ def test_qquantity(qtbot): assert w.magnitude() == 1 +def test_qquantity_exponents(qtbot): + w = QQuantity(1, "m^2") + qtbot.addWidget(w) + + assert w.value() == 1 * w.unitRegistry().meter ** 2 + assert w.magnitude() == 1 + assert w.units() == w.unitRegistry().meter ** 2 + assert w.text() == "1 meter ** 2" + w.setUnits("cm^2") + assert w.value() == 10000 * w.unitRegistry().centimeter ** 2 + assert w.magnitude() == 10000 + assert w.units() == w.unitRegistry().centimeter ** 2 + assert w.text() == "10000.0 centimeter ** 2" + + +def test_qquantity_non_simple_units(qtbot): + with pytest.raises(NotImplementedError): + qtbot.addWidget(QQuantity(1, "m/s")) + + with pytest.raises(NotImplementedError): + qtbot.addWidget(QQuantity(1, "N")) + + def test_change_qquantity_value(qtbot): w = QQuantity() qtbot.addWidget(w)