From 160de4c5267f2a54d0eef5ed6a2a07497481e84a Mon Sep 17 00:00:00 2001 From: Andrew Mulbrook Date: Mon, 23 Mar 2026 22:56:53 -0500 Subject: [PATCH] Simple text editor for basic notes --- trovedb/qgui/main_window.py | 17 +++++++++------- trovedb/qgui/note_browser.py | 20 +++++++++++-------- trovedb/qgui/note_tool_stack.py | 32 +++++++++++++++++++++++++++++++ trovedb/qgui/tool.py | 14 ++++++++++++++ trovedb/qgui/tool_basic_editor.py | 28 +++++++++++++++++++++++++++ 5 files changed, 96 insertions(+), 15 deletions(-) create mode 100644 trovedb/qgui/note_tool_stack.py create mode 100644 trovedb/qgui/tool.py create mode 100644 trovedb/qgui/tool_basic_editor.py diff --git a/trovedb/qgui/main_window.py b/trovedb/qgui/main_window.py index daa611a..d4cdde4 100644 --- a/trovedb/qgui/main_window.py +++ b/trovedb/qgui/main_window.py @@ -1,7 +1,6 @@ from PySide6.QtCore import Qt from PySide6.QtGui import QAction, QKeySequence from PySide6.QtWidgets import ( - QLabel, QMainWindow, QSplitter, QStatusBar, @@ -13,7 +12,9 @@ from PySide6.QtWidgets import ( from .settings import get_settings from trovedb import trove as tr + from .note_browser import NoteBrowser +from .note_tool_stack import NoteToolStack class TroveMainWindow(QMainWindow): def __init__(self, trove: tr.Trove): @@ -44,15 +45,17 @@ class TroveMainWindow(QMainWindow): layout.setSpacing(0) # Horizontal splitter: tree | editor - splitter = QSplitter(Qt.Orientation.Horizontal) + self._splitter = QSplitter(Qt.Orientation.Horizontal) self._note_browser = NoteBrowser(trove) - splitter.addWidget(self._note_browser) + self._splitter.addWidget(self._note_browser) - self._tool = QLabel("View/Edit Tool") - splitter.addWidget(self._tool) + self._tool_stack = NoteToolStack() + self._splitter.addWidget(self._tool_stack) - layout.addWidget(splitter, stretch=1) + layout.addWidget(self._splitter, stretch=1) + + self._note_browser.activeNoteChanged.connect(self._tool_stack.onNoteSelected) # ── Status bar ── self.setStatusBar(QStatusBar()) @@ -71,4 +74,4 @@ class TroveMainWindow(QMainWindow): if geometry: self.restoreGeometry(geometry) if state: - self.restoreState(state) \ No newline at end of file + self.restoreState(state) diff --git a/trovedb/qgui/note_browser.py b/trovedb/qgui/note_browser.py index 6be5177..ec5f178 100644 --- a/trovedb/qgui/note_browser.py +++ b/trovedb/qgui/note_browser.py @@ -21,8 +21,7 @@ class NoteBrowser(QWidget): tree_selected(TreeNote) -- emitted when a tree note is activated """ - note_selected = Signal(object) # Note - tree_selected = Signal(object) # TreeNote + activeNoteChanged = Signal(object) # Note def __init__(self, trove: Trove, parent: Optional[QWidget] = None): super().__init__(parent) @@ -38,6 +37,8 @@ class NoteBrowser(QWidget): layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(2) + self._activeNote: Optional[Note] = None + self._tree = QTreeView() self._tree.setModel(self._model) self._tree.setHeaderHidden(True) @@ -67,12 +68,7 @@ class NoteBrowser(QWidget): def _on_activated(self, index: QModelIndex) -> None: note = index.data(Qt.ItemDataRole.UserRole) - if note is None: - return - if isinstance(note, TreeNote): - self.tree_selected.emit(note) - else: - self.note_selected.emit(note) + self._handle_set_active_note(note) def _on_context_menu(self, pos) -> None: index = self._tree.indexAt(pos) @@ -105,6 +101,14 @@ class NoteBrowser(QWidget): # Public API # ------------------------------------------------------------------ + def _handle_set_active_note(self, note: Note | None) -> None: + """Called by the controller to update the active note.""" + if not isinstance(note, Note): + note = None + if self._activeNote != note: + self._activeNote = note + self.activeNoteChanged.emit(note) + def select_note(self, note: Note) -> None: """Programmatically select the node matching the given note object.""" match = self._find_index(self._model.index(0, 0, QModelIndex()), note) diff --git a/trovedb/qgui/note_tool_stack.py b/trovedb/qgui/note_tool_stack.py new file mode 100644 index 0000000..d5cb2c5 --- /dev/null +++ b/trovedb/qgui/note_tool_stack.py @@ -0,0 +1,32 @@ +from typing import Generator + +from PySide6.QtCore import Property, Slot +from PySide6.QtWidgets import QStackedWidget, QWidget, QLabel + +import trovedb.trove as tr + +from .tool import Tool +from .tool_basic_editor import ToolBasicEditor + + +class NoteToolStack(QStackedWidget): + def __init__(self, parent: QWidget | None = None): + super().__init__(parent) + self.setContentsMargins(0, 0, 0, 0) + self.addWidget(QLabel("No note selected")) + + @Slot(object) + def onNoteSelected(self, note: tr.Note): + for tool in self._iter_tools(): + if tool.note == note: + self.setCurrentWidget(tool) + return + tool = ToolBasicEditor(note) + self.addWidget(tool) + self.setCurrentWidget(tool) + + def _iter_tools(self) -> Generator[Tool, None, None]: + for i in range(self.count()): + widget = self.widget(i) + if isinstance(widget, Tool): + yield widget diff --git a/trovedb/qgui/tool.py b/trovedb/qgui/tool.py new file mode 100644 index 0000000..53f2829 --- /dev/null +++ b/trovedb/qgui/tool.py @@ -0,0 +1,14 @@ +"""Tool Base Class""" + +from PySide6.QtWidgets import QWidget + +import trovedb.trove as tr + +class Tool(QWidget): + def __init__(self, note: tr.Note, parent=None): + super().__init__(parent) + self._note = note + + @property + def note(self) -> tr.Note: + return self._note diff --git a/trovedb/qgui/tool_basic_editor.py b/trovedb/qgui/tool_basic_editor.py new file mode 100644 index 0000000..530b88c --- /dev/null +++ b/trovedb/qgui/tool_basic_editor.py @@ -0,0 +1,28 @@ +"""Tool Supporting Basic Editor Functions""" +from typing import cast, Protocol +from PySide6.QtWidgets import QTextEdit, QVBoxLayout + +import trovedb.trove as tr +from .tool import Tool + + +class ToolBasicEditor(Tool): + def __init__(self, note, parent=None): + super().__init__(note, parent) + + layout = QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(0) + + self._text_edit = QTextEdit() + layout.addWidget(self._text_edit) + self.refresh() + + def _refresh_blob(self, note: tr.Blob): + self._text_edit.setPlainText(note.read().decode("utf-8")) + + def refresh(self): + if isinstance(self.note, tr.Blob): + self._refresh_blob(cast(tr.Blob, self.note)) + +