Possible ListBox bug?


#1

I’ve observed what seems to be a bug.

Say you have a ListBox connected to a ListBoxModel that has two rows. If you click on the second row, the ListBoxModel will get a callback to selectedRowsChanged() with lastRowSelected == 1. This is fine.

Now say that something causes the ListBoxModel to delete that row. Let’s say we have a Button, hooked up to a controller class that is a ButtonListener. When the Button is clicked, the controller object is notified via an event, and the controller then asks the ListBoxModel to delete the second row. After doing so, the ListBoxModel might then generate an event (saying the model’s state has changed). The ListBox might be a listener for this kind of event, so it calls updateContent() to refresh the view of the model.

At this point, ListBox will callback to the ListBoxModel’s selectedRowsChanged() function, with lastRowSelected == 0. However, the row is not actually selected – the call to ListBoxModel’s paintListBoxItem() function, when rowNumber == 0, will have rowIsSelected == false.

In other words, if a ListBox says row N in a ListBoxModel selected, and row N is deleted by the ListBoxModel, ListBox will say that row N-1 is selected in the selectedRowsChanged() callback to the model, but the call to paint shows row N-1 as not selected.


#2

Part of the behaviour might be from the function:

void ListBox::updateContent()
{
    hasDoneInitialUpdate = true;
    totalItems = (model != 0) ? model->getNumRows() : 0;

    bool selectionChanged = false;

    if (selected [selected.size() - 1] >= totalItems)
    {
        selected.removeRange (totalItems, INT_MAX - totalItems);

        if (selected.size() == 0)
            lastRowSelected = totalItems - 1;
        else if (lastRowSelected >= totalItems)
            lastRowSelected = getSelectedRow (0);

        selectionChanged = true;
    }

    viewport->updateVisibleArea (isVisible());
    viewport->resized();

    if (selectionChanged && model != 0)
        model->selectedRowsChanged (lastRowSelected);

The first if statement is true when the row that was last selected no longer exists in the model – i.e. if the last row in the model was deleted.

All the rows selected from that index onward are removed.

The nested if statement is true when there are no longer any rows selected. If so, we say the last row selected was the one before it.

In my example above, row index 1 is deleted, so we tell the model, via selectedRowsChanged(), that lastRowSelected is 0; but the call paintListBoxItem() with rowNumber as 0 and rowIsSelected as false.

There is a caching error somewhere here.


#3

Upon further inspection, I would suggest there is a problem with

[code] if (selected.size() == 0)
lastRowSelected = totalItems - 1;

[/code]

What this looks like is “if nothing is selected, then the last row is selected” – but the last row is not actually marked as selected, in the selected variable.

I would suggest one of two changes, depending on the behaviour desired.

if (selected.size() == 0) lastRowSelected = -1;

if (selected.size() == 0) { lastRowSelected = totalItems - 1; selected.addRange(lastRowSelected, 1); }


#4

Thanks Thomas, that’s some good bug-hunting. I think what I was going for was your first suggestion, where lastRowSelected = -1, so I’ll get that tweaked right away!

In fact, I think it could probably just be simplified to this:

    if (selected [selected.size() - 1] >= totalItems)
    {
        selected.removeRange (totalItems, INT_MAX - totalItems);
        lastRowSelected = getSelectedRow (0);
        selectionChanged = true;
    }

#5