From bf33578006bcaff2334a2ea88b369c3671460d51 Mon Sep 17 00:00:00 2001
From: Andreas Bolin <2535580+andbo467@users.noreply.github.com>
Date: Sat, 30 Jul 2022 05:27:36 +0200
Subject: [PATCH] workspace dump

---
 ...ics_axis_item.py => graphics_axes_item.py} | 145 +++++++++++-------
 .../scheduler-gui/graphics_component_item.py  |   7 +-
 b_asic/scheduler-gui/graphics_graph_event.py  | 133 ++++++++++++----
 b_asic/scheduler-gui/graphics_graph_item.py   |  65 ++++++--
 .../scheduler-gui/graphics_timeline_item.py   | 116 ++++++++++++++
 b_asic/scheduler-gui/main_window.py           |  11 +-
 b_asic/scheduler-gui/main_window.ui           |   6 +
 7 files changed, 376 insertions(+), 107 deletions(-)
 rename b_asic/scheduler-gui/{graphics_axis_item.py => graphics_axes_item.py} (55%)
 create mode 100644 b_asic/scheduler-gui/graphics_timeline_item.py

diff --git a/b_asic/scheduler-gui/graphics_axis_item.py b/b_asic/scheduler-gui/graphics_axes_item.py
similarity index 55%
rename from b_asic/scheduler-gui/graphics_axis_item.py
rename to b_asic/scheduler-gui/graphics_axes_item.py
index 875b7fc8..712e6cfb 100644
--- a/b_asic/scheduler-gui/graphics_axis_item.py
+++ b/b_asic/scheduler-gui/graphics_axes_item.py
@@ -1,15 +1,15 @@
 #!/usr/bin/env python3
 # -*- coding: utf-8 -*-
-"""B-ASIC Scheduler-gui Graphics Axis Item Module.
+"""B-ASIC Scheduler-gui Graphics Axes Item Module.
 
-Contains the scheduler-gui GraphicsAxisItem class for drawing and maintain the axis in a graph.
+Contains the scheduler-gui GraphicsAxesItem class for drawing and maintain the axes in a graph.
 """
 from operator import contains
 import os
 import sys
 from typing     import Any, Optional
 from pprint     import pprint
-from typing     import Any, AnyStr, Generic, Protocol, TypeVar, Union, Optional, overload, Final, final, Dict
+from typing     import Any, Union, Optional, overload, Final, final, Dict, List
 # from typing_extensions import Self, Final, Literal, LiteralString, TypeAlias, final
 import numpy    as np
 from copy       import deepcopy
@@ -36,90 +36,118 @@ from qtpy.QtCore    import (
 
 # B-ASIC
 import logger
-from graphics_graph_event   import GraphicsGraphEvent
+from graphics_timeline_item import GraphicsTimelineItem
 
 
 
-class GraphicsAxisItem(QGraphicsItemGroup):
-    """A class to represent axis in a graph."""
+class GraphicsAxesItem(QGraphicsItemGroup):
+    """A class to represent axes in a graph."""
     _scale:         float = 1.0
     """Static, changed from MainWindow."""
-    _width_padding: float
     _width:         float
+    _width_padding: float
     _height:        float
     _dy_height:     float
     _x_indent:      float
-    _axis:          Dict[str, Union[QGraphicsItemGroup, QGraphicsLineItem]]
+    _axes:          Dict[str, Union[QGraphicsItemGroup, QGraphicsLineItem]]
+    _event_items:   List[QGraphicsItem]
+    _timeline:      GraphicsTimelineItem
+    
 
     def __init__(self, width: float, height: float, x_indent: float  = 0.2, parent: Optional[QGraphicsItem] = None):
-        """Constructs a GraphicsAxisItem. 'parent' is passed to QGraphicsItemGroup's constructor."""
+        """Constructs a GraphicsAxesItem. 'parent' is passed to QGraphicsItemGroup's constructor."""
         super().__init__(parent)
 
-        self._dy_height = 5/self._scale
-        self._axis = {}
+        self._width = width
         self._width_padding = 0.6
-        self._width = width + self._width_padding
+        self._padded_width = width + self._width_padding
         self._height = height
+        self._dy_height = 5/self._scale
         self._x_indent = x_indent
+        self._axes = {}
+        self._event_items = []
+        # self._timeline = GraphicsTimelineItem()
+        # self._event_items.append(self._timeline)
 
-        self._make_axis()
+        self._make_axes()
 
 
     def clear(self) -> None:
-        """Sets all children's parent to 'None' and delete the axis."""
-        keys = list(self._axis.keys())
+        """Sets all children's parent to 'None' and delete the axes."""
+        # self._timeline.setParentItem(None)
+        self._event_items = []
+        keys = list(self._axes.keys())
         for key in keys:
