2008-04-12 Johannes Schmid <jhs@gnome.org>
[anjuta-git-plugin.git] / plugins / valgrind / vgrule-list.c
blobc450a49894b365cfe9538ce36edec0209d6eb65a
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3 * Authors: Jeffrey Stedfast <fejj@ximian.com>
5 * Copyright 2003 Ximian, Inc. (www.ximian.com)
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
24 #ifdef HAVE_CONFIG_H
25 #include <config.h>
26 #endif
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <sys/types.h>
32 #include <unistd.h>
33 #include <fcntl.h>
34 #include <errno.h>
36 #include <libgnome/gnome-i18n.h>
37 #include <gconf/gconf-client.h>
39 #include "vgrule-list.h"
40 #include "vgmarshal.h"
43 typedef struct _RuleNode {
44 struct _RuleNode *next;
45 struct _RuleNode *prev;
46 VgRule *rule;
47 } RuleNode;
49 enum {
50 COL_STRING_NAME,
51 COL_POINTER_RULE,
52 COL_POINTER_RULE_NODE,
53 COL_LAST
56 static GType col_types[] = {
57 G_TYPE_STRING,
58 G_TYPE_POINTER,
59 G_TYPE_POINTER,
62 enum {
63 RULE_ADDED,
64 LAST_SIGNAL
67 static unsigned int signals[LAST_SIGNAL] = { 0 };
69 #define SUPPRESSIONS_KEY "/apps/anjuta/valgrind/general/suppressions"
72 static void vg_rule_list_class_init (VgRuleListClass *klass);
73 static void vg_rule_list_init (VgRuleList *list);
74 static void vg_rule_list_destroy (GtkObject *obj);
75 static void vg_rule_list_finalize (GObject *obj);
78 static GtkVBoxClass *parent_class = NULL;
81 GType
82 vg_rule_list_get_type (void)
84 static GType type = 0;
86 if (!type) {
87 static const GTypeInfo info = {
88 sizeof (VgRuleListClass),
89 NULL, /* base_class_init */
90 NULL, /* base_class_finalize */
91 (GClassInitFunc) vg_rule_list_class_init,
92 NULL, /* class_finalize */
93 NULL, /* class_data */
94 sizeof (VgRuleList),
95 0, /* n_preallocs */
96 (GInstanceInitFunc) vg_rule_list_init,
99 type = g_type_register_static (GTK_TYPE_VBOX, "VgRuleList", &info, 0);
102 return type;
105 static void
106 vg_rule_list_class_init (VgRuleListClass *klass)
108 GObjectClass *object_class = G_OBJECT_CLASS (klass);
109 GtkObjectClass *gtk_object_class = GTK_OBJECT_CLASS (klass);
111 parent_class = g_type_class_ref (GTK_TYPE_VBOX);
113 object_class->finalize = vg_rule_list_finalize;
114 gtk_object_class->destroy = vg_rule_list_destroy;
116 signals[RULE_ADDED]
117 = g_signal_new ("rule-added",
118 G_OBJECT_CLASS_TYPE (object_class),
119 G_SIGNAL_RUN_FIRST,
120 G_STRUCT_OFFSET (VgRuleListClass, rule_added),
121 NULL, NULL,
122 vg_marshal_NONE__POINTER,
123 G_TYPE_NONE, 1,
124 G_TYPE_POINTER);
128 static GtkWidget *
129 rule_editor_dialog_new (GtkWindow *parent, VgRule *rule)
131 GtkWidget *dialog, *editor;
133 /* FIXME: we should really get this title from somewhere else? */
134 dialog = gtk_dialog_new_with_buttons (_("Valgrind Suppression"), parent,
135 GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
136 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
137 GTK_STOCK_OK, GTK_RESPONSE_OK, NULL);
139 if (rule != NULL)
140 editor = vg_rule_editor_new_from_rule (rule);
141 else
142 editor = vg_rule_editor_new ();
144 gtk_container_set_border_width (GTK_CONTAINER (editor), 6);
145 gtk_widget_show (editor);
147 gtk_box_set_spacing (GTK_BOX (GTK_DIALOG (dialog)->vbox), 3);
148 gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox), editor, TRUE, TRUE, 0);
150 g_object_set_data (G_OBJECT (dialog), "editor", editor);
152 return dialog;
155 static void
156 add_response_cb (GtkDialog *dialog, int response, gpointer user_data)
158 VgRuleList *list = user_data;
159 GtkWidget *editor;
160 GtkTreeIter iter;
161 RuleNode *node;
162 VgRule *rule;
164 if (response == GTK_RESPONSE_OK) {
165 const char *name;
166 GtkWidget *msg;
168 editor = g_object_get_data (G_OBJECT (dialog), "editor");
170 name = vg_rule_editor_get_name (VG_RULE_EDITOR (editor));
171 if (!name || *name == '\0') {
172 msg = gtk_message_dialog_new (GTK_WINDOW (dialog), GTK_DIALOG_MODAL |
173 GTK_DIALOG_DESTROY_WITH_PARENT,
174 GTK_MESSAGE_WARNING,
175 GTK_BUTTONS_CLOSE,
176 _("You have forgotten to name your suppression rule."));
178 gtk_dialog_run (GTK_DIALOG (msg));
179 gtk_widget_destroy (msg);
180 return;
183 list->changed = TRUE;
185 rule = vg_rule_editor_get_rule (VG_RULE_EDITOR (editor));
187 node = g_new (RuleNode, 1);
188 node->rule = rule;
190 list_append_node (&list->rules, (ListNode *) node);
192 vg_rule_list_save (list);
194 gtk_list_store_append (GTK_LIST_STORE (list->model), &iter);
196 gtk_list_store_set (GTK_LIST_STORE (list->model), &iter,
197 COL_STRING_NAME, rule->name,
198 COL_POINTER_RULE, rule,
199 COL_POINTER_RULE_NODE, node, -1);
201 g_signal_emit (list, signals[RULE_ADDED], 0, rule);
204 gtk_widget_destroy (GTK_WIDGET (dialog));
207 static void
208 add_cb (GtkWidget *button, gpointer user_data)
210 VgRuleList *list = user_data;
211 GtkWidget *parent;
212 GtkWidget *dialog;
214 parent = gtk_widget_get_toplevel (GTK_WIDGET (list));
215 parent = GTK_WIDGET_TOPLEVEL (parent) ? parent : NULL;
217 dialog = rule_editor_dialog_new (GTK_WINDOW (parent), NULL);
218 g_signal_connect (dialog, "response", G_CALLBACK (add_response_cb), list);
220 gtk_widget_show (dialog);
223 static void
224 edit_response_cb (GtkDialog *dialog, int response, gpointer user_data)
226 VgRuleList *list = user_data;
227 GtkWidget *editor;
228 GtkTreePath *path;
229 GtkTreeIter iter;
230 RuleNode *node;
231 VgRule *rule;
233 if (response == GTK_RESPONSE_OK) {
234 const char *name;
235 GtkWidget *msg;
237 editor = g_object_get_data (G_OBJECT (dialog), "editor");
239 name = vg_rule_editor_get_name (VG_RULE_EDITOR (editor));
240 if (!name || *name == '\0') {
241 msg = gtk_message_dialog_new (GTK_WINDOW (dialog), GTK_DIALOG_MODAL |
242 GTK_DIALOG_DESTROY_WITH_PARENT,
243 GTK_MESSAGE_WARNING,
244 GTK_BUTTONS_CLOSE,
245 _("You have forgotten to name your suppression rule."));
247 gtk_dialog_run (GTK_DIALOG (msg));
248 gtk_widget_destroy (msg);
249 return;
252 list->changed = TRUE;
254 rule = vg_rule_editor_get_rule (VG_RULE_EDITOR (editor));
256 path = g_object_get_data (G_OBJECT (dialog), "path");
257 if (gtk_tree_model_get_iter (list->model, &iter, path)) {
258 /* replace the old rule node... */
259 gtk_tree_model_get (list->model, &iter, COL_POINTER_RULE_NODE, &node, -1);
260 vg_rule_free (node->rule);
261 node->rule = rule;
262 } else {
263 /* create a new rule node... */
264 node = g_new (RuleNode, 1);
265 node->rule = rule;
267 list_append_node (&list->rules, (ListNode *) node);
269 gtk_list_store_append (GTK_LIST_STORE (list->model), &iter);
272 gtk_list_store_set (GTK_LIST_STORE (list->model), &iter,
273 COL_STRING_NAME, rule->name,
274 COL_POINTER_RULE, rule,
275 COL_POINTER_RULE_NODE, node, -1);
277 /* FIXME: only emit if we've changed something that matters? */
278 g_signal_emit (list, signals[RULE_ADDED], 0, rule);
281 gtk_widget_destroy (GTK_WIDGET (dialog));
284 static void
285 edit_cb (GtkWidget *button, gpointer user_data)
287 VgRuleList *list = user_data;
288 GtkTreeSelection *selection;
289 GtkTreeModel *model = NULL;
290 VgRule *rule = NULL;
291 GtkWidget *parent;
292 GtkWidget *dialog;
293 GtkTreePath *path;
294 GtkTreeIter iter;
296 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (list->list));
297 if (gtk_tree_selection_get_selected (selection, &model, &iter))
298 gtk_tree_model_get (model, &iter, COL_POINTER_RULE, &rule, -1);
300 if (rule == NULL)
301 return;
303 path = gtk_tree_model_get_path (model, &iter);
305 parent = gtk_widget_get_toplevel (GTK_WIDGET (list));
306 parent = GTK_WIDGET_TOPLEVEL (parent) ? parent : NULL;
308 dialog = rule_editor_dialog_new (GTK_WINDOW (parent), rule);
309 g_signal_connect (dialog, "response", G_CALLBACK (edit_response_cb), list);
310 g_object_set_data_full (G_OBJECT (dialog), "path", path, (GDestroyNotify) gtk_tree_path_free);
312 gtk_widget_show (dialog);
315 static void
316 remove_cb (GtkWidget *button, gpointer user_data)
318 VgRuleList *list = user_data;
319 GtkTreeSelection *selection;
320 GtkTreeModel *model = NULL;
321 RuleNode *node = NULL;
322 VgRule *rule = NULL;
323 GtkTreeIter iter;
325 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (list->list));
326 if (gtk_tree_selection_get_selected (selection, &model, &iter))
327 gtk_tree_model_get (model, &iter, COL_POINTER_RULE, &rule, COL_POINTER_RULE_NODE, &node, -1);
329 if (rule == NULL)
330 return;
332 list->changed = TRUE;
334 gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
335 list_node_unlink ((ListNode *) node);
336 vg_rule_free (rule);
337 g_free (node);
340 static void
341 row_activate_cb (GtkTreeView *treeview, GtkTreePath *path, GtkTreeViewColumn *column, gpointer user_data)
343 /* double-clicked on a rule, pop the rule up in an editor */
344 edit_cb (NULL, user_data);
347 static void
348 selection_change_cb (GtkTreeSelection *selection, gpointer user_data)
350 VgRuleList *list = user_data;
351 GtkTreeModel *model;
352 GtkTreeIter iter;
353 int state;
355 state = gtk_tree_selection_get_selected (selection, &model, &iter);
356 if (state == 0)
357 gtk_widget_grab_focus (list->add);
359 gtk_widget_set_sensitive (list->edit, state);
360 gtk_widget_set_sensitive (list->remove, state);
363 static void
364 vg_rule_list_init (VgRuleList *list)
366 GtkTreeSelection *selection;
367 GtkCellRenderer *renderer;
368 GtkWidget *hbox, *vbox;
369 GtkWidget *scrolled;
371 hbox = gtk_hbox_new (FALSE, 6);
373 scrolled = gtk_scrolled_window_new (NULL, NULL);
374 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled),
375 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
376 gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled), GTK_SHADOW_IN);
378 list->model = GTK_TREE_MODEL (gtk_list_store_newv (COL_LAST, col_types));
379 list->list = gtk_tree_view_new_with_model (list->model);
381 renderer = gtk_cell_renderer_text_new ();
382 gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (list->list), -1, "",
383 renderer, "text", 0, NULL);
385 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (list->list));
386 gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
387 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (list->list), FALSE);
389 g_signal_connect (selection, "changed", G_CALLBACK (selection_change_cb), list);
390 g_signal_connect (list->list, "row-activated", G_CALLBACK (row_activate_cb), list);
392 gtk_widget_show (list->list);
393 gtk_container_add (GTK_CONTAINER (scrolled), list->list);
395 gtk_widget_show (scrolled);
396 gtk_box_pack_start (GTK_BOX (hbox), scrolled, TRUE, TRUE, 0);
398 vbox = gtk_vbox_new (FALSE, 3);
400 list->add = gtk_button_new_from_stock (GTK_STOCK_ADD);
401 g_signal_connect (list->add, "clicked", G_CALLBACK (add_cb), list);
402 gtk_widget_show (list->add);
403 gtk_box_pack_start (GTK_BOX (vbox), list->add, FALSE, FALSE, 0);
405 list->edit = gtk_button_new_with_mnemonic (_("_Edit"));
406 gtk_widget_set_sensitive (list->edit, FALSE);
407 g_signal_connect (list->edit, "clicked", G_CALLBACK (edit_cb), list);
408 gtk_widget_show (list->edit);
409 gtk_box_pack_start (GTK_BOX (vbox), list->edit, FALSE, FALSE, 0);
411 list->remove = gtk_button_new_from_stock (GTK_STOCK_REMOVE);
412 gtk_widget_set_sensitive (list->remove, FALSE);
413 g_signal_connect (list->remove, "clicked", G_CALLBACK (remove_cb), list);
414 gtk_widget_show (list->remove);
415 gtk_box_pack_start (GTK_BOX (vbox), list->remove, FALSE, FALSE, 0);
417 gtk_widget_show (vbox);
418 gtk_box_pack_start (GTK_BOX (hbox), vbox, FALSE, FALSE, 0);
420 gtk_widget_show (hbox);
421 gtk_container_add (GTK_CONTAINER (list), hbox);
423 /* init our data */
424 list_init (&list->rules);
425 list->changed = FALSE;
426 list->filename = NULL;
427 list->parser = NULL;
428 list->gio = NULL;
429 list->show_id = 0;
430 list->load_id = 0;
433 static void
434 vg_rule_list_finalize (GObject *obj)
436 VgRuleList *list = (VgRuleList *) obj;
437 RuleNode *n, *nn;
439 g_free (list->filename);
441 vg_rule_parser_free (list->parser);
443 n = (RuleNode *) list->rules.head;
444 while (n->next != NULL) {
445 nn = n->next;
446 vg_rule_free (n->rule);
447 g_free (n);
448 n = nn;
451 G_OBJECT_CLASS (parent_class)->finalize (obj);
454 static void
455 vg_rule_list_destroy (GtkObject *obj)
457 VgRuleList *list = (VgRuleList *) obj;
459 if (list->gio != NULL) {
460 g_io_channel_close (list->gio);
461 g_io_channel_unref (list->gio);
462 list->load_id = 0;
463 list->gio = NULL;
466 GTK_OBJECT_CLASS (parent_class)->destroy (obj);
470 static void
471 load_rule_cb (VgRuleParser *parser, VgRule *rule, gpointer user_data)
473 VgRuleList *list = user_data;
474 GtkTreeIter iter;
475 RuleNode *node;
477 node = g_new (RuleNode, 1);
478 node->rule = rule;
480 list_append_node (&list->rules, (ListNode *) node);
482 gtk_list_store_append (GTK_LIST_STORE (list->model), &iter);
483 gtk_list_store_set (GTK_LIST_STORE (list->model), &iter,
484 COL_STRING_NAME, rule->name,
485 COL_POINTER_RULE, rule,
486 COL_POINTER_RULE_NODE, node, -1);
489 static gboolean
490 load_rules_step_cb (GIOChannel *gio, GIOCondition condition, gpointer user_data)
492 VgRuleList *list = user_data;
494 if ((condition & G_IO_IN) && vg_rule_parser_step (list->parser) <= 0)
495 goto disconnect;
497 if (condition & G_IO_HUP)
498 goto disconnect;
500 return TRUE;
502 disconnect:
504 vg_rule_parser_free (list->parser);
505 list->parser = NULL;
507 g_io_channel_close (list->gio);
508 g_io_channel_unref (list->gio);
509 list->load_id = 0;
510 list->gio = NULL;
512 return FALSE;
515 static void
516 load_rules (GtkWidget *widget, gpointer user_data)
518 VgRuleList *list = user_data;
519 int fd;
521 list->changed = FALSE;
523 if (list->show_id != 0) {
524 g_signal_handler_disconnect (list, list->show_id);
525 list->show_id = 0;
528 if (list->filename == NULL)
529 return;
531 if ((fd = open (list->filename, O_RDONLY)) == -1)
532 return;
534 list->parser = vg_rule_parser_new (fd, load_rule_cb, list);
535 list->gio = g_io_channel_unix_new (fd);
536 list->load_id = g_io_add_watch (list->gio, G_IO_IN | G_IO_HUP, load_rules_step_cb, list);
540 GtkWidget *
541 vg_rule_list_new (const char *filename)
543 VgRuleList *list;
545 list = g_object_new (VG_TYPE_RULE_LIST, NULL);
546 list->filename = g_strdup (filename);
548 /* suppressions file may be large, so don't actually load it
549 until the user shows it for the first time */
550 list->show_id = g_signal_connect (list, "map", G_CALLBACK (load_rules), list);
552 return GTK_WIDGET (list);
556 void
557 vg_rule_list_set_filename (VgRuleList *list, const char *filename)
559 RuleNode *n, *nn;
561 g_free (list->filename);
562 list->filename = g_strdup (filename);
564 if (list->show_id != 0) {
565 /* good, the user hasn't shown the dialog yet.
566 * this means that we don't have to do anything special!
568 } else {
569 if (list->load_id != 0) {
570 /* this means the file is currently being loaded. ugh */
571 vg_rule_parser_free (list->parser);
572 g_io_channel_close (list->gio);
573 g_io_channel_unref (list->gio);
574 list->load_id = 0;
575 list->gio = NULL;
578 n = (RuleNode *) list->rules.head;
579 while (n->next != NULL) {
580 nn = n->next;
581 vg_rule_free (n->rule);
582 g_free (n);
583 n = nn;
586 gtk_list_store_clear (GTK_LIST_STORE (list->model));
588 if (!GTK_WIDGET_MAPPED (list))
589 list->show_id = g_signal_connect (list, "map", G_CALLBACK (load_rules), list);
590 else
591 load_rules (GTK_WIDGET (list), list);
597 vg_rule_list_save (VgRuleList *list)
599 GtkWidget *parent, *msg;
600 char *filename = NULL;
601 const char *basename;
602 RuleNode *node;
603 int fd = -1;
605 /* only save if we need to... */
606 if (!list->changed)
607 return 0;
609 if (list->filename == NULL)
610 goto exception;
612 if (!(basename = strrchr (list->filename, '/')))
613 basename = list->filename;
614 else
615 basename++;
617 filename = g_strdup_printf ("%.*s.#%s", (basename - list->filename),
618 list->filename, basename);
620 if ((fd = open (filename, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, 0666)) == -1)
621 goto exception;
623 if (vg_suppressions_file_write_header (fd, "This Valgrind suppresion file was generated by Alleyoop") == -1)
624 goto exception;
626 node = (RuleNode *) list->rules.head;
627 while (node->next != NULL) {
628 if (vg_suppressions_file_append_rule (fd, node->rule) == -1)
629 goto exception;
630 node = node->next;
633 close (fd);
634 fd = -1;
636 if (rename (filename, list->filename) == -1)
637 goto exception;
639 g_free (filename);
641 return 0;
643 exception:
645 parent = gtk_widget_get_toplevel (GTK_WIDGET (list));
646 parent = GTK_WIDGET_TOPLEVEL (parent) ? parent : NULL;
648 msg = gtk_message_dialog_new (GTK_WINDOW (parent), GTK_DIALOG_MODAL,
649 GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
650 _("Cannot save suppression rules: %s"),
651 list->filename ? g_strerror (errno) :
652 _("You have not set a suppressions file in your settings."));
654 g_signal_connect_swapped (msg, "response", G_CALLBACK (gtk_widget_destroy), msg);
656 gtk_widget_show (msg);
658 if (fd != -1)
659 close (fd);
661 if (filename) {
662 unlink (filename);
663 g_free (filename);
666 return -1;
670 void
671 vg_rule_list_add_rule (VgRuleList *list, const char *title, GtkWindow *parent, VgErrorSummary *summary)
673 GtkWidget *dialog, *editor;
674 GConfClient *gconf;
676 dialog = gtk_dialog_new_with_buttons (title, parent, GTK_DIALOG_MODAL |
677 GTK_DIALOG_DESTROY_WITH_PARENT,
678 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
679 GTK_STOCK_OK, GTK_RESPONSE_OK, NULL);
681 if (summary != NULL)
682 editor = vg_rule_editor_new_from_summary (summary);
683 else
684 editor = vg_rule_editor_new ();
686 gtk_widget_show (editor);
687 gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox), editor, TRUE, TRUE, 0);
688 g_signal_connect (dialog, "response", G_CALLBACK (add_response_cb), list);
689 g_object_set_data (G_OBJECT (dialog), "editor", editor);
691 if (list->filename == NULL) {
692 gconf = gconf_client_get_default ();
694 // FIXME: hardcoded path
695 list->filename = g_strdup_printf ("%s/.anjuta/valgrind.supp", getenv ("HOME"));
696 gconf_client_set_string (gconf, SUPPRESSIONS_KEY, list->filename, NULL);
697 g_object_unref (gconf);
700 /* FIXME: what if the rules haven't finished loading before the user adds this rule? */
702 gtk_widget_show (dialog);