* lib/text.h: Added text_get_line() declaration
[dia.git] / lib / bezier_conn.c
blobe4aa29b953a6e518cdfdbc398742f8ecce161847
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 /** \file bezier_conn.c Allows to construct object consisting of bezier lines */
23 #include <config.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <math.h>
28 #include <string.h> /* memcpy() */
30 #include "bezier_conn.h"
31 #include "intl.h"
32 #include "message.h"
33 #include "diarenderer.h"
35 #define HANDLE_BEZMAJOR (HANDLE_CUSTOM1)
36 #define HANDLE_LEFTCTRL (HANDLE_CUSTOM2)
37 #define HANDLE_RIGHTCTRL (HANDLE_CUSTOM3)
39 enum change_type {
40 TYPE_ADD_POINT,
41 TYPE_REMOVE_POINT
44 struct PointChange {
45 ObjectChange obj_change;
47 enum change_type type;
48 int applied;
50 BezPoint point;
51 BezCornerType corner_type;
52 int pos;
53 /* owning ref when not applied for ADD_POINT
54 * owning ref when applied for REMOVE_POINT */
55 Handle *handle1, *handle2, *handle3;
56 /* NULL if not connected */
57 ConnectionPoint *connected_to1, *connected_to2, *connected_to3;
60 struct CornerChange {
61 ObjectChange obj_change;
62 /* Only one kind of corner_change */
63 int applied;
65 Handle *handle;
66 /* Old places when SET_CORNER_TYPE is applied */
67 Point point_left, point_right;
68 BezCornerType old_type, new_type;
71 static ObjectChange *
72 bezierconn_create_point_change(BezierConn *bez, enum change_type type,
73 BezPoint *point, BezCornerType corner_type,
74 int segment,
75 Handle *handle1, ConnectionPoint *connected_to1,
76 Handle *handle2, ConnectionPoint *connected_to2,
77 Handle *handle3, ConnectionPoint *connected_to3);
78 static ObjectChange *
79 bezierconn_create_corner_change(BezierConn *bez, Handle *handle,
80 Point *point_left, Point *point_right,
81 BezCornerType old_corner_type,
82 BezCornerType new_corner_type);
84 /** Set up a handle for any part of a bezierconn
85 * @param handle A handle to set up.
86 * @param id Handle id (HANDLE_BEZMAJOR or HANDLE_BEZMINOER)
88 static void
89 setup_handle(Handle *handle, HandleId id)
91 handle->id = id;
92 handle->type = HANDLE_MINOR_CONTROL;
93 handle->connect_type = (id == HANDLE_BEZMAJOR) ?
94 HANDLE_CONNECTABLE : HANDLE_NONCONNECTABLE;
95 handle->connected_to = NULL;
98 /** Get the number in the array of handles that a given handle has.
99 * @param bez A bezierconn object with handles set up.
100 * @param handle A handle object.
101 * @returns The index in bex->object.handles of the handle object, or -1 if
102 * `handle' is not in the array.
104 static int
105 get_handle_nr(BezierConn *bez, Handle *handle)
107 int i = 0;
108 for (i=0;i<bez->object.num_handles;i++) {
109 if (bez->object.handles[i] == handle)
110 return i;
112 return -1;
115 void new_handles(BezierConn *bez, int num_points);
117 #define get_comp_nr(hnum) (((int)(hnum)+2)/3)
118 #define get_major_nr(hnum) (((int)(hnum)+1)/3)
120 /** Function called to move one of the handles associated with the
121 * bezierconn.
122 * @param obj The object whose handle is being moved.
123 * @param handle The handle being moved.
124 * @param pos The position it has been moved to (corrected for
125 * vertical/horizontal only movement).
126 * @param cp If non-NULL, the connectionpoint found at this position.
127 * If @a cp is NULL, there may or may not be a connectionpoint.
128 * @param reason ignored
129 * @param modifiers ignored
130 * @return NULL
132 ObjectChange*
133 bezierconn_move_handle(BezierConn *bez, Handle *handle,
134 Point *to, ConnectionPoint *cp,
135 HandleMoveReason reason, ModifierKeys modifiers)
137 int handle_nr, comp_nr;
138 Point delta, pt;
140 delta = *to;
141 point_sub(&delta, &handle->pos);
143 handle_nr = get_handle_nr(bez, handle);
144 comp_nr = get_comp_nr(handle_nr);
145 switch(handle->id) {
146 case HANDLE_MOVE_STARTPOINT:
147 bez->points[0].p1 = *to;
148 /* shift adjacent point */
149 point_add(&bez->points[1].p1, &delta);
150 break;
151 case HANDLE_MOVE_ENDPOINT:
152 bez->points[bez->numpoints-1].p3 = *to;
153 /* shift adjacent point */
154 point_add(&bez->points[bez->numpoints-1].p2, &delta);
155 break;
156 case HANDLE_BEZMAJOR:
157 bez->points[comp_nr].p3 = *to;
158 /* shift adjacent point */
159 point_add(&bez->points[comp_nr].p2, &delta);
160 point_add(&bez->points[comp_nr+1].p1, &delta);
161 break;
162 case HANDLE_LEFTCTRL:
163 bez->points[comp_nr].p2 = *to;
164 if (comp_nr < bez->numpoints - 1) {
165 switch (bez->corner_types[comp_nr]) {
166 case BEZ_CORNER_SYMMETRIC:
167 pt = bez->points[comp_nr].p3;
168 point_sub(&pt, &bez->points[comp_nr].p2);
169 point_add(&pt, &bez->points[comp_nr].p3);
170 bez->points[comp_nr+1].p1 = pt;
171 break;
172 case BEZ_CORNER_SMOOTH: {
173 real len;
174 pt = bez->points[comp_nr+1].p1;
175 point_sub(&pt, &bez->points[comp_nr].p3);
176 len = point_len(&pt);
177 pt = bez->points[comp_nr].p2;
178 point_sub(&pt, &bez->points[comp_nr].p3);
179 if (point_len(&pt) > 0)
180 point_normalize(&pt);
181 else { pt.x = 1.0; pt.y = 0.0; }
182 point_scale(&pt, -len);
183 point_add(&pt, &bez->points[comp_nr].p3);
184 bez->points[comp_nr+1].p1 = pt;
185 break;
187 case BEZ_CORNER_CUSP:
188 /* Do nothing to the other guy */
189 break;
192 break;
193 case HANDLE_RIGHTCTRL:
194 bez->points[comp_nr].p1 = *to;
195 if (comp_nr > 1) {
196 switch (bez->corner_types[comp_nr-1]) {
197 case BEZ_CORNER_SYMMETRIC:
198 pt = bez->points[comp_nr - 1].p3;
199 point_sub(&pt, &bez->points[comp_nr].p1);
200 point_add(&pt, &bez->points[comp_nr - 1].p3);
201 bez->points[comp_nr-1].p2 = pt;
202 break;
203 case BEZ_CORNER_SMOOTH: {
204 real len;
205 pt = bez->points[comp_nr-1].p2;
206 point_sub(&pt, &bez->points[comp_nr-1].p3);
207 len = point_len(&pt);
208 pt = bez->points[comp_nr].p1;
209 point_sub(&pt, &bez->points[comp_nr-1].p3);
210 if (point_len(&pt) > 0)
211 point_normalize(&pt);
212 else { pt.x = 1.0; pt.y = 0.0; }
213 point_scale(&pt, -len);
214 point_add(&pt, &bez->points[comp_nr-1].p3);
215 bez->points[comp_nr-1].p2 = pt;
216 break;
218 case BEZ_CORNER_CUSP:
219 /* Do nothing to the other guy */
220 break;
223 break;
224 default:
225 message_error("Internal error in bezierconn_move_handle.\n");
226 break;
228 return NULL;
232 /** Function called to move the entire object.
233 * @param obj The object being moved.
234 * @param pos Where the object is being moved to. This is the first point
235 * of the points array.
236 * @return NULL
238 ObjectChange*
239 bezierconn_move(BezierConn *bez, Point *to)
241 Point p;
242 int i;
244 p = *to;
245 point_sub(&p, &bez->points[0].p1);
247 bez->points[0].p1 = *to;
248 for (i = 1; i < bez->numpoints; i++) {
249 point_add(&bez->points[i].p1, &p);
250 point_add(&bez->points[i].p2, &p);
251 point_add(&bez->points[i].p3, &p);
253 return NULL;
257 /** Return the segment of the bezierconn closest to a given point.
258 * @param bez The bezierconn object
259 * @param point A point to find the closest segment to.
260 * @param line_width Line width of the bezier line.
261 * @returns The index of the segment closest to point.
264 bezierconn_closest_segment(BezierConn *bez, Point *point, real line_width)
266 Point last;
267 int i;
268 real dist = G_MAXDOUBLE;
269 int closest;
271 closest = 0;
272 last = bez->points[0].p1;
273 for (i = 0; i < bez->numpoints - 1; i++) {
274 real new_dist = distance_bez_seg_point(&last, &bez->points[i+1].p1,
275 &bez->points[i+1].p2, &bez->points[i+1].p3,
276 line_width, point);
277 if (new_dist < dist) {
278 dist = new_dist;
279 closest = i;
281 last = bez->points[i+1].p3;
283 return closest;
286 /** Return the handle closest to a given point.
287 * @param bez A bezierconn object
288 * @param point A point to find distances from
289 * @return The handle on `bez' closest to `point'.
290 * @bug Why isn't this just a function on object that scans the handles?
292 Handle *
293 bezierconn_closest_handle(BezierConn *bez, Point *point)
295 int i, hn;
296 real dist;
297 Handle *closest;
299 closest = bez->object.handles[0];
300 dist = distance_point_point( point, &closest->pos);
301 for (i = 1, hn = 1; i < bez->numpoints; i++, hn++) {
302 real new_dist;
304 new_dist = distance_point_point(point, &bez->points[i].p1);
305 if (new_dist < dist) {
306 dist = new_dist;
307 closest = bez->object.handles[hn];
309 hn++;
311 new_dist = distance_point_point(point, &bez->points[i].p2);
312 if (new_dist < dist) {
313 dist = new_dist;
314 closest = bez->object.handles[hn];
316 hn++;
318 new_dist = distance_point_point(point, &bez->points[i].p3);
319 if (new_dist < dist) {
320 dist = new_dist;
321 closest = bez->object.handles[hn];
324 return closest;
327 /** Retrun the major handle for the control point with the handle closest to
328 * a given point.
329 * @param bez A bezier connection
330 * @param point A point
331 * @return The major (middle) handle of the bezier control that has the
332 * handle closest to point.
333 * @bug Don't we really want the major handle that's actually closest to
334 * the point? This is used in connection with object menus and could cause
335 * some unexpected selection of handles if a different segment has a control
336 * point close to the major handle.
338 Handle *
339 bezierconn_closest_major_handle(BezierConn *bez, Point *point)
341 Handle *closest = bezierconn_closest_handle(bez, point);
343 return bez->object.handles[3*get_major_nr(get_handle_nr(bez, closest))];
346 /** Return the distance from a bezier to a point.
347 * @param bez A bezierconn object.
348 * @param point A point to compare with.
349 * @param line_width The line width of the bezier line.
350 * @returns The shortest distance from the point to any part of the bezier.
352 real
353 bezierconn_distance_from(BezierConn *bez, Point *point, real line_width)
355 return distance_bez_line_point(bez->points, bez->numpoints,
356 line_width, point);
359 /** Add a trio of handles to a bezier.
360 * @param bez The bezierconn having handles added.
361 * @param pos Where in the list of segments to add the handle
362 * @param point The bezier point to add. This should already be initialized
363 * with sensible positions.
364 * @param corner_type What kind of corner this bezpoint should be.
365 * @param handle1 The handle that will be put on the bezier line.
366 * @param handle2 The handle that will be put before handle1
367 * @param handle3 The handle that will be put after handle1
368 * @bug check that the handle ordering is correctly described.
370 static void
371 add_handles(BezierConn *bez, int pos, BezPoint *point,
372 BezCornerType corner_type, Handle *handle1,
373 Handle *handle2, Handle *handle3)
375 int i;
376 DiaObject *obj;
378 g_assert(pos > 0);
380 obj = (DiaObject *)bez;
381 bez->numpoints++;
382 bez->points = g_realloc(bez->points, bez->numpoints*sizeof(BezPoint));
383 bez->corner_types = g_realloc(bez->corner_types,
384 bez->numpoints * sizeof(BezCornerType));
386 for (i = bez->numpoints-1; i > pos; i--) {
387 bez->points[i] = bez->points[i-1];
388 bez->corner_types[i] = bez->corner_types[i-1];
390 bez->points[pos] = *point;
391 bez->points[pos].p1 = bez->points[pos+1].p1;
392 bez->points[pos+1].p1 = point->p1;
393 bez->corner_types[pos] = corner_type;
394 object_add_handle_at(obj, handle1, 3*pos-2);
395 object_add_handle_at(obj, handle2, 3*pos-1);
396 object_add_handle_at(obj, handle3, 3*pos);
398 if (pos==bez->numpoints-1) {
399 obj->handles[obj->num_handles-4]->type = HANDLE_MINOR_CONTROL;
400 obj->handles[obj->num_handles-4]->id = HANDLE_BEZMAJOR;
404 /** Remove a trio of handles from a bezierconn.
405 * @param bez The bezierconn to remove handles from.
406 * @param pos The position in the bezierpoint array to remove handles and
407 * bezpoint at.
409 static void
410 remove_handles(BezierConn *bez, int pos)
412 int i;
413 DiaObject *obj;
414 Handle *old_handle1, *old_handle2, *old_handle3;
415 Point tmppoint;
417 g_assert(pos > 0);
419 obj = (DiaObject *)bez;
421 if (pos==obj->num_handles-1) {
422 obj->handles[obj->num_handles-4]->type = HANDLE_MAJOR_CONTROL;
423 obj->handles[obj->num_handles-4]->id = HANDLE_MOVE_ENDPOINT;
426 /* delete the points */
427 bez->numpoints--;
428 tmppoint = bez->points[pos].p1;
429 for (i = pos; i < bez->numpoints; i++) {
430 bez->points[i] = bez->points[i+1];
431 bez->corner_types[i] = bez->corner_types[i+1];
433 bez->points[pos].p1 = tmppoint;
434 bez->points = g_realloc(bez->points, bez->numpoints*sizeof(BezPoint));
435 bez->corner_types = g_realloc(bez->corner_types,
436 bez->numpoints * sizeof(BezCornerType));
438 old_handle1 = obj->handles[3*pos-2];
439 old_handle2 = obj->handles[3*pos-1];
440 old_handle3 = obj->handles[3*pos];
441 object_remove_handle(&bez->object, old_handle1);
442 object_remove_handle(&bez->object, old_handle2);
443 object_remove_handle(&bez->object, old_handle3);
447 /** Add a point by splitting segment into two, putting the new point at
448 * 'point' or, if NULL, in the middle. This function will attempt to come
449 * up with reasonable placements for the control points.
450 * @param bez The bezierconn to add the segment to.
451 * @param segment Which segment to split.
452 * @param point Where to put the new corner, or NULL if undetermined.
453 * @returns An ObjectChange object with undo information for the split.
455 ObjectChange *
456 bezierconn_add_segment(BezierConn *bez, int segment, Point *point)
458 BezPoint realpoint;
459 BezCornerType corner_type = BEZ_CORNER_SYMMETRIC;
460 Handle *new_handle1, *new_handle2, *new_handle3;
461 Point startpoint;
463 if (segment == 0)
464 startpoint = bez->points[0].p1;
465 else
466 startpoint = bez->points[segment].p3;
468 if (point == NULL) {
469 realpoint.p1.x = (startpoint.x + bez->points[segment+1].p3.x) / 6;
470 realpoint.p1.y = (startpoint.y + bez->points[segment+1].p3.y) / 6;
471 realpoint.p2.x = (startpoint.x + bez->points[segment+1].p3.x) / 3;
472 realpoint.p2.y = (startpoint.y + bez->points[segment+1].p3.y) / 3;
473 realpoint.p3.x = (startpoint.x + bez->points[segment+1].p3.x) / 2;
474 realpoint.p3.y = (startpoint.y + bez->points[segment+1].p3.y) / 2;
475 } else {
476 realpoint.p2.x = point->x+(startpoint.x - bez->points[segment+1].p3.x)/6;
477 realpoint.p2.y = point->y+(startpoint.y - bez->points[segment+1].p3.y)/6;
478 realpoint.p3 = *point;
479 /* this really goes into the next segment ... */
480 realpoint.p1.x = point->x-(startpoint.x - bez->points[segment+1].p3.x)/6;
481 realpoint.p1.y = point->y-(startpoint.y - bez->points[segment+1].p3.y)/6;
483 realpoint.type = BEZ_CURVE_TO;
485 new_handle1 = g_malloc(sizeof(Handle));
486 new_handle2 = g_malloc(sizeof(Handle));
487 new_handle3 = g_malloc(sizeof(Handle));
488 setup_handle(new_handle1, HANDLE_RIGHTCTRL);
489 setup_handle(new_handle2, HANDLE_LEFTCTRL);
490 setup_handle(new_handle3, HANDLE_BEZMAJOR);
491 add_handles(bez, segment+1, &realpoint, corner_type,
492 new_handle1, new_handle2, new_handle3);
493 return bezierconn_create_point_change(bez, TYPE_ADD_POINT,
494 &realpoint, corner_type, segment+1,
495 new_handle1, NULL,
496 new_handle2, NULL,
497 new_handle3, NULL);
500 /** Remove a segment from a bezierconn.
501 * @param bez The bezierconn to remove a segment from.
502 * @param pos The index of the segment to remove.
503 * @returns Undo information for the segment removal.
505 ObjectChange *
506 bezierconn_remove_segment(BezierConn *bez, int pos)
508 Handle *old_handle1, *old_handle2, *old_handle3;
509 ConnectionPoint *cpt1, *cpt2, *cpt3;
510 BezPoint old_point;
511 BezCornerType old_ctype;
513 g_assert(pos > 0);
514 g_assert(bez->numpoints > 2);
516 if (pos == bez->numpoints-1) pos--;
518 old_handle1 = bez->object.handles[3*pos-2];
519 old_handle2 = bez->object.handles[3*pos-1];
520 old_handle3 = bez->object.handles[3*pos];
521 old_point = bez->points[pos];
522 old_ctype = bez->corner_types[pos];
524 cpt1 = old_handle1->connected_to;
525 cpt2 = old_handle2->connected_to;
526 cpt3 = old_handle3->connected_to;
528 object_unconnect((DiaObject *)bez, old_handle1);
529 object_unconnect((DiaObject *)bez, old_handle2);
530 object_unconnect((DiaObject *)bez, old_handle3);
532 remove_handles(bez, pos);
534 bezierconn_update_data(bez);
536 return bezierconn_create_point_change(bez, TYPE_REMOVE_POINT,
537 &old_point, old_ctype, pos,
538 old_handle1, cpt1,
539 old_handle2, cpt2,
540 old_handle3, cpt3);
543 /** Update a corner to have less freedom in its control handles, arranging
544 * the control points at some reasonable places.
545 * @param bez A bezierconn to straighten a corner of
546 * @param comp_nr The index into the corner_types array of the corner to
547 * straighten.
548 * @bug what happens if we're going from symmetric to smooth?
550 static void
551 bezierconn_straighten_corner(BezierConn *bez, int comp_nr)
553 /* Neat thing would be to have the kind of straigthening depend on
554 which handle was chosen: Mid-handle does average, other leaves that
555 handle where it is. */
556 switch (bez->corner_types[comp_nr]) {
557 case BEZ_CORNER_SYMMETRIC: {
558 Point pt1, pt2;
559 pt1 = bez->points[comp_nr].p3;
560 point_sub(&pt1, &bez->points[comp_nr].p2);
561 pt2 = bez->points[comp_nr].p3;
562 point_sub(&pt2, &bez->points[comp_nr+1].p1);
563 point_scale(&pt2, -1.0);
564 point_add(&pt1, &pt2);
565 point_scale(&pt1, 0.5);
566 pt2 = pt1;
567 point_scale(&pt1, -1.0);
568 point_add(&pt1, &bez->points[comp_nr].p3);
569 point_add(&pt2, &bez->points[comp_nr].p3);
570 bez->points[comp_nr].p2 = pt1;
571 bez->points[comp_nr+1].p1 = pt2;
572 bezierconn_update_data(bez);
574 break;
575 case BEZ_CORNER_SMOOTH: {
576 Point pt1, pt2;
577 real len1, len2;
578 pt1 = bez->points[comp_nr].p3;
579 point_sub(&pt1, &bez->points[comp_nr].p2);
580 pt2 = bez->points[comp_nr].p3;
581 point_sub(&pt2, &bez->points[comp_nr+1].p1);
582 len1 = point_len(&pt1);
583 len2 = point_len(&pt2);
584 point_scale(&pt2, -1.0);
585 if (len1 > 0)
586 point_normalize(&pt1);
587 if (len2 > 0)
588 point_normalize(&pt2);
589 point_add(&pt1, &pt2);
590 point_scale(&pt1, 0.5);
591 pt2 = pt1;
592 point_scale(&pt1, -len1);
593 point_add(&pt1, &bez->points[comp_nr].p3);
594 point_scale(&pt2, len2);
595 point_add(&pt2, &bez->points[comp_nr].p3);
596 bez->points[comp_nr].p2 = pt1;
597 bez->points[comp_nr+1].p1 = pt2;
598 bezierconn_update_data(bez);
600 break;
601 case BEZ_CORNER_CUSP:
602 break;
606 /** Change the corner type of a bezier line.
607 * @param bez The bezierconn that has the corner
608 * @param handle The handle whose corner should be set.
609 * @param corner_type What type of corner the handle should have.
610 * @returns Undo information about the corner change.
612 ObjectChange *
613 bezierconn_set_corner_type(BezierConn *bez, Handle *handle,
614 BezCornerType corner_type)
616 Handle *mid_handle;
617 Point old_left, old_right;
618 int old_type;
619 int handle_nr, comp_nr;
621 handle_nr = get_handle_nr(bez, handle);
623 switch (handle->id) {
624 case HANDLE_BEZMAJOR:
625 mid_handle = handle;
626 break;
627 case HANDLE_LEFTCTRL:
628 handle_nr++;
629 mid_handle = bez->object.handles[handle_nr];
630 break;
631 case HANDLE_RIGHTCTRL:
632 handle_nr--;
633 mid_handle = bez->object.handles[handle_nr];
634 break;
635 default:
636 message_warning(_("Internal error: Setting corner type of endpoint of bezier"));
637 return NULL;
640 comp_nr = get_major_nr(handle_nr);
642 old_type = bez->corner_types[comp_nr];
643 old_left = bez->points[comp_nr].p2;
644 old_right = bez->points[comp_nr+1].p1;
646 bez->corner_types[comp_nr] = corner_type;
648 bezierconn_straighten_corner(bez, comp_nr);
650 return bezierconn_create_corner_change(bez, mid_handle, &old_left, &old_right,
651 old_type, corner_type);
654 /** Update handle array and handle positions after changes.
655 * @param bez A bezierconn to update.
657 void
658 bezierconn_update_data(BezierConn *bez)
660 int i;
661 DiaObject *obj = &bez->object;
663 /* handle the case of whole points array update (via set_prop) */
664 if (3*bez->numpoints-2 != obj->num_handles) {
665 g_assert(0 == obj->num_connections);
667 for (i = 0; i < obj->num_handles; i++)
668 g_free(obj->handles[i]);
669 g_free(obj->handles);
671 obj->num_handles = 3*bez->numpoints-2;
672 obj->handles = g_new(Handle*, obj->num_handles);
674 new_handles(bez, bez->numpoints);
677 /* Update handles: */
678 bez->object.handles[0]->pos = bez->points[0].p1;
679 for (i = 1; i < bez->numpoints; i++) {
680 bez->object.handles[3*i-2]->pos = bez->points[i].p1;
681 bez->object.handles[3*i-1]->pos = bez->points[i].p2;
682 bez->object.handles[3*i]->pos = bez->points[i].p3;
686 /** Update the boundingbox of the connection.
687 * @param bez A bezier line to update bounding box for.
689 void
690 bezierconn_update_boundingbox(BezierConn *bez)
692 g_assert(bez != NULL);
694 polybezier_bbox(&bez->points[0],
695 bez->numpoints,
696 &bez->extra_spacing, FALSE,
697 &bez->object.bounding_box);
700 /** Draw the main line of a bezier conn.
701 * Note that this sets the linestyle, linejoin and linecaps to hardcoded
702 * values.
703 * @param bez The bezier conn to draw.
704 * @param renderer The renderer to draw with.
705 * @param width The linewidth of the bezier.
707 void
708 bezierconn_simple_draw(BezierConn *bez, DiaRenderer *renderer, real width)
710 BezPoint *points;
712 g_assert(bez != NULL);
713 g_assert(renderer != NULL);
715 points = &bez->points[0];
717 DIA_RENDERER_GET_CLASS(renderer)->set_linewidth(renderer, width);
718 DIA_RENDERER_GET_CLASS(renderer)->set_linestyle(renderer, LINESTYLE_SOLID);
719 DIA_RENDERER_GET_CLASS(renderer)->set_linejoin(renderer, LINEJOIN_ROUND);
720 DIA_RENDERER_GET_CLASS(renderer)->set_linecaps(renderer, LINECAPS_BUTT);
722 DIA_RENDERER_GET_CLASS(renderer)->draw_bezier(renderer, points, bez->numpoints, &color_black);
725 /** Draw the control lines from the points of the bezier conn.
726 * Note that the control lines are hardcoded to be dotted with dash length 1.
727 * @param bez The bezier conn to draw control lines for.
728 * @param renderer A renderer to draw with.
730 void
731 bezierconn_draw_control_lines(BezierConn *bez, DiaRenderer *renderer)
733 Color line_colour = {0.0, 0.0, 0.6};
734 Point startpoint;
735 int i;
737 /* setup DiaRenderer ... */
738 DIA_RENDERER_GET_CLASS(renderer)->set_linewidth(renderer, 0);
739 DIA_RENDERER_GET_CLASS(renderer)->set_linestyle(renderer, LINESTYLE_DOTTED);
740 DIA_RENDERER_GET_CLASS(renderer)->set_dashlength(renderer, 1);
741 DIA_RENDERER_GET_CLASS(renderer)->set_linejoin(renderer, LINEJOIN_MITER);
742 DIA_RENDERER_GET_CLASS(renderer)->set_linecaps(renderer, LINECAPS_BUTT);
744 startpoint = bez->points[0].p1;
745 for (i = 1; i < bez->numpoints; i++) {
746 DIA_RENDERER_GET_CLASS(renderer)->draw_line(renderer, &startpoint, &bez->points[i].p1,
747 &line_colour);
748 DIA_RENDERER_GET_CLASS(renderer)->draw_line(renderer, &bez->points[i].p2, &bez->points[i].p3,
749 &line_colour);
750 startpoint = bez->points[i].p3;
754 /** Create all handles used by a bezier conn.
755 * @param bez A bezierconn object initialized with room for 3*num_points-2
756 * handles.
757 * @param num_points The number of points of the bezierconn.
759 void
760 new_handles(BezierConn *bez, int num_points)
762 DiaObject *obj;
763 int i;
765 obj = &bez->object;
767 obj->handles[0] = g_new(Handle,1);
768 obj->handles[0]->connect_type = HANDLE_CONNECTABLE;
769 obj->handles[0]->connected_to = NULL;
770 obj->handles[0]->type = HANDLE_MAJOR_CONTROL;
771 obj->handles[0]->id = HANDLE_MOVE_STARTPOINT;
773 for (i = 1; i < num_points; i++) {
774 obj->handles[3*i-2] = g_new(Handle, 1);
775 obj->handles[3*i-1] = g_new(Handle, 1);
776 obj->handles[3*i] = g_new(Handle, 1);
778 setup_handle(obj->handles[3*i-2], HANDLE_RIGHTCTRL);
779 setup_handle(obj->handles[3*i-1], HANDLE_LEFTCTRL);
781 obj->handles[3*i]->connect_type = HANDLE_CONNECTABLE;
782 obj->handles[3*i]->connected_to = NULL;
783 obj->handles[3*i]->type = HANDLE_MAJOR_CONTROL;
784 obj->handles[3*i]->id = HANDLE_MOVE_ENDPOINT;
788 /** Initialize a bezierconn object with the given amount of points.
789 * The points array of the bezierconn object should be previously
790 * initialized with appropriate positions.
791 * This will set up handles and make all corners symmetric.
792 * @param bez A newly allocated bezierconn object.
793 * @param num_points The initial number of points on the curve.
795 void
796 bezierconn_init(BezierConn *bez, int num_points)
798 DiaObject *obj;
799 int i;
801 obj = &bez->object;
803 object_init(obj, 3*num_points-2, 0);
805 bez->numpoints = num_points;
807 bez->points = g_new(BezPoint, num_points);
808 bez->corner_types = g_new(BezCornerType, num_points);
809 bez->points[0].type = BEZ_MOVE_TO;
810 bez->corner_types[0] = BEZ_CORNER_SYMMETRIC;
811 for (i = 1; i < num_points; i++) {
812 bez->points[i].type = BEZ_CURVE_TO;
813 bez->corner_types[i] = BEZ_CORNER_SYMMETRIC;
816 new_handles(bez, num_points);
818 bezierconn_update_data(bez);
821 /** Set a bezierconn to use the given array of points.
822 * This function does *not* set up handles
823 * @param bez A bezierconn to operate on
824 * @param num_points The number of points in the `points' array.
825 * @param points The new points that this bezier should be set to use.
827 void
828 bezierconn_set_points(BezierConn *bez, int num_points, BezPoint *points)
830 int i;
832 bez->numpoints = num_points;
834 if (bez->points)
835 g_free(bez->points);
837 bez->points = g_malloc((bez->numpoints)*sizeof(BezPoint));
839 for (i=0;i<bez->numpoints;i++) {
840 bez->points[i] = points[i];
845 /** Copy a bezierconn objects. This function in turn invokes object_copy.
846 * @param from The object to copy from.
847 * @param to The object to copy to.
849 void
850 bezierconn_copy(BezierConn *from, BezierConn *to)
852 int i;
853 DiaObject *toobj, *fromobj;
855 toobj = &to->object;
856 fromobj = &from->object;
858 object_copy(fromobj, toobj);
860 to->numpoints = from->numpoints;
862 to->points = g_new(BezPoint, to->numpoints);
863 to->corner_types = g_new(BezCornerType, to->numpoints);
865 for (i = 0; i < to->numpoints; i++) {
866 to->points[i] = from->points[i];
867 to->corner_types[i] = from->corner_types[i];
870 to->object.handles[0] = g_new(Handle,1);
871 *to->object.handles[0] = *from->object.handles[0];
872 for (i = 1; i < to->object.num_handles - 1; i++) {
873 to->object.handles[i] = g_new(Handle, 1);
874 setup_handle(to->object.handles[i], from->object.handles[i]->id);
876 to->object.handles[to->object.num_handles-1] = g_new(Handle,1);
877 *to->object.handles[to->object.num_handles-1] =
878 *from->object.handles[to->object.num_handles-1];
880 memcpy(&to->extra_spacing,&from->extra_spacing,sizeof(to->extra_spacing));
881 bezierconn_update_data(to);
884 /** Destroy a bezierconn object.
885 * @param bez An object to destroy. This function in turn calls object_destroy
886 * and frees structures allocated by this bezierconn, but not the object itself
888 void
889 bezierconn_destroy(BezierConn *bez)
891 int i, nh;
892 Handle **temp_handles;
894 /* Need to store these temporary since object.handles is
895 freed by object_destroy() */
896 nh = bez->object.num_handles;
897 temp_handles = g_new(Handle *, nh);
898 for (i = 0; i < nh; i++)
899 temp_handles[i] = bez->object.handles[i];
901 object_destroy(&bez->object);
903 for (i = 0; i < nh; i++)
904 g_free(temp_handles[i]);
905 g_free(temp_handles);
907 g_free(bez->points);
908 g_free(bez->corner_types);
912 /** Save the data defined by a bezierconn object to XML.
913 * @param bez The object to save.
914 * @param obj_node The XML node to save it into
915 * @bug shouldn't this have version?
917 void
918 bezierconn_save(BezierConn *bez, ObjectNode obj_node)
920 int i;
921 AttributeNode attr;
923 object_save(&bez->object, obj_node);
925 attr = new_attribute(obj_node, "bez_points");
927 data_add_point(attr, &bez->points[0].p1);
928 for (i = 1; i < bez->numpoints; i++) {
929 data_add_point(attr, &bez->points[i].p1);
930 data_add_point(attr, &bez->points[i].p2);
931 data_add_point(attr, &bez->points[i].p3);
934 attr = new_attribute(obj_node, "corner_types");
935 for (i = 0; i < bez->numpoints; i++)
936 data_add_enum(attr, bez->corner_types[i]);
939 /** Load a bezierconn object from XML.
940 * Does object_init() on the bezierconn object.
941 * @param bez A newly allocated bezierconn object to load into.
942 * @param obj_node The XML node to load from.
943 * @bug shouldn't this have version?
944 * @bug Couldn't this use the setup_handles function defined above?
946 void
947 bezierconn_load(BezierConn *bez, ObjectNode obj_node)
949 int i;
950 AttributeNode attr;
951 DataNode data;
953 DiaObject *obj = &bez->object;
955 object_load(obj, obj_node);
957 attr = object_find_attribute(obj_node, "bez_points");
959 if (attr != NULL)
960 bez->numpoints = (attribute_num_data(attr) + 2)/3;
961 else
962 bez->numpoints = 0;
964 object_init(obj, 3 * bez->numpoints - 2, 0);
966 data = attribute_first_data(attr);
967 if (bez->numpoints != 0) {
968 bez->points = g_new(BezPoint, bez->numpoints);
969 bez->points[0].type = BEZ_MOVE_TO;
970 data_point(data, &bez->points[0].p1);
971 data = data_next(data);
973 for (i = 1; i < bez->numpoints; i++) {
974 bez->points[i].type = BEZ_CURVE_TO;
975 data_point(data, &bez->points[i].p1);
976 data = data_next(data);
977 data_point(data, &bez->points[i].p2);
978 data = data_next(data);
979 data_point(data, &bez->points[i].p3);
980 data = data_next(data);
984 bez->corner_types = g_new(BezCornerType, bez->numpoints);
986 attr = object_find_attribute(obj_node, "corner_types");
987 /* if corner_types is missing or corrupt */
988 if (!attr || attribute_num_data(attr) != bez->numpoints) {
989 for (i = 0; i < bez->numpoints; i++) {
990 bez->corner_types[i] = BEZ_CORNER_SYMMETRIC;
992 } else {
993 data = attribute_first_data(attr);
994 for (i = 0; i < bez->numpoints; i++) {
995 bez->corner_types[i] = data_enum(data);
996 data = data_next(data);
1000 obj->handles[0] = g_new(Handle, 1);
1001 obj->handles[0]->connect_type = HANDLE_CONNECTABLE;
1002 obj->handles[0]->connected_to = NULL;
1003 obj->handles[0]->type = HANDLE_MAJOR_CONTROL;
1004 obj->handles[0]->id = HANDLE_MOVE_STARTPOINT;
1006 for (i = 1; i < bez->numpoints; i++) {
1007 obj->handles[3*i-2] = g_new(Handle, 1);
1008 setup_handle(obj->handles[3*i-2], HANDLE_RIGHTCTRL);
1009 obj->handles[3*i-1] = g_new(Handle, 1);
1010 setup_handle(obj->handles[3*i-1], HANDLE_LEFTCTRL);
1011 obj->handles[3*i] = g_new(Handle, 1);
1012 setup_handle(obj->handles[3*i], HANDLE_BEZMAJOR);
1015 obj->handles[obj->num_handles-1]->connect_type = HANDLE_CONNECTABLE;
1016 obj->handles[obj->num_handles-1]->connected_to = NULL;
1017 obj->handles[obj->num_handles-1]->type = HANDLE_MAJOR_CONTROL;
1018 obj->handles[obj->num_handles-1]->id = HANDLE_MOVE_ENDPOINT;
1020 bezierconn_update_data(bez);
1023 /*** Undo support ***/
1025 /** Free undo information about adding or removing points.
1026 * @param change The undo information to free.
1028 static void
1029 bezierconn_point_change_free(struct PointChange *change)
1031 if ( (change->type==TYPE_ADD_POINT && !change->applied) ||
1032 (change->type==TYPE_REMOVE_POINT && change->applied) ){
1033 g_free(change->handle1);
1034 g_free(change->handle2);
1035 g_free(change->handle3);
1036 change->handle1 = NULL;
1037 change->handle2 = NULL;
1038 change->handle3 = NULL;
1042 /** Apply a point addition/removal.
1043 * @param change The change to apply.
1044 * @param obj The object (must be a BezierConn) to apply the change to.
1046 static void
1047 bezierconn_point_change_apply(struct PointChange *change, DiaObject *obj)
1049 change->applied = 1;
1050 switch (change->type) {
1051 case TYPE_ADD_POINT:
1052 add_handles((BezierConn *)obj, change->pos, &change->point,
1053 change->corner_type,
1054 change->handle1, change->handle2, change->handle3);
1055 break;
1056 case TYPE_REMOVE_POINT:
1057 object_unconnect(obj, change->handle1);
1058 object_unconnect(obj, change->handle2);
1059 object_unconnect(obj, change->handle3);
1060 remove_handles((BezierConn *)obj, change->pos);
1061 break;
1065 /** Revert (unapply) a point addition/removal.
1066 * @param change The change to revert.
1067 * @param obj The object (must be a BezierConn) to revert the change of.
1069 static void
1070 bezierconn_point_change_revert(struct PointChange *change, DiaObject *obj)
1072 switch (change->type) {
1073 case TYPE_ADD_POINT:
1074 remove_handles((BezierConn *)obj, change->pos);
1075 break;
1076 case TYPE_REMOVE_POINT:
1077 add_handles((BezierConn *)obj, change->pos, &change->point,
1078 change->corner_type,
1079 change->handle1, change->handle2, change->handle3);
1081 if (change->connected_to1)
1082 object_connect(obj, change->handle1, change->connected_to1);
1083 if (change->connected_to2)
1084 object_connect(obj, change->handle2, change->connected_to2);
1085 if (change->connected_to3)
1086 object_connect(obj, change->handle3, change->connected_to3);
1088 break;
1090 change->applied = 0;
1093 /** Create undo information about a point being added or removed.
1094 * @param bez The object that the change applies to (ignored)
1095 * @param type The type of change (either TYPE_ADD_POINT or TYPE_REMOVE_POINT)
1096 * @param point The point being added or removed.
1097 * @param corner_type Which type of corner is at the point.
1098 * @param pos The position of the point.
1099 * @param handle1 The first (central) handle.
1100 * @param connected_to1 What the first handle is connected to.
1101 * @param handle2 The second (left-hand) handle.
1102 * @param connected_to2 What the second handle is connected to.
1103 * @param handle3 The third (right-hand) handle.
1104 * @param connected_to3 What the third handle is connected to.
1105 * @return Newly created undo information.
1106 * @bug Describe what the state of the point and handles should be at start.
1107 * @bug Can these handles be connected to anything at all?
1109 static ObjectChange *
1110 bezierconn_create_point_change(BezierConn *bez, enum change_type type,
1111 BezPoint *point, BezCornerType corner_type,
1112 int pos,
1113 Handle *handle1, ConnectionPoint *connected_to1,
1114 Handle *handle2, ConnectionPoint *connected_to2,
1115 Handle *handle3, ConnectionPoint *connected_to3)
1117 struct PointChange *change;
1119 change = g_new(struct PointChange, 1);
1121 change->obj_change.apply = (ObjectChangeApplyFunc) bezierconn_point_change_apply;
1122 change->obj_change.revert = (ObjectChangeRevertFunc) bezierconn_point_change_revert;
1123 change->obj_change.free = (ObjectChangeFreeFunc) bezierconn_point_change_free;
1125 change->type = type;
1126 change->applied = 1;
1127 change->point = *point;
1128 change->corner_type = corner_type;
1129 change->pos = pos;
1130 change->handle1 = handle1;
1131 change->connected_to1 = connected_to1;
1132 change->handle2 = handle2;
1133 change->connected_to2 = connected_to2;
1134 change->handle3 = handle3;
1135 change->connected_to3 = connected_to3;
1137 return (ObjectChange *)change;
1140 /** Apply a change of corner type. This may change the position of the
1141 * control handles by calling bezierconn_straighten_corner.
1142 * @param change The undo information to apply.
1143 * @param obj The object to apply the undo information too.
1145 static void
1146 bezierconn_corner_change_apply(struct CornerChange *change,
1147 DiaObject *obj)
1149 BezierConn *bez = (BezierConn *)obj;
1150 int handle_nr = get_handle_nr(bez, change->handle);
1151 int comp_nr = get_major_nr(handle_nr);
1153 bezierconn_straighten_corner(bez, comp_nr);
1155 bez->corner_types[comp_nr] = change->new_type;
1157 change->applied = 1;
1160 /** Revert (unapply) a change of corner type. This may move the position
1161 * of the control handles to what they were before applying.
1162 * @param change Undo information to apply.
1163 * @param obj The bezierconn object to apply the change to.
1165 static void
1166 bezierconn_corner_change_revert(struct CornerChange *change,
1167 DiaObject *obj)
1169 BezierConn *bez = (BezierConn *)obj;
1170 int handle_nr = get_handle_nr(bez, change->handle);
1171 int comp_nr = get_major_nr(handle_nr);
1173 bez->points[comp_nr].p2 = change->point_left;
1174 bez->points[comp_nr+1].p1 = change->point_right;
1175 bez->corner_types[comp_nr] = change->old_type;
1177 change->applied = 0;
1180 /** Create new undo information about a changing the type of a corner.
1181 * Note that the created ObjectChange object has nothing in it that needs
1182 * freeing.
1183 * @param bez The bezierconn object this applies to.
1184 * @param handle The handle of the corner being changed.
1185 * @param point_left The position of the left control handle.
1186 * @param point_right The position of the right control handle.
1187 * @param old_corner_type The corner type before applying.
1188 * @param new_corner_type The corner type being changed to.
1189 * @returns Newly allocated undo information.
1191 static ObjectChange *
1192 bezierconn_create_corner_change(BezierConn *bez, Handle *handle,
1193 Point *point_left, Point *point_right,
1194 BezCornerType old_corner_type,
1195 BezCornerType new_corner_type)
1197 struct CornerChange *change;
1199 change = g_new(struct CornerChange, 1);
1201 change->obj_change.apply = (ObjectChangeApplyFunc) bezierconn_corner_change_apply;
1202 change->obj_change.revert = (ObjectChangeRevertFunc) bezierconn_corner_change_revert;
1203 change->obj_change.free = (ObjectChangeFreeFunc) NULL;
1205 change->old_type = old_corner_type;
1206 change->new_type = new_corner_type;
1207 change->applied = 1;
1209 change->handle = handle;
1210 change->point_left = *point_left;
1211 change->point_right = *point_right;
1213 return (ObjectChange *)change;