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 "e-rule-editor.h"
26 
27 /* for getenv only, remove when getenv need removed */
28 #include <stdlib.h>
29 #include <string.h>
30 
31 #include <glib/gi18n.h>
32 
33 #include "e-alert-dialog.h"
34 #include "e-misc-utils.h"
35 
36 #define E_RULE_EDITOR_GET_PRIVATE(obj) \
37 	(G_TYPE_INSTANCE_GET_PRIVATE \
38 	((obj), E_TYPE_RULE_EDITOR, ERuleEditorPrivate))
39 
40 enum {
41 	BUTTON_ADD,
42 	BUTTON_EDIT,
43 	BUTTON_DELETE,
44 	BUTTON_TOP,
45 	BUTTON_UP,
46 	BUTTON_DOWN,
47 	BUTTON_BOTTOM,
48 	BUTTON_LAST
49 };
50 
51 struct _ERuleEditorPrivate {
52 	GtkButton *buttons[BUTTON_LAST];
53 	gint drag_index;
54 };
55 
G_DEFINE_TYPE(ERuleEditor,e_rule_editor,GTK_TYPE_DIALOG)56 G_DEFINE_TYPE (
57 	ERuleEditor,
58 	e_rule_editor,
59 	GTK_TYPE_DIALOG)
60 
61 static gboolean
62 update_selected_rule (ERuleEditor *editor)
63 {
64 	GtkTreeSelection *selection;
65 	GtkTreeModel *model;
66 	GtkTreeIter iter;
67 
68 	selection = gtk_tree_view_get_selection (editor->list);
69 	if (selection && gtk_tree_selection_get_selected (selection, &model, &iter)) {
70 		gtk_tree_model_get (GTK_TREE_MODEL (editor->model), &iter, 1, &editor->current, -1);
71 		return TRUE;
72 	}
73 
74 	return FALSE;
75 }
76 
77 static void
dialog_rule_changed(EFilterRule * fr,GtkWidget * dialog)78 dialog_rule_changed (EFilterRule *fr,
79                      GtkWidget *dialog)
80 {
81 	g_return_if_fail (dialog != NULL);
82 
83 	gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog), GTK_RESPONSE_OK, fr && fr->parts);
84 }
85 
86 static void
add_editor_response(GtkWidget * dialog,gint button,ERuleEditor * editor)87 add_editor_response (GtkWidget *dialog,
88                      gint button,
89                      ERuleEditor *editor)
90 {
91 	GtkTreeSelection *selection;
92 	GtkTreePath *path;
93 	GtkTreeIter iter;
94 
95 	g_signal_handlers_disconnect_by_func (editor->edit, G_CALLBACK (dialog_rule_changed), editor->dialog);
96 
97 	if (button == GTK_RESPONSE_OK) {
98 		EAlert *alert = NULL;
99 		if (!e_filter_rule_validate (editor->edit, &alert)) {
100 			e_alert_run_dialog (GTK_WINDOW (dialog), alert);
101 			g_object_unref (alert);
102 			return;
103 		}
104 
105 		if (e_rule_context_find_rule (editor->context, editor->edit->name, editor->edit->source)) {
106 			e_alert_run_dialog_for_args (
107 				GTK_WINDOW (dialog),
108 				"filter:bad-name-notunique",
109 				editor->edit->name, NULL);
110 			return;
111 		}
112 
113 		g_object_ref (editor->edit);
114 
115 		e_rule_context_add_rule (editor->context, editor->edit);
116 
117 		if (g_strcmp0 (editor->source, editor->edit->source) == 0) {
118 			gtk_list_store_append (editor->model, &iter);
119 			gtk_list_store_set (
120 				editor->model, &iter,
121 				0, editor->edit->name,
122 				1, editor->edit,
123 				2, editor->edit->enabled, -1);
124 			selection = gtk_tree_view_get_selection (editor->list);
125 			gtk_tree_selection_select_iter (selection, &iter);
126 
127 			/* scroll to the newly added row */
128 			path = gtk_tree_model_get_path (
129 				GTK_TREE_MODEL (editor->model), &iter);
130 			gtk_tree_view_scroll_to_cell (
131 				editor->list, path, NULL, TRUE, 1.0, 0.0);
132 			gtk_tree_path_free (path);
133 
134 			editor->current = editor->edit;
135 		} else {
136 			editor->current = NULL;
137 			update_selected_rule (editor);
138 		}
139 	}
140 
141 	gtk_widget_destroy (dialog);
142 }
143 
144 static void
editor_destroy(ERuleEditor * editor,GObject * deadbeef)145 editor_destroy (ERuleEditor *editor,
146                 GObject *deadbeef)
147 {
148 	g_clear_object (&editor->edit);
149 
150 	editor->dialog = NULL;
151 
152 	gtk_widget_set_sensitive (GTK_WIDGET (editor), TRUE);
153 	e_rule_editor_set_sensitive (editor);
154 }
155 
156 static void
cursor_changed(GtkTreeView * treeview,ERuleEditor * editor)157 cursor_changed (GtkTreeView *treeview,
158                 ERuleEditor *editor)
159 {
160 	if (update_selected_rule (editor)) {
161 		g_return_if_fail (editor->current);
162 
163 		e_rule_editor_set_sensitive (editor);
164 	}
165 }
166 
167 static void
rule_add(GtkWidget * widget,ERuleEditor * editor)168 rule_add (GtkWidget *widget,
169           ERuleEditor *editor)
170 {
171 	GtkWidget *rules;
172 	GtkWidget *content_area;
173 
174 	if (editor->edit != NULL)
175 		return;
176 
177 	editor->edit = e_rule_editor_create_rule (editor);
178 	e_filter_rule_set_source (editor->edit, editor->source);
179 	rules = e_filter_rule_get_widget (editor->edit, editor->context);
180 
181 	editor->dialog = gtk_dialog_new ();
182 	gtk_dialog_add_buttons (
183 		GTK_DIALOG (editor->dialog),
184 		_("_Cancel"), GTK_RESPONSE_CANCEL,
185 		_("_OK"), GTK_RESPONSE_OK,
186 		NULL);
187 
188 	gtk_window_set_title ((GtkWindow *) editor->dialog, _("Add Rule"));
189 	gtk_window_set_default_size (GTK_WINDOW (editor->dialog), 650, 400);
190 	gtk_window_set_resizable (GTK_WINDOW (editor->dialog), TRUE);
191 	gtk_window_set_transient_for ((GtkWindow *) editor->dialog, (GtkWindow *) editor);
192 	gtk_container_set_border_width ((GtkContainer *) editor->dialog, 6);
193 
194 	content_area = gtk_dialog_get_content_area (GTK_DIALOG (editor->dialog));
195 	gtk_box_pack_start (GTK_BOX (content_area), rules, TRUE, TRUE, 3);
196 
197 	g_signal_connect (
198 		editor->dialog, "response",
199 		G_CALLBACK (add_editor_response), editor);
200 	g_object_weak_ref ((GObject *) editor->dialog, (GWeakNotify) editor_destroy, editor);
201 
202 	g_signal_connect (
203 		editor->edit, "changed",
204 		G_CALLBACK (dialog_rule_changed), editor->dialog);
205 	dialog_rule_changed (editor->edit, editor->dialog);
206 
207 	gtk_widget_set_sensitive (GTK_WIDGET (editor), FALSE);
208 
209 	gtk_widget_show (editor->dialog);
210 }
211 
212 static void
edit_editor_response(GtkWidget * dialog,gint button,ERuleEditor * editor)213 edit_editor_response (GtkWidget *dialog,
214                       gint button,
215                       ERuleEditor *editor)
216 {
217 	EFilterRule *rule;
218 	GtkTreePath *path;
219 	GtkTreeIter iter;
220 	gint pos;
221 
222 	g_signal_handlers_disconnect_by_func (editor->edit, G_CALLBACK (dialog_rule_changed), editor->dialog);
223 
224 	if (button == GTK_RESPONSE_OK) {
225 		EAlert *alert = NULL;
226 		if (!e_filter_rule_validate (editor->edit, &alert)) {
227 			e_alert_run_dialog (GTK_WINDOW (dialog), alert);
228 			g_object_unref (alert);
229 			return;
230 		}
231 
232 		rule = e_rule_context_find_rule (
233 			editor->context,
234 			editor->edit->name,
235 			editor->edit->source);
236 
237 		if (rule != NULL && rule != editor->current) {
238 			e_alert_run_dialog_for_args (
239 				GTK_WINDOW (dialog),
240 				"filter:bad-name-notunique",
241 				rule->name, NULL);
242 			return;
243 		}
244 
245 		pos = e_rule_context_get_rank_rule (
246 			editor->context,
247 			editor->current,
248 			editor->source);
249 
250 		if (pos != -1) {
251 			path = gtk_tree_path_new ();
252 			gtk_tree_path_append_index (path, pos);
253 			gtk_tree_model_get_iter (
254 				GTK_TREE_MODEL (editor->model), &iter, path);
255 			gtk_tree_path_free (path);
256 
257 			/* replace the old rule with the new rule */
258 			e_filter_rule_copy (editor->current, editor->edit);
259 
260 			if (g_strcmp0 (editor->source, editor->edit->source) == 0) {
261 				gtk_list_store_set (
262 					editor->model, &iter,
263 					0, editor->edit->name, -1);
264 			} else {
265 				gtk_list_store_remove (editor->model, &iter);
266 				editor->current = NULL;
267 
268 				update_selected_rule (editor);
269 			}
270 		}
271 	}
272 
273 	gtk_widget_destroy (dialog);
274 }
275 
276 static void
rule_edit(GtkWidget * widget,ERuleEditor * editor)277 rule_edit (GtkWidget *widget,
278            ERuleEditor *editor)
279 {
280 	GtkWidget *rules;
281 	GtkWidget *content_area;
282 
283 	update_selected_rule (editor);
284 
285 	if (editor->current == NULL || editor->edit != NULL)
286 		return;
287 
288 	editor->edit = e_filter_rule_clone (editor->current);
289 
290 	rules = e_filter_rule_get_widget (editor->edit, editor->context);
291 
292 	editor->dialog = gtk_dialog_new ();
293 	gtk_dialog_add_buttons (
294 		(GtkDialog *) editor->dialog,
295 				_("_Cancel"), GTK_RESPONSE_CANCEL,
296 				_("_OK"), GTK_RESPONSE_OK,
297 				NULL);
298 
299 	gtk_window_set_title ((GtkWindow *) editor->dialog, _("Edit Rule"));
300 	gtk_window_set_default_size (GTK_WINDOW (editor->dialog), 650, 400);
301 	gtk_window_set_resizable (GTK_WINDOW (editor->dialog), TRUE);
302 	gtk_window_set_transient_for (GTK_WINDOW (editor->dialog), GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (editor))));
303 	gtk_container_set_border_width ((GtkContainer *) editor->dialog, 6);
304 
305 	content_area = gtk_dialog_get_content_area (GTK_DIALOG (editor->dialog));
306 	gtk_box_pack_start (GTK_BOX (content_area), rules, TRUE, TRUE, 3);
307 
308 	g_signal_connect (
309 		editor->dialog, "response",
310 		G_CALLBACK (edit_editor_response), editor);
311 	g_object_weak_ref ((GObject *) editor->dialog, (GWeakNotify) editor_destroy, editor);
312 
313 	g_signal_connect (
314 		editor->edit, "changed",
315 		G_CALLBACK (dialog_rule_changed), editor->dialog);
316 	dialog_rule_changed (editor->edit, editor->dialog);
317 
318 	gtk_widget_set_sensitive (GTK_WIDGET (editor), FALSE);
319 
320 	gtk_widget_show (editor->dialog);
321 }
322 
323 static void
rule_delete(GtkWidget * widget,ERuleEditor * editor)324 rule_delete (GtkWidget *widget,
325              ERuleEditor *editor)
326 {
327 	GtkTreeSelection *selection;
328 	GtkTreePath *path;
329 	GtkTreeIter iter;
330 	gint pos, len;
331 
332 	update_selected_rule (editor);
333 
334 	pos = e_rule_context_get_rank_rule (editor->context, editor->current, editor->source);
335 	if (pos != -1) {
336 		GtkWindow *parent;
337 		GtkWidget *toplevel;
338 		gint response;
339 
340 		toplevel = gtk_widget_get_toplevel (widget);
341 		parent = GTK_IS_WINDOW (toplevel) ? GTK_WINDOW (toplevel) : NULL;
342 
343 		response = e_alert_run_dialog_for_args (parent, "filter:remove-rule-question",
344 			(editor->current && editor->current->name) ? editor->current->name : "", NULL);
345 
346 		if (response != GTK_RESPONSE_YES)
347 			pos = -1;
348 	}
349 
350 	if (pos != -1) {
351 		EFilterRule *delete_rule = editor->current;
352 
353 		editor->current = NULL;
354 
355 		e_rule_context_remove_rule (editor->context, delete_rule);
356 
357 		path = gtk_tree_path_new ();
358 		gtk_tree_path_append_index (path, pos);
359 		gtk_tree_model_get_iter (GTK_TREE_MODEL (editor->model), &iter, path);
360 		gtk_list_store_remove (editor->model, &iter);
361 		gtk_tree_path_free (path);
362 
363 		g_object_unref (delete_rule);
364 
365 		/* now select the next rule */
366 		len = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (editor->model), NULL);
367 		pos = pos >= len ? len - 1 : pos;
368 
369 		if (pos >= 0) {
370 			path = gtk_tree_path_new ();
371 			gtk_tree_path_append_index (path, pos);
372 			gtk_tree_model_get_iter (GTK_TREE_MODEL (editor->model), &iter, path);
373 			gtk_tree_path_free (path);
374 
375 			/* select the new row */
376 			selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (editor->list));
377 			gtk_tree_selection_select_iter (selection, &iter);
378 
379 			/* scroll to the selected row */
380 			path = gtk_tree_model_get_path ((GtkTreeModel *) editor->model, &iter);
381 			gtk_tree_view_scroll_to_cell (editor->list, path, NULL, FALSE, 0.0, 0.0);
382 			gtk_tree_path_free (path);
383 
384 			/* update our selection state */
385 			cursor_changed (editor->list, editor);
386 			return;
387 		}
388 	}
389 
390 	e_rule_editor_set_sensitive (editor);
391 }
392 
393 static void
rule_move(ERuleEditor * editor,gint from,gint to)394 rule_move (ERuleEditor *editor,
395            gint from,
396            gint to)
397 {
398 	GtkTreeSelection *selection;
399 	GtkTreePath *path;
400 	GtkTreeIter iter;
401 	EFilterRule *rule;
402 
403 	e_rule_context_rank_rule (
404 		editor->context, editor->current, editor->source, to);
405 
406 	path = gtk_tree_path_new ();
407 	gtk_tree_path_append_index (path, from);
408 	gtk_tree_model_get_iter (GTK_TREE_MODEL (editor->model), &iter, path);
409 	gtk_tree_path_free (path);
410 
411 	gtk_tree_model_get (GTK_TREE_MODEL (editor->model), &iter, 1, &rule, -1);
412 	g_return_if_fail (rule != NULL);
413 
414 	/* remove and then re-insert the row at the new location */
415 	gtk_list_store_remove (editor->model, &iter);
416 	gtk_list_store_insert (editor->model, &iter, to);
417 
418 	/* set the data on the row */
419 	gtk_list_store_set (editor->model, &iter, 0, rule->name, 1, rule, 2, rule->enabled, -1);
420 
421 	/* select the row */
422 	selection = gtk_tree_view_get_selection (editor->list);
423 	gtk_tree_selection_select_iter (selection, &iter);
424 
425 	/* scroll to the selected row */
426 	path = gtk_tree_model_get_path ((GtkTreeModel *) editor->model, &iter);
427 	gtk_tree_view_scroll_to_cell (editor->list, path, NULL, FALSE, 0.0, 0.0);
428 	gtk_tree_path_free (path);
429 
430 	e_rule_editor_set_sensitive (editor);
431 }
432 
433 static void
rule_top(GtkWidget * widget,ERuleEditor * editor)434 rule_top (GtkWidget *widget,
435           ERuleEditor *editor)
436 {
437 	gint pos;
438 
439 	update_selected_rule (editor);
440 
441 	pos = e_rule_context_get_rank_rule (
442 		editor->context, editor->current, editor->source);
443 	if (pos > 0)
444 		rule_move (editor, pos, 0);
445 }
446 
447 static void
rule_up(GtkWidget * widget,ERuleEditor * editor)448 rule_up (GtkWidget *widget,
449          ERuleEditor *editor)
450 {
451 	gint pos;
452 
453 	update_selected_rule (editor);
454 
455 	pos = e_rule_context_get_rank_rule (
456 		editor->context, editor->current, editor->source);
457 	if (pos > 0)
458 		rule_move (editor, pos, pos - 1);
459 }
460 
461 static void
rule_down(GtkWidget * widget,ERuleEditor * editor)462 rule_down (GtkWidget *widget,
463            ERuleEditor *editor)
464 {
465 	gint pos;
466 
467 	update_selected_rule (editor);
468 
469 	pos = e_rule_context_get_rank_rule (
470 		editor->context, editor->current, editor->source);
471 	if (pos >= 0)
472 		rule_move (editor, pos, pos + 1);
473 }
474 
475 static void
rule_bottom(GtkWidget * widget,ERuleEditor * editor)476 rule_bottom (GtkWidget *widget,
477              ERuleEditor *editor)
478 {
479 	gint pos;
480 	gint count = 0;
481 	EFilterRule *rule = NULL;
482 
483 	update_selected_rule (editor);
484 
485 	pos = e_rule_context_get_rank_rule (
486 		editor->context, editor->current, editor->source);
487 	/* There's probably a better/faster way to get the count of the list here */
488 	while ((rule = e_rule_context_next_rule (editor->context, rule, editor->source)))
489 		count++;
490 	count--;
491 	if (pos >= 0)
492 		rule_move (editor, pos, count);
493 }
494 
495 static struct {
496 	const gchar *name;
497 	GCallback func;
498 } edit_buttons[] = {
499 	{ "rule_add",    G_CALLBACK (rule_add)    },
500 	{ "rule_edit",   G_CALLBACK (rule_edit)   },
501 	{ "rule_delete", G_CALLBACK (rule_delete) },
502 	{ "rule_top",    G_CALLBACK (rule_top)    },
503 	{ "rule_up",     G_CALLBACK (rule_up)     },
504 	{ "rule_down",   G_CALLBACK (rule_down)   },
505 	{ "rule_bottom", G_CALLBACK (rule_bottom) },
506 };
507 
508 static void
rule_editor_finalize(GObject * object)509 rule_editor_finalize (GObject *object)
510 {
511 	ERuleEditor *editor = E_RULE_EDITOR (object);
512 
513 	g_object_unref (editor->context);
514 
515 	g_clear_pointer (&editor->source, g_free);
516 
517 	/* Chain up to parent's finalize() method. */
518 	G_OBJECT_CLASS (e_rule_editor_parent_class)->finalize (object);
519 }
520 
521 static void
rule_editor_dispose(GObject * object)522 rule_editor_dispose (GObject *object)
523 {
524 	ERuleEditor *editor = E_RULE_EDITOR (object);
525 
526 	if (editor->dialog != NULL) {
527 		gtk_widget_destroy (GTK_WIDGET (editor->dialog));
528 		editor->dialog = NULL;
529 	}
530 
531 	/* Chain up to parent's dispose() method. */
532 	G_OBJECT_CLASS (e_rule_editor_parent_class)->dispose (object);
533 }
534 
535 static void
rule_editor_set_source(ERuleEditor * editor,const gchar * source)536 rule_editor_set_source (ERuleEditor *editor,
537                         const gchar *source)
538 {
539 	EFilterRule *rule = NULL;
540 	GtkTreeIter iter;
541 
542 	gtk_list_store_clear (editor->model);
543 
544 	while ((rule = e_rule_context_next_rule (editor->context, rule, source)) != NULL) {
545 		gtk_list_store_append (editor->model, &iter);
546 		gtk_list_store_set (
547 			editor->model, &iter,
548 			0, rule->name, 1, rule, 2, rule->enabled, -1);
549 	}
550 
551 	g_free (editor->source);
552 	editor->source = g_strdup (source);
553 	editor->current = NULL;
554 	e_rule_editor_set_sensitive (editor);
555 }
556 
557 static void
rule_editor_set_sensitive(ERuleEditor * editor)558 rule_editor_set_sensitive (ERuleEditor *editor)
559 {
560 	EFilterRule *rule = NULL;
561 	gint index = -1, count = 0;
562 
563 	while ((rule = e_rule_context_next_rule (editor->context, rule, editor->source))) {
564 		if (rule == editor->current)
565 			index = count;
566 		count++;
567 	}
568 
569 	count--;
570 
571 	gtk_widget_set_sensitive (GTK_WIDGET (editor->priv->buttons[BUTTON_EDIT]), index != -1);
572 	gtk_widget_set_sensitive (GTK_WIDGET (editor->priv->buttons[BUTTON_DELETE]), index != -1);
573 	gtk_widget_set_sensitive (GTK_WIDGET (editor->priv->buttons[BUTTON_TOP]), index > 0);
574 	gtk_widget_set_sensitive (GTK_WIDGET (editor->priv->buttons[BUTTON_UP]), index > 0);
575 	gtk_widget_set_sensitive (GTK_WIDGET (editor->priv->buttons[BUTTON_DOWN]), index >= 0 && index < count);
576 	gtk_widget_set_sensitive (GTK_WIDGET (editor->priv->buttons[BUTTON_BOTTOM]), index >= 0 && index < count);
577 }
578 
579 static EFilterRule *
rule_editor_create_rule(ERuleEditor * editor)580 rule_editor_create_rule (ERuleEditor *editor)
581 {
582 	EFilterRule *rule;
583 	EFilterPart *part;
584 
585 	/* create a rule with 1 part in it */
586 	rule = e_filter_rule_new ();
587 	part = e_rule_context_next_part (editor->context, NULL);
588 	e_filter_rule_add_part (rule, e_filter_part_clone (part));
589 
590 	return rule;
591 }
592 
593 static void
e_rule_editor_class_init(ERuleEditorClass * class)594 e_rule_editor_class_init (ERuleEditorClass *class)
595 {
596 	GObjectClass *object_class;
597 
598 	g_type_class_add_private (class, sizeof (ERuleEditorPrivate));
599 
600 	object_class = G_OBJECT_CLASS (class);
601 	object_class->finalize = rule_editor_finalize;
602 	object_class->dispose = rule_editor_dispose;
603 
604 	class->set_source = rule_editor_set_source;
605 	class->set_sensitive = rule_editor_set_sensitive;
606 	class->create_rule = rule_editor_create_rule;
607 }
608 
609 static void
e_rule_editor_init(ERuleEditor * editor)610 e_rule_editor_init (ERuleEditor *editor)
611 {
612 	editor->priv = E_RULE_EDITOR_GET_PRIVATE (editor);
613 	editor->priv->drag_index = -1;
614 }
615 
616 /**
617  * rule_editor_new:
618  *
619  * Create a new ERuleEditor object.
620  *
621  * Return value: A new #ERuleEditor object.
622  **/
623 ERuleEditor *
e_rule_editor_new(ERuleContext * context,const gchar * source,const gchar * label)624 e_rule_editor_new (ERuleContext *context,
625                    const gchar *source,
626                    const gchar *label)
627 {
628 	ERuleEditor *editor = (ERuleEditor *) g_object_new (E_TYPE_RULE_EDITOR, NULL);
629 	GtkBuilder *builder;
630 
631 	builder = gtk_builder_new ();
632 	e_load_ui_builder_definition (builder, "filter.ui");
633 	e_rule_editor_construct (editor, context, builder, source, label);
634 	gtk_widget_hide (e_builder_get_widget (builder, "label17"));
635 	gtk_widget_hide (e_builder_get_widget (builder, "filter_source_combobox"));
636 	g_object_unref (builder);
637 
638 	return editor;
639 }
640 
641 void
e_rule_editor_set_sensitive(ERuleEditor * editor)642 e_rule_editor_set_sensitive (ERuleEditor *editor)
643 {
644 	ERuleEditorClass *class;
645 
646 	g_return_if_fail (E_IS_RULE_EDITOR (editor));
647 
648 	class = E_RULE_EDITOR_GET_CLASS (editor);
649 	g_return_if_fail (class != NULL);
650 	g_return_if_fail (class->set_sensitive != NULL);
651 
652 	class->set_sensitive (editor);
653 }
654 
655 void
e_rule_editor_set_source(ERuleEditor * editor,const gchar * source)656 e_rule_editor_set_source (ERuleEditor *editor,
657                           const gchar *source)
658 {
659 	ERuleEditorClass *class;
660 
661 	g_return_if_fail (E_IS_RULE_EDITOR (editor));
662 
663 	class = E_RULE_EDITOR_GET_CLASS (editor);
664 	g_return_if_fail (class != NULL);
665 	g_return_if_fail (class->set_source != NULL);
666 
667 	class->set_source (editor, source);
668 }
669 
670 EFilterRule *
e_rule_editor_create_rule(ERuleEditor * editor)671 e_rule_editor_create_rule (ERuleEditor *editor)
672 {
673 	ERuleEditorClass *class;
674 
675 	g_return_val_if_fail (E_IS_RULE_EDITOR (editor), NULL);
676 
677 	class = E_RULE_EDITOR_GET_CLASS (editor);
678 	g_return_val_if_fail (class != NULL, NULL);
679 	g_return_val_if_fail (class->create_rule != NULL, NULL);
680 
681 	return class->create_rule (editor);
682 }
683 
684 static void
double_click(GtkTreeView * treeview,GtkTreePath * path,GtkTreeViewColumn * column,ERuleEditor * editor)685 double_click (GtkTreeView *treeview,
686               GtkTreePath *path,
687               GtkTreeViewColumn *column,
688               ERuleEditor *editor)
689 {
690 	GtkTreeSelection *selection;
691 	GtkTreeModel *model;
692 	GtkTreeIter iter;
693 
694 	selection = gtk_tree_view_get_selection (editor->list);
695 	if (gtk_tree_selection_get_selected (selection, &model, &iter))
696 		gtk_tree_model_get (GTK_TREE_MODEL (editor->model), &iter, 1, &editor->current, -1);
697 
698 	if (editor->current)
699 		rule_edit ((GtkWidget *) treeview, editor);
700 }
701 
702 static void
rule_able_toggled(GtkCellRendererToggle * renderer,gchar * path_string,gpointer user_data)703 rule_able_toggled (GtkCellRendererToggle *renderer,
704                    gchar *path_string,
705                    gpointer user_data)
706 {
707 	GtkWidget *table = user_data;
708 	GtkTreeModel *model;
709 	GtkTreePath *path;
710 	GtkTreeIter iter;
711 
712 	path = gtk_tree_path_new_from_string (path_string);
713 	model = gtk_tree_view_get_model (GTK_TREE_VIEW (table));
714 
715 	if (gtk_tree_model_get_iter (model, &iter, path)) {
716 		EFilterRule *rule = NULL;
717 
718 		gtk_tree_model_get (model, &iter, 1, &rule, -1);
719 
720 		if (rule) {
721 			rule->enabled = !rule->enabled;
722 			gtk_list_store_set (GTK_LIST_STORE (model), &iter, 2, rule->enabled, -1);
723 		}
724 	}
725 
726 	gtk_tree_path_free (path);
727 }
728 
729 static void
editor_tree_drag_begin_cb(GtkWidget * widget,GdkDragContext * context,gpointer user_data)730 editor_tree_drag_begin_cb (GtkWidget *widget,
731 			   GdkDragContext *context,
732 			   gpointer user_data)
733 {
734 	ERuleEditor *editor = user_data;
735 	GtkTreeSelection *selection;
736 	cairo_surface_t *surface;
737 	GtkTreeModel *model;
738 	GtkTreePath *path;
739 	GtkTreeIter iter;
740 	EFilterRule *rule = NULL;
741 
742 	g_return_if_fail (editor != NULL);
743 
744 	selection = gtk_tree_view_get_selection (editor->list);
745 	if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
746 		editor->priv->drag_index = -1;
747 		return;
748 	}
749 
750 	gtk_tree_model_get (model, &iter, 1, &rule, -1);
751 	if (!rule) {
752 		editor->priv->drag_index = -1;
753 		return;
754 	}
755 
756 	editor->priv->drag_index = e_rule_context_get_rank_rule (editor->context, rule, editor->source);
757 
758 	path = gtk_tree_model_get_path (model, &iter);
759 
760 	surface = gtk_tree_view_create_row_drag_icon (editor->list, path);
761 	gtk_drag_set_icon_surface (context, surface);
762 
763 	gtk_tree_path_free (path);
764 }
765 
766 static gboolean
editor_tree_drag_drop_cb(GtkWidget * widget,GdkDragContext * context,gint x,gint y,guint time,gpointer user_data)767 editor_tree_drag_drop_cb (GtkWidget *widget,
768 			  GdkDragContext *context,
769 			  gint x,
770 			  gint y,
771 			  guint time,
772 			  gpointer user_data)
773 {
774 	ERuleEditor *editor = user_data;
775 
776 	g_return_val_if_fail (editor != NULL, FALSE);
777 
778 	editor->priv->drag_index = -1;
779 
780 	return TRUE;
781 }
782 
783 static void
editor_tree_drag_end_cb(GtkWidget * widget,GdkDragContext * context,gpointer user_data)784 editor_tree_drag_end_cb (GtkWidget *widget,
785 			 GdkDragContext *context,
786 			 gpointer user_data)
787 {
788 	ERuleEditor *editor = user_data;
789 
790 	g_return_if_fail (editor != NULL);
791 
792 	editor->priv->drag_index = -1;
793 }
794 
795 static gboolean
editor_tree_drag_motion_cb(GtkWidget * widget,GdkDragContext * context,gint x,gint y,guint time,gpointer user_data)796 editor_tree_drag_motion_cb (GtkWidget *widget,
797 			    GdkDragContext *context,
798 			    gint x,
799 			    gint y,
800 			    guint time,
801 			    gpointer user_data)
802 {
803 	ERuleEditor *editor = user_data;
804 	GtkTreeModel *model;
805 	GtkTreeIter iter;
806 	GtkTreePath *path = NULL;
807 	EFilterRule *rule = NULL;
808 
809 	g_return_val_if_fail (editor != NULL, FALSE);
810 
811 	if (editor->priv->drag_index == -1 ||
812 	    !gtk_tree_view_get_dest_row_at_pos (editor->list, x, y, &path, NULL))
813 		return FALSE;
814 
815 	model = gtk_tree_view_get_model (editor->list);
816 
817 	g_warn_if_fail (gtk_tree_model_get_iter (model, &iter, path));
818 
819 	gtk_tree_path_free (path);
820 
821 	gtk_tree_model_get (model, &iter, 1, &rule, -1);
822 
823 	if (rule) {
824 		gint drop_index;
825 
826 		drop_index = e_rule_context_get_rank_rule (editor->context, rule, editor->source);
827 
828 		if (drop_index != editor->priv->drag_index && drop_index >= 0) {
829 			editor->current = e_rule_context_find_rank_rule (editor->context, editor->priv->drag_index, editor->source);
830 			rule_move (editor, editor->priv->drag_index, drop_index);
831 
832 			editor->priv->drag_index = drop_index;
833 
834 			/* to update the editor->current */
835 			cursor_changed (NULL, editor);
836 		}
837 	}
838 
839 	gdk_drag_status (context, (!rule || editor->priv->drag_index == -1) ? 0 : GDK_ACTION_MOVE, time);
840 
841 	return TRUE;
842 }
843 
844 void
e_rule_editor_construct(ERuleEditor * editor,ERuleContext * context,GtkBuilder * builder,const gchar * source,const gchar * label)845 e_rule_editor_construct (ERuleEditor *editor,
846                          ERuleContext *context,
847                          GtkBuilder *builder,
848                          const gchar *source,
849                          const gchar *label)
850 {
851 	const GtkTargetEntry row_targets[] = {
852 		{ (gchar *) "ERuleEditorRow", GTK_TARGET_SAME_WIDGET, 0 }
853 	};
854 	GtkWidget *widget;
855 	GtkWidget *action_area;
856 	GtkWidget *content_area;
857 	GtkTreeViewColumn *column;
858 	GtkCellRenderer *renderer;
859 	GtkTreeSelection *selection;
860 	GObject *object;
861 	GList *list;
862 	gint i;
863 
864 	g_return_if_fail (E_IS_RULE_EDITOR (editor));
865 	g_return_if_fail (E_IS_RULE_CONTEXT (context));
866 	g_return_if_fail (GTK_IS_BUILDER (builder));
867 
868 	editor->context = g_object_ref (context);
869 
870 	action_area = gtk_dialog_get_action_area (GTK_DIALOG (editor));
871 	content_area = gtk_dialog_get_content_area (GTK_DIALOG (editor));
872 
873 	gtk_window_set_resizable ((GtkWindow *) editor, TRUE);
874 	gtk_window_set_default_size ((GtkWindow *) editor, 350, 400);
875 	gtk_widget_realize ((GtkWidget *) editor);
876 	gtk_container_set_border_width (GTK_CONTAINER (action_area), 12);
877 
878 	widget = e_builder_get_widget (builder, "rule_editor");
879 	gtk_box_pack_start (GTK_BOX (content_area), widget, TRUE, TRUE, 0);
880 
881 	for (i = 0; i < BUTTON_LAST; i++) {
882 		widget = e_builder_get_widget (builder, edit_buttons[i].name);
883 		editor->priv->buttons[i] = GTK_BUTTON (widget);
884 		g_signal_connect (
885 			widget, "clicked",
886 			G_CALLBACK (edit_buttons[i].func), editor);
887 	}
888 
889 	object = gtk_builder_get_object (builder, "rule_tree_view");
890 	editor->list = GTK_TREE_VIEW (object);
891 
892 	column = gtk_tree_view_get_column (GTK_TREE_VIEW (object), 0);
893 	g_return_if_fail (column != NULL);
894 
895 	gtk_tree_view_column_set_visible (column, FALSE);
896 	list = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (column));
897 	g_return_if_fail (list != NULL);
898 
899 	renderer = GTK_CELL_RENDERER (list->data);
900 	g_warn_if_fail (GTK_IS_CELL_RENDERER_TOGGLE (renderer));
901 
902 	g_list_free (list);
903 
904 	g_signal_connect (
905 		renderer, "toggled",
906 		G_CALLBACK (rule_able_toggled), editor->list);
907 
908 	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (object));
909 	gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
910 
911 	object = gtk_builder_get_object (builder, "rule_list_store");
912 	editor->model = GTK_LIST_STORE (object);
913 
914 	g_signal_connect (
915 		editor->list, "cursor-changed",
916 		G_CALLBACK (cursor_changed), editor);
917 	g_signal_connect (
918 		editor->list, "row-activated",
919 		G_CALLBACK (double_click), editor);
920 
921 	widget = e_builder_get_widget (builder, "rule_label");
922 	gtk_label_set_label (GTK_LABEL (widget), label);
923 	gtk_label_set_mnemonic_widget (
924 		GTK_LABEL (widget), GTK_WIDGET (editor->list));
925 
926 	rule_editor_set_source (editor, source);
927 
928 	gtk_dialog_add_buttons (
929 		GTK_DIALOG (editor),
930 		_("_Cancel"), GTK_RESPONSE_CANCEL,
931 		_("_OK"), GTK_RESPONSE_OK,
932 		NULL);
933 
934 	gtk_drag_source_set (GTK_WIDGET (editor->list), GDK_BUTTON1_MASK, row_targets, G_N_ELEMENTS (row_targets), GDK_ACTION_MOVE);
935 	gtk_drag_dest_set (GTK_WIDGET (editor->list), GTK_DEST_DEFAULT_MOTION, row_targets, G_N_ELEMENTS (row_targets), GDK_ACTION_MOVE);
936 
937 	g_signal_connect (editor->list, "drag-begin",
938 		G_CALLBACK (editor_tree_drag_begin_cb), editor);
939 	g_signal_connect (editor->list, "drag-drop",
940 		G_CALLBACK (editor_tree_drag_drop_cb), editor);
941 	g_signal_connect (editor->list, "drag-end",
942 		G_CALLBACK (editor_tree_drag_end_cb), editor);
943 	g_signal_connect (editor->list, "drag-motion",
944 		G_CALLBACK (editor_tree_drag_motion_cb), editor);
945 }
946