Hi,
Attached is a new patch to replace the previous one that I sent that
does what Gael suggested. It works well and seems fairly efficient to
me, but again I am new to python and I could be mistaken about what Gael
was suggesting. Basically, I created a base class that does the
blocking and collects events of any particular set of types specified by
name at initiation. I then created two subclasses that specify exactly
which events they deal with and do additional processing beyond just
collecting events, one for mouse events and one for mouse+keyboard
events. These are then called by ginput and waitforbuttonpress
respectively. I also changed my version of waitforbuttonpress so that
it precisely matches the functionality of matlab's waitforbuttonpress,
with the exception of a timeout option.
Comments welcome.
Cheers,
David
On Fri, 2008-07-11 at 16:12 +0200, Gael Varoquaux wrote:
> On Fri, Jul 11, 2008 at 03:22:30PM +0200, David M. Kaplan wrote:
> > The way I have implemented it is by adding an additional class
> > BlockingKeyMouseInput, which is quite similar to BlockingMouseInput, but
> > waits for both key and mouse events. A smarter person than I could
> > probably combine these two classes and make something that would serve
> > both functions. But I am basically new to python and don't feel
> > comfortable. Perhaps someone else could take a look and make
> > improvements/simplifications?
>
> The only significantly different lines are the two lines where an
> mplconnect is done to register the callback. You could abstract this in a
> method and then have a base class and two sub classes for each call: the
> blocking from mouse and the blocking from button.
>
> > The other thing that I have noticed with both ginput and
> > waitforbuttonpress is that if you use ctrl-c to break out of either,
> > then the callback functions remain attached to their respective events
> > (e.g., try ginput(n=-1,timeout=-1,verbose=True) and hit ctrl-c). This
> > probably isn't a huge problem, but it would be nice if there was a way
> > to say "if ctrl-c is pressed, cleanup nicely". Does someone know if
> > that is possible?
>
> I think this is a good usecase for a try: ... finally: ... .
>
> I don't have time to do these changes right now, as I am very busy both
> with IPython and Mayavi, and will be travelling next two weeks, but you
> can have a good at them, and someone else will probably commit your
> patch, if you removed the code duplication.
>
> Cheers,
>
> Gaƫl
>
--
**********************************
David M. Kaplan
Assistant Researcher
UCSC / Institute of Marine Sciences
Ocean Sciences
1156 High St.
SC, CA 95064
Phone: 831-459-4789
Fax: 831-459-4882
http://pmc.ucsc.edu/~dmk/
**********************************
Index: lib/matplotlib/pyplot.py
===================================================================
--- lib/matplotlib/pyplot.py (revision 5735)
+++ lib/matplotlib/pyplot.py (working copy)
@@ -335,7 +335,21 @@
if Figure.ginput.__doc__ is not None:
ginput.__doc__ = dedent(Figure.ginput.__doc__)
+def waitforbuttonpress(*args, **kwargs):
+ """
+ Blocking call to interact with the figure.
+ This will wait for *n* key or mouse clicks from the user and
+ return a list containing True's for keyboard clicks and False's
+ for mouse clicks.
+
+ If *timeout* is negative, does not timeout.
+ """
+ return gcf().waitforbuttonpress(*args, **kwargs)
+if Figure.waitforbuttonpress.__doc__ is not None:
+ waitforbuttonpress.__doc__ = dedent(Figure.waitforbuttonpress.__doc__)
+
+
# Putting things in figures
def figtext(*args, **kwargs):
Index: lib/matplotlib/figure.py
===================================================================
--- lib/matplotlib/figure.py (revision 5735)
+++ lib/matplotlib/figure.py (working copy)
@@ -6,8 +6,16 @@
:class:`SubplotParams`
control the default spacing of the subplots
+:class:`BlockingInput`
+ creates a callable object to retrieve events in a blocking way for interactive sessions
+
+:class:`BlockingKeyMouseInput`
+ creates a callable object to retrieve key or mouse clicks in a blocking way for interactive sessions.
+ Note: Subclass of BlockingInput. Used by waitforbuttonpress
+
:class:`BlockingMouseInput`
- creates a callable object to retrieve mouse clicks in a blocking way for interactive sessions
+ creates a callable object to retrieve mouse clicks in a blocking way for interactive sessions.
+ Note: Subclass of BlockingInput. Used by ginput
:class:`Figure`
top level container for all plot elements
@@ -118,24 +126,107 @@
setattr(self, s, val)
-class BlockingMouseInput(object):
+class BlockingInput(object):
"""
- Class that creates a callable object to retrieve mouse clicks in a
+ Class that creates a callable object to retrieve events in a
blocking way.
"""
- def __init__(self, fig):
+ def __init__(self, fig, eventslist=()):
self.fig = fig
+ assert isinstance(eventslist, tuple), \
+ "Requires a tuple of event name strings"
+ self.eventslist = eventslist
-
- def on_click(self, event):
+ def on_event(self, event):
"""
Event handler that will be passed to the current figure to
- retrieve clicks.
+ retrieve events.
"""
+ self.events.append(event)
+
+ if self.verbose:
+ print "Event %i" % len(self.events)
+
+ # This will extract info from events
+ self.post_event()
+
+ if len(self.events) >= self.n and self.n > 0:
+ self.done = True
+
+ def post_event(self):
+ """For baseclass, do nothing but collect events"""
+ pass
+
+ def cleanup(self):
+ """Remove callbacks"""
+ for cb in self.callbacks:
+ self.fig.canvas.mpl_disconnect(cb)
+
+ self.callbacks=[]
+
+ def __call__(self, n=1, timeout=30, verbose=False ):
+ """
+ Blocking call to retrieve n events
+ """
+
+ assert isinstance(n, int), "Requires an integer argument"
+ self.n = n
+
+ self.events = []
+ self.done = False
+ self.verbose = verbose
+ self.callbacks = []
+
+ # Ensure that the figure is shown
+ self.fig.show()
+
+ # connect the events to the on_event function call
+ for n in self.eventslist:
+ self.callbacks.append( self.fig.canvas.mpl_connect(n, self.on_event) )
+
+ try:
+ # wait for n clicks
+ counter = 0
+ while not self.done:
+ self.fig.canvas.flush_events()
+ time.sleep(0.01)
+
+ # check for a timeout
+ counter += 1
+ if timeout > 0 and counter > timeout/0.01:
+ print "Timeout reached";
+ break;
+ finally: # Activated on exception like ctrl-c
+ self.cleanup()
+
+ # Disconnect the callbacks
+ self.cleanup()
+
+ # Return the events in this case
+ return self.events
+
+class BlockingMouseInput(BlockingInput):
+ """
+ Class that creates a callable object to retrieve mouse clicks in a
+ blocking way.
+ """
+ def __init__(self, fig):
+ BlockingInput.__init__(self, fig=fig, eventslist=('button_press_event',) )
+
+ def post_event(self):
+ """
+ This will be called to process events
+ """
+ assert len(self.events)>0, "No events yet"
+
+ event = self.events[-1]
+
if event.button == 3:
# If it's a right click, pop the last coordinates.
if len(self.clicks) > 0:
self.clicks.pop()
+ del self.events[-2:] # Remove button=3 event and previous event
+
if self.show_clicks:
mark = self.marks.pop()
mark.remove()
@@ -153,51 +244,56 @@
self.marks.extend(
event.inaxes.plot([event.xdata,], [event.ydata,], 'r+') )
self.fig.canvas.draw()
- if self.n > 0 and len(self.clicks) >= self.n:
- self.done = True
+ def cleanup(self):
+ # clean the figure
+ if self.show_clicks:
+ for mark in self.marks:
+ mark.remove()
+ self.marks = []
+ self.fig.canvas.draw()
+ # Call base class to remove callbacks
+ BlockingInput.cleanup(self)
+
def __call__(self, n=1, timeout=30, verbose=False, show_clicks=True):
"""
Blocking call to retrieve n coordinate pairs through mouse
clicks.
"""
- self.verbose = verbose
- self.done = False
+ self.show_clicks = show_clicks
self.clicks = []
- self.show_clicks = True
self.marks = []
+ BlockingInput.__call__(self,n=n,timeout=timeout,verbose=verbose)
- assert isinstance(n, int), "Requires an integer argument"
- self.n = n
+ return self.clicks
- # Ensure that the figure is shown
- self.fig.show()
- # connect the click events to the on_click function call
- self.callback = self.fig.canvas.mpl_connect('button_press_event',
- self.on_click)
- # wait for n clicks
- counter = 0
- while not self.done:
- self.fig.canvas.flush_events()
- time.sleep(0.01)
+class BlockingKeyMouseInput(BlockingInput):
+ """
+ Class that creates a callable object to retrieve a single mouse or
+ keyboard click
+ """
+ def __init__(self, fig):
+ BlockingInput.__init__(self, fig=fig, eventslist=('button_press_event','key_press_event') )
- # check for a timeout
- counter += 1
- if timeout > 0 and counter > timeout/0.01:
- print "ginput timeout";
- break;
+ def post_event(self):
+ """
+ Determines if it is a key event
+ """
+ assert len(self.events)>0, "No events yet"
- # Disconnect the event, clean the figure, and return what we have
- self.fig.canvas.mpl_disconnect(self.callback)
- self.callback = None
- if self.show_clicks:
- for mark in self.marks:
- mark.remove()
- self.fig.canvas.draw()
- return self.clicks
+ self.keyormouse = self.events[-1].name == 'key_press_event'
+ def __call__(self, timeout=30, verbose=False):
+ """
+ Blocking call to retrieve a single mouse or key click
+ Returns True if key click, False if mouse, or None if timeout
+ """
+ self.keyormouse = None
+ BlockingInput.__call__(self,n=1,timeout=timeout,verbose=verbose)
+ return self.keyormouse
+
class Figure(Artist):
"""
@@ -1117,10 +1213,27 @@
blocking_mouse_input = BlockingMouseInput(self)
return blocking_mouse_input(n=n, timeout=timeout,
- verbose=verbose, show_clicks=True)
+ verbose=verbose, show_clicks=show_clicks)
+ def waitforbuttonpress(self, timeout=-1):
+ """
+ call signature::
+ waitforbuttonpress(self, timeout=-1)
+ Blocking call to interact with the figure.
+
+ This will return True is a key was pressed, False if a mouse
+ button was pressed and None if *timeout* was reached without
+ either being pressed.
+
+ If *timeout* is negative, does not timeout.
+ """
+
+ blocking_input = BlockingKeyMouseInput(self)
+ return blocking_input(timeout=timeout)
+
+
def figaspect(arg):
"""
Create a figure with specified aspect ratio. If *arg* is a number,
-------------------------------------------------------------------------
Sponsored by: SourceForge.net Community Choice Awards: VOTE NOW!
Studies have shown that voting for your favorite open source project,
along with a healthy diet, reduces your potential for chronic lameness
and boredom. Vote Now at http://www.sourceforge.net/community/cca08
_______________________________________________
Matplotlib-devel mailing list
Matplotlib-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/matplotlib-devel