pythonlistboxpseudocode

Implement a listbox


I need to implement a listbox for a mobile. The only relevant controls are up and down arrow keys. The listbox should display as many rows of items from a list as will fit on the screen (screen_rows), one row should be highighted (sel_row) and the display should wrap if the user hits up arrow when the first item is highlighted or down arrow if the last item is highlighted (that is, the last item should be displayed and highlighted if the user hits up when the first item is highlighted). Up arrow highlights the previous item and down arrow highlights the next item.

I've put something together, but am concerned I've missed something in testing. There must be a standard way to do this, given the prevalence of listboxes out there.

def up_key(self):
    if self.sel_row > 0:
       self.sel_row -= 1

    elif self.top_item > 0:  # top_item is the index of the first list item 
        self.top_item -= 1

    elif self.top_item == 0:
        if self.n_lines >= self.screen_rows: # n_lines is the number of items in the list
            self.top_item = self.n_lines - self.screen_rows
            self.sel_row = min(self.screen_rows-1, self.n_lines-1)
        else:
            self.top_item = 0
            self.sel_row = self.n_lines-1


def down_key(self):
    if self.sel_row < self.screen_rows-1 and self.sel_row < self.n_lines-1:
        self.sel_row += 1

    elif self.sel_row == self.screen_rows-1:
        bottom_item = self.top_item + self.screen_rows
        if bottom_item == self.n_lines:
            self.top_item = 0
            self.sel_row = 0
        if bottom_item < self.n_lines:
            self.top_item += 1

    elif self.sel_row == self.n_lines-1:
        self.top_item = 0
        self.sel_row = 0

def set_pos(self, pos):  # display item with index pos
    if pos < 0:
        pos = 0
    elif pos >= self.n_lines:
        pos = self.n_lines - 1

    if pos < self.screen_rows:
        self.top_item = 0
        self.sel_row = pos
    else:
        self.sel_row = min(self.screen_rows, self.n_lines)//2 - 1
        self.top_item = pos - self.sel_row
        if self.top_item >= self.n_lines - self.screen_rows:
            self.top_item = self.n_lines - self.screen_rows - 1
            self.sel_row = pos - self.top_item - 1

EDIT: after each function I call a redraw screen function, which redraws the screen with top_item at the top and sel-row highlighted.

I've added a pseudo-code tag, in case someone has a version in something that's not python.


Solution

  • Few Python programs implement listboxes from scratch -- they're normally just taken from existing toolkits. That may explain why there's no real cross-toolkit "standard"!-)

    Coming to your code, I imagine set_pos is meant to be called right after either up_key or down_key are finished (you don't make this entirely clear).

    My main worry would be the repetitiousness and asymmetry between your two _key routines. Surely given that your specs are so similar for up and down keys, you want to delegate to a single function which takes an "increment" argument, either +1 or -1. That common function could first do self.sel_row += increment, then immediately return in the common case where sel_row is still fine, i.e if self.top_item <= self.sel_row < self.top_item + self.screen_rows; otherwise deal with the cases where sel_row has exited the currently displayed region, by adjusting self.top_item, exiting if that causes no need to wraparound, or finally dealing with the wraparound cases.

    I'd be keen to apply "flat is better than nested" by repeatedly using constructs of the form "do some required state chance; if things are now fine, return" rather than logically more complex "if doing a simple thing will be OK, then do the simple thing; else if something a bit more complicated but not terrible is needed, then do the complicated something; else if we're in a really complicated case, deal with the really complicated problem" -- the latter is far more prone to error and harder to follow in any case.