Gergely Polonkai
aa71f8211d
This is the latest version found at http://scentric.net/tutorial/treeview-tutorial-xml.tar.gz
7850 lines
280 KiB
XML
7850 lines
280 KiB
XML
<?xml version="1.0"?>
|
|
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN"
|
|
"http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd" [
|
|
]>
|
|
|
|
<book id="treeview-tutorial">
|
|
|
|
<bookinfo>
|
|
<date>September 1st, 2003</date>
|
|
<title>GTK+ 2.0 Tree View Tutorial</title>
|
|
<authorgroup>
|
|
<author>
|
|
<firstname>Tim-Philipp</firstname>
|
|
<surname>Müller</surname>
|
|
</author>
|
|
</authorgroup>
|
|
<abstract>
|
|
<para>
|
|
This is a tutorial on how to use the GTK (the GIMP Toolkit)
|
|
GtkTreeView widget through its C interface.
|
|
</para>
|
|
<para>
|
|
Please mail all comments and suggestions to <email>tim at centricular dot net</email>
|
|
</para>
|
|
<para>
|
|
A tarball of the tutorial for off-line reading including the example source codes
|
|
is available here: <ulink url="treeview-tutorial.tar.gz">treeview-tutorial.tar.gz</ulink>.
|
|
</para>
|
|
<para>
|
|
There is also a <ulink url="treeview-tutorial.pdf">version in PDF format</ulink> (for easier
|
|
printing) and the raw <ulink url="treeview-tutorial-xml.tar.gz">docbook XML source document</ulink>.
|
|
</para>
|
|
<para>
|
|
This tutorial is work-in-progress. The latest version can
|
|
be found at <ulink url="http://scentric.net/tutorial/">
|
|
http://scentric.net/tutorial/</ulink>.
|
|
</para>
|
|
<para>
|
|
Some sections are a bit outdated (e.g. GtkTreeModelFilter has been in Gtk since 2.4),
|
|
just haven't gotten around to rewrite them or update them. Sorry!
|
|
</para>
|
|
<para>
|
|
Last updated: September 29th, 2006
|
|
</para>
|
|
</abstract>
|
|
</bookinfo>
|
|
|
|
<toc></toc>
|
|
|
|
|
|
<!-- ***************************************************************** -->
|
|
<chapter id="ch-TreeView">
|
|
<title>Lists and Trees: the GtkTreeView Widget</title>
|
|
|
|
<para>
|
|
<literal>GtkTreeView</literal> is a widget that displays single- or multi-columned lists and trees.
|
|
It replaces the old Gtk+-1.2 GtkCList and GtkCTree widgets. Even though <literal>GtkTreeView</literal> is
|
|
slightly harder to master than its predecessors, it is so much more powerful and flexible
|
|
that most application developers will not want to miss it once they have come to know it.
|
|
</para>
|
|
|
|
<para>
|
|
The purpose of this chapter is not to provide an exhaustive documentation of <literal>GtkTreeView</literal> -
|
|
that is what the <ulink url="http://developer.gnome.org/doc/API/2.0/gtk/TreeWidgetObjects.html">API documentation</ulink> is for,
|
|
which should be read alongside with this tutorial.
|
|
The goal is rather to present an introduction to the most commonly-used aspects of <literal>GtkTreeView</literal>,
|
|
and to demonstrate how the various <literal>GtkTreeView</literal> components and concepts work together.
|
|
Furthermore, an attempt has been made to shed some light on custom tree models and custom cell renderers,
|
|
which seem to be often-mentioned, but rarely explained.
|
|
</para>
|
|
|
|
<para>
|
|
Developers looking for a quick and dirty introduction that teaches them everything they need
|
|
to know in less than five paragraphs will not find it here. In the author's experience,
|
|
developers who do not understand how the tree view and the models work together will run
|
|
into problems once they try to modify the given examples, whereas developers who have worked
|
|
with other toolkits that employ the Model/View/Controller-design will find that the API reference
|
|
provides all the information they need to know in more condensed form anyway. Those who disagree
|
|
may jump straight to the <link linkend="sec-treeview-col-example">working example code</link> of course.
|
|
</para>
|
|
|
|
<para>
|
|
Please note that the code examples in the following sections do not necessarily demonstrate how
|
|
<literal>GtkTreeView</literal> is used best in a particular situation. There are different ways
|
|
to achieve the same result, and the examples merely show those different ways, so that developers
|
|
are able to decide which one is most suitable for the task at hand.
|
|
</para>
|
|
|
|
<sect1 id="sec-TreeView-HelloWorld">
|
|
<title>Hello World</title>
|
|
|
|
<para>
|
|
For the impatient, here is a small treeview 'Hello World' program (which can
|
|
also be found in the examples section of the <ulink url="treeview-tutorial.tar.gz">
|
|
treeview-tutorial.tar.gz</ulink> tarball).
|
|
</para>
|
|
|
|
<programlisting role="C">
|
|
|
|
/*
|
|
* Compile with:
|
|
* gcc -o helloworld helloworld.c `pkg-config --cflags --libs gtk+-2.0`
|
|
*
|
|
*/
|
|
|
|
#include <gtk/gtk.h>
|
|
|
|
enum
|
|
{
|
|
COL_NAME = 0,
|
|
COL_AGE,
|
|
NUM_COLS
|
|
} ;
|
|
|
|
|
|
static GtkTreeModel *
|
|
create_and_fill_model (void)
|
|
{
|
|
GtkListStore *store;
|
|
GtkTreeIter iter;
|
|
|
|
store = gtk_list_store_new (NUM_COLS, G_TYPE_STRING, G_TYPE_UINT);
|
|
|
|
/* Append a row and fill in some data */
|
|
gtk_list_store_append (store, &iter);
|
|
gtk_list_store_set (store, &iter,
|
|
COL_NAME, "Heinz El-Mann",
|
|
COL_AGE, 51,
|
|
-1);
|
|
|
|
/* append another row and fill in some data */
|
|
gtk_list_store_append (store, &iter);
|
|
gtk_list_store_set (store, &iter,
|
|
COL_NAME, "Jane Doe",
|
|
COL_AGE, 23,
|
|
-1);
|
|
|
|
/* ... and a third row */
|
|
gtk_list_store_append (store, &iter);
|
|
gtk_list_store_set (store, &iter,
|
|
COL_NAME, "Joe Bungop",
|
|
COL_AGE, 91,
|
|
-1);
|
|
|
|
return GTK_TREE_MODEL (store);
|
|
}
|
|
|
|
static GtkWidget *
|
|
create_view_and_model (void)
|
|
{
|
|
GtkCellRenderer *renderer;
|
|
GtkTreeModel *model;
|
|
GtkWidget *view;
|
|
|
|
view = gtk_tree_view_new ();
|
|
|
|
/* --- Column #1 --- */
|
|
|
|
renderer = gtk_cell_renderer_text_new ();
|
|
gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (view),
|
|
-1,
|
|
"Name",
|
|
renderer,
|
|
"text", COL_NAME,
|
|
NULL);
|
|
|
|
/* --- Column #2 --- */
|
|
|
|
renderer = gtk_cell_renderer_text_new ();
|
|
gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (view),
|
|
-1,
|
|
"Age",
|
|
renderer,
|
|
"text", COL_AGE,
|
|
NULL);
|
|
|
|
model = create_and_fill_model ();
|
|
|
|
gtk_tree_view_set_model (GTK_TREE_VIEW (view), model);
|
|
|
|
/* The tree view has acquired its own reference to the
|
|
* model, so we can drop ours. That way the model will
|
|
* be freed automatically when the tree view is destroyed */
|
|
|
|
g_object_unref (model);
|
|
|
|
return view;
|
|
}
|
|
|
|
|
|
int
|
|
main (int argc, char **argv)
|
|
{
|
|
GtkWidget *window;
|
|
GtkWidget *view;
|
|
|
|
gtk_init (&argc, &argv);
|
|
|
|
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
|
|
g_signal_connect (window, "delete_event", gtk_main_quit, NULL); /* dirty */
|
|
|
|
view = create_view_and_model ();
|
|
|
|
gtk_container_add (GTK_CONTAINER (window), view);
|
|
|
|
gtk_widget_show_all (window);
|
|
|
|
gtk_main ();
|
|
|
|
return 0;
|
|
}
|
|
</programlisting>
|
|
|
|
</sect1>
|
|
|
|
</chapter>
|
|
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
|
|
<chapter id="sec-treeview-components">
|
|
<title>Components: Model, Renderer, Column, View</title>
|
|
|
|
<para>
|
|
The most important concept underlying <literal>GtkTreeView</literal> is that of complete
|
|
separation between data and how that data is displayed on the screen. This is commonly known
|
|
as Model/View/Controller-design (MVC). Data of various type (strings, numbers, images, etc.)
|
|
is stored in a 'model'. The 'view' is then told which data to display, where to display it,
|
|
and how to display it. One of the advantages of this approach is that you can have multiple
|
|
views that display the same data (a directory tree for example) in different ways, or in the
|
|
same way multiple times, with only one copy of the underlying data. This avoids duplication of
|
|
data and programming effort if the same data is re-used in different contexts. Also, when the
|
|
data in the model is updated, all views automatically get updated as well.
|
|
</para>
|
|
|
|
<para>
|
|
So, while <literal>GtkTreeModel</literal> is used to store data, there are other components
|
|
that determine which data is displayed in the <literal>GtkTreeView</literal> and how it is
|
|
displayed. These components are <literal>GtkTreeViewColumn</literal> and <literal>GtkCellRenderer</literal>.
|
|
A <literal>GtkTreeView</literal> is made up of tree view columns. These
|
|
are the columns that users perceive as columns. They have a clickable column header with a
|
|
column title that can be hidden, and can be resized and sorted. Tree view columns do not
|
|
display any data, they are only used as a device to represent the user-side of the tree view
|
|
(sorting etc.) and serve as packing widgets for the components that do the actual rendering
|
|
of data onto the screen, namely the <literal>GtkCellRenderer</literal> family of objects (I call them 'objects'
|
|
because they are not GtkWidgets). There are a number of different cell renderers that specialise
|
|
in rendering certain data like strings, pixbufs, or toggle buttons. More on this <link linkend="sec-renderer">later</link>.
|
|
</para>
|
|
|
|
<para>
|
|
Cell renderers are packed into tree view columns to display data. A tree view column needs to contain at least one cell
|
|
renderer, but can contain multiple cell renderers. For example, if one wanted to display a 'Filename' column where each
|
|
filename has a little icon on the left indicating the file type, one would pack a <literal>GtkCellRendererPixbuf</literal>
|
|
and a <literal>GtkCellRendererText</literal> into one tree view column. Packing renderers into a tree view column is
|
|
similar to packing widgets into a <literal>GtkHBox</literal>.
|
|
</para>
|
|
|
|
</chapter>
|
|
|
|
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
|
|
<chapter id="sec-treemodels">
|
|
<title>GtkTreeModels for Data Storage: GtkListStore and GtkTreeStore</title>
|
|
|
|
<para>
|
|
It is important to realise what <ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeModel.html"><literal>GtkTreeModel</literal></ulink>
|
|
is and what it is not. <literal>GtkTreeModel</literal> is basically just an 'interface' to the data store,
|
|
meaning that it is a standardised set of functions that allows a <literal>GtkTreeView</literal> widget
|
|
(and the application programmer) to query certain characteristics of a data store,
|
|
for example how many rows there are, which rows have children, and how many children a particular row has.
|
|
It also provides functions to retrieve data from the data store, and tell the tree view what type of data
|
|
is stored in the model. Every data store must implement the <literal>GtkTreeModel</literal> interface and
|
|
provide these functions, which you can use by casting a store to a tree model with
|
|
<literal>GTK_TREE_MODEL(store)</literal>. <literal>GtkTreeModel</literal> itself only provides a way to
|
|
query a data store's characteristics and to retrieve existing data, it does not provide a way to remove
|
|
or add rows to the store or put data into the store. This is done using the specific store's functions.
|
|
</para>
|
|
|
|
<para>
|
|
Gtk+ comes with two built-in data stores (models):
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkListStore.html"><literal>GtkListStore</literal></ulink> and
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeStore.html"><literal>GtkTreeStore</literal></ulink>.
|
|
As the names imply, <literal>GtkListStore</literal> is used for simple lists of data items where items have
|
|
no hierarchical parent-child relationships, and <literal>GtkTreeStore</literal> is used for tree-like data
|
|
structures, where items can have parent-child relationships. A list of files in a directory would be an example
|
|
of a simple list structure, whereas a directory tree is an example for a tree structure. A list is
|
|
basically just a special case of a tree with none of the items having any children, so one could use
|
|
a tree store to maintain a simple list of items as well. The only reason <literal>GtkListStore</literal>
|
|
exists is in order to provide an easier interface that does not need to cater for child-parent relationships,
|
|
and because a simple list model can be optimised for the special case where no children exist, which makes
|
|
it faster and more efficient.
|
|
</para>
|
|
|
|
<para>
|
|
<literal>GtkListStore</literal> and <literal>GtkTreeStore</literal> should cater for most types of data an
|
|
application developer might want to display in a <literal>GtkTreeView</literal>. However, it should be noted
|
|
that <literal>GtkListStore</literal> and <literal>GtkTreeStore</literal> have been designed with flexibility in
|
|
mind. If you plan to store a lot of data, or have a large number of rows, you should consider implementing your own
|
|
custom model that stores and manipulates data your own way and implements the <literal>GtkTreeModel</literal>
|
|
interface. This will not only be more efficient, but probably also lead to saner code in the long run, and give
|
|
you more control over your data. See <link linkend="sec-custom-models">below</link> for more details on how
|
|
to implement custom models.
|
|
</para>
|
|
|
|
<para>
|
|
Tree model implementations like <literal>GtkListStore</literal> and <literal>GtkTreeStore</literal> will take
|
|
care of the view side for you once you have configured the <literal>GtkTreeView</literal> to display what you
|
|
want. If you change data in the store, the model will notify the tree view and your data display will be
|
|
updated. If you add or remove rows, the model will also notify the store, and your row will appear in or
|
|
disappear from the view as well.
|
|
</para>
|
|
|
|
|
|
<!-- ************************************************************** -->
|
|
<sect1 id="sec-treemodel-data">
|
|
<title>How Data is Organised in a Store</title>
|
|
|
|
<para>
|
|
A model (data store) has model columns and rows. While a tree view will display each row in the model as a row
|
|
in the view, the model's columns are not to be confused with a view's columns. A model column represents a certain
|
|
data field of an item that has a fixed data type. You need to know what kind of data you want to store when you
|
|
create a list store or a tree store, as you can not add new fields later on.
|
|
</para>
|
|
|
|
<para>
|
|
For example, we might want to display a list of files. We would create a list store with two fields: a field
|
|
that stores the filename (ie. a string) and a field that stores the file size (ie. an unsigned integer).
|
|
The filename would be stored in column 0 of the model, and the file size would be stored in column 1 of the model.
|
|
For each file we would add a row to the list store, and set the row's fields to the filename and the file size.
|
|
</para>
|
|
|
|
<para>
|
|
The <ulink url="http://developer.gnome.org/doc/API/2.0/gobject/gobject-GType.html">GLib type system (GType)</ulink>
|
|
is used to indicate what type of data is stored in a model column. These are the most commonly used types:
|
|
</para>
|
|
|
|
<itemizedlist>
|
|
<listitem>
|
|
<simpara>
|
|
<literal>G_TYPE_BOOLEAN</literal>
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
<literal>G_TYPE_INT</literal>, <literal>G_TYPE_UINT</literal>
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
<literal>G_TYPE_LONG</literal>, <literal>G_TYPE_ULONG</literal>, <literal>G_TYPE_INT64</literal>, <literal>G_TYPE_UINT64</literal> (these are not supported in early gtk+-2.0.x versions)
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
<literal>G_TYPE_FLOAT</literal>, <literal>G_TYPE_DOUBLE</literal>
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
<literal>G_TYPE_STRING</literal> - stores a string in the store (makes a copy of the original string)
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
<literal>G_TYPE_POINTER</literal> - stores a pointer value (does not copy any data into the store, just stores the pointer value!)
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
<literal>GDK_TYPE_PIXBUF</literal> - stores a GdkPixbuf in the store (increases the pixbuf's
|
|
refcount, <link linkend="sec-treemodel-storing-gobjects">see below</link>)
|
|
</simpara>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
<para>
|
|
You do not need to understand the type system, it will usually suffice to know the above types,
|
|
so you can tell a list store or tree store what kind of data you want to store. Advanced users can derive
|
|
their own types from the fundamental GLib types. For simple structures you could
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gobject/gobject-Boxed-Types.html#g-boxed-type-register-static">register</ulink>
|
|
a new <ulink url="http://developer.gnome.org/doc/API/2.0/gobject/gobject-Boxed-Types.html">boxed type</ulink>
|
|
for example, but that is usually not necessary. <literal>G_TYPE_POINTER</literal> will often do as well, you
|
|
will just need to take care of memory allocation and freeing yourself then.
|
|
</para>
|
|
|
|
<para>
|
|
Storing <literal>GObject</literal>-derived types (most <literal>GDK_TYPE_FOO</literal>
|
|
and <literal>GTK_TYPE_FOO</literal>) is a special case that is dealt with
|
|
<link linkend="sec-treemodel-storing-gobjects">further below</link>.
|
|
</para>
|
|
|
|
<para>
|
|
Here is an example of how to create a list store:
|
|
</para>
|
|
|
|
<programlisting role="C">
|
|
GtkListStore *list_store;
|
|
|
|
list_store = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_UINT);
|
|
</programlisting>
|
|
|
|
<para>
|
|
This creates a new list store with two columns. Column 0 stores a string and column 1 stores an unsigned integer for each row.
|
|
At this point the model has no rows yet of course. Before we start to add rows, let's have a look at the different ways used
|
|
to refer to a particular row.
|
|
</para>
|
|
|
|
</sect1>
|
|
|
|
<!-- ************************************************************** -->
|
|
<sect1 id="sec-treemodel-rowref">
|
|
<title>Refering to Rows: GtkTreeIter, GtkTreePath, GtkTreeRowReference</title>
|
|
|
|
<para>
|
|
There are different ways to refer to a specific row. The two you will have to deal with are
|
|
<literal>GtkTreeIter</literal> and <literal>GtkTreePath</literal>.
|
|
</para>
|
|
|
|
<sect2 id="sec-treemodel-rowref-path">
|
|
<title>GtkTreePath</title>
|
|
<subtitle>Describing a row 'geographically'</subtitle>
|
|
|
|
<para>
|
|
A <literal>GtkTreePath</literal> is a comparatively straight-forward way to describe the
|
|
logical position of a row in the model. As a <literal>GtkTreeView</literal> always displays
|
|
<emphasis>all</emphasis> rows in a model, a tree path always describes the same row in both model and view.
|
|
</para>
|
|
|
|
<figure id="sec-treemodel-treepath-sshot">
|
|
<title>Tree Paths</title>
|
|
<screenshot>
|
|
<mediaobject>
|
|
<imageobject>
|
|
<imagedata fileref="images/treepath.png" format="PNG" />
|
|
</imageobject>
|
|
<imageobject>
|
|
<imagedata fileref="images/treepath.eps" format="EPS" />
|
|
</imageobject>
|
|
</mediaobject>
|
|
</screenshot>
|
|
</figure>
|
|
|
|
<para>
|
|
The picture shows the tree path in string form next to the label. Basically,
|
|
it just counts the children from the imaginary root of the tree view. An
|
|
empty tree path string would specify that imaginary invisible root. Now
|
|
'Songs' is the first child (from the root) and thus its tree path is just "0".
|
|
'Videos' is the second child from the root, and its tree path is "1". 'oggs'
|
|
is the second child of the first item from the root, so its tree path is "0:1".
|
|
So you just count your way down from the root to the row in question, and you
|
|
get your tree path.
|
|
</para>
|
|
|
|
<para>
|
|
To clarify this, a tree path of "3:9:4:1" would basically mean
|
|
<emphasis>in human language</emphasis> (attention - this is not what it really means!)
|
|
something along the lines of: go to the 3rd top-level row. Now go to the 9th child
|
|
of that row. Proceed to the 4th child of the previous row. Then continue to the
|
|
1st child of that. Now you are at the row this tree path describes.
|
|
This is not what it means for Gtk+ though. While humans start counting at 1, computers
|
|
usually start counting at 0. So the real meaning of the tree path "3:9:4:1" is: Go to the
|
|
4th top-level row. Then go to the 10th child of that row. Pick the 5th child of that row. Then
|
|
proceed to the 2nd child of the previous row. Now you are at the row this
|
|
tree path describes. :)
|
|
</para>
|
|
|
|
<para>
|
|
The implication of this way of refering to rows is as follows: if you insert or delete
|
|
rows in the middle or if the rows are resorted, a tree path might suddenly refer to a
|
|
completely different row than it refered to before the insertion/deletion/resorting. This
|
|
is important to keep in mind.
|
|
(See the <link linkend="sec-treemodel-rowref-rowref">section on
|
|
<literal>GtkTreeRowReference</literal>s below</link> for a tree path that keeps updating
|
|
itself to make sure it always refers to the same row when the model changes).
|
|
</para>
|
|
|
|
<para>
|
|
This effect becomes apparent if you imagine what would happen if we were to delete the
|
|
row entitled 'funny clips' from the tree in the above picture. The row 'movie trailers' would
|
|
suddenly be the first and only child of 'clips', and be described by the tree path that
|
|
formerly belonged to 'funny clips', ie. "1:0:0".
|
|
</para>
|
|
|
|
<para>
|
|
You can get a new <literal>GtkTreePath</literal> from a
|
|
path in string form using <literal>gtk_tree_path_new_from_string</literal>,
|
|
and you can convert a given <literal>GtkTreePath</literal> into its string
|
|
notation with <literal>gtk_tree_path_to_string</literal>. Usually you will
|
|
rarely have to handle the string notation, it is described here merely to
|
|
demonstrate the concept of tree paths.
|
|
</para>
|
|
|
|
<para>
|
|
Instead of the string notation, <literal>GtkTreePath</literal> uses an integer
|
|
array internally. You can get the depth (ie. the nesting level) of a tree path
|
|
with <ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeModel.html#gtk-tree-path-get-depth">
|
|
<literal>gtk_tree_path_get_depth</literal></ulink>. A depth of 0 is the imaginary
|
|
invisible root node of the tree view and model. A depth of 1 means that the
|
|
tree path describes a top-level row. As lists are just trees without child nodes,
|
|
all rows in a list always have tree paths of depth 1.
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeModel.html#gtk-tree-path-get-indices">
|
|
<literal>gtk_tree_path_get_indices</literal></ulink> returns the internal integer
|
|
array of a tree path. You will rarely need to operate with those either.
|
|
</para>
|
|
|
|
<para>
|
|
If you operate with tree paths, you are most likely to use a given tree path,
|
|
and use functions like
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeModel.html#gtk_tree_path_up">
|
|
<literal>gtk_tree_path_up</literal></ulink>,
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeModel.html#gtk-tree-path-down">
|
|
<literal>gtk_tree_path_down</literal></ulink>,
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeModel.html#gtk-tree-path-next">
|
|
<literal>gtk_tree_path_next</literal></ulink>,
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeModel.html#gtk-tree-path-prev">
|
|
<literal>gtk_tree_path_prev</literal></ulink>,
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeModel.html#gtk-tree-path-is-ancestor">
|
|
<literal>gtk_tree_path_is_ancestor</literal></ulink>, or
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeModel.html#gtk-tree-path-is-descendant">
|
|
<literal>gtk_tree_path_is_descendant</literal></ulink>.
|
|
Note that this way you can construct and operate on tree paths that refer to
|
|
rows that do not exist in model or view! The only way to check whether a
|
|
path is valid for a specific model (ie. the row described by the path exists)
|
|
is to convert the path into an iter using
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeModel.html#gtk-tree-model-get-iter">
|
|
<literal>gtk_tree_model_get_iter</literal></ulink>.
|
|
</para>
|
|
|
|
<para>
|
|
<literal>GtkTreePath</literal> is an opaque structure, with its details hidden from the compiler.
|
|
If you need to make a copy of a tree path, use
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeModel.html#gtk-tree-path-copy">
|
|
<literal>gtk_tree_path_copy</literal></ulink>.
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
|
|
<sect2 id="sec-treemodel-rowref-iter">
|
|
<title>GtkTreeIter</title>
|
|
<subtitle>Refering to a row in model-speak</subtitle>
|
|
<para>
|
|
Another way to refer to a row in a list or tree is <literal>GtkTreeIter</literal>. A tree iter is just a
|
|
structure that contains a couple of pointers that mean something to the model you are using. Tree iters are
|
|
used internally by models, and they often contain a direct pointer to the internal data of the row in question.
|
|
You should never look at the content of a tree iter and you must not modify it directly either.
|
|
</para>
|
|
|
|
<para>
|
|
All tree models (and therefore also <literal>GtkListStore</literal> and <literal>GtkTreeStore</literal>)
|
|
must support the <ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeModel.html">
|
|
<literal>GtkTreeModel</literal></ulink> functions that operate on tree iters (e.g. get
|
|
the tree iter for the first child of the row specified by a given tree iter, get the first row in the
|
|
list/tree, get the n-th child of a given iter etc.). Some of these functions are:
|
|
</para>
|
|
|
|
<itemizedlist>
|
|
<listitem>
|
|
<simpara>
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeModel.html#gtk-tree-model-get-iter-first">
|
|
<literal>gtk_tree_model_get_iter_first</literal></ulink> - sets the given iter to the first top-level item in the list or tree
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeModel.html#gtk-tree-model-iter-next">
|
|
<literal>gtk_tree_model_iter_next</literal></ulink> - sets the given iter to the next item at the current level in a list or tree.
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeModel.html#gtk-tree-model-iter-children">
|
|
<literal>gtk_tree_model_iter_children</literal></ulink> - sets the first given iter to the first child of the row referenced by
|
|
the second iter (not very useful for lists, mostly useful for trees).
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeModel.html#gtk-tree-model-iter-n-children">
|
|
<literal>gtk_tree_model_iter_n_children</literal></ulink> - returns the number of children the row referenced by the provided
|
|
iter has. If you pass <literal>NULL</literal> instead of a pointer to an iter structure, this function will return
|
|
the number of top-level rows. You can also use this function to count the number of items in a list store.
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeModel.html#gtk-tree-model-iter-nth-child">
|
|
<literal>gtk_tree_model_iter_nth_child</literal></ulink> - sets the first iter to the n-th child of the row referenced
|
|
by the second iter. If you pass <literal>NULL</literal> instead of a pointer to an iter structure as the second
|
|
iter, you can get the first iter set to the n-th row of a list.
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeModel.html#gtk-tree-model-iter-parent">
|
|
<literal>gtk_tree_model_iter_parent</literal></ulink> - sets the first iter to the parent of the row referenced by the
|
|
second iter (does nothing for lists, only useful for trees).
|
|
</simpara>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
<para>
|
|
Almost all of those functions return <literal>TRUE</literal> if the requested operation succeeded, and
|
|
return <literal>FALSE</literal> otherwise. There are more functions that operate on iters. Check out the
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeModel.html">
|
|
<literal>GtkTreeModel</literal> API reference</ulink> for details.
|
|
</para>
|
|
|
|
<para>
|
|
You might notice that there is no <literal>gtk_tree_model_iter_prev</literal>. This is unlikely to be
|
|
implemented for a variety of reasons. It should be fairly simple to write a helper function that provides
|
|
this functionality though once you have read this section.
|
|
</para>
|
|
|
|
<para>
|
|
Tree iters are used to retrieve data from the store, and to put data into the store. You also get a tree iter
|
|
as result if you add a new row to the store using <literal>gtk_list_store_append</literal> or
|
|
<literal>gtk_tree_store_append</literal>.
|
|
</para>
|
|
|
|
<para>
|
|
Tree iters are often only valid for a short time, and might become invalid if the store changes with some
|
|
models. It is therefore usually a bad idea to store tree iters, unless you really know what you are doing.
|
|
You can use <literal>gtk_tree_model_get_flags</literal> to get a model's flags, and check whether the
|
|
<literal>GTK_TREE_MODEL_ITERS_PERSIST</literal> flag is set (in which case a tree iter will be valid as
|
|
long as a row exists), yet still it is not advisable to store iter structures unless you really mean to
|
|
do that. There is a better way to keep track of a row over time: <literal>GtkTreeRowReference</literal>
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="sec-treemodel-rowref-rowref">
|
|
<title>GtkTreeRowReference</title>
|
|
<subtitle>Keeping track of rows even when the model changes</subtitle>
|
|
|
|
<para>
|
|
A <literal>GtkTreeRowReference</literal> is basically an object that takes a tree path,
|
|
and watches a model for changes. If anything changes, like rows getting inserted or
|
|
removed, or rows getting re-ordered, the tree row reference object will keep the given
|
|
tree path up to date, so that it always points to the same row as before. In case the
|
|
given row is removed, the tree row reference will become invalid.
|
|
</para>
|
|
|
|
<para>
|
|
A new tree row reference can be created with
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeModel.html#gtk-tree-row-reference-new">
|
|
<literal>gtk_tree_row_reference_new</literal></ulink>, given a model and a tree path. After
|
|
that, the tree row reference will keep updating the path whenever the model changes. The current
|
|
tree path of the row originally refered to when the tree row reference was created can be
|
|
retrieved with <ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeModel.html#gtk-tree-row-reference-get-path">
|
|
<literal>gtk_tree_row_reference_get_path</literal></ulink>. If the row has been deleted,
|
|
<literal>NULL</literal> will be returned instead of of a tree path. The tree path returned
|
|
is a <emphasis>copy</emphasis>, so it will need to be freed with
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeModel.html#gtk-tree-path-free">
|
|
<literal>gtk_tree_path_free</literal></ulink> when it is no longer needed.
|
|
</para>
|
|
|
|
<para>
|
|
You can check whether the row referenced still exists with
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeModel.html#gtk-tree-row-reference-valid">
|
|
<literal>gtk_tree_row_reference_valid</literal></ulink>, and free it with
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeModel.html#gtk-tree-row-reference-free">
|
|
<literal></literal></ulink> when no longer needed.
|
|
</para>
|
|
|
|
<para>
|
|
For the curious: internally, the tree row reference connects to the tree model's
|
|
<literal>"row-inserted"</literal>, <literal>"row-deleted"</literal>, and
|
|
<literal>"rows-reordered"</literal> signals and updates its internal tree path
|
|
whenever something happened to the model that affects the position of the
|
|
referenced row.
|
|
</para>
|
|
|
|
<para>
|
|
Note that using tree row references entails a small overhead. This is hardly significant
|
|
for 99.9% of all applications out there, but when you have multiple thousands of rows
|
|
and/or row references, this might be something to keep in mind (because whenever rows are
|
|
inserted, removed, or reordered, a signal will be sent out and processed for each
|
|
row reference).
|
|
</para>
|
|
|
|
<para>
|
|
If you have read the tutorial only up to here so far, it is hard to
|
|
explain really what tree row references are good for. An example
|
|
where tree row references come in handy can be found further below
|
|
in the <link linkend="sec-treemodel-remove-many-rows">section on removing multiple rows in one go</link>.
|
|
</para>
|
|
|
|
<para>
|
|
In practice, a programmer can either use tree row references to keep track of rows over time,
|
|
or store tree iters directly (if, and only if, the model has persistent iters). Both
|
|
<literal>GtkListStore</literal> and <literal>GtkTreeStore</literal> have persistent iters,
|
|
so storing iters is possible. However, using tree row references is definitively the
|
|
Right Way(tm) to do things, even though it comes with some overhead that might impact
|
|
performance in case of trees that have a very large number of rows (in that
|
|
case it might be preferable to write a custom model anyway though). Especially beginners
|
|
might find it easier to handle and store tree row references than iters, because tree
|
|
row references are handled by pointer value, which you can easily add to a <literal>GList</literal>
|
|
or pointer array, while it is easy to store tree iters in a wrong way.
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="sec-treemodel-rowref-usage">
|
|
<title>Usage</title>
|
|
|
|
<para>
|
|
Tree iters can easily be converted into tree paths using
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeModel.html#gtk-tree-model-get-path">
|
|
<literal>gtk_tree_model_get_path</literal></ulink>, and tree
|
|
paths can easily be converted into tree iters using
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeModel.html#gtk-tree-model-get-iter">
|
|
<literal>gtk_tree_model_get_iter</literal></ulink>.
|
|
Here is an example that shows how to get the iter from the tree path that
|
|
is passed to us from the tree view in the <literal>"row-activated"</literal>
|
|
signal callback. We need the iter here to retrieve data from the store
|
|
</para>
|
|
|
|
<programlisting role="C">
|
|
/************************************************************
|
|
* *
|
|
* Converting a GtkTreePath into a GtkTreeIter *
|
|
* *
|
|
************************************************************/
|
|
|
|
/************************************************************
|
|
*
|
|
* onTreeViewRowActivated: a row has been double-clicked
|
|
*
|
|
************************************************************/
|
|
|
|
void
|
|
onTreeViewRowActivated (GtkTreeView *view, GtkTreePath *path,
|
|
GtkTreeViewColumn *col, gpointer userdata)
|
|
{
|
|
GtkTreeIter iter;
|
|
GtkTreeModel *model;
|
|
|
|
model = gtk_tree_view_get_model(view);
|
|
|
|
if (gtk_tree_model_get_iter(model, &iter, path))
|
|
{
|
|
gchar *name;
|
|
|
|
gtk_tree_model_get(model, &iter, COL_NAME, &name, -1);
|
|
|
|
g_print ("The row containing the name '%s' has been double-clicked.\n", name);
|
|
|
|
g_free(name);
|
|
}
|
|
}
|
|
</programlisting>
|
|
|
|
|
|
<para>
|
|
Tree row references
|
|
reveal the current path of a row with <literal>gtk_tree_row_reference_get_path</literal>. There is no direct
|
|
way to get a tree iter from a tree row reference, you have to retrieve the tree row reference's path first and
|
|
then convert that into a tree iter.
|
|
</para>
|
|
|
|
<para>
|
|
As tree iters are only valid for a short time, they are usually allocated on the stack, as in
|
|
the following example (keep in mind that <literal>GtkTreeIter</literal> is just a structure
|
|
that contains data fields you do not need to know anything about):
|
|
</para>
|
|
|
|
<programlisting role="C">
|
|
/************************************************************
|
|
* *
|
|
* Going through every row in a list store *
|
|
* *
|
|
************************************************************/
|
|
|
|
void
|
|
traverse_list_store (GtkListStore *liststore)
|
|
{
|
|
GtkTreeIter iter;
|
|
gboolean valid;
|
|
|
|
g_return_if_fail ( liststore != NULL );
|
|
|
|
/* Get first row in list store */
|
|
valid = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(liststore), &iter);
|
|
|
|
while (valid)
|
|
{
|
|
/* ... do something with that row using the iter ... */
|
|
/* (Here column 0 of the list store is of type G_TYPE_STRING) */
|
|
gtk_list_store_set(liststore, &iter, 0, "Joe", -1);
|
|
|
|
/* Make iter point to the next row in the list store */
|
|
valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(liststore), &iter);
|
|
}
|
|
}
|
|
</programlisting>
|
|
|
|
<para>
|
|
The code above asks the model to fill the iter structure to make it point to the first
|
|
row in the list store. If there is a first row and the list store is not empty, the
|
|
iter will be set, and <ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeModel.html#gtk-tree-model-get-iter-first">
|
|
<literal>gtk_tree_model_get_iter_first</literal></ulink> will return
|
|
<literal>TRUE</literal>. If there is no first row, it will just return <literal>FALSE</literal>.
|
|
If a first row exists, the while loop will be entered and we change some of the first
|
|
row's data. Then we ask the model to make the given iter point to the next row, until there
|
|
are no more rows, which is when <ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeModel.html#gtk-tree-model-iter-next">
|
|
<literal>gtk_tree_model_iter_next</literal></ulink> returns <literal>FALSE</literal>.
|
|
Instead of traversing the list store we could also have used
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeModel.html#gtk-tree-model-foreach">
|
|
<literal>gtk_tree_model_foreach</literal></ulink>
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
</sect1>
|
|
|
|
<!-- ************************************************************** -->
|
|
<sect1 id="sec-treemodel-add-rows">
|
|
<title>Adding Rows to a Store</title>
|
|
|
|
<!-- ************************************************************** -->
|
|
<sect2 id="sec-liststore-add-rows">
|
|
<title>Adding Rows to a List Store</title>
|
|
|
|
<para>
|
|
Rows are added to a list store with <ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkListStore.html#gtk-list-store-append">
|
|
<literal>gtk_list_store_append</literal></ulink>.
|
|
This will insert a new empty row at the end of the list. There are other functions,
|
|
documented in the <ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkListStore.html">
|
|
GtkListStore API reference</ulink>, that give you more control
|
|
about where exactly the new row is inserted, but as they work very similar to
|
|
<literal>gtk_list_store_append</literal> and are fairly straight-forward to use,
|
|
we will not deal with them here.
|
|
</para>
|
|
|
|
<para>
|
|
Here is a simple example of how to create a list store and add some (empty) rows to it:
|
|
</para>
|
|
|
|
<programlisting role="C">
|
|
GtkListStore *liststore;
|
|
GtkTreeIter iter;
|
|
|
|
liststore = gtk_list_store_new(1, G_TYPE_STRING);
|
|
|
|
/* Append an empty row to the list store. Iter will point to the new row */
|
|
gtk_list_store_append(liststore, &iter);
|
|
|
|
/* Append an empty row to the list store. Iter will point to the new row */
|
|
gtk_list_store_append(liststore, &iter);
|
|
|
|
/* Append an empty row to the list store. Iter will point to the new row */
|
|
gtk_list_store_append(liststore, &iter);
|
|
</programlisting>
|
|
|
|
<para>
|
|
This in itself is not very useful yet of course. We will add data to the rows in the next section.
|
|
</para>
|
|
</sect2>
|
|
|
|
<!-- ************************************************************** -->
|
|
<sect2 id="sec-treestore-add-rows">
|
|
<title>Adding Rows to a Tree Store</title>
|
|
|
|
<para>
|
|
Adding rows to a tree store works similar to adding rows to a list store, only that
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeStore.html#gtk-tree-store-append">
|
|
<literal>gtk_tree_store_append</literal></ulink> is the function to use and one more
|
|
argument is required, namely the tree iter to the parent of the row to insert.
|
|
If you supply NULL instead of providing the tree iter of another row, a new top-level
|
|
row will be inserted. If you do provide a parent tree iter, the new empty row will be
|
|
inserted after any already existing children of the parent. Again, there are other ways
|
|
to insert a row into the tree store and they are documented in the
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeStore.html">
|
|
GtkTreeStore API reference manual</ulink>. Another short example:
|
|
</para>
|
|
|
|
<programlisting role="C">
|
|
GtkTreeStore *treestore;
|
|
GtkTreeIter iter, child;
|
|
|
|
treestore = gtk_tree_store_new(1, G_TYPE_STRING);
|
|
|
|
/* Append an empty top-level row to the tree store.
|
|
* Iter will point to the new row */
|
|
gtk_tree_store_append(treestore, &iter, NULL);
|
|
|
|
/* Append another empty top-level row to the tree store.
|
|
* Iter will point to the new row */
|
|
gtk_tree_store_append(treestore, &iter, NULL);
|
|
|
|
/* Append a child to the row we just added.
|
|
* Child will point to the new row */
|
|
gtk_tree_store_append(treestore, &child, &iter);
|
|
|
|
/* Get the first row, and add a child to it as well (could have been done
|
|
* right away earlier of course, this is just for demonstration purposes) */
|
|
if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(treestore), &iter))
|
|
{
|
|
/* Child will point to new row */
|
|
gtk_tree_store_append(treestore, &child, &iter);
|
|
}
|
|
else
|
|
{
|
|
g_error("Oops, we should have a first row in the tree store!\n");
|
|
}
|
|
</programlisting>
|
|
|
|
</sect2>
|
|
|
|
<!-- ************************************************************** -->
|
|
<sect2 id="sec-treestore-adding-many-rows">
|
|
<title>Speed Issues when Adding a Lot of Rows</title>
|
|
|
|
<para>
|
|
A common scenario is that a model needs to be filled with a lot of rows at some point, either at start-up,
|
|
or when some file is opened. An equally common scenario is that this takes an awfully long time even on powerful
|
|
machines once the model contains more than a couple of thousand rows, with an exponentially decreasing rate of
|
|
insertion. As already pointed out above, <link linkend="sec-custom-models">writing a custom model</link>
|
|
might be the best thing to do in this case. Nevertheless, there are some things you can do to work around
|
|
this problem and speed things up a bit even with the stock Gtk+ models:
|
|
</para>
|
|
|
|
<para>
|
|
Firstly, you should detach your list store or tree store from the tree view before doing your mass insertions, then
|
|
do your insertions, and only connect your store to the tree view again when you are done with your insertions. Like this:
|
|
</para>
|
|
|
|
<programlisting role="C">
|
|
...
|
|
|
|
model = gtk_tree_view_get_model(GTK_TREE_VIEW(view));
|
|
|
|
g_object_ref(model); /* Make sure the model stays with us after the tree view unrefs it */
|
|
|
|
gtk_tree_view_set_model(GTK_TREE_VIEW(view), NULL); /* Detach model from view */
|
|
|
|
... insert a couple of thousand rows ...
|
|
|
|
gtk_tree_view_set_model(GTK_TREE_VIEW(view), model); /* Re-attach model to view */
|
|
|
|
g_object_unref(model);
|
|
|
|
...
|
|
</programlisting>
|
|
|
|
<para>
|
|
Secondly, you should make sure that sorting is disabled while you are doing your mass insertions, otherwise your store
|
|
might be resorted after each and every single row insertion, which is going to be everything but fast.
|
|
</para>
|
|
|
|
<para>
|
|
Thirdly, you should not keep around a lot of tree row references if you have so many rows, because with each
|
|
insertion (or removal) every single tree row reference will check whether its path needs to be updated or not.
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
</sect1>
|
|
|
|
<!-- ************************************************************** -->
|
|
<sect1 id="sec-treemodel-data-manipulation">
|
|
<title>Manipulating Row Data</title>
|
|
|
|
<para>
|
|
Adding empty rows to a data store is not terribly exciting, so let's see how we can add or change data in the store.
|
|
</para>
|
|
|
|
<para>
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkListStore.html#gtk-list-store-set">
|
|
<literal>gtk_list_store_set</literal></ulink> and
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeStore.html#gtk-tree-store-set">
|
|
<literal>gtk_tree_store_set</literal></ulink> are used
|
|
to manipulate a given row's data. There is also <literal>gtk_list_store_set_value</literal>
|
|
and <literal>gtk_tree_store_set_value</literal>, but those should only be used by people
|
|
familiar with GLib's GValue system.
|
|
</para>
|
|
|
|
<para>
|
|
Both <literal>gtk_list_store_set</literal> and <literal>gtk_tree_store_set</literal> take
|
|
a variable number of arguments, and must be terminated with a -1 argument. The first two
|
|
arguments are a pointer to the model, and the iter pointing to the row whose data we want
|
|
to change. They are followed by a variable number of (column, data) argument pairs,
|
|
terminated by a -1. The column refers to the model column number and is usually an enum
|
|
value (to make the code more readable and to make changes easier). The data should be of
|
|
the same data type as the model column.
|
|
</para>
|
|
|
|
<para>
|
|
Here is an example where we create a store that stores two strings and one integer for each row:
|
|
</para>
|
|
|
|
<programlisting role="C">
|
|
enum
|
|
{
|
|
COL_FIRST_NAME = 0,
|
|
COL_LAST_NAME,
|
|
COL_YEAR_BORN,
|
|
NUM_COLS
|
|
};
|
|
|
|
GtkListStore *liststore;
|
|
GtkTreeIter iter;
|
|
|
|
liststore = gtk_list_store_new(NUM_COLS, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_UINT);
|
|
|
|
/* Append an empty row to the list store. Iter will point to the new row */
|
|
gtk_list_store_append(liststore, &iter);
|
|
|
|
/* Fill fields with some data */
|
|
gtk_list_store_set (liststore, &iter,
|
|
COL_FIRST_NAME, "Joe",
|
|
COL_LAST_NAME, "Average",
|
|
COL_YEAR_BORN, (guint) 1970,
|
|
-1);
|
|
</programlisting>
|
|
|
|
<para>
|
|
You do not need to worry about allocating and freeing memory for the data to store.
|
|
The model (or more precisely: the GLib/GObject GType and GValue system) will take care of that for you.
|
|
If you store a string, for example, the model will make a copy of the string and store that. If you then
|
|
set the field to a new string later on, the model will automatically free the old string and again make
|
|
a copy of the new string and store the copy. This applies to almost all types, be it
|
|
<literal>G_TYPE_STRING</literal> or <literal>GDK_TYPE_PIXBUF</literal>.
|
|
</para>
|
|
|
|
<para>
|
|
The exception to note is <literal>G_TYPE_POINTER</literal>. If you allocate a chunk of
|
|
data or a complex structure and store it in a <literal>G_TYPE_POINTER</literal> field,
|
|
only the pointer <emphasis>value</emphasis> is stored. The model does not know anything
|
|
about the size or content of the data your pointer refers to, so it could not even make
|
|
a copy if it wanted to, so you need to allocate and free the memory yourself in this case.
|
|
However, if you do not want to do that yourself and want the model to take care of your
|
|
custom data for you, then you need to register your own type and derive it from one of
|
|
the GLib fundamental types (usually <ulink url="http://developer.gnome.org/doc/API/2.0/gobject/gobject-Boxed-Types.html">G_TYPE_BOXED</ulink>).
|
|
See the GObject <ulink url="http://developer.gnome.org/doc/API/2.0/gobject/gobject-GType.html">GType reference manual</ulink>
|
|
for details. Making a copy of data involves memory allocation and other overhead of course,
|
|
so one should consider the performance implications of using a custom GLib type over a
|
|
G_TYPE_POINTER carefully before taking that approach. Again, a custom model might be the
|
|
better alternative, depending on the overall amount of data to be stored (and retrieved).
|
|
</para>
|
|
|
|
</sect1>
|
|
|
|
<!-- ************************************************************** -->
|
|
<sect1 id="sec-treemodel-data-retrieval">
|
|
<title>Retrieving Row Data</title>
|
|
|
|
<para>
|
|
Storing data is not very useful if it cannot be retrieved again. This is done using
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeModel.html#gtk-tree-model-get">
|
|
<literal>gtk_tree_model_get</literal></ulink>, which takes similar arguments as
|
|
<literal>gtk_list_store_set</literal> or <literal>gtk_tree_store_set</literal> do, only
|
|
that it takes (column, pointer) arguments. The pointer must point to a variable that is
|
|
of the same type as the data stored in that particular model column.
|
|
</para>
|
|
|
|
<para>
|
|
Here is the previous example extended to traverse the list store and print out the data stored.
|
|
As an extra, we use <literal>gtk_tree_model_foreach</literal> to traverse the store and retrieve
|
|
the row number from the <literal>GtkTreePath</literal> passed to us in the foreach callback function:
|
|
</para>
|
|
|
|
<programlisting role="C">
|
|
#include <gtk/gtk.h>
|
|
|
|
enum
|
|
{
|
|
COL_FIRST_NAME = 0,
|
|
COL_LAST_NAME,
|
|
COL_YEAR_BORN,
|
|
NUM_COLS
|
|
};
|
|
|
|
gboolean
|
|
foreach_func (GtkTreeModel *model,
|
|
GtkTreePath *path,
|
|
GtkTreeIter *iter,
|
|
gpointer user_data)
|
|
{
|
|
gchar *first_name, *last_name, *tree_path_str;
|
|
guint year_of_birth;
|
|
|
|
/* Note: here we use 'iter' and not '&iter', because we did not allocate
|
|
* the iter on the stack and are already getting the pointer to a tree iter */
|
|
|
|
gtk_tree_model_get (model, iter,
|
|
COL_FIRST_NAME, &first_name,
|
|
COL_LAST_NAME, &last_name,
|
|
COL_YEAR_BORN, &year_of_birth,
|
|
-1);
|
|
|
|
tree_path_str = gtk_tree_path_to_string(path);
|
|
|
|
g_print ("Row %s: %s %s, born %u\n", tree_path_str,
|
|
first_name, last_name, year_of_birth);
|
|
|
|
g_free(tree_path_str);
|
|
|
|
g_free(first_name); /* gtk_tree_model_get made copies of */
|
|
g_free(last_name); /* the strings for us when retrieving them */
|
|
|
|
return FALSE; /* do not stop walking the store, call us with next row */
|
|
}
|
|
|
|
void
|
|
create_and_fill_and_dump_store (void)
|
|
{
|
|
GtkListStore *liststore;
|
|
GtkTreeIter iter;
|
|
|
|
liststore = gtk_list_store_new(NUM_COLS, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_UINT);
|
|
|
|
/* Append an empty row to the list store. Iter will point to the new row */
|
|
gtk_list_store_append(liststore, &iter);
|
|
|
|
/* Fill fields with some data */
|
|
gtk_list_store_set (liststore, &iter,
|
|
COL_FIRST_NAME, "Joe",
|
|
COL_LAST_NAME, "Average",
|
|
COL_YEAR_BORN, (guint) 1970,
|
|
-1);
|
|
|
|
/* Append another row, and fill in some data */
|
|
gtk_list_store_append(liststore, &iter);
|
|
|
|
gtk_list_store_set (liststore, &iter,
|
|
COL_FIRST_NAME, "Jane",
|
|
COL_LAST_NAME, "Common",
|
|
COL_YEAR_BORN, (guint) 1967,
|
|
-1);
|
|
|
|
/* Append yet another row, and fill it */
|
|
gtk_list_store_append(liststore, &iter);
|
|
|
|
gtk_list_store_set (liststore, &iter,
|
|
COL_FIRST_NAME, "Yo",
|
|
COL_LAST_NAME, "Da",
|
|
COL_YEAR_BORN, (guint) 1873,
|
|
-1);
|
|
|
|
/* Now traverse the list */
|
|
|
|
gtk_tree_model_foreach(GTK_TREE_MODEL(liststore), foreach_func, NULL);
|
|
}
|
|
|
|
int
|
|
main (int argc, char **argv)
|
|
{
|
|
gtk_init(&argc, &argv);
|
|
|
|
create_and_fill_and_dump_store();
|
|
|
|
return 0;
|
|
}
|
|
|
|
</programlisting>
|
|
|
|
<para>
|
|
Note that when a new row is created, all fields of a row are set to a default NIL value
|
|
appropriate for the data type in question. A field of type <literal>G_TYPE_INT</literal>
|
|
will automatically contain the value 0 until it is set to a different value, and strings
|
|
and all kind of pointer types will be <literal>NULL</literal> until set to something else.
|
|
Those are valid contents for the model, and if you are not sure that row contents have
|
|
been set to something, you need to be prepared to handle <literal>NULL</literal> pointers
|
|
and the like in your code.
|
|
</para>
|
|
|
|
<para>
|
|
Run the above program with an additional empty row and look at the output to see
|
|
this in effect.
|
|
</para>
|
|
|
|
<sect2 id="sec-treemodel-retrieved-data-disposal">
|
|
<title>Freeing Retrieved Row Data</title>
|
|
|
|
<para>
|
|
Unless you are dealing with a model column of type <literal>G_TYPE_POINTER</literal>,
|
|
<literal>gtk_tree_model_get</literal> will always make <emphasis>copies</emphasis> of
|
|
the data retrieved.
|
|
</para>
|
|
|
|
<para>
|
|
In the case of strings, this means that you need to <literal>g_free</literal> the
|
|
string returned when you don't need it any longer, as in the example above.
|
|
</para>
|
|
|
|
<para>
|
|
If you retrieve a <literal>GObject</literal> such as a <literal>GdkPixbuf</literal>
|
|
from the store, <literal>gtk_tree_model_get</literal> will automatically add a
|
|
reference to it, so you need to call <literal>g_object_unref</literal> on the
|
|
retrieved object once you are done with it:
|
|
</para>
|
|
|
|
<programlisting role="C">
|
|
...
|
|
|
|
GdkPixbuf *pixbuf;
|
|
|
|
gtk_tree_model_get (model, &iter,
|
|
COL_PICTURE, &pixbuf,
|
|
NULL);
|
|
|
|
if (pixbuf != NULL)
|
|
{
|
|
do_something_with_pixbuf (pixbuf);
|
|
g_object_unref (pixbuf);
|
|
}
|
|
|
|
...
|
|
</programlisting>
|
|
|
|
<para>
|
|
Similarly, <literal>GBoxed</literal>-derived types retrieved from a model
|
|
need to be freed with <literal>g_boxed_free</literal> when done with them
|
|
(don't worry if you have never heard of <literal>GBoxed</literal>).
|
|
</para>
|
|
|
|
<para>
|
|
If the model column is of type <literal>G_TYPE_POINTER</literal>,
|
|
<literal>gtk_tree_model_get</literal> will simply copy the pointer
|
|
value, but not the data (even if if it wanted to, it couldn't copy
|
|
the data, because it would not know how to copy it or what to copy
|
|
exactly). If you store pointers to objects or strings in a pointer
|
|
column (which you should not do unless you really know what you are
|
|
doing and why you are doing it), you do not need to unref or free
|
|
the returned values as described above, because
|
|
<literal>gtk_tree_model_get</literal> would not know what kind of
|
|
data they are and therefore won't ref or copy them on retrieval.
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
</sect1>
|
|
|
|
<!-- ************************************************************** -->
|
|
<sect1 id="sec-treemodel-remove-row">
|
|
<title>Removing Rows</title>
|
|
|
|
<para>
|
|
Rows can easily be removed with <ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkListStore.html#gtk-list-store-remove">
|
|
<literal>gtk_list_store_remove</literal></ulink> and
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeStore.html#gtk-tree-store-remove">
|
|
<literal>gtk_tree_store_remove</literal></ulink>. The removed row will
|
|
automatically be removed from the tree view as well, and all data
|
|
stored will automatically be freed, with the exception of
|
|
<literal>G_TYPE_POINTER</literal> columns (see above).
|
|
</para>
|
|
|
|
<para>
|
|
Removing a single row is fairly straight forward: you need to get the iter that
|
|
identifies the row you want to remove, and then use one of the above functions.
|
|
Here is a simple example that removes a row when you double-click on it (bad from
|
|
a user interface point of view, but then it is just an example):
|
|
</para>
|
|
|
|
<programlisting role="C">
|
|
|
|
static void
|
|
onRowActivated (GtkTreeView *view,
|
|
GtkTreePath *path,
|
|
GtkTreeViewColumn *col,
|
|
gpointer user_data)
|
|
{
|
|
GtkTreeModel *model;
|
|
GtkTreeIter iter;
|
|
|
|
g_print ("Row has been double-clicked. Removing row.\n");
|
|
|
|
model = gtk_tree_view_get_model(view);
|
|
|
|
if (!gtk_tree_model_get_iter(model, &iter, path))
|
|
return; /* path describes a non-existing row - should not happen */
|
|
|
|
gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
|
|
}
|
|
|
|
|
|
void
|
|
create_treeview (void)
|
|
{
|
|
...
|
|
g_signal_connect(treeview, "row-activated", G_CALLBACK(onRowActivated), NULL);
|
|
...
|
|
}
|
|
|
|
</programlisting>
|
|
|
|
<para>
|
|
<emphasis>Note:</emphasis> <ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkListStore.html#gtk-list-store-remove">
|
|
<literal>gtk_list_store_remove</literal></ulink> and
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeStore.html#gtk-tree-store-remove">
|
|
<literal>gtk_tree_store_remove</literal></ulink> both have slightly different
|
|
semantics in Gtk+-2.0 and Gtk+-2.2 and later. In Gtk+-2.0, both functions do
|
|
not return a value, while in later Gtk+ versions those functions return either
|
|
<literal>TRUE</literal> or <literal>FALSE</literal> to indicate whether the
|
|
iter given has been set to the next valid row (or invalidated if there is no
|
|
next row). This is important to keep in mind when writing code that is supposed
|
|
to work with all Gtk+-2.x versions. In that case you should just ignore the
|
|
value returned (as in the call above) and check the iter with
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkListStore.html#gtk-list-store-iter-is-valid">
|
|
<literal>gtk_list_store_iter_is_valid</literal></ulink> if you need it.
|
|
</para>
|
|
|
|
<para>
|
|
If you want to remove the n-th row from a list (or the n-th child of a tree node),
|
|
you have two approaches: either you first create a <literal>GtkTreePath</literal>
|
|
that describes that row and then turn it into an iter and remove it; or you take
|
|
the iter of the parent node and use
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeModel.html#gtk-tree-model-iter-nth-child">
|
|
<literal>gtk_tree_model_iter_nth_child</literal></ulink> (which will also work
|
|
for list stores if you use <literal>NULL</literal> as the parent iter. Of course
|
|
you could also start with the iter of the first top-level row, and then step-by-step
|
|
move it to the row you want, although that seems a rather awkward way of doing it.
|
|
</para>
|
|
|
|
<para>
|
|
The following code snippet will remove the n-th row of a list if it exists:
|
|
</para>
|
|
|
|
|
|
<programlisting role="C">
|
|
|
|
/******************************************************************
|
|
*
|
|
* list_store_remove_nth_row
|
|
*
|
|
* Removes the nth row of a list store if it exists.
|
|
*
|
|
* Returns TRUE on success or FALSE if the row does not exist.
|
|
*
|
|
******************************************************************/
|
|
|
|
gboolean
|
|
list_store_remove_nth_row (GtkListStore *store, gint n)
|
|
{
|
|
GtkTreeIter iter;
|
|
|
|
g_return_val_if_fail (GTK_IS_LIST_STORE(store), FALSE);
|
|
|
|
/* NULL means the parent is the virtual root node, so the
|
|
* n-th top-level element is returned in iter, which is
|
|
* the n-th row in a list store (as a list store only has
|
|
* top-level elements, and no children) */
|
|
if (gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(store), &iter, NULL, n))
|
|
{
|
|
gtk_list_store_remove(store, &iter);
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
</programlisting>
|
|
|
|
</sect1>
|
|
|
|
<!-- ************************************************************** -->
|
|
<sect1 id="sec-treemodel-remove-many-rows">
|
|
<title>Removing Multiple Rows</title>
|
|
|
|
<para>
|
|
Removing multiple rows at once can be a bit tricky at times, and requires some thought on how to do this best. For
|
|
example, it is not possible to traverse a store with <literal>gtk_tree_model_foreach</literal>, check in the callback
|
|
function whether the given row should be removed and then just remove it by calling one of the stores' remove functions.
|
|
This will not work, because the model is changed from within the foreach loop, which might suddenly invalidate formerly
|
|
valid tree iters in the foreach function, and thus lead to unpredictable results.
|
|
</para>
|
|
|
|
<para>
|
|
You could traverse the store in a <literal>while</literal> loop of course, and call
|
|
<literal>gtk_list_store_remove</literal> or <literal>gtk_tree_store_remove</literal>
|
|
whenever you want to remove a row, and then just continue if the remove functions
|
|
returns <literal>TRUE</literal> (meaning that the iter is still valid and now points
|
|
to the row after the row that was removed). However, this approach will only work
|
|
with Gtk+-2.2 or later and will not work if you want your programs to compile
|
|
and work with Gtk+-2.0 as well, for the reasons outlined above (in Gtk+-2.0 the
|
|
remove functions did not set the passed iter to the next valid row). Also, while
|
|
this approach might be feasable for a list store, it gets a bit awkward for a
|
|
tree store.
|
|
</para>
|
|
|
|
<para>
|
|
Here is an example for an alternative approach to removing multiple rows
|
|
in one go (here we want to remove all rows from the store that contain
|
|
persons that have been born after 1980, but it could just as well be
|
|
all selected rows or some other criterion):
|
|
</para>
|
|
|
|
<programlisting role="C">
|
|
|
|
/******************************************************************
|
|
*
|
|
* Removing multiple rows in one go
|
|
*
|
|
******************************************************************/
|
|
|
|
...
|
|
|
|
gboolean
|
|
foreach_func (GtkTreeModel *model,
|
|
GtkTreePath *path,
|
|
GtkTreeIter *iter,
|
|
GList **rowref_list)
|
|
{
|
|
guint year_of_birth;
|
|
|
|
g_assert ( rowref_list != NULL );
|
|
|
|
gtk_tree_model_get (model, iter, COL_YEAR_BORN, &year_of_birth, -1);
|
|
|
|
if ( year_of_birth > 1980 )
|
|
{
|
|
GtkTreeRowReference *rowref;
|
|
|
|
rowref = gtk_tree_row_reference_new(model, path);
|
|
|
|
*rowref_list = g_list_append(*rowref_list, rowref);
|
|
}
|
|
|
|
return FALSE; /* do not stop walking the store, call us with next row */
|
|
}
|
|
|
|
void
|
|
remove_people_born_after_1980 (void)
|
|
{
|
|
GList *rr_list = NULL; /* list of GtkTreeRowReferences to remove */
|
|
GList *node;
|
|
|
|
gtk_tree_model_foreach(GTK_TREE_MODEL(store),
|
|
(GtkTreeModelForeachFunc) foreach_func,
|
|
&rr_list);
|
|
|
|
for ( node = rr_list; node != NULL; node = node->next )
|
|
{
|
|
GtkTreePath *path;
|
|
|
|
path = gtk_tree_row_reference_get_path((GtkTreeRowReference*)node->data);
|
|
|
|
if (path)
|
|
{
|
|
GtkTreeIter iter;
|
|
|
|
if (gtk_tree_model_get_iter(GTK_TREE_MODEL(store), &iter, path))
|
|
{
|
|
gtk_list_store_remove(store, &iter);
|
|
}
|
|
|
|
/* FIXME/CHECK: Do we need to free the path here? */
|
|
}
|
|
}
|
|
|
|
g_list_foreach(rr_list, (GFunc) gtk_tree_row_reference_free, NULL);
|
|
g_list_free(rr_list);
|
|
}
|
|
|
|
...
|
|
|
|
</programlisting>
|
|
|
|
<para>
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkListStore.html#gtk-list-store-clear">
|
|
<literal>gtk_list_store_clear</literal></ulink> and <ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeStore.html#gtk-tree-store-clear">
|
|
<literal>gtk_tree_store_clear</literal></ulink> come in handy if you want to remove all rows.
|
|
</para>
|
|
|
|
</sect1>
|
|
|
|
<!-- ************************************************************** -->
|
|
<sect1 id="sec-treemodel-storing-gobjects">
|
|
<title>Storing GObjects (Pixbufs etc.)</title>
|
|
|
|
<para>
|
|
A special case are <literal>GObject</literal> types, like <literal>GDK_TYPE_PIXBUF</literal>,
|
|
that get stored in a list or tree store. The store will not make a copy of the object, rather
|
|
it will increase the object's refcount. The store will then unref the object again if it is
|
|
no longer needed (ie. a new object is stored in the old object's place, the current value
|
|
is replaced by NULL, the row is removed, or the store is destroyed).
|
|
</para>
|
|
<para>
|
|
From a developer perspective, this means that you need to <literal>g_object_unref</literal>
|
|
an object that you have just added to the store if you want the store to automatically dispose
|
|
of it when no longer needed. This is because on object creation, the object has an initial
|
|
refcount of 1, which is "your" refcount, and the object will only be destroyed when it
|
|
reaches a refcount of 0. Here is the life cycle of a pixbuf:
|
|
</para>
|
|
|
|
<programlisting role="C">
|
|
GtkListStore *list_store;
|
|
GtkTreeIter iter;
|
|
GdkPixbuf *pixbuf;
|
|
GError *error = NULL;
|
|
|
|
list_store = gtk_list_store_new (2, GDK_TYPE_PIXBUF, G_TYPE_STRING);
|
|
|
|
pixbuf = gdk_pixbuf_new_from_file("icon.png", &error);
|
|
|
|
/* pixbuf has a refcount of 1 after creation */
|
|
|
|
if (error)
|
|
{
|
|
g_critical ("Could not load pixbuf: %s\n", error->message);
|
|
g_error_free(error);
|
|
return;
|
|
}
|
|
|
|
gtk_list_store_append(list_store, &iter);
|
|
|
|
gtk_list_store_set(list_store, &iter, 0, pixbuf, 1, "foo", -1);
|
|
|
|
/* pixbuf has a refcount of 2 now, as the list store has added its own reference */
|
|
|
|
g_object_unref(pixbuf);
|
|
|
|
/* pixbuf has a refcount of 1 now that we have released our initial reference */
|
|
|
|
/* we don't want an icon in that row any longer */
|
|
gtk_list_store_set(list_store, &iter, 0, NULL, -1);
|
|
|
|
/* pixbuf has automatically been destroyed after its refcount has reached 0.
|
|
* The list store called g_object_unref() on the pixbuf when it replaced
|
|
* the object in the store with a new value (NULL). */
|
|
</programlisting>
|
|
|
|
<para>
|
|
Having learned how to add, manipulate, and retrieve data from a store, the next step is to get that data
|
|
displayed in a <literal>GtkTreeView</literal> widget.
|
|
</para>
|
|
|
|
</sect1>
|
|
|
|
<!-- ************************************************************** -->
|
|
<sect1 id="sec-treemodel-storing-structs">
|
|
<title>Storing Data Structures: of Pointers, GBoxed Types, and GObject (TODO)</title>
|
|
|
|
<para>
|
|
Unfinished chapter.
|
|
</para>
|
|
|
|
<!--
|
|
<para>
|
|
<emphasis>
|
|
This section is for advanced users. You might want to skip it
|
|
for now and come back to it later when you find yourself
|
|
needing to store C-type custom data structures of your own
|
|
in a tree model.
|
|
</emphasis>
|
|
</para>
|
|
|
|
<para>
|
|
<emphasis>
|
|
This section is also not really very refined and only a rough draft.
|
|
Its purpose is just to introduce some approaches that might not be
|
|
immediately obvious for someone not familiar with the API.
|
|
</emphasis>
|
|
</para>
|
|
|
|
<para>
|
|
If the data you want to display becomes more complex (or simply
|
|
just a lot), storing it into a list store or tree store using
|
|
a trizillion model columns is not always the most convenient
|
|
way. It quickly leads to bulky code and unnecessary overhead.
|
|
</para>
|
|
|
|
<para>
|
|
Also, you often have an object or item of data where you
|
|
only want to display <emphasis>some</emphasis> of the data
|
|
in the tree view and have a fair bit of data you need to keep
|
|
track of in addition. Storing all the data in the model is
|
|
inconvenient, and keeping two copies of the data around and
|
|
syncing the data in the model with the data in the structure
|
|
is also inconvenient, even more so if the data changes
|
|
regularly.
|
|
</para>
|
|
|
|
<para>
|
|
Consider a tree view that displays the download status of
|
|
various files, e.g. a web browser's download window. You
|
|
might want to display the following data for each download:
|
|
<itemizedlist>
|
|
<listitem><para>
|
|
Destination File Name or Path
|
|
</para></listitem>
|
|
<listitem><para>
|
|
Source URL
|
|
</para></listitem>
|
|
<listitem><para>
|
|
Size of Download
|
|
</para></listitem>
|
|
<listitem><para>
|
|
Download Status (Completed, Paused, Queued, Downloading, etc.)
|
|
</para></listitem>
|
|
<listitem><para>
|
|
Download Speed (if applicable)
|
|
</para></listitem>
|
|
<listitem><para>
|
|
Estimated Time Left
|
|
</para></listitem>
|
|
</itemizedlist>
|
|
</para>
|
|
|
|
<para>
|
|
An obvious solution is to keep track of a download in a structure,
|
|
something along the lines of
|
|
</para>
|
|
|
|
<programlisting role="C">
|
|
typedef struct _MyDownload MyDownload;
|
|
struct _MyDownload
|
|
{
|
|
gchar *dest_fn;
|
|
gchar *src_url;
|
|
guint status;
|
|
guint64 size;
|
|
guint64 transfered;
|
|
gfloat speed;
|
|
guint seconds_left;
|
|
|
|
... more variables for the download action itself ..
|
|
};
|
|
</programlisting>
|
|
|
|
<para>
|
|
Then you could have list store with a column of <literal>G_TYPE_POINTER</literal>
|
|
and just store a pointer to the structure there for each row. Instead of connecting
|
|
model columns to cell renderer properties via attributes (see below), you will have
|
|
to use cell data functions with this approach.
|
|
</para>
|
|
|
|
<para>
|
|
The main issue with storing struct pointers in a list store is that memory management
|
|
becomes a bit awkward. You have to make sure that you remove the pointer data and/or
|
|
whole row from the store when you free the structure. Equally, you need to free the
|
|
structure 'manually' when you remove the row and don't need it any longer. Nothing
|
|
that cannot be solved, yet still not exactly elegant either.
|
|
</para>
|
|
|
|
<para>
|
|
The advantage is of course that code that retrieves data from a row (e.g. a selected row)
|
|
becomes much cleaner, and that there is less overhead when retrieving data (like string
|
|
copies). Compare
|
|
</para>
|
|
|
|
<programlisting role="C">
|
|
void
|
|
treeview_on_row_activated (GtkTreeView *treeview,
|
|
GtkTreePath *path,
|
|
GtkTreeViewColumn *col,
|
|
gpointer data)
|
|
{
|
|
GtkTreeModel *model;
|
|
GtkTreeIter iter;
|
|
|
|
model = gtk_tree_view_get_model (treeview);
|
|
|
|
if (gtk_tree_model_get_iter (model, &iter, path))
|
|
{
|
|
gchar *fn, *url;
|
|
guint status;
|
|
|
|
gtk_tree_model_get (model, &iter,
|
|
COL_FILENAME, &fn,
|
|
COL_URL, &url,
|
|
COL_STATUS, &status,
|
|
-1);
|
|
|
|
... do something with data on row double-click ....
|
|
|
|
g_free (fn);
|
|
g_free (url);
|
|
}
|
|
}
|
|
</programlisting>
|
|
|
|
<para>
|
|
with
|
|
</para>
|
|
|
|
<programlisting role="C">
|
|
void
|
|
treeview_on_row_activated (GtkTreeView *treeview,
|
|
GtkTreePath *path,
|
|
GtkTreeViewColumn *col,
|
|
gpointer data)
|
|
{
|
|
GtkTreeModel *model;
|
|
GtkTreeIter iter;
|
|
|
|
model = gtk_tree_view_get_model (treeview);
|
|
|
|
if (gtk_tree_model_get_iter (model, &iter, path))
|
|
{
|
|
MyDownload *dl;
|
|
|
|
gtk_tree_model_get (model, &iter, 0, &dl, -1);
|
|
|
|
... do something with data on row double-click ....
|
|
}
|
|
}
|
|
</programlisting>
|
|
|
|
<para>
|
|
Now, It would be nice if one could use memory management
|
|
techniques like reference counting with one's custom
|
|
structures, and have the tree models make use of them
|
|
automatically, just like the tree models take care of
|
|
memory management for strings or for GObjects like GdkPixbuf.
|
|
</para>
|
|
|
|
<para>
|
|
The obvious way would be to derive MyDownload from GObject,
|
|
in which case it will be handled automatically by the models.
|
|
This requires a bit of a dive into the GObject system though
|
|
and makes things more complicated than they have to be.
|
|
</para>
|
|
|
|
<para>
|
|
The alternative is to register your own type as a GBoxed-derived
|
|
type with the GType system, and implement some simple refcounting
|
|
yourself. Some code snippets:
|
|
</para>
|
|
|
|
|
|
<programlisting role="C">
|
|
#define MY_TYPE_DOWNLOAD (my_download_get_type())
|
|
|
|
typedef struct _MyDownload MyDownload;
|
|
struct _MyDownload
|
|
{
|
|
gchar *dest_fn;
|
|
gchar *src_url;
|
|
guint status;
|
|
|
|
... more ...
|
|
|
|
gint refcount;
|
|
};
|
|
|
|
void
|
|
my_download_ref (MyDownload *dl)
|
|
{
|
|
g_return_if_fail (dl->refcount > 0);
|
|
dl->refcount += 1;
|
|
}
|
|
|
|
void
|
|
my_download_unref (MyDownload *dl)
|
|
{
|
|
g_return_if_fail (dl->refcount > 0);
|
|
dl->refcount -= 1;
|
|
|
|
if (dl->refcount == 0)
|
|
{
|
|
/* free structure content */
|
|
g_free (dl->dest_fn);
|
|
g_free (dl->src_url);
|
|
|
|
/* poison memory */
|
|
memset (dl, 0xFF, sizeof (MyDownload));
|
|
|
|
/* free structure itself */
|
|
g_free (dl);
|
|
}
|
|
}
|
|
|
|
static gpointer
|
|
my_download_copy_func (gpointer boxed)
|
|
{
|
|
my_download_ref ((MyDownload*) boxed);
|
|
|
|
return boxed;
|
|
}
|
|
|
|
static void
|
|
my_download_free_func (gpointer boxed)
|
|
{
|
|
/* for clarity */
|
|
my_download_unref ((MyDownload*) boxed);
|
|
}
|
|
|
|
GType
|
|
my_download_get_type (void)
|
|
{
|
|
static GType my_type = 0;
|
|
if (my_type == 0)
|
|
{
|
|
my_type = g_boxed_type_register_static ("MyDownload",
|
|
my_download_copy_func,
|
|
my_download_free_func);
|
|
}
|
|
return my_type;
|
|
}
|
|
|
|
MyDownload *
|
|
make_new_download (const gchar *from, const gchar *to)
|
|
{
|
|
MyDownload *dl;
|
|
|
|
dl = g_new0 (MyDownload, 1);
|
|
|
|
dl->src_url = g_strdup (from);
|
|
dl->dest_fn = g_strdup (to);
|
|
|
|
dl->status = 0;
|
|
dl->refcount = 1;
|
|
|
|
return dl;
|
|
}
|
|
</programlisting>
|
|
|
|
-->
|
|
|
|
</sect1>
|
|
|
|
</chapter>
|
|
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
|
|
<chapter id="sec-treeview">
|
|
<title>Creating a Tree View</title>
|
|
|
|
<para>
|
|
In order to display data in a tree view widget, we need to create one
|
|
first, and we need to instruct it where to get the data to display
|
|
from.
|
|
</para>
|
|
|
|
<para>
|
|
A new tree view is created with:
|
|
</para>
|
|
|
|
<programlisting role="C">
|
|
GtkWidget *view;
|
|
|
|
view = gtk_tree_view_new();
|
|
</programlisting>
|
|
|
|
<!-- ************************************************************** -->
|
|
<sect1 id="sec-treeview-connect-model">
|
|
<title>Connecting Tree View and Model</title>
|
|
|
|
<para>
|
|
Before we proceed to the next section where we display data on the screen,
|
|
we need connect our data store to the tree view, so it knows where to
|
|
get the data to display from. This is achieved with
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeView.html#gtk-tree-view-set-model">
|
|
<literal>gtk_tree_view_set_model</literal></ulink>, which will by itself
|
|
do very little. However, it is a prerequisite for what we do in the
|
|
following sections.
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeView.html#gtk-tree-view-new-with-model">
|
|
<literal>gtk_tree_view_new_with_model</literal></ulink> is a convenience
|
|
function for the previous two.
|
|
</para>
|
|
|
|
<para>
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeView.html#gtk-tree-view-get-model">
|
|
<literal>gtk_tree_view_get_model</literal></ulink> will return the model that
|
|
is currently attached to a given tree view, which is particularly useful in
|
|
callbacks where you only get passed the tree view widget (after all, we do not
|
|
want to go down the road of global variables, which will inevitably lead to
|
|
the Dark Side, do we?).
|
|
</para>
|
|
|
|
<!-- ************************************************************** -->
|
|
<sect2 id="sec-treeview-connect-model-refcounting">
|
|
<title>Reference counting</title>
|
|
|
|
<para>
|
|
Tree models like <literal>GtkListStore</literal> and <literal>GtkTreeStore</literal>
|
|
are <literal>GObjects</literal> and have a reference count of 1 after creation. The
|
|
tree view will add its own reference to the model when you add the model with
|
|
<literal>gtk_tree_view_set_model</literal>, and will unref it again when you replace
|
|
the model with another model, unset the model by passing <literal>NULL</literal> as
|
|
a model, or when the tree view is destroyed.
|
|
|
|
<footnote><para>
|
|
'Reference counting' means that an object has a counter that can be
|
|
increased or decreased (ref-ed and unref-ed). If the counter is unref-ed to 0, the
|
|
object is automatically destroyed. This is useful, because other objects or
|
|
application programmers only have to think about whether <emphasis>they themselves</emphasis>
|
|
are still using that object or not, without knowing anything about others also using
|
|
it. The object is simply automatically destroyed when no one is using it any more.
|
|
</para></footnote>
|
|
|
|
</para>
|
|
|
|
<para>
|
|
This means that you need to take care of "your" reference yourself, otherwise the
|
|
model will not be destroyed properly when you disconnect it from the tree view, and
|
|
its memory will not be freed (which does not matter much if the same model is
|
|
connected to the tree view from application start to end). If you plan to use
|
|
the same model for a tree view for the whole duration of the application, you can
|
|
get rid of "your" reference right after you have connected the model to the
|
|
view - then the model will be destroyed automatically when the tree view is
|
|
destroyed (which will be automatically destroyed when the window it is in is
|
|
destroyed):
|
|
</para>
|
|
|
|
<programlisting role="C">
|
|
GtkListStore *liststore;
|
|
GtkWidget *view;
|
|
|
|
view = gtk_tree_view_new();
|
|
|
|
liststore = gtk_list_store_new(1, G_TYPE_STRING);
|
|
|
|
gtk_tree_view_set_model(GTK_TREE_VIEW(view), GTK_TREE_MODEL(liststore));
|
|
|
|
g_object_unref(liststore);
|
|
|
|
/* Now the model will be destroyed when the tree view is destroyed */
|
|
</programlisting>
|
|
|
|
</sect2>
|
|
|
|
</sect1>
|
|
|
|
<!-- ************************************************************** -->
|
|
<sect1 id="sec-treeview-look">
|
|
<title>Tree View Look and Feel</title>
|
|
|
|
<para>
|
|
There are a couple of ways to influence the look and feel of the tree
|
|
view. You can hide or show column headers with
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeView.html#gtk-tree-view-set-headers-visible">
|
|
<literal>gtk_tree_view_set_headers_visible</literal></ulink>, and set
|
|
them clickable or not with
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeView.html#gtk-tree-view-set-headers-clickable">
|
|
<literal>gtk_tree_view_set_headers_clickable</literal></ulink> (which
|
|
will be done automatically for you if you enable sorting).
|
|
</para>
|
|
|
|
<para>
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeView.html#gtk-tree-view-set-rules-hint">
|
|
<literal>gtk_tree_view_set_rules_hint</literal></ulink> will enable or disable
|
|
rules in the tree view.
|
|
|
|
<footnote>
|
|
<para>
|
|
'Rules' means that every second line of the tree view has a
|
|
shaded background, which makes it easier to see which cell
|
|
belongs to which row in tree views that have a lot of columns.
|
|
</para>
|
|
</footnote>
|
|
|
|
As the function name implies, this setting is only a
|
|
hint; in the end it depends on the active Gtk+ theme engine if the tree view
|
|
shows ruled lines or not. Users seem to have strong feelings about rules
|
|
in tree views, so it is probably a good idea to provide an option somewhere
|
|
to disable rule hinting if you set it on tree views (but then, people also
|
|
seem to have strong feelings about options abundance and 'sensible' default
|
|
options, so whatever you do will probably upset someone at some point).
|
|
</para>
|
|
|
|
<para>
|
|
The expander column can be set with <ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeView.html#gtk-tree-view-set-expander-column">
|
|
<literal>gtk_tree_view_set_expander_column</literal></ulink>. This is the column where
|
|
child elements are indented with respect to their parents, and where rows with children
|
|
have an 'expander' arrow with which a node's children can be collapsed (hidden) or
|
|
expanded (shown). By default, this is the first column.
|
|
</para>
|
|
|
|
</sect1>
|
|
|
|
</chapter>
|
|
|
|
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
|
|
<chapter id="sec-treeviewcol-renderer">
|
|
<title>Mapping Data to the Screen: GtkTreeViewColumn and GtkCellRenderer</title>
|
|
|
|
<para>
|
|
As outlined above, tree view columns represent the visible columns on the
|
|
screen that have a column header with a column name and can be resized or
|
|
sorted. A tree view is made up of tree view columns, and you need at least
|
|
one tree view column in order to display something in the tree view. Tree
|
|
view columns, however, do not display anything by themselves, this is done
|
|
by specialised <literal>GtkCellRenderer</literal> objects. Cell renderers
|
|
are packed into tree view columns much like widgets are packed into
|
|
<literal>GtkHBoxes</literal>.
|
|
</para>
|
|
|
|
<para>
|
|
Here is a diagram (courtesy of Owen Taylor) that pictures the relationship
|
|
between tree view columns and cell renderers:
|
|
</para>
|
|
|
|
<figure id="sec-treeviewcol-renderer-diagram">
|
|
<title>Cell Renderer Properties</title>
|
|
<screenshot>
|
|
<mediaobject>
|
|
<imageobject>
|
|
<imagedata fileref="images/tree-view-column.png" format="PNG" />
|
|
</imageobject>
|
|
<imageobject>
|
|
<imagedata fileref="images/tree-view-column.eps" format="EPS" />
|
|
</imageobject>
|
|
</mediaobject>
|
|
</screenshot>
|
|
</figure>
|
|
|
|
<para>
|
|
In the above diagram, both 'Country' and 'Representative' are tree view columns,
|
|
where the 'Country' and 'Representative' labels are the column headers.
|
|
The 'Country' column contains two cell renderers, one to display the flag icons,
|
|
and one to display the country name. The 'Representative' column only contains
|
|
one cell renderer to display the representative's name.
|
|
</para>
|
|
|
|
|
|
<!-- ************************************************************** -->
|
|
<sect1 id="sec-renderer">
|
|
<title>Cell Renderers</title>
|
|
|
|
<para>
|
|
Cell renderers are objects that are responsible for the actual rendering
|
|
of data within a <literal>GtkTreeViewColumn</literal>. They are basically
|
|
just GObjects (ie. not widgets) that have certain properties, and those
|
|
properties determine how a single cell is drawn.
|
|
</para>
|
|
|
|
<para>
|
|
In order to draw cells in different rows with different content,
|
|
a cell renderer's properties need to be set accordingly for each
|
|
single row/cell to render. This is done either via
|
|
<link linkend="sec-treeview-col-attributes">attributes</link> or
|
|
<link linkend="sec-treeview-col-celldatafunc">cell data functions</link>
|
|
(see below). If you set up attributes,
|
|
you tell Gtk which model column contains the data from which a
|
|
property should be set before rendering a certain row. Then the
|
|
properties of a cell renderer are set automatically according to
|
|
the data in the model before each row is rendered. Alternatively,
|
|
you can set up cell data functions, which are called for each
|
|
row to be rendererd, so that you can manually set the properties
|
|
of the cell renderer before it is rendered. Both approaches can
|
|
be used at the same time as well. Lastly, you can set a cell
|
|
renderer property when you create the cell renderer. That way it
|
|
will be used for all rows/cells to be rendered (unless it is
|
|
changed later of course).
|
|
</para>
|
|
|
|
<para>
|
|
Different cell renderers exist for different purposes:
|
|
</para>
|
|
|
|
<itemizedlist>
|
|
<listitem>
|
|
<simpara>
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkCellRendererText.html">
|
|
<literal>GtkCellRendererText</literal></ulink> renders strings or numbers or
|
|
boolean values as text ("Joe", "99.32", "true")
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkCellRendererPixbuf.html">
|
|
<literal>GtkCellRendererPixbuf</literal></ulink> is used to display images;
|
|
either user-defined images, or one of the stock icons that come with Gtk+.
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkCellRendererToggle.html">
|
|
<literal>GtkCellRendererToggle</literal></ulink> displays a boolean value in
|
|
form of a check box or as a radio button.
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkCellEditable.html">
|
|
<literal>GtkCellEditable</literal></ulink> is a special cell that
|
|
implements editable cells (ie. GtkEntry or GtkSpinbutton in a treeview).
|
|
This is not a cell renderer! If you want to have editable text cells, use
|
|
<literal>GtkCellRendererText</literal> and make sure the "editable" property
|
|
is set. <literal>GtkCellEditable</literal> is only used by implementations
|
|
of editable cells and widgets that can be inside of editable cells.
|
|
You are unlikely to ever need it.
|
|
</simpara>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
<para>
|
|
Contrary to what one may think, a cell renderer does not render just one
|
|
single cell, but is responsible for rendering part or whole of a tree
|
|
view column for each single row. It basically starts in the first row
|
|
and renders its part of the column there. Then it proceeds to the next
|
|
row and renders its part of the column there again. And so on.
|
|
</para>
|
|
|
|
<para>
|
|
How does a cell renderer know what to render? A cell renderer object
|
|
has certain 'properties' that are documented in the
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/TreeWidgetObjects.html">
|
|
API reference</ulink> (just like most other objects, and widgets).
|
|
These properties determine what the cell renderer is going to
|
|
render and how it is going to be rendered. Whenever the cell
|
|
renderer is called upon to render a certain cell, it looks at
|
|
its properties and renders the cell accordingly. This means that
|
|
whenever you set a property or change a property of the cell
|
|
renderer, this will affect all rows that are rendered after the
|
|
change, until you change the property again.
|
|
</para>
|
|
|
|
<para>
|
|
Here is a diagram (courtesy of Owen Taylor) that tries to show what is going
|
|
on when rows are rendered:
|
|
</para>
|
|
|
|
<figure id="sec-treeviewcol-diagram">
|
|
<title>GtkTreeViewColumns and GtkCellRenderers</title>
|
|
<screenshot>
|
|
<mediaobject>
|
|
<imageobject>
|
|
<imagedata fileref="images/cell-renderer-properties.png" format="PNG" />
|
|
</imageobject>
|
|
<imageobject>
|
|
<imagedata fileref="images/cell-renderer-properties.eps" format="EPS" />
|
|
</imageobject>
|
|
</mediaobject>
|
|
</screenshot>
|
|
</figure>
|
|
|
|
<para>
|
|
The above diagram shows the process when attributes are used. In the example, a
|
|
text cell renderer's <literal>"text"</literal> property has been linked to the
|
|
first model column. The <literal>"text"</literal> property contains the string
|
|
to be rendered. The <literal>"foreground"</literal> property, which contains the
|
|
colour of the text to be shown, has been linked to the second model column. Finally,
|
|
the <literal>"strikethrough"</literal> property, which determines whether the text
|
|
should be with a horizontal line that strikes through the text, has been connected to
|
|
the third model column (of type <literal>G_TYPE_BOOLEAN</literal>).
|
|
</para>
|
|
|
|
<para>
|
|
With this setup, the cell renderer's properties are 'loaded' from the model before
|
|
each cell is rendered.
|
|
</para>
|
|
|
|
<para>
|
|
Here is a silly and utterly useless little example that demonstrates
|
|
this behaviour, and introduces some of the most commonly used
|
|
properties of <literal>GtkCellRendererText</literal>:
|
|
</para>
|
|
|
|
<programlisting role="C">
|
|
#include <gtk/gtk.h>
|
|
|
|
enum
|
|
{
|
|
COL_FIRST_NAME = 0,
|
|
COL_LAST_NAME,
|
|
NUM_COLS
|
|
} ;
|
|
|
|
static GtkTreeModel *
|
|
create_and_fill_model (void)
|
|
{
|
|
GtkTreeStore *treestore;
|
|
GtkTreeIter toplevel, child;
|
|
|
|
treestore = gtk_tree_store_new(NUM_COLS, G_TYPE_STRING, G_TYPE_STRING);
|
|
|
|
/* Append a top level row and leave it empty */
|
|
gtk_tree_store_append(treestore, &toplevel, NULL);
|
|
|
|
/* Append a second top level row, and fill it with some data */
|
|
gtk_tree_store_append(treestore, &toplevel, NULL);
|
|
gtk_tree_store_set(treestore, &toplevel,
|
|
COL_FIRST_NAME, "Joe",
|
|
COL_LAST_NAME, "Average",
|
|
-1);
|
|
|
|
/* Append a child to the second top level row, and fill in some data */
|
|
gtk_tree_store_append(treestore, &child, &toplevel);
|
|
gtk_tree_store_set(treestore, &child,
|
|
COL_FIRST_NAME, "Jane",
|
|
COL_LAST_NAME, "Average",
|
|
-1);
|
|
|
|
return GTK_TREE_MODEL(treestore);
|
|
}
|
|
|
|
static GtkWidget *
|
|
create_view_and_model (void)
|
|
{
|
|
GtkTreeViewColumn *col;
|
|
GtkCellRenderer *renderer;
|
|
GtkWidget *view;
|
|
GtkTreeModel *model;
|
|
|
|
view = gtk_tree_view_new();
|
|
|
|
/* --- Column #1 --- */
|
|
|
|
col = gtk_tree_view_column_new();
|
|
|
|
gtk_tree_view_column_set_title(col, "First Name");
|
|
|
|
/* pack tree view column into tree view */
|
|
gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);
|
|
|
|
renderer = gtk_cell_renderer_text_new();
|
|
|
|
/* pack cell renderer into tree view column */
|
|
gtk_tree_view_column_pack_start(col, renderer, TRUE);
|
|
|
|
/* set 'text' property of the cell renderer */
|
|
g_object_set(renderer, "text", "Boooo!", NULL);
|
|
|
|
|
|
/* --- Column #2 --- */
|
|
|
|
col = gtk_tree_view_column_new();
|
|
|
|
gtk_tree_view_column_set_title(col, "Last Name");
|
|
|
|
/* pack tree view column into tree view */
|
|
gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);
|
|
|
|
renderer = gtk_cell_renderer_text_new();
|
|
|
|
/* pack cell renderer into tree view column */
|
|
gtk_tree_view_column_pack_start(col, renderer, TRUE);
|
|
|
|
/* set 'cell-background' property of the cell renderer */
|
|
g_object_set(renderer,
|
|
"cell-background", "Orange",
|
|
"cell-background-set", TRUE,
|
|
NULL);
|
|
|
|
model = create_and_fill_model();
|
|
|
|
gtk_tree_view_set_model(GTK_TREE_VIEW(view), model);
|
|
|
|
g_object_unref(model); /* destroy model automatically with view */
|
|
|
|
gtk_tree_selection_set_mode(gtk_tree_view_get_selection(GTK_TREE_VIEW(view)),
|
|
GTK_SELECTION_NONE);
|
|
|
|
return view;
|
|
}
|
|
|
|
|
|
int
|
|
main (int argc, char **argv)
|
|
{
|
|
GtkWidget *window;
|
|
GtkWidget *view;
|
|
|
|
gtk_init(&argc, &argv);
|
|
|
|
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
|
|
g_signal_connect(window, "delete_event", gtk_main_quit, NULL); /* dirty */
|
|
|
|
view = create_view_and_model();
|
|
|
|
gtk_container_add(GTK_CONTAINER(window), view);
|
|
|
|
gtk_widget_show_all(window);
|
|
|
|
gtk_main();
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
</programlisting>
|
|
|
|
<para>
|
|
The above code should produce something looking like this:
|
|
</para>
|
|
|
|
<figure id="sec-renderer-sshot">
|
|
<title>Persistent Cell Renderer Properties</title>
|
|
<screenshot>
|
|
<mediaobject>
|
|
<imageobject>
|
|
<imagedata fileref="images/renderer.png" format="PNG" />
|
|
</imageobject>
|
|
<imageobject>
|
|
<imagedata fileref="images/renderer.eps" format="EPS" />
|
|
</imageobject>
|
|
</mediaobject>
|
|
</screenshot>
|
|
</figure>
|
|
|
|
<para>
|
|
It looks like the tree view display is partly correct and partly incomplete.
|
|
On the one hand the tree view renders the correct number of rows (note
|
|
how there is no orange on the right after row 3), and it displays the
|
|
hierarchy correctly (on the left), but it does not display any of the
|
|
data that we have stored in the model. This is because we have made no
|
|
connection between what the cell renderers should render and the data
|
|
in the model. We have simply set some cell renderer properties on
|
|
start-up, and the cell renderers adhere to those set properties meticulously.
|
|
</para>
|
|
|
|
<para>
|
|
There are two different ways to connect cell renderers to data in the model:
|
|
<link linkend="sec-treeview-col-attributes">attributes</link> and
|
|
<link linkend="sec-treeview-col-celldatafunc">cell data functions</link>.
|
|
</para>
|
|
|
|
</sect1>
|
|
|
|
<!-- ************************************************************** -->
|
|
<sect1 id="sec-treeview-col-attributes">
|
|
<title>Attributes</title>
|
|
|
|
<para>
|
|
An attribute is a connection between a cell renderer property and a
|
|
field/column in the model. Whenever a cell is to be rendered, a cell renderer
|
|
property will be set to the values of the specified model column of the row
|
|
that is to be rendered. It is very important that the column's data type is
|
|
the same type that a property takes according to the API reference manual.
|
|
Here is some code to look at:
|
|
</para>
|
|
|
|
<programlisting role="C">
|
|
|
|
...
|
|
|
|
col = gtk_tree_view_column_new();
|
|
|
|
renderer = gtk_cell_renderer_text_new();
|
|
|
|
gtk_tree_view_column_pack_start(col, renderer, TRUE);
|
|
|
|
gtk_tree_view_column_add_attribute(col, renderer, "text", COL_FIRST_NAME);
|
|
|
|
...
|
|
|
|
</programlisting>
|
|
|
|
<para>
|
|
This means that the text cell renderer property <literal>"text"</literal>
|
|
will be set to the string in model column <literal>COL_FIRST_NAME</literal>
|
|
of each row to be drawn. It is important to internalise the difference
|
|
between <ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeViewColumn.html#gtk-tree-view-column-add-attribute">
|
|
<literal>gtk_tree_view_column_add_attribute</literal></ulink> and
|
|
<literal>g_object_set</literal>: <literal>g_object_set</literal> sets
|
|
a property to a certain <emphasis>value</emphasis>, while
|
|
<literal>gtk_tree_view_column_add_attribute</literal> sets a property
|
|
to whatever is in the specified _model column_ at the time of rendering.
|
|
</para>
|
|
|
|
<para>
|
|
Again, when setting attributes it is very important that the data type
|
|
stored in a model column is the same as the data type that a property
|
|
requires as argument. Check the API reference manual to see the data type
|
|
that is required for each property. When reading through the example a bit
|
|
further above, you might have noticed that we set the <literal>"cell-background"</literal>
|
|
property of a <ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkCellRendererText.html">
|
|
<literal>GtkCellRendererText</literal></ulink>, even though the
|
|
API documentation does not list such a property. We can do this, because
|
|
<literal>GtkCellRendererText</literal> is derived from
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkCellRenderer.html">
|
|
<literal>GtkCellRenderer</literal></ulink>, which does in fact
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkCellRenderer.html#GtkCellRenderer--cell-background">
|
|
have</ulink> such a property. Derived classes inherit the properties of their
|
|
parents. This is the same as with widgets that you can cast into one of their
|
|
ancestor classes. The API reference has an
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/ch01.html">object hierarchy</ulink>
|
|
that shows you which classes a widget or some other object is derived from.
|
|
</para>
|
|
|
|
<para>
|
|
There are two more noteworthy things about <literal>GtkCellRenderer</literal>
|
|
properties: one is that sometimes there are different properties which do
|
|
the same, but take different arguments, such as the <literal>"foreground"</literal>
|
|
and <literal>"foreground-gdk"</literal> properties of <literal>GtkCellRendererText</literal>
|
|
(which specify the text colour). The <literal>"foreground"</literal> property take a colour in string form, such as "Orange" or
|
|
"CornflowerBlue", whereas <literal>"foreground-gdk"</literal> takes a
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gdk/gdk-Colormaps-and-Colors.html#GdkColor">GdkColor</ulink>
|
|
argument. It is up to you to
|
|
decide which one to use - the effect will be the same. The other thing worth mentioning is
|
|
that most properties have a <literal>"foo-set"</literal> property taking a boolean value
|
|
as argument, such as <literal>"foreground-set"</literal>. This is useful when you want
|
|
to have a certain setting have an effect or not. If you set the <literal>"foreground"</literal>
|
|
property, but set <literal>"foreground-set"</literal> to <literal>FALSE</literal>, then your
|
|
foreground color setting will be disregarded. This is useful in cell data functions (see
|
|
below), or, for example, if you want set the foreground colour to a certain value at
|
|
start-up, but only want this to be in effect in some columns, but not in others (in which
|
|
case you could just connect the <literal>"foreground-set"</literal> property to a model
|
|
column of type <literal>G_TYPE_BOOLEAN</literal> with
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeViewColumn.html#gtk-tree-view-column-add-attribute">
|
|
<literal>gtk_tree_view_column_add_attribute</literal></ulink>.
|
|
</para>
|
|
|
|
<para>
|
|
Setting column attributes is the most straight-forward way to get your model data to be
|
|
displayed. This is usually used whenever you want the data in the model to be displayed
|
|
exactly as it is in the model.
|
|
</para>
|
|
|
|
<para>
|
|
Another way to get your model data displayed on the screen is to set up cell data functions.
|
|
</para>
|
|
|
|
</sect1>
|
|
|
|
<!-- ************************************************************** -->
|
|
<sect1 id="sec-treeview-col-celldatafunc">
|
|
<title>Cell Data Functions</title>
|
|
|
|
<para>
|
|
A cell data function is a function that is called for a specific cell renderer
|
|
for each single row before that row is rendered. It gives you maximum control
|
|
over what exactly is going to be rendered, as you can set the cell renderer's
|
|
properties just like you want to have them. Remember not only to
|
|
<emphasis>set</emphasis> a property if you want it to be active, but also to
|
|
<emphasis>unset</emphasis> a property if it should not be active (and it might
|
|
have been set in the previous row).
|
|
</para>
|
|
|
|
<para>
|
|
Cell data functions are often used if you want more fine-grained control over
|
|
what is to be displayed, or if the standard way to display something is not
|
|
quite like you want it to be. A case in point are floating point numbers. If
|
|
you want floating point numbers to be displayed in a certain way, say with only
|
|
one digit after the colon/comma, then you need to use a cell data function.
|
|
Use <ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeViewColumn.html#gtk-tree-view-column-set-cell-data-func">
|
|
<literal>gtk_tree_view_column_set_cell_data_func</literal></ulink> to set up a
|
|
cell data function for a particular cell renderer. Here is an example:
|
|
</para>
|
|
|
|
<programlisting role="C">
|
|
|
|
enum
|
|
{
|
|
COLUMN_NAME = 0,
|
|
COLUMN_AGE_FLOAT,
|
|
NUM_COLS
|
|
};
|
|
|
|
...
|
|
|
|
static void
|
|
age_cell_data_function (GtkTreeViewColumn *col,
|
|
GtkCellRenderer *renderer,
|
|
GtkTreeModel *model,
|
|
GtkTreeIter *iter,
|
|
gpointer user_data)
|
|
{
|
|
gfloat age;
|
|
gchar buf[20];
|
|
|
|
gtk_tree_model_get(model, iter, COLUMN_AGE_FLOAT, &age, -1);
|
|
|
|
g_snprintf(buf, sizeof(buf), "%.1f", age);
|
|
|
|
g_object_set(renderer, "text", buf, NULL);
|
|
}
|
|
|
|
...
|
|
|
|
liststore = gtk_list_store_new(NUM_COLS, G_TYPE_STRING, G_TYPE_FLOAT);
|
|
|
|
col = gtk_tree_view_column_new();
|
|
|
|
cell = gtk_cell_renderer_text_new();
|
|
|
|
gtk_tree_view_column_pack_start(col, cell, TRUE);
|
|
|
|
gtk_tree_view_column_set_cell_data_func(col, cell, age_cell_data_func, NULL, NULL);
|
|
|
|
...
|
|
|
|
</programlisting>
|
|
|
|
<para>
|
|
for each row to be rendered by this particular cell renderer, the cell data
|
|
function is going to be called, which then retrieves the float from the model,
|
|
and turns it into a string where the float has only one digit after the
|
|
colon/comma, and renders that with the text cell renderer.
|
|
</para>
|
|
|
|
<para>
|
|
This is only a simple example, you can make cell data functions a lot more
|
|
complicated if you want to. As always, there is a trade-off to keep in mind
|
|
though. Your cell data function is going to be called every single time a
|
|
cell in that (renderer) column is going to be rendered. Go and check how
|
|
often this function is called in your program if you ever use one. If you
|
|
do time-consuming operations within a cell data function, things are not
|
|
going to be fast, especially if you have a lot of rows. The alternative
|
|
in this case would have been to make an additional column
|
|
COLUMN_AGE_FLOAT_STRING of type <literal>G_TYPE_STRING</literal>, and to set
|
|
the float in string form whenever you set the float itself in a row, and then
|
|
hook up the string column to a text cell renderer using attributes. This way
|
|
the float to string conversion would only need to be done once. This is a cpu
|
|
cycles / memory trade-off, and it depends on your particular case which one
|
|
is more suitable. Things you should probably not do is to convert long strings
|
|
into UTF8 format in a cell data function, for example.
|
|
</para>
|
|
|
|
<para>
|
|
You might notice that your cell data function is called at times even for rows
|
|
that are not visible at the moment. This is because the tree view needs to know
|
|
its total height, and in order to calculate this it needs to know the height of
|
|
each and every single row, and it can only know that by having it measured, which
|
|
is going to be slow when you have a lot of rows with different heights (if your
|
|
rows all have the same height, there should not be any visible delay though).
|
|
</para>
|
|
|
|
</sect1>
|
|
|
|
<!-- ************************************************************** -->
|
|
<sect1 id="sec-treeview-celltext-types">
|
|
<title>GtkCellRendererText and Integer, Boolean and Float Types</title>
|
|
|
|
<para>
|
|
It has been said before that, when using attributes to connect data from
|
|
the model to a cell renderer property, the data in the model column
|
|
specified in <literal>gtk_tree_view_column_add_attribute</literal> must
|
|
always be of the same type as the data type that the property requires.
|
|
</para>
|
|
|
|
<para>
|
|
This is usually true, but there is an exception: if you use
|
|
<literal>gtk_tree_view_column_add_attribute</literal> to connect
|
|
a text cell renderer's <literal>"text"</literal> property to a
|
|
model column, the model column does not need to be of
|
|
<literal>G_TYPE_STRING</literal>, it can also be one of most
|
|
other fundamental GLib types, e.g.
|
|
<literal>G_TYPE_BOOLEAN</literal>,
|
|
<literal>G_TYPE_INT</literal>,
|
|
<literal>G_TYPE_UINT</literal>,
|
|
<literal>G_TYPE_LONG</literal>,
|
|
<literal>G_TYPE_ULONG</literal>,
|
|
<literal>G_TYPE_INT64</literal>,
|
|
<literal>G_TYPE_UINT64</literal>,
|
|
<literal>G_TYPE_FLOAT</literal>, or
|
|
<literal>G_TYPE_DOUBLE</literal>. The text cell renderer
|
|
will automatically display the values of these types
|
|
correctly in the tree view. For example:
|
|
</para>
|
|
|
|
<programlisting role="C">
|
|
enum
|
|
{
|
|
COL_NAME = 0,
|
|
COL_YEAR_BORN,
|
|
NUM_COLS
|
|
};
|
|
|
|
liststore = gtk_list_store_new(NUM_COLS, G_TYPE_STRING, G_TYPE_UINT);
|
|
|
|
...
|
|
|
|
cell = gtk_cell_renderer_text_new();
|
|
col = gtk_tree_view_column_new();
|
|
gtk_tree_view_column_add_attribute(col, cell, "text", COL_YEAR_BORN);
|
|
|
|
...
|
|
</programlisting>
|
|
|
|
<para>
|
|
Even though the <literal>"text"</literal> property would require a
|
|
string value, we use a model column of an integer type when setting
|
|
attributes. The integer will then automatically be converted into
|
|
a string before the cell renderer property is set
|
|
<footnote><para>
|
|
For those interested, the conversion actually takes place within
|
|
<literal>g_object_set_property</literal>. Before a certain cell is
|
|
rendered, the tree view column will call <literal>gtk_tree_model_get_value</literal>
|
|
to set the cell renderer properties according to values stored in
|
|
the tree model (if any are mapped via <literal>gtk_tree_view_column_add_attribute</literal>
|
|
or one of the convenience functions that do the same thing), and then
|
|
pass on the <literal>GValue</literal> retrieved to
|
|
<literal>g_object_set_property</literal>.
|
|
</para></footnote>.
|
|
</para>
|
|
|
|
<para>
|
|
If you are using a floating point type, ie. <literal>G_TYPE_FLOAT</literal>
|
|
or <literal>G_TYPE_DOUBLE</literal>, there is no way to tell the text cell
|
|
renderer how many digits after the floating point (or comma) should be
|
|
rendered. If you only want a certain amount of digits after the point/comma,
|
|
you will need to use a <link linkend="sec-treeview-col-celldatafunc">cell data function</link>.
|
|
</para>
|
|
|
|
</sect1>
|
|
|
|
|
|
<!-- ************************************************************** -->
|
|
<sect1 id="sec-treeview-col-utf8-pango">
|
|
<title>GtkCellRendererText, UTF8, and pango markup</title>
|
|
|
|
<para>
|
|
All text used in Gtk+-2.0 widgets needs to be in UTF8 encoding, and
|
|
<literal>GtkCellRendererText</literal> is no exception. Text in plain
|
|
ASCII is automatically valid UTF8, but as soon as you have special
|
|
characters that do not exist in plain ASCII (usually characters that
|
|
are not used in the English language alphabet), they need to be in
|
|
UTF8 encoding. There are many different character encodings that all
|
|
specify different ways to tell the computer which character is meant.
|
|
Gtk+-2.0 uses UTF8, and whenever you have text that is in a different
|
|
encoding, you need to convert it to UTF8 encoding first, using one of the GLib
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/glib/glib-Character-Set-Conversion.html">
|
|
<literal>g_convert</literal></ulink> family of functions. If you only
|
|
use text input from other Gtk+ widgets, you are on the safe side, as
|
|
they will return all text in UTF8 as well.
|
|
</para>
|
|
|
|
<para>
|
|
However, if you use 'external' sources of text input, then you
|
|
must convert that text from the text's encoding (or the user's locale) to UTF8,
|
|
or it will not be rendered correctly (either not at all, or it will be cut off
|
|
after the first invalid character). Filenames are especially hard, because there
|
|
is no indication whatsoever what character encoding a filename is in (it might
|
|
have been created when the user was using a different locale, so filename encoding
|
|
is basically unreliable and broken). You may want to convert to UTF8 with fallback
|
|
characters in that case. You can check whether a string is valid UTF8 with
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/glib/glib-Unicode-Manipulation.html#g-utf8-validate">
|
|
<literal>g_utf8_validate</literal></ulink>. You should, in this author's opinion
|
|
at least, put these checks into your code at crucial places wherever it is
|
|
not affecting performance, especially if you are an English-speaking programmer
|
|
that has little experience with non-English locales. It will make it easier for
|
|
others and yourself to spot problems with non-English locales later on.
|
|
</para>
|
|
|
|
<para>
|
|
In addition to the "text" property, GtkCellRendererText also has a "markup"
|
|
property that takes text with <ulink url="http://developer.gnome.org/doc/API/2.0/pango/PangoMarkupFormat.html">
|
|
pango markup</ulink> as input. Pango markup allows you to place special
|
|
tags into a text string that affect the style the text is rendered (see the pango
|
|
documentation). Basically you can achieve everything you can achieve with the other
|
|
properties also with pango markup (only that using properties is more efficient and
|
|
less messy). Pango markup has one distinct advantage though that you cannot achieve
|
|
with text cell renderer properties: with pango markup, you can change the text style
|
|
in the middle of the text, so you could, for example, render one part of a text string
|
|
in bold print, and the rest of the text in normal. Here is an example of a string with
|
|
pango markup:
|
|
</para>
|
|
|
|
<para><literal>
|
|
"You can have text in <b>bold</b> or in a <span color='Orange'>different color</span>"
|
|
</literal></para>
|
|
|
|
<para>
|
|
When using the <literal>"markup"</literal> property, you need to take into account
|
|
that the <literal>"markup"</literal> and <literal>"text"</literal> properties do
|
|
not seem to be mutually exclusive (I suppose this could be called a bug).
|
|
In other words: whenever you set <literal>"markup"</literal> (and have used the
|
|
<literal>"text"</literal> property before), set the <literal>"text"</literal>
|
|
property to NULL, and vice versa. Example:
|
|
</para>
|
|
|
|
<programlisting role="C">
|
|
...
|
|
|
|
void
|
|
foo_cell_data_function ( ... )
|
|
{
|
|
...
|
|
if (foo->is_important)
|
|
g_object_set(renderer, "markup", "<b>important</b>", "text", NULL, NULL);
|
|
else
|
|
g_object_set(renderer, "markup", NULL, "text", "not important", NULL);
|
|
...
|
|
}
|
|
|
|
...
|
|
</programlisting>
|
|
|
|
<para>
|
|
Another thing to keep in mind when using pango markup text is that you might need to escape
|
|
text if you construct strings with pango markup on the fly using random input data. For example:
|
|
</para>
|
|
|
|
<programlisting role="C">
|
|
...
|
|
|
|
void
|
|
foo_cell_data_function ( ... )
|
|
{
|
|
gchar *markuptxt;
|
|
|
|
...
|
|
/* This might be problematic if artist_string or title_string
|
|
* contain markup characters/entities: */
|
|
markuptxt = g_strdup_printf("<b>%s</b> - <i>%s</i>",
|
|
artist_string, title_string);
|
|
...
|
|
g_object_set(renderer, "markup", markuptxt, "text", NULL, NULL);
|
|
...
|
|
g_free(markuptxt);
|
|
}
|
|
|
|
...
|
|
</programlisting>
|
|
|
|
<para>
|
|
The above example will not work if artist_string is "Simon & Garfunkel" for example, because the &
|
|
character is one of the characters that is special. They need to be escaped, so that pango knows that they
|
|
do not refer to any pango markup, but are just characters. In this case the string would need to be
|
|
"Simon &amp; Garfunkel" in order to make sense in between the pango markup in which it is going to be
|
|
pasted. You can escape a string with <ulink url="http://developer.gnome.org/doc/API/2.0/glib/glib-Simple-XML-Subset-Parser.html#g-markup-escape-text">
|
|
<literal>g_markup_escape</literal></ulink> (and you will need to free the
|
|
resulting newly-allocated string again with <literal>g_free</literal>).
|
|
</para>
|
|
|
|
<para>
|
|
It is possible to combine both pango markup and text cell renderer properties. Both will be 'added' together
|
|
to render the string in question, only that the text cell renderer properties will be applied to the whole
|
|
string. If you set the <literal>"markup"</literal> property to normal text without any pango markup,
|
|
it will render as normal text just as if you had used the <literal>"text"</literal> property. However,
|
|
as opposed to the <literal>"text"</literal> property, special characters in the <literal>"markup"</literal>
|
|
property text would still need to be escaped, even if you do not use pango markup in the text.
|
|
</para>
|
|
|
|
</sect1>
|
|
|
|
<!-- ************************************************************** -->
|
|
<sect1 id="sec-treeview-col-example">
|
|
<title>A Working Example</title>
|
|
|
|
<para>
|
|
Here is our example from the very beginning again (with an additional column though),
|
|
only that the contents of the model are rendered properly on the screen this time.
|
|
Both attributes and a cell data function are used for demonstration purposes.
|
|
</para>
|
|
|
|
<programlisting role="C">
|
|
#include <gtk/gtk.h>
|
|
|
|
enum
|
|
{
|
|
COL_FIRST_NAME = 0,
|
|
COL_LAST_NAME,
|
|
COL_YEAR_BORN,
|
|
NUM_COLS
|
|
} ;
|
|
|
|
static GtkTreeModel *
|
|
create_and_fill_model (void)
|
|
{
|
|
GtkTreeStore *treestore;
|
|
GtkTreeIter toplevel, child;
|
|
|
|
treestore = gtk_tree_store_new(NUM_COLS,
|
|
G_TYPE_STRING,
|
|
G_TYPE_STRING,
|
|
G_TYPE_UINT);
|
|
|
|
/* Append a top level row and leave it empty */
|
|
gtk_tree_store_append(treestore, &toplevel, NULL);
|
|
gtk_tree_store_set(treestore, &toplevel,
|
|
COL_FIRST_NAME, "Maria",
|
|
COL_LAST_NAME, "Incognito",
|
|
-1);
|
|
|
|
/* Append a second top level row, and fill it with some data */
|
|
gtk_tree_store_append(treestore, &toplevel, NULL);
|
|
gtk_tree_store_set(treestore, &toplevel,
|
|
COL_FIRST_NAME, "Jane",
|
|
COL_LAST_NAME, "Average",
|
|
COL_YEAR_BORN, (guint) 1962,
|
|
-1);
|
|
|
|
/* Append a child to the second top level row, and fill in some data */
|
|
gtk_tree_store_append(treestore, &child, &toplevel);
|
|
gtk_tree_store_set(treestore, &child,
|
|
COL_FIRST_NAME, "Janinita",
|
|
COL_LAST_NAME, "Average",
|
|
COL_YEAR_BORN, (guint) 1985,
|
|
-1);
|
|
|
|
return GTK_TREE_MODEL(treestore);
|
|
}
|
|
|
|
void
|
|
age_cell_data_func (GtkTreeViewColumn *col,
|
|
GtkCellRenderer *renderer,
|
|
GtkTreeModel *model,
|
|
GtkTreeIter *iter,
|
|
gpointer user_data)
|
|
{
|
|
guint year_born;
|
|
guint year_now = 2003; /* to save code not relevant for the example */
|
|
gchar buf[64];
|
|
|
|
gtk_tree_model_get(model, iter, COL_YEAR_BORN, &year_born, -1);
|
|
|
|
if (year_born <= year_now && year_born > 0)
|
|
{
|
|
guint age = year_now - year_born;
|
|
|
|
g_snprintf(buf, sizeof(buf), "%u years old", age);
|
|
|
|
g_object_set(renderer, "foreground-set", FALSE, NULL); /* print this normal */
|
|
}
|
|
else
|
|
{
|
|
g_snprintf(buf, sizeof(buf), "age unknown");
|
|
|
|
/* make red */
|
|
g_object_set(renderer, "foreground", "Red", "foreground-set", TRUE, NULL);
|
|
}
|
|
|
|
g_object_set(renderer, "text", buf, NULL);
|
|
}
|
|
|
|
|
|
static GtkWidget *
|
|
create_view_and_model (void)
|
|
{
|
|
GtkTreeViewColumn *col;
|
|
GtkCellRenderer *renderer;
|
|
GtkWidget *view;
|
|
GtkTreeModel *model;
|
|
|
|
view = gtk_tree_view_new();
|
|
|
|
/* --- Column #1 --- */
|
|
|
|
col = gtk_tree_view_column_new();
|
|
|
|
gtk_tree_view_column_set_title(col, "First Name");
|
|
|
|
/* pack tree view column into tree view */
|
|
gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);
|
|
|
|
renderer = gtk_cell_renderer_text_new();
|
|
|
|
/* pack cell renderer into tree view column */
|
|
gtk_tree_view_column_pack_start(col, renderer, TRUE);
|
|
|
|
/* connect 'text' property of the cell renderer to
|
|
* model column that contains the first name */
|
|
gtk_tree_view_column_add_attribute(col, renderer, "text", COL_FIRST_NAME);
|
|
|
|
|
|
/* --- Column #2 --- */
|
|
|
|
col = gtk_tree_view_column_new();
|
|
|
|
gtk_tree_view_column_set_title(col, "Last Name");
|
|
|
|
/* pack tree view column into tree view */
|
|
gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);
|
|
|
|
renderer = gtk_cell_renderer_text_new();
|
|
|
|
/* pack cell renderer into tree view column */
|
|
gtk_tree_view_column_pack_start(col, renderer, TRUE);
|
|
|
|
/* connect 'text' property of the cell renderer to
|
|
* model column that contains the last name */
|
|
gtk_tree_view_column_add_attribute(col, renderer, "text", COL_LAST_NAME);
|
|
|
|
/* set 'weight' property of the cell renderer to
|
|
* bold print (we want all last names in bold) */
|
|
g_object_set(renderer,
|
|
"weight", PANGO_WEIGHT_BOLD,
|
|
"weight-set", TRUE,
|
|
NULL);
|
|
|
|
|
|
/* --- Column #3 --- */
|
|
|
|
col = gtk_tree_view_column_new();
|
|
|
|
gtk_tree_view_column_set_title(col, "Age");
|
|
|
|
/* pack tree view column into tree view */
|
|
gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);
|
|
|
|
renderer = gtk_cell_renderer_text_new();
|
|
|
|
/* pack cell renderer into tree view column */
|
|
gtk_tree_view_column_pack_start(col, renderer, TRUE);
|
|
|
|
/* connect a cell data function */
|
|
gtk_tree_view_column_set_cell_data_func(col, renderer, age_cell_data_func, NULL, NULL);
|
|
|
|
|
|
model = create_and_fill_model();
|
|
|
|
gtk_tree_view_set_model(GTK_TREE_VIEW(view), model);
|
|
|
|
g_object_unref(model); /* destroy model automatically with view */
|
|
|
|
gtk_tree_selection_set_mode(gtk_tree_view_get_selection(GTK_TREE_VIEW(view)),
|
|
GTK_SELECTION_NONE);
|
|
|
|
return view;
|
|
}
|
|
|
|
|
|
int
|
|
main (int argc, char **argv)
|
|
{
|
|
GtkWidget *window;
|
|
GtkWidget *view;
|
|
|
|
gtk_init(&argc, &argv);
|
|
|
|
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
|
|
g_signal_connect(window, "delete_event", gtk_main_quit, NULL); /* dirty */
|
|
|
|
view = create_view_and_model();
|
|
|
|
gtk_container_add(GTK_CONTAINER(window), view);
|
|
|
|
gtk_widget_show_all(window);
|
|
|
|
gtk_main();
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
</programlisting>
|
|
|
|
|
|
</sect1>
|
|
|
|
<!-- ************************************************************** -->
|
|
<sect1 id="sec-treeview-col-whole-row">
|
|
<title>How to Make a Whole Row Bold or Coloured</title>
|
|
|
|
<para>
|
|
This seems to be a frequently asked question, so it is worth mentioning it here. You
|
|
have the two approaches mentioned above: either you use cell data functions, and check
|
|
in each whether a particular row should be highlighted in a particular way (bold, coloured, whatever),
|
|
and then set the renderer properties accordingly (and unset them if you want that row to look normal),
|
|
or you use attributes. Cell data functions are most likely not the right choice in this case though.
|
|
</para>
|
|
|
|
<para>
|
|
If you only want every second line to have a gray background to make it easier
|
|
for the user to see which data belongs to which line in wide tree views, then
|
|
you do not have to bother with the stuff mentioned here. Instead just set the
|
|
rules hint on the tree view as described in the <link linkend="sec-treeview-look">
|
|
here</link>, and everything will be done automatically, in colours that conform
|
|
to the chosen theme even (unless the theme disables rule hints, that is).
|
|
</para>
|
|
|
|
<para>
|
|
Otherwise, the most suitable approach for most cases is that you add two columns
|
|
to your model, one for the property itself (e.g. a column COL_ROW_COLOR
|
|
of type <literal>G_TYPE_STRING</literal>), and one for the boolean flag
|
|
of the property (e.g. a column COL_ROW_COLOR_SET of type
|
|
<literal>G_TYPE_BOOLEAN</literal>). You would then connect these columns
|
|
with the <literal>"foreground"</literal> and <literal>"foreground-set"</literal>
|
|
properties of each renderer. Now, whenever you set a row's COL_ROW_COLOR
|
|
field to a colour, and set that row's COL_ROW_COLOR_SET field to <literal>TRUE</literal>,
|
|
then this column will be rendered in the colour of your choice. If you
|
|
only want either the default text colour or one special other colour,
|
|
you could even achieve the same thing with just one extra model column:
|
|
in this case you could just set all renderer's <literal>"foreground"</literal>
|
|
property to whatever special color you want, and only connect the COL_ROW_COLOR_SET
|
|
column to all renderer's <literal>"foreground-set"</literal> property using
|
|
attributes. This works similar with any other attribute, only that you need to adjust
|
|
the data type for the property of course (e.g. <literal>"weight"</literal> would take a
|
|
<literal>G_TYPE_INT</literal>, in form of a <literal>PANGO_WEIGHT_FOO</literal>
|
|
define in this case).
|
|
</para>
|
|
|
|
<para>
|
|
As a general rule, you should not change the text colour or the background
|
|
colour of a cell unless you have a really good reason for it.
|
|
To <ulink url="http://ometer.com/gtk-colors.html">quote</ulink> Havoc
|
|
Pennington: <quote>Because colors in GTK+ represent a theme the user has chosen, you should never
|
|
set colors purely for aesthetic reasons. If users don't like GTK+ gray, they can change it themselves
|
|
to their favorite shade of orange.</quote>
|
|
</para>
|
|
|
|
</sect1>
|
|
|
|
<!-- ************************************************************** -->
|
|
<sect1 id="sec-treeview-col-pixbufs">
|
|
<title>How to Pack Icons into the Tree View</title>
|
|
|
|
<para>
|
|
So far we have only put text in the tree view. While everything you need
|
|
to know to display icons (in the form of <literal>GdkPixbuf</literal>s)
|
|
has been introduced in the previous sections, a short example might help
|
|
to make things clearer. The following code will pack an icon and some
|
|
text into the same tree view column:
|
|
</para>
|
|
|
|
<programlisting role="C">
|
|
|
|
enum
|
|
{
|
|
COL_ICON = 0,
|
|
COL_TEXT,
|
|
NUM_COLS
|
|
};
|
|
|
|
GtkListStore *
|
|
create_liststore(void)
|
|
{
|
|
GtkListStore *store;
|
|
GtkTreeIter iter;
|
|
GdkPixbuf *icon;
|
|
GError *error = NULL;
|
|
|
|
store = gtk_list_store_new(2, GDK_TYPE_PIXBUF, G_TYPE_STRING);
|
|
|
|
icon = gdk_pixbuf_new_from_file("icon.png", &error);
|
|
if (error)
|
|
{
|
|
g_warning ("Could not load icon: %s\n", error->message);
|
|
g_error_free(error);
|
|
error = NULL;
|
|
}
|
|
|
|
gtk_list_store_append(store, &iter);
|
|
gtk_list_store_set(store, &iter,
|
|
COL_ICON, icon,
|
|
COL_TEXT, "example",
|
|
-1);
|
|
|
|
return store;
|
|
}
|
|
|
|
GtkWidget *
|
|
create_treeview(void)
|
|
{
|
|
GtkTreeModel *model;
|
|
GtkTreeViewColumn *col;
|
|
GtkCellRenderer *renderer;
|
|
GtkWidget *view;
|
|
|
|
model = GTK_TREE_MODEL(create_liststore());
|
|
|
|
view = gtk_tree_view_new_with_model(model);
|
|
|
|
col = gtk_tree_view_column_new();
|
|
gtk_tree_view_column_set_title(col, "Title");
|
|
|
|
renderer = gtk_cell_renderer_pixbuf_new();
|
|
gtk_tree_view_column_pack_start(col, renderer, FALSE);
|
|
gtk_tree_view_column_set_attributes(col, renderer,
|
|
"pixbuf", COL_ICON,
|
|
NULL);
|
|
|
|
renderer = gtk_cell_renderer_text_new();
|
|
gtk_tree_view_column_pack_start(col, renderer, TRUE);
|
|
gtk_tree_view_column_set_attributes(col, renderer,
|
|
"text", COL_TEXT,
|
|
NULL);
|
|
|
|
gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);
|
|
|
|
gtk_widget_show_all(view);
|
|
|
|
return view;
|
|
}
|
|
|
|
</programlisting>
|
|
|
|
<para>
|
|
Note that the tree view will not resize icons for you, but displays
|
|
them in their original size. If you want to display
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/gtk-Stock-Items.html">
|
|
stock icons</ulink> instead of <literal>GdkPixbuf</literal>s loaded
|
|
from file, you should have a look at the <literal>"stock-id"</literal>
|
|
property of <ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkCellRendererPixbuf.html">
|
|
<literal>GtkCellRendererPixbuf</literal></ulink> (and your model column
|
|
should be of type <literal>G_TYPE_STRING</literal>, as all stock IDs are
|
|
just strings by which to identify the stock icon).
|
|
</para>
|
|
|
|
</sect1>
|
|
|
|
</chapter>
|
|
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
|
|
<chapter id="sec-sel-click-menus">
|
|
<title>Selections, Double-Clicks and Context Menus</title>
|
|
|
|
<!-- ************************************************************** -->
|
|
<sect1 id="sec-selections">
|
|
<title>Handling Selections</title>
|
|
|
|
<para>
|
|
One of the most basic features of a list or tree view is that rows can be selected or unselected.
|
|
Selections are handled using the <ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeSelection.html">
|
|
<literal>GtkTreeSelection</literal></ulink> object of a tree view. Every
|
|
tree view automatically has a <literal>GtkTreeSelection</literal> associated with it, and you can
|
|
get it using <ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeView.html#gtk-tree-view-get-selection">
|
|
<literal>gtk_tree_view_get_selection</literal></ulink>. Selections are handled completely on
|
|
the tree view side, which means that the model knows nothing about which rows are selected or not.
|
|
There is no particular reason why selection handling could not have been implemented with functions
|
|
that access the tree view widget directly, but for reasons of API cleanliness and code clarity the
|
|
Gtk+ developers decided to create this special <literal>GtkTreeSelection</literal> object that then
|
|
internally deals with the tree view widget. You will never need to create a tree selection object,
|
|
it will be created for you automatically when you create a new tree view. You only need to use said
|
|
<literal>gtk_tree_view_get_selection</literal> function to get a pointer to the selection object.
|
|
</para>
|
|
|
|
<para>
|
|
There are three ways to deal with tree view selections: either you get a list of the currently selected
|
|
rows whenever you need it, for example within a context menu function, or you keep track of all select
|
|
and unselect actions and keep a list of the currently selected rows around for whenever you need them;
|
|
as a last resort, you can also traverse your list or tree and check each single row for whether it is
|
|
selected or not (which you need to do if you want all rows that are <emphasis>not</emphasis> selected
|
|
for example).
|
|
</para>
|
|
|
|
<!-- ************************************************************** -->
|
|
<sect2 id="sec-selections-modes">
|
|
<title>Selection Modes</title>
|
|
|
|
<para>
|
|
You can use <literal>gtk_tree_selection_set_mode</literal> to influence the way that selections are
|
|
handled. There are four selection modes:
|
|
</para>
|
|
|
|
<itemizedlist>
|
|
<listitem>
|
|
<simpara>
|
|
<literal>GTK_SELECTION_NONE</literal> - no items can be selected
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
<literal>GTK_SELECTION_SINGLE</literal> - no more than one item can be selected
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
<literal>GTK_SELECTION_BROWSE</literal> - exactly one item is always selected
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
<literal>GTK_SELECTION_MULTIPLE</literal> - anything between no item and all items can be selected
|
|
</simpara>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
</sect2>
|
|
|
|
<!-- ************************************************************** -->
|
|
<sect2 id="sec-selections-current">
|
|
<title>Getting the Currently Selected Rows</title>
|
|
|
|
<para>
|
|
You can access the currently selected rows either by traversing all selected rows using
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeSelection.html#gtk-tree-selection-selected-foreach">
|
|
<literal>gtk_tree_selection_selected_foreach</literal></ulink> or get a
|
|
<literal>GList</literal> of tree paths of the selected rows using
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeSelection.html#gtk-tree-selection-get-selected-rows">
|
|
<literal>gtk_tree_selection_get_selected_rows</literal></ulink>. Note
|
|
that this function is only available in Gtk+-2.2 and newer, which means that you can't
|
|
use it or need to reimplement it if you want your application to work with older
|
|
installations.
|
|
</para>
|
|
|
|
<para>
|
|
If the selection mode you are using is either <literal>GTK_SELECTION_SINGLE</literal>
|
|
or <literal>GTK_SELECTION_BROWSE</literal>, the most convenient way to get the
|
|
selected row is the function <literal>gtk_tree_selection_get_selected</literal>,
|
|
which will return <literal>TRUE</literal> and fill in the specified tree iter with
|
|
the selected row (if a row is selected), and return <literal>FALSE</literal> otherwise.
|
|
It is used like this:
|
|
</para>
|
|
|
|
<programlisting role="C">
|
|
...
|
|
|
|
GtkTreeSelection *selection;
|
|
GtkTreeModel *model;
|
|
GtkTreeIter iter;
|
|
|
|
/* This will only work in single or browse selection mode! */
|
|
|
|
selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
|
|
if (gtk_tree_selection_get_selected(selection, &model, &iter))
|
|
{
|
|
gchar *name;
|
|
|
|
gtk_tree_model_get (model, &iter, COL_NAME, &name, -1);
|
|
|
|
g_print ("selected row is: %s\n", name);
|
|
|
|
g_free(name);
|
|
}
|
|
else
|
|
{
|
|
g_print ("no row selected.\n");
|
|
}
|
|
|
|
...
|
|
</programlisting>
|
|
|
|
<para>
|
|
One thing you need to be aware of is that you need to take care when removing rows from
|
|
the model in a <literal>gtk_tree_selection_selected_foreach</literal> callback, or when
|
|
looping through the list that <literal>gtk_tree_selection_get_selected_rows</literal> returns
|
|
(because it contains paths, and when you remove rows in the middle, then the old paths will
|
|
point to either a non-existing row, or to another row than the one selected). You have two ways
|
|
around this problem: one way is to use the solution to removing multiple rows that has been
|
|
<link linkend="sec-treemodel-remove-row">described above</link>, ie. to get tree row references
|
|
for all selected rows and then remove the rows one by one; the other solution is to sort the
|
|
list of selected tree paths so that the last rows come first in the list, so that you remove
|
|
rows from the end of the list or tree. You cannot remove rows from within a foreach callback in
|
|
any case, that is simply not allowed.
|
|
</para>
|
|
|
|
<para>
|
|
Here is an example of how to use <literal>gtk_tree_selection_selected_foreach</literal>:
|
|
</para>
|
|
|
|
<programlisting role="C">
|
|
...
|
|
|
|
gboolean
|
|
view_selected_foreach_func (GtkTreeModel *model,
|
|
GtkTreePath *path,
|
|
GtkTreeIter *iter,
|
|
gpointer userdata)
|
|
{
|
|
gchar *name;
|
|
|
|
gtk_tree_model_get (model, iter, COL_NAME, &name, -1);
|
|
|
|
g_print ("%s is selected\n", name);
|
|
}
|
|
|
|
|
|
void
|
|
do_something_with_all_selected_rows (GtkWidget *treeview)
|
|
{
|
|
GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));
|
|
|
|
gtk_tree_selection_selected_foreach(selection, view_selected_foreach_func, NULL);
|
|
}
|
|
|
|
|
|
void
|
|
create_view (void)
|
|
{
|
|
GtkWidget *view;
|
|
GtkTreeSelection *selection;
|
|
|
|
...
|
|
|
|
view = gtk_tree_view_new();
|
|
|
|
...
|
|
|
|
selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
|
|
|
|
gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
|
|
...
|
|
}
|
|
|
|
...
|
|
</programlisting>
|
|
|
|
</sect2>
|
|
|
|
<!-- ************************************************************** -->
|
|
<sect2 id="sec-selections-function">
|
|
<title>Using Selection Functions</title>
|
|
|
|
<para>
|
|
You can set up a custom selection function with
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeSelection.html#gtk-tree-selection-set-select-function">
|
|
<literal>gtk_tree_selection_set_select_function</literal></ulink>. This
|
|
function will then be called every time a row is going to be selected or
|
|
unselected (meaning: it will be called before the selection status of that
|
|
row is changed). Selection functions are commonly used for the following things:
|
|
</para>
|
|
|
|
<orderedlist>
|
|
<listitem>
|
|
<para>
|
|
... to keep track of the currently selected items (then you maintain a list
|
|
of selected items yourself). In this case, note again that your selection
|
|
function is called <emphasis>before</emphasis> the row's selection status is
|
|
changed. In other words: if the row is <emphasis>going to be</emphasis>
|
|
selected, then the boolean path_currently_selected variable that is passed
|
|
to the selection function is still <literal>FALSE</literal>. Also note that
|
|
the selection function might not always be called when a row is removed,
|
|
so you either have to unselect a row before you remove it to make sure your
|
|
selection function is called and removes the row from your list, or check
|
|
the validity of a row when you process the selection list you keep. You
|
|
should not store tree paths in your self-maintained list of of selected rows,
|
|
because whenever rows are added or removed or the model is resorted the paths
|
|
might point to other rows. Use tree row references or other unique means of
|
|
identifying a row instead.
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
... to tell Gtk+ whether it is allowed to select or unselect that specific row
|
|
(you should make sure though that it is otherwise obvious to a user whether a
|
|
row can be selected or not, otherwise the user will be confused if she just
|
|
cannot select or unselect a row). This is done by returning TRUE or FALSE in the
|
|
selection function.
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
... to take additional action whenever a row is selected or unselected.
|
|
</para>
|
|
</listitem>
|
|
</orderedlist>
|
|
|
|
<para>
|
|
Yet another simple example:
|
|
</para>
|
|
|
|
<programlisting role="C">
|
|
|
|
...
|
|
|
|
gboolean
|
|
view_selection_func (GtkTreeSelection *selection,
|
|
GtkTreeModel *model,
|
|
GtkTreePath *path,
|
|
gboolean path_currently_selected,
|
|
gpointer userdata)
|
|
{
|
|
GtkTreeIter iter;
|
|
|
|
if (gtk_tree_model_get_iter(model, &iter, path))
|
|
{
|
|
gchar *name;
|
|
|
|
gtk_tree_model_get(model, &iter, COL_NAME, &name, -1);
|
|
|
|
if (!path_currently_selected)
|
|
{
|
|
g_print ("%s is going to be selected.\n", name);
|
|
}
|
|
else
|
|
{
|
|
g_print ("%s is going to be unselected.\n", name);
|
|
}
|
|
|
|
g_free(name);
|
|
}
|
|
|
|
return TRUE; /* allow selection state to change */
|
|
}
|
|
|
|
|
|
void
|
|
create_view (void)
|
|
{
|
|
GtkWidget *view;
|
|
GtkTreeSelection *selection;
|
|
|
|
...
|
|
|
|
view = gtk_tree_view_new();
|
|
|
|
...
|
|
|
|
selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
|
|
|
|
gtk_tree_selection_set_select_function(selection, view_selection_func, NULL, NULL);
|
|
...
|
|
}
|
|
|
|
...
|
|
|
|
</programlisting>
|
|
|
|
</sect2>
|
|
|
|
<!-- ************************************************************** -->
|
|
<sect2 id="sec-selections-check-row">
|
|
<title>Checking Whether a Row is Selected</title>
|
|
|
|
<para>
|
|
You can check whether a given row is selected or not using the functions
|
|
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeSelection.html#gtk-tree-selection-iter-is-selected">
|
|
<literal>gtk_tree_selection_iter_is_selected</literal></ulink>. or
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeSelection.html#gtk-tree-selection-path-is-selected">
|
|
<literal>gtk_tree_selection_path_is_selected</literal></ulink>.
|
|
If you want to know all rows that are <emphasis>not</emphasis> selected,
|
|
for example, you could just traverse the whole list or tree, and use the
|
|
above functions to check for each row whether it is selected or not.
|
|
</para>
|
|
|
|
|
|
</sect2>
|
|
|
|
<!-- ************************************************************** -->
|
|
<sect2 id="sec-selections-selecting-rows">
|
|
<title>Selecting and Unselecting Rows</title>
|
|
|
|
<para>
|
|
You can select or unselect rows manually with
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeSelection.html#gtk-tree-selection-select-iter">
|
|
<literal>gtk_tree_selection_select_iter</literal></ulink>,
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeSelection.html#gtk-tree-selection-select-path">
|
|
<literal>gtk_tree_selection_select_path</literal></ulink>,
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeSelection.html#gtk-tree-selection-unselect-iter">
|
|
<literal>gtk_tree_selection_unselect_iter</literal></ulink>,
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeSelection.html#gtk-tree-selection-unselect-path">
|
|
<literal>gtk_tree_selection_unselect_path</literal></ulink>,
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeSelection.html#gtk-tree-selection-select-all">
|
|
<literal>gtk_tree_selection_select_all</literal></ulink>, and
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeSelection.html#gtk-tree-selection-unselect-all">
|
|
<literal>gtk_tree_selection_unselect_all</literal></ulink>
|
|
should you ever need to do that.
|
|
</para>
|
|
|
|
|
|
</sect2>
|
|
|
|
|
|
|
|
<!-- ************************************************************** -->
|
|
<sect2 id="sec-selections-row-count">
|
|
<title>Getting the Number of Selected Rows</title>
|
|
|
|
<para>
|
|
Sometimes you want to know the number of rows that are currently selected (for example to set
|
|
context menu entries active or inactive before you pop up a context menu). If you are using
|
|
selection mode <literal>GTK_SELECTION_SINGLE</literal> or <literal>GTK_SELECTION_BROWSE</literal>,
|
|
this is trivial to check with <literal>gtk_tree_selection_get_selected</literal>, which will return
|
|
either <literal>TRUE</literal> or <literal>FALSE</literal> (meaning one selected row or no selected row).
|
|
</para>
|
|
|
|
<para>
|
|
If you are using <literal>GTK_SELECTION_MULTIPLE</literal> or want a more general approach that works
|
|
for all selection modes, <literal>gtk_tree_selection_count_selected_rows</literal> will return the
|
|
information you are looking for. The only caveat with this function is that it only exists in Gtk+-2.2
|
|
and newer, so you will have to reimplement it if you want users with old installations that still use Gtk+-2.0
|
|
to be able to use your program as well. Here is a way to reimplement this function:
|
|
</para>
|
|
|
|
<programlisting role="C">
|
|
static void
|
|
count_foreach_helper (GtkTreeModel *model,
|
|
GtkTreePath *path,
|
|
GtkTreeIter *iter,
|
|
gpointer userdata)
|
|
{
|
|
gint *p_count = (gint*) userdata;
|
|
|
|
g_assert (p_count != NULL);
|
|
|
|
*p_count = *p_count + 1;
|
|
}
|
|
|
|
gint
|
|
my_tree_selection_count_selected_rows (GtkTreeSelection *selection)
|
|
{
|
|
gint count = 0;
|
|
|
|
gtk_tree_selection_selected_foreach(selection, count_foreach_helper, &count);
|
|
|
|
return count;
|
|
}
|
|
</programlisting>
|
|
|
|
</sect2>
|
|
|
|
</sect1>
|
|
|
|
<!-- ************************************************************** -->
|
|
<sect1 id="sec-selections-double-click">
|
|
<title>Double-Clicks on a Row</title>
|
|
|
|
<para>
|
|
Catching double-clicks on a row is quite easy and is done by connecting
|
|
to a tree view's <literal>"row-activated"</literal> signal, like this:
|
|
</para>
|
|
|
|
<programlisting role="C">
|
|
|
|
void
|
|
view_onRowActivated (GtkTreeView *treeview,
|
|
GtkTreePath *path,
|
|
GtkTreeViewColumn *col,
|
|
gpointer userdata)
|
|
{
|
|
GtkTreeModel *model;
|
|
GtkTreeIter iter;
|
|
|
|
g_print ("A row has been double-clicked!\n");
|
|
|
|
model = gtk_tree_view_get_model(treeview);
|
|
|
|
if (gtk_tree_model_get_iter(model, &iter, path))
|
|
{
|
|
gchar *name;
|
|
|
|
gtk_tree_model_get(model, &iter, COLUMN_NAME, &name, -1);
|
|
|
|
g_print ("Double-clicked row contains name %s\n", name);
|
|
|
|
g_free(name);
|
|
}
|
|
}
|
|
|
|
void
|
|
create_view (void)
|
|
{
|
|
GtkWidget *view;
|
|
|
|
view = gtk_tree_view_new();
|
|
|
|
...
|
|
|
|
g_signal_connect(view, "row-activated", (GCallback) view_onRowActivated, NULL);
|
|
|
|
...
|
|
}
|
|
|
|
|
|
</programlisting>
|
|
|
|
</sect1>
|
|
|
|
<!-- ************************************************************** -->
|
|
<sect1 id="sec-selections-context-menus">
|
|
<title>Context Menus on Right Click</title>
|
|
|
|
<para>
|
|
Context menus are context-dependent menus that pop up when a user right-clicks on a list or tree
|
|
and usually let the user do something with the selected items or manipulate the list or tree in other ways.
|
|
</para>
|
|
|
|
<para>
|
|
Right-clicks on a tree view are caught just like mouse button clicks are caught with any other widgets, namely
|
|
by connecting to the tree view's "button_press_event" signal handler (which is a GtkWidget signal, and as
|
|
GtkTreeView is derived from GtkWidget it has this signal as well). Additionally, you should also connect to
|
|
the "popup-menu" signal, so users can access your context menu without a mouse. The "popup-menu" signal is
|
|
emitted when the user presses Shift-F10. Also, you should make sure that all functions provided in your
|
|
context menu can also be accessed by other means such as the application's main menu. See the
|
|
<ulink url="http://developer.gnome.org/projects/gup/hig/">GNOME Human Interface Guidelines (HIG)</ulink> for
|
|
more details. Straight from the a-snippet-of-code-says-more-than-a-thousand-words-department, some
|
|
code to look at:
|
|
</para>
|
|
|
|
<programlisting role="C">
|
|
|
|
void
|
|
view_popup_menu_onDoSomething (GtkWidget *menuitem, gpointer userdata)
|
|
{
|
|
/* we passed the view as userdata when we connected the signal */
|
|
GtkTreeView *treeview = GTK_TREE_VIEW(userdata);
|
|
|
|
g_print ("Do something!\n");
|
|
}
|
|
|
|
|
|
void
|
|
view_popup_menu (GtkWidget *treeview, GdkEventButton *event, gpointer userdata)
|
|
{
|
|
GtkWidget *menu, *menuitem;
|
|
|
|
menu = gtk_menu_new();
|
|
|
|
menuitem = gtk_menu_item_new_with_label("Do something");
|
|
|
|
g_signal_connect(menuitem, "activate",
|
|
(GCallback) view_popup_menu_onDoSomething, treeview);
|
|
|
|
gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
|
|
|
|
gtk_widget_show_all(menu);
|
|
|
|
/* Note: event can be NULL here when called from view_onPopupMenu;
|
|
* gdk_event_get_time() accepts a NULL argument */
|
|
gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
|
|
(event != NULL) ? event->button : 0,
|
|
gdk_event_get_time((GdkEvent*)event));
|
|
}
|
|
|
|
|
|
gboolean
|
|
view_onButtonPressed (GtkWidget *treeview, GdkEventButton *event, gpointer userdata)
|
|
{
|
|
/* single click with the right mouse button? */
|
|
if (event->type == GDK_BUTTON_PRESS && event->button == 3)
|
|
{
|
|
g_print ("Single right click on the tree view.\n");
|
|
|
|
/* optional: select row if no row is selected or only
|
|
* one other row is selected (will only do something
|
|
* if you set a tree selection mode as described later
|
|
* in the tutorial) */
|
|
if (1)
|
|
{
|
|
GtkTreeSelection *selection;
|
|
|
|
selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));
|
|
|
|
/* Note: gtk_tree_selection_count_selected_rows() does not
|
|
* exist in gtk+-2.0, only in gtk+ >= v2.2 ! */
|
|
if (gtk_tree_selection_count_selected_rows(selection) <= 1)
|
|
{
|
|
GtkTreePath *path;
|
|
|
|
/* Get tree path for row that was clicked */
|
|
if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(treeview),
|
|
(gint) event->x,
|
|
(gint) event->y,
|
|
&path, NULL, NULL, NULL))
|
|
{
|
|
gtk_tree_selection_unselect_all(selection);
|
|
gtk_tree_selection_select_path(selection, path);
|
|
gtk_tree_path_free(path);
|
|
}
|
|
}
|
|
} /* end of optional bit */
|
|
|
|
view_popup_menu(treeview, event, userdata);
|
|
|
|
return TRUE; /* we handled this */
|
|
}
|
|
|
|
return FALSE; /* we did not handle this */
|
|
}
|
|
|
|
|
|
gboolean
|
|
view_onPopupMenu (GtkWidget *treeview, gpointer userdata)
|
|
{
|
|
view_popup_menu(treeview, NULL, userdata);
|
|
|
|
return TRUE; /* we handled this */
|
|
}
|
|
|
|
|
|
void
|
|
create_view (void)
|
|
{
|
|
GtkWidget *view;
|
|
|
|
view = gtk_tree_view_new();
|
|
|
|
...
|
|
|
|
g_signal_connect(view, "button-press-event", (GCallback) view_onButtonPressed, NULL);
|
|
g_signal_connect(view, "popup-menu", (GCallback) view_onPopupMenu, NULL);
|
|
|
|
...
|
|
}
|
|
|
|
</programlisting>
|
|
|
|
</sect1>
|
|
|
|
</chapter>
|
|
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
|
|
<chapter id="sec-sorting">
|
|
<title>Sorting</title>
|
|
|
|
<para>
|
|
Lists and trees are meant to be sorted. This is done using the
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeSortable.html">
|
|
<literal>GtkTreeSortable</literal></ulink> interface that can be implemented
|
|
by tree models. 'Interface' means that you can just cast a
|
|
<literal>GtkTreeModel</literal> into a <literal>GtkTreeSortable</literal> with
|
|
<literal>GTK_TREE_SORTABLE(model)</literal> and use the documented tree sortable
|
|
functions on it, just like we did before when we cast a list store to a tree model
|
|
and used the <literal>gtk_tree_model_foo</literal> family of functions.
|
|
Both <literal>GtkListStore</literal> and <literal>GtkTreeStore</literal>
|
|
implement the tree sortable interface.
|
|
</para>
|
|
|
|
<para>
|
|
The most straight forward way to sort a list store or tree store is to directly use the tree sortable
|
|
interface on them. This will sort the store in place, meaning that rows will actually be reordered in
|
|
the store if required. This has the advantage that the position of a row in the tree view will always
|
|
be the same as the position of a row in the model, in other words: a tree path refering to a row in
|
|
the view will always refer to the same row in the model, so you can get a row's iter easily with
|
|
<literal>gtk_tree_model_get_iter</literal> using a tree path supplied by the tree view.
|
|
This is not only convenient, but also sufficient for most scenarios.
|
|
</para>
|
|
|
|
<para>
|
|
However, there are cases when sorting a model in place is not desirable, for
|
|
example when several tree views display the same model with different sortings,
|
|
or when the unsorted state of the model has some special meaning and needs to
|
|
be restored at some point. This is where
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeModelSort.html">
|
|
<literal>GtkTreeModelSort</literal></ulink> comes in, which is a special model
|
|
that maps the unsorted rows of a child model (e.g. a list store or
|
|
tree store) into a sorted state without changing the child model.
|
|
</para>
|
|
|
|
<!-- ************************************************************** -->
|
|
<sect1 id="sec-sorting-tree-sortable">
|
|
<title>GtkTreeSortable</title>
|
|
|
|
<para>
|
|
The tree sortable interface is fairly simple and should be easy to use.
|
|
Basically you define a 'sort column ID' integer for every criterion you
|
|
might want to sort by and tell the tree sortable which function should be
|
|
called to compare two rows (represented by two tree iters) for every sort ID with
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeSortable.html#gtk-tree-sortable-set-sort-func">
|
|
<literal>gtk_tree_sortable_set_sort_func</literal></ulink>. Then you sort the
|
|
model by setting the sort column ID and sort order with
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeSortable.html#gtk-tree-sortable-set-column-id">
|
|
<literal>gtk_tree_sortable_set_sort_column_id</literal></ulink>, and the model
|
|
will be re-sorted using the compare function you have set up. Your sort column
|
|
IDs can correspond to your model columns, but they do not have to (you might
|
|
want to sort according to a criterion that is not directly represented by the
|
|
data in one single model column, for example). Some code to illustrate this:
|
|
</para>
|
|
|
|
<programlisting role="C">
|
|
|
|
enum
|
|
{
|
|
COL_NAME = 0,
|
|
COL_YEAR_BORN
|
|
};
|
|
|
|
|
|
enum
|
|
{
|
|
SORTID_NAME = 0,
|
|
SORTID_YEAR
|
|
};
|
|
|
|
|
|
GtkTreeModel *liststore = NULL;
|
|
|
|
|
|
void
|
|
toolbar_onSortByYear (void)
|
|
{
|
|
GtkTreeSortable *sortable;
|
|
GtkSortType order;
|
|
gint sortid;
|
|
|
|
sortable = GTK_TREE_SORTABLE(liststore);
|
|
|
|
/* If we are already sorting by year, reverse sort order,
|
|
* otherwise set it to year in ascending order */
|
|
|
|
if (gtk_tree_sortable_get_sort_column_id(sortable, &sortid, &order) == TRUE
|
|
&& sortid == SORTID_YEAR)
|
|
{
|
|
GtkSortType neworder;
|
|
|
|
neworder = (order == GTK_SORT_ASCENDING) ? GTK_SORT_DESCENDING : GTK_SORT_ASCENDING;
|
|
|
|
gtk_tree_sortable_set_sort_column_id(sortable, SORTID_YEAR, neworder);
|
|
}
|
|
else
|
|
{
|
|
gtk_tree_sortable_set_sort_column_id(sortable, SORTID_YEAR, GTK_SORT_ASCENDING);
|
|
}
|
|
}
|
|
|
|
|
|
/* This is not pretty. Of course you can also use a
|
|
* separate compare function for each sort ID value */
|
|
|
|
gint
|
|
sort_iter_compare_func (GtkTreeModel *model,
|
|
GtkTreeIter *a,
|
|
GtkTreeIter *b,
|
|
gpointer userdata)
|
|
{
|
|
gint sortcol = GPOINTER_TO_INT(userdata);
|
|
gint ret = 0;
|
|
|
|
switch (sortcol)
|
|
{
|
|
case SORTID_NAME:
|
|
{
|
|
gchar *name1, *name2;
|
|
|
|
gtk_tree_model_get(model, a, COL_NAME, &name1, -1);
|
|
gtk_tree_model_get(model, b, COL_NAME, &name2, -1);
|
|
|
|
if (name1 == NULL || name2 == NULL)
|
|
{
|
|
if (name1 == NULL && name2 == NULL)
|
|
break; /* both equal => ret = 0 */
|
|
|
|
ret = (name1 == NULL) ? -1 : 1;
|
|
}
|
|
else
|
|
{
|
|
ret = g_utf8_collate(name1,name2);
|
|
}
|
|
|
|
g_free(name1);
|
|
g_free(name2);
|
|
}
|
|
break;
|
|
|
|
case SORTID_YEAR:
|
|
{
|
|
guint year1, year2;
|
|
|
|
gtk_tree_model_get(model, a, COL_YEAR_BORN, &year1, -1);
|
|
gtk_tree_model_get(model, b, COL_YEAR_BORN, &year2, -1);
|
|
|
|
if (year1 != year2)
|
|
{
|
|
ret = (year1 > year2) ? 1 : -1;
|
|
}
|
|
/* else both equal => ret = 0 */
|
|
}
|
|
break;
|
|
|
|
default:
|
|
g_return_val_if_reached(0);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
void
|
|
create_list_and_view (void)
|
|
{
|
|
GtkTreeSortable *sortable;
|
|
|
|
...
|
|
|
|
liststore = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_UINT);
|
|
|
|
sortable = GTK_TREE_SORTABLE(liststore);
|
|
|
|
gtk_tree_sortable_set_sort_func(sortable, SORTID_NAME, sort_iter_compare_func,
|
|
GINT_TO_POINTER(SORTID_NAME), NULL);
|
|
|
|
gtk_tree_sortable_set_sort_func(sortable, SORTID_YEAR, sort_iter_compare_func,
|
|
GINT_TO_POINTER(SORTID_YEAR), NULL);
|
|
|
|
/* set initial sort order */
|
|
gtk_tree_sortable_set_sort_column_id(sortable, SORTID_NAME, GTK_SORT_ASCENDING);
|
|
|
|
...
|
|
|
|
view = gtk_tree_view_new_with_model(liststore);
|
|
|
|
...
|
|
|
|
}
|
|
|
|
</programlisting>
|
|
|
|
<para>
|
|
Usually things are a bit easier if you make use of the tree view column headers for sorting,
|
|
in which case you only need to assign sort column IDs and your compare functions, but do not
|
|
need to set the current sort column ID or order yourself (see <link linkend="sec-sorting-view-cols">below</link>).
|
|
</para>
|
|
|
|
<para>
|
|
Your tree iter compare function should return a negative value if the row specified by iter a comes before
|
|
the row specified by iter b, and a positive value if row b comes before row a. It should return 0 if both
|
|
rows are equal according to your sorting criterion (you might want to use a second sort criterion though
|
|
to avoid 'jumping' of equal rows when the store gets resorted). Your tree iter compare function should
|
|
not take the sort order into account, but assume an ascending sort order (otherwise bad things will happen).
|
|
</para>
|
|
|
|
</sect1>
|
|
|
|
<!-- ************************************************************** -->
|
|
<sect1 id="sec-sorting-model-sort">
|
|
<title>GtkTreeModelSort</title>
|
|
|
|
<para>
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeModelSort.html">
|
|
<literal>GtkTreeModelSort</literal></ulink> is a wrapper tree model.
|
|
It takes another tree model such as a list store or a tree store as
|
|
child model, and presents the child model to the 'outside' (ie. a tree
|
|
view or whoever else is accessing it via the tree model interface) in a
|
|
sorted state. It does that without changing the order of the rows in the
|
|
child model. This is useful if you want to display the same model in
|
|
different tree views with different sorting criteria for each tree view,
|
|
for example, or if you need to restore the original unsorted state of
|
|
your store again at some point.
|
|
</para>
|
|
|
|
<para>
|
|
<literal>GtkTreeModelSort</literal> implements the <literal>GtkTreeSortable</literal> interface, so you can treat
|
|
it just as if it was your data store for sorting purposes. Here is the basic setup with a tree view:
|
|
</para>
|
|
|
|
<programlisting role="C">
|
|
|
|
...
|
|
|
|
void
|
|
create_list_and_view (void)
|
|
{
|
|
...
|
|
|
|
liststore = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_UINT);
|
|
|
|
sortmodel = gtk_tree_model_sort_new_with_model(liststore);
|
|
|
|
gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(sortmodel), SORTID_NAME,
|
|
sort_func, GINT_TO_POINTER(SORTID_NAME), NULL);
|
|
|
|
gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(sortmodel), SORTID_YEAR,
|
|
sort_func, GINT_TO_POINTER(SORTID_YEAR), NULL);
|
|
|
|
/* set initial sort order */
|
|
gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(sortmodel),
|
|
SORTID_NAME, GTK_SORT_ASCENDING);
|
|
|
|
...
|
|
|
|
view = gtk_tree_view_new_with_model(sortmodel);
|
|
|
|
...
|
|
|
|
}
|
|
|
|
...
|
|
|
|
</programlisting>
|
|
|
|
<para>
|
|
However, when using the sort tree model, you need to be careful when
|
|
you use iters and paths with the model. This is because a path pointing
|
|
to a row in the view (and the sort tree model here) does probably not
|
|
point to the same row in the child model which is your original list
|
|
store or tree store, because the row order in the child model is
|
|
probably different from the sorted order. Similarly, an iter that
|
|
is valid for the sort tree model is not valid for the child model,
|
|
and vice versa. You can convert paths and iters from and to the child
|
|
model using
|
|
<literal>gtk_tree_model_sort_convert_child_path_to_path</literal>,
|
|
<literal>gtk_tree_model_sort_convert_child_iter_to_iter</literal>,
|
|
<literal>gtk_tree_model_sort_convert_path_to_child_path</literal>, and
|
|
<literal>gtk_tree_model_sort_convert_iter_to_child_iter</literal>.
|
|
You are unlikely to need these functions frequently though, as you can
|
|
still directly use <literal>gtk_tree_model_get</literal>
|
|
on the sort tree model with a path supplied by the tree view.
|
|
</para>
|
|
|
|
<para>
|
|
For the tree view, the sort tree model is the 'real' model - it knows
|
|
nothing about the sort tree model's child model at all, which means
|
|
that any path or iter that you get passed from the tree view in a callback
|
|
or otherwise will refer to the sort tree model, and that you need to pass
|
|
a path or iter refering to the sort tree model as well if you call tree
|
|
view functions.
|
|
</para>
|
|
|
|
</sect1>
|
|
|
|
|
|
<!-- ************************************************************** -->
|
|
<sect1 id="sec-sorting-view-cols">
|
|
<title>Sorting and Tree View Column Headers</title>
|
|
|
|
<para>
|
|
Unless you have hidden your tree view column headers or use custom tree
|
|
view column header widgets, each tree view column's header can be made
|
|
clickable. Clicking on a tree view column's header will then sort the list
|
|
according to the data in that column. You need to do two things to make this
|
|
happen: firstly, you need to tell your model which sort function to use for
|
|
which sort column ID with
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeSortable.html#gtk-tree-sortable-set-sort-func">
|
|
<literal>gtk_tree_sortable_set_sort_func</literal></ulink>.
|
|
Once you have done this, you tell each tree view column which sort column ID
|
|
should be active if this column's header is clicked. This is
|
|
done with <ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeViewColumn.html#gtk-tree-view-column-set-sort-column-id">
|
|
<literal>gtk_tree_view_column_set_sort_column_id</literal></ulink>.
|
|
</para>
|
|
|
|
<para>
|
|
And that is really all you need to do to get your list or tree sorted.
|
|
The tree view columns will automatically set the active sort column ID
|
|
and sort order for you if you click on a column header.
|
|
</para>
|
|
|
|
</sect1>
|
|
|
|
|
|
<!-- ************************************************************** -->
|
|
<sect1 id="sec-sorting-string-nocase">
|
|
<title>Case-insensitive String Comparing</title>
|
|
|
|
<para>
|
|
As described above in the <link linkend="sec-treeview-col-utf8-pango">"GtkCellRendererText, UTF8, and pango markup" section</link>,
|
|
all strings that are to be displayed in the tree view need to be encoded
|
|
in UTF8 encoding. All ASCII strings are valid UTF8, but as soon as non-ASCII
|
|
characters are used, things get a bit tricky and the character encoding matters.
|
|
</para>
|
|
|
|
<para>
|
|
Comparing two ASCII strings ignoring the case is trivial and can be done
|
|
using <literal>g_ascii_strcasecmp</literal>, for example. <literal>strcasecmp</literal>
|
|
will usually do the same, only that it is also locale-aware to some extent.
|
|
The only problem is that a lot of users use locale character encodings that
|
|
are not UTF8, so <literal>strcasecmp</literal> does not take us very far.
|
|
</para>
|
|
|
|
<para>
|
|
<literal>g_utf8_collate</literal> will compare two strings in UTF8 encoding,
|
|
but it does not ignore the case. In order to achieve at least half-way correct
|
|
linguistic case-insensitive sorting, we need to take a two-step approach.
|
|
For example, we could use <literal>g_utf8_casefold</literal> to convert the
|
|
strings to compare into a form that is independent of case, and then use
|
|
<literal>g_utf8_collate</literal> to compare those two strings (note that the
|
|
strings returned by <literal>g_utf8_casefold</literal> will not resemble the
|
|
original string in any recognisable way; they will work fine for comparisons
|
|
though). Alternatively, one could use <literal>g_utf8_strdown</literal>
|
|
on both strings and then compare the results again with
|
|
<literal>g_utf8_collate</literal>.
|
|
</para>
|
|
|
|
<para>
|
|
Obviously, all this is not going to be very fast, and adds up if you have a
|
|
lot of rows. To speed things up, you can create a 'collation key' with
|
|
<literal>g_utf8_collate_key</literal> and store that in your model as
|
|
well. A collation key is just a string that does not mean anything to
|
|
us, but can be used with <literal>strcmp</literal> for string comparison
|
|
purposes (which is a lot faster than <literal>g_utf8_collate</literal>).
|
|
</para>
|
|
|
|
<para>
|
|
It should be noted that the way <literal>g_utf8_collate</literal> sorts
|
|
is dependent on the current locale. Make sure you are not working in the
|
|
'C' locale (=default, none specified) before you are wondering about weird
|
|
sorting orders. Check with 'echo $LANG' on a command line what you current
|
|
locale is set to.
|
|
</para>
|
|
|
|
<para>
|
|
Check out the <ulink url="http://developer.gnome.org/doc/API/2.0/glib/glib-Unicode-Manipulation.html#g-utf8-casefold">
|
|
"Unicode Manipulation" section</ulink> in the GLib API Reference for more details.
|
|
</para>
|
|
|
|
</sect1>
|
|
|
|
</chapter>
|
|
|
|
|
|
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
|
|
<chapter id="sec-editable-cells">
|
|
<title>Editable Cells</title>
|
|
|
|
<sect1 id="sec-editable-cells-text">
|
|
<title>Editable Text Cells</title>
|
|
|
|
<para>
|
|
With <literal>GtkCellRendererText</literal> you can not only display
|
|
text, but you can also allow the user to edit a single cell's text
|
|
right in the tree view by double-clicking on a cell.
|
|
</para>
|
|
|
|
<para>
|
|
To make this work you need to tell the cell renderer that a cell is
|
|
editable, which you can do by setting the <literal>"editable"</literal>
|
|
property of the text cell renderer in question to <literal>TRUE</literal>.
|
|
You can either do this on a per-row basis (which allows you to set each
|
|
single cell either editable or not) by connecting the <literal>"editable"</literal>
|
|
property to a boolean type column in your tree model using attributes; or you
|
|
can just do a ...
|
|
</para>
|
|
|
|
<programlisting role="C">
|
|
g_object_set(renderer, "editable", TRUE, NULL);
|
|
</programlisting>
|
|
|
|
<para>
|
|
... when you create the renderer, which sets all rows in that particular
|
|
renderer column to be editable.
|
|
</para>
|
|
|
|
<para>
|
|
Now that our cells are editable, we also want to be notified when a cell has
|
|
been edited. This can be achieved by connecting to the cell renderer's
|
|
<literal>"edited"</literal> signal:
|
|
</para>
|
|
|
|
<programlisting role="C">
|
|
g_signal_connect(renderer, "edited", (GCallback) cell_edited_callback, NULL);
|
|
</programlisting>
|
|
|
|
<para>
|
|
This callback is then called whenever a cell has been edited. Instead of
|
|
<literal>NULL</literal> we could have passed a pointer to the model as
|
|
user data for convenience, as we probably want to store the new value in
|
|
the model.
|
|
</para>
|
|
|
|
<para>
|
|
The callback for the <literal>"edited"</literal> signal looks like this
|
|
(the API reference is a bit lacking in this particular case):
|
|
</para>
|
|
|
|
<programlisting role="C">
|
|
void cell_edited_callback (GtkCellRendererText *cell,
|
|
gchar *path_string,
|
|
gchar *new_text,
|
|
gpointer user_data);
|
|
</programlisting>
|
|
|
|
<para>
|
|
The tree path is passed to the <literal>"edited"</literal> signal callback
|
|
in string form. You can convert this into a <literal>GtkTreePath</literal> with
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeModel.html#gtk-tree-path-new-from-string">
|
|
<literal>gtk_tree_path_new_from_string</literal></ulink>, or convert it into an
|
|
iter with <ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeModel.html#gtk-tree-model-get-iter-from-string">
|
|
<literal>gtk_tree_model_get_iter_from_string</literal></ulink>.
|
|
</para>
|
|
|
|
<para>
|
|
Note that the cell renderer will not change the data for you in the store.
|
|
After a cell has been edited, you will only receive an <literal>"edited"</literal>
|
|
signal. If you do not change the data in the store, the old text will be rendered
|
|
again as if nothing had happened.
|
|
</para>
|
|
|
|
<para>
|
|
If you have multiple (renderer) columns with editable cells, it is not necessary
|
|
to have a different callback for each renderer, you can use the same callback
|
|
for all renderers, and attach some data to each renderer, which you can later
|
|
retrieve again in the callback to know which renderer/column has been edited.
|
|
This is done like this, for example:
|
|
</para>
|
|
|
|
<programlisting role="C">
|
|
renderer = gtk_cell_renderer_text_new();
|
|
...
|
|
g_object_set_data(G_OBJECT(renderer), "my_column_num", GUINT_TO_POINTER(COLUMN_NAME));
|
|
|
|
...
|
|
|
|
renderer = gtk_cell_renderer_text_new();
|
|
...
|
|
g_object_set_data(G_OBJECT(renderer), "my_column_num", GUINT_TO_POINTER(COLUMN_YEAR_OF_BIRTH));
|
|
|
|
...
|
|
</programlisting>
|
|
|
|
<para>
|
|
where COLUMN_NAME and COLUMN_YEAR_OF_BIRTH are enum values. In your callback you can
|
|
then get the column number with
|
|
</para>
|
|
|
|
<programlisting role="C">
|
|
guint column_number = GPOINTER_TO_UINT(g_object_get_data(G_OBJECT(renderer), "my_column_num"));
|
|
</programlisting>
|
|
|
|
<para>
|
|
You can use this mechanism to attach all kinds of custom data to any
|
|
object or widget, with a string identifier to your liking.
|
|
</para>
|
|
|
|
<para>
|
|
A good example for editable cells is in gtk-demo, which is part of the Gtk+
|
|
source code tree (in gtk+-2.x.y/demos/gtk-demo).
|
|
</para>
|
|
|
|
<sect2 id="sec-editable-cells-text-set">
|
|
<title>Setting the cursor to a specific cell</title>
|
|
|
|
<para>
|
|
You can move the cursor to a specific cell in a tree view with
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeView.html#gtk-tree-view-set-cursor">
|
|
<literal>gtk_tree_view_set_cursor</literal></ulink>
|
|
(or <ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeView.html#gtk-tree-view-set-cursor-on-cell">
|
|
<literal>gtk_tree_view_set_cursor_on_cell</literal></ulink> if you have
|
|
multiple editable cell renderers packed into one tree view column), and start editing
|
|
the cell if you want to. Similarly, you can get the current row and focus column with
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeView.html#gtk-tree-view-get-cursor">
|
|
<literal>gtk_tree_view_get_cursor</literal></ulink>.
|
|
Use <ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkWidget.html#gtk-widget-grab-focus">
|
|
<literal>gtk_widget_grab_focus(treeview)</literal></ulink> will make sure that the tree view
|
|
has the keyboard focus.
|
|
</para>
|
|
|
|
<para>
|
|
As the API reference points out, the tree view needs to be realised for cell editing to
|
|
happen. In other words: If you want to start editing a specific cell right at program
|
|
startup, you need to set up an idle timeout with
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/glib/glib-The-Main-Event-Loop.html#g-idle-add">
|
|
<literal>g_idle_add</literal></ulink> that does this for you as soon as the window and
|
|
everything else has been realised (return <literal>FALSE</literal> in the
|
|
timeout to make it run only once). Alternatively you could connect to the
|
|
<literal>"realize"</literal> signal of the treeview with
|
|
<literal>g_signal_connect_after</literal> to achieve the same thing.
|
|
</para>
|
|
|
|
<para>
|
|
Connect to the tree view's <literal>"cursor-changed"</literal> and/or
|
|
<literal>"move-cursor"</literal> signals to keep track of the current
|
|
position of the cursor.
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
</sect1>
|
|
|
|
<sect1 id="sec-editable-cells-toggle">
|
|
<title>Editable Toggle and Radio Button Cells</title>
|
|
|
|
<para>
|
|
Just like you can set a <literal>GtkCellRendererText</literal> editable,
|
|
you can specify whether a <literal>GtkCellRendererToggle</literal> should
|
|
change its state when clicked by setting the <literal>"activatable"</literal>
|
|
property - either when you create the renderer (in which case all cells in
|
|
that column will be clickable) or by connecting the renderer property to a
|
|
model column of boolean type via attributes.
|
|
</para>
|
|
|
|
<para>
|
|
Connect to the <literal>"toggled"</literal> signal of the toggle cell
|
|
renderer to be notified when the user clicks on a toggle button
|
|
(or radio button). The user click will not change the value in the
|
|
store, or the appearance of the value rendered. The toggle button will
|
|
only change state when you update the value in the store. Until then
|
|
it will be in an "inconsistent" state, which is also why you should
|
|
read the current value of that cell from the model, and not from the
|
|
cell renderer.
|
|
</para>
|
|
|
|
<para>
|
|
The callback for the <literal>"toggled"</literal> signal looks like this
|
|
(the API reference is a bit lacking in this particular case):
|
|
</para>
|
|
|
|
<programlisting role="C">
|
|
void cell_toggled_callback (GtkCellRendererToggle *cell,
|
|
gchar *path_string,
|
|
gpointer user_data);
|
|
</programlisting>
|
|
|
|
<para>
|
|
Just like with the <literal>"edited"</literal> signal of the text cell renderer,
|
|
the tree path is passed to the <literal>"toggled"</literal> signal callback
|
|
in string form. You can convert this into a <literal>GtkTreePath</literal> with
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeModel.html#gtk-tree-path-new-from-string">
|
|
<literal>gtk_tree_path_new_from_string</literal></ulink>, or convert it into an
|
|
iter with <ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeModel.html#gtk-tree-model-get-iter-from-string">
|
|
<literal>gtk_tree_model_get_iter_from_string</literal></ulink>.
|
|
</para>
|
|
|
|
</sect1>
|
|
|
|
<sect1 id="sec-editable-cells-spin">
|
|
<title>Editable Spin Button Cells</title>
|
|
|
|
<para>
|
|
Even though <literal>GtkSpinButton</literal> implements the
|
|
<literal>GtkCellEditable</literal> interface (as does
|
|
<literal>GtkEntry</literal>), there is no easy way to get
|
|
a cell renderer that uses a spin button instead of a normal
|
|
entry when in editing mode.
|
|
</para>
|
|
|
|
<para>
|
|
To get this functionality, you need to either write a new
|
|
cell renderer that works very similar to <literal>GtkCellRendererText</literal>,
|
|
or you need to write a new cell renderer class that derives from
|
|
the text cell renderer and changes the behaviour in editing mode.
|
|
</para>
|
|
|
|
<para>
|
|
The cleanest solution would probably be to write a 'CellRendererNumeric'
|
|
that does everything that the text cell renderer does, only that it
|
|
has a float type property instead of the <literal>"text"</literal>
|
|
property, and an additional digits property. However, no one seems
|
|
to have done this yet, so you need to either write one, or find
|
|
another solution to get spin buttons in editing mode.
|
|
</para>
|
|
|
|
<para>
|
|
Among this tutorial's code examples there is a hackish CellRendererSpin
|
|
implementation which is based on <literal>GtkCellRendererText</literal>
|
|
and shows spin buttons in editing mode. The implementation is not very
|
|
refined though, so you need to make sure it works in your particular
|
|
context, and modify it as needed.
|
|
</para>
|
|
|
|
</sect1>
|
|
|
|
</chapter>
|
|
|
|
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
|
|
<chapter id="sec-misc">
|
|
<title>Miscellaneous</title>
|
|
|
|
<para>
|
|
This section deals with issues and questions that did not seem to
|
|
fit in anywhere else. If you can think of something else that should
|
|
be dealt with here, do not hesitate to send a mail to
|
|
<email>tim at centricular dot net</email>.
|
|
</para>
|
|
|
|
<sect1 id="sec-misc-column-index">
|
|
<title>Getting the Column Number from a Tree View Column Widget</title>
|
|
|
|
<para>
|
|
Signal callbacks often only get passed a pointer to a
|
|
<literal>GtkTreeViewColumn</literal> when the application programmer
|
|
really just wants to know which column <emphasis>number</emphasis>
|
|
was affected. There are two ways to find out the position of a column
|
|
within the tree view. One way is to write a small helper function that
|
|
looks up the column number from a given tree view column object,
|
|
like this for example:
|
|
|
|
<footnote>
|
|
<para>This function has been inspired by
|
|
<ulink url="http://mail.gnome.org/archives/gtk-list/2003-July/msg00060.html">
|
|
this</ulink> mailing list message (thanks to Ken Rastatter for the link and
|
|
the topic suggestion).
|
|
</para>
|
|
</footnote>.
|
|
|
|
</para>
|
|
|
|
<programlisting role="C">
|
|
/* Returns column number or -1 if not found or on error */
|
|
|
|
gint
|
|
get_col_number_from_tree_view_column (GtkTreeViewColumn *col)
|
|
{
|
|
GList *cols;
|
|
gint num;
|
|
|
|
g_return_val_if_fail ( col != NULL, -1 );
|
|
g_return_val_if_fail ( col->tree_view != NULL, -1 );
|
|
|
|
cols = gtk_tree_view_get_columns(GTK_TREE_VIEW(col->tree_view));
|
|
|
|
num = g_list_index(cols, (gpointer) col);
|
|
|
|
g_list_free(cols);
|
|
|
|
return num;
|
|
}
|
|
</programlisting>
|
|
|
|
<para>
|
|
Alternatively, it is possible to use <literal>g_object_set_data</literal>
|
|
and <literal>g_object_get_data</literal> on the tree view column in order
|
|
to identify which column it is. This also has the advantage that you can
|
|
still keep track of your columns even if the columns get re-ordered within
|
|
the tree view (a feature which is usually disabled though). Use like this:
|
|
</para>
|
|
|
|
<programlisting role="C">
|
|
...
|
|
|
|
enum
|
|
{
|
|
COL_FIRSTNAME,
|
|
COL_SURNAME,
|
|
};
|
|
|
|
...
|
|
|
|
void
|
|
some_callback (GtkWidget *treeview, ..., GtkTreeViewColumn *col, ...)
|
|
{
|
|
guint colnum = GPOINTER_TO_UINT(g_object_get_data(G_OBJECT(col), "columnnum"));
|
|
|
|
...
|
|
}
|
|
|
|
void
|
|
create_view(void)
|
|
{
|
|
...
|
|
col = gtk_tree_view_column_new();
|
|
g_object_set_data(G_OBJECT(col), "columnnum", GUINT_TO_POINTER(COL_FIRSTNAME));
|
|
...
|
|
col = gtk_tree_view_column_new();
|
|
g_object_set_data(G_OBJECT(col), "columnnum", GUINT_TO_POINTER(COL_SURNAME));
|
|
...
|
|
}
|
|
</programlisting>
|
|
|
|
<para>
|
|
<literal>"columnnum"</literal> is a random string in the above
|
|
example - you can use whatever string you want instead, or store
|
|
multiple bits of data (with different string identifiers of course).
|
|
Of course you can also combine both approaches, as they do slightly
|
|
different things (the first tracks the 'physical' position of a
|
|
column within the tree view, the second tracks the 'meaning' of a
|
|
column to you, independent of its position within the view).
|
|
</para>
|
|
|
|
</sect1>
|
|
|
|
<sect1 id="sec-misc-expander-visibility">
|
|
<title>Column Expander Visibility</title>
|
|
|
|
<sect2 id="sec-misc-expander-visibility-hide">
|
|
<title>Hiding the Column Expander</title>
|
|
|
|
<para>
|
|
Is it possible to hide the column expander completely? Yes and no.
|
|
What follows, is probably a dirty hack at best and there is no
|
|
guarantee that it will work with upcoming Gtk+ versions or even with
|
|
all past versions (although the latter is easy enough to test of
|
|
course).
|
|
</para>
|
|
|
|
<para>
|
|
What you can do to hide the column expander is to create an empty
|
|
tree view column (containing empty strings, for example) and make
|
|
this the first column in the tree view. Then you can hide that
|
|
column with <ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeViewColumn.html#gtk-tree-view-column-set-visible">
|
|
<literal>gtk_tree_view_column_set_visible</literal></ulink>. You
|
|
will notice that the expander column will now automatically move
|
|
to the formerly second, now first, visible column in the tree view.
|
|
However, if you call <ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeView.html#gtk-tree-view-set-expander-column">
|
|
<literal>gtk_tree_view_set_expander_column</literal></ulink> right
|
|
after the call to <literal>_set_visible</literal>, then the expander
|
|
will move back to the hidden column, and no expander is visible
|
|
any longer.
|
|
</para>
|
|
|
|
<para>
|
|
This means of course that you will have to take care
|
|
of expanding and collapsing rows yourself and use the appropriate
|
|
tree view functions. While it is at last thinkable that one could
|
|
implement custom expanders using custom cell rendereres or pixbuf
|
|
cell renderers, this is probably a task that will keep you busy
|
|
for more than five minutes. Keep those head ache tablets nearby
|
|
if you attempt it anyway...
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="sec-misc-expander-visibility-force">
|
|
<title>Forcing Column Expander Visibility</title>
|
|
|
|
<para>
|
|
There are situations where an expander should be visible even if
|
|
the row in question does not have any children yet, for instance
|
|
when part of a model should only be loaded on request when a node
|
|
gets expanded (e.g. to show the contents of a directory). This
|
|
is not possible. An expander is only shown if a node has children.
|
|
</para>
|
|
|
|
<para>
|
|
A work-around for this problem exists however: simply attach an
|
|
empty child row and set the node to collapsed state. Then listen
|
|
for the tree view's <literal>"row-expanded"</literal> signal,
|
|
and fill the contents of the already existing row with the first
|
|
new row, then append new child rows. See
|
|
<ulink url="http://mail.gnome.org/archives/gtk-app-devel-list/2003-May/msg00241.html">
|
|
this mailing list thread</ulink> for more details.
|
|
</para>
|
|
|
|
</sect2>
|
|
</sect1>
|
|
|
|
<sect1 id="sec-misc-get-renderer-from-click">
|
|
<title>Getting the Cell Renderer a Click Event Happened On</title>
|
|
|
|
<para>
|
|
It seems that in many cases when people want to know the cell
|
|
renderer a click event happened on, they do not really need to
|
|
know the cell renderer, but rather want to modify an individual
|
|
cell in a particular column. For this you do not need to know
|
|
the cell renderer. Use <literal>
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeView.html#gtk-tree-view-get-path-at-pos">
|
|
gtk_tree_view_get_path_at_pos</ulink></literal> to get a tree
|
|
path from the x and y coordinates of the button event that is
|
|
passed to you in a <literal>"button-press-event"</literal> signal
|
|
callback (if you use the <literal>"row-activated"</literal> signal
|
|
to catch double-clicks you get the tree path passed directly into
|
|
the callback function). Then convert that tree path into an iter
|
|
using <literal><ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeModel.html#gtk-tree-model-get-iter">
|
|
gtk_tree_model_get_iter</ulink></literal> and modify the data in
|
|
the cell you want to modify with <literal>gtk_list_store_set</literal>
|
|
or <literal>gtk_tree_store_set</literal>.
|
|
</para>
|
|
|
|
<para>
|
|
If you really do need to know the cell renderer where a button press
|
|
event happened, that is a bit more tricky. Here is a suggestion on
|
|
how to approach this issue (the function has not been well-tested and
|
|
might not work correctly if the content rendered by one renderer in
|
|
different columns varies in width; please send suggestions on how
|
|
to fix or improve this function to the author):
|
|
</para>
|
|
|
|
<programlisting role="C">
|
|
static gboolean
|
|
tree_view_get_cell_from_pos(GtkTreeView *view, guint x, guint y, GtkCellRenderer **cell)
|
|
{
|
|
GtkTreeViewColumn *col = NULL;
|
|
GList *node, *columns, *cells;
|
|
guint colx = 0;
|
|
|
|
g_return_val_if_fail ( view != NULL, FALSE );
|
|
g_return_val_if_fail ( cell != NULL, FALSE );
|
|
|
|
/* (1) find column and column x relative to tree view coordinates */
|
|
|
|
columns = gtk_tree_view_get_columns(view);
|
|
|
|
for (node = columns; node != NULL && col == NULL; node = node->next)
|
|
{
|
|
GtkTreeViewColumn *checkcol = (GtkTreeViewColumn*) node->data;
|
|
|
|
if (x >= colx && x < (colx + checkcol->width))
|
|
col = checkcol;
|
|
else
|
|
colx += checkcol->width;
|
|
}
|
|
|
|
g_list_free(columns);
|
|
|
|
if (col == NULL)
|
|
return FALSE; /* not found */
|
|
|
|
/* (2) find the cell renderer within the column */
|
|
|
|
cells = gtk_tree_view_column_get_cell_renderers(col);
|
|
|
|
for (node = cells; node != NULL; node = node->next)
|
|
{
|
|
GtkCellRenderer *checkcell = (GtkCellRenderer*) node->data;
|
|
guint width = 0, height = 0;
|
|
|
|
/* Will this work for all packing modes? doesn't that
|
|
* return a random width depending on the last content
|
|
* rendered? */
|
|
gtk_cell_renderer_get_size(checkcell, GTK_WIDGET(view), NULL, NULL, NULL, &width, NULL);
|
|
|
|
if (x >= colx && x < (colx + width))
|
|
{
|
|
*cell = checkcell;
|
|
g_list_free(cells);
|
|
return TRUE;
|
|
}
|
|
|
|
colx += width;
|
|
}
|
|
|
|
g_list_free(cells);
|
|
return FALSE; /* not found */
|
|
}
|
|
|
|
static gboolean
|
|
onButtonPress (GtkWidget *view, GdkEventButton *bevent, gpointer data)
|
|
{
|
|
GtkCellRenderer *renderer = NULL;
|
|
|
|
if (tree_view_get_cell_from_pos(GTK_TREE_VIEW(view), bevent->x, bevent->y, &renderer))
|
|
g_print ("Renderer found\n");
|
|
else
|
|
g_print ("Renderer not found!\n");
|
|
}
|
|
|
|
</programlisting>
|
|
|
|
</sect1>
|
|
|
|
|
|
<!-- ************************************************************** -->
|
|
|
|
|
|
<sect1 id="sec-misc-glade">
|
|
<title>Glade and Tree Views</title>
|
|
|
|
<para>
|
|
A frequently asked question is how you can add columns to a <literal>GtkTreeView</literal>
|
|
in <ulink url="http://glade.gnome.org">Glade</ulink>.
|
|
<footnote>
|
|
<para>
|
|
Do <emphasis>not</emphasis> use Glade to generate code for you. Use Glade to create
|
|
the interface. It will save the interface into a .glade file in XML format. You can
|
|
then use libglade2 to construct your interface (windows etc.) from that .glade file.
|
|
See <ulink url="http://lists.ximian.com/archives/public/glade-devel/2003-February/000015.html">
|
|
this mailing list message for a short discussion about why you should avoid Glade
|
|
code generation</ulink>.
|
|
</para>
|
|
</footnote>
|
|
The answer is basically that you don't, and that you can't. The only thing glade/libglade
|
|
can do for you is to create the <literal>GtkTreeView</literal> for you with nothing in it.
|
|
You will need to look up the tree view widget at the start of your application (after the
|
|
interface has been created of course), and connect your list store or tree store to it.
|
|
Then you will need to add <literal>GtkTreeViewColumn</literal>s and cell renderers to display
|
|
the information from the model as you want it to be displayed. You will need to do all
|
|
that from within your application.
|
|
</para>
|
|
|
|
<para>
|
|
An alternative approach is to derive your own special widget from <literal>GtkTreeView</literal>
|
|
that sets up everything as you want it to, and then use the 'custom widget' function in glade.
|
|
Of course this still means that you have to write all the code to fill in the columns and
|
|
cell renderers and to create the model yourself.
|
|
</para>
|
|
|
|
</sect1>
|
|
|
|
</chapter>
|
|
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
|
|
<chapter id="sec-dnd">
|
|
<title>Drag'n'Drop (DnD) **** needs revision ***</title>
|
|
|
|
<para>
|
|
****** NEEDS REVISION
|
|
</para>
|
|
|
|
<para>
|
|
This section needs revision more than any other section. If you
|
|
know anything about tree view drag'n'drop, you probably know more
|
|
than the author of this text. Please give some feedback in that case.
|
|
</para>
|
|
|
|
<para>
|
|
If you want to dive into treeview drag'n'drop, you might want to
|
|
check out <ulink url="http://mail.gnome.org/archives/gtk-devel-list/2001-November/msg00018.html">
|
|
Owen Taylor's mail on that topic</ulink>. It might not be completely
|
|
identical to what has actually been implemented, but it gives a great
|
|
overview, and provides more information than the docs do.
|
|
</para>
|
|
|
|
<para>
|
|
In addition to the standard Gtk+ Drag and Drop mechanisms that work
|
|
with any widget, there are special Drag and Drop mechanisms just for
|
|
the tree view widget. You usually want to use the tree-view specific
|
|
Drag-and-Drop framework.
|
|
</para>
|
|
|
|
<!-- ************************************************************** -->
|
|
<sect1 id="sec-dnd-selected-item-info">
|
|
<title>Drag'n'Dropping Row-Unrelated Data to and from a Tree View from other Windows or Widgets</title>
|
|
|
|
<para>
|
|
Drag'n'Dropping general information from or to a tree view widget works
|
|
just like it works with any other widget and involves the standard
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/gtk-Drag-and-Drop.html">
|
|
Gtk+ Drag and Drop</ulink> mechanisms. If you use this, you can
|
|
receive drops to or initiate drags from anywhere in your tree view
|
|
(including empty sections). This is not row- or column-specific and
|
|
<emphasis>is most likely not want you want</emphasis>.
|
|
Nevertheless, here is a small example
|
|
of a tree view in which you can drag'n'drop URIs from other
|
|
applications (browsers, for example), with the dropped URIs just
|
|
being appended to the list (note that usually you would probably
|
|
rather want to set up your whole window as a target then and not
|
|
just the tree view widget):
|
|
</para>
|
|
|
|
<programlisting role="C">
|
|
#include <gtk/gtk.h>
|
|
|
|
enum
|
|
{
|
|
COL_URI = 0,
|
|
NUM_COLS
|
|
} ;
|
|
|
|
|
|
void
|
|
view_onDragDataReceived(GtkWidget *wgt, GdkDragContext *context, int x, int y,
|
|
GtkSelectionData *seldata, guint info, guint time,
|
|
gpointer userdata)
|
|
{
|
|
GtkTreeModel *model;
|
|
GtkTreeIter iter;
|
|
|
|
model = GTK_TREE_MODEL(userdata);
|
|
|
|
gtk_list_store_append(GTK_LIST_STORE(model), &iter);
|
|
|
|
gtk_list_store_set(GTK_LIST_STORE(model), &iter, COL_URI, (gchar*)seldata->data, -1);
|
|
}
|
|
|
|
static GtkWidget *
|
|
create_view_and_model (void)
|
|
{
|
|
GtkTreeViewColumn *col;
|
|
GtkCellRenderer *renderer;
|
|
GtkListStore *liststore;
|
|
GtkWidget *view;
|
|
|
|
liststore = gtk_list_store_new(NUM_COLS, G_TYPE_STRING);
|
|
|
|
view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(liststore));
|
|
|
|
g_object_unref(liststore); /* destroy model with view */
|
|
|
|
col = gtk_tree_view_column_new();
|
|
renderer = gtk_cell_renderer_text_new();
|
|
|
|
gtk_tree_view_column_set_title(col, "URI");
|
|
gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);
|
|
gtk_tree_view_column_pack_start(col, renderer, TRUE);
|
|
gtk_tree_view_column_add_attribute(col, renderer, "text", COL_URI);
|
|
|
|
gtk_tree_selection_set_mode(gtk_tree_view_get_selection(GTK_TREE_VIEW(view)),
|
|
GTK_SELECTION_SINGLE);
|
|
|
|
/* Make tree view a destination for Drag'n'Drop */
|
|
if (1)
|
|
{
|
|
enum
|
|
{
|
|
TARGET_STRING,
|
|
TARGET_URL
|
|
};
|
|
|
|
static GtkTargetEntry targetentries[] =
|
|
{
|
|
{ "STRING", 0, TARGET_STRING },
|
|
{ "text/plain", 0, TARGET_STRING },
|
|
{ "text/uri-list", 0, TARGET_URL },
|
|
};
|
|
|
|
gtk_drag_dest_set(view, GTK_DEST_DEFAULT_ALL, targetentries, 3,
|
|
GDK_ACTION_COPY|GDK_ACTION_MOVE|GDK_ACTION_LINK);
|
|
|
|
g_signal_connect(view, "drag_data_received",
|
|
G_CALLBACK(view_onDragDataReceived), liststore);
|
|
}
|
|
|
|
return view;
|
|
}
|
|
|
|
int
|
|
main (int argc, char **argv)
|
|
{
|
|
GtkWidget *window, *vbox, *view, *label;
|
|
|
|
gtk_init(&argc, &argv);
|
|
|
|
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
|
|
g_signal_connect(window, "delete_event", gtk_main_quit, NULL); /* dirty */
|
|
gtk_window_set_default_size(GTK_WINDOW(window), 400, 200);
|
|
|
|
vbox = gtk_vbox_new(FALSE, 0);
|
|
gtk_container_add(GTK_CONTAINER(window), vbox);
|
|
|
|
label = gtk_label_new("\nDrag and drop links from your browser into the tree view.\n");
|
|
gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
|
|
|
|
view = create_view_and_model();
|
|
gtk_box_pack_start(GTK_BOX(vbox), view, TRUE, TRUE, 0);
|
|
|
|
gtk_widget_show_all(window);
|
|
|
|
gtk_main();
|
|
|
|
return 0;
|
|
}
|
|
</programlisting>
|
|
|
|
<para>
|
|
If you are receiving drops into a tree view, you can connect to the view's
|
|
<literal>"drag-motion"</literal> signal to track the mouse pointer while
|
|
it is in a drag and drop operation over the tree view. This is useful for
|
|
example if you want to expand a collapsed node in a tree when the mouse
|
|
hovers above the node for a certain amount of time during a drag'n'drop
|
|
operation. Here is an example of how to achieve this:
|
|
</para>
|
|
|
|
<programlisting role="C">
|
|
/***************************************************************************
|
|
*
|
|
* onDragMotion_expand_timeout
|
|
*
|
|
* Timeout used to make sure that we expand rows only
|
|
* after hovering about them for a certain amount
|
|
* of time while doing Drag'n'Drop
|
|
*
|
|
***************************************************************************/
|
|
|
|
gboolean
|
|
onDragMotion_expand_timeout (GtkTreePath **path)
|
|
{
|
|
g_return_val_if_fail ( path != NULL, FALSE );
|
|
g_return_val_if_fail ( *path != NULL, FALSE );
|
|
|
|
gtk_tree_view_expand_row(GTK_TREE_VIEW(view), *path, FALSE);
|
|
|
|
return FALSE; /* only call once */
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
*
|
|
* view_onDragMotion: we don't want to expand unexpanded nodes
|
|
* immediately when the mouse pointer passes across
|
|
* them during DnD. Instead, we only want to expand
|
|
* the node if the pointer has been hovering above the
|
|
* node for at least 1.5 seconds or so. To achieve this,
|
|
* we use a timeout that is removed whenever the row
|
|
* in focus changes.
|
|
*
|
|
***************************************************************************/
|
|
|
|
static gboolean
|
|
view_onDragMotion (GtkWidget *widget, GdkDragContext *context, gint x,
|
|
gint y, guint time, gpointer data)
|
|
{
|
|
static GtkTreePath *lastpath; /* NULL */
|
|
GtkTreePath *path = NULL;
|
|
|
|
if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget), x, y, &path, NULL, NULL, NULL))
|
|
{
|
|
if (!lastpath || ((lastpath) && gtk_tree_path_compare(lastpath, path) != 0))
|
|
{
|
|
(void) g_source_remove_by_user_data(&lastpath);
|
|
|
|
if (!gtk_tree_view_row_expanded(GTK_TREE_VIEW(widget), path))
|
|
{
|
|
/* 1500 = 1.5 secs */
|
|
g_timeout_add(1500, (GSourceFunc) onDragMotion_expand_timeout, &lastpath);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
g_source_remove_by_user_data(&lastpath);
|
|
}
|
|
|
|
if (lastpath)
|
|
gtk_tree_path_free(lastpath);
|
|
|
|
lastpath = path;
|
|
|
|
return TRUE;
|
|
}
|
|
</programlisting>
|
|
|
|
|
|
<para>
|
|
Connect to the view's <literal>"drag-drop"</literal> signal to be called
|
|
when the drop happens. You can translate the coordinates provided into
|
|
a tree path with <ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeView.html#gtk-tree-view-get-path-at-pos">
|
|
<literal>gtk_tree_view_get_path_at_pos</literal></ulink>.
|
|
</para>
|
|
|
|
</sect1>
|
|
|
|
<!-- ************************************************************** -->
|
|
<sect1 id="sec-dnd-rows-within-tree">
|
|
<title>Dragging Rows Around Within a Tree **** TODO ***</title>
|
|
|
|
<para>
|
|
****** TODO
|
|
</para>
|
|
|
|
<para>
|
|
Both <literal>GtkListStore</literal> and <literal>GtkTreeStore</literal>
|
|
implement the <literal>GtkTreeDragDest</literal> and <literal>GtkTreeDragSource</literal>
|
|
interfaces, which means that they have in-built support for row reordering. You need to
|
|
call <literal>gtk_tree_view_set_reorderable</literal> to activate this, and then connect
|
|
to the tree model's signals to catch the reorderings that take place.
|
|
</para>
|
|
|
|
<para>
|
|
*** SOMEONE NEEDS TO WRITE THIS SECTION (I have never gotten this to work
|
|
in a way that does not suck, ie. where one does not have to place the row
|
|
to move exact to the pixel on the target row).
|
|
</para>
|
|
|
|
</sect1>
|
|
|
|
<!-- ************************************************************** -->
|
|
<sect1 id="sec-dnd-rows-between-trees">
|
|
<title>Dragging Rows from One Tree to Another **** TODO ***</title>
|
|
|
|
<para>
|
|
****** TODO (is this possible at all in Gtk+ <= 2.2?)
|
|
</para>
|
|
|
|
</sect1>
|
|
|
|
</chapter>
|
|
|
|
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
<chapter id="sec-custom-models">
|
|
<title>Writing Custom Models</title>
|
|
|
|
<!-- ************************************************************** -->
|
|
<sect1 id="sec-custom-models-when">
|
|
<title>When is a Custom Model Useful?</title>
|
|
|
|
<para>
|
|
A custom tree model gives you complete control over your data and how it is
|
|
represented to the outside (e.g. to the tree view widget). It has the advantage
|
|
that you can store, access and modify your data exactly how you need it, and
|
|
you can optimise the way your data is stored and retrieved, as you can write
|
|
your own functions to access your data and need not rely solely on the
|
|
<literal>gtk_tree_model_get</literal>. A model tailored to your needs will probably
|
|
also be a lot faster than the generic list and tree stores that come with gtk
|
|
and that have been designed with flexibility in mind.
|
|
</para>
|
|
|
|
<para>
|
|
Another case where a custom model might come in handy is when you have all your
|
|
data already stored in an external tree-like structure (for example a libxml2 XML
|
|
tree) and only want to display that structure. Then you could write a custom model
|
|
that maps that structure to a tree model (which is probably not quite as trivial
|
|
as it sounds though).
|
|
</para>
|
|
|
|
<para>
|
|
Using a custom model you could also implement a filter model that only displays
|
|
certain rows according to some filter criterion instead of displaying all rows
|
|
(Gtk+-2.4 has a filter model, <literal>
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeModelFilter.html">
|
|
GtkTreeModelFilter</ulink></literal>, that
|
|
does exactly that and much more, but you might want to implement this yourself
|
|
anyway. If you need to use GtkTreeModelFilter in Gtk-2.0 or Gtk-2.2,
|
|
check out the code examples of this tutorial - there is GuiTreeModelFilter,
|
|
which is basically just the original GtkTreeModelFilter but has been made
|
|
to work with earlier Gtk-2.x versions and has a different name space, so that
|
|
it does not clash with Gtk-2.4).
|
|
</para>
|
|
|
|
<para>
|
|
However, all this comes at a cost: you are unlikely to write a useful custom model
|
|
in less than a thousand lines, unless you strip all newline characters. Writing a
|
|
custom model is not as difficult as it might sound though, and it may well be worth the
|
|
effort, not least because it will result in much saner code if you have a lot of
|
|
data to keep track of.
|
|
</para>
|
|
|
|
</sect1>
|
|
|
|
<!-- ************************************************************** -->
|
|
<sect1 id="sec-custom-models-what">
|
|
<title>What Does Writing a Custom Model Involve?</title>
|
|
|
|
<para>
|
|
Basically, all you need to do is to write a new GObject that implements the
|
|
<literal>GtkTreeModel</literal> interface, <literal>GtkTreeModelIface</literal>.
|
|
Intimate knowledge about the GLib GObject system is not a requirement - you
|
|
just need to copy some boilerplate code and modify it a bit. The core of your
|
|
custom tree model is your own implementation of a couple of
|
|
<literal>gtk_tree_model_foo</literal> functions that reveal the structure of your
|
|
data, ie. how many rows there are, how many
|
|
children a row has, how many columns there are and what type of data they contain.
|
|
Furthermore, you need to provide functions that convert a tree path to a tree iter
|
|
and a tree iter to a tree path. Additionally, you should provide some functions to
|
|
add and remove rows to your custom model, but those are only ever used by yourself
|
|
anyway, so they do not fall within the scope of the tree model interface.
|
|
</para>
|
|
|
|
<para>
|
|
The functions you <emphasis>need</emphasis> to implement are:
|
|
</para>
|
|
|
|
<itemizedlist>
|
|
|
|
<listitem>
|
|
<para>
|
|
<literal>get_flags</literal> - tells the outside that your model has certain special characterstics, like persistent iters.
|
|
</para>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para>
|
|
<literal>get_n_columns</literal> - how many data fields per row are visible to the outside that uses gtk_tree_model_get, e.g. cell renderer attributes
|
|
</para>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para>
|
|
<literal>get_column_type</literal> - what type of data is stored in a data field (model column) that is visible to the outside
|
|
</para>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para>
|
|
<literal>get_iter</literal> - take a tree path and fill an iter structure so that you know which row it refers to
|
|
</para>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para>
|
|
<literal>get_path</literal> - take an iter and convert it into a tree path, ie. the 'physical' position within the model
|
|
</para>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para>
|
|
<literal>get_value</literal> - retrieve data from a row
|
|
</para>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para>
|
|
<literal>iter_next</literal> - take an iter structure and make it point to the next row
|
|
</para>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para>
|
|
<literal>iter_children</literal> - tell whether the row represented by a given iter has any children or not
|
|
</para>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para>
|
|
<literal>iter_n_children</literal> - tell how many children a row represented by a given iter has
|
|
</para>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para>
|
|
<literal>iter_nth_child</literal> - set a given iter structure to the n-th child of a given parent iter
|
|
</para>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para>
|
|
<literal>iter_parent</literal> - set a given iter structure to the parent of a given child iter
|
|
</para>
|
|
</listitem>
|
|
|
|
</itemizedlist>
|
|
|
|
<para>
|
|
It is up to you to decide which of your data you make 'visible' to the outside in form of model columns
|
|
and which not. You can always implement functions specific to your custom model that will return any data
|
|
in any form you desire. You only <emphasis>need</emphasis> to make data 'visble' to the outside via the GType and GValue system
|
|
if you want the tree view components to access it (e.g. when setting cell renderer attributes).
|
|
</para>
|
|
|
|
</sect1>
|
|
|
|
<!-- ************************************************************** -->
|
|
<sect1 id="sec-custom-model-list">
|
|
<title>Example: A Simple Custom List Model</title>
|
|
|
|
<para>
|
|
What follows is the outline for a simple custom list model. You can find the complete source
|
|
code for this model <link linkend="sec-custom-model-code">below</link>. The beginning
|
|
of the code might look a bit scary, but you can just skip most of the GObject and GType
|
|
stuff and proceed to the heart of the custom list, ie. the implementation of the
|
|
tree model functions.
|
|
</para>
|
|
|
|
<para>
|
|
Our list model is represented by a simple list of records, where each row corresponds to a
|
|
<literal>CustomRecord</literal> structure which keeps track of the data we are interested in.
|
|
For now, we only want to keep track of persons' names and years of birth (usually this would not really
|
|
justify a custom model, but this is still just an example). It is trivial to extend
|
|
the model to deal with additional fields in the <literal>CustomRecord</literal> structure.
|
|
</para>
|
|
|
|
<para>
|
|
Within the model, more precisely: the <literal>CustomList</literal> structure, the list is
|
|
stored as a pointer array, which not only provides fast access to the n-th record in the list,
|
|
but also comes in handy later on when we add sorting. Apart from that, any other kind of
|
|
list-specific data would go in this structure as well (the active sort column, for example, or
|
|
hash tables to speed up searching for a specific row, etc.).
|
|
</para>
|
|
|
|
<para>
|
|
Each row in our list is represented by a <literal>CustomRecord</literal> structure. You can
|
|
store whatever other data you need in that structure. How you make row data available is up
|
|
to you. Either you export it via the tree model interface using the
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gobject/gobject-Standard-Parameter-and-Value-Types.html">GValue system</ulink>,
|
|
so that you can use <literal>gtk_tree_model_get</literal> to retrieve your data, or you
|
|
provide custom model-specific functions to retrieve data, for example <literal>custom_list_get_name</literal>,
|
|
taking a tree iter or a tree path as argument. Of course you can also do both.
|
|
</para>
|
|
|
|
<para>
|
|
Furthermore, you will need to provide your own functions to add rows, remove rows, and
|
|
set or modify row data, and you need to let the view and others know whenever something
|
|
changes in your model by emitting the appropriate signals via the provided tree model
|
|
functions.
|
|
</para>
|
|
|
|
<para>
|
|
Some thought should go into how exactly you fill the
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeModel.html#GtkTreeIter">
|
|
<literal>GtkTreeIter</literal> fields</ulink> of the tree iters used by
|
|
your model. You have three pointer fields at your disposal. These should be filled
|
|
so that you can easily identify the row given the iter, and should also facilitate
|
|
access to the next row and the parent row (if any). If your model advertises to
|
|
have persistent iters, you need to make sure that the content of your iters is
|
|
perfectly valid even if the user stores it somewhere for later use and the model
|
|
gets changed or reordered. The 'stamp' field of a tree iter should be filled by
|
|
a random model-instance-specific integer that was assigned to the model when it
|
|
was created. This way you can catch iters that do not belong to your model.
|
|
If your model does not have persistent iters, then you should change the model's
|
|
stamp whenever the model changes, so that you can catch invalid iters that get
|
|
passed to your functions (note: in the code below we do not check the stamp
|
|
of the iters in order to save a couple of lines of code to print here).
|
|
</para>
|
|
|
|
<para>
|
|
In our specific example, we simply store a pointer to a row's
|
|
<literal>CustomRecord</literal> structure in our model's tree iters,
|
|
which is valid as long as the row exists. Additionally we store the
|
|
position of a row within the list in the <literal>CustomRecord</literal>
|
|
as well, which is not only intuitive, but is also useful later on
|
|
when we resort the list.
|
|
</para>
|
|
|
|
<para>
|
|
If you want to store an integer value in an iter's fields, you should
|
|
use GLib's <literal>GINT_TO_POINTER</literal> and
|
|
<literal>GPOINTER_TO_INT</literal> macros for that.
|
|
</para>
|
|
|
|
<para>
|
|
Let's look at the code sections in a bit more detail:
|
|
</para>
|
|
|
|
<!-- ************************************************************** -->
|
|
<sect2 id="sec-custom-model-list-header">
|
|
<title>custom-list.h</title>
|
|
|
|
<para>
|
|
The <link linkend="sec-custom-model-code-header">header file</link> for our
|
|
custom list model defines some standard type casts and type check macros,
|
|
our <literal>CustomRecord</literal> structure, our <literal>CustomList</literal>
|
|
structure, and some enums for the model columns we are exporting.
|
|
</para>
|
|
|
|
<para>The <literal>CustomRecord</literal> structure represents one row, while
|
|
the <literal>CustomList</literal> structure contains all list-specific data.
|
|
You can add additional fields to both structures without problems. For example,
|
|
you might need a function that quickly looks up rows given the name or year
|
|
of birth, for which additional hashtables or so might come in handy
|
|
(which you would need to keep up to date as you insert, modify or remove
|
|
rows of course).
|
|
</para>
|
|
|
|
<para>
|
|
The only function you must export is <literal>custom_list_get_type</literal>,
|
|
as it is used by the type check and type cast macros that are also defined
|
|
in the header file. Additionally, we want to export a function to create one
|
|
instance of our custom model, and a function that adds some rows. You will
|
|
probably add more custom model-specific functions to modify the model as you
|
|
extend it to suit your needs.
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
<!-- ************************************************************** -->
|
|
<sect2 id="sec-custom-model-list-body">
|
|
<title>custom-list.c</title>
|
|
|
|
<para>
|
|
Firstly, we need some boilerplate code to register our custom model
|
|
with the GObject type system. You can skip this section and proceed to the
|
|
tree model implementation.
|
|
</para>
|
|
|
|
<para>
|
|
Functions of interested in this section are <literal>custom_list_init</literal> and
|
|
<literal>custom_list_get_type</literal>. In <literal>custom_list_init</literal> we
|
|
define what data type our exported model columns have, and how many columns we export.
|
|
Towards the end of <literal>custom_list_get_type</literal> we register the
|
|
<literal>GtkTreeModel</literal> interface with our custom model object. This is where
|
|
we can also register additional interfaces (e.g. <literal>GtkTreeSortable</literal>
|
|
or one of the Drag'n'Drop interfaces) that we want to implement.
|
|
</para>
|
|
|
|
<para>
|
|
In <literal>custom_list_tree_model_init</literal> we override those tree model functions
|
|
that we need to implement with our own functions. If it is beneficial for your model to
|
|
know which rows are currently displayed in the tree view (for example for caching), you
|
|
might want to override the <literal>ref_node</literal> and <literal>unref_node</literal>
|
|
functions as well.
|
|
</para>
|
|
|
|
<para>
|
|
Let's have a look at the heart of the object type registration:
|
|
</para>
|
|
|
|
<programlisting role="C">
|
|
GType
|
|
custom_list_get_type (void)
|
|
{
|
|
static GType custom_list_type = 0;
|
|
|
|
if (custom_list_type)
|
|
return custom_list_type;
|
|
|
|
/* Some boilerplate type registration stuff */
|
|
if (1)
|
|
{
|
|
static const GTypeInfo custom_list_info =
|
|
{
|
|
sizeof (CustomListClass),
|
|
NULL, /* base_init */
|
|
NULL, /* base_finalize */
|
|
(GClassInitFunc) custom_list_class_init,
|
|
NULL, /* class finalize */
|
|
NULL, /* class_data */
|
|
sizeof (CustomList),
|
|
0, /* n_preallocs */
|
|
(GInstanceInitFunc) custom_list_init
|
|
};
|
|
|
|
custom_list_type = g_type_register_static (G_TYPE_OBJECT, "CustomList",
|
|
&custom_list_info, (GTypeFlags)0);
|
|
}
|
|
|
|
/* Here we register our GtkTreeModel interface with the type system */
|
|
if (1)
|
|
{
|
|
static const GInterfaceInfo tree_model_info =
|
|
{
|
|
(GInterfaceInitFunc) custom_list_tree_model_init,
|
|
NULL,
|
|
NULL
|
|
};
|
|
|
|
g_type_add_interface_static (custom_list_type, GTK_TYPE_TREE_MODEL, &tree_model_info);
|
|
}
|
|
|
|
return custom_list_type;
|
|
}
|
|
|
|
</programlisting>
|
|
|
|
<para>
|
|
Here we just return the type assigned to our custom list by the type system
|
|
if we have already registered it. If not, we register it and save the type.
|
|
Of the three callbacks that we pass to the type system, only two are of
|
|
immediate interest to us, namely <literal>custom_list_tree_model_init</literal>
|
|
and <literal>custom_list_init</literal>.
|
|
</para>
|
|
|
|
<para>
|
|
In <literal>custom_list_tree_model_init</literal> we fill the tree model
|
|
interface structure with pointers to our own functions (at least the ones
|
|
we implement):
|
|
</para>
|
|
|
|
<programlisting role="C">
|
|
static void
|
|
custom_list_tree_model_init (GtkTreeModelIface *iface)
|
|
{
|
|
/* Here we override the GtkTreeModel
|
|
* interface functions that we implement */
|
|
iface->get_flags = custom_list_get_flags;
|
|
iface->get_n_columns = custom_list_get_n_columns;
|
|
iface->get_column_type = custom_list_get_column_type;
|
|
iface->get_iter = custom_list_get_iter;
|
|
iface->get_path = custom_list_get_path;
|
|
iface->get_value = custom_list_get_value;
|
|
iface->iter_next = custom_list_iter_next;
|
|
iface->iter_children = custom_list_iter_children;
|
|
iface->iter_has_child = custom_list_iter_has_child;
|
|
iface->iter_n_children = custom_list_iter_n_children;
|
|
iface->iter_nth_child = custom_list_iter_nth_child;
|
|
iface->iter_parent = custom_list_iter_parent;
|
|
}
|
|
</programlisting>
|
|
|
|
<para>
|
|
In <literal>custom_list_init</literal> we initialised the custom
|
|
list structure to sensible default values. This function will be
|
|
called whenever a new instance of our custom list is created, which
|
|
we do in <literal>custom_list_new</literal>.
|
|
</para>
|
|
|
|
<para>
|
|
<literal>custom_list_finalize</literal> is called just before one
|
|
of our lists is going to be destroyed. You should free all resources
|
|
that you have dynamically allocated in there.
|
|
</para>
|
|
|
|
<para>
|
|
Having taken care of all the type system stuff, we now come to the heart of our custom model,
|
|
namely the tree model implementation. Our tree model functions need to behave exactly as the
|
|
API reference requires them to behave, including all special cases, otherwise things will not
|
|
work. Here is a list of links to the API reference descriptions of the functions we are
|
|
implementing:
|
|
</para>
|
|
|
|
<itemizedlist>
|
|
<listitem>
|
|
<para><ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeModel.html#gtk-tree-model-get-flags">gtk_tree_model_get_flags</ulink></para>
|
|
</listitem>
|
|
<listitem>
|
|
<para><ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeModel.html#gtk-tree-model-get-n-columns">gtk_tree_model_get_n_columns</ulink></para>
|
|
</listitem>
|
|
<listitem>
|
|
<para><ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeModel.html#gtk-tree-model-get-column-type">gtk_tree_model_get_column_type</ulink></para>
|
|
</listitem>
|
|
<listitem>
|
|
<para><ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeModel.html#gtk-tree-model-get-iter">gtk_tree_model_get_iter</ulink></para>
|
|
</listitem>
|
|
<listitem>
|
|
<para><ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeModel.html#gtk-tree-model-get-path">gtk_tree_model_get_path</ulink></para>
|
|
</listitem>
|
|
<listitem>
|
|
<para><ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeModel.html#gtk-tree-model-get-value">gtk_tree_model_get_value</ulink></para>
|
|
</listitem>
|
|
<listitem>
|
|
<para><ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeModel.html#gtk-tree-model-iter-next">gtk_tree_model_iter_next</ulink></para>
|
|
</listitem>
|
|
<listitem>
|
|
<para><ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeModel.html#gtk-tree-model-iter-children">gtk_tree_model_iter_children</ulink></para>
|
|
</listitem>
|
|
<listitem>
|
|
<para><ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeModel.html#gtk-tree-model-iter-has-child">gtk_tree_model_iter_has_child</ulink></para>
|
|
</listitem>
|
|
<listitem>
|
|
<para><ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeModel.html#gtk-tree-model-iter-n-children">gtk_tree_model_iter_n_children</ulink></para>
|
|
</listitem>
|
|
<listitem>
|
|
<para><ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeModel.html#gtk-tree-model-iter-nth-child">gtk_tree_model_iter_nth_child</ulink></para>
|
|
</listitem>
|
|
<listitem>
|
|
<para><ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeModel.html#gtk-tree-model-iter-parent">gtk_tree_model_iter_parent</ulink></para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
<para>
|
|
Almost all functions are more or less straight-forward and self-explanatory
|
|
in connection with the API reference descriptions, so you should be able to
|
|
jump right into <link linkend="sec-custom-model-code-body">the code</link>
|
|
and see how it works.
|
|
</para>
|
|
|
|
<para>
|
|
After the tree model implementation we have those functions that are specific to our
|
|
custom model. <literal>custom_list_new</literal> will create a new custom list for us,
|
|
and <literal>custom_list_append_record</literal> will append a new record to the end
|
|
of the list. Note the call to <literal>gtk_tree_model_row_inserted</literal> at the
|
|
end of our append function, which emits a <literal>"row-inserted"</literal>
|
|
signal on the model and informs all interested objects (tree views, tree row references)
|
|
that a new row has been inserted, and where it has been inserted.
|
|
</para>
|
|
|
|
<para>
|
|
You will need to emit tree model signals whenever something changes, e.g. rows are inserted,
|
|
removed, or reordered, or when a row changes from a child-less row to a row which has children,
|
|
or if a row's data changes. Here are the functions you need to use in those cases (we only
|
|
implement row insertions here - other cases are left as an exercise for the reader):
|
|
</para>
|
|
|
|
<itemizedlist>
|
|
<listitem>
|
|
<para><ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeModel.html#gtk-tree-model-row-inserted">gtk_tree_model_row_inserted</ulink></para>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para><ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeModel.html#gtk-tree-model-row-changed">gtk_tree_model_row_changed</ulink> (makes tree view redraw that row)</para>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para><ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeModel.html#gtk-tree-model-row-has-child-toggled">gtk_tree_model_row_has_child_toggled</ulink></para>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para><ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeModel.html#gtk-tree-model-row-deleted">gtk_tree_model_row_deleted</ulink></para>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para><ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeModel.html#gtk-tree-model-rows-reordered">gtk_tree_model_rows_reordered</ulink>
|
|
(note <ulink url="http://bugs.gnome.org/show_bug.cgi?id=124790">bug 124790</ulink>)</para>
|
|
</listitem>
|
|
|
|
</itemizedlist>
|
|
|
|
<para>
|
|
And that is all you have to do to write a custom model.
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
</sect1>
|
|
|
|
<!-- ************************************************************** -->
|
|
<sect1 id="sec-custom-model-tree">
|
|
<title>From a List to a Tree</title>
|
|
|
|
<para>
|
|
Writing a custom model for a tree is a bit trickier than a simple list model,
|
|
but follows the same pattern. Basically you just need to extend the above
|
|
model to cater for the case of children. You could do this by keeping track
|
|
of the whole tree hierarchy in the <literal>CustomList</literal> structure,
|
|
using GLib N-ary trees for example, or you could do this by keeping track
|
|
of each row's children within the row's <literal>CustomRecord</literal>
|
|
structure, keeping only a pointer to the (invisible) root record in the
|
|
<literal>CustomList</literal> structure.
|
|
</para>
|
|
|
|
<para>
|
|
TODO: do we need anything else here?
|
|
</para>
|
|
|
|
</sect1>
|
|
|
|
<!-- ************************************************************** -->
|
|
<sect1 id="sec-custom-model-sorting">
|
|
<title>Additional interfaces, here: the GtkTreeSortable interface</title>
|
|
|
|
<para>
|
|
A custom model can implement additional interfaces to extend its functionality.
|
|
Additional interfaces are:
|
|
</para>
|
|
|
|
<itemizedlist>
|
|
<listitem>
|
|
<para><ulink url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeSortable.html#GtkTreeSortableIface">GtkTreeSortableIface</ulink></para>
|
|
</listitem>
|
|
<listitem>
|
|
<para><ulink url="http://developer.gnome.org/doc/API/2.0/gtk/gtk-GtkTreeView-drag-and-drop.html#GtkTreeDragDestIface">GtkTreeDragDestIface</ulink></para>
|
|
</listitem>
|
|
<listitem>
|
|
<para><ulink url="http://developer.gnome.org/doc/API/2.0/gtk/gtk-GtkTreeView-drag-and-drop.html#GtkTreeDragSourceIface">GtkTreeDragSourceIface</ulink></para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
<para>
|
|
Here, we will show how to implement additional interfaces at the example of the
|
|
<literal>GtkTreeSortable</literal> interface, which we will implement only partially
|
|
(enough to make it functional and useful though).
|
|
</para>
|
|
|
|
<para>
|
|
Three things are necessary to add another interface: we will need to register the interface
|
|
with our model in <literal>custom_list_get_type</literal>, provide an interface init function
|
|
where we set the interface to our own implementation of the interface functions,
|
|
and then provide the implementation of those functions.
|
|
</para>
|
|
|
|
<para>
|
|
Firstly, we need to provide the function prototypes for our functions at the beginning of
|
|
the file:
|
|
</para>
|
|
|
|
<programlisting role="C">
|
|
|
|
/* custom-list.c */
|
|
|
|
...
|
|
|
|
/* -- GtkTreeSortable interface functions -- */
|
|
|
|
static gboolean custom_list_sortable_get_sort_column_id (GtkTreeSortable *sortable,
|
|
gint *sort_col_id,
|
|
GtkSortType *order);
|
|
|
|
static void custom_list_sortable_set_sort_column_id (GtkTreeSortable *sortable,
|
|
gint sort_col_id,
|
|
GtkSortType order);
|
|
|
|
static void custom_list_sortable_set_sort_func (GtkTreeSortable *sortable,
|
|
gint sort_col_id,
|
|
GtkTreeIterCompareFunc sort_func,
|
|
gpointer user_data,
|
|
GtkDestroyNotify destroy_func);
|
|
|
|
static void custom_list_sortable_set_default_sort_func (GtkTreeSortable *sortable,
|
|
GtkTreeIterCompareFunc sort_func,
|
|
gpointer user_data,
|
|
GtkDestroyNotify destroy_func);
|
|
|
|
static gboolean custom_list_sortable_has_default_sort_func (GtkTreeSortable *sortable);
|
|
|
|
static void custom_list_resort (CustomList *custom_list);
|
|
|
|
...
|
|
</programlisting>
|
|
|
|
<para>
|
|
Next, let's extend our <literal>CustomList</literal> structure with a field for the currently active
|
|
sort column ID and one for the sort order, and add an enum for the sort column IDs:
|
|
</para>
|
|
|
|
<programlisting role="C">
|
|
/* custom-list.h */
|
|
|
|
enum
|
|
{
|
|
SORT_ID_NONE = 0,
|
|
SORT_ID_NAME,
|
|
SORT_ID_YEAR_BORN,
|
|
};
|
|
|
|
...
|
|
|
|
struct _CustomList
|
|
{
|
|
GObject parent;
|
|
|
|
guint num_rows; /* number of rows that we have */
|
|
CustomRecord **rows; /* a dynamically allocated array of pointers to the
|
|
* CustomRecord structure for each row */
|
|
|
|
gint n_columns;
|
|
GType column_types[CUSTOM_LIST_N_COLUMNS];
|
|
|
|
gint sort_id;
|
|
GtkSortType sort_order;
|
|
|
|
gint stamp; /* Random integer to check whether an iter belongs to our model */
|
|
};
|
|
|
|
...
|
|
</programlisting>
|
|
|
|
<para>
|
|
Now, we make sure we initialise the new fields in <literal>custom_list_new</literal>, and add our new
|
|
interface:
|
|
</para>
|
|
|
|
<programlisting role="C">
|
|
...
|
|
|
|
static void custom_list_sortable_init (GtkTreeSortableIface *iface);
|
|
|
|
...
|
|
|
|
void
|
|
custom_list_init (CustomList *custom_list)
|
|
{
|
|
...
|
|
custom_list->sort_id = SORT_ID_NONE;
|
|
custom_list->sort_order = GTK_SORT_ASCENDING;
|
|
...
|
|
}
|
|
|
|
|
|
GType
|
|
custom_list_get_type (void)
|
|
{
|
|
...
|
|
/* Add GtkTreeSortable interface */
|
|
if (1)
|
|
{
|
|
static const GInterfaceInfo tree_sortable_info =
|
|
{
|
|
(GInterfaceInitFunc) custom_list_sortable_init,
|
|
NULL,
|
|
NULL
|
|
};
|
|
|
|
g_type_add_interface_static (custom_list_type, GTK_TYPE_TREE_SORTABLE, &tree_sortable_info);
|
|
}
|
|
...
|
|
}
|
|
|
|
|
|
static void
|
|
custom_list_sortable_init (GtkTreeSortableIface *iface)
|
|
{
|
|
iface->get_sort_column_id = custom_list_sortable_get_sort_column_id;
|
|
iface->set_sort_column_id = custom_list_sortable_set_sort_column_id;
|
|
iface->set_sort_func = custom_list_sortable_set_sort_func; /* NOT SUPPORTED */
|
|
iface->set_default_sort_func = custom_list_sortable_set_default_sort_func; /* NOT SUPPORTED */
|
|
iface->has_default_sort_func = custom_list_sortable_has_default_sort_func; /* NOT SUPPORTED */
|
|
}
|
|
|
|
</programlisting>
|
|
|
|
<para>
|
|
Now that we have finally taken care of the administrativa, we implement the tree sortable interface functions:
|
|
</para>
|
|
|
|
<programlisting role="C">
|
|
|
|
static gboolean
|
|
custom_list_sortable_get_sort_column_id (GtkTreeSortable *sortable,
|
|
gint *sort_col_id,
|
|
GtkSortType *order)
|
|
{
|
|
CustomList *custom_list;
|
|
|
|
g_return_val_if_fail ( sortable != NULL , FALSE );
|
|
g_return_val_if_fail ( CUSTOM_IS_LIST(sortable), FALSE );
|
|
|
|
custom_list = CUSTOM_LIST(sortable);
|
|
|
|
if (sort_col_id)
|
|
*sort_col_id = custom_list->sort_id;
|
|
|
|
if (order)
|
|
*order = custom_list->sort_order;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
static void
|
|
custom_list_sortable_set_sort_column_id (GtkTreeSortable *sortable,
|
|
gint sort_col_id,
|
|
GtkSortType order)
|
|
{
|
|
CustomList *custom_list;
|
|
|
|
g_return_if_fail ( sortable != NULL );
|
|
g_return_if_fail ( CUSTOM_IS_LIST(sortable) );
|
|
|
|
custom_list = CUSTOM_LIST(sortable);
|
|
|
|
if (custom_list->sort_id == sort_col_id && custom_list->sort_order == order)
|
|
return;
|
|
|
|
custom_list->sort_id = sort_col_id;
|
|
custom_list->sort_order = order;
|
|
|
|
custom_list_resort(custom_list);
|
|
|
|
/* emit "sort-column-changed" signal to tell any tree views
|
|
* that the sort column has changed (so the little arrow
|
|
* in the column header of the sort column is drawn
|
|
* in the right column) */
|
|
|
|
gtk_tree_sortable_sort_column_changed(sortable);
|
|
}
|
|
|
|
|
|
static void
|
|
custom_list_sortable_set_sort_func (GtkTreeSortable *sortable,
|
|
gint sort_col_id,
|
|
GtkTreeIterCompareFunc sort_func,
|
|
gpointer user_data,
|
|
GtkDestroyNotify destroy_func)
|
|
{
|
|
g_warning ("%s is not supported by the CustomList model.\n", __FUNCTION__);
|
|
}
|
|
|
|
|
|
static void
|
|
custom_list_sortable_set_default_sort_func (GtkTreeSortable *sortable,
|
|
GtkTreeIterCompareFunc sort_func,
|
|
gpointer user_data,
|
|
GtkDestroyNotify destroy_func)
|
|
{
|
|
g_warning ("%s is not supported by the CustomList model.\n", __FUNCTION__);
|
|
}
|
|
|
|
|
|
static gboolean
|
|
custom_list_sortable_has_default_sort_func (GtkTreeSortable *sortable)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
</programlisting>
|
|
|
|
|
|
<para>
|
|
Now, last but not least, the only thing missing is the function that does the actual
|
|
sorting. We do not implement <literal>set_sort_func</literal>, <literal>set_default_sort_func</literal>
|
|
and <literal>set_has_default_sort_func</literal> because we use our own internal sort function here.
|
|
</para>
|
|
|
|
<para>
|
|
The actual sorting is done using GLib's <literal>g_qsort_with_data</literal> function, which sorts an
|
|
array using the QuickSort algorithm. Note how we notify the tree view and other objects of the new
|
|
row order by emitting the "rows-reordered" signal on the tree model.
|
|
</para>
|
|
|
|
<programlisting role="C">
|
|
|
|
static gint
|
|
custom_list_compare_records (gint sort_id, CustomRecord *a, CustomRecord *b)
|
|
{
|
|
switch(sort_id)
|
|
{
|
|
case SORT_ID_NONE:
|
|
return 0;
|
|
|
|
case SORT_ID_NAME:
|
|
{
|
|
if ((a->name) && (b->name))
|
|
return g_utf8_collate(a->name, b->name);
|
|
|
|
if (a->name == b->name)
|
|
return 0; /* both are NULL */
|
|
else
|
|
return (a->name == NULL) ? -1 : 1;
|
|
}
|
|
|
|
case SORT_ID_YEAR_BORN:
|
|
{
|
|
if (a->year_born == b->year_born)
|
|
return 0;
|
|
|
|
return (a->year_born > b->year_born) ? 1 : -1;
|
|
}
|
|
}
|
|
|
|
g_return_val_if_reached(0);
|
|
}
|
|
|
|
|
|
static gint
|
|
custom_list_qsort_compare_func (CustomRecord **a, CustomRecord **b, CustomList *custom_list)
|
|
{
|
|
gint ret;
|
|
|
|
g_assert ((a) && (b) && (custom_list));
|
|
|
|
ret = custom_list_compare_records(custom_list->sort_id, *a, *b);
|
|
|
|
/* Swap -1 and 1 if sort order is reverse */
|
|
if (ret != 0 && custom_list->sort_order == GTK_SORT_DESCENDING)
|
|
ret = (ret < 0) ? 1 : -1;
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static void
|
|
custom_list_resort (CustomList *custom_list)
|
|
{
|
|
GtkTreePath *path;
|
|
gint *neworder, i;
|
|
|
|
g_return_if_fail ( custom_list != NULL );
|
|
g_return_if_fail ( CUSTOM_IS_LIST(custom_list) );
|
|
|
|
if (custom_list->sort_id == SORT_ID_NONE)
|
|
return;
|
|
|
|
if (custom_list->num_rows == 0)
|
|
return;
|
|
|
|
/* resort */
|
|
g_qsort_with_data(custom_list->rows,
|
|
custom_list->num_rows,
|
|
sizeof(CustomRecord*),
|
|
(GCompareDataFunc) custom_list_qsort_compare_func,
|
|
custom_list);
|
|
|
|
/* let other objects know about the new order */
|
|
neworder = g_new0(gint, custom_list->num_rows);
|
|
|
|
for (i = 0; i < custom_list->num_rows; ++i)
|
|
{
|
|
/* Note that the API reference might be wrong about
|
|
* this, see bug number 124790 on bugs.gnome.org.
|
|
* Both will work, but one will give you 'jumpy'
|
|
* selections after row reordering. */
|
|
/* neworder[(custom_list->rows[i])->pos] = i; */
|
|
neworder[i] = (custom_list->rows[i])->pos;
|
|
(custom_list->rows[i])->pos = i;
|
|
}
|
|
|
|
path = gtk_tree_path_new();
|
|
|
|
gtk_tree_model_rows_reordered(GTK_TREE_MODEL(custom_list), path, NULL, neworder);
|
|
|
|
gtk_tree_path_free(path);
|
|
g_free(neworder);
|
|
}
|
|
|
|
</programlisting>
|
|
|
|
<para>
|
|
Finally, we should make sure that the model is resorted after we have inserted
|
|
a new row by adding a call to <literal>custom_list_resort</literal> to the end
|
|
of <literal>custom_list_append</literal>:
|
|
</para>
|
|
|
|
<programlisting role="C">
|
|
...
|
|
void
|
|
custom_list_append_record (CustomList *custom_list, const gchar *name, guint year_born)
|
|
{
|
|
...
|
|
|
|
custom_list_resort(custom_list);
|
|
}
|
|
|
|
</programlisting>
|
|
|
|
<para>
|
|
And that is it. Adding two calls to <literal>gtk_tree_view_column_set_sort_column_id</literal> in main.c
|
|
is left as yet another exercise for the reader.
|
|
</para>
|
|
|
|
<para>
|
|
If you are interested in seeing string sorting speed issues in action, you should modify main.c like this:
|
|
</para>
|
|
|
|
<programlisting role="C">
|
|
GtkWidget *
|
|
create_view_and_model (void)
|
|
{
|
|
gint i;
|
|
...
|
|
for (i=0; i < 1000; ++i)
|
|
{
|
|
fill_model(customlist);
|
|
}
|
|
...
|
|
}
|
|
</programlisting>
|
|
|
|
<para>
|
|
Most likely, sorting 24000 rows by name will take up to several seconds now. Now, if you go back to
|
|
<literal>custom_list_compare_records</literal> and replace the call to <literal>g_utf8_collate</literal> with:
|
|
</para>
|
|
|
|
<programlisting role="C">
|
|
static gint
|
|
custom_list_compare_records (gint sort_id, CustomRecord *a, CustomRecord *b)
|
|
{
|
|
...
|
|
|
|
if ((a->name) && (b->name))
|
|
return strcmp(a->name_collate_key,b->name_collate_key);
|
|
|
|
...
|
|
}
|
|
</programlisting>
|
|
|
|
<para>
|
|
... then you should hopefully register a dramatic speed increase when sorting by name.
|
|
</para>
|
|
|
|
</sect1>
|
|
|
|
<!-- ************************************************************** -->
|
|
<sect1 id="sec-custom-model-code">
|
|
<title>Working Example: Custom List Model Source Code</title>
|
|
|
|
<para>
|
|
Here is the complete source code for the custom list model presented
|
|
<link linkend="sec-custom-model-list">above</link>. Compile with:
|
|
</para>
|
|
|
|
<programlisting>
|
|
gcc -o customlist custom-list.c main.c `pkg-config --cflags --libs gtk+-2.0`
|
|
</programlisting>
|
|
|
|
<itemizedlist>
|
|
<listitem>
|
|
<para><link linkend="sec-custom-model-code-header">custom-list.h</link></para>
|
|
</listitem>
|
|
<listitem>
|
|
<para><link linkend="sec-custom-model-code-body">custom-list.c</link></para>
|
|
</listitem>
|
|
<listitem>
|
|
<para><link linkend="sec-custom-model-code-example">main.c</link></para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
<!-- ************************************************************** -->
|
|
<sect2 id="sec-custom-model-code-header">
|
|
<title>custom-list.h</title>
|
|
|
|
<programlisting role="C">
|
|
#ifndef _custom_list_h_included_
|
|
#define _custom_list_h_included_
|
|
|
|
#include <gtk/gtk.h>
|
|
|
|
/* Some boilerplate GObject defines. 'klass' is used
|
|
* instead of 'class', because 'class' is a C++ keyword */
|
|
|
|
#define CUSTOM_TYPE_LIST (custom_list_get_type ())
|
|
#define CUSTOM_LIST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CUSTOM_TYPE_LIST, CustomList))
|
|
#define CUSTOM_LIST_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), CUSTOM_TYPE_LIST, CustomListClass))
|
|
#define CUSTOM_IS_LIST(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CUSTOM_TYPE_LIST))
|
|
#define CUSTOM_IS_LIST_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), CUSTOM_TYPE_LIST))
|
|
#define CUSTOM_LIST_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), CUSTOM_TYPE_LIST, CustomListClass))
|
|
|
|
/* The data columns that we export via the tree model interface */
|
|
|
|
enum
|
|
{
|
|
CUSTOM_LIST_COL_RECORD = 0,
|
|
CUSTOM_LIST_COL_NAME,
|
|
CUSTOM_LIST_COL_YEAR_BORN,
|
|
CUSTOM_LIST_N_COLUMNS,
|
|
} ;
|
|
|
|
|
|
typedef struct _CustomRecord CustomRecord;
|
|
typedef struct _CustomList CustomList;
|
|
typedef struct _CustomListClass CustomListClass;
|
|
|
|
|
|
|
|
/* CustomRecord: this structure represents a row */
|
|
|
|
struct _CustomRecord
|
|
{
|
|
/* data - you can extend this */
|
|
gchar *name;
|
|
gchar *name_collate_key;
|
|
guint year_born;
|
|
|
|
/* admin stuff used by the custom list model */
|
|
guint pos; /* pos within the array */
|
|
};
|
|
|
|
|
|
|
|
/* CustomList: this structure contains everything we need for our
|
|
* model implementation. You can add extra fields to
|
|
* this structure, e.g. hashtables to quickly lookup
|
|
* rows or whatever else you might need, but it is
|
|
* crucial that 'parent' is the first member of the
|
|
* structure. */
|
|
|
|
struct _CustomList
|
|
{
|
|
GObject parent; /* this MUST be the first member */
|
|
|
|
guint num_rows; /* number of rows that we have */
|
|
CustomRecord **rows; /* a dynamically allocated array of pointers to
|
|
* the CustomRecord structure for each row */
|
|
|
|
/* These two fields are not absolutely necessary, but they */
|
|
/* speed things up a bit in our get_value implementation */
|
|
gint n_columns;
|
|
GType column_types[CUSTOM_LIST_N_COLUMNS];
|
|
|
|
gint stamp; /* Random integer to check whether an iter belongs to our model */
|
|
};
|
|
|
|
|
|
|
|
/* CustomListClass: more boilerplate GObject stuff */
|
|
|
|
struct _CustomListClass
|
|
{
|
|
GObjectClass parent_class;
|
|
};
|
|
|
|
|
|
GType custom_list_get_type (void);
|
|
|
|
CustomList *custom_list_new (void);
|
|
|
|
void custom_list_append_record (CustomList *custom_list,
|
|
const gchar *name,
|
|
guint year_born);
|
|
|
|
#endif /* _custom_list_h_included_ */
|
|
|
|
</programlisting>
|
|
|
|
<itemizedlist>
|
|
<listitem>
|
|
<para><link linkend="sec-custom-model-code-header">custom-list.h</link></para>
|
|
</listitem>
|
|
<listitem>
|
|
<para><link linkend="sec-custom-model-code-body">custom-list.c</link></para>
|
|
</listitem>
|
|
<listitem>
|
|
<para><link linkend="sec-custom-model-code-example">main.c</link></para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
</sect2>
|
|
|
|
<!-- ************************************************************** -->
|
|
<sect2 id="sec-custom-model-code-body">
|
|
<title>custom-list.c</title>
|
|
|
|
<programlisting role="C">
|
|
|
|
#include "custom-list.h"
|
|
|
|
|
|
/* boring declarations of local functions */
|
|
|
|
static void custom_list_init (CustomList *pkg_tree);
|
|
|
|
static void custom_list_class_init (CustomListClass *klass);
|
|
|
|
static void custom_list_tree_model_init (GtkTreeModelIface *iface);
|
|
|
|
static void custom_list_finalize (GObject *object);
|
|
|
|
static GtkTreeModelFlags custom_list_get_flags (GtkTreeModel *tree_model);
|
|
|
|
static gint custom_list_get_n_columns (GtkTreeModel *tree_model);
|
|
|
|
static GType custom_list_get_column_type (GtkTreeModel *tree_model,
|
|
gint index);
|
|
|
|
static gboolean custom_list_get_iter (GtkTreeModel *tree_model,
|
|
GtkTreeIter *iter,
|
|
GtkTreePath *path);
|
|
|
|
static GtkTreePath *custom_list_get_path (GtkTreeModel *tree_model,
|
|
GtkTreeIter *iter);
|
|
|
|
static void custom_list_get_value (GtkTreeModel *tree_model,
|
|
GtkTreeIter *iter,
|
|
gint column,
|
|
GValue *value);
|
|
|
|
static gboolean custom_list_iter_next (GtkTreeModel *tree_model,
|
|
GtkTreeIter *iter);
|
|
|
|
static gboolean custom_list_iter_children (GtkTreeModel *tree_model,
|
|
GtkTreeIter *iter,
|
|
GtkTreeIter *parent);
|
|
|
|
static gboolean custom_list_iter_has_child (GtkTreeModel *tree_model,
|
|
GtkTreeIter *iter);
|
|
|
|
static gint custom_list_iter_n_children (GtkTreeModel *tree_model,
|
|
GtkTreeIter *iter);
|
|
|
|
static gboolean custom_list_iter_nth_child (GtkTreeModel *tree_model,
|
|
GtkTreeIter *iter,
|
|
GtkTreeIter *parent,
|
|
gint n);
|
|
|
|
static gboolean custom_list_iter_parent (GtkTreeModel *tree_model,
|
|
GtkTreeIter *iter,
|
|
GtkTreeIter *child);
|
|
|
|
|
|
|
|
static GObjectClass *parent_class = NULL; /* GObject stuff - nothing to worry about */
|
|
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* custom_list_get_type: here we register our new type and its interfaces
|
|
* with the type system. If you want to implement
|
|
* additional interfaces like GtkTreeSortable, you
|
|
* will need to do it here.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
GType
|
|
custom_list_get_type (void)
|
|
{
|
|
static GType custom_list_type = 0;
|
|
|
|
if (custom_list_type)
|
|
return custom_list_type;
|
|
|
|
/* Some boilerplate type registration stuff */
|
|
if (1)
|
|
{
|
|
static const GTypeInfo custom_list_info =
|
|
{
|
|
sizeof (CustomListClass),
|
|
NULL, /* base_init */
|
|
NULL, /* base_finalize */
|
|
(GClassInitFunc) custom_list_class_init,
|
|
NULL, /* class finalize */
|
|
NULL, /* class_data */
|
|
sizeof (CustomList),
|
|
0, /* n_preallocs */
|
|
(GInstanceInitFunc) custom_list_init
|
|
};
|
|
|
|
custom_list_type = g_type_register_static (G_TYPE_OBJECT, "CustomList",
|
|
&custom_list_info, (GTypeFlags)0);
|
|
}
|
|
|
|
/* Here we register our GtkTreeModel interface with the type system */
|
|
if (1)
|
|
{
|
|
static const GInterfaceInfo tree_model_info =
|
|
{
|
|
(GInterfaceInitFunc) custom_list_tree_model_init,
|
|
NULL,
|
|
NULL
|
|
};
|
|
|
|
g_type_add_interface_static (custom_list_type, GTK_TYPE_TREE_MODEL, &tree_model_info);
|
|
}
|
|
|
|
return custom_list_type;
|
|
}
|
|
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* custom_list_class_init: more boilerplate GObject/GType stuff.
|
|
* Init callback for the type system,
|
|
* called once when our new class is created.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
static void
|
|
custom_list_class_init (CustomListClass *klass)
|
|
{
|
|
GObjectClass *object_class;
|
|
|
|
parent_class = (GObjectClass*) g_type_class_peek_parent (klass);
|
|
object_class = (GObjectClass*) klass;
|
|
|
|
object_class->finalize = custom_list_finalize;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* custom_list_tree_model_init: init callback for the interface registration
|
|
* in custom_list_get_type. Here we override
|
|
* the GtkTreeModel interface functions that
|
|
* we implement.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
static void
|
|
custom_list_tree_model_init (GtkTreeModelIface *iface)
|
|
{
|
|
iface->get_flags = custom_list_get_flags;
|
|
iface->get_n_columns = custom_list_get_n_columns;
|
|
iface->get_column_type = custom_list_get_column_type;
|
|
iface->get_iter = custom_list_get_iter;
|
|
iface->get_path = custom_list_get_path;
|
|
iface->get_value = custom_list_get_value;
|
|
iface->iter_next = custom_list_iter_next;
|
|
iface->iter_children = custom_list_iter_children;
|
|
iface->iter_has_child = custom_list_iter_has_child;
|
|
iface->iter_n_children = custom_list_iter_n_children;
|
|
iface->iter_nth_child = custom_list_iter_nth_child;
|
|
iface->iter_parent = custom_list_iter_parent;
|
|
}
|
|
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* custom_list_init: this is called everytime a new custom list object
|
|
* instance is created (we do that in custom_list_new).
|
|
* Initialise the list structure's fields here.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
static void
|
|
custom_list_init (CustomList *custom_list)
|
|
{
|
|
custom_list->n_columns = CUSTOM_LIST_N_COLUMNS;
|
|
|
|
custom_list->column_types[0] = G_TYPE_POINTER; /* CUSTOM_LIST_COL_RECORD */
|
|
custom_list->column_types[1] = G_TYPE_STRING; /* CUSTOM_LIST_COL_NAME */
|
|
custom_list->column_types[2] = G_TYPE_UINT; /* CUSTOM_LIST_COL_YEAR_BORN */
|
|
|
|
g_assert (CUSTOM_LIST_N_COLUMNS == 3);
|
|
|
|
custom_list->num_rows = 0;
|
|
custom_list->rows = NULL;
|
|
|
|
custom_list->stamp = g_random_int(); /* Random int to check whether an iter belongs to our model */
|
|
|
|
}
|
|
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* custom_list_finalize: this is called just before a custom list is
|
|
* destroyed. Free dynamically allocated memory here.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
static void
|
|
custom_list_finalize (GObject *object)
|
|
{
|
|
/* CustomList *custom_list = CUSTOM_LIST(object); */
|
|
|
|
/* free all records and free all memory used by the list */
|
|
#warning IMPLEMENT
|
|
|
|
/* must chain up - finalize parent */
|
|
(* parent_class->finalize) (object);
|
|
}
|
|
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* custom_list_get_flags: tells the rest of the world whether our tree model
|
|
* has any special characteristics. In our case,
|
|
* we have a list model (instead of a tree), and each
|
|
* tree iter is valid as long as the row in question
|
|
* exists, as it only contains a pointer to our struct.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
static GtkTreeModelFlags
|
|
custom_list_get_flags (GtkTreeModel *tree_model)
|
|
{
|
|
g_return_val_if_fail (CUSTOM_IS_LIST(tree_model), (GtkTreeModelFlags)0);
|
|
|
|
return (GTK_TREE_MODEL_LIST_ONLY | GTK_TREE_MODEL_ITERS_PERSIST);
|
|
}
|
|
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* custom_list_get_n_columns: tells the rest of the world how many data
|
|
* columns we export via the tree model interface
|
|
*
|
|
*****************************************************************************/
|
|
|
|
static gint
|
|
custom_list_get_n_columns (GtkTreeModel *tree_model)
|
|
{
|
|
g_return_val_if_fail (CUSTOM_IS_LIST(tree_model), 0);
|
|
|
|
return CUSTOM_LIST(tree_model)->n_columns;
|
|
}
|
|
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* custom_list_get_column_type: tells the rest of the world which type of
|
|
* data an exported model column contains
|
|
*
|
|
*****************************************************************************/
|
|
|
|
static GType
|
|
custom_list_get_column_type (GtkTreeModel *tree_model,
|
|
gint index)
|
|
{
|
|
g_return_val_if_fail (CUSTOM_IS_LIST(tree_model), G_TYPE_INVALID);
|
|
g_return_val_if_fail (index < CUSTOM_LIST(tree_model)->n_columns && index >= 0, G_TYPE_INVALID);
|
|
|
|
return CUSTOM_LIST(tree_model)->column_types[index];
|
|
}
|
|
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* custom_list_get_iter: converts a tree path (physical position) into a
|
|
* tree iter structure (the content of the iter
|
|
* fields will only be used internally by our model).
|
|
* We simply store a pointer to our CustomRecord
|
|
* structure that represents that row in the tree iter.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
static gboolean
|
|
custom_list_get_iter (GtkTreeModel *tree_model,
|
|
GtkTreeIter *iter,
|
|
GtkTreePath *path)
|
|
{
|
|
CustomList *custom_list;
|
|
CustomRecord *record;
|
|
gint *indices, n, depth;
|
|
|
|
g_assert(CUSTOM_IS_LIST(tree_model));
|
|
g_assert(path!=NULL);
|
|
|
|
custom_list = CUSTOM_LIST(tree_model);
|
|
|
|
indices = gtk_tree_path_get_indices(path);
|
|
depth = gtk_tree_path_get_depth(path);
|
|
|
|
/* we do not allow children */
|
|
g_assert(depth == 1); /* depth 1 = top level; a list only has top level nodes and no children */
|
|
|
|
n = indices[0]; /* the n-th top level row */
|
|
|
|
if ( n >= custom_list->num_rows || n < 0 )
|
|
return FALSE;
|
|
|
|
record = custom_list->rows[n];
|
|
|
|
g_assert(record != NULL);
|
|
g_assert(record->pos == n);
|
|
|
|
/* We simply store a pointer to our custom record in the iter */
|
|
iter->stamp = custom_list->stamp;
|
|
iter->user_data = record;
|
|
iter->user_data2 = NULL; /* unused */
|
|
iter->user_data3 = NULL; /* unused */
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* custom_list_get_path: converts a tree iter into a tree path (ie. the
|
|
* physical position of that row in the list).
|
|
*
|
|
*****************************************************************************/
|
|
|
|
static GtkTreePath *
|
|
custom_list_get_path (GtkTreeModel *tree_model,
|
|
GtkTreeIter *iter)
|
|
{
|
|
GtkTreePath *path;
|
|
CustomRecord *record;
|
|
CustomList *custom_list;
|
|
|
|
g_return_val_if_fail (CUSTOM_IS_LIST(tree_model), NULL);
|
|
g_return_val_if_fail (iter != NULL, NULL);
|
|
g_return_val_if_fail (iter->user_data != NULL, NULL);
|
|
|
|
custom_list = CUSTOM_LIST(tree_model);
|
|
|
|
record = (CustomRecord*) iter->user_data;
|
|
|
|
path = gtk_tree_path_new();
|
|
gtk_tree_path_append_index(path, record->pos);
|
|
|
|
return path;
|
|
}
|
|
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* custom_list_get_value: Returns a row's exported data columns
|
|
* (_get_value is what gtk_tree_model_get uses)
|
|
*
|
|
*****************************************************************************/
|
|
|
|
static void
|
|
custom_list_get_value (GtkTreeModel *tree_model,
|
|
GtkTreeIter *iter,
|
|
gint column,
|
|
GValue *value)
|
|
{
|
|
CustomRecord *record;
|
|
CustomList *custom_list;
|
|
|
|
g_return_if_fail (CUSTOM_IS_LIST (tree_model));
|
|
g_return_if_fail (iter != NULL);
|
|
g_return_if_fail (column < CUSTOM_LIST(tree_model)->n_columns);
|
|
|
|
g_value_init (value, CUSTOM_LIST(tree_model)->column_types[column]);
|
|
|
|
custom_list = CUSTOM_LIST(tree_model);
|
|
|
|
record = (CustomRecord*) iter->user_data;
|
|
|
|
g_return_if_fail ( record != NULL );
|
|
|
|
if(record->pos >= custom_list->num_rows)
|
|
g_return_if_reached();
|
|
|
|
switch(column)
|
|
{
|
|
case CUSTOM_LIST_COL_RECORD:
|
|
g_value_set_pointer(value, record);
|
|
break;
|
|
|
|
case CUSTOM_LIST_COL_NAME:
|
|
g_value_set_string(value, record->name);
|
|
break;
|
|
|
|
case CUSTOM_LIST_COL_YEAR_BORN:
|
|
g_value_set_uint(value, record->year_born);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* custom_list_iter_next: Takes an iter structure and sets it to point
|
|
* to the next row.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
static gboolean
|
|
custom_list_iter_next (GtkTreeModel *tree_model,
|
|
GtkTreeIter *iter)
|
|
{
|
|
CustomRecord *record, *nextrecord;
|
|
CustomList *custom_list;
|
|
|
|
g_return_val_if_fail (CUSTOM_IS_LIST (tree_model), FALSE);
|
|
|
|
if (iter == NULL || iter->user_data == NULL)
|
|
return FALSE;
|
|
|
|
custom_list = CUSTOM_LIST(tree_model);
|
|
|
|
record = (CustomRecord *) iter->user_data;
|
|
|
|
/* Is this the last record in the list? */
|
|
if ((record->pos + 1) >= custom_list->num_rows)
|
|
return FALSE;
|
|
|
|
nextrecord = custom_list->rows[(record->pos + 1)];
|
|
|
|
g_assert ( nextrecord != NULL );
|
|
g_assert ( nextrecord->pos == (record->pos + 1) );
|
|
|
|
iter->stamp = custom_list->stamp;
|
|
iter->user_data = nextrecord;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* custom_list_iter_children: Returns TRUE or FALSE depending on whether
|
|
* the row specified by 'parent' has any children.
|
|
* If it has children, then 'iter' is set to
|
|
* point to the first child. Special case: if
|
|
* 'parent' is NULL, then the first top-level
|
|
* row should be returned if it exists.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
static gboolean
|
|
custom_list_iter_children (GtkTreeModel *tree_model,
|
|
GtkTreeIter *iter,
|
|
GtkTreeIter *parent)
|
|
{
|
|
CustomList *custom_list;
|
|
|
|
g_return_val_if_fail (parent == NULL || parent->user_data != NULL, FALSE);
|
|
|
|
/* this is a list, nodes have no children */
|
|
if (parent)
|
|
return FALSE;
|
|
|
|
/* parent == NULL is a special case; we need to return the first top-level row */
|
|
|
|
g_return_val_if_fail (CUSTOM_IS_LIST (tree_model), FALSE);
|
|
|
|
custom_list = CUSTOM_LIST(tree_model);
|
|
|
|
/* No rows => no first row */
|
|
if (custom_list->num_rows == 0)
|
|
return FALSE;
|
|
|
|
/* Set iter to first item in list */
|
|
iter->stamp = custom_list->stamp;
|
|
iter->user_data = custom_list->rows[0];
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* custom_list_iter_has_child: Returns TRUE or FALSE depending on whether
|
|
* the row specified by 'iter' has any children.
|
|
* We only have a list and thus no children.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
static gboolean
|
|
custom_list_iter_has_child (GtkTreeModel *tree_model,
|
|
GtkTreeIter *iter)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* custom_list_iter_n_children: Returns the number of children the row
|
|
* specified by 'iter' has. This is usually 0,
|
|
* as we only have a list and thus do not have
|
|
* any children to any rows. A special case is
|
|
* when 'iter' is NULL, in which case we need
|
|
* to return the number of top-level nodes,
|
|
* ie. the number of rows in our list.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
static gint
|
|
custom_list_iter_n_children (GtkTreeModel *tree_model,
|
|
GtkTreeIter *iter)
|
|
{
|
|
CustomList *custom_list;
|
|
|
|
g_return_val_if_fail (CUSTOM_IS_LIST (tree_model), -1);
|
|
g_return_val_if_fail (iter == NULL || iter->user_data != NULL, FALSE);
|
|
|
|
custom_list = CUSTOM_LIST(tree_model);
|
|
|
|
/* special case: if iter == NULL, return number of top-level rows */
|
|
if (!iter)
|
|
return custom_list->num_rows;
|
|
|
|
return 0; /* otherwise, this is easy again for a list */
|
|
}
|
|
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* custom_list_iter_nth_child: If the row specified by 'parent' has any
|
|
* children, set 'iter' to the n-th child and
|
|
* return TRUE if it exists, otherwise FALSE.
|
|
* A special case is when 'parent' is NULL, in
|
|
* which case we need to set 'iter' to the n-th
|
|
* row if it exists.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
static gboolean
|
|
custom_list_iter_nth_child (GtkTreeModel *tree_model,
|
|
GtkTreeIter *iter,
|
|
GtkTreeIter *parent,
|
|
gint n)
|
|
{
|
|
CustomRecord *record;
|
|
CustomList *custom_list;
|
|
|
|
g_return_val_if_fail (CUSTOM_IS_LIST (tree_model), FALSE);
|
|
|
|
custom_list = CUSTOM_LIST(tree_model);
|
|
|
|
/* a list has only top-level rows */
|
|
if(parent)
|
|
return FALSE;
|
|
|
|
/* special case: if parent == NULL, set iter to n-th top-level row */
|
|
|
|
if( n >= custom_list->num_rows )
|
|
return FALSE;
|
|
|
|
record = custom_list->rows[n];
|
|
|
|
g_assert( record != NULL );
|
|
g_assert( record->pos == n );
|
|
|
|
iter->stamp = custom_list->stamp;
|
|
iter->user_data = record;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* custom_list_iter_parent: Point 'iter' to the parent node of 'child'. As
|
|
* we have a list and thus no children and no
|
|
* parents of children, we can just return FALSE.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
static gboolean
|
|
custom_list_iter_parent (GtkTreeModel *tree_model,
|
|
GtkTreeIter *iter,
|
|
GtkTreeIter *child)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* custom_list_new: This is what you use in your own code to create a
|
|
* new custom list tree model for you to use.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
CustomList *
|
|
custom_list_new (void)
|
|
{
|
|
CustomList *newcustomlist;
|
|
|
|
newcustomlist = (CustomList*) g_object_new (CUSTOM_TYPE_LIST, NULL);
|
|
|
|
g_assert( newcustomlist != NULL );
|
|
|
|
return newcustomlist;
|
|
}
|
|
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* custom_list_append_record: Empty lists are boring. This function can
|
|
* be used in your own code to add rows to the
|
|
* list. Note how we emit the "row-inserted"
|
|
* signal after we have appended the row
|
|
* internally, so the tree view and other
|
|
* interested objects know about the new row.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
void
|
|
custom_list_append_record (CustomList *custom_list,
|
|
const gchar *name,
|
|
guint year_born)
|
|
{
|
|
GtkTreeIter iter;
|
|
GtkTreePath *path;
|
|
CustomRecord *newrecord;
|
|
gulong newsize;
|
|
guint pos;
|
|
|
|
g_return_if_fail (CUSTOM_IS_LIST(custom_list));
|
|
g_return_if_fail (name != NULL);
|
|
|
|
pos = custom_list->num_rows;
|
|
|
|
custom_list->num_rows++;
|
|
|
|
newsize = custom_list->num_rows * sizeof(CustomRecord*);
|
|
|
|
custom_list->rows = g_realloc(custom_list->rows, newsize);
|
|
|
|
newrecord = g_new0(CustomRecord, 1);
|
|
|
|
newrecord->name = g_strdup(name);
|
|
newrecord->name_collate_key = g_utf8_collate_key(name,-1); /* for fast sorting, used later */
|
|
newrecord->year_born = year_born;
|
|
|
|
custom_list->rows[pos] = newrecord;
|
|
newrecord->pos = pos;
|
|
|
|
/* inform the tree view and other interested objects
|
|
* (e.g. tree row references) that we have inserted
|
|
* a new row, and where it was inserted */
|
|
|
|
path = gtk_tree_path_new();
|
|
gtk_tree_path_append_index(path, newrecord->pos);
|
|
|
|
custom_list_get_iter(GTK_TREE_MODEL(custom_list), &iter, path);
|
|
|
|
gtk_tree_model_row_inserted(GTK_TREE_MODEL(custom_list), path, &iter);
|
|
|
|
gtk_tree_path_free(path);
|
|
}
|
|
|
|
</programlisting>
|
|
|
|
<itemizedlist>
|
|
<listitem>
|
|
<para><link linkend="sec-custom-model-code-header">custom-list.h</link></para>
|
|
</listitem>
|
|
<listitem>
|
|
<para><link linkend="sec-custom-model-code-body">custom-list.c</link></para>
|
|
</listitem>
|
|
<listitem>
|
|
<para><link linkend="sec-custom-model-code-example">main.c</link></para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
</sect2>
|
|
|
|
<!-- ************************************************************** -->
|
|
<sect2 id="sec-custom-model-code-example">
|
|
<title>main.c</title>
|
|
|
|
<para>
|
|
The following couple of lines provide a working test case that makes use of our custom list. It
|
|
creates one of our custom lists, adds some records, and displays it in a tree view.
|
|
</para>
|
|
|
|
<programlisting role="C">
|
|
|
|
#include "custom-list.h"
|
|
#include <stdlib.h>
|
|
|
|
void
|
|
fill_model (CustomList *customlist)
|
|
{
|
|
const gchar *firstnames[] = { "Joe", "Jane", "William", "Hannibal", "Timothy", "Gargamel", NULL } ;
|
|
const gchar *surnames[] = { "Grokowich", "Twitch", "Borheimer", "Bork", NULL } ;
|
|
const gchar **fname, **sname;
|
|
|
|
for (sname = surnames; *sname != NULL; sname++)
|
|
{
|
|
for (fname = firstnames; *fname != NULL; fname++)
|
|
{
|
|
gchar *name = g_strdup_printf ("%s %s", *fname, *sname);
|
|
|
|
custom_list_append_record (customlist, name, 1900 + (guint) (103.0*rand()/(RAND_MAX+1900.0)));
|
|
|
|
g_free(name);
|
|
}
|
|
}
|
|
}
|
|
|
|
GtkWidget *
|
|
create_view_and_model (void)
|
|
{
|
|
GtkTreeViewColumn *col;
|
|
GtkCellRenderer *renderer;
|
|
CustomList *customlist;
|
|
GtkWidget *view;
|
|
|
|
customlist = custom_list_new();
|
|
fill_model(customlist);
|
|
|
|
view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(customlist));
|
|
|
|
g_object_unref(customlist); /* destroy store automatically with view */
|
|
|
|
renderer = gtk_cell_renderer_text_new();
|
|
col = gtk_tree_view_column_new();
|
|
|
|
gtk_tree_view_column_pack_start (col, renderer, TRUE);
|
|
gtk_tree_view_column_add_attribute (col, renderer, "text", CUSTOM_LIST_COL_NAME);
|
|
gtk_tree_view_column_set_title (col, "Name");
|
|
gtk_tree_view_append_column(GTK_TREE_VIEW(view),col);
|
|
|
|
renderer = gtk_cell_renderer_text_new();
|
|
col = gtk_tree_view_column_new();
|
|
gtk_tree_view_column_pack_start (col, renderer, TRUE);
|
|
gtk_tree_view_column_add_attribute (col, renderer, "text", CUSTOM_LIST_COL_YEAR_BORN);
|
|
gtk_tree_view_column_set_title (col, "Year Born");
|
|
gtk_tree_view_append_column(GTK_TREE_VIEW(view),col);
|
|
|
|
return view;
|
|
}
|
|
|
|
int
|
|
main (int argc, char **argv)
|
|
{
|
|
GtkWidget *window, *view, *scrollwin;
|
|
|
|
gtk_init(&argc,&argv);
|
|
|
|
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
|
|
gtk_window_set_default_size (GTK_WINDOW(window), 200, 400);
|
|
g_signal_connect(window, "delete_event", gtk_main_quit, NULL);
|
|
|
|
scrollwin = gtk_scrolled_window_new(NULL,NULL);
|
|
|
|
view = create_view_and_model();
|
|
|
|
gtk_container_add(GTK_CONTAINER(scrollwin), view);
|
|
gtk_container_add(GTK_CONTAINER(window), scrollwin);
|
|
|
|
gtk_widget_show_all(window);
|
|
|
|
gtk_main();
|
|
|
|
return 0;
|
|
}
|
|
|
|
</programlisting>
|
|
|
|
</sect2>
|
|
|
|
</sect1>
|
|
|
|
</chapter>
|
|
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
|
|
<chapter id="sec-custom-cell-renderers">
|
|
<title>Writing Custom Cell Renderers</title>
|
|
|
|
<para>
|
|
The cell renderers that come with Gtk+ should be sufficient for most purposes,
|
|
but there might be occasions where you want to display something in a tree view
|
|
that you cannot display with the provided cell renderers, or where you want to
|
|
derive from one of the provided cell renderers to extend its functionality.
|
|
</para>
|
|
|
|
<para>
|
|
You can do this by writing a new object that derives from
|
|
<literal>GtkCellRenderer</literal> (or even one of the other cell renderers
|
|
if you just want to extend an existing one).
|
|
</para>
|
|
|
|
<para>
|
|
Three things you need to do in the course of that:
|
|
</para>
|
|
|
|
<itemizedlist>
|
|
<listitem>
|
|
<para>
|
|
Register some new properties that your renderer needs with the type
|
|
system and write your own <literal>set_property</literal> and
|
|
<literal>get_property</literal> functions to set and get your new
|
|
renderer's properties.
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
Write your own <literal>cell_renderer_get_size</literal> function and
|
|
override the parent object's function (usually the parent is of type
|
|
<literal>GtkCellRenderer</literal>. Note that you should honour the
|
|
standard properties for padding and cell alignment of the parent
|
|
object here.
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
Write your own <literal>cell_renderer_render</literal> function and
|
|
override the parent object's function. This function does
|
|
the actual rendering.
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
<para>
|
|
The GObject type system stuff of writing a new cell renderer is similar to
|
|
what we have done above when writing a custom tree model, and is relatively
|
|
straight forward in this case. Copy and paste and modify according to your
|
|
own needs.
|
|
</para>
|
|
|
|
<para>
|
|
Good examples of cell renderer code to look at or even modify are
|
|
<literal>GtkCellRendererPixbuf</literal> and <literal>GtkCellRendererToggle</literal>
|
|
in the Gtk+ source code tree. Both cases are less than five hundred lines
|
|
of code to look at and thus should be fairly easy to digest.
|
|
</para>
|
|
|
|
<sect1 id="sec-custom-cell-renderer-example">
|
|
<title>Working Example: a Progress Bar Cell Renderer</title>
|
|
|
|
<para>
|
|
In the following we will write a custom cell renderer to render progress bars
|
|
into a tree view (the code was "heavily inspired" by Sean Egan's progress bar
|
|
cell renderer implementation in GAIM):
|
|
</para>
|
|
|
|
<itemizedlist>
|
|
<listitem>
|
|
<para><link linkend="sec-custom-cell-renderer-example-header">custom-cell-renderer-progressbar.h</link></para>
|
|
</listitem>
|
|
<listitem>
|
|
<para><link linkend="sec-custom-cell-renderer-example-body">custom-cell-renderer-progressbar.c</link></para>
|
|
</listitem>
|
|
<listitem>
|
|
<para><link linkend="sec-custom-cell-renderer-example-test">main.c</link></para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
<sect2 id="sec-custom-cell-renderer-example-header">
|
|
<title>custom-cell-renderer-progressbar.h</title>
|
|
|
|
<para>
|
|
The header file consists of the usual GObject type cast and type check defines
|
|
and our <literal>CustomCellRendererProgress</literal> structure. As the type
|
|
of the parent indicates, we derive from <literal>GtkCellRenderer</literal>. The
|
|
parent object must always be the first item in the structure (note also that it
|
|
is not a pointer to an object, but the parent object structure itself embedded
|
|
in our structure).
|
|
</para>
|
|
|
|
<para>
|
|
Our <literal>CustomCellRendererProgress</literal> structure is fairly uneventful
|
|
and contains only a double precision float variable in which we store our new
|
|
<literal>"percentage"</literal> property (which will determine how long the
|
|
progressbar is going to be).
|
|
</para>
|
|
|
|
<programlisting role="C">
|
|
#ifndef _custom_cell_renderer_progressbar_included_
|
|
#define _custom_cell_renderer_progressbar_included_
|
|
|
|
#include <gtk/gtk.h>
|
|
|
|
/* Some boilerplate GObject type check and type cast macros.
|
|
* 'klass' is used here instead of 'class', because 'class'
|
|
* is a c++ keyword */
|
|
|
|
#define CUSTOM_TYPE_CELL_RENDERER_PROGRESS (custom_cell_renderer_progress_get_type())
|
|
#define CUSTOM_CELL_RENDERER_PROGRESS(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), CUSTOM_TYPE_CELL_RENDERER_PROGRESS, CustomCellRendererProgress))
|
|
#define CUSTOM_CELL_RENDERER_PROGRESS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), CUSTOM_TYPE_CELL_RENDERER_PROGRESS, CustomCellRendererProgressClass))
|
|
#define CUSTOM_IS_CELL_PROGRESS_PROGRESS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CUSTOM_TYPE_CELL_RENDERER_PROGRESS))
|
|
#define CUSTOM_IS_CELL_PROGRESS_PROGRESS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), CUSTOM_TYPE_CELL_RENDERER_PROGRESS))
|
|
#define CUSTOM_CELL_RENDERER_PROGRESS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), CUSTOM_TYPE_CELL_RENDERER_PROGRESS, CustomCellRendererProgressClass))
|
|
|
|
typedef struct _CustomCellRendererProgress CustomCellRendererProgress;
|
|
typedef struct _CustomCellRendererProgressClass CustomCellRendererProgressClass;
|
|
|
|
/* CustomCellRendererProgress: Our custom cell renderer
|
|
* structure. Extend according to need */
|
|
|
|
struct _CustomCellRendererProgress
|
|
{
|
|
GtkCellRenderer parent;
|
|
|
|
gdouble progress;
|
|
};
|
|
|
|
|
|
struct _CustomCellRendererProgressClass
|
|
{
|
|
GtkCellRendererClass parent_class;
|
|
};
|
|
|
|
|
|
GType custom_cell_renderer_progress_get_type (void);
|
|
|
|
GtkCellRenderer *custom_cell_renderer_progress_new (void);
|
|
|
|
|
|
#endif /* _custom_cell_renderer_progressbar_included_ */
|
|
</programlisting>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="sec-custom-cell-renderer-example-body">
|
|
<title>custom-cell-renderer-progressbar.c</title>
|
|
|
|
<para>
|
|
The code contains everything as described above, so let's jump right into it:
|
|
</para>
|
|
|
|
<programlisting role="C">
|
|
#include "custom-cell-renderer-progressbar.h"
|
|
|
|
/* This is based mainly on GtkCellRendererProgress
|
|
* in GAIM, written and (c) 2002 by Sean Egan
|
|
* (Licensed under the GPL), which in turn is
|
|
* based on Gtk's GtkCellRenderer[Text|Toggle|Pixbuf]
|
|
* implementation by Jonathan Blandford */
|
|
|
|
/* Some boring function declarations: GObject type system stuff */
|
|
|
|
static void custom_cell_renderer_progress_init (CustomCellRendererProgress *cellprogress);
|
|
|
|
static void custom_cell_renderer_progress_class_init (CustomCellRendererProgressClass *klass);
|
|
|
|
static void custom_cell_renderer_progress_get_property (GObject *object,
|
|
guint param_id,
|
|
GValue *value,
|
|
GParamSpec *pspec);
|
|
|
|
static void custom_cell_renderer_progress_set_property (GObject *object,
|
|
guint param_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec);
|
|
|
|
static void custom_cell_renderer_progress_finalize (GObject *gobject);
|
|
|
|
|
|
/* These functions are the heart of our custom cell renderer: */
|
|
|
|
static void custom_cell_renderer_progress_get_size (GtkCellRenderer *cell,
|
|
GtkWidget *widget,
|
|
GdkRectangle *cell_area,
|
|
gint *x_offset,
|
|
gint *y_offset,
|
|
gint *width,
|
|
gint *height);
|
|
|
|
static void custom_cell_renderer_progress_render (GtkCellRenderer *cell,
|
|
GdkWindow *window,
|
|
GtkWidget *widget,
|
|
GdkRectangle *background_area,
|
|
GdkRectangle *cell_area,
|
|
GdkRectangle *expose_area,
|
|
guint flags);
|
|
|
|
|
|
enum
|
|
{
|
|
PROP_PERCENTAGE = 1,
|
|
};
|
|
|
|
static gpointer parent_class;
|
|
|
|
|
|
/***************************************************************************
|
|
*
|
|
* custom_cell_renderer_progress_get_type: here we register our type with
|
|
* the GObject type system if we
|
|
* haven't done so yet. Everything
|
|
* else is done in the callbacks.
|
|
*
|
|
***************************************************************************/
|
|
|
|
GType
|
|
custom_cell_renderer_progress_get_type (void)
|
|
{
|
|
static GType cell_progress_type = 0;
|
|
|
|
if (cell_progress_type)
|
|
return cell_progress_type;
|
|
|
|
if (1)
|
|
{
|
|
static const GTypeInfo cell_progress_info =
|
|
{
|
|
sizeof (CustomCellRendererProgressClass),
|
|
NULL, /* base_init */
|
|
NULL, /* base_finalize */
|
|
(GClassInitFunc) custom_cell_renderer_progress_class_init,
|
|
NULL, /* class_finalize */
|
|
NULL, /* class_data */
|
|
sizeof (CustomCellRendererProgress),
|
|
0, /* n_preallocs */
|
|
(GInstanceInitFunc) custom_cell_renderer_progress_init,
|
|
};
|
|
|
|
/* Derive from GtkCellRenderer */
|
|
cell_progress_type = g_type_register_static (GTK_TYPE_CELL_RENDERER,
|
|
"CustomCellRendererProgress",
|
|
&cell_progress_info,
|
|
0);
|
|
}
|
|
|
|
return cell_progress_type;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
*
|
|
* custom_cell_renderer_progress_init: set some default properties of the
|
|
* parent (GtkCellRenderer).
|
|
*
|
|
***************************************************************************/
|
|
|
|
static void
|
|
custom_cell_renderer_progress_init (CustomCellRendererProgress *cellrendererprogress)
|
|
{
|
|
GTK_CELL_RENDERER(cellrendererprogress)->mode = GTK_CELL_RENDERER_MODE_INERT;
|
|
GTK_CELL_RENDERER(cellrendererprogress)->xpad = 2;
|
|
GTK_CELL_RENDERER(cellrendererprogress)->ypad = 2;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
*
|
|
* custom_cell_renderer_progress_class_init:
|
|
*
|
|
* set up our own get_property and set_property functions, and
|
|
* override the parent's functions that we need to implement.
|
|
* And make our new "percentage" property known to the type system.
|
|
* If you want cells that can be activated on their own (ie. not
|
|
* just the whole row selected) or cells that are editable, you
|
|
* will need to override 'activate' and 'start_editing' as well.
|
|
*
|
|
***************************************************************************/
|
|
|
|
static void
|
|
custom_cell_renderer_progress_class_init (CustomCellRendererProgressClass *klass)
|
|
{
|
|
GtkCellRendererClass *cell_class = GTK_CELL_RENDERER_CLASS(klass);
|
|
GObjectClass *object_class = G_OBJECT_CLASS(klass);
|
|
|
|
parent_class = g_type_class_peek_parent (klass);
|
|
object_class->finalize = custom_cell_renderer_progress_finalize;
|
|
|
|
/* Hook up functions to set and get our
|
|
* custom cell renderer properties */
|
|
object_class->get_property = custom_cell_renderer_progress_get_property;
|
|
object_class->set_property = custom_cell_renderer_progress_set_property;
|
|
|
|
/* Override the two crucial functions that are the heart
|
|
* of a cell renderer in the parent class */
|
|
cell_class->get_size = custom_cell_renderer_progress_get_size;
|
|
cell_class->render = custom_cell_renderer_progress_render;
|
|
|
|
/* Install our very own properties */
|
|
g_object_class_install_property (object_class,
|
|
PROP_PERCENTAGE,
|
|
g_param_spec_double ("percentage",
|
|
"Percentage",
|
|
"The fractional progress to display",
|
|
0, 1, 0,
|
|
G_PARAM_READWRITE));
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
*
|
|
* custom_cell_renderer_progress_finalize: free any resources here
|
|
*
|
|
***************************************************************************/
|
|
|
|
static void
|
|
custom_cell_renderer_progress_finalize (GObject *object)
|
|
{
|
|
/*
|
|
CustomCellRendererProgress *cellrendererprogress = CUSTOM_CELL_RENDERER_PROGRESS(object);
|
|
*/
|
|
|
|
/* Free any dynamically allocated resources here */
|
|
|
|
(* G_OBJECT_CLASS (parent_class)->finalize) (object);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
*
|
|
* custom_cell_renderer_progress_get_property: as it says
|
|
*
|
|
***************************************************************************/
|
|
|
|
static void
|
|
custom_cell_renderer_progress_get_property (GObject *object,
|
|
guint param_id,
|
|
GValue *value,
|
|
GParamSpec *psec)
|
|
{
|
|
CustomCellRendererProgress *cellprogress = CUSTOM_CELL_RENDERER_PROGRESS(object);
|
|
|
|
switch (param_id)
|
|
{
|
|
case PROP_PERCENTAGE:
|
|
g_value_set_double(value, cellprogress->progress);
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, psec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
*
|
|
* custom_cell_renderer_progress_set_property: as it says
|
|
*
|
|
***************************************************************************/
|
|
|
|
static void
|
|
custom_cell_renderer_progress_set_property (GObject *object,
|
|
guint param_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
CustomCellRendererProgress *cellprogress = CUSTOM_CELL_RENDERER_PROGRESS (object);
|
|
|
|
switch (param_id)
|
|
{
|
|
case PROP_PERCENTAGE:
|
|
cellprogress->progress = g_value_get_double(value);
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, param_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/***************************************************************************
|
|
*
|
|
* custom_cell_renderer_progress_new: return a new cell renderer instance
|
|
*
|
|
***************************************************************************/
|
|
|
|
GtkCellRenderer *
|
|
custom_cell_renderer_progress_new (void)
|
|
{
|
|
return g_object_new(CUSTOM_TYPE_CELL_RENDERER_PROGRESS, NULL);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
*
|
|
* custom_cell_renderer_progress_get_size: crucial - calculate the size
|
|
* of our cell, taking into account
|
|
* padding and alignment properties
|
|
* of parent.
|
|
*
|
|
***************************************************************************/
|
|
|
|
#define FIXED_WIDTH 100
|
|
#define FIXED_HEIGHT 10
|
|
|
|
static void
|
|
custom_cell_renderer_progress_get_size (GtkCellRenderer *cell,
|
|
GtkWidget *widget,
|
|
GdkRectangle *cell_area,
|
|
gint *x_offset,
|
|
gint *y_offset,
|
|
gint *width,
|
|
gint *height)
|
|
{
|
|
gint calc_width;
|
|
gint calc_height;
|
|
|
|
calc_width = (gint) cell->xpad * 2 + FIXED_WIDTH;
|
|
calc_height = (gint) cell->ypad * 2 + FIXED_HEIGHT;
|
|
|
|
if (width)
|
|
*width = calc_width;
|
|
|
|
if (height)
|
|
*height = calc_height;
|
|
|
|
if (cell_area)
|
|
{
|
|
if (x_offset)
|
|
{
|
|
*x_offset = cell->xalign * (cell_area->width - calc_width);
|
|
*x_offset = MAX (*x_offset, 0);
|
|
}
|
|
|
|
if (y_offset)
|
|
{
|
|
*y_offset = cell->yalign * (cell_area->height - calc_height);
|
|
*y_offset = MAX (*y_offset, 0);
|
|
}
|
|
}
|
|
}99
|
|
|
|
|
|
/***************************************************************************
|
|
*
|
|
* custom_cell_renderer_progress_render: crucial - do the rendering.
|
|
*
|
|
***************************************************************************/
|
|
|
|
static void
|
|
custom_cell_renderer_progress_render (GtkCellRenderer *cell,
|
|
GdkWindow *window,
|
|
GtkWidget *widget,
|
|
GdkRectangle *background_area,
|
|
GdkRectangle *cell_area,
|
|
GdkRectangle *expose_area,
|
|
guint flags)
|
|
{
|
|
CustomCellRendererProgress *cellprogress = CUSTOM_CELL_RENDERER_PROGRESS (cell);
|
|
GtkStateType state;
|
|
gint width, height;
|
|
gint x_offset, y_offset;
|
|
|
|
custom_cell_renderer_progress_get_size (cell, widget, cell_area,
|
|
&x_offset, &y_offset,
|
|
&width, &height);
|
|
|
|
if (GTK_WIDGET_HAS_FOCUS (widget))
|
|
state = GTK_STATE_ACTIVE;
|
|
else
|
|
state = GTK_STATE_NORMAL;
|
|
|
|
width -= cell->xpad*2;
|
|
height -= cell->ypad*2;
|
|
|
|
gtk_paint_box (widget->style,
|
|
window,
|
|
GTK_STATE_NORMAL, GTK_SHADOW_IN,
|
|
NULL, widget, "trough",
|
|
cell_area->x + x_offset + cell->xpad,
|
|
cell_area->y + y_offset + cell->ypad,
|
|
width - 1, height - 1);
|
|
|
|
gtk_paint_box (widget->style,
|
|
window,
|
|
state, GTK_SHADOW_OUT,
|
|
NULL, widget, "bar",
|
|
cell_area->x + x_offset + cell->xpad,
|
|
cell_area->y + y_offset + cell->ypad,
|
|
width * cellprogress->progress,
|
|
height - 1);
|
|
}
|
|
|
|
</programlisting>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="sec-custom-cell-renderer-example-test">
|
|
<title>main.c</title>
|
|
|
|
<para>
|
|
And here is a little test that makes use of our new <literal>CustomCellRendererProgress</literal>:
|
|
</para>
|
|
|
|
<programlisting role="C">
|
|
#include "custom-cell-renderer-progressbar.h"
|
|
|
|
static GtkListStore *liststore;
|
|
|
|
static gboolean increasing = TRUE; /* direction of progress bar change */
|
|
|
|
enum
|
|
{
|
|
COL_PERCENTAGE = 0,
|
|
COL_TEXT,
|
|
NUM_COLS
|
|
};
|
|
|
|
#define STEP 0.01
|
|
|
|
gboolean
|
|
increase_progress_timeout (GtkCellRenderer *renderer)
|
|
{
|
|
GtkTreeIter iter;
|
|
gfloat perc = 0.0;
|
|
gchar buf[20];
|
|
|
|
gtk_tree_model_get_iter_first(GTK_TREE_MODEL(liststore), &iter); /* first and only row */
|
|
|
|
gtk_tree_model_get (GTK_TREE_MODEL(liststore), &iter, COL_PERCENTAGE, &perc, -1);
|
|
|
|
if ( perc > (1.0-STEP) || (perc < STEP && perc > 0.0) )
|
|
{
|
|
increasing = (!increasing);
|
|
}
|
|
|
|
if (increasing)
|
|
perc = perc + STEP;
|
|
else
|
|
perc = perc - STEP;
|
|
|
|
g_snprintf(buf, sizeof(buf), "%u %%", (guint)(perc*100));
|
|
|
|
gtk_list_store_set (liststore, &iter, COL_PERCENTAGE, perc, COL_TEXT, buf, -1);
|
|
|
|
return TRUE; /* Call again */
|
|
}
|
|
|
|
|
|
GtkWidget *
|
|
create_view_and_model (void)
|
|
{
|
|
GtkTreeViewColumn *col;
|
|
GtkCellRenderer *renderer;
|
|
GtkTreeIter iter;
|
|
GtkWidget *view;
|
|
|
|
liststore = gtk_list_store_new(NUM_COLS, G_TYPE_FLOAT, G_TYPE_STRING);
|
|
gtk_list_store_append(liststore, &iter);
|
|
gtk_list_store_set (liststore, &iter, COL_PERCENTAGE, 0.5, -1); /* start at 50% */
|
|
|
|
view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(liststore));
|
|
|
|
g_object_unref(liststore); /* destroy store automatically with view */
|
|
|
|
renderer = gtk_cell_renderer_text_new();
|
|
col = gtk_tree_view_column_new();
|
|
gtk_tree_view_column_pack_start (col, renderer, TRUE);
|
|
gtk_tree_view_column_add_attribute (col, renderer, "text", COL_TEXT);
|
|
gtk_tree_view_column_set_title (col, "Progress");
|
|
gtk_tree_view_append_column(GTK_TREE_VIEW(view),col);
|
|
|
|
renderer = custom_cell_renderer_progress_new();
|
|
col = gtk_tree_view_column_new();
|
|
gtk_tree_view_column_pack_start (col, renderer, TRUE);
|
|
gtk_tree_view_column_add_attribute (col, renderer, "percentage", COL_PERCENTAGE);
|
|
gtk_tree_view_column_set_title (col, "Progress");
|
|
gtk_tree_view_append_column(GTK_TREE_VIEW(view),col);
|
|
|
|
g_timeout_add(50, (GSourceFunc) increase_progress_timeout, NULL);
|
|
|
|
return view;
|
|
}
|
|
|
|
|
|
int
|
|
main (int argc, char **argv)
|
|
{
|
|
GtkWidget *window, *view;
|
|
|
|
gtk_init(&argc,&argv);
|
|
|
|
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
|
|
gtk_window_set_default_size (GTK_WINDOW(window), 150, 100);
|
|
g_signal_connect(window, "delete_event", gtk_main_quit, NULL);
|
|
|
|
view = create_view_and_model();
|
|
|
|
gtk_container_add(GTK_CONTAINER(window), view);
|
|
|
|
gtk_widget_show_all(window);
|
|
|
|
gtk_main();
|
|
|
|
return 0;
|
|
}
|
|
|
|
</programlisting>
|
|
|
|
</sect2>
|
|
|
|
</sect1>
|
|
|
|
<sect1 id="sec-custom-cell-renderers-external">
|
|
<title>Cell Renderers Others Have Written</title>
|
|
<para>
|
|
Just in case you are one of those people who do not
|
|
like to re-invent the wheel, here is a list of custom
|
|
cell renderers other people have written:
|
|
</para>
|
|
|
|
<itemizedlist>
|
|
<listitem>
|
|
<para>
|
|
<ulink url="http://cvs.sourceforge.net/cgi-bin/viewcvs.cgi/gaim/gaim/src/gtkcellrendererprogress.c">Progress bar cell renderer</ulink> (gaim)
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
<ulink url="http://cvs.gnome.org/bonsai/cvsblame.cgi?file=mrproject%2Fsrc%2Fcell-renderers/mg-cell-renderer-date.c&rev=&root=/cvs/gnome">Date cell renderer</ulink> (mrproject) (is this one easy to re-use?)
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
<ulink url="http://cvs.gnome.org/bonsai/cvsblame.cgi?file=mrproject%2Fsrc%2Fcell-renderers/mg-cell-renderer-list.c&rev=&root=/cvs/gnome">List/combo cell renderer</ulink> (mrproject) (is this one easy to re-use?)
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
<ulink url="http://cvs.gnome.org/bonsai/cvsblame.cgi?file=mrproject%2Fsrc%2Fcell-renderers/mg-cell-renderer-popup.c&rev=&root=/cvs/gnome">Pop-up cell renderer</ulink> (mrproject) (what does this do?)
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
Your custom cell renderer here?!
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
</sect1>
|
|
|
|
</chapter>
|
|
|
|
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
|
|
<chapter id="sec-other-info">
|
|
<title>Other Resources</title>
|
|
|
|
<para>
|
|
A short tutorial like this cannot possibly cover everything. Luckily,
|
|
there is a lot more information out there. Here is a list of links
|
|
that you might find useful (if you have any links that should appear
|
|
here as well, please send them to <literal>tim at centricular dot net</literal>).
|
|
|
|
</para>
|
|
|
|
<itemizedlist>
|
|
<listitem>
|
|
<para>
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gtk/index.html">Gtk+ API Reference Manual</ulink>
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/gdk/index.html">Gdk API Reference Manual</ulink>
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/pango/index.html">Pango API Reference Manual</ulink>
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
<ulink url="http://developer.gnome.org/doc/API/2.0/glib/index.html">GLib API Reference Manual</ulink>
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
<ulink url="http://mail.gnome.org/archives/gtk-app-devel-list/index.html">gtk-app-devel mailing list archives</ulink> - search them!
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
<ulink url="http://cvs.gnome.org/bonsai/rview.cgi?cvsroot=/cvs/gnome&dir=gtk%2b/demos/gtk-demo">gtk-demo</ulink> -
|
|
part of the Gtk+ source code (look in gtk+-2.x.y/demos/gtk-demo), especially
|
|
<ulink url="http://cvs.gnome.org/bonsai/cvsblame.cgi?file=gtk%2B%2Fdemos%2Fgtk-demo/list_store.c&rev=&root=/cvs/gnome">list_store.c</ulink>,
|
|
<ulink url="http://cvs.gnome.org/bonsai/cvsblame.cgi?file=gtk%2B%2Fdemos%2Fgtk-demo/tree_store.c&rev=&root=/cvs/gnome">tree_store.c</ulink>, and
|
|
<ulink url="http://cvs.gnome.org/bonsai/cvsblame.cgi?file=gtk%2B%2Fdemos%2Fgtk-demo/stock_browser.c&rev=&root=/cvs/gnome">stock_browser.c</ulink>
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
<ulink url="http://www.gtkmm.org/gtkmm2/docs/tutorial/html/ch08.html">TreeView tutorial using Gtk's C++ interface (gtkmm)</ulink>
|
|
</para>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para>
|
|
<ulink url="http://liw.iki.fi/liw/texts/gtktreeview-tutorial.html">TreeView tutorial using Gtk's python interface</ulink>
|
|
</para>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para>
|
|
Some slides from Owen Taylor's GUADEC 2003 <ulink url="http://people.redhat.com/otaylor/tutorial/guadec2003/">tutorial</ulink>
|
|
(<ulink url="http://people.redhat.com/otaylor/tutorial/guadec2003/gtk-tut.ps">postscript</ulink>,
|
|
<ulink url="http://people.redhat.com/otaylor/tutorial/guadec2003/gtk-tut.pdf">pdf</ulink>, see pages 13-15)
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
<ulink url="http://www.gnome.org/softwaremap/">Existing applications</ulink> - yes, they exist, and <emphasis>you</emphasis>
|
|
can look at their source code. <ulink url="http://sourceforge.net/">SourceForge's</ulink> WebCVS browse feature is quite
|
|
useful, and the same goes for <ulink url="http://cvs.gnome.org/bonsai/rview.cgi?cvsroot=/cvs/gnome">GNOME</ulink> as well.
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
If your intention is to display external data (from a database, or in XML form) as a list or tree or table,
|
|
you might also be interested <ulink url="http://www.gnome-db.org/">GnomeDB</ulink>, especially
|
|
libgda and libgnomedb (e.g. the GnomeDBGrid widget). See also
|
|
<ulink url="ftp://kalamazoolinux.org/pub/pdf/dbaccess.pdf">this PDF presentation</ulink>
|
|
(page 24ff).
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
your link here!
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
</chapter>
|
|
|
|
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
<!-- ************************************************************** -->
|
|
|
|
<chapter id="sec-treeviewtut-license">
|
|
<title>Copyright, License, Credits, and Revision History</title>
|
|
|
|
<sect1 id="sec-treeviewtut-copyright-license">
|
|
<title>Copyright and License</title>
|
|
|
|
<para>
|
|
Copyright (c) 2003-2004 Tim-Philipp Müller <email>tim at centricular dot net</email>
|
|
</para>
|
|
|
|
<para>
|
|
This tutorial may be redistributed and modified freely in any form, as
|
|
long as all authors are given due credit for their work and all non-trivial
|
|
changes by third parties are clearly marked as such either within the
|
|
document (e.g. in a revision history), or at an external and publicly
|
|
accessible place that is refered to in the document (e.g. a CVS repository).
|
|
</para>
|
|
</sect1>
|
|
|
|
<sect1 id="sec-treeviewtut-credits">
|
|
<title>Credits</title>
|
|
|
|
<para>
|
|
Thanks to Axel C. for proof-reading the first drafts, for many suggestions,
|
|
and for introducing me to the tree view widget in the first place (back then
|
|
when I was still convinced that porting to Gtk+-2.x was unnecessary, Gtk+-1.2
|
|
applications looked nice, and Aristotle had already said everything about
|
|
politics that needs to be said).
|
|
</para>
|
|
|
|
<para>
|
|
Harring Figueiredo <ulink url="http://mail.gnome.org/archives/gtk-app-devel-list/2003-September/msg00240.html">shed</ulink>
|
|
some light on how GtkListStore and GtkTreeStore deal with pixbufs.
|
|
</para>
|
|
|
|
<para>
|
|
Ken Rastatter <ulink url="http://mail.gnome.org/archives/gtk-app-devel-list/2003-September/msg00250.html">suggested</ulink>
|
|
some additional topics (with complete references even).
|
|
</para>
|
|
|
|
<para>
|
|
Both Andrej Prsa and Alan B. Canon sent me a couple of suggestions, and 'taf2', Massimo Mangoni and others spotted some typos.
|
|
</para>
|
|
|
|
<para>
|
|
Many thanks to all of them, and of course also to kris and everyone else
|
|
in #gtk+.
|
|
</para>
|
|
|
|
</sect1>
|
|
|
|
<sect1 id="sec-treeviewtut-history">
|
|
<title>Revision History</title>
|
|
|
|
<itemizedlist>
|
|
<title>
|
|
5th June 2005
|
|
</title>
|
|
<listitem>
|
|
<para>
|
|
Remove unnecessary col = gtk_tree_view_column_new() im hello world
|
|
code (leftover from migration to convenience functions).
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
<itemizedlist>
|
|
<title>
|
|
3rd February 2005
|
|
</title>
|
|
<listitem>
|
|
<para>
|
|
Point out that GObjects such as GdkPixbufs retrieved with
|
|
gtk_tree_model_get() need to be g_object_unref()'ed after
|
|
use, as gtk_tree_model_get() adds a reference.
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
Added explicit (gint) event->x double to int conversion to
|
|
code snippet using gtk_tree_view_get_path_at_pos() to avoid
|
|
compiler warnings.
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
<itemizedlist>
|
|
<title>
|
|
9th September 2004
|
|
</title>
|
|
<listitem>
|
|
<para>
|
|
Fixed another mistake in tree path explanation: text did not
|
|
correspond picture (s/movie clips/movie trailers/); (thanks
|
|
to Benjamin Brandt for spotting it).
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
<itemizedlist>
|
|
<title>
|
|
6th August 2004
|
|
</title>
|
|
<listitem>
|
|
<para>
|
|
Fixed mistake in tree path explanation (s/4th/5th/)
|
|
(thanks to both Andrew Kirillov and Benjamin Brandt
|
|
for spotting it).
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
<itemizedlist>
|
|
<title>
|
|
30th April 2004
|
|
</title>
|
|
<listitem>
|
|
<para>
|
|
Added Hello World
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
<itemizedlist>
|
|
<title>
|
|
31st March 2004
|
|
</title>
|
|
<listitem>
|
|
<para>
|
|
Fixed fatal typo in custom list code: g_assert() in custom_list_init() should be ==, not !=
|
|
(spotted by mmc).
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
Added link to Owen Taylor's mail on the GtkTreeView Drag'n'Drop API.
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
<itemizedlist>
|
|
<title>
|
|
24th January 2004
|
|
</title>
|
|
<listitem>
|
|
<para>
|
|
Fixed typo in code example (remove n-th row example)
|
|
(Thanks to roel for spotting it).
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
Changed 'Context menus' section title
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
<itemizedlist>
|
|
<title>
|
|
19th January 2004
|
|
</title>
|
|
<listitem>
|
|
<para>
|
|
Expanded section on GtkTreeRowReferences, and on removing multiple rows.
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
<itemizedlist>
|
|
<title>
|
|
8th January 2004
|
|
</title>
|
|
<listitem>
|
|
<para>
|
|
Added tiny section on Glade and treeviews
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
Added more detail to the section describing GtkTreePath, GtkTreeIter et.al.
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
Reformatted document structure: instead of one single chapter with lots
|
|
of sections, have multiple chapters (this tutorial is way to big to
|
|
become part of the Gtk+ tutorial anyway); enumerate chapters and sections.
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
Expanded the section on tree view columns and cell renderers, with help
|
|
of two diagrams by Owen Taylor (from the GUADEC 2003 Gtk+ tutorial slides).
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
<itemizedlist>
|
|
<title>
|
|
10th December 2003
|
|
</title>
|
|
<listitem>
|
|
<para>
|
|
Added more information about how to remove a single row, or more specifically, the n-th row of a list store
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
Added a short example about how to pack icons into the tree view.
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
<itemizedlist>
|
|
<title>
|
|
28th October 2003
|
|
</title>
|
|
<listitem>
|
|
<para>
|
|
Editable cells will work fine even if selection is set to GTK_SELECTION_NONE. Removed sentences that say otherwise.
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
<itemizedlist>
|
|
<title>
|
|
23rd October 2003
|
|
</title>
|
|
<listitem>
|
|
<para>
|
|
fix 'jumpy' selections in custom model GtkTreeSortable interface implementation. gtk_tree_model_rows_reordered() does not
|
|
seem to work like the API reference implies (see <ulink url="http://bugs.gnome.org/show_bug.cgi?id=124790">bug #124790</ulink>)
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
added section about how to get the cell renderer a button click happened on
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
added section about editable cells with spin buttons (and a CellRendererSpin implementation to the examples)
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
<itemizedlist>
|
|
<title>
|
|
10th October 2003
|
|
</title>
|
|
<listitem>
|
|
<para>
|
|
make custom model GtkTreeSortable implementation emit "sort-column-changed" signal when sortid is changed
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
fixed code typo in selection function section; added a paragraph about rule hint to 'make whole row coloured or bold' section
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
<itemizedlist>
|
|
<title>
|
|
7th October 2003
|
|
</title>
|
|
<listitem>
|
|
<para>
|
|
Reformatted source code to make it fit on pages when generating ps/pdf output
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
Added link to PDF and docbook XML versions.
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
</sect1>
|
|
|
|
</chapter>
|
|
|
|
|
|
</book>
|
|
|