/* e-storage-set-store.c * Copyright (C) 2002 Ximian, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #include #include #include #include "e-icon-factory.h" #include "e-storage-set-store.h" #include "e-shell-constants.h" struct _EStorageSetStorePrivate { EStorageSet *storage_set; GHashTable *checkboxes; GHashTable *path_to_node; GHashTable *type_name_to_pixbuf; GNode *root; gint stamp; EStorageSetStoreHasCheckBoxFunc has_checkbox_func; gpointer has_checkbox_func_data; }; #define G_NODE(node) ((GNode *)(node)) #define VALID_ITER(iter, store) ((iter) != NULL && (iter)->user_data != NULL && (store)->priv->stamp == (iter)->stamp) #define VALID_COL(col) ((col) >= 0 && (col) < E_STORAGE_SET_STORE_COLUMN_COUNT) static GObjectClass *parent_class = NULL; static gboolean has_checkbox (EStorageSetStore *store, const gchar *folder_path) { EStorageSetStorePrivate *priv = store->priv; g_return_val_if_fail (folder_path != NULL, FALSE); if (strchr (folder_path + 1, '/') == NULL) { /* If it's a toplevel, never allow checking it. */ return FALSE; } #if 0 if (priv->has_checkbox_func) return (* priv->has_checkbox_func) (priv->storage_set, folder_path, priv->has_checkbox_func_data); #endif return TRUE; } /* GtkTreeModel interface implementation */ static guint esss_get_flags(GtkTreeModel *tree_model) { g_return_val_if_fail(E_IS_STORAGE_SET_STORE(tree_model), 0); return GTK_TREE_MODEL_ITERS_PERSIST; } static gint esss_get_n_columns(GtkTreeModel *tree_model) { g_return_val_if_fail(E_IS_STORAGE_SET_STORE(tree_model), 0); return E_STORAGE_SET_STORE_COLUMN_COUNT; } static GType esss_get_column_type(GtkTreeModel *tree_model, gint index) { EStorageSetStore *store = (EStorageSetStore *) tree_model; GType retval; g_return_val_if_fail(E_IS_STORAGE_SET_STORE(tree_model), G_TYPE_INVALID); g_return_val_if_fail(VALID_COL(index), G_TYPE_INVALID); switch (index) { case E_STORAGE_SET_STORE_COLUMN_NAME: retval = G_TYPE_STRING; break; case E_STORAGE_SET_STORE_COLUMN_HIGHLIGHT: retval = G_TYPE_INT; break; case E_STORAGE_SET_STORE_COLUMN_CHECKED: case E_STORAGE_SET_STORE_COLUMN_CHECKABLE: retval = G_TYPE_BOOLEAN; break; case E_STORAGE_SET_STORE_COLUMN_ICON: retval = GDK_TYPE_PIXBUF; break; default: g_assert_not_reached (); } return retval; } static gboolean esss_get_iter(GtkTreeModel *tree_model, GtkTreeIter *iter, GtkTreePath *path) { EStorageSetStore *store = (EStorageSetStore *) tree_model; GtkTreeIter parent; gint *indices; gint depth, i; g_return_val_if_fail(E_IS_STORAGE_SET_STORE(tree_model), FALSE); g_return_val_if_fail(iter != NULL, FALSE); indices = gtk_tree_path_get_indices(path); depth = gtk_tree_path_get_depth(path); g_return_val_if_fail(depth > 0, FALSE); parent.stamp = store->priv->stamp; parent.user_data = store->priv->root; for (i = 0; i < depth; i++) { if (!gtk_tree_model_iter_nth_child (tree_model, iter, &parent, indices[i])) return FALSE; parent = *iter; } return TRUE; } static GtkTreePath * tree_path_from_node (EStorageSetStore *store, GNode *node) { GtkTreePath *retval; GNode *tmp_node, *curr_node; gint i = 0; if (node == store->priv->root) return NULL; retval = gtk_tree_path_new(); for (curr_node = node; curr_node != store->priv->root; curr_node = curr_node->parent) { if (curr_node->parent == NULL) { gtk_tree_path_free(retval); return NULL; } for (i = 0, tmp_node = curr_node->parent->children; tmp_node && tmp_node != curr_node; i++, tmp_node = tmp_node->next); if (tmp_node == NULL) { gtk_tree_path_free(retval); return NULL; } gtk_tree_path_prepend_index(retval, i); } return retval; } static GtkTreePath * esss_get_path(GtkTreeModel *tree_model, GtkTreeIter *iter) { EStorageSetStore *store = (EStorageSetStore *) tree_model; g_return_val_if_fail(E_IS_STORAGE_SET_STORE(tree_model), NULL); g_return_val_if_fail(VALID_ITER(iter, store), NULL); if (iter->user_data == store->priv->root) return NULL; return tree_path_from_node (store, G_NODE (iter->user_data)); } static GdkPixbuf * get_pixbuf_for_folder (EStorageSetStore *store, EFolder *folder) { const char *type_name; EStorageSetStorePrivate *priv; EFolderTypeRegistry *folder_type_registry; EStorageSet *storage_set; GdkPixbuf *icon_pixbuf; GdkPixbuf *scaled_pixbuf; const char *custom_icon_name; int icon_pixbuf_width, icon_pixbuf_height; priv = store->priv; custom_icon_name = e_folder_get_custom_icon_name (folder); if (custom_icon_name != NULL) return e_icon_factory_get_icon (custom_icon_name, TRUE); type_name = e_folder_get_type_string (folder); scaled_pixbuf = g_hash_table_lookup (priv->type_name_to_pixbuf, type_name); if (scaled_pixbuf != NULL) return scaled_pixbuf; storage_set = priv->storage_set; folder_type_registry = e_storage_set_get_folder_type_registry (storage_set); icon_pixbuf = e_folder_type_registry_get_icon_for_type (folder_type_registry, type_name, TRUE); if (icon_pixbuf == NULL) return NULL; icon_pixbuf_width = gdk_pixbuf_get_width (icon_pixbuf); icon_pixbuf_height = gdk_pixbuf_get_height (icon_pixbuf); if (icon_pixbuf_width == E_SHELL_MINI_ICON_SIZE && icon_pixbuf_height == E_SHELL_MINI_ICON_SIZE) { scaled_pixbuf = g_object_ref (icon_pixbuf); } else { scaled_pixbuf = gdk_pixbuf_new (gdk_pixbuf_get_colorspace (icon_pixbuf), gdk_pixbuf_get_has_alpha (icon_pixbuf), gdk_pixbuf_get_bits_per_sample (icon_pixbuf), E_SHELL_MINI_ICON_SIZE, E_SHELL_MINI_ICON_SIZE); gdk_pixbuf_scale (icon_pixbuf, scaled_pixbuf, 0, 0, E_SHELL_MINI_ICON_SIZE, E_SHELL_MINI_ICON_SIZE, 0.0, 0.0, (double) E_SHELL_MINI_ICON_SIZE / gdk_pixbuf_get_width (icon_pixbuf), (double) E_SHELL_MINI_ICON_SIZE / gdk_pixbuf_get_height (icon_pixbuf), GDK_INTERP_HYPER); } g_hash_table_insert (priv->type_name_to_pixbuf, g_strdup (type_name), scaled_pixbuf); return scaled_pixbuf; } static void esss_get_value(GtkTreeModel *tree_model, GtkTreeIter *iter, gint column, GValue *value) { gchar *folder_path; const gchar *folder_name; int unread_count; EStorageSetStore *store = (EStorageSetStore *) tree_model; EFolder *folder; GNode *node; gboolean is_storage; g_return_if_fail(E_IS_STORAGE_SET_STORE(tree_model)); g_return_if_fail(VALID_ITER(iter, store)); g_return_if_fail(VALID_COL(column)); node = G_NODE (iter->user_data); g_value_init(value, esss_get_column_type(tree_model, column)); is_storage = (node->parent == store->priv->root); if (is_storage) folder_path = g_strconcat ("/", node->data, NULL); else folder_path = g_strdup (node->data); folder = e_storage_set_get_folder(store->priv->storage_set, folder_path); g_free (folder_path); switch (column) { case E_STORAGE_SET_STORE_COLUMN_NAME: if (!folder) { g_value_set_string(value, "?"); return; } folder_name = e_folder_get_name(folder); unread_count = e_folder_get_unread_count(folder); if (unread_count > 0) { gchar *with_unread = g_strdup_printf("%s (%d)", folder_name, unread_count); g_object_set_data_full(G_OBJECT(folder), "name_with_unread", with_unread, g_free); g_value_set_string(value, with_unread); } else g_value_set_string(value, folder_name); break; case E_STORAGE_SET_STORE_COLUMN_HIGHLIGHT: if (!folder) { g_value_set_boolean(value, FALSE); return; } g_value_set_int(value, is_storage || e_folder_get_highlighted(folder) ? PANGO_WEIGHT_BOLD : 0); break; case E_STORAGE_SET_STORE_COLUMN_CHECKED: if (is_storage || !store->priv->checkboxes) { g_value_set_boolean(value, FALSE); return; } g_value_set_boolean(value, g_hash_table_lookup(store->priv->checkboxes, folder_path) ? TRUE : FALSE); break; case E_STORAGE_SET_STORE_COLUMN_CHECKABLE: g_value_set_boolean(value, !is_storage && has_checkbox(store, (gchar *)node->data)); break; case E_STORAGE_SET_STORE_COLUMN_ICON: if (!is_storage) g_value_set_object (value, get_pixbuf_for_folder (store, folder)); break; default: g_assert_not_reached (); break; } } static gboolean esss_iter_next(GtkTreeModel *tree_model, GtkTreeIter *iter) { g_return_val_if_fail(E_IS_STORAGE_SET_STORE(tree_model), FALSE); g_return_val_if_fail(VALID_ITER(iter, E_STORAGE_SET_STORE(tree_model)), FALSE); if (G_NODE(iter->user_data)->next) { iter->user_data = G_NODE(iter->user_data)->next; return TRUE; } else return FALSE; } static gboolean esss_iter_children(GtkTreeModel *tree_model, GtkTreeIter *iter, GtkTreeIter *parent) { GNode *children; EStorageSetStore *store = (EStorageSetStore *) tree_model; g_return_val_if_fail(E_IS_STORAGE_SET_STORE(tree_model), FALSE); g_return_val_if_fail(iter != NULL, FALSE); g_return_val_if_fail(parent == NULL || parent->user_data != NULL, FALSE); g_return_val_if_fail(parent == NULL || parent->stamp == store->priv->stamp, FALSE); if (parent) children = G_NODE(parent->user_data)->children; else children = G_NODE(store->priv->root)->children; if (children) { iter->stamp = store->priv->stamp; iter->user_data = children; return TRUE; } else return FALSE; } static gboolean esss_iter_has_child(GtkTreeModel *tree_model, GtkTreeIter *iter) { EStorageSetStore *store = (EStorageSetStore *) tree_model; g_return_val_if_fail(E_IS_STORAGE_SET_STORE(tree_model), FALSE); g_return_val_if_fail(VALID_ITER(iter, store), FALSE); return G_NODE(iter->user_data)->children != NULL; } static gint esss_iter_n_children(GtkTreeModel *tree_model, GtkTreeIter *iter) { GNode *node; gint i = 0; EStorageSetStore *store = (EStorageSetStore *) tree_model; g_return_val_if_fail(E_IS_STORAGE_SET_STORE(tree_model), 0); g_return_val_if_fail(iter == NULL || iter->user_data != NULL, FALSE); if (iter == NULL) node = G_NODE(store->priv->root)->children; else node = G_NODE(iter->user_data)->children; while (node) { i++; node = node->next; } return i; } static gboolean esss_iter_nth_child(GtkTreeModel *tree_model, GtkTreeIter *iter, GtkTreeIter *parent, gint n) { GNode *parent_node; GNode *child; EStorageSetStore *store = (EStorageSetStore *) tree_model; g_return_val_if_fail(E_IS_STORAGE_SET_STORE(tree_model), FALSE); g_return_val_if_fail(iter != NULL, FALSE); g_return_val_if_fail(parent == NULL || parent->user_data != NULL, FALSE); if (parent == NULL) parent_node = store->priv->root; else parent_node = parent->user_data; child = g_node_nth_child(parent_node, n); if (child) { iter->user_data = child; iter->stamp = store->priv->stamp; return TRUE; } else return FALSE; } static gboolean esss_iter_parent(GtkTreeModel *tree_model, GtkTreeIter *iter, GtkTreeIter *child) { GNode *parent; EStorageSetStore *store = (EStorageSetStore *) tree_model; g_return_val_if_fail(E_IS_STORAGE_SET_STORE(tree_model), FALSE); g_return_val_if_fail(iter != NULL, FALSE); g_return_val_if_fail(VALID_ITER(child, store), FALSE); parent = G_NODE(child->user_data)->parent; g_assert(parent != NULL); if (parent != store->priv->root) { iter->user_data = parent; iter->stamp = store->priv->stamp; return TRUE; } else return FALSE; } static void esss_tree_model_init(GtkTreeModelIface *iface) { iface->get_flags = esss_get_flags; iface->get_n_columns = esss_get_n_columns; iface->get_column_type = esss_get_column_type; iface->get_iter = esss_get_iter; iface->get_path = esss_get_path; iface->get_value = esss_get_value; iface->iter_next = esss_iter_next; iface->iter_children = esss_iter_children; iface->iter_has_child = esss_iter_has_child; iface->iter_n_children = esss_iter_n_children; iface->iter_nth_child = esss_iter_nth_child; iface->iter_parent = esss_iter_parent; } /* GtkTreeDragSource interface implementation */ static gboolean esss_drag_data_delete(GtkTreeDragSource *source, GtkTreePath *path) { GtkTreeIter iter; g_return_val_if_fail(E_IS_STORAGE_SET_STORE(source), FALSE); if (gtk_tree_model_get_iter(GTK_TREE_MODEL(source), &iter, path)) { #if 0 e_storage_set_store_remove(E_STORAGE_SET_STORE(source), &iter); #endif return TRUE; } else { return FALSE; } } static gboolean esss_drag_data_get(GtkTreeDragSource *source, GtkTreePath *path, GtkSelectionData *selection_data) { g_return_val_if_fail(E_IS_STORAGE_SET_STORE(source), FALSE); /* Note that we don't need to handle the GTK_TREE_MODEL_ROW * target, because the default handler does it for us, but * we do anyway for the convenience of someone maybe overriding the * default handler. */ if (gtk_tree_set_row_drag_data(selection_data, GTK_TREE_MODEL(source), path)) { return TRUE; } else { /* FIXME handle text targets at least. */ } return FALSE; } static void esss_drag_source_init(GtkTreeDragSourceIface * iface) { iface->drag_data_delete = esss_drag_data_delete; iface->drag_data_get = esss_drag_data_get; } /* GtkTreeDragDest interface implementation */ static void copy_node_data(EStorageSetStore *store, GtkTreeIter *src_iter, GtkTreeIter *dest_iter) { } static void recursive_node_copy(EStorageSetStore * store, GtkTreeIter * src_iter, GtkTreeIter * dest_iter) { } static gboolean esss_drag_data_received(GtkTreeDragDest * drag_dest, GtkTreePath * dest, GtkSelectionData * selection_data) { #if 0 GtkTreeModel *tree_model; EStorageSetStore *store; GtkTreeModel *src_model = NULL; GtkTreePath *src_path = NULL; gboolean retval = FALSE; g_return_val_if_fail(E_IS_STORAGE_SET_STORE(drag_dest), FALSE); tree_model = GTK_TREE_MODEL(drag_dest); store = E_STORAGE_SET_STORE(drag_dest); validate_tree(store); if (gtk_tree_get_row_drag_data(selection_data, &src_model, &src_path) && src_model == tree_model) { /* Copy the given row to a new position */ GtkTreeIter src_iter; GtkTreeIter dest_iter; GtkTreePath *prev; if (!gtk_tree_model_get_iter(src_model, &src_iter, src_path)) { goto out; } /* Get the path to insert _after_ (dest is the path to insert _before_) */ prev = gtk_tree_path_copy(dest); if (!gtk_tree_path_prev(prev)) { GtkTreeIter dest_parent; GtkTreePath *parent; GtkTreeIter *dest_parent_p; /* dest was the first spot at the current depth; which means * we are supposed to prepend. */ /* Get the parent, NULL if parent is the root */ dest_parent_p = NULL; parent = gtk_tree_path_copy(dest); if (gtk_tree_path_up(parent) && gtk_tree_path_get_depth(parent) > 0) { gtk_tree_model_get_iter(tree_model, &dest_parent, parent); dest_parent_p = &dest_parent; } gtk_tree_path_free(parent); parent = NULL; e_storage_set_store_prepend(E_STORAGE_SET_STORE(tree_model), &dest_iter, dest_parent_p); retval = TRUE; } else { if (gtk_tree_model_get_iter (GTK_TREE_MODEL(tree_model), &dest_iter, prev)) { GtkTreeIter tmp_iter = dest_iter; if (GPOINTER_TO_INT (g_object_get_data (G_OBJECT(tree_model), "gtk-tree-model-drop-append"))) { GtkTreeIter parent; if (gtk_tree_model_iter_parent (GTK_TREE_MODEL(tree_model), &parent, &tmp_iter)) e_storage_set_store_append (E_STORAGE_SET_STORE (tree_model), &dest_iter, &parent); else e_storage_set_store_append (E_STORAGE_SET_STORE (tree_model), &dest_iter, NULL); } else e_storage_set_store_insert_after (E_STORAGE_SET_STORE(tree_model), &dest_iter, NULL, &tmp_iter); retval = TRUE; } } g_object_set_data(G_OBJECT(tree_model), "gtk-tree-model-drop-append", NULL); gtk_tree_path_free(prev); /* If we succeeded in creating dest_iter, walk src_iter tree branch, * duplicating it below dest_iter. */ if (retval) { recursive_node_copy(store, &src_iter, &dest_iter); } } else { /* FIXME maybe add some data targets eventually, or handle text * targets in the simple case. */ } out: if (src_path) gtk_tree_path_free(src_path); return retval; #else return FALSE; #endif } static gboolean esss_row_drop_possible(GtkTreeDragDest * drag_dest, GtkTreePath * dest_path, GtkSelectionData * selection_data) { #if 0 GtkTreeModel *src_model = NULL; GtkTreePath *src_path = NULL; GtkTreePath *tmp = NULL; gboolean retval = FALSE; if (!gtk_tree_get_row_drag_data(selection_data, &src_model, &src_path)) goto out; /* can only drag to ourselves */ if (src_model != GTK_TREE_MODEL(drag_dest)) goto out; /* Can't drop into ourself. */ if (gtk_tree_path_is_ancestor(src_path, dest_path)) goto out; /* Can't drop if dest_path's parent doesn't exist */ { GtkTreeIter iter; if (gtk_tree_path_get_depth(dest_path) > 1) { tmp = gtk_tree_path_copy(dest_path); gtk_tree_path_up(tmp); if (!gtk_tree_model_get_iter (GTK_TREE_MODEL(drag_dest), &iter, tmp)) goto out; } } /* Can otherwise drop anywhere. */ retval = TRUE; out: if (src_path) gtk_tree_path_free(src_path); if (tmp) gtk_tree_path_free(tmp); return retval; #else return FALSE; #endif } static void esss_drag_dest_init(GtkTreeDragDestIface * iface) { iface->drag_data_received = esss_drag_data_received; iface->row_drop_possible = esss_row_drop_possible; } typedef struct { gint offset; GNode *node; } SortTuple; static gint folder_sort_callback (gconstpointer a, gconstpointer b, gpointer user_data) { EStorageSetStore *store = (EStorageSetStore *) user_data; EStorageSetStorePrivate *priv = store->priv; EFolder *folder_1, *folder_2; char *folder_path_1, *folder_path_2; int priority_1, priority_2; folder_path_1 = (gchar *)((SortTuple *)a)->node->data; folder_path_2 = (gchar *)((SortTuple *)b)->node->data; folder_1 = e_storage_set_get_folder (priv->storage_set, folder_path_1); folder_2 = e_storage_set_get_folder (priv->storage_set, folder_path_2); priority_1 = e_folder_get_sorting_priority (folder_1); priority_2 = e_folder_get_sorting_priority (folder_2); if (priority_1 == priority_2) return g_utf8_collate (e_folder_get_name (folder_1), e_folder_get_name (folder_2)); else if (priority_1 < priority_2) return -1; else /* priority_1 > priority_2 */ return +1; } static gint storage_sort_callback (gconstpointer a, gconstpointer b, gpointer user_data) { char *folder_path_1; char *folder_path_2; gboolean path_1_local; gboolean path_2_local; folder_path_1 = (gchar *)((SortTuple *)a)->node->data; folder_path_2 = (gchar *)((SortTuple *)b)->node->data; /* FIXME bad hack to put the "my evolution" and "local" storages on * top. */ if (strcmp (folder_path_1, E_SUMMARY_STORAGE_NAME) == 0) return -1; if (strcmp (folder_path_2, E_SUMMARY_STORAGE_NAME) == 0) return +1; path_1_local = ! strcmp (folder_path_1, E_LOCAL_STORAGE_NAME); path_2_local = ! strcmp (folder_path_2, E_LOCAL_STORAGE_NAME); if (path_1_local && path_2_local) return 0; if (path_1_local) return -1; if (path_2_local) return 1; return g_utf8_collate (folder_path_1, folder_path_2); } static void esss_sort (EStorageSetStore *store, GNode *parent, GCompareDataFunc callback) { GtkTreeIter iter; GArray *sort_array; GNode *node; GNode *tmp_node; gint list_length; gint i; gint *new_order; GtkTreePath *path; node = parent->children; if (node == NULL || node->next == NULL) return; list_length = 0; for (tmp_node = node; tmp_node; tmp_node = tmp_node->next) list_length++; sort_array = g_array_sized_new(FALSE, FALSE, sizeof(SortTuple), list_length); i = 0; for (tmp_node = node; tmp_node; tmp_node = tmp_node->next) { SortTuple tuple; tuple.offset = i; tuple.node = tmp_node; g_array_append_val(sort_array, tuple); i++; } /* Sort the array */ g_array_sort_with_data(sort_array, callback, store); for (i = 0; i < list_length - 1; i++) { g_array_index(sort_array, SortTuple, i).node->next = g_array_index(sort_array, SortTuple, i + 1).node; g_array_index(sort_array, SortTuple, i + 1).node->prev = g_array_index(sort_array, SortTuple, i).node; } g_array_index(sort_array, SortTuple, list_length - 1).node->next = NULL; g_array_index(sort_array, SortTuple, 0).node->prev = NULL; parent->children = g_array_index(sort_array, SortTuple, 0).node; /* Let the world know about our new order */ new_order = g_new(gint, list_length); for (i = 0; i < list_length; i++) new_order[i] = g_array_index(sort_array, SortTuple, i).offset; iter.stamp = store->priv->stamp; iter.user_data = parent; path = esss_get_path(GTK_TREE_MODEL(store), &iter); gtk_tree_model_rows_reordered(GTK_TREE_MODEL(store), path, &iter, new_order); if (path) gtk_tree_path_free(path); g_free(new_order); g_array_free(sort_array, TRUE); } static void esss_init(EStorageSetStore *store) { store->priv = g_new0(EStorageSetStorePrivate, 1); store->priv->storage_set = NULL; store->priv->checkboxes = NULL; store->priv->root = g_node_new(NULL); do { store->priv->stamp = g_random_int(); } while (store->priv->stamp == 0); store->priv->path_to_node = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); store->priv->type_name_to_pixbuf = g_hash_table_new (g_str_hash, g_str_equal); } static void esss_dispose(GObject *object) { EStorageSetStore *store = E_STORAGE_SET_STORE(object); if (store->priv->storage_set) g_object_unref(store->priv->storage_set); store->priv->storage_set = NULL; (*parent_class->dispose) (object); } static void node_free (GNode *node, gpointer data) { g_free (node->data); } static void pixbuf_free_func (gpointer key, gpointer value, gpointer user_data) { g_free (key); g_object_unref (value); } static void esss_finalize(GObject *object) { EStorageSetStore *store = E_STORAGE_SET_STORE(object); g_node_children_foreach(store->priv->root, G_TRAVERSE_ALL, node_free, NULL); g_hash_table_foreach (store->priv->type_name_to_pixbuf, pixbuf_free_func, NULL); g_hash_table_destroy (store->priv->type_name_to_pixbuf); g_hash_table_destroy (store->priv->path_to_node); if (store->priv->checkboxes) g_hash_table_destroy (store->priv->checkboxes); g_free (store->priv); (*parent_class->finalize) (object); } static void esss_class_init(EStorageSetStoreClass *class) { GObjectClass *object_class; parent_class = g_type_class_peek_parent(class); object_class = (GObjectClass *) class; object_class->dispose = esss_dispose; object_class->finalize = esss_finalize; } GType e_storage_set_store_get_type(void) { static GType store_type = 0; if (!store_type) { static const GTypeInfo store_info = { sizeof(EStorageSetStoreClass), NULL, /* base_init */ NULL, /* base_finalize */ (GClassInitFunc) esss_class_init, NULL, /* class_finalize */ NULL, /* class_data */ sizeof(EStorageSetStore), 0, /* n_preallocs */ (GInstanceInitFunc) esss_init }; static const GInterfaceInfo tree_model_info = { (GInterfaceInitFunc) esss_tree_model_init, NULL, NULL }; static const GInterfaceInfo drag_source_info = { (GInterfaceInitFunc) esss_drag_source_init, NULL, NULL }; static const GInterfaceInfo drag_dest_info = { (GInterfaceInitFunc) esss_drag_dest_init, NULL, NULL }; store_type = g_type_register_static(G_TYPE_OBJECT, "EStorageSetStore", &store_info, 0); g_type_add_interface_static(store_type, GTK_TYPE_TREE_MODEL, &tree_model_info); g_type_add_interface_static(store_type, GTK_TYPE_TREE_DRAG_SOURCE, &drag_source_info); g_type_add_interface_static(store_type, GTK_TYPE_TREE_DRAG_DEST, &drag_dest_info); } return store_type; } /* Handling of the "changed" signal in EFolders displayed in the EStorageSetStore. */ typedef struct { EStorageSetStore *store; GNode * node; } FolderChangedCallbackData; static void folder_changed_cb (EFolder *folder, void *data) { FolderChangedCallbackData *callback_data; GtkTreePath *path; GtkTreeIter iter; callback_data = (FolderChangedCallbackData *) data; iter.user_data = callback_data->node; iter.stamp = callback_data->store->priv->stamp; path = esss_get_path (GTK_TREE_MODEL (callback_data->store), &iter); gtk_tree_model_row_changed (GTK_TREE_MODEL (callback_data->store), path, &iter); if (path) gtk_tree_path_free (path); } static void folder_name_changed_cb (EFolder *folder, void *data) { FolderChangedCallbackData *callback_data; callback_data = (FolderChangedCallbackData *) data; esss_sort (callback_data->store, callback_data->node->parent, folder_sort_callback); } static void setup_folder_changed_callbacks (EStorageSetStore *store, EFolder *folder, GNode *node) { FolderChangedCallbackData *callback_data = g_new0 (FolderChangedCallbackData, 1); callback_data->store = store; callback_data->node = node; g_signal_connect (G_OBJECT (folder), "changed", G_CALLBACK (folder_changed_cb), callback_data); g_signal_connect_data (G_OBJECT (folder), "name_changed", G_CALLBACK (folder_name_changed_cb), callback_data, (GClosureNotify)g_free, 0); } static void insert_folders (EStorageSetStore *store, GNode *parent, EStorage *storage, const gchar *path) { EStorageSetStorePrivate *priv; GList *folder_path_list, *p; const gchar *storage_name = e_storage_get_name (storage); priv = store->priv; folder_path_list = e_storage_get_subfolder_paths (storage, path); if (folder_path_list == NULL) return; for (p = folder_path_list; p != NULL; p = p->next) { EFolder *folder; const char *subpath = (const char *) p->data; char *folder_path = g_strconcat ("/", storage_name, subpath, NULL); gchar *key = g_strdup (folder_path+1); GNode *node = g_node_new (folder_path); g_node_append (parent, node); g_hash_table_replace (priv->path_to_node, key, node); folder = e_storage_get_folder (storage, subpath); setup_folder_changed_callbacks (store, folder, node); insert_folders (store, node, storage, subpath); } esss_sort (store, parent, folder_sort_callback); e_free_string_list (folder_path_list); } /* StorageSet signal handling. */ static void new_storage_cb (EStorageSet *storage_set, EStorage *storage, void *data) { EStorageSetStore *store = E_STORAGE_SET_STORE (data); EStorageSetStorePrivate *priv = store->priv; gchar *storage_name = g_strdup (e_storage_get_name (storage)); GNode *node = g_node_new (g_strdup (e_storage_get_name (storage))); GtkTreePath *path; GtkTreeIter iter; g_hash_table_replace (priv->path_to_node, storage_name, node); g_node_append (priv->root, node); esss_sort (store, priv->root, storage_sort_callback); iter.user_data = node; iter.stamp = store->priv->stamp; path = esss_get_path (GTK_TREE_MODEL (store), &iter); gtk_tree_model_row_inserted (GTK_TREE_MODEL (store), path, &iter); if (path) gtk_tree_path_free (path); } static void removed_storage_cb (EStorageSet *storage_set, EStorage *storage, void *data) { EStorageSetStore *store = E_STORAGE_SET_STORE (data); EStorageSetStorePrivate *priv = store->priv; const gchar *name = e_storage_get_name (storage); GNode *node = g_hash_table_lookup (priv->path_to_node, name); GtkTreePath *path; if (node == NULL) { g_warning ("EStorageSetStore: unknown storage removed -- %s", name); return; } g_hash_table_remove (priv->path_to_node, name); /* FIXME: subfolder hashtable entries might be leaked */ path = tree_path_from_node (store, node); gtk_tree_model_row_deleted (GTK_TREE_MODEL (store), path); if (path) gtk_tree_path_free (path); g_node_destroy (node); } static void new_folder_cb (EStorageSet *storage_set, const char *path, void *data) { EStorageSetStore *store = E_STORAGE_SET_STORE (data); EStorageSetStorePrivate *priv = store->priv; GNode *parent_node, *new_node; const char *last_separator; char *parent_path; char *copy_of_path; GtkTreeIter iter; GtkTreePath *treepath; last_separator = strrchr (path, E_PATH_SEPARATOR); parent_path = g_strndup (path + 1, last_separator - path - 1); parent_node = g_hash_table_lookup (priv->path_to_node, parent_path); g_free (parent_path); if (parent_node == NULL) { g_warning ("EStorageSetStore: EStorageSet reported new subfolder for non-existing folder -- %s", parent_path); return; } copy_of_path = g_strdup (path); new_node = g_node_new (copy_of_path); g_node_append (parent_node, new_node); iter.user_data = new_node; iter.stamp = priv->stamp; treepath = esss_get_path (GTK_TREE_MODEL (store), &iter); gtk_tree_model_row_inserted (GTK_TREE_MODEL (store), treepath, &iter); if (treepath) gtk_tree_path_free (treepath); g_hash_table_replace (priv->path_to_node, g_strdup (path + 1), new_node); setup_folder_changed_callbacks (store, e_storage_set_get_folder (storage_set, path), new_node); esss_sort (store, parent_node, folder_sort_callback); } static void updated_folder_cb (EStorageSet *storage_set, const char *path, void *data) { EStorageSetStore *store = E_STORAGE_SET_STORE (data); EStorageSetStorePrivate *priv = store->priv; GNode *node; GtkTreeIter iter; GtkTreePath *treepath; node = g_hash_table_lookup (priv->path_to_node, path+1); if (node == NULL) { g_warning ("EStorageSetStore: unknown folder updated -- %s", path); return; } iter.user_data = node; iter.stamp = priv->stamp; treepath = esss_get_path (GTK_TREE_MODEL (store), &iter); gtk_tree_model_row_changed (GTK_TREE_MODEL (store), treepath, &iter); if (treepath) gtk_tree_path_free (treepath); } static void removed_folder_cb (EStorageSet *storage_set, const char *path, void *data) { EStorageSetStore *store = E_STORAGE_SET_STORE (data); EStorageSetStorePrivate *priv = store->priv; GNode *node; GtkTreePath *treepath; node = g_hash_table_lookup (priv->path_to_node, path+1); if (node == NULL) { g_warning ("EStorageSetStore: unknown folder removed -- %s", path); return; } g_hash_table_remove (priv->path_to_node, path+1); /* FIXME: subfolder hashtable entries might be leaked */ treepath = tree_path_from_node (store, node); gtk_tree_model_row_deleted (GTK_TREE_MODEL (store), treepath); if (treepath) gtk_tree_path_free (treepath); g_node_destroy (node); } static void close_folder_cb (EStorageSet *storage_set, const char *path, void *data) { g_warning ("FIXME: EStorageSetStore: needs to handle close_folder properly"); #if 0 EStorageSetStore *store; EStorageSetStorePrivate *priv; ETreeModel *etree; ETreePath node; store = E_STORAGE_SET_STORE (data); priv = store->priv; etree = priv->etree_model; node = lookup_node_in_hash (store, path); e_tree_model_node_request_collapse (priv->etree_model, node); #endif } static void connect_storage_set (EStorageSetStore *store, EStorageSet *storage_set, gboolean show_folders) { EStorageSetStorePrivate *priv; GList *storage_list; GList *p; priv = store->priv; priv->storage_set = storage_set; g_object_ref (storage_set); storage_list = e_storage_set_get_storage_list (storage_set); for (p = storage_list; p != NULL; p = p->next) { EStorage *storage = E_STORAGE (p->data); const char *name = e_storage_get_name (storage); GNode *node = g_node_new (g_strdup (name)); g_node_append (priv->root, node); g_hash_table_replace (priv->path_to_node, g_strdup (name), node); if (show_folders) insert_folders (store, node, storage, "/"); } esss_sort (store, priv->root, storage_sort_callback); e_free_object_list (storage_list); g_signal_connect_object (storage_set, "new_storage", G_CALLBACK (new_storage_cb), store, 0); g_signal_connect_object (storage_set, "removed_storage", G_CALLBACK (removed_storage_cb), store, 0); if (!show_folders) return; g_signal_connect_object (storage_set, "new_folder", G_CALLBACK (new_folder_cb), store, 0); g_signal_connect_object (storage_set, "updated_folder", G_CALLBACK (updated_folder_cb), store, 0); g_signal_connect_object (storage_set, "removed_folder", G_CALLBACK (removed_folder_cb), store, 0); g_signal_connect_object (storage_set, "close_folder", G_CALLBACK (close_folder_cb), store, 0); } /** * e_storage_set_store_new: * @storage_set: the #EStorageSet that the store exposes * @show_folders: flag indicating if subfolders should be shown * * Creates a new tree store from the provided #EStorageSet. * * Return value: a new #EStorageSetStore **/ EStorageSetStore * e_storage_set_store_new(EStorageSet *storage_set, gboolean show_folders) { EStorageSetStore *store; g_return_val_if_fail (E_IS_STORAGE_SET(storage_set), NULL); store = E_STORAGE_SET_STORE (g_object_new (E_STORAGE_SET_STORE_TYPE, NULL)); connect_storage_set (store, storage_set, show_folders); return store; } static gboolean esss_real_set_value(EStorageSetStore *store, GtkTreeIter *iter, gint column, GValue *value) { gchar *path; if (column != E_STORAGE_SET_STORE_COLUMN_CHECKED) return FALSE; path = G_NODE (iter->user_data)->data; if (g_value_get_boolean (value)) { g_hash_table_insert (store->priv->checkboxes, path, path); } else { g_hash_table_remove (store->priv->checkboxes, path); } return TRUE; } /** * e_storage_set_store_set_value: * @store: a #EStorageSetStore * @iter: A valid #GtkTreeIter for the row being modified * @column: column number to modify * @value: new value for the cell * * Sets the data in the cell specified by @iter and @column. * The type of @value must be convertible to the type of the * column. * **/ void e_storage_set_store_set_value(EStorageSetStore *store, GtkTreeIter *iter, gint column, GValue *value) { g_return_if_fail(E_IS_STORAGE_SET_STORE(store)); g_return_if_fail(VALID_ITER(iter, store)); g_return_if_fail(VALID_COL(column)); g_return_if_fail(G_IS_VALUE(value)); if (esss_real_set_value (store, iter, column, value)) { GtkTreePath *path; path = gtk_tree_model_get_path(GTK_TREE_MODEL(store), iter); gtk_tree_model_row_changed(GTK_TREE_MODEL(store), path, iter); if (path) gtk_tree_path_free(path); } } /** * e_storage_set_store_get_tree_path: * @store: a #EStorageSetStore * @folder_path: a string representing the #EStorageSet folder path * * Gets a #GtkTreePath corresponding to the folder path specified. * * Return value: the tree path of the folder **/ GtkTreePath * e_storage_set_store_get_tree_path (EStorageSetStore *store, const gchar *folder_path) { GNode *node; g_return_if_fail(E_IS_STORAGE_SET_STORE(store)); node = g_hash_table_lookup (store->priv->path_to_node, folder_path+1); return tree_path_from_node (store, node); } /** * e_storage_set_store_get_folder_path: * @store: a #EStorageSetStore * @folder_path: a string representing the #EStorageSet folder path * * Gets a #GtkTreePath corresponding to the folder path specified. * * Return value: the tree path of the folder **/ const gchar * e_storage_set_store_get_folder_path (EStorageSetStore *store, GtkTreePath *tree_path) { GtkTreeIter iter; GNode *node; g_return_if_fail(E_IS_STORAGE_SET_STORE(store)); if (!esss_get_iter (GTK_TREE_MODEL (store), &iter, tree_path)) return NULL; node = G_NODE (iter.user_data); return (const gchar *)node->data; } /** * e_storage_set_store_set_has_checkbox_func: * @store: a #EStorageSetStore * @func: a callback to determine if a row is checked * @data: callback data * * Sets a callback function for checkbox visibility determination **/ void e_storage_set_store_set_has_checkbox_func (EStorageSetStore *store, EStorageSetStoreHasCheckBoxFunc func, gpointer data) { g_return_if_fail(E_IS_STORAGE_SET_STORE(store)); store->priv->has_checkbox_func = func; store->priv->has_checkbox_func_data = data; }