Mercurial > hg > xemacs-beta
diff pkg-src/tree-x/draw.c @ 163:0132846995bd r20-3b8
Import from CVS: tag r20-3b8
author | cvs |
---|---|
date | Mon, 13 Aug 2007 09:43:35 +0200 |
parents | |
children | 85ec50267440 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pkg-src/tree-x/draw.c Mon Aug 13 09:43:35 2007 +0200 @@ -0,0 +1,1280 @@ +/* ---------------------------------------------------------------------------- + * File : draw.c + * Purpose : drawing-specific routines for dynamic tree program + * ---------------------------------------------------------------------------- + */ + +#include <X11/Intrinsic.h> +#include <X11/StringDefs.h> + +#include "defs.h" +#include "tree.h" +#include "dbl.h" +#include "intf.h" + +/* ------------------------------------------------------------------------- */ +/* Global Variables */ +/* ------------------------------------------------------------------------- */ + +Tree *TheTree; + + +/* ------------------------------------------------------------------------- */ +/* Local Variables */ +/* ------------------------------------------------------------------------- */ + +static char AnimationMode = FALSE; +static char strbuf[BUFSIZ]; +static int AnimationStep = ANIMATION_STEP; + +/* ------------------------------------------------------------------------- */ +/* Forward Function Declarations */ +/* ------------------------------------------------------------------------- */ + +void DrawNode(); +void DrawTreeContour(); + + +/* ------------------------------------------------------------------------- */ +/* Functions */ +/* ------------------------------------------------------------------------- */ + + +/* ---------------------------------------------------------------------------- + * + * BeginFrame() provides an abstraction for double buffering. It should + * be called prior to creating a new frame of animation. + * + * ---------------------------------------------------------------------------- + */ + +void +BeginFrame() +{ + DBLbegin_frame(TreeDrawingAreaDB); +} + + +/* ---------------------------------------------------------------------------- + * + * EndFrame() provides an abstraction for double buffering. It should + * be called after creating a new frame of animation. + * + * ---------------------------------------------------------------------------- + */ + +void +EndFrame() +{ + DBLend_frame(TreeDrawingAreaDB, 0); +} + + +/* ---------------------------------------------------------------------------- + * + * GetDrawingSize() gets the size of the drawing area, and returns the + * dimensions in the arguments. + * + * ---------------------------------------------------------------------------- + */ + +void +GetDrawingSize(width, height) + int *width, *height; +{ + Dimension w, h; + + XtVaGetValues(TreeDrawingArea, + XtNwidth, &w, + XtNheight, &h, + NULL); + + *width = (int) w; + *height = (int) h; +} + + +/* ---------------------------------------------------------------------------- + * + * SetDrawingSize() sets the size of the drawing area to the given + * dimensions. + * + * ---------------------------------------------------------------------------- + */ + +void +SetDrawingSize(width, height) + int width, height; +{ + XtVaSetValues(TreeDrawingArea, + XtNwidth, (Dimension) width, + XtNheight, (Dimension) height, + NULL); +} + + +/* ---------------------------------------------------------------------------- + * + * SetDrawingTree() is used to specify what tree is to be drawn in the + * drawing area. + * + * ---------------------------------------------------------------------------- + */ + +void +SetDrawingTree(tree) + Tree *tree; +{ + TheTree = tree; +} + + +/* ---------------------------------------------------------------------------- + * + * SetNodeLabel() sets the label text of the specified node and computes + * the bounding rectangle so that the layout can be determined. This + * function is called when new nodes are created. If TreeAlignNodes is + * True, the string is truncated so that the node's width is no longer + * than TreeParentDistance. + * + * ---------------------------------------------------------------------------- + */ + +void +SetNodeLabel(node, label) + Tree *node; + char *label; +{ + int len; + int dummy; + XCharStruct rtrn; + + len = strlen(label); + while (len > 1) { + XTextExtents(TreeLabelFont, label, len, &dummy, &dummy, &dummy, &rtrn); + node->width = rtrn.lbearing + rtrn.rbearing + (LABEL_MAT_WIDTH * 2) + 1; + node->height = rtrn.ascent + rtrn.descent + (LABEL_MAT_HEIGHT * 2) + 1; + if (TreeAlignNodes) { + if (node->width <= (2 * TreeParentDistance)) + break; + else + len--; + } + else + break; + } + + node->label.text = label; + node->label.len = len; + node->label.xoffset = LABEL_MAT_WIDTH + 1, + node->label.yoffset = rtrn.ascent + LABEL_MAT_HEIGHT + 1; +} + + +/* ---------------------------------------------------------------------------- + * + * SetDrawColor() sets the drawing color of the TreeDrawingArea. + * + * ---------------------------------------------------------------------------- + */ + +void +SetDrawColor(color) + int color; +{ + XSetForeground(TreeDrawingAreaDB->display, TreeDrawingAreaDB->gc, + TreeDrawingAreaDB->colors[color]); +} + +/* ---------------------------------------------------------------------------- + * + * SetLineWidth() sets the line width of lines drawn in the TreeDrawingArea. + * + * ---------------------------------------------------------------------------- + */ + +void +SetLineWidth(width) + unsigned int width; +{ + XSetLineAttributes(TreeDrawingAreaDB->display, TreeDrawingAreaDB->gc, + width, LineSolid, CapButt, JoinRound); +} + + +/* ---------------------------------------------------------------------------- + * + * SetContours() sets the visibility of three possible contour modes: + * the outside contour, all subtree contours, or selected contours. + * + * ---------------------------------------------------------------------------- + */ + +void +SetContours(option) + ContourOption option; +{ + if (option == NoContours) { + switch (TreeShowContourOption) { + case OutsideContour: + DrawTreeContour(TheTree, New, BACKGROUND_COLOR, FALSE, FALSE, FALSE); + break; + case AllContours: + DrawTreeContour(TheTree, New, BACKGROUND_COLOR, FALSE, FALSE, TRUE); + break; + case SelectedContours: + DrawTreeContour(TheTree, New, BACKGROUND_COLOR, FALSE, TRUE, TRUE); + break; + default: + ; + } + DrawTreeContour(TheTree, New, BACKGROUND_COLOR, FALSE, FALSE, TRUE); + } + else if (option == OutsideContour) { + switch (TreeShowContourOption) { + case AllContours: + DrawTreeContour(TheTree, New, BACKGROUND_COLOR, FALSE, FALSE, TRUE); + break; + case SelectedContours: + DrawTreeContour(TheTree, New, BACKGROUND_COLOR, FALSE, TRUE, TRUE); + break; + default: + ; + } + DrawTreeContour(TheTree, New, CONTOUR_COLOR, FALSE, FALSE, FALSE); + } else if (option == AllContours) { + DrawTreeContour(TheTree, New, CONTOUR_COLOR, FALSE, FALSE, TRUE); + } else if (option == SelectedContours) { + switch (TreeShowContourOption) { + case AllContours: + DrawTreeContour(TheTree, New, BACKGROUND_COLOR, FALSE, FALSE, TRUE); + break; + case OutsideContour: + DrawTreeContour(TheTree, New, BACKGROUND_COLOR, FALSE, FALSE, FALSE); + break; + default: + DrawTreeContour(TheTree, New, BACKGROUND_COLOR, FALSE, FALSE, TRUE); + } + DrawTreeContour(TheTree, New, CONTOUR_COLOR, FALSE, TRUE, TRUE); + } + TreeShowContourOption = option; +} + + +/* ---------------------------------------------------------------------------- + * + * HiliteNode() is called by Unzip() to change the color of a node. + * + * ---------------------------------------------------------------------------- + */ + +void +HiliteNode(tree, pos_mode) + Tree *tree; +{ + SetDrawColor(HIGHLIGHT_COLOR); + DrawNode(tree, pos_mode); + SetDrawColor(TREE_COLOR); +} + + +/* ---------------------------------------------------------------------------- + * + * DrawNode() takes a node and draws the node in the specified widget + * at its (x,y) coordinate. (x, y) indicates the upper-left corner where + * the node is drawn. Also, a line is drawn from the center of the left + * edge to the center of the parent's right edge. 'draw_mode' specifies + * the drawing mode (whether or not the node is erased, rather than drawn). + * 'pos_mode' determines whether or not to use the old position of the node. + * This flag is used in animating the movement of a node from its old + * position to its new position. + * + * ---------------------------------------------------------------------------- + */ + +void +DrawNode(node, pos_mode) + Tree *node; + PosMode pos_mode; +{ + Widget w; + GC gc; + + w = TreeDrawingArea; + gc = TreeDrawingAreaDB->gc; + + if (pos_mode == Old) { + XDrawString(XtDisplay(w), XtWindow(w), gc, + node->old_pos.x + node->label.xoffset, + node->old_pos.y + node->label.yoffset, + node->label.text, node->label.len); + XDrawRectangle(XtDisplay(w), XtWindow(w), gc, + node->old_pos.x, node->old_pos.y, + node->width, node->height); + if (node->parent) + XDrawLine(XtDisplay(w), XtWindow(w), gc, + node->old_pos.x - 1, + node->old_pos.y + (node->height / 2), + node->parent->old_pos.x + node->parent->width + 1, + node->parent->old_pos.y + (node->parent->height / 2)); + if (node->elision) { + XSetFillStyle(TreeDrawingAreaDB->display, TreeDrawingAreaDB->gc, + FillTiled); + XFillRectangle(XtDisplay(w), XtWindow(w), gc, + node->old_pos.x + node->width - ELISION_WIDTH, + node->old_pos.y + 1, ELISION_WIDTH, node->height - 1); + XSetFillStyle(TreeDrawingAreaDB->display, TreeDrawingAreaDB->gc, + FillSolid); + } + } else { + XDrawString(XtDisplay(w), XtWindow(w), gc, + node->pos.x + node->label.xoffset, + node->pos.y + node->label.yoffset, + node->label.text, node->label.len); + + XDrawRectangle(XtDisplay(w), XtWindow(w), gc, + node->pos.x, node->pos.y, + node->width, node->height); + if (node->parent) + XDrawLine(XtDisplay(w), XtWindow(w), gc, + node->pos.x - 1, + node->pos.y + (node->height / 2), + node->parent->pos.x + node->parent->width + 1, + node->parent->pos.y + (node->parent->height / 2)); + if (node->elision) { + XSetFillStyle(TreeDrawingAreaDB->display, TreeDrawingAreaDB->gc, + FillTiled); + XFillRectangle(XtDisplay(w), XtWindow(w), gc, + node->pos.x + node->width - ELISION_WIDTH, + node->pos.y + 1, ELISION_WIDTH, node->height - 1); + XSetFillStyle(TreeDrawingAreaDB->display, TreeDrawingAreaDB->gc, + FillSolid); + } + } +} + + +/* ---------------------------------------------------------------------------- + * + * DrawTreeContour() draws the contour of the specified subtree. Bridges + * are not traversed, so the actual subtree contour is drawn, as opposed + * to the merged contour. 'color' specifies the drawing color. If 'detach' + * is True, the lines attaching the subtree contour to the node are not + * drawn. If 'select' is true, then only subtrees that are flagged as + * selected are shown. If 'recursive' is True, the entire tree is traversed. + * + * ---------------------------------------------------------------------------- + */ + +void +DrawTreeContour(tree, pos_mode, color, detach, select, recursive) + Tree *tree; + PosMode pos_mode; + int color; + int detach; + int select; + int recursive; +{ + Widget w = TreeDrawingArea; + Polyline *contour, *tail; + Tree *child; + int x, y, i; + + if (tree == NULL) + return; + + if ((select && tree->show_contour) || !select) { + + SetDrawColor(color); + SetLineWidth(TreeContourWidth); + + /* draw upper contour */ + contour = tree->contour.upper.head; + tail = tree->contour.upper.tail; + if (pos_mode == Old) { + x = tree->old_pos.x - tree->border; + y = tree->old_pos.y - tree->border; + } + else { + x = tree->pos.x - tree->border; + y = tree->pos.y - tree->border; + } + + if (detach) { /* skip over attaching lines */ + for (i = 0 ; i < 2 ; i++) { + x += contour->dx; + y += contour->dy; + contour = contour->link; + } + } + + while (contour) { + XDrawLine(XtDisplay(w), XtWindow(w), TreeDrawingAreaDB->gc, + x, y, x + contour->dx, y + contour->dy); + x += contour->dx; + y += contour->dy; + if (contour == tail) /* make sure you don't follow bridges */ + contour = NULL; + else + contour = contour->link; + } + + /* draw lower contour */ + contour = tree->contour.lower.head; + tail = tree->contour.lower.tail; + if (pos_mode == Old) { + x = tree->old_pos.x - tree->border; + y = tree->old_pos.y + tree->border + tree->height; + } else { + x = tree->pos.x - tree->border; + y = tree->pos.y + tree->border + tree->height; + } + + if (detach) { /* skip over attaching lines */ + for (i = 0 ; i < 2 ; i++) { + x += contour->dx; + y += contour->dy; + contour = contour->link; + } + } + + while (contour) { + XDrawLine(XtDisplay(w), XtWindow(w), TreeDrawingAreaDB->gc, + x, y, x + contour->dx, y + contour->dy); + x += contour->dx; + y += contour->dy; + if (contour == tail) /* make sure you don't follow bridges */ + contour = NULL; + else + contour = contour->link; + } + } + + if (recursive) { + FOREACH_CHILD(child, tree) + if (!child->elision) + DrawTreeContour(child, pos_mode, color, + detach, select, recursive); + } + + SetDrawColor(TREE_COLOR); + SetLineWidth(0); +} + + +/* ---------------------------------------------------------------------------- + * + * DrawTree() traverses the given tree, drawing the node and connecting + * segments. The tree contours are also drawn at each step, if enabled. + * 'draw_mode' specifies the drawing mode in which the tree is drawn. + * 'pos_mode' determines whether or not to use the old position of the node. + * This flag is used in animating the movement of a node from its old + * position to its new position. DrawNode() is called to draw an individual + * node. + * + * ---------------------------------------------------------------------------- + */ + +void +DrawTree(tree, pos_mode) + Tree *tree; + PosMode pos_mode; +{ + if (tree == NULL) + return; + + DrawNode(tree, pos_mode); + + /* do stuff that animates Unzip() */ + if (tree->split) { + if (!AnimationMode || + (tree->pos.x == tree->old_pos.x && + tree->pos.y == tree->old_pos.y)) + DrawTreeContour(tree, pos_mode, SPLIT_COLOR, FALSE, FALSE, FALSE); + else + DrawTreeContour(tree, pos_mode, ACTION_COLOR, FALSE, FALSE, FALSE); + } + if (tree->on_path) + HiliteNode(tree, pos_mode); + + if (tree->child && !tree->elision) + DrawTree(tree->child, pos_mode); + if (tree->sibling) + DrawTree(tree->sibling, pos_mode); +} + + +/* ---------------------------------------------------------------------------- + * + * ShiftTree() adjusts the positions of each node so that it moves from + * the "old" position towards the "new position". This is used by + * AnimateTree(). 'done' is set to FALSE if the tree is not in its + * final position; it is used to determine when to stop animating the tree. + * + * ---------------------------------------------------------------------------- + */ + +void +ShiftTree(tree, done) + Tree *tree; + int *done; +{ + Tree *child; + + if (tree->old_pos.x != tree->pos.x || + tree->old_pos.y != tree->pos.y) + { + tree->old_pos.x = tree->pos.x; + tree->old_pos.y = tree->pos.y; + } + + FOREACH_CHILD(child, tree) + ShiftTree(child, done); +} + + +/* ---------------------------------------------------------------------------- + * + * AnimateTree() draws the given tree in a series of steps to give the + * effect of animation from the "old" layout to the "new" layout of the + * tree. + * + * The algorithm used here is not efficient; the entire tree is drawn + * on each iteration of the animation sequence; it would be more efficient + * to only redraw what is necessary. However, the method used here takes + * advantage of existing code without modification. + * + * ---------------------------------------------------------------------------- + */ + +void +AnimateTree(tree) + Tree *tree; +{ + int done = FALSE; + + AnimationMode = FALSE; + /* highlight which nodes have to move */ + BeginFrame(); + DrawTree(tree, Old); + EndFrame(); + Pause(); + if (PauseAfterStep) + AnimationStep = ANIMATION_STEP_STEP; + while (!done) { + done = TRUE; + ShiftTree(tree, &done); + BeginFrame(); + DrawTree(tree, Old); + EndFrame(); + if (PauseAfterStep) + Pause(); + } + if (PauseAfterStep) + AnimationStep = ANIMATION_STEP; + AnimationMode = FALSE; +} + + +/* ---------------------------------------------------------------------------- + * + * AnimateZip() generates a sequence of frames that animates the Zip() step. + * It is similar in logical structure to Zip(). + * + * ---------------------------------------------------------------------------- + */ + +void +AnimateZip(tree) + Tree *tree; +{ + Tree *child; + + /* show results of Join() step */ + if (tree->child) { + BeginFrame(); + FOREACH_CHILD(child, tree) + child->split = FALSE; + DrawTree(TheTree, New); + DrawTreeContour(tree, New, CONTOUR_COLOR, TRUE, FALSE, FALSE); + EndFrame(); + + StatusMsg("Zip: merge and join contours", FALSE); + Pause(); + + /* show results of AttachParent() step */ + BeginFrame(); + DrawTree(TheTree, New); + DrawTreeContour(tree, New, CONTOUR_COLOR, FALSE, FALSE, FALSE); + EndFrame(); + + StatusMsg("Zip: attach parents", FALSE); + Pause(); + } + + tree->on_path = FALSE; + + if (tree->parent) + AnimateZip(tree->parent); + else { + tree->on_path = FALSE; + BeginFrame(); + DrawTree(TheTree, New); + DrawTreeContour(TheTree, New, CONTOUR_COLOR, FALSE, FALSE, FALSE); + EndFrame(); + StatusMsg("Zip: reassemble entire contour", FALSE); + Pause(); + } +} + + +/* ---------------------------------------------------------------------------- + * + * CountNodes() returns the number of nodes in the specified tree. + * Nodes below a node that has been collapsed are ignored. + * + * ---------------------------------------------------------------------------- + */ + +int +CountNodes(tree) + Tree *tree; +{ + int num_nodes = 1; /* count root of subtree */ + Tree *child; + + if (!tree->elision) { + FOREACH_CHILD(child, tree) + num_nodes += CountNodes(child); + } + return (num_nodes); +} + + +/* ---------------------------------------------------------------------------- + * + * CollectNodeRectangles() is a recursive function used by + * GetSubTreeRectangles() to collect the rectangles of descendant nodes + * into the pre-allocated storage passed to this function. + * + * ---------------------------------------------------------------------------- + */ + +void +CollectNodeRectangles(node, rectangles, fill) + Tree *node; + XRectangle **rectangles; + int fill; +{ + Tree *child; + + (*rectangles)->x = node->pos.x; + (*rectangles)->y = node->pos.y; + if (fill) { + (*rectangles)->width = node->width + 1; + (*rectangles)->height = node->height + 1; + } else { + (*rectangles)->width = node->width; + (*rectangles)->height = node->height; + } + (*rectangles)++; + + if (!node->elision) + FOREACH_CHILD(child, node) + CollectNodeRectangles(child, rectangles, fill); +} + + +/* ---------------------------------------------------------------------------- + * + * GetSubTreeRectangles() builds an array of XRectangles that contain + * all the node rectangles in the tree, except the root node itself. + * The array is returned in 'rectangles' and the number of rectangles + * is returned in 'nrectangles.' Storage for the rectangles is allocated + * in this function. This function is used by PickAction to determine + * what rectangles need to be dissolved away. 'fill', if True, specifies + * that the rectangles should be 1 pixel larger in each dimension to + * compensate for FillRectangle behavior. + * + * ---------------------------------------------------------------------------- + */ + +void +GetSubTreeRectangles(tree, rectangles, nrectangles, fill) + Tree *tree; + XRectangle **rectangles; + int *nrectangles, fill; +{ + Tree *child; + XRectangle *crect; /* current rectangle */ + + *nrectangles = CountNodes(tree) - 1; /* don't count root node */ + *rectangles = (XRectangle *) malloc(sizeof(XRectangle) * *nrectangles); + ASSERT(*rectangles, "could not allocate memory for rectangles"); + + crect = *rectangles; + if (!tree->elision) + FOREACH_CHILD(child, tree) + CollectNodeRectangles(child, &crect, fill); +} + + +/* ---------------------------------------------------------------------------- + * + * CollectNodeSegments() is a recursive function used by GetSubTreeSegments() + * to collect the line segments connecting nodes into the pre-allocated + * storage passed to this function. + * + * ---------------------------------------------------------------------------- + */ + +void +CollectNodeSegments(node, segments) + Tree *node; + XSegment **segments; +{ + Tree *child; + + (*segments)->x1 = node->pos.x - 1; + (*segments)->y1 = node->pos.y + (node->height / 2), + (*segments)->x2 = node->parent->pos.x + node->parent->width + 1; + (*segments)->y2 = node->parent->pos.y + (node->parent->height / 2); + (*segments)++; + + if (!node->elision) + FOREACH_CHILD(child, node) + CollectNodeSegments(child, segments); +} + + +/* ---------------------------------------------------------------------------- + * + * GetSubTreeSegments() builds an array of XSegments that contain + * all the line segments connecting the nodes in the tree. The array is + * returned in 'segments' and the number of segments is returned in + * 'nsegments.' Storage for the segments is allocated in this function. + * This function is used by PickAction to determine what line segments + * rectangles need to be dissolved away. + * + * ---------------------------------------------------------------------------- + */ + +void +GetSubTreeSegments(tree, segments, nsegments) + Tree *tree; + XSegment **segments; + int *nsegments; +{ + Tree *child; + XSegment *cseg; /* current segment */ + + *nsegments = CountNodes(tree) - 1; + *segments = (XSegment *) malloc(sizeof(XSegment) * *nsegments); + ASSERT(*segments, "could not allocate memory for segments"); + + cseg = *segments; + if (!tree->elision) + FOREACH_CHILD(child, tree) + CollectNodeSegments(child, &cseg); +} + + +/* ---------------------------------------------------------------------------- + * + * ComputeSubTreeExtent() computes the extent of a subtree. This is + * easily computed based on the tree's contour, as in ComputeTreeSize(). + * This extent is stored in the node, and used by SearchTree for + * pick-correlation. + * + * This function assumes that the given tree has at least one child; do not + * pass a leaf node to this function. + * + * ---------------------------------------------------------------------------- + */ + +void +ComputeSubTreeExtent(tree) + Tree *tree; +{ + int width, height; + int x_offset, y_offset; + + ComputeTreeSize(tree, &width, &height, &x_offset, &y_offset); + tree->subextent.pos.x = tree->child->pos.x - tree->child->border; + tree->subextent.pos.y = tree->pos.y - y_offset; + tree->subextent.width = width - (tree->child->pos.x - tree->pos.x) - 1; + tree->subextent.height = height - 1; +} + + +/* ---------------------------------------------------------------------------- + * + * SearchTree() determines if a node's rectangular region encloses the + * specified point in (x,y). Rather than using a brute-force search + * through all node rectangles of a given tree, the subtree extents + * are used in a recursive fashion to drive the search as long as the + * given point is enclosed in an extent. In the worst case, the search + * time would be on the order of a brute-force search, but with complex + * trees, this method reduces the number of visits. + * + * The extent of a subtree is computed by ComputeSubTreeExtent() and is + * stored in each node of the tree. + * + * ---------------------------------------------------------------------------- + */ + +int +SearchTree(tree, x, y, node) + Tree *tree, **node; + int x, y; +{ + Tree *child; + + if (tree == NULL) + return (FALSE); + + if (PT_IN_RECT(x, y, tree->pos.x, tree->pos.y, + tree->pos.x + tree->width, + tree->pos.y + tree->height)) { + *node = tree; + return (TRUE); + } + if (tree->child && (PT_IN_EXTENT(x, y, tree->subextent))) + FOREACH_CHILD(child, tree) { + if (SearchTree(child, x, y, node)) + return (TRUE); + } + return (FALSE); +} + + +/* ---------------------------------------------------------------------------- + * + * ExposeHandler() handles expose events in the TreeDrawingArea. This + * function is not intelligent; it just redraws the entire contents. + * + * ---------------------------------------------------------------------------- + */ + +void +ExposeHandler(w, client_data, event) + Widget w; + caddr_t client_data; + XExposeEvent *event; +{ + + if (event->count == 0) { + BeginFrame(); + SetContours(TreeShowContourOption); + DrawTree(TheTree, New); + EndFrame(); + } +} + + +/* ---------------------------------------------------------------------------- + * + * ExpandCollapseNode is called to expand or collapse a node in the tree. + * + * ---------------------------------------------------------------------------- + */ + +void +ExpandCollapseNode(node) + Tree *node; +{ + int width, height; + int old_width, old_height; + int x_offset, y_offset; + XRectangle *rectangles; + XSegment *segments; + int nrectangles, nsegments; + int expand = FALSE; + Widget w = TreeDrawingArea; + + StatusMsg("", TRUE); + + /* hilite node so that we know where we are */ + /* DrawTree will hilite it as a side effect */ + if (TreeShowSteps) + node->on_path = TRUE; + + /* erase the contour before changing in the tree */ + if ((TreeShowContourOption != NoContours) || TreeShowSteps) { + BeginFrame(); + DrawTree(TheTree, New); + EndFrame(); + } + + sprintf(strbuf, "Node `%s' selected for %s", node->label.text, + node->elision ? "expansion" : "collapse"); + StatusMsg(strbuf, FALSE); + Pause(); + + if (node->parent) + Unzip(node->parent); + else { + StatusMsg("Show entire contour", FALSE); + if (TreeShowSteps) { + BeginFrame(); + DrawTreeContour(TheTree, New, CONTOUR_COLOR, FALSE, FALSE, FALSE); + DrawTree(TheTree, New); + EndFrame(); + Pause(); + } + } + + /* are we collapsing a subtree? */ + if (!node->elision) { + StatusMsg("Collapse subtree", FALSE); + GetSubTreeRectangles(node, &rectangles, &nrectangles, TRUE); + GetSubTreeSegments(node, &segments, &nsegments); + DissolveTree(XtDisplay(w), XtWindow(w), + rectangles, nrectangles, + segments, nsegments, TRUE); + free(rectangles); + free(segments); + Pause(); + + StatusMsg("Replace subtree contour with leaf contour", FALSE); + node->elision = TRUE; + if (TreeShowSteps) + node->split = TRUE; /* turned off in AnimateZip */ + node->old_contour = node->contour; + node->width += ELISION_WIDTH; + LayoutLeaf(node); + BeginFrame(); + SetContours(TreeShowContourOption); + DrawTree(TheTree, New); + EndFrame(); + Pause(); + } else { + StatusMsg("Replace leaf contour with old subtree contour", FALSE); + if (TreeShowSteps) + node->split = TRUE; /* turned off in AnimateZip */ + RuboutLeaf(node); + node->contour = node->old_contour; + expand = TRUE; + } + + if (node->parent) + Zip(node->parent); + + ComputeTreeSize(TheTree, &width, &height, &x_offset, &y_offset); + PetrifyTree(TheTree, x_offset + MAT_SIZE, y_offset + MAT_SIZE); + GetDrawingSize(&old_width, &old_height); + + if (expand) { + SetDrawingSize(width + (2 * MAT_SIZE), height + (2 * MAT_SIZE)); + BeginFrame(); + DrawTree(TheTree, Old); + EndFrame(); + Pause(); + StatusMsg("Move tree to new configuration", FALSE); + AnimateTree(TheTree); + } else { + /* we are shrinking or staying the same */ + StatusMsg("Move tree to new configuration", FALSE); + AnimateTree(TheTree); + SetDrawingSize(width + (2 * MAT_SIZE), height + (2 * MAT_SIZE)); + } + + if (expand) { + StatusMsg("Expand subtree", FALSE); + node->elision = FALSE; + + /* erase elision marker */ + XSetFunction(TreeDrawingAreaDB->display, TreeDrawingAreaDB->gc, + GXclear); + XFillRectangle(XtDisplay(w), XtWindow(w), TreeDrawingAreaDB->gc, + node->pos.x + node->width - ELISION_WIDTH + 1, + node->pos.y, ELISION_WIDTH, node->height + 1); + XSetFunction(TreeDrawingAreaDB->display, TreeDrawingAreaDB->gc, + GXcopy); + node->width -= ELISION_WIDTH; + + GetSubTreeRectangles(node, &rectangles, &nrectangles, FALSE); + GetSubTreeSegments(node, &segments, &nsegments); + /* dissolve the tree back in */ + DissolveTree(XtDisplay(w), XtWindow(w), + rectangles, nrectangles, + segments, nsegments, FALSE); + free(rectangles); + free(segments); + + /* draw text of nodes */ + BeginFrame(); + SetContours(TreeShowContourOption); + DrawTree(TheTree, New); + EndFrame(); + Pause(); + } + + if (TreeShowSteps) { + node->on_path = FALSE; + if (node->parent) + AnimateZip(node->parent); + else + node->split = FALSE; + } + + /* BUG: the display isn't properly updated here! */ + /* There should probably be some code here that + clears the tree below the node currently being + collapsed or expanded. Hack added 20.03.95 (torgeir@ii.uib.no). + I'll try to fix this later. */ + + XClearArea(TreeDisplay, XtWindow(TreeDrawingArea), 0, 0, 0, 0, FALSE); + + BeginFrame(); + SetContours(TreeShowContourOption); + DrawTree(TheTree, New); + EndFrame(); + + StatusMsg("Ready", TRUE); +} + +/* ---------------------------------------------------------------------------- + * + * InsertNode() handles the task of inserting a new node in the tree, + * at the given position with respect to 'base_node'. When 'node_pos' is + * either Before or After, it is assumed that 'base_node' has a parent. + * + * ---------------------------------------------------------------------------- + */ + +void +InsertNode(base_node, node_pos, new_node_text) + Tree *base_node; + NodePosition node_pos; + char *new_node_text; +{ + Tree *new_node; + Tree *parent; + Tree *sibling = NULL; + Tree *child; + + int width, height; + int x_offset, y_offset; + + StatusMsg("", TRUE); + + new_node = MakeNode(); /* should check for memory failure */ + SetNodeLabel(new_node, new_node_text); + LayoutLeaf(new_node); + + /* figure out parent & sibling */ + if (node_pos == Child) { + parent = base_node; + /* find last child, if one exists */ + FOREACH_CHILD(child, parent) + sibling = child; + } else if (node_pos == After) { + parent = base_node->parent; + sibling = base_node; + } else if (node_pos == Before) { + parent = base_node->parent; + FOREACH_CHILD(child, parent) + if (child->sibling == base_node) { + sibling = child; + break; + } + } + + if (TreeShowSteps) + parent->on_path = TRUE; + + if ((TreeShowContourOption != NoContours) || + TreeShowSteps) { + BeginFrame(); + DrawTree(TheTree, New); + EndFrame(); + } + + sprintf(strbuf, "Inserting `%s' as child of node `%s'", + new_node_text, parent->label.text); + StatusMsg(strbuf, FALSE); + Pause(); + + /* erase the contour before changing in the tree */ + + Insert(parent, new_node, sibling); + + ComputeTreeSize(TheTree, &width, &height, &x_offset, &y_offset); + PetrifyTree(TheTree, x_offset + MAT_SIZE, y_offset + MAT_SIZE); + + if (sibling) + new_node->old_pos = sibling->old_pos; + else if (new_node->sibling) + new_node->old_pos = new_node->sibling->old_pos; + else { + new_node->old_pos.x = new_node->pos.x; + new_node->old_pos.y = parent->old_pos.y; + } + + if (TreeShowSteps) + new_node->split = TRUE; + + SetDrawingSize(width + (2 * MAT_SIZE), height + (2 * MAT_SIZE)); + BeginFrame(); + DrawTree(TheTree, Old); + EndFrame(); + StatusMsg("Insert: add new node and contour", FALSE); + Pause(); + + StatusMsg("Move tree to new configuration", FALSE); + AnimateTree(TheTree); + + if (TreeShowSteps) { + if (parent) + AnimateZip(parent); + } + + BeginFrame(); + SetContours(TreeShowContourOption); + DrawTree(TheTree, New); + EndFrame(); + + StatusMsg("Ready", TRUE); +} + +/* ---------------------------------------------------------------------------- + * + * DeleteNode() handles the task of deleting a given node in the tree. + * + * ---------------------------------------------------------------------------- + */ + +void +DeleteNode(node) + Tree *node; +{ + Tree *parent; + + XRectangle *rectangles; + XSegment *segments; + int nrectangles, nsegments; + Widget w = TreeDrawingArea; + int width, height; + int x_offset, y_offset; + + StatusMsg("", TRUE); + + if (TreeShowSteps) + node->on_path = TRUE; + + /* erase the contour before changing in the tree */ + if ((TreeShowContourOption != NoContours) || + TreeShowSteps) { + BeginFrame(); + DrawTree(TheTree, New); + EndFrame(); + } + + sprintf(strbuf, "Node `%s' selected for deletion", node->label.text); + StatusMsg(strbuf, FALSE); + Pause(); + + parent = node->parent; + + if (parent) + Unzip(parent); + else + TheTree = NULL; /* delete root of tree */ + + /* fade out deleted subtree */ + StatusMsg("Delete subtree", FALSE); + GetSubTreeRectangles(node, &rectangles, &nrectangles, TRUE); + GetSubTreeSegments(node, &segments, &nsegments); + DissolveTree(XtDisplay(w), XtWindow(w), + rectangles, nrectangles, + segments, nsegments, TRUE); + free(rectangles); + free(segments); + + Delete(node); + + BeginFrame(); + if (TheTree) + DrawTree(TheTree, New); + EndFrame(); + Pause(); + + if (parent) + Zip(parent); + + if (TheTree) { + ComputeTreeSize(TheTree, &width, &height, &x_offset, &y_offset); + PetrifyTree(TheTree, x_offset + MAT_SIZE, y_offset + MAT_SIZE); + StatusMsg("Move tree to new configuration", FALSE); + AnimateTree(TheTree); + SetDrawingSize(width + (2 * MAT_SIZE), height + (2 * MAT_SIZE)); + Pause(); + + if (TreeShowSteps) { + if (parent) + AnimateZip(parent); + } + + BeginFrame(); + SetContours(TreeShowContourOption); + DrawTree(TheTree, New); + EndFrame(); + + } + + StatusMsg("Ready", TRUE); +} + + +/* ---------------------------------------------------------------------------- + * + * ResetLabels() is called when the TreeAlignNodes mode is changed. + * When TreeParentDistance changes, the node width changes, so this + * function forces each node's width to be recomputed. + * + * ---------------------------------------------------------------------------- + */ + +ResetLabels(tree) + Tree *tree; +{ + Tree *child; + + SetNodeLabel(tree, tree->label.text); + FOREACH_CHILD(child, tree) + ResetLabels(child); +} + + +/* ---------------------------------------------------------------------------- + * + * SetupTree() handles the task of setting up the specified tree in + * the drawing area. + * + * ---------------------------------------------------------------------------- + */ + +void +SetupTree(tree) + Tree *tree; +{ + int width, height; + int x_offset, y_offset; + + LayoutTree(tree); + ComputeTreeSize(tree, &width, &height, &x_offset, &y_offset); + PetrifyTree(tree, x_offset + MAT_SIZE, y_offset + MAT_SIZE); + SetDrawingTree(tree); + SetDrawingSize(width + (2 * MAT_SIZE), height + (2 * MAT_SIZE)); + BeginFrame(); + SetContours(TreeShowContourOption); + DrawTree(tree, New); + EndFrame(); +} + + + + + +