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 = ©->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 = ©->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] = ©->mount_point;
654 setup_mount_point (copy_obj->connections[0],
655 copy_obj,
656 ©_obj->handles[0]->pos);
657
658 compound_update_data (comp);
659 compound_sanity_check (copy, "Copied");
660 return ©->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