diff --git a/src/prefabs/subTaskMenu.py b/src/prefabs/subTaskMenu.py new file mode 100644 index 0000000..4a6c6e0 --- /dev/null +++ b/src/prefabs/subTaskMenu.py @@ -0,0 +1,20 @@ +from typing import Optional + +from PySide6.QtWidgets import QWidget +from qfluentwidgets import Action, FluentIcon, RoundMenu + +from prefabs.customFluentIcon import CustomFluentIcon + + +class SubTaskMenu(RoundMenu): + def __init__(self, title: str = "", parent: Optional[QWidget] = None) -> None: + super().__init__(title, parent) + + self.addSubTaskAction = Action(CustomFluentIcon.ADD_SUBTASK, "Add Subtask") + self.editTimeAction = Action(FluentIcon.EDIT, "Edit Time") + self.deleteSubTaskAction = Action(FluentIcon.DELETE, "Delete Subtask") + + self.addAction(self.addSubTaskAction) + self.addAction(self.editTimeAction) + self.addSeparator() + self.addAction(self.deleteSubTaskAction) diff --git a/src/prefabs/taskList.py b/src/prefabs/taskList.py index 7d836e2..c9e386a 100644 --- a/src/prefabs/taskList.py +++ b/src/prefabs/taskList.py @@ -1,14 +1,28 @@ -from typing import Optional +from typing import TYPE_CHECKING, Optional, cast from PySide6.QtCore import QModelIndex, Qt -from PySide6.QtGui import QColor, QDragEnterEvent, QDropEvent, QPainter, QPaintEvent, QPen, QResizeEvent +from PySide6.QtGui import ( + QColor, + QContextMenuEvent, + QDragEnterEvent, + QDropEvent, + QPainter, + QPaintEvent, + QPen, + QResizeEvent, +) from PySide6.QtWidgets import QAbstractItemView, QProxyStyle, QStyle, QStyleOption, QWidget from qfluentwidgets import TreeView, isDarkTheme -from models.taskListModel import TaskListModel +from models.taskListModel import TaskListModel, TaskNode +from prefabs.subTaskMenu import SubTaskMenu from prefabs.taskListItemDelegate import TaskListItemDelegate +from prefabs.taskMenu import TaskMenu from ui_py.ui_tasks_list_view import Ui_TaskView +if TYPE_CHECKING: + from views.subinterfaces.tasksView import TaskListView + # from: https://www.qtcentre.org/threads/35443-Customize-drop-indicator-in-QTreeView?p=167572#post167572 class TaskListStyle(QProxyStyle): @@ -193,3 +207,44 @@ def _onItemCollapsed(self, index: QModelIndex) -> None: model: TaskListModel = self.model() if model: model.setData(index, False, model.IsExpandedRole) + + def contextMenuEvent(self, e: QContextMenuEvent) -> None: + index: QModelIndex = self.indexAt(e.pos()) + task_id: int = self.model().data(index, TaskListModel.IDRole) + task_node: TaskNode = self.model().getTaskNodeById(task_id) + is_root_task: bool = task_node.is_root() + + parent_view = self.parentWidget() + while parent_view is not None: + if parent_view.objectName() == "task_interface": + break + parent_view = parent_view.parentWidget() + + assert parent_view is not None + parent_view = cast("TaskListView", parent_view) + + if not is_root_task: + menu = SubTaskMenu() + + menu.addSubTaskAction.triggered.connect(parent_view.addSubTaskAction.trigger) + menu.editTimeAction.triggered.connect(parent_view.editTaskTimeButton.click) + menu.deleteSubTaskAction.triggered.connect(parent_view.deleteTaskButton.click) + menu.exec(e.globalPos()) + else: + menu = TaskMenu() + + menu.addTaskAction.triggered.connect(parent_view.addTaskAction.trigger) + menu.addSubTaskAction.triggered.connect(parent_view.addSubTaskAction.trigger) + menu.editTimeAction.triggered.connect(parent_view.editTaskTimeButton.click) + + menu.markTaskAsIncompleteAction.triggered.connect(parent_view.toggleTaskCompletion) + menu.markTaskAsCompletedAction.triggered.connect(parent_view.toggleTaskCompletion) + + if self.objectName() == "todoTasksList": + menu.removeAction(menu.markTaskAsIncompleteAction) + elif self.objectName() == "completedTasksList": + menu.removeAction(menu.markTaskAsCompletedAction) + + menu.deleteTaskAction.triggered.connect(parent_view.deleteTaskButton.click) + + menu.exec(e.globalPos()) diff --git a/src/prefabs/taskMenu.py b/src/prefabs/taskMenu.py new file mode 100644 index 0000000..f9b5b2a --- /dev/null +++ b/src/prefabs/taskMenu.py @@ -0,0 +1,26 @@ +from typing import Optional + +from PySide6.QtWidgets import QWidget +from qfluentwidgets import Action, FluentIcon, RoundMenu + +from prefabs.customFluentIcon import CustomFluentIcon + + +class TaskMenu(RoundMenu): + def __init__(self, title: str = "", parent: Optional[QWidget] = None) -> None: + super().__init__(title, parent) + + self.addTaskAction = Action(FluentIcon.ADD, "Add Task") + self.addSubTaskAction = Action(CustomFluentIcon.ADD_SUBTASK, "Add Subtask") + self.editTimeAction = Action(FluentIcon.EDIT, "Edit Time") + self.markTaskAsIncompleteAction = Action(FluentIcon.CANCEL, "Mark as Incomplete") + self.markTaskAsCompletedAction = Action(FluentIcon.ACCEPT, "Mark as Completed") + self.deleteTaskAction = Action(FluentIcon.DELETE, "Delete Subtask") + + self.addAction(self.addTaskAction) + self.addAction(self.addSubTaskAction) + self.addAction(self.editTimeAction) + self.addAction(self.markTaskAsIncompleteAction) + self.addAction(self.markTaskAsCompletedAction) + self.addSeparator() + self.addAction(self.deleteTaskAction) diff --git a/src/views/subinterfaces/tasksView.py b/src/views/subinterfaces/tasksView.py index 31cf0f1..748f258 100644 --- a/src/views/subinterfaces/tasksView.py +++ b/src/views/subinterfaces/tasksView.py @@ -331,6 +331,40 @@ def deleteTask(self) -> None: model.deleteTask(selectedIndex.row(), parent_index) + def toggleTaskCompletion(self) -> None: + """ + toggle completion state for the selected root task by simulating a drag and drop programmatically. + """ + if self.todoTasksList.selectionModel().hasSelection(): + source_list = self.todoTasksList + target_list = self.completedTasksList + elif self.completedTasksList.selectionModel().hasSelection(): + source_list = self.completedTasksList + target_list = self.todoTasksList + else: + return + + index = source_list.selectionModel().currentIndex() + source_model: TaskListModel = source_list.model() + target_model: TaskListModel = target_list.model() + task_id = index.data(TaskListModel.IDRole) + task_node = source_model.getTaskNodeById(task_id) + + if task_node is None or not task_node.is_root(): + return + + try: + mime_data = source_model.mimeData([index]) + ok = target_model.dropMimeData(mime_data, Qt.DropAction.MoveAction, -1, 0, QModelIndex()) + if not ok: + return + + source_model.finishDrag() + source_model.removeRows(index.row(), 1, QModelIndex()) + finally: + source_model.finishDrag() + target_model.finishDrag() + @restoreFocus def editTaskTime(self) -> None: row = None