Support checkboxes, and TODO shortcut
This commit is contained in:
parent
7c3ec19748
commit
773afa5464
5 changed files with 323 additions and 79 deletions
|
|
@ -5,6 +5,7 @@
|
||||||
* Add ability to export to Markdown (and fix heading styles)
|
* Add ability to export to Markdown (and fix heading styles)
|
||||||
* Represent in the History diff pane when an image was the thing that changed
|
* Represent in the History diff pane when an image was the thing that changed
|
||||||
* Support theme choice in settings (light/dark/system)
|
* Support theme choice in settings (light/dark/system)
|
||||||
|
* Add Checkboxes in the editor. Typing 'TODO' at the start of a line will auto-convert into a checkbox.
|
||||||
|
|
||||||
# 0.1.9
|
# 0.1.9
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,11 @@ class Editor(QTextEdit):
|
||||||
_HEADING_SIZES = (24.0, 18.0, 14.0)
|
_HEADING_SIZES = (24.0, 18.0, 14.0)
|
||||||
_IMAGE_EXTS = (".png", ".jpg", ".jpeg", ".bmp", ".gif", ".webp")
|
_IMAGE_EXTS = (".png", ".jpg", ".jpeg", ".bmp", ".gif", ".webp")
|
||||||
_DATA_IMG_RX = re.compile(r'src=["\']data:image/[^;]+;base64,([^"\']+)["\']', re.I)
|
_DATA_IMG_RX = re.compile(r'src=["\']data:image/[^;]+;base64,([^"\']+)["\']', re.I)
|
||||||
|
# --- Checkbox hack --- #
|
||||||
|
_CHECK_UNCHECKED = "\u2610" # ☐
|
||||||
|
_CHECK_CHECKED = "\u2611" # ☑
|
||||||
|
_CHECK_RX = re.compile(r"^\s*([\u2610\u2611])\s") # ☐/☑ plus a space
|
||||||
|
_CHECKBOX_SCALE = 1.35
|
||||||
|
|
||||||
def __init__(self, theme_manager: ThemeManager, *args, **kwargs):
|
def __init__(self, theme_manager: ThemeManager, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
@ -451,11 +456,52 @@ class Editor(QTextEdit):
|
||||||
self.viewport().setCursor(Qt.IBeamCursor)
|
self.viewport().setCursor(Qt.IBeamCursor)
|
||||||
super().mouseMoveEvent(e)
|
super().mouseMoveEvent(e)
|
||||||
|
|
||||||
|
def mousePressEvent(self, e):
|
||||||
|
if e.button() == Qt.LeftButton and not (e.modifiers() & Qt.ControlModifier):
|
||||||
|
cur = self.cursorForPosition(e.pos())
|
||||||
|
b = cur.block()
|
||||||
|
state, pref = self._checkbox_info_for_block(b)
|
||||||
|
if state is not None:
|
||||||
|
col = cur.position() - b.position()
|
||||||
|
if col <= max(1, pref): # clicked on ☐/☑ (and the following space)
|
||||||
|
self._set_block_checkbox_state(b, not state)
|
||||||
|
return
|
||||||
|
return super().mousePressEvent(e)
|
||||||
|
|
||||||
def keyPressEvent(self, e):
|
def keyPressEvent(self, e):
|
||||||
key = e.key()
|
key = e.key()
|
||||||
|
|
||||||
# Pre-insert: stop link/format bleed for “word boundary” keys
|
|
||||||
if key in (Qt.Key_Space, Qt.Key_Tab):
|
if key in (Qt.Key_Space, Qt.Key_Tab):
|
||||||
|
c = self.textCursor()
|
||||||
|
b = c.block()
|
||||||
|
pos_in_block = c.position() - b.position()
|
||||||
|
|
||||||
|
if (
|
||||||
|
pos_in_block >= 4
|
||||||
|
and b.text().startswith("TODO")
|
||||||
|
and b.text()[:pos_in_block] == "TODO"
|
||||||
|
and self._checkbox_info_for_block(b)[0] is None
|
||||||
|
):
|
||||||
|
tcur = QTextCursor(self.document())
|
||||||
|
tcur.setPosition(b.position()) # start of block
|
||||||
|
tcur.setPosition(
|
||||||
|
b.position() + 4, QTextCursor.KeepAnchor
|
||||||
|
) # select "TODO"
|
||||||
|
tcur.beginEditBlock()
|
||||||
|
tcur.removeSelectedText()
|
||||||
|
tcur.insertText(self._CHECK_UNCHECKED + " ") # insert "☐ "
|
||||||
|
tcur.endEditBlock()
|
||||||
|
|
||||||
|
# visuals: size bump
|
||||||
|
if hasattr(self, "_style_checkbox_glyph"):
|
||||||
|
self._style_checkbox_glyph(b)
|
||||||
|
|
||||||
|
# caret after the inserted prefix; swallow the key (we already added a space)
|
||||||
|
c.setPosition(b.position() + 2)
|
||||||
|
self.setTextCursor(c)
|
||||||
|
return
|
||||||
|
|
||||||
|
# not a TODO-at-start case
|
||||||
self._break_anchor_for_next_char()
|
self._break_anchor_for_next_char()
|
||||||
return super().keyPressEvent(e)
|
return super().keyPressEvent(e)
|
||||||
|
|
||||||
|
|
@ -472,6 +518,26 @@ class Editor(QTextEdit):
|
||||||
super().insertPlainText("\n") # start a normal paragraph
|
super().insertPlainText("\n") # start a normal paragraph
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# --- CHECKBOX handling: continue on Enter; "escape" on second Enter ---
|
||||||
|
b = c.block()
|
||||||
|
state, pref = self._checkbox_info_for_block(b)
|
||||||
|
if state is not None and not c.hasSelection():
|
||||||
|
text_after = b.text()[pref:].strip()
|
||||||
|
if c.atBlockEnd() and text_after == "":
|
||||||
|
# Empty checkbox item -> remove the prefix and insert a plain new line
|
||||||
|
cur = QTextCursor(self.document())
|
||||||
|
cur.setPosition(b.position())
|
||||||
|
cur.movePosition(QTextCursor.Right, QTextCursor.KeepAnchor, pref)
|
||||||
|
cur.removeSelectedText()
|
||||||
|
return super().keyPressEvent(e)
|
||||||
|
else:
|
||||||
|
# Normal continuation: new checkbox on the next line
|
||||||
|
super().keyPressEvent(e) # make the new block
|
||||||
|
super().insertPlainText(self._CHECK_UNCHECKED + " ")
|
||||||
|
if hasattr(self, "_style_checkbox_glyph"):
|
||||||
|
self._style_checkbox_glyph(self.textCursor().block())
|
||||||
|
return
|
||||||
|
|
||||||
# Follow-on style: if we typed a heading and press Enter at end of block,
|
# Follow-on style: if we typed a heading and press Enter at end of block,
|
||||||
# new paragraph should revert to Normal.
|
# new paragraph should revert to Normal.
|
||||||
if not c.hasSelection() and c.atBlockEnd() and self._is_heading_typing():
|
if not c.hasSelection() and c.atBlockEnd() and self._is_heading_typing():
|
||||||
|
|
@ -520,6 +586,125 @@ class Editor(QTextEdit):
|
||||||
cursor.mergeCharFormat(fmt)
|
cursor.mergeCharFormat(fmt)
|
||||||
self.mergeCurrentCharFormat(fmt)
|
self.mergeCurrentCharFormat(fmt)
|
||||||
|
|
||||||
|
# ====== Checkbox core ======
|
||||||
|
def _base_point_size_for_block(self, block) -> float:
|
||||||
|
# Try the block’s char format, then editor font
|
||||||
|
sz = block.charFormat().fontPointSize()
|
||||||
|
if sz <= 0:
|
||||||
|
sz = self.fontPointSize()
|
||||||
|
if sz <= 0:
|
||||||
|
sz = self.font().pointSizeF() or 12.0
|
||||||
|
return float(sz)
|
||||||
|
|
||||||
|
def _style_checkbox_glyph(self, block):
|
||||||
|
"""Apply larger size (and optional symbol font) to the single ☐/☑ char."""
|
||||||
|
state, _ = self._checkbox_info_for_block(block)
|
||||||
|
if state is None:
|
||||||
|
return
|
||||||
|
doc = self.document()
|
||||||
|
c = QTextCursor(doc)
|
||||||
|
c.setPosition(block.position())
|
||||||
|
c.movePosition(QTextCursor.Right, QTextCursor.KeepAnchor, 1) # select ☐/☑ only
|
||||||
|
|
||||||
|
base = self._base_point_size_for_block(block)
|
||||||
|
fmt = QTextCharFormat()
|
||||||
|
fmt.setFontPointSize(base * self._CHECKBOX_SCALE)
|
||||||
|
# keep the glyph centered on the text baseline
|
||||||
|
fmt.setVerticalAlignment(QTextCharFormat.AlignMiddle)
|
||||||
|
|
||||||
|
c.mergeCharFormat(fmt)
|
||||||
|
|
||||||
|
def _checkbox_info_for_block(self, block):
|
||||||
|
"""Return (state, prefix_len): state in {None, False, True}, prefix_len in chars."""
|
||||||
|
text = block.text()
|
||||||
|
m = self._CHECK_RX.match(text)
|
||||||
|
if not m:
|
||||||
|
return None, 0
|
||||||
|
ch = m.group(1)
|
||||||
|
state = True if ch == self._CHECK_CHECKED else False
|
||||||
|
return state, m.end()
|
||||||
|
|
||||||
|
def _set_block_checkbox_present(self, block, present: bool):
|
||||||
|
state, pref = self._checkbox_info_for_block(block)
|
||||||
|
doc = self.document()
|
||||||
|
c = QTextCursor(doc)
|
||||||
|
c.setPosition(block.position())
|
||||||
|
c.beginEditBlock()
|
||||||
|
try:
|
||||||
|
if present and state is None:
|
||||||
|
c.insertText(self._CHECK_UNCHECKED + " ")
|
||||||
|
state = False
|
||||||
|
self._style_checkbox_glyph(block)
|
||||||
|
else:
|
||||||
|
if state is not None:
|
||||||
|
c.movePosition(QTextCursor.Right, QTextCursor.KeepAnchor, pref)
|
||||||
|
c.removeSelectedText()
|
||||||
|
state = None
|
||||||
|
finally:
|
||||||
|
c.endEditBlock()
|
||||||
|
|
||||||
|
return state
|
||||||
|
|
||||||
|
def _set_block_checkbox_state(self, block, checked: bool):
|
||||||
|
"""Switch ☐/☑ at the start of the block."""
|
||||||
|
state, pref = self._checkbox_info_for_block(block)
|
||||||
|
if state is None:
|
||||||
|
return
|
||||||
|
doc = self.document()
|
||||||
|
c = QTextCursor(doc)
|
||||||
|
c.setPosition(block.position())
|
||||||
|
c.movePosition(QTextCursor.Right, QTextCursor.KeepAnchor, 1) # just the symbol
|
||||||
|
c.beginEditBlock()
|
||||||
|
try:
|
||||||
|
c.removeSelectedText()
|
||||||
|
c.insertText(self._CHECK_CHECKED if checked else self._CHECK_UNCHECKED)
|
||||||
|
self._style_checkbox_glyph(block)
|
||||||
|
finally:
|
||||||
|
c.endEditBlock()
|
||||||
|
|
||||||
|
# Public API used by toolbar
|
||||||
|
def toggle_checkboxes(self):
|
||||||
|
"""
|
||||||
|
Toggle checkbox prefix on/off for the current block(s).
|
||||||
|
If all targeted blocks already have a checkbox, remove them; otherwise add.
|
||||||
|
"""
|
||||||
|
c = self.textCursor()
|
||||||
|
doc = self.document()
|
||||||
|
|
||||||
|
if c.hasSelection():
|
||||||
|
start = doc.findBlock(c.selectionStart())
|
||||||
|
end = doc.findBlock(c.selectionEnd() - 1)
|
||||||
|
else:
|
||||||
|
start = end = c.block()
|
||||||
|
|
||||||
|
# Decide intent: add or remove?
|
||||||
|
b = start
|
||||||
|
all_have = True
|
||||||
|
while True:
|
||||||
|
state, _ = self._checkbox_info_for_block(b)
|
||||||
|
if state is None:
|
||||||
|
all_have = False
|
||||||
|
break
|
||||||
|
if b == end:
|
||||||
|
break
|
||||||
|
b = b.next()
|
||||||
|
|
||||||
|
# Apply
|
||||||
|
b = start
|
||||||
|
while True:
|
||||||
|
self._set_block_checkbox_present(b, present=not all_have)
|
||||||
|
if b == end:
|
||||||
|
break
|
||||||
|
b = b.next()
|
||||||
|
|
||||||
|
def toggle_current_checkbox_state(self):
|
||||||
|
"""Tick/untick the current line if it starts with a checkbox."""
|
||||||
|
b = self.textCursor().block()
|
||||||
|
state, _ = self._checkbox_info_for_block(b)
|
||||||
|
if state is None:
|
||||||
|
return
|
||||||
|
self._set_block_checkbox_state(b, not state)
|
||||||
|
|
||||||
@Slot()
|
@Slot()
|
||||||
def apply_weight(self):
|
def apply_weight(self):
|
||||||
cur = self.currentCharFormat()
|
cur = self.currentCharFormat()
|
||||||
|
|
|
||||||
127
bouquin/lock_overlay.py
Normal file
127
bouquin/lock_overlay.py
Normal file
|
|
@ -0,0 +1,127 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from PySide6.QtCore import Qt, QEvent
|
||||||
|
from PySide6.QtGui import QPalette
|
||||||
|
from PySide6.QtWidgets import QWidget, QVBoxLayout, QLabel, QPushButton
|
||||||
|
|
||||||
|
|
||||||
|
class LockOverlay(QWidget):
|
||||||
|
def __init__(self, parent: QWidget, on_unlock: callable):
|
||||||
|
super().__init__(parent)
|
||||||
|
self.setObjectName("LockOverlay")
|
||||||
|
self.setAttribute(Qt.WA_StyledBackground, True)
|
||||||
|
self.setFocusPolicy(Qt.StrongFocus)
|
||||||
|
self.setGeometry(parent.rect())
|
||||||
|
|
||||||
|
self._styling = False # <-- reentrancy guard
|
||||||
|
self._last_dark: bool | None = None
|
||||||
|
|
||||||
|
lay = QVBoxLayout(self)
|
||||||
|
lay.addStretch(1)
|
||||||
|
|
||||||
|
msg = QLabel("Locked due to inactivity", self)
|
||||||
|
msg.setObjectName("lockLabel")
|
||||||
|
msg.setAlignment(Qt.AlignCenter)
|
||||||
|
|
||||||
|
self._btn = QPushButton("Unlock", self)
|
||||||
|
self._btn.setObjectName("unlockButton")
|
||||||
|
self._btn.setFixedWidth(200)
|
||||||
|
self._btn.setCursor(Qt.PointingHandCursor)
|
||||||
|
self._btn.setAutoDefault(True)
|
||||||
|
self._btn.setDefault(True)
|
||||||
|
self._btn.clicked.connect(on_unlock)
|
||||||
|
|
||||||
|
lay.addWidget(msg, 0, Qt.AlignCenter)
|
||||||
|
lay.addWidget(self._btn, 0, Qt.AlignCenter)
|
||||||
|
lay.addStretch(1)
|
||||||
|
|
||||||
|
self._apply_overlay_style()
|
||||||
|
self.hide()
|
||||||
|
|
||||||
|
def _is_dark(self, pal: QPalette) -> bool:
|
||||||
|
c = pal.color(QPalette.Window)
|
||||||
|
luma = 0.2126 * c.redF() + 0.7152 * c.greenF() + 0.0722 * c.blueF()
|
||||||
|
return luma < 0.5
|
||||||
|
|
||||||
|
def _apply_overlay_style(self):
|
||||||
|
if self._styling:
|
||||||
|
return
|
||||||
|
dark = self._is_dark(self.palette())
|
||||||
|
if dark == self._last_dark:
|
||||||
|
return
|
||||||
|
self._styling = True
|
||||||
|
try:
|
||||||
|
if dark:
|
||||||
|
link = self.palette().color(QPalette.Link)
|
||||||
|
accent_hex = link.name() # e.g. "#FFA500"
|
||||||
|
r, g, b = link.red(), link.green(), link.blue()
|
||||||
|
|
||||||
|
self.setStyleSheet(
|
||||||
|
f"""
|
||||||
|
#LockOverlay {{ background-color: rgb(0,0,0); }} /* opaque, no transparency */
|
||||||
|
#LockOverlay QLabel#lockLabel {{ color: {accent_hex}; font-weight: 600; }}
|
||||||
|
|
||||||
|
#LockOverlay QPushButton#unlockButton {{
|
||||||
|
color: {accent_hex};
|
||||||
|
background-color: rgba({r},{g},{b},0.10);
|
||||||
|
border: 1px solid {accent_hex};
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 8px 16px;
|
||||||
|
}}
|
||||||
|
#LockOverlay QPushButton#unlockButton:hover {{
|
||||||
|
background-color: rgba({r},{g},{b},0.16);
|
||||||
|
border-color: {accent_hex};
|
||||||
|
}}
|
||||||
|
#LockOverlay QPushButton#unlockButton:pressed {{
|
||||||
|
background-color: rgba({r},{g},{b},0.24);
|
||||||
|
}}
|
||||||
|
#LockOverlay QPushButton#unlockButton:focus {{
|
||||||
|
outline: none;
|
||||||
|
border-color: {accent_hex};
|
||||||
|
}}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# (light mode unchanged)
|
||||||
|
self.setStyleSheet(
|
||||||
|
"""
|
||||||
|
#LockOverlay { background-color: rgba(0,0,0,120); }
|
||||||
|
#LockOverlay QLabel#lockLabel { color: palette(window-text); font-weight: 600; }
|
||||||
|
#LockOverlay QPushButton#unlockButton {
|
||||||
|
color: palette(button-text);
|
||||||
|
background-color: rgba(255,255,255,0.92);
|
||||||
|
border: 1px solid rgba(0,0,0,0.25);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 8px 16px;
|
||||||
|
}
|
||||||
|
#LockOverlay QPushButton#unlockButton:hover {
|
||||||
|
background-color: rgba(255,255,255,1.0);
|
||||||
|
border-color: rgba(0,0,0,0.35);
|
||||||
|
}
|
||||||
|
#LockOverlay QPushButton#unlockButton:pressed {
|
||||||
|
background-color: rgba(245,245,245,1.0);
|
||||||
|
}
|
||||||
|
#LockOverlay QPushButton#unlockButton:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: palette(highlight);
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
self._last_dark = dark
|
||||||
|
finally:
|
||||||
|
self._styling = False
|
||||||
|
|
||||||
|
def changeEvent(self, ev):
|
||||||
|
super().changeEvent(ev)
|
||||||
|
# Only re-style on palette flips
|
||||||
|
if ev.type() in (QEvent.PaletteChange, QEvent.ApplicationPaletteChange):
|
||||||
|
self._apply_overlay_style()
|
||||||
|
|
||||||
|
def eventFilter(self, obj, event):
|
||||||
|
if obj is self.parent() and event.type() in (QEvent.Resize, QEvent.Show):
|
||||||
|
self.setGeometry(obj.rect())
|
||||||
|
return False
|
||||||
|
|
||||||
|
def showEvent(self, e):
|
||||||
|
super().showEvent(e)
|
||||||
|
self._btn.setFocus()
|
||||||
|
|
@ -32,10 +32,8 @@ from PySide6.QtWidgets import (
|
||||||
QCalendarWidget,
|
QCalendarWidget,
|
||||||
QDialog,
|
QDialog,
|
||||||
QFileDialog,
|
QFileDialog,
|
||||||
QLabel,
|
|
||||||
QMainWindow,
|
QMainWindow,
|
||||||
QMessageBox,
|
QMessageBox,
|
||||||
QPushButton,
|
|
||||||
QSizePolicy,
|
QSizePolicy,
|
||||||
QSplitter,
|
QSplitter,
|
||||||
QVBoxLayout,
|
QVBoxLayout,
|
||||||
|
|
@ -46,6 +44,7 @@ from .db import DBManager
|
||||||
from .editor import Editor
|
from .editor import Editor
|
||||||
from .history_dialog import HistoryDialog
|
from .history_dialog import HistoryDialog
|
||||||
from .key_prompt import KeyPrompt
|
from .key_prompt import KeyPrompt
|
||||||
|
from .lock_overlay import LockOverlay
|
||||||
from .save_dialog import SaveDialog
|
from .save_dialog import SaveDialog
|
||||||
from .search import Search
|
from .search import Search
|
||||||
from .settings import APP_ORG, APP_NAME, load_db_config, save_db_config
|
from .settings import APP_ORG, APP_NAME, load_db_config, save_db_config
|
||||||
|
|
@ -54,78 +53,6 @@ from .toolbar import ToolBar
|
||||||
from .theme import Theme, ThemeManager
|
from .theme import Theme, ThemeManager
|
||||||
|
|
||||||
|
|
||||||
class _LockOverlay(QWidget):
|
|
||||||
def __init__(self, parent: QWidget, on_unlock: callable):
|
|
||||||
super().__init__(parent)
|
|
||||||
self.setObjectName("LockOverlay")
|
|
||||||
self.setAttribute(Qt.WA_StyledBackground, True)
|
|
||||||
self.setFocusPolicy(Qt.StrongFocus)
|
|
||||||
self.setGeometry(parent.rect())
|
|
||||||
|
|
||||||
lay = QVBoxLayout(self)
|
|
||||||
lay.addStretch(1)
|
|
||||||
|
|
||||||
msg = QLabel("Locked due to inactivity")
|
|
||||||
msg.setAlignment(Qt.AlignCenter)
|
|
||||||
|
|
||||||
self._btn = QPushButton("Unlock")
|
|
||||||
self._btn.setFixedWidth(200)
|
|
||||||
self._btn.setCursor(Qt.PointingHandCursor)
|
|
||||||
self._btn.setAutoDefault(True)
|
|
||||||
self._btn.setDefault(True)
|
|
||||||
self._btn.clicked.connect(on_unlock)
|
|
||||||
|
|
||||||
lay.addWidget(msg, 0, Qt.AlignCenter)
|
|
||||||
lay.addWidget(self._btn, 0, Qt.AlignCenter)
|
|
||||||
lay.addStretch(1)
|
|
||||||
|
|
||||||
self._apply_overlay_style()
|
|
||||||
|
|
||||||
self.hide() # start hidden
|
|
||||||
|
|
||||||
def _apply_overlay_style(self):
|
|
||||||
pal = self.palette()
|
|
||||||
bg = (
|
|
||||||
pal.window().color().darker(180)
|
|
||||||
if pal.color(QPalette.Window).value() < 128
|
|
||||||
else pal.window().color().lighter(110)
|
|
||||||
)
|
|
||||||
text = pal.windowText().color()
|
|
||||||
btn_bg = pal.button().color()
|
|
||||||
btn_fg = pal.buttonText().color()
|
|
||||||
border = pal.mid().color()
|
|
||||||
|
|
||||||
hover_bg = btn_bg.lighter(106) # +6%
|
|
||||||
press_bg = btn_bg.darker(106) # -6%
|
|
||||||
|
|
||||||
self.setStyleSheet(
|
|
||||||
f"""
|
|
||||||
#LockOverlay {{ background-color: {bg.name()}; }}
|
|
||||||
#LockOverlay QLabel {{ color: {text.name()}; font-size: 18px; }}
|
|
||||||
#LockOverlay QPushButton {{
|
|
||||||
background-color: {btn_bg.name()};
|
|
||||||
color: {btn_fg.name()};
|
|
||||||
padding: 6px 14px;
|
|
||||||
border: 1px solid {border.name()};
|
|
||||||
border-radius: 6px;
|
|
||||||
font-size: 14px;
|
|
||||||
}}
|
|
||||||
#LockOverlay QPushButton:hover {{ background-color: {hover_bg.name()}; }}
|
|
||||||
#LockOverlay QPushButton:pressed {{ background-color: {press_bg.name()}; }}
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
|
|
||||||
# keep overlay sized with its parent
|
|
||||||
def eventFilter(self, obj, event):
|
|
||||||
if obj is self.parent() and event.type() in (QEvent.Resize, QEvent.Show):
|
|
||||||
self.setGeometry(obj.rect())
|
|
||||||
return False
|
|
||||||
|
|
||||||
def showEvent(self, e):
|
|
||||||
super().showEvent(e)
|
|
||||||
self._btn.setFocus()
|
|
||||||
|
|
||||||
|
|
||||||
class MainWindow(QMainWindow):
|
class MainWindow(QMainWindow):
|
||||||
def __init__(self, themes: ThemeManager, *args, **kwargs):
|
def __init__(self, themes: ThemeManager, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
@ -182,6 +109,7 @@ class MainWindow(QMainWindow):
|
||||||
self.toolBar.headingRequested.connect(self.editor.apply_heading)
|
self.toolBar.headingRequested.connect(self.editor.apply_heading)
|
||||||
self.toolBar.bulletsRequested.connect(self.editor.toggle_bullets)
|
self.toolBar.bulletsRequested.connect(self.editor.toggle_bullets)
|
||||||
self.toolBar.numbersRequested.connect(self.editor.toggle_numbers)
|
self.toolBar.numbersRequested.connect(self.editor.toggle_numbers)
|
||||||
|
self.toolBar.checkboxesRequested.connect(self.editor.toggle_checkboxes)
|
||||||
self.toolBar.alignRequested.connect(self.editor.setAlignment)
|
self.toolBar.alignRequested.connect(self.editor.setAlignment)
|
||||||
self.toolBar.historyRequested.connect(self._open_history)
|
self.toolBar.historyRequested.connect(self._open_history)
|
||||||
self.toolBar.insertImageRequested.connect(self._on_insert_image)
|
self.toolBar.insertImageRequested.connect(self._on_insert_image)
|
||||||
|
|
@ -207,8 +135,7 @@ class MainWindow(QMainWindow):
|
||||||
self._idle_timer.start()
|
self._idle_timer.start()
|
||||||
|
|
||||||
# full-window overlay that sits on top of the central widget
|
# full-window overlay that sits on top of the central widget
|
||||||
self._lock_overlay = _LockOverlay(self.centralWidget(), self._on_unlock_clicked)
|
self._lock_overlay = LockOverlay(self.centralWidget(), self._on_unlock_clicked)
|
||||||
self._lock_overlay._apply_overlay_style()
|
|
||||||
self.centralWidget().installEventFilter(self._lock_overlay)
|
self.centralWidget().installEventFilter(self._lock_overlay)
|
||||||
|
|
||||||
self._locked = False
|
self._locked = False
|
||||||
|
|
@ -375,7 +302,6 @@ class MainWindow(QMainWindow):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Apply to the search widget (if it's also a rich-text widget)
|
|
||||||
self.search.document().setDefaultStyleSheet(css)
|
self.search.document().setDefaultStyleSheet(css)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
@ -562,7 +488,7 @@ class MainWindow(QMainWindow):
|
||||||
|
|
||||||
def _on_text_changed(self):
|
def _on_text_changed(self):
|
||||||
self._dirty = True
|
self._dirty = True
|
||||||
self._save_timer.start(10000) # autosave after idle
|
self._save_timer.start(5000) # autosave after idle
|
||||||
|
|
||||||
def _adjust_day(self, delta: int):
|
def _adjust_day(self, delta: int):
|
||||||
"""Move selection by delta days (negative for previous)."""
|
"""Move selection by delta days (negative for previous)."""
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ class ToolBar(QToolBar):
|
||||||
headingRequested = Signal(int)
|
headingRequested = Signal(int)
|
||||||
bulletsRequested = Signal()
|
bulletsRequested = Signal()
|
||||||
numbersRequested = Signal()
|
numbersRequested = Signal()
|
||||||
|
checkboxesRequested = Signal()
|
||||||
alignRequested = Signal(Qt.AlignmentFlag)
|
alignRequested = Signal(Qt.AlignmentFlag)
|
||||||
historyRequested = Signal()
|
historyRequested = Signal()
|
||||||
insertImageRequested = Signal()
|
insertImageRequested = Signal()
|
||||||
|
|
@ -86,6 +87,9 @@ class ToolBar(QToolBar):
|
||||||
self.actNumbers.setToolTip("Numbered list")
|
self.actNumbers.setToolTip("Numbered list")
|
||||||
self.actNumbers.setCheckable(True)
|
self.actNumbers.setCheckable(True)
|
||||||
self.actNumbers.triggered.connect(self.numbersRequested)
|
self.actNumbers.triggered.connect(self.numbersRequested)
|
||||||
|
self.actCheckboxes = QAction("☐", self)
|
||||||
|
self.actCheckboxes.setToolTip("Toggle checkboxes")
|
||||||
|
self.actCheckboxes.triggered.connect(self.checkboxesRequested)
|
||||||
|
|
||||||
# Images
|
# Images
|
||||||
self.actInsertImg = QAction("Image", self)
|
self.actInsertImg = QAction("Image", self)
|
||||||
|
|
@ -150,6 +154,7 @@ class ToolBar(QToolBar):
|
||||||
self.actNormal,
|
self.actNormal,
|
||||||
self.actBullets,
|
self.actBullets,
|
||||||
self.actNumbers,
|
self.actNumbers,
|
||||||
|
self.actCheckboxes,
|
||||||
self.actInsertImg,
|
self.actInsertImg,
|
||||||
self.actAlignL,
|
self.actAlignL,
|
||||||
self.actAlignC,
|
self.actAlignC,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue