Skip to content

Commit

Permalink
better tab handling, copy function updated
Browse files Browse the repository at this point in the history
  • Loading branch information
JoschD committed Dec 14, 2023
1 parent 4c8a5e4 commit 2bc03a0
Show file tree
Hide file tree
Showing 6 changed files with 138 additions and 54 deletions.
2 changes: 1 addition & 1 deletion omc3_gui/plotting/classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,4 @@ class ZoomingViewBox(ExViewBox):
# self.getdataInRect()
# self.changePointsColors()
# else:
# self.updateScaleBox(ev.buttonDownPos(), ev.pos())
# self.updateScaleBox(ev.buttonDownPos(), ev.pos())
54 changes: 29 additions & 25 deletions omc3_gui/segment_by_segment/main_controller.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import logging
from functools import partial
from pathlib import Path
from typing import Any, Dict, List, Sequence
from typing import Any, Dict, List, Sequence, Tuple

from omc3.sbs_propagation import segment_by_segment
from qtpy import QtWidgets
Expand All @@ -13,10 +13,12 @@
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 SegmentDataModel, SegmentItemModel, compare_segments
from omc3_gui.utils.base_classes import Controller
from omc3_gui.utils.ui_base_classes import Controller
from omc3_gui.utils.file_dialogs import OpenDirectoriesDialog
from omc3_gui.utils.threads import BackgroundThread
from omc3_gui.segment_by_segment.segment_view import SegmentDialog
from omc3.definitions.optics import ColumnsAndLabels
from omc3_gui.plotting.classes import DualPlot

LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -231,7 +233,11 @@ def segment_selection_changed(self, segments: Sequence[SegmentItemModel] = None)
if len(segments) > 1:
LOGGER.debug("More than one segment selected. Clearing Plots.")
return

# Plot segements
def_and_widget: Tuple[ColumnsAndLabels, DualPlot] = self._view.get_current_tab()
definition, widget = def_and_widget
# plot_segments()

@Slot()
def add_default_segments(self):
Expand All @@ -248,7 +254,7 @@ def add_default_segments(self):
continue

for segment_tuple in DEFAULT_SEGMENTS:
segment = SegmentDataModel(*segment_tuple)
segment = SegmentDataModel(measurement, *segment_tuple)
segment.start = f"{segment.start}.B{beam}"
segment.end = f"{segment.end}.B{beam}"
measurement.try_add_segment(segment)
Expand Down Expand Up @@ -291,15 +297,26 @@ def copy_segment(self, segments: Sequence[SegmentItemModel] = None):
LOGGER.error("Please select at least one measurement.")
return

for measurement in selected_measurements:
for segment_item in segments:
for segment_item in segments:
new_segment_name = f"{segment_item.name} - Copy"
for measurement in selected_measurements:
# Check if copied segment name already exists in one of the measurements
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)
measurement.get_segment_by_name(new_segment_name)
except NameError:
pass
else:
LOGGER.error(
f"Could not create copy \"{new_segment_name}\" as it already exists in {measurement.display()}."
)
break
else:
# None of the measurements have the copied segment name, so add to the measurements
for measurement in selected_measurements:
for segment in segment_item.segments:
new_segment = segment.copy()
new_segment.name = new_segment_name
measurement.try_add_segment(new_segment)

self.measurement_selection_changed(selected_measurements)

Expand All @@ -324,7 +341,7 @@ def remove_segment(self, segments: Sequence[SegmentItemModel] = None):
self.measurement_selection_changed(selected_measurements)

@Slot()
def run_segments(self, segments: Sequence[SegmentDataModel] = None):
def run_segments(self, segments: Sequence[SegmentItemModel] = None):
if segments is None:
segments = self._view.get_selected_segments()
if not segments:
Expand Down Expand Up @@ -354,16 +371,3 @@ def run_segments(self, segments: Sequence[SegmentDataModel] = None):

LOGGER.info(f"Starting {measurement_task.message}")
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

28 changes: 13 additions & 15 deletions omc3_gui/segment_by_segment/main_view.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,28 @@

