betty.gui.model - Betty documentation

Hide navigation sidebar
Hide table of contents sidebar
Skip to content
Back to top
Toggle table of contents sidebar

# Source code for betty.gui.model

    """
    Provide entity management widgets for the Graphical User Interface.
    """

    from __future__ import annotations

    from typing import Callable, cast, Iterator, TYPE_CHECKING

    from PyQt6.QtWidgets import (
        QFormLayout,
        QLabel,
        QComboBox,
        QLineEdit,
        QWidget,
        QPushButton,
        QVBoxLayout,
        QHBoxLayout,
    )
    from typing_extensions import override

    from betty.gui.locale import LocalizedObject
    from betty.model import UserFacingEntity, Entity
    from betty.project import EntityReference, EntityReferenceSequence

    if TYPE_CHECKING:
        from betty.locale import Localizable
        from betty.app import App

    class EntityReferenceCollector(LocalizedObject, QWidget):
        """
        A form widget that allows users to configure an entity reference.
        """

        def __init__(
            self,
            app: App,
            entity_reference: EntityReference[UserFacingEntity & Entity],
            label_builder: Callable[[], str] | None = None,
            caption_builder: Callable[[], str] | None = None,
        ):
            super().__init__(app)
            self._entity_reference = entity_reference
            self._label_builder = label_builder
            self._caption_builder = caption_builder

            self._layout = QFormLayout()
            self.setLayout(self._layout)

            if self._entity_reference.entity_type_is_constrained:
                self._entity_type_label = QLabel()
            else:

                def _update_entity_type() -> None:
                    self._entity_reference.entity_type = self._entity_type.currentData()

                self._entity_type = QComboBox()
                self._entity_type.currentIndexChanged.connect(_update_entity_type)
                entity_types = enumerate(
                    sorted(
                        cast(
                            Iterator[type["UserFacingEntity & Entity"]],
                            filter(
                                lambda entity_type: issubclass(
                                    entity_type, UserFacingEntity
                                ),
                                self._app.entity_types,
                            ),
                        ),
                        key=lambda entity_type: entity_type.entity_type_label().localize(
                            self._app.localizer
                        ),
                    )
                )
                for i, entity_type in entity_types:
                    self._entity_type.addItem(
                        entity_type.entity_type_label().localize(self._app.localizer),
                        entity_type,
                    )
                    if entity_type == self._entity_reference.entity_type:
                        self._entity_type.setCurrentIndex(i)
                self._entity_type_label = QLabel()
                self._layout.addRow(self._entity_type_label, self._entity_type)

            def _update_entity_id() -> None:
                self._entity_reference.entity_id = self._entity_id.text()

            self._entity_id = QLineEdit()
            self._entity_id.textChanged.connect(_update_entity_id)
            self._entity_id_label = QLabel()
            self._layout.addRow(self._entity_id_label, self._entity_id)

            self._set_translatables()

        @override
        def _set_translatables(self) -> None:
            super()._set_translatables()
            if self._entity_reference.entity_type:
                self._entity_id_label.setText(
                    self._app.localizer._("{entity_type_label} ID").format(
                        entity_type_label=self._entity_reference.entity_type.entity_type_label().localize(
                            self._app.localizer
                        ),
                    )
                )
            else:
                self._entity_id_label.setText(self._app.localizer._("Entity ID"))

    class EntityReferenceSequenceCollector(LocalizedObject, QWidget):
        """
        A form widget that allows users to configure a sequence of entity references.
        """

        def __init__(
            self,
            app: App,
            entity_references: EntityReferenceSequence[UserFacingEntity & Entity],
            label_text: Localizable | None = None,
            caption_text: Localizable | None = None,
        ):
            super().__init__(app)
            self._entity_references = entity_references
            self._label_text = label_text
            self._caption_text = caption_text
            self._entity_reference_collectors: list[EntityReferenceCollector] = [
                EntityReferenceCollector(self._app, entity_reference)
                for entity_reference in entity_references
            ]

            self._layout = QVBoxLayout()
            self.setLayout(self._layout)

            if label_text:
                self._label = QLabel()
                self._layout.addWidget(self._label)

            self._entity_reference_collectors_widget = QWidget()
            self._entity_reference_collectors_layout = QVBoxLayout()
            self._entity_reference_collectors_widget.setLayout(
                self._entity_reference_collectors_layout
            )
            self._layout.addWidget(self._entity_reference_collectors_widget)

            self._entity_reference_collection_widgets: list[QWidget] = []
            self._entity_reference_remove_buttons: list[QPushButton] = []

            if caption_text:
                self._caption = QLabel()
                self._layout.addWidget(self._caption)

            self._add_entity_reference_button = QPushButton()
            self._add_entity_reference_button.released.connect(self._add_entity_reference)
            self._layout.addWidget(self._add_entity_reference_button)

            self._build_entity_references_collection()

            self._set_translatables()

        def _add_entity_reference(self) -> None:
            entity_reference = EntityReference["UserFacingEntity & Entity"]()
            self._entity_references.append(entity_reference)
            self._build_entity_reference_collection(
                len(self._entity_references) - 1, entity_reference
            )
            self._set_translatables()

        def _remove_entity_reference(self, i: int) -> None:
            del self._entity_references[i]
            del self._entity_reference_collectors[i]
            del self._entity_reference_remove_buttons[i]
            self._entity_reference_collectors_layout.removeWidget(
                self._entity_reference_collection_widgets[i]
            )
            del self._entity_reference_collection_widgets[i]

        def _build_entity_references_collection(self) -> None:
            for i, entity_reference in enumerate(self._entity_references):
                self._build_entity_reference_collection(i, entity_reference)

        def _build_entity_reference_collection(
            self,
            i: int,
            entity_reference: EntityReference[UserFacingEntity & Entity],
        ) -> None:
            widget = QWidget()
            layout = QHBoxLayout()
            widget.setLayout(layout)
            self._entity_reference_collection_widgets.insert(i, widget)
            self._entity_reference_collectors_layout.insertWidget(i, widget)

            entity_reference_collector = EntityReferenceCollector(
                self._app, entity_reference
            )
            self._entity_reference_collectors.append(entity_reference_collector)
            layout.addWidget(entity_reference_collector)

            entity_reference_remove_button = QPushButton()
            self._entity_reference_remove_buttons.insert(i, entity_reference_remove_button)
            entity_reference_remove_button.released.connect(
                lambda: self._remove_entity_reference(i)
            )
            layout.addWidget(entity_reference_remove_button)

        @override
        def _set_translatables(self) -> None:
            super()._set_translatables()
            if self._label_text:
                self._label.setText(self._label_text.localize(self._app.localizer))
            if self._caption_text:
                self._caption.setText(self._caption_text.localize(self._app.localizer))
            self._add_entity_reference_button.setText(
                self._app.localizer._("Add an entity")
            )
            for entity_reference_remove_button in self._entity_reference_remove_buttons:
                entity_reference_remove_button.setText(self._app.localizer._("Remove"))