1 /* gEDA - GPL Electronic Design Automation
2  * libgeda - gEDA's library
3  * Copyright (C) 1998-2010 Ales Hvezda
4  * Copyright (C) 1998-2010 gEDA Contributors (see ChangeLog for details)
5  *
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.
10  *
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.
15  *
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
19  */
20 
21 /*! \file o_attrib.c
22  *  \brief utility functions for attributes
23  *
24  *  Attributes are normal text objects. An attribute is a text object
25  *  that has a text string that is delimited by an equal "=" character.
26  *  The part before the equal character is called <b>name</b> the
27  *  part of the string behind the equal character is called <b>value</b>
28  *
29  *  Attributes are attached to OBJECTs (st_object). Each attribute has
30  *  a reference to the object it is attached to. Each object that has
31  *  attributes has a list of pionters to its attributes.
32  *
33  *  \image html o_attrib_overview.png
34  *  \image latex o_attrib_overview.pdf "attribute overview" width=14cm
35  *
36  *  \note
37  *  Be sure in o_copy o_move o_delete you maintain the attributes
38  *  delete is a bare, because you will have to unattach the other end
39  *  and in o_save o_read as well
40  *  and in o_select when selecting objects, select the attributes
41  */
42 
43 #include <config.h>
44 #include <missing.h>
45 
46 #include <stdio.h>
47 #ifdef HAVE_STRING_H
48 #include <string.h>
49 #endif
50 #include <math.h>
51 
52 #include "libgeda_priv.h"
53 
54 #ifdef HAVE_LIBDMALLOC
55 #include <dmalloc.h>
56 #endif
57 
58 
59 /*! \brief Add an attribute to an existing attribute list.
60  *  \par Function Description
61  *  Add an attribute to an existing attribute list.
62  *
63  *  \param [in]  toplevel   The TOPLEVEL object.
64  *  \param [in]  object     The OBJECT we're adding the attribute to.
65  *  \param [in]  item       The item you want to add as an attribute.
66  *  \return nothing.
67  */
o_attrib_add(TOPLEVEL * toplevel,OBJECT * object,OBJECT * item)68 void o_attrib_add(TOPLEVEL *toplevel, OBJECT *object, OBJECT *item)
69 {
70   /* Add link from item to attrib listing */
71   item->attached_to = object;
72   object->attribs = g_list_append (object->attribs, item);
73 
74   o_attrib_emit_attribs_changed (toplevel, object);
75 }
76 
77 
78 /*! \brief Check whether a attrib is attached to another object
79  *  \par Function Description
80  *  This function checks whether the object \a attrib is attached to
81  *  the \a object.
82  *
83  *  \param [in]  toplevel   The TOPLEVEL object.
84  *  \param [in]  attrib     The attribute to be checket.
85  *  \param [in]  object     The object where you want to add item as an attribute.
86  *  \return TRUE if attrib is an attribute of object, FALSE otherwise
87  */
o_attrib_is_attached(TOPLEVEL * toplevel,OBJECT * attrib,OBJECT * object)88 gboolean o_attrib_is_attached (TOPLEVEL *toplevel,
89                                OBJECT *attrib, OBJECT *object)
90 {
91   if (attrib == NULL || object == NULL)
92     return FALSE;
93 
94   if (attrib->attached_to == object)
95     return TRUE;
96 
97   return FALSE;
98 }
99 
100 
101 /*! \brief Attach existing attribute to an object.
102  *  \par Function Description
103  *  Attach existing attribute to an object.
104  *
105  *  \param [in]  toplevel     The TOPLEVEL object.
106  *  \param [in]  attrib       The attribute to be added.
107  *  \param [out] object       The object where you want to add item as an attribute.
108  *  \param [in]  set_color    Whether or not we should set the new attribute's color.
109  */
o_attrib_attach(TOPLEVEL * toplevel,OBJECT * attrib,OBJECT * object,int set_color)110 void o_attrib_attach (TOPLEVEL *toplevel, OBJECT *attrib, OBJECT *object,
111                       int set_color)
112 {
113   g_return_if_fail (attrib != NULL);
114   g_return_if_fail (object != NULL);
115 
116   /* is the object already part of the list ? */
117   if (g_list_find (object->attribs, attrib)) {
118     g_warning ("Attribute [%s] already attached\n", attrib->text->string);
119     return;
120   }
121 
122   if (attrib->type != OBJ_TEXT) {
123     g_warning (_("Attempt to attach non text item as an attribute!\n"));
124     return;
125   }
126 
127   if (attrib->attached_to != NULL) {
128     g_warning (_("Attempt to attach attribute [%s] to more than one object\n"),
129                 attrib->text->string);
130     return;
131   }
132 
133   o_attrib_add (toplevel, object, attrib);
134 
135   if (set_color)
136     o_set_color (toplevel, attrib, ATTRIBUTE_COLOR);
137 }
138 
139 
140 /*! \brief Attach list of existing attributes to an object.
141  *  \par Function Description
142  *  Attach list of existing attributes to an object.
143  *
144  *  \param [in]  toplevel   The TOPLEVEL object.
145  *  \param [in]  attr_list  The list of attributes to be added.
146  *  \param [out] object     The object where you want to add item as an attribute.
147  *  \param [in]  set_color    Whether or not we should set the new attribute's color.
148  */
o_attrib_attach_list(TOPLEVEL * toplevel,GList * attr_list,OBJECT * object,int set_color)149 void o_attrib_attach_list (TOPLEVEL *toplevel,
150                            GList *attr_list, OBJECT *object, int set_color)
151 {
152   GList *iter;
153 
154   for (iter = attr_list; iter != NULL; iter = g_list_next (iter))
155     o_attrib_attach (toplevel, iter->data, object, set_color);
156 }
157 
158 
159 /*! \brief Detach all attribute items in a list.
160  *  \par Function Description
161  *  Detach all attributes from an object.
162  *
163  *  \param [in]     toplevel  The TOPLEVEL object.
164  *  \param [in,out] object    The object whos attributes to detach.
165  */
o_attrib_detach_all(TOPLEVEL * toplevel,OBJECT * object)166 void o_attrib_detach_all(TOPLEVEL *toplevel, OBJECT *object)
167 {
168   OBJECT *a_current;
169   GList *a_iter;
170 
171   o_attrib_freeze_hooks (toplevel, object);
172 
173   for (a_iter = object->attribs; a_iter != NULL;
174        a_iter = g_list_next (a_iter)) {
175     a_current = a_iter->data;
176 
177     a_current->attached_to = NULL;
178     o_set_color (toplevel, a_current, DETACHED_ATTRIBUTE_COLOR);
179     o_attrib_emit_attribs_changed (toplevel, object);
180   }
181 
182   g_list_free (object->attribs);
183   object->attribs = NULL;
184 
185   o_attrib_thaw_hooks (toplevel, object);
186 }
187 
188 /*! \brief Print all attributes to a Postscript document.
189  *  \par Function Description
190  *  Print all attributes to a Postscript document.
191  *
192  *  \param [in] attributes  List of attributes to print.
193  */
o_attrib_print(GList * attributes)194 void o_attrib_print(GList *attributes)
195 {
196   OBJECT *a_current;
197   GList *a_iter;
198 
199   a_iter = attributes;
200 
201   while (a_iter != NULL) {
202     a_current = a_iter->data;
203     printf("Attribute points to: %s\n", a_current->name);
204     if (a_current->text) {
205       printf("\tText is: %s\n", a_current->text->string);
206     }
207 
208     a_iter = g_list_next (a_iter);
209   }
210 }
211 
212 /*! \todo Finish function.
213  *  \brief Remove an attribute item from an attribute list.
214  *  \par Function Description
215  *  This function removes the given attribute from an attribute list.
216  *  This function should be used when detaching an attribute.
217  *
218  *  \param [in] toplevel  The TOPLEVEL object.
219  *  \param [in] list      The attribute list to remove attribute from.
220  *  \param [in] remove    The OBJECT to remove from list.
221  */
o_attrib_remove(TOPLEVEL * toplevel,GList ** list,OBJECT * remove)222 void o_attrib_remove(TOPLEVEL *toplevel, GList **list, OBJECT *remove)
223 {
224   OBJECT *attached_to;
225 
226   g_return_if_fail (remove != NULL);
227 
228   attached_to = remove->attached_to;
229   remove->attached_to = NULL;
230 
231   *list = g_list_remove (*list, remove);
232 
233   o_attrib_emit_attribs_changed (toplevel, attached_to);
234 }
235 
236 /*! \brief Read attributes from a buffer.
237  *  \par Function Description
238  *  Read attributes from a TextBuffer.
239  *
240  *  \param [in]  toplevel               The TOPLEVEL object.
241  *  \param [in]  object_to_get_attribs  Object which gets these attribs.
242  *  \param [in]  tb                     The text buffer to read from.
243  *  \param [in]  release_ver            libgeda release version number.
244  *  \param [in]  fileformat_ver         file format version number.
245  *  \return GList of attributes read, or NULL on error.
246  */
o_read_attribs(TOPLEVEL * toplevel,OBJECT * object_to_get_attribs,TextBuffer * tb,unsigned int release_ver,unsigned int fileformat_ver,GError ** err)247 GList *o_read_attribs (TOPLEVEL *toplevel,
248                        OBJECT *object_to_get_attribs,
249                        TextBuffer *tb,
250                        unsigned int release_ver, unsigned int fileformat_ver, GError ** err)
251 {
252   GList *object_list = NULL;
253   OBJECT *new_obj;
254   const char *line = NULL;
255   char objtype;
256   int ATTACH=FALSE;
257 
258   while (1) {
259 
260     line = s_textbuffer_next_line (tb);
261     if (line == NULL) break;
262 
263     sscanf(line, "%c", &objtype);
264     switch (objtype) {
265 
266       case(OBJ_LINE):
267         if ((new_obj = o_line_read (toplevel, line, release_ver, fileformat_ver, err)) == NULL)
268           goto error;
269         object_list = g_list_prepend (object_list, new_obj);
270         break;
271 
272 
273       case(OBJ_NET):
274         if ((new_obj = o_net_read (toplevel, line, release_ver, fileformat_ver, err)) == NULL)
275           goto error;
276         object_list = g_list_prepend (object_list, new_obj);
277         break;
278 
279       case(OBJ_BUS):
280         if ((new_obj = o_bus_read (toplevel, line, release_ver, fileformat_ver, err)) == NULL)
281           goto error;
282         object_list = g_list_prepend (object_list, new_obj);
283         break;
284 
285       case(OBJ_BOX):
286         if ((new_obj = o_box_read (toplevel, line, release_ver, fileformat_ver, err)) == NULL)
287           goto error;
288         object_list = g_list_prepend (object_list, new_obj);
289         break;
290 
291       case(OBJ_CIRCLE):
292         if ((new_obj = o_circle_read (toplevel, line, release_ver, fileformat_ver, err)) == NULL)
293           goto error;
294         object_list = g_list_prepend (object_list, new_obj);
295         break;
296 
297       case(OBJ_COMPLEX):
298       case(OBJ_PLACEHOLDER):
299         if ((new_obj = o_complex_read (toplevel, line, release_ver, fileformat_ver, err)) == NULL)
300           goto error;
301         object_list = g_list_prepend (object_list, new_obj);
302         break;
303 
304       case(OBJ_PATH):
305         new_obj = o_path_read (toplevel, line, tb, release_ver, fileformat_ver, err);
306         if (new_obj == NULL)
307           goto error;
308         object_list = g_list_prepend (object_list, new_obj);
309         break;
310 
311       case(OBJ_PIN):
312         if ((new_obj = o_pin_read (toplevel, line, release_ver, fileformat_ver, err)) == NULL)
313           goto error;
314         object_list = g_list_prepend (object_list, new_obj);
315         break;
316 
317       case(OBJ_ARC):
318         if ((new_obj = o_arc_read (toplevel, line, release_ver, fileformat_ver, err)) == NULL)
319           goto error;
320         object_list = g_list_prepend (object_list, new_obj);
321         break;
322 
323       case(OBJ_TEXT):
324         new_obj = o_text_read (toplevel, line, tb, release_ver, fileformat_ver, err);
325         if (new_obj == NULL)
326           goto error;
327         object_list = g_list_prepend (object_list, new_obj);
328         ATTACH=TRUE;
329 
330         break;
331 
332       case(ENDATTACH_ATTR):
333         return object_list;
334         break;
335     }
336 
337     if (ATTACH) {
338       o_attrib_attach (toplevel, new_obj, object_to_get_attribs, FALSE);
339       ATTACH=FALSE;
340     } else {
341       g_set_error(err, EDA_ERROR, EDA_ERROR_PARSE, _("Tried to attach a non-text item as an attribute"));
342       goto error;
343     }
344   }
345 
346   /* The attribute list wasn't terminated, so it's a parse error! */
347   g_set_error (err, EDA_ERROR, EDA_ERROR_PARSE,
348                _("Unexpected end-of-file in attribute list"));
349 
350 error:
351   s_delete_object_glist(toplevel, object_list);
352   return NULL;
353 }
354 
355 
356 /*! \brief Get name and value from an attribute 'name=value' string.
357  *  \par Function Description
358  *  This function parses the character string \a string expected to be
359  *  an attribute string of the form 'name=value'.
360  *
361  *  It returns TRUE if it has been able to parse the string into the
362  *  name and value parts of an attribute. Otherwise it returns FALSE,
363  *  in that case \a *name_ptr and \a *value_ptr are set to NULL.
364  *
365  *  \a name_ptr and/or \a value_ptr can be NULL.
366  *  If not NULL, the caller must g_free these returned strings.
367  *
368  *  \note
369  *  If you get an invalid attribute (improper) with a name and no
370  *  value, then it is NOT an attribute. Also, there cannot be any
371  *  spaces beside the equals sign
372  *
373  *  \param [in]  string     String to split into name/value pair.
374  *  \param [out] name_ptr   The return location for the name, or NULL.
375  *  \param [out] value_ptr  The return location for the value, or NULL.
376  *  \return TRUE on success, FALSE otherwise.
377  */
378 gboolean
o_attrib_string_get_name_value(const gchar * string,gchar ** name_ptr,gchar ** value_ptr)379 o_attrib_string_get_name_value (const gchar *string, gchar **name_ptr, gchar **value_ptr)
380 {
381   gchar *ptr, *prev_char, *next_char;
382 
383   if (name_ptr != NULL)
384     *name_ptr = NULL;
385   if (value_ptr != NULL)
386     *value_ptr = NULL;
387 
388   g_return_val_if_fail (string != NULL, FALSE);
389 
390   ptr = g_utf8_strchr (string, -1, g_utf8_get_char ("="));
391   if (ptr == NULL) {
392     return FALSE;
393   }
394 
395   prev_char = g_utf8_find_prev_char (string, ptr);
396   next_char = g_utf8_find_next_char (ptr, NULL);
397   if (prev_char == NULL || *prev_char == ' ' ||
398       next_char == NULL || *next_char == ' ' || *next_char == '\0' ) {
399     return FALSE;
400   }
401 
402   if (name_ptr != NULL) {
403     *name_ptr = g_strndup (string, (ptr - string));
404   }
405 
406   if (value_ptr != NULL) {
407     *value_ptr = g_strdup (next_char);
408   }
409 
410   return TRUE;
411 }
412 
413 
414 /*! \brief Get name and value from an attribute OBJECT
415  *  \par Function Description
416  *  See o_attrib_string_get_name_value() for more details
417  *
418  *  \param [in]  attrib     The attribute OBJECT whos name/value to return.
419  *  \param [out] name_ptr   The return location for the name, or NULL.
420  *  \param [out] value_ptr  The return location for the value, or NULL.
421  *  \return TRUE on success, FALSE otherwise.
422  */
423 gboolean
o_attrib_get_name_value(OBJECT * attrib,gchar ** name_ptr,gchar ** value_ptr)424 o_attrib_get_name_value (OBJECT *attrib, gchar **name_ptr, gchar **value_ptr)
425 {
426   g_return_val_if_fail (attrib->type == OBJ_TEXT, FALSE);
427 
428   return o_attrib_string_get_name_value (attrib->text->string,
429                                          name_ptr, value_ptr);
430 }
431 
432 
433 /*! \brief Find all floating attributes in the given object list.
434  *  \par Function Description
435  *  Find all floating attributes in the given object list.
436  *
437  *  \param [in] list  GList of OBJECTs to search for floating attributes.
438  *  \return GList of floating attributes from the input list
439  *
440  *  \warning
441  *  Caller must g_list_free returned list.
442  */
o_attrib_find_floating_attribs(const GList * list)443 GList *o_attrib_find_floating_attribs (const GList *list)
444 {
445   GList *floating_attributes = NULL;
446   const GList *iter;
447   OBJECT *o_current;
448 
449   for (iter = list; iter != NULL; iter = g_list_next (iter)) {
450     o_current = iter->data;
451 
452     /* Skip non text objects, attached attributes and text which doesn't
453      * constitute a valid attributes (e.g. general text placed on the page)
454      */
455     if (o_current->type == OBJ_TEXT &&
456         o_current->attached_to == NULL &&
457         o_attrib_get_name_value (o_current, NULL, NULL)) {
458 
459       floating_attributes = g_list_prepend (floating_attributes, o_current);
460     }
461   }
462 
463   return g_list_reverse (floating_attributes);
464 }
465 
466 
467 /*! \brief Find an attribute in a list.
468  *  \par Function Description
469  *  Search for attribute by name.
470  *
471  *  Counter is the n'th occurance of the attribute, and starts searching
472  *  from zero.  Zero is the first occurance of an attribute.
473  *
474  *  \param [in] list     GList of attributes to search.
475  *  \param [in] name     Character string with attribute name to search for.
476  *  \param [in] count    Which occurance to return.
477  *  \return The n'th attribute object in the given list with the given name.
478  */
o_attrib_find_attrib_by_name(const GList * list,char * name,int count)479 OBJECT *o_attrib_find_attrib_by_name (const GList *list, char *name, int count)
480 {
481   OBJECT *a_current;
482   const GList *iter;
483   char *found_name;
484   int internal_counter = 0;
485 
486   for (iter = list; iter != NULL; iter = g_list_next (iter)) {
487     a_current = iter->data;
488 
489     g_return_val_if_fail (a_current->type == OBJ_TEXT, NULL);
490 
491     if (!o_attrib_get_name_value (a_current, &found_name, NULL))
492       continue;
493 
494     if (strcmp (name, found_name) == 0) {
495       if (internal_counter == count) {
496         g_free (found_name);
497         return a_current;
498       }
499       internal_counter++;
500     }
501 
502     g_free (found_name);
503   }
504 
505   return NULL;
506 }
507 
508 
509 /*! \brief Search attribute list by name.
510  *  \par Function Description
511  *  Search for attribute by name.
512  *
513  *  Counter is the n'th occurance of the attribute, and starts searching
514  *  from zero.  Zero is the first occurance of an attribute.
515  *
516  *  \param [in] list     GList of attributes to search.
517  *  \param [in] name     Character string with attribute name to search for.
518  *  \param [in] counter  Which occurance to return.
519  *  \return Character string with attribute value, NULL otherwise.
520  */
o_attrib_search_attrib_list_by_name(const GList * list,char * name,int counter)521 static char *o_attrib_search_attrib_list_by_name (const GList *list, char *name, int counter)
522 {
523   OBJECT *attrib;
524   char *value = NULL;
525 
526   attrib = o_attrib_find_attrib_by_name (list, name, counter);
527 
528   if (attrib != NULL)
529     o_attrib_get_name_value (attrib, NULL, &value);
530 
531   return value;
532 }
533 
534 
535 /*! \brief Search floating attribute by name.
536  *  \par Function Description
537  *  Search for attribute by name.
538  *
539  *  Counter is the n'th occurance of the attribute, and starts searching
540  *  from zero.  Zero is the first occurance of an attribute.
541  *
542  *  \param [in] list     GList of OBJECTs to search for floating attributes.
543  *  \param [in] name     Character string with attribute name to search for.
544  *  \param [in] counter  Which occurance to return.
545  *  \return Character string with attribute value, NULL otherwise.
546  *
547  *  \warning
548  *  Caller must g_free returned character string.
549  */
o_attrib_search_floating_attribs_by_name(const GList * list,char * name,int counter)550 char *o_attrib_search_floating_attribs_by_name (const GList *list, char *name, int counter)
551 {
552   char *result;
553   GList *attributes;
554 
555   attributes = o_attrib_find_floating_attribs (list);
556   result = o_attrib_search_attrib_list_by_name (attributes, name, counter);
557   g_list_free (attributes);
558 
559   return result;
560 }
561 
562 
563 /*! \brief Search attached attributes by name.
564  *  \par Function Description
565  *  Search for attribute by name.
566  *
567  *  Counter is the n'th occurance of the attribute, and starts searching
568  *  from zero.  Zero is the first occurance of an attribute.
569  *
570  *  \param [in] object   The OBJECT whos attached attributes to search.
571  *  \param [in] name     Character string with attribute name to search for.
572  *  \param [in] counter  Which occurance to return.
573  *  \return Character string with attribute value, NULL otherwise.
574  *
575  *  \warning
576  *  Caller must g_free returned character string.
577  */
o_attrib_search_attached_attribs_by_name(OBJECT * object,char * name,int counter)578 char *o_attrib_search_attached_attribs_by_name (OBJECT *object, char *name, int counter)
579 {
580   return o_attrib_search_attrib_list_by_name (object->attribs, name, counter);
581 }
582 
583 
584 /*! \brief Search inherited attribute by name.
585  *  \par Function Description
586  *  Search for attribute by name.
587  *
588  *  Counter is the n'th occurance of the attribute, and starts searching
589  *  from zero.  Zero is the first occurance of an attribute.
590  *
591  *  \param [in] object   The OBJECT whos inherited attributes to search.
592  *  \param [in] name     Character string with attribute name to search for.
593  *  \param [in] counter  Which occurance to return.
594  *  \return Character string with attribute value, NULL otherwise.
595  *
596  *  \warning
597  *  Caller must g_free returned character string.
598  */
o_attrib_search_inherited_attribs_by_name(OBJECT * object,char * name,int counter)599 char *o_attrib_search_inherited_attribs_by_name (OBJECT *object, char *name, int counter)
600 {
601   g_return_val_if_fail (object->type == OBJ_COMPLEX ||
602                         object->type == OBJ_PLACEHOLDER, NULL);
603 
604   return o_attrib_search_floating_attribs_by_name (object->complex->prim_objs, name, counter);
605 }
606 
607 
608 /*! \brief Search attributes of object by name.
609  *  \par Function Description
610  *  Search for attribute by name.
611  *
612  *  Counter is the n'th occurance of the attribute, and starts searching
613  *  from zero.  Zero is the first occurance of an attribute.
614  *
615  *  \param [in] object   OBJECT who's attributes to search.
616  *  \param [in] name     Character string with attribute name to search for.
617  *  \param [in] counter  Which occurance to return.
618  *  \return Character string with attribute value, NULL otherwise.
619  *
620  *  \warning
621  *  Caller must g_free returned character string.
622  */
o_attrib_search_object_attribs_by_name(OBJECT * object,char * name,int counter)623 char *o_attrib_search_object_attribs_by_name (OBJECT *object, char *name, int counter)
624 {
625   char *result;
626   GList *attributes;
627 
628   attributes = o_attrib_return_attribs (object);
629   result = o_attrib_search_attrib_list_by_name (attributes, name, counter);
630   g_list_free (attributes);
631 
632   return result;
633 }
634 
635 
636 /*! \brief Get all attached attributes of the specified OBJECT.
637  *  \par Function Description
638  *  This function returns all attributes of the specified object.
639  *
640  *  The returned GList should be freed using the #g_list_free().
641  *
642  *  This function aggregates the attached and inherited attributes
643  *  belonging to a given OBJECT. (inherited attributes are those
644  *  which live as toplevel un-attached attributes inside in a
645  *  complex OBJECT's prim_objs).
646  *
647  *  \param [in] object       OBJECT whos attributes to return.
648  *  \return A GList of attributes belinging to the passed object.
649  */
o_attrib_return_attribs(OBJECT * object)650 GList * o_attrib_return_attribs (OBJECT *object)
651 {
652   GList *attribs = NULL;
653   GList *inherited_attribs;
654   OBJECT *a_current;
655   GList *a_iter;
656 
657   g_return_val_if_fail (object != NULL, NULL);
658 
659   /* Directly attached attributes */
660   for (a_iter = object->attribs; a_iter != NULL;
661        a_iter = g_list_next (a_iter)) {
662     a_current = a_iter->data;
663 
664     if (a_current->type != OBJ_TEXT)
665       continue;
666 
667     /* Don't add invalid attributes to the list */
668     if (!o_attrib_get_name_value (a_current, NULL, NULL))
669       continue;
670 
671     attribs = g_list_prepend (attribs, a_current);
672   }
673 
674   attribs = g_list_reverse (attribs);
675 
676   /* Inherited attributes (inside complex objects) */
677   if (object->type == OBJ_COMPLEX ||
678       object->type == OBJ_PLACEHOLDER) {
679 
680     inherited_attribs =
681       o_attrib_find_floating_attribs (object->complex->prim_objs);
682 
683     attribs = g_list_concat (attribs, inherited_attribs);
684   }
685 
686   return attribs;
687 }
688 
689 
690 /*! \brief Query whether a given attribute OBJECT is "inherited"
691  *  \par Function Description
692  *  This function returns TRUE if the given attribute OBJECT is a
693  *  toplevel un-attached attribute inside a complex's prim_objs.
694  *
695  *  \param [in] attrib       OBJECT who's status to query.
696  *  \return TRUE if the given attribute is inside a symbol
697  */
o_attrib_is_inherited(OBJECT * attrib)698 int o_attrib_is_inherited (OBJECT *attrib)
699 {
700   return (attrib->attached_to == NULL &&
701           attrib->parent != NULL);
702 }
703 
704 
705 typedef struct {
706   AttribsChangedFunc func;
707   void *data;
708 } AttribsChangedHook;
709 
710 
o_attrib_append_attribs_changed_hook(TOPLEVEL * toplevel,AttribsChangedFunc func,void * data)711 void o_attrib_append_attribs_changed_hook (TOPLEVEL *toplevel,
712                                            AttribsChangedFunc func,
713                                            void *data)
714 {
715   AttribsChangedHook *new_hook;
716 
717   new_hook = g_new0 (AttribsChangedHook, 1);
718   new_hook->func = func;
719   new_hook->data = data;
720 
721   toplevel->attribs_changed_hooks =
722     g_list_append (toplevel->attribs_changed_hooks, new_hook);
723 }
724 
725 
call_attribs_changed_hook(gpointer data,gpointer user_data)726 static void call_attribs_changed_hook (gpointer data, gpointer user_data)
727 {
728   AttribsChangedHook *hook = data;
729   OBJECT *object = user_data;
730 
731   hook->func (hook->data, object);
732 }
733 
734 
o_attrib_emit_attribs_changed(TOPLEVEL * toplevel,OBJECT * object)735 void o_attrib_emit_attribs_changed (TOPLEVEL *toplevel, OBJECT *object)
736 {
737   if (object->attrib_notify_freeze_count > 0) {
738     object->attrib_notify_pending = 1;
739     return;
740   }
741 
742 //  printf ("The attributes of object %p have changed\n", object);
743 
744   object->attrib_notify_pending = 0;
745 
746   g_list_foreach (toplevel->attribs_changed_hooks,
747                   call_attribs_changed_hook, object);
748 }
749 
o_attrib_freeze_hooks(TOPLEVEL * toplevel,OBJECT * object)750 void o_attrib_freeze_hooks (TOPLEVEL *toplevel, OBJECT *object)
751 {
752   object->attrib_notify_freeze_count ++;
753 }
754 
o_attrib_thaw_hooks(TOPLEVEL * toplevel,OBJECT * object)755 void o_attrib_thaw_hooks (TOPLEVEL *toplevel, OBJECT *object)
756 {
757   g_return_if_fail (object->attrib_notify_freeze_count > 0);
758 
759   object->attrib_notify_freeze_count --;
760 
761   if (object->attrib_notify_freeze_count == 0 &&
762       object->attrib_notify_pending)
763     o_attrib_emit_attribs_changed (toplevel, object);
764 }
765