Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 18 additions & 4 deletions src/controller/file/deserialization/migrations.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
logger = getLogger(__name__)


def replace_old_filter_configurations(f: Filter) -> Filter:
def replace_old_filter_configurations(f: Filter) -> tuple[Filter, bool]:
"""Replace outdated filter representations if necessary.

Some filter representations may have been replaced with their vFilter
Expand All @@ -22,10 +22,24 @@ def replace_old_filter_configurations(f: Filter) -> Filter:

Returns:
The original filter if no modification was required, or a new
updated representation.
updated representation. Also True if a migration happened.

"""
migration_happened: bool = False
if f.filter_type == FilterTypeEnumeration.FILTER_TYPE_CUES:
f.filter_type = int(FilterTypeEnumeration.VFILTER_CUES)
logger.info("Replaced filter type of filter %s to become virtual.", f.filter_id)
return f
logger.info("Replaced filter type of filter %s to become virtual on next load. Reloading is advised.",
f.filter_id)
migration_happened = True
if f.filter_type in [
FilterTypeEnumeration.FILTER_FADER_RAW,
FilterTypeEnumeration.FILTER_FADER_HSI,
FilterTypeEnumeration.FILTER_FADER_HSIA,
FilterTypeEnumeration.FILTER_FADER_HSIU,
FilterTypeEnumeration.FILTER_FADER_HSIAU,
]:
f.filter_type = int(int(f.filter_type) * -1)
logger.info("Replaced filter type of filter %s to become virtual on next load. Reloading is advised",
f.filter_id)
migration_happened = True
return f, migration_happened
27 changes: 19 additions & 8 deletions src/controller/file/read.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ def read_document(file_name: str, board_configuration: BoardConfiguration) -> bo
_clean_tags(root, prefix)

scene_defs_to_be_parsed = []
migration_happened = False
loaded_banksets: dict[str, BankSet] = {}

pn.total_step_count += len(root)
Expand All @@ -149,7 +150,7 @@ def read_document(file_name: str, board_configuration: BoardConfiguration) -> bo

pn.total_step_count += len(scene_defs_to_be_parsed)
for scene_def in scene_defs_to_be_parsed:
_parse_scene(scene_def, board_configuration, loaded_banksets)
migration_happened |= _parse_scene(scene_def, board_configuration, loaded_banksets)
pn.current_step_number += 1

pn.current_step_number += 1
Expand All @@ -169,6 +170,9 @@ def read_document(file_name: str, board_configuration: BoardConfiguration) -> bo
board_configuration.broadcaster.end_show_file_parsing.emit()
board_configuration.broadcaster.show_file_loaded.emit()
pn.close()
if migration_happened:
board_configuration.broadcaster.message_box_required.emit("A filter migration was performed. Please save and "
"reload the show file.")
return True


Expand Down Expand Up @@ -260,7 +264,7 @@ def _parse_filter_page(element: ET.Element, parent_scene: Scene, instantiated_pa

def _parse_scene(
scene_element: ET.Element, board_configuration: BoardConfiguration, loaded_banksets: dict[str, BankSet]
) -> None:
) -> bool:
"""Load a scene from the show file data structure.

Args:
Expand All @@ -284,12 +288,17 @@ def _parse_scene(

scene = Scene(scene_id=scene_id, human_readable_name=human_readable_name, board_configuration=board_configuration)

if scene_element.attrib.get("linkedBankset") in loaded_banksets:
scene.linked_bankset = loaded_banksets[scene_element.attrib["linkedBankset"]]

filter_pages = []
ui_page_elements = []
migration_happened = False

for child in scene_element:
match child.tag:
case "filter":
_parse_filter(child, scene)
migration_happened |= _parse_filter(child, scene)
case "filterpage":
filter_pages.append(child)
case "uipage":
Expand All @@ -309,13 +318,11 @@ def _parse_scene(
logger.error("No suitable parent found while parsing filter pages")
break

if scene_element.attrib.get("linkedBankset") in loaded_banksets:
scene.linked_bankset = loaded_banksets[scene_element.attrib["linkedBankset"]]

for ui_page_element in ui_page_elements:
_append_ui_page(ui_page_element, scene)

board_configuration.broadcaster.scene_created.emit(scene)
return migration_happened


def _append_ui_page(page_def: ET.Element, scene: Scene) -> None:
Expand Down Expand Up @@ -387,13 +394,16 @@ def _append_ui_page(page_def: ET.Element, scene: Scene) -> None:
scene.ui_pages.append(page)


def _parse_filter(filter_element: ET.Element, scene: Scene) -> None:
def _parse_filter(filter_element: ET.Element, scene: Scene) -> bool:
"""Load a filter from the XML definition.

Args:
filter_element: The XML data to load the filter from.
scene: The scene to append the filter to.

Returns:
bool: True if a migration happened and the show file should be saved and reloaded.

