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