Skip to content
This repository

Simple GUI interface enhancements #851

Merged
merged 12 commits into from almost 2 years ago

6 participants

Phil Elson Eric Firing Benjamin Root Thomas Hisch Christoph Buchner Jouni K. Seppänen
Phil Elson
Collaborator

In this pull request, I have:

  • Added a close figure key ("q" by default)
  • Fixed full screen toggle key ("f" by default)
  • Added full screen toggle capability to Tk backend
  • Added tooltips to the Tk backend, and factored other GUI backends to use the same definition.

I am unable to test on all backends (and operating systems), so there is the possibility that I have missed something very obvious, hence I have based this against master rather than 1.1.x.

Phil Elson pelson commented on the diff April 23, 2012
lib/matplotlib/backends/backend_qt.py
... ...
@@ -295,20 +295,6 @@ def set_window_title(self, title):
295 295
         self.window.setCaption(title)
296 296
 
297 297
 class NavigationToolbar2QT( NavigationToolbar2, qt.QWidget ):
298  
-    # list of toolitems to add to the toolbar, format is:
299  
-    # text, tooltip_text, image_file, callback(str)
300  
-    toolitems = (
301  
-        ('Home', 'Reset original view', 'home.ppm', 'home'),
302  
-        ('Back', 'Back to  previous view','back.ppm', 'back'),
303  
-        ('Forward', 'Forward to next view','forward.ppm', 'forward'),
304  
-        (None, None, None, None),
305  
-        ('Pan', 'Pan axes with left mouse, zoom with right', 'move.ppm', 'pan'),
306  
-        ('Zoom', 'Zoom to rectangle','zoom_to_rect.ppm', 'zoom'),
307  
-        (None, None, None, None),
308  
-        ('Subplots', 'Configure subplots','subplots.png', 'configure_subplots'),
3
Phil Elson Collaborator
pelson added a note April 23, 2012

The inconsistency with the image file has not been carried through to this change request. Does anyone know of a good reason why it might have had a different extension?

Benjamin Root Collaborator
WeatherGod added a note April 27, 2012

This might have actually been a recurring issue. There might be some mailing list threads about it.

Phil Elson Collaborator
pelson added a note April 27, 2012

Couldn't find anything. I tried searching for:

The changeset which introduced it cee76c6 seems to suggest it was just an extension rather than a bug fix. Since I was able to test this on my machine & it is consistent with the other image files, I suggest that this was worth highlighting, but not likely to cause any issues.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Phil Elson
Collaborator

Closes #830.

Phil Elson
Collaborator

I have now been able to test these changes with qt4agg, gtkagg and tkagg backends on a Linux (Ubuntu) install.

Eric Firing
Owner
Benjamin Root
Collaborator

Was 'q' already active on some backends? if not, then I think what we can do is keep this code, but set the default rcParam for quit to an empty list. This way, the mechanism is in-place rather than in some commented-out, hard-coded spots in some backends.

Otherwise, I think just about everything else makes great sense.

Phil Elson
Collaborator

Was 'q' already active on some backends? if not, then I think what we can do is keep this code, but set the default rcParam for quit to an empty list

No its not active in any backend as far as I can see. I personally will be enabling this feature (as I suspect will the original requester @birkenfield ), so maybe its worth taking a straw poll on the mpl-users mailing-list?

It would seem strange to me that all the other keyboard shortcuts are enabled by default, so maybe we could ask:

  • Who uses the keyboard shortcuts?
  • Would you re-enable them if we disabled them all by default?
  • Which shortcuts do you actually use? (for instance, the 'f' key hasn't worked on master for 2 months since @jdh2358's 0082466 changeset and no one has reported it)
  • If it were available would you use a 'q' keyboard short-cut to close a figure?

I don't really mind either way which way it goes, but I do think its worth thinking about consistency.

Phil Elson
Collaborator
pelson commented May 01, 2012

Just as an update, I will be away from a computer for a couple of weeks and wont have access to contribute and/or make adjustments to the pull.

Did anyone have any feeling as to whether asking the user community is the right thing to do? If it is not then I will remove the default "q" straight away so that this can be integrated onto master.

Thomas Hisch
thisch commented May 04, 2012

+1 especially for the "q" short-cut. I would prefer if it is turned on by default as I'm used to gnuplots behavior and the missing short-cut bothers me most in mpl.

Actually I tried to implement this a few weeks ago but I was not able to figure out how to destroy the figure. It's great that there even exists a PR for this!

Christoph Buchner

To contribute a (fresh) user's perspective here, I was pretty suprised/alarmed that I could not close the figure window with Ctrl-W on Ubuntu, and subsequently searched for a relevant bug report. Ctrl-W is the default shortcut for closing windows here, afaik. Ctrl-Q didn't work either. Alt-F4 worked, though, but this may be some Windows-compatibility thing recognized by Ubuntu?

I think the window should respond to platform-default shortcuts, but I don't know how complicated it would be to implement this. Failing that, just 'q' would be fine, too.

Benjamin Root
Collaborator

I think you touched on something important. FWIW, on my CentOS6 system with gnome 2 installed, Alt-F4 is the keyboard shortcut for closing a window (Ctrl-W and Ctrl-Q do nothing). The "close" event is largely a system-level idiom. 'q' is not a system default anywhere for closing a window. My opinion is now that 'q' should not be turned on as default. Maybe add a comment to the matplotlibrc.template that 'Alt-F4' should always work anyway?

Phil Elson
Collaborator
pelson commented May 21, 2012

I have made some changes which might make the behaviour more palatable. I have removed the q key and replaced it with both ctrl+w (as is standard in Ubuntu) and escape. The changes are currently not cross backend as I have only implemented the modifier keys in Tk. In summary:

  • keys pressed with a modifier will be combined in the KeyPress event. i.e. pressing shift + ctrl + q would trigger an event with a key value of of "ctrl+Q".
  • quit has bindings of ctrl+w and escape (perhaps a ctrl+q should be implemented and escape removed)
  • save has another binding (the one you would expect) of ctrl+s (the original "s" is not removed)
  • fullscreen has another binding of ctrl+f
  • I propose that all default key bindings become modifier triggered (however I have done nothing to progress this proposal as that would be a far bigger interface change)
  • modifier keys will require implementing in qt, gtk and wx

At this point I would like us to find a common ground since clearly the "quick close keyboard shortcut that isn't alt+F4" is desirable by several users (myself included), whilst simply implementing a "q" is seen as bad interface design by others (myself included). My proposed middle ground therefore is ctrl+w (and possibly escape and/or ctrl+q) for closing a figure.

@efiring & @WeatherGod does this go some way to improving the original proposal?

Eric Firing
Owner
efiring commented May 27, 2012

http://en.wikipedia.org/wiki/Table_of_keyboard_shortcuts
There seem to be two questions here. One is what shortcuts should be enabled by default, and the other is what key combinations should mpl be able to recognize for potential use as shortcuts.
Starting with the second question, I don't think there will be much controversy: so long as the complexity doesn't get out of hand, and it doesn't introduce bugs or other maintenance problems, enabling the recognition of key combinations in all backends is a Good Thing. It seems like the gui toolkits should make this trivial, but evidently they don't.
For the first question, moving away from the use of simple letters by default to trigger major changes is definitely good.

The first few times I ran into "f", it was pretty irritating; I was minding my own business, typing, and suddenly a plot window filled the screen. (I use focus follows mouse, so probably I had bumped the trackpad such that the cursor moved into a plot window while I was typing.) I didn't even know there were shortcuts, much less that they had been activated by default, so I did not know what had gone wrong, or how to undo it!
My preference would be to have all shortcuts be opt-in via editing of the matplotlibrc file, but I have learned to live with having some on by default, so as long as the situation is not made worse than it is at present, I may grumble occasionally but I won't yell and scream. I even occasionally use "g" to turn on a grid; a minor convenience.

Eric Firing
Owner
efiring commented May 27, 2012

Regarding key specifics: I strongly prefer that "escape" not be included as a default window closer.

Phil Elson
Collaborator
pelson commented May 28, 2012

Thanks Eric, I found your example of why single keystrokes were irritating very useful.

I have added a commit which removes the escape key shortcut, and works on improving some of the relevant documentation. Additionally, I have put in some TODOs on the backends that I am unable to run/test.

My intentions are to implement the complex keystroke changes (i.e. deprecating p in favour of ctrl+p) in another pull request once this has been put back on the master (as I think there is a lot of room for debate about sensible defaults).

I will need to get this, and another pull (#897), tested with a combination of backends and OSes and therefore will put a post on the devel mailing list asking for some help testing (or implementing the missing backend's) new features. Other than that, is the pull heading in the right direction? Are there code review type actions to be done before sending out the mpl-devel mailing list message?

lib/matplotlib/backend_bases.py
... ...
@@ -2262,8 +2264,13 @@ def key_press_handler(event, canvas, toolbar=None):
2262 2264
 
2263 2265
     # toggle fullscreen mode (default key 'f')
2264 2266
     if event.key in fullscreen_keys:
2265  
-        self.full_screen_toggle()
  2267
+        fig_manager = Gcf.get_active()
1
Phil Elson Collaborator
pelson added a note June 05, 2012

Use canvas.manager as per #919.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Phil Elson
Collaborator
pelson commented June 18, 2012

Note to self: Consider addressing #215 in this PR.

Phil Elson
Collaborator
pelson commented June 20, 2012

@jkseppan or other OSX developer: Would you mind having a look at this PR and running a couple of checks?

import matplotlib
matplotlib.use('TkAgg')
matplotlib.use('Gtk3')
matplotlib.use('Qt4')

import matplotlib.pyplot as plt

plt.plot(range(10))

def onkeyboard(event):
    print event.key

cid = plt.gcf().canvas.mpl_connect('key_press_event', onkeyboard)
plt.show()

And check that when pressing ctrl+w the window closes, pressing f the window full screens, and that the toolbar buttons are displaying correctly (other backends should all work as previously, but the ctrl+w shortcut wont be working.
For the Tk backend, please also hover over a toolbar icon and see if a tooltip appears.

Thanks,


@efiring and/or @WeatherGod, are you happy with this change?

Phil Elson
Collaborator
pelson commented June 24, 2012

I have been able to test this on both Linux (Gtk, Gtk3, wx, tk, pyqt4, pyside) and OSX (tk, gtk, pyside) and on each of the backends I get at least the desired ctrl+w functionality (on OSX, ctrl really is the control button, and not the cmd button).

Phil Elson
Collaborator
pelson commented June 24, 2012

Capability wise, this PR is now up to speed. Are there any further review actions/comments?

lib/matplotlib/backends/backend_tkagg.py
@@ -217,7 +230,15 @@ def filter_destroy(evt):
217 230
 
218 231
         self._master = master
219 232
         self._tkcanvas.focus_set()
220  
-
  233
+        
  234
+        if sys.platform == 'darwin':
  235
+            # to make a tkagg window pop up on top on osx, osascript can be used
  236
+            # this came from http://sourceforge.net/mailarchive/message.php?msg_id=23718545
  237
+            cmd = ("""/usr/bin/osascript -e 'tell app "Finder" to set """ + \
  238
+                    """frontmost of process "%s" to true'""") % \
1
Eric Firing Owner
efiring added a note June 24, 2012

Here and below, a couple matters of style:
First, please avoid backslash line continuations. Parentheses can be used instead.
Second, and less importantly, python has automatic string concatenation, so there is no need for the explicit addition when breaking a string across lines.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
lib/matplotlib/backends/backend_tkagg.py
@@ -217,7 +230,15 @@ def filter_destroy(evt):
217 230
 
218 231
         self._master = master
219 232
         self._tkcanvas.focus_set()
220  
-
  233
+        
  234
+        if sys.platform == 'darwin':
1
Eric Firing Owner
efiring added a note June 24, 2012

Just looking at this diff, it seems very odd to be calling an external script here--it makes it appear as if this script will be called, and will pop up a window, every time a figure is made with Tk on OS X. I presume this isn't actually the case...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Jouni K. Seppänen
Collaborator

Tested on OS X 10.6.8, Python 2.7.2, TkAgg: window closes on ctrl+w (and on cmd+w, which is the more natural Mac binding), fullscreens on f, toolbar icons look fine and have tooltips.

Jouni K. Seppänen
Collaborator

OS X 10.6.8, Python 2.7.3 installed via MacPorts:

TkAgg: crashes (likely not related to this change - I suspect the MacPorts Tkinter installation, since it works fine with my python.org Python)
Qt4Agg: fullscreens on f, closes on ctrl+w (not on cmd+w), icons look fine, no tooltips
Gtk3Agg: can't get pygobject installed properly, with some trying

OS X package management being what it is, I can't be sure that something I happen to have installed isn't interfering with some of the backends (though I'm using virtualenv to install packages under the MacPorts python). For proper testing, we should probably have a virtual machine image with all the correct dependencies installed, and a script that installs a git version of matplotlib from scratch.

Phil Elson
Collaborator
pelson commented July 01, 2012

@efiring: I've removed some the two blocks that you highlighted as they don't belong in this PR. I will re-consider them for another pull request (once I have figured out how to implement it properly).

I think this PR is in a state to merge now. Obviously, there is no way that we can test every combination of OS, Python version, backend etc., but any further issues that crop up we can deal with in future issues/PRs. Anyone disagree?

Eric Firing efiring merged commit 5b90a27 into from July 01, 2012
Eric Firing efiring closed this July 01, 2012
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
23  doc/users/navigation_toolbar.rst
Source Rendered
@@ -83,23 +83,24 @@ Navigation Keyboard Shortcuts
83 83
 
84 84
 The following table holds all the default keys, which can be overwritten by use of your matplotlibrc (#keymap.\*).
85 85
 
86  
-================================== ==============================================
  86
+================================== =================================================
87 87
 Command                            Keyboard Shortcut(s)
88  
-================================== ==============================================
  88
+================================== =================================================
89 89
 Home/Reset                         **h** or **r** or **home**
90 90
 Back                               **c** or **left arrow** or **backspace**
91 91
 Forward                            **v** or **right arrow**
92 92
 Pan/Zoom                           **p**
93 93
 Zoom-to-rect                       **o**
94  
-Save                               **s**
95  
-Toggle fullscreen                  **f**
96  
-Constrain pan/zoom to x axis       hold **x**
97  
-Constrain pan/zoom to y axis       hold **y**
98  
-Preserve aspect ratio              hold **CONTROL**
99  
-Toggle grid                        **g**
100  
-Toggle x axis scale (log/linear)   **L** or **k**
101  
-Toggle y axis scale (log/linear)   **l**
102  
-================================== ==============================================
  94
+Save                               **ctrl** + **s**
  95
+Toggle fullscreen                  **ctrl** + **f**
  96
+Close plot                         **ctrl** + **w**
  97
+Constrain pan/zoom to x axis       hold **x** when panning/zooming with mouse 
  98
+Constrain pan/zoom to y axis       hold **y** when panning/zooming with mouse
  99
+Preserve aspect ratio              hold **CONTROL** when panning/zooming with mouse
  100
+Toggle grid                        **g** when mouse is over an axes
  101
+Toggle x axis scale (log/linear)   **L** or **k**  when mouse is over an axes
  102
+Toggle y axis scale (log/linear)   **l** when mouse is over an axes
  103
+================================== =================================================
103 104
 
104 105
 If you are using :mod:`matplotlib.pyplot` the toolbar will be created
105 106
 automatically for every figure.  If you are writing your own user
165  lib/matplotlib/backend_bases.py
@@ -1190,6 +1190,7 @@ class CloseEvent(Event):
1190 1190
     def __init__(self, name, canvas, guiEvent=None):
1191 1191
         Event.__init__(self, name, canvas, guiEvent)
1192 1192
 
  1193
+
1193 1194
 class LocationEvent(Event):
1194 1195
     """
1195 1196
     An event that has a screen location
@@ -1295,9 +1296,6 @@ def _update_enter_leave(self):
1295 1296
         LocationEvent.lastevent = self
1296 1297
 
1297 1298
 
1298  
-
1299  
-
1300  
-
1301 1299
 class MouseEvent(LocationEvent):
1302 1300
     """
1303 1301
     A mouse event ('button_press_event', 'button_release_event', 'scroll_event',
@@ -1307,10 +1305,12 @@ class MouseEvent(LocationEvent):
1307 1305
     attributes, the following attributes are defined:
1308 1306
 
1309 1307
     *button*
1310  
-        button pressed None, 1, 2, 3, 'up', 'down' (up and down are used for scroll events)
  1308
+        button pressed None, 1, 2, 3, 'up', 'down' (up and down are used 
  1309
+        for scroll events)
1311 1310
 
1312 1311
     *key*
1313  
-        the key pressed: None, chr(range(255), 'shift', 'win', or 'control'
  1312
+        the key depressed when the mouse event triggered (see 
  1313
+        :class:`KeyEvent`)
1314 1314
 
1315 1315
     *step*
1316 1316
         number of scroll steps (positive for 'up', negative for 'down')
@@ -1319,7 +1319,7 @@ class MouseEvent(LocationEvent):
1319 1319
     Example usage::
1320 1320
 
1321 1321
         def on_press(event):
1322  
-            print 'you pressed', event.button, event.xdata, event.ydata
  1322
+            print('you pressed', event.button, event.xdata, event.ydata)
1323 1323
 
1324 1324
         cid = fig.canvas.mpl_connect('button_press_event', on_press)
1325 1325
 
@@ -1346,8 +1346,10 @@ def __init__(self, name, canvas, x, y, button=None, key=None,
1346 1346
         self.dblclick = dblclick
1347 1347
 
1348 1348
     def __str__(self):
1349  
-        return "MPL MouseEvent: xy=(%d,%d) xydata=(%s,%s) button=%d dblclick=%s inaxes=%s"%\
1350  
-             (self.x,self.y,str(self.xdata),str(self.ydata),self.button,self.dblclick,self.inaxes)
  1349
+        return ("MPL MouseEvent: xy=(%d,%d) xydata=(%s,%s) button=%d " +
  1350
+                "dblclick=%s inaxes=%s") % (self.x, self.y, self.xdata, 
  1351
+                self.ydata, self.button, self.dblclick, self.inaxes)
  1352
+
1351 1353
 
1352 1354
 class PickEvent(Event):
1353 1355
     """
@@ -1377,7 +1379,7 @@ def on_pick(event):
1377 1379
             thisline = event.artist
1378 1380
             xdata, ydata = thisline.get_data()
1379 1381
             ind = event.ind
1380  
-            print 'on pick line:', zip(xdata[ind], ydata[ind])
  1382
+            print('on pick line:', zip(xdata[ind], ydata[ind]))
1381 1383
 
1382 1384
         cid = fig.canvas.mpl_connect('pick_event', on_pick)
1383 1385
 
@@ -1400,16 +1402,23 @@ class KeyEvent(LocationEvent):
1400 1402
     attributes, the following attributes are defined:
1401 1403
 
1402 1404
     *key*
1403  
-        the key pressed: None, chr(range(255), shift, win, or control
1404  
-
1405  
-    This interface may change slightly when better support for
1406  
-    modifier keys is included.
1407  
-
1408  
-
  1405
+        the key(s) pressed. Could be **None**, a single case sensitive ascii
  1406
+        character ("g", "G", "#", etc.), a special key 
  1407
+        ("control", "shift", "f1", "up", etc.) or a 
  1408
+        combination of the above (e.g. "ctrl+alt+g", "ctrl+alt+G").
  1409
+    
  1410
+    .. note::
  1411
+     
  1412
+        Modifier keys will be prefixed to the pressed key and will be in the
  1413
+        order "ctrl", "alt", "super". The exception to this rule is when the
  1414
+        pressed key is itself a modifier key, therefore "ctrl+alt" and 
  1415
+        "alt+control" can both be valid key values.
  1416
+    
  1417
+         
1409 1418
     Example usage::
1410 1419
 
1411 1420
         def on_key(event):
1412  
-            print 'you pressed', event.key, event.xdata, event.ydata
  1421
+            print('you pressed', event.key, event.xdata, event.ydata)
1413 1422
 
1414 1423
         cid = fig.canvas.mpl_connect('key_press_event', on_key)
1415 1424
 
@@ -1419,7 +1428,6 @@ def __init__(self, name, canvas, key, x=0, y=0, guiEvent=None):
1419 1428
         self.key = key
1420 1429
 
1421 1430
 
1422  
-
1423 1431
 class FigureCanvasBase(object):
1424 1432
     """
1425 1433
     The canvas the figure renders into.
@@ -1448,7 +1456,6 @@ class FigureCanvasBase(object):
1448 1456
         'close_event'
1449 1457
         ]
1450 1458
 
1451  
-
1452 1459
     def __init__(self, figure):
1453 1460
         figure.set_canvas(self)
1454 1461
         self.figure = figure
@@ -1657,7 +1664,8 @@ def button_press_event(self, x, y, button, dblclick=False, guiEvent=None):
1657 1664
         """
1658 1665
         self._button = button
1659 1666
         s = 'button_press_event'
1660  
-        mouseevent = MouseEvent(s, self, x, y, button, self._key, dblclick=dblclick, guiEvent=guiEvent)
  1667
+        mouseevent = MouseEvent(s, self, x, y, button, self._key, 
  1668
+                                dblclick=dblclick, guiEvent=guiEvent)
1661 1669
         self.callbacks.process(s, mouseevent)
1662 1670
 
1663 1671
     def button_release_event(self, x, y, button, guiEvent=None):
@@ -1743,7 +1751,7 @@ def enter_notify_event(self, guiEvent=None, xy=None):
1743 1751
         self.callbacks.process('figure_enter_event', event)
1744 1752
 
1745 1753
     def idle_event(self, guiEvent=None):
1746  
-        'call when GUI is idle'
  1754
+        """Called when GUI is idle."""
1747 1755
         s = 'idle_event'
1748 1756
         event = IdleEvent(s, self, guiEvent=guiEvent)
1749 1757
         self.callbacks.process(s, event)
@@ -1789,7 +1797,7 @@ def draw_cursor(self, event):
1789 1797
 
1790 1798
     def get_width_height(self):
1791 1799
         """
1792  
-        return the figure width and height in points or pixels
  1800
+        Return the figure width and height in points or pixels
1793 1801
         (depending on the backend), truncated to integers
1794 1802
         """
1795 1803
         return int(self.figure.bbox.width), int(self.figure.bbox.height)
@@ -1911,7 +1919,6 @@ def get_supported_filetypes_grouped(self):
1911 1919
             groupings[name].sort()
1912 1920
         return groupings
1913 1921
 
1914  
-
1915 1922
     def _get_print_method(self, format):
1916 1923
         method_name = 'print_%s' % format
1917 1924
 
@@ -1936,7 +1943,6 @@ def _print_method(*args, **kwargs):
1936 1943
 
1937 1944
         return getattr(self, method_name)
1938 1945
 
1939  
-
1940 1946
     def print_figure(self, filename, dpi=None, facecolor='w', edgecolor='w',
1941 1947
                      orientation='portrait', format=None, **kwargs):
1942 1948
         """
@@ -2078,9 +2084,6 @@ def print_figure(self, filename, dpi=None, facecolor='w', edgecolor='w',
2078 2084
             #self.figure.canvas.draw() ## seems superfluous
2079 2085
         return result
2080 2086
 
2081  
-
2082  
-
2083  
-
2084 2087
     def get_default_filetype(self):
2085 2088
         """
2086 2089
         Get the default savefig file format as specified in rcParam
@@ -2099,7 +2102,7 @@ def set_window_title(self, title):
2099 2102
 
2100 2103
     def switch_backends(self, FigureCanvasClass):
2101 2104
         """
2102  
-        instantiate an instance of FigureCanvasClass
  2105
+        Instantiate an instance of FigureCanvasClass
2103 2106
 
2104 2107
         This is used for backend switching, eg, to instantiate a
2105 2108
         FigureCanvasPS from a FigureCanvasGTK.  Note, deep copying is
@@ -2148,7 +2151,7 @@ def func(event)
2148 2151
         Example usage::
2149 2152
 
2150 2153
             def on_press(event):
2151  
-                print 'you pressed', event.button, event.xdata, event.ydata
  2154
+                print('you pressed', event.button, event.xdata, event.ydata)
2152 2155
 
2153 2156
             cid = canvas.mpl_connect('button_press_event', on_press)
2154 2157
 
@@ -2158,7 +2161,7 @@ def on_press(event):
2158 2161
 
2159 2162
     def mpl_disconnect(self, cid):
2160 2163
         """
2161  
-        disconnect callback id cid
  2164
+        Disconnect callback id cid
2162 2165
 
2163 2166
         Example usage::
2164 2167
 
@@ -2274,9 +2277,6 @@ def key_press_handler(event, canvas, toolbar=None):
2274 2277
 
2275 2278
     """
2276 2279
     # these bindings happen whether you are over an axes or not
2277  
-    #if event.key == 'q':
2278  
-    #    self.destroy() # how cruel to have to destroy oneself!
2279  
-    #    return
2280 2280
 
2281 2281
     if event.key is None:
2282 2282
         return
@@ -2289,6 +2289,7 @@ def key_press_handler(event, canvas, toolbar=None):
2289 2289
     pan_keys = rcParams['keymap.pan']
2290 2290
     zoom_keys = rcParams['keymap.zoom']
2291 2291
     save_keys = rcParams['keymap.save']
  2292
+    quit_keys = rcParams['keymap.quit']
2292 2293
     grid_keys = rcParams['keymap.grid']
2293 2294
     toggle_yscale_keys = rcParams['keymap.yscale']
2294 2295
     toggle_xscale_keys = rcParams['keymap.xscale']
@@ -2298,6 +2299,10 @@ def key_press_handler(event, canvas, toolbar=None):
2298 2299
     if event.key in fullscreen_keys:
2299 2300
         canvas.manager.full_screen_toggle()
2300 2301
 
  2302
+    # quit the figure (defaut key 'ctrl+w')
  2303
+    if event.key in quit_keys:
  2304
+        Gcf.destroy_fig(canvas.figure)
  2305
+       
2301 2306
     if toolbar is not None:
2302 2307
         # home or reset mnemonic  (default key 'h', 'home' and 'r')
2303 2308
         if event.key in home_keys:
@@ -2322,7 +2327,8 @@ def key_press_handler(event, canvas, toolbar=None):
2322 2327
     if event.inaxes is None:
2323 2328
         return
2324 2329
 
2325  
-    # the mouse has to be over an axes to trigger these
  2330
+    # these bindings require the mouse to be over an axes to trigger
  2331
+
2326 2332
     # switching on/off a grid in current axes (default key 'g')
2327 2333
     if event.key in grid_keys:
2328 2334
         event.inaxes.grid()
@@ -2387,16 +2393,16 @@ def __init__(self, canvas, num):
2387 2393
     def destroy(self):
2388 2394
         pass
2389 2395
 
2390  
-    def full_screen_toggle (self):
  2396
+    def full_screen_toggle(self):
2391 2397
         pass
2392 2398
 
2393 2399
     def resize(self, w, h):
2394  
-        'For gui backends: resize window in pixels'
  2400
+        """"For gui backends, resize the window (in pixels)."""
2395 2401
         pass
2396 2402
 
2397 2403
     def key_press(self, event):
2398 2404
         """
2399  
-        implement the default mpl key bindings defined at
  2405
+        Implement the default mpl key bindings defined at
2400 2406
         :ref:`key-event-handling`
2401 2407
         """
2402 2408
         key_press_handler(event, self.canvas, self.canvas.toolbar)
@@ -2414,13 +2420,13 @@ def set_window_title(self, title):
2414 2420
         """
2415 2421
         pass
2416 2422
 
2417  
-# cursors
2418  
-class Cursors:  #namespace
  2423
+
  2424
+class Cursors:
  2425
+    # this class is only used as a simple namespace
2419 2426
     HAND, POINTER, SELECT_REGION, MOVE = range(4)
2420 2427
 cursors = Cursors()
2421 2428
 
2422 2429
 
2423  
-
2424 2430
 class NavigationToolbar2(object):
2425 2431
     """
2426 2432
     Base class for the navigation cursor, version 2
@@ -2464,6 +2470,25 @@ class NavigationToolbar2(object):
2464 2470
 
2465 2471
     That's it, we'll do the rest!
2466 2472
     """
  2473
+    
  2474
+    # list of toolitems to add to the toolbar, format is:
  2475
+    # (
  2476
+    #   text, # the text of the button (often not visible to users)
  2477
+    #   tooltip_text, # the tooltip shown on hover (where possible)
  2478
+    #   image_file, # name of the image for the button (without the extension) 
  2479
+    #   name_of_method, # name of the method in NavigationToolbar2 to call
  2480
+    # )
  2481
+    toolitems = (
  2482
+        ('Home', 'Reset original view', 'home', 'home'),
  2483
+        ('Back', 'Back to  previous view', 'back', 'back'),
  2484
+        ('Forward', 'Forward to next view', 'forward', 'forward'),
  2485
+        (None, None, None, None),
  2486
+        ('Pan', 'Pan axes with left mouse, zoom with right', 'move', 'pan'),
  2487
+        ('Zoom', 'Zoom to rectangle', 'zoom_to_rect', 'zoom'),
  2488
+        (None, None, None, None),
  2489
+        ('Subplots', 'Configure subplots', 'subplots', 'configure_subplots'),
  2490
+        ('Save', 'Save the figure', 'filesave', 'save_figure'),
  2491
+        )
2467 2492
 
2468 2493
     def __init__(self, canvas):
2469 2494
         self.canvas = canvas
@@ -2488,11 +2513,11 @@ def __init__(self, canvas):
2488 2513
         self.set_history_buttons()
2489 2514
 
2490 2515
     def set_message(self, s):
2491  
-        'display a message on toolbar or in status bar'
  2516
+        """Display a message on toolbar or in status bar"""
2492 2517
         pass
2493 2518
 
2494 2519
     def back(self, *args):
2495  
-        'move back up the view lim stack'
  2520
+        """move back up the view lim stack"""
2496 2521
         self._views.back()
2497 2522
         self._positions.back()
2498 2523
         self.set_history_buttons()
@@ -2502,18 +2527,18 @@ def dynamic_update(self):
2502 2527
         pass
2503 2528
 
2504 2529
     def draw_rubberband(self, event, x0, y0, x1, y1):
2505  
-        'draw a rectangle rubberband to indicate zoom limits'
  2530
+        """Draw a rectangle rubberband to indicate zoom limits"""
2506 2531
         pass
2507 2532
 
2508 2533
     def forward(self, *args):
2509  
-        'move forward in the view lim stack'
  2534
+        """Move forward in the view lim stack"""
2510 2535
         self._views.forward()
2511 2536
         self._positions.forward()
2512 2537
         self.set_history_buttons()
2513 2538
         self._update_view()
2514 2539
 
2515 2540
     def home(self, *args):
2516  
-        'restore the original view'
  2541
+        """Restore the original view"""
2517 2542
         self._views.home()
2518 2543
         self._positions.home()
2519 2544
         self.set_history_buttons()
@@ -2542,8 +2567,6 @@ class implementation.
2542 2567
         raise NotImplementedError
2543 2568
 
2544 2569
     def mouse_move(self, event):
2545  
-        #print 'mouse_move', event.button
2546  
-
2547 2570
         if not event.inaxes or not self._active:
2548 2571
             if self._lastCursor != cursors.POINTER:
2549 2572
                 self.set_cursor(cursors.POINTER)
@@ -2561,9 +2584,10 @@ def mouse_move(self, event):
2561 2584
 
2562 2585
         if event.inaxes and event.inaxes.get_navigate():
2563 2586
 
2564  
-            try: s = event.inaxes.format_coord(event.xdata, event.ydata)
2565  
-            except ValueError: pass
2566  
-            except OverflowError: pass
  2587
+            try: 
  2588
+                s = event.inaxes.format_coord(event.xdata, event.ydata)
  2589
+            except (ValueError, OverflowError):
  2590
+                pass
2567 2591
             else:
2568 2592
                 if len(self.mode):
2569 2593
                     self.set_message('%s, %s' % (self.mode, s))
@@ -2572,7 +2596,7 @@ def mouse_move(self, event):
2572 2596
         else: self.set_message(self.mode)
2573 2597
 
2574 2598
     def pan(self,*args):
2575  
-        'Activate the pan/zoom tool. pan with left button, zoom with right'
  2599
+        """Activate the pan/zoom tool. pan with left button, zoom with right"""
2576 2600
         # set the pointer icon and button press funcs to the
2577 2601
         # appropriate callbacks
2578 2602
 
@@ -2604,11 +2628,11 @@ def pan(self,*args):
2604 2628
         self.set_message(self.mode)
2605 2629
 
2606 2630
     def press(self, event):
2607  
-        'this will be called whenver a mouse button is pressed'
  2631
+        """Called whenver a mouse button is pressed."""
2608 2632
         pass
2609 2633
 
2610 2634
     def press_pan(self, event):
2611  
-        'the press mouse button in pan/zoom mode callback'
  2635
+        """the press mouse button in pan/zoom mode callback"""
2612 2636
 
2613 2637
         if event.button == 1:
2614 2638
             self._button_pressed=1
@@ -2636,7 +2660,7 @@ def press_pan(self, event):
2636 2660
         self.press(event)
2637 2661
 
2638 2662
     def press_zoom(self, event):
2639  
-        'the press mouse button in zoom to rect mode callback'
  2663
+        """the press mouse button in zoom to rect mode callback"""
2640 2664
         if event.button == 1:
2641 2665
             self._button_pressed=1
2642 2666
         elif  event.button == 3:
@@ -2658,17 +2682,14 @@ def press_zoom(self, event):
2658 2682
                                        a.transData.frozen() ))
2659 2683
 
2660 2684
         id1 = self.canvas.mpl_connect('motion_notify_event', self.drag_zoom)
2661  
-
2662 2685
         id2 = self.canvas.mpl_connect('key_press_event',
2663 2686
                                       self._switch_on_zoom_mode)
2664 2687
         id3 = self.canvas.mpl_connect('key_release_event',
2665 2688
                                       self._switch_off_zoom_mode)
2666 2689
 
2667 2690
         self._ids_zoom = id1, id2, id3
2668  
-
2669 2691
         self._zoom_mode = event.key
2670 2692
 
2671  
-
2672 2693
         self.press(event)
2673 2694
 
2674 2695
     def _switch_on_zoom_mode(self, event):
@@ -2680,7 +2701,7 @@ def _switch_off_zoom_mode(self, event):
2680 2701
         self.mouse_move(event)
2681 2702
 
2682 2703
     def push_current(self):
2683  
-        'push the current view limits and position onto the stack'
  2704
+        """push the current view limits and position onto the stack"""
2684 2705
         lims = []; pos = []
2685 2706
         for a in self.canvas.figure.get_axes():
2686 2707
             xmin, xmax = a.get_xlim()
@@ -2695,11 +2716,11 @@ def push_current(self):
2695 2716
         self.set_history_buttons()
2696 2717
 
2697 2718
     def release(self, event):
2698  
-        'this will be called whenever mouse button is released'
  2719
+        """this will be called whenever mouse button is released"""
2699 2720
         pass
2700 2721
 
2701 2722
     def release_pan(self, event):
2702  
-        'the release mouse button callback in pan/zoom mode'
  2723
+        """the release mouse button callback in pan/zoom mode"""
2703 2724
 
2704 2725
         if self._button_pressed is None:
2705 2726
             return
@@ -2715,7 +2736,7 @@ def release_pan(self, event):
2715 2736
         self.draw()
2716 2737
 
2717 2738
     def drag_pan(self, event):
2718  
-        'the drag callback in pan/zoom mode'
  2739
+        """the drag callback in pan/zoom mode"""
2719 2740
 
2720 2741
         for a, ind in self._xypress:
2721 2742
             #safer to use the recorded button at the press than current button:
@@ -2724,7 +2745,7 @@ def drag_pan(self, event):
2724 2745
         self.dynamic_update()
2725 2746
 
2726 2747
     def drag_zoom(self, event):
2727  
-        'the drag callback in zoom mode'
  2748
+        """the drag callback in zoom mode"""
2728 2749
 
2729 2750
         if self._xypress:
2730 2751
             x, y = event.x, event.y
@@ -2744,10 +2765,8 @@ def drag_zoom(self, event):
2744 2765
 
2745 2766
             self.draw_rubberband(event, x, y, lastx, lasty)
2746 2767
 
2747  
-
2748  
-
2749 2768
     def release_zoom(self, event):
2750  
-        'the release mouse button callback in zoom to rect mode'
  2769
+        """the release mouse button callback in zoom to rect mode"""
2751 2770
         for zoom_id in self._ids_zoom:
2752 2771
             self.canvas.mpl_disconnect(zoom_id)
2753 2772
         self._ids_zoom = []
@@ -2855,7 +2874,7 @@ def release_zoom(self, event):
2855 2874
         self.release(event)
2856 2875
 
2857 2876
     def draw(self):
2858  
-        'redraw the canvases, update the locators'
  2877
+        """Redraw the canvases, update the locators"""
2859 2878
         for a in self.canvas.figure.get_axes():
2860 2879
             xaxis = getattr(a, 'xaxis', None)
2861 2880
             yaxis = getattr(a, 'yaxis', None)
@@ -2871,12 +2890,10 @@ def draw(self):
2871 2890
                 loc.refresh()
2872 2891
         self.canvas.draw()
2873 2892
 
2874  
-
2875  
-
2876 2893
     def _update_view(self):
2877  
-        '''update the viewlim and position from the view and
  2894
+        """Update the viewlim and position from the view and
2878 2895
         position stack for each axes
2879  
-        '''
  2896
+        """
2880 2897
 
2881 2898
         lims = self._views()
2882 2899
         if lims is None:  return
@@ -2892,9 +2909,8 @@ def _update_view(self):
2892 2909
 
2893 2910
         self.draw()
2894 2911
 
2895  
-
2896 2912
     def save_figure(self, *args):
2897  
-        'save the current figure'
  2913
+        """Save the current figure"""
2898 2914
         raise NotImplementedError
2899 2915
 
2900 2916
     def set_cursor(self, cursor):
@@ -2905,13 +2921,13 @@ def set_cursor(self, cursor):
2905 2921
         pass
2906 2922
 
2907 2923
     def update(self):
2908  
-        'reset the axes stack'
  2924
+        """Reset the axes stack"""
2909 2925
         self._views.clear()
2910 2926
         self._positions.clear()
2911 2927
         self.set_history_buttons()
2912 2928
 
2913 2929
     def zoom(self, *args):
2914  
-        'activate zoom to rect mode'
  2930
+        """Activate zoom to rect mode"""
2915 2931
         if self._active == 'ZOOM':
2916 2932
             self._active = None
2917 2933
         else:
@@ -2938,7 +2954,6 @@ def zoom(self, *args):
2938 2954
 
2939 2955
         self.set_message(self.mode)
2940 2956
 
2941  
-
2942 2957
     def set_history_buttons(self):
2943  
-        'enable or disable back/forward button'
  2958
+        """Enable or disable back/forward button"""
2944 2959
         pass
2  lib/matplotlib/backends/backend_fltkagg.py
@@ -136,6 +136,8 @@ def handle(self, event):
136 136
                     self._key=special_key[ikey]
137 137
                 except:
138 138
                     self._key=None
  139
+            
  140
+            # TODO: Handle ctrl, alt, super modifiers.
139 141
             FigureCanvasBase.key_press_event(self._source, self._key)
140 142
             return 1
141 143
         elif event == Fltk.FL_KEYUP:
35  lib/matplotlib/backends/backend_gtk.py
@@ -189,6 +189,10 @@ class FigureCanvasGTK (gtk.DrawingArea, FigureCanvasBase):
189 189
                65455 : '/',
190 190
                65439 : 'dec',
191 191
                65421 : 'enter',
  192
+               65511 : 'super',
  193
+               65512 : 'super',
  194
+               65406 : 'alt',
  195
+               65289 : 'tab',
192 196
                }
193 197
 
194 198
     # Setting this as a static constant prevents
@@ -322,16 +326,20 @@ def enter_notify_event(self, widget, event):
322 326
     def _get_key(self, event):
323 327
         if event.keyval in self.keyvald:
324 328
             key = self.keyvald[event.keyval]
325  
-        elif event.keyval <256:
  329
+        elif event.keyval < 256:
326 330
             key = chr(event.keyval)
327 331
         else:
328 332
             key = None
329  
-
330  
-        ctrl  = event.state & gdk.CONTROL_MASK
331  
-        shift = event.state & gdk.SHIFT_MASK
  333
+            
  334
+        for key_mask, prefix in (
  335
+                                 [gdk.MOD4_MASK, 'super'],
  336
+                                 [gdk.MOD1_MASK, 'alt'], 
  337
+                                 [gdk.CONTROL_MASK, 'ctrl'],):
  338
+            if event.state & key_mask:
  339
+                key = '{}+{}'.format(prefix, key)
  340
+        
332 341
         return key
333 342
 
334  
-
335 343
     def configure_event(self, widget, event):
336 344
         if _debug: print('FigureCanvasGTK.%s' % fn_name())
337 345
         if widget.window is None:
@@ -592,7 +600,7 @@ def show(self):
592 600
         # show the figure window
593 601
         self.window.show()
594 602
 
595  
-    def full_screen_toggle (self):
  603
+    def full_screen_toggle(self):
596 604
         self._full_screen_flag = not self._full_screen_flag
597 605
         if self._full_screen_flag:
598 606
             self.window.fullscreen()
@@ -624,19 +632,6 @@ def resize(self, width, height):
624 632
 
625 633
 
626 634
 class NavigationToolbar2GTK(NavigationToolbar2, gtk.Toolbar):
627  
-    # list of toolitems to add to the toolbar, format is:
628  
-    # text, tooltip_text, image_file, callback(str)
629  
-    toolitems = (
630  
-        ('Home', 'Reset original view', 'home.png', 'home'),
631  
-        ('Back', 'Back to  previous view','back.png', 'back'),
632  
-        ('Forward', 'Forward to next view','forward.png', 'forward'),
633  
-        ('Pan', 'Pan axes with left mouse, zoom with right', 'move.png','pan'),
634  
-        ('Zoom', 'Zoom to rectangle','zoom_to_rect.png', 'zoom'),
635  
-        (None, None, None, None),
636  
-        ('Subplots', 'Configure subplots','subplots.png', 'configure_subplots'),
637  
-        ('Save', 'Save the figure','filesave.png', 'save_figure'),
638  
-        )
639  
-
640 635
     def __init__(self, canvas, window):
641 636
         self.win = window
642 637
         gtk.Toolbar.__init__(self)
@@ -704,7 +699,7 @@ def _init_toolbar2_4(self):
704 699
             if text is None:
705 700
                 self.insert( gtk.SeparatorToolItem(), -1 )
706 701
                 continue
707  
-            fname = os.path.join(basedir, image_file)
  702
+            fname = os.path.join(basedir, image_file + '.png')
708 703
             image = gtk.Image()
709 704
             image.set_from_file(fname)
710 705
             tbutton = gtk.ToolButton(image, text)
29  lib/matplotlib/backends/backend_gtk3.py
@@ -259,15 +259,21 @@ def enter_notify_event(self, widget, event):
259 259
     def _get_key(self, event):
260 260
         if event.keyval in self.keyvald:
261 261
             key = self.keyvald[event.keyval]
262  
-        elif event.keyval <256:
  262
+        elif event.keyval < 256:
263 263
             key = chr(event.keyval)
264 264
         else:
265 265
             key = None
266 266
 
267  
-        #ctrl  = event.get_state() & Gdk.EventMask.CONTROL
268  
-        #shift = event.get_state() & Gdk.EventMask.SHIFT
269  
-        return key
  267
+        modifiers = [
  268
+                     (Gdk.ModifierType.MOD4_MASK, 'super'),
  269
+                     (Gdk.ModifierType.MOD1_MASK, 'alt'),
  270
+                     (Gdk.ModifierType.CONTROL_MASK, 'ctrl'),
  271
+                    ]
  272
+        for key_mask, prefix in modifiers:
  273
+            if event.state & key_mask:
  274
+                key = '{}+{}'.format(prefix, key)
270 275
 
  276
+        return key
271 277
 
272 278
     def configure_event(self, widget, event):
273 279
         if _debug: print 'FigureCanvasGTK3.%s' % fn_name()
@@ -453,19 +459,6 @@ def resize(self, width, height):
453 459
 
454 460
 
455 461
 class NavigationToolbar2GTK3(NavigationToolbar2, Gtk.Toolbar):
456  
-    # list of toolitems to add to the toolbar, format is:
457  
-    # text, tooltip_text, image_file, callback(str)
458  
-    toolitems = (
459  
-        ('Home', 'Reset original view', 'home.png', 'home'),
460  
-        ('Back', 'Back to  previous view','back.png', 'back'),
461  
-        ('Forward', 'Forward to next view','forward.png', 'forward'),
462  
-        ('Pan', 'Pan axes with left mouse, zoom with right', 'move.png','pan'),
463  
-        ('Zoom', 'Zoom to rectangle','zoom_to_rect.png', 'zoom'),
464  
-        (None, None, None, None),
465  
-        ('Subplots', 'Configure subplots','subplots.png', 'configure_subplots'),
466  
-        ('Save', 'Save the figure','filesave.png', 'save_figure'),
467  
-        )
468  
-
469 462
     def __init__(self, canvas, window):
470 463
         self.win = window
471 464
         GObject.GObject.__init__(self)
@@ -516,7 +509,7 @@ def _init_toolbar(self):
516 509
             if text is None:
517 510
                 self.insert( Gtk.SeparatorToolItem(), -1 )
518 511
                 continue
519  
-            fname = os.path.join(basedir, image_file)
  512
+            fname = os.path.join(basedir, image_file + '.png')
520 513
             image = Gtk.Image()
521 514
             image.set_from_file(fname)
522 515
             tbutton = Gtk.ToolButton()
12  lib/matplotlib/backends/backend_macosx.py
@@ -20,12 +20,13 @@
20 20
 import matplotlib
21 21
 from matplotlib.backends import _macosx
22 22
 
  23
+
23 24
 class Show(ShowBase):
24 25
     def mainloop(self):
25 26
         _macosx.show()
26  
-
27 27
 show = Show()
28 28
 
  29
+
29 30
 class RendererMac(RendererBase):
30 31
     """
31 32
     The renderer handles drawing/rendering operations. Most of the renderer's
@@ -136,7 +137,7 @@ def _draw_mathtext(self, gc, x, y, s, prop, angle):
136 137
 
137 138
     def draw_text(self, gc, x, y, s, prop, angle, ismath=False):
138 139
         if ismath:
139  
-           self._draw_mathtext(gc, x, y, s, prop, angle)
  140
+            self._draw_mathtext(gc, x, y, s, prop, angle)
140 141
         else:
141 142
             family =  prop.get_family()
142 143
             weight = prop.get_weight()
@@ -174,6 +175,7 @@ def points_to_pixels(self, points):
174 175
     def option_image_nocomposite(self):
175 176
         return True
176 177
 
  178
+
177 179
 class GraphicsContextMac(_macosx.GraphicsContext, GraphicsContextBase):
178 180
     """
179 181
     The GraphicsContext wraps a Quartz graphics context. All methods
@@ -211,6 +213,7 @@ def set_clip_path(self, path):
211 213
         path = path.get_fully_transformed_path()
212 214
         _macosx.GraphicsContext.set_clip_path(self, path)
213 215
 
  216
+
214 217
 ########################################################################
215 218
 #
216 219
 # The following functions and classes are for pylab and implement
@@ -245,6 +248,7 @@ def new_figure_manager(num, *args, **kwargs):
245 248
     manager = FigureManagerMac(canvas, num)
246 249
     return manager
247 250
 
  251
+
248 252
 class TimerMac(_macosx.Timer, TimerBase):
249 253
     '''
250 254
     Subclass of :class:`backend_bases.TimerBase` that uses CoreFoundation
@@ -262,7 +266,6 @@ class TimerMac(_macosx.Timer, TimerBase):
262 266
     # completely implemented at the C-level (in _macosx.Timer)
263 267
 
264 268
 
265  
-
266 269
 class FigureCanvasMac(_macosx.FigureCanvas, FigureCanvasBase):
267 270
     """
268 271
     The canvas the figure renders into.  Calls the draw and print fig
@@ -346,6 +349,7 @@ def new_timer(self, *args, **kwargs):
346 349
         """
347 350
         return TimerMac(*args, **kwargs)
348 351
 
  352
+
349 353
 class FigureManagerMac(_macosx.FigureManager, FigureManagerBase):
350 354
     """
351 355
     Wrap everything up into a window for the pylab interface
@@ -377,6 +381,7 @@ def notify_axes_change(fig):
377 381
     def close(self):
378 382
         Gcf.destroy(self.num)
379 383
 
  384
+
380 385
 class NavigationToolbarMac(_macosx.NavigationToolbar):
381 386
 
382 387
     def __init__(self, canvas):
@@ -444,6 +449,7 @@ def save_figure(self, *args):
444 449
             return
445 450
         self.canvas.print_figure(filename)
446 451
 
  452
+
447 453
 class NavigationToolbar2Mac(_macosx.NavigationToolbar2, NavigationToolbar2):
448 454
 
449 455
     def __init__(self, canvas):
20  lib/matplotlib/backends/backend_qt.py
@@ -181,6 +181,8 @@ def _get_key( self, event ):
181 181
         else:
182 182
             key = None
183 183
 
  184
+        # TODO: Handle ctrl, alt, super modifiers. qt4 backend has implemented.
  185
+
184 186
         return key
185 187
 
186 188
     def flush_events(self):
@@ -295,20 +297,6 @@ def set_window_title(self, title):
295 297
         self.window.setCaption(title)
296 298
 
297 299
 class NavigationToolbar2QT( NavigationToolbar2, qt.QWidget ):
298  
-    # list of toolitems to add to the toolbar, format is:
299  
-    # text, tooltip_text, image_file, callback(str)
300  
-    toolitems = (
301  
-        ('Home', 'Reset original view', 'home.ppm', 'home'),
302  
-        ('Back', 'Back to  previous view','back.ppm', 'back'),
303  
-        ('Forward', 'Forward to next view','forward.ppm', 'forward'),
304  
-        (None, None, None, None),
305  
-        ('Pan', 'Pan axes with left mouse, zoom with right', 'move.ppm', 'pan'),
306  
-        ('Zoom', 'Zoom to rectangle','zoom_to_rect.ppm', 'zoom'),
307  
-        (None, None, None, None),
308  
-        ('Subplots', 'Configure subplots','subplots.png', 'configure_subplots'),
309  
-        ('Save', 'Save the figure','filesave.ppm', 'save_figure'),
310  
-        )
311  
-
312 300
     def __init__( self, canvas, parent ):
313 301
         self.canvas = canvas
314 302
         self.buttons = {}
@@ -329,8 +317,8 @@ def _init_toolbar( self ):
329 317
                 self.layout.addSpacing( 8 )
330 318
                 continue
331 319
 
332  
-            fname = os.path.join( basedir, image_file )
333  
-            image = qt.QPixmap()
  320
+            fname = os.path.join(basedir, image_file + '.ppm')
  321
+            image = qt.QPixmap() 
334 322
             image.load( fname )
335 323
 
336 324
             button = qt.QPushButton( qt.QIconSet( image ), "", self )
99  lib/matplotlib/backends/backend_qt4.py
... ...
@@ -1,6 +1,7 @@
1 1
 from __future__ import division, print_function
2 2
 import math
3 3
 import os
  4
+import signal
4 5
 import sys
5 6
 
6 7
 import matplotlib
@@ -60,8 +61,10 @@ def _create_qApp():
60 61
 
61 62
 class Show(ShowBase):
62 63
     def mainloop(self):
63  
-        QtGui.qApp.exec_()
  64
+        # allow KeyboardInterrupt exceptions to close the plot window.
  65
+        signal.signal(signal.SIGINT, signal.SIG_DFL)
64 66
 
  67
+        QtGui.qApp.exec_()
65 68
 show = Show()
66 69
 
67 70
 
@@ -120,6 +123,7 @@ class FigureCanvasQT( QtGui.QWidget, FigureCanvasBase ):
120 123
     keyvald = { QtCore.Qt.Key_Control : 'control',
121 124
                 QtCore.Qt.Key_Shift : 'shift',
122 125
                 QtCore.Qt.Key_Alt : 'alt',
  126
+                QtCore.Qt.Key_Meta : 'super',
123 127
                 QtCore.Qt.Key_Return : 'enter',
124 128
                 QtCore.Qt.Key_Left : 'left',
125 129
                 QtCore.Qt.Key_Up : 'up',
@@ -143,6 +147,33 @@ class FigureCanvasQT( QtGui.QWidget, FigureCanvasBase ):
143 147
                 QtCore.Qt.Key_PageUp : 'pageup',
144 148
                 QtCore.Qt.Key_PageDown : 'pagedown',
145 149
                }
  150
+
  151
+    # define the modifier keys which are to be collected on keyboard events.
  152
+    # format is: [(modifier_flag, modifier_name, equivalent_key)
  153
+    _modifier_keys = [
  154
+                      (QtCore.Qt.MetaModifier, 'super', QtCore.Qt.Key_Meta),
  155
+                      (QtCore.Qt.AltModifier, 'alt', QtCore.Qt.Key_Alt),
  156
+                      (QtCore.Qt.ControlModifier, 'ctrl', QtCore.Qt.Key_Control) 
  157
+                      ]
  158
+    
  159
+    _ctrl_modifier = QtCore.Qt.ControlModifier
  160
+    
  161
+    if sys.platform == 'darwin':
  162
+        # in OSX, the control and super (aka cmd/apple) keys are switched, so 
  163
+        # switch them back.
  164
+        keyvald.update({
  165
+                        QtCore.Qt.Key_Control : 'super', # cmd/apple key
  166
+                        QtCore.Qt.Key_Meta : 'control',
  167
+                        })
  168
+        
  169
+        _modifier_keys = [
  170
+                          (QtCore.Qt.ControlModifier, 'super', QtCore.Qt.Key_Control),
  171
+                          (QtCore.Qt.AltModifier, 'alt', QtCore.Qt.Key_Alt),
  172
+                          (QtCore.Qt.MetaModifier, 'ctrl', QtCore.Qt.Key_Meta),
  173
+                         ]
  174
+        
  175
+        _ctrl_modifier = QtCore.Qt.MetaModifier
  176
+
146 177
     # map Qt button codes to MouseEvent's ones:
147 178
     buttond = {QtCore.Qt.LeftButton  : 1,
148 179
                QtCore.Qt.MidButton   : 2,
@@ -150,6 +181,7 @@ class FigureCanvasQT( QtGui.QWidget, FigureCanvasBase ):
150 181
                # QtCore.Qt.XButton1 : None,
151 182
                # QtCore.Qt.XButton2 : None,
152 183
                }
  184
+
153 185
     def __init__( self, figure ):
154 186
         if DEBUG: print('FigureCanvasQt: ', figure)
155 187
         _create_qApp()
@@ -268,12 +300,32 @@ def minumumSizeHint( self ):
268 300
     def _get_key( self, event ):
269 301
         if event.isAutoRepeat():
270 302
             return None
  303
+
271 304
         if event.key() < 256:
272  
-            key = str(event.text())
273  
-        elif event.key() in self.keyvald:
274  
-            key = self.keyvald[ event.key() ]
  305
+            key = unicode(event.text())
  306
+            # if the control key is being pressed, we don't get the correct
  307
+            # characters, so interpret them directly from the event.key(). 
  308
+            # Unfortunately, this means that we cannot handle key's case 
  309
+            # since event.key() is not case sensitive, whereas event.text() is,
  310
+            # Finally, since it is not possible to get the CapsLock state
  311
+            # we cannot accurately compute the case of a pressed key when 
  312
+            # ctrl+shift+p is pressed.
  313