2014-07-23 19:48:39 +00:00
|
|
|
|
<?xml version="1.0" encoding="UTF-8"?>
|
2014-07-22 23:28:11 +00:00
|
|
|
|
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN"
|
2014-07-23 19:48:39 +00:00
|
|
|
|
"http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd">
|
2014-07-22 23:28:11 +00:00
|
|
|
|
<book id="treeview-tutorial">
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<bookinfo>
|
2014-07-22 23:28:11 +00:00
|
|
|
|
<date>September 1st, 2003</date>
|
2014-07-23 19:48:39 +00:00
|
|
|
|
|
2014-07-22 23:28:11 +00:00
|
|
|
|
<title>GTK+ 2.0 Tree View Tutorial</title>
|
2014-07-23 19:48:39 +00:00
|
|
|
|
|
2014-07-22 23:28:11 +00:00
|
|
|
|
<authorgroup>
|
|
|
|
|
<author>
|
|
|
|
|
<firstname>Tim-Philipp</firstname>
|
2014-07-23 19:48:39 +00:00
|
|
|
|
|
|
|
|
|
<surname>Müller</surname>
|
2014-07-22 23:28:11 +00:00
|
|
|
|
</author>
|
|
|
|
|
</authorgroup>
|
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<abstract>
|
|
|
|
|
<para>This is a tutorial on how to use the GTK (the GIMP Toolkit)
|
|
|
|
|
GtkTreeView widget through its C interface.</para>
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<para>Please mail all comments and suggestions to <email>tim at
|
|
|
|
|
centricular dot net</email></para>
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<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>
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<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>
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<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>
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<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>
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<para>Last updated: September 29th, 2006</para>
|
|
|
|
|
</abstract>
|
|
|
|
|
</bookinfo>
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<toc/>
|
|
|
|
|
|
|
|
|
|
<chapter id="ch-TreeView">
|
|
|
|
|
<title>Lists and Trees: the GtkTreeView Widget</title>
|
|
|
|
|
|
2014-07-23 22:07:40 +00:00
|
|
|
|
<para><classname>GtkTreeView</classname> is a widget that displays single-
|
|
|
|
|
or multi-columned lists and trees. It replaces the old Gtk+-1.2
|
|
|
|
|
<classname>GtkCList</classname> and <classname>GtkCTree</classname>
|
|
|
|
|
widgets. Even though <classname>GtkTreeView</classname> 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>
|
2014-07-23 19:48:39 +00:00
|
|
|
|
|
|
|
|
|
<para>The purpose of this chapter is not to provide an exhaustive
|
2014-07-23 22:07:40 +00:00
|
|
|
|
documentation of <classname>GtkTreeView</classname> – that is what the
|
|
|
|
|
<ulink
|
|
|
|
|
url="https://developer.gnome.org/gtk3/stable/TreeWidgetObjects.html">API
|
2014-07-23 19:48:39 +00:00
|
|
|
|
documentation</ulink> is for, which should be read alongside with this
|
|
|
|
|
tutorial. The goal is rather to present an introduction to the most
|
2014-07-23 22:07:40 +00:00
|
|
|
|
commonly-used aspects of <classname>GtkTreeView</classname>, and to
|
|
|
|
|
demonstrate how the various <classname>GtkTreeView</classname> components and
|
2014-07-23 19:48:39 +00:00
|
|
|
|
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
|
2014-07-23 22:07:40 +00:00
|
|
|
|
find it here. In the authors’ experience, developers who do not understand
|
2014-07-23 19:48:39 +00:00
|
|
|
|
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
|
2014-07-23 22:07:40 +00:00
|
|
|
|
with other toolkits that employ the Model/View/Controller design will find
|
2014-07-23 19:48:39 +00:00
|
|
|
|
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
|
2014-07-23 22:07:40 +00:00
|
|
|
|
necessarily demonstrate how <classname>GtkTreeView</classname> 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
|
2014-07-23 19:48:39 +00:00
|
|
|
|
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">
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* 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>
|
2014-07-23 19:48:39 +00:00
|
|
|
|
</sect1>
|
|
|
|
|
</chapter>
|
|
|
|
|
|
|
|
|
|
<chapter id="sec-treeview-components">
|
|
|
|
|
<title>Components: Model, Renderer, Column, View</title>
|
|
|
|
|
|
2014-07-23 22:07:40 +00:00
|
|
|
|
<para>The most important concept underlying
|
|
|
|
|
<classname>GtkTreeView</classname> 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 <classname>GtkTreeModel</classname> is used to store data,
|
2014-07-23 19:48:39 +00:00
|
|
|
|
there are other components that determine which data is displayed in the
|
2014-07-23 22:07:40 +00:00
|
|
|
|
<classname>GtkTreeView</classname> and how it is displayed. These
|
|
|
|
|
components are <classname>GtkTreeViewColumn</classname> and
|
|
|
|
|
<classname>GtkCellRenderer</classname>. A <classname>GtkTreeView</classname> is
|
2014-07-23 19:48:39 +00:00
|
|
|
|
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
|
2014-07-23 22:07:40 +00:00
|
|
|
|
<classname>GtkCellRenderer</classname> family of objects (I call them
|
2014-07-23 19:48:39 +00:00
|
|
|
|
'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
|
2014-07-23 22:07:40 +00:00
|
|
|
|
<classname>GtkCellRendererPixbuf</classname> and a
|
|
|
|
|
<classname>GtkCellRendererText</classname> into one tree view column. Packing
|
2014-07-23 19:48:39 +00:00
|
|
|
|
renderers into a tree view column is similar to packing widgets into a
|
2014-07-23 22:07:40 +00:00
|
|
|
|
<classname>GtkHBox</classname>.</para>
|
2014-07-23 19:48:39 +00:00
|
|
|
|
</chapter>
|
|
|
|
|
|
|
|
|
|
<chapter id="sec-treemodels">
|
|
|
|
|
<title>GtkTreeModels for Data Storage: GtkListStore and
|
|
|
|
|
GtkTreeStore</title>
|
|
|
|
|
|
|
|
|
|
<para>It is important to realise what <ulink
|
2014-07-23 22:07:40 +00:00
|
|
|
|
url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeModel.html"><classname>GtkTreeModel</classname></ulink>
|
|
|
|
|
is and what it is not. <classname>GtkTreeModel</classname> is basically just
|
2014-07-23 19:48:39 +00:00
|
|
|
|
an 'interface' to the data store, meaning that it is a standardised set of
|
2014-07-23 22:07:40 +00:00
|
|
|
|
functions that allows a <classname>GtkTreeView</classname> widget (and the
|
2014-07-23 19:48:39 +00:00
|
|
|
|
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
|
2014-07-23 22:07:40 +00:00
|
|
|
|
<classname>GtkTreeModel</classname> interface and provide these functions,
|
2014-07-23 19:48:39 +00:00
|
|
|
|
which you can use by casting a store to a tree model with
|
2014-07-23 22:07:40 +00:00
|
|
|
|
<literal>GTK_TREE_MODEL(store)</literal>. <classname>GtkTreeModel</classname>
|
2014-07-23 19:48:39 +00:00
|
|
|
|
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
|
2014-07-23 22:07:40 +00:00
|
|
|
|
url="http://developer.gnome.org/doc/API/2.0/gtk/GtkListStore.html"><classname>GtkListStore</classname></ulink>
|
2014-07-23 19:48:39 +00:00
|
|
|
|
and <ulink
|
2014-07-23 22:07:40 +00:00
|
|
|
|
url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeStore.html"><classname>GtkTreeStore</classname></ulink>.
|
|
|
|
|
As the names imply, <classname>GtkListStore</classname> is used for simple
|
2014-07-23 19:48:39 +00:00
|
|
|
|
lists of data items where items have no hierarchical parent-child
|
2014-07-23 22:07:40 +00:00
|
|
|
|
relationships, and <classname>GtkTreeStore</classname> is used for tree-like
|
2014-07-23 19:48:39 +00:00
|
|
|
|
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
|
2014-07-23 22:07:40 +00:00
|
|
|
|
as well. The only reason <classname>GtkListStore</classname> exists is in
|
2014-07-23 19:48:39 +00:00
|
|
|
|
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>
|
|
|
|
|
|
2014-07-23 22:07:40 +00:00
|
|
|
|
<para><classname>GtkListStore</classname> and <classname>GtkTreeStore</classname>
|
2014-07-23 19:48:39 +00:00
|
|
|
|
should cater for most types of data an application developer might want to
|
2014-07-23 22:07:40 +00:00
|
|
|
|
display in a <classname>GtkTreeView</classname>. However, it should be noted
|
|
|
|
|
that <classname>GtkListStore</classname> and <classname>GtkTreeStore</classname>
|
2014-07-23 19:48:39 +00:00
|
|
|
|
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
|
2014-07-23 22:07:40 +00:00
|
|
|
|
implements the <classname>GtkTreeModel</classname> interface. This will not
|
2014-07-23 19:48:39 +00:00
|
|
|
|
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>
|
|
|
|
|
|
2014-07-23 22:07:40 +00:00
|
|
|
|
<para>Tree model implementations like <classname>GtkListStore</classname> and
|
|
|
|
|
<classname>GtkTreeStore</classname> will take care of the view side for you
|
|
|
|
|
once you have configured the <classname>GtkTreeView</classname> to display
|
2014-07-23 19:48:39 +00:00
|
|
|
|
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>
|
|
|
|
|
|
2014-07-23 22:07:40 +00:00
|
|
|
|
<para>Storing <classname>GObject</classname>-derived types (most
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<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">
|
2014-07-22 23:28:11 +00:00
|
|
|
|
GtkListStore *list_store;
|
|
|
|
|
|
|
|
|
|
list_store = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_UINT);
|
|
|
|
|
</programlisting>
|
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<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
|
2014-07-23 22:07:40 +00:00
|
|
|
|
will have to deal with are <classname>GtkTreeIter</classname> and
|
|
|
|
|
<classname>GtkTreePath</classname>.</para>
|
2014-07-23 19:48:39 +00:00
|
|
|
|
|
|
|
|
|
<sect2 id="sec-treemodel-rowref-path">
|
|
|
|
|
<title>GtkTreePath</title>
|
|
|
|
|
|
|
|
|
|
<subtitle>Describing a row 'geographically'</subtitle>
|
|
|
|
|
|
2014-07-23 22:07:40 +00:00
|
|
|
|
<para>A <classname>GtkTreePath</classname> is a comparatively
|
2014-07-23 19:48:39 +00:00
|
|
|
|
straight-forward way to describe the logical position of a row in the
|
2014-07-23 22:07:40 +00:00
|
|
|
|
model. As a <classname>GtkTreeView</classname> always displays
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<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
|
2014-07-23 22:07:40 +00:00
|
|
|
|
<classname>GtkTreeRowReference</classname>s below</link> for a tree path
|
2014-07-23 19:48:39 +00:00
|
|
|
|
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>
|
|
|
|
|
|
2014-07-23 22:07:40 +00:00
|
|
|
|
<para>You can get a new <classname>GtkTreePath</classname> from a path in
|
2014-07-23 19:48:39 +00:00
|
|
|
|
string form using <literal>gtk_tree_path_new_from_string</literal>,
|
2014-07-23 22:07:40 +00:00
|
|
|
|
and you can convert a given <classname>GtkTreePath</classname> into its
|
2014-07-23 19:48:39 +00:00
|
|
|
|
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>
|
|
|
|
|
|
2014-07-23 22:07:40 +00:00
|
|
|
|
<para>Instead of the string notation, <classname>GtkTreePath</classname>
|
2014-07-23 19:48:39 +00:00
|
|
|
|
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>
|
|
|
|
|
|
2014-07-23 22:07:40 +00:00
|
|
|
|
<para><classname>GtkTreePath</classname> is an opaque structure, with its
|
2014-07-23 19:48:39 +00:00
|
|
|
|
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
|
2014-07-23 22:07:40 +00:00
|
|
|
|
<classname>GtkTreeIter</classname>. A tree iter is just a structure that
|
2014-07-23 19:48:39 +00:00
|
|
|
|
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
|
2014-07-23 22:07:40 +00:00
|
|
|
|
<classname>GtkListStore</classname> and <classname>GtkTreeStore</classname>)
|
2014-07-23 19:48:39 +00:00
|
|
|
|
must support the <ulink
|
|
|
|
|
url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeModel.html">
|
2014-07-23 22:07:40 +00:00
|
|
|
|
<classname>GtkTreeModel</classname></ulink> functions that operate on tree
|
2014-07-23 19:48:39 +00:00
|
|
|
|
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">
|
2014-07-23 22:07:40 +00:00
|
|
|
|
<classname>GtkTreeModel</classname> API reference</ulink> for
|
2014-07-23 19:48:39 +00:00
|
|
|
|
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:
|
2014-07-23 22:07:40 +00:00
|
|
|
|
<classname>GtkTreeRowReference</classname></para>
|
2014-07-23 19:48:39 +00:00
|
|
|
|
</sect2>
|
|
|
|
|
|
|
|
|
|
<sect2 id="sec-treemodel-rowref-rowref">
|
|
|
|
|
<title>GtkTreeRowReference</title>
|
|
|
|
|
|
|
|
|
|
<subtitle>Keeping track of rows even when the model changes</subtitle>
|
|
|
|
|
|
2014-07-23 22:07:40 +00:00
|
|
|
|
<para>A <classname>GtkTreeRowReference</classname> is basically an object
|
2014-07-23 19:48:39 +00:00
|
|
|
|
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/></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
|
2014-07-23 22:07:40 +00:00
|
|
|
|
<classname>GtkListStore</classname> and <classname>GtkTreeStore</classname>
|
2014-07-23 19:48:39 +00:00
|
|
|
|
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
|
2014-07-23 22:07:40 +00:00
|
|
|
|
pointer value, which you can easily add to a <classname>GList</classname>
|
2014-07-23 19:48:39 +00:00
|
|
|
|
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">
|
2014-07-22 23:28:11 +00:00
|
|
|
|
/************************************************************
|
|
|
|
|
* *
|
|
|
|
|
* 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>
|
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<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>
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<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
|
2014-07-23 22:07:40 +00:00
|
|
|
|
<classname>GtkTreeIter</classname> is just a structure that contains data
|
2014-07-23 19:48:39 +00:00
|
|
|
|
fields you do not need to know anything about):</para>
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<programlisting role="C">
|
2014-07-22 23:28:11 +00:00
|
|
|
|
/************************************************************
|
|
|
|
|
* *
|
|
|
|
|
* 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>
|
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<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">
|
2014-07-22 23:28:11 +00:00
|
|
|
|
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>
|
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<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">
|
2014-07-22 23:28:11 +00:00
|
|
|
|
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>
|
2014-07-23 19:48:39 +00:00
|
|
|
|
</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">
|
2014-07-22 23:28:11 +00:00
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
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>
|
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<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">
|
2014-07-22 23:28:11 +00:00
|
|
|
|
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>
|
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<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
|
2014-07-23 22:07:40 +00:00
|
|
|
|
retrieve the row number from the <classname>GtkTreePath</classname> passed
|
2014-07-23 19:48:39 +00:00
|
|
|
|
to us in the foreach callback function:</para>
|
|
|
|
|
|
|
|
|
|
<programlisting role="C">
|
2014-07-22 23:28:11 +00:00
|
|
|
|
#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>
|
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<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>
|
|
|
|
|
|
2014-07-23 22:07:40 +00:00
|
|
|
|
<para>If you retrieve a <classname>GObject</classname> such as a
|
|
|
|
|
<classname>GdkPixbuf</classname> from the store,
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<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">
|
|
|
|
|
...
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
GdkPixbuf *pixbuf;
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
gtk_tree_model_get (model, &iter,
|
|
|
|
|
COL_PICTURE, &pixbuf,
|
|
|
|
|
NULL);
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
|
|
|
|
if (pixbuf != NULL)
|
|
|
|
|
{
|
|
|
|
|
do_something_with_pixbuf (pixbuf);
|
|
|
|
|
g_object_unref (pixbuf);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
...
|
|
|
|
|
</programlisting>
|
|
|
|
|
|
2014-07-23 22:07:40 +00:00
|
|
|
|
<para>Similarly, <classname>GBoxed</classname>-derived types retrieved
|
2014-07-23 19:48:39 +00:00
|
|
|
|
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
|
2014-07-23 22:07:40 +00:00
|
|
|
|
<classname>GBoxed</classname>).</para>
|
2014-07-23 19:48:39 +00:00
|
|
|
|
|
|
|
|
|
<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">
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
|
|
|
|
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>
|
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<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
|
2014-07-23 22:07:40 +00:00
|
|
|
|
<classname>GtkTreePath</classname> that describes that row and then turn it
|
2014-07-23 19:48:39 +00:00
|
|
|
|
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">
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
|
|
|
|
/******************************************************************
|
|
|
|
|
*
|
|
|
|
|
* 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>
|
2014-07-23 19:48:39 +00:00
|
|
|
|
</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">
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
|
|
|
|
/******************************************************************
|
|
|
|
|
*
|
|
|
|
|
* 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>
|
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<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>
|
|
|
|
|
|
2014-07-23 22:07:40 +00:00
|
|
|
|
<para>A special case are <classname>GObject</classname> types, like
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<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">
|
2014-07-22 23:28:11 +00:00
|
|
|
|
GtkListStore *list_store;
|
|
|
|
|
GtkTreeIter iter;
|
|
|
|
|
GdkPixbuf *pixbuf;
|
|
|
|
|
GError *error = NULL;
|
|
|
|
|
|
|
|
|
|
list_store = gtk_list_store_new (2, GDK_TYPE_PIXBUF, G_TYPE_STRING);
|
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
pixbuf = gdk_pixbuf_new_from_file("icon.png", &error);
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
|
|
|
|
/* pixbuf has a refcount of 1 after creation */
|
|
|
|
|
|
|
|
|
|
if (error)
|
|
|
|
|
{
|
2014-07-23 19:48:39 +00:00
|
|
|
|
g_critical ("Could not load pixbuf: %s\n", error->message);
|
2014-07-22 23:28:11 +00:00
|
|
|
|
g_error_free(error);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
gtk_list_store_append(list_store, &iter);
|
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
gtk_list_store_set(list_store, &iter, 0, pixbuf, 1, "foo", -1);
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
|
|
|
|
/* 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>
|
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<para>Having learned how to add, manipulate, and retrieve data from a
|
|
|
|
|
store, the next step is to get that data displayed in a
|
2014-07-23 22:07:40 +00:00
|
|
|
|
<classname>GtkTreeView</classname> widget.</para>
|
2014-07-23 19:48:39 +00:00
|
|
|
|
</sect1>
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<sect1 id="sec-treemodel-storing-structs">
|
|
|
|
|
<title>Storing Data Structures: of Pointers, GBoxed Types, and GObject
|
|
|
|
|
(TODO)</title>
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<para>Unfinished chapter.</para>
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<!--
|
2014-07-22 23:28:11 +00:00
|
|
|
|
<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>
|
|
|
|
|
|
|
|
|
|
-->
|
2014-07-23 19:48:39 +00:00
|
|
|
|
</sect1>
|
|
|
|
|
</chapter>
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<chapter id="sec-treeview">
|
|
|
|
|
<title>Creating a Tree View</title>
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<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>
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<para>A new tree view is created with:</para>
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<programlisting role="C">
|
|
|
|
|
GtkWidget *view;
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
view = gtk_tree_view_new();
|
|
|
|
|
</programlisting>
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<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>
|
|
|
|
|
|
2014-07-23 22:07:40 +00:00
|
|
|
|
<para>Tree models like <classname>GtkListStore</classname> and
|
|
|
|
|
<classname>GtkTreeStore</classname> are <classname>GObjects</classname> and
|
2014-07-23 19:48:39 +00:00
|
|
|
|
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">
|
2014-07-22 23:28:11 +00:00
|
|
|
|
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>
|
2014-07-23 19:48:39 +00:00
|
|
|
|
</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
|
2014-07-23 22:07:40 +00:00
|
|
|
|
themselves, this is done by specialised <classname>GtkCellRenderer</classname>
|
2014-07-23 19:48:39 +00:00
|
|
|
|
objects. Cell renderers are packed into tree view columns much like
|
2014-07-23 22:07:40 +00:00
|
|
|
|
widgets are packed into <classname>GtkHBox</classname>es.</para>
|
2014-07-23 19:48:39 +00:00
|
|
|
|
|
|
|
|
|
<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
|
2014-07-23 22:07:40 +00:00
|
|
|
|
rendering of data within a <classname>GtkTreeViewColumn</classname>. They
|
2014-07-23 19:48:39 +00:00
|
|
|
|
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">
|
2014-07-23 22:07:40 +00:00
|
|
|
|
<classname>GtkCellRendererText</classname></ulink> renders strings or
|
2014-07-23 19:48:39 +00:00
|
|
|
|
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">
|
2014-07-23 22:07:40 +00:00
|
|
|
|
<classname>GtkCellRendererPixbuf</classname></ulink> is used to display
|
2014-07-23 19:48:39 +00:00
|
|
|
|
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">
|
2014-07-23 22:07:40 +00:00
|
|
|
|
<classname>GtkCellRendererToggle</classname></ulink> displays a boolean
|
2014-07-23 19:48:39 +00:00
|
|
|
|
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">
|
2014-07-23 22:07:40 +00:00
|
|
|
|
<classname>GtkCellEditable</classname></ulink> is a special cell that
|
2014-07-23 19:48:39 +00:00
|
|
|
|
implements editable cells (ie. GtkEntry or GtkSpinbutton in a
|
|
|
|
|
treeview). This is not a cell renderer! If you want to have editable
|
2014-07-23 22:07:40 +00:00
|
|
|
|
text cells, use <classname>GtkCellRendererText</classname> and make sure
|
|
|
|
|
the "editable" property is set. <classname>GtkCellEditable</classname>
|
2014-07-23 19:48:39 +00:00
|
|
|
|
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
|
2014-07-23 22:07:40 +00:00
|
|
|
|
used properties of <classname>GtkCellRendererText</classname>:</para>
|
2014-07-23 19:48:39 +00:00
|
|
|
|
|
|
|
|
|
<programlisting role="C">
|
2014-07-22 23:28:11 +00:00
|
|
|
|
#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>
|
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<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">
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
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>
|
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<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">
|
2014-07-23 22:07:40 +00:00
|
|
|
|
<classname>GtkCellRendererText</classname></ulink>, even though the API
|
2014-07-23 19:48:39 +00:00
|
|
|
|
documentation does not list such a property. We can do this, because
|
2014-07-23 22:07:40 +00:00
|
|
|
|
<classname>GtkCellRendererText</classname> is derived from <ulink
|
2014-07-23 19:48:39 +00:00
|
|
|
|
url="http://developer.gnome.org/doc/API/2.0/gtk/GtkCellRenderer.html">
|
2014-07-23 22:07:40 +00:00
|
|
|
|
<classname>GtkCellRenderer</classname></ulink>, which does in fact <ulink
|
2014-07-23 19:48:39 +00:00
|
|
|
|
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
|
2014-07-23 22:07:40 +00:00
|
|
|
|
<classname>GtkCellRenderer</classname> properties: one is that sometimes
|
2014-07-23 19:48:39 +00:00
|
|
|
|
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
|
2014-07-23 22:07:40 +00:00
|
|
|
|
<classname>GtkCellRendererText</classname> (which specify the text colour).
|
2014-07-23 19:48:39 +00:00
|
|
|
|
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">
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
|
|
|
|
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>
|
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<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">
|
2014-07-22 23:28:11 +00:00
|
|
|
|
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>
|
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<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
|
2014-07-23 22:07:40 +00:00
|
|
|
|
pass on the <classname>GValue</classname> retrieved to
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<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">
|
2014-07-22 23:28:11 +00:00
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
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>
|
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<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>
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<programlisting role="C">
|
2014-07-22 23:28:11 +00:00
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
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>
|
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<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">
|
2014-07-22 23:28:11 +00:00
|
|
|
|
#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>
|
2014-07-23 19:48:39 +00:00
|
|
|
|
</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
|
2014-07-23 22:07:40 +00:00
|
|
|
|
<classname>GdkPixbuf</classname>s) has been introduced in the previous
|
2014-07-23 19:48:39 +00:00
|
|
|
|
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">
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
{
|
2014-07-23 19:48:39 +00:00
|
|
|
|
g_warning ("Could not load icon: %s\n", error->message);
|
2014-07-22 23:28:11 +00:00
|
|
|
|
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>
|
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<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">
|
2014-07-23 22:07:40 +00:00
|
|
|
|
stock icons</ulink> instead of <classname>GdkPixbuf</classname>s loaded from
|
2014-07-23 19:48:39 +00:00
|
|
|
|
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">
|
2014-07-23 22:07:40 +00:00
|
|
|
|
<classname>GtkCellRendererPixbuf</classname></ulink> (and your model column
|
2014-07-23 19:48:39 +00:00
|
|
|
|
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">
|
2014-07-23 22:07:40 +00:00
|
|
|
|
<classname>GtkTreeSelection</classname></ulink> object of a tree view. Every
|
|
|
|
|
tree view automatically has a <classname>GtkTreeSelection</classname>
|
2014-07-23 19:48:39 +00:00
|
|
|
|
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
|
2014-07-23 22:07:40 +00:00
|
|
|
|
to create this special <classname>GtkTreeSelection</classname> object that
|
2014-07-23 19:48:39 +00:00
|
|
|
|
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
|
2014-07-23 22:07:40 +00:00
|
|
|
|
a <classname>GList</classname> of tree paths of the selected rows using
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<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">
|
2014-07-22 23:28:11 +00:00
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
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>
|
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<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">
|
2014-07-22 23:28:11 +00:00
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
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>
|
2014-07-23 19:48:39 +00:00
|
|
|
|
</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">
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
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>
|
2014-07-23 19:48:39 +00:00
|
|
|
|
</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">
|
2014-07-22 23:28:11 +00:00
|
|
|
|
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>
|
2014-07-23 19:48:39 +00:00
|
|
|
|
</sect2>
|
|
|
|
|
</sect1>
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<sect1 id="sec-selections-double-click">
|
|
|
|
|
<title>Double-Clicks on a Row</title>
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<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>
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<programlisting role="C">
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
|
|
|
|
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>
|
2014-07-23 19:48:39 +00:00
|
|
|
|
</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">
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
|
|
|
|
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>
|
2014-07-23 19:48:39 +00:00
|
|
|
|
</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">
|
2014-07-23 22:07:40 +00:00
|
|
|
|
<classname>GtkTreeSortable</classname></ulink> interface that can be
|
2014-07-23 19:48:39 +00:00
|
|
|
|
implemented by tree models. 'Interface' means that you can just cast a
|
2014-07-23 22:07:40 +00:00
|
|
|
|
<classname>GtkTreeModel</classname> into a <classname>GtkTreeSortable</classname>
|
2014-07-23 19:48:39 +00:00
|
|
|
|
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>
|
2014-07-23 22:07:40 +00:00
|
|
|
|
family of functions. Both <classname>GtkListStore</classname> and
|
|
|
|
|
<classname>GtkTreeStore</classname> implement the tree sortable
|
2014-07-23 19:48:39 +00:00
|
|
|
|
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">
|
2014-07-23 22:07:40 +00:00
|
|
|
|
<classname>GtkTreeModelSort</classname></ulink> comes in, which is a special
|
2014-07-23 19:48:39 +00:00
|
|
|
|
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">
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
|
|
|
|
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)
|
2014-07-23 19:48:39 +00:00
|
|
|
|
break; /* both equal => ret = 0 */
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
{
|
2014-07-23 19:48:39 +00:00
|
|
|
|
ret = (year1 > year2) ? 1 : -1;
|
2014-07-22 23:28:11 +00:00
|
|
|
|
}
|
2014-07-23 19:48:39 +00:00
|
|
|
|
/* else both equal => ret = 0 */
|
2014-07-22 23:28:11 +00:00
|
|
|
|
}
|
|
|
|
|
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>
|
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<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">
|
2014-07-23 22:07:40 +00:00
|
|
|
|
<classname>GtkTreeModelSort</classname></ulink> is a wrapper tree model. It
|
2014-07-23 19:48:39 +00:00
|
|
|
|
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>
|
|
|
|
|
|
2014-07-23 22:07:40 +00:00
|
|
|
|
<para><classname>GtkTreeModelSort</classname> implements the
|
|
|
|
|
<classname>GtkTreeSortable</classname> interface, so you can treat it just
|
2014-07-23 19:48:39 +00:00
|
|
|
|
as if it was your data store for sorting purposes. Here is the basic
|
|
|
|
|
setup with a tree view:</para>
|
|
|
|
|
|
|
|
|
|
<programlisting role="C">
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
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>
|
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<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>
|
|
|
|
|
|
2014-07-23 22:07:40 +00:00
|
|
|
|
<para>With <classname>GtkCellRendererText</classname> you can not only
|
2014-07-23 19:48:39 +00:00
|
|
|
|
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">
|
2014-07-22 23:28:11 +00:00
|
|
|
|
g_object_set(renderer, "editable", TRUE, NULL);
|
|
|
|
|
</programlisting>
|
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<para>... when you create the renderer, which sets all rows in that
|
|
|
|
|
particular renderer column to be editable.</para>
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<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>
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<programlisting role="C">
|
2014-07-22 23:28:11 +00:00
|
|
|
|
g_signal_connect(renderer, "edited", (GCallback) cell_edited_callback, NULL);
|
|
|
|
|
</programlisting>
|
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<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>
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<para>The callback for the <literal>"edited"</literal> signal looks like
|
|
|
|
|
this (the API reference is a bit lacking in this particular
|
|
|
|
|
case):</para>
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<programlisting role="C">
|
2014-07-22 23:28:11 +00:00
|
|
|
|
void cell_edited_callback (GtkCellRendererText *cell,
|
|
|
|
|
gchar *path_string,
|
|
|
|
|
gchar *new_text,
|
|
|
|
|
gpointer user_data);
|
|
|
|
|
</programlisting>
|
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<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">
|
2014-07-22 23:28:11 +00:00
|
|
|
|
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>
|
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<para>where COLUMN_NAME and COLUMN_YEAR_OF_BIRTH are enum values. In
|
|
|
|
|
your callback you can then get the column number with</para>
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<programlisting role="C">
|
2014-07-22 23:28:11 +00:00
|
|
|
|
guint column_number = GPOINTER_TO_UINT(g_object_get_data(G_OBJECT(renderer), "my_column_num"));
|
|
|
|
|
</programlisting>
|
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<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>
|
|
|
|
|
|
2014-07-23 22:07:40 +00:00
|
|
|
|
<para>Just like you can set a <classname>GtkCellRendererText</classname>
|
2014-07-23 19:48:39 +00:00
|
|
|
|
editable, you can specify whether a
|
2014-07-23 22:07:40 +00:00
|
|
|
|
<classname>GtkCellRendererToggle</classname> should change its state when
|
2014-07-23 19:48:39 +00:00
|
|
|
|
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">
|
2014-07-22 23:28:11 +00:00
|
|
|
|
void cell_toggled_callback (GtkCellRendererToggle *cell,
|
|
|
|
|
gchar *path_string,
|
|
|
|
|
gpointer user_data);
|
|
|
|
|
</programlisting>
|
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<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
|
2014-07-23 22:07:40 +00:00
|
|
|
|
convert this into a <classname>GtkTreePath</classname> with <ulink
|
2014-07-23 19:48:39 +00:00
|
|
|
|
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>
|
|
|
|
|
|
2014-07-23 22:07:40 +00:00
|
|
|
|
<para>Even though <classname>GtkSpinButton</classname> implements the
|
|
|
|
|
<classname>GtkCellEditable</classname> interface (as does
|
|
|
|
|
<classname>GtkEntry</classname>), there is no easy way to get a cell
|
2014-07-23 19:48:39 +00:00
|
|
|
|
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
|
2014-07-23 22:07:40 +00:00
|
|
|
|
<classname>GtkCellRendererText</classname>, or you need to write a new cell
|
2014-07-23 19:48:39 +00:00
|
|
|
|
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
|
2014-07-23 22:07:40 +00:00
|
|
|
|
<classname>GtkCellRendererText</classname> and shows spin buttons in editing
|
2014-07-23 19:48:39 +00:00
|
|
|
|
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
|
2014-07-23 22:07:40 +00:00
|
|
|
|
<classname>GtkTreeViewColumn</classname> when the application programmer
|
2014-07-23 19:48:39 +00:00
|
|
|
|
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">
|
2014-07-22 23:28:11 +00:00
|
|
|
|
/* 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>
|
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<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>
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<programlisting role="C">
|
2014-07-22 23:28:11 +00:00
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
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();
|
2014-07-23 19:48:39 +00:00
|
|
|
|
g_object_set_data(G_OBJECT(col), "columnnum", GUINT_TO_POINTER(COL_FIRSTNAME));
|
2014-07-22 23:28:11 +00:00
|
|
|
|
...
|
|
|
|
|
col = gtk_tree_view_column_new();
|
2014-07-23 19:48:39 +00:00
|
|
|
|
g_object_set_data(G_OBJECT(col), "columnnum", GUINT_TO_POINTER(COL_SURNAME));
|
2014-07-22 23:28:11 +00:00
|
|
|
|
...
|
|
|
|
|
}
|
|
|
|
|
</programlisting>
|
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<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">
|
2014-07-22 23:28:11 +00:00
|
|
|
|
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;
|
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
/* Will this work for all packing modes? doesn't that
|
2014-07-22 23:28:11 +00:00
|
|
|
|
* 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))
|
2014-07-23 19:48:39 +00:00
|
|
|
|
g_print ("Renderer found\n");
|
2014-07-22 23:28:11 +00:00
|
|
|
|
else
|
2014-07-23 19:48:39 +00:00
|
|
|
|
g_print ("Renderer not found!\n");
|
2014-07-22 23:28:11 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
</programlisting>
|
2014-07-23 19:48:39 +00:00
|
|
|
|
</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
|
2014-07-23 22:07:40 +00:00
|
|
|
|
<classname>GtkTreeView</classname> in <ulink
|
2014-07-23 19:48:39 +00:00
|
|
|
|
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
|
2014-07-23 22:07:40 +00:00
|
|
|
|
<classname>GtkTreeView</classname> for you with nothing in it. You will need
|
2014-07-23 19:48:39 +00:00
|
|
|
|
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
|
2014-07-23 22:07:40 +00:00
|
|
|
|
<classname>GtkTreeViewColumn</classname>s and cell renderers to display the
|
2014-07-23 19:48:39 +00:00
|
|
|
|
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
|
2014-07-23 22:07:40 +00:00
|
|
|
|
<classname>GtkTreeView</classname> that sets up everything as you want it
|
2014-07-23 19:48:39 +00:00
|
|
|
|
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">
|
2014-07-22 23:28:11 +00:00
|
|
|
|
#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();
|
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
gtk_tree_view_column_set_title(col, "URI");
|
2014-07-22 23:28:11 +00:00
|
|
|
|
gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);
|
|
|
|
|
gtk_tree_view_column_pack_start(col, renderer, TRUE);
|
2014-07-23 19:48:39 +00:00
|
|
|
|
gtk_tree_view_column_add_attribute(col, renderer, "text", COL_URI);
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
|
|
|
|
gtk_tree_selection_set_mode(gtk_tree_view_get_selection(GTK_TREE_VIEW(view)),
|
|
|
|
|
GTK_SELECTION_SINGLE);
|
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
/* Make tree view a destination for Drag'n'Drop */
|
2014-07-22 23:28:11 +00:00
|
|
|
|
if (1)
|
|
|
|
|
{
|
|
|
|
|
enum
|
|
|
|
|
{
|
|
|
|
|
TARGET_STRING,
|
|
|
|
|
TARGET_URL
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static GtkTargetEntry targetentries[] =
|
|
|
|
|
{
|
2014-07-23 19:48:39 +00:00
|
|
|
|
{ "STRING", 0, TARGET_STRING },
|
|
|
|
|
{ "text/plain", 0, TARGET_STRING },
|
|
|
|
|
{ "text/uri-list", 0, TARGET_URL },
|
2014-07-22 23:28:11 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
gtk_drag_dest_set(view, GTK_DEST_DEFAULT_ALL, targetentries, 3,
|
|
|
|
|
GDK_ACTION_COPY|GDK_ACTION_MOVE|GDK_ACTION_LINK);
|
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
g_signal_connect(view, "drag_data_received",
|
2014-07-22 23:28:11 +00:00
|
|
|
|
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);
|
2014-07-23 19:48:39 +00:00
|
|
|
|
g_signal_connect(window, "delete_event", gtk_main_quit, NULL); /* dirty */
|
2014-07-22 23:28:11 +00:00
|
|
|
|
gtk_window_set_default_size(GTK_WINDOW(window), 400, 200);
|
|
|
|
|
|
|
|
|
|
vbox = gtk_vbox_new(FALSE, 0);
|
|
|
|
|
gtk_container_add(GTK_CONTAINER(window), vbox);
|
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
label = gtk_label_new("\nDrag and drop links from your browser into the tree view.\n");
|
2014-07-22 23:28:11 +00:00
|
|
|
|
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>
|
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<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>
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<programlisting role="C">
|
2014-07-22 23:28:11 +00:00
|
|
|
|
/***************************************************************************
|
|
|
|
|
*
|
|
|
|
|
* onDragMotion_expand_timeout
|
|
|
|
|
*
|
|
|
|
|
* Timeout used to make sure that we expand rows only
|
|
|
|
|
* after hovering about them for a certain amount
|
2014-07-23 19:48:39 +00:00
|
|
|
|
* of time while doing Drag'n'Drop
|
2014-07-22 23:28:11 +00:00
|
|
|
|
*
|
|
|
|
|
***************************************************************************/
|
|
|
|
|
|
|
|
|
|
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 */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/***************************************************************************
|
|
|
|
|
*
|
2014-07-23 19:48:39 +00:00
|
|
|
|
* view_onDragMotion: we don't want to expand unexpanded nodes
|
2014-07-22 23:28:11 +00:00
|
|
|
|
* 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;
|
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
|
|
|
|
</programlisting>
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<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>
|
|
|
|
|
|
2014-07-23 22:07:40 +00:00
|
|
|
|
<para>Both <classname>GtkListStore</classname> and
|
|
|
|
|
<classname>GtkTreeStore</classname> implement the
|
|
|
|
|
<classname>GtkTreeDragDest</classname> and
|
|
|
|
|
<classname>GtkTreeDragSource</classname> interfaces, which means that they
|
2014-07-23 19:48:39 +00:00
|
|
|
|
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
|
2014-07-23 22:07:40 +00:00
|
|
|
|
displaying all rows (Gtk+-2.4 has a filter model, <classname> <ulink
|
2014-07-23 19:48:39 +00:00
|
|
|
|
url="http://developer.gnome.org/doc/API/2.0/gtk/GtkTreeModelFilter.html">
|
2014-07-23 22:07:40 +00:00
|
|
|
|
GtkTreeModelFilter</ulink></classname>, that does exactly that and much
|
2014-07-23 19:48:39 +00:00
|
|
|
|
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
|
2014-07-23 22:07:40 +00:00
|
|
|
|
implements the <classname>GtkTreeModel</classname> interface,
|
|
|
|
|
<classname>GtkTreeModelIface</classname>. Intimate knowledge about the GLib
|
2014-07-23 19:48:39 +00:00
|
|
|
|
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
|
2014-07-23 22:07:40 +00:00
|
|
|
|
each row corresponds to a <classname>CustomRecord</classname> structure
|
2014-07-23 19:48:39 +00:00
|
|
|
|
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
|
2014-07-23 22:07:40 +00:00
|
|
|
|
fields in the <classname>CustomRecord</classname> structure.</para>
|
2014-07-23 19:48:39 +00:00
|
|
|
|
|
|
|
|
|
<para>Within the model, more precisely: the
|
2014-07-23 22:07:40 +00:00
|
|
|
|
<classname>CustomList</classname> structure, the list is stored as a pointer
|
2014-07-23 19:48:39 +00:00
|
|
|
|
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
|
2014-07-23 22:07:40 +00:00
|
|
|
|
<classname>CustomRecord</classname> structure. You can store whatever other
|
2014-07-23 19:48:39 +00:00
|
|
|
|
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">
|
2014-07-23 22:07:40 +00:00
|
|
|
|
<classname>GtkTreeIter</classname> fields</ulink> of the tree iters used by
|
2014-07-23 19:48:39 +00:00
|
|
|
|
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
|
2014-07-23 22:07:40 +00:00
|
|
|
|
<classname>CustomRecord</classname> structure in our model's tree iters,
|
2014-07-23 19:48:39 +00:00
|
|
|
|
which is valid as long as the row exists. Additionally we store the
|
2014-07-23 22:07:40 +00:00
|
|
|
|
position of a row within the list in the <classname>CustomRecord</classname>
|
2014-07-23 19:48:39 +00:00
|
|
|
|
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
|
2014-07-23 22:07:40 +00:00
|
|
|
|
and type check macros, our <classname>CustomRecord</classname> structure,
|
|
|
|
|
our <classname>CustomList</classname> structure, and some enums for the
|
2014-07-23 19:48:39 +00:00
|
|
|
|
model columns we are exporting.</para>
|
|
|
|
|
|
2014-07-23 22:07:40 +00:00
|
|
|
|
<para>The <classname>CustomRecord</classname> structure represents one
|
|
|
|
|
row, while the <classname>CustomList</classname> structure contains all
|
2014-07-23 19:48:39 +00:00
|
|
|
|
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
|
2014-07-23 22:07:40 +00:00
|
|
|
|
<classname>GtkTreeModel</classname> interface with our custom model
|
2014-07-23 19:48:39 +00:00
|
|
|
|
object. This is where we can also register additional interfaces (e.g.
|
2014-07-23 22:07:40 +00:00
|
|
|
|
<classname>GtkTreeSortable</classname> or one of the Drag'n'Drop
|
2014-07-23 19:48:39 +00:00
|
|
|
|
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">
|
2014-07-22 23:28:11 +00:00
|
|
|
|
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>
|
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<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>
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<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>
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<programlisting role="C">
|
2014-07-22 23:28:11 +00:00
|
|
|
|
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>
|
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<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
|
2014-07-23 22:07:40 +00:00
|
|
|
|
<classname>CustomList</classname> structure, using GLib N-ary trees for
|
2014-07-23 19:48:39 +00:00
|
|
|
|
example, or you could do this by keeping track of each row's children
|
2014-07-23 22:07:40 +00:00
|
|
|
|
within the row's <classname>CustomRecord</classname> structure, keeping only
|
2014-07-23 19:48:39 +00:00
|
|
|
|
a pointer to the (invisible) root record in the
|
2014-07-23 22:07:40 +00:00
|
|
|
|
<classname>CustomList</classname> structure.</para>
|
2014-07-23 19:48:39 +00:00
|
|
|
|
|
|
|
|
|
<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
|
2014-07-23 22:07:40 +00:00
|
|
|
|
example of the <classname>GtkTreeSortable</classname> interface, which we
|
2014-07-23 19:48:39 +00:00
|
|
|
|
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">
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
|
|
|
|
/* 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>
|
|
|
|
|
|
2014-07-23 22:07:40 +00:00
|
|
|
|
<para>Next, let's extend our <classname>CustomList</classname> structure
|
2014-07-23 19:48:39 +00:00
|
|
|
|
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>
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<programlisting role="C">
|
2014-07-22 23:28:11 +00:00
|
|
|
|
/* 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>
|
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<para>Now, we make sure we initialise the new fields in
|
|
|
|
|
<literal>custom_list_new</literal>, and add our new interface:</para>
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<programlisting role="C">
|
2014-07-22 23:28:11 +00:00
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
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>
|
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<para>Now that we have finally taken care of the administrativa, we
|
|
|
|
|
implement the tree sortable interface functions:</para>
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<programlisting role="C">
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
|
|
|
|
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>
|
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<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>
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<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>
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<programlisting role="C">
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
|
|
|
|
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>
|
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<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>
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<programlisting role="C">
|
2014-07-22 23:28:11 +00:00
|
|
|
|
...
|
|
|
|
|
void
|
|
|
|
|
custom_list_append_record (CustomList *custom_list, const gchar *name, guint year_born)
|
|
|
|
|
{
|
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
custom_list_resort(custom_list);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
</programlisting>
|
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<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>
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<para>If you are interested in seeing string sorting speed issues in
|
|
|
|
|
action, you should modify main.c like this:</para>
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<programlisting role="C">
|
2014-07-22 23:28:11 +00:00
|
|
|
|
GtkWidget *
|
|
|
|
|
create_view_and_model (void)
|
|
|
|
|
{
|
|
|
|
|
gint i;
|
|
|
|
|
...
|
|
|
|
|
for (i=0; i < 1000; ++i)
|
|
|
|
|
{
|
|
|
|
|
fill_model(customlist);
|
|
|
|
|
}
|
|
|
|
|
...
|
|
|
|
|
}
|
|
|
|
|
</programlisting>
|
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<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>
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<programlisting role="C">
|
2014-07-22 23:28:11 +00:00
|
|
|
|
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>
|
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<para>... then you should hopefully register a dramatic speed increase
|
|
|
|
|
when sorting by name.</para>
|
|
|
|
|
</sect1>
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<sect1 id="sec-custom-model-code">
|
|
|
|
|
<title>Working Example: Custom List Model Source Code</title>
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<para>Here is the complete source code for the custom list model
|
|
|
|
|
presented <link linkend="sec-custom-model-list">above</link>. Compile
|
|
|
|
|
with:</para>
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<programlisting>
|
2014-07-22 23:28:11 +00:00
|
|
|
|
gcc -o customlist custom-list.c main.c `pkg-config --cflags --libs gtk+-2.0`
|
|
|
|
|
</programlisting>
|
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<itemizedlist>
|
|
|
|
|
<listitem>
|
|
|
|
|
<para><link
|
|
|
|
|
linkend="sec-custom-model-code-header">custom-list.h</link></para>
|
|
|
|
|
</listitem>
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<listitem>
|
|
|
|
|
<para><link
|
|
|
|
|
linkend="sec-custom-model-code-body">custom-list.c</link></para>
|
|
|
|
|
</listitem>
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<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">
|
2014-07-22 23:28:11 +00:00
|
|
|
|
#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>
|
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<itemizedlist>
|
|
|
|
|
<listitem>
|
|
|
|
|
<para><link
|
|
|
|
|
linkend="sec-custom-model-code-header">custom-list.h</link></para>
|
|
|
|
|
</listitem>
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<listitem>
|
|
|
|
|
<para><link
|
|
|
|
|
linkend="sec-custom-model-code-body">custom-list.c</link></para>
|
|
|
|
|
</listitem>
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<listitem>
|
|
|
|
|
<para><link
|
|
|
|
|
linkend="sec-custom-model-code-example">main.c</link></para>
|
|
|
|
|
</listitem>
|
|
|
|
|
</itemizedlist>
|
|
|
|
|
</sect2>
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<sect2 id="sec-custom-model-code-body">
|
|
|
|
|
<title>custom-list.c</title>
|
|
|
|
|
|
|
|
|
|
<programlisting role="C">
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
|
|
|
|
#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>
|
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<itemizedlist>
|
|
|
|
|
<listitem>
|
|
|
|
|
<para><link
|
|
|
|
|
linkend="sec-custom-model-code-header">custom-list.h</link></para>
|
|
|
|
|
</listitem>
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<listitem>
|
|
|
|
|
<para><link
|
|
|
|
|
linkend="sec-custom-model-code-body">custom-list.c</link></para>
|
|
|
|
|
</listitem>
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<listitem>
|
|
|
|
|
<para><link
|
|
|
|
|
linkend="sec-custom-model-code-example">main.c</link></para>
|
|
|
|
|
</listitem>
|
|
|
|
|
</itemizedlist>
|
|
|
|
|
</sect2>
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<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">
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
|
|
|
|
#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>
|
2014-07-23 19:48:39 +00:00
|
|
|
|
</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
|
2014-07-23 22:07:40 +00:00
|
|
|
|
<classname>GtkCellRenderer</classname> (or even one of the other cell
|
2014-07-23 19:48:39 +00:00
|
|
|
|
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
|
2014-07-23 22:07:40 +00:00
|
|
|
|
is of type <classname>GtkCellRenderer</classname>. Note that you should
|
2014-07-23 19:48:39 +00:00
|
|
|
|
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
|
2014-07-23 22:07:40 +00:00
|
|
|
|
<classname>GtkCellRendererPixbuf</classname> and
|
|
|
|
|
<classname>GtkCellRendererToggle</classname> in the Gtk+ source code tree.
|
2014-07-23 19:48:39 +00:00
|
|
|
|
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
|
2014-07-23 22:07:40 +00:00
|
|
|
|
check defines and our <classname>CustomCellRendererProgress</classname>
|
2014-07-23 19:48:39 +00:00
|
|
|
|
structure. As the type of the parent indicates, we derive from
|
2014-07-23 22:07:40 +00:00
|
|
|
|
<classname>GtkCellRenderer</classname>. The parent object must always be
|
2014-07-23 19:48:39 +00:00
|
|
|
|
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>
|
|
|
|
|
|
2014-07-23 22:07:40 +00:00
|
|
|
|
<para>Our <classname>CustomCellRendererProgress</classname> structure is
|
2014-07-23 19:48:39 +00:00
|
|
|
|
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">
|
2014-07-22 23:28:11 +00:00
|
|
|
|
#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>
|
2014-07-23 19:48:39 +00:00
|
|
|
|
</sect2>
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<sect2 id="sec-custom-cell-renderer-example-body">
|
|
|
|
|
<title>custom-cell-renderer-progressbar.c</title>
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<para>The code contains everything as described above, so let's jump
|
|
|
|
|
right into it:</para>
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<programlisting role="C">
|
|
|
|
|
#include "custom-cell-renderer-progressbar.h"
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
|
|
|
|
/* This is based mainly on GtkCellRendererProgress
|
|
|
|
|
* in GAIM, written and (c) 2002 by Sean Egan
|
|
|
|
|
* (Licensed under the GPL), which in turn is
|
2014-07-23 19:48:39 +00:00
|
|
|
|
* based on Gtk's GtkCellRenderer[Text|Toggle|Pixbuf]
|
2014-07-22 23:28:11 +00:00
|
|
|
|
* 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
|
2014-07-23 19:48:39 +00:00
|
|
|
|
* haven't done so yet. Everything
|
2014-07-22 23:28:11 +00:00
|
|
|
|
* 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,
|
2014-07-23 19:48:39 +00:00
|
|
|
|
"CustomCellRendererProgress",
|
2014-07-22 23:28:11 +00:00
|
|
|
|
&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
|
2014-07-23 19:48:39 +00:00
|
|
|
|
* override the parent's functions that we need to implement.
|
|
|
|
|
* And make our new "percentage" property known to the type system.
|
2014-07-22 23:28:11 +00:00
|
|
|
|
* If you want cells that can be activated on their own (ie. not
|
|
|
|
|
* just the whole row selected) or cells that are editable, you
|
2014-07-23 19:48:39 +00:00
|
|
|
|
* will need to override 'activate' and 'start_editing' as well.
|
2014-07-22 23:28:11 +00:00
|
|
|
|
*
|
|
|
|
|
***************************************************************************/
|
|
|
|
|
|
|
|
|
|
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,
|
2014-07-23 19:48:39 +00:00
|
|
|
|
g_param_spec_double ("percentage",
|
|
|
|
|
"Percentage",
|
|
|
|
|
"The fractional progress to display",
|
2014-07-22 23:28:11 +00:00
|
|
|
|
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,
|
2014-07-23 19:48:39 +00:00
|
|
|
|
NULL, widget, "trough",
|
2014-07-22 23:28:11 +00:00
|
|
|
|
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,
|
2014-07-23 19:48:39 +00:00
|
|
|
|
NULL, widget, "bar",
|
2014-07-22 23:28:11 +00:00
|
|
|
|
cell_area->x + x_offset + cell->xpad,
|
|
|
|
|
cell_area->y + y_offset + cell->ypad,
|
|
|
|
|
width * cellprogress->progress,
|
|
|
|
|
height - 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
</programlisting>
|
2014-07-23 19:48:39 +00:00
|
|
|
|
</sect2>
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<sect2 id="sec-custom-cell-renderer-example-test">
|
|
|
|
|
<title>main.c</title>
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<para>And here is a little test that makes use of our new
|
2014-07-23 22:07:40 +00:00
|
|
|
|
<classname>CustomCellRendererProgress</classname>:</para>
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
<programlisting role="C">
|
|
|
|
|
#include "custom-cell-renderer-progressbar.h"
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
2014-07-23 19:48:39 +00:00
|
|
|
|
g_snprintf(buf, sizeof(buf), "%u %%", (guint)(perc*100));
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
|
|
|
|
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);
|
2014-07-23 19:48:39 +00:00
|
|
|
|
gtk_tree_view_column_add_attribute (col, renderer, "text", COL_TEXT);
|
|
|
|
|
gtk_tree_view_column_set_title (col, "Progress");
|
2014-07-22 23:28:11 +00:00
|
|
|
|
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);
|
2014-07-23 19:48:39 +00:00
|
|
|
|
gtk_tree_view_column_add_attribute (col, renderer, "percentage", COL_PERCENTAGE);
|
|
|
|
|
gtk_tree_view_column_set_title (col, "Progress");
|
2014-07-22 23:28:11 +00:00
|
|
|
|
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);
|
2014-07-23 19:48:39 +00:00
|
|
|
|
g_signal_connect(window, "delete_event", gtk_main_quit, NULL);
|
2014-07-22 23:28:11 +00:00
|
|
|
|
|
|
|
|
|
view = create_view_and_model();
|
|
|
|
|
|
|
|
|
|
gtk_container_add(GTK_CONTAINER(window), view);
|
|
|
|
|
|
|
|
|
|
gtk_widget_show_all(window);
|
|
|
|
|
|
|
|
|
|
gtk_main();
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
</programlisting>
|
2014-07-23 19:48:39 +00:00
|
|
|
|
</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>
|
2014-07-22 23:28:11 +00:00
|
|
|
|
</book>
|