4 * This file implements line items for canvas widgets.
6 * Copyright 1991-1992 Regents of the University of California.
7 * Permission to use, copy, modify, and distribute this
8 * software and its documentation for any purpose and without
9 * fee is hereby granted, provided that the above copyright
10 * notice appear in all copies. The University of California
11 * makes no representations about the suitability of this
12 * software for any purpose. It is provided "as is" without
13 * express or implied warranty.
17 static char rcsid
[] = "$Header: /user6/ouster/wish/RCS/tkCanvLine.c,v 1.7 92/07/28 15:40:08 ouster Exp $ SPRITE (Berkeley)";
27 * The structure below defines the record for each line item.
30 typedef struct LineItem
{
31 Tk_Item header
; /* Generic stuff that's the same for all
32 * types. MUST BE FIRST IN STRUCTURE. */
33 Tk_Canvas
*canvasPtr
; /* Canvas containing item. Needed for
34 * parsing arrow shapes. */
35 int numPoints
; /* Number of points in line (always >= 2). */
36 double *coordPtr
; /* Pointer to malloc-ed array containing
37 * x- and y-coords of all points in line.
38 * X-coords are even-valued indices, y-coords
39 * are corresponding odd-valued indices. */
40 int width
; /* Width of line. */
41 XColor
*fg
; /* Foreground color for line. */
42 Pixmap fillStipple
; /* Stipple bitmap for filling line. */
43 int capStyle
; /* Cap style for line. */
44 int joinStyle
; /* Join style for line. */
45 GC gc
; /* Graphics context for filling line. */
46 Tk_Uid arrow
; /* Indicates whether or not to draw arrowheads:
47 * "none", "first", "last", or "both". */
48 float arrowShapeA
; /* Distance from tip of arrowhead to center. */
49 float arrowShapeB
; /* Distance from tip of arrowhead to trailing
50 * point, measured along shaft. */
51 float arrowShapeC
; /* Distance of trailing points from outside
53 double *firstArrowPtr
; /* Points to array of 5 points describing
54 * polygon for arrowhead at first point in
55 * line. First point of arrowhead is tip.
56 * Malloc'ed. NULL means no arrowhead at
58 double *lastArrowPtr
; /* Points to polygon for arrowhead at last
59 * point in line (5 points, first of which
60 * is tip). Malloc'ed. NULL means no
61 * arrowhead at last point. */
62 int smooth
; /* Non-zero means draw line smoothed (i.e.
63 * with Bezier splines). */
64 int splineSteps
; /* Number of steps in each spline segment. */
68 * Number of points in an arrowHead:
71 #define PTS_IN_ARROW 6
74 * Prototypes for procedures defined in this file:
77 static void ComputeLineBbox
_ANSI_ARGS_((Tk_Canvas
*canvasPtr
,
79 static int ConfigureLine
_ANSI_ARGS_((
80 Tk_Canvas
*canvasPtr
, Tk_Item
*itemPtr
, int argc
,
81 char **argv
, int flags
));
82 static int ConfigureArrows
_ANSI_ARGS_((Tk_Canvas
*canvasPtr
,
84 static int CreateLine
_ANSI_ARGS_((Tk_Canvas
*canvasPtr
,
85 struct Tk_Item
*itemPtr
, int argc
, char **argv
));
86 static void DeleteLine
_ANSI_ARGS_((Tk_Item
*itemPtr
));
87 static void DisplayLine
_ANSI_ARGS_((Tk_Canvas
*canvasPtr
,
88 Tk_Item
*itemPtr
, Drawable dst
));
89 static int LineCoords
_ANSI_ARGS_((Tk_Canvas
*canvasPtr
,
90 Tk_Item
*itemPtr
, int argc
, char **argv
));
91 static int LineToArea
_ANSI_ARGS_((Tk_Canvas
*canvasPtr
,
92 Tk_Item
*itemPtr
, double *rectPtr
));
93 static double LineToPoint
_ANSI_ARGS_((Tk_Canvas
*canvasPtr
,
94 Tk_Item
*itemPtr
, double *coordPtr
));
95 static int ParseArrowShape
_ANSI_ARGS_((ClientData clientData
,
96 Tcl_Interp
*interp
, Tk_Window tkwin
, char *value
,
97 char *recordPtr
, int offset
));
98 static char * PrintArrowShape
_ANSI_ARGS_((ClientData clientData
,
99 Tk_Window tkwin
, char *recordPtr
, int offset
,
100 Tcl_FreeProc
**freeProcPtr
));
101 static void ScaleLine
_ANSI_ARGS_((Tk_Canvas
*canvasPtr
,
102 Tk_Item
*itemPtr
, double originX
, double originY
,
103 double scaleX
, double scaleY
));
104 static void TranslateLine
_ANSI_ARGS_((Tk_Canvas
*canvasPtr
,
105 Tk_Item
*itemPtr
, double deltaX
, double deltaY
));
108 * Information used for parsing configuration specs. If you change any
109 * of the default strings, be sure to change the corresponding default
110 * values in CreateLine.
113 static Tk_CustomOption arrowShapeOption
= {ParseArrowShape
,
114 PrintArrowShape
, (ClientData
) NULL
};
116 static Tk_ConfigSpec configSpecs
[] = {
117 {TK_CONFIG_UID
, "-arrow", (char *) NULL
, (char *) NULL
,
118 "none", Tk_Offset(LineItem
, arrow
), TK_CONFIG_DONT_SET_DEFAULT
},
119 {TK_CONFIG_CUSTOM
, "-arrowshape", (char *) NULL
, (char *) NULL
,
120 "8 10 3", Tk_Offset(LineItem
, arrowShapeA
),
121 TK_CONFIG_DONT_SET_DEFAULT
, &arrowShapeOption
},
122 {TK_CONFIG_CAP_STYLE
, "-capstyle", (char *) NULL
, (char *) NULL
,
123 "butt", Tk_Offset(LineItem
, capStyle
), TK_CONFIG_DONT_SET_DEFAULT
},
124 {TK_CONFIG_COLOR
, "-fill", (char *) NULL
, (char *) NULL
,
125 "black", Tk_Offset(LineItem
, fg
), 0},
126 {TK_CONFIG_JOIN_STYLE
, "-joinstyle", (char *) NULL
, (char *) NULL
,
127 "round", Tk_Offset(LineItem
, joinStyle
), TK_CONFIG_DONT_SET_DEFAULT
},
128 {TK_CONFIG_BOOLEAN
, "-smooth", (char *) NULL
, (char *) NULL
,
129 "no", Tk_Offset(LineItem
, smooth
), TK_CONFIG_DONT_SET_DEFAULT
},
130 {TK_CONFIG_INT
, "-splinesteps", (char *) NULL
, (char *) NULL
,
131 "12", Tk_Offset(LineItem
, splineSteps
), TK_CONFIG_DONT_SET_DEFAULT
},
132 {TK_CONFIG_BITMAP
, "-stipple", (char *) NULL
, (char *) NULL
,
133 (char *) NULL
, Tk_Offset(LineItem
, fillStipple
), TK_CONFIG_NULL_OK
},
134 {TK_CONFIG_CUSTOM
, "-tags", (char *) NULL
, (char *) NULL
,
135 (char *) NULL
, 0, TK_CONFIG_NULL_OK
, &tkCanvasTagsOption
},
136 {TK_CONFIG_PIXELS
, "-width", (char *) NULL
, (char *) NULL
,
137 "1", Tk_Offset(LineItem
, width
), TK_CONFIG_DONT_SET_DEFAULT
},
138 {TK_CONFIG_END
, (char *) NULL
, (char *) NULL
, (char *) NULL
,
143 * The structures below defines the line item type by means
144 * of procedures that can be invoked by generic item code.
147 Tk_ItemType TkLineType
= {
149 sizeof(LineItem
), /* itemSize */
150 CreateLine
, /* createProc */
151 configSpecs
, /* configSpecs */
152 ConfigureLine
, /* configureProc */
153 LineCoords
, /* coordProc */
154 DeleteLine
, /* deleteProc */
155 DisplayLine
, /* displayProc */
156 0, /* alwaysRedraw */
157 LineToPoint
, /* pointProc */
158 LineToArea
, /* areaProc */
159 (Tk_ItemPostscriptProc
*) NULL
, /* postscriptProc */
160 ScaleLine
, /* scaleProc */
161 TranslateLine
, /* translateProc */
162 (Tk_ItemIndexProc
*) NULL
, /* indexProc */
163 (Tk_ItemCursorProc
*) NULL
, /* cursorProc */
164 (Tk_ItemSelectionProc
*) NULL
, /* selectionProc */
165 (Tk_ItemInsertProc
*) NULL
, /* insertProc */
166 (Tk_ItemDCharsProc
*) NULL
, /* dTextProc */
167 (Tk_ItemType
*) NULL
/* nextPtr */
171 * The Tk_Uid's below refer to uids for the various arrow types:
174 static Tk_Uid noneUid
= NULL
;
175 static Tk_Uid firstUid
= NULL
;
176 static Tk_Uid lastUid
= NULL
;
177 static Tk_Uid bothUid
= NULL
;
180 * The definition below determines how large are static arrays
181 * used to hold spline points (splines larger than this have to
182 * have their arrays malloc-ed).
185 #define MAX_STATIC_POINTS 200
188 *--------------------------------------------------------------
192 * This procedure is invoked to create a new line item in
196 * A standard Tcl return value. If an error occurred in
197 * creating the item, then an error message is left in
198 * canvasPtr->interp->result; in this case itemPtr is
199 * left uninitialized, so it can be safely freed by the
203 * A new line item is created.
205 *--------------------------------------------------------------
210 register Tk_Canvas
*canvasPtr
, /* Canvas to hold new item. */
211 Tk_Item
*itemPtr
, /* Record to hold new item; header
212 * has been initialized by caller. */
213 int argc
, /* Number of arguments in argv. */
214 char **argv
/* Arguments describing line. */
217 register LineItem
*linePtr
= (LineItem
*) itemPtr
;
221 Tcl_AppendResult(canvasPtr
->interp
, "wrong # args: should be \"",
222 Tk_PathName(canvasPtr
->tkwin
),
223 "\" create x1 y1 x2 y2 ?x3 y3 ...? ?options?",
229 * Carry out initialization that is needed to set defaults and to
230 * allow proper cleanup after errors during the the remainder of
234 linePtr
->canvasPtr
= canvasPtr
;
235 linePtr
->numPoints
= 0;
236 linePtr
->coordPtr
= NULL
;
239 linePtr
->fillStipple
= None
;
240 linePtr
->capStyle
= CapButt
;
241 linePtr
->joinStyle
= JoinRound
;
243 if (noneUid
== NULL
) {
244 noneUid
= Tk_GetUid("none");
245 firstUid
= Tk_GetUid("first");
246 lastUid
= Tk_GetUid("last");
247 bothUid
= Tk_GetUid("both");
249 linePtr
->arrow
= noneUid
;
250 linePtr
->arrowShapeA
= 8.0;
251 linePtr
->arrowShapeB
= 10.0;
252 linePtr
->arrowShapeC
= 3.0;
253 linePtr
->firstArrowPtr
= NULL
;
254 linePtr
->lastArrowPtr
= NULL
;
256 linePtr
->splineSteps
= 12;
259 * Count the number of points and then parse them into a point
260 * array. Leading arguments are assumed to be points if they
261 * start with a digit or a minus sign followed by a digit.
264 for (i
= 4; i
< (argc
-1); i
+=2) {
265 if ((!isdigit(argv
[i
][0])) &&
266 ((argv
[i
][0] != '-') || (!isdigit(argv
[i
][1])))) {
270 if (LineCoords(canvasPtr
, itemPtr
, i
, argv
) != TCL_OK
) {
273 if (ConfigureLine(canvasPtr
, itemPtr
, argc
-i
, argv
+i
, 0) == TCL_OK
) {
283 *--------------------------------------------------------------
287 * This procedure is invoked to process the "coords" widget
288 * command on lines. See the user documentation for details
292 * Returns TCL_OK or TCL_ERROR, and sets canvasPtr->interp->result.
295 * The coordinates for the given item may be changed.
297 *--------------------------------------------------------------
302 register Tk_Canvas
*canvasPtr
, /* Canvas containing item. */
303 Tk_Item
*itemPtr
, /* Item whose coordinates are to be
304 * read or modified. */
305 int argc
, /* Number of coordinates supplied in
307 char **argv
/* Array of coordinates: x1, y1,
311 register LineItem
*linePtr
= (LineItem
*) itemPtr
;
316 for (i
= 0; i
< 2*linePtr
->numPoints
; i
++) {
317 sprintf(buffer
, "%g", linePtr
->coordPtr
[i
]);
318 Tcl_AppendElement(canvasPtr
->interp
, buffer
, 0);
320 } else if (argc
< 4) {
321 Tcl_AppendResult(canvasPtr
->interp
,
322 "too few coordinates for line: must have at least 4",
325 } else if (argc
& 1) {
326 Tcl_AppendResult(canvasPtr
->interp
,
327 "odd number of coordinates specified for line",
332 if (linePtr
->numPoints
!= numPoints
) {
333 if (linePtr
->coordPtr
!= NULL
) {
334 ckfree((char *) linePtr
->coordPtr
);
336 linePtr
->coordPtr
= (double *) ckalloc((unsigned)
337 (sizeof(double) * argc
));
338 linePtr
->numPoints
= numPoints
;
340 for (i
= argc
-1; i
>= 0; i
--) {
341 if (TkGetCanvasCoord(canvasPtr
, argv
[i
], &linePtr
->coordPtr
[i
])
346 ComputeLineBbox(canvasPtr
, linePtr
);
352 *--------------------------------------------------------------
356 * This procedure is invoked to configure various aspects
357 * of a line item such as its background color.
360 * A standard Tcl result code. If an error occurs, then
361 * an error message is left in canvasPtr->interp->result.
364 * Configuration information, such as colors and stipple
365 * patterns, may be set for itemPtr.
367 *--------------------------------------------------------------
372 Tk_Canvas
*canvasPtr
, /* Canvas containing itemPtr. */
373 Tk_Item
*itemPtr
, /* Line item to reconfigure. */
374 int argc
, /* Number of elements in argv. */
375 char **argv
, /* Arguments describing things to configure. */
376 int flags
/* Flags to pass to Tk_ConfigureWidget. */
379 register LineItem
*linePtr
= (LineItem
*) itemPtr
;
384 if (Tk_ConfigureWidget(canvasPtr
->interp
, canvasPtr
->tkwin
,
385 configSpecs
, argc
, argv
, (char *) linePtr
, flags
) != TCL_OK
) {
390 * A few of the options require additional processing, such as
394 if (linePtr
->fg
== NULL
) {
397 gcValues
.foreground
= linePtr
->fg
->pixel
;
398 gcValues
.join_style
= linePtr
->joinStyle
;
399 if (linePtr
->width
< 0) {
402 gcValues
.line_width
= linePtr
->width
;
403 mask
= GCForeground
|GCJoinStyle
|GCLineWidth
;
404 if (linePtr
->fillStipple
!= None
) {
405 gcValues
.stipple
= linePtr
->fillStipple
;
406 gcValues
.fill_style
= FillStippled
;
407 mask
|= GCStipple
|GCFillStyle
;
409 if (linePtr
->arrow
== noneUid
) {
410 gcValues
.cap_style
= linePtr
->capStyle
;
413 newGC
= Tk_GetGC(canvasPtr
->tkwin
, mask
, &gcValues
);
415 if (linePtr
->gc
!= None
) {
416 Tk_FreeGC(linePtr
->gc
);
421 * Keep spline parameters within reasonable limits.
424 if (linePtr
->splineSteps
< 1) {
425 linePtr
->splineSteps
= 1;
426 } else if (linePtr
->splineSteps
> 100) {
427 linePtr
->splineSteps
= 100;
431 * Setup arrowheads, if needed. If arrowheads are turned off,
432 * restore the line's endpoints (they were shortened when the
433 * arrowheads were added).
436 if ((linePtr
->firstArrowPtr
!= NULL
) && (linePtr
->arrow
!= firstUid
)
437 && (linePtr
->arrow
!= bothUid
)) {
438 linePtr
->coordPtr
[0] = linePtr
->firstArrowPtr
[0];
439 linePtr
->coordPtr
[1] = linePtr
->firstArrowPtr
[1];
440 ckfree((char *) linePtr
->firstArrowPtr
);
441 linePtr
->firstArrowPtr
= NULL
;
443 if ((linePtr
->lastArrowPtr
!= NULL
) && (linePtr
->arrow
!= lastUid
)
444 && (linePtr
->arrow
!= bothUid
)) {
447 index
= 2*(linePtr
->numPoints
-1);
448 linePtr
->coordPtr
[index
] = linePtr
->lastArrowPtr
[0];
449 linePtr
->coordPtr
[index
+1] = linePtr
->lastArrowPtr
[1];
450 ckfree((char *) linePtr
->lastArrowPtr
);
451 linePtr
->lastArrowPtr
= NULL
;
453 if (linePtr
->arrow
!= noneUid
) {
454 if ((linePtr
->arrow
!= firstUid
) && (linePtr
->arrow
!= lastUid
)
455 && (linePtr
->arrow
!= bothUid
)) {
456 Tcl_AppendResult(canvasPtr
->interp
, "bad arrow spec \"",
457 linePtr
->arrow
, "\": must be none, first, last, or both",
459 linePtr
->arrow
= noneUid
;
462 ConfigureArrows(canvasPtr
, linePtr
);
466 * Recompute bounding box for line.
469 ComputeLineBbox(canvasPtr
, linePtr
);
475 *--------------------------------------------------------------
479 * This procedure is called to clean up the data structure
480 * associated with a line item.
486 * Resources associated with itemPtr are released.
488 *--------------------------------------------------------------
493 Tk_Item
*itemPtr
/* Item that is being deleted. */
496 register LineItem
*linePtr
= (LineItem
*) itemPtr
;
498 if (linePtr
->coordPtr
!= NULL
) {
499 ckfree((char *) linePtr
->coordPtr
);
501 if (linePtr
->fg
!= NULL
) {
502 Tk_FreeColor(linePtr
->fg
);
504 if (linePtr
->fillStipple
!= None
) {
505 Tk_FreeBitmap(linePtr
->fillStipple
);
507 if (linePtr
->gc
!= None
) {
508 Tk_FreeGC(linePtr
->gc
);
510 if (linePtr
->firstArrowPtr
!= NULL
) {
511 ckfree((char *) linePtr
->firstArrowPtr
);
513 if (linePtr
->lastArrowPtr
!= NULL
) {
514 ckfree((char *) linePtr
->lastArrowPtr
);
519 *--------------------------------------------------------------
523 * This procedure is invoked to compute the bounding box of
524 * all the pixels that may be drawn as part of a line.
530 * The fields x1, y1, x2, and y2 are updated in the header
533 *--------------------------------------------------------------
538 register Tk_Canvas
*canvasPtr
, /* Canvas that contains item. */
539 LineItem
*linePtr
/* Item whose bbos is to be
543 register double *coordPtr
;
546 coordPtr
= linePtr
->coordPtr
;
547 linePtr
->header
.x1
= linePtr
->header
.x2
= *coordPtr
;
548 linePtr
->header
.y1
= linePtr
->header
.y2
= coordPtr
[1];
551 * Compute the bounding box of all the points in the line,
552 * then expand in all directions by the line's width to take
553 * care of butting or rounded corners and projecting or
554 * rounded caps. This expansion is an overestimate (worst-case
555 * is square root of two over two) but it's simple. Don't do
556 * anything special for curves. This causes an additional
557 * overestimate in the bounding box, but is faster.
560 for (i
= 1, coordPtr
= linePtr
->coordPtr
+2; i
< linePtr
->numPoints
;
561 i
++, coordPtr
+= 2) {
562 TkIncludePoint(canvasPtr
, (Tk_Item
*) linePtr
, coordPtr
);
564 linePtr
->header
.x1
-= linePtr
->width
;
565 linePtr
->header
.x2
+= linePtr
->width
;
566 linePtr
->header
.y1
-= linePtr
->width
;
567 linePtr
->header
.y2
+= linePtr
->width
;
570 * For mitered lines, make a second pass through all the points.
571 * Compute the locations of the two miter vertex points and add
572 * those into the bounding box.
575 if (linePtr
->joinStyle
== JoinMiter
) {
576 for (i
= linePtr
->numPoints
, coordPtr
= linePtr
->coordPtr
; i
>= 3;
577 i
--, coordPtr
+= 2) {
581 if (TkGetMiterPoints(coordPtr
, coordPtr
+2, coordPtr
+4,
582 (double) linePtr
->width
, miter
, miter
+2)) {
583 for (j
= 0; j
< 4; j
+= 2) {
584 TkIncludePoint(canvasPtr
, (Tk_Item
*) linePtr
, miter
+j
);
591 * Add in the sizes of arrowheads, if any.
594 if (linePtr
->arrow
!= noneUid
) {
595 if (linePtr
->arrow
!= lastUid
) {
596 for (i
= 0, coordPtr
= linePtr
->firstArrowPtr
; i
< PTS_IN_ARROW
;
597 i
++, coordPtr
+= 2) {
598 TkIncludePoint(canvasPtr
, (Tk_Item
*) linePtr
, coordPtr
);
601 if (linePtr
->arrow
!= firstUid
) {
602 for (i
= 0, coordPtr
= linePtr
->lastArrowPtr
; i
< PTS_IN_ARROW
;
603 i
++, coordPtr
+= 2) {
604 TkIncludePoint(canvasPtr
, (Tk_Item
*) linePtr
, coordPtr
);
610 * Add one more pixel of fudge factor just to be safe (e.g.
611 * X may round differently than we do).
614 linePtr
->header
.x1
-= 1;
615 linePtr
->header
.x2
+= 1;
616 linePtr
->header
.y1
-= 1;
617 linePtr
->header
.y2
+= 1;
621 *--------------------------------------------------------------
625 * This procedure is invoked to draw a line item in a given
632 * ItemPtr is drawn in drawable using the transformation
633 * information in canvasPtr.
635 *--------------------------------------------------------------
640 register Tk_Canvas
*canvasPtr
, /* Canvas that contains item. */
641 Tk_Item
*itemPtr
, /* Item to be displayed. */
642 Drawable drawable
/* Pixmap or window in which to draw
646 register LineItem
*linePtr
= (LineItem
*) itemPtr
;
647 XPoint staticPoints
[MAX_STATIC_POINTS
];
649 register XPoint
*pPtr
;
650 register double *coordPtr
;
653 if (linePtr
->gc
== None
) {
658 * Build up an array of points in screen coordinates. Use a
659 * static array unless the line has an enormous number of points;
660 * in this case, dynamically allocate an array. For smoothed lines,
661 * generate the curve points on each redisplay.
664 if ((linePtr
->smooth
) && (linePtr
->numPoints
> 2)) {
665 numPoints
= 1 + linePtr
->numPoints
*linePtr
->splineSteps
;
667 numPoints
= linePtr
->numPoints
;
670 if (numPoints
<= MAX_STATIC_POINTS
) {
671 pointPtr
= staticPoints
;
673 pointPtr
= (XPoint
*) ckalloc((unsigned) (numPoints
* sizeof(XPoint
)));
676 if (linePtr
->smooth
) {
677 numPoints
= TkMakeBezierCurve(canvasPtr
, linePtr
->coordPtr
,
678 linePtr
->numPoints
, linePtr
->splineSteps
, pointPtr
,
681 for (i
= 0, coordPtr
= linePtr
->coordPtr
, pPtr
= pointPtr
;
682 i
< linePtr
->numPoints
; i
+= 1, coordPtr
+= 2, pPtr
++) {
683 pPtr
->x
= SCREEN_X(canvasPtr
, *coordPtr
);
684 pPtr
->y
= SCREEN_Y(canvasPtr
, coordPtr
[1]);
689 * Display line, the free up line storage if it was dynamically
693 XDrawLines(Tk_Display(canvasPtr
->tkwin
), drawable
, linePtr
->gc
,
694 pointPtr
, numPoints
, CoordModeOrigin
);
695 if (pointPtr
!= staticPoints
) {
696 ckfree((char *) pointPtr
);
700 * Display arrowheads, if they are wanted.
703 if (linePtr
->arrow
!= noneUid
) {
704 if (linePtr
->arrow
!= lastUid
) {
705 TkFillPolygon(canvasPtr
, linePtr
->firstArrowPtr
, PTS_IN_ARROW
,
706 drawable
, linePtr
->gc
);
708 if (linePtr
->arrow
!= firstUid
) {
709 TkFillPolygon(canvasPtr
, linePtr
->lastArrowPtr
, PTS_IN_ARROW
,
710 drawable
, linePtr
->gc
);
716 *--------------------------------------------------------------
720 * Computes the distance from a given point to a given
721 * line, in canvas units.
724 * The return value is 0 if the point whose x and y coordinates
725 * are pointPtr[0] and pointPtr[1] is inside the line. If the
726 * point isn't inside the line then the return value is the
727 * distance from the point to the line.
732 *--------------------------------------------------------------
738 Tk_Canvas
*canvasPtr
, /* Canvas containing item. */
739 Tk_Item
*itemPtr
, /* Item to check against point. */
740 double *pointPtr
/* Pointer to x and y coordinates. */
743 register LineItem
*linePtr
= (LineItem
*) itemPtr
;
744 register double *coordPtr
, *linePoints
;
745 double staticSpace
[2*MAX_STATIC_POINTS
];
747 double bestDist
, dist
;
748 int numPoints
, count
;
749 int changedMiterToBevel
; /* Non-zero means that a mitered corner
750 * had to be treated as beveled after all
751 * because the angle was < 11 degrees. */
756 * Handle smoothed lines by generating an expanded set of points
757 * against which to do the check.
760 if ((linePtr
->smooth
) && (linePtr
->numPoints
> 2)) {
761 numPoints
= 1 + linePtr
->numPoints
*linePtr
->splineSteps
;
762 if (numPoints
<= MAX_STATIC_POINTS
) {
763 linePoints
= staticSpace
;
765 linePoints
= (double *) ckalloc((unsigned)
766 (2*numPoints
*sizeof(double)));
768 numPoints
= TkMakeBezierCurve(canvasPtr
, linePtr
->coordPtr
,
769 linePtr
->numPoints
, linePtr
->splineSteps
, (XPoint
*) NULL
,
772 numPoints
= linePtr
->numPoints
;
773 linePoints
= linePtr
->coordPtr
;
777 * The overall idea is to iterate through all of the edges of
778 * the line, computing a polygon for each edge and testing the
779 * point against that polygon. In addition, there are additional
780 * tests to deal with rounded joints and caps.
783 changedMiterToBevel
= 0;
784 for (count
= numPoints
, coordPtr
= linePoints
; count
>= 2;
785 count
--, coordPtr
+= 2) {
788 * If rounding is done around the first point then compute
789 * the distance between the point and the point.
792 if (((linePtr
->capStyle
== CapRound
) && (count
== numPoints
))
793 || ((linePtr
->joinStyle
== JoinRound
)
794 && (count
!= numPoints
))) {
795 dist
= hypot(coordPtr
[0] - pointPtr
[0], coordPtr
[1] - pointPtr
[1])
796 - linePtr
->width
/2.0;
800 } else if (dist
< bestDist
) {
806 * Compute the polygonal shape corresponding to this edge,
807 * consisting of two points for the first point of the edge
808 * and two points for the last point of the edge.
811 if (count
== numPoints
) {
812 TkGetButtPoints(coordPtr
+2, coordPtr
, (double) linePtr
->width
,
813 linePtr
->capStyle
== CapProjecting
, poly
, poly
+2);
814 } else if ((linePtr
->joinStyle
== JoinMiter
) && !changedMiterToBevel
) {
820 TkGetButtPoints(coordPtr
+2, coordPtr
, (double) linePtr
->width
, 0,
824 * If this line uses beveled joints, then check the distance
825 * to a polygon comprising the last two points of the previous
826 * polygon and the first two from this polygon; this checks
827 * the wedges that fill the mitered joint.
830 if ((linePtr
->joinStyle
== JoinBevel
) || changedMiterToBevel
) {
833 dist
= TkPolygonToPoint(poly
, 5, pointPtr
);
837 } else if (dist
< bestDist
) {
840 changedMiterToBevel
= 0;
844 TkGetButtPoints(coordPtr
, coordPtr
+2, (double) linePtr
->width
,
845 linePtr
->capStyle
== CapProjecting
, poly
+4, poly
+6);
846 } else if (linePtr
->joinStyle
== JoinMiter
) {
847 if (TkGetMiterPoints(coordPtr
, coordPtr
+2, coordPtr
+4,
848 (double) linePtr
->width
, poly
+4, poly
+6) == 0) {
849 changedMiterToBevel
= 1;
850 TkGetButtPoints(coordPtr
, coordPtr
+2, (double) linePtr
->width
,
854 TkGetButtPoints(coordPtr
, coordPtr
+2, (double) linePtr
->width
, 0,
859 dist
= TkPolygonToPoint(poly
, 5, pointPtr
);
863 } else if (dist
< bestDist
) {
869 * If caps are rounded, check the distance to the cap around the
870 * final end point of the line.
873 if (linePtr
->capStyle
== CapRound
) {
874 dist
= hypot(coordPtr
[0] - pointPtr
[0], coordPtr
[1] - pointPtr
[1])
875 - linePtr
->width
/2.0;
879 } else if (dist
< bestDist
) {
885 * If there are arrowheads, check the distance to the arrowheads.
888 if (linePtr
->arrow
!= noneUid
) {
889 if (linePtr
->arrow
!= lastUid
) {
890 dist
= TkPolygonToPoint(linePtr
->firstArrowPtr
, PTS_IN_ARROW
,
895 } else if (dist
< bestDist
) {
899 if (linePtr
->arrow
!= firstUid
) {
900 dist
= TkPolygonToPoint(linePtr
->lastArrowPtr
, PTS_IN_ARROW
,
905 } else if (dist
< bestDist
) {
912 if ((linePoints
!= staticSpace
) && (linePoints
!= linePtr
->coordPtr
)) {
913 ckfree((char *) linePoints
);
919 *--------------------------------------------------------------
923 * This procedure is called to determine whether an item
924 * lies entirely inside, entirely outside, or overlapping
925 * a given rectangular area.
928 * -1 is returned if the item is entirely outside the
929 * area, 0 if it overlaps, and 1 if it is entirely
930 * inside the given area.
935 *--------------------------------------------------------------
941 Tk_Canvas
*canvasPtr
, /* Canvas containing item. */
942 Tk_Item
*itemPtr
, /* Item to check against line. */
946 register LineItem
*linePtr
= (LineItem
*) itemPtr
;
947 register double *coordPtr
;
948 double staticSpace
[2*MAX_STATIC_POINTS
];
949 double *linePoints
, poly
[10];
951 int numPoints
, count
;
952 int changedMiterToBevel
; /* Non-zero means that a mitered corner
953 * had to be treated as beveled after all
954 * because the angle was < 11 degrees. */
955 int inside
; /* Tentative guess about what to return,
956 * based on all points seen so far: one
957 * means everything seen so far was
958 * inside the area; -1 means everything
959 * was outside the area. 0 means overlap
962 radius
= linePtr
->width
/2.0;
966 * Handle smoothed lines by generating an expanded set of points
967 * against which to do the check.
970 if ((linePtr
->smooth
) && (linePtr
->numPoints
> 2)) {
971 numPoints
= 1 + linePtr
->numPoints
*linePtr
->splineSteps
;
972 if (numPoints
<= MAX_STATIC_POINTS
) {
973 linePoints
= staticSpace
;
975 linePoints
= (double *) ckalloc((unsigned)
976 (2*numPoints
*sizeof(double)));
978 numPoints
= TkMakeBezierCurve(canvasPtr
, linePtr
->coordPtr
,
979 linePtr
->numPoints
, linePtr
->splineSteps
, (XPoint
*) NULL
,
982 numPoints
= linePtr
->numPoints
;
983 linePoints
= linePtr
->coordPtr
;
986 coordPtr
= linePoints
;
987 if ((coordPtr
[0] >= rectPtr
[0]) && (coordPtr
[0] <= rectPtr
[2])
988 && (coordPtr
[1] >= rectPtr
[1]) && (coordPtr
[1] <= rectPtr
[3])) {
993 * Iterate through all of the edges of the line, computing a polygon
994 * for each edge and testing the area against that polygon. In
995 * addition, there are additional tests to deal with rounded joints
999 changedMiterToBevel
= 0;
1000 for (count
= numPoints
; count
>= 2; count
--, coordPtr
+= 2) {
1003 * If rounding is done around the first point of the edge
1004 * then test a circular region around the point with the
1008 if (((linePtr
->capStyle
== CapRound
) && (count
== numPoints
))
1009 || ((linePtr
->joinStyle
== JoinRound
)
1010 && (count
!= numPoints
))) {
1011 poly
[0] = coordPtr
[0] - radius
;
1012 poly
[1] = coordPtr
[1] - radius
;
1013 poly
[2] = coordPtr
[0] + radius
;
1014 poly
[3] = coordPtr
[1] + radius
;
1015 if (TkOvalToArea(poly
, rectPtr
) != inside
) {
1022 * Compute the polygonal shape corresponding to this edge,
1023 * consisting of two points for the first point of the edge
1024 * and two points for the last point of the edge.
1027 if (count
== numPoints
) {
1028 TkGetButtPoints(coordPtr
+2, coordPtr
, (double) linePtr
->width
,
1029 linePtr
->capStyle
== CapProjecting
, poly
, poly
+2);
1030 } else if ((linePtr
->joinStyle
== JoinMiter
) && !changedMiterToBevel
) {
1036 TkGetButtPoints(coordPtr
+2, coordPtr
, (double) linePtr
->width
, 0,
1040 * If the last joint was beveled, then also check a
1041 * polygon comprising the last two points of the previous
1042 * polygon and the first two from this polygon; this checks
1043 * the wedges that fill the beveled joint.
1046 if ((linePtr
->joinStyle
== JoinBevel
) || changedMiterToBevel
) {
1049 if (TkPolygonToArea(poly
, 5, rectPtr
) != inside
) {
1053 changedMiterToBevel
= 0;
1057 TkGetButtPoints(coordPtr
, coordPtr
+2, (double) linePtr
->width
,
1058 linePtr
->capStyle
== CapProjecting
, poly
+4, poly
+6);
1059 } else if (linePtr
->joinStyle
== JoinMiter
) {
1060 if (TkGetMiterPoints(coordPtr
, coordPtr
+2, coordPtr
+4,
1061 (double) linePtr
->width
, poly
+4, poly
+6) == 0) {
1062 changedMiterToBevel
= 1;
1063 TkGetButtPoints(coordPtr
, coordPtr
+2, (double) linePtr
->width
,
1067 TkGetButtPoints(coordPtr
, coordPtr
+2, (double) linePtr
->width
, 0,
1072 if (TkPolygonToArea(poly
, 5, rectPtr
) != inside
) {
1079 * If caps are rounded, check the cap around the final point
1083 if (linePtr
->capStyle
== CapRound
) {
1084 poly
[0] = coordPtr
[0] - radius
;
1085 poly
[1] = coordPtr
[1] - radius
;
1086 poly
[2] = coordPtr
[0] + radius
;
1087 poly
[3] = coordPtr
[1] + radius
;
1088 if (TkOvalToArea(poly
, rectPtr
) != inside
) {
1095 * Check arrowheads, if any.
1098 if (linePtr
->arrow
!= noneUid
) {
1099 if (linePtr
->arrow
!= lastUid
) {
1100 if (TkPolygonToArea(linePtr
->firstArrowPtr
, PTS_IN_ARROW
,
1101 rectPtr
) != inside
) {
1106 if (linePtr
->arrow
!= firstUid
) {
1107 if (TkPolygonToArea(linePtr
->lastArrowPtr
, PTS_IN_ARROW
,
1108 rectPtr
) != inside
) {
1116 if ((linePoints
!= staticSpace
) && (linePoints
!= linePtr
->coordPtr
)) {
1117 ckfree((char *) linePoints
);
1123 *--------------------------------------------------------------
1127 * This procedure is invoked to rescale a line item.
1133 * The line referred to by itemPtr is rescaled so that the
1134 * following transformation is applied to all point
1136 * x' = originX + scaleX*(x-originX)
1137 * y' = originY + scaleY*(y-originY)
1139 *--------------------------------------------------------------
1144 Tk_Canvas
*canvasPtr
, /* Canvas containing line. */
1145 Tk_Item
*itemPtr
, /* Line to be scaled. */
1147 double originY
, /* Origin about which to scale rect. */
1148 double scaleX
, /* Amount to scale in X direction. */
1149 double scaleY
/* Amount to scale in Y direction. */
1152 LineItem
*linePtr
= (LineItem
*) itemPtr
;
1153 register double *coordPtr
;
1156 for (i
= 0, coordPtr
= linePtr
->coordPtr
; i
< linePtr
->numPoints
;
1157 i
++, coordPtr
+= 2) {
1158 coordPtr
[0] = originX
+ scaleX
*(*coordPtr
- originX
);
1159 coordPtr
[1] = originY
+ scaleY
*(coordPtr
[1] - originY
);
1161 if (linePtr
->firstArrowPtr
!= NULL
) {
1162 for (i
= 0, coordPtr
= linePtr
->firstArrowPtr
; i
< PTS_IN_ARROW
;
1163 i
++, coordPtr
+= 2) {
1164 coordPtr
[0] = originX
+ scaleX
*(coordPtr
[0] - originX
);
1165 coordPtr
[1] = originY
+ scaleY
*(coordPtr
[1] - originY
);
1168 if (linePtr
->lastArrowPtr
!= NULL
) {
1169 for (i
= 0, coordPtr
= linePtr
->lastArrowPtr
; i
< PTS_IN_ARROW
;
1170 i
++, coordPtr
+= 2) {
1171 coordPtr
[0] = originX
+ scaleX
*(coordPtr
[0] - originX
);
1172 coordPtr
[1] = originY
+ scaleY
*(coordPtr
[1] - originY
);
1175 ComputeLineBbox(canvasPtr
, linePtr
);
1179 *--------------------------------------------------------------
1183 * This procedure is called to move a line by a given amount.
1189 * The position of the line is offset by (xDelta, yDelta), and
1190 * the bounding box is updated in the generic part of the item
1193 *--------------------------------------------------------------
1198 Tk_Canvas
*canvasPtr
, /* Canvas containing item. */
1199 Tk_Item
*itemPtr
, /* Item that is being moved. */
1201 double deltaY
/* Amount by which item is to be
1205 LineItem
*linePtr
= (LineItem
*) itemPtr
;
1206 register double *coordPtr
;
1209 for (i
= 0, coordPtr
= linePtr
->coordPtr
; i
< linePtr
->numPoints
;
1210 i
++, coordPtr
+= 2) {
1211 coordPtr
[0] += deltaX
;
1212 coordPtr
[1] += deltaY
;
1214 if (linePtr
->firstArrowPtr
!= NULL
) {
1215 for (i
= 0, coordPtr
= linePtr
->firstArrowPtr
; i
< PTS_IN_ARROW
;
1216 i
++, coordPtr
+= 2) {
1217 coordPtr
[0] += deltaX
;
1218 coordPtr
[1] += deltaY
;
1221 if (linePtr
->lastArrowPtr
!= NULL
) {
1222 for (i
= 0, coordPtr
= linePtr
->lastArrowPtr
; i
< PTS_IN_ARROW
;
1223 i
++, coordPtr
+= 2) {
1224 coordPtr
[0] += deltaX
;
1225 coordPtr
[1] += deltaY
;
1228 ComputeLineBbox(canvasPtr
, linePtr
);
1232 *--------------------------------------------------------------
1234 * ParseArrowShape --
1236 * This procedure is called back during option parsing to
1237 * parse arrow shape information.
1240 * The return value is a standard Tcl result: TCL_OK means
1241 * that the arrow shape information was parsed ok, and
1242 * TCL_ERROR means it couldn't be parsed.
1245 * Arrow information in recordPtr is updated.
1247 *--------------------------------------------------------------
1253 ClientData clientData
, /* Not used. */
1254 Tcl_Interp
*interp
, /* Used for error reporting. */
1255 Tk_Window tkwin
, /* Not used. */
1256 char *value
, /* Textual specification of arrow shape. */
1257 char *recordPtr
, /* Pointer to item record in which to
1258 * store arrow information. */
1259 int offset
/* Offset of shape information in widget
1263 LineItem
*linePtr
= (LineItem
*) recordPtr
;
1268 if (offset
!= Tk_Offset(LineItem
, arrowShapeA
)) {
1269 panic("ParseArrowShape received bogus offset");
1272 if (Tcl_SplitList(interp
, value
, &argc
, &argv
) != TCL_OK
) {
1274 Tcl_ResetResult(interp
);
1275 Tcl_AppendResult(interp
, "bad arrow shape \"", value
,
1276 "\": must be list with three numbers", (char *) NULL
);
1278 ckfree((char *) argv
);
1285 if ((TkGetCanvasCoord(linePtr
->canvasPtr
, argv
[0], &a
) != TCL_OK
)
1286 || (TkGetCanvasCoord(linePtr
->canvasPtr
, argv
[1], &b
) != TCL_OK
)
1287 || (TkGetCanvasCoord(linePtr
->canvasPtr
, argv
[2], &c
) != TCL_OK
)) {
1290 linePtr
->arrowShapeA
= a
;
1291 linePtr
->arrowShapeB
= b
;
1292 linePtr
->arrowShapeC
= c
;
1293 ckfree((char *) argv
);
1298 *--------------------------------------------------------------
1300 * PrintArrowShape --
1302 * This procedure is a callback invoked by the configuration
1303 * code to return a printable value describing an arrow shape.
1311 *--------------------------------------------------------------
1317 ClientData clientData
, /* Not used. */
1318 Tk_Window tkwin
, /* Window associated with linePtr's widget. */
1319 char *recordPtr
, /* Pointer to item record containing current
1320 * shape information. */
1321 int offset
, /* Offset of arrow information in record. */
1322 Tcl_FreeProc
**freeProcPtr
/* Store address of procedure to call to
1323 * free string here. */
1326 LineItem
*linePtr
= (LineItem
*) recordPtr
;
1329 buffer
= ckalloc(120);
1330 sprintf(buffer
, "%.5g %.5g %.5g", linePtr
->arrowShapeA
,
1331 linePtr
->arrowShapeB
, linePtr
->arrowShapeC
);
1332 *freeProcPtr
= (Tcl_FreeProc
*) free
;
1337 *--------------------------------------------------------------
1339 * ConfigureArrows --
1341 * If arrowheads have been requested for a line, this
1342 * procedure makes arrangements for the arrowheads.
1345 * A standard Tcl return value. If an error occurs, then
1346 * an error message is left in canvasPtr->interp->result.
1349 * Information in linePtr is set up for one or two arrowheads.
1350 * the firstArrowPtr and lastArrowPtr polygons are allocated
1351 * and initialized, if need be, and the end points of the line
1352 * are adjusted so that a thick line doesn't stick out past
1355 *--------------------------------------------------------------
1361 Tk_Canvas
*canvasPtr
, /* Canvas in which arrows will be
1362 * displayed (interp and tkwin
1363 * fields are needed). */
1364 register LineItem
*linePtr
/* Item to configure for arrows. */
1367 double *poly
, *coordPtr
;
1368 double dx
, dy
, length
, sinTheta
, cosTheta
, temp
, shapeC
;
1369 double fracHeight
; /* Line width as fraction of
1370 * arrowhead width. */
1371 double backup
; /* Distance to backup end points
1372 * so the line ends in the middle
1373 * of the arrowhead. */
1374 double vertX
, vertY
; /* Position of arrowhead vertex. */
1377 * If there's an arrowhead on the first point of the line, compute
1378 * its polygon and adjust the first point of the line so that the
1379 * line doesn't stick out past the leading edge of the arrowhead.
1382 shapeC
= linePtr
->arrowShapeC
+ linePtr
->width
/2.0;
1383 fracHeight
= (linePtr
->width
/2.0)/shapeC
;
1384 backup
= fracHeight
*linePtr
->arrowShapeB
1385 + linePtr
->arrowShapeA
*(1.0 - fracHeight
)/2.0;
1386 if (linePtr
->arrow
!= lastUid
) {
1387 poly
= linePtr
->firstArrowPtr
;
1389 poly
= (double *) ckalloc((unsigned)
1390 (2*PTS_IN_ARROW
*sizeof(double)));
1391 poly
[0] = poly
[10] = linePtr
->coordPtr
[0];
1392 poly
[1] = poly
[11] = linePtr
->coordPtr
[1];
1393 linePtr
->firstArrowPtr
= poly
;
1395 dx
= poly
[0] - linePtr
->coordPtr
[2];
1396 dy
= poly
[1] - linePtr
->coordPtr
[3];
1397 length
= hypot(dx
, dy
);
1399 sinTheta
= cosTheta
= 0.0;
1401 sinTheta
= dy
/length
;
1402 cosTheta
= dx
/length
;
1404 vertX
= poly
[0] - linePtr
->arrowShapeA
*cosTheta
;
1405 vertY
= poly
[1] - linePtr
->arrowShapeA
*sinTheta
;
1406 temp
= shapeC
*sinTheta
;
1407 poly
[2] = poly
[0] - linePtr
->arrowShapeB
*cosTheta
+ temp
;
1408 poly
[8] = poly
[2] - 2*temp
;
1409 temp
= shapeC
*cosTheta
;
1410 poly
[3] = poly
[1] - linePtr
->arrowShapeB
*sinTheta
- temp
;
1411 poly
[9] = poly
[3] + 2*temp
;
1412 poly
[4] = poly
[2]*fracHeight
+ vertX
*(1.0-fracHeight
);
1413 poly
[5] = poly
[3]*fracHeight
+ vertY
*(1.0-fracHeight
);
1414 poly
[6] = poly
[8]*fracHeight
+ vertX
*(1.0-fracHeight
);
1415 poly
[7] = poly
[9]*fracHeight
+ vertY
*(1.0-fracHeight
);
1418 * Polygon done. Now move the first point towards the second so
1419 * that the corners at the end of the line are inside the
1423 linePtr
->coordPtr
[0] = poly
[0] - backup
*cosTheta
;
1424 linePtr
->coordPtr
[1] = poly
[1] - backup
*sinTheta
;
1428 * Similar arrowhead calculation for the last point of the line.
1431 if (linePtr
->arrow
!= firstUid
) {
1432 coordPtr
= linePtr
->coordPtr
+ 2*(linePtr
->numPoints
-2);
1433 poly
= linePtr
->lastArrowPtr
;
1435 poly
= (double *) ckalloc((unsigned)
1436 (2*PTS_IN_ARROW
*sizeof(double)));
1437 poly
[0] = poly
[10] = coordPtr
[2];
1438 poly
[1] = poly
[11] = coordPtr
[3];
1439 linePtr
->lastArrowPtr
= poly
;
1441 dx
= poly
[0] - coordPtr
[0];
1442 dy
= poly
[1] - coordPtr
[1];
1443 length
= hypot(dx
, dy
);
1445 sinTheta
= cosTheta
= 0.0;
1447 sinTheta
= dy
/length
;
1448 cosTheta
= dx
/length
;
1450 vertX
= poly
[0] - linePtr
->arrowShapeA
*cosTheta
;
1451 vertY
= poly
[1] - linePtr
->arrowShapeA
*sinTheta
;
1452 temp
= shapeC
*sinTheta
;
1453 poly
[2] = poly
[0] - linePtr
->arrowShapeB
*cosTheta
+ temp
;
1454 poly
[8] = poly
[2] - 2*temp
;
1455 temp
= shapeC
*cosTheta
;
1456 poly
[3] = poly
[1] - linePtr
->arrowShapeB
*sinTheta
- temp
;
1457 poly
[9] = poly
[3] + 2*temp
;
1458 poly
[4] = poly
[2]*fracHeight
+ vertX
*(1.0-fracHeight
);
1459 poly
[5] = poly
[3]*fracHeight
+ vertY
*(1.0-fracHeight
);
1460 poly
[6] = poly
[8]*fracHeight
+ vertX
*(1.0-fracHeight
);
1461 poly
[7] = poly
[9]*fracHeight
+ vertY
*(1.0-fracHeight
);
1462 coordPtr
[2] = poly
[0] - backup
*cosTheta
;
1463 coordPtr
[3] = poly
[1] - backup
*sinTheta
;