/* Part of XPCE --- The SWI-Prolog GUI toolkit Author: Jan Wielemaker and Anjo Anjewierden E-mail: jan@swi.psy.uva.nl WWW: http://www.swi.psy.uva.nl/projects/xpce/ Copyright (c) 1985-2002, University of Amsterdam All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #ifndef M_PI #define M_PI (3.141593) #endif #ifdef MAXFLOAT #define DBL_INFINITE MAXFLOAT #elif defined(HUGE_VAL) #define DBL_INFINITE HUGE_VAL #else #define DBL_INFINITE HUGE #endif status initialiseLine(Line ln, Int xa, Int ya, Int xb, Int yb, Name arrows) { if ( isDefault(xa) ) xa = ZERO; if ( isDefault(ya) ) ya = ZERO; if ( isDefault(xb) ) xb = ZERO; if ( isDefault(yb) ) yb = ZERO; assign(ln, start_x, xa); assign(ln, start_y, ya); assign(ln, end_x, xb); assign(ln, end_y, yb); initialiseJoint((Joint) ln, ZERO, ZERO, ZERO, ZERO, arrows); return requestComputeGraphical(ln, DEFAULT); } #define area_points(x, y, w, h) { x1 = x; \ y1 = y; \ x2 = x+w; \ x2 += (w>=0 ? -1 : 1); \ y2 = y+h; \ y2 += (h>=0 ? -1 : 1); \ } static status loadLine(Line ln, IOSTREAM *fd, ClassDef def) { TRY(loadSlotsObject(ln, fd, def)); if ( isNil(ln->start_x) ) /* convert old (pre-4.9.4) line */ { Area a = ln->area; /* representation */ int x = valInt(a->x); int y = valInt(a->y); int w = valInt(a->w); int h = valInt(a->h); int x1, y1, x2, y2; area_points(x, y, w, h); assign(ln, start_x, toInt(x1)); assign(ln, start_y, toInt(y1)); assign(ln, end_x, toInt(x2)); assign(ln, end_y, toInt(y2)); } succeed; } status adjustFirstArrowLine(Line ln) { if ( notNil(ln->first_arrow) ) { Any av[4]; av[0] = ln->start_x; av[1] = ln->start_y; av[2] = ln->end_x; av[3] = ln->end_y; if ( qadSendv(ln->first_arrow, NAME_points, 4, av) ) { assign(ln->first_arrow, displayed, ON); return ComputeGraphical(ln->first_arrow); } } fail; } status adjustSecondArrowLine(Line ln) { if ( notNil(ln->second_arrow) ) { Any av[4]; av[0] = ln->end_x; av[1] = ln->end_y; av[2] = ln->start_x; av[3] = ln->start_y; if ( qadSendv(ln->second_arrow, NAME_points, 4, av) ) { assign(ln->second_arrow, displayed, ON); return ComputeGraphical(ln->second_arrow); } } fail; } status computeLine(Line ln) { if ( notNil(ln->request_compute) ) { int x1 = valInt(ln->start_x); int x2 = valInt(ln->end_x); int y1 = valInt(ln->start_y); int y2 = valInt(ln->end_y); int pen = valInt(ln->pen); int x, y, w, h; Area a = ln->area; if ( x1 < x2 ) { x = x1; w = x2-x1; } else { x = x2; w = x1-x2; } if ( y1 < y2 ) { y = y1; h = y2-y1; } else { y = y2; h = y1-y2; } if ( pen == 1 ) { w++; h++; } else if ( pen > 1 ) { int ex = (h > 0 ? (pen*h)/(w+h) : 0); /* h = 0: horizontal line */ int ey = (w > 0 ? (pen*w)/(w+h) : 0); /* w = 0: vertical line */ int hx = ex/2; int hy = ey/2; x -= hx; w += ex; y -= hy; h += ey; } if ( ln->selected == ON ) /* should be solved elsewhere */ { x -= 3; y -= 3; w += 6; h += 6; } CHANGING_GRAPHICAL(ln, assign(a, x, toInt(x)); assign(a, y, toInt(y)); assign(a, w, toInt(w)); assign(a, h, toInt(h)); if ( adjustFirstArrowLine(ln) ) unionNormalisedArea(a, ln->first_arrow->area); if ( adjustSecondArrowLine(ln) ) unionNormalisedArea(a, ln->second_arrow->area); changedEntireImageGraphical(ln)); assign(ln, request_compute, NIL); } succeed; } status copyLine(Line l1, Line l2) { copyJoint((Joint) l1, (Joint) l2); assign(l1, start_x, l2->start_x); assign(l1, start_y, l2->start_y); assign(l1, end_x, l2->end_x); assign(l1, end_y, l2->end_y); succeed; } static status RedrawAreaLine(Line ln, Area a) { int x, y, w, h; int x1 = valInt(ln->start_x); int x2 = valInt(ln->end_x); int y1 = valInt(ln->start_y); int y2 = valInt(ln->end_y); int pen = valInt(ln->pen); initialiseDeviceGraphical(ln, &x, &y, &w, &h); if ( pen != 0 ) { r_thickness(pen); r_dash(ln->texture); r_line(x1, y1, x2, y2); } if ( adjustFirstArrowLine(ln) ) RedrawArea(ln->first_arrow, a); if ( adjustSecondArrowLine(ln) ) RedrawArea(ln->second_arrow, a); return RedrawAreaGraphical(ln, a); } status paintSelectedLine(Line ln) /* assumes device is initialised! */ { r_complement(valInt(ln->start_x)-2, valInt(ln->start_y)-2, 5, 5); r_complement(valInt(ln->end_x)-2, valInt(ln->end_y)-2, 5, 5); succeed; } static status geometryLine(Line ln, Int x, Int y, Int w, Int h) { int needcompute = FALSE; Int dx = ZERO, dy = ZERO; if ( notDefault(w) ) { assign(ln, end_x, add(ln->start_x, w)); needcompute++; } if ( notDefault(h) ) { assign(ln, end_y, add(ln->start_y, h)); needcompute++; } if ( notDefault(x) ) { dx = sub(x, ln->area->x); assign(ln, start_x, add(ln->start_x, dx)); assign(ln, end_x, add(ln->end_x, dx)); } if ( notDefault(y) ) { dy = sub(y, ln->area->y); assign(ln, start_y, add(ln->start_y, dy)); assign(ln, end_y, add(ln->end_y, dy)); } CHANGING_GRAPHICAL(ln, if ( needcompute ) requestComputeGraphical(ln, DEFAULT); else { Area a = ln->area; assign(a, x, add(a->x, dx)); assign(a, y, add(a->y, dy)); changedEntireImageGraphical(ln); }); succeed; } static status startLine(Line ln, Point pos) { return pointsLine(ln, pos->x, pos->y, DEFAULT, DEFAULT); } static status endLine(Line ln, Point pos) { return pointsLine(ln, DEFAULT, DEFAULT, pos->x, pos->y); } static status startXLine(Line ln, Int x) { return pointsLine(ln, x, DEFAULT, DEFAULT, DEFAULT); } static status startYLine(Line ln, Int y) { return pointsLine(ln, DEFAULT, y, DEFAULT, DEFAULT); } static status endXLine(Line ln, Int x) { return pointsLine(ln, DEFAULT, DEFAULT, x, DEFAULT); } static status endYLine(Line ln, Int y) { return pointsLine(ln, DEFAULT, DEFAULT, DEFAULT, y); } static Point getStartLine(Line ln) { answer(answerObject(ClassPoint, ln->start_x, ln->start_y, EAV)); } static Point getEndLine(Line ln) { answer(answerObject(ClassPoint, ln->end_x, ln->end_y, EAV)); } status pointsLine(Line ln, Int sx, Int sy, Int ex, Int ey) { if ( !isDefault(sx) ) assign(ln, start_x, sx); if ( !isDefault(sy) ) assign(ln, start_y, sy); if ( !isDefault(ex) ) assign(ln, end_x, ex); if ( !isDefault(ey) ) assign(ln, end_y, ey); return requestComputeGraphical(ln, DEFAULT); } static status resizeLine(Line ln, Real xfactor, Real yfactor, Point origin) { float xf, yf; int ox = valInt(ln->area->x); int oy = valInt(ln->area->y); init_resize_graphical(ln, xfactor, yfactor, origin, &xf, &yf, &ox, &oy); if ( xf != 1.0 || yf != 1.0 ) { int x1, y1, x2, y2; x1 = ox + rfloat((float) (valInt(ln->start_x)-ox) * xf); x2 = ox + rfloat((float) (valInt(ln->end_x)-ox) * xf); y1 = oy + rfloat((float) (valInt(ln->start_y)-oy) * yf); y2 = oy + rfloat((float) (valInt(ln->end_y)-oy) * yf); assign(ln, start_x, toInt(x1)); assign(ln, start_y, toInt(y1)); assign(ln, end_x, toInt(x2)); assign(ln, end_y, toInt(y2)); return requestComputeGraphical(ln, DEFAULT); } succeed; } /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - int distanceLineToPoint() Calculate the distance between the infinite extended line through (x1, y1) and (x2, y2) to the point (px, py) or, if extended is 0, the distance to the line-segment between the two points. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ static int dist(int x1, int y1, int x2, int y2) { double dx = (double)(x2-x1); double dy = (double)(y2-y1); return rfloat(sqrt(dx*dx+dy*dy)); } int distanceLineToPoint(int x1, int y1, int x2, int y2, int px, int py, int extended) { float a; if ( y1 == y2 ) { if ( extended ) { return abs(y1 - py); } else { if ( x1 < x2 ) { if ( px < x1 ) return dist(x1, y1, px, py); else if ( px > x2 ) return dist(x2, y2, px, py); else return abs(y1 - py); } else { if ( px < x2 ) return dist(x2, y2, px, py); else if ( px > x1 ) return dist(x1, y1, px, py); else return abs(y1 - py); } } } if ( x1 == x2 ) { if ( extended ) { return abs(x1 - px); } else { if ( y1 < y2 ) { if ( py < y1 ) return dist(x1, y1, px, py); else if ( py > y2 ) return dist(x2, y2, px, py); else return abs(x1 - px); } else { if ( py < y2 ) return dist(x2, y2, px, py); else if ( py > y1 ) return dist(x1, y1, px, py); else return abs(x1 - px); } } } a = ((float)(y2 - y1)) / ((float)(x2 - x1)); if ( !extended ) { int xproj = rfloat((px - a*(float)(y1-py)+a*a*(float)x1)/(1.0+a*a)); /*Cprintf("Xproj = %d\n", xproj);*/ if ( x1 < x2 ) { if ( xproj < x1 ) return dist(x1, y1, px, py); else if ( xproj > x2 ) return dist(x2, y2, px, py); else goto not_extended; } else { if ( xproj < x2 ) return dist(x2, y2, px, py); else if ( xproj > x1 ) return dist(x1, y1, px, py); else goto not_extended; } } not_extended: return abs(rfloat((((float)(px - x1)) * a + ((float)(y1 - py))) / sqrt(1.0 + a*a))); } static status inEventAreaLine(Line ln, Int x, Int y) { int d; static int evtol = -1; if ( evtol < 0 ) { Int v = getClassVariableValueObject(ln, NAME_eventTolerance); evtol = (v ? valInt(v) : 5); } d = distanceLineToPoint(valInt(ln->start_x), valInt(ln->start_y), valInt(ln->end_x), valInt(ln->end_y), valInt(x), valInt(y), FALSE); if ( d < evtol ) succeed; fail; } static Int getDistanceLine(Line ln, Any obj, BoolObj segment) { if ( instanceOfObject(obj, ClassEvent) && notNil(ln->device) ) { if ( !(obj = getPositionEvent((EventObj) obj, (Graphical) ln->device)) ) fail; } if ( instanceOfObject(obj, ClassPoint) ) { Point pt = obj; int extended = (segment != OFF); answer(toInt(distanceLineToPoint(valInt(ln->start_x), valInt(ln->start_y), valInt(ln->end_x), valInt(ln->end_y), valInt(pt->x), valInt(pt->y), extended))); } else { Graphical gr2 = obj; answer(getDistanceArea(ln->area, gr2->area)); } } static Int getLengthLine(Line ln) { int dx = valInt(ln->end_x) - valInt(ln->start_x); int dy = valInt(ln->end_y) - valInt(ln->start_y); answer(toInt(isqrt(dx*dx + dy*dy))); } static void parms_line(Line ln, int *a, double *b) /* y = a + bx */ { int x1 = valInt(ln->start_x); int x2 = valInt(ln->end_x); int y1 = valInt(ln->start_y); int y2 = valInt(ln->end_y); if ( x1 == x2 ) { *b = DBL_INFINITE; /* vertical */ *a = 0; } else { *b = (double)(y2 - y1) / (double)(x2 - x1); *a = y1 - rfloat(*b * (double)x1); } DEBUG(NAME_intersection, Cprintf("%d,%d --> %d,%d: y = %d + %2fx\n", x1, y1, x2, y2, *a, *b)); } Point getIntersectionLine(Line l1, Line l2) { double b1, b2; int a1, a2; double xx; int xy; parms_line(l1, &a1, &b1); parms_line(l2, &a2, &b2); if ( b1 == b2 ) fail; /* parallel */ if ( b1 == DBL_INFINITE ) /* l1 is vertical */ { xx = (double) valInt(l1->end_x); xy = a2 + rfloat(b2 * xx); } else if ( b2 == DBL_INFINITE ) /* l2 is vertical */ { xx = (double) valInt(l2->end_x); xy = a1 + rfloat(b1 * xx); } else { xx = (double)(a2 - a1) / (b1 - b2); xy = a1 + rfloat(b1 * xx); } answer(answerObject(ClassPoint, toInt(rfloat(xx)), toInt(xy), EAV)); } Real getAngleLine(Line ln, Point p) { int x1 = valInt(ln->start_x); int x2 = valInt(ln->end_x); int y1 = valInt(ln->start_y); int y2 = valInt(ln->end_y); double angle; int rte = 0; /* relative-to-end */ if ( notDefault(p) && get_distance_point(p, x2, y2) < get_distance_point(p, x1, y1) ) rte++; if ( rte ) angle = atan2((double)(y2-y1), (double)(x1-x2)); else angle = atan2((double)(y1-y2), (double)(x2-x1)); if ( angle < 0 ) angle = 2.0 * M_PI + angle; angle = (angle * 180.0) / M_PI; answer(CtoReal(angle)); } static status normaliseLine(Line ln) { succeed; } static status orientationLine(Line ln, Name o) { succeed; } static status penLine(Line ln, Int pen) { if ( ln->pen != pen ) { assign(ln, pen, pen); return requestComputeGraphical(ln, DEFAULT); } succeed; } /******************************* * CLASS DECLARATION * *******************************/ /* Type declarations */ static char *T_points[] = { "start_x=[int]", "start_y=[int]", "end_x=[int]", "end_y=[int]" }; static char *T_initialise[] = { "start_x=[int]", "start_y=[int]", "end_x=[int]", "end_y=[int]", "arrows=[{none,first,second,both}]" }; static char *T_resize[] = { "factor_x=real", "factor_y=[real]", "origin=[point]" }; static char *T_geometry[] = { "x=[int]", "y=[int]", "width=[int]", "height=[int]" }; static char *T_distance[] = { "to=graphical|point|event", "segment=[bool]" }; /* Instance Variables */ vardecl var_line[] = { SV(NAME_startX, "int", IV_GET|IV_STORE, startXLine, NAME_tip, "X of start-point"), SV(NAME_startY, "int", IV_GET|IV_STORE, startYLine, NAME_tip, "Y of start-point"), SV(NAME_endX, "int", IV_GET|IV_STORE, endXLine, NAME_tip, "X of end-point"), SV(NAME_endY, "int", IV_GET|IV_STORE, endYLine, NAME_tip, "Y of end-point") }; /* Send Methods */ static senddecl send_line[] = { SM(NAME_initialise, 5, T_initialise, initialiseLine, DEFAULT, "Create line (X1,Y1) - (X2,Y2) with arrows"), SM(NAME_normalise, 0, NULL, normaliseLine, DEFAULT, "Redefined from graphical: no-op"), SM(NAME_orientation, 1, "{north_west,south_west,north_east,south_east}", orientationLine, DEFAULT, "Redefined from graphical: no-op"), SM(NAME_compute, 0, NULL, computeLine, NAME_update, "Update <-area of the line"), SM(NAME_geometry, 4, T_geometry, geometryLine, NAME_resize, "Define start and vector"), SM(NAME_copy, 1, "line", copyLine, NAME_copy, "Copy attributes from other line"), SM(NAME_DrawPostScript, 1, "{head,body}", drawPostScriptLine, NAME_postscript, "Create PostScript"), SM(NAME_end, 1, "point", endLine, NAME_tip, "Set end-point of line segment"), SM(NAME_points, 4, T_points, pointsLine, NAME_tip, "Reconfigure line (X1,Y1) - (X2,Y2)"), SM(NAME_start, 1, "point", startLine, NAME_tip, "Set start-point of line segment"), SM(NAME_resize, 3, T_resize, resizeLine, NAME_area, "Resize line with specified factor"), SM(NAME_pen, 1, "0..", penLine, NAME_appearance, "Thickness of drawing pen") }; /* Get Methods */ static getdecl get_line[] = { GM(NAME_angle, 1, "degrees=real", "origin=[point]", getAngleLine, NAME_calculate, "Angle"), GM(NAME_intersection, 1, "point", "with=line", getIntersectionLine, NAME_calculate, "Intersection between both infinitely extended lines"), GM(NAME_length, 0, "int", NULL, getLengthLine, NAME_calculate, "Distance between start and end-points"), GM(NAME_distance, 2, "int", T_distance, getDistanceLine, NAME_calculate, "Distance between areas or to point"), GM(NAME_end, 0, "point", NULL, getEndLine, NAME_tip, "New point representing end-point"), GM(NAME_start, 0, "point", NULL, getStartLine, NAME_tip, "New point representing start-point") }; /* Resources */ static classvardecl rc_line[] = { RC(NAME_selectionHandles, RC_REFINE, "line", NULL) }; /* Class Declaration */ static Name line_termnames[] = { NAME_startX, NAME_startY, NAME_endX, NAME_endY, NAME_arrows }; ClassDecl(line_decls, var_line, send_line, get_line, rc_line, 5, line_termnames, "$Rev$"); status makeClassLine(Class class) { declareClass(class, &line_decls); setRedrawFunctionClass(class, RedrawAreaLine); setInEventAreaFunctionClass(class, inEventAreaLine); setLoadStoreFunctionClass(class, loadLine, NULL); succeed; }