Skip to content

Commit a4b366e

Browse files
committed
Almost-working tooltip positioning.
I still need to figure out why the tooltip alternates between displaying in the middle of the screen and at the right position.
1 parent 4c820e1 commit a4b366e

File tree

1 file changed

+64
-6
lines changed

1 file changed

+64
-6
lines changed

bpython/urwid.py

Lines changed: 64 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -114,14 +114,36 @@ def format_tokens(tokensource):
114114

115115
class BPythonEdit(urwid.Edit):
116116

117-
"""Customized editor *very* tightly interwoven with URWIDRepl."""
117+
"""Customized editor *very* tightly interwoven with URWIDRepl.
118+
119+
Changes include:
120+
121+
- The edit text supports markup, not just the caption.
122+
This works by calling set_edit_markup from the change event
123+
as well as whenever markup changes while text does not.
124+
125+
- The widget can be made readonly, which currently just means
126+
it is no longer selectable and stops drawing the cursor.
127+
128+
This is currently a one-way operation, but that is just because
129+
I only need and test the readwrite->readonly transition.
130+
"""
118131

119132
def __init__(self, *args, **kwargs):
120133
self._bpy_text = ''
121134
self._bpy_attr = []
122135
self._bpy_selectable = True
123136
urwid.Edit.__init__(self, *args, **kwargs)
124137

138+
def make_readonly(self):
139+
self._bpy_selectable = False
140+
# This is necessary to prevent the listbox we are in getting
141+
# fresh cursor coords of None from get_cursor_coords
142+
# immediately after we go readonly and then getting a cached
143+
# canvas that still has the cursor set. It spots that
144+
# inconsistency and raises.
145+
self._invalidate()
146+
125147
def set_edit_markup(self, markup):
126148
"""Call this when markup changes but the underlying text does not.
127149
@@ -144,6 +166,14 @@ def get_cursor_coords(self, *args, **kwargs):
144166
return None
145167
return urwid.Edit.get_cursor_coords(self, *args, **kwargs)
146168

169+
def render(self, size, focus=False):
170+
# XXX I do not want to have to do this, but listbox gets confused
171+
# if I do not (getting None out of get_cursor_coords because
172+
# we just became unselectable, then having this render a cursor)
173+
if not self._bpy_selectable:
174+
focus = False
175+
return urwid.Edit.render(self, size, focus=focus)
176+
147177
def get_pref_col(self, size):
148178
# Need to make this deal with us being nonselectable
149179
if not self._bpy_selectable:
@@ -183,12 +213,15 @@ def render(self, size, focus=False):
183213

184214
class URWIDRepl(repl.Repl):
185215

186-
def __init__(self, main_loop, listbox, listwalker, tooltiptext,
187-
interpreter, statusbar, config):
216+
# XXX this is getting silly, need to split this up somehow
217+
def __init__(self, main_loop, frame, listbox, listwalker, overlay,
218+
tooltiptext, interpreter, statusbar, config):
188219
repl.Repl.__init__(self, interpreter, config)
189220
self.main_loop = main_loop
221+
self.frame = frame
190222
self.listbox = listbox
191223
self.listwalker = listwalker
224+
self.overlay = overlay
192225
self.tooltiptext = tooltiptext
193226
self.edits = []
194227
self.edit = None
@@ -251,8 +284,9 @@ def _populate_completion(self, main_loop, user_data):
251284
if self.argspec:
252285
text = '%s\n\n%r' % (text, self.argspec)
253286
self.tooltiptext.set_text(text)
287+
self.frame.body = self.overlay
254288
else:
255-
self.tooltiptext.set_text('NOPE')
289+
self.frame.body = self.listbox
256290

257291
def reprint_line(self, lineno, tokens):
258292
edit = self.edits[-len(self.buffer) + lineno - 1]
@@ -283,19 +317,43 @@ def prompt(self, more):
283317
self.edits.append(self.edit)
284318
self.listwalker.append(self.edit)
285319
self.listbox.set_focus(len(self.listwalker) - 1)
320+
# Hide the tooltip
321+
self.frame.body = self.listbox
286322

287323
def on_input_change(self, edit, text):
288324
tokens = self.tokenize(text, False)
289325
edit.set_edit_markup(list(format_tokens(tokens)))
290326
# If we call this synchronously the get_edit_text() in repl.cw
291327
# still returns the old text...
292328
self.main_loop.set_alarm_in(0, self._populate_completion)
329+
self._reposition_tooltip()
330+
331+
def _reposition_tooltip(self):
332+
# Reposition the tooltip based on cursor position.
333+
screen_cols, screen_rows = self.main_loop.screen.get_cols_rows()
334+
# XXX this should use self.listbox.get_cursor_coords
335+
# but that doesn't exist (urwid oversight)
336+
offset, inset = self.listbox.get_focus_offset_inset(
337+
(screen_cols, screen_rows))
338+
rel_x, rel_y = self.edit.get_cursor_coords((screen_cols,))
339+
y = offset + rel_y
340+
if y < 0:
341+
# Cursor off the screen (no clue if this can happen).
342+
# Just clamp to 0.
343+
y = 0
344+
# XXX not sure if these overlay attributes are meant to be public...
345+
if y * 2 < screen_rows:
346+
self.overlay.valign_type = 'fixed top'
347+
self.overlay.valign_amount = y + 1
348+
else:
349+
self.overlay.valign_type = 'fixed bottom'
350+
self.overlay.valign_amount = screen_rows - y - 1
293351

294352
def handle_input(self, event):
295353
if event == 'enter':
296354
inp = self.edit.get_edit_text()
297355
self.history.append(inp)
298-
self.edit._bpy_selectable = False
356+
self.edit.make_readonly()
299357
# XXX what is this s_hist thing?
300358
self.stdout_hist += inp + '\n'
301359
self.edit = None
@@ -374,7 +432,7 @@ def main(args=None, locals_=None, banner=None):
374432
loop = urwid.MainLoop(frame, palette, event_loop=event_loop)
375433

376434
# TODO: hook up idle callbacks somewhere.
377-
myrepl = URWIDRepl(loop, listbox, listwalker, tooltiptext,
435+
myrepl = URWIDRepl(loop, frame, listbox, listwalker, overlay, tooltiptext,
378436
interpreter, statusbar, config)
379437

380438
# XXX HACK: circular dependency between the event loop and repl.

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy