Docs!
[dia.git] / lib / bezier_conn.c
blob5d401efa497a0eadfaee2030d9f3cc55472ec8e6
1 /* Dia -- an diagram creation/manipulation program
2 * Copyright (C) 1999 Alexander Larsson
4 * BezierConn Copyright (C) 1999 James Henstridge
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
21 #include <config.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <math.h>
26 #include <string.h> /* memcpy() */
28 #include "bezier_conn.h"
29 #include "intl.h"
30 #include "message.h"
31 #include "diarenderer.h"
33 #define HANDLE_BEZMAJOR (HANDLE_CUSTOM1)
34 #define HANDLE_LEFTCTRL (HANDLE_CUSTOM2)
35 #define HANDLE_RIGHTCTRL (HANDLE_CUSTOM3)
37 enum change_type {
38 TYPE_ADD_POINT,
39 TYPE_REMOVE_POINT
42 struct PointChange {
43 ObjectChange obj_change;
45 enum change_type type;
46 int applied;
48 BezPoint point;
49 BezCornerType corner_type;
50 int pos;
51 /* owning ref when not applied for ADD_POINT
52 * owning ref when applied for REMOVE_POINT */
53 Handle *handle1, *handle2, *handle3;
54 /* NULL if not connected */
55 ConnectionPoint *connected_to1, *connected_to2, *connected_to3;
58 struct CornerChange {
59 ObjectChange obj_change;
60 /* Only one kind of corner_change */
61 int applied;
63 Handle *handle;
64 /* Old places when SET_CORNER_TYPE is applied */
65 Point point_left, point_right;
66 BezCornerType old_type, new_type;
69 static ObjectChange *
70 bezierconn_create_point_change(BezierConn *bez, enum change_type type,
71 BezPoint *point, BezCornerType corner_type,
72 int segment,
73 Handle *handle1, ConnectionPoint *connected_to1,
74 Handle *handle2, ConnectionPoint *connected_to2,
75 Handle *handle3, ConnectionPoint *connected_to3);
76 static ObjectChange *
77 bezierconn_create_corner_change(BezierConn *bez, Handle *handle,
78 Point *point_left, Point *point_right,
79 BezCornerType old_corner_type,
80 BezCornerType new_corner_type);
82 /** Set up a handle for any part of a bezierconn
83 * @param handle A handle to set up.
84 * @param id Handle id (HANDLE_BEZMAJOR or HANDLE_BEZMINOER)
86 static void
87 setup_handle(Handle *handle, HandleId id)
89 handle->id = id;
90 handle->type = HANDLE_MINOR_CONTROL;
91 handle->connect_type = (id == HANDLE_BEZMAJOR) ?
92 HANDLE_CONNECTABLE : HANDLE_NONCONNECTABLE;
93 handle->connected_to = NULL;
96 /** Get the number in the array of handles that a given handle has.
97 * @param bez A bezierconn object with handles set up.
98 * @param handle A handle object.
99 * @returns The index in bex->object.handles of the handle object, or -1 if
100 * `handle' is not in the array.
102 static int
103 get_handle_nr(BezierConn *bez, Handle *handle)
105 int i = 0;
106 for (i=0;i<bez->object.num_handles;i++) {
107 if (bez->object.handles[i] == handle)
108 return i;
110 return -1;
113 void new_handles(BezierConn *bez, int num_points);
115 #define get_comp_nr(hnum) (((int)(hnum)+2)/3)
116 #define get_major_nr(hnum) (((int)(hnum)+1)/3)
118 /** Function called to move one of the handles associated with the
119 * bezierconn.
120 * @param obj The object whose handle is being moved.
121 * @param handle The handle being moved.
122 * @param pos The position it has been moved to (corrected for
123 * vertical/horizontal only movement).
124 * @param cp If non-NULL, the connectionpoint found at this position.
125 * If @a cp is NULL, there may or may not be a connectionpoint.
126 * @param reason ignored
127 * @param modifiers ignored
128 * @return NULL
130 ObjectChange*
131 bezierconn_move_handle(BezierConn *bez, Handle *handle,
132 Point *to, ConnectionPoint *cp,
133 HandleMoveReason reason, ModifierKeys modifiers)
135 int handle_nr, comp_nr;
136 Point delta, pt;
138 delta = *to;
139 point_sub(&delta, &handle->pos);
141 handle_nr = get_handle_nr(bez, handle);
142 comp_nr = get_comp_nr(handle_nr);
143 switch(handle->id) {
144 case HANDLE_MOVE_STARTPOINT:
145 bez->points[0].p1 = *to;
146 /* shift adjacent point */
147 point_add(&bez->points[1].p1, &delta);
148 break;
149 case HANDLE_MOVE_ENDPOINT:
150 bez->points[bez->numpoints-1].p3 = *to;
151 /* shift adjacent point */
152 point_add(&bez->points[bez->numpoints-1].p2, &delta);
153 break;
154 case HANDLE_BEZMAJOR:
155 bez->points[comp_nr].p3 = *to;
156 /* shift adjacent point */
157 point_add(&bez->points[comp_nr].p2, &delta);
158 point_add(&bez->points[comp_nr+1].p1, &delta);
159 break;
160 case HANDLE_LEFTCTRL:
161 bez->points[comp_nr].p2 = *to;
162 if (comp_nr < bez->numpoints - 1) {
163 switch (bez->corner_types[comp_nr]) {
164 case BEZ_CORNER_SYMMETRIC:
165 pt = bez->points[comp_nr].p3;
166 point_sub(&pt, &bez->points[comp_nr].p2);
167 point_add(&pt, &bez->points[comp_nr].p3);
168 bez->points[comp_nr+1].p1 = pt;
169 break;
170 case BEZ_CORNER_SMOOTH: {
171 real len;
172 pt = bez->points[comp_nr+1].p1;
173 point_sub(&pt, &bez->points[comp_nr].p3);
174 len = point_len(&pt);
175 pt = bez->points[comp_nr].p2;
176 point_sub(&pt, &bez->points[comp_nr].p3);
177 if (point_len(&pt) > 0)
178 point_normalize(&pt);
179 else { pt.x = 1.0; pt.y = 0.0; }
180 point_scale(&pt, -len);
181 point_add(&pt, &bez->points[comp_nr].p3);
182 bez->points[comp_nr+1].p1 = pt;
183 break;
185 case BEZ_CORNER_CUSP:
186 /* Do nothing to the other guy */
187 break;
190 break;
191 case HANDLE_RIGHTCTRL:
192 bez->points[comp_nr].p1 = *to;
193 if (comp_nr > 1) {
194 switch (bez->corner_types[comp_nr-1]) {
195 case BEZ_CORNER_SYMMETRIC:
196 pt = bez->points[comp_nr - 1].p3;
197 point_sub(&pt, &bez->points[comp_nr].p1);
198 point_add(&pt, &bez->points[comp_nr - 1].p3);
199 bez->points[comp_nr-1].p2 = pt;
200 break;
201 case BEZ_CORNER_SMOOTH: {
202 real len;
203 pt = bez->points[comp_nr-1].p2;
204 point_sub(&pt, &bez->points[comp_nr-1].p3);
205 len = point_len(&pt);
206 pt = bez->points[comp_nr].p1;
207 point_sub(&pt, &bez->points[comp_nr-1].p3);
208 if (point_len(&pt) > 0)
209 point_normalize(&pt);
210 else { pt.x = 1.0; pt.y = 0.0; }
211 point_scale(&pt, -len);
212 point_add(&pt, &bez->points[comp_nr-1].p3);
213 bez->points[comp_nr-1].p2 = pt;
214 break;
216 case BEZ_CORNER_CUSP:
217 /* Do nothing to the other guy */
218 break;
221 break;
222 default:
223 message_error("Internal error in bezierconn_move_handle.\n");
224 break;
226 return NULL;
230 /** Function called to move the entire object.
231 * @param obj The object being moved.
232 * @param pos Where the object is being moved to. This is the first point
233 * of the points array.
234 * @return NULL
236 ObjectChange*
237 bezierconn_move(BezierConn *bez, Point *to)
239 Point p;
240 int i;
242 p = *to;
243 point_sub(&p, &bez->points[0].p1);
245 bez->points[0].p1 = *to;
246 for (i = 1; i < bez->numpoints; i++) {
247 point_add(&bez->points[i].p1, &p);
248 point_add(&bez->points[i].p2, &p);
249 point_add(&bez->points[i].p3, &p);
251 return NULL;
255 /** Return the segment of the bezierconn closest to a given point.
256 * @param bez The bezierconn object
257 * @param point A point to find the closest segment to.
258 * @param line_width Line width of the bezier line.
259 * @returns The index of the segment closest to point.
262 bezierconn_closest_segment(BezierConn *bez, Point *point, real line_width)
264 Point last;
265 int i;
266 real dist = G_MAXDOUBLE;
267 int closest;
269 closest = 0;
270 last = bez->points[0].p1;
271 for (i = 0; i < bez->numpoints - 1; i++) {
272 real new_dist = distance_bez_seg_point(&last, &bez->points[i+1].p1,
273 &bez->points[i+1].p2, &bez->points[i+1].p3,
274 line_width, point);
275 if (new_dist < dist) {
276 dist = new_dist;
277 closest = i;
279 last = bez->points[i+1].p3;
281 return closest;
284 /** Return the handle closest to a given point.
285 * @param bez A bezierconn object
286 * @param point A point to find distances from
287 * @returns The handle on `bez' closest to `point'.
288 * @bugs Why isn't this just a function on object that scans the handles?
290 Handle *
291 bezierconn_closest_handle(BezierConn *bez, Point *point)
293 int i, hn;
294 real dist;
295 Handle *closest;
297 closest = bez->object.handles[0];
298 dist = distance_point_point( point, &closest->pos);
299 for (i = 1, hn = 1; i < bez->numpoints; i++, hn++) {
300 real new_dist;
302 new_dist = distance_point_point(point, &bez->points[i].p1);
303 if (new_dist < dist) {
304 dist = new_dist;
305 closest = bez->object.handles[hn];
307 hn++;
309 new_dist = distance_point_point(point, &bez->points[i].p2);
310 if (new_dist < dist) {
311 dist = new_dist;
312 closest = bez->object.handles[hn];
314 hn++;
316 new_dist = distance_point_point(point, &bez->points[i].p3);
317 if (new_dist < dist) {
318 dist = new_dist;
319 closest = bez->object.handles[hn];
322 return closest;
325 /** Retrun the major handle for the control point with the handle closest to
326 * a given point.
327 * @param bez A bezier connection
328 * @param point A point
329 * @returns The major (middle) handle of the bezier control that has the
330 * handle closest to point.
331 * @bugs Don't we really want the major handle that's actually closest to
332 * the point? This is used in connection with object menus and could cause
333 * some unexpected selection of handles if a different segment has a control
334 * point close to the major handle.
336 Handle *
337 bezierconn_closest_major_handle(BezierConn *bez, Point *point)
339 Handle *closest = bezierconn_closest_handle(bez, point);
341 return bez->object.handles[3*get_major_nr(get_handle_nr(bez, closest))];
344 /** Return the distance from a bezier to a point.
345 * @param bez A bezierconn object.
346 * @param point A point to compare with.
347 * @param line_width The line width of the bezier line.
348 * @returns The shortest distance from the point to any part of the bezier.
350 real
351 bezierconn_distance_from(BezierConn *bez, Point *point, real line_width)
353 return distance_bez_line_point(bez->points, bez->numpoints,
354 line_width, point);
357 /** Add a trio of handles to a bezier.
358 * @param bez The bezierconn having handles added.
359 * @param pos Where in the list of segments to add the handle
360 * @param point The bezier point to add. This should already be initialized
361 * with sensible positions.
362 * @param corner_type What kind of corner this bezpoint should be.
363 * @param handle1 The handle that will be put on the bezier line.
364 * @param handle2 The handle that will be put before handle1
365 * @param handle3 The handle that will be put after handle1
366 * @bugs check that the handle ordering is correctly described.
368 static void
369 add_handles(BezierConn *bez, int pos, BezPoint *point,
370 BezCornerType corner_type, Handle *handle1,
371 Handle *handle2, Handle *handle3)
373 int i;
374 DiaObject *obj;
376 g_assert(pos > 0);
378 obj = (DiaObject *)bez;
379 bez->numpoints++;
380 bez->points = g_realloc(bez->points, bez->numpoints*sizeof(BezPoint));
381 bez->corner_types = g_realloc(bez->corner_types,
382 bez->numpoints * sizeof(BezCornerType));
384 for (i = bez->numpoints-1; i > pos; i--) {
385 bez->points[i] = bez->points[i-1];
386 bez->corner_types[i] = bez->corner_types[i-1];
388 bez->points[pos] = *point;
389 bez->points[pos].p1 = bez->points[pos+1].p1;
390 bez->points[pos+1].p1 = point->p1;
391 bez->corner_types[pos] = corner_type;
392 object_add_handle_at(obj, handle1, 3*pos-2);
393 object_add_handle_at(obj, handle2, 3*pos-1);
394 object_add_handle_at(obj, handle3, 3*pos);
396 if (pos==bez->numpoints-1) {
397 obj->handles[obj->num_handles-4]->type = HANDLE_MINOR_CONTROL;
398 obj->handles[obj->num_handles-4]->id = HANDLE_BEZMAJOR;
402 /** Remove a trio of handles from a bezierconn.
403 * @param bez The bezierconn to remove handles from.
404 * @param pos The position in the bezierpoint array to remove handles and
405 * bezpoint at.
407 static void
408 remove_handles(BezierConn *bez, int pos)
410 int i;
411 DiaObject *obj;
412 Handle *old_handle1, *old_handle2, *old_handle3;
413 Point tmppoint;
415 g_assert(pos > 0);
417 obj = (DiaObject *)bez;
419 if (pos==obj->num_handles-1) {
420 obj->handles[obj->num_handles-4]->type = HANDLE_MAJOR_CONTROL;
421 obj->handles[obj->num_handles-4]->id = HANDLE_MOVE_ENDPOINT;
424 /* delete the points */
425 bez->numpoints--;
426 tmppoint = bez->points[pos].p1;
427 for (i = pos; i < bez->numpoints; i++) {
428 bez->points[i] = bez->points[i+1];
429 bez->corner_types[i] = bez->corner_types[i+1];
431 bez->points[pos].p1 = tmppoint;
432 bez->points = g_realloc(bez->points, bez->numpoints*sizeof(BezPoint));
433 bez->corner_types = g_realloc(bez->corner_types,
434 bez->numpoints * sizeof(BezCornerType));
436 old_handle1 = obj->handles[3*pos-2];
437 old_handle2 = obj->handles[3*pos-1];
438 old_handle3 = obj->handles[3*pos];
439 object_remove_handle(&bez->object, old_handle1);
440 object_remove_handle(&bez->object, old_handle2);
441 object_remove_handle(&bez->object, old_handle3);
445 /** Add a point by splitting segment into two, putting the new point at
446 * 'point' or, if NULL, in the middle. This function will attempt to come
447 * up with reasonable placements for the control points.
448 * @param bez The bezierconn to add the segment to.
449 * @param segment Which segment to split.
450 * @param point Where to put the new corner, or NULL if undetermined.
451 * @returns An ObjectChange object with undo information for the split.
453 ObjectChange *
454 bezierconn_add_segment(BezierConn *bez, int segment, Point *point)
456 BezPoint realpoint;
457 BezCornerType corner_type = BEZ_CORNER_SYMMETRIC;
458 Handle *new_handle1, *new_handle2, *new_handle3;
459 Point startpoint;
461 if (segment == 0)
462 startpoint = bez->points[0].p1;
463 else
464 startpoint = bez->points[segment].p3;
466 if (point == NULL) {
467 realpoint.p1.x = (startpoint.x + bez->points[segment+1].p3.x) / 6;
468 realpoint.p1.y = (startpoint.y + bez->points[segment+1].p3.y) / 6;
469 realpoint.p2.x = (startpoint.x + bez->points[segment+1].p3.x) / 3;
470 realpoint.p2.y = (startpoint.y + bez->points[segment+1].p3.y) / 3;
471 realpoint.p3.x = (startpoint.x + bez->points[segment+1].p3.x) / 2;
472 realpoint.p3.y = (startpoint.y + bez->points[segment+1].p3.y) / 2;
473 } else {
474 realpoint.p2.x = point->x+(startpoint.x - bez->points[segment+1].p3.x)/6;
475 realpoint.p2.y = point->y+(startpoint.y - bez->points[segment+1].p3.y)/6;
476 realpoint.p3 = *point;
477 /* this really goes into the next segment ... */
478 realpoint.p1.x = point->x-(startpoint.x - bez->points[segment+1].p3.x)/6;
479 realpoint.p1.y = point->y-(startpoint.y - bez->points[segment+1].p3.y)/6;
481 realpoint.type = BEZ_CURVE_TO;
483 new_handle1 = g_malloc(sizeof(Handle));
484 new_handle2 = g_malloc(sizeof(Handle));
485 new_handle3 = g_malloc(sizeof(Handle));
486 setup_handle(new_handle1, HANDLE_RIGHTCTRL);
487 setup_handle(new_handle2, HANDLE_LEFTCTRL);
488 setup_handle(new_handle3, HANDLE_BEZMAJOR);
489 add_handles(bez, segment+1, &realpoint, corner_type,
490 new_handle1, new_handle2, new_handle3);
491 return bezierconn_create_point_change(bez, TYPE_ADD_POINT,
492 &realpoint, corner_type, segment+1,
493 new_handle1, NULL,
494 new_handle2, NULL,
495 new_handle3, NULL);
498 /** Remove a segment from a bezierconn.
499 * @param bez The bezierconn to remove a segment from.
500 * @param pos The index of the segment to remove.
501 * @returns Undo information for the segment removal.
503 ObjectChange *
504 bezierconn_remove_segment(BezierConn *bez, int pos)
506 Handle *old_handle1, *old_handle2, *old_handle3;
507 ConnectionPoint *cpt1, *cpt2, *cpt3;
508 BezPoint old_point;
509 BezCornerType old_ctype;
511 g_assert(pos > 0);
512 g_assert(bez->numpoints > 2);
514 if (pos == bez->numpoints-1) pos--;
516 old_handle1 = bez->object.handles[3*pos-2];
517 old_handle2 = bez->object.handles[3*pos-1];
518 old_handle3 = bez->object.handles[3*pos];
519 old_point = bez->points[pos];
520 old_ctype = bez->corner_types[pos];
522 cpt1 = old_handle1->connected_to;
523 cpt2 = old_handle2->connected_to;
524 cpt3 = old_handle3->connected_to;
526 object_unconnect((DiaObject *)bez, old_handle1);
527 object_unconnect((DiaObject *)bez, old_handle2);
528 object_unconnect((DiaObject *)bez, old_handle3);
530 remove_handles(bez, pos);
532 bezierconn_update_data(bez);
534 return bezierconn_create_point_change(bez, TYPE_REMOVE_POINT,
535 &old_point, old_ctype, pos,
536 old_handle1, cpt1,
537 old_handle2, cpt2,
538 old_handle3, cpt3);
541 /** Update a corner to have less freedom in its control handles, arranging
542 * the control points at some reasonable places.
543 * @param bez A bezierconn to straighten a corner of
544 * @param comp_nr The index into the corner_types array of the corner to
545 * straighten.
546 * @bugs what happens if we're going from symmetric to smooth?
548 static void
549 bezierconn_straighten_corner(BezierConn *bez, int comp_nr)
551 /* Neat thing would be to have the kind of straigthening depend on
552 which handle was chosen: Mid-handle does average, other leaves that
553 handle where it is. */
554 switch (bez->corner_types[comp_nr]) {
555 case BEZ_CORNER_SYMMETRIC: {
556 Point pt1, pt2;
557 pt1 = bez->points[comp_nr].p3;
558 point_sub(&pt1, &bez->points[comp_nr].p2);
559 pt2 = bez->points[comp_nr].p3;
560 point_sub(&pt2, &bez->points[comp_nr+1].p1);
561 point_scale(&pt2, -1.0);
562 point_add(&pt1, &pt2);
563 point_scale(&pt1, 0.5);
564 pt2 = pt1;
565 point_scale(&pt1, -1.0);
566 point_add(&pt1, &bez->points[comp_nr].p3);
567 point_add(&pt2, &bez->points[comp_nr].p3);
568 bez->points[comp_nr].p2 = pt1;
569 bez->points[comp_nr+1].p1 = pt2;
570 bezierconn_update_data(bez);
572 break;
573 case BEZ_CORNER_SMOOTH: {
574 Point pt1, pt2;
575 real len1, len2;
576 pt1 = bez->points[comp_nr].p3;
577 point_sub(&pt1, &bez->points[comp_nr].p2);
578 pt2 = bez->points[comp_nr].p3;
579 point_sub(&pt2, &bez->points[comp_nr+1].p1);
580 len1 = point_len(&pt1);
581 len2 = point_len(&pt2);
582 point_scale(&pt2, -1.0);
583 if (len1 > 0)
584 point_normalize(&pt1);
585 if (len2 > 0)
586 point_normalize(&pt2);
587 point_add(&pt1, &pt2);
588 point_scale(&pt1, 0.5);
589 pt2 = pt1;
590 point_scale(&pt1, -len1);
591 point_add(&pt1, &bez->points[comp_nr].p3);
592 point_scale(&pt2, len2);
593 point_add(&pt2, &bez->points[comp_nr].p3);
594 bez->points[comp_nr].p2 = pt1;
595 bez->points[comp_nr+1].p1 = pt2;
596 bezierconn_update_data(bez);
598 break;
599 case BEZ_CORNER_CUSP:
600 break;
604 /** Change the corner type of a bezier line.
605 * @param bez The bezierconn that has the corner
606 * @param handle The handle whose corner should be set.
607 * @param corner_type What type of corner the handle should have.
608 * @returns Undo information about the corner change.
610 ObjectChange *
611 bezierconn_set_corner_type(BezierConn *bez, Handle *handle,
612 BezCornerType corner_type)
614 Handle *mid_handle;
615 Point old_left, old_right;
616 int old_type;
617 int handle_nr, comp_nr;
619 handle_nr = get_handle_nr(bez, handle);
621 switch (handle->id) {
622 case HANDLE_BEZMAJOR:
623 mid_handle = handle;
624 break;
625 case HANDLE_LEFTCTRL:
626 handle_nr++;
627 mid_handle = bez->object.handles[handle_nr];
628 break;
629 case HANDLE_RIGHTCTRL:
630 handle_nr--;
631 mid_handle = bez->object.handles[handle_nr];
632 break;
633 default:
634 message_warning(_("Internal error: Setting corner type of endpoint of bezier"));
635 return NULL;
638 comp_nr = get_major_nr(handle_nr);
640 old_type = bez->corner_types[comp_nr];
641 old_left = bez->points[comp_nr].p2;
642 old_right = bez->points[comp_nr+1].p1;
644 bez->corner_types[comp_nr] = corner_type;
646 bezierconn_straighten_corner(bez, comp_nr);
648 return bezierconn_create_corner_change(bez, mid_handle, &old_left, &old_right,
649 old_type, corner_type);
652 /** Update handle array and handle positions after changes.
653 * @param bez A bezierconn to update.
655 void
656 bezierconn_update_data(BezierConn *bez)
658 int i;
659 DiaObject *obj = &bez->object;
661 /* handle the case of whole points array update (via set_prop) */
662 if (3*bez->numpoints-2 != obj->num_handles) {
663 g_assert(0 == obj->num_connections);
665 for (i = 0; i < obj->num_handles; i++)
666 g_free(obj->handles[i]);
667 g_free(obj->handles);
669 obj->num_handles = 3*bez->numpoints-2;
670 obj->handles = g_new(Handle*, obj->num_handles);
672 new_handles(bez, bez->numpoints);
675 /* Update handles: */
676 bez->object.handles[0]->pos = bez->points[0].p1;
677 for (i = 1; i < bez->numpoints; i++) {
678 bez->object.handles[3*i-2]->pos = bez->points[i].p1;
679 bez->object.handles[3*i-1]->pos = bez->points[i].p2;
680 bez->object.handles[3*i]->pos = bez->points[i].p3;
684 /** Update the boundingbox of the connection.
685 * @param bez A bezier line to update bounding box for.
687 void
688 bezierconn_update_boundingbox(BezierConn *bez)
690 g_assert(bez != NULL);
692 polybezier_bbox(&bez->points[0],
693 bez->numpoints,
694 &bez->extra_spacing, FALSE,
695 &bez->object.bounding_box);
698 /** Draw the main line of a bezier conn.
699 * Note that this sets the linestyle, linejoin and linecaps to hardcoded
700 * values.
701 * @param bez The bezier conn to draw.
702 * @param renderer The renderer to draw with.
703 * @param width The linewidth of the bezier.
705 void
706 bezierconn_simple_draw(BezierConn *bez, DiaRenderer *renderer, real width)
708 BezPoint *points;
710 g_assert(bez != NULL);
711 g_assert(renderer != NULL);
713 points = &bez->points[0];
715 DIA_RENDERER_GET_CLASS(renderer)->set_linewidth(renderer, width);
716 DIA_RENDERER_GET_CLASS(renderer)->set_linestyle(renderer, LINESTYLE_SOLID);
717 DIA_RENDERER_GET_CLASS(renderer)->set_linejoin(renderer, LINEJOIN_ROUND);
718 DIA_RENDERER_GET_CLASS(renderer)->set_linecaps(renderer, LINECAPS_BUTT);
720 DIA_RENDERER_GET_CLASS(renderer)->draw_bezier(renderer, points, bez->numpoints, &color_black);
723 /** Draw the control lines from the points of the bezier conn.
724 * Note that the control lines are hardcoded to be dotted with dash length 1.
725 * @param bez The bezier conn to draw control lines for.
726 * @param renderer A renderer to draw with.
728 void
729 bezierconn_draw_control_lines(BezierConn *bez, DiaRenderer *renderer)
731 Color line_colour = {0.0, 0.0, 0.6};
732 Point startpoint;
733 int i;
735 /* setup DiaRenderer ... */
736 DIA_RENDERER_GET_CLASS(renderer)->set_linewidth(renderer, 0);
737 DIA_RENDERER_GET_CLASS(renderer)->set_linestyle(renderer, LINESTYLE_DOTTED);
738 DIA_RENDERER_GET_CLASS(renderer)->set_dashlength(renderer, 1);
739 DIA_RENDERER_GET_CLASS(renderer)->set_linejoin(renderer, LINEJOIN_MITER);
740 DIA_RENDERER_GET_CLASS(renderer)->set_linecaps(renderer, LINECAPS_BUTT);
742 startpoint = bez->points[0].p1;
743 for (i = 1; i < bez->numpoints; i++) {
744 DIA_RENDERER_GET_CLASS(renderer)->draw_line(renderer, &startpoint, &bez->points[i].p1,
745 &line_colour);
746 DIA_RENDERER_GET_CLASS(renderer)->draw_line(renderer, &bez->points[i].p2, &bez->points[i].p3,
747 &line_colour);
748 startpoint = bez->points[i].p3;
752 /** Create all handles used by a bezier conn.
753 * @param bez A bezierconn object initialized with room for 3*num_points-2
754 * handles.
755 * @param num_points The number of points of the bezierconn.
757 void
758 new_handles(BezierConn *bez, int num_points)
760 DiaObject *obj;
761 int i;
763 obj = &bez->object;
765 obj->handles[0] = g_new(Handle,1);
766 obj->handles[0]->connect_type = HANDLE_CONNECTABLE;
767 obj->handles[0]->connected_to = NULL;
768 obj->handles[0]->type = HANDLE_MAJOR_CONTROL;
769 obj->handles[0]->id = HANDLE_MOVE_STARTPOINT;
771 for (i = 1; i < num_points; i++) {
772 obj->handles[3*i-2] = g_new(Handle, 1);
773 obj->handles[3*i-1] = g_new(Handle, 1);
774 obj->handles[3*i] = g_new(Handle, 1);
776 setup_handle(obj->handles[3*i-2], HANDLE_RIGHTCTRL);
777 setup_handle(obj->handles[3*i-1], HANDLE_LEFTCTRL);
779 obj->handles[3*i]->connect_type = HANDLE_CONNECTABLE;
780 obj->handles[3*i]->connected_to = NULL;
781 obj->handles[3*i]->type = HANDLE_MAJOR_CONTROL;
782 obj->handles[3*i]->id = HANDLE_MOVE_ENDPOINT;
786 /** Initialize a bezierconn object with the given amount of points.
787 * The points array of the bezierconn object should be previously
788 * initialized with appropriate positions.
789 * This will set up handles and make all corners symmetric.
790 * @param bez A newly allocated bezierconn object.
791 * @param num_points The initial number of points on the curve.
793 void
794 bezierconn_init(BezierConn *bez, int num_points)
796 DiaObject *obj;
797 int i;
799 obj = &bez->object;
801 object_init(obj, 3*num_points-2, 0);
803 bez->numpoints = num_points;
805 bez->points = g_new(BezPoint, num_points);
806 bez->corner_types = g_new(BezCornerType, num_points);
807 bez->points[0].type = BEZ_MOVE_TO;
808 bez->corner_types[0] = BEZ_CORNER_SYMMETRIC;
809 for (i = 1; i < num_points; i++) {
810 bez->points[i].type = BEZ_CURVE_TO;
811 bez->corner_types[i] = BEZ_CORNER_SYMMETRIC;
814 new_handles(bez, num_points);
816 bezierconn_update_data(bez);
819 /** Set a bezierconn to use the given array of points.
820 * This function does *not* set up handles
821 * @param bez A bezierconn to operate on
822 * @param num_points The number of points in the `points' array.
823 * @param points The new points that this bezier should be set to use.
825 void
826 bezierconn_set_points(BezierConn *bez, int num_points, BezPoint *points)
828 int i;
830 bez->numpoints = num_points;
832 if (bez->points)
833 g_free(bez->points);
835 bez->points = g_malloc((bez->numpoints)*sizeof(BezPoint));
837 for (i=0;i<bez->numpoints;i++) {
838 bez->points[i] = points[i];
843 /** Copy a bezierconn objects. This function in turn invokes object_copy.
844 * @param from The object to copy from.
845 * @param to The object to copy to.
847 void
848 bezierconn_copy(BezierConn *from, BezierConn *to)
850 int i;
851 DiaObject *toobj, *fromobj;
853 toobj = &to->object;
854 fromobj = &from->object;
856 object_copy(fromobj, toobj);
858 to->numpoints = from->numpoints;
860 to->points = g_new(BezPoint, to->numpoints);
861 to->corner_types = g_new(BezCornerType, to->numpoints);
863 for (i = 0; i < to->numpoints; i++) {
864 to->points[i] = from->points[i];
865 to->corner_types[i] = from->corner_types[i];
868 to->object.handles[0] = g_new(Handle,1);
869 *to->object.handles[0] = *from->object.handles[0];
870 for (i = 1; i < to->object.num_handles - 1; i++) {
871 to->object.handles[i] = g_new(Handle, 1);
872 setup_handle(to->object.handles[i], from->object.handles[i]->id);
874 to->object.handles[to->object.num_handles-1] = g_new(Handle,1);
875 *to->object.handles[to->object.num_handles-1] =
876 *from->object.handles[to->object.num_handles-1];
878 memcpy(&to->extra_spacing,&from->extra_spacing,sizeof(to->extra_spacing));
879 bezierconn_update_data(to);
882 /** Destroy a bezierconn object.
883 * @param bez An object to destroy. This function in turn calls object_destroy
884 * and frees structures allocated by this bezierconn, but not the object itself
886 void
887 bezierconn_destroy(BezierConn *bez)
889 int i, nh;
890 Handle **temp_handles;
892 /* Need to store these temporary since object.handles is
893 freed by object_destroy() */
894 nh = bez->object.num_handles;
895 temp_handles = g_new(Handle *, nh);
896 for (i = 0; i < nh; i++)
897 temp_handles[i] = bez->object.handles[i];
899 object_destroy(&bez->object);
901 for (i = 0; i < nh; i++)
902 g_free(temp_handles[i]);
903 g_free(temp_handles);
905 g_free(bez->points);
906 g_free(bez->corner_types);
910 /** Save the data defined by a bezierconn object to XML.
911 * @param bez The object to save.
912 * @param obj_node The XML node to save it into
913 * @bugs shouldn't this have version?
915 void
916 bezierconn_save(BezierConn *bez, ObjectNode obj_node)
918 int i;
919 AttributeNode attr;
921 object_save(&bez->object, obj_node);
923 attr = new_attribute(obj_node, "bez_points");
925 data_add_point(attr, &bez->points[0].p1);
926 for (i = 1; i < bez->numpoints; i++) {
927 data_add_point(attr, &bez->points[i].p1);
928 data_add_point(attr, &bez->points[i].p2);
929 data_add_point(attr, &bez->points[i].p3);
932 attr = new_attribute(obj_node, "corner_types");
933 for (i = 0; i < bez->numpoints; i++)
934 data_add_enum(attr, bez->corner_types[i]);
937 /** Load a bezierconn object from XML.
938 * Does object_init() on the bezierconn object.
939 * @param bez A newly allocated bezierconn object to load into.
940 * @param obj_node The XML node to load from.
941 * @bugs shouldn't this have version?
942 * @bugs Couldn't this use the setup_handles function defined above?
944 void
945 bezierconn_load(BezierConn *bez, ObjectNode obj_node)
947 int i;
948 AttributeNode attr;
949 DataNode data;
951 DiaObject *obj = &bez->object;
953 object_load(obj, obj_node);
955 attr = object_find_attribute(obj_node, "bez_points");
957 if (attr != NULL)
958 bez->numpoints = (attribute_num_data(attr) + 2)/3;
959 else
960 bez->numpoints = 0;
962 object_init(obj, 3 * bez->numpoints - 2, 0);
964 data = attribute_first_data(attr);
965 if (bez->numpoints != 0) {
966 bez->points = g_new(BezPoint, bez->numpoints);
967 bez->points[0].type = BEZ_MOVE_TO;
968 data_point(data, &bez->points[0].p1);
969 data = data_next(data);
971 for (i = 1; i < bez->numpoints; i++) {
972 bez->points[i].type = BEZ_CURVE_TO;
973 data_point(data, &bez->points[i].p1);
974 data = data_next(data);
975 data_point(data, &bez->points[i].p2);
976 data = data_next(data);
977 data_point(data, &bez->points[i].p3);
978 data = data_next(data);
982 bez->corner_types = g_new(BezCornerType, bez->numpoints);
984 attr = object_find_attribute(obj_node, "corner_types");
985 /* if corner_types is missing or corrupt */
986 if (!attr || attribute_num_data(attr) != bez->numpoints) {
987 for (i = 0; i < bez->numpoints; i++) {
988 bez->corner_types[i] = BEZ_CORNER_SYMMETRIC;
990 } else {
991 data = attribute_first_data(attr);
992 for (i = 0; i < bez->numpoints; i++) {
993 bez->corner_types[i] = data_enum(data);
994 data = data_next(data);
998 obj->handles[0] = g_new(Handle, 1);
999 obj->handles[0]->connect_type = HANDLE_CONNECTABLE;
1000 obj->handles[0]->connected_to = NULL;
1001 obj->handles[0]->type = HANDLE_MAJOR_CONTROL;
1002 obj->handles[0]->id = HANDLE_MOVE_STARTPOINT;
1004 for (i = 1; i < bez->numpoints; i++) {
1005 obj->handles[3*i-2] = g_new(Handle, 1);
1006 setup_handle(obj->handles[3*i-2], HANDLE_RIGHTCTRL);
1007 obj->handles[3*i-1] = g_new(Handle, 1);
1008 setup_handle(obj->handles[3*i-1], HANDLE_LEFTCTRL);
1009 obj->handles[3*i] = g_new(Handle, 1);
1010 setup_handle(obj->handles[3*i], HANDLE_BEZMAJOR);
1013 obj->handles[obj->num_handles-1]->connect_type = HANDLE_CONNECTABLE;
1014 obj->handles[obj->num_handles-1]->connected_to = NULL;
1015 obj->handles[obj->num_handles-1]->type = HANDLE_MAJOR_CONTROL;
1016 obj->handles[obj->num_handles-1]->id = HANDLE_MOVE_ENDPOINT;
1018 bezierconn_update_data(bez);
1021 /*** Undo support ***/
1023 /** Free undo information about adding or removing points.
1024 * @param change The undo information to free.
1026 static void
1027 bezierconn_point_change_free(struct PointChange *change)
1029 if ( (change->type==TYPE_ADD_POINT && !change->applied) ||
1030 (change->type==TYPE_REMOVE_POINT && change->applied) ){
1031 g_free(change->handle1);
1032 g_free(change->handle2);
1033 g_free(change->handle3);
1034 change->handle1 = NULL;
1035 change->handle2 = NULL;
1036 change->handle3 = NULL;
1040 /** Apply a point addition/removal.
1041 * @param change The change to apply.
1042 * @param obj The object (must be a BezierConn) to apply the change to.
1044 static void
1045 bezierconn_point_change_apply(struct PointChange *change, DiaObject *obj)
1047 change->applied = 1;
1048 switch (change->type) {
1049 case TYPE_ADD_POINT:
1050 add_handles((BezierConn *)obj, change->pos, &change->point,
1051 change->corner_type,
1052 change->handle1, change->handle2, change->handle3);
1053 break;
1054 case TYPE_REMOVE_POINT:
1055 object_unconnect(obj, change->handle1);
1056 object_unconnect(obj, change->handle2);
1057 object_unconnect(obj, change->handle3);
1058 remove_handles((BezierConn *)obj, change->pos);
1059 break;
1063 /** Revert (unapply) a point addition/removal.
1064 * @param change The change to revert.
1065 * @param obj The object (must be a BezierConn) to revert the change of.
1067 static void
1068 bezierconn_point_change_revert(struct PointChange *change, DiaObject *obj)
1070 switch (change->type) {
1071 case TYPE_ADD_POINT:
1072 remove_handles((BezierConn *)obj, change->pos);
1073 break;
1074 case TYPE_REMOVE_POINT:
1075 add_handles((BezierConn *)obj, change->pos, &change->point,
1076 change->corner_type,
1077 change->handle1, change->handle2, change->handle3);
1079 if (change->connected_to1)
1080 object_connect(obj, change->handle1, change->connected_to1);
1081 if (change->connected_to2)
1082 object_connect(obj, change->handle2, change->connected_to2);
1083 if (change->connected_to3)
1084 object_connect(obj, change->handle3, change->connected_to3);
1086 break;
1088 change->applied = 0;
1091 /** Create undo information about a point being added or removed.
1092 * @param bez The object that the change applies to (ignored)
1093 * @param type The type of change (either TYPE_ADD_POINT or TYPE_REMOVE_POINT)
1094 * @param point The point being added or removed.
1095 * @param corner_type Which type of corner is at the point.
1096 * @param pos The position of the point.
1097 * @param handle1 The first (central) handle.
1098 * @param connected_to1 What the first handle is connected to.
1099 * @param handle2 The second (left-hand) handle.
1100 * @param connected_to2 What the second handle is connected to.
1101 * @param handle3 The third (right-hand) handle.
1102 * @param connected_to3 What the third handle is connected to.
1103 * @returns Newly created undo information.
1104 * @bugs Describe what the state of the point and handles should be at start.
1105 * @bugs Can these handles be connected to anything at all?
1107 static ObjectChange *
1108 bezierconn_create_point_change(BezierConn *bez, enum change_type type,
1109 BezPoint *point, BezCornerType corner_type,
1110 int pos,
1111 Handle *handle1, ConnectionPoint *connected_to1,
1112 Handle *handle2, ConnectionPoint *connected_to2,
1113 Handle *handle3, ConnectionPoint *connected_to3)
1115 struct PointChange *change;
1117 change = g_new(struct PointChange, 1);
1119 change->obj_change.apply = (ObjectChangeApplyFunc) bezierconn_point_change_apply;
1120 change->obj_change.revert = (ObjectChangeRevertFunc) bezierconn_point_change_revert;
1121 change->obj_change.free = (ObjectChangeFreeFunc) bezierconn_point_change_free;
1123 change->type = type;
1124 change->applied = 1;
1125 change->point = *point;
1126 change->corner_type = corner_type;
1127 change->pos = pos;
1128 change->handle1 = handle1;
1129 change->connected_to1 = connected_to1;
1130 change->handle2 = handle2;
1131 change->connected_to2 = connected_to2;
1132 change->handle3 = handle3;
1133 change->connected_to3 = connected_to3;
1135 return (ObjectChange *)change;
1138 /** Apply a change of corner type. This may change the position of the
1139 * control handles by calling bezierconn_straighten_corner.
1140 * @param change The undo information to apply.
1141 * @param obj The object to apply the undo information too.
1143 static void
1144 bezierconn_corner_change_apply(struct CornerChange *change,
1145 DiaObject *obj)
1147 BezierConn *bez = (BezierConn *)obj;
1148 int handle_nr = get_handle_nr(bez, change->handle);
1149 int comp_nr = get_major_nr(handle_nr);
1151 bezierconn_straighten_corner(bez, comp_nr);
1153 bez->corner_types[comp_nr] = change->new_type;
1155 change->applied = 1;
1158 /** Revert (unapply) a change of corner type. This may move the position
1159 * of the control handles to what they were before applying.
1160 * @param change Undo information to apply.
1161 * @param obj The bezierconn object to apply the change to.
1163 static void
1164 bezierconn_corner_change_revert(struct CornerChange *change,
1165 DiaObject *obj)
1167 BezierConn *bez = (BezierConn *)obj;
1168 int handle_nr = get_handle_nr(bez, change->handle);
1169 int comp_nr = get_major_nr(handle_nr);
1171 bez->points[comp_nr].p2 = change->point_left;
1172 bez->points[comp_nr+1].p1 = change->point_right;
1173 bez->corner_types[comp_nr] = change->old_type;
1175 change->applied = 0;
1178 /** Create new undo information about a changing the type of a corner.
1179 * Note that the created ObjectChange object has nothing in it that needs
1180 * freeing.
1181 * @param bez The bezierconn object this applies to.
1182 * @param handle The handle of the corner being changed.
1183 * @param point_left The position of the left control handle.
1184 * @param point_right The position of the right control handle.
1185 * @param old_corner_type The corner type before applying.
1186 * @param new_corner_type The corner type being changed to.
1187 * @returns Newly allocated undo information.
1189 static ObjectChange *
1190 bezierconn_create_corner_change(BezierConn *bez, Handle *handle,
1191 Point *point_left, Point *point_right,
1192 BezCornerType old_corner_type,
1193 BezCornerType new_corner_type)
1195 struct CornerChange *change;
1197 change = g_new(struct CornerChange, 1);
1199 change->obj_change.apply = (ObjectChangeApplyFunc) bezierconn_corner_change_apply;
1200 change->obj_change.revert = (ObjectChangeRevertFunc) bezierconn_corner_change_revert;
1201 change->obj_change.free = (ObjectChangeFreeFunc) NULL;
1203 change->old_type = old_corner_type;
1204 change->new_type = new_corner_type;
1205 change->applied = 1;
1207 change->handle = handle;
1208 change->point_left = *point_left;
1209 change->point_right = *point_right;
1211 return (ObjectChange *)change;