#include <Cocoa/Cocoa.h> #include <ApplicationServices/ApplicationServices.h> #include <sys/socket.h> #include <Python.h> #define PYOSINPUTHOOK_REPETITIVE 1 /* Remove this once Python is fixed */ #if PY_MAJOR_VERSION >= 3 #define PY3K 1 #else #define PY3K 0 #endif /* Proper way to check for the OS X version we are compiling for, from http://developer.apple.com/documentation/DeveloperTools/Conceptual/cross_development */ #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1060 #define COMPILING_FOR_10_6 #endif #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1070 #define COMPILING_FOR_10_7 #endif #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 10100 #define COMPILING_FOR_10_10 #endif /* CGFloat was defined in Mac OS X 10.5 */ #ifndef CGFLOAT_DEFINED #define CGFloat float #endif /* Various NSApplicationDefined event subtypes */ #define STOP_EVENT_LOOP 2 #define WINDOW_CLOSING 3 /* Keep track of number of windows present Needed to know when to stop the NSApp */ static long FigureWindowCount = 0; /* -------------------------- Helper function ---------------------------- */ static void _stdin_callback(CFReadStreamRef stream, CFStreamEventType eventType, void* info) { CFRunLoopRef runloop = info; CFRunLoopStop(runloop); } static int sigint_fd = -1; static void _sigint_handler(int sig) { const char c = 'i'; write(sigint_fd, &c, 1); } static void _sigint_callback(CFSocketRef s, CFSocketCallBackType type, CFDataRef address, const void * data, void *info) { char c; int* interrupted = info; CFSocketNativeHandle handle = CFSocketGetNative(s); CFRunLoopRef runloop = CFRunLoopGetCurrent(); read(handle, &c, 1); *interrupted = 1; CFRunLoopStop(runloop); } static CGEventRef _eventtap_callback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon) { CFRunLoopRef runloop = refcon; CFRunLoopStop(runloop); return event; } static int wait_for_stdin(void) { int interrupted = 0; const UInt8 buffer[] = "/dev/fd/0"; const CFIndex n = (CFIndex)strlen((char*)buffer); CFRunLoopRef runloop = CFRunLoopGetCurrent(); CFURLRef url = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, buffer, n, false); CFReadStreamRef stream = CFReadStreamCreateWithFile(kCFAllocatorDefault, url); CFRelease(url); CFReadStreamOpen(stream); #ifdef PYOSINPUTHOOK_REPETITIVE if (!CFReadStreamHasBytesAvailable(stream)) /* This is possible because of how PyOS_InputHook is called from Python */ { #endif int error; int channel[2]; CFSocketRef sigint_socket = NULL; PyOS_sighandler_t py_sigint_handler = NULL; CFStreamClientContext clientContext = {0, NULL, NULL, NULL, NULL}; clientContext.info = runloop; CFReadStreamSetClient(stream, kCFStreamEventHasBytesAvailable, _stdin_callback, &clientContext); CFReadStreamScheduleWithRunLoop(stream, runloop, kCFRunLoopDefaultMode); error = socketpair(AF_UNIX, SOCK_STREAM, 0, channel); if (error==0) { CFSocketContext context; context.version = 0; context.info = &interrupted; context.retain = NULL; context.release = NULL; context.copyDescription = NULL; fcntl(channel[0], F_SETFL, O_WRONLY | O_NONBLOCK); sigint_socket = CFSocketCreateWithNative( kCFAllocatorDefault, channel[1], kCFSocketReadCallBack, _sigint_callback, &context); if (sigint_socket) { CFRunLoopSourceRef source; source = CFSocketCreateRunLoopSource(kCFAllocatorDefault, sigint_socket, 0); CFRelease(sigint_socket); if (source) { CFRunLoopAddSource(runloop, source, kCFRunLoopDefaultMode); CFRelease(source); sigint_fd = channel[0]; py_sigint_handler = PyOS_setsig(SIGINT, _sigint_handler); } } } NSEvent* event; NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; while (true) { while (true) { event = [NSApp nextEventMatchingMask: NSAnyEventMask untilDate: [NSDate distantPast] inMode: NSDefaultRunLoopMode dequeue: YES]; if (!event) break; [NSApp sendEvent: event]; } CFRunLoopRun(); if (interrupted || CFReadStreamHasBytesAvailable(stream)) break; } [pool release]; if (py_sigint_handler) PyOS_setsig(SIGINT, py_sigint_handler); CFReadStreamUnscheduleFromRunLoop(stream, runloop, kCFRunLoopCommonModes); if (sigint_socket) CFSocketInvalidate(sigint_socket); if (error==0) { close(channel[0]); close(channel[1]); } #ifdef PYOSINPUTHOOK_REPETITIVE } #endif CFReadStreamClose(stream); CFRelease(stream); if (interrupted) { errno = EINTR; raise(SIGINT); return -1; } return 1; } /* ---------------------------- Cocoa classes ---------------------------- */ @interface WindowServerConnectionManager : NSObject { } + (WindowServerConnectionManager*)sharedManager; - (void)launch:(NSNotification*)notification; @end @interface Window : NSWindow { PyObject* manager; } - (Window*)initWithContentRect:(NSRect)rect styleMask:(unsigned int)mask backing:(NSBackingStoreType)bufferingType defer:(BOOL)deferCreation withManager: (PyObject*)theManager; - (NSRect)constrainFrameRect:(NSRect)rect toScreen:(NSScreen*)screen; - (BOOL)closeButtonPressed; - (void)dealloc; @end @interface ToolWindow : NSWindow { } - (ToolWindow*)initWithContentRect:(NSRect)rect master:(NSWindow*)window; - (void)masterCloses:(NSNotification*)notification; - (void)close; @end #ifdef COMPILING_FOR_10_6 @interface View : NSView <NSWindowDelegate> #else @interface View : NSView #endif { PyObject* canvas; NSRect rubberband; BOOL inside; NSTrackingRectTag tracking; @public double device_scale; } - (void)dealloc; - (void)drawRect:(NSRect)rect; - (void)windowDidResize:(NSNotification*)notification; - (View*)initWithFrame:(NSRect)rect; - (void)setCanvas: (PyObject*)newCanvas; - (void)windowWillClose:(NSNotification*)notification; - (BOOL)windowShouldClose:(NSNotification*)notification; - (BOOL)isFlipped; - (void)mouseEntered:(NSEvent*)event; - (void)mouseExited:(NSEvent*)event; - (void)mouseDown:(NSEvent*)event; - (void)mouseUp:(NSEvent*)event; - (void)mouseDragged:(NSEvent*)event; - (void)mouseMoved:(NSEvent*)event; - (void)rightMouseDown:(NSEvent*)event; - (void)rightMouseUp:(NSEvent*)event; - (void)rightMouseDragged:(NSEvent*)event; - (void)otherMouseDown:(NSEvent*)event; - (void)otherMouseUp:(NSEvent*)event; - (void)otherMouseDragged:(NSEvent*)event; - (void)setRubberband:(NSRect)rect; - (void)removeRubberband; - (const char*)convertKeyEvent:(NSEvent*)event; - (void)keyDown:(NSEvent*)event; - (void)keyUp:(NSEvent*)event; - (void)scrollWheel:(NSEvent *)event; - (BOOL)acceptsFirstResponder; //- (void)flagsChanged:(NSEvent*)event; @end @interface ScrollableButton : NSButton { SEL scrollWheelUpAction; SEL scrollWheelDownAction; } - (void)setScrollWheelUpAction:(SEL)action; - (void)setScrollWheelDownAction:(SEL)action; - (void)scrollWheel:(NSEvent *)event; @end @interface MenuItem: NSMenuItem { int index; } + (MenuItem*)menuItemWithTitle:(NSString*)title; + (MenuItem*)menuItemSelectAll; + (MenuItem*)menuItemInvertAll; + (MenuItem*)menuItemForAxis:(int)i; - (void)toggle:(id)sender; - (void)selectAll:(id)sender; - (void)invertAll:(id)sender; - (int)index; @end /* ---------------------------- Python classes ---------------------------- */ static CGFloat _get_device_scale(CGContextRef cr) { CGSize pixelSize = CGContextConvertSizeToDeviceSpace(cr, CGSizeMake(1, 1)); return pixelSize.width; } typedef struct { PyObject_HEAD View* view; } FigureCanvas; static PyObject* FigureCanvas_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { FigureCanvas *self = (FigureCanvas*)type->tp_alloc(type, 0); if (!self) return NULL; self->view = [View alloc]; return (PyObject*)self; } static int FigureCanvas_init(FigureCanvas *self, PyObject *args, PyObject *kwds) { int width; int height; if(!self->view) { PyErr_SetString(PyExc_RuntimeError, "NSView* is NULL"); return -1; } if(!PyArg_ParseTuple(args, "ii", &width, &height)) return -1; NSRect rect = NSMakeRect(0.0, 0.0, width, height); self->view = [self->view initWithFrame: rect]; [self->view setCanvas: (PyObject*)self]; return 0; } static void FigureCanvas_dealloc(FigureCanvas* self) { if (self->view) { [self->view setCanvas: NULL]; [self->view release]; } Py_TYPE(self)->tp_free((PyObject*)self); } static PyObject* FigureCanvas_repr(FigureCanvas* self) { #if PY3K return PyUnicode_FromFormat("FigureCanvas object %p wrapping NSView %p", (void*)self, (void*)(self->view)); #else return PyString_FromFormat("FigureCanvas object %p wrapping NSView %p", (void*)self, (void*)(self->view)); #endif } static PyObject* FigureCanvas_draw(FigureCanvas* self) { View* view = self->view; if(view) /* The figure may have been closed already */ { /* Whereas drawRect creates its own autorelease pool, apparently * [view display] also needs one. Create and release it here. */ NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; [view display]; [pool release]; } Py_RETURN_NONE; } static PyObject* FigureCanvas_invalidate(FigureCanvas* self) { View* view = self->view; if(!view) { PyErr_SetString(PyExc_RuntimeError, "NSView* is NULL"); return NULL; } [view setNeedsDisplay: YES]; Py_RETURN_NONE; } static PyObject* FigureCanvas_flush_events(FigureCanvas* self) { View* view = self->view; if(!view) { PyErr_SetString(PyExc_RuntimeError, "NSView* is NULL"); return NULL; } [view displayIfNeeded]; Py_RETURN_NONE; } static PyObject* FigureCanvas_set_rubberband(FigureCanvas* self, PyObject *args) { View* view = self->view; int x0, y0, x1, y1; NSRect rubberband; if(!view) { PyErr_SetString(PyExc_RuntimeError, "NSView* is NULL"); return NULL; } if(!PyArg_ParseTuple(args, "iiii", &x0, &y0, &x1, &y1)) return NULL; x0 /= view->device_scale; x1 /= view->device_scale; y0 /= view->device_scale; y1 /= view->device_scale; if (x1 > x0) { rubberband.origin.x = x0; rubberband.size.width = x1 - x0; } else { rubberband.origin.x = x1; rubberband.size.width = x0 - x1; } if (y1 > y0) { rubberband.origin.y = y0; rubberband.size.height = y1 - y0; } else { rubberband.origin.y = y1; rubberband.size.height = y0 - y1; } [view setRubberband: rubberband]; Py_RETURN_NONE; } static PyObject* FigureCanvas_remove_rubberband(FigureCanvas* self) { View* view = self->view; if(!view) { PyErr_SetString(PyExc_RuntimeError, "NSView* is NULL"); return NULL; } [view removeRubberband]; Py_RETURN_NONE; } static NSImage* _read_ppm_image(PyObject* obj) { int width; int height; const char* data; int n; int i; NSBitmapImageRep* bitmap; unsigned char* bitmapdata; if (!obj) return NULL; if (!PyTuple_Check(obj)) return NULL; if (!PyArg_ParseTuple(obj, "iit#", &width, &height, &data, &n)) return NULL; if (width*height*3 != n) return NULL; /* RGB image uses 3 colors / pixel */ bitmap = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes: NULL pixelsWide: width pixelsHigh: height bitsPerSample: 8 samplesPerPixel: 3 hasAlpha: NO isPlanar: NO colorSpaceName: NSDeviceRGBColorSpace bitmapFormat: 0 bytesPerRow: width*3 bitsPerPixel: 24]; if (!bitmap) return NULL; bitmapdata = [bitmap bitmapData]; for (i = 0; i < n; i++) bitmapdata[i] = data[i]; NSSize size = NSMakeSize(width, height); NSImage* image = [[NSImage alloc] initWithSize: size]; if (image) [image addRepresentation: bitmap]; [bitmap release]; return image; } static PyObject* FigureCanvas_start_event_loop(FigureCanvas* self, PyObject* args, PyObject* keywords) { float timeout = 0.0; static char* kwlist[] = {"timeout", NULL}; if(!PyArg_ParseTupleAndKeywords(args, keywords, "f", kwlist, &timeout)) return NULL; int error; int interrupted = 0; int channel[2]; CFSocketRef sigint_socket = NULL; PyOS_sighandler_t py_sigint_handler = NULL; CFRunLoopRef runloop = CFRunLoopGetCurrent(); error = pipe(channel); if (error==0) { CFSocketContext context = {0, NULL, NULL, NULL, NULL}; fcntl(channel[1], F_SETFL, O_WRONLY | O_NONBLOCK); context.info = &interrupted; sigint_socket = CFSocketCreateWithNative(kCFAllocatorDefault, channel[0], kCFSocketReadCallBack, _sigint_callback, &context); if (sigint_socket) { CFRunLoopSourceRef source; source = CFSocketCreateRunLoopSource(kCFAllocatorDefault, sigint_socket, 0); CFRelease(sigint_socket); if (source) { CFRunLoopAddSource(runloop, source, kCFRunLoopDefaultMode); CFRelease(source); sigint_fd = channel[1]; py_sigint_handler = PyOS_setsig(SIGINT, _sigint_handler); } } else close(channel[0]); } NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; NSDate* date = (timeout > 0.0) ? [NSDate dateWithTimeIntervalSinceNow: timeout] : [NSDate distantFuture]; while (true) { NSEvent* event = [NSApp nextEventMatchingMask: NSAnyEventMask untilDate: date inMode: NSDefaultRunLoopMode dequeue: YES]; if (!event || [event type]==NSApplicationDefined) break; [NSApp sendEvent: event]; } [pool release]; if (py_sigint_handler) PyOS_setsig(SIGINT, py_sigint_handler); if (sigint_socket) CFSocketInvalidate(sigint_socket); if (error==0) close(channel[1]); if (interrupted) raise(SIGINT); Py_RETURN_NONE; } static PyObject* FigureCanvas_stop_event_loop(FigureCanvas* self) { NSEvent* event = [NSEvent otherEventWithType: NSApplicationDefined location: NSZeroPoint modifierFlags: 0 timestamp: 0.0 windowNumber: 0 context: nil subtype: STOP_EVENT_LOOP data1: 0 data2: 0]; [NSApp postEvent: event atStart: true]; Py_RETURN_NONE; } static PyMethodDef FigureCanvas_methods[] = { {"draw", (PyCFunction)FigureCanvas_draw, METH_NOARGS, "Draws the canvas." }, {"invalidate", (PyCFunction)FigureCanvas_invalidate, METH_NOARGS, "Invalidates the canvas." }, {"flush_events", (PyCFunction)FigureCanvas_flush_events, METH_NOARGS, "Flush the GUI events for the figure." }, {"set_rubberband", (PyCFunction)FigureCanvas_set_rubberband, METH_VARARGS, "Specifies a new rubberband rectangle and invalidates it." }, {"remove_rubberband", (PyCFunction)FigureCanvas_remove_rubberband, METH_NOARGS, "Removes the current rubberband rectangle." }, {"start_event_loop", (PyCFunction)FigureCanvas_start_event_loop, METH_KEYWORDS | METH_VARARGS, "Runs the event loop until the timeout or until stop_event_loop is called.\n", }, {"stop_event_loop", (PyCFunction)FigureCanvas_stop_event_loop, METH_NOARGS, "Stops the event loop that was started by start_event_loop.\n", }, {NULL} /* Sentinel */ }; static char FigureCanvas_doc[] = "A FigureCanvas object wraps a Cocoa NSView object.\n"; static PyTypeObject FigureCanvasType = { PyVarObject_HEAD_INIT(NULL, 0) "_macosx.FigureCanvas", /*tp_name*/ sizeof(FigureCanvas), /*tp_basicsize*/ 0, /*tp_itemsize*/ (destructor)FigureCanvas_dealloc, /*tp_dealloc*/ 0, /*tp_print*/ 0, /*tp_getattr*/ 0, /*tp_setattr*/ 0, /*tp_compare*/ (reprfunc)FigureCanvas_repr, /*tp_repr*/ 0, /*tp_as_number*/ 0, /*tp_as_sequence*/ 0, /*tp_as_mapping*/ 0, /*tp_hash */ 0, /*tp_call*/ 0, /*tp_str*/ 0, /*tp_getattro*/ 0, /*tp_setattro*/ 0, /*tp_as_buffer*/ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ FigureCanvas_doc, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ FigureCanvas_methods, /* tp_methods */ 0, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ (initproc)FigureCanvas_init, /* tp_init */ 0, /* tp_alloc */ FigureCanvas_new, /* tp_new */ }; typedef struct { PyObject_HEAD Window* window; } FigureManager; static PyObject* FigureManager_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { Window* window = [Window alloc]; if (!window) return NULL; FigureManager *self = (FigureManager*)type->tp_alloc(type, 0); if (!self) { [window release]; return NULL; } self->window = window; ++FigureWindowCount; return (PyObject*)self; } static int FigureManager_init(FigureManager *self, PyObject *args, PyObject *kwds) { NSRect rect; Window* window; View* view; const char* title; PyObject* size; int width, height; PyObject* obj; FigureCanvas* canvas; if(!self->window) { PyErr_SetString(PyExc_RuntimeError, "NSWindow* is NULL"); return -1; } if(!PyArg_ParseTuple(args, "Os", &obj, &title)) return -1; canvas = (FigureCanvas*)obj; view = canvas->view; if (!view) /* Something really weird going on */ { PyErr_SetString(PyExc_RuntimeError, "NSView* is NULL"); return -1; } size = PyObject_CallMethod(obj, "get_width_height", ""); if(!size) return -1; if(!PyArg_ParseTuple(size, "ii", &width, &height)) { Py_DECREF(size); return -1; } Py_DECREF(size); rect.origin.x = 100; rect.origin.y = 350; rect.size.height = height; rect.size.width = width; NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; self->window = [self->window initWithContentRect: rect styleMask: NSTitledWindowMask | NSClosableWindowMask | NSResizableWindowMask | NSMiniaturizableWindowMask backing: NSBackingStoreBuffered defer: YES withManager: (PyObject*)self]; window = self->window; [window setTitle: [NSString stringWithCString: title encoding: NSASCIIStringEncoding]]; [window setAcceptsMouseMovedEvents: YES]; [window setDelegate: view]; [window makeFirstResponder: view]; [[window contentView] addSubview: view]; [pool release]; return 0; } static PyObject* FigureManager_repr(FigureManager* self) { #if PY3K return PyUnicode_FromFormat("FigureManager object %p wrapping NSWindow %p", (void*) self, (void*)(self->window)); #else return PyString_FromFormat("FigureManager object %p wrapping NSWindow %p", (void*) self, (void*)(self->window)); #endif } static void FigureManager_dealloc(FigureManager* self) { Window* window = self->window; if(window) { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; [window close]; [pool release]; } Py_TYPE(self)->tp_free((PyObject*)self); } static PyObject* FigureManager_show(FigureManager* self) { Window* window = self->window; if(window) { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; [window makeKeyAndOrderFront: nil]; [window orderFrontRegardless]; [pool release]; } Py_RETURN_NONE; } static PyObject* FigureManager_destroy(FigureManager* self) { Window* window = self->window; if(window) { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; [window close]; [pool release]; self->window = NULL; } Py_RETURN_NONE; } static PyObject* FigureManager_set_window_title(FigureManager* self, PyObject *args, PyObject *kwds) { char* title; if(!PyArg_ParseTuple(args, "es", "UTF-8", &title)) return NULL; Window* window = self->window; if(window) { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; NSString* ns_title = [[[NSString alloc] initWithCString: title encoding: NSUTF8StringEncoding] autorelease]; [window setTitle: ns_title]; [pool release]; } PyMem_Free(title); Py_RETURN_NONE; } static PyObject* FigureManager_get_window_title(FigureManager* self) { Window* window = self->window; PyObject* result = NULL; if(window) { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; NSString* title = [window title]; if (title) { const char* cTitle = [title UTF8String]; result = PyUnicode_FromString(cTitle); } [pool release]; } if (result) { return result; } else { Py_RETURN_NONE; } } static PyMethodDef FigureManager_methods[] = { {"show", (PyCFunction)FigureManager_show, METH_NOARGS, "Shows the window associated with the figure manager." }, {"destroy", (PyCFunction)FigureManager_destroy, METH_NOARGS, "Closes the window associated with the figure manager." }, {"set_window_title", (PyCFunction)FigureManager_set_window_title, METH_VARARGS, "Sets the title of the window associated with the figure manager." }, {"get_window_title", (PyCFunction)FigureManager_get_window_title, METH_NOARGS, "Returns the title of the window associated with the figure manager." }, {NULL} /* Sentinel */ }; static char FigureManager_doc[] = "A FigureManager object wraps a Cocoa NSWindow object.\n"; static PyTypeObject FigureManagerType = { PyVarObject_HEAD_INIT(NULL, 0) "_macosx.FigureManager", /*tp_name*/ sizeof(FigureManager), /*tp_basicsize*/ 0, /*tp_itemsize*/ (destructor)FigureManager_dealloc, /*tp_dealloc*/ 0, /*tp_print*/ 0, /*tp_getattr*/ 0, /*tp_setattr*/ 0, /*tp_compare*/ (reprfunc)FigureManager_repr, /*tp_repr*/ 0, /*tp_as_number*/ 0, /*tp_as_sequence*/ 0, /*tp_as_mapping*/ 0, /*tp_hash */ 0, /*tp_call*/ 0, /*tp_str*/ 0, /*tp_getattro*/ 0, /*tp_setattro*/ 0, /*tp_as_buffer*/ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ FigureManager_doc, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ FigureManager_methods, /* tp_methods */ 0, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ (initproc)FigureManager_init, /* tp_init */ 0, /* tp_alloc */ FigureManager_new, /* tp_new */ }; @interface NavigationToolbarHandler : NSObject { PyObject* toolbar; } - (NavigationToolbarHandler*)initWithToolbar:(PyObject*)toolbar; -(void)left:(id)sender; -(void)right:(id)sender; -(void)up:(id)sender; -(void)down:(id)sender; -(void)zoominx:(id)sender; -(void)zoominy:(id)sender; -(void)zoomoutx:(id)sender; -(void)zoomouty:(id)sender; @end typedef struct { PyObject_HEAD NSPopUpButton* menu; NavigationToolbarHandler* handler; } NavigationToolbar; @implementation NavigationToolbarHandler - (NavigationToolbarHandler*)initWithToolbar:(PyObject*)theToolbar { [self init]; toolbar = theToolbar; return self; } -(void)left:(id)sender { PyObject* result; PyGILState_STATE gstate; gstate = PyGILState_Ensure(); result = PyObject_CallMethod(toolbar, "panx", "i", -1); if(result) Py_DECREF(result); else PyErr_Print(); PyGILState_Release(gstate); } -(void)right:(id)sender { PyObject* result; PyGILState_STATE gstate; gstate = PyGILState_Ensure(); result = PyObject_CallMethod(toolbar, "panx", "i", 1); if(result) Py_DECREF(result); else PyErr_Print(); PyGILState_Release(gstate); } -(void)up:(id)sender { PyObject* result; PyGILState_STATE gstate; gstate = PyGILState_Ensure(); result = PyObject_CallMethod(toolbar, "pany", "i", 1); if(result) Py_DECREF(result); else PyErr_Print(); PyGILState_Release(gstate); } -(void)down:(id)sender { PyObject* result; PyGILState_STATE gstate; gstate = PyGILState_Ensure(); result = PyObject_CallMethod(toolbar, "pany", "i", -1); if(result) Py_DECREF(result); else PyErr_Print(); PyGILState_Release(gstate); } -(void)zoominx:(id)sender { PyObject* result; PyGILState_STATE gstate; gstate = PyGILState_Ensure(); result = PyObject_CallMethod(toolbar, "zoomx", "i", 1); if(result) Py_DECREF(result); else PyErr_Print(); PyGILState_Release(gstate); } -(void)zoomoutx:(id)sender { PyObject* result; PyGILState_STATE gstate; gstate = PyGILState_Ensure(); result = PyObject_CallMethod(toolbar, "zoomx", "i", -1); if(result) Py_DECREF(result); else PyErr_Print(); PyGILState_Release(gstate); } -(void)zoominy:(id)sender { PyObject* result; PyGILState_STATE gstate; gstate = PyGILState_Ensure(); result = PyObject_CallMethod(toolbar, "zoomy", "i", 1); if(result) Py_DECREF(result); else PyErr_Print(); PyGILState_Release(gstate); } -(void)zoomouty:(id)sender { PyObject* result; PyGILState_STATE gstate; gstate = PyGILState_Ensure(); result = PyObject_CallMethod(toolbar, "zoomy", "i", -1); if(result) Py_DECREF(result); else PyErr_Print(); PyGILState_Release(gstate); } -(void)save_figure:(id)sender { PyObject* result; PyGILState_STATE gstate; gstate = PyGILState_Ensure(); result = PyObject_CallMethod(toolbar, "save_figure", ""); if(result) Py_DECREF(result); else PyErr_Print(); PyGILState_Release(gstate); } @end static PyObject* NavigationToolbar_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { NavigationToolbarHandler* handler = [NavigationToolbarHandler alloc]; if (!handler) return NULL; NavigationToolbar *self = (NavigationToolbar*)type->tp_alloc(type, 0); if (!self) { [handler release]; return NULL; } self->handler = handler; return (PyObject*)self; } static int NavigationToolbar_init(NavigationToolbar *self, PyObject *args, PyObject *kwds) { int i; NSRect rect; const float smallgap = 2; const float biggap = 10; const int height = 32; PyObject* images; PyObject* obj; FigureCanvas* canvas; View* view; obj = PyObject_GetAttrString((PyObject*)self, "canvas"); if (obj==NULL) { PyErr_SetString(PyExc_AttributeError, "Attempt to install toolbar for NULL canvas"); return -1; } Py_DECREF(obj); /* Don't increase the reference count */ if (!PyObject_IsInstance(obj, (PyObject*) &FigureCanvasType)) { PyErr_SetString(PyExc_TypeError, "Attempt to install toolbar for object that is not a FigureCanvas"); return -1; } canvas = (FigureCanvas*)obj; view = canvas->view; if(!view) { PyErr_SetString(PyExc_RuntimeError, "NSView* is NULL"); return -1; } if(!PyArg_ParseTuple(args, "O", &images)) return -1; if(!PyDict_Check(images)) return -1; NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; NSRect bounds = [view bounds]; NSWindow* window = [view window]; bounds.origin.y += height; [view setFrame: bounds]; bounds.size.height += height; [window setContentSize: bounds.size]; char* imagenames[9] = {"stock_left", "stock_right", "stock_zoom-in", "stock_zoom-out", "stock_up", "stock_down", "stock_zoom-in", "stock_zoom-out", "stock_save_as"}; NSString* tooltips[9] = { @"Pan left with click or wheel mouse (bidirectional)", @"Pan right with click or wheel mouse (bidirectional)", @"Zoom In X (shrink the x axis limits) with click or wheel mouse (bidirectional)", @"Zoom Out X (expand the x axis limits) with click or wheel mouse (bidirectional)", @"Pan up with click or wheel mouse (bidirectional)", @"Pan down with click or wheel mouse (bidirectional)", @"Zoom in Y (shrink the y axis limits) with click or wheel mouse (bidirectional)", @"Zoom Out Y (expand the y axis limits) with click or wheel mouse (bidirectional)", @"Save the figure"}; SEL actions[9] = {@selector(left:), @selector(right:), @selector(zoominx:), @selector(zoomoutx:), @selector(up:), @selector(down:), @selector(zoominy:), @selector(zoomouty:), @selector(save_figure:)}; SEL scroll_actions[9][2] = {{@selector(left:), @selector(right:)}, {@selector(left:), @selector(right:)}, {@selector(zoominx:), @selector(zoomoutx:)}, {@selector(zoominx:), @selector(zoomoutx:)}, {@selector(up:), @selector(down:)}, {@selector(up:), @selector(down:)}, {@selector(zoominy:), @selector(zoomouty:)}, {@selector(zoominy:), @selector(zoomouty:)}, {nil,nil}, }; rect.size.width = 120; rect.size.height = 24; rect.origin.x = biggap; rect.origin.y = 0.5*(height - rect.size.height); self->menu = [[NSPopUpButton alloc] initWithFrame: rect pullsDown: YES]; [self->menu setAutoenablesItems: NO]; [[window contentView] addSubview: self->menu]; [self->menu release]; rect.origin.x += rect.size.width + biggap; rect.size.width = 24; self->handler = [self->handler initWithToolbar: (PyObject*)self]; for (i = 0; i < 9; i++) { NSButton* button; SEL scrollWheelUpAction = scroll_actions[i][0]; SEL scrollWheelDownAction = scroll_actions[i][1]; if (scrollWheelUpAction && scrollWheelDownAction) { ScrollableButton* scrollable_button = [ScrollableButton alloc]; [scrollable_button initWithFrame: rect]; [scrollable_button setScrollWheelUpAction: scrollWheelUpAction]; [scrollable_button setScrollWheelDownAction: scrollWheelDownAction]; button = (NSButton*)scrollable_button; } else { button = [NSButton alloc]; [button initWithFrame: rect]; } PyObject* imagedata = PyDict_GetItemString(images, imagenames[i]); NSImage* image = _read_ppm_image(imagedata); [button setBezelStyle: NSShadowlessSquareBezelStyle]; [button setButtonType: NSMomentaryLightButton]; if(image) { [button setImage: image]; [image release]; } [button setToolTip: tooltips[i]]; [button setTarget: self->handler]; [button setAction: actions[i]]; [[window contentView] addSubview: button]; [button release]; rect.origin.x += rect.size.width + smallgap; } [[window contentView] display]; [pool release]; return 0; } static void NavigationToolbar_dealloc(NavigationToolbar *self) { [self->handler release]; Py_TYPE(self)->tp_free((PyObject*)self); } static PyObject* NavigationToolbar_repr(NavigationToolbar* self) { #if PY3K return PyUnicode_FromFormat("NavigationToolbar object %p", (void*)self); #else return PyString_FromFormat("NavigationToolbar object %p", (void*)self); #endif } static char NavigationToolbar_doc[] = "NavigationToolbar\n"; static PyObject* NavigationToolbar_update (NavigationToolbar* self) { int n; NSPopUpButton* button = self->menu; if (!button) { PyErr_SetString(PyExc_RuntimeError, "Menu button is NULL"); return NULL; } PyObject* canvas = PyObject_GetAttrString((PyObject*)self, "canvas"); if (canvas==NULL) { PyErr_SetString(PyExc_AttributeError, "Failed to find canvas"); return NULL; } Py_DECREF(canvas); /* Don't keep a reference here */ PyObject* figure = PyObject_GetAttrString(canvas, "figure"); if (figure==NULL) { PyErr_SetString(PyExc_AttributeError, "Failed to find figure"); return NULL; } Py_DECREF(figure); /* Don't keep a reference here */ PyObject* axes = PyObject_GetAttrString(figure, "axes"); if (axes==NULL) { PyErr_SetString(PyExc_AttributeError, "Failed to find figure axes"); return NULL; } Py_DECREF(axes); /* Don't keep a reference here */ if (!PyList_Check(axes)) { PyErr_SetString(PyExc_TypeError, "Figure axes is not a list"); return NULL; } n = PyList_GET_SIZE(axes); NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; [button removeAllItems]; NSMenu* menu = [button menu]; [menu addItem: [MenuItem menuItemWithTitle: @"Axes"]]; if (n==0) { [button setEnabled: NO]; } else { int i; [menu addItem: [MenuItem menuItemSelectAll]]; [menu addItem: [MenuItem menuItemInvertAll]]; [menu addItem: [NSMenuItem separatorItem]]; for (i = 0; i < n; i++) { [menu addItem: [MenuItem menuItemForAxis: i]]; } [button setEnabled: YES]; } [pool release]; Py_RETURN_NONE; } static PyObject* NavigationToolbar_get_active (NavigationToolbar* self) { NSPopUpButton* button = self->menu; if (!button) { PyErr_SetString(PyExc_RuntimeError, "Menu button is NULL"); return NULL; } NSMenu* menu = [button menu]; NSArray* items = [menu itemArray]; size_t n = [items count]; int* states = calloc(n, sizeof(int)); if (!states) { PyErr_SetString(PyExc_RuntimeError, "calloc failed"); return NULL; } int i; unsigned int m = 0; NSEnumerator* enumerator = [items objectEnumerator]; MenuItem* item; while ((item = [enumerator nextObject])) { if ([item isSeparatorItem]) continue; i = [item index]; if (i < 0) continue; if ([item state]==NSOnState) { states[i] = 1; m++; } } Py_ssize_t list_index = 0; PyObject* list = PyList_New(m); size_t state_index; for (state_index = 0; state_index < n; state_index++) { if(states[state_index]==1) { PyList_SET_ITEM(list, list_index++, PyLong_FromSize_t(state_index)); } } free(states); return list; } static PyMethodDef NavigationToolbar_methods[] = { {"update", (PyCFunction)NavigationToolbar_update, METH_NOARGS, "Updates the toolbar menu." }, {"get_active", (PyCFunction)NavigationToolbar_get_active, METH_NOARGS, "Returns a list of integers identifying which items in the menu are selected." }, {NULL} /* Sentinel */ }; static PyTypeObject NavigationToolbarType = { PyVarObject_HEAD_INIT(NULL, 0) "_macosx.NavigationToolbar", /*tp_name*/ sizeof(NavigationToolbar), /*tp_basicsize*/ 0, /*tp_itemsize*/ (destructor)NavigationToolbar_dealloc, /*tp_dealloc*/ 0, /*tp_print*/ 0, /*tp_getattr*/ 0, /*tp_setattr*/ 0, /*tp_compare*/ (reprfunc)NavigationToolbar_repr, /*tp_repr*/ 0, /*tp_as_number*/ 0, /*tp_as_sequence*/ 0, /*tp_as_mapping*/ 0, /*tp_hash */ 0, /*tp_call*/ 0, /*tp_str*/ 0, /*tp_getattro*/ 0, /*tp_setattro*/ 0, /*tp_as_buffer*/ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ NavigationToolbar_doc, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ NavigationToolbar_methods, /* tp_methods */ 0, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ (initproc)NavigationToolbar_init, /* tp_init */ 0, /* tp_alloc */ NavigationToolbar_new, /* tp_new */ }; @interface NavigationToolbar2Handler : NSObject { PyObject* toolbar; NSButton* panbutton; NSButton* zoombutton; } - (NavigationToolbar2Handler*)initWithToolbar:(PyObject*)toolbar; - (void)installCallbacks:(SEL[7])actions forButtons: (NSButton*[7])buttons; - (void)home:(id)sender; - (void)back:(id)sender; - (void)forward:(id)sender; - (void)pan:(id)sender; - (void)zoom:(id)sender; - (void)configure_subplots:(id)sender; - (void)save_figure:(id)sender; @end typedef struct { PyObject_HEAD NSPopUpButton* menu; NSText* messagebox; NavigationToolbar2Handler* handler; } NavigationToolbar2; @implementation NavigationToolbar2Handler - (NavigationToolbar2Handler*)initWithToolbar:(PyObject*)theToolbar { [self init]; toolbar = theToolbar; return self; } - (void)installCallbacks:(SEL[7])actions forButtons: (NSButton*[7])buttons { int i; for (i = 0; i < 7; i++) { SEL action = actions[i]; NSButton* button = buttons[i]; [button setTarget: self]; [button setAction: action]; if (action==@selector(pan:)) panbutton = button; if (action==@selector(zoom:)) zoombutton = button; } } -(void)home:(id)sender { PyObject* result; PyGILState_STATE gstate; gstate = PyGILState_Ensure(); result = PyObject_CallMethod(toolbar, "home", ""); if(result) Py_DECREF(result); else PyErr_Print(); PyGILState_Release(gstate); } -(void)back:(id)sender { PyObject* result; PyGILState_STATE gstate; gstate = PyGILState_Ensure(); result = PyObject_CallMethod(toolbar, "back", ""); if(result) Py_DECREF(result); else PyErr_Print(); PyGILState_Release(gstate); } -(void)forward:(id)sender { PyObject* result; PyGILState_STATE gstate; gstate = PyGILState_Ensure(); result = PyObject_CallMethod(toolbar, "forward", ""); if(result) Py_DECREF(result); else PyErr_Print(); PyGILState_Release(gstate); } -(void)pan:(id)sender { PyObject* result; PyGILState_STATE gstate; if ([sender state]) { if (zoombutton) [zoombutton setState: NO]; } gstate = PyGILState_Ensure(); result = PyObject_CallMethod(toolbar, "pan", ""); if(result) Py_DECREF(result); else PyErr_Print(); PyGILState_Release(gstate); } -(void)zoom:(id)sender { PyObject* result; PyGILState_STATE gstate; if ([sender state]) { if (panbutton) [panbutton setState: NO]; } gstate = PyGILState_Ensure(); result = PyObject_CallMethod(toolbar, "zoom", ""); if(result) Py_DECREF(result); else PyErr_Print(); PyGILState_Release(gstate); } -(void)configure_subplots:(id)sender { PyObject* canvas; View* view; PyObject* size; NSRect rect; int width, height; rect.origin.x = 100; rect.origin.y = 350; PyGILState_STATE gstate = PyGILState_Ensure(); PyObject* master = PyObject_GetAttrString(toolbar, "canvas"); if (master==nil) { PyErr_Print(); PyGILState_Release(gstate); return; } canvas = PyObject_CallMethod(toolbar, "prepare_configure_subplots", ""); if(!canvas) { PyErr_Print(); Py_DECREF(master); PyGILState_Release(gstate); return; } view = ((FigureCanvas*)canvas)->view; if (!view) /* Something really weird going on */ { PyErr_SetString(PyExc_RuntimeError, "NSView* is NULL"); PyErr_Print(); Py_DECREF(canvas); Py_DECREF(master); PyGILState_Release(gstate); return; } size = PyObject_CallMethod(canvas, "get_width_height", ""); Py_DECREF(canvas); if(!size) { PyErr_Print(); Py_DECREF(master); PyGILState_Release(gstate); return; } int ok = PyArg_ParseTuple(size, "ii", &width, &height); Py_DECREF(size); if (!ok) { PyErr_Print(); Py_DECREF(master); PyGILState_Release(gstate); return; } NSWindow* mw = [((FigureCanvas*)master)->view window]; Py_DECREF(master); PyGILState_Release(gstate); rect.size.width = width; rect.size.height = height; ToolWindow* window = [ [ToolWindow alloc] initWithContentRect: rect master: mw]; [window setContentView: view]; [view release]; [window makeKeyAndOrderFront: nil]; } -(void)save_figure:(id)sender { PyObject* result; PyGILState_STATE gstate; gstate = PyGILState_Ensure(); result = PyObject_CallMethod(toolbar, "save_figure", ""); if(result) Py_DECREF(result); else PyErr_Print(); PyGILState_Release(gstate); } @end static PyObject* NavigationToolbar2_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { NavigationToolbar2Handler* handler = [NavigationToolbar2Handler alloc]; if (!handler) return NULL; NavigationToolbar2 *self = (NavigationToolbar2*)type->tp_alloc(type, 0); if (!self) { [handler release]; return NULL; } self->handler = handler; return (PyObject*)self; } static int NavigationToolbar2_init(NavigationToolbar2 *self, PyObject *args, PyObject *kwds) { PyObject* obj; FigureCanvas* canvas; View* view; int i; NSRect rect; NSSize size; NSSize scale; const float gap = 2; const int height = 36; const int imagesize = 24; const char* basedir; obj = PyObject_GetAttrString((PyObject*)self, "canvas"); if (obj==NULL) { PyErr_SetString(PyExc_AttributeError, "Attempt to install toolbar for NULL canvas"); return -1; } Py_DECREF(obj); /* Don't increase the reference count */ if (!PyObject_IsInstance(obj, (PyObject*) &FigureCanvasType)) { PyErr_SetString(PyExc_TypeError, "Attempt to install toolbar for object that is not a FigureCanvas"); return -1; } canvas = (FigureCanvas*)obj; view = canvas->view; if(!view) { PyErr_SetString(PyExc_RuntimeError, "NSView* is NULL"); return -1; } if(!PyArg_ParseTuple(args, "s", &basedir)) return -1; NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; NSRect bounds = [view bounds]; NSWindow* window = [view window]; bounds.origin.y += height; [view setFrame: bounds]; bounds.size.height += height; [window setContentSize: bounds.size]; NSString* dir = [NSString stringWithCString: basedir encoding: NSASCIIStringEncoding]; NSButton* buttons[7]; NSString* images[7] = {@"home.pdf", @"back.pdf", @"forward.pdf", @"move.pdf", @"zoom_to_rect.pdf", @"subplots.pdf", @"filesave.pdf"}; NSString* tooltips[7] = {@"Reset original view", @"Back to previous view", @"Forward to next view", @"Pan axes with left mouse, zoom with right", @"Zoom to rectangle", @"Configure subplots", @"Save the figure"}; SEL actions[7] = {@selector(home:), @selector(back:), @selector(forward:), @selector(pan:), @selector(zoom:), @selector(configure_subplots:), @selector(save_figure:)}; NSButtonType buttontypes[7] = {NSMomentaryLightButton, NSMomentaryLightButton, NSMomentaryLightButton, NSPushOnPushOffButton, NSPushOnPushOffButton, NSMomentaryLightButton, NSMomentaryLightButton}; rect.origin.x = 0; rect.origin.y = 0; rect.size.width = imagesize; rect.size.height = imagesize; #ifdef COMPILING_FOR_10_7 rect = [window convertRectToBacking: rect]; #endif size = rect.size; scale.width = imagesize / size.width; scale.height = imagesize / size.height; rect.size.width = 32; rect.size.height = 32; rect.origin.x = gap; rect.origin.y = 0.5*(height - rect.size.height); for (i = 0; i < 7; i++) { NSString* filename = [dir stringByAppendingPathComponent: images[i]]; NSImage* image = [[NSImage alloc] initWithContentsOfFile: filename]; buttons[i] = [[NSButton alloc] initWithFrame: rect]; [image setSize: size]; [buttons[i] setBezelStyle: NSShadowlessSquareBezelStyle]; [buttons[i] setButtonType: buttontypes[i]]; [buttons[i] setImage: image]; [buttons[i] scaleUnitSquareToSize: scale]; [buttons[i] setImagePosition: NSImageOnly]; [buttons[i] setToolTip: tooltips[i]]; [[window contentView] addSubview: buttons[i]]; [buttons[i] release]; [image release]; rect.origin.x += rect.size.width + gap; } self->handler = [self->handler initWithToolbar: (PyObject*)self]; [self->handler installCallbacks: actions forButtons: buttons]; NSFont* font = [NSFont systemFontOfSize: 0.0]; rect.size.width = 300; rect.size.height = 0; rect.origin.x += height; NSText* messagebox = [[NSText alloc] initWithFrame: rect]; [messagebox setFont: font]; [messagebox setDrawsBackground: NO]; [messagebox setSelectable: NO]; /* if selectable, the messagebox can become first responder, * which is not supposed to happen */ rect = [messagebox frame]; rect.origin.y = 0.5 * (height - rect.size.height); [messagebox setFrameOrigin: rect.origin]; [[window contentView] addSubview: messagebox]; [messagebox release]; [[window contentView] display]; [pool release]; self->messagebox = messagebox; return 0; } static void NavigationToolbar2_dealloc(NavigationToolbar2 *self) { [self->handler release]; Py_TYPE(self)->tp_free((PyObject*)self); } static PyObject* NavigationToolbar2_repr(NavigationToolbar2* self) { #if PY3K return PyUnicode_FromFormat("NavigationToolbar2 object %p", (void*)self); #else return PyString_FromFormat("NavigationToolbar2 object %p", (void*)self); #endif } static char NavigationToolbar2_doc[] = "NavigationToolbar2\n"; static PyObject* NavigationToolbar2_set_message(NavigationToolbar2 *self, PyObject* args) { const char* message; #if PY3K if(!PyArg_ParseTuple(args, "y", &message)) return NULL; #else if(!PyArg_ParseTuple(args, "s", &message)) return NULL; #endif NSText* messagebox = self->messagebox; if (messagebox) { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; NSString* text = [NSString stringWithUTF8String: message]; [messagebox setString: text]; [pool release]; } Py_RETURN_NONE; } static PyMethodDef NavigationToolbar2_methods[] = { {"set_message", (PyCFunction)NavigationToolbar2_set_message, METH_VARARGS, "Set the message to be displayed on the toolbar." }, {NULL} /* Sentinel */ }; static PyTypeObject NavigationToolbar2Type = { PyVarObject_HEAD_INIT(NULL, 0) "_macosx.NavigationToolbar2", /*tp_name*/ sizeof(NavigationToolbar2), /*tp_basicsize*/ 0, /*tp_itemsize*/ (destructor)NavigationToolbar2_dealloc, /*tp_dealloc*/ 0, /*tp_print*/ 0, /*tp_getattr*/ 0, /*tp_setattr*/ 0, /*tp_compare*/ (reprfunc)NavigationToolbar2_repr, /*tp_repr*/ 0, /*tp_as_number*/ 0, /*tp_as_sequence*/ 0, /*tp_as_mapping*/ 0, /*tp_hash */ 0, /*tp_call*/ 0, /*tp_str*/ 0, /*tp_getattro*/ 0, /*tp_setattro*/ 0, /*tp_as_buffer*/ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ NavigationToolbar2_doc, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ NavigationToolbar2_methods, /* tp_methods */ 0, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ (initproc)NavigationToolbar2_init, /* tp_init */ 0, /* tp_alloc */ NavigationToolbar2_new, /* tp_new */ }; static PyObject* choose_save_file(PyObject* unused, PyObject* args) { int result; const char* title; char* default_filename; if(!PyArg_ParseTuple(args, "ses", &title, "UTF-8", &default_filename)) return NULL; NSSavePanel* panel = [NSSavePanel savePanel]; [panel setTitle: [NSString stringWithCString: title encoding: NSASCIIStringEncoding]]; NSString* ns_default_filename = [[NSString alloc] initWithCString: default_filename encoding: NSUTF8StringEncoding]; PyMem_Free(default_filename); #ifdef COMPILING_FOR_10_6 [panel setNameFieldStringValue: ns_default_filename]; result = [panel runModal]; #else result = [panel runModalForDirectory: nil file: ns_default_filename]; #endif [ns_default_filename release]; #ifdef COMPILING_FOR_10_10 if (result == NSModalResponseOK) #else if (result == NSOKButton) #endif { #ifdef COMPILING_FOR_10_6 NSURL* url = [panel URL]; NSString* filename = [url path]; if (!filename) { PyErr_SetString(PyExc_RuntimeError, "Failed to obtain filename"); return 0; } #else NSString* filename = [panel filename]; #endif unsigned int n = [filename length]; unichar* buffer = malloc(n*sizeof(unichar)); [filename getCharacters: buffer]; #if PY3K PyObject* string = PyUnicode_FromKindAndData(PyUnicode_2BYTE_KIND, buffer, n); #else PyObject* string = PyUnicode_FromUnicode(buffer, n); #endif free(buffer); return string; } Py_RETURN_NONE; } static PyObject* set_cursor(PyObject* unused, PyObject* args) { int i; if(!PyArg_ParseTuple(args, "i", &i)) return NULL; switch (i) { case 0: [[NSCursor pointingHandCursor] set]; break; case 1: [[NSCursor arrowCursor] set]; break; case 2: [[NSCursor crosshairCursor] set]; break; case 3: [[NSCursor openHandCursor] set]; break; /* OSX handles busy state itself so no need to set a cursor here */ case 4: break; default: return NULL; } Py_RETURN_NONE; } @implementation WindowServerConnectionManager static WindowServerConnectionManager *sharedWindowServerConnectionManager = nil; + (WindowServerConnectionManager *)sharedManager { if (sharedWindowServerConnectionManager == nil) { sharedWindowServerConnectionManager = [[super allocWithZone:NULL] init]; } return sharedWindowServerConnectionManager; } + (id)allocWithZone:(NSZone *)zone { return [[self sharedManager] retain]; } + (id)copyWithZone:(NSZone *)zone { return self; } + (id)retain { return self; } - (NSUInteger)retainCount { return NSUIntegerMax; //denotes an object that cannot be released } - (oneway void)release { // Don't release a singleton object } - (id)autorelease { return self; } - (void)launch:(NSNotification*)notification { CFRunLoopRef runloop; CFMachPortRef port; CFRunLoopSourceRef source; NSDictionary* dictionary = [notification userInfo]; if (! [[dictionary valueForKey:@"NSApplicationName"] isEqualToString:@"Python"]) return; NSNumber* psnLow = [dictionary valueForKey: @"NSApplicationProcessSerialNumberLow"]; NSNumber* psnHigh = [dictionary valueForKey: @"NSApplicationProcessSerialNumberHigh"]; ProcessSerialNumber psn; psn.highLongOfPSN = [psnHigh intValue]; psn.lowLongOfPSN = [psnLow intValue]; runloop = CFRunLoopGetCurrent(); port = CGEventTapCreateForPSN(&psn, kCGHeadInsertEventTap, kCGEventTapOptionListenOnly, kCGEventMaskForAllEvents, &_eventtap_callback, runloop); source = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, port, 0); CFRunLoopAddSource(runloop, source, kCFRunLoopDefaultMode); CFRelease(port); } @end @implementation Window - (Window*)initWithContentRect:(NSRect)rect styleMask:(unsigned int)mask backing:(NSBackingStoreType)bufferingType defer:(BOOL)deferCreation withManager: (PyObject*)theManager { self = [super initWithContentRect: rect styleMask: mask backing: bufferingType defer: deferCreation]; manager = theManager; Py_INCREF(manager); return self; } - (NSRect)constrainFrameRect:(NSRect)rect toScreen:(NSScreen*)screen { /* Allow window sizes larger than the screen */ NSRect suggested = [super constrainFrameRect: rect toScreen: screen]; const CGFloat difference = rect.size.height - suggested.size.height; suggested.origin.y -= difference; suggested.size.height += difference; return suggested; } - (BOOL)closeButtonPressed { PyObject* result; PyGILState_STATE gstate; gstate = PyGILState_Ensure(); result = PyObject_CallMethod(manager, "close", ""); if(result) Py_DECREF(result); else PyErr_Print(); PyGILState_Release(gstate); return YES; } - (void)close { [super close]; --FigureWindowCount; if (!FigureWindowCount) [NSApp stop: self]; /* This is needed for show(), which should exit from [NSApp run] * after all windows are closed. */ } - (void)dealloc { PyGILState_STATE gstate; gstate = PyGILState_Ensure(); Py_DECREF(manager); PyGILState_Release(gstate); /* The reference count of the view that was added as a subview to the * content view of this window was increased during the call to addSubview, * and is decreased during the call to [super dealloc]. */ [super dealloc]; } @end @implementation ToolWindow - (ToolWindow*)initWithContentRect:(NSRect)rect master:(NSWindow*)window { [self initWithContentRect: rect styleMask: NSTitledWindowMask | NSClosableWindowMask | NSResizableWindowMask | NSMiniaturizableWindowMask backing: NSBackingStoreBuffered defer: YES]; [self setTitle: @"Subplot Configuration Tool"]; [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(masterCloses:) name: NSWindowWillCloseNotification object: window]; return self; } - (void)masterCloses:(NSNotification*)notification { [self close]; } - (void)close { [[NSNotificationCenter defaultCenter] removeObserver: self]; [super close]; } @end @implementation View - (BOOL)isFlipped { return NO; } - (View*)initWithFrame:(NSRect)rect { self = [super initWithFrame: rect]; rubberband = NSZeroRect; inside = false; tracking = 0; device_scale = 1; return self; } - (void)dealloc { FigureCanvas* fc = (FigureCanvas*)canvas; if (fc) fc->view = NULL; [self removeTrackingRect: tracking]; [super dealloc]; } - (void)setCanvas: (PyObject*)newCanvas { canvas = newCanvas; } static void _buffer_release(void* info, const void* data, size_t size) { PyBuffer_Release((Py_buffer *)info); } static int _copy_agg_buffer(CGContextRef cr, PyObject *renderer) { Py_buffer buffer; if (PyObject_GetBuffer(renderer, &buffer, PyBUF_CONTIG_RO) == -1) { PyErr_Print(); return 1; } if (buffer.ndim != 3 || buffer.shape[2] != 4) { PyBuffer_Release(&buffer); return 1; } const Py_ssize_t nrows = buffer.shape[0]; const Py_ssize_t ncols = buffer.shape[1]; const size_t bytesPerComponent = 1; const size_t bitsPerComponent = 8 * bytesPerComponent; const size_t nComponents = 4; /* red, green, blue, alpha */ const size_t bitsPerPixel = bitsPerComponent * nComponents; const size_t bytesPerRow = nComponents * bytesPerComponent * ncols; CGColorSpaceRef colorspace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB); if (!colorspace) { PyBuffer_Release(&buffer); return 1; } CGDataProviderRef provider = CGDataProviderCreateWithData(&buffer, buffer.buf, buffer.len, _buffer_release); if (!provider) { PyBuffer_Release(&buffer); CGColorSpaceRelease(colorspace); return 1; } CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault | kCGImageAlphaLast; CGImageRef bitmap = CGImageCreate(ncols, nrows, bitsPerComponent, bitsPerPixel, bytesPerRow, colorspace, bitmapInfo, provider, NULL, false, kCGRenderingIntentDefault); CGColorSpaceRelease(colorspace); CGDataProviderRelease(provider); if (!bitmap) { PyBuffer_Release(&buffer); return 1; } CGFloat deviceScale = _get_device_scale(cr); CGContextSaveGState(cr); CGContextDrawImage(cr, CGRectMake(0, 0, ncols/deviceScale, nrows/deviceScale), bitmap); CGImageRelease(bitmap); CGContextRestoreGState(cr); return 0; } -(void)drawRect:(NSRect)rect { PyObject* renderer = NULL; PyObject* renderer_buffer = NULL; PyGILState_STATE gstate = PyGILState_Ensure(); CGContextRef cr = [[NSGraphicsContext currentContext] graphicsPort]; double new_device_scale = _get_device_scale(cr); if (device_scale != new_device_scale) { device_scale = new_device_scale; if (!PyObject_CallMethod(canvas, "_set_device_scale", "d", device_scale, NULL)) { PyErr_Print(); goto exit; } } renderer = PyObject_CallMethod(canvas, "_draw", "", NULL); if (!renderer) { PyErr_Print(); goto exit; } renderer_buffer = PyObject_GetAttrString(renderer, "_renderer"); if (!renderer_buffer) { PyErr_Print(); goto exit; } if (_copy_agg_buffer(cr, renderer_buffer)) { printf("copy_agg_buffer failed\n"); goto exit; } if (!NSIsEmptyRect(rubberband)) { NSFrameRect(rubberband); } exit: Py_XDECREF(renderer_buffer); Py_XDECREF(renderer); PyGILState_Release(gstate); } - (void)windowDidResize: (NSNotification*)notification { int width, height; Window* window = [notification object]; NSSize size = [[window contentView] frame].size; NSRect rect = [self frame]; size.height -= rect.origin.y; width = size.width; height = size.height; [self setFrameSize: size]; PyGILState_STATE gstate = PyGILState_Ensure(); PyObject* result = PyObject_CallMethod( canvas, "resize", "ii", width, height); if(result) Py_DECREF(result); else PyErr_Print(); PyGILState_Release(gstate); if (tracking) [self removeTrackingRect: tracking]; tracking = [self addTrackingRect: [self bounds] owner: self userData: nil assumeInside: NO]; [self setNeedsDisplay: YES]; } - (void)windowWillClose:(NSNotification*)notification { PyGILState_STATE gstate; PyObject* result; gstate = PyGILState_Ensure(); result = PyObject_CallMethod(canvas, "close_event", ""); if(result) Py_DECREF(result); else PyErr_Print(); PyGILState_Release(gstate); } - (BOOL)windowShouldClose:(NSNotification*)notification { NSWindow* window = [self window]; NSEvent* event = [NSEvent otherEventWithType: NSApplicationDefined location: NSZeroPoint modifierFlags: 0 timestamp: 0.0 windowNumber: 0 context: nil subtype: WINDOW_CLOSING data1: 0 data2: 0]; [NSApp postEvent: event atStart: true]; if ([window respondsToSelector: @selector(closeButtonPressed)]) { BOOL closed = [((Window*) window) closeButtonPressed]; /* If closed, the window has already been closed via the manager. */ if (closed) return NO; } return YES; } - (void)mouseEntered:(NSEvent *)event { PyGILState_STATE gstate; PyObject* result; NSWindow* window = [self window]; if ([window isKeyWindow]==false) return; gstate = PyGILState_Ensure(); result = PyObject_CallMethod(canvas, "enter_notify_event", ""); if(result) Py_DECREF(result); else PyErr_Print(); PyGILState_Release(gstate); [window setAcceptsMouseMovedEvents: YES]; inside = true; } - (void)mouseExited:(NSEvent *)event { PyGILState_STATE gstate; PyObject* result; NSWindow* window = [self window]; if ([window isKeyWindow]==false) return; if (inside==false) return; gstate = PyGILState_Ensure(); result = PyObject_CallMethod(canvas, "leave_notify_event", ""); if(result) Py_DECREF(result); else PyErr_Print(); PyGILState_Release(gstate); [[self window] setAcceptsMouseMovedEvents: NO]; inside = false; } - (void)mouseDown:(NSEvent *)event { int x, y; int num; int dblclick = 0; PyObject* result; PyGILState_STATE gstate; NSPoint location = [event locationInWindow]; location = [self convertPoint: location fromView: nil]; x = location.x * device_scale; y = location.y * device_scale; switch ([event type]) { case NSLeftMouseDown: { unsigned int modifier = [event modifierFlags]; if (modifier & NSControlKeyMask) /* emulate a right-button click */ num = 3; else if (modifier & NSAlternateKeyMask) /* emulate a middle-button click */ num = 2; else { num = 1; if ([NSCursor currentCursor]==[NSCursor openHandCursor]) [[NSCursor closedHandCursor] set]; } break; } case NSOtherMouseDown: num = 2; break; case NSRightMouseDown: num = 3; break; default: return; /* Unknown mouse event */ } if ([event clickCount] == 2) { dblclick = 1; } gstate = PyGILState_Ensure(); result = PyObject_CallMethod(canvas, "button_press_event", "iiii", x, y, num, dblclick); if(result) Py_DECREF(result); else PyErr_Print(); PyGILState_Release(gstate); } - (void)mouseUp:(NSEvent *)event { int num; int x, y; PyObject* result; PyGILState_STATE gstate; NSPoint location = [event locationInWindow]; location = [self convertPoint: location fromView: nil]; x = location.x * device_scale; y = location.y * device_scale; switch ([event type]) { case NSLeftMouseUp: num = 1; if ([NSCursor currentCursor]==[NSCursor closedHandCursor]) [[NSCursor openHandCursor] set]; break; case NSOtherMouseUp: num = 2; break; case NSRightMouseUp: num = 3; break; default: return; /* Unknown mouse event */ } gstate = PyGILState_Ensure(); result = PyObject_CallMethod(canvas, "button_release_event", "iii", x, y, num); if(result) Py_DECREF(result); else PyErr_Print(); PyGILState_Release(gstate); } - (void)mouseMoved:(NSEvent *)event { int x, y; NSPoint location = [event locationInWindow]; location = [self convertPoint: location fromView: nil]; x = location.x * device_scale; y = location.y * device_scale; PyGILState_STATE gstate = PyGILState_Ensure(); PyObject* result = PyObject_CallMethod(canvas, "motion_notify_event", "ii", x, y); if(result) Py_DECREF(result); else PyErr_Print(); PyGILState_Release(gstate); } - (void)mouseDragged:(NSEvent *)event { int x, y; NSPoint location = [event locationInWindow]; location = [self convertPoint: location fromView: nil]; x = location.x * device_scale; y = location.y * device_scale; PyGILState_STATE gstate = PyGILState_Ensure(); PyObject* result = PyObject_CallMethod(canvas, "motion_notify_event", "ii", x, y); if(result) Py_DECREF(result); else PyErr_Print(); PyGILState_Release(gstate); } - (void)rightMouseDown:(NSEvent *)event { int x, y; int num = 3; int dblclick = 0; PyObject* result; PyGILState_STATE gstate; NSPoint location = [event locationInWindow]; location = [self convertPoint: location fromView: nil]; x = location.x * device_scale; y = location.y * device_scale; gstate = PyGILState_Ensure(); if ([event clickCount] == 2) { dblclick = 1; } result = PyObject_CallMethod(canvas, "button_press_event", "iiii", x, y, num, dblclick); if(result) Py_DECREF(result); else PyErr_Print(); PyGILState_Release(gstate); } - (void)rightMouseUp:(NSEvent *)event { int x, y; int num = 3; PyObject* result; PyGILState_STATE gstate; NSPoint location = [event locationInWindow]; location = [self convertPoint: location fromView: nil]; x = location.x * device_scale; y = location.y * device_scale; gstate = PyGILState_Ensure(); result = PyObject_CallMethod(canvas, "button_release_event", "iii", x, y, num); if(result) Py_DECREF(result); else PyErr_Print(); PyGILState_Release(gstate); } - (void)rightMouseDragged:(NSEvent *)event { int x, y; NSPoint location = [event locationInWindow]; location = [self convertPoint: location fromView: nil]; x = location.x * device_scale; y = location.y * device_scale; PyGILState_STATE gstate = PyGILState_Ensure(); PyObject* result = PyObject_CallMethod(canvas, "motion_notify_event", "ii", x, y); if(result) Py_DECREF(result); else PyErr_Print(); PyGILState_Release(gstate); } - (void)otherMouseDown:(NSEvent *)event { int x, y; int num = 2; int dblclick = 0; PyObject* result; PyGILState_STATE gstate; NSPoint location = [event locationInWindow]; location = [self convertPoint: location fromView: nil]; x = location.x * device_scale; y = location.y * device_scale; gstate = PyGILState_Ensure(); if ([event clickCount] == 2) { dblclick = 1; } result = PyObject_CallMethod(canvas, "button_press_event", "iiii", x, y, num, dblclick); if(result) Py_DECREF(result); else PyErr_Print(); PyGILState_Release(gstate); } - (void)otherMouseUp:(NSEvent *)event { int x, y; int num = 2; PyObject* result; PyGILState_STATE gstate; NSPoint location = [event locationInWindow]; location = [self convertPoint: location fromView: nil]; x = location.x * device_scale; y = location.y * device_scale; gstate = PyGILState_Ensure(); result = PyObject_CallMethod(canvas, "button_release_event", "iii", x, y, num); if(result) Py_DECREF(result); else PyErr_Print(); PyGILState_Release(gstate); } - (void)otherMouseDragged:(NSEvent *)event { int x, y; NSPoint location = [event locationInWindow]; location = [self convertPoint: location fromView: nil]; x = location.x * device_scale; y = location.y * device_scale; PyGILState_STATE gstate = PyGILState_Ensure(); PyObject* result = PyObject_CallMethod(canvas, "motion_notify_event", "ii", x, y); if(result) Py_DECREF(result); else PyErr_Print(); PyGILState_Release(gstate); } - (void)setRubberband:(NSRect)rect { if (!NSIsEmptyRect(rubberband)) [self setNeedsDisplayInRect: rubberband]; rubberband = rect; [self setNeedsDisplayInRect: rubberband]; } - (void)removeRubberband { if (NSIsEmptyRect(rubberband)) return; [self setNeedsDisplayInRect: rubberband]; rubberband = NSZeroRect; } - (const char*)convertKeyEvent:(NSEvent*)event { NSDictionary* specialkeymappings = [NSDictionary dictionaryWithObjectsAndKeys: @"left", [NSNumber numberWithUnsignedLong:NSLeftArrowFunctionKey], @"right", [NSNumber numberWithUnsignedLong:NSRightArrowFunctionKey], @"up", [NSNumber numberWithUnsignedLong:NSUpArrowFunctionKey], @"down", [NSNumber numberWithUnsignedLong:NSDownArrowFunctionKey], @"f1", [NSNumber numberWithUnsignedLong:NSF1FunctionKey], @"f2", [NSNumber numberWithUnsignedLong:NSF2FunctionKey], @"f3", [NSNumber numberWithUnsignedLong:NSF3FunctionKey], @"f4", [NSNumber numberWithUnsignedLong:NSF4FunctionKey], @"f5", [NSNumber numberWithUnsignedLong:NSF5FunctionKey], @"f6", [NSNumber numberWithUnsignedLong:NSF6FunctionKey], @"f7", [NSNumber numberWithUnsignedLong:NSF7FunctionKey], @"f8", [NSNumber numberWithUnsignedLong:NSF8FunctionKey], @"f9", [NSNumber numberWithUnsignedLong:NSF9FunctionKey], @"f10", [NSNumber numberWithUnsignedLong:NSF10FunctionKey], @"f11", [NSNumber numberWithUnsignedLong:NSF11FunctionKey], @"f12", [NSNumber numberWithUnsignedLong:NSF12FunctionKey], @"f13", [NSNumber numberWithUnsignedLong:NSF13FunctionKey], @"f14", [NSNumber numberWithUnsignedLong:NSF14FunctionKey], @"f15", [NSNumber numberWithUnsignedLong:NSF15FunctionKey], @"f16", [NSNumber numberWithUnsignedLong:NSF16FunctionKey], @"f17", [NSNumber numberWithUnsignedLong:NSF17FunctionKey], @"f18", [NSNumber numberWithUnsignedLong:NSF18FunctionKey], @"f19", [NSNumber numberWithUnsignedLong:NSF19FunctionKey], @"scroll_lock", [NSNumber numberWithUnsignedLong:NSScrollLockFunctionKey], @"break", [NSNumber numberWithUnsignedLong:NSBreakFunctionKey], @"insert", [NSNumber numberWithUnsignedLong:NSInsertFunctionKey], @"delete", [NSNumber numberWithUnsignedLong:NSDeleteFunctionKey], @"home", [NSNumber numberWithUnsignedLong:NSHomeFunctionKey], @"end", [NSNumber numberWithUnsignedLong:NSEndFunctionKey], @"pagedown", [NSNumber numberWithUnsignedLong:NSPageDownFunctionKey], @"pageup", [NSNumber numberWithUnsignedLong:NSPageUpFunctionKey], @"backspace", [NSNumber numberWithUnsignedLong:NSDeleteCharacter], @"enter", [NSNumber numberWithUnsignedLong:NSEnterCharacter], @"tab", [NSNumber numberWithUnsignedLong:NSTabCharacter], @"enter", [NSNumber numberWithUnsignedLong:NSCarriageReturnCharacter], @"backtab", [NSNumber numberWithUnsignedLong:NSBackTabCharacter], @"escape", [NSNumber numberWithUnsignedLong:27], nil ]; NSMutableString* returnkey = [NSMutableString string]; if ([event modifierFlags] & NSControlKeyMask) [returnkey appendString:@"ctrl+" ]; if ([event modifierFlags] & NSAlternateKeyMask) [returnkey appendString:@"alt+" ]; if ([event modifierFlags] & NSCommandKeyMask) [returnkey appendString:@"cmd+" ]; unichar uc = [[event charactersIgnoringModifiers] characterAtIndex:0]; NSString* specialchar = [specialkeymappings objectForKey:[NSNumber numberWithUnsignedLong:uc]]; if (specialchar){ if ([event modifierFlags] & NSShiftKeyMask) [returnkey appendString:@"shift+" ]; [returnkey appendString:specialchar]; } else [returnkey appendString:[event charactersIgnoringModifiers]]; return [returnkey UTF8String]; } - (void)keyDown:(NSEvent*)event { PyObject* result; const char* s = [self convertKeyEvent: event]; PyGILState_STATE gstate = PyGILState_Ensure(); if (s==NULL) { result = PyObject_CallMethod(canvas, "key_press_event", "O", Py_None); } else { result = PyObject_CallMethod(canvas, "key_press_event", "s", s); } if(result) Py_DECREF(result); else PyErr_Print(); PyGILState_Release(gstate); } - (void)keyUp:(NSEvent*)event { PyObject* result; const char* s = [self convertKeyEvent: event]; PyGILState_STATE gstate = PyGILState_Ensure(); if (s==NULL) { result = PyObject_CallMethod(canvas, "key_release_event", "O", Py_None); } else { result = PyObject_CallMethod(canvas, "key_release_event", "s", s); } if(result) Py_DECREF(result); else PyErr_Print(); PyGILState_Release(gstate); } - (void)scrollWheel:(NSEvent*)event { int step; float d = [event deltaY]; if (d > 0) step = 1; else if (d < 0) step = -1; else return; NSPoint location = [event locationInWindow]; NSPoint point = [self convertPoint: location fromView: nil]; int x = (int)round(point.x * device_scale); int y = (int)round(point.y * device_scale - 1); PyObject* result; PyGILState_STATE gstate = PyGILState_Ensure(); result = PyObject_CallMethod(canvas, "scroll_event", "iii", x, y, step); if(result) Py_DECREF(result); else PyErr_Print(); PyGILState_Release(gstate); } - (BOOL)acceptsFirstResponder { return YES; } /* This is all wrong. Address of pointer is being passed instead of pointer, keynames don't match up with what the front-end and does the front-end even handle modifier keys by themselves? - (void)flagsChanged:(NSEvent*)event { const char *s = NULL; if (([event modifierFlags] & NSControlKeyMask) == NSControlKeyMask) s = "control"; else if (([event modifierFlags] & NSShiftKeyMask) == NSShiftKeyMask) s = "shift"; else if (([event modifierFlags] & NSAlternateKeyMask) == NSAlternateKeyMask) s = "alt"; else return; PyGILState_STATE gstate = PyGILState_Ensure(); PyObject* result = PyObject_CallMethod(canvas, "key_press_event", "s", &s); if(result) Py_DECREF(result); else PyErr_Print(); PyGILState_Release(gstate); } */ @end @implementation ScrollableButton - (void)setScrollWheelUpAction:(SEL)action { scrollWheelUpAction = action; } - (void)setScrollWheelDownAction:(SEL)action { scrollWheelDownAction = action; } - (void)scrollWheel:(NSEvent*)event { float d = [event deltaY]; Window* target = [self target]; if (d > 0) [NSApp sendAction: scrollWheelUpAction to: target from: self]; else if (d < 0) [NSApp sendAction: scrollWheelDownAction to: target from: self]; } @end @implementation MenuItem + (MenuItem*)menuItemWithTitle: (NSString*)title { MenuItem* item = [[MenuItem alloc] initWithTitle: title action: nil keyEquivalent: @""]; item->index = -1; return [item autorelease]; } + (MenuItem*)menuItemForAxis: (int)i { NSString* title = [NSString stringWithFormat: @"Axis %d", i+1]; MenuItem* item = [[MenuItem alloc] initWithTitle: title action: @selector(toggle:) keyEquivalent: @""]; [item setTarget: item]; [item setState: NSOnState]; item->index = i; return [item autorelease]; } + (MenuItem*)menuItemSelectAll { MenuItem* item = [[MenuItem alloc] initWithTitle: @"Select All" action: @selector(selectAll:) keyEquivalent: @""]; [item setTarget: item]; item->index = -1; return [item autorelease]; } + (MenuItem*)menuItemInvertAll { MenuItem* item = [[MenuItem alloc] initWithTitle: @"Invert All" action: @selector(invertAll:) keyEquivalent: @""]; [item setTarget: item]; item->index = -1; return [item autorelease]; } - (void)toggle:(id)sender { if ([self state]) [self setState: NSOffState]; else [self setState: NSOnState]; } - (void)selectAll:(id)sender { NSMenu* menu = [sender menu]; if(!menu) return; /* Weird */ NSArray* items = [menu itemArray]; NSEnumerator* enumerator = [items objectEnumerator]; MenuItem* item; while ((item = [enumerator nextObject])) { if (item->index >= 0) [item setState: NSOnState]; } } - (void)invertAll:(id)sender { NSMenu* menu = [sender menu]; if(!menu) return; /* Weird */ NSArray* items = [menu itemArray]; NSEnumerator* enumerator = [items objectEnumerator]; MenuItem* item; while ((item = [enumerator nextObject])) { if (item->index < 0) continue; if ([item state]==NSOffState) [item setState: NSOnState]; else [item setState: NSOffState]; } } - (int)index { return self->index; } @end static PyObject* show(PyObject* self) { [NSApp activateIgnoringOtherApps: YES]; NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; NSArray *windowsArray = [NSApp windows]; NSEnumerator *enumerator = [windowsArray objectEnumerator]; NSWindow *window; while ((window = [enumerator nextObject])) { [window orderFront:nil]; } [pool release]; Py_BEGIN_ALLOW_THREADS [NSApp run]; Py_END_ALLOW_THREADS Py_RETURN_NONE; } typedef struct { PyObject_HEAD CFRunLoopTimerRef timer; } Timer; static PyObject* Timer_new(PyTypeObject* type, PyObject *args, PyObject *kwds) { Timer* self = (Timer*)type->tp_alloc(type, 0); if (!self) return NULL; self->timer = NULL; return (PyObject*) self; } static PyObject* Timer_repr(Timer* self) { #if PY3K return PyUnicode_FromFormat("Timer object %p wrapping CFRunLoopTimerRef %p", (void*) self, (void*)(self->timer)); #else return PyString_FromFormat("Timer object %p wrapping CFRunLoopTimerRef %p", (void*) self, (void*)(self->timer)); #endif } static char Timer_doc[] = "A Timer object wraps a CFRunLoopTimerRef and can add it to the event loop.\n"; static void timer_callback(CFRunLoopTimerRef timer, void* info) { PyObject* method = info; PyGILState_STATE gstate = PyGILState_Ensure(); PyObject* result = PyObject_CallFunction(method, NULL); if (result) { Py_DECREF(result); } else { PyErr_Print(); } PyGILState_Release(gstate); } static void context_cleanup(const void* info) { Py_DECREF((PyObject*)info); } static PyObject* Timer__timer_start(Timer* self, PyObject* args) { CFRunLoopRef runloop; CFRunLoopTimerRef timer; CFRunLoopTimerContext context; double milliseconds; CFTimeInterval interval; PyObject* attribute; PyObject* failure; runloop = CFRunLoopGetCurrent(); if (!runloop) { PyErr_SetString(PyExc_RuntimeError, "Failed to obtain run loop"); return NULL; } attribute = PyObject_GetAttrString((PyObject*)self, "_interval"); if (attribute==NULL) { PyErr_SetString(PyExc_AttributeError, "Timer has no attribute '_interval'"); return NULL; } milliseconds = PyFloat_AsDouble(attribute); failure = PyErr_Occurred(); Py_DECREF(attribute); if (failure) return NULL; attribute = PyObject_GetAttrString((PyObject*)self, "_single"); if (attribute==NULL) { PyErr_SetString(PyExc_AttributeError, "Timer has no attribute '_single'"); return NULL; } switch (PyObject_IsTrue(attribute)) { case 1: interval = 0; break; case 0: interval = milliseconds / 1000.0; break; case -1: default: PyErr_SetString(PyExc_ValueError, "Cannot interpret _single attribute as True of False"); return NULL; } Py_DECREF(attribute); attribute = PyObject_GetAttrString((PyObject*)self, "_on_timer"); if (attribute==NULL) { PyErr_SetString(PyExc_AttributeError, "Timer has no attribute '_on_timer'"); return NULL; } if (!PyMethod_Check(attribute)) { PyErr_SetString(PyExc_RuntimeError, "_on_timer should be a Python method"); return NULL; } context.version = 0; context.retain = NULL; context.release = context_cleanup; context.copyDescription = NULL; context.info = attribute; timer = CFRunLoopTimerCreate(kCFAllocatorDefault, 0, interval, 0, 0, timer_callback, &context); if (!timer) { Py_DECREF(attribute); PyErr_SetString(PyExc_RuntimeError, "Failed to create timer"); return NULL; } if (self->timer) { CFRunLoopTimerInvalidate(self->timer); CFRelease(self->timer); } CFRunLoopAddTimer(runloop, timer, kCFRunLoopCommonModes); /* Don't release the timer here, since the run loop may be destroyed and * the timer lost before we have a chance to decrease the reference count * of the attribute */ self->timer = timer; Py_RETURN_NONE; } static PyObject* Timer__timer_stop(Timer* self) { if (self->timer) { CFRunLoopTimerInvalidate(self->timer); CFRelease(self->timer); self->timer = NULL; } Py_RETURN_NONE; } static void Timer_dealloc(Timer* self) { Timer__timer_stop(self); Py_TYPE(self)->tp_free((PyObject*)self); } static PyMethodDef Timer_methods[] = { {"_timer_start", (PyCFunction)Timer__timer_start, METH_VARARGS, "Initialize and start the timer." }, {"_timer_stop", (PyCFunction)Timer__timer_stop, METH_NOARGS, "Stop the timer." }, {NULL} /* Sentinel */ }; static PyTypeObject TimerType = { PyVarObject_HEAD_INIT(NULL, 0) "_macosx.Timer", /*tp_name*/ sizeof(Timer), /*tp_basicsize*/ 0, /*tp_itemsize*/ (destructor)Timer_dealloc, /*tp_dealloc*/ 0, /*tp_print*/ 0, /*tp_getattr*/ 0, /*tp_setattr*/ 0, /*tp_compare*/ (reprfunc)Timer_repr, /*tp_repr*/ 0, /*tp_as_number*/ 0, /*tp_as_sequence*/ 0, /*tp_as_mapping*/ 0, /*tp_hash */ 0, /*tp_call*/ 0, /*tp_str*/ 0, /*tp_getattro*/ 0, /*tp_setattro*/ 0, /*tp_as_buffer*/ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ Timer_doc, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ Timer_methods, /* tp_methods */ 0, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ 0, /* tp_init */ 0, /* tp_alloc */ Timer_new, /* tp_new */ }; static bool verify_framework(void) { #ifdef COMPILING_FOR_10_6 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; NSRunningApplication* app = [NSRunningApplication currentApplication]; NSApplicationActivationPolicy activationPolicy = [app activationPolicy]; [pool release]; switch (activationPolicy) { case NSApplicationActivationPolicyRegular: case NSApplicationActivationPolicyAccessory: return true; case NSApplicationActivationPolicyProhibited: break; } #else ProcessSerialNumber psn; if (CGMainDisplayID()!=0 && GetCurrentProcess(&psn)==noErr && SetFrontProcess(&psn)==noErr) return true; #endif PyErr_SetString(PyExc_RuntimeError, "Python is not installed as a framework. The Mac OS X backend will " "not be able to function correctly if Python is not installed as a " "framework. See the Python documentation for more information on " "installing Python as a framework on Mac OS X. Please either reinstall " "Python as a framework, or try one of the other backends. If you are " "using (Ana)Conda please install python.app and replace the use of 'python' " "with 'pythonw'. See 'Working with Matplotlib on OSX' " "in the Matplotlib FAQ for more information."); return false; } static struct PyMethodDef methods[] = { {"show", (PyCFunction)show, METH_NOARGS, "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." }, {"choose_save_file", (PyCFunction)choose_save_file, METH_VARARGS, "Closes the window." }, {"set_cursor", (PyCFunction)set_cursor, METH_VARARGS, "Sets the active cursor." }, {NULL, NULL, 0, NULL}/* sentinel */ }; #if PY3K static struct PyModuleDef moduledef = { PyModuleDef_HEAD_INIT, "_macosx", "Mac OS X native backend", -1, methods, NULL, NULL, NULL, NULL }; PyObject* PyInit__macosx(void) #else void init_macosx(void) #endif { PyObject *module; if (PyType_Ready(&FigureCanvasType) < 0 || PyType_Ready(&FigureManagerType) < 0 || PyType_Ready(&NavigationToolbarType) < 0 || PyType_Ready(&NavigationToolbar2Type) < 0 || PyType_Ready(&TimerType) < 0) #if PY3K return NULL; #else return; #endif NSApp = [NSApplication sharedApplication]; if (!verify_framework()) #if PY3K return NULL; #else return; #endif #if PY3K module = PyModule_Create(&moduledef); if (module==NULL) return NULL; #else module = Py_InitModule4("_macosx", methods, "Mac OS X native backend", NULL, PYTHON_API_VERSION); #endif Py_INCREF(&FigureCanvasType); Py_INCREF(&FigureManagerType); Py_INCREF(&NavigationToolbarType); Py_INCREF(&NavigationToolbar2Type); Py_INCREF(&TimerType); PyModule_AddObject(module, "FigureCanvas", (PyObject*) &FigureCanvasType); PyModule_AddObject(module, "FigureManager", (PyObject*) &FigureManagerType); PyModule_AddObject(module, "NavigationToolbar", (PyObject*) &NavigationToolbarType); PyModule_AddObject(module, "NavigationToolbar2", (PyObject*) &NavigationToolbar2Type); PyModule_AddObject(module, "Timer", (PyObject*) &TimerType); PyOS_InputHook = wait_for_stdin; NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; WindowServerConnectionManager* connectionManager = [WindowServerConnectionManager sharedManager]; NSWorkspace* workspace = [NSWorkspace sharedWorkspace]; NSNotificationCenter* notificationCenter = [workspace notificationCenter]; [notificationCenter addObserver: connectionManager selector: @selector(launch:) name: NSWorkspaceDidLaunchApplicationNotification object: nil]; [pool release]; #if PY3K return module; #endif }