From 80c20f33a89c5c9d1bb1452b68cd10b1b45e5570 Mon Sep 17 00:00:00 2001 From: Hastings Greer Date: Tue, 27 Oct 2015 10:42:16 -0400 Subject: [PATCH 1/9] Added a text entry widget, that allows usere to register to be notified when text changes or text is submitted --- CHANGELOG | 4 + doc/users/whats_new.rst | 9 +- lib/matplotlib/widgets.py | 252 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 264 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 00791883a7e5..ce177318dce1 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,10 @@ + 2015-11-16 Levels passed to contour(f) and tricontour(f) must be in increasing order. +2015-10-21 Added TextBox widget + + 2015-10-21 Added get_ticks_direction() 2015-02-27 Added the rcParam 'image.composite_image' to permit users diff --git a/doc/users/whats_new.rst b/doc/users/whats_new.rst index d09102aa2dd5..9f6b1c097cb0 100644 --- a/doc/users/whats_new.rst +++ b/doc/users/whats_new.rst @@ -1,4 +1,4 @@ -.. _whats-new: +w.. _whats-new: ************************ What's new in matplotlib @@ -244,6 +244,13 @@ Some parameters have been added, others have been improved. Widgets ------- +Added TextBox Widget + +```````````````````` +Added a widget that allows text entry by reading key events when it is active. +Text caret in text box is visible when it is active, can be moved using arrow keys but not mouse + + Active state of Selectors ````````````````````````` diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py index 20fe3b12f446..e399f838d30a 100644 --- a/lib/matplotlib/widgets.py +++ b/lib/matplotlib/widgets.py @@ -17,6 +17,7 @@ from six.moves import zip import numpy as np +from matplotlib import rcParams from .mlab import dist from .patches import Circle, Rectangle, Ellipse @@ -627,6 +628,256 @@ def disconnect(self, cid): except KeyError: pass +class TextBox(AxesWidget): + """ + A GUI neutral text input box. + + For the text box to remain responsive + you must keep a reference to it. + + The following attributes are accessible + + *ax* + The :class:`matplotlib.axes.Axes` the button renders into. + + *label* + A :class:`matplotlib.text.Text` instance. + + *color* + The color of the text box when not hovering. + + *hovercolor* + The color of the text box when hovering. + + Call :meth:`on_text_change` to be updated whenever the text changes + Call :meth:`on_submit` to be updated whenever the user hits enter or leaves the text entry field + """ + + def __init__(self, ax, label, initial = '', + color='.95', hovercolor='1'): + """ + Parameters + ---------- + ax : matplotlib.axes.Axes + The :class:`matplotlib.axes.Axes` instance the button + will be placed into. + + label : str + Label for this text box. Accepts string. + + initial : str + Initial value in the text box + + color : color + The color of the box + + hovercolor : color + The color of the box when the mouse is over it + """ + AxesWidget.__init__(self, ax) + + self.DIST_FROM_LEFT = .05 + + self.params_to_disable = [] + for key in rcParams.keys(): + if u'keymap' in key: + self.params_to_disable += [key] + + self.text = initial + + + + + self.label = ax.text(0.0,0.5, label, + verticalalignment='center', + horizontalalignment='right', + transform=ax.transAxes) + self.text_disp = self._make_text_disp(self.text) + + self.cnt = 0 + self.change_observers = {} + self.submit_observers = {} + + self.ax.set_xlim(0, 1) #If these lines are removed, the cursor won't appear + self.ax.set_ylim(0, 1) #the first time the box is clicked + + self.cursor_index = 0; + self.cursor = self.ax.vlines(0, 0, 0) #because this is initialized, _render_cursor + self.cursor.set_visible(False) #can assume that cursor exists + + + self.connect_event('button_press_event', self._click) + self.connect_event('button_release_event', self._release) + self.connect_event('motion_notify_event', self._motion) + self.connect_event('key_press_event', self._keypress) + ax.set_navigate(False) + ax.set_axis_bgcolor(color) + ax.set_xticks([]) + ax.set_yticks([]) + self.color = color + self.hovercolor = hovercolor + + self._lastcolor = color + + self.capturekeystrokes = False + + + + + def _make_text_disp(self, string): + return self.ax.text(self.DIST_FROM_LEFT, 0.5, string, + verticalalignment='center', + horizontalalignment='left', + transform=self.ax.transAxes) + def _rendercursor(self): + #this is a hack to figure out where the cursor should go. + #we draw the text up to where the cursor should go, measure + #save its dimensions, draw the real text, then put the cursor + #at the saved dimensions + + widthtext = self.text[:self.cursor_index] + no_text = False + if(widthtext == "" or widthtext == " " or widthtext == " "): + no_text = widthtext == "" + widthtext = "," + + + wt_disp = self._make_text_disp(widthtext) + + self.ax.figure.canvas.draw() + bb = wt_disp.get_window_extent() + inv = self.ax.transData.inverted() + bb = inv.transform(bb) + wt_disp.set_visible(False) + if no_text: + bb[1, 0] = bb[0, 0] + #hack done + self.cursor.set_visible(False) + + + self.cursor = self.ax.vlines(bb[1, 0], bb[0, 1], bb[1, 1]) + self.ax.figure.canvas.draw() + + def _notify_submit_observers(self): + for cid, func in six.iteritems(self.submit_observers): + func(self.text) + + def _release(self, event): + if self.ignore(event): + return + if event.canvas.mouse_grabber != self.ax: + return + event.canvas.release_mouse(self.ax) + + def _keypress(self, event): + if self.ignore(event): + return + if self.capturekeystrokes: + key = event.key + + if(len(key) == 1): + self.text = (self.text[:self.cursor_index] + key + + self.text[self.cursor_index:]) + self.cursor_index += 1 + elif key == "right": + if self.cursor_index != len(self.text): + self.cursor_index += 1 + elif key == "left": + if self.cursor_index != 0: + self.cursor_index -= 1 + elif key == "home": + self.cursor_index = 0 + elif key == "end": + self.cursor_index = len(self.text) + elif(key == "backspace"): + if self.cursor_index != 0: + self.text = (self.text[:self.cursor_index - 1] + + self.text[self.cursor_index:]) + self.cursor_index -= 1 + elif(key == "delete"): + if self.cursor_index != len(self.text): + self.text = (self.text[:self.cursor_index] + + self.text[self.cursor_index + 1:]) + self.text_disp.remove() + self.text_disp = self._make_text_disp(self.text) + self._rendercursor() + for cid, func in six.iteritems(self.change_observers): + func(self.text) + if key == "enter": + self._notify_submit_observers() + + def _click(self, event): + if self.ignore(event): + return + if event.inaxes != self.ax: + notifysubmit = False + #because _notify_submit_users might throw an error in the + #user's code, we only want to call it once we've already done + #our cleanup. + if self.capturekeystrokes: + for key in self.params_to_disable: + rcParams[key] = self.reset_params[key] + notifysubmit = True + self.capturekeystrokes = False + self.cursor.set_visible(False) + self.ax.figure.canvas.draw() + + if notifysubmit: + self._notify_submit_observers() + return + if not self.eventson: + return + if event.canvas.mouse_grabber != self.ax: + event.canvas.grab_mouse(self.ax) + if not(self.capturekeystrokes): + self.capturekeystrokes = True + self.reset_params = {} + for key in self.params_to_disable: + self.reset_params[key] = rcParams[key] + rcParams[key] = [] + self.cursor_index = len(self.text) + self._rendercursor() + + + def _motion(self, event): + if self.ignore(event): + return + if event.inaxes == self.ax: + c = self.hovercolor + else: + c = self.color + if c != self._lastcolor: + self.ax.set_axis_bgcolor(c) + self._lastcolor = c + if self.drawon: + self.ax.figure.canvas.draw() + + def on_text_change(self, func): + """ + When the text changes, call this *func* with event + + A connection id is returned which can be used to disconnect + """ + cid = self.cnt + self.change_observers[cid] = func + self.cnt += 1 + return cid + def on_submit(self, func): + """ + When the user hits enter or leaves the submision box, call this *func* with event + + A connection id is returned which can be used to disconnect + """ + cid = self.cnt + self.submit_observers[cid] = func + self.cnt += 1 + return cid + def disconnect(self, cid): + """remove the observer with connection id *cid*""" + try: + del self.observers[cid] + except KeyError: + pass class RadioButtons(AxesWidget): """ @@ -925,6 +1176,7 @@ def funchspace(self, val): self.targetfig.canvas.draw() + class Cursor(AxesWidget): """ A horizontal and vertical line that spans the axes and moves with From e70217b1263ea6b00593adb4012c8a0b2e8cf248 Mon Sep 17 00:00:00 2001 From: Hastings Greer Date: Sat, 31 Oct 2015 16:58:16 -0400 Subject: [PATCH 2/9] Added a text box example: evaluates any string inputs as y(x). --- doc/users/whats_new.rst | 4 +- examples/widgets/textbox.py | 23 ++++++ lib/matplotlib/widgets.py | 161 ++++++++++++++++++------------------ 3 files changed, 106 insertions(+), 82 deletions(-) create mode 100644 examples/widgets/textbox.py diff --git a/doc/users/whats_new.rst b/doc/users/whats_new.rst index 9f6b1c097cb0..1d69985ff252 100644 --- a/doc/users/whats_new.rst +++ b/doc/users/whats_new.rst @@ -1,4 +1,4 @@ -w.. _whats-new: +.. _whats-new: ************************ What's new in matplotlib @@ -245,8 +245,8 @@ Widgets ------- Added TextBox Widget - ```````````````````` + Added a widget that allows text entry by reading key events when it is active. Text caret in text box is visible when it is active, can be moved using arrow keys but not mouse diff --git a/examples/widgets/textbox.py b/examples/widgets/textbox.py new file mode 100644 index 000000000000..271675a9757d --- /dev/null +++ b/examples/widgets/textbox.py @@ -0,0 +1,23 @@ + +import numpy as np +import matplotlib.pyplot as plt +from matplotlib.widgets import TextBox +fig, ax = plt.subplots() +plt.subplots_adjust(bottom=0.2) +t = np.arange(-2.0, 2.0, 0.001) +s = t ** 2 +initial_text = "t ** 2" +l, = plt.plot(t, s, lw=2) + + +def submit(text): + ydata = eval(text) + l.set_ydata(ydata) + ax.set_ylim(np.min(ydata), np.max(ydata)) + plt.draw() + +axbox = plt.axes([0.1, 0.05, 0.8, 0.075]) +text_box = TextBox(axbox, 'Evaluate', initial=initial_text) +text_box.on_submit(submit) + +plt.show() diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py index e399f838d30a..776bc57e5630 100644 --- a/lib/matplotlib/widgets.py +++ b/lib/matplotlib/widgets.py @@ -628,14 +628,14 @@ def disconnect(self, cid): except KeyError: pass + class TextBox(AxesWidget): """ A GUI neutral text input box. - For the text box to remain responsive - you must keep a reference to it. + For the text box to remain responsive you must keep a reference to it. - The following attributes are accessible + The following attributes are accessible: *ax* The :class:`matplotlib.axes.Axes` the button renders into. @@ -649,11 +649,13 @@ class TextBox(AxesWidget): *hovercolor* The color of the text box when hovering. - Call :meth:`on_text_change` to be updated whenever the text changes - Call :meth:`on_submit` to be updated whenever the user hits enter or leaves the text entry field + Call :meth:`on_text_change` to be updated whenever the text changes. + + Call :meth:`on_submit` to be updated whenever the user hits enter or + leaves the text entry field. """ - def __init__(self, ax, label, initial = '', + def __init__(self, ax, label, initial='', color='.95', hovercolor='1'): """ Parameters @@ -664,10 +666,10 @@ def __init__(self, ax, label, initial = '', label : str Label for this text box. Accepts string. - + initial : str Initial value in the text box - + color : color The color of the box @@ -675,37 +677,37 @@ def __init__(self, ax, label, initial = '', The color of the box when the mouse is over it """ AxesWidget.__init__(self, ax) - - self.DIST_FROM_LEFT = .05 - + + self.DIST_FROM_LEFT = .05 + self.params_to_disable = [] - for key in rcParams.keys(): + for key in rcParams.keys(): if u'keymap' in key: - self.params_to_disable += [key] - + self.params_to_disable += [key] + self.text = initial - - - - - self.label = ax.text(0.0,0.5, label, + self.label = ax.text(0.0, 0.5, label, verticalalignment='center', horizontalalignment='right', transform=ax.transAxes) self.text_disp = self._make_text_disp(self.text) - + self.cnt = 0 self.change_observers = {} self.submit_observers = {} - - self.ax.set_xlim(0, 1) #If these lines are removed, the cursor won't appear - self.ax.set_ylim(0, 1) #the first time the box is clicked - - self.cursor_index = 0; - self.cursor = self.ax.vlines(0, 0, 0) #because this is initialized, _render_cursor - self.cursor.set_visible(False) #can assume that cursor exists - - + + # If these lines are removed, the cursor won't appear the first + # time the box is clicked: + self.ax.set_xlim(0, 1) + self.ax.set_ylim(0, 1) + + self.cursor_index = 0 + + # Because this is initialized, _render_cursor + # can assume that cursor exists. + self.cursor = self.ax.vlines(0, 0, 0) + self.cursor.set_visible(False) + self.connect_event('button_press_event', self._click) self.connect_event('button_release_event', self._release) self.connect_event('motion_notify_event', self._motion) @@ -718,110 +720,107 @@ def __init__(self, ax, label, initial = '', self.hovercolor = hovercolor self._lastcolor = color - - self.capturekeystrokes = False - - - + self.capturekeystrokes = False + def _make_text_disp(self, string): return self.ax.text(self.DIST_FROM_LEFT, 0.5, string, - verticalalignment='center', - horizontalalignment='left', - transform=self.ax.transAxes) + verticalalignment='center', + horizontalalignment='left', + transform=self.ax.transAxes) + def _rendercursor(self): - #this is a hack to figure out where the cursor should go. - #we draw the text up to where the cursor should go, measure - #save its dimensions, draw the real text, then put the cursor - #at the saved dimensions - + # this is a hack to figure out where the cursor should go. + # we draw the text up to where the cursor should go, measure + # save its dimensions, draw the real text, then put the cursor + # at the saved dimensions + widthtext = self.text[:self.cursor_index] no_text = False if(widthtext == "" or widthtext == " " or widthtext == " "): no_text = widthtext == "" widthtext = "," - - + wt_disp = self._make_text_disp(widthtext) - + self.ax.figure.canvas.draw() bb = wt_disp.get_window_extent() inv = self.ax.transData.inverted() bb = inv.transform(bb) wt_disp.set_visible(False) if no_text: - bb[1, 0] = bb[0, 0] - #hack done + bb[1, 0] = bb[0, 0] + # hack done self.cursor.set_visible(False) - - + self.cursor = self.ax.vlines(bb[1, 0], bb[0, 1], bb[1, 1]) self.ax.figure.canvas.draw() def _notify_submit_observers(self): for cid, func in six.iteritems(self.submit_observers): func(self.text) - + def _release(self, event): if self.ignore(event): return if event.canvas.mouse_grabber != self.ax: return event.canvas.release_mouse(self.ax) - + def _keypress(self, event): if self.ignore(event): return if self.capturekeystrokes: key = event.key - + if(len(key) == 1): - self.text = (self.text[:self.cursor_index] + key + - self.text[self.cursor_index:]) - self.cursor_index += 1 + self.text = (self.text[:self.cursor_index] + key + + self.text[self.cursor_index:]) + self.cursor_index += 1 elif key == "right": - if self.cursor_index != len(self.text): - self.cursor_index += 1 + if self.cursor_index != len(self.text): + self.cursor_index += 1 elif key == "left": - if self.cursor_index != 0: - self.cursor_index -= 1 + if self.cursor_index != 0: + self.cursor_index -= 1 elif key == "home": - self.cursor_index = 0 + self.cursor_index = 0 elif key == "end": - self.cursor_index = len(self.text) + self.cursor_index = len(self.text) elif(key == "backspace"): if self.cursor_index != 0: - self.text = (self.text[:self.cursor_index - 1] + - self.text[self.cursor_index:]) + self.text = (self.text[:self.cursor_index - 1] + + self.text[self.cursor_index:]) self.cursor_index -= 1 elif(key == "delete"): if self.cursor_index != len(self.text): - self.text = (self.text[:self.cursor_index] + - self.text[self.cursor_index + 1:]) + self.text = (self.text[:self.cursor_index] + + self.text[self.cursor_index + 1:]) + self.text_disp.remove() self.text_disp = self._make_text_disp(self.text) self._rendercursor() for cid, func in six.iteritems(self.change_observers): func(self.text) if key == "enter": - self._notify_submit_observers() - + self._notify_submit_observers() + def _click(self, event): if self.ignore(event): return if event.inaxes != self.ax: - notifysubmit = False - #because _notify_submit_users might throw an error in the - #user's code, we only want to call it once we've already done - #our cleanup. + notifysubmit = False + # because _notify_submit_users might throw an error in the + # user's code, we only want to call it once we've already done + # our cleanup. if self.capturekeystrokes: - for key in self.params_to_disable: + for key in self.params_to_disable: rcParams[key] = self.reset_params[key] - notifysubmit = True + notifysubmit = True self.capturekeystrokes = False self.cursor.set_visible(False) self.ax.figure.canvas.draw() - + if notifysubmit: self._notify_submit_observers() return @@ -838,7 +837,6 @@ def _click(self, event): self.cursor_index = len(self.text) self._rendercursor() - def _motion(self, event): if self.ignore(event): return @@ -854,24 +852,27 @@ def _motion(self, event): def on_text_change(self, func): """ - When the text changes, call this *func* with event + When the text changes, call this *func* with event. - A connection id is returned which can be used to disconnect + A connection id is returned which can be used to disconnect. """ cid = self.cnt self.change_observers[cid] = func self.cnt += 1 return cid + def on_submit(self, func): """ - When the user hits enter or leaves the submision box, call this *func* with event + When the user hits enter or leaves the submision box, call this + *func* with event. - A connection id is returned which can be used to disconnect + A connection id is returned which can be used to disconnect. """ cid = self.cnt self.submit_observers[cid] = func self.cnt += 1 return cid + def disconnect(self, cid): """remove the observer with connection id *cid*""" try: @@ -879,6 +880,7 @@ def disconnect(self, cid): except KeyError: pass + class RadioButtons(AxesWidget): """ A GUI neutral radio button. @@ -1176,7 +1178,6 @@ def funchspace(self, val): self.targetfig.canvas.draw() - class Cursor(AxesWidget): """ A horizontal and vertical line that spans the axes and moves with From 26b4950f650e9b740612cf02a7ac44ff52693a9b Mon Sep 17 00:00:00 2001 From: Hastings Greer Date: Wed, 20 Jan 2016 14:27:50 -0500 Subject: [PATCH 3/9] refactored "start typing" and "stop typing" into their own functions to aid readability --- lib/matplotlib/widgets.py | 59 ++++++++++++++++++++++++--------------- 1 file changed, 36 insertions(+), 23 deletions(-) diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py index 776bc57e5630..53e90f6dd11b 100644 --- a/lib/matplotlib/widgets.py +++ b/lib/matplotlib/widgets.py @@ -686,7 +686,7 @@ def __init__(self, ax, label, initial='', self.params_to_disable += [key] self.text = initial - self.label = ax.text(0.0, 0.5, label, + self.label = ax.text(-0.01, 0.5, label, verticalalignment='center', horizontalalignment='right', transform=ax.transAxes) @@ -732,7 +732,7 @@ def _make_text_disp(self, string): def _rendercursor(self): # this is a hack to figure out where the cursor should go. # we draw the text up to where the cursor should go, measure - # save its dimensions, draw the real text, then put the cursor + # and save its dimensions, draw the real text, then put the cursor # at the saved dimensions widthtext = self.text[:self.cursor_index] @@ -804,38 +804,51 @@ def _keypress(self, event): func(self.text) if key == "enter": self._notify_submit_observers() + + def begin_typing(self, x): + self.capturekeystrokes = True + #disable command keys so that the user can type without + #command keys causing figure to be saved, etc + self.reset_params = {} + for key in self.params_to_disable: + self.reset_params[key] = rcParams[key] + rcParams[key] = [] + #now, we have to figure out where the cursor goes. + #approximate it based on assuming all characters the same length + print(x) + self.cursor_index = len(self.text) + self._rendercursor() + + def stop_typing(self): + notifysubmit = False + # because _notify_submit_users might throw an error in the + # user's code, we only want to call it once we've already done + # our cleanup. + if self.capturekeystrokes: + #since the user is no longer typing, + #reactivate the standard command keys + for key in self.params_to_disable: + rcParams[key] = self.reset_params[key] + notifysubmit = True + self.capturekeystrokes = False + self.cursor.set_visible(False) + self.ax.figure.canvas.draw() + if notifysubmit: + self._notify_submit_observers() + def _click(self, event): if self.ignore(event): return if event.inaxes != self.ax: - notifysubmit = False - # because _notify_submit_users might throw an error in the - # user's code, we only want to call it once we've already done - # our cleanup. - if self.capturekeystrokes: - for key in self.params_to_disable: - rcParams[key] = self.reset_params[key] - notifysubmit = True - self.capturekeystrokes = False - self.cursor.set_visible(False) - self.ax.figure.canvas.draw() - - if notifysubmit: - self._notify_submit_observers() + self.stop_typing() return if not self.eventson: return if event.canvas.mouse_grabber != self.ax: event.canvas.grab_mouse(self.ax) if not(self.capturekeystrokes): - self.capturekeystrokes = True - self.reset_params = {} - for key in self.params_to_disable: - self.reset_params[key] = rcParams[key] - rcParams[key] = [] - self.cursor_index = len(self.text) - self._rendercursor() + self.begin_typing(event.x) def _motion(self, event): if self.ignore(event): From f7001c11b9350fe0ec2a408b5de1465ea0dfff3f Mon Sep 17 00:00:00 2001 From: Hastings Greer Date: Wed, 20 Jan 2016 14:37:22 -0500 Subject: [PATCH 4/9] made textbox lose focus when window is resized: this prevents cursor and text from getting misaligned --- lib/matplotlib/widgets.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py index 53e90f6dd11b..31483ff7b784 100644 --- a/lib/matplotlib/widgets.py +++ b/lib/matplotlib/widgets.py @@ -712,6 +712,7 @@ def __init__(self, ax, label, initial='', self.connect_event('button_release_event', self._release) self.connect_event('motion_notify_event', self._motion) self.connect_event('key_press_event', self._keypress) + self.connect_event('resize_event', self._resize) ax.set_navigate(False) ax.set_axis_bgcolor(color) ax.set_xticks([]) @@ -849,7 +850,10 @@ def _click(self, event): event.canvas.grab_mouse(self.ax) if not(self.capturekeystrokes): self.begin_typing(event.x) - + + def _resize(self, event): + self.stop_typing() + def _motion(self, event): if self.ignore(event): return From 7961826c215b985f72769a05785dcb1f0a6590fb Mon Sep 17 00:00:00 2001 From: Hastings Greer Date: Wed, 20 Jan 2016 15:08:33 -0500 Subject: [PATCH 5/9] added adjustable padding between label and text box --- lib/matplotlib/widgets.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py index 31483ff7b784..1f2b4715ed28 100644 --- a/lib/matplotlib/widgets.py +++ b/lib/matplotlib/widgets.py @@ -656,7 +656,7 @@ class TextBox(AxesWidget): """ def __init__(self, ax, label, initial='', - color='.95', hovercolor='1'): + color='.95', hovercolor='1', label_pad=.01): """ Parameters ---------- @@ -675,6 +675,9 @@ def __init__(self, ax, label, initial='', hovercolor : color The color of the box when the mouse is over it + + label_pad : float + the distance between the label and the right side of the textbox """ AxesWidget.__init__(self, ax) @@ -686,7 +689,7 @@ def __init__(self, ax, label, initial='', self.params_to_disable += [key] self.text = initial - self.label = ax.text(-0.01, 0.5, label, + self.label = ax.text(-label_pad, 0.5, label, verticalalignment='center', horizontalalignment='right', transform=ax.transAxes) @@ -805,7 +808,7 @@ def _keypress(self, event): func(self.text) if key == "enter": self._notify_submit_observers() - + def begin_typing(self, x): self.capturekeystrokes = True #disable command keys so that the user can type without @@ -816,10 +819,9 @@ def begin_typing(self, x): rcParams[key] = [] #now, we have to figure out where the cursor goes. #approximate it based on assuming all characters the same length - print(x) self.cursor_index = len(self.text) self._rendercursor() - + def stop_typing(self): notifysubmit = False # because _notify_submit_users might throw an error in the @@ -837,7 +839,7 @@ def stop_typing(self): if notifysubmit: self._notify_submit_observers() - + def _click(self, event): if self.ignore(event): return @@ -850,10 +852,10 @@ def _click(self, event): event.canvas.grab_mouse(self.ax) if not(self.capturekeystrokes): self.begin_typing(event.x) - + def _resize(self, event): self.stop_typing() - + def _motion(self, event): if self.ignore(event): return From 893962f4117280f2d08040b666c21abaf5a73586 Mon Sep 17 00:00:00 2001 From: Hastings Greer Date: Sat, 13 Feb 2016 15:52:37 -0500 Subject: [PATCH 6/9] enabled moving the cursor by clicking --- doc/users/whats_new.rst | 2 +- lib/matplotlib/widgets.py | 36 +++++++++++++++++++++++++++++------- 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/doc/users/whats_new.rst b/doc/users/whats_new.rst index 1d69985ff252..2909bda28e14 100644 --- a/doc/users/whats_new.rst +++ b/doc/users/whats_new.rst @@ -248,7 +248,7 @@ Added TextBox Widget ```````````````````` Added a widget that allows text entry by reading key events when it is active. -Text caret in text box is visible when it is active, can be moved using arrow keys but not mouse +Text caret in text box is visible when it is active, can be moved using arrow keys and mouse Active state of Selectors diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py index 1f2b4715ed28..8a6d1b94ef34 100644 --- a/lib/matplotlib/widgets.py +++ b/lib/matplotlib/widgets.py @@ -717,7 +717,7 @@ def __init__(self, ax, label, initial='', self.connect_event('key_press_event', self._keypress) self.connect_event('resize_event', self._resize) ax.set_navigate(False) - ax.set_axis_bgcolor(color) + ax.set_facecolor(color) ax.set_xticks([]) ax.set_yticks([]) self.color = color @@ -817,10 +817,6 @@ def begin_typing(self, x): for key in self.params_to_disable: self.reset_params[key] = rcParams[key] rcParams[key] = [] - #now, we have to figure out where the cursor goes. - #approximate it based on assuming all characters the same length - self.cursor_index = len(self.text) - self._rendercursor() def stop_typing(self): notifysubmit = False @@ -828,7 +824,7 @@ def stop_typing(self): # user's code, we only want to call it once we've already done # our cleanup. if self.capturekeystrokes: - #since the user is no longer typing, + #since the user is no longer typing, #reactivate the standard command keys for key in self.params_to_disable: rcParams[key] = self.reset_params[key] @@ -839,6 +835,31 @@ def stop_typing(self): if notifysubmit: self._notify_submit_observers() + def position_cursor(self, x): + #now, we have to figure out where the cursor goes. + #approximate it based on assuming all characters the same length + if len(self.text) == 0: + self.cursor_index = 0 + else: + bb = self.text_disp.get_window_extent() + + trans = self.ax.transData + inv = self.ax.transData.inverted() + bb = trans.transform(inv.transform(bb)) + + text_start = bb[0, 0] + text_end = bb[1, 0] + + ratio = (x - text_start) / (text_end - text_start) + + if ratio < 0: + ratio = 0 + if ratio > 1: + ratio = 1 + + self.cursor_index = int(len(self.text) * ratio) + + self._rendercursor() def _click(self, event): if self.ignore(event): @@ -852,6 +873,7 @@ def _click(self, event): event.canvas.grab_mouse(self.ax) if not(self.capturekeystrokes): self.begin_typing(event.x) + self.position_cursor(event.x) def _resize(self, event): self.stop_typing() @@ -864,7 +886,7 @@ def _motion(self, event): else: c = self.color if c != self._lastcolor: - self.ax.set_axis_bgcolor(c) + self.ax.set_facecolor(c) self._lastcolor = c if self.drawon: self.ax.figure.canvas.draw() From c92af554ce29ff65f9f0410ef8ee1f9d6ef1961a Mon Sep 17 00:00:00 2001 From: Adrien Chardon Date: Sat, 27 Aug 2016 16:36:48 +0200 Subject: [PATCH 7/9] [Widget/Text] Dont validate the input when clicking outside of the widget --- lib/matplotlib/widgets.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py index 8a6d1b94ef34..6b4d04d88ad8 100644 --- a/lib/matplotlib/widgets.py +++ b/lib/matplotlib/widgets.py @@ -865,6 +865,7 @@ def _click(self, event): if self.ignore(event): return if event.inaxes != self.ax: + self.capturekeystrokes = False self.stop_typing() return if not self.eventson: From 1c86e82d7e75be233ed480af8fdfaff89bd169f5 Mon Sep 17 00:00:00 2001 From: Sterling Smith Date: Fri, 17 Jun 2016 11:27:31 -0700 Subject: [PATCH 8/9] TextBox: Added set_val method for convenient scripted setting and updating. --- lib/matplotlib/widgets.py | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py index 6b4d04d88ad8..e44918889c90 100644 --- a/lib/matplotlib/widgets.py +++ b/lib/matplotlib/widgets.py @@ -717,7 +717,7 @@ def __init__(self, ax, label, initial='', self.connect_event('key_press_event', self._keypress) self.connect_event('resize_event', self._resize) ax.set_navigate(False) - ax.set_facecolor(color) + ax.set_axis_bgcolor(color) ax.set_xticks([]) ax.set_yticks([]) self.color = color @@ -804,11 +804,25 @@ def _keypress(self, event): self.text_disp.remove() self.text_disp = self._make_text_disp(self.text) self._rendercursor() - for cid, func in six.iteritems(self.change_observers): - func(self.text) + self._notify_change_observers() if key == "enter": self._notify_submit_observers() + def set_val(self,val): + newval = str(val) + if self.text==newval: + return + self.text = newval + self.text_disp.remove() + self.text_disp = self._make_text_disp(self.text) + self._rendercursor() + self._notify_change_observers() + self._notify_submit_observers() + + def _notify_change_observers(self): + for cid, func in six.iteritems(self.change_observers): + func(self.text) + def begin_typing(self, x): self.capturekeystrokes = True #disable command keys so that the user can type without @@ -887,7 +901,7 @@ def _motion(self, event): else: c = self.color if c != self._lastcolor: - self.ax.set_facecolor(c) + self.ax.set_axis_bgcolor(c) self._lastcolor = c if self.drawon: self.ax.figure.canvas.draw() From bde104072269deb45909b75ac67f1c255f8c8b09 Mon Sep 17 00:00:00 2001 From: Sterling Smith Date: Mon, 29 Aug 2016 09:45:29 -0700 Subject: [PATCH 9/9] set_axis_bgcolor -> set_facecolor per request of @tacaswell --- lib/matplotlib/backend_bases.py | 4 ++-- lib/matplotlib/widgets.py | 22 +++++++++++----------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 3effb393cd85..63ca290854bf 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -2606,14 +2606,14 @@ def _get_uniform_gridstate(ticks): # keys in list 'all' enables all axes (default key 'a'), # otherwise if key is a number only enable this particular axes # if it was the axes, where the event was raised - if not (event.key in all): + if not (event.key in all_keys): n = int(event.key) - 1 for i, a in enumerate(canvas.figure.get_axes()): # consider axes, in which the event was raised # FIXME: Why only this axes? if event.x is not None and event.y is not None \ and a.in_axes(event): - if event.key in all: + if event.key in all_keys: a.set_navigate(True) else: a.set_navigate(i == n) diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py index e44918889c90..1c781649c9ac 100644 --- a/lib/matplotlib/widgets.py +++ b/lib/matplotlib/widgets.py @@ -675,7 +675,7 @@ def __init__(self, ax, label, initial='', hovercolor : color The color of the box when the mouse is over it - + label_pad : float the distance between the label and the right side of the textbox """ @@ -717,7 +717,7 @@ def __init__(self, ax, label, initial='', self.connect_event('key_press_event', self._keypress) self.connect_event('resize_event', self._resize) ax.set_navigate(False) - ax.set_axis_bgcolor(color) + ax.set_facecolor(color) ax.set_xticks([]) ax.set_yticks([]) self.color = color @@ -808,9 +808,9 @@ def _keypress(self, event): if key == "enter": self._notify_submit_observers() - def set_val(self,val): + def set_val(self, val): newval = str(val) - if self.text==newval: + if self.text == newval: return self.text = newval self.text_disp.remove() @@ -825,8 +825,8 @@ def _notify_change_observers(self): def begin_typing(self, x): self.capturekeystrokes = True - #disable command keys so that the user can type without - #command keys causing figure to be saved, etc + # disable command keys so that the user can type without + # command keys causing figure to be saved, etc self.reset_params = {} for key in self.params_to_disable: self.reset_params[key] = rcParams[key] @@ -838,8 +838,8 @@ def stop_typing(self): # user's code, we only want to call it once we've already done # our cleanup. if self.capturekeystrokes: - #since the user is no longer typing, - #reactivate the standard command keys + # since the user is no longer typing, + # reactivate the standard command keys for key in self.params_to_disable: rcParams[key] = self.reset_params[key] notifysubmit = True @@ -850,8 +850,8 @@ def stop_typing(self): self._notify_submit_observers() def position_cursor(self, x): - #now, we have to figure out where the cursor goes. - #approximate it based on assuming all characters the same length + # now, we have to figure out where the cursor goes. + # approximate it based on assuming all characters the same length if len(self.text) == 0: self.cursor_index = 0 else: @@ -901,7 +901,7 @@ def _motion(self, event): else: c = self.color if c != self._lastcolor: - self.ax.set_axis_bgcolor(c) + self.ax.set_facecolor(c) self._lastcolor = c if self.drawon: self.ax.figure.canvas.draw()