From 210aaba92badad17a71b79a23f57f7d99905b26e Mon Sep 17 00:00:00 2001 From: Faerbit Date: Sun, 28 Nov 2021 17:22:39 +0100 Subject: [PATCH] stuff --- src/fime/data.py | 98 ++++++++++++++++-------- src/fime/report.py | 16 ++-- src/fime/util.py | 1 - src/fime/worklog.py | 180 ++++++++++++++++++++++++++++++++++---------- 4 files changed, 215 insertions(+), 80 deletions(-) diff --git a/src/fime/data.py b/src/fime/data.py index 4c8144d..19d39fd 100644 --- a/src/fime/data.py +++ b/src/fime/data.py @@ -2,7 +2,9 @@ import atexit import base64 import json import os +import time from collections.abc import MutableMapping +from copy import deepcopy from datetime import datetime, date, timedelta from threading import Thread, Event from typing import List, Tuple, Dict @@ -193,10 +195,11 @@ class LogCommentsData: month_str = pdate.strftime("%Y-%m") day_str = pdate.strftime("%d") month = self._data.setdefault(month_str, {}) + if month.setdefault(day_str, self._init_day)["log"] == encoded: + return # no changes month.setdefault(day_str, self._init_day)["log"] = encoded - # trigger save if necessary - if self._data[month_str] != month: - self._data[month_str] = month + # trigger save + self._data[month_str] = month def add_log_entry(self, task: str): now = datetime.now() @@ -241,7 +244,7 @@ class LogCommentsData: return dict() comment_data = self._data[month_str][day_str]["comments"] ret = dict() - for k, v in comment_data: + for k, v in comment_data.items(): k_dec = base64.b64decode(k.encode("utf-8")).decode("utf-8") v_dec = base64.b64decode(v.encode("utf-8")).decode("utf-8") ret[k_dec] = v_dec @@ -250,17 +253,18 @@ class LogCommentsData: def set_comments(self, pdate: date, comments: Dict[str, str]): self._ensure_format(pdate) encoded = dict() - for k, v in comments: + for k, v in comments.items(): k_enc = base64.b64encode(k.encode("utf-8")).decode("utf-8") v_enc = base64.b64encode(v.encode("utf-8")).decode("utf-8") encoded[k_enc] = v_enc month_str = pdate.strftime("%Y-%m") day_str = pdate.strftime("%d") month = self._data.setdefault(month_str, {}) + if month.setdefault(day_str, self._init_day)["comments"] == encoded: + return # no changes month.setdefault(day_str, self._init_day)["comments"] = encoded - # trigger save if necessary - if self._data[month_str] != month: - self._data[month_str] = month + # trigger save + self._data[month_str] = month class Log: @@ -293,10 +297,15 @@ class Log: return Worklog(self._data) +# TODO remove +dEV = False + + def summary(lcd: LogCommentsData, pdate: date) -> Tuple[Dict[str, timedelta], timedelta]: log = lcd.get_log(pdate) - if pdate == date.today(): - log.append((datetime.now(), "End")) + if dEV: + if pdate == date.today(): + log.append((datetime.now(), "End")) tasks_sums = {} total_sum = timedelta() for i, le in enumerate(log): @@ -318,15 +327,37 @@ def duration_to_str(duration: timedelta) -> str: return f"{dhours:02d}:{dmins:02d}" -class Report: +class PrevNextable: def __init__(self, data: LogCommentsData): self._data = data self._date = date.today() - self._not_log_len = 0 self._prev = None self._next = None self._update_prev_next() + def _update_prev_next(self): + self._prev, self._next = self._data.get_prev_next_avail(self._date) + + def prev_next_avail(self) -> Tuple[bool, bool]: + return self._prev is not None, self._next is not None + + def previous(self): + self._date = self._prev + self._update_prev_next() + + def next(self): + self._date = self._next + self._update_prev_next() + + def date(self) -> str: + return self._date.strftime("%Y-%m-%d") + + +class Report(PrevNextable): + def __init__(self, data: LogCommentsData): + super().__init__(data) + self._not_log_len = 0 + def report(self) -> Tuple[List[List[str]], int]: log = self._data.get_log(self._date) if self._date == date.today(): @@ -365,24 +396,29 @@ class Report: )) self._data.set_log(self._date, report) - def _update_prev_next(self): - self._prev, self._next = self._data.get_prev_next_avail(self._date) - def prev_next_avail(self): - return self._prev is not None, self._next is not None - - def previous(self): - self._date = self._prev - self._update_prev_next() - - def next(self): - self._date = self._next - self._update_prev_next() - - def date(self): - return self._date.strftime("%Y-%m-%d") - - -class Worklog: +class Worklog(PrevNextable): def __init__(self, data: LogCommentsData): - self._data = data + super().__init__(data) + self._worklog = [] + + @property + def worklog(self) -> List[List[str]]: + tasks_summary, total_sum = summary(self._data, self._date) + comments = self._data.get_comments(self._date) + self._worklog = [] + for task, duration in tasks_summary.items(): + self._worklog.append([task, comments.setdefault(task, ""), duration_to_str(duration)]) + self._worklog.append(["Total sum", "", duration_to_str(total_sum)]) + return deepcopy(self._worklog) + + @worklog.setter + def worklog(self, worklog: List[List[str]]): + log = self._data.get_log(self._date) + set_comments = dict() + for i, (task, comment, duration) in enumerate(worklog[:-1]): + set_comments[task] = comment + if self._worklog[i][0] != task: + log = list(map(lambda x: (x[0], x[1].replace(self._worklog[i][0], task)), log)) + self._data.set_comments(self._date, set_comments) + self._data.set_log(self._date, log) diff --git a/src/fime/report.py b/src/fime/report.py index 6fe5ffd..a5870ff 100644 --- a/src/fime/report.py +++ b/src/fime/report.py @@ -1,3 +1,5 @@ +from typing import List, Optional + try: from PySide6 import QtCore, QtGui, QtWidgets except ImportError: @@ -5,7 +7,7 @@ except ImportError: from datetime import datetime -from fime.data import Tasks +from fime.data import Tasks, Report from fime.util import get_screen_height, get_icon, EditStartedDetector @@ -25,8 +27,8 @@ class Report(QtWidgets.QDialog): def __init__(self, tasks: Tasks, parent, *args, **kwargs): super().__init__(parent, *args, **kwargs) - self._report = None - self._report_data = None + self._report: Optional[Report] = None + self._report_data: Optional[List[List[str]]] = None self._changing_items = False self._new_log_task = "" self._new_log_pos = -1 @@ -81,7 +83,7 @@ class Report(QtWidgets.QDialog): self.ok_button.setText("OK") self.ok_button.setIcon(get_icon("dialog-ok")) self.ok_button.pressed.connect(self._accept) - self.ok_button.setAutoDefault(True) + self.ok_button.setAutoDefault(False) blayout = QtWidgets.QHBoxLayout() blayout.addWidget(self.previous_button) @@ -95,7 +97,7 @@ class Report(QtWidgets.QDialog): layout.addLayout(blayout) self.setLayout(layout) - def set_data(self, data): + def set_data(self, data: Report): self._report = data self.update_title() self.refresh_table() @@ -137,9 +139,9 @@ class Report(QtWidgets.QDialog): ) def update_prev_next(self): - prev, _next = self._report.prev_next_avail() + prev, next_ = self._report.prev_next_avail() self.previous_button.setEnabled(prev) - self.next_button.setEnabled(_next) + self.next_button.setEnabled(next_) @QtCore.Slot() def del_log(self): diff --git a/src/fime/util.py b/src/fime/util.py index 99261e3..183ae59 100644 --- a/src/fime/util.py +++ b/src/fime/util.py @@ -35,6 +35,5 @@ class EditStartedDetector(QtWidgets.QStyledItemDelegate): def createEditor(self, parent, option, index): editor = super().createEditor(parent, option, index) editor.editingFinished.connect(self.editFinished) - editor.selectionChanged.connect(self.editFinished) self.editStarted.emit() return editor diff --git a/src/fime/worklog.py b/src/fime/worklog.py index 7cd89b3..ddfad23 100644 --- a/src/fime/worklog.py +++ b/src/fime/worklog.py @@ -1,6 +1,9 @@ import enum -from functools import reduce +from datetime import datetime +from functools import reduce, partial +from typing import Optional, List, Tuple +from fime.data import Worklog from fime.progressindicator import ProgressIndicator from fime.task_completer import TaskCompleter from fime.util import get_icon, get_screen_height, EditStartedDetector @@ -15,20 +18,20 @@ from fime.util import get_screen_width import fime.icons -class Worklog(QtWidgets.QDialog): +class WorklogDialog(QtWidgets.QDialog): class TabTable(QtWidgets.QTableWidget): def __init__(self, parent, *args, **kwargs): super().__init__(parent, *args, **kwargs) - def focusNextPrevChild(self, next): + def focusNextPrevChild(self, next_): if self.currentColumn() == 1: event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress, - QtCore.Qt.Key_Down if next else QtCore.Qt.Key_Up, + QtCore.Qt.Key_Down if next_ else QtCore.Qt.Key_Up, QtCore.Qt.NoModifier) self.keyPressEvent(event) if event.isAccepted(): return True - return super().focusNextPrevChild(next) + return super().focusNextPrevChild(next_) class ClickWidget(QtWidgets.QWidget): def __init__(self, item, table, parent, *args, **kwargs): @@ -64,18 +67,32 @@ class Worklog(QtWidgets.QDialog): self.text_changed.emit(self.document().toPlainText(), self.row) class TaskItemCompleter(EditStartedDetector): + edit_finished_row = QtCore.Signal(int) + def __init__(self, config, parent=None): super().__init__(parent) self.config = config + #self.row = -1 def createEditor(self, parent, option, index): editor = super().createEditor(parent, option, index) + #self.row = index.row() + #editor.editingFinished.connect(partial(self.edit_finished_row_target, index.row())) + #editor.editingFinished.connect(self.edit_finished_row_target) + #self.editFinished.connect(self.edit_finished_row_target) + #self.editFinished.connect(partial(self.edit_finished_row_target, index.row())) completer = TaskCompleter(self.config) completer.setFilterMode(QtCore.Qt.MatchFlag.MatchContains) completer.setCaseSensitivity(QtCore.Qt.CaseInsensitive) editor.setCompleter(completer) return editor + @QtCore.Slot() + def edit_finished_row_target(self, row): + #def edit_finished_row_target(self): + self.edit_finished_row.emit(row) + #self.edit_finished_row.emit(self.row) + class Status(enum.Enum): PROGRESS = enum.auto() OK = enum.auto() @@ -87,23 +104,27 @@ class Worklog(QtWidgets.QDialog): self.setWindowTitle("Worklog") self._changing_items = False - self._report_data = [] - self._status = [] + self._worklog: Optional[Worklog] = None + self._worklog_data: List[List[str]] = [] + self._status: List[Tuple[WorklogDialog.Status, str]] = [] self.row_height = None - self.focussed = False + self._focussed = False - self.tableWidget = Worklog.TabTable(self) + #self.tableWidget = WorklogDialog.TabTable(self) + self.tableWidget = QtWidgets.QTableWidget(self) self.tableWidget.setColumnCount(3) self.tableWidget.setHorizontalHeaderLabels(["Task", "Comment", "Duration"]) self.tableWidget.cellChanged.connect(self.cell_changed) - taskItemCompleter = Worklog.TaskItemCompleter(config, self) + taskItemCompleter = WorklogDialog.TaskItemCompleter(config, self) taskItemCompleter.editStarted.connect(self.disable_buttons) - taskItemCompleter.editFinished.connect(self.enable_ok) + taskItemCompleter.editFinished.connect(self.enable_buttons) + #taskItemCompleter.editFinished.connect(self.ensure_stati) + #taskItemCompleter.edit_finished_row.connect(self.update_status_view) self.tableWidget.setItemDelegateForColumn(0, taskItemCompleter) self.hheader = QtWidgets.QHeaderView(QtCore.Qt.Orientation.Horizontal) self.hheader.setMinimumSectionSize(10) self.tableWidget.setHorizontalHeader(self.hheader) - self.hheader.setSectionResizeMode(self.hheader.logicalIndex(0), QtWidgets.QHeaderView.Stretch) + self.hheader.setSectionResizeMode(self.hheader.logicalIndex(0), QtWidgets.QHeaderView.Fixed) self.hheader.setSectionResizeMode(self.hheader.logicalIndex(1), QtWidgets.QHeaderView.Fixed) self.hheader.resizeSection(self.hheader.logicalIndex(1), get_screen_width(self) * 0.2) self.hheader.setSectionResizeMode(self.hheader.logicalIndex(2), QtWidgets.QHeaderView.ResizeToContents) @@ -113,6 +134,18 @@ class Worklog(QtWidgets.QDialog): self.tableWidget.setVerticalHeader(self.vheader) self.tableWidget.setMaximumWidth(get_screen_width(self) * 0.8) + self.previous_button = QtWidgets.QPushButton() + self.previous_button.setText("Previous") + self.previous_button.setIcon(get_icon("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(get_icon("arrow-right")) + self.next_button.pressed.connect(self.next) + self.next_button.setAutoDefault(False) + self.upload_button = QtWidgets.QPushButton() self.upload_button.setText("Upload") self.upload_button.setIcon(get_icon("cloud-upload")) @@ -124,10 +157,12 @@ class Worklog(QtWidgets.QDialog): self.ok_button = QtWidgets.QPushButton() self.ok_button.setText("OK") self.ok_button.setIcon(get_icon("dialog-ok")) - #self.ok_button.pressed.connect(self._accept) - self.ok_button.setAutoDefault(True) + self.ok_button.pressed.connect(self._accept) + self.ok_button.setAutoDefault(False) alayout = QtWidgets.QHBoxLayout() + alayout.addWidget(self.previous_button) + alayout.addWidget(self.next_button) alayout.addSpacerItem(QtWidgets.QSpacerItem(1, 1, QtWidgets.QSizePolicy.Expanding)) alayout.addWidget(self.upload_button) alayout.addWidget(self.ok_button) @@ -137,64 +172,93 @@ class Worklog(QtWidgets.QDialog): layout.addLayout(alayout) self.setLayout(layout) - def set_data(self, data): - self._report_data = data - for i, _ in enumerate(self._report_data): - self._status.append((Worklog.Status.PROGRESS, "Fetching")) + def set_data(self, worklog: Worklog): + self._worklog = worklog self.refresh_table() + self.update_title() + self.update_prev_next() def update_status(self, row, new_status, status_text="Could not find specified issue"): self._status[row] = (new_status, status_text) self.update_status_view(row) - all_ok = reduce(lambda acc, it: acc and it[0] is Worklog.Status.OK, self._status, True) + all_ok = reduce(lambda acc, it: acc and it[0] is WorklogDialog.Status.OK, self._status, True) if all_ok: self.upload_button.setEnabled(True) + def save(self): + self._worklog.worklog = self._worklog_data + + def update_title(self): + self.setWindowTitle(f"Worklog {self._worklog.date()}") + def refresh_table(self): + self._worklog_data = self._worklog.worklog + for i, _ in enumerate(self._worklog_data[:-1]): + self._status.append((WorklogDialog.Status.PROGRESS, "Fetching")) self._changing_items = True if not self.row_height: self.tableWidget.setRowCount(1) item = QtWidgets.QTableWidgetItem("test") self.tableWidget.setItem(0, 0, item) self.row_height = self.tableWidget.rowHeight(0) - self.tableWidget.setRowCount(len(self._report_data)) + self.tableWidget.setRowCount(len(self._worklog_data)) - for row, _ in enumerate(self._report_data): - item0 = QtWidgets.QTableWidgetItem(self._report_data[row][0]) + for row, _ in enumerate(self._worklog_data): + item0 = QtWidgets.QTableWidgetItem(self._worklog_data[row][0]) self.tableWidget.setItem(row, 0, item0) - self.update_status_view(row) - text_edit = Worklog.TableTextEdit(self._report_data[row][1], row, self) - text_edit.textChanged.connect(self.on_resize) - text_edit.setFrameStyle(QtWidgets.QFrame.NoFrame) - text_edit.text_changed.connect(self.text_edit_changed) - self.tableWidget.setCellWidget(row, 1, text_edit) - if row == 0 and not self.focussed: - text_edit.setFocus() - self.focussed = True - item2 = QtWidgets.QTableWidgetItem(self._report_data[row][2]) + if row == len(self._worklog_data) - 1: + self.tableWidget.removeCellWidget(row, 0) + item0.setFlags(item0.flags() & QtCore.Qt.ItemIsEnabled) + item1 = QtWidgets.QTableWidgetItem("") + self.tableWidget.setItem(row, 1, item1) + item1.setFlags(item1.flags() & QtCore.Qt.ItemIsEnabled) + else: + self.update_status_view(row) + text_edit = WorklogDialog.TableTextEdit(self._worklog_data[row][1], row, self) + text_edit.textChanged.connect(self.on_resize) + text_edit.setFrameStyle(QtWidgets.QFrame.NoFrame) + text_edit.text_changed.connect(self.text_edit_changed) + self.tableWidget.setCellWidget(row, 1, text_edit) + if row == 0 and not self._focussed: + text_edit.setFocus() + self._focussed = True + item2 = QtWidgets.QTableWidgetItem(self._worklog_data[row][2]) self.tableWidget.setItem(row, 2, item2) item2.setFlags(item2.flags() & QtCore.Qt.ItemIsEnabled) self._changing_items = False self.tableWidget.resizeColumnToContents(0) - desired_width = self.tableWidget.columnWidth(0) + self.tableWidget.columnWidth(1) + self.tableWidget.columnWidth(2) + self.tableWidget.setColumnWidth(0, self.tableWidget.columnWidth(0) + self.row_height) + desired_width = self.tableWidget.columnWidth(0) + self.tableWidget.columnWidth(1) + self.tableWidget.columnWidth(2) + 4 self.tableWidget.setMinimumWidth(min(desired_width, self.tableWidget.maximumWidth())) + self.adjustSize() screen = QtGui.QGuiApplication.screenAt(self.pos()) x = ((screen.size().width() - self.tableWidget.width()) // 2) + screen.geometry().left() y = ((screen.size().height() - self.height()) // 2) + screen.geometry().top() self.move(x, y) + @QtCore.Slot() + def ensure_stati(self): + print("ensure_stati") + for i in range(len(self._worklog_data)-1): + cw = self.tableWidget.cellWidget(i, 0) + if type(cw) is not WorklogDialog.ClickWidget: + print(f"ensure_stati: {self.tableWidget.item(i, 0).text()}") + self.update_status_view(i) + #print(cw) + def update_status_view(self, row): - if self._status[row][0] is Worklog.Status.PROGRESS: + print(self.tableWidget.item(row, 0).text()) + if self._status[row][0] is WorklogDialog.Status.PROGRESS: icon = ProgressIndicator(self) icon.setMaximumSize(QtCore.QSize(self.row_height * 0.75, self.row_height * 0.75)) icon.setAnimationDelay(70) icon.startAnimation() else: - if self._status[row][0] is Worklog.Status.OK: + if self._status[row][0] is WorklogDialog.Status.OK: item_name = "dialog-ok" - elif self._status[row][0] is Worklog.Status.ERROR: + elif self._status[row][0] is WorklogDialog.Status.ERROR: item_name = "edit-delete-remove" icon = QtWidgets.QLabel() icon.setPixmap(get_icon(item_name).pixmap(self.row_height * 0.75, self.row_height * 0.75)) @@ -204,7 +268,7 @@ class Worklog(QtWidgets.QDialog): layout.setContentsMargins(self.row_height * 0.1, self.row_height * 0.1, self.row_height * 0.1, self.row_height * 0.1) - wdgt = Worklog.ClickWidget(self.tableWidget.item(row, 0), self.tableWidget, self) + wdgt = WorklogDialog.ClickWidget(self.tableWidget.item(row, 0), self.tableWidget, self) wdgt.setLayout(layout) self.tableWidget.setCellWidget(row, 0, wdgt) @@ -214,21 +278,55 @@ class Worklog(QtWidgets.QDialog): @QtCore.Slot() def disable_buttons(self): + self.previous_button.setEnabled(False) + self.next_button.setEnabled(False) self.upload_button.setEnabled(False) self.ok_button.setEnabled(False) @QtCore.Slot() - def enable_ok(self): + def enable_buttons(self): self.ok_button.setEnabled(True) + self.update_prev_next() + + def update_prev_next(self): + prev, next_ = self._worklog.prev_next_avail() + self.previous_button.setEnabled(prev) + self.next_button.setEnabled(next_) + + @QtCore.Slot() + def previous(self): + self.save() + self._worklog.previous() + self.update_title() + self._focussed = False + self.refresh_table() + self.update_prev_next() + + @QtCore.Slot() + def next(self): + self.save() + self._worklog.next() + self.update_title() + self._focussed = False + self.refresh_table() + self.update_prev_next() @QtCore.Slot() def cell_changed(self, row, _): + #print(f"{datetime.now()} cell_changed: {self._changing_items}") if self._changing_items: return - self._report_data[row][0] = self.tableWidget.item(row, 0).text() - self.update_status(row, Worklog.Status.PROGRESS, "Fetching") + print(f"{datetime.now()} cell_changed") + self._worklog_data[row][0] = self.tableWidget.item(row, 0).text() + self.save() + self.update_status(row, WorklogDialog.Status.PROGRESS, "Fetching") self.refresh_table() @QtCore.Slot() def text_edit_changed(self, text, row): - self._report_data[row][1] = text + self._worklog_data[row][1] = text + + @QtCore.Slot() + def _accept(self): + self.save() + self.accept()