1 /* Copyright (c) 2006, Nokia Corporation
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * * Neither the name of the Nokia Corporation nor the names of its
14 * contributors may be used to endorse or promote products derived from
15 * this software without specific prior written permission.
17 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
18 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
19 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
20 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
21 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
22 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
24 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
25 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
26 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 #include <glib/gi18n.h>
33 #include <tny-account-store-view.h>
34 #include <tny-gtk-account-list-model.h>
35 #include <tny-gtk-folder-store-tree-model.h>
36 #include <tny-gtk-header-list-model.h>
37 #include <tny-folder.h>
38 #include <tny-folder-monitor.h>
39 #include <tny-account-store.h>
40 #include <tny-account.h>
41 #include <tny-folder.h>
42 #include <tny-camel-folder.h>
43 #include <tny-simple-list.h>
44 #include <modest-tny-folder.h>
45 #include <modest-marshal.h>
46 #include <modest-icon-names.h>
47 #include <modest-tny-account-store.h>
48 #include <modest-text-utils.h>
49 #include <modest-runtime.h>
50 #include "modest-folder-view.h"
52 /* 'private'/'protected' functions */
53 static void modest_folder_view_class_init (ModestFolderViewClass *klass);
54 static void modest_folder_view_init (ModestFolderView *obj);
55 static void modest_folder_view_finalize (GObject *obj);
57 static void tny_account_store_view_init (gpointer g,
60 static void modest_folder_view_set_account_store (TnyAccountStoreView *self,
61 TnyAccountStore *account_store);
63 static gboolean update_model (ModestFolderView *self,
64 ModestTnyAccountStore *account_store);
66 static gboolean update_model_empty (ModestFolderView *self);
68 static void on_selection_changed (GtkTreeSelection *sel, gpointer data);
70 static void on_account_update (TnyAccountStore *account_store,
74 static gint cmp_rows (GtkTreeModel *tree_model,
80 static void on_drag_data_get (GtkWidget *widget,
81 GdkDragContext *context,
82 GtkSelectionData *selection_data,
87 static void on_drag_data_received (GtkWidget *widget,
88 GdkDragContext *context,
91 GtkSelectionData *selection_data,
96 static gboolean on_drag_motion (GtkWidget *widget,
97 GdkDragContext *context,
103 static gint expand_row_timeout (gpointer data);
105 static void setup_drag_and_drop (GtkTreeView *self);
111 static const GtkTargetEntry drag_types[] =
113 { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_WIDGET, TARGET_TREE_ROW },
116 #define ROW_REF_DATA_NAME "row-ref"
119 FOLDER_SELECTION_CHANGED_SIGNAL,
124 typedef struct _ModestFolderViewPrivate ModestFolderViewPrivate;
125 struct _ModestFolderViewPrivate {
126 TnyAccountStore *account_store;
127 TnyFolder *cur_folder;
128 GtkTreeRowReference *cur_row;
133 GtkTreeSelection *cur_selection;
134 TnyFolderStoreQuery *query;
135 guint timer_expander;
137 TnyFolderMonitor *monitor;
139 #define MODEST_FOLDER_VIEW_GET_PRIVATE(o) \
140 (G_TYPE_INSTANCE_GET_PRIVATE((o), \
141 MODEST_TYPE_FOLDER_VIEW, \
142 ModestFolderViewPrivate))
144 static GObjectClass *parent_class = NULL;
146 static guint signals[LAST_SIGNAL] = {0};
149 modest_folder_view_get_type (void)
151 static GType my_type = 0;
153 static const GTypeInfo my_info = {
154 sizeof(ModestFolderViewClass),
155 NULL, /* base init */
156 NULL, /* base finalize */
157 (GClassInitFunc) modest_folder_view_class_init,
158 NULL, /* class finalize */
159 NULL, /* class data */
160 sizeof(ModestFolderView),
162 (GInstanceInitFunc) modest_folder_view_init,
166 static const GInterfaceInfo tny_account_store_view_info = {
167 (GInterfaceInitFunc) tny_account_store_view_init, /* interface_init */
168 NULL, /* interface_finalize */
169 NULL /* interface_data */
173 my_type = g_type_register_static (GTK_TYPE_TREE_VIEW,
177 g_type_add_interface_static (my_type,
178 TNY_TYPE_ACCOUNT_STORE_VIEW,
179 &tny_account_store_view_info);
185 modest_folder_view_class_init (ModestFolderViewClass *klass)
187 GObjectClass *gobject_class;
188 gobject_class = (GObjectClass*) klass;
190 parent_class = g_type_class_peek_parent (klass);
191 gobject_class->finalize = modest_folder_view_finalize;
193 klass->update_model = modest_folder_view_update_model;
195 g_type_class_add_private (gobject_class,
196 sizeof(ModestFolderViewPrivate));
198 signals[FOLDER_SELECTION_CHANGED_SIGNAL] =
199 g_signal_new ("folder_selection_changed",
200 G_TYPE_FROM_CLASS (gobject_class),
202 G_STRUCT_OFFSET (ModestFolderViewClass,
203 folder_selection_changed),
205 modest_marshal_VOID__POINTER_BOOLEAN,
206 G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_BOOLEAN);
209 * ModestFolderView::folder-moved
210 * @self: the #MailOperation that emits the signal
211 * @folder: the #TnyFolder that is going to be moved
212 * @parent: then #TnyFolderStore that is going to be the new parent
213 * @done: indicates if the folder move was correctly completed or not
214 * @user_data: user data set when the signal handler was connected
216 * Emitted when a the user wants to move a folder through a
217 * drag and drop action
219 signals[FOLDER_MOVED_SIGNAL] =
220 g_signal_new ("folder_moved",
221 G_TYPE_FROM_CLASS (gobject_class),
223 G_STRUCT_OFFSET (ModestFolderViewClass, folder_moved),
225 modest_marshal_VOID__POINTER_POINTER_POINTER,
226 G_TYPE_NONE, 3, G_TYPE_POINTER, G_TYPE_POINTER, G_TYPE_POINTER);
232 text_cell_data (GtkTreeViewColumn *column, GtkCellRenderer *renderer,
233 GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data)
241 g_return_if_fail (column);
242 g_return_if_fail (tree_model);
244 gtk_tree_model_get (tree_model, iter,
245 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &fname,
246 TNY_GTK_FOLDER_STORE_TREE_MODEL_UNREAD_COLUMN, &unread,
247 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
248 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder,
250 rendobj = G_OBJECT(renderer);
255 if (folder && type != TNY_FOLDER_TYPE_ROOT) { /* FIXME: tnymail bug? crashes with root folders */
256 if (modest_tny_folder_is_local_folder (folder)) {
258 type = modest_tny_folder_get_local_folder_type (folder);
259 if (type != TNY_FOLDER_TYPE_UNKNOWN) {
261 fname = g_strdup(modest_local_folder_info_get_type_display_name (type));
264 } else if (folder && type == TNY_FOLDER_TYPE_ROOT) {
265 if (strcmp (fname, MODEST_LOCAL_FOLDERS_ACCOUNT_NAME) == 0) {/* FIXME: hack */
267 fname = g_strdup (MODEST_LOCAL_FOLDERS_DISPLAY_NAME);
272 gchar *folder_title = g_strdup_printf ("%s (%d)", fname, unread);
273 g_object_set (rendobj,"text", folder_title, "weight", 800, NULL);
274 g_free (folder_title);
276 g_object_set (rendobj,"text", fname, "weight", 400, NULL);
283 get_cached_icon (const gchar *name)
288 static GHashTable *icon_cache = NULL;
290 g_return_val_if_fail (name, NULL);
292 if (G_UNLIKELY(!icon_cache))
293 icon_cache = modest_cache_mgr_get_cache (modest_runtime_get_cache_mgr(),
294 MODEST_CACHE_MGR_CACHE_TYPE_PIXBUF);
296 if (!icon_cache || !g_hash_table_lookup_extended (icon_cache, name, &orig_key, &pixbuf)) {
297 pixbuf = (gpointer)gdk_pixbuf_new_from_file (name, &err);
299 g_printerr ("modest: error in icon factory while loading '%s': %s\n",
303 /* if we cannot find it, we still insert (if we have a cache), so we get the error
306 g_hash_table_insert (icon_cache, g_strdup(name),(gpointer)pixbuf);
308 return GDK_PIXBUF(pixbuf);
313 icon_cell_data (GtkTreeViewColumn *column, GtkCellRenderer *renderer,
314 GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data)
322 rendobj = G_OBJECT(renderer);
323 gtk_tree_model_get (tree_model, iter,
324 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
325 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &fname,
326 TNY_GTK_FOLDER_STORE_TREE_MODEL_UNREAD_COLUMN, &unread, -1);
327 rendobj = G_OBJECT(renderer);
329 if (type == TNY_FOLDER_TYPE_NORMAL || type == TNY_FOLDER_TYPE_UNKNOWN) {
330 type = modest_tny_folder_guess_folder_type_from_name (fname);
335 case TNY_FOLDER_TYPE_ROOT:
336 pixbuf = get_cached_icon (MODEST_FOLDER_ICON_ACCOUNT);
338 case TNY_FOLDER_TYPE_INBOX:
339 pixbuf = get_cached_icon (MODEST_FOLDER_ICON_INBOX);
341 case TNY_FOLDER_TYPE_OUTBOX:
342 pixbuf = get_cached_icon (MODEST_FOLDER_ICON_OUTBOX);
344 case TNY_FOLDER_TYPE_JUNK:
345 pixbuf = get_cached_icon (MODEST_FOLDER_ICON_JUNK);
347 case TNY_FOLDER_TYPE_SENT:
348 pixbuf = get_cached_icon (MODEST_FOLDER_ICON_SENT);
350 case TNY_FOLDER_TYPE_TRASH:
351 pixbuf = get_cached_icon (MODEST_FOLDER_ICON_TRASH);
353 case TNY_FOLDER_TYPE_DRAFTS:
354 pixbuf = get_cached_icon (MODEST_FOLDER_ICON_DRAFTS);
356 case TNY_FOLDER_TYPE_NOTES:
357 pixbuf = get_cached_icon (MODEST_FOLDER_ICON_NOTES);
359 case TNY_FOLDER_TYPE_CALENDAR:
360 pixbuf = get_cached_icon (MODEST_FOLDER_ICON_CALENDAR);
362 case TNY_FOLDER_TYPE_CONTACTS:
363 pixbuf = get_cached_icon (MODEST_FOLDER_ICON_CONTACTS);
365 case TNY_FOLDER_TYPE_NORMAL:
367 pixbuf = get_cached_icon (MODEST_FOLDER_ICON_NORMAL);
370 g_object_set (rendobj, "pixbuf", pixbuf, NULL);
374 modest_folder_view_init (ModestFolderView *obj)
376 ModestFolderViewPrivate *priv;
377 GtkTreeViewColumn *column;
378 GtkCellRenderer *renderer;
379 GtkTreeSelection *sel;
381 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(obj);
383 priv->timer_expander = 0;
384 priv->account_store = NULL;
385 priv->cur_folder = NULL;
386 priv->cur_row = NULL;
388 priv->monitor = NULL;
390 priv->lock = g_mutex_new ();
392 column = gtk_tree_view_column_new ();
393 gtk_tree_view_append_column (GTK_TREE_VIEW(obj),column);
395 renderer = gtk_cell_renderer_pixbuf_new();
396 gtk_tree_view_column_pack_start (column, renderer, FALSE);
397 gtk_tree_view_column_set_cell_data_func(column, renderer,
398 icon_cell_data, NULL, NULL);
400 renderer = gtk_cell_renderer_text_new();
401 gtk_tree_view_column_pack_start (column, renderer, FALSE);
402 gtk_tree_view_column_set_cell_data_func(column, renderer,
403 text_cell_data, NULL, NULL);
405 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(obj));
406 gtk_tree_selection_set_mode (sel, GTK_SELECTION_SINGLE);
408 gtk_tree_view_column_set_spacing (column, 2);
409 gtk_tree_view_column_set_resizable (column, TRUE);
410 gtk_tree_view_column_set_fixed_width (column, TRUE);
411 gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW(obj), FALSE);
412 gtk_tree_view_set_enable_search (GTK_TREE_VIEW(obj), FALSE);
414 setup_drag_and_drop (GTK_TREE_VIEW(obj));
418 tny_account_store_view_init (gpointer g, gpointer iface_data)
420 TnyAccountStoreViewIface *klass = (TnyAccountStoreViewIface *)g;
422 klass->set_account_store_func = modest_folder_view_set_account_store;
428 modest_folder_view_finalize (GObject *obj)
430 ModestFolderViewPrivate *priv;
431 GtkTreeSelection *sel;
433 g_return_if_fail (obj);
435 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(obj);
437 if (priv->timer_expander != 0) {
438 g_source_remove (priv->timer_expander);
439 priv->timer_expander = 0;
442 if (priv->account_store) {
443 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
445 g_object_unref (G_OBJECT(priv->account_store));
446 priv->account_store = NULL;
450 g_mutex_free (priv->lock);
455 g_object_unref (G_OBJECT (priv->query));
459 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(obj));
461 g_signal_handler_disconnect (G_OBJECT(sel), priv->sig2);
463 G_OBJECT_CLASS(parent_class)->finalize (obj);
468 modest_folder_view_set_account_store (TnyAccountStoreView *self, TnyAccountStore *account_store)
470 ModestFolderViewPrivate *priv;
473 g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
474 g_return_if_fail (TNY_IS_ACCOUNT_STORE (account_store));
476 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
477 device = tny_account_store_get_device (account_store);
479 if (G_UNLIKELY (priv->account_store)) {
480 TnyDevice *old_device;
482 old_device = tny_account_store_get_device (priv->account_store);
484 if (g_signal_handler_is_connected (G_OBJECT (old_device), priv->sig1))
485 g_signal_handler_disconnect (G_OBJECT (old_device),
488 g_object_unref (G_OBJECT (priv->account_store));
489 g_object_unref (G_OBJECT (old_device));
492 priv->account_store = g_object_ref (G_OBJECT (account_store));
494 priv->sig1 = g_signal_connect (G_OBJECT(account_store), "account_update",
495 G_CALLBACK (on_account_update), self);
497 if (!update_model (MODEST_FOLDER_VIEW (self),
498 MODEST_TNY_ACCOUNT_STORE (priv->account_store)))
499 g_printerr ("modest: failed to update model\n");
501 g_object_unref (G_OBJECT (device));
505 on_account_update (TnyAccountStore *account_store, const gchar *account,
508 if (!update_model (MODEST_FOLDER_VIEW(user_data),
509 MODEST_TNY_ACCOUNT_STORE(account_store)))
510 g_printerr ("modest: failed to update model for changes in '%s'",
515 modest_folder_view_set_title (ModestFolderView *self, const gchar *title)
517 GtkTreeViewColumn *col;
519 g_return_if_fail (self);
521 col = gtk_tree_view_get_column (GTK_TREE_VIEW(self), 0);
523 g_printerr ("modest: failed get column for title\n");
527 gtk_tree_view_column_set_title (col, title);
528 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(self),
533 modest_folder_view_new (TnyFolderStoreQuery *query)
536 ModestFolderViewPrivate *priv;
537 GtkTreeSelection *sel;
539 self = G_OBJECT (g_object_new (MODEST_TYPE_FOLDER_VIEW, NULL));
540 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
542 priv->query = g_object_ref (query);
544 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
545 priv->sig2 = g_signal_connect (sel, "changed",
546 G_CALLBACK (on_selection_changed), self);
547 return GTK_WIDGET(self);
552 update_model_empty (ModestFolderView *self)
554 ModestFolderViewPrivate *priv;
556 g_return_val_if_fail (self, FALSE);
557 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
559 g_mutex_lock (priv->lock);
562 tny_folder_monitor_stop (priv->monitor);
563 g_object_unref(G_OBJECT(priv->monitor));
564 priv->monitor = NULL;
567 g_mutex_unlock (priv->lock);
569 g_signal_emit (G_OBJECT(self), signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0,
575 /* this feels dirty; any other way to expand all the root items? */
577 expand_root_items (ModestFolderView *self)
580 path = gtk_tree_path_new_first ();
582 /* all folders should have child items, so.. */
583 while (gtk_tree_view_expand_row (GTK_TREE_VIEW(self), path, FALSE))
584 gtk_tree_path_next (path);
586 gtk_tree_path_free (path);
590 update_model (ModestFolderView *self, ModestTnyAccountStore *account_store)
592 ModestFolderViewPrivate *priv;
594 TnyList *account_list;
595 GtkTreeModel *model, *sortable;
597 g_return_val_if_fail (account_store, FALSE);
599 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
601 /* Notify that there is no folder selected */
602 update_model_empty (self);
604 /* FIXME: the local accounts are not shown when the query
605 selects only the subscribed folders. */
606 /* model = tny_gtk_folder_store_tree_model_new (TRUE, priv->query); */
607 model = tny_gtk_folder_store_tree_model_new (TRUE, NULL);
608 account_list = TNY_LIST(model);
610 tny_account_store_get_accounts (TNY_ACCOUNT_STORE(account_store),
612 TNY_ACCOUNT_STORE_STORE_ACCOUNTS);
614 sortable = gtk_tree_model_sort_new_with_model (model);
615 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(sortable),
616 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN,
618 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
619 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN,
620 cmp_rows, NULL, NULL);
623 gtk_tree_view_set_model (GTK_TREE_VIEW(self), sortable);
624 expand_root_items (self); /* expand all account folders */
627 g_object_unref (model);
633 on_selection_changed (GtkTreeSelection *sel, gpointer user_data)
635 GtkTreeModel *model_sort, *model;
636 TnyFolder *folder = NULL;
637 GtkTreeIter iter, iter_sort;
639 ModestFolderView *tree_view;
640 ModestFolderViewPrivate *priv;
643 g_return_if_fail (sel);
644 g_return_if_fail (user_data);
646 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(user_data);
647 priv->cur_selection = sel;
649 /* folder was _un_selected if true */
650 if (!gtk_tree_selection_get_selected (sel, &model_sort, &iter_sort)) {
651 priv->cur_folder = NULL; /* FIXME: need this? */
652 priv->cur_row = NULL; /* FIXME: need this? */
656 model = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (model_sort));
657 gtk_tree_model_sort_convert_iter_to_child_iter (GTK_TREE_MODEL_SORT (model_sort),
661 tree_view = MODEST_FOLDER_VIEW (user_data);
663 gtk_tree_model_get (model, &iter,
664 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
665 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder,
668 if (type == TNY_FOLDER_TYPE_ROOT)
671 /* emit 2 signals: one for the unselection of the old one,
672 * and one for the selection of the new on */
673 g_signal_emit (G_OBJECT(tree_view), signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0,
674 priv->cur_folder, FALSE);
676 if (priv->cur_folder) {
677 /* tny_folder_sync (priv->cur_folder, TRUE, NULL); /\* FIXME *\/ */
678 gtk_tree_row_reference_free (priv->cur_row);
680 priv->cur_folder = folder;
681 g_signal_emit (G_OBJECT(tree_view), signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0,
684 path = gtk_tree_model_get_path (model_sort, &iter_sort);
685 priv->cur_row = gtk_tree_row_reference_new (model_sort, path);
686 gtk_tree_path_free (path);
690 modest_folder_view_update_model (ModestFolderView *self, TnyAccountStore *account_store)
692 g_return_val_if_fail (MODEST_IS_FOLDER_VIEW (self), FALSE);
694 g_signal_emit (G_OBJECT(self), signals[FOLDER_SELECTION_CHANGED_SIGNAL],
697 return update_model (self, MODEST_TNY_ACCOUNT_STORE(account_store)); /* ugly */
701 modest_folder_view_get_selected (ModestFolderView *self)
703 ModestFolderViewPrivate *priv;
705 g_return_val_if_fail (self, NULL);
707 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
708 if (priv->cur_folder)
709 g_object_ref (priv->cur_folder);
711 return priv->cur_folder;
715 get_model_iter (ModestFolderView *self,
716 GtkTreeModel **model,
719 GtkTreeModel *model_sort;
720 GtkTreeIter iter_sort;
722 ModestFolderViewPrivate *priv;
724 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
726 if (!priv->cur_folder)
729 if (!gtk_tree_row_reference_valid (priv->cur_row))
732 model_sort = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
733 *model = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (model_sort));
735 /* Get path to retrieve iter */
736 path = gtk_tree_row_reference_get_path (priv->cur_row);
737 if (!gtk_tree_model_get_iter (model_sort, &iter_sort, path))
740 gtk_tree_model_sort_convert_iter_to_child_iter (GTK_TREE_MODEL_SORT (model_sort),
747 modest_folder_view_rename (ModestFolderView *self)
751 ModestFolderViewPrivate *priv;
754 g_return_val_if_fail (MODEST_IS_FOLDER_VIEW (self), FALSE);
756 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
758 if (!get_model_iter (self, &model, &iter))
761 /* Remove old name */
762 gtk_tree_model_get (model, &iter,
763 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN,
768 gtk_tree_store_set (GTK_TREE_STORE (model), &iter,
769 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN,
770 tny_folder_get_name (priv->cur_folder), -1);
772 /* Invalidate selection */
773 g_signal_emit (G_OBJECT(self), signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0,
774 priv->cur_folder, TRUE);
780 modest_folder_view_add_subfolder (ModestFolderView *self, TnyFolder *folder)
783 GtkTreeIter iter, child;
785 g_return_val_if_fail (MODEST_IS_FOLDER_VIEW (self), FALSE);
787 if (!get_model_iter (self, &model, &iter))
790 /* Append a new child to the folder */
791 gtk_tree_store_append (GTK_TREE_STORE (model), &child, &iter);
792 gtk_tree_store_set (GTK_TREE_STORE (model), &child,
793 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN,
794 tny_folder_get_name (TNY_FOLDER (folder)),
795 TNY_GTK_FOLDER_STORE_TREE_MODEL_UNREAD_COLUMN,
796 tny_folder_get_unread_count (TNY_FOLDER (folder)),
797 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN,
798 tny_folder_get_folder_type (TNY_FOLDER (folder)),
799 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN,
806 cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
810 gchar *name1, *name2;
812 TnyFolder *folder1, *folder2;
814 gtk_tree_model_get (tree_model, iter1,
815 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name1,
816 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
817 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder1,
819 gtk_tree_model_get (tree_model, iter2,
820 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name2,
821 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder2,
824 /* local_folders should be the last one */
825 if (type == TNY_FOLDER_TYPE_ROOT) {
826 /* the account name is also the name of the root folder
827 * in case of local folders */
828 if (name1 && strcmp (name1, MODEST_LOCAL_FOLDERS_ACCOUNT_NAME) == 0)
830 else if (name2 && strcmp (name2, MODEST_LOCAL_FOLDERS_ACCOUNT_NAME) == 0)
833 cmp = modest_text_utils_utf8_strcmp (name1, name2, TRUE);
835 cmp = modest_text_utils_utf8_strcmp (name1, name2, TRUE);
843 /*****************************************************************************/
844 /* DRAG and DROP stuff */
845 /*****************************************************************************/
848 * This function fills the #GtkSelectionData with the row and the
849 * model that has been dragged. It's called when this widget is a
850 * source for dnd after the event drop happened
853 on_drag_data_get (GtkWidget *widget,
854 GdkDragContext *context,
855 GtkSelectionData *selection_data,
860 GtkTreeSelection *selection;
861 GtkTreeModel *model_sort, *model;
863 GtkTreePath *source_row_sort;
864 GtkTreePath *source_row;
866 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
867 gtk_tree_selection_get_selected (selection, &model_sort, &iter);
868 source_row_sort = gtk_tree_model_get_path (model_sort, &iter);
870 /* Get the unsorted path and model */
871 model = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (model_sort));
872 source_row = gtk_tree_model_sort_convert_path_to_child_path (GTK_TREE_MODEL_SORT (model_sort),
875 gtk_tree_set_row_drag_data (selection_data,
879 gtk_tree_path_free (source_row_sort);
880 gtk_tree_path_free (source_row);
884 * This function receives the data set by the "drag-data-get" signal
885 * handler. This information comes within the #GtkSelectionData. This
886 * function will manage both the drags of folders of the treeview and
887 * drags of headers of the header view widget.
890 on_drag_data_received (GtkWidget *widget,
891 GdkDragContext *context,
894 GtkSelectionData *selection_data,
899 GtkWidget *source_widget;
900 GtkTreeModel *model_sort, *dest_model, *source_model;
901 GtkTreePath *source_row, *dest_row, *child_dest_row;
902 GtkTreeViewDropPosition pos;
903 gboolean success = FALSE, delete_source = FALSE;
905 /* Do not allow further process */
906 g_signal_stop_emission_by_name (widget, "drag-data-received");
908 /* Check if the get_data failed */
909 if (selection_data == NULL || selection_data->length < 0)
913 if (context->action == GDK_ACTION_MOVE)
914 delete_source = TRUE;
917 source_widget = gtk_drag_get_source_widget (context);
918 model_sort = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
920 gtk_tree_get_row_drag_data (selection_data,
924 /* Select the destination model */
925 if (source_widget == widget)
926 dest_model = source_model;
928 dest_model = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (model_sort));
931 /* Get the path to the destination row. Can not call
932 gtk_tree_view_get_drag_dest_row() because the source row
933 it's not selected anymore */
934 gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget),
939 /* Only allow drops IN other rows */
941 pos == GTK_TREE_VIEW_DROP_BEFORE ||
942 pos == GTK_TREE_VIEW_DROP_AFTER)
945 /* Get the destination row in the usorted model */
947 gtk_tree_model_sort_convert_path_to_child_path (GTK_TREE_MODEL_SORT (model_sort),
949 gtk_tree_path_free (dest_row);
951 /* Drags from the header view */
952 if ((target_type == TARGET_TREE_ROW) && (source_widget != widget)) {
954 /* TODO: do the mail operation */
958 GtkTreeRowReference *source_row_reference;
959 GtkTreeIter parent_iter, iter;
961 TnyFolderStore *parent_folder;
964 /* Check if the drag is possible */
965 if (!gtk_tree_path_compare (source_row, child_dest_row))
968 if (!gtk_tree_drag_dest_row_drop_possible (GTK_TREE_DRAG_DEST (dest_model),
973 /* Do the mail operation */
974 gtk_tree_model_get_iter (source_model, &parent_iter, child_dest_row);
975 gtk_tree_model_get_iter (source_model, &iter, source_row);
976 gtk_tree_model_get (source_model, &parent_iter,
977 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN,
979 gtk_tree_model_get (source_model, &iter,
980 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN,
983 g_signal_emit (G_OBJECT (widget), signals[FOLDER_MOVED_SIGNAL], 0,
984 folder, parent_folder, &done);
988 /* Get a row reference to the source path because the path
989 could change after the insertion. The gtk_drag_finish() is
990 not able to delete the source because that, so we have to
992 source_row_reference = gtk_tree_row_reference_new (source_model, source_row);
993 gtk_tree_path_free (source_row);
995 /* Insert the dragged row as a child of the dest row */
996 gtk_tree_path_down (child_dest_row);
997 if (gtk_tree_drag_dest_drag_data_received (GTK_TREE_DRAG_DEST (dest_model),
1001 source_row = gtk_tree_row_reference_get_path (source_row_reference);
1005 gtk_tree_row_reference_free (source_row_reference);
1008 gtk_tree_path_free (child_dest_row);
1010 /* Save the new path, will be used by the
1011 drag-data-delete handler */
1013 g_object_set_data (G_OBJECT (source_widget),
1015 gtk_tree_path_copy (source_row));
1018 gtk_drag_finish (context, success, (success && delete_source), time);
1022 * We define a "drag-drop" signal handler because we do not want to
1023 * use the default one, because the default one always calls
1024 * gtk_drag_finish and we prefer to do it in the "drag-data-received"
1025 * signal handler, because there we have all the information available
1026 * to know if the dnd was a success or not.
1029 drag_drop_cb (GtkWidget *widget,
1030 GdkDragContext *context,
1036 if (!context->targets)
1039 /* Request the data from the source. */
1040 gtk_drag_get_data(widget,
1042 GDK_POINTER_TO_ATOM (context->targets->data),
1049 * This function deletes the data that has been dragged from its
1050 * source widget. Since is a function received by the source of the
1051 * drag, this function only deletes rows of the folder view
1052 * widget. The header view widget will need to define its own one.
1055 drag_data_delete_cb (GtkWidget *widget,
1056 GdkDragContext *context,
1059 GtkTreePath *source_row;
1060 GtkTreeModel *model_sort, *model;
1062 /* Clean dest row */
1063 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
1065 GTK_TREE_VIEW_DROP_BEFORE);
1067 model_sort = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
1068 model = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (model_sort));
1069 source_row = g_object_steal_data (G_OBJECT (widget), ROW_REF_DATA_NAME);
1071 /* Delete the source row */
1072 gtk_tree_drag_source_drag_data_delete (GTK_TREE_DRAG_SOURCE (model),
1075 gtk_tree_path_free (source_row);
1079 * This function expands a node of a tree view if it's not expanded
1080 * yet. Not sure why it needs the threads stuff, but gtk+`example code
1081 * does that, so that's why they're here.
1084 expand_row_timeout (gpointer data)
1086 GtkTreeView *tree_view = data;
1087 GtkTreePath *dest_path = NULL;
1088 GtkTreeViewDropPosition pos;
1089 gboolean result = FALSE;
1091 GDK_THREADS_ENTER ();
1093 gtk_tree_view_get_drag_dest_row (tree_view,
1098 (pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER ||
1099 pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE)) {
1100 gtk_tree_view_expand_row (tree_view, dest_path, FALSE);
1101 gtk_tree_path_free (dest_path);
1105 gtk_tree_path_free (dest_path);
1110 GDK_THREADS_LEAVE ();
1116 * This function is called whenever the pointer is moved over a widget
1117 * while dragging some data. It installs a timeout that will expand a
1118 * node of the treeview if not expanded yet. This function also calls
1119 * gdk_drag_status in order to set the suggested action that will be
1120 * used by the "drag-data-received" signal handler to know if we
1121 * should do a move or just a copy of the data.
1124 on_drag_motion (GtkWidget *widget,
1125 GdkDragContext *context,
1131 GtkTreeViewDropPosition pos;
1132 GtkTreePath *dest_row;
1133 ModestFolderViewPrivate *priv;
1134 GdkDragAction suggested_action;
1136 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (widget);
1138 if (priv->timer_expander != 0) {
1139 g_source_remove (priv->timer_expander);
1140 priv->timer_expander = 0;
1143 gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget),
1151 /* Expand the selected row after 1/2 second */
1152 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), dest_row)) {
1153 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), dest_row, pos);
1154 priv->timer_expander = g_timeout_add (500, expand_row_timeout, widget);
1156 gtk_tree_path_free (dest_row);
1158 /* Select the desired action. By default we pick MOVE */
1159 suggested_action = GDK_ACTION_MOVE;
1161 if (context->actions == GDK_ACTION_COPY)
1162 gdk_drag_status(context, GDK_ACTION_COPY, time);
1163 else if (context->actions == GDK_ACTION_MOVE)
1164 gdk_drag_status(context, GDK_ACTION_MOVE, time);
1165 else if (context->actions & suggested_action)
1166 gdk_drag_status(context, suggested_action, time);
1168 gdk_drag_status(context, GDK_ACTION_DEFAULT, time);
1174 * This function sets the treeview as a source and a target for dnd
1175 * events. It also connects all the requirede signals.
1178 setup_drag_and_drop (GtkTreeView *self)
1180 /* Set up the folder view as a dnd destination. Set only the
1181 highlight flag, otherwise gtk will have a different
1183 gtk_drag_dest_set (GTK_WIDGET (self),
1184 GTK_DEST_DEFAULT_HIGHLIGHT,
1186 G_N_ELEMENTS (drag_types),
1187 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1189 gtk_signal_connect(GTK_OBJECT (self),
1190 "drag_data_received",
1191 GTK_SIGNAL_FUNC(on_drag_data_received),
1195 /* Set up the treeview as a dnd source */
1196 gtk_drag_source_set (GTK_WIDGET (self),
1199 G_N_ELEMENTS (drag_types),
1200 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1202 gtk_signal_connect(GTK_OBJECT (self),
1204 GTK_SIGNAL_FUNC(drag_data_delete_cb),
1207 gtk_signal_connect(GTK_OBJECT (self),
1209 GTK_SIGNAL_FUNC(on_drag_motion),
1213 gtk_signal_connect(GTK_OBJECT (self),
1215 GTK_SIGNAL_FUNC(on_drag_data_get),
1218 gtk_signal_connect(GTK_OBJECT (self),
1220 GTK_SIGNAL_FUNC(drag_drop_cb),