missing NULL terminator in set_config_x
[geda-gaf.git] / gschem / src / o_path.c
blob154c221e11f97ae20b4d8195736ab260c1cba6af
1 /* gEDA - GPL Electronic Design Automation
2 * gschem - gEDA Schematic Capture
3 * Copyright (C) 1998-2010 Ales Hvezda
4 * Copyright (C) 1998-2020 gEDA Contributors (see ChangeLog for details)
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20 #include <config.h>
22 #include <stdio.h>
23 #include <math.h>
24 #include <cairo.h>
26 #include "gschem.h"
28 #define NUM_BEZIER_SEGMENTS 100
31 typedef void (*FILL_FUNC) (GschemToplevel *w_current,
32 COLOR *color, PATH *path,
33 gint fill_width,
34 gint angle1, gint pitch1, gint angle2, gint pitch2);
36 static PATH *path_copy_modify (PATH *path, int dx, int dy,
37 int new_x, int new_y, int whichone)
39 PATH *new_path;
40 int x1, y1, x2, y2, x3, y3;
41 int i;
42 int grip_no = 0;
44 new_path = g_malloc (sizeof (PATH));
45 new_path->sections = g_malloc (path->num_sections * sizeof (PATH_SECTION));
46 new_path->num_sections = path->num_sections;
47 new_path->num_sections_max = path->num_sections;
49 for (i = 0; i < path->num_sections; i++) {
50 PATH_SECTION *section = &path->sections[i];
51 PATH_SECTION *new_section = &new_path->sections[i];
53 x1 = section->x1 + dx; y1 = section->y1 + dy;
54 x2 = section->x2 + dx; y2 = section->y2 + dy;
55 x3 = section->x3 + dx; y3 = section->y3 + dy;
57 switch (section->code) {
58 case PATH_CURVETO:
59 /* Two control point grips */
60 if (whichone == grip_no++) {
61 x1 = new_x; y1 = new_y;
63 if (whichone == grip_no++) {
64 x2 = new_x; y2 = new_y;
66 /* Fall through */
67 case PATH_MOVETO:
68 case PATH_MOVETO_OPEN:
69 case PATH_LINETO:
70 /* Destination point grip */
71 if (whichone == grip_no++) {
72 x3 = new_x; y3 = new_y;
74 case PATH_END:
75 break;
78 new_section->code = section->code;
79 new_section->x1 = x1; new_section->y1 = y1;
80 new_section->x2 = x2; new_section->y2 = y2;
81 new_section->x3 = x3; new_section->y3 = y3;
83 return new_path;
86 /*! \brief Calculate path bounding box for rubber purposes
87 * \par Function Description
88 * Calculate the bounding box of \a path, returning its bounds in \a
89 * min_x, \a max_y, \a max_x and \a min_y. If \a path is NULL, the
90 * PATH object currently being edited is used, with any required
91 * control point changes applied.
93 static void
94 path_rubber_bbox (GschemToplevel *w_current, PATH *path,
95 int *min_x, int *max_y, int *max_x, int *min_y)
97 int x1, y1, x2, y2, x3, y3;
98 int new_x, new_y, whichone;
99 int grip_no = 0;
100 int i;
102 g_assert (w_current);
104 if (path == NULL)
105 path = w_current->which_object->path;
107 *min_x = G_MAXINT; *max_x = G_MININT;
108 *min_y = G_MAXINT; *max_y = G_MININT;
110 new_x = w_current->second_wx;
111 new_y = w_current->second_wy;
112 whichone = w_current->which_grip;
114 for (i = 0; i < path->num_sections; i++) {
115 PATH_SECTION *section = &path->sections[i];
117 x1 = section->x1; y1 = section->y1;
118 x2 = section->x2; y2 = section->y2;
119 x3 = section->x3; y3 = section->y3;
121 switch (section->code) {
122 case PATH_CURVETO:
123 /* Two control point grips */
124 if (whichone == grip_no++) {
125 x1 = new_x; y1 = new_y;
127 if (whichone == grip_no++) {
128 x2 = new_x; y2 = new_y;
130 *min_x = MIN (*min_x, x1); *min_y = MIN (*min_y, y1);
131 *max_x = MAX (*max_x, x1); *max_y = MAX (*max_y, y1);
132 *min_x = MIN (*min_x, x2); *min_y = MIN (*min_y, y2);
133 *max_x = MAX (*max_x, x2); *max_y = MAX (*max_y, y2);
134 /* Fall through */
135 case PATH_MOVETO:
136 case PATH_MOVETO_OPEN:
137 case PATH_LINETO:
138 /* Destination point grip */
139 if (whichone == grip_no++) {
140 x3 = new_x; y3 = new_y;
142 *min_x = MIN (*min_x, x3); *min_y = MIN (*min_y, y3);
143 *max_x = MAX (*max_x, x3); *max_y = MAX (*max_y, y3);
144 case PATH_END:
145 break;
150 /*! Default capacity of newly created path objects, in path
151 * sections. */
152 #define TEMP_PATH_DEFAULT_SIZE 8
154 /*! \brief Add elements to the temporary PATH.
155 * \par Function Description
156 * Check if the temporary #PATH object used when interactively
157 * creating paths has room for additional sections. If not, doubles
158 * its capacity.
160 static void
161 path_expand (GschemToplevel *w_current)
163 PATH *p = w_current->temp_path;
164 if (p->num_sections == p->num_sections_max) {
165 p->num_sections_max *= 2;
166 p->sections = g_renew (PATH_SECTION, p->sections,
167 p->num_sections_max);
171 /*! \brief Add new sections to the temporary path while drawing.
172 * \par Function Description
173 * Calculates the next section to be added to a path while drawing.
174 * The temporary slots in the #GschemToplevel structure are used as
175 * follows:
176 * - first_wx and first_wy contain the location of the next point
177 * that will lie on the path
178 * - second_wx and second_wy contain the location of the next
179 * point's control point.
180 * - third_wx and third_wy contain the location of the previous
181 * point's control point.
182 * - temp_path is the new #PATH object (i.e. sequence of path
183 * sections that comprise the path drawn so far).
185 * path_next_sections() adds up to two additional sections to the
186 * temporary path, and returns the number of sections added, on the
187 * basis that: a path starts with a MOVETO the first point; two cusp
188 * nodes (control points coincident with the node position) generate a
189 * LINETO section; and a path ends either whenever the user clicks on
190 * either the first or the current node.
192 * \return the number of path sections added.
194 static int
195 path_next_sections (GschemToplevel *w_current)
197 gboolean cusp_point, cusp_prev, close_path, end_path, start_path;
198 PATH *p;
199 PATH_SECTION *section, *prev_section;
200 int x1, y1, x2, y2, x3, y3;
201 int save_num_sections;
203 g_assert (w_current);
204 g_assert (w_current->temp_path != NULL);
205 g_assert (w_current->temp_path->sections != NULL);
207 x1 = w_current->first_wx;
208 y1 = w_current->first_wy;
209 x2 = w_current->second_wx;
210 y2 = w_current->second_wy;
211 x3 = w_current->third_wx;
212 y3 = w_current->third_wy;
213 p = w_current->temp_path;
215 save_num_sections = p->num_sections;
217 /* Check whether the section that's being added is the initial
218 * MOVETO. This is detected if the path is currently empty. */
219 start_path = (p->num_sections == 0);
221 prev_section = start_path ? NULL : &p->sections[p->num_sections - 1];
223 /* Check whether the point that's being added has a handle offset. */
224 cusp_point = (w_current->first_wx == w_current->second_wx
225 && w_current->first_wy == w_current->second_wy);
227 /* Check whether there's a leftover control handle from the previous
228 * point. */
229 cusp_prev = (!start_path
230 && prev_section->x3 == x3
231 && prev_section->y3 == y3);
233 /* Check whether the section that's being added closes the path.
234 * This is detected if the location of the node is the same as the
235 * location of the starting node, and there is at least one section
236 * in the path in addition to the initial MOVETO section. */
237 section = &p->sections[0];
238 close_path = (!start_path
239 && x1 == section->x3
240 && y1 == section->y3);
242 /* Check whether the section that's being added ends the path. This
243 * is detected if the location of the node is the same as the
244 * location of the previous node. */
245 end_path = (!start_path
246 && x1 == prev_section->x3
247 && y1 == prev_section->y3);
249 /* Create section */
250 if (start_path) {
251 /* At the start of the path, just create the initial MOVETO. */
252 path_expand (w_current);
253 section = &p->sections[p->num_sections++];
254 section->code = PATH_MOVETO;
255 section->x3 = x1;
256 section->y3 = y1;
258 } else if (!end_path) {
259 path_expand (w_current);
260 section = &p->sections[p->num_sections++];
262 /* If there are two cusp points, then add a line segment. If the
263 * path is being closed, closing the path adds an implicit line
264 * segment. */
265 if (cusp_prev && cusp_point && close_path) {
266 section->code = PATH_END;
268 } else if (cusp_prev && cusp_point) {
269 section->code = PATH_LINETO;
270 section->x3 = x1;
271 section->y3 = y1;
273 } else {
274 /* If there are one or more Bezier control points, the section
275 * needs to be a CURVETO. The control point of the current
276 * point is mirrored about the point (i.e. the line is kept
277 * continuous through the point). */
278 section->code = PATH_CURVETO;
279 section->x1 = x3;
280 section->y1 = y3;
281 section->x2 = x1 + (x1 - x2);
282 section->y2 = y1 + (y1 - y2);
283 section->x3 = x1;
284 section->y3 = y1;
286 if (close_path) {
287 path_expand (w_current);
288 section = &p->sections[p->num_sections++];
289 section->code = PATH_END;
293 /* Return the number of sections added */
294 return p->num_sections - save_num_sections;
297 /*! \brief Invalidate current path creation screen region.
298 * \par Function Description
299 * Invalidates the screen region occupied by the current path creation
300 * preview and control handle helpers.
302 void
303 o_path_invalidate_rubber (GschemToplevel *w_current)
305 int added_sections;
306 int min_x, min_y, max_x, max_y;
308 g_return_if_fail (w_current != NULL);
310 GschemPageView *page_view = gschem_toplevel_get_current_page_view (w_current);
311 g_return_if_fail (page_view != NULL);
313 /* Calculate any new sections */
314 added_sections = path_next_sections (w_current);
316 path_rubber_bbox (w_current, w_current->temp_path,
317 &min_x, &max_y, &max_x, &min_y);
319 /* Expand the bounding box to include any control handles
320 * that are currently being drawn. */
321 min_x = MIN (min_x, w_current->second_wx);
322 max_x = MAX (max_x, w_current->second_wx);
323 min_y = MIN (min_y, w_current->second_wy);
324 max_y = MAX (max_y, w_current->second_wy);
326 gschem_page_view_invalidate_world_rect (page_view,
327 min_x,
328 min_y,
329 max_x,
330 max_y);
332 w_current->temp_path->num_sections -= added_sections;
335 /*! \brief Start process to input a new path.
336 * \par Function Description
337 * This function starts the process of interactively adding a path to
338 * the current sheet by resetting the path creation state and
339 * enabling preview ("rubber") drawing.
341 * For details of how #GschemToplevel fields are used during the
342 * path creation process, see path_next_sections().
344 * \param [in] w_current The GschemToplevel object.
345 * \param [in] w_x Current x coordinate of pointer in world units.
346 * \param [in] w_y Current y coordinate of pointer in world units.
348 void
349 o_path_start(GschemToplevel *w_current, int w_x, int w_y)
351 g_assert (w_current);
353 w_current->pathcontrol = TRUE;
354 i_action_start (w_current);
356 /* Reset path creation state */
357 if (w_current->temp_path != NULL) {
358 w_current->temp_path->num_sections = 0;
359 } else {
360 PATH *p = g_new0 (PATH, 1);
361 p->sections = g_new0 (PATH_SECTION, TEMP_PATH_DEFAULT_SIZE);
362 p->num_sections = 0;
363 p->num_sections_max = TEMP_PATH_DEFAULT_SIZE;
364 w_current->temp_path = p;
367 w_current->which_grip = -1;
368 w_current->first_wx = w_x;
369 w_current->first_wy = w_y;
370 w_current->second_wx = w_x;
371 w_current->second_wy = w_y;
372 w_current->third_wx = w_x;
373 w_current->third_wy = w_y;
375 /* Enable preview drawing */
376 w_current->rubber_visible = TRUE;
379 /* \brief Begin inputting a new path node.
380 * \par Function Description
381 * Re-enters path creation mode, saving the current pointer location
382 * as the location of the next path control point.
384 void
385 o_path_continue (GschemToplevel *w_current, int w_x, int w_y)
387 g_assert (w_current);
388 g_assert (w_current->inside_action != 0);
390 w_current->pathcontrol = TRUE;
392 o_path_invalidate_rubber (w_current);
394 w_current->first_wx = w_x;
395 w_current->first_wy = w_y;
396 w_current->second_wx = w_x;
397 w_current->second_wy = w_y;
399 o_path_invalidate_rubber (w_current);
402 /* \brief Give feedback on path creation during mouse movement.
403 * \par Function Description
404 * If the user is currently in the process of creating a path node
405 * (i.e. has mouse button pressed), moves the next node's control
406 * point. If the user has not yet pressed the mouse button to start
407 * defining a path node, moves the next node's location and control
408 * point together.
410 void
411 o_path_motion (GschemToplevel *w_current, int w_x, int w_y)
413 g_assert (w_current);
414 g_assert (w_current->inside_action != 0);
416 o_path_invalidate_rubber (w_current);
418 w_current->second_wx = w_x;
419 w_current->second_wy = w_y;
421 if (!w_current->pathcontrol) {
422 w_current->first_wx = w_x;
423 w_current->first_wy = w_y;
426 o_path_invalidate_rubber (w_current);
429 /*! \brief End the input of a path.
430 * \par Function Description
431 * This function ends the process of interactively adding a path to the
432 * current sheet.
434 * It first erases the last temporary path displayed, calculates the
435 * corresponding world coordinates of the two ends of the path and finally
436 * adds a new initialized path object to the list of object of the current
437 * sheet.
439 * \param [in] w_current The GschemToplevel object.
440 * \param [in] w_x (unused)
441 * \param [in] w_y (unused)
443 void
444 o_path_end(GschemToplevel *w_current, int w_x, int w_y)
446 gboolean close_path, end_path, start_path;
447 PATH *p;
448 PATH_SECTION *section, *prev_section;
449 int x1, y1, x2, y2;
451 g_assert (w_current);
452 g_assert (w_current->inside_action != 0);
453 g_assert (w_current->toplevel);
454 g_assert (w_current->temp_path != NULL);
455 g_assert (w_current->temp_path->sections != NULL);
457 GschemPageView *page_view = gschem_toplevel_get_current_page_view (w_current);
458 g_return_if_fail (page_view != NULL);
460 PAGE *page = gschem_page_view_get_page (page_view);
461 g_return_if_fail (page != NULL);
463 TOPLEVEL *toplevel = page->toplevel;
464 g_return_if_fail (toplevel != NULL);
466 o_path_invalidate_rubber (w_current);
468 x1 = w_current->first_wx;
469 y1 = w_current->first_wy;
470 x2 = w_current->second_wx;
471 y2 = w_current->second_wy;
472 p = w_current->temp_path;
474 /* Check whether the section that's being added is the initial
475 * MOVETO. This is detected if the path is currently empty. */
476 start_path = (p->num_sections == 0);
478 prev_section = start_path ? NULL : &p->sections[p->num_sections - 1];
480 /* Check whether the section that's being added closes the path.
481 * This is detected if the location of the node is the same as the
482 * location of the starting node, and there is at least one section
483 * in the path in addition to the initial MOVETO section. */
484 section = &p->sections[0];
485 close_path = (!start_path
486 && x1 == section->x3
487 && y1 == section->y3);
489 /* Check whether the section that's being added ends the path. This
490 * is detected if the location of the node is the same as the
491 * location of the previous node. */
492 end_path = (!start_path
493 && x1 == prev_section->x3
494 && y1 == prev_section->y3);
496 /* Add predicted next sections */
497 path_next_sections (w_current);
499 if (end_path || close_path) {
500 /* Add object to page and clean up path drawing state */
501 OBJECT *obj = o_path_new_take_path (toplevel, OBJ_PATH,
502 GRAPHIC_COLOR, p);
503 w_current->temp_path = NULL;
504 w_current->first_wx = -1;
505 w_current->first_wy = -1;
506 w_current->second_wx = -1;
507 w_current->second_wy = -1;
508 w_current->third_wx = -1;
509 w_current->third_wy = -1;
511 s_page_append (toplevel, page, obj);
512 g_run_hook_object (w_current, "%add-objects-hook", obj);
513 gschem_toplevel_page_content_changed (w_current, page);
514 o_undo_savestate (w_current, page, UNDO_ALL, _("Path"));
516 w_current->rubber_visible = FALSE;
518 i_action_stop (w_current);
519 } else {
520 /* Leave state as it is and continue path drawing... */
522 /* Save the control point coordinates for the next section */
523 w_current->third_wx = x2;
524 w_current->third_wy = y2;
526 w_current->pathcontrol = FALSE;
530 /*! \brief End the input of a path.
532 * Called on right-click. Confirms all path segments except for the
533 * current one. If there is only one path segment, cancels the path.
535 * \param [in] w_current The GschemToplevel object.
537 void
538 o_path_end_path (GschemToplevel *w_current)
540 PATH *p = w_current->temp_path;
541 PATH_SECTION *prev_section;
543 if (p->num_sections < 2) {
544 o_path_invalidate_rubber (w_current);
545 i_action_stop (w_current);
546 return;
549 prev_section = &p->sections[p->num_sections - 1];
550 w_current->first_wx = prev_section->x3;
551 w_current->first_wy = prev_section->y3;
552 o_path_end (w_current, 0, 0);
555 /*! \brief Draw path creation preview.
556 * \par Function Description
557 * Draw a preview of the path currently being drawn, including a
558 * helper line showing the control point of the node being drawn (if
559 * applicable).
561 void
562 o_path_draw_rubber (GschemToplevel *w_current, EdaRenderer *renderer)
564 OBJECT object;
565 int added_sections = 0;
567 /* Draw a helper for when we're dragging a control point */
568 if (w_current->first_wx != w_current->second_wx
569 || w_current->first_wy != w_current->second_wy) {
570 double wwidth = 0;
571 cairo_t *cr = eda_renderer_get_cairo_context (renderer);
572 GArray *color_map = eda_renderer_get_color_map (renderer);
573 int flags = eda_renderer_get_cairo_flags (renderer);
575 eda_cairo_line (cr, flags, END_NONE, wwidth,
576 w_current->first_wx, w_current->first_wy,
577 w_current->second_wx, w_current->second_wy);
579 eda_cairo_set_source_color (cr, SELECT_COLOR, color_map);
580 eda_cairo_stroke (cr, flags, TYPE_SOLID, END_NONE, wwidth, -1, -1);
582 /* Now draw the rest of the path */
584 /* Calculate any new sections */
585 added_sections = path_next_sections (w_current);
587 /* Setup a fake object to pass the drawing routine */
588 memset (&object, 0, sizeof (OBJECT));
589 object.type = OBJ_PATH;
590 object.color = SELECT_COLOR;
591 object.line_width = 0; /* clamped to 1 pixel in circle_path */
592 object.path = w_current->temp_path;
594 eda_renderer_draw (renderer, &object);
596 /* Throw away the added sections again */
597 w_current->temp_path->num_sections -= added_sections;
600 void
601 o_path_invalidate_rubber_grips (GschemToplevel *w_current)
603 int min_x, min_y, max_x, max_y;
605 GschemPageView *page_view = gschem_toplevel_get_current_page_view (w_current);
606 g_return_if_fail (page_view != NULL);
608 path_rubber_bbox (w_current, NULL,
609 &min_x, &max_y, &max_x, &min_y);
611 gschem_page_view_invalidate_world_rect (page_view,
612 min_x,
613 min_y,
614 max_x,
615 max_y);
619 /*! \brief Draw temporary path while dragging end.
620 * \par Function Description
621 * This function manages the erase/update/draw process of temporary path
622 * when modifying one end of the path.
623 * The path is described by four <B>*w_current</B> variables : the first end
624 * of the path is (<B>first_wx</B>,<B>first_wy</B>), the second end is
625 * (<B>second_wx</B>,<B>second_wy</B>).
626 * The first end is constant. The second end is updated to the (<B>w_x</B>,<B>w_y</B>).
628 * \param [in] w_current The GschemToplevel object.
629 * \param [in] w_x Current x coordinate of pointer in world units.
630 * \param [in] w_y Current y coordinate of pointer in world units.
632 void o_path_motion_grips (GschemToplevel *w_current, int w_x, int w_y)
634 g_assert (w_current->inside_action != 0);
636 if (w_current->rubber_visible)
637 o_path_invalidate_rubber_grips (w_current);
639 w_current->second_wx = w_x;
640 w_current->second_wy = w_y;
642 o_path_invalidate_rubber_grips (w_current);
643 w_current->rubber_visible = 1;
647 /*! \brief Draw path from GschemToplevel object.
648 * \par Function Description
649 * This function draws a path with an exclusive or function over the sheet.
650 * The color of the box is <B>SELECT_COLOR</B>. The path is
651 * described by the two points (<B>w_current->first_wx</B>,
652 * <B>w_current->first_wy</B>) and (<B>w_current->second_wx</B>,<B>w_current->second_wy</B>).
654 * \param [in] w_current The GschemToplevel object.
656 void
657 o_path_draw_rubber_grips (GschemToplevel *w_current, EdaRenderer *renderer)
659 OBJECT object;
661 /* Setup a fake object to pass the drawing routine */
662 memset (&object, 0, sizeof (OBJECT));
663 object.type = OBJ_PATH;
664 object.color = SELECT_COLOR;
665 object.line_width = 0; /* clamped to 1 pixel in circle_path */
666 object.path = path_copy_modify (w_current->which_object->path, 0, 0,
667 w_current->second_wx,
668 w_current->second_wy, w_current->which_grip);
670 eda_renderer_draw (renderer, &object);
671 g_free (object.path->sections);
672 g_free (object.path);