|
#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);
|
|
}
|