# from omc3_gui.segment_by_segment.segment_by_segment_ui import Ui_main_window
import logging
from typing import Dict, List, Sequence, Tuple
from typing import Dict, Iterator, List, Sequence, Tuple

from PyQt5 import QtGui
from qtpy import QtGui, QtWidgets
from qtpy.QtCore import QItemSelectionModel, QModelIndex, Qt, Signal, Slot
from qtpy.QtCore import QItemSelectionModel, QModelIndex, Qt, Signal, Slot, QEvent

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 SegmentItemModel
from omc3_gui.utils import colors
from omc3_gui.utils.base_classes import View
from omc3_gui.utils.ui_base_classes import View
from omc3_gui.utils.counter import HorizontalGridLayoutFiller
from omc3_gui.utils.styles import MONOSPACED_TOOLTIP
from omc3_gui.utils.widgets import (DefaultButton, EditButton, OpenButton, RemoveButton, RunButton)
from omc3.definitions.optics import ColumnsAndLabels, PHASE_COLUMN
from omc3_gui.utils.iteration_classes import IterClass

LOGGER = logging.getLogger(__name__)

class Tab:
PHASE: str = "Phase"
class Tabs(IterClass):
PHASE: ColumnsAndLabels = PHASE_COLUMN


class SbSWindow(View):
Expand All @@ -41,7 +43,6 @@ def __init__(self, parent=None):
# Widgets ---
self._cental: QtWidgets.QSplitter = None
self._tabs_widget: QtWidgets.QTabWidget = None
self._tabs: Dict[str, DualPlot] = None
self._list_view_measurements: QtWidgets.QListView = None
self._table_segments: QtWidgets.QTableView = None

Expand Down Expand Up @@ -191,7 +192,8 @@ def build_segment_buttons():

def build_tabs_widget(): # --- Right Hand Side
self._tabs_widget = QtWidgets.QTabWidget()
self._tabs = self._create_tabs(self._tabs_widget)
for tab in Tabs.values():
self._tabs_widget.addTab(DualPlot(), tab.text_label.capitalize())
return self._tabs_widget

self._central.addWidget(build_tabs_widget())
Expand All @@ -202,14 +204,10 @@ def build_tabs_widget(): # --- Right Hand Side

self.setCentralWidget(self._central)

def _create_tabs(self, tab_widget: QtWidgets.QTabWidget) -> Dict[str, "DualPlot"]:
tabs = {}

new_plot = DualPlot()
tab_widget.addTab(new_plot, Tab.PHASE)
tabs[Tab.PHASE] = new_plot

return tabs
def get_current_tab(self) -> Tuple[ColumnsAndLabels, DualPlot]:
widget = self._tabs_widget.currentWidget()
index = self._tabs_widget.currentIndex()
return list(Tabs.values())[index], widget

# Getters and Setters
def set_measurements(self, measurement_model: MeasurementListModel):
Expand Down
55 changes: 42 additions & 13 deletions omc3_gui/segment_by_segment/segment_model.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
from __future__ import annotations # Together with TYPE_CHECKING: allow circular imports for type-hints
from dataclasses import dataclass
from dataclasses import dataclass, field
from typing import List, Optional, Union

from omc3_gui.utils.dataclass_ui import metafield
from omc3_gui.utils import colors
from omc3.segment_by_segment.segments import SegmentDiffs

from typing import TYPE_CHECKING

if TYPE_CHECKING:
from omc3_gui.segment_by_segment.measurement_model import OpticsMeasurement



OK = f"<span color=\"{colors.GREEN_DARK}\">✓</span>"
NO = f"<span color=\"{colors.RED_DARK}\">✗</span>"
# HTML in tooltips does not work for me, and I cannot figure out why (jdilly)
# OK = f"<font color=\"{colors.GREEN_DARK}\">✓</font>"
# NO = f"<font color=\"{colors.RED_DARK}\">✗</font>"
OK = "✓"
NO = "✗"

def not_empty(value):
return value != ""
Expand All @@ -25,23 +28,33 @@ class SegmentDataModel:
name: str = metafield("Name", "Name of the Segment", validate=not_empty)
start: Optional[str] = metafield("Start", "Start of the Segment", default=None, validate=not_empty)
end: Optional[str] = metafield("End", "End of the Segment", default=None, validate=not_empty)
_data: Optional[dict] = None
_data: Optional[SegmentDiffs] = None

