From 0caf0efeef8163cf1b3a29af7f5ebd2d17426066 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Fri, 31 Oct 2025 16:34:20 +1100 Subject: [PATCH 1/4] 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 2/4] 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 3/4] 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 4/4] 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"