|
| 1 | +from __future__ import division |
| 2 | +""" |
| 3 | + backend_cocoaagg.py |
| 4 | +
|
| 5 | + A native Cocoa backend via PyObjC in OSX. |
| 6 | +
|
| 7 | + Author: Charles Moad ([email protected]) |
| 8 | +
|
| 9 | + Notes: |
| 10 | + - THIS IS STILL IN DEVELOPMENT! |
| 11 | + - Requires PyObjC (currently testing v1.3.6) |
| 12 | + - Only works with 10.3 at this time (10.4 is high priority) |
| 13 | +""" |
| 14 | + |
| 15 | +import os, sys |
| 16 | + |
| 17 | +try: |
| 18 | + import objc |
| 19 | +except: |
| 20 | + print >>sys.stderr, 'The CococaAgg backend required PyObjC to be installed!' |
| 21 | + sys.exit() |
| 22 | + |
| 23 | +from Foundation import * |
| 24 | +from AppKit import * |
| 25 | +from PyObjCTools import NibClassBuilder, AppHelper |
| 26 | + |
| 27 | +import matplotlib |
| 28 | +from matplotlib.figure import Figure |
| 29 | +from matplotlib.backend_bases import FigureManagerBase |
| 30 | +from backend_agg import FigureCanvasAgg |
| 31 | +import pylab |
| 32 | + |
| 33 | +mplBundle = NSBundle.bundleWithPath_(matplotlib.get_data_path()) |
| 34 | + |
| 35 | +def new_figure_manager(num, *args, **kwargs): |
| 36 | + thisFig = Figure( *args, **kwargs ) |
| 37 | + canvas = FigureCanvasAgg(thisFig) |
| 38 | + return FigureManagerCocoaAgg(canvas, num) |
| 39 | + |
| 40 | +def show(): |
| 41 | + # Let the cocoa run loop take over |
| 42 | + NSApplication.sharedApplication().run() |
| 43 | + |
| 44 | +def draw_if_interactive(): |
| 45 | + if matplotlib.is_interactive(): |
| 46 | + print >>sys.stderr, 'Not implemented yet' |
| 47 | + |
| 48 | +NibClassBuilder.extractClasses('Matplotlib.nib', mplBundle) |
| 49 | + |
| 50 | +class MatplotlibController(NibClassBuilder.AutoBaseClass): |
| 51 | + # available outlets: |
| 52 | + # NSWindow plotWindow |
| 53 | + # NSImageView plotView |
| 54 | + |
| 55 | + def awakeFromNib(self): |
| 56 | + self.plotView.setImageFrameStyle_(NSImageFrameGroove) |
| 57 | + # Get a reference to the active canvas |
| 58 | + self.canvas = pylab.get_current_fig_manager().canvas |
| 59 | + # Issue a update |
| 60 | + self.windowDidResize_(None) |
| 61 | + |
| 62 | + def updatePlot(self): |
| 63 | + self.canvas.draw() # tell the agg to render |
| 64 | + |
| 65 | + w,h = self.canvas.figure.get_width_height() |
| 66 | + |
| 67 | + image = NSImage.alloc().initWithSize_((w,h)) |
| 68 | + brep = NSBitmapImageRep.alloc().initWithBitmapDataPlanes_pixelsWide_pixelsHigh_bitsPerSample_samplesPerPixel_hasAlpha_isPlanar_colorSpaceName_bytesPerRow_bitsPerPixel_( |
| 69 | + (self.canvas.buffer_rgba(),'','','',''), # Image data |
| 70 | + int(w), # width |
| 71 | + int(h), # height |
| 72 | + 8, # bits per pixel |
| 73 | + 4, # components per pixel |
| 74 | + True, # has alpha? |
| 75 | + False, # is planar? |
| 76 | + NSCalibratedRGBColorSpace, # color space |
| 77 | + 0, # row bytes (should be 0 for non planar) |
| 78 | + 32) # bits per pixel |
| 79 | + |
| 80 | + image.addRepresentation_(brep) |
| 81 | + self.plotView.setImage_(image) |
| 82 | + self.plotView.setNeedsDisplay_(True) |
| 83 | + |
| 84 | + def saveFigure_(self, sender): |
| 85 | + pass |
| 86 | + |
| 87 | + def quit_(self, sender): |
| 88 | + pass |
| 89 | + |
| 90 | + def windowDidResize_(self, sender): |
| 91 | + w,h = self.plotView.frame().size |
| 92 | + dpi = self.canvas.figure.dpi.get() |
| 93 | + self.canvas.figure.set_figsize_inches(w / dpi, h / dpi) |
| 94 | + self.updatePlot() |
| 95 | + |
| 96 | +class MPLBootstrap(NSObject): |
| 97 | + def startWithBundle_(self, bundle): |
| 98 | + NSApplicationLoad() |
| 99 | + if not bundle.loadNibFile_externalNameTable_withZone_( |
| 100 | + 'Matplotlib.nib', |
| 101 | + {}, |
| 102 | + None): |
| 103 | + print >>sys.stderr, 'Unable to load Matplotlib Cocoa UI!' |
| 104 | + sys.exit() |
| 105 | + |
| 106 | +class FigureManagerCocoaAgg(FigureManagerBase): |
| 107 | + def __init__(self, canvas, num): |
| 108 | + FigureManagerBase.__init__(self, canvas, num) |
| 109 | + if not WMEnable('Matplotlib'): |
| 110 | + print >>sys.stderr, 'Unable to hook to native window manager!' |
| 111 | + sys.exit() |
| 112 | + |
| 113 | + MPLBootstrap.alloc().init().performSelectorOnMainThread_withObject_waitUntilDone_( |
| 114 | + 'startWithBundle:', |
| 115 | + mplBundle, |
| 116 | + False) |
| 117 | + |
| 118 | +FigureManager = FigureManagerCocoaAgg |
| 119 | + |
| 120 | +#### Everything below taken from PyObjC examples |
| 121 | +#### This is a hack to allow python scripts to access |
| 122 | +#### the window manager without running pythonw. |
| 123 | +def S(*args): |
| 124 | + return ''.join(args) |
| 125 | + |
| 126 | +OSErr = objc._C_SHT |
| 127 | +OUTPSN = 'o^{ProcessSerialNumber=LL}' |
| 128 | +INPSN = 'n^{ProcessSerialNumber=LL}' |
| 129 | +FUNCTIONS=[ |
| 130 | + # These two are public API |
| 131 | + ( u'GetCurrentProcess', S(OSErr, OUTPSN) ), |
| 132 | + ( u'SetFrontProcess', S(OSErr, INPSN) ), |
| 133 | + # This is undocumented SPI |
| 134 | + ( u'CPSSetProcessName', S(OSErr, INPSN, objc._C_CHARPTR) ), |
| 135 | + ( u'CPSEnableForegroundOperation', S(OSErr, INPSN) ), |
| 136 | +] |
| 137 | +def WMEnable(name='Python'): |
| 138 | + if isinstance(name, unicode): |
| 139 | + name = name.encode('utf8') |
| 140 | + mainBundle = NSBundle.mainBundle() |
| 141 | + bPath = os.path.split(os.path.split(os.path.split(sys.executable)[0])[0])[0] |
| 142 | + if mainBundle.bundlePath() == bPath: |
| 143 | + return True |
| 144 | + bndl = NSBundle.bundleWithPath_(objc.pathForFramework('/System/Library/Frameworks/ApplicationServices.framework')) |
| 145 | + if bndl is None: |
| 146 | + print >>sys.stderr, 'ApplicationServices missing' |
| 147 | + return False |
| 148 | + d = {} |
| 149 | + objc.loadBundleFunctions(bndl, d, FUNCTIONS) |
| 150 | + for (fn, sig) in FUNCTIONS: |
| 151 | + if fn not in d: |
| 152 | + print >>sys.stderr, 'Missing', fn |
| 153 | + return False |
| 154 | + err, psn = d['GetCurrentProcess']() |
| 155 | + if err: |
| 156 | + print >>sys.stderr, 'GetCurrentProcess', (err, psn) |
| 157 | + return False |
| 158 | + err = d['CPSSetProcessName'](psn, name) |
| 159 | + if err: |
| 160 | + print >>sys.stderr, 'CPSSetProcessName', (err, psn) |
| 161 | + return False |
| 162 | + err = d['CPSEnableForegroundOperation'](psn) |
| 163 | + if err: |
| 164 | + print >>sys.stderr, 'CPSEnableForegroundOperation', (err, psn) |
| 165 | + return False |
| 166 | + err = d['SetFrontProcess'](psn) |
| 167 | + if err: |
| 168 | + print >>sys.stderr, 'SetFrontProcess', (err, psn) |
| 169 | + return False |
| 170 | + return True |
| 171 | + |
0 commit comments