/[ascend]/branches/pallav/ascxx/backend_gtk3.py
ViewVC logotype

Contents of /branches/pallav/ascxx/backend_gtk3.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 2868 - (show annotations) (download) (as text)
Mon Mar 23 16:45:33 2015 UTC (9 years, 11 months ago) by pallav
File MIME type: text/x-python
File size: 38406 byte(s)
Added the matplotlib backend files
1 from __future__ import division
2
3 import os, sys
4 def fn_name(): return sys._getframe(1).f_code.co_name
5
6 try:
7 from gi.repository import Gtk, Gdk, GObject
8 from gi.repository import Pango
9 import cairo
10 except ImportError:
11 raise ImportError("GTK3 backend requires pygobject to be installed.")
12
13 import matplotlib
14 from matplotlib._pylab_helpers import Gcf
15 from matplotlib.backend_bases import RendererBase, GraphicsContextBase, \
16 FigureManagerBase, FigureCanvasBase, NavigationToolbar2, cursors, TimerBase
17 from matplotlib.backend_bases import ShowBase
18 from matplotlib.backends import backend_cairo
19
20 from matplotlib.cbook import is_string_like, is_writable_file_like
21 from matplotlib.colors import colorConverter
22 from matplotlib.figure import Figure
23 from matplotlib.widgets import SubplotTool
24
25 from matplotlib import lines
26 from matplotlib import cbook
27 from matplotlib import verbose
28
29 backend_version = "0.1.0"
30
31 _debug = False
32 #_debug = True
33
34 if _debug: print "using backend_gtk3"
35
36 # the true dots per inch on the screen; should be display dependent
37 # see http://groups.google.com/groups?q=screen+dpi+x11&hl=en&lr=&ie=UTF-8&oe=UTF-8&safe=off&selm=7077.26e81ad5%40swift.cs.tcd.ie&rnum=5 for some info about screen dpi
38 PIXELS_PER_INCH = 96
39
40 cursord = {
41 cursors.MOVE : Gdk.Cursor.new(Gdk.CursorType.FLEUR),
42 cursors.HAND : Gdk.Cursor.new(Gdk.CursorType.HAND2),
43 cursors.POINTER : Gdk.Cursor.new(Gdk.CursorType.LEFT_PTR),
44 cursors.SELECT_REGION : Gdk.Cursor.new(Gdk.CursorType.TCROSS),
45 }
46
47 def draw_if_interactive():
48 """
49 Is called after every pylab drawing command
50 """
51 if matplotlib.is_interactive():
52 figManager = Gcf.get_active()
53 if figManager is not None:
54 figManager.canvas.draw_idle()
55
56 class Show(ShowBase):
57 def mainloop(self):
58 if Gtk.main_level() == 0:
59 Gtk.main()
60
61 show = Show()
62
63 def new_figure_manager(num, *args, **kwargs):
64 """
65 Create a new figure manager instance
66 """
67 FigureClass = kwargs.pop('FigureClass', Figure)
68 thisFig = FigureClass(*args, **kwargs)
69 canvas = FigureCanvasGTK3(thisFig)
70 manager = FigureManagerGTK3(canvas, num)
71 return manager
72
73
74 class TimerGTK3(TimerBase):
75 '''
76 Subclass of :class:`backend_bases.TimerBase` that uses GTK3 for timer events.
77
78 Attributes:
79 * interval: The time between timer events in milliseconds. Default
80 is 1000 ms.
81 * single_shot: Boolean flag indicating whether this timer should
82 operate as single shot (run once and then stop). Defaults to False.
83 * callbacks: Stores list of (func, args) tuples that will be called
84 upon timer events. This list can be manipulated directly, or the
85 functions add_callback and remove_callback can be used.
86 '''
87 def _timer_start(self):
88 # Need to stop it, otherwise we potentially leak a timer id that will
89 # never be stopped.
90 self._timer_stop()
91 self._timer = GObject.timeout_add(self._interval, self._on_timer)
92
93 def _timer_stop(self):
94 if self._timer is not None:
95 GObject.source_remove(self._timer)
96 self._timer = None
97
98 def _timer_set_interval(self):
99 # Only stop and restart it if the timer has already been started
100 if self._timer is not None:
101 self._timer_stop()
102 self._timer_start()
103
104 def _on_timer(self):
105 TimerBase._on_timer(self)
106
107 # Gtk timeout_add() requires that the callback returns True if it
108 # is to be called again.
109 if len(self.callbacks) > 0 and not self._single:
110 return True
111 else:
112 self._timer = None
113 return False
114
115 class RendererGTK3Cairo (backend_cairo.RendererCairo):
116 def set_context (self, ctx):
117 self.gc.ctx = ctx
118
119 class FigureCanvasGTK3 (Gtk.DrawingArea, backend_cairo.FigureCanvasCairo):
120 keyvald = {65507 : 'control',
121 65505 : 'shift',
122 65513 : 'alt',
123 65508 : 'control',
124 65506 : 'shift',
125 65514 : 'alt',
126 65361 : 'left',
127 65362 : 'up',
128 65363 : 'right',
129 65364 : 'down',
130 65307 : 'escape',
131 65470 : 'f1',
132 65471 : 'f2',
133 65472 : 'f3',
134 65473 : 'f4',
135 65474 : 'f5',
136 65475 : 'f6',
137 65476 : 'f7',
138 65477 : 'f8',
139 65478 : 'f9',
140 65479 : 'f10',
141 65480 : 'f11',
142 65481 : 'f12',
143 65300 : 'scroll_lock',
144 65299 : 'break',
145 65288 : 'backspace',
146 65293 : 'enter',
147 65379 : 'insert',
148 65535 : 'delete',
149 65360 : 'home',
150 65367 : 'end',
151 65365 : 'pageup',
152 65366 : 'pagedown',
153 65438 : '0',
154 65436 : '1',
155 65433 : '2',
156 65435 : '3',
157 65430 : '4',
158 65437 : '5',
159 65432 : '6',
160 65429 : '7',
161 65431 : '8',
162 65434 : '9',
163 65451 : '+',
164 65453 : '-',
165 65450 : '*',
166 65455 : '/',
167 65439 : 'dec',
168 65421 : 'enter',
169 }
170
171 # Setting this as a static constant prevents
172 # this resulting expression from leaking
173 event_mask = (Gdk.EventMask.BUTTON_PRESS_MASK |
174 Gdk.EventMask.BUTTON_RELEASE_MASK |
175 Gdk.EventMask.EXPOSURE_MASK |
176 Gdk.EventMask.KEY_PRESS_MASK |
177 Gdk.EventMask.KEY_RELEASE_MASK |
178 Gdk.EventMask.ENTER_NOTIFY_MASK |
179 Gdk.EventMask.LEAVE_NOTIFY_MASK |
180 Gdk.EventMask.POINTER_MOTION_MASK |
181 Gdk.EventMask.POINTER_MOTION_HINT_MASK)
182
183 def __init__(self, figure):
184 if _debug: print 'FigureCanvasGTK3.%s' % fn_name()
185 FigureCanvasBase.__init__(self, figure)
186 GObject.GObject.__init__(self)
187
188 self._idle_draw_id = 0
189 self._need_redraw = True
190 self._lastCursor = None
191
192 self.connect('scroll_event', self.scroll_event)
193 self.connect('button_press_event', self.button_press_event)
194 self.connect('button_release_event', self.button_release_event)
195 self.connect('configure_event', self.configure_event)
196 self.connect('draw', self.on_draw_event)
197 self.connect('key_press_event', self.key_press_event)
198 self.connect('key_release_event', self.key_release_event)
199 self.connect('motion_notify_event', self.motion_notify_event)
200 self.connect('leave_notify_event', self.leave_notify_event)
201 self.connect('enter_notify_event', self.enter_notify_event)
202
203 self.set_events(self.__class__.event_mask)
204
205 self.set_double_buffered(False)
206 self.set_can_focus(True)
207 self._renderer_init()
208 self._idle_event_id = GObject.idle_add(self.idle_event)
209
210 def destroy(self):
211 #Gtk.DrawingArea.destroy(self)
212 self.close_event()
213 GObject.source_remove(self._idle_event_id)
214 if self._idle_draw_id != 0:
215 GObject.source_remove(self._idle_draw_id)
216
217 def scroll_event(self, widget, event):
218 if _debug: print 'FigureCanvasGTK3.%s' % fn_name()
219 x = event.x
220 # flipy so y=0 is bottom of canvas
221 y = self.get_allocation().height - event.y
222 if event.direction==Gdk.ScrollDirection.UP:
223 step = 1
224 else:
225 step = -1
226 FigureCanvasBase.scroll_event(self, x, y, step, guiEvent=event)
227 return False # finish event propagation?
228
229 def button_press_event(self, widget, event):
230 if _debug: print 'FigureCanvasGTK3.%s' % fn_name()
231 x = event.x
232 # flipy so y=0 is bottom of canvas
233 y = self.get_allocation().height - event.y
234 FigureCanvasBase.button_press_event(self, x, y, event.button, guiEvent=event)
235 return False # finish event propagation?
236
237 def button_release_event(self, widget, event):
238 if _debug: print 'FigureCanvasGTK3.%s' % fn_name()
239 x = event.x
240 # flipy so y=0 is bottom of canvas
241 y = self.get_allocation().height - event.y
242 FigureCanvasBase.button_release_event(self, x, y, event.button, guiEvent=event)
243 return False # finish event propagation?
244
245 def key_press_event(self, widget, event):
246 if _debug: print 'FigureCanvasGTK3.%s' % fn_name()
247 key = self._get_key(event)
248 if _debug: print "hit", key
249 FigureCanvasBase.key_press_event(self, key, guiEvent=event)
250 return False # finish event propagation?
251
252 def key_release_event(self, widget, event):
253 if _debug: print 'FigureCanvasGTK3.%s' % fn_name()
254 key = self._get_key(event)
255 if _debug: print "release", key
256 FigureCanvasBase.key_release_event(self, key, guiEvent=event)
257 return False # finish event propagation?
258
259 def motion_notify_event(self, widget, event):
260 if _debug: print 'FigureCanvasGTK3.%s' % fn_name()
261 if event.is_hint:
262 t, x, y, state = event.window.get_pointer()
263 else:
264 x, y, state = event.x, event.y, event.get_state()
265
266 # flipy so y=0 is bottom of canvas
267 y = self.get_allocation().height - y
268 FigureCanvasBase.motion_notify_event(self, x, y, guiEvent=event)
269 return False # finish event propagation?
270
271 def leave_notify_event(self, widget, event):
272 FigureCanvasBase.leave_notify_event(self, event)
273
274 def enter_notify_event(self, widget, event):
275 FigureCanvasBase.enter_notify_event(self, event)
276
277 def _get_key(self, event):
278 if event.keyval in self.keyvald:
279 key = self.keyvald[event.keyval]
280 elif event.keyval <256:
281 key = chr(event.keyval)
282 else:
283 key = None
284
285 #ctrl = event.get_state() & Gdk.EventMask.CONTROL
286 #shift = event.get_state() & Gdk.EventMask.SHIFT
287 return key
288
289
290 def configure_event(self, widget, event):
291 if _debug: print 'FigureCanvasGTK3.%s' % fn_name()
292 if widget.get_property("window") is None:
293 return
294 w, h = event.width, event.height
295 if w < 3 or h < 3:
296 return # empty fig
297
298 # resize the figure (in inches)
299 dpi = self.figure.dpi
300 self.figure.set_size_inches (w/dpi, h/dpi)
301 self._need_redraw = True
302
303 return False # finish event propagation?
304
305
306 def draw(self):
307 self._need_redraw = True
308 if self.get_visible() and self.get_mapped():
309 self.queue_draw()
310 # do a synchronous draw (its less efficient than an async draw,
311 # but is required if/when animation is used)
312 self.get_property("window").process_updates (False)
313
314 def draw_idle(self):
315 def idle_draw(*args):
316 self.draw()
317 self._idle_draw_id = 0
318 return False
319 if self._idle_draw_id == 0:
320 self._idle_draw_id = GObject.idle_add(idle_draw)
321
322 def _renderer_init(self):
323 """use cairo renderer"""
324 if _debug: print '%s.%s()' % (self.__class__.__name__, fn_name())
325 self._renderer = RendererGTK3Cairo(self.figure.dpi)
326
327 def _render_figure(self, width, height):
328 """use cairo renderer"""
329 self._renderer.set_width_height (width, height)
330 self.figure.draw (self._renderer)
331
332 def on_draw_event(self, widget, ctx):
333 """ GtkDrawable draw event, like expose_event in GTK 2.X
334 """
335 if _debug: print 'FigureCanvasGTK3.%s' % fn_name()
336
337 if self._need_redraw:
338 self._renderer.set_context(ctx)
339 allocation = self.get_allocation()
340 x, y, w, h = allocation.x, allocation.y, allocation.width, allocation.height
341 self._render_figure(w, h)
342 self._need_redraw = False
343
344 return False # finish event propagation?
345
346 def get_default_filetype(self):
347 return 'png'
348
349 def new_timer(self, *args, **kwargs):
350 """
351 Creates a new backend-specific subclass of :class:`backend_bases.Timer`.
352 This is useful for getting periodic events through the backend's native
353 event loop. Implemented only for backends with GUIs.
354
355 optional arguments:
356
357 *interval*
358 Timer interval in milliseconds
359 *callbacks*
360 Sequence of (func, args, kwargs) where func(*args, **kwargs) will
361 be executed by the timer every *interval*.
362 """
363 return TimerGTK3(*args, **kwargs)
364
365 def flush_events(self):
366 Gdk.threads_enter()
367 while Gtk.events_pending():
368 Gtk.main_iteration(True)
369 Gdk.flush()
370 Gdk.threads_leave()
371
372 def start_event_loop(self,timeout):
373 FigureCanvasBase.start_event_loop_default(self,timeout)
374 start_event_loop.__doc__=FigureCanvasBase.start_event_loop_default.__doc__
375
376 def stop_event_loop(self):
377 FigureCanvasBase.stop_event_loop_default(self)
378 stop_event_loop.__doc__=FigureCanvasBase.stop_event_loop_default.__doc__
379
380 FigureCanvas = FigureCanvasGTK3
381
382 class FigureManagerGTK3(FigureManagerBase):
383 """
384 Public attributes
385
386 canvas : The FigureCanvas instance
387 num : The Figure number
388 toolbar : The Gtk.Toolbar (gtk only)
389 vbox : The Gtk.VBox containing the canvas and toolbar (gtk only)
390 window : The Gtk.Window (gtk only)
391 """
392 def __init__(self, canvas, num):
393 if _debug: print 'FigureManagerGTK3.%s' % fn_name()
394 FigureManagerBase.__init__(self, canvas, num)
395
396 self.window = Gtk.Window()
397 self.window.set_title("Figure %d" % num)
398 if (window_icon):
399 try:
400 self.window.set_icon_from_file(window_icon)
401 except:
402 # some versions of gtk throw a glib.GError but not
403 # all, so I am not sure how to catch it. I am unhappy
404 # diong a blanket catch here, but an not sure what a
405 # better way is - JDH
406 verbose.report('Could not load matplotlib icon: %s' % sys.exc_info()[1])
407
408 self.vbox = Gtk.Box()
409 self.vbox.set_property("orientation", Gtk.Orientation.VERTICAL)
410 self.window.add(self.vbox)
411 self.vbox.show()
412
413 self.canvas.show()
414
415 # attach a show method to the figure for pylab ease of use
416 self.canvas.figure.show = lambda *args: self.window.show()
417
418 self.vbox.pack_start(self.canvas, True, True, 0)
419
420 self.toolbar = self._get_toolbar(canvas)
421
422 # calculate size for window
423 w = int (self.canvas.figure.bbox.width)
424 h = int (self.canvas.figure.bbox.height)
425
426 if self.toolbar is not None:
427 self.toolbar.show()
428 self.vbox.pack_end(self.toolbar, False, False, 0)
429 size_request = self.toolbar.size_request()
430 h += size_request.height
431
432 self.window.set_default_size (w, h)
433
434 def destroy(*args):
435 Gcf.destroy(num)
436 self.window.connect("destroy", destroy)
437 self.window.connect("delete_event", destroy)
438 if matplotlib.is_interactive():
439 self.window.show()
440
441 def notify_axes_change(fig):
442 'this will be called whenever the current axes is changed'
443 if self.toolbar is not None: self.toolbar.update()
444 self.canvas.figure.add_axobserver(notify_axes_change)
445
446 self.canvas.grab_focus()
447
448 def destroy(self, *args):
449 if _debug: print 'FigureManagerGTK3.%s' % fn_name()
450 self.vbox.destroy()
451 self.window.destroy()
452 self.canvas.destroy()
453 if self.toolbar:
454 self.toolbar.destroy()
455 self.__dict__.clear() #Is this needed? Other backends don't have it.
456
457 if Gcf.get_num_fig_managers()==0 and \
458 not matplotlib.is_interactive() and \
459 Gtk.main_level() >= 1:
460 Gtk.main_quit()
461
462 def show(self):
463 # show the figure window
464 self.window.show()
465
466 def full_screen_toggle (self):
467 self._full_screen_flag = not self._full_screen_flag
468 if self._full_screen_flag:
469 self.window.fullscreen()
470 else:
471 self.window.unfullscreen()
472 _full_screen_flag = False
473
474
475 def _get_toolbar(self, canvas):
476 # must be inited after the window, drawingArea and figure
477 # attrs are set
478 if matplotlib.rcParams['toolbar'] == 'classic':
479 toolbar = NavigationToolbar (canvas, self.window)
480 elif matplotlib.rcParams['toolbar'] == 'toolbar2':
481 toolbar = NavigationToolbar2GTK3 (canvas, self.window)
482 else:
483 toolbar = None
484 return toolbar
485
486 def set_window_title(self, title):
487 self.window.set_title(title)
488
489 def resize(self, width, height):
490 'set the canvas size in pixels'
491 #_, _, cw, ch = self.canvas.allocation
492 #_, _, ww, wh = self.window.allocation
493 #self.window.resize (width-cw+ww, height-ch+wh)
494 self.window.resize(width, height)
495
496
497 class NavigationToolbar2GTK3(NavigationToolbar2, Gtk.Toolbar):
498 # list of toolitems to add to the toolbar, format is:
499 # text, tooltip_text, image_file, callback(str)
500 toolitems = (
501 ('Home', 'Reset original view', 'home.png', 'home'),
502 ('Back', 'Back to previous view','back.png', 'back'),
503 ('Forward', 'Forward to next view','forward.png', 'forward'),
504 ('Pan', 'Pan axes with left mouse, zoom with right', 'move.png','pan'),
505 ('Zoom', 'Zoom to rectangle','zoom_to_rect.png', 'zoom'),
506 (None, None, None, None),
507 ('Subplots', 'Configure subplots','subplots.png', 'configure_subplots'),
508 ('Save', 'Save the figure','filesave.png', 'save_figure'),
509 )
510
511 def __init__(self, canvas, window):
512 self.win = window
513 GObject.GObject.__init__(self)
514 NavigationToolbar2.__init__(self, canvas)
515 self.ctx = None
516
517 def set_message(self, s):
518 self.message.set_label(s)
519
520 def set_cursor(self, cursor):
521 self.canvas.get_property("window").set_cursor(cursord[cursor])
522 #self.canvas.set_cursor(cursord[cursor])
523
524 def release(self, event):
525 try: del self._pixmapBack
526 except AttributeError: pass
527
528 def dynamic_update(self):
529 # legacy method; new method is canvas.draw_idle
530 self.canvas.draw_idle()
531
532 def draw_rubberband(self, event, x0, y0, x1, y1):
533 'adapted from http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/189744'
534 self.ctx = self.canvas.get_property("window").cairo_create()
535
536 # todo: instead of redrawing the entire figure, copy the part of
537 # the figure that was covered by the previous rubberband rectangle
538 self.canvas.draw()
539
540 height = self.canvas.figure.bbox.height
541 y1 = height - y1
542 y0 = height - y0
543 w = abs(x1 - x0)
544 h = abs(y1 - y0)
545 rect = [int(val)for val in min(x0,x1), min(y0, y1), w, h]
546
547 self.ctx.new_path()
548 self.ctx.set_line_width(0.5)
549 self.ctx.rectangle(rect[0], rect[1], rect[2], rect[3])
550 self.ctx.set_source_rgb(0, 0, 0)
551 self.ctx.stroke()
552
553 def _init_toolbar(self):
554 self.set_style(Gtk.ToolbarStyle.ICONS)
555 basedir = os.path.join(matplotlib.rcParams['datapath'],'images')
556
557 for text, tooltip_text, image_file, callback in self.toolitems:
558 if text is None:
559 self.insert( Gtk.SeparatorToolItem(), -1 )
560 continue
561 fname = os.path.join(basedir, image_file)
562 image = Gtk.Image()
563 image.set_from_file(fname)
564 tbutton = Gtk.ToolButton()
565 tbutton.set_label(text)
566 tbutton.set_icon_widget(image)
567 self.insert(tbutton, -1)
568 tbutton.connect('clicked', getattr(self, callback))
569 tbutton.set_tooltip_text(tooltip_text)
570
571 toolitem = Gtk.SeparatorToolItem()
572 self.insert(toolitem, -1)
573 toolitem.set_draw(False)
574 toolitem.set_expand(True)
575
576 toolitem = Gtk.ToolItem()
577 self.insert(toolitem, -1)
578 self.message = Gtk.Label()
579 toolitem.add(self.message)
580
581 self.show_all()
582
583 def get_filechooser(self):
584 return FileChooserDialog(
585 title='Save the figure',
586 parent=self.win,
587 filetypes=self.canvas.get_supported_filetypes(),
588 default_filetype=self.canvas.get_default_filetype())
589
590 def save_figure(self, *args):
591 fname, format = self.get_filechooser().get_filename_from_user()
592 if fname:
593 try:
594 self.canvas.print_figure(fname, format=format)
595 except Exception, e:
596 error_msg_gtk(str(e), parent=self)
597
598 def configure_subplots(self, button):
599 toolfig = Figure(figsize=(6,3))
600 canvas = self._get_canvas(toolfig)
601 toolfig.subplots_adjust(top=0.9)
602 tool = SubplotTool(self.canvas.figure, toolfig)
603
604 w = int (toolfig.bbox.width)
605 h = int (toolfig.bbox.height)
606
607
608 window = Gtk.Window()
609 if (window_icon):
610 try: window.set_icon_from_file(window_icon)
611 except:
612 # we presumably already logged a message on the
613 # failure of the main plot, don't keep reporting
614 pass
615 window.set_title("Subplot Configuration Tool")
616 window.set_default_size(w, h)
617 vbox = Gtk.Box()
618 vbox.set_property("orientation", Gtk.Orientation.VERTICAL)
619 window.add(vbox)
620 vbox.show()
621
622 canvas.show()
623 vbox.pack_start(canvas, True, True, 0)
624 window.show()
625
626 def _get_canvas(self, fig):
627 return FigureCanvasGTK3(fig)
628
629
630 class NavigationToolbar(Gtk.Toolbar):
631 """
632 Public attributes
633
634 canvas - the FigureCanvas (Gtk.DrawingArea)
635 win - the Gtk.Window
636
637 """
638 # list of toolitems to add to the toolbar, format is:
639 # text, tooltip_text, image, callback(str), callback_arg, scroll(bool)
640 toolitems = (
641 ('Left', 'Pan left with click or wheel mouse (bidirectional)',
642 Gtk.STOCK_GO_BACK, 'panx', -1, True),
643 ('Right', 'Pan right with click or wheel mouse (bidirectional)',
644 Gtk.STOCK_GO_FORWARD, 'panx', 1, True),
645 ('Zoom In X',
646 'Zoom In X (shrink the x axis limits) with click or wheel'
647 ' mouse (bidirectional)',
648 Gtk.STOCK_ZOOM_IN, 'zoomx', 1, True),
649 ('Zoom Out X',
650 'Zoom Out X (expand the x axis limits) with click or wheel'
651 ' mouse (bidirectional)',
652 Gtk.STOCK_ZOOM_OUT, 'zoomx', -1, True),
653 (None, None, None, None, None, None,),
654 ('Up', 'Pan up with click or wheel mouse (bidirectional)',
655 Gtk.STOCK_GO_UP, 'pany', 1, True),
656 ('Down', 'Pan down with click or wheel mouse (bidirectional)',
657 Gtk.STOCK_GO_DOWN, 'pany', -1, True),
658 ('Zoom In Y',
659 'Zoom in Y (shrink the y axis limits) with click or wheel'
660 ' mouse (bidirectional)',
661 Gtk.STOCK_ZOOM_IN, 'zoomy', 1, True),
662 ('Zoom Out Y',
663 'Zoom Out Y (expand the y axis limits) with click or wheel'
664 ' mouse (bidirectional)',
665 Gtk.STOCK_ZOOM_OUT, 'zoomy', -1, True),
666 (None, None, None, None, None, None,),
667 ('Save', 'Save the figure',
668 Gtk.STOCK_SAVE, 'save_figure', None, False),
669 )
670
671 def __init__(self, canvas, window):
672 """
673 figManager is the FigureManagerGTK3 instance that contains the
674 toolbar, with attributes figure, window and drawingArea
675
676 """
677 GObject.GObject.__init__(self)
678
679 self.canvas = canvas
680 # Note: Gtk.Toolbar already has a 'window' attribute
681 self.win = window
682
683 self.set_style(Gtk.ToolbarStyle.ICONS)
684
685 self._create_toolitems()
686 self.update = self._update
687 self.fileselect = FileChooserDialog(
688 title='Save the figure',
689 parent=self.win,
690 filetypes=self.canvas.get_supported_filetypes(),
691 default_filetype=self.canvas.get_default_filetype())
692 self.show_all()
693 self.update()
694
695 def _create_toolitems(self):
696 iconSize = Gtk.IconSize.SMALL_TOOLBAR
697
698 for text, tooltip_text, image_num, callback, callback_arg, scroll \
699 in self.toolitems:
700 if text is None:
701 self.insert( Gtk.SeparatorToolItem(), -1 )
702 continue
703 image = Gtk.Image()
704 image.set_from_stock(image_num, iconSize)
705 tbutton = Gtk.ToolButton()
706 tbutton.set_label(text)
707 tbutton.set_icon_widget(image)
708 self.insert(tbutton, -1)
709 if callback_arg:
710 tbutton.connect('clicked', getattr(self, callback),
711 callback_arg)
712 else:
713 tbutton.connect('clicked', getattr(self, callback))
714 if scroll:
715 tbutton.connect('scroll_event', getattr(self, callback))
716 tbutton.set_tooltip_text(tooltip_text)
717
718 # Axes toolitem, is empty at start, update() adds a menu if >=2 axes
719 self.axes_toolitem = Gtk.ToolItem()
720 self.insert(self.axes_toolitem, 0)
721 self.axes_toolitem.set_tooltip_text(
722 'Select axes that controls affect')
723
724 align = Gtk.Alignment (xalign=0.5, yalign=0.5, xscale=0.0, yscale=0.0)
725 self.axes_toolitem.add(align)
726
727 self.menubutton = Gtk.Button ("Axes")
728 align.add (self.menubutton)
729
730 def position_menu (menu):
731 """Function for positioning a popup menu.
732 Place menu below the menu button, but ensure it does not go off
733 the bottom of the screen.
734 The default is to popup menu at current mouse position
735 """
736 x0, y0 = self.window.get_origin()
737 x1, y1, m = self.window.get_pointer()
738 x2, y2 = self.menubutton.get_pointer()
739 sc_h = self.get_screen().get_height()
740 w, h = menu.size_request()
741
742 x = x0 + x1 - x2
743 y = y0 + y1 - y2 + self.menubutton.allocation.height
744 y = min(y, sc_h - h)
745 return x, y, True
746
747 def button_clicked (button, data=None):
748 self.axismenu.popup (None, None, position_menu, 0,
749 Gtk.get_current_event_time())
750
751 self.menubutton.connect ("clicked", button_clicked)
752
753
754 def _update(self):
755 # called by __init__() and FigureManagerGTK3
756
757 self._axes = self.canvas.figure.axes
758
759 if len(self._axes) >= 2:
760 self.axismenu = self._make_axis_menu()
761 self.menubutton.show_all()
762 else:
763 self.menubutton.hide()
764
765 self.set_active(range(len(self._axes)))
766
767
768 def _make_axis_menu(self):
769 # called by self._update*()
770
771 def toggled(item, data=None):
772 if item == self.itemAll:
773 for item in items: item.set_active(True)
774 elif item == self.itemInvert:
775 for item in items:
776 item.set_active(not item.get_active())
777
778 ind = [i for i,item in enumerate(items) if item.get_active()]
779 self.set_active(ind)
780
781 menu = Gtk.Menu()
782
783 self.itemAll = Gtk.MenuItem("All")
784 menu.append(self.itemAll)
785 self.itemAll.connect("activate", toggled)
786
787 self.itemInvert = Gtk.MenuItem("Invert")
788 menu.append(self.itemInvert)
789 self.itemInvert.connect("activate", toggled)
790
791 items = []
792 for i in range(len(self._axes)):
793 item = Gtk.CheckMenuItem("Axis %d" % (i+1))
794 menu.append(item)
795 item.connect("toggled", toggled)
796 item.set_active(True)
797 items.append(item)
798
799 menu.show_all()
800 return menu
801
802
803 def set_active(self, ind):
804 self._ind = ind
805 self._active = [ self._axes[i] for i in self._ind ]
806
807 def panx(self, button, direction):
808 'panx in direction'
809
810 for a in self._active:
811 a.xaxis.pan(direction)
812 self.canvas.draw()
813 self._need_redraw = True
814 return True
815
816 def pany(self, button, direction):
817 'pany in direction'
818 for a in self._active:
819 a.yaxis.pan(direction)
820 self.canvas.draw()
821 return True
822
823 def zoomx(self, button, direction):
824 'zoomx in direction'
825 for a in self._active:
826 a.xaxis.zoom(direction)
827 self.canvas.draw()
828 return True
829
830 def zoomy(self, button, direction):
831 'zoomy in direction'
832 for a in self._active:
833 a.yaxis.zoom(direction)
834 self.canvas.draw()
835 return True
836
837 def get_filechooser(self):
838 return FileChooserDialog(
839 title='Save the figure',
840 parent=self.win,
841 filetypes=self.canvas.get_supported_filetypes(),
842 default_filetype=self.canvas.get_default_filetype())
843
844 def save_figure(self, *args):
845 fname, format = self.get_filechooser().get_filename_from_user()
846 if fname:
847 try:
848 self.canvas.print_figure(fname, format=format)
849 except Exception, e:
850 error_msg_gtk(str(e), parent=self)
851
852
853 class FileChooserDialog(Gtk.FileChooserDialog):
854 """GTK+ file selector which remembers the last file/directory
855 selected and presents the user with a menu of supported image formats
856 """
857 def __init__ (self,
858 title = 'Save file',
859 parent = None,
860 action = Gtk.FileChooserAction.SAVE,
861 buttons = (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
862 Gtk.STOCK_SAVE, Gtk.ResponseType.OK),
863 path = None,
864 filetypes = [],
865 default_filetype = None
866 ):
867 super (FileChooserDialog, self).__init__ (title, parent, action,
868 buttons)
869 self.set_default_response (Gtk.ResponseType.OK)
870
871 if not path: path = os.getcwd() + os.sep
872
873 # create an extra widget to list supported image formats
874 self.set_current_folder (path)
875 self.set_current_name ('image.' + default_filetype)
876
877 hbox = Gtk.Box(spacing=10)
878 hbox.pack_start(Gtk.Label(label="File Format:"), False, False, 0)
879
880 liststore = Gtk.ListStore(GObject.TYPE_STRING)
881 cbox = Gtk.ComboBox() #liststore)
882 cbox.set_model(liststore)
883 cell = Gtk.CellRendererText()
884 cbox.pack_start(cell, True)
885 cbox.add_attribute(cell, 'text', 0)
886 hbox.pack_start(cbox, False, False, 0)
887
888 self.filetypes = filetypes
889 self.sorted_filetypes = filetypes.items()
890 self.sorted_filetypes.sort()
891 default = 0
892 for i, (ext, name) in enumerate(self.sorted_filetypes):
893 liststore.append(["%s (*.%s)" % (name, ext)])
894 if ext == default_filetype:
895 default = i
896 cbox.set_active(default)
897 self.ext = default_filetype
898
899 def cb_cbox_changed (cbox, data=None):
900 """File extension changed"""
901 head, filename = os.path.split(self.get_filename())
902 root, ext = os.path.splitext(filename)
903 ext = ext[1:]
904 new_ext = self.sorted_filetypes[cbox.get_active()][0]
905 self.ext = new_ext
906
907 if ext in self.filetypes:
908 filename = root + '.' + new_ext
909 elif ext == '':
910 filename = filename.rstrip('.') + '.' + new_ext
911
912 self.set_current_name (filename)
913 cbox.connect ("changed", cb_cbox_changed)
914
915 hbox.show_all()
916 self.set_extra_widget(hbox)
917
918 def get_filename_from_user (self):
919 while True:
920 filename = None
921 if self.run() != int(Gtk.ResponseType.OK):
922 break
923 filename = self.get_filename()
924 break
925
926 self.hide()
927 return filename, self.ext
928
929 class DialogLineprops:
930 """
931 A GUI dialog for controlling lineprops
932 """
933 signals = (
934 'on_combobox_lineprops_changed',
935 'on_combobox_linestyle_changed',
936 'on_combobox_marker_changed',
937 'on_colorbutton_linestyle_color_set',
938 'on_colorbutton_markerface_color_set',
939 'on_dialog_lineprops_okbutton_clicked',
940 'on_dialog_lineprops_cancelbutton_clicked',
941 )
942
943 linestyles = [ls for ls in lines.Line2D.lineStyles if ls.strip()]
944 linestyled = dict([ (s,i) for i,s in enumerate(linestyles)])
945
946
947 markers = [m for m in lines.Line2D.markers if cbook.is_string_like(m)]
948
949 markerd = dict([(s,i) for i,s in enumerate(markers)])
950
951 def __init__(self, lines):
952 import Gtk.glade
953
954 datadir = matplotlib.get_data_path()
955 gladefile = os.path.join(datadir, 'lineprops.glade')
956 if not os.path.exists(gladefile):
957 raise IOError('Could not find gladefile lineprops.glade in %s'%datadir)
958
959 self._inited = False
960 self._updateson = True # suppress updates when setting widgets manually
961 self.wtree = Gtk.glade.XML(gladefile, 'dialog_lineprops')
962 self.wtree.signal_autoconnect(dict([(s, getattr(self, s)) for s in self.signals]))
963
964 self.dlg = self.wtree.get_widget('dialog_lineprops')
965
966 self.lines = lines
967
968 cbox = self.wtree.get_widget('combobox_lineprops')
969 cbox.set_active(0)
970 self.cbox_lineprops = cbox
971
972 cbox = self.wtree.get_widget('combobox_linestyles')
973 for ls in self.linestyles:
974 cbox.append_text(ls)
975 cbox.set_active(0)
976 self.cbox_linestyles = cbox
977
978 cbox = self.wtree.get_widget('combobox_markers')
979 for m in self.markers:
980 cbox.append_text(m)
981 cbox.set_active(0)
982 self.cbox_markers = cbox
983 self._lastcnt = 0
984 self._inited = True
985
986
987 def show(self):
988 'populate the combo box'
989 self._updateson = False
990 # flush the old
991 cbox = self.cbox_lineprops
992 for i in range(self._lastcnt-1,-1,-1):
993 cbox.remove_text(i)
994
995 # add the new
996 for line in self.lines:
997 cbox.append_text(line.get_label())
998 cbox.set_active(0)
999
1000 self._updateson = True
1001 self._lastcnt = len(self.lines)
1002 self.dlg.show()
1003
1004 def get_active_line(self):
1005 'get the active line'
1006 ind = self.cbox_lineprops.get_active()
1007 line = self.lines[ind]
1008 return line
1009
1010
1011 def get_active_linestyle(self):
1012 'get the active lineinestyle'
1013 ind = self.cbox_linestyles.get_active()
1014 ls = self.linestyles[ind]
1015 return ls
1016
1017 def get_active_marker(self):
1018 'get the active lineinestyle'
1019 ind = self.cbox_markers.get_active()
1020 m = self.markers[ind]
1021 return m
1022
1023 def _update(self):
1024 'update the active line props from the widgets'
1025 if not self._inited or not self._updateson: return
1026 line = self.get_active_line()
1027 ls = self.get_active_linestyle()
1028 marker = self.get_active_marker()
1029 line.set_linestyle(ls)
1030 line.set_marker(marker)
1031
1032 button = self.wtree.get_widget('colorbutton_linestyle')
1033 color = button.get_color()
1034 r, g, b = [val/65535. for val in color.red, color.green, color.blue]
1035 line.set_color((r,g,b))
1036
1037 button = self.wtree.get_widget('colorbutton_markerface')
1038 color = button.get_color()
1039 r, g, b = [val/65535. for val in color.red, color.green, color.blue]
1040 line.set_markerfacecolor((r,g,b))
1041
1042 line.figure.canvas.draw()
1043
1044
1045
1046 def on_combobox_lineprops_changed(self, item):
1047 'update the widgets from the active line'
1048 if not self._inited: return
1049 self._updateson = False
1050 line = self.get_active_line()
1051
1052 ls = line.get_linestyle()
1053 if ls is None: ls = 'None'
1054 self.cbox_linestyles.set_active(self.linestyled[ls])
1055
1056 marker = line.get_marker()
1057 if marker is None: marker = 'None'
1058 self.cbox_markers.set_active(self.markerd[marker])
1059
1060 r,g,b = colorConverter.to_rgb(line.get_color())
1061 color = Gdk.Color(*[int(val*65535) for val in r,g,b])
1062 button = self.wtree.get_widget('colorbutton_linestyle')
1063 button.set_color(color)
1064
1065 r,g,b = colorConverter.to_rgb(line.get_markerfacecolor())
1066 color = Gdk.Color(*[int(val*65535) for val in r,g,b])
1067 button = self.wtree.get_widget('colorbutton_markerface')
1068 button.set_color(color)
1069 self._updateson = True
1070
1071 def on_combobox_linestyle_changed(self, item):
1072 self._update()
1073
1074 def on_combobox_marker_changed(self, item):
1075 self._update()
1076
1077 def on_colorbutton_linestyle_color_set(self, button):
1078 self._update()
1079
1080 def on_colorbutton_markerface_color_set(self, button):
1081 'called colorbutton marker clicked'
1082 self._update()
1083
1084 def on_dialog_lineprops_okbutton_clicked(self, button):
1085 self._update()
1086 self.dlg.hide()
1087
1088 def on_dialog_lineprops_cancelbutton_clicked(self, button):
1089 self.dlg.hide()
1090
1091 # set icon used when windows are minimized
1092 try:
1093
1094 if sys.platform == 'win32':
1095 icon_filename = 'matplotlib.png'
1096 else:
1097 icon_filename = 'matplotlib.svg'
1098 window_icon = os.path.join(matplotlib.rcParams['datapath'], 'images', icon_filename)
1099 except:
1100 window_icon = None
1101 verbose.report('Could not load matplotlib icon: %s' % sys.exc_info()[1])
1102
1103 def error_msg_gtk(msg, parent=None):
1104 if parent is not None: # find the toplevel Gtk.Window
1105 parent = parent.get_toplevel()
1106 if not parent.is_toplevel():
1107 parent = None
1108
1109 if not is_string_like(msg):
1110 msg = ','.join(map(str,msg))
1111
1112 dialog = Gtk.MessageDialog(
1113 parent = parent,
1114 type = Gtk.MessageType.ERROR,
1115 buttons = Gtk.ButtonsType.OK,
1116 message_format = msg)
1117 dialog.run()
1118 dialog.destroy()
1119
1120
1121 FigureManager = FigureManagerGTK3

john.pye@anu.edu.au
ViewVC Help
Powered by ViewVC 1.1.22