From 0caf0efeef8163cf1b3a29af7f5ebd2d17426066 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Fri, 31 Oct 2025 16:34:20 +1100 Subject: [PATCH 01/18] Allow jumping to today --- bouquin/main_window.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/bouquin/main_window.py b/bouquin/main_window.py index 394ccb9..5b8bc97 100644 --- a/bouquin/main_window.py +++ b/bouquin/main_window.py @@ -97,6 +97,13 @@ class MainWindow(QMainWindow): nav_menu.addAction(act_next) self.addAction(act_next) + act_today = QAction("Today", self) + act_today.setShortcut("Ctrl+T") + act_today.setShortcutContext(Qt.ApplicationShortcut) + act_today.triggered.connect(self._adjust_today) + nav_menu.addAction(act_today) + self.addAction(act_today) + # Autosave self._dirty = False self._save_timer = QTimer(self) @@ -176,6 +183,11 @@ class MainWindow(QMainWindow): d = self.calendar.selectedDate().addDays(delta) self.calendar.setSelectedDate(d) + def _adjust_today(self): + """Jump to today.""" + today = QDate.currentDate() + self.calendar.setSelectedDate(today) + def _on_date_changed(self): """ When the calendar selection changes, save the previous day's note if dirty, From f778afd268cd3a3f2e677375f5939ef0b78a7381 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Fri, 31 Oct 2025 16:46:42 +1100 Subject: [PATCH 02/18] Add ability to change the key --- README.md | 1 - bouquin/db.py | 19 +++++++++++++++++++ bouquin/main_window.py | 2 +- bouquin/settings_dialog.py | 32 ++++++++++++++++++++++++++++++-- 4 files changed, 50 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 8b20e14..b874668 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,6 @@ There is deliberately no network connectivity or syncing intended. * Search * Taxonomy/tagging - * Ability to change the SQLCipher key * Export to other formats (plaintext, json, sql etc) diff --git a/bouquin/db.py b/bouquin/db.py index 1ea60fa..15cc4c9 100644 --- a/bouquin/db.py +++ b/bouquin/db.py @@ -64,6 +64,25 @@ class DBManager: cur.execute("PRAGMA user_version = 1;") self.conn.commit() + def rekey(self, new_key: str) -> None: + """ + Change the SQLCipher passphrase in-place, then reopen the connection + with the new key to verify. + """ + if self.conn is None: + raise RuntimeError("Database is not connected") + cur = self.conn.cursor() + # Change the encryption key of the currently open database + cur.execute(f"PRAGMA rekey = '{new_key}';") + self.conn.commit() + + # Close and reopen with the new key to verify and restore PRAGMAs + self.conn.close() + self.conn = None + self.cfg.key = new_key + if not self.connect(): + raise sqlite.Error("Re-open failed after rekey") + def get_entry(self, date_iso: str) -> str: cur = self.conn.cursor() cur.execute("SELECT content FROM entries WHERE date = ?;", (date_iso,)) diff --git a/bouquin/main_window.py b/bouquin/main_window.py index 5b8bc97..309ef28 100644 --- a/bouquin/main_window.py +++ b/bouquin/main_window.py @@ -231,7 +231,7 @@ class MainWindow(QMainWindow): self._save_date(self._current_date_iso(), explicit) def _open_settings(self): - dlg = SettingsDialog(self.cfg, self) + dlg = SettingsDialog(self.cfg, self.db, self) if dlg.exec() == QDialog.Accepted: new_cfg = dlg.config if new_cfg.path != self.cfg.path: diff --git a/bouquin/settings_dialog.py b/bouquin/settings_dialog.py index 790c4e0..ca2514c 100644 --- a/bouquin/settings_dialog.py +++ b/bouquin/settings_dialog.py @@ -13,17 +13,20 @@ from PySide6.QtWidgets import ( QFileDialog, QDialogButtonBox, QSizePolicy, + QMessageBox, ) -from .db import DBConfig +from .db import DBConfig, DBManager from .settings import save_db_config +from .key_prompt import KeyPrompt class SettingsDialog(QDialog): - def __init__(self, cfg: DBConfig, parent=None): + def __init__(self, cfg: DBConfig, db: DBManager, parent=None): super().__init__(parent) self.setWindowTitle("Settings") self._cfg = DBConfig(path=cfg.path, key="") + self._db = db form = QFormLayout() form.setFieldGrowthPolicy(QFormLayout.ExpandingFieldsGrow) @@ -44,12 +47,17 @@ class SettingsDialog(QDialog): h.setStretch(1, 0) form.addRow("Database path", path_row) + # Change key button + self.rekey_btn = QPushButton("Change key") + self.rekey_btn.clicked.connect(self._change_key) + bb = QDialogButtonBox(QDialogButtonBox.Save | QDialogButtonBox.Cancel) bb.accepted.connect(self._save) bb.rejected.connect(self.reject) v = QVBoxLayout(self) v.addLayout(form) + v.addWidget(self.rekey_btn) v.addWidget(bb) def _browse(self): @@ -67,6 +75,26 @@ class SettingsDialog(QDialog): save_db_config(self._cfg) self.accept() + def _change_key(self): + p1 = KeyPrompt(self, title="Change key", message="Enter new key") + if p1.exec() != QDialog.Accepted: + return + new_key = p1.key() + p2 = KeyPrompt(self, title="Change key", message="Re-enter new key") + if p2.exec() != QDialog.Accepted: + return + if new_key != p2.key(): + QMessageBox.warning(self, "Key mismatch", "The two entries did not match.") + return + if not new_key: + QMessageBox.warning(self, "Empty key", "Key cannot be empty.") + return + try: + self._db.rekey(new_key) + QMessageBox.information(self, "Key changed", "The database key was updated.") + except Exception as e: + QMessageBox.critical(self, "Error", f"Could not change key:\n{e}") + @property def config(self) -> DBConfig: return self._cfg From 3db384e7e440a76887a6f354612509f4d2248c82 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Fri, 31 Oct 2025 16:47:18 +1100 Subject: [PATCH 03/18] Add CHANGELOG.md --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..40a4f3b --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,8 @@ +# 0.1.1 + + * Add ability to change the key + * Add ability to jump to today's date + +# 0.1.0 + + * Initial release. From cc9453997eb0bf13793efcd475108579758f1b57 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Fri, 31 Oct 2025 16:50:53 +1100 Subject: [PATCH 04/18] Add shortcut for Settings (Ctrl+E) so as not to collide with Ctrl+S (Save) --- CHANGELOG.md | 1 + bouquin/main_window.py | 3 ++- pyproject.toml | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 40a4f3b..07944f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ * Add ability to change the key * Add ability to jump to today's date + * Add shortcut for Settings (Ctrl+E) so as not to collide with Ctrl+S (Save) # 0.1.0 diff --git a/bouquin/main_window.py b/bouquin/main_window.py index 309ef28..6b0451c 100644 --- a/bouquin/main_window.py +++ b/bouquin/main_window.py @@ -72,7 +72,8 @@ class MainWindow(QMainWindow): act_save.setShortcut("Ctrl+S") act_save.triggered.connect(lambda: self._save_current(explicit=True)) file_menu.addAction(act_save) - act_settings = QAction("&Settings", self) + act_settings = QAction("S&ettings", self) + act_save.setShortcut("Ctrl+E") act_settings.triggered.connect(self._open_settings) file_menu.addAction(act_settings) file_menu.addSeparator() diff --git a/pyproject.toml b/pyproject.toml index 1be51b7..2be1386 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "bouquin" -version = "0.1.0" +version = "0.1.1" description = "Bouquin is a simple, opinionated notebook application written in Python, PyQt and SQLCipher." authors = ["Miguel Jacq "] readme = "README.md" From 50fee4ec78491f705d9b1c6e991c3e0ce9460697 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Fri, 31 Oct 2025 16:55:51 +1100 Subject: [PATCH 05/18] Add repository url to pyproject.toml --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 2be1386..3d2088d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,6 +5,7 @@ description = "Bouquin is a simple, opinionated notebook application written in authors = ["Miguel Jacq "] readme = "README.md" license = "GPL-3.0-or-later" +repository = "https://git.mig5.net/mig5/bouquin" [tool.poetry.dependencies] python = ">=3.9,<3.14" From e0d7826fe0f2bdb1af748f64aeb6389029935f90 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sat, 1 Nov 2025 16:41:57 +1100 Subject: [PATCH 06/18] Switch to HTML (QTextEdit) and a style toolbar --- CHANGELOG.md | 5 ++ bouquin/editor.py | 118 +++++++++++++++++++++++++++++++++++++++++ bouquin/highlighter.py | 112 -------------------------------------- bouquin/main_window.py | 64 +++++++++++++++------- bouquin/toolbar.py | 95 +++++++++++++++++++++++++++++++++ 5 files changed, 263 insertions(+), 131 deletions(-) create mode 100644 bouquin/editor.py delete mode 100644 bouquin/highlighter.py create mode 100644 bouquin/toolbar.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 07944f2..c54582d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# 0.1.2 + + * Switch from Markdown to HTML via QTextEdit, with a toolbar + * Fix Settings shortcut and change nav menu from 'File' to 'Application' + # 0.1.1 * Add ability to change the key diff --git a/bouquin/editor.py b/bouquin/editor.py new file mode 100644 index 0000000..bf40a6b --- /dev/null +++ b/bouquin/editor.py @@ -0,0 +1,118 @@ +from PySide6.QtGui import ( + QColor, + QFont, + QFontDatabase, + QTextCharFormat, + QTextListFormat, + QTextBlockFormat, +) +from PySide6.QtCore import Slot +from PySide6.QtWidgets import QTextEdit + + +class Editor(QTextEdit): + def __init__(self): + super().__init__() + tab_w = 4 * self.fontMetrics().horizontalAdvance(" ") + self.setTabStopDistance(tab_w) + + def merge_on_sel(self, fmt): + """ + Sets the styling on the selected characters. + """ + cursor = self.textCursor() + if not cursor.hasSelection(): + cursor.select(cursor.SelectionType.WordUnderCursor) + cursor.mergeCharFormat(fmt) + self.mergeCurrentCharFormat(fmt) + + @Slot(QFont.Weight) + def apply_weight(self, weight): + fmt = QTextCharFormat() + fmt.setFontWeight(weight) + self.merge_on_sel(fmt) + + @Slot() + def apply_italic(self): + cur = self.currentCharFormat() + fmt = QTextCharFormat() + fmt.setFontItalic(not cur.fontItalic()) + self.merge_on_sel(fmt) + + @Slot() + def apply_underline(self): + cur = self.currentCharFormat() + fmt = QTextCharFormat() + fmt.setFontUnderline(not cur.fontUnderline()) + self.merge_on_sel(fmt) + + @Slot() + def apply_strikethrough(self): + cur = self.currentCharFormat() + fmt = QTextCharFormat() + fmt.setFontStrikeOut(not cur.fontStrikeOut()) + self.merge_on_sel(fmt) + + @Slot() + def apply_code(self): + c = self.textCursor() + if not c.hasSelection(): + c.select(c.SelectionType.BlockUnderCursor) + + bf = QTextBlockFormat() + bf.setLeftMargin(12) + bf.setRightMargin(12) + bf.setTopMargin(6) + bf.setBottomMargin(6) + bf.setBackground(QColor(245, 245, 245)) + bf.setNonBreakableLines(True) + + cf = QTextCharFormat() + mono = QFontDatabase.systemFont(QFontDatabase.SystemFont.FixedFont) + cf.setFont(mono) + cf.setFontFixedPitch(True) + + # If the current block already looks like a code block, remove styling + cur_bf = c.blockFormat() + is_code = ( + cur_bf.nonBreakableLines() + and cur_bf.background().color().rgb() == QColor(245, 245, 245).rgb() + ) + if is_code: + # clear: margins/background/wrapping + bf = QTextBlockFormat() + cf = QTextCharFormat() + + c.mergeBlockFormat(bf) + c.mergeBlockCharFormat(cf) + + @Slot(int) + def apply_heading(self, size): + fmt = QTextCharFormat() + if size: + fmt.setFontWeight(QFont.Weight.Bold) + fmt.setFontPointSize(size) + else: + fmt.setFontWeight(QFont.Weight.Normal) + fmt.setFontPointSize(self.font().pointSizeF()) + self.merge_on_sel(fmt) + + def toggle_bullets(self): + c = self.textCursor() + lst = c.currentList() + if lst and lst.format().style() == QTextListFormat.Style.ListDisc: + lst.remove(c.block()) + return + fmt = QTextListFormat() + fmt.setStyle(QTextListFormat.Style.ListDisc) + c.createList(fmt) + + def toggle_numbers(self): + c = self.textCursor() + lst = c.currentList() + if lst and lst.format().style() == QTextListFormat.Style.ListDecimal: + lst.remove(c.block()) + return + fmt = QTextListFormat() + fmt.setStyle(QTextListFormat.Style.ListDecimal) + c.createList(fmt) diff --git a/bouquin/highlighter.py b/bouquin/highlighter.py deleted file mode 100644 index 456dfa2..0000000 --- a/bouquin/highlighter.py +++ /dev/null @@ -1,112 +0,0 @@ -from __future__ import annotations - -import re -from PySide6.QtGui import QFont, QTextCharFormat, QSyntaxHighlighter, QColor - - -class MarkdownHighlighter(QSyntaxHighlighter): - ST_NORMAL = 0 - ST_CODE = 1 - - FENCE = re.compile(r"^```") - - def __init__(self, document): - super().__init__(document) - - base_size = document.defaultFont().pointSizeF() or 12.0 - - # Monospace for code - self.mono = QFont("Monospace") - self.mono.setStyleHint(QFont.TypeWriter) - - # Light, high-contrast scheme for code - self.col_bg = QColor("#eef2f6") # light code bg - self.col_fg = QColor("#1f2328") # dark text - - # Formats - self.fmt_h = [QTextCharFormat() for _ in range(6)] - for i, f in enumerate(self.fmt_h, start=1): - f.setFontWeight(QFont.Weight.Bold) - f.setFontPointSize(base_size + (7 - i)) - self.fmt_bold = QTextCharFormat() - self.fmt_bold.setFontWeight(QFont.Weight.Bold) - self.fmt_italic = QTextCharFormat() - self.fmt_italic.setFontItalic(True) - self.fmt_quote = QTextCharFormat() - self.fmt_quote.setForeground(QColor("#6a737d")) - self.fmt_link = QTextCharFormat() - self.fmt_link.setFontUnderline(True) - self.fmt_list = QTextCharFormat() - self.fmt_list.setFontWeight(QFont.Weight.DemiBold) - self.fmt_strike = QTextCharFormat() - self.fmt_strike.setFontStrikeOut(True) - - # Uniform code style - self.fmt_code = QTextCharFormat() - self.fmt_code.setFont(self.mono) - self.fmt_code.setFontPointSize(max(6.0, base_size - 1)) - self.fmt_code.setBackground(self.col_bg) - self.fmt_code.setForeground(self.col_fg) - - # Simple patterns - self.re_heading = re.compile(r"^(#{1,6}) +.*$") - self.re_bold = re.compile(r"\*\*(.+?)\*\*|__(.+?)__") - self.re_italic = re.compile(r"\*(?!\*)(.+?)\*|_(?!_)(.+?)_") - self.re_strike = re.compile(r"~~(.+?)~~") - self.re_inline_code = re.compile(r"`([^`]+)`") - self.re_link = re.compile(r"\[([^\]]+)\]\(([^)]+)\)") - self.re_list = re.compile(r"^ *(?:[-*+] +|[0-9]+[.)] +)") - self.re_quote = re.compile(r"^> ?.*$") - - def highlightBlock(self, text: str) -> None: - prev = self.previousBlockState() - in_code = prev == self.ST_CODE - - if in_code: - # Entire line is code - self.setFormat(0, len(text), self.fmt_code) - if self.FENCE.match(text): - self.setCurrentBlockState(self.ST_NORMAL) - else: - self.setCurrentBlockState(self.ST_CODE) - return - - # Starting/ending a fenced block? - if self.FENCE.match(text): - self.setFormat(0, len(text), self.fmt_code) - self.setCurrentBlockState(self.ST_CODE) - return - - # --- Normal markdown styling --- - m = self.re_heading.match(text) - if m: - level = min(len(m.group(1)), 6) - self.setFormat(0, len(text), self.fmt_h[level - 1]) - self.setCurrentBlockState(self.ST_NORMAL) - return - - m = self.re_list.match(text) - if m: - self.setFormat(m.start(), m.end() - m.start(), self.fmt_list) - - if self.re_quote.match(text): - self.setFormat(0, len(text), self.fmt_quote) - - for m in self.re_inline_code.finditer(text): - self.setFormat(m.start(), m.end() - m.start(), self.fmt_code) - - for m in self.re_bold.finditer(text): - self.setFormat(m.start(), m.end() - m.start(), self.fmt_bold) - - for m in self.re_italic.finditer(text): - self.setFormat(m.start(), m.end() - m.start(), self.fmt_italic) - - for m in self.re_strike.finditer(text): - self.setFormat(m.start(), m.end() - m.start(), self.fmt_strike) - - for m in self.re_link.finditer(text): - start = m.start(1) - 1 - length = len(m.group(1)) + 2 - self.setFormat(start, length, self.fmt_link) - - self.setCurrentBlockState(self.ST_NORMAL) diff --git a/bouquin/main_window.py b/bouquin/main_window.py index 6b0451c..359481e 100644 --- a/bouquin/main_window.py +++ b/bouquin/main_window.py @@ -3,24 +3,28 @@ from __future__ import annotations import sys from PySide6.QtCore import QDate, QTimer, Qt -from PySide6.QtGui import QAction, QFont, QTextCharFormat +from PySide6.QtGui import ( + QAction, + QFont, + QTextCharFormat, +) from PySide6.QtWidgets import ( - QDialog, QCalendarWidget, + QDialog, QMainWindow, QMessageBox, - QPlainTextEdit, + QSizePolicy, QSplitter, QVBoxLayout, QWidget, - QSizePolicy, ) from .db import DBManager -from .settings import APP_NAME, load_db_config, save_db_config +from .editor import Editor from .key_prompt import KeyPrompt -from .highlighter import MarkdownHighlighter +from .settings import APP_NAME, load_db_config, save_db_config from .settings_dialog import SettingsDialog +from .toolbar import ToolBar class MainWindow(QMainWindow): @@ -40,6 +44,8 @@ class MainWindow(QMainWindow): self.calendar.setGridVisible(True) self.calendar.selectionChanged.connect(self._on_date_changed) + # Lock the calendar to the left panel at the top to stop it stretching + # when the main window is resized. left_panel = QWidget() left_layout = QVBoxLayout(left_panel) left_layout.setContentsMargins(8, 8, 8, 8) @@ -47,10 +53,22 @@ class MainWindow(QMainWindow): left_layout.addStretch(1) left_panel.setFixedWidth(self.calendar.sizeHint().width() + 16) - self.editor = QPlainTextEdit() - tab_w = 4 * self.editor.fontMetrics().horizontalAdvance(" ") - self.editor.setTabStopDistance(tab_w) - self.highlighter = MarkdownHighlighter(self.editor.document()) + # This is the note-taking editor + self.editor = Editor() + + # Toolbar for controlling styling + tb = ToolBar() + self.addToolBar(tb) + # Wire toolbar intents to editor methods + tb.boldRequested.connect(self.editor.apply_weight) + tb.italicRequested.connect(self.editor.apply_italic) + tb.underlineRequested.connect(self.editor.apply_underline) + tb.strikeRequested.connect(self.editor.apply_strikethrough) + tb.codeRequested.connect(self.editor.apply_code) + tb.headingRequested.connect(self.editor.apply_heading) + tb.bulletsRequested.connect(self.editor.toggle_bullets) + tb.numbersRequested.connect(self.editor.toggle_numbers) + tb.alignRequested.connect(self.editor.setAlignment) split = QSplitter() split.addWidget(left_panel) @@ -67,13 +85,13 @@ class MainWindow(QMainWindow): # Menu bar (File) mb = self.menuBar() - file_menu = mb.addMenu("&File") + file_menu = mb.addMenu("&Application") act_save = QAction("&Save", self) act_save.setShortcut("Ctrl+S") act_save.triggered.connect(lambda: self._save_current(explicit=True)) file_menu.addAction(act_save) act_settings = QAction("S&ettings", self) - act_save.setShortcut("Ctrl+E") + act_settings.setShortcut("Ctrl+E") act_settings.triggered.connect(self._open_settings) file_menu.addAction(act_settings) file_menu.addSeparator() @@ -82,7 +100,7 @@ class MainWindow(QMainWindow): act_quit.triggered.connect(self.close) file_menu.addAction(act_quit) - # Navigate menu with next/previous day + # Navigate menu with next/previous/today nav_menu = mb.addMenu("&Navigate") act_prev = QAction("Previous Day", self) act_prev.setShortcut("Ctrl+P") @@ -112,12 +130,14 @@ class MainWindow(QMainWindow): self._save_timer.timeout.connect(self._save_current) self.editor.textChanged.connect(self._on_text_changed) - # First load + mark dates with content + # First load + mark dates in calendar with content self._load_selected_date() self._refresh_calendar_marks() - # --- DB lifecycle def _try_connect(self) -> bool: + """ + Try to connect to the database. + """ try: self.db = DBManager(self.cfg) ok = self.db.connect() @@ -131,6 +151,9 @@ class MainWindow(QMainWindow): return ok def _prompt_for_key_until_valid(self) -> bool: + """ + Prompt for the SQLCipher key. + """ while True: dlg = KeyPrompt(self, message="Enter a key to unlock the notebook") if dlg.exec() != QDialog.Accepted: @@ -139,8 +162,11 @@ class MainWindow(QMainWindow): if self._try_connect(): return True - # --- Calendar marks to indicate text exists for htat day ----------------- def _refresh_calendar_marks(self): + """ + Sets a bold marker on the day to indicate that text exists + for that day. + """ fmt_bold = QTextCharFormat() fmt_bold.setFontWeight(QFont.Weight.Bold) # Clear previous marks @@ -169,7 +195,7 @@ class MainWindow(QMainWindow): QMessageBox.critical(self, "Read Error", str(e)) return self.editor.blockSignals(True) - self.editor.setPlainText(text) + self.editor.setHtml(text) self.editor.blockSignals(False) self._dirty = False # track which date the editor currently represents @@ -212,7 +238,7 @@ class MainWindow(QMainWindow): """ if not self._dirty and not explicit: return - text = self.editor.toPlainText() + text = self.editor.toHtml() try: self.db.upsert_entry(date_iso, text) except Exception as e: @@ -249,7 +275,7 @@ class MainWindow(QMainWindow): self._load_selected_date() self._refresh_calendar_marks() - def closeEvent(self, event): # noqa: N802 + def closeEvent(self, event): try: self._save_current() self.db.close() diff --git a/bouquin/toolbar.py b/bouquin/toolbar.py new file mode 100644 index 0000000..471fd8e --- /dev/null +++ b/bouquin/toolbar.py @@ -0,0 +1,95 @@ +from PySide6.QtCore import Signal, Qt +from PySide6.QtGui import QFont, QAction +from PySide6.QtWidgets import QToolBar + +class ToolBar(QToolBar): + boldRequested = Signal(QFont.Weight) + italicRequested = Signal() + underlineRequested = Signal() + strikeRequested = Signal() + codeRequested = Signal() + headingRequested = Signal(int) + bulletsRequested = Signal() + numbersRequested = Signal() + alignRequested = Signal(Qt.AlignmentFlag) + + def __init__(self, parent=None): + super().__init__("Format", parent) + self._build_actions() + + def _build_actions(self): + # Bold + bold = QAction("Bold", self) + bold.setShortcut("Ctrl+B") + bold.triggered.connect(lambda: self.boldRequested.emit(QFont.Weight.Bold)) + + italic = QAction("Italic", self) + italic.setShortcut("Ctrl+I") + italic.triggered.connect(self.italicRequested) + + underline = QAction("Underline", self) + underline.setShortcut("Ctrl+U") + underline.triggered.connect(self.underlineRequested) + + strike = QAction("Strikethrough", self) + strike.setShortcut("Ctrl+-") + strike.triggered.connect(self.strikeRequested) + + code = QAction("", self) + code.setShortcut("Ctrl+`") + code.triggered.connect(self.codeRequested) + + # Headings + h1 = QAction("H1", self) + h1.setShortcut("Ctrl+1") + h2 = QAction("H2", self) + h2.setShortcut("Ctrl+2") + h3 = QAction("H3", self) + h3.setShortcut("Ctrl+3") + normal = QAction("Normal", self) + normal.setShortcut("Ctrl+P") + + h1.triggered.connect(lambda: self.headingRequested.emit(24)) + h2.triggered.connect(lambda: self.headingRequested.emit(18)) + h3.triggered.connect(lambda: self.headingRequested.emit(14)) + normal.triggered.connect(lambda: self.headingRequested.emit(0)) + + # Lists + bullets = QAction("• Bullets", self) + bullets.triggered.connect(self.bulletsRequested) + numbers = QAction("1. Numbered", self) + numbers.triggered.connect(self.numbersRequested) + + # Alignment + left = QAction("Align Left", self) + center = QAction("Align Center", self) + right = QAction("Align Right", self) + + left.triggered.connect( + lambda: self.alignRequested.emit(Qt.AlignmentFlag.AlignLeft) + ) + center.triggered.connect( + lambda: self.alignRequested.emit(Qt.AlignmentFlag.AlignHCenter) + ) + right.triggered.connect( + lambda: self.alignRequested.emit(Qt.AlignmentFlag.AlignRight) + ) + + self.addActions( + [ + bold, + italic, + underline, + strike, + code, + h1, + h2, + h3, + normal, + bullets, + numbers, + left, + center, + right, + ] + ) From 72862f9a4f277a86ea895762b2282d714aead5ea Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sat, 1 Nov 2025 16:45:59 +1100 Subject: [PATCH 07/18] Update test to account for HTML --- tests/test_ui.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_ui.py b/tests/test_ui.py index 5df04bc..280a01a 100644 --- a/tests/test_ui.py +++ b/tests/test_ui.py @@ -30,11 +30,11 @@ def test_manual_save_current_day(patched_main_window, qtbot): win, *_ = patched_main_window # Type into the editor and save - win.editor.setPlainText("Test note") + win.editor.setHtml("Test note") win._save_current(explicit=True) # call directly to avoid waiting timers day = win._current_date_iso() - assert win.db.get_entry(day) == "Test note" + assert "Test note" in win.db.get_entry(day) def test_switch_day_saves_previous(patched_main_window, qtbot): @@ -45,13 +45,13 @@ def test_switch_day_saves_previous(patched_main_window, qtbot): # Write on Day 1 d1 = win.calendar.selectedDate() d1_iso = f"{d1.year():04d}-{d1.month():02d}-{d1.day():02d}" - win.editor.setPlainText("Notes day 1") + win.editor.setHtml("Notes day 1") # Trigger a day change (this path calls _on_date_changed via signal) d2 = d1.addDays(1) win.calendar.setSelectedDate(d2) # After changing, previous day should be saved; editor now shows day 2 content (empty) - assert win.db.get_entry(d1_iso) == "Notes day 1" + assert "Notes day 1" in win.db.get_entry(d1_iso) assert win.editor.toPlainText() == "" From 53e99af9128e5b0bf14bd2e1448d05ab53771471 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sat, 1 Nov 2025 17:44:23 +1100 Subject: [PATCH 08/18] Add Search ability --- CHANGELOG.md | 1 + README.md | 9 +- bouquin/db.py | 6 ++ bouquin/editor.py | 2 + bouquin/main.py | 3 +- bouquin/main_window.py | 10 +- bouquin/search.py | 195 +++++++++++++++++++++++++++++++++++++ bouquin/settings_dialog.py | 4 +- bouquin/toolbar.py | 3 + 9 files changed, 224 insertions(+), 9 deletions(-) create mode 100644 bouquin/search.py diff --git a/CHANGELOG.md b/CHANGELOG.md index c54582d..06ef835 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # 0.1.2 * Switch from Markdown to HTML via QTextEdit, with a toolbar + * Add search ability * Fix Settings shortcut and change nav menu from 'File' to 'Application' # 0.1.1 diff --git a/README.md b/README.md index b874668..3307543 100644 --- a/README.md +++ b/README.md @@ -20,16 +20,15 @@ There is deliberately no network connectivity or syncing intended. ## Features * Every 'page' is linked to the calendar day - * Basic markdown + * Text is HTML with basic styling + * Search * Automatic periodic saving (or explicitly save) - * Navigating from one day to the next automatically saves - * Basic keyboard shortcuts * Transparent integrity checking of the database when it opens + * Rekey the database (change the password) ## Yet to do - * Search * Taxonomy/tagging * Export to other formats (plaintext, json, sql etc) @@ -47,7 +46,7 @@ There is deliberately no network connectivity or syncing intended. * Download the whl and run it -### From PyPi +### From PyPi/pip * `pip install bouquin` diff --git a/bouquin/db.py b/bouquin/db.py index 15cc4c9..c75847e 100644 --- a/bouquin/db.py +++ b/bouquin/db.py @@ -100,6 +100,12 @@ class DBManager: ) self.conn.commit() + def search_entries(self, text: str) -> list[str]: + cur = self.conn.cursor() + pattern = f"%{text}%" + cur.execute("SELECT * FROM entries WHERE TRIM(content) LIKE ?", (pattern,)) + return [r for r in cur.fetchall()] + def dates_with_content(self) -> list[str]: cur = self.conn.cursor() cur.execute("SELECT date FROM entries WHERE TRIM(content) <> '';") diff --git a/bouquin/editor.py b/bouquin/editor.py index bf40a6b..7fe55c0 100644 --- a/bouquin/editor.py +++ b/bouquin/editor.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from PySide6.QtGui import ( QColor, QFont, diff --git a/bouquin/main.py b/bouquin/main.py index 9beb4d9..3e5f90b 100644 --- a/bouquin/main.py +++ b/bouquin/main.py @@ -11,5 +11,6 @@ def main(): app = QApplication(sys.argv) app.setApplicationName(APP_NAME) app.setOrganizationName(APP_ORG) - win = MainWindow(); win.show() + win = MainWindow() + win.show() sys.exit(app.exec()) diff --git a/bouquin/main_window.py b/bouquin/main_window.py index 359481e..bceaa8d 100644 --- a/bouquin/main_window.py +++ b/bouquin/main_window.py @@ -22,6 +22,7 @@ from PySide6.QtWidgets import ( from .db import DBManager from .editor import Editor from .key_prompt import KeyPrompt +from .search import Search from .settings import APP_NAME, load_db_config, save_db_config from .settings_dialog import SettingsDialog from .toolbar import ToolBar @@ -44,12 +45,16 @@ class MainWindow(QMainWindow): self.calendar.setGridVisible(True) self.calendar.selectionChanged.connect(self._on_date_changed) + self.search = Search(self.db) + self.search.openDateRequested.connect(self._load_selected_date) + # Lock the calendar to the left panel at the top to stop it stretching # when the main window is resized. left_panel = QWidget() left_layout = QVBoxLayout(left_panel) left_layout.setContentsMargins(8, 8, 8, 8) left_layout.addWidget(self.calendar, alignment=Qt.AlignTop) + left_layout.addWidget(self.search, alignment=Qt.AlignBottom) left_layout.addStretch(1) left_panel.setFixedWidth(self.calendar.sizeHint().width() + 16) @@ -187,8 +192,9 @@ class MainWindow(QMainWindow): d = self.calendar.selectedDate() return f"{d.year():04d}-{d.month():02d}-{d.day():02d}" - def _load_selected_date(self): - date_iso = self._current_date_iso() + def _load_selected_date(self, date_iso=False): + if not date_iso: + date_iso = self._current_date_iso() try: text = self.db.get_entry(date_iso) except Exception as e: diff --git a/bouquin/search.py b/bouquin/search.py new file mode 100644 index 0000000..8177905 --- /dev/null +++ b/bouquin/search.py @@ -0,0 +1,195 @@ +from __future__ import annotations + +import re +from typing import Iterable, Tuple + +from PySide6.QtCore import Qt, Signal +from PySide6.QtGui import QFont, QTextCharFormat, QTextCursor, QTextDocument +from PySide6.QtWidgets import ( + QLabel, + QLineEdit, + QListWidget, + QListWidgetItem, + QHBoxLayout, + QVBoxLayout, + QWidget, +) + +# type: rows are (date_iso, content) +Row = Tuple[str, str] + + +class Search(QWidget): + """Encapsulates the search UI + logic and emits a signal when a result is chosen.""" + + openDateRequested = Signal(str) + + def __init__(self, db, parent: QWidget | None = None): + super().__init__(parent) + self._db = db + + self.search = QLineEdit() + self.search.setPlaceholderText("Search for notes here") + self.search.textChanged.connect(self._search) + + self.results = QListWidget() + self.results.setUniformItemSizes(False) + self.results.setSelectionMode(self.results.SelectionMode.SingleSelection) + self.results.itemClicked.connect(self._open_selected) + self.results.hide() + + lay = QVBoxLayout(self) + lay.setContentsMargins(0, 0, 0, 0) + lay.setSpacing(6) + lay.addWidget(self.search) + lay.addWidget(self.results) + + def _open_selected(self, item: QListWidgetItem): + date_str = item.data(Qt.ItemDataRole.UserRole) + if date_str: + self.openDateRequested.emit(date_str) + + def _search(self, text: str): + """ + Search for the supplied text in the database. + For all rows found, populate the results widget with a clickable preview. + """ + q = text.strip() + if not q: + self.results.clear() + self.results.hide() + return + + try: + rows: Iterable[Row] = self._db.search_entries(q) + except Exception: + # be quiet on DB errors here; caller can surface if desired + rows = [] + + self._populate_results(q, rows) + + def _populate_results(self, query: str, rows: Iterable[Row]): + self.results.clear() + rows = list(rows) + if not rows: + self.results.hide() + return + + self.results.show() + + for date_str, content in rows: + # Build an HTML fragment around the match and whether to show ellipses + frag_html, left_ell, right_ell = self._make_html_snippet( + content, query, radius=60, maxlen=180 + ) + + # ---- Per-item widget: date on top, preview row below (with ellipses) ---- + container = QWidget() + outer = QVBoxLayout(container) + outer.setContentsMargins(8, 6, 8, 6) + outer.setSpacing(2) + + # Date label (plain text) + date_lbl = QLabel(date_str) + date_lbl.setTextFormat(Qt.TextFormat.PlainText) + date_f = date_lbl.font() + date_f.setPointSizeF(date_f.pointSizeF() - 1) + date_lbl.setFont(date_f) + date_lbl.setStyleSheet("color:#666;") + outer.addWidget(date_lbl) + + # Preview row with optional ellipses + row = QWidget() + h = QHBoxLayout(row) + h.setContentsMargins(0, 0, 0, 0) + h.setSpacing(4) + + if left_ell: + left = QLabel("…") + left.setStyleSheet("color:#888;") + h.addWidget(left, 0, Qt.AlignmentFlag.AlignTop) + + preview = QLabel() + preview.setTextFormat(Qt.TextFormat.RichText) + preview.setWordWrap(True) + preview.setOpenExternalLinks(True) # keep links in your HTML clickable + preview.setText( + frag_html + if frag_html + else "(no preview)" + ) + h.addWidget(preview, 1) + + if right_ell: + right = QLabel("…") + right.setStyleSheet("color:#888;") + h.addWidget(right, 0, Qt.AlignmentFlag.AlignBottom) + + outer.addWidget(row) + + # ---- Add to list ---- + item = QListWidgetItem() + item.setData(Qt.ItemDataRole.UserRole, date_str) + item.setSizeHint(container.sizeHint()) + + self.results.addItem(item) + self.results.setItemWidget(item, container) + + # --- Snippet/highlight helpers ----------------------------------------- + def _make_html_snippet(self, html_src: str, query: str, *, radius=60, maxlen=180): + doc = QTextDocument() + doc.setHtml(html_src) + plain = doc.toPlainText() + if not plain: + return "", False, False + + tokens = [t for t in re.split(r"\s+", query.strip()) if t] + L = len(plain) + + # Find first occurrence (phrase first, then earliest token) + idx, mlen = -1, 0 + if tokens: + lower = plain.lower() + phrase = " ".join(tokens).lower() + j = lower.find(phrase) + if j >= 0: + idx, mlen = j, len(phrase) + else: + for t in tokens: + tj = lower.find(t.lower()) + if tj >= 0 and (idx < 0 or tj < idx): + idx, mlen = tj, len(t) + # Compute window + if idx < 0: + start, end = 0, min(L, maxlen) + else: + start = max(0, min(idx - radius, max(0, L - maxlen))) + end = min(L, max(idx + mlen + radius, start + maxlen)) + + # Bold all token matches that fall inside [start, end) + if tokens: + lower = plain.lower() + fmt = QTextCharFormat() + fmt.setFontWeight(QFont.Weight.Bold) + for t in tokens: + t_low = t.lower() + pos = start + while True: + k = lower.find(t_low, pos) + if k == -1 or k >= end: + break + c = QTextCursor(doc) + c.setPosition(k) + c.setPosition(k + len(t), QTextCursor.MoveMode.KeepAnchor) + c.mergeCharFormat(fmt) + pos = k + len(t) + + # Select the window and export as HTML fragment + c = QTextCursor(doc) + c.setPosition(start) + c.setPosition(end, QTextCursor.MoveMode.KeepAnchor) + fragment_html = ( + c.selection().toHtml() + ) # preserves original styles + our bolding + + return fragment_html, start > 0, end < L diff --git a/bouquin/settings_dialog.py b/bouquin/settings_dialog.py index ca2514c..a59e1c6 100644 --- a/bouquin/settings_dialog.py +++ b/bouquin/settings_dialog.py @@ -91,7 +91,9 @@ class SettingsDialog(QDialog): return try: self._db.rekey(new_key) - QMessageBox.information(self, "Key changed", "The database key was updated.") + QMessageBox.information( + self, "Key changed", "The database key was updated." + ) except Exception as e: QMessageBox.critical(self, "Error", f"Could not change key:\n{e}") diff --git a/bouquin/toolbar.py b/bouquin/toolbar.py index 471fd8e..93c7ee3 100644 --- a/bouquin/toolbar.py +++ b/bouquin/toolbar.py @@ -1,7 +1,10 @@ +from __future__ import annotations + from PySide6.QtCore import Signal, Qt from PySide6.QtGui import QFont, QAction from PySide6.QtWidgets import QToolBar + class ToolBar(QToolBar): boldRequested = Signal(QFont.Weight) italicRequested = Signal() From e146a92b315a15e6707cb9f73fb47c0c30128849 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sat, 1 Nov 2025 17:44:58 +1100 Subject: [PATCH 09/18] Bump version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 3d2088d..0e7310f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "bouquin" -version = "0.1.1" +version = "0.1.2" description = "Bouquin is a simple, opinionated notebook application written in Python, PyQt and SQLCipher." authors = ["Miguel Jacq "] readme = "README.md" From ff3f5fcf3a9ef444779d2267285c55e554076b4d Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sat, 1 Nov 2025 17:52:53 +1100 Subject: [PATCH 10/18] Update screenshot --- screenshot.png | Bin 55893 -> 88039 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/screenshot.png b/screenshot.png index a0d47bfedebbec745b4cd48f7edc6bf997c75686..85b8e83a264485f3884b74aca6086d6eb199c287 100644 GIT binary patch literal 88039 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 PNf|$S(oZKw&RY9liCx8c literal 55893 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 From f9d92811dca8e974e4d73bdacd047cb50ccb31a2 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sun, 2 Nov 2025 10:04:34 +1100 Subject: [PATCH 11/18] Remove explicit cipher_compatability PRAGMA --- bouquin/db.py | 1 - 1 file changed, 1 deletion(-) diff --git a/bouquin/db.py b/bouquin/db.py index c75847e..0073903 100644 --- a/bouquin/db.py +++ b/bouquin/db.py @@ -23,7 +23,6 @@ class DBManager: self.conn = sqlite.connect(str(self.cfg.path)) cur = self.conn.cursor() cur.execute(f"PRAGMA key = '{self.cfg.key}';") - cur.execute("PRAGMA cipher_compatibility = 4;") cur.execute("PRAGMA journal_mode = WAL;") self.conn.commit() try: From 39c0cb61da8da6778d3b3cbd6fbab87cd288f194 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sun, 2 Nov 2025 10:15:27 +1100 Subject: [PATCH 12/18] Fix set/unset of bold text --- bouquin/editor.py | 6 ++++-- bouquin/toolbar.py | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/bouquin/editor.py b/bouquin/editor.py index 7fe55c0..a5b7643 100644 --- a/bouquin/editor.py +++ b/bouquin/editor.py @@ -28,9 +28,11 @@ class Editor(QTextEdit): cursor.mergeCharFormat(fmt) self.mergeCurrentCharFormat(fmt) - @Slot(QFont.Weight) - def apply_weight(self, weight): + @Slot() + def apply_weight(self): + cur = self.currentCharFormat() fmt = QTextCharFormat() + weight = QFont.Weight.Normal if cur.fontWeight() == QFont.Weight.Bold else QFont.Weight.Bold fmt.setFontWeight(weight) self.merge_on_sel(fmt) diff --git a/bouquin/toolbar.py b/bouquin/toolbar.py index 93c7ee3..a3a8cef 100644 --- a/bouquin/toolbar.py +++ b/bouquin/toolbar.py @@ -6,7 +6,7 @@ from PySide6.QtWidgets import QToolBar class ToolBar(QToolBar): - boldRequested = Signal(QFont.Weight) + boldRequested = Signal() italicRequested = Signal() underlineRequested = Signal() strikeRequested = Signal() @@ -24,7 +24,7 @@ class ToolBar(QToolBar): # Bold bold = QAction("Bold", self) bold.setShortcut("Ctrl+B") - bold.triggered.connect(lambda: self.boldRequested.emit(QFont.Weight.Bold)) + bold.triggered.connect(self.boldRequested) italic = QAction("Italic", self) italic.setShortcut("Ctrl+I") From baf9b41f44aab3a93ffa14355ca59191331f89cc Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sun, 2 Nov 2025 10:21:42 +1100 Subject: [PATCH 13/18] Reduce size of preview. Fix jumping to the appropriate day in calendar when clicking on a search result --- bouquin/main_window.py | 2 ++ bouquin/search.py | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/bouquin/main_window.py b/bouquin/main_window.py index bceaa8d..5208e7b 100644 --- a/bouquin/main_window.py +++ b/bouquin/main_window.py @@ -206,6 +206,8 @@ class MainWindow(QMainWindow): self._dirty = False # track which date the editor currently represents self._active_date_iso = date_iso + qd = QDate.fromString(date_iso, "yyyy-MM-dd") + self.calendar.setSelectedDate(qd) def _on_text_changed(self): self._dirty = True diff --git a/bouquin/search.py b/bouquin/search.py index 8177905..8cd2fd5 100644 --- a/bouquin/search.py +++ b/bouquin/search.py @@ -80,7 +80,7 @@ class Search(QWidget): for date_str, content in rows: # Build an HTML fragment around the match and whether to show ellipses frag_html, left_ell, right_ell = self._make_html_snippet( - content, query, radius=60, maxlen=180 + content, query, radius=30, maxlen=90 ) # ---- Per-item widget: date on top, preview row below (with ellipses) ---- @@ -112,7 +112,7 @@ class Search(QWidget): preview = QLabel() preview.setTextFormat(Qt.TextFormat.RichText) preview.setWordWrap(True) - preview.setOpenExternalLinks(True) # keep links in your HTML clickable + preview.setOpenExternalLinks(True) preview.setText( frag_html if frag_html From c4f99f9b2b018310cc772c94a8983637543d20ba Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sun, 2 Nov 2025 10:59:43 +1100 Subject: [PATCH 14/18] Remember app window position on screen --- bouquin/main_window.py | 45 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/bouquin/main_window.py b/bouquin/main_window.py index 5208e7b..0e1364f 100644 --- a/bouquin/main_window.py +++ b/bouquin/main_window.py @@ -2,10 +2,12 @@ from __future__ import annotations import sys -from PySide6.QtCore import QDate, QTimer, Qt +from PySide6.QtCore import QDate, QTimer, Qt, QSettings from PySide6.QtGui import ( QAction, + QCursor, QFont, + QGuiApplication, QTextCharFormat, ) from PySide6.QtWidgets import ( @@ -139,6 +141,10 @@ class MainWindow(QMainWindow): self._load_selected_date() self._refresh_calendar_marks() + # Restore window position from settings + self.settings = QSettings(APP_NAME) + self._restore_window_position() + def _try_connect(self) -> bool: """ Try to connect to the database. @@ -283,8 +289,45 @@ class MainWindow(QMainWindow): self._load_selected_date() self._refresh_calendar_marks() + def _restore_window_position(self): + geom = self.settings.value("main/geometry", None) + state = self.settings.value("main/windowState", None) + was_max = self.settings.value("main/maximized", False, type=bool) + + if geom is not None: + self.restoreGeometry(geom) + if state is not None: + self.restoreState(state) + if not self._rect_on_any_screen(self.frameGeometry()): + self._move_to_cursor_screen_center() + else: + # First run: place window on the screen where the mouse cursor is. + self._move_to_cursor_screen_center() + + # If it was maximized, do that AFTER the window exists in the event loop. + if was_max: + QTimer.singleShot(0, self.showMaximized) + + def _rect_on_any_screen(self, rect): + for sc in QGuiApplication.screens(): + if sc.availableGeometry().intersects(rect): + return True + return False + + def _move_to_cursor_screen_center(self): + screen = QGuiApplication.screenAt(QCursor.pos()) or QGuiApplication.primaryScreen() + r = screen.availableGeometry() + # Center the window in that screen’s available area + self.move(r.center() - self.rect().center()) + + def closeEvent(self, event): try: + # Save window position + self.settings.setValue("main/geometry", self.saveGeometry()) + self.settings.setValue("main/windowState", self.saveState()) + self.settings.setValue("main/maximized", self.isMaximized()) + # Ensure we save any last pending edits to the db self._save_current() self.db.close() except Exception: From 327e7882b5021094146988eed1da83d80385ecd0 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sun, 2 Nov 2025 11:00:00 +1100 Subject: [PATCH 15/18] Clickable URL links --- bouquin/editor.py | 130 ++++++++++++++++++++++++++++++++++++- bouquin/toolbar.py | 1 + bouquin/url_highlighter.py | 25 +++++++ 3 files changed, 153 insertions(+), 3 deletions(-) create mode 100644 bouquin/url_highlighter.py diff --git a/bouquin/editor.py b/bouquin/editor.py index a5b7643..1f7e132 100644 --- a/bouquin/editor.py +++ b/bouquin/editor.py @@ -1,23 +1,147 @@ from __future__ import annotations from PySide6.QtGui import ( + QBrush, QColor, + QDesktopServices, QFont, QFontDatabase, QTextCharFormat, + QTextCursor, QTextListFormat, QTextBlockFormat, ) -from PySide6.QtCore import Slot +from PySide6.QtCore import Qt, QUrl, Signal, Slot, QRegularExpression from PySide6.QtWidgets import QTextEdit +from .url_highlighter import UrlHighlighter class Editor(QTextEdit): - def __init__(self): - super().__init__() + linkActivated = Signal(str) + + _URL_RX = QRegularExpression(r"(https?://[^\s<>\"]+|www\.[^\s<>\"]+)") + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) tab_w = 4 * self.fontMetrics().horizontalAdvance(" ") self.setTabStopDistance(tab_w) + self.setTextInteractionFlags( + Qt.TextInteractionFlag.TextEditorInteraction + | Qt.TextInteractionFlag.LinksAccessibleByMouse + | Qt.TextInteractionFlag.LinksAccessibleByKeyboard + ) + + self.setAcceptRichText(True) + + # Turn raw URLs into anchors + self._linkifying = False + self.textChanged.connect(self._linkify_document) + self.viewport().setMouseTracking(True) + + def _linkify_document(self): + if self._linkifying: + return + self._linkifying = True + + doc = self.document() + cur = QTextCursor(doc) + cur.beginEditBlock() + + block = doc.begin() + while block.isValid(): + text = block.text() + it = self._URL_RX.globalMatch(text) + while it.hasNext(): + m = it.next() + start = block.position() + m.capturedStart() + end = start + m.capturedLength() + + cur.setPosition(start) + cur.setPosition(end, QTextCursor.KeepAnchor) + + fmt = cur.charFormat() + if fmt.isAnchor(): # already linkified; skip + continue + + href = m.captured(0) + if href.startswith("www."): + href = "https://" + href + + fmt.setAnchor(True) + # Qt 6: use setAnchorHref; for compatibility, also set names. + try: + fmt.setAnchorHref(href) + except AttributeError: + fmt.setAnchorNames([href]) + + fmt.setFontUnderline(True) + fmt.setForeground(Qt.blue) + cur.setCharFormat(fmt) + + block = block.next() + + cur.endEditBlock() + self._linkifying = False + + def mouseReleaseEvent(self, e): + if e.button() == Qt.LeftButton and (e.modifiers() & Qt.ControlModifier): + href = self.anchorAt(e.pos()) + if href: + QDesktopServices.openUrl(QUrl.fromUserInput(href)) + self.linkActivated.emit(href) + return + super().mouseReleaseEvent(e) + + def mouseMoveEvent(self, e): + if (e.modifiers() & Qt.ControlModifier) and self.anchorAt(e.pos()): + self.viewport().setCursor(Qt.PointingHandCursor) + else: + self.viewport().setCursor(Qt.IBeamCursor) + super().mouseMoveEvent(e) + + def keyPressEvent(self, e): + key = e.key() + + # Pre-insert: stop link/format bleed for “word boundary” keys + if key in (Qt.Key_Space, Qt.Key_Tab): + self._break_anchor_for_next_char() + return super().keyPressEvent(e) + + # When pressing Enter/return key, insert first, then neutralise the empty block’s inline format + if key in (Qt.Key_Return, Qt.Key_Enter): + super().keyPressEvent(e) # create the new (possibly empty) paragraph + + # If we're on an empty block, clear the insertion char format so the + # *next* Enter will create another new line (not consume the press to reset formatting). + c = self.textCursor() + block = c.block() + if block.length() == 1: + self._clear_insertion_char_format() + return + + return super().keyPressEvent(e) + + def _clear_insertion_char_format(self): + """Reset inline typing format (keeps lists, alignment, margins, etc.).""" + nf = QTextCharFormat() + self.setCurrentCharFormat(nf) + + def _break_anchor_for_next_char(self): + c = self.textCursor() + fmt = c.charFormat() + if fmt.isAnchor() or fmt.fontUnderline() or fmt.foreground().style() != 0: + # clone, then strip just the link-specific bits so the next char is plain text + nf = QTextCharFormat(fmt) + nf.setAnchor(False) + nf.setFontUnderline(False) + nf.clearForeground() + try: + nf.setAnchorHref("") + except AttributeError: + nf.setAnchorNames([]) + self.setCurrentCharFormat(nf) + def merge_on_sel(self, fmt): """ Sets the styling on the selected characters. diff --git a/bouquin/toolbar.py b/bouquin/toolbar.py index a3a8cef..0951f20 100644 --- a/bouquin/toolbar.py +++ b/bouquin/toolbar.py @@ -19,6 +19,7 @@ class ToolBar(QToolBar): def __init__(self, parent=None): super().__init__("Format", parent) self._build_actions() + self.setObjectName("Format") def _build_actions(self): # Bold diff --git a/bouquin/url_highlighter.py b/bouquin/url_highlighter.py new file mode 100644 index 0000000..1fd1af6 --- /dev/null +++ b/bouquin/url_highlighter.py @@ -0,0 +1,25 @@ +from __future__ import annotations + +import re +from PySide6.QtGui import QSyntaxHighlighter, QTextCharFormat +from PySide6.QtCore import Qt, QRegularExpression + +class UrlHighlighter(QSyntaxHighlighter): + def __init__(self, doc): + super().__init__(doc) + self.rx = QRegularExpression(r"(https?://[^\s<>\"]+|www\.[^\s<>\"]+)") + + def highlightBlock(self, text: str): + it = self.rx.globalMatch(text) + while it.hasNext(): + m = it.next() + href = m.captured(0) + if href.startswith("www."): + href = "https://" + href + + fmt = QTextCharFormat() + fmt.setAnchor(True) + fmt.setAnchorHref(href) + fmt.setFontUnderline(True) + fmt.setForeground(Qt.blue) + self.setFormat(m.capturedStart(0), m.capturedLength(0), fmt) From 4f773e1c1b8085a1ec88c80de2183e7240470232 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sun, 2 Nov 2025 11:13:52 +1100 Subject: [PATCH 16/18] Detect fresh install and guide the user to set an encryption passphrase so they know why they're prompted --- bouquin/key_prompt.py | 4 ++-- bouquin/main_window.py | 19 ++++++++++++++++--- bouquin/settings_dialog.py | 6 +++--- 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/bouquin/key_prompt.py b/bouquin/key_prompt.py index 1fe8dee..095093c 100644 --- a/bouquin/key_prompt.py +++ b/bouquin/key_prompt.py @@ -14,8 +14,8 @@ class KeyPrompt(QDialog): def __init__( self, parent=None, - title: str = "Unlock database", - message: str = "Enter SQLCipher key", + title: str = "Enter key", + message: str = "Enter key", ): super().__init__(parent) self.setWindowTitle(title) diff --git a/bouquin/main_window.py b/bouquin/main_window.py index 0e1364f..8a03852 100644 --- a/bouquin/main_window.py +++ b/bouquin/main_window.py @@ -1,5 +1,6 @@ from __future__ import annotations +import os import sys from PySide6.QtCore import QDate, QTimer, Qt, QSettings @@ -37,8 +38,14 @@ class MainWindow(QMainWindow): self.setMinimumSize(1000, 650) self.cfg = load_db_config() + if not os.path.exists(self.cfg.path): + # Fresh database/first time use, so guide the user re: setting a key + first_time = True + else: + first_time = False + # Always prompt for the key (we never store it) - if not self._prompt_for_key_until_valid(): + if not self._prompt_for_key_until_valid(first_time): sys.exit(1) # ---- UI: Left fixed panel (calendar) + right editor ----------------- @@ -161,12 +168,18 @@ class MainWindow(QMainWindow): return False return ok - def _prompt_for_key_until_valid(self) -> bool: + def _prompt_for_key_until_valid(self, first_time: bool) -> bool: """ Prompt for the SQLCipher key. """ + if first_time: + title = "Set an encryption key" + message = "Bouquin encrypts your data.\n\nPlease create a strong passphrase to encrypt the notebook.\n\nYou can always change it later!" + else: + title = "Unlock encrypted notebook" + message = "Enter your key to unlock the notebook" while True: - dlg = KeyPrompt(self, message="Enter a key to unlock the notebook") + dlg = KeyPrompt(self, title, message) if dlg.exec() != QDialog.Accepted: return False self.cfg.key = dlg.key() diff --git a/bouquin/settings_dialog.py b/bouquin/settings_dialog.py index a59e1c6..d739630 100644 --- a/bouquin/settings_dialog.py +++ b/bouquin/settings_dialog.py @@ -76,11 +76,11 @@ class SettingsDialog(QDialog): self.accept() def _change_key(self): - p1 = KeyPrompt(self, title="Change key", message="Enter new key") + p1 = KeyPrompt(self, title="Change key", message="Enter a new encryption key") if p1.exec() != QDialog.Accepted: return new_key = p1.key() - p2 = KeyPrompt(self, title="Change key", message="Re-enter new key") + p2 = KeyPrompt(self, title="Change key", message="Re-enter the new key") if p2.exec() != QDialog.Accepted: return if new_key != p2.key(): @@ -92,7 +92,7 @@ class SettingsDialog(QDialog): try: self._db.rekey(new_key) QMessageBox.information( - self, "Key changed", "The database key was updated." + self, "Key changed", "The notebook was re-encrypted with the new key!" ) except Exception as e: QMessageBox.critical(self, "Error", f"Could not change key:\n{e}") From 43bbe971eb855ece25b9e6b3e28307cba8a3b0e3 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sun, 2 Nov 2025 11:44:22 +1100 Subject: [PATCH 17/18] Add ability to save the key to avoid being prompted for it --- README.md | 4 +- bouquin/editor.py | 14 ++++--- bouquin/main_window.py | 18 +++++---- bouquin/settings.py | 4 +- bouquin/settings_dialog.py | 77 +++++++++++++++++++++++++++++++++++--- bouquin/toolbar.py | 2 +- bouquin/url_highlighter.py | 25 ------------- 7 files changed, 98 insertions(+), 46 deletions(-) delete mode 100644 bouquin/url_highlighter.py diff --git a/README.md b/README.md index 3307543..94d0648 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ It uses [SQLCipher bindings](https://pypi.org/project/sqlcipher3-wheels) as a dr for SQLite3. This means that the underlying database for the notebook is encrypted at rest. To increase security, the SQLCipher key is requested when the app is opened, and is not written -to disk. +to disk unless the user configures it to be in the settings. There is deliberately no network connectivity or syncing intended. @@ -35,6 +35,8 @@ There is deliberately no network connectivity or syncing intended. ## How to install +Make sure you have `libxcb-cursor0` installed (it may be called something else on non-Debian distributions). + ### From source * Clone this repo or download the tarball from the releases page diff --git a/bouquin/editor.py b/bouquin/editor.py index 1f7e132..eb3b664 100644 --- a/bouquin/editor.py +++ b/bouquin/editor.py @@ -1,7 +1,6 @@ from __future__ import annotations from PySide6.QtGui import ( - QBrush, QColor, QDesktopServices, QFont, @@ -14,7 +13,6 @@ from PySide6.QtGui import ( from PySide6.QtCore import Qt, QUrl, Signal, Slot, QRegularExpression from PySide6.QtWidgets import QTextEdit -from .url_highlighter import UrlHighlighter class Editor(QTextEdit): linkActivated = Signal(str) @@ -55,13 +53,13 @@ class Editor(QTextEdit): while it.hasNext(): m = it.next() start = block.position() + m.capturedStart() - end = start + m.capturedLength() + end = start + m.capturedLength() cur.setPosition(start) cur.setPosition(end, QTextCursor.KeepAnchor) fmt = cur.charFormat() - if fmt.isAnchor(): # already linkified; skip + if fmt.isAnchor(): # already linkified; skip continue href = m.captured(0) @@ -110,7 +108,7 @@ class Editor(QTextEdit): # When pressing Enter/return key, insert first, then neutralise the empty block’s inline format if key in (Qt.Key_Return, Qt.Key_Enter): - super().keyPressEvent(e) # create the new (possibly empty) paragraph + super().keyPressEvent(e) # create the new (possibly empty) paragraph # If we're on an empty block, clear the insertion char format so the # *next* Enter will create another new line (not consume the press to reset formatting). @@ -156,7 +154,11 @@ class Editor(QTextEdit): def apply_weight(self): cur = self.currentCharFormat() fmt = QTextCharFormat() - weight = QFont.Weight.Normal if cur.fontWeight() == QFont.Weight.Bold else QFont.Weight.Bold + weight = ( + QFont.Weight.Normal + if cur.fontWeight() == QFont.Weight.Bold + else QFont.Weight.Bold + ) fmt.setFontWeight(weight) self.merge_on_sel(fmt) diff --git a/bouquin/main_window.py b/bouquin/main_window.py index 8a03852..0f90197 100644 --- a/bouquin/main_window.py +++ b/bouquin/main_window.py @@ -26,7 +26,7 @@ from .db import DBManager from .editor import Editor from .key_prompt import KeyPrompt from .search import Search -from .settings import APP_NAME, load_db_config, save_db_config +from .settings import APP_ORG, APP_NAME, load_db_config, save_db_config from .settings_dialog import SettingsDialog from .toolbar import ToolBar @@ -44,9 +44,12 @@ class MainWindow(QMainWindow): else: first_time = False - # Always prompt for the key (we never store it) - if not self._prompt_for_key_until_valid(first_time): - sys.exit(1) + # Prompt for the key unless it is found in config + if not self.cfg.key: + if not self._prompt_for_key_until_valid(first_time): + sys.exit(1) + else: + self._try_connect() # ---- UI: Left fixed panel (calendar) + right editor ----------------- self.calendar = QCalendarWidget() @@ -149,7 +152,7 @@ class MainWindow(QMainWindow): self._refresh_calendar_marks() # Restore window position from settings - self.settings = QSettings(APP_NAME) + self.settings = QSettings(APP_ORG, APP_NAME) self._restore_window_position() def _try_connect(self) -> bool: @@ -328,12 +331,13 @@ class MainWindow(QMainWindow): return False def _move_to_cursor_screen_center(self): - screen = QGuiApplication.screenAt(QCursor.pos()) or QGuiApplication.primaryScreen() + screen = ( + QGuiApplication.screenAt(QCursor.pos()) or QGuiApplication.primaryScreen() + ) r = screen.availableGeometry() # Center the window in that screen’s available area self.move(r.center() - self.rect().center()) - def closeEvent(self, event): try: # Save window position diff --git a/bouquin/settings.py b/bouquin/settings.py index 508e12f..ec45094 100644 --- a/bouquin/settings.py +++ b/bouquin/settings.py @@ -21,9 +21,11 @@ def get_settings() -> QSettings: def load_db_config() -> DBConfig: s = get_settings() path = Path(s.value("db/path", str(default_db_path()))) - return DBConfig(path=path, key="") + key = s.value("db/key", "") + return DBConfig(path=path, key=key) def save_db_config(cfg: DBConfig) -> None: s = get_settings() s.setValue("db/path", str(cfg.path)) + s.setValue("db/key", str(cfg.key)) diff --git a/bouquin/settings_dialog.py b/bouquin/settings_dialog.py index d739630..70ae8f6 100644 --- a/bouquin/settings_dialog.py +++ b/bouquin/settings_dialog.py @@ -3,8 +3,12 @@ from __future__ import annotations from pathlib import Path from PySide6.QtWidgets import ( + QCheckBox, QDialog, QFormLayout, + QFrame, + QGroupBox, + QLabel, QHBoxLayout, QVBoxLayout, QWidget, @@ -15,9 +19,12 @@ from PySide6.QtWidgets import ( QSizePolicy, QMessageBox, ) +from PySide6.QtCore import Qt, Slot +from PySide6.QtGui import QPalette + from .db import DBConfig, DBManager -from .settings import save_db_config +from .settings import load_db_config, save_db_config from .key_prompt import KeyPrompt @@ -27,10 +34,11 @@ class SettingsDialog(QDialog): self.setWindowTitle("Settings") self._cfg = DBConfig(path=cfg.path, key="") self._db = db + self.key = "" form = QFormLayout() form.setFieldGrowthPolicy(QFormLayout.ExpandingFieldsGrow) - self.setMinimumWidth(520) + self.setMinimumWidth(560) self.setSizeGripEnabled(True) self.path_edit = QLineEdit(str(self._cfg.path)) @@ -47,18 +55,65 @@ class SettingsDialog(QDialog): h.setStretch(1, 0) form.addRow("Database path", path_row) + # Encryption settings + enc_group = QGroupBox("Encryption") + enc = QVBoxLayout(enc_group) + enc.setContentsMargins(12, 8, 12, 12) + enc.setSpacing(6) + + # Checkbox to remember key + self.save_key_btn = QCheckBox("Remember key") + current_settings = load_db_config() + if current_settings.key: + self.save_key_btn.setChecked(True) + else: + self.save_key_btn.setChecked(False) + self.save_key_btn.setCursor(Qt.PointingHandCursor) + self.save_key_btn.toggled.connect(self.save_key_btn_clicked) + enc.addWidget(self.save_key_btn, 0, Qt.AlignLeft) + + # Explanation for remembering key + self.save_key_label = QLabel( + "If you don't want to be prompted for your encryption key, check this to remember it. " + "WARNING: the key is saved to disk and could be recoverable if your disk is compromised." + ) + self.save_key_label.setWordWrap(True) + self.save_key_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum) + # make it look secondary + pal = self.save_key_label.palette() + pal.setColor(self.save_key_label.foregroundRole(), pal.color(QPalette.Mid)) + self.save_key_label.setPalette(pal) + + exp_row = QHBoxLayout() + exp_row.setContentsMargins(24, 0, 0, 0) # indent to line up under the checkbox + exp_row.addWidget(self.save_key_label) + enc.addLayout(exp_row) + + line = QFrame() + line.setFrameShape(QFrame.HLine) + line.setFrameShadow(QFrame.Sunken) + enc.addWidget(line) + # Change key button self.rekey_btn = QPushButton("Change key") + self.rekey_btn.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.rekey_btn.clicked.connect(self._change_key) + enc.addWidget(self.rekey_btn, 0, Qt.AlignLeft) + # Put the group into the form so it spans the full width nicely + form.addRow(enc_group) + + # Buttons bb = QDialogButtonBox(QDialogButtonBox.Save | QDialogButtonBox.Cancel) bb.accepted.connect(self._save) bb.rejected.connect(self.reject) + # Root layout (adjust margins/spacing a bit) v = QVBoxLayout(self) + v.setContentsMargins(12, 12, 12, 12) + v.setSpacing(10) v.addLayout(form) - v.addWidget(self.rekey_btn) - v.addWidget(bb) + v.addWidget(bb, 0, Qt.AlignRight) def _browse(self): p, _ = QFileDialog.getSaveFileName( @@ -71,7 +126,7 @@ class SettingsDialog(QDialog): self.path_edit.setText(p) def _save(self): - self._cfg = DBConfig(path=Path(self.path_edit.text()), key="") + self._cfg = DBConfig(path=Path(self.path_edit.text()), key=self.key) save_db_config(self._cfg) self.accept() @@ -97,6 +152,18 @@ class SettingsDialog(QDialog): except Exception as e: QMessageBox.critical(self, "Error", f"Could not change key:\n{e}") + @Slot(bool) + def save_key_btn_clicked(self, checked: bool): + if checked: + p1 = KeyPrompt( + self, title="Enter your key", message="Enter the encryption key" + ) + if p1.exec() != QDialog.Accepted: + return + self.key = p1.key() + self._cfg = DBConfig(path=Path(self.path_edit.text()), key=self.key) + save_db_config(self._cfg) + @property def config(self) -> DBConfig: return self._cfg diff --git a/bouquin/toolbar.py b/bouquin/toolbar.py index 0951f20..c796ac8 100644 --- a/bouquin/toolbar.py +++ b/bouquin/toolbar.py @@ -1,7 +1,7 @@ from __future__ import annotations from PySide6.QtCore import Signal, Qt -from PySide6.QtGui import QFont, QAction +from PySide6.QtGui import QAction from PySide6.QtWidgets import QToolBar diff --git a/bouquin/url_highlighter.py b/bouquin/url_highlighter.py deleted file mode 100644 index 1fd1af6..0000000 --- a/bouquin/url_highlighter.py +++ /dev/null @@ -1,25 +0,0 @@ -from __future__ import annotations - -import re -from PySide6.QtGui import QSyntaxHighlighter, QTextCharFormat -from PySide6.QtCore import Qt, QRegularExpression - -class UrlHighlighter(QSyntaxHighlighter): - def __init__(self, doc): - super().__init__(doc) - self.rx = QRegularExpression(r"(https?://[^\s<>\"]+|www\.[^\s<>\"]+)") - - def highlightBlock(self, text: str): - it = self.rx.globalMatch(text) - while it.hasNext(): - m = it.next() - href = m.captured(0) - if href.startswith("www."): - href = "https://" + href - - fmt = QTextCharFormat() - fmt.setAnchor(True) - fmt.setAnchorHref(href) - fmt.setFontUnderline(True) - fmt.setForeground(Qt.blue) - self.setFormat(m.capturedStart(0), m.capturedLength(0), fmt) From 6cae652643a18627a747753ae6534e19225f077c Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sun, 2 Nov 2025 11:56:28 +1100 Subject: [PATCH 18/18] Abbreviated toolbar symbols and add tooltips --- CHANGELOG.md | 9 +++ bouquin/toolbar.py | 169 +++++++++++++++++++++++++++++---------------- 2 files changed, 118 insertions(+), 60 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 06ef835..b45483f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +# 0.1.3 + + * Fix bold toggle + * Improvements to preview size in search results + * Make URLs highlighted and clickable (Ctrl+click) + * Explain the purpose of the encryption key for first-time use + * Support saving the encryption key to the settings file to avoid being prompted (off by default) + * Abbreviated toolbar symbols to keep things tidier. Add tooltips + # 0.1.2 * Switch from Markdown to HTML via QTextEdit, with a toolbar diff --git a/bouquin/toolbar.py b/bouquin/toolbar.py index c796ac8..182b527 100644 --- a/bouquin/toolbar.py +++ b/bouquin/toolbar.py @@ -1,7 +1,7 @@ from __future__ import annotations from PySide6.QtCore import Signal, Qt -from PySide6.QtGui import QAction +from PySide6.QtGui import QAction, QKeySequence, QFont, QFontDatabase from PySide6.QtWidgets import QToolBar @@ -18,82 +18,131 @@ class ToolBar(QToolBar): def __init__(self, parent=None): super().__init__("Format", parent) - self._build_actions() self.setObjectName("Format") + self.setToolButtonStyle(Qt.ToolButtonTextOnly) + self._build_actions() + self._apply_toolbar_styles() def _build_actions(self): - # Bold - bold = QAction("Bold", self) - bold.setShortcut("Ctrl+B") - bold.triggered.connect(self.boldRequested) + self.actBold = QAction("Bold", self) + self.actBold.setShortcut(QKeySequence.Bold) + self.actBold.triggered.connect(self.boldRequested) - italic = QAction("Italic", self) - italic.setShortcut("Ctrl+I") - italic.triggered.connect(self.italicRequested) + self.actItalic = QAction("Italic", self) + self.actItalic.setShortcut(QKeySequence.Italic) + self.actItalic.triggered.connect(self.italicRequested) - underline = QAction("Underline", self) - underline.setShortcut("Ctrl+U") - underline.triggered.connect(self.underlineRequested) + self.actUnderline = QAction("Underline", self) + self.actUnderline.setShortcut(QKeySequence.Underline) + self.actUnderline.triggered.connect(self.underlineRequested) - strike = QAction("Strikethrough", self) - strike.setShortcut("Ctrl+-") - strike.triggered.connect(self.strikeRequested) + self.actStrike = QAction("Strikethrough", self) + self.actStrike.setShortcut("Ctrl+-") + self.actStrike.triggered.connect(self.strikeRequested) - code = QAction("", self) - code.setShortcut("Ctrl+`") - code.triggered.connect(self.codeRequested) + self.actCode = QAction("Inline code", self) + self.actCode.setShortcut("Ctrl+`") + self.actCode.triggered.connect(self.codeRequested) # Headings - h1 = QAction("H1", self) - h1.setShortcut("Ctrl+1") - h2 = QAction("H2", self) - h2.setShortcut("Ctrl+2") - h3 = QAction("H3", self) - h3.setShortcut("Ctrl+3") - normal = QAction("Normal", self) - normal.setShortcut("Ctrl+P") - - h1.triggered.connect(lambda: self.headingRequested.emit(24)) - h2.triggered.connect(lambda: self.headingRequested.emit(18)) - h3.triggered.connect(lambda: self.headingRequested.emit(14)) - normal.triggered.connect(lambda: self.headingRequested.emit(0)) + self.actH1 = QAction("Heading 1", self) + self.actH2 = QAction("Heading 2", self) + self.actH3 = QAction("Heading 3", self) + self.actNormal = QAction("Normal text", self) + self.actH1.setShortcut("Ctrl+1") + self.actH2.setShortcut("Ctrl+2") + self.actH3.setShortcut("Ctrl+3") + self.actNormal.setShortcut("Ctrl+N") + self.actH1.triggered.connect(lambda: self.headingRequested.emit(24)) + self.actH2.triggered.connect(lambda: self.headingRequested.emit(18)) + self.actH3.triggered.connect(lambda: self.headingRequested.emit(14)) + self.actNormal.triggered.connect(lambda: self.headingRequested.emit(0)) # Lists - bullets = QAction("• Bullets", self) - bullets.triggered.connect(self.bulletsRequested) - numbers = QAction("1. Numbered", self) - numbers.triggered.connect(self.numbersRequested) + self.actBullets = QAction("Bulleted list", self) + self.actBullets.triggered.connect(self.bulletsRequested) + self.actNumbers = QAction("Numbered list", self) + self.actNumbers.triggered.connect(self.numbersRequested) # Alignment - left = QAction("Align Left", self) - center = QAction("Align Center", self) - right = QAction("Align Right", self) - - left.triggered.connect( - lambda: self.alignRequested.emit(Qt.AlignmentFlag.AlignLeft) + self.actAlignL = QAction("Align left", self) + self.actAlignC = QAction("Align center", self) + self.actAlignR = QAction("Align right", self) + self.actAlignL.triggered.connect(lambda: self.alignRequested.emit(Qt.AlignLeft)) + self.actAlignC.triggered.connect( + lambda: self.alignRequested.emit(Qt.AlignHCenter) ) - center.triggered.connect( - lambda: self.alignRequested.emit(Qt.AlignmentFlag.AlignHCenter) - ) - right.triggered.connect( - lambda: self.alignRequested.emit(Qt.AlignmentFlag.AlignRight) + self.actAlignR.triggered.connect( + lambda: self.alignRequested.emit(Qt.AlignRight) ) self.addActions( [ - bold, - italic, - underline, - strike, - code, - h1, - h2, - h3, - normal, - bullets, - numbers, - left, - center, - right, + self.actBold, + self.actItalic, + self.actUnderline, + self.actStrike, + self.actCode, + self.actH1, + self.actH2, + self.actH3, + self.actNormal, + self.actBullets, + self.actNumbers, + self.actAlignL, + self.actAlignC, + self.actAlignR, ] ) + + def _apply_toolbar_styles(self): + self._style_letter_button(self.actBold, "B", bold=True) + self._style_letter_button(self.actItalic, "I", italic=True) + self._style_letter_button(self.actUnderline, "U", underline=True) + self._style_letter_button(self.actStrike, "S", strike=True) + + # Monospace look for code; use a fixed font + code_font = QFontDatabase.systemFont(QFontDatabase.FixedFont) + self._style_letter_button(self.actCode, "", custom_font=code_font) + + # Headings + self._style_letter_button(self.actH1, "H1") + self._style_letter_button(self.actH2, "H2") + self._style_letter_button(self.actH3, "H3") + self._style_letter_button(self.actNormal, "N") + + # Lists + self._style_letter_button(self.actBullets, "•") + self._style_letter_button(self.actNumbers, "1.") + + # Alignment + self._style_letter_button(self.actAlignL, "L") + self._style_letter_button(self.actAlignC, "C") + self._style_letter_button(self.actAlignR, "R") + + def _style_letter_button( + self, + action: QAction, + text: str, + *, + bold: bool = False, + italic: bool = False, + underline: bool = False, + strike: bool = False, + custom_font: QFont | None = None, + ): + btn = self.widgetForAction(action) + if not btn: + return + btn.setText(text) + f = custom_font if custom_font is not None else QFont(btn.font()) + if custom_font is None: + f.setBold(bold) + f.setItalic(italic) + f.setUnderline(underline) + f.setStrikeOut(strike) + btn.setFont(f) + + # Keep accessibility/tooltip readable + btn.setToolTip(action.text()) + btn.setAccessibleName(action.text())