kopia lustrzana https://github.com/corrscope/corrscope
commit
32ca28cf92
|
@ -211,6 +211,7 @@ class MainWindow(qw.QMainWindow):
|
|||
cfg = yaml.load(cfg_path)
|
||||
|
||||
# Raises color getter exceptions
|
||||
# FIXME if error halfway, clear "file path" and load empty model.
|
||||
self.load_cfg(cfg, cfg_path)
|
||||
|
||||
except Exception as e:
|
||||
|
@ -582,37 +583,6 @@ def default_property(path: str, default: Any) -> property:
|
|||
return property(getter, setter)
|
||||
|
||||
|
||||
def color2hex_property(path: str) -> property:
|
||||
def getter(self: "ConfigModel"):
|
||||
color_attr = rgetattr(self.cfg, path)
|
||||
return color2hex(color_attr)
|
||||
|
||||
def setter(self: "ConfigModel", val: str):
|
||||
color = color2hex(val)
|
||||
rsetattr(self.cfg, path, color)
|
||||
|
||||
return property(getter, setter)
|
||||
|
||||
|
||||
def color2hex_maybe_property(path: str) -> property:
|
||||
# TODO turn into class, and use __set_name__ to determine assignment LHS=path.
|
||||
def getter(self: "ConfigModel"):
|
||||
color_attr = rgetattr(self.cfg, path)
|
||||
if not color_attr:
|
||||
return ""
|
||||
return color2hex(color_attr)
|
||||
|
||||
def setter(self: "ConfigModel", val: str):
|
||||
color: Optional[str]
|
||||
if val:
|
||||
color = color2hex(val)
|
||||
else:
|
||||
color = None
|
||||
rsetattr(self.cfg, path, color)
|
||||
|
||||
return property(getter, setter)
|
||||
|
||||
|
||||
def path_strip_quotes(path: str) -> str:
|
||||
if len(path) and path[0] == path[-1] == '"':
|
||||
return path[1:-1]
|
||||
|
@ -656,10 +626,6 @@ class ConfigModel(PresentationModel):
|
|||
combo_text[path] = flatten_text
|
||||
del path
|
||||
|
||||
render__bg_color = color2hex_property("render__bg_color")
|
||||
render__init_line_color = color2hex_property("render__init_line_color")
|
||||
render__grid_color = color2hex_maybe_property("render__grid_color")
|
||||
|
||||
@property
|
||||
def render_resolution(self) -> str:
|
||||
render = self.cfg.render
|
||||
|
|
|
@ -18,6 +18,7 @@ from PyQt5.QtGui import QPalette, QColor
|
|||
from PyQt5.QtWidgets import QWidget
|
||||
|
||||
from corrscope.config import CorrError, DumpableAttrs
|
||||
from corrscope.gui.util import color2hex
|
||||
from corrscope.triggers import lerp
|
||||
from corrscope.util import obj_name, perr
|
||||
|
||||
|
@ -95,7 +96,12 @@ def map_gui(view: "MainWindow", model: PresentationModel) -> None:
|
|||
) # dear pyqt, add generic mypy return types
|
||||
for widget in widgets:
|
||||
path = widget.objectName()
|
||||
widget.bind_widget(model, path)
|
||||
|
||||
# Exclude nameless ColorText inside BoundColorWidget wrapper,
|
||||
# since bind_widget(path="") will crash.
|
||||
# BoundColorWidget.bind_widget() handles binding children.
|
||||
if path:
|
||||
widget.bind_widget(model, path)
|
||||
|
||||
|
||||
Signal = Any
|
||||
|
@ -108,7 +114,18 @@ class BoundWidget(QWidget):
|
|||
pmodel: PresentationModel
|
||||
path: str
|
||||
|
||||
def bind_widget(self, model: PresentationModel, path: str) -> None:
|
||||
def bind_widget(
|
||||
self, model: PresentationModel, path: str, connect_to_model=True
|
||||
) -> None:
|
||||
"""
|
||||
connect_to_model=False means:
|
||||
- self: ColorText is created and owned by BoundColorWidget wrapper.
|
||||
- Wrapper forwards model changes to self (which updates other widgets).
|
||||
(model.update_widget[path] != self)
|
||||
- self.gui_changed invokes self.set_model() (which updates model
|
||||
AND other widgets).
|
||||
- wrapper.gui_changed signal is NEVER emitted.
|
||||
"""
|
||||
try:
|
||||
self.default_palette = self.palette()
|
||||
self.error_palette = self.calc_error_palette()
|
||||
|
@ -117,8 +134,9 @@ class BoundWidget(QWidget):
|
|||
self.path = path
|
||||
self.cfg2gui()
|
||||
|
||||
# Allow widget to be updated by other events.
|
||||
model.update_widget[path] = self.cfg2gui
|
||||
if connect_to_model:
|
||||
# Allow widget to be updated by other events.
|
||||
model.update_widget[path] = self.cfg2gui
|
||||
|
||||
# Allow pmodel to be changed by widget.
|
||||
self.gui_changed.connect(self.set_model)
|
||||
|
@ -278,6 +296,181 @@ class BoundComboBox(qw.QComboBox, BoundWidget):
|
|||
self.pmodel[self.path] = self.combo_symbols[combo_index]
|
||||
|
||||
|
||||
# Color-specific widgets
|
||||
|
||||
|
||||
class BoundColorWidget(BoundWidget, qw.QWidget):
|
||||
"""
|
||||
- set_gui(): Model is sent to self.text, which updates all other widgets.
|
||||
- self.text: ColorText
|
||||
- When self.text changes, it converts to hex, then updates the pmodel
|
||||
and sends signal `hex_color` which updates check/button.
|
||||
- self does NOT update the pmodel. (gui_changed is never emitted.)
|
||||
"""
|
||||
|
||||
optional = False
|
||||
|
||||
def __init__(self, parent: qw.QWidget):
|
||||
qw.QWidget.__init__(self, parent)
|
||||
|
||||
layout = qw.QHBoxLayout()
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.setLayout(layout)
|
||||
|
||||
# Setup text field.
|
||||
self.text = _ColorText(self, self.optional)
|
||||
layout.addWidget(self.text) # http://doc.qt.io/qt-5/qlayout.html#addItem
|
||||
|
||||
# Setup checkbox
|
||||
if self.optional:
|
||||
self.check = _ColorCheckBox(self, self.text)
|
||||
self.check.setToolTip("Enable/Disable Color")
|
||||
layout.addWidget(self.check)
|
||||
|
||||
# Setup colored button.
|
||||
self.button = _ColorButton(self, self.text)
|
||||
self.button.setToolTip("Pick Color")
|
||||
layout.addWidget(self.button)
|
||||
|
||||
# override BoundWidget
|
||||
def bind_widget(self, model: PresentationModel, path: str) -> None:
|
||||
super().bind_widget(model, path)
|
||||
self.text.bind_widget(model, path, connect_to_model=False)
|
||||
|
||||
# impl BoundWidget
|
||||
def set_gui(self, value: Optional[str]):
|
||||
self.text.set_gui(value)
|
||||
|
||||
# impl BoundWidget
|
||||
# Never gets emitted. self.text.set_model is responsible for updating model.
|
||||
gui_changed = qc.pyqtSignal(str)
|
||||
|
||||
# impl BoundWidget
|
||||
# Never gets called.
|
||||
def set_model(self, value):
|
||||
raise RuntimeError(
|
||||
"BoundColorWidget.gui_changed -> set_model should not be called"
|
||||
)
|
||||
|
||||
|
||||
class OptionalColorWidget(BoundColorWidget):
|
||||
optional = True
|
||||
|
||||
|
||||
class _ColorText(BoundLineEdit):
|
||||
"""
|
||||
- Validates and converts colors to hex (from model AND gui)
|
||||
- If __init__ optional, special-cases missing colors.
|
||||
"""
|
||||
|
||||
def __init__(self, parent: QWidget, optional: bool):
|
||||
super().__init__(parent)
|
||||
self.optional = optional
|
||||
|
||||
hex_color = qc.pyqtSignal(str)
|
||||
|
||||
def set_gui(self, value: Optional[str]):
|
||||
"""model2gui"""
|
||||
|
||||
if self.optional and not value:
|
||||
value = ""
|
||||
else:
|
||||
value = color2hex(value) # raises CorrError if invalid.
|
||||
|
||||
# Don't write back to model immediately.
|
||||
# Loading is a const process, only editing the GUI should change the model.
|
||||
with qc.QSignalBlocker(self):
|
||||
self.setText(value)
|
||||
|
||||
# Write to other GUI widgets immediately.
|
||||
self.hex_color.emit(value) # calls button.set_color()
|
||||
|
||||
@pyqtSlot(str)
|
||||
def set_model(self: BoundWidget, value: str):
|
||||
"""gui2model"""
|
||||
|
||||
if self.optional and not value:
|
||||
value = None
|
||||
else:
|
||||
try:
|
||||
value = color2hex(value)
|
||||
except CorrError:
|
||||
self.setPalette(self.error_palette)
|
||||
self.hex_color.emit("") # calls button.set_color()
|
||||
return
|
||||
|
||||
self.setPalette(self.default_palette)
|
||||
self.hex_color.emit(value or "") # calls button.set_color()
|
||||
self.pmodel[self.path] = value
|
||||
|
||||
def sizeHint(self) -> qc.QSize:
|
||||
"""Reduce the width taken up by #rrggbb color text boxes."""
|
||||
return self.minimumSizeHint()
|
||||
|
||||
|
||||
class _ColorButton(qw.QPushButton):
|
||||
def __init__(self, parent: QWidget, text: "_ColorText"):
|
||||
qw.QPushButton.__init__(self, parent)
|
||||
self.clicked.connect(self.on_clicked)
|
||||
|
||||
# Initialize "current color"
|
||||
self.curr_color = QColor()
|
||||
|
||||
# Initialize text
|
||||
self.color_text = text
|
||||
text.hex_color.connect(self.set_color)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_clicked(self):
|
||||
# https://bugreports.qt.io/browse/QTBUG-38537
|
||||
# On Windows, QSpinBox height is wrong if stylesheets are enabled.
|
||||
# And QColorDialog(parent=self) contains QSpinBox and inherits our stylesheets.
|
||||
# So set parent=self.window().
|
||||
|
||||
color: QColor = qw.QColorDialog.getColor(self.curr_color, self.window())
|
||||
if not color.isValid():
|
||||
return
|
||||
|
||||
self.color_text.setText(color.name()) # textChanged calls self.set_color()
|
||||
|
||||
@pyqtSlot(str)
|
||||
def set_color(self, hex_color: str):
|
||||
color = QColor(hex_color)
|
||||
self.curr_color = color
|
||||
|
||||
if color.isValid():
|
||||
# Tooltips inherit our styles. Don't change their background.
|
||||
qss = f"{obj_name(self)} {{ background: {color.name()}; }}"
|
||||
else:
|
||||
qss = ""
|
||||
|
||||
self.setStyleSheet(qss)
|
||||
|
||||
|
||||
class _ColorCheckBox(qw.QCheckBox):
|
||||
def __init__(self, parent: QWidget, text: "_ColorText"):
|
||||
qw.QCheckBox.__init__(self, parent)
|
||||
self.stateChanged.connect(self.on_check)
|
||||
|
||||
self.color_text = text
|
||||
text.hex_color.connect(self.set_color)
|
||||
|
||||
@pyqtSlot(str)
|
||||
def set_color(self, hex_color: str):
|
||||
with qc.QSignalBlocker(self):
|
||||
self.setChecked(bool(hex_color))
|
||||
|
||||
@pyqtSlot(CheckState)
|
||||
def on_check(self, value: CheckState):
|
||||
"""Qt.PartiallyChecked probably should not happen."""
|
||||
Qt = qc.Qt
|
||||
assert value in [Qt.Unchecked, Qt.PartiallyChecked, Qt.Checked]
|
||||
if value != Qt.Unchecked:
|
||||
self.color_text.setText("#ffffff")
|
||||
else:
|
||||
self.color_text.setText("")
|
||||
|
||||
|
||||
# Unused
|
||||
def try_behead(string: str, header: str) -> Optional[str]:
|
||||
if not string.startswith(header):
|
||||
|
|
|
@ -144,11 +144,7 @@
|
|||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="BoundLineEdit" name="render__bg_color">
|
||||
<property name="text">
|
||||
<string>bg</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="BoundColorWidget" name="render__bg_color" native="true"/>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="render__init_line_colorL">
|
||||
|
@ -158,11 +154,7 @@
|
|||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="BoundLineEdit" name="render__init_line_color">
|
||||
<property name="text">
|
||||
<string>fg</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="BoundColorWidget" name="render__init_line_color" native="true"/>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="render__line_widthL">
|
||||
|
@ -189,7 +181,7 @@
|
|||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="BoundLineEdit" name="render__grid_color"/>
|
||||
<widget class="OptionalColorWidget" name="render__grid_color" native="true"/>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="render__midline_colorL">
|
||||
|
@ -199,7 +191,7 @@
|
|||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="BoundLineEdit" name="render__midline_color"/>
|
||||
<widget class="OptionalColorWidget" name="render__midline_color" native="true"/>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="BoundCheckBox" name="render__v_midline">
|
||||
|
@ -706,6 +698,18 @@
|
|||
<extends>QTableView</extends>
|
||||
<header>corrscope/gui/__init__.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>BoundColorWidget</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>corrscope/gui/data_bind.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>OptionalColorWidget</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>corrscope/gui/data_bind.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
|
|
Ładowanie…
Reference in New Issue