2008-07-06 Johannes Schmid <jhs@gnome.org>
[anjuta-git-plugin.git] / plugins / valgrind / vgrule-list.c
blob987ae19e1f34a036426cd2676575a6ffcb7eb0af
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 <glib/gi18n.h>
37 #include <gconf/gconf-client.h>
38 #include <libanjuta/anjuta-utils.h>
40 #include "vgrule-list.h"
41 #include "vgmarshal.h"
44 typedef struct _RuleNode {
45 struct _RuleNode *next;
46 struct _RuleNode *prev;
47 VgRule *rule;
48 } RuleNode;
50 enum {
51 COL_STRING_NAME,
52 COL_POINTER_RULE,
53 COL_POINTER_RULE_NODE,
54 COL_LAST
57 static GType col_types[] = {
58 G_TYPE_STRING,
59 G_TYPE_POINTER,
60 G_TYPE_POINTER,
63 enum {
64 RULE_ADDED,
65 LAST_SIGNAL
68 static unsigned int signals[LAST_SIGNAL] = { 0 };
70 #define SUPPRESSIONS_KEY "/apps/anjuta/valgrind/general/suppressions"
73 static void vg_rule_list_class_init (VgRuleListClass *klass);
74 static void vg_rule_list_init (VgRuleList *list);
75 static void vg_rule_list_destroy (GtkObject *obj);
76 static void vg_rule_list_finalize (GObject *obj);
79 static GtkVBoxClass *parent_class = NULL;
82 GType
83 vg_rule_list_get_type (void)
85 static GType type = 0;
87 if (!type) {
88 static const GTypeInfo info = {
89 sizeof (VgRuleListClass),
90 NULL, /* base_class_init */
91 NULL, /* base_class_finalize */
92 (GClassInitFunc) vg_rule_list_class_init,
93 NULL, /* class_finalize */
94 NULL, /* class_data */
95 sizeof (VgRuleList),
96 0, /* n_preallocs */
97 (GInstanceInitFunc) vg_rule_list_init,
100 type = g_type_register_static (GTK_TYPE_VBOX, "VgRuleList", &info, 0);
103 return type;
106 static void
107 vg_rule_list_class_init (VgRuleListClass *klass)
109 GObjectClass *object_class = G_OBJECT_CLASS (klass);
110 GtkObjectClass *gtk_object_class = GTK_OBJECT_CLASS (klass);
112 parent_class = g_type_class_ref (GTK_TYPE_VBOX);
114 object_class->finalize = vg_rule_list_finalize;
115 gtk_object_class->destroy = vg_rule_list_destroy;
117 signals[RULE_ADDED]
118 = g_signal_new ("rule-added",
119 G_OBJECT_CLASS_TYPE (object_class),
120 G_SIGNAL_RUN_FIRST,
121 G_STRUCT_OFFSET (VgRuleListClass, rule_added),
122 NULL, NULL,
123 vg_marshal_NONE__POINTER,
124 G_TYPE_NONE, 1,
125 G_TYPE_POINTER);
129 static GtkWidget *
130 rule_editor_dialog_new (GtkWindow *parent, VgRule *rule)
132 GtkWidget *dialog, *editor;
134 /* FIXME: we should really get this title from somewhere else? */
135 dialog = gtk_dialog_new_with_buttons (_("Valgrind Suppression"), parent,
136 GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
137 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
138 GTK_STOCK_OK, GTK_RESPONSE_OK, NULL);
140 if (rule != NULL)
141 editor = vg_rule_editor_new_from_rule (rule);
142 else
143 editor = vg_rule_editor_new ();
145 gtk_container_set_border_width (GTK_CONTAINER (editor), 6);
146 gtk_widget_show (editor);
148 gtk_box_set_spacing (GTK_BOX (GTK_DIALOG (dialog)->vbox), 3);
149 gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox), editor, TRUE, TRUE, 0);
151 g_object_set_data (G_OBJECT (dialog), "editor", editor);
153 return dialog;
156 static void
157 add_response_cb (GtkDialog *dialog, int response, gpointer user_data)
159 VgRuleList *list = user_data;
160 GtkWidget *editor;
161 GtkTreeIter iter;
162 RuleNode *node;
163 VgRule *rule;
165 if (response == GTK_RESPONSE_OK) {
166 const char *name;
167 GtkWidget *msg;
169 editor = g_object_get_data (G_OBJECT (dialog), "editor");
171 name = vg_rule_editor_get_name (VG_RULE_EDITOR (editor));
172 if (!name || *name == '\0') {
173 msg = gtk_message_dialog_new (GTK_WINDOW (dialog), GTK_DIALOG_MODAL |
174 GTK_DIALOG_DESTROY_WITH_PARENT,
175 GTK_MESSAGE_WARNING,
176 GTK_BUTTONS_CLOSE,
177 _("You have forgotten to name your suppression rule."));
179 gtk_dialog_run (GTK_DIALOG (msg));
180 gtk_widget_destroy (msg);
181 return;
184 list->changed = TRUE;
186 rule = vg_rule_editor_get_rule (VG_RULE_EDITOR (editor));
188 node = g_new (RuleNode, 1);
189 node->rule = rule;
191 list_append_node (&list->rules, (ListNode *) node);
193 vg_rule_list_save (list);
195 gtk_list_store_append (GTK_LIST_STORE (list->model), &iter);
197 gtk_list_store_set (GTK_LIST_STORE (list->model), &iter,
198 COL_STRING_NAME, rule->name,
199 COL_POINTER_RULE, rule,
200 COL_POINTER_RULE_NODE, node, -1);
202 g_signal_emit (list, signals[RULE_ADDED], 0, rule);
205 gtk_widget_destroy (GTK_WIDGET (dialog));
208 static void
209 add_cb (GtkWidget *button, gpointer user_data)
211 VgRuleList *list = user_data;
212 GtkWidget *parent;
213 GtkWidget *dialog;
215 parent = gtk_widget_get_toplevel (GTK_WIDGET (list));
216 parent = GTK_WIDGET_TOPLEVEL (parent) ? parent : NULL;
218 dialog = rule_editor_dialog_new (GTK_WINDOW (parent), NULL);
219 g_signal_connect (dialog, "response", G_CALLBACK (add_response_cb), list);
221 gtk_widget_show (dialog);
224 static void
225 edit_response_cb (GtkDialog *dialog, int response, gpointer user_data)
227 VgRuleList *list = user_data;
228 GtkWidget *editor;
229 GtkTreePath *path;
230 GtkTreeIter iter;
231 RuleNode *node;
232 VgRule *rule;
234 if (response == GTK_RESPONSE_OK) {
235 const char *name;
236 GtkWidget *msg;
238 editor = g_object_get_data (G_OBJECT (dialog), "editor");
240 name = vg_rule_editor_get_name (VG_RULE_EDITOR (editor));
241 if (!name || *name == '\0') {
242 msg = gtk_message_dialog_new (GTK_WINDOW (dialog), GTK_DIALOG_MODAL |
243 GTK_DIALOG_DESTROY_WITH_PARENT,
244 GTK_MESSAGE_WARNING,
245 GTK_BUTTONS_CLOSE,
246 _("You have forgotten to name your suppression rule."));
248 gtk_dialog_run (GTK_DIALOG (msg));
249 gtk_widget_destroy (msg);
250 return;
253 list->changed = TRUE;
255 rule = vg_rule_editor_get_rule (VG_RULE_EDITOR (editor));
257 path = g_object_get_data (G_OBJECT (dialog), "path");
258 if (gtk_tree_model_get_iter (list->model, &iter, path)) {
259 /* replace the old rule node... */
260 gtk_tree_model_get (list->model, &iter, COL_POINTER_RULE_NODE, &node, -1);
261 vg_rule_free (node->rule);
262 node->rule = rule;
263 } else {
264 /* create a new rule node... */
265 node = g_new (RuleNode, 1);
266 node->rule = rule;
268 list_append_node (&list->rules, (ListNode *) node);
270 gtk_list_store_append (GTK_LIST_STORE (list->model), &iter);
273 gtk_list_store_set (GTK_LIST_STORE (list->model), &iter,
274 COL_STRING_NAME, rule->name,
275 COL_POINTER_RULE, rule,
276 COL_POINTER_RULE_NODE, node, -1);
278 /* FIXME: only emit if we've changed something that matters? */
279 g_signal_emit (list, signals[RULE_ADDED], 0, rule);
282 gtk_widget_destroy (GTK_WIDGET (dialog));
285 static void
286 edit_cb (GtkWidget *button, gpointer user_data)
288 VgRuleList *list = user_data;
289 GtkTreeSelection *selection;
290 GtkTreeModel *model = NULL;
291 VgRule *rule = NULL;
292 GtkWidget *parent;
293 GtkWidget *dialog;
294 GtkTreePath *path;
295 GtkTreeIter iter;
297 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (list->list));
298 if (gtk_tree_selection_get_selected (selection, &model, &iter))
299 gtk_tree_model_get (model, &iter, COL_POINTER_RULE, &rule, -1);
301 if (rule == NULL)
302 return;
304 path = gtk_tree_model_get_path (model, &iter);
306 parent = gtk_widget_get_toplevel (GTK_WIDGET (list));
307 parent = GTK_WIDGET_TOPLEVEL (parent) ? parent : NULL;
309 dialog = rule_editor_dialog_new (GTK_WINDOW (parent), rule);
310 g_signal_connect (dialog, "response", G_CALLBACK (edit_response_cb), list);
311 g_object_set_data_full (G_OBJECT (dialog), "path", path, (GDestroyNotify) gtk_tree_path_free);
313 gtk_widget_show (dialog);
316 static void
317 remove_cb (GtkWidget *button, gpointer user_data)
319 VgRuleList *list = user_data;
320 GtkTreeSelection *selection;
321 GtkTreeModel *model = NULL;
322 RuleNode *node = NULL;
323 VgRule *rule = NULL;
324 GtkTreeIter iter;
326 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (list->list));
327 if (gtk_tree_selection_get_selected (selection, &model, &iter))
328 gtk_tree_model_get (model, &iter, COL_POINTER_RULE, &rule, COL_POINTER_RULE_NODE, &node, -1);
330 if (rule == NULL)
331 return;
333 list->changed = TRUE;
335 gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
336 list_node_unlink ((ListNode *) node);
337 vg_rule_free (rule);
338 g_free (node);
341 static void
342 row_activate_cb (GtkTreeView *treeview, GtkTreePath *path, GtkTreeViewColumn *column, gpointer user_data)
344 /* double-clicked on a rule, pop the rule up in an editor */
345 edit_cb (NULL, user_data);
348 static void
349 selection_change_cb (GtkTreeSelection *selection, gpointer user_data)
351 VgRuleList *list = user_data;
352 GtkTreeModel *model;
353 GtkTreeIter iter;
354 int state;
356 state = gtk_tree_selection_get_selected (selection, &model, &iter);
357 if (state == 0)
358 gtk_widget_grab_focus (list->add);
360 gtk_widget_set_sensitive (list->edit, state);
361 gtk_widget_set_sensitive (list->remove, state);
364 static void
365 vg_rule_list_init (VgRuleList *list)
367 GtkTreeSelection *selection;
368 GtkCellRenderer *renderer;
369 GtkWidget *hbox, *vbox;
370 GtkWidget *scrolled;
372 hbox = gtk_hbox_new (FALSE, 6);
374 scrolled = gtk_scrolled_window_new (NULL, NULL);
375 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled),
376 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
377 gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled), GTK_SHADOW_IN);
379 list->model = GTK_TREE_MODEL (gtk_list_store_newv (COL_LAST, col_types));
380 list->list = gtk_tree_view_new_with_model (list->model);
382 renderer = gtk_cell_renderer_text_new ();
383 gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (list->list), -1, "",
384 renderer, "text", 0, NULL);
386 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (list->list));
387 gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
388 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (list->list), FALSE);
390 g_signal_connect (selection, "changed", G_CALLBACK (selection_change_cb), list);
391 g_signal_connect (list->list, "row-activated", G_CALLBACK (row_activate_cb), list);
393 gtk_widget_show (list->list);
394 gtk_container_add (GTK_CONTAINER (scrolled), list->list);
396 gtk_widget_show (scrolled);
397 gtk_box_pack_start (GTK_BOX (hbox), scrolled, TRUE, TRUE, 0);
399 vbox = gtk_vbox_new (FALSE, 3);
401 list->add = gtk_button_new_from_stock (GTK_STOCK_ADD);
402 g_signal_connect (list->add, "clicked", G_CALLBACK (add_cb), list);
403 gtk_widget_show (list->add);
404 gtk_box_pack_start (GTK_BOX (vbox), list->add, FALSE, FALSE, 0);
406 list->edit = gtk_button_new_with_mnemonic (_("_Edit"));
407 gtk_widget_set_sensitive (list->edit, FALSE);
408 g_signal_connect (list->edit, "clicked", G_CALLBACK (edit_cb), list);
409 gtk_widget_show (list->edit);
410 gtk_box_pack_start (GTK_BOX (vbox), list->edit, FALSE, FALSE, 0);
412 list->remove = gtk_button_new_from_stock (GTK_STOCK_REMOVE);
413 gtk_widget_set_sensitive (list->remove, FALSE);
414 g_signal_connect (list->remove, "clicked", G_CALLBACK (remove_cb), list);
415 gtk_widget_show (list->remove);
416 gtk_box_pack_start (GTK_BOX (vbox), list->remove, FALSE, FALSE, 0);
418 gtk_widget_show (vbox);
419 gtk_box_pack_start (GTK_BOX (hbox), vbox, FALSE, FALSE, 0);
421 gtk_widget_show (hbox);
422 gtk_container_add (GTK_CONTAINER (list), hbox);
424 /* init our data */
425 list_init (&list->rules);
426 list->changed = FALSE;
427 list->filename = NULL;
428 list->parser = NULL;
429 list->gio = NULL;
430 list->show_id = 0;
431 list->load_id = 0;
434 static void
435 vg_rule_list_finalize (GObject *obj)
437 VgRuleList *list = (VgRuleList *) obj;
438 RuleNode *n, *nn;
440 g_free (list->filename);
442 vg_rule_parser_free (list->parser);
444 n = (RuleNode *) list->rules.head;
445 while (n->next != NULL) {
446 nn = n->next;
447 vg_rule_free (n->rule);
448 g_free (n);
449 n = nn;
452 G_OBJECT_CLASS (parent_class)->finalize (obj);
455 static void
456 vg_rule_list_destroy (GtkObject *obj)
458 VgRuleList *list = (VgRuleList *) obj;
460 if (list->gio != NULL) {
461 g_io_channel_close (list->gio);
462 g_io_channel_unref (list->gio);
463 list->load_id = 0;
464 list->gio = NULL;
467 GTK_OBJECT_CLASS (parent_class)->destroy (obj);
471 static void
472 load_rule_cb (VgRuleParser *parser, VgRule *rule, gpointer user_data)
474 VgRuleList *list = user_data;
475 GtkTreeIter iter;
476 RuleNode *node;
478 node = g_new (RuleNode, 1);
479 node->rule = rule;
481 list_append_node (&list->rules, (ListNode *) node);
483 gtk_list_store_append (GTK_LIST_STORE (list->model), &iter);
484 gtk_list_store_set (GTK_LIST_STORE (list->model), &iter,
485 COL_STRING_NAME, rule->name,
486 COL_POINTER_RULE, rule,
487 COL_POINTER_RULE_NODE, node, -1);
490 static gboolean
491 load_rules_step_cb (GIOChannel *gio, GIOCondition condition, gpointer user_data)
493 VgRuleList *list = user_data;
495 if ((condition & G_IO_IN) && vg_rule_parser_step (list->parser) <= 0)
496 goto disconnect;
498 if (condition & G_IO_HUP)
499 goto disconnect;
501 return TRUE;
503 disconnect:
505 vg_rule_parser_free (list->parser);
506 list->parser = NULL;
508 g_io_channel_close (list->gio);
509 g_io_channel_unref (list->gio);
510 list->load_id = 0;
511 list->gio = NULL;
513 return FALSE;
516 static void
517 load_rules (GtkWidget *widget, gpointer user_data)
519 VgRuleList *list = user_data;
520 int fd;
522 list->changed = FALSE;
524 if (list->show_id != 0) {
525 g_signal_handler_disconnect (list, list->show_id);
526 list->show_id = 0;
529 if (list->filename == NULL)
530 return;
532 if ((fd = open (list->filename, O_RDONLY)) == -1)
533 return;
535 list->parser = vg_rule_parser_new (fd, load_rule_cb, list);
536 list->gio = g_io_channel_unix_new (fd);
537 list->load_id = g_io_add_watch (list->gio, G_IO_IN | G_IO_HUP, load_rules_step_cb, list);
541 GtkWidget *
542 vg_rule_list_new (const char *filename)
544 VgRuleList *list;
546 list = g_object_new (VG_TYPE_RULE_LIST, NULL);
547 list->filename = g_strdup (filename);
549 /* suppressions file may be large, so don't actually load it
550 until the user shows it for the first time */
551 list->show_id = g_signal_connect (list, "map", G_CALLBACK (load_rules), list);
553 return GTK_WIDGET (list);
557 void
558 vg_rule_list_set_filename (VgRuleList *list, const char *filename)
560 RuleNode *n, *nn;
562 g_free (list->filename);
563 list->filename = g_strdup (filename);
565 if (list->show_id != 0) {
566 /* good, the user hasn't shown the dialog yet.
567 * this means that we don't have to do anything special!
569 } else {
570 if (list->load_id != 0) {
571 /* this means the file is currently being loaded. ugh */
572 vg_rule_parser_free (list->parser);
573 g_io_channel_close (list->gio);
574 g_io_channel_unref (list->gio);
575 list->load_id = 0;
576 list->gio = NULL;
579 n = (RuleNode *) list->rules.head;
580 while (n->next != NULL) {
581 nn = n->next;
582 vg_rule_free (n->rule);
583 g_free (n);
584 n = nn;
587 gtk_list_store_clear (GTK_LIST_STORE (list->model));
589 if (!GTK_WIDGET_MAPPED (list))
590 list->show_id = g_signal_connect (list, "map", G_CALLBACK (load_rules), list);
591 else
592 load_rules (GTK_WIDGET (list), list);
598 vg_rule_list_save (VgRuleList *list)
600 GtkWidget *parent, *msg;
601 char *filename = NULL;
602 const char *basename;
603 RuleNode *node;
604 int fd = -1;
606 /* only save if we need to... */
607 if (!list->changed)
608 return 0;
610 if (list->filename == NULL)
611 goto exception;
613 if (!(basename = strrchr (list->filename, '/')))
614 basename = list->filename;
615 else
616 basename++;
618 filename = g_strdup_printf ("%.*s.#%s", (basename - list->filename),
619 list->filename, basename);
621 if ((fd = open (filename, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, 0666)) == -1)
622 goto exception;
624 if (vg_suppressions_file_write_header (fd, "This Valgrind suppresion file was generated by Alleyoop") == -1)
625 goto exception;
627 node = (RuleNode *) list->rules.head;
628 while (node->next != NULL) {
629 if (vg_suppressions_file_append_rule (fd, node->rule) == -1)
630 goto exception;
631 node = node->next;
634 close (fd);
635 fd = -1;
637 if (rename (filename, list->filename) == -1)
638 goto exception;
640 g_free (filename);
642 return 0;
644 exception:
646 parent = gtk_widget_get_toplevel (GTK_WIDGET (list));
647 parent = GTK_WIDGET_TOPLEVEL (parent) ? parent : NULL;
649 msg = gtk_message_dialog_new (GTK_WINDOW (parent), GTK_DIALOG_MODAL,
650 GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
651 _("Cannot save suppression rules: %s"),
652 list->filename ? g_strerror (errno) :
653 _("You have not set a suppressions file in your settings."));
655 g_signal_connect_swapped (msg, "response", G_CALLBACK (gtk_widget_destroy), msg);
657 gtk_widget_show (msg);
659 if (fd != -1)
660 close (fd);
662 if (filename) {
663 unlink (filename);
664 g_free (filename);
667 return -1;
671 void
672 vg_rule_list_add_rule (VgRuleList *list, const char *title, GtkWindow *parent, VgErrorSummary *summary)
674 GtkWidget *dialog, *editor;
675 GConfClient *gconf;
677 dialog = gtk_dialog_new_with_buttons (title, parent, GTK_DIALOG_MODAL |
678 GTK_DIALOG_DESTROY_WITH_PARENT,
679 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
680 GTK_STOCK_OK, GTK_RESPONSE_OK, NULL);
682 if (summary != NULL)
683 editor = vg_rule_editor_new_from_summary (summary);
684 else
685 editor = vg_rule_editor_new ();
687 gtk_widget_show (editor);
688 gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox), editor, TRUE, TRUE, 0);
689 g_signal_connect (dialog, "response", G_CALLBACK (add_response_cb), list);
690 g_object_set_data (G_OBJECT (dialog), "editor", editor);
692 if (list->filename == NULL) {
693 gconf = gconf_client_get_default ();
695 // FIXME: hardcoded path
696 list->filename = anjuta_util_get_user_config_file_path ("valgrind.supp", NULL);
697 gconf_client_set_string (gconf, SUPPRESSIONS_KEY, list->filename, NULL);
698 g_object_unref (gconf);
701 /* FIXME: what if the rules haven't finished loading before the user adds this rule? */
703 gtk_widget_show (dialog);