Skip to content

Commit 1040bd6

Browse files
committed
Calculate tooltip position when needed instead of when changed.
This is actually simpler and means we get things like output or scrolling while the tooltip is up right.
1 parent a264966 commit 1040bd6

File tree

1 file changed

+43
-45
lines changed

1 file changed

+43
-45
lines changed

bpython/urwid.py

Lines changed: 43 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -183,33 +183,64 @@ def get_pref_col(self, size):
183183
return urwid.Edit.get_pref_col(self, size)
184184

185185

186-
class Tooltip(urwid.Overlay):
186+
class Tooltip(urwid.BoxWidget):
187187

188-
"""Exactly like Overlay but passes events to the bottom window.
188+
"""Container inspired by Overlay to position our tooltip.
189189
190-
Also uses the cursor position from the bottom window
191-
(even if this cursor ends up on top of the top window!)
190+
This passes keyboard events to the bottom instead of the top window.
192191
193-
This is a quick and dirty hack.
192+
It also positions the top window relative to the cursor position
193+
from the bottom window and hides it if there is no cursor.
194194
"""
195195

196-
# TODO: mouse events
196+
def __init__(self, bottom_w, top_w):
197+
self.__super.__init__()
198+
199+
self.bottom_w = bottom_w
200+
self.top_w = top_w
201+
197202
def selectable(self):
198203
return self.bottom_w.selectable()
199204

200205
def keypress(self, size, key):
201-
# XXX is just passing size along correct?
202206
return self.bottom_w.keypress(size, key)
203207

208+
def mouse_event(self, size, event, button, col, row, focus):
209+
# TODO: pass to top widget if visible and inside it.
210+
if not hasattr(self.bottom_w, 'mouse_event'):
211+
return False
212+
213+
return self.bottom_w.mouse_event(
214+
size, event, button, col, row, focus)
215+
204216
def get_cursor_coords(self, size):
205217
return self.bottom_w.get_cursor_coords(size)
206218

207219
def render(self, size, focus=False):
208-
canvas = urwid.Overlay.render(self, size, focus)
209-
# XXX HACK: re-render the bottom and steal its cursor coords
220+
maxcol, maxrow = size
210221
bottom_c = self.bottom_w.render(size, focus)
211-
canvas = urwid.CompositeCanvas(canvas)
212-
canvas.cursor = bottom_c.cursor
222+
cursor = bottom_c.cursor
223+
if not cursor:
224+
# Hide the tooltip if there is no cursor.
225+
return bottom_c
226+
227+
# TODO: deal with the tooltip not needing all the space we have.
228+
cursor_x, cursor_y = cursor
229+
if cursor_y * 2 < maxrow:
230+
# Cursor is in the top half. Tooltip goes below it:
231+
y = cursor_y + 1
232+
rows = maxrow - y
233+
else:
234+
# Cursor is in the bottom half. Tooltip fills the area above:
235+
y = 0
236+
rows = cursor_y
237+
# The top window never gets focus.
238+
top_c = self.top_w.render((maxcol, rows))
239+
240+
combi_c = urwid.CanvasOverlay(top_c, bottom_c, 0, y)
241+
# Use the cursor coordinates from the bottom canvas.
242+
canvas = urwid.CompositeCanvas(combi_c)
243+
canvas.cursor = cursor
213244
return canvas
214245

215246

@@ -360,37 +391,6 @@ def on_input_change(self, edit, text):
360391
# If we call this synchronously the get_edit_text() in repl.cw
361392
# still returns the old text...
362393
self.main_loop.set_alarm_in(0, self._populate_completion)
363-
self._reposition_tooltip()
364-
365-
def _reposition_tooltip(self):
366-
# Reposition the tooltip based on cursor position.
367-
screen_cols, screen_rows = self.main_loop.screen.get_cols_rows()
368-
# XXX this should use self.listbox.get_cursor_coords
369-
# but that doesn't exist (urwid oversight)
370-
offset, inset = self.listbox.get_focus_offset_inset(
371-
(screen_cols, screen_rows))
372-
rel_x, rel_y = self.edit.get_cursor_coords((screen_cols,))
373-
y = offset + rel_y
374-
if y < 0:
375-
# Cursor off the screen (no clue if this can happen).
376-
# Just clamp to 0.
377-
y = 0
378-
379-
# XXX the tooltip is displayed way too huge now. The easiest way
380-
# to fix that is probably to figure out how much size the
381-
# listbox actually needs here and adjust height_amount.
382-
383-
# XXX not sure if these overlay attributes are meant to be public...
384-
if y * 2 < screen_rows:
385-
self.overlay.valign_type = 'fixed top'
386-
self.overlay.valign_amount = y + 1
387-
self.overlay.height_type = 'fixed bottom'
388-
self.overlay.height_amount = 0
389-
else:
390-
self.overlay.valign_type = 'fixed bottom'
391-
self.overlay.valign_amount = screen_rows - y - 1
392-
self.overlay.height_type = 'fixed top'
393-
self.overlay.height_amount = 0
394394

395395
def handle_input(self, event):
396396
if event == 'enter':
@@ -469,9 +469,7 @@ def main(args=None, locals_=None, banner=None):
469469
tooltip = urwid.ListBox(urwid.SimpleListWalker([
470470
urwid.Text(''), urwid.Text(''), urwid.Text('')]))
471471
# TODO: this linebox should use the 'main' color.
472-
overlay = Tooltip(urwid.LineBox(tooltip), listbox,
473-
'left', ('relative', 100),
474-
('fixed top', 0), ('fixed bottom', 0))
472+
overlay = Tooltip(listbox, urwid.LineBox(tooltip))
475473

476474
frame = urwid.Frame(overlay, footer=statusbar.widget)
477475

0 commit comments

Comments
 (0)