diff pkg-src/tree-x/tree.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/tree.c	Mon Aug 13 09:43:35 2007 +0200
@@ -0,0 +1,725 @@
+/* ----------------------------------------------------------------------------
+ * File    : tree.c
+ * Purpose : dynamic tree program based on Sven Moen's algorithm
+ * ----------------------------------------------------------------------------
+ */
+
+#include "defs.h"
+#include "tree.h"
+#include "dbl.h"
+#include "intf.h"
+#include <string.h>
+
+/* ------------------------------------------------------------------------- */
+/*				Global Variables                             */
+/* ------------------------------------------------------------------------- */
+
+int NumLines = 0;
+int NumNodes = 0;
+
+
+/* ----------------------------------------------------------------------------
+ * 
+ *   MakeLine() allocates the memory required for a Polyline and 
+ *   initializes the fields of a Polyline to the arguments. The
+ *   newly-allocated Polyline is returned by the function.
+ * 
+ * ----------------------------------------------------------------------------
+ */
+
+Polyline*
+MakeLine(dx, dy, link)
+   short dx;
+   short dy;
+   Polyline *link;
+{
+   Polyline *new;
+
+   new = (Polyline *) malloc(sizeof(Polyline));
+   NASSERT(new, "could not allocate memory for polyline");
+   NumLines++;
+
+   new->dx = dx;
+   new->dy = dy;
+   new->link = link;
+
+   return (new);
+}
+
+/* ----------------------------------------------------------------------------
+ * 
+ *   MakeNode() allocates the memory required for a tree node, and
+ *   zeros out all the fields in the Node. It returns a pointer to the
+ *   tree node upon success, and NULL upon failure.
+ * 
+ * ----------------------------------------------------------------------------
+ */
+
+Tree*
+MakeNode()
+{
+   Tree *node;
+   
+   node = (Tree *) malloc(sizeof(Tree));
+   NASSERT(node, "could not allocate memory for node");
+   NumNodes++;
+
+   if (node == NULL)
+      return (NULL);
+   else {
+#ifdef SYSV
+      memset((char *) node, 0, sizeof(Tree));
+#else
+      bzero((char *) node, sizeof(Tree));
+#endif
+      return (node);
+   }
+}
+
+/* ----------------------------------------------------------------------------
+ * 
+ *   MakeBridge()
+ * 
+ * ----------------------------------------------------------------------------
+ */
+
+Polyline*
+MakeBridge(line1, x1, y1, line2, x2, y2)
+   Polyline *line1, *line2;
+   int x1, x2, y1, y2;
+{
+   int dx, dy, s;
+   Polyline *r;
+
+   dx = x2 + line2->dx - x1;
+   if (line2->dx == 0)
+      dy = line2->dy;
+   else {
+      s = dx * line2->dy;
+      dy = s / line2->dx;
+   }
+   r = MakeLine(dx, dy, line2->link);
+   line1->link = MakeLine(0, y2 + line2->dy - dy - y1, r);
+
+   return (r);
+}
+
+/* ----------------------------------------------------------------------------
+ * 
+ *   Offset() computes the necessary offset that prevents two line segments
+ *   from intersecting each other. This is the "heart" of the merge step
+ *   that computes how two subtree contours should be separated. 
+ * 
+ *   The code is taken directly from Sven Moen's paper, with changes in
+ *   some variable names to give more meaning: 
+ *   
+ *   - px,py indicate the x- and y-coordinates of the point on the longer
+ *     segment if the previous Offset() call had two unequal segments
+ * 
+ *   - lx,ly indicate the dx and dy values of the "lower" line segment
+ * 
+ *   - ux,uy indicate the dx and dy values of the "upper" line segment
+ * 
+ * ----------------------------------------------------------------------------
+ */
+
+int
+Offset(px, py, lx, ly, ux, uy)
+   int px, py, lx, ly, ux, uy;
+{
+   int d, s, t;
+
+   if (ux <= px || px+lx <= 0)
+      return 0;
+
+   t = ux*ly - lx*uy;
+
+   if (t > 0) {
+      if (px < 0) {
+	 s = px*ly;
+	 d = s/lx - py;
+      }
+      else if (px > 0) {
+	 s = px*uy;
+	 d = s/ux - py;
+      }
+      else {
+	 d = -py;
+      }
+   }
+   else {
+      if (ux < px+lx) {
+	 s = (ux-px) * ly;
+	 d = uy - (py + s/lx);
+      }
+      else if (ux > px+lx) {
+	 s = (lx+px) * uy;
+	 d = s/ux - (py+ly);
+      }
+      else {
+	 d = uy - (py+ly);
+      }
+   }
+
+   return MAX(0, d);
+}
+
+/* ----------------------------------------------------------------------------
+ * 
+ *   Merge()
+ * 
+ * ----------------------------------------------------------------------------
+ */
+
+int
+Merge(c1, c2)
+   Polygon *c1, *c2;
+{
+   int x, y, total, d;
+   Polyline *lower, *upper, *bridge;
+
+   x = y = total = 0;
+
+   /*  compare lower part of upper child's contour 
+    *  with upper part of lower child's contour
+    */
+   upper = c1->lower.head;
+   lower = c2->upper.head;
+
+   while (lower && upper) {
+      d = Offset(x, y, lower->dx, lower->dy, upper->dx, upper->dy);
+      y += d;
+      total += d;
+
+      if (x + lower->dx <= upper->dx) {
+	 x += lower->dx;
+	 y += lower->dy;
+	 lower = lower->link;
+      }
+      else {
+	 x -= upper->dx;
+	 y -= upper->dy;
+	 upper = upper->link;
+      }
+   }
+	 
+   if (lower) {
+      bridge = MakeBridge(c1->upper.tail, 0, 0, lower, x, y);
+      c1->upper.tail = (bridge->link) ? c2->upper.tail : bridge;
+      c1->lower.tail = c2->lower.tail;
+   }
+   else {
+      bridge = MakeBridge(c2->lower.tail, x, y, upper, 0, 0);
+      if (!bridge->link) 
+	 c1->lower.tail = bridge;
+   }
+   c1->lower.head = c2->lower.head;
+
+   return (total);
+}
+
+/* ----------------------------------------------------------------------------
+ * 
+ *   DetachParent() reverses the effects of AttachParent by removing
+ *   the four line segments that connect the subtree contour to the
+ *   node specified by 'tree'. 
+ * 
+ * ----------------------------------------------------------------------------
+ */
+
+void
+DetachParent(tree)
+   Tree *tree;
+{
+   free(tree->contour.upper.head->link);
+   free(tree->contour.upper.head);
+   tree->contour.upper.head = NULL;
+   tree->contour.upper.tail = NULL;
+
+   free(tree->contour.lower.head->link);
+   free(tree->contour.lower.head);
+   tree->contour.lower.head = NULL;
+   tree->contour.lower.tail = NULL;
+
+   NumLines -= 4;
+}
+
+/* ----------------------------------------------------------------------------
+ * 
+ *   AttachParent() 
+ *   This function also establishes the position of the first child
+ *   The code follows Sven Moen's version, with slight modification to
+ *   support varying borders at different levels.
+ * 
+ * ----------------------------------------------------------------------------
+ */
+
+void
+AttachParent(tree, h)
+   Tree *tree;
+   int h;
+{
+   int x, y1, y2;
+
+   if (TreeAlignNodes)
+      x = tree->border + (TreeParentDistance * 2) +
+	 (TreeParentDistance - tree->width);
+   else
+      x = tree->border + TreeParentDistance;
+   y2 = (h - tree->height)/2 - tree->border;
+   y1 = y2 + tree->height + (2 * tree->border) - h; 
+   tree->child->offset.x = x + tree->width;
+   tree->child->offset.y = y1;
+   tree->contour.upper.head = MakeLine(tree->width, 0,
+				       MakeLine(x, y1,
+						tree->contour.upper.head));
+   tree->contour.lower.head = MakeLine(tree->width, 0,
+				       MakeLine(x, y2,
+						tree->contour.lower.head));
+}
+
+/* ----------------------------------------------------------------------------
+ * 
+ *   Split()
+ *   The tree passed to Split() must have at least 1 child, because
+ *   it doesn't make sense to split a leaf (there are no bridges)
+ * 
+ * ----------------------------------------------------------------------------
+ */
+
+void
+Split(tree)
+   Tree *tree;
+{
+   Tree *child;
+   Polyline *link;
+
+   FOREACH_CHILD(child, tree) {
+      if (link = child->contour.upper.tail->link) {
+	 free(link->link);
+	 free(link);
+	 child->contour.upper.tail->link = NULL;
+	 NumLines -= 2;
+      }
+      if (link = child->contour.lower.tail->link) {
+	 free(link->link);
+	 free(link);
+	 NumLines -= 2;
+	 child->contour.lower.tail->link = NULL;
+      }
+   }
+}
+
+/* ----------------------------------------------------------------------------
+ * 
+ *   Join() merges all subtree contours of the given tree and returns the
+ *   height of the entire tree contour. 
+ * 
+ * ----------------------------------------------------------------------------
+ */
+
+int
+Join(tree)
+   Tree *tree;
+{
+   Tree *child;
+   int d, h, sum;
+
+   /*   to start, set the parent's contour and height
+    *   to contour and height of first child 
+    */
+   child = tree->child;
+   tree->contour = child->contour;
+   sum = h = child->height + (2 * child->border);
+
+   /* extend contour to include contours of all children of parent */
+   for (child = child->sibling ; child ; child = child->sibling) {
+      d = Merge(&tree->contour, &child->contour);
+      child->offset.y = d + h;
+      child->offset.x = 0;
+      h = child->height + (2 * child->border);
+      /* keep cumulative heights of subtree contours */
+      sum += d + h;
+   }
+   return sum;
+}
+
+/* ----------------------------------------------------------------------------
+ * 
+ *   RuboutLeaf() accepts a single node (leaf) and removes its contour.
+ *   The memory associated with the contour is deallocated. 
+ * 
+ * ----------------------------------------------------------------------------
+ */
+
+void
+RuboutLeaf(tree)
+   Tree *tree;
+{
+   free(tree->contour.upper.head);
+   free(tree->contour.lower.tail);
+   free(tree->contour.lower.head);
+   tree->contour.upper.head = NULL;   
+   tree->contour.upper.tail = NULL;   
+   tree->contour.lower.head = NULL;   
+   tree->contour.lower.tail = NULL;   
+   NumLines -= 3;
+}
+
+/* ----------------------------------------------------------------------------
+ * 
+ *   LayoutLeaf() accepts a single node (leaf) and forms its contour. This
+ *   function assumes that the width, height, and border fields of the 
+ *   node have been assigned meaningful values.
+ * 
+ * ----------------------------------------------------------------------------
+ */
+
+void
+LayoutLeaf(tree)
+   Tree *tree;
+{
+   tree->node_height = 0;
+   tree->border = TreeBorderSize;
+
+   tree->contour.upper.tail = MakeLine(tree->width + 2 * tree->border, 0,
+				       NULL);
+   tree->contour.upper.head = tree->contour.upper.tail;
+   
+   tree->contour.lower.tail = MakeLine(0, -tree->height - 2 * tree->border,
+				       NULL);
+   tree->contour.lower.head = MakeLine(tree->width + 2 * tree->border, 0,
+				       tree->contour.lower.tail);
+
+}
+
+/* ----------------------------------------------------------------------------
+ * 
+ *   LayoutTree() traverses the given tree (in depth-first order), and forms
+ *   subtree or leaf contours at each node as needed. Each node's contour is
+ *   stored in its "contour" field. Elision is also supported by generating
+ *   the contour for both the expanded and collapsed node. This routine
+ *   also computes the tree height of each node in the tree, so that variable
+ *   density layout can be demonstrated.
+ * 
+ * ----------------------------------------------------------------------------
+ */
+
+void
+LayoutTree(tree)
+   Tree *tree;
+{
+   Tree *child;
+   int   height = 0;
+
+   FOREACH_CHILD(child, tree) {
+      LayoutTree(child);
+
+      if (child->elision) {	/* support elision */
+	 child->old_contour = child->contour;
+	 LayoutLeaf(child);
+      }
+
+   }
+
+   if (tree->child) {
+
+      FOREACH_CHILD(child, tree) 
+	 height = MAX(child->node_height, height);
+      tree->node_height = height + 1;
+
+      if (TreeLayoutDensity == Fixed)
+	 tree->border = TreeBorderSize;
+      else
+	 tree->border =
+	    (int) (TreeBorderSize * (tree->node_height * DENSITY_FACTOR));
+
+      AttachParent(tree, Join(tree));
+   }
+   else
+      LayoutLeaf(tree);
+}
+
+/* ------------------------------------------------------------------------- */
+
+void
+Unzip(tree)
+   Tree *tree;
+{
+   Tree *child;
+
+#ifdef INTF
+   if (TreeShowSteps) {
+      HiliteNode(tree, New);
+      tree->on_path = TRUE;
+      StatusMsg("Unzip: follow parent links up to root");
+      Pause();
+   }
+#endif   
+
+   if (tree->parent)
+      Unzip(tree->parent);
+
+   if (tree->child) {
+
+#ifdef INTF
+      /*   draw entire contour; do it only for root, because the last
+       *   frame drawn in this function will have already drawn the  
+       *   contour for the most recently split subtree.              
+       */
+      if (TreeShowSteps) {
+	 if (tree->parent == NULL) {
+	    BeginFrame();
+	      DrawTreeContour(tree, New, CONTOUR_COLOR, FALSE, FALSE, FALSE);
+	      DrawTree(TheTree, New);
+	    EndFrame();
+	    StatusMsg("Unzip: disassemble entire contour");
+	    Pause();
+	 }
+      }
+#endif
+
+#ifdef INTF
+      /* draw contour as it would appear after DetachParent() */
+      if (TreeShowSteps) {
+	 BeginFrame();
+	   DrawTreeContour(tree, New, CONTOUR_COLOR, TRUE,
+			   FALSE, FALSE, FALSE);
+	   DrawTree(TheTree, New);
+	 EndFrame();
+	 StatusMsg("Unzip: detach parent");
+	 Pause();
+      }
+#endif
+
+      DetachParent(tree);
+      Split(tree);
+
+#ifdef INTF
+      if (TreeShowSteps) {
+	 BeginFrame();
+           /* mark other subtree contours as split, and */
+	   /* draw only the contour on path in full     */
+	   FOREACH_CHILD(child, tree) {
+	      if (!child->on_path) 
+		 child->split = TRUE;
+	      else
+		 DrawTreeContour(child, New, CONTOUR_COLOR,
+				 FALSE, FALSE, FALSE);
+	   }
+	   DrawTree(TheTree, New);
+	 EndFrame();
+	 StatusMsg("Unzip: split tree");
+	 Pause();
+      }
+#endif
+
+   }
+   else
+      RuboutLeaf(tree);		/* leaf node */
+}
+
+/* ------------------------------------------------------------------------- */
+
+void
+Zip(tree)
+   Tree *tree;
+{
+   if (tree->child)
+      AttachParent(tree, Join(tree));
+   else
+      LayoutLeaf(tree);
+
+   if (tree->parent)
+      Zip(tree->parent);
+}
+
+/* ----------------------------------------------------------------------------
+ * 
+ *   Insert() adds the specified child to parent, just after the specified
+ *   sibling. If 'sibling' is Null, the child is added as the first child.
+ * 
+ * ----------------------------------------------------------------------------
+ */
+
+void
+Insert(parent, child, sibling)
+   Tree *parent, *child, *sibling;
+{
+   Unzip(parent);
+   child->parent = parent;
+   if (sibling) {
+      child->sibling = sibling->sibling;
+      sibling->sibling = child;
+   }
+   else {
+      child->sibling = parent->child;
+      parent->child = child;
+   }
+   Zip(parent);
+}
+
+
+
+
+/* ----------------------------------------------------------------------------
+ * 
+ *   Delete() traverses the specified tree and frees all storage
+ *   allocated to the subtree, including contours and bridges.
+ *   If the tree had a preceding sibling, the preceding sibling is
+ *   modified to point to the tree's succeeding sibling, if any.
+ * 
+ * ----------------------------------------------------------------------------
+ */
+
+Delete(tree)
+   Tree *tree;
+{
+   Tree *sibling = NULL;
+   Tree *parent, *child;
+
+   /* find sibling */
+   parent = tree->parent;
+   if (parent) {
+      FOREACH_CHILD(child, parent)
+	 if (child->sibling == tree) {
+	    sibling = child;
+	    break;
+	 }
+   }
+   if (sibling)
+      sibling->sibling = tree->sibling;
+   else if (parent)
+      parent->child = tree->sibling;
+   
+   DeleteTree(tree, FALSE);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * 
+ *   DeleteTree() is the recursive function that supports Delete(). 
+ *   If 'contour' is True, then only the contours are recursively deleted.
+ *   This flag should be True when you are regenerating a tree's layout
+ *   and still want to preserve the nodes. Since contours would be deleted
+ *   only due to a change in sibling or level distance, each node's border
+ *   value is updated with the current value of TreeBorderSize;
+ * 
+ * ----------------------------------------------------------------------------
+ */
+
+DeleteTree(tree, contour)
+   Tree *tree;
+   int   contour;
+{
+   Tree *child;
+
+   if (tree->elision) {
+      RuboutLeaf(tree);
+      tree->contour = tree->old_contour;
+      tree->old_contour.upper.head = NULL;    /* flag to note 'NULL' contour */
+   }
+
+   if (!IS_LEAF(tree)) {
+      DetachParent(tree);
+      Split(tree);
+
+      FOREACH_CHILD(child,tree)
+	 DeleteTree(child, contour);
+   }
+   else
+      RuboutLeaf(tree);
+
+   if (contour) 
+      tree->border = TreeBorderSize;
+   else {
+      free(tree->label.text);
+      free(tree);
+      NumNodes--;
+   }
+}
+
+
+/* ----------------------------------------------------------------------------
+ * 
+ *   ComputeTreeSize() 
+ *   This function should be called after tree layout.
+ * 
+ * ----------------------------------------------------------------------------
+ */
+
+void
+ComputeTreeSize(tree, width, height, x_offset, y_offset)
+   Tree *tree;
+   int *width, *height;
+   int *x_offset, *y_offset;
+{
+   Polyline *contour, *tail;
+   int upper_min_y = 0, lower_max_y = 0;
+   int upper_abs_y = 0, lower_abs_y = 0;
+   int x = 0;
+
+   /* do upper contour */
+   contour = tree->contour.upper.head;
+   tail    = tree->contour.upper.tail;
+   while (contour) {
+      if ((contour->dy + upper_abs_y) < upper_min_y) 
+	 upper_min_y = contour->dy + upper_abs_y;
+      upper_abs_y += contour->dy;
+      if (contour == tail)
+	 contour = NULL;
+      else
+	 contour = contour->link;
+   }
+
+   /* do lower contour */
+   contour = tree->contour.lower.head;
+   tail    = tree->contour.lower.tail;
+   while (contour) {
+      if ((contour->dy + lower_abs_y) > lower_max_y)
+	 lower_max_y = contour->dy + lower_abs_y;
+      lower_abs_y += contour->dy;
+      x += contour->dx;
+      if (contour == tail)
+	 contour = NULL;
+      else
+	 contour = contour->link;
+   }
+
+   *width = x + 1;
+   *height = lower_max_y - upper_min_y +
+             (tree->height + (2 * tree->border)) + 1;
+   if (x_offset)
+      *x_offset = tree->border;
+   if (y_offset)
+      *y_offset = - upper_min_y + tree->border;
+}
+
+/* ----------------------------------------------------------------------------
+ * 
+ *   PetrifyTree()
+ * 
+ * ----------------------------------------------------------------------------
+ */
+
+void
+PetrifyTree(tree, x, y)
+   Tree *tree;
+   int x, y;
+{
+   int width, height;
+   int x_offset, y_offset;
+   
+   tree->old_pos = tree->pos;	/* used by AnimateTree */
+
+   /* fix position of each node */
+   tree->pos.x = x + tree->offset.x;
+   tree->pos.y = y + tree->offset.y;
+   
+   if (tree->child) {
+      PetrifyTree(tree->child, tree->pos.x, tree->pos.y);
+      ComputeSubTreeExtent(tree); /* for benefit of interface picking */
+   }
+   if (tree->sibling)
+      PetrifyTree(tree->sibling, tree->pos.x, tree->pos.y);
+}