/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with the program; if not, see <http://www.gnu.org/licenses/>
*
*
* Authors:
* Not Zed <notzed@lostzed.mmc.com.au>
* Jeffrey Stedfast <fejj@ximian.com>
*
* Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <string.h>
#include <gtk/gtk.h>
#include <glib/gi18n.h>
#include "libevolution-utils/e-alert-dialog.h"
#include "e-filter-rule.h"
#include "e-rule-context.h"
#define E_FILTER_RULE_GET_PRIVATE(obj) \
(G_TYPE_INSTANCE_GET_PRIVATE \
((obj), E_TYPE_FILTER_RULE, EFilterRulePrivate))
typedef struct _FilterPartData FilterPartData;
typedef struct _FilterRuleData FilterRuleData;
struct _EFilterRulePrivate {
gint frozen;
};
struct _FilterPartData {
EFilterRule *rule;
ERuleContext *context;
EFilterPart *part;
GtkWidget *partwidget;
GtkWidget *container;
};
struct _FilterRuleData {
EFilterRule *rule;
ERuleContext *context;
GtkWidget *parts;
};
enum {
CHANGED,
LAST_SIGNAL
};
static guint signals[LAST_SIGNAL];
G_DEFINE_TYPE (
EFilterRule,
e_filter_rule,
G_TYPE_OBJECT)
static void
filter_rule_grouping_changed_cb (GtkComboBox *combo_box,
EFilterRule *rule)
{
rule->grouping = gtk_combo_box_get_active (combo_box);
}
static void
filter_rule_threading_changed_cb (GtkComboBox *combo_box,
EFilterRule *rule)
{
rule->threading = gtk_combo_box_get_active (combo_box);
}
static void
part_combobox_changed (GtkComboBox *combobox,
FilterPartData *data)
{
EFilterPart *part = NULL;
EFilterPart *newpart;
gint index, i;
index = gtk_combo_box_get_active (combobox);
for (i = 0, part = e_rule_context_next_part (data->context, part);
part && i < index;
i++, part = e_rule_context_next_part (data->context, part)) {
/* traverse until reached index */
}
g_return_if_fail (part != NULL);
g_return_if_fail (i == index);
/* dont update if we haven't changed */
if (!strcmp (part->title, data->part->title))
return;
/* here we do a widget shuffle, throw away the old widget/rulepart,
* and create another */
if (data->partwidget)
gtk_container_remove (GTK_CONTAINER (data->container), data->partwidget);
newpart = e_filter_part_clone (part);
e_filter_part_copy_values (newpart, data->part);
e_filter_rule_replace_part (data->rule, data->part, newpart);
g_object_unref (data->part);
data->part = newpart;
data->partwidget = e_filter_part_get_widget (newpart);
if (data->partwidget)
gtk_box_pack_start (
GTK_BOX (data->container),
data->partwidget, TRUE, TRUE, 0);
}
static GtkWidget *
get_rule_part_widget (ERuleContext *context,
EFilterPart *newpart,
EFilterRule *rule)
{
EFilterPart *part = NULL;
GtkWidget *combobox;
GtkWidget *hbox;
GtkWidget *p;
gint index = 0, current = 0;
FilterPartData *data;
data = g_malloc0 (sizeof (*data));
data->rule = rule;
data->context = context;
data->part = newpart;
hbox = gtk_hbox_new (FALSE, 0);
/* only set to automatically clean up the memory */
g_object_set_data_full ((GObject *) hbox, "data", data, g_free);
p = e_filter_part_get_widget (newpart);
data->partwidget = p;
data->container = hbox;
combobox = gtk_combo_box_text_new ();
/* sigh, this is a little ugly */
while ((part = e_rule_context_next_part (context, part))) {
gtk_combo_box_text_append_text (
GTK_COMBO_BOX_TEXT (combobox), _(part->title));
if (!strcmp (newpart->title, part->title))
current = index;
index++;
}
gtk_combo_box_set_active (GTK_COMBO_BOX (combobox), current);
g_signal_connect (
combobox, "changed",
G_CALLBACK (part_combobox_changed), data);
gtk_widget_show (combobox);
gtk_box_pack_start (GTK_BOX (hbox), combobox, FALSE, FALSE, 0);
if (p)
gtk_box_pack_start (GTK_BOX (hbox), p, TRUE, TRUE, 0);
gtk_widget_show_all (hbox);
return hbox;
}
static void
less_parts (GtkWidget *button,
FilterRuleData *data)
{
EFilterPart *part;
GtkWidget *rule;
FilterPartData *part_data;
if (g_list_length (data->rule->parts) < 1)
return;
rule = g_object_get_data ((GObject *) button, "rule");
part_data = g_object_get_data ((GObject *) rule, "data");
g_return_if_fail (part_data != NULL);
part = part_data->part;
/* remove the part from the list */
e_filter_rule_remove_part (data->rule, part);
g_object_unref (part);
/* and from the display */
gtk_container_remove (GTK_CONTAINER (data->parts), rule);
gtk_container_remove (GTK_CONTAINER (data->parts), button);
}
static void
attach_rule (GtkWidget *rule,
FilterRuleData *data,
EFilterPart *part,
gint row)
{
GtkWidget *remove;
gtk_table_attach (GTK_TABLE (data->parts), rule, 0, 1, row, row + 1,
GTK_EXPAND | GTK_FILL, 0, 0, 0);
remove = gtk_button_new_from_stock (GTK_STOCK_REMOVE);
g_object_set_data ((GObject *) remove, "rule", rule);
g_signal_connect (
remove, "clicked",
G_CALLBACK (less_parts), data);
gtk_table_attach (GTK_TABLE (data->parts), remove, 1, 2, row, row + 1,
0, 0, 0, 0);
gtk_widget_show (remove);
}
static void
do_grab_focus_cb (GtkWidget *widget,
gpointer data)
{
gboolean *done = (gboolean *) data;
if (*done || !widget)
return;
if (gtk_widget_get_can_focus (widget) || GTK_IS_COMBO_BOX (widget)) {
*done = TRUE;
gtk_widget_grab_focus (widget);
} else if (GTK_IS_CONTAINER (widget)) {
gtk_container_foreach (GTK_CONTAINER (widget), do_grab_focus_cb, done);
}
}
static void
more_parts (GtkWidget *button,
FilterRuleData *data)
{
EFilterPart *new;
/* first make sure that the last part is ok */
if (data->rule->parts) {
EFilterPart *part;
GList *l;
EAlert *alert = NULL;
l = g_list_last (data->rule->parts);
part = l->data;
if (!e_filter_part_validate (part, &alert)) {
GtkWidget *toplevel;
toplevel = gtk_widget_get_toplevel (button);
e_alert_run_dialog (GTK_WINDOW (toplevel), alert);
return;
}
}
/* create a new rule entry, use the first type of rule */
new = e_rule_context_next_part (data->context, NULL);
if (new) {
GtkWidget *w;
guint rows;
new = e_filter_part_clone (new);
e_filter_rule_add_part (data->rule, new);
w = get_rule_part_widget (data->context, new, data->rule);
g_object_get (data->parts, "n-rows", &rows, NULL);
gtk_table_resize (GTK_TABLE (data->parts), rows + 1, 2);
attach_rule (w, data, new, rows);
if (GTK_IS_CONTAINER (w)) {
gboolean done = FALSE;
gtk_container_foreach (GTK_CONTAINER (w), do_grab_focus_cb, &done);
} else
gtk_widget_grab_focus (w);
/* also scroll down to see new part */
w = (GtkWidget *) g_object_get_data (G_OBJECT (button), "scrolled-window");
if (w) {
GtkAdjustment *adjustment;
adjustment = gtk_scrolled_window_get_vadjustment (
GTK_SCROLLED_WINDOW (w));
if (adjustment) {
gdouble upper;
upper = gtk_adjustment_get_upper (adjustment);
gtk_adjustment_set_value (adjustment, upper);
}
}
}
}
static void
name_changed (GtkEntry *entry,
EFilterRule *rule)
{
g_free (rule->name);
rule->name = g_strdup (gtk_entry_get_text (entry));
}
GtkWidget *
e_filter_rule_get_widget (EFilterRule *rule,
ERuleContext *context)
{
EFilterRuleClass *class;
g_return_val_if_fail (E_IS_FILTER_RULE (rule), NULL);
g_return_val_if_fail (E_IS_RULE_CONTEXT (context), NULL);
class = E_FILTER_RULE_GET_CLASS (rule);
g_return_val_if_fail (class->get_widget != NULL, NULL);
return class->get_widget (rule, context);
}
static void
filter_rule_load_set (xmlNodePtr node,
EFilterRule *rule,
ERuleContext *context)
{
xmlNodePtr work;
gchar *rulename;
EFilterPart *part;
work = node->children;
while (work) {
if (!strcmp ((gchar *) work->name, "part")) {
rulename = (gchar *) xmlGetProp (work, (xmlChar *)"name");
part = e_rule_context_find_part (context, rulename);
if (part) {
part = e_filter_part_clone (part);
e_filter_part_xml_decode (part, work);
e_filter_rule_add_part (rule, part);
} else {
g_warning ("cannot find rule part '%s'\n", rulename);
}
xmlFree (rulename);
} else if (work->type == XML_ELEMENT_NODE) {
g_warning ("Unknown xml node in part: %s", work->name);
}
work = work->next;
}
}
static void
filter_rule_finalize (GObject *object)
{
EFilterRule *rule = E_FILTER_RULE (object);
g_free (rule->name);
g_free (rule->source);
g_list_foreach (rule->parts, (GFunc) g_object_unref, NULL);
g_list_free (rule->parts);
/* Chain up to parent's finalize() method. */
G_OBJECT_CLASS (e_filter_rule_parent_class)->finalize (object);
}
static gint
filter_rule_validate (EFilterRule *rule,
EAlert **alert)
{
gint valid = TRUE;
GList *parts;
g_warn_if_fail (alert == NULL || *alert == NULL);
if (!rule->name || !*rule->name) {
if (alert)
*alert = e_alert_new ("filter:no-name", NULL);
return FALSE;
}
/* validate rule parts */
parts = rule->parts;
valid = parts != NULL;
while (parts && valid) {
valid = e_filter_part_validate ((EFilterPart *) parts->data, alert);
parts = parts->next;
}
return valid;
}
static gint
filter_rule_eq (EFilterRule *rule_a,
EFilterRule *rule_b)
{
GList *link_a;
GList *link_b;
if (rule_a->enabled != rule_b->enabled)
return FALSE;
if (rule_a->grouping != rule_b->grouping)
return FALSE;
if (rule_a->threading != rule_b->threading)
return FALSE;
if (g_strcmp0 (rule_a->name, rule_b->name) != 0)
return FALSE;
if (g_strcmp0 (rule_a->source, rule_b->source) != 0)
return FALSE;
link_a = rule_a->parts;
link_b = rule_b->parts;
while (link_a != NULL && link_b != NULL) {
EFilterPart *part_a = link_a->data;
EFilterPart *part_b = link_b->data;
if (!e_filter_part_eq (part_a, part_b))
return FALSE;
link_a = g_list_next (link_a);
link_b = g_list_next (link_b);
}
if (link_a != NULL || link_b != NULL)
return FALSE;
return TRUE;
}
static xmlNodePtr
filter_rule_xml_encode (EFilterRule *rule)
{
xmlNodePtr node, set, work;
GList *l;
node = xmlNewNode (NULL, (xmlChar *)"rule");
xmlSetProp (
node, (xmlChar *)"enabled",
(xmlChar *)(rule->enabled ? "true" : "false"));
switch (rule->grouping) {
case E_FILTER_GROUP_ALL:
xmlSetProp (node, (xmlChar *)"grouping", (xmlChar *)"all");
break;
case E_FILTER_GROUP_ANY:
xmlSetProp (node, (xmlChar *)"grouping", (xmlChar *)"any");
break;
}
switch (rule->threading) {
case E_FILTER_THREAD_NONE:
break;
case E_FILTER_THREAD_ALL:
xmlSetProp (node, (xmlChar *)"threading", (xmlChar *)"all");
break;
case E_FILTER_THREAD_REPLIES:
xmlSetProp (node, (xmlChar *)"threading", (xmlChar *)"replies");
break;
case E_FILTER_THREAD_REPLIES_PARENTS:
xmlSetProp (node, (xmlChar *)"threading", (xmlChar *)"replies_parents");
break;
case E_FILTER_THREAD_SINGLE:
xmlSetProp (node, (xmlChar *)"threading", (xmlChar *)"single");
break;
}
if (rule->source) {
xmlSetProp (node, (xmlChar *)"source", (xmlChar *) rule->source);
} else {
/* set to the default filter type */
xmlSetProp (node, (xmlChar *)"source", (xmlChar *)"incoming");
}
if (rule->name) {
gchar *escaped = g_markup_escape_text (rule->name, -1);
work = xmlNewNode (NULL, (xmlChar *)"title");
xmlNodeSetContent (work, (xmlChar *) escaped);
xmlAddChild (node, work);
g_free (escaped);
}
set = xmlNewNode (NULL, (xmlChar *)"partset");
xmlAddChild (node, set);
l = rule->parts;
while (l) {
work = e_filter_part_xml_encode ((EFilterPart *) l->data);
xmlAddChild (set, work);
l = l->next;
}
return node;
}
static gint
filter_rule_xml_decode (EFilterRule *rule,
xmlNodePtr node,
ERuleContext *context)
{
xmlNodePtr work;
gchar *grouping;
gchar *source;
g_free (rule->name);
rule->name = NULL;
grouping = (gchar *) xmlGetProp (node, (xmlChar *)"enabled");
if (!grouping)
rule->enabled = TRUE;
else {
rule->enabled = strcmp (grouping, "false") != 0;
xmlFree (grouping);
}
grouping = (gchar *) xmlGetProp (node, (xmlChar *)"grouping");
if (!strcmp (grouping, "any"))
rule->grouping = E_FILTER_GROUP_ANY;
else
rule->grouping = E_FILTER_GROUP_ALL;
xmlFree (grouping);
rule->threading = E_FILTER_THREAD_NONE;
if (context->flags & E_RULE_CONTEXT_THREADING
&& (grouping = (gchar *) xmlGetProp (node, (xmlChar *)"threading"))) {
if (!strcmp (grouping, "all"))
rule->threading = E_FILTER_THREAD_ALL;
else if (!strcmp (grouping, "replies"))
rule->threading = E_FILTER_THREAD_REPLIES;
else if (!strcmp (grouping, "replies_parents"))
rule->threading = E_FILTER_THREAD_REPLIES_PARENTS;
else if (!strcmp (grouping, "single"))
rule->threading = E_FILTER_THREAD_SINGLE;
xmlFree (grouping);
}
g_free (rule->source);
source = (gchar *) xmlGetProp (node, (xmlChar *)"source");
if (source) {
rule->source = g_strdup (source);
xmlFree (source);
} else {
/* default filter type */
rule->source = g_strdup ("incoming");
}
work = node->children;
while (work) {
if (!strcmp ((gchar *) work->name, "partset")) {
filter_rule_load_set (work, rule, context);
} else if (!strcmp ((gchar *) work->name, "title") ||
!strcmp ((gchar *) work->name, "_title")) {
if (!rule->name) {
gchar *str, *decstr = NULL;
str = (gchar *) xmlNodeGetContent (work);
if (str) {
decstr = g_strdup (_(str));
xmlFree (str);
}
rule->name = decstr;
}
}
work = work->next;
}
return 0;
}
static void
filter_rule_build_code (EFilterRule *rule,
GString *out)
{
switch (rule->threading) {
case E_FILTER_THREAD_NONE:
break;
case E_FILTER_THREAD_ALL:
g_string_append (out, " (match-threads \"all\" ");
break;
case E_FILTER_THREAD_REPLIES:
g_string_append (out, " (match-threads \"replies\" ");
break;
case E_FILTER_THREAD_REPLIES_PARENTS:
g_string_append (out, " (match-threads \"replies_parents\" ");
break;
case E_FILTER_THREAD_SINGLE:
g_string_append (out, " (match-threads \"single\" ");
break;
}
switch (rule->grouping) {
case E_FILTER_GROUP_ALL:
g_string_append (out, " (and\n ");
break;
case E_FILTER_GROUP_ANY:
g_string_append (out, " (or\n ");
break;
default:
g_warning ("Invalid grouping");
}
e_filter_part_build_code_list (rule->parts, out);
g_string_append (out, ")\n");
if (rule->threading != E_FILTER_THREAD_NONE)
g_string_append (out, ")\n");
}
static void
filter_rule_copy (EFilterRule *dest,
EFilterRule *src)
{
GList *node;
dest->enabled = src->enabled;
g_free (dest->name);
dest->name = g_strdup (src->name);
g_free (dest->source);
dest->source = g_strdup (src->source);
dest->grouping = src->grouping;
dest->threading = src->threading;
if (dest->parts) {
g_list_foreach (dest->parts, (GFunc) g_object_unref, NULL);
g_list_free (dest->parts);
dest->parts = NULL;
}
node = src->parts;
while (node) {
EFilterPart *part;
part = e_filter_part_clone (node->data);
dest->parts = g_list_append (dest->parts, part);
node = node->next;
}
}
static void
ensure_scrolled_width_cb (GtkAdjustment *adj,
GParamSpec *param_spec,
GtkScrolledWindow *scrolled_window)
{
gtk_scrolled_window_set_min_content_width (scrolled_window,
gtk_adjustment_get_upper (adj));
}
static void
ensure_scrolled_height_cb (GtkAdjustment *adj,
GParamSpec *param_spec,
GtkScrolledWindow *scrolled_window)
{
GtkWidget *toplevel;
GdkScreen *screen;
gint toplevel_height, scw_height, require_scw_height = 0, max_height;
toplevel = gtk_widget_get_toplevel (GTK_WIDGET (scrolled_window));
if (!toplevel || !gtk_widget_is_toplevel (toplevel))
return;
scw_height = gtk_widget_get_allocated_height (GTK_WIDGET (scrolled_window));
gtk_widget_get_preferred_height_for_width (gtk_bin_get_child (GTK_BIN (scrolled_window)),
gtk_widget_get_allocated_width (GTK_WIDGET (scrolled_window)),
&require_scw_height, NULL);
if (scw_height >= require_scw_height) {
if (require_scw_height > 0)
gtk_scrolled_window_set_min_content_height (scrolled_window, require_scw_height);
return;
}
if (!GTK_IS_WINDOW (toplevel) ||
!gtk_widget_get_window (toplevel))
return;
screen = gtk_window_get_screen (GTK_WINDOW (toplevel));
if (screen) {
gint monitor;
GdkRectangle workarea;
monitor = gdk_screen_get_monitor_at_window (screen, gtk_widget_get_window (toplevel));
if (monitor < 0)
monitor = 0;
gdk_screen_get_monitor_workarea (screen, monitor, &workarea);
/* can enlarge up to 4 / 5 monitor's work area height */
max_height = workarea.height * 4 / 5;
} else {
return;
}
toplevel_height = gtk_widget_get_allocated_height (toplevel);
if (toplevel_height + require_scw_height - scw_height > max_height)
return;
gtk_scrolled_window_set_min_content_height (scrolled_window, require_scw_height);
}
static GtkWidget *
filter_rule_get_widget (EFilterRule *rule,
ERuleContext *context)
{
GtkGrid *hgrid, *vgrid, *inframe;
GtkWidget *parts, *add, *label, *name, *w;
GtkWidget *combobox;
GtkWidget *scrolledwindow;
GtkAdjustment *hadj, *vadj;
GList *l;
gchar *text;
EFilterPart *part;
FilterRuleData *data;
gint rows, i;
/* this stuff should probably be a table, but the
* rule parts need to be a vbox */
vgrid = GTK_GRID (gtk_grid_new ());
gtk_grid_set_row_spacing (vgrid, 6);
gtk_orientable_set_orientation (GTK_ORIENTABLE (vgrid), GTK_ORIENTATION_VERTICAL);
label = gtk_label_new_with_mnemonic (_("R_ule name:"));
name = gtk_entry_new ();
gtk_widget_set_hexpand (name, TRUE);
gtk_widget_set_halign (name, GTK_ALIGN_FILL);
gtk_label_set_mnemonic_widget ((GtkLabel *) label, name);
if (!rule->name) {
rule->name = g_strdup (_("Untitled"));
gtk_entry_set_text (GTK_ENTRY (name), rule->name);
/* FIXME: do we want the following code in the future? */
/*gtk_editable_select_region (GTK_EDITABLE (name), 0, -1);*/
} else {
gtk_entry_set_text (GTK_ENTRY (name), rule->name);
}
g_signal_connect (
name, "realize",
G_CALLBACK (gtk_widget_grab_focus), name);
hgrid = GTK_GRID (gtk_grid_new ());
gtk_grid_set_column_spacing (hgrid, 12);
gtk_grid_attach (hgrid, label, 0, 0, 1, 1);
gtk_grid_attach_next_to (hgrid, name, label, GTK_POS_RIGHT, 1, 1);
gtk_container_add (GTK_CONTAINER (vgrid), GTK_WIDGET (hgrid));
g_signal_connect (
name, "changed",
G_CALLBACK (name_changed), rule);
hgrid = GTK_GRID (gtk_grid_new ());
gtk_grid_set_column_spacing (hgrid, 12);
gtk_container_add (GTK_CONTAINER (vgrid), GTK_WIDGET (hgrid));
/* this is the parts table, it should probably be inside a scrolling list */
rows = g_list_length (rule->parts);
parts = gtk_table_new (rows, 2, FALSE);
/* data for the parts part of the display */
data = g_malloc0 (sizeof (*data));
data->context = context;
data->rule = rule;
data->parts = parts;
/* only set to automatically clean up the memory */
g_object_set_data_full ((GObject *) vgrid, "data", data, g_free);
if (context->flags & E_RULE_CONTEXT_GROUPING) {
const gchar *thread_types[] = {
N_("all the following conditions"),
N_("any of the following conditions")
};
hgrid = GTK_GRID (gtk_grid_new ());
gtk_grid_set_column_spacing (hgrid, 12);
label = gtk_label_new_with_mnemonic (_("_Find items which match:"));
combobox = gtk_combo_box_text_new ();
for (i = 0; i < 2; i++) {
gtk_combo_box_text_append_text (
GTK_COMBO_BOX_TEXT (combobox),
_(thread_types[i]));
}
gtk_label_set_mnemonic_widget ((GtkLabel *) label, combobox);
gtk_combo_box_set_active (GTK_COMBO_BOX (combobox), rule->grouping);
gtk_grid_attach (hgrid, label, 0, 0, 1, 1);
gtk_grid_attach_next_to (hgrid, combobox, label, GTK_POS_RIGHT, 1, 1);
g_signal_connect (
combobox, "changed",
G_CALLBACK (filter_rule_grouping_changed_cb), rule);
gtk_container_add (GTK_CONTAINER (vgrid), GTK_WIDGET (hgrid));
} else {
text = g_strdup_printf ("<b>%s</b>",
_("Find items that meet the following conditions"));
label = gtk_label_new (text);
gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
gtk_container_add (GTK_CONTAINER (vgrid), label);
g_free (text);
}
hgrid = GTK_GRID (gtk_grid_new ());
gtk_grid_set_column_spacing (hgrid, 12);
if (context->flags & E_RULE_CONTEXT_THREADING) {
const gchar *thread_types[] = {
/* Translators: "None" for not including threads;
* part of "Include threads: None" */
N_("None"),
N_("All related"),
N_("Replies"),
N_("Replies and parents"),
N_("No reply or parent")
};
label = gtk_label_new_with_mnemonic (_("I_nclude threads:"));
combobox = gtk_combo_box_text_new ();
for (i = 0; i < 5; i++) {
gtk_combo_box_text_append_text (
GTK_COMBO_BOX_TEXT (combobox),
_(thread_types[i]));
}
gtk_label_set_mnemonic_widget ((GtkLabel *) label, combobox);
gtk_combo_box_set_active (GTK_COMBO_BOX (combobox), rule->threading);
gtk_grid_attach (hgrid, label, 0, 0, 1, 1);
gtk_grid_attach_next_to (hgrid, combobox, label, GTK_POS_RIGHT, 1, 1);
g_signal_connect (
combobox, "changed",
G_CALLBACK (filter_rule_threading_changed_cb), rule);
}
gtk_container_add (GTK_CONTAINER (vgrid), GTK_WIDGET (hgrid));
hgrid = GTK_GRID (gtk_grid_new ());
gtk_grid_set_column_spacing (hgrid, 3);
gtk_widget_set_vexpand (GTK_WIDGET (hgrid), TRUE);
gtk_widget_set_valign (GTK_WIDGET (hgrid), GTK_ALIGN_FILL);
gtk_container_add (GTK_CONTAINER (vgrid), GTK_WIDGET (hgrid));
label = gtk_label_new ("");
gtk_grid_attach (hgrid, label, 0, 0, 1, 1);
inframe = GTK_GRID (gtk_grid_new ());
gtk_grid_set_row_spacing (inframe, 6);
gtk_orientable_set_orientation (GTK_ORIENTABLE (inframe), GTK_ORIENTATION_VERTICAL);
gtk_widget_set_hexpand (GTK_WIDGET (inframe), TRUE);
gtk_widget_set_halign (GTK_WIDGET (inframe), GTK_ALIGN_FILL);
gtk_widget_set_vexpand (GTK_WIDGET (inframe), TRUE);
gtk_widget_set_valign (GTK_WIDGET (inframe), GTK_ALIGN_FILL);
gtk_grid_attach_next_to (hgrid, GTK_WIDGET (inframe), label, GTK_POS_RIGHT, 1, 1);
l = rule->parts;
i = 0;
while (l) {
part = l->data;
w = get_rule_part_widget (context, part, rule);
attach_rule (w, data, part, i++);
l = g_list_next (l);
}
hadj = GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 1.0, 1.0, 1.0, 1.0));
vadj = GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 1.0, 1.0, 1.0, 1.0));
scrolledwindow = gtk_scrolled_window_new (hadj, vadj);
g_signal_connect (
hadj, "notify::upper",
G_CALLBACK (ensure_scrolled_width_cb), scrolledwindow);
g_signal_connect (
vadj, "notify::upper",
G_CALLBACK (ensure_scrolled_height_cb), scrolledwindow);
gtk_scrolled_window_set_policy (
GTK_SCROLLED_WINDOW (scrolledwindow),
GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
gtk_scrolled_window_add_with_viewport (
GTK_SCROLLED_WINDOW (scrolledwindow), parts);
gtk_widget_set_vexpand (scrolledwindow, TRUE);
gtk_widget_set_valign (scrolledwindow, GTK_ALIGN_FILL);
gtk_widget_set_hexpand (scrolledwindow, TRUE);
gtk_widget_set_halign (scrolledwindow, GTK_ALIGN_FILL);
gtk_container_add (GTK_CONTAINER (inframe), scrolledwindow);
hgrid = GTK_GRID (gtk_grid_new ());
gtk_grid_set_column_spacing (hgrid, 3);
add = gtk_button_new_with_mnemonic (_("A_dd Condition"));
gtk_button_set_image (
GTK_BUTTON (add), gtk_image_new_from_stock (
GTK_STOCK_ADD, GTK_ICON_SIZE_BUTTON));
g_signal_connect (
add, "clicked",
G_CALLBACK (more_parts), data);
gtk_grid_attach (hgrid, add, 0, 0, 1, 1);
gtk_container_add (GTK_CONTAINER (inframe), GTK_WIDGET (hgrid));
gtk_widget_show_all (GTK_WIDGET (vgrid));
g_object_set_data (G_OBJECT (add), "scrolled-window", scrolledwindow);
return GTK_WIDGET (vgrid);
}
static void
e_filter_rule_class_init (EFilterRuleClass *class)
{
GObjectClass *object_class;
g_type_class_add_private (class, sizeof (EFilterRulePrivate));
object_class = G_OBJECT_CLASS (class);
object_class->finalize = filter_rule_finalize;
class->validate = filter_rule_validate;
class->eq = filter_rule_eq;
class->xml_encode = filter_rule_xml_encode;
class->xml_decode = filter_rule_xml_decode;
class->build_code = filter_rule_build_code;
class->copy = filter_rule_copy;
class->get_widget = filter_rule_get_widget;
signals[CHANGED] = g_signal_new (
"changed",
E_TYPE_FILTER_RULE,
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (EFilterRuleClass, changed),
NULL,
NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE, 0);
}
static void
e_filter_rule_init (EFilterRule *rule)
{
rule->priv = E_FILTER_RULE_GET_PRIVATE (rule);
rule->enabled = TRUE;
}
/**
* filter_rule_new:
*
* Create a new EFilterRule object.
*
* Return value: A new #EFilterRule object.
**/
EFilterRule *
e_filter_rule_new (void)
{
return g_object_new (E_TYPE_FILTER_RULE, NULL);
}
EFilterRule *
e_filter_rule_clone (EFilterRule *rule)
{
EFilterRule *clone;
g_return_val_if_fail (E_IS_FILTER_RULE (rule), NULL);
clone = g_object_new (G_OBJECT_TYPE (rule), NULL);
e_filter_rule_copy (clone, rule);
return clone;
}
void
e_filter_rule_set_name (EFilterRule *rule,
const gchar *name)
{
g_return_if_fail (E_IS_FILTER_RULE (rule));
if (g_strcmp0 (rule->name, name) == 0)
return;
g_free (rule->name);
rule->name = g_strdup (name);
e_filter_rule_emit_changed (rule);
}
void
e_filter_rule_set_source (EFilterRule *rule,
const gchar *source)
{
g_return_if_fail (E_IS_FILTER_RULE (rule));
if (g_strcmp0 (rule->source, source) == 0)
return;
g_free (rule->source);
rule->source = g_strdup (source);
e_filter_rule_emit_changed (rule);
}
gint
e_filter_rule_validate (EFilterRule *rule,
EAlert **alert)
{
EFilterRuleClass *class;
g_return_val_if_fail (E_IS_FILTER_RULE (rule), FALSE);
class = E_FILTER_RULE_GET_CLASS (rule);
g_return_val_if_fail (class->validate != NULL, FALSE);
return class->validate (rule, alert);
}
gint
e_filter_rule_eq (EFilterRule *rule_a,
EFilterRule *rule_b)
{
EFilterRuleClass *class;
g_return_val_if_fail (E_IS_FILTER_RULE (rule_a), FALSE);
g_return_val_if_fail (E_IS_FILTER_RULE (rule_b), FALSE);
class = E_FILTER_RULE_GET_CLASS (rule_a);
g_return_val_if_fail (class->eq != NULL, FALSE);
if (G_OBJECT_TYPE (rule_a) != G_OBJECT_TYPE (rule_b))
return FALSE;
return class->eq (rule_a, rule_b);
}
xmlNodePtr
e_filter_rule_xml_encode (EFilterRule *rule)
{
EFilterRuleClass *class;
g_return_val_if_fail (E_IS_FILTER_RULE (rule), NULL);
class = E_FILTER_RULE_GET_CLASS (rule);
g_return_val_if_fail (class->xml_encode != NULL, NULL);
return class->xml_encode (rule);
}
gint
e_filter_rule_xml_decode (EFilterRule *rule,
xmlNodePtr node,
ERuleContext *context)
{
EFilterRuleClass *class;
gint result;
g_return_val_if_fail (E_IS_FILTER_RULE (rule), FALSE);
g_return_val_if_fail (node != NULL, FALSE);
g_return_val_if_fail (E_IS_RULE_CONTEXT (context), FALSE);
class = E_FILTER_RULE_GET_CLASS (rule);
g_return_val_if_fail (class->xml_decode != NULL, FALSE);
rule->priv->frozen++;
result = class->xml_decode (rule, node, context);
rule->priv->frozen--;
e_filter_rule_emit_changed (rule);
return result;
}
void
e_filter_rule_copy (EFilterRule *dst_rule,
EFilterRule *src_rule)
{
EFilterRuleClass *class;
g_return_if_fail (E_IS_FILTER_RULE (dst_rule));
g_return_if_fail (E_IS_FILTER_RULE (src_rule));
class = E_FILTER_RULE_GET_CLASS (dst_rule);
g_return_if_fail (class->copy != NULL);
class->copy (dst_rule, src_rule);
e_filter_rule_emit_changed (dst_rule);
}
void
e_filter_rule_add_part (EFilterRule *rule,
EFilterPart *part)
{
g_return_if_fail (E_IS_FILTER_RULE (rule));
g_return_if_fail (E_IS_FILTER_PART (part));
rule->parts = g_list_append (rule->parts, part);
e_filter_rule_emit_changed (rule);
}
void
e_filter_rule_remove_part (EFilterRule *rule,
EFilterPart *part)
{
g_return_if_fail (E_IS_FILTER_RULE (rule));
g_return_if_fail (E_IS_FILTER_PART (part));
rule->parts = g_list_remove (rule->parts, part);
e_filter_rule_emit_changed (rule);
}
void
e_filter_rule_replace_part (EFilterRule *rule,
EFilterPart *old_part,
EFilterPart *new_part)
{
GList *link;
g_return_if_fail (E_IS_FILTER_RULE (rule));
g_return_if_fail (E_IS_FILTER_PART (old_part));
g_return_if_fail (E_IS_FILTER_PART (new_part));
link = g_list_find (rule->parts, old_part);
if (link != NULL)
link->data = new_part;
else
rule->parts = g_list_append (rule->parts, new_part);
e_filter_rule_emit_changed (rule);
}
void
e_filter_rule_build_code (EFilterRule *rule,
GString *out)
{
EFilterRuleClass *class;
g_return_if_fail (E_IS_FILTER_RULE (rule));
g_return_if_fail (out != NULL);
class = E_FILTER_RULE_GET_CLASS (rule);
g_return_if_fail (class->build_code != NULL);
class->build_code (rule, out);
}
void
e_filter_rule_emit_changed (EFilterRule *rule)
{
g_return_if_fail (E_IS_FILTER_RULE (rule));
if (rule->priv->frozen == 0)
g_signal_emit (rule, signals[CHANGED], 0);
}
EFilterRule *
e_filter_rule_next_list (GList *list,
EFilterRule *last,
const gchar *source)
{
GList *link = list;
if (last != NULL) {
link = g_list_find (link, last);
if (link == NULL)
link = list;
else
link = g_list_next (link);
}
if (source != NULL) {
while (link != NULL) {
EFilterRule *rule = link->data;
if (g_strcmp0 (rule->source, source) == 0)
break;
link = g_list_next (link);
}
}
return (link != NULL) ? link->data : NULL;
}
EFilterRule *
e_filter_rule_find_list (GList *list,
const gchar *name,
const gchar *source)
{
GList *link;
g_return_val_if_fail (name != NULL, FALSE);
for (link = list; link != NULL; link = g_list_next (link)) {
EFilterRule *rule = link->data;
if (strcmp (rule->name, name) == 0)
if (source == NULL || (rule->source != NULL &&
strcmp (rule->source, source) == 0))
return rule;
}
return NULL;
}
#ifdef FOR_TRANSLATIONS_ONLY
static gchar *list[] = {
N_("Incoming"), N_("Outgoing")
};
#endif