Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Simple GUI interface enhancements #851

Merged
merged 12 commits into from

6 participants

@pelson
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.

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

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?

@WeatherGod Collaborator

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

@pelson Collaborator
pelson added a note

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
@pelson
Collaborator

Closes #830.

@pelson
Collaborator

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

@efiring
Owner
@WeatherGod
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.

@pelson
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.

@pelson
Collaborator

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.

@thisch

+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!

@bilderbuchi

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.

@WeatherGod
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?

@pelson
Collaborator

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?

@efiring
Owner

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.

@efiring
Owner

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

@pelson
Collaborator

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):
# toggle fullscreen mode (default key 'f')
if event.key in fullscreen_keys:
- self.full_screen_toggle()
+ fig_manager = Gcf.get_active()
@pelson Collaborator
pelson added a note

Use canvas.manager as per #919.

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

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

@pelson
Collaborator

@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?

@pelson
Collaborator

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).

@pelson
Collaborator

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):
self._master = master
self._tkcanvas.focus_set()
-
+
+ if sys.platform == 'darwin':
+ # to make a tkagg window pop up on top on osx, osascript can be used
+ # this came from http://sourceforge.net/mailarchive/message.php?msg_id=23718545
+ cmd = ("""/usr/bin/osascript -e 'tell app "Finder" to set """ + \
+ """frontmost of process "%s" to true'""") % \
@efiring Owner
efiring added a note

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):
self._master = master
self._tkcanvas.focus_set()
-
+
+ if sys.platform == 'darwin':
@efiring Owner
efiring added a note

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
@jkseppan
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.

@jkseppan
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.

@pelson
Collaborator

@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?

@efiring efiring merged commit 5b90a27 into from
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Jun 24, 2012
  1. @pelson

    added a quit shortcut key for gui backends and fixed "toggle full scr…

    Phil Elson authored pelson committed
    …een" shortcut key which was broken.
  2. @pelson

    Backend factorisation for tooltip sharing.

    Phil Elson authored pelson committed
  3. @pelson

    removed todo

    Phil Elson authored pelson committed
  4. @pelson

    Fullscreen rather than maximize in Tk. Added fullscreen support to qt…

    Phil Elson authored pelson committed
    …4 backend.
  5. @pelson

    added modifier key support to the tk backend (i.e. ctrl+key). Additio…

    Phil Elson authored pelson committed
    …nal support will need to be added for qt, gtk and wx backends.
  6. @pelson

    Several fixes to WX. Modifier keys implemented for qt, wx and gtk bac…

    pelson authored pelson committed
    …kends.
  7. @pelson

    Doc improvements, removed escape for quit plot, added todos to backen…

    Phil Elson authored pelson committed
    …ds I cannot test.
  8. @pelson
  9. @pelson
  10. @pelson

    Added better 'super' key support.

    pelson authored pelson committed
  11. @pelson

    More osx tweaks

    pelson authored
Commits on Jul 1, 2012
  1. @pelson
