DRY up some code

This commit is contained in:
Miguel Jacq 2025-11-10 10:25:46 +11:00
parent 77eec9cc84
commit eac37d8843
Signed by: mig5
GPG key ID: 59B3F0C24135C6A9
5 changed files with 52 additions and 88 deletions

View file

@ -1,3 +1,8 @@
# 0.2.1.4
* Increase font size of normal text
* DRY up some code
# 0.2.1.3 # 0.2.1.3
* Ensure checkbox only can get checked on/off if it is clicked right on its block position, not any click on the whole line * Ensure checkbox only can get checked on/off if it is clicked right on its block position, not any click on the whole line

View file

@ -109,14 +109,6 @@ class HistoryDialog(QDialog):
self._load_versions() self._load_versions()
# --- Data/UX helpers --- # --- Data/UX helpers ---
def _fmt_local(self, iso_utc: str) -> str:
"""
Convert UTC in the database to user's local tz
"""
dt = datetime.fromisoformat(iso_utc.replace("Z", "+00:00"))
local = dt.astimezone()
return local.strftime("%Y-%m-%d %H:%M:%S %Z")
def _load_versions(self): def _load_versions(self):
# [{id,version_no,created_at,note,is_current}] # [{id,version_no,created_at,note,is_current}]
self._versions = self._db.list_versions(self._date) self._versions = self._db.list_versions(self._date)
@ -126,7 +118,11 @@ class HistoryDialog(QDialog):
) )
self.list.clear() self.list.clear()
for v in self._versions: for v in self._versions:
label = f"v{v['version_no']}{self._fmt_local(v['created_at'])}" created_at = datetime.fromisoformat(
v["created_at"].replace("Z", "+00:00")
).astimezone()
created_at_local = created_at.strftime("%Y-%m-%d %H:%M:%S %Z")
label = f"v{v['version_no']}{created_at_local}"
if v.get("note"): if v.get("note"):
label += f" · {v['note']}" label += f" · {v['note']}"
if v["is_current"]: if v["is_current"]:

View file

