aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoris2020-05-10 20:24:24 +0200
committerJoris2020-05-10 20:24:24 +0200
commit8a6e10d401eea8db0947f8c4b309b8a6256f9748 (patch)
tree6e2dc6956ce7825f0f1b6c3ed6f0b4171fe9c274
parent3e1415c738facb8b0274adb50ae65f218fd59c9b (diff)
downloadtodo-8a6e10d401eea8db0947f8c4b309b8a6256f9748.tar.gz
todo-8a6e10d401eea8db0947f8c4b309b8a6256f9748.tar.bz2
todo-8a6e10d401eea8db0947f8c4b309b8a6256f9748.zip
Add tags panel
-rw-r--r--src/db/init.py21
-rw-r--r--src/db/tags.py71
-rw-r--r--src/db/tasks.py15
-rw-r--r--src/gui/color.py11
-rw-r--r--src/gui/tags/dialog.py64
-rw-r--r--src/gui/tags/form/state.py58
-rw-r--r--src/gui/tags/form/widget.py135
-rw-r--r--src/gui/tags/signal.py27
-rw-r--r--src/gui/tags/table/menu.py25
-rw-r--r--src/gui/tags/table/model.py103
-rw-r--r--src/gui/tags/table/widget.py71
-rw-r--r--src/gui/tags/widget.py26
-rw-r--r--src/gui/tasks/form/state.py3
-rw-r--r--src/gui/tasks/form/widget.py1
-rw-r--r--src/gui/tasks/table/menu.py2
-rw-r--r--src/gui/tasks/table/model.py1
-rw-r--r--src/gui/tasks/widget.py3
-rw-r--r--src/gui/window.py8
-rw-r--r--src/main.py5
-rw-r--r--src/model/tag.py12
-rw-r--r--src/model/task.py2
21 files changed, 635 insertions, 29 deletions
diff --git a/src/db/init.py b/src/db/init.py
index 8fb7098..8292dfc 100644
--- a/src/db/init.py
+++ b/src/db/init.py
@@ -3,14 +3,20 @@ import os.path
import time
def init(path):
+
is_db_new = not os.path.isfile(path)
+
database = sqlite3.connect(path)
+
if is_db_new:
- database.cursor().execute(
+
+ cursor = database.cursor()
+
+ cursor.execute(
" CREATE TABLE IF NOT EXISTS tasks("
" id INTEGER PRIMARY KEY,"
" created_at INTEGER NOT NULL,"
- " modified_at INTEGER NOT NULL,"
+ " updated_at INTEGER NOT NULL,"
" name TEXT NOT NULL,"
" duration INTEGER,"
" tag TEXT,"
@@ -18,5 +24,16 @@ def init(path):
" priority INT,"
" description TEXT"
" )")
+
+ cursor.execute(
+ " CREATE TABLE IF NOT EXISTS tags("
+ " id INTEGER PRIMARY KEY,"
+ " created_at INTEGER NOT NULL,"
+ " updated_at INTEGER NOT NULL,"
+ " name TEXT NOT NULL,"
+ " color TEXT NOT NULL"
+ " )")
+
database.commit()
+
return database
diff --git a/src/db/tags.py b/src/db/tags.py
new file mode 100644
index 0000000..0f0d345
--- /dev/null
+++ b/src/db/tags.py
@@ -0,0 +1,71 @@
+from sqlite3 import Cursor
+import time
+
+from model.tag import Tag, ValidTagForm
+
+def get(cursor: Cursor) -> Tag:
+ cursor.execute(
+ " SELECT"
+ " id,"
+ " created_at,"
+ " updated_at,"
+ " name,"
+ " color"
+ " FROM tags")
+
+ res = []
+
+ for tag in cursor.fetchall():
+ res.append(Tag(
+ id = tag[0],
+ created_at = tag[1],
+ updated_at = tag[2],
+ name = tag[3],
+ color = tag[4]
+ ))
+
+ return res
+
+def insert(cursor: Cursor, form: ValidTagForm):
+ now = int(time.time())
+ cursor.execute(
+ " INSERT INTO tags("
+ " created_at,"
+ " updated_at,"
+ " name,"
+ " color"
+ " ) VALUES (?, ?, ?, ?)",
+ (now, now, form.name, form.color))
+
+ return Tag(
+ id = cursor.lastrowid,
+ created_at = now,
+ updated_at = now,
+ name = form.name,
+ color = form.color
+ )
+
+def update(cursor: Cursor, tag: Tag, form: ValidTagForm):
+ now = int(time.time())
+
+ cursor.execute(
+ " UPDATE tags SET"
+ " updated_at = ?,"
+ " name = ?,"
+ " color = ?"
+ " WHERE id = ?",
+ (now, form.name, form.color, tag.id))
+
+ return Tag(
+ id = tag.id,
+ created_at = tag.created_at,
+ updated_at = now,
+ name = form.name,
+ color = form.color
+ )
+
+def delete(cursor: Cursor, ids):
+ if len(ids) >= 1:
+ cursor.execute(
+ 'DELETE FROM tags WHERE id IN (%s)' % ','.join('?'*len(ids)),
+ ids)
diff --git a/src/db/tasks.py b/src/db/tasks.py
index 5fdd25e..29d3ba6 100644
--- a/src/db/tasks.py
+++ b/src/db/tasks.py
@@ -8,7 +8,7 @@ def get(cursor: Cursor) -> Task:
" SELECT"
" id,"
" created_at,"
- " modified_at,"
+ " updated_at,"
" name,"
" duration,"
" tag,"
@@ -23,7 +23,7 @@ def get(cursor: Cursor) -> Task:
res.append(Task(
id = task[0],
created_at = task[1],
- modified_at = task[2],
+ updated_at = task[2],
name = task[3],
duration = task[4],
tag = task[5],
@@ -39,7 +39,7 @@ def insert(cursor: Cursor, form: ValidTaskForm):
cursor.execute(
" INSERT INTO tasks("
" created_at,"
- " modified_at,"
+ " updated_at,"
" name,"
" duration,"
" tag,"
@@ -52,7 +52,7 @@ def insert(cursor: Cursor, form: ValidTaskForm):
return Task(
id = cursor.lastrowid,
created_at = now,
- modified_at = now,
+ updated_at = now,
name = form.name,
duration = form.duration,
tag = form.tag,
@@ -65,9 +65,8 @@ def update(cursor: Cursor, task: Task, form: ValidTaskForm):
now = int(time.time())
cursor.execute(
- " UPDATE tasks"
- " SET"
- " modified_at = ?,"
+ " UPDATE tasks SET"
+ " updated_at = ?,"
" name = ?,"
" duration = ?,"
" tag = ?,"
@@ -80,7 +79,7 @@ def update(cursor: Cursor, task: Task, form: ValidTaskForm):
return Task(
id = task.id,
created_at = task.created_at,
- modified_at = now,
+ updated_at = now,
name = form.name,
duration = form.duration,
tag = form.tag,
diff --git a/src/gui/color.py b/src/gui/color.py
index f02fbae..cc7e5a8 100644
--- a/src/gui/color.py
+++ b/src/gui/color.py
@@ -1,9 +1,12 @@
from PyQt5 import QtGui
-red: QtGui.QColor = QtGui.QColor(200, 30, 30)
-orange: QtGui.QColor = QtGui.QColor(200, 100, 30)
-green: QtGui.QColor = QtGui.QColor(30, 180, 30)
-blue: QtGui.QColor = QtGui.QColor(30, 30, 200)
+black = QtGui.QColor(0, 0, 0)
+red = QtGui.QColor(200, 30, 30)
+orange = QtGui.QColor(200, 100, 30)
+green = QtGui.QColor(30, 180, 30)
+blue = QtGui.QColor(30, 30, 200)
+
+text = black
easy_difficulty = green
normal_difficulty = orange
diff --git a/src/gui/tags/dialog.py b/src/gui/tags/dialog.py
new file mode 100644
index 0000000..1dd99fa
--- /dev/null
+++ b/src/gui/tags/dialog.py
@@ -0,0 +1,64 @@
+from PyQt5 import QtCore, QtWidgets
+
+from model.tag import Tag, ValidTagForm
+
+import db.tags
+import gui.tags.form.widget
+
+def add(database, parent_widget, add_tag_signal):
+
+ def on_add(form: ValidTagForm):
+ tag = db.tags.insert(database.cursor(), form)
+ database.commit()
+ add_tag_signal.emit(tag)
+
+ return widget(parent_widget, 'Add a tag', 'add', None, on_add)
+
+def update(database, parent_widget, update_tag_signal, row, tag):
+
+ def on_update(form: ValidTagForm):
+ updated_tag = db.tags.update(database.cursor(), tag, form)
+ update_tag_signal.emit(row, updated_tag)
+ database.commit()
+
+ return widget(parent_widget, 'Modify a tag', 'modify', tag, on_update)
+
+def show_delete(database, table, rows):
+ confirm = QtWidgets.QMessageBox.question(
+ table,
+ 'Tag deletion',
+ 'Do you really want to delete the selected tags ?',
+ QtWidgets.QMessageBox.No | QtWidgets.QMessageBox.Yes,
+ QtWidgets.QMessageBox.Yes)
+
+ if confirm == QtWidgets.QMessageBox.Yes:
+ db.tags.delete(database.cursor(), table.model().row_ids(rows))
+ database.commit()
+ table.model().delete_tags(rows)
+
+def widget(
+ parent: QtWidgets.QWidget,
+ title: str,
+ action_title: str,
+ tag: Tag,
+ on_validated):
+
+ dialog = QtWidgets.QDialog(parent)
+ dialog.setWindowTitle(title)
+ dialog.setMinimumSize(QtCore.QSize(320, 240))
+
+ layout = QtWidgets.QVBoxLayout(dialog)
+ dialog.setLayout(layout)
+
+ def on_dialog_validated(form):
+ dialog.accept()
+ on_validated(form)
+
+ layout.addWidget(gui.tags.form.widget.widget(
+ parent = dialog,
+ action_title = action_title,
+ tag = tag,
+ on_validated = on_dialog_validated,
+ on_cancel = lambda: dialog.reject()))
+
+ return dialog
diff --git a/src/gui/tags/form/state.py b/src/gui/tags/form/state.py
new file mode 100644
index 0000000..931e67a
--- /dev/null
+++ b/src/gui/tags/form/state.py
@@ -0,0 +1,58 @@
+from PyQt5 import QtCore
+from typing import Optional
+
+from model.tag import ValidTagForm
+
+class TagFormEdition:
+ def __init__(
+ self,
+ name,
+ name_signal,
+ color,
+ color_signal):
+
+ self._name = name
+ self._color = color
+ self._signal = ValidTagFormSignal()
+
+ name_signal.connect(lambda n: self.on_name_signal(n))
+ color_signal.connect(lambda d: self.on_color_signal(d))
+
+ def get(self) -> Optional[ValidTagForm]:
+ name = self._name.strip()
+ color = self._color.strip()
+
+ if name and color:
+ return ValidTagForm(
+ name = name,
+ color = color)
+ else:
+ return None
+
+ def on_name_signal(self, name: str):
+ self._name = name
+ self.emit()
+
+ def on_color_signal(self, color: str):
+ self._color = color
+ self.emit()
+
+ def emit(self):
+ validForm = self.get()
+ if validForm:
+ self._signal.emit(validForm)
+
+ def signal(self):
+ return self._signal
+
+class ValidTagFormSignal(QtCore.QObject):
+ _signal = QtCore.pyqtSignal(ValidTagForm, name = 'validTagForm')
+
+ def __init__(self):
+ QtCore.QObject.__init__(self)
+
+ def emit(self, form: Optional[ValidTagForm]):
+ self._signal.emit(form)
+
+ def connect(self, f):
+ self._signal.connect(f)
diff --git a/src/gui/tags/form/widget.py b/src/gui/tags/form/widget.py
new file mode 100644
index 0000000..92a5db2
--- /dev/null
+++ b/src/gui/tags/form/widget.py
@@ -0,0 +1,135 @@
+from PyQt5 import QtWidgets, QtCore, QtGui
+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.color
+
+def widget(
+ parent: QtWidgets.QWidget,
+ action_title: str,
+ tag: Tag,
+ on_validated,
+ on_cancel):
+
+ widget = QtWidgets.QWidget(parent)
+ layout = QtWidgets.QVBoxLayout(widget)
+ widget.setLayout(layout)
+
+ grid = QtWidgets.QWidget(widget)
+ layout.addWidget(grid)
+ grid_layout = QtWidgets.QGridLayout(grid)
+ grid.setLayout(grid_layout)
+
+ init_name = tag.name if tag is not None else ''
+ name_input = line_edit(grid, grid_layout, 0, 'Name', init_name)
+
+ 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(
+ init_name,
+ name_input.textChanged,
+ init_color,
+ color_input.textChanged)
+
+ def on_validate():
+ form = tag_form_edition.get()
+ if form:
+ on_validated(form)
+
+ layout.addWidget(buttons(
+ parent = widget,
+ action_title = action_title,
+ tag_form_signal = tag_form_edition.signal(),
+ on_validate = on_validate,
+ on_cancel = on_cancel))
+
+ return widget
+
+def line_edit(
+ parent,
+ layout: QtWidgets.QGridLayout,
+ n: int,
+ label: str,
+ default_value: str) -> QtWidgets.QLineEdit:
+
+ label = QtWidgets.QLabel(label, parent)
+ layout.addWidget(label, n, 0)
+
+ edit = QtWidgets.QLineEdit(parent)
+ if default_value != None:
+ edit.setText(default_value)
+ layout.addWidget(edit, n, 1)
+
+ return edit
+
+def color_edit(
+ parent,
+ layout: QtWidgets.QGridLayout,
+ n: int,
+ label: str,
+ init_color: QtGui.QColor) -> QtWidgets.QLineEdit:
+
+ label = QtWidgets.QLabel(label, parent)
+ layout.addWidget(label, n, 0)
+
+ edit = ColorInput(init_color, parent)
+ layout.addWidget(edit, n, 1)
+
+ return edit
+
+class ColorInput(QtWidgets.QLineEdit):
+
+ def __init__(self, init_color: QtGui.QColor, parent):
+ super().__init__(parent)
+ self.setReadOnly(True)
+ self.installEventFilter(self)
+ self._color = init_color
+ self.update(init_color)
+ self._is_editing = False
+
+ def eventFilter(self, source, event):
+ if source is self and event.type() == QtCore.QEvent.FocusIn:
+ if not self._is_editing:
+ self._is_editing = True
+ color = QtWidgets.QColorDialog.getColor(self._color, self)
+ self.update(color)
+ else:
+ self._is_editing = False
+ self.clearFocus()
+ return super(ColorInput, self).eventFilter(source, event)
+
+ def update(self, color: QtGui.QColor):
+ self.setText(color.name().upper())
+ palette = QtGui.QPalette()
+ palette.setColor(QtGui.QPalette.Base, color)
+ palette.setColor(QtGui.QPalette.Text, color)
+ self.setPalette(palette)
+
+def buttons(parent, action_title, tag_form_signal, on_validate, on_cancel):
+ widget = QtWidgets.QWidget(parent)
+ layout = QtWidgets.QHBoxLayout(widget)
+
+ validate = QtWidgets.QPushButton(action_title, widget)
+ validate.setDisabled(True)
+ validate.setIcon(gui.icon.dialog_apply(validate.style()))
+ validate.clicked.connect(on_validate);
+ layout.addWidget(validate)
+
+ def on_tag_form_signal(form: Optional[ValidTagForm]):
+ if form:
+ validate.setEnabled(True)
+ else:
+ validate.setDisabled(True)
+
+ tag_form_signal.connect(on_tag_form_signal)
+
+ cancel = QtWidgets.QPushButton('cancel', widget)
+ cancel.setIcon(gui.icon.dialog_cancel(cancel.style()))
+ cancel.clicked.connect(on_cancel)
+ layout.addWidget(cancel)
+
+ return widget
diff --git a/src/gui/tags/signal.py b/src/gui/tags/signal.py
new file mode 100644
index 0000000..57ec696
--- /dev/null
+++ b/src/gui/tags/signal.py
@@ -0,0 +1,27 @@
+from PyQt5 import QtCore
+
+from model.tag import Tag
+
+class AddTag(QtCore.QObject):
+ _signal = QtCore.pyqtSignal(Tag, name = 'addTag')
+
+ def __init__(self):
+ QtCore.QObject.__init__(self)
+
+ def emit(self, tag):
+ self._signal.emit(tag)
+
+ def get(self):
+ return self._signal
+
+class UpdateTag(QtCore.QObject):
+ _signal = QtCore.pyqtSignal(int, Tag, name = 'updateTag')
+
+ def __init__(self):
+ QtCore.QObject.__init__(self)
+
+ def emit(self, row, tag):
+ self._signal.emit(row, tag)
+
+ def get(self):
+ return self._signal
diff --git a/src/gui/tags/table/menu.py b/src/gui/tags/table/menu.py
new file mode 100644
index 0000000..7acdda1
--- /dev/null
+++ b/src/gui/tags/table/menu.py
@@ -0,0 +1,25 @@
+from PyQt5 import QtWidgets
+
+import db.tags
+import gui.tags.dialog
+from model.tag import Tag, ValidTagForm
+
+def open(database, table, update_tag_signal, position):
+ rows = set([index.row() for index in table.selectedIndexes()])
+
+ menu = QtWidgets.QMenu(table)
+
+ if len(rows) == 1:
+ modify_action = menu.addAction(gui.icon.dialog_open(menu.style()), 'modify')
+ else:
+ modify_action = QtWidgets.QAction(menu)
+
+ delete_action = menu.addAction(gui.icon.trash(menu.style()), 'delete')
+
+ action = menu.exec_(table.mapToGlobal(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_()
+ elif action == delete_action:
+ gui.tags.dialog.show_delete(database, table, rows)
diff --git a/src/gui/tags/table/model.py b/src/gui/tags/table/model.py
new file mode 100644
index 0000000..7c66b5d
--- /dev/null
+++ b/src/gui/tags/table/model.py
@@ -0,0 +1,103 @@
+from PyQt5 import QtCore, QtWidgets, QtGui
+from PyQt5.QtCore import Qt
+
+from model.tag import Tag
+import time
+import math
+import util.array
+import util.range
+import gui.color
+
+columns = 1
+
+headers = ['Name', 'Color']
+
+default_sort = (0, Qt.AscendingOrder)
+
+class TableModel(QtCore.QAbstractTableModel):
+ def __init__(self, tags):
+ super(TableModel, self).__init__()
+ self._tags = tags
+
+ 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):
+ tag = self._tags[index.row()]
+
+ if role == Qt.DisplayRole:
+ if index.column() == 0:
+ return tag.name
+ elif index.column() == 1:
+ return tag.color
+ elif role == Qt.BackgroundRole:
+ return QtGui.QBrush(QtGui.QColor(tag.color))
+ else:
+ return QtCore.QVariant()
+
+ def rowCount(self, index):
+ return len(self._tags)
+
+ def columnCount(self, index):
+ return columns
+
+ def get_at(self, row):
+ if row >= 0 and row < len(self._tags):
+ return self._tags[row]
+
+ def insert_tag(self, header: QtWidgets.QHeaderView, tag: Tag) -> int:
+ at = self.insert_position(header, tag)
+ self.beginInsertRows(QtCore.QModelIndex(), at, at)
+ self._tags.insert(at, tag)
+ self.endInsertRows()
+ return at
+
+ def insert_position(self, header: QtWidgets.QHeaderView, tag: Tag) -> int:
+ row = header.sortIndicatorSection()
+ order = header.sortIndicatorOrder()
+ is_rev = is_reversed(row, order)
+ return util.array.insert_position(
+ sort_key(tag, row, is_rev),
+ [sort_key(t, row, is_rev) for t in self._tags],
+ is_rev)
+
+ def update_tag(self, header: QtWidgets.QHeaderView, row, tag: Tag) -> int:
+ self.delete_tag_range(row, 1)
+ return self.insert_tag(header, tag)
+
+ def delete_tags(self, indexes):
+ for range in reversed(util.range.from_indexes(indexes)):
+ self.delete_tag_range(range.start, range.length)
+ return True
+
+ def delete_tag_range(self, row, rows):
+ self.beginRemoveRows(QtCore.QModelIndex(), row, row + rows - 1)
+ self._tags = self._tags[:row] + self._tags[row + rows:]
+ self.endRemoveRows()
+ return True
+
+ def row_ids(self, rows):
+ return [tag.id for i, tag in enumerate(self._tags) if i in rows]
+
+ def sort(self, row: int, order: Qt.SortOrder):
+ self.layoutAboutToBeChanged.emit()
+ is_rev = is_reversed(row, order)
+ self._tags = sorted(
+ self._tags,
+ key = lambda tag: sort_key(tag, row, is_rev),
+ reverse = is_rev)
+ self.layoutChanged.emit()
+
+def sort_key(tag: Tag, row: int, is_reversed: bool):
+ if row == 0:
+ return tag.name.lower()
+ elif row == 1:
+ return tag.color
+
+def is_reversed(row: int, order: Qt.SortOrder) -> bool:
+ return order == Qt.DescendingOrder
diff --git a/src/gui/tags/table/widget.py b/src/gui/tags/table/widget.py
new file mode 100644
index 0000000..89c9990
--- /dev/null
+++ b/src/gui/tags/table/widget.py
@@ -0,0 +1,71 @@
+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
+from model.tag import Tag, ValidTagForm
+
+class Widget(QtWidgets.QTableView):
+
+ def __init__(self, database, parent, add_tag_signal):
+ super().__init__(parent)
+
+ self._database = database
+ self._update_tag_signal = gui.tags.signal.UpdateTag()
+
+ tags = db.tags.get(self._database.cursor())
+ table_model = gui.tags.table.model.TableModel(tags)
+
+ self.setModel(table_model)
+ self.sortByColumn(
+ gui.tags.table.model.default_sort[0],
+ gui.tags.table.model.default_sort[1])
+ self.setSortingEnabled(True)
+ self.setSelectionBehavior(QtWidgets.QTableView.SelectRows)
+ self.horizontalHeader().setStretchLastSection(True)
+ self.resizeColumns()
+
+ self.doubleClicked.connect(lambda index: self.on_double_click(index.row()))
+
+ # # Menu
+ self.setContextMenuPolicy(Qt.CustomContextMenu)
+ self.customContextMenuRequested.connect(lambda position: gui.tags.table.menu.open(self._database, 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))
+
+ def insert(self, tag):
+ self.model().insert_tag(self.horizontalHeader(), tag)
+ self.resizeColumns()
+
+ def update(self, row, tag):
+ row = self.model().update_tag(self.horizontalHeader(), row, tag)
+ self.selectRow(row)
+ self.resizeColumns()
+
+ def resizeColumns(self):
+ for column in range(gui.tags.table.model.columns):
+ self.resizeColumnToContents(column)
+
+ def keyPressEvent(self, event):
+ super().keyPressEvent(event)
+ if event.key() in (Qt.Key_Return, Qt.Key_Enter):
+ rows = self.get_selected_rows()
+ 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_()
+ elif event.key() == Qt.Key_Delete:
+ rows = self.get_selected_rows()
+ gui.tags.dialog.show_delete(self._database, 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_()
diff --git a/src/gui/tags/widget.py b/src/gui/tags/widget.py
new file mode 100644
index 0000000..a9d870e
--- /dev/null
+++ b/src/gui/tags/widget.py
@@ -0,0 +1,26 @@
+from PyQt5 import QtWidgets
+
+import gui.tags.dialog
+import gui.tags.signal
+import gui.tags.table.widget
+import gui.icon
+
+def widget(database, parent):
+ widget = QtWidgets.QWidget(parent)
+
+ layout = QtWidgets.QVBoxLayout(widget)
+ widget.setLayout(layout)
+
+ add_tag_signal = gui.tags.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_())
+ layout.addWidget(add_tag_button)
+
+ table = gui.tags.table.widget.Widget(database, widget, add_tag_signal)
+ layout.addWidget(table)
+
+ return widget
diff --git a/src/gui/tasks/form/state.py b/src/gui/tasks/form/state.py
index 727bedd..9bd3ae3 100644
--- a/src/gui/tasks/form/state.py
+++ b/src/gui/tasks/form/state.py
@@ -1,6 +1,5 @@
-import re
from PyQt5 import QtCore
-from typing import NamedTuple, Optional
+from typing import Optional
from model.task import ValidTaskForm
from model.difficulty import Difficulty
diff --git a/src/gui/tasks/form/widget.py b/src/gui/tasks/form/widget.py
index adaf757..9feaad0 100644
--- a/src/gui/tasks/form/widget.py
+++ b/src/gui/tasks/form/widget.py
@@ -1,7 +1,6 @@
from PyQt5 import QtWidgets, QtCore
from typing import Optional, Tuple, List, Any
-import db.tasks
from model.task import Task, ValidTaskForm
from model import difficulty, priority
import gui.icon
diff --git a/src/gui/tasks/table/menu.py b/src/gui/tasks/table/menu.py
index edc9833..435ff25 100644
--- a/src/gui/tasks/table/menu.py
+++ b/src/gui/tasks/table/menu.py
@@ -14,7 +14,7 @@ def open(database, table, update_task_signal, position):
else:
modify_action = QtWidgets.QAction(menu)
- delete_action = menu.addAction(gui.icon.trash(menu.style()), 'Delete')
+ delete_action = menu.addAction(gui.icon.trash(menu.style()), 'delete')
action = menu.exec_(table.mapToGlobal(position))
if action == modify_action and len(rows) == 1:
diff --git a/src/gui/tasks/table/model.py b/src/gui/tasks/table/model.py
index bf9e386..ab969ec 100644
--- a/src/gui/tasks/table/model.py
+++ b/src/gui/tasks/table/model.py
@@ -35,7 +35,6 @@ class TableModel(QtCore.QAbstractTableModel):
task = self._tasks[index.row()]
if role == Qt.DisplayRole:
- task = self._tasks[index.row()]
if index.column() == 0:
return age_since(task.created_at)
elif index.column() == 1:
diff --git a/src/gui/tasks/widget.py b/src/gui/tasks/widget.py
index 61ad605..ea4acb9 100644
--- a/src/gui/tasks/widget.py
+++ b/src/gui/tasks/widget.py
@@ -1,9 +1,8 @@
-from PyQt5 import QtWidgets, QtCore
+from PyQt5 import QtWidgets
import gui.tasks.signal
import gui.tasks.table.widget
import gui.icon
-from model.task import ValidTaskForm
def widget(database, parent):
widget = QtWidgets.QWidget(parent)
diff --git a/src/gui/window.py b/src/gui/window.py
index aa22f7e..622f65d 100644
--- a/src/gui/window.py
+++ b/src/gui/window.py
@@ -1,13 +1,17 @@
from PyQt5 import QtCore, QtWidgets
import gui.tasks.widget
+import gui.tags.widget
def get(database):
window = QtWidgets.QMainWindow()
window.setWindowTitle("todo-next")
window.setMinimumSize(QtCore.QSize(640, 480))
- centralWidget = QtWidgets.QWidget(window)
- window.setCentralWidget(gui.tasks.widget.widget(database, centralWidget))
+ 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')
return window
diff --git a/src/main.py b/src/main.py
index e791196..c4ff4e9 100644
--- a/src/main.py
+++ b/src/main.py
@@ -11,11 +11,6 @@ args = arguments.parser().parse_args()
database = db.init.init(args.database if args.database != None else 'database')
app = QtWidgets.QApplication(sys.argv)
-# # Allows to catch Ctrl-C event
-# timer = QtCore.QTimer()
-# timer.timeout.connect(lambda: None)
-# timer.start(100)
-
window = gui.window.get(database)
window.show()
res = app.exec_()
diff --git a/src/model/tag.py b/src/model/tag.py
new file mode 100644
index 0000000..030b223
--- /dev/null
+++ b/src/model/tag.py
@@ -0,0 +1,12 @@
+from typing import NamedTuple
+
+class Tag(NamedTuple):
+ id: int
+ created_at: int
+ updated_at: int
+ name: str
+ color: str
+
+class ValidTagForm(NamedTuple):
+ name: str
+ color: str
diff --git a/src/model/task.py b/src/model/task.py
index f5e0ae5..4bb9a89 100644
--- a/src/model/task.py
+++ b/src/model/task.py
@@ -6,7 +6,7 @@ from model.priority import Priority
class Task(NamedTuple):
id: int
created_at: int
- modified_at: int
+ updated_at: int
name: str
duration: int
tag: str