8000 Added a TextBox widget by keflavich · Pull Request #1983 · matplotlib/matplotlib · GitHub
[go: up one dir, main page]

Skip to content

Added a TextBox widget #1983

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 26 commits into from
Closed
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
TextBox produces str values
  • Loading branch information
Matt Terry committed Jun 5, 2013
commit 4b963a58d8fc9c5b80c7e1c672fa5ba0873cf768
74 changes: 45 additions & 29 deletions lib/matplotlib/widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -1664,7 +1664,8 @@ def onmove(self, event):


class TextBox(AxesWidget):
def __init__(self, ax, s='', enter_callback=None, **text_kwargs):
def __init__(self, ax, s='', allowed_chars=None, type=str,
enter_callback=None, **text_kwargs):
"""
Editable text box

Expand All @@ -1684,8 +1685,15 @@ def __init__(self, ax, s='', enter_callback=None, **text_kwargs):
The parent axes for the widget

*s* : str
The initial text of the TextBox. Should be able to be coerced
to a float.
The initial text of the TextBox.

*allowed_chars* : seq
TextBox will only respond if event.key in allowed_chars. Defaults
to None, which accepts anything.

*type* : type
Construct self.value using this type. self.value is only updated
if self.type(<text>) succeeds.

*enter_callback* : function
A function of one argument that will be called with
Expand All @@ -1700,7 +1708,9 @@ def __init__(self, ax, s='', enter_callback=None, **text_kwargs):
self.ax.set_yticks([])
self.ax.set_xticks([])

self.value = float(s)
self.type = type
self.allowed_chars = allowed_chars
self.value = self.type(s)
self.text = self.ax.text(0.025, 0.2, s,
transform=self.ax.transAxes, **text_kwargs)

Expand All @@ -1710,26 +1720,23 @@ def __init__(self, ax, s='', enter_callback=None, **text_kwargs):
self._cursorpos = len(self.text.get_text())
self.old_callbacks = {}

self.redraw()
self.connect_event('button_press_event', self._mouse_activate)
self.redraw_callbacks = []
self.redraw()

@property
def cursor(self):
# Macos has issues with render objects. Lazily generating the cursor
# solve some of the problems associated
# macosx does not provide render objects until the first fram is done.
# Lazily generating the cursor avoids issues
if self._cursor is None:
r = self._get_text_right() # needs a renderer
self._cursor = self.ax.plot([r, r],
[0.2, 0.8],
transform=self.ax.transAxes,
)[0]
x, y = self._get_cursor_endpoints() # needs a renderer
self._cursor, = self.ax.plot(x, y, transform=self.ax.transAxes)
self._cursor.set_visible(False)
return self._cursor

def redraw(self):
# blitting doesn't clear old text
#self.ax.redraw_in_frame()
#self.canvas.blit(self.ax.bbox)
for f in self.redraw_callbacks:
f()
self.canvas.draw()

def _mouse_activate(self, event):
Expand Down Expand Up @@ -1777,9 +1784,6 @@ def keypress(self, event):
if not isinstance(event.key, str):
# event.key may be None
return
elif event.key in '0123456789.eE-+':
newt = t[:self._cursorpos] + event.key + t[self._cursorpos:]
self._cursorpos += 1
elif event.key == 'backspace': # simulate backspace
if self._cursorpos > 0:
newt = t[:self._cursorpos - 1] + t[self._cursorpos:]
Expand All @@ -1796,30 +1800,42 @@ def keypress(self, event):
if self.enter_callback is not None:
self.enter_callback(self.value)
self.end_text_entry()
elif self.allowed_chars is None:
newt = t[:self._cursorpos] + event.key + t[self._cursorpos:]
self._cursorpos += len(event.key)
elif event.key in self.allowed_chars:
newt = t[:self._cursorpos] + event.key + t[self._cursorpos:]
self._cursorpos += 1
else:
return # do not allow abcdef...

self.set_text(newt)
r = self._get_text_right()
self.cursor.set_xdata([r, r])
x, y = self._get_cursor_endpoints()
self.cursor.set_xdata(x)
self.redraw()

def set_text(self, text):
try:
# only try to update if there's a real value
self.value = float(text)
self.value = self.type(text)
except ValueError:
pass
# but always change the text
self.text.set_text(text)

def _get_text_right(self):
def _get_cursor_endpoints(self):
# to get cursor position
# change text to chars left of the cursor
text = self.text.get_text()
self.text.set_text(text[:self._cursorpos])
bbox = self.text.get_window_extent()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This get_window_extent() causes problems on MacOSX. (related to the blitting problems with macosx). You can get around it by calling self.canvas.draw() before get_window_extent(), but that can be laggy. If you check the backend, you can only inflict the lag on macosx users. But then you're putting backend checks into supposedly backend independent widgets.

Any less hacky ideas of how to get the rendered width of a block of text?

l, b, w, h = bbox.bounds

renderer = self.ax.get_renderer_cache()
en = renderer.points_to_pixels(self.text.get_fontsize()) / 2.
l, b, w, h = bbox.bounds # in pixels
r = l + w
# now restore correct text
self.text.set_text(text)

r = l + self._cursorpos * np.ceil(en)
r, t = self.ax.transAxes.inverted().transform((r, b + h))
return r
# cursor line in data coordinates
bx, by = self.ax.transAxes.inverted().transform((r, b))
tx, ty = self.ax.transAxes.inverted().transform((r, b + h))
dy = 0.5 * (ty - by)
return [bx, tx], [by - dy, ty + dy]
0