-            print(f'clear() key: {key}')
-            self._axis[key].setParentItem(None)
-            del self._axis[key]
+            self._axes[key].setParentItem(None)
+            del self._axes[key]
     
 
     @property
     def width(self) -> float:
         """Get or set the current x-axis width. Setting the width to a new
-        value will update the axis automatically."""
+        value will update the axes automatically."""
         return self._width
     @width.setter
     def width(self, width: float) -> None:
-        if self._width != width + self._width_padding:
-            self._width = width + self._width_padding
-            self.update_axis()
+        if self._width != width:
+            self.update_axes(width = width)
 
     @property
     def height(self) -> float:
         """Get or set the current y-axis height. Setting the height to a new
-        value will update the axis automatically."""
+        value will update the axes automatically."""
         return self._height
     @height.setter
     def height(self, height: float) -> None:
         if self._height != height:
-            self._height = height
-            self.update_axis()
+            self.update_axes(height = height)
     
     @property
     def x_indent(self) -> float:
         """Get or set the current x-axis indent. Setting the indent to a new
-        value will update the axis automatically."""
+        value will update the axes automatically."""
         return self._x_indent
     @x_indent.setter
     def x_indent(self, x_indent: float) -> None:
         if self._x_indent != x_indent:
-            self._x_indent = x_indent
-            self.update_axis()
+            self.update_axes(x_indent = x_indent)
+    
+    @property
+    def event_items(self) -> List[QGraphicsItem]:
+        """Returnes a list of objects, that receives events."""
+        return self._event_items
+    
+    @property
+    def timeline(self) -> GraphicsTimelineItem:
+        return self._timeline
+
+    def _register_event_item(self, item: QGraphicsItem) -> None:
+        """Register an object that receives events."""
+        # item.setFlag(QGraphicsItem.ItemIsMovable)       # mouse move events
+        # item.setAcceptHoverEvents(True)                 # mouse hover events
+        # item.setAcceptedMouseButtons(Qt.LeftButton)     # accepted buttons for movements
+        self._event_items.append(item)
 
 
-    def update_axis(self, width: Optional[float] = None, height: Optional[float] = None, x_indent: Optional[float] = None) -> None:
-        """Updates the current axis with the new 'width', 'height' and 'x_indent'. If any of the
+    def update_axes(self, width: Optional[float] = None, height: Optional[float] = None, x_indent: Optional[float] = None) -> None:
+        """Updates the current axes with the new 'width', 'height' and 'x_indent'. If any of the
         parameters is omitted, the stored value will be used."""
-        if width is not None:    self._width = width + self._width_padding
+        if width is not None:
+            self._width = width
+            self._padded_width = width + self._width_padding
         if height is not None:   self._height = height
         if x_indent is not None: self._x_indent = x_indent
-        self.clear()                    # make sure the group is empty
-        self._make_axis()               # make new axis
+        print(width is not None or height is not None or x_indent is not None)
+        if (width is not None
+            or height is not None
+            or x_indent is not None):
+            self.clear()
+            self._make_axes()
 
 
-    def _make_axis(self) -> None:
-        """Makes new axis out of the stored attributes."""
+    def _make_axes(self) -> None:
+        """Makes new axes out of the stored attributes."""
+        # self.prepareGeometryChange()
         ## define pencils
         pen = QPen()
         pen.setWidthF(2/self._scale)
@@ -128,10 +156,10 @@ class GraphicsAxisItem(QGraphicsItemGroup):
         ledger_pen.setWidthF(0)         # 0 = cosmetic pen 1px width
         
         ## x-axis
-        self._axis['x'] = QGraphicsItemGroup()
-        line = QGraphicsLineItem(0, 0, self.width, 0)
+        self._axes['x'] = QGraphicsItemGroup()
+        line = QGraphicsLineItem(0, 0, self._padded_width, 0)
         line.setPen(pen)
-        self._axis['x'].addToGroup(line)
+        self._axes['x'].addToGroup(line)
         # x-axis arrow
         arrow_size = 8/self._scale
         p0 = QPointF(0, sin(pi/6) * arrow_size)
@@ -141,51 +169,58 @@ class GraphicsAxisItem(QGraphicsItemGroup):
         arrow = QGraphicsPolygonItem(polygon)
         arrow.setPen(pen)
         arrow.setBrush(QBrush(Qt.SolidPattern))
-        arrow.setPos(self.width, 0)
-        self._axis['x'].addToGroup(arrow)
+        arrow.setPos(self._padded_width, 0)
+        self._axes['x'].addToGroup(arrow)
         # x-axis scale
         x_scale = []
         x_scale_labels = []
         x_ledger = []
