/* ---------------------------------------------------------------------------- * File : draw.c * Purpose : drawing-specific routines for dynamic tree program * ---------------------------------------------------------------------------- */ #include #include #include #include "dissolve.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; /* ------------------------------------------------------------------------- */ /* Functions */ /* ------------------------------------------------------------------------- */ /* ---------------------------------------------------------------------------- * * BeginFrame() provides an abstraction for double buffering. It should * be called prior to creating a new frame of animation. * * ---------------------------------------------------------------------------- */ void BeginFrame(void) { DBLbegin_frame(TreeDrawingAreaDB); } /* ---------------------------------------------------------------------------- * * EndFrame() provides an abstraction for double buffering. It should * be called after creating a new frame of animation. * * ---------------------------------------------------------------------------- */ void EndFrame(void) { /* Changed second parameter from 0 to 1 at Torgeir Veimo's suggestion on 9/21/1997. -- Bob Weiner, BeOpen.com */ DBLend_frame(TreeDrawingAreaDB, 1); } /* ---------------------------------------------------------------------------- * * GetDrawingSize() gets the size of the drawing area, and returns the * dimensions in the arguments. * * ---------------------------------------------------------------------------- */ static void GetDrawingSize(int *width, int *height) { Dimension w, h; Arg al [2]; XtSetArg (al [0], XtNwidth, &w); XtSetArg (al [1], XtNheight, &h); XtGetValues(TreeDrawingArea, al, 2); *width = (int) w; *height = (int) h; } /* ---------------------------------------------------------------------------- * * SetDrawingSize() sets the size of the drawing area to the given * dimensions. * * ---------------------------------------------------------------------------- */ static void SetDrawingSize(int width, int height) { Arg al [2]; XtSetArg (al [0], XtNwidth, (Dimension) width); XtSetArg (al [1], XtNheight, (Dimension) height); XtSetValues (TreeDrawingArea, al, 2); XClearArea(TreeDrawingAreaDB->display, TreeDrawingAreaDB->window, 0, 0, 0, 0, FALSE); } /* ---------------------------------------------------------------------------- * * SetDrawingTree() is used to specify what tree is to be drawn in the * drawing area. * * ---------------------------------------------------------------------------- */ void SetDrawingTree(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(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(int color) { XSetForeground(TreeDrawingAreaDB->display, TreeDrawingAreaDB->gc, TreeDrawingAreaDB->colors[color]); } /* ---------------------------------------------------------------------------- * * SetLineWidth() sets the line width of lines drawn in the TreeDrawingArea. * * ---------------------------------------------------------------------------- */ static void SetLineWidth(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. * * ---------------------------------------------------------------------------- */ static void SetContours(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 *tree, PosMode pos_mode) { 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(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 *tree, PosMode pos_mode, int color, int detach_p, int select_p, int recursive) { Widget w = TreeDrawingArea; Polyline *contour, *tail; Tree *child; int x, y, i; if (tree == NULL) return; if ((select_p && tree->show_contour) || !select_p) { 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_p) { /* 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_p) { /* 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_p, select_p, 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 *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); maybe_highlight_node(tree); } /* ---------------------------------------------------------------------------- * * 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. * * ---------------------------------------------------------------------------- */ static void ShiftTree(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. * * ---------------------------------------------------------------------------- */ static void AnimateTree(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(). * * ---------------------------------------------------------------------------- */ static void AnimateZip(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. * * ---------------------------------------------------------------------------- */ static int CountNodes(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. * * ---------------------------------------------------------------------------- */ static void CollectNodeRectangles(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. * * ---------------------------------------------------------------------------- */ static void GetSubTreeRectangles(Tree *tree, XRectangle **rectangles, int *nrectangles, int 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. * * ---------------------------------------------------------------------------- */ static void CollectNodeSegments(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. * * ---------------------------------------------------------------------------- */ static void GetSubTreeSegments(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) { 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 *tree, int x, int y, Tree **node) { 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(Widget w, XtPointer closure, XEvent *event, Boolean *continue_to_dispatch) { if (event->xexpose.count == 0) { BeginFrame(); SetContours(TreeShowContourOption); DrawTree(TheTree, New); EndFrame(); } } /* ---------------------------------------------------------------------------- * * ExpandCollapseNode is called to expand or collapse a node in the tree. * * ---------------------------------------------------------------------------- */ void ExpandCollapseNode(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(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; } } else { parent = NULL; abort(); } 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(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. * * ---------------------------------------------------------------------------- */ void ResetLabels(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) { 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(); }