evolution-3.6.4/filter/e-filter-rule.c

No issues found

   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  *		Not Zed <notzed@lostzed.mmc.com.au>
  18  *		Jeffrey Stedfast <fejj@ximian.com>
  19  *
  20  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
  21  *
  22  */
  23 
  24 #ifdef HAVE_CONFIG_H
  25 #include <config.h>
  26 #endif
  27 
  28 #include <string.h>
  29 
  30 #include <gtk/gtk.h>
  31 #include <glib/gi18n.h>
  32 
  33 #include "libevolution-utils/e-alert-dialog.h"
  34 
  35 #include "e-filter-rule.h"
  36 #include "e-rule-context.h"
  37 
  38 #define E_FILTER_RULE_GET_PRIVATE(obj) \
  39 	(G_TYPE_INSTANCE_GET_PRIVATE \
  40 	((obj), E_TYPE_FILTER_RULE, EFilterRulePrivate))
  41 
  42 typedef struct _FilterPartData FilterPartData;
  43 typedef struct _FilterRuleData FilterRuleData;
  44 
  45 struct _EFilterRulePrivate {
  46 	gint frozen;
  47 };
  48 
  49 struct _FilterPartData {
  50 	EFilterRule *rule;
  51 	ERuleContext *context;
  52 	EFilterPart *part;
  53 	GtkWidget *partwidget;
  54 	GtkWidget *container;
  55 };
  56 
  57 struct _FilterRuleData {
  58 	EFilterRule *rule;
  59 	ERuleContext *context;
  60 	GtkWidget *parts;
  61 };
  62 
  63 enum {
  64 	CHANGED,
  65 	LAST_SIGNAL
  66 };
  67 
  68 static guint signals[LAST_SIGNAL];
  69 
  70 G_DEFINE_TYPE (
  71 	EFilterRule,
  72 	e_filter_rule,
  73 	G_TYPE_OBJECT)
  74 
  75 static void
  76 filter_rule_grouping_changed_cb (GtkComboBox *combo_box,
  77                                  EFilterRule *rule)
  78 {
  79 	rule->grouping = gtk_combo_box_get_active (combo_box);
  80 }
  81 
  82 static void
  83 filter_rule_threading_changed_cb (GtkComboBox *combo_box,
  84                                   EFilterRule *rule)
  85 {
  86 	rule->threading = gtk_combo_box_get_active (combo_box);
  87 }
  88 
  89 static void
  90 part_combobox_changed (GtkComboBox *combobox,
  91                        FilterPartData *data)
  92 {
  93 	EFilterPart *part = NULL;
  94 	EFilterPart *newpart;
  95 	gint index, i;
  96 
  97 	index = gtk_combo_box_get_active (combobox);
  98 	for (i = 0, part = e_rule_context_next_part (data->context, part);
  99 		part && i < index;
 100 		i++, part = e_rule_context_next_part (data->context, part)) {
 101 		/* traverse until reached index */
 102 	}
 103 
 104 	g_return_if_fail (part != NULL);
 105 	g_return_if_fail (i == index);
 106 
 107 	/* dont update if we haven't changed */
 108 	if (!strcmp (part->title, data->part->title))
 109 		return;
 110 
 111 	/* here we do a widget shuffle, throw away the old widget/rulepart,
 112 	 * and create another */
 113 	if (data->partwidget)
 114 		gtk_container_remove (GTK_CONTAINER (data->container), data->partwidget);
 115 
 116 	newpart = e_filter_part_clone (part);
 117 	e_filter_part_copy_values (newpart, data->part);
 118 	e_filter_rule_replace_part (data->rule, data->part, newpart);
 119 	g_object_unref (data->part);
 120 	data->part = newpart;
 121 	data->partwidget = e_filter_part_get_widget (newpart);
 122 	if (data->partwidget)
 123 		gtk_box_pack_start (
 124 			GTK_BOX (data->container),
 125 			data->partwidget, TRUE, TRUE, 0);
 126 }
 127 
 128 static GtkWidget *
 129 get_rule_part_widget (ERuleContext *context,
 130                       EFilterPart *newpart,
 131                       EFilterRule *rule)
 132 {
 133 	EFilterPart *part = NULL;
 134 	GtkWidget *combobox;
 135 	GtkWidget *hbox;
 136 	GtkWidget *p;
 137 	gint index = 0, current = 0;
 138 	FilterPartData *data;
 139 
 140 	data = g_malloc0 (sizeof (*data));
 141 	data->rule = rule;
 142 	data->context = context;
 143 	data->part = newpart;
 144 
 145 	hbox = gtk_hbox_new (FALSE, 0);
 146 	/* only set to automatically clean up the memory */
 147 	g_object_set_data_full ((GObject *) hbox, "data", data, g_free);
 148 
 149 	p = e_filter_part_get_widget (newpart);
 150 
 151 	data->partwidget = p;
 152 	data->container = hbox;
 153 
 154 	combobox = gtk_combo_box_text_new ();
 155 
 156 	/* sigh, this is a little ugly */
 157 	while ((part = e_rule_context_next_part (context, part))) {
 158 		gtk_combo_box_text_append_text (
 159 			GTK_COMBO_BOX_TEXT (combobox), _(part->title));
 160 
 161 		if (!strcmp (newpart->title, part->title))
 162 			current = index;
 163 
 164 		index++;
 165 	}
 166 
 167 	gtk_combo_box_set_active (GTK_COMBO_BOX (combobox), current);
 168 	g_signal_connect (
 169 		combobox, "changed",
 170 		G_CALLBACK (part_combobox_changed), data);
 171 	gtk_widget_show (combobox);
 172 
 173 	gtk_box_pack_start (GTK_BOX (hbox), combobox, FALSE, FALSE, 0);
 174 	if (p)
 175 		gtk_box_pack_start (GTK_BOX (hbox), p, TRUE, TRUE, 0);
 176 
 177 	gtk_widget_show_all (hbox);
 178 
 179 	return hbox;
 180 }
 181 
 182 static void
 183 less_parts (GtkWidget *button,
 184             FilterRuleData *data)
 185 {
 186 	EFilterPart *part;
 187 	GtkWidget *rule;
 188 	FilterPartData *part_data;
 189 
 190 	if (g_list_length (data->rule->parts) < 1)
 191 		return;
 192 
 193 	rule = g_object_get_data ((GObject *) button, "rule");
 194 	part_data = g_object_get_data ((GObject *) rule, "data");
 195 
 196 	g_return_if_fail (part_data != NULL);
 197 
 198 	part = part_data->part;
 199 
 200 	/* remove the part from the list */
 201 	e_filter_rule_remove_part (data->rule, part);
 202 	g_object_unref (part);
 203 
 204 	/* and from the display */
 205 	gtk_container_remove (GTK_CONTAINER (data->parts), rule);
 206 	gtk_container_remove (GTK_CONTAINER (data->parts), button);
 207 }
 208 
 209 static void
 210 attach_rule (GtkWidget *rule,
 211              FilterRuleData *data,
 212              EFilterPart *part,
 213              gint row)
 214 {
 215 	GtkWidget *remove;
 216 
 217 	gtk_table_attach (
 218 		GTK_TABLE (data->parts), rule, 0, 1, row, row + 1,
 219 		GTK_EXPAND | GTK_FILL, 0, 0, 0);
 220 
 221 	remove = gtk_button_new_from_stock (GTK_STOCK_REMOVE);
 222 	g_object_set_data ((GObject *) remove, "rule", rule);
 223 	g_signal_connect (
 224 		remove, "clicked",
 225 		G_CALLBACK (less_parts), data);
 226 	gtk_table_attach (
 227 		GTK_TABLE (data->parts), remove, 1, 2, row, row + 1,
 228 		0, 0, 0, 0);
 229 
 230 	gtk_widget_show (remove);
 231 }
 232 
 233 static void
 234 do_grab_focus_cb (GtkWidget *widget,
 235                   gpointer data)
 236 {
 237 	gboolean *done = (gboolean *) data;
 238 
 239 	if (*done || !widget)
 240 		return;
 241 
 242 	if (gtk_widget_get_can_focus (widget) || GTK_IS_COMBO_BOX (widget)) {
 243 		*done = TRUE;
 244 		gtk_widget_grab_focus (widget);
 245 	} else if (GTK_IS_CONTAINER (widget)) {
 246 		gtk_container_foreach (GTK_CONTAINER (widget), do_grab_focus_cb, done);
 247 	}
 248 }
 249 
 250 static void
 251 more_parts (GtkWidget *button,
 252             FilterRuleData *data)
 253 {
 254 	EFilterPart *new;
 255 
 256 	/* first make sure that the last part is ok */
 257 	if (data->rule->parts) {
 258 		EFilterPart *part;
 259 		GList *l;
 260 		EAlert *alert = NULL;
 261 
 262 		l = g_list_last (data->rule->parts);
 263 		part = l->data;
 264 		if (!e_filter_part_validate (part, &alert)) {
 265 			GtkWidget *toplevel;
 266 			toplevel = gtk_widget_get_toplevel (button);
 267 			e_alert_run_dialog (GTK_WINDOW (toplevel), alert);
 268 			return;
 269 		}
 270 	}
 271 
 272 	/* create a new rule entry, use the first type of rule */
 273 	new = e_rule_context_next_part (data->context, NULL);
 274 	if (new) {
 275 		GtkWidget *w;
 276 		guint rows;
 277 
 278 		new = e_filter_part_clone (new);
 279 		e_filter_rule_add_part (data->rule, new);
 280 		w = get_rule_part_widget (data->context, new, data->rule);
 281 
 282 		g_object_get (data->parts, "n-rows", &rows, NULL);
 283 		gtk_table_resize (GTK_TABLE (data->parts), rows + 1, 2);
 284 		attach_rule (w, data, new, rows);
 285 
 286 		if (GTK_IS_CONTAINER (w)) {
 287 			gboolean done = FALSE;
 288 
 289 			gtk_container_foreach (GTK_CONTAINER (w), do_grab_focus_cb, &done);
 290 		} else
 291 			gtk_widget_grab_focus (w);
 292 
 293 		/* also scroll down to see new part */
 294 		w = (GtkWidget *) g_object_get_data (G_OBJECT (button), "scrolled-window");
 295 		if (w) {
 296 			GtkAdjustment *adjustment;
 297 
 298 			adjustment = gtk_scrolled_window_get_vadjustment (
 299 				GTK_SCROLLED_WINDOW (w));
 300 			if (adjustment) {
 301 				gdouble upper;
 302 
 303 				upper = gtk_adjustment_get_upper (adjustment);
 304 				gtk_adjustment_set_value (adjustment, upper);
 305 			}
 306 
 307 		}
 308 	}
 309 }
 310 
 311 static void
 312 name_changed (GtkEntry *entry,
 313               EFilterRule *rule)
 314 {
 315 	g_free (rule->name);
 316 	rule->name = g_strdup (gtk_entry_get_text (entry));
 317 }
 318 
 319 GtkWidget *
 320 e_filter_rule_get_widget (EFilterRule *rule,
 321                           ERuleContext *context)
 322 {
 323 	EFilterRuleClass *class;
 324 
 325 	g_return_val_if_fail (E_IS_FILTER_RULE (rule), NULL);
 326 	g_return_val_if_fail (E_IS_RULE_CONTEXT (context), NULL);
 327 
 328 	class = E_FILTER_RULE_GET_CLASS (rule);
 329 	g_return_val_if_fail (class->get_widget != NULL, NULL);
 330 
 331 	return class->get_widget (rule, context);
 332 }
 333 
 334 static void
 335 filter_rule_load_set (xmlNodePtr node,
 336                       EFilterRule *rule,
 337                       ERuleContext *context)
 338 {
 339 	xmlNodePtr work;
 340 	gchar *rulename;
 341 	EFilterPart *part;
 342 
 343 	work = node->children;
 344 	while (work) {
 345 		if (!strcmp ((gchar *) work->name, "part")) {
 346 			rulename = (gchar *) xmlGetProp (work, (xmlChar *)"name");
 347 			part = e_rule_context_find_part (context, rulename);
 348 			if (part) {
 349 				part = e_filter_part_clone (part);
 350 				e_filter_part_xml_decode (part, work);
 351 				e_filter_rule_add_part (rule, part);
 352 			} else {
 353 				g_warning ("cannot find rule part '%s'\n", rulename);
 354 			}
 355 			xmlFree (rulename);
 356 		} else if (work->type == XML_ELEMENT_NODE) {
 357 			g_warning ("Unknown xml node in part: %s", work->name);
 358 		}
 359 		work = work->next;
 360 	}
 361 }
 362 
 363 static void
 364 filter_rule_finalize (GObject *object)
 365 {
 366 	EFilterRule *rule = E_FILTER_RULE (object);
 367 
 368 	g_free (rule->name);
 369 	g_free (rule->source);
 370 
 371 	g_list_foreach (rule->parts, (GFunc) g_object_unref, NULL);
 372 	g_list_free (rule->parts);
 373 
 374 	/* Chain up to parent's finalize() method. */
 375 	G_OBJECT_CLASS (e_filter_rule_parent_class)->finalize (object);
 376 }
 377 
 378 static gint
 379 filter_rule_validate (EFilterRule *rule,
 380                       EAlert **alert)
 381 {
 382 	gint valid = TRUE;
 383 	GList *parts;
 384 
 385 	g_warn_if_fail (alert == NULL || *alert == NULL);
 386 	if (!rule->name || !*rule->name) {
 387 		if (alert)
 388 			*alert = e_alert_new ("filter:no-name", NULL);
 389 
 390 		return FALSE;
 391 	}
 392 
 393 	/* validate rule parts */
 394 	parts = rule->parts;
 395 	valid = parts != NULL;
 396 	while (parts && valid) {
 397 		valid = e_filter_part_validate ((EFilterPart *) parts->data, alert);
 398 		parts = parts->next;
 399 	}
 400 
 401 	return valid;
 402 }
 403 
 404 static gint
 405 filter_rule_eq (EFilterRule *rule_a,
 406                 EFilterRule *rule_b)
 407 {
 408 	GList *link_a;
 409 	GList *link_b;
 410 
 411 	if (rule_a->enabled != rule_b->enabled)
 412 		return FALSE;
 413 
 414 	if (rule_a->grouping != rule_b->grouping)
 415 		return FALSE;
 416 
 417 	if (rule_a->threading != rule_b->threading)
 418 		return FALSE;
 419 
 420 	if (g_strcmp0 (rule_a->name, rule_b->name) != 0)
 421 		return FALSE;
 422 
 423 	if (g_strcmp0 (rule_a->source, rule_b->source) != 0)
 424 		return FALSE;
 425 
 426 	link_a = rule_a->parts;
 427 	link_b = rule_b->parts;
 428 
 429 	while (link_a != NULL && link_b != NULL) {
 430 		EFilterPart *part_a = link_a->data;
 431 		EFilterPart *part_b = link_b->data;
 432 
 433 		if (!e_filter_part_eq (part_a, part_b))
 434 			return FALSE;
 435 
 436 		link_a = g_list_next (link_a);
 437 		link_b = g_list_next (link_b);
 438 	}
 439 
 440 	if (link_a != NULL || link_b != NULL)
 441 		return FALSE;
 442 
 443 	return TRUE;
 444 }
 445 
 446 static xmlNodePtr
 447 filter_rule_xml_encode (EFilterRule *rule)
 448 {
 449 	xmlNodePtr node, set, work;
 450 	GList *l;
 451 
 452 	node = xmlNewNode (NULL, (xmlChar *)"rule");
 453 
 454 	xmlSetProp (
 455 		node, (xmlChar *)"enabled",
 456 		(xmlChar *)(rule->enabled ? "true" : "false"));
 457 
 458 	switch (rule->grouping) {
 459 	case E_FILTER_GROUP_ALL:
 460 		xmlSetProp (node, (xmlChar *)"grouping", (xmlChar *)"all");
 461 		break;
 462 	case E_FILTER_GROUP_ANY:
 463 		xmlSetProp (node, (xmlChar *)"grouping", (xmlChar *)"any");
 464 		break;
 465 	}
 466 
 467 	switch (rule->threading) {
 468 	case E_FILTER_THREAD_NONE:
 469 		break;
 470 	case E_FILTER_THREAD_ALL:
 471 		xmlSetProp (node, (xmlChar *)"threading", (xmlChar *)"all");
 472 		break;
 473 	case E_FILTER_THREAD_REPLIES:
 474 		xmlSetProp (node, (xmlChar *)"threading", (xmlChar *)"replies");
 475 		break;
 476 	case E_FILTER_THREAD_REPLIES_PARENTS:
 477 		xmlSetProp (node, (xmlChar *)"threading", (xmlChar *)"replies_parents");
 478 		break;
 479 	case E_FILTER_THREAD_SINGLE:
 480 		xmlSetProp (node, (xmlChar *)"threading", (xmlChar *)"single");
 481 		break;
 482 	}
 483 
 484 	if (rule->source) {
 485 		xmlSetProp (node, (xmlChar *)"source", (xmlChar *) rule->source);
 486 	} else {
 487 		/* set to the default filter type */
 488 		xmlSetProp (node, (xmlChar *)"source", (xmlChar *)"incoming");
 489 	}
 490 
 491 	if (rule->name) {
 492 		gchar *escaped = g_markup_escape_text (rule->name, -1);
 493 
 494 		work = xmlNewNode (NULL, (xmlChar *)"title");
 495 		xmlNodeSetContent (work, (xmlChar *) escaped);
 496 		xmlAddChild (node, work);
 497 
 498 		g_free (escaped);
 499 	}
 500 
 501 	set = xmlNewNode (NULL, (xmlChar *)"partset");
 502 	xmlAddChild (node, set);
 503 	l = rule->parts;
 504 	while (l) {
 505 		work = e_filter_part_xml_encode ((EFilterPart *) l->data);
 506 		xmlAddChild (set, work);
 507 		l = l->next;
 508 	}
 509 
 510 	return node;
 511 }
 512 
 513 static gint
 514 filter_rule_xml_decode (EFilterRule *rule,
 515                         xmlNodePtr node,
 516                         ERuleContext *context)
 517 {
 518 	xmlNodePtr work;
 519 	gchar *grouping;
 520 	gchar *source;
 521 
 522 	g_free (rule->name);
 523 	rule->name = NULL;
 524 
 525 	grouping = (gchar *) xmlGetProp (node, (xmlChar *)"enabled");
 526 	if (!grouping)
 527 		rule->enabled = TRUE;
 528 	else {
 529 		rule->enabled = strcmp (grouping, "false") != 0;
 530 		xmlFree (grouping);
 531 	}
 532 
 533 	grouping = (gchar *) xmlGetProp (node, (xmlChar *)"grouping");
 534 	if (!strcmp (grouping, "any"))
 535 		rule->grouping = E_FILTER_GROUP_ANY;
 536 	else
 537 		rule->grouping = E_FILTER_GROUP_ALL;
 538 	xmlFree (grouping);
 539 
 540 	rule->threading = E_FILTER_THREAD_NONE;
 541 	if (context->flags & E_RULE_CONTEXT_THREADING
 542 	    && (grouping = (gchar *) xmlGetProp (node, (xmlChar *)"threading"))) {
 543 		if (!strcmp (grouping, "all"))
 544 			rule->threading = E_FILTER_THREAD_ALL;
 545 		else if (!strcmp (grouping, "replies"))
 546 			rule->threading = E_FILTER_THREAD_REPLIES;
 547 		else if (!strcmp (grouping, "replies_parents"))
 548 			rule->threading = E_FILTER_THREAD_REPLIES_PARENTS;
 549 		else if (!strcmp (grouping, "single"))
 550 			rule->threading = E_FILTER_THREAD_SINGLE;
 551 		xmlFree (grouping);
 552 	}
 553 
 554 	g_free (rule->source);
 555 	source = (gchar *) xmlGetProp (node, (xmlChar *)"source");
 556 	if (source) {
 557 		rule->source = g_strdup (source);
 558 		xmlFree (source);
 559 	} else {
 560 		/* default filter type */
 561 		rule->source = g_strdup ("incoming");
 562 	}
 563 
 564 	work = node->children;
 565 	while (work) {
 566 		if (!strcmp ((gchar *) work->name, "partset")) {
 567 			filter_rule_load_set (work, rule, context);
 568 		} else if (!strcmp ((gchar *) work->name, "title") ||
 569 			!strcmp ((gchar *) work->name, "_title")) {
 570 
 571 			if (!rule->name) {
 572 				gchar *str, *decstr = NULL;
 573 
 574 				str = (gchar *) xmlNodeGetContent (work);
 575 				if (str) {
 576 					decstr = g_strdup (_(str));
 577 					xmlFree (str);
 578 				}
 579 				rule->name = decstr;
 580 			}
 581 		}
 582 		work = work->next;
 583 	}
 584 
 585 	return 0;
 586 }
 587 
 588 static void
 589 filter_rule_build_code (EFilterRule *rule,
 590                         GString *out)
 591 {
 592 	switch (rule->threading) {
 593 	case E_FILTER_THREAD_NONE:
 594 		break;
 595 	case E_FILTER_THREAD_ALL:
 596 		g_string_append (out, " (match-threads \"all\" ");
 597 		break;
 598 	case E_FILTER_THREAD_REPLIES:
 599 		g_string_append (out, " (match-threads \"replies\" ");
 600 		break;
 601 	case E_FILTER_THREAD_REPLIES_PARENTS:
 602 		g_string_append (out, " (match-threads \"replies_parents\" ");
 603 		break;
 604 	case E_FILTER_THREAD_SINGLE:
 605 		g_string_append (out, " (match-threads \"single\" ");
 606 		break;
 607 	}
 608 
 609 	switch (rule->grouping) {
 610 	case E_FILTER_GROUP_ALL:
 611 		g_string_append (out, " (and\n  ");
 612 		break;
 613 	case E_FILTER_GROUP_ANY:
 614 		g_string_append (out, " (or\n  ");
 615 		break;
 616 	default:
 617 		g_warning ("Invalid grouping");
 618 	}
 619 
 620 	e_filter_part_build_code_list (rule->parts, out);
 621 	g_string_append (out, ")\n");
 622 
 623 	if (rule->threading != E_FILTER_THREAD_NONE)
 624 		g_string_append (out, ")\n");
 625 }
 626 
 627 static void
 628 filter_rule_copy (EFilterRule *dest,
 629                   EFilterRule *src)
 630 {
 631 	GList *node;
 632 
 633 	dest->enabled = src->enabled;
 634 
 635 	g_free (dest->name);
 636 	dest->name = g_strdup (src->name);
 637 
 638 	g_free (dest->source);
 639 	dest->source = g_strdup (src->source);
 640 
 641 	dest->grouping = src->grouping;
 642 	dest->threading = src->threading;
 643 
 644 	if (dest->parts) {
 645 		g_list_foreach (dest->parts, (GFunc) g_object_unref, NULL);
 646 		g_list_free (dest->parts);
 647 		dest->parts = NULL;
 648 	}
 649 
 650 	node = src->parts;
 651 	while (node) {
 652 		EFilterPart *part;
 653 
 654 		part = e_filter_part_clone (node->data);
 655 		dest->parts = g_list_append (dest->parts, part);
 656 		node = node->next;
 657 	}
 658 }
 659 
 660 static void
 661 ensure_scrolled_width_cb (GtkAdjustment *adj,
 662                           GParamSpec *param_spec,
 663                           GtkScrolledWindow *scrolled_window)
 664 {
 665 	gtk_scrolled_window_set_min_content_width (
 666 		scrolled_window,
 667 		gtk_adjustment_get_upper (adj));
 668 }
 669 
 670 static void
 671 ensure_scrolled_height_cb (GtkAdjustment *adj,
 672                            GParamSpec *param_spec,
 673                            GtkScrolledWindow *scrolled_window)
 674 {
 675 	GtkWidget *toplevel;
 676 	GdkScreen *screen;
 677 	gint toplevel_height, scw_height, require_scw_height = 0, max_height;
 678 
 679 	toplevel = gtk_widget_get_toplevel (GTK_WIDGET (scrolled_window));
 680 	if (!toplevel || !gtk_widget_is_toplevel (toplevel))
 681 		return;
 682 
 683 	scw_height = gtk_widget_get_allocated_height (GTK_WIDGET (scrolled_window));
 684 
 685 	gtk_widget_get_preferred_height_for_width (gtk_bin_get_child (GTK_BIN (scrolled_window)),
 686 		gtk_widget_get_allocated_width (GTK_WIDGET (scrolled_window)),
 687 		&require_scw_height, NULL);
 688 
 689 	if (scw_height >= require_scw_height) {
 690 		if (require_scw_height > 0)
 691 			gtk_scrolled_window_set_min_content_height (scrolled_window, require_scw_height);
 692 		return;
 693 	}
 694 
 695 	if (!GTK_IS_WINDOW (toplevel) ||
 696 	    !gtk_widget_get_window (toplevel))
 697 		return;
 698 
 699 	screen = gtk_window_get_screen (GTK_WINDOW (toplevel));
 700 	if (screen) {
 701 		gint monitor;
 702 		GdkRectangle workarea;
 703 
 704 		monitor = gdk_screen_get_monitor_at_window (screen, gtk_widget_get_window (toplevel));
 705 		if (monitor < 0)
 706 			monitor = 0;
 707 
 708 		gdk_screen_get_monitor_workarea (screen, monitor, &workarea);
 709 
 710 		/* can enlarge up to 4 / 5 monitor's work area height */
 711 		max_height = workarea.height * 4 / 5;
 712 	} else {
 713 		return;
 714 	}
 715 
 716 	toplevel_height = gtk_widget_get_allocated_height (toplevel);
 717 	if (toplevel_height + require_scw_height - scw_height > max_height)
 718 		return;
 719 
 720 	gtk_scrolled_window_set_min_content_height (scrolled_window, require_scw_height);
 721 }
 722 
 723 static GtkWidget *
 724 filter_rule_get_widget (EFilterRule *rule,
 725                         ERuleContext *context)
 726 {
 727 	GtkGrid *hgrid, *vgrid, *inframe;
 728 	GtkWidget *parts, *add, *label, *name, *w;
 729 	GtkWidget *combobox;
 730 	GtkWidget *scrolledwindow;
 731 	GtkAdjustment *hadj, *vadj;
 732 	GList *l;
 733 	gchar *text;
 734 	EFilterPart *part;
 735 	FilterRuleData *data;
 736 	gint rows, i;
 737 
 738 	/* this stuff should probably be a table, but the
 739 	 * rule parts need to be a vbox */
 740 	vgrid = GTK_GRID (gtk_grid_new ());
 741 	gtk_grid_set_row_spacing (vgrid, 6);
 742 	gtk_orientable_set_orientation (GTK_ORIENTABLE (vgrid), GTK_ORIENTATION_VERTICAL);
 743 
 744 	label = gtk_label_new_with_mnemonic (_("R_ule name:"));
 745 	name = gtk_entry_new ();
 746 	gtk_widget_set_hexpand (name, TRUE);
 747 	gtk_widget_set_halign (name, GTK_ALIGN_FILL);
 748 	gtk_label_set_mnemonic_widget ((GtkLabel *) label, name);
 749 
 750 	if (!rule->name) {
 751 		rule->name = g_strdup (_("Untitled"));
 752 		gtk_entry_set_text (GTK_ENTRY (name), rule->name);
 753 		/* FIXME: do we want the following code in the future? */
 754 		/*gtk_editable_select_region (GTK_EDITABLE (name), 0, -1);*/
 755 	} else {
 756 		gtk_entry_set_text (GTK_ENTRY (name), rule->name);
 757 	}
 758 
 759 	g_signal_connect (
 760 		name, "realize",
 761 		G_CALLBACK (gtk_widget_grab_focus), name);
 762 
 763 	hgrid = GTK_GRID (gtk_grid_new ());
 764 	gtk_grid_set_column_spacing (hgrid, 12);
 765 
 766 	gtk_grid_attach (hgrid, label, 0, 0, 1, 1);
 767 	gtk_grid_attach_next_to (hgrid, name, label, GTK_POS_RIGHT, 1, 1);
 768 
 769 	gtk_container_add (GTK_CONTAINER (vgrid), GTK_WIDGET (hgrid));
 770 
 771 	g_signal_connect (
 772 		name, "changed",
 773 		G_CALLBACK (name_changed), rule);
 774 
 775 	hgrid = GTK_GRID (gtk_grid_new ());
 776 	gtk_grid_set_column_spacing (hgrid, 12);
 777 	gtk_container_add (GTK_CONTAINER (vgrid), GTK_WIDGET (hgrid));
 778 
 779 	/* this is the parts table, it should probably be inside a scrolling list */
 780 	rows = g_list_length (rule->parts);
 781 	parts = gtk_table_new (rows, 2, FALSE);
 782 
 783 	/* data for the parts part of the display */
 784 	data = g_malloc0 (sizeof (*data));
 785 	data->context = context;
 786 	data->rule = rule;
 787 	data->parts = parts;
 788 
 789 	/* only set to automatically clean up the memory */
 790 	g_object_set_data_full ((GObject *) vgrid, "data", data, g_free);
 791 
 792 	if (context->flags & E_RULE_CONTEXT_GROUPING) {
 793 		const gchar *thread_types[] = {
 794 			N_("all the following conditions"),
 795 			N_("any of the following conditions")
 796 		};
 797 
 798 		hgrid = GTK_GRID (gtk_grid_new ());
 799 		gtk_grid_set_column_spacing (hgrid, 12);
 800 
 801 		label = gtk_label_new_with_mnemonic (_("_Find items which match:"));
 802 		combobox = gtk_combo_box_text_new ();
 803 
 804 		for (i = 0; i < 2; i++) {
 805 			gtk_combo_box_text_append_text (
 806 				GTK_COMBO_BOX_TEXT (combobox),
 807 				_(thread_types[i]));
 808 		}
 809 
 810 		gtk_label_set_mnemonic_widget ((GtkLabel *) label, combobox);
 811 		gtk_combo_box_set_active (GTK_COMBO_BOX (combobox), rule->grouping);
 812 
 813 		gtk_grid_attach (hgrid, label, 0, 0, 1, 1);
 814 		gtk_grid_attach_next_to (hgrid, combobox, label, GTK_POS_RIGHT, 1, 1);
 815 
 816 		g_signal_connect (
 817 			combobox, "changed",
 818 			G_CALLBACK (filter_rule_grouping_changed_cb), rule);
 819 
 820 		gtk_container_add (GTK_CONTAINER (vgrid), GTK_WIDGET (hgrid));
 821 	} else {
 822 		text = g_strdup_printf (
 823 			"<b>%s</b>",
 824 			_("Find items that meet the following conditions"));
 825 		label = gtk_label_new (text);
 826 		gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
 827 		gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
 828 		gtk_container_add (GTK_CONTAINER (vgrid), label);
 829 		g_free (text);
 830 	}
 831 
 832 	hgrid = GTK_GRID (gtk_grid_new ());
 833 	gtk_grid_set_column_spacing (hgrid, 12);
 834 
 835 	if (context->flags & E_RULE_CONTEXT_THREADING) {
 836 		const gchar *thread_types[] = {
 837 			/* Translators: "None" for not including threads;
 838 			 * part of "Include threads: None" */
 839 			N_("None"),
 840 			N_("All related"),
 841 			N_("Replies"),
 842 			N_("Replies and parents"),
 843 			N_("No reply or parent")
 844 		};
 845 
 846 		label = gtk_label_new_with_mnemonic (_("I_nclude threads:"));
 847 		combobox = gtk_combo_box_text_new ();
 848 
 849 		for (i = 0; i < 5; i++) {
 850 			gtk_combo_box_text_append_text (
 851 				GTK_COMBO_BOX_TEXT (combobox),
 852 				_(thread_types[i]));
 853 		}
 854 
 855 		gtk_label_set_mnemonic_widget ((GtkLabel *) label, combobox);
 856 		gtk_combo_box_set_active (GTK_COMBO_BOX (combobox), rule->threading);
 857 
 858 		gtk_grid_attach (hgrid, label, 0, 0, 1, 1);
 859 		gtk_grid_attach_next_to (hgrid, combobox, label, GTK_POS_RIGHT, 1, 1);
 860 
 861 		g_signal_connect (
 862 			combobox, "changed",
 863 			G_CALLBACK (filter_rule_threading_changed_cb), rule);
 864 	}
 865 
 866 	gtk_container_add (GTK_CONTAINER (vgrid), GTK_WIDGET (hgrid));
 867 
 868 	hgrid = GTK_GRID (gtk_grid_new ());
 869 	gtk_grid_set_column_spacing (hgrid, 3);
 870 	gtk_widget_set_vexpand (GTK_WIDGET (hgrid), TRUE);
 871 	gtk_widget_set_valign (GTK_WIDGET (hgrid), GTK_ALIGN_FILL);
 872 
 873 	gtk_container_add (GTK_CONTAINER (vgrid), GTK_WIDGET (hgrid));
 874 
 875 	label = gtk_label_new ("");
 876 	gtk_grid_attach (hgrid, label, 0, 0, 1, 1);
 877 
 878 	inframe = GTK_GRID (gtk_grid_new ());
 879 	gtk_grid_set_row_spacing (inframe, 6);
 880 	gtk_orientable_set_orientation (GTK_ORIENTABLE (inframe), GTK_ORIENTATION_VERTICAL);
 881 	gtk_widget_set_hexpand (GTK_WIDGET (inframe), TRUE);
 882 	gtk_widget_set_halign (GTK_WIDGET (inframe), GTK_ALIGN_FILL);
 883 	gtk_widget_set_vexpand (GTK_WIDGET (inframe), TRUE);
 884 	gtk_widget_set_valign (GTK_WIDGET (inframe), GTK_ALIGN_FILL);
 885 	gtk_grid_attach_next_to (hgrid, GTK_WIDGET (inframe), label, GTK_POS_RIGHT, 1, 1);
 886 
 887 	l = rule->parts;
 888 	i = 0;
 889 	while (l) {
 890 		part = l->data;
 891 		w = get_rule_part_widget (context, part, rule);
 892 		attach_rule (w, data, part, i++);
 893 		l = g_list_next (l);
 894 	}
 895 
 896 	hadj = GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 1.0, 1.0, 1.0, 1.0));
 897 	vadj = GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 1.0, 1.0, 1.0, 1.0));
 898 	scrolledwindow = gtk_scrolled_window_new (hadj, vadj);
 899 
 900 	g_signal_connect (
 901 		hadj, "notify::upper",
 902 		G_CALLBACK (ensure_scrolled_width_cb), scrolledwindow);
 903 	g_signal_connect (
 904 		vadj, "notify::upper",
 905 		G_CALLBACK (ensure_scrolled_height_cb), scrolledwindow);
 906 
 907 	gtk_scrolled_window_set_policy (
 908 		GTK_SCROLLED_WINDOW (scrolledwindow),
 909 		GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
 910 
 911 	gtk_scrolled_window_add_with_viewport (
 912 		GTK_SCROLLED_WINDOW (scrolledwindow), parts);
 913 
 914 	gtk_widget_set_vexpand (scrolledwindow, TRUE);
 915 	gtk_widget_set_valign (scrolledwindow, GTK_ALIGN_FILL);
 916 	gtk_widget_set_hexpand (scrolledwindow, TRUE);
 917 	gtk_widget_set_halign (scrolledwindow, GTK_ALIGN_FILL);
 918 	gtk_container_add (GTK_CONTAINER (inframe), scrolledwindow);
 919 
 920 	hgrid = GTK_GRID (gtk_grid_new ());
 921 	gtk_grid_set_column_spacing (hgrid, 3);
 922 
 923 	add = gtk_button_new_with_mnemonic (_("A_dd Condition"));
 924 	gtk_button_set_image (
 925 		GTK_BUTTON (add), gtk_image_new_from_stock (
 926 		GTK_STOCK_ADD, GTK_ICON_SIZE_BUTTON));
 927 	g_signal_connect (
 928 		add, "clicked",
 929 		G_CALLBACK (more_parts), data);
 930 	gtk_grid_attach (hgrid, add, 0, 0, 1, 1);
 931 
 932 	gtk_container_add (GTK_CONTAINER (inframe), GTK_WIDGET (hgrid));
 933 
 934 	gtk_widget_show_all (GTK_WIDGET (vgrid));
 935 
 936 	g_object_set_data (G_OBJECT (add), "scrolled-window", scrolledwindow);
 937 
 938 	return GTK_WIDGET (vgrid);
 939 }
 940 
 941 static void
 942 e_filter_rule_class_init (EFilterRuleClass *class)
 943 {
 944 	GObjectClass *object_class;
 945 
 946 	g_type_class_add_private (class, sizeof (EFilterRulePrivate));
 947 
 948 	object_class = G_OBJECT_CLASS (class);
 949 	object_class->finalize = filter_rule_finalize;
 950 
 951 	class->validate = filter_rule_validate;
 952 	class->eq = filter_rule_eq;
 953 	class->xml_encode = filter_rule_xml_encode;
 954 	class->xml_decode = filter_rule_xml_decode;
 955 	class->build_code = filter_rule_build_code;
 956 	class->copy = filter_rule_copy;
 957 	class->get_widget = filter_rule_get_widget;
 958 
 959 	signals[CHANGED] = g_signal_new (
 960 		"changed",
 961 		E_TYPE_FILTER_RULE,
 962 		G_SIGNAL_RUN_LAST,
 963 		G_STRUCT_OFFSET (EFilterRuleClass, changed),
 964 		NULL,
 965 		NULL,
 966 		g_cclosure_marshal_VOID__VOID,
 967 		G_TYPE_NONE, 0);
 968 }
 969 
 970 static void
 971 e_filter_rule_init (EFilterRule *rule)
 972 {
 973 	rule->priv = E_FILTER_RULE_GET_PRIVATE (rule);
 974 	rule->enabled = TRUE;
 975 }
 976 
 977 /**
 978  * filter_rule_new:
 979  *
 980  * Create a new EFilterRule object.
 981  *
 982  * Return value: A new #EFilterRule object.
 983  **/
 984 EFilterRule *
 985 e_filter_rule_new (void)
 986 {
 987 	return g_object_new (E_TYPE_FILTER_RULE, NULL);
 988 }
 989 
 990 EFilterRule *
 991 e_filter_rule_clone (EFilterRule *rule)
 992 {
 993 	EFilterRule *clone;
 994 
 995 	g_return_val_if_fail (E_IS_FILTER_RULE (rule), NULL);
 996 
 997 	clone = g_object_new (G_OBJECT_TYPE (rule), NULL);
 998 	e_filter_rule_copy (clone, rule);
 999 
1000 	return clone;
1001 }
1002 
1003 void
1004 e_filter_rule_set_name (EFilterRule *rule,
1005                         const gchar *name)
1006 {
1007 	g_return_if_fail (E_IS_FILTER_RULE (rule));
1008 
1009 	if (g_strcmp0 (rule->name, name) == 0)
1010 		return;
1011 
1012 	g_free (rule->name);
1013 	rule->name = g_strdup (name);
1014 
1015 	e_filter_rule_emit_changed (rule);
1016 }
1017 
1018 void
1019 e_filter_rule_set_source (EFilterRule *rule,
1020                           const gchar *source)
1021 {
1022 	g_return_if_fail (E_IS_FILTER_RULE (rule));
1023 
1024 	if (g_strcmp0 (rule->source, source) == 0)
1025 		return;
1026 
1027 	g_free (rule->source);
1028 	rule->source = g_strdup (source);
1029 
1030 	e_filter_rule_emit_changed (rule);
1031 }
1032 
1033 gint
1034 e_filter_rule_validate (EFilterRule *rule,
1035                         EAlert **alert)
1036 {
1037 	EFilterRuleClass *class;
1038 
1039 	g_return_val_if_fail (E_IS_FILTER_RULE (rule), FALSE);
1040 
1041 	class = E_FILTER_RULE_GET_CLASS (rule);
1042 	g_return_val_if_fail (class->validate != NULL, FALSE);
1043 
1044 	return class->validate (rule, alert);
1045 }
1046 
1047 gint
1048 e_filter_rule_eq (EFilterRule *rule_a,
1049                   EFilterRule *rule_b)
1050 {
1051 	EFilterRuleClass *class;
1052 
1053 	g_return_val_if_fail (E_IS_FILTER_RULE (rule_a), FALSE);
1054 	g_return_val_if_fail (E_IS_FILTER_RULE (rule_b), FALSE);
1055 
1056 	class = E_FILTER_RULE_GET_CLASS (rule_a);
1057 	g_return_val_if_fail (class->eq != NULL, FALSE);
1058 
1059 	if (G_OBJECT_TYPE (rule_a) != G_OBJECT_TYPE (rule_b))
1060 		return FALSE;
1061 
1062 	return class->eq (rule_a, rule_b);
1063 }
1064 
1065 xmlNodePtr
1066 e_filter_rule_xml_encode (EFilterRule *rule)
1067 {
1068 	EFilterRuleClass *class;
1069 
1070 	g_return_val_if_fail (E_IS_FILTER_RULE (rule), NULL);
1071 
1072 	class = E_FILTER_RULE_GET_CLASS (rule);
1073 	g_return_val_if_fail (class->xml_encode != NULL, NULL);
1074 
1075 	return class->xml_encode (rule);
1076 }
1077 
1078 gint
1079 e_filter_rule_xml_decode (EFilterRule *rule,
1080                           xmlNodePtr node,
1081                           ERuleContext *context)
1082 {
1083 	EFilterRuleClass *class;
1084 	gint result;
1085 
1086 	g_return_val_if_fail (E_IS_FILTER_RULE (rule), FALSE);
1087 	g_return_val_if_fail (node != NULL, FALSE);
1088 	g_return_val_if_fail (E_IS_RULE_CONTEXT (context), FALSE);
1089 
1090 	class = E_FILTER_RULE_GET_CLASS (rule);
1091 	g_return_val_if_fail (class->xml_decode != NULL, FALSE);
1092 
1093 	rule->priv->frozen++;
1094 	result = class->xml_decode (rule, node, context);
1095 	rule->priv->frozen--;
1096 
1097 	e_filter_rule_emit_changed (rule);
1098 
1099 	return result;
1100 }
1101 
1102 void
1103 e_filter_rule_copy (EFilterRule *dst_rule,
1104                     EFilterRule *src_rule)
1105 {
1106 	EFilterRuleClass *class;
1107 
1108 	g_return_if_fail (E_IS_FILTER_RULE (dst_rule));
1109 	g_return_if_fail (E_IS_FILTER_RULE (src_rule));
1110 
1111 	class = E_FILTER_RULE_GET_CLASS (dst_rule);
1112 	g_return_if_fail (class->copy != NULL);
1113 
1114 	class->copy (dst_rule, src_rule);
1115 
1116 	e_filter_rule_emit_changed (dst_rule);
1117 }
1118 
1119 void
1120 e_filter_rule_add_part (EFilterRule *rule,
1121                         EFilterPart *part)
1122 {
1123 	g_return_if_fail (E_IS_FILTER_RULE (rule));
1124 	g_return_if_fail (E_IS_FILTER_PART (part));
1125 
1126 	rule->parts = g_list_append (rule->parts, part);
1127 
1128 	e_filter_rule_emit_changed (rule);
1129 }
1130 
1131 void
1132 e_filter_rule_remove_part (EFilterRule *rule,
1133                            EFilterPart *part)
1134 {
1135 	g_return_if_fail (E_IS_FILTER_RULE (rule));
1136 	g_return_if_fail (E_IS_FILTER_PART (part));
1137 
1138 	rule->parts = g_list_remove (rule->parts, part);
1139 
1140 	e_filter_rule_emit_changed (rule);
1141 }
1142 
1143 void
1144 e_filter_rule_replace_part (EFilterRule *rule,
1145                             EFilterPart *old_part,
1146                             EFilterPart *new_part)
1147 {
1148 	GList *link;
1149 
1150 	g_return_if_fail (E_IS_FILTER_RULE (rule));
1151 	g_return_if_fail (E_IS_FILTER_PART (old_part));
1152 	g_return_if_fail (E_IS_FILTER_PART (new_part));
1153 
1154 	link = g_list_find (rule->parts, old_part);
1155 	if (link != NULL)
1156 		link->data = new_part;
1157 	else
1158 		rule->parts = g_list_append (rule->parts, new_part);
1159 
1160 	e_filter_rule_emit_changed (rule);
1161 }
1162 
1163 void
1164 e_filter_rule_build_code (EFilterRule *rule,
1165                           GString *out)
1166 {
1167 	EFilterRuleClass *class;
1168 
1169 	g_return_if_fail (E_IS_FILTER_RULE (rule));
1170 	g_return_if_fail (out != NULL);
1171 
1172 	class = E_FILTER_RULE_GET_CLASS (rule);
1173 	g_return_if_fail (class->build_code != NULL);
1174 
1175 	class->build_code (rule, out);
1176 }
1177 
1178 void
1179 e_filter_rule_emit_changed (EFilterRule *rule)
1180 {
1181 	g_return_if_fail (E_IS_FILTER_RULE (rule));
1182 
1183 	if (rule->priv->frozen == 0)
1184 		g_signal_emit (rule, signals[CHANGED], 0);
1185 }
1186 
1187 EFilterRule *
1188 e_filter_rule_next_list (GList *list,
1189                          EFilterRule *last,
1190                          const gchar *source)
1191 {
1192 	GList *link = list;
1193 
1194 	if (last != NULL) {
1195 		link = g_list_find (link, last);
1196 		if (link == NULL)
1197 			link = list;
1198 		else
1199 			link = g_list_next (link);
1200 	}
1201 
1202 	if (source != NULL) {
1203 		while (link != NULL) {
1204 			EFilterRule *rule = link->data;
1205 
1206 			if (g_strcmp0 (rule->source, source) == 0)
1207 				break;
1208 
1209 			link = g_list_next (link);
1210 		}
1211 	}
1212 
1213 	return (link != NULL) ? link->data : NULL;
1214 }
1215 
1216 EFilterRule *
1217 e_filter_rule_find_list (GList *list,
1218                          const gchar *name,
1219                          const gchar *source)
1220 {
1221 	GList *link;
1222 
1223 	g_return_val_if_fail (name != NULL, FALSE);
1224 
1225 	for (link = list; link != NULL; link = g_list_next (link)) {
1226 		EFilterRule *rule = link->data;
1227 
1228 		if (strcmp (rule->name, name) == 0)
1229 			if (source == NULL || (rule->source != NULL &&
1230 				strcmp (rule->source, source) == 0))
1231 				return rule;
1232 	}
1233 
1234 	return NULL;
1235 }
1236 
1237 #ifdef FOR_TRANSLATIONS_ONLY
1238 
1239 static gchar *list[] = {
1240   N_("Incoming"), N_("Outgoing")
1241 };
1242 #endif