from PyQt6.QtWidgets import QWidget, QVBoxLayout, QLabel, QScrollArea, QTableView, QFormLayout from PyQt6.QtCore import QAbstractTableModel, QModelIndex, Qt import numpy as np from cpdctrl.led_script import LedScript import time import datetime timedelta = [("d", 24*3600), ("h", 3600), ("m", 60), ("s", 1)] def duration_to_string(duration: float) -> str: """ Convert a duration in seconds to a string of the form "d h m s" where only the largest units are included. Parameters ---------- duration: float Duration in seconds. Returns ------- String representation of the duration. """ include = False s = "" sign = 1 if duration > 0 else -1 time_left = abs(int(duration)) for i, (unit, unit_seconds) in enumerate(timedelta): t = int(time_left / unit_seconds) if t != 0 or include or unit == "s": s += f"{sign*t:02}{unit} " include = True time_left %= unit_seconds return s.strip() class TimeLeft(QWidget): def __init__(self, parent=None): super().__init__(parent) self.t_end = None self.t_start = None self.setLayout(QFormLayout()) self.setLayout(QFormLayout()) self.w_time_passed = QLabel("N.A.") self.layout().addRow(QLabel("Time passed:"), self.w_time_passed) self.w_time_left = QLabel("N.A.") self.layout().addRow(QLabel("Time left:"), self.w_time_left) self.w_end_time = QLabel("N.A.") self.layout().addRow(QLabel("End time:"), self.w_end_time) def reset(self): self.t_start = None self.t_end = None self.w_time_passed.setText("N.A.") self.w_time_left.setText("N.A.") self.w_end_time.setText("N.A.") def set_start_end_time(self, t_start: float, t_end: float): """ Set the end time Parameters ---------- t_start The start time in seconds since epoch t_end The end time in seconds since epoch """ self.t_start = t_start self.t_end = t_end self.w_end_time.setText(datetime.datetime.fromtimestamp(t_end).strftime("%Y-%m-%d %H:%M:%S")) def update_time(self, t_now: float): """ Update the time left display Parameters ---------- t_now The current time in seconds since epoch """ if self.t_end is None: raise RuntimeError("Update called before end time was set") self.w_time_left.setText(duration_to_string(self.t_end - t_now)) self.w_time_passed.setText(duration_to_string(t_now - self.t_start)) class LedScriptTableModel(QAbstractTableModel): """ A table model for the led script. It only allows editing values that are in the future """ def __init__(self, led_script: LedScript, parent=None): super().__init__(parent) self.led_script = led_script self.indices = [i[0] for i in LedScript.ARRAY_DTYPE] self.dt = 0 def rowCount(self, parent=None): return self.led_script.script[self.indices[0]].shape[0] def columnCount(self, parent=None): return len(self.indices) def data(self, index: QModelIndex, role: int): if role == Qt.ItemDataRole.DisplayRole: return str(self.led_script.script[self.indices[index.column()]][index.row()]) def headerData(self, section: int, orientation: Qt.Orientation, role: int): if role == Qt.ItemDataRole.DisplayRole: if orientation == Qt.Orientation.Horizontal: return self.indices[section] else: return str(section) def setData(self, index: QModelIndex, value, role: int): if role == Qt.ItemDataRole.EditRole: self.led_script.script[self.indices[index.column()]][index.row()] = value return True return False def addRows(self, rowCount: int, parent: QModelIndex): self.beginInsertRows(parent, self.rowCount(), self.rowCount() + rowCount - 1) for i in self.indices: np.append(self.led_script.script[i], np.zeros((rowCount, self.led_script.script[i].shape[1])), axis=0) self.endInsertRows() def removeRows(self, row: int, count: int, parent: QModelIndex): self.beginRemoveRows(parent, row, row + count - 1) rows = [i for i in range(row, row+count)] for i in self.indices: np.delete(self.led_script.script[i], rows, axis=0) self.endRemoveRows() def flags(self, index: QModelIndex): flags = Qt.ItemFlag.ItemIsSelectable if index.row() >= self.led_script.get_current_index(self.dt): flags |= Qt.ItemFlag.ItemIsEnabled if index.column() in [0, 2]: flags |= Qt.ItemFlag.ItemIsEditable return flags def update_time(self, t_now: float): self.dt = self.led_script.get_dt(t_now) class LedScriptViewer(QWidget): def __init__(self, led_script: LedScript, parent=None): super().__init__(parent) self.led_script = None self.model = LedScriptTableModel(led_script, self) self.l_vbox = QVBoxLayout() self.setLayout(self.l_vbox) self.l_vbox.addWidget(QLabel("You may edit the dt and led values of future rows.")) self.w_scroll = QScrollArea() self.l_vbox.addWidget(self.w_scroll) self.w_table = QTableView(self) self.w_table.setModel(self.model) self.w_table.show() self.w_scroll.setWidget(self.w_table) self.w_scroll.setWidgetResizable(True) self.w_scroll.setFixedHeight(200) self.w_time_left = TimeLeft(self) self.l_vbox.addWidget(self.w_time_left) self.l_vbox.addStretch(1) def set_script(self, led_script: LedScript): self.model = LedScriptTableModel(led_script, self) self.w_table.setModel(self.model) self.w_table.show() def set_relative_time(self, t: float): pass raise NotImplementedError() def update_time(self, t_now: float): self.model.update_time(t_now) self.w_time_left.update_time(t_now) self.w_table.update()