-        self._axis['x_ledger'] = QGraphicsItemGroup()
-        for i in range(int(self.width) + 1):
+        self._axes['x_ledger'] = QGraphicsItemGroup()
+        for i in range(int(self._padded_width) + 1):
             x_pos = QPointF(self.x_indent + i, 0)
             # vertical x-scale
             x_scale.append(QGraphicsLineItem(0, 0, 0, 0.05))
             x_scale[i].setPen(pen)
             x_scale[i].setPos(x_pos)
-            self._axis['x'].addToGroup(x_scale[i])
+            self._axes['x'].addToGroup(x_scale[i])
             # numbers
             x_scale_labels.append(QGraphicsSimpleTextItem(str(i)))
             x_scale_labels[i].setScale(x_scale_labels[i].scale() / self._scale)
             center = x_pos - self.mapFromItem(x_scale_labels[i], x_scale_labels[i].boundingRect().center())
             x_scale_labels[i].setPos(center + QPointF(0, 0.2))
-            self._axis['x'].addToGroup(x_scale_labels[i])
+            self._axes['x'].addToGroup(x_scale_labels[i])
             # vertical x-ledger
-            x_ledger.append(QGraphicsLineItem(0, 0, 0, self.height))
-            if i == int(self.width):                                       # last line is special
+            if i == int(self.width):                                       # last line is a timeline
                 ledger_pen.setWidthF(2/self._scale)
                 ledger_pen.setStyle(Qt.DashLine)
                 ledger_pen.setColor(Qt.black)
+                # self._timeline.setLine(0, 0, 0, self.height)
+                # x_ledger.append(self._timeline)
+                self._timeline = GraphicsTimelineItem(0, 0, 0, self.height)
+                self._timeline.set_text_scale(1.05/self._scale)
+                x_ledger.append(self._timeline)
+                self._register_event_item(x_ledger[i])
+            else:
+                x_ledger.append(QGraphicsLineItem(0, 0, 0, self.height))
             x_ledger[i].setPen(ledger_pen)
             x_ledger[i].setPos(x_pos)
-            self._axis['x_ledger'].addToGroup(x_ledger[i])
+            self._axes['x_ledger'].addToGroup(x_ledger[i])
         # x-axis label
         label = QGraphicsSimpleTextItem('time')
         label.setScale(label.scale() / self._scale)
         center = self.mapFromItem(arrow, arrow.boundingRect().center())     # =center of arrow
         center -= self.mapFromItem(label, label.boundingRect().center())    # -center of label
         label.setPos(center + QPointF(0, 0.2))                              # move down under arrow
-        self._axis['x'].addToGroup(label)
+        self._axes['x'].addToGroup(label)
 
         # y-axis
-        self._axis['y'] = QGraphicsLineItem(0, 0, 0, self.height + self._dy_height)
-        self._axis['y'].setPen(pen)
+        self._axes['y'] = QGraphicsLineItem(0, 0, 0, self.height + self._dy_height)
+        self._axes['y'].setPen(pen)
         
         # put it all together
-        self._axis['x_ledger'].setPos(0, self._dy_height)
-        self.addToGroup(self._axis['x_ledger'])
-        self._axis['x'].setPos(0, self.height + self._dy_height)
-        self.addToGroup(self._axis['x'])
-        self.addToGroup(self._axis['y'])
+        self._axes['x_ledger'].setPos(0, self._dy_height)
+        self.addToGroup(self._axes['x_ledger'])
+        self._axes['x'].setPos(0, self.height + self._dy_height)
+        self.addToGroup(self._axes['x'])
+        self.addToGroup(self._axes['y'])
         
\ No newline at end of file
diff --git a/b_asic/scheduler-gui/graphics_component_item.py b/b_asic/scheduler-gui/graphics_component_item.py
index e151dd64..d7cce989 100644
--- a/b_asic/scheduler-gui/graphics_component_item.py
+++ b/b_asic/scheduler-gui/graphics_component_item.py
@@ -97,7 +97,12 @@ class GraphicsComponentItem(QGraphicsItemGroup):
             self.clear()
             self._height = height
             self._make_component()
-
+            
+    
+    @property
+    def event_items(self) -> List[QGraphicsItem]:
+        """Returnes a list of objects, that receives events."""
+        return [self]
 
     def _make_component(self) -> None:
         """Makes a new component out of the stored attributes."""
diff --git a/b_asic/scheduler-gui/graphics_graph_event.py b/b_asic/scheduler-gui/graphics_graph_event.py
index 38e460e4..4819db35 100644
--- a/b_asic/scheduler-gui/graphics_graph_event.py
+++ b/b_asic/scheduler-gui/graphics_graph_event.py
@@ -37,7 +37,9 @@ from qtpy.QtCore    import (
     QPoint, QPointF)
 
 from abc import ABC
