Skip to content

Commit 792ba13

Browse files
committed
First attempt to implement a timer in the Mac OS X backend. Blitting has not
(yet?) been implemented, but otherwise it seems to work fine. svn path=/trunk/matplotlib/; revision=8741
1 parent 9831cc9 commit 792ba13

File tree

2 files changed

+226
-1
lines changed

2 files changed

+226
-1
lines changed

lib/matplotlib/backends/backend_macosx.py

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
from matplotlib._pylab_helpers import Gcf
77
from matplotlib.backend_bases import RendererBase, GraphicsContextBase,\
8-
FigureManagerBase, FigureCanvasBase, NavigationToolbar2
8+
FigureManagerBase, FigureCanvasBase, NavigationToolbar2, TimerBase
99
from matplotlib.backend_bases import ShowBase
1010

1111
from matplotlib.cbook import maxdict
@@ -240,6 +240,24 @@ def new_figure_manager(num, *args, **kwargs):
240240
manager = FigureManagerMac(canvas, num)
241241
return manager
242242

243+
class TimerMac(_macosx.Timer, TimerBase):
244+
'''
245+
Subclass of :class:`backend_bases.TimerBase` that uses CoreFoundation
246+
run loops for timer events.
247+
248+
Attributes:
249+
* interval: The time between timer events in milliseconds. Default
250+
is 1000 ms.
251+
* single_shot: Boolean flag indicating whether this timer should
252+
operate as single shot (run once and then stop). Defaults to False.
253+
* callbacks: Stores list of (func, args) tuples that will be called
254+
upon timer events. This list can be manipulated directly, or the
255+
functions add_callback and remove_callback can be used.
256+
'''
257+
# completely implemented at the C-level (in _macosx.Timer)
258+
259+
260+
243261
class FigureCanvasMac(_macosx.FigureCanvas, FigureCanvasBase):
244262
"""
245263
The canvas the figure renders into. Calls the draw and print fig
@@ -310,6 +328,21 @@ def print_gif(self, filename, *args, **kwargs):
310328
def get_default_filetype(self):
311329
return 'png'
312330

331+
def new_timer(self, *args, **kwargs):
332+
"""
333+
Creates a new backend-specific subclass of :class:`backend_bases.Timer`.
334+
This is useful for getting periodic events through the backend's native
335+
event loop. Implemented only for backends with GUIs.
336+
337+
optional arguments:
338+
339+
*interval*
340+
Timer interval in milliseconds
341+
*callbacks*
342+
Sequence of (func, args, kwargs) where func(*args, **kwargs) will
343+
be executed by the timer every *interval*.
344+
"""
345+
return TimerMac(*args, **kwargs)
313346

314347
class FigureManagerMac(_macosx.FigureManager, FigureManagerBase):
315348
"""

src/_macosx.m

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5494,6 +5494,195 @@ - (int)index
54945494
return Py_True;
54955495
}
54965496

