1 /* Dia -- an diagram creation/manipulation program
2  * Copyright (C) 1999 Alexander Larsson
3  *
4  * BezierConn  Copyright (C) 1999 James Henstridge
5  *
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.
10  *
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.
15  *
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.
19  */
20 
21  /** \file bezier_conn.c Allows to construct object consisting of bezier lines */
22 
23 #include <config.h>
24 
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <math.h>
28 #include <string.h> /* memcpy() */
29 
30 #include "bezier_conn.h"
31 #include "intl.h"
32 #include "message.h"
33 #include "diarenderer.h"
34 
35 #define HANDLE_BEZMAJOR  (HANDLE_CUSTOM1)
36 #define HANDLE_LEFTCTRL  (HANDLE_CUSTOM2)
37 #define HANDLE_RIGHTCTRL (HANDLE_CUSTOM3)
38 
39 enum change_type {
40   TYPE_ADD_POINT,
41   TYPE_REMOVE_POINT
42 };
43 
44 struct PointChange {
45   ObjectChange obj_change;
46 
47   enum change_type type;
48   int applied;
49 
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;
58 };
59 
60 struct CornerChange {
61   ObjectChange obj_change;
62   /* Only one kind of corner_change */
63   int applied;
64 
65   Handle *handle;
66   /* Old places when SET_CORNER_TYPE is applied */
67   Point point_left, point_right;
68   BezCornerType old_type, new_type;
69 };
70 
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);
83 
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)
87  */
88 static void
setup_handle(Handle * handle,HandleId id)89 setup_handle(Handle *handle, HandleId id)
90 {
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;
96 }
97 
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.
103  */
104 static int
get_handle_nr(BezierConn * bez,Handle * handle)105 get_handle_nr(BezierConn *bez, Handle *handle)
106 {
107   int i = 0;
108   for (i=0;i<bez->object.num_handles;i++) {
109     if (bez->object.handles[i] == handle)
110       return i;
111   }
112   return -1;
113 }
114 
115 void new_handles(BezierConn *bez, int num_points);
116 
117 #define get_comp_nr(hnum) (((int)(hnum)+2)/3)
118 #define get_major_nr(hnum) (((int)(hnum)+1)/3)
119 
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
131  */
132 ObjectChange*
bezierconn_move_handle(BezierConn * bez,Handle * handle,Point * to,ConnectionPoint * cp,HandleMoveReason reason,ModifierKeys modifiers)133 bezierconn_move_handle(BezierConn *bez, Handle *handle,
134 		       Point *to, ConnectionPoint *cp,
135 		       HandleMoveReason reason, ModifierKeys modifiers)
136 {
137   int handle_nr, comp_nr;
138   Point delta, pt;
139 
140   delta = *to;
141   point_sub(&delta, &handle->pos);
142 
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;
186       }
187       case BEZ_CORNER_CUSP:
188 	/* Do nothing to the other guy */
189 	break;
190       }
191     }
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;
217       }
218       case BEZ_CORNER_CUSP:
219 	/* Do nothing to the other guy */
220 	break;
221       }
222     }
223     break;
224   default:
225     message_error("Internal error in bezierconn_move_handle.\n");
226     break;
227   }
228   return NULL;
229 }
230 
231 
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
237  */
238 ObjectChange*
bezierconn_move(BezierConn * bez,Point * to)239 bezierconn_move(BezierConn *bez, Point *to)
240 {
241   Point p;
242   int i;
243 
244   p = *to;
245   point_sub(&p, &bez->points[0].p1);
246 
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);
252   }
253   return NULL;
254 }
255 
256 
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.
262  */
263 int
bezierconn_closest_segment(BezierConn * bez,Point * point,real line_width)264 bezierconn_closest_segment(BezierConn *bez, Point *point, real line_width)
265 {
266   Point last;
267   int i;
268   real dist = G_MAXDOUBLE;
269   int closest;
270 
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;
280     }
281     last = bez->points[i+1].p3;
282   }
283   return closest;
284 }
285 
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?
291  */
292 Handle *
bezierconn_closest_handle(BezierConn * bez,Point * point)293 bezierconn_closest_handle(BezierConn *bez, Point *point)
294 {
295   int i, hn;
296   real dist;
297   Handle *closest;
298 
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;
303 
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];
308     }
309     hn++;
310 
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];
315     }
316     hn++;
317 
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];
322     }
323   }
324   return closest;
325 }
326 
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.
337  */
338 Handle *
bezierconn_closest_major_handle(BezierConn * bez,Point * point)339 bezierconn_closest_major_handle(BezierConn *bez, Point *point)
340 {
341   Handle *closest = bezierconn_closest_handle(bez, point);
342 
343   return bez->object.handles[3*get_major_nr(get_handle_nr(bez, closest))];
344 }
345 
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.
351  */
352 real
bezierconn_distance_from(BezierConn * bez,Point * point,real line_width)353 bezierconn_distance_from(BezierConn *bez, Point *point, real line_width)
354 {
355   return distance_bez_line_point(bez->points, bez->numpoints,
356 				 line_width, point);
357 }
358 
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.
369  */
370 static void
add_handles(BezierConn * bez,int pos,BezPoint * point,BezCornerType corner_type,Handle * handle1,Handle * handle2,Handle * handle3)371 add_handles(BezierConn *bez, int pos, BezPoint *point,
372 	    BezCornerType corner_type, Handle *handle1,
373 	    Handle *handle2, Handle *handle3)
374 {
375   int i;
376   DiaObject *obj;
377 
378   g_assert(pos > 0);
379 
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));
385 
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];
389   }
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);
397 
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;
401   }
402 }
403 
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.
408  */
409 static void
remove_handles(BezierConn * bez,int pos)410 remove_handles(BezierConn *bez, int pos)
411 {
412   int i;
413   DiaObject *obj;
414   Handle *old_handle1, *old_handle2, *old_handle3;
415   Point tmppoint;
416 
417   g_assert(pos > 0);
418 
419   obj = (DiaObject *)bez;
420 
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;
424   }
425 
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];
432   }
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));
437 
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);
444 }
445 
446 
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.
454  */
455 ObjectChange *
bezierconn_add_segment(BezierConn * bez,int segment,Point * point)456 bezierconn_add_segment(BezierConn *bez, int segment, Point *point)
457 {
458   BezPoint realpoint;
459   BezCornerType corner_type = BEZ_CORNER_SYMMETRIC;
460   Handle *new_handle1, *new_handle2, *new_handle3;
461   Point startpoint;
462 
463   if (segment == 0)
464     startpoint = bez->points[0].p1;
465   else
466     startpoint = bez->points[segment].p3;
467 
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;
482   }
483   realpoint.type = BEZ_CURVE_TO;
484 
485   new_handle1 = g_new0(Handle,1);
486   new_handle2 = g_new0(Handle,1);
487   new_handle3 = g_new0(Handle,1);
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);
498 }
499 
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.
504  */
505 ObjectChange *
bezierconn_remove_segment(BezierConn * bez,int pos)506 bezierconn_remove_segment(BezierConn *bez, int pos)
507 {
508   Handle *old_handle1, *old_handle2, *old_handle3;
509   ConnectionPoint *cpt1, *cpt2, *cpt3;
510   BezPoint old_point;
511   BezCornerType old_ctype;
512 
513   g_assert(pos > 0);
514   g_assert(bez->numpoints > 2);
515 
516   if (pos == bez->numpoints-1) pos--;
517 
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];
523 
524   cpt1 = old_handle1->connected_to;
525   cpt2 = old_handle2->connected_to;
526   cpt3 = old_handle3->connected_to;
527 
528   object_unconnect((DiaObject *)bez, old_handle1);
529   object_unconnect((DiaObject *)bez, old_handle2);
530   object_unconnect((DiaObject *)bez, old_handle3);
531 
532   remove_handles(bez, pos);
533 
534   bezierconn_update_data(bez);
535 
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);
541 }
542 
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?
549  */
550 static void
bezierconn_straighten_corner(BezierConn * bez,int comp_nr)551 bezierconn_straighten_corner(BezierConn *bez, int comp_nr)
552 {
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);
573   }
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);
599   }
600     break;
601   case BEZ_CORNER_CUSP:
602     break;
603   }
604 }
605 
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.
611  */
612 ObjectChange *
bezierconn_set_corner_type(BezierConn * bez,Handle * handle,BezCornerType corner_type)613 bezierconn_set_corner_type(BezierConn *bez, Handle *handle,
614 			   BezCornerType corner_type)
615 {
616   Handle *mid_handle;
617   Point old_left, old_right;
618   int old_type;
619   int handle_nr, comp_nr;
620 
621   handle_nr = get_handle_nr(bez, handle);
622 
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;
638   }
639 
640   comp_nr = get_major_nr(handle_nr);
641 
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;
645 
646   bez->corner_types[comp_nr] = corner_type;
647 
648   bezierconn_straighten_corner(bez, comp_nr);
649 
650   return bezierconn_create_corner_change(bez, mid_handle, &old_left, &old_right,
651 					 old_type, corner_type);
652 }
653 
654 /** Update handle array and handle positions after changes.
655  * @param bez A bezierconn to update.
656  */
657 void
bezierconn_update_data(BezierConn * bez)658 bezierconn_update_data(BezierConn *bez)
659 {
660   int i;
661   DiaObject *obj = &bez->object;
662 
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);
666 
667     for (i = 0; i < obj->num_handles; i++)
668       g_free(obj->handles[i]);
669     g_free(obj->handles);
670 
671     obj->num_handles = 3*bez->numpoints-2;
672     obj->handles = g_new(Handle*, obj->num_handles);
673 
674     new_handles(bez, bez->numpoints);
675   }
676 
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;
683   }
684 }
685 
686 /** Update the boundingbox of the connection.
687  * @param bez A bezier line to update bounding box for.
688  */
689 void
bezierconn_update_boundingbox(BezierConn * bez)690 bezierconn_update_boundingbox(BezierConn *bez)
691 {
692   g_assert(bez != NULL);
693 
694   polybezier_bbox(&bez->points[0],
695                   bez->numpoints,
696                   &bez->extra_spacing, FALSE,
697                   &bez->object.bounding_box);
698 }
699 
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.
706  */
707 void
bezierconn_simple_draw(BezierConn * bez,DiaRenderer * renderer,real width)708 bezierconn_simple_draw(BezierConn *bez, DiaRenderer *renderer, real width)
709 {
710   BezPoint *points;
711 
712   g_assert(bez != NULL);
713   g_assert(renderer != NULL);
714 
715   points = &bez->points[0];
716 
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);
721 
722   DIA_RENDERER_GET_CLASS(renderer)->draw_bezier(renderer, points, bez->numpoints, &color_black);
723 }
724 
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.
729  */
730 void
bezierconn_draw_control_lines(BezierConn * bez,DiaRenderer * renderer)731 bezierconn_draw_control_lines(BezierConn *bez, DiaRenderer *renderer)
732 {
733   Color line_colour = {0.0, 0.0, 0.6};
734   Point startpoint;
735   int i;
736 
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);
743 
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;
751   }
752 }
753 
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.
758  */
759 void
new_handles(BezierConn * bez,int num_points)760 new_handles(BezierConn *bez, int num_points)
761 {
762   DiaObject *obj;
763   int i;
764 
765   obj = &bez->object;
766 
767   obj->handles[0] = g_new0(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;
772 
773   for (i = 1; i < num_points; i++) {
774     obj->handles[3*i-2] = g_new0(Handle,1);
775     obj->handles[3*i-1] = g_new0(Handle,1);
776     obj->handles[3*i] = g_new0(Handle,1);
777 
778     setup_handle(obj->handles[3*i-2], HANDLE_RIGHTCTRL);
779     setup_handle(obj->handles[3*i-1], HANDLE_LEFTCTRL);
780 
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;
785   }
786 }
787 
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.
794  */
795 void
bezierconn_init(BezierConn * bez,int num_points)796 bezierconn_init(BezierConn *bez, int num_points)
797 {
798   DiaObject *obj;
799   int i;
800 
801   obj = &bez->object;
802 
803   object_init(obj, 3*num_points-2, 0);
804 
805   bez->numpoints = num_points;
806 
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;
814   }
815 
816   new_handles(bez, num_points);
817 
818   bezierconn_update_data(bez);
819 }
820 
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.
826  */
827 void
bezierconn_set_points(BezierConn * bez,int num_points,BezPoint * points)828 bezierconn_set_points(BezierConn *bez, int num_points, BezPoint *points)
829 {
830   int i;
831 
832   bez->numpoints = num_points;
833 
834   if (bez->points)
835     g_free(bez->points);
836 
837   bez->points = g_malloc((bez->numpoints)*sizeof(BezPoint));
838 
839   for (i=0;i<bez->numpoints;i++) {
840     bez->points[i] = points[i];
841   }
842 }
843 
844 
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.
848  */
849 void
bezierconn_copy(BezierConn * from,BezierConn * to)850 bezierconn_copy(BezierConn *from, BezierConn *to)
851 {
852   int i;
853   DiaObject *toobj, *fromobj;
854 
855   toobj = &to->object;
856   fromobj = &from->object;
857 
858   object_copy(fromobj, toobj);
859 
860   to->numpoints = from->numpoints;
861 
862   to->points = g_new(BezPoint, to->numpoints);
863   to->corner_types = g_new(BezCornerType, to->numpoints);
864 
865   for (i = 0; i < to->numpoints; i++) {
866     to->points[i] = from->points[i];
867     to->corner_types[i] = from->corner_types[i];
868   }
869 
870   to->object.handles[0] = g_new0(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_new0(Handle,1);
874     setup_handle(to->object.handles[i], from->object.handles[i]->id);
875   }
876   to->object.handles[to->object.num_handles-1] = g_new0(Handle,1);
877   *to->object.handles[to->object.num_handles-1] =
878     *from->object.handles[to->object.num_handles-1];
879 
880   memcpy(&to->extra_spacing,&from->extra_spacing,sizeof(to->extra_spacing));
881   bezierconn_update_data(to);
882 }
883 
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
887  */
888 void
bezierconn_destroy(BezierConn * bez)889 bezierconn_destroy(BezierConn *bez)
890 {
891   int i, nh;
892   Handle **temp_handles;
893 
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];
900 
901   object_destroy(&bez->object);
902 
903   for (i = 0; i < nh; i++)
904     g_free(temp_handles[i]);
905   g_free(temp_handles);
906 
907   g_free(bez->points);
908   g_free(bez->corner_types);
909 }
910 
911 
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?
916  */
917 void
bezierconn_save(BezierConn * bez,ObjectNode obj_node)918 bezierconn_save(BezierConn *bez, ObjectNode obj_node)
919 {
920   int i;
921   AttributeNode attr;
922 
923   object_save(&bez->object, obj_node);
924 
925   attr = new_attribute(obj_node, "bez_points");
926 
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);
932   }
933 
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]);
937 }
938 
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?
945  */
946 void
bezierconn_load(BezierConn * bez,ObjectNode obj_node)947 bezierconn_load(BezierConn *bez, ObjectNode obj_node)
948 {
949   int i;
950   AttributeNode attr;
951   DataNode data;
952 
953   DiaObject *obj = &bez->object;
954 
955   object_load(obj, obj_node);
956 
957   attr = object_find_attribute(obj_node, "bez_points");
958 
959   if (attr != NULL)
960     bez->numpoints = (attribute_num_data(attr) + 2)/3;
961   else
962     bez->numpoints = 0;
963 
964   object_init(obj, 3 * bez->numpoints - 2, 0);
965 
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);
972 
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);
981     }
982   }
983 
984   bez->corner_types = g_new(BezCornerType, bez->numpoints);
985 
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;
991     }
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);
997     }
998   }
999 
1000   obj->handles[0] = g_new0(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;
1005 
1006   for (i = 1; i < bez->numpoints; i++) {
1007     obj->handles[3*i-2] = g_new0(Handle,1);
1008     setup_handle(obj->handles[3*i-2], HANDLE_RIGHTCTRL);
1009     obj->handles[3*i-1] = g_new0(Handle,1);
1010     setup_handle(obj->handles[3*i-1], HANDLE_LEFTCTRL);
1011     obj->handles[3*i]   = g_new0(Handle,1);
1012     setup_handle(obj->handles[3*i],   HANDLE_BEZMAJOR);
1013   }
1014 
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;
1019 
1020   bezierconn_update_data(bez);
1021 }
1022 
1023 /*** Undo support ***/
1024 
1025 /** Free undo information about adding or removing points.
1026  * @param change The undo information to free.
1027  */
1028 static void
bezierconn_point_change_free(struct PointChange * change)1029 bezierconn_point_change_free(struct PointChange *change)
1030 {
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;
1039   }
1040 }
1041 
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.
1045  */
1046 static void
bezierconn_point_change_apply(struct PointChange * change,DiaObject * obj)1047 bezierconn_point_change_apply(struct PointChange *change, DiaObject *obj)
1048 {
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;
1062   }
1063 }
1064 
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.
1068  */
1069 static void
bezierconn_point_change_revert(struct PointChange * change,DiaObject * obj)1070 bezierconn_point_change_revert(struct PointChange *change, DiaObject *obj)
1071 {
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);
1080 
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);
1087 
1088     break;
1089   }
1090   change->applied = 0;
1091 }
1092 
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?
1108  */
1109 static ObjectChange *
bezierconn_create_point_change(BezierConn * bez,enum change_type type,BezPoint * point,BezCornerType corner_type,int pos,Handle * handle1,ConnectionPoint * connected_to1,Handle * handle2,ConnectionPoint * connected_to2,Handle * handle3,ConnectionPoint * connected_to3)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)
1116 {
1117   struct PointChange *change;
1118 
1119   change = g_new(struct PointChange, 1);
1120 
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;
1124 
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;
1136 
1137   return (ObjectChange *)change;
1138 }
1139 
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.
1144  */
1145 static void
bezierconn_corner_change_apply(struct CornerChange * change,DiaObject * obj)1146 bezierconn_corner_change_apply(struct CornerChange *change,
1147 			       DiaObject *obj)
1148 {
1149   BezierConn *bez = (BezierConn *)obj;
1150   int handle_nr = get_handle_nr(bez, change->handle);
1151   int comp_nr = get_major_nr(handle_nr);
1152 
1153   bezierconn_straighten_corner(bez, comp_nr);
1154 
1155   bez->corner_types[comp_nr] = change->new_type;
1156 
1157   change->applied = 1;
1158 }
1159 
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.
1164  */
1165 static void
bezierconn_corner_change_revert(struct CornerChange * change,DiaObject * obj)1166 bezierconn_corner_change_revert(struct CornerChange *change,
1167 				DiaObject *obj)
1168 {
1169   BezierConn *bez = (BezierConn *)obj;
1170   int handle_nr = get_handle_nr(bez, change->handle);
1171   int comp_nr = get_major_nr(handle_nr);
1172 
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;
1176 
1177   change->applied = 0;
1178 }
1179 
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.
1190  */
1191 static ObjectChange *
bezierconn_create_corner_change(BezierConn * bez,Handle * handle,Point * point_left,Point * point_right,BezCornerType old_corner_type,BezCornerType new_corner_type)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)
1196 {
1197   struct CornerChange *change;
1198 
1199   change = g_new(struct CornerChange, 1);
1200 
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;
1204 
1205   change->old_type = old_corner_type;
1206   change->new_type = new_corner_type;
1207   change->applied = 1;
1208 
1209   change->handle = handle;
1210   change->point_left = *point_left;
1211   change->point_right = *point_right;
1212 
1213   return (ObjectChange *)change;
1214 }
1215