aboutsummaryrefslogtreecommitdiff
path: root/src/gui/tasks/table/widget.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/gui/tasks/table/widget.py')
-rw-r--r--src/gui/tasks/table/widget.py263
1 files changed, 225 insertions, 38 deletions
diff --git a/src/gui/tasks/table/widget.py b/src/gui/tasks/table/widget.py
index 95ebe44..0a8d216 100644
--- a/src/gui/tasks/table/widget.py
+++ b/src/gui/tasks/table/widget.py
@@ -1,54 +1,173 @@
-from PyQt5 import QtWidgets
+from PyQt5 import QtWidgets, QtCore, QtGui
from PyQt5.QtCore import Qt
+from typing import List, Tuple
+import time
+import math
-import db.tasks
+from model import difficulty, priority
+from model.difficulty import Difficulty
+from model.priority import Priority
+from model.tag import Tag
+from model.task import Task
+from model.task_tag import TaskTag
+import database
+import db.tags
+import db.task_tags
+import gui.color
+import gui.signal
+import gui.tasks.dialog
+import gui.tasks.duration
+import gui.tasks.signal
import gui.tasks.signal
import gui.tasks.table.menu
-import gui.tasks.table.model
-import gui.tasks.dialog
-from model.task import Task, ValidTaskForm
+import service.tasks
+import util.array
+import util.range
-class Widget(QtWidgets.QTableView):
+header_labels = ["Age", "Name", "Duration", "Difficulty", "Priority", "Tag"]
- def __init__(self, database, parent, add_task_signal):
- super().__init__(parent)
+class Widget(QtWidgets.QTableWidget):
+ def __init__(
+ self,
+ parent,
+ on_show: gui.signal.Reload,
+ add_task_signal: gui.tasks.signal.AddTask):
- self._database = database
- self._update_task_signal = gui.tasks.signal.UpdateTask()
+ super().__init__(parent)
- tasks = db.tasks.get(self._database.cursor())
- table_model = gui.tasks.table.model.TableModel(tasks)
+ self.init_state()
+ self.sort()
- self.setModel(table_model)
- self.sortByColumn(
- gui.tasks.table.model.default_sort[0],
- gui.tasks.table.model.default_sort[1])
- self.setSortingEnabled(True)
self.setSelectionBehavior(QtWidgets.QTableView.SelectRows)
- self.horizontalHeader().setStretchLastSection(True)
- self.resizeColumns()
+ self.init_header()
+ self.setRowCount(len(self._tasks))
+ self.setColumnCount(len(header_labels))
+ self.setColumnWidth(1, 500)
- self.doubleClicked.connect(lambda index: self.on_double_click(index.row()))
+ self.update_view()
+ self.horizontalHeader().setStretchLastSection(True)
# Menu
self.setContextMenuPolicy(Qt.CustomContextMenu)
- self.customContextMenuRequested.connect(lambda position: gui.tasks.table.menu.open(self._database, self, self._update_task_signal, position))
+ self.customContextMenuRequested.connect(lambda position: gui.tasks.table.menu.open(self, self._update_task_signal, position))
+
+ self.doubleClicked.connect(lambda index: self.on_double_click(index.row()))
+
+ add_task_signal.connect(lambda task, tags: self.insert(task, tags))
+ self._update_task_signal.connect(lambda row, task, tags: self.update_task(row, task, tags))
+ on_show.connect(lambda: self.on_show())
+
+ def on_show(self):
+ self._tags = db.tags.get(database.cursor())
+ self.update_view()
+
+ def init_state(self):
+ self._update_task_signal = gui.tasks.signal.UpdateTask()
+ cursor = database.cursor()
+ self._tasks = service.tasks.get(cursor)
+ self._task_tags = db.task_tags.get(cursor)
+ self._tags = db.tags.get(cursor)
+ self._sort_column = 0
+ self._sort_is_ascending = True
+
- add_task_signal.get().connect(lambda task: self.insert(task))
- self._update_task_signal.get().connect(lambda row, task: self.update(row, task))
+ def init_header(self):
+ h = QtWidgets.QHeaderView(Qt.Horizontal, self)
+ self._header_model = QtGui.QStandardItemModel()
+ self._header_model.setHorizontalHeaderLabels(header_labels)
+ h.setModel(self._header_model)
+ h.setSectionsClickable(True)
+ h.sectionClicked.connect(self.on_header_click)
+ self.setHorizontalHeader(h)
+ # header.setSectionResizeMode(2, QtWidgets.QHeaderView.ResizeToContents)
- def insert(self, task):
- self.model().insert_task(self.horizontalHeader(), task)
- self.resizeColumns()
+ def on_header_click(self, column):
+ if self._sort_column == column:
+ self._sort_is_ascending = not self._sort_is_ascending
+ else:
+ self._sort_is_ascending = True
+ self._sort_column = column
+ self.sort()
+ self._header_model.setItem(column, QtGui.QStandardItem('Hey!'))
+ self.update_view()
- def update(self, row, task):
- row = self.model().update_task(self.horizontalHeader(), row, task)
+ def sort(self):
+ is_rev = self.is_reversed()
+ self._tasks = sorted(
+ self._tasks,
+ key = lambda task: self.sort_key(task, is_rev),
+ reverse = is_rev)
+
+ def update_task(self, row, task: Task, tags: List[int]):
+ # TODO: just update if sort order is not impacted
+ # self._tasks[row] = task
+ # task_ids = [t.id for t in self._tasks]
+ # filtred_task_tags = [tt for tt in self._task_tags if tt.task_id in task_ids]
+ # new_task_tags = [TaskTag(task_id=task.id, tag_id=tag_id) for tag_id in tags]
+ # self._task_tags = filtred_task_tags + new_task_tags
+ # self.update_row(row)
+ self.delete_rows([row])
+ row = self.insert(task, tags)
self.selectRow(row)
- self.resizeColumns()
- def resizeColumns(self):
- for column in range(gui.tasks.table.model.columns):
- self.resizeColumnToContents(column)
+ def update_view(self):
+ for row in range(len(self._tasks)):
+ self.update_row(row)
+
+ def update_row(self, row: int):
+ task = self._tasks[row]
+ self.setItem(row, 0, item(age_since(task.created_at)))
+ self.setItem(row, 1, item(task.name))
+ self.setCellWidget(row, 2, colored_label(self, gui.tasks.duration.format(task.duration), gui.tasks.duration.color(task.duration)))
+ self.setCellWidget(row, 3, colored_label(self, difficulty.format(task.difficulty), difficulty_color(task.difficulty)))
+ self.setCellWidget(row, 4, colored_label(self, priority.format(task.priority), priority_color(task.priority)))
+ tag_ids = [tt.tag_id for tt in self._task_tags if tt.task_id == task.id]
+ res_tags = sorted([tag for tag in self._tags if tag.id in tag_ids], key=lambda t: t.name)
+ self.setCellWidget(row, 5, render_tags(self, res_tags))
+ self.setRowHeight(row, 45)
+
+ def insert(self, task: Task, tags: List[int]) -> int:
+ is_rev = self.is_reversed()
+ row = util.array.insert_position(
+ self.sort_key(task, is_rev),
+ [self.sort_key(t, is_rev) for t in self._tasks],
+ is_rev)
+ self._tasks.insert(row, task)
+ self._task_tags += [TaskTag(task_id=task.id, tag_id=tag_id) for tag_id in tags]
+ self.insertRow(row)
+ self.update_row(row)
+ self._task_tags += [TaskTag(task_id=task.id, tag_id=tag_id) for tag_id in tags]
+ return row
+
+ def is_reversed(self) -> bool:
+ if self._sort_column == 0:
+ return self._sort_is_ascending
+ else:
+ return not self._sort_is_ascending
+
+ def sort_key(self, task: Task, is_reversed: bool):
+ row = self._sort_column
+ if row == 0:
+ return task.created_at
+ elif row == 1:
+ return task.name.lower()
+ elif row == 2:
+ if is_reversed:
+ return task.duration
+ else:
+ return (task.duration == 0, task.duration)
+ elif row == 3:
+ return task.difficulty
+ elif row == 4:
+ return task.priority
+ elif row == 5:
+ tag_ids = [tt.tag_id for tt in self._task_tags if tt.task_id == task.id]
+ tags = sorted([tag.name.lower() for tag in self._tags if tag.id in tag_ids])
+ key = "".join(tags)
+ if is_reversed:
+ return key
+ else:
+ return (key == "", key)
def keyPressEvent(self, event):
super().keyPressEvent(event)
@@ -56,16 +175,84 @@ class Widget(QtWidgets.QTableView):
rows = self.get_selected_rows()
if len(rows) == 1:
row = rows[0]
- task = self.model().get_at(row)
- gui.tasks.dialog.update(
- self._database, self, self._update_task_signal, row, task).exec_()
+ (task, tags) = self.get_at(row)
+ gui.tasks.dialog.update(self, self._update_task_signal, row, task, tags).exec_()
elif event.key() == Qt.Key_Delete:
rows = self.get_selected_rows()
- gui.tasks.dialog.show_delete(self._database, self, rows)
+ gui.tasks.dialog.show_delete(self, rows, lambda: self.delete_rows(rows))
+
+ def delete_rows(self, rows: List[int]):
+ task_ids = [task.id for i, task in enumerate(self._tasks) if i in rows]
+ service.tasks.delete(database.cursor(), task_ids)
+ self._tasks = [t for t in self._tasks if t.id not in task_ids]
+ self._task_tags = [tt for tt in self._task_tags if tt.task_id in [t.id for t in self._tasks]]
+ for row in sorted(rows, reverse=True):
+ self.removeRow(row)
def get_selected_rows(self):
return list(set([index.row() for index in self.selectedIndexes()]))
def on_double_click(self, row: int):
- task = self.model().get_at(row)
- gui.tasks.dialog.update(self._database, self, self._update_task_signal, row, task).exec_()
+ (task, tags) = self.get_at(row)
+ gui.tasks.dialog.update(self, self._update_task_signal, row, task, tags).exec_()
+
+ def get_at(self, row: int) -> Tuple[Task, List[int]]:
+ task = self._tasks[row]
+ tags = [tt.tag_id for tt in self._task_tags if tt.task_id == task.id]
+ return (task, tags)
+
+def item(text: str):
+ item = QtWidgets.QTableWidgetItem(text)
+ item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
+ return item
+
+def age_since(timestamp):
+ diff = int(time.time()) - timestamp
+ if diff >= 60 * 60 * 24:
+ return '' + str(math.floor(diff / 60 / 60 / 24)) + 'd'
+ elif diff >= 60 * 60:
+ return '' + str(math.floor(diff / 60 / 60)) + 'h'
+ elif diff >= 60:
+ return '' + str(math.floor(diff / 60)) + 'm'
+ else:
+ return '1m'
+
+def colored_label(parent, text: str, color: QtGui.QColor):
+ label = QtWidgets.QLabel(text)
+ palette = QtGui.QPalette()
+ palette.setColor(QtGui.QPalette.Text, color)
+ label.setPalette(palette)
+ return label
+
+def render_tags(parent, tags: List[Tag]):
+ widget = QtWidgets.QWidget(parent)
+
+ layout = QtWidgets.QHBoxLayout(widget)
+ widget.setLayout(layout)
+
+ for tag in tags:
+ label = QtWidgets.QLabel(tag.name)
+ label.setContentsMargins(3, 3, 3, 3)
+ palette = QtGui.QPalette()
+ palette.setColor(QtGui.QPalette.Base, QtGui.QColor(tag.color))
+ label.setAutoFillBackground(True)
+ label.setPalette(palette)
+ layout.addWidget(label)
+
+ return widget
+
+def difficulty_color(d: Difficulty) -> QtGui.QColor:
+ if d == Difficulty.EASY:
+ return gui.color.easy_difficulty
+ elif d == Difficulty.NORMAL:
+ return gui.color.normal_difficulty
+ elif d == Difficulty.HARD:
+ return gui.color.hard_difficulty
+
+def priority_color(p: Priority) -> QtGui.QColor:
+ if p == Priority.LOW:
+ return gui.color.low_priority
+ elif p == Priority.MIDDLE:
+ return gui.color.middle_priority
+ elif p == Priority.HIGH:
+ return gui.color.high_priority