gschem: patch results: Add function x_patch_guess_filename
[geda-gaf.git] / gschem / src / gschem_patch_dockable.c
blob7ac7ffdd7761cd31d6834f04e7152bd289724a9c
1 /* gEDA - GPL Electronic Design Automation
2 * gschem - gEDA Schematic Capture
3 * Copyright (C) 1998-2010 Ales Hvezda
4 * Copyright (C) 1998-2013 gEDA Contributors (see ChangeLog for details)
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21 /*! \file gschem_patch_dockable.c
22 * \brief List diffs resulting from a patch.
25 #include <config.h>
27 #include <stdio.h>
28 #ifdef HAVE_STDLIB_H
29 #include <stdlib.h>
30 #endif
31 #ifdef HAVE_STRING_H
32 #include <string.h>
33 #endif
35 #include <sys/stat.h>
36 #include <libgen.h> /* for dirname(3) */
38 #include "gschem.h"
39 #include "../include/gschem_patch_dockable.h"
41 enum {
42 COLUMN_FILENAME,
43 COLUMN_LOCATION,
44 COLUMN_ACTION,
45 COLUMN_HAS_ERROR,
46 COLUMN_OBJECT,
47 COLUMN_COUNT
50 typedef void (*NotifyFunc) (void *, void *);
53 static gpointer parent_class = NULL;
55 static void class_init (GschemPatchDockableClass *class);
56 static void instance_init (GschemPatchDockable *patch_dockable);
57 static void dispose (GObject *object);
58 static GtkWidget *create_widget (GschemDockable *dockable);
60 static void add_hit_to_store (GschemPatchDockable *patch_dockable,
61 gschem_patch_hit_t *hit);
62 static void clear_store (GschemPatchDockable *patch_dockable);
63 static GSList *get_pages (GList *pages, gboolean descend);
64 static GList *get_subpages (PAGE *page);
65 static void object_weakref_cb (OBJECT *object,
66 GschemPatchDockable *patch_dockable);
67 static void remove_object (GschemPatchDockable *patch_dockable,
68 OBJECT *object);
69 static void tree_selection_changed (GtkTreeSelection *selection,
70 gpointer user_data);
73 GType
74 gschem_patch_dockable_get_type ()
76 static GType type = 0;
78 if (type == 0) {
79 static const GTypeInfo info = {
80 sizeof (GschemPatchDockableClass),
81 NULL, /* base_init */
82 NULL, /* base_finalize */
83 (GClassInitFunc) class_init,
84 NULL, /* class_finalize */
85 NULL, /* class_data */
86 sizeof (GschemPatchDockable),
87 0, /* n_preallocs */
88 (GInstanceInitFunc) instance_init,
89 NULL /* value_table */
92 type = g_type_register_static (GSCHEM_TYPE_DOCKABLE,
93 "GschemPatchDockable",
94 &info, 0);
97 return type;
101 static void
102 class_init (GschemPatchDockableClass *class)
104 parent_class = g_type_class_peek_parent (class);
106 GSCHEM_DOCKABLE_CLASS (class)->create_widget = create_widget;
108 G_OBJECT_CLASS (class)->dispose = dispose;
112 static void
113 instance_init (GschemPatchDockable *patch_dockable)
115 patch_dockable->store = gtk_list_store_new (COLUMN_COUNT,
116 G_TYPE_STRING,
117 G_TYPE_STRING,
118 G_TYPE_STRING,
119 G_TYPE_BOOLEAN,
120 G_TYPE_POINTER);
124 static void
125 dispose (GObject *object)
127 GschemPatchDockable *patch_dockable = GSCHEM_PATCH_DOCKABLE (object);
129 if (patch_dockable->store) {
130 clear_store (patch_dockable);
131 g_clear_object (&patch_dockable->store);
134 G_OBJECT_CLASS (parent_class)->dispose (object);
138 static GtkWidget *
139 create_widget (GschemDockable *dockable)
141 GschemPatchDockable *patch_dockable = GSCHEM_PATCH_DOCKABLE (dockable);
143 GtkTreeViewColumn *column;
144 GtkCellRenderer *renderer;
145 GtkWidget *scrolled;
146 GtkTreeSelection *selection;
147 GtkWidget *tree_widget;
149 scrolled = gtk_scrolled_window_new (NULL, NULL);
151 tree_widget = gtk_tree_view_new_with_model (
152 GTK_TREE_MODEL (patch_dockable->store));
153 gtk_tree_view_set_search_column (GTK_TREE_VIEW (tree_widget),
154 COLUMN_LOCATION);
155 gtk_container_add (GTK_CONTAINER (scrolled), tree_widget);
157 /* filename column */
159 column = gtk_tree_view_column_new ();
160 gtk_tree_view_column_set_resizable (column, TRUE);
161 gtk_tree_view_column_set_title (column, _("Filename"));
163 gtk_tree_view_append_column (GTK_TREE_VIEW (tree_widget), column);
165 renderer = gtk_cell_renderer_text_new ();
166 gtk_tree_view_column_pack_start (column, renderer, TRUE);
167 gtk_tree_view_column_add_attribute (
168 column, renderer, "text", COLUMN_FILENAME);
169 gtk_tree_view_column_add_attribute (
170 column, renderer, "foreground-set", COLUMN_HAS_ERROR);
171 g_object_set (renderer, "foreground", "red", NULL);
173 /* location column */
175 column = gtk_tree_view_column_new ();
176 gtk_tree_view_column_set_resizable (column, TRUE);
177 gtk_tree_view_column_set_title (column, _("Comp/Pin"));
179 gtk_tree_view_append_column (GTK_TREE_VIEW (tree_widget), column);
181 renderer = gtk_cell_renderer_text_new ();
182 gtk_tree_view_column_pack_start (column, renderer, TRUE);
183 gtk_tree_view_column_add_attribute (
184 column, renderer, "text", COLUMN_LOCATION);
185 gtk_tree_view_column_add_attribute (
186 column, renderer, "foreground-set", COLUMN_HAS_ERROR);
187 g_object_set (renderer, "foreground", "red", NULL);
189 /* action column */
191 column = gtk_tree_view_column_new ();
192 gtk_tree_view_column_set_resizable (column, TRUE);
193 gtk_tree_view_column_set_title (column, _("Required action"));
195 gtk_tree_view_append_column (GTK_TREE_VIEW (tree_widget), column);
197 renderer = gtk_cell_renderer_text_new ();
198 gtk_tree_view_column_pack_start (column, renderer, TRUE);
199 gtk_tree_view_column_add_attribute (
200 column, renderer, "text", COLUMN_ACTION);
201 gtk_tree_view_column_add_attribute (
202 column, renderer, "foreground-set", COLUMN_HAS_ERROR);
203 g_object_set (renderer, "foreground", "red", NULL);
205 /* attach signal to detect user selection */
207 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree_widget));
208 g_signal_connect (selection, "changed",
209 G_CALLBACK (tree_selection_changed), patch_dockable);
211 gtk_widget_show_all (scrolled);
212 return scrolled;
216 /******************************************************************************/
219 gchar *
220 x_patch_guess_filename (PAGE *page)
222 if (page->patch_filename != NULL)
223 return g_strdup (page->patch_filename);
225 if (page->is_untitled)
226 return NULL;
228 size_t len = strlen (page->page_filename);
229 if (len < 4 ||
230 (g_ascii_strcasecmp (page->page_filename + len - 4, ".sch") != 0 &&
231 g_ascii_strcasecmp (page->page_filename + len - 4, ".sym") != 0))
232 return g_strdup_printf ("%s.bap", page->page_filename);
234 gchar *fn = g_strdup (page->page_filename);
235 strcpy (fn + len - 4, ".bap");
236 return fn;
240 /*! \brief Let the user select a patch file, and import that patch file.
242 void
243 x_patch_import (GschemToplevel *w_current)
245 PAGE *page = gschem_toplevel_get_toplevel (w_current)->page_current;
246 GtkWidget *dialog;
247 gchar *patch_filename;
248 GtkWidget *extra_widget;
249 GtkFileFilter *filter;
251 dialog = gtk_file_chooser_dialog_new (_("Import Patch..."),
252 GTK_WINDOW (w_current->main_window),
253 GTK_FILE_CHOOSER_ACTION_OPEN,
254 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
255 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
256 NULL);
257 gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog),
258 GTK_RESPONSE_ACCEPT,
259 GTK_RESPONSE_CANCEL, -1);
261 patch_filename = x_patch_guess_filename (page);
263 if (patch_filename != NULL &&
264 g_file_test (patch_filename, G_FILE_TEST_EXISTS |
265 G_FILE_TEST_IS_REGULAR))
266 gtk_file_chooser_set_filename (GTK_FILE_CHOOSER (dialog), patch_filename);
267 else
268 /* dirname(3) modifies its argument, but that's fine here */
269 gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog),
270 dirname (patch_filename));
272 g_free (patch_filename);
274 extra_widget = gtk_check_button_new_with_label (_("Descend into hierarchy"));
275 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (extra_widget),
276 page->patch_descend);
277 gtk_file_chooser_set_extra_widget (GTK_FILE_CHOOSER (dialog), extra_widget);
279 filter = gtk_file_filter_new ();
280 gtk_file_filter_set_name (filter, _("Back-annotation patches"));
281 gtk_file_filter_add_pattern (filter, "*.bap");
282 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
284 filter = gtk_file_filter_new ();
285 gtk_file_filter_set_name (filter, _("All files"));
286 gtk_file_filter_add_pattern (filter, "*");
287 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
289 if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT) {
290 g_free (page->patch_filename);
291 page->patch_filename =
292 gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
293 page->patch_descend =
294 gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (extra_widget));
296 x_patch_do_import (w_current, page);
299 gtk_widget_destroy (dialog);
303 /*! \brief Find all objects that have an outstanding patch mismatch.
305 * Uses the page's current patch filename and descent flag.
307 * The results are placed in the dockable's GtkListStore.
309 void
310 x_patch_do_import (GschemToplevel *w_current, PAGE *page)
312 GschemPatchDockable *patch_dockable =
313 GSCHEM_PATCH_DOCKABLE (w_current->patch_dockable);
315 GList *pages = geda_list_get_glist (w_current->toplevel->pages);
316 gschem_patch_state_t st;
317 struct stat buf;
318 GSList *all_pages, *objects;
320 g_return_if_fail (patch_dockable != NULL);
321 g_return_if_fail (patch_dockable->store != NULL);
323 g_return_if_fail (page->patch_filename != NULL);
325 if (gschem_patch_state_init (&st, page->patch_filename) != 0) {
326 g_warning ("Unable to open patch file %s\n", page->patch_filename);
328 GtkWidget *dialog = gtk_message_dialog_new (
329 GTK_WINDOW (w_current->main_window),
330 GTK_DIALOG_DESTROY_WITH_PARENT,
331 GTK_MESSAGE_ERROR,
332 GTK_BUTTONS_CLOSE,
333 _("Failed to load patch file \"%s\".\n"
334 "See the standard error output for more information."),
335 page->patch_filename);
336 gtk_window_set_title (GTK_WINDOW (dialog), _("Failed to import patch"));
337 gtk_dialog_run (GTK_DIALOG (dialog));
338 gtk_widget_destroy (dialog);
339 return;
342 if (stat (page->patch_filename, &buf) != -1) {
343 page->patch_seen_on_disk = TRUE;
344 page->patch_mtime = buf.st_mtim;
347 all_pages = get_pages (pages, page->patch_descend);
349 for (GSList *page_iter = all_pages;
350 page_iter != NULL; page_iter = page_iter->next) {
351 PAGE *page = (PAGE *) page_iter->data;
353 if (page == NULL) {
354 g_warning ("NULL page encountered");
355 continue;
358 for (const GList *object_iter = s_page_objects (page);
359 object_iter != NULL; object_iter = object_iter->next) {
360 OBJECT *object = (OBJECT *) object_iter->data;
362 if (object == NULL) {
363 g_warning ("NULL object encountered");
364 continue;
367 gschem_patch_state_build (&st, object);
371 g_slist_free (all_pages);
373 objects = gschem_patch_state_execute (&st);
374 gschem_patch_state_destroy (&st);
376 clear_store (patch_dockable);
378 for (GSList *object_iter = objects;
379 object_iter != NULL; object_iter = object_iter->next) {
380 gschem_patch_hit_t *hit = (gschem_patch_hit_t *) object_iter->data;
381 add_hit_to_store (patch_dockable, hit);
384 gschem_patch_free_hit_list (objects);
386 if (objects != NULL)
387 gschem_dockable_present (GSCHEM_DOCKABLE (patch_dockable));
391 /*! \brief Place a result in the store so the user can see and select it.
393 static void
394 add_hit_to_store (GschemPatchDockable *patch_dockable, gschem_patch_hit_t *hit)
396 static const char *UNKNOWN_FILE_NAME = "N/A";
398 char *basename;
399 OBJECT *final_object = NULL;
400 GtkTreeIter tree_iter;
402 /* TODO: this is an ugly workaround: can't put pins or objects
403 directly on the list because they have no object page; use
404 their complex object's first visible text instead
405 Fix: be able to jump to any OBJECT
408 OBJECT *page_obj;
409 GList *l;
410 int found_pin;
412 if (hit->object != NULL)
413 l = o_attrib_return_attribs (hit->object);
414 else
415 l = NULL;
417 if (l == NULL) {
418 gtk_list_store_append (patch_dockable->store, &tree_iter);
419 gtk_list_store_set (patch_dockable->store,
420 &tree_iter,
421 COLUMN_FILENAME, UNKNOWN_FILE_NAME,
422 COLUMN_LOCATION, hit->loc_name,
423 COLUMN_ACTION, hit->action,
424 COLUMN_HAS_ERROR, hit->object == NULL,
425 COLUMN_OBJECT, final_object,
426 -1);
427 return;
430 found_pin = 0;
431 for (GList *i = l; i != NULL; i = i->next) {
432 final_object = i->data;
433 if (final_object->type == OBJ_TEXT) {
434 page_obj = gschem_page_get_page_object (final_object);
435 if (o_is_visible (page_obj)) {
436 found_pin = 1;
437 break;
441 g_list_free (l);
442 if (!found_pin) {
443 g_warning ("no pin text to zoom to");
444 page_obj = final_object = NULL;
447 if (final_object == NULL) {
448 g_warning ("no text attrib?");
449 page_obj = final_object = NULL;
451 if (page_obj != NULL && !page_obj->page->is_untitled)
452 basename = g_path_get_basename (page_obj->page->page_filename);
453 else
454 basename = NULL;
456 s_object_weak_ref (final_object, (NotifyFunc) object_weakref_cb,
457 patch_dockable);
459 gtk_list_store_append (patch_dockable->store, &tree_iter);
461 if (basename != NULL) {
462 gtk_list_store_set (patch_dockable->store,
463 &tree_iter,
464 COLUMN_FILENAME, basename,
465 COLUMN_LOCATION, hit->loc_name,
466 COLUMN_ACTION, hit->action,
467 COLUMN_HAS_ERROR, hit->object == NULL,
468 COLUMN_OBJECT, final_object,
469 -1);
470 g_free (basename);
471 } else {
472 gtk_list_store_set (patch_dockable->store,
473 &tree_iter,
474 COLUMN_FILENAME, UNKNOWN_FILE_NAME,
475 COLUMN_LOCATION, hit->loc_name,
476 COLUMN_ACTION, hit->action,
477 COLUMN_HAS_ERROR, hit->object == NULL,
478 COLUMN_OBJECT, final_object,
479 -1);
484 /*! \brief delete all items from the list store
486 * This function deletes all items in the list store and removes all the weak
487 * references to the objects.
489 * \param [in] patch_dockable
491 static void
492 clear_store (GschemPatchDockable *patch_dockable)
494 GtkTreeIter iter;
495 gboolean valid;
497 g_return_if_fail (patch_dockable != NULL);
498 g_return_if_fail (patch_dockable->store != NULL);
500 valid = gtk_tree_model_get_iter_first (
501 GTK_TREE_MODEL (patch_dockable->store), &iter);
503 while (valid) {
504 GValue value = G_VALUE_INIT;
506 gtk_tree_model_get_value (GTK_TREE_MODEL (patch_dockable->store),
507 &iter,
508 COLUMN_OBJECT,
509 &value);
511 if (G_VALUE_HOLDS_POINTER (&value)) {
512 OBJECT *object = g_value_get_pointer (&value);
514 if (object != NULL)
515 s_object_weak_unref (object, (NotifyFunc) object_weakref_cb,
516 patch_dockable);
519 g_value_unset (&value);
521 valid = gtk_tree_model_iter_next (
522 GTK_TREE_MODEL (patch_dockable->store), &iter);
525 gtk_list_store_clear (patch_dockable->store);
529 /*! \brief obtain a list of pages for an operation
531 * Descends the heirarchy of pages, if selected, and removes duplicate pages.
533 * \param [in] pages the list of pages to begin search
534 * \param [in] descend alose locates subpages
535 * \return a list of all the pages
537 static GSList*
538 get_pages (GList *pages, gboolean descend)
540 GList *input_list = g_list_copy (pages);
541 GSList *output_list = NULL;
542 GHashTable *visit_list = g_hash_table_new (NULL, NULL);
544 while (input_list != NULL) {
545 PAGE *page = (PAGE*) input_list->data;
547 input_list = g_list_delete_link (input_list, input_list);
549 if (page == NULL) {
550 g_warning ("NULL page encountered");
551 continue;
554 /** \todo the following function becomes available in glib 2.32 */
555 /* if (g_hash_table_contains (visit_list, page)) { */
557 if (g_hash_table_lookup_extended (visit_list, page, NULL, NULL)) {
558 continue;
561 output_list = g_slist_prepend (output_list, page);
562 g_hash_table_insert (visit_list, page, NULL);
564 if (descend) {
565 input_list = g_list_concat (input_list, get_subpages (page));
569 g_hash_table_destroy (visit_list);
571 return g_slist_reverse (output_list);
575 /*! \brief get the subpages of a schematic page
577 * if any subpages are not loaded, this function will load them.
579 * \param [in] page the parent page
580 * \return a list of all the subpages
582 static GList*
583 get_subpages (PAGE *page)
585 GList *page_list = NULL;
587 g_return_val_if_fail (page != NULL, NULL);
589 for (const GList *object_iter = s_page_objects (page);
590 object_iter != NULL; object_iter = object_iter->next) {
591 OBJECT *object = (OBJECT *) object_iter->data;
592 char *attrib;
593 char **filenames;
595 if (object == NULL) {
596 g_warning ("NULL object encountered");
597 continue;
600 if (object->type != OBJ_COMPLEX) {
601 continue;
604 attrib = o_attrib_search_attached_attribs_by_name (object,
605 "source",
608 if (attrib == NULL) {
609 attrib = o_attrib_search_inherited_attribs_by_name (object,
610 "source",
614 if (attrib == NULL) {
615 continue;
618 filenames = g_strsplit (attrib, ",", 0);
620 if (filenames == NULL) {
621 continue;
624 for (char **iter = filenames; *iter != NULL; iter++) {
625 PAGE *subpage = s_hierarchy_load_subpage (page, *iter, NULL);
627 if (subpage != NULL) {
628 page_list = g_list_prepend (page_list, subpage);
632 g_strfreev (filenames);
635 return g_list_reverse (page_list);
639 /*! \brief callback for an object that has been destroyed
641 * \param [in] object the object that has been destroyed
642 * \param [in] patch_dockable
644 static void
645 object_weakref_cb (OBJECT *object, GschemPatchDockable *patch_dockable)
647 g_return_if_fail (patch_dockable != NULL);
649 remove_object (patch_dockable, object);
653 /*! \brief remove an object from the store
655 * This function gets called in response to the object deletion. And, doesn't
656 * dereference the object.
658 * This function doesn't remove the weak reference, under the assumption that
659 * the object is being destroyed.
661 * \param [in] patch_dockable
662 * \param [in] object the object to remove from the store
664 static void
665 remove_object (GschemPatchDockable *patch_dockable, OBJECT *object)
667 GtkTreeIter iter;
668 gboolean valid;
670 g_return_if_fail (object != NULL);
671 g_return_if_fail (patch_dockable != NULL);
672 g_return_if_fail (patch_dockable->store != NULL);
674 valid = gtk_tree_model_get_iter_first (
675 GTK_TREE_MODEL (patch_dockable->store), &iter);
677 while (valid) {
678 GValue value = G_VALUE_INIT;
680 gtk_tree_model_get_value (GTK_TREE_MODEL (patch_dockable->store),
681 &iter,
682 COLUMN_OBJECT,
683 &value);
685 if (G_VALUE_HOLDS_POINTER (&value)) {
686 OBJECT *other = g_value_get_pointer (&value);
688 if (object == other) {
689 g_value_unset (&value);
690 valid = gtk_list_store_remove (patch_dockable->store, &iter);
691 continue;
695 g_value_unset (&value);
696 valid = gtk_tree_model_iter_next (
697 GTK_TREE_MODEL (patch_dockable->store), &iter);
702 /*! \brief Callback for tree selection "changed" signal.
704 * Switches to the page (if applicable) and zooms to the location of
705 * the mismatch.
707 static void
708 tree_selection_changed (GtkTreeSelection *selection, gpointer user_data)
710 GschemPatchDockable *patch_dockable = GSCHEM_PATCH_DOCKABLE (user_data);
711 g_return_if_fail (selection != NULL);
712 g_return_if_fail (patch_dockable != NULL);
714 GtkTreeIter iter;
715 if (!gtk_tree_selection_get_selected (selection, NULL, &iter))
716 return;
718 GValue value = G_VALUE_INIT;
719 gtk_tree_model_get_value (GTK_TREE_MODEL (patch_dockable->store), &iter,
720 COLUMN_OBJECT, &value);
721 if (!G_VALUE_HOLDS_POINTER (&value)) {
722 g_value_unset (&value);
723 return;
726 OBJECT *object = g_value_get_pointer (&value);
727 g_value_unset (&value);
728 if (object == NULL)
729 return;
731 OBJECT *page_obj = gschem_page_get_page_object (object);
732 g_return_if_fail (page_obj != NULL);
735 GschemPageView *view = gschem_toplevel_get_current_page_view (
736 GSCHEM_DOCKABLE (patch_dockable)->w_current);
737 g_return_if_fail (view != NULL);
739 x_window_set_current_page (GSCHEM_DOCKABLE (patch_dockable)->w_current,
740 page_obj->page);
742 gschem_page_view_zoom_text (view, object, TRUE);