Merge pull request #191 from nyanpasu64/gui-unit-suffix

Add unit suffixes to GUI spinboxes
pull/357/head
nyanpasu64 2019-02-10 23:34:37 -08:00 zatwierdzone przez GitHub
commit 3ccbcc6734
7 zmienionych plików z 117 dodań i 39 usunięć

Wyświetl plik

@ -24,6 +24,8 @@ __all__ = [
"copy_config",
"DumpableAttrs",
"KeywordAttrs",
"with_units",
"get_units",
"Alias",
"Ignored",
"DumpEnumAsStr",
@ -222,6 +224,18 @@ class KeywordAttrs(DumpableAttrs):
super().__init_subclass__(kw_only=True, **kwargs)
UNIT_SUFFIX = "suffix"
def with_units(unit, **kwargs):
metadata = {UNIT_SUFFIX: f" {unit}"}
return attr.ib(metadata=metadata, **kwargs)
def get_units(field: attr.Attribute) -> str:
return field.metadata.get(UNIT_SUFFIX, "")
@attr.dataclass
class Alias:
"""

Wyświetl plik

@ -12,7 +12,7 @@ import attr
from corrscope import outputs as outputs_
from corrscope.channel import Channel, ChannelConfig
from corrscope.config import KeywordAttrs, DumpEnumAsStr, CorrError
from corrscope.config import KeywordAttrs, DumpEnumAsStr, CorrError, with_units
from corrscope.layout import LayoutConfig
from corrscope.renderer import MatplotlibRenderer, RendererConfig, Renderer
from corrscope.triggers import (
@ -51,13 +51,13 @@ class Config(
""" Default values indicate optional attributes. """
master_audio: Optional[str]
begin_time: float = 0
begin_time: float = with_units("s", default=0)
end_time: Optional[float] = None
fps: int
trigger_ms: int
render_ms: int
trigger_ms: int = with_units("ms")
render_ms: int = with_units("ms")
# Performance
trigger_subsampling: int = 1

Wyświetl plik

@ -10,14 +10,16 @@ from typing import (
TYPE_CHECKING,
Union,
Sequence,
Tuple,
)
import attr
from PyQt5 import QtWidgets as qw, QtCore as qc
from PyQt5.QtCore import pyqtSlot
from PyQt5.QtGui import QPalette, QColor
from PyQt5.QtWidgets import QWidget
from corrscope.config import CorrError, DumpableAttrs
from corrscope.config import CorrError, DumpableAttrs, get_units
from corrscope.gui.util import color2hex
from corrscope.triggers import lerp
from corrscope.util import obj_name, perr
@ -229,12 +231,25 @@ class BoundLineEdit(qw.QLineEdit, BoundWidget):
class BoundSpinBox(qw.QSpinBox, BoundWidget):
def bind_widget(self, model: PresentationModel, path: str, *args, **kwargs) -> None:
BoundWidget.bind_widget(self, model, path, *args, **kwargs)
try:
parent, name = flatten_attr(model.cfg, path)
except AttributeError:
return
fields = attr.fields_dict(type(parent))
field = fields[name]
self.setSuffix(get_units(field))
set_gui = alias("setValue")
gui_changed = alias("valueChanged")
set_model = model_setter(int)
class BoundDoubleSpinBox(qw.QDoubleSpinBox, BoundWidget):
bind_widget = BoundSpinBox.bind_widget
set_gui = alias("setValue")
gui_changed = alias("valueChanged")
set_model = model_setter(float)
@ -495,11 +510,13 @@ def rgetattr(obj: DumpableAttrs, dunder_delim_path: str, *default) -> Any:
:return: obj.attr1.attr2.etc
"""
def _getattr(obj, attr):
return getattr(obj, attr, *default)
attrs: List[Any] = dunder_delim_path.split(DUNDER)
return functools.reduce(_getattr, [obj] + attrs)
try:
return functools.reduce(getattr, attrs, obj)
except AttributeError:
if default:
return default[0]
raise
def rhasattr(obj, dunder_delim_path: str):
@ -510,6 +527,20 @@ def rhasattr(obj, dunder_delim_path: str):
return False
def flatten_attr(obj, dunder_delim_path: str) -> Tuple[Any, str]:
"""
:param obj: Object
:param dunder_delim_path: 'attr1__attr2__etc'
:return: (shallow_obj, name) such that
getattr(shallow_obj, name) == rgetattr(obj, dunder_delim_path).
"""
parent, _, name = dunder_delim_path.rpartition(DUNDER)
parent_obj = rgetattr(obj, parent) if parent else obj
return parent_obj, name
# https://stackoverflow.com/a/31174427/2683842
def rsetattr(obj, dunder_delim_path: str, val):
"""
@ -517,7 +548,5 @@ def rsetattr(obj, dunder_delim_path: str, val):
:param dunder_delim_path: 'attr1__attr2__etc'
:param val: obj.attr1.attr2.etc = val
"""
parent, _, name = dunder_delim_path.rpartition(DUNDER)
parent_obj = rgetattr(obj, parent) if parent else obj
parent_obj, name = flatten_attr(obj, dunder_delim_path)
return setattr(parent_obj, name, val)

Wyświetl plik

@ -54,7 +54,7 @@
<item row="1" column="0">
<widget class="QLabel" name="trigger_msL">
<property name="text">
<string>Trigger Width (ms)</string>
<string>Trigger Width</string>
</property>
</widget>
</item>
@ -71,7 +71,7 @@
<item row="2" column="0">
<widget class="QLabel" name="render_msL">
<property name="text">
<string>Render Width (ms)</string>
<string>Render Width</string>
</property>
</widget>
</item>
@ -102,7 +102,7 @@
<item row="4" column="0">
<widget class="QLabel" name="begin_timeL">
<property name="text">
<string>Begin Time (s)</string>
<string>Begin Time</string>
</property>
</widget>
</item>

Wyświetl plik

@ -6,7 +6,7 @@ import attr
import matplotlib
import numpy as np
from corrscope.config import DumpableAttrs
from corrscope.config import DumpableAttrs, with_units
from corrscope.layout import RendererLayout, LayoutConfig, EdgeFinder
from corrscope.outputs import RGB_DEPTH, ByteBuffer
from corrscope.util import coalesce
@ -54,7 +54,7 @@ def default_color() -> str:
class RendererConfig(DumpableAttrs, always_dump="*"):
width: int
height: int
line_width: float = 1.5
line_width: float = with_units("px", default=1.5)
bg_color: str = "#000000"
init_line_color: str = default_color()

Wyświetl plik

@ -1,3 +1,4 @@
import attr
import pytest
from ruamel.yaml import yaml_object
@ -9,12 +10,13 @@ from corrscope.config import (
Ignored,
CorrError,
CorrWarning,
with_units,
get_units,
)
# YAML Idiosyncrasies: https://docs.saltstack.com/en/develop/topics/troubleshooting/yaml_idiosyncrasies.html
# Load/dump infrastructure testing
import attr
def test_dumpable_attrs():
@ -52,6 +54,26 @@ def test_yaml_object():
assert s == "!Bar {}\n"
# Test per-field unit suffixes (used by GUI)
def test_unit_suffix():
class Foo(DumpableAttrs):
xs: int = with_units("xs")
ys: int = with_units("ys", default=2)
no_unit: int = 3
# Assert class constructor works.
foo = Foo(1, 2, 3)
foo_default = Foo(1)
# Assert units work.
foo_fields = attr.fields(Foo)
assert get_units(foo_fields.xs) == " xs"
assert get_units(foo_fields.ys) == " ys"
assert get_units(foo_fields.no_unit) == ""
# Dataclass dump testing

Wyświetl plik

@ -1,6 +1,24 @@
import pytest
from corrscope.gui.data_bind import rgetattr, rsetattr, rhasattr
from corrscope.gui.data_bind import rgetattr, rsetattr, rhasattr, flatten_attr
class Person(object):
def __init__(self):
self.pet = Pet()
self.residence = Residence()
class Pet(object):
def __init__(self, name="Fido", species="Dog"):
self.name = name
self.species = species
class Residence(object):
def __init__(self, type="House", sqft=None):
self.type = type
self.sqft = sqft
def test_rgetattr():
@ -9,22 +27,6 @@ def test_rgetattr():
https://stackoverflow__com/a/31174427/
"""
class Person(object):
def __init__(self):
self.pet = Pet()
self.residence = Residence()
class Pet(object):
def __init__(self, name="Fido", species="Dog"):
self.name = name
self.species = species
class Residence(object):
def __init__(self, type="House", sqft=None):
self.type = type
self.sqft = sqft
p = Person()
# Test rgetattr(present)
@ -55,9 +57,20 @@ def test_rgetattr():
assert not rhasattr(p, "ghost__species")
@pytest.mark.xfail(
reason="rgetattr copied from Stack Overflow and subtly broken", strict=True
)
def test_flatten_attr():
p = Person()
# Test nested
flat, name = flatten_attr(p, "pet__name")
assert flat is p.pet
assert name == "name"
# Test 1 level
flat, name = flatten_attr(p, "pet")
assert flat is p
assert name == "pet"
def test_rgetattr_broken():
"""
rgetattr(default) fails to short-circuit/return on the first missing attribute.
@ -72,5 +85,5 @@ def test_rgetattr_broken():
- None.foo AKA return 1 to caller
"""
result = rgetattr(None, "foo__bar__imag", 1)
result = rgetattr(object(), "nothing__imag", 1)
assert result == 1, result