Project

General

Profile

Bug #546 ยป treeview_stale_index.cc

Johny Mattsson, 10/01/2010 05:26 AM

 
#include <Wt/WTreeView>
#include <Wt/WApplication>
#include <Wt/WAbstractItemModel>
#include <Wt/WPushButton>
#include <Wt/WContainerWidget>
#include <Wt/WVBoxLayout>


/*
* Usage:
* 1) Build (link with -lwt -lwthttp) & start test program.
* 2) Note that there are 3 nodes visible, a parent node and a/b nodes under
* that.
* 3) Press the "Remove A" button.
* 4) Observe that the "a" node disappears.
* 5) Collapse the parent node.
* 6) Expand the parent node. At this point the test program will abort due
* to receiving an old "a" index in the parent() function. This is Bad(tm).
*
* Alternatively, if the WTreeView has enabled selection, step 5 & 6 can be
* replaced by:
* 5) Attempt to select either of the remaining nodes. At this point the
* test program will abort due to receiving an old "a" index in the
* parent() function. This is Bad(tm).
*/

class TestModel : public Wt::WAbstractItemModel
{
public:
TestModel ()
: Wt::WAbstractItemModel (),
par (createIndex (0, 0, (void *)("parent"))),
a (createIndex (0, 0, (void *)("node a"))),
b (createIndex (1, 0, (void *)("node b"))),
b2 (createIndex (0, 0, (void *)("node b"))),
invalid (),
a_deleted (false)
{
}

void remove_a ()
{
if (!a_deleted)
{
beginRemoveRows (par, a.row (), a.row ());
a_deleted = true;
// If instead we move the beginRemoveRows() call to AFTER we've
// removed the row in the model, the WTreeView behaves better.
// This seems to be highly dangerous if the model will also
// be used by any other type of view.
// beginRemoveRows (par, a.row (), a.row ());
endRemoveRows ();
}
}

void assert_fresh (const Wt::WModelIndex &idx, const std::string &where) const
{
if (a_deleted && idx == a)
{
std::cout << "### Error: stale index '" <<
static_cast<const char *> (idx.internalPointer ()) <<
"' in " << where << "\n";
abort ();
}
}

int rowCount (const Wt::WModelIndex &idx) const
{
if (idx == invalid)
return 1;
if (idx == par)
return 2;
return 0;
}

int columnCount (const Wt::WModelIndex &idx) const
{
if (idx == invalid || idx == par)
return 1;
return 0;
}

Wt::WModelIndex index (int row, int col, const Wt::WModelIndex &p) const
{
assert_fresh (p, "TestModel::index()");

if (p == invalid)
{
if (row == 0 && col == 0)
return par;
}
else if (p == par && col == 0)
{
if (a_deleted)
{
// The correct index at parent,0,0 is now b2, not a
if (row == 0) return b2;
}
else {
if (row == 0) return a;
if (row == 1) return b;
}
}
return invalid;
}

Wt::WModelIndex parent (const Wt::WModelIndex &idx) const
{
assert_fresh (idx, "TestModel::parent()");
if (idx == a || idx == b)
return par;
return invalid;
}

boost::any data (const Wt::WModelIndex &idx, int role) const
{
assert_fresh (idx, "TestModel::data()");

if (role == Wt::DisplayRole)
return boost::any (
static_cast<const char *> (idx.internalPointer ()));
return boost::any ();
}

Wt::WFlags<Wt::ItemFlag> flags (const Wt::WModelIndex &idx) const
{
if (idx == par || idx == a || idx == b || idx == b2)
return Wt::ItemIsSelectable;
else
return 0;
}

private:
const Wt::WModelIndex par, a, b, b2, invalid;
bool a_deleted;
};

class TestApp : public Wt::WApplication
{
public:
explicit TestApp (const Wt::WEnvironment &env)
: Wt::WApplication (env),
model (new TestModel ())
{
Wt::WVBoxLayout *vbl = new Wt::WVBoxLayout ();
root ()->setLayout (vbl);
Wt::WTreeView *tv = new Wt::WTreeView ();
tv->setModel (model);
tv->expand (model->index (0, 0, Wt::WModelIndex ()));
// The problem can manifest itself during selection as well as
// during an expand. See top comments.
//tv->setSelectionMode (Wt::SingleSelection);
vbl->addWidget (tv, 100);
Wt::WPushButton *btn = new Wt::WPushButton ("Remove A");
btn->clicked ().connect (SLOT(model, TestModel::remove_a));
vbl->addWidget (btn);
}

private:
TestModel *model;
};




Wt::WApplication *
createApp (const Wt::WEnvironment &env)
{
return new TestApp (env);
}

int main (int argc, char *argv[])
{
return Wt::WRun (argc, argv, &createApp);
}
    (1-1/1)