* lib/text.h: Added text_get_line() declaration
[dia.git] / lib / object.c
blob866dcb11a6077d83affe0be567f3934a7fe7233f
1 /* Dia -- an diagram creation/manipulation program
2 * Copyright (C) 1998 Alexander Larsson
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.
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.
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.
19 /** \file lib/object.c implementation of DiaObject related functions */
20 #include <config.h>
22 #include <stdio.h>
23 #include <string.h>
25 #include <gtk/gtk.h>
27 #include "object.h"
28 #include "diagramdata.h" /* for Layer */
29 #include "message.h"
30 #include "parent.h"
32 #include "dummy_dep.h"
34 #include "debug.h"
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.
44 void
45 object_init(DiaObject *obj,
46 int num_handles,
47 int num_connections)
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;
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;
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.
67 void
68 object_destroy(DiaObject *obj)
70 object_unconnect_all(obj);
72 if (obj->handles)
73 g_free(obj->handles);
75 if (obj->connections)
76 g_free(obj->connections);
81 /** Copy the object-level information of this object.
82 * This includes type, position, bounding box, number of handles and
83 * connections, operations, parentability, parent and children.
84 * After this copying you have to fix up:
85 handles
86 connections
87 children/parents
88 * In particular the children lists will contain the same objects, which
89 * is not a valid situation.
90 * @param from The object being copied from
91 * @param to The object being copied to. This object does not need to
92 * have been object_init'ed, but if it is, its handles and
93 * connections arrays will be freed.
94 * @bugs Any existing children list will not be freed and will leak.
96 void
97 object_copy(DiaObject *from, DiaObject *to)
99 to->type = from->type;
100 to->position = from->position;
101 to->bounding_box = from->bounding_box;
103 to->num_handles = from->num_handles;
104 if (to->handles != NULL) g_free(to->handles);
105 if (to->num_handles>0)
106 to->handles = g_malloc(sizeof(Handle *)*to->num_handles);
107 else
108 to->handles = NULL;
110 to->num_connections = from->num_connections;
111 if (to->connections != NULL) g_free(to->connections);
112 if (to->num_connections>0)
113 to->connections = g_malloc0(sizeof(ConnectionPoint *) * to->num_connections);
114 else
115 to->connections = NULL;
117 to->ops = from->ops;
119 to->flags = from->flags;
120 to->parent = from->parent;
121 to->children = g_list_copy(from->children);
124 /** A hash function of a pointer value. Not the most well-spreadout
125 * function, as it has the same low bits as the pointer.
127 static guint
128 pointer_hash(gpointer some_pointer)
130 return (guint) some_pointer;
134 /** Copy a list of objects, keeping connections and parent-children
135 * relation ships between the objects. It is assumed that the
136 * ops->copy function correctly creates the connections and handles
137 * objects.
138 * @param list_orig The original list. This list will not be changed,
139 * nor will its objects.
140 * @return A list with the list_orig copies copied.
141 * @bugs Any children of an object in the list that are not themselves
142 * in the list will cause a NULL entry in the children list.
144 GList *
145 object_copy_list(GList *list_orig)
147 GList *list_copy;
148 GList *list;
149 DiaObject *obj;
150 DiaObject *obj_copy;
151 GHashTable *hash_table;
152 int i;
154 hash_table = g_hash_table_new((GHashFunc) pointer_hash, NULL);
156 /* First ops->copy the entire list */
157 list = list_orig;
158 list_copy = NULL;
159 while (list != NULL) {
160 obj = (DiaObject *)list->data;
162 obj_copy = obj->ops->copy(obj);
164 g_hash_table_insert(hash_table, obj, obj_copy);
166 list_copy = g_list_append(list_copy, obj_copy);
168 list = g_list_next(list);
171 /* Rebuild the connections and parent/child references between the
172 objects in the list: */
173 list = list_orig;
174 while (list != NULL) {
175 obj = (DiaObject *)list->data;
176 obj_copy = g_hash_table_lookup(hash_table, obj);
178 if (obj_copy->parent)
179 obj_copy->parent = g_hash_table_lookup(hash_table, obj_copy->parent);
181 if (object_flags_set(obj_copy, DIA_OBJECT_CAN_PARENT)
182 && obj_copy->children)
184 GList *child_list = obj_copy->children;
185 while(child_list)
187 DiaObject *child_obj = (DiaObject *) child_list->data;
188 child_list->data = g_hash_table_lookup(hash_table, child_obj);
189 child_list = g_list_next(child_list);
193 for (i=0;i<obj->num_handles;i++) {
194 ConnectionPoint *con_point;
195 con_point = obj->handles[i]->connected_to;
197 if ( con_point != NULL ) {
198 DiaObject *other_obj;
199 DiaObject *other_obj_copy;
200 int con_point_nr;
202 other_obj = con_point->object;
203 other_obj_copy = g_hash_table_lookup(hash_table, other_obj);
205 if (other_obj_copy == NULL)
206 break; /* other object was not on list. */
208 con_point_nr=0;
209 while (other_obj->connections[con_point_nr] != con_point) {
210 con_point_nr++;
213 object_connect(obj_copy, obj_copy->handles[i],
214 other_obj_copy->connections[con_point_nr]);
218 list = g_list_next(list);
221 g_hash_table_destroy(hash_table);
223 return list_copy;
226 /** Move a number of objects the same distance. Any children of objects in
227 * the list are moved as well. This is intended to be called from within
228 * object_list_move_delta.
229 * @param objects The list of objects to move. This list must not contain
230 * any object that is a child (at any depth) of another object.
231 * @see parent_list_affected_hierarchy
232 * @param delta How far to move the objects.
233 * @param affected Whether to check parent boundaries???
234 * @return Undo information for the move, or NULL if no objects moved.
235 * @bugs If a parent and is child are both in the list, is the child moved
236 * twice?
237 * @bugs The return Change object only contains info for a single object.
239 ObjectChange*
240 object_list_move_delta_r(GList *objects, Point *delta, gboolean affected)
242 GList *list;
243 DiaObject *obj;
244 Point pos;
245 ObjectChange *objchange = NULL;
247 if (delta->x == 0 && delta->y == 0)
248 return NULL;
250 list = objects;
251 while (list != NULL) {
252 obj = (DiaObject *) list->data;
254 pos = obj->position;
255 point_add(&pos, delta);
257 if (obj->parent && affected)
259 Rectangle p_ext;
260 Rectangle c_ext;
261 Point new_delta;
263 parent_handle_extents(obj->parent, &p_ext);
264 parent_handle_extents(obj, &c_ext);
265 new_delta = parent_move_child_delta(&p_ext, &c_ext, delta);
266 point_add(&pos, &new_delta);
267 point_add(delta, &new_delta);
269 objchange = obj->ops->move(obj, &pos);
271 if (object_flags_set(obj, DIA_OBJECT_CAN_PARENT) && obj->children)
272 objchange = object_list_move_delta_r(obj->children, delta, FALSE);
274 list = g_list_next(list);
276 return objchange;
279 /** Move a set of objects a given amount.
280 * @param objects The list ob objects to move.
281 * @param delta The amount to move them.
282 * @bugs Why does this work? Seems like some objects are moved more than once.
284 extern ObjectChange*
285 object_list_move_delta(GList *objects, Point *delta)
287 GList *list;
288 DiaObject *obj;
289 GList *process;
290 ObjectChange *objchange = NULL;
292 objects = parent_list_affected_hierarchy(objects);
293 list = objects;
294 /* The recursive function object_list_move_delta cannot process the toplevel
295 (in selection) objects so we have to have this extra loop */
296 while (list != NULL)
298 obj = (DiaObject *) list->data;
300 process = NULL;
301 process = g_list_append(process, obj);
302 objchange = object_list_move_delta_r(process, delta, (obj->parent != NULL) );
303 g_list_free(process);
305 list = g_list_next(list);
307 return objchange;
310 /** Destroy a list of objects by calling ops->destroy on each in turn.
311 * @param list_to_be_destroyed A of objects list to destroy. The list itself
312 * will also be freed.
314 void
315 destroy_object_list(GList *list_to_be_destroyed)
317 GList *list;
318 DiaObject *obj;
320 list = list_to_be_destroyed;
321 while (list != NULL) {
322 obj = (DiaObject *)list->data;
324 obj->ops->destroy(obj);
325 g_free(obj);
327 list = g_list_next(list);
330 g_list_free(list_to_be_destroyed);
333 /** Add a new handle to an object. This is merely a utility wrapper around
334 * object_add_handle_at().
335 * @param obj The object to add a handle to. This object must have been
336 * object_init()ed.
337 * @param handle The new handle to add. The handle will be inserted as the
338 * last handle in the list.
340 void
341 object_add_handle(DiaObject *obj, Handle *handle)
343 object_add_handle_at(obj, handle, obj->num_handles);
346 /** Add a new handle to an object at a given position. This is merely
347 a utility wrapper around object_add_handle_at().
348 * @param obj The object to add a handle to. This object must have been
349 * object_init()ed.
350 * @param handle The new handle to add.
351 * @param pos Where in the list of handles (0 <= pos <= obj->num_handles) to
352 * add the handle.
354 void
355 object_add_handle_at(DiaObject *obj, Handle *handle, int pos)
357 int i;
359 g_assert(0 <= pos && pos <= obj->num_handles);
361 obj->num_handles++;
363 obj->handles =
364 g_realloc(obj->handles, obj->num_handles*sizeof(Handle *));
366 for (i=obj->num_handles-1; i > pos; i--) {
367 obj->handles[i] = obj->handles[i-1];
369 obj->handles[pos] = handle;
372 /** Remove a handle from an object.
373 * @param obj The object to remove a handle from.
374 * @param handle The handle to remove. If the handle does not exist on this
375 * object, an error message is displayed. The handle is not
376 * freed by this call.
378 void
379 object_remove_handle(DiaObject *obj, Handle *handle)
381 int i, handle_nr;
383 handle_nr = -1;
384 for (i=0;i<obj->num_handles;i++) {
385 if (obj->handles[i] == handle)
386 handle_nr = i;
389 if (handle_nr < 0) {
390 message_error("Internal error, object_remove_handle: Handle doesn't exist");
391 return;
394 for (i=handle_nr;i<(obj->num_handles-1);i++) {
395 obj->handles[i] = obj->handles[i+1];
397 obj->handles[obj->num_handles-1] = NULL;
399 obj->num_handles--;
401 obj->handles =
402 g_realloc(obj->handles, obj->num_handles*sizeof(Handle *));
405 /** Add a new connectionpoint to an object.
406 * This is merely a convenience handler to add a connectionpoint at the
407 * end of an objects connectionpoint list.
408 * @see object_add_connectionpoint_at.
410 void
411 object_add_connectionpoint(DiaObject *obj, ConnectionPoint *conpoint)
413 object_add_connectionpoint_at(obj, conpoint, obj->num_connections);
416 /** Add a new connectionpoint to an object.
417 * @param obj The object to add the connectionpoint to.
418 * @param conpoint The connectionpoiint to add.
419 * @param pos Where in the list to add the connectionpoint
420 * (0 <= pos <= obj->num_connections).
422 void
423 object_add_connectionpoint_at(DiaObject *obj,
424 ConnectionPoint *conpoint, int pos)
426 int i;
428 obj->num_connections++;
430 obj->connections =
431 g_realloc(obj->connections,
432 obj->num_connections*sizeof(ConnectionPoint *));
434 for (i=obj->num_connections-1; i > pos; i--) {
435 obj->connections[i] = obj->connections[i-1];
437 obj->connections[pos] = conpoint;
440 /** Remove an existing connectionpoint from and object.
441 * @param obj The object to remove the connectionpoint from.
442 * @param conpoint The connectionpoint to remove. The connectionpoint
443 * will not be freed by this function, but any handles
444 * connected to the connectionpoint will be
445 * disconnected.
446 * If the connectionpoint does not exist on the object,
447 * an error message is displayed.
449 void
450 object_remove_connectionpoint(DiaObject *obj, ConnectionPoint *conpoint)
452 int i, nr;
454 nr = -1;
455 for (i=0;i<obj->num_connections;i++) {
456 if (obj->connections[i] == conpoint)
457 nr = i;
460 if (nr < 0) {
461 message_error("Internal error, object_remove_connectionpoint: "
462 "ConnectionPoint doesn't exist");
463 return;
466 object_remove_connections_to(conpoint);
468 for (i=nr;i<(obj->num_connections-1);i++) {
469 obj->connections[i] = obj->connections[i+1];
471 obj->connections[obj->num_connections-1] = NULL;
473 obj->num_connections--;
475 obj->connections =
476 g_realloc(obj->connections, obj->num_connections*sizeof(ConnectionPoint *));
480 /** Make a connection between the handle and the connectionpoint.
481 * @param obj The object having the handle.
482 * @param handle The handle being connected. This handle must not have
483 * connect_type HANDLE_NONCONNECTABLE, or an incomprehensible
484 * error message is displayed to the user.
485 * @param connectionpoint The connection point to connect to.
487 void
488 object_connect(DiaObject *obj, Handle *handle,
489 ConnectionPoint *connectionpoint)
491 if (handle->connect_type==HANDLE_NONCONNECTABLE) {
492 message_error("Error? trying to connect a non connectable handle.\n"
493 "Check this out...\n");
494 return;
496 handle->connected_to = connectionpoint;
497 connectionpoint->connected =
498 g_list_prepend(connectionpoint->connected, obj);
501 /** Disconnect handle from whatever it may be connected to.
502 * @param connected_obj The object having the handle.
503 * @param handle The handle to disconnect
505 void
506 object_unconnect(DiaObject *connected_obj, Handle *handle)
508 ConnectionPoint *connectionpoint;
510 connectionpoint = handle->connected_to;
512 if (connectionpoint!=NULL) {
513 connectionpoint->connected =
514 g_list_remove(connectionpoint->connected, connected_obj);
515 handle->connected_to = NULL;
519 /** Remove all connections to the given connectionpoint.
520 * After this call, the connectionpoints connected field will be NULL,
521 * the list will have been freed, and no handles will be connected to the
522 * connectionpoint.
523 * @param conpoint A connectionpoint.
525 void
526 object_remove_connections_to(ConnectionPoint *conpoint)
528 GList *list;
529 DiaObject *connected_obj;
530 int i;
532 list = conpoint->connected;
533 while (list != NULL) {
534 connected_obj = (DiaObject *)list->data;
536 for (i=0;i<connected_obj->num_handles;i++) {
537 if (connected_obj->handles[i]->connected_to == conpoint) {
538 connected_obj->handles[i]->connected_to = NULL;
541 list = g_list_next(list);
543 g_list_free(conpoint->connected);
544 conpoint->connected = NULL;
547 /** Remove all connections to and from an object.
548 * @param obj An object to disconnect from all connectionpoints and handles.
550 void
551 object_unconnect_all(DiaObject *obj)
553 int i;
555 for (i=0;i<obj->num_handles;i++) {
556 object_unconnect(obj, obj->handles[i]);
558 for (i=0;i<obj->num_connections;i++) {
559 object_remove_connections_to(obj->connections[i]);
563 /** Save the object-specific parts of an object.
564 * Note that this does not save the attributes of an object, merely the
565 * basic data (currently position and bounding box).
566 * @param obj An object to save.
567 * @param obj_node An XML node to save the data to.
569 void
570 object_save(DiaObject *obj, ObjectNode obj_node)
572 data_add_point(new_attribute(obj_node, "obj_pos"),
573 &obj->position);
574 data_add_rectangle(new_attribute(obj_node, "obj_bb"),
575 &obj->bounding_box);
578 /** Load the object-specific parts of an object.
579 * Note that this does not load the attributes of an object, merely the
580 * basic data (currently position and bounding box).
581 * @param obj An object to load into.
582 * @param obj_node An XML node to load the data from.
584 void
585 object_load(DiaObject *obj, ObjectNode obj_node)
587 AttributeNode attr;
589 obj->position.x = 0.0;
590 obj->position.y = 0.0;
591 attr = object_find_attribute(obj_node, "obj_pos");
592 if (attr != NULL)
593 data_point( attribute_first_data(attr), &obj->position );
595 obj->bounding_box.left = obj->bounding_box.right = 0.0;
596 obj->bounding_box.top = obj->bounding_box.bottom = 0.0;
597 attr = object_find_attribute(obj_node, "obj_bb");
598 if (attr != NULL)
599 data_rectangle( attribute_first_data(attr), &obj->bounding_box );
602 /** Returns the layer that the given object belongs to.
603 * @param obj An object.
604 * @return The layer that contains the object, or NULL if the object is
605 * not in any layer.
607 Layer *
608 dia_object_get_parent_layer(DiaObject *obj) {
609 return obj->parent_layer;
612 /** Returns true if `obj' is currently selected.
613 * Note that this takes time proportional to the number of selected
614 * objects, so don't use it frivolously.
615 * Note that if the objects is not in a layer (e.g. grouped), it is never
616 * considered selected.
617 * @param obj An object to test for selectedness.
618 * @return TRUE if the object is selected.
620 gboolean
621 dia_object_is_selected (const DiaObject *obj)
623 Layer *layer = obj->parent_layer;
624 DiagramData *diagram = layer ? layer->parent_diagram : NULL;
625 GList * selected;
627 /* although this is a little bogus, it is better than crashing
628 * It appears as if neither group members nor "parented" objects do have their
629 * parent_layer set (but they aren't slected either, are they ? --hb
630 * No, grouped objects at least aren't selectable, but they may need
631 * to test selectedness when rendering beziers. Parented objects are
632 * a different thing, though. */
633 if (!diagram)
634 return FALSE;
636 selected = diagram->selected;
637 for (; selected != NULL; selected = g_list_next(selected)) {
638 if (selected->data == obj) return TRUE;
640 return FALSE;
643 /** Return the top-most object in the parent chain that has the given
644 * flags set.
645 * @param obj An object to start at. If this is NULL, NULL is returned.
646 * @param flags The flags to check. If 0, the top-most parent is returned.
647 * If more than one flag is given, the top-most parent that has all the given
648 * flags set is returned.
649 * @returns An object that either has all the flags set or
650 * is obj itself. It is guaranteed that no parent of this object has all the
651 * given flags set.
653 DiaObject *
654 dia_object_get_parent_with_flags(DiaObject *obj, guint flags)
656 DiaObject *top = obj;
657 if (obj == NULL) {
658 return NULL;
660 while (obj->parent != NULL) {
661 obj = obj->parent;
662 if ((obj->flags & flags) == flags) {
663 top = obj;
666 return top;
669 /** Utility function: Checks if an objects can be selected.
670 * Reasons for not being selectable include:
671 * Being inside a closed group.
672 * Being in a non-active layer.
674 * @param obj An object to test.
675 * @returns TRUE if the object is not currently selected.
677 gboolean
678 dia_object_is_selectable(DiaObject *obj)
680 if (obj->parent_layer == NULL) {
681 return FALSE;
683 return obj->parent_layer == obj->parent_layer->parent_diagram->active_layer
684 && obj == dia_object_get_parent_with_flags(obj, DIA_OBJECT_GRABS_CHILD_INPUT);
688 /****** DiaObject register: **********/
690 static guint hash(gpointer key)
692 char *string = (char *)key;
693 int sum;
695 sum = 0;
696 while (*string) {
697 sum += (*string);
698 string++;
701 return sum;
704 static gint compare(gpointer a, gpointer b)
706 return strcmp((char *)a, (char *)b)==0;
709 static GHashTable *object_type_table = NULL;
711 /** Initialize the object registry. */
712 void
713 object_registry_init(void)
715 object_type_table = g_hash_table_new( (GHashFunc) hash, (GCompareFunc) compare );
718 /** Register the type of an object.
719 * This should be called as part of dia_plugin_init calls in modules that
720 * define objects for sheets. If an object type with the given name is
721 * already registered (typically due to a user saving a local copy), a
722 * warning is display to the user.
723 * @param type The type information.
725 void
726 object_register_type(DiaObjectType *type)
728 if (g_hash_table_lookup(object_type_table, type->name) != NULL) {
729 message_warning("Several object-types were named %s.\n"
730 "Only first one will be used.\n"
731 "Some things might not work as expected.\n",
732 type->name);
733 return;
735 g_hash_table_insert(object_type_table, type->name, type);
739 /** Performs a function on each registered object type.
740 * @param func A function foo(DiaObjectType, gpointer) to call.
741 * @param user_data Data passed through to the functions.
743 void
744 object_registry_foreach (GHFunc func, gpointer user_data)
746 g_hash_table_foreach (object_type_table, func, user_data);
749 /** Get the object type information associated with a name.
750 * @param name A type name.
751 * @return A DiaObjectType for an object type with the given name, or
752 * NULL if no such type is registered.
754 DiaObjectType *
755 object_get_type(char *name)
757 return (DiaObjectType *) g_hash_table_lookup(object_type_table, name);
760 /** True if all the given flags are set, false otherwise.
761 * @param obj An object to test.
762 * @param flags Flags to check if they are set. See definitions in object.h
763 * @return TRUE if all the flags given are set on the object.
765 gboolean
766 object_flags_set(DiaObject *obj, gint flags)
768 return (obj->flags & flags) == flags;
771 /** Utility function that always returns FALSE given any object.
772 * @param obj Not used.
773 * @return FALSE
776 object_return_false(DiaObject *obj)
778 return FALSE;
781 /** Utility function that always returns NULL given any object.
782 * @param obj Not used.
783 * @return NULL
785 void *
786 object_return_null(DiaObject *obj)
788 return NULL;
791 /** Utility function that always returns nothing given any object.
792 * @param obj Not used.
794 void
795 object_return_void(DiaObject *obj)
797 return;
800 /** Load an object from XML based on its properties.
801 * This function is suitable for implementing the object load function
802 * for an object with normal attributes. Any version-dependent handling
803 * should be done after calling this function.
804 * @param type The type of the object, used for creation.
805 * @param obj_node The XML node defining the object.
806 * @param version The version of the object found in the XML structure.
807 * @param filename The name of the file that the XML came from, for error
808 * messages.
809 * @return A newly created object with properties loaded.
811 DiaObject *
812 object_load_using_properties(const DiaObjectType *type,
813 ObjectNode obj_node, int version,
814 const char *filename)
816 DiaObject *obj;
817 Point startpoint = {0.0,0.0};
818 Handle *handle1,*handle2;
820 obj = type->ops->create(&startpoint,NULL, &handle1,&handle2);
821 object_load_props(obj,obj_node);
822 return obj;
825 /** Save an object into an XML structure based on its properties.
826 * This function is suitable for implementing the object save function
827 * for an object with normal attributes.
828 * @param obj The object to save.
829 * @param obj_node The XML structure to save into.
830 * @param version The version of the objects structure this will be saved as
831 * (for allowing backwards compatibility).
832 * @param filename The name of the file being saved to, for error messages.
834 void
835 object_save_using_properties(DiaObject *obj, ObjectNode obj_node,
836 int version, const char *filename)
838 object_save_props(obj,obj_node);
841 /** Copy an object based solely on its properties.
842 * This function is suitable for implementing the object save function
843 * for an object with normal attributes.
844 * @param obj An object to copy.
846 DiaObject *object_copy_using_properties(DiaObject *obj)
848 Point startpoint = {0.0,0.0};
849 Handle *handle1,*handle2;
850 DiaObject *newobj = obj->type->ops->create(&startpoint,NULL,
851 &handle1,&handle2);
852 object_copy_props(newobj,obj,FALSE);
853 return newobj;
856 /** Return a box that all 'real' parts of the object is bounded by.
857 * In most cases, this is the same as the enclosing box, but things like
858 * bezier controls would lie outside of this.
859 * @param obj The object to get the bounding box for.
860 * @return A pointer to a Rectangle object. This object should *not*
861 * be freed after use, as it belongs to the object.
863 Rectangle *
864 dia_object_get_bounding_box(const DiaObject *obj) {
865 return &obj->bounding_box;
868 /** Return a box that encloses all interactively rendered parts of the object.
869 * @param obj The object to get the enclosing box for.
870 * @return A pointer to a Rectangle object. This object should *not*
871 * be freed after use, as it belongs to the object.
873 Rectangle *dia_object_get_enclosing_box(const DiaObject *obj) {
874 /* I believe we can do this comparison, as it is only to compare for cases
875 * where it would be set explicitly to 0.
877 if (obj->enclosing_box.top == 0.0 &&
878 obj->enclosing_box.bottom == 0.0 &&
879 obj->enclosing_box.left == 0.0 &&
880 obj->enclosing_box.right == 0.0) {
881 return &obj->bounding_box;
882 } else {
883 } return &obj->enclosing_box;
888 /* The below are for debugging purposes only. */
890 /** Check that a DiaObject maintains its invariants and constrains.
891 * @param obj An object to check
892 * @return TRUE if the object is OK. */
893 gboolean
894 dia_object_sanity_check(const DiaObject *obj, const gchar *msg) {
895 int i;
896 /* Check the type */
897 dia_assert_true(obj->type != NULL,
898 "%s: Object %p has null type\n",
899 msg, obj);
900 if (obj != NULL) {
901 dia_assert_true(obj->type->name != NULL &&
902 g_utf8_validate(obj->type->name, -1, NULL),
903 "%s: Object %p has illegal type name %p (%s)\n",
904 msg, obj, obj->type->name);
905 /* Check the position vs. the bounding box */
906 /* Check the handles */
907 dia_assert_true(obj->num_handles >= 0,
908 "%s: Object %p has < 0 (%d) handles\n",
909 msg, obj, obj->num_handles);
910 if (obj->num_handles != 0) {
911 dia_assert_true(obj->handles != NULL,
912 "%s: Object %p has null handles\n", obj);
914 for (i = 0; i < obj->num_handles; i++) {
915 Handle *h = obj->handles[i];
916 dia_assert_true(h != NULL, "%s: Object %p handle %d is null\n",
917 msg, obj, i);
918 if (h != NULL) {
919 /* Check handle id */
920 dia_assert_true((h->id >= 0 && h->id <= HANDLE_MOVE_ENDPOINT)
921 || (h->id >= HANDLE_CUSTOM1 && h->id <= HANDLE_CUSTOM9),
922 "%s: Object %p handle %d (%p) has wrong id %d\n",
923 msg, obj, i, h, h->id);
924 /* Check handle type */
925 dia_assert_true(h->type >= 0 && h->type <= NUM_HANDLE_TYPES,
926 "%s: Object %p handle %d (%p) has wrong type %d\n",
927 msg, obj, i, h, h->type);
928 /* Check handle pos is legal pos */
929 /* Check connect type is legal */
930 dia_assert_true(h->connect_type >= 0
931 && h->connect_type <= HANDLE_CONNECTABLE_NOBREAK,
932 "%s: Object %p handle %d (%p) has wrong connect type %d\n",
933 msg, obj, i, h, h->connect_type);
934 /* Check that if connected, connection makes sense */
935 do { /* do...while(FALSE) to make aborting easy */
936 ConnectionPoint *cp = h->connected_to;
937 if (cp != NULL) {
938 gboolean found = FALSE;
939 GList *conns;
940 if (!dia_assert_true(cp->object != NULL,
941 "%s: Handle %d (%p) on object %p connects to CP %p with NULL object\n",
942 msg, i, h, obj, cp)) break;
943 if (!dia_assert_true(cp->object->type != NULL,
944 "%s: Handle %d (%p) on object %p connects to CP %p with untyped object %p\n",
945 msg, i, h, obj, cp, cp->object)) break;
946 if (!dia_assert_true(cp->object->type->name != NULL &&
947 g_utf8_validate(cp->object->type->name, -1, NULL),
948 "%s: Handle %d (%p) on object %p connects to CP %p with untyped object %p\n",
949 msg, i, h, obj, cp, cp->object)) break;
950 dia_assert_true(fabs(cp->pos.x - h->pos.x) < 0.0000001 &&
951 fabs(cp->pos.y - h->pos.y) < 0.0000001,
952 "%s: Handle %d (%p) on object %p has pos %f, %f,\nbut its CP %p of object %p has pos %f, %f\n",
953 msg, i, h, obj, h->pos.x, h->pos.y,
954 cp, cp->object, cp->pos.x, cp->pos.y);
955 for (conns = cp->connected; conns != NULL; conns = g_list_next(conns)) {
956 DiaObject *obj2 = (DiaObject *)conns->data;
957 int j;
959 for (j = 0; j < obj2->num_handles; j++) {
960 if (obj2->handles[j]->connected_to == cp) found = TRUE;
963 dia_assert_true(found == TRUE,
964 "%s: Handle %d (%p) on object %p is connected to %p on object %p, but is not in its connect list\n",
965 msg, i, h, obj, cp, cp->object);
967 } while (FALSE);
970 /* Check the connections */
971 dia_assert_true(obj->num_connections >= 0,
972 "%s: Object %p has < 0 (%d) connection points\n",
973 msg, obj, obj->num_connections);
974 if (obj->num_connections != 0) {
975 dia_assert_true(obj->connections != NULL,
976 "%s: Object %p has NULL connections array\n",
977 msg, obj);
979 for (i = 0; i < obj->num_connections; i++) {
980 GList *connected;
981 ConnectionPoint *cp = obj->connections[i];
982 int j;
983 dia_assert_true(cp != NULL, "%s: Object %p has null CP at %d\n", msg, obj, i);
984 if (cp != NULL) {
985 dia_assert_true(cp->object == obj,
986 "%s: Object %p CP %d (%p) points to other obj %p\n",
987 msg, obj, i, cp, cp->object);
988 dia_assert_true((cp->directions & (~DIR_ALL)) == 0,
989 "%s: Object %p CP %d (%p) has illegal directions %d\n",
990 msg, obj, i, cp, cp->directions);
991 dia_assert_true((cp->flags & CP_FLAGS_MAIN) == cp->flags,
992 "%s: Object %p CP %d (%p) has illegal flags %d\n",
993 msg, obj, i, cp, cp->flags);
994 dia_assert_true(cp->name == NULL
995 || g_utf8_validate(cp->name, -1, NULL),
996 "%s: Object %p CP %d (%p) has non-UTF8 name %s\n",
997 msg, obj, i, cp, cp->name);
998 j = 0;
999 for (connected = cp->connected; connected != NULL;
1000 connected = g_list_next(connected)) {
1001 DiaObject *obj2;
1002 obj2 = connected->data;
1003 dia_assert_true(obj2 != NULL, "%s: Object %p CP %d (%p) has NULL object at index %d\n",
1004 msg, obj, i, cp, j);
1005 if (obj2 != NULL) {
1006 int k;
1007 gboolean found_handle = FALSE;
1008 dia_assert_true(obj2->type->name != NULL &&
1009 g_utf8_validate(obj2->type->name, -1, NULL),
1010 "%s: Object %p CP %d (%p) connected to untyped object %p (%s) at index %d\n",
1011 msg, obj, i, cp, obj2, obj2->type->name, j);
1012 /* Check that connected object points back to this CP */
1013 for (k = 0; k < obj2->num_handles; k++) {
1014 if (obj2->handles[k] != NULL &&
1015 obj2->handles[k]->connected_to == cp) {
1016 found_handle = TRUE;
1019 dia_assert_true(found_handle,
1020 "%s: Object %p CP %d (%p) connected to %p (%s) at index %d, but no handle points back\n",
1021 msg, obj, i, cp, obj2, obj2->type->name, j);
1023 j++;
1027 /* Check the children */
1029 return TRUE;