/* 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 static status updateHandlesTree(Tree); static status rootTree(Tree t, Node root, BoolObj relink); static Any div_h_2; /* h/2 */ static Any div_w_2; /* h/2 */ static status initialiseTree(Tree t, Node node) { if ( isDefault(node) ) node = NIL; initialiseFigure((Figure) t); assign(t, auto_layout, ON); assign(t, link, newObject(ClassLink, NAME_parent, NAME_son, EAV)); assign(t, rootHandlers, newObject(ClassChain, EAV)); assign(t, leafHandlers, newObject(ClassChain, EAV)); assign(t, nodeHandlers, newObject(ClassChain, EAV)); assign(t, collapsedHandlers, newObject(ClassChain, EAV)); obtainClassVariablesObject(t); if ( !div_h_2 ) { div_h_2 = newObject(ClassDivide, NAME_h, TWO, EAV); protectObject(div_h_2); } assign(t, sonHandle, newObject(ClassHandle, minInt(t->linkGap), div_h_2, NAME_son, EAV)); assign(t, parentHandle, newObject(ClassHandle, newObject(ClassPlus, NAME_w, t->linkGap, EAV), div_h_2, NAME_parent, EAV)); assign(t, root, NIL); assign(t, displayRoot, NIL); if ( notNil(node) ) rootTree(t, node, OFF); requestComputeTree(t); succeed; } static status rootTree(Tree t, Node root, BoolObj relink) { if ( isNil(root) ) { if ( notNil(t->root) ) { setFlag(t, F_FREEING); /* HACK! */ freeObject(t->root); clearFlag(t, F_FREEING); assign(t, root, NIL); assign(t, displayRoot, NIL); clearDevice((Device)t, NAME_erase); } } else { if ( notNil(t->root) ) { if ( relink == ON ) { Node oldroot = t->root; addCodeReference(oldroot); displayTree(t, root); assign(t, root, root); assign(t, displayRoot, root); assign(root, collapsed, OFF); send(root, NAME_son, oldroot, EAV); delCodeReference(oldroot); return requestComputeTree(t); } else rootTree(t, NIL, OFF); } displayTree(t, root); assign(t, root, root); assign(t, displayRoot, root); } return requestComputeTree(t); } static status unlinkTree(Tree t) { if ( notNil(t->root) ) freeObject(t->root); return unlinkDevice((Device) t); } status requestComputeTree(Tree t) { return requestComputeGraphical(t, DEFAULT); } /******************************* * REPAINT * *******************************/ static int leading_x_tree(Tree t) { Node n; if ( notNil((n=t->displayRoot)) && t->direction == NAME_list ) { Image img = NULL; if ( n->collapsed == ON ) img = getClassVariableValueObject(t, NAME_collapsedImage); else if ( n->collapsed == OFF ) img = getClassVariableValueObject(t, NAME_expandedImage); if ( img && notNil(img) ) { int lg = valInt(t->levelGap)/2; int iw2 = (valInt(img->size->w)+1)/2; return lg+iw2; } } return 0; } static void RedrawAreaNode(Node node, Image cimg, Image eimg) { Graphical img = node->image; Tree t = node->tree; int lg = valInt(t->levelGap)/2; Node lastnode; int ly = valInt(img->area->y) + valInt(img->area->h)/2; int lx = valInt(img->area->x); Image i = NULL; if ( node->collapsed == OFF && eimg ) i = eimg; else if ( node->collapsed == ON && cimg ) i = cimg; if ( i || node != t->displayRoot ) r_line(lx-lg, ly, lx, ly); /* line to parent */ if ( i ) { int iw = valInt(i->size->w); int ih = valInt(i->size->h); r_image(i, 0, 0, lx-lg-(iw+1)/2, ly-(ih+1)/2, iw, ih, OFF); } if ( notNil(node->sons) && node->collapsed != ON && (lastnode = getTailChain(node->sons)) ) /* I have sons */ { int fy = valInt(getBottomSideGraphical(img)); Graphical last = lastnode->image; int ty = valInt(last->area->y) + valInt(last->area->h)/2; int lx = valInt(img->area->x) + lg; Cell cell; r_line(lx, fy, lx, ty); for_cell(cell, node->sons) RedrawAreaNode(cell->value, cimg, eimg); } } static status RedrawAreaTree(Tree t, Area area) { device_draw_context ctx; Any bg, obg; if ( notNil(bg = RedrawBoxFigure((Figure)t, area)) ) obg = r_background(bg); else obg = NULL; if ( EnterRedrawAreaDevice((Device)t, area, &ctx) ) { Cell cell; /* list-style connection lines */ if ( t->direction == NAME_list && notNil(t->displayRoot) && notNil(t->displayRoot->collapsed) ) { Line proto = t->link->line; if ( proto->pen != ZERO ) { Colour old = NULL; Image cimg = getClassVariableValueObject(t, NAME_collapsedImage); Image eimg = getClassVariableValueObject(t, NAME_expandedImage); r_thickness(valInt(proto->pen)); r_dash(proto->texture); if ( notDefault(proto->colour) ) old = r_colour(proto->colour); RedrawAreaNode(t->displayRoot, cimg, eimg); if ( old ) r_colour(old); } } /* from class device (the nodes) */ for_cell(cell, t->graphicals) { Graphical gr = cell->value; if ( gr->displayed == ON && overlapArea(area, gr->area) ) RedrawArea(gr, area); } ExitRedrawAreaDevice((Device)t, area, &ctx); } RedrawAreaGraphical(t, area); /* selection and orther generic stuff*/ if ( obg ) r_background(obg); succeed; } /******************************** * EVENTS * ********************************/ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - When displayed in `list' mode, return the node with a [+] or [-] sign to indicate a expand/collapse request. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ static Node getNodeToCollapseOrExpand(Node node, int x, int y, Image cimg, Image eimg) { Graphical img = node->image; Tree t = node->tree; int lg = valInt(t->levelGap)/2; Node lastnode; Image i = NULL; if ( node->collapsed == OFF && eimg ) i = eimg; else if ( node->collapsed == ON && cimg ) i = cimg; if ( i ) { int ly = valInt(img->area->y) + valInt(img->area->h)/2; int lx = valInt(img->area->x); int iw = valInt(i->size->w); int ih = valInt(i->size->h); int iw2 = (ih+1)/2; int ih2 = (iw+1)/2; int x0 = lx-lg-iw2; int y0 = ly-ih2; if ( x >= x0 && x <= x0+iw && y >= y0 && y <= y0+ih ) return node; } if ( notNil(node->sons) && node->collapsed != ON && (lastnode = getTailChain(node->sons)) ) /* I have sons */ { Cell cell; for_cell(cell, node->sons) { Node n2; if ( (n2=getNodeToCollapseOrExpand(cell->value, x, y, cimg, eimg)) ) return n2; } } fail; } static status eventTree(Tree t, EventObj ev) { Cell cell; if ( eventDevice(t, ev) ) succeed; for_cell(cell, t->pointed) { Node n; if ( !(n = getFindNodeNode(t->displayRoot, cell->value)) ) continue; if ( qadSendv(n, NAME_event, 1, (Any*)&ev) ) succeed; } if ( t->direction == NAME_list && notNil(t->displayRoot) && isAEvent(ev, NAME_msLeftDown) ) { Image cimg = getClassVariableValueObject(t, NAME_collapsedImage); Image eimg = getClassVariableValueObject(t, NAME_expandedImage); Int x, y; Node n; get_xy_event(ev, t, OFF, &x, &y); if ( (n=getNodeToCollapseOrExpand(t->displayRoot, valInt(x), valInt(y), cimg, eimg)) ) { send(n, NAME_collapsed, n->collapsed == ON ? OFF : ON, EAV); succeed; } } fail; } static status levelGapTree(Tree t, Int i) { if (i == t->levelGap) succeed; assign(t, levelGap, i); requestComputeTree(t); succeed; } status displayTree(Tree t, Node n) { if ( n->tree != t ) { Cell cell; if ( notNil(n->tree) ) return errorPce(t, NAME_alreadyShown, n, n->tree); send(n->image, NAME_handle, t->sonHandle, EAV); send(n->image, NAME_handle, t->parentHandle, EAV); assign(n, tree, t); for_cell(cell, n->parents) relateImageNode(cell->value, n); for_cell(cell, n->sons) displayTree(t, cell->value); } succeed; } static status neighbourGapTree(Tree t, Int i) { if (i == t->neighbourGap) succeed; assign(t, neighbourGap, i); requestComputeTree(t); succeed; } static status linkGapTree(Tree t, Int i) { if ( i == t->linkGap) succeed; assign(t, linkGap, i); updateHandlesTree(t); requestComputeTree(t); succeed; } static status computeBoundingBoxFigureTree(Tree t) { if ( t->badBoundingBox == ON ) { Area a = t->area; Int ox = a->x, oy = a->y, ow = a->w, oh = a->h; int ex = leading_x_tree(t); computeBoundingBoxDevice((Device) t); if ( t->border != ZERO ) increaseArea(t->area, t->border); if ( ex ) { assign(a, x, toInt(valInt(a->x)-ex)); assign(a, w, toInt(valInt(a->w)+2*ex)); } if ( ox != a->x || oy != a->y || ow != a->w || oh != a->h ) changedAreaGraphical((Graphical)t, ox, oy, ow, oh); } succeed; } static status computeFigureTree(Tree t) { if ( notNil(t->request_compute) ) { if ( t->pen != ZERO || notNil(t->background) ) { CHANGING_GRAPHICAL(t, { computeGraphicalsDevice((Device) t); computeBoundingBoxFigureTree(t); }); } else { computeGraphicalsDevice((Device) t); computeBoundingBoxFigureTree(t); } assign(t, request_compute, NIL); } succeed; } static status computeTree(Tree t) { if ( notNil(t->request_compute) ) { if ( t->auto_layout == ON ) { Any old = t->request_compute; assign(t, request_compute, NIL); /* hack ... */ send(t, NAME_layout, EAV); assign(t, request_compute, old); } computeFigureTree(t); } succeed; } static status layoutTree(Tree t) { int ex; if ( isNil(t->displayRoot) ) succeed; ex = leading_x_tree(t); if ( send(t->displayRoot, NAME_computeLevel, ZERO, EAV) && get(t->displayRoot, NAME_computeSize, ZERO, EAV) && send(t->displayRoot, NAME_computeLayout, ZERO, toInt(ex), ZERO, EAV) ) succeed; fail; } static status autoLayoutTree(Tree t, BoolObj val) { if ( t->auto_layout != val ) { assign(t, auto_layout, val); if ( val == ON ) send(t, NAME_layout, EAV); } succeed; } static status directionTree(Tree t, Name dir) { if ( t->direction == dir ) succeed; assign(t, direction, dir); updateHandlesTree(t); requestComputeTree(t); succeed; } static int updateHandlesTree(Tree t) { if ( equalName(t->direction, NAME_horizontal) ) { send(t->parentHandle, NAME_xPosition, newObject(ClassPlus, NAME_w, t->linkGap, EAV), EAV); send(t->parentHandle, NAME_yPosition, div_h_2, EAV); send(t->sonHandle, NAME_xPosition, minInt(t->linkGap), EAV); send(t->sonHandle, NAME_yPosition, div_h_2, EAV); send(t->parentHandle, NAME_kind, NAME_parent, EAV); send(t->sonHandle, NAME_kind, NAME_son, EAV); } else if ( equalName(t->direction, NAME_vertical) ) { if ( !div_w_2) { div_w_2 = newObject(ClassDivide, NAME_w, TWO, EAV); protectObject(div_w_2); } send(t->parentHandle, NAME_xPosition, div_w_2, EAV); send(t->parentHandle, NAME_yPosition, newObject(ClassPlus, NAME_h, t->linkGap, EAV), EAV); send(t->sonHandle, NAME_xPosition, div_w_2, EAV); send(t->sonHandle, NAME_yPosition, minInt(t->linkGap), EAV); send(t->parentHandle, NAME_kind, NAME_parent, EAV); send(t->sonHandle, NAME_kind, NAME_son, EAV); } else { send(t->parentHandle, NAME_kind, NAME_none, EAV); send(t->sonHandle, NAME_kind, NAME_none, EAV); } succeed; } status unzoomTree(Tree t) { return zoomTree(t, t->root); } status zoomTree(Tree t, Node n) { if ( n->tree != t ) fail; if ( t->displayRoot == n ) succeed; assign(t, displayRoot, n); updateDisplayedTree(t); requestComputeTree(t); succeed; } static status rootHandlerTree(Tree t, Handler h) { return prependChain(t->rootHandlers, h); } static status leafHandlerTree(Tree t, Handler h) { return prependChain(t->leafHandlers, h); } static status nodeHandlerTree(Tree t, Handler h) { return prependChain(t->nodeHandlers, h); } static status collapsedHandlerTree(Tree t, Handler h) { return prependChain(t->collapsedHandlers, h); } static status forAllTree(Tree t, Code msg) { if ( notNil(t->root) ) return forAllNode(t->root, msg); succeed; } static status forSomeTree(Tree t, Code msg) { if ( notNil(t->root) ) return forSomeNode(t->root, msg); succeed; } /******************************** * VISUAL * ********************************/ static void add_nodes_tree(Node n, Chain ch) { if ( notNil(n) ) { Cell cell; appendChain(ch, n); for_cell(cell, n->sons) add_nodes_tree(cell->value, ch); } } static Chain getContainsTree(Tree t) { Chain ch = answerObject(ClassChain, EAV); add_nodes_tree(t->root, ch); answer(ch); } /******************************* * CLASS DECLARATION * *******************************/ /* Type declarations */ static char *T_root[] = { "root=node*", "relink=[bool]" }; /* Instance Variables */ static vardecl var_tree[] = { IV(NAME_root, "node*", IV_GET, NAME_nodes, "Root-node of the tree"), IV(NAME_displayRoot, "node*", IV_GET, NAME_scroll, "Root of visible subtree"), SV(NAME_autoLayout, "bool", IV_GET|IV_STORE, autoLayoutTree, NAME_layout, "Enforce automatic layout?"), SV(NAME_levelGap, "int", IV_GET|IV_STORE, levelGapTree, NAME_layout, "Distance between levels"), SV(NAME_neighbourGap, "int", IV_GET|IV_STORE, neighbourGapTree, NAME_layout, "Distance between neighbours"), SV(NAME_linkGap, "int", IV_GET|IV_STORE, linkGapTree, NAME_layout, "Distance between line and image"), SV(NAME_direction, "{horizontal,vertical,list}", IV_GET|IV_STORE, directionTree, NAME_layout, "Layout of the tree"), IV(NAME_link, "link", IV_GET, NAME_layout, "Generic connection between graphicals"), IV(NAME_parentHandle, "handle", IV_GET, NAME_relation, "Connection point to parent"), IV(NAME_sonHandle, "handle", IV_GET, NAME_relation, "Connection point to sons"), IV(NAME_rootHandlers, "chain", IV_GET, NAME_event, "Event handlers for root"), IV(NAME_leafHandlers, "chain", IV_GET, NAME_event, "Event handlers for leafs"), IV(NAME_nodeHandlers, "chain", IV_GET, NAME_event, "Event handlers for any type of node"), IV(NAME_collapsedHandlers, "chain", IV_GET, NAME_event, "Event handlers for collapsed nodes") }; /* Send Methods */ static senddecl send_tree[] = { SM(NAME_compute, 0, NULL, computeTree, DEFAULT, "Recompute the tree layout"), SM(NAME_event, 1, "event", eventTree, DEFAULT, "Process an event"), SM(NAME_initialise, 1, "root=[node]*", initialiseTree, DEFAULT, "Create a tree from a root node"), SM(NAME_unlink, 0, NULL, unlinkTree, DEFAULT, "Removes all the nodes"), SM(NAME_collapsedHandler, 1, "recogniser", collapsedHandlerTree, NAME_event, "Add recogniser for collapsed node"), SM(NAME_leafHandler, 1, "recogniser", leafHandlerTree, NAME_event, "Add recogniser for leaf node"), SM(NAME_nodeHandler, 1, "recogniser", nodeHandlerTree, NAME_event, "Add recogniser for any node node"), SM(NAME_rootHandler, 1, "recogniser", rootHandlerTree, NAME_event, "Add recogniser for root node"), SM(NAME_forAll, 1, "code", forAllTree, NAME_iterate, "Run code on all nodes (demand acceptance)"), SM(NAME_forSome, 1, "code", forSomeTree, NAME_iterate, "Run code on all nodes"), SM(NAME_root, 2, T_root, rootTree, NAME_nodes, "Set root node"), SM(NAME_unzoom, 0, NULL, unzoomTree, NAME_scroll, "Make the visible root the real root"), SM(NAME_zoom, 1, "node", zoomTree, NAME_scroll, "Zoom to a particular node"), SM(NAME_layout, 0, NULL, layoutTree, NAME_update, "Recompute layout"), SM(NAME_DrawPostScript, 1, "{head,body}", drawPostScriptTree, NAME_postscript, "Create PostScript") }; /* Get Methods */ static getdecl get_tree[] = { GM(NAME_contains, 0, "chain", NULL, getContainsTree, DEFAULT, "New chain with nodes I manage") }; /* Resources */ static classvardecl rc_tree[] = { RC(NAME_direction, "name", "horizontal", "Default style {horizontal,vertical,list}"), RC(NAME_levelGap, "int", "50", "Gap between levels"), RC(NAME_linkGap, "int", "2", "Gap between link-line and image"), RC(NAME_neighbourGap, "int", "0", "Gap between neighbours"), RC(NAME_expandedImage, "image*", "@tree_expanded_image", "Image left of node if it is expanded <-direction: list"), RC(NAME_collapsedImage, "image*", "@tree_collapsed_image", "Image left of node if it is collapsed <-direction: list") }; /* Class Declaration */ static Name tree_termnames[] = { NAME_root }; ClassDecl(tree_decls, var_tree, send_tree, get_tree, rc_tree, 1, tree_termnames, "$Rev$"); status makeClassTree(Class class) { declareClass(class, &tree_decls); delegateClass(class, NAME_root); setRedrawFunctionClass(class, RedrawAreaTree); succeed; }