aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/database.py19
-rw-r--r--src/db/init.py11
-rw-r--r--src/db/tags.py3
-rw-r--r--src/db/task_tags.py30
-rw-r--r--src/db/tasks.py21
-rw-r--r--src/gui/icon.py2
-rw-r--r--src/gui/signal.py13
-rw-r--r--src/gui/tags/list.py47
-rw-r--r--src/gui/tags/panel/dialog.py (renamed from src/gui/tags/dialog.py)11
-rw-r--r--src/gui/tags/panel/form/state.py (renamed from src/gui/tags/form/state.py)0
-rw-r--r--src/gui/tags/panel/form/widget.py (renamed from src/gui/tags/form/widget.py)8
-rw-r--r--src/gui/tags/panel/signal.py (renamed from src/gui/tags/signal.py)8
-rw-r--r--src/gui/tags/panel/table/menu.py (renamed from src/gui/tags/table/menu.py)8
-rw-r--r--src/gui/tags/panel/table/model.py (renamed from src/gui/tags/table/model.py)0
-rw-r--r--src/gui/tags/panel/table/widget.py (renamed from src/gui/tags/table/widget.py)37
-rw-r--r--src/gui/tags/panel/widget.py (renamed from src/gui/tags/widget.py)15
-rw-r--r--src/gui/tasks/dialog.py38
-rw-r--r--src/gui/tasks/form/state.py19
-rw-r--r--src/gui/tasks/form/widget.py15
-rw-r--r--src/gui/tasks/signal.py21
-rw-r--r--src/gui/tasks/table/menu.py10
-rw-r--r--src/gui/tasks/table/model.py165
-rw-r--r--src/gui/tasks/table/widget.py263
-rw-r--r--src/gui/tasks/widget.py8
-rw-r--r--src/gui/window.py16
-rw-r--r--src/main.py5
-rw-r--r--src/model/task.py5
-rw-r--r--src/model/task_tag.py5
-rw-r--r--src/service/tasks.py27
29 files changed, 508 insertions, 322 deletions
diff --git a/src/database.py b/src/database.py
new file mode 100644
index 0000000..478f62e
--- /dev/null
+++ b/src/database.py
@@ -0,0 +1,19 @@
+import db.init
+
+_database = None
+
+def init(path):
+ global _database
+ _database = db.init.init(path)
+
+def cursor():
+ global _database
+ return _database.cursor()
+
+def commit():
+ global _database
+ _database.commit()
+
+def close():
+ global _database
+ _database.close()
diff --git a/src/db/init.py b/src/db/init.py
index 8292dfc..6b4cbea 100644
--- a/src/db/init.py
+++ b/src/db/init.py
@@ -19,7 +19,6 @@ def init(path):
" updated_at INTEGER NOT NULL,"
" name TEXT NOT NULL,"
" duration INTEGER,"
- " tag TEXT,"
" difficulty INT,"
" priority INT,"
" description TEXT"
@@ -34,6 +33,16 @@ def init(path):
" color TEXT NOT NULL"
" )")
+ cursor.execute(
+ " CREATE TABLE IF NOT EXISTS task_tags("
+ " task_id INTEGER NOT NULL,"
+ " tag_id INTEGER NOT NULL,"
+ " created_at INTEGER NOT NULL,"
+ " FOREIGN KEY (task_id) REFERENCES tasks(id),"
+ " FOREIGN KEY (tag_id) REFERENCES tags(id),"
+ " PRIMARY KEY (task_id, tag_id)"
+ " )")
+
database.commit()
return database
diff --git a/src/db/tags.py b/src/db/tags.py
index 0f0d345..76a276d 100644
--- a/src/db/tags.py
+++ b/src/db/tags.py
@@ -1,9 +1,10 @@
from sqlite3 import Cursor
import time
+from typing import List
from model.tag import Tag, ValidTagForm
-def get(cursor: Cursor) -> Tag:
+def get(cursor: Cursor) -> List[Tag]:
cursor.execute(
" SELECT"
" id,"
diff --git a/src/db/task_tags.py b/src/db/task_tags.py
new file mode 100644
index 0000000..34366e0
--- /dev/null
+++ b/src/db/task_tags.py
@@ -0,0 +1,30 @@
+from sqlite3 import Cursor
+import time
+from typing import List
+
+from model.task_tag import TaskTag
+
+def get(cursor: Cursor) -> List[TaskTag]:
+ cursor.execute("SELECT task_id, tag_id FROM task_tags")
+ return [TaskTag(r[0], r[1]) for r in cursor.fetchall()]
+
+def insert_many(cursor: Cursor, task_id: int, tag_ids: List[int]) -> List[TaskTag] :
+ now = int(time.time())
+
+ task_tags = [TaskTag(task_id = task_id, tag_id = tag) for tag in tag_ids]
+
+ cursor.executemany(
+ " INSERT INTO task_tags("
+ " task_id,"
+ " tag_id,"
+ " created_at"
+ " ) VALUES (?, ?, ?)",
+ [(t.task_id, t.tag_id, now) for t in task_tags])
+
+ return task_tags
+
+def delete(cursor: Cursor, task_ids: List[int]):
+ if len(task_ids) >= 1:
+ cursor.execute(
+ 'DELETE FROM task_tags WHERE task_id IN (%s)' % ','.join('?'*len(task_ids)),
+ task_ids)
diff --git a/src/db/tasks.py b/src/db/tasks.py
index 29d3ba6..b72965b 100644
--- a/src/db/tasks.py
+++ b/src/db/tasks.py
@@ -1,9 +1,10 @@
from sqlite3 import Cursor
import time
+from typing import List
from model.task import Task, ValidTaskForm
-def get(cursor: Cursor) -> Task:
+def get(cursor: Cursor) -> List[Task]:
cursor.execute(
" SELECT"
" id,"
@@ -11,7 +12,6 @@ def get(cursor: Cursor) -> Task:
" updated_at,"
" name,"
" duration,"
- " tag,"
" difficulty,"
" priority,"
" description"
@@ -26,10 +26,9 @@ def get(cursor: Cursor) -> Task:
updated_at = task[2],
name = task[3],
duration = task[4],
- tag = task[5],
- difficulty = task[6],
- priority = task[7],
- description = task[8]
+ difficulty = task[5],
+ priority = task[6],
+ description = task[7]
))
return res
@@ -42,12 +41,11 @@ def insert(cursor: Cursor, form: ValidTaskForm):
" updated_at,"
" name,"
" duration,"
- " tag,"
" difficulty,"
" priority,"
" description"
- " ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
- (now, now, form.name, form.duration, form.tag, int(form.difficulty), int(form.priority), form.description))
+ " ) VALUES (?, ?, ?, ?, ?, ?, ?)",
+ (now, now, form.name, form.duration, int(form.difficulty), int(form.priority), form.description))
return Task(
id = cursor.lastrowid,
@@ -55,7 +53,6 @@ def insert(cursor: Cursor, form: ValidTaskForm):
updated_at = now,
name = form.name,
duration = form.duration,
- tag = form.tag,
difficulty = form.difficulty,
priority = form.priority,
description = form.description
@@ -69,12 +66,11 @@ def update(cursor: Cursor, task: Task, form: ValidTaskForm):
" updated_at = ?,"
" name = ?,"
" duration = ?,"
- " tag = ?,"
" difficulty = ?,"
" priority = ?,"
" description = ?"
" WHERE id = ?",
- (now, form.name, form.duration, form.tag, int(form.difficulty), int(form.priority), form.description, task.id))
+ (now, form.name, form.duration, int(form.difficulty), int(form.priority), form.description, task.id))
return Task(
id = task.id,
@@ -82,7 +78,6 @@ def update(cursor: Cursor, task: Task, form: ValidTaskForm):
updated_at = now,
name = form.name,
duration = form.duration,
- tag = form.tag,
difficulty = form.difficulty,
priority = form.priority,
description = form.description
diff --git a/src/gui/icon.py b/src/gui/icon.py
index 2f8830e..c6584cb 100644
--- a/src/gui/icon.py
+++ b/src/gui/icon.py
@@ -7,7 +7,7 @@ def dialog_open(style):
return style.standardIcon(QtWidgets.QStyle.SP_DialogOpenButton)
def dialog_apply(style):
- return style.standardIcon(QtWidgets.QStyle.SP_DialogApplyButton)
+ return style.standardIcon(QtWidgets.QStyle.SP_DialogOkButton)
def dialog_cancel(style):
return style.standardIcon(QtWidgets.QStyle.SP_DialogCancelButton)
diff --git a/src/gui/signal.py b/src/gui/signal.py
new file mode 100644
index 0000000..b604929
--- /dev/null
+++ b/src/gui/signal.py
@@ -0,0 +1,13 @@
+from PyQt5 import QtCore
+
+class Reload(QtCore.QObject):
+ _signal = QtCore.pyqtSignal(name = 'reload')
+
+ def __init__(self):
+ QtCore.QObject.__init__(self)
+
+ def emit(self):
+ self._signal.emit()
+
+ def connect(self, f):
+ self._signal.connect(f)
diff --git a/src/gui/tags/list.py b/src/gui/tags/list.py
new file mode 100644
index 0000000..ad70dd0
--- /dev/null
+++ b/src/gui/tags/list.py
@@ -0,0 +1,47 @@
+from PyQt5 import QtWidgets, QtCore
+from typing import List, Tuple
+
+from model.tag import Tag
+import db.tags
+import database
+
+class SelectionSignal(QtCore.QObject):
+ _signal = QtCore.pyqtSignal(list, name = 'selection')
+
+ def __init__(self):
+ QtCore.QObject.__init__(self)
+
+ def emit(self, tag_ids):
+ self._signal.emit(tag_ids)
+
+ def connect(self, f):
+ self._signal.connect(f)
+
+def widget(parent, init_tags: List[int]) -> Tuple[QtWidgets.QWidget, SelectionSignal]:
+ widget = QtWidgets.QWidget(parent)
+ signal = SelectionSignal()
+
+ layout = QtWidgets.QVBoxLayout(widget)
+ widget.setLayout(layout)
+
+ layout.addWidget(QtWidgets.QLabel('Tags'))
+
+ list_widget = QtWidgets.QListWidget()
+ list_widget.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection)
+ layout.addWidget(list_widget)
+
+ tags = db.tags.get(database.cursor())
+
+ for tag in tags:
+ item = QtWidgets.QListWidgetItem(tag.name)
+ list_widget.addItem(item)
+ if tag.id in init_tags:
+ item.setSelected(True)
+
+ def on_item_selection_changed():
+ tag_texts = [item.text() for item in list_widget.selectedItems()]
+ signal.emit([tag.id for tag in tags if tag.name in tag_texts])
+
+ list_widget.itemSelectionChanged.connect(on_item_selection_changed)
+
+ return (widget, signal)
diff --git a/src/gui/tags/dialog.py b/src/gui/tags/panel/dialog.py
index 1dd99fa..7a6d3bc 100644
--- a/src/gui/tags/dialog.py
+++ b/src/gui/tags/panel/dialog.py
@@ -3,9 +3,10 @@ from PyQt5 import QtCore, QtWidgets
from model.tag import Tag, ValidTagForm
import db.tags
-import gui.tags.form.widget
+import gui.tags.panel.form.widget
+import database
-def add(database, parent_widget, add_tag_signal):
+def add(parent_widget, add_tag_signal):
def on_add(form: ValidTagForm):
tag = db.tags.insert(database.cursor(), form)
@@ -14,7 +15,7 @@ def add(database, parent_widget, add_tag_signal):
return widget(parent_widget, 'Add a tag', 'add', None, on_add)
-def update(database, parent_widget, update_tag_signal, row, tag):
+def update(parent_widget, update_tag_signal, row, tag):
def on_update(form: ValidTagForm):
updated_tag = db.tags.update(database.cursor(), tag, form)
@@ -23,7 +24,7 @@ def update(database, parent_widget, update_tag_signal, row, tag):
return widget(parent_widget, 'Modify a tag', 'modify', tag, on_update)
-def show_delete(database, table, rows):
+def show_delete(table, rows):
confirm = QtWidgets.QMessageBox.question(
table,
'Tag deletion',
@@ -54,7 +55,7 @@ def widget(
dialog.accept()
on_validated(form)
- layout.addWidget(gui.tags.form.widget.widget(
+ layout.addWidget(gui.tags.panel.form.widget.widget(
parent = dialog,
action_title = action_title,
tag = tag,
diff --git a/src/gui/tags/form/state.py b/src/gui/tags/panel/form/state.py
index 931e67a..931e67a 100644
--- a/src/gui/tags/form/state.py
+++ b/src/gui/tags/panel/form/state.py
diff --git a/src/gui/tags/form/widget.py b/src/gui/tags/panel/form/widget.py
index 92a5db2..5627e3b 100644
--- a/src/gui/tags/form/widget.py
+++ b/src/gui/tags/panel/form/widget.py
@@ -4,7 +4,7 @@ from typing import Optional, Tuple, List, Any
from model.tag import Tag, ValidTagForm
from model import difficulty, priority
import gui.icon
-import gui.tags.form.state
+import gui.tags.panel.form.state
import gui.color
def widget(
@@ -29,7 +29,7 @@ def widget(
init_color = tag.color if tag is not None else '#FFFFFF'
color_input = color_edit(grid, grid_layout, 1, 'Color', QtGui.QColor(init_color))
- tag_form_edition = gui.tags.form.state.TagFormEdition(
+ tag_form_edition = gui.tags.panel.form.state.TagFormEdition(
init_name,
name_input.textChanged,
init_color,
@@ -96,13 +96,15 @@ class ColorInput(QtWidgets.QLineEdit):
if not self._is_editing:
self._is_editing = True
color = QtWidgets.QColorDialog.getColor(self._color, self)
- self.update(color)
+ if color.isValid():
+ self.update(color)
else:
self._is_editing = False
self.clearFocus()
return super(ColorInput, self).eventFilter(source, event)
def update(self, color: QtGui.QColor):
+ self._color = color
self.setText(color.name().upper())
palette = QtGui.QPalette()
palette.setColor(QtGui.QPalette.Base, color)
diff --git a/src/gui/tags/signal.py b/src/gui/tags/panel/signal.py
index 57ec696..9417530 100644
--- a/src/gui/tags/signal.py
+++ b/src/gui/tags/panel/signal.py
@@ -11,8 +11,8 @@ class AddTag(QtCore.QObject):
def emit(self, tag):
self._signal.emit(tag)
- def get(self):
- return self._signal
+ def connect(self, f):
+ self._signal.connect(f)
class UpdateTag(QtCore.QObject):
_signal = QtCore.pyqtSignal(int, Tag, name = 'updateTag')
@@ -23,5 +23,5 @@ class UpdateTag(QtCore.QObject):
def emit(self, row, tag):
self._signal.emit(row, tag)
- def get(self):
- return self._signal
+ def connect(self, f):
+ self._signal.connect(f)
diff --git a/src/gui/tags/table/menu.py b/src/gui/tags/panel/table/menu.py
index 7acdda1..f95c097 100644
--- a/src/gui/tags/table/menu.py
+++ b/src/gui/tags/panel/table/menu.py
@@ -1,10 +1,10 @@
from PyQt5 import QtWidgets
import db.tags
-import gui.tags.dialog
+import gui.tags.panel.dialog
from model.tag import Tag, ValidTagForm
-def open(database, table, update_tag_signal, position):
+def open(table, update_tag_signal, position):
rows = set([index.row() for index in table.selectedIndexes()])
menu = QtWidgets.QMenu(table)
@@ -20,6 +20,6 @@ def open(database, table, update_tag_signal, position):
if action == modify_action and len(rows) == 1:
row = list(rows)[0]
tag = table.model().get_at(row)
- gui.tags.dialog.update(database, table, update_tag_signal, row, tag).exec_()
+ gui.tags.panel.dialog.update(table, update_tag_signal, row, tag).exec_()
elif action == delete_action:
- gui.tags.dialog.show_delete(database, table, rows)
+ gui.tags.panel.dialog.show_delete(table, rows)
diff --git a/src/gui/tags/table/model.py b/src/gui/tags/panel/table/model.py
index 7c66b5d..7c66b5d 100644
--- a/src/gui/tags/table/model.py
+++ b/src/gui/tags/panel/table/model.py
diff --git a/src/gui/tags/table/widget.py b/src/gui/tags/panel/table/widget.py
index 89c9990..f0bf82c 100644
--- a/src/gui/tags/table/widget.py
+++ b/src/gui/tags/panel/table/widget.py
@@ -2,27 +2,27 @@ from PyQt5 import QtWidgets
from PyQt5.QtCore import Qt
import db.tags
-import gui.tags.signal
-import gui.tags.table.menu
-import gui.tags.table.model
-import gui.tags.dialog
+import gui.tags.panel.signal
+import gui.tags.panel.table.menu
+import gui.tags.panel.table.model
+import gui.tags.panel.dialog
from model.tag import Tag, ValidTagForm
+import database
class Widget(QtWidgets.QTableView):
- def __init__(self, database, parent, add_tag_signal):
+ def __init__(self, parent, add_tag_signal):
super().__init__(parent)
- self._database = database
- self._update_tag_signal = gui.tags.signal.UpdateTag()
+ self._update_tag_signal = gui.tags.panel.signal.UpdateTag()
- tags = db.tags.get(self._database.cursor())
- table_model = gui.tags.table.model.TableModel(tags)
+ tags = db.tags.get(database.cursor())
+ table_model = gui.tags.panel.table.model.TableModel(tags)
self.setModel(table_model)
self.sortByColumn(
- gui.tags.table.model.default_sort[0],
- gui.tags.table.model.default_sort[1])
+ gui.tags.panel.table.model.default_sort[0],
+ gui.tags.panel.table.model.default_sort[1])
self.setSortingEnabled(True)
self.setSelectionBehavior(QtWidgets.QTableView.SelectRows)
self.horizontalHeader().setStretchLastSection(True)
@@ -32,10 +32,10 @@ class Widget(QtWidgets.QTableView):
# # Menu
self.setContextMenuPolicy(Qt.CustomContextMenu)
- self.customContextMenuRequested.connect(lambda position: gui.tags.table.menu.open(self._database, self, self._update_tag_signal, position))
+ self.customContextMenuRequested.connect(lambda position: gui.tags.panel.table.menu.open(self, self._update_tag_signal, position))
- add_tag_signal.get().connect(lambda tag: self.insert(tag))
- self._update_tag_signal.get().connect(lambda row, tag: self.update(row, tag))
+ add_tag_signal.connect(lambda tag: self.insert(tag))
+ self._update_tag_signal.connect(lambda row, tag: self.update(row, tag))
def insert(self, tag):
self.model().insert_tag(self.horizontalHeader(), tag)
@@ -47,7 +47,7 @@ class Widget(QtWidgets.QTableView):
self.resizeColumns()
def resizeColumns(self):
- for column in range(gui.tags.table.model.columns):
+ for column in range(gui.tags.panel.table.model.columns):
self.resizeColumnToContents(column)
def keyPressEvent(self, event):
@@ -57,15 +57,14 @@ class Widget(QtWidgets.QTableView):
if len(rows) == 1:
row = rows[0]
tag = self.model().get_at(row)
- gui.tags.dialog.update(
- self._database, self, self._update_tag_signal, row, tag).exec_()
+ gui.tags.panel.dialog.update(self, self._update_tag_signal, row, tag).exec_()
elif event.key() == Qt.Key_Delete:
rows = self.get_selected_rows()
- gui.tags.dialog.show_delete(self._database, self, rows)
+ gui.tags.panel.dialog.show_delete(self, rows)
def get_selected_rows(self):
return list(set([index.row() for index in self.selectedIndexes()]))
def on_double_click(self, row: int):
tag = self.model().get_at(row)
- gui.tags.dialog.update(self._database, self, self._update_tag_signal, row, tag).exec_()
+ gui.tags.panel.dialog.update(self, self._update_tag_signal, row, tag).exec_()
diff --git a/src/gui/tags/widget.py b/src/gui/tags/panel/widget.py
index a9d870e..3da55c9 100644
--- a/src/gui/tags/widget.py
+++ b/src/gui/tags/panel/widget.py
@@ -1,26 +1,25 @@
from PyQt5 import QtWidgets
-import gui.tags.dialog
-import gui.tags.signal
-import gui.tags.table.widget
+import gui.tags.panel.dialog
+import gui.tags.panel.signal
+import gui.tags.panel.table.widget
import gui.icon
-def widget(database, parent):
+def widget(parent):
widget = QtWidgets.QWidget(parent)
layout = QtWidgets.QVBoxLayout(widget)
widget.setLayout(layout)
- add_tag_signal = gui.tags.signal.AddTag()
+ add_tag_signal = gui.tags.panel.signal.AddTag()
add_tag_button = QtWidgets.QPushButton('Add a tag', widget)
add_tag_button.setIcon(gui.icon.new_folder(widget.style()))
- add_tag_button.clicked.connect(lambda: gui.tags.dialog.add(
- database, widget, add_tag_signal).exec_())
+ add_tag_button.clicked.connect(lambda: gui.tags.panel.dialog.add(widget, add_tag_signal).exec_())
layout.addWidget(add_tag_button)
- table = gui.tags.table.widget.Widget(database, widget, add_tag_signal)
+ table = gui.tags.panel.table.widget.Widget(widget, add_tag_signal)
layout.addWidget(table)
return widget
diff --git a/src/gui/tasks/dialog.py b/src/gui/tasks/dialog.py
index 2e0d8d4..a28ecb0 100644
--- a/src/gui/tasks/dialog.py
+++ b/src/gui/tasks/dialog.py
@@ -1,46 +1,47 @@
from PyQt5 import QtCore, QtWidgets
+from typing import List
+from model.tag import Tag
from model.task import Task, ValidTaskForm
-
+import database
+import db.task_tags
import db.tasks
import gui.tasks.form.widget
+import service.tasks
-def add(database, parent_widget, add_task_signal):
+def add(parent_widget, add_task_signal):
- def on_add(form: ValidTaskForm):
- task = db.tasks.insert(database.cursor(), form)
- database.commit()
- add_task_signal.emit(task)
+ def on_add(task_form: ValidTaskForm):
+ task = service.tasks.create(database.cursor(), task_form)
+ add_task_signal.emit(task, task_form.tags)
- return widget(parent_widget, 'Add a task', 'add', None, on_add)
+ return widget(parent_widget, 'Add a task', 'add', None, [], on_add)
-def update(database, parent_widget, update_task_signal, row, task):
+def update(parent_widget, update_task_signal, row: int, task: Task, tags: List[int]):
- def on_update(form: ValidTaskForm):
- updated_task = db.tasks.update(database.cursor(), task, form)
- update_task_signal.emit(row, updated_task)
- database.commit()
+ def on_update(task_form: ValidTaskForm):
+ updated_task = service.tasks.update(database.cursor(), task, task_form)
+ update_task_signal.emit(row, updated_task, task_form.tags)
- return widget(parent_widget, 'Modify a task', 'modify', task, on_update)
+ return widget(parent_widget, 'Modify a task', 'modify', task, tags, on_update)
-def show_delete(database, table, rows):
+def show_delete(parent, rows: List[int], on_delete):
confirm = QtWidgets.QMessageBox.question(
- table,
+ parent,
'Task deletion',
'Do you really want to delete the selected tasks ?',
QtWidgets.QMessageBox.No | QtWidgets.QMessageBox.Yes,
QtWidgets.QMessageBox.Yes)
if confirm == QtWidgets.QMessageBox.Yes:
- db.tasks.delete(database.cursor(), table.model().row_ids(rows))
- database.commit()
- table.model().delete_tasks(rows)
+ on_delete()
def widget(
parent: QtWidgets.QWidget,
title: str,
action_title: str,
task: Task,
+ tags: List[int],
on_validated):
dialog = QtWidgets.QDialog(parent)
@@ -58,6 +59,7 @@ def widget(
parent = dialog,
action_title = action_title,
task = task,
+ tags = tags,
on_validated = on_dialog_validated,
on_cancel = lambda: dialog.reject()))
diff --git a/src/gui/tasks/form/state.py b/src/gui/tasks/form/state.py
index 9bd3ae3..5b48cd9 100644
--- a/src/gui/tasks/form/state.py
+++ b/src/gui/tasks/form/state.py
@@ -5,6 +5,7 @@ from model.task import ValidTaskForm
from model.difficulty import Difficulty
from model.priority import Priority
import gui.tasks.duration
+import gui.tags.list
class TaskFormEdition:
def __init__(
@@ -13,34 +14,32 @@ class TaskFormEdition:
name_signal,
duration,
duration_signal,
- tag,
- tag_signal,
difficulty,
difficulty_signal,
priority,
priority_signal,
+ tags_signal: gui.tags.list.SelectionSignal,
description,
description_signal):
self._name = name
self._duration = duration
- self._tag = tag
self._difficulty = difficulty
self._priority = priority
+ self._tags = []
self._description = description
self._signal = ValidTaskFormSignal()
name_signal.connect(lambda n: self.on_name_signal(n))
duration_signal.connect(lambda d: self.on_duration_signal(d))
- tag_signal.connect(lambda t: self.on_tag_signal(t))
difficulty_signal.connect(lambda d: self.on_difficulty_signal(d))
priority_signal.connect(lambda p: self.on_priority_signal(p))
+ tags_signal.connect(lambda ts: self.on_tags_signal(ts))
description_signal.connect(lambda d: self.on_description_signal(d))
def get(self) -> Optional[ValidTaskForm]:
name = self._name.strip()
duration = gui.tasks.duration.parse(self._duration)
- tag = self._tag.strip()
difficulty = self._difficulty
priority = self._priority
description = self._description.strip()
@@ -49,9 +48,9 @@ class TaskFormEdition:
return ValidTaskForm(
name = name,
duration = duration,
- tag = tag,
difficulty = difficulty,
priority = priority,
+ tags = self._tags,
description = description)
else:
return None
@@ -64,10 +63,6 @@ class TaskFormEdition:
self._duration = duration
self.emit()
- def on_tag_signal(self, tag: str):
- self._tag = tag
- self.emit()
-
def on_difficulty_signal(self, index: int):
self._difficulty = Difficulty(index)
self.emit()
@@ -76,6 +71,10 @@ class TaskFormEdition:
self._priority = Priority(index)
self.emit()
+ def on_tags_signal(self, tags: [int]):
+ self._tags = tags
+ self.emit()
+
def on_description_signal(self, description: str):
self._description = description
self.emit()
diff --git a/src/gui/tasks/form/widget.py b/src/gui/tasks/form/widget.py
index 9feaad0..49339e1 100644
--- a/src/gui/tasks/form/widget.py
+++ b/src/gui/tasks/form/widget.py
@@ -2,10 +2,12 @@ from PyQt5 import QtWidgets, QtCore
from typing import Optional, Tuple, List, Any
from model.task import Task, ValidTaskForm
+from model.tag import Tag
from model import difficulty, priority
import gui.icon
import gui.tasks.form.state
import gui.tasks.duration
+import gui.tags.list
class TextEditSignal(QtCore.QObject):
_signal = QtCore.pyqtSignal(str, name = 'textEdit')
@@ -23,6 +25,7 @@ def widget(
parent: QtWidgets.QWidget,
action_title: str,
task: Task,
+ tags: List[int],
on_validated,
on_cancel):
@@ -41,9 +44,6 @@ def widget(
init_duration = gui.tasks.duration.format(task.duration) if task is not None else ''
duration_input = line_edit(grid, grid_layout, 1, 'Duration', init_duration)
- init_tag = task.tag if task is not None else ''
- tag_input = line_edit(grid, grid_layout, 2, 'Tag', init_tag)
-
init_difficulty = task.difficulty if task is not None else difficulty.Difficulty.NORMAL
difficulty_input = combo_box(
grid,
@@ -62,6 +62,9 @@ def widget(
[priority.format(d) for d in priority.values],
int(init_priority))
+ (tags_list_widget, tags_signal) = tags_selection(widget, tags)
+ layout.addWidget(tags_list_widget)
+
init_description = task.description if task is not None else ''
(description_input, description_signal) = text_edit(widget, 'Description', init_description)
layout.addWidget(description_input)
@@ -71,12 +74,11 @@ def widget(
name_input.textChanged,
init_duration,
duration_input.textChanged,
- init_tag,
- tag_input.textChanged,
init_difficulty,
difficulty_input.currentIndexChanged,
init_priority,
priority_input.currentIndexChanged,
+ tags_signal,
init_description,
description_signal)
@@ -131,6 +133,9 @@ def combo_box(
return box
+def tags_selection(parent, init_tags: List[int]) -> QtWidgets.QWidget:
+ return gui.tags.list.widget(parent, init_tags)
+
def text_edit(
parent,
label: str,
diff --git a/src/gui/tasks/signal.py b/src/gui/tasks/signal.py
index 7d926e1..e62a838 100644
--- a/src/gui/tasks/signal.py
+++ b/src/gui/tasks/signal.py
@@ -1,27 +1,28 @@
from PyQt5 import QtCore
+from typing import List
from model.task import Task
class AddTask(QtCore.QObject):
- _signal = QtCore.pyqtSignal(Task, name = 'addTask')
+ _signal = QtCore.pyqtSignal(Task, list, name = 'addTask')
def __init__(self):
QtCore.QObject.__init__(self)
- def emit(self, task):
- self._signal.emit(task)
+ def emit(self, task: Task, tags: List[int]):
+ self._signal.emit(task, tags)
- def get(self):
- return self._signal
+ def connect(self, f):
+ self._signal.connect(f)
class UpdateTask(QtCore.QObject):
- _signal = QtCore.pyqtSignal(int, Task, name = 'updateTask')
+ _signal = QtCore.pyqtSignal(int, Task, list, name = 'updateTask')
def __init__(self):
QtCore.QObject.__init__(self)
- def emit(self, row, task):
- self._signal.emit(row, task)
+ def emit(self, row: int, task: Task, tags: List[int]):
+ self._signal.emit(row, task, tags)
- def get(self):
- return self._signal
+ def connect(self, f):
+ self._signal.connect(f)
diff --git a/src/gui/tasks/table/menu.py b/src/gui/tasks/table/menu.py
index 435ff25..f22176c 100644
--- a/src/gui/tasks/table/menu.py
+++ b/src/gui/tasks/table/menu.py
@@ -1,10 +1,12 @@
from PyQt5 import QtWidgets
+from typing import List
import db.tasks
import gui.tasks.dialog
from model.task import Task, ValidTaskForm
+from model.tag import Tag
-def open(database, table, update_task_signal, position):
+def open(table, update_task_signal, position):
rows = set([index.row() for index in table.selectedIndexes()])
menu = QtWidgets.QMenu(table)
@@ -19,7 +21,7 @@ def open(database, table, update_task_signal, position):
action = menu.exec_(table.mapToGlobal(position))
if action == modify_action and len(rows) == 1:
row = list(rows)[0]
- task = table.model().get_at(row)
- gui.tasks.dialog.update(database, table, update_task_signal, row, task).exec_()
+ (task, tags) = table.get_at(row)
+ gui.tasks.dialog.update(table, update_task_signal, row, task, tags).exec_()
elif action == delete_action:
- gui.tasks.dialog.show_delete(database, table, rows)
+ gui.tasks.dialog.show_delete(table, rows, lambda: table.delete_rows(rows))
diff --git a/src/gui/tasks/table/model.py b/src/gui/tasks/table/model.py
deleted file mode 100644
index ab969ec..0000000
--- a/src/gui/tasks/table/model.py
+++ /dev/null
@@ -1,165 +0,0 @@
-from PyQt5 import QtCore, QtWidgets, QtGui
-from PyQt5.QtCore import Qt
-
-from model.task import Task
-from model.difficulty import Difficulty
-from model.priority import Priority
-from model import difficulty, priority
-import time
-import math
-import util.array
-import util.range
-import gui.tasks.duration
-import gui.color
-
-columns = 6
-
-headers = ['Age', 'Name', 'Duration', 'Difficulty', 'Priority', 'Tag']
-
-default_sort = (0, Qt.AscendingOrder)
-
-class TableModel(QtCore.QAbstractTableModel):
- def __init__(self, tasks):
- super(TableModel, self).__init__()
- self._tasks = tasks
-
- def headerData(self, section, orientation, role):
- if role == Qt.DisplayRole and orientation == Qt.Horizontal:
- return headers[section]
- elif role == Qt.DisplayRole and orientation == Qt.Vertical:
- return section + 1
- else:
- return QtCore.QVariant()
-
- def data(self, index, role):
- task = self._tasks[index.row()]
-
- if role == Qt.DisplayRole:
- if index.column() == 0:
- return age_since(task.created_at)
- elif index.column() == 1:
- return task.name
- elif index.column() == 2:
- return gui.tasks.duration.format(task.duration)
- elif index.column() == 3:
- return difficulty.format(task.difficulty)
- elif index.column() == 4:
- return priority.format(task.priority)
- elif index.column() == 5:
- return task.tag
- elif role == Qt.ForegroundRole:
- if index.column() == 2:
- return QtGui.QBrush(gui.tasks.duration.color(task.duration))
- elif index.column() == 3:
- return QtGui.QBrush(difficulty_color(task.difficulty))
- elif index.column() == 4:
- return QtGui.QBrush(priority_color(task.priority))
- else:
- return QtCore.QVariant()
-
- def rowCount(self, index):
- return len(self._tasks)
-
- def columnCount(self, index):
- return columns
-
- def get_at(self, row):
- if row >= 0 and row < len(self._tasks):
- return self._tasks[row]
-
-
- def insert_task(self, header: QtWidgets.QHeaderView, task: Task) -> int:
- at = self.insert_position(header, task)
- self.beginInsertRows(QtCore.QModelIndex(), at, at)
- self._tasks.insert(at, task)
- self.endInsertRows()
- return at
-
- def insert_position(self, header: QtWidgets.QHeaderView, task: Task) -> int:
- row = header.sortIndicatorSection()
- order = header.sortIndicatorOrder()
- is_rev = is_reversed(row, order)
- return util.array.insert_position(
- sort_key(task, row, is_rev),
- [sort_key(t, row, is_rev) for t in self._tasks],
- is_rev)
-
- def update_task(self, header: QtWidgets.QHeaderView, row, task: Task) -> int:
- self.delete_task_range(row, 1)
- return self.insert_task(header, task)
-
- def delete_tasks(self, indexes):
- for range in reversed(util.range.from_indexes(indexes)):
- self.delete_task_range(range.start, range.length)
- return True
-
- def delete_task_range(self, row, rows):
- self.beginRemoveRows(QtCore.QModelIndex(), row, row + rows - 1)
- self._tasks = self._tasks[:row] + self._tasks[row + rows:]
- self.endRemoveRows()
- return True
-
- def row_ids(self, rows):
- return [task.id for i, task in enumerate(self._tasks) if i in rows]
-
- def sort(self, row: int, order: Qt.SortOrder):
- self.layoutAboutToBeChanged.emit()
- is_rev = is_reversed(row, order)
- self._tasks = sorted(
- self._tasks,
- key = lambda task: sort_key(task, row, is_rev),
- reverse = is_rev)
- self.layoutChanged.emit()
-
-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 sort_key(task: Task, row: int, is_reversed: bool):
- 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:
- if is_reversed:
- return task.tag.lower()
- else:
- return (task.tag == '', task.tag.lower())
-
-def is_reversed(row: int, order: Qt.SortOrder) -> bool:
- if row == 0:
- return order == Qt.AscendingOrder
- else:
- return order == Qt.DescendingOrder
-
-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
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
diff --git a/src/gui/tasks/widget.py b/src/gui/tasks/widget.py
index ea4acb9..36fb743 100644
--- a/src/gui/tasks/widget.py
+++ b/src/gui/tasks/widget.py
@@ -3,8 +3,9 @@ from PyQt5 import QtWidgets
import gui.tasks.signal
import gui.tasks.table.widget
import gui.icon
+import gui.signal
-def widget(database, parent):
+def widget(parent, on_show: gui.signal.Reload):
widget = QtWidgets.QWidget(parent)
layout = QtWidgets.QVBoxLayout(widget)
@@ -14,11 +15,10 @@ def widget(database, parent):
add_task_button = QtWidgets.QPushButton('Add a task', widget)
add_task_button.setIcon(gui.icon.new_folder(widget.style()))
- add_task_button.clicked.connect(lambda: gui.tasks.dialog.add(
- database, widget, add_task_signal).exec_())
+ add_task_button.clicked.connect(lambda: gui.tasks.dialog.add(widget, add_task_signal).exec_())
layout.addWidget(add_task_button)
- table = gui.tasks.table.widget.Widget(database, widget, add_task_signal)
+ table = gui.tasks.table.widget.Widget(widget, on_show, add_task_signal)
layout.addWidget(table)
return widget
diff --git a/src/gui/window.py b/src/gui/window.py
index 622f65d..4865edf 100644
--- a/src/gui/window.py
+++ b/src/gui/window.py
@@ -1,9 +1,11 @@
from PyQt5 import QtCore, QtWidgets
import gui.tasks.widget
-import gui.tags.widget
+import gui.tasks.widget
+import gui.tags.panel.widget
+import gui.signal
-def get(database):
+def get():
window = QtWidgets.QMainWindow()
window.setWindowTitle("todo-next")
window.setMinimumSize(QtCore.QSize(640, 480))
@@ -11,7 +13,13 @@ def get(database):
tabs = QtWidgets.QTabWidget(window)
window.setCentralWidget(tabs)
- tabs.addTab(gui.tasks.widget.widget(database, tabs), 'Tasks')
- tabs.addTab(gui.tags.widget.widget(database, tabs), 'Tags')
+ open_tasks = gui.signal.Reload()
+ def on_current_tab_changed(index: int):
+ if index == 0:
+ open_tasks.emit()
+ tabs.currentChanged.connect(on_current_tab_changed)
+
+ tabs.addTab(gui.tasks.widget.widget(tabs, open_tasks), 'Tasks')
+ tabs.addTab(gui.tags.panel.widget.widget(tabs), 'Tags')
return window
diff --git a/src/main.py b/src/main.py
index c4ff4e9..104063b 100644
--- a/src/main.py
+++ b/src/main.py
@@ -6,12 +6,13 @@ import os.path
import db.init
import gui.window
import arguments
+import database
args = arguments.parser().parse_args()
-database = db.init.init(args.database if args.database != None else 'database')
+database.init(args.database if args.database != None else 'database')
app = QtWidgets.QApplication(sys.argv)
-window = gui.window.get(database)
+window = gui.window.get()
window.show()
res = app.exec_()
diff --git a/src/model/task.py b/src/model/task.py
index 4bb9a89..69f9807 100644
--- a/src/model/task.py
+++ b/src/model/task.py
@@ -1,4 +1,4 @@
-from typing import NamedTuple
+from typing import NamedTuple, List
from model.difficulty import Difficulty
from model.priority import Priority
@@ -9,7 +9,6 @@ class Task(NamedTuple):
updated_at: int
name: str
duration: int
- tag: str
difficulty: Difficulty
priority: Priority
description: str
@@ -17,7 +16,7 @@ class Task(NamedTuple):
class ValidTaskForm(NamedTuple):
name: str
duration: int
- tag: str
difficulty: Difficulty
priority: Priority
+ tags: List[int]
description: str
diff --git a/src/model/task_tag.py b/src/model/task_tag.py
new file mode 100644
index 0000000..0a33c66
--- /dev/null
+++ b/src/model/task_tag.py
@@ -0,0 +1,5 @@
+from typing import NamedTuple
+
+class TaskTag(NamedTuple):
+ task_id: int
+ tag_id: int
diff --git a/src/service/tasks.py b/src/service/tasks.py
new file mode 100644
index 0000000..6c3444b
--- /dev/null
+++ b/src/service/tasks.py
@@ -0,0 +1,27 @@
+from typing import List
+
+from model.task import Task, ValidTaskForm
+import db.tasks
+import db.task_tags
+import database
+
+def get(cursor) -> List[Task]:
+ return db.tasks.get(cursor)
+
+def create(cursor, task_form: ValidTaskForm) -> Task:
+ task = db.tasks.insert(cursor, task_form)
+ new_task_tags = db.task_tags.insert_many(cursor, task.id, task_form.tags)
+ database.commit()
+ return task
+
+def update(cursor, task: Task, task_form: ValidTaskForm) -> Task:
+ updated_task = db.tasks.update(cursor, task, task_form)
+ db.task_tags.delete(cursor, [task.id])
+ new_task_tags = db.task_tags.insert_many(cursor, task.id, task_form.tags)
+ database.commit()
+ return updated_task
+
+def delete(cursor, task_ids: List[int]):
+ db.tasks.delete(cursor, task_ids)
+ db.task_tags.delete(cursor, task_ids)
+ database.commit()