Revision: 5782
          http://matplotlib.svn.sourceforge.net/matplotlib/?rev=5782&view=rev
Author:   dmkaplan
Date:     2008-07-17 19:20:32 +0000 (Thu, 17 Jul 2008)

Log Message:
-----------
Adding more functionality for interacting with figure windows through
mouse clicks and keyboard clicks.  This includes adding a
waitforbuttonpress function, as well as manual contour label location
selection for clabel.  The underlying Blocking* classes that drive
this interaction have been moved into a separate file
"lib/matplotlib/blocking_input.py".  Also added a changelog entry.

Modified Paths:
--------------
    trunk/matplotlib/CHANGELOG
    trunk/matplotlib/lib/matplotlib/contour.py
    trunk/matplotlib/lib/matplotlib/figure.py
    trunk/matplotlib/lib/matplotlib/pyplot.py

Added Paths:
-----------
    trunk/matplotlib/lib/matplotlib/blocking_input.py

Modified: trunk/matplotlib/CHANGELOG
===================================================================
--- trunk/matplotlib/CHANGELOG  2008-07-17 19:15:58 UTC (rev 5781)
+++ trunk/matplotlib/CHANGELOG  2008-07-17 19:20:32 UTC (rev 5782)
@@ -1,3 +1,6 @@
+2008-07-17 Added ability to manually select contour label locations.
+          Also added a waitforbuttonpress function. - DMK
+
 2008-07-17 Fix bug with NaNs at end of path (thanks, Andrew Straw for
            the report) - MGD
 

