#include <Cocoa/Cocoa.h>
#include <ApplicationServices/ApplicationServices.h>
#include <sys/socket.h>
#include <Python.h>
#include "numpy/arrayobject.h"
#include "path_cleanup.h"
/* 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 >= 1050
#define COMPILING_FOR_10_5
#endif
static int nwin = 0; /* The number of open windows */
/* Use Atsui for Mac OS X 10.4, CoreText for Mac OS X 10.5 */
#ifndef COMPILING_FOR_10_5
static int ngc = 0; /* The number of graphics contexts in use */
/* For drawing Unicode strings with ATSUI */
static ATSUStyle style = NULL;
static ATSUTextLayout layout = NULL;
#endif
/* CGFloat was defined in Mac OS X 10.5 */
#ifndef CGFLOAT_DEFINED
#define CGFloat float
#endif
/* Various NSApplicationDefined event subtypes */
#define STDIN_READY 0
#define SIGINT_CALLED 1
#define STOP_EVENT_LOOP 2
#define WINDOW_CLOSING 3
/* Path definitions */
#define STOP 0
#define MOVETO 1
#define LINETO 2
#define CURVE3 3
#define CURVE4 4
#define CLOSEPOLY 0x4f
/* Hatching */
#define HATCH_SIZE 72
/* -------------------------- Helper function ---------------------------- */
static void stdin_ready(CFReadStreamRef readStream, CFStreamEventType eventType, void* context)
{
NSEvent* event = [NSEvent otherEventWithType: NSApplicationDefined
location: NSZeroPoint
modifierFlags: 0
timestamp: 0.0
windowNumber: 0
context: nil
subtype: STDIN_READY
data1: 0
data2: 0];
[NSApp postEvent: event atStart: true];
}
static int sigint_fd = -1;
static void _sigint_handler(int sig)
{
const char c = 'i';
write(sigint_fd, &c, 1);
}
static void _callback(CFSocketRef s,
CFSocketCallBackType type,
CFDataRef address,
const void * data,
void *info)
{
char c;
CFSocketNativeHandle handle = CFSocketGetNative(s);
read(handle, &c, 1);
NSEvent* event = [NSEvent otherEventWithType: NSApplicationDefined
location: NSZeroPoint
modifierFlags: 0
timestamp: 0.0
windowNumber: 0
context: nil
subtype: SIGINT_CALLED
data1: 0
data2: 0];
[NSApp postEvent: event atStart: true];
}
static int wait_for_stdin(void)
{
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);
if (!CFReadStreamHasBytesAvailable(stream))
/* This is possible because of how PyOS_InputHook is called from Python */
{
int error;
int interrupted = 0;
int channel[2];
CFSocketRef sigint_socket = NULL;
PyOS_sighandler_t py_sigint_handler = NULL;
CFStreamClientContext clientContext = {0, NULL, NULL, NULL, NULL};
CFReadStreamSetClient(stream,
kCFStreamEventHasBytesAvailable,
stdin_ready,
&clientContext);
CFReadStreamScheduleWithRunLoop(stream, runloop, kCFRunLoopCommonModes);
error = pipe(channel);
if (error==0)
{
fcntl(channel[1], F_SETFL, O_WRONLY | O_NONBLOCK);
sigint_socket = CFSocketCreateWithNative(kCFAllocatorDefault,
channel[0],
kCFSocketReadCallBack,
_callback,
NULL);
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 = [NSDate distantFuture];
while (true)
{ NSEvent* event = [NSApp nextEventMatchingMask: NSAnyEventMask
untilDate: date
inMode: NSDefaultRunLoopMode
dequeue: YES];
if (!event) break; /* No windows open */
if ([event type]==NSApplicationDefined)
{ short subtype = [event subtype];
if (subtype==STDIN_READY) break;
if (subtype==SIGINT_CALLED)
{ interrupted = true;
break;
}
}
[NSApp sendEvent: event];
}
[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[1]);
if (interrupted) raise(SIGINT);
}
CFReadStreamClose(stream);
return 1;
}
#ifndef COMPILING_FOR_10_5
static int _init_atsui(void)
{
OSStatus status;
status = ATSUCreateStyle(&style);
if (status!=noErr)
{
PyErr_SetString(PyExc_RuntimeError, "ATSUCreateStyle failed");
return 0;
}
status = ATSUCreateTextLayout(&layout);
if (status!=noErr)
{
status = ATSUDisposeStyle(style);
if (status!=noErr)
PyErr_WarnEx(PyExc_RuntimeWarning, "ATSUDisposeStyle failed", 1);
PyErr_SetString(PyExc_RuntimeError, "ATSUCreateTextLayout failed");
return 0;
}
return 1;
}
static void _dealloc_atsui(void)
{
OSStatus status;
status = ATSUDisposeStyle(style);
if (status!=noErr)
PyErr_WarnEx(PyExc_RuntimeWarning, "ATSUDisposeStyle failed", 1);
status = ATSUDisposeTextLayout(layout);
if (status!=noErr)
PyErr_WarnEx(PyExc_RuntimeWarning, "ATSUDisposeTextLayout failed", 1);
}
#endif
static int _draw_path(CGContextRef cr, void* iterator)
{
double x1, y1, x2, y2, x3, y3;
int n = 0;
unsigned code;
while (true)
{
code = get_vertex(iterator, &x1, &y1);
if (code == CLOSEPOLY)
{
CGContextClosePath(cr);
n++;
}
else if (code == STOP)
{
break;
}
else if (code == MOVETO)
{
CGContextMoveToPoint(cr, x1, y1);
n++;
}
else if (code==LINETO)
{
CGContextAddLineToPoint(cr, x1, y1);
n++;
}
else if (code==CURVE3)
{
get_vertex(iterator, &x2, &y2);
CGContextAddQuadCurveToPoint(cr, x1, y1, x2, y2);
n+=2;
}
else if (code==CURVE4)
{
get_vertex(iterator, &x2, &y2);
get_vertex(iterator, &x3, &y3);
CGContextAddCurveToPoint(cr, x1, y1, x2, y2, x3, y3);
n+=3;
}
}
return n;
}
static void _draw_hatch(void *info, CGContextRef cr)
{
PyObject* hatchpath = (PyObject*)info;
PyObject* transform;
int nd = 2;
npy_intp dims[2] = {3, 3};
int typenum = NPY_DOUBLE;
double data[9] = {HATCH_SIZE, 0, 0, 0, HATCH_SIZE, 0, 0, 0, 1};
double rect[4] = { 0.0, 0.0, HATCH_SIZE, HATCH_SIZE};
int n;
transform = PyArray_SimpleNewFromData(nd, dims, typenum, data);
if (!transform)
{
PyGILState_STATE gstate = PyGILState_Ensure();
PyErr_Print();
PyGILState_Release(gstate);
return;
}
void* iterator = get_path_iterator(hatchpath,
transform,
0,
0,
rect,
SNAP_FALSE,
1.0,
0);
Py_DECREF(transform);
if (!iterator)
{
PyGILState_STATE gstate = PyGILState_Ensure();
PyErr_SetString(PyExc_RuntimeError, "failed to obtain path iterator for hatching");
PyErr_Print();
PyGILState_Release(gstate);
return;
}
n = _draw_path(cr, iterator);
free_path_iterator(iterator);
if (n==0) return;
CGContextSetLineWidth(cr, 1.0);
CGContextSetLineCap(cr, kCGLineCapSquare);
CGContextDrawPath(cr, kCGPathFillStroke);
}
static void _release_hatch(void* info)
{
PyObject* hatchpath = (PyObject*)info;
Py_DECREF(hatchpath);
}
/* ---------------------------- Cocoa classes ---------------------------- */
@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)close;
- (void)dealloc;
@end
@interface ToolWindow : NSWindow
{
}
- (ToolWindow*)initWithContentRect:(NSRect)rect master:(NSWindow*)window;
- (void)masterCloses:(NSNotification*)notification;
- (void)close;
@end
@interface View : NSView
{ PyObject* canvas;
NSRect rubberband;
BOOL inside;
NSTrackingRectTag tracking;
}
- (void)dealloc;
- (void)drawRect:(NSRect)rect;
- (void)windowDidResize:(NSNotification*)notification;
- (View*)initWithFrame:(NSRect)rect;
- (void)setCanvas: (PyObject*)newCanvas;
- (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;
- (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 ---------------------------- */
typedef struct {
PyObject_HEAD
CGContextRef cr;
NSSize size;
int level;
CGFloat color[4];
} GraphicsContext;
static CGMutablePathRef _create_path(void* iterator)
{
unsigned code;
CGMutablePathRef p;
double x1, y1, x2, y2, x3, y3;
p = CGPathCreateMutable();
if (!p) return NULL;
while (true)
{
code = get_vertex(iterator, &x1, &y1);
if (code == CLOSEPOLY)
{
CGPathCloseSubpath(p);
}
else if (code == STOP)
{
break;
}
else if (code == MOVETO)
{
CGPathMoveToPoint(p, NULL, x1, y1);
}
else if (code==LINETO)
{
CGPathAddLineToPoint(p, NULL, x1, y1);
}
else if (code==CURVE3)
{
get_vertex(iterator, &x2, &y2);
CGPathAddQuadCurveToPoint(p, NULL, x1, y1, x2, y2);
}
else if (code==CURVE4)
{
get_vertex(iterator, &x2, &y2);
get_vertex(iterator, &x3, &y3);
CGPathAddCurveToPoint(p, NULL, x1, y1, x2, y2, x3, y3);
}
}
return p;
}
static int _get_snap(GraphicsContext* self, enum e_snap_mode* mode)
{
PyObject* snap = PyObject_CallMethod((PyObject*)self, "get_snap", "");
if(!snap) return 0;
if(snap==Py_None) *mode = SNAP_AUTO;
else if (PyBool_Check(snap)) *mode = SNAP_TRUE;
else *mode = SNAP_FALSE;
Py_DECREF(snap);
return 1;
}
static PyObject*
GraphicsContext_new(PyTypeObject* type, PyObject *args, PyObject *kwds)
{
GraphicsContext* self = (GraphicsContext*)type->tp_alloc(type, 0);
if (!self) return NULL;
self->cr = NULL;
self->level = 0;
#ifndef COMPILING_FOR_10_5
if (ngc==0)
{
int ok = _init_atsui();
if (!ok)
{
return NULL;
}
}
ngc++;
#endif
return (PyObject*) self;
}
#ifndef COMPILING_FOR_10_5
static void
GraphicsContext_dealloc(GraphicsContext *self)
{
ngc--;
if (ngc==0) _dealloc_atsui();
self->ob_type->tp_free((PyObject*)self);
}
#endif
static PyObject*
GraphicsContext_repr(GraphicsContext* self)
{
return PyString_FromFormat("GraphicsContext object %p wrapping the Quartz 2D graphics context %p", (void*)self, (void*)(self->cr));
}
static PyObject*
GraphicsContext_save (GraphicsContext* self)
{
CGContextRef cr = self->cr;
if (!cr)
{
PyErr_SetString(PyExc_RuntimeError, "CGContextRef is NULL");
return NULL;
}
CGContextSaveGState(cr);
self->level++;
Py_INCREF(Py_None);
return Py_None;
}
static PyObject*
GraphicsContext_restore (GraphicsContext* self)
{
CGContextRef cr = self->cr;
if (!cr)
{
PyErr_SetString(PyExc_RuntimeError, "CGContextRef is NULL");
return NULL;
}
if (self->level==0)
{
PyErr_SetString(PyExc_RuntimeError,
"Attempting to execute CGContextRestoreGState on an empty stack");
return NULL;
}
CGContextRestoreGState(cr);
self->level--;
Py_INCREF(Py_None);
return Py_None;
}
static PyObject*
GraphicsContext_set_alpha (GraphicsContext* self, PyObject* args)
{
float alpha;
if (!PyArg_ParseTuple(args, "f", &alpha)) return NULL;
CGContextRef cr = self->cr;
if (!cr)
{
PyErr_SetString(PyExc_RuntimeError, "CGContextRef is NULL");
return NULL;
}
CGContextSetAlpha(cr, alpha);
self->color[3] = alpha;
Py_INCREF(Py_None);
return Py_None;
}
static BOOL
_set_antialiased(CGContextRef cr, PyObject* antialiased)
{
const int shouldAntialias = PyObject_IsTrue(antialiased);
if (shouldAntialias < 0)
{
PyErr_SetString(PyExc_ValueError,
"Failed to read antialiaseds variable");
return false;
}
CGContextSetShouldAntialias(cr, shouldAntialias);
return true;
}
static PyObject*
GraphicsContext_set_antialiased (GraphicsContext* self, PyObject* args)
{
CGContextRef cr = self->cr;
if (!cr)
{
PyErr_SetString(PyExc_RuntimeError, "CGContextRef is NULL");
return NULL;
}
if (!_set_antialiased(cr, args)) return NULL;
Py_INCREF(Py_None);
return Py_None;
}
static PyObject*
GraphicsContext_set_capstyle (GraphicsContext* self, PyObject* args)
{
char* string;
CGLineCap cap;
if (!PyArg_ParseTuple(args, "s", &string)) return NULL;
if (!strcmp(string, "butt")) cap = kCGLineCapButt;
else if (!strcmp(string, "round")) cap = kCGLineCapRound;
else if (!strcmp(string, "projecting")) cap = kCGLineCapSquare;
else
{
PyErr_SetString(PyExc_ValueError,
"capstyle should be 'butt', 'round', or 'projecting'");
return NULL;
}
CGContextRef cr = self->cr;
if (!cr)
{
PyErr_SetString(PyExc_RuntimeError, "CGContextRef is NULL");
return NULL;
}
CGContextSetLineCap(cr, cap);
Py_INCREF(Py_None);
return Py_None;
}
static PyObject*
GraphicsContext_set_clip_rectangle (GraphicsContext* self, PyObject* args)
{
CGRect rect;
float x, y, width, height;
if (!PyArg_ParseTuple(args, "(ffff)", &x, &y, &width, &height)) return NULL;
rect.origin.x = x;
rect.origin.y = y;
rect.size.width = width;
rect.size.height = height;
CGContextRef cr = self->cr;
if (!cr)
{
PyErr_SetString(PyExc_RuntimeError, "CGContextRef is NULL");
return NULL;
}
CGContextClipToRect(cr, rect);
Py_INCREF(Py_None);
return Py_None;
}
static PyObject*
GraphicsContext_set_clip_path (GraphicsContext* self, PyObject* args)
{
int n;
CGContextRef cr = self->cr;
PyObject* path;
int nd = 2;
npy_intp dims[2] = {3, 3};
int typenum = NPY_DOUBLE;
double data[] = {1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0};
if (!cr)
{
PyErr_SetString(PyExc_RuntimeError, "CGContextRef is NULL");
return NULL;
}
if(!PyArg_ParseTuple(args, "O", &path)) return NULL;
PyObject* transform = PyArray_SimpleNewFromData(nd, dims, typenum, data);
if (!transform) return NULL;
double rect[4] = {0.0, 0.0, self->size.width, self->size.height};
void* iterator = get_path_iterator(path,
transform,
0,
0,
rect,
SNAP_AUTO,
1.0,
0);
Py_DECREF(transform);
if (!iterator)
{
PyErr_SetString(PyExc_RuntimeError,
"set_clip_path: failed to obtain path iterator for clipping");
return NULL;
}
n = _draw_path(cr, iterator);
free_path_iterator(iterator);
if (n > 0) CGContextClip(cr);
Py_INCREF(Py_None);
return Py_None;
}
static BOOL
_set_dashes(CGContextRef cr, PyObject* linestyle)
{
CGFloat phase = 0.0;
PyObject* offset;
PyObject* dashes;
if (!PyArg_ParseTuple(linestyle, "OO", &offset, &dashes))
{
PyErr_SetString(PyExc_TypeError,
"failed to obtain the offset and dashes from the linestyle");
return false;
}
if (offset!=Py_None)
{
if (PyFloat_Check(offset)) phase = PyFloat_AsDouble(offset);
else if (PyInt_Check(offset)) phase = PyInt_AsLong(offset);
else
{
PyErr_SetString(PyExc_TypeError,
"offset should be a floating point value");
return false;
}
}
if (dashes!=Py_None)
{
if (PyList_Check(dashes)) dashes = PyList_AsTuple(dashes);
else if (PyTuple_Check(dashes)) Py_INCREF(dashes);
else
{
PyErr_SetString(PyExc_TypeError,
"dashes should be a tuple or a list");
return false;
}
int n = PyTuple_GET_SIZE(dashes);
int i;
CGFloat* lengths = malloc(n*sizeof(CGFloat));
if(!lengths)
{
PyErr_SetString(PyExc_MemoryError, "Failed to store dashes");
Py_DECREF(dashes);
return false;
}
for (i = 0; i < n; i++)
{
PyObject* value = PyTuple_GET_ITEM(dashes, i);
if (PyFloat_Check(value))
lengths[i] = (CGFloat) PyFloat_AS_DOUBLE(value);
else if (PyInt_Check(value))
lengths[i] = (CGFloat) PyInt_AS_LONG(value);
else break;
}
Py_DECREF(dashes);
if (i < n) /* break encountered */
{
free(lengths);
PyErr_SetString(PyExc_TypeError, "Failed to read dashes");
return false;
}
CGContextSetLineDash(cr, phase, lengths, n);
free(lengths);
}
else
CGContextSetLineDash(cr, phase, NULL, 0);
return true;
}
static PyObject*
GraphicsContext_set_dashes (GraphicsContext* self, PyObject* args)
{
CGContextRef cr = self->cr;
if (!cr)
{
PyErr_SetString(PyExc_RuntimeError, "CGContextRef is NULL");
return NULL;
}
if (!_set_dashes(cr, args))
return NULL;
Py_INCREF(Py_None);
return Py_None;
}
static PyObject*
GraphicsContext_set_foreground(GraphicsContext* self, PyObject* args)
{
float r, g, b;
if(!PyArg_ParseTuple(args, "(fff)", &r, &g, &b)) return NULL;
CGContextRef cr = self->cr;
if (!cr)
{
PyErr_SetString(PyExc_RuntimeError, "CGContextRef is NULL");
return NULL;
}
CGContextSetRGBStrokeColor(cr, r, g, b, 1.0);
CGContextSetRGBFillColor(cr, r, g, b, 1.0);
self->color[0] = r;
self->color[1] = g;
self->color[2] = b;
Py_INCREF(Py_None);
return Py_None;
}
static PyObject*
GraphicsContext_set_graylevel(GraphicsContext* self, PyObject* args)
{ float gray;
if(!PyArg_ParseTuple(args, "f", &gray)) return NULL;
CGContextRef cr = self->cr;
if (!cr)
{
PyErr_SetString(PyExc_RuntimeError, "CGContextRef is NULL");
return NULL;
}
CGContextSetGrayStrokeColor(cr, gray, 1.0);
CGContextSetGrayFillColor(cr, gray, 1.0);
Py_INCREF(Py_None);
return Py_None;
}
static PyObject*
GraphicsContext_set_linewidth (GraphicsContext* self, PyObject* args)
{
float width;
if (!PyArg_ParseTuple(args, "f", &width)) return NULL;
CGContextRef cr = self->cr;
if (!cr)
{
PyErr_SetString(PyExc_RuntimeError, "CGContextRef is NULL");
return NULL;
}
CGContextSetLineWidth(cr, width);
Py_INCREF(Py_None);
return Py_None;
}
static PyObject*
GraphicsContext_set_joinstyle(GraphicsContext* self, PyObject* args)
{ char* string;
CGLineJoin join;
if (!PyArg_ParseTuple(args, "s", &string)) return NULL;
if (!strcmp(string, "miter")) join = kCGLineJoinMiter;
else if (!strcmp(string, "round")) join = kCGLineJoinRound;
else if (!strcmp(string, "bevel")) join = kCGLineJoinBevel;
else
{
PyErr_SetString(PyExc_ValueError,
"joinstyle should be 'miter', 'round', or 'bevel'");
return NULL;
}
CGContextRef cr = self->cr;
if (!cr)
{
PyErr_SetString(PyExc_RuntimeError, "CGContextRef is NULL");
return NULL;
}
CGContextSetLineJoin(cr, join);
Py_INCREF(Py_None);
return Py_None;
}
static PyObject*
GraphicsContext_draw_path (GraphicsContext* self, PyObject* args)
{
PyObject* path;
PyObject* transform;
PyObject* rgbFace;
float linewidth;
int n;
void* iterator;
CGContextRef cr = self->cr;
double rect[4] = { 0.0, 0.0, self->size.width, self->size.height};
if (!cr)
{
PyErr_SetString(PyExc_RuntimeError, "CGContextRef is NULL");
return NULL;
}
if(!PyArg_ParseTuple(args, "OOf|O",
&path,
&transform,
&linewidth,
&rgbFace)) return NULL;
if(rgbFace==Py_None) rgbFace = NULL;
iterator = get_path_iterator(path,
transform,
1,
0,
rect,
SNAP_AUTO,
linewidth,
rgbFace == NULL);
if (!iterator)
{
PyErr_SetString(PyExc_RuntimeError,
"draw_path: failed to obtain path iterator");
return NULL;
}
n = _draw_path(cr, iterator);
free_path_iterator(iterator);
if (n > 0)
{
PyObject* hatchpath;
if(rgbFace)
{
float r, g, b;
if (!PyArg_ParseTuple(rgbFace, "fff", &r, &g, &b))
return NULL;
CGContextSaveGState(cr);
CGContextSetRGBFillColor(cr, r, g, b, 1.0);
CGContextDrawPath(cr, kCGPathFillStroke);
CGContextRestoreGState(cr);
}
else CGContextStrokePath(cr);
hatchpath = PyObject_CallMethod((PyObject*)self, "get_hatch_path", "");
if (!hatchpath)
{
return NULL;
}
else if (hatchpath==Py_None)
{
Py_DECREF(hatchpath);
}
else
{
CGPatternRef pattern;
CGColorSpaceRef baseSpace;
CGColorSpaceRef patternSpace;
static const CGPatternCallbacks callbacks = {0,
&_draw_hatch,
&_release_hatch};
baseSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
if (!baseSpace)
{
Py_DECREF(hatchpath);
PyErr_SetString(PyExc_RuntimeError,
"draw_path: CGColorSpaceCreateWithName failed");
return NULL;
}
patternSpace = CGColorSpaceCreatePattern(baseSpace);
CGColorSpaceRelease(baseSpace);
if (!patternSpace)
{
Py_DECREF(hatchpath);
PyErr_SetString(PyExc_RuntimeError,
"draw_path: CGColorSpaceCreatePattern failed");
return NULL;
}
CGContextSetFillColorSpace(cr, patternSpace);
CGColorSpaceRelease(patternSpace);
pattern = CGPatternCreate((void*)hatchpath,
CGRectMake(0, 0, HATCH_SIZE, HATCH_SIZE),
CGAffineTransformIdentity,
HATCH_SIZE, HATCH_SIZE,
kCGPatternTilingNoDistortion,
false,
&callbacks);
CGContextSetFillPattern(cr, pattern, self->color);
CGPatternRelease(pattern);
iterator = get_path_iterator(path,
transform,
1,
0,
rect,
SNAP_AUTO,
linewidth,
0);
if (!iterator)
{
Py_DECREF(hatchpath);
PyErr_SetString(PyExc_RuntimeError,
"draw_path: failed to obtain path iterator for hatching");
return NULL;
}
n = _draw_path(cr, iterator);
free_path_iterator(iterator);
CGContextFillPath(cr);
}
}
Py_INCREF(Py_None);
return Py_None;
}
static PyObject*
GraphicsContext_draw_markers (GraphicsContext* self, PyObject* args)
{
PyObject* marker_path;
PyObject* marker_transform;
PyObject* path;
PyObject* transform;
float linewidth;
PyObject* rgbFace;
int ok;
float r, g, b;
CGMutablePathRef marker;
void* iterator;
double rect[4] = {0.0, 0.0, self->size.width, self->size.height};
enum e_snap_mode mode;
double xc, yc;
unsigned code;
CGContextRef cr = self->cr;
if (!cr)
{
PyErr_SetString(PyExc_RuntimeError, "CGContextRef is NULL");
return NULL;
}
if(!PyArg_ParseTuple(args, "OOOOf|O",
&marker_path,
&marker_transform,
&path,
&transform,
&linewidth,
&rgbFace)) return NULL;
if(rgbFace==Py_None) rgbFace = NULL;
if (rgbFace)
{
ok = PyArg_ParseTuple(rgbFace, "fff", &r, &g, &b);
if (!ok)
{
return NULL;
}
CGContextSetRGBFillColor(cr, r, g, b, 1.0);
}
ok = _get_snap(self, &mode);
if (!ok)
{
return NULL;
}
iterator = get_path_iterator(marker_path,
marker_transform,
0,
0,
rect,
mode,
linewidth,
0);
if (!iterator)
{
PyErr_SetString(PyExc_RuntimeError,
"draw_markers: failed to obtain path iterator for marker");
return NULL;
}
marker = _create_path(iterator);
free_path_iterator(iterator);
if (!marker)
{
PyErr_SetString(PyExc_RuntimeError,
"draw_markers: failed to draw marker path");
return NULL;
}
iterator = get_path_iterator(path,
transform,
1,
1,
rect,
SNAP_TRUE,
1.0,
0);
if (!iterator)
{
CGPathRelease(marker);
PyErr_SetString(PyExc_RuntimeError,
"draw_markers: failed to obtain path iterator");
return NULL;
}
while (true)
{
code = get_vertex(iterator, &xc, &yc);
if (code == STOP)
{
break;
}
else if (code == MOVETO || code == LINETO || code == CURVE3 || code ==CURVE4)
{
CGContextSaveGState(cr);
CGContextTranslateCTM(cr, xc, yc);
CGContextAddPath(cr, marker);
CGContextRestoreGState(cr);
}
if(rgbFace) CGContextDrawPath(cr, kCGPathFillStroke);
else CGContextStrokePath(cr);
}
free_path_iterator(iterator);
CGPathRelease(marker);
Py_INCREF(Py_None);
return Py_None;
}
static BOOL _clip(CGContextRef cr, PyObject* object)
{
if (object == Py_None) return true;
PyArrayObject* array = NULL;
array = (PyArrayObject*) PyArray_FromObject(object, PyArray_DOUBLE, 2, 2);
if (!array)
{
PyErr_SetString(PyExc_ValueError, "failed to read clipping bounding box");
return false;
}
if (PyArray_NDIM(array)!=2 || PyArray_DIM(array, 0)!=2 || PyArray_DIM(array, 1)!=2)
{
Py_DECREF(array);
PyErr_SetString(PyExc_ValueError, "clipping bounding box should be a 2x2 array");
return false;
}
const double l = *(double*)PyArray_GETPTR2(array, 0, 0);
const double b = *(double*)PyArray_GETPTR2(array, 0, 1);
const double r = *(double*)PyArray_GETPTR2(array, 1, 0);
const double t = *(double*)PyArray_GETPTR2(array, 1, 1);
Py_DECREF(array);
CGRect rect;
rect.origin.x = (CGFloat) l;
rect.origin.y = (CGFloat) b;
rect.size.width = (CGFloat) (r-l);
rect.size.height = (CGFloat) (t-b);
CGContextClipToRect(cr, rect);
return true;
}
static BOOL
_set_offset(CGContextRef cr, PyObject* offsets, int index, PyObject* transform)
{
CGFloat tx;
CGFloat ty;
double x = *(double*)PyArray_GETPTR2(offsets, index, 0);
double y = *(double*)PyArray_GETPTR2(offsets, index, 1);
PyObject* translation = PyObject_CallMethod(transform, "transform_point",
"((ff))", x, y);
if (!translation)
{
return false;
}
if (!PyArray_Check(translation))
{
Py_DECREF(translation);
PyErr_SetString(PyExc_ValueError,
"transform_point did not return a NumPy array");
return false;
}
if (PyArray_NDIM(translation)!=1 || PyArray_DIM(translation, 0)!=2)
{
Py_DECREF(translation);
PyErr_SetString(PyExc_ValueError,
"transform_point did not return an approriate array");
return false;
}
tx = (CGFloat)(*(double*)PyArray_GETPTR1(translation, 0));
ty = (CGFloat)(*(double*)PyArray_GETPTR1(translation, 1));
Py_DECREF(translation);
CGContextTranslateCTM(cr, tx, ty);
return true;
}
static PyObject*
GraphicsContext_draw_path_collection (GraphicsContext* self, PyObject* args)
{
PyObject* cliprect;
PyObject* clippath;
PyObject* clippath_transform;
PyObject* paths;
PyObject* transforms;
PyObject* offsets;
PyObject* offset_transform;
PyObject* facecolors;
PyObject* edgecolors;
PyObject* linewidths;
PyObject* linestyles;
PyObject* antialiaseds;
CGContextRef cr = self->cr;
if (!cr)
{
PyErr_SetString(PyExc_RuntimeError, "CGContextRef is NULL");
return NULL;
}
if(!PyArg_ParseTuple(args, "OOOOOOOOOOOO", &cliprect,
&clippath,
&clippath_transform,
&paths,
&transforms,
&offsets,
&offset_transform,
&facecolors,
&edgecolors,
&linewidths,
&linestyles,
&antialiaseds))
return NULL;
int ok = 1;
Py_ssize_t i;
Py_ssize_t Np = 0;
Py_ssize_t N = 0;
CGMutablePathRef* p = NULL;
/* --------- Prepare some variables for the path iterator ------------- */
void* iterator;
double rect[4] = {0.0, 0.0, self->size.width, self->size.height};
enum e_snap_mode mode;
ok = _get_snap(self, &mode);
if (!ok)
{
return NULL;
}
/* ------------------- Check paths ------------------------------------ */
if (!PySequence_Check(paths))
{
PyErr_SetString(PyExc_ValueError, "paths must be a sequence object");
return NULL;
}
const Py_ssize_t Npaths = PySequence_Size(paths);
/* ------------------- Check transforms ------------------------------- */
if (!PySequence_Check(transforms))
{
PyErr_SetString(PyExc_ValueError, "transforms must be a sequence object");
return NULL;
}
const Py_ssize_t Ntransforms = PySequence_Size(transforms);
if (Ntransforms==0)
{
PyErr_SetString(PyExc_ValueError, "transforms should contain at least one item");
return NULL;
}
/* ------------------- Read drawing arrays ---------------------------- */
CGContextSaveGState(cr);
offsets = PyArray_FromObject(offsets, NPY_DOUBLE, 0, 2);
facecolors = PyArray_FromObject(facecolors, NPY_DOUBLE, 1, 2);
edgecolors = PyArray_FromObject(edgecolors, NPY_DOUBLE, 1, 2);
/* ------------------- Check offsets array ---------------------------- */
if (!offsets ||
(PyArray_NDIM(offsets)==2 && PyArray_DIM(offsets, 1)!=2) ||
(PyArray_NDIM(offsets)==1 && PyArray_DIM(offsets, 0)!=0))
{
PyErr_SetString(PyExc_ValueError, "Offsets array must be Nx2");
ok = 0;
goto exit;
}
const Py_ssize_t Noffsets = PyArray_DIM(offsets, 0);
/* ------------------- Check facecolors array ------------------------- */
if (!facecolors ||
(PyArray_NDIM(facecolors)==1 && PyArray_DIM(facecolors, 0)!=0) ||
(PyArray_NDIM(facecolors)==2 && PyArray_DIM(facecolors, 1)!=4))
{
PyErr_SetString(PyExc_ValueError, "Facecolors must by a Nx4 numpy array or empty");
ok = 0;
goto exit;
}
/* ------------------- Check edgecolors array ------------------------- */
if (!edgecolors ||
(PyArray_NDIM(edgecolors)==1 && PyArray_DIM(edgecolors, 0)!=0) ||
(PyArray_NDIM(edgecolors)==2 && PyArray_DIM(edgecolors, 1)!=4))
{
PyErr_SetString(PyExc_ValueError, "Edgecolors must by a Nx4 numpy array or empty");
ok = 0;
goto exit;
}
/* -------------------------------------------------------------------- */
if (Npaths==0) goto exit; /* Nothing to do */
/* -------------------------------------------------------------------- */
Np = Npaths > Ntransforms ? Npaths : Ntransforms;
N = Np > Noffsets ? Np : Noffsets;
p = malloc(Np*sizeof(CGMutablePathRef));
if (!p)
{
ok = 0;
goto exit;
}
for (i = 0; i < Np; i++)
{
PyObject* path;
PyObject* transform;
p[i] = NULL;
path = PySequence_ITEM(paths, i % Npaths);
if (!path)
{
ok = 0;
goto exit;
}
transform = PySequence_ITEM(transforms, i % Ntransforms);
if (!transform)
{
PyErr_SetString(PyExc_RuntimeError, "failed to obtain transform");
Py_DECREF(path);
ok = 0;
goto exit;
}
iterator = get_path_iterator(path,
transform,
1,
0,
rect,
mode,
1.0,
/* Hardcoding stroke width to 1.0
here, but for true
correctness, the paths would
need to be set up for each
different linewidth that may
be applied below. This
difference is very minute in
practice, so this hardcoding
is probably ok for now. --
MGD */
0);
Py_DECREF(transform);
Py_DECREF(path);
if (!iterator)
{
PyErr_SetString(PyExc_RuntimeError,
"failed to obtain path iterator");
ok = 0;
goto exit;
}
p[i] = _create_path(iterator);
free_path_iterator(iterator);
if (!p[i])
{
PyErr_SetString(PyExc_RuntimeError, "failed to create path");
ok = 0;
goto exit;
}
}
/* ------------------- Set clipping path ------------------------------ */
if (!_clip(cr, cliprect))
{
ok = 0;
goto exit;
}
if (clippath!=Py_None)
{
int n;
iterator = get_path_iterator(clippath,
clippath_transform,
0,
0,
rect,
SNAP_AUTO,
1.0,
0);
if (!iterator)
{
PyErr_SetString(PyExc_RuntimeError,
"draw_path_collection: failed to obtain path iterator for clipping");
ok = 0;
goto exit;
}
n = _draw_path(cr, iterator);
free_path_iterator(iterator);
if (n > 0) CGContextClip(cr);
}
/* ------------------- Check the other arguments ---------------------- */
if (!PySequence_Check(linewidths))
{
PyErr_SetString(PyExc_ValueError, "linewidths must be a sequence object");
ok = 0;
goto exit;
}
if (!PySequence_Check(linestyles))
{
PyErr_SetString(PyExc_ValueError, "linestyles must be a sequence object");
ok = 0;
goto exit;
}
if (!PySequence_Check(antialiaseds))
{
PyErr_SetString(PyExc_ValueError, "antialiaseds must be a sequence object");
ok = 0;
goto exit;
}
Py_ssize_t Nfacecolors = PyArray_DIM(facecolors, 0);
Py_ssize_t Nedgecolors = PyArray_DIM(edgecolors, 0);
Py_ssize_t Nlinewidths = PySequence_Size(linewidths);
Py_ssize_t Nlinestyles = PySequence_Size(linestyles);
Py_ssize_t Naa = PySequence_Size(antialiaseds);
if (N < Nlinestyles) Nlinestyles = N;
if ((Nfacecolors == 0 && Nedgecolors == 0) || Np == 0) goto exit;
/* Preset graphics context properties if possible */
if (Naa==1)
{
PyObject* antialiased = PySequence_ITEM(antialiaseds, 0);
if (antialiased)
{
ok = _set_antialiased(cr, antialiased);
Py_DECREF(antialiased);
}
else
{
PyErr_SetString(PyExc_SystemError,
"Failed to read element from antialiaseds array");
ok = 0;
}
if (!ok) goto exit;
}
if (Nlinewidths==1)
{
PyObject* linewidth = PySequence_ITEM(linewidths, 0);
if (!linewidth)
{
PyErr_SetString(PyExc_SystemError,
"Failed to read element from linewidths array");
ok = 0;
goto exit;
}
CGContextSetLineWidth(cr, (CGFloat)PyFloat_AsDouble(linewidth));
Py_DECREF(linewidth);
}
else if (Nlinewidths==0)
CGContextSetLineWidth(cr, 0.0);
if (Nlinestyles==1)
{
PyObject* linestyle = PySequence_ITEM(linestyles, 0);
if (!linestyle)
{
PyErr_SetString(PyExc_SystemError,
"Failed to read element from linestyles array");
ok = 0;
goto exit;
}
ok = _set_dashes(cr, linestyle);
Py_DECREF(linestyle);
if (!ok) goto exit;
}
if (Nedgecolors==1)
{
const double r = *(double*)PyArray_GETPTR2(edgecolors, 0, 0);
const double g = *(double*)PyArray_GETPTR2(edgecolors, 0, 1);
const double b = *(double*)PyArray_GETPTR2(edgecolors, 0, 2);
const double a = *(double*)PyArray_GETPTR2(edgecolors, 0, 3);
CGContextSetRGBStrokeColor(cr, r, g, b, a);
}
if (Nfacecolors==1)
{
const double r = *(double*)PyArray_GETPTR2(facecolors, 0, 0);
const double g = *(double*)PyArray_GETPTR2(facecolors, 0, 1);
const double b = *(double*)PyArray_GETPTR2(facecolors, 0, 2);
const double a = *(double*)PyArray_GETPTR2(facecolors, 0, 3);
CGContextSetRGBFillColor(cr, r, g, b, a);
}
for (i = 0; i < N; i++)
{
if (CGPathIsEmpty(p[i % Np])) continue;
CGContextSaveGState(cr);
if (Noffsets)
{
ok = _set_offset(cr, offsets, i % Noffsets, offset_transform);
if (!ok)
{
CGContextRestoreGState(cr);
goto exit;
}
}
if (Naa > 1)
{
PyObject* antialiased = PySequence_ITEM(antialiaseds, i % Naa);
if (antialiased)
{
ok = _set_antialiased(cr, antialiased);
Py_DECREF(antialiased);
}
else
{
PyErr_SetString(PyExc_SystemError,
"Failed to read element from antialiaseds array");
ok = 0;
}
if (!ok) goto exit;
}
if (Nlinewidths > 1)
{
PyObject* linewidth = PySequence_ITEM(linewidths, i % Nlinewidths);
if (!linewidth)
{
PyErr_SetString(PyExc_SystemError,
"Failed to read element from linewidths array");
ok = 0;
goto exit;
}
CGContextSetLineWidth(cr, (CGFloat)PyFloat_AsDouble(linewidth));
Py_DECREF(linewidth);
}
if (Nlinestyles > 1)
{
PyObject* linestyle = PySequence_ITEM(linestyles, i % Nlinestyles);
if (!linestyle)
{
PyErr_SetString(PyExc_SystemError,
"Failed to read element from linestyles array");
ok = 0;
goto exit;
}
ok = _set_dashes(cr, linestyle);
Py_DECREF(linestyle);
if (!ok) goto exit;
}
if (Nedgecolors > 1)
{
npy_intp fi = i % Nedgecolors;
const double r = *(double*)PyArray_GETPTR2(edgecolors, fi, 0);
const double g = *(double*)PyArray_GETPTR2(edgecolors, fi, 1);
const double b = *(double*)PyArray_GETPTR2(edgecolors, fi, 2);
const double a = *(double*)PyArray_GETPTR2(edgecolors, fi, 3);
CGContextSetRGBStrokeColor(cr, r, g, b, a);
}
CGContextAddPath(cr, p[i % Np]);
if (Nfacecolors > 1)
{
npy_intp fi = i % Nfacecolors;
const double r = *(double*)PyArray_GETPTR2(facecolors, fi, 0);
const double g = *(double*)PyArray_GETPTR2(facecolors, fi, 1);
const double b = *(double*)PyArray_GETPTR2(facecolors, fi, 2);
const double a = *(double*)PyArray_GETPTR2(facecolors, fi, 3);
CGContextSetRGBFillColor(cr, r, g, b, a);
if (Nedgecolors > 0) CGContextDrawPath(cr, kCGPathFillStroke);
else CGContextFillPath(cr);
}
else if (Nfacecolors==1)
{
if (Nedgecolors > 0) CGContextDrawPath(cr, kCGPathFillStroke);
else CGContextFillPath(cr);
}
else /* We checked Nedgecolors != 0 above */
CGContextStrokePath(cr);
CGContextRestoreGState(cr);
}
exit:
CGContextRestoreGState(cr);
Py_XDECREF(offsets);
Py_XDECREF(facecolors);
Py_XDECREF(edgecolors);
if (p)
{
for (i = 0; i < Np; i++)
{
if (!p[i]) break;
CGPathRelease(p[i]);
}
free(p);
}
if (!ok) return NULL;
Py_INCREF(Py_None);
return Py_None;
}
static PyObject*
GraphicsContext_draw_quad_mesh (GraphicsContext* self, PyObject* args)
{
PyObject* master_transform;
PyObject* cliprect;
PyObject* clippath;
PyObject* clippath_transform;
int meshWidth;
int meshHeight;
PyObject* coordinates;
PyObject* offsets;
PyObject* offset_transform;
PyObject* facecolors;
int antialiased;
int showedges;
CGContextRef cr = self->cr;
if (!cr)
{
PyErr_SetString(PyExc_RuntimeError, "CGContextRef is NULL");
return NULL;
}
if(!PyArg_ParseTuple(args, "OOOOiiOOOOii",
&master_transform,
&cliprect,
&clippath,
&clippath_transform,
&meshWidth,
&meshHeight,
&coordinates,
&offsets,
&offset_transform,
&facecolors,
&antialiased,
&showedges)) return NULL;
int ok = 1;
CGContextSaveGState(cr);
CGAffineTransform master;
double rect[4] = {0.0, 0.0, self->size.width, self->size.height};
/* ------------------- Set master transform --------------------------- */
PyObject* values = PyObject_CallMethod(master_transform, "to_values", "");
if (!values)
{
ok = 0;
goto exit;
}
if (PyTuple_Check(values))
{
double a, b, c, d, tx, ty;
/* CGAffineTransform contains CGFloat; cannot use master directly */
ok = PyArg_ParseTuple(values, "dddddd", &a, &b, &c, &d, &tx, &ty);
master.a = a;
master.b = b;
master.c = c;
master.d = d;
master.tx = tx;
master.ty = ty;
}
else
{
ok = 0;
}
Py_DECREF(values);
if (!ok) goto exit;
CGContextConcatCTM(cr, master);
/* ------------------- Set clipping path ------------------------------ */
ok = _clip(cr, cliprect);
if (!ok) goto exit;
if (clippath!=Py_None)
{
int n;
void* iterator = get_path_iterator(clippath,
clippath_transform,
0,
0,
rect,
SNAP_AUTO,
1.0,
0);
if (iterator)
{
PyErr_SetString(PyExc_RuntimeError,
"draw_quad_mesh: failed to obtain path iterator");
ok = 0;
goto exit;
}
n = _draw_path(cr, iterator);
free_path_iterator(iterator);
if (n > 0) CGContextClip(cr);
}
/* ------------------- Check coordinates array ------------------------ */
coordinates = PyArray_FromObject(coordinates, NPY_DOUBLE, 3, 3);
if (!coordinates ||
PyArray_NDIM(coordinates) != 3 || PyArray_DIM(coordinates, 2) != 2)
{
PyErr_SetString(PyExc_ValueError, "Invalid coordinates array");
ok = 0;
goto exit;
}
/* ------------------- Check offsets array ---------------------------- */
offsets = PyArray_FromObject(offsets, NPY_DOUBLE, 0, 2);
if (!offsets ||
(PyArray_NDIM(offsets)==2 && PyArray_DIM(offsets, 1)!=2) ||
(PyArray_NDIM(offsets)==1 && PyArray_DIM(offsets, 0)!=0))
{
PyErr_SetString(PyExc_ValueError, "Offsets array must be Nx2");
ok = 0;
goto exit;
}
const Py_ssize_t Noffsets = PyArray_DIM(offsets, 0);
/* ------------------- Check facecolors array ------------------------- */
facecolors = PyArray_FromObject(facecolors, NPY_DOUBLE, 1, 2);
if (!facecolors ||
(PyArray_NDIM(facecolors)==1 && PyArray_DIM(facecolors, 0)!=0) ||
(PyArray_NDIM(facecolors)==2 && PyArray_DIM(facecolors, 1)!=4))
{
PyErr_SetString(PyExc_ValueError, "Facecolors must by a Nx4 numpy array or empty");
ok = 0;
goto exit;
}
/* ------------------- Check the other arguments ---------------------- */
size_t Npaths = meshWidth * meshHeight;
size_t Nfacecolors = (size_t) PyArray_DIM(facecolors, 0);
if ((Nfacecolors == 0 && !showedges) || Npaths == 0)
{
/* Nothing to do here */
goto exit;
}
size_t i = 0;
size_t iw = 0;
size_t ih = 0;
/* Preset graphics context properties if possible */
CGContextSetShouldAntialias(cr, antialiased);
CGContextSetLineWidth(cr, 0.0);
if (Nfacecolors==1)
{
const double r = *(double*)PyArray_GETPTR2(facecolors, 0, 0);
const double g = *(double*)PyArray_GETPTR2(facecolors, 0, 1);
const double b = *(double*)PyArray_GETPTR2(facecolors, 0, 2);
const double a = *(double*)PyArray_GETPTR2(facecolors, 0, 3);
CGContextSetRGBFillColor(cr, r, g, b, a);
if (antialiased && !showedges)
{
CGContextSetRGBStrokeColor(cr, r, g, b, a);
}
}
if (showedges)
{
CGContextSetRGBStrokeColor(cr, 0, 0, 0, 1);
}
double x, y;
for (ih = 0; ih < meshHeight; ih++)
{
for (iw = 0; iw < meshWidth; iw++, i++)
{
CGContextSaveGState(cr);
if (Noffsets)
{
ok = _set_offset(cr, offsets, i % Noffsets, offset_transform);
if (!ok)
{
CGContextRestoreGState(cr);
goto exit;
}
}
CGPoint points[4];
x = *(double*)PyArray_GETPTR3(coordinates, ih, iw, 0);
y = *(double*)PyArray_GETPTR3(coordinates, ih, iw, 1);
if (isnan(x) || isnan(y)) continue;
points[0].x = (CGFloat)x;
points[0].y = (CGFloat)y;
x = *(double*)PyArray_GETPTR3(coordinates, ih, iw+1, 0);
y = *(double*)PyArray_GETPTR3(coordinates, ih, iw+1, 1);
if (isnan(x) || isnan(y)) continue;
points[1].x = (CGFloat)x;
points[1].y = (CGFloat)y;
x = *(double*)PyArray_GETPTR3(coordinates, ih+1, iw+1, 0);
y = *(double*)PyArray_GETPTR3(coordinates, ih+1, iw+1, 1);
if (isnan(x) || isnan(y)) continue;
points[2].x = (CGFloat)x;
points[2].y = (CGFloat)y;
x = *(double*)PyArray_GETPTR3(coordinates, ih+1, iw, 0);
y = *(double*)PyArray_GETPTR3(coordinates, ih+1, iw, 1);
if (isnan(x) || isnan(y)) continue;
points[3].x = (CGFloat)x;
points[3].y = (CGFloat)y;
CGContextMoveToPoint(cr, points[3].x, points[3].y);
CGContextAddLines(cr, points, 4);
if (Nfacecolors > 1)
{
npy_intp fi = i % Nfacecolors;
const double r = *(double*)PyArray_GETPTR2(facecolors, fi, 0);
const double g = *(double*)PyArray_GETPTR2(facecolors, fi, 1);
const double b = *(double*)PyArray_GETPTR2(facecolors, fi, 2);
const double a = *(double*)PyArray_GETPTR2(facecolors, fi, 3);
CGContextSetRGBFillColor(cr, r, g, b, a);
if (showedges)
{
CGContextDrawPath(cr, kCGPathFillStroke);
}
else if (antialiased)
{
CGContextSetRGBStrokeColor(cr, r, g, b, a);
CGContextDrawPath(cr, kCGPathFillStroke);
}
else
{
CGContextFillPath(cr);
}
}
else if (Nfacecolors==1)
{
if (showedges || antialiased)
{
CGContextDrawPath(cr, kCGPathFillStroke);
}
else
{
CGContextFillPath(cr);
}
}
else if (showedges)
{
CGContextStrokePath(cr);
}
CGContextRestoreGState(cr);
}
}
exit:
CGContextRestoreGState(cr);
Py_XDECREF(offsets);
Py_XDECREF(facecolors);
Py_XDECREF(coordinates);
if (!ok) return NULL;
Py_INCREF(Py_None);
return Py_None;
}
#ifdef COMPILING_FOR_10_5
static CTFontRef
#else
static ATSFontRef
#endif
setfont(CGContextRef cr, PyObject* family, float size, const char weight[],
const char italic[])
{
#define NMAP 40
#define NFONT 31
int i, j, n;
const char* temp;
const char* name = "Times-Roman";
CFStringRef string;
#ifdef COMPILING_FOR_10_5
CTFontRef font = 0;
#else
ATSFontRef font = 0;
#endif
const int k = (strcmp(italic, "italic") ? 0 : 2)
+ (strcmp(weight, "bold") ? 0 : 1);
struct {char* name; int index;} map[NMAP] = {
{"New Century Schoolbook", 0},
{"Century Schoolbook L", 0},
{"Utopia", 1},
{"ITC Bookman", 2},
{"Bookman", 2},
{"Bitstream Vera Serif", 3},
{"Nimbus Roman No9 L", 4},
{"Times New Roman", 5},
{"Times", 6},
{"Palatino", 7},
{"Charter", 8},
{"serif", 0},
{"Lucida Grande", 9},
{"Verdana", 10},
{"Geneva", 11},
{"Lucida", 12},
{"Bitstream Vera Sans", 13},
{"Arial", 14},
{"Helvetica", 15},
{"Avant Garde", 16},
{"sans-serif", 10},
{"Apple Chancery", 17},
{"Textile", 18},
{"Zapf Chancery", 19},
{"Sand", 20},
{"cursive", 17},
{"Comic Sans MS", 21},
{"Chicago", 22},
{"Charcoal", 23},
{"Impact", 24},
{"Western", 25},
{"fantasy", 21},
{"Andale Mono", 26},
{"Bitstream Vera Sans Mono", 27},
{"Nimbus Mono L", 28},
{"Courier", 29},
{"Courier New", 30},
{"Fixed", 30},
{"Terminal", 30},
{"monospace", 30},
};
const char* psnames[NFONT][4] = {
{"CenturySchoolbook", /* 0 */
"CenturySchoolbook-Bold",
"CenturySchoolbook-Italic",
"CenturySchoolbook-BoldItalic"},
{"Utopia", /* 1 */
"Utopia-Bold",
"Utopia-Italic",
"Utopia-BoldItalic"},
{"Bookman-Light", /* 2 */
"Bookman-Bold",
"Bookman-LightItalic",
"Bookman-BoldItalic"},
{"BitstreamVeraSerif-Roman", /* 3 */
"BitstreamVeraSerif-Bold",
"",
""},
{"NimbusRomNo9L-Reg", /* 4 */
"NimbusRomNo9T-Bol",
"NimbusRomNo9L-RegIta",
"NimbusRomNo9T-BolIta"},
{"TimesNewRomanPSMT", /* 5 */
"TimesNewRomanPS-BoldMT",
"TimesNewRomanPS-ItalicMT",
"TimesNewRomanPS-BoldItalicMT"},
{"Times-Roman", /* 6 */
"Times-Bold",
"Times-Italic",
"Times-BoldItalic"},
{"Palatino-Roman", /* 7 */
"Palatino-Bold",
"Palatino-Italic",
"Palatino-BoldItalic"},
{"CharterBT-Roman", /* 8 */
"CharterBT-Bold",
"CharterBT-Italic",
"CharterBT-BoldItalic"},
{"LucidaGrande", /* 9 */
"LucidaGrande-Bold",
"",
""},
{"Verdana", /* 10 */
"Verdana-Bold",
"Verdana-Italic",
"Verdana-BoldItalic"},
{"Geneva", /* 11 */
"",
"",
""},
{"LucidaSans", /* 12 */
"LucidaSans-Demi",
"LucidaSans-Italic",
"LucidaSans-DemiItalic"},
{"BitstreamVeraSans-Roman", /* 13 */
"BitstreamVeraSans-Bold",
"BitstreamVeraSans-Oblique",
"BitstreamVeraSans-BoldOblique"},
{"ArialMT", /* 14 */
"Arial-BoldMT",
"Arial-ItalicMT",
"Arial-BoldItalicMT"},
{"Helvetica", /* 15 */
"Helvetica-Bold",
"",
""},
{"AvantGardeITC-Book", /* 16 */
"AvantGardeITC-Demi",
"AvantGardeITC-BookOblique",
"AvantGardeITC-DemiOblique"},
{"Apple-Chancery", /* 17 */
"",
"",
""},
{"TextileRegular", /* 18 */
"",
"",
""},
{"ZapfChancery-Roman", /* 19 */
"ZapfChancery-Bold",
"ZapfChancery-Italic",
"ZapfChancery-MediumItalic"},
{"SandRegular", /* 20 */
"",
"",
""},
{"ComicSansMS", /* 21 */
"ComicSansMS-Bold",
"",
""},
{"Chicago", /* 22 */
"",
"",
""},
{"Charcoal", /* 23 */
"",
"",
""},
{"Impact", /* 24 */
"",
"",
""},
{"Playbill", /* 25 */
"",
"",
""},
{"AndaleMono", /* 26 */
"",
"",
""},
{"BitstreamVeraSansMono-Roman", /* 27 */
"BitstreamVeraSansMono-Bold",
"BitstreamVeraSansMono-Oblique",
"BitstreamVeraSansMono-BoldOb"},
{"NimbusMonL-Reg", /* 28 */
"NimbusMonL-Bol",
"NimbusMonL-RegObl",
"NimbusMonL-BolObl"},
{"Courier", /* 29 */
"Courier-Bold",
"",
""},
{"CourierNewPS", /* 30 */
"CourierNewPS-BoldMT",
"CourierNewPS-ItalicMT",
"CourierNewPS-Bold-ItalicMT"},
};
if(!PyList_Check(family)) return 0;
n = PyList_GET_SIZE(family);
for (i = 0; i < n; i++)
{
PyObject* item = PyList_GET_ITEM(family, i);
if(!PyString_Check(item)) return 0;
temp = PyString_AS_STRING(item);
for (j = 0; j < NMAP; j++)
{ if (!strcmp(map[j].name, temp))
{ temp = psnames[map[j].index][k];
break;
}
}
/* If the font name is not found in mapping, we assume */
/* that the user specified the Postscript name directly */
/* Check if this font can be found on the system */
string = CFStringCreateWithCString(kCFAllocatorDefault,
temp,
kCFStringEncodingMacRoman);
#ifdef COMPILING_FOR_10_5
font = CTFontCreateWithName(string, size, NULL);
#else
font = ATSFontFindFromPostScriptName(string, kATSOptionFlagsDefault);
#endif
CFRelease(string);
if(font)
{
name = temp;
break;
}
}
if(!font)
{ string = CFStringCreateWithCString(kCFAllocatorDefault,
name,
kCFStringEncodingMacRoman);
#ifdef COMPILING_FOR_10_5
font = CTFontCreateWithName(string, size, NULL);
#else
font = ATSFontFindFromPostScriptName(string, kATSOptionFlagsDefault);
#endif
CFRelease(string);
}
#ifndef COMPILING_FOR_10_5
CGContextSelectFont(cr, name, size, kCGEncodingMacRoman);
#endif
return font;
}
#ifdef COMPILING_FOR_10_5
static PyObject*
GraphicsContext_draw_text (GraphicsContext* self, PyObject* args)
{
float x;
float y;
const UniChar* text;
int n;
PyObject* family;
float size;
const char* weight;
const char* italic;
float angle;
CTFontRef font;
CGColorRef color;
CGFloat descent;
CFStringRef keys[2];
CFTypeRef values[2];
CGContextRef cr = self->cr;
if (!cr)
{
PyErr_SetString(PyExc_RuntimeError, "CGContextRef is NULL");
return NULL;
}
if(!PyArg_ParseTuple(args, "ffu#Ofssf",
&x,
&y,
&text,
&n,
&family,
&size,
&weight,
&italic,
&angle)) return NULL;
font = setfont(cr, family, size, weight, italic);
color = CGColorCreateGenericRGB(self->color[0],
self->color[1],
self->color[2],
self->color[3]);
keys[0] = kCTFontAttributeName;
keys[1] = kCTForegroundColorAttributeName;
values[0] = font;
values[1] = color;
CFDictionaryRef attributes = CFDictionaryCreate(kCFAllocatorDefault,
(const void**)&keys,
(const void**)&values,
2,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
CGColorRelease(color);
CFRelease(font);
CFStringRef s = CFStringCreateWithCharacters(kCFAllocatorDefault, text, n);
CFAttributedStringRef string = CFAttributedStringCreate(kCFAllocatorDefault,
s,
attributes);
CFRelease(s);
CFRelease(attributes);
CTLineRef line = CTLineCreateWithAttributedString(string);
CFRelease(string);
CTLineGetTypographicBounds(line, NULL, &descent, NULL);
if (!line)
{
PyErr_SetString(PyExc_RuntimeError,
"CTLineCreateWithAttributedString failed");
return NULL;
}
CGContextSetTextMatrix(cr, CGAffineTransformIdentity);
if (angle)
{
CGContextSaveGState(cr);
CGContextTranslateCTM(cr, x, y);
CGContextRotateCTM(cr, angle*M_PI/180);
CTLineDraw(line, cr);
CGContextRestoreGState(cr);
}
else
{
CGContextSetTextPosition(cr, x, y);
CTLineDraw(line, cr);
}
CFRelease(line);
Py_INCREF(Py_None);
return Py_None;
}
static PyObject*
GraphicsContext_get_text_width_height_descent(GraphicsContext* self, PyObject* args)
{
const UniChar* text;
int n;
PyObject* family;
float size;
const char* weight;
const char* italic;
CGFloat ascent;
CGFloat descent;
double width;
CGRect rect;
CTFontRef font;
CGContextRef cr = self->cr;
if (!cr)
{
PyErr_SetString(PyExc_RuntimeError, "CGContextRef is NULL");
return NULL;
}
if(!PyArg_ParseTuple(args, "u#Ofss",
&text, &n, &family, &size, &weight, &italic))
return NULL;
font = setfont(cr, family, size, weight, italic);
CFStringRef keys[1];
CFTypeRef values[1];
keys[0] = kCTFontAttributeName;
values[0] = font;
CFDictionaryRef attributes = CFDictionaryCreate(kCFAllocatorDefault,
(const void**)&keys,
(const void**)&values,
1,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
CFRelease(font);
CFStringRef s = CFStringCreateWithCharacters(kCFAllocatorDefault, text, n);
CFAttributedStringRef string = CFAttributedStringCreate(kCFAllocatorDefault,
s,
attributes);
CFRelease(s);
CFRelease(attributes);
CTLineRef line = CTLineCreateWithAttributedString(string);
CFRelease(string);
if (!line)
{
PyErr_SetString(PyExc_RuntimeError,
"CTLineCreateWithAttributedString failed");
return NULL;
}
width = CTLineGetTypographicBounds(line, &ascent, &descent, NULL);
rect = CTLineGetImageBounds(line, cr);
CFRelease(line);
return Py_BuildValue("fff", width, rect.size.height, descent);
}
#else
static PyObject*
GraphicsContext_draw_text (GraphicsContext* self, PyObject* args)
{
float x;
float y;
const UniChar* text;
int n;
PyObject* family;
float size;
const char* weight;
const char* italic;
float angle;
ATSFontRef atsfont;
CGContextRef cr = self->cr;
if (!cr)
{
PyErr_SetString(PyExc_RuntimeError, "CGContextRef is NULL");
return NULL;
}
if(!PyArg_ParseTuple(args, "ffu#Ofssf",
&x,
&y,
&text,
&n,
&family,
&size,
&weight,
&italic,
&angle)) return NULL;
atsfont = setfont(cr, family, size, weight, italic);
OSStatus status;
ATSUAttributeTag tags[] = {kATSUFontTag, kATSUSizeTag, kATSUQDBoldfaceTag};
ByteCount sizes[] = {sizeof(ATSUFontID), sizeof(Fixed), sizeof(Boolean)};
Fixed atsuSize = Long2Fix(size);
Boolean isBold = FALSE; /* setfont takes care of this */
ATSUAttributeValuePtr values[] = {&atsfont, &atsuSize, &isBold};
status = ATSUSetAttributes(style, 3, tags, sizes, values);
if (status!=noErr)
{
PyErr_SetString(PyExc_RuntimeError, "ATSUSetAttributes failed");
return NULL;
}
status = ATSUSetTextPointerLocation(layout,
text,
kATSUFromTextBeginning, /* offset from beginning */
kATSUToTextEnd, /* length of text range */
n); /* length of text buffer */
if (status!=noErr)
{
PyErr_SetString(PyExc_RuntimeError,
"ATSUCreateTextLayoutWithTextPtr failed");
return NULL;
}
status = ATSUSetRunStyle(layout,
style,
kATSUFromTextBeginning,
kATSUToTextEnd);
if (status!=noErr)
{
PyErr_SetString(PyExc_RuntimeError, "ATSUSetRunStyle failed");
return NULL;
}
Fixed atsuAngle = X2Fix(angle);
ATSUAttributeTag tags2[] = {kATSUCGContextTag, kATSULineRotationTag};
ByteCount sizes2[] = {sizeof (CGContextRef), sizeof(Fixed)};
ATSUAttributeValuePtr values2[] = {&cr, &atsuAngle};
status = ATSUSetLayoutControls(layout, 2, tags2, sizes2, values2);
if (status!=noErr)
{
PyErr_SetString(PyExc_RuntimeError, "ATSUSetLayoutControls failed");
return NULL;
}
status = ATSUDrawText(layout,
kATSUFromTextBeginning,
kATSUToTextEnd,
X2Fix(x),
X2Fix(y));
if (status!=noErr)
{
PyErr_SetString(PyExc_RuntimeError, "ATSUDrawText failed");
return NULL;
}
Py_INCREF(Py_None);
return Py_None;
}
static PyObject*
GraphicsContext_get_text_width_height_descent(GraphicsContext* self, PyObject* args)
{
const UniChar* text;
int n;
PyObject* family;
float size;
const char* weight;
const char* italic;
ATSFontRef atsfont;
CGContextRef cr = self->cr;
if (!cr)
{
PyErr_SetString(PyExc_RuntimeError, "CGContextRef is NULL");
return NULL;
}
if(!PyArg_ParseTuple(args, "u#Ofss", &text, &n, &family, &size, &weight, &italic)) return NULL;
atsfont = setfont(cr, family, size, weight, italic);
OSStatus status = noErr;
ATSUAttributeTag tags[] = {kATSUFontTag,
kATSUSizeTag,
kATSUQDBoldfaceTag,
kATSUQDItalicTag};
ByteCount sizes[] = {sizeof(ATSUFontID),
sizeof(Fixed),
sizeof(Boolean),
sizeof(Boolean)};
Fixed atsuSize = Long2Fix(size);
Boolean isBold = FALSE; /* setfont takes care of this */
Boolean isItalic = FALSE; /* setfont takes care of this */
ATSUAttributeValuePtr values[] = {&atsfont, &atsuSize, &isBold, &isItalic};
status = ATSUSetAttributes(style, 4, tags, sizes, values);
if (status!=noErr)
{
PyErr_SetString(PyExc_RuntimeError, "ATSUSetAttributes failed");
return NULL;
}
status = ATSUSetTextPointerLocation(layout,
text,
kATSUFromTextBeginning, /* offset from beginning */
kATSUToTextEnd, /* length of text range */
n); /* length of text buffer */
if (status!=noErr)
{
PyErr_SetString(PyExc_RuntimeError,
"ATSUCreateTextLayoutWithTextPtr failed");
return NULL;
}
status = ATSUSetRunStyle(layout,
style,
kATSUFromTextBeginning,
kATSUToTextEnd);
if (status!=noErr)
{
PyErr_SetString(PyExc_RuntimeError, "ATSUSetRunStyle failed");
return NULL;
}
ATSUAttributeTag tag = kATSUCGContextTag;
ByteCount bc = sizeof (CGContextRef);
ATSUAttributeValuePtr value = &cr;
status = ATSUSetLayoutControls(layout, 1, &tag, &bc, &value);
if (status!=noErr)
{
PyErr_SetString(PyExc_RuntimeError, "ATSUSetLayoutControls failed");
return NULL;
}
ATSUTextMeasurement before;
ATSUTextMeasurement after;
ATSUTextMeasurement ascent;
ATSUTextMeasurement descent;
status = ATSUGetUnjustifiedBounds(layout,
kATSUFromTextBeginning, kATSUToTextEnd,
&before, &after, &ascent, &descent);
if (status!=noErr)
{
PyErr_SetString(PyExc_RuntimeError, "ATSUGetUnjustifiedBounds failed");
return NULL;
}
const float width = FixedToFloat(after-before);
const float height = FixedToFloat(ascent-descent);
return Py_BuildValue("fff", width, height, FixedToFloat(descent));
}
#endif
static void _data_provider_release(void* info, const void* data, size_t size)
{
PyObject* image = (PyObject*)info;
Py_DECREF(image);
}
static PyObject*
GraphicsContext_draw_mathtext(GraphicsContext* self, PyObject* args)
{
float x, y, angle;
npy_intp nrows, ncols;
int n;
PyObject* object;
PyArrayObject* image;
CGContextRef cr = self->cr;
if (!cr)
{
PyErr_SetString(PyExc_RuntimeError, "CGContextRef is NULL");
return NULL;
}
if(!PyArg_ParseTuple(args, "fffO", &x, &y, &angle, &object)) return NULL;
/* ------------- Check the image ---------------------------- */
if(!PyArray_Check (object))
{
PyErr_SetString(PyExc_TypeError, "image should be a NumPy array.");
return NULL;
}
image = (PyArrayObject*) object;
if(PyArray_NDIM(image) != 2)
{
PyErr_Format(PyExc_TypeError,
"image has incorrect rank (%d expected 2)",
PyArray_NDIM(image));
return NULL;
}
if (PyArray_TYPE(image) != NPY_UBYTE)
{
PyErr_SetString(PyExc_TypeError,
"image has incorrect type (should be uint8)");
return NULL;
}
if (!PyArray_ISCONTIGUOUS(image))
{
PyErr_SetString(PyExc_TypeError, "image array is not contiguous");
return NULL;
}
nrows = PyArray_DIM(image, 0);
ncols = PyArray_DIM(image, 1);
if (nrows != (int) nrows || ncols != (int) ncols)
{
PyErr_SetString(PyExc_RuntimeError, "bitmap image too large");
return NULL;
}
n = nrows * ncols;
Py_INCREF(object);
const size_t bytesPerComponent = 1;
const size_t bitsPerComponent = 8 * bytesPerComponent;
const size_t nComponents = 1; /* gray */
const size_t bitsPerPixel = bitsPerComponent * nComponents;
const size_t bytesPerRow = nComponents * bytesPerComponent * ncols;
CGDataProviderRef provider = CGDataProviderCreateWithData(object,
PyArray_DATA(image),
n,
_data_provider_release);
CGImageRef bitmap = CGImageMaskCreate ((int) ncols,
(int) nrows,
bitsPerComponent,
bitsPerPixel,
bytesPerRow,
provider,
NULL,
false);
CGDataProviderRelease(provider);
if(!bitmap)
{
PyErr_SetString(PyExc_RuntimeError, "CGImageMaskCreate failed");
return NULL;
}
if (angle==0.0)
{
CGContextDrawImage(cr, CGRectMake(x,y,ncols,nrows), bitmap);
}
else
{
CGContextSaveGState(cr);
CGContextTranslateCTM(cr, x, y);
CGContextRotateCTM(cr, angle*M_PI/180);
CGContextDrawImage(cr, CGRectMake(0,0,ncols,nrows), bitmap);
CGContextRestoreGState(cr);
}
CGImageRelease(bitmap);
Py_INCREF(Py_None);
return Py_None;
}
static PyObject*
GraphicsContext_draw_image(GraphicsContext* self, PyObject* args)
{
float x, y;
int nrows, ncols;
const char* data;
int n;
PyObject* image;
PyObject* cliprect;
PyObject* clippath;
PyObject* clippath_transform;
CGContextRef cr = self->cr;
if (!cr)
{
PyErr_SetString(PyExc_RuntimeError, "CGContextRef is NULL");
return NULL;
}
if(!PyArg_ParseTuple(args, "ffiiOOOO", &x,
&y,
&nrows,
&ncols,
&image,
&cliprect,
&clippath,
&clippath_transform)) return NULL;
CGColorSpaceRef colorspace;
CGDataProviderRef provider;
double rect[4] = {0.0, 0.0, self->size.width, self->size.height};
if (!PyString_Check(image))
{
PyErr_SetString(PyExc_RuntimeError, "image is not a string");
return NULL;
}
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;
colorspace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
if (!colorspace)
{
PyErr_SetString(PyExc_RuntimeError, "failed to create a color space");
return NULL;
}
Py_INCREF(image);
n = PyString_GET_SIZE(image);
data = PyString_AsString(image);
provider = CGDataProviderCreateWithData(image,
data,
n,
_data_provider_release);
CGImageRef bitmap = CGImageCreate (ncols,
nrows,
bitsPerComponent,
bitsPerPixel,
bytesPerRow,
colorspace,
kCGImageAlphaLast,
provider,
NULL,
false,
kCGRenderingIntentDefault);
CGColorSpaceRelease(colorspace);
CGDataProviderRelease(provider);
if(!bitmap)
{
PyErr_SetString(PyExc_RuntimeError, "CGImageMaskCreate failed");
return NULL;
}
BOOL ok = true;
CGContextSaveGState(cr);
if (!_clip(cr, cliprect)) ok = false;
else if (clippath!=Py_None)
{
int n;
void* iterator = get_path_iterator(clippath,
clippath_transform,
0,
0,
rect,
SNAP_AUTO,
1.0,
0);
if (iterator)
{
n = _draw_path(cr, iterator);
free_path_iterator(iterator);
if (n > 0) CGContextClip(cr);
}
else
{
PyErr_SetString(PyExc_RuntimeError,
"draw_image: failed to obtain path iterator for clipping");
ok = false;
}
}
if (ok) CGContextDrawImage(cr, CGRectMake(x,y,ncols,nrows), bitmap);
CGImageRelease(bitmap);
CGContextRestoreGState(cr);
if (!ok) return NULL;
Py_INCREF(Py_None);
return Py_None;
}
static PyMethodDef GraphicsContext_methods[] = {
{"save",
(PyCFunction)GraphicsContext_save,
METH_NOARGS,
"Saves the current graphics context onto the stack."
},
{"restore",
(PyCFunction)GraphicsContext_restore,
METH_NOARGS,
"Restores the current graphics context from the stack."
},
{"get_text_width_height_descent",
(PyCFunction)GraphicsContext_get_text_width_height_descent,
METH_VARARGS,
"Returns the width, height, and descent of the text."
},
{"set_alpha",
(PyCFunction)GraphicsContext_set_alpha,
METH_VARARGS,
"Sets the opacitiy level for objects drawn in a graphics context"
},
{"set_antialiased",
(PyCFunction)GraphicsContext_set_antialiased,
METH_VARARGS,
"Sets anti-aliasing on or off for a graphics context."
},
{"set_capstyle",
(PyCFunction)GraphicsContext_set_capstyle,
METH_VARARGS,
"Sets the style for the endpoints of lines in a graphics context."
},
{"set_clip_rectangle",
(PyCFunction)GraphicsContext_set_clip_rectangle,
METH_VARARGS,
"Sets the clipping path to the area defined by the specified rectangle."
},
{"set_clip_path",
(PyCFunction)GraphicsContext_set_clip_path,
METH_VARARGS,
"Sets the clipping path."
},
{"set_dashes",
(PyCFunction)GraphicsContext_set_dashes,
METH_VARARGS,
"Sets the pattern for dashed lines in a graphics context."
},
{"set_foreground",
(PyCFunction)GraphicsContext_set_foreground,
METH_VARARGS,
"Sets the current stroke and fill color to a value in the DeviceRGB color space."
},
{"set_graylevel",
(PyCFunction)GraphicsContext_set_graylevel,
METH_VARARGS,
"Sets the current stroke and fill color to a value in the DeviceGray color space."
},
{"set_linewidth",
(PyCFunction)GraphicsContext_set_linewidth,
METH_VARARGS,
"Sets the line width for a graphics context."
},
{"set_joinstyle",
(PyCFunction)GraphicsContext_set_joinstyle,
METH_VARARGS,
"Sets the style for the joins of connected lines in a graphics context."
},
{"draw_path",
(PyCFunction)GraphicsContext_draw_path,
METH_VARARGS,
"Draw a path in the graphics context and strokes and (if rgbFace is not None) fills it."
},
{"draw_markers",
(PyCFunction)GraphicsContext_draw_markers,
METH_VARARGS,
"Draws a marker in the graphics context at each of the vertices in path."
},
{"draw_path_collection",
(PyCFunction)GraphicsContext_draw_path_collection,
METH_VARARGS,
"Draw a collection of paths in the graphics context."
},
{"draw_quad_mesh",
(PyCFunction)GraphicsContext_draw_quad_mesh,
METH_VARARGS,
"Draws a mesh in the graphics context."
},
{"draw_text",
(PyCFunction)GraphicsContext_draw_text,
METH_VARARGS,
"Draw a string at (x,y) with the given properties in the graphics context."
},
{"draw_mathtext",
(PyCFunction)GraphicsContext_draw_mathtext,
METH_VARARGS,
"Draw a TeX string at (x,y) as a bitmap in the graphics context."
},
{"draw_image",
(PyCFunction)GraphicsContext_draw_image,
METH_VARARGS,
"Draw an image at (x,y) in the graphics context."
},
{NULL} /* Sentinel */
};
static char GraphicsContext_doc[] =
"A GraphicsContext object wraps a Quartz 2D graphics context\n"
"(CGContextRef). Most methods either draw into the graphics context\n"
"(moveto, lineto, etc.) or set the drawing properties (set_linewidth,\n"
"set_joinstyle, etc.).\n";
static PyTypeObject GraphicsContextType = {
PyObject_HEAD_INIT(NULL)
0, /*ob_size*/
"_macosx.GraphicsContext", /*tp_name*/
sizeof(GraphicsContext), /*tp_basicsize*/
0, /*tp_itemsize*/
#ifdef COMPILING_FOR_10_5
0, /*tp_dealloc*/
#else
(destructor)GraphicsContext_dealloc, /*tp_dealloc*/
#endif
0, /*tp_print*/
0, /*tp_getattr*/
0, /*tp_setattr*/
0, /*tp_compare*/
(reprfunc)GraphicsContext_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*/
GraphicsContext_doc, /* tp_doc */
0, /* tp_traverse */
0, /* tp_clear */
0, /* tp_richcompare */
0, /* tp_weaklistoffset */
0, /* tp_iter */
0, /* tp_iternext */
GraphicsContext_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 */
GraphicsContext_new, /* tp_new */
};
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];
}
self->ob_type->tp_free((PyObject*)self);
}
static PyObject*
FigureCanvas_repr(FigureCanvas* self)
{
return PyString_FromFormat("FigureCanvas object %p wrapping NSView %p",
(void*)self, (void*)(self->view));
}
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_INCREF(Py_None);
return Py_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_INCREF(Py_None);
return Py_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;
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_INCREF(Py_None);
return Py_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_INCREF(Py_None);
return Py_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_write_bitmap(FigureCanvas* self, PyObject* args)
{
View* view = self->view;
int n;
const unichar* characters;
NSSize size;
double width, height;
if(!view)
{
PyErr_SetString(PyExc_RuntimeError, "NSView* is NULL");
return NULL;
}
/* NSSize contains CGFloat; cannot use size directly */
if(!PyArg_ParseTuple(args, "u#dd",
&characters, &n, &width, &height)) return NULL;
size.width = width;
size.height = height;
/* This function may be called from inside the event loop, when an
* autorelease pool is available, or from Python, when no autorelease
* pool is available. To be able to handle the latter case, we need to
* create an autorelease pool here. */
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
NSRect rect = [view bounds];
NSString* filename = [NSString stringWithCharacters: characters
length: (unsigned)n];
NSString* extension = [filename pathExtension];
/* Calling dataWithPDFInsideRect on the view causes its update status
* to be cleared. Save the status here, and invalidate the view if not
* up to date after calling dataWithPDFInsideRect. */
const BOOL invalid = [view needsDisplay];
NSData* data = [view dataWithPDFInsideRect: rect];
if (invalid) [view setNeedsDisplay: YES];
NSImage* image = [[NSImage alloc] initWithData: data];
[image setScalesWhenResized: YES];
[image setSize: size];
data = [image TIFFRepresentation];
[image release];
if (! [extension isEqualToString: @"tiff"] &&
! [extension isEqualToString: @"tif"])
{
NSBitmapImageFileType filetype;
NSBitmapImageRep* bitmapRep = [NSBitmapImageRep imageRepWithData: data];
if ([extension isEqualToString: @"bmp"])
filetype = NSBMPFileType;
else if ([extension isEqualToString: @"gif"])
filetype = NSGIFFileType;
else if ([extension isEqualToString: @"jpg"] ||
[extension isEqualToString: @"jpeg"])
filetype = NSJPEGFileType;
else if ([extension isEqualToString: @"png"])
filetype = NSPNGFileType;
else
{ PyErr_SetString(PyExc_ValueError, "Unknown file type");
return NULL;
}
data = [bitmapRep representationUsingType:filetype properties:nil];
}
[data writeToFile: filename atomically: YES];
[pool release];
Py_INCREF(Py_None);
return Py_None;
}
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,
_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_INCREF(Py_None);
return Py_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_INCREF(Py_None);
return Py_None;
}
static PyMethodDef FigureCanvas_methods[] = {
{"draw",
(PyCFunction)FigureCanvas_draw,
METH_NOARGS,
"Draws the canvas."
},
{"invalidate",
(PyCFunction)FigureCanvas_invalidate,
METH_NOARGS,
"Invalidates the canvas."
},
{"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."
},
{"write_bitmap",
(PyCFunction)FigureCanvas_write_bitmap,
METH_VARARGS,
"Saves the figure to the specified file as a bitmap\n"
"(bmp, gif, jpeg, or png).\n"
},
{"start_event_loop",
(PyCFunction)FigureCanvas_start_event_loop,
METH_KEYWORDS,
"Runs the event loop until the timeout or until stop_event_loop is called.\n",
},
{"stop_event_loop",
(PyCFunction)FigureCanvas_stop_event_loop,
METH_KEYWORDS,
"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 = {
PyObject_HEAD_INIT(NULL)
0, /*ob_size*/
"_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;
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;
NSApp = [NSApplication sharedApplication];
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];
[view release];
[window makeKeyAndOrderFront: nil];
nwin++;
[pool release];
return 0;
}
static PyObject*
FigureManager_repr(FigureManager* self)
{
return PyString_FromFormat("FigureManager object %p wrapping NSWindow %p",
(void*) self, (void*)(self->window));
}
static void
FigureManager_dealloc(FigureManager* self)
{
Window* window = self->window;
if(window)
{
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
[window close];
[pool release];
}
self->ob_type->tp_free((PyObject*)self);
}
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_INCREF(Py_None);
return Py_None;
}
static PyMethodDef FigureManager_methods[] = {
{"destroy",
(PyCFunction)FigureManager_destroy,
METH_NOARGS,
"Closes 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 = {
PyObject_HEAD_INIT(NULL)
0, /*ob_size*/
"_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++)
{
ScrollableButton* button;
SEL scrollWheelUpAction = scroll_actions[i][0];
SEL scrollWheelDownAction = scroll_actions[i][1];
if (scrollWheelUpAction || scrollWheelDownAction)
button = [ScrollableButton alloc];
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]];
if (scrollWheelUpAction)
[button setScrollWheelUpAction: scrollWheelUpAction];
if (scrollWheelDownAction)
[button setScrollWheelDownAction: scrollWheelDownAction];
[[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];
self->ob_type->tp_free((PyObject*)self);
}
static PyObject*
NavigationToolbar_repr(NavigationToolbar* self)
{
return PyString_FromFormat("NavigationToolbar object %p", (void*)self);
}
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_INCREF(Py_None);
return Py_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];
unsigned int n = [items count];
int* states = malloc(n*sizeof(int));
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++;
}
else states[i] = 0;
}
int j = 0;
PyObject* list = PyList_New(m);
for (i = 0; i < n; i++)
{
if(states[i]==1)
{
PyList_SET_ITEM(list, j, PyInt_FromLong(i));
j++;
}
}
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 = {
PyObject_HEAD_INIT(NULL)
0, /*ob_size*/
"_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;
const float gap = 2;
const int height = 36;
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.png",
@"back.png",
@"forward.png",
@"move.png",
@"zoom_to_rect.png",
@"subplots.png",
@"filesave.png"};
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.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++)
{
const NSSize size = {24, 24};
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] 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 += 200;
NSText* messagebox = [[NSText alloc] initWithFrame: rect];
[messagebox setFont: font];
[messagebox setDrawsBackground: NO];
[messagebox setEditable: NO];
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];
self->ob_type->tp_free((PyObject*)self);
}
static PyObject*
NavigationToolbar2_repr(NavigationToolbar2* self)
{
return PyString_FromFormat("NavigationToolbar2 object %p", (void*)self);
}
static char NavigationToolbar2_doc[] =
"NavigationToolbar2\n";
static PyObject*
NavigationToolbar2_set_message(NavigationToolbar2 *self, PyObject* args)
{
const char* message;
if(!PyArg_ParseTuple(args, "s", &message)) return NULL;
NSText* messagebox = self->messagebox;
if (messagebox)
{ NSString* text = [NSString stringWithUTF8String: message];
[messagebox setString: text];
}
Py_INCREF(Py_None);
return Py_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 = {
PyObject_HEAD_INIT(NULL)
0, /*ob_size*/
"_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;
if(!PyArg_ParseTuple(args, "s", &title)) return NULL;
NSSavePanel* panel = [NSSavePanel savePanel];
[panel setTitle: [NSString stringWithCString: title
encoding: NSASCIIStringEncoding]];
result = [panel runModal];
if (result == NSOKButton)
{
NSString* filename = [panel filename];
unsigned int n = [filename length];
unichar* buffer = malloc(n*sizeof(unichar));
[filename getCharacters: buffer];
PyObject* string = PyUnicode_FromUnicode(buffer, n);
free(buffer);
return string;
}
Py_INCREF(Py_None);
return Py_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;
default: return NULL;
}
Py_INCREF(Py_None);
return Py_None;
}
static char show__doc__[] = "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.";
static PyObject*
show(PyObject* self)
{
if(nwin > 0) [NSApp run];
Py_INCREF(Py_None);
return Py_None;
}
@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];
nwin--;
if(nwin==0) [NSApp stop: self];
}
- (void)dealloc
{
PyGILState_STATE gstate;
gstate = PyGILState_Ensure();
Py_DECREF(manager);
PyGILState_Release(gstate);
[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;
return self;
}
- (void)dealloc
{
FigureCanvas* fc = (FigureCanvas*)canvas;
if (fc) fc->view = NULL;
[self removeTrackingRect: tracking];
[super dealloc];
}
- (void)setCanvas: (PyObject*)newCanvas
{
canvas = newCanvas;
}
-(void)drawRect:(NSRect)rect
{
PyObject* result;
PyGILState_STATE gstate = PyGILState_Ensure();
PyObject* figure = PyObject_GetAttrString(canvas, "figure");
if (!figure)
{
PyErr_Print();
PyGILState_Release(gstate);
return;
}
PyObject* renderer = PyObject_GetAttrString(canvas, "renderer");
if (!renderer)
{
PyErr_Print();
Py_DECREF(figure);
PyGILState_Release(gstate);
return;
}
GraphicsContext* gc = (GraphicsContext*) PyObject_GetAttrString(renderer, "gc");
if (!gc)
{
PyErr_Print();
Py_DECREF(figure);
Py_DECREF(renderer);
PyGILState_Release(gstate);
return;
}
gc->size = [self frame].size;
CGContextRef cr = (CGContextRef) [[NSGraphicsContext currentContext] graphicsPort];
gc->cr = cr;
gc->level = 0;
result = PyObject_CallMethod(figure, "draw", "O", renderer);
if(result)
Py_DECREF(result);
else
PyErr_Print();
gc->cr = nil;
if (!NSIsEmptyRect(rubberband)) NSFrameRect(rubberband);
Py_DECREF(gc);
Py_DECREF(figure);
Py_DECREF(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];
}
- (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;
PyObject* result;
PyGILState_STATE gstate;
NSPoint location = [event locationInWindow];
location = [self convertPoint: location fromView: nil];
x = location.x;
y = location.y;
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 */
}
gstate = PyGILState_Ensure();
result = PyObject_CallMethod(canvas, "button_press_event", "iii", x, y, num);
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;
y = location.y;
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;
y = location.y;
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;
y = location.y;
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;
PyObject* result;
PyGILState_STATE gstate;
NSPoint location = [event locationInWindow];
location = [self convertPoint: location fromView: nil];
x = location.x;
y = location.y;
gstate = PyGILState_Ensure();
result = PyObject_CallMethod(canvas, "button_press_event", "iii", x, y, num);
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;
y = location.y;
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;
y = location.y;
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;
PyObject* result;
PyGILState_STATE gstate;
NSPoint location = [event locationInWindow];
location = [self convertPoint: location fromView: nil];
x = location.x;
y = location.y;
gstate = PyGILState_Ensure();
result = PyObject_CallMethod(canvas, "button_press_event", "iii", x, y, num);
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;
y = location.y;
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;
y = location.y;
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
{
NSString* text = [event charactersIgnoringModifiers];
unichar uc = [text characterAtIndex:0];
int i = (int)uc;
if ([event modifierFlags] & NSNumericPadKeyMask)
{
if (i > 256)
{
if (uc==NSLeftArrowFunctionKey) return "left";
else if (uc==NSUpArrowFunctionKey) return "up";
else if (uc==NSRightArrowFunctionKey) return "right";
else if (uc==NSDownArrowFunctionKey) return "down";
else if (uc==NSF1FunctionKey) return "f1";
else if (uc==NSF2FunctionKey) return "f2";
else if (uc==NSF3FunctionKey) return "f3";
else if (uc==NSF4FunctionKey) return "f4";
else if (uc==NSF5FunctionKey) return "f5";
else if (uc==NSF6FunctionKey) return "f6";
else if (uc==NSF7FunctionKey) return "f7";
else if (uc==NSF8FunctionKey) return "f8";
else if (uc==NSF9FunctionKey) return "f9";
else if (uc==NSF10FunctionKey) return "f10";
else if (uc==NSF11FunctionKey) return "f11";
else if (uc==NSF12FunctionKey) return "f12";
else if (uc==NSScrollLockFunctionKey) return "scroll_lock";
else if (uc==NSBreakFunctionKey) return "break";
else if (uc==NSInsertFunctionKey) return "insert";
else if (uc==NSDeleteFunctionKey) return "delete";
else if (uc==NSHomeFunctionKey) return "home";
else if (uc==NSEndFunctionKey) return "end";
else if (uc==NSPageUpFunctionKey) return "pageup";
else if (uc==NSPageDownFunctionKey) return "pagedown";
}
else if ((char)uc == '.') return "dec";
}
switch (i)
{
case 127: return "backspace";
case 13: return "enter";
case 3: return "enter";
case 27: return "escape";
default:
{
static char s[2];
s[0] = (char)uc;
s[1] = '\0';
return (const char*)s;
}
}
return NULL;
}
- (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);
int y = (int)round(point.y - 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);
}
- (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 struct PyMethodDef methods[] = {
{"show",
(PyCFunction)show,
METH_NOARGS,
show__doc__
},
{"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 */
};
void init_macosx(void)
{ PyObject *m;
import_array();
if (PyType_Ready(&GraphicsContextType) < 0) return;
if (PyType_Ready(&FigureCanvasType) < 0) return;
if (PyType_Ready(&FigureManagerType) < 0) return;
if (PyType_Ready(&NavigationToolbarType) < 0) return;
if (PyType_Ready(&NavigationToolbar2Type) < 0) return;
m = Py_InitModule4("_macosx",
methods,
"Mac OS X native backend",
NULL,
PYTHON_API_VERSION);
Py_INCREF(&GraphicsContextType);
Py_INCREF(&FigureCanvasType);
Py_INCREF(&FigureManagerType);
Py_INCREF(&NavigationToolbarType);
Py_INCREF(&NavigationToolbar2Type);
PyModule_AddObject(m, "GraphicsContext", (PyObject*) &GraphicsContextType);
PyModule_AddObject(m, "FigureCanvas", (PyObject*) &FigureCanvasType);
PyModule_AddObject(m, "FigureManager", (PyObject*) &FigureManagerType);
PyModule_AddObject(m, "NavigationToolbar", (PyObject*) &NavigationToolbarType);
PyModule_AddObject(m, "NavigationToolbar2", (PyObject*) &NavigationToolbar2Type);
PyOS_InputHook = wait_for_stdin;
}