/* 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 forwards int bestConnectionPoint(Device, Name, int, int, Graphical, Handle *, int *, int *); forwards int relateConnection(Connection c, Graphical from, Graphical to); static int distanceLineToPoint_int(int x1, int y1, int x2, int y2, int px, int py); #define getStartXLine(ln) (ln)->start_x #define getStartYLine(ln) (ln)->start_y #define getEndXLine(ln) (ln)->end_x #define getEndYLine(ln) (ln)->end_y static status initialiseConnection(Connection c, Graphical from, Graphical to, Link link, Name from_handle, Name to_handle) { static Link default_link = NIL; TRY(initialiseLine((Line) c, ZERO, ZERO, ZERO, ZERO, DEFAULT)); if ( isDefault(link) ) { if ( isNil(default_link) ) default_link = globalObject(NAME_defaultLink, ClassLink, EAV); link = default_link; } copyLine((Line) c, link->line); assign(c, link, link); assign(c, from_handle, isDefault(from_handle) ? (Name) NIL : from_handle); assign(c, to_handle, isDefault(to_handle) ? (Name) NIL : to_handle); assign(c, fixed_from, isDefault(from_handle) ? OFF : ON); assign(c, fixed_to, isDefault(to_handle) ? OFF : ON); return relateConnection(c, from, to); } static status unlinkConnection(Connection c) { if ( notNil(c->from) ) detachConnectionGraphical(c->from, c); if ( notNil(c->to) ) detachConnectionGraphical(c->to, c); return unlinkGraphical((Graphical) c); } static status relateConnection(Connection c, Graphical from, Graphical to) { if ( from != c->from && notNil(c->from) ) { detachConnectionGraphical(c->from, c); assign(c, from, NIL); } if ( to != c->to && notNil(c->to) ) { detachConnectionGraphical(c->to, c); assign(c, to, NIL); } if ( notNil(from) ) { attachConnectionGraphical(from, c); assign(c, from, from); } if ( notNil(to) ) { attachConnectionGraphical(to, c); assign(c, to, to); } return updateDeviceConnection(c); } static status updateLineConnection(Connection c, Int x1, Int y1, Int x2, Int y2) { if ( x1 != getStartXLine((Line) c) || y1 != getStartYLine((Line) c) || x2 != getEndXLine((Line) c) || y2 != getEndYLine((Line) c) ) send(c, NAME_points, x1, y1, x2, y2, EAV); succeed; } #define NO_POINTS 0 #define SAME_POINTS 1 #define POINTS_CHANGED 2 static status getConnectionPointsConnection(Connection c, Graphical from, Graphical to, int *x1, int *y1, int *x2, int *y2) { int cxfrom, cyfrom; Device dev = c->device; Handle hf = FAIL, ht = FAIL; int hf_ok = FALSE, ht_ok = FALSE; if ( notNil(c->from_handle) ) hf = getHandleGraphical(from, c->from_handle); if ( notNil(c->to_handle) ) ht = getHandleGraphical(to, c->to_handle); if ( c->fixed_from == ON && hf != FAIL ) { *x1 = valInt(getXHandle(hf, from, dev)); *y1 = valInt(getYHandle(hf, from, dev)); hf_ok = TRUE; } if ( c->fixed_to == ON && ht != FAIL ) { *x2 = valInt(getXHandle(ht, to, dev)); *y2 = valInt(getYHandle(ht, to, dev)); ht_ok = TRUE; } if ( hf_ok && ht_ok ) /* both fixed and available */ return POINTS_CHANGED; if ( hf_ok && !ht_ok ) /* `to_handle' needs to be fixed */ { if ( !bestConnectionPoint(dev, c->link->to, *x1, *y1, to, &ht, x2, y2) ) return NO_POINTS; assign(c, to_handle, ht->name); return POINTS_CHANGED; } if ( !hf_ok && ht_ok ) /* `from_handle' needs to be fixed */ { if ( !bestConnectionPoint(dev, c->link->from, *x2, *y2, from, &hf, x1, y1)) return NO_POINTS; assign(c, from_handle, hf->name); return POINTS_CHANGED; } if ( hf != FAIL && ht != FAIL ) { if ( getXHandle(hf, from, dev) == getStartXLine((Line) c) && getYHandle(hf, from, dev) == getStartYLine((Line) c) && getXHandle(ht, to, dev) == getEndXLine((Line) c) && getYHandle(ht, to, dev) == getEndYLine((Line) c) && hf->name == c->link->from && ht->name == c->link->to ) return SAME_POINTS; } cxfrom = valInt(getAbsoluteXGraphical(from,dev)) + valInt(from->area->w)/2; cyfrom = valInt(getAbsoluteYGraphical(from,dev)) + valInt(from->area->h)/2; DEBUG(NAME_absolutePosition, Cprintf("getConnectionPointsConnection(): dev=%s\n", pp(dev))); TRY(bestConnectionPoint(dev, c->link->to, cxfrom, cyfrom, to, &ht, x2, y2)); TRY(bestConnectionPoint(dev, c->link->from, *x2, *y2, from, &hf, x1, y1)); TRY(bestConnectionPoint(dev, c->link->to, *x1, *y1, to, &ht, x2, y2)); assign(c, from_handle, hf->name); assign(c, to_handle, ht->name); return POINTS_CHANGED; } /* (JW) Determine the best point to link up with a handle of specified type. It assumes the connection line starts at (x, y). */ static int bestConnectionPoint(Device dev, Name kind, int x, int y, Graphical gr, Handle *hp, int *xp, int *yp) { int bestx = 0, besty = 0, bestd=10000000, bestdc=10000000; int cx, cy; int X, Y, D, DC; int found = FAIL; Cell cell; DEBUG(NAME_handle, Cprintf("bestConnectionPoint(%s, %s, %d, %d, %s) --> ", pp(dev), pp(kind), x, y, pp(gr))); #define FindAHandle \ { Handle h = cell->value; \ if ( h->kind == kind ) \ { if ( !found ) \ { *hp = h; \ found = SUCCEED; \ } else \ goto findbest; \ } \ } if ( notNil(gr->handles) ) { for_cell(cell, gr->handles) FindAHandle; } if ( notNil(classOfObject(gr)->handles) ) { for_cell(cell, classOfObject(gr)->handles) FindAHandle; } if ( found ) { Int hx, hy; getXYHandle(*hp, gr, dev, &hx, &hy); *xp = valInt(hx); *yp = valInt(hy); DEBUG(NAME_handle, Cprintf("%s, %d, %d\n", pp((*hp)->name), *xp, *yp)); succeed; } findbest: cx = valInt(getAbsoluteXGraphical(gr, dev)) + valInt(gr->area->w)/2; cy = valInt(getAbsoluteYGraphical(gr, dev)) + valInt(gr->area->h)/2; #define FindBestHandle \ { Handle h = cell->value; \ Int hx, hy; \ double dx, dy; \ if ( h->kind != kind ) \ continue; \ getXYHandle(h, gr, dev, &hx, &hy); \ X = valInt(hx); Y = valInt(hy); \ dx = (double)(x-X); dy = (double)(y-Y); \ D = rdouble(sqrt(dx*dx+dy*dy)); \ DC = distanceLineToPoint_int(x, y, X, Y, cx, cy); \ if ((D + DC < bestd + bestdc) || found == FAIL) \ { bestd = D; \ bestdc = DC; \ bestx = X; \ besty = Y; \ *hp = h; \ found = SUCCEED; \ } \ } if ( notNil(gr->handles) ) { for_cell(cell, gr->handles) FindBestHandle; } if ( notNil(classOfObject(gr)->handles) ) { for_cell(cell, classOfObject(gr)->handles) FindBestHandle; } if ( found == FAIL ) { DEBUG(NAME_handle, Cprintf("FAIL\n")); fail; } DEBUG(NAME_handle, Cprintf("%s, %d, %d\n", pp((*hp)->name), bestx, besty)); *xp = bestx; *yp = besty; return found; } status updateHideExposeConnection(Connection c) { Device ldev; if ( isNil(ldev = c->device) ) succeed; if ( ldev == c->from->device && ldev == c->to->device ) { if ( beforeChain(ldev->graphicals, c->from, c->to) ) exposeGraphical(c, c->to); else exposeGraphical(c, c->from); } else exposeGraphical(c, DEFAULT); succeed; } static status computeConnection(Connection c) { if ( notNil(c->request_compute) ) { Graphical from = c->from; Graphical to = c->to; Device dev = c->device; if ( getIsDisplayedGraphical(from, dev) == ON && getIsDisplayedGraphical(to, dev) == ON ) { int x1, y1, x2, y2; switch( getConnectionPointsConnection(c, from, to, &x1, &y1, &x2, &y2) ) { case POINTS_CHANGED: updateLineConnection(c, toInt(x1), toInt(y1), toInt(x2), toInt(y2)); /*FALLTHROUGH*/ case SAME_POINTS: computeLine((Line)c); DisplayedGraphical(c, ON); assign(c, request_compute, NIL); succeed; case NO_POINTS: break; } } assign(c, request_compute, NIL); return DisplayedGraphical(c, OFF); } succeed; } status updateDeviceConnection(Connection c) { Graphical from = c->from; Graphical to = c->to; Device device; if ( isNil(from) || isNil(to) || !(device = getCommonDeviceGraphical(c->from, c->to)) ) return DeviceGraphical(c, NIL); DeviceGraphical(c, device); return requestComputeGraphical(c, DEFAULT); } static status updateLinkAttributesConnection(Connection c) { Line proto = c->link->line; CHANGING_GRAPHICAL(c, assign(c, texture, proto->texture); assign(c, pen, proto->pen); setArrowsJoint((Joint) c, proto->first_arrow, proto->second_arrow); changedEntireImageGraphical(c)); return requestComputeGraphical(c, DEFAULT); } /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Connections cannot be moved themselves. The only method understood by a connection that changes it's start and end-position is ->points. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ static int in_points = 0; static status pointsConnection(Connection c, Int x1, Int y1, Int x2, Int y2) { in_points++; pointsLine((Line) c, x1, y1, x2, y2); in_points--; succeed; } static status geometryConnection(Connection c, Int x, Int y, Int w, Int h) { if ( in_points > 0 ) return geometryGraphical(c, x, y, w, h); succeed; } static Graphical getOppositeConnection(Connection c, Graphical gr) { if ( c->to == gr ) answer(c->from); if ( c->from == gr ) answer(c->to); fail; } static status eventConnection(Connection c, EventObj ev) { if ( eventGraphical(c, ev) ) succeed; if ( c->active != OFF ) { Chain recognisers; Cell cell; TRY( recognisers = getAllRecognisersGraphical(c->link->line, OFF) ); for_cell(cell, recognisers) if ( qadSendv(cell->value, NAME_event, 1, (Any*)&ev) ) succeed; } fail; } /******************************* * CLASS DECLARATION * *******************************/ /* Type declarations */ static char *T_initialise[] = { "from=graphical", "to=graphical", "link=[link]", "handle_from=[name]*", "handle_to=[name]*" }; static char *T_points[] = { "start_x=[int]", "start_y=[int]", "end_x=[int]", "end_y=[int]" }; static char *T_geometry[] = { "x=[int]", "y=[int]", "width=[int]", "height=[int]" }; static char *T_relate[] = { "from=graphical*", "to=graphical*" }; /* Instance Variables */ static vardecl var_connection[] = { IV(NAME_link, "link", IV_GET, NAME_relation, "Generic definition of the link"), IV(NAME_from, "graphical*", IV_GET, NAME_relation, "Graphical at `from' side"), IV(NAME_to, "graphical*", IV_GET, NAME_relation, "Graphical at `to' side"), IV(NAME_fromHandle, "name*", IV_GET, NAME_relation, "Name of 1st handle link is connected to"), IV(NAME_toHandle, "name*", IV_GET, NAME_relation, "Name of 2nd handle link is connected to"), IV(NAME_fixedFrom, "bool", IV_GET, NAME_relation, "From side is fixed"), IV(NAME_fixedTo, "bool", IV_GET, NAME_relation, "To side is fixed") }; /* Send Methods */ static senddecl send_connection[] = { SM(NAME_compute, 0, NULL, computeConnection, DEFAULT, "Recompute the line"), SM(NAME_geometry, 4, T_geometry, geometryConnection, DEFAULT, "Do nothing: constrained by connected graphicals"), SM(NAME_initialise, 5, T_initialise, initialiseConnection, DEFAULT, "Create from graphicals, link and handle names"), SM(NAME_points, 4, T_points, pointsConnection, DEFAULT, "Set X1, Y1, X2, Y2"), SM(NAME_unlink, 0, NULL, unlinkConnection, DEFAULT, "Detach from graphicals"), SM(NAME_relate, 2, T_relate, relateConnection, NAME_relation, "Specify the graphicals related"), SM(NAME_event, 1, "event", eventConnection, NAME_event, "Process an event"), SM(NAME_updateLinkAttributes, 0, NULL, updateLinkAttributesConnection, NAME_update, "Re-read the link properties") }; /* Get Methods */ static getdecl get_connection[] = { GM(NAME_opposite, 1, "graphical", "graphical", getOppositeConnection, NAME_relation, "Other side of the connection"), GM(NAME_idealLength, 0, "0..", NULL, getFailObject, NAME_layout, "Ideal length for `graphical->layout'") }; /* Resources */ #define rc_connection NULL /* static classvardecl rc_connection[] = { }; */ /* Class Declaration */ static Name connection_termnames[] = { NAME_from, NAME_to, NAME_link, NAME_fromHandle, NAME_toHandle }; ClassDecl(connection_decls, var_connection, send_connection, get_connection, rc_connection, 5, connection_termnames, "$Rev$"); status makeClassConnection(Class class) { declareClass(class, &connection_decls); cloneStyleClass(class, NAME_relation); distanceLineToPoint_int(0, 0, 10, 10, 0, 10); /* initialise */ succeed; } #define STEP 20 #define ENTRIES 200 #define EPS ((float) (ENTRIES / STEP)) #define EMS (ENTRIES * STEP) static int distanceLineToPoint_int(int x1, int y1, int x2, int y2, int px, int py) { static int atable[ENTRIES+1]; static int done = FALSE; int a, d; int dx = x2-x1; int dy = y2-y1; if ( y1 == y2 || abs(dy) * 16 < abs(dx) ) return abs(y1 - py); if ( x1 == x2 || abs(dx) * 16 < abs(dy) ) return abs(x1 - px); if ( !done ) { int i; for(i=0; i <= ENTRIES; i++) atable[i] = rfloat((float) ENTRIES * sqrt(1.0 + (i/EPS)*(i/EPS))); done = TRUE; } a = (ENTRIES*(dy)) / dx; if ( a < -EMS ) { a = -EMS; } else { if ( a > EMS ) a = EMS; } d = (a*(px-x1) + ENTRIES*(y1-py)) / atable[abs(a)/STEP]; return abs(d); }