-from graphics_component_item import GraphicsComponentItem
+from graphics_component_item    import GraphicsComponentItem
+from graphics_axes_item         import GraphicsAxesItem
+from graphics_timeline_item     import GraphicsTimelineItem
 
 
 
@@ -45,26 +47,34 @@ from graphics_component_item import GraphicsComponentItem
 # class GraphicsGraphEvent(ABC):
 class GraphicsGraphEvent(QGraphicsItem):
     """Event filter and handlers for GraphicsGraphItem"""
-    _components:    List[GraphicsComponentItem]
+    _axes:          GraphicsAxesItem
     _current_pos:   QPointF
+    _delta_time:    int
 
 
     #################
     #### Filters ####
     #################
-    def installSceneEventFilters(self) -> None:
-        """Installs an event filter for 'item' on 'self', causing
-        all events for 'item' to first pass through 'self's
-        sceneEventFilter() function."""
-        for item in self._components:
+    @overload
+    def installSceneEventFilters(self, filterItems: QGraphicsItem) -> None: ...
+    @overload
+    def installSceneEventFilters(self, filterItems: List[QGraphicsItem]) -> None: ...
+    def installSceneEventFilters(self, filterItems) -> None:
+        """Installs an event filter for 'filterItems' on 'self', causing all events
+        for 'filterItems' to first pass through 'self's sceneEventFilter()
+        function. 'filterItems' can be one object or a list of objects."""
+        for item in filterItems:
             item.installSceneEventFilter(self)
-        # self.setFiltersChildEvents(True)    # default false
-        
-    def removeSceneEventFilters(self) -> None:
-        """Removes an event filter on 'item' from 'self'."""
-        for item in self._components:
+    
+    @overload
+    def removeSceneEventFilters(self, filterItems: QGraphicsItem) -> None: ...
+    @overload
+    def removeSceneEventFilters(self, filterItems: List[QGraphicsItem]) -> None: ...
+    def removeSceneEventFilters(self, filterItems) -> None:
+        """Removes an event filter on 'filterItems' from 'self'. 'filterItems' can
+        be one object or a list of objects."""
+        for item in filterItems:
             item.removeSceneEventFilter(self)
-        # self.setFiltersChildEvents(False)
         
     
     def sceneEventFilter(self, item: QGraphicsItem, event: QEvent) -> bool:
@@ -73,7 +83,7 @@ class GraphicsGraphEvent(QGraphicsItem):
         the event chain."""
         handler = None
 
-        if isinstance(item, GraphicsComponentItem):
+        if isinstance(item, GraphicsComponentItem):     # one component
             switch = {
                 QEvent.FocusIn:                         self.comp_focusInEvent,
                 QEvent.GraphicsSceneContextMenu:        self.comp_contextMenuEvent,
@@ -90,12 +100,24 @@ class GraphicsGraphEvent(QGraphicsItem):
                 QEvent.GraphicsSceneMouseDoubleClick:   self.comp_mouseDoubleClickEvent,
                 QEvent.GraphicsSceneWheel:              self.comp_wheelEvent
             }
-
             handler = switch.get(event.type())
+            
+        elif isinstance(item, GraphicsTimelineItem):       # the timeline
+            print(f'{type(item).__name__} received {event.type()}')
+            switch = {
+                QEvent.GraphicsSceneHoverEnter:         self.timeline_hoverEnterEvent,
+                QEvent.GraphicsSceneHoverLeave:         self.timeline_hoverLeaveEvent,
+                QEvent.GraphicsSceneMouseMove:          self.timeline_mouseMoveEvent,
+                QEvent.GraphicsSceneMousePress:         self.timeline_mousePressEvent,
+                QEvent.GraphicsSceneMouseRelease:       self.timeline_mouseReleaseEvent,
+            }
+            handler = switch.get(event.type())
+            
         else:
-            raise TypeError
+            raise TypeError(f"Received an unexpected event '{event.type()}' "
+                            f"from an '{type(item).__name__}' object.")
         
-        if handler:
+        if handler is not None:
             handler(event)
             return True
         return False    # returns False if event is ignored and pass through event to its child
@@ -124,32 +146,36 @@ class GraphicsGraphEvent(QGraphicsItem):
     def comp_hoverMoveEvent(self, event: QGraphicsSceneHoverEvent) -> None: ...
     
     def comp_hoverLeaveEvent(self, event: QGraphicsSceneHoverEvent) -> None:
-        """Changes the cursor to ArrowCursor when leaving an object."""
-        self.setCursor(QCursor(Qt.ArrowCursor))
+        """Unsets the cursor to default cursor."""
+        self.unsetCursor()
 
     def comp_mouseMoveEvent(self, event: QGraphicsSceneMouseEvent) -> None:
         """Set the position of the graphical element in the graphic scene, 
