aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJoris2020-06-06 17:44:26 +0200
committerJoris2020-06-06 19:54:03 +0200
commit1595e0de940a86a7810df0e02e43838d97c0d846 (patch)
tree9701eeec0d98baa9f6044b1911df68e4c8539819 /src
parent6b9195000eb5404c247288b384d7ca2bacc1ab23 (diff)
Provide nix build
Diffstat (limited to 'src')
-rw-r--r--src/arguments.py13
-rw-r--r--src/database.py19
-rw-r--r--src/db/init.py51
-rw-r--r--src/db/tags.py72
-rw-r--r--src/db/task_tags.py39
-rw-r--r--src/db/tasks.py103
-rw-r--r--src/gui/color.py21
-rw-r--r--src/gui/icon.py27
-rw-r--r--src/gui/signal.py13
-rw-r--r--src/gui/tags/list.py47
-rw-r--r--src/gui/tags/panel/dialog.py65
-rw-r--r--src/gui/tags/panel/form/state.py58
-rw-r--r--src/gui/tags/panel/form/widget.py137
-rw-r--r--src/gui/tags/panel/signal.py27
-rw-r--r--src/gui/tags/panel/table/menu.py36
-rw-r--r--src/gui/tags/panel/table/model.py104
-rw-r--r--src/gui/tags/panel/table/widget.py73
-rw-r--r--src/gui/tags/panel/widget.py30
-rw-r--r--src/gui/tasks/dialog.py78
-rw-r--r--src/gui/tasks/duration.py50
-rw-r--r--src/gui/tasks/form/state.py100
-rw-r--r--src/gui/tasks/form/widget.py184
-rw-r--r--src/gui/tasks/signal.py28
-rw-r--r--src/gui/tasks/table/menu.py49
-rw-r--r--src/gui/tasks/table/widget.py284
-rw-r--r--src/gui/tasks/test_duration.py21
-rw-r--r--src/gui/tasks/widget.py30
-rw-r--r--src/gui/window.py36
-rw-r--r--src/main.py20
-rw-r--r--src/model/difficulty.py30
-rw-r--r--src/model/priority.py30
-rw-r--r--src/model/status.py30
-rw-r--r--src/model/tag.py12
-rw-r--r--src/model/task.py22
-rw-r--r--src/model/task_tag.py5
-rw-r--r--src/service/tasks.py32
-rw-r--r--src/util/array.py5
-rw-r--r--src/util/range.py30
-rw-r--r--src/util/test_array.py12
-rw-r--r--src/util/test_range.py6
40 files changed, 0 insertions, 2029 deletions
diff --git a/src/arguments.py b/src/arguments.py
deleted file mode 100644
index bb60dce..0000000
--- a/src/arguments.py
+++ /dev/null
@@ -1,13 +0,0 @@
-import argparse
-
-def parser() -> argparse.ArgumentParser:
- parser = argparse.ArgumentParser(
- usage = "%(prog)s [OPTION]",
- description = "Manage a context-base next-action list."
- )
- parser.add_argument(
- "-v", "--version", action = "version",
- version = f"{parser.prog} version 1.0.0"
- )
- parser.add_argument("-d", "--database")
- return parser
diff --git a/src/database.py b/src/database.py
deleted file mode 100644
index 478f62e..0000000
--- a/src/database.py
+++ /dev/null
@@ -1,19 +0,0 @@
-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
deleted file mode 100644
index 5d847a3..0000000
--- a/src/db/init.py
+++ /dev/null
@@ -1,51 +0,0 @@
-import sqlite3
-import os.path
-import time
-
-def init(path):
-
- is_db_new = not os.path.isfile(path)
-
- database = sqlite3.connect(path)
-
- cursor = database.cursor()
-
- if is_db_new:
-
- cursor.execute(
- " CREATE TABLE IF NOT EXISTS tasks("
- " id INTEGER PRIMARY KEY,"
- " created_at INTEGER NOT NULL,"
- " updated_at INTEGER NOT NULL,"
- " name TEXT NOT NULL,"
- " duration INTEGER,"
- " difficulty INT,"
- " priority INT,"
- " description TEXT,"
- " status 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"
- " )")
-
- 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)"
- " )")
-
- cursor.execute("PRAGMA foreign_keys = ON")
-
- database.commit()
-
- return database
diff --git a/src/db/tags.py b/src/db/tags.py
deleted file mode 100644
index 666bd1e..0000000
--- a/src/db/tags.py
+++ /dev/null
@@ -1,72 +0,0 @@
-from sqlite3 import Cursor
-import time
-from typing import List
-
-from model.tag import Tag, ValidTagForm
-
-def get(cursor: Cursor) -> List[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/task_tags.py b/src/db/task_tags.py
deleted file mode 100644
index e8c0ee0..0000000
--- a/src/db/task_tags.py
+++ /dev/null
@@ -1,39 +0,0 @@
-from sqlite3 import Cursor
-import time
-from typing import List
-
-from model.task_tag import TaskTag
-
-def one_is_used(cursor: Cursor, tag_ids: List[int]) -> bool:
- if len(tag_ids) >= 1:
- cursor.execute(
- "SELECT task_id FROM task_tags WHERE tag_id IN (%s) LIMIT 1" % ",".join("?"*len(tag_ids)),
- tag_ids)
- return len(cursor.fetchall()) == 1
- else:
- return False
-
-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
deleted file mode 100644
index efb88d6..0000000
--- a/src/db/tasks.py
+++ /dev/null
@@ -1,103 +0,0 @@
-from sqlite3 import Cursor
-import time
-from typing import List
-
-from model.task import Task, ValidTaskForm
-from model.status import Status
-from model import difficulty, priority, status
-
-def get(cursor: Cursor, s: Status) -> List[Task]:
- cursor.execute(
- " SELECT"
- " id,"
- " created_at,"
- " updated_at,"
- " name,"
- " duration,"
- " difficulty,"
- " priority,"
- " description"
- " FROM"
- " tasks"
- " WHERE"
- " status = ?",
- (status.format(s),))
-
- res = []
-
- for task in cursor.fetchall():
- res.append(Task(
- id = task[0],
- created_at = task[1],
- updated_at = task[2],
- name = task[3],
- duration = task[4],
- difficulty = difficulty.parse(task[5]),
- priority = priority.parse(task[6]),
- description = task[7]
- ))
-
- return res
-
-def insert(cursor: Cursor, s: Status, form: ValidTaskForm):
- now = int(time.time())
- cursor.execute(
- " INSERT INTO tasks("
- " created_at,"
- " updated_at,"
- " name,"
- " duration,"
- " difficulty,"
- " priority,"
- " description,"
- " status"
- " ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
- (now, now, form.name, form.duration, difficulty.format(form.difficulty), priority.format(form.priority), form.description, status.format(s)))
-
- return Task(
- id = cursor.lastrowid,
- created_at = now,
- updated_at = now,
- name = form.name,
- duration = form.duration,
- difficulty = form.difficulty,
- priority = form.priority,
- description = form.description
- )
-
-def update(cursor: Cursor, task: Task, form: ValidTaskForm):
- now = int(time.time())
-
- cursor.execute(
- " UPDATE tasks SET"
- " updated_at = ?,"
- " name = ?,"
- " duration = ?,"
- " difficulty = ?,"
- " priority = ?,"
- " description = ?"
- " WHERE id = ?",
- (now, form.name, form.duration, difficulty.format(form.difficulty), priority.format(form.priority), form.description, task.id))
-
- return Task(
- id = task.id,
- created_at = task.created_at,
- updated_at = now,
- name = form.name,
- duration = form.duration,
- difficulty = form.difficulty,
- priority = form.priority,
- description = form.description
- )
-
-def delete(cursor: Cursor, ids: List[int]):
- if len(ids) >= 1:
- cursor.execute(
- "DELETE FROM tasks WHERE id IN (%s)" % ",".join("?"*len(ids)),
- ids)
-
-def update_status(cursor: Cursor, ids: List[int], s: Status):
- if len(ids) >= 1:
- cursor.execute(
- "UPDATE tasks SET status = ? WHERE id IN (%s)" % ",".join("?"*len(ids)),
- [status.format(s)] + ids)
diff --git a/src/gui/color.py b/src/gui/color.py
deleted file mode 100644
index cc7e5a8..0000000
--- a/src/gui/color.py
+++ /dev/null
@@ -1,21 +0,0 @@
-from PyQt5 import QtGui
-
-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
-hard_difficulty = red
-
-low_priority = green
-middle_priority = orange
-high_priority = red
-
-short_duration = green
-medium_duration = orange
-long_duration = red
diff --git a/src/gui/icon.py b/src/gui/icon.py
deleted file mode 100644
index 7e2156d..0000000
--- a/src/gui/icon.py
+++ /dev/null
@@ -1,27 +0,0 @@
-from PyQt5 import QtWidgets
-
-# List of icons: https://joekuan.wordpress.com/2015/09/23/list-of-qt-icons/
-
-def task_ready(style):
- return style.standardIcon(QtWidgets.QStyle.SP_DialogApplyButton)
-
-def task_waiting(style):
- return style.standardIcon(QtWidgets.QStyle.SP_BrowserReload)
-
-def task_maybe(style):
- return style.standardIcon(QtWidgets.QStyle.SP_TitleBarContextHelpButton)
-
-def new_folder(style):
- return style.standardIcon(QtWidgets.QStyle.SP_FileDialogNewFolder)
-
-def dialog_open(style):
- return style.standardIcon(QtWidgets.QStyle.SP_DialogOpenButton)
-
-def dialog_ok(style):
- return style.standardIcon(QtWidgets.QStyle.SP_DialogOkButton)
-
-def dialog_cancel(style):
- return style.standardIcon(QtWidgets.QStyle.SP_DialogCancelButton)
-
-def trash(style):
- return style.standardIcon(QtWidgets.QStyle.SP_TrashIcon)
diff --git a/src/gui/signal.py b/src/gui/signal.py
deleted file mode 100644
index 99100f1..0000000
--- a/src/gui/signal.py
+++ /dev/null
@@ -1,13 +0,0 @@
-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
deleted file mode 100644
index f7eeba3..0000000
--- a/src/gui/tags/list.py
+++ /dev/null
@@ -1,47 +0,0 @@
-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/panel/dialog.py b/src/gui/tags/panel/dialog.py
deleted file mode 100644
index f0ca986..0000000
--- a/src/gui/tags/panel/dialog.py
+++ /dev/null
@@ -1,65 +0,0 @@
-from PyQt5 import QtCore, QtWidgets
-
-from model.tag import Tag, ValidTagForm
-
-import db.tags
-import gui.tags.panel.form.widget
-import database
-
-def add(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(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(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.panel.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/panel/form/state.py b/src/gui/tags/panel/form/state.py
deleted file mode 100644
index fbec956..0000000
--- a/src/gui/tags/panel/form/state.py
+++ /dev/null
@@ -1,58 +0,0 @@
-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/panel/form/widget.py b/src/gui/tags/panel/form/widget.py
deleted file mode 100644
index 7079e57..0000000
--- a/src/gui/tags/panel/form/widget.py
+++ /dev/null
@@ -1,137 +0,0 @@
-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.panel.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.panel.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)
- 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)
- 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_ok(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/panel/signal.py b/src/gui/tags/panel/signal.py
deleted file mode 100644
index 022abde..0000000
--- a/src/gui/tags/panel/signal.py
+++ /dev/null
@@ -1,27 +0,0 @@
-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 connect(self, f):
- self._signal.connect(f)
-
-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 connect(self, f):
- self._signal.connect(f)
diff --git a/src/gui/tags/panel/table/menu.py b/src/gui/tags/panel/table/menu.py
deleted file mode 100644
index f9ee148..0000000
--- a/src/gui/tags/panel/table/menu.py
+++ /dev/null
@@ -1,36 +0,0 @@
-from PyQt5 import QtWidgets, QtCore
-
-from model.tag import Tag, ValidTagForm
-import database
-import db.tags
-import db.task_tags
-import gui.tags.panel.dialog
-
-def open(table, update_tag_signal, position):
- rows = set([index.row() for index in table.selectedIndexes()])
-
- menu = QtWidgets.QMenu(table)
-
- actions = 0
-
- if len(rows) == 1:
- modify_action = menu.addAction(gui.icon.dialog_open(menu.style()), "modify")
- actions += 1
- else:
- modify_action = QtWidgets.QAction(menu)
-
- tags = table.model().row_ids(rows)
- if not db.task_tags.one_is_used(database.cursor(), tags):
- delete_action = menu.addAction(gui.icon.trash(menu.style()), "delete")
- actions += 1
- else:
- delete_action = QtWidgets.QAction(menu)
-
- if actions > 0:
- action = menu.exec_(table.mapToGlobal(position + QtCore.QPoint(15, 20)))
- if action == modify_action and len(rows) == 1:
- row = list(rows)[0]
- tag = table.model().get_at(row)
- gui.tags.panel.dialog.update(table, update_tag_signal, row, tag).exec_()
- elif action == delete_action:
- gui.tags.panel.dialog.show_delete(table, rows)
diff --git a/src/gui/tags/panel/table/model.py b/src/gui/tags/panel/table/model.py
deleted file mode 100644
index 00ca785..0000000
--- a/src/gui/tags/panel/table/model.py
+++ /dev/null
@@ -1,104 +0,0 @@
-from PyQt5 import QtCore, QtWidgets, QtGui
-from PyQt5.QtCore import Qt
-from typing import List
-
-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/panel/table/widget.py b/src/gui/tags/panel/table/widget.py
deleted file mode 100644
index 0ef67c2..0000000
--- a/src/gui/tags/panel/table/widget.py
+++ /dev/null
@@ -1,73 +0,0 @@
-from PyQt5 import QtWidgets
-from PyQt5.QtCore import Qt
-
-from model.tag import Tag, ValidTagForm
-import database
-import db.tags
-import db.task_tags
-import gui.tags.panel.dialog
-import gui.tags.panel.signal
-import gui.tags.panel.table.menu
-import gui.tags.panel.table.model
-
-class Widget(QtWidgets.QTableView):
-
- def __init__(self, parent, add_tag_signal):
- super().__init__(parent)
-
- self._update_tag_signal = gui.tags.panel.signal.UpdateTag()
-
- tags = db.tags.get(database.cursor())
- table_model = gui.tags.panel.table.model.TableModel(tags)
-
- self.setModel(table_model)
- self.sortByColumn(
- 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)
- 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.panel.table.menu.open(self, self._update_tag_signal, position))
-
- 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)
- 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.panel.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.panel.dialog.update(self, self._update_tag_signal, row, tag).exec_()
- elif event.key() == Qt.Key_Delete:
- rows = self.get_selected_rows()
- tags = self.model().row_ids(rows)
- if not db.task_tags.one_is_used(database.cursor(), tags):
- 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.panel.dialog.update(self, self._update_tag_signal, row, tag).exec_()
diff --git a/src/gui/tags/panel/widget.py b/src/gui/tags/panel/widget.py
deleted file mode 100644
index faca1da..0000000
--- a/src/gui/tags/panel/widget.py
+++ /dev/null
@@ -1,30 +0,0 @@
-from PyQt5 import QtWidgets
-
-import gui.tags.panel.dialog
-import gui.tags.panel.signal
-import gui.tags.panel.table.widget
-import gui.icon
-
-def widget(parent):
- widget = QtWidgets.QWidget(parent)
-
- layout = QtWidgets.QVBoxLayout(widget)
- widget.setLayout(layout)
-
- layout.addSpacing(15)
-
- add_tag_signal = gui.tags.panel.signal.AddTag()
-
- add_tag_button = QtWidgets.QPushButton(" Add a tag", widget)
- add_tag_button.setFixedHeight(30)
- add_tag_button.setIcon(gui.icon.new_folder(widget.style()))
-
- add_tag_button.clicked.connect(lambda: gui.tags.panel.dialog.add(widget, add_tag_signal).exec_())
- layout.addWidget(add_tag_button)
-
- layout.addSpacing(20)
-
- 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
deleted file mode 100644
index 2bf3b6b..0000000
--- a/src/gui/tasks/dialog.py
+++ /dev/null
@@ -1,78 +0,0 @@
-from PyQt5 import QtCore, QtWidgets
-from typing import List
-
-from model.tag import Tag
-from model.task import Task, ValidTaskForm
-from model.status import Status
-import database
-import db.task_tags
-import db.tasks
-import gui.tasks.form.widget
-import service.tasks
-
-def add(parent_widget, status: Status, add_task_signal):
-
- def on_add(task_form: ValidTaskForm):
- task = service.tasks.create(database.cursor(), status, task_form)
- add_task_signal.emit(task, task_form.tags)
-
- return widget(parent_widget, "Add a task", "add", None, [], on_add)
-
-def update(parent_widget, update_task_signal, row: int, task: Task, tags: List[int]):
-
- 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, tags, on_update)
-
-def confirm_delete(parent, rows: List[int], on_confirm):
- confirm = QtWidgets.QMessageBox.question(
- 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:
- on_confirm()
-
-def confirm_move(parent, rows: List[int], move_to: Status, on_confirm):
- confirm = QtWidgets.QMessageBox.question(
- parent,
- "Task move",
- "Do you really want to move the selected tasks ?",
- QtWidgets.QMessageBox.No | QtWidgets.QMessageBox.Yes,
- QtWidgets.QMessageBox.Yes)
-
- if confirm == QtWidgets.QMessageBox.Yes:
- on_confirm()
-
-def widget(
- parent: QtWidgets.QWidget,
- title: str,
- action_title: str,
- task: Task,
- tags: List[int],
- 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.tasks.form.widget.widget(
- parent = dialog,
- action_title = action_title,
- task = task,
- tags = tags,
- on_validated = on_dialog_validated,
- on_cancel = lambda: dialog.reject()))
-
- return dialog
diff --git a/src/gui/tasks/duration.py b/src/gui/tasks/duration.py
deleted file mode 100644
index dc948e6..0000000
--- a/src/gui/tasks/duration.py
+++ /dev/null
@@ -1,50 +0,0 @@
-from PyQt5 import QtGui
-from typing import Optional
-import math
-import re
-
-import gui.color
-
-def format(minutes: int):
- if minutes >= 60 * 24:
- return "" + format_decimal(minutes / 60 / 24) + "d"
- elif minutes >= 60:
- return "" + format_decimal(minutes / 60) + "h"
- elif minutes > 0:
- return "" + str(minutes) + "m"
- else:
- return ""
-
-def format_decimal(d: float) -> str:
- return "{0:.2g}".format(d)
-
-def parse(duration: str) -> Optional[int]:
- duration = duration.strip()
- if duration:
- result = re.match("^(\d+)(\.(\d+))?([mhd])$", duration.strip())
- if result:
- n = int(result.group(1))
- if result.group(3):
- d = int(result.group(3)) * pow(10, -1 * len(result.group(3)))
- else:
- d = 0
- num = n + d
- unit = result.group(4)
- if unit == "m":
- return math.floor(num)
- elif unit == "h":
- return math.floor(num * 60)
- elif unit == "d":
- return math.floor(num * 60 * 24)
- else:
- return None
- else:
- return 0
-
-def color(minutes: int):
- if minutes <= 15:
- return gui.color.short_duration
- elif minutes < 60:
- return gui.color.medium_duration
- else:
- return gui.color.long_duration
diff --git a/src/gui/tasks/form/state.py b/src/gui/tasks/form/state.py
deleted file mode 100644
index 09e658e..0000000
--- a/src/gui/tasks/form/state.py
+++ /dev/null
@@ -1,100 +0,0 @@
-from PyQt5 import QtCore
-from typing import Optional
-
-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__(
- self,
- name,
- name_signal,
- duration,
- duration_signal,
- difficulty,
- difficulty_signal,
- priority,
- priority_signal,
- tags_signal: gui.tags.list.SelectionSignal,
- description,
- description_signal):
-
- self._name = name
- self._duration = duration
- 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))
- 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)
- difficulty = self._difficulty
- priority = self._priority
- description = self._description.strip()
-
- if name and duration != None:
- return ValidTaskForm(
- name = name,
- duration = duration,
- difficulty = difficulty,
- priority = priority,
- tags = self._tags,
- description = description)
- else:
- return None
-
- def on_name_signal(self, name: str):
- self._name = name
- self.emit()
-
- def on_duration_signal(self, duration: str):
- self._duration = duration
- self.emit()
-
- def on_difficulty_signal(self, index: int):
- self._difficulty = Difficulty(index)
- self.emit()
-
- def on_priority_signal(self, index: int):
- 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()
-
- def emit(self):
- validForm = self.get()
- if validForm:
- self._signal.emit(validForm)
-
- def signal(self):
- return self._signal
-
-class ValidTaskFormSignal(QtCore.QObject):
- _signal = QtCore.pyqtSignal(ValidTaskForm, name = "validTaskForm")
-
- def __init__(self):
- QtCore.QObject.__init__(self)
-
- def emit(self, form: Optional[ValidTaskForm]):
- self._signal.emit(form)
-
- def connect(self, f):
- self._signal.connect(f)
diff --git a/src/gui/tasks/form/widget.py b/src/gui/tasks/form/widget.py
deleted file mode 100644
index 70d506d..0000000
--- a/src/gui/tasks/form/widget.py
+++ /dev/null
@@ -1,184 +0,0 @@
-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")
-
- def __init__(self):
- QtCore.QObject.__init__(self)
-
- def emit(self, text: str):
- self._signal.emit(text)
-
- def connect(self, f):
- self._signal.connect(f)
-
-def widget(
- parent: QtWidgets.QWidget,
- action_title: str,
- task: Task,
- tags: List[int],
- 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 = task.name if task is not None else ""
- name_input = line_edit(grid, grid_layout, 0, "Name", init_name)
-
- 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_difficulty = task.difficulty if task is not None else difficulty.Difficulty.NORMAL
- difficulty_input = combo_box(
- grid,
- grid_layout,
- 3,
- "Difficulty",
- [difficulty.format(d) for d in difficulty.values],
- int(init_difficulty))
-
- init_priority = task.priority if task is not None else priority.Priority.MIDDLE
- priority_input = combo_box(
- grid,
- grid_layout,
- 4,
- "Priority",
- [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)
-
- task_form_edition = gui.tasks.form.state.TaskFormEdition(
- init_name,
- name_input.textChanged,
- init_duration,
- duration_input.textChanged,
- init_difficulty,
- difficulty_input.currentIndexChanged,
- init_priority,
- priority_input.currentIndexChanged,
- tags_signal,
- init_description,
- description_signal)
-
- def on_validate():
- form = task_form_edition.get()
- if form:
- on_validated(form)
-
- layout.addWidget(buttons(
- parent = widget,
- action_title = action_title,
- task_form_signal = task_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 combo_box(
- parent,
- layout: QtWidgets.QGridLayout,
- n: int,
- label: str,
- values: List[str],
- default_value: int) -> QtWidgets.QComboBox:
-
- label = QtWidgets.QLabel(label, parent)
- layout.addWidget(label, n, 0)
-
- box = QtWidgets.QComboBox(parent)
- for value in values:
- box.addItem(value)
- if default_value != None:
- box.setCurrentIndex(default_value)
- layout.addWidget(box, n, 1)
-
- 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,
- default_value: str) -> Tuple[QtWidgets.QWidget, TextEditSignal]:
-
- widget = QtWidgets.QWidget(parent)
- layout = QtWidgets.QVBoxLayout(widget)
-
- signal = TextEditSignal()
-
- label = QtWidgets.QLabel(label, parent)
- layout.addWidget(label)
-
- edit = QtWidgets.QTextEdit(parent)
- if default_value != None:
- edit.insertPlainText(default_value)
- layout.addWidget(edit)
-
- edit.textChanged.connect(lambda: signal.emit(edit.toPlainText()))
-
- return (widget, signal)
-
-def buttons(parent, action_title, task_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_ok(validate.style()))
- validate.clicked.connect(on_validate);
- layout.addWidget(validate)
-
- def on_task_form_signal(form: Optional[ValidTaskForm]):
- if form:
- validate.setEnabled(True)
- else:
- validate.setDisabled(True)
-
- task_form_signal.connect(on_task_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/tasks/signal.py b/src/gui/tasks/signal.py
deleted file mode 100644
index 074e8ec..0000000
--- a/src/gui/tasks/signal.py
+++ /dev/null
@@ -1,28 +0,0 @@
-from PyQt5 import QtCore
-from typing import List
-
-from model.task import Task
-
-class AddTask(QtCore.QObject):
- _signal = QtCore.pyqtSignal(Task, list, name = "addTask")
-
- def __init__(self):
- QtCore.QObject.__init__(self)
-
- def emit(self, task: Task, tags: List[int]):
- self._signal.emit(task, tags)
-
- def connect(self, f):
- self._signal.connect(f)
-
-class UpdateTask(QtCore.QObject):
- _signal = QtCore.pyqtSignal(int, Task, list, name = "updateTask")
-
- def __init__(self):
- QtCore.QObject.__init__(self)
-
- def emit(self, row: int, task: Task, tags: List[int]):
- self._signal.emit(row, task, tags)
-
- def connect(self, f):
- self._signal.connect(f)
diff --git a/src/gui/tasks/table/menu.py b/src/gui/tasks/table/menu.py
deleted file mode 100644
index 5356be2..0000000
--- a/src/gui/tasks/table/menu.py
+++ /dev/null
@@ -1,49 +0,0 @@
-from PyQt5 import QtWidgets, QtCore
-from typing import List
-
-import db.tasks
-import gui.tasks.dialog
-from model.status import Status
-from model.task import Task, ValidTaskForm
-from model.tag import Tag
-
-def open(table: QtWidgets.QTableWidget, status: Status, update_task_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")
-
- if status != Status.READY:
- move_to_ready = menu.addAction(gui.icon.task_ready(menu.style()), "move to ready")
- else:
- move_to_ready = QtWidgets.QAction(menu)
-
- if status != Status.WAITING:
- move_to_waiting = menu.addAction(gui.icon.task_waiting(menu.style()), "move to waiting")
- else:
- move_to_waiting = QtWidgets.QAction(menu)
-
- if status != Status.MAYBE:
- move_to_maybe = menu.addAction(gui.icon.task_maybe(menu.style()), "move to maybe")
- else:
- move_to_maybe = QtWidgets.QAction(menu)
-
- action = menu.exec_(table.mapToGlobal(position + QtCore.QPoint(15, 20)))
- if action == modify_action and len(rows) == 1:
- row = list(rows)[0]
- (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.confirm_delete(table, rows, lambda: table.delete_rows(rows))
- elif action == move_to_ready:
- gui.tasks.dialog.confirm_move(table, rows, Status.READY, lambda: table.update_status(rows, Status.READY))
- elif action == move_to_waiting:
- gui.tasks.dialog.confirm_move(table, rows, Status.WAITING, lambda: table.update_status(rows, Status.WAITING))
- elif action == move_to_maybe:
- gui.tasks.dialog.confirm_move(table, rows, Status.MAYBE, lambda: table.update_status(rows, Status.MAYBE))
diff --git a/src/gui/tasks/table/widget.py b/src/gui/tasks/table/widget.py
deleted file mode 100644
index aacae2f..0000000
--- a/src/gui/tasks/table/widget.py
+++ /dev/null
@@ -1,284 +0,0 @@
-from PyQt5 import QtWidgets, QtCore, QtGui
-from PyQt5.QtCore import Qt
-from typing import List, Tuple
-import time
-import math
-
-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
-from model.status import Status
-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 service.tasks
-import util.array
-import util.range
-
-class Widget(QtWidgets.QTableWidget):
- def __init__(
- self,
- parent,
- on_show: gui.signal.Reload,
- add_task_signal: gui.tasks.signal.AddTask,
- status: Status):
- super().__init__(parent)
-
- self.init_state(status)
- self.sort()
-
- self.setSelectionBehavior(QtWidgets.QTableView.SelectRows)
- self.init_header()
- self.setRowCount(len(self._tasks))
- self.setColumnCount(len(self.header_labels()))
- self.setColumnWidth(1, 500)
-
- self.update_view()
- self.horizontalHeader().setStretchLastSection(True)
-
- # Menu
- self.setContextMenuPolicy(Qt.CustomContextMenu)
- self.customContextMenuRequested.connect(lambda position: gui.tasks.table.menu.open(self, status, 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 init_state(self, status: Status):
- self._update_task_signal = gui.tasks.signal.UpdateTask()
- cursor = database.cursor()
- self._status = status
- self._tasks = service.tasks.get(cursor, self._status)
- self._task_tags = db.task_tags.get(cursor)
- self._tags = db.tags.get(cursor)
- self._sort_column = 0
- self._sort_is_ascending = True
-
- def init_header(self):
- self._header_view = QtWidgets.QHeaderView(Qt.Horizontal, self)
- self._header_model = QtGui.QStandardItemModel()
- self._header_model.setHorizontalHeaderLabels(self.header_labels())
- self._header_view.setModel(self._header_model)
- self._header_view.setSectionsClickable(True)
- self._header_view.sectionClicked.connect(self.on_header_click)
- self.setHorizontalHeader(self._header_view)
-
- def on_show(self):
- cursor = database.cursor()
- self._tasks = service.tasks.get(cursor, self._status)
- self._task_tags = db.task_tags.get(cursor)
- self._tags = db.tags.get(cursor)
- self.setRowCount(len(self._tasks))
- self.sort()
- self.update_view()
-
- 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.setHorizontalHeaderLabels(self.header_labels())
- self.update_view()
-
- 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]):
- self._tasks[row] = task
- filtred_task_tags = [tt for tt in self._task_tags if tt.task_id in [t.id for t in self._tasks if t.id != task.id]]
- 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
-
- # Update task in table
- self.sort()
- row_after_sort = [i for i in range(len(self._tasks)) if self._tasks[i].id == task.id][0]
- if row_after_sort == row:
- self.update_row(row)
- else:
- self.removeRow(row)
- self.insertRow(row_after_sort)
- self.update_row(row_after_sort)
- self.selectRow(row_after_sort)
-
- 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]
- self.setRowCount(len(self._tasks))
- 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)
- if event.key() in (Qt.Key_Return, Qt.Key_Enter):
- rows = self.get_selected_rows()
- if len(rows) == 1:
- row = rows[0]
- (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, 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.remove_rows_from_table(rows, task_ids)
-
- def update_status(self, rows: List[int], status: Status):
- task_ids = [task.id for i, task in enumerate(self._tasks) if i in rows]
- service.tasks.update_status(database.cursor(), task_ids, status)
- self.remove_rows_from_table(rows, task_ids)
-
- def remove_rows_from_table(self, rows: List[int], task_ids: List[int]):
- for row in sorted(rows, reverse=True):
- self.removeRow(row)
- 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]]
- self.setRowCount(len(self._tasks))
-
- def get_selected_rows(self):
- return list(set([index.row() for index in self.selectedIndexes()]))
-
- def on_double_click(self, row: int):
- (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 header_labels(self):
- labels = ["Age", "Name", "Duration", "Difficulty", "Priority", "Tag"]
- if self._sort_is_ascending:
- sign = "▼"
- else:
- sign = "▲"
- labels[self._sort_column] = labels[self._sort_column] + " " + sign
- return labels
-
-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/test_duration.py b/src/gui/tasks/test_duration.py
deleted file mode 100644
index 9d5d9b8..0000000
--- a/src/gui/tasks/test_duration.py
+++ /dev/null
@@ -1,21 +0,0 @@
-from gui.tasks.duration import format, parse
-
-def test_format():
- assert format(0) == ""
- assert format(0.5) == "0.5m"
- assert format(35) == "35m"
- assert format(60) == "1h"
- assert format(61) == "1h"
- assert format(90) == "1.5h"
- assert format(1440) == "1d"
-
-def test_parse():
- assert parse("") == 0
- assert parse("42") == None
- assert parse("hey") == None
- assert parse("1h30") == None
- assert parse("1h30m") == None
- assert parse("17m") == 17
- assert parse("90m") == 90
- assert parse("1.5h") == 90
- assert parse("2d") == 2880
diff --git a/src/gui/tasks/widget.py b/src/gui/tasks/widget.py
deleted file mode 100644
index 87b15d3..0000000
--- a/src/gui/tasks/widget.py
+++ /dev/null
@@ -1,30 +0,0 @@
-from PyQt5 import QtWidgets
-
-from model.status import Status
-import gui.tasks.signal
-import gui.tasks.table.widget
-import gui.icon
-import gui.signal
-
-def widget(parent, on_show: gui.signal.Reload, status: Status):
- widget = QtWidgets.QWidget(parent)
-
- layout = QtWidgets.QVBoxLayout(widget)
- widget.setLayout(layout)
-
- layout.addSpacing(15)
-
- add_task_signal = gui.tasks.signal.AddTask()
-
- add_task_button = QtWidgets.QPushButton(" Add a task", widget)
- add_task_button.setFixedHeight(30)
- add_task_button.setIcon(gui.icon.new_folder(widget.style()))
- add_task_button.clicked.connect(lambda: gui.tasks.dialog.add(widget, status, add_task_signal).exec_())
- layout.addWidget(add_task_button)
-
- layout.addSpacing(20)
-
- table = gui.tasks.table.widget.Widget(widget, on_show, add_task_signal, status)
- layout.addWidget(table)
-
- return widget
diff --git a/src/gui/window.py b/src/gui/window.py
deleted file mode 100644
index 584fda6..0000000
--- a/src/gui/window.py
+++ /dev/null
@@ -1,36 +0,0 @@
-from PyQt5 import QtCore, QtWidgets
-
-import gui.tasks.widget
-import gui.tasks.widget
-import gui.tags.panel.widget
-import gui.signal
-from model.status import Status
-
-def get():
- window = QtWidgets.QMainWindow()
- window.setWindowTitle("todo-next")
- window.setMinimumSize(QtCore.QSize(640, 480))
-
- tabs = QtWidgets.QTabWidget(window)
- window.setCentralWidget(tabs)
-
- show_ready = gui.signal.Reload()
- show_waiting = gui.signal.Reload()
- show_maybe = gui.signal.Reload()
-
- def on_current_tab_changed(index: int):
- if index == 0:
- show_ready.emit()
- elif index == 1:
- show_waiting.emit()
- elif index == 2:
- show_maybe.emit()
-
- tabs.currentChanged.connect(on_current_tab_changed)
-
- tabs.addTab(gui.tasks.widget.widget(tabs, show_ready, Status.READY), "Ready")
- tabs.addTab(gui.tasks.widget.widget(tabs, show_waiting, Status.WAITING), "Waiting")
- tabs.addTab(gui.tasks.widget.widget(tabs, show_maybe, Status.MAYBE), "Maybe")
- tabs.addTab(gui.tags.panel.widget.widget(tabs), "Tags")
-
- return window
diff --git a/src/main.py b/src/main.py
deleted file mode 100644
index f89872a..0000000
--- a/src/main.py
+++ /dev/null
@@ -1,20 +0,0 @@
-import sys
-from PyQt5 import QtCore, QtWidgets
-import sqlite3
-import os.path
-
-import db.init
-import gui.window
-import arguments
-import database
-
-args = arguments.parser().parse_args()
-database.init(args.database if args.database != None else "database")
-app = QtWidgets.QApplication(sys.argv)
-
-window = gui.window.get()
-window.show()
-res = app.exec_()
-
-database.close()
-sys.exit(res)
diff --git a/src/model/difficulty.py b/src/model/difficulty.py
deleted file mode 100644
index 526cdb9..0000000
--- a/src/model/difficulty.py
+++ /dev/null
@@ -1,30 +0,0 @@
-from enum import IntEnum
-from typing import Optional
-
-class Difficulty(IntEnum):
- EASY = 0
- NORMAL = 1
- HARD = 2
-
-values = [
- Difficulty.EASY,
- Difficulty.NORMAL,
- Difficulty.HARD]
-
-def format(difficulty: Difficulty) -> str:
- if difficulty == Difficulty.EASY:
- return "Easy"
- elif difficulty == Difficulty.NORMAL:
- return "Normal"
- elif difficulty == Difficulty.HARD:
- return "Hard"
-
-def parse(string: str) -> Optional[Difficulty]:
- if string == "Easy":
- return Difficulty.EASY
- elif string == "Normal":
- return Difficulty.NORMAL
- elif string == "Hard":
- return Difficulty.HARD
- else:
- return None
diff --git a/src/model/priority.py b/src/model/priority.py
deleted file mode 100644
index 5948104..0000000
--- a/src/model/priority.py
+++ /dev/null
@@ -1,30 +0,0 @@
-from enum import IntEnum
-from typing import Optional
-
-class Priority(IntEnum):
- LOW = 0
- MIDDLE = 1
- HIGH = 2
-
-values = [
- Priority.LOW,
- Priority.MIDDLE,
- Priority.HIGH]
-
-def format(priority: Priority) -> str:
- if priority == Priority.LOW:
- return "Low"
- elif priority == Priority.MIDDLE:
- return "Middle"
- elif priority == Priority.HIGH:
- return "High"
-
-def parse(string: str) -> Optional[Priority]:
- if string == "Low":
- return Priority.LOW
- elif string == "Middle":
- return Priority.MIDDLE
- elif string == "High":
- return Priority.HIGH
- else:
- return None
diff --git a/src/model/status.py b/src/model/status.py
deleted file mode 100644
index 6881e0a..0000000
--- a/src/model/status.py
+++ /dev/null
@@ -1,30 +0,0 @@
-from enum import IntEnum
-from typing import Optional
-
-class Status(IntEnum):
- READY = 0
- WAITING = 1
- MAYBE = 2
-
-values = [
- Status.READY,
- Status.WAITING,
- Status.MAYBE]
-
-def format(status: Status) -> str:
- if status == Status.READY:
- return "Ready"
- elif status == Status.WAITING:
- return "Waiting"
- elif status == Status.MAYBE:
- return "Maybe"
-
-def parse(string: str) -> Optional[Status]:
- if string == "Ready":
- return Status.READY
- elif string == "Waiting":
- return Status.WAITING
- elif string == "Maybe":
- return Status.MAYBE
- else:
- return None
diff --git a/src/model/tag.py b/src/model/tag.py
deleted file mode 100644
index 030b223..0000000
--- a/src/model/tag.py
+++ /dev/null
@@ -1,12 +0,0 @@
-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
deleted file mode 100644
index 69f9807..0000000
--- a/src/model/task.py
+++ /dev/null
@@ -1,22 +0,0 @@
-from typing import NamedTuple, List
-
-from model.difficulty import Difficulty
-from model.priority import Priority
-
-class Task(NamedTuple):
- id: int
- created_at: int
- updated_at: int
- name: str
- duration: int
- difficulty: Difficulty
- priority: Priority
- description: str
-
-class ValidTaskForm(NamedTuple):
- name: str
- duration: int
- difficulty: Difficulty
- priority: Priority
- tags: List[int]
- description: str
diff --git a/src/model/task_tag.py b/src/model/task_tag.py
deleted file mode 100644
index 0a33c66..0000000
--- a/src/model/task_tag.py
+++ /dev/null
@@ -1,5 +0,0 @@
-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
deleted file mode 100644
index 87194c4..0000000
--- a/src/service/tasks.py
+++ /dev/null
@@ -1,32 +0,0 @@
-from typing import List
-
-from model.task import Task, ValidTaskForm
-from model.status import Status
-import db.tasks
-import db.task_tags
-import database
-
-def get(cursor, status: Status) -> List[Task]:
- return db.tasks.get(cursor, status)
-
-def create(cursor, status: Status, task_form: ValidTaskForm) -> Task:
- task = db.tasks.insert(cursor, status, task_form)
- db.task_tags.insert_many(cursor, task.id, task_form.tags)
- database.commit()
- return task
-
-def update(cursor, task: Task, task_form: ValidTaskForm) -> Task:
- db.task_tags.delete(cursor, [task.id])
- updated_task = db.tasks.update(cursor, task, task_form)
- db.task_tags.insert_many(cursor, task.id, task_form.tags)
- database.commit()
- return updated_task
-
-def delete(cursor, task_ids: List[int]):
- db.task_tags.delete(cursor, task_ids)
- db.tasks.delete(cursor, task_ids)
- database.commit()
-
-def update_status(cursor, task_ids: List[int], status: Status) -> List[Task]:
- db.tasks.update_status(cursor, task_ids, status)
- database.commit()
diff --git a/src/util/array.py b/src/util/array.py
deleted file mode 100644
index bb4eee3..0000000
--- a/src/util/array.py
+++ /dev/null
@@ -1,5 +0,0 @@
-def insert_position(x, xs, is_reversed: bool) -> int:
- for i, y in enumerate(xs):
- if is_reversed and x >= y or not is_reversed and x <= y:
- return i
- return len(xs)
diff --git a/src/util/range.py b/src/util/range.py
deleted file mode 100644
index bd4b27e..0000000
--- a/src/util/range.py
+++ /dev/null
@@ -1,30 +0,0 @@
-from typing import NamedTuple, List
-
-class Range(NamedTuple):
- start: int
- length: int
-
-def from_indexes(indexes: List[int]) -> List[Range]:
- ranges = []
- curr_range_start = 0
- curr_range_len = 0
-
- last_index = -1
-
- for index in sorted(indexes):
- if index == curr_range_start + curr_range_len:
- curr_range_len += 1
- else:
- if curr_range_len > 0:
- ranges.append(Range(
- start = curr_range_start,
- length = curr_range_len))
- curr_range_start = index
- curr_range_len = 1
-
- if curr_range_len > 0:
- ranges.append(Range(
- start = curr_range_start,
- length = curr_range_len))
-
- return ranges
diff --git a/src/util/test_array.py b/src/util/test_array.py
deleted file mode 100644
index 38759b9..0000000
--- a/src/util/test_array.py
+++ /dev/null
@@ -1,12 +0,0 @@
-from array import insert_position
-
-def test_insert_position():
- assert insert_position(0, [], False) == 0
- assert insert_position(1, [1, 2, 3], False) == 0
- assert insert_position(2, [1, 2, 3], False) == 1
- assert insert_position(3, [1, 2, 3], False) == 2
- assert insert_position(8, [1, 2, 3], False) == 3
- assert insert_position(8, [3, 2, 1], True) == 0
- assert insert_position(3, [3, 2, 1], True) == 0
- assert insert_position(2, [3, 2, 1], True) == 1
- assert insert_position(1, [3, 2, 1], True) == 2
diff --git a/src/util/test_range.py b/src/util/test_range.py
deleted file mode 100644
index 0bd909b..0000000
--- a/src/util/test_range.py
+++ /dev/null
@@ -1,6 +0,0 @@
-from range import from_indexes, Range
-
-def test_from_indexes():
- assert from_indexes([]) == []
- assert from_indexes([1]) == [Range(1, 1)]
- assert from_indexes([9, 6, 0, 10]) == [Range(0, 1), Range(6, 1), Range(9, 2)]