17
17
from six .moves import zip
18
18
19
19
import numpy as np
20
+ from matplotlib import rcParams
20
21
21
22
from .mlab import dist
22
23
from .patches import Circle , Rectangle , Ellipse
@@ -628,6 +629,314 @@ def disconnect(self, cid):
628
629
pass
629
630
630
631
632
+ class TextBox (AxesWidget ):
633
+ """
634
+ A GUI neutral text input box.
635
+
636
+ For the text box to remain responsive you must keep a reference to it.
637
+
638
+ The following attributes are accessible:
639
+
640
+ *ax*
641
+ The :class:`matplotlib.axes.Axes` the button renders into.
642
+
643
+ *label*
644
+ A :class:`matplotlib.text.Text` instance.
645
+
646
+ *color*
647
+ The color of the text box when not hovering.
648
+
649
+ *hovercolor*
650
+ The color of the text box when hovering.
651
+
652
+ Call :meth:`on_text_change` to be updated whenever the text changes.
653
+
654
+ Call :meth:`on_submit` to be updated whenever the user hits enter or
655
+ leaves the text entry field.
656
+ """
657
+
658
+ def __init__ (self , ax , label , initial = '' ,
659
+ color = '.95' , hovercolor = '1' , label_pad = .01 ):
660
+ """
661
+ Parameters
662
+ ----------
663
+ ax : matplotlib.axes.Axes
664
+ The :class:`matplotlib.axes.Axes` instance the button
665
+ will be placed into.
666
+
667
+ label : str
668
+ Label for this text box. Accepts string.
669
+
670
+ initial : str
671
+ Initial value in the text box
672
+
673
+ color : color
674
+ The color of the box
675
+
676
+ hovercolor : color
677
+ The color of the box when the mouse is over it
678
+
679
+ label_pad : float
680
+ the distance between the label and the right side of the textbox
681
+ """
682
+ AxesWidget .__init__ (self , ax )
683
+
684
+ self .DIST_FROM_LEFT = .05
685
+
686
+ self .params_to_disable = []
687
+ for key in rcParams .keys ():
688
+ if u'keymap' in key :
689
+ self .params_to_disable += [key ]
690
+
691
+ self .text = initial
692
+ self .label = ax .text (- label_pad , 0.5 , label ,
693
+ verticalalignment = 'center' ,
694
+ horizontalalignment = 'right' ,
695
+ transform = ax .transAxes )
696
+ self .text_disp = self ._make_text_disp (self .text )
697
+
698
+ self .cnt = 0
699
+ self .change_observers = {}
700
+ self .submit_observers = {}
701
+
702
+ # If these lines are removed, the cursor won't appear the first
703
+ # time the box is clicked:
704
+ self .ax .set_xlim (0 , 1 )
705
+ self .ax .set_ylim (0 , 1 )
706
+
707
+ self .cursor_index = 0
708
+
709
+ # Because this is initialized, _render_cursor
710
+ # can assume that cursor exists.
711
+ self .cursor = self .ax .vlines (0 , 0 , 0 )
712
+ self .cursor .set_visible (False )
713
+
714
+ self .connect_event ('button_press_event' , self ._click )
715
+ self .connect_event ('button_release_event' , self ._release )
716
+ self .connect_event ('motion_notify_event' , self ._motion )
717
+ self .connect_event ('key_press_event' , self ._keypress )
718
+ self .connect_event ('resize_event' , self ._resize )
719
+ ax .set_navigate (False )
720
+ ax .set_facecolor (color )
721
+ ax .set_xticks ([])
722
+ ax .set_yticks ([])
723
+ self .color = color
724
+ self .hovercolor = hovercolor
725
+
726
+ self ._lastcolor = color
727
+
728
+ self .capturekeystrokes = False
729
+
730
+ def _make_text_disp (self , string ):
731
+ return self .ax .text (self .DIST_FROM_LEFT , 0.5 , string ,
732
+ verticalalignment = 'center' ,
733
+ horizontalalignment = 'left' ,
734
+ transform = self .ax .transAxes )
735
+
736
+ def _rendercursor (self ):
737
+ # this is a hack to figure out where the cursor should go.
738
+ # we draw the text up to where the cursor should go, measure
739
+ # and save its dimensions, draw the real text, then put the cursor
740
+ # at the saved dimensions
741
+
742
+ widthtext = self .text [:self .cursor_index ]
743
+ no_text = False
744
+ if (widthtext == "" or widthtext == " " or widthtext == " " ):
745
+ no_text = widthtext == ""
746
+ widthtext = ","
747
+
748
+ wt_disp = self ._make_text_disp (widthtext )
749
+
750
+ self .ax .figure .canvas .draw ()
751
+ bb = wt_disp .get_window_extent ()
752
+ inv = self .ax .transData .inverted ()
753
+ bb = inv .transform (bb )
754
+ wt_disp .set_visible (False )
755
+ if no_text :
756
+ bb [1 , 0 ] = bb [0 , 0 ]
757
+ # hack done
758
+ self .cursor .set_visible (False )
759
+
760
+ self .cursor = self .ax .vlines (bb [1 , 0 ], bb [0 , 1 ], bb [1 , 1 ])
761
+ self .ax .figure .canvas .draw ()
762
+
763
+ def _notify_submit_observers (self ):
764
+ for cid , func in six .iteritems (self .submit_observers ):
765
+ func (self .text )
766
+
767
+ def _release (self , event ):
768
+ if self .ignore (event ):
769
+ return
770
+ if event .canvas .mouse_grabber != self .ax :
771
+ return
772
+ event .canvas .release_mouse (self .ax )
773
+
774
+ def _keypress (self , event ):
775
+ if self .ignore (event ):
776
+ return
777
+ if self .capturekeystrokes :
778
+ key = event .key
779
+
780
+ if (len (key ) == 1 ):
781
+ self .text = (self .text [:self .cursor_index ] + key +
782
+ self .text [self .cursor_index :])
783
+ self .cursor_index += 1
784
+ elif key == "right" :
785
+ if self .cursor_index != len (self .text ):
786
+ self .cursor_index += 1
787
+ elif key == "left" :
788
+ if self .cursor_index != 0 :
789
+ self .cursor_index -= 1
790
+ elif key == "home" :
791
+ self .cursor_index = 0
792
+ elif key == "end" :
793
+ self .cursor_index = len (self .text )
794
+ elif (key == "backspace" ):
795
+ if self .cursor_index != 0 :
796
+ self .text = (self .text [:self .cursor_index - 1 ] +
797
+ self .text [self .cursor_index :])
798
+ self .cursor_index -= 1
799
+ elif (key == "delete" ):
800
+ if self .cursor_index != len (self .text ):
801
+ self .text = (self .text [:self .cursor_index ] +
802
+ self .text [self .cursor_index + 1 :])
803
+
804
+ self .text_disp .remove ()
805
+ self .text_disp = self ._make_text_disp (self .text )
806
+ self ._rendercursor ()
807
+ self ._notify_change_observers ()
808
+ if key == "enter" :
809
+ self ._notify_submit_observers ()
810
+
811
+ def set_val (self , val ):
812
+ newval = str (val )
813
+ if self .text == newval :
814
+ return
815
+ self .text = newval
816
+ self .text_disp .remove ()
817
+ self .text_disp = self ._make_text_disp (self .text )
818
+ self ._rendercursor ()
819
+ self ._notify_change_observers ()
820
+ self ._notify_submit_observers ()
821
+
822
+ def _notify_change_observers (self ):
823
+ for cid , func in six .iteritems (self .change_observers ):
824
+ func (self .text )
825
+
826
+ def begin_typing (self , x ):
827
+ self .capturekeystrokes = True
828
+ # disable command keys so that the user can type without
829
+ # command keys causing figure to be saved, etc
830
+ self .reset_params = {}
831
+ for key in self .params_to_disable :
832
+ self .reset_params [key ] = rcParams [key ]
833
+ rcParams [key ] = []
834
+
835
+ def stop_typing (self ):
836
+ notifysubmit = False
837
+ # because _notify_submit_users might throw an error in the
838
+ # user's code, we only want to call it once we've already done
839
+ # our cleanup.
840
+ if self .capturekeystrokes :
841
+ # since the user is no longer typing,
842
+ # reactivate the standard command keys
843
+ for key in self .params_to_disable :
844
+ rcParams [key ] = self .reset_params [key ]
845
+ notifysubmit = True
846
+ self .capturekeystrokes = False
847
+ self .cursor .set_visible (False )
848
+ self .ax .figure .canvas .draw ()
849
+ if notifysubmit :
850
+ self ._notify_submit_observers ()
851
+
852
+ def position_cursor (self , x ):
853
+ # now, we have to figure out where the cursor goes.
854
+ # approximate it based on assuming all characters the same length
855
+ if len (self .text ) == 0 :
856
+ self .cursor_index = 0
857
+ else :
858
+ bb = self .text_disp .get_window_extent ()
859
+
860
+ trans = self .ax .transData
861
+ inv = self .ax .transData .inverted ()
862
+ bb = trans .transform (inv .transform (bb ))
863
+
864
+ text_start = bb [0 , 0 ]
865
+ text_end = bb [1 , 0 ]
866
+
867
+ ratio = (x - text_start ) / (text_end - text_start )
868
+
869
+ if ratio < 0 :
870
+ ratio = 0
871
+ if ratio > 1 :
872
+ ratio = 1
873
+
874
+ self .cursor_index = int (len (self .text ) * ratio )
875
+
876
+ self ._rendercursor ()
877
+
878
+ def _click (self , event ):
879
+ if self .ignore (event ):
880
+ return
881
+ if event .inaxes != self .ax :
882
+ self .capturekeystrokes = False
883
+ self .stop_typing ()
884
+ return
885
+ if not self .eventson :
886
+ return
887
+ if event .canvas .mouse_grabber != self .ax :
888
+ event .canvas .grab_mouse (self .ax )
889
+ if not (self .capturekeystrokes ):
890
+ self .begin_typing (event .x )
891
+ self .position_cursor (event .x )
892
+
893
+ def _resize (self , event ):
894
+ self .stop_typing ()
895
+
896
+ def _motion (self , event ):
897
+ if self .ignore (event ):
898
+ return
899
+ if event .inaxes == self .ax :
900
+ c = self .hovercolor
901
+ else :
902
+ c = self .color
903
+ if c != self ._lastcolor :
904
+ self .ax .set_facecolor (c )
905
+ self ._lastcolor = c
906
+ if self .drawon :
907
+ self .ax .figure .canvas .draw ()
908
+
909
+ def on_text_change (self , func ):
910
+ """
911
+ When the text changes, call this *func* with event.
912
+
913
+ A connection id is returned which can be used to disconnect.
914
+ """
915
+ cid = self .cnt
916
+ self .change_observers [cid ] = func
917
+ self .cnt += 1
918
+ return cid
919
+
920
+ def on_submit (self , func ):
921
+ """
922
+ When the user hits enter or leaves the submision box, call this
923
+ *func* with event.
924
+
925
+ A connection id is returned which can be used to disconnect.
926
+ """
927
+ cid = self .cnt
928
+ self .submit_observers [cid ] = func
929
+ self .cnt += 1
930
+ return cid
931
+
932
+ def disconnect (self , cid ):
933
+ """remove the observer with connection id *cid*"""
934
+ try :
935
+ del self .observers [cid ]
936
+ except KeyError :
937
+ pass
938
+
939
+
631
940
class RadioButtons (AxesWidget ):
632
941
"""
633
942
A GUI neutral radio button.
0 commit comments