evolution-3.6.4/mail/em-folder-tree.c

Location Tool Test ID Function Issue
em-folder-tree.c:1966:2 clang-analyzer Function call argument is an uninitialized value
em-folder-tree.c:1966:2 clang-analyzer Function call argument is an uninitialized value
   1 /*
   2  * This program is free software; you can redistribute it and/or
   3  * modify it under the terms of the GNU Lesser General Public
   4  * License as published by the Free Software Foundation; either
   5  * version 2 of the License, or (at your option) version 3.
   6  *
   7  * This program is distributed in the hope that it will be useful,
   8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
   9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  10  * Lesser General Public License for more details.
  11  *
  12  * You should have received a copy of the GNU Lesser General Public
  13  * License along with the program; if not, see <http://www.gnu.org/licenses/>
  14  *
  15  *
  16  * Authors:
  17  *		Jeffrey Stedfast <fejj@ximian.com>
  18  *
  19  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
  20  *
  21  */
  22 
  23 #ifdef HAVE_CONFIG_H
  24 #include <config.h>
  25 #endif
  26 
  27 #include <stdio.h>
  28 #include <stdlib.h>
  29 #include <string.h>
  30 #include <sys/types.h>
  31 #include <sys/stat.h>
  32 #include <unistd.h>
  33 #include <fcntl.h>
  34 #include <errno.h>
  35 
  36 #include <libxml/tree.h>
  37 
  38 #include <glib/gi18n.h>
  39 #include <gdk/gdkkeysyms.h>
  40 #include <glib/gi18n.h>
  41 
  42 #include "e-util/e-mktemp.h"
  43 #include "e-util/e-icon-factory.h"
  44 #include "libevolution-utils/e-alert-dialog.h"
  45 #include "e-util/e-util.h"
  46 
  47 #include "misc/e-selectable.h"
  48 
  49 #include "em-vfolder-editor-rule.h"
  50 
  51 #include "libemail-utils/mail-mt.h"
  52 #include "libemail-engine/e-mail-folder-utils.h"
  53 #include "libemail-engine/e-mail-session.h"
  54 #include "libemail-engine/mail-ops.h"
  55 #include "libemail-engine/mail-tools.h"
  56 
  57 #include "em-utils.h"
  58 #include "em-folder-tree.h"
  59 #include "em-folder-utils.h"
  60 #include "em-folder-selector.h"
  61 #include "em-folder-properties.h"
  62 #include "em-event.h"
  63 #include "mail-send-recv.h"
  64 #include "mail-vfolder-ui.h"
  65 
  66 #include "e-mail-ui-session.h"
  67 
  68 #define d(x)
  69 
  70 #define EM_FOLDER_TREE_GET_PRIVATE(obj) \
  71 	(G_TYPE_INSTANCE_GET_PRIVATE \
  72 	((obj), EM_TYPE_FOLDER_TREE, EMFolderTreePrivate))
  73 
  74 typedef struct _AsyncContext AsyncContext;
  75 
  76 #define EM_FOLDER_TREE_GET_PRIVATE(obj) \
  77 	(G_TYPE_INSTANCE_GET_PRIVATE \
  78 	((obj), EM_TYPE_FOLDER_TREE, EMFolderTreePrivate))
  79 
  80 struct _selected_uri {
  81 	gchar *key;		/* store:path or account/path */
  82 	gchar *uri;
  83 	CamelService *service;
  84 	gchar *path;
  85 };
  86 
  87 struct _EMFolderTreePrivate {
  88 	EMailSession *session;
  89 	EAlertSink *alert_sink;
  90 
  91 	/* selected_uri structures of each path pending selection. */
  92 	GSList *select_uris;
  93 
  94 	/* Removed as they're encountered, so use this
  95 	 * to find URI's not presnet but selected. */
  96 	GHashTable *select_uris_table;
  97 
  98 	guint32 excluded;
  99 	gboolean	(*excluded_func)	(EMFolderTree *folder_tree,
 100 						 GtkTreeModel *model,
 101 						 GtkTreeIter *iter,
 102 						 gpointer data);
 103 	gpointer excluded_data;
 104 
 105 	guint cursor_set:1;	/* set to TRUE means we or something
 106 				 * else has set the cursor, otherwise
 107 				 * we need to set it when we set the
 108 				 * selection */
 109 
 110 	guint autoscroll_id;
 111 	guint autoexpand_id;
 112 	GtkTreeRowReference *autoexpand_row;
 113 
 114 	guint loading_row_id;
 115 	guint loaded_row_id;
 116 
 117 	GtkTreeRowReference *drag_row;
 118 	gboolean skip_double_click;
 119 
 120 	GtkCellRenderer *text_renderer;
 121 	PangoEllipsizeMode ellipsize;
 122 
 123 	GtkWidget *selectable; /* an ESelectable, where to pass selectable calls */
 124 
 125 	/* Signal handler IDs */
 126 	gulong selection_changed_handler_id;
 127 };
 128 
 129 struct _AsyncContext {
 130 	EActivity *activity;
 131 	EMFolderTree *folder_tree;
 132 	GtkTreeRowReference *root;
 133 	gchar *full_name;
 134 };
 135 
 136 enum {
 137 	PROP_0,
 138 	PROP_ALERT_SINK,
 139 	PROP_COPY_TARGET_LIST,
 140 	PROP_ELLIPSIZE,
 141 	PROP_MODEL,
 142 	PROP_PASTE_TARGET_LIST,
 143 	PROP_SESSION
 144 };
 145 
 146 enum {
 147 	FOLDER_ACTIVATED,  /* aka double-clicked or user hit enter */
 148 	FOLDER_SELECTED,
 149 	POPUP_EVENT,
 150 	HIDDEN_KEY_EVENT,
 151 	LAST_SIGNAL
 152 };
 153 
 154 /* Drag & Drop types */
 155 enum DndDragType {
 156 	DND_DRAG_TYPE_FOLDER,          /* drag an evo folder */
 157 	DND_DRAG_TYPE_TEXT_URI_LIST,   /* drag to an mbox file */
 158 	NUM_DRAG_TYPES
 159 };
 160 
 161 enum DndDropType {
 162 	DND_DROP_TYPE_UID_LIST,        /* drop a list of message uids */
 163 	DND_DROP_TYPE_FOLDER,          /* drop an evo folder */
 164 	DND_DROP_TYPE_MESSAGE_RFC822,  /* drop a message/rfc822 stream */
 165 	DND_DROP_TYPE_TEXT_URI_LIST,   /* drop an mbox file */
 166 	NUM_DROP_TYPES
 167 };
 168 
 169 static GtkTargetEntry drag_types[] = {
 170 	{ (gchar *) "x-folder",         0, DND_DRAG_TYPE_FOLDER         },
 171 	{ (gchar *) "text/uri-list",    0, DND_DRAG_TYPE_TEXT_URI_LIST  },
 172 };
 173 
 174 static GtkTargetEntry drop_types[] = {
 175 	{ (gchar *) "x-uid-list" ,      0, DND_DROP_TYPE_UID_LIST       },
 176 	{ (gchar *) "x-folder",         0, DND_DROP_TYPE_FOLDER         },
 177 	{ (gchar *) "message/rfc822",   0, DND_DROP_TYPE_MESSAGE_RFC822 },
 178 	{ (gchar *) "text/uri-list",    0, DND_DROP_TYPE_TEXT_URI_LIST  },
 179 };
 180 
 181 static GdkAtom drag_atoms[NUM_DRAG_TYPES];
 182 static GdkAtom drop_atoms[NUM_DROP_TYPES];
 183 
 184 static guint signals[LAST_SIGNAL] = { 0 };
 185 
 186 struct _folder_tree_selection_data {
 187 	GtkTreeModel *model;
 188 	GtkTreeIter *iter;
 189 	gboolean set;
 190 };
 191 
 192 /* Forward Declarations */
 193 static void em_folder_tree_selectable_init (ESelectableInterface *interface);
 194 
 195 G_DEFINE_TYPE_WITH_CODE (
 196 	EMFolderTree,
 197 	em_folder_tree,
 198 	GTK_TYPE_TREE_VIEW,
 199 	G_IMPLEMENT_INTERFACE (
 200 		E_TYPE_SELECTABLE,
 201 		em_folder_tree_selectable_init))
 202 
 203 static void
 204 async_context_free (AsyncContext *context)
 205 {
 206 	if (context->activity != NULL)
 207 		g_object_unref (context->activity);
 208 
 209 	if (context->folder_tree != NULL)
 210 		g_object_unref (context->folder_tree);
 211 
 212 	gtk_tree_row_reference_free (context->root);
 213 
 214 	g_free (context->full_name);
 215 
 216 	g_slice_free (AsyncContext, context);
 217 }
 218 
 219 static void
 220 folder_tree_get_folder_info_cb (CamelStore *store,
 221                                 GAsyncResult *result,
 222                                 AsyncContext *context)
 223 {
 224 	struct _EMFolderTreeModelStoreInfo *si;
 225 	CamelFolderInfo *folder_info;
 226 	CamelFolderInfo *child_info;
 227 	EAlertSink *alert_sink;
 228 	GtkTreeView *tree_view;
 229 	GtkTreeModel *model;
 230 	GtkTreePath *path;
 231 	GtkTreeIter root;
 232 	GtkTreeIter iter;
 233 	GtkTreeIter titer;
 234 	gboolean is_store;
 235 	gboolean iter_is_placeholder;
 236 	gboolean valid;
 237 	GError *error = NULL;
 238 
 239 	alert_sink = e_activity_get_alert_sink (context->activity);
 240 
 241 	folder_info = camel_store_get_folder_info_finish (
 242 		store, result, &error);
 243 
 244 	tree_view = GTK_TREE_VIEW (context->folder_tree);
 245 	model = gtk_tree_view_get_model (tree_view);
 246 
 247 	/* Check if our parent folder has been deleted/unsubscribed. */
 248 	if (!gtk_tree_row_reference_valid (context->root)) {
 249 		g_clear_error (&error);
 250 		goto exit;
 251 	}
 252 
 253 	path = gtk_tree_row_reference_get_path (context->root);
 254 	valid = gtk_tree_model_get_iter (model, &root, path);
 255 	g_return_if_fail (valid);
 256 
 257 	gtk_tree_model_get (model, &root, COL_BOOL_IS_STORE, &is_store, -1);
 258 
 259 	/* If we had an error, then we need to re-set the
 260 	 * load subdirs state and collapse the node. */
 261 	if (error != NULL) {
 262 		gtk_tree_store_set (
 263 			GTK_TREE_STORE (model), &root,
 264 			COL_BOOL_LOAD_SUBDIRS, TRUE, -1);
 265 		gtk_tree_view_collapse_row (tree_view, path);
 266 	}
 267 
 268 	gtk_tree_path_free (path);
 269 
 270 	if (e_activity_handle_cancellation (context->activity, error)) {
 271 		g_warn_if_fail (folder_info == NULL);
 272 		async_context_free (context);
 273 		g_error_free (error);
 274 		return;
 275 
 276 	/* XXX POP3 stores always return a "no folder" error because they
 277 	 *     have no folder hierarchy to scan.  Just ignore the error. */
 278 	} else if (g_error_matches (
 279 			error, CAMEL_STORE_ERROR,
 280 			CAMEL_STORE_ERROR_NO_FOLDER)) {
 281 		g_warn_if_fail (folder_info == NULL);
 282 		async_context_free (context);
 283 		g_error_free (error);
 284 		return;
 285 
 286 	} else if (error != NULL) {
 287 		g_warn_if_fail (folder_info == NULL);
 288 		e_alert_submit (
 289 			alert_sink, "mail:folder-open",
 290 			error->message, NULL);
 291 		async_context_free (context);
 292 		g_error_free (error);
 293 		return;
 294 	}
 295 
 296 	/* If we've just set up an NNTP account, for example, and haven't
 297 	 * subscribed to any folders yet, folder_info may legitimately be
 298 	 * NULL at this point.  We handle that case below.  Proceed. */
 299 
 300 	/* Check if the store has been removed. */
 301 	si = em_folder_tree_model_lookup_store_info (
 302 		EM_FOLDER_TREE_MODEL (model), store);
 303 	if (si == NULL)
 304 		goto exit;
 305 
 306 	/* Make sure we still need to load the tree subfolders. */
 307 
 308 	iter_is_placeholder = FALSE;
 309 
 310 	/* Get the first child (which will be a placeholder row). */
 311 	valid = gtk_tree_model_iter_children (model, &iter, &root);
 312 
 313 	/* Traverse to the last valid iter, or the placeholder row. */
 314 	while (valid) {
 315 		gboolean is_store_node = FALSE;
 316 		gboolean is_folder_node = FALSE;
 317 
 318 		titer = iter; /* Preserve the last valid iter */
 319 
 320 		gtk_tree_model_get (
 321 			model, &iter,
 322 			COL_BOOL_IS_STORE, &is_store_node,
 323 			COL_BOOL_IS_FOLDER, &is_folder_node, -1);
 324 
 325 		/* Stop on a "Loading..." placeholder row. */
 326 		if (!is_store_node && !is_folder_node) {
 327 			iter_is_placeholder = TRUE;
 328 			break;
 329 		}
 330 
 331 		valid = gtk_tree_model_iter_next (model, &iter);
 332 	}
 333 
 334 	iter = titer;
 335 	child_info = folder_info;
 336 
 337 	/* FIXME Camel's IMAP code is totally on crack here: the
 338 	 *       folder_info we got back should be for the folder
 339 	 *       we're expanding, and folder_info->child should be
 340 	 *       what we want to fill our tree with... *sigh* */
 341 	if (folder_info != NULL) {
 342 		gboolean names_match;
 343 
 344 		names_match = (g_strcmp0 (
 345 			folder_info->full_name,
 346 			context->full_name) == 0);
 347 		if (names_match) {
 348 			child_info = folder_info->child;
 349 			if (child_info == NULL)
 350 				child_info = folder_info->next;
 351 		}
 352 	}
 353 
 354 	/* The folder being expanded has no children after all.  Remove
 355 	 * the "Loading..." placeholder row and collapse the parent. */
 356 	if (child_info == NULL) {
 357 		if (iter_is_placeholder)
 358 			gtk_tree_store_remove (GTK_TREE_STORE (model), &iter);
 359 
 360 		if (is_store) {
 361 			path = gtk_tree_model_get_path (model, &root);
 362 			gtk_tree_view_collapse_row (tree_view, path);
 363 			gtk_tree_path_free (path);
 364 			goto exit;
 365 		}
 366 
 367 	} else {
 368 		while (child_info != NULL) {
 369 			GtkTreeRowReference *reference;
 370 
 371 			/* Check if we already have this row cached. */
 372 			reference = g_hash_table_lookup (
 373 				si->full_hash, child_info->full_name);
 374 
 375 			if (reference == NULL) {
 376 				/* If we're on a placeholder row, reuse
 377 				 * the row for the first child folder. */
 378 				if (iter_is_placeholder)
 379 					iter_is_placeholder = FALSE;
 380 				else
 381 					gtk_tree_store_append (
 382 						GTK_TREE_STORE (model),
 383 						&iter, &root);
 384 
 385 				em_folder_tree_model_set_folder_info (
 386 					EM_FOLDER_TREE_MODEL (model),
 387 					&iter, si, child_info, TRUE);
 388 			}
 389 
 390 			child_info = child_info->next;
 391 		}
 392 
 393 		/* Remove the "Loading..." placeholder row. */
 394 		if (iter_is_placeholder)
 395 			gtk_tree_store_remove (GTK_TREE_STORE (model), &iter);
 396 	}
 397 
 398 	gtk_tree_store_set (
 399 		GTK_TREE_STORE (model), &root,
 400 		COL_BOOL_LOAD_SUBDIRS, FALSE, -1);
 401 
 402 exit:
 403 	if (folder_info != NULL)
 404 		camel_store_free_folder_info (store, folder_info);
 405 
 406 	async_context_free (context);
 407 }
 408 
 409 static void
 410 folder_tree_emit_popup_event (EMFolderTree *folder_tree,
 411                               GdkEvent *event)
 412 {
 413 	g_signal_emit (folder_tree, signals[POPUP_EVENT], 0, event);
 414 }
 415 
 416 static void
 417 folder_tree_free_select_uri (struct _selected_uri *u)
 418 {
 419 	g_free (u->uri);
 420 	if (u->service)
 421 		g_object_unref (u->service);
 422 	g_free (u->key);
 423 	g_free (u->path);
 424 	g_free (u);
 425 }
 426 
 427 static gboolean
 428 folder_tree_select_func (GtkTreeSelection *selection,
 429                          GtkTreeModel *model,
 430                          GtkTreePath *path,
 431                          gboolean selected)
 432 {
 433 	EMFolderTreePrivate *priv;
 434 	GtkTreeView *tree_view;
 435 	gboolean is_store;
 436 	guint32 flags;
 437 	GtkTreeIter iter;
 438 
 439 	tree_view = gtk_tree_selection_get_tree_view (selection);
 440 
 441 	priv = EM_FOLDER_TREE_GET_PRIVATE (tree_view);
 442 
 443 	if (selected)
 444 		return TRUE;
 445 
 446 	if (priv->excluded == 0 && priv->excluded_func == NULL)
 447 		return TRUE;
 448 
 449 	if (!gtk_tree_model_get_iter (model, &iter, path))
 450 		return TRUE;
 451 
 452 	if (priv->excluded_func != NULL)
 453 		return priv->excluded_func (
 454 			EM_FOLDER_TREE (tree_view), model,
 455 			&iter, priv->excluded_data);
 456 
 457 	gtk_tree_model_get (
 458 		model, &iter, COL_UINT_FLAGS, &flags,
 459 		COL_BOOL_IS_STORE, &is_store, -1);
 460 
 461 	if (is_store)
 462 		flags |= CAMEL_FOLDER_NOSELECT;
 463 
 464 	return (flags & priv->excluded) == 0;
 465 }
 466 
 467 /* NOTE: Removes and frees the selected uri structure */
 468 static void
 469 folder_tree_select_uri (EMFolderTree *folder_tree,
 470                         GtkTreePath *path,
 471                         struct _selected_uri *u)
 472 {
 473 	EMFolderTreePrivate *priv = folder_tree->priv;
 474 	GtkTreeView *tree_view;
 475 	GtkTreeSelection *selection;
 476 
 477 	tree_view = GTK_TREE_VIEW (folder_tree);
 478 	selection = gtk_tree_view_get_selection (tree_view);
 479 	gtk_tree_selection_select_path (selection, path);
 480 	if (!priv->cursor_set) {
 481 		gtk_tree_view_set_cursor (tree_view, path, NULL, FALSE);
 482 		priv->cursor_set = TRUE;
 483 	}
 484 	gtk_tree_view_scroll_to_cell (tree_view, path, NULL, TRUE, 0.8f, 0.0f);
 485 	g_hash_table_remove (priv->select_uris_table, u->key);
 486 	priv->select_uris = g_slist_remove (priv->select_uris, u);
 487 	folder_tree_free_select_uri (u);
 488 }
 489 
 490 static void
 491 folder_tree_expand_node (const gchar *key,
 492                          EMFolderTree *folder_tree)
 493 {
 494 	struct _EMFolderTreeModelStoreInfo *si = NULL;
 495 	GtkTreeRowReference *row;
 496 	GtkTreeView *tree_view;
 497 	GtkTreeModel *model;
 498 	GtkTreePath *path;
 499 	EMailSession *session;
 500 	CamelService *service;
 501 	const gchar *p;
 502 	gchar *uid;
 503 	gsize n;
 504 	struct _selected_uri *u;
 505 
 506 	if (!(p = strchr (key, '/')))
 507 		n = strlen (key);
 508 	else
 509 		n = (p - key);
 510 
 511 	uid = g_alloca (n + 1);
 512 	memcpy (uid, key, n);
 513 	uid[n] = '\0';
 514 
 515 	tree_view = GTK_TREE_VIEW (folder_tree);
 516 	model = gtk_tree_view_get_model (tree_view);
 517 
 518 	session = em_folder_tree_get_session (folder_tree);
 519 
 520 	service = camel_session_ref_service (CAMEL_SESSION (session), uid);
 521 
 522 	if (CAMEL_IS_STORE (service))
 523 		si = em_folder_tree_model_lookup_store_info (
 524 			EM_FOLDER_TREE_MODEL (model),
 525 			CAMEL_STORE (service));
 526 
 527 	if (service != NULL)
 528 		g_object_unref (service);
 529 
 530 	if (si == NULL)
 531 		return;
 532 
 533 	if (p != NULL && p[1]) {
 534 		if (!(row = g_hash_table_lookup (si->full_hash, p + 1)))
 535 			return;
 536 	} else
 537 		row = si->row;
 538 
 539 	path = gtk_tree_row_reference_get_path (row);
 540 	gtk_tree_view_expand_to_path (tree_view, path);
 541 
 542 	u = g_hash_table_lookup (folder_tree->priv->select_uris_table, key);
 543 	if (u)
 544 		folder_tree_select_uri (folder_tree, path, u);
 545 
 546 	gtk_tree_path_free (path);
 547 }
 548 
 549 static void
 550 folder_tree_maybe_expand_row (EMFolderTreeModel *model,
 551                               GtkTreePath *tree_path,
 552                               GtkTreeIter *iter,
 553                               EMFolderTree *folder_tree)
 554 {
 555 	EMFolderTreePrivate *priv = folder_tree->priv;
 556 	CamelStore *store;
 557 	gchar *full_name;
 558 	gchar *key;
 559 	const gchar *uid;
 560 	struct _selected_uri *u;
 561 
 562 	gtk_tree_model_get (
 563 		GTK_TREE_MODEL (model), iter,
 564 		COL_STRING_FULL_NAME, &full_name,
 565 		COL_POINTER_CAMEL_STORE, &store, -1);
 566 
 567 	uid = camel_service_get_uid (CAMEL_SERVICE (store));
 568 	key = g_strdup_printf ("%s/%s", uid, full_name ? full_name : "");
 569 
 570 	u = g_hash_table_lookup (priv->select_uris_table, key);
 571 	if (u) {
 572 		gchar *c = strrchr (key, '/');
 573 
 574 		*c = '\0';
 575 		folder_tree_expand_node (key, folder_tree);
 576 
 577 		folder_tree_select_uri (folder_tree, tree_path, u);
 578 	}
 579 
 580 	g_free (full_name);
 581 	g_free (key);
 582 }
 583 
 584 static void
 585 folder_tree_clear_selected_list (EMFolderTree *folder_tree)
 586 {
 587 	EMFolderTreePrivate *priv = folder_tree->priv;
 588 
 589 	g_slist_foreach (priv->select_uris, (GFunc) folder_tree_free_select_uri, NULL);
 590 	g_slist_free (priv->select_uris);
 591 	g_hash_table_destroy (priv->select_uris_table);
 592 	priv->select_uris = NULL;
 593 	priv->select_uris_table = g_hash_table_new (g_str_hash, g_str_equal);
 594 	priv->cursor_set = FALSE;
 595 }
 596 
 597 static void
 598 folder_tree_cell_edited_cb (EMFolderTree *folder_tree,
 599                             const gchar *path_string,
 600                             const gchar *new_name)
 601 {
 602 	CamelFolderInfo *folder_info;
 603 	CamelStore *store;
 604 	GtkTreeView *tree_view;
 605 	GtkTreeModel *model;
 606 	GtkTreePath *path;
 607 	GtkTreeIter iter;
 608 	gchar *old_name = NULL;
 609 	gchar *old_full_name = NULL;
 610 	gchar *new_full_name = NULL;
 611 	gchar *folder_uri;
 612 	gchar **strv;
 613 	gpointer parent;
 614 	guint index;
 615 	GError *local_error = NULL;
 616 
 617 	/* XXX Consider splitting this into separate async functions:
 618 	 *     em_folder_tree_rename_folder_async()
 619 	 *     em_folder_tree_rename_folder_finish() */
 620 
 621 	parent = gtk_widget_get_toplevel (GTK_WIDGET (folder_tree));
 622 	parent = gtk_widget_is_toplevel (parent) ? parent : NULL;
 623 
 624 	tree_view = GTK_TREE_VIEW (folder_tree);
 625 	model = gtk_tree_view_get_model (tree_view);
 626 	path = gtk_tree_path_new_from_string (path_string);
 627 	gtk_tree_model_get_iter (model, &iter, path);
 628 	gtk_tree_path_free (path);
 629 
 630 	gtk_tree_model_get (
 631 		model, &iter,
 632 		COL_POINTER_CAMEL_STORE, &store,
 633 		COL_STRING_DISPLAY_NAME, &old_name,
 634 		COL_STRING_FULL_NAME, &old_full_name, -1);
 635 
 636 	if (!old_name || !old_full_name || g_strcmp0 (new_name, old_name) == 0)
 637 		goto exit;
 638 
 639 	/* Check for invalid characters. */
 640 	if (strchr (new_name, '/') != NULL) {
 641 		e_alert_run_dialog_for_args (
 642 			parent, "mail:no-rename-folder",
 643 			old_name, new_name,
 644 			_("Folder names cannot contain '/'"), NULL);
 645 		goto exit;
 646 	}
 647 
 648 	/* Build the new name from the old name. */
 649 	strv = g_strsplit_set (old_full_name, "/", 0);
 650 	index = g_strv_length (strv) - 1;
 651 	g_free (strv[index]);
 652 	strv[index] = g_strdup (new_name);
 653 	new_full_name = g_strjoinv ("/", strv);
 654 	g_strfreev (strv);
 655 
 656 	/* Check for duplicate folder name. */
 657 	/* FIXME camel_store_get_folder_info() may block. */
 658 	folder_info = camel_store_get_folder_info_sync (
 659 		store, new_full_name,
 660 		CAMEL_STORE_FOLDER_INFO_FAST, NULL, NULL);
 661 	if (folder_info != NULL) {
 662 		e_alert_run_dialog_for_args (
 663 			parent, "mail:no-rename-folder-exists",
 664 			old_name, new_name, NULL);
 665 		camel_store_free_folder_info (store, folder_info);
 666 		goto exit;
 667 	}
 668 
 669 	/* FIXME camel_store_rename_folder_sync() may block. */
 670 	camel_store_rename_folder_sync (
 671 		store, old_full_name, new_full_name, NULL, &local_error);
 672 
 673 	if (local_error != NULL) {
 674 		e_alert_run_dialog_for_args (
 675 			parent, "mail:no-rename-folder",
 676 			old_full_name, new_full_name,
 677 			local_error->message, NULL);
 678 		g_error_free (local_error);
 679 		goto exit;
 680 	}
 681 
 682 	folder_uri = e_mail_folder_uri_build (store, new_full_name);
 683 	em_folder_tree_set_selected (folder_tree, folder_uri, FALSE);
 684 	g_free (folder_uri);
 685 
 686  exit:
 687 
 688 	g_free (old_name);
 689 	g_free (old_full_name);
 690 	g_free (new_full_name);
 691 }
 692 
 693 static gboolean
 694 subdirs_contain_unread (GtkTreeModel *model,
 695                         GtkTreeIter *root)
 696 {
 697 	guint unread;
 698 	GtkTreeIter iter;
 699 
 700 	if (!gtk_tree_model_iter_children (model, &iter, root))
 701 		return FALSE;
 702 
 703 	do {
 704 		gtk_tree_model_get (model, &iter, COL_UINT_UNREAD, &unread, -1);
 705 		if (unread)
 706 			return TRUE;
 707 
 708 		if (gtk_tree_model_iter_has_child (model, &iter))
 709 			if (subdirs_contain_unread (model, &iter))
 710 				return TRUE;
 711 	} while (gtk_tree_model_iter_next (model, &iter));
 712 
 713 	return FALSE;
 714 }
 715 
 716 static void
 717 folder_tree_render_display_name (GtkTreeViewColumn *column,
 718                                  GtkCellRenderer *renderer,
 719                                  GtkTreeModel *model,
 720                                  GtkTreeIter *iter)
 721 {
 722 	CamelService *service;
 723 	PangoWeight weight;
 724 	gboolean is_store, bold, subdirs_unread = FALSE;
 725 	gboolean editable;
 726 	guint unread;
 727 	gchar *name;
 728 
 729 	gtk_tree_model_get (
 730 		model, iter,
 731 		COL_STRING_DISPLAY_NAME, &name,
 732 		COL_POINTER_CAMEL_STORE, &service,
 733 		COL_BOOL_IS_STORE, &is_store,
 734 		COL_UINT_UNREAD, &unread, -1);
 735 
 736 	g_object_get (renderer, "editable", &editable, NULL);
 737 
 738 	bold = is_store || unread;
 739 
 740 	if (gtk_tree_model_iter_has_child (model, iter)) {
 741 		gboolean expanded = TRUE;
 742 
 743 		g_object_get (renderer, "is-expanded", &expanded, NULL);
 744 
 745 		if (!bold || !expanded)
 746 			subdirs_unread = subdirs_contain_unread (model, iter);
 747 	}
 748 
 749 	bold = !editable && (bold || subdirs_unread);
 750 	weight = bold ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL;
 751 	g_object_set (renderer, "weight", weight, NULL);
 752 
 753 	if (is_store) {
 754 		const gchar *display_name;
 755 
 756 		display_name = camel_service_get_display_name (service);
 757 		g_object_set (renderer, "text", display_name, NULL);
 758 
 759 	} else if (!editable && unread > 0) {
 760 		gchar *name_and_unread;
 761 
 762 		/* Translators: This is the string used for displaying the
 763 		 * folder names in folder trees. The first "%s" will be
 764 		 * replaced by the folder's name and "%u" will be replaced
 765 		 * with the number of unread messages in the folder. The
 766 		 * second %s will be replaced with a "+" letter for collapsed
 767 		 * folders with unread messages in some subfolder too,
 768 		 * or with an empty string for other cases.
 769 		 *
 770 		 * Most languages should translate this as "%s (%u%s)". The
 771 		 * languages that use localized digits (like Persian) may
 772 		 * need to replace "%u" with "%Iu". Right-to-left languages
 773 		 * (like Arabic and Hebrew) may need to add bidirectional
 774 		 * formatting codes to take care of the cases the folder
 775 		 * name appears in either direction.
 776 		 *
 777 		 * Do not translate the "folder-display|" part. Remove it
 778 		 * from your translation.
 779 		 */
 780 		name_and_unread = g_strdup_printf (
 781 			C_("folder-display", "%s (%u%s)"),
 782 			name, unread, subdirs_unread ? "+" : "");
 783 		g_object_set (renderer, "text", name_and_unread, NULL);
 784 		g_free (name_and_unread);
 785 
 786 	} else {
 787 		g_object_set (renderer, "text", name, NULL);
 788 	}
 789 
 790 	g_free (name);
 791 }
 792 
 793 static void
 794 folder_tree_render_icon (GtkTreeViewColumn *column,
 795                          GtkCellRenderer *renderer,
 796                          GtkTreeModel *model,
 797                          GtkTreeIter *iter)
 798 {
 799 	GtkTreeSelection *selection;
 800 	GtkTreePath *drag_dest_row;
 801 	GtkWidget *tree_view;
 802 	GIcon *icon;
 803 	guint unread;
 804 	guint old_unread;
 805 	gchar *icon_name;
 806 	gboolean is_selected;
 807 	gboolean is_drafts = FALSE;
 808 	gboolean is_drag_dest = FALSE;
 809 	guint32 fi_flags = 0;
 810 
 811 	gtk_tree_model_get (
 812 		model, iter,
 813 		COL_STRING_ICON_NAME, &icon_name,
 814 		COL_UINT_UNREAD_LAST_SEL, &old_unread,
 815 		COL_UINT_UNREAD, &unread,
 816 		COL_BOOL_IS_DRAFT, &is_drafts,
 817 		COL_UINT_FLAGS, &fi_flags,
 818 		-1);
 819 
 820 	if (icon_name == NULL)
 821 		return;
 822 
 823 	tree_view = gtk_tree_view_column_get_tree_view (column);
 824 	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree_view));
 825 	is_selected = gtk_tree_selection_iter_is_selected (selection, iter);
 826 
 827 	gtk_tree_view_get_drag_dest_row (
 828 		GTK_TREE_VIEW (tree_view), &drag_dest_row, NULL);
 829 	if (drag_dest_row != NULL) {
 830 		GtkTreePath *path;
 831 
 832 		path = gtk_tree_model_get_path (model, iter);
 833 		if (gtk_tree_path_compare (path, drag_dest_row) == 0)
 834 			is_drag_dest = TRUE;
 835 		gtk_tree_path_free (path);
 836 
 837 		gtk_tree_path_free (drag_dest_row);
 838 	}
 839 
 840 	if (g_strcmp0 (icon_name, "folder") == 0) {
 841 		if (is_selected) {
 842 			g_free (icon_name);
 843 			icon_name = g_strdup ("folder-open");
 844 		} else if (is_drag_dest) {
 845 			g_free (icon_name);
 846 			icon_name = g_strdup ("folder-drag-accept");
 847 		}
 848 	}
 849 
 850 	icon = g_themed_icon_new (icon_name);
 851 
 852 	/* Show an emblem if there's new mail. */
 853 	if (!is_selected && unread > old_unread && !is_drafts && !(fi_flags & CAMEL_FOLDER_VIRTUAL)) {
 854 		GIcon *temp_icon;
 855 		GEmblem *emblem;
 856 
 857 		temp_icon = g_themed_icon_new ("emblem-new");
 858 		emblem = g_emblem_new (temp_icon);
 859 		g_object_unref (temp_icon);
 860 
 861 		temp_icon = g_emblemed_icon_new (icon, emblem);
 862 		g_object_unref (emblem);
 863 		g_object_unref (icon);
 864 
 865 		icon = temp_icon;
 866 	}
 867 
 868 	g_object_set (renderer, "gicon", icon, NULL);
 869 
 870 	g_object_unref (icon);
 871 	g_free (icon_name);
 872 }
 873 
 874 static void
 875 folder_tree_selection_changed_cb (EMFolderTree *folder_tree,
 876                                   GtkTreeSelection *selection)
 877 {
 878 	GtkTreeModel *model;
 879 	GtkTreeIter iter;
 880 	GList *list;
 881 	CamelStore *store = NULL;
 882 	CamelFolderInfoFlags flags = 0;
 883 	guint unread = 0;
 884 	guint old_unread = 0;
 885 	gchar *folder_name = NULL;
 886 
 887 	list = gtk_tree_selection_get_selected_rows (selection, &model);
 888 
 889 	if (list == NULL)
 890 		goto exit;
 891 
 892 	gtk_tree_model_get_iter (model, &iter, list->data);
 893 
 894 	gtk_tree_model_get (
 895 		model, &iter,
 896 		COL_POINTER_CAMEL_STORE, &store,
 897 		COL_STRING_FULL_NAME, &folder_name,
 898 		COL_UINT_FLAGS, &flags,
 899 		COL_UINT_UNREAD, &unread,
 900 		COL_UINT_UNREAD_LAST_SEL, &old_unread, -1);
 901 
 902 	/* Sync unread counts to distinguish new incoming mail. */
 903 	if (unread != old_unread)
 904 		gtk_tree_store_set (
 905 			GTK_TREE_STORE (model), &iter,
 906 			COL_UINT_UNREAD_LAST_SEL, unread, -1);
 907 
 908 exit:
 909 	g_signal_emit (
 910 		folder_tree, signals[FOLDER_SELECTED], 0,
 911 		store, folder_name, flags);
 912 
 913 	g_free (folder_name);
 914 
 915 	g_list_foreach (list, (GFunc) gtk_tree_path_free, NULL);
 916 	g_list_free (list);
 917 }
 918 
 919 static void
 920 folder_tree_copy_expanded_cb (GtkTreeView *unused,
 921                               GtkTreePath *path,
 922                               GtkTreeView *tree_view)
 923 {
 924 	gtk_tree_view_expand_row (tree_view, path, FALSE);
 925 }
 926 
 927 static void
 928 folder_tree_copy_selection_cb (GtkTreeModel *model,
 929                                GtkTreePath *path,
 930                                GtkTreeIter *iter,
 931                                GtkTreeView *tree_view)
 932 {
 933 	GtkTreeSelection *selection;
 934 
 935 	selection = gtk_tree_view_get_selection (tree_view);
 936 	gtk_tree_selection_select_path (selection, path);
 937 
 938 	/* Center the tree view on the selected path. */
 939 	gtk_tree_view_scroll_to_cell (tree_view, path, NULL, TRUE, 0.5, 0.0);
 940 }
 941 
 942 static void
 943 folder_tree_copy_state (EMFolderTree *folder_tree)
 944 {
 945 	GtkTreeSelection *selection;
 946 	GtkTreeView *tree_view;
 947 	GtkTreeModel *model;
 948 
 949 	tree_view = GTK_TREE_VIEW (folder_tree);
 950 	model = gtk_tree_view_get_model (tree_view);
 951 
 952 	selection = em_folder_tree_model_get_selection (
 953 		EM_FOLDER_TREE_MODEL (model));
 954 	if (selection == NULL)
 955 		return;
 956 
 957 	gtk_tree_view_map_expanded_rows (
 958 		tree_view, (GtkTreeViewMappingFunc)
 959 		folder_tree_copy_expanded_cb, folder_tree);
 960 
 961 	gtk_tree_selection_selected_foreach (
 962 		selection, (GtkTreeSelectionForeachFunc)
 963 		folder_tree_copy_selection_cb, folder_tree);
 964 }
 965 
 966 static void
 967 folder_tree_set_alert_sink (EMFolderTree *folder_tree,
 968                             EAlertSink *alert_sink)
 969 {
 970 	g_return_if_fail (E_IS_ALERT_SINK (alert_sink));
 971 	g_return_if_fail (folder_tree->priv->alert_sink == NULL);
 972 
 973 	folder_tree->priv->alert_sink = g_object_ref (alert_sink);
 974 }
 975 
 976 static void
 977 folder_tree_set_session (EMFolderTree *folder_tree,
 978                          EMailSession *session)
 979 {
 980 	g_return_if_fail (E_IS_MAIL_SESSION (session));
 981 	g_return_if_fail (folder_tree->priv->session == NULL);
 982 
 983 	folder_tree->priv->session = g_object_ref (session);
 984 }
 985 
 986 static GtkTargetList *
 987 folder_tree_get_copy_target_list (EMFolderTree *folder_tree)
 988 {
 989 	GtkTargetList *target_list = NULL;
 990 
 991 	if (E_IS_SELECTABLE (folder_tree->priv->selectable)) {
 992 		ESelectable *selectable;
 993 
 994 		selectable = E_SELECTABLE (folder_tree->priv->selectable);
 995 		target_list = e_selectable_get_copy_target_list (selectable);
 996 	}
 997 
 998 	return target_list;
 999 }