"""
filter_id = ""
filter_type = 0
Expand Down Expand Up @@ -427,10 +437,11 @@ def _parse_filter(filter_element: ET.Element, scene: Scene) -> None:
case _:
logger.warning("Filter %s contains unknown element: %s", filter_id, child.tag)

filter_ = replace_old_filter_configurations(filter_)
filter_, migration_happened = replace_old_filter_configurations(filter_)
if isinstance(filter_, VirtualFilter):
filter_.deserialize()
scene.append_filter(filter_)
return migration_happened


def _parse_channel_link(initial_parameters_element: ET.Element, filter_: Filter) -> None:
Expand Down
1 change: 1 addition & 0 deletions src/model/broadcaster.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ class Broadcaster(QtCore.QObject, metaclass=QObjectSingletonMeta):
view_to_action_config: QtCore.Signal = QtCore.Signal()
view_leave_action_config: QtCore.Signal = QtCore.Signal()
application_closing: QtCore.Signal = QtCore.Signal()
message_box_required: QtCore.Signal = QtCore.Signal(str)
################################################################
save_button_pressed: QtCore.Signal = QtCore.Signal()
commit_button_pressed: QtCore.Signal = QtCore.Signal()
Expand Down
6 changes: 6 additions & 0 deletions src/model/filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,12 @@ class FilterTypeEnumeration(IntFlag):
Negative values indicate virtual filters.
"""

VFILTER_FADER_RAW = -39
VFILTER_FADER_HSI = -40
VFILTER_FADER_HSIA = -41
VFILTER_FADER_HSIU = -42
VFILTER_FADER_HSIAU = -43

VFILTER_SEQUENCER = -12
VFILTER_COLOR_MIXER = -11
VFILTER_IMPORT = -10
Expand Down
94 changes: 94 additions & 0 deletions src/model/virtual_filters/fader_vfilter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
"""Module containing vFilter wrapper for fader filters."""
from __future__ import annotations

from logging import getLogger
from typing import TYPE_CHECKING, override

from model import Filter
from model.control_desk import BankSet, BanksetIDUpdateListener
from model.filter import FilterTypeEnumeration, VirtualFilter

if TYPE_CHECKING:
from model.scene import Scene

logger = getLogger(__name__)

class FaderUpdatingVFilter(VirtualFilter, BanksetIDUpdateListener):
"""VFilter wrapper to automatically register bank set updates on filter load."""

def __init__(
self,
scene: Scene,
filter_id: str,
filter_type: FilterTypeEnumeration,
pos: tuple[int] | None = None,
) -> None:
"""Initialize the filter.

As this virtual filter simply adds the required callbacks to bank set handling, it behaves much like the
original filter.

Args:
scene: The scene of the filter.
filter_id: The id of the filter.
filter_type: The type of the filter. Must be one of the fader ones.
pos: The position of the filter inside the filter page.

"""
super().__init__(scene, filter_id, filter_type=int(filter_type), pos=pos)
self.bankset_model: BankSet | None = None
self.update_bankset_listener()

@override
def resolve_output_port_id(self, virtual_port_id: str) -> str | None:
return f"{self.filter_id}:{virtual_port_id}"

@override
def instantiate_filters(self, filter_list: list[Filter]) -> None:
f = Filter(self.scene, self.filter_id, FilterTypeEnumeration(self.filter_type * -1),
pos=self.pos, filter_configurations=self.filter_configurations.copy(),
initial_parameters=self.initial_parameters.copy())
f.channel_links.update(self.channel_links)
f.gui_update_keys.update(self.gui_update_keys)
f.initial_parameters.update(self.initial_parameters)
f.in_data_types.update(self.in_data_types)
f.default_values.update(self.default_values)
filter_list.append(f)

def update_bankset_listener(self) -> None:
"""Update the attached bank set UUID change listener of this fader."""
if not "set_id" in self.filter_configurations.keys() or self.scene.linked_bankset is None:

Check failure on line 60 in src/model/virtual_filters/fader_vfilter.py

View workflow job for this annotation

GitHub Actions / test_on_main_pr

Ruff (E713)

src/model/virtual_filters/fader_vfilter.py:60:16: E713 Test for membership should be `not in`

Check failure on line 60 in src/model/virtual_filters/fader_vfilter.py

View workflow job for this annotation

GitHub Actions / test_on_main_pr

Ruff (SIM118)

src/model/virtual_filters/fader_vfilter.py:60:16: SIM118 Use `key in dict` instead of `key in dict.keys()`
return
set_id = self.filter_configurations["set_id"]

if self.scene.linked_bankset.id == set_id:
self._bankset_model = self.scene.linked_bankset
else:
for bs in BankSet.linked_bank_sets():
if bs.id == set_id:
self._bankset_model = bs
break
if self._bankset_model is None:
column_candidate = self.scene.linked_bankset.get_column(
self.filter_configurations.get("column_id"))
if column_candidate:
self.filter_configurations["set_id"] = self.scene.linked_bankset.id
self._bankset_model = self.scene.linked_bankset

if self._bankset_model is not None:
self._bankset_model.id_update_listeners.append(self)

@override
def notify_on_new_id(self, new_id: str) -> None:
logger.debug("New bankset ID: %s", new_id)
self.filter.filter_configurations["set_id"] = new_id

