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.
36 #include <libgen.h> /* for dirname(3) */
39 #include "../include/gschem_patch_dockable.h"
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
,
69 static void tree_selection_changed (GtkTreeSelection
*selection
,
74 gschem_patch_dockable_get_type ()
76 static GType type
= 0;
79 static const GTypeInfo info
= {
80 sizeof (GschemPatchDockableClass
),
82 NULL
, /* base_finalize */
83 (GClassInitFunc
) class_init
,
84 NULL
, /* class_finalize */
85 NULL
, /* class_data */
86 sizeof (GschemPatchDockable
),
88 (GInstanceInitFunc
) instance_init
,
89 NULL
/* value_table */
92 type
= g_type_register_static (GSCHEM_TYPE_DOCKABLE
,
93 "GschemPatchDockable",
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
;
113 instance_init (GschemPatchDockable
*patch_dockable
)
115 patch_dockable
->store
= gtk_list_store_new (COLUMN_COUNT
,
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
);
139 create_widget (GschemDockable
*dockable
)
141 GschemPatchDockable
*patch_dockable
= GSCHEM_PATCH_DOCKABLE (dockable
);
143 GtkTreeViewColumn
*column
;
144 GtkCellRenderer
*renderer
;
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
),
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
);
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
);
216 /******************************************************************************/
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
)
228 size_t len
= strlen (page
->page_filename
);
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");
240 /*! \brief Let the user select a patch file, and import that patch file.
243 x_patch_import (GschemToplevel
*w_current
)
245 PAGE
*page
= gschem_toplevel_get_toplevel (w_current
)->page_current
;
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
,
257 gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog
),
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
);
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.
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
;
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
,
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
);
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
;
354 g_warning ("NULL page encountered");
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");
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
);
387 gschem_dockable_present (GSCHEM_DOCKABLE (patch_dockable
));
391 /*! \brief Place a result in the store so the user can see and select it.
394 add_hit_to_store (GschemPatchDockable
*patch_dockable
, gschem_patch_hit_t
*hit
)
396 static const char *UNKNOWN_FILE_NAME
= "N/A";
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
412 if (hit
->object
!= NULL
)
413 l
= o_attrib_return_attribs (hit
->object
);
418 gtk_list_store_append (patch_dockable
->store
, &tree_iter
);
419 gtk_list_store_set (patch_dockable
->store
,
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
,
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
)) {
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
);
456 s_object_weak_ref (final_object
, (NotifyFunc
) object_weakref_cb
,
459 gtk_list_store_append (patch_dockable
->store
, &tree_iter
);
461 if (basename
!= NULL
) {
462 gtk_list_store_set (patch_dockable
->store
,
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
,
472 gtk_list_store_set (patch_dockable
->store
,
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
,
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
492 clear_store (GschemPatchDockable
*patch_dockable
)
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
);
504 GValue value
= G_VALUE_INIT
;
506 gtk_tree_model_get_value (GTK_TREE_MODEL (patch_dockable
->store
),
511 if (G_VALUE_HOLDS_POINTER (&value
)) {
512 OBJECT
*object
= g_value_get_pointer (&value
);
515 s_object_weak_unref (object
, (NotifyFunc
) object_weakref_cb
,
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
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
);
550 g_warning ("NULL page encountered");
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
)) {
561 output_list
= g_slist_prepend (output_list
, page
);
562 g_hash_table_insert (visit_list
, page
, NULL
);
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
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
;
595 if (object
== NULL
) {
596 g_warning ("NULL object encountered");
600 if (object
->type
!= OBJ_COMPLEX
) {
604 attrib
= o_attrib_search_attached_attribs_by_name (object
,
608 if (attrib
== NULL
) {
609 attrib
= o_attrib_search_inherited_attribs_by_name (object
,
614 if (attrib
== NULL
) {
618 filenames
= g_strsplit (attrib
, ",", 0);
620 if (filenames
== NULL
) {
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
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
665 remove_object (GschemPatchDockable
*patch_dockable
, OBJECT
*object
)
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
);
678 GValue value
= G_VALUE_INIT
;
680 gtk_tree_model_get_value (GTK_TREE_MODEL (patch_dockable
->store
),
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
);
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
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
);
715 if (!gtk_tree_selection_get_selected (selection
, NULL
, &iter
))
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
);
726 OBJECT
*object
= g_value_get_pointer (&value
);
727 g_value_unset (&value
);
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
,
742 gschem_page_view_zoom_text (view
, object
, TRUE
);