Ensure tabs are ordered by calendar date, and some other code cleanups

This commit is contained in:
Miguel Jacq 2025-11-10 08:05:17 +11:00
parent ab1af80d10
commit dfde0d6e6c
Signed by: mig5
GPG key ID: 59B3F0C24135C6A9
4 changed files with 131 additions and 63 deletions

View file

@ -1,3 +1,8 @@
# 0.2.1.2
* Ensure tabs are ordered by calendar date
* Some other code cleanups
# 0.2.1.1 # 0.2.1.1
* Fix history preview pane to be in markdown * Fix history preview pane to be in markdown

View file

@ -3,7 +3,6 @@ from __future__ import annotations
import csv import csv
import html import html
import json import json
import os
from dataclasses import dataclass from dataclasses import dataclass
from pathlib import Path from pathlib import Path

View file

@ -322,7 +322,61 @@ class MainWindow(QMainWindow):
if self._try_connect(): if self._try_connect():
return True return True
# ----------------- Tab management ----------------- # # ----------------- Tab and date management ----------------- #
def _current_date_iso(self) -> str:
d = self.calendar.selectedDate()
return f"{d.year():04d}-{d.month():02d}-{d.day():02d}"
def _date_key(self, qd: QDate) -> tuple[int, int, int]:
return (qd.year(), qd.month(), qd.day())
def _index_for_date_insert(self, date: QDate) -> int:
"""Return the index where a tab for `date` should be inserted (ascending order)."""
key = self._date_key(date)
for i in range(self.tab_widget.count()):
w = self.tab_widget.widget(i)
d = getattr(w, "current_date", None)
if isinstance(d, QDate) and d.isValid():
if self._date_key(d) > key:
return i
return self.tab_widget.count()
def _reorder_tabs_by_date(self):
"""Reorder existing tabs by their date (ascending)."""
bar = self.tab_widget.tabBar()
dated, undated = [], []
for i in range(self.tab_widget.count()):
w = self.tab_widget.widget(i)
d = getattr(w, "current_date", None)
if isinstance(d, QDate) and d.isValid():
dated.append((d, w))
else:
undated.append(w)
dated.sort(key=lambda t: self._date_key(t[0]))
with QSignalBlocker(self.tab_widget):
# Update labels to yyyy-MM-dd
for d, w in dated:
idx = self.tab_widget.indexOf(w)
if idx != -1:
self.tab_widget.setTabText(idx, d.toString("yyyy-MM-dd"))
# Move dated tabs into target positions 0..len(dated)-1
for target_pos, (_, w) in enumerate(dated):
cur = self.tab_widget.indexOf(w)
if cur != -1 and cur != target_pos:
bar.moveTab(cur, target_pos)
# Keep any undated pages (if they ever exist) after the dated ones
start = len(dated)
for offset, w in enumerate(undated):
cur = self.tab_widget.indexOf(w)
target = start + offset
if cur != -1 and cur != target:
bar.moveTab(cur, target)
def _tab_index_for_date(self, date: QDate) -> int: def _tab_index_for_date(self, date: QDate) -> int:
"""Return the index of the tab showing `date`, or -1 if none.""" """Return the index of the tab showing `date`, or -1 if none."""
@ -369,9 +423,7 @@ class MainWindow(QMainWindow):
editor.cursorPositionChanged.connect(self._sync_toolbar) editor.cursorPositionChanged.connect(self._sync_toolbar)
editor.textChanged.connect(self._on_text_changed) editor.textChanged.connect(self._on_text_changed)
# Determine tab title # Set tab title
if date is None:
date = self.calendar.selectedDate()
tab_title = date.toString("yyyy-MM-dd") tab_title = date.toString("yyyy-MM-dd")
# Add the tab # Add the tab
@ -384,6 +436,12 @@ class MainWindow(QMainWindow):
# 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
# Insert at sorted position
tab_title = date.toString("yyyy-MM-dd")
pos = self._index_for_date_insert(date)
index = self.tab_widget.insertTab(pos, editor, tab_title)
self.tab_widget.setCurrentIndex(index)
return editor return editor
def _close_tab(self, index: int): def _close_tab(self, index: int):
@ -425,36 +483,6 @@ class MainWindow(QMainWindow):
return return
getattr(ed, method_name)(*args) getattr(ed, method_name)(*args)
def _bind_toolbar(self):
if getattr(self, "_toolbar_bound", False):
return
tb = self.toolBar
# keep refs so we never create new lambdas (prevents accidental dupes)
self._tb_bold = lambda: self._call_editor("apply_weight")
self._tb_italic = lambda: self._call_editor("apply_italic")
self._tb_strike = lambda: self._call_editor("apply_strikethrough")
self._tb_code = lambda: self._call_editor("apply_code")
self._tb_heading = lambda level: self._call_editor("apply_heading", level)
self._tb_bullets = lambda: self._call_editor("toggle_bullets")
self._tb_numbers = lambda: self._call_editor("toggle_numbers")
self._tb_checkboxes = lambda: self._call_editor("toggle_checkboxes")
tb.boldRequested.connect(self._tb_bold)
tb.italicRequested.connect(self._tb_italic)
tb.strikeRequested.connect(self._tb_strike)
tb.codeRequested.connect(self._tb_code)
tb.headingRequested.connect(self._tb_heading)
tb.bulletsRequested.connect(self._tb_bullets)
tb.numbersRequested.connect(self._tb_numbers)
tb.checkboxesRequested.connect(self._tb_checkboxes)
# these arent editor methods
tb.historyRequested.connect(self._open_history)
tb.insertImageRequested.connect(self._on_insert_image)
self._toolbar_bound = True
def current_editor(self) -> MarkdownEditor | None: def current_editor(self) -> MarkdownEditor | None:
"""Get the currently active editor.""" """Get the currently active editor."""
return self.tab_widget.currentWidget() return self.tab_widget.currentWidget()
@ -564,6 +592,7 @@ class MainWindow(QMainWindow):
if action == open_in_new_tab_action and clicked_date and clicked_date.isValid(): if action == open_in_new_tab_action and clicked_date and clicked_date.isValid():
self._open_date_in_tab(clicked_date) self._open_date_in_tab(clicked_date)
# ----------------- Some theme helpers -------------------#
def _retheme_overrides(self): def _retheme_overrides(self):
if hasattr(self, "_lock_overlay"): if hasattr(self, "_lock_overlay"):
self._lock_overlay._apply_overlay_style() self._lock_overlay._apply_overlay_style()
@ -698,6 +727,36 @@ class MainWindow(QMainWindow):
# --- UI handlers --------------------------------------------------------- # --- UI handlers ---------------------------------------------------------
def _bind_toolbar(self):
if getattr(self, "_toolbar_bound", False):
return
tb = self.toolBar
# keep refs so we never create new lambdas (prevents accidental dupes)
self._tb_bold = lambda: self._call_editor("apply_weight")
self._tb_italic = lambda: self._call_editor("apply_italic")
self._tb_strike = lambda: self._call_editor("apply_strikethrough")
self._tb_code = lambda: self._call_editor("apply_code")
self._tb_heading = lambda level: self._call_editor("apply_heading", level)
self._tb_bullets = lambda: self._call_editor("toggle_bullets")
self._tb_numbers = lambda: self._call_editor("toggle_numbers")
self._tb_checkboxes = lambda: self._call_editor("toggle_checkboxes")
tb.boldRequested.connect(self._tb_bold)
tb.italicRequested.connect(self._tb_italic)
tb.strikeRequested.connect(self._tb_strike)
tb.codeRequested.connect(self._tb_code)
tb.headingRequested.connect(self._tb_heading)
tb.bulletsRequested.connect(self._tb_bullets)
tb.numbersRequested.connect(self._tb_numbers)
tb.checkboxesRequested.connect(self._tb_checkboxes)
# these arent editor methods
tb.historyRequested.connect(self._open_history)
tb.insertImageRequested.connect(self._on_insert_image)
self._toolbar_bound = True
def _sync_toolbar(self): def _sync_toolbar(self):
fmt = self.editor.currentCharFormat() fmt = self.editor.currentCharFormat()
c = self.editor.textCursor() c = self.editor.textCursor()
@ -740,12 +799,8 @@ class MainWindow(QMainWindow):
self.toolBar.actBullets.setChecked(bool(bullets_on)) self.toolBar.actBullets.setChecked(bool(bullets_on))
self.toolBar.actNumbers.setChecked(bool(numbers_on)) self.toolBar.actNumbers.setChecked(bool(numbers_on))
def _current_date_iso(self) -> str:
d = self.calendar.selectedDate()
return f"{d.year():04d}-{d.month():02d}-{d.day():02d}"
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 (backward compatibility).""" """Load a date into the current editor"""
editor = self.current_editor() editor = self.current_editor()
if not editor: if not editor:
return return
@ -762,6 +817,9 @@ class MainWindow(QMainWindow):
if current_index >= 0: if current_index >= 0:
self.tab_widget.setTabText(current_index, date_iso) self.tab_widget.setTabText(current_index, date_iso)
# Keep tabs sorted by date
self._reorder_tabs_by_date()
def _load_date_into_editor( def _load_date_into_editor(
self, date: QDate, editor: MarkdownEditor, extra_data=False self, date: QDate, editor: MarkdownEditor, extra_data=False
): ):
@ -888,6 +946,34 @@ class MainWindow(QMainWindow):
if current_index >= 0: if current_index >= 0:
self.tab_widget.setTabText(current_index, new_date.toString("yyyy-MM-dd")) self.tab_widget.setTabText(current_index, new_date.toString("yyyy-MM-dd"))
# Keep tabs sorted by date
self._reorder_tabs_by_date()
# ----------- History handler ------------#
def _open_history(self):
date_iso = self._current_date_iso()
dlg = HistoryDialog(self.db, date_iso, self)
if dlg.exec() == QDialog.Accepted:
# refresh editor + calendar (head pointer may have changed)
self._load_selected_date(date_iso)
self._refresh_calendar_marks()
# ----------- Image insert handler ------------#
def _on_insert_image(self):
# Let the user pick one or many images
paths, _ = QFileDialog.getOpenFileNames(
self,
"Insert image(s)",
"",
"Images (*.png *.jpg *.jpeg *.bmp *.gif *.webp)",
)
if not paths:
return
# Insert each image
for path_str in paths:
self.editor.insert_image_from_path(Path(path_str))
# --------------- Database saving of content ---------------- #
def _save_date(self, date_iso: str, explicit: bool = False, note: str = "autosave"): def _save_date(self, date_iso: str, explicit: bool = False, note: str = "autosave"):
""" """
Save editor contents into the given date. Shows status on success. Save editor contents into the given date. Shows status on success.
@ -937,28 +1023,6 @@ class MainWindow(QMainWindow):
except Exception: except Exception:
pass pass
def _open_history(self):
date_iso = self._current_date_iso()
dlg = HistoryDialog(self.db, date_iso, self)
if dlg.exec() == QDialog.Accepted:
# refresh editor + calendar (head pointer may have changed)
self._load_selected_date(date_iso)
self._refresh_calendar_marks()
def _on_insert_image(self):
# Let the user pick one or many images
paths, _ = QFileDialog.getOpenFileNames(
self,
"Insert image(s)",
"",
"Images (*.png *.jpg *.jpeg *.bmp *.gif *.webp)",
)
if not paths:
return
# Insert each image
for path_str in paths:
self.editor.insert_image_from_path(Path(path_str))
# ----------- Settings handler ------------# # ----------- Settings handler ------------#
def _open_settings(self): def _open_settings(self):
dlg = SettingsDialog(self.cfg, self.db, self) dlg = SettingsDialog(self.cfg, self.db, self)

View file

@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "bouquin" name = "bouquin"
version = "0.2.1.1" version = "0.2.1.2"
description = "Bouquin is a simple, opinionated notebook application written in Python, PyQt and SQLCipher." description = "Bouquin is a simple, opinionated notebook application written in Python, PyQt and SQLCipher."
authors = ["Miguel Jacq <mig@mig5.net>"] authors = ["Miguel Jacq <mig@mig5.net>"]
readme = "README.md" readme = "README.md"