Skip to content

Commit e60da20

Browse files
committed
FIX: Add update capability to interval/singleshot timer properties
1 parent ed8131b commit e60da20

File tree

2 files changed

+40
-4
lines changed

2 files changed

+40
-4
lines changed

lib/matplotlib/tests/test_backends_interactive.py

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -626,29 +626,47 @@ def _impl_test_interactive_timers():
626626
# We only want singleshot if we specify that ourselves, otherwise we want
627627
# a repeating timer
628628
import os
629+
import sys
629630
from unittest.mock import Mock
630631
import matplotlib.pyplot as plt
631632
# increase pause duration on CI to let things spin up
632633
# particularly relevant for gtk3cairo
633634
pause_time = 2 if os.getenv("CI") else 0.5
635+
expected_100ms_calls = int(pause_time / 0.1)
634636
fig = plt.figure()
635637
plt.pause(pause_time)
636638
timer = fig.canvas.new_timer(0.1)
637639
mock = Mock()
638640
timer.add_callback(mock)
639641
timer.start()
640642
plt.pause(pause_time)
641-
timer.stop()
642-
assert mock.call_count > 1
643+
# NOTE: The timer is as fast as possible, but this is different between backends
644+
# so we can't assert on the exact number but it should be faster than 100ms
645+
assert mock.call_count > expected_100ms_calls, \
646+
f"Expected more than {expected_100ms_calls} calls, got {mock.call_count}"
647+
648+
# Test updating the interval updates a running timer
649+
timer.interval = 100
650+
mock.call_count = 0
651+
plt.pause(pause_time)
652+
# GTK4 on macos runners produces about 3x as many calls as expected
653+
# It works locally and on Linux though, so only skip when running on CI
654+
if not (os.getenv("CI")
655+
and "gtk4" in os.getenv("MPLBACKEND")
656+
and sys.platform == "darwin"):
657+
# Could be off due to when the timers actually get fired (especially on CI)
658+
assert 1 < mock.call_count <= expected_100ms_calls + 1, \
659+
f"Expected less than {expected_100ms_calls + 1} calls, " \
660+
"got {mock.call_count}"
643661

644662
# Now turn it into a single shot timer and verify only one gets triggered
645663
mock.call_count = 0
646664
timer.single_shot = True
647-
timer.start()
648665
plt.pause(pause_time)
649666
assert mock.call_count == 1
650667

651-
# Make sure we can start the timer a second time
668+
# Make sure we can start the timer after stopping
669+
timer.stop()
652670
timer.start()
653671
plt.pause(pause_time)
654672
assert mock.call_count == 2

src/_macosx.m

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1789,6 +1789,18 @@ - (void)flagsChanged:(NSEvent *)event
17891789
Py_RETURN_NONE;
17901790
}
17911791

1792+
static PyObject*
1793+
Timer__timer_update(Timer* self)
1794+
{
1795+
// stop and invalidate a timer if it is already running and then create a new one
1796+
// where the start() method retrieves the updated interval internally
1797+
if (self->timer) {
1798+
Timer__timer_stop_impl(self);
1799+
gil_call_method((PyObject*)self, "_timer_start");
1800+
}
1801+
Py_RETURN_NONE;
1802+
}
1803+
17921804
static void
17931805
Timer_dealloc(Timer* self)
17941806
{
@@ -1815,6 +1827,12 @@ - (void)flagsChanged:(NSEvent *)event
18151827
{"_timer_stop",
18161828
(PyCFunction)Timer__timer_stop,
18171829
METH_NOARGS},
1830+
{"_timer_set_interval",
1831+
(PyCFunction)Timer__timer_update,
1832+
METH_NOARGS},
1833+
{"_timer_set_single_shot",
1834+
(PyCFunction)Timer__timer_update,
1835+
METH_NOARGS},
18181836
{} // sentinel
18191837
},
18201838
};

0 commit comments

Comments
 (0)