This page is out of date. Refresh to see the latest.
View
23 doc/users/navigation_toolbar.rst
@@ -83,23 +83,24 @@ Navigation Keyboard Shortcuts
The following table holds all the default keys, which can be overwritten by use of your matplotlibrc (#keymap.\*).
-================================== ==============================================
+================================== =================================================
Command Keyboard Shortcut(s)
-================================== ==============================================
+================================== =================================================
Home/Reset **h** or **r** or **home**
Back **c** or **left arrow** or **backspace**
Forward **v** or **right arrow**
Pan/Zoom **p**
Zoom-to-rect **o**
-Save **s**
-Toggle fullscreen **f**
-Constrain pan/zoom to x axis hold **x**
-Constrain pan/zoom to y axis hold **y**
-Preserve aspect ratio hold **CONTROL**
-Toggle grid **g**
-Toggle x axis scale (log/linear) **L** or **k**
-Toggle y axis scale (log/linear) **l**
-================================== ==============================================
+Save **ctrl** + **s**
+Toggle fullscreen **ctrl** + **f**
+Close plot **ctrl** + **w**
+Constrain pan/zoom to x axis hold **x** when panning/zooming with mouse
+Constrain pan/zoom to y axis hold **y** when panning/zooming with mouse
+Preserve aspect ratio hold **CONTROL** when panning/zooming with mouse
+Toggle grid **g** when mouse is over an axes
+Toggle x axis scale (log/linear) **L** or **k** when mouse is over an axes
+Toggle y axis scale (log/linear) **l** when mouse is over an axes
+================================== =================================================
If you are using :mod:`matplotlib.pyplot` the toolbar will be created
automatically for every figure. If you are writing your own user
View
165 lib/matplotlib/backend_bases.py
@@ -1190,6 +1190,7 @@ class CloseEvent(Event):
def __init__(self, name, canvas, guiEvent=None):
Event.__init__(self, name, canvas, guiEvent)
+
class LocationEvent(Event):
"""
An event that has a screen location
@@ -1295,9 +1296,6 @@ def _update_enter_leave(self):
LocationEvent.lastevent = self
-
-
-
class MouseEvent(LocationEvent):
"""
A mouse event ('button_press_event', 'button_release_event', 'scroll_event',
@@ -1307,10 +1305,12 @@ class MouseEvent(LocationEvent):
attributes, the following attributes are defined:
*button*
- button pressed None, 1, 2, 3, 'up', 'down' (up and down are used for scroll events)
+ button pressed None, 1, 2, 3, 'up', 'down' (up and down are used
+ for scroll events)
*key*
- the key pressed: None, chr(range(255), 'shift', 'win', or 'control'
+ the key depressed when the mouse event triggered (see
+ :class:`KeyEvent`)
*step*
number of scroll steps (positive for 'up', negative for 'down')
@@ -1319,7 +1319,7 @@ class MouseEvent(LocationEvent):
Example usage::
def on_press(event):
- print 'you pressed', event.button, event.xdata, event.ydata
+ print('you pressed', event.button, event.xdata, event.ydata)
cid = fig.canvas.mpl_connect('button_press_event', on_press)
@@ -1346,8 +1346,10 @@ def __init__(self, name, canvas, x, y, button=None, key=None,
self.dblclick = dblclick
def __str__(self):
- return "MPL MouseEvent: xy=(%d,%d) xydata=(%s,%s) button=%d dblclick=%s inaxes=%s"%\
- (self.x,self.y,str(self.xdata),str(self.ydata),self.button,self.dblclick,self.inaxes)
+ return ("MPL MouseEvent: xy=(%d,%d) xydata=(%s,%s) button=%d " +
+ "dblclick=%s inaxes=%s") % (self.x, self.y, self.xdata,
+ self.ydata, self.button, self.dblclick, self.inaxes)
+
class PickEvent(Event):
"""
@@ -1377,7 +1379,7 @@ def on_pick(event):
thisline = event.artist
xdata, ydata = thisline.get_data()
ind = event.ind
- print 'on pick line:', zip(xdata[ind], ydata[ind])
+ print('on pick line:', zip(xdata[ind], ydata[ind]))
cid = fig.canvas.mpl_connect('pick_event', on_pick)
@@ -1400,16 +1402,23 @@ class KeyEvent(LocationEvent):
attributes, the following attributes are defined:
*key*
- the key pressed: None, chr(range(255), shift, win, or control
-
- This interface may change slightly when better support for
- modifier keys is included.
-
-
+ the key(s) pressed. Could be **None**, a single case sensitive ascii
+ character ("g", "G", "#", etc.), a special key
+ ("control", "shift", "f1", "up", etc.) or a
+ combination of the above (e.g. "ctrl+alt+g", "ctrl+alt+G").
+
+ .. note::
+
+ Modifier keys will be prefixed to the pressed key and will be in the
+ order "ctrl", "alt", "super". The exception to this rule is when the
+ pressed key is itself a modifier key, therefore "ctrl+alt" and
+ "alt+control" can both be valid key values.
+
+
Example usage::
def on_key(event):
- print 'you pressed', event.key, event.xdata, event.ydata
+ print('you pressed', event.key, event.xdata, event.ydata)
cid = fig.canvas.mpl_connect('key_press_event', on_key)
@@ -1419,7 +1428,6 @@ def __init__(self, name, canvas, key, x=0, y=0, guiEvent=None):
self.key = key
-
class FigureCanvasBase(object):
"""
The canvas the figure renders into.
@@ -1448,7 +1456,6 @@ class FigureCanvasBase(object):
'close_event'
]
-
def __init__(self, figure):
figure.set_canvas(self)
self.figure = figure
@@ -1657,7 +1664,8 @@ def button_press_event(self, x, y, button, dblclick=False, guiEvent=None):
"""
self._button = button
s = 'button_press_event'
- mouseevent = MouseEvent(s, self, x, y, button, self._key, dblclick=dblclick, guiEvent=guiEvent)
+ mouseevent = MouseEvent(s, self, x, y, button, self._key,
+ dblclick=dblclick, guiEvent=guiEvent)
self.callbacks.process(s, mouseevent)
def button_release_event(self, x, y, button, guiEvent=None):
@@ -1743,7 +1751,7 @@ def enter_notify_event(self, guiEvent=None, xy=None):
self.callbacks.process('figure_enter_event', event)
def idle_event(self, guiEvent=None):
- 'call when GUI is idle'
+ """Called when GUI is idle."""
s = 'idle_event'
event = IdleEvent(s, self, guiEvent=guiEvent)
self.callbacks.process(s, event)
@@ -1789,7 +1797,7 @@ def draw_cursor(self, event):
def get_width_height(self):
"""
- return the figure width and height in points or pixels
+ Return the figure width and height in points or pixels
(depending on the backend), truncated to integers
"""
return int(self.figure.bbox.width), int(self.figure.bbox.height)
@@ -1911,7 +1919,6 @@ def get_supported_filetypes_grouped(self):
groupings[name].sort()
return groupings
-
def _get_print_method(self, format):
method_name = 'print_%s' % format
@@ -1936,7 +1943,6 @@ def _print_method(*args, **kwargs):
return getattr(self, method_name)
-
def print_figure(self, filename, dpi=None, facecolor='w', edgecolor='w',
orientation='portrait', format=None, **kwargs):
"""
@@ -2078,9 +2084,6 @@ def print_figure(self, filename, dpi=None, facecolor='w', edgecolor='w',
#self.figure.canvas.draw() ## seems superfluous
return result
-
-
-
def get_default_filetype(self):
"""
Get the default savefig file format as specified in rcParam
@@ -2099,7 +2102,7 @@ def set_window_title(self, title):
def switch_backends(self, FigureCanvasClass):
"""
- instantiate an instance of FigureCanvasClass
+ Instantiate an instance of FigureCanvasClass
This is used for backend switching, eg, to instantiate a
FigureCanvasPS from a FigureCanvasGTK. Note, deep copying is
@@ -2148,7 +2151,7 @@ def func(event)
Example usage::
def on_press(event):
- print 'you pressed', event.button, event.xdata, event.ydata
+ print('you pressed', event.button, event.xdata, event.ydata)
cid = canvas.mpl_connect('button_press_event', on_press)
@@ -2158,7 +2161,7 @@ def on_press(event):
def mpl_disconnect(self, cid):
"""
- disconnect callback id cid
+ Disconnect callback id cid
Example usage::
@@ -2274,9 +2277,6 @@ def key_press_handler(event, canvas, toolbar=None):
"""
# these bindings happen whether you are over an axes or not
- #if event.key == 'q':
- # self.destroy() # how cruel to have to destroy oneself!
- # return
if event.key is None:
return
@@ -2289,6 +2289,7 @@ def key_press_handler(event, canvas, toolbar=None):
pan_keys = rcParams['keymap.pan']
zoom_keys = rcParams['keymap.zoom']
save_keys = rcParams['keymap.save']
+ quit_keys = rcParams['keymap.quit']
grid_keys = rcParams['keymap.grid']
toggle_yscale_keys = rcParams['keymap.yscale']
toggle_xscale_keys = rcParams['keymap.xscale']
@@ -2298,6 +2299,10 @@ def key_press_handler(event, canvas, toolbar=None):
if event.key in fullscreen_keys:
canvas.manager.full_screen_toggle()
+ # quit the figure (defaut key 'ctrl+w')
+ if event.key in quit_keys:
+ Gcf.destroy_fig(canvas.figure)
+
if toolbar is not None:
# home or reset mnemonic (default key 'h', 'home' and 'r')
if event.key in home_keys:
@@ -2322,7 +2327,8 @@ def key_press_handler(event, canvas, toolbar=None):
if event.inaxes is None:
return
- # the mouse has to be over an axes to trigger these
+ # these bindings require the mouse to be over an axes to trigger
+
# switching on/off a grid in current axes (default key 'g')
if event.key in grid_keys:
event.inaxes.grid()
@@ -2387,16 +2393,16 @@ def __init__(self, canvas, num):
def destroy(self):
pass
- def full_screen_toggle (self):
+ def full_screen_toggle(self):
pass
def resize(self, w, h):
- 'For gui backends: resize window in pixels'
+ """"For gui backends, resize the window (in pixels)."""
pass
def key_press(self, event):
"""
- implement the default mpl key bindings defined at
+ Implement the default mpl key bindings defined at
:ref:`key-event-handling`
"""
key_press_handler(event, self.canvas, self.canvas.toolbar)
@@ -2414,13 +2420,13 @@ def set_window_title(self, title):
"""
pass
-# cursors
-class Cursors: #namespace
+
+class Cursors:
+ # this class is only used as a simple namespace
HAND, POINTER, SELECT_REGION, MOVE = range(4)
cursors = Cursors()
-
class NavigationToolbar2(object):
"""
Base class for the navigation cursor, version 2
@@ -2464,6 +2470,25 @@ class NavigationToolbar2(object):
That's it, we'll do the rest!
"""
+
+ # list of toolitems to add to the toolbar, format is:
+ # (
+ # text, # the text of the button (often not visible to users)
+ # tooltip_text, # the tooltip shown on hover (where possible)
+ # image_file, # name of the image for the button (without the extension)
+ # name_of_method, # name of the method in NavigationToolbar2 to call
+ # )
+ toolitems = (
+ ('Home', 'Reset original view', 'home', 'home'),
+ ('Back', 'Back to previous view', 'back', 'back'),
+ ('Forward', 'Forward to next view', 'forward', 'forward'),
+ (None, None, None, None),
+ ('Pan', 'Pan axes with left mouse, zoom with right', 'move', 'pan'),
+ ('Zoom', 'Zoom to rectangle', 'zoom_to_rect', 'zoom'),
+ (None, None, None, None),
+ ('Subplots', 'Configure subplots', 'subplots', 'configure_subplots'),
+ ('Save', 'Save the figure', 'filesave', 'save_figure'),
+ )
def __init__(self, canvas):
self.canvas = canvas
@@ -2488,11 +2513,11 @@ def __init__(self, canvas):
self.set_history_buttons()
def set_message(self, s):
- 'display a message on toolbar or in status bar'
+ """Display a message on toolbar or in status bar"""
pass
def back(self, *args):
- 'move back up the view lim stack'
+ """move back up the view lim stack"""
self._views.back()
self._positions.back()
self.set_history_buttons()
@@ -2502,18 +2527,18 @@ def dynamic_update(self):
pass
def draw_rubberband(self, event, x0, y0, x1, y1):
- 'draw a rectangle rubberband to indicate zoom limits'
+ """Draw a rectangle rubberband to indicate zoom limits"""
pass
def forward(self, *args):
- 'move forward in the view lim stack'
+ """Move forward in the view lim stack"""
self._views.forward()
self._positions.forward()
self.set_history_buttons()
self._update_view()
def home(self, *args):
- 'restore the original view'
+ """Restore the original view"""
self._views.home()
self._positions.home()
self.set_history_buttons()
@@ -2542,8 +2567,6 @@ class implementation.
raise NotImplementedError
def mouse_move(self, event):
- #print 'mouse_move', event.button
-
if not event.inaxes or not self._active:
if self._lastCursor != cursors.POINTER:
self.set_cursor(cursors.POINTER)
@@ -2561,9 +2584,10 @@ def mouse_move(self, event):
if event.inaxes and event.inaxes.get_navigate():
- try: s = event.inaxes.format_coord(event.xdata, event.ydata)
- except ValueError: pass
- except OverflowError: pass
+ try:
+ s = event.inaxes.format_coord(event.xdata, event.ydata)
+ except (ValueError, OverflowError):
+ pass
else:
if len(self.mode):
self.set_message('%s, %s' % (self.mode, s))
@@ -2572,7 +2596,7 @@ def mouse_move(self, event):
else: self.set_message(self.mode)
def pan(self,*args):
- 'Activate the pan/zoom tool. pan with left button, zoom with right'
+ """Activate the pan/zoom tool. pan with left button, zoom with right"""
# set the pointer icon and button press funcs to the
# appropriate callbacks
@@ -2604,11 +2628,11 @@ def pan(self,*args):
self.set_message(self.mode)
def press(self, event):
- 'this will be called whenver a mouse button is pressed'
+ """Called whenver a mouse button is pressed."""
pass
def press_pan(self, event):
- 'the press mouse button in pan/zoom mode callback'
+ """the press mouse button in pan/zoom mode callback"""
if event.button == 1:
self._button_pressed=1
@@ -2636,7 +2660,7 @@ def press_pan(self, event):
self.press(event)
def press_zoom(self, event):
- 'the press mouse button in zoom to rect mode callback'
+ """the press mouse button in zoom to rect mode callback"""
if event.button == 1:
self._button_pressed=1
elif event.button == 3:
@@ -2658,17 +2682,14 @@ def press_zoom(self, event):
a.transData.frozen() ))
id1 = self.canvas.mpl_connect('motion_notify_event', self.drag_zoom)
-
id2 = self.canvas.mpl_connect('key_press_event',
self._switch_on_zoom_mode)
id3 = self.canvas.mpl_connect('key_release_event',
self._switch_off_zoom_mode)
self._ids_zoom = id1, id2, id3
-
self._zoom_mode = event.key
-
self.press(event)
def _switch_on_zoom_mode(self, event):
@@ -2680,7 +2701,7 @@ def _switch_off_zoom_mode(self, event):
self.mouse_move(event)
def push_current(self):
- 'push the current view limits and position onto the stack'
+ """push the current view limits and position onto the stack"""
lims = []; pos = []
for a in self.canvas.figure.get_axes():
xmin, xmax = a.get_xlim()
@@ -2695,11 +2716,11 @@ def push_current(self):
self.set_history_buttons()
def release(self, event):
- 'this will be called whenever mouse button is released'
+ """this will be called whenever mouse button is released"""
pass
def release_pan(self, event):
- 'the release mouse button callback in pan/zoom mode'
+ """the release mouse button callback in pan/zoom mode"""
if self._button_pressed is None:
return
@@ -2715,7 +2736,7 @@ def release_pan(self, event):
self.draw()
def drag_pan(self, event):
- 'the drag callback in pan/zoom mode'
+ """the drag callback in pan/zoom mode"""
for a, ind in self._xypress:
#safer to use the recorded button at the press than current button:
@@ -2724,7 +2745,7 @@ def drag_pan(self, event):
self.dynamic_update()
def drag_zoom(self, event):
- 'the drag callback in zoom mode'
+ """the drag callback in zoom mode"""
if self._xypress:
x, y = event.x, event.y
@@ -2744,10 +2765,8 @@ def drag_zoom(self, event):
self.draw_rubberband(event, x, y, lastx, lasty)
-
-
def release_zoom(self, event):
- 'the release mouse button callback in zoom to rect mode'
+ """the release mouse button callback in zoom to rect mode"""
for zoom_id in self._ids_zoom:
self.canvas.mpl_disconnect(zoom_id)
self._ids_zoom = []
@@ -2855,7 +2874,7 @@ def release_zoom(self, event):
self.release(event)
def draw(self):
- 'redraw the canvases, update the locators'
+ """Redraw the canvases, update the locators"""
for a in self.canvas.figure.get_axes():
xaxis = getattr(a, 'xaxis', None)
yaxis = getattr(a, 'yaxis', None)
@@ -2871,12 +2890,10 @@ def draw(self):
loc.refresh()
self.canvas.draw()
-
-
def _update_view(self):
- '''update the viewlim and position from the view and
+ """Update the viewlim and position from the view and
position stack for each axes
- '''
+ """
lims = self._views()
if lims is None: return
@@ -2892,9 +2909,8 @@ def _update_view(self):
self.draw()
-
def save_figure(self, *args):
- 'save the current figure'
+ """Save the current figure"""
raise NotImplementedError
def set_cursor(self, cursor):
@@ -2905,13 +2921,13 @@ def set_cursor(self, cursor):
pass
def update(self):
- 'reset the axes stack'
+ """Reset the axes stack"""
self._views.clear()
self._positions.clear()
self.set_history_buttons()
def zoom(self, *args):
- 'activate zoom to rect mode'
+ """Activate zoom to rect mode"""
if self._active == 'ZOOM':
self._active = None
else:
@@ -2938,7 +2954,6 @@ def zoom(self, *args):
self.set_message(self.mode)
-
def set_history_buttons(self):
- 'enable or disable back/forward button'
+ """Enable or disable back/forward button"""
pass
View
2  lib/matplotlib/backends/backend_fltkagg.py
@@ -136,6 +136,8 @@ def handle(self, event):
self._key=special_key[ikey]
except:
self._key=None
+
+ # TODO: Handle ctrl, alt, super modifiers.
FigureCanvasBase.key_press_event(self._source, self._key)
return 1
elif event == Fltk.FL_KEYUP:
View
35 lib/matplotlib/backends/backend_gtk.py
@@ -189,6 +189,10 @@ class FigureCanvasGTK (gtk.DrawingArea, FigureCanvasBase):
65455 : '/',
65439 : 'dec',
65421 : 'enter',
+ 65511 : 'super',
+ 65512 : 'super',
+ 65406 : 'alt',
+ 65289 : 'tab',
}
# Setting this as a static constant prevents
@@ -322,16 +326,20 @@ def enter_notify_event(self, widget, event):
def _get_key(self, event):
if event.keyval in self.keyvald:
key = self.keyvald[event.keyval]
- elif event.keyval <256:
+ elif event.keyval < 256:
key = chr(event.keyval)
else:
key = None
-
- ctrl = event.state & gdk.CONTROL_MASK
- shift = event.state & gdk.SHIFT_MASK
+
+ for key_mask, prefix in (
+ [gdk.MOD4_MASK, 'super'],
+ [gdk.MOD1_MASK, 'alt'],
+ [gdk.CONTROL_MASK, 'ctrl'],):
+ if event.state & key_mask:
+ key = '{}+{}'.format(prefix, key)
+
return key
-
def configure_event(self, widget, event):
if _debug: print('FigureCanvasGTK.%s' % fn_name())
if widget.window is None:
@@ -592,7 +600,7 @@ def show(self):
# show the figure window
self.window.show()
- def full_screen_toggle (self):
+ def full_screen_toggle(self):
self._full_screen_flag = not self._full_screen_flag
if self._full_screen_flag:
self.window.fullscreen()
@@ -624,19 +632,6 @@ def resize(self, width, height):
class NavigationToolbar2GTK(NavigationToolbar2, gtk.Toolbar):
- # list of toolitems to add to the toolbar, format is:
- # text, tooltip_text, image_file, callback(str)
- toolitems = (
- ('Home', 'Reset original view', 'home.png', 'home'),
- ('Back', 'Back to previous view','back.png', 'back'),
- ('Forward', 'Forward to next view','forward.png', 'forward'),
- ('Pan', 'Pan axes with left mouse, zoom with right', 'move.png','pan'),
- ('Zoom', 'Zoom to rectangle','zoom_to_rect.png', 'zoom'),
- (None, None, None, None),
- ('Subplots', 'Configure subplots','subplots.png', 'configure_subplots'),
- ('Save', 'Save the figure','filesave.png', 'save_figure'),
- )
-
def __init__(self, canvas, window):
self.win = window
gtk.Toolbar.__init__(self)
@@ -704,7 +699,7 @@ def _init_toolbar2_4(self):
if text is None:
self.insert( gtk.SeparatorToolItem(), -1 )
continue
- fname = os.path.join(basedir, image_file)
+ fname = os.path.join(basedir, image_file + '.png')
image = gtk.Image()
image.set_from_file(fname)
tbutton = gtk.ToolButton(image, text)
View
29 lib/matplotlib/backends/backend_gtk3.py
@@ -259,15 +259,21 @@ def enter_notify_event(self, widget, event):
def _get_key(self, event):
if event.keyval in self.keyvald:
key = self.keyvald[event.keyval]
- elif event.keyval <256:
+ elif event.keyval < 256:
key = chr(event.keyval)
else:
key = None
- #ctrl = event.get_state() & Gdk.EventMask.CONTROL
- #shift = event.get_state() & Gdk.EventMask.SHIFT
- return key
+ modifiers = [
+ (Gdk.ModifierType.MOD4_MASK, 'super'),
+ (Gdk.ModifierType.MOD1_MASK, 'alt'),
+ (Gdk.ModifierType.CONTROL_MASK, 'ctrl'),
+ ]
+ for key_mask, prefix in modifiers:
+ if event.state & key_mask:
+ key = '{}+{}'.format(prefix, key)
+ return key
def configure_event(self, widget, event):
if _debug: print 'FigureCanvasGTK3.%s' % fn_name()
@@ -453,19 +459,6 @@ def resize(self, width, height):
class NavigationToolbar2GTK3(NavigationToolbar2, Gtk.Toolbar):
- # list of toolitems to add to the toolbar, format is:
- # text, tooltip_text, image_file, callback(str)
- toolitems = (
- ('Home', 'Reset original view', 'home.png', 'home'),
- ('Back', 'Back to previous view','back.png', 'back'),
- ('Forward', 'Forward to next view','forward.png', 'forward'),
- ('Pan', 'Pan axes with left mouse, zoom with right', 'move.png','pan'),
- ('Zoom', 'Zoom to rectangle','zoom_to_rect.png', 'zoom'),
- (None, None, None, None),
- ('Subplots', 'Configure subplots','subplots.png', 'configure_subplots'),
- ('Save', 'Save the figure','filesave.png', 'save_figure'),
- )
-
def __init__(self, canvas, window):
self.win = window
GObject.GObject.__init__(self)
@@ -516,7 +509,7 @@ def _init_toolbar(self):
if text is None:
self.insert( Gtk.SeparatorToolItem(), -1 )
continue
- fname = os.path.join(basedir, image_file)
+ fname = os.path.join(basedir, image_file + '.png')
image = Gtk.Image()
image.set_from_file(fname)
tbutton = Gtk.ToolButton()
View
12 lib/matplotlib/backends/backend_macosx.py
@@ -20,12 +20,13 @@
import matplotlib
from matplotlib.backends import _macosx
+
class Show(ShowBase):
def mainloop(self):
_macosx.show()
-
show = Show()
+
class RendererMac(RendererBase):
"""
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):
def draw_text(self, gc, x, y, s, prop, angle, ismath=False):
if ismath:
- self._draw_mathtext(gc, x, y, s, prop, angle)
+ self._draw_mathtext(gc, x, y, s, prop, angle)
else:
family = prop.get_family()
weight = prop.get_weight()
@@ -174,6 +175,7 @@ def points_to_pixels(self, points):
def option_image_nocomposite(self):
return True
+
class GraphicsContextMac(_macosx.GraphicsContext, GraphicsContextBase):
"""
The GraphicsContext wraps a Quartz graphics context. All methods
@@ -211,6 +213,7 @@ def set_clip_path(self, path):
path = path.get_fully_transformed_path()
_macosx.GraphicsContext.set_clip_path(self, path)
+
########################################################################
#
# The following functions and classes are for pylab and implement
@@ -245,6 +248,7 @@ def new_figure_manager(num, *args, **kwargs):
manager = FigureManagerMac(canvas, num)
return manager
+
class TimerMac(_macosx.Timer, TimerBase):
'''
Subclass of :class:`backend_bases.TimerBase` that uses CoreFoundation
@@ -262,7 +266,6 @@ class TimerMac(_macosx.Timer, TimerBase):
# completely implemented at the C-level (in _macosx.Timer)
-
class FigureCanvasMac(_macosx.FigureCanvas, FigureCanvasBase):
"""
The canvas the figure renders into. Calls the draw and print fig
@@ -346,6 +349,7 @@ def new_timer(self, *args, **kwargs):
"""
return TimerMac(*args, **kwargs)
+
class FigureManagerMac(_macosx.FigureManager, FigureManagerBase):
"""
Wrap everything up into a window for the pylab interface
@@ -377,6 +381,7 @@ def notify_axes_change(fig):
def close(self):
Gcf.destroy(self.num)
+
class NavigationToolbarMac(_macosx.NavigationToolbar):
def __init__(self, canvas):
@@ -444,6 +449,7 @@ def save_figure(self, *args):
return
self.canvas.print_figure(filename)
+
class NavigationToolbar2Mac(_macosx.NavigationToolbar2, NavigationToolbar2):
def __init__(self, canvas):
View
20 lib/matplotlib/backends/backend_qt.py
@@ -181,6 +181,8 @@ def _get_key( self, event ):
else:
key = None
+ # TODO: Handle ctrl, alt, super modifiers. qt4 backend has implemented.
+
return key
def flush_events(self):
@@ -295,20 +297,6 @@ def set_window_title(self, title):
self.window.setCaption(title)
class NavigationToolbar2QT( NavigationToolbar2, qt.QWidget ):
- # list of toolitems to add to the toolbar, format is:
- # text, tooltip_text, image_file, callback(str)
- toolitems = (
- ('Home', 'Reset original view', 'home.ppm', 'home'),
- ('Back', 'Back to previous view','back.ppm', 'back'),
- ('Forward', 'Forward to next view','forward.ppm', 'forward'),
- (None, None, None, None),
- ('Pan', 'Pan axes with left mouse, zoom with right', 'move.ppm', 'pan'),
- ('Zoom', 'Zoom to rectangle','zoom_to_rect.ppm', 'zoom'),
- (None, None, None, None),
- ('Subplots', 'Configure subplots','subplots.png', 'configure_subplots'),
@pelson Collaborator
pelson added a note

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?

@WeatherGod Collaborator

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

@pelson Collaborator
pelson added a note

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
- ('Save', 'Save the figure','filesave.ppm', 'save_figure'),
- )
-
def __init__( self, canvas, parent ):
self.canvas = canvas
self.buttons = {}
@@ -329,8 +317,8 @@ def _init_toolbar( self ):
self.layout.addSpacing( 8 )
continue
- fname = os.path.join( basedir, image_file )
- image = qt.QPixmap()
+ fname = os.path.join(basedir, image_file + '.ppm')
+ image = qt.QPixmap()
image.load( fname )
button = qt.QPushButton( qt.QIconSet( image ), "", self )
View
99 lib/matplotlib/backends/backend_qt4.py
@@ -1,6 +1,7 @@
from __future__ import division, print_function
import math
import os
+import signal
import sys
import matplotlib
@@ -60,8 +61,10 @@ def _create_qApp():
class Show(ShowBase):
def mainloop(self):
- QtGui.qApp.exec_()
+ # allow KeyboardInterrupt exceptions to close the plot window.
+ signal.signal(signal.SIGINT, signal.SIG_DFL)
+ QtGui.qApp.exec_()
show = Show()
@@ -120,6 +123,7 @@ class FigureCanvasQT( QtGui.QWidget, FigureCanvasBase ):
keyvald = { QtCore.Qt.Key_Control : 'control',
QtCore.Qt.Key_Shift : 'shift',
QtCore.Qt.Key_Alt : 'alt',
+ QtCore.Qt.Key_Meta : 'super',
QtCore.Qt.Key_Return : 'enter',
QtCore.Qt.Key_Left : 'left',
QtCore.Qt.Key_Up : 'up',
@@ -143,6 +147,33 @@ class FigureCanvasQT( QtGui.QWidget, FigureCanvasBase ):
QtCore.Qt.Key_PageUp : 'pageup',
QtCore.Qt.Key_PageDown : 'pagedown',
}
+
+ # define the modifier keys which are to be collected on keyboard events.
+ # format is: [(modifier_flag, modifier_name, equivalent_key)
+ _modifier_keys = [
+ (QtCore.Qt.MetaModifier, 'super', QtCore.Qt.Key_Meta),
+ (QtCore.Qt.AltModifier, 'alt', QtCore.Qt.Key_Alt),
+ (QtCore.Qt.ControlModifier, 'ctrl', QtCore.Qt.Key_Control)
+ ]
+
+ _ctrl_modifier = QtCore.Qt.ControlModifier
+
+ if sys.platform == 'darwin':
+ # in OSX, the control and super (aka cmd/apple) keys are switched, so
+ # switch them back.
+ keyvald.update({
+ QtCore.Qt.Key_Control : 'super', # cmd/apple key
+ QtCore.Qt.Key_Meta : 'control',
+ })
+
+ _modifier_keys = [
+ (QtCore.Qt.ControlModifier, 'super', QtCore.Qt.Key_Control),
+ (QtCore.Qt.AltModifier, 'alt', QtCore.Qt.Key_Alt),
+ (QtCore.Qt.MetaModifier, 'ctrl', QtCore.Qt.Key_Meta),
+ ]
+
+ _ctrl_modifier = QtCore.Qt.MetaModifier
+
# map Qt button codes to MouseEvent's ones:
buttond = {QtCore.Qt.LeftButton : 1,
QtCore.Qt.MidButton : 2,
@@ -150,6 +181,7 @@ class FigureCanvasQT( QtGui.QWidget, FigureCanvasBase ):
# QtCore.Qt.XButton1 : None,
# QtCore.Qt.XButton2 : None,
}
+
def __init__( self, figure ):
if DEBUG: print('FigureCanvasQt: ', figure)
_create_qApp()
@@ -268,12 +300,32 @@ def minumumSizeHint( self ):
def _get_key( self, event ):
if event.isAutoRepeat():
return None
+
if event.key() < 256:
- key = str(event.text())
- elif event.key() in self.keyvald:
- key = self.keyvald[ event.key() ]
+ key = unicode(event.text())
+ # if the control key is being pressed, we don't get the correct
+ # characters, so interpret them directly from the event.key().
+ # Unfortunately, this means that we cannot handle key's case
+ # since event.key() is not case sensitive, whereas event.text() is,
+ # Finally, since it is not possible to get the CapsLock state
+ # we cannot accurately compute the case of a pressed key when
+ # ctrl+shift+p is pressed.
+ if int(event.modifiers()) & self._ctrl_modifier:
+ # we always get an uppercase character
+ key = chr(event.key())
+ # if shift is not being pressed, lowercase it (as mentioned,
+ # this does not take into account the CapsLock state)
+ if not int(event.modifiers()) & QtCore.Qt.ShiftModifier:
+ key = key.lower()
+
else:
- key = None
+ key = self.keyvald.get(event.key())
+
+ if key is not None:
+ # prepend the ctrl, alt, super keys if appropriate (sorted in that order)
+ for modifier, prefix, Qt_key in self._modifier_keys:
+ if event.key() != Qt_key and int(event.modifiers()) & modifier == modifier:
+ key = u'{}+{}'.format(prefix, key)
return key
@@ -373,9 +425,9 @@ def __init__( self, canvas, num ):
self.canvas.figure.show = lambda *args: self.window.show()
def notify_axes_change( fig ):
- # This will be called whenever the current axes is changed
- if self.toolbar is not None:
- self.toolbar.update()
+ # This will be called whenever the current axes is changed
+ if self.toolbar is not None:
+ self.toolbar.update()
self.canvas.figure.add_axobserver( notify_axes_change )
@QtCore.Slot()
@@ -383,6 +435,12 @@ def _show_message(self,s):
# Fixes a PySide segfault.
self.window.statusBar().showMessage(s)
+ def full_screen_toggle(self):
+ if self.window.isFullScreen():
+ self.window.showNormal()
+ else:
+ self.window.showFullScreen()
+
def _widgetclosed( self ):
if self.window._destroying: return
self.window._destroying = True
@@ -394,7 +452,6 @@ def _widgetclosed( self ):
# Gcf can get destroyed before the Gcf.destroy
# line is run, leading to a useless AttributeError.
-
def _get_toolbar(self, canvas, parent):
# must be inited after the window, drawingArea and figure
# attrs are set
@@ -441,22 +498,14 @@ def _icon(self, name):
def _init_toolbar(self):
self.basedir = os.path.join(matplotlib.rcParams[ 'datapath' ],'images')
- a = self.addAction(self._icon('home.png'), 'Home', self.home)
- a.setToolTip('Reset original view')
- a = self.addAction(self._icon('back.png'), 'Back', self.back)
- a.setToolTip('Back to previous view')
- a = self.addAction(self._icon('forward.png'), 'Forward', self.forward)
- a.setToolTip('Forward to next view')
- self.addSeparator()
- a = self.addAction(self._icon('move.png'), 'Pan', self.pan)
- a.setToolTip('Pan axes with left mouse, zoom with right')
- a = self.addAction(self._icon('zoom_to_rect.png'), 'Zoom', self.zoom)
- a.setToolTip('Zoom to rectangle')
- self.addSeparator()
- a = self.addAction(self._icon('subplots.png'), 'Subplots',
- self.configure_subplots)
- a.setToolTip('Configure subplots')
-
+ for text, tooltip_text, image_file, callback in self.toolitems:
+ if text is None:
+ self.addSeparator()
+ else:
+ a = self.addAction(self._icon(image_file + '.png'), text, getattr(self, callback))
+ if tooltip_text is not None:
+ a.setToolTip(tooltip_text)
+
if figureoptions is not None:
a = self.addAction(self._icon("qt4_editor_options.png"),
'Customize', self.edit_parameters)
View
169 lib/matplotlib/backends/backend_tkagg.py
@@ -127,6 +127,7 @@ class FigureCanvasTkAgg(FigureCanvasAgg):
keyvald = {65507 : 'control',
65505 : 'shift',
65513 : 'alt',
+ 65515 : 'super',
65508 : 'control',
65506 : 'shift',
65514 : 'alt',
@@ -174,6 +175,18 @@ class FigureCanvasTkAgg(FigureCanvasAgg):
65439 : 'dec',
65421 : 'enter',
}
+
+ _keycode_lookup = {
+ 262145: 'control',
+ 524320: 'alt',
+ 524352: 'alt',
+ 1048584: 'super',
+ 1048592: 'super',
+ 131074: 'shift',
+ 131076: 'shift',
+ }
+ """_keycode_lookup is used for badly mapped (i.e. no event.key_sym set)
+ keys on apple keyboards."""
def __init__(self, figure, master=None, resize_callback=None):
FigureCanvasAgg.__init__(self, figure)
@@ -217,7 +230,7 @@ def filter_destroy(evt):
self._master = master
self._tkcanvas.focus_set()
-
+
def resize(self, event):
width, height = event.width, event.height
if self._resize_callback is not None:
@@ -399,13 +412,40 @@ def _get_key(self, event):
val = event.keysym_num
if val in self.keyvald:
key = self.keyvald[val]
- elif val<256:
+ elif val == 0 and sys.platform == 'darwin' and \
+ event.keycode in self._keycode_lookup:
+ key = self._keycode_lookup[event.keycode]
+ elif val < 256:
key = chr(val)
else:
key = None
+
+ # add modifier keys to the key string. Bit details originate from
+ # http://effbot.org/tkinterbook/tkinter-events-and-bindings.htm
+ # BIT_SHIFT = 0x001; BIT_CAPSLOCK = 0x002; BIT_CONTROL = 0x004;
+ # BIT_LEFT_ALT = 0x008; BIT_NUMLOCK = 0x010; BIT_RIGHT_ALT = 0x080;
+ # BIT_MB_1 = 0x100; BIT_MB_2 = 0x200; BIT_MB_3 = 0x400;
+ # In general, the modifier key is excluded from the modifier flag,
+ # however this is not the case on "darwin", so double check that
+ # we aren't adding repeat modifier flags to a modifier key.
+ modifiers = [(6, 'super', 'super'),
+ (3, 'alt', 'alt'),
+ (2, 'ctrl', 'control'),
+ ]
+ if sys.platform == 'darwin':
+ modifiers = [(3, 'super', 'super'),
+ (4, 'alt', 'alt'),
+ (2, 'ctrl', 'control'),
+ ]
+
+ if key is not None:
+ # note, shift is not added to the keys as this is already accounted for
+ for bitmask, prefix, key_name in modifiers:
+ if event.state & (1 << bitmask) and key_name not in key:
+ key = '{}+{}'.format(prefix, key)
+
return key
-
def key_press(self, event):
key = self._get_key(event)
FigureCanvasBase.key_press_event(self, key, guiEvent=event)
@@ -457,7 +497,7 @@ def __init__(self, canvas, num, window):
self.window.wm_title("Figure %d" % num)
self.canvas = canvas
self._num = num
- t1,t2,w,h = canvas.figure.bbox.bounds
+ _, _, w, h = canvas.figure.bbox.bounds
w, h = int(w), int(h)
self.window.minsize(int(w*3/4),int(h*3/4))
if matplotlib.rcParams['toolbar']=='classic':
@@ -476,12 +516,9 @@ def notify_axes_change(fig):
if self.toolbar != None: self.toolbar.update()
self.canvas.figure.add_axobserver(notify_axes_change)
-
-
# attach a show method to the figure for pylab ease of use
self.canvas.figure.show = lambda *args: self.show()
-
def resize(self, width, height=None):
# before 09-12-22, the resize method takes a single *event*
# parameter. On the other hand, the resize method of other
@@ -499,7 +536,6 @@ def resize(self, width, height=None):
self.toolbar.configure(width=width)
-
def show(self):
"""
this function doesn't segfault but causes the
@@ -518,7 +554,6 @@ def destroy(*args):
self.canvas.draw_idle()
self._shown = True
-
def destroy(self, *args):
if self.window is not None:
#self.toolbar.destroy()
@@ -533,6 +568,11 @@ def destroy(self, *args):
def set_window_title(self, title):
self.window.wm_title(title)
+ def full_screen_toggle(self):
+ is_fullscreen = bool(self.window.attributes('-fullscreen'))
+ self.window.attributes('-fullscreen', not is_fullscreen)
+
+
class AxisMenu:
def __init__(self, master, naxes):
self._master = master
@@ -595,9 +635,10 @@ def select_all(self):
a.set(1)
self.set_active()
+
class NavigationToolbar(Tk.Frame):
"""
- Public attriubutes
+ Public attributes
canvas - the FigureCanvas (gtk.DrawingArea)
win - the gtk.Window
@@ -625,39 +666,39 @@ def __init__(self, canvas, window):
self.update() # Make axes menu
self.bLeft = self._Button(
- text="Left", file="stock_left.ppm",
+ text="Left", file="stock_left",
command=lambda x=-1: self.panx(x))
self.bRight = self._Button(
- text="Right", file="stock_right.ppm",
+ text="Right", file="stock_right",
command=lambda x=1: self.panx(x))
self.bZoomInX = self._Button(
- text="ZoomInX",file="stock_zoom-in.ppm",
+ text="ZoomInX",file="stock_zoom-in",
command=lambda x=1: self.zoomx(x))
self.bZoomOutX = self._Button(
- text="ZoomOutX", file="stock_zoom-out.ppm",
+ text="ZoomOutX", file="stock_zoom-out",
command=lambda x=-1: self.zoomx(x))
self.bUp = self._Button(
- text="Up", file="stock_up.ppm",
+ text="Up", file="stock_up",
command=lambda y=1: self.pany(y))
self.bDown = self._Button(
- text="Down", file="stock_down.ppm",
+ text="Down", file="stock_down",
command=lambda y=-1: self.pany(y))
self.bZoomInY = self._Button(
- text="ZoomInY", file="stock_zoom-in.ppm",
+ text="ZoomInY", file="stock_zoom-in",
command=lambda y=1: self.zoomy(y))
self.bZoomOutY = self._Button(
- text="ZoomOutY",file="stock_zoom-out.ppm",
+ text="ZoomOutY",file="stock_zoom-out",
command=lambda y=-1: self.zoomy(y))
self.bSave = self._Button(
- text="Save", file="stock_save_as.ppm",
+ text="Save", file="stock_save_as",
command=self.save_figure)
self.pack(side=Tk.BOTTOM, fill=Tk.X)
@@ -722,7 +763,7 @@ def update(self):
class NavigationToolbar2TkAgg(NavigationToolbar2, Tk.Frame):
"""
- Public attriubutes
+ Public attributes
canvas - the FigureCanvas (gtk.DrawingArea)
win - the gtk.Window
@@ -762,9 +803,9 @@ def release(self, event):
def set_cursor(self, cursor):
self.window.configure(cursor=cursord[cursor])
- def _Button(self, text, file, command):
- file = os.path.join(rcParams['datapath'], 'images', file)
- im = Tk.PhotoImage(master=self, file=file)
+ def _Button(self, text, file, command, extension='.ppm'):
+ img_file = os.path.join(rcParams['datapath'], 'images', file + extension)
+ im = Tk.PhotoImage(master=self, file=img_file)
b = Tk.Button(
master=self, text=text, padx=2, pady=2, image=im, command=command)
b._ntimage = im
@@ -780,27 +821,16 @@ def _init_toolbar(self):
self.update() # Make axes menu
- self.bHome = self._Button( text="Home", file="home.ppm",
- command=self.home)
-
- self.bBack = self._Button( text="Back", file="back.ppm",
- command = self.back)
-
- self.bForward = self._Button(text="Forward", file="forward.ppm",
- command = self.forward)
-
- self.bPan = self._Button( text="Pan", file="move.ppm",
- command = self.pan)
-
- self.bZoom = self._Button( text="Zoom",
- file="zoom_to_rect.ppm",
- command = self.zoom)
-
- self.bsubplot = self._Button( text="Configure Subplots", file="subplots.ppm",
- command = self.configure_subplots)
-
- self.bsave = self._Button( text="Save", file="filesave.ppm",
- command = self.save_figure)
+ for text, tooltip_text, image_file, callback in self.toolitems:
+ if text is None:
+ # spacer, unhandled in Tk
+ pass
+ else:
+ button = self._Button(text=text, file=image_file,
+ command=getattr(self, callback))
+ if tooltip_text is not None:
+ ToolTip.createToolTip(button, tooltip_text)
+
self.message = Tk.StringVar(master=self)
self._message_label = Tk.Label(master=self, textvariable=self.message)
self._message_label.pack(side=Tk.RIGHT)
@@ -878,3 +908,54 @@ def dynamic_update(self):
FigureManager = FigureManagerTkAgg
+
+
+class ToolTip(object):
+ """
+ Tooltip recipe from
+ http://www.voidspace.org.uk/python/weblog/arch_d7_2006_07_01.shtml#e387
+ """
+ @staticmethod
+ def createToolTip(widget, text):
+ toolTip = ToolTip(widget)
+ def enter(event):
+ toolTip.showtip(text)
+ def leave(event):
+ toolTip.hidetip()
+ widget.bind('<Enter>', enter)
+ widget.bind('<Leave>', leave)
+
+ def __init__(self, widget):
+ self.widget = widget
+ self.tipwindow = None
+ self.id = None
+ self.x = self.y = 0
+
+ def showtip(self, text):
+ "Display text in tooltip window"
+ self.text = text
+ if self.tipwindow or not self.text:
+ return
+ x, y, _, _ = self.widget.bbox("insert")
+ x = x + self.widget.winfo_rootx() + 27
+ y = y + self.widget.winfo_rooty()
+ self.tipwindow = tw = Tk.Toplevel(self.widget)
+ tw.wm_overrideredirect(1)
+ tw.wm_geometry("+%d+%d" % (x, y))
+ try:
+ # For Mac OS
+ tw.tk.call("::tk::unsupported::MacWindowStyle",
+ "style", tw._w,
+ "help", "noActivates")
+ except Tk.TclError:
+ pass
+ label = Tk.Label(tw, text=self.text, justify=Tk.LEFT,
+ background="#ffffe0", relief=Tk.SOLID, borderwidth=1,
+ )
+ label.pack(ipadx=1)
+
+ def hidetip(self):
+ tw = self.tipwindow
+ self.tipwindow = None
+ if tw:
+ tw.destroy()
View
83 lib/matplotlib/backends/backend_wx.py
@@ -1242,13 +1242,20 @@ def _get_key(self, evt):
keyval = evt.m_keyCode
if keyval in self.keyvald:
key = self.keyvald[keyval]
- elif keyval <256:
+ elif keyval < 256:
key = chr(keyval)
+ # wx always returns an uppercase, so make it lowercase if the shift
+ # key is not depressed (NOTE: this will not handle Caps Lock)
+ if not evt.ShiftDown():
+ key = key.lower()
else:
key = None
- # why is wx upcasing this?
- if key is not None: key = key.lower()
+ for meth, prefix in (
+ [evt.AltDown, 'alt'],
+ [evt.ControlDown, 'ctrl'], ):
+ if meth():
+ key = '{}+{}'.format(prefix, key)
return key
@@ -1476,6 +1483,7 @@ def __init__(self, num, fig):
self.SetStatusBar(statbar)
self.canvas = self.get_canvas(fig)
self.canvas.SetInitialSize(wx.Size(fig.bbox.width, fig.bbox.height))
+ self.canvas.SetFocus()
self.sizer =wx.BoxSizer(wx.VERTICAL)
self.sizer.Add(self.canvas, 1, wx.TOP | wx.LEFT | wx.EXPAND)
# By adding toolbar in sizer, we are able to put it at the bottom
@@ -1742,8 +1750,6 @@ def updateButtonText(self, lst):
self.SetLabel("Axes: %s" % axis_txt[:-1])
-
-
cursord = {
cursors.MOVE : wx.CURSOR_HAND,
cursors.HAND : wx.CURSOR_HAND,
@@ -1787,57 +1793,33 @@ def _init_toolbar(self):
DEBUG_MSG("_init_toolbar", 1, self)
self._parent = self.canvas.GetParent()
- _NTB2_HOME =wx.NewId()
- self._NTB2_BACK =wx.NewId()
- self._NTB2_FORWARD =wx.NewId()
- self._NTB2_PAN =wx.NewId()
- self._NTB2_ZOOM =wx.NewId()
- _NTB2_SAVE = wx.NewId()
- _NTB2_SUBPLOT =wx.NewId()
-
- self.SetToolBitmapSize(wx.Size(24,24))
-
- self.AddSimpleTool(_NTB2_HOME, _load_bitmap('home.png'),
- 'Home', 'Reset original view')
- self.AddSimpleTool(self._NTB2_BACK, _load_bitmap('back.png'),
- 'Back', 'Back navigation view')
- self.AddSimpleTool(self._NTB2_FORWARD, _load_bitmap('forward.png'),
- 'Forward', 'Forward navigation view')
- # todo: get new bitmap
- self.AddCheckTool(self._NTB2_PAN, _load_bitmap('move.png'),
- shortHelp='Pan',
- longHelp='Pan with left, zoom with right')
- self.AddCheckTool(self._NTB2_ZOOM, _load_bitmap('zoom_to_rect.png'),
- shortHelp='Zoom', longHelp='Zoom to rectangle')
- self.AddSeparator()
- self.AddSimpleTool(_NTB2_SUBPLOT, _load_bitmap('subplots.png'),
- 'Configure subplots', 'Configure subplot parameters')
- self.AddSimpleTool(_NTB2_SAVE, _load_bitmap('filesave.png'),
- 'Save', 'Save plot contents to file')
-
- bind(self, wx.EVT_TOOL, self.home, id=_NTB2_HOME)
- bind(self, wx.EVT_TOOL, self.forward, id=self._NTB2_FORWARD)
- bind(self, wx.EVT_TOOL, self.back, id=self._NTB2_BACK)
- bind(self, wx.EVT_TOOL, self.zoom, id=self._NTB2_ZOOM)
- bind(self, wx.EVT_TOOL, self.pan, id=self._NTB2_PAN)
- bind(self, wx.EVT_TOOL, self.configure_subplot, id=_NTB2_SUBPLOT)
- bind(self, wx.EVT_TOOL, self.save, id=_NTB2_SAVE)
+ self.wx_ids = {}
+ for text, tooltip_text, image_file, callback in self.toolitems:
+ if text is None:
+ self.AddSeparator()
+ continue
+ self.wx_ids[text] = wx.NewId()
+ if text in ['Pan', 'Zoom']:
+ self.AddCheckTool(self.wx_ids[text], _load_bitmap(image_file + '.png'),
+ shortHelp=text, longHelp=tooltip_text)
+ else:
+ self.AddSimpleTool(self.wx_ids[text], _load_bitmap(image_file + '.png'),
+ text, tooltip_text)
+ bind(self, wx.EVT_TOOL, getattr(self, callback), id=self.wx_ids[text])
self.Realize()
-
def zoom(self, *args):
- self.ToggleTool(self._NTB2_PAN, False)
+ self.ToggleTool(self.wx_ids['Pan'], False)
NavigationToolbar2.zoom(self, *args)
def pan(self, *args):
- self.ToggleTool(self._NTB2_ZOOM, False)
+ self.ToggleTool(self.wx_ids['Zoom'], False)
NavigationToolbar2.pan(self, *args)
-
- def configure_subplot(self, evt):
+ def configure_subplots(self, evt):
frame = wx.Frame(None, -1, "Configure subplots")
toolfig = Figure((6,3))
@@ -1855,7 +1837,7 @@ def configure_subplot(self, evt):
tool = SubplotTool(self.canvas.figure, toolfig)
frame.Show()
- def save(self, evt):
+ def save_figure(self, *args):
# Fetch the required filename and file type.
filetypes, exts, filter_index = self.canvas._get_imagesave_wildcards()
default_file = "image." + self.canvas.get_default_filetype()
@@ -1881,7 +1863,7 @@ def save(self, evt):
os.path.join(dirname, filename), format=format)
except Exception as e:
error_msg_wx(str(e))
-
+
def set_cursor(self, cursor):
cursor =wx.StockCursor(cursord[cursor])
self.canvas.SetCursor( cursor )
@@ -1948,8 +1930,8 @@ def set_message(self, s):
def set_history_buttons(self):
can_backward = (self._views._pos > 0)
can_forward = (self._views._pos < len(self._views._elements) - 1)
- self.EnableTool(self._NTB2_BACK, can_backward)
- self.EnableTool(self._NTB2_FORWARD, can_forward)
+ self.EnableTool(self.wx_ids['Back'], can_backward)
+ self.EnableTool(self.wx_ids['Forward'], can_forward)
class NavigationToolbarWx(wx.ToolBar):
@@ -2149,13 +2131,12 @@ def _onMouseWheel(self, evt):
direction = -1
self.button_fn(direction)
- _onSave = NavigationToolbar2Wx.save
+ _onSave = NavigationToolbar2Wx.save_figure
def _onClose(self, evt):
self.GetParent().Destroy()
-
class StatusBarWx(wx.StatusBar):
"""
A status bar is added to _FigureFrame to allow measurements and the
View
7 lib/matplotlib/rcsetup.py
@@ -574,14 +574,15 @@ def __call__(self, s):
'agg.path.chunksize' : [0, validate_int], # 0 to disable chunking;
# recommend about 20000 to
# enable. Experimental.
- # key-mappings
- 'keymap.fullscreen' : ['f', validate_stringlist],
+ # key-mappings (multi-character mappings should be a list/tuple)
+ 'keymap.fullscreen' : [('f', 'ctrl+f'), validate_stringlist],
'keymap.home' : [['h', 'r', 'home'], validate_stringlist],
'keymap.back' : [['left', 'c', 'backspace'], validate_stringlist],
'keymap.forward' : [['right', 'v'], validate_stringlist],
'keymap.pan' : ['p', validate_stringlist],
'keymap.zoom' : ['o', validate_stringlist],
- 'keymap.save' : ['s', validate_stringlist],
+ 'keymap.save' : [('s', 'ctrl+s'), validate_stringlist],
+ 'keymap.quit' : [('ctrl+w', ), validate_stringlist],
'keymap.grid' : ['g', validate_stringlist],
'keymap.yscale' : ['l', validate_stringlist],
'keymap.xscale' : [['k', 'L'], validate_stringlist],
View
1  matplotlibrc.template
@@ -410,6 +410,7 @@ text.hinting_factor : 8 # Specifies the amount of softness for hinting in the
#keymap.pan : p # pan mnemonic
#keymap.zoom : o # zoom mnemonic
#keymap.save : s # saving current figure
+#keymap.quit : ctrl+w # close the current figure
#keymap.grid : g # switching on/off a grid in current axes
#keymap.yscale : l # toggle scaling of y-axes ('log'/'linear')
#keymap.xscale : L, k # toggle scaling of x-axes ('log'/'linear')
View
2  src/_macosx.m
@@ -5416,6 +5416,7 @@ - (void)keyDown:(NSEvent*)event
{
PyObject* result;
const char* s = [self convertKeyEvent: event];
+ /* TODO: Handle ctrl, alt, super modifiers. qt4 has implemented these. */
PyGILState_STATE gstate = PyGILState_Ensure();
if (s==NULL)
{
@@ -5437,6 +5438,7 @@ - (void)keyUp:(NSEvent*)event
{
PyObject* result;
const char* s = [self convertKeyEvent: event];
+ /* TODO: Handle ctrl, alt, super modifiers. qt4 has implemented these. */
PyGILState_STATE gstate = PyGILState_Ensure();
if (s==NULL)
{
Something went wrong with that request. Please try again.