|
| 1 | +""" |
| 2 | +backend_qtagg.py version 0.0.1 |
| 3 | +http://www.tjora.no/python/matplotlib/qtaggbackend |
| 4 | +
|
| 5 | +This is a not very tested matplotlib qt-backend that uses the |
| 6 | +Agg-backend for rendering etc. |
| 7 | +
|
| 8 | +Tested on Windows with |
| 9 | + python 2.3.4 |
| 10 | + matplotlib 0.70.1 |
| 11 | + PyQt-3.11-040518-Python23 |
| 12 | +
|
| 13 | +It does not work in IPython interactive mode, and will therefore raise |
| 14 | +an Exception if that is tried. I suppose that IPython must be changed |
| 15 | +to support a Qt main loop in a thread for this to work ok. |
| 16 | +
|
| 17 | +Problems: |
| 18 | + Should FigureManagerQtAgg.destroy do anything? |
| 19 | + |
| 20 | +REQUIREMENTS |
| 21 | +
|
| 22 | + PyQt of some unknown version. Does not require anything but a basic Qt, |
| 23 | + that is no Enterprise version etc. (Uses QWidget, and not QCanvas) |
| 24 | +
|
| 25 | +Copyright (C) Sigve Tjora, 2005 |
| 26 | + |
| 27 | + License: This work is licensed under the matplotlib license( PSF |
| 28 | + compatible). A copy should be included with this source code. |
| 29 | +
|
| 30 | +""" |
| 31 | + |
| 32 | +from __future__ import division |
| 33 | + |
| 34 | +import sys |
| 35 | +from matplotlib import verbose |
| 36 | +from matplotlib._pylab_helpers import Gcf |
| 37 | +from matplotlib.backend_bases import RendererBase, GraphicsContextBase,\ |
| 38 | + FigureManagerBase, FigureCanvasBase, error_msg, NavigationToolbar2 |
| 39 | + |
| 40 | +from matplotlib.cbook import enumerate |
| 41 | +from matplotlib.figure import Figure |
| 42 | +from matplotlib.transforms import Bbox |
| 43 | +import matplotlib |
| 44 | +from backend_agg import RendererAgg, FigureCanvasAgg |
| 45 | +import qt |
| 46 | +import os.path |
| 47 | + |
| 48 | +def error_msg_qtagg(msg, *args): |
| 49 | + """ |
| 50 | + Signal an error condition. |
| 51 | + - in a GUI backend, popup a error dialog. |
| 52 | + - in a non-GUI backend delete this function and use |
| 53 | + 'from matplotlib.backend_bases import error_msg' |
| 54 | + """ |
| 55 | + makeSureWeHaveAQApp() |
| 56 | + qt.QMessageBox.warning(None, "Matplotlib", msg, qt.QMessageBox.Ok) |
| 57 | + raise SystemExit |
| 58 | + |
| 59 | + |
| 60 | + |
| 61 | +######################################################################## |
| 62 | +# |
| 63 | +# The following functions and classes are for pylab and implement |
| 64 | +# window/figure managers, etc... |
| 65 | +# |
| 66 | +######################################################################## |
| 67 | + |
| 68 | +def draw_if_interactive(): |
| 69 | + """ |
| 70 | + For image backends - is not required |
| 71 | + For GUI backends - this should be overriden if drawing should be done in |
| 72 | + interactive python mode |
| 73 | + """ |
| 74 | + if matplotlib.is_interactive(): |
| 75 | + raise NotImplementedError("Interactive drawing not supported on Qt-backend.") |
| 76 | + #figManager = Gcf.get_active() |
| 77 | + #if figManager != None: |
| 78 | + #figManager.canvas.draw() |
| 79 | + |
| 80 | +def show(mainloop = True): |
| 81 | + """ |
| 82 | + For image backends - is not required |
| 83 | + For GUI backends - show() is usually the last line of a pylab script and |
| 84 | + tells the backend that it is time to draw. In interactive mode, this may |
| 85 | + be a do nothing func. See the GTK backend for an example of how to handle |
| 86 | + interactive versus batch mode |
| 87 | + """ |
| 88 | + for manager in Gcf.get_all_fig_managers(): |
| 89 | + # do something to display the GUI |
| 90 | + manager.window.show() |
| 91 | + if mainloop: |
| 92 | + qt.qApp.connect(qt.qApp, qt.SIGNAL("lastWindowClosed()") |
| 93 | + , qt.qApp |
| 94 | + , qt.SLOT("quit()") |
| 95 | + ) |
| 96 | + qt.qApp.exec_loop() |
| 97 | + |
| 98 | +def new_figure_manager(num, *args, **kwargs): |
| 99 | + """ |
| 100 | + Create a new figure manager instance |
| 101 | + """ |
| 102 | + makeSureWeHaveAQApp() |
| 103 | + thisFig = Figure(*args, **kwargs) |
| 104 | + window = qt.QMainWindow() |
| 105 | + canvas = FigureCanvasQtAgg(thisFig, window) |
| 106 | + if matplotlib.rcParams['toolbar']=='toolbar2': |
| 107 | + toolbar = NavigationToolbar2QtAgg(canvas, window) |
| 108 | + else: |
| 109 | + toolbar = None |
| 110 | + manager = FigureManagerQtAgg(canvas, num, window, toolbar) |
| 111 | + window.resize(qt.QSize(600,483).expandedTo(window.minimumSizeHint())) |
| 112 | + window.clearWState(qt.Qt.WState_Polished) |
| 113 | + return manager |
| 114 | + |
| 115 | + |
| 116 | +class FigureCanvasQtAgg(qt.QWidget, FigureCanvasAgg): |
| 117 | + """ |
| 118 | + The canvas the figure renders into. Calls the draw and print fig |
| 119 | + methods, creates the renderers, etc... |
| 120 | +
|
| 121 | + Public attribute |
| 122 | +
|
| 123 | + figure - A Figure instance |
| 124 | +
|
| 125 | + Note GUI templates will want to connect events for button presses, |
| 126 | + mouse movements and key presses to functions that call the base |
| 127 | + class methods button_press_event, button_release_event, |
| 128 | + motion_notify_event, key_press_event, and key_release_event. See, |
| 129 | + eg backend_gtk.py, backend_wx.py and backend_tkagg.py |
| 130 | + """ |
| 131 | + def __init__(self, figure, window): |
| 132 | + qt.QWidget.__init__(self, window) |
| 133 | + FigureCanvasAgg.__init__(self, figure) |
| 134 | + self.buffer = qt.QPixmap() |
| 135 | + |
| 136 | + #Coordinates of the select rectangle if it is needed. |
| 137 | + self.rect = None |
| 138 | + |
| 139 | + def draw(self): |
| 140 | + """ |
| 141 | + Draw the figure using the renderer |
| 142 | + """ |
| 143 | + FigureCanvasAgg.draw(self) |
| 144 | + self.stringBuffer = str(self.buffer_rgba()) |
| 145 | + self.qimage = qt.QImage(self.stringBuffer, |
| 146 | + self.renderer.width, |
| 147 | + self.renderer.height, |
| 148 | + 32, |
| 149 | + None, |
| 150 | + 0, |
| 151 | + qt.QImage.IgnoreEndian) |
| 152 | + self.update() |
| 153 | + |
| 154 | + def paintEvent(self, ev): |
| 155 | + self.buffer.convertFromImage(self.qimage, qt.Qt.OrderedAlphaDither) |
| 156 | + if self.rect: |
| 157 | + self.p = qt.QPainter() |
| 158 | + self.p.pen().setStyle(qt.QPen.DotLine) |
| 159 | + self.p.begin(self.buffer) |
| 160 | + x0,y0, x1,y1 = self.rect |
| 161 | + self.p.drawRect(x0,y0, x1-x0, y1-y0) |
| 162 | + self.p.flush() |
| 163 | + self.p.end() |
| 164 | + # blit the pixmap to the widget |
| 165 | + qt.bitBlt(self, 0, 0, self.buffer) |
| 166 | + |
| 167 | + def resizeEvent(self, ev): |
| 168 | + width, height = ev.size().width(), ev.size().height() |
| 169 | + |
| 170 | + # compute desired figure size in inches |
| 171 | + dpival = self.figure.dpi.get() |
| 172 | + winch = width/dpival |
| 173 | + hinch = height/dpival |
| 174 | + self.figure.set_figsize_inches(winch, hinch) |
| 175 | + |
| 176 | + self.draw() |
| 177 | + |
| 178 | + #Events to pass on to matplotlib |
| 179 | + def keyPressEvent(self, ev): |
| 180 | + self.keyPressEvent(ev.text()) |
| 181 | + def keyReleaseEvent(self, ev): |
| 182 | + self.keyReleaseEvent(ev.text()) |
| 183 | + def mouseMoveEvent(self, ev): |
| 184 | + x, y = self.getMatplotlibCoord(ev) |
| 185 | + self.motion_notify_event(x, y) |
| 186 | + def mousePressEvent(self, ev): |
| 187 | + x, y = self.getMatplotlibCoord(ev) |
| 188 | + self.button_press_event(x, y, self.get_matlab_button(ev)) |
| 189 | + def mouseReleaseEvent(self, ev): |
| 190 | + x, y = self.getMatplotlibCoord(ev) |
| 191 | + self.button_release_event(x, y, self.get_matlab_button(ev)) |
| 192 | + def get_matlab_button(self, ev): |
| 193 | + b = ev.button() |
| 194 | + if b == qt.QMouseEvent.NoButton: |
| 195 | + return None |
| 196 | + elif b == qt.QMouseEvent.LeftButton: |
| 197 | + return 1 |
| 198 | + elif b == qt.QMouseEvent.MidButton: |
| 199 | + return 2 |
| 200 | + elif b == qt.QMouseEvent.RightButton: |
| 201 | + return 3 |
| 202 | + else: |
| 203 | + return None |
| 204 | + def getMatplotlibCoord(self, ev): |
| 205 | + x = ev.x() |
| 206 | + y = self.height() - ev.y() |
| 207 | + return x,y |
| 208 | + def print_figure(self, filename, dpi=150, |
| 209 | + facecolor='w', edgecolor='w', |
| 210 | + orientation='portrait'): |
| 211 | + |
| 212 | + agg = self.switch_backends(FigureCanvasAgg) |
| 213 | + agg.print_figure(filename, dpi, facecolor, edgecolor, orientation) |
| 214 | + |
| 215 | +class NavigationToolbar2QtAgg(NavigationToolbar2, qt.QToolBar): |
| 216 | + """ |
| 217 | + Public attriubutes |
| 218 | +
|
| 219 | + canvas - the FigureCanvas (qt.QWidget) |
| 220 | + window - the qt.QMainWindow |
| 221 | + """ |
| 222 | + def __init__(self, canvas, window): |
| 223 | + qt.QToolBar.__init__(self, qt.QString(""), window, qt.Qt.DockTop) |
| 224 | + self.canvas = canvas |
| 225 | + self.window = window |
| 226 | + NavigationToolbar2.__init__(self, canvas) |
| 227 | + |
| 228 | + def _init_toolbar(self): |
| 229 | + self.window.statusBar() |
| 230 | + |
| 231 | + basedir = matplotlib.rcParams['datapath'] |
| 232 | + |
| 233 | + self.imageHome = qt.QPixmap(os.path.join(basedir, "home.png")) |
| 234 | + self.imageBack = qt.QPixmap(os.path.join(basedir, "back.png")) |
| 235 | + self.imageForward = qt.QPixmap(os.path.join(basedir, "forward.png")) |
| 236 | + self.imageMove = qt.QPixmap(os.path.join(basedir, "move.png")) |
| 237 | + self.imageSave = qt.QPixmap(os.path.join(basedir, "filesave.png")) |
| 238 | + self.imageZoom = qt.QPixmap(os.path.join(basedir, "zoom_to_rect.png")) |
| 239 | + |
| 240 | + self.homeAction = qt.QAction(self,"homeAction") |
| 241 | + self.homeAction.setIconSet(qt.QIconSet(self.imageHome)) |
| 242 | + self.backAction = qt.QAction(self,"backAction") |
| 243 | + self.backAction.setIconSet(qt.QIconSet(self.imageBack)) |
| 244 | + self.forwardAction = qt.QAction(self,"forwardAction") |
| 245 | + self.forwardAction.setIconSet(qt.QIconSet(self.imageForward)) |
| 246 | + self.moveAction = qt.QAction(self,"moveAction") |
| 247 | + self.moveAction.setIconSet(qt.QIconSet(self.imageMove)) |
| 248 | + self.zoomAction = qt.QAction(self,"zoomAction") |
| 249 | + self.zoomAction.setIconSet(qt.QIconSet(self.imageZoom)) |
| 250 | + self.fileSaveAction = qt.QAction(self,"fileSaveAction") |
| 251 | + self.fileSaveAction.setIconSet(qt.QIconSet(self.imageSave)) |
| 252 | + |
| 253 | + self.homeAction.addTo(self) |
| 254 | + self.backAction.addTo(self) |
| 255 | + self.forwardAction.addTo(self) |
| 256 | + self.moveAction.addTo(self) |
| 257 | + self.zoomAction.addTo(self) |
| 258 | + self.fileSaveAction.addTo(self) |
| 259 | + |
| 260 | + self.connect(self.fileSaveAction,qt.SIGNAL("activated()"),self.save_figure) |
| 261 | + self.connect(self.backAction,qt.SIGNAL("activated()"),self.back) |
| 262 | + self.connect(self.forwardAction,qt.SIGNAL("activated()"),self.forward) |
| 263 | + self.connect(self.homeAction,qt.SIGNAL("activated()"),self.home) |
| 264 | + self.connect(self.moveAction,qt.SIGNAL("activated()"),self.pan) |
| 265 | + self.connect(self.zoomAction,qt.SIGNAL("activated()"),self.zoom) |
| 266 | + |
| 267 | + self.setCaption("Navigation toolbar") |
| 268 | + self.homeAction.setText("Home") |
| 269 | + self.homeAction.setMenuText("Home") |
| 270 | + self.backAction.setText("Back") |
| 271 | + self.backAction.setMenuText("Back") |
| 272 | + self.forwardAction.setText("Forward") |
| 273 | + self.forwardAction.setMenuText("Forward") |
| 274 | + self.moveAction.setText("Move / pan") |
| 275 | + self.moveAction.setMenuText("Move / pan") |
| 276 | + self.zoomAction.setText("Zoom") |
| 277 | + self.zoomAction.setMenuText("Zoom") |
| 278 | + self.fileSaveAction.setText("Save figure") |
| 279 | + self.fileSaveAction.setMenuText("Save figure") |
| 280 | + |
| 281 | + def set_message(self, s): |
| 282 | + self.window.statusBar().message(s) |
| 283 | + |
| 284 | + def save_figure(self): |
| 285 | + s = qt.QFileDialog.getSaveFileName("", |
| 286 | + "Images (*.png *.xpm *.jpg)", |
| 287 | + self.window, |
| 288 | + "save file dialog" |
| 289 | + "Choose a filename to save figure under" ) |
| 290 | + if s: |
| 291 | + self.canvas.print_figure(str(s)) |
| 292 | + |
| 293 | + def draw_rubberband(self, event, x0, y0, x1, y1): |
| 294 | + height = self.canvas.figure.bbox.height() |
| 295 | + y0 = height-y0 |
| 296 | + y1 = height-y1 |
| 297 | + self.canvas.rect = (x0, y0, x1, y1) |
| 298 | + self.canvas.update() |
| 299 | + |
| 300 | + def release(self, event): |
| 301 | + self.canvas.rect = None |
| 302 | + |
| 303 | + |
| 304 | +class FigureManagerQtAgg(FigureManagerBase): |
| 305 | + """ |
| 306 | + Wrap everything up into a window for the pylab interface |
| 307 | +
|
| 308 | + For non interactive backends, the base class does all the work |
| 309 | + """ |
| 310 | + def __init__(self, canvas, num, window, toolbar): |
| 311 | + FigureManagerBase.__init__(self, canvas, num) |
| 312 | + self.window = window |
| 313 | + self.window.setCaption("Figure %d" % num) |
| 314 | + self.window.setCentralWidget(self.canvas) |
| 315 | + self.toolbar = toolbar |
| 316 | + |
| 317 | +# def destroy(self, *args): |
| 318 | +# print "destroy figure manager" |
| 319 | +# self.window.hide() |
| 320 | + |
| 321 | +######################################################################## |
| 322 | +# |
| 323 | +# Now just provide the standard names that backend.__init__ is expecting |
| 324 | +# |
| 325 | +######################################################################## |
| 326 | + |
| 327 | +def makeSureWeHaveAQApp(): |
| 328 | + """This have to be seperated, so if the classes are used in |
| 329 | + in a program with another QApplication instance, we use that |
| 330 | + one instead. |
| 331 | + """ |
| 332 | + global myQtApp |
| 333 | + try: |
| 334 | + #Detect if a qApp exists. |
| 335 | + n = qt.qApp.name() |
| 336 | + except RuntimeError: |
| 337 | + myQtApp = qt.QApplication([]) |
| 338 | + |
| 339 | +FigureManager = FigureManagerQtAgg |
| 340 | +error_msg = error_msg_qtagg |
| 341 | + |
0 commit comments