1 /*
2  * This program is free software; you can redistribute it and/or modify it
3  * under the terms of the GNU Lesser General Public License as published by
4  * the Free Software Foundation.
5  *
6  * This program is distributed in the hope that it will be useful, but
7  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
8  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
9  * for more details.
10  *
11  * You should have received a copy of the GNU Lesser General Public License
12  * along with this program; if not, see <http://www.gnu.org/licenses/>.
13  *
14  *
15  * Authors:
16  *		Not Zed <notzed@lostzed.mmc.com.au>
17  *		Jeffrey Stedfast <fejj@ximian.com>
18  *
19  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
20  *
21  */
22 
23 #include "evolution-config.h"
24 
25 #include <string.h>
26 
27 #include <gtk/gtk.h>
28 #include <glib/gi18n.h>
29 
30 #include "e-alert-dialog.h"
31 #include "e-dialog-widgets.h"
32 #include "e-filter-rule.h"
33 #include "e-rule-context.h"
34 #include "e-misc-utils.h"
35 
36 #define E_FILTER_RULE_GET_PRIVATE(obj) \
37 	(G_TYPE_INSTANCE_GET_PRIVATE \
38 	((obj), E_TYPE_FILTER_RULE, EFilterRulePrivate))
39 
40 typedef struct _FilterPartData FilterPartData;
41 typedef struct _FilterRuleData FilterRuleData;
42 
43 struct _EFilterRulePrivate {
44 	gint frozen;
45 };
46 
47 struct _FilterPartData {
48 	EFilterRule *rule;
49 	ERuleContext *context;
50 	EFilterPart *part;
51 	GtkWidget *partwidget;
52 	GtkWidget *container;
53 };
54 
55 struct _FilterRuleData {
56 	EFilterRule *rule;
57 	ERuleContext *context;
58 	GtkGrid *parts_grid;
59 	GtkWidget *drag_widget;
60 
61 	gint n_rows;
62 };
63 
64 enum {
65 	CHANGED,
66 	LAST_SIGNAL
67 };
68 
69 static guint signals[LAST_SIGNAL];
70 
G_DEFINE_TYPE(EFilterRule,e_filter_rule,G_TYPE_OBJECT)71 G_DEFINE_TYPE (
72 	EFilterRule,
73 	e_filter_rule,
74 	G_TYPE_OBJECT)
75 
76 static void
77 filter_rule_grouping_changed_cb (GtkComboBox *combo_box,
78                                  EFilterRule *rule)
79 {
80 	rule->grouping = gtk_combo_box_get_active (combo_box);
81 }
82 
83 static void
filter_rule_threading_changed_cb(GtkComboBox * combo_box,EFilterRule * rule)84 filter_rule_threading_changed_cb (GtkComboBox *combo_box,
85                                   EFilterRule *rule)
86 {
87 	rule->threading = gtk_combo_box_get_active (combo_box);
88 }
89 
90 static void
part_combobox_changed(GtkComboBox * combobox,FilterPartData * data)91 part_combobox_changed (GtkComboBox *combobox,
92                        FilterPartData *data)
93 {
94 	EFilterPart *part = NULL;
95 	EFilterPart *newpart;
96 	gint index, i;
97 
98 	index = gtk_combo_box_get_active (combobox);
99 	for (i = 0, part = e_rule_context_next_part (data->context, part);
100 		part && i < index;
101 		i++, part = e_rule_context_next_part (data->context, part)) {
102 		/* traverse until reached index */
103 	}
104 
105 	if (!part) {
106 		g_warn_if_reached ();
107 		return;
108 	}
109 
110 	g_return_if_fail (i == index);
111 
112 	/* dont update if we haven't changed */
113 	if (!strcmp (part->title, data->part->title))
114 		return;
115 
116 	/* here we do a widget shuffle, throw away the old widget/rulepart,
117 	 * and create another */
118 	if (data->partwidget)
119 		gtk_container_remove (GTK_CONTAINER (data->container), data->partwidget);
120 
121 	newpart = e_filter_part_clone (part);
122 	e_filter_part_copy_values (newpart, data->part);
123 	e_filter_rule_replace_part (data->rule, data->part, newpart);
124 	g_object_unref (data->part);
125 	data->part = newpart;
126 	data->partwidget = e_filter_part_get_widget (newpart);
127 	if (data->partwidget)
128 		gtk_box_pack_start (
129 			GTK_BOX (data->container),
130 			data->partwidget, TRUE, TRUE, 0);
131 }
132 
133 static GtkWidget *
get_rule_part_widget(ERuleContext * context,EFilterPart * newpart,EFilterRule * rule)134 get_rule_part_widget (ERuleContext *context,
135                       EFilterPart *newpart,
136                       EFilterRule *rule)
137 {
138 	EFilterPart *part = NULL;
139 	GtkWidget *combobox;
140 	GtkWidget *hbox;
141 	GtkWidget *p;
142 	gint index = 0, current = 0;
143 	FilterPartData *data;
144 
145 	data = g_malloc0 (sizeof (*data));
146 	data->rule = rule;
147 	data->context = context;
148 	data->part = newpart;
149 
150 	hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
151 	/* only set to automatically clean up the memory */
152 	g_object_set_data_full ((GObject *) hbox, "data", data, g_free);
153 
154 	p = e_filter_part_get_widget (newpart);
155 
156 	data->partwidget = p;
157 	data->container = hbox;
158 
159 	combobox = gtk_combo_box_text_new ();
160 
161 	/* sigh, this is a little ugly */
162 	while ((part = e_rule_context_next_part (context, part))) {
163 		gtk_combo_box_text_append_text (
164 			GTK_COMBO_BOX_TEXT (combobox), _(part->title));
165 
166 		if (!strcmp (newpart->title, part->title))
167 			current = index;
168 
169 		index++;
170 	}
171 
172 	gtk_combo_box_set_active (GTK_COMBO_BOX (combobox), current);
173 	g_signal_connect (
174 		combobox, "changed",
175 		G_CALLBACK (part_combobox_changed), data);
176 	gtk_widget_show (combobox);
177 
178 	gtk_box_pack_start (GTK_BOX (hbox), combobox, FALSE, FALSE, 0);
179 	if (p)
180 		gtk_box_pack_start (GTK_BOX (hbox), p, TRUE, TRUE, 0);
181 
182 	gtk_widget_show_all (hbox);
183 
184 	return hbox;
185 }
186 
187 enum {
188 	DND_TYPE_FILTER_RULE,
189 	N_DND_TYPES
190 };
191 
192 static GtkTargetEntry dnd_types[] = {
193 	{ (gchar *) "x-evolution-filter-rule", GTK_TARGET_SAME_APP, DND_TYPE_FILTER_RULE }
194 };
195 
196 static GdkAtom dnd_atoms[N_DND_TYPES] = { 0 };
197 
198 static void
event_box_drag_begin(GtkWidget * widget,GdkDragContext * context,gpointer user_data)199 event_box_drag_begin (GtkWidget *widget,
200 		      GdkDragContext *context,
201 		      gpointer user_data)
202 {
203 	FilterRuleData *data = user_data;
204 	cairo_surface_t *surface;
205 	cairo_t *cr;
206 	GtkStyleContext *style_context;
207 
208 	data->drag_widget = widget;
209 
210 	/* Just 1x1 dot as an image. No need to have there real icon,
211 	   because the move is done immediately */
212 	surface = gdk_window_create_similar_surface ( gtk_widget_get_window (widget), CAIRO_CONTENT_COLOR, 1, 1);
213 	style_context = gtk_widget_get_style_context (widget);
214 	cr = cairo_create (surface);
215 	gtk_render_background (style_context, cr, 0, 0, 1, 1);
216 	cairo_destroy (cr);
217 
218 	cairo_surface_set_device_offset (surface, 0, 0);
219 
220 	gtk_drag_set_icon_surface (context, surface);
221 }
222 
223 static gboolean
event_box_drag_drop(GtkWidget * widget,GdkDragContext * context,gint x,gint y,guint time,gpointer user_data)224 event_box_drag_drop (GtkWidget *widget,
225 		     GdkDragContext *context,
226 		     gint x,
227 		     gint y,
228 		     guint time,
229 		     gpointer user_data)
230 {
231 	FilterRuleData *data = user_data;
232 
233 	data->drag_widget = NULL;
234 
235 	return FALSE;
236 }
237 
238 static void
event_box_drag_end(GtkWidget * widget,GdkDragContext * context,gpointer user_data)239 event_box_drag_end (GtkWidget *widget,
240 		    GdkDragContext *context,
241 		    gpointer user_data)
242 {
243 	FilterRuleData *data = user_data;
244 
245 	data->drag_widget = NULL;
246 }
247 
248 static gboolean
event_box_drag_motion_cb(GtkWidget * widget,GdkDragContext * context,gint x,gint y,guint time,gpointer user_data)249 event_box_drag_motion_cb (GtkWidget *widget,
250 			  GdkDragContext *context,
251 			  gint x,
252 			  gint y,
253 			  guint time,
254 			  gpointer user_data)
255 {
256 	FilterRuleData *data = user_data;
257 
258 	gdk_drag_status (context, widget == data->drag_widget ? 0 : GDK_ACTION_MOVE, time);
259 
260 	if (widget != data->drag_widget) {
261 		gint index, index_src = -1, index_des = -1;
262 
263 		for (index = 0; index < data->n_rows && (index_src == -1 || index_des == -1); index++) {
264 			GtkWidget *event_box;
265 
266 			event_box = gtk_grid_get_child_at (data->parts_grid, 0, index);
267 			if (event_box == data->drag_widget) {
268 				index_src = index;
269 			} else if (event_box == widget) {
270 				index_des = index;
271 			}
272 		}
273 
274 		g_warn_if_fail (index_src != -1);
275 		g_warn_if_fail (index_des != -1);
276 		g_warn_if_fail (index_src != index_des);
277 
278 		if (index_src != -1 && index_des != -1 && index_src != index_des) {
279 			GtkWidget *event_box, *content, *remove_button;
280 			gpointer rule;
281 
282 			/* Move internal data first */
283 			rule = g_list_nth_data (data->rule->parts, index_src);
284 			data->rule->parts = g_list_remove (data->rule->parts, rule);
285 			data->rule->parts = g_list_insert (data->rule->parts, rule, index_des);
286 
287 			/* Then the UI part */
288 			event_box = gtk_grid_get_child_at (data->parts_grid, 0, index_src);
289 			content = gtk_grid_get_child_at (data->parts_grid, 1, index_src);
290 			remove_button = gtk_grid_get_child_at (data->parts_grid, 2, index_src);
291 
292 			g_warn_if_fail (event_box != NULL);
293 			g_warn_if_fail (content != NULL);
294 			g_warn_if_fail (remove_button != NULL);
295 
296 			g_object_ref (event_box);
297 			g_object_ref (content);
298 			g_object_ref (remove_button);
299 
300 			gtk_grid_remove_row (data->parts_grid, index_src);
301 			gtk_grid_insert_row (data->parts_grid, index_des);
302 			gtk_grid_attach (data->parts_grid, event_box, 0, index_des, 1, 1);
303 			gtk_grid_attach (data->parts_grid, content, 1, index_des, 1, 1);
304 			gtk_grid_attach (data->parts_grid, remove_button, 2, index_des, 1, 1);
305 
306 			g_object_unref (event_box);
307 			g_object_unref (content);
308 			g_object_unref (remove_button);
309 		}
310 	}
311 
312 	return TRUE;
313 }
314 
315 static gboolean
rule_widget_drag_motion_cb(GtkWidget * widget,GdkDragContext * context,gint x,gint y,guint time,gpointer user_data)316 rule_widget_drag_motion_cb (GtkWidget *widget,
317 			    GdkDragContext *context,
318 			    gint x,
319 			    gint y,
320 			    guint time,
321 			    gpointer user_data)
322 {
323 	FilterRuleData *data = user_data;
324 	gint ii;
325 
326 	for (ii = 0; ii < data->n_rows; ii++) {
327 		if (gtk_grid_get_child_at (data->parts_grid, 1, ii) == widget) {
328 			return event_box_drag_motion_cb (gtk_grid_get_child_at (data->parts_grid, 0, ii),
329 				context, x, y, time, user_data);
330 		}
331 	}
332 
333 	gdk_drag_status (context, 0, time);
334 
335 	return FALSE;
336 }
337 
338 static void
less_parts(GtkWidget * button,FilterRuleData * data)339 less_parts (GtkWidget *button,
340             FilterRuleData *data)
341 {
342 	EFilterPart *part;
343 	GtkWidget *content = NULL;
344 	FilterPartData *part_data;
345 	gint index;
346 
347 	if (g_list_length (data->rule->parts) < 1)
348 		return;
349 
350 	for (index = 0; index < data->n_rows; index++) {
351 		if (button == gtk_grid_get_child_at (data->parts_grid, 2, index)) {
352 			content = gtk_grid_get_child_at (data->parts_grid, 1, index);
353 			break;
354 		}
355 	}
356 
357 	g_return_if_fail (content != NULL);
358 
359 	part_data = g_object_get_data ((GObject *) content, "data");
360 
361 	g_return_if_fail (part_data != NULL);
362 
363 	part = part_data->part;
364 
365 	index = g_list_index (data->rule->parts, part);
366 	g_warn_if_fail (index >= 0);
367 
368 	/* remove the part from the list */
369 	e_filter_rule_remove_part (data->rule, part);
370 	g_object_unref (part);
371 
372 	/* and from the display */
373 	if (index >= 0) {
374 		gtk_grid_remove_row (data->parts_grid, index);
375 		data->n_rows--;
376 	}
377 }
378 
379 static void
attach_rule(GtkWidget * rule,FilterRuleData * data,EFilterPart * part,gint row)380 attach_rule (GtkWidget *rule,
381              FilterRuleData *data,
382              EFilterPart *part,
383              gint row)
384 {
385 	GtkWidget *remove;
386 	GtkWidget *event_box, *label;
387 
388 	event_box = gtk_event_box_new ();
389 	label = gtk_label_new ("⇕");
390 	gtk_container_add (GTK_CONTAINER (event_box), label);
391 	gtk_widget_set_sensitive (label, FALSE);
392 	gtk_widget_show (label);
393 
394 	g_object_set (G_OBJECT (event_box),
395 		"halign", GTK_ALIGN_FILL,
396 		"hexpand", FALSE,
397 		"valign", GTK_ALIGN_FILL,
398 		"vexpand", FALSE,
399 		"visible", TRUE,
400 		NULL);
401 
402 	g_object_set (G_OBJECT (rule),
403 		"halign", GTK_ALIGN_FILL,
404 		"hexpand", TRUE,
405 		"valign", GTK_ALIGN_CENTER,
406 		"vexpand", FALSE,
407 		NULL);
408 
409 	remove = e_dialog_button_new_with_icon ("list-remove", _("_Remove"));
410 	g_object_set (G_OBJECT (remove),
411 		"halign", GTK_ALIGN_START,
412 		"hexpand", FALSE,
413 		"valign", GTK_ALIGN_CENTER,
414 		"vexpand", FALSE,
415 		"visible", TRUE,
416 		NULL);
417 
418 	g_signal_connect (
419 		remove, "clicked",
420 		G_CALLBACK (less_parts), data);
421 
422 	gtk_grid_insert_row (data->parts_grid, row);
423 	gtk_grid_attach (data->parts_grid, event_box, 0, row, 1, 1);
424 	gtk_grid_attach (data->parts_grid, rule, 1, row, 1, 1);
425 	gtk_grid_attach (data->parts_grid, remove, 2, row, 1, 1);
426 
427 	if (!dnd_atoms[0]) {
428 		gint ii;
429 
430 		for (ii = 0; ii < N_DND_TYPES; ii++)
431 			dnd_atoms[ii] = gdk_atom_intern (dnd_types[ii].target, FALSE);
432 	}
433 
434 	gtk_drag_source_set (event_box, GDK_BUTTON1_MASK, dnd_types, N_DND_TYPES, GDK_ACTION_MOVE);
435 	gtk_drag_dest_set (event_box, GTK_DEST_DEFAULT_MOTION, dnd_types, N_DND_TYPES, GDK_ACTION_MOVE);
436 
437 	g_signal_connect (event_box, "drag-begin",
438 		G_CALLBACK (event_box_drag_begin), data);
439 	g_signal_connect (event_box, "drag-motion",
440 		G_CALLBACK (event_box_drag_motion_cb), data);
441 	g_signal_connect (event_box, "drag-drop",
442 		G_CALLBACK (event_box_drag_drop), data);
443 	g_signal_connect (event_box, "drag-end",
444 		G_CALLBACK (event_box_drag_end), data);
445 
446 	gtk_drag_dest_set (rule, GTK_DEST_DEFAULT_MOTION, dnd_types, N_DND_TYPES, GDK_ACTION_MOVE);
447 
448 	g_signal_connect (rule, "drag-motion",
449 		G_CALLBACK (rule_widget_drag_motion_cb), data);
450 }
451 
452 static void
do_grab_focus_cb(GtkWidget * widget,gpointer data)453 do_grab_focus_cb (GtkWidget *widget,
454                   gpointer data)
455 {
456 	gboolean *done = (gboolean *) data;
457 
458 	if (*done || !widget)
459 		return;
460 
461 	if (gtk_widget_get_can_focus (widget) || GTK_IS_COMBO_BOX (widget)) {
462 		*done = TRUE;
463 		gtk_widget_grab_focus (widget);
464 	} else if (GTK_IS_CONTAINER (widget)) {
465 		gtk_container_foreach (GTK_CONTAINER (widget), do_grab_focus_cb, done);
466 	}
467 }
468 
469 static void parts_grid_mapped_cb (GtkWidget *widget,
470 				  GtkScrolledWindow *scrolled_window);
471 
472 static void
more_parts(GtkWidget * button,FilterRuleData * data)473 more_parts (GtkWidget *button,
474             FilterRuleData *data)
475 {
476 	EFilterPart *new;
477 
478 	/* first make sure that the last part is ok */
479 	if (data->rule->parts) {
480 		EFilterPart *part;
481 		GList *l;
482 		EAlert *alert = NULL;
483 
484 		l = g_list_last (data->rule->parts);
485 		part = l->data;
486 		if (!e_filter_part_validate (part, &alert)) {
487 			GtkWidget *toplevel;
488 			toplevel = gtk_widget_get_toplevel (button);
489 			e_alert_run_dialog (GTK_WINDOW (toplevel), alert);
490 			return;
491 		}
492 	}
493 
494 	/* create a new rule entry, use the first type of rule */
495 	new = e_rule_context_next_part (data->context, NULL);
496 	if (new) {
497 		GtkWidget *w;
498 
499 		new = e_filter_part_clone (new);
500 		e_filter_rule_add_part (data->rule, new);
501 		w = get_rule_part_widget (data->context, new, data->rule);
502 
503 		attach_rule (w, data, new, data->n_rows);
504 		data->n_rows++;
505 
506 		if (GTK_IS_CONTAINER (w)) {
507 			gboolean done = FALSE;
508 
509 			gtk_container_foreach (GTK_CONTAINER (w), do_grab_focus_cb, &done);
510 		} else
511 			gtk_widget_grab_focus (w);
512 
513 		/* also scroll down to see new part */
514 		w = (GtkWidget *) g_object_get_data (G_OBJECT (button), "scrolled-window");
515 		if (w) {
516 			GtkAdjustment *adjustment;
517 
518 			adjustment = gtk_scrolled_window_get_vadjustment (
519 				GTK_SCROLLED_WINDOW (w));
520 			if (adjustment) {
521 				gdouble upper;
522 
523 				upper = gtk_adjustment_get_upper (adjustment);
524 				gtk_adjustment_set_value (adjustment, upper);
525 			}
526 
527 			parts_grid_mapped_cb (NULL, GTK_SCROLLED_WINDOW (w));
528 		}
529 	}
530 }
531 
532 static void
name_changed(GtkEntry * entry,EFilterRule * rule)533 name_changed (GtkEntry *entry,
534               EFilterRule *rule)
535 {
536 	g_free (rule->name);
537 	rule->name = g_strdup (gtk_entry_get_text (entry));
538 }
539 
540 GtkWidget *
e_filter_rule_get_widget(EFilterRule * rule,ERuleContext * context)541 e_filter_rule_get_widget (EFilterRule *rule,
542                           ERuleContext *context)
543 {
544 	EFilterRuleClass *class;
545 
546 	g_return_val_if_fail (E_IS_FILTER_RULE (rule), NULL);
547 	g_return_val_if_fail (E_IS_RULE_CONTEXT (context), NULL);
548 
549 	class = E_FILTER_RULE_GET_CLASS (rule);
550 	g_return_val_if_fail (class != NULL, NULL);
551 	g_return_val_if_fail (class->get_widget != NULL, NULL);
552 
553 	return class->get_widget (rule, context);
554 }
555 
556 static void
filter_rule_load_set(xmlNodePtr node,EFilterRule * rule,ERuleContext * context)557 filter_rule_load_set (xmlNodePtr node,
558                       EFilterRule *rule,
559                       ERuleContext *context)
560 {
561 	xmlNodePtr work;
562 	gchar *rulename;
563 	EFilterPart *part;
564 
565 	work = node->children;
566 	while (work) {
567 		if (!strcmp ((gchar *) work->name, "part")) {
568 			rulename = (gchar *) xmlGetProp (work, (xmlChar *)"name");
569 			part = e_rule_context_find_part (context, rulename);
570 			if (part) {
571 				part = e_filter_part_clone (part);
572 				e_filter_part_xml_decode (part, work);
573 				e_filter_rule_add_part (rule, part);
574 			} else {
575 				g_warning ("cannot find rule part '%s'\n", rulename);
576 			}
577 			xmlFree (rulename);
578 		} else if (work->type == XML_ELEMENT_NODE) {
579 			g_warning ("Unknown xml node in part: %s", work->name);
580 		}
581 		work = work->next;
582 	}
583 }
584 
585 static void
filter_rule_finalize(GObject * object)586 filter_rule_finalize (GObject *object)
587 {
588 	EFilterRule *rule = E_FILTER_RULE (object);
589 
590 	g_free (rule->name);
591 	g_free (rule->source);
592 
593 	g_list_foreach (rule->parts, (GFunc) g_object_unref, NULL);
594 	g_list_free (rule->parts);
595 
596 	/* Chain up to parent's finalize() method. */
597 	G_OBJECT_CLASS (e_filter_rule_parent_class)->finalize (object);
598 }
599 
600 static gint
filter_rule_validate(EFilterRule * rule,EAlert ** alert)601 filter_rule_validate (EFilterRule *rule,
602                       EAlert **alert)
603 {
604 	gint valid = TRUE;
605 	GList *parts;
606 
607 	g_warn_if_fail (alert == NULL || *alert == NULL);
608 	if (!rule->name || !*rule->name) {
609 		if (alert)
610 			*alert = e_alert_new ("filter:no-name", NULL);
611 
612 		return FALSE;
613 	}
614 
615 	/* validate rule parts */
616 	parts = rule->parts;
617 	valid = parts != NULL;
618 	while (parts && valid) {
619 		valid = e_filter_part_validate ((EFilterPart *) parts->data, alert);
620 		parts = parts->next;
621 	}
622 
623 	if (!valid && !rule->parts && alert)
624 		*alert = e_alert_new ("filter:no-condition", NULL);
625 
626 	return valid;
627 }
628 
629 static gint
filter_rule_eq(EFilterRule * rule_a,EFilterRule * rule_b)630 filter_rule_eq (EFilterRule *rule_a,
631                 EFilterRule *rule_b)
632 {
633 	GList *link_a;
634 	GList *link_b;
635 
636 	if (rule_a->enabled != rule_b->enabled)
637 		return FALSE;
638 
639 	if (rule_a->grouping != rule_b->grouping)
640 		return FALSE;
641 
642 	if (rule_a->threading != rule_b->threading)
643 		return FALSE;
644 
645 	if (g_strcmp0 (rule_a->name, rule_b->name) != 0)
646 		return FALSE;
647 
648 	if (g_strcmp0 (rule_a->source, rule_b->source) != 0)
649 		return FALSE;
650 
651 	link_a = rule_a->parts;
652 	link_b = rule_b->parts;
653 
654 	while (link_a != NULL && link_b != NULL) {
655 		EFilterPart *part_a = link_a->data;
656 		EFilterPart *part_b = link_b->data;
657 
658 		if (!e_filter_part_eq (part_a, part_b))
659 			return FALSE;
660 
661 		link_a = g_list_next (link_a);
662 		link_b = g_list_next (link_b);
663 	}
664 
665 	if (link_a != NULL || link_b != NULL)
666 		return FALSE;
667 
668 	return TRUE;
669 }
670 
671 static xmlNodePtr
filter_rule_xml_encode(EFilterRule * rule)672 filter_rule_xml_encode (EFilterRule *rule)
673 {
674 	xmlNodePtr node, set, work;
675 	GList *l;
676 
677 	node = xmlNewNode (NULL, (xmlChar *)"rule");
678 
679 	xmlSetProp (
680 		node, (xmlChar *)"enabled",
681 		(xmlChar *)(rule->enabled ? "true" : "false"));
682 
683 	switch (rule->grouping) {
684 	case E_FILTER_GROUP_ALL:
685 		xmlSetProp (node, (xmlChar *)"grouping", (xmlChar *)"all");
686 		break;
687 	case E_FILTER_GROUP_ANY:
688 		xmlSetProp (node, (xmlChar *)"grouping", (xmlChar *)"any");
689 		break;
690 	}
691 
692 	switch (rule->threading) {
693 	case E_FILTER_THREAD_NONE:
694 		break;
695 	case E_FILTER_THREAD_ALL:
696 		xmlSetProp (node, (xmlChar *)"threading", (xmlChar *)"all");
697 		break;
698 	case E_FILTER_THREAD_REPLIES:
699 		xmlSetProp (node, (xmlChar *)"threading", (xmlChar *)"replies");
700 		break;
701 	case E_FILTER_THREAD_REPLIES_PARENTS:
702 		xmlSetProp (node, (xmlChar *)"threading", (xmlChar *)"replies_parents");
703 		break;
704 	case E_FILTER_THREAD_SINGLE:
705 		xmlSetProp (node, (xmlChar *)"threading", (xmlChar *)"single");
706 		break;
707 	}
708 
709 	if (rule->source) {
710 		xmlSetProp (node, (xmlChar *)"source", (xmlChar *) rule->source);
711 	} else {
712 		/* set to the default filter type */
713 		xmlSetProp (node, (xmlChar *)"source", (xmlChar *)"incoming");
714 	}
715 
716 	if (rule->name) {
717 		gchar *escaped = g_markup_escape_text (rule->name, -1);
718 
719 		work = xmlNewNode (NULL, (xmlChar *)"title");
720 		xmlNodeSetContent (work, (xmlChar *) escaped);
721 		xmlAddChild (node, work);
722 
723 		g_free (escaped);
724 	}
725 
726 	set = xmlNewNode (NULL, (xmlChar *)"partset");
727 	xmlAddChild (node, set);
728 	l = rule->parts;
729 	while (l) {
730 		work = e_filter_part_xml_encode ((EFilterPart *) l->data);
731 		xmlAddChild (set, work);
732 		l = l->next;
733 	}
734 
735 	return node;
736 }
737 
738 static gint
filter_rule_xml_decode(EFilterRule * rule,xmlNodePtr node,ERuleContext * context)739 filter_rule_xml_decode (EFilterRule *rule,
740                         xmlNodePtr node,
741                         ERuleContext *context)
742 {
743 	xmlNodePtr work;
744 	gchar *grouping;
745 	gchar *source;
746 
747 	g_free (rule->name);
748 	rule->name = NULL;
749 
750 	grouping = (gchar *) xmlGetProp (node, (xmlChar *)"enabled");
751 	if (!grouping)
752 		rule->enabled = TRUE;
753 	else {
754 		rule->enabled = strcmp (grouping, "false") != 0;
755 		xmlFree (grouping);
756 	}
757 
758 	grouping = (gchar *) xmlGetProp (node, (xmlChar *)"grouping");
759 	if (!strcmp (grouping, "any"))
760 		rule->grouping = E_FILTER_GROUP_ANY;
761 	else
762 		rule->grouping = E_FILTER_GROUP_ALL;
763 	xmlFree (grouping);
764 
765 	rule->threading = E_FILTER_THREAD_NONE;
766 	if (context->flags & E_RULE_CONTEXT_THREADING
767 	    && (grouping = (gchar *) xmlGetProp (node, (xmlChar *)"threading"))) {
768 		if (!strcmp (grouping, "all"))
769 			rule->threading = E_FILTER_THREAD_ALL;
770 		else if (!strcmp (grouping, "replies"))
771 			rule->threading = E_FILTER_THREAD_REPLIES;
772 		else if (!strcmp (grouping, "replies_parents"))
773 			rule->threading = E_FILTER_THREAD_REPLIES_PARENTS;
774 		else if (!strcmp (grouping, "single"))
775 			rule->threading = E_FILTER_THREAD_SINGLE;
776 		xmlFree (grouping);
777 	}
778 
779 	g_free (rule->source);
780 	source = (gchar *) xmlGetProp (node, (xmlChar *)"source");
781 	if (source) {
782 		rule->source = g_strdup (source);
783 		xmlFree (source);
784 	} else {
785 		/* default filter type */
786 		rule->source = g_strdup ("incoming");
787 	}
788 
789 	work = node->children;
790 	while (work) {
791 		if (!strcmp ((gchar *) work->name, "partset")) {
792 			filter_rule_load_set (work, rule, context);
793 		} else if (!strcmp ((gchar *) work->name, "title") ||
794 			!strcmp ((gchar *) work->name, "_title")) {
795 
796 			if (!rule->name) {
797 				gchar *str, *decstr = NULL;
798 
799 				str = (gchar *) xmlNodeGetContent (work);
800 				if (str) {
801 					decstr = g_strdup (_(str));
802 					xmlFree (str);
803 				}
804 				rule->name = decstr;
805 			}
806 		}
807 		work = work->next;
808 	}
809 
810 	return 0;
811 }
812 
813 static void
filter_rule_build_code_for_parts(EFilterRule * rule,GList * parts,gboolean without_match_all,gboolean force_match_all,GString * out)814 filter_rule_build_code_for_parts (EFilterRule *rule,
815 				  GList *parts,
816 				  gboolean without_match_all,
817 				  gboolean force_match_all,
818 				  GString *out)
819 {
820 	g_return_if_fail (rule != NULL);
821 	g_return_if_fail (parts != NULL);
822 	g_return_if_fail (out != NULL);
823 
824 	switch (rule->threading) {
825 	case E_FILTER_THREAD_NONE:
826 		break;
827 	case E_FILTER_THREAD_ALL:
828 		g_string_append (out, " (match-threads \"all\" ");
829 		break;
830 	case E_FILTER_THREAD_REPLIES:
831 		g_string_append (out, " (match-threads \"replies\" ");
832 		break;
833 	case E_FILTER_THREAD_REPLIES_PARENTS:
834 		g_string_append (out, " (match-threads \"replies_parents\" ");
835 		break;
836 	case E_FILTER_THREAD_SINGLE:
837 		g_string_append (out, " (match-threads \"single\" ");
838 		break;
839 	}
840 
841 	if ((rule->threading != E_FILTER_THREAD_NONE && !without_match_all) || force_match_all)
842 		g_string_append (out, "(match-all ");
843 
844 	if (parts->next) {
845 		switch (rule->grouping) {
846 		case E_FILTER_GROUP_ALL:
847 			g_string_append (out, " (and\n  ");
848 			break;
849 		case E_FILTER_GROUP_ANY:
850 			g_string_append (out, " (or\n  ");
851 			break;
852 		default:
853 			g_warning ("Invalid grouping");
854 		}
855 	}
856 
857 	e_filter_part_build_code_list (parts, out);
858 
859 	if (parts->next)
860 		g_string_append (out, ")\n");
861 
862 	if (rule->threading != E_FILTER_THREAD_NONE) {
863 		if (without_match_all && !force_match_all)
864 			g_string_append (out, ")\n");
865 		else
866 			g_string_append (out, "))\n");
867 	} else if (force_match_all) {
868 		g_string_append (out, ")\n");
869 	}
870 }
871 
872 static void
filter_rule_build_code(EFilterRule * rule,GString * out)873 filter_rule_build_code (EFilterRule *rule,
874                         GString *out)
875 {
876 	GList *link;
877 	gboolean has_body_search = FALSE;
878 
879 	if (!rule->parts)
880 		return;
881 
882 	for (link = rule->parts; link && !has_body_search; link = g_list_next (link)) {
883 		EFilterPart *part = link->data;
884 
885 		has_body_search = g_strcmp0 (part->name, "body") == 0;
886 	}
887 
888 	if (has_body_search) {
889 		GList *body_searches = NULL, *other_searches = NULL;
890 
891 		for (link = rule->parts; link; link = g_list_next (link)) {
892 			EFilterPart *part = link->data;
893 
894 			if (g_strcmp0 (part->name, "body") == 0) {
895 				body_searches = g_list_prepend (body_searches, part);
896 			} else {
897 				other_searches = g_list_prepend (other_searches, part);
898 			}
899 		}
900 
901 		if (other_searches && body_searches) {
902 			switch (rule->grouping) {
903 			case E_FILTER_GROUP_ALL:
904 				g_string_append (out, "(and ");
905 				break;
906 			case E_FILTER_GROUP_ANY:
907 				g_string_append (out, "(or ");
908 				break;
909 			default:
910 				g_warning ("Invalid grouping");
911 			}
912 
913 			body_searches = g_list_reverse (body_searches);
914 			other_searches = g_list_reverse (other_searches);
915 
916 			filter_rule_build_code_for_parts (rule, other_searches, FALSE, TRUE, out);
917 
918 			g_string_append_c (out, ' ');
919 
920 			filter_rule_build_code_for_parts (rule, body_searches, TRUE, FALSE, out);
921 
922 			g_string_append_c (out, ')');
923 		} else {
924 			filter_rule_build_code_for_parts (rule, rule->parts, FALSE, FALSE, out);
925 		}
926 
927 		g_list_free (body_searches);
928 		g_list_free (other_searches);
929 	} else {
930 		filter_rule_build_code_for_parts (rule, rule->parts, FALSE, FALSE, out);
931 	}
932 }
933 
934 static void
filter_rule_copy(EFilterRule * dest,EFilterRule * src)935 filter_rule_copy (EFilterRule *dest,
936                   EFilterRule *src)
937 {
938 	GList *node;
939 
940 	dest->enabled = src->enabled;
941 
942 	g_free (dest->name);
943 	dest->name = g_strdup (src->name);
944 
945 	g_free (dest->source);
946 	dest->source = g_strdup (src->source);
947 
948 	dest->grouping = src->grouping;
949 	dest->threading = src->threading;
950 
951 	if (dest->parts) {
952 		g_list_foreach (dest->parts, (GFunc) g_object_unref, NULL);
953 		g_list_free (dest->parts);
954 		dest->parts = NULL;
955 	}
956 
957 	node = src->parts;
958 	while (node) {
959 		EFilterPart *part;
960 
961 		part = e_filter_part_clone (node->data);
962 		dest->parts = g_list_append (dest->parts, part);
963 		node = node->next;
964 	}
965 }
966 
967 static void
ensure_scrolled_width_cb(GtkAdjustment * adj,GParamSpec * param_spec,GtkScrolledWindow * scrolled_window)968 ensure_scrolled_width_cb (GtkAdjustment *adj,
969                           GParamSpec *param_spec,
970                           GtkScrolledWindow *scrolled_window)
971 {
972 	gtk_scrolled_window_set_min_content_width (
973 		scrolled_window,
974 		gtk_adjustment_get_upper (adj));
975 }
976 
977 static void
parts_grid_mapped_cb(GtkWidget * widget,GtkScrolledWindow * scrolled_window)978 parts_grid_mapped_cb (GtkWidget *widget,
979 		      GtkScrolledWindow *scrolled_window)
980 {
981 	g_return_if_fail (GTK_IS_SCROLLED_WINDOW (scrolled_window));
982 
983 	ensure_scrolled_width_cb (gtk_scrolled_window_get_hadjustment (scrolled_window), NULL, scrolled_window);
984 	e_util_ensure_scrolled_window_height (scrolled_window);
985 }
986 
987 static GtkWidget *
filter_rule_get_widget(EFilterRule * rule,ERuleContext * context)988 filter_rule_get_widget (EFilterRule *rule,
989                         ERuleContext *context)
990 {
991 	GtkGrid *hgrid, *vgrid, *inframe, *parts_grid;
992 	GtkWidget *add, *label, *name, *w;
993 	GtkWidget *combobox;
994 	GtkWidget *scrolledwindow;
995 	GtkAdjustment *hadj, *vadj;
996 	GList *link;
997 	gchar *text;
998 	EFilterPart *part;
999 	FilterRuleData *data;
1000 	gint i;
1001 
1002 	/* this stuff should probably be a table, but the
1003 	 * rule parts need to be a vbox */
1004 	vgrid = GTK_GRID (gtk_grid_new ());
1005 	gtk_grid_set_row_spacing (vgrid, 6);
1006 	gtk_orientable_set_orientation (GTK_ORIENTABLE (vgrid), GTK_ORIENTATION_VERTICAL);
1007 
1008 	label = gtk_label_new_with_mnemonic (_("R_ule name:"));
1009 	name = gtk_entry_new ();
1010 	gtk_widget_set_hexpand (name, TRUE);
1011 	gtk_widget_set_halign (name, GTK_ALIGN_FILL);
1012 	gtk_label_set_mnemonic_widget ((GtkLabel *) label, name);
1013 
1014 	if (!rule->name) {
1015 		rule->name = g_strdup (_("Untitled"));
1016 		gtk_entry_set_text (GTK_ENTRY (name), rule->name);
1017 		/* FIXME: do we want the following code in the future? */
1018 		/*gtk_editable_select_region (GTK_EDITABLE (name), 0, -1);*/
1019 	} else {
1020 		gtk_entry_set_text (GTK_ENTRY (name), rule->name);
1021 	}
1022 
1023 	g_signal_connect (
1024 		name, "realize",
1025 		G_CALLBACK (gtk_widget_grab_focus), name);
1026 
1027 	hgrid = GTK_GRID (gtk_grid_new ());
1028 	gtk_grid_set_column_spacing (hgrid, 12);
1029 
1030 	gtk_grid_attach (hgrid, label, 0, 0, 1, 1);
1031 	gtk_grid_attach_next_to (hgrid, name, label, GTK_POS_RIGHT, 1, 1);
1032 
1033 	gtk_container_add (GTK_CONTAINER (vgrid), GTK_WIDGET (hgrid));
1034 
1035 	g_signal_connect (
1036 		name, "changed",
1037 		G_CALLBACK (name_changed), rule);
1038 
1039 	hgrid = GTK_GRID (gtk_grid_new ());
1040 	gtk_grid_set_column_spacing (hgrid, 12);
1041 	gtk_container_add (GTK_CONTAINER (vgrid), GTK_WIDGET (hgrid));
1042 
1043 	parts_grid = GTK_GRID (gtk_grid_new ());
1044 	g_object_set (G_OBJECT (parts_grid),
1045 		"halign", GTK_ALIGN_FILL,
1046 		"hexpand", TRUE,
1047 		"valign", GTK_ALIGN_FILL,
1048 		"vexpand", TRUE,
1049 		NULL);
1050 
1051 	/* data for the parts part of the display */
1052 	data = g_malloc0 (sizeof (FilterRuleData));
1053 	data->context = context;
1054 	data->rule = rule;
1055 	data->parts_grid = parts_grid;
1056 	data->drag_widget = NULL;
1057 	data->n_rows = 0;
1058 
1059 	/* only set to automatically clean up the memory */
1060 	g_object_set_data_full ((GObject *) vgrid, "data", data, g_free);
1061 
1062 	if (context->flags & E_RULE_CONTEXT_GROUPING) {
1063 		const gchar *thread_types[] = {
1064 			N_("all the following conditions"),
1065 			N_("any of the following conditions")
1066 		};
1067 
1068 		hgrid = GTK_GRID (gtk_grid_new ());
1069 		gtk_grid_set_column_spacing (hgrid, 12);
1070 
1071 		label = gtk_label_new_with_mnemonic (_("_Find items which match:"));
1072 		combobox = gtk_combo_box_text_new ();
1073 
1074 		for (i = 0; i < 2; i++) {
1075 			gtk_combo_box_text_append_text (
1076 				GTK_COMBO_BOX_TEXT (combobox),
1077 				_(thread_types[i]));
1078 		}
1079 
1080 		gtk_label_set_mnemonic_widget ((GtkLabel *) label, combobox);
1081 		gtk_combo_box_set_active (GTK_COMBO_BOX (combobox), rule->grouping);
1082 
1083 		gtk_grid_attach (hgrid, label, 0, 0, 1, 1);
1084 		gtk_grid_attach_next_to (hgrid, combobox, label, GTK_POS_RIGHT, 1, 1);
1085 
1086 		g_signal_connect (
1087 			combobox, "changed",
1088 			G_CALLBACK (filter_rule_grouping_changed_cb), rule);
1089 
1090 		gtk_container_add (GTK_CONTAINER (vgrid), GTK_WIDGET (hgrid));
1091 	} else {
1092 		text = g_strdup_printf (
1093 			"<b>%s</b>",
1094 			_("Find items that meet the following conditions"));
1095 		label = gtk_label_new (text);
1096 		gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
1097 		gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
1098 		gtk_container_add (GTK_CONTAINER (vgrid), label);
1099 		g_free (text);
1100 	}
1101 
1102 	hgrid = GTK_GRID (gtk_grid_new ());
1103 	gtk_grid_set_column_spacing (hgrid, 12);
1104 
1105 	if (context->flags & E_RULE_CONTEXT_THREADING) {
1106 		const gchar *thread_types[] = {
1107 			/* Translators: "None" for not including threads;
1108 			 * part of "Include threads: None" */
1109 			N_("None"),
1110 			N_("All related"),
1111 			N_("Replies"),
1112 			N_("Replies and parents"),
1113 			N_("No reply or parent")
1114 		};
1115 
1116 		label = gtk_label_new_with_mnemonic (_("I_nclude threads:"));
1117 		combobox = gtk_combo_box_text_new ();
1118 
1119 		for (i = 0; i < 5; i++) {
1120 			gtk_combo_box_text_append_text (
1121 				GTK_COMBO_BOX_TEXT (combobox),
1122 				_(thread_types[i]));
1123 		}
1124 
1125 		gtk_label_set_mnemonic_widget ((GtkLabel *) label, combobox);
1126 		gtk_combo_box_set_active (GTK_COMBO_BOX (combobox), rule->threading);
1127 
1128 		gtk_grid_attach (hgrid, label, 0, 0, 1, 1);
1129 		gtk_grid_attach_next_to (hgrid, combobox, label, GTK_POS_RIGHT, 1, 1);
1130 
1131 		g_signal_connect (
1132 			combobox, "changed",
1133 			G_CALLBACK (filter_rule_threading_changed_cb), rule);
1134 	}
1135 
1136 	gtk_container_add (GTK_CONTAINER (vgrid), GTK_WIDGET (hgrid));
1137 
1138 	hgrid = GTK_GRID (gtk_grid_new ());
1139 	gtk_grid_set_column_spacing (hgrid, 3);
1140 	gtk_widget_set_vexpand (GTK_WIDGET (hgrid), TRUE);
1141 	gtk_widget_set_valign (GTK_WIDGET (hgrid), GTK_ALIGN_FILL);
1142 
1143 	gtk_container_add (GTK_CONTAINER (vgrid), GTK_WIDGET (hgrid));
1144 
1145 	label = gtk_label_new ("");
1146 	gtk_grid_attach (hgrid, label, 0, 0, 1, 1);
1147 
1148 	inframe = GTK_GRID (gtk_grid_new ());
1149 	gtk_grid_set_row_spacing (inframe, 6);
1150 	gtk_orientable_set_orientation (GTK_ORIENTABLE (inframe), GTK_ORIENTATION_VERTICAL);
1151 	gtk_widget_set_hexpand (GTK_WIDGET (inframe), TRUE);
1152 	gtk_widget_set_halign (GTK_WIDGET (inframe), GTK_ALIGN_FILL);
1153 	gtk_widget_set_vexpand (GTK_WIDGET (inframe), TRUE);
1154 	gtk_widget_set_valign (GTK_WIDGET (inframe), GTK_ALIGN_FILL);
1155 	gtk_grid_attach_next_to (hgrid, GTK_WIDGET (inframe), label, GTK_POS_RIGHT, 1, 1);
1156 
1157 	for (link = rule->parts; link; link = g_list_next (link)) {
1158 		part = link->data;
1159 		w = get_rule_part_widget (context, part, rule);
1160 
1161 		attach_rule (w, data, part, data->n_rows);
1162 		data->n_rows++;
1163 	}
1164 
1165 	hadj = GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 1.0, 1.0, 1.0, 1.0));
1166 	vadj = GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 1.0, 1.0, 1.0, 1.0));
1167 	scrolledwindow = gtk_scrolled_window_new (hadj, vadj);
1168 
1169 	g_signal_connect (parts_grid, "map",
1170 		G_CALLBACK (parts_grid_mapped_cb), scrolledwindow);
1171 	e_signal_connect_notify (
1172 		hadj, "notify::upper",
1173 		G_CALLBACK (ensure_scrolled_width_cb), scrolledwindow);
1174 	e_signal_connect_notify_swapped (
1175 		vadj, "notify::upper",
1176 		G_CALLBACK (e_util_ensure_scrolled_window_height), scrolledwindow);
1177 
1178 	gtk_scrolled_window_set_policy (
1179 		GTK_SCROLLED_WINDOW (scrolledwindow),
1180 		GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
1181 
1182 	gtk_scrolled_window_add_with_viewport (
1183 		GTK_SCROLLED_WINDOW (scrolledwindow), GTK_WIDGET (parts_grid));
1184 
1185 	gtk_widget_set_vexpand (scrolledwindow, TRUE);
1186 	gtk_widget_set_valign (scrolledwindow, GTK_ALIGN_FILL);
1187 	gtk_widget_set_hexpand (scrolledwindow, TRUE);
1188 	gtk_widget_set_halign (scrolledwindow, GTK_ALIGN_FILL);
1189 	gtk_container_add (GTK_CONTAINER (inframe), scrolledwindow);
1190 
1191 	hgrid = GTK_GRID (gtk_grid_new ());
1192 	gtk_grid_set_column_spacing (hgrid, 3);
1193 
1194 	add = e_dialog_button_new_with_icon ("list-add", _("A_dd Condition"));
1195 	g_signal_connect (
1196 		add, "clicked",
1197 		G_CALLBACK (more_parts), data);
1198 	gtk_grid_attach (hgrid, add, 0, 0, 1, 1);
1199 
1200 	gtk_container_add (GTK_CONTAINER (inframe), GTK_WIDGET (hgrid));
1201 
1202 	gtk_widget_show_all (GTK_WIDGET (vgrid));
1203 
1204 	g_object_set_data (G_OBJECT (add), "scrolled-window", scrolledwindow);
1205 
1206 	return GTK_WIDGET (vgrid);
1207 }
1208 
1209 static void
e_filter_rule_class_init(EFilterRuleClass * class)1210 e_filter_rule_class_init (EFilterRuleClass *class)
1211 {
1212 	GObjectClass *object_class;
1213 
1214 	g_type_class_add_private (class, sizeof (EFilterRulePrivate));
1215 
1216 	object_class = G_OBJECT_CLASS (class);
1217 	object_class->finalize = filter_rule_finalize;
1218 
1219 	class->validate = filter_rule_validate;
1220 	class->eq = filter_rule_eq;
1221 	class->xml_encode = filter_rule_xml_encode;
1222 	class->xml_decode = filter_rule_xml_decode;
1223 	class->build_code = filter_rule_build_code;
1224 	class->copy = filter_rule_copy;
1225 	class->get_widget = filter_rule_get_widget;
1226 
1227 	signals[CHANGED] = g_signal_new (
1228 		"changed",
1229 		E_TYPE_FILTER_RULE,
1230 		G_SIGNAL_RUN_LAST,
1231 		G_STRUCT_OFFSET (EFilterRuleClass, changed),
1232 		NULL,
1233 		NULL,
1234 		g_cclosure_marshal_VOID__VOID,
1235 		G_TYPE_NONE, 0);
1236 }
1237 
1238 static void
e_filter_rule_init(EFilterRule * rule)1239 e_filter_rule_init (EFilterRule *rule)
1240 {
1241 	rule->priv = E_FILTER_RULE_GET_PRIVATE (rule);
1242 	rule->enabled = TRUE;
1243 }
1244 
1245 /**
1246  * filter_rule_new:
1247  *
1248  * Create a new EFilterRule object.
1249  *
1250  * Return value: A new #EFilterRule object.
1251  **/
1252 EFilterRule *
e_filter_rule_new(void)1253 e_filter_rule_new (void)
1254 {
1255 	return g_object_new (E_TYPE_FILTER_RULE, NULL);
1256 }
1257 
1258 EFilterRule *
e_filter_rule_clone(EFilterRule * rule)1259 e_filter_rule_clone (EFilterRule *rule)
1260 {
1261 	EFilterRule *clone;
1262 
1263 	g_return_val_if_fail (E_IS_FILTER_RULE (rule), NULL);
1264 
1265 	clone = g_object_new (G_OBJECT_TYPE (rule), NULL);
1266 	e_filter_rule_copy (clone, rule);
1267 
1268 	return clone;
1269 }
1270 
1271 void
e_filter_rule_set_name(EFilterRule * rule,const gchar * name)1272 e_filter_rule_set_name (EFilterRule *rule,
1273                         const gchar *name)
1274 {
1275 	g_return_if_fail (E_IS_FILTER_RULE (rule));
1276 
1277 	if (g_strcmp0 (rule->name, name) == 0)
1278 		return;
1279 
1280 	g_free (rule->name);
1281 	rule->name = g_strdup (name);
1282 
1283 	e_filter_rule_emit_changed (rule);
1284 }
1285 
1286 void
e_filter_rule_set_source(EFilterRule * rule,const gchar * source)1287 e_filter_rule_set_source (EFilterRule *rule,
1288                           const gchar *source)
1289 {
1290 	g_return_if_fail (E_IS_FILTER_RULE (rule));
1291 
1292 	if (g_strcmp0 (rule->source, source) == 0)
1293 		return;
1294 
1295 	g_free (rule->source);
1296 	rule->source = g_strdup (source);
1297 
1298 	e_filter_rule_emit_changed (rule);
1299 }
1300 
1301 gint
e_filter_rule_validate(EFilterRule * rule,EAlert ** alert)1302 e_filter_rule_validate (EFilterRule *rule,
1303                         EAlert **alert)
1304 {
1305 	EFilterRuleClass *class;
1306 
1307 	g_return_val_if_fail (E_IS_FILTER_RULE (rule), FALSE);
1308 
1309 	class = E_FILTER_RULE_GET_CLASS (rule);
1310 	g_return_val_if_fail (class != NULL, FALSE);
1311 	g_return_val_if_fail (class->validate != NULL, FALSE);
1312 
1313 	return class->validate (rule, alert);
1314 }
1315 
1316 gint
e_filter_rule_eq(EFilterRule * rule_a,EFilterRule * rule_b)1317 e_filter_rule_eq (EFilterRule *rule_a,
1318                   EFilterRule *rule_b)
1319 {
1320 	EFilterRuleClass *class;
1321 
1322 	g_return_val_if_fail (E_IS_FILTER_RULE (rule_a), FALSE);
1323 	g_return_val_if_fail (E_IS_FILTER_RULE (rule_b), FALSE);
1324 
1325 	class = E_FILTER_RULE_GET_CLASS (rule_a);
1326 	g_return_val_if_fail (class != NULL, FALSE);
1327 	g_return_val_if_fail (class->eq != NULL, FALSE);
1328 
1329 	if (G_OBJECT_TYPE (rule_a) != G_OBJECT_TYPE (rule_b))
1330 		return FALSE;
1331 
1332 	return class->eq (rule_a, rule_b);
1333 }
1334 
1335 xmlNodePtr
e_filter_rule_xml_encode(EFilterRule * rule)1336 e_filter_rule_xml_encode (EFilterRule *rule)
1337 {
1338 	EFilterRuleClass *class;
1339 
1340 	g_return_val_if_fail (E_IS_FILTER_RULE (rule), NULL);
1341 
1342 	class = E_FILTER_RULE_GET_CLASS (rule);
1343 	g_return_val_if_fail (class != NULL, NULL);
1344 	g_return_val_if_fail (class->xml_encode != NULL, NULL);
1345 
1346 	return class->xml_encode (rule);
1347 }
1348 
1349 gint
e_filter_rule_xml_decode(EFilterRule * rule,xmlNodePtr node,ERuleContext * context)1350 e_filter_rule_xml_decode (EFilterRule *rule,
1351                           xmlNodePtr node,
1352                           ERuleContext *context)
1353 {
1354 	EFilterRuleClass *class;
1355 	gint result;
1356 
1357 	g_return_val_if_fail (E_IS_FILTER_RULE (rule), FALSE);
1358 	g_return_val_if_fail (node != NULL, FALSE);
1359 	g_return_val_if_fail (E_IS_RULE_CONTEXT (context), FALSE);
1360 
1361 	class = E_FILTER_RULE_GET_CLASS (rule);
1362 	g_return_val_if_fail (class != NULL, FALSE);
1363 	g_return_val_if_fail (class->xml_decode != NULL, FALSE);
1364 
1365 	rule->priv->frozen++;
1366 	result = class->xml_decode (rule, node, context);
1367 	rule->priv->frozen--;
1368 
1369 	e_filter_rule_emit_changed (rule);
1370 
1371 	return result;
1372 }
1373 
1374 void
e_filter_rule_copy(EFilterRule * dst_rule,EFilterRule * src_rule)1375 e_filter_rule_copy (EFilterRule *dst_rule,
1376                     EFilterRule *src_rule)
1377 {
1378 	EFilterRuleClass *class;
1379 
1380 	g_return_if_fail (E_IS_FILTER_RULE (dst_rule));
1381 	g_return_if_fail (E_IS_FILTER_RULE (src_rule));
1382 
1383 	class = E_FILTER_RULE_GET_CLASS (dst_rule);
1384 	g_return_if_fail (class != NULL);
1385 	g_return_if_fail (class->copy != NULL);
1386 
1387 	class->copy (dst_rule, src_rule);
1388 
1389 	e_filter_rule_emit_changed (dst_rule);
1390 }
1391 
1392 void
e_filter_rule_add_part(EFilterRule * rule,EFilterPart * part)1393 e_filter_rule_add_part (EFilterRule *rule,
1394                         EFilterPart *part)
1395 {
1396 	g_return_if_fail (E_IS_FILTER_RULE (rule));
1397 	g_return_if_fail (E_IS_FILTER_PART (part));
1398 
1399 	rule->parts = g_list_append (rule->parts, part);
1400 
1401 	e_filter_rule_emit_changed (rule);
1402 }
1403 
1404 void
e_filter_rule_remove_part(EFilterRule * rule,EFilterPart * part)1405 e_filter_rule_remove_part (EFilterRule *rule,
1406                            EFilterPart *part)
1407 {
1408 	g_return_if_fail (E_IS_FILTER_RULE (rule));
1409 	g_return_if_fail (E_IS_FILTER_PART (part));
1410 
1411 	rule->parts = g_list_remove (rule->parts, part);
1412 
1413 	e_filter_rule_emit_changed (rule);
1414 }
1415 
1416 void
e_filter_rule_replace_part(EFilterRule * rule,EFilterPart * old_part,EFilterPart * new_part)1417 e_filter_rule_replace_part (EFilterRule *rule,
1418                             EFilterPart *old_part,
1419                             EFilterPart *new_part)
1420 {
1421 	GList *link;
1422 
1423 	g_return_if_fail (E_IS_FILTER_RULE (rule));
1424 	g_return_if_fail (E_IS_FILTER_PART (old_part));
1425 	g_return_if_fail (E_IS_FILTER_PART (new_part));
1426 
1427 	link = g_list_find (rule->parts, old_part);
1428 	if (link != NULL)
1429 		link->data = new_part;
1430 	else
1431 		rule->parts = g_list_append (rule->parts, new_part);
1432 
1433 	e_filter_rule_emit_changed (rule);
1434 }
1435 
1436 void
e_filter_rule_build_code(EFilterRule * rule,GString * out)1437 e_filter_rule_build_code (EFilterRule *rule,
1438                           GString *out)
1439 {
1440 	EFilterRuleClass *class;
1441 
1442 	g_return_if_fail (E_IS_FILTER_RULE (rule));
1443 	g_return_if_fail (out != NULL);
1444 
1445 	class = E_FILTER_RULE_GET_CLASS (rule);
1446 	g_return_if_fail (class != NULL);
1447 	g_return_if_fail (class->build_code != NULL);
1448 
1449 	class->build_code (rule, out);
1450 }
1451 
1452 void
e_filter_rule_emit_changed(EFilterRule * rule)1453 e_filter_rule_emit_changed (EFilterRule *rule)
1454 {
1455 	g_return_if_fail (E_IS_FILTER_RULE (rule));
1456 
1457 	if (rule->priv->frozen == 0)
1458 		g_signal_emit (rule, signals[CHANGED], 0);
1459 }
1460 
1461 EFilterRule *
e_filter_rule_next_list(GList * list,EFilterRule * last,const gchar * source)1462 e_filter_rule_next_list (GList *list,
1463                          EFilterRule *last,
1464                          const gchar *source)
1465 {
1466 	GList *link = list;
1467 
1468 	if (last != NULL) {
1469 		link = g_list_find (link, last);
1470 		if (link == NULL)
1471 			link = list;
1472 		else
1473 			link = g_list_next (link);
1474 	}
1475 
1476 	if (source != NULL) {
1477 		while (link != NULL) {
1478 			EFilterRule *rule = link->data;
1479 
1480 			if (g_strcmp0 (rule->source, source) == 0)
1481 				break;
1482 
1483 			link = g_list_next (link);
1484 		}
1485 	}
1486 
1487 	return (link != NULL) ? link->data : NULL;
1488 }
1489 
1490 EFilterRule *
e_filter_rule_find_list(GList * list,const gchar * name,const gchar * source)1491 e_filter_rule_find_list (GList *list,
1492                          const gchar *name,
1493                          const gchar *source)
1494 {
1495 	GList *link;
1496 
1497 	g_return_val_if_fail (name != NULL, NULL);
1498 
1499 	for (link = list; link != NULL; link = g_list_next (link)) {
1500 		EFilterRule *rule = link->data;
1501 
1502 		if (strcmp (rule->name, name) == 0)
1503 			if (source == NULL || (rule->source != NULL &&
1504 				strcmp (rule->source, source) == 0))
1505 				return rule;
1506 	}
1507 
1508 	return NULL;
1509 }
1510 
1511 #ifdef FOR_TRANSLATIONS_ONLY
1512 
1513 static gchar *list[] = {
1514   N_("Incoming"), N_("Outgoing")
1515 };
1516 #endif
1517