diff options
author | Joris | 2020-06-06 17:44:26 +0200 |
---|---|---|
committer | Joris | 2020-06-06 19:54:03 +0200 |
commit | 1595e0de940a86a7810df0e02e43838d97c0d846 (patch) | |
tree | 9701eeec0d98baa9f6044b1911df68e4c8539819 /src | |
parent | 6b9195000eb5404c247288b384d7ca2bacc1ab23 (diff) |
Provide nix build
Diffstat (limited to 'src')
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)] |