/* * Arc.c -- Implementation of Arc item. * * Authors : Patrick Lecoanet. * Creation date : Wed Mar 30 16:24:09 1994 * * $Id$ */ /* * Copyright (c) 1993 - 1999 CENA, Patrick Lecoanet -- * * This code is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This code is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this code; if not, write to the Free * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * */ #include #include "Item.h" #include "Geo.h" #include "Draw.h" #include "Types.h" #include "WidgetInfo.h" #include "Image.h" static const char rcsid[] = "$Id$"; static const char compile_id[]="$Compile: " __FILE__ " " __DATE__ " " __TIME__ " $"; /* * Bit offset of flags. */ #define FILLED_BIT 1<<0 /* If the arc is filled with color/pattern */ #define CLOSED_BIT 1<<1 /* If the arc outline is closed */ #define PIE_SLICE_BIT 1<<2 /* If the arc is closed as a pie slice or a chord */ #define FIRST_END_OK 1<<3 #define LAST_END_OK 1<<4 #define USING_POLY_BIT 1<<5 static double Pick(Item item, ZnPoint *p, Item start_item, int aperture, Item *a_item, int *a_part); /* ********************************************************************************** * * Specific Arc item record. * ********************************************************************************** */ typedef struct _ArcItemStruct { ItemStruct header; /* Public data */ ZnPoint coords[2]; int start_angle; int angle_extent; Pixmap line_pattern; Pixmap fill_pattern; ZnColorGradient fill_color; ZnGradientGeom grad_geom; ZnColor line_color; int line_width; LineStyle line_style; ZnLineEnd first_end; ZnLineEnd last_end; char *tile_name; unsigned char flags; /* Private data */ ZnPoint orig; ZnPoint corner; ZnPoint center1; ZnPoint center2; ZnImage tile; ZnList render_shape; } ArcItemStruct, *ArcItem; static ZnAttrConfig arc_attrs[] = { { ZN_CONFIG_BOOL, "-closed", NULL, Tk_Offset(ArcItemStruct, flags), CLOSED_BIT, ZN_COORDS_FLAG, False }, { ZN_CONFIG_BOOL, "-composerotation", NULL, Tk_Offset(ArcItemStruct, header.flags), COMPOSE_ROTATION_BIT, ZN_COORDS_FLAG, False }, { ZN_CONFIG_BOOL, "-composescale", NULL, Tk_Offset(ArcItemStruct, header.flags), COMPOSE_SCALE_BIT, ZN_COORDS_FLAG, False }, { ZN_CONFIG_ANGLE, "-extent", NULL, Tk_Offset(ArcItemStruct, angle_extent), 0, ZN_COORDS_FLAG, False }, { ZN_CONFIG_GRADIENT_COLOR, "-fillcolor", NULL, Tk_Offset(ArcItemStruct, fill_color), 0, ZN_DRAW_FLAG, False }, { ZN_CONFIG_BOOL, "-filled", NULL, Tk_Offset(ArcItemStruct, flags), FILLED_BIT, ZN_COORDS_FLAG, False }, { ZN_CONFIG_PATTERN, "-fillpattern", NULL, Tk_Offset(ArcItemStruct, fill_pattern), 0, ZN_DRAW_FLAG, False }, { ZN_CONFIG_LINE_END, "-firstend", NULL, Tk_Offset(ArcItemStruct, first_end), 0, ZN_COORDS_FLAG, False }, { ZN_CONFIG_GRADIENT_GEOM, "-gradient", NULL, Tk_Offset(ArcItemStruct, grad_geom), 0, ZN_COORDS_FLAG, False }, { ZN_CONFIG_LINE_END, "-lastend", NULL, Tk_Offset(ArcItemStruct, last_end), 0, ZN_COORDS_FLAG, False }, { ZN_CONFIG_COLOR, "-linecolor", NULL, Tk_Offset(ArcItemStruct, line_color), 0, ZN_DRAW_FLAG|ZN_BORDER_FLAG, False }, { ZN_CONFIG_PATTERN, "-linepattern", NULL, Tk_Offset(ArcItemStruct, line_pattern), 0, ZN_DRAW_FLAG, False }, { ZN_CONFIG_LINE_STYLE, "-linestyle", NULL, Tk_Offset(ArcItemStruct, line_style), 0, ZN_DRAW_FLAG, False }, { ZN_CONFIG_DIM, "-linewidth", NULL, Tk_Offset(ArcItemStruct, line_width), 0, ZN_COORDS_FLAG, False }, { ZN_CONFIG_BOOL, "-pieslice", NULL, Tk_Offset(ArcItemStruct, flags), PIE_SLICE_BIT, ZN_COORDS_FLAG, False }, { ZN_CONFIG_PRI, "-priority", NULL, Tk_Offset(ArcItemStruct, header.priority), 0, ZN_DRAW_FLAG|ZN_REPICK_FLAG, False }, { ZN_CONFIG_BOOL, "-sensitive", NULL, Tk_Offset(ArcItemStruct, header.flags), SENSITIVE_BIT, ZN_REPICK_FLAG, False }, { ZN_CONFIG_ANGLE, "-startangle", NULL, Tk_Offset(ArcItemStruct, start_angle), 0, ZN_COORDS_FLAG, False }, { ZN_CONFIG_TAGS, "-tags", NULL, Tk_Offset(ArcItemStruct, header.tags), 0, 0, False }, { ZN_CONFIG_IMAGE, "-tile", NULL, Tk_Offset(ArcItemStruct, tile_name), 0, ZN_DRAW_FLAG|ZN_TILE_FLAG, False }, { ZN_CONFIG_BOOL, "-visible", NULL, Tk_Offset(ArcItemStruct, header.flags), VISIBLE_BIT, ZN_DRAW_FLAG|ZN_REPICK_FLAG|ZN_VIS_FLAG, False }, { ZN_CONFIG_END, NULL, NULL, 0, 0, 0 } }; /* ********************************************************************************** * * ArcTileChange -- * ********************************************************************************** */ static void ArcTileChange(ClientData client_data, int x, int y, int width, int height, int image_width, int image_height) { ArcItem arc = (ArcItem) client_data; InvalidateImage(arc->tile_name); ITEM.Invalidate((Item) arc, ZN_COORDS_FLAG); } /* ********************************************************************************** * * Init -- * ********************************************************************************** */ static int Init(Item item, int *argc, Tcl_Obj *CONST *args[]) { WidgetInfo *wi = item->wi; ArcItem arc = (ArcItem) item; Tcl_Obj **elems; int num_elems; /* Init attributes */ SET(item->flags, VISIBLE_BIT); SET(item->flags, SENSITIVE_BIT); SET(item->flags, COMPOSE_ROTATION_BIT); SET(item->flags, COMPOSE_SCALE_BIT); item->priority = DEFAULT_ARC_PRIORITY; arc->start_angle = 0; arc->angle_extent = 360; CLEAR(arc->flags, FILLED_BIT); CLEAR(arc->flags, CLOSED_BIT); CLEAR(arc->flags, PIE_SLICE_BIT); CLEAR(arc->flags, USING_POLY_BIT); arc->line_pattern = ZnUnspecifiedPattern; arc->fill_pattern = ZnUnspecifiedPattern; arc->line_style = LINE_SIMPLE; arc->line_width = 1; arc->first_end = arc->last_end = NULL; arc->tile_name = ""; arc->tile = ZnUnspecifiedImage; arc->render_shape = NULL; if (*argc < 1) { Tcl_AppendResult(wi->interp, " arc coords expected", NULL); return ZN_ERROR; } if ((Tcl_ListObjGetElements(wi->interp, (*args)[0], &num_elems, &elems) == ZN_ERROR) || (num_elems != 4) || (Tcl_GetDoubleFromObj(wi->interp, elems[0], &arc->coords[0].x) == ZN_ERROR) || (Tcl_GetDoubleFromObj(wi->interp, elems[1], &arc->coords[0].y) == ZN_ERROR) || (Tcl_GetDoubleFromObj(wi->interp, elems[2], &arc->coords[1].x) == ZN_ERROR) || (Tcl_GetDoubleFromObj(wi->interp, elems[3], &arc->coords[1].y) == ZN_ERROR)) { Tcl_AppendResult(wi->interp, " malformed arc coords", NULL); return ZN_ERROR; }; (*args)++; (*argc)--; arc->fill_color = ZnGetColorGradient(wi->interp, wi->win, ZnNameOfColor(wi->fore_color)); arc->line_color = ZnGetColorByValue(wi->win, wi->fore_color); arc->grad_geom = NULL; return ZN_OK; } /* ********************************************************************************** * * Clone -- * ********************************************************************************** */ static void Clone(Item item) { ArcItem arc = (ArcItem) item; WidgetInfo *wi = item->wi; char *text; if (strlen(arc->tile_name) != 0) { text = ZnMalloc((strlen(arc->tile_name) + 1) * sizeof(char)); strcpy(text, arc->tile_name); arc->tile_name = text; arc->tile = Tk_GetImage(wi->interp, wi->win, arc->tile_name, ArcTileChange, (ClientData) arc); } if (arc->first_end) { LineEndDuplicate(arc->first_end); } if (arc->last_end) { LineEndDuplicate(arc->last_end); } if (arc->line_pattern != ZnUnspecifiedPattern) { arc->line_pattern = Tk_GetBitmap(wi->interp, wi->win, Tk_NameOfBitmap(wi->dpy, arc->line_pattern)); } if (arc->fill_pattern != ZnUnspecifiedPattern) { arc->fill_pattern = Tk_GetBitmap(wi->interp, wi->win, Tk_NameOfBitmap(wi->dpy, arc->fill_pattern)); } arc->line_color = ZnGetColorByValue(wi->win, arc->line_color); arc->fill_color = ZnGetColorGradientByValue(arc->fill_color); if (arc->grad_geom) { arc->grad_geom = GradientGeomDuplicate(arc->grad_geom); } arc->render_shape = NULL; } /* ********************************************************************************** * * Destroy -- * ********************************************************************************** */ static void Destroy(Item item) { WidgetInfo *wi = item->wi; ArcItem arc = (ArcItem) item; if (arc->render_shape) { ZnListFree(arc->render_shape); } if (arc->first_end) { LineEndDelete(arc->first_end); } if (arc->last_end) { LineEndDelete(arc->last_end); } if (arc->tile != ZnUnspecifiedImage) { Tk_FreeImage(arc->tile); arc->tile = ZnUnspecifiedImage; } if (strlen(arc->tile_name) != 0) { ZnFree(arc->tile_name); } if (arc->line_pattern != ZnUnspecifiedPattern) { Tk_FreeBitmap(wi->dpy, arc->line_pattern); } if (arc->fill_pattern != ZnUnspecifiedPattern) { Tk_FreeBitmap(wi->dpy, arc->fill_pattern); } ZnFreeColorGradient(arc->fill_color); ZnFreeColor(arc->line_color); if (arc->grad_geom) { GradientGeomDelete(arc->grad_geom); } } /* ********************************************************************************** * * Setup flags to control the precedence between the * graphical attributes. * ********************************************************************************** */ static void SetRenderFlags(ArcItem arc) { ASSIGN(arc->flags, FIRST_END_OK, (arc->first_end != NULL) && ISCLEAR(arc->flags, CLOSED_BIT) && ISCLEAR(arc->flags, FILLED_BIT) && arc->line_width /*&& ISCLEAR(arc->flags, RELIEF_OK)*/); ASSIGN(arc->flags, LAST_END_OK, (arc->last_end != NULL) && ISCLEAR(arc->flags, CLOSED_BIT) && ISCLEAR(arc->flags, FILLED_BIT) && arc->line_width /*&& ISCLEAR(arc->flags, RELIEF_OK)*/); } /* ********************************************************************************** * * Configure -- * ********************************************************************************** */ static int Configure(Item item, int argc, Tcl_Obj *CONST argv[], int *flags) { WidgetInfo *wi = item->wi; ArcItem arc = (ArcItem) item; int status = ZN_OK; status = ITEM_P.ConfigureAttributes((char *) item, -1, argc, argv, flags); if (ISSET(*flags, ZN_TILE_FLAG)) { Tk_Image tile; if (strcmp(arc->tile_name, "") != 0) { tile = Tk_GetImage(wi->interp, wi->win, arc->tile_name, ArcTileChange, (ClientData) arc); if (tile == NULL) { /* * The name will not be in sync with the image in * this case. */ status = ZN_ERROR; } } else { tile = ZnUnspecifiedImage; } if (arc->tile != ZnUnspecifiedImage) { Tk_FreeImage(arc->tile); } arc->tile = tile; } SetRenderFlags(arc); return status; } /* ********************************************************************************** * * Query -- * ********************************************************************************** */ static int Query(Item item, int argc, Tcl_Obj *CONST argv[]) { if (ITEM_P.QueryAttribute((char *) item, -1, argv[0]) == ZN_ERROR) { return ZN_ERROR; } return ZN_OK; } /* * Tangent -- * Compute a point describing the arc tangent at the first/last * end of the arc. The point is on the tangent segment next to * the arc (ie: it is suitable for drawing arrows). */ void Tangent(ArcItem arc, ZnBool first, ZnPoint *p) { double a2, b2, w_2, h_2; double angle; ZnPoint p1, center; if (first) { angle = DegreesToRadian(arc->start_angle); } else { angle = DegreesToRadian(arc->start_angle + arc->angle_extent); } p1.x = cos(angle); p1.y = sin(angle); w_2 = (arc->corner.x - arc->orig.x) / 2.0; h_2 = (arc->corner.y - arc->orig.y) / 2.0; center.x = (arc->orig.x + arc->corner.x) / 2.0; center.y = (arc->orig.y + arc->corner.y) / 2.0; /* * Slope of the initial segment. * * a1 = (center->y - p1.y) / (center->x - p1.x); * a2 = -1/a1; */ a2 = - p1.x / p1.y; b2 = p1.y - a2*p1.x; if (p1.y == 0.0) { p->x = p1.x; if (!first) { p->y = p1.y - 10.0; } else { p->y = p1.y + 10.0; } } else { if ((!first && (p1.y < 0.0)) || (first && (p1.y > 0.0))) { p->x = p1.x - 10.0; } else { p->x = p1.x + 10.0; } p->y = a2*p->x + b2; } p->x = center.x + REAL_TO_INT(p->x*w_2); p->y = center.y + REAL_TO_INT(p->y*h_2); } /* ********************************************************************************** * * ComputeCoordinates -- * ********************************************************************************** */ static void ComputeCoordinates(Item item, ZnBool force) { WidgetInfo *wi = item->wi; ArcItem arc = (ArcItem) item; ZnReal angle, sin1, cos1, sin2, cos2; ZnReal tmp, w_2, h_2, center_x, center_y; int num_p, i; ZnPoint *p_list, p; ZnPoint end_points[LINE_END_POINTS]; ZnReal width_2, height_2, ox, oy; ResetBBox(&item->item_bounding_box); /* * If it is neither filled nor outlined, then nothing to show. */ if (!arc->line_width && ISCLEAR(arc->flags, FILLED_BIT)) { return; } /* * Special casing for ellipse rotation and gradient. */ ZnTransfoDecompose(wi->current_transfo, NULL, NULL, &angle, NULL); width_2 = (arc->coords[1].x - arc->coords[0].x)/2.0; height_2 = (arc->coords[1].y - arc->coords[0].y)/2.0; if (((angle >= PRECISION_LIMIT) && ((width_2 - height_2) > PRECISION_LIMIT)) || arc->grad_geom) { SET(arc->flags, USING_POLY_BIT); if (!arc->render_shape) { arc->render_shape = ZnListNew(4, sizeof(ZnPoint)); } GetArcPath(DegreesToRadian(arc->start_angle), DegreesToRadian(arc->start_angle+arc->angle_extent), (ISCLEAR(arc->flags, CLOSED_BIT) ? 0 : ISCLEAR(arc->flags, PIE_SLICE_BIT) ? 1 : 2), arc->render_shape); p_list = (ZnPoint *) ZnListArray(arc->render_shape); num_p = ZnListSize(arc->render_shape); ox = (arc->coords[1].x + arc->coords[0].x)/2.0; oy = (arc->coords[1].y + arc->coords[0].y)/2.0; for (i = 0; i < num_p; i++, p_list++) { p.x = ox + p_list->x*width_2; p.y = oy + p_list->y*height_2; ZnTransformPoint(wi->current_transfo, &p, p_list); AddPointToBBox(&item->item_bounding_box, p_list->x, p_list->y); } tmp = (arc->line_width + 1.0) / 2.0 + 1.0; item->item_bounding_box.orig.x -= tmp; item->item_bounding_box.orig.y -= tmp; item->item_bounding_box.corner.x += tmp; item->item_bounding_box.corner.y += tmp; /* * Add the arrows if any. */ if (ISSET(arc->flags, FIRST_END_OK)) { GetLineEnd(p_list, p_list+1, arc->line_width, CapRound, arc->first_end, end_points); AddPointsToBBox(&item->item_bounding_box, end_points, LINE_END_POINTS); } if (ISSET(arc->flags, LAST_END_OK)) { GetLineEnd(&p_list[num_p-1], &p_list[num_p-2], arc->line_width, CapRound, arc->last_end, end_points); AddPointsToBBox(&item->item_bounding_box, end_points, LINE_END_POINTS); } return; } /* ******* ******** ********** * This part is for X drawn arcs: not rotated. ******* ******** ********** */ CLEAR(arc->flags, USING_POLY_BIT); ZnTransformPoint(wi->current_transfo, &arc->coords[0], &arc->orig); ZnTransformPoint(wi->current_transfo, &arc->coords[1], &arc->corner); if (arc->orig.x > arc->corner.x) { tmp = arc->orig.x; arc->orig.x = arc->corner.x; arc->corner.x = tmp; } if (arc->orig.y > arc->corner.y) { tmp = arc->orig.y; arc->orig.y = arc->corner.y; arc->corner.y = tmp; } /* * now compute the two points at the centers of the ends of the arc. * We first compute the position for a unit circle and then scale * to fit the shape. * Angles are running clockwise and y coordinates are inverted. */ angle = DegreesToRadian(arc->start_angle); sin1 = sin(angle); cos1 = cos(angle); angle += DegreesToRadian(arc->angle_extent); sin2 = sin(angle); cos2 = cos(angle); w_2 = (arc->corner.x - arc->orig.x) / 2; h_2 = (arc->corner.y - arc->orig.y) / 2; center_x = (arc->corner.x + arc->orig.x) / 2; center_y = (arc->corner.y + arc->orig.y) / 2; arc->center1.x = center_x + REAL_TO_INT(cos1*w_2); arc->center1.y = center_y + REAL_TO_INT(sin1*h_2); arc->center2.x = center_x + REAL_TO_INT(cos2*w_2); arc->center2.y = center_y + REAL_TO_INT(sin2*h_2); /* * Add the ends centers to the bbox. */ AddPointToBBox(&item->item_bounding_box, arc->center1.x, arc->center1.y); AddPointToBBox(&item->item_bounding_box, arc->center2.x, arc->center2.y); /* * If the arc is filled or if the outline is closed in pie slice, * add the center of the arc. */ if ((ISSET(arc->flags, FILLED_BIT) || ISSET(arc->flags, CLOSED_BIT)) && ISSET(arc->flags, PIE_SLICE_BIT)) { AddPointToBBox(&item->item_bounding_box, center_x, center_y); } /* * Then add the 3-o'clock, 6-o'clock, 9-o'clock, 12-o'clock position * as required. */ tmp = -arc->start_angle; if (tmp < 0) { tmp += 360; } if ((tmp < arc->angle_extent) || ((tmp - 360) > arc->angle_extent)) { AddPointToBBox(&item->item_bounding_box, arc->corner.x, center_y); } tmp = 180 - arc->start_angle; if (tmp < 0) { tmp += 360; } if ((tmp < arc->angle_extent) || ((tmp - 360) > arc->angle_extent)) { AddPointToBBox(&item->item_bounding_box, arc->orig.x, center_y); } tmp = 90 - arc->start_angle; if (tmp < 0) { tmp += 360; } if ((tmp < arc->angle_extent) || ((tmp - 360) > arc->angle_extent)) { AddPointToBBox(&item->item_bounding_box, center_x, arc->corner.y); } tmp = 270 - arc->start_angle; if (tmp < 0) { tmp += 360; } if ((tmp < arc->angle_extent) || ((tmp - 360) > arc->angle_extent)) { AddPointToBBox(&item->item_bounding_box, center_x, arc->orig.y); } /* * Now take care of the arc outline width plus one pixel of margin. */ tmp = (arc->line_width + 1.0) / 2.0 + 1.0; item->item_bounding_box.orig.x -= tmp; item->item_bounding_box.orig.y -= tmp; item->item_bounding_box.corner.x += tmp; item->item_bounding_box.corner.y += tmp; /* * Then add the arrows if any. */ if (ISSET(arc->flags, FIRST_END_OK)) { Tangent(arc, True, &p); GetLineEnd(&arc->center1, &p, arc->line_width, CapRound, arc->first_end, end_points); AddPointsToBBox(&item->item_bounding_box, end_points, LINE_END_POINTS); } if (ISSET(arc->flags, LAST_END_OK)) { Tangent(arc, False, &p); GetLineEnd(&arc->center2, &p, arc->line_width, CapRound, arc->last_end, end_points); AddPointsToBBox(&item->item_bounding_box, end_points, LINE_END_POINTS); } } /* ********************************************************************************** * * ToArea -- * Tell if the object is entirely outside (-1), * entirely inside (1) or in between (0). * ********************************************************************************** */ static int ToArea(Item item, ZnBBox *area, Tk_Uid tag_uid, int enclosed, ZnBool report) { ArcItem arc = (ArcItem) item; WidgetInfo *wi = item->wi; ZnPoint *points; ZnPoint pts[20]; /* Should be at least LINE_END_POINTS large */ ZnPoint center, tang; ZnBBox t_area; int num_points, result=-1, result2; ZnReal lw = arc->line_width; ZnReal rx, ry, angle, tmp; ZnBool inside, new_inside; if (ISSET(arc->flags, USING_POLY_BIT) && (ISSET(arc->flags, FILLED_BIT) || (arc->line_width))) { GetBezierPath(arc->render_shape, wi->work_pts); points = (ZnPoint *) ZnListArray(wi->work_pts); num_points = ZnListSize(wi->work_pts); if (ISSET(arc->flags, FILLED_BIT)) { result = PolygonInBBox(points, num_points, area, NULL); if (result == 0) { return 0; } } if (arc->line_width > 0) { result2 = PolylineInBBox(points, num_points, arc->line_width, CapRound, JoinRound, area); if (ISCLEAR(arc->flags, FILLED_BIT)) { if (result2 == 0) { return 0; } result = result2; } else if (result2 != result) { return 0; } if (ISSET(arc->flags, CLOSED_BIT) && ISSET(arc->flags, PIE_SLICE_BIT)) { pts[0] = points[num_points-1]; pts[1] = points[0]; if (PolylineInBBox(pts, 2, arc->line_width, CapRound, JoinRound, area) != result) { return 0; } } /* * Check line ends. */ if (ISSET(arc->flags, FIRST_END_OK)) { GetLineEnd(&points[0], &points[1], arc->line_width, CapRound, arc->first_end, pts); if (PolygonInBBox(pts, LINE_END_POINTS, area, NULL) != result) { return 0; } } if (ISSET(arc->flags, LAST_END_OK)) { GetLineEnd(&points[num_points-1], &points[num_points-2], arc->line_width, CapRound, arc->last_end, pts); if (PolygonInBBox(pts, LINE_END_POINTS, area, NULL) != result) { return 0; } } } return result; } /* ******* ******** ********** * The rest of this code deal with non rotated arcs. * * It has been stolen from tkCanvArc.c * ******* ******** ********** */ /* * Transform both the arc and the rectangle so that the arc's oval * is centered on the origin. */ center.x = (arc->orig.x + arc->corner.x)/2.0; center.y = (arc->orig.y + arc->corner.y)/2.0; t_area.orig.x = area->orig.x - center.x; t_area.orig.y = area->orig.y - center.y; t_area.corner.x = area->corner.x - center.x; t_area.corner.y = area->corner.y - center.y; rx = arc->corner.x - center.x + lw/2.0; ry = arc->corner.y - center.y + lw/2.0; /* * Find the extreme points of the arc and see whether these are all * inside the rectangle (in which case we're done), partly in and * partly out (in which case we're done), or all outside (in which * case we have more work to do). The extreme points include the * following, which are checked in order: * * 1. The outside points of the arc, corresponding to start and * extent. * 2. The center of the arc (but only in pie-slice mode). * 3. The 12, 3, 6, and 9-o'clock positions (but only if the arc * includes those angles). */ points = pts; angle = DegreesToRadian(arc->start_angle); points->x = rx*cos(angle); points->y = ry*sin(angle); angle += DegreesToRadian(arc->angle_extent); points[1].x = rx*cos(angle); points[1].y = ry*sin(angle); num_points = 2; points += 2; if (ISSET(arc->flags, PIE_SLICE_BIT) && (arc->angle_extent < 180.0)) { points->x = 0.0; points->y = 0.0; num_points++; points++; } tmp = -arc->start_angle; if (tmp < 0) { tmp += 360.0; } if ((tmp < arc->angle_extent) || ((tmp-360) > arc->angle_extent)) { points->x = rx; points->y = 0.0; num_points++; points++; } tmp = 180.0 - arc->start_angle; if (tmp < 0) { tmp += 360.0; } if ((tmp < arc->angle_extent) || ((tmp-360) > arc->angle_extent)) { points->x = 0.0; points->y = ry; num_points++; points++; } tmp = 90.0 - arc->start_angle; if (tmp < 0) { tmp += 360.0; } if ((tmp < arc->angle_extent) || ((tmp-360) > arc->angle_extent)) { points->x = -rx; points->y = 0.0; num_points++; points++; } tmp = 270.0 - arc->start_angle; if (tmp < 0) { tmp += 360.0; } if ((tmp < arc->angle_extent) || ((tmp-360) > arc->angle_extent)) { points->x = 0.0; points->y = -ry; num_points++; } /* * Now that we've located the extreme points, loop through them all * to see which are inside the rectangle. */ inside = PointInBBox(&t_area, pts->x, pts->y); for (points = pts+1; num_points > 1; points++, num_points--) { new_inside = PointInBBox(&t_area, points->x, points->y); if (new_inside != inside) { return 0; } } result = inside ? 1 : -1; /* * So far, oval appears to be outside rectangle, but can't yet tell * for sure. Next, test each of the four sides of the rectangle * against the bounding region for the arc. If any intersections * are found, then return "overlapping". First, test against the * polygon(s) forming the sides of a chord or pie-slice. */ if ((lw >= 1.0) && (ISSET(arc->flags, CLOSED_BIT))) { if (ISSET(arc->flags, PIE_SLICE_BIT)) { pts[0] = arc->center1; pts[1] = center; pts[2] = arc->center2; num_points = 3; } else { pts[0] = arc->center1; pts[1] = arc->center2; num_points = 2; } if (PolylineInBBox(pts, num_points, lw, CapRound, JoinRound, area) != result) { return 0; } } else if (ISSET(arc->flags, FILLED_BIT)) { if (ISSET(arc->flags, PIE_SLICE_BIT)) { if ((LineInBBox(¢er, &arc->center1, area) != result) || (LineInBBox(¢er, &arc->center2, area) != result)) { return 0; } } else { if (LineInBBox(&arc->center1, &arc->center2, area) != result) { return 0; } } } /* * Check line ends. */ if (ISSET(arc->flags, FIRST_END_OK)) { Tangent(arc, True, &tang); GetLineEnd(&arc->center1, &tang, arc->line_width, CapRound, arc->first_end, pts); if (PolygonInBBox(pts, LINE_END_POINTS, area, NULL) != result) { return 0; } } if (ISSET(arc->flags, LAST_END_OK)) { Tangent(arc, False, &tang); GetLineEnd(&arc->center2, &tang, arc->line_width, CapRound, arc->last_end, pts); if (PolygonInBBox(pts, LINE_END_POINTS, area, NULL) != result) { return 0; } } if (result == 1) { return result; } /* * Next check for overlap between each of the four sides and the * outer perimiter of the arc. If the arc isn't filled, then also * check the inner perimeter of the arc. */ if (HorizLineToArc(t_area.orig.x, t_area.corner.x, t_area.orig.y, rx, ry, arc->start_angle, arc->angle_extent) || HorizLineToArc(t_area.orig.x, t_area.corner.x, t_area.corner.y, rx, ry, arc->start_angle, arc->angle_extent) || VertLineToArc(t_area.orig.x, t_area.orig.y, t_area.corner.y, rx, ry, arc->start_angle, arc->angle_extent) || VertLineToArc(t_area.corner.x, t_area.orig.y, t_area.corner.y, rx, ry, arc->start_angle, arc->angle_extent)) { return 0; } if ((lw > 1.0) && ISCLEAR(arc->flags, FILLED_BIT)) { rx -= lw; ry -= lw; if (HorizLineToArc(t_area.orig.x, t_area.corner.x, t_area.orig.y, rx, ry, arc->start_angle, arc->angle_extent) || HorizLineToArc(t_area.orig.x, t_area.corner.x, t_area.corner.y, rx, ry, arc->start_angle, arc->angle_extent) || VertLineToArc(t_area.orig.x, t_area.orig.y, t_area.corner.y, rx, ry, arc->start_angle, arc->angle_extent) || VertLineToArc(t_area.corner.x, t_area.orig.y, t_area.corner.y, rx, ry, arc->start_angle, arc->angle_extent)) { return 0; } } /* * The arc still appears to be totally disjoint from the rectangle, * but it's also possible that the rectangle is totally inside the arc. * Do one last check, which is to check one point of the rectangle * to see if it's inside the arc. If it is, we've got overlap. If * it isn't, the arc's really outside the rectangle. */ if (Pick(item, &area->orig, ZN_NO_ITEM, 0, NULL, NULL) == 0.0) { return 0; } return -1; } /* ********************************************************************************** * * Draw -- * ********************************************************************************** */ static void Draw(Item item) { WidgetInfo *wi = item->wi; ArcItem arc = (ArcItem) item; XGCValues values; int width=0, height=0; ZnPoint *p=NULL; XPoint *xp=NULL; int num_points=0, i; if (ISSET(arc->flags, USING_POLY_BIT) && (ISSET(arc->flags, FILLED_BIT) || (arc->line_width))) { GetBezierPath(arc->render_shape, wi->work_pts); p = (ZnPoint *) ZnListArray(wi->work_pts); num_points = ZnListSize(wi->work_pts); ZnListAssertSize(wi->work_xpts, num_points); xp = (XPoint *) ZnListArray(wi->work_xpts); for (i = 0; i < num_points; i++, p++) { xp[i].x = (short) p->x; xp[i].y = (short) p->y; } p = (ZnPoint *) ZnListArray(wi->work_pts); } else { width = arc->corner.x - arc->orig.x; height = arc->corner.y - arc->orig.y; } /* Fill if requested */ if (ISSET(arc->flags, FILLED_BIT)) { if (arc->grad_geom) { ZnPoly poly; POLY_CONTOUR1(&poly, p, num_points); DrawPolygonGradient(wi, arc->grad_geom, arc->fill_color, &poly, &item->item_bounding_box); } else { values.foreground = ZnPixel(ZnColorGradientMidColor(wi->win, arc->fill_color)); values.arc_mode = ISSET(arc->flags, PIE_SLICE_BIT) ? ArcPieSlice : ArcChord; if (arc->tile != ZnUnspecifiedImage) { /* Fill Tiled */ Pixmap pmap = GetImagePixmap(wi->win, arc->tile_name, arc->tile, NULL); values.fill_style = FillTiled; values.tile = pmap; values.ts_x_origin = (int) item->item_bounding_box.orig.x; values.ts_y_origin = (int) item->item_bounding_box.orig.y; XChangeGC(wi->dpy, wi->gc, GCTileStipXOrigin|GCTileStipYOrigin|GCFillStyle|GCTile|GCArcMode, &values); } else if (arc->fill_pattern != ZnUnspecifiedPattern) { /* Fill stippled */ values.fill_style = FillStippled; values.stipple = arc->fill_pattern; values.ts_x_origin = (int) item->item_bounding_box.orig.x; values.ts_y_origin = (int) item->item_bounding_box.orig.y; XChangeGC(wi->dpy, wi->gc, GCTileStipXOrigin|GCTileStipYOrigin|GCFillStyle|GCStipple|GCForeground|GCArcMode, &values); } else { /* Fill solid */ values.fill_style = FillSolid; XChangeGC(wi->dpy, wi->gc, GCForeground|GCFillStyle|GCArcMode, &values); } if (ISSET(arc->flags, USING_POLY_BIT)) { XFillPolygon(wi->dpy, wi->draw_buffer, wi->gc, xp, num_points, Convex, CoordModeOrigin); } else { XFillArc(wi->dpy, wi->draw_buffer, wi->gc, arc->orig.x, arc->orig.y, width, height, -arc->start_angle*64, -arc->angle_extent*64); } } } /* * Draw the arc. */ if (arc->line_width) { ZnPoint end_points[LINE_END_POINTS]; XPoint xap[LINE_END_POINTS]; ZnPoint tang; SetLineStyle(wi->dpy, wi->gc, arc->line_style); values.foreground = ZnPixel(arc->line_color); values.line_width = (arc->line_width == 1) ? 0 : arc->line_width; values.cap_style = CapRound; values.join_style = JoinRound; if (arc->line_pattern == ZnUnspecifiedPattern) { values.fill_style = FillSolid; XChangeGC(wi->dpy, wi->gc, GCFillStyle|GCLineWidth|GCCapStyle|GCJoinStyle|GCForeground, &values); } else { values.fill_style = FillStippled; values.stipple = arc->line_pattern; XChangeGC(wi->dpy, wi->gc, GCFillStyle|GCStipple|GCLineWidth|GCCapStyle|GCJoinStyle|GCForeground, &values); } if (ISSET(arc->flags, USING_POLY_BIT)) { XDrawLines(wi->dpy, wi->draw_buffer, wi->gc, xp, num_points, CoordModeOrigin); if (ISSET(arc->flags, CLOSED_BIT) && ISSET(arc->flags, PIE_SLICE_BIT)) { XDrawLine(wi->dpy, wi->draw_buffer, wi->gc, xp[num_points-1].x, xp[num_points-1].y, xp[0].x, xp[0].y); } if (ISSET(arc->flags, FIRST_END_OK)) { p = (ZnPoint *) ZnListArray(arc->render_shape); GetLineEnd(p, p+1, arc->line_width, CapRound, arc->first_end, end_points); for (i = 0; i < LINE_END_POINTS; i++) { xap[i].x = end_points[i].x; xap[i].y = end_points[i].y; } XFillPolygon(wi->dpy, wi->draw_buffer, wi->gc, xap, LINE_END_POINTS, Nonconvex, CoordModeOrigin); } if (ISSET(arc->flags, LAST_END_OK)) { p = (ZnPoint *) ZnListArray(arc->render_shape); num_points = ZnListSize(arc->render_shape); GetLineEnd(&p[num_points-1], &p[num_points-2], arc->line_width, CapRound, arc->last_end, end_points); for (i = 0; i < LINE_END_POINTS; i++) { xap[i].x = end_points[i].x; xap[i].y = end_points[i].y; } XFillPolygon(wi->dpy, wi->draw_buffer, wi->gc, xap, LINE_END_POINTS, Nonconvex, CoordModeOrigin); } } else { XDrawArc(wi->dpy, wi->draw_buffer, wi->gc, arc->orig.x, arc->orig.y, width, height, -arc->start_angle*64, -arc->angle_extent*64); /* * If the outline is closed, draw the closure. */ if (ISSET(arc->flags, CLOSED_BIT)) { if (ISSET(arc->flags, PIE_SLICE_BIT)) { XPoint points[3]; points[0].x = arc->center1.x; points[0].y = arc->center1.y; points[1].x = (arc->corner.x + arc->orig.x) / 2; points[1].y = (arc->corner.y + arc->orig.y) / 2; points[2].x = arc->center2.x; points[2].y = arc->center2.y; XDrawLines(wi->dpy, wi->draw_buffer, wi->gc, points, 3, CoordModeOrigin); } else { XDrawLine(wi->dpy, wi->draw_buffer, wi->gc, arc->center1.x, arc->center1.y, arc->center2.x, arc->center2.y); } } if (ISSET(arc->flags, FIRST_END_OK)) { Tangent(arc, True, &tang); GetLineEnd(&arc->center1, &tang, arc->line_width, CapRound, arc->first_end, end_points); for (i = 0; i < LINE_END_POINTS; i++) { xap[i].x = end_points[i].x; xap[i].y = end_points[i].y; } XFillPolygon(wi->dpy, wi->draw_buffer, wi->gc, xap, LINE_END_POINTS, Nonconvex, CoordModeOrigin); } if (ISSET(arc->flags, LAST_END_OK)) { Tangent(arc, False, &tang); GetLineEnd(&arc->center2, &tang, arc->line_width, CapRound, arc->last_end, end_points); for (i = 0; i < LINE_END_POINTS; i++) { xap[i].x = end_points[i].x; xap[i].y = end_points[i].y; } XFillPolygon(wi->dpy, wi->draw_buffer, wi->gc, xap, LINE_END_POINTS, Nonconvex, CoordModeOrigin); } } } } /* ********************************************************************************** * * Render -- * ********************************************************************************** */ static void Render(Item item) { /*WidgetInfo *wi = item->wi; ArcItem arc = (ArcItem) item;*/ } /* ********************************************************************************** * * IsSensitive -- * ********************************************************************************** */ static ZnBool IsSensitive(Item item, int item_part) { return (ISSET(item->flags, SENSITIVE_BIT) && item->parent->class->IsSensitive(item->parent, ZN_NO_PART)); } /* ********************************************************************************** * * Pick -- * ********************************************************************************** */ static double Pick(Item item, ZnPoint *p, Item start_item, int aperture, Item *a_item, int *a_part) { WidgetInfo *wi = item->wi; ArcItem arc = (ArcItem) item; double dist = 1e40, new_dist; ZnBool point_in_angle, filled, closed; ZnBool in_triangle, acute_angle; ZnPoint p1, center, tang; ZnPoint *points; ZnPoint end_points[LINE_END_POINTS]; int num_points; int width, height; ZnDim lw = arc->line_width; if (ISSET(arc->flags, USING_POLY_BIT) && (ISSET(arc->flags, FILLED_BIT) || (arc->line_width))) { GetBezierPath(arc->render_shape, wi->work_pts); points = (ZnPoint *) ZnListArray(wi->work_pts); num_points = ZnListSize(wi->work_pts); if (ISSET(arc->flags, FILLED_BIT)) { dist = PolygonToPointDist(points, num_points, p); if (dist <= 0.0) { return 0.0; } } if (arc->line_width > 0) { new_dist = PolylineToPointDist(points, num_points, arc->line_width, CapRound, JoinRound, p); if (new_dist < dist) { dist = new_dist; } if (ISSET(arc->flags, CLOSED_BIT) && ISSET(arc->flags, PIE_SLICE_BIT)) { new_dist = LineToPointDist(&points[num_points-1], &points[0], p); if (new_dist < dist) { dist = new_dist; } } if (dist <= 0.0) { return 0.0; } } /* * Check line ends. */ if (ISSET(arc->flags, FIRST_END_OK)) { GetLineEnd(&points[0], &points[1], arc->line_width, CapRound, arc->first_end, end_points); new_dist = PolygonToPointDist(end_points, LINE_END_POINTS, p); if (new_dist < dist) { dist = new_dist; } if (dist <= 0.0) { return 0.0; } } if (ISSET(arc->flags, LAST_END_OK)) { GetLineEnd(&points[num_points-1], &points[num_points-2], arc->line_width, CapRound, arc->last_end, end_points); new_dist = PolygonToPointDist(end_points, LINE_END_POINTS, p); if (new_dist < dist) { dist = new_dist; } if (dist <= 0.0) { return 0.0; } } return dist; } /* ******* ******** ********** * The rest of this code deal with non rotated or relief arcs. * ******* ******** ********** */ center.x = (arc->corner.x + arc->orig.x) / 2; center.y = (arc->corner.y + arc->orig.y) / 2; width = arc->corner.x - arc->orig.x; height = arc->corner.y - arc->orig.y; /* * Let see if the point is in the angular range. First * transform the coordinates so that the oval is circular. */ p1.y = (p->y - center.y) / height; p1.x = (p->x - center.x) / width; point_in_angle = PointInAngle(arc->start_angle, arc->angle_extent, &p1); /* * Now try to compute the distance dealing with the * many possible configurations. */ filled = !ISCLEAR(arc->flags, FILLED_BIT); closed = !ISCLEAR(arc->flags, CLOSED_BIT); /* * First the case of an arc not filled, not closed. We suppose * here that the outline is drawn since we cannot get here without * filling or outlining. */ if (!filled && !closed) { if (point_in_angle) { dist = OvalToPointDist(¢er, width, height, lw, p); if (dist < 0.0) { dist = -dist; } } else { dist = hypot((p->x - arc->center1.x), (p->y - arc->center1.y)); } new_dist = hypot((p->x - arc->center2.x), (p->y - arc->center2.y)); if (new_dist < dist) { dist = new_dist; } /* Take into account CapRounded path. */ if (lw > 1) { dist -= lw/2; if (dist < 0.0) { dist = 0.0; } } if (dist == 0.0) { return 0.0; } /* * Check line ends. */ if (ISSET(arc->flags, FIRST_END_OK)) { Tangent(arc, True, &tang); GetLineEnd(&arc->center1, &tang, arc->line_width, CapRound, arc->first_end, end_points); new_dist = PolygonToPointDist(end_points, LINE_END_POINTS, p); if (new_dist < dist) { dist = new_dist; } if (dist <= 0.0) { return 0.0; } } if (ISSET(arc->flags, LAST_END_OK)) { Tangent(arc, False, &tang); GetLineEnd(&arc->center2, &tang, arc->line_width, CapRound, arc->last_end, end_points); new_dist = PolygonToPointDist(end_points, LINE_END_POINTS, p); if (new_dist < dist) { dist = new_dist; } if (dist <= 0.0) { return 0.0; } } return dist; } /* * Try to deal with filled and/or outline-closed arcs (not having full * angular extent). */ if (ISSET(arc->flags, PIE_SLICE_BIT)) { dist = LineToPointDist(¢er, &arc->center1, p); new_dist = LineToPointDist(¢er, &arc->center2, p); if (new_dist < dist) { dist = new_dist; } if (arc->line_width > 1) { if (closed) { dist -= arc->line_width/2; } /* * The arc outline is CapRounded so if it is not * full extent, includes the caps. */ else { new_dist = hypot(p->x - arc->center1.x, p->y - arc->center1.y) - lw/2; if (new_dist < dist) { dist = new_dist; } new_dist = hypot(p->x - arc->center2.x, p->y - arc->center2.y) - lw/2; if (new_dist < dist) { dist = new_dist; } } } if (dist <= 0.0) { return 0.0; } if (point_in_angle) { new_dist = OvalToPointDist(¢er, width, height, lw, p); if (new_dist < dist) { dist = new_dist; } if (dist < 0.0) { dist = filled ? 0.0 : -dist; } } return dist; } /* * This is a chord closed oval. */ dist = LineToPointDist(&arc->center1, &arc->center2, p); if (arc->line_width > 1) { if (closed) { dist -= arc->line_width/2; } /* * The arc outline is CapRounded so if it is not * full extent, includes the caps. */ else { new_dist = hypot(p->x - arc->center1.x, p->y - arc->center1.y) - lw/2; if (new_dist < dist) { dist = new_dist; } new_dist = hypot(p->x - arc->center2.x, p->y - arc->center2.y) - lw/2; if (new_dist < dist) { dist = new_dist; } } } if (dist <= 0.0) { return 0.0; } /* * Need to check the point against the triangle formed * by the difference between Chord mode and PieSlice mode. * This triangle needs to be included if extend is more than * 180 degrees and excluded otherwise. We try to see if * the center of the arc and the point are both on the same * side of the chord. If they are, the point is in the triangle */ if (arc->center1.x == arc->center2.x) { in_triangle = ((center.x <= arc->center1.x) && (p->x <= arc->center1.x)) || ((center.x > arc->center1.x) && (p->x > arc->center1.x)); } else { double a, b; a = ((double) (arc->center2.y - arc->center1.y)) / ((double) (arc->center2.x - arc->center1.x)); b = arc->center1.y - a*arc->center1.x; in_triangle = (((a*center.x + b - center.y) >= 0.0) == ((a*p->x + b - p->y) >= 0.0)); } acute_angle = ((arc->angle_extent > -180) && (arc->angle_extent < 180)); if (!point_in_angle && !acute_angle && filled && in_triangle) { return 0.0; } if (point_in_angle && (!acute_angle || !in_triangle)) { new_dist = OvalToPointDist(¢er, width, height, lw, p); if (new_dist < dist) { dist = new_dist; } if (dist < 0.0) { dist = filled ? 0.0 : -dist; } if (dist == 0.0) { return 0.0; } } return dist; } /* ********************************************************************************** * * GetClipVertices -- * Get the clipping shape. * ********************************************************************************** */ static ZnBool GetClipVertices(Item item, ZnPoly *poly) { WidgetInfo *wi = item->wi; ArcItem arc = (ArcItem) item; ZnReal ox, oy, width_2, height_2; int i, num_p; ZnPoint *p_list; if (ISCLEAR(arc->flags, USING_POLY_BIT)) { if (!arc->render_shape) { arc->render_shape = ZnListNew(4, sizeof(ZnPoint)); } GetArcPath(DegreesToRadian(arc->start_angle), DegreesToRadian(arc->start_angle+arc->angle_extent), (ISCLEAR(arc->flags, CLOSED_BIT) ? 0 : ISCLEAR(arc->flags, PIE_SLICE_BIT) ? 1 : 2), arc->render_shape); ox = (arc->corner.x + arc->orig.x) / 2.0; oy = (arc->corner.y + arc->orig.y) / 2.0; width_2 = (arc->corner.x - arc->orig.x) / 2.0; height_2 = (arc->corner.y - arc->orig.y) / 2.0; num_p = ZnListSize(arc->render_shape); p_list = (ZnPoint *) ZnListArray(arc->render_shape); for (i = 0; i < num_p; i++, p_list++) { p_list->x = ox + p_list->x*width_2; p_list->y = oy + p_list->y*height_2; } SET(arc->flags, USING_POLY_BIT); } GetBezierPath(arc->render_shape, wi->work_pts); POLY_CONTOUR1(poly, (ZnPoint *) ZnListArray(wi->work_pts), ZnListSize(wi->work_pts)); return False; } /* ********************************************************************************** * * Coords -- * Return or edit the item vertices. * ********************************************************************************** */ static int Coords(Item item, int contour, int index, int cmd, ZnPoint **pts, int *num_pts) { ArcItem arc = (ArcItem) item; if ((cmd == COORDS_ADD) || (cmd == COORDS_ADD_LAST) || (cmd == COORDS_REMOVE)) { Tcl_AppendResult(item->wi->interp, " arcs can't add or remove vertices", NULL); return ZN_ERROR; } else if (cmd == COORDS_REPLACE_ALL) { if (*num_pts != 2) { Tcl_AppendResult(item->wi->interp, " coords command need 2 points on arcs", NULL); return ZN_ERROR; } arc->coords[0] = (*pts)[0]; arc->coords[1] = (*pts)[1]; ITEM.Invalidate(item, ZN_COORDS_FLAG); } else if (cmd == COORDS_REPLACE) { if (*num_pts < 1) { Tcl_AppendResult(item->wi->interp, " coords command need at least 1 point", NULL); return ZN_ERROR; } if (index < 0) { index += 2; } if ((index < 0) || (index > 1)) { range_err: Tcl_AppendResult(item->wi->interp, " incorrect coord index, should be between -2 and 1", NULL); return ZN_ERROR; } arc->coords[index] = (*pts)[0]; ITEM.Invalidate(item, ZN_COORDS_FLAG); } else if (cmd == COORDS_READ_ALL) { *num_pts = 2; *pts = arc->coords; } else if (cmd == COORDS_READ) { if (index < 0) { index += 2; } if ((index < 0) || (index > 1)) { goto range_err; } *num_pts = 1; *pts = &arc->coords[index]; } return ZN_OK; } /* ********************************************************************************** * * PostScript -- * ********************************************************************************** */ static void PostScript(Item item, PostScriptInfo ps_info) { } /* ********************************************************************************** * * Exported functions struct -- * ********************************************************************************** */ static ItemClassStruct ARC_ITEM_CLASS = { sizeof(ArcItemStruct), False, /* has_fields */ 0, /* num_parts */ False, /* has_anchors */ "arc", arc_attrs, Init, Clone, Destroy, Configure, Query, NULL, /* GetFieldSet */ NULL, /* GetAnchor */ GetClipVertices, Coords, NULL, /* InsertChars */ NULL, /* DeleteChars */ NULL, /* Cursor */ NULL, /* Index */ NULL, /* Part */ NULL, /* Selection */ NULL, /* Contour */ ComputeCoordinates, ToArea, Draw, Render, IsSensitive, Pick, NULL, /* PickVertex */ PostScript }; ZnItemClassId ZnArc = (ZnItemClassId) &ARC_ITEM_CLASS;