@ -169,9 +169,7 @@ class MainWindow(QMainWindow):
self.statusBar().showMessage("Ready", 800) self.statusBar().showMessage("Ready", 800)
# Add findBar and add it to the statusBar # Add findBar and add it to the statusBar
# FindBar will get the current editor dynamically via a callable # FindBar will get the current editor dynamically via a callable
self.findBar = FindBar( self.findBar = FindBar(lambda: self.editor, shortcut_parent=self, parent=self)
lambda: self.current_editor(), shortcut_parent=self, parent=self
)
self.statusBar().addPermanentWidget(self.findBar) self.statusBar().addPermanentWidget(self.findBar)
# When the findBar closes, put the caret back in the editor # When the findBar closes, put the caret back in the editor
self.findBar.closed.connect(self._focus_editor_now) self.findBar.closed.connect(self._focus_editor_now)
@ -431,7 +429,7 @@ class MainWindow(QMainWindow):
self.tab_widget.setCurrentIndex(index) self.tab_widget.setCurrentIndex(index)
# Load the date's content # Load the date's content
self._load_date_into_editor(date, editor) self._load_date_into_editor(date)
# Store the date with the editor so we can save it later # Store the date with the editor so we can save it later
editor.current_date = date editor.current_date = date
@ -478,19 +476,12 @@ class MainWindow(QMainWindow):
""" """
Call the relevant method of the MarkdownEditor class on bind Call the relevant method of the MarkdownEditor class on bind
""" """
ed = self.current_editor() getattr(self.editor, method_name)(*args)
if ed is None:
return
getattr(ed, method_name)(*args)
def current_editor(self) -> MarkdownEditor | None:
"""Get the currently active editor."""
return self.tab_widget.currentWidget()
@property @property
def editor(self) -> MarkdownEditor | None: def editor(self) -> MarkdownEditor | None:
"""Compatibility property to get current editor (for existing code).""" """Get the currently active editor."""
return self.current_editor() return self.tab_widget.currentWidget()
def _date_from_calendar_pos(self, pos) -> QDate | None: def _date_from_calendar_pos(self, pos) -> QDate | None:
"""Translate a QCalendarWidget local pos to the QDate under the cursor.""" """Translate a QCalendarWidget local pos to the QDate under the cursor."""
@ -801,16 +792,12 @@ class MainWindow(QMainWindow):
def _load_selected_date(self, date_iso=False, extra_data=False): def _load_selected_date(self, date_iso=False, extra_data=False):
"""Load a date into the current editor""" """Load a date into the current editor"""
editor = self.current_editor()
if not editor:
return
if not date_iso: if not date_iso:
date_iso = self._current_date_iso() date_iso = self._current_date_iso()
qd = QDate.fromString(date_iso, "yyyy-MM-dd") qd = QDate.fromString(date_iso, "yyyy-MM-dd")
self._load_date_into_editor(qd, editor, extra_data) self._load_date_into_editor(qd, extra_data)
editor.current_date = qd self.editor.current_date = qd
# Update tab title # Update tab title
current_index = self.tab_widget.currentIndex() current_index = self.tab_widget.currentIndex()
@ -820,9 +807,7 @@ class MainWindow(QMainWindow):
# Keep tabs sorted by date # Keep tabs sorted by date
self._reorder_tabs_by_date() self._reorder_tabs_by_date()
def _load_date_into_editor( def _load_date_into_editor(self, date: QDate, extra_data=False):
self, date: QDate, editor: MarkdownEditor, extra_data=False
):
"""Load a specific date's content into a given editor.""" """Load a specific date's content into a given editor."""
date_iso = date.toString("yyyy-MM-dd") date_iso = date.toString("yyyy-MM-dd")
try: try:
@ -833,14 +818,14 @@ class MainWindow(QMainWindow):
text += "\n" text += "\n"
text += extra_data text += extra_data
# Force a save now so we don't lose it. # Force a save now so we don't lose it.
self._set_editor_markdown_preserve_view(text, editor) self._set_editor_markdown_preserve_view(text)
self._dirty = True self._dirty = True
self._save_date(date_iso, True) self._save_date(date_iso, True)
except Exception as e: except Exception as e:
QMessageBox.critical(self, "Read Error", str(e)) QMessageBox.critical(self, "Read Error", str(e))
return return
self._set_editor_markdown_preserve_view(text, editor) self._set_editor_markdown_preserve_view(text)
self._dirty = False self._dirty = False
def _save_editor_content(self, editor: MarkdownEditor): def _save_editor_content(self, editor: MarkdownEditor):
@ -921,10 +906,6 @@ class MainWindow(QMainWindow):
if getattr(self, "_showing_context_menu", False): if getattr(self, "_showing_context_menu", False):
return return
editor = self.current_editor()
if not editor:
return
# Stop pending autosave and persist current buffer if needed # Stop pending autosave and persist current buffer if needed
try: try:
self._save_timer.stop() self._save_timer.stop()
@ -932,14 +913,14 @@ class MainWindow(QMainWindow):
pass pass
# Save the current editor's content if dirty # Save the current editor's content if dirty
if hasattr(editor, "current_date") and self._dirty: if hasattr(self.editor, "current_date") and self._dirty:
prev_date_iso = editor.current_date.toString("yyyy-MM-dd") prev_date_iso = self.editor.current_date.toString("yyyy-MM-dd")
self._save_date(prev_date_iso, explicit=False) self._save_date(prev_date_iso, explicit=False)
# Now load the newly selected date into the current tab # Now load the newly selected date into the current tab
new_date = self.calendar.selectedDate() new_date = self.calendar.selectedDate()
self._load_date_into_editor(new_date, editor) self._load_date_into_editor(new_date)
editor.current_date = new_date self.editor.current_date = new_date
# Update tab title # Update tab title
current_index = self.tab_widget.currentIndex() current_index = self.tab_widget.currentIndex()
@ -1003,10 +984,6 @@ class MainWindow(QMainWindow):
except Exception: except Exception:
pass pass
editor = self.current_editor()
if not editor or not hasattr(editor, "current_date"):
return
if explicit: if explicit:
# Prompt for a note # Prompt for a note
dlg = SaveDialog(self) dlg = SaveDialog(self)
@ -1016,7 +993,7 @@ class MainWindow(QMainWindow):
else: else:
note = "autosave" note = "autosave"
# Save the current editor's date # Save the current editor's date
date_iso = editor.current_date.toString("yyyy-MM-dd") date_iso = self.editor.current_date.toString("yyyy-MM-dd")
self._save_date(date_iso, explicit, note) self._save_date(date_iso, explicit, note)
try: try:
self._save_timer.start() self._save_timer.start()
@ -1314,17 +1291,18 @@ If you want an encrypted backup, choose Backup instead of Export.
return return
if not self.isActiveWindow(): if not self.isActiveWindow():
return return
editor = self.current_editor()
if not editor:
return
# Belt-and-suspenders: do it now and once more on the next tick # Belt-and-suspenders: do it now and once more on the next tick
editor.setFocus(Qt.ActiveWindowFocusReason) self.editor.setFocus(Qt.ActiveWindowFocusReason)
editor.ensureCursorVisible() self.editor.ensureCursorVisible()
QTimer.singleShot( QTimer.singleShot(
0, 0,
lambda: ( lambda: (
editor.setFocus(Qt.ActiveWindowFocusReason) if editor else None, (
editor.ensureCursorVisible() if editor else None, self.editor.setFocus(Qt.ActiveWindowFocusReason)
if self.editor
else None
),
self.editor.ensureCursorVisible() if self.editor else None,
), ),
) )
@ -1339,44 +1317,36 @@ If you want an encrypted backup, choose Backup instead of Export.
if ev.type() == QEvent.ActivationChange and self.isActiveWindow(): if ev.type() == QEvent.ActivationChange and self.isActiveWindow():
QTimer.singleShot(0, self._focus_editor_now) QTimer.singleShot(0, self._focus_editor_now)
def _set_editor_markdown_preserve_view( def _set_editor_markdown_preserve_view(self, markdown: str):
self, markdown: str, editor: MarkdownEditor | None = None
):
if editor is None:
editor = self.current_editor()
if not editor:
return
ed = editor
# Save caret/selection and scroll # Save caret/selection and scroll
cur = ed.textCursor() cur = self.editor.textCursor()
old_pos, old_anchor = cur.position(), cur.anchor() old_pos, old_anchor = cur.position(), cur.anchor()
v = ed.verticalScrollBar().value() v = self.editor.verticalScrollBar().value()
h = ed.horizontalScrollBar().value() h = self.editor.horizontalScrollBar().value()
# Only touch the doc if it actually changed # Only touch the doc if it actually changed
ed.blockSignals(True) self.editor.blockSignals(True)
if ed.to_markdown() != markdown: if self.editor.to_markdown() != markdown:
ed.from_markdown(markdown) self.editor.from_markdown(markdown)
ed.blockSignals(False) self.editor.blockSignals(False)
# Restore scroll first # Restore scroll first
ed.verticalScrollBar().setValue(v) self.editor.verticalScrollBar().setValue(v)
ed.horizontalScrollBar().setValue(h) self.editor.horizontalScrollBar().setValue(h)
# Restore caret/selection (bounded to new doc length) # Restore caret/selection (bounded to new doc length)
doc_length = ed.document().characterCount() - 1 doc_length = self.editor.document().characterCount() - 1
old_pos = min(old_pos, doc_length) old_pos = min(old_pos, doc_length)
old_anchor = min(old_anchor, doc_length) old_anchor = min(old_anchor, doc_length)
cur = ed.textCursor() cur = self.editor.textCursor()
cur.setPosition(old_anchor) cur.setPosition(old_anchor)
mode = ( mode = (
QTextCursor.KeepAnchor if old_anchor != old_pos else QTextCursor.MoveAnchor QTextCursor.KeepAnchor if old_anchor != old_pos else QTextCursor.MoveAnchor
) )
cur.setPosition(old_pos, mode) cur.setPosition(old_pos, mode)
ed.setTextCursor(cur) self.editor.setTextCursor(cur)
# Refresh highlights if the theme changed # Refresh highlights if the theme changed
if hasattr(self, "findBar"): if hasattr(self, "findBar"):

