aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--default.nix12
-rw-r--r--todo/db/init.py4
-rw-r--r--todo/db/tasks.py36
-rw-r--r--todo/gui/color.py12
-rw-r--r--todo/gui/icon.py5
-rw-r--r--todo/gui/tags/panel/dialog.py8
-rw-r--r--todo/gui/tags/panel/form/widget.py38
-rw-r--r--todo/gui/tags/panel/table/menu.py2
-rw-r--r--todo/gui/tags/panel/table/model.py2
-rw-r--r--todo/gui/tags/panel/table/widget.py2
-rw-r--r--todo/gui/tags/panel/widget.py2
-rw-r--r--todo/gui/tasks/dialog.py8
-rw-r--r--todo/gui/tasks/duration.py50
-rw-r--r--todo/gui/tasks/form/state.py45
-rw-r--r--todo/gui/tasks/form/widget.py82
-rw-r--r--todo/gui/tasks/table/menu.py12
-rw-r--r--todo/gui/tasks/table/widget.py55
-rw-r--r--todo/gui/tasks/test_duration.py21
-rw-r--r--todo/gui/tasks/widget.py2
-rw-r--r--todo/gui/window.py6
-rw-r--r--todo/model/difficulty.py30
-rw-r--r--todo/model/priority.py30
-rw-r--r--todo/model/task.py13
-rw-r--r--todo/util/gui/__init__.py0
-rw-r--r--todo/util/gui/color_input.py31
-rw-r--r--todo/util/gui/date_input.py79
26 files changed, 226 insertions, 361 deletions
diff --git a/default.nix b/default.nix
index 4430918..d040783 100644
--- a/default.nix
+++ b/default.nix
@@ -1,9 +1,9 @@
-with (import (builtins.fetchGit {
- name = "nixpkgs-20.03";
- url = "git@github.com:nixos/nixpkgs.git";
- rev = "5272327b81ed355bbed5659b8d303cf2979b6953";
- ref = "refs/tags/20.03";
-}) {});
+with import (builtins.fetchGit {
+ name = "nixos-21.05-2021-08-02";
+ url = "https://github.com/nixos/nixpkgs/";
+ ref = "refs/heads/nixos-21.05";
+ rev = "d4590d21006387dcb190c516724cb1e41c0f8fdf";
+}) {};
python38Packages.buildPythonApplication rec {
pname = "todo";
diff --git a/todo/db/init.py b/todo/db/init.py
index 5d847a3..0fb9ec1 100644
--- a/todo/db/init.py
+++ b/todo/db/init.py
@@ -18,9 +18,7 @@ def init(path):
" created_at INTEGER NOT NULL,"
" updated_at INTEGER NOT NULL,"
" name TEXT NOT NULL,"
- " duration INTEGER,"
- " difficulty INT,"
- " priority INT,"
+ " due_date TEXT,"
" description TEXT,"
" status TEXT"
" )")
diff --git a/todo/db/tasks.py b/todo/db/tasks.py
index fc23bf0..1abbb51 100644
--- a/todo/db/tasks.py
+++ b/todo/db/tasks.py
@@ -1,10 +1,12 @@
from sqlite3 import Cursor
import time
from typing import List
+from PyQt5 import QtCore
+from datetime import date
from todo.model.task import Task, ValidTaskForm
from todo.model.status import Status
-from todo.model import difficulty, priority, status
+from todo.model import status
def get(cursor: Cursor, s: Status) -> List[Task]:
cursor.execute(
@@ -13,9 +15,7 @@ def get(cursor: Cursor, s: Status) -> List[Task]:
" created_at,"
" updated_at,"
" name,"
- " duration,"
- " difficulty,"
- " priority,"
+ " due_date,"
" description"
" FROM"
" tasks"
@@ -31,10 +31,8 @@ def get(cursor: Cursor, s: Status) -> List[Task]:
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]
+ due_date = date.fromisoformat(task[4]) if task[4] else None,
+ description = task[5]
))
return res
@@ -46,22 +44,18 @@ def insert(cursor: Cursor, s: Status, form: ValidTaskForm):
" created_at,"
" updated_at,"
" name,"
- " duration,"
- " difficulty,"
- " priority,"
+ " due_date,"
" description,"
" status"
- " ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
- (now, now, form.name, form.duration, difficulty.format(form.difficulty), priority.format(form.priority), form.description, status.format(s)))
+ " ) VALUES (?, ?, ?, ?, ?, ?)",
+ (now, now, form.name, form.due_date.isoformat() if form.due_date else "", 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,
+ due_date = form.due_date,
description = form.description
)
@@ -72,21 +66,17 @@ def update(cursor: Cursor, task: Task, form: ValidTaskForm):
" UPDATE tasks SET"
" updated_at = ?,"
" name = ?,"
- " duration = ?,"
- " difficulty = ?,"
- " priority = ?,"
+ " due_date = ?,"
" description = ?"
" WHERE id = ?",
- (now, form.name, form.duration, difficulty.format(form.difficulty), priority.format(form.priority), form.description, task.id))
+ (now, form.name, form.due_date.isoformat() if form.due_date else "", 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,
+ due_date = form.due_date,
description = form.description
)
diff --git a/todo/gui/color.py b/todo/gui/color.py
index cc7e5a8..7ce74e5 100644
--- a/todo/gui/color.py
+++ b/todo/gui/color.py
@@ -7,15 +7,3 @@ 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/todo/gui/icon.py b/todo/gui/icon.py
index 7e2156d..4c9f017 100644
--- a/todo/gui/icon.py
+++ b/todo/gui/icon.py
@@ -18,7 +18,10 @@ def dialog_open(style):
return style.standardIcon(QtWidgets.QStyle.SP_DialogOpenButton)
def dialog_ok(style):
- return style.standardIcon(QtWidgets.QStyle.SP_DialogOkButton)
+ return style.standardIcon(QtWidgets.QStyle.SP_DialogApplyButton)
+
+def dialog_reset(style):
+ return style.standardIcon(QtWidgets.QStyle.SP_DialogResetButton)
def dialog_cancel(style):
return style.standardIcon(QtWidgets.QStyle.SP_DialogCancelButton)
diff --git a/todo/gui/tags/panel/dialog.py b/todo/gui/tags/panel/dialog.py
index 6a0e5c4..fde1f1f 100644
--- a/todo/gui/tags/panel/dialog.py
+++ b/todo/gui/tags/panel/dialog.py
@@ -12,7 +12,7 @@ def add(parent_widget, add_tag_signal):
todo.database.commit()
add_tag_signal.emit(tag)
- return widget(parent_widget, "Add a tag", "add", None, on_add)
+ return widget(parent_widget, "Ajouter une étiquette", "Ajouter", None, on_add)
def update(parent_widget, update_tag_signal, row, tag):
@@ -21,13 +21,13 @@ def update(parent_widget, update_tag_signal, row, tag):
update_tag_signal.emit(row, updated_tag)
todo.database.commit()
- return widget(parent_widget, "Modify a tag", "modify", tag, on_update)
+ return widget(parent_widget, "Modifier une étiquette", "Modifier", tag, on_update)
def confirm_delete(table, rows):
confirm = QtWidgets.QMessageBox.question(
table,
- "Tag deletion",
- "Do you really want to delete the selected tags ?",
+ "Suppression des étiquettes",
+ "Voulez-vous vraiment supprimer les étiquettes sélectionnées ?",
QtWidgets.QMessageBox.No | QtWidgets.QMessageBox.Yes,
QtWidgets.QMessageBox.Yes)
diff --git a/todo/gui/tags/panel/form/widget.py b/todo/gui/tags/panel/form/widget.py
index 9ac4eb1..1b1744d 100644
--- a/todo/gui/tags/panel/form/widget.py
+++ b/todo/gui/tags/panel/form/widget.py
@@ -2,10 +2,10 @@ from PyQt5 import QtWidgets, QtCore, QtGui
from typing import Optional, Tuple, List, Any
from todo.model.tag import Tag, ValidTagForm
-from todo.model import difficulty, priority
import todo.gui.icon
import todo.gui.tags.panel.form.state
import todo.gui.color
+from todo.util.gui.color_input import ColorInput
def widget(
parent: QtWidgets.QWidget,
@@ -24,10 +24,10 @@ def widget(
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)
+ name_input = line_edit(grid, grid_layout, 0, "Nom", 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))
+ color_input = color_edit(grid, grid_layout, 1, "Couleur", QtGui.QColor(init_color))
tag_form_edition = todo.gui.tags.panel.form.state.TagFormEdition(
init_name,
@@ -81,36 +81,6 @@ def color_edit(
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)
@@ -129,7 +99,7 @@ def buttons(parent, action_title, tag_form_signal, on_validate, on_cancel):
tag_form_signal.connect(on_tag_form_signal)
- cancel = QtWidgets.QPushButton("cancel", widget)
+ cancel = QtWidgets.QPushButton("Annuler", widget)
cancel.setIcon(todo.gui.icon.dialog_cancel(cancel.style()))
cancel.clicked.connect(on_cancel)
layout.addWidget(cancel)
diff --git a/todo/gui/tags/panel/table/menu.py b/todo/gui/tags/panel/table/menu.py
index 0446841..7ceded7 100644
--- a/todo/gui/tags/panel/table/menu.py
+++ b/todo/gui/tags/panel/table/menu.py
@@ -31,5 +31,5 @@ def open(table, update_tag_signal, position):
row = list(rows)[0]
tag = table.model().get_at(row)
todo.gui.tags.panel.dialog.update(table, update_tag_signal, row, tag).exec_()
- elif action == delete_action:
+ elif action == delete_action and len(rows) > 0:
todo.gui.tags.panel.dialog.confirm_delete(table, rows)
diff --git a/todo/gui/tags/panel/table/model.py b/todo/gui/tags/panel/table/model.py
index 6f9d71a..0112b03 100644
--- a/todo/gui/tags/panel/table/model.py
+++ b/todo/gui/tags/panel/table/model.py
@@ -10,7 +10,7 @@ import todo.util.range
columns = 1
-headers = ["Name", "Color"]
+headers = ["Nom", "Couleur"]
default_sort = (0, Qt.AscendingOrder)
diff --git a/todo/gui/tags/panel/table/widget.py b/todo/gui/tags/panel/table/widget.py
index 1d10c73..dc2f1c5 100644
--- a/todo/gui/tags/panel/table/widget.py
+++ b/todo/gui/tags/panel/table/widget.py
@@ -62,7 +62,7 @@ class Widget(QtWidgets.QTableView):
elif event.key() == Qt.Key_Delete:
rows = self.get_selected_rows()
tags = self.model().row_ids(rows)
- if not todo.db.task_tags.one_is_used(todo.database.cursor(), tags):
+ if not todo.db.task_tags.one_is_used(todo.database.cursor(), tags) and len(rows) > 0:
todo.gui.tags.panel.dialog.confirm_delete(self, rows)
def get_selected_rows(self):
diff --git a/todo/gui/tags/panel/widget.py b/todo/gui/tags/panel/widget.py
index 071442e..675b0ff 100644
--- a/todo/gui/tags/panel/widget.py
+++ b/todo/gui/tags/panel/widget.py
@@ -15,7 +15,7 @@ def widget(parent):
add_tag_signal = todo.gui.tags.panel.signal.AddTag()
- add_tag_button = QtWidgets.QPushButton(" Add a tag", widget)
+ add_tag_button = QtWidgets.QPushButton(" Ajouter une étiquette", widget)
add_tag_button.setFixedHeight(30)
add_tag_button.setIcon(todo.gui.icon.new_folder(widget.style()))
diff --git a/todo/gui/tasks/dialog.py b/todo/gui/tasks/dialog.py
index 2bed6af..9cfa2be 100644
--- a/todo/gui/tasks/dialog.py
+++ b/todo/gui/tasks/dialog.py
@@ -14,7 +14,7 @@ def add(parent_widget, status: Status, add_task_signal):
task = todo.service.tasks.create(todo.database.cursor(), status, task_form)
add_task_signal.emit(task, task_form.tags)
- return widget(parent_widget, "Add a task", "add", None, [], on_add)
+ return widget(parent_widget, "Ajouter une tâche", "ajouter", None, [], on_add)
def update(parent_widget, update_task_signal, row: int, task: Task, tags: List[int]):
@@ -22,13 +22,13 @@ def update(parent_widget, update_task_signal, row: int, task: Task, tags: List[i
updated_task = todo.service.tasks.update(todo.database.cursor(), task, tags, task_form)
update_task_signal.emit(row, updated_task, task_form.tags)
- return widget(parent_widget, "Modify a task", "modify", task, tags, on_update)
+ return widget(parent_widget, "Modifier une tâche", "modifier", 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 ?",
+ "Suppression des tâches",
+ "Voulez-vous vraiment supprimer les tâches selectionnées ?",
QtWidgets.QMessageBox.No | QtWidgets.QMessageBox.Yes,
QtWidgets.QMessageBox.Yes)
diff --git a/todo/gui/tasks/duration.py b/todo/gui/tasks/duration.py
deleted file mode 100644
index 81db661..0000000
--- a/todo/gui/tasks/duration.py
+++ /dev/null
@@ -1,50 +0,0 @@
-from PyQt5 import QtGui
-from typing import Optional
-import math
-import re
-
-import todo.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 todo.gui.color.short_duration
- elif minutes < 60:
- return todo.gui.color.medium_duration
- else:
- return todo.gui.color.long_duration
diff --git a/todo/gui/tasks/form/state.py b/todo/gui/tasks/form/state.py
index c073214..ed64747 100644
--- a/todo/gui/tasks/form/state.py
+++ b/todo/gui/tasks/form/state.py
@@ -1,10 +1,8 @@
from PyQt5 import QtCore
from typing import Optional, List
+from datetime import date, datetime
from todo.model.task import ValidTaskForm
-from todo.model.difficulty import Difficulty
-from todo.model.priority import Priority
-import todo.gui.tasks.duration
import todo.gui.tags.list
class TaskFormEdition:
@@ -12,45 +10,33 @@ class TaskFormEdition:
self,
name,
name_signal,
- duration,
- duration_signal,
- difficulty,
- difficulty_signal,
- priority,
- priority_signal,
+ due_date,
+ due_date_signal,
tags: List[int],
tags_signal: todo.gui.tags.list.SelectionSignal,
description,
description_signal):
self._name = name
- self._duration = duration
- self._difficulty = difficulty
- self._priority = priority
+ self._due_date = parse_date(due_date)
self._tags = 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))
+ due_date_signal.connect(lambda d: self.on_due_date_signal(d))
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 = todo.gui.tasks.duration.parse(self._duration)
- difficulty = self._difficulty
- priority = self._priority
+ due_date = self._due_date
description = self._description.strip()
- if name and duration != None:
+ if name:
return ValidTaskForm(
name = name,
- duration = duration,
- difficulty = difficulty,
- priority = priority,
+ due_date = due_date,
tags = self._tags,
description = description)
else:
@@ -60,16 +46,8 @@ class TaskFormEdition:
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)
+ def on_due_date_signal(self, due_date: str):
+ self._due_date = parse_date(due_date)
self.emit()
def on_tags_signal(self, tags: List[int]):
@@ -99,3 +77,6 @@ class ValidTaskFormSignal(QtCore.QObject):
def connect(self, f):
self._signal.connect(f)
+
+def parse_date(d: str) -> date:
+ return datetime.strptime(d, "%d/%m/%Y").date() if d else None
diff --git a/todo/gui/tasks/form/widget.py b/todo/gui/tasks/form/widget.py
index a6ce488..a401eaa 100644
--- a/todo/gui/tasks/form/widget.py
+++ b/todo/gui/tasks/form/widget.py
@@ -3,11 +3,10 @@ from typing import Optional, Tuple, List, Any
from todo.model.task import Task, ValidTaskForm
from todo.model.tag import Tag
-from todo.model import difficulty, priority
import todo.gui.icon
import todo.gui.tasks.form.state
-import todo.gui.tasks.duration
import todo.gui.tags.list
+from todo.util.gui.date_input import DateInput
class TextEditSignal(QtCore.QObject):
_signal = QtCore.pyqtSignal(str, name = "textEdit")
@@ -34,33 +33,11 @@ def widget(
widget.setLayout(layout)
init_name = task.name if task is not None else ""
- name_input = name_edit(widget, layout, "Name", init_name)
-
- grid = QtWidgets.QWidget(widget)
- layout.addWidget(grid)
- grid_layout = QtWidgets.QGridLayout(grid)
- grid.setLayout(grid_layout)
-
- init_duration = todo.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))
+ name_input = name_edit(widget, layout, "Nom", init_name)
+
+ init_due_date = (task.due_date.strftime("%d/%m/%Y") if task.due_date else None) if task else None
+ (due_date_input, due_date_signal) = date_edit(widget, "Échéance", init_due_date)
+ layout.addWidget(due_date_input)
(tags_list_widget, tags_signal) = tags_selection(widget, tags)
layout.addWidget(tags_list_widget)
@@ -72,12 +49,8 @@ def widget(
task_form_edition = todo.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,
+ init_due_date,
+ due_date_signal,
tags,
tags_signal,
init_description,
@@ -119,23 +92,6 @@ def name_edit(
return edit
-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,
@@ -162,7 +118,8 @@ def tags_selection(parent, init_tags: List[int]) -> QtWidgets.QWidget:
def text_edit(
parent,
label: str,
- default_value: str) -> Tuple[QtWidgets.QWidget, TextEditSignal]:
+ default_value: str
+ ) -> Tuple[QtWidgets.QWidget, TextEditSignal]:
widget = QtWidgets.QWidget(parent)
layout = QtWidgets.QVBoxLayout(widget)
@@ -181,6 +138,23 @@ def text_edit(
return (widget, signal)
+def date_edit(
+ parent,
+ label: str,
+ init_date: str
+ ) -> QtWidgets.QLineEdit:
+
+ widget = QtWidgets.QWidget(parent)
+ layout = QtWidgets.QVBoxLayout(widget)
+
+ label = QtWidgets.QLabel(label, parent)
+ layout.addWidget(label)
+
+ date_input = DateInput(init_date, parent)
+ layout.addWidget(date_input)
+
+ return (widget, date_input.textChanged)
+
def buttons(parent, action_title, task_form_signal, on_validate, on_cancel):
widget = QtWidgets.QWidget(parent)
layout = QtWidgets.QHBoxLayout(widget)
@@ -199,7 +173,7 @@ def buttons(parent, action_title, task_form_signal, on_validate, on_cancel):
task_form_signal.connect(on_task_form_signal)
- cancel = QtWidgets.QPushButton("cancel", widget)
+ cancel = QtWidgets.QPushButton("Annuler", widget)
cancel.setIcon(todo.gui.icon.dialog_cancel(cancel.style()))
cancel.clicked.connect(on_cancel)
layout.addWidget(cancel)
diff --git a/todo/gui/tasks/table/menu.py b/todo/gui/tasks/table/menu.py
index 78c85ac..ad8415b 100644
--- a/todo/gui/tasks/table/menu.py
+++ b/todo/gui/tasks/table/menu.py
@@ -12,24 +12,24 @@ def open(table: QtWidgets.QTableWidget, status: Status, update_task_signal, posi
menu = QtWidgets.QMenu(table)
if len(rows) == 1:
- modify_action = menu.addAction(todo.gui.icon.dialog_open(menu.style()), "modify")
+ modify_action = menu.addAction(todo.gui.icon.dialog_open(menu.style()), "Modifier")
else:
modify_action = QtWidgets.QAction(menu)
- delete_action = menu.addAction(todo.gui.icon.trash(menu.style()), "delete")
+ delete_action = menu.addAction(todo.gui.icon.trash(menu.style()), "Supprimer")
if status != Status.READY:
- move_to_ready = menu.addAction(todo.gui.icon.task_ready(menu.style()), "move to ready")
+ move_to_ready = menu.addAction(todo.gui.icon.task_ready(menu.style()), "Mettre à prêt")
else:
move_to_ready = QtWidgets.QAction(menu)
if status != Status.WAITING:
- move_to_waiting = menu.addAction(todo.gui.icon.task_waiting(menu.style()), "move to waiting")
+ move_to_waiting = menu.addAction(todo.gui.icon.task_waiting(menu.style()), "Mettre en attente")
else:
move_to_waiting = QtWidgets.QAction(menu)
if status != Status.MAYBE:
- move_to_maybe = menu.addAction(todo.gui.icon.task_maybe(menu.style()), "move to maybe")
+ move_to_maybe = menu.addAction(todo.gui.icon.task_maybe(menu.style()), "Mettre à peut-être")
else:
move_to_maybe = QtWidgets.QAction(menu)
@@ -38,7 +38,7 @@ def open(table: QtWidgets.QTableWidget, status: Status, update_task_signal, posi
row = list(rows)[0]
(task, tags) = table.get_at(row)
todo.gui.tasks.dialog.update(table, update_task_signal, row, task, tags).exec_()
- elif action == delete_action:
+ elif action == delete_action and len(rows) > 0:
todo.gui.tasks.dialog.confirm_delete(table, rows, lambda: table.delete_rows(rows))
elif action == move_to_ready:
table.update_status(rows, Status.READY)
diff --git a/todo/gui/tasks/table/widget.py b/todo/gui/tasks/table/widget.py
index b379abc..e374c90 100644
--- a/todo/gui/tasks/table/widget.py
+++ b/todo/gui/tasks/table/widget.py
@@ -1,12 +1,10 @@
from PyQt5 import QtWidgets, QtCore, QtGui
from PyQt5.QtCore import Qt
from typing import List, Tuple
+from datetime import date, timedelta
import time
import math
-from todo.model import difficulty, priority
-from todo.model.difficulty import Difficulty
-from todo.model.priority import Priority
from todo.model.tag import Tag
from todo.model.task import Task
from todo.model.task_tag import TaskTag
@@ -17,7 +15,6 @@ import todo.db.task_tags
import todo.gui.color
import todo.gui.signal
import todo.gui.tasks.dialog
-import todo.gui.tasks.duration
import todo.gui.tasks.signal
import todo.gui.tasks.signal
import todo.gui.tasks.table.menu
@@ -128,12 +125,10 @@ class Widget(QtWidgets.QTableWidget):
else:
name = task.name
self.setItem(row, 1, item(name))
- self.setCellWidget(row, 2, colored_label(self, todo.gui.tasks.duration.format(task.duration), todo.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)))
+ self.setCellWidget(row, 2, colored_label(self, task.due_date.strftime("%d/%m/%Y") if task.due_date else "", due_date_color(task.due_date)))
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.setCellWidget(row, 3, render_tags(self, res_tags))
self.setRowHeight(row, 50)
def insert(self, task: Task, tags: List[int]) -> int:
@@ -163,14 +158,10 @@ class Widget(QtWidgets.QTableWidget):
return task.name.lower()
elif row == 2:
if is_reversed:
- return task.duration
+ return task.due_date.isoformat() if task.due_date else ""
else:
- return (task.duration == 0, task.duration)
+ return (task.due_date == None, task.due_date.isoformat() if task.due_date else "")
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)
@@ -181,13 +172,13 @@ class Widget(QtWidgets.QTableWidget):
def keyPressEvent(self, event):
super().keyPressEvent(event)
+ rows = self.get_selected_rows()
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)
todo.gui.tasks.dialog.update(self, self._update_task_signal, row, task, tags).exec_()
- elif event.key() == Qt.Key_Delete:
+ elif event.key() == Qt.Key_Delete and len(rows) > 0:
rows = self.get_selected_rows()
todo.gui.tasks.dialog.confirm_delete(self, rows, lambda: self.delete_rows(rows))
@@ -221,7 +212,7 @@ class Widget(QtWidgets.QTableWidget):
return (task, tags)
def header_labels(self):
- labels = ["Age", "Name", "Duration", "Difficulty", "Priority", "Tag"]
+ labels = ["Âge", "Nom", "Échéance", "Étiquettes"]
if self._sort_is_ascending:
sign = "▼"
else:
@@ -237,7 +228,7 @@ def item(text: str):
def age_since(timestamp):
diff = int(time.time()) - timestamp
if diff >= 60 * 60 * 24:
- return "" + str(math.floor(diff / 60 / 60 / 24)) + "d"
+ return "" + str(math.floor(diff / 60 / 60 / 24)) + "j"
elif diff >= 60 * 60:
return "" + str(math.floor(diff / 60 / 60)) + "h"
elif diff >= 60:
@@ -245,6 +236,18 @@ def age_since(timestamp):
else:
return "1m"
+def due_date_color(d: date) -> QtGui.QColor:
+ if d != None:
+ today = date.today()
+ if d < today:
+ return todo.gui.color.red
+ elif d < today + timedelta(days = 7):
+ return todo.gui.color.orange
+ else:
+ return todo.gui.color.black
+ else:
+ return todo.gui.color.black
+
def colored_label(parent, text: str, color: QtGui.QColor):
label = QtWidgets.QLabel(text)
palette = QtGui.QPalette()
@@ -268,19 +271,3 @@ def render_tags(parent, tags: List[Tag]):
layout.addWidget(label)
return widget
-
-def difficulty_color(d: Difficulty) -> QtGui.QColor:
- if d == Difficulty.EASY:
- return todo.gui.color.easy_difficulty
- elif d == Difficulty.NORMAL:
- return todo.gui.color.normal_difficulty
- elif d == Difficulty.HARD:
- return todo.gui.color.hard_difficulty
-
-def priority_color(p: Priority) -> QtGui.QColor:
- if p == Priority.LOW:
- return todo.gui.color.low_priority
- elif p == Priority.MIDDLE:
- return todo.gui.color.middle_priority
- elif p == Priority.HIGH:
- return todo.gui.color.high_priority
diff --git a/todo/gui/tasks/test_duration.py b/todo/gui/tasks/test_duration.py
deleted file mode 100644
index 1435e2d..0000000
--- a/todo/gui/tasks/test_duration.py
+++ /dev/null
@@ -1,21 +0,0 @@
-from todo.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/todo/gui/tasks/widget.py b/todo/gui/tasks/widget.py
index cc7fe96..7c8f80b 100644
--- a/todo/gui/tasks/widget.py
+++ b/todo/gui/tasks/widget.py
@@ -16,7 +16,7 @@ def widget(parent, on_show: todo.gui.signal.Reload, status: Status):
add_task_signal = todo.gui.tasks.signal.AddTask()
- add_task_button = QtWidgets.QPushButton(" Add a task", widget)
+ add_task_button = QtWidgets.QPushButton(" Ajouter une tâche", widget)
add_task_button.setFixedHeight(30)
add_task_button.setIcon(todo.gui.icon.new_folder(widget.style()))
add_task_button.clicked.connect(lambda: todo.gui.tasks.dialog.add(widget, status, add_task_signal).exec_())
diff --git a/todo/gui/window.py b/todo/gui/window.py
index 0391ee9..18518e6 100644
--- a/todo/gui/window.py
+++ b/todo/gui/window.py
@@ -28,9 +28,9 @@ def get():
tabs.currentChanged.connect(on_current_tab_changed)
- tabs.addTab(todo.gui.tasks.widget.widget(tabs, show_ready, Status.READY), "Ready")
- tabs.addTab(todo.gui.tasks.widget.widget(tabs, show_waiting, Status.WAITING), "Waiting")
- tabs.addTab(todo.gui.tasks.widget.widget(tabs, show_maybe, Status.MAYBE), "Maybe")
+ tabs.addTab(todo.gui.tasks.widget.widget(tabs, show_ready, Status.READY), "Prêt")
+ tabs.addTab(todo.gui.tasks.widget.widget(tabs, show_waiting, Status.WAITING), "En attente")
+ tabs.addTab(todo.gui.tasks.widget.widget(tabs, show_maybe, Status.MAYBE), "Peut-être")
tabs.addTab(todo.gui.tags.panel.widget.widget(tabs), "Tags")
return window
diff --git a/todo/model/difficulty.py b/todo/model/difficulty.py
deleted file mode 100644
index 526cdb9..0000000
--- a/todo/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/todo/model/priority.py b/todo/model/priority.py
deleted file mode 100644
index 5948104..0000000
--- a/todo/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/todo/model/task.py b/todo/model/task.py
index f20cbc9..086a84b 100644
--- a/todo/model/task.py
+++ b/todo/model/task.py
@@ -1,22 +1,17 @@
from typing import NamedTuple, List
-
-from todo.model.difficulty import Difficulty
-from todo.model.priority import Priority
+from PyQt5 import QtCore
+from datetime import date
class Task(NamedTuple):
id: int
created_at: int
updated_at: int
name: str
- duration: int
- difficulty: Difficulty
- priority: Priority
+ due_date: date
description: str
class ValidTaskForm(NamedTuple):
name: str
- duration: int
- difficulty: Difficulty
- priority: Priority
+ due_date: date
tags: List[int]
description: str
diff --git a/todo/util/gui/__init__.py b/todo/util/gui/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/todo/util/gui/__init__.py
diff --git a/todo/util/gui/color_input.py b/todo/util/gui/color_input.py
new file mode 100644
index 0000000..032aea1
--- /dev/null
+++ b/todo/util/gui/color_input.py
@@ -0,0 +1,31 @@
+from PyQt5 import QtWidgets, QtCore, QtGui
+
+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)
diff --git a/todo/util/gui/date_input.py b/todo/util/gui/date_input.py
new file mode 100644
index 0000000..06536c7
--- /dev/null
+++ b/todo/util/gui/date_input.py
@@ -0,0 +1,79 @@
+from PyQt5 import QtWidgets, QtCore, QtGui
+
+import todo.gui.icon
+
+class DateInput(QtWidgets.QLineEdit):
+
+ def __init__(self, init_date: str, parent):
+ super().__init__(parent)
+ self.setReadOnly(True)
+ self.installEventFilter(self)
+ self._date = init_date
+ self.update(init_date)
+ 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
+ Calendar(self, self._date, self.update).exec_()
+ else:
+ self._is_editing = False
+ self.clearFocus()
+ return super(DateInput, self).eventFilter(source, event)
+
+ def update(self, date: str):
+ self._date = date
+ self.setText(date)
+
+class Calendar(QtWidgets.QDialog):
+
+ def __init__(self, parent: QtWidgets.QWidget, init_date: str, update_date):
+ super().__init__(parent)
+
+ self._update_date = update_date
+ self._calendar = QtWidgets.QCalendarWidget(self)
+
+ self.setWindowTitle("Sélectionnez une date")
+ self.setMinimumSize(QtCore.QSize(300, 400))
+
+ layout = QtWidgets.QVBoxLayout(self)
+ self.setLayout(layout)
+
+ self._calendar.setMinimumDate(QtCore.QDate.currentDate())
+ self._calendar.activated.connect(self.select)
+ if init_date:
+ self._calendar.setSelectedDate(QtCore.QDate.fromString(init_date, "dd/MM/yyyy"))
+ layout.addWidget(self._calendar)
+
+ buttons = QtWidgets.QWidget(self)
+ buttons_layout = QtWidgets.QHBoxLayout(buttons)
+ layout.addWidget(buttons)
+
+ accept_button = QtWidgets.QPushButton("Sélectionner", self)
+ accept_button.setIcon(todo.gui.icon.dialog_ok(accept_button.style()))
+ accept_button.clicked.connect(self.select)
+ buttons_layout.addWidget(accept_button)
+
+ reset_button = QtWidgets.QPushButton("Effacer", self)
+ reset_button.setIcon(todo.gui.icon.dialog_reset(reset_button.style()))
+ reset_button.clicked.connect(self.reset)
+ buttons_layout.addWidget(reset_button)
+
+ cancel_button = QtWidgets.QPushButton("Annuler", self)
+ cancel_button.setIcon(todo.gui.icon.dialog_cancel(cancel_button.style()))
+ cancel_button.clicked.connect(lambda: self.reject())
+ buttons_layout.addWidget(cancel_button)
+
+ def select(self):
+ self.accept()
+ self._update_date(self._calendar.selectedDate().toString("dd/MM/yyyy"))
+
+ def reset(self):
+ self.accept()
+ self._update_date("")
+
+ def keyPressEvent(self, event):
+ super().keyPressEvent(event)
+ if event.key() in (QtCore.Qt.Key_Delete, QtCore.Qt.Key_Backspace):
+ self.reset()