No issues found
1 /*
2 *
3 * This program is free software; you can redistribute it and/or
4 * modify it under the terms of the GNU Lesser General Public
5 * License as published by the Free Software Foundation; either
6 * version 2 of the License, or (at your option) version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public
14 * License along with the program; if not, see <http://www.gnu.org/licenses/>
15 *
16 *
17 * Authors:
18 * Chenthill Palanisamy <pchenthill@novell.com>
19 *
20 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
21 *
22 */
23
24 #ifdef HAVE_CONFIG_H
25 #include <config.h>
26 #endif
27
28 #include <string.h>
29 #include <gtk/gtk.h>
30 #include <glib/gi18n.h>
31 #include <e-util/e-plugin-ui.h>
32
33 #include <libemail-engine/e-mail-folder-utils.h>
34
35 #include <mail/em-folder-tree.h>
36 #include <mail/em-utils.h>
37
38 #include <shell/e-shell-sidebar.h>
39 #include <shell/e-shell-view.h>
40 #include <shell/e-shell-window.h>
41
42 #define PRIMARY_TEXT \
43 N_("Also mark messages in subfolders?")
44 #define SECONDARY_TEXT \
45 N_("Do you want to mark messages as read in the current folder " \
46 "only, or in the current folder as well as all subfolders?")
47
48 typedef struct _AsyncContext AsyncContext;
49
50 struct _AsyncContext {
51 EActivity *activity;
52 GQueue folder_names;
53 };
54
55 enum {
56 MARK_ALL_READ_CANCEL,
57 MARK_ALL_READ_CURRENT_FOLDER,
58 MARK_ALL_READ_WITH_SUBFOLDERS
59 };
60
61 gboolean e_plugin_ui_init (GtkUIManager *ui_manager,
62 EShellView *shell_view);
63 gint e_plugin_lib_enable (EPlugin *ep,
64 gint enable);
65
66 static void
67 async_context_free (AsyncContext *context)
68 {
69 if (context->activity != NULL)
70 g_object_unref (context->activity);
71
72 /* This should be empty already, but just to be sure... */
73 while (!g_queue_is_empty (&context->folder_names))
74 g_free (g_queue_pop_head (&context->folder_names));
75
76 g_slice_free (AsyncContext, context);
77 }
78
79 gint
80 e_plugin_lib_enable (EPlugin *ep,
81 gint enable)
82 {
83 return 0;
84 }
85
86 static void
87 button_clicked_cb (GtkButton *button,
88 GtkDialog *dialog)
89 {
90 gpointer response;
91
92 response = g_object_get_data (G_OBJECT (button), "response");
93 gtk_dialog_response (dialog, GPOINTER_TO_INT (response));
94 }
95
96 static gint
97 prompt_user (gboolean has_subfolders)
98 {
99 GtkWidget *container;
100 GtkWidget *dialog;
101 GtkWidget *grid;
102 GtkWidget *widget;
103 GtkWidget *vbox;
104 gchar *markup;
105 gint response, ret;
106
107 if (!has_subfolders) {
108 EShell *shell;
109 GtkWindow *parent;
110
111 shell = e_shell_get_default ();
112 parent = e_shell_get_active_window (shell);
113
114 return em_utils_prompt_user (
115 parent, NULL, "mail:ask-mark-all-read", NULL) ?
116 MARK_ALL_READ_CURRENT_FOLDER : MARK_ALL_READ_CANCEL;
117 }
118
119 dialog = gtk_dialog_new ();
120 widget = gtk_dialog_get_action_area (GTK_DIALOG (dialog));
121 gtk_widget_hide (widget);
122 gtk_window_set_title (GTK_WINDOW (dialog), "");
123 gtk_container_set_border_width (GTK_CONTAINER (dialog), 12);
124 vbox = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
125
126 /* Grid */
127 widget = gtk_grid_new ();
128 gtk_grid_set_row_spacing (GTK_GRID (widget), 12);
129 gtk_grid_set_column_spacing (GTK_GRID (widget), 12);
130 gtk_box_pack_start (GTK_BOX (vbox), widget, TRUE, TRUE, 0);
131 gtk_widget_show (widget);
132
133 grid = widget;
134
135 /* Question Icon */
136 widget = gtk_image_new_from_stock (
137 GTK_STOCK_DIALOG_QUESTION, GTK_ICON_SIZE_DIALOG);
138 gtk_widget_set_valign (widget, GTK_ALIGN_START);
139 gtk_grid_attach (GTK_GRID (grid), widget, 0, 0, 1, 3);
140 gtk_widget_show (widget);
141
142 /* Primary Text */
143 markup = g_markup_printf_escaped (
144 "<big><b>%s</b></big>", gettext (PRIMARY_TEXT));
145 widget = gtk_label_new (markup);
146 gtk_label_set_line_wrap (GTK_LABEL (widget), TRUE);
147 gtk_label_set_use_markup (GTK_LABEL (widget), TRUE);
148 gtk_misc_set_alignment (GTK_MISC (widget), 0.0, 0.0);
149 gtk_grid_attach (GTK_GRID (grid), widget, 1, 0, 1, 1);
150 gtk_widget_show (widget);
151 g_free (markup);
152
153 /* Secondary Text */
154 widget = gtk_label_new (gettext (SECONDARY_TEXT));
155 gtk_widget_set_vexpand (widget, TRUE);
156 gtk_widget_set_valign (widget, GTK_ALIGN_START);
157 gtk_label_set_line_wrap (GTK_LABEL (widget), TRUE);
158 gtk_misc_set_alignment (GTK_MISC (widget), 0.0, 0.0);
159 gtk_grid_attach (GTK_GRID (grid), widget, 1, 1, 1, 1);
160 gtk_widget_show (widget);
161
162 /* Action Area */
163 widget = gtk_hbox_new (FALSE, 6);
164 gtk_widget_set_halign (widget, GTK_ALIGN_END);
165 gtk_grid_attach (GTK_GRID (grid), widget, 1, 2, 1, 1);
166 gtk_widget_show (widget);
167
168 container = widget;
169
170 /* Cancel button */
171 widget = gtk_button_new_from_stock (GTK_STOCK_CANCEL);
172 g_object_set_data (
173 G_OBJECT (widget), "response",
174 GINT_TO_POINTER (GTK_RESPONSE_CANCEL));
175 g_signal_connect (
176 widget, "clicked",
177 G_CALLBACK (button_clicked_cb), dialog);
178 gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
179 gtk_widget_show (widget);
180
181 /* To Translators: It's a response button caption on a question
182 * "Do you want to mark messages as read in the current folder
183 * only, or in the current folder as well as all subfolders?" */
184 widget = gtk_button_new_with_mnemonic (
185 _("In Current Folder and _Subfolders"));
186 g_object_set_data (
187 G_OBJECT (widget), "response",
188 GINT_TO_POINTER (GTK_RESPONSE_YES));
189 g_signal_connect (
190 widget, "clicked",
191 G_CALLBACK (button_clicked_cb), dialog);
192 gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
193 gtk_widget_show (widget);
194
195 /* To Translators: It's a response button caption on a question
196 * "Do you want to mark messages as read in the current folder
197 * only, or in the current folder as well as all subfolders?" */
198 widget = gtk_button_new_with_mnemonic (
199 _("In Current _Folder Only"));
200 g_object_set_data (
201 G_OBJECT (widget), "response",
202 GINT_TO_POINTER (GTK_RESPONSE_NO));
203 g_signal_connect (
204 widget, "clicked",
205 G_CALLBACK (button_clicked_cb), dialog);
206 gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
207 gtk_widget_show (widget);
208
209 response = gtk_dialog_run (GTK_DIALOG (dialog));
210
211 gtk_widget_destroy (dialog);
212
213 if (response == GTK_RESPONSE_YES)
214 ret = MARK_ALL_READ_WITH_SUBFOLDERS;
215 else if (response == GTK_RESPONSE_NO)
216 ret = MARK_ALL_READ_CURRENT_FOLDER;
217 else
218 ret = MARK_ALL_READ_CANCEL;
219
220 return ret;
221 }
222
223 static gboolean
224 scan_folder_tree_for_unread_helper (GtkTreeModel *model,
225 GtkTreeIter *iter,
226 GtkTreePath *path,
227 gboolean is_first_node,
228 gint initial_depth,
229 gint *relative_depth)
230 {
231 /* This is based on gtk_tree_model_foreach(). Unfortunately
232 * that function insists on traversing the entire tree model. */
233
234 do {
235 GtkTreeIter child;
236 gboolean folder_has_unread;
237 gboolean is_draft = FALSE;
238 gboolean is_store = FALSE;
239 gboolean is_trash;
240 gboolean is_virtual;
241 guint unread = 0;
242 guint folder_flags = 0;
243 guint folder_type;
244
245 gtk_tree_model_get (
246 model, iter,
247 COL_UINT_FLAGS, &folder_flags,
248 COL_UINT_UNREAD, &unread,
249 COL_BOOL_IS_STORE, &is_store,
250 COL_BOOL_IS_DRAFT, &is_draft, -1);
251
252 folder_type = (folder_flags & CAMEL_FOLDER_TYPE_MASK);
253 is_virtual = ((folder_flags & CAMEL_FOLDER_VIRTUAL) != 0);
254 is_trash = (folder_type == CAMEL_FOLDER_TYPE_TRASH);
255
256 folder_has_unread =
257 !is_store && !is_draft &&
258 (!is_virtual || !is_trash) &&
259 unread > 0 && unread != ~((guint) 0);
260
261 if (folder_has_unread) {
262 gint current_depth;
263
264 current_depth = gtk_tree_path_get_depth (path);
265 *relative_depth = current_depth - initial_depth + 1;
266
267 /* If we find unread messages in a child of the
268 * selected folder, short-circuit the recursion. */
269 if (*relative_depth > 1)
270 return TRUE;
271 }
272
273 if (gtk_tree_model_iter_children (model, &child, iter)) {
274 gtk_tree_path_down (path);
275
276 if (scan_folder_tree_for_unread_helper (
277 model, &child, path, FALSE,
278 initial_depth, relative_depth))
279 return TRUE;
280
281 gtk_tree_path_up (path);
282 }
283
284 /* do not check sibling nodes of the selected folder */
285 if (is_first_node)
286 break;
287
288 gtk_tree_path_next (path);
289
290 } while (gtk_tree_model_iter_next (model, iter));
291
292 return FALSE;
293 }
294
295 static gint
296 scan_folder_tree_for_unread (const gchar *folder_uri)
297 {
298 GtkTreeRowReference *reference;
299 EMFolderTreeModel *model;
300 gint relative_depth = 0;
301
302 /* Traverses the selected folder and its children and returns
303 * the relative depth of the furthest child folder containing
304 * unread mail. Except, we abort the traversal as soon as we
305 * find a child folder with unread messages. So the possible
306 * return values are:
307 *
308 * Depth = 0: No unread mail found.
309 * Depth = 1: Unread mail only in selected folder.
310 * Depth = 2: Unread mail in one of the child folders.
311 */
312
313 if (folder_uri == NULL)
314 return 0;
315
316 model = em_folder_tree_model_get_default ();
317 reference = em_folder_tree_model_lookup_uri (model, folder_uri);
318
319 if (gtk_tree_row_reference_valid (reference)) {
320 GtkTreePath *path;
321 GtkTreeIter iter;
322
323 path = gtk_tree_row_reference_get_path (reference);
324 gtk_tree_model_get_iter (GTK_TREE_MODEL (model), &iter, path);
325
326 scan_folder_tree_for_unread_helper (
327 GTK_TREE_MODEL (model), &iter, path, TRUE,
328 gtk_tree_path_get_depth (path), &relative_depth);
329
330 gtk_tree_path_free (path);
331 }
332
333 return relative_depth;
334 }
335
336 static void
337 collect_folder_names (GQueue *folder_names,
338 CamelFolderInfo *folder_info)
339 {
340 while (folder_info != NULL) {
341 if (folder_info->child != NULL)
342 collect_folder_names (
343 folder_names, folder_info->child);
344
345 g_queue_push_tail (
346 folder_names, g_strdup (folder_info->full_name));
347
348 folder_info = folder_info->next;
349 }
350 }
351
352 static void
353 mar_got_folder (CamelStore *store,
354 GAsyncResult *result,
355 AsyncContext *context)
356 {
357 EAlertSink *alert_sink;
358 GCancellable *cancellable;
359 CamelFolder *folder;
360 gchar *folder_name;
361 GError *error = NULL;
362 GPtrArray *uids;
363 gint ii;
364
365 alert_sink = e_activity_get_alert_sink (context->activity);
366 cancellable = e_activity_get_cancellable (context->activity);
367
368 folder = camel_store_get_folder_finish (store, result, &error);
369
370 if (e_activity_handle_cancellation (context->activity, error)) {
371 g_warn_if_fail (folder == NULL);
372 async_context_free (context);
373 g_error_free (error);
374 return;
375
376 } else if (error != NULL) {
377 g_warn_if_fail (folder == NULL);
378 e_alert_submit (
379 alert_sink, "mail:folder-open",
380 error->message, NULL);
381 async_context_free (context);
382 g_error_free (error);
383 return;
384 }
385
386 g_return_if_fail (CAMEL_IS_FOLDER (folder));
387
388 camel_folder_freeze (folder);
389
390 uids = camel_folder_get_uids (folder);
391
392 for (ii = 0; ii < uids->len; ii++)
393 camel_folder_set_message_flags (
394 folder, uids->pdata[ii],
395 CAMEL_MESSAGE_SEEN,
396 CAMEL_MESSAGE_SEEN);
397
398 camel_folder_free_uids (folder, uids);
399
400 camel_folder_thaw (folder);
401 g_object_unref (folder);
402
403 /* If the folder name queue is empty, we're done. */
404 if (g_queue_is_empty (&context->folder_names)) {
405 e_activity_set_state (context->activity, E_ACTIVITY_COMPLETED);
406 async_context_free (context);
407 return;
408 }
409
410 folder_name = g_queue_pop_head (&context->folder_names);
411
412 camel_store_get_folder (
413 store, folder_name, 0,
414 G_PRIORITY_DEFAULT, cancellable,
415 (GAsyncReadyCallback) mar_got_folder, context);
416
417 g_free (folder_name);
418 }
419
420 static void
421 mar_got_folder_info (CamelStore *store,
422 GAsyncResult *result,
423 AsyncContext *context)
424 {
425 EAlertSink *alert_sink;
426 GCancellable *cancellable;
427 CamelFolderInfo *folder_info;
428 gchar *folder_name;
429 gint response;
430 GError *error = NULL;
431
432 alert_sink = e_activity_get_alert_sink (context->activity);
433 cancellable = e_activity_get_cancellable (context->activity);
434
435 folder_info = camel_store_get_folder_info_finish (
436 store, result, &error);
437
438 if (e_activity_handle_cancellation (context->activity, error)) {
439 g_warn_if_fail (folder_info == NULL);
440 async_context_free (context);
441 g_error_free (error);
442 return;
443
444 /* XXX This EAlert primary text isn't technically correct since
445 * we're just collecting folder tree info and haven't actually
446 * opened any folders yet, but the user doesn't need to know. */
447 } else if (error != NULL) {
448 g_warn_if_fail (folder_info == NULL);
449 e_alert_submit (
450 alert_sink, "mail:folder-open",
451 error->message, NULL);
452 async_context_free (context);
453 g_error_free (error);
454 return;
455 }
456
457 g_return_if_fail (folder_info != NULL);
458
459 response = prompt_user (folder_info->child != NULL);
460
461 if (response == MARK_ALL_READ_CURRENT_FOLDER)
462 g_queue_push_tail (
463 &context->folder_names,
464 g_strdup (folder_info->full_name));
465
466 if (response == MARK_ALL_READ_WITH_SUBFOLDERS)
467 collect_folder_names (&context->folder_names, folder_info);
468
469 camel_store_free_folder_info (store, folder_info);
470
471 /* If the user cancelled, we're done. */
472 if (g_queue_is_empty (&context->folder_names)) {
473 e_activity_set_state (context->activity, E_ACTIVITY_COMPLETED);
474 async_context_free (context);
475 return;
476 }
477
478 folder_name = g_queue_pop_head (&context->folder_names);
479
480 camel_store_get_folder (
481 store, folder_name, 0,
482 G_PRIORITY_DEFAULT, cancellable,
483 (GAsyncReadyCallback) mar_got_folder, context);
484
485 g_free (folder_name);
486 }
487
488 static void
489 action_mail_mark_read_recursive_cb (GtkAction *action,
490 EShellView *shell_view)
491 {
492 EAlertSink *alert_sink;
493 GCancellable *cancellable;
494 EShellBackend *shell_backend;
495 EShellContent *shell_content;
496 EShellSidebar *shell_sidebar;
497 EMFolderTree *folder_tree;
498 AsyncContext *context;
499 CamelStore *store;
500 gchar *folder_name;
501
502 shell_backend = e_shell_view_get_shell_backend (shell_view);
503 shell_content = e_shell_view_get_shell_content (shell_view);
504 shell_sidebar = e_shell_view_get_shell_sidebar (shell_view);
505
506 g_object_get (shell_sidebar, "folder-tree", &folder_tree, NULL);
507
508 /* This action should only be activatable if a folder is selected. */
509 if (!em_folder_tree_get_selected (folder_tree, &store, &folder_name))
510 g_return_if_reached ();
511
512 g_object_unref (folder_tree);
513
514 /* Open the selected folder asynchronously. */
515
516 context = g_slice_new0 (AsyncContext);
517 context->activity = e_activity_new ();
518 g_queue_init (&context->folder_names);
519
520 alert_sink = E_ALERT_SINK (shell_content);
521 e_activity_set_alert_sink (context->activity, alert_sink);
522
523 cancellable = camel_operation_new ();
524 e_activity_set_cancellable (context->activity, cancellable);
525
526 e_shell_backend_add_activity (shell_backend, context->activity);
527
528 camel_store_get_folder_info (
529 store, folder_name,
530 CAMEL_STORE_FOLDER_INFO_FAST |
531 CAMEL_STORE_FOLDER_INFO_RECURSIVE,
532 G_PRIORITY_DEFAULT, cancellable,
533 (GAsyncReadyCallback) mar_got_folder_info, context);
534
535 g_object_unref (cancellable);
536
537 g_object_unref (store);
538 g_free (folder_name);
539 }
540
541 static GtkActionEntry entries[] = {
542
543 { "mail-mark-read-recursive",
544 "mail-mark-read",
545 N_("Mark Me_ssages as Read"),
546 NULL,
547 NULL, /* XXX Add a tooltip! */
548 G_CALLBACK (action_mail_mark_read_recursive_cb) }
549 };
550
551 static void
552 update_actions_cb (EShellView *shell_view,
553 gpointer user_data)
554 {
555 GtkActionGroup *action_group;
556 EShellSidebar *shell_sidebar;
557 EShellWindow *shell_window;
558 EMFolderTree *folder_tree;
559 GtkAction *action;
560 gchar *folder_uri;
561 gboolean visible;
562
563 g_return_if_fail (E_IS_SHELL_VIEW (shell_view));
564
565 shell_window = e_shell_view_get_shell_window (shell_view);
566 action_group = e_shell_window_get_action_group (shell_window, "mail");
567
568 action = gtk_action_group_get_action (action_group, entries[0].name);
569 g_return_if_fail (action != NULL);
570
571 shell_sidebar = e_shell_view_get_shell_sidebar (shell_view);
572 g_object_get (shell_sidebar, "folder-tree", &folder_tree, NULL);
573 folder_uri = em_folder_tree_get_selected_uri (folder_tree);
574
575 visible = em_folder_tree_get_selected (folder_tree, NULL, NULL)
576 && scan_folder_tree_for_unread (folder_uri) > 0;
577 gtk_action_set_visible (action, visible);
578
579 g_object_unref (folder_tree);
580 g_free (folder_uri);
581 }
582
583 gboolean
584 e_plugin_ui_init (GtkUIManager *ui_manager,
585 EShellView *shell_view)
586 {
587 EShellWindow *shell_window;
588 GtkActionGroup *action_group;
589
590 shell_window = e_shell_view_get_shell_window (shell_view);
591 action_group = e_shell_window_get_action_group (shell_window, "mail");
592
593 /* Add actions to the "mail" action group. */
594 gtk_action_group_add_actions (
595 action_group, entries,
596 G_N_ELEMENTS (entries), shell_view);
597
598 g_signal_connect (
599 shell_view, "update-actions",
600 G_CALLBACK (update_actions_cb), NULL);
601
602 return TRUE;
603 }