release
dev builds
extras
themes manual
wiki
device status forums
mailing lists
IRC bugs
patches
dev guide



Search | Go
Wiki > Main > DocsIndex > ListWidget (r4)

Beginners guide to using the List Widget

What is the List Widget

Since you're reading this, you're probably in the need of some nice, efficient, generic, well tested piece of code to help you show a list of something in your contribution to RockBox. Well, that piece of code is usually referred to as the List Widget in the IRC channel and it can be found in %ROCKBOX_HOME%/apps/gui/list.c. I'm writing this guide because I hope I can make it simpler to use by new contributors to RockBox.

Even though lists can be used in several, well known situations like file browsing, menus and the music database, the highly generic way lists are implemented in this case expands those situations to even implement a text viewer, a file/folder property viewer and many other things to come.

A brief discussion of the architecture

Let's start off by pointing out that I didn't write the List Widget, as a matter of fact, I started using it yesterday and I (sadly) realized this document didn't already exist. So discussing the architecture may well be out of my jurisdiction. However, I do know this brief explanation can clarify some things to new contributors and will hopefully be improved by other more experienced users.

When someone hears about something that handles lists, at first thought he can expect the code to be a generic data structure that will hold all the necessary information in some kind of data structure (i.e.: a linked list). However, the List Widget is not designed to store data, but either to display data in the form of a list and handle user interaction like scrolling and selecting items within the list.

But if the List Widget will not store the data, where/how will the data be stored? Well, that's the great part. It's entirely up to you. It can well be a linked list, an array, a file, you name it. All that is needed for the list to work is that you're able to reference the appropriate item in the list by it's index.

