/* 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 <string.h>
#include <gtk/gtktreemodel.h>
#include <gtk/gtktreednd.h>
#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;
}