Bug 793125 - Crash due to popup menus left attached too long
[evolution.git] / src / mail / em-subscription-editor.c
blob404cb7d1d4bbed4a407b2fe26e14c364634f2112
1 /*
2 * em-subscription-editor.c
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation.
8 * This program is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
11 * for more details.
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program; if not, see <http://www.gnu.org/licenses/>.
18 #include "evolution-config.h"
20 #include <string.h>
21 #include <glib/gi18n-lib.h>
23 #include <e-util/e-util.h>
25 #include "em-folder-utils.h"
26 #include "em-subscription-editor.h"
28 #define EM_SUBSCRIPTION_EDITOR_GET_PRIVATE(obj) \
29 (G_TYPE_INSTANCE_GET_PRIVATE \
30 ((obj), EM_TYPE_SUBSCRIPTION_EDITOR, EMSubscriptionEditorPrivate))
32 #define FOLDER_CAN_SELECT(folder_info) \
33 ((folder_info) != NULL && \
34 ((folder_info)->flags & CAMEL_FOLDER_NOSELECT) == 0)
35 #define FOLDER_SUBSCRIBED(folder_info) \
36 ((folder_info) != NULL && \
37 ((folder_info)->flags & CAMEL_FOLDER_SUBSCRIBED) != 0)
39 typedef struct _AsyncContext AsyncContext;
40 typedef struct _TreeRowData TreeRowData;
41 typedef struct _StoreData StoreData;
43 struct _EMSubscriptionEditorPrivate {
44 EMailSession *session;
45 CamelStore *initial_store;
47 GtkWidget *combo_box; /* not referenced */
48 GtkWidget *entry; /* not referenced */
49 GtkWidget *notebook; /* not referenced */
50 GtkWidget *subscribe_button; /* not referenced */
51 GtkWidget *subscribe_arrow; /* not referenced */
52 GtkWidget *unsubscribe_button; /* not referenced */
53 GtkWidget *unsubscribe_arrow; /* not referenced */
54 GtkWidget *collapse_all_button; /* not referenced */
55 GtkWidget *expand_all_button; /* not referenced */
56 GtkWidget *refresh_button; /* not referenced */
57 GtkWidget *stop_button; /* not referenced */
59 /* Indicies coincide with the combo box. */
60 GPtrArray *stores;
62 /* Points at an item in the stores array. */
63 StoreData *active;
65 /* Casefolded search string. */
66 gchar *search_string;
68 guint timeout_id;
71 struct _TreeRowData {
72 CamelFolderInfo *folder_info;
73 GtkTreeRowReference *reference;
76 struct _AsyncContext {
77 EMSubscriptionEditor *editor;
78 GQueue *tree_rows;
81 struct _StoreData {
82 CamelStore *store;
83 GtkTreeView *tree_view;
84 GtkTreeModel *list_store;
85 GtkTreeModel *tree_store;
86 GCancellable *cancellable;
87 CamelFolderInfo *folder_info;
88 gboolean filtered_view;
89 gboolean needs_refresh;
92 enum {
93 PROP_0,
94 PROP_SESSION,
95 PROP_STORE
98 enum {
99 COL_CASEFOLDED, /* G_TYPE_STRING */
100 COL_FOLDER_NAME, /* G_TYPE_STRING */
101 COL_FOLDER_ICON, /* G_TYPE_STRING */
102 COL_FOLDER_INFO, /* G_TYPE_POINTER */
103 N_COLUMNS
106 G_DEFINE_TYPE (EMSubscriptionEditor, em_subscription_editor, GTK_TYPE_DIALOG)
108 static void
109 tree_row_data_free (TreeRowData *tree_row_data)
111 g_return_if_fail (tree_row_data != NULL);
113 gtk_tree_row_reference_free (tree_row_data->reference);
114 g_slice_free (TreeRowData, tree_row_data);
117 static AsyncContext *
118 async_context_new (EMSubscriptionEditor *editor,
119 GQueue *tree_rows)
121 AsyncContext *context;
123 context = g_slice_new0 (AsyncContext);
124 context->editor = g_object_ref (editor);
126 /* Transfer GQueue contents. */
127 context->tree_rows = g_queue_copy (tree_rows);
128 g_queue_clear (tree_rows);
130 return context;
133 static void
134 async_context_free (AsyncContext *context)
136 while (!g_queue_is_empty (context->tree_rows))
137 tree_row_data_free (g_queue_pop_head (context->tree_rows));
139 g_object_unref (context->editor);
140 g_queue_free (context->tree_rows);
142 g_slice_free (AsyncContext, context);
145 static void
146 store_data_free (StoreData *data)
148 if (data->store != NULL)
149 g_object_unref (data->store);
151 if (data->tree_view != NULL)
152 g_object_unref (data->tree_view);
154 if (data->list_store != NULL)
155 g_object_unref (data->list_store);
157 if (data->tree_store != NULL)
158 g_object_unref (data->tree_store);
160 if (data->cancellable != NULL) {
161 g_cancellable_cancel (data->cancellable);
162 g_object_unref (data->cancellable);
165 camel_folder_info_free (data->folder_info);
167 g_slice_free (StoreData, data);
170 static void
171 subscription_editor_populate (EMSubscriptionEditor *editor,
172 CamelFolderInfo *folder_info,
173 GtkTreeIter *parent,
174 GList **expand_paths)
176 GtkListStore *list_store;
177 GtkTreeStore *tree_store;
179 list_store = GTK_LIST_STORE (editor->priv->active->list_store);
180 tree_store = GTK_TREE_STORE (editor->priv->active->tree_store);
182 while (folder_info != NULL) {
183 GtkTreeIter iter;
184 const gchar *icon_name;
185 gchar *casefolded;
187 icon_name =
188 em_folder_utils_get_icon_name (folder_info->flags);
190 casefolded = g_utf8_casefold (folder_info->full_name, -1);
192 gtk_list_store_append (list_store, &iter);
194 gtk_list_store_set (
195 list_store, &iter,
196 COL_CASEFOLDED, casefolded,
197 COL_FOLDER_ICON, icon_name,
198 COL_FOLDER_NAME, folder_info->full_name,
199 COL_FOLDER_INFO, folder_info, -1);
201 gtk_tree_store_append (tree_store, &iter, parent);
203 gtk_tree_store_set (
204 tree_store, &iter,
205 COL_CASEFOLDED, NULL, /* not needed */
206 COL_FOLDER_ICON, icon_name,
207 COL_FOLDER_NAME, folder_info->display_name,
208 COL_FOLDER_INFO, folder_info, -1);
210 if (FOLDER_SUBSCRIBED (folder_info)) {
211 GtkTreePath *path;
213 path = gtk_tree_model_get_path (
214 GTK_TREE_MODEL (tree_store), &iter);
215 *expand_paths = g_list_prepend (*expand_paths, path);
218 g_free (casefolded);
220 if (folder_info->child != NULL)
221 subscription_editor_populate (
222 editor, folder_info->child,
223 &iter, expand_paths);
225 folder_info = folder_info->next;
229 static void
230 expand_paths_cb (gpointer path,
231 gpointer tree_view)
233 gtk_tree_view_expand_to_path (tree_view, path);
236 static void
237 subscription_editor_get_folder_info_done (CamelStore *store,
238 GAsyncResult *result,
239 EMSubscriptionEditor *editor)
241 GtkTreePath *path;
242 GtkTreeView *tree_view;
243 GtkTreeModel *list_store;
244 GtkTreeModel *tree_store;
245 GtkTreeModel *model;
246 GtkTreeSelection *selection;
247 CamelFolderInfo *folder_info;
248 GdkWindow *window;
249 GList *expand_paths = NULL;
250 GError *error = NULL;
252 folder_info = camel_store_get_folder_info_finish (
253 store, result, &error);
255 /* Just return quietly if we were cancelled. */
256 if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
257 g_warn_if_fail (folder_info == NULL);
258 g_error_free (error);
259 goto exit;
262 gtk_widget_set_sensitive (editor->priv->notebook, TRUE);
263 gtk_widget_set_sensitive (editor->priv->refresh_button, TRUE);
264 gtk_widget_set_sensitive (editor->priv->stop_button, FALSE);
266 window = gtk_widget_get_window (GTK_WIDGET (editor));
267 gdk_window_set_cursor (window, NULL);
269 /* XXX Do something smarter with errors. */
270 if (error != NULL) {
271 g_warn_if_fail (folder_info == NULL);
272 e_notice (GTK_WINDOW (editor), GTK_MESSAGE_ERROR, "%s", error->message);
273 g_error_free (error);
274 goto exit;
277 g_return_if_fail (folder_info != NULL);
279 camel_folder_info_free (editor->priv->active->folder_info);
280 editor->priv->active->folder_info = folder_info;
282 tree_view = editor->priv->active->tree_view;
283 list_store = editor->priv->active->list_store;
284 tree_store = editor->priv->active->tree_store;
286 gtk_list_store_clear (GTK_LIST_STORE (list_store));
287 gtk_tree_store_clear (GTK_TREE_STORE (tree_store));
289 model = gtk_tree_view_get_model (tree_view);
290 gtk_tree_view_set_model (tree_view, NULL);
291 subscription_editor_populate (editor, folder_info, NULL, &expand_paths);
292 gtk_tree_view_set_model (tree_view, model);
294 g_list_foreach (expand_paths, expand_paths_cb, tree_view);
295 g_list_foreach (expand_paths, (GFunc) gtk_tree_path_free, NULL);
296 g_list_free (expand_paths);
298 path = gtk_tree_path_new_first ();
299 selection = gtk_tree_view_get_selection (tree_view);
300 gtk_tree_selection_select_path (selection, path);
301 gtk_tree_path_free (path);
303 gtk_widget_grab_focus (GTK_WIDGET (tree_view));
305 exit:
306 g_object_unref (editor);
309 static void
310 subscription_editor_subscribe_folder_done (CamelSubscribable *subscribable,
311 GAsyncResult *result,
312 AsyncContext *context)
314 GtkTreeView *tree_view;
315 GtkTreeModel *tree_model;
316 GtkTreeSelection *selection;
317 GtkTreePath *path;
318 GtkTreeIter iter;
319 GdkWindow *window;
320 GError *error = NULL;
321 TreeRowData *tree_row_data;
323 camel_subscribable_subscribe_folder_finish (
324 subscribable, result, &error);
326 /* Just return quietly if we were cancelled. */
327 if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
328 g_error_free (error);
329 goto exit;
332 tree_row_data = g_queue_pop_head (context->tree_rows);
334 /* XXX Do something smarter with errors. */
335 if (error == NULL)
336 tree_row_data->folder_info->flags |= CAMEL_FOLDER_SUBSCRIBED;
337 else {
338 e_notice (
339 GTK_WINDOW (context->editor),
340 GTK_MESSAGE_ERROR, "%s", error->message);
341 g_error_free (error);
342 tree_row_data_free (tree_row_data);
343 goto exit;
346 /* Update the toggle renderer in the selected row. */
347 tree_model = gtk_tree_row_reference_get_model (tree_row_data->reference);
348 path = gtk_tree_row_reference_get_path (tree_row_data->reference);
349 gtk_tree_model_get_iter (tree_model, &iter, path);
350 gtk_tree_model_row_changed (tree_model, path, &iter);
351 gtk_tree_path_free (path);
353 tree_row_data_free (tree_row_data);
355 if (!g_queue_is_empty (context->tree_rows)) {
356 GCancellable *cancellable;
358 /* continue with the next to subscribe */
359 tree_row_data = g_queue_peek_head (context->tree_rows);
360 g_return_if_fail (tree_row_data != NULL);
362 cancellable = context->editor->priv->active->cancellable;
364 camel_subscribable_subscribe_folder (
365 subscribable, tree_row_data->folder_info->full_name,
366 G_PRIORITY_DEFAULT, cancellable, (GAsyncReadyCallback)
367 subscription_editor_subscribe_folder_done, context);
368 return;
371 exit:
372 gtk_widget_set_sensitive (context->editor->priv->notebook, TRUE);
373 gtk_widget_set_sensitive (context->editor->priv->refresh_button, TRUE);
374 gtk_widget_set_sensitive (context->editor->priv->stop_button, FALSE);
376 window = gtk_widget_get_window (GTK_WIDGET (context->editor));
377 gdk_window_set_cursor (window, NULL);
379 /* Update the Subscription/Unsubscription buttons. */
380 tree_view = context->editor->priv->active->tree_view;
381 selection = gtk_tree_view_get_selection (tree_view);
382 g_signal_emit_by_name (selection, "changed");
384 async_context_free (context);
386 gtk_widget_grab_focus (GTK_WIDGET (tree_view));
389 static void
390 subscription_editor_subscribe_many (EMSubscriptionEditor *editor,
391 GQueue *tree_rows)
393 TreeRowData *tree_row_data;
394 GCancellable *cancellable;
395 CamelStore *active_store;
396 AsyncContext *context;
397 GdkCursor *cursor;
398 GdkWindow *window;
400 g_return_if_fail (editor != NULL);
402 if (g_queue_is_empty (tree_rows))
403 return;
405 tree_row_data = g_queue_peek_head (tree_rows);
406 g_return_if_fail (tree_row_data != NULL);
408 /* Cancel any operation on this store still in progress. */
409 gtk_button_clicked (GTK_BUTTON (editor->priv->stop_button));
411 /* Start a new 'subscription' operation. */
412 editor->priv->active->cancellable = g_cancellable_new ();
414 gtk_widget_set_sensitive (editor->priv->notebook, FALSE);
415 gtk_widget_set_sensitive (editor->priv->subscribe_button, FALSE);
416 gtk_widget_set_sensitive (editor->priv->subscribe_arrow, FALSE);
417 gtk_widget_set_sensitive (editor->priv->unsubscribe_button, FALSE);
418 gtk_widget_set_sensitive (editor->priv->unsubscribe_arrow, FALSE);
419 gtk_widget_set_sensitive (editor->priv->refresh_button, FALSE);
420 gtk_widget_set_sensitive (editor->priv->stop_button, TRUE);
422 cursor = gdk_cursor_new (GDK_WATCH);
423 window = gtk_widget_get_window (GTK_WIDGET (editor));
424 gdk_window_set_cursor (window, cursor);
425 g_object_unref (cursor);
427 context = async_context_new (editor, tree_rows);
429 active_store = editor->priv->active->store;
430 cancellable = editor->priv->active->cancellable;
432 camel_subscribable_subscribe_folder (
433 CAMEL_SUBSCRIBABLE (active_store),
434 tree_row_data->folder_info->full_name,
435 G_PRIORITY_DEFAULT, cancellable, (GAsyncReadyCallback)
436 subscription_editor_subscribe_folder_done, context);
439 static void
440 subscription_editor_unsubscribe_folder_done (CamelSubscribable *subscribable,
441 GAsyncResult *result,
442 AsyncContext *context)
444 GtkTreeView *tree_view;
445 GtkTreeModel *tree_model;
446 GtkTreeSelection *selection;
447 GtkTreePath *path;
448 GtkTreeIter iter;
449 GdkWindow *window;
450 GError *error = NULL;
451 TreeRowData *tree_row_data;
453 camel_subscribable_unsubscribe_folder_finish (
454 subscribable, result, &error);
456 /* Just return quietly if we were cancelled. */
457 if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
458 g_error_free (error);
459 goto exit;
462 tree_row_data = g_queue_pop_head (context->tree_rows);
464 /* XXX Do something smarter with errors. */
465 if (error == NULL)
466 tree_row_data->folder_info->flags &= ~CAMEL_FOLDER_SUBSCRIBED;
467 else {
468 e_notice (
469 GTK_WINDOW (context->editor),
470 GTK_MESSAGE_ERROR, "%s", error->message);
471 g_error_free (error);
472 tree_row_data_free (tree_row_data);
473 goto exit;
476 /* Update the toggle renderer in the selected row. */
477 tree_model = gtk_tree_row_reference_get_model (tree_row_data->reference);
478 path = gtk_tree_row_reference_get_path (tree_row_data->reference);
479 gtk_tree_model_get_iter (tree_model, &iter, path);
480 gtk_tree_model_row_changed (tree_model, path, &iter);
481 gtk_tree_path_free (path);
483 tree_row_data_free (tree_row_data);
485 if (!g_queue_is_empty (context->tree_rows)) {
486 GCancellable *cancellable;
488 /* continue with the next to unsubscribe */
489 tree_row_data = g_queue_peek_head (context->tree_rows);
490 g_return_if_fail (tree_row_data != NULL);
492 cancellable = context->editor->priv->active->cancellable;
494 camel_subscribable_unsubscribe_folder (
495 subscribable, tree_row_data->folder_info->full_name,
496 G_PRIORITY_DEFAULT, cancellable, (GAsyncReadyCallback)
497 subscription_editor_unsubscribe_folder_done, context);
498 return;
501 exit:
502 gtk_widget_set_sensitive (context->editor->priv->notebook, TRUE);
503 gtk_widget_set_sensitive (context->editor->priv->refresh_button, TRUE);
504 gtk_widget_set_sensitive (context->editor->priv->stop_button, FALSE);
506 window = gtk_widget_get_window (GTK_WIDGET (context->editor));
507 gdk_window_set_cursor (window, NULL);
509 /* Update the Subscription/Unsubscription buttons. */
510 tree_view = context->editor->priv->active->tree_view;
511 selection = gtk_tree_view_get_selection (tree_view);
512 g_signal_emit_by_name (selection, "changed");
514 async_context_free (context);
516 gtk_widget_grab_focus (GTK_WIDGET (tree_view));
519 static void
520 subscription_editor_unsubscribe_many (EMSubscriptionEditor *editor,
521 GQueue *tree_rows)
523 TreeRowData *tree_row_data;
524 AsyncContext *context;
525 CamelStore *active_store;
526 GdkCursor *cursor;
527 GdkWindow *window;
529 g_return_if_fail (editor != NULL);
531 if (g_queue_is_empty (tree_rows))
532 return;
534 tree_row_data = g_queue_peek_head (tree_rows);
535 g_return_if_fail (tree_row_data != NULL);
537 /* Cancel any operation on this store still in progress. */
538 gtk_button_clicked (GTK_BUTTON (editor->priv->stop_button));
540 /* Start a new 'subscription' operation. */
541 editor->priv->active->cancellable = g_cancellable_new ();
543 gtk_widget_set_sensitive (editor->priv->notebook, FALSE);
544 gtk_widget_set_sensitive (editor->priv->subscribe_button, FALSE);
545 gtk_widget_set_sensitive (editor->priv->subscribe_arrow, FALSE);
546 gtk_widget_set_sensitive (editor->priv->unsubscribe_button, FALSE);
547 gtk_widget_set_sensitive (editor->priv->unsubscribe_arrow, FALSE);
548 gtk_widget_set_sensitive (editor->priv->refresh_button, FALSE);
549 gtk_widget_set_sensitive (editor->priv->stop_button, TRUE);
551 cursor = gdk_cursor_new (GDK_WATCH);
552 window = gtk_widget_get_window (GTK_WIDGET (editor));
553 gdk_window_set_cursor (window, cursor);
554 g_object_unref (cursor);
556 context = async_context_new (editor, tree_rows);
558 active_store = editor->priv->active->store;
560 camel_subscribable_unsubscribe_folder (
561 CAMEL_SUBSCRIBABLE (active_store),
562 tree_row_data->folder_info->full_name, G_PRIORITY_DEFAULT,
563 editor->priv->active->cancellable, (GAsyncReadyCallback)
564 subscription_editor_unsubscribe_folder_done, context);
567 static GtkWidget *
568 subscription_editor_create_menu_item (const gchar *label,
569 gboolean sensitive,
570 GCallback activate_cb,
571 EMSubscriptionEditor *editor)
573 GtkWidget *item;
575 item = gtk_menu_item_new_with_mnemonic (label);
576 gtk_widget_set_sensitive (item, sensitive);
578 gtk_widget_show (item);
580 g_signal_connect_swapped (
581 item, "activate", activate_cb, editor);
583 return item;
586 static TreeRowData *
587 subscription_editor_tree_row_data_from_iter (GtkTreeView *tree_view,
588 GtkTreeModel *model,
589 GtkTreeIter *iter,
590 gboolean *is_expanded)
592 TreeRowData *tree_row_data;
593 CamelFolderInfo *folder_info = NULL;
594 GtkTreeRowReference *reference;
595 GtkTreePath *path;
597 gtk_tree_model_get (
598 model, iter, COL_FOLDER_INFO, &folder_info, -1);
600 if (!FOLDER_CAN_SELECT (folder_info))
601 return NULL;
603 path = gtk_tree_model_get_path (model, iter);
604 reference = gtk_tree_row_reference_new (model, path);
605 if (is_expanded)
606 *is_expanded = gtk_tree_view_row_expanded (tree_view, path);
607 gtk_tree_path_free (path);
609 tree_row_data = g_slice_new0 (TreeRowData);
610 tree_row_data->folder_info = folder_info;
611 tree_row_data->reference = reference;
613 return tree_row_data;
616 typedef enum {
617 PICK_ALL,
618 PICK_SUBSCRIBED,
619 PICK_UNSUBSCRIBED
620 } EPickMode;
622 static gboolean
623 can_pick_folder_info (CamelFolderInfo *fi,
624 EPickMode mode)
626 if (!FOLDER_CAN_SELECT (fi))
627 return FALSE;
629 if (mode == PICK_ALL)
630 return TRUE;
632 return (FOLDER_SUBSCRIBED (fi) ? 1 : 0) == (mode == PICK_SUBSCRIBED ? 1 : 0);
635 struct PickAllData {
636 GtkTreeView *tree_view;
637 EPickMode mode;
638 GHashTable *skip_folder_infos;
639 GQueue *out_tree_rows;
642 static gboolean
643 pick_all_cb (GtkTreeModel *model,
644 GtkTreePath *path,
645 GtkTreeIter *iter,
646 gpointer user_data)
648 struct PickAllData *data = user_data;
649 TreeRowData *tree_row_data;
651 tree_row_data = subscription_editor_tree_row_data_from_iter (
652 data->tree_view, model, iter, NULL);
653 if (tree_row_data == NULL)
654 return FALSE;
656 if (can_pick_folder_info (tree_row_data->folder_info, data->mode) &&
657 (data->skip_folder_infos == NULL ||
658 !g_hash_table_contains (
659 data->skip_folder_infos,
660 tree_row_data->folder_info))) {
661 g_queue_push_tail (data->out_tree_rows, tree_row_data);
662 } else
663 tree_row_data_free (tree_row_data);
665 return FALSE;
668 /* skip_folder_infos contains CamelFolderInfo-s to skip;
669 * these should come from the tree view; can be NULL
670 * to include everything.
672 static void
673 subscription_editor_pick_all (EMSubscriptionEditor *editor,
674 EPickMode mode,
675 GHashTable *skip_folder_infos,
676 GQueue *out_tree_rows)
678 GtkTreeView *tree_view;
679 GtkTreeModel *tree_model;
680 struct PickAllData data;
682 tree_view = editor->priv->active->tree_view;
683 tree_model = gtk_tree_view_get_model (tree_view);
685 data.tree_view = tree_view;
686 data.mode = mode;
687 data.skip_folder_infos = skip_folder_infos;
688 data.out_tree_rows = out_tree_rows;
690 gtk_tree_model_foreach (tree_model, pick_all_cb, &data);
693 static void
694 subscription_editor_pick_shown (EMSubscriptionEditor *editor,
695 EPickMode mode,
696 GQueue *out_tree_rows)
698 GtkTreeView *tree_view;
699 GtkTreeModel *tree_model;
700 GtkTreeIter iter, iter2;
701 gboolean found = TRUE;
703 tree_view = editor->priv->active->tree_view;
704 tree_model = gtk_tree_view_get_model (tree_view);
706 if (!gtk_tree_model_get_iter_first (tree_model, &iter))
707 return;
709 while (found) {
710 TreeRowData *tree_row_data;
711 gboolean is_expanded = FALSE;
713 found = FALSE;
714 tree_row_data = subscription_editor_tree_row_data_from_iter (
715 tree_view, tree_model, &iter, &is_expanded);
717 if (tree_row_data != NULL) {
718 if (can_pick_folder_info (tree_row_data->folder_info, mode))
719 g_queue_push_tail (out_tree_rows, tree_row_data);
720 else
721 tree_row_data_free (tree_row_data);
724 if (is_expanded && gtk_tree_model_iter_children (
725 tree_model, &iter2, &iter)) {
726 iter = iter2;
727 found = TRUE;
728 } else {
729 iter2 = iter;
730 if (gtk_tree_model_iter_next (tree_model, &iter2)) {
731 iter = iter2;
732 found = TRUE;
733 } else {
734 while (found = gtk_tree_model_iter_parent (
735 tree_model, &iter2, &iter), found) {
736 iter = iter2;
737 if (gtk_tree_model_iter_next (
738 tree_model, &iter2)) {
739 iter = iter2;
740 break;
748 static void
749 subscription_editor_subscribe (EMSubscriptionEditor *editor)
751 GtkTreeSelection *selection;
752 GtkTreeModel *tree_model;
753 GtkTreeView *tree_view;
754 GtkTreeIter iter;
755 gboolean have_selection;
756 GQueue tree_rows = G_QUEUE_INIT;
757 TreeRowData *tree_row_data;
759 tree_view = editor->priv->active->tree_view;
760 selection = gtk_tree_view_get_selection (tree_view);
762 have_selection = gtk_tree_selection_get_selected (
763 selection, &tree_model, &iter);
764 g_return_if_fail (have_selection);
766 tree_row_data = subscription_editor_tree_row_data_from_iter (
767 tree_view, tree_model, &iter, NULL);
769 g_queue_push_tail (&tree_rows, tree_row_data);
770 subscription_editor_subscribe_many (editor, &tree_rows);
771 g_warn_if_fail (g_queue_is_empty (&tree_rows));
774 static void
775 subscription_editor_subscribe_shown (EMSubscriptionEditor *editor)
777 GQueue tree_rows = G_QUEUE_INIT;
779 subscription_editor_pick_shown (
780 editor, PICK_UNSUBSCRIBED, &tree_rows);
781 subscription_editor_subscribe_many (editor, &tree_rows);
784 static void
785 subscription_editor_subscribe_all (EMSubscriptionEditor *editor)
787 GQueue tree_rows = G_QUEUE_INIT;
789 subscription_editor_pick_all (
790 editor, PICK_UNSUBSCRIBED, NULL, &tree_rows);
791 subscription_editor_subscribe_many (editor, &tree_rows);
794 static void
795 subscription_editor_subscribe_popup_cb (EMSubscriptionEditor *editor)
797 GtkWidget *menu;
798 GtkTreeIter iter;
799 gboolean tree_filled;
801 tree_filled = editor->priv->active &&
802 gtk_tree_model_get_iter_first (
803 editor->priv->active->filtered_view
804 ? editor->priv->active->list_store
805 : editor->priv->active->tree_store,
806 &iter);
808 menu = gtk_menu_new ();
810 gtk_menu_shell_append (
811 GTK_MENU_SHELL (menu),
812 subscription_editor_create_menu_item (
813 _("_Subscribe"),
814 gtk_widget_get_sensitive (
815 editor->priv->subscribe_button),
816 G_CALLBACK (subscription_editor_subscribe),
817 editor));
819 gtk_menu_shell_append (
820 GTK_MENU_SHELL (menu),
821 subscription_editor_create_menu_item (
822 _("Su_bscribe To Shown"),
823 tree_filled,
824 G_CALLBACK (subscription_editor_subscribe_shown),
825 editor));
827 gtk_menu_shell_append (
828 GTK_MENU_SHELL (menu),
829 subscription_editor_create_menu_item (
830 _("Subscribe To _All"),
831 tree_filled,
832 G_CALLBACK (subscription_editor_subscribe_all),
833 editor));
835 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (editor), NULL);
836 g_signal_connect (menu, "deactivate", G_CALLBACK (gtk_menu_detach), NULL);
838 g_object_set (menu,
839 "anchor-hints", (GDK_ANCHOR_FLIP_Y |
840 GDK_ANCHOR_SLIDE |
841 GDK_ANCHOR_RESIZE),
842 NULL);
844 gtk_menu_popup_at_widget (GTK_MENU (menu),
845 editor->priv->subscribe_button,
846 GDK_GRAVITY_SOUTH_WEST,
847 GDK_GRAVITY_NORTH_WEST,
848 NULL);
851 static void
852 subscription_editor_unsubscribe_hidden (EMSubscriptionEditor *editor)
854 GQueue tree_rows = G_QUEUE_INIT;
855 GHashTable *skip_shown;
857 subscription_editor_pick_shown (editor, PICK_ALL, &tree_rows);
858 g_return_if_fail (!g_queue_is_empty (&tree_rows));
860 skip_shown = g_hash_table_new (g_direct_hash, g_direct_equal);
862 while (!g_queue_is_empty (&tree_rows)) {
863 TreeRowData *tree_row_data;
865 tree_row_data = g_queue_pop_head (&tree_rows);
867 if (tree_row_data == NULL)
868 continue;
870 g_hash_table_add (skip_shown, tree_row_data->folder_info);
872 tree_row_data_free (tree_row_data);
875 subscription_editor_pick_all (
876 editor, PICK_SUBSCRIBED, skip_shown, &tree_rows);
877 subscription_editor_unsubscribe_many (editor, &tree_rows);
879 g_hash_table_destroy (skip_shown);
882 static void
883 subscription_editor_unsubscribe_all (EMSubscriptionEditor *editor)
885 GQueue tree_rows = G_QUEUE_INIT;
887 subscription_editor_pick_all (
888 editor, PICK_SUBSCRIBED, NULL, &tree_rows);
889 subscription_editor_unsubscribe_many (editor, &tree_rows);
892 static void
893 subscription_editor_unsubscribe (EMSubscriptionEditor *editor)
895 GtkTreeSelection *selection;
896 GtkTreeModel *tree_model;
897 GtkTreeView *tree_view;
898 GtkTreeIter iter;
899 gboolean have_selection;
900 GQueue tree_rows = G_QUEUE_INIT;
901 TreeRowData *tree_row_data;
903 tree_view = editor->priv->active->tree_view;
904 selection = gtk_tree_view_get_selection (tree_view);
906 have_selection = gtk_tree_selection_get_selected (
907 selection, &tree_model, &iter);
908 g_return_if_fail (have_selection);
910 tree_row_data = subscription_editor_tree_row_data_from_iter (
911 tree_view, tree_model, &iter, NULL);
913 g_queue_push_tail (&tree_rows, tree_row_data);
914 subscription_editor_unsubscribe_many (editor, &tree_rows);
917 static void
918 subscription_editor_unsubscribe_popup_cb (EMSubscriptionEditor *editor)
920 GtkWidget *menu;
921 GtkTreeIter iter;
922 gboolean tree_filled;
924 tree_filled = editor->priv->active &&
925 gtk_tree_model_get_iter_first (
926 editor->priv->active->filtered_view
927 ? editor->priv->active->list_store
928 : editor->priv->active->tree_store,
929 &iter);
931 menu = gtk_menu_new ();
933 gtk_menu_shell_append (
934 GTK_MENU_SHELL (menu),
935 subscription_editor_create_menu_item (
936 _("_Unsubscribe"),
937 gtk_widget_get_sensitive (
938 editor->priv->unsubscribe_button),
939 G_CALLBACK (subscription_editor_unsubscribe),
940 editor));
942 gtk_menu_shell_append (
943 GTK_MENU_SHELL (menu),
944 subscription_editor_create_menu_item (
945 _("Unsu_bscribe From Hidden"),
946 tree_filled,
947 G_CALLBACK (subscription_editor_unsubscribe_hidden),
948 editor));
950 gtk_menu_shell_append (
951 GTK_MENU_SHELL (menu),
952 subscription_editor_create_menu_item (
953 _("Unsubscribe From _All"),
954 tree_filled,
955 G_CALLBACK (subscription_editor_unsubscribe_all),
956 editor));
958 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (editor), NULL);
959 g_signal_connect (menu, "deactivate", G_CALLBACK (gtk_menu_detach), NULL);
961 g_object_set (menu,
962 "anchor-hints", (GDK_ANCHOR_FLIP_Y |
963 GDK_ANCHOR_SLIDE |
964 GDK_ANCHOR_RESIZE),
965 NULL);
967 gtk_menu_popup_at_widget (GTK_MENU (menu),
968 editor->priv->unsubscribe_button,
969 GDK_GRAVITY_SOUTH_WEST,
970 GDK_GRAVITY_NORTH_WEST,
971 NULL);
974 static void
975 subscription_editor_collapse_all (EMSubscriptionEditor *editor)
977 gtk_tree_view_collapse_all (editor->priv->active->tree_view);
980 static void
981 subscription_editor_expand_all (EMSubscriptionEditor *editor)
983 gtk_tree_view_expand_all (editor->priv->active->tree_view);
986 static void
987 subscription_editor_refresh (EMSubscriptionEditor *editor)
989 GdkCursor *cursor;
990 GdkWindow *window;
992 /* Cancel any operation on this store still in progress. */
993 gtk_button_clicked (GTK_BUTTON (editor->priv->stop_button));
995 /* Start a new 'refresh' operation. */
996 editor->priv->active->cancellable = g_cancellable_new ();
998 gtk_widget_set_sensitive (editor->priv->notebook, FALSE);
999 gtk_widget_set_sensitive (editor->priv->subscribe_button, FALSE);
1000 gtk_widget_set_sensitive (editor->priv->subscribe_arrow, FALSE);
1001 gtk_widget_set_sensitive (editor->priv->unsubscribe_button, FALSE);
1002 gtk_widget_set_sensitive (editor->priv->unsubscribe_arrow, FALSE);
1003 gtk_widget_set_sensitive (editor->priv->refresh_button, FALSE);
1004 gtk_widget_set_sensitive (editor->priv->stop_button, TRUE);
1006 cursor = gdk_cursor_new (GDK_WATCH);
1007 window = gtk_widget_get_window (GTK_WIDGET (editor));
1008 gdk_window_set_cursor (window, cursor);
1009 g_object_unref (cursor);
1011 camel_store_get_folder_info (
1012 editor->priv->active->store, NULL,
1013 CAMEL_STORE_FOLDER_INFO_RECURSIVE |
1014 CAMEL_STORE_FOLDER_INFO_NO_VIRTUAL |
1015 CAMEL_STORE_FOLDER_INFO_SUBSCRIPTION_LIST,
1016 G_PRIORITY_DEFAULT, editor->priv->active->cancellable,
1017 (GAsyncReadyCallback) subscription_editor_get_folder_info_done,
1018 g_object_ref (editor));
1021 static void
1022 subscription_editor_stop (EMSubscriptionEditor *editor)
1024 GdkWindow *window;
1026 if (editor->priv->active->cancellable != NULL) {
1027 g_cancellable_cancel (editor->priv->active->cancellable);
1028 g_object_unref (editor->priv->active->cancellable);
1029 editor->priv->active->cancellable = NULL;
1032 gtk_widget_set_sensitive (editor->priv->notebook, TRUE);
1033 gtk_widget_set_sensitive (editor->priv->subscribe_button, TRUE);
1034 gtk_widget_set_sensitive (editor->priv->subscribe_arrow, TRUE);
1035 gtk_widget_set_sensitive (editor->priv->unsubscribe_button, TRUE);
1036 gtk_widget_set_sensitive (editor->priv->unsubscribe_arrow, TRUE);
1037 gtk_widget_set_sensitive (editor->priv->refresh_button, TRUE);
1038 gtk_widget_set_sensitive (editor->priv->stop_button, FALSE);
1039 gtk_widget_grab_focus (GTK_WIDGET (editor->priv->active->tree_view));
1041 window = gtk_widget_get_window (GTK_WIDGET (editor));
1042 gdk_window_set_cursor (window, NULL);
1045 static gboolean
1046 subscription_editor_filter_cb (GtkTreeModel *tree_model,
1047 GtkTreeIter *iter,
1048 EMSubscriptionEditor *editor)
1050 CamelFolderInfo *folder_info;
1051 gchar *casefolded;
1052 gboolean match;
1054 /* If there's no search string let everything through. */
1055 if (editor->priv->search_string == NULL)
1056 return TRUE;
1058 gtk_tree_model_get (
1059 tree_model, iter,
1060 COL_CASEFOLDED, &casefolded,
1061 COL_FOLDER_INFO, &folder_info, -1);
1063 match = FOLDER_CAN_SELECT (folder_info) &&
1064 (casefolded != NULL) && (*casefolded != '\0') &&
1065 (strstr (casefolded, editor->priv->search_string) != NULL);
1067 g_free (casefolded);
1069 return match;
1072 static void
1073 subscription_editor_update_view (EMSubscriptionEditor *editor)
1075 GtkEntry *entry;
1076 GtkTreeView *tree_view;
1077 GtkTreeModel *tree_model;
1078 const gchar *text;
1080 entry = GTK_ENTRY (editor->priv->entry);
1081 tree_view = editor->priv->active->tree_view;
1083 editor->priv->timeout_id = 0;
1085 text = gtk_entry_get_text (entry);
1087 if (text != NULL && *text != '\0') {
1088 g_free (editor->priv->search_string);
1089 editor->priv->search_string = g_utf8_casefold (text, -1);
1091 /* Install the list store in the tree view if needed. */
1092 if (!editor->priv->active->filtered_view) {
1093 GtkTreeSelection *selection;
1094 GtkTreePath *path;
1096 tree_model = gtk_tree_model_filter_new (
1097 editor->priv->active->list_store, NULL);
1098 gtk_tree_model_filter_set_visible_func (
1099 GTK_TREE_MODEL_FILTER (tree_model),
1100 (GtkTreeModelFilterVisibleFunc)
1101 subscription_editor_filter_cb, editor,
1102 (GDestroyNotify) NULL);
1103 gtk_tree_view_set_model (tree_view, tree_model);
1104 g_object_unref (tree_model);
1106 path = gtk_tree_path_new_first ();
1107 selection = gtk_tree_view_get_selection (tree_view);
1108 gtk_tree_selection_select_path (selection, path);
1109 gtk_tree_path_free (path);
1111 editor->priv->active->filtered_view = TRUE;
1114 tree_model = gtk_tree_view_get_model (tree_view);
1115 gtk_tree_model_filter_refilter (
1116 GTK_TREE_MODEL_FILTER (tree_model));
1118 gtk_entry_set_icon_sensitive (
1119 entry, GTK_ENTRY_ICON_SECONDARY, TRUE);
1121 gtk_widget_set_sensitive (
1122 editor->priv->collapse_all_button, FALSE);
1123 gtk_widget_set_sensitive (
1124 editor->priv->expand_all_button, FALSE);
1126 } else {
1127 /* Install the tree store in the tree view if needed. */
1128 if (editor->priv->active->filtered_view) {
1129 GtkTreeSelection *selection;
1130 GtkTreePath *path;
1132 tree_model = editor->priv->active->tree_store;
1133 gtk_tree_view_set_model (tree_view, tree_model);
1135 path = gtk_tree_path_new_first ();
1136 selection = gtk_tree_view_get_selection (tree_view);
1137 gtk_tree_selection_select_path (selection, path);
1138 gtk_tree_path_free (path);
1140 editor->priv->active->filtered_view = FALSE;
1143 gtk_entry_set_icon_sensitive (
1144 entry, GTK_ENTRY_ICON_SECONDARY, FALSE);
1146 gtk_widget_set_sensitive (
1147 editor->priv->collapse_all_button, TRUE);
1148 gtk_widget_set_sensitive (
1149 editor->priv->expand_all_button, TRUE);
1153 static gboolean
1154 subscription_editor_timeout_cb (gpointer user_data)
1156 EMSubscriptionEditor *editor;
1158 editor = EM_SUBSCRIPTION_EDITOR (user_data);
1159 subscription_editor_update_view (editor);
1160 editor->priv->timeout_id = 0;
1162 return FALSE;
1165 static void
1166 subscription_editor_combo_box_changed_cb (GtkComboBox *combo_box,
1167 EMSubscriptionEditor *editor)
1169 StoreData *data;
1170 gint index;
1172 index = gtk_combo_box_get_active (combo_box);
1173 g_return_if_fail (index < editor->priv->stores->len);
1175 data = g_ptr_array_index (editor->priv->stores, index);
1176 g_return_if_fail (data != NULL);
1178 editor->priv->active = data;
1180 subscription_editor_stop (editor);
1181 subscription_editor_update_view (editor);
1183 g_object_notify (G_OBJECT (editor), "store");
1185 if (data->needs_refresh) {
1186 subscription_editor_refresh (editor);
1187 data->needs_refresh = FALSE;
1191 static void
1192 subscription_editor_entry_changed_cb (GtkEntry *entry,
1193 EMSubscriptionEditor *editor)
1195 const gchar *text;
1197 if (editor->priv->timeout_id > 0) {
1198 g_source_remove (editor->priv->timeout_id);
1199 editor->priv->timeout_id = 0;
1202 text = gtk_entry_get_text (entry);
1204 if (text != NULL && *text != '\0') {
1205 editor->priv->timeout_id = e_named_timeout_add_seconds (
1206 1, subscription_editor_timeout_cb, editor);
1207 } else {
1208 subscription_editor_update_view (editor);
1212 static void
1213 subscription_editor_icon_release_cb (GtkEntry *entry,
1214 GtkEntryIconPosition icon_pos,
1215 GdkEvent *event,
1216 EMSubscriptionEditor *editor)
1218 if (icon_pos == GTK_ENTRY_ICON_SECONDARY)
1219 gtk_entry_set_text (entry, "");
1222 static void
1223 subscription_editor_renderer_toggled_cb (GtkCellRendererToggle *renderer,
1224 const gchar *path_string,
1225 EMSubscriptionEditor *editor)
1227 GtkTreeSelection *selection;
1228 GtkTreeView *tree_view;
1229 GtkTreePath *path;
1231 tree_view = editor->priv->active->tree_view;
1232 selection = gtk_tree_view_get_selection (tree_view);
1234 path = gtk_tree_path_new_from_string (path_string);
1235 gtk_tree_selection_select_path (selection, path);
1236 gtk_tree_path_free (path);
1238 if (gtk_cell_renderer_toggle_get_active (renderer))
1239 subscription_editor_unsubscribe (editor);
1240 else
1241 subscription_editor_subscribe (editor);
1244 static void
1245 subscription_editor_render_toggle_cb (GtkCellLayout *cell_layout,
1246 GtkCellRenderer *renderer,
1247 GtkTreeModel *tree_model,
1248 GtkTreeIter *iter)
1250 CamelFolderInfo *folder_info;
1252 gtk_tree_model_get (
1253 tree_model, iter, COL_FOLDER_INFO, &folder_info, -1);
1255 g_object_set (
1256 renderer, "active", FOLDER_SUBSCRIBED (folder_info),
1257 "visible", FOLDER_CAN_SELECT (folder_info), NULL);
1260 static void
1261 subscription_editor_selection_changed_cb (GtkTreeSelection *selection,
1262 EMSubscriptionEditor *editor)
1264 GtkTreeModel *tree_model;
1265 GtkTreeIter iter;
1267 if (gtk_tree_selection_get_selected (selection, &tree_model, &iter)) {
1268 CamelFolderInfo *folder_info;
1270 gtk_tree_model_get (
1271 tree_model, &iter,
1272 COL_FOLDER_INFO, &folder_info, -1);
1273 gtk_widget_set_sensitive (
1274 editor->priv->subscribe_button,
1275 FOLDER_CAN_SELECT (folder_info) &&
1276 !FOLDER_SUBSCRIBED (folder_info));
1277 gtk_widget_set_sensitive (
1278 editor->priv->unsubscribe_button,
1279 FOLDER_CAN_SELECT (folder_info) &&
1280 FOLDER_SUBSCRIBED (folder_info));
1281 } else {
1282 gtk_widget_set_sensitive (
1283 editor->priv->subscribe_button, FALSE);
1284 gtk_widget_set_sensitive (
1285 editor->priv->unsubscribe_button, FALSE);
1288 gtk_widget_set_sensitive (editor->priv->subscribe_arrow, TRUE);
1289 gtk_widget_set_sensitive (editor->priv->unsubscribe_arrow, TRUE);
1292 static void
1293 em_subscription_editor_get_unread_total_text_cb (GtkTreeViewColumn *tree_column,
1294 GtkCellRenderer *cell,
1295 GtkTreeModel *tree_model,
1296 GtkTreeIter *iter,
1297 gpointer user_data)
1299 CamelFolderInfo *folder_info = NULL;
1300 GString *text = NULL;
1302 g_return_if_fail (GTK_IS_CELL_RENDERER_TEXT (cell));
1303 g_return_if_fail (GTK_IS_TREE_MODEL (tree_model));
1304 g_return_if_fail (iter != NULL);
1306 gtk_tree_model_get (tree_model, iter, COL_FOLDER_INFO, &folder_info, -1);
1308 if (folder_info && folder_info->total > 0 && folder_info->unread >= 0 && folder_info->unread <= folder_info->total) {
1309 text = g_string_new ("");
1311 if (folder_info->unread > 0)
1312 g_string_append_printf (
1313 text, ngettext ("%d unread, ",
1314 "%d unread, ", folder_info->unread), folder_info->unread);
1316 g_string_append_printf (
1317 text, ngettext ("%d total", "%d total",
1318 folder_info->total), folder_info->total);
1321 g_object_set (G_OBJECT (cell), "text", text ? text->str : NULL, NULL);
1323 if (text)
1324 g_string_free (text, TRUE);
1327 static void
1328 subscription_editor_add_store (EMSubscriptionEditor *editor,
1329 CamelStore *store)
1331 StoreData *data;
1332 CamelService *service;
1333 GtkListStore *list_store;
1334 GtkTreeStore *tree_store;
1335 GtkTreeViewColumn *column;
1336 GtkTreeSelection *selection;
1337 GtkCellRenderer *renderer;
1338 GtkComboBoxText *combo_box;
1339 GtkWidget *container;
1340 GtkWidget *widget;
1341 const gchar *display_name;
1343 service = CAMEL_SERVICE (store);
1344 display_name = camel_service_get_display_name (service);
1346 combo_box = GTK_COMBO_BOX_TEXT (editor->priv->combo_box);
1347 gtk_combo_box_text_append_text (combo_box, display_name);
1349 tree_store = gtk_tree_store_new (
1350 N_COLUMNS,
1351 /* COL_CASEFOLDED */ G_TYPE_STRING,
1352 /* COL_FOLDER_ICON */ G_TYPE_STRING,
1353 /* COL_FOLDER_NAME */ G_TYPE_STRING,
1354 /* COL_FOLDER_INFO */ G_TYPE_POINTER);
1356 list_store = gtk_list_store_new (
1357 N_COLUMNS,
1358 /* COL_CASEFOLDED */ G_TYPE_STRING,
1359 /* COL_FOLDER_ICON */ G_TYPE_STRING,
1360 /* COL_FOLDER_NAME */ G_TYPE_STRING,
1361 /* COL_FOLDER_INFO */ G_TYPE_POINTER);
1363 container = editor->priv->notebook;
1365 widget = gtk_scrolled_window_new (NULL, NULL);
1366 gtk_scrolled_window_set_policy (
1367 GTK_SCROLLED_WINDOW (widget),
1368 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1369 gtk_scrolled_window_set_shadow_type (
1370 GTK_SCROLLED_WINDOW (widget), GTK_SHADOW_IN);
1371 gtk_notebook_append_page (GTK_NOTEBOOK (container), widget, NULL);
1372 gtk_container_child_set (
1373 GTK_CONTAINER (container), widget,
1374 "tab-fill", FALSE, "tab-expand", FALSE, NULL);
1375 gtk_widget_show (widget);
1377 container = widget;
1379 widget = gtk_tree_view_new_with_model (GTK_TREE_MODEL (tree_store));
1380 gtk_tree_view_set_enable_search (GTK_TREE_VIEW (widget), TRUE);
1381 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (widget), FALSE);
1382 gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (widget), TRUE);
1383 gtk_tree_view_set_search_column (
1384 GTK_TREE_VIEW (widget), COL_FOLDER_NAME);
1385 gtk_container_add (GTK_CONTAINER (container), widget);
1386 gtk_widget_show (widget);
1388 column = gtk_tree_view_column_new ();
1389 gtk_tree_view_append_column (GTK_TREE_VIEW (widget), column);
1391 renderer = gtk_cell_renderer_toggle_new ();
1392 g_object_set (renderer, "activatable", TRUE, NULL);
1393 gtk_tree_view_column_pack_start (column, renderer, FALSE);
1395 gtk_cell_layout_set_cell_data_func (
1396 GTK_CELL_LAYOUT (column), renderer,
1397 (GtkCellLayoutDataFunc) subscription_editor_render_toggle_cb,
1398 NULL, (GDestroyNotify) NULL);
1400 g_signal_connect (
1401 renderer, "toggled",
1402 G_CALLBACK (subscription_editor_renderer_toggled_cb), editor);
1404 column = gtk_tree_view_column_new ();
1405 gtk_tree_view_append_column (GTK_TREE_VIEW (widget), column);
1406 gtk_tree_view_set_expander_column (GTK_TREE_VIEW (widget), column);
1408 renderer = gtk_cell_renderer_pixbuf_new ();
1409 gtk_tree_view_column_pack_start (column, renderer, FALSE);
1410 gtk_tree_view_column_add_attribute (
1411 column, renderer, "icon-name", COL_FOLDER_ICON);
1413 renderer = gtk_cell_renderer_text_new ();
1414 gtk_tree_view_column_pack_start (column, renderer, TRUE);
1415 gtk_tree_view_column_add_attribute (
1416 column, renderer, "text", COL_FOLDER_NAME);
1418 column = gtk_tree_view_column_new ();
1419 gtk_tree_view_append_column (GTK_TREE_VIEW (widget), column);
1421 renderer = gtk_cell_renderer_text_new ();
1422 gtk_tree_view_column_pack_start (column, renderer, FALSE);
1423 gtk_tree_view_column_set_cell_data_func (column, renderer,
1424 em_subscription_editor_get_unread_total_text_cb, NULL, NULL);
1426 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
1428 g_signal_connect (
1429 selection, "changed",
1430 G_CALLBACK (subscription_editor_selection_changed_cb), editor);
1432 data = g_slice_new0 (StoreData);
1433 data->store = g_object_ref (store);
1434 data->tree_view = g_object_ref (widget);
1435 data->list_store = GTK_TREE_MODEL (list_store);
1436 data->tree_store = GTK_TREE_MODEL (tree_store);
1437 data->needs_refresh = TRUE;
1439 g_ptr_array_add (editor->priv->stores, data);
1442 static void
1443 emse_notebook_sensitive_changed_cb (GtkWidget *notebook,
1444 GParamSpec *param,
1445 GtkDialog *editor)
1447 gtk_dialog_set_response_sensitive (
1448 editor, GTK_RESPONSE_CLOSE,
1449 gtk_widget_get_sensitive (notebook));
1452 static gboolean
1453 subscription_editor_delete_event_cb (EMSubscriptionEditor *editor,
1454 GdkEvent *event,
1455 gpointer user_data)
1457 /* stop processing if the button is insensitive */
1458 return !gtk_widget_get_sensitive (editor->priv->notebook);
1461 static void
1462 subscription_editor_response_cb (EMSubscriptionEditor *editor,
1463 gint response_id,
1464 gpointer user_data)
1466 if (!gtk_widget_get_sensitive (editor->priv->notebook))
1467 g_signal_stop_emission_by_name (editor, "response");
1470 static void
1471 subscription_editor_set_store (EMSubscriptionEditor *editor,
1472 CamelStore *store)
1474 g_return_if_fail (editor->priv->initial_store == NULL);
1476 if (CAMEL_IS_SUBSCRIBABLE (store))
1477 editor->priv->initial_store = g_object_ref (store);
1480 static void
1481 subscription_editor_set_session (EMSubscriptionEditor *editor,
1482 EMailSession *session)
1484 g_return_if_fail (E_IS_MAIL_SESSION (session));
1485 g_return_if_fail (editor->priv->session == NULL);
1487 editor->priv->session = g_object_ref (session);
1490 static void
1491 subscription_editor_set_property (GObject *object,
1492 guint property_id,
1493 const GValue *value,
1494 GParamSpec *pspec)
1496 switch (property_id) {
1497 case PROP_SESSION:
1498 subscription_editor_set_session (
1499 EM_SUBSCRIPTION_EDITOR (object),
1500 g_value_get_object (value));
1501 return;
1503 case PROP_STORE:
1504 subscription_editor_set_store (
1505 EM_SUBSCRIPTION_EDITOR (object),
1506 g_value_get_object (value));
1507 return;
1510 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1513 static void
1514 subscription_editor_get_property (GObject *object,
1515 guint property_id,
1516 GValue *value,
1517 GParamSpec *pspec)
1519 switch (property_id) {
1520 case PROP_SESSION:
1521 g_value_set_object (
1522 value,
1523 em_subscription_editor_get_session (
1524 EM_SUBSCRIPTION_EDITOR (object)));
1525 return;
1527 case PROP_STORE:
1528 g_value_set_object (
1529 value,
1530 em_subscription_editor_get_store (
1531 EM_SUBSCRIPTION_EDITOR (object)));
1532 return;
1535 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1538 static void
1539 subscription_editor_dispose (GObject *object)
1541 EMSubscriptionEditorPrivate *priv;
1543 priv = EM_SUBSCRIPTION_EDITOR_GET_PRIVATE (object);
1545 if (priv->session != NULL) {
1546 g_object_unref (priv->session);
1547 priv->session = NULL;
1550 if (priv->initial_store != NULL) {
1551 g_object_unref (priv->initial_store);
1552 priv->initial_store = NULL;
1555 if (priv->timeout_id > 0) {
1556 g_source_remove (priv->timeout_id);
1557 priv->timeout_id = 0;
1560 g_ptr_array_set_size (priv->stores, 0);
1562 /* Chain up to parent's dispose() method. */
1563 G_OBJECT_CLASS (em_subscription_editor_parent_class)->dispose (object);
1566 static void
1567 subscription_editor_finalize (GObject *object)
1569 EMSubscriptionEditorPrivate *priv;
1571 priv = EM_SUBSCRIPTION_EDITOR_GET_PRIVATE (object);
1573 g_ptr_array_free (priv->stores, TRUE);
1575 g_free (priv->search_string);
1577 /* Chain up to parent's finalize() method. */
1578 G_OBJECT_CLASS (em_subscription_editor_parent_class)->finalize (object);
1581 static void
1582 subscription_editor_constructed (GObject *object)
1584 EMSubscriptionEditor *editor;
1586 editor = EM_SUBSCRIPTION_EDITOR (object);
1588 /* Pick an initial store based on the default mail account, if
1589 * one wasn't already given in em_subscription_editor_new(). */
1590 if (editor->priv->initial_store == NULL) {
1591 ESource *source;
1592 ESourceRegistry *registry;
1593 CamelService *service;
1594 EMailSession *session;
1596 session = em_subscription_editor_get_session (editor);
1597 registry = e_mail_session_get_registry (session);
1599 source = e_source_registry_ref_default_mail_account (registry);
1601 service = camel_session_ref_service (
1602 CAMEL_SESSION (session),
1603 e_source_get_uid (source));
1605 if (CAMEL_IS_SUBSCRIBABLE (service))
1606 editor->priv->initial_store = g_object_ref (service);
1608 if (service != NULL)
1609 g_object_unref (service);
1611 g_object_unref (source);
1614 /* Chain up to parent's constructed() method. */
1615 G_OBJECT_CLASS (em_subscription_editor_parent_class)->constructed (object);
1617 g_signal_connect (
1618 editor, "delete-event",
1619 G_CALLBACK (subscription_editor_delete_event_cb), NULL);
1620 g_signal_connect (
1621 editor, "response",
1622 G_CALLBACK (subscription_editor_response_cb), NULL);
1625 static void
1626 subscription_editor_realize (GtkWidget *widget)
1628 EMSubscriptionEditor *editor;
1629 EMFolderTreeModel *model;
1630 GtkComboBox *combo_box;
1631 GList *list, *link;
1632 gint initial_index = 0;
1634 editor = EM_SUBSCRIPTION_EDITOR (widget);
1636 /* Chain up to parent's realize() method. */
1637 GTK_WIDGET_CLASS (em_subscription_editor_parent_class)->realize (widget);
1639 /* Find stores to display, and watch for the initial store. */
1641 model = em_folder_tree_model_get_default ();
1642 list = em_folder_tree_model_list_stores (model);
1644 for (link = list; link != NULL; link = g_list_next (link)) {
1645 CamelStore *store = CAMEL_STORE (link->data);
1647 if (!CAMEL_IS_SUBSCRIBABLE (store))
1648 continue;
1650 if (store == editor->priv->initial_store)
1651 initial_index = editor->priv->stores->len;
1653 subscription_editor_add_store (editor, store);
1656 g_list_free (list);
1658 /* The subscription editor should only be accessible if
1659 * at least one enabled store supports subscriptions. */
1660 g_return_if_fail (editor->priv->stores->len > 0);
1662 combo_box = GTK_COMBO_BOX (editor->priv->combo_box);
1663 gtk_combo_box_set_active (combo_box, initial_index);
1665 g_signal_connect (
1666 combo_box, "changed",
1667 G_CALLBACK (subscription_editor_combo_box_changed_cb), editor);
1669 subscription_editor_combo_box_changed_cb (combo_box, editor);
1672 static void
1673 em_subscription_editor_class_init (EMSubscriptionEditorClass *class)
1675 GObjectClass *object_class;
1676 GtkWidgetClass *widget_class;
1678 g_type_class_add_private (class, sizeof (EMSubscriptionEditorPrivate));
1680 object_class = G_OBJECT_CLASS (class);
1681 object_class->set_property = subscription_editor_set_property;
1682 object_class->get_property = subscription_editor_get_property;
1683 object_class->dispose = subscription_editor_dispose;
1684 object_class->finalize = subscription_editor_finalize;
1685 object_class->constructed = subscription_editor_constructed;
1687 widget_class = GTK_WIDGET_CLASS (class);
1688 widget_class->realize = subscription_editor_realize;
1690 g_object_class_install_property (
1691 object_class,
1692 PROP_SESSION,
1693 g_param_spec_object (
1694 "session",
1695 NULL,
1696 NULL,
1697 E_TYPE_MAIL_SESSION,
1698 G_PARAM_READWRITE |
1699 G_PARAM_CONSTRUCT_ONLY |
1700 G_PARAM_STATIC_STRINGS));
1702 g_object_class_install_property (
1703 object_class,
1704 PROP_STORE,
1705 g_param_spec_object (
1706 "store",
1707 NULL,
1708 NULL,
1709 CAMEL_TYPE_STORE,
1710 G_PARAM_READWRITE |
1711 G_PARAM_CONSTRUCT_ONLY |
1712 G_PARAM_STATIC_STRINGS));
1715 static void
1716 em_subscription_editor_init (EMSubscriptionEditor *editor)
1718 GtkWidget *container;
1719 GtkWidget *widget;
1720 GtkWidget *box;
1721 const gchar *tooltip;
1723 editor->priv = EM_SUBSCRIPTION_EDITOR_GET_PRIVATE (editor);
1725 editor->priv->stores = g_ptr_array_new_with_free_func (
1726 (GDestroyNotify) store_data_free);
1728 gtk_container_set_border_width (GTK_CONTAINER (editor), 5);
1729 gtk_window_set_title (GTK_WINDOW (editor), _("Folder Subscriptions"));
1730 gtk_window_set_default_size (GTK_WINDOW (editor), 600, 400);
1732 e_restore_window (
1733 GTK_WINDOW (editor),
1734 "/org/gnome/evolution/mail/subscription-window/",
1735 E_RESTORE_WINDOW_SIZE);
1737 gtk_dialog_add_button (
1738 GTK_DIALOG (editor),
1739 _("_Close"), GTK_RESPONSE_CLOSE);
1741 container = gtk_dialog_get_content_area (GTK_DIALOG (editor));
1743 widget = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
1744 gtk_container_set_border_width (GTK_CONTAINER (widget), 5);
1745 gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0);
1746 gtk_widget_show (widget);
1748 container = box = widget;
1750 widget = gtk_grid_new ();
1751 gtk_grid_set_row_spacing (GTK_GRID (widget), 6);
1752 gtk_grid_set_column_spacing (GTK_GRID (widget), 6);
1753 gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
1754 gtk_widget_show (widget);
1756 container = widget;
1758 widget = gtk_combo_box_text_new ();
1759 gtk_widget_set_hexpand (widget, TRUE);
1760 gtk_grid_attach (GTK_GRID (container), widget, 1, 0, 1, 1);
1761 editor->priv->combo_box = widget;
1762 gtk_widget_show (widget);
1764 widget = gtk_label_new_with_mnemonic (_("_Account:"));
1765 gtk_label_set_mnemonic_widget (
1766 GTK_LABEL (widget), editor->priv->combo_box);
1767 gtk_misc_set_alignment (GTK_MISC (widget), 1.0, 0.5);
1768 gtk_grid_attach (GTK_GRID (container), widget, 0, 0, 1, 1);
1769 gtk_widget_show (widget);
1771 widget = gtk_entry_new ();
1772 gtk_entry_set_icon_from_icon_name (
1773 GTK_ENTRY (widget),
1774 GTK_ENTRY_ICON_SECONDARY, "edit-clear");
1775 gtk_entry_set_icon_tooltip_text (
1776 GTK_ENTRY (widget),
1777 GTK_ENTRY_ICON_SECONDARY, _("Clear Search"));
1778 gtk_entry_set_icon_sensitive (
1779 GTK_ENTRY (widget),
1780 GTK_ENTRY_ICON_SECONDARY, FALSE);
1781 gtk_widget_set_hexpand (widget, TRUE);
1782 gtk_grid_attach (GTK_GRID (container), widget, 1, 1, 1, 1);
1783 editor->priv->entry = widget;
1784 gtk_widget_show (widget);
1786 g_signal_connect (
1787 widget, "changed",
1788 G_CALLBACK (subscription_editor_entry_changed_cb), editor);
1790 g_signal_connect (
1791 widget, "icon-release",
1792 G_CALLBACK (subscription_editor_icon_release_cb), editor);
1794 widget = gtk_label_new_with_mnemonic (_("Sho_w items that contain:"));
1795 gtk_label_set_mnemonic_widget (
1796 GTK_LABEL (widget), editor->priv->entry);
1797 gtk_misc_set_alignment (GTK_MISC (widget), 1.0, 0.5);
1798 gtk_grid_attach (GTK_GRID (container), widget, 0, 1, 1, 1);
1799 gtk_widget_show (widget);
1801 container = box;
1803 widget = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
1804 gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0);
1805 gtk_widget_show (widget);
1807 container = widget;
1809 widget = gtk_notebook_new ();
1810 gtk_notebook_set_show_tabs (GTK_NOTEBOOK (widget), FALSE);
1811 gtk_notebook_set_show_border (GTK_NOTEBOOK (widget), FALSE);
1812 gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0);
1813 editor->priv->notebook = widget;
1814 gtk_widget_show (widget);
1816 e_binding_bind_property (
1817 editor->priv->combo_box, "active",
1818 editor->priv->notebook, "page",
1819 G_BINDING_BIDIRECTIONAL |
1820 G_BINDING_SYNC_CREATE);
1822 e_signal_connect_notify (
1823 widget, "notify::sensitive",
1824 G_CALLBACK (emse_notebook_sensitive_changed_cb), editor);
1826 widget = gtk_button_box_new (GTK_ORIENTATION_VERTICAL);
1827 gtk_box_set_spacing (GTK_BOX (widget), 6);
1828 gtk_button_box_set_layout (
1829 GTK_BUTTON_BOX (widget), GTK_BUTTONBOX_START);
1830 gtk_box_pack_start (GTK_BOX (container), widget, FALSE, TRUE, 0);
1831 gtk_widget_show (widget);
1833 container = box = widget;
1835 widget = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2);
1836 gtk_box_pack_start (GTK_BOX (container), widget, FALSE, TRUE, 0);
1837 gtk_widget_show (widget);
1839 container = widget;
1841 tooltip = _("Subscribe to the selected folder");
1842 widget = gtk_button_new_with_mnemonic (_("Su_bscribe"));
1843 gtk_widget_set_sensitive (widget, FALSE);
1844 gtk_widget_set_tooltip_text (widget, tooltip);
1845 editor->priv->subscribe_button = widget;
1846 gtk_widget_show (widget);
1848 g_signal_connect_swapped (
1849 widget, "clicked",
1850 G_CALLBACK (subscription_editor_subscribe), editor);
1852 widget = gtk_button_new ();
1853 gtk_button_set_image (
1854 GTK_BUTTON (widget),
1855 gtk_arrow_new (GTK_ARROW_DOWN, GTK_SHADOW_NONE));
1856 editor->priv->subscribe_arrow = widget;
1857 gtk_widget_show (widget);
1859 g_signal_connect_swapped (
1860 widget, "clicked",
1861 G_CALLBACK (subscription_editor_subscribe_popup_cb), editor);
1863 if (gtk_widget_get_direction (container) == GTK_TEXT_DIR_LTR) {
1864 gtk_box_pack_start (
1865 GTK_BOX (container),
1866 editor->priv->subscribe_button, TRUE, TRUE, 0);
1867 gtk_box_pack_start (
1868 GTK_BOX (container),
1869 editor->priv->subscribe_arrow, FALSE, FALSE, 0);
1870 } else {
1871 gtk_box_pack_start (
1872 GTK_BOX (container),
1873 editor->priv->subscribe_arrow, FALSE, FALSE, 0);
1874 gtk_box_pack_start (
1875 GTK_BOX (container),
1876 editor->priv->subscribe_button, TRUE, TRUE, 0);
1879 container = box;
1881 widget = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2);
1882 gtk_box_pack_start (GTK_BOX (container), widget, FALSE, TRUE, 0);
1883 gtk_widget_show (widget);
1885 container = widget;
1887 tooltip = _("Unsubscribe from the selected folder");
1888 widget = gtk_button_new_with_mnemonic (_("_Unsubscribe"));
1889 gtk_widget_set_sensitive (widget, FALSE);
1890 gtk_widget_set_tooltip_text (widget, tooltip);
1891 editor->priv->unsubscribe_button = widget;
1892 gtk_widget_show (widget);
1894 g_signal_connect_swapped (
1895 widget, "clicked",
1896 G_CALLBACK (subscription_editor_unsubscribe), editor);
1898 widget = gtk_button_new ();
1899 gtk_button_set_image (
1900 GTK_BUTTON (widget),
1901 gtk_arrow_new (GTK_ARROW_DOWN, GTK_SHADOW_NONE));
1902 editor->priv->unsubscribe_arrow = widget;
1903 gtk_widget_show (widget);
1905 g_signal_connect_swapped (
1906 widget, "clicked",
1907 G_CALLBACK (subscription_editor_unsubscribe_popup_cb), editor);
1909 if (gtk_widget_get_direction (container) == GTK_TEXT_DIR_LTR) {
1910 gtk_box_pack_start (
1911 GTK_BOX (container),
1912 editor->priv->unsubscribe_button, TRUE, TRUE, 0);
1913 gtk_box_pack_start (
1914 GTK_BOX (container),
1915 editor->priv->unsubscribe_arrow, FALSE, FALSE, 0);
1916 } else {
1917 gtk_box_pack_start (
1918 GTK_BOX (container),
1919 editor->priv->unsubscribe_arrow, FALSE, FALSE, 0);
1920 gtk_box_pack_start (
1921 GTK_BOX (container),
1922 editor->priv->unsubscribe_button, TRUE, TRUE, 0);
1925 container = box;
1927 tooltip = _("Collapse all folders");
1928 widget = gtk_button_new_with_mnemonic (_("C_ollapse All"));
1929 gtk_widget_set_tooltip_text (widget, tooltip);
1930 gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
1931 editor->priv->collapse_all_button = widget;
1932 gtk_widget_show (widget);
1934 g_signal_connect_swapped (
1935 widget, "clicked",
1936 G_CALLBACK (subscription_editor_collapse_all), editor);
1938 tooltip = _("Expand all folders");
1939 widget = gtk_button_new_with_mnemonic (_("E_xpand All"));
1940 gtk_widget_set_tooltip_text (widget, tooltip);
1941 gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
1942 editor->priv->expand_all_button = widget;
1943 gtk_widget_show (widget);
1945 g_signal_connect_swapped (
1946 widget, "clicked",
1947 G_CALLBACK (subscription_editor_expand_all), editor);
1949 tooltip = _("Refresh the folder list");
1950 widget = e_dialog_button_new_with_icon ("view-refresh", _("_Refresh"));
1951 gtk_widget_set_tooltip_text (widget, tooltip);
1952 gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
1953 gtk_widget_set_sensitive (widget, FALSE);
1954 editor->priv->refresh_button = widget;
1955 gtk_widget_show (widget);
1957 g_signal_connect_swapped (
1958 widget, "clicked",
1959 G_CALLBACK (subscription_editor_refresh), editor);
1961 tooltip = _("Stop the current operation");
1962 widget = e_dialog_button_new_with_icon ("process-stop", _("_Stop"));
1963 gtk_widget_set_tooltip_text (widget, tooltip);
1964 gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
1965 gtk_widget_set_sensitive (widget, FALSE);
1966 editor->priv->stop_button = widget;
1967 gtk_widget_show (widget);
1969 g_signal_connect_swapped (
1970 widget, "clicked",
1971 G_CALLBACK (subscription_editor_stop), editor);
1974 GtkWidget *
1975 em_subscription_editor_new (GtkWindow *parent,
1976 EMailSession *session,
1977 CamelStore *initial_store)
1979 g_return_val_if_fail (GTK_IS_WINDOW (parent), NULL);
1980 g_return_val_if_fail (E_IS_MAIL_SESSION (session), NULL);
1982 return g_object_new (
1983 EM_TYPE_SUBSCRIPTION_EDITOR,
1984 "session", session,
1985 "store", initial_store,
1986 "transient-for", parent,
1987 NULL);
1990 EMailSession *
1991 em_subscription_editor_get_session (EMSubscriptionEditor *editor)
1993 g_return_val_if_fail (EM_IS_SUBSCRIPTION_EDITOR (editor), NULL);
1995 return editor->priv->session;
1998 CamelStore *
1999 em_subscription_editor_get_store (EMSubscriptionEditor *editor)
2001 g_return_val_if_fail (EM_IS_SUBSCRIPTION_EDITOR (editor), NULL);
2003 if (editor->priv->active == NULL)
2004 return NULL;
2006 return editor->priv->active->store;