Tutorial

Inti Logo

« Tree Selections  Creating the View Component »

Creating a Model

Inti provides two types of models that can be used: ListStore and TreeStore. ListStore is used to model columned list widgets, while TreeStore models columned tree widgets. It is possible to develop a new type of model, but the existing models should be satisfactory for all but the most specialized of situations.

To create a new model call one of the following constructors:

ListStore(int n_columns, ...);

ListStore(const std::vector<GType>& types);

The first constructor creates a liststore of n_columns, each of the types passed in the variable argument list. For example:

Gtk::ListStore *model = new Gtk::ListStore(3, G_TYPE_INT, G_TYPE_STRING, G_TYPE_BOOLEAN);

The second constructor does the same thing except you pass a vector of types. Since the number of types is known the n_columns argument is not required. For example:

std::vector<GType> types;
types.push_back(G_TYPE_INT);
types.push_back(G_TYPE_STRING);
types.push_back(G_TYPE_BOOLEAN);
Gtk::ListStore *model = new Gtk::ListStore(types);

Both examples create a list store with three columns: an integer column, a string column and a boolean column. The columns appear in the view in the order of declaration. Typically the 3 is never passed directly like that; usually an enum is created wherein the different columns are enumerated, followed by a token that represents the total number of columns. The next example will illustrate this, only using a tree store instead of a list store. Creating a tree store operates almost exactly the same.

enum
{
   TITLE_COLUMN,    // Book title
   AUTHOR_COLUMN,   // Author
   CHECKED_COLUMN,  // Is checked out?
   N_COLUMNS        // Total number of columns
};


Gtk::TreeStore *model = new Gtk::TreeStore(N_COLUMNS, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN);

Adding data to the model is done using one of the Gtk::ListStore or Gtk::TreeStore set_() methods, depending upon which sort of model was created. To do this, a TreeIter must be acquired. The iterator points to the location where data will be added. Once an iterator has been acquired, Gtk::TreeStore::set_() is used to apply data to the part of the model that the iterator points to.

For a ListStore you can acquire an iterator with one of the following methods:

Gtk::TreeIter insert(int position);

Gtk::TreeIter insert_before(Gtk::TreeIter& sibling);

Gtk::TreeIter insert_after(Gtk::TreeIter& sibling);

Gtk::TreeIter prepend();

Gtk::TreeIter append();

All these methods add a new row to the model and return an iterator set to the new row. The first method inserts the row at the specified position. The next two methods insert the row before and after the row identified by sibling. The fourth method prepends the row to the beginning of the list and the the last method appends the row to the end of the list.

For a TreeStore you can acquire an iterator with one of the following methods:

Gtk::TreeIter insert(int position, Gtk::TreeIter *parent = 0);

Gtk::TreeIter insert_before(Gtk::TreeIter& sibling, Gtk::TreeIter *parent = 0);

Gtk::TreeIter insert_after(Gtk::TreeIter& sibling, Gtk::TreeIter *parent = 0);

Gtk::TreeIter prepend(Gtk::TreeIter *parent = 0);

Gtk::TreeIter append(Gtk::TreeIter *parent = 0);

As with ListStore, these methods add a new row to the model and return an iterator set to this new row, except there is an extra argument: a pointer to a parent iterator. Remember, a TreeStore implements a tree-like hierachical structure so you can can have toplevel rows, parent rows and child rows. A Child row itself can be a parent to a list of child rows which get displayed when the user clicks on the row's arrow indicator.

In the TreeStore methods above, if parent is null the row is added to the toplevel, otherwise the row is added to the list of child rows for the parent. By default, rows are added to the toplevel.

There are six methods that can be used to set values in a ListStore or TreeStore:

void set_value(Gtk::TreeIter& iter, int column, const G::Value& value);

void set_value(Gtk::TreeIter& iter, int column, const char *str);

template<typename DataType>
void set_value(const TreeIter& iter, int column, const DataType& data)
{
    G::Value value(gtk_list_store()->column_headers[column]);
    value.set(data);
    gtk_list_store_set_value(gtk_tree_store(), iter, column, value.g_value());
}

template<typename DataType>
void set_enum(const TreeIter& iter, int column, const DataType& data)
{
    G::Value value(gtk_list_store()->column_headers[column]);
    value.set((int)data);
    gtk_tree_list_set_value(gtk_tree_store(), iter, column, value.g_value());
}

template<typename DataType>
void set_object(const TreeIter& iter, int column, const DataType& data)
{
    G::Value value(gtk_list_store()->column_headers[column]);
    value.set((G::Object*)data);
    gtk_list_store_set_value(gtk_list_store(), iter, column, value.g_value());
}

template<typename DataType>
void set_pointer(const TreeIter& iter, int column, const DataType& data)
{
    G::Value value(gtk_list_store()->column_headers[column]);
    value.set((void*)data);
    gtk_list_store_set_value(gtk_list_store(), iter, column, value.g_value());
}