Added: trunk/matplotlib/lib/matplotlib/blocking_input.py
===================================================================
--- trunk/matplotlib/lib/matplotlib/blocking_input.py                           
(rev 0)
+++ trunk/matplotlib/lib/matplotlib/blocking_input.py   2008-07-17 19:20:32 UTC 
(rev 5782)
@@ -0,0 +1,540 @@
+"""
+This provides several classes used for blocking interaction with figure 
windows:
+
+: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.  
+    Note: Subclass of BlockingInput.  Used by ginput
+
+:class:`BlockingContourLabeler`
+    creates a callable object to retrieve mouse clicks in a blocking way that 
will then be used to place labels on a ContourSet
+    Note: Subclass of BlockingMouseInput.  Used by clabel
+"""
+
+import time
+import numpy as np
+import matplotlib.path as path
+
+class BlockingInput(object):
+    """
+    Class that creates a callable object to retrieve events in a
+    blocking way.
+    """
+    def __init__(self, fig, eventslist=()):
+        self.fig = fig
+        assert isinstance(eventslist, tuple), "Requires a tuple of event name 
strings"
+        self.eventslist = eventslist
+
+    def on_event(self, event):
+        """
+        Event handler that will be passed to the current figure to
+        retrieve events.
+        """
+        # Add a new event to list - using a separate function is
+        # overkill for the base class, but this is consistent with
+        # subclasses
+        self.add_event(event)
+
+        if self.verbose:
+            print "Event %i" % len(self.events)
+
+        # This will extract info from events
+        self.post_event()
+        
+        # Check if we have enough events already
+        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):
+        """Disconnect all callbacks"""
+        for cb in self.callbacks:
+            self.fig.canvas.mpl_disconnect(cb)
+
+        self.callbacks=[]
+
+    def add_event(self,event):
+        """For base class, this just appends an event to events."""
+        self.events.append(event)
+
+    def pop_event(self,index=-1):
+        """
+        This removes an event from the event list.  Defaults to
+        removing last event, but an index can be supplied.  Note that
+        this does not check that there are events, much like the
+        normal pop method.  If not events exist, this will throw an
+        exception.
+        """
+        self.events.pop(index)
+
+    def pop(self,index=-1):
+        self.pop_event(index)
+    pop.__doc__=pop_event.__doc__
+
+    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]
+        button = event.button
+
+        # Using additional methods for each button is a bit overkill
+        # for this class, but it allows for easy overloading.  Also,
+        # this would make it easy to attach other type of non-mouse
+        # events to these "mouse" actions.  For example, the matlab
+        # version of ginput also allows you to add points with
+        # keyboard clicks.  This could easily be added to this class
+        # with relatively minor modification to post_event and
+        # __init__.
+        if button == 3:
+            self.button3(event)
+        elif button == 2:
+            self.button2(event)
+        else:
+            self.button1(event)
+
+    def button1( self, event ):
+        """
+        Will be called for any event involving a button other than
+        button 2 or 3.  This will add a click if it is inside axes.
+        """
+        if event.inaxes:
+            self.add_click(event)
+        else: # If not a valid click, remove from event list
+            BlockingInput.pop(self)
+
+    def button2( self, event ):
+        """
+        Will be called for any event involving button 2.
+        Button 2 ends blocking input.
+        """
+
+        # Remove last event just for cleanliness
+        BlockingInput.pop(self)
+
+        # This will exit even if not in infinite mode.  This is
+        # consistent with matlab and sometimes quite useful, but will
+        # require the user to test how many points were actually
+        # returned before using data.
+        self.done = True
+
+    def button3( self, event ):
+        """
+        Will be called for any event involving button 3.
+        Button 3 removes the last click.
+        """
+        # Remove this last event
+        BlockingInput.pop(self)
+
+        # Now remove any existing clicks if possible
+        if len(self.events)>0:
+            self.pop()
+
+    def add_click(self,event):
+        """
+        This add the coordinates of an event to the list of clicks
+        """
+        self.clicks.append((event.xdata,event.ydata))
+        if self.verbose:
+            print "input %i: %f,%f" % (len(self.clicks),
+                                       event.xdata, event.ydata)
+
+        # If desired plot up click
+        if self.show_clicks:
+            self.marks.extend(
+                event.inaxes.plot([event.xdata,], [event.ydata,], 'r+') )
+            self.fig.canvas.draw()
+
+    def pop_click(self,index=-1):
+        """
+        This removes a click from the list of clicks.  Defaults to
+        removing the last click.
+        """
+        self.clicks.pop(index)
+        
+        if self.show_clicks:
+            mark = self.marks.pop(index)
+            mark.remove()
+            self.fig.canvas.draw()
+
+    def pop(self,index=-1):
+        """
+        This removes a click and the associated event from the object.
+        Defaults to removing the last click, but any index can be
+        supplied.
+        """
+        self.pop_click(index)
+        BlockingInput.pop(self,index)
+
+    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.show_clicks = show_clicks
+        self.clicks      = []
+        self.marks       = []
+        BlockingInput.__call__(self,n=n,timeout=timeout,verbose=verbose)
+
+        return self.clicks
+
+class BlockingContourLabeler( BlockingMouseInput ):
+    """
+    Class that creates a callable object that uses mouse clicks on a
+    figure window to place contour labels.
+    """
+    def __init__(self,cs):
+        self.cs = cs
+        BlockingMouseInput.__init__(self, fig=cs.ax.figure )
+
+    def button1(self,event):
+        """
+        This will be called if an event involving a button other than
+        2 or 3 occcurs.  This will add a label to a contour.
+        """
+        if event.inaxes == self.cs.ax:
+            conmin,segmin,imin,xmin,ymin = self.cs.find_nearest_contour(
+                event.x, event.y)[:5]
+
+            paths = self.cs.collections[conmin].get_paths()
+            lc = paths[segmin].vertices
+
+            # Figure out label rotation.  This is very cludgy.
+            # Ideally, there would be one method in ContourLabeler
+            # that would figure out the best rotation for a label at a
+            # point, but the way automatic label rotation is done is
+            # quite mysterious to me and doesn't seem easy to
+            # generalize to non-automatic label placement.  The method
+            # used below is not very robust!  It basically looks one
+            # point before and one point after label location on
+            # contour and takes mean of angles of two vectors formed.
+            # This produces "acceptable" results, but not nearly as
+            # nice as automatic method.
+            ll = lc[max(0,imin-1):imin+2] # Get points around point
+            dd = np.diff(ll,axis=0)
+            rotation = np.mean( np.arctan2(dd[:,1], dd[:,0]) ) * 180 / np.pi
+            if rotation > 90:
+                rotation = rotation -180
+            if rotation < -90:
+                rotation = 180 + rotation
+
+            self.cs.add_label(xmin,ymin,rotation,conmin)
+
+            if self.inline:
+                # Get label width for breaking contours
+                lw = self.cs.get_label_width(self.cs.label_levels[conmin], 
+                                             self.cs.fmt, 
+                                             self.cs.fslist[conmin])
+                # Break contour
+                new=self.cs.break_linecontour(lc,rotation,lw,imin)
+                if len(new[0]):
+                    paths[segmin] = path.Path(new[0])
+                if len(new[1]):
+                    paths.extend([path.Path(new[1])])
+
+            self.fig.canvas.draw()
+        else: # Remove event if not valid
+            BlockingInput.pop(self)
+            
+    def button3(self,event):
+        """
+        This will be called if button 3 is clicked.  This will remove
+        a label if not in inline mode.  Unfortunately, if one is doing
+        inline labels, then there is currently no way to fix the
+        broken contour - once humpty-dumpty is broken, he can't be put
+        back together.  In inline mode, this does nothing.
+        """
+        if self.inline:
+            pass
+        else:
+            self.cs.pop_label()
+            self.cs.ax.figure.canvas.draw()
+
+    def __call__(self,inline,n=-1,timeout=-1):
+        self.inline=inline
+        BlockingMouseInput.__call__(self,n=n,timeout=timeout,verbose=False,
+                                    show_clicks=False)
+
+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') )
+
+    def post_event(self):
+        """
+        Determines if it is a key event
+        """
+        assert len(self.events)>0, "No events yet"
+
+        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
+
+"""
+This provides several classes used for interaction with figure windows:
+
+: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.  
+    Note: Subclass of BlockingInput.  Used by ginput
+"""
+
+import time
+
+class BlockingInput(object):
+    """
+    Class that creates a callable object to retrieve events in a
+    blocking way.
+    """
+    def __init__(self, fig, eventslist=()):
+        self.fig = fig
+        assert isinstance(eventslist, tuple), \
+            "Requires a tuple of event name strings"
+        self.eventslist = eventslist
+
+    def on_event(self, event):
+        """
+        Event handler that will be passed to the current figure to
+        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()
+                    self.fig.canvas.draw()
+        elif event.button == 2 and self.n < 0:
+            # If it's a middle click, and we are in infinite mode, finish
+            self.done = True
+        elif event.inaxes:
+            # If it's a valid click, append the coordinates to the list
+            self.clicks.append((event.xdata, event.ydata))
+            if self.verbose:
+                print "input %i: %f,%f" % (len(self.clicks),
+                                    event.xdata, event.ydata)
+            if self.show_clicks:
+                self.marks.extend(
+                    event.inaxes.plot([event.xdata,], [event.ydata,], 'r+') )
+                self.fig.canvas.draw()
+
+    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.show_clicks = show_clicks
+        self.clicks      = []
+        self.marks       = []
+        BlockingInput.__call__(self,n=n,timeout=timeout,verbose=verbose)
+
+        return self.clicks
+
+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') )
+
+    def post_event(self):
+        """
+        Determines if it is a key event
+        """
+        assert len(self.events)>0, "No events yet"
+
+        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
+

Modified: trunk/matplotlib/lib/matplotlib/contour.py
===================================================================
--- trunk/matplotlib/lib/matplotlib/contour.py  2008-07-17 19:15:58 UTC (rev 
5781)
+++ trunk/matplotlib/lib/matplotlib/contour.py  2008-07-17 19:20:32 UTC (rev 
5782)
@@ -17,6 +17,9 @@
 import matplotlib.text as text
 import matplotlib.cbook as cbook
 
+# Import needed for adding manual selection capability to clabel
+from matplotlib.blocking_input import BlockingContourLabeler
+
 # We can't use a single line collection for contour because a line
 # collection can have only a single line style, and we want to be able to have
 # dashed negative contours, for example, and solid positive contours.
@@ -69,6 +72,13 @@
           *fmt*:
             a format string for the label. Default is '%1.3f'
 
+          *manual*:
+            if *True*, contour labels will be placed manually using
+            mouse clicks.  Click the first button near a contour to
+            add a label, click the second button (or potentially both
+            mouse buttons at once) to finish adding labels.  The third
+            button can be used to remove the last label added, but
+            only if labels are not inline.
 
         """
         fontsize = kwargs.get('fontsize', None)
