The TnyGtkHeaderListModel
Tinymail 1.0
http://tinymail.org/API/libtinymail-1.0/TnyGtkHeaderListModel.html
About
This type is a Gtk+ type. This means that it depends on the Gtk+ ui toolkit. The header-list-model is a model for a GtkTreeView. It stores in an efficient way TnyHeader instances and if used with a correctly configured GtkTreeView (using a Model View Controller pattern), it makes them visible. It implements both GtkTreeModel and TnyList. Although it's recommended to only use the TnyList API on instances of this type, this means that you can use both the TnyList and GtkTreeModel API with it.
By itself wont the TnyGtkHeaderListModel get automatically updated when for example Push E-mail events happen. In combination with a TnyFolderMonitor, however, this component will be turned into an automatically updated list of TnyHeader instances that came from a TnyFolder.
It's possible to let it automatically get filled up while downloading the summary of a folder takes place. This is what some people refer to as incrementally filling up the view while downloading the summary of a folder. It indeed works the exact same way as the Push E-mail feature. Downloads of individual message summary items (which are eventually converted to TnyHeader intances), is handled in the same way as new information coming from Push E-mail (which on IMAP gets its information during the IDLE state).
Usage
static void
on_select_new_folder (MyPriv *priv, GtkTreeView *view, TnyFolder *folder)
{
GtkTreeModel *model = tny_gtk_header_list_model_new ();
tny_gtk_header_list_model_set_folder
(TNY_GTK_HEADER_LIST_MODEL (model), folder, FALSE);
/* Set up the view with columns, etc etc */
gtk_tree_view_set_model (view, model);
priv->view = view;
}
static void
on_reload_accounts (MyPriv *priv, GtkTreeView *view)
{
gtk_tree_view_set_model (priv->view, NULL);
}
Usage with a TnyFolderMonitor
About
When new headers get added, you might want your model to get automatically updated. A good example is when that model is used by the currently active summary view, and the user doesn't want to use a so called "refresh / retrieve" button.
Among the things that Push E-mail mean, it also means that this summary view and its underlying model get automatically updated by pushes that are initiated by the server. By that I mean that the server will push the fact that there is a new message all the way through the service layer, to the model and that way eventually also to the view. The server also pushes changes to the flags (the read and unread status are examples of information about messages that is handled by these flags) and expunges (permanent removals).
The observer design pattern, abstract
In Tinymail this feature utilizes the observer design pattern. Differently put (not using the "design pattern" buzzword) this means that it's using the "don't call me, I will call you" principle. It means that the view nor the model will initiate a call to know about changes. In abstract, the model registers itself as an observer of the TnyFolder instance. The view being an observer of that model, will get updated whenever the model changes. Whenever the folder issues a change, it will notify that change to all its observers. To put this in perspective: the folder called the model, the model didn't call the folder (don't call me, I will call you).
The TnyFolderMonitor, in practice
Less abstract but rather practically it was unpractical to let all TnyList implementations, like the TnyGtkHeaderListModel, also implement TnyFolderObserver. In stead, the TnyFolderMonitor plays the role of that TnyFolderObserver. The TnyFolderMonitor instance can have zero or more TnyList instances that it will update upon change notifications coming from the TnyFolder.
And in code …
You will see a "priv" variable here. That 'priv' stands for an instance of a structure that holds some private information. I used it to indicate that you should store a reference to your monitor instance somewhere. That's because when a new folder gets selected, you will need to stop and unreference the monitor. Then you create a new one for the newly selected folder.
If you don't do that, you will have a big memory leak: the TnyList instance will be kept alive. That's because it will still be referenced by the TnyFolderMonitor. The instances that by far consume most of the memory are the lists of TnyHeader instances when displaying the summary of a folder. You really don't want to leak those, So make sure that you correctly cleanup (stop and unreference) the TnyFolderMonitor.
static void
on_select_new_folder (MyPriv *priv, GtkTreeView *view, TnyFolder *folder)
{
GtkTreeModel *model = tny_gtk_header_list_model_new ();
tny_gtk_header_list_model_set_folder
(TNY_GTK_HEADER_LIST_MODEL (model), folder, FALSE);
if (priv->monitor) {
tny_folder_monitor_stop (priv->monitor);
g_object_unref (G_OBJECT (priv->monitor));
}
priv->monitor = tny_folder_monitor_new (folder);
tny_folder_monitor_add_list (priv->monitor,
TNY_LIST (model));
tny_folder_monitor_start (priv->monitor);
/* Set up the view with columns, etc etc */
gtk_tree_view_set_model (view, model);
priv->view = view;
}
static void
on_reload_accounts (MyPriv *priv, GtkTreeView *view)
{
if (priv->monitor) {
tny_folder_monitor_stop (priv->monitor);
g_object_unref (G_OBJECT (priv->monitor));
}
priv->monitor = NULL;
gtk_tree_view_set_model (view, NULL);
}
Caveat for above code
When you have a "unload account" or "reload the account" feature, or something that resets the summary view, or whatever: then also stop and unreference the folder monitor. If you forget this, you'll have a memory leak. That's because the TnyList instances that are registered with that TnyFolderMonitor will not be unreferenced.
The tny_folder_monitor_add_list does indeed add a reference to your list. Only both stopping and unreferencing the monitor will decrease that reference. You have to stop and unreference it.
Configuring the GtkTreeView
TnyFolder *folder;
GtkTreeView *view = gtk_tree_view_new ();
GtkCellRenderer *renderer = gtk_cell_renderer_text_new ();
GtkTreeColumn *column = gtk_tree_view_column_new_with_attributes
(_("From"), renderer, "text",
TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN, NULL);
gtk_tree_view_append_column (view, column);
renderer = gtk_cell_renderer_text_new ();
column = gtk_tree_view_column_new_with_attributes
(_("Subject"), renderer, "text",
TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, NULL);
gtk_tree_view_append_column (view, column);
folder = get_inbox ();
on_select_new_folder (priv, view, folder);
Making it sortable
There are two pre-implemented GtkTreeIterCompareFunc functions available in the libtinymailui-gtk library:
- tny_gtk_header_list_model_received_date_sort_func
- tny_gtk_header_list_model_sent_date_sort_func
These two implement fast compare functions for the date-received and the date-sent functions. For the other columns it's possible to let the standard infrastructure of GtkTreeView handle the comparing and sorting for you (it'll use a qsort variant internally). You can also provide yuor own GtkTreeIterCompareFunc implementations for this.
GtkTreeModel *oldsortable, *sortable, *model;
GtkTreeView *view = ...;
TnyFolder *folder = ...;
model = tny_gtk_header_list_model_new ();
tny_gtk_header_list_model_set_folder
(TNY_GTK_HEADER_LIST_MODEL (model), folder, FALSE);
oldsortable = gtk_tree_view_get_model (view);
if (oldsortable && GTK_IS_TREE_MODEL_SORT (oldsortable))
{
GtkTreeModel *oldmodel = gtk_tree_model_sort_get_model
(GTK_TREE_MODEL_SORT (oldsortable));
if (oldmodel)
g_object_unref (G_OBJECT (oldmodel));
g_object_unref (G_OBJECT (oldsortable));
}
sortable = gtk_tree_model_sort_new_with_model (model);
gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
TNY_GTK_HEADER_LIST_MODEL_DATE_RECEIVED_COLUMN,
tny_gtk_header_list_model_received_date_sort_func,
NULL, NULL);
gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_COLUMN,
tny_gtk_header_list_model_sent_date_sort_func,
NULL, NULL);
gtk_tree_view_set_model (view, sortable);
Caveat for above samples
Always make sure that 'model' is unreferenced if it's no longer needed. The list of TnyHeader instances (which is what those models are) probably consumes most of the memory. If the model instance doesn't get unreferenced, all will be kept in memory for ever and big memory leaks will be the result of that.
Getting one TnyHeader instance from the model
static void
on_header_view_tree_row_activated (GtkTreeView *treeview, GtkTreePath *path,
GtkTreeViewColumn *col, gpointer userdata) {
GtkTreeModel *model;
GtkTreeIter iter;
model = gtk_tree_view_get_model(treeview);
if (gtk_tree_model_get_iter(model, &iter, path))
{
TnyHeader *header;
gtk_tree_model_get (model, &iter,
TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
&header, -1);
if (header) {
printf ("%s\n", tny_header_get_subject (header));
g_object_unref (G_OBJECT (header));
}
}
}
Caveat for above sample
Always make sure that 'header' is unreferenced if it's no longer needed. Each living header instance will keep the model alive (it will not get its final reference count at zero). This will introduce a big memory leak, as this means that every TnyHeader instance in the model will be kept alive with it. You need to unreference the header instances that you have been using. When used on object columns (like the INSTANCE_COLUMN of the TnyGtkHeaderListModel), the gtk_tree_model_get API adds a reference. You need to get rid of that reference once you are done using the instance.