1000 
1001 static GtkTargetList *
1002 folder_tree_get_paste_target_list (EMFolderTree *folder_tree)
1003 {
1004 	GtkTargetList *target_list = NULL;
1005 
1006 	if (E_IS_SELECTABLE (folder_tree->priv->selectable)) {
1007 		ESelectable *selectable;
1008 
1009 		selectable = E_SELECTABLE (folder_tree->priv->selectable);
1010 		target_list = e_selectable_get_paste_target_list (selectable);
1011 	}
1012 
1013 	return target_list;
1014 }
1015 
1016 static void
1017 folder_tree_set_property (GObject *object,
1018                           guint property_id,
1019                           const GValue *value,
1020                           GParamSpec *pspec)
1021 {
1022 	switch (property_id) {
1023 		case PROP_ALERT_SINK:
1024 			folder_tree_set_alert_sink (
1025 				EM_FOLDER_TREE (object),
1026 				g_value_get_object (value));
1027 			return;
1028 
1029 		case PROP_ELLIPSIZE:
1030 			em_folder_tree_set_ellipsize (
1031 				EM_FOLDER_TREE (object),
1032 				g_value_get_enum (value));
1033 			return;
1034 
1035 		case PROP_MODEL:
1036 			gtk_tree_view_set_model (
1037 				GTK_TREE_VIEW (object),
1038 				g_value_get_object (value));
1039 			return;
1040 
1041 		case PROP_SESSION:
1042 			folder_tree_set_session (
1043 				EM_FOLDER_TREE (object),
1044 				g_value_get_object (value));
1045 			return;
1046 	}
1047 
1048 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1049 }
1050 
1051 static void
1052 folder_tree_get_property (GObject *object,
1053                           guint property_id,
1054                           GValue *value,
1055                           GParamSpec *pspec)
1056 {
1057 	switch (property_id) {
1058 		case PROP_ALERT_SINK:
1059 			g_value_set_object (
1060 				value,
1061 				em_folder_tree_get_alert_sink (
1062 				EM_FOLDER_TREE (object)));
1063 			return;
1064 
1065 		case PROP_COPY_TARGET_LIST:
1066 			g_value_set_boxed (
1067 				value,
1068 				folder_tree_get_copy_target_list (
1069 				EM_FOLDER_TREE (object)));
1070 			return;
1071 
1072 		case PROP_ELLIPSIZE:
1073 			g_value_set_enum (
1074 				value,
1075 				em_folder_tree_get_ellipsize (
1076 				EM_FOLDER_TREE (object)));
1077 			return;
1078 
1079 		case PROP_MODEL:
1080 			g_value_set_object (
1081 				value,
1082 				gtk_tree_view_get_model (
1083 				GTK_TREE_VIEW (object)));
1084 			return;
1085 
1086 		case PROP_PASTE_TARGET_LIST:
1087 			g_value_set_boxed (
1088 				value,
1089 				folder_tree_get_paste_target_list (
1090 				EM_FOLDER_TREE (object)));
1091 			return;
1092 
1093 		case PROP_SESSION:
1094 			g_value_set_object (
1095 				value,
1096 				em_folder_tree_get_session (
1097 				EM_FOLDER_TREE (object)));
1098 			return;
1099 	}
1100 
1101 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1102 }
1103 
1104 static void
1105 folder_tree_dispose (GObject *object)
1106 {
1107 	EMFolderTreePrivate *priv;
1108 	GtkTreeModel *model;
1109 
1110 	priv = EM_FOLDER_TREE_GET_PRIVATE (object);
1111 	model = gtk_tree_view_get_model (GTK_TREE_VIEW (object));
1112 
1113 	if (priv->loaded_row_id != 0) {
1114 		g_signal_handler_disconnect (model, priv->loaded_row_id);
1115 		priv->loaded_row_id = 0;
1116 	}
1117 
1118 	if (priv->autoscroll_id != 0) {
1119 		g_source_remove (priv->autoscroll_id);
1120 		priv->autoscroll_id = 0;
1121 	}
1122 
1123 	if (priv->autoexpand_id != 0) {
1124 		gtk_tree_row_reference_free (priv->autoexpand_row);
1125 		priv->autoexpand_row = NULL;
1126 
1127 		g_source_remove (priv->autoexpand_id);
1128 		priv->autoexpand_id = 0;
1129 	}
1130 
1131 	if (priv->alert_sink != NULL) {
1132 		g_object_unref (priv->alert_sink);
1133 		priv->alert_sink = NULL;
1134 	}
1135 
1136 	if (priv->session != NULL) {
1137 		g_object_unref (priv->session);
1138 		priv->session = NULL;
1139 	}
1140 
1141 	if (priv->text_renderer != NULL) {
1142 		g_object_unref (priv->text_renderer);
1143 		priv->text_renderer = NULL;
1144 	}
1145 
1146 	/* Chain up to parent's dispose() method. */
1147 	G_OBJECT_CLASS (em_folder_tree_parent_class)->dispose (object);
1148 }
1149 
1150 static void
1151 folder_tree_finalize (GObject *object)
1152 {
1153 	EMFolderTreePrivate *priv;
1154 
1155 	priv = EM_FOLDER_TREE_GET_PRIVATE (object);
1156 
1157 	if (priv->select_uris != NULL) {
1158 		g_slist_foreach (
1159 			priv->select_uris,
1160 			(GFunc) folder_tree_free_select_uri, NULL);
1161 		g_slist_free (priv->select_uris);
1162 		priv->select_uris = NULL;
1163 	}
1164 
1165 	if (priv->select_uris_table) {
1166 		g_hash_table_destroy (priv->select_uris_table);
1167 		priv->select_uris_table = NULL;
1168 	}
1169 
1170 	/* Chain up to parent's finalize() method. */
1171 	G_OBJECT_CLASS (em_folder_tree_parent_class)->finalize (object);
1172 }
1173 
1174 static void
1175 folder_tree_constructed (GObject *object)
1176 {
1177 	EMFolderTreePrivate *priv;
1178 	GtkTreeSelection *selection;
1179 	GtkTreeViewColumn *column;
1180 	GtkCellRenderer *renderer;
1181 	GtkTreeView *tree_view;
1182 	GtkTreeModel *model;
1183 	gulong handler_id;
1184 
1185 	priv = EM_FOLDER_TREE_GET_PRIVATE (object);
1186 
1187 	/* Chain up to parent's constructed() method. */
1188 	G_OBJECT_CLASS (em_folder_tree_parent_class)->constructed (object);
1189 
1190 	tree_view = GTK_TREE_VIEW (object);
1191 	model = gtk_tree_view_get_model (tree_view);
1192 	selection = gtk_tree_view_get_selection (tree_view);
1193 
1194 	handler_id = g_signal_connect (
1195 		model, "loading-row",
1196 		G_CALLBACK (folder_tree_maybe_expand_row), object);
1197 	priv->loading_row_id = handler_id;
1198 
1199 	handler_id = g_signal_connect (
1200 		model, "loaded-row",
1201 		G_CALLBACK (folder_tree_maybe_expand_row), object);
1202 	priv->loaded_row_id = handler_id;
1203 
1204 	handler_id = g_signal_connect_swapped (
1205 		selection, "changed",
1206 		G_CALLBACK (folder_tree_selection_changed_cb), object);
1207 	priv->selection_changed_handler_id = handler_id;
1208 
1209 	column = gtk_tree_view_column_new ();
1210 	gtk_tree_view_append_column (tree_view, column);
1211 
1212 	renderer = gtk_cell_renderer_pixbuf_new ();
1213 	gtk_tree_view_column_pack_start (column, renderer, FALSE);
1214 	gtk_tree_view_column_add_attribute (
1215 		column, renderer, "visible", COL_BOOL_IS_FOLDER);
1216 	gtk_tree_view_column_set_cell_data_func (
1217 		column, renderer, (GtkTreeCellDataFunc)
1218 		folder_tree_render_icon, NULL, NULL);
1219 
1220 	renderer = gtk_cell_renderer_text_new ();
1221 	gtk_tree_view_column_pack_start (column, renderer, TRUE);
1222 	gtk_tree_view_column_set_cell_data_func (
1223 		column, renderer, (GtkTreeCellDataFunc)
1224 		folder_tree_render_display_name, NULL, NULL);
1225 	priv->text_renderer = g_object_ref (renderer);
1226 
1227 	g_object_bind_property (
1228 		object, "ellipsize",
1229 		renderer, "ellipsize",
1230 		G_BINDING_SYNC_CREATE);
1231 
1232 	g_signal_connect_swapped (
1233 		renderer, "edited",
1234 		G_CALLBACK (folder_tree_cell_edited_cb), object);
1235 
1236 	gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
1237 	gtk_tree_selection_set_select_function (
1238 		selection, (GtkTreeSelectionFunc)
1239 		folder_tree_select_func, NULL, NULL);
1240 
1241 	gtk_tree_view_set_headers_visible (tree_view, FALSE);
1242 	gtk_tree_view_set_search_column (tree_view, COL_STRING_DISPLAY_NAME);
1243 
1244 	folder_tree_copy_state (EM_FOLDER_TREE (object));
1245 	gtk_widget_show (GTK_WIDGET (object));
1246 }
1247 
1248 static gboolean
1249 folder_tree_button_press_event (GtkWidget *widget,
1250                                 GdkEventButton *event)
1251 {
1252 	EMFolderTreePrivate *priv;
1253 	GtkWidgetClass *widget_class;
1254 	GtkTreeSelection *selection;
1255 	GtkTreeView *tree_view;
1256 	GtkTreePath *path;
1257 	gulong handler_id;
1258 
1259 	priv = EM_FOLDER_TREE_GET_PRIVATE (widget);
1260 
1261 	tree_view = GTK_TREE_VIEW (widget);
1262 	selection = gtk_tree_view_get_selection (tree_view);
1263 
1264 	if (gtk_tree_selection_get_mode (selection) == GTK_SELECTION_SINGLE)
1265 		folder_tree_clear_selected_list (EM_FOLDER_TREE (widget));
1266 
1267 	priv->cursor_set = TRUE;
1268 
1269 	if (event->button != 3)
1270 		goto chainup;
1271 
1272 	if (!gtk_tree_view_get_path_at_pos (
1273 		tree_view, event->x, event->y,
1274 		&path, NULL, NULL, NULL))
1275 		goto chainup;
1276 
1277 	/* Select and focus the row that was right-clicked, but prevent
1278 	 * a "folder-selected" signal emission since this does not count
1279 	 * as a folder selection in the sense we mean. */
1280 	handler_id = priv->selection_changed_handler_id;
1281 	g_signal_handler_block (selection, handler_id);
1282 	gtk_tree_selection_select_path (selection, path);
1283 	gtk_tree_view_set_cursor (tree_view, path, NULL, FALSE);
1284 	g_signal_handler_unblock (selection, handler_id);
1285 
1286 	gtk_tree_path_free (path);
1287 
1288 	folder_tree_emit_popup_event (
1289 		EM_FOLDER_TREE (tree_view), (GdkEvent *) event);
1290 
1291 chainup:
1292 
1293 	/* Chain up to parent's button_press_event() method. */
1294 	widget_class = GTK_WIDGET_CLASS (em_folder_tree_parent_class);
1295 	return widget_class->button_press_event (widget, event);
1296 }
1297 
1298 static gboolean
1299 folder_tree_key_press_event (GtkWidget *widget,
1300                              GdkEventKey *event)
1301 {
1302 	EMFolderTreePrivate *priv;
1303 	GtkWidgetClass *widget_class;
1304 	GtkTreeSelection *selection;
1305 	GtkTreeView *tree_view;
1306 
1307 	if (event && event->type == GDK_KEY_PRESS &&
1308 		(event->keyval == GDK_KEY_space ||
1309 		 event->keyval == '.' ||
1310 		 event->keyval == ',' ||
1311 		 event->keyval == '[' ||
1312 		 event->keyval == ']')) {
1313 		g_signal_emit (widget, signals[HIDDEN_KEY_EVENT], 0, event);
1314 
1315 		return TRUE;
1316 	}
1317 
1318 	priv = EM_FOLDER_TREE_GET_PRIVATE (widget);
1319 
1320 	tree_view = GTK_TREE_VIEW (widget);
1321 	selection = gtk_tree_view_get_selection (tree_view);
1322 
1323 	if (gtk_tree_selection_get_mode (selection) == GTK_SELECTION_SINGLE)
1324 		folder_tree_clear_selected_list (EM_FOLDER_TREE (widget));
1325 
1326 	priv->cursor_set = TRUE;
1327 
1328 	/* Chain up to parent's key_press_event() method. */
1329 	widget_class = GTK_WIDGET_CLASS (em_folder_tree_parent_class);
1330 	return widget_class->key_press_event (widget, event);
1331 }
1332 
1333 static gboolean
1334 folder_tree_popup_menu (GtkWidget *widget)
1335 {
1336 	folder_tree_emit_popup_event (EM_FOLDER_TREE (widget), NULL);
1337 
1338 	return TRUE;
1339 }
1340 
1341 static void
1342 folder_tree_row_activated (GtkTreeView *tree_view,
1343                            GtkTreePath *path,
1344                            GtkTreeViewColumn *column)
1345 {
1346 	EMFolderTreePrivate *priv;
1347 	GtkTreeModel *model;
1348 	gchar *folder_name;
1349 	GtkTreeIter iter;
1350 	CamelStore *store;
1351 	CamelFolderInfoFlags flags;
1352 
1353 	priv = EM_FOLDER_TREE_GET_PRIVATE (tree_view);
1354 
1355 	model = gtk_tree_view_get_model (tree_view);
1356 
1357 	if (priv->skip_double_click)
1358 		return;
1359 
1360 	if (!gtk_tree_model_get_iter (model, &iter, path))
1361 		return;
1362 
1363 	gtk_tree_model_get (
1364 		model, &iter,
1365 		COL_POINTER_CAMEL_STORE, &store,
1366 		COL_STRING_FULL_NAME, &folder_name,
1367 		COL_UINT_FLAGS, &flags, -1);
1368 
1369 	folder_tree_clear_selected_list (EM_FOLDER_TREE (tree_view));
1370 
1371 	g_signal_emit (
1372 		tree_view, signals[FOLDER_SELECTED], 0,
1373 		store, folder_name, flags);
1374 
1375 	g_signal_emit (
1376 		tree_view, signals[FOLDER_ACTIVATED], 0,
1377 		store, folder_name);
1378 
1379 	g_free (folder_name);
1380 }
1381 
1382 static gboolean
1383 folder_tree_test_collapse_row (GtkTreeView *tree_view,
1384                                GtkTreeIter *iter,
1385                                GtkTreePath *path)
1386 {
1387 	GtkTreeSelection *selection;
1388 	GtkTreeModel *model;
1389 	GtkTreeIter cursor;
1390 
1391 	selection = gtk_tree_view_get_selection (tree_view);
1392 
1393 	if (!gtk_tree_selection_get_selected (selection, &model, &cursor))
1394 		goto exit;
1395 
1396 	/* Select the collapsed node IFF it is a
1397 	 * parent of the currently selected folder. */
1398 	if (gtk_tree_store_is_ancestor (GTK_TREE_STORE (model), iter, &cursor))
1399 		gtk_tree_view_set_cursor (tree_view, path, NULL, FALSE);
1400 
1401 exit:
1402 	return FALSE;
1403 }
1404 
1405 static void
1406 folder_tree_row_expanded (GtkTreeView *tree_view,
1407                           GtkTreeIter *iter,
1408                           GtkTreePath *path)
1409 {
1410 	EActivity *activity;
1411 	GCancellable *cancellable;
1412 	EMFolderTree *folder_tree;
1413 	AsyncContext *context;
1414 	GtkTreeModel *model;
1415 	CamelStore *store;
1416 	gchar *full_name;
1417 	gboolean load;
1418 
1419 	folder_tree = EM_FOLDER_TREE (tree_view);
1420 	model = gtk_tree_view_get_model (tree_view);
1421 
1422 	gtk_tree_model_get (
1423 		model, iter,
1424 		COL_STRING_FULL_NAME, &full_name,
1425 		COL_POINTER_CAMEL_STORE, &store,
1426 		COL_BOOL_LOAD_SUBDIRS, &load, -1);
1427 
1428 	if (!load) {
1429 		g_free (full_name);
1430 		return;
1431 	}
1432 
1433 	gtk_tree_store_set (
1434 		GTK_TREE_STORE (model), iter,
1435 		COL_BOOL_LOAD_SUBDIRS, FALSE, -1);
1436 
1437 	/* Retrieve folder info asynchronously. */
1438 
1439 	activity = em_folder_tree_new_activity (folder_tree);
1440 	cancellable = e_activity_get_cancellable (activity);
1441 
1442 	context = g_slice_new0 (AsyncContext);
1443 	context->activity = activity;
1444 	context->folder_tree = g_object_ref (folder_tree);
1445 	context->root = gtk_tree_row_reference_new (model, path);
1446 	context->full_name = g_strdup (full_name);
1447 
1448 	camel_store_get_folder_info (
1449 		store, full_name,
1450 		CAMEL_STORE_FOLDER_INFO_FAST |
1451 		CAMEL_STORE_FOLDER_INFO_RECURSIVE |
1452 		CAMEL_STORE_FOLDER_INFO_SUBSCRIBED,
1453 		G_PRIORITY_DEFAULT, cancellable,
1454 		(GAsyncReadyCallback) folder_tree_get_folder_info_cb,
1455 		context);
1456 
1457 	g_free (full_name);
1458 }
1459 
1460 static void
1461 em_folder_tree_class_init (EMFolderTreeClass *class)
1462 {
1463 	GObjectClass *object_class;
1464 	GtkWidgetClass *widget_class;
1465 	GtkTreeViewClass *tree_view_class;
1466 
1467 	g_type_class_add_private (class, sizeof (EMFolderTreePrivate));
1468 
1469 	object_class = G_OBJECT_CLASS (class);
1470 	object_class->set_property = folder_tree_set_property;
1471 	object_class->get_property = folder_tree_get_property;
1472 	object_class->dispose = folder_tree_dispose;
1473 	object_class->finalize = folder_tree_finalize;
1474 	object_class->constructed = folder_tree_constructed;
1475 
1476 	widget_class = GTK_WIDGET_CLASS (class);
1477 	widget_class->button_press_event = folder_tree_button_press_event;
1478 	widget_class->key_press_event = folder_tree_key_press_event;
1479 	widget_class->popup_menu = folder_tree_popup_menu;
1480 
1481 	tree_view_class = GTK_TREE_VIEW_CLASS (class);
1482 	tree_view_class->row_activated = folder_tree_row_activated;
1483 	tree_view_class->test_collapse_row = folder_tree_test_collapse_row;
1484 	tree_view_class->row_expanded = folder_tree_row_expanded;
1485 
1486 	g_object_class_install_property (
1487 		object_class,
1488 		PROP_ALERT_SINK,
1489 		g_param_spec_object (
1490 			"alert-sink",
1491 			NULL,
1492 			NULL,
1493 			E_TYPE_ALERT_SINK,
1494 			G_PARAM_READWRITE |
1495 			G_PARAM_CONSTRUCT_ONLY |
1496 			G_PARAM_STATIC_STRINGS));
1497 
1498 	/* Inherited from ESelectableInterface */
1499 	g_object_class_override_property (
1500 		object_class,
1501 		PROP_COPY_TARGET_LIST,
1502 		"copy-target-list");
1503 
1504 	g_object_class_install_property (
1505 		object_class,
1506 		PROP_ELLIPSIZE,
1507 		g_param_spec_enum (
1508 			"ellipsize",
1509 			NULL,
1510 			NULL,
1511 			PANGO_TYPE_ELLIPSIZE_MODE,
1512 			PANGO_ELLIPSIZE_NONE,
1513 			G_PARAM_READWRITE));
1514 
1515 	/* XXX We override the GtkTreeView:model property to add
1516 	 *     G_PARAM_CONSTRUCT_ONLY so the model is set by the
1517 	 *     time we get to folder_tree_constructed(). */
1518 	g_object_class_install_property (
1519 		object_class,
1520 		PROP_MODEL,
1521 		g_param_spec_object (
1522 			"model",
1523 			"TreeView Model",
1524 			"The model for the tree view",
1525 			GTK_TYPE_TREE_MODEL,
1526 			G_PARAM_READWRITE |
1527 			G_PARAM_CONSTRUCT_ONLY));
1528 
1529 	/* Inherited from ESelectableInterface */
1530 	g_object_class_override_property (
1531 		object_class,
1532 		PROP_PASTE_TARGET_LIST,
1533 		"paste-target-list");
1534 
1535 	g_object_class_install_property (
1536 		object_class,
1537 		PROP_SESSION,
1538 		g_param_spec_object (
1539 			"session",
1540 			NULL,
1541 			NULL,
1542 			E_TYPE_MAIL_SESSION,
1543 			G_PARAM_READWRITE |
1544 			G_PARAM_CONSTRUCT_ONLY |
1545 			G_PARAM_STATIC_STRINGS));
1546 
1547 	signals[FOLDER_SELECTED] = g_signal_new (
1548 		"folder-selected",
1549 		G_OBJECT_CLASS_TYPE (object_class),
1550 		G_SIGNAL_RUN_FIRST,
1551 		G_STRUCT_OFFSET (EMFolderTreeClass, folder_selected),
1552 		NULL, NULL,
1553 		e_marshal_VOID__OBJECT_STRING_UINT,
1554 		G_TYPE_NONE, 3,
1555 		CAMEL_TYPE_STORE,
1556 		G_TYPE_STRING,
1557 		G_TYPE_UINT);
1558 
1559 	signals[FOLDER_ACTIVATED] = g_signal_new (
1560 		"folder-activated",
1561 		G_OBJECT_CLASS_TYPE (object_class),
1562 		G_SIGNAL_RUN_FIRST,
1563 		G_STRUCT_OFFSET (EMFolderTreeClass, folder_activated),
1564 		NULL, NULL,
1565 		e_marshal_VOID__OBJECT_STRING,
1566 		G_TYPE_NONE, 2,
1567 		CAMEL_TYPE_STORE,
1568 		G_TYPE_STRING);
1569 
1570 	signals[POPUP_EVENT] = g_signal_new (
1571 		"popup-event",
1572 		G_OBJECT_CLASS_TYPE (object_class),
1573 		G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
1574 		G_STRUCT_OFFSET (EMFolderTreeClass, popup_event),
1575 		NULL, NULL,
1576 		g_cclosure_marshal_VOID__BOXED,
1577 		G_TYPE_NONE, 1,
1578 		GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
1579 
1580 	signals[HIDDEN_KEY_EVENT] = g_signal_new (
1581 		"hidden-key-event",
1582 		G_OBJECT_CLASS_TYPE (object_class),
1583 		G_SIGNAL_RUN_LAST,
1584 		G_STRUCT_OFFSET (EMFolderTreeClass, hidden_key_event),
1585 		NULL, NULL,
1586 		g_cclosure_marshal_VOID__BOXED,
1587 		G_TYPE_NONE, 1,
1588 		GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
1589 }
1590 
1591 static void
1592 em_folder_tree_init (EMFolderTree *folder_tree)
1593 {
1594 	GHashTable *select_uris_table;
1595 	AtkObject *a11y;
1596 
1597 	select_uris_table = g_hash_table_new (g_str_hash, g_str_equal);
1598 
1599 	folder_tree->priv = EM_FOLDER_TREE_GET_PRIVATE (folder_tree);
1600 	folder_tree->priv->select_uris_table = select_uris_table;
1601 
1602 	/* FIXME Gross hack. */
1603 	gtk_widget_set_can_focus (GTK_WIDGET (folder_tree), TRUE);
1604 
1605 	a11y = gtk_widget_get_accessible (GTK_WIDGET (folder_tree));
1606 	atk_object_set_name (a11y, _("Mail Folder Tree"));
1607 }
1608 
1609 /* Sets a selectable widget, which will be used for update-actions and
1610  * select-all selectable interface functions. This can be NULL, then nothing
1611  * can be selected and calling selectable function does nothing. */
1612 void
1613 em_folder_tree_set_selectable_widget (EMFolderTree *folder_tree,
1614                                       GtkWidget *selectable)
1615 {
1616 	g_return_if_fail (EM_IS_FOLDER_TREE (folder_tree));
1617 
1618 	if (selectable != NULL)
1619 		g_return_if_fail (E_IS_SELECTABLE (selectable));
1620 
1621 	folder_tree->priv->selectable = selectable;
1622 }
1623 
1624 static void
1625 folder_tree_selectable_update_actions (ESelectable *selectable,
1626                                         EFocusTracker *focus_tracker,
1627                                         GdkAtom *clipboard_targets,
1628                                         gint n_clipboard_targets)
1629 {
1630 	EMFolderTree *folder_tree;
1631 
1632 	folder_tree = EM_FOLDER_TREE (selectable);
1633 	g_return_if_fail (folder_tree != NULL);
1634 
1635 	if (folder_tree->priv->selectable != NULL) {
1636 		ESelectableInterface *interface;
1637 		ESelectable *selectable;
1638 
1639 		selectable = E_SELECTABLE (folder_tree->priv->selectable);
1640 		interface = E_SELECTABLE_GET_INTERFACE (selectable);
1641 		g_return_if_fail (interface->update_actions != NULL);
1642 
1643 		interface->update_actions (
1644 			selectable, focus_tracker,
1645 			clipboard_targets, n_clipboard_targets);
1646 	}
1647 }
1648 
1649 static void
1650 folder_tree_selectable_cut_clipboard (ESelectable *selectable)
1651 {
1652 	ESelectableInterface *interface;
1653 	EMFolderTree *folder_tree;
1654 	GtkWidget *proxy;
1655 
1656 	folder_tree = EM_FOLDER_TREE (selectable);
1657 	proxy = folder_tree->priv->selectable;
1658 
1659 	if (!E_IS_SELECTABLE (proxy))
1660 		return;
1661 
1662 	interface = E_SELECTABLE_GET_INTERFACE (proxy);
1663 
1664 	if (interface->cut_clipboard == NULL)
1665 		return;
1666 
1667 	if (gtk_widget_get_can_focus (proxy))
1668 		gtk_widget_grab_focus (proxy);
1669 
1670 	interface->cut_clipboard (E_SELECTABLE (proxy));
1671 }
1672 
1673 static void
1674 folder_tree_selectable_copy_clipboard (ESelectable *selectable)
1675 {
1676 	ESelectableInterface *interface;
1677 	EMFolderTree *folder_tree;
1678 	GtkWidget *proxy;
1679 
1680 	folder_tree = EM_FOLDER_TREE (selectable);
1681 	proxy = folder_tree->priv->selectable;
1682 
1683 	if (!E_IS_SELECTABLE (proxy))
1684 		return;
1685 
1686 	interface = E_SELECTABLE_GET_INTERFACE (proxy);
1687 
1688 	if (interface->copy_clipboard == NULL)
1689 		return;
1690 
1691 	if (gtk_widget_get_can_focus (proxy))
1692 		gtk_widget_grab_focus (proxy);
1693 
1694 	interface->copy_clipboard (E_SELECTABLE (proxy));
1695 }
1696 
1697 static void
1698 folder_tree_selectable_paste_clipboard (ESelectable *selectable)
1699 {
1700 	ESelectableInterface *interface;
1701 	EMFolderTree *folder_tree;
1702 	GtkWidget *proxy;
1703 
1704 	folder_tree = EM_FOLDER_TREE (selectable);
1705 	proxy = folder_tree->priv->selectable;
1706 
1707 	if (!E_IS_SELECTABLE (proxy))
1708 		return;
1709 
1710 	interface = E_SELECTABLE_GET_INTERFACE (proxy);
1711 
1712 	if (interface->paste_clipboard == NULL)
1713 		return;
1714 
1715 	if (gtk_widget_get_can_focus (proxy))
1716 		gtk_widget_grab_focus (proxy);
1717 
1718 	interface->paste_clipboard (E_SELECTABLE (proxy));
1719 }
1720 
1721 static void
1722 folder_tree_selectable_delete_selection (ESelectable *selectable)
1723 {
1724 	ESelectableInterface *interface;
1725 	EMFolderTree *folder_tree;
1726 	GtkWidget *proxy;
1727 
1728 	folder_tree = EM_FOLDER_TREE (selectable);
1729 	proxy = folder_tree->priv->selectable;
1730 
1731 	if (!E_IS_SELECTABLE (proxy))
1732 		return;
1733 
1734 	interface = E_SELECTABLE_GET_INTERFACE (proxy);
1735 
1736 	if (interface->delete_selection == NULL)
1737 		return;
1738 
1739 	if (gtk_widget_get_can_focus (proxy))
1740 		gtk_widget_grab_focus (proxy);
1741 
1742 	interface->delete_selection (E_SELECTABLE (proxy));
1743 }
1744 
1745 static void
1746 folder_tree_selectable_select_all (ESelectable *selectable)
1747 {
1748 	ESelectableInterface *interface;
1749 	EMFolderTree *folder_tree;
1750 	GtkWidget *proxy;
1751 
1752 	folder_tree = EM_FOLDER_TREE (selectable);
1753 	proxy = folder_tree->priv->selectable;
1754 
1755 	if (!E_IS_SELECTABLE (proxy))
1756 		return;
1757 
1758 	interface = E_SELECTABLE_GET_INTERFACE (proxy);
1759 
1760 	if (interface->select_all == NULL)
1761 		return;
1762 
1763 	if (gtk_widget_get_can_focus (proxy))
1764 		gtk_widget_grab_focus (proxy);
1765 
1766 	interface->select_all (E_SELECTABLE (proxy));
1767 }
1768 
1769 static void
1770 em_folder_tree_selectable_init (ESelectableInterface *interface)
1771 {
1772 	interface->update_actions = folder_tree_selectable_update_actions;
1773 	interface->cut_clipboard = folder_tree_selectable_cut_clipboard;
1774 	interface->copy_clipboard = folder_tree_selectable_copy_clipboard;
1775 	interface->paste_clipboard = folder_tree_selectable_paste_clipboard;
1776 	interface->delete_selection = folder_tree_selectable_delete_selection;
1777 	interface->select_all = folder_tree_selectable_select_all;
1778 }
1779 
1780 GtkWidget *
1781 em_folder_tree_new (EMailSession *session,
1782                     EAlertSink *alert_sink)
1783 {
1784 	EMFolderTreeModel *model;
1785 
1786 	g_return_val_if_fail (E_IS_MAIL_SESSION (session), NULL);
1787 	g_return_val_if_fail (E_IS_ALERT_SINK (alert_sink), NULL);
1788 
1789 	model = em_folder_tree_model_get_default ();
1790 
1791 	return em_folder_tree_new_with_model (session, alert_sink, model);
1792 }
1793 
1794 GtkWidget *
1795 em_folder_tree_new_with_model (EMailSession *session,
1796                                EAlertSink *alert_sink,
1797                                EMFolderTreeModel *model)
1798 {
1799 	g_return_val_if_fail (E_IS_MAIL_SESSION (session), NULL);
1800 	g_return_val_if_fail (E_IS_ALERT_SINK (alert_sink), NULL);
1801 	g_return_val_if_fail (EM_IS_FOLDER_TREE_MODEL (model), NULL);
1802 
1803 	return g_object_new (
1804 		EM_TYPE_FOLDER_TREE,
1805 		"alert-sink", alert_sink,
1806 		"session", session,
1807 		"model", model, NULL);
1808 }
1809 
1810 EActivity *
1811 em_folder_tree_new_activity (EMFolderTree *folder_tree)
1812 {
1813 	EActivity *activity;
1814 	EMailSession *session;
1815 	EAlertSink *alert_sink;
1816 	GCancellable *cancellable;
1817 
1818 	g_return_val_if_fail (EM_IS_FOLDER_TREE (folder_tree), NULL);
1819 
1820 	activity = e_activity_new ();
1821 
1822 	alert_sink = em_folder_tree_get_alert_sink (folder_tree);
1823 	e_activity_set_alert_sink (activity, alert_sink);
1824 
1825 	cancellable = camel_operation_new ();
1826 	e_activity_set_cancellable (activity, cancellable);
1827 	g_object_unref (cancellable);
1828 
1829 	session = em_folder_tree_get_session (folder_tree);
1830 	e_mail_ui_session_add_activity (
1831 		E_MAIL_UI_SESSION (session), activity);
1832 
1833 	return activity;
1834 }
1835 
1836 PangoEllipsizeMode
1837 em_folder_tree_get_ellipsize (EMFolderTree *folder_tree)
1838 {
1839 	g_return_val_if_fail (EM_IS_FOLDER_TREE (folder_tree), 0);
1840 
1841 	return folder_tree->priv->ellipsize;
1842 }
1843 
1844 void
1845 em_folder_tree_set_ellipsize (EMFolderTree *folder_tree,
1846                               PangoEllipsizeMode ellipsize)
1847 {
1848 	g_return_if_fail (EM_IS_FOLDER_TREE (folder_tree));
1849 
1850 	if (ellipsize == folder_tree->priv->ellipsize)
1851 		return;
1852 
1853 	folder_tree->priv->ellipsize = ellipsize;
1854 
1855 	g_object_notify (G_OBJECT (folder_tree), "ellipsize");
1856 }
1857 
1858 EAlertSink *
1859 em_folder_tree_get_alert_sink (EMFolderTree *folder_tree)
1860 {
1861 	g_return_val_if_fail (EM_IS_FOLDER_TREE (folder_tree), NULL);
1862 
1863 	return folder_tree->priv->alert_sink;
1864 }
1865 
1866 EMailSession *
1867 em_folder_tree_get_session (EMFolderTree *folder_tree)
1868 {
1869 	g_return_val_if_fail (EM_IS_FOLDER_TREE (folder_tree), NULL);
1870 
1871 	return folder_tree->priv->session;
1872 }
1873 
1874 static void
1875 tree_drag_begin (GtkWidget *widget,
1876                  GdkDragContext *context,
1877                  EMFolderTree *folder_tree)
1878 {
1879 	EMFolderTreePrivate *priv = folder_tree->priv;
1880 	GtkTreeSelection *selection;
1881 	GtkTreeView *tree_view;
1882 	cairo_surface_t *s;
1883 	GtkTreeModel *model;
1884 	GtkTreePath *path;
1885 	GtkTreeIter iter;
1886 
1887 	tree_view = GTK_TREE_VIEW (widget);
1888 	selection = gtk_tree_view_get_selection (tree_view);
1889 	if (!gtk_tree_selection_get_selected (selection, &model, &iter))
1890 		return;
1891 
1892 	path = gtk_tree_model_get_path (model, &iter);
1893 	priv->drag_row = gtk_tree_row_reference_new (model, path);
1894 
1895 	s = gtk_tree_view_create_row_drag_icon (tree_view, path);
1896 	gtk_drag_set_icon_surface (context, s);
1897 
1898 	gtk_tree_path_free (path);
1899 }
1900 
1901 static void
1902 tree_drag_data_get (GtkWidget *widget,
1903                     GdkDragContext *context,
1904                     GtkSelectionData *selection,
1905                     guint info,
1906                     guint time,
1907                     EMFolderTree *folder_tree)
1908 {
1909 	EMFolderTreePrivate *priv = folder_tree->priv;
1910 	GtkTreeModel *model;
1911 	GtkTreePath *src_path;
1912 	CamelFolder *folder;
1913 	CamelStore *store;
1914 	GtkTreeIter iter;
1915 	gchar *folder_name;
1916 	gchar *folder_uri;
1917 
1918 	if (!priv->drag_row || !(src_path =
1919 		gtk_tree_row_reference_get_path (priv->drag_row)))
1920 		return;
1921 
1922 	model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_tree));
1923 
1924 	if (!gtk_tree_model_get_iter (model, &iter, src_path))
1925 		goto fail;
1926 
1927 	gtk_tree_model_get (
1928 		model, &iter,
1929 		COL_POINTER_CAMEL_STORE, &store,
1930 		COL_STRING_FULL_NAME, &folder_name, -1);
1931 
1932 	/* make sure user isn't trying to drag on a placeholder row */
1933 	if (folder_name == NULL)
1934 		goto fail;
1935 
1936 	folder_uri = e_mail_folder_uri_build (store, folder_name);
1937 
1938 	switch (info) {
1939 	case DND_DRAG_TYPE_FOLDER:
1940 		/* dragging to a new location in the folder tree */
1941 		gtk_selection_data_set (
1942 			selection, drag_atoms[info], 8,
1943 			(guchar *) folder_uri, strlen (folder_uri) + 1);
1944 		break;
1945 	case DND_DRAG_TYPE_TEXT_URI_LIST:
1946 		/* dragging to nautilus or something, probably */
1947 		/* FIXME camel_store_get_folder_sync() may block. */
1948 		if ((folder = camel_store_get_folder_sync (
1949 			store, folder_name, 0, NULL, NULL))) {
1950 
1951 			GPtrArray *uids = camel_folder_get_uids (folder);
1952 
1953 			em_utils_selection_set_urilist (selection, folder, uids);
1954 			camel_folder_free_uids (folder, uids);
1955 			g_object_unref (folder);
1956 		}
1957 		break;
1958 	default:
1959 		abort ();
1960 	}
1961 
1962 	g_free (folder_uri);
1963 
1964 fail:
1965 	gtk_tree_path_free (src_path);
1966 	g_free (folder_name);
Function call argument is an uninitialized value
(emitted by clang-analyzer)

TODO: a detailed trace is available in the data model (not yet rendered in this report)

Function call argument is an uninitialized value
(emitted by clang-analyzer)

TODO: a detailed trace is available in the data model (not yet rendered in this report)

1967 } 1968 1969 static gboolean 1970 ask_drop_folder (EMFolderTree *folder_tree, 1971 const gchar *src_folder_uri, 1972 const gchar *des_full_name, 1973 CamelStore *des_store, 1974 gboolean is_move) 1975 { 1976 const gchar *key = is_move ? "prompt-on-folder-drop-move" : "prompt-on-folder-drop-copy"; 1977 EMailSession *session; 1978 GSettings *settings; 1979 gchar *set_value, *src_folder_name = NULL; 1980 GError *error = NULL; 1981 GtkWidget *widget; 1982 GtkWindow *parent; 1983 gint response; 1984 1985 g_return_val_if_fail (folder_tree != NULL, FALSE); 1986 g_return_val_if_fail (src_folder_uri != NULL, FALSE); 1987 g_return_val_if_fail (des_full_name != NULL || des_store != NULL, FALSE); 1988 1989 settings = g_settings_new ("org.gnome.evolution.mail"); 1990 set_value = g_settings_get_string (settings, key); 1991 1992 if (g_strcmp0 (set_value, "never") == 0) { 1993 g_object_unref (settings); 1994 g_free (set_value); 1995 1996 return FALSE; 1997 } else if (g_strcmp0 (set_value, "always") == 0) { 1998 g_object_unref (settings); 1999 g_free (set_value); 2000 2001 return TRUE; 2002 } 2003 2004 g_free (set_value); 2005 2006 session = em_folder_tree_get_session (folder_tree); 2007 2008 e_mail_folder_uri_parse ( 2009 CAMEL_SESSION (session), 2010 src_folder_uri, NULL, &src_folder_name, &error); 2011 2012 if (error != NULL) { 2013 g_warning ( 2014 "%s: Failed to convert '%s' to folder name: %s", 2015 G_STRFUNC, src_folder_uri, error->message); 2016 g_object_unref (settings); 2017 g_error_free (error); 2018 2019 return FALSE; 2020 } 2021 2022 parent = NULL; 2023 widget = gtk_widget_get_toplevel (GTK_WIDGET (folder_tree)); 2024 if (widget && gtk_widget_is_toplevel (widget) && GTK_IS_WINDOW (widget)) 2025 parent = GTK_WINDOW (widget); 2026 2027 widget = e_alert_dialog_new_for_args ( 2028 parent, 2029 is_move ? "mail:ask-folder-drop-move" : "mail:ask-folder-drop-copy", 2030 src_folder_name, 2031 des_full_name && *des_full_name ? des_full_name : 2032 camel_service_get_display_name (CAMEL_SERVICE (des_store)), 2033 NULL); 2034 response = gtk_dialog_run (GTK_DIALOG (widget)); 2035 gtk_widget_destroy (widget); 2036 2037 if (response == GTK_RESPONSE_OK) 2038 g_settings_set_string (settings, key, "always"); 2039 else if (response == GTK_RESPONSE_CANCEL) 2040 g_settings_set_string (settings, key, "never"); 2041 2042 g_free (src_folder_name); 2043 g_object_unref (settings); 2044 2045 return response == GTK_RESPONSE_YES || response == GTK_RESPONSE_OK; 2046 } 2047 2048 /* Drop handling */ 2049 struct _DragDataReceivedAsync { 2050 MailMsg base; 2051 2052 /* input data */ 2053 GdkDragContext *context; 2054 2055 /* Only selection->data and selection->length are valid */ 2056 GtkSelectionData *selection; 2057 2058 EMFolderTree *folder_tree; 2059 EMailSession *session; 2060 CamelStore *store; 2061 gchar *full_name; 2062 gchar *dest_folder_uri; 2063 guint32 action; 2064 guint info; 2065 2066 guint move : 1; 2067 guint moved : 1; 2068 guint aborted : 1; 2069 }; 2070 2071 static void 2072 folder_tree_drop_folder (struct _DragDataReceivedAsync *m) 2073 { 2074 CamelFolder *folder; 2075 CamelStore *parent_store; 2076 GCancellable *cancellable; 2077 const gchar *folder_name; 2078 const gchar *full_name; 2079 const guchar *data; 2080 2081 data = gtk_selection_data_get_data (m->selection); 2082 2083 d (printf (" * Drop folder '%s' onto '%s'\n", data, m->full_name)); 2084 2085 cancellable = m->base.cancellable; 2086 2087 folder = e_mail_session_uri_to_folder_sync ( 2088 m->session, (gchar *) data, 0, 2089 cancellable, &m->base.error); 2090 if (folder == NULL) 2091 return; 2092 2093 full_name = camel_folder_get_full_name (folder); 2094 parent_store = camel_folder_get_parent_store (folder); 2095 2096 em_folder_utils_copy_folders ( 2097 parent_store, full_name, m->store, 2098 m->full_name ? m->full_name : "", m->move); 2099 2100 folder_name = strrchr (full_name, '/'); 2101 if (folder_name) 2102 folder_name++; 2103 else 2104 folder_name = full_name; 2105 2106 if (m->full_name) { 2107 gchar *dest_root_name; 2108 2109 dest_root_name = g_strconcat (m->full_name, "/", folder_name, NULL); 2110 m->dest_folder_uri = e_mail_folder_uri_build (m->store, dest_root_name); 2111 g_free (dest_root_name); 2112 } else { 2113 m->dest_folder_uri = e_mail_folder_uri_build (m->store, folder_name); 2114 } 2115 2116 g_object_unref (folder); 2117 } 2118 2119 static gchar * 2120 folder_tree_drop_async__desc (struct _DragDataReceivedAsync *m) 2121 { 2122 const guchar *data; 2123 2124 data = gtk_selection_data_get_data (m->selection); 2125 2126 if (m->info == DND_DROP_TYPE_FOLDER) { 2127 gchar *folder_name = NULL; 2128 gchar *res; 2129 2130 e_mail_folder_uri_parse ( 2131 CAMEL_SESSION (m->session), 2132 (gchar *) data, NULL, &folder_name, NULL); 2133 g_return_val_if_fail (folder_name != NULL, NULL); 2134 2135 if (m->move) 2136 res = g_strdup_printf ( 2137 _("Moving folder %s"), folder_name); 2138 else 2139 res = g_strdup_printf ( 2140 _("Copying folder %s"), folder_name); 2141 g_free (folder_name); 2142 2143 return res; 2144 } else { 2145 if (m->move) 2146 return g_strdup_printf ( 2147 _("Moving messages into folder %s"), 2148 m->full_name); 2149 else 2150 return g_strdup_printf ( 2151 _("Copying messages into folder %s"), 2152 m->full_name); 2153 } 2154 } 2155 2156 static void 2157 folder_tree_drop_async__exec (struct _DragDataReceivedAsync *m, 2158 GCancellable *cancellable, 2159 GError **error) 2160 { 2161 CamelFolder *folder; 2162 2163 /* for types other than folder, we can't drop to the root path */ 2164 if (m->info == DND_DROP_TYPE_FOLDER) { 2165 /* copy or move (aka rename) a folder */ 2166 folder_tree_drop_folder (m); 2167 } else if (m->full_name == NULL) { 2168 g_set_error ( 2169 error, CAMEL_ERROR, CAMEL_ERROR_GENERIC, 2170 _("Cannot drop message(s) into toplevel store")); 2171 } else if ((folder = camel_store_get_folder_sync ( 2172 m->store, m->full_name, 0, cancellable, error))) { 2173 2174 switch (m->info) { 2175 case DND_DROP_TYPE_UID_LIST: 2176 /* import a list of uids from another evo folder */ 2177 em_utils_selection_get_uidlist ( 2178 m->selection, m->session, folder, m->move, 2179 cancellable, error); 2180 m->moved = m->move && (!error || !*error); 2181 break; 2182 case DND_DROP_TYPE_MESSAGE_RFC822: 2183 /* import a message/rfc822 stream */ 2184 em_utils_selection_get_message (m->selection, folder); 2185 break; 2186 case DND_DROP_TYPE_TEXT_URI_LIST: 2187 /* import an mbox, maildir, or mh folder? */ 2188 em_utils_selection_get_urilist (m->selection, folder); 2189 break; 2190 default: 2191 abort (); 2192 } 2193 g_object_unref (folder); 2194 } 2195 } 2196 2197 static void 2198 folder_tree_drop_async__free (struct _DragDataReceivedAsync *m) 2199 { 2200 if (m->move && m->dest_folder_uri) { 2201 GList *selected_list; 2202 2203 selected_list = g_list_append (NULL, m->dest_folder_uri); 2204 em_folder_tree_set_selected_list (m->folder_tree, selected_list, FALSE); 2205 g_list_free (selected_list); 2206 } 2207 2208 g_object_unref (m->folder_tree); 2209 g_object_unref (m->session); 2210 g_object_unref (m->context); 2211 g_object_unref (m->store); 2212 g_free (m->full_name); 2213 g_free (m->dest_folder_uri); 2214 gtk_selection_data_free (m->selection); 2215 } 2216 2217 static MailMsgInfo folder_tree_drop_async_info = { 2218 sizeof (struct _DragDataReceivedAsync), 2219 (MailMsgDescFunc) folder_tree_drop_async__desc, 2220 (MailMsgExecFunc) folder_tree_drop_async__exec, 2221 (MailMsgDoneFunc) NULL, 2222 (MailMsgFreeFunc) folder_tree_drop_async__free 2223 }; 2224 2225 static void 2226 tree_drag_data_action (struct _DragDataReceivedAsync *m) 2227 { 2228 m->move = m->action == GDK_ACTION_MOVE; 2229 mail_msg_unordered_push (m); 2230 } 2231 2232 static void 2233 tree_drag_data_received (GtkWidget *widget, 2234 GdkDragContext *context, 2235 gint x, 2236 gint y, 2237 GtkSelectionData *selection, 2238 guint info, 2239 guint time, 2240 EMFolderTree *folder_tree) 2241 { 2242 GtkTreeViewDropPosition pos; 2243 GtkTreeModel *model; 2244 GtkTreeView *tree_view; 2245 GtkTreePath *dest_path = NULL; 2246 EMailSession *session; 2247 struct _DragDataReceivedAsync *m; 2248 gboolean is_store; 2249 CamelStore *store; 2250 GtkTreeIter iter; 2251 gchar *full_name; 2252 2253 tree_view = GTK_TREE_VIEW (folder_tree); 2254 model = gtk_tree_view_get_model (tree_view); 2255 2256 session = em_folder_tree_get_session (folder_tree); 2257 2258 if (!gtk_tree_view_get_dest_row_at_pos (tree_view, x, y, &dest_path, &pos)) 2259 return; 2260 2261 /* this means we are receiving no data */ 2262 if (gtk_selection_data_get_data (selection) == NULL) { 2263 gtk_drag_finish (context, FALSE, FALSE, GDK_CURRENT_TIME); 2264 gtk_tree_path_free (dest_path); 2265 return; 2266 } 2267 2268 if (gtk_selection_data_get_length (selection) == -1) { 2269 gtk_drag_finish (context, FALSE, FALSE, GDK_CURRENT_TIME); 2270 gtk_tree_path_free (dest_path); 2271 return; 2272 } 2273 2274 if (!gtk_tree_model_get_iter (model, &iter, dest_path)) { 2275 gtk_drag_finish (context, FALSE, FALSE, GDK_CURRENT_TIME); 2276 gtk_tree_path_free (dest_path); 2277 return; 2278 } 2279 2280 gtk_tree_model_get ( 2281 model, &iter, 2282 COL_POINTER_CAMEL_STORE, &store, 2283 COL_BOOL_IS_STORE, &is_store, 2284 COL_STRING_FULL_NAME, &full_name, -1); 2285 2286 /* make sure user isn't try to drop on a placeholder row */ 2287 if (full_name == NULL && !is_store) { 2288 gtk_drag_finish (context, FALSE, FALSE, GDK_CURRENT_TIME); 2289 gtk_tree_path_free (dest_path); 2290 return; 2291 } 2292 2293 if (info == DND_DROP_TYPE_FOLDER && 2294 !ask_drop_folder (folder_tree, 2295 (const gchar *) gtk_selection_data_get_data (selection), 2296 full_name, store, 2297 gdk_drag_context_get_selected_action (context) == GDK_ACTION_MOVE)) { 2298 gtk_drag_finish (context, FALSE, FALSE, GDK_CURRENT_TIME); 2299 gtk_tree_path_free (dest_path); 2300 g_free (full_name); 2301 return; 2302 } 2303 2304 m = mail_msg_new (&folder_tree_drop_async_info); 2305 m->folder_tree = g_object_ref (folder_tree); 2306 m->session = g_object_ref (session); 2307 m->context = g_object_ref (context); 2308 m->store = g_object_ref (store); 2309 m->full_name = full_name; 2310 m->dest_folder_uri = NULL; 2311 m->action = gdk_drag_context_get_selected_action (context); 2312 m->info = info; 2313 2314 /* need to copy, goes away once we exit */ 2315 m->selection = gtk_selection_data_copy (selection); 2316 2317 tree_drag_data_action (m); 2318 gtk_tree_path_free (dest_path); 2319 } 2320 2321 static gboolean 2322 is_special_local_folder (const gchar *name) 2323 { 2324 return strcmp (name, "Drafts") == 0 2325 || strcmp (name, "Inbox") == 0 2326 || strcmp (name, "Outbox") == 0 2327 || strcmp (name, "Sent") == 0 2328 || strcmp (name, "Templates") == 0; 2329 } 2330 2331 static GdkAtom 2332 folder_tree_drop_target (EMFolderTree *folder_tree, 2333 GdkDragContext *context, 2334 GtkTreePath *path, 2335 GdkDragAction *actions, 2336 GdkDragAction *suggested_action) 2337 { 2338 EMFolderTreePrivate *p = folder_tree->priv; 2339 gchar *dst_full_name = NULL; 2340 gchar *src_full_name = NULL; 2341 CamelStore *dst_store; 2342 CamelStore *src_store = NULL; 2343 GdkAtom atom = GDK_NONE; 2344 gboolean is_store; 2345 GtkTreeModel *model; 2346 GtkTreeIter iter; 2347 GList *targets; 2348 const gchar *uid; 2349 gboolean src_is_local; 2350 gboolean src_is_vfolder; 2351 gboolean dst_is_vfolder; 2352 guint32 flags = 0; 2353 2354 /* This is a bit of a mess, but should handle all the cases properly */ 2355 2356 model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_tree)); 2357 2358 if (!gtk_tree_model_get_iter (model, &iter, path)) 2359 return GDK_NONE; 2360 2361 /* We may override these further down. */ 2362 *actions = gdk_drag_context_get_actions (context); 2363 *suggested_action = gdk_drag_context_get_suggested_action (context); 2364 2365 gtk_tree_model_get ( 2366 model, &iter, 2367 COL_BOOL_IS_STORE, &is_store, 2368 COL_POINTER_CAMEL_STORE, &dst_store, 2369 COL_STRING_FULL_NAME, &dst_full_name, 2370 COL_UINT_FLAGS, &flags, -1); 2371 2372 uid = camel_service_get_uid (CAMEL_SERVICE (dst_store)); 2373 dst_is_vfolder = (g_strcmp0 (uid, E_MAIL_SESSION_VFOLDER_UID) == 0); 2374 2375 targets = gdk_drag_context_list_targets (context); 2376 2377 /* Check for special destinations */ 2378 2379 /* Don't allow copying/moving into the UNMATCHED vfolder. */ 2380 if (dst_is_vfolder) 2381 if (g_strcmp0 (dst_full_name, CAMEL_UNMATCHED_NAME) == 0) 2382 goto done; 2383 2384 /* Don't allow copying/moving into a vTrash folder. */ 2385 if (g_strcmp0 (dst_full_name, CAMEL_VTRASH_NAME) == 0) 2386 goto done; 2387 2388 /* Don't allow copying/moving into a vJunk folder. */ 2389 if (g_strcmp0 (dst_full_name, CAMEL_VJUNK_NAME) == 0) 2390 goto done; 2391 2392 if (flags & CAMEL_FOLDER_NOSELECT) 2393 goto done; 2394 2395 if (p->drag_row) { 2396 GtkTreePath *src_path = gtk_tree_row_reference_get_path (p->drag_row); 2397 2398 if (src_path) { 2399 guint32 src_flags = 0; 2400 2401 if (gtk_tree_model_get_iter (model, &iter, src_path)) 2402 gtk_tree_model_get ( 2403 model, &iter, 2404 COL_POINTER_CAMEL_STORE, &src_store, 2405 COL_STRING_FULL_NAME, &src_full_name, 2406 COL_UINT_FLAGS, &src_flags, -1); 2407 2408 /* can't dnd onto itself or below itself - bad things happen, 2409 * no point dragging to where we were either */ 2410 if (gtk_tree_path_compare (path, src_path) == 0 2411 || gtk_tree_path_is_descendant (path, src_path) 2412 || (gtk_tree_path_is_ancestor (path, src_path) 2413 && gtk_tree_path_get_depth (path) == 2414 gtk_tree_path_get_depth (src_path) - 1)) { 2415 gtk_tree_path_free (src_path); 2416 goto done; 2417 } 2418 2419 gtk_tree_path_free (src_path); 2420 2421 if ((src_flags & CAMEL_FOLDER_TYPE_MASK) == CAMEL_FOLDER_TYPE_INBOX || 2422 (src_flags & CAMEL_FOLDER_SYSTEM) != 0) { 2423 /* allow only copy of the Inbox and other system folders */ 2424 GdkAtom xfolder; 2425 2426 /* force copy for special local folders */ 2427 *suggested_action = GDK_ACTION_COPY; 2428 *actions = GDK_ACTION_COPY; 2429 xfolder = drop_atoms[DND_DROP_TYPE_FOLDER]; 2430 while (targets != NULL) { 2431 if (targets->data == (gpointer) xfolder) { 2432 atom = xfolder; 2433 goto done; 2434 } 2435 2436 targets = targets->next; 2437 } 2438 2439 goto done; 2440 } 2441 } 2442 } 2443 2444 /* Check for special sources, and vfolder stuff */ 2445 if (src_store != NULL && src_full_name != NULL) { 2446 2447 uid = camel_service_get_uid (CAMEL_SERVICE (src_store)); 2448 2449 src_is_local = 2450 (g_strcmp0 (uid, E_MAIL_SESSION_LOCAL_UID) == 0); 2451 src_is_vfolder = 2452 (g_strcmp0 (uid, E_MAIL_SESSION_VFOLDER_UID) == 0); 2453 2454 /* FIXME: this is a total hack, but i think all we can do at present */ 2455 /* Check for dragging from special folders which can't be moved/copied */ 2456 2457 /* Don't allow moving any of the the special local folders. */ 2458 if (src_is_local && is_special_local_folder (src_full_name)) { 2459 GdkAtom xfolder; 2460 2461 /* force copy for special local folders */ 2462 *suggested_action = GDK_ACTION_COPY; 2463 *actions = GDK_ACTION_COPY; 2464 xfolder = drop_atoms[DND_DROP_TYPE_FOLDER]; 2465 while (targets != NULL) { 2466 if (targets->data == (gpointer) xfolder) { 2467 atom = xfolder; 2468 goto done; 2469 } 2470 2471 targets = targets->next; 2472 } 2473 2474 goto done; 2475 } 2476 2477 /* Don't allow copying/moving the UNMATCHED vfolder. */ 2478 if (src_is_vfolder) 2479 if (g_strcmp0 (src_full_name, CAMEL_UNMATCHED_NAME) == 0) 2480 goto done; 2481 2482 /* Don't allow copying/moving any vTrash folder. */ 2483 if (g_strcmp0 (src_full_name, CAMEL_VTRASH_NAME) == 0) 2484 goto done; 2485 2486 /* Don't allow copying/moving any vJunk folder. */ 2487 if (g_strcmp0 (src_full_name, CAMEL_VJUNK_NAME) == 0) 2488 goto done; 2489 2490 /* Don't allow copying/moving any maildir 'inbox'. */ 2491 if (g_strcmp0 (src_full_name, ".") == 0) 2492 goto done; 2493 2494 /* Search Folders can only be dropped into other 2495 * Search Folders. */ 2496 if (src_is_vfolder) { 2497 /* force move only for vfolders */ 2498 *suggested_action = GDK_ACTION_MOVE; 2499 2500 if (dst_is_vfolder) { 2501 GdkAtom xfolder; 2502 2503 xfolder = drop_atoms[DND_DROP_TYPE_FOLDER]; 2504 while (targets != NULL) { 2505 if (targets->data == (gpointer) xfolder) { 2506 atom = xfolder; 2507 goto done; 2508 } 2509 2510 targets = targets->next; 2511 } 2512 } 2513 2514 goto done; 2515 } 2516 } 2517 2518 /* Can't drag anything but a Search Folder into a Search Folder. */ 2519 if (dst_is_vfolder) 2520 goto done; 2521 2522 /* Now we either have a store or a normal folder. */ 2523 2524 if (is_store) { 2525 GdkAtom xfolder; 2526 2527 xfolder = drop_atoms[DND_DROP_TYPE_FOLDER]; 2528 while (targets != NULL) { 2529 if (targets->data == (gpointer) xfolder) { 2530 atom = xfolder; 2531 goto done; 2532 } 2533 2534 targets = targets->next; 2535 } 2536 } else { 2537 gint i; 2538 2539 while (targets != NULL) { 2540 for (i = 0; i < NUM_DROP_TYPES; i++) { 2541 if (targets->data == (gpointer) drop_atoms[i]) { 2542 atom = drop_atoms[i]; 2543 goto done; 2544 } 2545 } 2546 2547 targets = targets->next; 2548 } 2549 } 2550 2551 done: 2552 2553 g_free (dst_full_name); 2554 g_free (src_full_name); 2555 2556 return atom; 2557 } 2558 2559 static gboolean 2560 tree_drag_drop (GtkWidget *widget, 2561 GdkDragContext *context, 2562 gint x, 2563 gint y, 2564 guint time, 2565 EMFolderTree *folder_tree) 2566 { 2567 EMFolderTreePrivate *priv = folder_tree->priv; 2568 GtkTreeViewColumn *column; 2569 GtkTreeView *tree_view; 2570 gint cell_x, cell_y; 2571 GdkDragAction actions; 2572 GdkDragAction suggested_action; 2573 GtkTreePath *path; 2574 GdkAtom target; 2575 2576 tree_view = GTK_TREE_VIEW (folder_tree); 2577 2578 if (priv->autoscroll_id != 0) { 2579 g_source_remove (priv->autoscroll_id); 2580 priv->autoscroll_id = 0; 2581 } 2582 2583 if (priv->autoexpand_id != 0) { 2584 gtk_tree_row_reference_free (priv->autoexpand_row); 2585 priv->autoexpand_row = NULL; 2586 2587 g_source_remove (priv->autoexpand_id); 2588 priv->autoexpand_id = 0; 2589 } 2590 2591 if (!gtk_tree_view_get_path_at_pos ( 2592 tree_view, x, y, &path, &column, &cell_x, &cell_y)) 2593 return FALSE; 2594 2595 target = folder_tree_drop_target ( 2596 folder_tree, context, path, 2597 &actions, &suggested_action); 2598 2599 gtk_tree_path_free (path); 2600 2601 return (target != GDK_NONE); 2602 } 2603 2604 static void 2605 tree_drag_end (GtkWidget *widget, 2606 GdkDragContext *context, 2607 EMFolderTree *folder_tree) 2608 { 2609 EMFolderTreePrivate *priv = folder_tree->priv; 2610 2611 if (priv->drag_row != NULL) { 2612 gtk_tree_row_reference_free (priv->drag_row); 2613 priv->drag_row = NULL; 2614 } 2615 2616 /* FIXME: undo anything done in drag-begin */ 2617 } 2618 2619 static void 2620 tree_drag_leave (GtkWidget *widget, 2621 GdkDragContext *context, 2622 guint time, 2623 EMFolderTree *folder_tree) 2624 { 2625 EMFolderTreePrivate *priv = folder_tree->priv; 2626 GtkTreeView *tree_view; 2627 2628 tree_view = GTK_TREE_VIEW (folder_tree); 2629 2630 if (priv->autoscroll_id != 0) { 2631 g_source_remove (priv->autoscroll_id); 2632 priv->autoscroll_id = 0; 2633 } 2634 2635 if (priv->autoexpand_id != 0) { 2636 gtk_tree_row_reference_free (priv->autoexpand_row); 2637 priv->autoexpand_row = NULL; 2638 2639 g_source_remove (priv->autoexpand_id); 2640 priv->autoexpand_id = 0; 2641 } 2642 2643 gtk_tree_view_set_drag_dest_row ( 2644 tree_view, NULL, GTK_TREE_VIEW_DROP_BEFORE); 2645 } 2646 2647 #define SCROLL_EDGE_SIZE 15 2648 2649 static gboolean 2650 tree_autoscroll (EMFolderTree *folder_tree) 2651 { 2652 GtkAdjustment *adjustment; 2653 GtkTreeView *tree_view; 2654 GdkRectangle rect; 2655 GdkWindow *window; 2656 gdouble value; 2657 gint offset, y; 2658 2659 /* Get the y pointer position relative to the treeview. */ 2660 tree_view = GTK_TREE_VIEW (folder_tree); 2661 window = gtk_tree_view_get_bin_window (tree_view); 2662 gdk_window_get_pointer (window, NULL, &y, NULL); 2663 2664 /* Rect is in coorinates relative to the scrolled window, 2665 * relative to the treeview. */ 2666 gtk_tree_view_get_visible_rect (tree_view, &rect); 2667 2668 /* Move y into the same coordinate system as rect. */ 2669 y += rect.y; 2670 2671 /* See if we are near the top edge. */ 2672 offset = y - (rect.y + 2 * SCROLL_EDGE_SIZE); 2673 if (offset > 0) { 2674 /* See if we are near the bottom edge. */ 2675 offset = y - (rect.y + rect.height - 2 * SCROLL_EDGE_SIZE); 2676 if (offset < 0) 2677 return TRUE; 2678 } 2679 2680 adjustment = gtk_tree_view_get_vadjustment (tree_view); 2681 value = gtk_adjustment_get_value (adjustment); 2682 gtk_adjustment_set_value (adjustment, MAX (value + offset, 0.0)); 2683 2684 return TRUE; 2685 } 2686 2687 static gboolean 2688 tree_autoexpand (EMFolderTree *folder_tree) 2689 { 2690 EMFolderTreePrivate *priv = folder_tree->priv; 2691 GtkTreeView *tree_view; 2692 GtkTreePath *path; 2693 2694 tree_view = GTK_TREE_VIEW (folder_tree); 2695 path = gtk_tree_row_reference_get_path (priv->autoexpand_row); 2696 gtk_tree_view_expand_row (tree_view, path, FALSE); 2697 gtk_tree_path_free (path); 2698 2699 return TRUE; 2700 } 2701 2702 static gboolean 2703 tree_drag_motion (GtkWidget *widget, 2704 GdkDragContext *context, 2705 gint x, 2706 gint y, 2707 guint time, 2708 EMFolderTree *folder_tree) 2709 { 2710 EMFolderTreePrivate *priv = folder_tree->priv; 2711 GtkTreeViewDropPosition pos; 2712 GtkTreeView *tree_view; 2713 GtkTreeModel *model; 2714 GdkDragAction actions; 2715 GdkDragAction suggested_action; 2716 GdkDragAction chosen_action = 0; 2717 GtkTreePath *path = NULL; 2718 GtkTreeIter iter; 2719 GdkAtom target; 2720 gint i; 2721 2722 tree_view = GTK_TREE_VIEW (folder_tree); 2723 model = gtk_tree_view_get_model (tree_view); 2724 2725 if (!gtk_tree_view_get_dest_row_at_pos (tree_view, x, y, &path, &pos)) 2726 return FALSE; 2727 2728 if (priv->autoscroll_id == 0) 2729 priv->autoscroll_id = g_timeout_add ( 2730 150, (GSourceFunc) tree_autoscroll, folder_tree); 2731 2732 gtk_tree_model_get_iter (model, &iter, path); 2733 2734 if (gtk_tree_model_iter_has_child (model, &iter) && 2735 !gtk_tree_view_row_expanded (tree_view, path)) { 2736 2737 if (priv->autoexpand_id != 0) { 2738 GtkTreePath *autoexpand_path; 2739 2740 autoexpand_path = gtk_tree_row_reference_get_path ( 2741 priv->autoexpand_row); 2742 if (gtk_tree_path_compare (autoexpand_path, path) != 0) { 2743 /* row changed, restart timer */ 2744 gtk_tree_row_reference_free (priv->autoexpand_row); 2745 priv->autoexpand_row = 2746 gtk_tree_row_reference_new (model, path); 2747 g_source_remove (priv->autoexpand_id); 2748 priv->autoexpand_id = g_timeout_add ( 2749 600, (GSourceFunc) 2750 tree_autoexpand, folder_tree); 2751 } 2752 2753 gtk_tree_path_free (autoexpand_path); 2754 } else { 2755 priv->autoexpand_id = g_timeout_add ( 2756 600, (GSourceFunc) 2757 tree_autoexpand, folder_tree); 2758 priv->autoexpand_row = 2759 gtk_tree_row_reference_new (model, path); 2760 } 2761 } else if (priv->autoexpand_id != 0) { 2762 gtk_tree_row_reference_free (priv->autoexpand_row); 2763 priv->autoexpand_row = NULL; 2764 2765 g_source_remove (priv->autoexpand_id); 2766 priv->autoexpand_id = 0; 2767 } 2768 2769 target = folder_tree_drop_target ( 2770 folder_tree, context, path, 2771 &actions, &suggested_action); 2772 for (i = 0; target != GDK_NONE && i < NUM_DROP_TYPES; i++) { 2773 if (drop_atoms[i] != target) 2774 continue; 2775 switch (i) { 2776 case DND_DROP_TYPE_UID_LIST: 2777 case DND_DROP_TYPE_FOLDER: 2778 chosen_action = suggested_action; 2779 if (chosen_action == GDK_ACTION_COPY && 2780 (actions & GDK_ACTION_MOVE)) 2781 chosen_action = GDK_ACTION_MOVE; 2782 gtk_tree_view_set_drag_dest_row ( 2783 tree_view, path, 2784 GTK_TREE_VIEW_DROP_INTO_OR_AFTER); 2785 break; 2786 default: 2787 gtk_tree_view_set_drag_dest_row ( 2788 tree_view, path, 2789 GTK_TREE_VIEW_DROP_INTO_OR_AFTER); 2790 chosen_action = suggested_action; 2791 break; 2792 } 2793 2794 break; 2795 } 2796 2797 gdk_drag_status (context, chosen_action, time); 2798 gtk_tree_path_free (path); 2799 2800 return chosen_action != 0; 2801 } 2802 2803 void 2804 em_folder_tree_enable_drag_and_drop (EMFolderTree *folder_tree) 2805 { 2806 GtkTreeView *tree_view; 2807 static gint setup = 0; 2808 gint i; 2809 2810 g_return_if_fail (EM_IS_FOLDER_TREE (folder_tree)); 2811 2812 tree_view = GTK_TREE_VIEW (folder_tree); 2813 2814 if (!setup) { 2815 for (i = 0; i < NUM_DRAG_TYPES; i++) 2816 drag_atoms[i] = gdk_atom_intern (drag_types[i].target, FALSE); 2817 2818 for (i = 0; i < NUM_DROP_TYPES; i++) 2819 drop_atoms[i] = gdk_atom_intern (drop_types[i].target, FALSE); 2820 2821 setup = 1; 2822 } 2823 2824 gtk_drag_source_set ( 2825 GTK_WIDGET (tree_view), GDK_BUTTON1_MASK,drag_types, 2826 NUM_DRAG_TYPES, GDK_ACTION_COPY | GDK_ACTION_MOVE); 2827 gtk_drag_dest_set ( 2828 GTK_WIDGET (tree_view), GTK_DEST_DEFAULT_ALL, drop_types, 2829 NUM_DROP_TYPES, GDK_ACTION_COPY | GDK_ACTION_MOVE); 2830 2831 g_signal_connect ( 2832 tree_view, "drag-begin", 2833 G_CALLBACK (tree_drag_begin), folder_tree); 2834 g_signal_connect ( 2835 tree_view, "drag-data-get", 2836 G_CALLBACK (tree_drag_data_get), folder_tree); 2837 g_signal_connect ( 2838 tree_view, "drag-data-received", 2839 G_CALLBACK (tree_drag_data_received), folder_tree); 2840 g_signal_connect ( 2841 tree_view, "drag-drop", 2842 G_CALLBACK (tree_drag_drop), folder_tree); 2843 g_signal_connect ( 2844 tree_view, "drag-end", 2845 G_CALLBACK (tree_drag_end), folder_tree); 2846 g_signal_connect ( 2847 tree_view, "drag-leave", 2848 G_CALLBACK (tree_drag_leave), folder_tree); 2849 g_signal_connect ( 2850 tree_view, "drag-motion", 2851 G_CALLBACK (tree_drag_motion), folder_tree); 2852 } 2853 2854 void 2855 em_folder_tree_set_excluded (EMFolderTree *folder_tree, 2856 guint32 flags) 2857 { 2858 g_return_if_fail (EM_IS_FOLDER_TREE (folder_tree)); 2859 2860 folder_tree->priv->excluded = flags; 2861 } 2862 2863 void 2864 em_folder_tree_set_excluded_func (EMFolderTree *folder_tree, 2865 EMFTExcludeFunc exclude, 2866 gpointer data) 2867 { 2868 g_return_if_fail (EM_IS_FOLDER_TREE (folder_tree)); 2869 g_return_if_fail (exclude != NULL); 2870 2871 folder_tree->priv->excluded_func = exclude; 2872 folder_tree->priv->excluded_data = data; 2873 } 2874 2875 GList * 2876 em_folder_tree_get_selected_uris (EMFolderTree *folder_tree) 2877 { 2878 GtkTreeSelection *selection; 2879 GtkTreeView *tree_view; 2880 GtkTreeModel *model; 2881 GList *list = NULL, *rows, *l; 2882 GSList *sl; 2883 2884 tree_view = GTK_TREE_VIEW (folder_tree); 2885 selection = gtk_tree_view_get_selection (tree_view); 2886 2887 /* at first, add lost uris */ 2888 for (sl = folder_tree->priv->select_uris; sl; sl = g_slist_next (sl)) { 2889 const gchar *uri; 2890 uri = ((struct _selected_uri *) sl->data)->uri; 2891 list = g_list_append (list, g_strdup (uri)); 2892 } 2893 2894 rows = gtk_tree_selection_get_selected_rows (selection, &model); 2895 for (l = rows; l; l = g_list_next (l)) { 2896 GtkTreeIter iter; 2897 GtkTreePath *path = l->data; 2898 2899 if (gtk_tree_model_get_iter (model, &iter, path)) { 2900 CamelStore *store; 2901 gchar *folder_name; 2902 2903 gtk_tree_model_get ( 2904 model, &iter, 2905 COL_POINTER_CAMEL_STORE, &store, 2906 COL_STRING_FULL_NAME, &folder_name, -1); 2907 2908 if (CAMEL_IS_STORE (store) && folder_name != NULL) { 2909 gchar *folder_uri; 2910 2911 folder_uri = e_mail_folder_uri_build ( 2912 store, folder_name); 2913 list = g_list_prepend (list, folder_uri); 2914 } 2915 2916 g_free (folder_name); 2917 } 2918 gtk_tree_path_free (path); 2919 } 2920 g_list_free (rows); 2921 2922 return g_list_reverse (list); 2923 } 2924 2925 static void 2926 get_selected_uris_path_iterate (GtkTreeModel *model, 2927 GtkTreePath *treepath, 2928 GtkTreeIter *iter, 2929 gpointer data) 2930 { 2931 GList **list = (GList **) data; 2932 gchar *full_name; 2933 2934 gtk_tree_model_get (model, iter, COL_STRING_FULL_NAME, &full_name, -1); 2935 *list = g_list_append (*list, full_name); 2936 } 2937 2938 GList * 2939 em_folder_tree_get_selected_paths (EMFolderTree *folder_tree) 2940 { 2941 GtkTreeSelection *selection; 2942 GtkTreeView *tree_view; 2943 GList *list = NULL; 2944 2945 tree_view = GTK_TREE_VIEW (folder_tree); 2946 selection = gtk_tree_view_get_selection (tree_view); 2947 2948 gtk_tree_selection_selected_foreach ( 2949 selection, get_selected_uris_path_iterate, &list); 2950 2951 return list; 2952 } 2953 2954 void 2955 em_folder_tree_set_selected_list (EMFolderTree *folder_tree, 2956 GList *list, 2957 gboolean expand_only) 2958 { 2959 EMFolderTreePrivate *priv = folder_tree->priv; 2960 EMailSession *session; 2961 2962 session = em_folder_tree_get_session (folder_tree); 2963 2964 /* FIXME: need to remove any currently selected stuff? */ 2965 if (!expand_only) 2966 folder_tree_clear_selected_list (folder_tree); 2967 2968 for (; list; list = list->next) { 2969 CamelStore *store; 2970 struct _selected_uri *u; 2971 const gchar *folder_uri; 2972 const gchar *uid; 2973 gchar *folder_name; 2974 gchar *expand_key; 2975 gchar *end; 2976 gboolean success; 2977 2978 /* This makes sure all our parents up to the root are 2979 * expanded. */ 2980 2981 folder_uri = list->data; 2982 2983 success = e_mail_folder_uri_parse ( 2984 CAMEL_SESSION (session), folder_uri, 2985 &store, &folder_name, NULL); 2986 2987 if (!success) 2988 continue; 2989 2990 uid = camel_service_get_uid (CAMEL_SERVICE (store)); 2991 expand_key = g_strdup_printf ("%s/%s", uid, folder_name); 2992 g_free (folder_name); 2993 2994 u = g_malloc0 (sizeof (*u)); 2995 u->uri = g_strdup (folder_uri); 2996 u->service = CAMEL_SERVICE (store); /* takes ownership */ 2997 u->key = g_strdup (expand_key); 2998 2999 if (!expand_only) { 3000 g_hash_table_insert ( 3001 priv->select_uris_table, u->key, u); 3002 priv->select_uris = 3003 g_slist_append (priv->select_uris, u); 3004 } 3005 3006 end = strrchr (expand_key, '/'); 3007 do { 3008 folder_tree_expand_node (expand_key, folder_tree); 3009 *end = 0; 3010 end = strrchr (expand_key, '/'); 3011 } while (end); 3012 3013 if (expand_only) 3014 folder_tree_free_select_uri (u); 3015 3016 g_free (expand_key); 3017 } 3018 } 3019 3020 #if 0 3021 static void 3022 dump_fi (CamelFolderInfo *fi, 3023 gint depth) 3024 { 3025 gint i; 3026 3027 while (fi != NULL) { 3028 for (i = 0; i < depth; i++) 3029 fputs (" ", stdout); 3030 3031 printf ("path='%s'; full_name='%s'\n", fi->path, fi->full_name); 3032 3033 if (fi->child) 3034 dump_fi (fi->child, depth + 1); 3035 3036 fi = fi->sibling; 3037 } 3038 } 3039 #endif 3040 3041 void 3042 em_folder_tree_set_selected (EMFolderTree *folder_tree, 3043 const gchar *uri, 3044 gboolean expand_only) 3045 { 3046 GList *l = NULL; 3047 3048 g_return_if_fail (EM_IS_FOLDER_TREE (folder_tree)); 3049 3050 if (uri && uri[0]) 3051 l = g_list_append (l, (gpointer) uri); 3052 3053 em_folder_tree_set_selected_list (folder_tree, l, expand_only); 3054 g_list_free (l); 3055 } 3056 3057 void 3058 em_folder_tree_select_next_path (EMFolderTree *folder_tree, 3059 gboolean skip_read_folders) 3060 { 3061 GtkTreeView *tree_view; 3062 GtkTreeSelection *selection; 3063 GtkTreeModel *model; 3064 GtkTreeIter iter, parent, child; 3065 GtkTreePath *current_path, *path = NULL; 3066 guint unread = 0; 3067 EMFolderTreePrivate *priv = folder_tree->priv; 3068 3069 g_return_if_fail (EM_IS_FOLDER_TREE (folder_tree)); 3070 3071 tree_view = GTK_TREE_VIEW (folder_tree); 3072 selection = gtk_tree_view_get_selection (tree_view); 3073 if (gtk_tree_selection_get_selected (selection, &model, &iter)) { 3074 3075 current_path = gtk_tree_model_get_path (model, &iter); 3076 3077 do { 3078 if (gtk_tree_model_iter_has_child (model, &iter)) { 3079 gtk_tree_model_iter_children (model, &child, &iter); 3080 path = gtk_tree_model_get_path (model, &child); 3081 iter = child; 3082 } else { 3083 while (1) { 3084 gboolean has_parent; 3085 3086 has_parent = gtk_tree_model_iter_parent ( 3087 model, &parent, &iter); 3088 3089 if (gtk_tree_model_iter_next (model, &iter)) { 3090 path = gtk_tree_model_get_path (model, &iter); 3091 break; 3092 } else { 3093 if (has_parent) { 3094 iter = parent; 3095 } else { 3096 /* Reached end. Wrapup*/ 3097 gtk_tree_model_get_iter_first (model, &iter); 3098 path = gtk_tree_model_get_path (model, &iter); 3099 break; 3100 } 3101 } 3102 } 3103 } 3104 gtk_tree_model_get (model, &iter, COL_UINT_UNREAD, &unread, -1); 3105 3106 /* TODO : Flags here for better options */ 3107 } while (skip_read_folders && unread <=0 && 3108 gtk_tree_path_compare (current_path, path)); 3109 } 3110 3111 if (path) { 3112 if (!gtk_tree_view_row_expanded (tree_view, path)) 3113 gtk_tree_view_expand_to_path (tree_view, path); 3114 3115 gtk_tree_selection_select_path (selection, path); 3116 3117 if (!priv->cursor_set) { 3118 gtk_tree_view_set_cursor (tree_view, path, NULL, FALSE); 3119 priv->cursor_set = TRUE; 3120 } 3121 gtk_tree_view_scroll_to_cell (tree_view, path, NULL, TRUE, 0.5f, 0.0f); 3122 } 3123 return; 3124 } 3125 3126 static gboolean 3127 folder_tree_descend (GtkTreeModel *model, 3128 GtkTreeIter *iter, 3129 GtkTreeIter *root) 3130 { 3131 GtkTreeIter parent; 3132 gint n_children; 3133 3134 /* Finds the rightmost descendant of the given root. */ 3135 3136 if (root == NULL) { 3137 n_children = gtk_tree_model_iter_n_children (model, NULL); 3138 3139 /* This will invalidate the iterator and return FALSE. */ 3140 if (n_children == 0) 3141 return gtk_tree_model_get_iter_first (model, iter); 3142 3143 gtk_tree_model_iter_nth_child ( 3144 model, &parent, NULL, n_children - 1); 3145 } else 3146 parent = *root; 3147 3148 n_children = gtk_tree_model_iter_n_children (model, &parent); 3149 3150 while (n_children > 0) { 3151 GtkTreeIter child; 3152 3153 gtk_tree_model_iter_nth_child ( 3154 model, &child, &parent, n_children - 1); 3155 3156 parent = child; 3157 3158 n_children = gtk_tree_model_iter_n_children (model, &parent); 3159 } 3160 3161 *iter = parent; 3162 3163 return TRUE; 3164 } 3165 3166 void 3167 em_folder_tree_select_prev_path (EMFolderTree *folder_tree, 3168 gboolean skip_read_folders) 3169 { 3170 GtkTreeView *tree_view; 3171 GtkTreeSelection *selection; 3172 GtkTreeModel *model; 3173 GtkTreePath *path = NULL; 3174 GtkTreePath *sentinel; 3175 GtkTreeIter iter; 3176 guint unread = 0; 3177 EMFolderTreePrivate *priv = folder_tree->priv; 3178 3179 g_return_if_fail (EM_IS_FOLDER_TREE (folder_tree)); 3180 3181 tree_view = GTK_TREE_VIEW (folder_tree); 3182 selection = gtk_tree_view_get_selection (tree_view); 3183 3184 /* Nothing selected means nothing to do. */ 3185 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) 3186 return; 3187 3188 /* This prevents us from looping over the model indefinitely, 3189 * looking for unread messages when there are none. */ 3190 sentinel = gtk_tree_model_get_path (model, &iter); 3191 3192 do { 3193 GtkTreeIter descendant; 3194 3195 if (path != NULL) 3196 gtk_tree_path_free (path); 3197 3198 path = gtk_tree_model_get_path (model, &iter); 3199 3200 if (gtk_tree_path_prev (path)) { 3201 gtk_tree_model_get_iter (model, &iter, path); 3202 folder_tree_descend (model, &descendant, &iter); 3203 3204 gtk_tree_path_free (path); 3205 path = gtk_tree_model_get_path (model, &descendant); 3206 3207 } else if (gtk_tree_path_get_depth (path) > 1) { 3208 gtk_tree_path_up (path); 3209 3210 } else { 3211 folder_tree_descend (model, &descendant, NULL); 3212 3213 gtk_tree_path_free (path); 3214 path = gtk_tree_model_get_path (model, &descendant); 3215 } 3216 3217 gtk_tree_model_get_iter (model, &iter, path); 3218 gtk_tree_model_get (model, &iter, COL_UINT_UNREAD, &unread, -1); 3219 3220 } while (skip_read_folders && unread <= 0 && 3221 gtk_tree_path_compare (path, sentinel) != 0); 3222 3223 if (!gtk_tree_view_row_expanded (tree_view, path)) 3224 gtk_tree_view_expand_to_path (tree_view, path); 3225 3226 gtk_tree_selection_select_path (selection, path); 3227 3228 if (!priv->cursor_set) { 3229 gtk_tree_view_set_cursor (tree_view, path, NULL, FALSE); 3230 priv->cursor_set = TRUE; 3231 } 3232 3233 gtk_tree_view_scroll_to_cell ( 3234 tree_view, path, NULL, TRUE, 0.5f, 0.0f); 3235 3236 gtk_tree_path_free (sentinel); 3237 gtk_tree_path_free (path); 3238 } 3239 3240 void 3241 em_folder_tree_edit_selected (EMFolderTree *folder_tree) 3242 { 3243 GtkTreeSelection *selection; 3244 GtkTreeViewColumn *column; 3245 GtkCellRenderer *renderer; 3246 GtkTreeView *tree_view; 3247 GtkTreeModel *model; 3248 GtkTreePath *path = NULL; 3249 GtkTreeIter iter; 3250 3251 g_return_if_fail (EM_IS_FOLDER_TREE (folder_tree)); 3252 3253 tree_view = GTK_TREE_VIEW (folder_tree); 3254 column = gtk_tree_view_get_column (tree_view, 0); 3255 selection = gtk_tree_view_get_selection (tree_view); 3256 renderer = folder_tree->priv->text_renderer; 3257 3258 if (gtk_tree_selection_get_selected (selection, &model, &iter)) 3259 path = gtk_tree_model_get_path (model, &iter); 3260 3261 if (path == NULL) 3262 return; 3263 3264 /* Make the text cell renderer editable, but only temporarily. 3265 * We don't want editing to be activated by simply clicking on 3266 * the folder name. Too easy for accidental edits to occur. */ 3267 g_object_set (renderer, "editable", TRUE, NULL); 3268 gtk_tree_view_expand_to_path (tree_view, path); 3269 gtk_tree_view_set_cursor_on_cell ( 3270 tree_view, path, column, renderer, TRUE); 3271 g_object_set (renderer, "editable", FALSE, NULL); 3272 3273 gtk_tree_path_free (path); 3274 } 3275 3276 gboolean 3277 em_folder_tree_get_selected (EMFolderTree *folder_tree, 3278 CamelStore **out_store, 3279 gchar **out_folder_name) 3280 { 3281 GtkTreeView *tree_view; 3282 GtkTreeSelection *selection; 3283 GtkTreeModel *model; 3284 GtkTreeIter iter; 3285 CamelStore *store = NULL; 3286 gchar *folder_name = NULL; 3287 3288 g_return_val_if_fail (EM_IS_FOLDER_TREE (folder_tree), FALSE); 3289 3290 tree_view = GTK_TREE_VIEW (folder_tree); 3291 selection = gtk_tree_view_get_selection (tree_view); 3292 3293 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) 3294 return FALSE; 3295 3296 gtk_tree_model_get ( 3297 model, &iter, 3298 COL_POINTER_CAMEL_STORE, &store, 3299 COL_STRING_FULL_NAME, &folder_name, -1); 3300 3301 /* We should always get a valid store. */ 3302 g_return_val_if_fail (CAMEL_IS_STORE (store), FALSE); 3303 3304 /* If a store is selected, the folder name will be NULL. 3305 * Treat this as though nothing is selected, so that callers 3306 * can assume a TRUE return value means a folder is selected. */ 3307 if (folder_name == NULL) 3308 return FALSE; 3309 3310 /* FIXME We really should be storing the CamelStore as a GObject 3311 * so it gets referenced. The pointer type is a relic of 3312 * days before Camel used GObject. */ 3313 if (out_store != NULL) 3314 *out_store = g_object_ref (store); 3315 3316 if (out_folder_name != NULL) 3317 *out_folder_name = folder_name; 3318 else 3319 g_free (folder_name); 3320 3321 return TRUE; 3322 } 3323 3324 gboolean 3325 em_folder_tree_store_root_selected (EMFolderTree *folder_tree, 3326 CamelStore **out_store) 3327 { 3328 GtkTreeView *tree_view; 3329 GtkTreeSelection *selection; 3330 GtkTreeModel *model; 3331 GtkTreeIter iter; 3332 CamelStore *store = NULL; 3333 gboolean is_store = FALSE; 3334 3335 g_return_val_if_fail (folder_tree != NULL, FALSE); 3336 g_return_val_if_fail (EM_IS_FOLDER_TREE (folder_tree), FALSE); 3337 3338 tree_view = GTK_TREE_VIEW (folder_tree); 3339 selection = gtk_tree_view_get_selection (tree_view); 3340 3341 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) 3342 return FALSE; 3343 3344 gtk_tree_model_get ( 3345 model, &iter, 3346 COL_POINTER_CAMEL_STORE, &store, 3347 COL_BOOL_IS_STORE, &is_store, -1); 3348 3349 /* We should always get a valid store. */ 3350 g_return_val_if_fail (CAMEL_IS_STORE (store), FALSE); 3351 3352 if (!is_store) 3353 return FALSE; 3354 3355 if (out_store != NULL) 3356 *out_store = g_object_ref (store); 3357 3358 return TRUE; 3359 } 3360 3361 gchar * 3362 em_folder_tree_get_selected_uri (EMFolderTree *folder_tree) 3363 { 3364 GtkTreeView *tree_view; 3365 GtkTreeSelection *selection; 3366 GtkTreeModel *model; 3367 GtkTreeIter iter; 3368 CamelStore *store; 3369 gchar *folder_name; 3370 gchar *folder_uri = NULL; 3371 3372 g_return_val_if_fail (EM_IS_FOLDER_TREE (folder_tree), NULL); 3373 3374 tree_view = GTK_TREE_VIEW (folder_tree); 3375 selection = gtk_tree_view_get_selection (tree_view); 3376 3377 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) 3378 return NULL; 3379 3380 gtk_tree_model_get ( 3381 model, &iter, 3382 COL_POINTER_CAMEL_STORE, &store, 3383 COL_STRING_FULL_NAME, &folder_name, -1); 3384 3385 if (CAMEL_IS_STORE (store) && folder_name != NULL) 3386 folder_uri = e_mail_folder_uri_build (store, folder_name); 3387 else if (CAMEL_IS_STORE (store)) 3388 folder_uri = e_mail_folder_uri_build (store, ""); 3389 3390 g_free (folder_name); 3391 3392 return folder_uri; 3393 } 3394 3395 CamelStore * 3396 em_folder_tree_get_selected_store (EMFolderTree *folder_tree) 3397 { 3398 GtkTreeView *tree_view; 3399 GtkTreeSelection *selection; 3400 GtkTreeModel *model; 3401 GtkTreeIter iter; 3402 CamelStore *store = NULL; 3403 3404 g_return_val_if_fail (EM_IS_FOLDER_TREE (folder_tree), NULL); 3405 3406 /* Don't use em_folder_tree_get_selected() here because we 3407 * want this to work whether a folder or store is selected. */ 3408 3409 tree_view = GTK_TREE_VIEW (folder_tree); 3410 selection = gtk_tree_view_get_selection (tree_view); 3411 3412 if (gtk_tree_selection_get_selected (selection, &model, &iter)) 3413 gtk_tree_model_get ( 3414 model, &iter, 3415 COL_POINTER_CAMEL_STORE, &store, -1); 3416 3417 return CAMEL_IS_STORE (store) ? store : NULL; 3418 } 3419 3420 void 3421 em_folder_tree_set_skip_double_click (EMFolderTree *folder_tree, 3422 gboolean skip) 3423 { 3424 folder_tree->priv->skip_double_click = skip; 3425 } 3426 3427 /* stores come first, then by uri */ 3428 static gint 3429 sort_by_store_and_uri (gconstpointer name1, 3430 gconstpointer name2) 3431 { 3432 const gchar *n1 = name1, *n2 = name2; 3433 gboolean is_store1, is_store2; 3434 3435 if (n1 == NULL || n2 == NULL) { 3436 if (n1 == n2) 3437 return 0; 3438 else 3439 return n1 ? -1 : 1; 3440 } 3441 3442 is_store1 = g_str_has_prefix (n1, "Store "); 3443 is_store2 = g_str_has_prefix (n2, "Store "); 3444 3445 if ((is_store1 || is_store2) && (!is_store1 || !is_store2)) { 3446 return is_store1 ? -1 : 1; 3447 } 3448 3449 return strcmp (n1, n2); 3450 } 3451 3452 /* restores state of a tree (collapsed/expanded) as stores in the given key_file */ 3453 void 3454 em_folder_tree_restore_state (EMFolderTree *folder_tree, 3455 GKeyFile *key_file) 3456 { 3457 EShell *shell; 3458 EMFolderTreeModel *folder_tree_model; 3459 EMailSession *session; 3460 GtkTreeModel *tree_model; 3461 GtkTreeView *tree_view; 3462 GtkTreeIter iter; 3463 gboolean valid; 3464 gchar **groups_arr; 3465 GSList *groups, *group; 3466 gboolean express_mode; 3467 gint ii; 3468 3469 /* Make sure we have a key file to restore state from. */ 3470 if (key_file == NULL) 3471 return; 3472 3473 /* XXX Pass this in. */ 3474 shell = e_shell_get_default (); 3475 express_mode = e_shell_get_express_mode (shell); 3476 3477 tree_view = GTK_TREE_VIEW (folder_tree); 3478 tree_model = gtk_tree_view_get_model (tree_view); 3479 3480 folder_tree_model = EM_FOLDER_TREE_MODEL (tree_model); 3481 session = em_folder_tree_model_get_session (folder_tree_model); 3482 g_return_if_fail (E_IS_MAIL_SESSION (session)); 3483 3484 /* Set the initial folder tree expanded state in two stages: 3485 * 3486 * 1) Iterate over the "Store" and "Folder" state file groups 3487 * and apply the "Expanded" keys where possible. 3488 * 3489 * 2) Iterate over the top-level nodes in the folder tree 3490 * (these are all stores) and expand those that have no 3491 * corresponding "Expanded" key in the state file. This 3492 * ensures that new stores are expanded by default. 3493 */ 3494 3495 /* Stage 1 */ 3496 3497 /* Collapse all so we have a clean slate. */ 3498 gtk_tree_view_collapse_all (tree_view); 3499 3500 groups_arr = g_key_file_get_groups (key_file, NULL); 3501 groups = NULL; 3502 3503 for (ii = 0; groups_arr[ii] != NULL; ii++) { 3504 groups = g_slist_prepend (groups, groups_arr[ii]); 3505 } 3506 3507 groups = g_slist_sort (groups, sort_by_store_and_uri); 3508 3509 for (group = groups; group != NULL; group = group->next) { 3510 GtkTreeRowReference *reference = NULL; 3511 CamelStore *store = NULL; 3512 const gchar *group_name = group->data; 3513 const gchar *key = STATE_KEY_EXPANDED; 3514 gchar *folder_name = NULL; 3515 gboolean expanded = FALSE; 3516 gboolean success = FALSE; 3517 3518 if (g_str_has_prefix (group_name, "Store ")) { 3519 CamelService *service; 3520 const gchar *uid = group_name + 6; 3521 3522 service = camel_session_ref_service ( 3523 CAMEL_SESSION (session), uid); 3524 if (CAMEL_IS_STORE (service)) { 3525 store = g_object_ref (service); 3526 success = TRUE; 3527 } 3528 if (service != NULL) 3529 g_object_unref (service); 3530 expanded = TRUE; 3531 3532 } else if (g_str_has_prefix (group_name, "Folder ")) { 3533 const gchar *uri = group_name + 7; 3534 3535 success = e_mail_folder_uri_parse ( 3536 CAMEL_SESSION (session), uri, 3537 &store, &folder_name, NULL); 3538 expanded = FALSE; 3539 } 3540 3541 if (g_key_file_has_key (key_file, group_name, key, NULL)) 3542 expanded = g_key_file_get_boolean ( 3543 key_file, group_name, key, NULL); 3544 3545 if (expanded && success) { 3546 EMFolderTreeModelStoreInfo *si; 3547 3548 si = em_folder_tree_model_lookup_store_info ( 3549 folder_tree_model, store); 3550 if (si != NULL) { 3551 if (folder_name != NULL) 3552 reference = g_hash_table_lookup ( 3553 si->full_hash, folder_name); 3554 else 3555 reference = si->row; 3556 } 3557 } 3558 3559 if (gtk_tree_row_reference_valid (reference)) { 3560 GtkTreePath *path; 3561 GtkTreeIter iter; 3562 3563 path = gtk_tree_row_reference_get_path (reference); 3564 gtk_tree_model_get_iter (tree_model, &iter, path); 3565 gtk_tree_view_expand_row (tree_view, path, FALSE); 3566 gtk_tree_path_free (path); 3567 } 3568 3569 if (store != NULL) 3570 g_object_unref (store); 3571 g_free (folder_name); 3572 } 3573 3574 g_slist_free (groups); 3575 g_strfreev (groups_arr); 3576 3577 /* Stage 2 */ 3578 3579 valid = gtk_tree_model_get_iter_first (tree_model, &iter); 3580 3581 while (valid) { 3582 CamelStore *store; 3583 CamelService *service; 3584 const gchar *key = STATE_KEY_EXPANDED; 3585 const gchar *uid; 3586 gboolean expand_row; 3587 gboolean built_in_store; 3588 gchar *group_name; 3589 3590 gtk_tree_model_get ( 3591 tree_model, &iter, 3592 COL_POINTER_CAMEL_STORE, &store, -1); 3593 3594 if (!CAMEL_IS_STORE (store)) 3595 goto next; 3596 3597 service = CAMEL_SERVICE (store); 3598 uid = camel_service_get_uid (service); 3599 group_name = g_strdup_printf ("Store %s", uid); 3600 3601 /* Expand stores that have no "Expanded" key. */ 3602 expand_row = !g_key_file_has_key ( 3603 key_file, group_name, key, NULL); 3604 3605 built_in_store = 3606 (g_strcmp0 (uid, E_MAIL_SESSION_LOCAL_UID) == 0) || 3607 (g_strcmp0 (uid, E_MAIL_SESSION_VFOLDER_UID) == 0); 3608 3609 if (express_mode && built_in_store) 3610 expand_row = FALSE; 3611 3612 if (expand_row) { 3613 GtkTreePath *path; 3614 3615 path = gtk_tree_model_get_path (tree_model, &iter); 3616 gtk_tree_view_expand_row (tree_view, path, FALSE); 3617 gtk_tree_path_free (path); 3618 } 3619 3620 g_free (group_name); 3621 3622 next: 3623 valid = gtk_tree_model_iter_next (tree_model, &iter); 3624 } 3625 }