Skip to content

Commit

Permalink
Happy with structure, now ready to load data!
Browse files Browse the repository at this point in the history
  • Loading branch information
JoschD committed Dec 8, 2023
1 parent e9f9dc9 commit 4c8a5e4
Show file tree
Hide file tree
Showing 9 changed files with 264 additions and 76 deletions.
65 changes: 43 additions & 22 deletions omc3_gui/segment_by_segment/main_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from omc3_gui.segment_by_segment.main_view import SbSWindow
from omc3_gui.segment_by_segment.measurement_model import OpticsMeasurement
from omc3_gui.segment_by_segment.measurement_view import OpticsMeasurementDialog
from omc3_gui.segment_by_segment.segment_model import SegmentModel
from omc3_gui.segment_by_segment.segment_model import SegmentDataModel, SegmentItemModel, compare_segments
from omc3_gui.utils.base_classes import Controller
from omc3_gui.utils.file_dialogs import OpenDirectoriesDialog
from omc3_gui.utils.threads import BackgroundThread
Expand Down Expand Up @@ -169,16 +169,21 @@ def measurement_selection_changed(self, measurements: Sequence[OpticsMeasurement
self.set_measurement_interaction_buttons_enabled(True)
self.set_all_segment_buttons_enabled(True)

if len(measurements) > 1:
self._view.button_edit_measurement.setEnabled(False)
self._view.set_segments(SegmentTableModel())
self.segment_selection_changed()
return

measurement = measurements[0]
segment_model = SegmentTableModel()
segment_model.add_items(measurement.segments)
self._view.set_segments(segment_model)
segment_table_items: List[SegmentItemModel] = []

for measurement in measurements:
for segment in measurement.segments:
for segment_item in segment_table_items:
if compare_segments(segment, segment_item):
segment_item.append_segment(segment)
break
else:
segment_table_items.append(SegmentItemModel.from_segments([segment]))

segment_table = SegmentTableModel()
segment_table.add_items(segment_table_items)
self._view.set_segments(segment_table)
self.segment_selection_changed()

def get_single_measurement(self) -> OpticsMeasurement:
Expand Down Expand Up @@ -213,11 +218,11 @@ def set_all_segment_buttons_enabled(self, enabled: bool = True):
button.setEnabled(enabled)

@Slot(tuple)
def segment_selection_changed(self, segments: Sequence[SegmentModel] = None):
def segment_selection_changed(self, segments: Sequence[SegmentItemModel] = None):
if segments is None:
segments = self._view.get_selected_segments()

LOGGER.debug(f"Showing {len(segments)} segments.")
LOGGER.debug(f"{len(segments)} Segment(s) selected.")
if not len(segments):
self.set_segment_interaction_buttons_enabled(False)
return
Expand All @@ -243,7 +248,7 @@ def add_default_segments(self):
continue

for segment_tuple in DEFAULT_SEGMENTS:
segment = SegmentModel(*segment_tuple)
segment = SegmentDataModel(*segment_tuple)
segment.start = f"{segment.start}.B{beam}"
segment.end = f"{segment.end}.B{beam}"
measurement.try_add_segment(segment)
Expand All @@ -266,12 +271,14 @@ def new_segment(self):

LOGGER.debug("Segment dialog closed. Updating segement.")
for measurement in selected_measurements:
measurement.try_add_segment(dialog.segment.copy())
new_segment_copy = dialog.segment.copy()
new_segment_copy.measurement = measurement
measurement.try_add_segment(new_segment_copy)

self.measurement_selection_changed(selected_measurements)

@Slot()
def copy_segment(self, segments: Sequence[SegmentModel] = None):
def copy_segment(self, segments: Sequence[SegmentItemModel] = None):
if segments is None:
segments = self._view.get_selected_segments()
if not segments:
Expand All @@ -285,14 +292,19 @@ def copy_segment(self, segments: Sequence[SegmentModel] = None):
return

for measurement in selected_measurements:
for segment in segments:
new_segment = SegmentModel(f"{segment.name} - Copy", start=segment.start, end=segment.end)
for segment_item in segments:
try:
meas_segment = measurement.get_segment_by_name(segment_item.name)
except NameError as e:
LOGGER.warning(f"Could not copy segment. {e!s}")
continue
new_segment = get_segment_copy_with_unique_name(meas_segment, measurement)
measurement.try_add_segment(new_segment)

self.measurement_selection_changed(selected_measurements)

@Slot()
def remove_segment(self, segments: Sequence[SegmentModel] = None):
def remove_segment(self, segments: Sequence[SegmentItemModel] = None):
if segments is None:
segments = self._view.get_selected_segments()
if not segments:
Expand All @@ -306,13 +318,13 @@ def remove_segment(self, segments: Sequence[SegmentModel] = None):
return

for measurement in selected_measurements:
for segment in segments:
measurement.try_remove_segment(segment)
for segment_item in segments:
measurement.try_remove_segment(segment_item.name)

self.measurement_selection_changed(selected_measurements)

@Slot()
def run_segments(self, segments: Sequence[SegmentModel] = None):
def run_segments(self, segments: Sequence[SegmentDataModel] = None):
if segments is None:
segments = self._view.get_selected_segments()
if not segments:
Expand Down Expand Up @@ -344,5 +356,14 @@ def run_segments(self, segments: Sequence[SegmentModel] = None):
measurement_task.start()



def get_segment_copy_with_unique_name(segment: SegmentDataModel, measurement: OpticsMeasurement) -> SegmentDataModel:
new_segment = segment.copy()
idx = 0
segment_names = [s.name for s in measurement.segments]
new_name = new_segment.name
while new_name in segment_names:
idx += 1
new_name = f"{segment.name}_{idx}"
new_segment.name = new_name
return new_segment

21 changes: 10 additions & 11 deletions omc3_gui/segment_by_segment/main_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@


from omc3_gui.segment_by_segment.measurement_model import OpticsMeasurement
from omc3_gui.segment_by_segment.segment_model import SegmentModel
from omc3_gui.segment_by_segment.segment_model import SegmentItemModel

LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -169,7 +169,7 @@ class SegmentTableModel(QtCore.QAbstractTableModel, UniqueItemListModel):
_COLUMNS = {0: "Segment", 1: "Start", 2: "End"}
_COLUMNS_MAP = {0: "name", 1: "start", 2: "end"}

_items: Dict[str, SegmentModel] # only for the IDE
_items: Dict[str, SegmentItemModel] # only for the IDE

def __init__(self, *args, **kwargs):
super(QtCore.QAbstractTableModel, self).__init__(*args, **kwargs)
Expand All @@ -189,34 +189,33 @@ def columnCount(self, parent=QtCore.QModelIndex()):
def data(self, index: QtCore.QModelIndex, role=QtCore.Qt.DisplayRole):
i = index.row()
j = index.column()
segment: SegmentModel = self.get_item_at(i)
segment: SegmentItemModel = self.get_item_at(i)

if role == QtCore.Qt.DisplayRole:
return str(getattr(segment, self._COLUMNS_MAP[j]))

if role == Qt.ToolTipRole:
return segment.tooltip()

if role == Qt.EditRole:
return segment

def setData(self, index, value, role):
i = index.row()
j = index.column()
segment: SegmentModel = self.get_item_at(i)
old_name = segment.name
segment: SegmentItemModel = self.get_item_at(i)

if role == Qt.EditRole:
if value is None or value == "":
return False

setattr(segment, self._COLUMNS_MAP[j], value)
if segment.name != old_name:
self.update_item(segment, old_id=old_name)
else:
self.dataChanged.emit(index, index)
self.dataChanged.emit(index, index)
return True

def toggle_row(self, index):
i = index.row()
segment: SegmentModel = self.get_item_at(i)
segment: SegmentItemModel = self.get_item_at(i)
segment.enabled = not segment.enabled
self.headerDataChanged.emit(Qt.Horizontal, 0, self.rowCount() - 1)
self.dataChanged.emit(self.index(i, 0), self.index(i, self.rowCount() - 1))
Expand Down
18 changes: 6 additions & 12 deletions omc3_gui/segment_by_segment/main_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@
from omc3_gui.plotting.classes import DualPlot
from omc3_gui.segment_by_segment.main_model import MeasurementListModel, SegmentTableModel
from omc3_gui.segment_by_segment.measurement_model import OpticsMeasurement
from omc3_gui.segment_by_segment.segment_model import SegmentModel
from omc3_gui.segment_by_segment.segment_model import SegmentItemModel
from omc3_gui.utils import colors
from omc3_gui.utils.base_classes import View
from omc3_gui.utils.counter import HorizontalGridLayoutFiller
from omc3_gui.utils.widgets import DefaultButton, EditButton, OpenButton, RemoveButton, RunButton, RunningSpinner
from omc3_gui.utils.styles import MONOSPACED_TOOLTIP
from omc3_gui.utils.widgets import (DefaultButton, EditButton, OpenButton, RemoveButton, RunButton)

LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -233,7 +234,7 @@ def set_segments(self, segment_model: SegmentTableModel):
def get_segments(self) -> SegmentTableModel:
return self._table_segments.model()

def get_selected_segments(self) -> Tuple[SegmentModel]:
def get_selected_segments(self) -> Tuple[SegmentItemModel]:
selected: List[QModelIndex] = self._table_segments.selectedIndexes()
return tuple(s.data(role=Qt.EditRole) for s in selected if s.column() == 0) # need only one per row

Expand Down Expand Up @@ -261,15 +262,7 @@ def __init__(self):
self.setModel(MeasurementListModel())
self.setItemDelegate(ColoredItemDelegate())
self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
tooltip_style = f"""
QToolTip {{
background-color: {colors.TOOLTIP_BACKGROUND}; /* Light gray background */
color: {colors.TOOLTIP_TEXT}; /* Dark gray text */
border: 1px solid {colors.TOOLTIP_BORDER}; /* Gray border */
font-family: "Courier New", monospace; /* Monospaced font */
}}
"""
self.setStyleSheet(tooltip_style)
self.setStyleSheet(MONOSPACED_TOOLTIP)


class SegmentTableView(QtWidgets.QTableView):
Expand All @@ -288,6 +281,7 @@ def __init__(self):
self.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
self.setShowGrid(True)
self.setStyleSheet(MONOSPACED_TOOLTIP)

# def mousePressEvent(self, e: QtGui.QMouseEvent) -> None:
# idx = self.indexAt(e.pos())
Expand Down
45 changes: 34 additions & 11 deletions omc3_gui/segment_by_segment/measurement_model.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@

from __future__ import annotations # Together with TYPE_CHECKING: allow circular imports for type-hints
import logging
from dataclasses import dataclass, field, fields
from pathlib import Path
Expand All @@ -9,8 +9,11 @@
PHASE_NAME)
from tfs.reader import read_headers

from omc3_gui.segment_by_segment.segment_model import SegmentModel
from omc3_gui.utils.dataclass_ui import choices_validator as choices, metafield
from typing import TYPE_CHECKING

if TYPE_CHECKING:
from omc3_gui.segment_by_segment.segment_model import SegmentDataModel

SEQUENCE = "SEQUENCE"
DATE = "DATE"
Expand Down Expand Up @@ -38,7 +41,7 @@ class OpticsMeasurement:
beam: int = metafield("Beam", "Beam of the accelerator", default=None, validate=choices(1, 2))
# List of segments. Using a list here, so the name and start/end can be changed
# without having to modify anything here.
_segments: List[SegmentModel] = field(default_factory=list)
_segments: List[SegmentDataModel] = field(default_factory=list)

DEFAULT_OUTPUT_DIR: ClassVar[str] = "sbs"

Expand Down Expand Up @@ -82,32 +85,52 @@ def tooltip(self) -> str:
return "\n".join(f"{name:{l}s}: {value}" for name, value in parts if value is not None)

# Segment Control ----------------------------------------------------------
def remove_segment(self, segment: SegmentModel):
self.segments.remove(segment)
def remove_segment(self, segment: SegmentDataModel):
try:
self.segments.remove(segment)
except ValueError as e:
raise ValueError(f"Segment with name {segment.name} is not in {self.display()}.") from e

def add_segment(self, segment: SegmentModel):
def add_segment(self, segment: SegmentDataModel):
if segment in self.segments:
raise NameError(f"Segment {segment} is already in {self}")
raise NameError(f"Segment {segment} is already in {self.display()}")

if segment.name in [s.name for s in self.segments]:
raise NameError(f"A segment with name {segment.name} is already in {self}")
raise NameError(f"A segment with name {segment.name} is already in {self.display()}")

self.segments.append(segment)

def try_add_segment(self, segment: SegmentModel):
def try_add_segment(self, segment: SegmentDataModel) -> bool:
try:
self.add_segment(segment)
except NameError as e:
LOGGER.error(str(e))
return False
return True

def try_remove_segment(self, segment: SegmentModel):
def try_remove_segment(self, segment: Union[SegmentDataModel, str]) -> bool:
if isinstance(segment, str):
try:
segment = self.get_segment_by_name(segment)
except NameError as e:
LOGGER.error(str(e))
return False

try:
self.remove_segment(segment)
except ValueError as e:
LOGGER.error(str(e))
return False
return True

def get_segment_by_name(self, name: str) -> SegmentDataModel:
for segment in self.segments:
if segment.name == name:
return segment
raise NameError(f"No segment with name {name} in {self.display()}.")

@property
def segments(self) -> List[SegmentModel]:
def segments(self) -> List[SegmentDataModel]:
return self._segments

# Segment-by-Segment Parameters --------------------------------------------
Expand Down
2 changes: 1 addition & 1 deletion omc3_gui/segment_by_segment/measurement_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def __init__(self, parent=None, optics_measurement: OpticsMeasurement = None):
optics_measurement = OpticsMeasurement(measurement_dir=TO_BE_DEFINED, output_dir=TO_BE_DEFINED)

dataclass_ui = DataClassUI.build_dataclass_ui(
field_def=[
field_definitions=[
FieldUIDef(name="measurement_dir", editable=False),
*(FieldUIDef(name) for name in ("model_dir", "output_dir", "accel", "beam", "year", "ring"))
],
Expand Down
Loading

0 comments on commit 4c8a5e4

Please sign in to comment.