| Location | Tool | Test ID | Function | Issue | 
|---|---|---|---|---|
| message-list.c:1538:4 | clang-analyzer | Value stored to 'found_re' is never read | ||
| message-list.c:1538:4 | clang-analyzer | Value stored to 'found_re' is never read | ||
| message-list.c:4773:20 | clang-analyzer | Access to field 'len' results in a dereference of a null pointer (loaded from variable 'uids') | ||
| message-list.c:4773:20 | clang-analyzer | Access to field 'len' results in a dereference of a null pointer (loaded from variable 'uids') | 
   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  *		Miguel de Icaza (miguel@ximian.com)
  18  *      Bertrand Guiheneuf (bg@aful.org)
  19  *      And just about everyone else in evolution ...
  20  *
  21  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
  22  *
  23  */
  24 
  25 #ifdef HAVE_CONFIG_H
  26 #include <config.h>
  27 #endif
  28 
  29 #include <sys/types.h>
  30 #include <sys/stat.h>
  31 #include <unistd.h>
  32 
  33 #include <string.h>
  34 #include <ctype.h>
  35 
  36 #include <glib/gi18n.h>
  37 #include <glib/gstdio.h>
  38 
  39 #include "e-util/e-icon-factory.h"
  40 #include "e-util/e-poolv.h"
  41 #include "e-util/e-util-private.h"
  42 #include "e-util/e-util.h"
  43 
  44 #include "misc/e-selectable.h"
  45 
  46 #include "shell/e-shell.h"
  47 #include "shell/e-shell-settings.h"
  48 
  49 #include "table/e-cell-checkbox.h"
  50 #include "table/e-cell-hbox.h"
  51 #include "table/e-cell-date.h"
  52 #include "table/e-cell-size.h"
  53 #include "table/e-cell-text.h"
  54 #include "table/e-cell-toggle.h"
  55 #include "table/e-cell-tree.h"
  56 #include "table/e-cell-vbox.h"
  57 #include "table/e-table-sorting-utils.h"
  58 #include "table/e-tree-memory-callbacks.h"
  59 #include "table/e-tree-memory.h"
  60 
  61 #include "libemail-utils/mail-mt.h"
  62 #include "libemail-engine/e-mail-utils.h"
  63 #include "libemail-engine/mail-config.h"
  64 #include "libemail-engine/mail-ops.h"
  65 #include "libemail-engine/mail-tools.h"
  66 
  67 #include "mail/e-mail-label-list-store.h"
  68 #include "mail/e-mail-ui-session.h"
  69 #include "mail/em-utils.h"
  70 #include "mail/message-list.h"
  71 
  72 /*#define TIMEIT */
  73 
  74 #ifdef TIMEIT
  75 #include <sys/time.h>
  76 #include <unistd.h>
  77 #endif
  78 
  79 #ifdef G_OS_WIN32
  80 #ifdef gmtime_r
  81 #undef gmtime_r
  82 #endif
  83 #ifdef localtime_r
  84 #undef localtime_r
  85 #endif
  86 
  87 /* The gmtime() and localtime() in Microsoft's C library are MT-safe */
  88 #define gmtime_r(tp,tmp) (gmtime(tp)?(*(tmp)=*gmtime(tp),(tmp)):0)
  89 #define localtime_r(tp,tmp) (localtime(tp)?(*(tmp)=*localtime(tp),(tmp)):0)
  90 #endif
  91 
  92 #define d(x)
  93 #define t(x)
  94 
  95 #define MESSAGE_LIST_GET_PRIVATE(obj) \
  96 	(G_TYPE_INSTANCE_GET_PRIVATE \
  97 	((obj), MESSAGE_LIST_TYPE, MessageListPrivate))
  98 
  99 struct _MLSelection {
 100 	GPtrArray *uids;
 101 	CamelFolder *folder;
 102 };
 103 
 104 struct _MessageListPrivate {
 105 	GtkWidget *invisible;	/* 4 selection */
 106 
 107 	EMailSession *session;
 108 
 109 	struct _MLSelection clipboard;
 110 	gboolean destroyed;
 111 
 112 	gboolean thread_latest;
 113 	gboolean any_row_changed; /* save state before regen list when this is set to true */
 114 
 115 	GtkTargetList *copy_target_list;
 116 	GtkTargetList *paste_target_list;
 117 
 118 	/* This aids in automatic message selection. */
 119 	time_t newest_read_date;
 120 	const gchar *newest_read_uid;
 121 	time_t oldest_unread_date;
 122 	const gchar *oldest_unread_uid;
 123 };
 124 
 125 enum {
 126 	PROP_0,
 127 	PROP_COPY_TARGET_LIST,
 128 	PROP_PASTE_TARGET_LIST,
 129 	PROP_SESSION
 130 };
 131 
 132 /* Forward Declarations */
 133 static void	message_list_selectable_init
 134 					(ESelectableInterface *interface);
 135 
 136 G_DEFINE_TYPE_WITH_CODE (
 137 	MessageList,
 138 	message_list,
 139 	E_TYPE_TREE,
 140 	G_IMPLEMENT_INTERFACE (
 141 		E_TYPE_SELECTABLE,
 142 		message_list_selectable_init))
 143 
 144 static struct {
 145 	const gchar *target;
 146 	GdkAtom atom;
 147 	guint32 actions;
 148 } ml_drag_info[] = {
 149 	{ "x-uid-list", NULL, GDK_ACTION_MOVE | GDK_ACTION_COPY },
 150 	{ "message/rfc822", NULL, GDK_ACTION_COPY },
 151 	{ "text/uri-list", NULL, GDK_ACTION_COPY },
 152 };
 153 
 154 enum {
 155 	DND_X_UID_LIST,		/* x-uid-list */
 156 	DND_MESSAGE_RFC822,	/* message/rfc822 */
 157 	DND_TEXT_URI_LIST	/* text/uri-list */
 158 };
 159 
 160 /* What we send */
 161 static GtkTargetEntry ml_drag_types[] = {
 162 	{ (gchar *) "x-uid-list", 0, DND_X_UID_LIST },
 163 	{ (gchar *) "text/uri-list", 0, DND_TEXT_URI_LIST },
 164 };
 165 
 166 /* What we accept */
 167 static GtkTargetEntry ml_drop_types[] = {
 168 	{ (gchar *) "x-uid-list", 0, DND_X_UID_LIST },
 169 	{ (gchar *) "message/rfc822", 0, DND_MESSAGE_RFC822 },
 170 	{ (gchar *) "text/uri-list", 0, DND_TEXT_URI_LIST },
 171 };
 172 
 173 /*
 174  * Default sizes for the ETable display
 175  *
 176  */
 177 #define N_CHARS(x) (CHAR_WIDTH * (x))
 178 
 179 #define COL_ICON_WIDTH         (16)
 180 #define COL_ATTACH_WIDTH       (16)
 181 #define COL_CHECK_BOX_WIDTH    (16)
 182 #define COL_FROM_EXPANSION     (24.0)
 183 #define COL_FROM_WIDTH_MIN     (32)
 184 #define COL_SUBJECT_EXPANSION  (30.0)
 185 #define COL_SUBJECT_WIDTH_MIN  (32)
 186 #define COL_SENT_EXPANSION     (24.0)
 187 #define COL_SENT_WIDTH_MIN     (32)
 188 #define COL_RECEIVED_EXPANSION (20.0)
 189 #define COL_RECEIVED_WIDTH_MIN (32)
 190 #define COL_TO_EXPANSION       (24.0)
 191 #define COL_TO_WIDTH_MIN       (32)
 192 #define COL_SIZE_EXPANSION     (6.0)
 193 #define COL_SIZE_WIDTH_MIN     (32)
 194 #define COL_SENDER_EXPANSION     (24.0)
 195 #define COL_SENDER_WIDTH_MIN     (32)
 196 
 197 enum {
 198 	NORMALISED_SUBJECT,
 199 	NORMALISED_FROM,
 200 	NORMALISED_TO,
 201 	NORMALISED_LAST
 202 };
 203 
 204 /* #define SMART_ADDRESS_COMPARE */
 205 
 206 #ifdef SMART_ADDRESS_COMPARE
 207 struct _EMailAddress {
 208 	ENameWestern *wname;
 209 	gchar *address;
 210 };
 211 
 212 typedef struct _EMailAddress EMailAddress;
 213 #endif /* SMART_ADDRESS_COMPARE */
 214 
 215 static void on_cursor_activated_cmd (ETree *tree, gint row, ETreePath path, gpointer user_data);
 216 static void on_selection_changed_cmd (ETree *tree, MessageList *ml);
 217 static gint on_click (ETree *tree, gint row, ETreePath path, gint col, GdkEvent *event, MessageList *list);
 218 static gchar *filter_date (time_t date);
 219 static gchar *filter_size (gint size);
 220 
 221 /* note: @changes is owned/freed by the caller */
 222 /*static void mail_do_regenerate_messagelist (MessageList *list, const gchar *search, const gchar *hideexpr, CamelFolderChangeInfo *changes);*/
 223 static void mail_regen_list (MessageList *ml, const gchar *search, const gchar *hideexpr, CamelFolderChangeInfo *changes, gboolean scroll_to_cursor);
 224 static void mail_regen_cancel (MessageList *ml);
 225 
 226 static void clear_info (gchar *key, ETreePath *node, MessageList *ml);
 227 
 228 static void	folder_changed			(CamelFolder *folder,
 229 						 CamelFolderChangeInfo *info,
 230 						 MessageList *ml);
 231 
 232 enum {
 233 	MESSAGE_SELECTED,
 234 	MESSAGE_LIST_BUILT,
 235 	LAST_SIGNAL
 236 };
 237 
 238 static guint message_list_signals[LAST_SIGNAL] = {0, };
 239 
 240 static const gchar *status_icons[] = {
 241 	"mail-unread",
 242 	"mail-read",
 243 	"mail-replied",
 244 	"mail-forward",
 245 	"stock_mail-unread-multiple",
 246 	"stock_mail-open-multiple"
 247 };
 248 
 249 static const gchar *score_icons[] = {
 250 	"stock_score-lowest",
 251 	"stock_score-lower",
 252 	"stock_score-low",
 253 	"stock_score-normal",
 254 	"stock_score-high",
 255 	"stock_score-higher",
 256 	"stock_score-highest"
 257 };
 258 
 259 static const gchar *attachment_icons[] = {
 260 	NULL,  /* empty icon */
 261 	"mail-attachment",
 262 	"stock_new-meeting"
 263 };
 264 
 265 static const gchar *flagged_icons[] = {
 266 	NULL,  /* empty icon */
 267 	"emblem-important"
 268 };
 269 
 270 static const gchar *followup_icons[] = {
 271 	NULL,  /* empty icon */
 272 	"stock_mail-flag-for-followup",
 273 	"stock_mail-flag-for-followup-done"
 274 };
 275 
 276 #ifdef SMART_ADDRESS_COMPARE
 277 static EMailAddress *
 278 e_mail_address_new (const gchar *address)
 279 {
 280 	CamelInternetAddress *cia;
 281 	EMailAddress *new;
 282 	const gchar *name = NULL, *addr = NULL;
 283 
 284 	cia = camel_internet_address_new ();
 285 	if (camel_address_unformat (CAMEL_ADDRESS (cia), address) == -1) {
 286 		g_object_unref (cia);
 287 		return NULL;
 288 	}
 289 	camel_internet_address_get (cia, 0, &name, &addr);
 290 
 291 	new = g_new (EMailAddress, 1);
 292 	new->address = g_strdup (addr);
 293 	if (name && *name) {
 294 		new->wname = e_name_western_parse (name);
 295 	} else {
 296 		new->wname = NULL;
 297 	}
 298 
 299 	g_object_unref (cia);
 300 
 301 	return new;
 302 }
 303 
 304 static void
 305 e_mail_address_free (EMailAddress *addr)
 306 {
 307 	g_return_if_fail (addr != NULL);
 308 
 309 	g_free (addr->address);
 310 	if (addr->wname)
 311 		e_name_western_free (addr->wname);
 312 	g_free (addr);
 313 }
 314 
 315 static gint
 316 e_mail_address_compare (gconstpointer address1,
 317                         gconstpointer address2)
 318 {
 319 	const EMailAddress *addr1 = address1;
 320 	const EMailAddress *addr2 = address2;
 321 	gint retval;
 322 
 323 	g_return_val_if_fail (addr1 != NULL, 1);
 324 	g_return_val_if_fail (addr2 != NULL, -1);
 325 
 326 	if (!addr1->wname && !addr2->wname) {
 327 		/* have to compare addresses, one or both don't have names */
 328 		g_return_val_if_fail (addr1->address != NULL, 1);
 329 		g_return_val_if_fail (addr2->address != NULL, -1);
 330 
 331 		return g_ascii_strcasecmp (addr1->address, addr2->address);
 332 	}
 333 
 334 	if (!addr1->wname)
 335 		return -1;
 336 	if (!addr2->wname)
 337 		return 1;
 338 
 339 	if (!addr1->wname->last && !addr2->wname->last) {
 340 		/* neither has a last name - default to address? */
 341 		/* FIXME: what do we compare next? */
 342 		g_return_val_if_fail (addr1->address != NULL, 1);
 343 		g_return_val_if_fail (addr2->address != NULL, -1);
 344 
 345 		return g_ascii_strcasecmp (addr1->address, addr2->address);
 346 	}
 347 
 348 	if (!addr1->wname->last)
 349 		return -1;
 350 	if (!addr2->wname->last)
 351 		return 1;
 352 
 353 	retval = g_ascii_strcasecmp (addr1->wname->last, addr2->wname->last);
 354 	if (retval)
 355 		return retval;
 356 
 357 	/* last names are identical - compare first names */
 358 
 359 	if (!addr1->wname->first && !addr2->wname->first)
 360 		return g_ascii_strcasecmp (addr1->address, addr2->address);
 361 
 362 	if (!addr1->wname->first)
 363 		return -1;
 364 	if (!addr2->wname->first)
 365 		return 1;
 366 
 367 	retval = g_ascii_strcasecmp (addr1->wname->first, addr2->wname->first);
 368 	if (retval)
 369 		return retval;
 370 
 371 	return g_ascii_strcasecmp (addr1->address, addr2->address);
 372 }
 373 #endif /* SMART_ADDRESS_COMPARE */
 374 
 375 static gint
 376 address_compare (gconstpointer address1,
 377                  gconstpointer address2,
 378                  gpointer cmp_cache)
 379 {
 380 #ifdef SMART_ADDRESS_COMPARE
 381 	EMailAddress *addr1, *addr2;
 382 #endif /* SMART_ADDRESS_COMPARE */
 383 	gint retval;
 384 
 385 	g_return_val_if_fail (address1 != NULL, 1);
 386 	g_return_val_if_fail (address2 != NULL, -1);
 387 
 388 #ifdef SMART_ADDRESS_COMPARE
 389 	addr1 = e_mail_address_new (address1);
 390 	addr2 = e_mail_address_new (address2);
 391 	retval = e_mail_address_compare (addr1, addr2);
 392 	e_mail_address_free (addr1);
 393 	e_mail_address_free (addr2);
 394 #else
 395 	retval = g_ascii_strcasecmp ((gchar *) address1, (gchar *) address2);
 396 #endif /* SMART_ADDRESS_COMPARE */
 397 
 398 	return retval;
 399 }
 400 
 401 static gchar *
 402 filter_size (gint size)
 403 {
 404 	gfloat fsize;
 405 
 406 	if (size < 1024) {
 407 		return g_strdup_printf ("%d", size);
 408 	} else {
 409 		fsize = ((gfloat) size) / 1024.0;
 410 		if (fsize < 1024.0) {
 411 			return g_strdup_printf ("%.2f K", fsize);
 412 		} else {
 413 			fsize /= 1024.0;
 414 			return g_strdup_printf ("%.2f M", fsize);
 415 		}
 416 	}
 417 }
 418 
 419 /* Gets the uid of the message displayed at a given view row */
 420 static const gchar *
 421 get_message_uid (MessageList *message_list,
 422                  ETreePath node)
 423 {
 424 	CamelMessageInfo *info;
 425 
 426 	g_return_val_if_fail (node != NULL, NULL);
 427 	info = e_tree_memory_node_get_data (E_TREE_MEMORY (message_list->model), node);
 428 	/* correct me if I'm wrong, but this should never be NULL, should it? */
 429 	g_return_val_if_fail (info != NULL, NULL);
 430 
 431 	return camel_message_info_uid (info);
 432 }
 433 
 434 /* Gets the CamelMessageInfo for the message displayed at the given
 435  * view row.
 436  */
 437 static CamelMessageInfo *
 438 get_message_info (MessageList *message_list,
 439                   ETreePath node)
 440 {
 441 	CamelMessageInfo *info;
 442 
 443 	g_return_val_if_fail (node != NULL, NULL);
 444 	info = e_tree_memory_node_get_data (E_TREE_MEMORY (message_list->model), node);
 445 	g_return_val_if_fail (info != NULL, NULL);
 446 
 447 	return info;
 448 }
 449 
 450 static const gchar *
 451 get_normalised_string (MessageList *message_list,
 452                        CamelMessageInfo *info,
 453                        gint col)
 454 {
 455 	const gchar *string, *str;
 456 	gchar *normalised;
 457 	EPoolv *poolv;
 458 	gint index;
 459 
 460 	switch (col) {
 461 	case COL_SUBJECT_NORM:
 462 		string = camel_message_info_subject (info);
 463 		index = NORMALISED_SUBJECT;
 464 		break;
 465 	case COL_FROM_NORM:
 466 		string = camel_message_info_from (info);
 467 		index = NORMALISED_FROM;
 468 		break;
 469 	case COL_TO_NORM:
 470 		string = camel_message_info_to (info);
 471 		index = NORMALISED_TO;
 472 		break;
 473 	default:
 474 		string = NULL;
 475 		index = NORMALISED_LAST;
 476 		g_warning ("Should not be reached\n");
 477 	}
 478 
 479 	/* slight optimisation */
 480 	if (string == NULL || string[0] == '\0')
 481 		return "";
 482 
 483 	poolv = g_hash_table_lookup (message_list->normalised_hash, camel_message_info_uid (info));
 484 	if (poolv == NULL) {
 485 		poolv = e_poolv_new (NORMALISED_LAST);
 486 		g_hash_table_insert (message_list->normalised_hash, (gchar *) camel_message_info_uid (info), poolv);
 487 	} else {
 488 		str = e_poolv_get (poolv, index);
 489 		if (*str)
 490 			return str;
 491 	}
 492 
 493 	if (col == COL_SUBJECT_NORM) {
 494 		EShell *shell = e_shell_get_default ();
 495 		gint skip_len;
 496 		const guchar *subject;
 497 		gboolean found_re = TRUE;
 498 
 499 		subject = (const guchar *) string;
 500 		while (found_re) {
 501 			found_re = em_utils_is_re_in_subject (shell, (const gchar *) subject, &skip_len) && skip_len > 0;
 502 			if (found_re)
 503 				subject += skip_len;
 504 
 505 			/* jump over any spaces */
 506 			while (*subject && isspace ((gint) *subject))
 507 				subject++;
 508 		}
 509 
 510 		/* jump over any spaces */
 511 		while (*subject && isspace ((gint) *subject))
 512 			subject++;
 513 
 514 		string = (const gchar *) subject;
 515 		normalised = g_utf8_collate_key (string, -1);
 516 	} else {
 517 		/* because addresses require strings, not collate keys */
 518 		normalised = g_strdup (string);
 519 	}
 520 
 521 	e_poolv_set (poolv, index, normalised, TRUE);
 522 
 523 	return e_poolv_get (poolv, index);
 524 }
 525 
 526 static void
 527 clear_selection (MessageList *ml,
 528                  struct _MLSelection *selection)
 529 {
 530 	if (selection->uids) {
 531 		em_utils_uids_free (selection->uids);
 532 		selection->uids = NULL;
 533 	}
 534 	if (selection->folder) {
 535 		g_object_unref (selection->folder);
 536 		selection->folder = NULL;
 537 	}
 538 }
 539 
 540 static ETreePath
 541 ml_search_forward (MessageList *ml,
 542                    gint start,
 543                    gint end,
 544                    guint32 flags,
 545                    guint32 mask)
 546 {
 547 	ETreePath path;
 548 	gint row;
 549 	CamelMessageInfo *info;
 550 	ETreeTableAdapter *etta;
 551 
 552 	etta = e_tree_get_table_adapter (E_TREE (ml));
 553 
 554 	for (row = start; row <= end; row++) {
 555 		path = e_tree_table_adapter_node_at_row (etta, row);
 556 		if (path
 557 		    && (info = get_message_info (ml, path))
 558 		    && (camel_message_info_flags (info) & mask) == flags)
 559 			return path;
 560 	}
 561 
 562 	return NULL;
 563 }
 564 
 565 static ETreePath
 566 ml_search_backward (MessageList *ml,
 567                     gint start,
 568                     gint end,
 569                     guint32 flags,
 570                     guint32 mask)
 571 {
 572 	ETreePath path;
 573 	gint row;
 574 	CamelMessageInfo *info;
 575 	ETreeTableAdapter *etta;
 576 
 577 	etta = e_tree_get_table_adapter (E_TREE (ml));
 578 
 579 	for (row = start; row >= end; row--) {
 580 		path = e_tree_table_adapter_node_at_row (etta, row);
 581 		if (path
 582 		    && (info = get_message_info (ml, path))
 583 		    && (camel_message_info_flags (info) & mask) == flags)
 584 			return path;
 585 	}
 586 
 587 	return NULL;
 588 }
 589 
 590 static ETreePath
 591 ml_search_path (MessageList *ml,
 592                 MessageListSelectDirection direction,
 593                 guint32 flags,
 594                 guint32 mask)
 595 {
 596 	ETreePath node;
 597 	gint row, count;
 598 	ETreeTableAdapter *etta;
 599 
 600 	etta = e_tree_get_table_adapter (E_TREE (ml));
 601 
 602 	if (ml->cursor_uid == NULL
 603 	    || (node = g_hash_table_lookup (ml->uid_nodemap, ml->cursor_uid)) == NULL)
 604 		return NULL;
 605 
 606 	row = e_tree_table_adapter_row_of_node (etta, node);
 607 	if (row == -1)
 608 		return NULL;
 609 	count = e_table_model_row_count ((ETableModel *) etta);
 610 
 611 	if ((direction & MESSAGE_LIST_SELECT_DIRECTION) == MESSAGE_LIST_SELECT_NEXT)
 612 		node = ml_search_forward (ml, row + 1, count - 1, flags, mask);
 613 	else
 614 		node = ml_search_backward (ml, row - 1, 0, flags, mask);
 615 
 616 	if (node == NULL && (direction & MESSAGE_LIST_SELECT_WRAP)) {
 617 		if ((direction & MESSAGE_LIST_SELECT_DIRECTION) == MESSAGE_LIST_SELECT_NEXT)
 618 			node = ml_search_forward (ml, 0, row, flags, mask);
 619 		else
 620 			node = ml_search_backward (ml, count - 1, row, flags, mask);
 621 	}
 622 
 623 	return node;
 624 }
 625 
 626 static void
 627 select_path (MessageList *ml,
 628              ETreePath path)
 629 {
 630 	ETree *tree;
 631 	ETreeTableAdapter *etta;
 632 	ETreeSelectionModel *etsm;
 633 
 634 	tree = E_TREE (ml);
 635 	etta = e_tree_get_table_adapter (tree);
 636 	etsm = (ETreeSelectionModel *) e_tree_get_selection_model (tree);
 637 
 638 	g_free (ml->cursor_uid);
 639 	ml->cursor_uid = NULL;
 640 
 641 	e_tree_table_adapter_show_node (etta, path);
 642 	e_tree_set_cursor (tree, path);
 643 	e_tree_selection_model_select_single_path (etsm, path);
 644 }
 645 
 646 /**
 647  * message_list_select:
 648  * @message_list: a MessageList
 649  * @direction: the direction to search in
 650  * @flags: a set of flag values
 651  * @mask: a mask for comparing against @flags
 652  *
 653  * This moves the message list selection to a suitable row. @flags and
 654  * @mask combine to specify what constitutes a suitable row. @direction is
 655  * %MESSAGE_LIST_SELECT_NEXT if it should find the next matching
 656  * message, or %MESSAGE_LIST_SELECT_PREVIOUS if it should find the
 657  * previous. %MESSAGE_LIST_SELECT_WRAP is an option bit which specifies the
 658  * search should wrap.
 659  *
 660  * If no suitable row is found, the selection will be
 661  * unchanged.
 662  *
 663  * Returns %TRUE if a new message has been selected or %FALSE otherwise.
 664  **/
 665 gboolean
 666 message_list_select (MessageList *ml,
 667                      MessageListSelectDirection direction,
 668                      guint32 flags,
 669                      guint32 mask)
 670 {
 671 	ETreePath path;
 672 
 673 	path = ml_search_path (ml, direction, flags, mask);
 674 	if (path) {
 675 		select_path (ml, path);
 676 
 677 		/* This function is usually called in response to a key
 678 		 * press, so grab focus if the message list is visible. */
 679 		if (gtk_widget_get_visible (GTK_WIDGET (ml)))
 680 			gtk_widget_grab_focus (GTK_WIDGET (ml));
 681 
 682 		return TRUE;
 683 	} else
 684 		return FALSE;
 685 }
 686 
 687 /**
 688  * message_list_can_select:
 689  * @ml:
 690  * @direction:
 691  * @flags:
 692  * @mask:
 693  *
 694  * Returns true if the selection specified is possible with the current view.
 695  *
 696  * Return value:
 697  **/
 698 gboolean
 699 message_list_can_select (MessageList *ml,
 700                          MessageListSelectDirection direction,
 701                          guint32 flags,
 702                          guint32 mask)
 703 {
 704 	return ml_search_path (ml, direction, flags, mask) != NULL;
 705 }
 706 
 707 /**
 708  * message_list_select_uid:
 709  * @message_list:
 710  * @uid:
 711  *
 712  * Selects the message with the given UID.
 713  **/
 714 void
 715 message_list_select_uid (MessageList *message_list,
 716                          const gchar *uid,
 717                          gboolean with_fallback)
 718 {
 719 	MessageListPrivate *priv;
 720 	GHashTable *uid_nodemap;
 721 	ETreePath node = NULL;
 722 
 723 	g_return_if_fail (IS_MESSAGE_LIST (message_list));
 724 
 725 	priv = message_list->priv;
 726 	uid_nodemap = message_list->uid_nodemap;
 727 
 728 	if (message_list->folder == NULL)
 729 		return;
 730 
 731 	/* Try to find the requested message UID. */
 732 	if (uid != NULL)
 733 		node = g_hash_table_lookup (uid_nodemap, uid);
 734 
 735 	/* If we're busy or waiting to regenerate the message list, cache
 736 	 * the UID so we can try again when we're done.  Otherwise if the
 737 	 * requested message UID was not found and 'with_fallback' is set,
 738 	 * try a couple fallbacks:
 739 	 *
 740 	 * 1) Oldest unread message in the list, by date received.
 741 	 * 2) Newest read message in the list, by date received.
 742 	 */
 743 	if (message_list->regen || message_list->regen_timeout_id) {
 744 		g_free (message_list->pending_select_uid);
 745 		message_list->pending_select_uid = g_strdup (uid);
 746 		message_list->pending_select_fallback = with_fallback;
 747 	} else if (with_fallback) {
 748 		if (node == NULL && priv->oldest_unread_uid != NULL)
 749 			node = g_hash_table_lookup (
 750 				uid_nodemap, priv->oldest_unread_uid);
 751 		if (node == NULL && priv->newest_read_uid != NULL)
 752 			node = g_hash_table_lookup (
 753 				uid_nodemap, priv->newest_read_uid);
 754 	}
 755 
 756 	if (node) {
 757 		ETree *tree;
 758 		ETreePath old_cur;
 759 
 760 		tree = E_TREE (message_list);
 761 		old_cur = e_tree_get_cursor (tree);
 762 
 763 		/* This will emit a changed signal that we'll pick up */
 764 		e_tree_set_cursor (tree, node);
 765 
 766 		if (old_cur == node)
 767 			g_signal_emit (
 768 				message_list,
 769 				message_list_signals[MESSAGE_SELECTED],
 770 				0, message_list->cursor_uid);
 771 	} else {
 772 		g_free (message_list->cursor_uid);
 773 		message_list->cursor_uid = NULL;
 774 		g_signal_emit (
 775 			message_list,
 776 			message_list_signals[MESSAGE_SELECTED],
 777 			0, NULL);
 778 	}
 779 }
 780 
 781 void
 782 message_list_select_next_thread (MessageList *ml)
 783 {
 784 	ETreePath node;
 785 	ETreeTableAdapter *etta;
 786 	gint i, count, row;
 787 
 788 	etta = e_tree_get_table_adapter (E_TREE (ml));
 789 
 790 	if (!ml->cursor_uid
 791 	    || (node = g_hash_table_lookup (ml->uid_nodemap, ml->cursor_uid)) == NULL)
 792 		return;
 793 
 794 	row = e_tree_table_adapter_row_of_node (etta, node);
 795 	if (row == -1)
 796 		return;
 797 	count = e_table_model_row_count ((ETableModel *) etta);
 798 
 799 	/* find the next node which has a root parent (i.e. toplevel node) */
 800 	for (i = row + 1; i < count - 1; i++) {
 801 		node = e_tree_table_adapter_node_at_row (etta, i);
 802 		if (node
 803 		    && e_tree_model_node_is_root (ml->model, e_tree_model_node_get_parent (ml->model, node))) {
 804 			select_path (ml, node);
 805 			return;
 806 		}
 807 	}
 808 }
 809 
 810 void
 811 message_list_select_prev_thread (MessageList *ml)
 812 {
 813 	ETreePath node;
 814 	ETreeTableAdapter *etta;
 815 	gint i, row;
 816 	gboolean skip_first;
 817 
 818 	etta = e_tree_get_table_adapter (E_TREE (ml));
 819 
 820 	if (!ml->cursor_uid
 821 	    || (node = g_hash_table_lookup (ml->uid_nodemap, ml->cursor_uid)) == NULL)
 822 		return;
 823 
 824 	row = e_tree_table_adapter_row_of_node (etta, node);
 825 	if (row == -1)
 826 		return;
 827 
 828 	/* skip first found if in the middle of the thread */
 829 	skip_first = !e_tree_model_node_is_root (ml->model, e_tree_model_node_get_parent (ml->model, node));
 830 
 831 	/* find the previous node which has a root parent (i.e. toplevel node) */
 832 	for (i = row - 1; i >= 0; i--) {
 833 		node = e_tree_table_adapter_node_at_row (etta, i);
 834 		if (node
 835 		    && e_tree_model_node_is_root (ml->model, e_tree_model_node_get_parent (ml->model, node))) {
 836 			if (skip_first) {
 837 				skip_first = FALSE;
 838 				continue;
 839 			}
 840 
 841 			select_path (ml, node);
 842 			return;
 843 		}
 844 	}
 845 }
 846 
 847 static gboolean
 848 message_list_select_all_timeout_cb (MessageList *message_list)
 849 {
 850 	ESelectionModel *etsm;
 851 
 852 	etsm = e_tree_get_selection_model (E_TREE (message_list));
 853 
 854 	e_selection_model_select_all (etsm);
 855 
 856 	return FALSE;
 857 }
 858 
 859 /**
 860  * message_list_select_all:
 861  * @message_list: Message List widget
 862  *
 863  * Selects all messages in the message list.
 864  **/
 865 void
 866 message_list_select_all (MessageList *message_list)
 867 {
 868 	g_return_if_fail (IS_MESSAGE_LIST (message_list));
 869 
 870 	if (message_list->threaded && message_list->regen_timeout_id) {
 871 		/* XXX The timeout below is added so that the execution
 872 		 *     thread to expand all conversation threads would
 873 		 *     have completed.  The timeout 505 is just to ensure
 874 		 *     that the value is a small delta more than the
 875 		 *     timeout value in mail_regen_list(). */
 876 		g_timeout_add (
 877 			55, (GSourceFunc)
 878 			message_list_select_all_timeout_cb,
 879 			message_list);
 880 	} else
 881 		/* If there is no threading, just select all immediately. */
 882 		message_list_select_all_timeout_cb (message_list);
 883 }
 884 
 885 typedef struct thread_select_info {
 886 	MessageList *ml;
 887 	GPtrArray *paths;
 888 } thread_select_info_t;
 889 
 890 static gboolean
 891 select_node (ETreeModel *model,
 892              ETreePath path,
 893              gpointer user_data)
 894 {
 895 	thread_select_info_t *tsi = (thread_select_info_t *) user_data;
 896 
 897 	g_ptr_array_add (tsi->paths, path);
 898 	return FALSE; /*not done yet */
 899 }
 900 
 901 static void
 902 select_thread (MessageList *message_list,
 903                void (*selector) (ETreePath,
 904                                  gpointer))
 905 {
 906 	ETree *tree;
 907 	ETreeSelectionModel *etsm;
 908 	thread_select_info_t tsi;
 909 
 910 	tsi.ml = message_list;
 911 	tsi.paths = g_ptr_array_new ();
 912 
 913 	tree = E_TREE (message_list);
 914 	etsm = (ETreeSelectionModel *) e_tree_get_selection_model (tree);
 915 
 916 	e_tree_selected_path_foreach (tree, selector, &tsi);
 917 
 918 	e_tree_selection_model_select_paths (etsm, tsi.paths);
 919 
 920 	g_ptr_array_free (tsi.paths, TRUE);
 921 }
 922 
 923 static void
 924 thread_select_foreach (ETreePath path,
 925                        gpointer user_data)
 926 {
 927 	thread_select_info_t *tsi = (thread_select_info_t *) user_data;
 928 	ETreeModel *model = tsi->ml->model;
 929 	ETreePath node, last;
 930 
 931 	node = path;
 932 
 933 	do {
 934 		last = node;
 935 		node = e_tree_model_node_get_parent (model, node);
 936 	} while (!e_tree_model_node_is_root (model, node));
 937 
 938 	g_ptr_array_add (tsi->paths, last);
 939 
 940 	e_tree_model_node_traverse (model, last, select_node, tsi);
 941 }
 942 
 943 /**
 944  * message_list_select_thread:
 945  * @message_list: Message List widget
 946  *
 947  * Selects all messages in the current thread (based on cursor).
 948  **/
 949 void
 950 message_list_select_thread (MessageList *message_list)
 951 {
 952 	select_thread (message_list, thread_select_foreach);
 953 }
 954 
 955 static void
 956 subthread_select_foreach (ETreePath path,
 957                           gpointer user_data)
 958 {
 959 	thread_select_info_t *tsi = (thread_select_info_t *) user_data;
 960 	ETreeModel *model = tsi->ml->model;
 961 
 962 	e_tree_model_node_traverse (model, path, select_node, tsi);
 963 }
 964 
 965 /**
 966  * message_list_select_subthread:
 967  * @message_list: Message List widget
 968  *
 969  * Selects all messages in the current subthread (based on cursor).
 970  **/
 971 void
 972 message_list_select_subthread (MessageList *message_list)
 973 {
 974 	select_thread (message_list, subthread_select_foreach);
 975 }
 976 
 977 /**
 978  * message_list_invert_selection:
 979  * @message_list: Message List widget
 980  *
 981  * Invert the current selection in the message-list.
 982  **/
 983 void
 984 message_list_invert_selection (MessageList *message_list)
 985 {
 986 	ESelectionModel *etsm;
 987 
 988 	etsm = e_tree_get_selection_model (E_TREE (message_list));
 989 
 990 	e_selection_model_invert_selection (etsm);
 991 }
 992 
 993 void
 994 message_list_copy (MessageList *ml,
 995                    gboolean cut)
 996 {
 997 	MessageListPrivate *p = ml->priv;
 998 	GPtrArray *uids;
 999 
1000 	clear_selection (ml, &p->clipboard);
1001 
1002 	uids = message_list_get_selected (ml);
1003 
1004 	if (uids->len > 0) {
1005 		if (cut) {
1006 			gint i;
1007 
1008 			camel_folder_freeze (ml->folder);
1009 			for (i = 0; i < uids->len; i++)
1010 				camel_folder_set_message_flags (
1011 					ml->folder, uids->pdata[i],
1012 					CAMEL_MESSAGE_SEEN |
1013 					CAMEL_MESSAGE_DELETED,
1014 					CAMEL_MESSAGE_SEEN |
1015 					CAMEL_MESSAGE_DELETED);
1016 
1017 			camel_folder_thaw (ml->folder);
1018 		}
1019 
1020 		p->clipboard.uids = uids;
1021 		p->clipboard.folder = g_object_ref (ml->folder);
1022 		gtk_selection_owner_set (p->invisible, GDK_SELECTION_CLIPBOARD, gtk_get_current_event_time ());
1023 	} else {
1024 		em_utils_uids_free (uids);
1025 		gtk_selection_owner_set (NULL, GDK_SELECTION_CLIPBOARD, gtk_get_current_event_time ());
1026 	}
1027 }
1028 
1029 void
1030 message_list_paste (MessageList *ml)
1031 {
1032 	gtk_selection_convert (
1033 		ml->priv->invisible, GDK_SELECTION_CLIPBOARD,
1034 		gdk_atom_intern ("x-uid-list", FALSE),
1035 		GDK_CURRENT_TIME);
1036 }
1037 
1038 /*
1039  * SimpleTableModel::col_count
1040  */
1041 static gint
1042 ml_column_count (ETreeModel *etm,
1043                  gpointer data)
1044 {
1045 	return COL_LAST;
1046 }
1047 
1048 /*
1049  * SimpleTableModel::has_save_id
1050  */
1051 static gboolean
1052 ml_has_save_id (ETreeModel *etm,
1053                 gpointer data)
1054 {
1055 	return TRUE;
1056 }
1057 
1058 /*
1059  * SimpleTableModel::get_save_id
1060  */
1061 static gchar *
1062 ml_get_save_id (ETreeModel *etm,
1063                 ETreePath path,
1064                 gpointer data)
1065 {
1066 	CamelMessageInfo *info;
1067 
1068 	if (e_tree_model_node_is_root (etm, path))
1069 		return g_strdup ("root");
1070 
1071 	/* Note: etable can ask for the save_id while we're clearing it,
1072 	 * which is the only time data should be null */
1073 	info = e_tree_memory_node_get_data (E_TREE_MEMORY (etm), path);
1074 	if (info == NULL)
1075 		return NULL;
1076 
1077 	return g_strdup (camel_message_info_uid (info));
1078 }
1079 
1080 /*
1081  * SimpleTableModel::has_save_id
1082  */
1083 static gboolean
1084 ml_has_get_node_by_id (ETreeModel *etm,
1085                        gpointer data)
1086 {
1087 	return TRUE;
1088 }
1089 
1090 /*
1091  * SimpleTableModel::get_save_id
1092  */
1093 static ETreePath
1094 ml_get_node_by_id (ETreeModel *etm,
1095                    const gchar *save_id,
1096                    gpointer data)
1097 {
1098 	MessageList *ml;
1099 
1100 	ml = data;
1101 
1102 	if (!strcmp (save_id, "root"))
1103 		return e_tree_model_get_root (etm);
1104 
1105 	return g_hash_table_lookup (ml->uid_nodemap, save_id);
1106 }
1107 
1108 static gpointer
1109 ml_duplicate_value (ETreeModel *etm,
1110                     gint col,
1111                     gconstpointer value,
1112                     gpointer data)
1113 {
1114 	switch (col) {
1115 	case COL_MESSAGE_STATUS:
1116 	case COL_FLAGGED:
1117 	case COL_SCORE:
1118 	case COL_ATTACHMENT:
1119 	case COL_DELETED:
1120 	case COL_UNREAD:
1121 	case COL_SENT:
1122 	case COL_RECEIVED:
1123 	case COL_SIZE:
1124 	case COL_FOLLOWUP_FLAG_STATUS:
1125 	case COL_FOLLOWUP_DUE_BY:
1126 		return (gpointer) value;
1127 
1128 	case COL_FROM:
1129 	case COL_SUBJECT:
1130 	case COL_TO:
1131 	case COL_SENDER:
1132 	case COL_RECIPIENTS:
1133 	case COL_MIXED_SENDER:
1134 	case COL_MIXED_RECIPIENTS:
1135 	case COL_FOLLOWUP_FLAG:
1136 	case COL_LOCATION:
1137 	case COL_LABELS:
1138 		return g_strdup (value);
1139 	default:
1140 		g_warning ("This shouldn't be reached\n");
1141 	}
1142 	return NULL;
1143 }
1144 
1145 static void
1146 ml_free_value (ETreeModel *etm,
1147                gint col,
1148                gpointer value,
1149                gpointer data)
1150 {
1151 	switch (col) {
1152 	case COL_MESSAGE_STATUS:
1153 	case COL_FLAGGED:
1154 	case COL_SCORE:
1155 	case COL_ATTACHMENT:
1156 	case COL_DELETED:
1157 	case COL_UNREAD:
1158 	case COL_SENT:
1159 	case COL_RECEIVED:
1160 	case COL_SIZE:
1161 	case COL_FOLLOWUP_FLAG_STATUS:
1162 	case COL_FOLLOWUP_DUE_BY:
1163 	case COL_FROM_NORM:
1164 	case COL_SUBJECT_NORM:
1165 	case COL_TO_NORM:
1166 	case COL_SUBJECT_TRIMMED:
1167 	case COL_COLOUR:
1168 		break;
1169 
1170 	case COL_FROM:
1171 	case COL_SUBJECT:
1172 	case COL_TO:
1173 	case COL_FOLLOWUP_FLAG:
1174 	case COL_LOCATION:
1175 	case COL_SENDER:
1176 	case COL_RECIPIENTS:
1177 	case COL_MIXED_SENDER:
1178 	case COL_MIXED_RECIPIENTS:
1179 	case COL_LABELS:
1180 		g_free (value);
1181 		break;
1182 	default:
1183 		g_warning ("%s: This shouldn't be reached (col:%d)", G_STRFUNC, col);
1184 	}
1185 }
1186 
1187 static gpointer
1188 ml_initialize_value (ETreeModel *etm,
1189                      gint col,
1190                      gpointer data)
1191 {
1192 	switch (col) {
1193 	case COL_MESSAGE_STATUS:
1194 	case COL_FLAGGED:
1195 	case COL_SCORE:
1196 	case COL_ATTACHMENT:
1197 	case COL_DELETED:
1198 	case COL_UNREAD:
1199 	case COL_SENT:
1200 	case COL_RECEIVED:
1201 	case COL_SIZE:
1202 	case COL_FOLLOWUP_FLAG_STATUS:
1203 	case COL_FOLLOWUP_DUE_BY:
1204 		return NULL;
1205 
1206 	case COL_FROM:
1207 	case COL_SUBJECT:
1208 	case COL_TO:
1209 	case COL_FOLLOWUP_FLAG:
1210 	case COL_LOCATION:
1211 	case COL_SENDER:
1212 	case COL_RECIPIENTS:
1213 	case COL_MIXED_SENDER:
1214 	case COL_MIXED_RECIPIENTS:
1215 	case COL_LABELS:
1216 		return g_strdup ("");
1217 	default:
1218 		g_warning ("This shouldn't be reached\n");
1219 	}
1220 
1221 	return NULL;
1222 }
1223 
1224 static gboolean
1225 ml_value_is_empty (ETreeModel *etm,
1226                    gint col,
1227                    gconstpointer value,
1228                    gpointer data)
1229 {
1230 	switch (col) {
1231 	case COL_MESSAGE_STATUS:
1232 	case COL_FLAGGED:
1233 	case COL_SCORE:
1234 	case COL_ATTACHMENT:
1235 	case COL_DELETED:
1236 	case COL_UNREAD:
1237 	case COL_SENT:
1238 	case COL_RECEIVED:
1239 	case COL_SIZE:
1240 	case COL_FOLLOWUP_FLAG_STATUS:
1241 	case COL_FOLLOWUP_DUE_BY:
1242 		return value == NULL;
1243 
1244 	case COL_FROM:
1245 	case COL_SUBJECT:
1246 	case COL_TO:
1247 	case COL_FOLLOWUP_FLAG:
1248 	case COL_LOCATION:
1249 	case COL_SENDER:
1250 	case COL_RECIPIENTS:
1251 	case COL_MIXED_SENDER:
1252 	case COL_MIXED_RECIPIENTS:
1253 	case COL_LABELS:
1254 		return !(value && *(gchar *) value);
1255 	default:
1256 		g_warning ("This shouldn't be reached\n");
1257 		return FALSE;
1258 	}
1259 }
1260 
1261 static const gchar *status_map[] = {
1262 	N_("Unseen"),
1263 	N_("Seen"),
1264 	N_("Answered"),
1265 	N_("Forwarded"),
1266 	N_("Multiple Unseen Messages"),
1267 	N_("Multiple Messages"),
1268 };
1269 
1270 static const gchar *score_map[] = {
1271 	N_("Lowest"),
1272 	N_("Lower"),
1273 	N_("Low"),
1274 	N_("Normal"),
1275 	N_("High"),
1276 	N_("Higher"),
1277 	N_("Highest"),
1278 };
1279 
1280 static gchar *
1281 ml_value_to_string (ETreeModel *etm,
1282                     gint col,
1283                     gconstpointer value,
1284                     gpointer data)
1285 {
1286 	guint i;
1287 
1288 	switch (col) {
1289 	case COL_MESSAGE_STATUS:
1290 		i = GPOINTER_TO_UINT (value);
1291 		if (i > 5)
1292 			return g_strdup ("");
1293 		return g_strdup (_(status_map[i]));
1294 
1295 	case COL_SCORE:
1296 		i = GPOINTER_TO_UINT (value) + 3;
1297 		if (i > 6)
1298 			i = 3;
1299 		return g_strdup (_(score_map[i]));
1300 
1301 	case COL_ATTACHMENT:
1302 	case COL_FLAGGED:
1303 	case COL_DELETED:
1304 	case COL_UNREAD:
1305 	case COL_FOLLOWUP_FLAG_STATUS:
1306 		return g_strdup_printf ("%u", GPOINTER_TO_UINT (value));
1307 
1308 	case COL_SENT:
1309 	case COL_RECEIVED:
1310 	case COL_FOLLOWUP_DUE_BY:
1311 		return filter_date (GPOINTER_TO_INT (value));
1312 
1313 	case COL_SIZE:
1314 		return filter_size (GPOINTER_TO_INT (value));
1315 
1316 	case COL_FROM:
1317 	case COL_SUBJECT:
1318 	case COL_TO:
1319 	case COL_FOLLOWUP_FLAG:
1320 	case COL_LOCATION:
1321 	case COL_SENDER:
1322 	case COL_RECIPIENTS:
1323 	case COL_MIXED_SENDER:
1324 	case COL_MIXED_RECIPIENTS:
1325 	case COL_LABELS:
1326 		return g_strdup (value);
1327 	default:
1328 		g_warning ("This shouldn't be reached\n");
1329 		return NULL;
1330 	}
1331 }
1332 
1333 static GdkPixbuf *
1334 ml_tree_icon_at (ETreeModel *etm,
1335                  ETreePath path,
1336                  gpointer model_data)
1337 {
1338 	/* we dont really need an icon ... */
1339 	return NULL;
1340 }
1341 
1342 static void
1343 for_node_and_subtree_if_collapsed (MessageList *ml,
1344                                    ETreePath node,
1345                                    CamelMessageInfo *mi,
1346                                    ETreePathFunc func,
1347                                    gpointer data)
1348 {
1349 	ETreeModel *etm = ml->model;
1350 	ETreePath child;
1351 
1352 	func (NULL, (ETreePath) mi, data);
1353 
1354 	if (!node)
1355 		return;
1356 
1357 	child = e_tree_model_node_get_first_child (etm, node);
1358 	if (child && !e_tree_node_is_expanded (E_TREE (ml), node))
1359 		e_tree_model_node_traverse (etm, node, func, data);
1360 }
1361 
1362 static gboolean
1363 unread_foreach (ETreeModel *etm,
1364                 ETreePath node,
1365                 gpointer data)
1366 {
1367 	gboolean *saw_unread = data;
1368 	CamelMessageInfo *info;
1369 
1370 	if (!etm)
1371 		info = (CamelMessageInfo *) node;
1372 	else
1373 		info = e_tree_memory_node_get_data ((ETreeMemory *) etm, node);
1374 	g_return_val_if_fail (info != NULL, FALSE);
1375 
1376 	if (!(camel_message_info_flags (info) & CAMEL_MESSAGE_SEEN))
1377 		*saw_unread = TRUE;
1378 
1379 	return FALSE;
1380 }
1381 
1382 struct LatestData {
1383 	gboolean sent;
1384 	time_t latest;
1385 };
1386 
1387 static gboolean
1388 latest_foreach (ETreeModel *etm,
1389                 ETreePath node,
1390                 gpointer data)
1391 {
1392 	struct LatestData *ld = data;
1393 	CamelMessageInfo *info;
1394 	time_t date;
1395 
1396 	if (!etm)
1397 		info = (CamelMessageInfo *) node;
1398 	else
1399 		info = e_tree_memory_node_get_data ((ETreeMemory *) etm, node);
1400 	g_return_val_if_fail (info != NULL, FALSE);
1401 
1402 	date = ld->sent ? camel_message_info_date_sent (info)
1403 			: camel_message_info_date_received (info);
1404 
1405 	if (ld->latest == 0 || date > ld->latest)
1406 		ld->latest = date;
1407 
1408 	return FALSE;
1409 }
1410 
1411 static gchar *
1412 sanitize_recipients (const gchar *string)
1413 {
1414 	GString     *gstring;
1415 	gboolean     quoted = FALSE;
1416 	const gchar *p;
1417 	GString *recipients = g_string_new ("");
1418 	gchar *single_add;
1419 	gchar **name;
1420 
1421 	if (!string || !*string)
1422 		return (gchar *) "";
1423 
1424 	gstring = g_string_new ("");
1425 
1426 	for (p = string; *p; p = g_utf8_next_char (p)) {
1427 		gunichar c = g_utf8_get_char (p);
1428 
1429 		if (c == '"')
1430 			quoted = ~quoted;
1431 		else if (c == ',' && !quoted) {
1432 			single_add = g_string_free (gstring, FALSE);
1433 			name = g_strsplit (single_add,"<",2);
1434 			g_string_append (recipients, *name);
1435 			g_string_append (recipients, ",");
1436 			g_free (single_add);
1437 			g_strfreev (name);
1438 			gstring = g_string_new ("");
1439 			continue;
1440 		}
1441 
1442 		g_string_append_unichar (gstring, c);
1443 	}
1444 
1445 	single_add = g_string_free (gstring, FALSE);
1446 	name = g_strsplit (single_add,"<",2);
1447 	g_string_append (recipients, *name);
1448 	g_free (single_add);
1449 	g_strfreev (name);
1450 
1451 	return g_string_free (recipients, FALSE);
1452 }
1453 
1454 struct LabelsData {
1455 	EMailLabelListStore *store;
1456 	GHashTable *labels_tag2iter;
1457 };
1458 
1459 static void
1460 add_label_if_known (struct LabelsData *ld,
1461                     const gchar *tag)
1462 {
1463 	GtkTreeIter label_defn;
1464 
1465 	if (e_mail_label_list_store_lookup (ld->store, tag, &label_defn)) {
1466 		g_hash_table_insert (
1467 			ld->labels_tag2iter,
1468 			/* Should be the same as the "tag" arg */
1469 			e_mail_label_list_store_get_tag (ld->store, &label_defn),
1470 			gtk_tree_iter_copy (&label_defn));
1471 	}
1472 }
1473 
1474 static gboolean
1475 add_all_labels_foreach (ETreeModel *etm,
1476                         ETreePath node,
1477                         gpointer data)
1478 {
1479 	struct LabelsData *ld = data;
1480 	CamelMessageInfo *msg_info;
1481 	const gchar *old_label;
1482 	gchar *new_label;
1483 	const CamelFlag *flag;
1484 
1485 	if (!etm)
1486 		msg_info = (CamelMessageInfo *) node;
1487 	else
1488 		msg_info = e_tree_memory_node_get_data ((ETreeMemory *) etm, node);
1489 	g_return_val_if_fail (msg_info != NULL, FALSE);
1490 
1491 	for (flag = camel_message_info_user_flags (msg_info); flag; flag = flag->next)
1492 		add_label_if_known (ld, flag->name);
1493 
1494 	old_label = camel_message_info_user_tag (msg_info, "label");
1495 	if (old_label != NULL) {
1496 		/* Convert old-style labels ("<name>") to "$Label<name>". */
1497 		new_label = g_alloca (strlen (old_label) + 10);
1498 		g_stpcpy (g_stpcpy (new_label, "$Label"), old_label);
1499 
1500 		add_label_if_known (ld, new_label);
1501 	}
1502 
1503 	return FALSE;
1504 }
1505 
1506 static const gchar *
1507 get_trimmed_subject (CamelMessageInfo *info)
1508 {
1509 	const gchar *subject;
1510 	const gchar *mlist;
1511 	gint mlist_len = 0;
1512 	gboolean found_mlist;
1513 
1514 	subject = camel_message_info_subject (info);
1515 	if (!subject || !*subject)
1516 		return subject;
1517 
1518 	mlist = camel_message_info_mlist (info);
1519 
1520 	if (mlist && *mlist) {
1521 		const gchar *mlist_end;
1522 
1523 		mlist_end = strchr (mlist, '@');
1524 		if (mlist_end)
1525 			mlist_len = mlist_end - mlist;
1526 		else
1527 			mlist_len = strlen (mlist);
1528 	}
1529 
1530 	do {
1531 		EShell *shell = e_shell_get_default ();
1532 		gint skip_len;
1533 		gboolean found_re = TRUE;
1534 
1535 		found_mlist = FALSE;
1536 
1537 		while (found_re) {
1538 			found_re = FALSE;
      (emitted by clang-analyzer)TODO: a detailed trace is available in the data model (not yet rendered in this report)
      (emitted by clang-analyzer)TODO: a detailed trace is available in the data model (not yet rendered in this report)
1539 
1540 			found_re = em_utils_is_re_in_subject (shell, (const gchar *) subject, &skip_len) && skip_len > 0;
1541 			if (found_re)
1542 				subject += skip_len;
1543 
1544 			/* jump over any spaces */
1545 			while (*subject && isspace ((gint) *subject))
1546 				subject++;
1547 		}
1548 
1549 		if (mlist_len &&
1550 		    *subject == '[' &&
1551 		    !g_ascii_strncasecmp ((gchar *) subject + 1, mlist, mlist_len) &&
1552 		    subject[1 + mlist_len] == ']') {
1553 			subject += 1 + mlist_len + 1;  /* jump over "[mailing-list]" */
1554 			found_mlist = TRUE;
1555 
1556 			/* jump over any spaces */
1557 			while (*subject && isspace ((gint) *subject))
1558 				subject++;
1559 		}
1560 	} while (found_mlist);
1561 
1562 	/* jump over any spaces */
1563 	while (*subject && isspace ((gint) *subject))
1564 		subject++;
1565 
1566 	return subject;
1567 }
1568 
1569 static gpointer
1570 ml_tree_value_at_ex (ETreeModel *etm,
1571                      ETreePath path,
1572                      gint col,
1573                      CamelMessageInfo *msg_info,
1574                      MessageList *message_list)
1575 {
1576 	EMailSession *session;
1577 	const gchar *str;
1578 	guint32 flags;
1579 
1580 	session = message_list_get_session (message_list);
1581 
1582 	g_return_val_if_fail (msg_info != NULL, NULL);
1583 
1584 	switch (col) {
1585 	case COL_MESSAGE_STATUS:
1586 		flags = camel_message_info_flags (msg_info);
1587 		if (flags & CAMEL_MESSAGE_ANSWERED)
1588 			return GINT_TO_POINTER (2);
1589 		else if (flags & CAMEL_MESSAGE_FORWARDED)
1590 			return GINT_TO_POINTER (3);
1591 		else if (flags & CAMEL_MESSAGE_SEEN)
1592 			return GINT_TO_POINTER (1);
1593 		else
1594 			return GINT_TO_POINTER (0);
1595 	case COL_FLAGGED:
1596 		return GINT_TO_POINTER ((camel_message_info_flags (msg_info) & CAMEL_MESSAGE_FLAGGED) != 0);
1597 	case COL_SCORE: {
1598 		const gchar *tag;
1599 		gint score = 0;
1600 
1601 		tag = camel_message_info_user_tag (msg_info, "score");
1602 		if (tag)
1603 			score = atoi (tag);
1604 
1605 		return GINT_TO_POINTER (score);
1606 	}
1607 	case COL_FOLLOWUP_FLAG_STATUS: {
1608 		const gchar *tag, *cmp;
1609 
1610 		/* FIXME: this all should be methods off of message-tag-followup class,
1611 		 * FIXME: the tag names should be namespaced :( */
1612 		tag = camel_message_info_user_tag (msg_info, "follow-up");
1613 		cmp = camel_message_info_user_tag (msg_info, "completed-on");
1614 		if (tag && tag[0]) {
1615 			if (cmp && cmp[0])
1616 				return GINT_TO_POINTER (2);
1617 			else
1618 				return GINT_TO_POINTER (1);
1619 		} else
1620 			return GINT_TO_POINTER (0);
1621 	}
1622 	case COL_FOLLOWUP_DUE_BY: {
1623 		const gchar *tag;
1624 		time_t due_by;
1625 
1626 		tag = camel_message_info_user_tag (msg_info, "due-by");
1627 		if (tag && *tag) {
1628 			due_by = camel_header_decode_date (tag, NULL);
1629 			return GINT_TO_POINTER (due_by);
1630 		} else {
1631 			return GINT_TO_POINTER (0);
1632 		}
1633 	}
1634 	case COL_FOLLOWUP_FLAG:
1635 		str = camel_message_info_user_tag (msg_info, "follow-up");
1636 		return (gpointer)(str ? str : "");
1637 	case COL_ATTACHMENT:
1638 		if (camel_message_info_user_flag (msg_info, "$has_cal"))
1639 			return GINT_TO_POINTER (2);
1640 		return GINT_TO_POINTER ((camel_message_info_flags (msg_info) & CAMEL_MESSAGE_ATTACHMENTS) != 0);
1641 	case COL_FROM:
1642 		str = camel_message_info_from (msg_info);
1643 		return (gpointer)(str ? str : "");
1644 	case COL_FROM_NORM:
1645 		return (gpointer) get_normalised_string (message_list, msg_info, col);
1646 	case COL_SUBJECT:
1647 		str = camel_message_info_subject (msg_info);
1648 		return (gpointer)(str ? str : "");
1649 	case COL_SUBJECT_TRIMMED:
1650 		str = get_trimmed_subject (msg_info);
1651 		return (gpointer)(str ? str : "");
1652 	case COL_SUBJECT_NORM:
1653 		return (gpointer) get_normalised_string (message_list, msg_info, col);
1654 	case COL_SENT: {
1655 		struct LatestData ld;
1656 		ld.sent = TRUE;
1657 		ld.latest = 0;
1658 
1659 		for_node_and_subtree_if_collapsed (message_list, path, msg_info, latest_foreach, &ld);
1660 
1661 		return GINT_TO_POINTER (ld.latest);
1662 	}
1663 	case COL_RECEIVED: {
1664 		struct LatestData ld;
1665 		ld.sent = FALSE;
1666 		ld.latest = 0;
1667 
1668 		for_node_and_subtree_if_collapsed (message_list, path, msg_info, latest_foreach, &ld);
1669 
1670 		return GINT_TO_POINTER (ld.latest);
1671 	}
1672 	case COL_TO:
1673 		str = camel_message_info_to (msg_info);
1674 		return (gpointer)(str ? str : "");
1675 	case COL_TO_NORM:
1676 		return (gpointer) get_normalised_string (message_list, msg_info, col);
1677 	case COL_SIZE:
1678 		return GINT_TO_POINTER (camel_message_info_size (msg_info));
1679 	case COL_DELETED:
1680 		return GINT_TO_POINTER ((camel_message_info_flags (msg_info) & CAMEL_MESSAGE_DELETED) != 0);
1681 	case COL_UNREAD: {
1682 		gboolean saw_unread = FALSE;
1683 
1684 		for_node_and_subtree_if_collapsed (message_list, path, msg_info, unread_foreach, &saw_unread);
1685 
1686 		return GINT_TO_POINTER (saw_unread);
1687 	}
1688 	case COL_COLOUR: {
1689 		const gchar *colour, *due_by, *completed, *followup;
1690 
1691 		/* Priority: colour tag; label tag; important flag; due-by tag */
1692 
1693 		/* This is astonisngly poorly written code */
1694 
1695 		/* To add to the woes, what color to show when the user choose multiple labels ?
1696 		Don't say that I need to have the new labels[with subject] column visible always */
1697 
1698 		colour = NULL;
1699 		due_by = camel_message_info_user_tag (msg_info, "due-by");
1700 		completed = camel_message_info_user_tag (msg_info, "completed-on");
1701 		followup = camel_message_info_user_tag (msg_info, "follow-up");
1702 		if (colour == NULL) {
1703 			/* Get all applicable labels. */
1704 			struct LabelsData ld;
1705 
1706 			ld.store = e_mail_ui_session_get_label_store (
1707 				E_MAIL_UI_SESSION (session));
1708 			ld.labels_tag2iter = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) gtk_tree_iter_free);
1709 			for_node_and_subtree_if_collapsed (message_list, path, msg_info, add_all_labels_foreach, &ld);
1710 
1711 			if (g_hash_table_size (ld.labels_tag2iter) == 1) {
1712 				GHashTableIter iter;
1713 				GtkTreeIter *label_defn;
1714 				GdkColor colour_val;
1715 				gchar *colour_alloced;
1716 
1717 				/* Extract the single label from the hashtable. */
1718 				g_hash_table_iter_init (&iter, ld.labels_tag2iter);
1719 				g_hash_table_iter_next (&iter, NULL, (gpointer *) &label_defn);
1720 
1721 				e_mail_label_list_store_get_color (ld.store, label_defn, &colour_val);
1722 
1723 				/* XXX Hack to avoid returning an allocated string. */
1724 				colour_alloced = gdk_color_to_string (&colour_val);
1725 				colour = g_intern_string (colour_alloced);
1726 				g_free (colour_alloced);
1727 			} else if (camel_message_info_flags (msg_info) & CAMEL_MESSAGE_FLAGGED) {
1728 				/* FIXME: extract from the important.xpm somehow. */
1729 				colour = "#A7453E";
1730 			} else if (((followup && *followup) || (due_by && *due_by)) && !(completed && *completed)) {
1731 				time_t now = time (NULL);
1732 
1733 				if ((followup && *followup) || now >= camel_header_decode_date (due_by, NULL))
1734 					colour = "#A7453E";
1735 			}
1736 
1737 			g_hash_table_destroy (ld.labels_tag2iter);
1738 		}
1739 
1740 		if (!colour)
1741 			colour = camel_message_info_user_tag (msg_info, "color");
1742 
1743 		return (gpointer) colour;
1744 	}
1745 	case COL_LOCATION: {
1746 		/* Fixme : freeing memory stuff (mem leaks) */
1747 		CamelStore *store;
1748 		CamelFolder *folder;
1749 		CamelService *service;
1750 		const gchar *store_name;
1751 		const gchar *folder_name;
1752 
1753 		folder = message_list->folder;
1754 
1755 		if (CAMEL_IS_VEE_FOLDER (folder))
1756 			folder = camel_vee_folder_get_location (
1757 				CAMEL_VEE_FOLDER (folder),
1758 				(CamelVeeMessageInfo *) msg_info, NULL);
1759 
1760 		store = camel_folder_get_parent_store (folder);
1761 		folder_name = camel_folder_get_full_name (folder);
1762 
1763 		service = CAMEL_SERVICE (store);
1764 		store_name = camel_service_get_display_name (service);
1765 
1766 		return g_strdup_printf ("%s : %s", store_name, folder_name);
1767 	}
1768 	case COL_MIXED_RECIPIENTS:
1769 	case COL_RECIPIENTS:{
1770 		str = camel_message_info_to (msg_info);
1771 
1772 		return sanitize_recipients (str);
1773 	}
1774 	case COL_MIXED_SENDER:
1775 	case COL_SENDER:{
1776 		gchar **sender_name = NULL;
1777 		str = camel_message_info_from (msg_info);
1778 		if (str && str[0] != '\0') {
1779 			gchar *res;
1780 			sender_name = g_strsplit (str,"<",2);
1781 			res = g_strdup (*sender_name);
1782 			g_strfreev (sender_name);
1783 			return (gpointer)(res);
1784 		}
1785 		else
1786 			return (gpointer)("");
1787 	}
1788 	case COL_LABELS:{
1789 		struct LabelsData ld;
1790 		GString *result = g_string_new ("");
1791 
1792 		ld.store = e_mail_ui_session_get_label_store (
1793 			E_MAIL_UI_SESSION (session));
1794 		ld.labels_tag2iter = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) gtk_tree_iter_free);
1795 		for_node_and_subtree_if_collapsed (message_list, path, msg_info, add_all_labels_foreach, &ld);
1796 
1797 		if (g_hash_table_size (ld.labels_tag2iter) > 0) {
1798 			GHashTableIter iter;
1799 			GtkTreeIter *label_defn;
1800 
1801 			g_hash_table_iter_init (&iter, ld.labels_tag2iter);
1802 			while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &label_defn)) {
1803 				gchar *label_name, *label_name_clean;
1804 
1805 				if (result->len > 0)
1806 					g_string_append (result, ", ");
1807 
1808 				label_name = e_mail_label_list_store_get_name (ld.store, label_defn);
1809 				label_name_clean = e_str_without_underscores (label_name);
1810 
1811 				g_string_append (result, label_name_clean);
1812 
1813 				g_free (label_name_clean);
1814 				g_free (label_name);
1815 			}
1816 		}
1817 
1818 		g_hash_table_destroy (ld.labels_tag2iter);
1819 		return (gpointer) g_string_free (result, FALSE);
1820 	}
1821 	default:
1822 		g_warning ("%s: This shouldn't be reached (col:%d)", G_STRFUNC, col);
1823 		return NULL;
1824 	}
1825 }
1826 
1827 static gpointer
1828 ml_tree_value_at (ETreeModel *etm,
1829                   ETreePath path,
1830                   gint col,
1831                   gpointer model_data)
1832 {
1833 	MessageList *message_list = model_data;
1834 	CamelMessageInfo *msg_info;
1835 
1836 	if (e_tree_model_node_is_root (etm, path))
1837 		return NULL;
1838 
1839 	/* retrieve the message information array */
1840 	msg_info = e_tree_memory_node_get_data (E_TREE_MEMORY (etm), path);
1841 	g_return_val_if_fail (msg_info != NULL, NULL);
1842 
1843 	return ml_tree_value_at_ex (etm, path, col, msg_info, message_list);
1844 }
1845 
1846 static gpointer
1847 ml_tree_sort_value_at (ETreeModel *etm,
1848                        ETreePath path,
1849                        gint col,
1850                        gpointer model_data)
1851 {
1852 	MessageList *message_list = model_data;
1853 	struct LatestData ld;
1854 
1855 	if (!(col == COL_SENT || col == COL_RECEIVED))
1856 		return ml_tree_value_at (etm, path, col, model_data);
1857 
1858 	if (e_tree_model_node_is_root (etm, path))
1859 		return NULL;
1860 
1861 	ld.sent = (col == COL_SENT);
1862 	ld.latest = 0;
1863 
1864 	latest_foreach (etm, path, &ld);
1865 	if (message_list->priv->thread_latest)
1866 		e_tree_model_node_traverse (etm, path, latest_foreach, &ld);
1867 
1868 	return GINT_TO_POINTER (ld.latest);
1869 }
1870 
1871 static void
1872 ml_tree_set_value_at (ETreeModel *etm,
1873                       ETreePath path,
1874                       gint col,
1875                       gconstpointer val,
1876                       gpointer model_data)
1877 {
1878 	g_warning ("This shouldn't be reached\n");
1879 }
1880 
1881 static gboolean
1882 ml_tree_is_cell_editable (ETreeModel *etm,
1883                           ETreePath path,
1884                           gint col,
1885                           gpointer model_data)
1886 {
1887 	return FALSE;
1888 }
1889 
1890 static gchar *
1891 filter_date (time_t date)
1892 {
1893 	time_t nowdate = time (NULL);
1894 	time_t yesdate;
1895 	struct tm then, now, yesterday;
1896 	gchar buf[26];
1897 	gboolean done = FALSE;
1898 
1899 	if (date == 0)
1900 		return g_strdup (_("?"));
1901 
1902 	localtime_r (&date, &then);
1903 	localtime_r (&nowdate, &now);
1904 	if (then.tm_mday == now.tm_mday &&
1905 	    then.tm_mon == now.tm_mon &&
1906 	    then.tm_year == now.tm_year) {
1907 		e_utf8_strftime_fix_am_pm (buf, 26, _("Today %l:%M %p"), &then);
1908 		done = TRUE;
1909 	}
1910 	if (!done) {
1911 		yesdate = nowdate - 60 * 60 * 24;
1912 		localtime_r (&yesdate, &yesterday);
1913 		if (then.tm_mday == yesterday.tm_mday &&
1914 		    then.tm_mon == yesterday.tm_mon &&
1915 		    then.tm_year == yesterday.tm_year) {
1916 			e_utf8_strftime_fix_am_pm (buf, 26, _("Yesterday %l:%M %p"), &then);
1917 			done = TRUE;
1918 		}
1919 	}
1920 	if (!done) {
1921 		gint i;
1922 		for (i = 2; i < 7; i++) {
1923 			yesdate = nowdate - 60 * 60 * 24 * i;
1924 			localtime_r (&yesdate, &yesterday);
1925 			if (then.tm_mday == yesterday.tm_mday &&
1926 			    then.tm_mon == yesterday.tm_mon &&
1927 			    then.tm_year == yesterday.tm_year) {
1928 				e_utf8_strftime_fix_am_pm (buf, 26, _("%a %l:%M %p"), &then);
1929 				done = TRUE;
1930 				break;
1931 			}
1932 		}
1933 	}
1934 	if (!done) {
1935 		if (then.tm_year == now.tm_year) {
1936 			e_utf8_strftime_fix_am_pm (buf, 26, _("%b %d %l:%M %p"), &then);
1937 		} else {
1938 			e_utf8_strftime_fix_am_pm (buf, 26, _("%b %d %Y"), &then);
1939 		}
1940 	}
1941 #if 0
1942 #ifdef CTIME_R_THREE_ARGS
1943 	ctime_r (&date, buf, 26);
1944 #else
1945 	ctime_r (&date, buf);
1946 #endif
1947 #endif
1948 
1949 	return g_strdup (buf);
1950 }
1951 
1952 static ECell * create_composite_cell (gint col)
1953 {
1954 	ECell *cell_vbox, *cell_hbox, *cell_sub, *cell_date, *cell_from, *cell_tree, *cell_attach;
1955 	GSettings *settings;
1956 	gchar *fixed_name = NULL;
1957 	gboolean show_email;
1958 	gint alt_col = (col == COL_FROM) ? COL_SENDER : COL_RECIPIENTS;
1959 	gboolean same_font = FALSE;
1960 
1961 	settings = g_settings_new ("org.gnome.evolution.mail");
1962 	show_email = g_settings_get_boolean (settings, "show-email");
1963 	same_font = g_settings_get_boolean (settings, "vertical-view-fonts");
1964 	g_object_unref (settings);
1965 	if (!same_font) {
1966 		settings = g_settings_new ("org.gnome.desktop.interface");
1967 		fixed_name = g_settings_get_string (settings, "monospace-font-name");
1968 		g_object_unref (settings);
1969 	}
1970 
1971 	cell_vbox = e_cell_vbox_new ();
1972 
1973 	cell_hbox = e_cell_hbox_new ();
1974 
1975 	/* Exclude the meeting icon. */
1976 	cell_attach = e_cell_toggle_new (attachment_icons, G_N_ELEMENTS (attachment_icons));
1977 
1978 	cell_date = e_cell_date_new (NULL, GTK_JUSTIFY_RIGHT);
1979 	e_cell_date_set_format_component (E_CELL_DATE (cell_date), "mail");
1980 	g_object_set (
1981 		cell_date,
1982 		"bold_column", COL_UNREAD,
1983 		"color_column", COL_COLOUR,
1984 		NULL);
1985 
1986 	cell_from = e_cell_text_new (NULL, GTK_JUSTIFY_LEFT);
1987 	g_object_set (
1988 		cell_from,
1989 		"bold_column", COL_UNREAD,
1990 		"color_column", COL_COLOUR,
1991 		NULL);
1992 
1993 	e_cell_hbox_append (E_CELL_HBOX (cell_hbox), cell_from, show_email ? col : alt_col, 68);
1994 	e_cell_hbox_append (E_CELL_HBOX (cell_hbox), cell_attach, COL_ATTACHMENT, 5);
1995 	e_cell_hbox_append (E_CELL_HBOX (cell_hbox), cell_date, COL_SENT, 27);
1996 	g_object_unref (cell_from);
1997 	g_object_unref (cell_attach);
1998 	g_object_unref (cell_date);
1999 
2000 	cell_sub = e_cell_text_new (fixed_name? fixed_name : NULL, GTK_JUSTIFY_LEFT);
2001 	g_object_set (
2002 		cell_sub,
2003 		"color_column", COL_COLOUR,
2004 		NULL);
2005 	cell_tree = e_cell_tree_new (TRUE, cell_sub);
2006 	e_cell_vbox_append (E_CELL_VBOX (cell_vbox), cell_hbox, COL_FROM);
2007 	e_cell_vbox_append (E_CELL_VBOX (cell_vbox), cell_tree, COL_SUBJECT);
2008 	g_object_unref (cell_sub);
2009 	g_object_unref (cell_hbox);
2010 	g_object_unref (cell_tree);
2011 
2012 	g_object_set_data (G_OBJECT (cell_vbox), "cell_date", cell_date);
2013 	g_object_set_data (G_OBJECT (cell_vbox), "cell_sub", cell_sub);
2014 	g_object_set_data (G_OBJECT (cell_vbox), "cell_from", cell_from);
2015 
2016 	g_free (fixed_name);
2017 
2018 	return cell_vbox;
2019 }
2020 
2021 static void
2022 composite_cell_set_strike_col (ECell *cell,
2023                                gint col)
2024 {
2025 	g_object_set (g_object_get_data (G_OBJECT (cell), "cell_date"),  "strikeout_column", col, NULL);
2026 	g_object_set (g_object_get_data (G_OBJECT (cell), "cell_from"),  "strikeout_column", col, NULL);
2027 }
2028 
2029 static ETableExtras *
2030 message_list_create_extras (void)
2031 {
2032 	ETableExtras *extras;
2033 	ECell *cell;
2034 
2035 	extras = e_table_extras_new ();
2036 	e_table_extras_add_icon_name (extras, "status", "mail-unread");
2037 	e_table_extras_add_icon_name (extras, "score", "stock_score-higher");
2038 	e_table_extras_add_icon_name (extras, "attachment", "mail-attachment");
2039 	e_table_extras_add_icon_name (extras, "flagged", "emblem-important");
2040 	e_table_extras_add_icon_name (extras, "followup", "stock_mail-flag-for-followup");
2041 
2042 	e_table_extras_add_compare (extras, "address_compare", address_compare);
2043 
2044 	cell = e_cell_toggle_new (
2045 		status_icons, G_N_ELEMENTS (status_icons));
2046 	e_table_extras_add_cell (extras, "render_message_status", cell);
2047 	g_object_unref (cell);
2048 
2049 	cell = e_cell_toggle_new (
2050 		attachment_icons, G_N_ELEMENTS (attachment_icons));
2051 	e_table_extras_add_cell (extras, "render_attachment", cell);
2052 	g_object_unref (cell);
2053 
2054 	cell = e_cell_toggle_new (
2055 		flagged_icons, G_N_ELEMENTS (flagged_icons));
2056 	e_table_extras_add_cell (extras, "render_flagged", cell);
2057 	g_object_unref (cell);
2058 
2059 	cell = e_cell_toggle_new (
2060 		followup_icons, G_N_ELEMENTS (followup_icons));
2061 	e_table_extras_add_cell (extras, "render_flag_status", cell);
2062 	g_object_unref (cell);
2063 
2064 	cell = e_cell_toggle_new (
2065 		score_icons, G_N_ELEMENTS (score_icons));
2066 	e_table_extras_add_cell (extras, "render_score", cell);
2067 	g_object_unref (cell);
2068 
2069 	/* date cell */
2070 	cell = e_cell_date_new (NULL, GTK_JUSTIFY_LEFT);
2071 	e_cell_date_set_format_component (E_CELL_DATE (cell), "mail");
2072 	g_object_set (
2073 		cell,
2074 		"bold_column", COL_UNREAD,
2075 		"color_column", COL_COLOUR,
2076 		NULL);
2077 	e_table_extras_add_cell (extras, "render_date", cell);
2078 	g_object_unref (cell);
2079 
2080 	/* text cell */
2081 	cell = e_cell_text_new (NULL, GTK_JUSTIFY_LEFT);
2082 	g_object_set (
2083 		cell,
2084 		"bold_column", COL_UNREAD,
2085 		"color_column", COL_COLOUR,
2086 		NULL);
2087 	e_table_extras_add_cell (extras, "render_text", cell);
2088 	g_object_unref (cell);
2089 
2090 	cell = e_cell_tree_new (TRUE, cell);
2091 	e_table_extras_add_cell (extras, "render_tree", cell);
2092 	g_object_unref (cell);
2093 
2094 	/* size cell */
2095 	cell = e_cell_size_new (NULL, GTK_JUSTIFY_RIGHT);
2096 	g_object_set (
2097 		cell,
2098 		"bold_column", COL_UNREAD,
2099 		"color_column", COL_COLOUR,
2100 		NULL);
2101 	e_table_extras_add_cell (extras, "render_size", cell);
2102 	g_object_unref (cell);
2103 
2104 	/* Composite cell for wide view */
2105 	cell = create_composite_cell (COL_FROM);
2106 	e_table_extras_add_cell (extras, "render_composite_from", cell);
2107 	g_object_unref (cell);
2108 
2109 	cell = create_composite_cell (COL_TO);
2110 	e_table_extras_add_cell (extras, "render_composite_to", cell);
2111 	g_object_unref (cell);
2112 
2113 	/* set proper format component for a default 'date' cell renderer */
2114 	cell = e_table_extras_get_cell (extras, "date");
2115 	e_cell_date_set_format_component (E_CELL_DATE (cell), "mail");
2116 
2117 	return extras;
2118 }
2119 
2120 static void
2121 save_tree_state (MessageList *ml)
2122 {
2123 	gchar *filename;
2124 
2125 	if (ml->folder == NULL || (ml->search && *ml->search))
2126 		return;
2127 
2128 	filename = mail_config_folder_to_cachename (ml->folder, "et-expanded-");
2129 	e_tree_save_expanded_state (E_TREE (ml), filename);
2130 	g_free (filename);
2131 
2132 	ml->priv->any_row_changed = FALSE;
2133 }
2134 
2135 static void
2136 load_tree_state (MessageList *ml,
2137                  xmlDoc *expand_state)
2138 {
2139 	if (ml->folder == NULL)
2140 		return;
2141 
2142 	if (expand_state) {
2143 		e_tree_load_expanded_state_xml (E_TREE (ml), expand_state);
2144 	} else if (!ml->search || !*ml->search) {
2145 		/* only when not searching */
2146 		gchar *filename;
2147 
2148 		filename = mail_config_folder_to_cachename (ml->folder, "et-expanded-");
2149 		e_tree_load_expanded_state (E_TREE (ml), filename);
2150 		g_free (filename);
2151 	}
2152 
2153 	ml->priv->any_row_changed = FALSE;
2154 }
2155 
2156 void
2157 message_list_save_state (MessageList *ml)
2158 {
2159 	save_tree_state (ml);
2160 }
2161 
2162 static void
2163 message_list_setup_etree (MessageList *message_list,
2164                           gboolean outgoing)
2165 {
2166 	/* build the spec based on the folder, and possibly from a saved file */
2167 	/* otherwise, leave default */
2168 	if (message_list->folder) {
2169 		gint data = 1;
2170 		ETableItem *item;
2171 
2172 		item = e_tree_get_item (E_TREE (message_list));
2173 
2174 		g_object_set (message_list, "uniform_row_height", TRUE, NULL);
2175 		g_object_set_data (G_OBJECT (((GnomeCanvasItem *) item)->canvas), "freeze-cursor", &data);
2176 
2177 		/* build based on saved file */
2178 		load_tree_state (message_list, NULL);
2179 	}
2180 }
2181 
2182 static void
2183 ml_selection_get (GtkWidget *widget,
2184                   GtkSelectionData *data,
2185                   guint info,
2186                   guint time_stamp,
2187                   MessageList *ml)
2188 {
2189 	struct _MLSelection *selection;
2190 
2191 	selection = &ml->priv->clipboard;
2192 
2193 	if (selection->uids == NULL)
2194 		return;
2195 
2196 	if (info & 2) {
2197 		/* text/plain */
2198 		d (printf ("setting text/plain selection for uids\n"));
2199 		em_utils_selection_set_mailbox (data, selection->folder, selection->uids);
2200 	} else {
2201 		/* x-uid-list */
2202 		d (printf ("setting x-uid-list selection for uids\n"));
2203 		em_utils_selection_set_uidlist (data, selection->folder, selection->uids);
2204 	}
2205 }
2206 
2207 static gboolean
2208 ml_selection_clear_event (GtkWidget *widget,
2209                           GdkEventSelection *event,
2210                           MessageList *ml)
2211 {
2212 	MessageListPrivate *p = ml->priv;
2213 
2214 	clear_selection (ml, &p->clipboard);
2215 
2216 	return TRUE;
2217 }
2218 
2219 static void
2220 ml_selection_received (GtkWidget *widget,
2221                        GtkSelectionData *selection_data,
2222                        guint time,
2223                        MessageList *message_list)
2224 {
2225 	EMailSession *session;
2226 	GdkAtom target;
2227 
2228 	target = gtk_selection_data_get_target (selection_data);
2229 
2230 	if (target != gdk_atom_intern ("x-uid-list", FALSE)) {
2231 		d (printf ("Unknown selection received by message-list\n"));
2232 		return;
2233 	}
2234 
2235 	session = message_list_get_session (message_list);
2236 
2237 	/* FIXME Not passing a GCancellable or GError here. */
2238 	em_utils_selection_get_uidlist (
2239 		selection_data, session, message_list->folder,
2240 		FALSE, NULL, NULL);
2241 }
2242 
2243 static void
2244 ml_tree_drag_data_get (ETree *tree,
2245                        gint row,
2246                        ETreePath path,
2247                        gint col,
2248                        GdkDragContext *context,
2249                        GtkSelectionData *data,
2250                        guint info,
2251                        guint time,
2252                        MessageList *ml)
2253 {
2254 	GPtrArray *uids;
2255 
2256 	uids = message_list_get_selected (ml);
2257 
2258 	if (uids->len > 0) {
2259 		switch (info) {
2260 		case DND_X_UID_LIST:
2261 			em_utils_selection_set_uidlist (data, ml->folder, uids);
2262 			break;
2263 		case DND_TEXT_URI_LIST:
2264 			em_utils_selection_set_urilist (data, ml->folder, uids);
2265 			break;
2266 		}
2267 	}
2268 
2269 	em_utils_uids_free (uids);
2270 }
2271 
2272 /* TODO: merge this with the folder tree stuff via empopup targets */
2273 /* Drop handling */
2274 struct _drop_msg {
2275 	MailMsg base;
2276 
2277 	GdkDragContext *context;
2278 
2279 	/* Only selection->data and selection->length are valid */
2280 	GtkSelectionData *selection;
2281 
2282 	CamelFolder *folder;
2283 	MessageList *message_list;
2284 
2285 	guint32 action;
2286 	guint info;
2287 
2288 	guint move : 1;
2289 	guint moved : 1;
2290 	guint aborted : 1;
2291 };
2292 
2293 static gchar *
2294 ml_drop_async_desc (struct _drop_msg *m)
2295 {
2296 	const gchar *full_name;
2297 
2298 	full_name = camel_folder_get_full_name (m->folder);
2299 
2300 	if (m->move)
2301 		return g_strdup_printf (_("Moving messages into folder %s"), full_name);
2302 	else
2303 		return g_strdup_printf (_("Copying messages into folder %s"), full_name);
2304 }
2305 
2306 static void
2307 ml_drop_async_exec (struct _drop_msg *m,
2308                     GCancellable *cancellable,
2309                     GError **error)
2310 {
2311 	EMailSession *session;
2312 
2313 	session = message_list_get_session (m->message_list);
2314 
2315 	switch (m->info) {
2316 	case DND_X_UID_LIST:
2317 		em_utils_selection_get_uidlist (
2318 			m->selection, session, m->folder,
2319 			m->action == GDK_ACTION_MOVE,
2320 			cancellable, error);
2321 		break;
2322 	case DND_MESSAGE_RFC822:
2323 		em_utils_selection_get_message (m->selection, m->folder);
2324 		break;
2325 	case DND_TEXT_URI_LIST:
2326 		em_utils_selection_get_urilist (m->selection, m->folder);
2327 		break;
2328 	}
2329 }
2330 
2331 static void
2332 ml_drop_async_done (struct _drop_msg *m)
2333 {
2334 	gboolean success, delete;
2335 
2336 	/* ?? */
2337 	if (m->aborted) {
2338 		success = FALSE;
2339 		delete = FALSE;
2340 	} else {
2341 		success = (m->base.error == NULL);
2342 		delete = success && m->move && !m->moved;
2343 	}
2344 
2345 	gtk_drag_finish (m->context, success, delete, GDK_CURRENT_TIME);
2346 }
2347 
2348 static void
2349 ml_drop_async_free (struct _drop_msg *m)
2350 {
2351 	g_object_unref (m->context);
2352 	g_object_unref (m->folder);
2353 	g_object_unref (m->message_list);
2354 	gtk_selection_data_free (m->selection);
2355 }
2356 
2357 static MailMsgInfo ml_drop_async_info = {
2358 	sizeof (struct _drop_msg),
2359 	(MailMsgDescFunc) ml_drop_async_desc,
2360 	(MailMsgExecFunc) ml_drop_async_exec,
2361 	(MailMsgDoneFunc) ml_drop_async_done,
2362 	(MailMsgFreeFunc) ml_drop_async_free
2363 };
2364 
2365 static void
2366 ml_drop_action (struct _drop_msg *m)
2367 {
2368 	m->move = m->action == GDK_ACTION_MOVE;
2369 	mail_msg_unordered_push (m);
2370 }
2371 
2372 static void
2373 ml_tree_drag_data_received (ETree *tree,
2374                             gint row,
2375                             ETreePath path,
2376                             gint col,
2377                             GdkDragContext *context,
2378                             gint x,
2379                             gint y,
2380                             GtkSelectionData *selection_data,
2381                             guint info,
2382                             guint time,
2383                             MessageList *ml)
2384 {
2385 	struct _drop_msg *m;
2386 
2387 	if (ml->folder == NULL)
2388 		return;
2389 
2390 	if (gtk_selection_data_get_data (selection_data) == NULL)
2391 		return;
2392 
2393 	if (gtk_selection_data_get_length (selection_data) == -1)
2394 		return;
2395 
2396 	m = mail_msg_new (&ml_drop_async_info);
2397 	m->context = g_object_ref (context);
2398 	m->folder = g_object_ref (ml->folder);
2399 	m->message_list = g_object_ref (ml);
2400 	m->action = gdk_drag_context_get_selected_action (context);
2401 	m->info = info;
2402 
2403 	/* need to copy, goes away once we exit */
2404 	m->selection = gtk_selection_data_copy (selection_data);
2405 
2406 	ml_drop_action (m);
2407 }
2408 
2409 struct search_child_struct {
2410 	gboolean found;
2411 	gconstpointer looking_for;
2412 };
2413 
2414 static void
2415 search_child_cb (GtkWidget *widget,
2416                  gpointer data)
2417 {
2418 	struct search_child_struct *search = (struct search_child_struct *) data;
2419 
2420 	search->found = search->found || g_direct_equal (widget, search->looking_for);
2421 }
2422 
2423 static gboolean
2424 is_tree_widget_children (ETree *tree,
2425                          gconstpointer widget)
2426 {
2427 	struct search_child_struct search;
2428 
2429 	search.found = FALSE;
2430 	search.looking_for = widget;
2431 
2432 	gtk_container_foreach (GTK_CONTAINER (tree), search_child_cb, &search);
2433 
2434 	return search.found;
2435 }
2436 
2437 static gboolean
2438 ml_tree_drag_motion (ETree *tree,
2439                      GdkDragContext *context,
2440                      gint x,
2441                      gint y,
2442                      guint time,
2443                      MessageList *ml)
2444 {
2445 	GList *targets;
2446 	GdkDragAction action, actions = 0;
2447 	GtkWidget *source_widget;
2448 
2449 	/* If drop target is name of the account/store and not actual folder, don't allow any action */
2450 	if (!ml->folder) {
2451 		gdk_drag_status (context, 0, time);
2452 		return TRUE;
2453 	}
2454 
2455 	source_widget = gtk_drag_get_source_widget (context);
2456 
2457 	/* If source widget is packed under 'tree', don't allow any action */
2458 	if (is_tree_widget_children (tree, source_widget)) {
2459 		gdk_drag_status (context, 0, time);
2460 		return TRUE;
2461 	}
2462 
2463 	if (EM_IS_FOLDER_TREE (source_widget)) {
2464 		EMFolderTree *folder_tree;
2465 		CamelFolder *folder = NULL;
2466 		CamelStore *selected_store;
2467 		gchar *selected_folder_name;
2468 		gboolean has_selection;
2469 
2470 		folder_tree = EM_FOLDER_TREE (source_widget);
2471 
2472 		has_selection = em_folder_tree_get_selected (
2473 			folder_tree, &selected_store, &selected_folder_name);
2474 
2475 		/* Sanity checks */
2476 		g_warn_if_fail (
2477 			(has_selection && selected_store != NULL) ||
2478 			(!has_selection && selected_store == NULL));
2479 		g_warn_if_fail (
2480 			(has_selection && selected_folder_name != NULL) ||
2481 			(!has_selection && selected_folder_name == NULL));
2482 
2483 		if (has_selection) {
2484 			folder = camel_store_get_folder_sync (
2485 				selected_store, selected_folder_name,
2486 				CAMEL_STORE_FOLDER_INFO_FAST, NULL, NULL);
2487 			g_object_unref (selected_store);
2488 			g_free (selected_folder_name);
2489 		}
2490 
2491 		if (folder == ml->folder) {
2492 			gdk_drag_status (context, 0, time);
2493 			return TRUE;
2494 		}
2495 	}
2496 
2497 	targets = gdk_drag_context_list_targets (context);
2498 	while (targets != NULL) {
2499 		gint i;
2500 
2501 		d (printf ("atom drop '%s'\n", gdk_atom_name (targets->data)));
2502 		for (i = 0; i < G_N_ELEMENTS (ml_drag_info); i++)
2503 			if (targets->data == (gpointer) ml_drag_info[i].atom)
2504 				actions |= ml_drag_info[i].actions;
2505 
2506 		targets = g_list_next (targets);
2507 	}
2508 	d (printf ("\n"));
2509 
2510 	actions &= gdk_drag_context_get_actions (context);
2511 	action = gdk_drag_context_get_suggested_action (context);
2512 	if (action == GDK_ACTION_COPY && (actions & GDK_ACTION_MOVE))
2513 		action = GDK_ACTION_MOVE;
2514 
2515 	gdk_drag_status (context, action, time);
2516 
2517 	return action != 0;
2518 }
2519 
2520 static void
2521 on_model_row_changed (ETableModel *model,
2522                       gint row,
2523                       MessageList *ml)
2524 {
2525 	ml->priv->any_row_changed = TRUE;
2526 }
2527 
2528 static gboolean
2529 ml_tree_sorting_changed (ETreeTableAdapter *adapter,
2530                          MessageList *ml)
2531 {
2532 	g_return_val_if_fail (ml != NULL, FALSE);
2533 
2534 	if (ml->threaded && ml->frozen == 0) {
2535 		if (ml->thread_tree) {
2536 			/* free the previous thread_tree to recreate it fully */
2537 			camel_folder_thread_messages_unref (ml->thread_tree);
2538 			ml->thread_tree = NULL;
2539 		}
2540 
2541 		mail_regen_list (ml, ml->search, NULL, NULL, TRUE);
2542 
2543 		return TRUE;
2544 	}
2545 
2546 	return FALSE;
2547 }
2548 
2549 static void
2550 message_list_set_session (MessageList *message_list,
2551                           EMailSession *session)
2552 {
2553 	g_return_if_fail (E_IS_MAIL_SESSION (session));
2554 	g_return_if_fail (message_list->priv->session == NULL);
2555 
2556 	message_list->priv->session = g_object_ref (session);
2557 }
2558 
2559 static void
2560 message_list_init (MessageList *message_list)
2561 {
2562 	MessageListPrivate *p;
2563 	GtkTargetList *target_list;
2564 	GdkAtom matom;
2565 
2566 	message_list->priv = MESSAGE_LIST_GET_PRIVATE (message_list);
2567 
2568 	message_list->normalised_hash = g_hash_table_new_full (
2569 		g_str_hash, g_str_equal,
2570 		(GDestroyNotify) NULL,
2571 		(GDestroyNotify) e_poolv_destroy);
2572 
2573 	message_list->search = NULL;
2574 	message_list->ensure_uid = NULL;
2575 
2576 	message_list->uid_nodemap = g_hash_table_new (g_str_hash, g_str_equal);
2577 
2578 	message_list->cursor_uid = NULL;
2579 	message_list->last_sel_single = FALSE;
2580 
2581 	message_list->regen_lock = g_mutex_new ();
2582 
2583 	/* TODO: Should this only get the selection if we're realised? */
2584 	p = message_list->priv;
2585 	p->invisible = gtk_invisible_new ();
2586 	p->destroyed = FALSE;
2587 	g_object_ref_sink (p->invisible);
2588 	p->any_row_changed = FALSE;
2589 
2590 	matom = gdk_atom_intern ("x-uid-list", FALSE);
2591 	gtk_selection_add_target (p->invisible, GDK_SELECTION_CLIPBOARD, matom, 0);
2592 	gtk_selection_add_target (p->invisible, GDK_SELECTION_CLIPBOARD, GDK_SELECTION_TYPE_STRING, 2);
2593 
2594 	g_signal_connect (
2595 		p->invisible, "selection_get",
2596 		G_CALLBACK (ml_selection_get), message_list);
2597 	g_signal_connect (
2598 		p->invisible, "selection_clear_event",
2599 		G_CALLBACK (ml_selection_clear_event), message_list);
2600 	g_signal_connect (
2601 		p->invisible, "selection_received",
2602 		G_CALLBACK (ml_selection_received), message_list);
2603 
2604 	/* FIXME This is currently unused. */
2605 	target_list = gtk_target_list_new (NULL, 0);
2606 	message_list->priv->copy_target_list = target_list;
2607 
2608 	/* FIXME This is currently unused. */
2609 	target_list = gtk_target_list_new (NULL, 0);
2610 	message_list->priv->paste_target_list = target_list;
2611 }
2612 
2613 static void
2614 message_list_set_property (GObject *object,
2615                            guint property_id,
2616                            const GValue *value,
2617                            GParamSpec *pspec)
2618 {
2619 	switch (property_id) {
2620 		case PROP_SESSION:
2621 			message_list_set_session (
2622 				MESSAGE_LIST (object),
2623 				g_value_get_object (value));
2624 			return;
2625 	}
2626 
2627 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
2628 }
2629 
2630 static void
2631 message_list_get_property (GObject *object,
2632                            guint property_id,
2633                            GValue *value,
2634                            GParamSpec *pspec)
2635 {
2636 	switch (property_id) {
2637 		case PROP_COPY_TARGET_LIST:
2638 			g_value_set_boxed (
2639 				value, message_list_get_copy_target_list (
2640 				MESSAGE_LIST (object)));
2641 			return;
2642 
2643 		case PROP_PASTE_TARGET_LIST:
2644 			g_value_set_boxed (
2645 				value, message_list_get_paste_target_list (
2646 				MESSAGE_LIST (object)));
2647 			return;
2648 
2649 		case PROP_SESSION:
2650 			g_value_set_object (
2651 				value, message_list_get_session (
2652 				MESSAGE_LIST (object)));
2653 			return;
2654 	}
2655 
2656 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
2657 }
2658 
2659 static void
2660 message_list_dispose (GObject *object)
2661 {
2662 	MessageList *message_list = MESSAGE_LIST (object);
2663 	MessageListPrivate *priv;
2664 
2665 	priv = message_list->priv;
2666 
2667 	if (priv->session != NULL) {
2668 		g_object_unref (priv->session);
2669 		priv->session = NULL;
2670 	}
2671 
2672 	if (priv->copy_target_list != NULL) {
2673 		gtk_target_list_unref (priv->copy_target_list);
2674 		priv->copy_target_list = NULL;
2675 	}
2676 
2677 	if (priv->paste_target_list != NULL) {
2678 		gtk_target_list_unref (priv->paste_target_list);
2679 		priv->paste_target_list = NULL;
2680 	}
2681 
2682 	priv->destroyed = TRUE;
2683 
2684 	if (message_list->folder)
2685 		mail_regen_cancel (message_list);
2686 
2687 	if (message_list->uid_nodemap) {
2688 		g_hash_table_foreach (message_list->uid_nodemap, (GHFunc) clear_info, message_list);
2689 		g_hash_table_destroy (message_list->uid_nodemap);
2690 		message_list->uid_nodemap = NULL;
2691 	}
2692 
2693 	if (message_list->folder) {
2694 		g_signal_handlers_disconnect_by_func (
2695 			message_list->folder, folder_changed, message_list);
2696 		g_object_unref (message_list->folder);
2697 		message_list->folder = NULL;
2698 	}
2699 
2700 	if (priv->invisible) {
2701 		g_object_unref (priv->invisible);
2702 		priv->invisible = NULL;
2703 	}
2704 
2705 	if (message_list->extras) {
2706 		g_object_unref (message_list->extras);
2707 		message_list->extras = NULL;
2708 	}
2709 
2710 	if (message_list->model) {
2711 		g_object_unref (message_list->model);
2712 		message_list->model = NULL;
2713 	}
2714 
2715 	if (message_list->idle_id != 0) {
2716 		g_source_remove (message_list->idle_id);
2717 		message_list->idle_id = 0;
2718 	}
2719 
2720 	if (message_list->seen_id) {
2721 		g_source_remove (message_list->seen_id);
2722 		message_list->seen_id = 0;
2723 	}
2724 
2725 	/* Chain up to parent's dispose() method. */
2726 	G_OBJECT_CLASS (message_list_parent_class)->dispose (object);
2727 }
2728 
2729 static void
2730 message_list_finalize (GObject *object)
2731 {
2732 	MessageList *message_list = MESSAGE_LIST (object);
2733 	MessageListPrivate *priv = message_list->priv;
2734 
2735 	g_hash_table_destroy (message_list->normalised_hash);
2736 
2737 	if (message_list->ensure_uid) {
2738 		g_free (message_list->ensure_uid);
2739 		message_list->ensure_uid = NULL;
2740 	}
2741 
2742 	if (message_list->thread_tree)
2743 		camel_folder_thread_messages_unref (message_list->thread_tree);
2744 
2745 	g_free (message_list->search);
2746 	g_free (message_list->ensure_uid);
2747 	g_free (message_list->frozen_search);
2748 	g_free (message_list->cursor_uid);
2749 
2750 	g_mutex_free (message_list->regen_lock);
2751 
2752 	clear_selection (message_list, &priv->clipboard);
2753 
2754 	/* Chain up to parent's finalize() method. */
2755 	G_OBJECT_CLASS (message_list_parent_class)->finalize (object);
2756 }
2757 
2758 static void
2759 message_list_selectable_update_actions (ESelectable *selectable,
2760                                         EFocusTracker *focus_tracker,
2761                                         GdkAtom *clipboard_targets,
2762                                         gint n_clipboard_targets)
2763 {
2764 	GtkAction *action;
2765 	gboolean sensitive;
2766 
2767 	action = e_focus_tracker_get_select_all_action (focus_tracker);
2768 	sensitive = (e_tree_row_count (E_TREE (selectable)) > 0);
2769 	gtk_action_set_tooltip (action, _("Select all visible messages"));
2770 	gtk_action_set_sensitive (action, sensitive);
2771 }
2772 
2773 static void
2774 message_list_selectable_select_all (ESelectable *selectable)
2775 {
2776 	message_list_select_all (MESSAGE_LIST (selectable));
2777 }
2778 
2779 static void
2780 message_list_class_init (MessageListClass *class)
2781 {
2782 	GObjectClass *object_class;
2783 	gint i;
2784 
2785 	for (i = 0; i < G_N_ELEMENTS (ml_drag_info); i++)
2786 		ml_drag_info[i].atom = gdk_atom_intern (ml_drag_info[i].target, FALSE);
2787 
2788 	g_type_class_add_private (class, sizeof (MessageListPrivate));
2789 
2790 	object_class = G_OBJECT_CLASS (class);
2791 	object_class->set_property = message_list_set_property;
2792 	object_class->get_property = message_list_get_property;
2793 	object_class->dispose = message_list_dispose;
2794 	object_class->finalize = message_list_finalize;
2795 
2796 	class->message_list_built = NULL;
2797 
2798 	/* Inherited from ESelectableInterface */
2799 	g_object_class_override_property (
2800 		object_class,
2801 		PROP_COPY_TARGET_LIST,
2802 		"copy-target-list");
2803 
2804 	/* Inherited from ESelectableInterface */
2805 	g_object_class_override_property (
2806 		object_class,
2807 		PROP_PASTE_TARGET_LIST,
2808 		"paste-target-list");
2809 
2810 	g_object_class_install_property (
2811 		object_class,
2812 		PROP_SESSION,
2813 		g_param_spec_object (
2814 			"session",
2815 			"Mail Session",
2816 			"The mail session",
2817 			E_TYPE_MAIL_SESSION,
2818 			G_PARAM_READWRITE |
2819 			G_PARAM_CONSTRUCT_ONLY));
2820 
2821 	message_list_signals[MESSAGE_SELECTED] = g_signal_new (
2822 		"message_selected",
2823 		MESSAGE_LIST_TYPE,
2824 		G_SIGNAL_RUN_LAST,
2825 		G_STRUCT_OFFSET (MessageListClass, message_selected),
2826 		NULL,
2827 		NULL,
2828 		g_cclosure_marshal_VOID__STRING,
2829 		G_TYPE_NONE, 1,
2830 		G_TYPE_STRING);
2831 
2832 	message_list_signals[MESSAGE_LIST_BUILT] = g_signal_new (
2833 		"message_list_built",
2834 		MESSAGE_LIST_TYPE,
2835 		G_SIGNAL_RUN_LAST,
2836 		G_STRUCT_OFFSET (MessageListClass, message_list_built),
2837 		NULL,
2838 		NULL,
2839 		g_cclosure_marshal_VOID__VOID,
2840 		G_TYPE_NONE, 0);
2841 }
2842 
2843 static void
2844 message_list_selectable_init (ESelectableInterface *interface)
2845 {
2846 	interface->update_actions = message_list_selectable_update_actions;
2847 	interface->select_all = message_list_selectable_select_all;
2848 }
2849 
2850 static void
2851 message_list_construct (MessageList *message_list)
2852 {
2853 	AtkObject *a11y;
2854 	gboolean constructed;
2855 	gchar *etspecfile;
2856 	GSettings *settings;
2857 
2858 	message_list->model =
2859 		e_tree_memory_callbacks_new (
2860 			ml_tree_icon_at,
2861 
2862 			ml_column_count,
2863 
2864 			ml_has_save_id,
2865 			ml_get_save_id,
2866 
2867 			ml_has_get_node_by_id,
2868 			ml_get_node_by_id,
2869 
2870 			ml_tree_sort_value_at,
2871 			ml_tree_value_at,
2872 			ml_tree_set_value_at,
2873 			ml_tree_is_cell_editable,
2874 
2875 			ml_duplicate_value,
2876 			ml_free_value,
2877 			ml_initialize_value,
2878 			ml_value_is_empty,
2879 			ml_value_to_string,
2880 
2881 			message_list);
2882 
2883 	settings = g_settings_new ("org.gnome.evolution.mail");
2884 	e_tree_memory_set_expanded_default (
2885 		E_TREE_MEMORY (message_list->model),
2886 		g_settings_get_boolean (settings, "thread-expand"));
2887 	message_list->priv->thread_latest =
2888 		g_settings_get_boolean (settings, "thread-latest");
2889 	g_object_unref (settings);
2890 
2891 	/*
2892 	 * The etree
2893 	 */
2894 	message_list->extras = message_list_create_extras ();
2895 
2896 	etspecfile = g_build_filename (EVOLUTION_ETSPECDIR, "message-list.etspec", NULL);
2897 	constructed = e_tree_construct_from_spec_file (
2898 		E_TREE (message_list), message_list->model,
2899 		message_list->extras, etspecfile, NULL);
2900 	g_free (etspecfile);
2901 
2902 	if (constructed)
2903 		e_tree_root_node_set_visible (E_TREE (message_list), FALSE);
2904 
2905 	if (atk_get_root () != NULL) {
2906 		a11y = gtk_widget_get_accessible (GTK_WIDGET (message_list));
2907 		atk_object_set_name (a11y, _("Messages"));
2908 	}
2909 
2910 	g_signal_connect (
2911 		e_tree_get_table_adapter (E_TREE (message_list)),
2912 		"model_row_changed",
2913 		G_CALLBACK (on_model_row_changed), message_list);
2914 
2915 	g_signal_connect (
2916 		message_list, "cursor_activated",
2917 		G_CALLBACK (on_cursor_activated_cmd), message_list);
2918 
2919 	g_signal_connect (
2920 		message_list, "click",
2921 		G_CALLBACK (on_click), message_list);
2922 
2923 	g_signal_connect (
2924 		message_list, "selection_change",
2925 		G_CALLBACK (on_selection_changed_cmd), message_list);
2926 
2927 	e_tree_drag_source_set (
2928 		E_TREE (message_list), GDK_BUTTON1_MASK,
2929 		ml_drag_types, G_N_ELEMENTS (ml_drag_types),
2930 		GDK_ACTION_MOVE | GDK_ACTION_COPY);
2931 
2932 	g_signal_connect (
2933 		message_list, "tree_drag_data_get",
2934 		G_CALLBACK (ml_tree_drag_data_get), message_list);
2935 
2936 	e_tree_drag_dest_set (
2937 		E_TREE (message_list), GTK_DEST_DEFAULT_ALL,
2938 		ml_drop_types, G_N_ELEMENTS (ml_drop_types),
2939 		GDK_ACTION_MOVE | GDK_ACTION_COPY);
2940 
2941 	g_signal_connect (
2942 		message_list, "tree_drag_data_received",
2943 		G_CALLBACK (ml_tree_drag_data_received), message_list);
2944 
2945 	g_signal_connect (
2946 		message_list, "drag-motion",
2947 		G_CALLBACK (ml_tree_drag_motion), message_list);
2948 
2949 	g_signal_connect (
2950 		e_tree_get_table_adapter (E_TREE (message_list)),
2951 		"sorting_changed",
2952 		G_CALLBACK (ml_tree_sorting_changed), message_list);
2953 }
2954 
2955 /**
2956  * message_list_new:
2957  *
2958  * Creates a new message-list widget.
2959  *
2960  * Returns a new message-list widget.
2961  **/
2962 GtkWidget *
2963 message_list_new (EMailSession *session)
2964 {
2965 	GtkWidget *message_list;
2966 
2967 	g_return_val_if_fail (E_IS_MAIL_SESSION (session), NULL);
2968 
2969 	message_list = g_object_new (
2970 		message_list_get_type (),
2971 		"session", session, NULL);
2972 
2973 	message_list_construct (MESSAGE_LIST (message_list));
2974 
2975 	return message_list;
2976 }
2977 
2978 EMailSession *
2979 message_list_get_session (MessageList *message_list)
2980 {
2981 	g_return_val_if_fail (IS_MESSAGE_LIST (message_list), NULL);
2982 
2983 	return message_list->priv->session;
2984 }
2985 
2986 static void
2987 clear_info (gchar *key,
2988             ETreePath *node,
2989             MessageList *ml)
2990 {
2991 	CamelMessageInfo *info;
2992 
2993 	info = e_tree_memory_node_get_data ((ETreeMemory *) ml->model, node);
2994 	camel_folder_free_message_info (ml->folder, info);
2995 	e_tree_memory_node_set_data ((ETreeMemory *) ml->model, node, NULL);
2996 }
2997 
2998 static void
2999 clear_tree (MessageList *ml,
3000             gboolean tfree)
3001 {
3002 	ETreeModel *etm = ml->model;
3003 
3004 #ifdef TIMEIT
3005 	struct timeval start, end;
3006 	gulong diff;
3007 
3008 	printf ("Clearing tree\n");
3009 	gettimeofday (&start, NULL);
3010 #endif
3011 
3012 	/* we also reset the uid_rowmap since it is no longer useful/valid anyway */
3013 	if (ml->folder)
3014 		g_hash_table_foreach (ml->uid_nodemap, (GHFunc) clear_info, ml);
3015 	g_hash_table_destroy (ml->uid_nodemap);
3016 	ml->uid_nodemap = g_hash_table_new (g_str_hash, g_str_equal);
3017 
3018 	ml->priv->newest_read_date = 0;
3019 	ml->priv->newest_read_uid = NULL;
3020 	ml->priv->oldest_unread_date = 0;
3021 	ml->priv->oldest_unread_uid = NULL;
3022 
3023 	if (ml->tree_root) {
3024 		/* we should be frozen already */
3025 		e_tree_memory_node_remove (E_TREE_MEMORY (etm), ml->tree_root);
3026 	}
3027 
3028 	ml->tree_root = e_tree_memory_node_insert (E_TREE_MEMORY (etm), NULL, 0, NULL);
3029 	if (tfree)
3030 		e_tree_model_rebuilt (E_TREE_MODEL (etm));
3031 #ifdef TIMEIT
3032 	gettimeofday (&end, NULL);
3033 	diff = end.tv_sec * 1000 + end.tv_usec / 1000;
3034 	diff -= start.tv_sec * 1000 + start.tv_usec / 1000;
3035 	printf ("Clearing tree took %ld.%03ld seconds\n", diff / 1000, diff % 1000);
3036 #endif
3037 
3038 }
3039 
3040 static gboolean
3041 folder_store_supports_vjunk_folder (CamelFolder *folder)
3042 {
3043 	CamelStore *store;
3044 
3045 	g_return_val_if_fail (folder != NULL, FALSE);
3046 
3047 	store = camel_folder_get_parent_store (folder);
3048 	if (!store)
3049 		return FALSE;
3050 
3051 	return (store->flags & (CAMEL_STORE_VJUNK | CAMEL_STORE_REAL_JUNK_FOLDER)) != 0 || CAMEL_IS_VEE_FOLDER (folder);
3052 }
3053 
3054 /* Check if the given node is selectable in the current message list,
3055  * which depends on the type of the folder (normal, junk, trash). */
3056 static gboolean
3057 is_node_selectable (MessageList *ml,
3058                     CamelMessageInfo *info)
3059 {
3060 	gboolean is_junk_folder;
3061 	gboolean is_trash_folder;
3062 	guint32 flags;
3063 	gboolean flag_junk;
3064 	gboolean flag_deleted;
3065 	gboolean store_has_vjunk;
3066 
3067 	g_return_val_if_fail (ml != NULL, FALSE);
3068 	g_return_val_if_fail (ml->folder != NULL, FALSE);
3069 	g_return_val_if_fail (info != NULL, FALSE);
3070 
3071 	store_has_vjunk = folder_store_supports_vjunk_folder (ml->folder);
3072 
3073 	/* check folder type */
3074 	is_junk_folder = store_has_vjunk && (ml->folder->folder_flags & CAMEL_FOLDER_IS_JUNK) != 0;
3075 	is_trash_folder = ml->folder->folder_flags & CAMEL_FOLDER_IS_TRASH;
3076 
3077 	/* check flags set on current message */
3078 	flags = camel_message_info_flags (info);
3079 	flag_junk = store_has_vjunk && (flags & CAMEL_MESSAGE_JUNK) != 0;
3080 	flag_deleted = flags & CAMEL_MESSAGE_DELETED;
3081 
3082 	/* perform actions depending on folder type */
3083 	if (is_junk_folder) {
3084 		/* messages in a junk folder are selectable only if
3085 		 * the message is marked as junk and if not deleted
3086 		 * when hidedeleted is set */
3087 		if (flag_junk && !(flag_deleted && ml->hidedeleted))
3088 			return TRUE;
3089 
3090 	} else if (is_trash_folder) {
3091 		/* messages in a trash folder are selectable unless
3092 		 * not deleted any more */
3093 		if (flag_deleted)
3094 			return TRUE;
3095 	} else {
3096 		/* in normal folders it depends on hidedeleted,
3097 		 * hidejunk and the message flags */
3098 		if (!(flag_junk && ml->hidejunk)
3099 		    && !(flag_deleted && ml->hidedeleted))
3100 			return TRUE;
3101 	}
3102 
3103 	return FALSE;
3104 }
3105 
3106 /* We try and find something that is selectable in our tree.  There is
3107  * actually no assurance that we'll find something that will still be
3108  * there next time, but its probably going to work most of the time. */
3109 static gchar *
3110 find_next_selectable (MessageList *ml)
3111 {
3112 	ETreePath node;
3113 	gint last;
3114 	gint vrow_orig;
3115 	gint vrow;
3116 	ETree *et = E_TREE (ml);
3117 	CamelMessageInfo *info;
3118 
3119 	node = g_hash_table_lookup (ml->uid_nodemap, ml->cursor_uid);
3120 	if (node == NULL)
3121 		return NULL;
3122 
3123 	info = get_message_info (ml, node);
3124 	if (info && is_node_selectable (ml, info))
3125 		return NULL;
3126 
3127 	last = e_tree_row_count (et);
3128 
3129 	/* model_to_view_row etc simply dont work for sorted views.  Sigh. */
3130 	vrow_orig = e_tree_row_of_node (et, node);
3131 
3132 	/* We already checked this node. */
3133 	vrow = vrow_orig + 1;
3134 
3135 	while (vrow < last) {
3136 		node = e_tree_node_at_row (et, vrow);
3137 		info = get_message_info (ml, node);
3138 		if (info && is_node_selectable (ml, info))
3139 			return g_strdup (camel_message_info_uid (info));
3140 		vrow++;
3141 	}
3142 
3143 	/* We didn't find any undeleted entries _below_ the currently selected one
3144  *       * so let's try to find one _above_ */
3145 	vrow = vrow_orig - 1;
3146 
3147 	while (vrow >= 0) {
3148 		node = e_tree_node_at_row (et, vrow);
3149 		info = get_message_info (ml, node);
3150 		if (info && is_node_selectable (ml, info))
3151 			return g_strdup (camel_message_info_uid (info));
3152 		vrow--;
3153 	}
3154 
3155 	return NULL;
3156 }
3157 
3158 static ETreePath *
3159 ml_uid_nodemap_insert (MessageList *message_list,
3160                        CamelMessageInfo *info,
3161                        ETreePath *parent_node,
3162                        gint row)
3163 {
3164 	ETreeMemory *tree;
3165 	ETreePath *node;
3166 	const gchar *uid;
3167 	time_t date;
3168 	guint flags;
3169 
3170 	if (parent_node == NULL)
3171 		parent_node = message_list->tree_root;
3172 
3173 	tree = E_TREE_MEMORY (message_list->model);
3174 	node = e_tree_memory_node_insert (tree, parent_node, row, info);
3175 
3176 	uid = camel_message_info_uid (info);
3177 	flags = camel_message_info_flags (info);
3178 	date = camel_message_info_date_received (info);
3179 
3180 	camel_folder_ref_message_info (message_list->folder, info);
3181 	g_hash_table_insert (message_list->uid_nodemap, (gpointer) uid, node);
3182 
3183 	/* Track the latest seen and unseen messages shown, used in
3184 	 * fallback heuristics for automatic message selection. */
3185 	if (flags & CAMEL_MESSAGE_SEEN) {
3186 		if (date > message_list->priv->newest_read_date) {
3187 			message_list->priv->newest_read_date = date;
3188 			message_list->priv->newest_read_uid = uid;
3189 		}
3190 	} else {
3191 		if (message_list->priv->oldest_unread_date == 0) {
3192 			message_list->priv->oldest_unread_date = date;
3193 			message_list->priv->oldest_unread_uid = uid;
3194 		} else if (date < message_list->priv->oldest_unread_date) {
3195 			message_list->priv->oldest_unread_date = date;
3196 			message_list->priv->oldest_unread_uid = uid;
3197 		}
3198 	}
3199 
3200 	return node;
3201 }
3202 
3203 static void
3204 ml_uid_nodemap_remove (MessageList *message_list,
3205                        CamelMessageInfo *info)
3206 {
3207 	const gchar *uid;
3208 
3209 	uid = camel_message_info_uid (info);
3210 
3211 	if (uid == message_list->priv->newest_read_uid) {
3212 		message_list->priv->newest_read_date = 0;
3213 		message_list->priv->newest_read_uid = NULL;
3214 	}
3215 
3216 	if (uid == message_list->priv->oldest_unread_uid) {
3217 		message_list->priv->oldest_unread_date = 0;
3218 		message_list->priv->oldest_unread_uid = NULL;
3219 	}
3220 
3221 	g_hash_table_remove (message_list->uid_nodemap, uid);
3222 	camel_folder_free_message_info (message_list->folder, info);
3223 }
3224 
3225 /* only call if we have a tree model */
3226 /* builds the tree structure */
3227 
3228 #define BROKEN_ETREE	/* avoid some broken code in etree(?) by not using the incremental update */
3229 
3230 static void build_subtree (MessageList *ml, ETreePath parent, CamelFolderThreadNode *c, gint *row);
3231 
3232 static void build_subtree_diff (MessageList *ml, ETreePath parent, ETreePath path, CamelFolderThreadNode *c, gint *row);
3233 
3234 static void
3235 build_tree (MessageList *ml,
3236             CamelFolderThread *thread,
3237             CamelFolderChangeInfo *changes,
3238 	    gboolean can_scroll_to_cursor)
3239 {
3240 	gint row = 0;
3241 	ETreeModel *etm = ml->model;
3242 	ETableItem *table_item = e_tree_get_item (E_TREE (ml));
3243 #ifndef BROKEN_ETREE
3244 	ETreePath *top;
3245 #endif
3246 	gchar *saveuid = NULL;
3247 #ifdef BROKEN_ETREE
3248 	GPtrArray *selected;
3249 #endif
3250 #ifdef TIMEIT
3251 	struct timeval start, end;
3252 	gulong diff;
3253 
3254 	printf ("Building tree\n");
3255 	gettimeofday (&start, NULL);
3256 #endif
3257 
3258 #ifdef TIMEIT
3259 	gettimeofday (&end, NULL);
3260 	diff = end.tv_sec * 1000 + end.tv_usec / 1000;
3261 	diff -= start.tv_sec * 1000 + start.tv_usec / 1000;
3262 	printf ("Loading tree state took %ld.%03ld seconds\n", diff / 1000, diff % 1000);
3263 #endif
3264 
3265 	if (ml->tree_root == NULL) {
3266 		ml->tree_root =	e_tree_memory_node_insert (E_TREE_MEMORY (etm), NULL, 0, NULL);
3267 	}
3268 
3269 	if (ml->cursor_uid)
3270 		saveuid = find_next_selectable (ml);
3271 
3272 #ifndef BROKEN_ETREE
3273 	top = e_tree_model_node_get_first_child (etm, ml->tree_root);
3274 	if (top == NULL || changes == NULL) {
3275 #else
3276 		selected = message_list_get_selected (ml);
3277 #endif
3278 		e_tree_memory_freeze (E_TREE_MEMORY (etm));
3279 		clear_tree (ml, FALSE);
3280 
3281 		build_subtree (ml, ml->tree_root, thread->tree, &row);
3282 
3283 		if (!can_scroll_to_cursor && table_item)
3284 			table_item->queue_show_cursor = FALSE;
3285 
3286 		e_tree_memory_thaw (E_TREE_MEMORY (etm));
3287 #ifdef BROKEN_ETREE
3288 
3289 		/* it's required to thaw & freeze, to propagate changes */
3290 		e_tree_memory_freeze (E_TREE_MEMORY (etm));
3291 
3292 		message_list_set_selected (ml, selected);
3293 		em_utils_uids_free (selected);
3294 
3295 		if (!can_scroll_to_cursor && table_item)
3296 			table_item->queue_show_cursor = FALSE;
3297 
3298 		e_tree_memory_thaw (E_TREE_MEMORY (etm));
3299 #else
3300 	} else {
3301 		static gint tree_equal (ETreeModel *etm, ETreePath ap, CamelFolderThreadNode *bp);
3302 
3303 		build_subtree_diff (ml, ml->tree_root, top,  thread->tree, &row);
3304 		top = e_tree_model_node_get_first_child (etm, ml->tree_root);
3305 		tree_equal (ml->model, top, thread->tree);
3306 	}
3307 #endif
3308 	if (!saveuid && ml->cursor_uid && g_hash_table_lookup (ml->uid_nodemap, ml->cursor_uid)) {
3309 		/* this makes sure a visible node is selected, like when
3310 		 * collapsing all nodes and a children had been selected
3311 		*/
3312 		saveuid = g_strdup (ml->cursor_uid);
3313 	}
3314 
3315 	if (saveuid) {
3316 		ETreePath node = g_hash_table_lookup (ml->uid_nodemap, saveuid);
3317 		if (node == NULL) {
3318 			g_free (ml->cursor_uid);
3319 			ml->cursor_uid = NULL;
3320 			g_signal_emit (ml, message_list_signals[MESSAGE_SELECTED], 0, NULL);
3321 		} else {
3322 			ETree *tree = E_TREE (ml);
3323 			ETreePath parent = node;
3324 
3325 			while (parent = e_tree_model_node_get_parent (etm, parent), parent) {
3326 				if (!e_tree_node_is_expanded (tree, parent))
3327 					node = parent;
3328 			}
3329 
3330 			e_tree_memory_freeze (E_TREE_MEMORY (etm));
3331 
3332 			e_tree_set_cursor (E_TREE (ml), node);
3333 
3334 			if (!can_scroll_to_cursor && table_item)
3335 				table_item->queue_show_cursor = FALSE;
3336 
3337 			e_tree_memory_thaw (E_TREE_MEMORY (etm));
3338 		}
3339 		g_free (saveuid);
3340 	} else if (ml->cursor_uid && !g_hash_table_lookup (ml->uid_nodemap, ml->cursor_uid)) {
3341 		g_free (ml->cursor_uid);
3342 		ml->cursor_uid = NULL;
3343 		g_signal_emit (ml, message_list_signals[MESSAGE_SELECTED], 0, NULL);
3344 	}
3345 
3346 #ifdef TIMEIT
3347 	gettimeofday (&end, NULL);
3348 	diff = end.tv_sec * 1000 + end.tv_usec / 1000;
3349 	diff -= start.tv_sec * 1000 + start.tv_usec / 1000;
3350 	printf ("Building tree took %ld.%03ld seconds\n", diff / 1000, diff % 1000);
3351 #endif
3352 }
3353 
3354 /* this is about 20% faster than build_subtree_diff,
3355  * entirely because e_tree_model_node_insert (xx, -1 xx)
3356  * is faster than inserting to the right row :( */
3357 /* Otherwise, this code would probably go as it does the same thing essentially */
3358 static void
3359 build_subtree (MessageList *ml,
3360                ETreePath parent,
3361                CamelFolderThreadNode *c,
3362                gint *row)
3363 {
3364 	ETreePath node;
3365 
3366 	while (c) {
3367 		/* phantom nodes no longer allowed */
3368 		if (!c->message) {
3369 			g_warning ("c->message shouldn't be NULL\n");
3370 			c = c->next;
3371 			continue;
3372 		}
3373 
3374 		node = ml_uid_nodemap_insert (
3375 			ml, (CamelMessageInfo *) c->message, parent, -1);
3376 
3377 		if (c->child) {
3378 			build_subtree (ml, node, c->child, row);
3379 		}
3380 		c = c->next;
3381 	}
3382 }
3383 
3384 /* compares a thread tree node with the etable tree node to see if they point to
3385  * the same object */
3386 static gint
3387 node_equal (ETreeModel *etm,
3388             ETreePath ap,
3389             CamelFolderThreadNode *bp)
3390 {
3391 	CamelMessageInfo *info;
3392 
3393 	info = e_tree_memory_node_get_data (E_TREE_MEMORY (etm), ap);
3394 
3395 	if (bp->message && strcmp (camel_message_info_uid (info), camel_message_info_uid (bp->message)) == 0)
3396 		return 1;
3397 
3398 	return 0;
3399 }
3400 
3401 #ifndef BROKEN_ETREE
3402 /* debug function - compare the two trees to see if they are the same */
3403 static gint
3404 tree_equal (ETreeModel *etm,
3405             ETreePath ap,
3406             CamelFolderThreadNode *bp)
3407 {
3408 	CamelMessageInfo *info;
3409 
3410 	while (ap && bp) {
3411 		if (!node_equal (etm, ap, bp)) {
3412 			g_warning ("Nodes in tree differ");
3413 			info = e_tree_memory_node_get_data (E_TREE_MEMORY (etm), ap);
3414 			printf ("table uid = %s\n", camel_message_info_uid (info));
3415 			printf ("camel uid = %s\n", camel_message_info_uid (bp->message));
3416 			return FALSE;
3417 		} else {
3418 			if (!tree_equal (etm, e_tree_model_node_get_first_child (etm, ap), bp->child))
3419 				return FALSE;
3420 		}
3421 		bp = bp->next;
3422 		ap = e_tree_model_node_get_next (etm, ap);
3423 	}
3424 
3425 	if (ap || bp) {
3426 		g_warning ("Tree differs, out of nodes in one branch");
3427 		if (ap) {
3428 			info = e_tree_memory_node_get_data (E_TREE_MEMORY (etm), ap);
3429 			if (info)
3430 				printf ("table uid = %s\n", camel_message_info_uid (info));
3431 			else
3432 				printf ("info is empty?\n");
3433 		}
3434 		if (bp) {
3435 			printf ("camel uid = %s\n", camel_message_info_uid (bp->message));
3436 			return FALSE;
3437 		}
3438 		return FALSE;
3439 	}
3440 	return TRUE;
3441 }
3442 #endif
3443 
3444 /* adds a single node, retains save state, and handles adding children if required */
3445 static void
3446 add_node_diff (MessageList *ml,
3447                ETreePath parent,
3448                ETreePath path,
3449                CamelFolderThreadNode *c,
3450                gint *row,
3451                gint myrow)
3452 {
3453 	CamelMessageInfo *info;
3454 	ETreePath node;
3455 
3456 	g_return_if_fail (c->message != NULL);
3457 
3458 	/* XXX Casting away constness. */
3459 	info = (CamelMessageInfo *) c->message;
3460 
3461 	/* we just update the hashtable key */
3462 	ml_uid_nodemap_remove (ml, info);
3463 	node = ml_uid_nodemap_insert (ml, info, parent, myrow);
3464 	(*row)++;
3465 
3466 	if (c->child) {
3467 		build_subtree_diff (ml, node, NULL, c->child, row);
3468 	}
3469 }
3470 
3471 /* removes node, children recursively and all associated data */
3472 static void
3473 remove_node_diff (MessageList *ml,
3474                   ETreePath node,
3475                   gint depth)
3476 {
3477 	ETreeModel *etm = ml->model;
3478 	ETreePath cp, cn;
3479 	CamelMessageInfo *info;
3480 
3481 	t (printf ("Removing node: %s\n", (gchar *) e_tree_memory_node_get_data (etm, node)));
3482 
3483 	/* we depth-first remove all node data's ... */
3484 	cp = e_tree_model_node_get_first_child (etm, node);
3485 	while (cp) {
3486 		cn = e_tree_model_node_get_next (etm, cp);
3487 		remove_node_diff (ml, cp, depth + 1);
3488 		cp = cn;
3489 	}
3490 
3491 	/* and the rowid entry - if and only if it is referencing this node */
3492 	info = e_tree_memory_node_get_data (E_TREE_MEMORY (etm), node);
3493 
3494 	/* and only at the toplevel, remove the node (etree should optimise this remove somewhat) */
3495 	if (depth == 0)
3496 		e_tree_memory_node_remove (E_TREE_MEMORY (etm), node);
3497 
3498 	g_return_if_fail (info);
3499 	ml_uid_nodemap_remove (ml, info);
3500 }
3501 
3502 /* applies a new tree structure to an existing tree, but only by changing things
3503  * that have changed */
3504 static void
3505 build_subtree_diff (MessageList *ml,
3506                     ETreePath parent,
3507                     ETreePath path,
3508                     CamelFolderThreadNode *c,
3509                     gint *row)
3510 {
3511 	ETreeModel *etm = ml->model;
3512 	ETreePath ap, *ai, *at, *tmp;
3513 	CamelFolderThreadNode *bp, *bi, *bt;
3514 	gint i, j, myrow = 0;
3515 
3516 	ap = path;
3517 	bp = c;
3518 
3519 	while (ap || bp) {
3520 		t (printf ("Processing row: %d (subtree row %d)\n", *row, myrow));
3521 		if (ap == NULL) {
3522 			t (printf ("out of old nodes\n"));
3523 			/* ran out of old nodes - remaining nodes are added */
3524 			add_node_diff (ml, parent, ap, bp, row, myrow);
3525 			myrow++;
3526 			bp = bp->next;
3527 		} else if (bp == NULL) {
3528 			t (printf ("out of new nodes\n"));
3529 			/* ran out of new nodes - remaining nodes are removed */
3530 			tmp = e_tree_model_node_get_next (etm, ap);
3531 			remove_node_diff (ml, ap, 0);
3532 			ap = tmp;
3533 		} else if (node_equal (etm, ap, bp)) {
3534 			/*t(printf("nodes match, verify\n"));*/
3535 			/* matching nodes, verify details/children */
3536 #if 0
3537 			if (bp->message) {
3538 				gpointer olduid, oldrow;
3539 				/* if this is a message row, check/update the row id map */
3540 				if (g_hash_table_lookup_extended (ml->uid_rowmap, camel_message_info_uid (bp->message), &olduid, &oldrow)) {
3541 					if ((gint) oldrow != (*row)) {
3542 						g_hash_table_insert (ml->uid_rowmap, olduid, (gpointer)(*row));
3543 					}
3544 				} else {
3545 					g_warning ("Cannot find uid %s in table?", camel_message_info_uid (bp->message));
3546 				}
3547 			}
3548 #endif
3549 			*row = (*row)+1;
3550 			myrow++;
3551 			tmp = e_tree_model_node_get_first_child (etm, ap);
3552 			/* make child lists match (if either has one) */
3553 			if (bp->child || tmp) {
3554 				build_subtree_diff (ml, ap, tmp, bp->child, row);
3555 			}
3556 			ap = e_tree_model_node_get_next (etm, ap);
3557 			bp = bp->next;
3558 		} else {
3559 			t (printf ("searching for matches\n"));
3560 			/* we have to scan each side for a match */
3561 			bi = bp->next;
3562 			ai = e_tree_model_node_get_next (etm, ap);
3563 			for (i = 1; bi != NULL; i++,bi = bi->next) {
3564 				if (node_equal (etm, ap, bi))
3565 					break;
3566 			}
3567 			for (j = 1; ai != NULL; j++,ai = e_tree_model_node_get_next (etm, ai)) {
3568 				if (node_equal (etm, ai, bp))
3569 					break;
3570 			}
3571 			if (i < j) {
3572 				/* smaller run of new nodes - must be nodes to add */
3573 				if (bi) {
3574 					bt = bp;
3575 					while (bt != bi) {
3576 						t (printf ("adding new node 0\n"));
3577 						add_node_diff (ml, parent, NULL, bt, row, myrow);
3578 						myrow++;
3579 						bt = bt->next;
3580 					}
3581 					bp = bi;
3582 				} else {
3583 					t (printf ("adding new node 1\n"));
3584 					/* no match in new nodes, add one, try next */
3585 					add_node_diff (ml, parent, NULL, bp, row, myrow);
3586 					myrow++;
3587 					bp = bp->next;
3588 				}
3589 			} else {
3590 				/* bigger run of old nodes - must be nodes to remove */
3591 				if (ai) {
3592 					at = ap;
3593 					while (at != ai) {
3594 						t (printf ("removing old node 0\n"));
3595 						tmp = e_tree_model_node_get_next (etm, at);
3596 						remove_node_diff (ml, at, 0);
3597 						at = tmp;
3598 					}
3599 					ap = ai;
3600 				} else {
3601 					t (printf ("adding new node 2\n"));
3602 					/* didn't find match in old nodes, must be new node? */
3603 					add_node_diff (ml, parent, NULL, bp, row, myrow);
3604 					myrow++;
3605 					bp = bp->next;
3606 #if 0
3607 					tmp = e_tree_model_node_get_next (etm, ap);
3608 					remove_node_diff (etm, ap, 0);
3609 					ap = tmp;
3610 #endif
3611 				}
3612 			}
3613 		}
3614 	}
3615 }
3616 
3617 #ifndef BROKEN_ETREE
3618 static void build_flat_diff (MessageList *ml, CamelFolderChangeInfo *changes);
3619 #endif
3620 
3621 static void
3622 build_flat (MessageList *ml,
3623             GPtrArray *summary,
3624             CamelFolderChangeInfo *changes)
3625 {
3626 	ETreeModel *etm = ml->model;
3627 	gchar *saveuid = NULL;
3628 	gint i;
3629 #ifdef BROKEN_ETREE
3630 	GPtrArray *selected;
3631 #endif
3632 #ifdef TIMEIT
3633 	struct timeval start, end;
3634 	gulong diff;
3635 
3636 	printf ("Building flat\n");
3637 	gettimeofday (&start, NULL);
3638 #endif
3639 
3640 	if (ml->cursor_uid)
3641 		saveuid = find_next_selectable (ml);
3642 
3643 #ifndef BROKEN_ETREE
3644 	if (changes) {
3645 		build_flat_diff (ml, changes);
3646 	} else {
3647 #else
3648 		selected = message_list_get_selected (ml);
3649 #endif
3650 		e_tree_memory_freeze (E_TREE_MEMORY (etm));
3651 		clear_tree (ml, FALSE);
3652 		for (i = 0; i < summary->len; i++) {
3653 			CamelMessageInfo *info = summary->pdata[i];
3654 
3655 			ml_uid_nodemap_insert (ml, info, NULL, -1);
3656 		}
3657 		e_tree_memory_thaw (E_TREE_MEMORY (etm));
3658 #ifdef BROKEN_ETREE
3659 		message_list_set_selected (ml, selected);
3660 		em_utils_uids_free (selected);
3661 #else
3662 	}
3663 #endif
3664 
3665 	if (saveuid) {
3666 		ETreePath node = g_hash_table_lookup (ml->uid_nodemap, saveuid);
3667 		if (node == NULL) {
3668 			g_free (ml->cursor_uid);
3669 			ml->cursor_uid = NULL;
3670 			g_signal_emit (ml, message_list_signals[MESSAGE_SELECTED], 0, NULL);
3671 		} else {
3672 			e_tree_set_cursor (E_TREE (ml), node);
3673 		}
3674 		g_free (saveuid);
3675 	}
3676 
3677 #ifdef TIMEIT
3678 	gettimeofday (&end, NULL);
3679 	diff = end.tv_sec * 1000 + end.tv_usec / 1000;
3680 	diff -= start.tv_sec * 1000 + start.tv_usec / 1000;
3681 	printf ("Building flat took %ld.%03ld seconds\n", diff / 1000, diff % 1000);
3682 #endif
3683 
3684 }
3685 
3686 static void
3687 message_list_change_first_visible_parent (MessageList *ml,
3688                                           ETreePath node)
3689 {
3690 	ETreePath first_visible = NULL;
3691 
3692 	while (node && (node = e_tree_model_node_get_parent (ml->model, node))) {
3693 		if (!e_tree_node_is_expanded (E_TREE (ml), node))
3694 			first_visible = node;
3695 	}
3696 
3697 	if (first_visible != NULL) {
3698 		e_tree_model_pre_change (ml->model);
3699 		e_tree_model_node_data_changed (ml->model, first_visible);
3700 	}
3701 }
3702 
3703 #ifndef BROKEN_ETREE
3704 
3705 static void
3706 build_flat_diff (MessageList *ml,
3707                  CamelFolderChangeInfo *changes)
3708 {
3709 	gint i;
3710 	ETreePath node;
3711 	CamelMessageInfo *info;
3712 
3713 #ifdef TIMEIT
3714 	struct timeval start, end;
3715 	gulong diff;
3716 
3717 	gettimeofday (&start, NULL);
3718 #endif
3719 
3720 	d (printf ("updating changes to display\n"));
3721 
3722 	/* remove individual nodes? */
3723 	d (printf ("Removing messages from view:\n"));
3724 	for (i = 0; i < changes->uid_removed->len; i++) {
3725 		node = g_hash_table_lookup (ml->uid_nodemap, changes->uid_removed->pdata[i]);
3726 		if (node) {
3727 			info = e_tree_memory_node_get_data (E_TREE_MEMORY (ml->model), node);
3728 			e_tree_memory_node_remove (E_TREE_MEMORY (ml->model), node);
3729 			ml_uid_nodemap_remove (ml, info);
3730 		}
3731 	}
3732 
3733 	/* add new nodes? - just append to the end */
3734 	d (printf ("Adding messages to view:\n"));
3735 	for (i = 0; i < changes->uid_added->len; i++) {
3736 		info = camel_folder_get_message_info (ml->folder, changes->uid_added->pdata[i]);
3737 		if (info) {
3738 			d (printf (" %s\n", (gchar *) changes->uid_added->pdata[i]));
3739 			ml_uid_nodemap_insert (ml, info, NULL, -1);
3740 		}
3741 	}
3742 
3743 	/* and update changes too */
3744 	d (printf ("Changing messages to view:\n"));
3745 	for (i = 0; i < changes->uid_changed->len; i++) {
3746 		ETreePath *node = g_hash_table_lookup (ml->uid_nodemap, changes->uid_changed->pdata[i]);
3747 		if (node) {
3748 			e_tree_model_pre_change (ml->model);
3749 			e_tree_model_node_data_changed (ml->model, node);
3750 
3751 			message_list_change_first_visible_parent (ml, node);
3752 		}
3753 	}
3754 
3755 #ifdef TIMEIT
3756 	gettimeofday (&end, NULL);
3757 	diff = end.tv_sec * 1000 + end.tv_usec / 1000;
3758 	diff -= start.tv_sec * 1000 + start.tv_usec / 1000;
3759 	printf ("Inserting changes took %ld.%03ld seconds\n", diff / 1000, diff % 1000);
3760 #endif
3761 
3762 }
3763 #endif /* BROKEN_ETREE */
3764 
3765 static CamelFolderChangeInfo *
3766 mail_folder_hide_by_flag (CamelFolder *folder,
3767                           MessageList *ml,
3768                           CamelFolderChangeInfo *changes,
3769                           gint flag)
3770 {
3771 	CamelFolderChangeInfo *newchanges;
3772 	CamelMessageInfo *info;
3773 	gint i;
3774 
3775 	newchanges = camel_folder_change_info_new ();
3776 
3777 	for (i = 0; i < changes->uid_changed->len; i++) {
3778 		ETreePath node;
3779 		guint32 flags;
3780 
3781 		node = g_hash_table_lookup (
3782 			ml->uid_nodemap, changes->uid_changed->pdata[i]);
3783 		info = camel_folder_get_message_info (
3784 			folder, changes->uid_changed->pdata[i]);
3785 		if (info)
3786 			flags = camel_message_info_flags (info);
3787 
3788 		if (node != NULL && info != NULL && (flags & flag) != 0)
3789 			camel_folder_change_info_remove_uid (
3790 				newchanges, changes->uid_changed->pdata[i]);
3791 		else if (node == NULL && info != NULL && (flags & flag) == 0)
3792 			camel_folder_change_info_add_uid (
3793 				newchanges, changes->uid_changed->pdata[i]);
3794 		else
3795 			camel_folder_change_info_change_uid (
3796 				newchanges, changes->uid_changed->pdata[i]);
3797 		if (info)
3798 			camel_folder_free_message_info (folder, info);
3799 	}
3800 
3801 	if (newchanges->uid_added->len > 0 || newchanges->uid_removed->len > 0) {
3802 		for (i = 0; i < changes->uid_added->len; i++)
3803 			camel_folder_change_info_add_uid (
3804 				newchanges, changes->uid_added->pdata[i]);
3805 		for (i = 0; i < changes->uid_removed->len; i++)
3806 			camel_folder_change_info_remove_uid (
3807 				newchanges, changes->uid_removed->pdata[i]);
3808 	} else {
3809 		camel_folder_change_info_clear (newchanges);
3810 		camel_folder_change_info_cat (newchanges, changes);
3811 	}
3812 
3813 	return newchanges;
3814 }
3815 
3816 static void
3817 folder_changed (CamelFolder *folder,
3818                 CamelFolderChangeInfo *changes,
3819                 MessageList *ml)
3820 {
3821 	CamelFolderChangeInfo *altered_changes = NULL;
3822 	gint i;
3823 
3824 	if (ml->priv->destroyed)
3825 		return;
3826 
3827 	d (printf ("folder changed event, changes = %p\n", changes));
3828 	if (changes) {
3829 		for (i = 0; i < changes->uid_removed->len; i++)
3830 			g_hash_table_remove (
3831 				ml->normalised_hash,
3832 				changes->uid_removed->pdata[i]);
3833 
3834 		/* check if the hidden state has changed, if so modify accordingly, then regenerate */
3835 		if (ml->hidejunk || ml->hidedeleted)
3836 			altered_changes = mail_folder_hide_by_flag (
3837 				folder, ml, changes,
3838 				(ml->hidejunk ? CAMEL_MESSAGE_JUNK : 0) |
3839 				(ml->hidedeleted ? CAMEL_MESSAGE_DELETED : 0));
3840 		else {
3841 			altered_changes = camel_folder_change_info_new ();
3842 			camel_folder_change_info_cat (altered_changes, changes);
3843 		}
3844 
3845 		if (altered_changes->uid_added->len == 0 && altered_changes->uid_removed->len == 0 && altered_changes->uid_changed->len < 100) {
3846 			for (i = 0; i < altered_changes->uid_changed->len; i++) {
3847 				ETreePath node = g_hash_table_lookup (ml->uid_nodemap, altered_changes->uid_changed->pdata[i]);
3848 				if (node) {
3849 					e_tree_model_pre_change (ml->model);
3850 					e_tree_model_node_data_changed (ml->model, node);
3851 
3852 					message_list_change_first_visible_parent (ml, node);
3853 				}
3854 			}
3855 
3856 			camel_folder_change_info_free (altered_changes);
3857 
3858 			g_signal_emit (ml, message_list_signals[MESSAGE_LIST_BUILT], 0);
3859 			return;
3860 		}
3861 	}
3862 
3863 	/* XXX This apparently eats the ChangeFolderChangeInfo. */
3864 	mail_regen_list (ml, ml->search, NULL, altered_changes, FALSE);
3865 }
3866 
3867 /**
3868  * message_list_set_folder:
3869  * @message_list: Message List widget
3870  * @folder: folder backend to be set
3871  * @outgoing: whether this is an outgoing folder
3872  *
3873  * Sets @folder to be the backend folder for @message_list. If
3874  * @outgoing is %TRUE, then the message-list UI changes to default to
3875  * the "Outgoing folder" column view.
3876  **/
3877 void
3878 message_list_set_folder (MessageList *message_list,
3879                          CamelFolder *folder,
3880                          gboolean outgoing)
3881 {
3882 	ETreeModel *etm = message_list->model;
3883 	gboolean hide_deleted;
3884 	GSettings *settings;
3885 
3886 	g_return_if_fail (IS_MESSAGE_LIST (message_list));
3887 
3888 	if (message_list->folder == folder)
3889 		return;
3890 
3891 	g_free (message_list->search);
3892 	message_list->search = NULL;
3893 
3894 	g_free (message_list->frozen_search);
3895 	message_list->frozen_search = NULL;
3896 
3897 	if (message_list->seen_id) {
3898 		g_source_remove (message_list->seen_id);
3899 		message_list->seen_id = 0;
3900 	}
3901 
3902 	/* reset the normalised sort performance hack */
3903 	g_hash_table_remove_all (message_list->normalised_hash);
3904 
3905 	mail_regen_cancel (message_list);
3906 
3907 	if (message_list->folder != NULL) {
3908 		save_tree_state (message_list);
3909 	}
3910 
3911 	e_tree_memory_freeze (E_TREE_MEMORY (etm));
3912 	clear_tree (message_list, TRUE);
3913 	e_tree_memory_thaw (E_TREE_MEMORY (etm));
3914 
3915 	/* remove the cursor activate idle handler */
3916 	if (message_list->idle_id != 0) {
3917 		g_source_remove (message_list->idle_id);
3918 		message_list->idle_id = 0;
3919 	}
3920 
3921 	if (message_list->folder) {
3922 		g_signal_handlers_disconnect_by_func (
3923 			message_list->folder, folder_changed, message_list);
3924 
3925 		if (message_list->uid_nodemap)
3926 			g_hash_table_foreach (message_list->uid_nodemap, (GHFunc) clear_info, message_list);
3927 
3928 		g_object_unref (message_list->folder);
3929 		message_list->folder = NULL;
3930 	}
3931 
3932 	if (message_list->thread_tree) {
3933 		camel_folder_thread_messages_unref (message_list->thread_tree);
3934 		message_list->thread_tree = NULL;
3935 	}
3936 
3937 	if (message_list->cursor_uid) {
3938 		g_free (message_list->cursor_uid);
3939 		message_list->cursor_uid = NULL;
3940 	}
3941 
3942 	/* Always emit message-selected, event when an account node
3943 	 * (folder == NULL) is selected, so that views know what happened and
3944 	 * can stop all running operations etc. */
3945 	g_signal_emit (message_list, message_list_signals[MESSAGE_SELECTED], 0, NULL);
3946 
3947 	if (CAMEL_IS_FOLDER (folder)) {
3948 		CamelStore *store;
3949 		gboolean non_trash_folder;
3950 		gint strikeout_col;
3951 		ECell *cell;
3952 
3953 		message_list->folder = g_object_ref (folder);
3954 		message_list->just_set_folder = TRUE;
3955 
3956 		store = camel_folder_get_parent_store (folder);
3957 
3958 		non_trash_folder =
3959 			((store->flags & CAMEL_STORE_VTRASH) == 0) ||
3960 			((folder->folder_flags & CAMEL_FOLDER_IS_TRASH) == 0);
3961 
3962 		/* Setup the strikeout effect for non-trash folders */
3963 		strikeout_col = non_trash_folder ? COL_DELETED : -1;
3964 
3965 		cell = e_table_extras_get_cell (message_list->extras, "render_date");
3966 		g_object_set (cell, "strikeout_column", strikeout_col, NULL);
3967 
3968 		cell = e_table_extras_get_cell (message_list->extras, "render_text");
3969 		g_object_set (cell, "strikeout_column", strikeout_col, NULL);
3970 
3971 		cell = e_table_extras_get_cell (message_list->extras, "render_size");
3972 		g_object_set (cell, "strikeout_column", strikeout_col, NULL);
3973 
3974 		cell = e_table_extras_get_cell (message_list->extras, "render_composite_from");
3975 		composite_cell_set_strike_col (cell, strikeout_col);
3976 
3977 		cell = e_table_extras_get_cell (message_list->extras, "render_composite_to");
3978 		composite_cell_set_strike_col (cell, strikeout_col);
3979 
3980 		/* Build the etree suitable for this folder */
3981 		message_list_setup_etree (message_list, outgoing);
3982 
3983 		g_signal_connect (
3984 			folder, "changed",
3985 			G_CALLBACK (folder_changed), message_list);
3986 
3987 		settings = g_settings_new ("org.gnome.evolution.mail");
3988 		hide_deleted = !g_settings_get_boolean (settings, "show-deleted");
3989 		g_object_unref (settings);
3990 
3991 		message_list->hidedeleted =
3992 			hide_deleted && non_trash_folder;
3993 		message_list->hidejunk =
3994 			folder_store_supports_vjunk_folder (message_list->folder) &&
3995 			!(folder->folder_flags & CAMEL_FOLDER_IS_JUNK) &&
3996 			!(folder->folder_flags & CAMEL_FOLDER_IS_TRASH);
3997 
3998 		if (message_list->frozen == 0)
3999 			mail_regen_list (message_list, message_list->search, NULL, NULL, TRUE);
4000 	}
4001 }
4002 
4003 GtkTargetList *
4004 message_list_get_copy_target_list (MessageList *message_list)
4005 {
4006 	g_return_val_if_fail (IS_MESSAGE_LIST (message_list), NULL);
4007 
4008 	return message_list->priv->copy_target_list;
4009 }
4010 
4011 GtkTargetList *
4012 message_list_get_paste_target_list (MessageList *message_list)
4013 {
4014 	g_return_val_if_fail (IS_MESSAGE_LIST (message_list), NULL);
4015 
4016 	return message_list->priv->paste_target_list;
4017 }
4018 
4019 static gboolean
4020 on_cursor_activated_idle (gpointer data)
4021 {
4022 	MessageList *message_list = data;
4023 	ESelectionModel *esm;
4024 	gint selected;
4025 
4026 	esm = e_tree_get_selection_model (E_TREE (message_list));
4027 	selected = e_selection_model_selected_count (esm);
4028 
4029 	if (selected == 1 && message_list->cursor_uid) {
4030 		d (printf ("emitting cursor changed signal, for uid %s\n", message_list->cursor_uid));
4031 		g_signal_emit (message_list, message_list_signals[MESSAGE_SELECTED], 0, message_list->cursor_uid);
4032 	} else {
4033 		g_signal_emit (message_list, message_list_signals[MESSAGE_SELECTED], 0, NULL);
4034 	}
4035 
4036 	message_list->idle_id = 0;
4037 	return FALSE;
4038 }
4039 
4040 static void
4041 on_cursor_activated_cmd (ETree *tree,
4042                          gint row,
4043                          ETreePath path,
4044                          gpointer user_data)
4045 {
4046 	MessageList *message_list = MESSAGE_LIST (user_data);
4047 	const gchar *new_uid;
4048 
4049 	if (path == NULL)
4050 		new_uid = NULL;
4051 	else
4052 		new_uid = get_message_uid (message_list, path);
4053 
4054 	/* Do not check the cursor_uid and the new_uid values, because the
4055 	* selected item (set in on_selection_changed_cmd) can be different
4056 	* from the one with a cursor (when selecting with Ctrl, for example).
4057 	* This has a little side-effect, when keeping list it that state,
4058 	* then changing folders forth and back will select and move cursor
4059 	* to that selected item. Does anybody consider it as a bug? */
4060 	if ((message_list->cursor_uid == NULL && new_uid == NULL)
4061 	    || (message_list->last_sel_single && message_list->cursor_uid != NULL && new_uid != NULL))
4062 		return;
4063 
4064 	g_free (message_list->cursor_uid);
4065 	message_list->cursor_uid = g_strdup (new_uid);
4066 
4067 	if (!message_list->idle_id) {
4068 		message_list->idle_id =
4069 			g_idle_add_full (
4070 				G_PRIORITY_LOW, on_cursor_activated_idle,
4071 				message_list, NULL);
4072 	}
4073 }
4074 
4075 static void
4076 on_selection_changed_cmd (ETree *tree,
4077                           MessageList *ml)
4078 {
4079 	GPtrArray *uids;
4080 	const gchar *newuid;
4081 	ETreePath cursor;
4082 
4083 	/* not sure if we could just ignore this for the cursor, i think sometimes you
4084 	 * only get a selection changed when you should also get a cursor activated? */
4085 	uids = message_list_get_selected (ml);
4086 	if (uids->len == 1)
4087 		newuid = g_ptr_array_index (uids, 0);
4088 	else if ((cursor = e_tree_get_cursor (tree)))
4089 		newuid = (gchar *) camel_message_info_uid (e_tree_memory_node_get_data ((ETreeMemory *) tree, cursor));
4090 	else
4091 		newuid = NULL;
4092 
4093 	/* If the selection isn't empty, then we ignore the no-uid check, since this event
4094 	 * is also used for other updating.  If it is empty, it might just be a setup event
4095 	 * from etree which we do need to ignore */
4096 	if ((newuid == NULL && ml->cursor_uid == NULL && uids->len == 0) ||
4097 	    (ml->last_sel_single && uids->len == 1 && newuid != NULL && ml->cursor_uid != NULL && !strcmp (ml->cursor_uid, newuid))) {
4098 		/* noop */
4099 	} else {
4100 		g_free (ml->cursor_uid);
4101 		ml->cursor_uid = g_strdup (newuid);
4102 		if (!ml->idle_id)
4103 			ml->idle_id = g_idle_add_full (G_PRIORITY_LOW, on_cursor_activated_idle, ml, NULL);
4104 	}
4105 
4106 	ml->last_sel_single = uids->len == 1;
4107 
4108 	em_utils_uids_free (uids);
4109 }
4110 
4111 static gint
4112 on_click (ETree *tree,
4113           gint row,
4114           ETreePath path,
4115           gint col,
4116           GdkEvent *event,
4117           MessageList *list)
4118 {
4119 	CamelMessageInfo *info;
4120 	gboolean folder_is_trash;
4121 	const gchar *uid;
4122 	gint flag = 0;
4123 	guint32 flags;
4124 
4125 	if (col == COL_MESSAGE_STATUS)
4126 		flag = CAMEL_MESSAGE_SEEN;
4127 	else if (col == COL_FLAGGED)
4128 		flag = CAMEL_MESSAGE_FLAGGED;
4129 	else if (col != COL_FOLLOWUP_FLAG_STATUS)
4130 		return FALSE;
4131 
4132 	if (!(info = get_message_info (list, path)))
4133 		return FALSE;
4134 
4135 	if (col == COL_FOLLOWUP_FLAG_STATUS) {
4136 		const gchar *tag, *cmp;
4137 
4138 		tag = camel_message_info_user_tag (info, "follow-up");
4139 		cmp = camel_message_info_user_tag (info, "completed-on");
4140 		if (tag && tag[0]) {
4141 			if (cmp && cmp[0]) {
4142 				camel_message_info_set_user_tag (info, "follow-up", NULL);
4143 				camel_message_info_set_user_tag (info, "due-by", NULL);
4144 				camel_message_info_set_user_tag (info, "completed-on", NULL);
4145 			} else {
4146 				gchar *text;
4147 
4148 				text = camel_header_format_date (time (NULL), 0);
4149 				camel_message_info_set_user_tag (info, "completed-on", text);
4150 				g_free (text);
4151 			}
4152 		} else {
4153 			/* default follow-up flag name to use when clicked in the message list column */
4154 			camel_message_info_set_user_tag (info, "follow-up", _("Follow-up"));
4155 			camel_message_info_set_user_tag (info, "completed-on", NULL);
4156 		}
4157 
4158 		return TRUE;
4159 	}
4160 
4161 	flags = camel_message_info_flags (info);
4162 
4163 	folder_is_trash =
4164 		((list->folder->folder_flags & CAMEL_FOLDER_IS_TRASH) != 0);
4165 
4166 	/* If a message was marked as deleted and the user flags it as
4167 	 * important or unread in a non-Trash folder, then undelete the
4168 	 * message.  We avoid automatically undeleting messages while
4169 	 * viewing a Trash folder because it would cause the message to
4170 	 * suddenly disappear from the message list, which is confusing
4171 	 * and alarming to the user. */
4172 	if (!folder_is_trash && flags & CAMEL_MESSAGE_DELETED) {
4173 		if (col == COL_FLAGGED && !(flags & CAMEL_MESSAGE_FLAGGED))
4174 			flag |= CAMEL_MESSAGE_DELETED;
4175 
4176 		if (col == COL_MESSAGE_STATUS && (flags & CAMEL_MESSAGE_SEEN))
4177 			flag |= CAMEL_MESSAGE_DELETED;
4178 	}
4179 
4180 	uid = camel_message_info_uid (info);
4181 	camel_folder_set_message_flags (list->folder, uid, flag, ~flags);
4182 
4183 	/* Notify the folder tree model that the user has marked a message
4184 	 * as unread so it doesn't mistake the event as new mail arriving. */
4185 	if (col == COL_MESSAGE_STATUS && (flags & CAMEL_MESSAGE_SEEN)) {
4186 		EMFolderTreeModel *model;
4187 
4188 		model = em_folder_tree_model_get_default ();
4189 		em_folder_tree_model_user_marked_unread (
4190 			model, list->folder, 1);
4191 	}
4192 
4193 	if (flag == CAMEL_MESSAGE_SEEN && list->seen_id) {
4194 		g_source_remove (list->seen_id);
4195 		list->seen_id = 0;
4196 	}
4197 
4198 	return TRUE;
4199 }
4200 
4201 struct _ml_selected_data {
4202 	MessageList *ml;
4203 	GPtrArray *uids;
4204 };
4205 
4206 static void
4207 ml_getselected_cb (ETreePath path,
4208                    gpointer user_data)
4209 {
4210 	struct _ml_selected_data *data = user_data;
4211 	const gchar *uid;
4212 
4213 	if (e_tree_model_node_is_root (data->ml->model, path))
4214 		return;
4215 
4216 	uid = get_message_uid (data->ml, path);
4217 	g_return_if_fail (uid != NULL);
4218 	g_ptr_array_add (data->uids, g_strdup (uid));
4219 }
4220 
4221 GPtrArray *
4222 message_list_get_uids (MessageList *ml)
4223 {
4224 	struct _ml_selected_data data = {
4225 		ml,
4226 		g_ptr_array_new ()
4227 	};
4228 
4229 	e_tree_path_foreach (E_TREE (ml), ml_getselected_cb, &data);
4230 
4231 	if (ml->folder && data.uids->len)
4232 		camel_folder_sort_uids (ml->folder, data.uids);
4233 
4234 	return data.uids;
4235 }
4236 
4237 GPtrArray *
4238 message_list_get_selected (MessageList *ml)
4239 {
4240 	struct _ml_selected_data data = {
4241 		ml,
4242 		g_ptr_array_new ()
4243 	};
4244 
4245 	e_tree_selected_path_foreach (E_TREE (ml), ml_getselected_cb, &data);
4246 
4247 	if (ml->folder && data.uids->len)
4248 		camel_folder_sort_uids (ml->folder, data.uids);
4249 
4250 	return data.uids;
4251 }
4252 
4253 void
4254 message_list_set_selected (MessageList *ml,
4255                            GPtrArray *uids)
4256 {
4257 	gint i;
4258 	ETreeSelectionModel *etsm;
4259 	ETreePath node;
4260 	GPtrArray *paths = g_ptr_array_new ();
4261 
4262 	etsm = (ETreeSelectionModel *) e_tree_get_selection_model (E_TREE (ml));
4263 	for (i = 0; i < uids->len; i++) {
4264 		node = g_hash_table_lookup (ml->uid_nodemap, uids->pdata[i]);
4265 		if (node)
4266 			g_ptr_array_add (paths, node);
4267 	}
4268 
4269 	e_tree_selection_model_select_paths (etsm, paths);
4270 	g_ptr_array_free (paths, TRUE);
4271 }
4272 
4273 struct ml_count_data {
4274 	MessageList *ml;
4275 	guint count;
4276 };
4277 
4278 static void
4279 ml_getcount_cb (ETreePath path,
4280                 gpointer user_data)
4281 {
4282 	struct ml_count_data *data = user_data;
4283 
4284 	if (!e_tree_model_node_is_root (data->ml->model, path))
4285 		data->count++;
4286 }
4287 
4288 guint
4289 message_list_count (MessageList *message_list)
4290 {
4291 	struct ml_count_data data = { message_list, 0 };
4292 
4293 	g_return_val_if_fail (message_list != NULL, 0);
4294 	g_return_val_if_fail (IS_MESSAGE_LIST (message_list), 0);
4295 
4296 	e_tree_path_foreach (E_TREE (message_list), ml_getcount_cb, &data);
4297 
4298 	return data.count;
4299 }
4300 
4301 static void
4302 ml_getselcount_cb (gint model_row,
4303                    gpointer user_data)
4304 {
4305 	struct ml_count_data *data = user_data;
4306 
4307 	data->count++;
4308 }
4309 
4310 guint
4311 message_list_selected_count (MessageList *message_list)
4312 {
4313 	struct ml_count_data data = { message_list, 0 };
4314 
4315 	g_return_val_if_fail (message_list != NULL, 0);
4316 	g_return_val_if_fail (IS_MESSAGE_LIST (message_list), 0);
4317 
4318 	e_tree_selected_row_foreach (E_TREE (message_list), ml_getselcount_cb, &data);
4319 
4320 	return data.count;
4321 }
4322 
4323 void
4324 message_list_freeze (MessageList *ml)
4325 {
4326 	ml->frozen++;
4327 }
4328 
4329 void
4330 message_list_thaw (MessageList *ml)
4331 {
4332 	g_return_if_fail (ml->frozen != 0);
4333 
4334 	ml->frozen--;
4335 	if (ml->frozen == 0) {
4336 		mail_regen_list (ml, ml->frozen_search ? ml->frozen_search : ml->search, NULL, NULL, TRUE);
4337 		g_free (ml->frozen_search);
4338 		ml->frozen_search = NULL;
4339 	}
4340 }
4341 
4342 /* set whether we are in threaded view or flat view */
4343 void
4344 message_list_set_threaded_expand_all (MessageList *ml)
4345 {
4346 	if (ml->threaded) {
4347 		ml->expand_all = 1;
4348 
4349 		if (ml->frozen == 0)
4350 			mail_regen_list (ml, ml->search, NULL, NULL, TRUE);
4351 	}
4352 }
4353 
4354 void
4355 message_list_set_threaded_collapse_all (MessageList *ml)
4356 {
4357 	if (ml->threaded) {
4358 		ml->collapse_all = 1;
4359 
4360 		if (ml->frozen == 0)
4361 			mail_regen_list (ml, ml->search, NULL, NULL, TRUE);
4362 	}
4363 }
4364 
4365 void
4366 message_list_set_threaded (MessageList *ml,
4367                            gboolean threaded)
4368 {
4369 	if (ml->threaded != threaded) {
4370 		ml->threaded = threaded;
4371 
4372 		if (ml->frozen == 0)
4373 			mail_regen_list (ml, ml->search, NULL, NULL, TRUE);
4374 	}
4375 }
4376 
4377 void
4378 message_list_set_hidedeleted (MessageList *ml,
4379                               gboolean hidedeleted)
4380 {
4381 	if (ml->hidedeleted != hidedeleted) {
4382 		ml->hidedeleted = hidedeleted;
4383 
4384 		if (ml->frozen == 0)
4385 			mail_regen_list (ml, ml->search, NULL, NULL, TRUE);
4386 	}
4387 }
4388 
4389 void
4390 message_list_set_search (MessageList *ml,
4391                          const gchar *search)
4392 {
4393 	if (search == NULL || search[0] == '\0')
4394 		if (ml->search == NULL || ml->search[0] == '\0')
4395 			return;
4396 
4397 	if (search != NULL && ml->search != NULL && strcmp (search, ml->search) == 0)
4398 		return;
4399 
4400 	if (ml->thread_tree) {
4401 		camel_folder_thread_messages_unref (ml->thread_tree);
4402 		ml->thread_tree = NULL;
4403 	}
4404 
4405 	if (ml->frozen == 0)
4406 		mail_regen_list (ml, search, NULL, NULL, TRUE);
4407 	else {
4408 		g_free (ml->frozen_search);
4409 		ml->frozen_search = g_strdup (search);
4410 	}
4411 }
4412 
4413 /* will ensure that the message with UID uid will be in the message list after the next rebuild */
4414 void
4415 message_list_ensure_message (MessageList *ml,
4416                              const gchar *uid)
4417 {
4418 	g_return_if_fail (ml != NULL);
4419 
4420 	g_free (ml->ensure_uid);
4421 	ml->ensure_uid = g_strdup (uid);
4422 }
4423 
4424 struct sort_column_data {
4425 	ETableCol *col;
4426 	gboolean ascending;
4427 };
4428 
4429 struct sort_message_info_data {
4430 	CamelMessageInfo *mi;
4431 	GPtrArray *values; /* read values so far, in order of sort_array_data::sort_columns */
4432 };
4433 
4434 struct sort_array_data {
4435 	MessageList *ml;
4436 	CamelFolder *folder;
4437 	GPtrArray *sort_columns; /* struct sort_column_data in order of sorting */
4438 	GHashTable *message_infos; /* uid -> struct sort_message_info_data */
4439 	gpointer cmp_cache;
4440 	GCancellable *cancellable;
4441 };
4442 
4443 static gint
4444 cmp_array_uids (gconstpointer a,
4445                 gconstpointer b,
4446                 gpointer user_data)
4447 {
4448 	const gchar *uid1 = *(const gchar **) a;
4449 	const gchar *uid2 = *(const gchar **) b;
4450 	struct sort_array_data *sort_data = user_data;
4451 	gint i, res = 0;
4452 	struct sort_message_info_data *md1, *md2;
4453 
4454 	g_return_val_if_fail (sort_data != NULL, 0);
4455 
4456 	md1 = g_hash_table_lookup (sort_data->message_infos, uid1);
4457 	md2 = g_hash_table_lookup (sort_data->message_infos, uid2);
4458 
4459 	g_return_val_if_fail (md1 != NULL, 0);
4460 	g_return_val_if_fail (md1->mi != NULL, 0);
4461 	g_return_val_if_fail (md2 != NULL, 0);
4462 	g_return_val_if_fail (md2->mi != NULL, 0);
4463 
4464 	if (!sort_data->ml ||
4465 	    sort_data->folder != sort_data->ml->folder ||
4466 	    g_cancellable_is_cancelled (sort_data->cancellable))
4467 		return 0;
4468 
4469 	for (i = 0;
4470 	     res == 0
4471 	     && i < sort_data->sort_columns->len
4472 	     && !g_cancellable_is_cancelled (sort_data->cancellable);
4473 	     i++) {
4474 		gpointer v1, v2;
4475 		struct sort_column_data *scol = g_ptr_array_index (sort_data->sort_columns, i);
4476 
4477 		if (md1->values->len <= i) {
4478 			v1 = ml_tree_value_at_ex (NULL, NULL, scol->col->compare_col, md1->mi, sort_data->ml);
4479 			g_ptr_array_add (md1->values, v1);
4480 		} else {
4481 			v1 = g_ptr_array_index (md1->values, i);
4482 		}
4483 
4484 		if (md2->values->len <= i) {
4485 			v2 = ml_tree_value_at_ex (NULL, NULL, scol->col->compare_col, md2->mi, sort_data->ml);
4486 			g_ptr_array_add (md2->values, v2);
4487 		} else {
4488 			v2 = g_ptr_array_index (md2->values, i);
4489 		}
4490 
4491 		if (v1 != NULL && v2 != NULL) {
4492 			res = (*scol->col->compare) (v1, v2, sort_data->cmp_cache);
4493 		} else if (v1 != NULL || v2 != NULL) {
4494 			res = v1 == NULL ? -1 : 1;
4495 		}
4496 
4497 		if (!scol->ascending)
4498 			res = res * (-1);
4499 	}
4500 
4501 	if (res == 0)
4502 		res = camel_folder_cmp_uids (sort_data->folder, uid1, uid2);
4503 
4504 	return res;
4505 }
4506 
4507 static void
4508 free_message_info_data (gpointer uid,
4509                         struct sort_message_info_data *data,
4510                         struct sort_array_data *sort_data)
4511 {
4512 	if (data->values) {
4513 		/* values in this array are not newly allocated, even ml_tree_value_at_ex
4514 		 * returns gpointer, not a gconstpointer */
4515 		g_ptr_array_free (data->values, TRUE);
4516 	}
4517 
4518 	camel_folder_free_message_info (sort_data->folder, data->mi);
4519 	g_free (data);
4520 }
4521 
4522 static void
4523 ml_sort_uids_by_tree (MessageList *ml,
4524                       GPtrArray *uids,
4525                       GCancellable *cancellable)
4526 {
4527 	ETreeTableAdapter *adapter;
4528 	ETableSortInfo *sort_info;
4529 	ETableHeader *full_header;
4530 	struct sort_array_data sort_data;
4531 	guint i, len;
4532 
4533 	if (g_cancellable_is_cancelled (cancellable))
4534 		return;
4535 
4536 	g_return_if_fail (ml != NULL);
4537 	g_return_if_fail (ml->folder != NULL);
4538 	g_return_if_fail (uids != NULL);
4539 
4540 	adapter = e_tree_get_table_adapter (E_TREE (ml));
4541 	g_return_if_fail (adapter != NULL);
4542 
4543 	sort_info = e_tree_table_adapter_get_sort_info (adapter);
4544 	full_header = e_tree_table_adapter_get_header (adapter);
4545 
4546 	if (!sort_info || uids->len == 0 || !full_header || e_table_sort_info_sorting_get_count (sort_info) == 0) {
4547 		camel_folder_sort_uids (ml->folder, uids);
4548 		return;
4549 	}
4550 
4551 	len = e_table_sort_info_sorting_get_count (sort_info);
4552 
4553 	sort_data.ml = ml;
4554 	sort_data.folder = g_object_ref (ml->folder);
4555 	sort_data.sort_columns = g_ptr_array_sized_new (len);
4556 	sort_data.message_infos = g_hash_table_new (g_str_hash, g_str_equal);
4557 	sort_data.cmp_cache = e_table_sorting_utils_create_cmp_cache ();
4558 	sort_data.cancellable = cancellable;
4559 
4560 	for (i = 0;
4561 	     i < len
4562 	     && ml->folder == sort_data.folder
4563 	     && !g_cancellable_is_cancelled (cancellable);
4564 	     i++) {
4565 		ETableSortColumn scol;
4566 		struct sort_column_data *data = g_new0 (struct sort_column_data, 1);
4567 
4568 		scol = e_table_sort_info_sorting_get_nth (sort_info, i);
4569 
4570 		data->ascending = scol.ascending;
4571 		data->col = e_table_header_get_column_by_col_idx (full_header, scol.column);
4572 		if (data->col == NULL)
4573 			data->col = e_table_header_get_column (full_header, e_table_header_count (full_header) - 1);
4574 
4575 		g_ptr_array_add (sort_data.sort_columns, data);
4576 	}
4577 
4578 	camel_folder_summary_prepare_fetch_all (ml->folder->summary, NULL);
4579 
4580 	for (i = 0;
4581 	     i < uids->len
4582 	     && ml->folder == sort_data.folder
4583 	     && !g_cancellable_is_cancelled (cancellable);
4584 	     i++) {
4585 		gchar *uid;
4586 		CamelMessageInfo *mi;
4587 		struct sort_message_info_data *md;
4588 
4589 		uid = g_ptr_array_index (uids, i);
4590 		mi = camel_folder_get_message_info (sort_data.folder, uid);
4591 		if (!mi) {
4592 			g_warning ("%s: Cannot find uid '%s' in folder '%s'", G_STRFUNC, uid, camel_folder_get_full_name (sort_data.folder));
4593 			continue;
4594 		}
4595 
4596 		md = g_new0 (struct sort_message_info_data, 1);
4597 		md->mi = mi;
4598 		md->values = g_ptr_array_sized_new (len);
4599 
4600 		g_hash_table_insert (sort_data.message_infos, uid, md);
4601 	}
4602 
4603 	if (sort_data.folder == ml->folder && !g_cancellable_is_cancelled (cancellable))
4604 		g_qsort_with_data (uids->pdata, uids->len, sizeof (gpointer), cmp_array_uids, &sort_data);
4605 
4606 	camel_folder_summary_unlock (sort_data.folder->summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
4607 
4608 	g_hash_table_foreach (sort_data.message_infos, (GHFunc) free_message_info_data, &sort_data);
4609 	g_hash_table_destroy (sort_data.message_infos);
4610 
4611 	g_ptr_array_foreach (sort_data.sort_columns, (GFunc) g_free, NULL);
4612 	g_ptr_array_free (sort_data.sort_columns, TRUE);
4613 
4614 	e_table_sorting_utils_free_cmp_cache (sort_data.cmp_cache);
4615 
4616 	g_object_unref (sort_data.folder);
4617 }
4618 
4619 /* ** REGENERATE MESSAGELIST ********************************************** */
4620 struct _regen_list_msg {
4621 	MailMsg base;
4622 
4623 	gint complete;
4624 
4625 	MessageList *ml;
4626 	gchar *search;
4627 	gchar *hideexpr;
4628 	CamelFolderChangeInfo *changes;
4629 	gboolean dotree;	/* we are building a tree */
4630 	gboolean hidedel;	/* we want to/dont want to show deleted messages */
4631 	gboolean hidejunk;	/* we want to/dont want to show junk messages */
4632 	gboolean thread_subject;
4633 	gboolean scroll_to_cursor; /* whether ensure scrolling to the cursor after rebuild */
4634 	CamelFolderThread *tree;
4635 
4636 	CamelFolder *folder;
4637 	GPtrArray *summary;
4638 
4639 	gint last_row; /* last selected (cursor) row */
4640 
4641 	xmlDoc *expand_state; /* stored expanded state of the previous view */
4642 };
4643 
4644 /*
4645  * maintain copy of summary
4646  *
4647  * any new messages added
4648  * any removed removed, etc.
4649  *
4650  * use vfolder to implement searches ???
4651  */
4652 
4653 static gchar *
4654 regen_list_desc (struct _regen_list_msg *m)
4655 {
4656 	return g_strdup (_("Generating message list"));
4657 }
4658 
4659 static void
4660 regen_list_exec (struct _regen_list_msg *m,
4661                  GCancellable *cancellable,
4662                  GError **error)
4663 {
4664 	GPtrArray *uids, *searchuids = NULL;
4665 	CamelMessageInfo *info;
4666 	ETreePath cursor;
4667 	ETree *tree;
4668 	gint i;
4669 	gchar *expr = NULL;
4670 	GError *local_error = NULL;
4671 
4672 	if (m->folder != m->ml->folder)
4673 		return;
4674 
4675 	tree = E_TREE (m->ml);
4676 	cursor = e_tree_get_cursor (tree);
4677 	if (cursor)
4678 		m->last_row = e_tree_table_adapter_row_of_node (e_tree_get_table_adapter (tree), cursor);
4679 
4680 	/* if we have hidedeleted on, use a search to find it out, merge with existing search if set */
4681 	if (m->hidedel) {
4682 		if (m->hidejunk) {
4683 			if (m->search) {
4684 				expr = g_alloca (strlen (m->search) + 92);
4685 				sprintf (expr, "(and (match-all (and (not (system-flag \"deleted\")) (not (system-flag \"junk\"))))\n %s)", m->search);
4686 			} else
4687 				expr = (gchar *) "(match-all (and (not (system-flag \"deleted\")) (not (system-flag \"junk\"))))";
4688 		} else {
4689 			if (m->search) {
4690 				expr = g_alloca (strlen (m->search) + 64);
4691 				sprintf (expr, "(and (match-all (not (system-flag \"deleted\")))\n %s)", m->search);
4692 			} else
4693 				expr = (gchar *) "(match-all (not (system-flag \"deleted\")))";
4694 		}
4695 	} else {
4696 		if (m->hidejunk) {
4697 			if (m->search) {
4698 				expr = g_alloca (strlen (m->search) + 64);
4699 				sprintf (expr, "(and (match-all (not (system-flag \"junk\")))\n %s)", m->search);
4700 			} else
4701 				expr = (gchar *) "(match-all (not (system-flag \"junk\")))";
4702 		} else {
4703 			expr = m->search;
4704 		}
4705 	}
4706 
4707 	if (expr == NULL) {
4708 		uids = camel_folder_get_uids (m->folder);
4709 	} else {
4710 		gboolean store_has_vjunk = folder_store_supports_vjunk_folder (m->folder);
4711 
4712 		searchuids = uids = camel_folder_search_by_expression (
4713 			m->folder, expr, cancellable, &local_error);
4714 		/* If m->changes is not NULL, then it means we are called from folder_changed event,
4715 		 * thus we will keep the selected message to be sure it doesn't disappear because
4716 		 * it no longer belong to our search filter. */
4717 		if (uids && ((m->changes && m->ml->cursor_uid) || m->ml->ensure_uid)) {
4718 			const gchar *looking_for = m->ml->cursor_uid;
4719 			/* ensure_uid has precedence of cursor_uid */
4720 			if (m->ml->ensure_uid)
4721 				looking_for = m->ml->ensure_uid;
4722 
4723 			for (i = 0; i < uids->len; i++) {
4724 				if (g_str_equal (looking_for, uids->pdata[i]))
4725 					break;
4726 			}
4727 
4728 			/* cursor_uid has been filtered out */
4729 			if (i == uids->len) {
4730 				CamelMessageInfo *looking_info = camel_folder_get_message_info (m->folder, looking_for);
4731 
4732 				if (looking_info) {
4733 					gboolean is_deleted = (camel_message_info_flags (looking_info) & CAMEL_MESSAGE_DELETED) != 0;
4734 					gboolean is_junk = store_has_vjunk && (camel_message_info_flags (looking_info) & CAMEL_MESSAGE_JUNK) != 0;
4735 
4736 					/* I would really like to check for CAMEL_MESSAGE_FOLDER_FLAGGED on a message,
4737 					 * so I would know whether it was changed locally, and then just check the changes
4738 					 * struct whether change came from the server, but with periodical save it doesn't
4739 					 * matter. So here just check whether the file was deleted and we show it based
4740 					 * on the flag whether we can view deleted messages or not. */
4741 
4742 					if ((!is_deleted || (is_deleted && !m->hidedel)) && (!is_junk || (is_junk && !m->hidejunk)))
4743 						g_ptr_array_add (uids, (gpointer) camel_pstring_strdup (looking_for));
4744 
4745 					camel_folder_free_message_info (m->folder, looking_info);
4746 				}
4747 			}
4748 		}
4749 	}
4750 
4751 	if (local_error != NULL) {
4752 		g_propagate_error (error, local_error);
4753 		return;
4754 	}
4755 
4756 	/* camel_folder_summary_prepare_fetch_all (m->folder->summary, NULL); */
4757 	if (!g_cancellable_is_cancelled (cancellable)) {
4758 		/* update/build a new tree */
4759 		if (m->dotree) {
4760 			ml_sort_uids_by_tree (m->ml, uids, cancellable);
4761 
4762 			if (m->tree)
4763 				camel_folder_thread_messages_apply (m->tree, uids);
4764 			else
4765 				m->tree = camel_folder_thread_messages_new (m->folder, uids, m->thread_subject);
4766 		} else {
4767 			camel_folder_sort_uids (m->ml->folder, uids);
4768 			m->summary = g_ptr_array_new ();
4769 
4770 			camel_folder_summary_prepare_fetch_all (
4771 				m->folder->summary, NULL);
4772 
4773 			for (i = 0; i < uids->len; i++) {
      (emitted by clang-analyzer)TODO: a detailed trace is available in the data model (not yet rendered in this report)
      (emitted by clang-analyzer)TODO: a detailed trace is available in the data model (not yet rendered in this report)
4774 				info = camel_folder_get_message_info (m->folder, uids->pdata[i]);
4775 				if (info)
4776 					g_ptr_array_add (m->summary, info);
4777 			}
4778 		}
4779 
4780 		m->complete = TRUE;
4781 	}
4782 
4783 	if (searchuids)
4784 		camel_folder_search_free (m->folder, searchuids);
4785 	else
4786 		camel_folder_free_uids (m->folder, uids);
4787 }
4788 
4789 static void
4790 regen_list_done (struct _regen_list_msg *m)
4791 {
4792 	ETree *tree;
4793 	GCancellable *cancellable;
4794 	gboolean searching;
4795 
4796 	cancellable = m->base.cancellable;
4797 
4798 	if (m->ml->priv->destroyed)
4799 		return;
4800 
4801 	if (!m->complete)
4802 		return;
4803 
4804 	if (g_cancellable_is_cancelled (cancellable))
4805 		return;
4806 
4807 	if (m->ml->folder != m->folder)
4808 		return;
4809 
4810 	tree = E_TREE (m->ml);
4811 
4812 	if (m->scroll_to_cursor)
4813 		e_tree_show_cursor_after_reflow (tree);
4814 
4815 	g_signal_handlers_block_by_func (e_tree_get_table_adapter (tree), ml_tree_sorting_changed, m->ml);
4816 
4817 	if (m->ml->search && m->ml->search != m->search)
4818 		g_free (m->ml->search);
4819 	m->ml->search = m->search;
4820 	m->search = NULL;
4821 
4822 	searching = m->ml->search && *m->ml->search;
4823 
4824 	if (m->dotree) {
4825 		gboolean forcing_expand_state = m->ml->expand_all || m->ml->collapse_all;
4826 
4827 		if (m->ml->just_set_folder) {
4828 			m->ml->just_set_folder = FALSE;
4829 			if (m->expand_state) {
4830 				/* rather load state from disk than use the memory data when changing folders */
4831 				xmlFreeDoc (m->expand_state);
4832 				m->expand_state = NULL;
4833 			}
4834 		}
4835 
4836 		if (forcing_expand_state || searching)
4837 			e_tree_force_expanded_state (tree, (m->ml->expand_all || searching) ? 1 : -1);
4838 
4839 		build_tree (m->ml, m->tree, m->changes, m->scroll_to_cursor);
4840 		if (m->ml->thread_tree)
4841 			camel_folder_thread_messages_unref (m->ml->thread_tree);
4842 		m->ml->thread_tree = m->tree;
4843 		m->tree = NULL;
4844 
4845 		if (forcing_expand_state || searching) {
4846 			if (m->ml->folder != NULL && tree != NULL && !searching)
4847 				save_tree_state (m->ml);
4848 			/* do not forget to set this back to use the default value... */
4849 			e_tree_force_expanded_state (tree, 0);
4850 		} else
4851 			load_tree_state (m->ml, m->expand_state);
4852 
4853 		m->ml->expand_all = 0;
4854 		m->ml->collapse_all = 0;
4855 	} else
4856 		build_flat (m->ml, m->summary, m->changes);
4857 
4858 	g_mutex_lock (m->ml->regen_lock);
4859 	m->ml->regen = g_list_remove (m->ml->regen, m);
4860 	g_mutex_unlock (m->ml->regen_lock);
4861 
4862 	if (m->ml->regen == NULL && m->ml->pending_select_uid) {
4863 		gchar *uid;
4864 		gboolean with_fallback;
4865 
4866 		uid = m->ml->pending_select_uid;
4867 		m->ml->pending_select_uid = NULL;
4868 		with_fallback = m->ml->pending_select_fallback;
4869 		message_list_select_uid (m->ml, uid, with_fallback);
4870 		g_free (uid);
4871 	} else if (m->ml->regen == NULL && m->ml->cursor_uid == NULL && m->last_row != -1) {
4872 		ETreeTableAdapter *etta = e_tree_get_table_adapter (tree);
4873 
4874 		if (m->last_row >= e_table_model_row_count (E_TABLE_MODEL (etta)))
4875 			m->last_row = e_table_model_row_count (E_TABLE_MODEL (etta)) - 1;
4876 
4877 		if (m->last_row >= 0) {
4878 			ETreePath path;
4879 
4880 			path = e_tree_table_adapter_node_at_row (etta, m->last_row);
4881 			if (path)
4882 				select_path (m->ml, path);
4883 		}
4884 	}
4885 
4886 	if (gtk_widget_get_visible (GTK_WIDGET (m->ml))) {
4887 		if (e_tree_row_count (E_TREE (m->ml)) <= 0) {
4888 			/* space is used to indicate no search too */
4889 			if (m->ml->search && *m->ml->search && strcmp (m->ml->search, " ") != 0)
4890 				e_tree_set_info_message (tree, _("No message satisfies your search criteria. "
4891 					"Change search criteria by selecting a new Show message filter from "
4892 					"the drop down list above or by running a new search either by clearing "
4893 					"it with Search->Clear menu item or by changing the query above."));
4894 			else
4895 				e_tree_set_info_message (tree, _("There are no messages in this folder."));
4896 		} else
4897 			e_tree_set_info_message (tree, NULL);
4898 	}
4899 
4900 	g_signal_handlers_unblock_by_func (e_tree_get_table_adapter (tree), ml_tree_sorting_changed, m->ml);
4901 
4902 	g_signal_emit (m->ml, message_list_signals[MESSAGE_LIST_BUILT], 0);
4903 	m->ml->priv->any_row_changed = FALSE;
4904 }
4905 
4906 static void
4907 regen_list_free (struct _regen_list_msg *m)
4908 {
4909 	gint i;
4910 
4911 	if (m->summary) {
4912 		for (i = 0; i < m->summary->len; i++)
4913 			camel_folder_free_message_info (m->folder, m->summary->pdata[i]);
4914 		g_ptr_array_free (m->summary, TRUE);
4915 	}
4916 
4917 	if (m->tree)
4918 		camel_folder_thread_messages_unref (m->tree);
4919 
4920 	g_free (m->search);
4921 	g_free (m->hideexpr);
4922 
4923 	g_object_unref (m->folder);
4924 
4925 	if (m->changes)
4926 		camel_folder_change_info_free (m->changes);
4927 
4928 	/* we have to poke this here as well since we might've been cancelled and regened wont get called */
4929 	g_mutex_lock (m->ml->regen_lock);
4930 	m->ml->regen = g_list_remove (m->ml->regen, m);
4931 	g_mutex_unlock (m->ml->regen_lock);
4932 
4933 	if (m->expand_state)
4934 		xmlFreeDoc (m->expand_state);
4935 
4936 	g_object_unref (m->ml);
4937 }
4938 
4939 static MailMsgInfo regen_list_info = {
4940 	sizeof (struct _regen_list_msg),
4941 	(MailMsgDescFunc) regen_list_desc,
4942 	(MailMsgExecFunc) regen_list_exec,
4943 	(MailMsgDoneFunc) regen_list_done,
4944 	(MailMsgFreeFunc) regen_list_free
4945 };
4946 
4947 static gboolean
4948 ml_regen_timeout (struct _regen_list_msg *m)
4949 {
4950 	g_mutex_lock (m->ml->regen_lock);
4951 	m->ml->regen = g_list_prepend (m->ml->regen, m);
4952 	g_mutex_unlock (m->ml->regen_lock);
4953 	/* TODO: we should manage our own thread stuff, would make cancelling outstanding stuff easier */
4954 	mail_msg_fast_ordered_push (m);
4955 
4956 	m->ml->regen_timeout_msg = NULL;
4957 	m->ml->regen_timeout_id = 0;
4958 
4959 	return FALSE;
4960 }
4961 
4962 static void
4963 mail_regen_cancel (MessageList *ml)
4964 {
4965 	/* cancel any outstanding regeneration requests, not we don't clear, they clear themselves */
4966 	if (ml->regen) {
4967 		GList *link;
4968 
4969 		g_mutex_lock (ml->regen_lock);
4970 
4971 		for (link = ml->regen; link != NULL; link = link->next) {
4972 			MailMsg *mm = link->data;
4973 			GCancellable *cancellable;
4974 
4975 			cancellable = mm->cancellable;
4976 			g_cancellable_cancel (cancellable);
4977 		}
4978 
4979 		g_mutex_unlock (ml->regen_lock);
4980 	}
4981 
4982 	/* including unqueued ones */
4983 	if (ml->regen_timeout_id) {
4984 		g_source_remove (ml->regen_timeout_id);
4985 		ml->regen_timeout_id = 0;
4986 		mail_msg_unref (ml->regen_timeout_msg);
4987 		ml->regen_timeout_msg = NULL;
4988 	}
4989 }
4990 
4991 static void
4992 mail_regen_list (MessageList *ml,
4993                  const gchar *search,
4994                  const gchar *hideexpr,
4995                  CamelFolderChangeInfo *changes,
4996                  gboolean scroll_to_cursor)
4997 {
4998 	struct _regen_list_msg *m;
4999 	GSettings *settings;
5000 	gboolean thread_subject;
5001 	gboolean searching;
5002 
5003 	/* report empty search as NULL, not as one/two-space string */
5004 	if (search && (strcmp (search, " ") == 0 || strcmp (search, "  ") == 0))
5005 		search = NULL;
5006 
5007 	if (ml->folder == NULL) {
5008 		if (ml->search != search) {
5009 			g_free (ml->search);
5010 			ml->search = g_strdup (search);
5011 		}
5012 		return;
5013 	}
5014 
5015 	mail_regen_cancel (ml);
5016 
5017 	settings = g_settings_new ("org.gnome.evolution.mail");
5018 	thread_subject = g_settings_get_boolean (settings, "thread-subject");
5019 	g_object_unref (settings);
5020 
5021 #ifndef BROKEN_ETREE
5022 	/* this can sometimes crash,so ... */
5023 
5024 	/* see if we need to goto the child thread at all anyway */
5025 	/* currently the only case is the flat view with updates and no search */
5026 	if (hideexpr == NULL && search == NULL && changes != NULL && !ml->threaded) {
5027 		build_flat_diff (ml, changes);
5028 		camel_folder_change_info_free (changes);
5029 		return;
5030 	}
5031 #endif
5032 
5033 	m = mail_msg_new (®en_list_info);
5034 	m->ml = g_object_ref (ml);
5035 	m->search = g_strdup (search);
5036 	m->hideexpr = g_strdup (hideexpr);
5037 	m->changes = changes;
5038 	m->dotree = ml->threaded;
5039 	m->hidedel = ml->hidedeleted;
5040 	m->hidejunk = ml->hidejunk;
5041 	m->thread_subject = thread_subject;
5042 	m->scroll_to_cursor = scroll_to_cursor;
5043 	m->folder = g_object_ref (ml->folder);
5044 	m->last_row = -1;
5045 	m->expand_state = NULL;
5046 
5047 	if ((!m->hidedel || !m->dotree) && ml->thread_tree) {
5048 		camel_folder_thread_messages_unref (ml->thread_tree);
5049 		ml->thread_tree = NULL;
5050 	} else if (ml->thread_tree) {
5051 		m->tree = ml->thread_tree;
5052 		camel_folder_thread_messages_ref (m->tree);
5053 	}
5054 
5055 	searching = ml->search && *ml->search && !g_str_equal (ml->search, " ");
5056 
5057 	if (e_tree_row_count (E_TREE (ml)) <= 0) {
5058 		if (gtk_widget_get_visible (GTK_WIDGET (ml))) {
5059 			/* there is some info why the message list is empty, let it be something useful */
5060 			gchar *txt = g_strconcat (_("Generating message list"), "..." , NULL);
5061 
5062 			e_tree_set_info_message (E_TREE (m->ml), txt);
5063 
5064 			g_free (txt);
5065 		}
5066 	} else if (ml->priv->any_row_changed && m->dotree && !ml->just_set_folder && !searching) {
5067 		/* there has been some change on any row, if it was an expand state change,
5068 		 * then let it save; if not, then nothing happen. */
5069 		message_list_save_state (ml);
5070 	} else if (m->dotree && !ml->just_set_folder && !searching) {
5071 		/* remember actual expand state and restore it after regen */
5072 		m->expand_state = e_tree_save_expanded_state_xml (E_TREE (ml));
5073 	}
5074 
5075 	/* if we're busy already kick off timeout processing, so normal updates are immediate */
5076 	if (ml->regen == NULL)
5077 		ml_regen_timeout (m);
5078 	else {
5079 		ml->regen_timeout_msg = m;
5080 		ml->regen_timeout_id = g_timeout_add (50, (GSourceFunc) ml_regen_timeout, m);
5081 	}
5082 }