154 lines
4.7 KiB
Python
154 lines
4.7 KiB
Python
|
|
"""
|
||
|
|
Reference-line management: a side-panel list of custom horizontal markers plus a
|
||
|
|
properties dialog.
|
||
|
|
|
||
|
|
`RefLineControlWidget` is a pure view over a list of `RefLineProps` owned by the
|
||
|
|
main window: it renders the list and emits intents (add / edit / remove / clear).
|
||
|
|
`RefLineDialog` edits one line's value, colour, line style, and tag.
|
||
|
|
"""
|
||
|
|
|
||
|
|
import logging
|
||
|
|
|
||
|
|
from PyQt5.QtWidgets import (
|
||
|
|
QWidget, QVBoxLayout, QHBoxLayout, QGroupBox, QListWidget, QPushButton,
|
||
|
|
QDialog, QFormLayout, QDoubleSpinBox, QComboBox, QLineEdit, QColorDialog,
|
||
|
|
QDialogButtonBox,
|
||
|
|
)
|
||
|
|
from PyQt5.QtGui import QColor
|
||
|
|
from PyQt5.QtCore import pyqtSignal
|
||
|
|
|
||
|
|
from plotspec import RefLineProps
|
||
|
|
|
||
|
|
|
||
|
|
_STYLE_CHOICES = [("Solid", "solid"), ("Dashed", "dash"), ("Dotted", "dot")]
|
||
|
|
|
||
|
|
|
||
|
|
class RefLineDialog(QDialog):
|
||
|
|
"""Edit one reference line's properties. Read the result via `result_props`."""
|
||
|
|
|
||
|
|
def __init__(self, parent, props: RefLineProps):
|
||
|
|
super().__init__(parent)
|
||
|
|
self.setWindowTitle("Reference line")
|
||
|
|
self._color = props.color
|
||
|
|
|
||
|
|
form = QFormLayout(self)
|
||
|
|
|
||
|
|
self.value_spin = QDoubleSpinBox()
|
||
|
|
self.value_spin.setRange(-1e6, 1e6)
|
||
|
|
self.value_spin.setDecimals(2)
|
||
|
|
self.value_spin.setValue(props.value)
|
||
|
|
form.addRow("Value:", self.value_spin)
|
||
|
|
|
||
|
|
self.color_button = QPushButton()
|
||
|
|
self.color_button.clicked.connect(self._pick_color)
|
||
|
|
self._refresh_color_button()
|
||
|
|
form.addRow("Colour:", self.color_button)
|
||
|
|
|
||
|
|
self.style_combo = QComboBox()
|
||
|
|
for label, key in _STYLE_CHOICES:
|
||
|
|
self.style_combo.addItem(label, key)
|
||
|
|
idx = self.style_combo.findData(props.style)
|
||
|
|
if idx >= 0:
|
||
|
|
self.style_combo.setCurrentIndex(idx)
|
||
|
|
form.addRow("Line style:", self.style_combo)
|
||
|
|
|
||
|
|
self.label_edit = QLineEdit(props.label)
|
||
|
|
self.label_edit.setPlaceholderText("(optional tag)")
|
||
|
|
form.addRow("Tag:", self.label_edit)
|
||
|
|
|
||
|
|
buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
||
|
|
buttons.accepted.connect(self.accept)
|
||
|
|
buttons.rejected.connect(self.reject)
|
||
|
|
form.addRow(buttons)
|
||
|
|
|
||
|
|
def _pick_color(self):
|
||
|
|
chosen = QColorDialog.getColor(QColor(self._color), self, "Reference line colour")
|
||
|
|
if chosen.isValid():
|
||
|
|
self._color = chosen.name()
|
||
|
|
self._refresh_color_button()
|
||
|
|
|
||
|
|
def _refresh_color_button(self):
|
||
|
|
self.color_button.setText(self._color)
|
||
|
|
# Show the colour as the button's background for a quick read.
|
||
|
|
self.color_button.setStyleSheet(f"background-color: {self._color};")
|
||
|
|
|
||
|
|
def result_props(self) -> RefLineProps:
|
||
|
|
return RefLineProps(
|
||
|
|
value=float(self.value_spin.value()),
|
||
|
|
color=self._color,
|
||
|
|
style=self.style_combo.currentData(),
|
||
|
|
label=self.label_edit.text().strip(),
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
class RefLineControlWidget(QWidget):
|
||
|
|
"""List of reference lines with Add / Edit / Remove / Clear controls."""
|
||
|
|
|
||
|
|
addRequested = pyqtSignal()
|
||
|
|
editRequested = pyqtSignal(int)
|
||
|
|
removeRequested = pyqtSignal(int)
|
||
|
|
clearRequested = pyqtSignal()
|
||
|
|
|
||
|
|
def __init__(self, parent=None):
|
||
|
|
super().__init__(parent)
|
||
|
|
self.logger = logging.getLogger(__name__)
|
||
|
|
self.initUI()
|
||
|
|
|
||
|
|
def initUI(self):
|
||
|
|
layout = QVBoxLayout(self)
|
||
|
|
layout.setContentsMargins(5, 5, 5, 5)
|
||
|
|
|
||
|
|
group_box = QGroupBox("Reference lines")
|
||
|
|
group_layout = QVBoxLayout(group_box)
|
||
|
|
|
||
|
|
self.line_list = QListWidget()
|
||
|
|
self.line_list.setMaximumHeight(110)
|
||
|
|
self.line_list.itemDoubleClicked.connect(self._on_double_click)
|
||
|
|
group_layout.addWidget(self.line_list)
|
||
|
|
|
||
|
|
row = QHBoxLayout()
|
||
|
|
self.add_button = QPushButton("Add")
|
||
|
|
self.add_button.clicked.connect(self.addRequested.emit)
|
||
|
|
row.addWidget(self.add_button)
|
||
|
|
|
||
|
|
self.edit_button = QPushButton("Edit…")
|
||
|
|
self.edit_button.clicked.connect(self._emit_edit)
|
||
|
|
row.addWidget(self.edit_button)
|
||
|
|
|
||
|
|
self.remove_button = QPushButton("Remove")
|
||
|
|
self.remove_button.clicked.connect(self._emit_remove)
|
||
|
|
row.addWidget(self.remove_button)
|
||
|
|
|
||
|
|
self.clear_button = QPushButton("Clear")
|
||
|
|
self.clear_button.clicked.connect(self.clearRequested.emit)
|
||
|
|
row.addWidget(self.clear_button)
|
||
|
|
|
||
|
|
group_layout.addLayout(row)
|
||
|
|
layout.addWidget(group_box)
|
||
|
|
|
||
|
|
def set_lines(self, lines: list[RefLineProps]):
|
||
|
|
"""Repopulate the list display from the current props (preserving selection)."""
|
||
|
|
current = self.line_list.currentRow()
|
||
|
|
self.line_list.clear()
|
||
|
|
for p in lines:
|
||
|
|
tag = f" {p.label}" if p.label else ""
|
||
|
|
self.line_list.addItem(f"{p.value:.2f}{tag}")
|
||
|
|
if 0 <= current < self.line_list.count():
|
||
|
|
self.line_list.setCurrentRow(current)
|
||
|
|
|
||
|
|
def _selected_row(self) -> int:
|
||
|
|
return self.line_list.currentRow()
|
||
|
|
|
||
|
|
def _emit_edit(self):
|
||
|
|
row = self._selected_row()
|
||
|
|
if row >= 0:
|
||
|
|
self.editRequested.emit(row)
|
||
|
|
|
||
|
|
def _emit_remove(self):
|
||
|
|
row = self._selected_row()
|
||
|
|
if row >= 0:
|
||
|
|
self.removeRequested.emit(row)
|
||
|
|
|
||
|
|
def _on_double_click(self, _item):
|
||
|
|
self._emit_edit()
|