/* * Draw.c -- Implementation of common drawing routines. * * Authors : Patrick Lecoanet. * Creation date : Sat Dec 10 12:51:30 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. * */ /* ********************************************************************************** * * The algorihms used to draw the arrows, to do the 3d effects and to * smooth the polygons are adapted from Tk. * ********************************************************************************** */ #include "config.h" #include "Types.h" #include "Draw.h" #include "Geo.h" #include "List.h" #include "WidgetInfo.h" #include #include #include #define POLYGON_RELIEF_DRAW 0 #define POLYGON_RELIEF_DIST 1 #define POLYGON_RELIEF_BBOX 2 #define POLYGON_RELIEF_IN_BBOX 3 #define TOP_CONTRAST 13 #define BOTTOM_CONTRAST 6 #define MAX_INTENSITY 65535 #define ARROW_SHAPE_B 10.0 #define ARROW_SHAPE_C 5.0 #define OPEN_ARROW_SHAPE_A 4.0 #define CLOSED_ARROW_SHAPE_A ARROW_SHAPE_B #define LIGHTNING_SHAPE_A_RATIO 10.0 #define LIGHTNING_SHAPE_B_RATIO 8.0 /* ********************************************************************************** * * SetLineStyle -- * ********************************************************************************** */ void SetLineStyle(Display *display, GC gc, LineStyle line_style) { XGCValues values; static const char dashed[] = { 8 }; static const char dotted[] = { 2, 5 }; static const char mixed[] = { 8, 5, 2, 5 }; values.line_style = LineOnOffDash; switch (line_style) { case LINE_DASHED : XSetDashes(display, gc, 0, dashed, 1); break; case LINE_MIXED : XSetDashes(display, gc, 0, mixed, 4); break; case LINE_DOTTED : XSetDashes(display, gc, 0, dotted, 2); break; default: values.line_style = LineSolid; break; } XChangeGC(display, gc, GCLineStyle, &values); } /* ********************************************************************************** * * GetLineShape -- * Compute the points describing the given line shape between point p1 and p2. * If bbox is non null, it is filled with the bounding box of the shape. * * For the time being this procedure handles straight lines, right and left * lightnings, right and left corners, right and left double corners.. * * * Here are the parameters for lightnings: * * ******* * ******* * * ****** * * ****** ******+ * * ****** ****** * *| * ****** ****** * * | LIGHTNING_SHAPE_A * ****** ****** * * | * ****** * * | * ..******.........................+.+.*........................******.. * | * * ****** * | * * ****** ****** * | * * ****** ****** * | * * ****** ****** * | * ******* ****** * | * ****** * | * ****** * | ******** * | | | | * | |----| | LIGHTNING_SHAPE_B * | | * |--------------------------------| LENGTH / 2 * ********************************************************************************** */ void GetLineShape(RadarPoint *p1, RadarPoint *p2, unsigned int line_width, LineShape shape, RadarBBox *bbox, RadarList to_points) { RadarPoint *points; int num_points, i; /* * Compute all line points according to shape. */ if ((shape == LINE_LEFT_LIGHTNING) || (shape == LINE_RIGHT_LIGHTNING)) { double alpha, theta; double length, length2; double shape_a, shape_b; double dx, dy; double temp; num_points = LIGHTNING_POINTS; RadarListAssertSize(to_points, num_points); points = (RadarPoint *) RadarListArray(to_points); points[0] = *p1; points[3] = *p2; dx = p2->x - p1->x; dy = p2->y - p1->y; length = hypot(dx, dy); shape_a = length/LIGHTNING_SHAPE_A_RATIO + ((double) line_width)/2; shape_b = length/LIGHTNING_SHAPE_B_RATIO + ((double) line_width)/2; if (shape == LINE_LEFT_LIGHTNING) alpha = atan2(shape_a, shape_b); else alpha = -atan2(shape_a, shape_b); length2 = hypot(shape_a, shape_b); theta = atan2(-dy, dx); dx = p1->x + dx/2; dy = p1->y + dy/2; temp = cos(theta + alpha) * length2; points[1].x = dx + temp; points[2].x = dx - temp; temp = sin(theta + alpha) * length2; points[1].y = dy - temp; points[2].y = dy + temp; } else if (shape == LINE_LEFT_CORNER || shape == LINE_RIGHT_CORNER) { num_points = CORNER_POINTS; RadarListAssertSize(to_points, num_points); points = (RadarPoint *) RadarListArray(to_points); points[0] = *p1; points[2] = *p2; if (shape == LINE_LEFT_CORNER) { points[1].x = p1->x; points[1].y = p2->y; } else { points[1].x = p2->x; points[1].y = p1->y; } } else if (shape == LINE_DOUBLE_LEFT_CORNER || shape == LINE_DOUBLE_RIGHT_CORNER) { int dx, dy; num_points = DOUBLE_CORNER_POINTS; RadarListAssertSize(to_points, num_points); points = (RadarPoint *) RadarListArray(to_points); points[0] = *p1; points[3] = *p2; if (shape == LINE_DOUBLE_LEFT_CORNER) { dy = p2->y - p1->y; points[1].x = p1->x; points[2].x = p2->x; points[1].y = points[2].y = p1->y + dy/2; } else { dx = p2->x - p1->x; points[1].x = points[2].x = p1->x + dx/2; points[1].y = p1->y; points[2].y = p2->y; } } else /* if (shape) == LINE_STRAIGHT) */ { num_points = STRAIGHT_POINTS; RadarListAssertSize(to_points, num_points); points = (RadarPoint *) RadarListArray(to_points); points[0] = *p1; points[1] = *p2; } /* * Fill in the bbox, if requested. */ if (bbox) { ResetBBox(bbox); for (i = 0; i < num_points; i++) { AddPointToBBox(bbox, points[i].x, points[i].y); } /* Enlarge to take line_width into account. */ if (line_width > 1) { int lw_2 = (line_width+1)/2; bbox->orig.x -= lw_2; bbox->orig.y -= lw_2; bbox->corner.x += lw_2; bbox->corner.y += lw_2; } } } /* ********************************************************************************** * * DrawLineShape -- * Draw a line given the points describing its path. It is designed to work * with GetLineShape albeit it does fairly trivial things. In the future some * shapes might need cooperation between the two and the clients will be ready * for that. * * ********************************************************************************** */ void DrawLineShape(Display *display, Drawable draw_buffer, GC gc, RadarPoint *p, int num_p, LineStyle line_style, RadarColor foreground, unsigned int line_width, LineShape shape) { XPoint *xpoints; int i; XGCValues values; /* * Setup GC. */ SetLineStyle(display, gc, line_style); values.foreground = RadarPixel(foreground); values.line_width = (line_width == 1) ? 0 : line_width; values.fill_style = FillSolid; values.join_style = JoinRound; values.cap_style = CapRound; XChangeGC(display, gc, GCFillStyle | GCLineWidth | GCJoinStyle | GCCapStyle | GCForeground, &values); xpoints = (XPoint *) alloca(num_p * sizeof(XPoint)); for (i = 0; i < num_p; i++) { xpoints[i].x = p[i].x; xpoints[i].y = p[i].y; } XDrawLines(display, draw_buffer, gc, xpoints, num_p, CoordModeOrigin); } /* ********************************************************************************** * * DrawRectangleRelief -- * Draw the bevels inside bbox. * ********************************************************************************** */ void DrawRectangleRelief(WidgetInfo *wi, ReliefStyle relief, RadarColorGradient gradient, XRectangle *bbox, unsigned int line_width) { XGCValues gc_values; XPoint top_points[7]; /* * If we haven't enough space to draw, exit. */ if ((bbox->width < 2*line_width) || (bbox->height < 2*line_width)) { return; } /* * Grooves and ridges are drawn with two recursives calls with * half the width of the original one. */ if ((relief == RELIEF_RIDGE) || (relief == RELIEF_GROOVE)) { unsigned int new_line_width; int offset; XRectangle internal_bbox; new_line_width = line_width/2; offset = line_width - new_line_width; DrawRectangleRelief(wi, (relief==RELIEF_GROOVE)?RELIEF_BEVEL_IN:RELIEF_BEVEL_OUT, gradient, bbox, new_line_width); internal_bbox = *bbox; internal_bbox.x +=offset; internal_bbox.y += offset; internal_bbox.width -= offset*2; internal_bbox.height -= offset*2; DrawRectangleRelief(wi, (relief==RELIEF_GROOVE)?RELIEF_BEVEL_OUT:RELIEF_BEVEL_IN, gradient, &internal_bbox, new_line_width); return; } gc_values.fill_style = FillSolid; if (relief == RELIEF_BEVEL_IN) { gc_values.foreground = RadarColorGradientPixel(gradient, wi->win, -1); } else { gc_values.foreground = RadarColorGradientPixel(gradient, wi->win, 0); } XChangeGC(wi->dpy, wi->gc, GCFillStyle|GCForeground, &gc_values); XFillRectangle(wi->dpy, wi->draw_buffer, wi->gc, bbox->x, bbox->y + bbox->height - line_width, bbox->width, line_width); XFillRectangle(wi->dpy, wi->draw_buffer, wi->gc, bbox->x + bbox->width - line_width, bbox->y, line_width, bbox->height); if (relief == RELIEF_BEVEL_IN) { gc_values.foreground = RadarColorGradientPixel(gradient, wi->win, 0); } else { gc_values.foreground = RadarColorGradientPixel(gradient, wi->win, -1); } XChangeGC(wi->dpy, wi->gc, GCFillStyle|GCForeground, &gc_values); top_points[0].x = top_points[1].x = top_points[6].x = bbox->x; top_points[0].y = top_points[6].y = bbox->y + bbox->height; top_points[1].y = top_points[2].y = bbox->y; top_points[2].x = bbox->x + bbox->width; top_points[3].x = bbox->x + bbox->width - line_width; top_points[3].y = top_points[4].y = bbox->y + line_width; top_points[4].x = top_points[5].x = bbox->x + line_width; top_points[5].y = bbox->y + bbox->height - line_width; XFillPolygon(wi->dpy, wi->draw_buffer, wi->gc, top_points, 7, Nonconvex, CoordModeOrigin); } static void DoPolygonRelief(RadarPoint *p, int num_points, unsigned int line_width, int what_to_do, ...) { int i, j, processed_points, *result; RadarPoint *p1, *p2; RadarPoint pp1, pp2, new_pp1, new_pp2; RadarPoint perp, c, shift1, shift2; RadarPoint bevel_points[4]; XPoint bevel_xpoints[4]; RadarBool parallel, closed; WidgetInfo *wi = NULL; ReliefStyle relief = 0; RadarColorGradient gradient = NULL; RadarPoint *pp = NULL; double *dist = NULL; RadarBBox *bbox = NULL; va_list var; va_start(var, what_to_do); if (what_to_do == POLYGON_RELIEF_DIST) { pp = va_arg(var, RadarPoint *); dist = va_arg(var, double *); *dist = 1.0e40; } if (what_to_do == POLYGON_RELIEF_IN_BBOX) { bbox = va_arg(var, RadarBBox *); result = va_arg(var, int *); } else if (what_to_do == POLYGON_RELIEF_BBOX) { bbox = va_arg(var, RadarBBox *); ResetBBox(bbox); } else if (what_to_do == POLYGON_RELIEF_DRAW) { wi = va_arg(var, WidgetInfo *); relief = va_arg(var, int); gradient = va_arg(var, RadarColorGradient); } va_end(var); /* * If the polygon is closed (last point is the same as first) open it by * dropping the last point. The algorithm closes the path automatically. * We remember this to decide if we draw the last bevel or not and if we * need to generate ends perpendicular to the path.. */ closed = False; if ((p->x == p[num_points-1].x) && (p->y == p[num_points-1].y)) { closed = True; num_points--; } /* * We loop on all vertices of the polygon. * At each step we try to compute the corresponding border * corner `corner'. Then we build a polygon for the bevel. * Things look like this: * * bevel[1] / * * / * | / * | / * pp1 * * p[i-1] * | | bevel[0] * | | * | | * | | bevel[3] * | | p[i] * | | p1 p2 * pp2 * *--------------------* * | * | * corner *----*--------------------* * bevel[2] new_pp1 new_pp2 * * pp1 and pp2 are the ends of a segment // to p1 p2 at line_width * from it. These points are *NOT* necessarily on the perpendicular * going through p1 or p2. * This loop needs a bootstrap phase of two iterations (i.e we need to * process two points). This is why we start at the point before the last * and then wrap to the first point. * The algorithm discards any duplicate contiguous points. * It makes a special case if two consecutives edges are parallel: * * bevel[1] pp1 pp2 a bevel[2] * *-----------*--------------*----------* * \ * \ * p[i-1] \ bevel[3] * *--------*-------------------------*---* corner * bevel[0] p2 p1 / * / * / * ----------*-----------*-------------* * new_pp1 new_pp2 c * * In such a case we need to compute a, b, corner from pp1, pp2, new_pp1 * and new_pp2. We compute the perpendicular to p&,p2 through p1, intersect * it with pp1,pp2 to obtain a, intersect it with new_pp1, new_pp2 to * obtain c, shift a,c and intersect it with p1,p2 to obtain corner. * */ processed_points = 0; if (!closed) { i = 0; p1 = p; } else { i = -2; p1 = &p[num_points-2]; } for (p2 = p1+1; i < num_points; i++, p1 = p2, p2++) { /* * When it is time to wrap, do it */ if ((i == -1) || (i == num_points-1)) { p2 = p; } /* * Skip over duplicate vertices. */ if ((p2->x == p1->x) && (p2->y == p1->y)) { continue; } ShiftLine(p1, p2, line_width, &new_pp1, &new_pp2); bevel_points[3] = *p1; parallel = False; /* * The first two cases are for `open' polygons. We compute * a bevel closure that is perpendicular to the path. */ if ((processed_points == 0) && !closed) { perp.x = p1->x + (p2->y - p1->y); perp.y = p1->y - (p2->x - p1->x); IntersectLines(p1, &perp, &new_pp1, &new_pp2, &bevel_points[2]); } else if ((processed_points == num_points-1) && !closed) { perp.x = p1->x + ((p1-1)->y - p1->y); perp.y = p1->y - ((p1-1)->x - p1->x); IntersectLines(p1, &perp, &pp1, &pp2, &bevel_points[2]); } else if (processed_points >= 1) { parallel = !IntersectLines(&new_pp1, &new_pp2, &pp1, &pp2, &bevel_points[2]); if (parallel) { perp.x = p1->x + (p2->y - p1->y); perp.y = p1->y - (p2->x - p1->x); IntersectLines(p1, &perp, &pp1, &pp2, &bevel_points[2]); IntersectLines(p1, &perp, &new_pp1, &new_pp2, &c); ShiftLine(p1, &perp, line_width, &shift1, &shift2); IntersectLines(p1, p2, &shift1, &shift2, &bevel_points[3]); } } if ((processed_points >= 2) || (!closed && (processed_points == 1))) { if (what_to_do == POLYGON_RELIEF_DIST) { double new_dist; new_dist = PolygonToPointDist(bevel_points, 4, pp); if (new_dist < 0) { new_dist = 0; } *dist = MIN(*dist, new_dist); } else if (what_to_do == POLYGON_RELIEF_IN_BBOX) { if (processed_points <= 2) { *result = PolygonInBBox(bevel_points, 4, bbox); if (*result == 0) { return; } } else { if (PolygonInBBox(bevel_points, 4, bbox) != *result) { *result = 0; return; } } } else if (what_to_do == POLYGON_RELIEF_BBOX) { int i; for (i = 0; i < 4; i++) { AddPointToBBox(bbox, bevel_points[i].x, bevel_points[i].y); } } else if (what_to_do == POLYGON_RELIEF_DRAW) { RadarReal dx, dy; RadarBool light_on_left; XGCValues gc_values; dx = bevel_points[3].x - bevel_points[0].x; dy = bevel_points[3].y - bevel_points[0].y; if (dx > 0.0) { light_on_left = (dy <= dx); } else { light_on_left = (dy < dx); } gc_values.fill_style = FillSolid; if (light_on_left ^ (relief == RELIEF_BEVEL_OUT)) { gc_values.foreground = RadarColorGradientPixel(gradient, wi->win, -1); } else { gc_values.foreground = RadarColorGradientPixel(gradient, wi->win, 0); } XChangeGC(wi->dpy, wi->gc, GCFillStyle|GCForeground, &gc_values); for (j = 0; j < 4; j++) { bevel_xpoints[j].x = REAL_TO_INT(bevel_points[j].x); bevel_xpoints[j].y = REAL_TO_INT(bevel_points[j].y); } XFillPolygon(wi->dpy, wi->draw_buffer, wi->gc, bevel_xpoints, 4, Convex, CoordModeOrigin); } } pp1 = new_pp1; pp2 = new_pp2; bevel_points[0] = bevel_points[3]; if (parallel) { bevel_points[1] = c; } else if ((processed_points >= 1) || !closed) { bevel_points[1] = bevel_points[2]; } processed_points++; } } /* ********************************************************************************** * * GetPolygonReliefBBox -- * Returns the bevelled polygon bounding box. * ********************************************************************************** */ void GetPolygonReliefBBox(RadarList points, unsigned int line_width, RadarBBox *bbox) { DoPolygonRelief(RadarListArray(points), RadarListSize(points), line_width, POLYGON_RELIEF_BBOX, bbox); } /* ********************************************************************************** * * PolygonReliefInBBox -- * Returns (-1) if the relief is entirely outside the bbox, (1) if it is * entirely inside or (0) if in between * ********************************************************************************** */ int PolygonReliefInBBox(RadarList points, unsigned int line_width, RadarBBox *area) { int result; DoPolygonRelief(RadarListArray(points), RadarListSize(points), line_width, POLYGON_RELIEF_IN_BBOX, area, &result); return result; } /* ********************************************************************************** * * PolygonReliefToPointDist -- * Returns the distance between the given point and * the bevelled polygon. * ********************************************************************************** */ double PolygonReliefToPointDist(RadarList points, unsigned int line_width, RadarPoint *pp) { double dist; DoPolygonRelief(RadarListArray(points), RadarListSize(points), line_width, POLYGON_RELIEF_DIST, pp, &dist); return dist; } /* ********************************************************************************** * * DrawPolygonRelief -- * Draw the bevels around path. * ********************************************************************************** */ void DrawPolygonRelief(WidgetInfo *wi, ReliefStyle relief, RadarColorGradient gradient, RadarPoint *points, int num_points, int line_width) { /* * Grooves and ridges are drawn with two calls. The first * with the original width, the second with half the width. */ if ((relief == RELIEF_RIDGE) || (relief == RELIEF_GROOVE)) { DoPolygonRelief(points, num_points, line_width, POLYGON_RELIEF_DRAW, wi, (int) (relief==RELIEF_GROOVE)?RELIEF_BEVEL_OUT:RELIEF_BEVEL_IN, gradient); DoPolygonRelief(points, num_points, line_width/2, POLYGON_RELIEF_DRAW, wi, (int) (relief==RELIEF_GROOVE)?RELIEF_BEVEL_IN:RELIEF_BEVEL_OUT, gradient); } else { DoPolygonRelief(points, num_points, line_width, POLYGON_RELIEF_DRAW, wi, (int) relief, gradient); } }