@override
def deserialize(self) -> None:
super().deserialize()
self.update_bankset_listener()

def __del__(self) -> None:
"""Deregister the bank set update listener."""
if self._bankset_model is not None:
self._bankset_model.id_update_listeners.remove(self)
5 changes: 5 additions & 0 deletions src/model/virtual_filters/vfilter_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from model.virtual_filters.color_mixer_vfilter import ColorMixerVFilter
from model.virtual_filters.cue_vfilter import CueFilter
from model.virtual_filters.effects_stacks.vfilter import EffectsStack
from model.virtual_filters.fader_vfilter import FaderUpdatingVFilter
from model.virtual_filters.import_vfilter import ImportVFilter
from model.virtual_filters.pan_tilt_constant import PanTiltConstantFilter
from model.virtual_filters.range_adapters import (
Expand Down Expand Up @@ -46,6 +47,10 @@ def construct_virtual_filter_instance(
if not filter_type < 0:
raise ValueError("The provided filter is not a virtual description.")
match filter_type:
case (FilterTypeEnumeration.VFILTER_FADER_RAW | FilterTypeEnumeration.VFILTER_FADER_HSI |
FilterTypeEnumeration.VFILTER_FADER_HSIA | FilterTypeEnumeration.FILTER_FADER_HSIU |
FilterTypeEnumeration.FILTER_FADER_HSIAU):
return FaderUpdatingVFilter(scene, filter_id, filter_type, pos=pos)
case FilterTypeEnumeration.VFILTER_COMBINED_FILTER_PRESET:
# TODO return virtual filter that instantiates a preset (as described in issue #48)
return None
Expand Down
9 changes: 8 additions & 1 deletion src/view/main_window.py
Original file line number Diff line number Diff line change
@@ -1,35 +1,35 @@
"""main Window for the Editor"""

from __future__ import annotations

import os.path
import platform
from typing import TYPE_CHECKING, override

from PySide6 import QtGui, QtWidgets
from PySide6.QtGui import QCloseEvent, QIcon, QKeySequence, QPixmap
from PySide6.QtWidgets import QApplication, QProgressBar, QWidget
from PySide6.QtWidgets import QApplication, QProgressBar, QWidget, QMessageBox

import proto.RealTimeControl_pb2
import style
from controller.file.showfile_dialogs import _save_show_file, show_load_showfile_dialog, show_save_showfile_dialog
from controller.network import NetworkManager
from controller.utils.process_notifications import get_global_process_state, get_progress_changed_signal
from model.board_configuration import BoardConfiguration
from model.broadcaster import Broadcaster
from model.control_desk import BankSet, ColorDeskColumn
from utility import resource_path
from view.action_setup_view.combined_action_setup_widget import CombinedActionSetupWidget
from view.console_mode.console_universe_selector import UniverseSelector
from view.dialogs.colum_dialog import ColumnDialog
from view.logging_view.logging_widget import LoggingWidget
from view.main_widget import MainWidget
from view.misc.settings.settings_dialog import SettingsDialog
from view.patch_view.patch_mode import PatchMode
from view.show_mode.editor.showmanager import ShowEditorWidget
from view.show_mode.player.showplayer import ShowPlayerWidget
from view.utility_widgets.wizzards.patch_plan_export import PatchPlanExportWizard
from view.utility_widgets.wizzards.theater_scene_wizard import TheaterSceneWizard

Check failure on line 32 in src/view/main_window.py

View workflow job for this annotation

GitHub Actions / test_on_main_pr

Ruff (I001)

src/view/main_window.py:3:1: I001 Import block is un-sorted or un-formatted

if TYPE_CHECKING:
from PySide6.QtWidgets import QWizard
Expand Down Expand Up @@ -132,6 +132,7 @@
self._utility_wizard: QWizard | None = None

self.setWindowIcon(QPixmap(resource_path(os.path.join("resources", "logo.png"))))
self._broadcaster.message_box_required.connect(self._open_message_box)

@property
def fish_connector(self) -> NetworkManager:
Expand Down Expand Up @@ -379,3 +380,9 @@
if self._utility_wizard is not None:
self._utility_wizard.deleteLater()
self._utility_wizard = None

def _open_message_box(self, message: str) -> None:
self._settings_dialog = QMessageBox(self)
self._settings_dialog.setText(message)
self._settings_dialog.setModal(True)
self._settings_dialog.show()
2 changes: 1 addition & 1 deletion src/view/show_mode/editor/filter_settings_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ def check_if_filter_has_special_widget(filter_: Filter) -> NodeEditorFilterConfi
The instantiated settings widget or None.

"""
if 39 <= filter_.filter_type <= 43:
if 39 <= filter_.filter_type <= 43 or -43 <= filter_.filter_type <= -39:
return ColumnSelect(filter_)
if filter_.filter_type in [FilterTypeEnumeration.FILTER_TYPE_CUES, FilterTypeEnumeration.VFILTER_CUES]:
return CueEditor(f=filter_)
Expand Down
Loading
Loading