Compare commits
No commits in common. "ada1d8ffadac114721a9b15b5dee296e27fe1197" and "b5563c18a4746fd0521180481b3cd094b7205a45" have entirely different histories.
ada1d8ffad
...
b5563c18a4
5 changed files with 155 additions and 162 deletions
|
|
@ -1,7 +1,3 @@
|
||||||
# 0.1.11
|
|
||||||
|
|
||||||
* Add missing export extensions to export_by_extension
|
|
||||||
|
|
||||||
# 0.1.10.2
|
# 0.1.10.2
|
||||||
|
|
||||||
* Fix for code blocks in dark mode
|
* Fix for code blocks in dark mode
|
||||||
|
|
|
||||||
|
|
@ -480,10 +480,6 @@ class DBManager:
|
||||||
self.export_txt(entries, file_path)
|
self.export_txt(entries, file_path)
|
||||||
elif ext in {".html", ".htm"}:
|
elif ext in {".html", ".htm"}:
|
||||||
self.export_html(entries, file_path)
|
self.export_html(entries, file_path)
|
||||||
elif ext in {".sql", ".sqlite"}:
|
|
||||||
self.export_sql(file_path)
|
|
||||||
elif ext == ".md":
|
|
||||||
self.export_markdown(file_path)
|
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"Unsupported extension: {ext}")
|
raise ValueError(f"Unsupported extension: {ext}")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -79,35 +79,46 @@ class Editor(QTextEdit):
|
||||||
self.textChanged.connect(self._linkify_document)
|
self.textChanged.connect(self._linkify_document)
|
||||||
self.viewport().setMouseTracking(True)
|
self.viewport().setMouseTracking(True)
|
||||||
|
|
||||||
# ---------------- Helpers ---------------- #
|
def _approx(self, a: float, b: float, eps: float = 0.5) -> bool:
|
||||||
|
return abs(float(a) - float(b)) <= eps
|
||||||
|
|
||||||
def _iter_frames(self, root=None):
|
def _is_heading_typing(self) -> bool:
|
||||||
"""Depth-first traversal of all frames (including root if passed)."""
|
"""Is the current *insertion* format using a heading size?"""
|
||||||
doc = self.document()
|
bf = self.textCursor().blockFormat()
|
||||||
stack = [root or doc.rootFrame()]
|
if bf.headingLevel() > 0:
|
||||||
while stack:
|
return True
|
||||||
f = stack.pop()
|
|
||||||
yield f
|
|
||||||
it = f.begin()
|
|
||||||
while not it.atEnd():
|
|
||||||
cf = it.currentFrame()
|
|
||||||
if cf is not None:
|
|
||||||
stack.append(cf)
|
|
||||||
it += 1
|
|
||||||
|
|
||||||
def _is_code_frame(self, frame, tolerant: bool = False) -> bool:
|
def _apply_normal_typing(self):
|
||||||
|
"""Switch the *insertion* format to Normal (default size, normal weight)."""
|
||||||
|
nf = QTextCharFormat()
|
||||||
|
nf.setFontPointSize(self.font().pointSizeF())
|
||||||
|
nf.setFontWeight(QFont.Weight.Normal)
|
||||||
|
self.mergeCurrentCharFormat(nf)
|
||||||
|
|
||||||
|
def _find_code_frame(self, cursor=None):
|
||||||
|
"""Return the nearest ancestor frame that's one of our code frames, else None."""
|
||||||
|
if cursor is None:
|
||||||
|
cursor = self.textCursor()
|
||||||
|
f = cursor.currentFrame()
|
||||||
|
while f:
|
||||||
|
if f.frameFormat().property(self._CODE_FRAME_PROP):
|
||||||
|
return f
|
||||||
|
f = f.parentFrame()
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _looks_like_code_frame(self, frame) -> bool:
|
||||||
"""
|
"""
|
||||||
True if 'frame' is a code frame.
|
Heuristic: treat a frame as 'code' if
|
||||||
- tolerant=False: require our property marker
|
- it still has our property, OR
|
||||||
- tolerant=True: also accept legacy background or non-wrapping heuristic
|
- it has the classic light code bg (≈245,245,245) or our dark bg, OR
|
||||||
|
- most blocks inside are non-wrapping (NonBreakableLines=True),
|
||||||
|
which we set in apply_code() and which survives HTML round-trip.
|
||||||
"""
|
"""
|
||||||
ff = frame.frameFormat()
|
ff = frame.frameFormat()
|
||||||
if ff.property(self._CODE_FRAME_PROP):
|
if ff.property(self._CODE_FRAME_PROP):
|
||||||
return True
|
return True
|
||||||
if not tolerant:
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Background colour check
|
# Background check
|
||||||
bg = ff.background()
|
bg = ff.background()
|
||||||
if bg.style() != Qt.NoBrush:
|
if bg.style() != Qt.NoBrush:
|
||||||
c = bg.color()
|
c = bg.color()
|
||||||
|
|
@ -125,7 +136,7 @@ class Editor(QTextEdit):
|
||||||
):
|
):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# Heuristic: mostly non-wrapping blocks
|
# Block formatting check (survives toHtml/fromHtml)
|
||||||
doc = self.document()
|
doc = self.document()
|
||||||
bc = QTextCursor(doc)
|
bc = QTextCursor(doc)
|
||||||
bc.setPosition(frame.firstPosition())
|
bc.setPosition(frame.firstPosition())
|
||||||
|
|
@ -135,112 +146,12 @@ class Editor(QTextEdit):
|
||||||
if not b.isValid():
|
if not b.isValid():
|
||||||
break
|
break
|
||||||
blocks += 1
|
blocks += 1
|
||||||
if b.blockFormat().nonBreakableLines():
|
bf = b.blockFormat()
|
||||||
|
if bf.nonBreakableLines():
|
||||||
codeish += 1
|
codeish += 1
|
||||||
bc.setPosition(b.position() + b.length())
|
bc.setPosition(b.position() + b.length())
|
||||||
return blocks > 0 and (codeish / blocks) >= 0.6
|
return blocks > 0 and (codeish / blocks) >= 0.6
|
||||||
|
|
||||||
def _nearest_code_frame(self, cursor=None, tolerant: bool = False):
|
|
||||||
"""Walk up parents from the cursor and return the first code frame."""
|
|
||||||
if cursor is None:
|
|
||||||
cursor = self.textCursor()
|
|
||||||
f = cursor.currentFrame()
|
|
||||||
while f:
|
|
||||||
if self._is_code_frame(f, tolerant=tolerant):
|
|
||||||
return f
|
|
||||||
f = f.parentFrame()
|
|
||||||
return None
|
|
||||||
|
|
||||||
def _code_block_formats(self, fg: QColor | None = None):
|
|
||||||
"""(QTextBlockFormat, QTextCharFormat) for code blocks."""
|
|
||||||
mono = QFontDatabase.systemFont(QFontDatabase.FixedFont)
|
|
||||||
|
|
||||||
bf = QTextBlockFormat()
|
|
||||||
bf.setTopMargin(0)
|
|
||||||
bf.setBottomMargin(0)
|
|
||||||
bf.setLeftMargin(12)
|
|
||||||
bf.setRightMargin(12)
|
|
||||||
bf.setNonBreakableLines(True)
|
|
||||||
|
|
||||||
cf = QTextCharFormat()
|
|
||||||
cf.setFont(mono)
|
|
||||||
cf.setFontFixedPitch(True)
|
|
||||||
if fg is not None:
|
|
||||||
cf.setForeground(fg)
|
|
||||||
return bf, cf
|
|
||||||
|
|
||||||
def _new_code_frame_format(self, bg: QColor) -> QTextFrameFormat:
|
|
||||||
"""Standard frame format for code blocks."""
|
|
||||||
ff = QTextFrameFormat()
|
|
||||||
ff.setBackground(bg)
|
|
||||||
ff.setPadding(6)
|
|
||||||
ff.setBorder(0)
|
|
||||||
ff.setLeftMargin(0)
|
|
||||||
ff.setRightMargin(0)
|
|
||||||
ff.setTopMargin(0)
|
|
||||||
ff.setBottomMargin(0)
|
|
||||||
ff.setProperty(self._CODE_FRAME_PROP, True)
|
|
||||||
return ff
|
|
||||||
|
|
||||||
def _retint_code_frame(self, frame, bg: QColor, fg: QColor | None):
|
|
||||||
"""Apply background to frame and standard code formats to all blocks inside."""
|
|
||||||
ff = frame.frameFormat()
|
|
||||||
ff.setBackground(bg)
|
|
||||||
frame.setFrameFormat(ff)
|
|
||||||
|
|
||||||
bf, cf = self._code_block_formats(fg)
|
|
||||||
doc = self.document()
|
|
||||||
bc = QTextCursor(doc)
|
|
||||||
bc.setPosition(frame.firstPosition())
|
|
||||||
while bc.position() < frame.lastPosition():
|
|
||||||
bc.select(QTextCursor.BlockUnderCursor)
|
|
||||||
bc.mergeBlockFormat(bf)
|
|
||||||
bc.mergeBlockCharFormat(cf)
|
|
||||||
if not bc.movePosition(QTextCursor.NextBlock):
|
|
||||||
break
|
|
||||||
|
|
||||||
def _safe_block_insertion_cursor(self):
|
|
||||||
"""
|
|
||||||
Return a cursor positioned for inserting an inline object (like an image):
|
|
||||||
- not inside a code frame (moves to after frame if necessary)
|
|
||||||
- at a fresh paragraph (inserts a block if mid-line)
|
|
||||||
Also updates the editor's current cursor to that position.
|
|
||||||
"""
|
|
||||||
c = QTextCursor(self.textCursor())
|
|
||||||
frame = self._nearest_code_frame(c, tolerant=False) # strict: our frames only
|
|
||||||
if frame:
|
|
||||||
out = QTextCursor(self.document())
|
|
||||||
out.setPosition(frame.lastPosition())
|
|
||||||
self.setTextCursor(out)
|
|
||||||
c = self.textCursor()
|
|
||||||
if c.positionInBlock() != 0:
|
|
||||||
c.insertBlock()
|
|
||||||
return c
|
|
||||||
|
|
||||||
def _scale_to_viewport(self, img: QImage, ratio: float = 0.92) -> QImage:
|
|
||||||
"""If the image is wider than viewport*ratio, scale it down proportionally."""
|
|
||||||
if self.viewport():
|
|
||||||
max_w = int(self.viewport().width() * ratio)
|
|
||||||
if img.width() > max_w:
|
|
||||||
return img.scaledToWidth(max_w, Qt.SmoothTransformation)
|
|
||||||
return img
|
|
||||||
|
|
||||||
def _approx(self, a: float, b: float, eps: float = 0.5) -> bool:
|
|
||||||
return abs(float(a) - float(b)) <= eps
|
|
||||||
|
|
||||||
def _is_heading_typing(self) -> bool:
|
|
||||||
"""Is the current *insertion* format using a heading size?"""
|
|
||||||
bf = self.textCursor().blockFormat()
|
|
||||||
if bf.headingLevel() > 0:
|
|
||||||
return True
|
|
||||||
|
|
||||||
def _apply_normal_typing(self):
|
|
||||||
"""Switch the *insertion* format to Normal (default size, normal weight)."""
|
|
||||||
nf = QTextCharFormat()
|
|
||||||
nf.setFontPointSize(self.font().pointSizeF())
|
|
||||||
nf.setFontWeight(QFont.Weight.Normal)
|
|
||||||
self.mergeCurrentCharFormat(nf)
|
|
||||||
|
|
||||||
def _code_theme_colors(self):
|
def _code_theme_colors(self):
|
||||||
"""Return (bg, fg) for code blocks based on the effective palette."""
|
"""Return (bg, fg) for code blocks based on the effective palette."""
|
||||||
pal = QApplication.instance().palette()
|
pal = QApplication.instance().palette()
|
||||||
|
|
@ -264,9 +175,46 @@ class Editor(QTextEdit):
|
||||||
cur = QTextCursor(doc)
|
cur = QTextCursor(doc)
|
||||||
cur.beginEditBlock()
|
cur.beginEditBlock()
|
||||||
try:
|
try:
|
||||||
for f in self._iter_frames(doc.rootFrame()):
|
# Traverse all frames reliably (iterator-based, works after reload)
|
||||||
if f is not doc.rootFrame() and self._is_code_frame(f, tolerant=True):
|
stack = [doc.rootFrame()]
|
||||||
self._retint_code_frame(f, bg, fg)
|
while stack:
|
||||||
|
f = stack.pop()
|
||||||
|
it = f.begin()
|
||||||
|
while not it.atEnd():
|
||||||
|
cf = it.currentFrame()
|
||||||
|
if cf is not None:
|
||||||
|
stack.append(cf)
|
||||||
|
it += 1
|
||||||
|
|
||||||
|
# Retint frames that look like code
|
||||||
|
if f is not doc.rootFrame() and self._looks_like_code_frame(f):
|
||||||
|
ff = f.frameFormat()
|
||||||
|
ff.setBackground(bg)
|
||||||
|
f.setFrameFormat(ff)
|
||||||
|
|
||||||
|
# Make sure the text inside stays readable and monospaced
|
||||||
|
mono = QFontDatabase.systemFont(QFontDatabase.FixedFont)
|
||||||
|
bc = QTextCursor(doc)
|
||||||
|
bc.setPosition(f.firstPosition())
|
||||||
|
while bc.position() < f.lastPosition():
|
||||||
|
bc.select(QTextCursor.BlockUnderCursor)
|
||||||
|
|
||||||
|
bf = QTextBlockFormat()
|
||||||
|
bf.setTopMargin(0)
|
||||||
|
bf.setBottomMargin(0)
|
||||||
|
bf.setLeftMargin(12)
|
||||||
|
bf.setRightMargin(12)
|
||||||
|
bf.setNonBreakableLines(True)
|
||||||
|
|
||||||
|
cf = QTextCharFormat()
|
||||||
|
cf.setFont(mono)
|
||||||
|
cf.setFontFixedPitch(True)
|
||||||
|
cf.setForeground(fg)
|
||||||
|
|
||||||
|
bc.mergeBlockFormat(bf)
|
||||||
|
bc.mergeBlockCharFormat(cf)
|
||||||
|
if not bc.movePosition(QTextCursor.NextBlock):
|
||||||
|
break
|
||||||
finally:
|
finally:
|
||||||
cur.endEditBlock()
|
cur.endEditBlock()
|
||||||
self.viewport().update()
|
self.viewport().update()
|
||||||
|
|
@ -328,7 +276,7 @@ class Editor(QTextEdit):
|
||||||
fmt.setFontUnderline(True)
|
fmt.setFontUnderline(True)
|
||||||
fmt.setForeground(self.palette().brush(QPalette.Link))
|
fmt.setForeground(self.palette().brush(QPalette.Link))
|
||||||
|
|
||||||
cur.mergeCharFormat(fmt) # merge so we don't clobber other styling
|
cur.mergeCharFormat(fmt) # merge so we don’t clobber other styling
|
||||||
|
|
||||||
cur.endEditBlock()
|
cur.endEditBlock()
|
||||||
finally:
|
finally:
|
||||||
|
|
@ -388,12 +336,26 @@ class Editor(QTextEdit):
|
||||||
html = html.replace(f"src='{old}'", f"src='{data_url}'")
|
html = html.replace(f"src='{old}'", f"src='{data_url}'")
|
||||||
return html
|
return html
|
||||||
|
|
||||||
# ---------------- Image insertion & sizing (DRY’d) ---------------- #
|
|
||||||
|
|
||||||
def _insert_qimage_at_cursor(self, img: QImage, autoscale=True):
|
def _insert_qimage_at_cursor(self, img: QImage, autoscale=True):
|
||||||
c = self._safe_block_insertion_cursor()
|
c = self.textCursor()
|
||||||
if autoscale:
|
|
||||||
img = self._scale_to_viewport(img)
|
# Don’t drop inside a code frame
|
||||||
|
frame = self._find_code_frame(c)
|
||||||
|
if frame:
|
||||||
|
out = QTextCursor(self.document())
|
||||||
|
out.setPosition(frame.lastPosition())
|
||||||
|
self.setTextCursor(out)
|
||||||
|
c = self.textCursor()
|
||||||
|
|
||||||
|
# Start a fresh paragraph if mid-line
|
||||||
|
if c.positionInBlock() != 0:
|
||||||
|
c.insertBlock()
|
||||||
|
|
||||||
|
if autoscale and self.viewport():
|
||||||
|
max_w = int(self.viewport().width() * 0.92)
|
||||||
|
if img.width() > max_w:
|
||||||
|
img = img.scaledToWidth(max_w, Qt.SmoothTransformation)
|
||||||
|
|
||||||
c.insertImage(img)
|
c.insertImage(img)
|
||||||
c.insertBlock() # one blank line after the image
|
c.insertBlock() # one blank line after the image
|
||||||
|
|
||||||
|
|
@ -503,8 +465,6 @@ class Editor(QTextEdit):
|
||||||
return
|
return
|
||||||
self._apply_image_size(tc, imgfmt, float(orig.width()), orig)
|
self._apply_image_size(tc, imgfmt, float(orig.width()), orig)
|
||||||
|
|
||||||
# ---------------- Context menu ---------------- #
|
|
||||||
|
|
||||||
def contextMenuEvent(self, e):
|
def contextMenuEvent(self, e):
|
||||||
menu = self.createStandardContextMenu()
|
menu = self.createStandardContextMenu()
|
||||||
tc, imgfmt, orig = self._image_info_at_cursor()
|
tc, imgfmt, orig = self._image_info_at_cursor()
|
||||||
|
|
@ -518,8 +478,6 @@ class Editor(QTextEdit):
|
||||||
sub.addAction("Reset to original", self._reset_image_size)
|
sub.addAction("Reset to original", self._reset_image_size)
|
||||||
menu.exec(e.globalPos())
|
menu.exec(e.globalPos())
|
||||||
|
|
||||||
# ---------------- Clipboard / DnD ---------------- #
|
|
||||||
|
|
||||||
def insertFromMimeData(self, source):
|
def insertFromMimeData(self, source):
|
||||||
# 1) Direct image from clipboard
|
# 1) Direct image from clipboard
|
||||||
if source.hasImage():
|
if source.hasImage():
|
||||||
|
|
@ -567,7 +525,7 @@ class Editor(QTextEdit):
|
||||||
data = base64.b64decode(m.group(1))
|
data = base64.b64decode(m.group(1))
|
||||||
img = QImage.fromData(data)
|
img = QImage.fromData(data)
|
||||||
if not img.isNull():
|
if not img.isNull():
|
||||||
self._insert_qimage_at_cursor(img, autoscale=True)
|
self._insert_qimage_at_cursor(self, img, autoscale=True)
|
||||||
return
|
return
|
||||||
except Exception:
|
except Exception:
|
||||||
pass # fall through
|
pass # fall through
|
||||||
|
|
@ -581,7 +539,19 @@ class Editor(QTextEdit):
|
||||||
Insert one or more images at the cursor. Large images can be auto-scaled
|
Insert one or more images at the cursor. Large images can be auto-scaled
|
||||||
to fit the viewport width while preserving aspect ratio.
|
to fit the viewport width while preserving aspect ratio.
|
||||||
"""
|
"""
|
||||||
c = self._safe_block_insertion_cursor()
|
c = self.textCursor()
|
||||||
|
|
||||||
|
# Avoid dropping images inside a code frame
|
||||||
|
frame = self._find_code_frame(c)
|
||||||
|
if frame:
|
||||||
|
out = QTextCursor(self.document())
|
||||||
|
out.setPosition(frame.lastPosition())
|
||||||
|
self.setTextCursor(out)
|
||||||
|
c = self.textCursor()
|
||||||
|
|
||||||
|
# Ensure there's a paragraph break if we're mid-line
|
||||||
|
if c.positionInBlock() != 0:
|
||||||
|
c.insertBlock()
|
||||||
|
|
||||||
for path in paths:
|
for path in paths:
|
||||||
reader = QImageReader(path)
|
reader = QImageReader(path)
|
||||||
|
|
@ -589,14 +559,14 @@ class Editor(QTextEdit):
|
||||||
if img.isNull():
|
if img.isNull():
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if autoscale:
|
if autoscale and self.viewport():
|
||||||
img = self._scale_to_viewport(img)
|
max_w = int(self.viewport().width() * 0.92) # ~92% of editor width
|
||||||
|
if img.width() > max_w:
|
||||||
|
img = img.scaledToWidth(max_w, Qt.SmoothTransformation)
|
||||||
|
|
||||||
c.insertImage(img)
|
c.insertImage(img)
|
||||||
c.insertBlock() # put each image on its own line
|
c.insertBlock() # put each image on its own line
|
||||||
|
|
||||||
# ---------------- Mouse & key handling ---------------- #
|
|
||||||
|
|
||||||
def mouseReleaseEvent(self, e):
|
def mouseReleaseEvent(self, e):
|
||||||
if e.button() == Qt.LeftButton and (e.modifiers() & Qt.ControlModifier):
|
if e.button() == Qt.LeftButton and (e.modifiers() & Qt.ControlModifier):
|
||||||
href = self.anchorAt(e.pos())
|
href = self.anchorAt(e.pos())
|
||||||
|
|
@ -667,7 +637,7 @@ class Editor(QTextEdit):
|
||||||
|
|
||||||
# If we're on an empty line inside a code frame, consume Enter and jump out
|
# If we're on an empty line inside a code frame, consume Enter and jump out
|
||||||
if c.block().length() == 1:
|
if c.block().length() == 1:
|
||||||
frame = self._nearest_code_frame(c, tolerant=False)
|
frame = self._find_code_frame(c)
|
||||||
if frame:
|
if frame:
|
||||||
out = QTextCursor(self.document())
|
out = QTextCursor(self.document())
|
||||||
out.setPosition(frame.lastPosition()) # after the frame's contents
|
out.setPosition(frame.lastPosition()) # after the frame's contents
|
||||||
|
|
@ -745,7 +715,7 @@ class Editor(QTextEdit):
|
||||||
|
|
||||||
# ====== Checkbox core ======
|
# ====== Checkbox core ======
|
||||||
def _base_point_size_for_block(self, block) -> float:
|
def _base_point_size_for_block(self, block) -> float:
|
||||||
# Try the block's char format, then editor font
|
# Try the block’s char format, then editor font
|
||||||
sz = block.charFormat().fontPointSize()
|
sz = block.charFormat().fontPointSize()
|
||||||
if sz <= 0:
|
if sz <= 0:
|
||||||
sz = self.fontPointSize()
|
sz = self.fontPointSize()
|
||||||
|
|
@ -901,16 +871,46 @@ class Editor(QTextEdit):
|
||||||
if not c.hasSelection():
|
if not c.hasSelection():
|
||||||
c.select(QTextCursor.BlockUnderCursor)
|
c.select(QTextCursor.BlockUnderCursor)
|
||||||
|
|
||||||
ff = self._new_code_frame_format(self._CODE_BG)
|
# Wrap the selection in a single frame (no per-block padding/margins).
|
||||||
|
ff = QTextFrameFormat()
|
||||||
|
ff.setBackground(self._CODE_BG)
|
||||||
|
ff.setPadding(6) # visual padding for the WHOLE block
|
||||||
|
ff.setBorder(0)
|
||||||
|
ff.setLeftMargin(0)
|
||||||
|
ff.setRightMargin(0)
|
||||||
|
ff.setTopMargin(0)
|
||||||
|
ff.setBottomMargin(0)
|
||||||
|
ff.setProperty(self._CODE_FRAME_PROP, True)
|
||||||
|
|
||||||
|
mono = QFontDatabase.systemFont(QFontDatabase.FixedFont)
|
||||||
|
|
||||||
c.beginEditBlock()
|
c.beginEditBlock()
|
||||||
try:
|
try:
|
||||||
c.insertFrame(ff) # with a selection, this wraps the selection
|
c.insertFrame(ff) # with a selection, this wraps the selection
|
||||||
|
|
||||||
# Format all blocks inside the new frame (keep fg=None on creation)
|
# Format all blocks inside the new frame: zero vertical margins, mono font, no wrapping
|
||||||
frame = self._nearest_code_frame(c, tolerant=False)
|
frame = self._find_code_frame(c)
|
||||||
if frame:
|
bc = QTextCursor(self.document())
|
||||||
self._retint_code_frame(frame, self._CODE_BG, fg=None)
|
bc.setPosition(frame.firstPosition())
|
||||||
|
|
||||||
|
while bc.position() < frame.lastPosition():
|
||||||
|
bc.select(QTextCursor.BlockUnderCursor)
|
||||||
|
|
||||||
|
bf = QTextBlockFormat()
|
||||||
|
bf.setTopMargin(0)
|
||||||
|
bf.setBottomMargin(0)
|
||||||
|
bf.setLeftMargin(12)
|
||||||
|
bf.setRightMargin(12)
|
||||||
|
bf.setNonBreakableLines(True)
|
||||||
|
|
||||||
|
cf = QTextCharFormat()
|
||||||
|
cf.setFont(mono)
|
||||||
|
cf.setFontFixedPitch(True)
|
||||||
|
|
||||||
|
bc.mergeBlockFormat(bf)
|
||||||
|
bc.mergeBlockCharFormat(cf)
|
||||||
|
|
||||||
|
bc.setPosition(bc.block().position() + bc.block().length())
|
||||||
finally:
|
finally:
|
||||||
c.endEditBlock()
|
c.endEditBlock()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -707,7 +707,7 @@ class MainWindow(QMainWindow):
|
||||||
QGuiApplication.screenAt(QCursor.pos()) or QGuiApplication.primaryScreen()
|
QGuiApplication.screenAt(QCursor.pos()) or QGuiApplication.primaryScreen()
|
||||||
)
|
)
|
||||||
r = screen.availableGeometry()
|
r = screen.availableGeometry()
|
||||||
# Center the window in that screen's available area
|
# Center the window in that screen’s available area
|
||||||
self.move(r.center() - self.rect().center())
|
self.move(r.center() - self.rect().center())
|
||||||
|
|
||||||
# ----------------- Export handler ----------------- #
|
# ----------------- Export handler ----------------- #
|
||||||
|
|
@ -836,7 +836,7 @@ If you want an encrypted backup, choose Backup instead of Export.
|
||||||
return
|
return
|
||||||
if minutes == 0:
|
if minutes == 0:
|
||||||
self._idle_timer.stop()
|
self._idle_timer.stop()
|
||||||
# If currently locked, unlock when user disables the timer:
|
# If you’re currently locked, unlock when user disables the timer:
|
||||||
if getattr(self, "_locked", False):
|
if getattr(self, "_locked", False):
|
||||||
try:
|
try:
|
||||||
self._locked = False
|
self._locked = False
|
||||||
|
|
|
||||||
|
|
@ -169,12 +169,13 @@ def test_code_block_enter_exits_on_empty_line(qtbot):
|
||||||
QTest.keyClick(e, Qt.Key_Return)
|
QTest.keyClick(e, Qt.Key_Return)
|
||||||
# Ensure we are on an empty block *inside* the code frame
|
# Ensure we are on an empty block *inside* the code frame
|
||||||
qtbot.waitUntil(
|
qtbot.waitUntil(
|
||||||
lambda: e._nearest_code_frame(e.textCursor(), tolerant=False) is not None
|
lambda: e._find_code_frame(e.textCursor()) is not None
|
||||||
and e.textCursor().block().length() == 1
|
and e.textCursor().block().length() == 1
|
||||||
)
|
)
|
||||||
|
|
||||||
# Second Enter should jump *out* of the frame
|
# Second Enter should jump *out* of the frame
|
||||||
QTest.keyClick(e, Qt.Key_Return)
|
QTest.keyClick(e, Qt.Key_Return)
|
||||||
|
# qtbot.waitUntil(lambda: e._find_code_frame(e.textCursor()) is None)
|
||||||
|
|
||||||
|
|
||||||
class DummyMenu:
|
class DummyMenu:
|
||||||
|
|
@ -309,7 +310,7 @@ def test_enter_leaves_code_frame(qtbot):
|
||||||
ev = QKeyEvent(QKeyEvent.KeyPress, Qt.Key_Return, Qt.NoModifier)
|
ev = QKeyEvent(QKeyEvent.KeyPress, Qt.Key_Return, Qt.NoModifier)
|
||||||
e.keyPressEvent(ev)
|
e.keyPressEvent(ev)
|
||||||
# After enter, the cursor should not be inside a code frame
|
# After enter, the cursor should not be inside a code frame
|
||||||
assert e._nearest_code_frame(e.textCursor(), tolerant=False) is None
|
assert e._find_code_frame(e.textCursor()) is None
|
||||||
|
|
||||||
|
|
||||||
def test_space_does_not_bleed_anchor_format(qtbot):
|
def test_space_does_not_bleed_anchor_format(qtbot):
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue