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