Exemple #1
0
    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
Exemple #2
0
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