from enum import Enum from math import trunc import pyqtgraph as pg from callbacks import CallbackDispatcher, CallbackType from detektor_plot import DetektorPlot from detektor_data import DetektorContainer class DetektorRegionState(Enum): UNSET = 1 SET = 4 COPIED = 5 class DetektorRegion(pg.LinearRegionItem): """Encapsulates a linear region that snaps to discrete X-axis values and shows a context menu.""" # in which mode the region is _state: DetektorRegionState = DetektorRegionState.UNSET _plot: DetektorPlot = None # the start and end is integer, not a label (time) _start_position: int = 0 _end_position: int = 0 def __init__(self, plot: DetektorPlot): super().__init__() # reference to the chart so we can add and remove the widget self._plot = plot # move the rectangle behind the plot lines self.setZValue(-10) # color is the same for selecting and hovering grid_color = pg.mkBrush((100, 100, 250, 50)) self.setBrush(grid_color) self.setHoverBrush(grid_color) self.setAcceptHoverEvents(True) # callback for changing the width self.sigRegionChanged.connect(self.snap_to_x_labels) @property def state(self): return self._state @state.setter def state(self, v: DetektorRegionState): """ Sets state and calls hooks if it changes from the previous one """ if v != self._state: self._state = v CallbackDispatcher().call(CallbackType.REGION_STATE) def set(self): """ Displays region occupying roughly a third of the actual view range """ self.state = DetektorRegionState.SET x_range, y_range = self._plot.view_box.viewRange() x_min, x_max = x_range third = (x_max - x_min) / 3 self.setRegion([x_min + third, x_max - third]) self.display() def unset(self): self.state = DetektorRegionState.UNSET self.hide() def get_safe_region(self): start, end = self.getRegion() if start < 0: start = 0 if end > DetektorContainer().get().data_count()-1: end = DetektorContainer().get().data_count() return trunc(start), trunc(end) def delete(self): """ Deletes data by cutting the region without keeping it """ start, end = self.get_safe_region() DetektorContainer().duplicate() DetektorContainer().get().cut(start, end) self.state = DetektorRegionState.UNSET self.hide() def copy(self): """ Copies the data """ start, end = self.get_safe_region() DetektorContainer().get().copy(start, end) self.state = DetektorRegionState.COPIED def cut(self): """ Cuts the data and hiding the region """ start, end = self.get_safe_region() DetektorContainer().duplicate() DetektorContainer().get().cut(start, end) self.state = DetektorRegionState.COPIED self.hide() def paste_end(self): DetektorContainer().duplicate() DetektorContainer().get().paste( DetektorContainer().get().data_count() ) self.unset() def paste_after(self): _, end = self.get_safe_region() DetektorContainer().duplicate() DetektorContainer().get().paste(end) self.unset() def paste_at(self): cursor_x = self._plot.cursorLine.getXPos() DetektorContainer().duplicate() DetektorContainer().get().paste(cursor_x) self.unset() def snap_to_x_labels(self): """Snaps the region boundaries to the nearest discrete X-axis values.""" min_x, max_x = self.getRegion() self.setRegion([int(round(min_x)), int(round(max_x))]) def display(self): """ Adds the region to the plot """ self._plot.graphWidget.addItem(self) def hide(self): """ Removes the region from the plot """ self._plot.graphWidget.removeItem(self)