@@ -76,8 +86,9 @@
         self.fmt = kwargs.get('fmt', '%1.3f')
         _colors = kwargs.get('colors', None)
 
+        # Detect if manual selection is desired and remove from argument list
+        self.manual_select=kwargs.get('manual',False)
 
-
         if len(args) == 0:
             levels = self.levels
             indices = range(len(self.levels))
@@ -126,10 +137,16 @@
         #self.cl_cvalues = [] # same
         self.cl_xy = []
 
-        self.labels(inline)
+        if self.manual_select:
+            print 'Select label locations manually using first mouse button.'
+            print 'End manual selection with second mouse button.'
+            if not inline:
+                print 'Remove last label by clicking third mouse button.'
 
-        for label in self.cl:
-            self.ax.add_artist(label)
+            blocking_contour_labeler = BlockingContourLabeler(self)
+            blocking_contour_labeler(inline)
+        else:
+            self.labels(inline)
 
         self.label_list =  cbook.silent_list('text.Text', self.cl)
         return self.label_list
@@ -335,19 +352,85 @@
 
         return x,y, rotation, dind
 
+    def add_label(self,x,y,rotation,icon):
+        dx,dy = self.ax.transData.inverted().transform_point((x,y))
+        t = text.Text(dx, dy, rotation = rotation,
+                      horizontalalignment='center',
+                      verticalalignment='center')
+
+        color = self.label_mappable.to_rgba(self.label_cvalues[icon],
+                                            alpha=self.alpha)
+
+        _text = self.get_text(self.label_levels[icon],self.fmt)
+        self.set_label_props(t, _text, color)
+        self.cl.append(t)
+        self.cl_cvalues.append(self.label_cvalues[icon])
+        
+        # Add label to plot here - useful for manual mode label selection
+        self.ax.add_artist(t)
+
+    def pop_label(self,index=-1):
+        '''Defaults to removing last label, but any index can be supplied'''
+        self.cl_cvalues.pop(index)
+        t = self.cl.pop(index)
+        t.remove()
+
+    def find_nearest_contour( self, x, y, pixel=True ):
+        """
+        Finds contour that is closest to a point.  Defaults to
+        measuring distance in pixels (screen space - useful for manual
+        contour labeling), but this can be controlled via a keyword
+        argument.
+
+        Returns a tuple containing the contour, segment, index of
+        segment, x & y of segment point and distance to minimum point.
+        """
+
+        # This function uses a method that is probably quite
+        # inefficient based on converting each contour segment to
+        # pixel coordinates and then comparing the given point to
+        # those coordinates for each contour.  This will probably be
+        # quite slow for complex contours, but for normal use it works
+        # sufficiently well that the time is not noticeable.
+        # Nonetheless, improvements could probably be made.
+
+        dmin = 1e10
+        conmin = None
+        segmin = None
+        xmin = None
+        ymin = None
+
+        for icon in self.label_indices:
+            con = self.collections[icon]
+            paths = con.get_paths()
+            for segNum, linepath in enumerate(paths):
+                lc = linepath.vertices
+
+                # transfer all data points to screen coordinates if desired
+                if pixel:
+                    lc = self.ax.transData.transform(lc)
+                
+                ds = (lc[:,0]-x)**2 + (lc[:,1]-y)**2
+                d = min( ds )
+                if d < dmin:
+                    dmin = d
+                    conmin = icon
+                    segmin = segNum
+                    imin = mpl.mlab.find( ds == d )[0]
+                    xmin = lc[imin,0]
+                    ymin = lc[imin,1]
+
+        return (conmin,segmin,imin,xmin,ymin,dmin)
+
     def labels(self, inline):
         levels = self.label_levels
         fslist = self.fslist
         trans = self.ax.transData
