|
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
|
@@ -627,6 +628,256 @@ def disconnect(self, cid):
|
627 | 628 | except KeyError:
|
628 | 629 | pass
|
629 | 630 |
|
| 631 | +class TextBox(AxesWidget): |
| 632 | + """ |
| 633 | + A GUI neutral text input box. |
| 634 | +
|
| 635 | + For the text box to remain responsive |
| 636 | + 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 | + Call :meth:`on_submit` to be updated whenever the user hits enter or leaves the text entry field |
| 654 | + """ |
| 655 | + |
| 656 | + def __init__(self, ax, label, initial = '', |
| 657 | + color='.95', hovercolor='1'): |
| 658 | + """ |
| 659 | + Parameters |
| 660 | + ---------- |
| 661 | + ax : matplotlib.axes.Axes |
| 662 | + The :class:`matplotlib.axes.Axes` instance the button |
| 663 | + will be placed into. |
| 664 | +
|
| 665 | + label : str |
| 666 | + Label for this text box. Accepts string. |
| 667 | + |
| 668 | + initial : str |
| 669 | + Initial value in the text box |
| 670 | + |
| 671 | + color : color |
| 672 | + The color of the box |
| 673 | +
|
| 674 | + hovercolor : color |
| 675 | + The color of the box when the mouse is over it |
| 676 | + """ |
| 677 | + AxesWidget.__init__(self, ax) |
| 678 | + |
| 679 | + self.DIST_FROM_LEFT = .05 |
| 680 | + |
| 681 | + self.params_to_disable = [] |
| 682 | + for key in rcParams.keys(): |
| 683 | + if u'keymap' in key: |
| 684 | + self.params_to_disable += [key] |
| 685 | + |
| 686 | + self.text = initial |
| 687 | + |
| 688 | + |
| 689 | + |
| 690 | + |
| 691 | + self.label = ax.text(0.0,0.5, label, |
| 692 | + verticalalignment='center', |
| 693 | + horizontalalignment='right', |
| 694 | + transform=ax.transAxes) |
| 695 | + self.text_disp = self._make_text_disp(self.text) |
| 696 | + |
| 697 | + self.cnt = 0 |
| 698 | + self.change_observers = {} |
| 699 | + self.submit_observers = {} |
| 700 | + |
| 701 | + self.ax.set_xlim(0, 1) #If these lines are removed, the cursor won't appear |
| 702 | + self.ax.set_ylim(0, 1) #the first time the box is clicked |
| 703 | + |
| 704 | + self.cursor_index = 0; |
| 705 | + self.cursor = self.ax.vlines(0, 0, 0) #because this is initialized, _render_cursor |
| 706 | + self.cursor.set_visible(False) #can assume that cursor exists |
| 707 | + |
| 708 | + |
| 709 | + self.connect_event('button_press_event', self._click) |
| 710 | + self.connect_event('button_release_event', self._release) |
| 711 | + self.connect_event('motion_notify_event', self._motion) |
| 712 | + self.connect_event('key_press_event', self._keypress) |
| 713 | + ax.set_navigate(False) |
| 714 | + ax.set_axis_bgcolor(color) |
| 715 | + ax.set_xticks([]) |
| 716 | + ax.set_yticks([]) |
| 717 | + self.color = color |
| 718 | + self.hovercolor = hovercolor |
| 719 | + |
| 720 | + self._lastcolor = color |
| 721 | + |
| 722 | + self.capturekeystrokes = False |
| 723 | + |
| 724 | + |
| 725 | + |
| 726 | + |
| 727 | + def _make_text_disp(self, string): |
| 728 | + return self.ax.text(self.DIST_FROM_LEFT, 0.5, string, |
| 729 | + verticalalignment='center', |
| 730 | + horizontalalignment='left', |
| 731 | + transform=self.ax.transAxes) |
| 732 | + def _rendercursor(self): |
| 733 | + #this is a hack to figure out where the cursor should go. |
| 734 | + #we draw the text up to where the cursor should go, measure |
| 735 | + #save its dimensions, draw the real text, then put the cursor |
| 736 | + #at the saved dimensions |
| 737 | + |
| 738 | + widthtext = self.text[:self.cursor_index] |
| 739 | + no_text = False |
| 740 | + if(widthtext == "" or widthtext == " " or widthtext == " "): |
| 741 | + no_text = widthtext == "" |
| 742 | + widthtext = "," |
| 743 | + |
| 744 | + |
| 745 | + wt_disp = self._make_text_disp(widthtext) |
| 746 | + |
| 747 | + self.ax.figure.canvas.draw() |
| 748 | + bb = wt_disp.get_window_extent() |
| 749 | + inv = self.ax.transData.inverted() |
| 750 | + bb = inv.transform(bb) |
| 751 | + wt_disp.set_visible(False) |
| 752 | + if no_text: |
| 753 | + bb[1, 0] = bb[0, 0] |
| 754 | + #hack done |
| 755 | + self.cursor.set_visible(False) |
| 756 | + |
| 757 | + |
| 758 | + self.cursor = self.ax.vlines(bb[1, 0], bb[0, 1], bb[1, 1]) |
| 759 | + self.ax.figure.canvas.draw() |
| 760 | + |
| 761 | + def _notify_submit_observers(self): |
| 762 | + for cid, func in six.iteritems(self.submit_observers): |
| 763 | + func(self.text) |
| 764 | + |
| 765 | + def _release(self, event): |
| 766 | + if self.ignore(event): |
| 767 | + return |
| 768 | + if event.canvas.mouse_grabber != self.ax: |
| 769 | + return |
| 770 | + event.canvas.release_mouse(self.ax) |
| 771 | + |
| 772 | + def _keypress(self, event): |
| 773 | + if self.ignore(event): |
| 774 | + return |
| 775 | + if self.capturekeystrokes: |
| 776 | + key = event.key |
| 777 | + |
| 778 | + if(len(key) == 1): |
| 779 | + self.text = (self.text[:self.cursor_index] + key + |
| 780 | + self.text[self.cursor_index:]) |
| 781 | + self.cursor_index += 1 |
| 782 | + elif key == "right": |
| 783 | + if self.cursor_index != len(self.text): |
| 784 | + self.cursor_index += 1 |
| 785 | + elif key == "left": |
| 786 | + if self.cursor_index != 0: |
| 787 | + self.cursor_index -= 1 |
| 788 | + elif key == "home": |
| 789 | + self.cursor_index = 0 |
| 790 | + elif key == "end": |
| 791 | + self.cursor_index = len(self.text) |
| 792 | + elif(key == "backspace"): |
| 793 | + if self.cursor_index != 0: |
| 794 | + self.text = (self.text[:self.cursor_index - 1] + |
| 795 | + self.text[self.cursor_index:]) |
| 796 | + self.cursor_index -= 1 |
| 797 | + elif(key == "delete"): |
| 798 | + if self.cursor_index != len(self.text): |
| 799 | + self.text = (self.text[:self.cursor_index] + |
| 800 | + self.text[self.cursor_index + 1:]) |
| 801 | + self.text_disp.remove() |
| 802 | + self.text_disp = self._make_text_disp(self.text) |
| 803 | + self._rendercursor() |
| 804 | + for cid, func in six.iteritems(self.change_observers): |
| 805 | + func(self.text) |
| 806 | + if key == "enter": |
| 807 | + self._notify_submit_observers() |
| 808 | + |
| 809 | + def _click(self, event): |
| 810 | + if self.ignore(event): |
| 811 | + return |
| 812 | + if event.inaxes != self.ax: |
| 813 | + notifysubmit = False |
| 814 | + #because _notify_submit_users might throw an error in the |
| 815 | + #user's code, we only want to call it once we've already done |
| 816 | + #our cleanup. |
| 817 | + if self.capturekeystrokes: |
| 818 | + for key in self.params_to_disable: |
| 819 | + rcParams[key] = self.reset_params[key] |
| 820 | + notifysubmit = True |
| 821 | + self.capturekeystrokes = False |
| 822 | + self.cursor.set_visible(False) |
| 823 | + self.ax.figure.canvas.draw() |
| 824 | + |
| 825 | + if notifysubmit: |
| 826 | + self._notify_submit_observers() |
| 827 | + return |
| 828 | + if not self.eventson: |
| 829 | + return |
| 830 | + if event.canvas.mouse_grabber != self.ax: |
| 831 | + event.canvas.grab_mouse(self.ax) |
| 832 | + if not(self.capturekeystrokes): |
| 833 | + self.capturekeystrokes = True |
| 834 | + self.reset_params = {} |
| 835 | + for key in self.params_to_disable: |
| 836 | + self.reset_params[key] = rcParams[key] |
| 837 | + rcParams[key] = [] |
| 838 | + self.cursor_index = len(self.text) |
| 839 | + self._rendercursor() |
| 840 | + |
| 841 | + |
| 842 | + def _motion(self, event): |
| 843 | + if self.ignore(event): |
| 844 | + return |
| 845 | + if event.inaxes == self.ax: |
| 846 | + c = self.hovercolor |
| 847 | + else: |
| 848 | + c = self.color |
| 849 | + if c != self._lastcolor: |
| 850 | + self.ax.set_axis_bgcolor(c) |
| 851 | + self._lastcolor = c |
| 852 | + if self.drawon: |
| 853 | + self.ax.figure.canvas.draw() |
| 854 | + |
| 855 | + def on_text_change(self, func): |
| 856 | + """ |
| 857 | + When the text changes, call this *func* with event |
| 858 | +
|
| 859 | + A connection id is returned which can be used to disconnect |
| 860 | + """ |
| 861 | + cid = self.cnt |
| 862 | + self.change_observers[cid] = func |
| 863 | + self.cnt += 1 |
| 864 | + return cid |
| 865 | + def on_submit(self, func): |
| 866 | + """ |
| 867 | + When the user hits enter or leaves the submision box, call this *func* with event |
| 868 | +
|
| 869 | + A connection id is returned which can be used to disconnect |
| 870 | + """ |
| 871 | + cid = self.cnt |
| 872 | + self.submit_observers[cid] = func |
| 873 | + self.cnt += 1 |
| 874 | + return cid |
| 875 | + def disconnect(self, cid): |
| 876 | + """remove the observer with connection id *cid*""" |
| 877 | + try: |
| 878 | + del self.observers[cid] |
| 879 | + except KeyError: |
| 880 | + pass |
630 | 881 |
|
631 | 882 | class RadioButtons(AxesWidget):
|
632 | 883 | """
|
@@ -925,6 +1176,7 @@ def funchspace(self, val):
|
925 | 1176 | self.targetfig.canvas.draw()
|
926 | 1177 |
|
927 | 1178 |
|
| 1179 | + |
928 | 1180 | class Cursor(AxesWidget):
|
929 | 1181 | """
|
930 | 1182 | A horizontal and vertical line that spans the axes and moves with
|
|
0 commit comments