Compare commits
7 commits
9435800910
...
81cf878ffd
| Author | SHA1 | Date | |
|---|---|---|---|
| 81cf878ffd | |||
| 73aa536a32 | |||
| 219b358569 | |||
| 5a54d809ed | |||
| 917e51aa0b | |||
| 576dc435ef | |||
| fdc72a1146 |
11 changed files with 108 additions and 67 deletions
|
|
@ -1,3 +1,10 @@
|
|||
# 0.5.2
|
||||
|
||||
* Update icon again to remove background
|
||||
* Adjust History icon and reorder toolbar items
|
||||
* Try to address checkbox/bullet size issues (again)
|
||||
* Fix HTML export of markdown (with newlines, tables and other styling preserved)
|
||||
|
||||
# 0.5.1
|
||||
|
||||
* Try to address Noto Sans font issue that works for both numbers and checkbox/bullets.
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
## Introduction
|
||||
|
||||
Bouquin ("Book-ahn") is a notebook and planner application written in Python, PyQt and SQLCipher.
|
||||
Bouquin ("Book-ahn") is a notebook and planner application written in Python, Qt and SQLCipher.
|
||||
|
||||
It is designed to treat each day as its own 'page', complete with Markdown rendering, tagging,
|
||||
search, reminders and time logging for those of us who need to keep track of not just TODOs, but
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import datetime as _dt
|
|||
import hashlib
|
||||
import html
|
||||
import json
|
||||
import markdown
|
||||
import re
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
|
@ -440,14 +441,33 @@ class DBManager:
|
|||
'<html lang="en">',
|
||||
'<meta charset="utf-8">',
|
||||
f"<title>{html.escape(title)}</title>",
|
||||
"<style>body{font:16px/1.5 system-ui,Segoe UI,Roboto,Helvetica,Arial,sans-serif;padding:24px;max-width:900px;margin:auto;}",
|
||||
"article{padding:16px 0;border-bottom:1px solid #ddd;} time{font-weight:600;color:#333;} section{margin-top:8px;}</style>",
|
||||
"<style>"
|
||||
"body{font:16px/1.5 system-ui,Segoe UI,Roboto,Helvetica,Arial,sans-serif;"
|
||||
"padding:24px;max-width:900px;margin:auto;}"
|
||||
"article{padding:16px 0;border-bottom:1px solid #ddd;}"
|
||||
"article header time{font-weight:600;color:#333;}"
|
||||
"section{margin-top:8px;}"
|
||||
"table{border-collapse:collapse;margin-top:8px;}"
|
||||
"th,td{border:1px solid #ddd;padding:4px 8px;text-align:left;}"
|
||||
"</style>",
|
||||
"<body>",
|
||||
f"<h1>{html.escape(title)}</h1>",
|
||||
]
|
||||
for d, c in entries:
|
||||
body_html = markdown.markdown(
|
||||
c,
|
||||
extensions=[
|
||||
"extra",
|
||||
"nl2br",
|
||||
],
|
||||
output_format="html5",
|
||||
)
|
||||
|
||||
parts.append(
|
||||
f"<article><header><time>{html.escape(d)}</time></header><section>{c}</section></article>"
|
||||
f"<article>"
|
||||
f"<header><time>{html.escape(d)}</time></header>"
|
||||
f"<section>{body_html}</section>"
|
||||
f"</article>"
|
||||
)
|
||||
parts.append("</body></html>")
|
||||
|
||||
|
|
|
|||
|
|
@ -4,21 +4,10 @@
|
|||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<!-- Rounded square background tile -->
|
||||
<rect
|
||||
x="0"
|
||||
y="0"
|
||||
width="512"
|
||||
height="512"
|
||||
rx="112"
|
||||
ry="112"
|
||||
fill="#0F172A"
|
||||
/>
|
||||
|
||||
<!-- Book cover -->
|
||||
<rect
|
||||
x="116"
|
||||
y="92"
|
||||
y="76"
|
||||
width="280"
|
||||
height="360"
|
||||
rx="48"
|
||||
|
|
@ -29,7 +18,7 @@
|
|||
<!-- Book spine -->
|
||||
<rect
|
||||
x="116"
|
||||
y="92"
|
||||
y="76"
|
||||
width="64"
|
||||
height="360"
|
||||
rx="40"
|
||||
|
|
@ -39,14 +28,14 @@
|
|||
|
||||
<!-- Folded page corner (top-right triangle) -->
|
||||
<path
|
||||
d="M396 92 L356 92 L396 132 Z"
|
||||
d="M396 76 L356 76 L396 116 Z"
|
||||
fill="#FEF9C3"
|
||||
/>
|
||||
|
||||
<!-- Keyhole: circular top -->
|
||||
<circle
|
||||
cx="256"
|
||||
cy="260"
|
||||
cy="256"
|
||||
r="34"
|
||||
fill="#0F172A"
|
||||
/>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 887 B After Width: | Height: | Size: 733 B |
|
|
@ -119,6 +119,22 @@ class MarkdownEditor(QTextEdit):
|
|||
self._apply_code_block_spacing()
|
||||
QTimer.singleShot(0, self._update_code_block_row_backgrounds)
|
||||
|
||||
def setFont(self, font: QFont) -> None: # type: ignore[override]
|
||||
"""
|
||||
Ensure that whenever the base editor font changes, our highlighter
|
||||
re-computes checkbox / bullet formats.
|
||||
"""
|
||||
# Keep qfont in sync
|
||||
self.qfont = QFont(font)
|
||||
super().setFont(self.qfont)
|
||||
|
||||
# If the highlighter is already attached, let it rebuild its formats
|
||||
highlighter = getattr(self, "highlighter", None)
|
||||
if highlighter is not None:
|
||||
refresh = getattr(highlighter, "refresh_for_font_change", None)
|
||||
if callable(refresh):
|
||||
refresh()
|
||||
|
||||
def showEvent(self, e):
|
||||
super().showEvent(e)
|
||||
# First time the widget is shown, Qt may rebuild layout once more.
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ from PySide6.QtGui import (
|
|||
QColor,
|
||||
QFont,
|
||||
QFontDatabase,
|
||||
QFontMetrics,
|
||||
QGuiApplication,
|
||||
QPalette,
|
||||
QSyntaxHighlighter,
|
||||
|
|
@ -33,6 +34,14 @@ class MarkdownHighlighter(QSyntaxHighlighter):
|
|||
self._setup_formats()
|
||||
self.rehighlight()
|
||||
|
||||
def refresh_for_font_change(self) -> None:
|
||||
"""
|
||||
Called when the editor's base font changes (zoom / settings).
|
||||
It rebuilds any formats that depend on the editor font metrics.
|
||||
"""
|
||||
self._setup_formats()
|
||||
self.rehighlight()
|
||||
|
||||
def _setup_formats(self):
|
||||
"""Setup text formats for different markdown elements."""
|
||||
|
||||
|
|
@ -110,8 +119,21 @@ class MarkdownHighlighter(QSyntaxHighlighter):
|
|||
|
||||
# Use Symbols font for checkbox and bullet glyphs if present
|
||||
if self._editor is not None and hasattr(self._editor, "symbols_font_family"):
|
||||
base_size = self._editor.qfont.pointSize()
|
||||
symbols_font = QFont(self._editor.symbols_font_family, base_size)
|
||||
base_font = QFont(self._editor.qfont) # copy of editor font
|
||||
symbols_font = QFont(self._editor.symbols_font_family)
|
||||
symbols_font.setPointSizeF(base_font.pointSizeF())
|
||||
|
||||
base_metrics = QFontMetrics(base_font)
|
||||
sym_metrics = QFontMetrics(symbols_font)
|
||||
|
||||
# If Symbols glyphs are noticeably shorter than the text,
|
||||
# scale them up so the visual heights roughly match.
|
||||
if sym_metrics.height() > 0:
|
||||
ratio = base_metrics.height() / sym_metrics.height()
|
||||
if ratio > 1.05: # more than ~5% smaller
|
||||
ratio = min(ratio, 1.4) # Oh, Tod, Tod. Don't overdo it.
|
||||
symbols_font.setPointSizeF(symbols_font.pointSizeF() * ratio)
|
||||
|
||||
self.checkbox_format.setFont(symbols_font)
|
||||
self.bullet_format.setFont(symbols_font)
|
||||
|
||||
|
|
|
|||
|
|
@ -191,7 +191,7 @@ class UpcomingRemindersWidget(QFrame):
|
|||
self.toggle_btn.clicked.connect(self._on_toggle)
|
||||
|
||||
self.add_btn = QToolButton()
|
||||
self.add_btn.setIcon(self.style().standardIcon(QStyle.SP_FileDialogNewFolder))
|
||||
self.add_btn.setText("⏰")
|
||||
self.add_btn.setToolTip("Add Reminder")
|
||||
self.add_btn.setAutoRaise(True)
|
||||
self.add_btn.clicked.connect(self._add_reminder)
|
||||
|
|
|
|||
|
|
@ -100,6 +100,17 @@ class ToolBar(QToolBar):
|
|||
self.actCheckboxes.setToolTip(strings._("toolbar_toggle_checkboxes"))
|
||||
self.actCheckboxes.triggered.connect(self.checkboxesRequested)
|
||||
|
||||
# Images
|
||||
self.actInsertImg = QAction("📸", self)
|
||||
self.actInsertImg.setToolTip(strings._("insert_images"))
|
||||
self.actInsertImg.setShortcut("Ctrl+Shift+I")
|
||||
self.actInsertImg.triggered.connect(self.insertImageRequested)
|
||||
|
||||
# History button
|
||||
self.actHistory = QAction("🔁", self)
|
||||
self.actHistory.setToolTip(strings._("history"))
|
||||
self.actHistory.triggered.connect(self.historyRequested)
|
||||
|
||||
# Alarm / reminder
|
||||
self.actAlarm = QAction("⏰", self)
|
||||
self.actAlarm.setToolTip(strings._("toolbar_alarm"))
|
||||
|
|
@ -115,17 +126,6 @@ class ToolBar(QToolBar):
|
|||
self.actTable.setToolTip(strings._("toolbar_insert_table"))
|
||||
self.actTable.triggered.connect(self.tableRequested)
|
||||
|
||||
# Images
|
||||
self.actInsertImg = QAction("📸", self)
|
||||
self.actInsertImg.setToolTip(strings._("insert_images"))
|
||||
self.actInsertImg.setShortcut("Ctrl+Shift+I")
|
||||
self.actInsertImg.triggered.connect(self.insertImageRequested)
|
||||
|
||||
# History button
|
||||
self.actHistory = QAction("⎌", self)
|
||||
self.actHistory.setToolTip(strings._("history"))
|
||||
self.actHistory.triggered.connect(self.historyRequested)
|
||||
|
||||
# Set exclusive buttons in QActionGroups
|
||||
self.grpHeadings = QActionGroup(self)
|
||||
self.grpHeadings.setExclusive(True)
|
||||
|
|
@ -162,10 +162,10 @@ class ToolBar(QToolBar):
|
|||
self.actBullets,
|
||||
self.actNumbers,
|
||||
self.actCheckboxes,
|
||||
self.actAlarm,
|
||||
self.actTimer,
|
||||
self.actTable,
|
||||
self.actInsertImg,
|
||||
self.actAlarm,
|
||||
self.actTimer,
|
||||
self.actHistory,
|
||||
]
|
||||
)
|
||||
|
|
@ -195,7 +195,7 @@ class ToolBar(QToolBar):
|
|||
self._style_letter_button(self.actTable, "⊞")
|
||||
|
||||
# History
|
||||
self._style_letter_button(self.actHistory, "⎌")
|
||||
self._style_letter_button(self.actHistory, "🔁")
|
||||
|
||||
def _style_letter_button(
|
||||
self,
|
||||
|
|
|
|||
17
poetry.lock
generated
17
poetry.lock
generated
|
|
@ -307,6 +307,21 @@ files = [
|
|||
{file = "iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "markdown"
|
||||
version = "3.10"
|
||||
description = "Python implementation of John Gruber's Markdown."
|
||||
optional = false
|
||||
python-versions = ">=3.10"
|
||||
files = [
|
||||
{file = "markdown-3.10-py3-none-any.whl", hash = "sha256:b5b99d6951e2e4948d939255596523444c0e677c669700b1d17aa4a8a464cb7c"},
|
||||
{file = "markdown-3.10.tar.gz", hash = "sha256:37062d4f2aa4b2b6b32aefb80faa300f82cc790cb949a35b8caede34f2b68c0e"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
docs = ["mdx_gh_links (>=0.2)", "mkdocs (>=1.6)", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-nature (>=0.6)", "mkdocs-section-index", "mkdocstrings[python]"]
|
||||
testing = ["coverage", "pyyaml"]
|
||||
|
||||
[[package]]
|
||||
name = "packaging"
|
||||
version = "25.0"
|
||||
|
|
@ -744,4 +759,4 @@ zstd = ["zstandard (>=0.18.0)"]
|
|||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = ">=3.10,<3.14"
|
||||
content-hash = "d5fd8ea759b6bd3f23336930bdce9241659256ed918ec31746787cc86e817235"
|
||||
content-hash = "86db2b4d6498ce9eba7fa9aedf9ce58fec6a63542b5f3bdb3cf6c4067e5b87df"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[tool.poetry]
|
||||
name = "bouquin"
|
||||
version = "0.5.1"
|
||||
version = "0.5.2"
|
||||
description = "Bouquin is a simple, opinionated notebook application written in Python, PyQt and SQLCipher."
|
||||
authors = ["Miguel Jacq <mig@mig5.net>"]
|
||||
readme = "README.md"
|
||||
|
|
@ -14,6 +14,7 @@ python = ">=3.10,<3.14"
|
|||
pyside6 = ">=6.8.1,<7.0.0"
|
||||
sqlcipher3-wheels = "^0.5.5.post0"
|
||||
requests = "^2.32.5"
|
||||
markdown = "^3.10"
|
||||
|
||||
[tool.poetry.scripts]
|
||||
bouquin = "bouquin.__main__:main"
|
||||
|
|
|
|||
|
|
@ -765,35 +765,6 @@ def test_reminder_not_today_skipped(qtbot, fresh_db):
|
|||
assert len(triggered_texts) == 0
|
||||
|
||||
|
||||
def test_reminder_context_menu_single_item(qtbot, fresh_db):
|
||||
"""Test context menu for a single reminder item."""
|
||||
reminder = Reminder(
|
||||
id=None,
|
||||
text="Test reminder",
|
||||
reminder_type=ReminderType.ONCE,
|
||||
time_str="14:30",
|
||||
date_iso=date.today().isoformat(),
|
||||
active=True,
|
||||
)
|
||||
fresh_db.save_reminder(reminder)
|
||||
|
||||
reminders_widget = UpcomingRemindersWidget(fresh_db)
|
||||
qtbot.addWidget(reminders_widget)
|
||||
reminders_widget.show()
|
||||
|
||||
# Refresh to populate the list
|
||||
reminders_widget.refresh()
|
||||
|
||||
# Select the first item
|
||||
if reminders_widget.reminder_list.count() > 0:
|
||||
reminders_widget.reminder_list.setCurrentRow(0)
|
||||
|
||||
# Show context menu (won't actually display in tests)
|
||||
reminders_widget._show_reminder_context_menu(
|
||||
reminders_widget.reminder_list.pos()
|
||||
)
|
||||
|
||||
|
||||
def test_reminder_context_menu_no_selection(qtbot, fresh_db):
|
||||
"""Test context menu with no selection returns early."""
|
||||
reminders_widget = UpcomingRemindersWidget(fresh_db)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue