From 9e13d3df178309ee0e6129a226df1de97e9c9f27 Mon Sep 17 00:00:00 2001 From: Leon Dietrich Date: Mon, 13 Oct 2025 17:27:37 +0200 Subject: [PATCH 1/6] fix: bank set updates not registering on non-default filter pages (untested) --- .../file/deserialization/migrations.py | 13 +++- src/model/filter.py | 6 ++ src/model/virtual_filters/fader_vfilter.py | 74 +++++++++++++++++++ src/model/virtual_filters/vfilter_factory.py | 3 + .../show_mode/editor/nodes/impl/faders.py | 50 ++++--------- 5 files changed, 110 insertions(+), 36 deletions(-) create mode 100644 src/model/virtual_filters/fader_vfilter.py diff --git a/src/controller/file/deserialization/migrations.py b/src/controller/file/deserialization/migrations.py index d07eb137..c9681269 100644 --- a/src/controller/file/deserialization/migrations.py +++ b/src/controller/file/deserialization/migrations.py @@ -27,5 +27,16 @@ def replace_old_filter_configurations(f: Filter) -> Filter: """ 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) + logger.info("Replaced filter type of filter %s to become virtual on next load. Reloading is advised.", + f.filter_id) + 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) return f diff --git a/src/model/filter.py b/src/model/filter.py index 07337668..f5b9f602 100644 --- a/src/model/filter.py +++ b/src/model/filter.py @@ -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 diff --git a/src/model/virtual_filters/fader_vfilter.py b/src/model/virtual_filters/fader_vfilter.py new file mode 100644 index 00000000..521c999d --- /dev/null +++ b/src/model/virtual_filters/fader_vfilter.py @@ -0,0 +1,74 @@ +"""Module containing vFilter wrapper for fader filters.""" +from __future__ import annotations + +from logging import getLogger +from typing import override, TYPE_CHECKING + +from model import Filter +from model.filter import VirtualFilter +from model.control_desk import BankSet, BanksetIDUpdateListener + +if TYPE_CHECKING: + from model.scene import Scene + from model.filter import FilterTypeEnumeration + +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: + 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: + 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 + + def __del__(self) -> None: + if self._bankset_model is not None: + self._bankset_model.id_update_listeners.remove(self) diff --git a/src/model/virtual_filters/vfilter_factory.py b/src/model/virtual_filters/vfilter_factory.py index b8494eb0..dcc58bd4 100644 --- a/src/model/virtual_filters/vfilter_factory.py +++ b/src/model/virtual_filters/vfilter_factory.py @@ -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 ( @@ -46,6 +47,8 @@ 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_type, filter_id, pos=pos) case FilterTypeEnumeration.VFILTER_COMBINED_FILTER_PRESET: # TODO return virtual filter that instantiates a preset (as described in issue #48) return None diff --git a/src/view/show_mode/editor/nodes/impl/faders.py b/src/view/show_mode/editor/nodes/impl/faders.py index 64a89461..cb7f761f 100644 --- a/src/view/show_mode/editor/nodes/impl/faders.py +++ b/src/view/show_mode/editor/nodes/impl/faders.py @@ -1,16 +1,21 @@ """Column fader filter nodes""" +from logging import getLogger from model import DataType, Filter, Scene from model.control_desk import BankSet from model.filter import FilterTypeEnumeration +from model.virtual_filters.fader_vfilter import FaderUpdatingVFilter from view.show_mode.editor.nodes.base.filternode import FilterNode +logger = getLogger(__name__) + class _FaderNode(FilterNode): def __init__(self, model: Filter | Scene, filter_type: FilterTypeEnumeration, name: str, terminals: dict[str, dict[str, str]]) -> None: - self._bankset_model: BankSet | None = None super().__init__(model=model, filter_type=filter_type, name=name, terminals=terminals) + self._bankset_model: BankSet | None = self.filter.bankset_model if ( + isinstance(self.filter, FaderUpdatingVFilter)) else None try: self.filter.filter_configurations["set_id"] = model.filter_configurations["set_id"] @@ -20,39 +25,14 @@ def __init__(self, model: Filter | Scene, filter_type: FilterTypeEnumeration, na self.filter.filter_configurations["column_id"] = model.filter_configurations["column_id"] except AttributeError: self.filter.filter_configurations["column_id"] = "" - self._update_bankset_listener() - - def _update_bankset_listener(self) -> None: - set_id = self.filter.filter_configurations["set_id"] - - if self.filter.scene.linked_bankset.id == set_id: - self._bankset_model = self.filter.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.filter.scene.linked_bankset.get_column( - self.filter.filter_configurations.get("column_id")) - if column_candidate: - self.filter.filter_configurations["set_id"] = self.filter.scene.linked_bankset.id - self._bankset_model = self.filter.scene.linked_bankset - - if self._bankset_model is not None: - self._bankset_model.id_update_listeners.append(self) - - def notify_on_new_id(self, new_id: str) -> None: - self.filter.filter_configurations["set_id"] = new_id def update_node_after_settings_changed(self) -> None: if self._bankset_model is not None: self._bankset_model.id_update_listeners.remove(self) - self._update_bankset_listener() - - def __del__(self) -> None: - if self._bankset_model is not None: - self._bankset_model.id_update_listeners.remove(self) + if isinstance(self.filter, FaderUpdatingVFilter): + self.filter.update_bankset_listener() + else: + logger.error("This fader filter has not been updated yet. Please save the show file and reload it NOW!") class FaderRawNode(_FaderNode): @@ -61,7 +41,7 @@ class FaderRawNode(_FaderNode): nodeName = "Raw" # noqa: N815 def __init__(self, model: Filter, name: str) -> None: - super().__init__(model=model, filter_type=FilterTypeEnumeration.FILTER_FADER_RAW, name=name, terminals={ + super().__init__(model=model, filter_type=FilterTypeEnumeration.VFILTER_FADER_RAW, name=name, terminals={ "primary": {"io": "out"}, "secondary": {"io": "out"}, }) @@ -75,7 +55,7 @@ class FaderHSINode(_FaderNode): nodeName = "HSI" # noqa: N815 def __init__(self, model: Filter, name: str) -> None: - super().__init__(model=model, filter_type=FilterTypeEnumeration.FILTER_FADER_HSI, name=name, terminals={ + super().__init__(model=model, filter_type=FilterTypeEnumeration.VFILTER_FADER_HSI, name=name, terminals={ "color": {"io": "out"}, }) @@ -93,7 +73,7 @@ class FaderHSIANode(_FaderNode): nodeName = "HSI-A" # noqa: N815 def __init__(self, model: Filter, name: str) -> None: - super().__init__(model=model, filter_type=FilterTypeEnumeration.FILTER_FADER_HSIA, name=name, terminals={ + super().__init__(model=model, filter_type=FilterTypeEnumeration.VFILTER_FADER_HSIA, name=name, terminals={ "color": {"io": "out"}, "amber": {"io": "out"}, }) @@ -113,7 +93,7 @@ class FaderHSIUNode(_FaderNode): nodeName = "HSI_U" # noqa: N815 def __init__(self, model: Filter, name: str) -> None: - super().__init__(model=model, filter_type=FilterTypeEnumeration.FILTER_FADER_HSIU, name=name, terminals={ + super().__init__(model=model, filter_type=FilterTypeEnumeration.VFILTER_FADER_HSIU, name=name, terminals={ "color": {"io": "out"}, "uv": {"io": "out"}, }) @@ -133,7 +113,7 @@ class FaderHSIAUNode(_FaderNode): nodeName = "HSI-AU" # noqa: N815 def __init__(self, model: Filter, name: str) -> None: - super().__init__(model=model, filter_type=FilterTypeEnumeration.FILTER_FADER_HSIAU, name=name, terminals={ + super().__init__(model=model, filter_type=FilterTypeEnumeration.VFILTER_FADER_HSIAU, name=name, terminals={ "color": {"io": "out"}, "amber": {"io": "out"}, "uv": {"io": "out"}, From 55336db55cf0aad4e6fff30908592cb21dedb5c5 Mon Sep 17 00:00:00 2001 From: Leon Dietrich Date: Mon, 13 Oct 2025 18:15:59 +0200 Subject: [PATCH 2/6] fix: filter node editor opening --- src/view/show_mode/editor/filter_settings_item.py | 2 +- src/view/show_mode/editor/nodes/type_to_node_dict.py | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/view/show_mode/editor/filter_settings_item.py b/src/view/show_mode/editor/filter_settings_item.py index 916bb305..5faabfec 100644 --- a/src/view/show_mode/editor/filter_settings_item.py +++ b/src/view/show_mode/editor/filter_settings_item.py @@ -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_) diff --git a/src/view/show_mode/editor/nodes/type_to_node_dict.py b/src/view/show_mode/editor/nodes/type_to_node_dict.py index 5114ac21..80b8dc9a 100644 --- a/src/view/show_mode/editor/nodes/type_to_node_dict.py +++ b/src/view/show_mode/editor/nodes/type_to_node_dict.py @@ -98,6 +98,11 @@ from view.show_mode.editor.nodes.import_node import ImportNode type_to_node: dict[int, str] = { + FilterTypeEnumeration.VFILTER_FADER_RAW: FaderRawNode.nodeName, + FilterTypeEnumeration.VFILTER_FADER_HSI: FaderHSINode.nodeName, + FilterTypeEnumeration.VFILTER_FADER_HSIA: FaderHSIANode.nodeName, + FilterTypeEnumeration.VFILTER_FADER_HSIU: FaderHSIUNode.nodeName, + FilterTypeEnumeration.VFILTER_FADER_HSIAU: FaderHSIAUNode.nodeName, FilterTypeEnumeration.VFILTER_SEQUENCER: SequencerNode.nodeName, FilterTypeEnumeration.VFILTER_COLOR_MIXER: ColorMixerVFilterNode.nodeName, FilterTypeEnumeration.VFILTER_IMPORT: ImportNode.nodeName, From d63c98eb5f44b053fcb1e33f54f01cb0c090b2ef Mon Sep 17 00:00:00 2001 From: Leon Dietrich Date: Tue, 14 Oct 2025 13:23:02 +0200 Subject: [PATCH 3/6] fix: import ordering --- src/model/virtual_filters/fader_vfilter.py | 5 ++--- src/model/virtual_filters/vfilter_factory.py | 4 +++- src/view/show_mode/editor/nodes/impl/faders.py | 5 ++++- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/model/virtual_filters/fader_vfilter.py b/src/model/virtual_filters/fader_vfilter.py index 521c999d..ea04ce4e 100644 --- a/src/model/virtual_filters/fader_vfilter.py +++ b/src/model/virtual_filters/fader_vfilter.py @@ -2,15 +2,14 @@ from __future__ import annotations from logging import getLogger -from typing import override, TYPE_CHECKING +from typing import TYPE_CHECKING, override from model import Filter -from model.filter import VirtualFilter from model.control_desk import BankSet, BanksetIDUpdateListener +from model.filter import FilterTypeEnumeration, VirtualFilter if TYPE_CHECKING: from model.scene import Scene - from model.filter import FilterTypeEnumeration logger = getLogger(__name__) diff --git a/src/model/virtual_filters/vfilter_factory.py b/src/model/virtual_filters/vfilter_factory.py index dcc58bd4..a9c172ef 100644 --- a/src/model/virtual_filters/vfilter_factory.py +++ b/src/model/virtual_filters/vfilter_factory.py @@ -47,7 +47,9 @@ 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: + 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_type, filter_id, pos=pos) case FilterTypeEnumeration.VFILTER_COMBINED_FILTER_PRESET: # TODO return virtual filter that instantiates a preset (as described in issue #48) diff --git a/src/view/show_mode/editor/nodes/impl/faders.py b/src/view/show_mode/editor/nodes/impl/faders.py index cb7f761f..b8b666a3 100644 --- a/src/view/show_mode/editor/nodes/impl/faders.py +++ b/src/view/show_mode/editor/nodes/impl/faders.py @@ -1,14 +1,17 @@ """Column fader filter nodes""" from logging import getLogger +from typing import TYPE_CHECKING from model import DataType, Filter, Scene -from model.control_desk import BankSet from model.filter import FilterTypeEnumeration from model.virtual_filters.fader_vfilter import FaderUpdatingVFilter from view.show_mode.editor.nodes.base.filternode import FilterNode logger = getLogger(__name__) +if TYPE_CHECKING: + from model.control_desk import BankSet + class _FaderNode(FilterNode): def __init__(self, model: Filter | Scene, filter_type: FilterTypeEnumeration, name: str, From 35abf1c053cb45cc93586d69f25582253b124a69 Mon Sep 17 00:00:00 2001 From: Leon Dietrich Date: Tue, 14 Oct 2025 13:29:48 +0200 Subject: [PATCH 4/6] add: doc in fader_vfilter.py --- src/model/virtual_filters/fader_vfilter.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/model/virtual_filters/fader_vfilter.py b/src/model/virtual_filters/fader_vfilter.py index ea04ce4e..18ed1b39 100644 --- a/src/model/virtual_filters/fader_vfilter.py +++ b/src/model/virtual_filters/fader_vfilter.py @@ -23,6 +23,18 @@ def __init__( 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() @@ -44,6 +56,7 @@ def instantiate_filters(self, filter_list: list[Filter]) -> None: filter_list.append(f) def update_bankset_listener(self) -> None: + """Update the attached bank set UUID change listener of this fader.""" set_id = self.filter_configurations["set_id"] if self.scene.linked_bankset.id == set_id: @@ -69,5 +82,6 @@ def notify_on_new_id(self, new_id: str) -> None: self.filter.filter_configurations["set_id"] = new_id 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) From ba56329e1c5827c7bbbc57054ad7e2997564b30d Mon Sep 17 00:00:00 2001 From: Leon Dietrich Date: Tue, 14 Oct 2025 13:48:17 +0200 Subject: [PATCH 5/6] fix: faders node docs --- .../show_mode/editor/nodes/impl/faders.py | 26 ++++++++++++++----- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/src/view/show_mode/editor/nodes/impl/faders.py b/src/view/show_mode/editor/nodes/impl/faders.py index b8b666a3..8bbdb586 100644 --- a/src/view/show_mode/editor/nodes/impl/faders.py +++ b/src/view/show_mode/editor/nodes/impl/faders.py @@ -1,4 +1,5 @@ -"""Column fader filter nodes""" +"""Column fader filter nodes.""" + from logging import getLogger from typing import TYPE_CHECKING @@ -39,11 +40,12 @@ def update_node_after_settings_changed(self) -> None: class FaderRawNode(_FaderNode): - """Filter to represent any filter fader""" + """Filter to represent any filter fader.""" nodeName = "Raw" # noqa: N815 def __init__(self, model: Filter, name: str) -> None: + """Initialize node.""" super().__init__(model=model, filter_type=FilterTypeEnumeration.VFILTER_FADER_RAW, name=name, terminals={ "primary": {"io": "out"}, "secondary": {"io": "out"}, @@ -54,10 +56,12 @@ def __init__(self, model: Filter, name: str) -> None: class FaderHSINode(_FaderNode): - """Filter to represent a hsi filter fader""" + """Filter to represent a hsi filter fader.""" + nodeName = "HSI" # noqa: N815 def __init__(self, model: Filter, name: str) -> None: + """Initialize node.""" super().__init__(model=model, filter_type=FilterTypeEnumeration.VFILTER_FADER_HSI, name=name, terminals={ "color": {"io": "out"}, }) @@ -72,10 +76,12 @@ def __init__(self, model: Filter, name: str) -> None: class FaderHSIANode(_FaderNode): - """Filter to represent a hsia filter fader""" + """Filter to represent a hsia filter fader.""" + nodeName = "HSI-A" # noqa: N815 def __init__(self, model: Filter, name: str) -> None: + """Initialize node.""" super().__init__(model=model, filter_type=FilterTypeEnumeration.VFILTER_FADER_HSIA, name=name, terminals={ "color": {"io": "out"}, "amber": {"io": "out"}, @@ -92,10 +98,12 @@ def __init__(self, model: Filter, name: str) -> None: class FaderHSIUNode(_FaderNode): - """Filter to represent a hsiu filter fader""" + """Filter to represent a hsiu filter fader.""" + nodeName = "HSI_U" # noqa: N815 def __init__(self, model: Filter, name: str) -> None: + """Initialize node.""" super().__init__(model=model, filter_type=FilterTypeEnumeration.VFILTER_FADER_HSIU, name=name, terminals={ "color": {"io": "out"}, "uv": {"io": "out"}, @@ -112,10 +120,12 @@ def __init__(self, model: Filter, name: str) -> None: class FaderHSIAUNode(_FaderNode): - """Filter to represent a hasiau filter fader""" + """Filter to represent a hasiau filter fader.""" + nodeName = "HSI-AU" # noqa: N815 def __init__(self, model: Filter, name: str) -> None: + """Initialize node.""" super().__init__(model=model, filter_type=FilterTypeEnumeration.VFILTER_FADER_HSIAU, name=name, terminals={ "color": {"io": "out"}, "amber": {"io": "out"}, @@ -133,10 +143,12 @@ def __init__(self, model: Filter, name: str) -> None: class FaderMainBrightness(FilterNode): - """Filter to the main brightness fader""" + """Filter to the main brightness fader.""" + nodeName = "global-ilumination" # noqa: N815 def __init__(self, model: Filter, name: str) -> None: + """Initialize node.""" super().__init__(model=model, filter_type=FilterTypeEnumeration.FILTER_TYPE_MAIN_BRIGHTNESS, name=name, terminals={"brightness": {"io": "out"}}) From ae894fcede40feedb96a2c04226f343a1f6282be Mon Sep 17 00:00:00 2001 From: Doralitze Date: Tue, 14 Oct 2025 15:34:18 +0200 Subject: [PATCH 6/6] fix: loading of show files with existing virtual fader filters --- .../file/deserialization/migrations.py | 9 ++++--- src/controller/file/read.py | 27 +++++++++++++------ src/model/broadcaster.py | 1 + src/model/virtual_filters/fader_vfilter.py | 7 +++++ src/model/virtual_filters/vfilter_factory.py | 2 +- src/view/main_window.py | 9 ++++++- 6 files changed, 42 insertions(+), 13 deletions(-) diff --git a/src/controller/file/deserialization/migrations.py b/src/controller/file/deserialization/migrations.py index c9681269..e1a92d9f 100644 --- a/src/controller/file/deserialization/migrations.py +++ b/src/controller/file/deserialization/migrations.py @@ -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 @@ -22,13 +22,15 @@ 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 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, @@ -39,4 +41,5 @@ def replace_old_filter_configurations(f: Filter) -> Filter: 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) - return f + migration_happened = True + return f, migration_happened diff --git a/src/controller/file/read.py b/src/controller/file/read.py index c27dda19..2b0b56da 100644 --- a/src/controller/file/read.py +++ b/src/controller/file/read.py @@ -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) @@ -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 @@ -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 @@ -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: @@ -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": @@ -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: @@ -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 @@ -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: diff --git a/src/model/broadcaster.py b/src/model/broadcaster.py index 648b2232..b0f697e1 100644 --- a/src/model/broadcaster.py +++ b/src/model/broadcaster.py @@ -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() diff --git a/src/model/virtual_filters/fader_vfilter.py b/src/model/virtual_filters/fader_vfilter.py index 18ed1b39..4e63e2fa 100644 --- a/src/model/virtual_filters/fader_vfilter.py +++ b/src/model/virtual_filters/fader_vfilter.py @@ -57,6 +57,8 @@ def instantiate_filters(self, filter_list: list[Filter]) -> None: 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: + return set_id = self.filter_configurations["set_id"] if self.scene.linked_bankset.id == set_id: @@ -81,6 +83,11 @@ 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: diff --git a/src/model/virtual_filters/vfilter_factory.py b/src/model/virtual_filters/vfilter_factory.py index a9c172ef..97346a3c 100644 --- a/src/model/virtual_filters/vfilter_factory.py +++ b/src/model/virtual_filters/vfilter_factory.py @@ -50,7 +50,7 @@ def construct_virtual_filter_instance( 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_type, filter_id, pos=pos) + 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 diff --git a/src/view/main_window.py b/src/view/main_window.py index aff68034..37aab983 100644 --- a/src/view/main_window.py +++ b/src/view/main_window.py @@ -8,7 +8,7 @@ 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 @@ -132,6 +132,7 @@ def __init__(self, parent: QWidget = None) -> None: 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: @@ -379,3 +380,9 @@ def _cleanup_wizard(self) -> None: 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()