Source code for simvx.editor.rename_class_dialog

"""Modal dialog for renaming a user-defined class project-wide.

Wraps :func:`simvx.editor.project_classes.rename_class` with a small
text input + "Also rename file" checkbox. Visible only when the
selected node's class is user-defined (lives outside ``simvx.core``);
the entry points (scene-tree right-click + Inspector toolbar button)
gate visibility before the dialog is opened.
"""

from __future__ import annotations

import logging
from typing import TYPE_CHECKING

from simvx.core import (
    Button,
    CheckBox,
    HBoxContainer,
    Label,
    Node,
    Panel,
    TextEdit,
    VBoxContainer,
    Vec2,
)

from .project_classes import RenameResult, rename_class

if TYPE_CHECKING:
    from .project_classes import ProjectClassIndex

log = logging.getLogger(__name__)


[docs] class RenameClassDialog(Panel): """Project-wide rename for a user-defined class.""" def __init__(self, **kwargs): super().__init__(**kwargs) self.size = Vec2(420, 180) self.visible = False body = VBoxContainer(name="Body") body.size = Vec2(400, 160) body.position = Vec2(10, 10) body.gap = 8 self.add_child(body) self._heading = Label(text="Rename class", name="Heading") self._heading.font_size = 14 body.add_child(self._heading) self._old_name_label = Label(text="", name="OldName") body.add_child(self._old_name_label) self._name_edit = TextEdit(name="NewName") self._name_edit.size = Vec2(380, 28) body.add_child(self._name_edit) self._also_rename_file = CheckBox(text="Also rename file to match", name="RenameFile") body.add_child(self._also_rename_file) self._error_label = Label(text="", name="Error") self._error_label.colour = (0.85, 0.30, 0.30, 1.0) body.add_child(self._error_label) actions = HBoxContainer(name="Actions") actions.gap = 8 body.add_child(actions) self._cancel_btn = Button(text="Cancel", name="Cancel") self._cancel_btn.size = Vec2(80, 28) self._cancel_btn.pressed.connect(self.dismiss) actions.add_child(self._cancel_btn) self._submit_btn = Button(text="Rename", name="Submit") self._submit_btn.size = Vec2(100, 28) self._submit_btn.pressed.connect(self._on_submit) actions.add_child(self._submit_btn) # Per-call wiring populated by show_for. self._project_index: ProjectClassIndex | None = None self._old_name: str = "" self._on_renamed: list = [] # ------------------------------------------------------------------ public
[docs] def show_for( self, node: Node, *, project_index: ProjectClassIndex, on_renamed=None, ) -> None: """Open for ``node``'s class. ``on_renamed`` fires after a successful rename with the :class:`RenameResult`.""" cls_name = type(node).__name__ self._old_name = cls_name self._project_index = project_index self._on_renamed = [on_renamed] if on_renamed is not None else [] self._old_name_label.text = f"Renaming class {cls_name!r}" self._name_edit.text = cls_name self._also_rename_file.checked = False self._error_label.text = "" self.visible = True # Position centred-ish if a parent gives us screen size info; otherwise # leave layout to the caller. if self._tree: self._tree.push_popup(self)
[docs] def dismiss(self) -> None: if not self.visible: return self.visible = False if self._tree: self._tree.pop_popup(self)
# ------------------------------------------------------------------ internals def _on_submit(self) -> None: new_name = self._name_edit.text.strip() if not new_name: self._error_label.text = "Class name cannot be empty" return if not new_name.isidentifier(): self._error_label.text = "Not a valid Python identifier" return if new_name == self._old_name: self.dismiss() return if self._project_index is None: self._error_label.text = "No project context" return try: result: RenameResult = rename_class( self._project_index, self._old_name, new_name, rename_file=bool(self._also_rename_file.checked), ) except ValueError as e: self._error_label.text = str(e) return for cb in self._on_renamed: try: cb(result) except Exception: log.exception("rename_class on_renamed callback failed") self.dismiss()