def __str__(self):
return self.name

def is_element(self):
return self.start is None or self.end is None
return is_element(self)

def to_input_string(self):
""" String representation of the segment as used in inputs."""
if self.is_element():
return self.name
return f"{self.name},{self.start},{self.end}"
return to_input_string(self)

@property
def data(self) -> SegmentDiffs:
if self._data is None or self._data.directory != self.measurement.output_dir or self._data.segment_name != self.name:
self._data = SegmentDiffs(self.measurement.output_dir, self.name)
return self._data

def has_run(self) -> bool:
return bool(self._data)
try:
return self.data.get_path("phase_x").is_file()
except AttributeError:
return False

def clear_data(self):
self._data = None

def copy(self):
return SegmentDataModel(measurement=self.measurement, name=self.name, start=self.start, end=self.end)

Expand Down Expand Up @@ -112,18 +125,34 @@ def append_segment(self, segment: SegmentDataModel):
if not compare_segments(self, segment):
raise ValueError(f"Given segment has a different definition than this {self.__class__.name}.")
self.segments.append(segment)

@property

def id(self) -> str:
""" Unique identifier for the measurement, used in the ItemModel. """
return self.name + str(self.start) + str(self.end)

def tooltip(self) -> str:
""" Returns a string with information about the segment,
as to be used in a tool-tip. """
parts = [f"{OK if segment.has_run() else NO} {segment.measurement.display()}" for segment in self.segments]
parts = [f" {OK if segment.has_run() else NO} {segment.measurement.display()}" for segment in self.segments]
return "Run | Contained in:\n" + "\n".join(parts)

def is_element(self):
return is_element(self)

def to_input_string(self):
""" String representation of the segment as used in inputs."""
return to_input_string(self)

def compare_segments(a: Union[SegmentDataModel, SegmentItemModel], b: Union[SegmentDataModel, SegmentItemModel]):
return a.name == b.name and a.start == b.start and a.end == b.end


# Common functions -------------------------------------------------------------

def is_element(segment: [SegmentItemModel, SegmentDataModel]):
return segment.start is None or segment.end is None

def to_input_string(segment: [SegmentItemModel, SegmentDataModel]):
if is_element(segment):
return segment.name
return f"{segment.name},{segment.start},{segment.end}"
53 changes: 53 additions & 0 deletions omc3_gui/utils/iteration_classes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
from typing import Iterator, Tuple, Any

EXCLUDED_NAME = "EXCLUDED_ATTRIBUTES"

# Metaclasses ------------------------------------------------------------------

class IterableAttributeNames(type):
""" Makes the class itself iterable over its attribute names. """


def __iter__(self) -> Iterator[str]:
for attr in dir(self):
if not attr.startswith("__") and attr != EXCLUDED_NAME and attr not in getattr(self, EXCLUDED_NAME, []):
yield attr


class IterableAttributeValues(type):
""" Makes the class itself iterable over its attribute values. """
def __iter__(self) -> Iterator[Any]:
for attr, value in self.__dict__.items():
if not attr.startswith("__"):
yield value


class IterableAttributeItems(type):
""" Makes the class itself iterable over its attribute name and values. """
def __iter__(self) -> Iterator[Tuple[str, Any]]:
for attr, value in self.__dict__.items():
if not attr.startswith("__"):
yield attr, value


# Iterable Class ---------------------------------------------------------------


class IterClass(metaclass=IterableAttributeNames):

EXCLUDED_ATTRIBUTES = ["keys", "values", "items"]

@classmethod
def keys(cls) -> Iterator[str]:
for attr in cls:
yield attr

@classmethod
def values(cls) -> Iterator[Any]:
for attr in cls:
yield getattr(cls, attr)

@classmethod
def items(cls) -> Iterator[Tuple[str, Any]]:
for attr in cls:
yield attr, getattr(cls, attr)
File renamed without changes.

0 comments on commit 2bc03a0

Please sign in to comment.