-        _colors = self.label_mappable.to_rgba(self.label_cvalues,
-                                                        alpha=self.alpha)
-        fmt = self.fmt
-        for icon, lev, color, cvalue, fsize in zip(self.label_indices,
-                                          self.label_levels,
-                                          _colors,
-                                          self.label_cvalues, fslist):
+
+        for icon, lev, fsize in zip(self.label_indices,
+                                    self.label_levels, fslist):
             con = self.collections[icon]
-            lw = self.get_label_width(lev, fmt, fsize)
+            lw = self.get_label_width(lev, self.fmt, fsize)
             additions = []
             paths = con.get_paths()
             for segNum, linepath in enumerate(paths):
@@ -362,16 +445,8 @@
                 slc = trans.transform(linecontour)
                 if self.print_label(slc,lw):
                     x,y, rotation, ind  = self.locate_label(slc, lw)
-                    # transfer the location of the label back to
-                    # data coordinates
-                    dx,dy = trans.inverted().transform_point((x,y))
-                    t = text.Text(dx, dy, rotation = rotation,
-                             horizontalalignment='center',
-                             verticalalignment='center')
-                    _text = self.get_text(lev,fmt)
-                    self.set_label_props(t, _text, color)
-                    self.cl.append(t)
-                    self.cl_cvalues.append(cvalue)
+                    self.add_label(x,y,rotation,icon)
+
                     if inline:
                         new = self.break_linecontour(linecontour, rotation, 
lw, ind)
                         if len(new[0]):

