Files
evolution/mail/em-folder-tree-model.c
Not Zed d3a7e6343b when we sort, handle not having the node in the tree. otherwise we always
2004-03-11  Not Zed  <NotZed@Ximian.com>

        * em-folder-tree-model.c (sort_cb): when we sort, handle not
        having the node in the tree.  otherwise we always compare against
        "" which puts it at the head of the branch, rather than the tail.
        See #55428.

svn path=/trunk/; revision=25024
2004-03-11 07:02:58 +00:00

997 lines
27 KiB
C

/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* Authors: Jeffrey Stedfast <fejj@ximian.com>
*
* Copyright 2003 Ximian, Inc. (www.ximian.com)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <e-util/e-mktemp.h>
#include <camel/camel-file-utils.h>
#include "mail-config.h"
#include "mail-session.h"
#include "mail-tools.h"
#include "mail-mt.h"
/* sigh, these 2 only needed for outbox total count checking - a mess */
#include "mail-component.h"
#include "mail-folder-cache.h"
#include "em-utils.h"
#include "em-marshal.h"
#include "em-folder-tree-model.h"
#define u(x) /* unread count debug */
#define d(x) x
static GType col_types[] = {
G_TYPE_STRING, /* display name */
G_TYPE_POINTER, /* store object */
G_TYPE_STRING, /* path */
G_TYPE_STRING, /* uri */
G_TYPE_UINT, /* unread count */
G_TYPE_UINT, /* flags */
G_TYPE_BOOLEAN, /* is a store node */
G_TYPE_BOOLEAN, /* has not-yet-loaded subfolders */
};
/* GObject virtual method overrides */
static void em_folder_tree_model_class_init (EMFolderTreeModelClass *klass);
static void em_folder_tree_model_init (EMFolderTreeModel *model);
static void em_folder_tree_model_finalize (GObject *obj);
/* interface init methods */
static void tree_model_iface_init (GtkTreeModelIface *iface);
static void tree_sortable_iface_init (GtkTreeSortableIface *iface);
static void account_changed (EAccountList *accounts, EAccount *account, gpointer user_data);
static void account_removed (EAccountList *accounts, EAccount *account, gpointer user_data);
enum {
LOADING_ROW,
FOLDER_ADDED,
LAST_SIGNAL
};
static guint signals[LAST_SIGNAL] = { 0, };
static GtkTreeStoreClass *parent_class = NULL;
GType
em_folder_tree_model_get_type (void)
{
static GType type = 0;
if (!type) {
static const GTypeInfo info = {
sizeof (EMFolderTreeModelClass),
NULL, /* base_class_init */
NULL, /* base_class_finalize */
(GClassInitFunc) em_folder_tree_model_class_init,
NULL, /* class_finalize */
NULL, /* class_data */
sizeof (EMFolderTreeModel),
0, /* n_preallocs */
(GInstanceInitFunc) em_folder_tree_model_init,
};
static const GInterfaceInfo tree_model_info = {
(GInterfaceInitFunc) tree_model_iface_init,
NULL,
NULL
};
static const GInterfaceInfo sortable_info = {
(GInterfaceInitFunc) tree_sortable_iface_init,
NULL,
NULL
};
type = g_type_register_static (GTK_TYPE_TREE_STORE, "EMFolderTreeModel", &info, 0);
g_type_add_interface_static (type, GTK_TYPE_TREE_MODEL,
&tree_model_info);
g_type_add_interface_static (type, GTK_TYPE_TREE_SORTABLE,
&sortable_info);
}
return type;
}
static void
em_folder_tree_model_class_init (EMFolderTreeModelClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
parent_class = g_type_class_ref (GTK_TYPE_TREE_STORE);
object_class->finalize = em_folder_tree_model_finalize;
/* signals */
signals[LOADING_ROW] =
g_signal_new ("loading-row",
G_OBJECT_CLASS_TYPE (object_class),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (EMFolderTreeModelClass, loading_row),
NULL, NULL,
em_marshal_VOID__POINTER_POINTER,
G_TYPE_NONE, 2,
G_TYPE_POINTER,
G_TYPE_POINTER);
signals[FOLDER_ADDED] =
g_signal_new ("folder-added",
G_OBJECT_CLASS_TYPE (object_class),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (EMFolderTreeModelClass, loading_row),
NULL, NULL,
em_marshal_VOID__STRING_STRING,
G_TYPE_NONE, 2,
G_TYPE_STRING,
G_TYPE_STRING);
}
static int
sort_cb (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer user_data)
{
extern CamelStore *vfolder_store;
char *aname, *bname;
CamelStore *store;
gboolean is_store;
gtk_tree_model_get (model, a, COL_BOOL_IS_STORE, &is_store,
COL_POINTER_CAMEL_STORE, &store,
COL_STRING_DISPLAY_NAME, &aname, -1);
gtk_tree_model_get (model, b, COL_STRING_DISPLAY_NAME, &bname, -1);
if (is_store) {
/* On This Computer is always first and VFolders is always last */
if (!strcmp (aname, _("On This Computer")))
return -1;
if (!strcmp (bname, _("On This Computer")))
return 1;
if (!strcmp (aname, _("VFolders")))
return 1;
if (!strcmp (bname, _("VFolders")))
return -1;
} else if (store == vfolder_store) {
/* perform no sorting, we want to display in the same
* order as they appear in the VFolder editor - UNMATCHED is always last */
GtkTreePath *path;
int ret;
if (aname && !strcmp (aname, _("UNMATCHED")))
return 1;
if (bname && !strcmp (bname, _("UNMATCHED")))
return -1;
path = gtk_tree_model_get_path (model, a);
if (path) {
aname = gtk_tree_path_to_string (path);
gtk_tree_path_free (path);
} else {
return 1;
}
path = gtk_tree_model_get_path (model, b);
if (path) {
bname = gtk_tree_path_to_string (path);
gtk_tree_path_free (path);
} else {
g_free(aname);
return -1;
}
ret = strcmp (aname, bname);
g_free (aname);
g_free (bname);
return ret;
} else {
/* Inbox is always first */
if (aname && (!strcmp (aname, "INBOX") || !strcmp (aname, _("Inbox"))))
return -1;
if (bname && (!strcmp (bname, "INBOX") || !strcmp (bname, _("Inbox"))))
return 1;
}
if (aname == NULL) {
if (bname == NULL)
return 0;
} else if (bname == NULL)
return 1;
return g_utf8_collate (aname, bname);
}
static void
em_folder_tree_model_init (EMFolderTreeModel *model)
{
model->store_hash = g_hash_table_new (g_direct_hash, g_direct_equal);
model->uri_hash = g_hash_table_new (g_str_hash, g_str_equal);
model->expanded = g_hash_table_new (g_str_hash, g_str_equal);
gtk_tree_sortable_set_default_sort_func ((GtkTreeSortable *) model, sort_cb, NULL, NULL);
model->accounts = mail_config_get_accounts ();
model->account_hash = g_hash_table_new (g_direct_hash, g_direct_equal);
model->account_changed_id = g_signal_connect (model->accounts, "account-changed", G_CALLBACK (account_changed), model);
model->account_removed_id = g_signal_connect (model->accounts, "account-removed", G_CALLBACK (account_removed), model);
}
static void
path_hash_free (gpointer key, gpointer value, gpointer user_data)
{
g_free (key);
gtk_tree_row_reference_free (value);
}
static void
store_info_free (struct _EMFolderTreeModelStoreInfo *si)
{
camel_object_remove_event (si->store, si->created_id);
camel_object_remove_event (si->store, si->deleted_id);
camel_object_remove_event (si->store, si->renamed_id);
camel_object_remove_event (si->store, si->subscribed_id);
camel_object_remove_event (si->store, si->unsubscribed_id);
g_free (si->display_name);
camel_object_unref (si->store);
gtk_tree_row_reference_free (si->row);
g_hash_table_foreach (si->path_hash, path_hash_free, NULL);
g_free (si);
}
static void
store_hash_free (gpointer key, gpointer value, gpointer user_data)
{
struct _EMFolderTreeModelStoreInfo *si = value;
store_info_free (si);
}
static void
uri_hash_free (gpointer key, gpointer value, gpointer user_data)
{
g_free (key);
gtk_tree_row_reference_free (value);
}
static gboolean
expanded_free (gpointer key, gpointer value, gpointer user_data)
{
g_free (key);
return TRUE;
}
static void
em_folder_tree_model_finalize (GObject *obj)
{
EMFolderTreeModel *model = (EMFolderTreeModel *) obj;
g_hash_table_foreach (model->store_hash, store_hash_free, NULL);
g_hash_table_destroy (model->store_hash);
g_hash_table_foreach (model->uri_hash, uri_hash_free, NULL);
g_hash_table_destroy (model->uri_hash);
g_hash_table_foreach (model->expanded, (GHFunc) expanded_free, NULL);
g_hash_table_destroy (model->expanded);
g_hash_table_destroy (model->account_hash);
g_signal_handler_disconnect (model->accounts, model->account_changed_id);
g_signal_handler_disconnect (model->accounts, model->account_removed_id);
g_free (model->filename);
G_OBJECT_CLASS (parent_class)->finalize (obj);
}
static void
tree_model_iface_init (GtkTreeModelIface *iface)
{
;
}
static void
tree_sortable_iface_init (GtkTreeSortableIface *iface)
{
;
}
static void
em_folder_tree_model_load_state (EMFolderTreeModel *model, const char *filename)
{
char *node;
FILE *fp;
g_hash_table_foreach_remove (model->expanded, expanded_free, NULL);
if ((fp = fopen (filename, "r")) == NULL)
return;
while (camel_file_util_decode_string (fp, &node) != -1)
g_hash_table_insert (model->expanded, node, GINT_TO_POINTER (TRUE));
fclose (fp);
}
EMFolderTreeModel *
em_folder_tree_model_new (const char *evolution_dir)
{
EMFolderTreeModel *model;
char *filename;
model = g_object_new (EM_TYPE_FOLDER_TREE_MODEL, NULL);
gtk_tree_store_set_column_types ((GtkTreeStore *) model, NUM_COLUMNS, col_types);
gtk_tree_sortable_set_sort_column_id ((GtkTreeSortable *) model,
GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID,
GTK_SORT_ASCENDING);
filename = g_build_filename (evolution_dir, "mail", "config", "folder-tree.state", NULL);
em_folder_tree_model_load_state (model, filename);
model->filename = filename;
return model;
}
static void
account_changed (EAccountList *accounts, EAccount *account, gpointer user_data)
{
EMFolderTreeModel *model = user_data;
struct _EMFolderTreeModelStoreInfo *si;
CamelProvider *provider;
CamelStore *store;
CamelException ex;
char *uri;
if (!(si = g_hash_table_lookup (model->account_hash, account)))
return;
em_folder_tree_model_remove_store (model, si->store);
if (!(uri = account->source->url))
return;
camel_exception_init (&ex);
if (!(provider = camel_provider_get(uri, &ex))) {
camel_exception_clear (&ex);
return;
}
/* make sure the new store belongs in the tree */
if (!(provider->flags & CAMEL_PROVIDER_IS_STORAGE))
return;
if (!(store = (CamelStore *) camel_session_get_service (session, uri, CAMEL_PROVIDER_STORE, &ex))) {
camel_exception_clear (&ex);
return;
}
em_folder_tree_model_add_store (model, store, account->name);
camel_object_unref (store);
}
static void
account_removed (EAccountList *accounts, EAccount *account, gpointer user_data)
{
EMFolderTreeModel *model = user_data;
struct _EMFolderTreeModelStoreInfo *si;
if (!(si = g_hash_table_lookup (model->account_hash, account)))
return;
em_folder_tree_model_remove_store (model, si->store);
}
void
em_folder_tree_model_set_folder_info (EMFolderTreeModel *model, GtkTreeIter *iter,
struct _EMFolderTreeModelStoreInfo *si,
CamelFolderInfo *fi)
{
GtkTreeRowReference *uri_row, *path_row;
unsigned int unread;
GtkTreePath *path;
GtkTreeIter sub;
gboolean load;
struct _CamelFolder *folder;
load = fi->child == NULL && !(fi->flags & (CAMEL_FOLDER_NOCHILDREN | CAMEL_FOLDER_NOINFERIORS));
path = gtk_tree_model_get_path ((GtkTreeModel *) model, iter);
uri_row = gtk_tree_row_reference_new ((GtkTreeModel *) model, path);
path_row = gtk_tree_row_reference_copy (uri_row);
gtk_tree_path_free (path);
g_hash_table_insert (model->uri_hash, g_strdup (fi->uri), uri_row);
g_hash_table_insert (si->path_hash, g_strdup (fi->path), path_row);
/* HACK: if we have the folder, and its the outbox folder, we need the total count, not unread */
/* This is duplicated in mail-folder-cache too, should perhaps be functionised */
unread = fi->unread == -1 ? 0 : fi->unread;
if (mail_note_get_folder_from_uri(fi->uri, &folder) && folder) {
if (folder == mail_component_get_folder(NULL, MAIL_COMPONENT_FOLDER_OUTBOX))
unread = camel_folder_get_message_count(folder);
camel_object_unref(folder);
}
gtk_tree_store_set ((GtkTreeStore *) model, iter,
COL_STRING_DISPLAY_NAME, fi->name,
COL_POINTER_CAMEL_STORE, si->store,
COL_STRING_FOLDER_PATH, fi->path,
COL_STRING_URI, fi->uri,
COL_UINT_UNREAD, unread,
COL_UINT_FLAGS, fi->flags,
COL_BOOL_IS_STORE, FALSE,
COL_BOOL_LOAD_SUBDIRS, load,
-1);
if (fi->child) {
fi = fi->child;
do {
gtk_tree_store_append ((GtkTreeStore *) model, &sub, iter);
em_folder_tree_model_set_folder_info (model, &sub, si, fi);
fi = fi->next;
} while (fi);
} else if (load) {
/* create a placeholder node for our subfolders... */
gtk_tree_store_append ((GtkTreeStore *) model, &sub, iter);
gtk_tree_store_set ((GtkTreeStore *) model, &sub,
COL_STRING_DISPLAY_NAME, _("Loading..."),
COL_POINTER_CAMEL_STORE, NULL,
COL_STRING_FOLDER_PATH, NULL,
COL_BOOL_LOAD_SUBDIRS, FALSE,
COL_BOOL_IS_STORE, FALSE,
COL_STRING_URI, NULL,
COL_UINT_UNREAD, 0,
-1);
path = gtk_tree_model_get_path ((GtkTreeModel *) model, iter);
g_signal_emit (model, signals[LOADING_ROW], 0, path, iter);
gtk_tree_path_free (path);
}
}
static void
folder_subscribed (CamelStore *store, CamelFolderInfo *fi, EMFolderTreeModel *model)
{
struct _EMFolderTreeModelStoreInfo *si;
GtkTreeRowReference *row;
GtkTreeIter parent, iter;
GtkTreePath *path;
gboolean load;
char *dirname;
if (!(si = g_hash_table_lookup (model->store_hash, store)))
goto done;
/* make sure we don't already know about it? */
if (g_hash_table_lookup (si->path_hash, fi->path))
goto done;
/* get our parent folder's path */
if (!(dirname = g_path_get_dirname (fi->path)))
goto done;
if (!strcmp (dirname, "/")) {
/* user subscribed to a toplevel folder */
row = si->row;
g_free (dirname);
} else {
row = g_hash_table_lookup (si->path_hash, dirname);
g_free (dirname);
/* if row is NULL, don't bother adding to the tree,
* when the user expands enough nodes - it will be
* added auto-magically */
if (row == NULL)
goto done;
}
path = gtk_tree_row_reference_get_path (row);
if (!(gtk_tree_model_get_iter ((GtkTreeModel *) model, &parent, path))) {
gtk_tree_path_free (path);
goto done;
}
gtk_tree_path_free (path);
/* make sure parent's subfolders have already been loaded */
gtk_tree_model_get ((GtkTreeModel *) model, &parent, COL_BOOL_LOAD_SUBDIRS, &load, -1);
if (load)
goto done;
/* append a new node */
gtk_tree_store_append ((GtkTreeStore *) model, &iter, &parent);
em_folder_tree_model_set_folder_info (model, &iter, si, fi);
g_signal_emit (model, signals[FOLDER_ADDED], 0, fi->path, fi->uri);
done:
camel_object_unref (store);
camel_folder_info_free (fi);
}
static void
folder_subscribed_cb (CamelStore *store, void *event_data, EMFolderTreeModel *model)
{
CamelFolderInfo *fi;
camel_object_ref (store);
fi = camel_folder_info_clone (event_data);
mail_async_event_emit (mail_async_event, MAIL_ASYNC_GUI, (MailAsyncFunc) folder_subscribed, store, fi, model);
}
static void
folder_unsubscribed (CamelStore *store, CamelFolderInfo *fi, EMFolderTreeModel *model)
{
struct _EMFolderTreeModelStoreInfo *si;
GtkTreeRowReference *row;
GtkTreePath *path;
GtkTreeIter iter;
if (!(si = g_hash_table_lookup (model->store_hash, store)))
goto done;
if (!(row = g_hash_table_lookup (si->path_hash, fi->path)))
goto done;
path = gtk_tree_row_reference_get_path (row);
if (!(gtk_tree_model_get_iter ((GtkTreeModel *) model, &iter, path))) {
gtk_tree_path_free (path);
goto done;
}
em_folder_tree_model_remove_folders (model, si, &iter);
done:
camel_object_unref (store);
camel_folder_info_free (fi);
}
static void
folder_unsubscribed_cb (CamelStore *store, void *event_data, EMFolderTreeModel *model)
{
CamelFolderInfo *fi;
camel_object_ref (store);
fi = camel_folder_info_clone (event_data);
mail_async_event_emit (mail_async_event, MAIL_ASYNC_GUI, (MailAsyncFunc) folder_unsubscribed, store, fi, model);
}
static void
folder_created_cb (CamelStore *store, void *event_data, EMFolderTreeModel *model)
{
CamelFolderInfo *fi;
/* we only want created events to do more work if we don't support subscriptions */
if (camel_store_supports_subscriptions (store))
return;
camel_object_ref (store);
fi = camel_folder_info_clone (event_data);
mail_async_event_emit (mail_async_event, MAIL_ASYNC_GUI, (MailAsyncFunc) folder_subscribed_cb, store, fi, model);
}
static void
folder_deleted_cb (CamelStore *store, void *event_data, EMFolderTreeModel *model)
{
CamelFolderInfo *fi;
/* we only want deleted events to do more work if we don't support subscriptions */
if (camel_store_supports_subscriptions (store))
return;
camel_object_ref (store);
fi = camel_folder_info_clone (event_data);
mail_async_event_emit (mail_async_event, MAIL_ASYNC_GUI, (MailAsyncFunc) folder_unsubscribed_cb, store, fi, model);
}
static void
folder_renamed (CamelStore *store, CamelRenameInfo *info, EMFolderTreeModel *model)
{
struct _EMFolderTreeModelStoreInfo *si;
GtkTreeRowReference *row;
GtkTreeIter root, iter;
GtkTreePath *path;
char *parent, *p;
if (!(si = g_hash_table_lookup (model->store_hash, store)))
goto done;
parent = g_strdup_printf ("/%s", info->old_base);
if (!(row = g_hash_table_lookup (si->path_hash, parent))) {
g_free (parent);
goto done;
}
g_free (parent);
path = gtk_tree_row_reference_get_path (row);
if (!(gtk_tree_model_get_iter ((GtkTreeModel *) model, &iter, path))) {
gtk_tree_path_free (path);
goto done;
}
em_folder_tree_model_remove_folders (model, si, &iter);
parent = g_strdup (info->new->path);
p = strrchr(parent, '/');
g_assert(p);
*p = 0;
if (parent == p) {
/* renamed to a toplevel folder on the store */
path = gtk_tree_row_reference_get_path (si->row);
} else {
if (!(row = g_hash_table_lookup (si->path_hash, parent))) {
/* NOTE: this should never happen, but I
* suppose if it does in reality, we can add
* code here to add the missing nodes to the
* tree */
g_assert_not_reached ();
g_free (parent);
goto done;
}
path = gtk_tree_row_reference_get_path (row);
}
g_free (parent);
if (!gtk_tree_model_get_iter ((GtkTreeModel *) model, &root, path)) {
gtk_tree_path_free (path);
g_assert_not_reached ();
goto done;
}
gtk_tree_store_append ((GtkTreeStore *) model, &iter, &root);
em_folder_tree_model_set_folder_info (model, &iter, si, info->new);
done:
camel_object_unref (store);
g_free (info->old_base);
camel_folder_info_free (info->new);
g_free (info);
}
static void
folder_renamed_cb (CamelStore *store, void *event_data, EMFolderTreeModel *model)
{
CamelRenameInfo *rinfo, *info = event_data;
camel_object_ref (store);
rinfo = g_new0 (CamelRenameInfo, 1);
rinfo->old_base = g_strdup (info->old_base);
rinfo->new = camel_folder_info_clone (info->new);
mail_async_event_emit (mail_async_event, MAIL_ASYNC_GUI, (MailAsyncFunc) folder_renamed, store, rinfo, model);
}
void
em_folder_tree_model_add_store (EMFolderTreeModel *model, CamelStore *store, const char *display_name)
{
struct _EMFolderTreeModelStoreInfo *si;
GtkTreeRowReference *row;
GtkTreeIter root, iter;
GtkTreePath *path;
EAccount *account;
char *uri;
g_return_if_fail (EM_IS_FOLDER_TREE_MODEL (model));
g_return_if_fail (CAMEL_IS_STORE (store));
g_return_if_fail (display_name != NULL);
if ((si = g_hash_table_lookup (model->store_hash, store)))
em_folder_tree_model_remove_store (model, store);
uri = camel_url_to_string (((CamelService *) store)->url, CAMEL_URL_HIDE_ALL);
account = mail_config_get_account_by_source_url (uri);
/* add the store to the tree */
gtk_tree_store_append ((GtkTreeStore *) model, &iter, NULL);
gtk_tree_store_set ((GtkTreeStore *) model, &iter,
COL_STRING_DISPLAY_NAME, display_name,
COL_POINTER_CAMEL_STORE, store,
COL_STRING_FOLDER_PATH, "/",
COL_BOOL_LOAD_SUBDIRS, TRUE,
COL_BOOL_IS_STORE, TRUE,
COL_STRING_URI, uri, -1);
path = gtk_tree_model_get_path ((GtkTreeModel *) model, &iter);
row = gtk_tree_row_reference_new ((GtkTreeModel *) model, path);
gtk_tree_path_free (path);
si = g_new (struct _EMFolderTreeModelStoreInfo, 1);
si->display_name = g_strdup (display_name);
camel_object_ref (store);
si->store = store;
si->account = account;
si->row = row;
si->path_hash = g_hash_table_new (g_str_hash, g_str_equal);
g_hash_table_insert (model->store_hash, store, si);
g_hash_table_insert (model->account_hash, account, si);
/* each store has folders... but we don't load them until the user demands them */
root = iter;
gtk_tree_store_append ((GtkTreeStore *) model, &iter, &root);
gtk_tree_store_set ((GtkTreeStore *) model, &iter,
COL_STRING_DISPLAY_NAME, _("Loading..."),
COL_POINTER_CAMEL_STORE, NULL,
COL_STRING_FOLDER_PATH, NULL,
COL_BOOL_LOAD_SUBDIRS, FALSE,
COL_BOOL_IS_STORE, FALSE,
COL_STRING_URI, NULL,
COL_UINT_UNREAD, 0,
-1);
g_free (uri);
/* listen to store events */
#define CAMEL_CALLBACK(func) ((CamelObjectEventHookFunc) func)
si->created_id = camel_object_hook_event (store, "folder_created", CAMEL_CALLBACK (folder_created_cb), model);
si->deleted_id = camel_object_hook_event (store, "folder_deleted", CAMEL_CALLBACK (folder_deleted_cb), model);
si->renamed_id = camel_object_hook_event (store, "folder_renamed", CAMEL_CALLBACK (folder_renamed_cb), model);
si->subscribed_id = camel_object_hook_event (store, "folder_subscribed", CAMEL_CALLBACK (folder_subscribed_cb), model);
si->unsubscribed_id = camel_object_hook_event (store, "folder_unsubscribed", CAMEL_CALLBACK (folder_unsubscribed_cb), model);
}
static void
em_folder_tree_model_remove_uri (EMFolderTreeModel *model, const char *uri)
{
GtkTreeRowReference *row;
g_return_if_fail (EM_IS_FOLDER_TREE_MODEL (model));
g_return_if_fail (uri != NULL);
if ((row = g_hash_table_lookup (model->uri_hash, uri))) {
g_hash_table_remove (model->uri_hash, uri);
gtk_tree_row_reference_free (row);
}
}
static void
em_folder_tree_model_remove_store_info (EMFolderTreeModel *model, CamelStore *store)
{
struct _EMFolderTreeModelStoreInfo *si;
g_return_if_fail (EM_IS_FOLDER_TREE_MODEL (model));
g_return_if_fail (CAMEL_IS_STORE (store));
if (!(si = g_hash_table_lookup (model->store_hash, store)))
return;
g_hash_table_remove (model->store_hash, si->store);
g_hash_table_remove (model->account_hash, si->account);
store_info_free (si);
}
void
em_folder_tree_model_remove_folders (EMFolderTreeModel *model, struct _EMFolderTreeModelStoreInfo *si, GtkTreeIter *toplevel)
{
GtkTreeRowReference *row;
char *uri, *folder_path;
gboolean is_store, go;
GtkTreeIter iter;
if (gtk_tree_model_iter_children ((GtkTreeModel *) model, &iter, toplevel)) {
do {
GtkTreeIter next = iter;
go = gtk_tree_model_iter_next ((GtkTreeModel *) model, &next);
em_folder_tree_model_remove_folders (model, si, &iter);
iter = next;
} while (go);
}
gtk_tree_model_get ((GtkTreeModel *) model, toplevel, COL_STRING_URI, &uri,
COL_STRING_FOLDER_PATH, &folder_path,
COL_BOOL_IS_STORE, &is_store, -1);
if (folder_path && (row = g_hash_table_lookup (si->path_hash, folder_path))) {
g_hash_table_remove (si->path_hash, folder_path);
gtk_tree_row_reference_free (row);
}
em_folder_tree_model_remove_uri (model, uri);
gtk_tree_store_remove ((GtkTreeStore *) model, toplevel);
if (is_store)
em_folder_tree_model_remove_store_info (model, si->store);
}
void
em_folder_tree_model_remove_store (EMFolderTreeModel *model, CamelStore *store)
{
struct _EMFolderTreeModelStoreInfo *si;
GtkTreePath *path;
GtkTreeIter iter;
g_return_if_fail (EM_IS_FOLDER_TREE_MODEL (model));
g_return_if_fail (CAMEL_IS_STORE (store));
if (!(si = g_hash_table_lookup (model->store_hash, store))) {
g_warning ("the store `%s' is not in the folder tree", si->display_name);
return;
}
path = gtk_tree_row_reference_get_path (si->row);
gtk_tree_model_get_iter ((GtkTreeModel *) model, &iter, path);
gtk_tree_path_free (path);
/* recursively remove subfolders and finally the toplevel store */
em_folder_tree_model_remove_folders (model, si, &iter);
}
gboolean
em_folder_tree_model_get_expanded (EMFolderTreeModel *model, const char *key)
{
if (g_hash_table_lookup (model->expanded, key))
return TRUE;
return FALSE;
}
void
em_folder_tree_model_set_expanded (EMFolderTreeModel *model, const char *key, gboolean expanded)
{
gpointer okey, oval;
if (g_hash_table_lookup_extended (model->expanded, key, &okey, &oval)) {
g_hash_table_remove (model->expanded, okey);
g_free (okey);
}
if (expanded)
g_hash_table_insert (model->expanded, g_strdup (key), GINT_TO_POINTER (TRUE));
}
static void
expanded_save (gpointer key, gpointer value, FILE *fp)
{
/* FIXME: don't save stale entries */
if (!GPOINTER_TO_INT (value))
return;
camel_file_util_encode_string (fp, key);
}
void
em_folder_tree_model_save_expanded (EMFolderTreeModel *model)
{
char *dirname, *tmpname;
FILE *fp;
int fd;
dirname = g_path_get_dirname (model->filename);
if (camel_mkdir (dirname, 0777) == -1 && errno != EEXIST) {
g_free (dirname);
return;
}
g_free (dirname);
tmpname = g_strdup_printf ("%s~", model->filename);
if (!(fp = fopen (tmpname, "w+"))) {
g_free (tmpname);
return;
}
g_hash_table_foreach (model->expanded, (GHFunc) expanded_save, fp);
if (fflush (fp) != 0)
goto exception;
if ((fd = fileno (fp)) == -1)
goto exception;
if (fsync (fd) == -1)
goto exception;
fclose (fp);
fp = NULL;
if (rename (tmpname, model->filename) == -1)
goto exception;
g_free (tmpname);
return;
exception:
if (fp != NULL)
fclose (fp);
unlink (tmpname);
g_free (tmpname);
}
void
em_folder_tree_model_set_unread_count (EMFolderTreeModel *model, CamelStore *store, const char *path, int unread)
{
struct _EMFolderTreeModelStoreInfo *si;
GtkTreeRowReference *row;
GtkTreePath *tree_path;
GtkTreeIter iter;
g_return_if_fail (EM_IS_FOLDER_TREE_MODEL (model));
g_return_if_fail (CAMEL_IS_STORE (store));
g_return_if_fail (path != NULL);
u(printf("set unread count %p '%s' %d\n", store, path, unread));
if (unread < 0)
unread = 0;
if (!(si = g_hash_table_lookup (model->store_hash, store))) {
u(printf(" can't find store\n"));
return;
}
if (!(row = g_hash_table_lookup (si->path_hash, path))) {
u(printf(" can't find row\n"));
return;
}
tree_path = gtk_tree_row_reference_get_path (row);
if (!gtk_tree_model_get_iter ((GtkTreeModel *) model, &iter, tree_path)) {
gtk_tree_path_free (tree_path);
return;
}
gtk_tree_path_free (tree_path);
gtk_tree_store_set ((GtkTreeStore *) model, &iter, COL_UINT_UNREAD, unread, -1);
}