The first two methods are ordinary functions. The iter argument is a valid iterator for the row being modified and column is the column number to modify. In the first method value is a G::Value that holds the new value for a cell. The second method lets you easily set string literals without using a template. The other four methods are template functions. They let you set cell values directly without using a G::Value. As you can see from the inline definitions these methods supply the G::Value for you.

Before going any further, just a word about G::Values and why there are four template functions. The G::Value class is declared in <inti/glib/value.h>. G:Value has 16 overloaded get() and set() methods. Most of these methods handle unique data types, like bool, gchar, gint, glong and gfloat without any trouble. However, enums and flags fall victim to compiler conversions and require special handling to prevent compiler errors. Rather than duplicate code, G::Value handles theses two types internally. All you are required to do is call the right method. For enums, you must cast the enum to an integer before calling the G::Value get() or set() methods. For flags, you must cast the flag to an unsigned integer before calling get() or set().

That brings us back to the four template functions.  All the template functions require a typename DataType. DataType is the actual data type you are passing. For the set_value() template it must be one of the standard data types, such as bool, int, float, double,  or an Inti::String. For set_enum() it can be any enumeration type. From the code you can see the enum is cast to an int. That's because G::Value handles enums internally as an int. For get_object() DataType must be a pointer to an object derived from G::Object. For get_pointer() DataType can be a pointer to any object. Such pointers are handled internally as a generic (void*) pointer, without any interpretation.

For example, to set a G_TYPE_BOOLEAN column you would do something like this:

model->set_value(iter, column_number, false);

and to set a G_TYPE_ENUM column, say Gtk::StateType, you would have to do this:

model->set_enum(iter, column_number, Gtk::STATE_ACTIVE);

There are five corresponding methods that can be used to get values from a ListStore or TreeStore:

void get_value(const TreeIter& iter, int column, G::Value& value) const;

template<typename DataType>
void get_value(const TreeIter& iter, int column, DataType& data) const
{
    G::Value value;
    gtk_tree_model_get_value(gtk_tree_model(), iter, column, value.g_value());
    value.get(data);
}

template<typename DataType, typename ValueType>
void get_enum(const TreeIter& iter, int column, DataType& data) const
{
    G::Value value;
    gtk_tree_model_get_value(gtk_tree_model(), iter, column, value.g_value());
    int tmp_data;
    value.get(tmp_data);
   
data  = static_cast<DataType>(tmp_data);
}
 
template<typename DataType>
void get_object(const TreeIter& iter, int column, DataType& data) const
{
    G::Value value;
    gtk_tree_model_get_value(gtk_tree_model(), iter, column, value.g_value());
    G::Object *object;
    value.get(object);
    data = static_cast<DataType>(object);
}
 
template<typename DataType>
void get_pointer(const TreeIter& iter, int column, DataType& data) const
{
    G::Value value;
    gtk_tree_model_get_value(gtk_tree_model(), iter, column, value.g_value());
    void *tmp_data = 0;
    value.get(tmp_data);
    data = static_cast<DataType>(tmp_data);
}

To get the value set for the G_TYPE_BOOLEAN column above you would do this:

bool value;
model->get_value(iter, column_number, value);


and to get the value set for the G_TYPE_ENUM column above you would do this:

Gtk::StateType value;
model->get_enum(iter, column_number, value);


So, putting it all together, consider the initial example:

// Acquire an iterator
Gtk::TreeIter  iter = model->append();

// Set the cell values
model->set_value(iter, TITLE_COLUMN, "The Principle of Reason");
model->set_value(iter, AUTHOR_COLUMN, "Martin Heidegger");
model->set_value(iter, CHECKED_COLUMN, false);

The argument to Gtk::TreeStore::append() is the parent iterator. It is used to add a row to a TreeStore as a child of an existing row. This means that the new row will only be visible when its parent is visible and in its expanded state. Consider the following example:

// Acquire a top-level iterator
Gtk::TreeIter parent_iter = model->append();

// Set the cell values
model->set_value(parent_iter, TITLE_COLUMN, "The Art of Computer Programming");
model->set_value(parent_iter, AUTHOR_COLUMN, "Donald E. Knuth");
model->set_value(parent_iter, CHECKED_COLUMN, false);

// Acquire a child iterator and set the values for the child cell
Gtk::TreeIter child_iter = model->append(&parent_iter);
model->set_value(child_iter, TITLE_COLUMN, "Volume 1: Fundamental Algorithms");

child_iter = model->append(&parent_iter);
model->set_value(child_iter, TITLE_COLUMN, "Volume 2: Seminumerical Algorithms");

child_iter = model->append(&parent_iter);
model->set_value(child_iter, TITLE_COLUMN, "Volume 3: Sorting and Searching");



« Tree Selections

Index
Tree and List Widget
Top
Creating the View Component  »