Document handling
New/Open/Save/Save as and Undo/Redo
This commit is contained in:
113
src/bedit_gui/controllers/document_controller.py
Normal file
113
src/bedit_gui/controllers/document_controller.py
Normal file
@@ -0,0 +1,113 @@
|
||||
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.views.dialogs.document_dialogs import (
|
||||
DocumentDialogs,
|
||||
SaveChangesChoice,
|
||||
)
|
||||
from bedit_gui.views.main_window import MainWindow
|
||||
|
||||
|
||||
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()
|
||||
|
||||
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:
|
||||
self.dialogs.show_file_error("Could not open document", exc)
|
||||
|
||||
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:
|
||||
self.dialogs.show_file_error("Could not save document", exc)
|
||||
return False
|
||||
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:
|
||||
self.dialogs.show_file_error("Could not save document", exc)
|
||||
return False
|
||||
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)
|
||||
36
src/bedit_gui/controllers/undo_controller.py
Normal file
36
src/bedit_gui/controllers/undo_controller.py
Normal file
@@ -0,0 +1,36 @@
|
||||
from PySide6.QtCore import QObject
|
||||
|
||||
from bedit_gui.documents import Document
|
||||
from bedit_gui.views.main_window import MainWindow
|
||||
|
||||
|
||||
class UndoController(QObject):
|
||||
def __init__(self, document: Document, window: MainWindow) -> None:
|
||||
super().__init__(window)
|
||||
|
||||
self.document = document
|
||||
self.window = window
|
||||
|
||||
undo_action = window.ui.actionUndo
|
||||
redo_action = window.ui.actionRedo
|
||||
undo_stack = document.undo_stack
|
||||
|
||||
undo_action.triggered.connect(undo_stack.undo)
|
||||
redo_action.triggered.connect(undo_stack.redo)
|
||||
|
||||
undo_stack.canUndoChanged.connect(undo_action.setEnabled)
|
||||
undo_stack.canRedoChanged.connect(redo_action.setEnabled)
|
||||
|
||||
undo_stack.undoTextChanged.connect(self._update_undo_text)
|
||||
undo_stack.redoTextChanged.connect(self._update_redo_text)
|
||||
|
||||
undo_action.setEnabled(undo_stack.canUndo())
|
||||
redo_action.setEnabled(undo_stack.canRedo())
|
||||
|
||||
def _update_undo_text(self, command: str) -> None:
|
||||
text = f"Undo {command}" if command else "Undo"
|
||||
self.window.ui.actionUndo.setText(text)
|
||||
|
||||
def _update_redo_text(self, command: str) -> None:
|
||||
text = f"Redo {command}" if command else "Redo"
|
||||
self.window.ui.actionRedo.setText(text)
|
||||
Reference in New Issue
Block a user