From 1ddd9488fd333fd50ada584bf098470e79cf4413 Mon Sep 17 00:00:00 2001
From: Oscar Gustafsson <oscar.gustafsson@gmail.com>
Date: Wed, 17 May 2023 17:49:25 +0200
Subject: [PATCH] Add move_operation_asap and move_operation_alap

---
 b_asic/gui_utils/icons.py              |  3 ++
 b_asic/schedule.py                     | 50 ++++++++++++++++++++++++++
 b_asic/scheduler_gui/operation_item.py | 22 ++++++++++--
 3 files changed, 73 insertions(+), 2 deletions(-)

diff --git a/b_asic/gui_utils/icons.py b/b_asic/gui_utils/icons.py
index 85fa6c81..4bd348a8 100644
--- a/b_asic/gui_utils/icons.py
+++ b/b_asic/gui_utils/icons.py
@@ -33,6 +33,9 @@ ICONS = {
     'full-screen-exit': 'mdi6.fullscreen-exit',
     'warning': 'fa5s.exclamation-triangle',
     'port-numbers': 'fa5s.hashtag',
+    'swap': 'fa5s.arrows-alt-v',
+    'asap': 'fa5s.fast-backward',
+    'alap': 'fa5s.fast-forward',
 }
 
 
diff --git a/b_asic/schedule.py b/b_asic/schedule.py
index db668d10..a4655e07 100644
--- a/b_asic/schedule.py
+++ b/b_asic/schedule.py
@@ -644,6 +644,56 @@ class Schedule:
         self._start_times[graph_id] = new_start
         return self
 
+    def move_operation_alap(self, graph_id: GraphID) -> "Schedule":
+        """
+        Move an operation as late as possible in the schedule.
+
+        This is basically the same as::
+
+            schedule.move_operation(graph_id, schedule.forward_slack(graph_id))
+
+        but Outputs will only move to the end of the schedule.
+
+        Parameters
+        ----------
+        graph_id : GraphID
+            The graph id of the operation to move.
+        """
+        op = self._sfg.find_by_id(graph_id)
+        if op is None:
+            raise ValueError(f"No operation with graph_id {graph_id!r} in schedule")
+        if isinstance(op, Output):
+            self.move_operation(
+                graph_id, self.schedule_time - self._start_times[graph_id]
+            )
+        else:
+            self.move_operation(graph_id, self.forward_slack(graph_id))
+        return self
+
+    def move_operation_asap(self, graph_id: GraphID) -> "Schedule":
+        """
+        Move an operation as soon as possible in the schedule.
+
+        This is basically the same as::
+
+            schedule.move_operation(graph_id, -schedule.backward_slack(graph_id))
+
+        but Inputs will only move to the start of the schedule.
+
+        Parameters
+        ----------
+        graph_id : GraphID
+            The graph id of the operation to move.
+        """
+        op = self._sfg.find_by_id(graph_id)
+        if op is None:
+            raise ValueError(f"No operation with graph_id {graph_id!r} in schedule")
+        if isinstance(op, Input):
+            self.move_operation(graph_id, -self._start_times[graph_id])
+        else:
+            self.move_operation(graph_id, -self.backward_slack(graph_id))
+        return self
+
     def _remove_delays_no_laps(self) -> None:
         """Remove delay elements without updating laps. Used when loading schedule."""
         delay_list = self._sfg.find_by_type_name(Delay.type_name())
diff --git a/b_asic/scheduler_gui/operation_item.py b/b_asic/scheduler_gui/operation_item.py
index c84529fa..e6d823e0 100644
--- a/b_asic/scheduler_gui/operation_item.py
+++ b/b_asic/scheduler_gui/operation_item.py
@@ -21,8 +21,10 @@ from qtpy.QtWidgets import (
     QMenu,
 )
 
-# B-ASIC
 from b_asic.graph_component import GraphID
+
+# B-ASIC
+from b_asic.gui_utils.icons import get_icon
 from b_asic.operation import Operation
 from b_asic.scheduler_gui._preferences import (
     OPERATION_EXECUTION_TIME_INACTIVE,
@@ -305,10 +307,20 @@ class OperationItem(QGraphicsItemGroup):
 
     def _open_context_menu(self):
         menu = QMenu()
-        swap = QAction("Swap")
+        swap = QAction(get_icon('swap'), "Swap")
         menu.addAction(swap)
         swap.setEnabled(self._operation.is_swappable)
         swap.triggered.connect(self._swap_io)
+        slacks = self._parent._schedule.slacks(self._operation.graph_id)
+        asap = QAction(get_icon('asap'), "Move as soon as possible")
+        asap.triggered.connect(self._move_asap)
+        asap.setEnabled(slacks[0] > 0)
+        menu.addAction(asap)
+        alap = QAction(get_icon('alap'), "Move as late as possible")
+        alap.triggered.connect(self._move_alap)
+        alap.setEnabled(slacks[1] > 0)
+        menu.addAction(alap)
+        menu.addSeparator()
         execution_time_plot = QAction(
             f"Show execution times for {self._operation.type_name()}"
         )
@@ -321,3 +333,9 @@ class OperationItem(QGraphicsItemGroup):
 
     def _execution_time_plot(self, event=None) -> None:
         self._parent._execution_time_plot(self._operation.type_name())
+
+    def _move_asap(self, event=None):
+        self._parent._schedule.move_operation_asap(self._operation.graph_id)
+
+    def _move_alap(self, event=None):
+        self._parent._schedule.move_operation_alap(self._operation.graph_id)
-- 
GitLab