Skip to content

Commit fe1d71c

Browse files
committed
Protect Experimental Timeline from recursive paint events - causing the timeline to become unresponsive
1 parent 8d9d0bb commit fe1d71c

File tree

1 file changed

+48
-36
lines changed
  • src/windows/views/timeline_backend/qwidget

1 file changed

+48
-36
lines changed

src/windows/views/timeline_backend/qwidget/base.py

Lines changed: 48 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,9 @@ def __init__(self, parent=None):
210210
# Internal flag to defer repaint scheduling from changed()
211211
self._suspend_changed_update = 0
212212

213+
# Guard against re-entrant paintEvent calls
214+
self._in_paint_event = False
215+
213216
# Strong references to dynamically created state transitions
214217
self._transitions = []
215218

@@ -728,45 +731,54 @@ def changed(self, action):
728731

729732
def paintEvent(self, event, *args):
730733
"""Custom paint routine for the timeline widget."""
731-
event.accept()
732-
painter = QPainter(self)
733-
painter.setRenderHints(
734-
QPainter.Antialiasing |
735-
QPainter.SmoothPixmapTransform |
736-
QPainter.TextAntialiasing,
737-
True,
738-
)
739-
740-
if not get_app().window.timeline:
741-
painter.end()
734+
if self._in_paint_event:
735+
log.warning("TimelineWidgetBase paintEvent skipped due to re-entrancy")
736+
event.accept()
737+
self.update()
742738
return
743739

744-
signature = self._panel_current_signature()
745-
if signature != self._panel_refresh_signature:
746-
self._panel_refresh_signature = signature
747-
if self._update_track_panel_properties():
748-
self.geometry.mark_dirty()
740+
self._in_paint_event = True
741+
painter = QPainter(self)
742+
try:
743+
event.accept()
744+
painter.setRenderHints(
745+
QPainter.Antialiasing |
746+
QPainter.SmoothPixmapTransform |
747+
QPainter.TextAntialiasing,
748+
True,
749+
)
749750

750-
self.geometry.ensure()
751-
self._ensure_keyframe_markers()
752-
753-
self.bg_painter.paint(painter, event.rect())
754-
self.track_painter.paint_background(painter)
755-
self.keyframe_panel_painter.paint(painter, mode="underlay")
756-
self.clip_painter.paint(painter)
757-
self.transition_painter.paint(painter)
758-
self.playback_cache_painter.paint(painter)
759-
self.keyframe_painter.paint(painter)
760-
self.track_painter.paint_names(painter)
761-
self.keyframe_panel_painter.paint(painter, mode="overlay")
762-
self.selection_painter.paint(painter)
763-
self.ruler_painter.paint(painter)
764-
self.marker_painter.paint(painter)
765-
self.playhead_painter.paint(painter)
766-
self.ruler_painter.paint_overlay(painter)
767-
self.scrollbar_painter.paint(painter)
768-
769-
painter.end()
751+
if not get_app().window.timeline:
752+
return
753+
754+
signature = self._panel_current_signature()
755+
if signature != self._panel_refresh_signature:
756+
self._panel_refresh_signature = signature
757+
if self._update_track_panel_properties():
758+
self.geometry.mark_dirty()
759+
760+
self.geometry.ensure()
761+
self._ensure_keyframe_markers()
762+
763+
self.bg_painter.paint(painter, event.rect())
764+
self.track_painter.paint_background(painter)
765+
self.keyframe_panel_painter.paint(painter, mode="underlay")
766+
self.clip_painter.paint(painter)
767+
self.transition_painter.paint(painter)
768+
self.playback_cache_painter.paint(painter)
769+
self.keyframe_painter.paint(painter)
770+
self.track_painter.paint_names(painter)
771+
self.keyframe_panel_painter.paint(painter, mode="overlay")
772+
self.selection_painter.paint(painter)
773+
self.ruler_painter.paint(painter)
774+
self.marker_painter.paint(painter)
775+
self.playhead_painter.paint(painter)
776+
self.ruler_painter.paint_overlay(painter)
777+
self.scrollbar_painter.paint(painter)
778+
finally:
779+
if painter.isActive():
780+
painter.end()
781+
self._in_paint_event = False
770782

771783
def closeEvent(self, event):
772784
"""Ensure background threads stop when the widget closes."""

0 commit comments

Comments
 (0)