from PySide2 import QtCore, QtGui, QtWidgets from datetime import datetime from data import Tasks class Report(QtWidgets.QDialog): class TaskItemCompleter(QtWidgets.QStyledItemDelegate): def __init__(self, tasks: Tasks, parent=None): super().__init__(parent) self._tasks = tasks def createEditor(self, parent, *_): editor = QtWidgets.QLineEdit(parent) completer = QtWidgets.QCompleter(self._tasks.all_tasks, parent) completer.setCaseSensitivity(QtCore.Qt.CaseInsensitive) editor.setCompleter(completer) return editor def __init__(self, tasks: Tasks, parent, *args, **kwargs): super().__init__(parent, *args, **kwargs) self._tasks = tasks self._report = None self._report_data = None self._changing_items = False self._new_log_task = "" self._new_log_pos = -1 self._edit_len = -1 self.setWindowTitle("Report") self.tableWidget = QtWidgets.QTableWidget() self.tableWidget.verticalHeader().hide() self.tableWidget.setColumnCount(3) self.tableWidget.setHorizontalHeaderLabels(["Task", "Start time", "Duration"]) self.tableWidget.cellChanged.connect(self.cell_changed) self.tableWidget.setItemDelegateForColumn(0, Report.TaskItemCompleter(self._tasks, self)) self.header = QtWidgets.QHeaderView(QtCore.Qt.Orientation.Horizontal) self.tableWidget.setHorizontalHeader(self.header) self.previous_button = QtWidgets.QPushButton() self.previous_button.setText("Previous") self.previous_button.setIcon(QtGui.QIcon.fromTheme("arrow-left")) self.previous_button.pressed.connect(self.previous) self.previous_button.setAutoDefault(False) self.next_button = QtWidgets.QPushButton() self.next_button.setText("Next") self.next_button.setIcon(QtGui.QIcon.fromTheme("arrow-right")) self.next_button.pressed.connect(self.next) self.next_button.setAutoDefault(False) new_button = QtWidgets.QPushButton() new_button.setText("New item") new_button.setIcon(QtGui.QIcon.fromTheme("list-add")) new_button.pressed.connect(self.new_log) new_button.setAutoDefault(False) del_button = QtWidgets.QPushButton() del_button.setText("Delete item") del_button.setIcon(QtGui.QIcon.fromTheme("list-remove")) del_button.pressed.connect(self.del_log) del_button.setAutoDefault(False) ok_button = QtWidgets.QPushButton() ok_button.setText("OK") ok_button.setIcon(QtGui.QIcon.fromTheme("dialog-ok")) ok_button.pressed.connect(self._accept) ok_button.setAutoDefault(True) blayout = QtWidgets.QHBoxLayout() blayout.addWidget(self.previous_button) blayout.addWidget(self.next_button) blayout.addWidget(new_button) blayout.addWidget(del_button) blayout.addWidget(ok_button) layout = QtWidgets.QVBoxLayout(self) layout.addWidget(self.tableWidget) layout.addLayout(blayout) self.setLayout(layout) def set_data(self, data): self._report = data self.update_title() self.refresh_table() self.update_prev_next() def save(self): self._report.save(self._report_data) def update_title(self): self.setWindowTitle(f"Report {self._report.date()}") def refresh_table(self): self._report_data, self._edit_len = self._report.report() self.tableWidget.setRowCount(len(self._report_data)) self._changing_items = True for row, _ in enumerate(self._report_data): item0 = QtWidgets.QTableWidgetItem(self._report_data[row][0]) self.tableWidget.setItem(row, 0, item0) item1 = QtWidgets.QTableWidgetItem(self._report_data[row][1]) self.tableWidget.setItem(row, 1, item1) item2 = QtWidgets.QTableWidgetItem(self._report_data[row][2]) self.tableWidget.setItem(row, 2, item2) item2.setFlags(item2.flags() & QtCore.Qt.ItemIsEnabled) if row >= self._edit_len: item0.setFlags(item1.flags() & QtCore.Qt.ItemIsEnabled) if row > self._edit_len: item1.setFlags(item0.flags() & QtCore.Qt.ItemIsEnabled) self._changing_items = False self.tableWidget.resizeColumnsToContents() min_width = 0 for i in range(3): min_width += self.header.sectionSize(i) self.tableWidget.setMinimumWidth(min_width * 1.33) self.header.setSectionResizeMode(QtWidgets.QHeaderView.Stretch) if self.tableWidget.rowCount() > 4: if hasattr(self.tableWidget, "screen"): screen_height = self.tableWidget.screen().size().height() else: print("unable to detect screen height falling back to default value of 1080") screen_height = 1080 self.tableWidget.setMinimumHeight(min((self.tableWidget.rowCount() + 2) * self.tableWidget.rowHeight(0), screen_height * 0.8)) def update_prev_next(self): prev, _next = self._report.prev_next_avail() self.previous_button.setEnabled(prev) self.next_button.setEnabled(_next) @QtCore.Slot() def del_log(self): row = self.tableWidget.currentRow() if row > len(self._report_data) - 4: return del self._report_data[row] self.save() self.refresh_table() @QtCore.Slot() def new_log(self): after = min(self.tableWidget.currentItem().row(), self._edit_len-1) + 1 self._new_log_pos = after self.tableWidget.insertRow(after) self.tableWidget.setCurrentCell(after, 0) item = QtWidgets.QTableWidgetItem() self._changing_items = True self.tableWidget.setItem(after, 0, item) self._changing_items = False self.tableWidget.editItem(item) @QtCore.Slot() def _accept(self): self.save() self.accept() @QtCore.Slot() def previous(self): self.save() self._report.previous() self.update_title() self.refresh_table() self.update_prev_next() @QtCore.Slot() def next(self): self.save() self._report.next() self.update_title() self.refresh_table() self.update_prev_next() @QtCore.Slot() def cell_changed(self, row, column): if self._changing_items: return if column == 2: self.tableWidget.item(row, column).setText(self._report_data[row][column]) return item = self.tableWidget.item(row, column) if column == 0: if self._new_log_pos == row: self._new_log_task = self.tableWidget.item(row, column).text() or "Unnamed" return else: self._report_data[row][column] = item.text() or "Unnamed" if column == 1: try: new_time = datetime.strptime(item.text(), "%H:%M").time() except ValueError: item.setText(self._report_data[row][column]) return if self._new_log_pos == row: self._report_data.insert(row, [self._new_log_task, new_time.strftime("%H:%M"), ""]) else: self._report_data[row][column] = new_time.strftime("%H:%M") self._new_log_pos = -1 self._new_log_task = "" self.save() self.refresh_table()