diff --git a/src/main/python/plotlyst/view/common.py b/src/main/python/plotlyst/view/common.py index 16b485bf7..43953fa35 100644 --- a/src/main/python/plotlyst/view/common.py +++ b/src/main/python/plotlyst/view/common.py @@ -370,9 +370,12 @@ def insert_before(parent: QWidget, widget: QWidget, reference: QWidget): parent.layout().insertWidget(i, widget) -def insert_after(parent: QWidget, widget: QWidget, reference: QWidget): +def insert_after(parent: QWidget, widget: QWidget, reference: QWidget, alignment=None): i = parent.layout().indexOf(reference) - parent.layout().insertWidget(i + 1, widget) + if alignment is not None: + parent.layout().insertWidget(i + 1, widget, alignment=alignment) + else: + parent.layout().insertWidget(i + 1, widget) def tool_btn(icon: QIcon, tooltip: str = '', checkable: bool = False, base: bool = False, diff --git a/src/main/python/plotlyst/view/scene_editor.py b/src/main/python/plotlyst/view/scene_editor.py index ec3e60e89..1fd7cf390 100644 --- a/src/main/python/plotlyst/view/scene_editor.py +++ b/src/main/python/plotlyst/view/scene_editor.py @@ -26,12 +26,12 @@ from PyQt6.QtWidgets import QWidget, QTableView from overrides import overrides from qtanim import fade_in -from qthandy import flow, clear_layout, underline, incr_font, margins +from qthandy import flow, underline, incr_font, margins from qtmenu import MenuWidget, ScrollableMenuWidget from src.main.python.plotlyst.core.client import json_client from src.main.python.plotlyst.core.domain import Novel, Scene, Document, StoryBeat, \ - Character, ScenePlotReference, TagReference, ScenePurposeType, ScenePurpose + Character, TagReference, ScenePurposeType, ScenePurpose from src.main.python.plotlyst.env import app_env from src.main.python.plotlyst.event.core import emit_info, EventListener, Event, emit_event from src.main.python.plotlyst.event.handler import event_dispatchers @@ -45,7 +45,6 @@ from src.main.python.plotlyst.view.widget.labels import CharacterLabel from src.main.python.plotlyst.view.widget.scene.editor import ScenePurposeSelectorWidget, ScenePurposeTypeButton, \ SceneStorylineEditor, SceneAgendaEditor -from src.main.python.plotlyst.view.widget.scene.plot import ScenePlotSelector from src.main.python.plotlyst.view.widget.scenes import SceneTagSelector @@ -87,8 +86,6 @@ def __init__(self, novel: Novel, scene: Optional[Scene] = None): self.ui.lblTitleEmoji.setText(emoji.emojize(':clapper_board:')) self.ui.lblSynopsisEmoji.setFont(self._emoji_font) self.ui.lblSynopsisEmoji.setText(emoji.emojize(':scroll:')) - self.ui.lblPlotEmoji.setFont(self._emoji_font) - self.ui.lblPlotEmoji.setText(emoji.emojize(':chart_increasing:')) self.ui.wdgStructure.setBeatsCheckable(True) self.ui.wdgStructure.setStructure(self.novel) @@ -140,7 +137,7 @@ def __init__(self, novel: Novel, scene: Optional[Scene] = None): self._btnPurposeType.selectionRequested.connect(self._resetPurposeEditor) self.ui.wdgMidbar.layout().insertWidget(0, self._btnPurposeType) - self._storylineEditor = SceneStorylineEditor() + self._storylineEditor = SceneStorylineEditor(self.novel) self.ui.tabStorylines.layout().addWidget(self._storylineEditor) self._agencyEditor = SceneAgendaEditor(self.novel) @@ -148,8 +145,6 @@ def __init__(self, novel: Novel, scene: Optional[Scene] = None): self.ui.btnClose.clicked.connect(self._on_close) - flow(self.ui.wdgPlotContainer) - self.ui.wdgSceneStructure.setUnsetCharacterSlot(self._pov_not_selected_notification) self._update_view(scene) @@ -185,12 +180,8 @@ def _update_view(self, scene: Optional[Scene] = None): self.ui.sbDay.setValue(self.scene.day) self.ui.wdgSceneStructure.setScene(self.novel, self.scene) - clear_layout(self.ui.wdgPlotContainer) - for plot_v in self.scene.plot_values: - self._add_plot_selector(plot_v) - self._add_plot_selector() - self.tag_selector.setScene(self.scene) + self._storylineEditor.setScene(self.scene) self.ui.lineTitle.setText(self.scene.title) self.ui.textSynopsis.setText(self.scene.synopsis) @@ -259,14 +250,6 @@ def _pov_not_selected_notification(self): emit_info('POV character must be selected first') qtanim.shake(self.ui.wdgPov) - def _add_plot_selector(self, plot_value: Optional[ScenePlotReference] = None): - if plot_value or len(self.novel.plots) > len(self.scene.plot_values): - plot_selector = ScenePlotSelector(self.novel, self.scene, simplified=len(self.scene.plot_values) > 0) - plot_selector.plotSelected.connect(self._add_plot_selector) - if plot_value: - plot_selector.setPlot(plot_value) - self.ui.wdgPlotContainer.layout().addWidget(plot_selector) - def _on_pov_changed(self, pov: Character): self.scene.pov = pov diff --git a/src/main/python/plotlyst/view/widget/scene/editor.py b/src/main/python/plotlyst/view/widget/scene/editor.py index 6cc359ebe..e5490ba8f 100644 --- a/src/main/python/plotlyst/view/widget/scene/editor.py +++ b/src/main/python/plotlyst/view/widget/scene/editor.py @@ -18,7 +18,6 @@ along with this program. If not, see . """ from abc import abstractmethod -from enum import Enum from functools import partial from typing import List, Optional, Any @@ -35,17 +34,18 @@ from src.main.python.plotlyst.common import raise_unrecognized_arg from src.main.python.plotlyst.core.domain import Scene, Novel, ScenePurpose, advance_story_scene_purpose, \ ScenePurposeType, reaction_story_scene_purpose, character_story_scene_purpose, setup_story_scene_purpose, \ - emotion_story_scene_purpose, exposition_story_scene_purpose, scene_purposes, Character + emotion_story_scene_purpose, exposition_story_scene_purpose, scene_purposes, Character, Plot from src.main.python.plotlyst.event.core import EventListener, Event, emit_event from src.main.python.plotlyst.event.handler import event_dispatchers from src.main.python.plotlyst.events import SceneChangedEvent from src.main.python.plotlyst.service.persistence import RepositoryPersistenceManager from src.main.python.plotlyst.view.common import DelayedSignalSlotConnector, action, wrap, label, scrolled, \ - ButtonPressResizeEventFilter + ButtonPressResizeEventFilter, insert_after from src.main.python.plotlyst.view.icons import IconRegistry from src.main.python.plotlyst.view.widget.characters import CharacterSelectorButton from src.main.python.plotlyst.view.widget.display import Icon from src.main.python.plotlyst.view.widget.input import RemovalButton +from src.main.python.plotlyst.view.widget.scene.plot import ScenePlotSelectorButton class SceneMiniEditor(QWidget, EventListener): @@ -362,11 +362,6 @@ def __init__(self, parent=None): self._wdgPurposes.layout().addWidget(spacer()) -class SceneElementIconDisplayMode(Enum): - Center = 0 - Left = 1 - - class SceneElementWidget(QWidget): def __init__(self, parent=None): super().__init__(parent) @@ -434,7 +429,7 @@ def leaveEvent(self, event: QEvent) -> None: else: self._btnClose.setVisible(False) - def setIcon(self, icon: str, colorActive: str): + def setIcon(self, icon: str, colorActive: str = 'black'): self._colorActive = QColor(colorActive) self._iconActive.setIcon(IconRegistry.from_name(icon, colorActive)) self._iconIdle.setIcon(IconRegistry.from_name(icon, 'lightgrey')) @@ -497,13 +492,32 @@ def _updateValue(self, value: Any): class PlotSceneElementEditor(TextBasedSceneElementWidget): - def __init__(self, parent=None): + def __init__(self, novel: Novel, parent=None): super().__init__(parent) + self._novel = novel + + self._btnPlotSelector = ScenePlotSelectorButton(self._novel) + self._btnPlotSelector.plotSelected.connect(self._plotSelected) + self._btnPlotSelector.setFixedHeight(self._titleActive.sizeHint().height()) + self._titleActive.setHidden(True) + insert_after(self._pageEditor, self._btnPlotSelector, reference=self._titleActive, + alignment=Qt.AlignmentFlag.AlignCenter) + + def setScene(self, scene: Scene): + self._btnPlotSelector.setScene(scene) + + def _plotSelected(self, plot: Plot): + self.setIcon(plot.icon, plot.icon_color) + font = self._btnPlotSelector.font() + font.setPointSize(self._titleActive.font().pointSize()) + self._btnPlotSelector.setFont(font) class AbstractSceneElementsEditor(QWidget): def __init__(self, parent=None): super().__init__(parent) + self._scene: Optional[Scene] = None + vbox(self) sp(self).h_exp() self._scrollarea, self._wdgElementsParent = scrolled(self, frameless=True) @@ -520,15 +534,19 @@ def __init__(self, parent=None): self._wdgElementsParent.layout().addWidget(self._lblBottom) self._wdgElementsParent.layout().addWidget(self._wdgElementsBottomRow) + def setScene(self, scene: Scene): + self._scene = scene + class SceneStorylineEditor(AbstractSceneElementsEditor): - def __init__(self, parent=None): + def __init__(self, novel: Novel, parent=None): super().__init__(parent) - self._lblBottom.setText('Character relations') + self._novel = novel + # self._lblBottom.setText('Character relations') - self._plotElement = PlotSceneElementEditor() + self._plotElement = PlotSceneElementEditor(self._novel) self._plotElement.setText('Plot') - self._plotElement.setIcon('fa5s.theater-masks', 'grey') + self._plotElement.setIcon('fa5s.theater-masks') self._plotElement.setPlaceholderText('Describe how this scene is related to the selected plot') self._themeElement = TextBasedSceneElementWidget() @@ -541,13 +559,18 @@ def __init__(self, parent=None): self._consequencesElement = TextBasedSceneElementWidget() self._consequencesElement.setText('Consequences') - self._consequencesElement.setIcon('mdi.ray-start-arrow', 'black') + self._consequencesElement.setIcon('mdi.ray-start-arrow') self._wdgElementsTopRow.layout().addWidget(self._plotElement) self._wdgElementsTopRow.layout().addWidget(self._themeElement) self._wdgElementsTopRow.layout().addWidget(self._outcomeElement) self._wdgElementsTopRow.layout().addWidget(self._consequencesElement) + @overrides + def setScene(self, scene: Scene): + super().setScene(scene) + self._plotElement.setScene(scene) + class SceneAgendaEditor(AbstractSceneElementsEditor): def __init__(self, novel: Novel, parent=None): diff --git a/src/main/python/plotlyst/view/widget/scene/plot.py b/src/main/python/plotlyst/view/widget/scene/plot.py index 6b12dd561..5c554b586 100644 --- a/src/main/python/plotlyst/view/widget/scene/plot.py +++ b/src/main/python/plotlyst/view/widget/scene/plot.py @@ -23,19 +23,18 @@ import qtanim from PyQt6.QtCore import Qt, pyqtSignal from PyQt6.QtGui import QColor, QMouseEvent, QShowEvent -from PyQt6.QtWidgets import QWidget, QToolButton, QTextEdit, QLabel +from PyQt6.QtWidgets import QWidget, QToolButton, QTextEdit, QLabel, QPushButton from overrides import overrides -from qthandy import vbox, hbox, pointy, transparent, retain_when_hidden, spacer, sp, decr_icon, ask_confirmation, line +from qthandy import vbox, hbox, transparent, retain_when_hidden, spacer, sp, decr_icon, line, pointy, underline from qthandy.filter import OpacityEventFilter from qtmenu import MenuWidget from src.main.python.plotlyst.core.domain import Novel, Scene, ScenePlotReference, PlotValue, ScenePlotValueCharge, Plot -from src.main.python.plotlyst.view.common import action, fade_out_and_gc +from src.main.python.plotlyst.view.common import action from src.main.python.plotlyst.view.icons import IconRegistry -from src.main.python.plotlyst.view.style.base import apply_white_menu -from src.main.python.plotlyst.view.widget.button import SecondaryActionToolButton, SecondaryActionPushButton +from src.main.python.plotlyst.view.widget.button import SecondaryActionToolButton from src.main.python.plotlyst.view.widget.display import IconText -from src.main.python.plotlyst.view.widget.labels import PlotValueLabel, SelectionItemLabel, ScenePlotValueLabel +from src.main.python.plotlyst.view.widget.labels import PlotValueLabel class ScenePlotValueChargeWidget(QWidget): @@ -157,68 +156,68 @@ def _commentChanged(self): self.plotReference.data.comment = self.textComment.toPlainText() -class ScenePlotSelector(QWidget): - plotSelected = pyqtSignal() +class ScenePlotSelectorMenu(MenuWidget): + plotSelected = pyqtSignal(Plot) - def __init__(self, novel: Novel, scene: Scene, simplified: bool = False, parent=None): - super(ScenePlotSelector, self).__init__(parent) - self.novel = novel - self.scene = scene + def __init__(self, novel: Novel, parent=None): + super().__init__(parent) + self._novel = novel + self._scene: Optional[Scene] = None + + self.aboutToShow.connect(self._beforeShow) + + def setScene(self, scene: Scene): + self._scene = scene + + def _beforeShow(self): + if self._scene is None: + return + self.clear() + occupied_plot_ids = [x.plot.id for x in self._scene.plot_values] + self.addSection('Link storylines to this scene') + self.addSeparator() + for plot in self._novel.plots: + action_ = action(plot.text, IconRegistry.from_name(plot.icon, plot.icon_color), + partial(self.plotSelected.emit, plot)) + if plot.id in occupied_plot_ids: + action_.setDisabled(True) + self.addAction(action_) + + +class ScenePlotSelectorButton(QPushButton): + plotSelected = pyqtSignal(Plot) + + def __init__(self, novel: Novel, parent=None): + super().__init__(parent) + self._novel = novel + self._scene: Optional[Scene] = None self.plotValue: Optional[ScenePlotReference] = None - hbox(self) - self.label: Optional[SelectionItemLabel] = None + transparent(self) + self.setProperty('no-menu', True) + self.setText('Storyline') - self.btnLinkPlot = SecondaryActionPushButton(self) - if simplified: - self.btnLinkPlot.setIcon(IconRegistry.plus_circle_icon('grey')) - else: - self.btnLinkPlot.setText('Associate plot') - self.layout().addWidget(self.btnLinkPlot) + self.installEventFilter(OpacityEventFilter(parent=self, leaveOpacity=0.7)) - self.btnLinkPlot.installEventFilter( - OpacityEventFilter(parent=self.btnLinkPlot, leaveOpacity=0.4 if simplified else 0.7)) + if self._novel.plots: + pointy(self) + underline(self) + self._menu = ScenePlotSelectorMenu(self._novel, self) + self._menu.plotSelected.connect(self._plotSelected) - self._menu = MenuWidget(self.btnLinkPlot) - self._menu.aboutToShow.connect(self._beforeShow) + def setScene(self, scene: Scene): + self._scene = scene + self._menu.setScene(scene) def setPlot(self, plotValue: ScenePlotReference): self.plotValue = plotValue - self.label = ScenePlotValueLabel(plotValue, self) - pointy(self.label) - self.label.clicked.connect(self._plotValueClicked) - - self.label.removalRequested.connect(self._remove) - self.layout().addWidget(self.label) - self.btnLinkPlot.setHidden(True) + self.setText(plotValue.plot.text) + underline(self, False) def _plotSelected(self, plot: Plot): plotValue = ScenePlotReference(plot) - self.scene.plot_values.append(plotValue) + # TODO add back later + # self._scene.plot_values.append(plotValue) self.setPlot(plotValue) - self.plotSelected.emit() - self._plotValueClicked() - - def _plotValueClicked(self): - menu = MenuWidget(self.label) - apply_white_menu(menu) - menu.addWidget(ScenePlotValueEditor(self.plotValue)) - menu.exec() - - def _beforeShow(self): - self._menu.clear() - occupied_plot_ids = [x.plot.id for x in self.scene.plot_values] - self._menu.addSection('Link plotlines to this scene') - self._menu.addSeparator() - for plot in self.novel.plots: - action_ = action(plot.text, IconRegistry.from_name(plot.icon, plot.icon_color), - partial(self._plotSelected, plot)) - if plot.id in occupied_plot_ids: - action_.setDisabled(True) - self._menu.addAction(action_) - - def _remove(self): - if ask_confirmation(f"Remove scene association for plot '{self.plotValue.plot.text}'?"): - self.scene.plot_values.remove(self.plotValue) - fade_out_and_gc(self.parent(), self) + self.plotSelected.emit(plot) diff --git a/ui/scene_editor.ui b/ui/scene_editor.ui index eee1e3a97..57c981cb5 100644 --- a/ui/scene_editor.ui +++ b/ui/scene_editor.ui @@ -287,49 +287,6 @@ - - - - - 0 - 0 - - - - - 3 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - emo - - - - - - - - -