1

I'd like to implement some hinting as to whether there remains items below or above the list of visible items in an urwid.ListBox when I scroll it up or down. The 'scroll down' hint should appear only when there remains items after the last visible item and it should disappear when the last, visible item is the last item in the list. The reverse applies with the 'scroll up' hint.

I then need to know how many visible items there is in the list. Is there a way to retrieve the number of visible items in a list box, which I suppose is equal to the height of the list box, right?

Here's a starting point of what I'd like to check:

# This example is based on https://cmsdk.com/python/is-there-a-focus-changed-event-in-urwid.html
import urwid

def callback():
    index = str(listbox.get_focus()[1])
    debug.set_text("Index of selected item: " + index)

captions = "A B C D E F".split()
debug = urwid.Text("Debug")
items = [urwid.Button(caption) for caption in captions]
walker = urwid.SimpleListWalker(items)
listbox = urwid.ListBox(walker)
urwid.connect_signal(walker, "modified", callback)
frame = urwid.Frame(body=listbox, header=debug)
urwid.MainLoop(frame).run()

The idea is to know if the listbox is fully visible within the frame when the terminal window is shrunk or not tall enough to display everything, i.e. frame.height >= listbox.height .

  • Could you share a minimal example of the code for what you've already tried? – Elias Dorneles Feb 19 '18 at 14:11
  • @elias Sure thing. Done. –  Feb 19 '18 at 16:20
  • As I'm starting to understand how urwid works, I'm expecting this question cannot be resolved until the ListBox class is derived since height cannot be determined unless a component is drawn. Most (if not all) drawing methods involve a size argument so I guess that's the deal, kind of, right? –  Feb 20 '18 at 08:34
  • right, ListBox has a `calculate_visible` method which needs the size, so I'm thinking it would be possible to create a subclass overwriting the render method to call that and set an attribute saying if there are items not visible... will try that as soon as i have some minutes :) – Elias Dorneles Feb 20 '18 at 10:14
  • Regarding the visualization of hidden list entries, I have described a possible solution in [this answer](https://stackoverflow.com/questions/52428684/how-to-indicate-that-a-urwid-listbox-has-more-items-than-displayed-at-the-moment/52445682#answer-52445682). – AFoeee Sep 21 '18 at 14:56

1 Answers1

1

So, here is one way of doing this by subclassing urwid.ListBox, we can add an attribute all_children_visible which is set at the times when we know the size of the widget (that is, when rendering or when handling an input event).

The sample code, based on the sample you provided:

import string
import urwid

class MyListBox(urwid.ListBox):
    all_children_visible = True

    def keypress(self, size, *args, **kwargs):
        self.all_children_visible = self._compute_all_children_visible(size)
        return super(MyListBox, self).keypress(size, *args, **kwargs)

    def mouse_event(self, size, *args, **kwargs):
        self.all_children_visible = self._compute_all_children_visible(size)
        return super(MyListBox, self).mouse_event(size, *args, **kwargs)

    def render(self, size, *args, **kwargs):
        self.all_children_visible = self._compute_all_children_visible(size)
        return super(MyListBox, self).render(size, *args, **kwargs)

    def _compute_all_children_visible(self, size):
        n_total_widgets = len(self.body)
        middle, top, bottom = self.calculate_visible(size)
        n_visible = len(top[1]) + len(bottom[1])
        if middle:
            n_visible += 1
        return n_total_widgets == n_visible

def callback():
    debug.set_text(
        "Are all children visible? {}\n".format(listbox.all_children_visible)
    )


captions = list(string.uppercase + string.lowercase)

# uncomment this line to test case of all children visible:
# captions = list(string.uppercase)

debug = urwid.Text("Debug")
items = [urwid.Button(caption) for caption in captions]
walker = urwid.SimpleListWalker(items)
listbox = MyListBox(walker)
urwid.connect_signal(walker, "modified", callback)
frame = urwid.Frame(body=listbox, header=debug)
urwid.MainLoop(frame).run()

I'm not sure how well this performs (I haven't tested it extensively), so I'm curious how this will perform for your case -- let me know how it goes. :)

Elias Dorneles
  • 22,556
  • 11
  • 85
  • 107
  • Thanks a whole lot @elias. Isn't "overriding" the `render()` method sufficient though? Logically it should be called by the other two base class methods, if I'm not mistaken. –  Feb 20 '18 at 10:57
  • @Nasha possibly yes, but I thought it would be good to set the property as early as possible, so that you can hook more logic in the event handlers which know already if things will fit or not and decide what to do about it even before rendering -- for example, you could want to call the callback inside the keypress method. – Elias Dorneles Feb 20 '18 at 11:33
  • Ok, I see now. I just might not need it in my application. –  Feb 20 '18 at 11:57
  • @Nasha btw, were you able to get this check of visible items thing working for you? – Elias Dorneles Feb 27 '18 at 22:34
  • I got somewhat distracted from the topic but I don't let this one out. I'll post an update as soon as I have it confirmed. –  Feb 27 '18 at 23:22
  • note: `string.uppercase` -> `string.ascii_uppercase`, `string.lowercase -> string.ascii_lowercase` for python3 – anon01 Mar 28 '18 at 05:20