5497+
typedef struct {
5498+
PyObject_HEAD
5499+
CFRunLoopTimerRef timer;
5500+
} Timer;
5501+
5502+
static PyObject*
5503+
Timer_new(PyTypeObject* type, PyObject *args, PyObject *kwds)
5504+
{
5505+
Timer* self = (Timer*)type->tp_alloc(type, 0);
5506+
if (!self) return NULL;
5507+
self->timer = NULL;
5508+
return (PyObject*) self;
5509+
}
5510+
5511+
static void
5512+
Timer_dealloc(Timer* self)
5513+
{
5514+
if (self->timer) {
5515+
PyObject* attribute;
5516+
CFRunLoopTimerContext context;
5517+
CFRunLoopTimerGetContext(self->timer, &context);
5518+
attribute = context.info;
5519+
Py_DECREF(attribute);
5520+
CFRunLoopRef runloop = CFRunLoopGetCurrent();
5521+
if (runloop) {
5522+
CFRunLoopRemoveTimer(runloop, self->timer, kCFRunLoopCommonModes);
5523+
}
5524+
CFRelease(self->timer);
5525+
self->timer = NULL;
5526+
}
5527+
self->ob_type->tp_free((PyObject*)self);
5528+
}
5529+
5530+
static PyObject*
5531+
Timer_repr(Timer* self)
5532+
{
5533+
return PyString_FromFormat("Timer object %p wrapping CFRunLoopTimerRef %p",
5534+
(void*) self, (void*)(self->timer));
5535+
}
5536+
5537+
static char Timer_doc[] =
5538+
"A Timer object wraps a CFRunLoopTimerRef and can add it to the event loop.\n";
5539+
5540+
static void timer_callback(CFRunLoopTimerRef timer, void* info)
5541+
{
5542+
PyObject* method = info;
5543+
PyGILState_STATE gstate = PyGILState_Ensure();
5544+
PyObject* result = PyObject_CallFunction(method, NULL);
5545+
if (result==NULL) PyErr_Print();
5546+
PyGILState_Release(gstate);
5547+
}
5548+
5549+
static PyObject*
5550+
Timer__timer_start(Timer* self, PyObject* args)
5551+
{
5552+
CFRunLoopRef runloop;
5553+
CFRunLoopTimerRef timer;
5554+
CFRunLoopTimerContext context;
5555+
double milliseconds;
5556+
CFTimeInterval interval;
5557+
PyObject* attribute;
5558+
PyObject* failure;
5559+
runloop = CFRunLoopGetCurrent();
5560+
if (!runloop) {
5561+
PyErr_SetString(PyExc_RuntimeError, "Failed to obtain run loop");
5562+
return NULL;
5563+
}
5564+
context.version = 0;
5565+
context.retain = 0;
5566+
context.release = 0;
5567+
context.copyDescription = 0;
5568+
attribute = PyObject_GetAttrString((PyObject*)self, "_interval");
5569+
if (attribute==NULL)
5570+
{
5571+
PyErr_SetString(PyExc_AttributeError, "Timer has no attribute '_interval'");
5572+
return NULL;
5573+
}
5574+
milliseconds = PyFloat_AsDouble(attribute);
5575+
failure = PyErr_Occurred();
5576+
Py_DECREF(attribute);
5577+
if (failure) return NULL;
5578+
attribute = PyObject_GetAttrString((PyObject*)self, "_single");
5579+
if (attribute==NULL)
5580+
{
5581+
PyErr_SetString(PyExc_AttributeError, "Timer has no attribute '_single'");
5582+
return NULL;
5583+
}
5584+
switch (PyObject_IsTrue(attribute)) {
5585+
case 1:
5586+
interval = 0;
5587+
break;
5588+
case 0:
5589+
interval = milliseconds / 1000.0;
5590+
break;
5591+
case -1:
5592+
default:
5593+
PyErr_SetString(PyExc_ValueError, "Cannot interpret _single attribute as True of False");
5594+
return NULL;
5595+
}
5596+
attribute = PyObject_GetAttrString((PyObject*)self, "_on_timer");
5597+
if (attribute==NULL)
5598+
{
5599+
PyErr_SetString(PyExc_AttributeError, "Timer has no attribute '_on_timer'");
5600+
return NULL;
5601+
}
5602+
if (!PyMethod_Check(attribute)) {
5603+
PyErr_SetString(PyExc_RuntimeError, "_on_timer should be a Python method");
5604+
return NULL;
5605+
}
5606+
context.info = attribute;
5607+
timer = CFRunLoopTimerCreate(kCFAllocatorDefault,
5608+
0,
5609+
interval,
5610+
0,
5611+
0,
5612+
timer_callback,
5613+
&context);
5614+
if (!timer) {
5615+
PyErr_SetString(PyExc_RuntimeError, "Failed to create timer");
5616+
return NULL;
5617+
}
5618+
Py_INCREF(attribute);
5619+
if (self->timer) {
5620+
CFRunLoopTimerGetContext(self->timer, &context);
5621+
attribute = context.info;
5622+
Py_DECREF(attribute);
5623+
CFRunLoopRemoveTimer(runloop, self->timer, kCFRunLoopCommonModes);
5624+
CFRelease(self->timer);
5625+
}
5626+
CFRunLoopAddTimer(runloop, timer, kCFRunLoopCommonModes);
5627+
/* Don't release the timer here, since the run loop may be destroyed and
5628+
* the timer lost before we have a chance to decrease the reference count
5629+
* of the attribute */
5630+
self->timer = timer;
5631+
Py_INCREF(Py_None);
5632+
return Py_None;
5633+
}
5634+
5635+
static PyMethodDef Timer_methods[] = {
5636+
{"_timer_start",
5637+
(PyCFunction)Timer__timer_start,
5638+
METH_VARARGS,
5639+
"Initialize and start the timer."
5640+
},
5641+
{NULL} /* Sentinel */
5642+
};
5643+
5644+
static PyTypeObject TimerType = {
5645+
PyObject_HEAD_INIT(NULL)
5646+
0, /*ob_size*/
5647+
"_macosx.Timer", /*tp_name*/
5648+
sizeof(Timer), /*tp_basicsize*/
5649+
0, /*tp_itemsize*/
5650+
(destructor)Timer_dealloc, /*tp_dealloc*/
5651+
0, /*tp_print*/
5652+
0, /*tp_getattr*/
5653+
0, /*tp_setattr*/
5654+
0, /*tp_compare*/
5655+
(reprfunc)Timer_repr, /*tp_repr*/
5656+
0, /*tp_as_number*/
5657+
0, /*tp_as_sequence*/
5658+
0, /*tp_as_mapping*/
5659+
0, /*tp_hash */
5660+
0, /*tp_call*/
5661+
0, /*tp_str*/
5662+
0, /*tp_getattro*/
5663+
0, /*tp_setattro*/
5664+
0, /*tp_as_buffer*/
5665+
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
5666+
Timer_doc, /* tp_doc */
5667+
0, /* tp_traverse */
5668+
0, /* tp_clear */
5669+
0, /* tp_richcompare */
5670+
0, /* tp_weaklistoffset */
5671+
0, /* tp_iter */
5672+
0, /* tp_iternext */
5673+
Timer_methods, /* tp_methods */
5674+
0, /* tp_members */
5675+
0, /* tp_getset */
5676+
0, /* tp_base */
5677+
0, /* tp_dict */
5678+
0, /* tp_descr_get */
5679+
0, /* tp_descr_set */
5680+
0, /* tp_dictoffset */
5681+
0, /* tp_init */
5682+
0, /* tp_alloc */
5683+
Timer_new, /* tp_new */
5684+
};
5685+
54975686
static struct PyMethodDef methods[] = {
54985687
{"show",
54995688
(PyCFunction)show,
@@ -5528,6 +5717,7 @@ void init_macosx(void)
55285717
if (PyType_Ready(&FigureManagerType) < 0) return;
55295718
if (PyType_Ready(&NavigationToolbarType) < 0) return;
55305719
if (PyType_Ready(&NavigationToolbar2Type) < 0) return;
5720+
if (PyType_Ready(&TimerType) < 0) return;
55315721

55325722
m = Py_InitModule4("_macosx",
55335723
methods,
@@ -5540,11 +5730,13 @@ void init_macosx(void)
55405730
Py_INCREF(&FigureManagerType);
55415731
Py_INCREF(&NavigationToolbarType);
55425732
Py_INCREF(&NavigationToolbar2Type);
5733+
Py_INCREF(&TimerType);
55435734
PyModule_AddObject(m, "GraphicsContext", (PyObject*) &GraphicsContextType);
55445735
PyModule_AddObject(m, "FigureCanvas", (PyObject*) &FigureCanvasType);
55455736
PyModule_AddObject(m, "FigureManager", (PyObject*) &FigureManagerType);
55465737
PyModule_AddObject(m, "NavigationToolbar", (PyObject*) &NavigationToolbarType);
55475738
PyModule_AddObject(m, "NavigationToolbar2", (PyObject*) &NavigationToolbar2Type);
5739+
PyModule_AddObject(m, "Timer", (PyObject*) &TimerType);
55485740

55495741
PyOS_InputHook = wait_for_stdin;
55505742
}

0 commit comments

Comments
 (0)