Modified: trunk/matplotlib/lib/matplotlib/figure.py
===================================================================
--- trunk/matplotlib/lib/matplotlib/figure.py   2008-07-17 19:15:58 UTC (rev 
5781)
+++ trunk/matplotlib/lib/matplotlib/figure.py   2008-07-17 19:20:32 UTC (rev 
5782)
@@ -6,9 +6,6 @@
 :class:`SubplotParams`
     control the default spacing of the subplots
 
-:class:`BlockingMouseInput`
-    creates a callable object to retrieve mouse clicks in a blocking way for 
interactive sessions
-
 :class:`Figure`
     top level container for all plot elements
 
@@ -32,6 +29,7 @@
 from transforms import Affine2D, Bbox, BboxTransformTo, TransformedBbox
 from projections import projection_factory, get_projection_names, \
     get_projection_class
+from matplotlib.blocking_input import BlockingMouseInput, BlockingKeyMouseInput
 
 import matplotlib.cbook as cbook
 
@@ -117,87 +115,6 @@
 
         setattr(self, s, val)
 
-
-class BlockingMouseInput(object):
-    """
-    Class that creates a callable object to retrieve mouse clicks in a
-    blocking way.
-    """
-    def __init__(self, fig):
-        self.fig = fig
-
-
-    def on_click(self, event):
-        """
-        Event handler that will be passed to the current figure to
-        retrieve clicks.
-        """
-        if event.button == 3:
-            # If it's a right click, pop the last coordinates.
-            if len(self.clicks) > 0:
-                self.clicks.pop()
-                if self.show_clicks:
-                    mark = self.marks.pop()
-                    mark.remove()
-                    self.fig.canvas.draw()
-        elif event.button == 2 and self.n < 0:
-            # If it's a middle click, and we are in infinite mode, finish
-            self.done = True
-        elif event.inaxes:
-            # If it's a valid click, append the coordinates to the list
-            self.clicks.append((event.xdata, event.ydata))
-            if self.verbose:
-                print "input %i: %f,%f" % (len(self.clicks),
-                                    event.xdata, event.ydata)
-            if self.show_clicks:
-                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 __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.clicks      = []
-        self.show_clicks = True
-        self.marks       = []
-
-        assert isinstance(n, int), "Requires an integer argument"
-        self.n = n
-
-        # 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)
-
-            # check for a timeout
-            counter += 1
-            if timeout > 0 and counter > timeout/0.01:
-                print "ginput timeout";
-                break;
-
-        # 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
-
-
 class Figure(Artist):
 
     """
@@ -1121,10 +1038,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,

Modified: trunk/matplotlib/lib/matplotlib/pyplot.py
===================================================================
--- trunk/matplotlib/lib/matplotlib/pyplot.py   2008-07-17 19:15:58 UTC (rev 
5781)
+++ trunk/matplotlib/lib/matplotlib/pyplot.py   2008-07-17 19:20:32 UTC (rev 
5782)
@@ -320,7 +320,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):


This was sent by the SourceForge.net collaborative development platform, the 
world's largest Open Source development site.

-------------------------------------------------------------------------
This SF.Net email is sponsored by the Moblin Your Move Developer's challenge
Build the coolest Linux based applications with Moblin SDK & win great prizes
Grand prize is a trip for two to an Open Source event anywhere in the world
http://moblin-contest.org/redirect.php?banner_id=100&url=/
_______________________________________________
Matplotlib-checkins mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/matplotlib-checkins

Reply via email to