diff options
author | maxim-yurchuk <maxim-yurchuk@yandex-team.com> | 2024-10-09 12:29:46 +0300 |
---|---|---|
committer | maxim-yurchuk <maxim-yurchuk@yandex-team.com> | 2024-10-09 13:14:22 +0300 |
commit | 9731d8a4bb7ee2cc8554eaf133bb85498a4c7d80 (patch) | |
tree | a8fb3181d5947c0d78cf402aa56e686130179049 /contrib/python/matplotlib/py2/src/_macosx.m | |
parent | a44b779cd359f06c3ebbef4ec98c6b38609d9d85 (diff) | |
download | ydb-9731d8a4bb7ee2cc8554eaf133bb85498a4c7d80.tar.gz |
publishFullContrib: true for ydb
<HIDDEN_URL>
commit_hash:c82a80ac4594723cebf2c7387dec9c60217f603e
Diffstat (limited to 'contrib/python/matplotlib/py2/src/_macosx.m')
-rw-r--r-- | contrib/python/matplotlib/py2/src/_macosx.m | 3174 |
1 files changed, 3174 insertions, 0 deletions
diff --git a/contrib/python/matplotlib/py2/src/_macosx.m b/contrib/python/matplotlib/py2/src/_macosx.m new file mode 100644 index 0000000000..8f44f1eb0c --- /dev/null +++ b/contrib/python/matplotlib/py2/src/_macosx.m @@ -0,0 +1,3174 @@ +#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 +} |