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
28 #define NUM_BEZIER_SEGMENTS 100
31 typedef void (*FILL_FUNC
) (GschemToplevel
*w_current
,
32 COLOR
*color
, PATH
*path
,
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
)
40 int x1
, y1
, x2
, y2
, x3
, y3
;
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
) {
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
;
68 case PATH_MOVETO_OPEN
:
70 /* Destination point grip */
71 if (whichone
== grip_no
++) {
72 x3
= new_x
; y3
= new_y
;
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
;
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.
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
;
102 g_assert (w_current
);
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
) {
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
);
136 case PATH_MOVETO_OPEN
:
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
);
150 /*! Default capacity of newly created path objects, in path
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
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
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.
195 path_next_sections (GschemToplevel
*w_current
)
197 gboolean cusp_point
, cusp_prev
, close_path
, end_path
, start_path
;
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
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
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
);
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
;
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
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
;
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
;
281 section
->x2
= x1
+ (x1
- x2
);
282 section
->y2
= y1
+ (y1
- y2
);
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.
303 o_path_invalidate_rubber (GschemToplevel
*w_current
)
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
,
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.
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;
360 PATH
*p
= g_new0 (PATH
, 1);
361 p
->sections
= g_new0 (PATH_SECTION
, TEMP_PATH_DEFAULT_SIZE
);
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.
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
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
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
439 * \param [in] w_current The GschemToplevel object.
440 * \param [in] w_x (unused)
441 * \param [in] w_y (unused)
444 o_path_end(GschemToplevel
*w_current
, int w_x
, int w_y
)
446 gboolean close_path
, end_path
, start_path
;
448 PATH_SECTION
*section
, *prev_section
;
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
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
,
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
);
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.
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
);
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
562 o_path_draw_rubber (GschemToplevel
*w_current
, EdaRenderer
*renderer
)
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
) {
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
;
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
,
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.
657 o_path_draw_rubber_grips (GschemToplevel
*w_current
, EdaRenderer
*renderer
)
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
);