-        translate coordinates of the cursor within the graphic element 
-        in the coordinate system of the parent object."""
+        translate coordinates of the cursor within the graphic element in the
+        coordinate system of the parent object. The object can only move
+        horizontally in x-axis scale steps."""
         # Qt.DragMoveCursor
         # button = event.button()
         item = self.scene().mouseGrabberItem()
         dx = (item.mapToParent(event.pos()) - self._current_pos).x()
         if dx > 0.505:
             pos = item.x() + 1.0
-            if self.is_valid_pos(pos):
-                self.prepareGeometryChange()
+            if self.is_component_valid_pos(pos):
+                # self.prepareGeometryChange()
                 item.setX(pos)
                 self._current_pos.setX(self._current_pos.x() + 1.0)
         elif dx < -0.505:
             pos = item.x() - 1.0
-            if self.is_valid_pos(pos):
-                self.prepareGeometryChange()
+            if self.is_component_valid_pos(pos):
+                # self.prepareGeometryChange()
                 item.setX(pos)
                 self._current_pos.setX(self._current_pos.x() - 1.0)
     
     def comp_mousePressEvent(self, event: QGraphicsSceneMouseEvent) -> None:
-        """Changes the cursor to ClosedHandCursor when grabbing an object."""
+        """Changes the cursor to ClosedHandCursor when grabbing an object and
+        stores the current position in item's parent coordinates. 'event' will
+        by default be accepted, and this item is then the mouse grabber. This
+        allows the item to receive future move, release and double-click events."""
         item = self.scene().mouseGrabberItem()
         self._current_pos = item.mapToParent(event.pos())
         self.setCursor(QCursor(Qt.ClosedHandCursor))
@@ -158,12 +184,61 @@ class GraphicsGraphEvent(QGraphicsItem):
     def comp_mouseReleaseEvent(self, event: QGraphicsSceneMouseEvent) -> None:
         """Changes the cursor to OpenHandCursor when releasing an object."""
         self.setCursor(QCursor(Qt.OpenHandCursor))
-        event.accept()
 
     def comp_mouseDoubleClickEvent(self, event: QGraphicsSceneMouseEvent) -> None: ...
     def comp_wheelEvent(self, event: QGraphicsSceneWheelEvent) -> None: ...
     
     ###############################################
-    #### Event Handlers: GraphicsComponentItem ####
+    #### Event Handlers: GraphicsLineTem       ####
     ###############################################
-    
\ No newline at end of file
+    def timeline_hoverEnterEvent(self, event: QGraphicsSceneHoverEvent) -> None:
+        """Changes the cursor to SizeHorCursor when hovering an object."""
+        self.setCursor(QCursor(Qt.SizeHorCursor))
+    
+    def timeline_hoverLeaveEvent(self, event: QGraphicsSceneHoverEvent) -> None:
+        """Unsets the cursor to default cursor."""
+        self.unsetCursor()
+
+    def timeline_mouseMoveEvent(self, event: QGraphicsSceneMouseEvent) -> None:
+        """Set the position of the graphical element in the graphic scene, 
+        translate coordinates of the cursor within the graphic element in the
+        coordinate system of the parent object. The object can only move
+        horizontally in x-axis scale steps."""
+        # Qt.DragMoveCursor
+        # button = event.button()
+        item = self.scene().mouseGrabberItem()
+        dx = (item.mapToParent(event.pos()) - self._current_pos).x()
+        if dx > 0.505:
+            pos = item.x() + 1.0
+            if self.is_valid_delta_time(self._delta_time + 1):
+                # self.prepareGeometryChange()
+                item.setX(pos)
+                self._current_pos.setX(self._current_pos.x() + 1.0)
+                self._delta_time += 1
+                self._axes.timeline.set_text(self._delta_time)
+        elif dx < -0.505:
+            pos = item.x() - 1.0
+            if self.is_valid_delta_time(self._delta_time - 1):
+                # self.prepareGeometryChange()
+                item.setX(pos)
+                self._current_pos.setX(self._current_pos.x() - 1.0)
+                self._delta_time -= 1
+                self._axes.timeline.set_text(self._delta_time)
+                
+    
+    def timeline_mousePressEvent(self, event: QGraphicsSceneMouseEvent) -> None:
+        """Stores the current position in item's parent coordinates. 'event' will
+        by default be accepted, and this item is then the mouse grabber. This
+        allows the item to receive future move, release and double-click events."""
+        item = self.scene().mouseGrabberItem()
+        self._current_pos = item.mapToParent(event.pos())
+        self._delta_time = 0
+        self._axes.timeline.set_text(self._delta_time)
+        self._axes.timeline.show_label()
+        event.accept()
+    
+    def timeline_mouseReleaseEvent(self, event: QGraphicsSceneMouseEvent) -> None:
+        """Updates the schedule time."""
+        if self._delta_time != 0:
+            self.set_schedule_time(self._delta_time)
+        self._axes.timeline.hide_label()
\ No newline at end of file
diff --git a/b_asic/scheduler-gui/graphics_graph_item.py b/b_asic/scheduler-gui/graphics_graph_item.py
index f2605558..684dd1bb 100644
--- a/b_asic/scheduler-gui/graphics_graph_item.py
+++ b/b_asic/scheduler-gui/graphics_graph_item.py
@@ -42,61 +42,90 @@ from b_asic.schedule            import Schedule
 from b_asic.graph_component     import GraphComponent
 from b_asic.special_operations  import Input, Output
 from graphics_component_item    import GraphicsComponentItem
