Skip to content

Commit

Permalink
Popup editor
Browse files Browse the repository at this point in the history
  • Loading branch information
zkovari committed Oct 21, 2024
1 parent 36e0310 commit 5c0f7ed
Show file tree
Hide file tree
Showing 2 changed files with 116 additions and 31 deletions.
6 changes: 6 additions & 0 deletions src/main/python/plotlyst/view/style/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,12 @@
border-radius: 6px;
}}
TaskWidget:hover {{
background-color: {BG_PRIMARY_COLOR};
border: 1px solid lightGrey;
border-radius: 6px;
}}
QSplitter::handle:horizontal {{
width: 20px;
border: 0px;
Expand Down
141 changes: 110 additions & 31 deletions src/main/python/plotlyst/view/widget/task.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,18 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
from functools import partial
from typing import Dict
from typing import Dict, Optional

import qtanim
from PyQt6.QtCore import Qt, pyqtSignal, QMimeData, QObject, QEvent
from PyQt6.QtGui import QFont
from PyQt6.QtWidgets import QWidget, QFrame, QSizePolicy, QLabel, QToolButton, QPushButton
from PyQt6.QtGui import QFont, QMouseEvent
from PyQt6.QtWidgets import QWidget, QFrame, QSizePolicy, QLabel, QToolButton, QPushButton, QDialog, QLineEdit, \
QTextEdit
from overrides import overrides
from qthandy import vbox, hbox, transparent, vspacer, margins, spacer, bold, retain_when_hidden, incr_font, \
gc, decr_icon, pointy
from qthandy.filter import VisibilityToggleEventFilter, OpacityEventFilter, DragEventFilter, DropEventFilter
gc, decr_icon, pointy, sp
from qthandy.filter import VisibilityToggleEventFilter, OpacityEventFilter, DragEventFilter, DropEventFilter, \
DisabledClickEventFilter
from qtmenu import MenuWidget

from plotlyst.common import RELAXED_WHITE_COLOR
Expand All @@ -40,18 +42,82 @@
TaskChangedFromWip
from plotlyst.service.persistence import RepositoryPersistenceManager
from plotlyst.view.common import ButtonPressResizeEventFilter, shadow, action, tool_btn, \
any_menu_visible, insert_before_the_end
any_menu_visible, insert_before_the_end, label, push_btn
from plotlyst.view.icons import IconRegistry
from plotlyst.view.layout import group
from plotlyst.view.widget.button import CollapseButton, TaskTagSelector
from plotlyst.view.widget.characters import CharacterSelectorButton
from plotlyst.view.widget.input import AutoAdjustableLineEdit
from plotlyst.view.widget.display import PopupDialog

TASK_WIDGET_MAX_WIDTH = 350

TASK_MIME_TYPE: str = 'application/task'


class TaskEditorPopup(PopupDialog):
def __init__(self, status: Optional[TaskStatus] = None, task: Optional[Task] = None, parent=None):
super().__init__(parent)
self._status = status
self._task = task

if self._status is None and self._task is None:
raise ValueError('Either status or task must be given')

self.title = label('Edit task' if self._task else 'Add new task', h4=True)
sp(self.title).v_max()
self.wdgTitle = QWidget()
hbox(self.wdgTitle)
self.wdgTitle.layout().addWidget(self.title, alignment=Qt.AlignmentFlag.AlignLeft)
self.wdgTitle.layout().addWidget(self.btnReset, alignment=Qt.AlignmentFlag.AlignRight)

# self.lblDesc = label("Define terms and definitions that are specific to your fictional world.",
# description=True, wordWrap=True)
# sp(self.lblDesc).v_max()

self.lineTitle = QLineEdit()
incr_font(self.lineTitle)
self.lineTitle.setProperty('white-bg', True)
self.lineTitle.setProperty('rounded', True)
self.lineTitle.setPlaceholderText('Title of your task')
self.lineTitle.textChanged.connect(self._titleChanged)

self.textSummary = QTextEdit()
self.textSummary.setProperty('white-bg', True)
self.textSummary.setProperty('rounded', True)
self.textSummary.setPlaceholderText('Task description')

self.btnConfirm = push_btn(text='Confirm', properties=['confirm', 'positive'])
sp(self.btnConfirm).h_exp()
self.btnConfirm.clicked.connect(self.accept)
self.btnConfirm.setDisabled(True)
self.btnConfirm.installEventFilter(
DisabledClickEventFilter(self.btnConfirm, lambda: qtanim.shake(self.lineTitle)))
self.btnCancel = push_btn(text='Cancel', properties=['confirm', 'cancel'])
self.btnCancel.clicked.connect(self.reject)

if self._task:
self.lineTitle.setText(self._task.title)
self.textSummary.setMarkdown(self._task.summary)

self.frame.layout().addWidget(self.wdgTitle)
self.frame.layout().addWidget(self.lineTitle)
self.frame.layout().addWidget(self.textSummary)
self.frame.layout().addWidget(group(self.btnCancel, self.btnConfirm), alignment=Qt.AlignmentFlag.AlignRight)

def display(self) -> Optional[Task]:
result = self.exec()
if result == QDialog.DialogCode.Accepted:
if self._task:
self._task.title = self.lineTitle.text()
self._task.summary = self.textSummary.toPlainText()
return self._task
else:
return Task(self.lineTitle.text(), self._status.id, summary=self.textSummary.toPlainText())

def _titleChanged(self, key: str):
self.btnConfirm.setEnabled(len(key) > 0)


class TaskWidget(QFrame):
removalRequested = pyqtSignal(object)
changed = pyqtSignal()
Expand All @@ -66,15 +132,15 @@ def __init__(self, task: Task, parent=None):
self.setMinimumHeight(75)
shadow(self, 3)

self._lineTitle = AutoAdjustableLineEdit(self, defaultWidth=100)
self._lineTitle.setPlaceholderText('New task')
self._lineTitle.setText(task.title)
self._lineTitle.setFrame(False)
self._lineTitle = label(task.title, wordWrap=True)
sp(self._lineTitle).h_exp()
font = QFont(app_env.sans_serif_font())
font.setWeight(QFont.Weight.Medium)
self._lineTitle.setFont(font)
incr_font(self._lineTitle)

self.setToolTip(self._task.summary)

self._charSelector = CharacterSelectorButton(app_env.novel, self, opacityEffectEnabled=False, iconSize=24)
self._charSelector.setToolTip('Link character')
decr_icon(self._charSelector)
Expand All @@ -85,7 +151,11 @@ def __init__(self, task: Task, parent=None):
retain_when_hidden(self._charSelector)
self._charSelector.characterSelected.connect(self._linkCharacter)
self._charSelector.menu().aboutToHide.connect(self._onLeave)
top_wdg = group(self._lineTitle, spacer(), self._charSelector, margin=0, spacing=1)
top_wdg = QWidget()
hbox(top_wdg, 0, 1)
top_wdg.layout().addWidget(self._lineTitle)
top_wdg.layout().addWidget(self._charSelector, alignment=Qt.AlignmentFlag.AlignTop)
# top_wdg = group(self._lineTitle, self._charSelector, margin=0, spacing=1)
self.layout().addWidget(top_wdg, alignment=Qt.AlignmentFlag.AlignTop)

self._wdgBottom = QWidget()
Expand All @@ -104,7 +174,7 @@ def __init__(self, task: Task, parent=None):
parent=self._wdgBottom)
decr_icon(self._btnMenu)
menu = MenuWidget(self._btnMenu)
menu.addAction(action('Rename', IconRegistry.edit_icon(), self._lineTitle.setFocus))
menu.addAction(action('Edit', IconRegistry.edit_icon(), self._edit))
menu.addSeparator()
menu.addAction(action('Delete', IconRegistry.trash_can_icon(), lambda: self.removalRequested.emit(self)))
menu.aboutToHide.connect(self._onLeave)
Expand All @@ -124,8 +194,8 @@ def __init__(self, task: Task, parent=None):
self._btnMenu.setHidden(True)

self.installEventFilter(self)
self._lineTitle.textEdited.connect(self._titleEdited)
self._lineTitle.editingFinished.connect(self._titleEditingFinished)
# self._lineTitle.textEdited.connect(self._titleEdited)
# self._lineTitle.editingFinished.connect(self._titleEditingFinished)

@overrides
def eventFilter(self, watched: QObject, event: QEvent) -> bool:
Expand All @@ -141,20 +211,27 @@ def eventFilter(self, watched: QObject, event: QEvent) -> bool:
self._onLeave()
return super(TaskWidget, self).eventFilter(watched, event)

@overrides
def mouseDoubleClickEvent(self, event: QMouseEvent) -> None:
super().mouseDoubleClickEvent(event)
self._edit()

def task(self) -> Task:
return self._task

def activate(self):
anim = qtanim.fade_in(self, 150)
anim.finished.connect(self._activated)

def _titleEdited(self, text: str):
self._task.title = text
self.changed.emit()
def refresh(self):
self._lineTitle.setText(self._task.title)
self.setToolTip(self._task.summary)

def _titleEditingFinished(self):
if not self._task.title:
self.removalRequested.emit(self)
def _edit(self):
task = TaskEditorPopup.popup(task=self._task)
if task:
self.refresh()
self.changed.emit()

def _activated(self):
self._lineTitle.setFocus()
Expand Down Expand Up @@ -316,7 +393,7 @@ def event_received(self, event: Event):
def status(self) -> TaskStatus:
return self._status

def addTask(self, task: Task, edit: bool = False) -> TaskWidget:
def addTask(self, task: Task) -> TaskWidget:
wdg = TaskWidget(task, self)
self._container.layout().insertWidget(self._container.layout().count() - 2, wdg,
alignment=Qt.AlignmentFlag.AlignTop)
Expand All @@ -328,8 +405,6 @@ def addTask(self, task: Task, edit: bool = False) -> TaskWidget:
wdg.changed.connect(partial(self.taskChanged.emit, task))
wdg.resolved.connect(partial(self.__removeTaskWidget, wdg))
wdg.resolved.connect(partial(self.taskResolved.emit, task))
if edit:
wdg.activate()

if self._status.wip:
emit_event(self._novel, TaskChangedToWip(self, task))
Expand All @@ -338,9 +413,11 @@ def addTask(self, task: Task, edit: bool = False) -> TaskWidget:
return wdg

def _addNewTask(self):
task = Task('', self._status.id)
self._novel.board.tasks.append(task)
self.addTask(task, edit=True)
task = TaskEditorPopup.popup(status=self._status)
if task:
self._novel.board.tasks.append(task)
wdg = self.addTask(task)
wdg.activate()

def _deleteTask(self, taskWidget: TaskWidget):
task = taskWidget.task()
Expand Down Expand Up @@ -421,10 +498,12 @@ def __init__(self, novel: Novel, parent=None):
def addNewTask(self):
if self._statusColumns:
column = self._firstStatusColumn()
task = Task('', column.status().id)
self._novel.board.tasks.append(task)
column.addTask(task, edit=True)
self.taskAdded.emit(task)
task = TaskEditorPopup.popup(status=column.status())
if task:
self._novel.board.tasks.append(task)
wdg = column.addTask(task)
wdg.activate()
self.taskAdded.emit(task)

def _firstStatusColumn(self) -> StatusColumnWidget:
return self._statusColumns[str(self._novel.board.statuses[0].id)]
Expand Down

0 comments on commit 5c0f7ed

Please sign in to comment.