Mercurial > hg > xemacs-beta
comparison 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 |
comparison
equal
deleted
inserted
replaced
162:4de2936b4e77 | 163:0132846995bd |
---|---|
1 /* ---------------------------------------------------------------------------- | |
2 * File : draw.c | |
3 * Purpose : drawing-specific routines for dynamic tree program | |
4 * ---------------------------------------------------------------------------- | |
5 */ | |
6 | |
7 #include <X11/Intrinsic.h> | |
8 #include <X11/StringDefs.h> | |
9 | |
10 #include "defs.h" | |
11 #include "tree.h" | |
12 #include "dbl.h" | |
13 #include "intf.h" | |
14 | |
15 /* ------------------------------------------------------------------------- */ | |
16 /* Global Variables */ | |
17 /* ------------------------------------------------------------------------- */ | |
18 | |
19 Tree *TheTree; | |
20 | |
21 | |
22 /* ------------------------------------------------------------------------- */ | |
23 /* Local Variables */ | |
24 /* ------------------------------------------------------------------------- */ | |
25 | |
26 static char AnimationMode = FALSE; | |
27 static char strbuf[BUFSIZ]; | |
28 static int AnimationStep = ANIMATION_STEP; | |
29 | |
30 /* ------------------------------------------------------------------------- */ | |
31 /* Forward Function Declarations */ | |
32 /* ------------------------------------------------------------------------- */ | |
33 | |
34 void DrawNode(); | |
35 void DrawTreeContour(); | |
36 | |
37 | |
38 /* ------------------------------------------------------------------------- */ | |
39 /* Functions */ | |
40 /* ------------------------------------------------------------------------- */ | |
41 | |
42 | |
43 /* ---------------------------------------------------------------------------- | |
44 * | |
45 * BeginFrame() provides an abstraction for double buffering. It should | |
46 * be called prior to creating a new frame of animation. | |
47 * | |
48 * ---------------------------------------------------------------------------- | |
49 */ | |
50 | |
51 void | |
52 BeginFrame() | |
53 { | |
54 DBLbegin_frame(TreeDrawingAreaDB); | |
55 } | |
56 | |
57 | |
58 /* ---------------------------------------------------------------------------- | |
59 * | |
60 * EndFrame() provides an abstraction for double buffering. It should | |
61 * be called after creating a new frame of animation. | |
62 * | |
63 * ---------------------------------------------------------------------------- | |
64 */ | |
65 | |
66 void | |
67 EndFrame() | |
68 { | |
69 DBLend_frame(TreeDrawingAreaDB, 0); | |
70 } | |
71 | |
72 | |
73 /* ---------------------------------------------------------------------------- | |
74 * | |
75 * GetDrawingSize() gets the size of the drawing area, and returns the | |
76 * dimensions in the arguments. | |
77 * | |
78 * ---------------------------------------------------------------------------- | |
79 */ | |
80 | |
81 void | |
82 GetDrawingSize(width, height) | |
83 int *width, *height; | |
84 { | |
85 Dimension w, h; | |
86 | |
87 XtVaGetValues(TreeDrawingArea, | |
88 XtNwidth, &w, | |
89 XtNheight, &h, | |
90 NULL); | |
91 | |
92 *width = (int) w; | |
93 *height = (int) h; | |
94 } | |
95 | |
96 | |
97 /* ---------------------------------------------------------------------------- | |
98 * | |
99 * SetDrawingSize() sets the size of the drawing area to the given | |
100 * dimensions. | |
101 * | |
102 * ---------------------------------------------------------------------------- | |
103 */ | |
104 | |
105 void | |
106 SetDrawingSize(width, height) | |
107 int width, height; | |
108 { | |
109 XtVaSetValues(TreeDrawingArea, | |
110 XtNwidth, (Dimension) width, | |
111 XtNheight, (Dimension) height, | |
112 NULL); | |
113 } | |
114 | |
115 | |
116 /* ---------------------------------------------------------------------------- | |
117 * | |
118 * SetDrawingTree() is used to specify what tree is to be drawn in the | |
119 * drawing area. | |
120 * | |
121 * ---------------------------------------------------------------------------- | |
122 */ | |
123 | |
124 void | |
125 SetDrawingTree(tree) | |
126 Tree *tree; | |
127 { | |
128 TheTree = tree; | |
129 } | |
130 | |
131 | |
132 /* ---------------------------------------------------------------------------- | |
133 * | |
134 * SetNodeLabel() sets the label text of the specified node and computes | |
135 * the bounding rectangle so that the layout can be determined. This | |
136 * function is called when new nodes are created. If TreeAlignNodes is | |
137 * True, the string is truncated so that the node's width is no longer | |
138 * than TreeParentDistance. | |
139 * | |
140 * ---------------------------------------------------------------------------- | |
141 */ | |
142 | |
143 void | |
144 SetNodeLabel(node, label) | |
145 Tree *node; | |
146 char *label; | |
147 { | |
148 int len; | |
149 int dummy; | |
150 XCharStruct rtrn; | |
151 | |
152 len = strlen(label); | |
153 while (len > 1) { | |
154 XTextExtents(TreeLabelFont, label, len, &dummy, &dummy, &dummy, &rtrn); | |
155 node->width = rtrn.lbearing + rtrn.rbearing + (LABEL_MAT_WIDTH * 2) + 1; | |
156 node->height = rtrn.ascent + rtrn.descent + (LABEL_MAT_HEIGHT * 2) + 1; | |
157 if (TreeAlignNodes) { | |
158 if (node->width <= (2 * TreeParentDistance)) | |
159 break; | |
160 else | |
161 len--; | |
162 } | |
163 else | |
164 break; | |
165 } | |
166 | |
167 node->label.text = label; | |
168 node->label.len = len; | |
169 node->label.xoffset = LABEL_MAT_WIDTH + 1, | |
170 node->label.yoffset = rtrn.ascent + LABEL_MAT_HEIGHT + 1; | |
171 } | |
172 | |
173 | |
174 /* ---------------------------------------------------------------------------- | |
175 * | |
176 * SetDrawColor() sets the drawing color of the TreeDrawingArea. | |
177 * | |
178 * ---------------------------------------------------------------------------- | |
179 */ | |
180 | |
181 void | |
182 SetDrawColor(color) | |
183 int color; | |
184 { | |
185 XSetForeground(TreeDrawingAreaDB->display, TreeDrawingAreaDB->gc, | |
186 TreeDrawingAreaDB->colors[color]); | |
187 } | |
188 | |
189 /* ---------------------------------------------------------------------------- | |
190 * | |
191 * SetLineWidth() sets the line width of lines drawn in the TreeDrawingArea. | |
192 * | |
193 * ---------------------------------------------------------------------------- | |
194 */ | |
195 | |
196 void | |
197 SetLineWidth(width) | |
198 unsigned int width; | |
199 { | |
200 XSetLineAttributes(TreeDrawingAreaDB->display, TreeDrawingAreaDB->gc, | |
201 width, LineSolid, CapButt, JoinRound); | |
202 } | |
203 | |
204 | |
205 /* ---------------------------------------------------------------------------- | |
206 * | |
207 * SetContours() sets the visibility of three possible contour modes: | |
208 * the outside contour, all subtree contours, or selected contours. | |
209 * | |
210 * ---------------------------------------------------------------------------- | |
211 */ | |
212 | |
213 void | |
214 SetContours(option) | |
215 ContourOption option; | |
216 { | |
217 if (option == NoContours) { | |
218 switch (TreeShowContourOption) { | |
219 case OutsideContour: | |
220 DrawTreeContour(TheTree, New, BACKGROUND_COLOR, FALSE, FALSE, FALSE); | |
221 break; | |
222 case AllContours: | |
223 DrawTreeContour(TheTree, New, BACKGROUND_COLOR, FALSE, FALSE, TRUE); | |
224 break; | |
225 case SelectedContours: | |
226 DrawTreeContour(TheTree, New, BACKGROUND_COLOR, FALSE, TRUE, TRUE); | |
227 break; | |
228 default: | |
229 ; | |
230 } | |
231 DrawTreeContour(TheTree, New, BACKGROUND_COLOR, FALSE, FALSE, TRUE); | |
232 } | |
233 else if (option == OutsideContour) { | |
234 switch (TreeShowContourOption) { | |
235 case AllContours: | |
236 DrawTreeContour(TheTree, New, BACKGROUND_COLOR, FALSE, FALSE, TRUE); | |
237 break; | |
238 case SelectedContours: | |
239 DrawTreeContour(TheTree, New, BACKGROUND_COLOR, FALSE, TRUE, TRUE); | |
240 break; | |
241 default: | |
242 ; | |
243 } | |
244 DrawTreeContour(TheTree, New, CONTOUR_COLOR, FALSE, FALSE, FALSE); | |
245 } else if (option == AllContours) { | |
246 DrawTreeContour(TheTree, New, CONTOUR_COLOR, FALSE, FALSE, TRUE); | |
247 } else if (option == SelectedContours) { | |
248 switch (TreeShowContourOption) { | |
249 case AllContours: | |
250 DrawTreeContour(TheTree, New, BACKGROUND_COLOR, FALSE, FALSE, TRUE); | |
251 break; | |
252 case OutsideContour: | |
253 DrawTreeContour(TheTree, New, BACKGROUND_COLOR, FALSE, FALSE, FALSE); | |
254 break; | |
255 default: | |
256 DrawTreeContour(TheTree, New, BACKGROUND_COLOR, FALSE, FALSE, TRUE); | |
257 } | |
258 DrawTreeContour(TheTree, New, CONTOUR_COLOR, FALSE, TRUE, TRUE); | |
259 } | |
260 TreeShowContourOption = option; | |
261 } | |
262 | |
263 | |
264 /* ---------------------------------------------------------------------------- | |
265 * | |
266 * HiliteNode() is called by Unzip() to change the color of a node. | |
267 * | |
268 * ---------------------------------------------------------------------------- | |
269 */ | |
270 | |
271 void | |
272 HiliteNode(tree, pos_mode) | |
273 Tree *tree; | |
274 { | |
275 SetDrawColor(HIGHLIGHT_COLOR); | |
276 DrawNode(tree, pos_mode); | |
277 SetDrawColor(TREE_COLOR); | |
278 } | |
279 | |
280 | |
281 /* ---------------------------------------------------------------------------- | |
282 * | |
283 * DrawNode() takes a node and draws the node in the specified widget | |
284 * at its (x,y) coordinate. (x, y) indicates the upper-left corner where | |
285 * the node is drawn. Also, a line is drawn from the center of the left | |
286 * edge to the center of the parent's right edge. 'draw_mode' specifies | |
287 * the drawing mode (whether or not the node is erased, rather than drawn). | |
288 * 'pos_mode' determines whether or not to use the old position of the node. | |
289 * This flag is used in animating the movement of a node from its old | |
290 * position to its new position. | |
291 * | |
292 * ---------------------------------------------------------------------------- | |
293 */ | |
294 | |
295 void | |
296 DrawNode(node, pos_mode) | |
297 Tree *node; | |
298 PosMode pos_mode; | |
299 { | |
300 Widget w; | |
301 GC gc; | |
302 | |
303 w = TreeDrawingArea; | |
304 gc = TreeDrawingAreaDB->gc; | |
305 | |
306 if (pos_mode == Old) { | |
307 XDrawString(XtDisplay(w), XtWindow(w), gc, | |
308 node->old_pos.x + node->label.xoffset, | |
309 node->old_pos.y + node->label.yoffset, | |
310 node->label.text, node->label.len); | |
311 XDrawRectangle(XtDisplay(w), XtWindow(w), gc, | |
312 node->old_pos.x, node->old_pos.y, | |
313 node->width, node->height); | |
314 if (node->parent) | |
315 XDrawLine(XtDisplay(w), XtWindow(w), gc, | |
316 node->old_pos.x - 1, | |
317 node->old_pos.y + (node->height / 2), | |
318 node->parent->old_pos.x + node->parent->width + 1, | |
319 node->parent->old_pos.y + (node->parent->height / 2)); | |
320 if (node->elision) { | |
321 XSetFillStyle(TreeDrawingAreaDB->display, TreeDrawingAreaDB->gc, | |
322 FillTiled); | |
323 XFillRectangle(XtDisplay(w), XtWindow(w), gc, | |
324 node->old_pos.x + node->width - ELISION_WIDTH, | |
325 node->old_pos.y + 1, ELISION_WIDTH, node->height - 1); | |
326 XSetFillStyle(TreeDrawingAreaDB->display, TreeDrawingAreaDB->gc, | |
327 FillSolid); | |
328 } | |
329 } else { | |
330 XDrawString(XtDisplay(w), XtWindow(w), gc, | |
331 node->pos.x + node->label.xoffset, | |
332 node->pos.y + node->label.yoffset, | |
333 node->label.text, node->label.len); | |
334 | |
335 XDrawRectangle(XtDisplay(w), XtWindow(w), gc, | |
336 node->pos.x, node->pos.y, | |
337 node->width, node->height); | |
338 if (node->parent) | |
339 XDrawLine(XtDisplay(w), XtWindow(w), gc, | |
340 node->pos.x - 1, | |
341 node->pos.y + (node->height / 2), | |
342 node->parent->pos.x + node->parent->width + 1, | |
343 node->parent->pos.y + (node->parent->height / 2)); | |
344 if (node->elision) { | |
345 XSetFillStyle(TreeDrawingAreaDB->display, TreeDrawingAreaDB->gc, | |
346 FillTiled); | |
347 XFillRectangle(XtDisplay(w), XtWindow(w), gc, | |
348 node->pos.x + node->width - ELISION_WIDTH, | |
349 node->pos.y + 1, ELISION_WIDTH, node->height - 1); | |
350 XSetFillStyle(TreeDrawingAreaDB->display, TreeDrawingAreaDB->gc, | |
351 FillSolid); | |
352 } | |
353 } | |
354 } | |
355 | |
356 | |
357 /* ---------------------------------------------------------------------------- | |
358 * | |
359 * DrawTreeContour() draws the contour of the specified subtree. Bridges | |
360 * are not traversed, so the actual subtree contour is drawn, as opposed | |
361 * to the merged contour. 'color' specifies the drawing color. If 'detach' | |
362 * is True, the lines attaching the subtree contour to the node are not | |
363 * drawn. If 'select' is true, then only subtrees that are flagged as | |
364 * selected are shown. If 'recursive' is True, the entire tree is traversed. | |
365 * | |
366 * ---------------------------------------------------------------------------- | |
367 */ | |
368 | |
369 void | |
370 DrawTreeContour(tree, pos_mode, color, detach, select, recursive) | |
371 Tree *tree; | |
372 PosMode pos_mode; | |
373 int color; | |
374 int detach; | |
375 int select; | |
376 int recursive; | |
377 { | |
378 Widget w = TreeDrawingArea; | |
379 Polyline *contour, *tail; | |
380 Tree *child; | |
381 int x, y, i; | |
382 | |
383 if (tree == NULL) | |
384 return; | |
385 | |
386 if ((select && tree->show_contour) || !select) { | |
387 | |
388 SetDrawColor(color); | |
389 SetLineWidth(TreeContourWidth); | |
390 | |
391 /* draw upper contour */ | |
392 contour = tree->contour.upper.head; | |
393 tail = tree->contour.upper.tail; | |
394 if (pos_mode == Old) { | |
395 x = tree->old_pos.x - tree->border; | |
396 y = tree->old_pos.y - tree->border; | |
397 } | |
398 else { | |
399 x = tree->pos.x - tree->border; | |
400 y = tree->pos.y - tree->border; | |
401 } | |
402 | |
403 if (detach) { /* skip over attaching lines */ | |
404 for (i = 0 ; i < 2 ; i++) { | |
405 x += contour->dx; | |
406 y += contour->dy; | |
407 contour = contour->link; | |
408 } | |
409 } | |
410 | |
411 while (contour) { | |
412 XDrawLine(XtDisplay(w), XtWindow(w), TreeDrawingAreaDB->gc, | |
413 x, y, x + contour->dx, y + contour->dy); | |
414 x += contour->dx; | |
415 y += contour->dy; | |
416 if (contour == tail) /* make sure you don't follow bridges */ | |
417 contour = NULL; | |
418 else | |
419 contour = contour->link; | |
420 } | |
421 | |
422 /* draw lower contour */ | |
423 contour = tree->contour.lower.head; | |
424 tail = tree->contour.lower.tail; | |
425 if (pos_mode == Old) { | |
426 x = tree->old_pos.x - tree->border; | |
427 y = tree->old_pos.y + tree->border + tree->height; | |
428 } else { | |
429 x = tree->pos.x - tree->border; | |
430 y = tree->pos.y + tree->border + tree->height; | |
431 } | |
432 | |
433 if (detach) { /* skip over attaching lines */ | |
434 for (i = 0 ; i < 2 ; i++) { | |
435 x += contour->dx; | |
436 y += contour->dy; | |
437 contour = contour->link; | |
438 } | |
439 } | |
440 | |
441 while (contour) { | |
442 XDrawLine(XtDisplay(w), XtWindow(w), TreeDrawingAreaDB->gc, | |
443 x, y, x + contour->dx, y + contour->dy); | |
444 x += contour->dx; | |
445 y += contour->dy; | |
446 if (contour == tail) /* make sure you don't follow bridges */ | |
447 contour = NULL; | |
448 else | |
449 contour = contour->link; | |
450 } | |
451 } | |
452 | |
453 if (recursive) { | |
454 FOREACH_CHILD(child, tree) | |
455 if (!child->elision) | |
456 DrawTreeContour(child, pos_mode, color, | |
457 detach, select, recursive); | |
458 } | |
459 | |
460 SetDrawColor(TREE_COLOR); | |
461 SetLineWidth(0); | |
462 } | |
463 | |
464 | |
465 /* ---------------------------------------------------------------------------- | |
466 * | |
467 * DrawTree() traverses the given tree, drawing the node and connecting | |
468 * segments. The tree contours are also drawn at each step, if enabled. | |
469 * 'draw_mode' specifies the drawing mode in which the tree is drawn. | |
470 * 'pos_mode' determines whether or not to use the old position of the node. | |
471 * This flag is used in animating the movement of a node from its old | |
472 * position to its new position. DrawNode() is called to draw an individual | |
473 * node. | |
474 * | |
475 * ---------------------------------------------------------------------------- | |
476 */ | |
477 | |
478 void | |
479 DrawTree(tree, pos_mode) | |
480 Tree *tree; | |
481 PosMode pos_mode; | |
482 { | |
483 if (tree == NULL) | |
484 return; | |
485 | |
486 DrawNode(tree, pos_mode); | |
487 | |
488 /* do stuff that animates Unzip() */ | |
489 if (tree->split) { | |
490 if (!AnimationMode || | |
491 (tree->pos.x == tree->old_pos.x && | |
492 tree->pos.y == tree->old_pos.y)) | |
493 DrawTreeContour(tree, pos_mode, SPLIT_COLOR, FALSE, FALSE, FALSE); | |
494 else | |
495 DrawTreeContour(tree, pos_mode, ACTION_COLOR, FALSE, FALSE, FALSE); | |
496 } | |
497 if (tree->on_path) | |
498 HiliteNode(tree, pos_mode); | |
499 | |
500 if (tree->child && !tree->elision) | |
501 DrawTree(tree->child, pos_mode); | |
502 if (tree->sibling) | |
503 DrawTree(tree->sibling, pos_mode); | |
504 } | |
505 | |
506 | |
507 /* ---------------------------------------------------------------------------- | |
508 * | |
509 * ShiftTree() adjusts the positions of each node so that it moves from | |
510 * the "old" position towards the "new position". This is used by | |
511 * AnimateTree(). 'done' is set to FALSE if the tree is not in its | |
512 * final position; it is used to determine when to stop animating the tree. | |
513 * | |
514 * ---------------------------------------------------------------------------- | |
515 */ | |
516 | |
517 void | |
518 ShiftTree(tree, done) | |
519 Tree *tree; | |
520 int *done; | |
521 { | |
522 Tree *child; | |
523 | |
524 if (tree->old_pos.x != tree->pos.x || | |
525 tree->old_pos.y != tree->pos.y) | |
526 { | |
527 tree->old_pos.x = tree->pos.x; | |
528 tree->old_pos.y = tree->pos.y; | |
529 } | |
530 | |
531 FOREACH_CHILD(child, tree) | |
532 ShiftTree(child, done); | |
533 } | |
534 | |
535 | |
536 /* ---------------------------------------------------------------------------- | |
537 * | |
538 * AnimateTree() draws the given tree in a series of steps to give the | |
539 * effect of animation from the "old" layout to the "new" layout of the | |
540 * tree. | |
541 * | |
542 * The algorithm used here is not efficient; the entire tree is drawn | |
543 * on each iteration of the animation sequence; it would be more efficient | |
544 * to only redraw what is necessary. However, the method used here takes | |
545 * advantage of existing code without modification. | |
546 * | |
547 * ---------------------------------------------------------------------------- | |
548 */ | |
549 | |
550 void | |
551 AnimateTree(tree) | |
552 Tree *tree; | |
553 { | |
554 int done = FALSE; | |
555 | |
556 AnimationMode = FALSE; | |
557 /* highlight which nodes have to move */ | |
558 BeginFrame(); | |
559 DrawTree(tree, Old); | |
560 EndFrame(); | |
561 Pause(); | |
562 if (PauseAfterStep) | |
563 AnimationStep = ANIMATION_STEP_STEP; | |
564 while (!done) { | |
565 done = TRUE; | |
566 ShiftTree(tree, &done); | |
567 BeginFrame(); | |
568 DrawTree(tree, Old); | |
569 EndFrame(); | |
570 if (PauseAfterStep) | |
571 Pause(); | |
572 } | |
573 if (PauseAfterStep) | |
574 AnimationStep = ANIMATION_STEP; | |
575 AnimationMode = FALSE; | |
576 } | |
577 | |
578 | |
579 /* ---------------------------------------------------------------------------- | |
580 * | |
581 * AnimateZip() generates a sequence of frames that animates the Zip() step. | |
582 * It is similar in logical structure to Zip(). | |
583 * | |
584 * ---------------------------------------------------------------------------- | |
585 */ | |
586 | |
587 void | |
588 AnimateZip(tree) | |
589 Tree *tree; | |
590 { | |
591 Tree *child; | |
592 | |
593 /* show results of Join() step */ | |
594 if (tree->child) { | |
595 BeginFrame(); | |
596 FOREACH_CHILD(child, tree) | |
597 child->split = FALSE; | |
598 DrawTree(TheTree, New); | |
599 DrawTreeContour(tree, New, CONTOUR_COLOR, TRUE, FALSE, FALSE); | |
600 EndFrame(); | |
601 | |
602 StatusMsg("Zip: merge and join contours", FALSE); | |
603 Pause(); | |
604 | |
605 /* show results of AttachParent() step */ | |
606 BeginFrame(); | |
607 DrawTree(TheTree, New); | |
608 DrawTreeContour(tree, New, CONTOUR_COLOR, FALSE, FALSE, FALSE); | |
609 EndFrame(); | |
610 | |
611 StatusMsg("Zip: attach parents", FALSE); | |
612 Pause(); | |
613 } | |
614 | |
615 tree->on_path = FALSE; | |
616 | |
617 if (tree->parent) | |
618 AnimateZip(tree->parent); | |
619 else { | |
620 tree->on_path = FALSE; | |
621 BeginFrame(); | |
622 DrawTree(TheTree, New); | |
623 DrawTreeContour(TheTree, New, CONTOUR_COLOR, FALSE, FALSE, FALSE); | |
624 EndFrame(); | |
625 StatusMsg("Zip: reassemble entire contour", FALSE); | |
626 Pause(); | |
627 } | |
628 } | |
629 | |
630 | |
631 /* ---------------------------------------------------------------------------- | |
632 * | |
633 * CountNodes() returns the number of nodes in the specified tree. | |
634 * Nodes below a node that has been collapsed are ignored. | |
635 * | |
636 * ---------------------------------------------------------------------------- | |
637 */ | |
638 | |
639 int | |
640 CountNodes(tree) | |
641 Tree *tree; | |
642 { | |
643 int num_nodes = 1; /* count root of subtree */ | |
644 Tree *child; | |
645 | |
646 if (!tree->elision) { | |
647 FOREACH_CHILD(child, tree) | |
648 num_nodes += CountNodes(child); | |
649 } | |
650 return (num_nodes); | |
651 } | |
652 | |
653 | |
654 /* ---------------------------------------------------------------------------- | |
655 * | |
656 * CollectNodeRectangles() is a recursive function used by | |
657 * GetSubTreeRectangles() to collect the rectangles of descendant nodes | |
658 * into the pre-allocated storage passed to this function. | |
659 * | |
660 * ---------------------------------------------------------------------------- | |
661 */ | |
662 | |
663 void | |
664 CollectNodeRectangles(node, rectangles, fill) | |
665 Tree *node; | |
666 XRectangle **rectangles; | |
667 int fill; | |
668 { | |
669 Tree *child; | |
670 | |
671 (*rectangles)->x = node->pos.x; | |
672 (*rectangles)->y = node->pos.y; | |
673 if (fill) { | |
674 (*rectangles)->width = node->width + 1; | |
675 (*rectangles)->height = node->height + 1; | |
676 } else { | |
677 (*rectangles)->width = node->width; | |
678 (*rectangles)->height = node->height; | |
679 } | |
680 (*rectangles)++; | |
681 | |
682 if (!node->elision) | |
683 FOREACH_CHILD(child, node) | |
684 CollectNodeRectangles(child, rectangles, fill); | |
685 } | |
686 | |
687 | |
688 /* ---------------------------------------------------------------------------- | |
689 * | |
690 * GetSubTreeRectangles() builds an array of XRectangles that contain | |
691 * all the node rectangles in the tree, except the root node itself. | |
692 * The array is returned in 'rectangles' and the number of rectangles | |
693 * is returned in 'nrectangles.' Storage for the rectangles is allocated | |
694 * in this function. This function is used by PickAction to determine | |
695 * what rectangles need to be dissolved away. 'fill', if True, specifies | |
696 * that the rectangles should be 1 pixel larger in each dimension to | |
697 * compensate for FillRectangle behavior. | |
698 * | |
699 * ---------------------------------------------------------------------------- | |
700 */ | |
701 | |
702 void | |
703 GetSubTreeRectangles(tree, rectangles, nrectangles, fill) | |
704 Tree *tree; | |
705 XRectangle **rectangles; | |
706 int *nrectangles, fill; | |
707 { | |
708 Tree *child; | |
709 XRectangle *crect; /* current rectangle */ | |
710 | |
711 *nrectangles = CountNodes(tree) - 1; /* don't count root node */ | |
712 *rectangles = (XRectangle *) malloc(sizeof(XRectangle) * *nrectangles); | |
713 ASSERT(*rectangles, "could not allocate memory for rectangles"); | |
714 | |
715 crect = *rectangles; | |
716 if (!tree->elision) | |
717 FOREACH_CHILD(child, tree) | |
718 CollectNodeRectangles(child, &crect, fill); | |
719 } | |
720 | |
721 | |
722 /* ---------------------------------------------------------------------------- | |
723 * | |
724 * CollectNodeSegments() is a recursive function used by GetSubTreeSegments() | |
725 * to collect the line segments connecting nodes into the pre-allocated | |
726 * storage passed to this function. | |
727 * | |
728 * ---------------------------------------------------------------------------- | |
729 */ | |
730 | |
731 void | |
732 CollectNodeSegments(node, segments) | |
733 Tree *node; | |
734 XSegment **segments; | |
735 { | |
736 Tree *child; | |
737 | |
738 (*segments)->x1 = node->pos.x - 1; | |
739 (*segments)->y1 = node->pos.y + (node->height / 2), | |
740 (*segments)->x2 = node->parent->pos.x + node->parent->width + 1; | |
741 (*segments)->y2 = node->parent->pos.y + (node->parent->height / 2); | |
742 (*segments)++; | |
743 | |
744 if (!node->elision) | |
745 FOREACH_CHILD(child, node) | |
746 CollectNodeSegments(child, segments); | |
747 } | |
748 | |
749 | |
750 /* ---------------------------------------------------------------------------- | |
751 * | |
752 * GetSubTreeSegments() builds an array of XSegments that contain | |
753 * all the line segments connecting the nodes in the tree. The array is | |
754 * returned in 'segments' and the number of segments is returned in | |
755 * 'nsegments.' Storage for the segments is allocated in this function. | |
756 * This function is used by PickAction to determine what line segments | |
757 * rectangles need to be dissolved away. | |
758 * | |
759 * ---------------------------------------------------------------------------- | |
760 */ | |
761 | |
762 void | |
763 GetSubTreeSegments(tree, segments, nsegments) | |
764 Tree *tree; | |
765 XSegment **segments; | |
766 int *nsegments; | |
767 { | |
768 Tree *child; | |
769 XSegment *cseg; /* current segment */ | |
770 | |
771 *nsegments = CountNodes(tree) - 1; | |
772 *segments = (XSegment *) malloc(sizeof(XSegment) * *nsegments); | |
773 ASSERT(*segments, "could not allocate memory for segments"); | |
774 | |
775 cseg = *segments; | |
776 if (!tree->elision) | |
777 FOREACH_CHILD(child, tree) | |
778 CollectNodeSegments(child, &cseg); | |
779 } | |
780 | |
781 | |
782 /* ---------------------------------------------------------------------------- | |
783 * | |
784 * ComputeSubTreeExtent() computes the extent of a subtree. This is | |
785 * easily computed based on the tree's contour, as in ComputeTreeSize(). | |
786 * This extent is stored in the node, and used by SearchTree for | |
787 * pick-correlation. | |
788 * | |
789 * This function assumes that the given tree has at least one child; do not | |
790 * pass a leaf node to this function. | |
791 * | |
792 * ---------------------------------------------------------------------------- | |
793 */ | |
794 | |
795 void | |
796 ComputeSubTreeExtent(tree) | |
797 Tree *tree; | |
798 { | |
799 int width, height; | |
800 int x_offset, y_offset; | |
801 | |
802 ComputeTreeSize(tree, &width, &height, &x_offset, &y_offset); | |
803 tree->subextent.pos.x = tree->child->pos.x - tree->child->border; | |
804 tree->subextent.pos.y = tree->pos.y - y_offset; | |
805 tree->subextent.width = width - (tree->child->pos.x - tree->pos.x) - 1; | |
806 tree->subextent.height = height - 1; | |
807 } | |
808 | |
809 | |
810 /* ---------------------------------------------------------------------------- | |
811 * | |
812 * SearchTree() determines if a node's rectangular region encloses the | |
813 * specified point in (x,y). Rather than using a brute-force search | |
814 * through all node rectangles of a given tree, the subtree extents | |
815 * are used in a recursive fashion to drive the search as long as the | |
816 * given point is enclosed in an extent. In the worst case, the search | |
817 * time would be on the order of a brute-force search, but with complex | |
818 * trees, this method reduces the number of visits. | |
819 * | |
820 * The extent of a subtree is computed by ComputeSubTreeExtent() and is | |
821 * stored in each node of the tree. | |
822 * | |
823 * ---------------------------------------------------------------------------- | |
824 */ | |
825 | |
826 int | |
827 SearchTree(tree, x, y, node) | |
828 Tree *tree, **node; | |
829 int x, y; | |
830 { | |
831 Tree *child; | |
832 | |
833 if (tree == NULL) | |
834 return (FALSE); | |
835 | |
836 if (PT_IN_RECT(x, y, tree->pos.x, tree->pos.y, | |
837 tree->pos.x + tree->width, | |
838 tree->pos.y + tree->height)) { | |
839 *node = tree; | |
840 return (TRUE); | |
841 } | |
842 if (tree->child && (PT_IN_EXTENT(x, y, tree->subextent))) | |
843 FOREACH_CHILD(child, tree) { | |
844 if (SearchTree(child, x, y, node)) | |
845 return (TRUE); | |
846 } | |
847 return (FALSE); | |
848 } | |
849 | |
850 | |
851 /* ---------------------------------------------------------------------------- | |
852 * | |
853 * ExposeHandler() handles expose events in the TreeDrawingArea. This | |
854 * function is not intelligent; it just redraws the entire contents. | |
855 * | |
856 * ---------------------------------------------------------------------------- | |
857 */ | |
858 | |
859 void | |
860 ExposeHandler(w, client_data, event) | |
861 Widget w; | |
862 caddr_t client_data; | |
863 XExposeEvent *event; | |
864 { | |
865 | |
866 if (event->count == 0) { | |
867 BeginFrame(); | |
868 SetContours(TreeShowContourOption); | |
869 DrawTree(TheTree, New); | |
870 EndFrame(); | |
871 } | |
872 } | |
873 | |
874 | |
875 /* ---------------------------------------------------------------------------- | |
876 * | |
877 * ExpandCollapseNode is called to expand or collapse a node in the tree. | |
878 * | |
879 * ---------------------------------------------------------------------------- | |
880 */ | |
881 | |
882 void | |
883 ExpandCollapseNode(node) | |
884 Tree *node; | |
885 { | |
886 int width, height; | |
887 int old_width, old_height; | |
888 int x_offset, y_offset; | |
889 XRectangle *rectangles; | |
890 XSegment *segments; | |
891 int nrectangles, nsegments; | |
892 int expand = FALSE; | |
893 Widget w = TreeDrawingArea; | |
894 | |
895 StatusMsg("", TRUE); | |
896 | |
897 /* hilite node so that we know where we are */ | |
898 /* DrawTree will hilite it as a side effect */ | |
899 if (TreeShowSteps) | |
900 node->on_path = TRUE; | |
901 | |
902 /* erase the contour before changing in the tree */ | |
903 if ((TreeShowContourOption != NoContours) || TreeShowSteps) { | |
904 BeginFrame(); | |
905 DrawTree(TheTree, New); | |
906 EndFrame(); | |
907 } | |
908 | |
909 sprintf(strbuf, "Node `%s' selected for %s", node->label.text, | |
910 node->elision ? "expansion" : "collapse"); | |
911 StatusMsg(strbuf, FALSE); | |
912 Pause(); | |
913 | |
914 if (node->parent) | |
915 Unzip(node->parent); | |
916 else { | |
917 StatusMsg("Show entire contour", FALSE); | |
918 if (TreeShowSteps) { | |
919 BeginFrame(); | |
920 DrawTreeContour(TheTree, New, CONTOUR_COLOR, FALSE, FALSE, FALSE); | |
921 DrawTree(TheTree, New); | |
922 EndFrame(); | |
923 Pause(); | |
924 } | |
925 } | |
926 | |
927 /* are we collapsing a subtree? */ | |
928 if (!node->elision) { | |
929 StatusMsg("Collapse subtree", FALSE); | |
930 GetSubTreeRectangles(node, &rectangles, &nrectangles, TRUE); | |
931 GetSubTreeSegments(node, &segments, &nsegments); | |
932 DissolveTree(XtDisplay(w), XtWindow(w), | |
933 rectangles, nrectangles, | |
934 segments, nsegments, TRUE); | |
935 free(rectangles); | |
936 free(segments); | |
937 Pause(); | |
938 | |
939 StatusMsg("Replace subtree contour with leaf contour", FALSE); | |
940 node->elision = TRUE; | |
941 if (TreeShowSteps) | |
942 node->split = TRUE; /* turned off in AnimateZip */ | |
943 node->old_contour = node->contour; | |
944 node->width += ELISION_WIDTH; | |
945 LayoutLeaf(node); | |
946 BeginFrame(); | |
947 SetContours(TreeShowContourOption); | |
948 DrawTree(TheTree, New); | |
949 EndFrame(); | |
950 Pause(); | |
951 } else { | |
952 StatusMsg("Replace leaf contour with old subtree contour", FALSE); | |
953 if (TreeShowSteps) | |
954 node->split = TRUE; /* turned off in AnimateZip */ | |
955 RuboutLeaf(node); | |
956 node->contour = node->old_contour; | |
957 expand = TRUE; | |
958 } | |
959 | |
960 if (node->parent) | |
961 Zip(node->parent); | |
962 | |
963 ComputeTreeSize(TheTree, &width, &height, &x_offset, &y_offset); | |
964 PetrifyTree(TheTree, x_offset + MAT_SIZE, y_offset + MAT_SIZE); | |
965 GetDrawingSize(&old_width, &old_height); | |
966 | |
967 if (expand) { | |
968 SetDrawingSize(width + (2 * MAT_SIZE), height + (2 * MAT_SIZE)); | |
969 BeginFrame(); | |
970 DrawTree(TheTree, Old); | |
971 EndFrame(); | |
972 Pause(); | |
973 StatusMsg("Move tree to new configuration", FALSE); | |
974 AnimateTree(TheTree); | |
975 } else { | |
976 /* we are shrinking or staying the same */ | |
977 StatusMsg("Move tree to new configuration", FALSE); | |
978 AnimateTree(TheTree); | |
979 SetDrawingSize(width + (2 * MAT_SIZE), height + (2 * MAT_SIZE)); | |
980 } | |
981 | |
982 if (expand) { | |
983 StatusMsg("Expand subtree", FALSE); | |
984 node->elision = FALSE; | |
985 | |
986 /* erase elision marker */ | |
987 XSetFunction(TreeDrawingAreaDB->display, TreeDrawingAreaDB->gc, | |
988 GXclear); | |
989 XFillRectangle(XtDisplay(w), XtWindow(w), TreeDrawingAreaDB->gc, | |
990 node->pos.x + node->width - ELISION_WIDTH + 1, | |
991 node->pos.y, ELISION_WIDTH, node->height + 1); | |
992 XSetFunction(TreeDrawingAreaDB->display, TreeDrawingAreaDB->gc, | |
993 GXcopy); | |
994 node->width -= ELISION_WIDTH; | |
995 | |
996 GetSubTreeRectangles(node, &rectangles, &nrectangles, FALSE); | |
997 GetSubTreeSegments(node, &segments, &nsegments); | |
998 /* dissolve the tree back in */ | |
999 DissolveTree(XtDisplay(w), XtWindow(w), | |
1000 rectangles, nrectangles, | |
1001 segments, nsegments, FALSE); | |
1002 free(rectangles); | |
1003 free(segments); | |
1004 | |
1005 /* draw text of nodes */ | |
1006 BeginFrame(); | |
1007 SetContours(TreeShowContourOption); | |
1008 DrawTree(TheTree, New); | |
1009 EndFrame(); | |
1010 Pause(); | |
1011 } | |
1012 | |
1013 if (TreeShowSteps) { | |
1014 node->on_path = FALSE; | |
1015 if (node->parent) | |
1016 AnimateZip(node->parent); | |
1017 else | |
1018 node->split = FALSE; | |
1019 } | |
1020 | |
1021 /* BUG: the display isn't properly updated here! */ | |
1022 /* There should probably be some code here that | |
1023 clears the tree below the node currently being | |
1024 collapsed or expanded. Hack added 20.03.95 (torgeir@ii.uib.no). | |
1025 I'll try to fix this later. */ | |
1026 | |
1027 XClearArea(TreeDisplay, XtWindow(TreeDrawingArea), 0, 0, 0, 0, FALSE); | |
1028 | |
1029 BeginFrame(); | |
1030 SetContours(TreeShowContourOption); | |
1031 DrawTree(TheTree, New); | |
1032 EndFrame(); | |
1033 | |
1034 StatusMsg("Ready", TRUE); | |
1035 } | |
1036 | |
1037 /* ---------------------------------------------------------------------------- | |
1038 * | |
1039 * InsertNode() handles the task of inserting a new node in the tree, | |
1040 * at the given position with respect to 'base_node'. When 'node_pos' is | |
1041 * either Before or After, it is assumed that 'base_node' has a parent. | |
1042 * | |
1043 * ---------------------------------------------------------------------------- | |
1044 */ | |
1045 | |
1046 void | |
1047 InsertNode(base_node, node_pos, new_node_text) | |
1048 Tree *base_node; | |
1049 NodePosition node_pos; | |
1050 char *new_node_text; | |
1051 { | |
1052 Tree *new_node; | |
1053 Tree *parent; | |
1054 Tree *sibling = NULL; | |
1055 Tree *child; | |
1056 | |
1057 int width, height; | |
1058 int x_offset, y_offset; | |
1059 | |
1060 StatusMsg("", TRUE); | |
1061 | |
1062 new_node = MakeNode(); /* should check for memory failure */ | |
1063 SetNodeLabel(new_node, new_node_text); | |
1064 LayoutLeaf(new_node); | |
1065 | |
1066 /* figure out parent & sibling */ | |
1067 if (node_pos == Child) { | |
1068 parent = base_node; | |
1069 /* find last child, if one exists */ | |
1070 FOREACH_CHILD(child, parent) | |
1071 sibling = child; | |
1072 } else if (node_pos == After) { | |
1073 parent = base_node->parent; | |
1074 sibling = base_node; | |
1075 } else if (node_pos == Before) { | |
1076 parent = base_node->parent; | |
1077 FOREACH_CHILD(child, parent) | |
1078 if (child->sibling == base_node) { | |
1079 sibling = child; | |
1080 break; | |
1081 } | |
1082 } | |
1083 | |
1084 if (TreeShowSteps) | |
1085 parent->on_path = TRUE; | |
1086 | |
1087 if ((TreeShowContourOption != NoContours) || | |
1088 TreeShowSteps) { | |
1089 BeginFrame(); | |
1090 DrawTree(TheTree, New); | |
1091 EndFrame(); | |
1092 } | |
1093 | |
1094 sprintf(strbuf, "Inserting `%s' as child of node `%s'", | |
1095 new_node_text, parent->label.text); | |
1096 StatusMsg(strbuf, FALSE); | |
1097 Pause(); | |
1098 | |
1099 /* erase the contour before changing in the tree */ | |
1100 | |
1101 Insert(parent, new_node, sibling); | |
1102 | |
1103 ComputeTreeSize(TheTree, &width, &height, &x_offset, &y_offset); | |
1104 PetrifyTree(TheTree, x_offset + MAT_SIZE, y_offset + MAT_SIZE); | |
1105 | |
1106 if (sibling) | |
1107 new_node->old_pos = sibling->old_pos; | |
1108 else if (new_node->sibling) | |
1109 new_node->old_pos = new_node->sibling->old_pos; | |
1110 else { | |
1111 new_node->old_pos.x = new_node->pos.x; | |
1112 new_node->old_pos.y = parent->old_pos.y; | |
1113 } | |
1114 | |
1115 if (TreeShowSteps) | |
1116 new_node->split = TRUE; | |
1117 | |
1118 SetDrawingSize(width + (2 * MAT_SIZE), height + (2 * MAT_SIZE)); | |
1119 BeginFrame(); | |
1120 DrawTree(TheTree, Old); | |
1121 EndFrame(); | |
1122 StatusMsg("Insert: add new node and contour", FALSE); | |
1123 Pause(); | |
1124 | |
1125 StatusMsg("Move tree to new configuration", FALSE); | |
1126 AnimateTree(TheTree); | |
1127 | |
1128 if (TreeShowSteps) { | |
1129 if (parent) | |
1130 AnimateZip(parent); | |
1131 } | |
1132 | |
1133 BeginFrame(); | |
1134 SetContours(TreeShowContourOption); | |
1135 DrawTree(TheTree, New); | |
1136 EndFrame(); | |
1137 | |
1138 StatusMsg("Ready", TRUE); | |
1139 } | |
1140 | |
1141 /* ---------------------------------------------------------------------------- | |
1142 * | |
1143 * DeleteNode() handles the task of deleting a given node in the tree. | |
1144 * | |
1145 * ---------------------------------------------------------------------------- | |
1146 */ | |
1147 | |
1148 void | |
1149 DeleteNode(node) | |
1150 Tree *node; | |
1151 { | |
1152 Tree *parent; | |
1153 | |
1154 XRectangle *rectangles; | |
1155 XSegment *segments; | |
1156 int nrectangles, nsegments; | |
1157 Widget w = TreeDrawingArea; | |
1158 int width, height; | |
1159 int x_offset, y_offset; | |
1160 | |
1161 StatusMsg("", TRUE); | |
1162 | |
1163 if (TreeShowSteps) | |
1164 node->on_path = TRUE; | |
1165 | |
1166 /* erase the contour before changing in the tree */ | |
1167 if ((TreeShowContourOption != NoContours) || | |
1168 TreeShowSteps) { | |
1169 BeginFrame(); | |
1170 DrawTree(TheTree, New); | |
1171 EndFrame(); | |
1172 } | |
1173 | |
1174 sprintf(strbuf, "Node `%s' selected for deletion", node->label.text); | |
1175 StatusMsg(strbuf, FALSE); | |
1176 Pause(); | |
1177 | |
1178 parent = node->parent; | |
1179 | |
1180 if (parent) | |
1181 Unzip(parent); | |
1182 else | |
1183 TheTree = NULL; /* delete root of tree */ | |
1184 | |
1185 /* fade out deleted subtree */ | |
1186 StatusMsg("Delete subtree", FALSE); | |
1187 GetSubTreeRectangles(node, &rectangles, &nrectangles, TRUE); | |
1188 GetSubTreeSegments(node, &segments, &nsegments); | |
1189 DissolveTree(XtDisplay(w), XtWindow(w), | |
1190 rectangles, nrectangles, | |
1191 segments, nsegments, TRUE); | |
1192 free(rectangles); | |
1193 free(segments); | |
1194 | |
1195 Delete(node); | |
1196 | |
1197 BeginFrame(); | |
1198 if (TheTree) | |
1199 DrawTree(TheTree, New); | |
1200 EndFrame(); | |
1201 Pause(); | |
1202 | |
1203 if (parent) | |
1204 Zip(parent); | |
1205 | |
1206 if (TheTree) { | |
1207 ComputeTreeSize(TheTree, &width, &height, &x_offset, &y_offset); | |
1208 PetrifyTree(TheTree, x_offset + MAT_SIZE, y_offset + MAT_SIZE); | |
1209 StatusMsg("Move tree to new configuration", FALSE); | |
1210 AnimateTree(TheTree); | |
1211 SetDrawingSize(width + (2 * MAT_SIZE), height + (2 * MAT_SIZE)); | |
1212 Pause(); | |
1213 | |
1214 if (TreeShowSteps) { | |
1215 if (parent) | |
1216 AnimateZip(parent); | |
1217 } | |
1218 | |
1219 BeginFrame(); | |
1220 SetContours(TreeShowContourOption); | |
1221 DrawTree(TheTree, New); | |
1222 EndFrame(); | |
1223 | |
1224 } | |
1225 | |
1226 StatusMsg("Ready", TRUE); | |
1227 } | |
1228 | |
1229 | |
1230 /* ---------------------------------------------------------------------------- | |
1231 * | |
1232 * ResetLabels() is called when the TreeAlignNodes mode is changed. | |
1233 * When TreeParentDistance changes, the node width changes, so this | |
1234 * function forces each node's width to be recomputed. | |
1235 * | |
1236 * ---------------------------------------------------------------------------- | |
1237 */ | |
1238 | |
1239 ResetLabels(tree) | |
1240 Tree *tree; | |
1241 { | |
1242 Tree *child; | |
1243 | |
1244 SetNodeLabel(tree, tree->label.text); | |
1245 FOREACH_CHILD(child, tree) | |
1246 ResetLabels(child); | |
1247 } | |
1248 | |
1249 | |
1250 /* ---------------------------------------------------------------------------- | |
1251 * | |
1252 * SetupTree() handles the task of setting up the specified tree in | |
1253 * the drawing area. | |
1254 * | |
1255 * ---------------------------------------------------------------------------- | |
1256 */ | |
1257 | |
1258 void | |
1259 SetupTree(tree) | |
1260 Tree *tree; | |
1261 { | |
1262 int width, height; | |
1263 int x_offset, y_offset; | |
1264 | |
1265 LayoutTree(tree); | |
1266 ComputeTreeSize(tree, &width, &height, &x_offset, &y_offset); | |
1267 PetrifyTree(tree, x_offset + MAT_SIZE, y_offset + MAT_SIZE); | |
1268 SetDrawingTree(tree); | |
1269 SetDrawingSize(width + (2 * MAT_SIZE), height + (2 * MAT_SIZE)); | |
1270 BeginFrame(); | |
1271 SetContours(TreeShowContourOption); | |
1272 DrawTree(tree, New); | |
1273 EndFrame(); | |
1274 } | |
1275 | |
1276 | |
1277 | |
1278 | |
1279 | |
1280 |