dictionary_lite --> dictionary_hash
[dyninst.git] / paradyn / src / UIthread / shg.C
1 /*
2  * Copyright (c) 1996 Barton P. Miller
3  * 
4  * We provide the Paradyn Parallel Performance Tools (below
5  * described as Paradyn") on an AS IS basis, and do not warrant its
6  * validity or performance.  We reserve the right to update, modify,
7  * or discontinue this software at any time.  We shall have no
8  * obligation to supply such updates or modifications or any other
9  * form of support to you.
10  * 
11  * This license is for research uses.  For such uses, there is no
12  * charge. We define "research use" to mean you may freely use it
13  * inside your organization for whatever purposes you see fit. But you
14  * may not re-distribute Paradyn or parts of Paradyn, in any form
15  * source or binary (including derivatives), electronic or otherwise,
16  * to any other organization or entity without our permission.
17  * 
18  * (for other uses, please contact us at paradyn@cs.wisc.edu)
19  * 
20  * All warranties, including without limitation, any warranty of
21  * merchantability or fitness for a particular purpose, are hereby
22  * excluded.
23  * 
24  * By your use of Paradyn, you understand and agree that we (or any
25  * other person or entity with proprietary rights in Paradyn) are
26  * under no obligation to provide either maintenance services,
27  * update services, notices of latent defects, or correction of
28  * defects for Paradyn.
29  * 
30  * Even if advised of the possibility of such damages, under no
31  * circumstances shall we (or any other person or entity with
32  * proprietary rights in the software licensed hereunder) be liable
33  * to you or any third party for direct, indirect, or consequential
34  * damages of any character regardless of type of action, including,
35  * without limitation, loss of profits, loss of use, loss of good
36  * will, or computer failure or malfunction.  You agree to indemnify
37  * us (and any other person or entity with proprietary rights in the
38  * software licensed hereunder) for any and all liability it may
39  * incur to third parties resulting from your use of Paradyn.
40  */
41
42 // shg.C
43 // new search history graph user interface, along the lines
44 // of the new where axis user interface
45 // Ariel Tamches
46
47 /* $Log: shg.C,v $
48 /* Revision 1.27  1997/10/28 20:36:16  tamches
49 /* dictionary_lite --> dictionary_hash
50 /*
51  * Revision 1.26  1997/09/24 19:21:56  tamches
52  * XFontStruct --> Tk_Font
53  * use of Tk_GetFontMetrics
54  *
55  * Revision 1.25  1996/11/26 16:06:56  naim
56  * Fixing asserts - naim
57  *
58  * Revision 1.24  1996/08/16 21:07:09  tamches
59  * updated copyright for release 1.1
60  *
61  * Revision 1.23  1996/05/01 20:55:55  tamches
62  * added inactivateAll
63  * changed configNode implementation
64  *
65  * Revision 1.22  1996/05/01 14:07:57  naim
66  * Multiples changes in UI to make call to requestNodeInfoCallback async.
67  * (UI<->PC) - naim
68  *
69  * Revision 1.21  1996/04/29  21:29:58  tamches
70  * fixed a bug in configNode which could fail to properly hide the root node
71  * when set to false with hideFalseNodes.
72  *
73  * Revision 1.20  1996/04/18 23:26:31  tamches
74  * fixed an assert failure that was happening when hideFalseNodes was on
75  * and when a node changed from true to false
76  *
77  * Revision 1.19  1996/04/16 18:37:31  karavan
78  * fine-tunification of UI-PC batching code, plus addification of some
79  * Ari-like verbification commentification.
80  *
81  * Revision 1.18  1996/04/13 04:39:46  karavan
82  * better implementation of batching for edge requests
83  *
84  * Revision 1.17  1996/04/09 19:25:10  karavan
85  * added batch mode to cut down on shg redraw time.
86  *
87  * Revision 1.16  1996/04/01 21:19:22  tamches
88  * changeHiddenNodes now checks for a NULL rootPtr
89  *
90  * Revision 1.15  1996/03/29 20:51:12  tamches
91  * on configNode, check for change in hide-ness is moved before check for
92  * true-ness; avoids an assertion failure when expanding a hidden node at times.
93  *
94  */
95
96 #include <assert.h>
97 #include "tkTools.h"
98 #include "shg.h"
99
100 #ifdef PARADYN
101 // the shg test program doesn't need this:
102 #include "performanceConsultant.thread.h" // for struct shg_node_info
103 #include "performanceConsultant.thread.CLNT.h" // for class performanceConsultantUser
104 #endif
105
106 // Define static member vrbles:
107 Tk_Font shg::theRootItemFontStruct, shg::theRootItemShadowFontStruct;
108 Tk_Font shg::theListboxItemFontStruct, shg::theListboxItemShadowFontStruct;
109
110 vector<Tk_3DBorder> shg::rootItemTk3DBordersByStyle; // init to empty vector
111 vector<Tk_3DBorder> shg::listboxItemTk3DBordersByStyle; // inits to empty vector
112
113 GC shg::rootItemInactiveTextGC, shg::rootItemActiveTextGC;
114 GC shg::rootItemInactiveShadowTextGC, shg::rootItemActiveShadowTextGC;
115   
116 GC shg::listboxInactiveItemGC, shg::listboxActiveItemGC;
117 GC shg::listboxInactiveShadowItemGC, shg::listboxActiveShadowItemGC;
118
119 GC shg::whyRefinementRayGC;
120 GC shg::whereRefinementRayGC;
121 GC shg::listboxRayGC;
122
123 int shg::listboxBorderPix = 3;
124 int shg::listboxScrollBarWidth = 16;
125
126 #ifdef PARADYN
127 extern performanceConsultantUser *perfConsult;
128 #endif
129
130 void shg::initializeStaticsIfNeeded() {
131    if (rootItemTk3DBordersByStyle.size() > 0)
132       return;
133
134    theRootItemFontStruct = consts.rootTextFontStruct;
135    theRootItemShadowFontStruct = theShgConsts.rootItemItalicFontStruct;
136
137    theListboxItemFontStruct = consts.listboxFontStruct;
138    theListboxItemShadowFontStruct = theShgConsts.listboxItemItalicFontStruct;
139
140    rootItemTk3DBordersByStyle = theShgConsts.rootItemTk3DBordersByStyle;
141    listboxItemTk3DBordersByStyle = theShgConsts.listboxItemTk3DBordersByStyle;
142
143    rootItemInactiveTextGC = theShgConsts.rootItemInactiveTextGC;
144    rootItemActiveTextGC = theShgConsts.rootItemActiveTextGC;
145    rootItemInactiveShadowTextGC = theShgConsts.rootItemInactiveShadowTextGC;
146    rootItemActiveShadowTextGC = theShgConsts.rootItemActiveShadowTextGC;
147
148    listboxInactiveItemGC = theShgConsts.listboxItemInactiveTextGC;
149    listboxActiveItemGC = theShgConsts.listboxItemActiveTextGC;
150    listboxInactiveShadowItemGC = theShgConsts.listboxItemInactiveShadowTextGC;
151    listboxActiveShadowItemGC = theShgConsts.listboxItemActiveShadowTextGC;
152
153    whyRefinementRayGC = theShgConsts.whyRefinementRayGC;
154    whereRefinementRayGC = theShgConsts.whereRefinementRayGC;
155    listboxRayGC = consts.listboxRayGC;
156 }
157
158 shg::shg(int iPhaseId, Tcl_Interp *iInterp, Tk_Window theTkWindow,
159          const string &iHorizSBName, const string &iVertSBName,
160          const string &iCurrItemLabelName,
161          bool iHideTrue, bool iHideFalse, bool iHideUnknown, bool iHideNever,
162          bool iHideActive, bool iHideInactive, bool iHideShadow) :
163             hash(&hashFunc),
164             hash2(&hashFunc2),
165             shadowNodeHash(&hashFuncShadow),
166             consts(iInterp, theTkWindow),
167             theShgConsts(iInterp, theTkWindow),
168             thePhaseId(iPhaseId),
169             horizSBName(iHorizSBName),
170             vertSBName(iVertSBName),
171             currItemLabelName(iCurrItemLabelName),
172             lastItemUnderMousePath(0, 0, consts, NULL, 0, 0) // yuck
173 {
174    initializeStaticsIfNeeded();
175
176    interp = iInterp;
177    horizScrollBarOffset = vertScrollBarOffset = 0;
178    
179    rootPtr = NULL;
180
181    rethink_nominal_centerx(); // will bomb if rootPtr is undefined, so we defer this...
182
183    lastItemUnderMouseX = lastItemUnderMouseY = -1;
184
185    hideTrueNodes = iHideTrue;
186    hideFalseNodes = iHideFalse;
187    hideUnknownNodes = iHideUnknown;
188    hideNeverSeenNodes = iHideNever;
189    hideActiveNodes = iHideActive;
190    hideInactiveNodes = iHideInactive;
191    hideShadowNodes = iHideShadow;
192 }
193
194 bool shg::state2hidden(shgRootNode::evaluationState es,
195                        bool active, bool shadow) const {
196    if (active && hideActiveNodes) return true;
197    if (!active && hideInactiveNodes) return true;
198
199    if (shadow && hideShadowNodes) return true;
200
201    if (es==shgRootNode::es_true && hideTrueNodes) return true;
202    else if (es==shgRootNode::es_false && hideFalseNodes) return true;
203    else if (es==shgRootNode::es_unknown && hideUnknownNodes) return true;
204    else if (es==shgRootNode::es_never && hideNeverSeenNodes) return true;
205
206    return false;
207 }
208
209 void shg::rethink_nominal_centerx() {
210    // similar to where axis
211    const int horizSpaceUsedByAll = (rootPtr==NULL) ? 0 : rootPtr->entire_width(consts);
212
213    if (horizSpaceUsedByAll <= Tk_Width(consts.theTkWindow))
214       nominal_centerx = Tk_Width(consts.theTkWindow) / 2;
215    else
216       nominal_centerx = horizSpaceUsedByAll / 2;
217 }
218
219 void shg::resizeScrollbars() {
220    // same as where axis
221    const int entire_width = rootPtr==NULL ? 0 : rootPtr->entire_width(consts);
222    const int entire_height = rootPtr==NULL ? 0 : rootPtr->entire_height(consts);
223
224    string commandStr = string("resize1Scrollbar ") + horizSBName + " " +
225                        string(entire_width) + " " +
226                        string(Tk_Width(consts.theTkWindow));
227    myTclEval(interp, commandStr);
228
229    commandStr = string("resize1Scrollbar ") + vertSBName + " " +
230                 string(entire_height) + " " +
231                 string(Tk_Height(consts.theTkWindow));
232    myTclEval(interp, commandStr);
233 }
234
235 bool shg::set_scrollbars(int absolute_x, int relative_x,
236                          int absolute_y, int relative_y,
237                          bool warpPointer) {
238    // Sets the scrollbars s.t. (absolute_x, absolute_y) will appear
239    // at window (relative) location (relative_x, relative_y).
240    // May need to take into account the current scrollbar setting...
241
242    bool anyChanges = true;
243    const int entire_width = rootPtr==NULL ? 0 : rootPtr->entire_width(consts);
244    const int entire_height = rootPtr==NULL ? 0 : rootPtr->entire_height(consts);
245
246    horizScrollBarOffset = -set_scrollbar(interp, horizSBName,
247                                          entire_width,
248                                          absolute_x, relative_x);
249       // relative_x will be updated
250
251    vertScrollBarOffset = -set_scrollbar(interp, vertSBName,
252                                         entire_height,
253                                         absolute_y, relative_y);
254
255    if (warpPointer)
256       XWarpPointer(Tk_Display(consts.theTkWindow),
257                    Tk_WindowId(consts.theTkWindow), // src win
258                    Tk_WindowId(consts.theTkWindow), // dest win
259                    0, 0, // src x,y
260                    0, 0, // src height, width
261                    relative_x, relative_y
262                    );
263
264    return anyChanges;
265 }
266
267 whereNodeGraphicalPath<shgRootNode> shg::point2path(int x, int y) const {
268    const int overallWindowBorderPix = 0; // prob should be a static vrble
269    const int root_centerx = nominal_centerx + horizScrollBarOffset;
270       // relative (not absolute) coord.  note: horizScrollBarOffset <= 0
271    const int root_topy = overallWindowBorderPix + vertScrollBarOffset;
272       // relative (not absolute) coord.  note: vertScrollBarOffset <= 0
273
274    return whereNodeGraphicalPath<shgRootNode>(x, y, consts, rootPtr,
275                                               root_centerx, root_topy);
276 }
277
278 bool shg::adjustHorizSBOffset(float newFirstFrac) {
279    // Does not redraw; returns true iff any changes were made
280    newFirstFrac = moveScrollBar(interp, horizSBName, newFirstFrac);
281
282    int widthOfAll = rootPtr==NULL ? 0 : rootPtr->entire_width(consts);
283    int oldHorizSBOffset = horizScrollBarOffset;
284    horizScrollBarOffset = -(int)(newFirstFrac * widthOfAll);
285       // note: always <= 0
286    return (horizScrollBarOffset != oldHorizSBOffset);
287 }
288
289 bool shg::adjustHorizSBOffsetFromDeltaPix(int deltapix) {
290    // does not redraw; returns true iff any changes.
291    const int widthOfAll = rootPtr==NULL ? 0 : rootPtr->entire_width(consts);
292    float newFirstFrac = (float)(-horizScrollBarOffset + deltapix) / widthOfAll;
293    return adjustHorizSBOffset(newFirstFrac);
294 }
295
296 bool shg::adjustHorizSBOffset() {
297    // does not redraw; returns true iff any changes
298    float firstFrac, lastFrac;
299    getScrollBarValues(interp, horizSBName, firstFrac, lastFrac);
300    return adjustHorizSBOffset(firstFrac);
301 }
302
303 bool shg::adjustVertSBOffset(float newFirstFrac) {
304    // does not redraw; returns true iff any changes.
305    newFirstFrac = moveScrollBar(interp, vertSBName, newFirstFrac);
306
307    int heightOfEverything = rootPtr==NULL ? 0 : rootPtr->entire_height(consts);
308    int oldVertScrollBarOffset = vertScrollBarOffset;
309    vertScrollBarOffset = -(int)(newFirstFrac * heightOfEverything);
310       // note: always <= 0
311    return (vertScrollBarOffset != oldVertScrollBarOffset);
312 }
313
314 bool shg::adjustVertSBOffsetFromDeltaPix(int deltapix) {
315    // does not redraw.  Returns true iff any changes were made.
316
317    const int heightOfEverything = rootPtr==NULL ? 0 : rootPtr->entire_height(consts);
318    float newFirst = (float)(-vertScrollBarOffset + deltapix) / heightOfEverything;
319    return adjustVertSBOffset(newFirst);
320 }
321
322 bool shg::adjustVertSBOffset() {
323    // Does not redraw.  Obtains PixFirst from actual tk scrollbar.
324    float first, last; // fractions (0.0 to 1.0)
325    getScrollBarValues(interp, vertSBName, first, last);
326
327    return adjustVertSBOffset(first);
328 }
329
330 void shg::draw(bool doubleBuffer, bool isXsynchOn) const {
331    // same as where axis
332    const int overallWindowBorderPix = 0;
333    Drawable theDrawable = (doubleBuffer && !isXsynchOn) ? consts.offscreenPixmap :
334                                                        Tk_WindowId(consts.theTkWindow);
335
336    if (doubleBuffer || isXsynchOn)
337       // clear the destination drawable before drawing onto it
338       XFillRectangle(consts.display,
339                      theDrawable,
340                      consts.erasingGC,
341                      0, // x-offset relative to drawable
342                      0, // y-offset relative to drawable
343                      Tk_Width(consts.theTkWindow),
344                      Tk_Height(consts.theTkWindow)
345                      );
346
347    if (rootPtr!=NULL)
348      rootPtr->draw(consts.theTkWindow, consts, theDrawable,
349                    nominal_centerx + horizScrollBarOffset,
350                       // relative (not absolute) coord
351                    overallWindowBorderPix + vertScrollBarOffset,
352                       // relative (not absolute) coord
353                    false, // not root only
354                    false // not listbox only
355                    );
356
357    if (doubleBuffer && !isXsynchOn)
358       // copy from offscreen pixmap onto the 'real' window
359       XCopyArea(consts.display,
360                 theDrawable, // source pixmap
361                 Tk_WindowId(consts.theTkWindow), // dest pixmap
362                 consts.erasingGC, // ?????
363                 0, 0, // source x,y pix
364                 Tk_Width(consts.theTkWindow),
365                 Tk_Height(consts.theTkWindow),
366                 0, 0 // dest x,y offset pix
367                 );
368 }
369
370 void shg::resize(bool currentlyDisplayedAbstraction) {
371    // same as where axis
372    const int newWindowWidth = Tk_Width(consts.theTkWindow);
373 //   const int newWindowHeight = Tk_Height(consts.theTkWindow); [not used]
374
375    consts.resize(); // reallocates offscreen pixmap; rethinks
376                     // 'listboxHeightWhereSBappears'
377
378    // Loop through EVERY listbox (gasp) and rethink whether or not it needs
379    // a scrollbar and (possibly) change listBoxActualHeight...
380    // _Must_ go _after_ the consts.resize()
381    if (rootPtr)
382       (void)rootPtr->rethinkListboxAfterResize(consts);
383
384    // Now the more conventional resize code:
385    if (rootPtr)
386       rootPtr->rethinkAfterResize(consts, newWindowWidth);
387    rethink_nominal_centerx(); // must go _after_ the rootPtr->rethinkAfterResize()
388
389    if (currentlyDisplayedAbstraction) {
390       // We have changed axis width and/or height.  Let's inform tcl, so it may
391       // rethink the scrollbar ranges.
392       resizeScrollbars();
393
394       // Now, let's update our own stored horiz & vert scrollbar offset values
395       adjustHorizSBOffset(); // obtain FirstPix from the actual tk scrollbar
396       adjustVertSBOffset (); // obtain FirstPix from the actual tk scrollbar
397    }
398 }
399
400 void shg::
401 processNonSliderButtonPress(whereNodeGraphicalPath<shgRootNode> &thePath) {
402    // same as where axis
403    nonSliderButtonCurrentlyPressed = true;
404    nonSliderButtonPressRegion = thePath.whatDoesPathEndIn();
405    nonSliderCurrentSubtree = thePath.getLastPathNode(rootPtr);
406    nonSliderSubtreeCenter = thePath.get_endpath_centerx();
407    nonSliderSubtreeTop = thePath.get_endpath_topy();
408
409    Tk_CreateEventHandler(consts.theTkWindow, ButtonReleaseMask,
410                          nonSliderButtonRelease, this);
411
412    nonSliderButtonAutoRepeatCallback(this);
413 }
414
415 void shg::nonSliderButtonAutoRepeatCallback(ClientData cd) {
416    // same as where axis
417    // If the mouse button has been released, do NOT re-invoke the timer.
418    shg *pthis = (shg *)cd;
419    if (!pthis->nonSliderButtonCurrentlyPressed)
420       return;
421
422    const int listboxLeft = pthis->nonSliderSubtreeCenter -
423                            pthis->nonSliderCurrentSubtree->
424                              horiz_pix_everything_below_root(pthis->consts) / 2;
425    const int listboxTop = pthis->nonSliderSubtreeTop +
426                           pthis->nonSliderCurrentSubtree->getNodeData().
427                                getHeightAsRoot() +
428                           pthis->consts.vertPixParent2ChildTop;
429
430    const int listboxActualDataPix = pthis->nonSliderCurrentSubtree->getListboxActualPixHeight() - 2 * listboxBorderPix;
431    int deltaYpix = 0;
432    int repeatIntervalMillisecs = 100;
433
434    switch (pthis->nonSliderButtonPressRegion) {
435       case whereNodeGraphicalPath<shgRootNode>::ListboxScrollbarUpArrow:
436          deltaYpix = -4;
437          repeatIntervalMillisecs = 10;
438          break;
439       case whereNodeGraphicalPath<shgRootNode>::ListboxScrollbarDownArrow:
440          deltaYpix = 4;
441          repeatIntervalMillisecs = 10;
442          break;
443       case whereNodeGraphicalPath<shgRootNode>::ListboxScrollbarPageup:
444          deltaYpix = -listboxActualDataPix;
445          repeatIntervalMillisecs = 250; // not so fast
446          break;
447       case whereNodeGraphicalPath<shgRootNode>::ListboxScrollbarPagedown:
448          deltaYpix = listboxActualDataPix;
449          repeatIntervalMillisecs = 250; // not so fast
450          break;
451       default:
452          assert(false);
453    }
454
455    (void)pthis->nonSliderCurrentSubtree->scroll_listbox(pthis->consts,
456                                                         listboxLeft, listboxTop,
457                                                         deltaYpix);
458    pthis->buttonAutoRepeatToken = Tk_CreateTimerHandler(repeatIntervalMillisecs,
459                                                         nonSliderButtonAutoRepeatCallback,
460                                                         pthis);
461 }
462
463 void shg::nonSliderButtonRelease(ClientData cd, XEvent *) {
464    // same as where axis
465    shg *pthis = (shg *)cd;
466
467    pthis->nonSliderButtonCurrentlyPressed = false;
468    Tk_DeleteTimerHandler(pthis->buttonAutoRepeatToken);
469 }
470
471 void shg::sliderMouseMotion(ClientData cd, XEvent *eventPtr) {
472    // This is a static method.
473    // Since, in an shg, things may expand at any time (even while one is sliding
474    // a scrollbar), we must be very careful.  The algorithm we use here is slower than
475    // that of the where axis, but is safer.  We use "slider_scrollbar_path"
476    // to obtain what the where axis caches and assumes not to change.
477    assert(eventPtr->type == MotionNotify);
478    shg *pthis = (shg *)cd;
479
480    const int y = eventPtr->xmotion.y;
481    const int amount_moved = y - pthis->slider_initial_yclick;
482       // may be negative, of course.
483    const int newScrollBarSliderTopPix = pthis->slider_initial_scrollbar_slider_top +
484                                         amount_moved;
485
486    assert(pthis->slider_currently_dragging_subtree != NULL);
487
488    const int overallWindowBorderPix = 0;
489    int root_centerx = pthis->nominal_centerx + pthis->horizScrollBarOffset;
490    int root_centery = overallWindowBorderPix + pthis->vertScrollBarOffset;
491    whereNodeGraphicalPath<shgRootNode> thePath(pthis->slider_scrollbar_path,
492                                                pthis->consts, pthis->rootPtr,
493                                                root_centerx, root_centery);
494
495    // a sanity check:
496    if (thePath.getLastPathNode(pthis->rootPtr) !=
497        pthis->slider_currently_dragging_subtree) {
498       cerr << "shg: slider-dragging-subtree unexpectedly changed (ignoring)" << endl;
499       return;
500    }
501
502    where4tree<shgRootNode> *ptr = pthis->slider_currently_dragging_subtree;
503    bool aflag;
504    aflag=(thePath.getLastPathNode(pthis->rootPtr) == ptr);
505    assert(aflag);
506
507    // The scrollbar may no longer exist in the listbox, if one or more items
508    // have been expanded from it, thus shrinking the listbox to the point where
509    // a scrollbar was no longer needed.  Check for that now.
510    if (!ptr->getScrollbar().isValid()) {
511       //cout << "scrollbar no longer valid; ending dragging vrbles" << endl;
512       XEvent hackEvent = *eventPtr;
513       hackEvent.type = ButtonRelease;
514       sliderButtonRelease(pthis, &hackEvent);
515       return;
516    }
517
518    int slider_scrollbar_top = thePath.get_endpath_topy() +
519                                  ptr->getNodeData().getHeightAsRoot() +
520                               pthis->consts.vertPixParent2ChildTop;
521
522    int slider_scrollbar_bottom = slider_scrollbar_top +
523                                  ptr->getListboxActualPixHeight() - 1;
524
525    int slider_scrollbar_left = thePath.get_endpath_centerx() -
526                                ptr->horiz_pix_everything_below_root(pthis->consts) / 2;
527
528    (void)ptr->rigListboxScrollbarSliderTopPix(pthis->consts,
529                                               slider_scrollbar_left,
530                                               slider_scrollbar_top,
531                                               slider_scrollbar_bottom,
532                                               newScrollBarSliderTopPix,
533                                               true // redraw now
534                                               );
535 }
536
537 void shg::sliderButtonRelease(ClientData cd, XEvent *eventPtr) {
538    assert(eventPtr->type == ButtonRelease);
539    shg *pthis = (shg *)cd;
540
541    Tk_DeleteEventHandler(pthis->consts.theTkWindow,
542                          PointerMotionMask,
543                          sliderMouseMotion,
544                          pthis);
545    Tk_DeleteEventHandler(pthis->consts.theTkWindow,
546                          ButtonReleaseMask,
547                          sliderButtonRelease,
548                          pthis);
549    pthis->slider_currently_dragging_subtree = NULL;
550 }
551
552
553 void shg::processSingleClick(int x, int y) {
554    if (rootPtr == NULL)
555       return;
556
557    whereNodeGraphicalPath<shgRootNode> thePath = point2path(x, y);
558
559    switch (thePath.whatDoesPathEndIn()) {
560       case whereNodeGraphicalPath<shgRootNode>::Nothing:
561          return;
562       case whereNodeGraphicalPath<shgRootNode>::ExpandedNode: {
563          where4tree<shgRootNode> *ptr = thePath.getLastPathNode(rootPtr);
564          ptr->toggle_highlight();
565          ptr->getNodeData().drawAsRoot(consts.theTkWindow,
566                                        Tk_WindowId(consts.theTkWindow),
567                                        thePath.get_endpath_centerx(),
568                                        thePath.get_endpath_topy());
569          return;
570       }
571       case whereNodeGraphicalPath<shgRootNode>::ListboxItem:
572          thePath.getLastPathNode(rootPtr)->toggle_highlight();
573          thePath.getParentOfLastPathNode(rootPtr)->
574                       draw(consts.theTkWindow,
575                            consts, Tk_WindowId(consts.theTkWindow),
576                            thePath.get_endpath_centerx(),
577                            thePath.get_endpath_topy(),
578                            false, // not root only
579                            true // listbox only
580                            );
581          return;
582       case whereNodeGraphicalPath<shgRootNode>::ListboxScrollbarUpArrow:
583       case whereNodeGraphicalPath<shgRootNode>::ListboxScrollbarDownArrow:
584       case whereNodeGraphicalPath<shgRootNode>::ListboxScrollbarPageup:
585       case whereNodeGraphicalPath<shgRootNode>::ListboxScrollbarPagedown:
586          processNonSliderButtonPress(thePath);
587          return;
588       case whereNodeGraphicalPath<shgRootNode>::ListboxScrollbarSlider: {
589          where4tree<shgRootNode> *parentPtr = thePath.getLastPathNode(rootPtr);
590
591          slider_initial_yclick = y;
592          slider_currently_dragging_subtree = parentPtr;
593          slider_scrollbar_path = thePath.getPath();
594
595          const int lbTop = thePath.get_endpath_topy() +
596                            parentPtr->getNodeData().getHeightAsRoot() +
597                            consts.vertPixParent2ChildTop;
598
599          int dummyint;
600          parentPtr->getScrollbar().getSliderCoords(lbTop,
601                      lbTop + parentPtr->getListboxActualPixHeight() - 1,
602                      parentPtr->getListboxActualPixHeight() - 2*listboxBorderPix,
603                      parentPtr->getListboxFullPixHeight() - 2*listboxBorderPix,
604                      slider_initial_scrollbar_slider_top, // filled in
605                      dummyint);
606
607          Tk_CreateEventHandler(consts.theTkWindow,
608                                ButtonReleaseMask,
609                                sliderButtonRelease,
610                                this);
611          Tk_CreateEventHandler(consts.theTkWindow,
612                                PointerMotionMask,
613                                sliderMouseMotion,
614                                this);
615          
616          return;
617       }
618       default:
619          assert(false);
620    }
621 }
622
623 void shg::processMiddleClick(int x, int y) {
624    // obtain information about a node...or, if the press was on
625    // a scrollbar, treat it as a normal left-button press
626    if (rootPtr == NULL)
627       return;
628
629    whereNodeGraphicalPath<shgRootNode> thePath = point2path(x, y);
630
631    switch (thePath.whatDoesPathEndIn()) {
632       case whereNodeGraphicalPath<shgRootNode>::Nothing:
633          return;
634       case whereNodeGraphicalPath<shgRootNode>::ListboxScrollbarUpArrow:
635       case whereNodeGraphicalPath<shgRootNode>::ListboxScrollbarDownArrow:
636       case whereNodeGraphicalPath<shgRootNode>::ListboxScrollbarPageup:
637       case whereNodeGraphicalPath<shgRootNode>::ListboxScrollbarPagedown:
638          processNonSliderButtonPress(thePath);
639          return;
640       case whereNodeGraphicalPath<shgRootNode>::ListboxScrollbarSlider: {
641          where4tree<shgRootNode> *parentPtr = thePath.getLastPathNode(rootPtr);
642
643          slider_initial_yclick = y;
644          slider_currently_dragging_subtree = parentPtr;
645          slider_scrollbar_path = thePath.getPath();
646
647          const int lbTop = thePath.get_endpath_topy() +
648                            parentPtr->getNodeData().getHeightAsRoot() +
649                            consts.vertPixParent2ChildTop;
650
651          int dummyint;
652          parentPtr->getScrollbar().getSliderCoords(lbTop,
653                      lbTop + parentPtr->getListboxActualPixHeight() - 1,
654                      parentPtr->getListboxActualPixHeight() - 2*listboxBorderPix,
655                      parentPtr->getListboxFullPixHeight() - 2*listboxBorderPix,
656                      slider_initial_scrollbar_slider_top, // filled in
657                      dummyint);
658
659          Tk_CreateEventHandler(consts.theTkWindow,
660                                ButtonReleaseMask,
661                                sliderButtonRelease,
662                                this);
663          Tk_CreateEventHandler(consts.theTkWindow,
664                                PointerMotionMask,
665                                sliderMouseMotion,
666                                this);
667          
668          return;
669       }
670       default: break;
671    }
672
673    // Middle-mouse-click on a node:
674    // Make an async request (to the PC) for information about this node
675 #ifdef PARADYN
676    const shgRootNode &theNode = thePath.getLastPathNode(rootPtr)->getNodeData();
677    perfConsult->requestNodeInfo(thePhaseId, theNode.getId());
678 #endif
679 }
680
681 bool shg::processDoubleClick(int x, int y) {
682    if (rootPtr==NULL)
683       return false;
684
685    // returns true iff a complete redraw is called for
686    bool scrollToWhenDone = false;
687    whereNodeGraphicalPath<shgRootNode> thePath = point2path(x, y);
688
689    switch (thePath.whatDoesPathEndIn()) {
690       case whereNodeGraphicalPath<shgRootNode>::Nothing:
691          return false;
692       case whereNodeGraphicalPath<shgRootNode>::ListboxScrollbarUpArrow:
693       case whereNodeGraphicalPath<shgRootNode>::ListboxScrollbarDownArrow:
694       case whereNodeGraphicalPath<shgRootNode>::ListboxScrollbarPageup:
695       case whereNodeGraphicalPath<shgRootNode>::ListboxScrollbarPagedown:
696          // processNonSliderButtonPress(thePath); // same as single-click
697          return false;
698       case whereNodeGraphicalPath<shgRootNode>::ListboxScrollbarSlider:
699          return false;
700       case whereNodeGraphicalPath<shgRootNode>::ExpandedNode: {
701          if (thePath.getSize()==0)
702             return false; // ignore a double-click on the root
703
704          // un-expand
705          if (!rootPtr->path2lbItemUnexpand(consts, thePath.getPath(), 0))
706             return false;
707
708          rethink_nominal_centerx();
709          resizeScrollbars();
710          adjustHorizSBOffset();
711          adjustVertSBOffset();
712          softScrollToEndOfPath(thePath.getPath());
713  
714          return true;
715       }
716       case whereNodeGraphicalPath<shgRootNode>::ListboxItem: {
717          const bool anyChanges = rootPtr->path2lbItemExpand(consts, thePath.getPath(), 0);
718          if (!anyChanges) {
719             // just change highlighted-ness
720             thePath.getLastPathNode(rootPtr)->toggle_highlight();
721             thePath.getParentOfLastPathNode(rootPtr)->
722                     draw(consts.theTkWindow, consts, Tk_WindowId(consts.theTkWindow),
723                          thePath.get_endpath_centerx(),
724                          thePath.get_endpath_topy(),
725                          false, // not root only
726                          true // listbox only
727                          );
728             return false;
729          }
730          else
731             // expansion was successfull
732             scrollToWhenDone = true;
733          break;
734       }
735       default:
736          assert(false);
737    }
738
739    // expansion or un-expansion successful
740
741    rethink_nominal_centerx();
742    
743    resizeScrollbars();
744
745    adjustHorizSBOffset();
746    adjustVertSBOffset();
747
748    if (scrollToWhenDone) {
749       const int overallWindowBorderPix = 0;
750       whereNodeGraphicalPath<shgRootNode> 
751            path_to_scroll_to(thePath.getPath(),
752                              consts, rootPtr,
753                              nominal_centerx,
754                              overallWindowBorderPix
755                              );
756       int newlyExpandedElemCenterX = path_to_scroll_to.get_endpath_centerx();
757       int newlyExpandedElemTopY    = path_to_scroll_to.get_endpath_topy();
758       int newlyExpandedElemMiddleY = newlyExpandedElemTopY +
759                                      path_to_scroll_to.getLastPathNode(rootPtr)->getNodeData().getHeightAsRoot() / 2;
760
761       (void)set_scrollbars(newlyExpandedElemCenterX, x,
762                            newlyExpandedElemMiddleY, y,
763                            true);
764    }
765
766    return true;
767 }
768
769 bool shg::softScrollToEndOfPath(const whereNodePosRawPath &thePath) {
770    assert(rootPtr);
771    const int overallWindowBorderPix = 0;
772    whereNodeGraphicalPath<shgRootNode>
773          scrollToPath(thePath, consts, rootPtr,
774                       nominal_centerx,
775                          // ignores scrollbar settings (an absolute coord),
776                       overallWindowBorderPix
777                          // absolute coord of root topy
778                       );
779    const int last_item_centerx = scrollToPath.get_endpath_centerx();
780    const int last_item_topy    = scrollToPath.get_endpath_topy();
781       // note: if the path ends in an lb item, these coords refer to the PARENT
782       //       node, which is expanded.
783
784    switch (scrollToPath.whatDoesPathEndIn()) {
785       case whereNodeGraphicalPath<shgRootNode>::ExpandedNode: {
786          const int last_item_middley =
787             last_item_topy +
788             scrollToPath.getLastPathNode(rootPtr)->getNodeData().getHeightAsRoot() / 2;
789          return set_scrollbars(last_item_centerx,
790                                Tk_Width(consts.theTkWindow) / 2,
791                                last_item_middley,
792                                Tk_Height(consts.theTkWindow) / 2,
793                                true);
794       }
795       case whereNodeGraphicalPath<shgRootNode>::ListboxItem: {
796          // First, let's scroll within the listbox (no redrawing yet)
797          where4tree<shgRootNode> *parent = scrollToPath.getParentOfLastPathNode(rootPtr);
798
799          Tk_FontMetrics listboxFontMetrics; // filled in
800          Tk_GetFontMetrics(consts.listboxFontStruct, &listboxFontMetrics);
801          
802          const unsigned itemHeight = consts.listboxVertPadAboveItem +
803                                      listboxFontMetrics.ascent +
804                                      consts.listboxVertPadAfterItemBaseline;
805
806          int scrollToVertPix = 0; // relative to listbox top
807          const unsigned childnum = scrollToPath.getPath().getLastItem();
808          for (unsigned childlcv=0; childlcv < childnum; childlcv++)
809             if (!parent->getChildIsExpandedFlag(childlcv))
810                scrollToVertPix += itemHeight;
811
812          int destItemRelToListboxTop = scrollToVertPix;
813          if (parent->getScrollbar().isValid()) {
814             (void)parent->scroll_listbox(consts, scrollToVertPix -
815                                          parent->getScrollbar().getPixFirst());
816
817             destItemRelToListboxTop -= parent->getScrollbar().getPixFirst();
818          }
819          if (destItemRelToListboxTop < 0) {
820             cout << "note: softScrollToEndOfPath() failed to scroll properly to item w/in listbox" << endl;
821          }
822
823          return set_scrollbars(scrollToPath.get_endpath_centerx() -
824                                   parent->horiz_pix_everything_below_root(consts)/2 +
825                                   parent->getListboxPixWidth() / 2,
826                                   // listbox centerx
827                                Tk_Width(consts.theTkWindow) / 2,
828                                scrollToPath.get_endpath_topy() +
829                                   parent->getNodeData().getHeightAsRoot() +
830                                   consts.vertPixParent2ChildTop +
831                                   destItemRelToListboxTop + itemHeight / 2,
832                                   // should be the middley of the lb item
833                                Tk_Height(consts.theTkWindow) / 2,
834                                true);
835       }
836       default: assert(false);
837    }
838
839    assert(false);
840    return false; // placate compiler
841 }
842
843 bool shg::changeHiddenNodesBase(bool isCurrShg) {
844    // private routine called by the 2 implementations of changeHiddenNodes, below.
845    if (rootPtr==NULL)
846       return false; // nothing changed since shg has no nodes!
847
848    // Now we need to loop thru each node in the dag, rethinking
849    // whether or not it is hidden:
850    if (!recursiveUpdateHiddenNodes(rootPtr))
851       return false; // nothing changed.  Somewhat surprising.
852
853    rootPtr->updateAnything2Draw(consts);
854    rethink_entire_layout(isCurrShg);
855
856    return true;
857 }
858
859 bool shg::changeHiddenNodes(bool newHideTrue, bool newHideFalse, bool newHideUnknown,
860                             bool newHideNeverSeen, bool newHideActive,
861                             bool newHideInactive, bool newHideShadow,
862                             bool isCurrShg) {
863    // returns true iff any changes
864    if (hideTrueNodes == newHideTrue && hideFalseNodes == newHideFalse &&
865        hideUnknownNodes == newHideUnknown && hideNeverSeenNodes == newHideNeverSeen &&
866        hideActiveNodes == newHideActive && hideInactiveNodes == newHideInactive &&
867        hideShadowNodes == newHideShadow)
868       return false; // nothing changed
869
870    hideTrueNodes = newHideTrue;
871    hideFalseNodes = newHideFalse;
872    hideUnknownNodes = newHideUnknown;
873    hideNeverSeenNodes = newHideNeverSeen;
874    hideActiveNodes = newHideActive;
875    hideInactiveNodes = newHideInactive;
876    hideShadowNodes = newHideShadow;
877
878    return changeHiddenNodesBase(isCurrShg);
879 }
880 bool shg::changeHiddenNodes(shg::changeType ct, bool hide, bool isCurrShg) {
881    switch (ct) {
882       case shg::ct_true:
883          if (hideTrueNodes == hide) return false;
884          hideTrueNodes = hide;
885          break;
886        case shg::ct_false:
887          if (hideFalseNodes == hide) return false;
888          hideFalseNodes = hide;
889          break;
890        case shg::ct_unknown:
891          if (hideUnknownNodes == hide) return false;
892          hideUnknownNodes = hide;
893          break;
894        case shg::ct_never:
895          if (hideNeverSeenNodes == hide) return false;
896          hideNeverSeenNodes = hide;
897          break;
898        case shg::ct_active:
899          if (hideActiveNodes == hide) return false;
900          hideActiveNodes = hide;
901          break;
902        case shg::ct_inactive:
903          if (hideInactiveNodes == hide) return false;
904          hideInactiveNodes = hide;
905          break;
906        case shg::ct_shadow:
907          if (hideShadowNodes == hide) return false;
908          hideShadowNodes = hide;
909          break;
910        default:
911          assert(false);
912    }
913
914    return changeHiddenNodesBase(isCurrShg);
915 }
916
917 bool shg::recursiveUpdateHiddenNodes(where4tree<shgRootNode> *ptr) {
918    // returns true iff any changes
919    bool anyChanges = false; // so far...
920
921    shgRootNode &theRootNode = ptr->getNodeData();
922    bool isShadowNode = theRootNode.isShadowNode();
923    bool curr_hidden = !theRootNode.anything2draw();
924
925    bool this_node_should_be_hidden = state2hidden(theRootNode.getEvalState(),
926                                                   theRootNode.isActive(),
927                                                   isShadowNode);
928    if (this_node_should_be_hidden != curr_hidden) {
929       anyChanges = true;
930       if (this_node_should_be_hidden)
931          theRootNode.hidify();
932       else
933          theRootNode.unhide();
934    }
935
936    // Continue for children:
937    for (unsigned childlcv=0; childlcv < ptr->getNumChildren(); childlcv++)
938       if (recursiveUpdateHiddenNodes(ptr->getChildTree(childlcv)))
939          anyChanges = true;
940
941    return anyChanges;
942 }
943
944 void shg::addNode(unsigned id, bool iActive, shgRootNode::evaluationState iEvalState,
945                   const string &label, const string &fullInfo,
946                   bool rootNodeFlag, bool isCurrShg) {
947    if (rootNodeFlag) {
948       assert(hash.size() == 0);
949       assert(rootPtr == NULL);
950    }
951    else {
952       assert(hash.size() > 0);
953       assert(rootPtr != NULL);
954    }
955
956    bool hidden = state2hidden(iEvalState, iActive, false);
957       // false --> not a shadow node
958    shgRootNode tempNewNode(id, iActive, iEvalState, false, label, fullInfo, hidden);
959
960    where4tree<shgRootNode> *newNode = new where4tree<shgRootNode>(tempNewNode);
961    assert(newNode);
962  
963    if (rootNodeFlag)
964       rootPtr = newNode;
965
966    assert(rootPtr);
967
968    // Note that processing here is very different from the where axis.
969    // In the where axis, you are always given a parent when inserting a node.
970    // Here, we are not (intentionally); creating nodes and adding edges must be
971    // kept separate, or else we couldn't create a dag (we would only be able to
972    // create a tree).
973    // So, we put the node in the hash table but don't try to link it to any parent
974    // until the addEdge...
975
976    // the id should not already be in use
977    assert(!hash.defines(id));
978    hash[id] = newNode;
979    assert(hash.defines(id));
980
981    assert(!hash2.defines(newNode));
982    hash2[newNode] = NULL; // no parent yet (until addEdge)
983    assert(hash2.defines(newNode));
984
985    if (rootNodeFlag)
986       // only in this case do we rethink layout sizes; otherwise,
987       // we wait until an edge connects to this new node.
988       rethink_entire_layout(isCurrShg);
989 }
990
991 shg::configNodeResult shg::configNode(unsigned id, bool newActive,
992                                       shgRootNode::evaluationState newEvalState,
993                                       bool isCurrShg,
994                                       bool rethinkIfNecessary) {
995    // Does not redraw.  Possible return values:
996    // noChanges, benignChanges (a node changed color but didn't expand/unexpand
997    // or hide/unhide), changesInvolvingJustExpandedness, changesInvolvingHideness.
998
999    // Note that a true value of "rethinkIfNecessary" can only lead to a return
1000    // value of noChanges or benignChanges, because any "higher" changes would trigger
1001    // the necessary rethink(s).
1002    //
1003    // In practice:
1004    // a return value indicating a change in just expandedness means that you should
1005    //    call rethink_entire_layout(isCurrShg)
1006    // a return value indicating a change in Hideness means that you should
1007    //    call rootPtr->updateAnything2Draw(consts)
1008    //    and  rethink_entire_layout(isCurrShg)
1009
1010    // Note: a change from "tentatively-true" to (anything else) will un-expand;
1011    //                to "tentatively=true" from (anything else) will expand;
1012    //       leading to a massive layout rethinkification.
1013    // In addition, changing to or from a hidden state will also lead to
1014    //    layout rethinkification.
1015    // But the rethinking is always suppressed if "rethinkIfNecessary" is false.
1016
1017    // Other changes are more simple -- simply changing the color of a node.
1018    assert(rootPtr);
1019
1020    assert(hash.defines(id));
1021    where4tree<shgRootNode> *ptr = hash[id];
1022    assert(ptr);
1023
1024    // just a sanity check: ptr should have a parent (will be NULL if ptr is the root)
1025    assert(hash2.defines(ptr));
1026    // added extra { } around this if, the AIX g++ compiler seems to need it
1027    //  jkh - 1/18/96
1028    if (ptr == rootPtr) {
1029       assert(hash2[ptr] == NULL);
1030    } else {
1031       assert(hash2[ptr] != NULL);
1032    } 
1033
1034    const shgRootNode::evaluationState oldEvalState = ptr->getNodeData().getEvalState();
1035    const bool oldActive = ptr->getNodeData().isActive();
1036    const bool oldHidden = state2hidden(oldEvalState, oldActive, false);
1037       // false --> we are not a shadow node
1038
1039    bool anyChanges = ptr->getNodeData().configStyle(newActive, newEvalState);
1040
1041    if (shadowNodeHash.defines(id)) {
1042       // shadow nodes exist for this id.  configStyle() them, too.
1043       vector< where4tree<shgRootNode>* > &shadowList = shadowNodeHash[id];
1044       for (unsigned i=0; i < shadowList.size(); i++) {
1045          where4tree<shgRootNode>* shadowNode = shadowList[i];
1046          if (shadowNode->getNodeData().configStyle(newActive, newEvalState))
1047             anyChanges = true;
1048       }
1049    }
1050    
1051    if (!anyChanges)
1052       return noChanges;
1053
1054    // note: if we are changing this to "true (active or not)", we explicitly expand
1055    // "ptr".  Unfortunately, we need ptr's parent node for that; hence the need
1056    // for the hash table "hash2" (a mapping of nodes to their parents).  It's
1057    // somewhat of a hack; a solution eliminating the need for "hash2" would save memory.
1058
1059    // Note that we make no effort to alter the expanded-ness of any shadowed nodes.
1060    // (Is this right?)
1061
1062    bool resultExpandednessChanged = false; // so far
1063    bool resultHidenessChanged     = false; // so far
1064    bool rethink_layout = false; // so far...
1065
1066    // Algorithm:
1067    // If a node becomes un-hidden, we unhide it first.
1068    // If a node becomes hidden, we hide it last.
1069    // In the middle come the possible expansions / un-expansions due to
1070    //    changing to/from es_true.
1071
1072    // Algorithm Step 1: If a node becomes un-hidden
1073    // Note that there's no need to check our shadow children, because the
1074    // only trait that may lead to a hidden-ness that differs from us is that
1075    // they're shadow nodes, and that trait isn't gonna change.
1076    assert(oldHidden == !ptr->getNodeData().anything2draw());
1077    bool new_hidden = state2hidden(newEvalState, newActive,
1078                                   false); // false --> not shadow node
1079    if (oldHidden != new_hidden && !new_hidden) {
1080       // The node is going to be un-hidden; let's do that first to avoid
1081       // an assertion failure.
1082       ptr->getNodeData().unhide();
1083
1084       if (rethinkIfNecessary) {
1085          (void)rootPtr->updateAnything2Draw(consts); // result should be true
1086          rethink_layout = true;
1087             // probably a bit more than is needed...
1088       }
1089       else
1090          resultHidenessChanged=true;
1091    }
1092
1093    // Algorithm Step 2: Expansion/un-expansion due to change to/from es_true
1094    bool autoExpand   = (newEvalState == shgRootNode::es_true);
1095    bool autoUnExpand = (oldEvalState == shgRootNode::es_true);
1096
1097    if (autoExpand || autoUnExpand) {
1098       where4tree<shgRootNode> *parentPtr = hash2[ptr];
1099       if (parentPtr == NULL)
1100          ; // either root node or a node which hasn't been addEdge'd in yet.
1101            // So, we skip the ceremonies (but we don't 'return' because there
1102            // may be more to do below w.r.t. hide-ness)
1103       else {
1104          // the following is unacceptably slow; it must be fixed by using real paths
1105          const unsigned numChildren = parentPtr->getNumChildren();
1106          for (unsigned childlcv=0; childlcv < numChildren; childlcv++)
1107             if (parentPtr->getChildTree(childlcv) == ptr) {
1108                if (autoExpand)
1109                   parentPtr->explicitlyExpandSubchild(consts, childlcv, true);
1110                      // true --> force expansion, even if it's a leaf node
1111                else
1112                   parentPtr->explicitlyUnexpandSubchild(consts, childlcv);
1113    
1114                if (rethinkIfNecessary)
1115                   rethink_layout = true;
1116                   // (all we really need to do is rethink all-expanded-children
1117                   // dimensions for all ancestors of parentPtr)
1118                else
1119                   resultExpandednessChanged = true;
1120
1121                break;
1122             } 
1123
1124          assert(rethink_layout || !rethinkIfNecessary);
1125       }
1126    }
1127   
1128    if (oldHidden != new_hidden && new_hidden) {
1129       // The node is going to be hidden; let's do that last to avoid
1130       // an assertion failure.
1131       ptr->getNodeData().hidify();
1132       
1133       if (rethinkIfNecessary) {
1134          rootPtr->updateAnything2Draw(consts); // result should be true
1135          rethink_layout = true;
1136             // probably a bit more than is needed...
1137       }
1138       else
1139          resultHidenessChanged = true;
1140    }
1141
1142    if (rethink_layout) {
1143       assert(rethinkIfNecessary);
1144       // If changes involving hideness occurred, they will have been processed,
1145       // since rethinkIfNecessary is true.  So right now all we have to do is
1146       // handle expansions that have occurred...
1147       rethink_entire_layout(isCurrShg);
1148
1149       // ...which means there's nothing left for outside code to do but redraw:
1150       return benignChanges;
1151    }
1152    else if (resultHidenessChanged)
1153       // the biggest change; user must call rootPtr->updateAnything2Draw(consts) plus
1154       // rethink_entire_layout(isCurrShg)
1155       return changesInvolvingHideness;
1156    else if (resultExpandednessChanged)
1157       // user must call rethink_entire_layout(isCurrShg)
1158       return changesInvolvingJustExpandedness;
1159    else
1160       // user doesn't need to do anything before redrawing...
1161       return benignChanges;
1162 }
1163
1164 bool shg::inactivateAll(bool isCurrShg) {
1165    // returns true iff any changes.  Does not redraw.
1166    // The usual case is just to change fg colors.  However, if we are e.g. hiding
1167    // inactive nodes, then a full rethinkification is a possibility.
1168
1169    bool anyChanges = false; // so far...
1170
1171    if (rootPtr == NULL)
1172       return false;
1173
1174    // Loop through everything in the main hash table ("hash")
1175    // Assume ids start at 0.  But don't assume they aren't skip-numbered
1176    // (just to be safe).  If nodes are radically skip-numbered, then this
1177    // algorithm will be too slow; a recursive traversal replacement should
1178    // be no problem...
1179    const unsigned totalNumNodes = hash.size();
1180    unsigned numNodesProcessed = 0;
1181    unsigned nodeId = 0;
1182
1183    bool needToRethinkHidden = false; // so far...
1184    bool needToRethinkLayout = false; // so far...
1185
1186    while (numNodesProcessed < totalNumNodes) {
1187       if (!hash.defines(nodeId)) {
1188          nodeId++;
1189          continue; // no progress made
1190       }
1191
1192       // success
1193       where4tree<shgRootNode> *nodePtr = hash[nodeId];
1194       assert(hash2.defines(nodePtr));
1195          // may be NULL (rootPtr or not yet addEdge'd), but at least it should be there.
1196
1197       // If this node has been addEdge'd (or is rootPtr), then call configNode.
1198       // Otherwise, configNode would just bomb, since it barfs on seeing any node
1199       // that hasn't been addEdge'd into the graph yet.  In that case, we configure
1200       // manually w/o rethinking.
1201       
1202       const shgRootNode::evaluationState oldEvalState = nodePtr->getNodeData().getEvalState();
1203
1204       if (nodePtr == rootPtr || hash2[nodePtr] != NULL) {
1205          configNodeResult localResult = configNode(nodeId, false,
1206                                                       // false --> not active
1207                                                    oldEvalState, isCurrShg,
1208                                                    false // don't rethink
1209                                                    );
1210          if (localResult != noChanges)
1211             anyChanges = true;
1212
1213          if (localResult == changesInvolvingHideness) {
1214             needToRethinkHidden = true;
1215             needToRethinkLayout = true;
1216          }
1217          else if (localResult == changesInvolvingJustExpandedness)
1218             needToRethinkLayout = true;
1219       }
1220       else {
1221          // manual job on nodes which haven't yet been addEdge'd into the graph.
1222          (void)nodePtr->getNodeData().configStyle(false, oldEvalState);
1223             // false --> inactivate
1224             // we ignore the return result; even if true, we don't want to redraw
1225       }
1226
1227       nodeId++;
1228       numNodesProcessed++;
1229    }
1230
1231    // Now do any deferred rethinkification, etc.:
1232    if (needToRethinkHidden)
1233       (void)rootPtr->updateAnything2Draw(consts); // result should be true
1234
1235    if (needToRethinkLayout)
1236       rethink_entire_layout(isCurrShg);
1237
1238    return anyChanges;   
1239 }
1240
1241 void shg::addEdge(unsigned fromId, unsigned toId,
1242                   shgRootNode::refinement theRefinement,
1243                   const char *label, // only used for shadow nodes; else NULL
1244                   bool isCurrShg,
1245                   bool rethinkFlag) {
1246    // What to do about hidden nodes?
1247    // 1) If child is a shadow node then we must initialize the hidden flag properly
1248    // 2) If not a shadow node then probably nothing to do, assuming the hidden
1249    //    flag was properly set when the child was addNode'd.
1250
1251    assert(rootPtr);
1252
1253    // Obviously, both nodes must already exist.
1254    assert(hash.defines(fromId));
1255    assert(hash.defines(toId));
1256    
1257    where4tree<shgRootNode> *parentPtr = hash[fromId];
1258    assert(parentPtr);
1259
1260    where4tree<shgRootNode> *childPtr  = hash[toId];
1261    assert(childPtr);
1262
1263    // If, before this addEdge, childPtr had no parents, then this is a "normal"
1264    // addEdge operation in which the child node will finally be made visible on the
1265    // screen.
1266    // Else, we are adding a shadow node.  More specifically, the new child of
1267    // "parentPtr" will just be made a shadow node.  We'll _create_ a separate node
1268    // ptr for this shadow node.  "childPtr" will be unused (more specifically, we'll
1269    // let it dangle and overwrite it)
1270
1271    bool addingShadowNode = false;
1272    if (hash2[childPtr] == NULL) {
1273       // We are _not_ adding a shadow node.
1274       hash2[childPtr] = parentPtr;
1275       assert(label==NULL);
1276
1277       // For drawing purposes, let's correctly update the refinement field of
1278       // the child node, which would otherwise be left at ref_undefined.
1279       childPtr->getNodeData().setRefinement(theRefinement);
1280    }
1281    else {
1282       // We are adding a shadow node.
1283       assert(label != NULL);
1284       addingShadowNode = true;
1285
1286       // copy all information from the existing "real" node...
1287       shgRootNode tempNewNode = childPtr->getNodeData();
1288       // ...and decide if it should be hidden...(if hide-shadow is in effect, the
1289       //    shadow node might be hidden while the real node isn't)
1290       if (state2hidden(tempNewNode.getEvalState(),
1291                        tempNewNode.isActive(),
1292                        true))
1293          tempNewNode.hidify();
1294       else
1295          tempNewNode.unhide();
1296
1297       // ...and make it a shadow node:
1298       childPtr = new where4tree<shgRootNode> (tempNewNode.shadowify(label));
1299       assert(childPtr);
1300
1301       vector<where4tree<shgRootNode>*> &theShadowNodes = shadowNodeHash[toId];
1302       theShadowNodes += childPtr;
1303
1304       // Note: we do not add shadow node pointers to hash2[] (is this right?)
1305
1306       // Note: we make no attempt to set the refinement field of the child shadow node.
1307       // It will have been copied from the "real" non-shadow node it points to, and
1308       // is presumably correct.  Hence no need to look at "theRefinement", except
1309       // perhaps to assert it's the same as that already present in the shadow node.
1310    }
1311
1312    // Should the child node be explicitly expanded when added to its parent?
1313    // Here are the rules:
1314    // 1) If a shadow node, then no.
1315    // 2) Else, yes iff the child node is true
1316    // Note that whether the node is active or not is irrelevant.
1317
1318    bool explicitlyExpandedFlag;
1319    if (addingShadowNode)
1320       explicitlyExpandedFlag = false;
1321    else {
1322       const shgRootNode &childNodeData = childPtr->getNodeData();
1323       shgRootNode::evaluationState theEvalState = childNodeData.getEvalState();
1324
1325       explicitlyExpandedFlag = (theEvalState == shgRootNode::es_true);
1326    }
1327
1328    parentPtr->addChild(childPtr,
1329                        explicitlyExpandedFlag,
1330                        consts,
1331                        false, // don't rethink graphics
1332                        false // don't resort
1333                        );
1334
1335    // rethink layout of entire shg (slow...):
1336    if (rethinkFlag) rethink_entire_layout(isCurrShg);
1337 }
1338
1339 #ifdef PARADYN
1340 void shg::nodeInformation(unsigned nodeId, const shg_node_info &theNodeInfo) {
1341    // In response to a middle-mouse-click...
1342
1343    // First, delete what was in the curr-item-description widget
1344    string commandStr = currItemLabelName + " config -state normal";
1345    myTclEval(interp, commandStr);
1346
1347    commandStr = currItemLabelName + " delete @0,0 end";
1348    myTclEval(interp, commandStr);
1349
1350    // Second, fill in the multi-line description string...
1351    // (Note that we do extra stuff in developer mode...)
1352
1353    string dataString;
1354    assert(hash.defines(nodeId));
1355    const shgRootNode &theNode = hash[nodeId]->getNodeData();
1356
1357    // shg test program doesn't have a devel mode
1358    extern bool inDeveloperMode;
1359    if (inDeveloperMode)
1360       dataString += string(theNode.getId()) + " ";
1361
1362    dataString += theNode.getLongName();
1363
1364    // The igen call isn't implemented in shg test program
1365    if (inDeveloperMode) {
1366       dataString += "\n";
1367       dataString += "curr concl: ";
1368       dataString += theNodeInfo.currentConclusion ? "true" : "false";
1369       dataString += " made at time ";
1370       dataString += string(theNodeInfo.timeTrueFalse) + "\n";
1371       dataString += string("curr value: ") + string(theNodeInfo.currentValue);
1372       dataString += string(" estim cost: ") + string(theNodeInfo.estimatedCost) + "\n";
1373       dataString += string("time from ") + string(theNodeInfo.startTime) + " to " +
1374                                            string(theNodeInfo.endTime) + "\n";
1375       dataString += string("persistent: ") + (theNodeInfo.persistent ? "true" : "false");
1376    }
1377
1378    commandStr = currItemLabelName + " insert end " + "\"" + dataString + "\"";
1379    myTclEval(interp, commandStr);
1380
1381    commandStr = currItemLabelName + " config -state disabled";
1382    myTclEval(interp, commandStr);
1383 }
1384 #endif