Ok, but if the List doesn't have the data, how will it know what to show in the list? Certainly, the list doesn't hold the data that is listed, so it need a link to the data. That link is implemented as a function pointer. For the widget to show the labels of the items in the list, you implement a function that will provide the label when the index of the item to be shown is handed over to it (as well as a couple other parameter I'll discuss later).

Besides allowing to easily list items, the widget has several other useful features including:

  • An optional scroll bar to give the user an idea of the number of items in the list and the current position.
  • The ability to cycle to the top/bottom of the list when the bottom/top is reached.
  • Scroll the label of an item if it's too long to fit inside the screen.
  • Show icons representing the type of item

How to use the widget

Enough talking, let's get down to business and start coding. As an example, I will assume a list of names is stored in a linked list in a structure like this:

struct names_list {
        char first_name[10];
        char last_name[10];
        struct names_list *next;
    }

Since this topic is not about linked lists, I'll skip the part where the list is filled with data. All you need to know is that we have a:

struct names_list *people;

that points to the first item in the list and that we can reach the next item in the list by invoking:

people->next;

as I said in the architecture, how the data is stored is entirely up to you.

Declaration of the list

In order to use the list, you obviously need to #include something. Luckily, plugin.h has everything we need, from data structures to function pointers. So we should start by:

#include "plugin.h"

but you probably already included that one. After that, we declare the list itself:

struct gui_synclist gui_people;

Initialization of the list widget

As I mentioned before, the list doesn't hold the data itself but a pointer to a function. Of course how you create the label of an item can vary a lot, but your function should look similar to this:

char * get_person_label(int selected_item, void *data, char *buffer){
    int i;
    struct names_list *temp_node = (struct names_list *)data;
    char text_buffer[50];

    for (i=0;i<selected_item && temp_node != NULL;i++){
        temp_node = temp_node->next;
    }
    if (temp_node == NULL){
        return NULL;
    }
    rb->snprintf(text_buffer, 50,"%s, %s", temp_node->last_name, temp_node->first_name);

    rb->strcpy(buffer, text_buffer);
    return buffer;
}

What can be seen here is:

  1. The function receives a pointer to some data. While this is optional, you can provide that pointer to your data structures when initializing the list.
  2. The way the widget informs the function what item it needs the label for is through a selected_item parameter. What you do with it is up to you.
  3. The label is strcpy'd to the buffer pointer and then the same buffer pointer is returned.

Now the actual initialization goes like this:

rb->gui_synclist_init(&gui_people, &get_person_label, people, false, 1);

What's all that????? relax, it's not that much:
  1. &gui_people: The pointer to the list. Remember we didn't declare it as a pointer but as an actual variable.
  2. &get_person_label: The pointer to the function that will provide the labels for all the items.
  3. people: The optional data parameter that allowed us to get the pointer to the actual linked list inside the get_person_label function.
  4. false: If set to true, the list will scroll all the labels that are too large to fit inside the screen at once. That looks kind of add. If set to false, only the selected item will scroll.
  5. 1: The number of lines that will be selected at once. If you need to show several lines per item, you can set this to a greater value. Of course, you would need to handle the situation in your label function (i.e.: set to show 3 lines per item, divide the selected_index by 3 to know the actual item number and calculate selected_index % 3 to know the part of the item you're showing)

Set some properties

Ok, the list is initialized, but it may need some more property-setting before it looks and behaves the way we want to. I'll set some properties all at once and elaborate later:

rb->gui_synclist_set_title(&gui_people, "Contacts", NOICON);
    rb->gui_synclist_set_icon_callback(&gui_people, &get_person_icon);
    rb->gui_synclist_set_nb_items(&gui_people, people_count);
    rb->gui_synclist_limit_scroll(&gui_people, true);
    rb->gui_synclist_select_item(&gui_people, 0);

Ok, what do you have here?

  1. Set the title. Of course we provide a string for that. The third parameter indicates the icon number (from an enum).
  2. Set the icon callback. This one works exactly the same as your label function, but it returns the icon number you want shown besides the selected_item. Once again, the icon is numbered in an enum called themable_icons.
  3. Set the number of items in the list. The list doesn't have all the data, remember? So we need to let it know the number of items it should look for and show.
  4. Indicate the list if we want to limit the scroll at the top and bottom. Setting this to false will allow the user to cycle.
  5. And finally, let the list know what's the originally selected item. Of course you can use this in a different way somewhere else in your code, but we need to do it at the begining too.

Handling interaction with the list

Last but not least, we have to draw the list in the screen and handle user interaction. Let's see a piece of code again and explain it line by line:

int curr_selection = 0; /* not strictly needed */
    int button;
    while (true) {
        rb->gui_syncstatusbar_draw(rb->statusbars, true); /* draw the statusbar, shuold be done often */
        button = rb->get_action(CONTEXT_LIST,TIMEOUT_BLOCK); /* user input */
        if (rb->gui_synclist_do_button(&gui_people, &button, LIST_WRAP_UNLESS_HELD)) { /* automatic handling of user input. _UNLESS_HELD can be _ON or _OFF also */
            continue;
        }
        switch (button) { /* process the user input */
            case ACTION_STD_OK:
                return rb->gui_synclist_get_sel_pos(&gui_people);;
            break;
            case ACTION_STD_CANCEL:
                return NULL;
            break;
        }
    }

We need both the list widget and ourselves to handle user button clicks so everything works fine. To allow this, we're expected to get user actions and hand them over to the widget. After that, we can further handle the actions to complete the functionality.

  1. I've made the loop infinite but some people are against this and prefer a flag set in a case inside the switch.
  2. Force the status bar to be shown. While this is a different subject, the list may leave a line where the statusbar usually goes.
  3. The actual drawing of the list. This is when our label/icon functions will be called to show the appropriate items according to the current position in the list. NOTE, only draw the list when exiting and re-enteriung the list, or the selection changes. otherwise text will never scroll.
  4. Wait for an action from the user.
  5. Let the list widget handle the user's action. The function returns the action taken or 0 if no action was taken. This let's us decide if we want to continue handling the action or not. Of course every situation can be handled differently.
  6. Further handle the action ourselves. Note that we're using a widget function to get the current position in the list and return that value.
  • LIST_WRAP_UNLESS_HELD -
    • LIST_WRAP_UNLESS_HELD means don't let the list wrap if the user is holding down up/down, shohuold be used everywhere except special cases..
    • LIST_WRAP_ON means always allow the list to wrap... not very nice.. shuoldnt be used
    • LIST_WRAP_OFF means never allow the list to wrap... shohuold only be used by the volume setting.

Further reading

All the functions of the widget are well documented in %ROCKBOX_HOME%/apps/gui/list.h, while I tried to make this a complete guide, there are some other functions of the list like adding/removing items I didn't cover. Also, I didn't write much about icons but you can find several examples in the plugins and the core itself.

-- MauricioPeccorini - 29 May 2007

commented the code to help new users, and fixed it up a bit -- JonathanGordon - 30 May 2007
Edit | Attach | Print version | History: r6 | r5 < r4 < r3 < r2 | Backlinks | View wiki text | More topic actions...
r4 - 17 Sep 2007 - 10:10:46 - JonathanGordon

Parents: DocsIndex
Copyright by the contributing authors.