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

Reply via email to