View file

@ -9,18 +9,18 @@ APP_ORG = "Bouquin"
APP_NAME = "Bouquin" APP_NAME = "Bouquin"
def default_db_path() -> Path:
base = Path(QStandardPaths.writableLocation(QStandardPaths.AppDataLocation))
return base / "notebook.db"
def get_settings() -> QSettings: def get_settings() -> QSettings:
return QSettings(APP_ORG, APP_NAME) return QSettings(APP_ORG, APP_NAME)
def load_db_config() -> DBConfig: def load_db_config() -> DBConfig:
s = get_settings() s = get_settings()
path = Path(s.value("db/path", str(default_db_path()))) default_db_path = str(
Path(QStandardPaths.writableLocation(QStandardPaths.AppDataLocation))
/ "notebook.db"
)
path = Path(s.value("db/path", default_db_path))
key = s.value("db/key", "") key = s.value("db/key", "")
idle = s.value("ui/idle_minutes", 15, type=int) idle = s.value("ui/idle_minutes", 15, type=int)
theme = s.value("ui/theme", "system", type=str) theme = s.value("ui/theme", "system", type=str)

View file

@ -1,6 +1,5 @@
from pathlib import Path from pathlib import Path
from bouquin.settings import ( from bouquin.settings import (
default_db_path,
get_settings, get_settings,
load_db_config, load_db_config,
save_db_config, save_db_config,
@ -8,12 +7,6 @@ from bouquin.settings import (
from bouquin.db import DBConfig from bouquin.db import DBConfig
def test_default_db_path_returns_writable_path(app, tmp_path):
p = default_db_path()
assert isinstance(p, Path)
p.parent.mkdir(parents=True, exist_ok=True)
def test_load_and_save_db_config_roundtrip(app, tmp_path): def test_load_and_save_db_config_roundtrip(app, tmp_path):
s = get_settings() s = get_settings()
for k in ["db/path", "db/key", "ui/idle_minutes", "ui/theme", "ui/move_todos"]: for k in ["db/path", "db/key", "ui/idle_minutes", "ui/theme", "ui/move_todos"]: