Source code for radioviz.services.action_descriptor

#  Copyright 2025–2026 European Union
#  Author: Bulgheroni Antonio (antonio.bulgheroni@ec.europa.eu)
#  SPDX-License-Identifier: EUPL-1.2
"""
Action descriptor module for Qt-based applications.

This module provides an :class:`ActionDescriptor` that creates descriptors for
action states with automatic signal emission. It allows defining boolean properties
that automatically emit Qt signals when their values change, making it easy to
integrate with Qt's signal-slot mechanism for UI updates.

"""

from __future__ import annotations

from typing import Optional


[docs] class ActionDescriptor: """ Descriptor for action state with automatic signal emission. This descriptor creates a property that automatically emits a Qt signal when its value changes. It's designed to work with Qt's signal-slot mechanism to enable dynamic UI updates based on action availability. :ivar action_name: The name of the action this descriptor manages :ivar _signal_name: The name of the signal to emit when the action state changes :ivar private_name: The internal private attribute name used to store the value :ivar descriptor_name: The name of the descriptor as assigned in the class """ def __init__(self, action_name: Optional[str] = None, signal_name: Optional[str] = None): """ Initialize the ActionDescriptor. :param action_name: The name of the action (if None, derived from descriptor name) :type action_name: str or None :param signal_name: The name of the signal to emit (if None, derived from action name) :type signal_name: str or None """ self.action_name: Optional[str] = action_name self._signal_name: Optional[str] = signal_name self.private_name: Optional[str] = None self.descriptor_name: Optional[str] = None def __set_name__(self, owner: type, name: str) -> None: """ Set the name of the descriptor when it's assigned to a class attribute. This method is automatically called by Python when the descriptor is assigned to a class attribute. It sets up the internal naming conventions and derives action names and signal names if not explicitly provided. :param owner: The class that owns this descriptor :type owner: type :param name: The name of the attribute this descriptor is assigned to :type name: str """ # This is called when the descriptor is assigned to a class attribute self.descriptor_name = name # Derive action_name from descriptor name if not provided if self.action_name is None: # If descriptor is named "can_action1", action_name becomes "action1" if name.startswith('can_'): self.action_name = name[4:] else: self.action_name = name if self.action_name is None: raise AttributeError('ActionDescriptor must have a valid action name.') self.private_name = f'_can_{self.action_name}' # Use provided signal_name or derive it if self._signal_name is None: self._signal_name = f'{self.action_name}_enabled_changed' @property def signal_name(self) -> str: """ Get the signal name associated with this action. :return: The name of the signal to emit :rtype: str """ if self._signal_name is None: raise AttributeError('ActionDescriptor must have a valid signal name.') return self._signal_name def __get__(self, obj: object | None, objtype: type | None = None) -> 'bool | ActionDescriptor': """ Get the value of the action state. This method is called when accessing the descriptor as a property. :param obj: The instance of the class this descriptor belongs to :type obj: QObject or None :param objtype: The type of the object (unused) :type objtype: type or None :return: The current value of the action state :rtype: bool """ if obj is None: return self if self.private_name is None: raise AttributeError('ActionDescriptor is not initialized.') return getattr(obj, self.private_name, False) def __set__(self, obj: object, value: bool) -> None: """ Set the value of the action state and emit signal if changed. This method is called when setting the descriptor as a property. It compares the new value with the current value and emits the corresponding signal if they differ. :param obj: The instance of the class this descriptor belongs to :type obj: QObject :param value: The new value for the action state :type value: bool :raise TypeError: if value is not a boolean """ if self.private_name is None: raise AttributeError('ActionDescriptor is not initialized.') old_value = getattr(obj, self.private_name, False) if value != old_value: setattr(obj, self.private_name, value) signal = getattr(obj, self.signal_name) signal.emit(value)