Ticket #55832: _macosx.m.modSL

File _macosx.m.modSL, 94.6 KB (added by fvaccari, 6 years ago)

The modified _macosx.m file

Line 
1#include <Cocoa/Cocoa.h>
2#include <ApplicationServices/ApplicationServices.h>
3#ifndef kCTForegroundColorFromContextAttributeName
4extern const CFStringRef kCTForegroundColorFromContextAttributeName AVAILABLE_MAC_OS_X_VERSION_10_5_AND_LATER;
5#endif
6#include <sys/socket.h>
7#include <Python.h>
8
9#define PYOSINPUTHOOK_REPETITIVE 1 /* Remove this once Python is fixed */
10
11#if PY_MAJOR_VERSION >= 3
12#define PY3K 1
13#else
14#define PY3K 0
15#endif
16
17/* Proper way to check for the OS X version we are compiling for, from
18   http://developer.apple.com/documentation/DeveloperTools/Conceptual/cross_development */
19#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1060
20#define COMPILING_FOR_10_6
21#endif
22#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1070
23#define COMPILING_FOR_10_7
24#endif
25#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 10100
26#define COMPILING_FOR_10_10
27#endif
28
29
30/* CGFloat was defined in Mac OS X 10.5 */
31#ifndef CGFLOAT_DEFINED
32#define CGFloat float
33#endif
34
35
36/* Various NSApplicationDefined event subtypes */
37#define STOP_EVENT_LOOP 2
38#define WINDOW_CLOSING 3
39
40
41/* Keep track of number of windows present
42   Needed to know when to stop the NSApp */
43static long FigureWindowCount = 0;
44
45/* -------------------------- Helper function ---------------------------- */
46
47static void
48_stdin_callback(CFReadStreamRef stream, CFStreamEventType eventType, void* info)
49{
50    CFRunLoopRef runloop = info;
51    CFRunLoopStop(runloop);
52}
53
54static int sigint_fd = -1;
55
56static void _sigint_handler(int sig)
57{
58    const char c = 'i';
59    write(sigint_fd, &c, 1);
60}
61
62static void _sigint_callback(CFSocketRef s,
63                             CFSocketCallBackType type,
64                             CFDataRef address,
65                             const void * data,
66                             void *info)
67{
68    char c;
69    int* interrupted = info;
70    CFSocketNativeHandle handle = CFSocketGetNative(s);
71    CFRunLoopRef runloop = CFRunLoopGetCurrent();
72    read(handle, &c, 1);
73    *interrupted = 1;
74    CFRunLoopStop(runloop);
75}
76
77static CGEventRef _eventtap_callback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon)
78{
79    CFRunLoopRef runloop = refcon;
80    CFRunLoopStop(runloop);
81    return event;
82}
83
84static int wait_for_stdin(void)
85{
86    int interrupted = 0;
87    const UInt8 buffer[] = "/dev/fd/0";
88    const CFIndex n = (CFIndex)strlen((char*)buffer);
89    CFRunLoopRef runloop = CFRunLoopGetCurrent();
90    CFURLRef url = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault,
91                                                           buffer,
92                                                           n,
93                                                           false);
94    CFReadStreamRef stream = CFReadStreamCreateWithFile(kCFAllocatorDefault,
95                                                        url);
96    CFRelease(url);
97
98    CFReadStreamOpen(stream);
99#ifdef PYOSINPUTHOOK_REPETITIVE
100    if (!CFReadStreamHasBytesAvailable(stream))
101    /* This is possible because of how PyOS_InputHook is called from Python */
102    {
103#endif
104        int error;
105        int channel[2];
106        CFSocketRef sigint_socket = NULL;
107        PyOS_sighandler_t py_sigint_handler = NULL;
108        CFStreamClientContext clientContext = {0, NULL, NULL, NULL, NULL};
109        clientContext.info = runloop;
110        CFReadStreamSetClient(stream,
111                              kCFStreamEventHasBytesAvailable,
112                              _stdin_callback,
113                              &clientContext);
114        CFReadStreamScheduleWithRunLoop(stream, runloop, kCFRunLoopDefaultMode);
115        error = socketpair(AF_UNIX, SOCK_STREAM, 0, channel);
116        if (error==0)
117        {
118            CFSocketContext context;
119            context.version = 0;
120            context.info = &interrupted;
121            context.retain = NULL;
122            context.release = NULL;
123            context.copyDescription = NULL;
124            fcntl(channel[0], F_SETFL, O_WRONLY | O_NONBLOCK);
125            sigint_socket = CFSocketCreateWithNative(
126                kCFAllocatorDefault,
127                channel[1],
128                kCFSocketReadCallBack,
129                _sigint_callback,
130                &context);
131            if (sigint_socket)
132            {
133                CFRunLoopSourceRef source;
134                source = CFSocketCreateRunLoopSource(kCFAllocatorDefault,
135                                                     sigint_socket,
136                                                     0);
137                CFRelease(sigint_socket);
138                if (source)
139                {
140                    CFRunLoopAddSource(runloop, source, kCFRunLoopDefaultMode);
141                    CFRelease(source);
142                    sigint_fd = channel[0];
143                    py_sigint_handler = PyOS_setsig(SIGINT, _sigint_handler);
144                }
145            }
146        }
147
148        NSEvent* event;
149        NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
150        while (true) {
151            while (true) {
152                event = [NSApp nextEventMatchingMask: NSAnyEventMask
153                                           untilDate: [NSDate distantPast]
154                                              inMode: NSDefaultRunLoopMode
155                                             dequeue: YES];
156                if (!event) break;
157                [NSApp sendEvent: event];
158            }
159            CFRunLoopRun();
160            if (interrupted || CFReadStreamHasBytesAvailable(stream)) break;
161        }
162        [pool release];
163
164        if (py_sigint_handler) PyOS_setsig(SIGINT, py_sigint_handler);
165        CFReadStreamUnscheduleFromRunLoop(stream,
166                                          runloop,
167                                          kCFRunLoopCommonModes);
168        if (sigint_socket) CFSocketInvalidate(sigint_socket);
169        if (error==0) {
170            close(channel[0]);
171            close(channel[1]);
172        }
173#ifdef PYOSINPUTHOOK_REPETITIVE
174    }
175#endif
176    CFReadStreamClose(stream);
177    CFRelease(stream);
178    if (interrupted) {
179        errno = EINTR;
180        raise(SIGINT);
181        return -1;
182    }
183    return 1;
184}
185
186/* ---------------------------- Cocoa classes ---------------------------- */
187
188@interface WindowServerConnectionManager : NSObject
189{
190}
191+ (WindowServerConnectionManager*)sharedManager;
192- (void)launch:(NSNotification*)notification;
193@end
194
195@interface Window : NSWindow
196{   PyObject* manager;
197}
198- (Window*)initWithContentRect:(NSRect)rect styleMask:(unsigned int)mask backing:(NSBackingStoreType)bufferingType defer:(BOOL)deferCreation withManager: (PyObject*)theManager;
199- (NSRect)constrainFrameRect:(NSRect)rect toScreen:(NSScreen*)screen;
200- (BOOL)closeButtonPressed;
201- (void)dealloc;
202@end
203
204@interface ToolWindow : NSWindow
205{
206}
207- (ToolWindow*)initWithContentRect:(NSRect)rect master:(NSWindow*)window;
208- (void)masterCloses:(NSNotification*)notification;
209- (void)close;
210@end
211
212#ifdef COMPILING_FOR_10_6
213@interface View : NSView <NSWindowDelegate>
214#else
215@interface View : NSView
216#endif
217{   PyObject* canvas;
218    NSRect rubberband;
219    BOOL inside;
220    NSTrackingRectTag tracking;
221    @public double device_scale;
222}
223- (void)dealloc;
224- (void)drawRect:(NSRect)rect;
225- (void)windowDidResize:(NSNotification*)notification;
226- (View*)initWithFrame:(NSRect)rect;
227- (void)setCanvas: (PyObject*)newCanvas;
228- (void)windowWillClose:(NSNotification*)notification;
229- (BOOL)windowShouldClose:(NSNotification*)notification;
230- (BOOL)isFlipped;
231- (void)mouseEntered:(NSEvent*)event;
232- (void)mouseExited:(NSEvent*)event;
233- (void)mouseDown:(NSEvent*)event;
234- (void)mouseUp:(NSEvent*)event;
235- (void)mouseDragged:(NSEvent*)event;
236- (void)mouseMoved:(NSEvent*)event;
237- (void)rightMouseDown:(NSEvent*)event;
238- (void)rightMouseUp:(NSEvent*)event;
239- (void)rightMouseDragged:(NSEvent*)event;
240- (void)otherMouseDown:(NSEvent*)event;
241- (void)otherMouseUp:(NSEvent*)event;
242- (void)otherMouseDragged:(NSEvent*)event;
243- (void)setRubberband:(NSRect)rect;
244- (void)removeRubberband;
245- (const char*)convertKeyEvent:(NSEvent*)event;
246- (void)keyDown:(NSEvent*)event;
247- (void)keyUp:(NSEvent*)event;
248- (void)scrollWheel:(NSEvent *)event;
249- (BOOL)acceptsFirstResponder;
250//- (void)flagsChanged:(NSEvent*)event;
251@end
252
253@interface ScrollableButton : NSButton
254{
255    SEL scrollWheelUpAction;
256    SEL scrollWheelDownAction;
257}
258- (void)setScrollWheelUpAction:(SEL)action;
259- (void)setScrollWheelDownAction:(SEL)action;
260- (void)scrollWheel:(NSEvent *)event;
261@end
262
263@interface MenuItem: NSMenuItem
264{   int index;
265}
266+ (MenuItem*)menuItemWithTitle:(NSString*)title;
267+ (MenuItem*)menuItemSelectAll;
268+ (MenuItem*)menuItemInvertAll;
269+ (MenuItem*)menuItemForAxis:(int)i;
270- (void)toggle:(id)sender;
271- (void)selectAll:(id)sender;
272- (void)invertAll:(id)sender;
273- (int)index;
274@end
275
276/* ---------------------------- Python classes ---------------------------- */
277
278static CGFloat _get_device_scale(CGContextRef cr)
279{
280    CGSize pixelSize = CGContextConvertSizeToDeviceSpace(cr, CGSizeMake(1, 1));
281    return pixelSize.width;
282}
283
284typedef struct {
285    PyObject_HEAD
286    View* view;
287} FigureCanvas;
288
289static PyObject*
290FigureCanvas_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
291{
292    FigureCanvas *self = (FigureCanvas*)type->tp_alloc(type, 0);
293    if (!self) return NULL;
294    self->view = [View alloc];
295    return (PyObject*)self;
296}
297
298static int
299FigureCanvas_init(FigureCanvas *self, PyObject *args, PyObject *kwds)
300{
301    int width;
302    int height;
303    if(!self->view)
304    {
305        PyErr_SetString(PyExc_RuntimeError, "NSView* is NULL");
306        return -1;
307    }
308
309    if(!PyArg_ParseTuple(args, "ii", &width, &height)) return -1;
310
311    NSRect rect = NSMakeRect(0.0, 0.0, width, height);
312    self->view = [self->view initWithFrame: rect];
313    [self->view setCanvas: (PyObject*)self];
314    return 0;
315}
316
317static void
318FigureCanvas_dealloc(FigureCanvas* self)
319{
320    if (self->view)
321    {
322        [self->view setCanvas: NULL];
323        [self->view release];
324    }
325    Py_TYPE(self)->tp_free((PyObject*)self);
326}
327
328static PyObject*
329FigureCanvas_repr(FigureCanvas* self)
330{
331#if PY3K
332    return PyUnicode_FromFormat("FigureCanvas object %p wrapping NSView %p",
333                               (void*)self, (void*)(self->view));
334#else
335    return PyString_FromFormat("FigureCanvas object %p wrapping NSView %p",
336                               (void*)self, (void*)(self->view));
337#endif
338}
339
340static PyObject*
341FigureCanvas_draw(FigureCanvas* self)
342{
343    View* view = self->view;
344
345    if(view) /* The figure may have been closed already */
346    {
347        /* Whereas drawRect creates its own autorelease pool, apparently
348         * [view display] also needs one. Create and release it here. */
349        NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
350        [view display];
351        [pool release];
352    }
353
354    Py_RETURN_NONE;
355}
356
357static PyObject*
358FigureCanvas_invalidate(FigureCanvas* self)
359{
360    View* view = self->view;
361    if(!view)
362    {
363        PyErr_SetString(PyExc_RuntimeError, "NSView* is NULL");
364        return NULL;
365    }
366    [view setNeedsDisplay: YES];
367    Py_RETURN_NONE;
368}
369
370static PyObject*
371FigureCanvas_flush_events(FigureCanvas* self)
372{
373    View* view = self->view;
374    if(!view)
375    {
376        PyErr_SetString(PyExc_RuntimeError, "NSView* is NULL");
377        return NULL;
378    }
379    [view displayIfNeeded];
380    Py_RETURN_NONE;
381}
382
383static PyObject*
384FigureCanvas_set_rubberband(FigureCanvas* self, PyObject *args)
385{
386    View* view = self->view;
387    int x0, y0, x1, y1;
388    NSRect rubberband;
389    if(!view)
390    {
391        PyErr_SetString(PyExc_RuntimeError, "NSView* is NULL");
392        return NULL;
393    }
394    if(!PyArg_ParseTuple(args, "iiii", &x0, &y0, &x1, &y1)) return NULL;
395
396    x0 /= view->device_scale;
397    x1 /= view->device_scale;
398    y0 /= view->device_scale;
399    y1 /= view->device_scale;
400
401    if (x1 > x0)
402    {
403        rubberband.origin.x = x0;
404        rubberband.size.width = x1 - x0;
405    }
406    else
407    {
408        rubberband.origin.x = x1;
409        rubberband.size.width = x0 - x1;
410    }
411    if (y1 > y0)
412    {
413        rubberband.origin.y = y0;
414        rubberband.size.height = y1 - y0;
415    }
416    else
417    {
418        rubberband.origin.y = y1;
419        rubberband.size.height = y0 - y1;
420    }
421
422    [view setRubberband: rubberband];
423    Py_RETURN_NONE;
424}
425
426static PyObject*
427FigureCanvas_remove_rubberband(FigureCanvas* self)
428{
429    View* view = self->view;
430    if(!view)
431    {
432        PyErr_SetString(PyExc_RuntimeError, "NSView* is NULL");
433        return NULL;
434    }
435    [view removeRubberband];
436    Py_RETURN_NONE;
437}
438
439static NSImage* _read_ppm_image(PyObject* obj)
440{
441    int width;
442    int height;
443    const char* data;
444    int n;
445    int i;
446    NSBitmapImageRep* bitmap;
447    unsigned char* bitmapdata;
448
449    if (!obj) return NULL;
450    if (!PyTuple_Check(obj)) return NULL;
451    if (!PyArg_ParseTuple(obj, "iit#", &width, &height, &data, &n)) return NULL;
452    if (width*height*3 != n) return NULL; /* RGB image uses 3 colors / pixel */
453
454    bitmap = [[NSBitmapImageRep alloc]
455                  initWithBitmapDataPlanes: NULL
456                                pixelsWide: width
457                                pixelsHigh: height
458                             bitsPerSample: 8
459                           samplesPerPixel: 3
460                                  hasAlpha: NO
461                                  isPlanar: NO
462                            colorSpaceName: NSDeviceRGBColorSpace
463                              bitmapFormat: 0
464                               bytesPerRow: width*3
465                               bitsPerPixel: 24];
466    if (!bitmap) return NULL;
467    bitmapdata = [bitmap bitmapData];
468    for (i = 0; i < n; i++) bitmapdata[i] = data[i];
469
470    NSSize size = NSMakeSize(width, height);
471    NSImage* image = [[NSImage alloc] initWithSize: size];
472    if (image) [image addRepresentation: bitmap];
473
474    [bitmap release];
475
476    return image;
477}
478
479static PyObject*
480FigureCanvas_start_event_loop(FigureCanvas* self, PyObject* args, PyObject* keywords)
481{
482    float timeout = 0.0;
483
484    static char* kwlist[] = {"timeout", NULL};
485    if(!PyArg_ParseTupleAndKeywords(args, keywords, "f", kwlist, &timeout))
486        return NULL;
487
488    int error;
489    int interrupted = 0;
490    int channel[2];
491    CFSocketRef sigint_socket = NULL;
492    PyOS_sighandler_t py_sigint_handler = NULL;
493
494    CFRunLoopRef runloop = CFRunLoopGetCurrent();
495
496    error = pipe(channel);
497    if (error==0)
498    {
499        CFSocketContext context = {0, NULL, NULL, NULL, NULL};
500        fcntl(channel[1], F_SETFL, O_WRONLY | O_NONBLOCK);
501
502        context.info = &interrupted;
503        sigint_socket = CFSocketCreateWithNative(kCFAllocatorDefault,
504                                                 channel[0],
505                                                 kCFSocketReadCallBack,
506                                                 _sigint_callback,
507                                                 &context);
508        if (sigint_socket)
509        {
510            CFRunLoopSourceRef source;
511            source = CFSocketCreateRunLoopSource(kCFAllocatorDefault,
512                                                 sigint_socket,
513                                                 0);
514            CFRelease(sigint_socket);
515            if (source)
516            {
517                CFRunLoopAddSource(runloop, source, kCFRunLoopDefaultMode);
518                CFRelease(source);
519                sigint_fd = channel[1];
520                py_sigint_handler = PyOS_setsig(SIGINT, _sigint_handler);
521            }
522        }
523        else
524            close(channel[0]);
525    }
526
527    NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
528    NSDate* date =
529        (timeout > 0.0) ? [NSDate dateWithTimeIntervalSinceNow: timeout]
530                        : [NSDate distantFuture];
531    while (true)
532    {   NSEvent* event = [NSApp nextEventMatchingMask: NSAnyEventMask
533                                            untilDate: date
534                                               inMode: NSDefaultRunLoopMode
535                                              dequeue: YES];
536       if (!event || [event type]==NSApplicationDefined) break;
537       [NSApp sendEvent: event];
538    }
539    [pool release];
540
541    if (py_sigint_handler) PyOS_setsig(SIGINT, py_sigint_handler);
542
543    if (sigint_socket) CFSocketInvalidate(sigint_socket);
544    if (error==0) close(channel[1]);
545    if (interrupted) raise(SIGINT);
546
547    Py_RETURN_NONE;
548}
549
550static PyObject*
551FigureCanvas_stop_event_loop(FigureCanvas* self)
552{
553    NSEvent* event = [NSEvent otherEventWithType: NSApplicationDefined
554                                        location: NSZeroPoint
555                                   modifierFlags: 0
556                                       timestamp: 0.0
557                                    windowNumber: 0
558                                         context: nil
559                                         subtype: STOP_EVENT_LOOP
560                                           data1: 0
561                                           data2: 0];
562    [NSApp postEvent: event atStart: true];
563    Py_RETURN_NONE;
564}
565
566static PyMethodDef FigureCanvas_methods[] = {
567    {"draw",
568     (PyCFunction)FigureCanvas_draw,
569     METH_NOARGS,
570     "Draws the canvas."
571    },
572    {"invalidate",
573     (PyCFunction)FigureCanvas_invalidate,
574     METH_NOARGS,
575     "Invalidates the canvas."
576    },
577    {"flush_events",
578     (PyCFunction)FigureCanvas_flush_events,
579     METH_NOARGS,
580     "Flush the GUI events for the figure."
581    },
582    {"set_rubberband",
583     (PyCFunction)FigureCanvas_set_rubberband,
584     METH_VARARGS,
585     "Specifies a new rubberband rectangle and invalidates it."
586    },
587    {"remove_rubberband",
588     (PyCFunction)FigureCanvas_remove_rubberband,
589     METH_NOARGS,
590     "Removes the current rubberband rectangle."
591    },
592    {"start_event_loop",
593     (PyCFunction)FigureCanvas_start_event_loop,
594     METH_KEYWORDS | METH_VARARGS,
595     "Runs the event loop until the timeout or until stop_event_loop is called.\n",
596    },
597    {"stop_event_loop",
598     (PyCFunction)FigureCanvas_stop_event_loop,
599     METH_NOARGS,
600     "Stops the event loop that was started by start_event_loop.\n",
601    },
602    {NULL}  /* Sentinel */
603};
604
605static char FigureCanvas_doc[] =
606"A FigureCanvas object wraps a Cocoa NSView object.\n";
607
608static PyTypeObject FigureCanvasType = {
609    PyVarObject_HEAD_INIT(NULL, 0)
610    "_macosx.FigureCanvas",    /*tp_name*/
611    sizeof(FigureCanvas),      /*tp_basicsize*/
612    0,                         /*tp_itemsize*/
613    (destructor)FigureCanvas_dealloc,     /*tp_dealloc*/
614    0,                         /*tp_print*/
615    0,                         /*tp_getattr*/
616    0,                         /*tp_setattr*/
617    0,                         /*tp_compare*/
618    (reprfunc)FigureCanvas_repr,     /*tp_repr*/
619    0,                         /*tp_as_number*/
620    0,                         /*tp_as_sequence*/
621    0,                         /*tp_as_mapping*/
622    0,                         /*tp_hash */
623    0,                         /*tp_call*/
624    0,                         /*tp_str*/
625    0,                         /*tp_getattro*/
626    0,                         /*tp_setattro*/
627    0,                         /*tp_as_buffer*/
628    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,        /*tp_flags*/
629    FigureCanvas_doc,          /* tp_doc */
630    0,                         /* tp_traverse */
631    0,                         /* tp_clear */
632    0,                         /* tp_richcompare */
633    0,                         /* tp_weaklistoffset */
634    0,                         /* tp_iter */
635    0,                         /* tp_iternext */
636    FigureCanvas_methods,      /* tp_methods */
637    0,                         /* tp_members */
638    0,                         /* tp_getset */
639    0,                         /* tp_base */
640    0,                         /* tp_dict */
641    0,                         /* tp_descr_get */
642    0,                         /* tp_descr_set */
643    0,                         /* tp_dictoffset */
644    (initproc)FigureCanvas_init,      /* tp_init */
645    0,                         /* tp_alloc */
646    FigureCanvas_new,          /* tp_new */
647};
648
649typedef struct {
650    PyObject_HEAD
651    Window* window;
652} FigureManager;
653
654static PyObject*
655FigureManager_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
656{
657    Window* window = [Window alloc];
658    if (!window) return NULL;
659    FigureManager *self = (FigureManager*)type->tp_alloc(type, 0);
660    if (!self)
661    {
662        [window release];
663        return NULL;
664    }
665    self->window = window;
666    ++FigureWindowCount;
667    return (PyObject*)self;
668}
669
670static int
671FigureManager_init(FigureManager *self, PyObject *args, PyObject *kwds)
672{
673    NSRect rect;
674    Window* window;
675    View* view;
676    const char* title;
677    PyObject* size;
678    int width, height;
679    PyObject* obj;
680    FigureCanvas* canvas;
681
682    if(!self->window)
683    {
684        PyErr_SetString(PyExc_RuntimeError, "NSWindow* is NULL");
685        return -1;
686    }
687
688    if(!PyArg_ParseTuple(args, "Os", &obj, &title)) return -1;
689
690    canvas = (FigureCanvas*)obj;
691    view = canvas->view;
692    if (!view) /* Something really weird going on */
693    {
694        PyErr_SetString(PyExc_RuntimeError, "NSView* is NULL");
695        return -1;
696    }
697
698    size = PyObject_CallMethod(obj, "get_width_height", "");
699    if(!size) return -1;
700    if(!PyArg_ParseTuple(size, "ii", &width, &height))
701    {    Py_DECREF(size);
702         return -1;
703    }
704    Py_DECREF(size);
705
706    rect.origin.x = 100;
707    rect.origin.y = 350;
708    rect.size.height = height;
709    rect.size.width = width;
710
711    NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
712    self->window = [self->window initWithContentRect: rect
713                                         styleMask: NSTitledWindowMask
714                                                  | NSClosableWindowMask
715                                                  | NSResizableWindowMask
716                                                  | NSMiniaturizableWindowMask
717                                           backing: NSBackingStoreBuffered
718                                             defer: YES
719                                       withManager: (PyObject*)self];
720    window = self->window;
721    [window setTitle: [NSString stringWithCString: title
722                                         encoding: NSASCIIStringEncoding]];
723
724    [window setAcceptsMouseMovedEvents: YES];
725    [window setDelegate: view];
726    [window makeFirstResponder: view];
727    [[window contentView] addSubview: view];
728
729    [pool release];
730    return 0;
731}
732
733static PyObject*
734FigureManager_repr(FigureManager* self)
735{
736#if PY3K
737    return PyUnicode_FromFormat("FigureManager object %p wrapping NSWindow %p",
738                               (void*) self, (void*)(self->window));
739#else
740    return PyString_FromFormat("FigureManager object %p wrapping NSWindow %p",
741                               (void*) self, (void*)(self->window));
742#endif
743}
744
745static void
746FigureManager_dealloc(FigureManager* self)
747{
748    Window* window = self->window;
749    if(window)
750    {
751        NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
752        [window close];
753        [pool release];
754    }
755    Py_TYPE(self)->tp_free((PyObject*)self);
756}
757
758static PyObject*
759FigureManager_show(FigureManager* self)
760{
761    Window* window = self->window;
762    if(window)
763    {
764        NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
765        [window makeKeyAndOrderFront: nil];
766        [window orderFrontRegardless];
767        [pool release];
768    }
769    Py_RETURN_NONE;
770}
771
772static PyObject*
773FigureManager_destroy(FigureManager* self)
774{
775    Window* window = self->window;
776    if(window)
777    {
778        NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
779        [window close];
780        [pool release];
781        self->window = NULL;
782    }
783    Py_RETURN_NONE;
784}
785
786static PyObject*
787FigureManager_set_window_title(FigureManager* self,
788                               PyObject *args, PyObject *kwds)
789{
790    char* title;
791    if(!PyArg_ParseTuple(args, "es", "UTF-8", &title))
792        return NULL;
793
794    Window* window = self->window;
795    if(window)
796    {
797        NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
798        NSString* ns_title = [[[NSString alloc]
799                               initWithCString: title
800                               encoding: NSUTF8StringEncoding] autorelease];
801        [window setTitle: ns_title];
802        [pool release];
803    }
804    PyMem_Free(title);
805    Py_RETURN_NONE;
806}
807
808static PyObject*
809FigureManager_get_window_title(FigureManager* self)
810{
811    Window* window = self->window;
812    PyObject* result = NULL;
813    if(window)
814    {
815        NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
816        NSString* title = [window title];
817        if (title) {
818            const char* cTitle = [title UTF8String];
819            result = PyUnicode_FromString(cTitle);
820        }
821        [pool release];
822    }
823    if (result) {
824        return result;
825    } else {
826        Py_RETURN_NONE;
827    }
828}
829
830static PyMethodDef FigureManager_methods[] = {
831    {"show",
832     (PyCFunction)FigureManager_show,
833     METH_NOARGS,
834     "Shows the window associated with the figure manager."
835    },
836    {"destroy",
837     (PyCFunction)FigureManager_destroy,
838     METH_NOARGS,
839     "Closes the window associated with the figure manager."
840    },
841    {"set_window_title",
842     (PyCFunction)FigureManager_set_window_title,
843     METH_VARARGS,
844     "Sets the title of the window associated with the figure manager."
845    },
846    {"get_window_title",
847     (PyCFunction)FigureManager_get_window_title,
848     METH_NOARGS,
849     "Returns the title of the window associated with the figure manager."
850    },
851    {NULL}  /* Sentinel */
852};
853
854static char FigureManager_doc[] =
855"A FigureManager object wraps a Cocoa NSWindow object.\n";
856
857static PyTypeObject FigureManagerType = {
858    PyVarObject_HEAD_INIT(NULL, 0)
859    "_macosx.FigureManager",   /*tp_name*/
860    sizeof(FigureManager),     /*tp_basicsize*/
861    0,                         /*tp_itemsize*/
862    (destructor)FigureManager_dealloc,     /*tp_dealloc*/
863    0,                         /*tp_print*/
864    0,                         /*tp_getattr*/
865    0,                         /*tp_setattr*/
866    0,                         /*tp_compare*/
867    (reprfunc)FigureManager_repr,     /*tp_repr*/
868    0,                         /*tp_as_number*/
869    0,                         /*tp_as_sequence*/
870    0,                         /*tp_as_mapping*/
871    0,                         /*tp_hash */
872    0,                         /*tp_call*/
873    0,                         /*tp_str*/
874    0,                         /*tp_getattro*/
875    0,                         /*tp_setattro*/
876    0,                         /*tp_as_buffer*/
877    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,        /*tp_flags*/
878    FigureManager_doc,         /* tp_doc */
879    0,                         /* tp_traverse */
880    0,                         /* tp_clear */
881    0,                         /* tp_richcompare */
882    0,                         /* tp_weaklistoffset */
883    0,                         /* tp_iter */
884    0,                         /* tp_iternext */
885    FigureManager_methods,     /* tp_methods */
886    0,                         /* tp_members */
887    0,                         /* tp_getset */
888    0,                         /* tp_base */
889    0,                         /* tp_dict */
890    0,                         /* tp_descr_get */
891    0,                         /* tp_descr_set */
892    0,                         /* tp_dictoffset */
893    (initproc)FigureManager_init,      /* tp_init */
894    0,                         /* tp_alloc */
895    FigureManager_new,          /* tp_new */
896};
897
898@interface NavigationToolbarHandler : NSObject
899{   PyObject* toolbar;
900}
901- (NavigationToolbarHandler*)initWithToolbar:(PyObject*)toolbar;
902-(void)left:(id)sender;
903-(void)right:(id)sender;
904-(void)up:(id)sender;
905-(void)down:(id)sender;
906-(void)zoominx:(id)sender;
907-(void)zoominy:(id)sender;
908-(void)zoomoutx:(id)sender;
909-(void)zoomouty:(id)sender;
910@end
911
912typedef struct {
913    PyObject_HEAD
914    NSPopUpButton* menu;
915    NavigationToolbarHandler* handler;
916} NavigationToolbar;
917
918@implementation NavigationToolbarHandler
919- (NavigationToolbarHandler*)initWithToolbar:(PyObject*)theToolbar
920{   [self init];
921    toolbar = theToolbar;
922    return self;
923}
924
925-(void)left:(id)sender
926{   PyObject* result;
927    PyGILState_STATE gstate;
928    gstate = PyGILState_Ensure();
929    result = PyObject_CallMethod(toolbar, "panx", "i", -1);
930    if(result)
931        Py_DECREF(result);
932    else
933        PyErr_Print();
934    PyGILState_Release(gstate);
935}
936
937-(void)right:(id)sender
938{   PyObject* result;
939    PyGILState_STATE gstate;
940    gstate = PyGILState_Ensure();
941    result = PyObject_CallMethod(toolbar, "panx", "i", 1);
942    if(result)
943        Py_DECREF(result);
944    else
945        PyErr_Print();
946    PyGILState_Release(gstate);
947}
948
949-(void)up:(id)sender
950{   PyObject* result;
951    PyGILState_STATE gstate;
952    gstate = PyGILState_Ensure();
953    result = PyObject_CallMethod(toolbar, "pany", "i", 1);
954    if(result)
955        Py_DECREF(result);
956    else
957        PyErr_Print();
958    PyGILState_Release(gstate);
959}
960
961-(void)down:(id)sender
962{   PyObject* result;
963    PyGILState_STATE gstate;
964    gstate = PyGILState_Ensure();
965    result = PyObject_CallMethod(toolbar, "pany", "i", -1);
966    if(result)
967        Py_DECREF(result);
968    else
969        PyErr_Print();
970    PyGILState_Release(gstate);
971}
972
973-(void)zoominx:(id)sender
974{   PyObject* result;
975    PyGILState_STATE gstate;
976    gstate = PyGILState_Ensure();
977    result = PyObject_CallMethod(toolbar, "zoomx", "i", 1);
978    if(result)
979        Py_DECREF(result);
980    else
981        PyErr_Print();
982    PyGILState_Release(gstate);
983}
984
985-(void)zoomoutx:(id)sender
986{   PyObject* result;
987    PyGILState_STATE gstate;
988    gstate = PyGILState_Ensure();
989    result = PyObject_CallMethod(toolbar, "zoomx", "i", -1);
990    if(result)
991        Py_DECREF(result);
992    else
993        PyErr_Print();
994    PyGILState_Release(gstate);
995}
996
997-(void)zoominy:(id)sender
998{   PyObject* result;
999    PyGILState_STATE gstate;
1000    gstate = PyGILState_Ensure();
1001    result = PyObject_CallMethod(toolbar, "zoomy", "i", 1);
1002    if(result)
1003        Py_DECREF(result);
1004    else
1005        PyErr_Print();
1006    PyGILState_Release(gstate);
1007}
1008
1009-(void)zoomouty:(id)sender
1010{   PyObject* result;
1011    PyGILState_STATE gstate;
1012    gstate = PyGILState_Ensure();
1013    result = PyObject_CallMethod(toolbar, "zoomy", "i", -1);
1014    if(result)
1015        Py_DECREF(result);
1016    else
1017        PyErr_Print();
1018    PyGILState_Release(gstate);
1019}
1020
1021-(void)save_figure:(id)sender
1022{   PyObject* result;
1023    PyGILState_STATE gstate;
1024    gstate = PyGILState_Ensure();
1025    result = PyObject_CallMethod(toolbar, "save_figure", "");
1026    if(result)
1027        Py_DECREF(result);
1028    else
1029        PyErr_Print();
1030    PyGILState_Release(gstate);
1031}
1032@end
1033
1034static PyObject*
1035NavigationToolbar_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
1036{
1037    NavigationToolbarHandler* handler = [NavigationToolbarHandler alloc];
1038    if (!handler) return NULL;
1039    NavigationToolbar *self = (NavigationToolbar*)type->tp_alloc(type, 0);
1040    if (!self)
1041    {   [handler release];
1042        return NULL;
1043    }
1044    self->handler = handler;
1045    return (PyObject*)self;
1046}
1047
1048static int
1049NavigationToolbar_init(NavigationToolbar *self, PyObject *args, PyObject *kwds)
1050{
1051    int i;
1052    NSRect rect;
1053
1054    const float smallgap = 2;
1055    const float biggap = 10;
1056    const int height = 32;
1057
1058    PyObject* images;
1059    PyObject* obj;
1060
1061    FigureCanvas* canvas;
1062    View* view;
1063
1064    obj = PyObject_GetAttrString((PyObject*)self, "canvas");
1065    if (obj==NULL)
1066    {
1067        PyErr_SetString(PyExc_AttributeError, "Attempt to install toolbar for NULL canvas");
1068        return -1;
1069    }
1070    Py_DECREF(obj); /* Don't increase the reference count */
1071    if (!PyObject_IsInstance(obj, (PyObject*) &FigureCanvasType))
1072    {
1073        PyErr_SetString(PyExc_TypeError, "Attempt to install toolbar for object that is not a FigureCanvas");
1074        return -1;
1075    }
1076    canvas = (FigureCanvas*)obj;
1077    view = canvas->view;
1078    if(!view)
1079    {
1080        PyErr_SetString(PyExc_RuntimeError, "NSView* is NULL");
1081        return -1;
1082    }
1083
1084    if(!PyArg_ParseTuple(args, "O", &images)) return -1;
1085    if(!PyDict_Check(images)) return -1;
1086
1087    NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1088    NSRect bounds = [view bounds];
1089    NSWindow* window = [view window];
1090
1091    bounds.origin.y += height;
1092    [view setFrame: bounds];
1093
1094    bounds.size.height += height;
1095    [window setContentSize: bounds.size];
1096
1097    char* imagenames[9] = {"stock_left",
1098                           "stock_right",
1099                           "stock_zoom-in",
1100                           "stock_zoom-out",
1101                           "stock_up",
1102                           "stock_down",
1103                           "stock_zoom-in",
1104                           "stock_zoom-out",
1105                           "stock_save_as"};
1106
1107    NSString* tooltips[9] = {
1108        @"Pan left with click or wheel mouse (bidirectional)",
1109        @"Pan right with click or wheel mouse (bidirectional)",
1110        @"Zoom In X (shrink the x axis limits) with click or wheel mouse (bidirectional)",
1111        @"Zoom Out X (expand the x axis limits) with click or wheel mouse (bidirectional)",
1112        @"Pan up with click or wheel mouse (bidirectional)",
1113        @"Pan down with click or wheel mouse (bidirectional)",
1114        @"Zoom in Y (shrink the y axis limits) with click or wheel mouse (bidirectional)",
1115        @"Zoom Out Y (expand the y axis limits) with click or wheel mouse (bidirectional)",
1116        @"Save the figure"};
1117
1118    SEL actions[9] = {@selector(left:),
1119                      @selector(right:),
1120                      @selector(zoominx:),
1121                      @selector(zoomoutx:),
1122                      @selector(up:),
1123                      @selector(down:),
1124                      @selector(zoominy:),
1125                      @selector(zoomouty:),
1126                      @selector(save_figure:)};
1127
1128    SEL scroll_actions[9][2] = {{@selector(left:),    @selector(right:)},
1129                                {@selector(left:),    @selector(right:)},
1130                                {@selector(zoominx:), @selector(zoomoutx:)},
1131                                {@selector(zoominx:), @selector(zoomoutx:)},
1132                                {@selector(up:),      @selector(down:)},
1133                                {@selector(up:),      @selector(down:)},
1134                                {@selector(zoominy:), @selector(zoomouty:)},
1135                                {@selector(zoominy:), @selector(zoomouty:)},
1136                                {nil,nil},
1137                               };
1138
1139
1140    rect.size.width = 120;
1141    rect.size.height = 24;
1142    rect.origin.x = biggap;
1143    rect.origin.y = 0.5*(height - rect.size.height);
1144    self->menu = [[NSPopUpButton alloc] initWithFrame: rect
1145                                            pullsDown: YES];
1146    [self->menu setAutoenablesItems: NO];
1147    [[window contentView] addSubview: self->menu];
1148    [self->menu release];
1149    rect.origin.x += rect.size.width + biggap;
1150    rect.size.width = 24;
1151
1152    self->handler = [self->handler initWithToolbar: (PyObject*)self];
1153    for (i = 0; i < 9; i++)
1154    {
1155        NSButton* button;
1156        SEL scrollWheelUpAction = scroll_actions[i][0];
1157        SEL scrollWheelDownAction = scroll_actions[i][1];
1158        if (scrollWheelUpAction && scrollWheelDownAction)
1159        {
1160            ScrollableButton* scrollable_button = [ScrollableButton alloc];
1161            [scrollable_button initWithFrame: rect];
1162            [scrollable_button setScrollWheelUpAction: scrollWheelUpAction];
1163            [scrollable_button setScrollWheelDownAction: scrollWheelDownAction];
1164            button = (NSButton*)scrollable_button;
1165        }
1166        else
1167        {
1168            button = [NSButton alloc];
1169            [button initWithFrame: rect];
1170        }
1171        PyObject* imagedata = PyDict_GetItemString(images, imagenames[i]);
1172        NSImage* image = _read_ppm_image(imagedata);
1173        [button setBezelStyle: NSShadowlessSquareBezelStyle];
1174        [button setButtonType: NSMomentaryLightButton];
1175        if(image)
1176        {
1177            [button setImage: image];
1178            [image release];
1179        }
1180        [button setToolTip: tooltips[i]];
1181        [button setTarget: self->handler];
1182        [button setAction: actions[i]];
1183        [[window contentView] addSubview: button];
1184        [button release];
1185        rect.origin.x += rect.size.width + smallgap;
1186    }
1187    [[window contentView] display];
1188    [pool release];
1189
1190    return 0;
1191}
1192
1193static void
1194NavigationToolbar_dealloc(NavigationToolbar *self)
1195{
1196    [self->handler release];
1197    Py_TYPE(self)->tp_free((PyObject*)self);
1198}
1199
1200static PyObject*
1201NavigationToolbar_repr(NavigationToolbar* self)
1202{
1203#if PY3K
1204    return PyUnicode_FromFormat("NavigationToolbar object %p", (void*)self);
1205#else
1206    return PyString_FromFormat("NavigationToolbar object %p", (void*)self);
1207#endif
1208}
1209
1210static char NavigationToolbar_doc[] =
1211"NavigationToolbar\n";
1212
1213static PyObject*
1214NavigationToolbar_update (NavigationToolbar* self)
1215{
1216    int n;
1217    NSPopUpButton* button = self->menu;
1218    if (!button)
1219    {
1220        PyErr_SetString(PyExc_RuntimeError, "Menu button is NULL");
1221        return NULL;
1222    }
1223
1224    PyObject* canvas = PyObject_GetAttrString((PyObject*)self, "canvas");
1225    if (canvas==NULL)
1226    {
1227        PyErr_SetString(PyExc_AttributeError, "Failed to find canvas");
1228        return NULL;
1229    }
1230    Py_DECREF(canvas); /* Don't keep a reference here */
1231    PyObject* figure = PyObject_GetAttrString(canvas, "figure");
1232    if (figure==NULL)
1233    {
1234        PyErr_SetString(PyExc_AttributeError, "Failed to find figure");
1235        return NULL;
1236    }
1237    Py_DECREF(figure); /* Don't keep a reference here */
1238    PyObject* axes = PyObject_GetAttrString(figure, "axes");
1239    if (axes==NULL)
1240    {
1241        PyErr_SetString(PyExc_AttributeError, "Failed to find figure axes");
1242        return NULL;
1243    }
1244    Py_DECREF(axes); /* Don't keep a reference here */
1245    if (!PyList_Check(axes))
1246    {
1247        PyErr_SetString(PyExc_TypeError, "Figure axes is not a list");
1248        return NULL;
1249    }
1250    n = PyList_GET_SIZE(axes);
1251
1252    NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1253    [button removeAllItems];
1254
1255    NSMenu* menu = [button menu];
1256    [menu addItem: [MenuItem menuItemWithTitle: @"Axes"]];
1257
1258    if (n==0)
1259    {
1260        [button setEnabled: NO];
1261    }
1262    else
1263    {
1264        int i;
1265        [menu addItem: [MenuItem menuItemSelectAll]];
1266        [menu addItem: [MenuItem menuItemInvertAll]];
1267        [menu addItem: [NSMenuItem separatorItem]];
1268        for (i = 0; i < n; i++)
1269        {
1270            [menu addItem: [MenuItem menuItemForAxis: i]];
1271        }
1272        [button setEnabled: YES];
1273    }
1274    [pool release];
1275    Py_RETURN_NONE;
1276}
1277
1278static PyObject*
1279NavigationToolbar_get_active (NavigationToolbar* self)
1280{
1281    NSPopUpButton* button = self->menu;
1282    if (!button)
1283    {
1284        PyErr_SetString(PyExc_RuntimeError, "Menu button is NULL");
1285        return NULL;
1286    }
1287    NSMenu* menu = [button menu];
1288    NSArray* items = [menu itemArray];
1289    size_t n = [items count];
1290    int* states = calloc(n, sizeof(int));
1291    if (!states)
1292    {
1293        PyErr_SetString(PyExc_RuntimeError, "calloc failed");
1294        return NULL;
1295    }
1296    int i;
1297    unsigned int m = 0;
1298    NSEnumerator* enumerator = [items objectEnumerator];
1299    MenuItem* item;
1300    while ((item = [enumerator nextObject]))
1301    {
1302        if ([item isSeparatorItem]) continue;
1303        i = [item index];
1304        if (i < 0) continue;
1305        if ([item state]==NSOnState)
1306        {
1307            states[i] = 1;
1308            m++;
1309        }
1310    }
1311    Py_ssize_t list_index = 0;
1312    PyObject* list = PyList_New(m);
1313    size_t state_index;
1314    for (state_index = 0; state_index < n; state_index++)
1315    {
1316        if(states[state_index]==1)
1317        {
1318            PyList_SET_ITEM(list, list_index++, PyLong_FromSize_t(state_index));
1319        }
1320    }
1321    free(states);
1322    return list;
1323}
1324
1325static PyMethodDef NavigationToolbar_methods[] = {
1326    {"update",
1327     (PyCFunction)NavigationToolbar_update,
1328     METH_NOARGS,
1329     "Updates the toolbar menu."
1330    },
1331    {"get_active",
1332     (PyCFunction)NavigationToolbar_get_active,
1333     METH_NOARGS,
1334     "Returns a list of integers identifying which items in the menu are selected."
1335    },
1336    {NULL}  /* Sentinel */
1337};
1338
1339static PyTypeObject NavigationToolbarType = {
1340    PyVarObject_HEAD_INIT(NULL, 0)
1341    "_macosx.NavigationToolbar", /*tp_name*/
1342    sizeof(NavigationToolbar), /*tp_basicsize*/
1343    0,                         /*tp_itemsize*/
1344    (destructor)NavigationToolbar_dealloc,     /*tp_dealloc*/
1345    0,                         /*tp_print*/
1346    0,                         /*tp_getattr*/
1347    0,                         /*tp_setattr*/
1348    0,                         /*tp_compare*/
1349    (reprfunc)NavigationToolbar_repr,     /*tp_repr*/
1350    0,                         /*tp_as_number*/
1351    0,                         /*tp_as_sequence*/
1352    0,                         /*tp_as_mapping*/
1353    0,                         /*tp_hash */
1354    0,                         /*tp_call*/
1355    0,                         /*tp_str*/
1356    0,                         /*tp_getattro*/
1357    0,                         /*tp_setattro*/
1358    0,                         /*tp_as_buffer*/
1359    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,        /*tp_flags*/
1360    NavigationToolbar_doc,     /* tp_doc */
1361    0,                         /* tp_traverse */
1362    0,                         /* tp_clear */
1363    0,                         /* tp_richcompare */
1364    0,                         /* tp_weaklistoffset */
1365    0,                         /* tp_iter */
1366    0,                         /* tp_iternext */
1367    NavigationToolbar_methods, /* tp_methods */
1368    0,                         /* tp_members */
1369    0,                         /* tp_getset */
1370    0,                         /* tp_base */
1371    0,                         /* tp_dict */
1372    0,                         /* tp_descr_get */
1373    0,                         /* tp_descr_set */
1374    0,                         /* tp_dictoffset */
1375    (initproc)NavigationToolbar_init,      /* tp_init */
1376    0,                         /* tp_alloc */
1377    NavigationToolbar_new,     /* tp_new */
1378};
1379
1380@interface NavigationToolbar2Handler : NSObject
1381{   PyObject* toolbar;
1382    NSButton* panbutton;
1383    NSButton* zoombutton;
1384}
1385- (NavigationToolbar2Handler*)initWithToolbar:(PyObject*)toolbar;
1386- (void)installCallbacks:(SEL[7])actions forButtons: (NSButton*[7])buttons;
1387- (void)home:(id)sender;
1388- (void)back:(id)sender;
1389- (void)forward:(id)sender;
1390- (void)pan:(id)sender;
1391- (void)zoom:(id)sender;
1392- (void)configure_subplots:(id)sender;
1393- (void)save_figure:(id)sender;
1394@end
1395
1396typedef struct {
1397    PyObject_HEAD
1398    NSPopUpButton* menu;
1399    NSText* messagebox;
1400    NavigationToolbar2Handler* handler;
1401} NavigationToolbar2;
1402
1403@implementation NavigationToolbar2Handler
1404- (NavigationToolbar2Handler*)initWithToolbar:(PyObject*)theToolbar
1405{   [self init];
1406    toolbar = theToolbar;
1407    return self;
1408}
1409
1410- (void)installCallbacks:(SEL[7])actions forButtons: (NSButton*[7])buttons
1411{
1412    int i;
1413    for (i = 0; i < 7; i++)
1414    {
1415        SEL action = actions[i];
1416        NSButton* button = buttons[i];
1417        [button setTarget: self];
1418        [button setAction: action];
1419        if (action==@selector(pan:)) panbutton = button;
1420        if (action==@selector(zoom:)) zoombutton = button;
1421    }
1422}
1423
1424-(void)home:(id)sender
1425{   PyObject* result;
1426    PyGILState_STATE gstate;
1427    gstate = PyGILState_Ensure();
1428    result = PyObject_CallMethod(toolbar, "home", "");
1429    if(result)
1430        Py_DECREF(result);
1431    else
1432        PyErr_Print();
1433    PyGILState_Release(gstate);
1434}
1435
1436-(void)back:(id)sender
1437{   PyObject* result;
1438    PyGILState_STATE gstate;
1439    gstate = PyGILState_Ensure();
1440    result = PyObject_CallMethod(toolbar, "back", "");
1441    if(result)
1442        Py_DECREF(result);
1443    else
1444        PyErr_Print();
1445    PyGILState_Release(gstate);
1446}
1447
1448-(void)forward:(id)sender
1449{   PyObject* result;
1450    PyGILState_STATE gstate;
1451    gstate = PyGILState_Ensure();
1452    result = PyObject_CallMethod(toolbar, "forward", "");
1453    if(result)
1454        Py_DECREF(result);
1455    else
1456        PyErr_Print();
1457    PyGILState_Release(gstate);
1458}
1459
1460-(void)pan:(id)sender
1461{   PyObject* result;
1462    PyGILState_STATE gstate;
1463    if ([sender state])
1464    {
1465        if (zoombutton) [zoombutton setState: NO];
1466    }
1467    gstate = PyGILState_Ensure();
1468    result = PyObject_CallMethod(toolbar, "pan", "");
1469    if(result)
1470        Py_DECREF(result);
1471    else
1472        PyErr_Print();
1473    PyGILState_Release(gstate);
1474}
1475
1476-(void)zoom:(id)sender
1477{   PyObject* result;
1478    PyGILState_STATE gstate;
1479    if ([sender state])
1480    {
1481        if (panbutton) [panbutton setState: NO];
1482    }
1483    gstate = PyGILState_Ensure();
1484    result = PyObject_CallMethod(toolbar, "zoom", "");
1485    if(result)
1486        Py_DECREF(result);
1487    else
1488        PyErr_Print();
1489    PyGILState_Release(gstate);
1490}
1491
1492-(void)configure_subplots:(id)sender
1493{   PyObject* canvas;
1494    View* view;
1495    PyObject* size;
1496    NSRect rect;
1497    int width, height;
1498
1499    rect.origin.x = 100;
1500    rect.origin.y = 350;
1501    PyGILState_STATE gstate = PyGILState_Ensure();
1502    PyObject* master = PyObject_GetAttrString(toolbar, "canvas");
1503    if (master==nil)
1504    {
1505        PyErr_Print();
1506        PyGILState_Release(gstate);
1507        return;
1508    }
1509    canvas = PyObject_CallMethod(toolbar, "prepare_configure_subplots", "");
1510    if(!canvas)
1511    {
1512        PyErr_Print();
1513        Py_DECREF(master);
1514        PyGILState_Release(gstate);
1515        return;
1516    }
1517
1518    view = ((FigureCanvas*)canvas)->view;
1519    if (!view) /* Something really weird going on */
1520    {
1521        PyErr_SetString(PyExc_RuntimeError, "NSView* is NULL");
1522        PyErr_Print();
1523        Py_DECREF(canvas);
1524        Py_DECREF(master);
1525        PyGILState_Release(gstate);
1526        return;
1527    }
1528
1529    size = PyObject_CallMethod(canvas, "get_width_height", "");
1530    Py_DECREF(canvas);
1531    if(!size)
1532    {
1533        PyErr_Print();
1534        Py_DECREF(master);
1535        PyGILState_Release(gstate);
1536        return;
1537    }
1538
1539    int ok = PyArg_ParseTuple(size, "ii", &width, &height);
1540    Py_DECREF(size);
1541    if (!ok)
1542    {
1543        PyErr_Print();
1544        Py_DECREF(master);
1545        PyGILState_Release(gstate);
1546        return;
1547    }
1548
1549    NSWindow* mw = [((FigureCanvas*)master)->view window];
1550    Py_DECREF(master);
1551    PyGILState_Release(gstate);
1552
1553    rect.size.width = width;
1554    rect.size.height = height;
1555
1556    ToolWindow* window = [ [ToolWindow alloc] initWithContentRect: rect
1557                                                           master: mw];
1558    [window setContentView: view];
1559    [view release];
1560    [window makeKeyAndOrderFront: nil];
1561}
1562
1563-(void)save_figure:(id)sender
1564{   PyObject* result;
1565    PyGILState_STATE gstate;
1566    gstate = PyGILState_Ensure();
1567    result = PyObject_CallMethod(toolbar, "save_figure", "");
1568    if(result)
1569        Py_DECREF(result);
1570    else
1571        PyErr_Print();
1572    PyGILState_Release(gstate);
1573}
1574@end
1575
1576static PyObject*
1577NavigationToolbar2_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
1578{
1579    NavigationToolbar2Handler* handler = [NavigationToolbar2Handler alloc];
1580    if (!handler) return NULL;
1581    NavigationToolbar2 *self = (NavigationToolbar2*)type->tp_alloc(type, 0);
1582    if (!self)
1583    {
1584        [handler release];
1585        return NULL;
1586    }
1587    self->handler = handler;
1588    return (PyObject*)self;
1589}
1590
1591static int
1592NavigationToolbar2_init(NavigationToolbar2 *self, PyObject *args, PyObject *kwds)
1593{
1594    PyObject* obj;
1595    FigureCanvas* canvas;
1596    View* view;
1597
1598    int i;
1599    NSRect rect;
1600    NSSize size;
1601    NSSize scale;
1602
1603    const float gap = 2;
1604    const int height = 36;
1605    const int imagesize = 24;
1606
1607    const char* basedir;
1608
1609    obj = PyObject_GetAttrString((PyObject*)self, "canvas");
1610    if (obj==NULL)
1611    {
1612        PyErr_SetString(PyExc_AttributeError, "Attempt to install toolbar for NULL canvas");
1613        return -1;
1614    }
1615    Py_DECREF(obj); /* Don't increase the reference count */
1616    if (!PyObject_IsInstance(obj, (PyObject*) &FigureCanvasType))
1617    {
1618        PyErr_SetString(PyExc_TypeError, "Attempt to install toolbar for object that is not a FigureCanvas");
1619        return -1;
1620    }
1621    canvas = (FigureCanvas*)obj;
1622    view = canvas->view;
1623    if(!view)
1624    {
1625        PyErr_SetString(PyExc_RuntimeError, "NSView* is NULL");
1626        return -1;
1627    }
1628
1629    if(!PyArg_ParseTuple(args, "s", &basedir)) return -1;
1630
1631    NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1632    NSRect bounds = [view bounds];
1633    NSWindow* window = [view window];
1634
1635    bounds.origin.y += height;
1636    [view setFrame: bounds];
1637
1638    bounds.size.height += height;
1639    [window setContentSize: bounds.size];
1640
1641    NSString* dir = [NSString stringWithCString: basedir
1642                                       encoding: NSASCIIStringEncoding];
1643
1644    NSButton* buttons[7];
1645
1646    NSString* images[7] = {@"home.pdf",
1647                           @"back.pdf",
1648                           @"forward.pdf",
1649                           @"move.pdf",
1650                           @"zoom_to_rect.pdf",
1651                           @"subplots.pdf",
1652                           @"filesave.pdf"};
1653
1654    NSString* tooltips[7] = {@"Reset original view",
1655                             @"Back to  previous view",
1656                             @"Forward to next view",
1657                             @"Pan axes with left mouse, zoom with right",
1658                             @"Zoom to rectangle",
1659                             @"Configure subplots",
1660                             @"Save the figure"};
1661
1662    SEL actions[7] = {@selector(home:),
1663                      @selector(back:),
1664                      @selector(forward:),
1665                      @selector(pan:),
1666                      @selector(zoom:),
1667                      @selector(configure_subplots:),
1668                      @selector(save_figure:)};
1669
1670    NSButtonType buttontypes[7] = {NSMomentaryLightButton,
1671                                   NSMomentaryLightButton,
1672                                   NSMomentaryLightButton,
1673                                   NSPushOnPushOffButton,
1674                                   NSPushOnPushOffButton,
1675                                   NSMomentaryLightButton,
1676                                   NSMomentaryLightButton};
1677
1678    rect.origin.x = 0;
1679    rect.origin.y = 0;
1680    rect.size.width = imagesize;
1681    rect.size.height = imagesize;
1682#ifdef COMPILING_FOR_10_7
1683    rect = [window convertRectToBacking: rect];
1684#endif
1685    size = rect.size;
1686    scale.width = imagesize / size.width;
1687    scale.height = imagesize / size.height;
1688
1689    rect.size.width = 32;
1690    rect.size.height = 32;
1691    rect.origin.x = gap;
1692    rect.origin.y = 0.5*(height - rect.size.height);
1693
1694    for (i = 0; i < 7; i++)
1695    {
1696        NSString* filename = [dir stringByAppendingPathComponent: images[i]];
1697        NSImage* image = [[NSImage alloc] initWithContentsOfFile: filename];
1698        buttons[i] = [[NSButton alloc] initWithFrame: rect];
1699        [image setSize: size];
1700        [buttons[i] setBezelStyle: NSShadowlessSquareBezelStyle];
1701        [buttons[i] setButtonType: buttontypes[i]];
1702        [buttons[i] setImage: image];
1703        [buttons[i] scaleUnitSquareToSize: scale];
1704        [buttons[i] setImagePosition: NSImageOnly];
1705        [buttons[i] setToolTip: tooltips[i]];
1706        [[window contentView] addSubview: buttons[i]];
1707        [buttons[i] release];
1708        [image release];
1709        rect.origin.x += rect.size.width + gap;
1710    }
1711
1712    self->handler = [self->handler initWithToolbar: (PyObject*)self];
1713    [self->handler installCallbacks: actions forButtons: buttons];
1714
1715    NSFont* font = [NSFont systemFontOfSize: 0.0];
1716    rect.size.width = 300;
1717    rect.size.height = 0;
1718    rect.origin.x += height;
1719    NSText* messagebox = [[NSText alloc] initWithFrame: rect];
1720    [messagebox setFont: font];
1721    [messagebox setDrawsBackground: NO];
1722    [messagebox setSelectable: NO];
1723    /* if selectable, the messagebox can become first responder,
1724     * which is not supposed to happen */
1725    rect = [messagebox frame];
1726    rect.origin.y = 0.5 * (height - rect.size.height);
1727    [messagebox setFrameOrigin: rect.origin];
1728    [[window contentView] addSubview: messagebox];
1729    [messagebox release];
1730    [[window contentView] display];
1731
1732    [pool release];
1733
1734    self->messagebox = messagebox;
1735    return 0;
1736}
1737
1738static void
1739NavigationToolbar2_dealloc(NavigationToolbar2 *self)
1740{
1741    [self->handler release];
1742    Py_TYPE(self)->tp_free((PyObject*)self);
1743}
1744
1745static PyObject*
1746NavigationToolbar2_repr(NavigationToolbar2* self)
1747{
1748#if PY3K
1749    return PyUnicode_FromFormat("NavigationToolbar2 object %p", (void*)self);
1750#else
1751    return PyString_FromFormat("NavigationToolbar2 object %p", (void*)self);
1752#endif
1753}
1754
1755static char NavigationToolbar2_doc[] =
1756"NavigationToolbar2\n";
1757
1758static PyObject*
1759NavigationToolbar2_set_message(NavigationToolbar2 *self, PyObject* args)
1760{
1761    const char* message;
1762
1763#if PY3K
1764    if(!PyArg_ParseTuple(args, "y", &message)) return NULL;
1765#else
1766    if(!PyArg_ParseTuple(args, "s", &message)) return NULL;
1767#endif
1768
1769    NSText* messagebox = self->messagebox;
1770
1771    if (messagebox)
1772    {   NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1773        NSString* text = [NSString stringWithUTF8String: message];
1774        [messagebox setString: text];
1775        [pool release];
1776    }
1777
1778    Py_RETURN_NONE;
1779}
1780
1781static PyMethodDef NavigationToolbar2_methods[] = {
1782    {"set_message",
1783     (PyCFunction)NavigationToolbar2_set_message,
1784     METH_VARARGS,
1785     "Set the message to be displayed on the toolbar."
1786    },
1787    {NULL}  /* Sentinel */
1788};
1789
1790static PyTypeObject NavigationToolbar2Type = {
1791    PyVarObject_HEAD_INIT(NULL, 0)
1792    "_macosx.NavigationToolbar2", /*tp_name*/
1793    sizeof(NavigationToolbar2), /*tp_basicsize*/
1794    0,                         /*tp_itemsize*/
1795    (destructor)NavigationToolbar2_dealloc,     /*tp_dealloc*/
1796    0,                         /*tp_print*/
1797    0,                         /*tp_getattr*/
1798    0,                         /*tp_setattr*/
1799    0,                         /*tp_compare*/
1800    (reprfunc)NavigationToolbar2_repr,     /*tp_repr*/
1801    0,                         /*tp_as_number*/
1802    0,                         /*tp_as_sequence*/
1803    0,                         /*tp_as_mapping*/
1804    0,                         /*tp_hash */
1805    0,                         /*tp_call*/
1806    0,                         /*tp_str*/
1807    0,                         /*tp_getattro*/
1808    0,                         /*tp_setattro*/
1809    0,                         /*tp_as_buffer*/
1810    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,        /*tp_flags*/
1811    NavigationToolbar2_doc,    /* tp_doc */
1812    0,                         /* tp_traverse */
1813    0,                         /* tp_clear */
1814    0,                         /* tp_richcompare */
1815    0,                         /* tp_weaklistoffset */
1816    0,                         /* tp_iter */
1817    0,                         /* tp_iternext */
1818    NavigationToolbar2_methods, /* tp_methods */
1819    0,                         /* tp_members */
1820    0,                         /* tp_getset */
1821    0,                         /* tp_base */
1822    0,                         /* tp_dict */
1823    0,                         /* tp_descr_get */
1824    0,                         /* tp_descr_set */
1825    0,                         /* tp_dictoffset */
1826    (initproc)NavigationToolbar2_init,      /* tp_init */
1827    0,                         /* tp_alloc */
1828    NavigationToolbar2_new,    /* tp_new */
1829};
1830
1831static PyObject*
1832choose_save_file(PyObject* unused, PyObject* args)
1833{
1834    int result;
1835    const char* title;
1836    char* default_filename;
1837    if(!PyArg_ParseTuple(args, "ses", &title, "UTF-8", &default_filename))
1838        return NULL;
1839
1840    NSSavePanel* panel = [NSSavePanel savePanel];
1841    [panel setTitle: [NSString stringWithCString: title
1842                                        encoding: NSASCIIStringEncoding]];
1843    NSString* ns_default_filename =
1844        [[NSString alloc]
1845         initWithCString: default_filename
1846         encoding: NSUTF8StringEncoding];
1847    PyMem_Free(default_filename);
1848#ifdef COMPILING_FOR_10_6
1849    [panel setNameFieldStringValue: ns_default_filename];
1850    result = [panel runModal];
1851#else
1852    result = [panel runModalForDirectory: nil file: ns_default_filename];
1853#endif
1854    [ns_default_filename release];
1855#ifdef COMPILING_FOR_10_10
1856    if (result == NSModalResponseOK)
1857#else
1858    if (result == NSOKButton)
1859#endif
1860    {
1861#ifdef COMPILING_FOR_10_6
1862        NSURL* url = [panel URL];
1863        NSString* filename = [url path];
1864        if (!filename) {
1865            PyErr_SetString(PyExc_RuntimeError, "Failed to obtain filename");
1866            return 0;
1867        }
1868#else
1869        NSString* filename = [panel filename];
1870#endif
1871        unsigned int n = [filename length];
1872        unichar* buffer = malloc(n*sizeof(unichar));
1873        [filename getCharacters: buffer];
1874#if PY3K
1875        PyObject* string =  PyUnicode_FromKindAndData(PyUnicode_2BYTE_KIND, buffer, n);
1876#else
1877        PyObject* string =  PyUnicode_FromUnicode(buffer, n);
1878#endif
1879        free(buffer);
1880        return string;
1881    }
1882    Py_RETURN_NONE;
1883}
1884
1885static PyObject*
1886set_cursor(PyObject* unused, PyObject* args)
1887{
1888    int i;
1889    if(!PyArg_ParseTuple(args, "i", &i)) return NULL;
1890    switch (i)
1891    { case 0: [[NSCursor pointingHandCursor] set]; break;
1892      case 1: [[NSCursor arrowCursor] set]; break;
1893      case 2: [[NSCursor crosshairCursor] set]; break;
1894      case 3: [[NSCursor openHandCursor] set]; break;
1895      /* OSX handles busy state itself so no need to set a cursor here */
1896      case 4: break;
1897      default: return NULL;
1898    }
1899    Py_RETURN_NONE;
1900}
1901
1902@implementation WindowServerConnectionManager
1903static WindowServerConnectionManager *sharedWindowServerConnectionManager = nil;
1904
1905+ (WindowServerConnectionManager *)sharedManager
1906{
1907    if (sharedWindowServerConnectionManager == nil)
1908    {
1909        sharedWindowServerConnectionManager = [[super allocWithZone:NULL] init];
1910    }
1911    return sharedWindowServerConnectionManager;
1912}
1913
1914+ (id)allocWithZone:(NSZone *)zone
1915{
1916    return [[self sharedManager] retain];
1917}
1918
1919+ (id)copyWithZone:(NSZone *)zone
1920{
1921    return self;
1922}
1923
1924+ (id)retain
1925{
1926    return self;
1927}
1928
1929- (NSUInteger)retainCount
1930{
1931    return NSUIntegerMax;  //denotes an object that cannot be released
1932}
1933
1934- (oneway void)release
1935{
1936    // Don't release a singleton object
1937}
1938
1939- (id)autorelease
1940{
1941    return self;
1942}
1943
1944- (void)launch:(NSNotification*)notification
1945{
1946    CFRunLoopRef runloop;
1947    CFMachPortRef port;
1948    CFRunLoopSourceRef source;
1949    NSDictionary* dictionary = [notification userInfo];
1950    if (! [[dictionary valueForKey:@"NSApplicationName"]
1951           isEqualToString:@"Python"])
1952        return;
1953    NSNumber* psnLow = [dictionary valueForKey: @"NSApplicationProcessSerialNumberLow"];
1954    NSNumber* psnHigh = [dictionary valueForKey: @"NSApplicationProcessSerialNumberHigh"];
1955    ProcessSerialNumber psn;
1956    psn.highLongOfPSN = [psnHigh intValue];
1957    psn.lowLongOfPSN = [psnLow intValue];
1958    runloop = CFRunLoopGetCurrent();
1959    port = CGEventTapCreateForPSN(&psn,
1960                                  kCGHeadInsertEventTap,
1961                                  kCGEventTapOptionListenOnly,
1962                                  kCGEventMaskForAllEvents,
1963                                  &_eventtap_callback,
1964                                  runloop);
1965    source = CFMachPortCreateRunLoopSource(kCFAllocatorDefault,
1966                                           port,
1967                                           0);
1968    CFRunLoopAddSource(runloop, source, kCFRunLoopDefaultMode);
1969    CFRelease(port);
1970}
1971@end
1972
1973@implementation Window
1974- (Window*)initWithContentRect:(NSRect)rect styleMask:(unsigned int)mask backing:(NSBackingStoreType)bufferingType defer:(BOOL)deferCreation withManager: (PyObject*)theManager
1975{
1976    self = [super initWithContentRect: rect
1977                            styleMask: mask
1978                              backing: bufferingType
1979                                defer: deferCreation];
1980    manager = theManager;
1981    Py_INCREF(manager);
1982    return self;
1983}
1984
1985- (NSRect)constrainFrameRect:(NSRect)rect toScreen:(NSScreen*)screen
1986{
1987    /* Allow window sizes larger than the screen */
1988    NSRect suggested = [super constrainFrameRect: rect toScreen: screen];
1989    const CGFloat difference = rect.size.height - suggested.size.height;
1990    suggested.origin.y -= difference;
1991    suggested.size.height += difference;
1992    return suggested;
1993}
1994
1995- (BOOL)closeButtonPressed
1996{
1997    PyObject* result;
1998    PyGILState_STATE gstate;
1999    gstate = PyGILState_Ensure();
2000    result = PyObject_CallMethod(manager, "close", "");
2001    if(result)
2002        Py_DECREF(result);
2003    else
2004        PyErr_Print();
2005    PyGILState_Release(gstate);
2006    return YES;
2007}
2008
2009- (void)close
2010{
2011    [super close];
2012    --FigureWindowCount;
2013    if (!FigureWindowCount) [NSApp stop: self];
2014    /* This is needed for show(), which should exit from [NSApp run]
2015     * after all windows are closed.
2016     */
2017}
2018
2019- (void)dealloc
2020{
2021    PyGILState_STATE gstate;
2022    gstate = PyGILState_Ensure();
2023    Py_DECREF(manager);
2024    PyGILState_Release(gstate);
2025    /* The reference count of the view that was added as a subview to the
2026     * content view of this window was increased during the call to addSubview,
2027     * and is decreased during the call to [super dealloc].
2028     */
2029    [super dealloc];
2030}
2031@end
2032
2033@implementation ToolWindow
2034- (ToolWindow*)initWithContentRect:(NSRect)rect master:(NSWindow*)window
2035{
2036    [self initWithContentRect: rect
2037                    styleMask: NSTitledWindowMask
2038                             | NSClosableWindowMask
2039                             | NSResizableWindowMask
2040                             | NSMiniaturizableWindowMask
2041                      backing: NSBackingStoreBuffered
2042                        defer: YES];
2043    [self setTitle: @"Subplot Configuration Tool"];
2044    [[NSNotificationCenter defaultCenter] addObserver: self
2045                                             selector: @selector(masterCloses:)
2046                                                 name: NSWindowWillCloseNotification
2047                                               object: window];
2048    return self;
2049}
2050
2051- (void)masterCloses:(NSNotification*)notification
2052{
2053    [self close];
2054}
2055
2056- (void)close
2057{
2058    [[NSNotificationCenter defaultCenter] removeObserver: self];
2059    [super close];
2060}
2061@end
2062
2063@implementation View
2064- (BOOL)isFlipped
2065{
2066    return NO;
2067}
2068
2069- (View*)initWithFrame:(NSRect)rect
2070{
2071    self = [super initWithFrame: rect];
2072    rubberband = NSZeroRect;
2073    inside = false;
2074    tracking = 0;
2075    device_scale = 1;
2076    return self;
2077}
2078
2079- (void)dealloc
2080{
2081    FigureCanvas* fc = (FigureCanvas*)canvas;
2082    if (fc) fc->view = NULL;
2083    [self removeTrackingRect: tracking];
2084    [super dealloc];
2085}
2086
2087- (void)setCanvas: (PyObject*)newCanvas
2088{
2089    canvas = newCanvas;
2090}
2091
2092static void _buffer_release(void* info, const void* data, size_t size) {
2093    PyBuffer_Release((Py_buffer *)info);
2094}
2095
2096static int _copy_agg_buffer(CGContextRef cr, PyObject *renderer)
2097{
2098    Py_buffer buffer;
2099
2100    if (PyObject_GetBuffer(renderer, &buffer, PyBUF_CONTIG_RO) == -1) {
2101        PyErr_Print();
2102        return 1;
2103    }
2104
2105    if (buffer.ndim != 3 || buffer.shape[2] != 4) {
2106        PyBuffer_Release(&buffer);
2107        return 1;
2108    }
2109
2110    const Py_ssize_t nrows = buffer.shape[0];
2111    const Py_ssize_t ncols = buffer.shape[1];
2112    const size_t bytesPerComponent = 1;
2113    const size_t bitsPerComponent = 8 * bytesPerComponent;
2114    const size_t nComponents = 4; /* red, green, blue, alpha */
2115    const size_t bitsPerPixel = bitsPerComponent * nComponents;
2116    const size_t bytesPerRow = nComponents * bytesPerComponent * ncols;
2117
2118    CGColorSpaceRef colorspace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
2119    if (!colorspace) {
2120        PyBuffer_Release(&buffer);
2121        return 1;
2122    }
2123
2124    CGDataProviderRef provider = CGDataProviderCreateWithData(&buffer,
2125                                                              buffer.buf,
2126                                                              buffer.len,
2127                                                              _buffer_release);
2128    if (!provider) {
2129        PyBuffer_Release(&buffer);
2130        CGColorSpaceRelease(colorspace);
2131        return 1;
2132    }
2133
2134    CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault | kCGImageAlphaLast;
2135    CGImageRef bitmap = CGImageCreate(ncols,
2136                                      nrows,
2137                                      bitsPerComponent,
2138                                      bitsPerPixel,
2139                                      bytesPerRow,
2140                                      colorspace,
2141                                      bitmapInfo,
2142                                      provider,
2143                                      NULL,
2144                                      false,
2145                                      kCGRenderingIntentDefault);
2146    CGColorSpaceRelease(colorspace);
2147    CGDataProviderRelease(provider);
2148
2149    if (!bitmap) {
2150        PyBuffer_Release(&buffer);
2151        return 1;
2152    }
2153
2154    CGFloat deviceScale = _get_device_scale(cr);
2155    CGContextSaveGState(cr);
2156    CGContextDrawImage(cr, CGRectMake(0, 0, ncols/deviceScale, nrows/deviceScale), bitmap);
2157    CGImageRelease(bitmap);
2158    CGContextRestoreGState(cr);
2159
2160    return 0;
2161}
2162
2163-(void)drawRect:(NSRect)rect
2164{
2165    PyObject* renderer = NULL;
2166    PyObject* renderer_buffer = NULL;
2167
2168    PyGILState_STATE gstate = PyGILState_Ensure();
2169
2170    CGContextRef cr = [[NSGraphicsContext currentContext] graphicsPort];
2171
2172    double new_device_scale = _get_device_scale(cr);
2173
2174    if (device_scale != new_device_scale) {
2175        device_scale = new_device_scale;
2176        if (!PyObject_CallMethod(canvas, "_set_device_scale", "d", device_scale, NULL)) {
2177            PyErr_Print();
2178            goto exit;
2179        }
2180    }
2181
2182    renderer = PyObject_CallMethod(canvas, "_draw", "", NULL);
2183    if (!renderer)
2184    {
2185        PyErr_Print();
2186        goto exit;
2187    }
2188
2189    renderer_buffer = PyObject_GetAttrString(renderer, "_renderer");
2190    if (!renderer_buffer) {
2191        PyErr_Print();
2192        goto exit;
2193    }
2194
2195    if (_copy_agg_buffer(cr, renderer_buffer)) {
2196        printf("copy_agg_buffer failed\n");
2197        goto exit;
2198    }
2199
2200
2201    if (!NSIsEmptyRect(rubberband)) {
2202        NSFrameRect(rubberband);
2203    }
2204
2205  exit:
2206    Py_XDECREF(renderer_buffer);
2207    Py_XDECREF(renderer);
2208
2209    PyGILState_Release(gstate);
2210}
2211
2212- (void)windowDidResize: (NSNotification*)notification
2213{
2214    int width, height;
2215    Window* window = [notification object];
2216    NSSize size = [[window contentView] frame].size;
2217    NSRect rect = [self frame];
2218
2219    size.height -= rect.origin.y;
2220    width = size.width;
2221    height = size.height;
2222
2223    [self setFrameSize: size];
2224
2225    PyGILState_STATE gstate = PyGILState_Ensure();
2226    PyObject* result = PyObject_CallMethod(
2227            canvas, "resize", "ii", width, height);
2228    if(result)
2229        Py_DECREF(result);
2230    else
2231        PyErr_Print();
2232    PyGILState_Release(gstate);
2233    if (tracking) [self removeTrackingRect: tracking];
2234    tracking = [self addTrackingRect: [self bounds]
2235                               owner: self
2236                            userData: nil
2237                        assumeInside: NO];
2238    [self setNeedsDisplay: YES];
2239}
2240
2241- (void)windowWillClose:(NSNotification*)notification
2242{
2243    PyGILState_STATE gstate;
2244    PyObject* result;
2245
2246    gstate = PyGILState_Ensure();
2247    result = PyObject_CallMethod(canvas, "close_event", "");
2248    if(result)
2249        Py_DECREF(result);
2250    else
2251        PyErr_Print();
2252    PyGILState_Release(gstate);
2253}
2254
2255- (BOOL)windowShouldClose:(NSNotification*)notification
2256{
2257    NSWindow* window = [self window];
2258    NSEvent* event = [NSEvent otherEventWithType: NSApplicationDefined
2259                                        location: NSZeroPoint
2260                                   modifierFlags: 0
2261                                       timestamp: 0.0
2262                                    windowNumber: 0
2263                                         context: nil
2264                                         subtype: WINDOW_CLOSING
2265                                           data1: 0
2266                                           data2: 0];
2267    [NSApp postEvent: event atStart: true];
2268    if ([window respondsToSelector: @selector(closeButtonPressed)])
2269    { BOOL closed = [((Window*) window) closeButtonPressed];
2270      /* If closed, the window has already been closed via the manager. */
2271      if (closed) return NO;
2272    }
2273    return YES;
2274}
2275
2276- (void)mouseEntered:(NSEvent *)event
2277{
2278    PyGILState_STATE gstate;
2279    PyObject* result;
2280    NSWindow* window = [self window];
2281    if ([window isKeyWindow]==false) return;
2282
2283    gstate = PyGILState_Ensure();
2284    result = PyObject_CallMethod(canvas, "enter_notify_event", "");
2285    if(result)
2286        Py_DECREF(result);
2287    else
2288        PyErr_Print();
2289    PyGILState_Release(gstate);
2290
2291    [window setAcceptsMouseMovedEvents: YES];
2292    inside = true;
2293}
2294
2295- (void)mouseExited:(NSEvent *)event
2296{
2297    PyGILState_STATE gstate;
2298    PyObject* result;
2299    NSWindow* window = [self window];
2300    if ([window isKeyWindow]==false) return;
2301
2302    if (inside==false) return;
2303    gstate = PyGILState_Ensure();
2304    result = PyObject_CallMethod(canvas, "leave_notify_event", "");
2305    if(result)
2306        Py_DECREF(result);
2307    else
2308        PyErr_Print();
2309    PyGILState_Release(gstate);
2310
2311    [[self window] setAcceptsMouseMovedEvents: NO];
2312    inside = false;
2313}
2314
2315- (void)mouseDown:(NSEvent *)event
2316{
2317    int x, y;
2318    int num;
2319    int dblclick = 0;
2320    PyObject* result;
2321    PyGILState_STATE gstate;
2322    NSPoint location = [event locationInWindow];
2323    location = [self convertPoint: location fromView: nil];
2324    x = location.x * device_scale;
2325    y = location.y * device_scale;
2326    switch ([event type])
2327    {    case NSLeftMouseDown:
2328         {   unsigned int modifier = [event modifierFlags];
2329             if (modifier & NSControlKeyMask)
2330                 /* emulate a right-button click */
2331                 num = 3;
2332             else if (modifier & NSAlternateKeyMask)
2333                 /* emulate a middle-button click */
2334                 num = 2;
2335             else
2336             {
2337                 num = 1;
2338                 if ([NSCursor currentCursor]==[NSCursor openHandCursor])
2339                     [[NSCursor closedHandCursor] set];
2340             }
2341             break;
2342         }
2343         case NSOtherMouseDown: num = 2; break;
2344         case NSRightMouseDown: num = 3; break;
2345         default: return; /* Unknown mouse event */
2346    }
2347    if ([event clickCount] == 2) {
2348      dblclick = 1;
2349    }
2350    gstate = PyGILState_Ensure();
2351    result = PyObject_CallMethod(canvas, "button_press_event", "iiii", x, y, num, dblclick);
2352    if(result)
2353        Py_DECREF(result);
2354    else
2355        PyErr_Print();
2356
2357    PyGILState_Release(gstate);
2358}
2359
2360- (void)mouseUp:(NSEvent *)event
2361{
2362    int num;
2363    int x, y;
2364    PyObject* result;
2365    PyGILState_STATE gstate;
2366    NSPoint location = [event locationInWindow];
2367    location = [self convertPoint: location fromView: nil];
2368    x = location.x * device_scale;
2369    y = location.y * device_scale;
2370    switch ([event type])
2371    {    case NSLeftMouseUp:
2372             num = 1;
2373             if ([NSCursor currentCursor]==[NSCursor closedHandCursor])
2374                 [[NSCursor openHandCursor] set];
2375             break;
2376         case NSOtherMouseUp: num = 2; break;
2377         case NSRightMouseUp: num = 3; break;
2378         default: return; /* Unknown mouse event */
2379    }
2380    gstate = PyGILState_Ensure();
2381    result = PyObject_CallMethod(canvas, "button_release_event", "iii", x, y, num);
2382    if(result)
2383        Py_DECREF(result);
2384    else
2385        PyErr_Print();
2386
2387    PyGILState_Release(gstate);
2388}
2389
2390- (void)mouseMoved:(NSEvent *)event
2391{
2392    int x, y;
2393    NSPoint location = [event locationInWindow];
2394    location = [self convertPoint: location fromView: nil];
2395    x = location.x * device_scale;
2396    y = location.y * device_scale;
2397    PyGILState_STATE gstate = PyGILState_Ensure();
2398    PyObject* result = PyObject_CallMethod(canvas, "motion_notify_event", "ii", x, y);
2399    if(result)
2400        Py_DECREF(result);
2401    else
2402        PyErr_Print();
2403
2404    PyGILState_Release(gstate);
2405}
2406
2407- (void)mouseDragged:(NSEvent *)event
2408{
2409    int x, y;
2410    NSPoint location = [event locationInWindow];
2411    location = [self convertPoint: location fromView: nil];
2412    x = location.x * device_scale;
2413    y = location.y * device_scale;
2414    PyGILState_STATE gstate = PyGILState_Ensure();
2415    PyObject* result = PyObject_CallMethod(canvas, "motion_notify_event", "ii", x, y);
2416    if(result)
2417        Py_DECREF(result);
2418    else
2419        PyErr_Print();
2420
2421    PyGILState_Release(gstate);
2422}
2423
2424- (void)rightMouseDown:(NSEvent *)event
2425{
2426    int x, y;
2427    int num = 3;
2428    int dblclick = 0;
2429    PyObject* result;
2430    PyGILState_STATE gstate;
2431    NSPoint location = [event locationInWindow];
2432    location = [self convertPoint: location fromView: nil];
2433    x = location.x * device_scale;
2434    y = location.y * device_scale;
2435    gstate = PyGILState_Ensure();
2436    if ([event clickCount] == 2) {
2437      dblclick = 1;
2438    }
2439    result = PyObject_CallMethod(canvas, "button_press_event", "iiii", x, y, num, dblclick);
2440    if(result)
2441        Py_DECREF(result);
2442    else
2443        PyErr_Print();
2444
2445    PyGILState_Release(gstate);
2446}
2447
2448- (void)rightMouseUp:(NSEvent *)event
2449{
2450    int x, y;
2451    int num = 3;
2452    PyObject* result;
2453    PyGILState_STATE gstate;
2454    NSPoint location = [event locationInWindow];
2455    location = [self convertPoint: location fromView: nil];
2456    x = location.x * device_scale;
2457    y = location.y * device_scale;
2458    gstate = PyGILState_Ensure();
2459    result = PyObject_CallMethod(canvas, "button_release_event", "iii", x, y, num);
2460    if(result)
2461        Py_DECREF(result);
2462    else
2463        PyErr_Print();
2464
2465    PyGILState_Release(gstate);
2466}
2467
2468- (void)rightMouseDragged:(NSEvent *)event
2469{
2470    int x, y;
2471    NSPoint location = [event locationInWindow];
2472    location = [self convertPoint: location fromView: nil];
2473    x = location.x * device_scale;
2474    y = location.y * device_scale;
2475    PyGILState_STATE gstate = PyGILState_Ensure();
2476    PyObject* result = PyObject_CallMethod(canvas, "motion_notify_event", "ii", x, y);
2477    if(result)
2478        Py_DECREF(result);
2479    else
2480        PyErr_Print();
2481
2482    PyGILState_Release(gstate);
2483}
2484
2485- (void)otherMouseDown:(NSEvent *)event
2486{
2487    int x, y;
2488    int num = 2;
2489    int dblclick = 0;
2490    PyObject* result;
2491    PyGILState_STATE gstate;
2492    NSPoint location = [event locationInWindow];
2493    location = [self convertPoint: location fromView: nil];
2494    x = location.x * device_scale;
2495    y = location.y * device_scale;
2496    gstate = PyGILState_Ensure();
2497    if ([event clickCount] == 2) {
2498      dblclick = 1;
2499    }
2500    result = PyObject_CallMethod(canvas, "button_press_event", "iiii", x, y, num, dblclick);
2501    if(result)
2502        Py_DECREF(result);
2503    else
2504        PyErr_Print();
2505
2506    PyGILState_Release(gstate);
2507}
2508
2509- (void)otherMouseUp:(NSEvent *)event
2510{
2511    int x, y;
2512    int num = 2;
2513    PyObject* result;
2514    PyGILState_STATE gstate;
2515    NSPoint location = [event locationInWindow];
2516    location = [self convertPoint: location fromView: nil];
2517    x = location.x * device_scale;
2518    y = location.y * device_scale;
2519    gstate = PyGILState_Ensure();
2520    result = PyObject_CallMethod(canvas, "button_release_event", "iii", x, y, num);
2521    if(result)
2522        Py_DECREF(result);
2523    else
2524        PyErr_Print();
2525
2526    PyGILState_Release(gstate);
2527}
2528
2529- (void)otherMouseDragged:(NSEvent *)event
2530{
2531    int x, y;
2532    NSPoint location = [event locationInWindow];
2533    location = [self convertPoint: location fromView: nil];
2534    x = location.x * device_scale;
2535    y = location.y * device_scale;
2536    PyGILState_STATE gstate = PyGILState_Ensure();
2537    PyObject* result = PyObject_CallMethod(canvas, "motion_notify_event", "ii", x, y);
2538    if(result)
2539        Py_DECREF(result);
2540    else
2541        PyErr_Print();
2542
2543    PyGILState_Release(gstate);
2544}
2545
2546- (void)setRubberband:(NSRect)rect
2547{
2548    if (!NSIsEmptyRect(rubberband)) [self setNeedsDisplayInRect: rubberband];
2549    rubberband = rect;
2550    [self setNeedsDisplayInRect: rubberband];
2551}
2552
2553- (void)removeRubberband
2554{
2555    if (NSIsEmptyRect(rubberband)) return;
2556    [self setNeedsDisplayInRect: rubberband];
2557    rubberband = NSZeroRect;
2558}
2559
2560
2561
2562- (const char*)convertKeyEvent:(NSEvent*)event
2563{
2564    NSDictionary* specialkeymappings = [NSDictionary dictionaryWithObjectsAndKeys:
2565                                        @"left", [NSNumber numberWithUnsignedLong:NSLeftArrowFunctionKey],
2566                                        @"right", [NSNumber numberWithUnsignedLong:NSRightArrowFunctionKey],
2567                                        @"up", [NSNumber numberWithUnsignedLong:NSUpArrowFunctionKey],
2568                                        @"down", [NSNumber numberWithUnsignedLong:NSDownArrowFunctionKey],
2569                                        @"f1", [NSNumber numberWithUnsignedLong:NSF1FunctionKey],
2570                                        @"f2", [NSNumber numberWithUnsignedLong:NSF2FunctionKey],
2571                                        @"f3", [NSNumber numberWithUnsignedLong:NSF3FunctionKey],
2572                                        @"f4", [NSNumber numberWithUnsignedLong:NSF4FunctionKey],
2573                                        @"f5", [NSNumber numberWithUnsignedLong:NSF5FunctionKey],
2574                                        @"f6", [NSNumber numberWithUnsignedLong:NSF6FunctionKey],
2575                                        @"f7", [NSNumber numberWithUnsignedLong:NSF7FunctionKey],
2576                                        @"f8", [NSNumber numberWithUnsignedLong:NSF8FunctionKey],
2577                                        @"f9", [NSNumber numberWithUnsignedLong:NSF9FunctionKey],
2578                                        @"f10", [NSNumber numberWithUnsignedLong:NSF10FunctionKey],
2579                                        @"f11", [NSNumber numberWithUnsignedLong:NSF11FunctionKey],
2580                                        @"f12", [NSNumber numberWithUnsignedLong:NSF12FunctionKey],
2581                                        @"f13", [NSNumber numberWithUnsignedLong:NSF13FunctionKey],
2582                                        @"f14", [NSNumber numberWithUnsignedLong:NSF14FunctionKey],
2583                                        @"f15", [NSNumber numberWithUnsignedLong:NSF15FunctionKey],
2584                                        @"f16", [NSNumber numberWithUnsignedLong:NSF16FunctionKey],
2585                                        @"f17", [NSNumber numberWithUnsignedLong:NSF17FunctionKey],
2586                                        @"f18", [NSNumber numberWithUnsignedLong:NSF18FunctionKey],
2587                                        @"f19", [NSNumber numberWithUnsignedLong:NSF19FunctionKey],
2588                                        @"scroll_lock", [NSNumber numberWithUnsignedLong:NSScrollLockFunctionKey],
2589                                        @"break", [NSNumber numberWithUnsignedLong:NSBreakFunctionKey],
2590                                        @"insert", [NSNumber numberWithUnsignedLong:NSInsertFunctionKey],
2591                                        @"delete", [NSNumber numberWithUnsignedLong:NSDeleteFunctionKey],
2592                                        @"home", [NSNumber numberWithUnsignedLong:NSHomeFunctionKey],
2593                                        @"end", [NSNumber numberWithUnsignedLong:NSEndFunctionKey],
2594                                        @"pagedown", [NSNumber numberWithUnsignedLong:NSPageDownFunctionKey],
2595                                        @"pageup", [NSNumber numberWithUnsignedLong:NSPageUpFunctionKey],
2596                                        @"backspace", [NSNumber numberWithUnsignedLong:NSDeleteCharacter],
2597                                        @"enter", [NSNumber numberWithUnsignedLong:NSEnterCharacter],
2598                                        @"tab", [NSNumber numberWithUnsignedLong:NSTabCharacter],
2599                                        @"enter", [NSNumber numberWithUnsignedLong:NSCarriageReturnCharacter],
2600                                        @"backtab", [NSNumber numberWithUnsignedLong:NSBackTabCharacter],
2601                                        @"escape", [NSNumber numberWithUnsignedLong:27],
2602                                        nil
2603                                        ];
2604
2605    NSMutableString* returnkey = [NSMutableString string];
2606    if ([event modifierFlags] & NSControlKeyMask)
2607        [returnkey appendString:@"ctrl+" ];
2608    if ([event modifierFlags] & NSAlternateKeyMask)
2609        [returnkey appendString:@"alt+" ];
2610    if ([event modifierFlags] & NSCommandKeyMask)
2611        [returnkey appendString:@"cmd+" ];
2612
2613    unichar uc = [[event charactersIgnoringModifiers] characterAtIndex:0];
2614    NSString* specialchar = [specialkeymappings objectForKey:[NSNumber numberWithUnsignedLong:uc]];
2615    if (specialchar)
2616        [returnkey appendString:specialchar];
2617    else
2618        [returnkey appendString:[event charactersIgnoringModifiers]];
2619
2620    return [returnkey UTF8String];
2621}
2622
2623- (void)keyDown:(NSEvent*)event
2624{
2625    PyObject* result;
2626    const char* s = [self convertKeyEvent: event];
2627    PyGILState_STATE gstate = PyGILState_Ensure();
2628    if (s==NULL)
2629    {
2630        result = PyObject_CallMethod(canvas, "key_press_event", "O", Py_None);
2631    }
2632    else
2633    {
2634        result = PyObject_CallMethod(canvas, "key_press_event", "s", s);
2635    }
2636    if(result)
2637        Py_DECREF(result);
2638    else
2639        PyErr_Print();
2640
2641    PyGILState_Release(gstate);
2642}
2643
2644- (void)keyUp:(NSEvent*)event
2645{
2646    PyObject* result;
2647    const char* s = [self convertKeyEvent: event];
2648    PyGILState_STATE gstate = PyGILState_Ensure();
2649    if (s==NULL)
2650    {
2651        result = PyObject_CallMethod(canvas, "key_release_event", "O", Py_None);
2652    }
2653    else
2654    {
2655        result = PyObject_CallMethod(canvas, "key_release_event", "s", s);
2656    }
2657    if(result)
2658        Py_DECREF(result);
2659    else
2660        PyErr_Print();
2661
2662    PyGILState_Release(gstate);
2663}
2664
2665- (void)scrollWheel:(NSEvent*)event
2666{
2667    int step;
2668    float d = [event deltaY];
2669    if (d > 0) step = 1;
2670    else if (d < 0) step = -1;
2671    else return;
2672    NSPoint location = [event locationInWindow];
2673    NSPoint point = [self convertPoint: location fromView: nil];
2674    int x = (int)round(point.x * device_scale);
2675    int y = (int)round(point.y * device_scale - 1);
2676
2677    PyObject* result;
2678    PyGILState_STATE gstate = PyGILState_Ensure();
2679    result = PyObject_CallMethod(canvas, "scroll_event", "iii", x, y, step);
2680    if(result)
2681        Py_DECREF(result);
2682    else
2683        PyErr_Print();
2684
2685    PyGILState_Release(gstate);
2686}
2687
2688- (BOOL)acceptsFirstResponder
2689{
2690    return YES;
2691}
2692
2693/* This is all wrong. Address of pointer is being passed instead of pointer, keynames don't
2694   match up with what the front-end and does the front-end even handle modifier keys by themselves?
2695
2696- (void)flagsChanged:(NSEvent*)event
2697{
2698    const char *s = NULL;
2699    if (([event modifierFlags] & NSControlKeyMask) == NSControlKeyMask)
2700        s = "control";
2701    else if (([event modifierFlags] & NSShiftKeyMask) == NSShiftKeyMask)
2702        s = "shift";
2703    else if (([event modifierFlags] & NSAlternateKeyMask) == NSAlternateKeyMask)
2704        s = "alt";
2705    else return;
2706    PyGILState_STATE gstate = PyGILState_Ensure();
2707    PyObject* result = PyObject_CallMethod(canvas, "key_press_event", "s", &s);
2708    if(result)
2709        Py_DECREF(result);
2710    else
2711        PyErr_Print();
2712
2713    PyGILState_Release(gstate);
2714}
2715 */
2716@end
2717
2718@implementation ScrollableButton
2719- (void)setScrollWheelUpAction:(SEL)action
2720{
2721    scrollWheelUpAction = action;
2722}
2723
2724- (void)setScrollWheelDownAction:(SEL)action
2725{
2726    scrollWheelDownAction = action;
2727}
2728
2729- (void)scrollWheel:(NSEvent*)event
2730{
2731    float d = [event deltaY];
2732    Window* target = [self target];
2733    if (d > 0)
2734        [NSApp sendAction: scrollWheelUpAction to: target from: self];
2735    else if (d < 0)
2736        [NSApp sendAction: scrollWheelDownAction to: target from: self];
2737}
2738@end
2739
2740@implementation MenuItem
2741+ (MenuItem*)menuItemWithTitle: (NSString*)title
2742{
2743    MenuItem* item = [[MenuItem alloc] initWithTitle: title
2744                                              action: nil
2745                                       keyEquivalent: @""];
2746    item->index = -1;
2747    return [item autorelease];
2748}
2749
2750+ (MenuItem*)menuItemForAxis: (int)i
2751{
2752    NSString* title = [NSString stringWithFormat: @"Axis %d", i+1];
2753    MenuItem* item = [[MenuItem alloc] initWithTitle: title
2754                                              action: @selector(toggle:)
2755                                       keyEquivalent: @""];
2756    [item setTarget: item];
2757    [item setState: NSOnState];
2758    item->index = i;
2759    return [item autorelease];
2760}
2761
2762+ (MenuItem*)menuItemSelectAll
2763{
2764    MenuItem* item = [[MenuItem alloc] initWithTitle: @"Select All"
2765                                              action: @selector(selectAll:)
2766                                       keyEquivalent: @""];
2767    [item setTarget: item];
2768    item->index = -1;
2769    return [item autorelease];
2770}
2771
2772+ (MenuItem*)menuItemInvertAll
2773{
2774    MenuItem* item = [[MenuItem alloc] initWithTitle: @"Invert All"
2775                                              action: @selector(invertAll:)
2776                                       keyEquivalent: @""];
2777    [item setTarget: item];
2778    item->index = -1;
2779    return [item autorelease];
2780}
2781
2782- (void)toggle:(id)sender
2783{
2784    if ([self state]) [self setState: NSOffState];
2785    else [self setState: NSOnState];
2786}
2787
2788- (void)selectAll:(id)sender
2789{
2790    NSMenu* menu = [sender menu];
2791    if(!menu) return; /* Weird */
2792    NSArray* items = [menu itemArray];
2793    NSEnumerator* enumerator = [items objectEnumerator];
2794    MenuItem* item;
2795    while ((item = [enumerator nextObject]))
2796    {
2797        if (item->index >= 0) [item setState: NSOnState];
2798    }
2799}
2800
2801- (void)invertAll:(id)sender
2802{
2803    NSMenu* menu = [sender menu];
2804    if(!menu) return; /* Weird */
2805    NSArray* items = [menu itemArray];
2806    NSEnumerator* enumerator = [items objectEnumerator];
2807    MenuItem* item;
2808    while ((item = [enumerator nextObject]))
2809    {
2810        if (item->index < 0) continue;
2811        if ([item state]==NSOffState) [item setState: NSOnState];
2812        else [item setState: NSOffState];
2813    }
2814}
2815
2816- (int)index
2817{
2818    return self->index;
2819}
2820@end
2821
2822static PyObject*
2823show(PyObject* self)
2824{
2825    [NSApp activateIgnoringOtherApps: YES];
2826    NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2827    NSArray *windowsArray = [NSApp windows];
2828    NSEnumerator *enumerator = [windowsArray objectEnumerator];
2829    NSWindow *window;
2830    while ((window = [enumerator nextObject])) {
2831        [window orderFront:nil];
2832    }
2833    [pool release];
2834    Py_BEGIN_ALLOW_THREADS
2835    [NSApp run];
2836    Py_END_ALLOW_THREADS
2837    Py_RETURN_NONE;
2838}
2839
2840typedef struct {
2841    PyObject_HEAD
2842    CFRunLoopTimerRef timer;
2843} Timer;
2844
2845static PyObject*
2846Timer_new(PyTypeObject* type, PyObject *args, PyObject *kwds)
2847{
2848    Timer* self = (Timer*)type->tp_alloc(type, 0);
2849    if (!self) return NULL;
2850    self->timer = NULL;
2851    return (PyObject*) self;
2852}
2853
2854static PyObject*
2855Timer_repr(Timer* self)
2856{
2857#if PY3K
2858    return PyUnicode_FromFormat("Timer object %p wrapping CFRunLoopTimerRef %p",
2859                               (void*) self, (void*)(self->timer));
2860#else
2861    return PyString_FromFormat("Timer object %p wrapping CFRunLoopTimerRef %p",
2862                               (void*) self, (void*)(self->timer));
2863#endif
2864}
2865
2866static char Timer_doc[] =
2867"A Timer object wraps a CFRunLoopTimerRef and can add it to the event loop.\n";
2868
2869static void timer_callback(CFRunLoopTimerRef timer, void* info)
2870{
2871    PyObject* method = info;
2872    PyGILState_STATE gstate = PyGILState_Ensure();
2873    PyObject* result = PyObject_CallFunction(method, NULL);
2874    if (result) {
2875        Py_DECREF(result);
2876    } else {
2877        PyErr_Print();
2878    }
2879    PyGILState_Release(gstate);
2880}
2881
2882static void context_cleanup(const void* info)
2883{
2884    Py_DECREF((PyObject*)info);
2885}
2886
2887static PyObject*
2888Timer__timer_start(Timer* self, PyObject* args)
2889{
2890    CFRunLoopRef runloop;
2891    CFRunLoopTimerRef timer;
2892    CFRunLoopTimerContext context;
2893    double milliseconds;
2894    CFTimeInterval interval;
2895    PyObject* attribute;
2896    PyObject* failure;
2897    runloop = CFRunLoopGetCurrent();
2898    if (!runloop) {
2899        PyErr_SetString(PyExc_RuntimeError, "Failed to obtain run loop");
2900        return NULL;
2901    }
2902    attribute = PyObject_GetAttrString((PyObject*)self, "_interval");
2903    if (attribute==NULL)
2904    {
2905        PyErr_SetString(PyExc_AttributeError, "Timer has no attribute '_interval'");
2906        return NULL;
2907    }
2908    milliseconds = PyFloat_AsDouble(attribute);
2909    failure = PyErr_Occurred();
2910    Py_DECREF(attribute);
2911    if (failure) return NULL;
2912    attribute = PyObject_GetAttrString((PyObject*)self, "_single");
2913    if (attribute==NULL)
2914    {
2915        PyErr_SetString(PyExc_AttributeError, "Timer has no attribute '_single'");
2916        return NULL;
2917    }
2918    switch (PyObject_IsTrue(attribute)) {
2919        case 1:
2920            interval = 0;
2921            break;
2922        case 0:
2923            interval = milliseconds / 1000.0;
2924            break;
2925        case -1:
2926        default:
2927            PyErr_SetString(PyExc_ValueError, "Cannot interpret _single attribute as True of False");
2928            return NULL;
2929    }
2930    Py_DECREF(attribute);
2931    attribute = PyObject_GetAttrString((PyObject*)self, "_on_timer");
2932    if (attribute==NULL)
2933    {
2934        PyErr_SetString(PyExc_AttributeError, "Timer has no attribute '_on_timer'");
2935        return NULL;
2936    }
2937    if (!PyMethod_Check(attribute)) {
2938        PyErr_SetString(PyExc_RuntimeError, "_on_timer should be a Python method");
2939        return NULL;
2940    }
2941    context.version = 0;
2942    context.retain = NULL;
2943    context.release = context_cleanup;
2944    context.copyDescription = NULL;
2945    context.info = attribute;
2946    timer = CFRunLoopTimerCreate(kCFAllocatorDefault,
2947                                 0,
2948                                 interval,
2949                                 0,
2950                                 0,
2951                                 timer_callback,
2952                                 &context);
2953    if (!timer) {
2954        Py_DECREF(attribute);
2955        PyErr_SetString(PyExc_RuntimeError, "Failed to create timer");
2956        return NULL;
2957    }
2958    if (self->timer) {
2959        CFRunLoopTimerInvalidate(self->timer);
2960        CFRelease(self->timer);
2961    }
2962    CFRunLoopAddTimer(runloop, timer, kCFRunLoopCommonModes);
2963    /* Don't release the timer here, since the run loop may be destroyed and
2964     * the timer lost before we have a chance to decrease the reference count
2965     * of the attribute */
2966    self->timer = timer;
2967    Py_RETURN_NONE;
2968}
2969
2970static PyObject*
2971Timer__timer_stop(Timer* self)
2972{
2973    if (self->timer) {
2974        CFRunLoopTimerInvalidate(self->timer);
2975        CFRelease(self->timer);
2976        self->timer = NULL;
2977    }
2978    Py_RETURN_NONE;
2979}
2980
2981static void
2982Timer_dealloc(Timer* self)
2983{
2984    Timer__timer_stop(self);
2985    Py_TYPE(self)->tp_free((PyObject*)self);
2986}
2987
2988static PyMethodDef Timer_methods[] = {
2989    {"_timer_start",
2990     (PyCFunction)Timer__timer_start,
2991     METH_VARARGS,
2992     "Initialize and start the timer."
2993    },
2994    {"_timer_stop",
2995     (PyCFunction)Timer__timer_stop,
2996     METH_NOARGS,
2997     "Stop the timer."
2998    },
2999    {NULL}  /* Sentinel */
3000};
3001
3002static PyTypeObject TimerType = {
3003    PyVarObject_HEAD_INIT(NULL, 0)
3004    "_macosx.Timer",           /*tp_name*/
3005    sizeof(Timer),             /*tp_basicsize*/
3006    0,                         /*tp_itemsize*/
3007    (destructor)Timer_dealloc,     /*tp_dealloc*/
3008    0,                         /*tp_print*/
3009    0,                         /*tp_getattr*/
3010    0,                         /*tp_setattr*/
3011    0,                         /*tp_compare*/
3012    (reprfunc)Timer_repr,      /*tp_repr*/
3013    0,                         /*tp_as_number*/
3014    0,                         /*tp_as_sequence*/
3015    0,                         /*tp_as_mapping*/
3016    0,                         /*tp_hash */
3017    0,                         /*tp_call*/
3018    0,                         /*tp_str*/
3019    0,                         /*tp_getattro*/
3020    0,                         /*tp_setattro*/
3021    0,                         /*tp_as_buffer*/
3022    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,        /*tp_flags*/
3023    Timer_doc,                 /* tp_doc */
3024    0,                         /* tp_traverse */
3025    0,                         /* tp_clear */
3026    0,                         /* tp_richcompare */
3027    0,                         /* tp_weaklistoffset */
3028    0,                         /* tp_iter */
3029    0,                         /* tp_iternext */
3030    Timer_methods,             /* tp_methods */
3031    0,                         /* tp_members */
3032    0,                         /* tp_getset */
3033    0,                         /* tp_base */
3034    0,                         /* tp_dict */
3035    0,                         /* tp_descr_get */
3036    0,                         /* tp_descr_set */
3037    0,                         /* tp_dictoffset */
3038    0,                         /* tp_init */
3039    0,                         /* tp_alloc */
3040    Timer_new,                 /* tp_new */
3041};
3042
3043static bool verify_framework(void)
3044{
3045#ifdef COMPILING_FOR_10_6
3046    NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
3047    NSRunningApplication* app = [NSRunningApplication currentApplication];
3048    NSApplicationActivationPolicy activationPolicy = [app activationPolicy];
3049    [pool release];
3050    switch (activationPolicy) {
3051        case NSApplicationActivationPolicyRegular:
3052        case NSApplicationActivationPolicyAccessory:
3053            return true;
3054        case NSApplicationActivationPolicyProhibited:
3055            break;
3056    }
3057#else
3058    ProcessSerialNumber psn;
3059    if (CGMainDisplayID()!=0
3060     && GetCurrentProcess(&psn)==noErr
3061     && SetFrontProcess(&psn)==noErr) return true;
3062#endif
3063    PyErr_SetString(PyExc_RuntimeError,
3064        "Python is not installed as a framework. The Mac OS X backend will "
3065        "not be able to function correctly if Python is not installed as a "
3066        "framework. See the Python documentation for more information on "
3067        "installing Python as a framework on Mac OS X. Please either reinstall "
3068        "Python as a framework, or try one of the other backends. If you are "
3069        "using (Ana)Conda please install python.app and replace the use of 'python' "
3070        "with 'pythonw'. See 'Working with Matplotlib on OSX' "
3071        "in the Matplotlib FAQ for more information.");
3072    return false;
3073}
3074
3075static struct PyMethodDef methods[] = {
3076   {"show",
3077    (PyCFunction)show,
3078    METH_NOARGS,
3079    "Show all the figures and enter the main loop.\nThis function does not return until all Matplotlib windows are closed,\nand is normally not needed in interactive sessions."
3080   },
3081   {"choose_save_file",
3082    (PyCFunction)choose_save_file,
3083    METH_VARARGS,
3084    "Closes the window."
3085   },
3086   {"set_cursor",
3087    (PyCFunction)set_cursor,
3088    METH_VARARGS,
3089    "Sets the active cursor."
3090   },
3091   {NULL,          NULL, 0, NULL}/* sentinel */
3092};
3093
3094#if PY3K
3095
3096static struct PyModuleDef moduledef = {
3097    PyModuleDef_HEAD_INIT,
3098    "_macosx",
3099    "Mac OS X native backend",
3100    -1,
3101    methods,
3102    NULL,
3103    NULL,
3104    NULL,
3105    NULL
3106};
3107
3108PyObject* PyInit__macosx(void)
3109
3110#else
3111
3112void init_macosx(void)
3113#endif
3114{
3115    PyObject *module;
3116
3117    if (PyType_Ready(&FigureCanvasType) < 0
3118     || PyType_Ready(&FigureManagerType) < 0
3119     || PyType_Ready(&NavigationToolbarType) < 0
3120     || PyType_Ready(&NavigationToolbar2Type) < 0
3121     || PyType_Ready(&TimerType) < 0)
3122#if PY3K
3123        return NULL;
3124#else
3125        return;
3126#endif
3127
3128    NSApp = [NSApplication sharedApplication];
3129
3130    if (!verify_framework())
3131#if PY3K
3132        return NULL;
3133#else
3134        return;
3135#endif
3136
3137#if PY3K
3138    module = PyModule_Create(&moduledef);
3139    if (module==NULL) return NULL;
3140#else
3141    module = Py_InitModule4("_macosx",
3142                            methods,
3143                            "Mac OS X native backend",
3144                            NULL,
3145                            PYTHON_API_VERSION);
3146#endif
3147
3148    Py_INCREF(&FigureCanvasType);
3149    Py_INCREF(&FigureManagerType);
3150    Py_INCREF(&NavigationToolbarType);
3151    Py_INCREF(&NavigationToolbar2Type);
3152    Py_INCREF(&TimerType);
3153    PyModule_AddObject(module, "FigureCanvas", (PyObject*) &FigureCanvasType);
3154    PyModule_AddObject(module, "FigureManager", (PyObject*) &FigureManagerType);
3155    PyModule_AddObject(module, "NavigationToolbar", (PyObject*) &NavigationToolbarType);
3156    PyModule_AddObject(module, "NavigationToolbar2", (PyObject*) &NavigationToolbar2Type);
3157    PyModule_AddObject(module, "Timer", (PyObject*) &TimerType);
3158
3159    PyOS_InputHook = wait_for_stdin;
3160
3161    NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
3162    WindowServerConnectionManager* connectionManager = [WindowServerConnectionManager sharedManager];
3163    NSWorkspace* workspace = [NSWorkspace sharedWorkspace];
3164    NSNotificationCenter* notificationCenter = [workspace notificationCenter];
3165    [notificationCenter addObserver: connectionManager
3166                           selector: @selector(launch:)
3167                               name: NSWorkspaceDidLaunchApplicationNotification
3168                             object: nil];
3169    [pool release];
3170#if PY3K
3171    return module;
3172#endif
3173}