Skip to content

Commit 45c022c

Browse files
committed
feat: persist window geometry and splitter positions via QSettings
1 parent 47566b0 commit 45c022c

2 files changed

Lines changed: 69 additions & 1 deletion

File tree

src/views/main_window.py

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
import os
1717
from typing import TypeVar, cast
1818

19-
from PySide6.QtCore import QDir, QFile, QModelIndex, Qt
19+
from PySide6.QtCore import QDir, QFile, QModelIndex, QSettings, Qt
2020
from PySide6.QtGui import QAction, QKeySequence, QShortcut
2121
from PySide6.QtUiTools import QUiLoader
2222
from PySide6.QtWidgets import (
@@ -30,6 +30,7 @@
3030
QMessageBox,
3131
QPlainTextEdit,
3232
QPushButton,
33+
QSplitter,
3334
QTabWidget,
3435
QTreeView,
3536
)
@@ -76,6 +77,10 @@ def __init__(self, viewmodel: MainViewModel) -> None:
7677
self._load_ui()
7778
self._setup_file_tree()
7879
self._connect_signals()
80+
self._load_settings()
81+
app = QApplication.instance()
82+
if app is not None:
83+
app.aboutToQuit.connect(self._save_settings)
7984

8085
def show(self) -> None:
8186
"""Show the main application window."""
@@ -181,6 +186,13 @@ def _load_ui(self) -> None:
181186
self._cursor_label = QLabel("Ln 1, Col 1 | 0 chars")
182187
self.ui.statusBar().addPermanentWidget(self._cursor_label)
183188

189+
self._main_splitter = _require(
190+
self.ui.findChild(QSplitter, "mainSplitter"), "mainSplitter"
191+
)
192+
self._left_splitter = _require(
193+
self.ui.findChild(QSplitter, "leftPanelSplitter"), "leftPanelSplitter"
194+
)
195+
184196
def _setup_file_tree(self) -> None:
185197
"""Configure QFileSystemModel rooted at the user's home directory."""
186198
self._fs_model = QFileSystemModel(self.ui)
@@ -411,6 +423,29 @@ def _update_cursor_label(self) -> None:
411423
chars = self._plain_text_edit.document().characterCount() - 1
412424
self._cursor_label.setText(f"Ln {line}, Col {col} | {chars:,} chars")
413425

426+
def _load_settings(self) -> None:
427+
"""Restore window geometry and splitter positions from QSettings.
428+
429+
Silent no-op when no settings exist yet (first launch or cleared).
430+
"""
431+
settings = QSettings("TextTools", "TextTools")
432+
if geometry := settings.value("window/geometry"):
433+
self.ui.restoreGeometry(geometry)
434+
if main_state := settings.value("splitter/main"):
435+
self._main_splitter.restoreState(main_state)
436+
if left_state := settings.value("splitter/left"):
437+
self._left_splitter.restoreState(left_state)
438+
439+
def _save_settings(self) -> None:
440+
"""Save window geometry and splitter positions to QSettings.
441+
442+
Connected to QApplication.aboutToQuit in __init__.
443+
"""
444+
settings = QSettings("TextTools", "TextTools")
445+
settings.setValue("window/geometry", self.ui.saveGeometry())
446+
settings.setValue("splitter/main", self._main_splitter.saveState())
447+
settings.setValue("splitter/left", self._left_splitter.saveState())
448+
414449
# ------------------------------------------ ViewModel signal handlers
415450

416451
def _on_document_loaded(self, content: str) -> None:

tests/integration/test_main_window.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,3 +493,36 @@ def test_cancelled_dialog_is_no_op(self, window, monkeypatch):
493493
window._viewmodel.file_saved.connect(emitted.append)
494494
window._on_action_save_as()
495495
assert emitted == []
496+
497+
498+
class TestConfigPersistence:
499+
def test_save_settings_writes_geometry(self, window, qtbot):
500+
"""_save_settings must write window/geometry to QSettings."""
501+
from PySide6.QtCore import QSettings
502+
s = QSettings("TextTools", "TextTools")
503+
s.remove("window/geometry")
504+
window.ui.show()
505+
window._save_settings()
506+
s2 = QSettings("TextTools", "TextTools")
507+
assert s2.value("window/geometry") is not None
508+
509+
def test_load_settings_does_not_raise_when_empty(self, window):
510+
"""_load_settings must not raise when no settings have been saved."""
511+
from PySide6.QtCore import QSettings
512+
QSettings("TextTools", "TextTools").clear()
513+
window._load_settings() # must not raise
514+
515+
def test_save_and_restore_geometry(self, window, qtbot):
516+
"""Geometry saved by _save_settings is restored by _load_settings."""
517+
from PySide6.QtCore import QSettings
518+
window.ui.show()
519+
window.ui.resize(700, 600)
520+
qtbot.wait(10)
521+
window._save_settings()
522+
window.ui.resize(300, 300)
523+
qtbot.wait(10)
524+
window._load_settings()
525+
qtbot.wait(10)
526+
size = window.ui.size()
527+
assert size.width() == 700
528+
assert size.height() == 600

0 commit comments

Comments
 (0)