1 /* Dia -- an diagram creation/manipulation program
2  * Copyright (C) 1998 Alexander Larsson
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17  */
18 
19 /** \file lib/object.c implementation of DiaObject related functions */
20 #include <config.h>
21 
22 #include <stdio.h>
23 #include <string.h>
24 
25 #include <gtk/gtk.h>
26 
27 #include "object.h"
28 #include "diagramdata.h" /* for Layer */
29 #include "message.h"
30 #include "parent.h"
31 
32 #include "dummy_dep.h"
33 
34 #include "debug.h"
35 
36 /** Initialize an already allocated object with the given number of handles
37  *  and connections.  This does not create the actual Handle and Connection
38  *  objects, which are expected to be added later.
39  * @param obj A newly allocated object with no handles or connections
40  *            previously allocated.
41  * @param num_handles the number of handles to allocate room for.
42  * @param num_connections the number of connections to allocate room for.
43  */
44 void
object_init(DiaObject * obj,int num_handles,int num_connections)45 object_init(DiaObject *obj,
46 	    int num_handles,
47 	    int num_connections)
48 {
49   obj->num_handles = num_handles;
50   if (num_handles>0)
51     obj->handles = g_malloc0(sizeof(Handle *) * num_handles);
52   else
53     obj->handles = NULL;
54 
55   obj->num_connections = num_connections;
56   if (num_connections>0)
57     obj->connections = g_malloc0(sizeof(ConnectionPoint *) * num_connections);
58   else
59     obj->connections = NULL;
60 }
61 
62 /** Destroy an objects allocations and disconnect it from everything else.
63  *  After calling this function, the object is no longer valid for use
64  *  in a diagram.  Note that this does not deallocate the object itself.
65  * @param obj The object being destroyed.
66  */
67 void
object_destroy(DiaObject * obj)68 object_destroy(DiaObject *obj)
69 {
70   object_unconnect_all(obj);
71 
72   if (obj->handles)
73     g_free(obj->handles);
74   obj->handles = NULL;
75   if (obj->connections)
76     g_free(obj->connections);
77   obj->connections = NULL;
78   if (obj->meta)
79     g_hash_table_destroy (obj->meta);
80   obj->meta = NULL;
81 }
82 
83 /** Copy the object-level information of this object.
84  *  This includes type, position, bounding box, number of handles and
85  *  connections, operations, parentability, parent and children.
86  *  After this copying you have to fix up:
87     handles
88     connections
89     children/parents
90  * In particular the children lists will contain the same objects, which
91  * is not a valid situation.
92  * @param from The object being copied from
93  * @param to The object being copied to.  This object does not need to
94  *           have been object_init'ed, but if it is, its handles and
95  *           connections arrays will be freed.
96  * @bugs Any existing children list will not be freed and will leak.
97  */
98 void
object_copy(DiaObject * from,DiaObject * to)99 object_copy(DiaObject *from, DiaObject *to)
100 {
101   to->type = from->type;
102   to->position = from->position;
103   to->bounding_box = from->bounding_box;
104 
105   to->num_handles = from->num_handles;
106   if (to->handles != NULL) g_free(to->handles);
107   if (to->num_handles>0)
108     to->handles = g_malloc(sizeof(Handle *)*to->num_handles);
109   else
110     to->handles = NULL;
111 
112   to->num_connections = from->num_connections;
113   if (to->connections != NULL) g_free(to->connections);
114   if (to->num_connections>0)
115     to->connections = g_malloc0(sizeof(ConnectionPoint *) * to->num_connections);
116   else
117     to->connections = NULL;
118 
119   to->ops = from->ops;
120 
121   to->flags = from->flags;
122   to->parent = from->parent;
123   to->children = g_list_copy(from->children);
124 }
125 
126 /** A hash function of a pointer value.  Not the most well-spreadout
127  * function, as it has the same low bits as the pointer.
128  */
129 static guint
pointer_hash(gpointer some_pointer)130 pointer_hash(gpointer some_pointer)
131 {
132   return GPOINTER_TO_UINT(some_pointer);
133 }
134 
135 
136 /** Copy a list of objects, keeping connections and parent-children
137  *  relation ships between the objects.  It is assumed that the
138  *  ops->copy function correctly creates the connections and handles
139  *  objects.
140  * @param list_orig The original list.  This list will not be changed,
141  *                  nor will its objects.
142  * @return A list with the list_orig copies copied.
143  * @bugs Any children of an object in the list that are not themselves
144  *       in the list will cause a NULL entry in the children list.
145  */
146 GList *
object_copy_list(GList * list_orig)147 object_copy_list(GList *list_orig)
148 {
149   GList *list_copy;
150   GList *list;
151   DiaObject *obj;
152   DiaObject *obj_copy;
153   GHashTable *hash_table;
154   int i;
155 
156   hash_table = g_hash_table_new((GHashFunc) pointer_hash, NULL);
157 
158   /* First ops->copy the entire list */
159   list = list_orig;
160   list_copy = NULL;
161   while (list != NULL) {
162     obj = (DiaObject *)list->data;
163 
164     obj_copy = obj->ops->copy(obj);
165 
166     g_hash_table_insert(hash_table, obj, obj_copy);
167 
168     list_copy = g_list_append(list_copy, obj_copy);
169 
170     list = g_list_next(list);
171   }
172 
173   /* Rebuild the connections and parent/child references between the
174   objects in the list: */
175   list = list_orig;
176   while (list != NULL) {
177     obj = (DiaObject *)list->data;
178     obj_copy = g_hash_table_lookup(hash_table, obj);
179 
180     if (obj_copy->parent)
181       obj_copy->parent = g_hash_table_lookup(hash_table, obj_copy->parent);
182 
183     if (object_flags_set(obj_copy, DIA_OBJECT_CAN_PARENT)
184 	&& obj_copy->children)
185     {
186       GList *child_list = obj_copy->children;
187       while(child_list)
188       {
189         DiaObject *child_obj = (DiaObject *) child_list->data;
190         child_list->data = g_hash_table_lookup(hash_table, child_obj);
191 	child_list = g_list_next(child_list);
192       }
193     }
194 
195     for (i=0;i<obj->num_handles;i++) {
196       ConnectionPoint *con_point;
197       con_point = obj->handles[i]->connected_to;
198 
199       if ( con_point != NULL ) {
200 	DiaObject *other_obj;
201 	DiaObject *other_obj_copy;
202 	int con_point_nr;
203 
204 	other_obj = con_point->object;
205 	other_obj_copy = g_hash_table_lookup(hash_table, other_obj);
206 
207 	if (other_obj_copy == NULL) {
208 	  /* Ensure we have no dangling connection to avoid crashing, on
209 	   * object_unconnect() e.g. bug #497070. Two questions remaining:
210 	   *  - shouldn't the object::copy() have initialized this to NULL?
211 	   *  - could we completely solve this by looking deeper into groups?
212 	   *    The sample from #497070 has nested groups but this function currently
213 	   *    works on one level at the time. Thus the object within the group are
214 	   *    invisible when we try to restore the groups connectons. BUT the
215 	   *    connectionpoints in the group are shared with the connectionpoints
216 	   *    of the inner objects ...
217 	   */
218 	  obj_copy->handles[i]->connected_to = NULL;
219 	  break; /* other object was not on list. */
220 	}
221 
222 	con_point_nr=0;
223 	while (other_obj->connections[con_point_nr] != con_point) {
224 	  con_point_nr++;
225 	}
226 
227 	object_connect(obj_copy, obj_copy->handles[i],
228 		       other_obj_copy->connections[con_point_nr]);
229       }
230     }
231 
232     list = g_list_next(list);
233   }
234 
235   g_hash_table_destroy(hash_table);
236 
237   return list_copy;
238 }
239 
240 /** Move a number of objects the same distance.  Any children of objects in
241  * the list are moved as well.  This is intended to be called from within
242  * object_list_move_delta.
243  * @param objects The list of objects to move.  This list must not contain
244  *                any object that is a child (at any depth) of another object.
245  *                @see parent_list_affected_hierarchy
246  * @param delta How far to move the objects.
247  * @param affected Whether to check parent boundaries???
248  * @return Undo information for the move, or NULL if no objects moved.
249  * @bugs If a parent and is child are both in the list, is the child moved
250  *       twice?
251  * @bugs The return Change object only contains info for a single object.
252  */
253 ObjectChange*
object_list_move_delta_r(GList * objects,Point * delta,gboolean affected)254 object_list_move_delta_r(GList *objects, Point *delta, gboolean affected)
255 {
256   GList *list;
257   DiaObject *obj;
258   Point pos;
259   ObjectChange *objchange = NULL;
260 
261   if (delta->x == 0 && delta->y == 0)
262        return NULL;
263 
264   list = objects;
265   while (list != NULL) {
266     obj = (DiaObject *) list->data;
267 
268     pos = obj->position;
269     point_add(&pos, delta);
270 
271     if (obj->parent && affected)
272     {
273       Rectangle p_ext;
274       Rectangle c_ext;
275       Point new_delta;
276 
277       parent_handle_extents(obj->parent, &p_ext);
278       parent_handle_extents(obj, &c_ext);
279       new_delta = parent_move_child_delta(&p_ext, &c_ext, delta);
280       point_add(&pos, &new_delta);
281       point_add(delta, &new_delta);
282     }
283     objchange = obj->ops->move(obj, &pos);
284 
285     if (object_flags_set(obj, DIA_OBJECT_CAN_PARENT) && obj->children)
286       objchange = object_list_move_delta_r(obj->children, delta, FALSE);
287 
288     list = g_list_next(list);
289   }
290   return objchange;
291 }
292 
293 /** Move a set of objects a given amount.
294  * @param objects The list ob objects to move.
295  * @param delta The amount to move them.
296  * @bugs Why does this work?  Seems like some objects are moved more than once.
297  */
298 extern ObjectChange*
object_list_move_delta(GList * objects,Point * delta)299 object_list_move_delta(GList *objects, Point *delta)
300 {
301   GList *list;
302   DiaObject *obj;
303   GList *process;
304   ObjectChange *objchange = NULL;
305 
306   objects = parent_list_affected_hierarchy(objects);
307   list = objects;
308   /* The recursive function object_list_move_delta cannot process the toplevel
309      (in selection) objects so we have to have this extra loop */
310   while (list != NULL)
311   {
312     obj = (DiaObject *) list->data;
313 
314     process = NULL;
315     process = g_list_append(process, obj);
316     objchange = object_list_move_delta_r(process, delta, (obj->parent != NULL) );
317     g_list_free(process);
318 
319     list = g_list_next(list);
320   }
321   return objchange;
322 }
323 
324 /** Destroy a list of objects by calling ops->destroy on each in turn.
325  * @param list_to_be_destroyed A of objects list to destroy.  The list itself
326  *                             will also be freed.
327  */
328 void
destroy_object_list(GList * list_to_be_destroyed)329 destroy_object_list(GList *list_to_be_destroyed)
330 {
331   GList *list;
332   DiaObject *obj;
333 
334   list = list_to_be_destroyed;
335   while (list != NULL) {
336     obj = (DiaObject *)list->data;
337 
338     obj->ops->destroy(obj);
339     g_free(obj);
340 
341     list = g_list_next(list);
342   }
343 
344   g_list_free(list_to_be_destroyed);
345 }
346 
347 /** Add a new handle to an object.  This is merely a utility wrapper around
348  * object_add_handle_at().
349  * @param obj The object to add a handle to.  This object must have been
350  *            object_init()ed.
351  * @param handle The new handle to add.  The handle will be inserted as the
352  *               last handle in the list.
353  */
354 void
object_add_handle(DiaObject * obj,Handle * handle)355 object_add_handle(DiaObject *obj, Handle *handle)
356 {
357   object_add_handle_at(obj, handle, obj->num_handles);
358 }
359 
360 /** Add a new handle to an object at a given position.  This is merely
361    a utility wrapper around object_add_handle_at().
362  * @param obj The object to add a handle to.  This object must have been
363  *            object_init()ed.
364  * @param handle The new handle to add.
365  * @param pos Where in the list of handles (0 <= pos <= obj->num_handles) to
366  *            add the handle.
367  */
368 void
object_add_handle_at(DiaObject * obj,Handle * handle,int pos)369 object_add_handle_at(DiaObject *obj, Handle *handle, int pos)
370 {
371   int i;
372 
373   g_assert(0 <= pos && pos <= obj->num_handles);
374 
375   obj->num_handles++;
376 
377   obj->handles =
378     g_realloc(obj->handles, obj->num_handles*sizeof(Handle *));
379 
380   for (i=obj->num_handles-1; i > pos; i--) {
381     obj->handles[i] = obj->handles[i-1];
382   }
383   obj->handles[pos] = handle;
384 }
385 
386 /** Remove a handle from an object.
387  * @param obj The object to remove a handle from.
388  * @param handle The handle to remove.  If the handle does not exist on this
389  *               object, an error message is displayed.  The handle is not
390  *               freed by this call.
391  */
392 void
object_remove_handle(DiaObject * obj,Handle * handle)393 object_remove_handle(DiaObject *obj, Handle *handle)
394 {
395   int i, handle_nr;
396 
397   handle_nr = -1;
398   for (i=0;i<obj->num_handles;i++) {
399     if (obj->handles[i] == handle)
400       handle_nr = i;
401   }
402 
403   if (handle_nr < 0) {
404     message_error("Internal error, object_remove_handle: Handle doesn't exist");
405     return;
406   }
407 
408   for (i=handle_nr;i<(obj->num_handles-1);i++) {
409     obj->handles[i] = obj->handles[i+1];
410   }
411   obj->handles[obj->num_handles-1] = NULL;
412 
413   obj->num_handles--;
414 
415   obj->handles =
416     g_realloc(obj->handles, obj->num_handles*sizeof(Handle *));
417 }
418 
419 /** Add a new connectionpoint to an object.
420  * This is merely a convenience handler to add a connectionpoint at the
421  * end of an objects connectionpoint list.
422  * @see object_add_connectionpoint_at.
423  */
424 void
object_add_connectionpoint(DiaObject * obj,ConnectionPoint * conpoint)425 object_add_connectionpoint(DiaObject *obj, ConnectionPoint *conpoint)
426 {
427   object_add_connectionpoint_at(obj, conpoint, obj->num_connections);
428 }
429 
430 /** Add a new connectionpoint to an object.
431  * @param obj The object to add the connectionpoint to.
432  * @param conpoint The connectionpoiint to add.
433  * @param pos Where in the list to add the connectionpoint
434  * (0 <= pos <= obj->num_connections).
435  */
436 void
object_add_connectionpoint_at(DiaObject * obj,ConnectionPoint * conpoint,int pos)437 object_add_connectionpoint_at(DiaObject *obj,
438 			      ConnectionPoint *conpoint, int pos)
439 {
440   int i;
441 
442   obj->num_connections++;
443 
444   obj->connections =
445     g_realloc(obj->connections,
446 	      obj->num_connections*sizeof(ConnectionPoint *));
447 
448   for (i=obj->num_connections-1; i > pos; i--) {
449     obj->connections[i] = obj->connections[i-1];
450   }
451   obj->connections[pos] = conpoint;
452 }
453 
454 /** Remove an existing connectionpoint from and object.
455  * @param obj The object to remove the connectionpoint from.
456  * @param conpoint The connectionpoint to remove.  The connectionpoint
457  *                 will not be freed by this function, but any handles
458  *                 connected to the connectionpoint will be
459  *                 disconnected.
460  *                 If the connectionpoint does not exist on the object,
461  *                 an error message is displayed.
462  */
463 void
object_remove_connectionpoint(DiaObject * obj,ConnectionPoint * conpoint)464 object_remove_connectionpoint(DiaObject *obj, ConnectionPoint *conpoint)
465 {
466   int i, nr;
467 
468   nr = -1;
469   for (i=0;i<obj->num_connections;i++) {
470     if (obj->connections[i] == conpoint)
471       nr = i;
472   }
473 
474   if (nr < 0) {
475     message_error("Internal error, object_remove_connectionpoint: "
476                   "ConnectionPoint doesn't exist");
477     return;
478   }
479 
480   object_remove_connections_to(conpoint);
481 
482   for (i=nr;i<(obj->num_connections-1);i++) {
483     obj->connections[i] = obj->connections[i+1];
484   }
485   obj->connections[obj->num_connections-1] = NULL;
486 
487   obj->num_connections--;
488 
489   obj->connections =
490     g_realloc(obj->connections, obj->num_connections*sizeof(ConnectionPoint *));
491 }
492 
493 
494 /** Make a connection between the handle and the connectionpoint.
495  * @param obj The object having the handle.
496  * @param handle The handle being connected.  This handle must not have
497  *               connect_type HANDLE_NONCONNECTABLE, or an incomprehensible
498  *               error message is displayed to the user.
499  * @param connectionpoint The connection point to connect to.
500  */
501 void
object_connect(DiaObject * obj,Handle * handle,ConnectionPoint * connectionpoint)502 object_connect(DiaObject *obj, Handle *handle,
503 	       ConnectionPoint *connectionpoint)
504 {
505   g_return_if_fail (obj && obj->type && obj->type->name);
506   g_return_if_fail (connectionpoint && connectionpoint->object &&
507                     connectionpoint->object->type && connectionpoint->object->type->name);
508   if (handle->connect_type==HANDLE_NONCONNECTABLE) {
509     message_error("Error? trying to connect a non connectable handle.\n"
510                   "'%s' -> '%s'\n"
511 		  "Check this out...\n",
512 		  obj->type->name,
513 		  connectionpoint->object->type->name);
514     return;
515   }
516   handle->connected_to = connectionpoint;
517   connectionpoint->connected =
518     g_list_prepend(connectionpoint->connected, obj);
519 }
520 
521 /** Disconnect handle from whatever it may be connected to.
522  * @param connected_obj The object having the handle.
523  * @param handle The handle to disconnect
524  */
525 void
object_unconnect(DiaObject * connected_obj,Handle * handle)526 object_unconnect(DiaObject *connected_obj, Handle *handle)
527 {
528   ConnectionPoint *connectionpoint;
529 
530   connectionpoint = handle->connected_to;
531 
532   if (connectionpoint!=NULL) {
533     connectionpoint->connected =
534       g_list_remove(connectionpoint->connected, connected_obj);
535     handle->connected_to = NULL;
536   }
537 }
538 
539 /** Remove all connections to the given connectionpoint.
540  * After this call, the connectionpoints connected field will be NULL,
541  * the list will have been freed, and no handles will be connected to the
542  * connectionpoint.
543  * @param conpoint A connectionpoint.
544  */
545 void
object_remove_connections_to(ConnectionPoint * conpoint)546 object_remove_connections_to(ConnectionPoint *conpoint)
547 {
548   GList *list;
549   DiaObject *connected_obj;
550   int i;
551 
552   list = conpoint->connected;
553   while (list != NULL) {
554     connected_obj = (DiaObject *)list->data;
555 
556     for (i=0;i<connected_obj->num_handles;i++) {
557       if (connected_obj->handles[i]->connected_to == conpoint) {
558 	connected_obj->handles[i]->connected_to = NULL;
559       }
560     }
561     list = g_list_next(list);
562   }
563   g_list_free(conpoint->connected);
564   conpoint->connected = NULL;
565 }
566 
567 /** Remove all connections to and from an object.
568  * @param obj An object to disconnect from all connectionpoints and handles.
569  */
570 void
object_unconnect_all(DiaObject * obj)571 object_unconnect_all(DiaObject *obj)
572 {
573   int i;
574 
575   for (i=0;i<obj->num_handles;i++) {
576     object_unconnect(obj, obj->handles[i]);
577   }
578   for (i=0;i<obj->num_connections;i++) {
579     object_remove_connections_to(obj->connections[i]);
580   }
581 }
582 
583 /** Save the object-specific parts of an object.
584  *  Note that this does not save the attributes of an object, merely the
585  *  basic data (currently position and bounding box).
586  * @param obj An object to save.
587  * @param obj_node An XML node to save the data to.
588  */
589 void
object_save(DiaObject * obj,ObjectNode obj_node)590 object_save(DiaObject *obj, ObjectNode obj_node)
591 {
592   data_add_point(new_attribute(obj_node, "obj_pos"),
593 		 &obj->position);
594   data_add_rectangle(new_attribute(obj_node, "obj_bb"),
595 		     &obj->bounding_box);
596   if (obj->meta)
597     data_add_dict (new_attribute(obj_node, "meta"), obj->meta);
598 }
599 
600 /** Load the object-specific parts of an object.
601  *  Note that this does not load the attributes of an object, merely the
602  *  basic data (currently position and bounding box).
603  * @param obj An object to load into.
604  * @param obj_node An XML node to load the data from.
605  */
606 void
object_load(DiaObject * obj,ObjectNode obj_node)607 object_load(DiaObject *obj, ObjectNode obj_node)
608 {
609   AttributeNode attr;
610 
611   obj->position.x = 0.0;
612   obj->position.y = 0.0;
613   attr = object_find_attribute(obj_node, "obj_pos");
614   if (attr != NULL)
615     data_point( attribute_first_data(attr), &obj->position );
616 
617   obj->bounding_box.left = obj->bounding_box.right = 0.0;
618   obj->bounding_box.top = obj->bounding_box.bottom = 0.0;
619   attr = object_find_attribute(obj_node, "obj_bb");
620   if (attr != NULL)
621     data_rectangle( attribute_first_data(attr), &obj->bounding_box );
622 
623   attr = object_find_attribute(obj_node, "meta");
624   if (attr != NULL)
625     obj->meta = data_dict (attribute_first_data(attr));
626 }
627 
628 /** Returns the layer that the given object belongs to.
629  * @param obj An object.
630  * @return The layer that contains the object, or NULL if the object is
631  * not in any layer.
632  */
633 Layer *
dia_object_get_parent_layer(DiaObject * obj)634 dia_object_get_parent_layer(DiaObject *obj) {
635   return obj->parent_layer;
636 }
637 
638 /** Returns true if `obj' is currently selected.
639  * Note that this takes time proportional to the number of selected
640  * objects, so don't use it frivolously.
641  * Note that if the objects is not in a layer (e.g. grouped), it is never
642  * considered selected.
643  * @param obj An object to test for selectedness.
644  * @return TRUE if the object is selected.
645  */
646 gboolean
dia_object_is_selected(const DiaObject * obj)647 dia_object_is_selected (const DiaObject *obj)
648 {
649   Layer *layer = obj->parent_layer;
650   DiagramData *diagram = layer ? layer->parent_diagram : NULL;
651   GList * selected;
652 
653   /* although this is a little bogus, it is better than crashing
654    * It appears as if neither group members nor "parented" objects do have their
655    * parent_layer set (but they aren't slected either, are they ? --hb
656    * No, grouped objects at least aren't selectable, but they may need
657    * to test selectedness when rendering beziers.  Parented objects are
658    * a different thing, though. */
659   if (!diagram)
660     return FALSE;
661 
662   selected = diagram->selected;
663   for (; selected != NULL; selected = g_list_next(selected)) {
664     if (selected->data == obj) return TRUE;
665   }
666   return FALSE;
667 }
668 
669 /** Return the top-most object in the parent chain that has the given
670  * flags set.
671  * @param obj An object to start at.  If this is NULL, NULL is returned.
672  * @param flags The flags to check.  If 0, the top-most parent is returned.
673  * If more than one flag is given, the top-most parent that has all the given
674  * flags set is returned.
675  * @returns An object that either has all the flags set or
676  * is obj itself.  It is guaranteed that no parent of this object has all the
677  * given flags set.
678  */
679 DiaObject *
dia_object_get_parent_with_flags(DiaObject * obj,guint flags)680 dia_object_get_parent_with_flags(DiaObject *obj, guint flags)
681 {
682   DiaObject *top = obj;
683   if (obj == NULL) {
684     return NULL;
685   }
686   while (obj->parent != NULL) {
687     obj = obj->parent;
688     if ((obj->flags & flags) == flags) {
689       top = obj;
690     }
691   }
692   return top;
693 }
694 
695 /** Utility function: Checks if an objects can be selected.
696  * Reasons for not being selectable include:
697  * Being inside a closed group.
698  * Being in a non-active layer.
699  *
700  * @param obj An object to test.
701  * @returns TRUE if the object is not currently selected.
702  */
703 gboolean
dia_object_is_selectable(DiaObject * obj)704 dia_object_is_selectable(DiaObject *obj)
705 {
706   if (obj->parent_layer == NULL) {
707     return FALSE;
708   }
709   return obj->parent_layer == obj->parent_layer->parent_diagram->active_layer
710     && obj == dia_object_get_parent_with_flags(obj, DIA_OBJECT_GRABS_CHILD_INPUT);
711 }
712 
713 
714 /****** DiaObject register: **********/
715 
hash(gpointer key)716 static guint hash(gpointer key)
717 {
718   char *string = (char *)key;
719   int sum;
720 
721   sum = 0;
722   while (*string) {
723     sum += (*string);
724     string++;
725   }
726 
727   return sum;
728 }
729 
compare(gpointer a,gpointer b)730 static gint compare(gpointer a, gpointer b)
731 {
732   return strcmp((char *)a, (char *)b)==0;
733 }
734 
735 static GHashTable *object_type_table = NULL;
736 
737 /** Initialize the object registry. */
738 void
object_registry_init(void)739 object_registry_init(void)
740 {
741   object_type_table = g_hash_table_new( (GHashFunc) hash, (GCompareFunc) compare );
742 }
743 
744 /** Register the type of an object.
745  *  This should be called as part of dia_plugin_init calls in modules that
746  *  define objects for sheets.  If an object type with the given name is
747  *  already registered (typically due to a user saving a local copy), a
748  *  warning is display to the user.
749  * @param type The type information.
750  */
751 void
object_register_type(DiaObjectType * type)752 object_register_type(DiaObjectType *type)
753 {
754   if (g_hash_table_lookup(object_type_table, type->name) != NULL) {
755     message_warning("Several object-types were named %s.\n"
756 		    "Only first one will be used.\n"
757 		    "Some things might not work as expected.\n",
758 		    type->name);
759     return;
760   }
761   g_hash_table_insert(object_type_table, type->name, type);
762 }
763 
764 
765 /** Performs a function on each registered object type.
766  * @param func A function foo(DiaObjectType, gpointer) to call.
767  * @param user_data Data passed through to the functions.
768  */
769 void
object_registry_foreach(GHFunc func,gpointer user_data)770 object_registry_foreach (GHFunc func, gpointer user_data)
771 {
772   g_hash_table_foreach (object_type_table, func, user_data);
773 }
774 
775 /** Get the object type information associated with a name.
776  * @param name A type name.
777  * @return A DiaObjectType for an object type with the given name, or
778  *         NULL if no such type is registered.
779  */
780 DiaObjectType *
object_get_type(char * name)781 object_get_type(char *name)
782 {
783   return (DiaObjectType *) g_hash_table_lookup(object_type_table, name);
784 }
785 
786 /** True if all the given flags are set, false otherwise.
787  * @param obj An object to test.
788  * @param flags Flags to check if they are set.  See definitions in object.h
789  * @return TRUE if all the flags given are set on the object.
790  */
791 gboolean
object_flags_set(DiaObject * obj,gint flags)792 object_flags_set(DiaObject *obj, gint flags)
793 {
794   return (obj->flags & flags) == flags;
795 }
796 
797 /** Load an object from XML based on its properties.
798  *  This function is suitable for implementing the object load function
799  *  for an object with normal attributes.  Any version-dependent handling
800  *  should be done after calling this function.
801  * @param type The type of the object, used for creation.
802  * @param obj_node The XML node defining the object.
803  * @param version The version of the object found in the XML structure.
804  * @param filename The name of the file that the XML came from, for error
805  *                 messages.
806  * @return A newly created object with properties loaded.
807  */
808 DiaObject *
object_load_using_properties(const DiaObjectType * type,ObjectNode obj_node,int version,const char * filename)809 object_load_using_properties(const DiaObjectType *type,
810                              ObjectNode obj_node, int version,
811                              const char *filename)
812 {
813   DiaObject *obj;
814   Point startpoint = {0.0,0.0};
815   Handle *handle1,*handle2;
816 
817   obj = type->ops->create(&startpoint,NULL, &handle1,&handle2);
818   object_load_props(obj,obj_node);
819   return obj;
820 }
821 
822 /** Save an object into an XML structure based on its properties.
823  *  This function is suitable for implementing the object save function
824  *  for an object with normal attributes.
825  * @param obj The object to save.
826  * @param obj_node The XML structure to save into.
827  * @param version The version of the objects structure this will be saved as
828  *                (for allowing backwards compatibility).
829  * @param filename The name of the file being saved to, for error messages.
830  */
831 void
object_save_using_properties(DiaObject * obj,ObjectNode obj_node,int version,const char * filename)832 object_save_using_properties(DiaObject *obj, ObjectNode obj_node,
833                              int version, const char *filename)
834 {
835   object_save_props(obj,obj_node);
836 }
837 
838 /** Copy an object based solely on its properties.
839  *  This function is suitable for implementing the object save function
840  *  for an object with normal attributes.
841  * @param obj An object to copy.
842  */
object_copy_using_properties(DiaObject * obj)843 DiaObject *object_copy_using_properties(DiaObject *obj)
844 {
845   Point startpoint = {0.0,0.0};
846   Handle *handle1,*handle2;
847   DiaObject *newobj = obj->type->ops->create(&startpoint,NULL,
848                                           &handle1,&handle2);
849   object_copy_props(newobj,obj,FALSE);
850   return newobj;
851 }
852 
853 /** Return a box that all 'real' parts of the object is bounded by.
854  *  In most cases, this is the same as the enclosing box, but things like
855  *  bezier controls would lie outside of this.
856  * @param obj The object to get the bounding box for.
857  * @return A pointer to a Rectangle object.  This object should *not*
858  *  be freed after use, as it belongs to the object.
859  */
860 const Rectangle *
dia_object_get_bounding_box(const DiaObject * obj)861 dia_object_get_bounding_box(const DiaObject *obj) {
862   return &obj->bounding_box;
863 }
864 
865 /** Return a box that encloses all interactively rendered parts of the object.
866  * @param obj The object to get the enclosing box for.
867  * @return A pointer to a Rectangle object.  This object should *not*
868  *  be freed after use, as it belongs to the object.
869  */
dia_object_get_enclosing_box(const DiaObject * obj)870 const Rectangle *dia_object_get_enclosing_box(const DiaObject *obj) {
871   /* I believe we can do this comparison, as it is only to compare for cases
872    * where it would be set explicitly to 0.
873    */
874   if (obj->enclosing_box.top == 0.0 &&
875       obj->enclosing_box.bottom == 0.0 &&
876       obj->enclosing_box.left == 0.0 &&
877       obj->enclosing_box.right == 0.0) {
878     return &obj->bounding_box;
879   } else {
880   } return &obj->enclosing_box;
881 }
882 
883 
884 
885 /* The below are for debugging purposes only. */
886 
887 /** Check that a DiaObject maintains its invariants and constrains.
888  * @param obj An object to check
889  * @return TRUE if the object is OK. */
890 gboolean
dia_object_sanity_check(const DiaObject * obj,const gchar * msg)891 dia_object_sanity_check(const DiaObject *obj, const gchar *msg) {
892   int i;
893   /* Check the type */
894   dia_assert_true(obj->type != NULL,
895 		  "%s: Object %p has null type\n",
896 		  msg, obj);
897   if (obj != NULL) {
898     dia_assert_true(obj->type->name != NULL &&
899 		    g_utf8_validate(obj->type->name, -1, NULL),
900 		    "%s: Object %p has illegal type name %p (%s)\n",
901 		    msg, obj, obj->type->name);
902     /* Check the position vs. the bounding box */
903     /* Check the handles */
904     dia_assert_true(obj->num_handles >= 0,
905 		    "%s: Object %p has < 0 (%d) handles\n",
906 		    msg, obj,  obj->num_handles);
907     if (obj->num_handles != 0) {
908       dia_assert_true(obj->handles != NULL,
909 		      "%s: Object %p has null handles\n", obj);
910     }
911     for (i = 0; i < obj->num_handles; i++) {
912       Handle *h = obj->handles[i];
913       dia_assert_true(h != NULL, "%s: Object %p handle %d is null\n",
914 		      msg, obj, i);
915       if (h != NULL) {
916 	/* Check handle id */
917 	dia_assert_true((h->id >= 0 && h->id <= HANDLE_MOVE_ENDPOINT)
918 			|| (h->id >= HANDLE_CUSTOM1 && h->id <= HANDLE_CUSTOM9),
919 			"%s: Object %p handle %d (%p) has wrong id %d\n",
920 			msg, obj, i, h, h->id);
921 	/* Check handle type */
922 	dia_assert_true(h->type >= 0 && h->type <= NUM_HANDLE_TYPES,
923 			"%s: Object %p handle %d (%p) has wrong type %d\n",
924 			msg, obj, i, h, h->type);
925 	/* Check handle pos is legal pos */
926 	/* Check connect type is legal */
927 	dia_assert_true(h->connect_type >= 0
928 			&& h->connect_type <= HANDLE_CONNECTABLE_NOBREAK,
929 			"%s: Object %p handle %d (%p) has wrong connect type %d\n",
930 			msg, obj, i, h, h->connect_type);
931 	/* Check that if connected, connection makes sense */
932 	do { /* do...while(FALSE) to make aborting easy */
933 	  ConnectionPoint *cp = h->connected_to;
934 	  if (cp != NULL) {
935 	    gboolean found = FALSE;
936 	    GList *conns;
937 	    if (!dia_assert_true(cp->object != NULL,
938 				 "%s: Handle %d (%p) on object %p connects to CP %p with NULL object\n",
939 				 msg, i, h, obj, cp)) break;
940 	    if (!dia_assert_true(cp->object->type != NULL,
941 				 "%s:  Handle %d (%p) on object %p connects to CP %p with untyped object %p\n",
942 				 msg, i, h, obj, cp, cp->object)) break;
943 	    if (!dia_assert_true(cp->object->type->name != NULL &&
944 				 g_utf8_validate(cp->object->type->name, -1, NULL),
945 				 "%s:  Handle %d (%p) on object %p connects to CP %p with untyped object %p\n",
946 				 msg, i, h, obj, cp, cp->object)) break;
947 	    dia_assert_true(fabs(cp->pos.x - h->pos.x) < 0.0000001 &&
948 			    fabs(cp->pos.y - h->pos.y) < 0.0000001,
949 			    "%s: Handle %d (%p) on object %p has pos %f, %f,\nbut its CP %p of object %p has pos %f, %f\n",
950 			    msg, i, h, obj, h->pos.x, h->pos.y,
951 			    cp, cp->object, cp->pos.x, cp->pos.y);
952 	    for (conns = cp->connected; conns != NULL; conns = g_list_next(conns)) {
953 	      DiaObject *obj2 = (DiaObject *)conns->data;
954 	      int j;
955 
956 	      for (j = 0; j < obj2->num_handles; j++) {
957 		if (obj2->handles[j]->connected_to == cp) found = TRUE;
958 	      }
959 	    }
960 	    dia_assert_true(found == TRUE,
961 			    "%s: Handle %d (%p) on object %p is connected to %p on object %p, but is not in its connect list\n",
962 			    msg, i, h, obj, cp, cp->object);
963 	  }
964 	} while (FALSE);
965       }
966     }
967     /* Check the connections */
968     dia_assert_true(obj->num_connections >= 0,
969 		    "%s: Object %p has < 0 (%d) connection points\n",
970 		    msg, obj, obj->num_connections);
971     if (obj->num_connections != 0) {
972       dia_assert_true(obj->connections != NULL,
973 		      "%s: Object %p has NULL connections array\n",
974 		      msg, obj);
975     }
976     for (i = 0; i < obj->num_connections; i++) {
977       GList *connected;
978       ConnectionPoint *cp = obj->connections[i];
979       int j;
980       dia_assert_true(cp != NULL, "%s: Object %p has null CP at %d\n", msg, obj, i);
981       if (cp != NULL) {
982 	dia_assert_true(cp->object == obj,
983 			"%s: Object %p CP %d (%p) points to other obj %p\n",
984 			msg, obj, i, cp, cp->object);
985 	dia_assert_true((cp->directions & (~DIR_ALL)) == 0,
986 			"%s: Object %p CP %d (%p) has illegal directions %d\n",
987 			msg, obj, i, cp, cp->directions);
988 	dia_assert_true((cp->flags & CP_FLAGS_MAIN) == cp->flags,
989 			"%s: Object %p CP %d (%p) has illegal flags %d\n",
990 			msg, obj, i, cp, cp->flags);
991 	dia_assert_true(cp->name == NULL
992 			|| g_utf8_validate(cp->name, -1, NULL),
993 			"%s: Object %p CP %d (%p) has non-UTF8 name %s\n",
994 			msg, obj, i, cp, cp->name);
995 	j = 0;
996 	for (connected = cp->connected; connected != NULL;
997 	     connected = g_list_next(connected)) {
998 	  DiaObject *obj2;
999 	  obj2 = connected->data;
1000 	  dia_assert_true(obj2 != NULL, "%s: Object %p CP %d (%p) has NULL object at index %d\n",
1001 			  msg, obj, i, cp, j);
1002 	  if (obj2 != NULL) {
1003 	    int k;
1004 	    gboolean found_handle = FALSE;
1005 	    dia_assert_true(obj2->type->name != NULL &&
1006 			    g_utf8_validate(obj2->type->name, -1, NULL),
1007 			    "%s: Object %p CP %d (%p) connected to untyped object %p (%s) at index %d\n",
1008 			    msg, obj, i, cp, obj2, obj2->type->name, j);
1009 	    /* Check that connected object points back to this CP */
1010 	    for (k = 0; k < obj2->num_handles; k++) {
1011 	      if (obj2->handles[k] != NULL &&
1012 		  obj2->handles[k]->connected_to == cp) {
1013 		found_handle = TRUE;
1014 	      }
1015 	    }
1016 	    dia_assert_true(found_handle,
1017 			    "%s: Object %p CP %d (%p) connected to %p (%s) at index %d, but no handle points back\n",
1018 			    msg, obj, i, cp, obj2, obj2->type->name, j);
1019 	  }
1020 	  j++;
1021 	}
1022       }
1023     }
1024     /* Check the children */
1025   }
1026   return TRUE;
1027 }
1028 
1029 void
dia_object_set_meta(DiaObject * obj,const gchar * key,const gchar * value)1030 dia_object_set_meta (DiaObject *obj, const gchar *key, const gchar *value)
1031 {
1032   g_return_if_fail (obj != NULL && key != NULL);
1033   if (!obj->meta)
1034     obj->meta = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
1035   if (value)
1036     g_hash_table_insert (obj->meta, g_strdup (key), g_strdup (value));
1037   else
1038     g_hash_table_remove (obj->meta, key);
1039 }
1040 
1041 gchar *
dia_object_get_meta(DiaObject * obj,const gchar * key)1042 dia_object_get_meta (DiaObject *obj, const gchar *key)
1043 {
1044   gchar *val;
1045   if (!obj->meta)
1046     return NULL;
1047   val = g_hash_table_lookup (obj->meta, key);
1048   return g_strdup (val);
1049 }
1050