from __future__ import annotations from pathlib import Path from typing import Protocol from PySide6.QtCore import QEvent, QObject from bedit_gui.documents import Document from bedit_gui.services.application_logging import get_logger from bedit_gui.views.dialogs.document_dialogs import ( DocumentDialogs, SaveChangesChoice, ) from bedit_gui.views.main_window import MainWindow logger = get_logger(__name__) class DocumentDialogProvider(Protocol): def choose_open_path(self, current_path: Path | None) -> Path | None: ... def choose_save_path(self, current_path: Path | None) -> Path | None: ... def ask_save_changes(self) -> SaveChangesChoice: ... def show_file_error(self, title: str, error: Exception) -> None: ... class DocumentController(QObject): """Coordinates the single-document workflow with the main window.""" def __init__( self, document: Document, window: MainWindow, dialogs: DocumentDialogProvider | None = None, ) -> None: super().__init__(window) self.document = document self.window = window self.dialogs = dialogs or DocumentDialogs(window) window.ui.actionNew_File.triggered.connect(self.new_document) window.ui.actionOpen_File.triggered.connect(self.open_document) window.ui.actionSave_File.triggered.connect(self.save_document) window.ui.actionSave_File_As.triggered.connect(self.save_document_as) window.ui.actionClose.triggered.connect(window.close) document.path_changed.connect(self.update_window_title) document.modified_changed.connect(self.update_window_title) window.installEventFilter(self) self.update_window_title() def new_document(self) -> None: if self.maybe_save_changes(): self.document.new() logger.info("Created new document") def open_document(self) -> None: if not self.maybe_save_changes(): return path = self.dialogs.choose_open_path(self.document.path) if path is None: return try: self.document.open(path) except (OSError, TypeError, ValueError) as exc: logger.exception("Could not open document %s", path) self.dialogs.show_file_error("Could not open document", exc) else: logger.info("Opened document: %s", path) def save_document(self) -> bool: if self.document.path is None: return self.save_document_as() try: self.document.save() except (OSError, TypeError, ValueError) as exc: logger.exception("Could not save document %s", self.document.path) self.dialogs.show_file_error("Could not save document", exc) return False logger.info("Saved document: %s", self.document.path) return True def save_document_as(self) -> bool: path = self.dialogs.choose_save_path(self.document.path) if path is None: return False try: self.document.save_as(path) except (OSError, TypeError, ValueError) as exc: logger.exception("Could not save document as %s", path) self.dialogs.show_file_error("Could not save document", exc) return False logger.info("Saved document as: %s", path) return True def maybe_save_changes(self) -> bool: if not self.document.modified: return True choice = self.dialogs.ask_save_changes() if choice is SaveChangesChoice.SAVE: return self.save_document() return choice is SaveChangesChoice.DISCARD def update_window_title(self, *_args: object) -> None: name = self.document.path.name if self.document.path else "Untitled" marker = "*" if self.document.modified else "" self.window.setWindowTitle(f"{name}{marker} — BEdit") def eventFilter(self, watched: QObject, event: QEvent) -> bool: window = getattr(self, "window", None) if ( watched is window and event.type() == QEvent.Type.Close and not self.maybe_save_changes() ): event.ignore() return True return super().eventFilter(watched, event)