-from graphics_axis_item         import GraphicsAxisItem
+from graphics_axes_item         import GraphicsAxesItem
 from graphics_graph_event       import GraphicsGraphEvent
 
 
 class GraphicsGraphItem(QGraphicsItemGroup, GraphicsGraphEvent):
     """A class to represent a graph in a QGraphicsScene. This class is a
     subclass of QGraphicsItemGroup and contains the objects, axes from
-    GraphicsAxisItem, as well as components from GraphicsComponentItem. It
+    GraphicsAxesItem, as well as components from GraphicsComponentItem. It
     also inherits from GraphicsGraphEvent, which acts as a filter for events
     to GraphicsComponentItem objects."""
     _schedule:          Schedule
-    _axis:              GraphicsAxisItem
+    _axes:              GraphicsAxesItem
     _components:        List[GraphicsComponentItem]
     _components_height: float
     _x_axis_indent:     float
+    _event_items:       List[QGraphicsItem]
 
     
     def __init__(self, schedule: Schedule, parent: Optional[QGraphicsItem] = None):
         """Constructs a GraphicsGraphItem. 'parent' is passed to QGraphicsItemGroup's constructor."""
         super().__init__(parent)
         self._schedule = deepcopy(schedule)
-        self._axis = None
+        self._axes = None
         self._components = []
         self._components_height = 0.0
         self._x_axis_indent = 0.2
+        self._event_items = []
 
         self._make_graph()
         
     def clear(self) -> None:
         """Sets all children's parent to 'None' and delete the children objects."""
+        self._event_items = []
         for item in self.childItems():
             item.setParentItem(None)
             del item
     
-
-    def is_valid_pos(self, pos: float) -> bool:
-        """Takes in a component position and returns true if the component's new position is valid, false otherwise."""
+    def is_component_valid_pos(self, pos: float) -> bool:
+        """Takes in a component position and returns true if the component's new
+        position is valid, false otherwise."""
         # TODO: implement
         # item = self.scene().mouseGrabberItem()
         if pos < 0:
             return False
         return True
 
+    def is_valid_delta_time(self, delta_time: int) -> bool:
+        """Takes in a delta time and returns true if the new schedule time is valid
+        , false otherwise."""
+        # TODO: implement
+        # item = self.scene().mouseGrabberItem()
+        assert self.schedule is not None , "No schedule installed."
+        return self.schedule.schedule_time + delta_time >= self.schedule.get_max_end_time()
+        
+    
+    def set_schedule_time(self, delta_time: int) -> None:
+        """Set the schedule time and redraw the graph."""
+        assert self.schedule is not None , "No schedule installed."
+        self.schedule.set_schedule_time(self.schedule.schedule_time + delta_time)
+        scene = self.scene()
+        if scene is not None:
+            self.removeSceneEventFilters(self._axes.event_items)
+        self._axes.update_axes(width = self._axes.width + delta_time)
+        if scene is not None:
+            self.installSceneEventFilters(self._axes.event_items)
+            # print(f'self._axes.event_items {self._axes.event_items}')
+        # print(f'set_schedule_time({delta_time})')
         
     @property
     def schedule(self) -> Schedule:
         return self._schedule
     
     @property
-    def axis(self) -> GraphicsAxisItem:
-        return self._axis
+    def axes(self) -> GraphicsAxesItem:
+        return self._axes
+    
+    @property
+    def components(self) -> list[GraphicsComponentItem]:
+        return self._components
     
     @property
-    def items(self) -> list[GraphicsComponentItem]:
-        return self._components.childItems()
+    def event_items(self) -> List[QGraphicsItem]:
+        """Returnes a list of objects, that receives events."""
+        return self._event_items
     
     def _make_graph(self) -> None:
         """Makes a new graph out of the stored attributes."""
@@ -112,16 +141,18 @@ class GraphicsGraphItem(QGraphicsItemGroup, GraphicsGraphEvent):
                 component.setPos(self._x_axis_indent + op_start_time, self._components_height)
                 self._components.append(component)
                 self._components_height += component.height
