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  * File:    compound.c
19  *
20  * Purpose: This file contains implementation of the database "compound"
21  *          code. It is a 'spider' like object with one connection point
22  *          and many arms going from the connnection point.
23  */
24 
25 #ifdef HAVE_CONFIG_H
26 # include <config.h>
27 #endif
28 
29 #include <gtk/gtk.h> /* because of GtkWidgket */
30 #include "pixmaps/compound.xpm"
31 #include "object.h"
32 #include "connectionpoint.h"
33 #include "diarenderer.h"
34 #include "attributes.h"
35 #include "geometry.h"
36 #include "propinternals.h"
37 
38 #include "debug.h"
39 
40 /* ------------------------------------------------------------------------ */
41 
42 #define DEFAULT_NUMARMS 2 /* must be >= 2 */
43 #define HANDLE_MOUNT_POINT (HANDLE_CUSTOM1)
44 #define HANDLE_ARM (HANDLE_CUSTOM2)
45 #define DEFAULT_ARM_X_DISTANCE 0.5
46 #define DEFAULT_ARM_Y_DISTANCE 0.5
47 
48 #define DEBUG_DRAW_MP_DIRECTION 0
49 #define DEBUG_DRAW_BOUNDING_BOX 0
50 
51 /* ------------------------------------------------------------------------ */
52 
53 typedef struct _Compound Compound;
54 typedef struct _CompoundState CompoundState;
55 typedef struct _ArmHandleState ArmHandleState;
56 typedef struct _CompoundChange CompoundChange;
57 typedef struct _MountPointMoveChange MountPointMoveChange;
58 
59 struct _Compound {
60   DiaObject object; /**< inheritance */
61 
62   /* there is only one connection point to this object */
63   ConnectionPoint mount_point;
64   /* the first handle is the handle at the mount_point */
65   Handle *handles; /* array of size is `num_arms + 1' */
66 
67   gint num_arms;
68   real line_width;
69   Color line_color;
70 };
71 
72 struct _CompoundState {
73   ArmHandleState * handle_states;
74   gint num_handles; /* num_arms = num_handles-1 */
75   real line_width;
76   Color line_color;
77 };
78 
79 struct _ArmHandleState {
80   /* id, type, and connect_type are static for the arms of a
81      Compound. there is no need to back them up */
82   Point pos;
83   ConnectionPoint * connected_to;
84 };
85 
86 struct _CompoundChange {
87   ObjectChange obj_change;
88   Compound * obj;
89   CompoundState * saved_state;
90 };
91 
92 struct _MountPointMoveChange {
93   ObjectChange obj_change;
94 
95   Compound * obj;
96   Point saved_pos;
97 };
98 
99 /* ------------------------------------------------------------------------ */
100 
101 static CompoundState * compound_state_new (Compound *);
102 static void compound_state_free (CompoundState *);
103 static void compound_state_set (CompoundState *, Compound *);
104 static CompoundChange * compound_change_new (Compound *, CompoundState *);
105 static void compound_change_apply (CompoundChange *, DiaObject *);
106 static void compound_change_free (CompoundChange *);
107 
108 static MountPointMoveChange *
109 mount_point_move_change_new (Compound *, Point *);
110 static void
111 mount_point_move_change_apply (MountPointMoveChange *, DiaObject *);
112 static void
113 mount_point_move_change_free (MountPointMoveChange *);
114 
115 static DiaObject * compound_create (Point *, void *, Handle **, Handle **);
116 static DiaObject * compound_load (ObjectNode, int, const char *);
117 static void compound_save (Compound *, ObjectNode, const char *);
118 static void compound_destroy (Compound *);
119 static void compound_draw (Compound *, DiaRenderer *);
120 static real compound_distance_from (Compound *, Point *);
121 static void compound_select (Compound *, Point *, DiaRenderer *);
122 static DiaObject * compound_copy (Compound *);
123 static ObjectChange * compound_move (Compound *, Point *);
124 static ObjectChange * compound_move_handle (Compound *, Handle *, Point *,
125                                             ConnectionPoint *,
126                                             HandleMoveReason, ModifierKeys);
127 static ObjectChange * compound_apply_properties_dialog (Compound *,
128                                                         GtkWidget *);
129 static PropDescription * compound_describe_props (Compound *);
130 static void compound_get_props (Compound *, GPtrArray *);
131 static void compound_set_props (Compound *, GPtrArray *);
132 static void compound_update_object (Compound *);
133 static ObjectChange * compound_flip_arms_cb (DiaObject *, Point *, gpointer);
134 static ObjectChange * compound_repos_mount_point_cb (DiaObject *, Point *,
135                                                      gpointer);
136 static DiaMenu * compound_object_menu(DiaObject *, Point *);
137 static gint adjust_handle_count_to (Compound *, gint);
138 static void setup_mount_point (ConnectionPoint *, DiaObject *, Point *);
139 static void setup_handle (Handle *, HandleId, HandleType, HandleConnectType);
140 static void compound_sanity_check (Compound *, gchar *);
141 static void update_mount_point_directions (Compound *);
142 static void compound_apply_props (Compound *, GPtrArray *, gboolean);
143 static void compound_update_data (Compound *);
144 
145 /* these two are responsible for setting the handles positions */
146 static void init_positions_for_handles_beginning_at_index (Compound *, gint);
147 static void init_default_handle_positions (Compound *c);
148 
149 
150 
151 /* ------------------------------------------------------------------------ */
152 
153 static ObjectTypeOps compound_type_ops =
154   {
155     (CreateFunc) compound_create,
156     (LoadFunc) compound_load,
157     (SaveFunc) compound_save
158   };
159 
160 DiaObjectType compound_type =
161   {
162     "Database - Compound", /* name */
163     0,                     /* version */
164     (char **) compound_xpm, /* pixmap */
165     &compound_type_ops,    /* operations */
166     NULL,
167     NULL
168   };
169 
170 static ObjectOps compound_ops =
171   {
172     (DestroyFunc)          compound_destroy,
173     (DrawFunc)             compound_draw,
174     (DistanceFunc)         compound_distance_from,
175     (SelectFunc)           compound_select,
176     (CopyFunc)             compound_copy,
177     (MoveFunc)             compound_move,
178     (MoveHandleFunc)       compound_move_handle,
179     (GetPropertiesFunc)    object_create_props_dialog,
180     (ApplyPropertiesDialogFunc)  compound_apply_properties_dialog,
181     (ObjectMenuFunc)       compound_object_menu,
182     (DescribePropsFunc)    compound_describe_props,
183     (GetPropsFunc)         compound_get_props,
184     (SetPropsFunc)         compound_set_props,
185     (TextEditFunc) 0,
186     (ApplyPropertiesListFunc) object_apply_props,
187 };
188 
189 /* actually, there is no upper limit - used 10 just for fun */
190 static PropNumData compound_num_arms_prop_extra_data = {2.0, 10.0, 1.0};
191 
192 static PropDescription compound_props[] =
193   {
194     OBJECT_COMMON_PROPERTIES,
195     PROP_STD_LINE_COLOUR_OPTIONAL,
196     PROP_STD_LINE_WIDTH_OPTIONAL,
197     { "num_arms", PROP_TYPE_INT, PROP_FLAG_VISIBLE,
198       N_("Number of arms"), NULL, &compound_num_arms_prop_extra_data},
199 
200     PROP_DESC_END
201   };
202 
203 static PropOffset compound_offsets[] =
204   {
205     OBJECT_COMMON_PROPERTIES_OFFSETS,
206     { "line_colour", PROP_TYPE_COLOUR, offsetof(Compound, line_color) },
207     { PROP_STDNAME_LINE_WIDTH, PROP_STDTYPE_LINE_WIDTH, offsetof(Compound, line_width) },
208     { "num_arms", PROP_TYPE_INT, offsetof(Compound, num_arms) },
209 
210     { NULL, 0, 0 }
211   };
212 
213 #define CENTER_BOTH 1
214 #define CENTER_VERTICAL 2
215 #define CENTER_HORIZONTAL 3
216 
217 #define FLIP_VERTICAL 1
218 #define FLIP_HORIZONTAL 2
219 
220 static DiaMenuItem compound_menu_items[] = {
221   { N_("Flip arms verticaly"), compound_flip_arms_cb,
222     GINT_TO_POINTER(1), DIAMENU_ACTIVE },
223   { N_("Flip arms horizontaly"), compound_flip_arms_cb,
224     GINT_TO_POINTER(2), DIAMENU_ACTIVE },
225   { N_("Center mount point verticaly"), compound_repos_mount_point_cb,
226     GINT_TO_POINTER (CENTER_VERTICAL), DIAMENU_ACTIVE },
227   { N_("Center mount point horizontaly"), compound_repos_mount_point_cb,
228     GINT_TO_POINTER (CENTER_HORIZONTAL), DIAMENU_ACTIVE },
229   { N_("Center mount point"), compound_repos_mount_point_cb,
230     GINT_TO_POINTER (CENTER_BOTH), DIAMENU_ACTIVE }
231 };
232 
233 static DiaMenu compound_menu = {
234   N_("Compound"),
235   sizeof (compound_menu_items) / sizeof (DiaMenuItem),
236   compound_menu_items,
237   NULL
238 };
239 
240 /* ------------------------------------------------------------------------ */
241 
242 static CompoundState *
compound_state_new(Compound * c)243 compound_state_new (Compound * c)
244 {
245   CompoundState * state;
246   DiaObject * obj;
247   gint i, num_handles;
248 
249   state = g_new0 (CompoundState, 1);
250   obj = &c->object;
251 
252   num_handles = c->object.num_handles;
253   state->num_handles = num_handles;
254   state->line_width = c->line_width;
255   state->line_color = c->line_color;
256   state->handle_states = g_new (ArmHandleState, num_handles);
257   for (i = 0; i < num_handles; i++)
258     {
259       state->handle_states[i].pos = obj->handles[i]->pos;
260       state->handle_states[i].connected_to = obj->handles[i]->connected_to;
261     }
262 
263   return state;
264 }
265 
266 static void
compound_state_set(CompoundState * state,Compound * comp)267 compound_state_set (CompoundState * state, Compound * comp)
268 {
269   gint i, num_handles;
270   Handle * h;
271   DiaObject * obj;
272   ArmHandleState * ahs;
273 
274   obj = &comp->object;
275   comp->line_width = state->line_width;
276   comp->line_color = state->line_color;
277   adjust_handle_count_to (comp, state->num_handles);
278   num_handles = comp->object.num_handles;
279   for (i = 0; i < num_handles; i++)
280     {
281       h = &comp->handles[i];
282       ahs = &state->handle_states[i];
283 
284       h->pos = ahs->pos;
285       if (h->connected_to != ahs->connected_to)
286         {
287           if (h->connected_to)
288             object_unconnect (obj, h);
289           if (ahs->connected_to)
290             object_connect (obj, h, ahs->connected_to);
291         }
292     }
293   comp->mount_point.pos = comp->handles[0].pos;
294   compound_update_data (comp);
295   compound_sanity_check (comp, "Restored state");
296 }
297 
298 static void
compound_state_free(CompoundState * state)299 compound_state_free (CompoundState * state)
300 {
301   g_free (state->handle_states);
302   g_free (state);
303 }
304 
305 static CompoundChange *
compound_change_new(Compound * comp,CompoundState * state)306 compound_change_new (Compound * comp, CompoundState * state)
307 {
308   CompoundChange * change;
309 
310   change = g_new (CompoundChange, 1);
311 
312   change->obj_change.apply = (ObjectChangeApplyFunc) compound_change_apply;
313   change->obj_change.revert = (ObjectChangeRevertFunc) compound_change_apply;
314   change->obj_change.free = (ObjectChangeFreeFunc) compound_change_free;
315 
316   change->obj = comp;
317   change->saved_state = state;
318 
319   return change;
320 }
321 
322 static void
compound_change_apply(CompoundChange * change,DiaObject * obj)323 compound_change_apply (CompoundChange * change, DiaObject * obj)
324 {
325   CompoundState * old_state;
326 
327   old_state = compound_state_new (change->obj);
328 
329   compound_state_set (change->saved_state, change->obj);
330   compound_state_free (change->saved_state);
331 
332   change->saved_state = old_state;
333 }
334 
335 static void
compound_change_free(CompoundChange * change)336 compound_change_free (CompoundChange * change)
337 {
338   compound_state_free (change->saved_state);
339 }
340 
341 static MountPointMoveChange *
mount_point_move_change_new(Compound * comp,Point * pos)342 mount_point_move_change_new (Compound * comp, Point * pos)
343 {
344   MountPointMoveChange * change;
345 
346   change = g_new (MountPointMoveChange, 1);
347   change->obj_change.apply =
348     (ObjectChangeApplyFunc) mount_point_move_change_apply;
349   change->obj_change.revert =
350     (ObjectChangeRevertFunc) mount_point_move_change_apply;
351   change->obj_change.free =
352     (ObjectChangeFreeFunc) mount_point_move_change_free;
353 
354   change->obj = comp;
355   change->saved_pos = *pos;
356 
357   return change;
358 }
359 
360 static void
mount_point_move_change_apply(MountPointMoveChange * change,DiaObject * obj)361 mount_point_move_change_apply (MountPointMoveChange * change, DiaObject * obj)
362 {
363   Compound * comp = change->obj;
364   Point old_pos = comp->handles[0].pos;
365 
366   comp->handles[0].pos = change->saved_pos;
367   comp->mount_point.pos = change->saved_pos;
368   compound_update_data (comp);
369 
370   change->saved_pos = old_pos;
371 
372   compound_sanity_check (comp, "After applying mount point move change");
373 }
374 
375 static void
mount_point_move_change_free(MountPointMoveChange * change)376 mount_point_move_change_free (MountPointMoveChange * change)
377 {
378   /* currently there is nothing to be done here */
379 }
380 
381 /* ------------------------------------------------------------------------ */
382 
383 static DiaObject *
compound_create(Point * start_point,void * user_data,Handle ** handle1,Handle ** handle2)384 compound_create (Point * start_point, void * user_data,
385                  Handle **handle1, Handle **handle2)
386 {
387   Compound * comp;
388   DiaObject * obj;
389   Handle * handle;
390   gint num_handles;
391   gint i;
392 
393   comp = g_new0 (Compound, 1);
394   obj = &comp->object;
395 
396   obj->type = &compound_type;
397   obj->ops = &compound_ops;
398 
399   comp->num_arms = DEFAULT_NUMARMS;
400   comp->line_width = attributes_get_default_linewidth ();
401   comp->line_color = attributes_get_foreground ();
402   /* init our mount-point */
403   setup_mount_point (&comp->mount_point, obj, start_point);
404 
405   num_handles = comp->num_arms + 1;
406   /* init the inherited object */
407   object_init (obj, num_handles, 1);
408   obj->connections[0] = &comp->mount_point;
409 
410   comp->handles = g_new0 (Handle, num_handles);
411   /* init the handle on the mount-point */
412   obj->handles[0] = &comp->handles[0];
413   setup_handle (obj->handles[0], HANDLE_MOUNT_POINT,
414                 HANDLE_MAJOR_CONTROL, HANDLE_NONCONNECTABLE);
415   /* now init the rest of the handles */
416   for (i = 1; i < num_handles; i++)
417     {
418       handle = &comp->handles[i];
419       obj->handles[i] = handle;
420       setup_handle (handle, HANDLE_ARM,
421                     HANDLE_MINOR_CONTROL, HANDLE_CONNECTABLE_NOBREAK);
422     }
423   init_default_handle_positions (comp);
424 
425   compound_update_data (comp);
426   compound_sanity_check (comp, "Created");
427 
428   *handle1 = &comp->handles[0];
429   *handle2 = &comp->handles[1];
430 
431   return &comp->object;
432 }
433 
434 static DiaObject *
compound_load(ObjectNode obj_node,int version,const char * filename)435 compound_load (ObjectNode obj_node, int version, const char *filename)
436 {
437   Compound * comp;
438   DiaObject * obj;
439   AttributeNode attr;
440   DataNode data;
441   gint i, num_handles;
442 
443   comp = g_new0 (Compound, 1);
444   obj = &comp->object;
445   /* load the objects position and bounding box */
446   object_load (obj, obj_node);
447   /* init the object */
448   obj->type = &compound_type;
449   obj->ops = &compound_ops;
450 
451   /* load the object's handles and its positions */
452   attr = object_find_attribute (obj_node, "comp_points");
453   g_assert (attr != NULL);
454   num_handles = attribute_num_data (attr);
455   g_assert (num_handles >= 3);
456   /* allocate space for object handles and connectionpoints point arrays */
457   object_init (obj, num_handles, 1);
458   data = attribute_first_data (attr);
459   /* init our mount_point */
460   setup_mount_point (&comp->mount_point, obj, NULL);
461   data_point (data, &comp->mount_point.pos);
462   obj->connections[0] = &comp->mount_point;
463   /* now init the handles */
464   comp->num_arms = num_handles-1;
465   comp->handles = g_new0 (Handle, num_handles);
466   setup_handle (&comp->handles[0], HANDLE_MOUNT_POINT,
467                 HANDLE_MAJOR_CONTROL, HANDLE_NONCONNECTABLE);
468   comp->handles[0].pos = comp->mount_point.pos;
469   obj->handles[0] = &comp->handles[0];
470   data = data_next (data);
471   for (i = 1; i < num_handles; i++)
472     {
473       obj->handles[i] = &comp->handles[i];
474       setup_handle (obj->handles[i], HANDLE_ARM,
475                     HANDLE_MINOR_CONTROL, HANDLE_CONNECTABLE_NOBREAK);
476       data_point (data, &obj->handles[i]->pos);
477       data = data_next (data);
478     }
479 
480   /* load remainding properties */
481   attr = object_find_attribute (obj_node, PROP_STDTYPE_LINE_WIDTH);
482   if (attr == NULL)
483     comp->line_width = 0.1;
484   else
485     comp->line_width = data_real (attribute_first_data (attr));
486   attr = object_find_attribute (obj_node, "line_colour");
487   if (attr == NULL)
488     comp->line_color = color_black;
489   else
490     data_color (attribute_first_data (attr), &comp->line_color);
491 
492   compound_update_data (comp);
493   compound_sanity_check (comp, "Loaded");
494   return &comp->object;
495 }
496 
497 static void
compound_save(Compound * comp,ObjectNode obj_node,const char * filename)498 compound_save (Compound *comp, ObjectNode obj_node, const char * filename)
499 {
500   gint i;
501   AttributeNode attr;
502   DiaObject * obj = &comp->object;
503 
504   compound_sanity_check (comp, "Saving");
505 
506   object_save (&comp->object, obj_node);
507 
508   attr = new_attribute(obj_node, "comp_points");
509   for (i = 0; i < obj->num_handles; i++)
510     {
511       Handle *h = obj->handles[i];
512       data_add_point (attr, &h->pos);
513     }
514 
515   attr = new_attribute (obj_node, PROP_STDNAME_LINE_WIDTH);
516   data_add_real (attr, comp->line_width);
517   attr = new_attribute (obj_node, "line_color");
518   data_add_color (attr, &comp->line_color);
519 }
520 
521 static void
compound_destroy(Compound * comp)522 compound_destroy (Compound * comp)
523 {
524   compound_sanity_check (comp, "Destroying");
525 
526   object_destroy (&comp->object);
527   g_free (comp->handles);
528 }
529 
530 static void
compound_draw(Compound * comp,DiaRenderer * renderer)531 compound_draw (Compound * comp, DiaRenderer * renderer)
532 {
533   DiaRendererClass * renderer_ops;
534   gint num_handles;
535   Point * mount_point_pos;
536   gint i;
537 
538   renderer_ops = DIA_RENDERER_GET_CLASS (renderer);
539   num_handles = comp->object.num_handles;
540   mount_point_pos = &comp->mount_point.pos;
541 
542   renderer_ops->set_linewidth (renderer, comp->line_width);
543 
544   for (i = 1; i < num_handles; i++)
545     {
546       renderer_ops->draw_line (renderer,
547                                mount_point_pos,
548                                &comp->handles[i].pos,
549                                &comp->line_color);
550 #if DEBUG_DRAW_BOUNDING_BOX
551  {
552    Point ul, br;
553    ul.x = comp->object.bounding_box.left;
554    ul.y = comp->object.bounding_box.top;
555    br.x = comp->object.bounding_box.right;
556    br.y = comp->object.bounding_box.bottom;
557 
558    renderer_ops->draw_rect (renderer, &ul, &br, &comp->line_color);
559  }
560 #endif
561     }
562 
563 #if DEBUG_DRAW_MP_DIRECTION
564  {
565    Point p = comp->mount_point.pos;
566    Color red = {1.0, 0.0, 0.0};
567    gchar dirs = comp->mount_point.directions;
568    if (dirs & DIR_NORTH)
569      p.y -= 1.0;
570    if (dirs & DIR_SOUTH)
571      p.y += 1.0;
572    if (dirs & DIR_WEST)
573      p.x -= 1.0;
574    if (dirs & DIR_EAST)
575      p.x += 1.0;
576 
577    renderer_ops->draw_line (renderer, &comp->mount_point.pos, &p, &red);
578  }
579 #endif
580 
581 }
582 
583 static real
compound_distance_from(Compound * comp,Point * point)584 compound_distance_from (Compound * comp, Point * point)
585 {
586   real dist = 0.0;
587   gint num_handles = comp->object.num_handles;
588   Point * mount_point_pos = &comp->mount_point.pos;
589   gint i;
590 
591   dist = distance_line_point (mount_point_pos,
592                               &comp->handles[1].pos,
593                               comp->line_width,
594                               point);
595   if (dist < 0.000001)
596     return 0.0;
597 
598   for (i = 2; i < num_handles; i++)
599     {
600       dist = MIN(distance_line_point (mount_point_pos,
601                                       &comp->handles[i].pos,
602                                       comp->line_width,
603                                       point),
604                  dist);
605       if (dist < 0.000001)
606         return 0.0;
607     }
608   return dist;
609 }
610 
611 static void
compound_select(Compound * comp,Point * clicked,DiaRenderer * interactive_renderer)612 compound_select (Compound * comp, Point * clicked,
613                  DiaRenderer * interactive_renderer)
614 {
615   compound_update_data (comp);
616 }
617 
618 static DiaObject *
compound_copy(Compound * comp)619 compound_copy (Compound * comp)
620 {
621   Compound * copy;
622   Handle * ch, * oh;
623   DiaObject * copy_obj, * comp_obj;
624   gint i, num_handles;
625 
626   comp_obj = &comp->object;
627   num_handles = comp->object.num_handles;
628 
629   g_assert (comp->num_arms >= 2);
630   g_assert (comp->num_arms+1 == num_handles);
631 
632   copy = g_new0 (Compound, 1);
633   copy_obj = &copy->object;
634   /* copy the properties */
635   copy->num_arms = comp->num_arms;
636   copy->line_width = comp->line_width;
637   copy->line_color = copy->line_color;
638 
639   /* this will allocate the object's pointer arrays for handles and
640      connection_points */
641   object_copy (comp_obj, copy_obj);
642   /* copy the handles */
643   copy->handles = g_new (Handle, num_handles);
644   for (i = 0; i < num_handles; i++)
645     {
646       ch = &copy->handles[i];
647       oh = &comp->handles[i];
648       setup_handle (ch, oh->id, oh->type, oh->connect_type);
649       ch->pos = oh->pos;
650       copy_obj->handles[i] = ch;
651     }
652   /* copy the connection/mount point */
653   copy_obj->connections[0] = &copy->mount_point;
654   setup_mount_point (copy_obj->connections[0],
655                      copy_obj,
656                      &copy_obj->handles[0]->pos);
657 
658   compound_update_data (comp);
659   compound_sanity_check (copy, "Copied");
660   return &copy->object;
661 }
662 
663 static ObjectChange *
compound_move(Compound * comp,Point * to)664 compound_move (Compound * comp, Point * to)
665 {
666   Point diff;
667   Handle * h;
668   gint i, num_handles;
669 
670   diff.x = to->x - comp->object.position.x;
671   diff.y = to->y - comp->object.position.y;
672 
673   num_handles = comp->object.num_handles;
674   for (i = 0; i < num_handles; i++)
675     {
676       h = &comp->handles[i];
677       point_add (&h->pos, &diff);
678     }
679   point_add (&comp->mount_point.pos, &diff);
680 
681   compound_update_data (comp);
682   return NULL;
683 }
684 
685 static ObjectChange *
compound_move_handle(Compound * comp,Handle * handle,Point * to,ConnectionPoint * cp,HandleMoveReason reason,ModifierKeys modifiers)686 compound_move_handle (Compound * comp, Handle * handle,
687                       Point * to, ConnectionPoint * cp,
688                       HandleMoveReason reason, ModifierKeys modifiers)
689 {
690   if (handle->id == HANDLE_MOUNT_POINT)
691     {
692       g_assert (handle == &comp->handles[0]);
693       comp->mount_point.pos = *to;
694     }
695   else
696     {
697       /** ATTENTION: the idea is that while the handles are connected to
698        *  an object and that object moves than this compound also moves
699        *  (as a whole)! unfortunatelly, this method gets called for each
700        *  handle connected and there is not method to register which
701        *  would be called after all the handles of this object has been
702        *  processed by this routine.  see also
703        *  app/connectionpoint_ops.c:diagram_update_connections_object
704        *
705        *  as a work around we say that when the first handle is moved,
706        *  only then we update the position of the mount_point. This
707        *  solves the problem for compounds whose handles (all) are
708        *  connected to some (even more than one) objects.
709        */
710       if (reason == HANDLE_MOVE_CONNECTED
711           && handle == &comp->handles[1])
712         {
713           Point diff = *to;
714           point_sub (&diff, &handle->pos);
715 
716           point_add (&comp->handles[0].pos, &diff);
717           point_add (&comp->mount_point.pos, &diff);
718         }
719     }
720   handle->pos = *to;
721   compound_update_data (comp);
722   return NULL;
723 }
724 
725 static ObjectChange *
compound_apply_properties_dialog(Compound * comp,GtkWidget * dialog_widget)726 compound_apply_properties_dialog (Compound * comp, GtkWidget * dialog_widget)
727 {
728   CompoundState * state;
729   PropDialog *dialog = prop_dialog_from_widget(dialog_widget);
730 
731   state = compound_state_new (comp);
732 
733   prop_get_data_from_widgets(dialog);
734   compound_apply_props (comp, dialog->props, FALSE);
735   return (ObjectChange *) compound_change_new (comp, state);
736 }
737 
738 static PropDescription *
compound_describe_props(Compound * comp)739 compound_describe_props (Compound * comp)
740 {
741   if (compound_props[0].quark == 0)
742     prop_desc_list_calculate_quarks (compound_props);
743   return compound_props;
744 }
745 
746 static void
compound_get_props(Compound * comp,GPtrArray * props)747 compound_get_props (Compound * comp, GPtrArray * props)
748 {
749   object_get_props_from_offsets(&comp->object, compound_offsets, props);
750 }
751 
752 /**
753  * Everything I know about is initialize so that this function get called
754  * only when the user is applying the default dialog.
755  */
756 static void
compound_set_props(Compound * comp,GPtrArray * props)757 compound_set_props (Compound * comp, GPtrArray * props)
758 {
759   compound_apply_props (comp, props, TRUE);
760 }
761 
762 static void
compound_apply_props(Compound * comp,GPtrArray * props,gboolean is_default)763 compound_apply_props (Compound * comp, GPtrArray * props, gboolean is_default)
764 {
765   gint change_count;
766   object_set_props_from_offsets (&comp->object, compound_offsets, props);
767   /* comp->num_arms has already been set by
768      the call to object_set_props_from_offsets () */
769   change_count = adjust_handle_count_to (comp, comp->num_arms+1);
770   if (change_count)
771     {
772       /* if there has been some arms added to the default object
773          reinitialize all handles' position */
774       if (change_count > 0)
775         {
776           if (is_default)
777             init_default_handle_positions (comp);
778           else
779             {
780               gint new_index = comp->object.num_handles - change_count;
781               init_positions_for_handles_beginning_at_index (comp, new_index);
782             }
783         }
784     }
785   compound_update_data (comp);
786   compound_sanity_check (comp, "After setting properties");
787 }
788 
789 
790 /**
791  * Determine the bounding box of the compound object and store it in the
792  * object. Also update the object's position to the upper left corner of
793  * the bounding box.
794  */
compound_update_object(Compound * comp)795 static void compound_update_object (Compound * comp)
796 {
797   Rectangle * bb;
798   Handle * h;
799   gint i;
800   gint num_handles = comp->object.num_handles;
801 
802   h = &comp->handles[0];
803   bb = &comp->object.bounding_box;
804   bb->right = bb->left = h->pos.x;
805   bb->top = bb->bottom = h->pos.y;
806   for (i = 1; i < num_handles; i++)
807     {
808       h = &comp->handles[i];
809       bb->left = MIN (h->pos.x, bb->left);
810       bb->right = MAX(h->pos.x, bb->right);
811       bb->top = MIN(h->pos.y, bb->top);
812       bb->bottom = MAX(h->pos.y, bb->bottom);
813     }
814   comp->object.position.x = bb->left;
815   comp->object.position.y = bb->top;
816 
817 #if DEBUG_DRAW_MP_DIRECTION
818   /* make the bounding box a bit larger because drawing the direction of
819    * the mount point can go beyond the current bounding box.
820    */
821   bb->left -= 1.0;
822   bb->right += 1.0;
823   bb->top -= 1.0;
824   bb->bottom += 1.0;
825 #endif
826 }
827 
828 /**
829  * Add or remove handles, so that comp->object.num_handles will equal to
830  * the specified value. When there have been no changes done 0 is
831  * returned, otherwise the number of handles removed (negative value) or
832  * the number of added handles (positive value) will be returned.
833  */
834 static gint
adjust_handle_count_to(Compound * comp,gint to)835 adjust_handle_count_to (Compound * comp, gint to)
836 {
837   DiaObject * obj = &comp->object;
838   gint old_count = obj->num_handles;
839   gint new_count = to;
840   Handle * h;
841   gint i;
842   gint diff = 0;
843 
844   /* we require to have always at least two arms! */
845   g_assert (new_count >= 3);
846 
847   if (new_count == old_count)
848     return diff; /* every thing is ok */
849 
850   obj->handles = g_realloc (obj->handles, new_count * sizeof(Handle *));
851   obj->num_handles = new_count;
852   comp->num_arms = new_count-1;
853 
854   if (new_count < old_count) /* removing */
855     {
856       for (i = new_count; i < old_count; i++)
857         object_unconnect (obj, &comp->handles[i]);
858       comp->handles = g_realloc (comp->handles, sizeof(Handle) * new_count);
859     }
860   else /* adding */
861     {
862       comp->handles = g_realloc (comp->handles, sizeof(Handle) * new_count);
863       for (i = old_count; i < new_count; i++)
864         {
865           h = &comp->handles[i];
866           setup_handle (h, HANDLE_ARM,
867                         HANDLE_MINOR_CONTROL, HANDLE_CONNECTABLE_NOBREAK);
868         }
869     }
870   for (i = 0; i < new_count; i++)
871     obj->handles[i] = &comp->handles[i];
872   return new_count - old_count;
873 }
874 
875 static void
setup_mount_point(ConnectionPoint * mp,DiaObject * obj,Point * pos)876 setup_mount_point (ConnectionPoint * mp, DiaObject * obj, Point * pos)
877 {
878   if (pos != NULL)
879     mp->pos = *pos;
880   mp->object = obj;
881   mp->connected = NULL;
882   mp->directions = DIR_ALL;
883   mp->flags = 0;
884 }
885 
886 static void
setup_handle(Handle * h,HandleId id,HandleType type,HandleConnectType ctype)887 setup_handle (Handle *h, HandleId id, HandleType type, HandleConnectType ctype)
888 {
889   g_assert (h != NULL);
890 
891   h->id = id;
892   h->type = type;
893   h->pos.x = h->pos.y = 0.0;
894   h->connect_type = ctype;
895   h->connected_to = NULL;
896 }
897 
898 /*
899  * Update the directions fields of our mount point, so that when an
900  * object is connected to it, let's say an OrthConn object, the
901  * connected object will automatically be placed at that side which
902  * contains no arms. If there is no free space left, DIR_ALL is assigned
903  * which allows new object to connect from any side.
904  */
905 static void
update_mount_point_directions(Compound * c)906 update_mount_point_directions (Compound *c)
907 {
908   Point *p;
909   Point mp_pos;
910   gint i, below_index;
911   gchar used_dir = DIR_NONE;
912 
913   below_index = c->object.num_handles;
914   mp_pos = c->mount_point.pos;
915   for (i = 1; i < below_index; i++)
916     {
917       p = &c->object.handles[i]->pos;
918       used_dir |= (p->x <= mp_pos.x) ? DIR_WEST : DIR_EAST;
919       used_dir |= (p->y <= mp_pos.y) ? DIR_NORTH : DIR_SOUTH;
920     }
921 
922   used_dir = (used_dir ^ DIR_ALL)&DIR_ALL;
923   if ((used_dir&DIR_ALL) == DIR_NONE)
924     used_dir = DIR_ALL;
925   c->mount_point.directions = used_dir;
926 }
927 
928 /**
929  * Reposition the handles to a default position. The handles will be on
930  * one vertical line equally distributed to the left of the mount point.
931  * The first handle will be set to the position of the mount_point! The
932  * mount point's position must be initialized before this function is
933  * called.
934  */
935 static void
init_default_handle_positions(Compound * c)936 init_default_handle_positions (Compound *c)
937 {
938   Handle * h;
939   Point run_point;
940   gint i, num_handles;
941 
942   num_handles = c->object.num_handles;
943   h = c->object.handles[0];
944   h->pos = c->mount_point.pos;
945   run_point = h->pos;
946   /* XXX: as you can see the code positions the points on a vertical line
947      to the left of the mount point; if you like to change this ...
948      here is the right place to do so.
949    */
950   run_point.x -= DEFAULT_ARM_X_DISTANCE;
951   run_point.y -= ((num_handles-2)*DEFAULT_ARM_Y_DISTANCE)/2.0;
952   for (i = 1; i < num_handles; i++)
953     {
954       h = c->object.handles[i];
955       h->pos = run_point;
956       run_point.y += DEFAULT_ARM_Y_DISTANCE;
957     }
958 }
959 
960 /**
961  * This routine will initialize the position of handles with index >=
962  * HINDEX.
963  */
964 static void
init_positions_for_handles_beginning_at_index(Compound * c,gint hindex)965 init_positions_for_handles_beginning_at_index (Compound *c, gint hindex)
966 {
967   Handle * h;
968   Point tmp;
969   gint i, num_handles, num_new_gaps;
970   real x_inc;
971   real y_inc;
972 
973   num_handles = c->object.num_handles;
974   g_assert (hindex < num_handles);
975   num_new_gaps = (num_handles - hindex)-1;
976 
977   /* XXX: the following is too simple! there is no code for checking the
978      new position not colliding with any other line of this object.
979      however, it is sufficient for now, but feel free to improve this
980      code if you would like to do so.
981   */
982 
983   tmp = c->mount_point.pos;
984   switch (c->mount_point.directions)
985     {
986     case DIR_NORTH:
987       tmp.y -= DEFAULT_ARM_Y_DISTANCE;
988       tmp.x -= (DEFAULT_ARM_X_DISTANCE*num_new_gaps)/2.0;
989       x_inc = DEFAULT_ARM_X_DISTANCE;
990       y_inc = 0.0;
991       break;
992     case DIR_EAST:
993       tmp.x += DEFAULT_ARM_X_DISTANCE;
994       tmp.y -= (DEFAULT_ARM_Y_DISTANCE*num_new_gaps)/2.0;
995       x_inc = 0.0;
996       y_inc = DEFAULT_ARM_Y_DISTANCE;
997       break;
998     case DIR_SOUTH:
999       tmp.y += DEFAULT_ARM_Y_DISTANCE;
1000       tmp.x -= (DEFAULT_ARM_X_DISTANCE*num_new_gaps)/2.0;
1001       x_inc = DEFAULT_ARM_X_DISTANCE;
1002       y_inc = 0.0;
1003       break;
1004     case DIR_WEST:
1005       tmp.x -= DEFAULT_ARM_X_DISTANCE;
1006       tmp.y -= (DEFAULT_ARM_Y_DISTANCE*num_new_gaps)/2.0;
1007       x_inc = 0.0;
1008       y_inc = DEFAULT_ARM_Y_DISTANCE;
1009       break;
1010     default:
1011       /* XXX this could be more intelligent */
1012       x_inc = DEFAULT_ARM_X_DISTANCE;
1013       y_inc = DEFAULT_ARM_Y_DISTANCE;
1014       tmp.x += x_inc;
1015       tmp.y += y_inc;
1016       break;
1017     }
1018 
1019   for (i = hindex; i < num_handles; i++)
1020     {
1021       h = c->object.handles[i];
1022       h->pos = tmp;
1023       tmp.x += x_inc;
1024       tmp.y += y_inc;
1025     }
1026 }
1027 
1028 /**
1029  * Perform the default updating of dynamic data. In particular this is:
1030  * - update the object's position and bounding box depending on the
1031  *   handles position.
1032  *
1033  * - make sure that the compound's num_arms correctly corresponds to the
1034  *   object's number of handles. If not create or remove handles.
1035  *
1036  * - update the mount points directions field depending on the position
1037  *   of the object's handles.
1038  */
1039 static void
compound_update_data(Compound * c)1040 compound_update_data (Compound *c)
1041 {
1042   /* make sure the number of handles is correct */
1043   adjust_handle_count_to (c, c->num_arms+1);
1044   /* this will update the object's position and bounding box */
1045   compound_update_object (c);
1046   /* this will compute the mount point's directions field depending on
1047      the position of the handles */
1048   update_mount_point_directions (c);
1049 }
1050 
1051 static DiaMenu *
compound_object_menu(DiaObject * obj,Point * p)1052 compound_object_menu(DiaObject *obj, Point *p)
1053 {
1054   Compound * comp = (Compound *) obj;
1055   gchar dir = comp->mount_point.directions;
1056 
1057   if (dir == DIR_ALL)
1058     {
1059       compound_menu_items[0].active = 0;
1060       compound_menu_items[1].active = 0;
1061     }
1062   else
1063     {
1064       compound_menu_items[0].active = ((dir & DIR_NORTH) == DIR_NORTH
1065                                        || ((dir & DIR_SOUTH) == DIR_SOUTH))
1066         ? DIAMENU_ACTIVE
1067         : 0;
1068       compound_menu_items[1].active = ((dir & DIR_WEST) == DIR_WEST
1069                                        || (dir & DIR_EAST) == DIR_EAST)
1070         ? DIAMENU_ACTIVE
1071         : 0;
1072     }
1073 
1074   return &compound_menu;
1075 }
1076 
1077 static ObjectChange *
compound_flip_arms_cb(DiaObject * obj,Point * pos,gpointer data)1078 compound_flip_arms_cb (DiaObject *obj, Point *pos, gpointer data)
1079 {
1080   Compound * comp = (Compound *) obj;
1081   gint direction = GPOINTER_TO_INT (data);
1082   Point * mppos = &comp->mount_point.pos;
1083   CompoundState * state;
1084   Handle * h;
1085   Point * p;
1086   gint num_handles, i;
1087 
1088   state = compound_state_new (comp);
1089 
1090   num_handles = obj->num_handles;
1091   for (i = 1; i < num_handles; i++)
1092     {
1093       h = obj->handles[i];
1094       object_unconnect (obj, h);
1095       p = &h->pos;
1096       if (direction == FLIP_VERTICAL)
1097         {
1098           p->y -= mppos->y;
1099           p->y *= -1.0;
1100           p->y += mppos->y;
1101         }
1102       else
1103         {
1104           p->x -= mppos->x;
1105           p->x *= -1.0;
1106           p->x += mppos->x;
1107         }
1108     }
1109 
1110   compound_update_data (comp);
1111   compound_sanity_check (comp, "After flipping sides");
1112   return (ObjectChange *) compound_change_new (comp, state);
1113 }
1114 
1115 static ObjectChange *
compound_repos_mount_point_cb(DiaObject * obj,Point * clicked,gpointer data)1116 compound_repos_mount_point_cb (DiaObject * obj, Point *clicked, gpointer data)
1117 {
1118   Compound * comp = (Compound *) obj;
1119   Handle * h;
1120   Point old_pos;
1121   Point pos;
1122   gint what_todo;
1123   gint i, num_handles;
1124 
1125   old_pos = comp->mount_point.pos;
1126   what_todo = GPOINTER_TO_INT (data);
1127 
1128   h = comp->object.handles[1];
1129   pos = h->pos;
1130   num_handles = comp->object.num_handles;
1131   for (i = 2; i < num_handles; i++)
1132     {
1133       h = comp->object.handles[i];
1134       point_add (&pos, &h->pos);
1135     }
1136   switch (what_todo)
1137     {
1138     case CENTER_BOTH:
1139       pos.x /= (real) (num_handles - 1);
1140       pos.y /= (real) (num_handles - 1);
1141       break;
1142     case CENTER_VERTICAL:
1143       pos.y /= (real) (num_handles - 1);
1144       pos.x = comp->handles[0].pos.x;
1145       break;
1146     case CENTER_HORIZONTAL:
1147       pos.x /= (real) (num_handles - 1);
1148       pos.y = comp->handles[0].pos.y;
1149       break;
1150     default:
1151       g_assert (FALSE);
1152     }
1153 
1154   comp->handles[0].pos = pos;
1155   comp->mount_point.pos = pos;
1156   compound_update_data (comp);
1157 
1158   return (ObjectChange *)mount_point_move_change_new (comp, &old_pos);
1159 }
1160 
1161 /**
1162  * The only purpose is to check whether the passed compound object is in
1163  * a consitent state. If it's not, abort the program with a message!
1164  */
1165 static void
compound_sanity_check(Compound * c,gchar * msg)1166 compound_sanity_check (Compound * c, gchar *msg)
1167 {
1168   DiaObject * obj;
1169   Point * ph, * pc;
1170   gint i;
1171 
1172   obj = &c->object;
1173 
1174   dia_object_sanity_check (obj, msg);
1175 
1176   dia_assert_true (obj->num_connections == 1,
1177                    "%s: Compound %p has not exactly one connection but %d!\n",
1178                    msg, c, obj->num_connections);
1179 
1180   dia_assert_true (obj->connections[0] == &c->mount_point,
1181                    "%s: Compound %p connection mismatch %p != %p!\n",
1182                    msg, c, obj->connections[0], &c->mount_point);
1183 
1184   dia_assert_true (obj->num_handles >= 3,
1185                    "%s: Object %p has only %d handles, but at least %d are required!\n",
1186                    msg, c, obj->num_handles, 3);
1187 
1188   dia_assert_true (obj->num_handles == (c->num_arms+1),
1189                    "%s: Compound %p has %d handles and %d arms. The number of arms must be the number of handles decreased by one!\n",
1190                    msg, c, obj->num_handles, c->num_arms);
1191 
1192   for (i = 0; i < obj->num_handles; i++)
1193     dia_assert_true (obj->handles[i] == &c->handles[i],
1194                      "%s: Compound %p handles mismatch at %d: %p != %p!\n",
1195                      msg, c, i, obj->handles[i], &c->handles[i]);
1196 
1197   ph = &obj->handles[0]->pos;
1198   pc = &c->mount_point.pos;
1199   dia_assert_true (ph->x == pc->x && ph->y == pc->y,
1200                    "%s: Compound %p handle[0]/mount_point position mismatch: (%f, %f) != (%f, %f)!\n",
1201                    msg, c, ph->x, ph->y, pc->x, pc->y);
1202 }
1203