diff --git a/CHANGELOG.md b/CHANGELOG.md index 5966eb0..c560986 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,3 @@ -# 0.1.10 - - * Improve search results window and highlight in calendar when there are matches. - * Fix styling issue with text that comes after a URL, so it doesn't appear as part of the URL. - # 0.1.9 * More styling/toolbar fixes to support toggled-on styles, exclusive styles (e.g it should not be diff --git a/bouquin/editor.py b/bouquin/editor.py index b7fc341..07ef6d3 100644 --- a/bouquin/editor.py +++ b/bouquin/editor.py @@ -349,7 +349,7 @@ class Editor(QTextEdit): if source.hasImage(): img = self._to_qimage(source.imageData()) if img is not None: - self._insert_qimage_at_cursor(img, autoscale=True) + self._insert_qimage_at_cursor(self, img, autoscale=True) return # 2) File URLs (drag/drop or paste) @@ -496,21 +496,12 @@ class Editor(QTextEdit): cur_fmt = self.textCursor().charFormat() # Do nothing unless either side indicates we're in/propagating an anchor - if not ( - ins_fmt.isAnchor() - or cur_fmt.isAnchor() - or ins_fmt.fontUnderline() - or ins_fmt.foreground().style() != Qt.NoBrush - ): + if not (ins_fmt.isAnchor() or cur_fmt.isAnchor()): return nf = QTextCharFormat(ins_fmt) - # stop the link itself nf.setAnchor(False) nf.setAnchorHref("") - # also stop the link *styling* - nf.setFontUnderline(False) - nf.clearForeground() self.setCurrentCharFormat(nf) diff --git a/bouquin/history_dialog.py b/bouquin/history_dialog.py index fee2a4f..5ee404c 100644 --- a/bouquin/history_dialog.py +++ b/bouquin/history_dialog.py @@ -165,10 +165,12 @@ class HistoryDialog(QDialog): sel_id = item.data(Qt.UserRole) if sel_id == self._current_id: return + sel = self._db.get_version(version_id=sel_id) + vno = sel["version_no"] # Flip head pointer try: self._db.revert_to_version(self._date, version_id=sel_id) except Exception as e: QMessageBox.critical(self, "Revert failed", str(e)) return - self.accept() + self.accept() # let the caller refresh the editor diff --git a/bouquin/main_window.py b/bouquin/main_window.py index 693456b..1428384 100644 --- a/bouquin/main_window.py +++ b/bouquin/main_window.py @@ -17,12 +17,11 @@ from PySide6.QtCore import ( ) from PySide6.QtGui import ( QAction, - QBrush, - QColor, QCursor, QDesktopServices, QFont, QGuiApplication, + QTextCharFormat, QTextListFormat, ) from PySide6.QtWidgets import ( @@ -133,15 +132,15 @@ class MainWindow(QMainWindow): self.search = Search(self.db) self.search.openDateRequested.connect(self._load_selected_date) - self.search.resultDatesChanged.connect(self._on_search_dates_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) - left_layout.addWidget(self.calendar) - left_layout.addWidget(self.search) + 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) # This is the note-taking editor @@ -314,44 +313,22 @@ class MainWindow(QMainWindow): if self._try_connect(): return True - def _on_search_dates_changed(self, date_strs: list[str]): - dates = set() - for ds in date_strs or []: - qd = QDate.fromString(ds, "yyyy-MM-dd") - if qd.isValid(): - dates.add(qd) - self._apply_search_highlights(dates) - - def _apply_search_highlights(self, dates: set): - yellow = QBrush(QColor("#fff9c4")) - old = getattr(self, "_search_highlighted_dates", set()) - - for d in old - dates: # clear removed - fmt = self.calendar.dateTextFormat(d) - fmt.setBackground(Qt.transparent) - self.calendar.setDateTextFormat(d, fmt) - - for d in dates: # apply new/current - fmt = self.calendar.dateTextFormat(d) - fmt.setBackground(yellow) - self.calendar.setDateTextFormat(d, fmt) - - self._search_highlighted_dates = dates - def _refresh_calendar_marks(self): - """Make days with entries bold, but keep any search highlight backgrounds.""" + """ + 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 for d in getattr(self, "_marked_dates", set()): - fmt = self.calendar.dateTextFormat(d) - fmt.setFontWeight(QFont.Weight.Normal) # remove bold only - self.calendar.setDateTextFormat(d, fmt) + self.calendar.setDateTextFormat(d, QTextCharFormat()) self._marked_dates = set() try: for date_iso in self.db.dates_with_content(): qd = QDate.fromString(date_iso, "yyyy-MM-dd") if qd.isValid(): - fmt = self.calendar.dateTextFormat(qd) - fmt.setFontWeight(QFont.Weight.Bold) # add bold only - self.calendar.setDateTextFormat(qd, fmt) + self.calendar.setDateTextFormat(qd, fmt_bold) self._marked_dates.add(qd) except Exception: pass diff --git a/bouquin/search.py b/bouquin/search.py index 27c7e17..8cd2fd5 100644 --- a/bouquin/search.py +++ b/bouquin/search.py @@ -6,12 +6,10 @@ from typing import Iterable, Tuple from PySide6.QtCore import Qt, Signal from PySide6.QtGui import QFont, QTextCharFormat, QTextCursor, QTextDocument from PySide6.QtWidgets import ( - QFrame, QLabel, QLineEdit, QListWidget, QListWidgetItem, - QSizePolicy, QHBoxLayout, QVBoxLayout, QWidget, @@ -25,7 +23,6 @@ class Search(QWidget): """Encapsulates the search UI + logic and emits a signal when a result is chosen.""" openDateRequested = Signal(str) - resultDatesChanged = Signal(list) def __init__(self, db, parent: QWidget | None = None): super().__init__(parent) @@ -33,21 +30,17 @@ class Search(QWidget): self.search = QLineEdit() self.search.setPlaceholderText("Search for notes here") - self.search.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed) self.search.textChanged.connect(self._search) self.results = QListWidget() self.results.setUniformItemSizes(False) self.results.setSelectionMode(self.results.SelectionMode.SingleSelection) - self.results.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding) self.results.itemClicked.connect(self._open_selected) self.results.hide() - self.results.setMinimumHeight(250) lay = QVBoxLayout(self) lay.setContentsMargins(0, 0, 0, 0) lay.setSpacing(6) - lay.setAlignment(Qt.AlignTop) lay.addWidget(self.search) lay.addWidget(self.results) @@ -65,7 +58,6 @@ class Search(QWidget): if not q: self.results.clear() self.results.hide() - self.resultDatesChanged.emit([]) # clear highlights return try: @@ -81,10 +73,8 @@ class Search(QWidget): rows = list(rows) if not rows: self.results.hide() - self.resultDatesChanged.emit([]) # clear highlights return - self.resultDatesChanged.emit(sorted({d for d, _ in rows})) self.results.show() for date_str, content in rows: @@ -100,13 +90,12 @@ class Search(QWidget): outer.setSpacing(2) # Date label (plain text) - date_lbl = QLabel() - date_lbl.setTextFormat(Qt.TextFormat.RichText) - date_lbl.setText(f"{date_str}:") + date_lbl = QLabel(date_str) + date_lbl.setTextFormat(Qt.TextFormat.PlainText) date_f = date_lbl.font() - date_f.setPointSizeF(date_f.pointSizeF() + 1) + date_f.setPointSizeF(date_f.pointSizeF() - 1) date_lbl.setFont(date_f) - date_lbl.setStyleSheet("color:#000;") + date_lbl.setStyleSheet("color:#666;") outer.addWidget(date_lbl) # Preview row with optional ellipses @@ -138,11 +127,6 @@ class Search(QWidget): outer.addWidget(row) - line = QFrame() - line.setFrameShape(QFrame.HLine) - line.setFrameShadow(QFrame.Sunken) - outer.addWidget(line) - # ---- Add to list ---- item = QListWidgetItem() item.setData(Qt.ItemDataRole.UserRole, date_str) diff --git a/tests/test_editor.py b/tests/test_editor.py index 6935143..cd5855d 100644 --- a/tests/test_editor.py +++ b/tests/test_editor.py @@ -4,8 +4,6 @@ from PySide6.QtTest import QTest from bouquin.editor import Editor -import re - def _move_cursor_to_first_image(editor: Editor) -> QTextImageFormat | None: c = editor.textCursor() @@ -23,57 +21,6 @@ def _move_cursor_to_first_image(editor: Editor) -> QTextImageFormat | None: return None -def _fmt_at(editor: Editor, pos: int): - c = editor.textCursor() - c.setPosition(pos) - c.movePosition(QTextCursor.Right, QTextCursor.KeepAnchor, 1) - return c.charFormat() - - -def test_space_breaks_link_anchor_and_styling(qtbot): - e = Editor() - e.resize(600, 300) - e.show() - qtbot.waitExposed(e) - - # Type a URL, which should be linkified (anchor + underline + blue) - url = "https://mig5.net" - QTest.keyClicks(e, url) - qtbot.waitUntil(lambda: e.toPlainText() == url) - - # Sanity: characters within the URL are anchors - for i in range(len(url)): - assert _fmt_at(e, i).isAnchor() - - # Hit Space – Editor.keyPressEvent() should call _break_anchor_for_next_char() - QTest.keyClick(e, Qt.Key_Space) - - # Type some normal text; it must not inherit the link formatting - tail = "this is a test" - QTest.keyClicks(e, tail) - qtbot.waitUntil(lambda: e.toPlainText().endswith(tail)) - - txt = e.toPlainText() - # Find where our 'tail' starts - start = txt.index(tail) - end = start + len(tail) - - # None of the trailing characters should be part of an anchor or visually underlined - for i in range(start, end): - fmt = _fmt_at(e, i) - assert not fmt.isAnchor(), f"char {i} unexpectedly still has an anchor" - assert not fmt.fontUnderline(), f"char {i} unexpectedly still underlined" - - # Optional: ensure the HTML only wraps the URL in , not the trailing text - html = e.document().toHtml() - assert re.search( - r']*href="https?://mig5\.net"[^>]*>(?:]*>)?https?://mig5\.net(?:)?\s+this is a test', - html, - re.S, - ), html - assert "this is a test" not in html - - def test_embed_qimage_saved_as_data_url(qtbot): e = Editor() e.resize(600, 400)