def _add_time_point(self, center_x, center_y, time_point): """Add a single time point item.""" x = center_x - (self.TIMEPOINT_DIAMETER / 2) y = center_y - (self.TIMEPOINT_DIAMETER / 2) # Create the acutal time point item time_point_item = QGraphicsEllipseItem(0, 0, self.TIMEPOINT_DIAMETER, self.TIMEPOINT_DIAMETER) # The used color is the strongest one of the FRM II colors. time_point_item.setBrush(QBrush(QColor(0x00, 0x71, 0xbb))) time_point_item.setPen(QPen(0)) self.scene().addItem(time_point_item) time_point_item.setPos(x, y) # place the time point item above the timeline and the selection item time_point_item.setZValue(2) # Create the label of the time point showing the time in the # defined strftime format on the right side of the time point item. label = QGraphicsTextItem(time_point.strftime(self.STRFTIME_FMT)) label.setFont(QFont('Monospace')) label_height = label.boundingRect().height() # minor height adjustment label_y = y - label_height / 6 self.scene().addItem(label) label.setPos(x + self.SELECTION_DIAMETER + self.LABEL_SPACING, label_y) # store references to the item and the timepoint in the same dict # to be able to use it for forward and reverse lookup self._time_point_items[time_point] = time_point_item self._time_point_items[time_point_item] = time_point
class DetTable(TableBase): """Class to display the detector including shielding and detector tube.""" _color = QColor('#ff66ff') def __init__(self, x, y, size=20, parent=None, scene=None): TableBase.__init__(self, x, y, size, parent, scene) self._halowidth = max(size / 4, 10) self._halo = Halo(x, y, size, self._halowidth, self, scene) self._tuberadius = size / 5 p = QPointF(self._tuberadius, self._tuberadius) s = QSizeF(2 * self._tuberadius, 2 * self._tuberadius) self._tube = QGraphicsEllipseItem(QRectF(-p, s), self) self._tube.setPen(QPen(QColor('black'), 3)) self._tube.setBrush(QBrush(QColor('white'))) self._tube.setZValue(20)
class TimelineWidget(QGraphicsView): """General widget to display timeline with a list of ordered timepoints. A timepoint is selectable via click and the timepointSelected signal can be used to react to it.""" timepointSelected = pyqtSignal(object) # datetime.datetime object # general layout and design parameters TIMEPOINT_DIAMETER = 30 SELECTION_DIAMETER = 40 TIMEPOINT_SPACING = 50 TIMELINE_WIDTH = 5 LABEL_SPACING = 20 MARGIN_HORIZONTAL = 5 STRFTIME_FMT = '%H:%M:%S\n%Y-%m-%d' def __init__(self, parent=None): QGraphicsView.__init__(self, QGraphicsScene(), parent) self.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) self.setRenderHints(QPainter.Antialiasing | QPainter.SmoothPixmapTransform) # margins set to 0 to simplify calculations self.setContentsMargins(0, 0, 0, 0) self.setViewportMargins(0, 0, 0, 0) # full viewport updates required to avoid optical double selections # caused by scrolling self.setViewportUpdateMode(QGraphicsView.FullViewportUpdate) self._time_points = [] self._time_point_items = {} self._selection_item = None # start with at least one time point (current time) to be able # to reuse size calculation methods for the inital size self.setTimePoints([datetime.now()]) @property def time_points(self): """Sorted list of all timepoints (sorted new to old)""" return self._time_points @property def selected_time_point(self): """Get the selected timeout as datetime object. None if there is no selection.""" if self._selection_item is None: return None return self._time_point_items[self._selection_item] @property def previous_time_point(self): """Get the timepoint before (older than) the currently selected one as datetime object. None if there is no selection""" try: index = self._time_points.index(self.selected_time_point) return self._time_points[index + 1] except (ValueError, IndexError): return None def resizeEvent(self, event): """Clear and readd all items on resize. Avoids complex scaling.""" self.scene().clear() self.setTimePoints(self._time_points) def setTimePoints(self, time_points): """Sets and list of datetime objects as timepoints, sets up all necessary graphics items and adjusts sizes.""" self.scene().clear() self._selection_item = None # store the timepoints sorted from new to old self._time_points = list(reversed(sorted(time_points))) self._time_point_items = {} # draw the timeline self._timeline = self._add_timeline() # draw the time points self._add_time_points() # update the scene size and a slightly larger widget size to avoid # superfluous scrolling (and displaying of scroll bars) size = self.scene().itemsBoundingRect().size() self.setSceneRect(0, 0, size.width(), size.height() - 5) if time_points: self.setMinimumWidth(size.width() * 1.2) self.setMaximumWidth(size.width() * 1.2) def mousePressEvent(self, event): """Handle mouse press events to support item selection.""" item = self.itemAt(event.pos()) if item in self._time_point_items: self._select_item(item) return QGraphicsView.mousePressEvent(self, event) def _select_item(self, item): """Select the given item by drawing a colored circle beneath the selected item (so it looks like a ring around it. Also emits the timepointSelected signal.""" # The selection_item used to signal the selection of a timepoint # is always the same and is only moved. if self._selection_item is None: self._selection_item = QGraphicsEllipseItem( 0, 0, self.SELECTION_DIAMETER, self.SELECTION_DIAMETER) # The used color is a cubical to the time point color self._selection_item.setBrush(QBrush(QColor(0x70, 0xbb, 0x00))) self._selection_item.setPen(QPen(0)) self.scene().addItem(self._selection_item) # center position of the timepoint circle center_x = item.pos().x() + self.TIMEPOINT_DIAMETER / 2 center_y = item.pos().y() + self.TIMEPOINT_DIAMETER / 2 # move selection item self._selection_item.setPos(center_x - self.SELECTION_DIAMETER / 2, center_y - self.SELECTION_DIAMETER / 2) # store the selection_item like a timepoint item (using the timepoint # of the selected item) self._time_point_items[self._selection_item] = \ self._time_point_items[item] # emit signal at the end to ensure a valid internal state before # anything can react to it self.timepointSelected.emit(self._time_point_items[item]) def _add_timeline(self): """Draw the timeline.""" # height is either the necessary space to display all items or the # maximal available display size, so it's looks nicely in larger # windows and enables scrolling in smaller ones. height = self.TIMEPOINT_DIAMETER * len(self._time_points) height += self.TIMEPOINT_SPACING * len(self._time_points) height = max(height, self.viewport().height()) # draw the timeline left aligned with enough space to draw the items # and the selection ring. x = self.MARGIN_HORIZONTAL + (self.SELECTION_DIAMETER / 2) # position the line on the left side of the item item = QGraphicsLineItem(0, 0, 0, height) # The used color for the timeline is the lightest one of the FRM II # colors item.setPen(QPen(QBrush(QColor(0xa3, 0xc1, 0xe7)), self.TIMELINE_WIDTH)) self.scene().addItem(item) # move the whole item to the desired timeline position item.setPos(x, 0) return item def _add_time_points(self): """Add all time point items.""" if not self._time_points: return timeline_pos = self._timeline.pos() timeline_size = self._timeline.boundingRect().size() height = timeline_size.height() # time points are always equally distributed on the timeline spacing = height / float(len(self._time_points)) center_x = timeline_pos.x() # add half of the items spacing on the top and bottom of the timeline start = timeline_pos.y() - spacing / 2 for i, entry in enumerate(self._time_points): self._add_time_point(center_x, start + spacing * (i + 1), entry) def _add_time_point(self, center_x, center_y, time_point): """Add a single time point item.""" x = center_x - (self.TIMEPOINT_DIAMETER / 2) y = center_y - (self.TIMEPOINT_DIAMETER / 2) # Create the acutal time point item time_point_item = QGraphicsEllipseItem(0, 0, self.TIMEPOINT_DIAMETER, self.TIMEPOINT_DIAMETER) # The used color is the strongest one of the FRM II colors. time_point_item.setBrush(QBrush(QColor(0x00, 0x71, 0xbb))) time_point_item.setPen(QPen(0)) self.scene().addItem(time_point_item) time_point_item.setPos(x, y) # place the time point item above the timeline and the selection item time_point_item.setZValue(2) # Create the label of the time point showing the time in the # defined strftime format on the right side of the time point item. label = QGraphicsTextItem(time_point.strftime(self.STRFTIME_FMT)) label.setFont(QFont('Monospace')) label_height = label.boundingRect().height() # minor height adjustment label_y = y - label_height / 6 self.scene().addItem(label) label.setPos(x + self.SELECTION_DIAMETER + self.LABEL_SPACING, label_y) # store references to the item and the timepoint in the same dict # to be able to use it for forward and reverse lookup self._time_point_items[time_point] = time_point_item self._time_point_items[time_point_item] = time_point