evolution-3.6.4/mail/message-list.c

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;
Value stored to 'found_re' is never read
(emitted by clang-analyzer)

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

Value stored to 'found_re' is never read
(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