Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion negpy/desktop/view/canvas/hud.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,9 @@ def __init__(self, parent=None):

self._message_timer = QTimer(self)
self._message_timer.setSingleShot(True)
self._message_timer.timeout.connect(lambda: self.showMessage(""))
# A bound method, not a lambda: a collection on another thread clears a closure and defers the
# widget's delete, so a due timer would call a function with no closure.
self._message_timer.timeout.connect(self._expire_message)
self._progress_timer = QTimer(self)
self._progress_timer.setSingleShot(True)
self._progress_timer.timeout.connect(self.hide_progress)
Expand Down Expand Up @@ -160,6 +162,9 @@ def showMessage(self, text: str, timeout: int = 0, kind: str = "info") -> None:
self._message_timer.start(timeout or _DEFAULT_MESSAGE_MS)
self._sync()

def _expire_message(self) -> None:
self.showMessage("")

def clear_message(self, text: str) -> None:
"""Clears the message only if it is still `text`, so a later message survives."""
if text and self.message.text() == text and not self.message.isHidden():
Expand Down
2 changes: 1 addition & 1 deletion negpy/desktop/view/sidebar/pick_better_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ def __init__(self, parent=None) -> None:
self._move_label.setAttribute(Qt.WidgetAttribute.WA_TransparentForMouseEvents)
self._move_timer = QTimer(self)
self._move_timer.setSingleShot(True)
self._move_timer.timeout.connect(lambda: self._move_label.setText(""))
self._move_timer.timeout.connect(self._move_label.clear)

# The photos inset from the panel's top by the reserved band — the move summary, the in-flight
# name and the A/Skip/B captions — so a caption that hugs a photo's top edge sits clear of
Expand Down
42 changes: 42 additions & 0 deletions tests/test_canvas_hud.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
import os
import subprocess
import sys
from pathlib import Path

from PyQt6.QtGui import QColor
from PyQt6.QtWidgets import QWidget

Expand All @@ -14,6 +19,43 @@ def _hud(width=1200):
return hud


_DUE_TIMER_AFTER_A_BACKGROUND_COLLECTION = """
import gc, threading, time
from PyQt6.QtTest import QTest
from PyQt6.QtWidgets import QApplication, QWidget
from negpy.desktop.view.canvas.hud import CanvasHud

app = QApplication([])
gc.disable()

def strip():
parent = QWidget()
hud = CanvasHud(parent)
hud.showMessage("x", 20)
hud._keepalive = parent

strip()
thread = threading.Thread(target=gc.collect)
thread.start()
thread.join()
time.sleep(0.05)
QTest.qWait(30)
"""


def test_a_message_timer_survives_a_collection_on_another_thread():
# In a subprocess: the failure is a segfault, which would end the test run.
run = subprocess.run(
[sys.executable, "-c", _DUE_TIMER_AFTER_A_BACKGROUND_COLLECTION],
capture_output=True,
text=True,
timeout=60,
cwd=Path(__file__).parent.parent,
env={**os.environ, "QT_QPA_PLATFORM": "offscreen"},
)
assert run.returncode == 0, run.stderr


def test_an_empty_strip_hides_so_the_canvas_keeps_its_height(qapp):
hud = _hud()
assert hud.isHidden()
Expand Down
Loading