evolution-3.6.4/plugins/mark-all-read/mark-all-read.c

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 }