+                self._event_items += component.event_items
         self._components_height += spacing
 
-        # build axis
+        # build axes
         schedule_time = self.schedule.schedule_time
-        self._axis = GraphicsAxisItem(schedule_time, self._components_height, self._x_axis_indent)
-        self._axis.width = schedule_time
+        self._axes = GraphicsAxesItem(schedule_time, self._components_height, self._x_axis_indent)
+        self._event_items += self._axes.event_items
+        # self._axes.width = schedule_time
 
-        # add axis and components
-        self.addToGroup(self._axis)
-        # self._axis.update_axis(schedule_time - 2, self._components_height, self._x_axis_indent)
+        # add axes and components
+        self.addToGroup(self._axes)
+        # self._axes.update_axes(schedule_time - 2, self._components_height, self._x_axis_indent)
         for component in self._components:
             self.addToGroup(component)
         # self.addToGroup(self._components)
\ No newline at end of file
diff --git a/b_asic/scheduler-gui/graphics_timeline_item.py b/b_asic/scheduler-gui/graphics_timeline_item.py
new file mode 100644
index 00000000..982ef750
--- /dev/null
+++ b/b_asic/scheduler-gui/graphics_timeline_item.py
@@ -0,0 +1,116 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""B-ASIC Scheduler-gui Graphics Timeline Item Module.
+
+Contains the a scheduler-gui GraphicsTimelineItem class for drawing and maintain the timeline in a graph.
+"""
+from operator import contains
+import os
+import sys
+from typing     import Any, Optional
+from pprint     import pprint
+from typing     import Any, Union, Optional, overload, Final, final, Dict, List
+# from typing_extensions import Self, Final, Literal, LiteralString, TypeAlias, final
+import numpy    as np
+from copy       import deepcopy
+from math       import cos, sin, pi
+
+import qtpy
+from qtpy import QtCore
+from qtpy import QtGui
+from qtpy import QtWidgets
+
+# QGraphics and QPainter imports
+from qtpy.QtCore    import (
+    Qt, QObject, QRect, QRectF, QPoint, QSize, QSizeF, QByteArray, qAbs, QLineF)
+from qtpy.QtGui     import (
+    QPaintEvent, QPainter, QPainterPath, QColor, QBrush, QPen, QFont, QPolygon, QIcon, QPixmap,
+    QLinearGradient, QTransform, QPolygonF)
+from qtpy.QtWidgets import (
+    QGraphicsView, QGraphicsScene, QGraphicsWidget,
+    QGraphicsLayout, QGraphicsLinearLayout, QGraphicsGridLayout, QGraphicsLayoutItem, QGraphicsAnchorLayout,
+    QGraphicsItem, QGraphicsItemGroup, QGraphicsPathItem, QGraphicsLineItem, QGraphicsTextItem, QGraphicsRectItem,
+    QStyleOptionGraphicsItem, QWidget, QGraphicsObject, QGraphicsSimpleTextItem, QGraphicsPolygonItem)
+from qtpy.QtCore    import (
+    QPoint, QPointF)
+
+# B-ASIC
+import logger
+
+
+
+class GraphicsTimelineItem(QGraphicsLineItem):
+    """A class to represent the timeline in GraphicsAxesItem."""
+    
+    # _scale:             float
+    _delta_time_label:  QGraphicsTextItem
+    
+    @overload
+    def __init__(self, line: QLineF, parent: Optional[QGraphicsItem] = None) -> None:
+        """Constructs a GraphicsTimelineItem out of 'line'. 'parent' is passed to
+        QGraphicsLineItem's constructor."""
+        ...
+    @overload
+    def __init__(self, parent:Optional[QGraphicsItem] = None) -> None:
+        """Constructs a GraphicsTimelineItem. 'parent' is passed to
+        QGraphicsLineItem's constructor."""
+        ...
+    @overload
+    def __init__(self, x1: float, y1: float, x2: float, y2: float, parent:Optional[QGraphicsItem] = None) -> None:
+        """Constructs a GraphicsTimelineItem from (x1, y1) to (x2, y2). 'parent' is
+        passed to QGraphicsLineItem's constructor."""
+        ...
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+            
+        self.setFlag(QGraphicsItem.ItemIsMovable)       # mouse move events
+        self.setAcceptHoverEvents(True)                 # mouse hover events
+        self.setAcceptedMouseButtons(Qt.LeftButton)     # accepted buttons for movements
+        
+        self._delta_time_label = QGraphicsTextItem()
+        self._delta_time_label.hide()
+        self._delta_time_label.setScale(1.05/75)        # TODO: dont hardcode scale
+        self._delta_time_label.setParentItem(self)
+        x_pos = - self._delta_time_label.mapRectToParent(self._delta_time_label.boundingRect()).width()/2
+        y_pos = 1.05 * self.line().dy()
+        self._delta_time_label.setPos(x_pos, y_pos)
+        # pen = QPen(Qt.black)
+        # self._delta_time_label.setPen(pen)
+        
+        
+    # @property
+    # def label(self) -> None:
+    #     return self._delta_time_label
+    
+    def set_text(self, number: int) -> None:
+        """Set the label text to 'number'."""
+        # self.prepareGeometryChange()
+        self._delta_time_label.setPlainText(f'( {number:+} )')
+        self._delta_time_label.setX(- self._delta_time_label.mapRectToParent(self._delta_time_label.boundingRect()).width()/2)
+        
+    # def set_text_pen(self, pen: QPen) -> None:
+    #     """Set the label pen to 'pen'."""
+    #     self._delta_time_label.setPen(pen)
+        
+    # def set_label_visible(self, visible: bool) -> None:
+    #     """If visible is True, the item is made visible. Otherwise, the item is
+    #     made invisible"""
+    #     self._delta_time_label.setVisible(visible)
+    
+    def show_label(self) -> None:
+        """Show the label (label are not visible by default). This convenience
+        function is equivalent to calling set_label_visible(True)."""
+        self._delta_time_label.show()
+    
+    def hide_label(self) -> None:
+        """Hide the label (label are not visible by default). This convenience
+        function is equivalent to calling set_label_visible(False)."""
+        self._delta_time_label.hide()
+        
+    def set_text_scale(self, scale: float) -> None:
+        self._delta_time_label.setScale(scale)
+
+    @property
+    def event_items(self) -> List[QGraphicsItem]:
+        """Returnes a list of objects, that receives events."""
+        return [self]
diff --git a/b_asic/scheduler-gui/main_window.py b/b_asic/scheduler-gui/main_window.py
index f3e03486..a62996bc 100644
--- a/b_asic/scheduler-gui/main_window.py
+++ b/b_asic/scheduler-gui/main_window.py
@@ -42,7 +42,7 @@ from qtpy.QtWidgets import (
 import logger
 from b_asic.schedule            import Schedule
 from graphics_graph_item        import GraphicsGraphItem
-from graphics_axis_item         import GraphicsAxisItem
+from graphics_axes_item         import GraphicsAxesItem
 from graphics_component_item    import GraphicsComponentItem
 
 # if sys.version_info >= (3, 9):
@@ -179,7 +179,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
         self.view.setScene(self._scene)
         self.view.scale(self._scale, self._scale)
         GraphicsComponentItem._scale = self._scale
-        GraphicsAxisItem._scale = self._scale
+        GraphicsAxesItem._scale = self._scale
         self._scene.changed.connect(self.shrink_scene_to_min_size)
         
     
@@ -259,7 +259,8 @@ class MainWindow(QMainWindow, Ui_MainWindow):
 
         self._graph = GraphicsGraphItem(schedule)
         self._scene.addItem(self._graph)
-        self._graph.installSceneEventFilters()
+        self._graph.installSceneEventFilters(self._graph.event_items)
+
         
         # graph.prepareGeometryChange()
         # graph.setPos(200, 20)
@@ -277,8 +278,8 @@ class MainWindow(QMainWindow, Ui_MainWindow):
         #     for component in graph.items:
         #         self._scene.addRect(component.mapRectToScene(component.boundingRect() - m), pen)
         #     pen.setColor(Qt.red)
-        #     for axis in graph.axis.childItems():
-        #         self._scene.addRect(axis.mapRectToScene(axis.boundingRect() - m), pen)
+        #     for axis in graph.axes.childItems():
+        #         self._scene.addRect(axis.mapRectToScene(axes.boundingRect() - m), pen)
         #     pen.setColor(Qt.green)
         #     # self._scene.addRect(self._scene.itemsBoundingRect() - m, pen)
             
diff --git a/b_asic/scheduler-gui/main_window.ui b/b_asic/scheduler-gui/main_window.ui
index 0b8c564c..030acc47 100644
--- a/b_asic/scheduler-gui/main_window.ui
+++ b/b_asic/scheduler-gui/main_window.ui
@@ -53,9 +53,15 @@
        <number>0</number>
       </property>
       <widget class="QGraphicsView" name="view">
+       <property name="alignment">
+        <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
+       </property>
        <property name="renderHints">
         <set>QPainter::HighQualityAntialiasing|QPainter::TextAntialiasing</set>
        </property>
+       <property name="viewportUpdateMode">
+        <enum>QGraphicsView::FullViewportUpdate</enum>
+       </property>
       </widget>
       <widget class="QTableWidget" name="info_table">
        <property name="editTriggers">
-- 
GitLab