XFontStruct --> Tk_Font; use of Tk_GetFontMetrics
[dyninst.git] / paradyn / src / UIthread / whereAxis.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 // whereAxis.C
43 // Ariel Tamches
44
45 // A where axis corresponds to _exactly_ one Paradyn abstraction.
46
47 /* $Log: whereAxis.C,v $
48 /* Revision 1.16  1997/09/24 19:29:05  tamches
49 /* XFontStruct --> Tk_Font; use of Tk_GetFontMetrics
50 /* for tcl 8.0
51 /*
52  * Revision 1.15  1996/11/26 16:07:08  naim
53  * Fixing asserts - naim
54  *
55  * Revision 1.14  1996/08/16 21:07:44  tamches
56  * updated copyright for release 1.1
57  *
58  * Revision 1.13  1996/02/15 23:14:56  tamches
59  * added code relating to the new line-GC indirection feature of where4tree
60  *
61  * Revision 1.12  1996/02/11 18:27:47  tamches
62  * Added a check for null rootPtr before drawing
63  *
64  * Revision 1.11  1996/02/07 19:15:06  tamches
65  * removed include of whereAxisMisc.h, except for the test program
66  *
67  * Revision 1.10  1996/01/11 04:45:37  tamches
68  * getSelections replaced - now returns Whole Program selection separately,
69  * runs faster, and alone with the new parseSelections of uimpd.tcl.C,
70  * avoids adding spurious metric/focus pairs when Whole Program is chosen
71  *
72  * Revision 1.9  1995/12/09 04:08:39  tamches
73  * shift-dbl-click now togggles highlight, like ctrl-dbl-click
74  *
75  * Revision 1.8  1995/11/20 03:27:50  tamches
76  * overallWindowBorderPix is no longer a global variable.
77  * double-click does a toggle_highlight of the parent node; same
78  * for ctrl-double-click.
79  * changed vector<whereAxisRootNode *> to vector<const whereAxisRootNode *>
80  *
81  * Revision 1.7  1995/10/17 22:20:15  tamches
82  * where axis is no longer a templated type; it uses where4tree with
83  * a template of whereAxisRootNode.
84  * Added many static vrbles and methods which had to be temporarily
85  * moved to whereAxisMisc for compiler-bug reasons in the past.
86  * Expanding a node now calls XWarpPointer more accurately.
87  * Same for un-expansion, finding, and navigating to a node.
88  *
89  */
90
91 #include <stdlib.h> // exit()
92
93 #include "minmax.h"
94
95 #ifndef PARADYN
96 #include "DMinclude.h"
97 #else
98 #include "paradyn/src/DMthread/DMinclude.h"
99 #endif
100
101 #include "whereAxis.h"
102
103 #ifndef PARADYN
104 // only the where axis test program needs this
105 #include "whereAxisMisc.h"
106 #endif
107
108 #include "tkTools.h"
109 #include "tk.h"
110
111 Tk_Font whereAxis::theRootItemFontStruct = NULL;
112 Tk_Font whereAxis::theListboxItemFontStruct = NULL;
113 Tk_3DBorder  whereAxis::rootItemTk3DBorder = NULL;
114 GC           whereAxis::rootItemTextGC = NULL;
115 Tk_3DBorder  whereAxis::listboxItem3DBorder = NULL;
116 GC           whereAxis::listboxItemGC = NULL;
117 GC           whereAxis::listboxRayGC = NULL;
118 GC           whereAxis::nonListboxRayGC = NULL;
119 int          whereAxis::listboxBorderPix = 3;
120 int          whereAxis::listboxScrollBarWidth = 16;
121
122
123 void whereAxis::initializeStaticsIfNeeded() {
124    if (theRootItemFontStruct == NULL)
125       // somewhat kludgy
126       theRootItemFontStruct = consts.rootTextFontStruct;
127
128    if (theListboxItemFontStruct == NULL)
129       // somewhat kludgy
130       theListboxItemFontStruct = consts.listboxFontStruct;
131
132    if (rootItemTk3DBorder == NULL)
133       // somewhat kludgy
134       rootItemTk3DBorder = consts.rootNodeBorder;
135
136    if (rootItemTextGC == NULL)
137       // somewhat kludgy
138       rootItemTextGC = consts.rootItemTextGC;
139
140    if (listboxItem3DBorder == NULL)
141       // somewhat kludgy
142       listboxItem3DBorder = consts.listboxBorder; // ???
143
144    if (listboxItemGC == NULL)
145       listboxItemGC = consts.listboxTextGC;
146
147    if (listboxRayGC == NULL)
148       listboxRayGC = consts.listboxRayGC;
149
150    if (nonListboxRayGC == NULL)
151       nonListboxRayGC = consts.subchildRayGC;
152 }
153
154 whereAxis::whereAxis(Tcl_Interp *in_interp, Tk_Window theTkWindow,
155                      const string &root_str,
156                      const string &iHorizSBName,
157                      const string &iVertSBName,
158                      const string &iNavigateMenuName) :
159              consts(in_interp, theTkWindow),
160              hash(&whereAxis::hashFunc, 32),
161              horizSBName(iHorizSBName),
162              vertSBName(iVertSBName),
163              navigateMenuName(iNavigateMenuName) {
164    initializeStaticsIfNeeded();
165
166    const resourceHandle rootResHandle = 0;
167
168    whereAxisRootNode tempRootNode(rootResHandle, root_str);
169    rootPtr = new where4tree<whereAxisRootNode>(tempRootNode);
170    assert(rootPtr);
171
172    hash[rootResHandle] = rootPtr;
173
174    beginSearchFromPtr = NULL;
175
176    interp = in_interp;
177       
178    horizScrollBarOffset = vertScrollBarOffset = 0;
179       
180    rethink_nominal_centerx();
181
182    nonSliderButtonCurrentlyPressed = false;
183    nonSliderCurrentSubtree = NULL;
184    slider_currently_dragging_subtree = NULL;
185 }
186
187 void whereAxis::rethink_nominal_centerx() {
188    // using Tk_Width(theTkWindow) as the available screen width, and
189    // using root->myEntireWidthAsDrawn(consts) as the amount of screen real
190    // estate used, this routine rethinks this->nominal_centerx.
191
192    const int horizSpaceUsedByTree = rootPtr->entire_width(consts);
193
194    // If the entire tree fits, then set center-x to window-width / 2.
195    // Otherwise, set center-x to used-width / 2;
196    if (horizSpaceUsedByTree <= Tk_Width(consts.theTkWindow))
197       nominal_centerx = Tk_Width(consts.theTkWindow) / 2;
198    else
199       nominal_centerx = horizSpaceUsedByTree / 2;
200 }
201
202 void whereAxis::resizeScrollbars() {
203    string commandStr = string("resize1Scrollbar ") + horizSBName + " " +
204                        string(rootPtr->entire_width(consts)) + " " +
205                        string(Tk_Width(consts.theTkWindow));
206    myTclEval(interp, commandStr);
207
208    commandStr = string("resize1Scrollbar ") + vertSBName + " " +
209                 string(rootPtr->entire_height(consts)) + " " +
210                 string(Tk_Height(consts.theTkWindow));
211    myTclEval(interp, commandStr);
212 }
213
214 bool whereAxis::set_scrollbars(int absolute_x, int relative_x,
215                                int absolute_y, int relative_y,
216                                bool warpPointer) {
217    // Sets the scrollbars s.t. (absolute_x, absolute_y) will appear
218    // at window (relative) location (relative_x, relative_y).
219    // May need to take into account the current scrollbar setting...
220
221    bool anyChanges = true;
222
223    horizScrollBarOffset = -set_scrollbar(interp, horizSBName,
224                                          rootPtr->entire_width(consts),
225                                          absolute_x, relative_x);
226       // relative_x will be updated
227    
228    vertScrollBarOffset = -set_scrollbar(interp, vertSBName,
229                                         rootPtr->entire_height(consts),
230                                         absolute_y, relative_y);
231
232    if (warpPointer)
233       XWarpPointer(Tk_Display(consts.theTkWindow),
234                    Tk_WindowId(consts.theTkWindow), // src win
235                    Tk_WindowId(consts.theTkWindow), // dest win
236                    0, 0, // src x,y
237                    0, 0, // src height, width
238                    relative_x, relative_y
239                    );
240
241    return anyChanges;
242 }
243
244 whereNodeGraphicalPath<whereAxisRootNode> whereAxis::point2path(int x, int y) const {
245    const int overallWindowBorderPix = 0;
246    const int root_centerx = nominal_centerx + horizScrollBarOffset;
247       // relative (not absolute) coord.  note: horizScrollBarOffset <= 0
248    const int root_topy = overallWindowBorderPix + vertScrollBarOffset;
249       // relative (not absolute) coord.  note: vertScrollBarOffset <= 0
250
251    return whereNodeGraphicalPath<whereAxisRootNode>(x, y, consts, rootPtr,
252                                                     root_centerx, root_topy);
253 }
254
255 void whereAxis::nonSliderButtonRelease(ClientData cd, XEvent *) {
256    whereAxis *pthis = (whereAxis *)cd;
257
258    pthis->nonSliderButtonCurrentlyPressed = false;
259    Tk_DeleteTimerHandler(pthis->buttonAutoRepeatToken);
260 }
261
262 void whereAxis::nonSliderButtonAutoRepeatCallback(ClientData cd) {
263    // If the mouse button has been released, do NOT re-invoke the timer.
264    whereAxis *pthis = (whereAxis *)cd;
265    if (!pthis->nonSliderButtonCurrentlyPressed)
266       return;
267
268    const int listboxLeft = pthis->nonSliderSubtreeCenter -
269                            pthis->nonSliderCurrentSubtree->
270                              horiz_pix_everything_below_root(pthis->consts) / 2;
271    const int listboxTop = pthis->nonSliderSubtreeTop +
272                           pthis->nonSliderCurrentSubtree->getNodeData().
273                                getHeightAsRoot() +
274                           pthis->consts.vertPixParent2ChildTop;
275
276    const int listboxActualDataPix = pthis->nonSliderCurrentSubtree->getListboxActualPixHeight() - 2 * listboxBorderPix;
277    int deltaYpix = 0;
278    int repeatIntervalMillisecs = 100;
279
280    switch (pthis->nonSliderButtonPressRegion) {
281       case whereNodeGraphicalPath<whereAxisRootNode>::ListboxScrollbarUpArrow:
282          deltaYpix = -4;
283          repeatIntervalMillisecs = 10;
284          break;
285       case whereNodeGraphicalPath<whereAxisRootNode>::ListboxScrollbarDownArrow:
286          deltaYpix = 4;
287          repeatIntervalMillisecs = 10;
288          break;
289       case whereNodeGraphicalPath<whereAxisRootNode>::ListboxScrollbarPageup:
290          deltaYpix = -listboxActualDataPix;
291          repeatIntervalMillisecs = 250; // not so fast
292          break;
293       case whereNodeGraphicalPath<whereAxisRootNode>::ListboxScrollbarPagedown:
294          deltaYpix = listboxActualDataPix;
295          repeatIntervalMillisecs = 250; // not so fast
296          break;
297       default:
298          assert(false);
299    }
300
301    (void)pthis->nonSliderCurrentSubtree->scroll_listbox(pthis->consts,
302                                                         listboxLeft, listboxTop,
303                                                         deltaYpix);
304    pthis->buttonAutoRepeatToken = Tk_CreateTimerHandler(repeatIntervalMillisecs,
305                                                         nonSliderButtonAutoRepeatCallback,
306                                                         pthis);
307 }
308
309 void whereAxis::
310 processNonSliderButtonPress(whereNodeGraphicalPath<whereAxisRootNode> &thePath) {
311    nonSliderButtonCurrentlyPressed = true;
312    nonSliderButtonPressRegion = thePath.whatDoesPathEndIn();
313    nonSliderCurrentSubtree = thePath.getLastPathNode(rootPtr);
314    nonSliderSubtreeCenter = thePath.get_endpath_centerx();
315    nonSliderSubtreeTop = thePath.get_endpath_topy();
316
317    Tk_CreateEventHandler(consts.theTkWindow, ButtonReleaseMask,
318                          nonSliderButtonRelease, this);
319
320    nonSliderButtonAutoRepeatCallback(this);
321 }
322
323 void whereAxis::sliderMouseMotion(ClientData cd, XEvent *eventPtr) {
324    assert(eventPtr->type == MotionNotify);
325    whereAxis *pthis = (whereAxis *)cd;
326
327    const int y = eventPtr->xmotion.y;
328    const int amount_moved = y - pthis->slider_initial_yclick;
329       // may be negative, of course.
330    const int newScrollBarSliderTopPix = pthis->slider_initial_scrollbar_slider_top +
331                                         amount_moved;
332
333    assert(pthis->slider_currently_dragging_subtree != NULL);
334    (void)pthis->slider_currently_dragging_subtree->
335            rigListboxScrollbarSliderTopPix(pthis->consts, pthis->slider_scrollbar_left,
336                                            pthis->slider_scrollbar_top,
337                                            pthis->slider_scrollbar_bottom,
338                                            newScrollBarSliderTopPix,
339                                            true // redraw now
340                                            );
341 }
342
343 void whereAxis::sliderButtonRelease(ClientData cd, XEvent *eventPtr) {
344    assert(eventPtr->type == ButtonRelease);
345    whereAxis *pthis = (whereAxis *)cd;
346       
347    Tk_DeleteEventHandler(pthis->consts.theTkWindow,
348                          PointerMotionMask,
349                          sliderMouseMotion,
350                          pthis);
351    Tk_DeleteEventHandler(pthis->consts.theTkWindow,
352                          ButtonReleaseMask,
353                          sliderButtonRelease,
354                          pthis);
355    pthis->slider_currently_dragging_subtree = NULL;
356 }
357
358 void whereAxis::addItem(const string &newName,
359                         resourceHandle parentUniqueId,
360                         resourceHandle newNodeUniqueId,
361                         bool rethinkGraphicsNow,
362                         bool resortNow) {
363    whereAxisRootNode tempRootNode(newNodeUniqueId, newName);
364    where4tree<whereAxisRootNode> *newNode = new where4tree<whereAxisRootNode>(tempRootNode);
365    assert(newNode);
366
367    assert(hash.defines(parentUniqueId));
368    where4tree<whereAxisRootNode> *parentPtr = hash[parentUniqueId];
369    assert(parentPtr != NULL);
370
371    parentPtr->addChild(newNode, false, // not explicitly expanded
372                        consts,
373                        rethinkGraphicsNow,
374                        resortNow);
375
376    assert(!hash.defines(newNodeUniqueId));
377    hash[newNodeUniqueId] = newNode;
378    assert(hash.defines(newNodeUniqueId));
379 }
380
381 #ifndef PARADYN
382 // only the where axis test program uses this stuff:
383 int whereAxis::readTree(ifstream &is,
384                         resourceHandle parentUniqueId,
385                         resourceHandle nextUniqueIdToUse) {
386    // returns the number of new nodes added, 0 if nothing could be read
387    char c;
388    is >> c;
389    if (!is) return false;
390    if (c == ')') {
391       is.putback(c);
392       return 0;
393    }
394
395    const bool oneItemTree = (c != '(');
396    if (oneItemTree)
397       is.putback(c);
398
399    string rootString = readItem(is);
400    if (rootString.length() == 0)
401       return 0; // could not read root name
402
403    const bool thisIsWhereAxisRoot = (nextUniqueIdToUse==parentUniqueId);
404    if (thisIsWhereAxisRoot) {
405       // a little hack for the where axis root node, which can't be added
406       // using addItem().  Not to mention we have to set "whereAxis::rootPtr"
407
408       whereAxisRootNode tempRootNode(nextUniqueIdToUse, rootString);
409       where4tree<whereAxisRootNode> *newParentNode = new where4tree<whereAxisRootNode> (tempRootNode);
410
411       assert(newParentNode);
412
413       assert(nextUniqueIdToUse==0);
414       assert(!hash.defines(nextUniqueIdToUse));
415       hash[nextUniqueIdToUse] = newParentNode;
416       assert(hash.defines(nextUniqueIdToUse));
417
418       rootPtr = newParentNode;
419    }
420    else
421       this->addItem(rootString, parentUniqueId, nextUniqueIdToUse, false, false);
422          // don't redraw; don't resort
423
424    if (oneItemTree)
425       return 1;
426
427    parentUniqueId = nextUniqueIdToUse++;
428
429    // now the children (if any)
430    int result = 1; // number of nodes read in so far...
431    while (true) {
432       int localResult = readTree(is, parentUniqueId, nextUniqueIdToUse);
433       if (localResult == 0)
434          break;
435
436       result += localResult;
437       nextUniqueIdToUse += localResult;
438    }
439
440    // now, do some graphical rethinking w.r.t. "result" that we had suppressed
441    // up until now in the name of improved performance...
442    assert(hash.defines(parentUniqueId));
443    where4tree<whereAxisRootNode> *theParentNode = hash[parentUniqueId];
444
445    theParentNode->doneAddingChildren(consts, true); // true --> resort
446
447    // eat the closing )
448    is >> c;
449    if (c != ')') {
450       cerr << "expected ) to close tree, but found " << c << endl;
451       return 0;
452    }
453
454    return result;
455 }
456
457 // only the where axis test program gets this routine
458 whereAxis::whereAxis(ifstream &infile, Tcl_Interp *in_interp,
459                      Tk_Window theTkWindow,
460                      const char *iHorizSBName, const char *iVertSBName,
461                      const char *iNavigateMenuName) :
462                               consts(in_interp, theTkWindow),
463                               hash(hashFunc, 32),
464                               horizSBName(iHorizSBName),
465                               vertSBName(iVertSBName),
466                               navigateMenuName(iNavigateMenuName) {
467    initializeStaticsIfNeeded();
468
469    interp = in_interp;
470    horizScrollBarOffset = vertScrollBarOffset = 0;
471
472    resourceHandle rootNodeUniqueId = 0;
473    const int result = readTree(infile, rootNodeUniqueId, rootNodeUniqueId);
474    assert(result > 0);
475
476    beginSearchFromPtr = NULL;  
477
478    rethink_nominal_centerx();
479 }
480 #endif
481
482 void whereAxis::draw(bool doubleBuffer,
483                      bool xsynchronize // are we debugging?
484                      ) const {
485    Drawable theDrawable = (doubleBuffer && !xsynchronize) ? consts.offscreenPixmap :
486                                                             Tk_WindowId(consts.theTkWindow);
487
488    if (doubleBuffer || xsynchronize)
489       // clear the offscreen pixmap before drawing onto it
490       XFillRectangle(consts.display,
491                      theDrawable,
492                      consts.erasingGC,
493                      0, // x-offset relative to drawable
494                      0, // y-offset relative to drawable
495                      Tk_Width(consts.theTkWindow),
496                      Tk_Height(consts.theTkWindow)
497                      );
498
499    const int overallWindowBorderPix = 0;
500
501    if (rootPtr)
502       rootPtr->draw(consts.theTkWindow, consts, theDrawable,
503                     nominal_centerx + horizScrollBarOffset,
504                        // relative (not absolute) coord
505                     overallWindowBorderPix + vertScrollBarOffset,
506                        // relative (not absolute) coord
507                     false, // not root only
508                     false // not listbox only
509                     );
510
511    if (doubleBuffer && !xsynchronize)
512       // copy from offscreen pixmap onto the 'real' window
513       XCopyArea(consts.display,
514                 theDrawable, // source pixmap
515                 Tk_WindowId(consts.theTkWindow), // dest pixmap
516                 consts.erasingGC, // ?????
517                 0, 0, // source x,y pix
518                 Tk_Width(consts.theTkWindow),
519                 Tk_Height(consts.theTkWindow),
520                 0, 0 // dest x,y offset pix
521                 );
522 }
523
524 void whereAxis::resize(bool currentlyDisplayedAbstraction) {
525    const int newWindowWidth = Tk_Width(consts.theTkWindow);
526 //   const int newWindowHeight = Tk_Height(consts.theTkWindow); [not used]
527
528    consts.resize(); // reallocates offscreen pixmap; rethinks
529                     // 'listboxHeightWhereSBappears'
530
531    // Loop through EVERY listbox (gasp) and rethink whether or not it needs
532    // a scrollbar and (possibly) change listBoxActualHeight...
533    // _Must_ go _after_ the consts.resize()
534    (void)rootPtr->rethinkListboxAfterResize(consts);
535
536    // Now the more conventional resize code:
537    rootPtr->rethinkAfterResize(consts, newWindowWidth);
538    rethink_nominal_centerx(); // must go _after_ the rootPtr->rethinkAfterResize()
539
540    if (currentlyDisplayedAbstraction) {
541       // We have changed axis width and/or height.  Let's inform tcl, so it may
542       // rethink the scrollbar ranges.
543       resizeScrollbars();
544
545       // Now, let's update our own stored horiz & vert scrollbar offset values
546       adjustHorizSBOffset(); // obtain FirstPix from the actual tk scrollbar
547       adjustVertSBOffset (); // obtain FirstPix from the actual tk scrollbar
548    }
549 }
550
551 void whereAxis::processSingleClick(int x, int y) {
552    whereNodeGraphicalPath<whereAxisRootNode> thePath=point2path(x, y);
553
554    switch (thePath.whatDoesPathEndIn()) {
555       case whereNodeGraphicalPath<whereAxisRootNode>::Nothing:
556 //         cout << "single-click in nothing at (" << x << "," << y << ")" << endl;
557          return;
558       case whereNodeGraphicalPath<whereAxisRootNode>::ExpandedNode: {
559 //         cout << "click on an non-listbox item; adjusting NAVIGATE menu..." << endl;
560          lastClickPath = thePath.getPath();
561          rethinkNavigateMenu();
562
563          // Now redraw the node in question...(its highlightedness changed)
564          where4tree<whereAxisRootNode> *ptr = thePath.getLastPathNode(rootPtr);
565          ptr->toggle_highlight();
566          ptr->getNodeData().drawAsRoot(consts.theTkWindow,
567                                        Tk_WindowId(consts.theTkWindow),
568                                        thePath.get_endpath_centerx(),
569                                        thePath.get_endpath_topy());
570          return;
571       }
572       case whereNodeGraphicalPath<whereAxisRootNode>::ListboxItem:
573 //         cout << "click on a listbox item; adjusting NAVIGATE menu..." << endl;
574          lastClickPath = thePath.getPath();
575          rethinkNavigateMenu();
576
577          // Now we have to redraw the item in question...(its highlightedness changed)
578          thePath.getLastPathNode(rootPtr)->toggle_highlight();
579
580          thePath.getParentOfLastPathNode(rootPtr)->draw(consts.theTkWindow,
581                                                         consts, Tk_WindowId(consts.theTkWindow),
582                                                         thePath.get_endpath_centerx(),
583                                                         thePath.get_endpath_topy(),
584                                                         false, // not root only
585                                                         true // listbox only
586                                                         );
587          break;
588       case whereNodeGraphicalPath<whereAxisRootNode>::ListboxScrollbarUpArrow:
589       case whereNodeGraphicalPath<whereAxisRootNode>::ListboxScrollbarDownArrow:
590       case whereNodeGraphicalPath<whereAxisRootNode>::ListboxScrollbarPageup:
591       case whereNodeGraphicalPath<whereAxisRootNode>::ListboxScrollbarPagedown:
592          processNonSliderButtonPress(thePath);
593          return;
594       case whereNodeGraphicalPath<whereAxisRootNode>::ListboxScrollbarSlider: {
595 //         cout << "looks like a click in a listbox scrollbar slider" << endl;
596
597          where4tree<whereAxisRootNode> *parentPtr = thePath.getLastPathNode(rootPtr);
598
599          slider_initial_yclick = y;
600          slider_currently_dragging_subtree = parentPtr;
601
602          const int lbTop = thePath.get_endpath_topy() +
603                            parentPtr->getNodeData().getHeightAsRoot() +
604                            consts.vertPixParent2ChildTop;
605
606          int dummyint;
607          parentPtr->getScrollbar().getSliderCoords(lbTop,
608                      lbTop + parentPtr->getListboxActualPixHeight() - 1,
609                      parentPtr->getListboxActualPixHeight() - 2*listboxBorderPix,
610                      parentPtr->getListboxFullPixHeight() - 2*listboxBorderPix,
611                      slider_initial_scrollbar_slider_top, // filled in
612                      dummyint);
613
614          slider_scrollbar_left = thePath.get_endpath_centerx() -
615                                  parentPtr->horiz_pix_everything_below_root(consts) / 2;
616          slider_scrollbar_top = thePath.get_endpath_topy() +
617                                 parentPtr->getNodeData().getHeightAsRoot() +
618                                 consts.vertPixParent2ChildTop;
619          slider_scrollbar_bottom = slider_scrollbar_top +
620                                    parentPtr->getListboxActualPixHeight() - 1;
621
622 //         cout << "slider click was on subtree whose root name is "
623 //              << parentPtr->getRootName() << endl;
624
625          Tk_CreateEventHandler(consts.theTkWindow,
626                                ButtonReleaseMask,
627                                sliderButtonRelease,
628                                this);
629          Tk_CreateEventHandler(consts.theTkWindow,
630                                PointerMotionMask,
631                                sliderMouseMotion,
632                                this);
633          break;
634       }
635       default:
636          assert(false);
637    }
638 }
639
640 /* ***************************************************************** */
641
642 bool whereAxis::processShiftDoubleClick(int x, int y) {
643    // returns true iff a complete redraw is called for
644
645    whereNodeGraphicalPath<whereAxisRootNode> thePath = point2path(x, y);
646
647    switch (thePath.whatDoesPathEndIn()) {
648       case whereNodeGraphicalPath<whereAxisRootNode>::Nothing:
649 //         cout << "shift-double-click in nothing" << endl;
650          return false;
651       case whereNodeGraphicalPath<whereAxisRootNode>::ListboxItem:
652 //         cout << "shift-double-click in lb item; ignoring" << endl;
653          return false;
654       case whereNodeGraphicalPath<whereAxisRootNode>::ExpandedNode:
655          break; // some breathing room for lots of code to follow...
656       case whereNodeGraphicalPath<whereAxisRootNode>::ListboxScrollbarUpArrow:
657       case whereNodeGraphicalPath<whereAxisRootNode>::ListboxScrollbarDownArrow:
658       case whereNodeGraphicalPath<whereAxisRootNode>::ListboxScrollbarPageup:
659       case whereNodeGraphicalPath<whereAxisRootNode>::ListboxScrollbarPagedown:
660          // in this case, do the same as a single-click
661          processNonSliderButtonPress(thePath);
662          return false; // no need to redraw further
663       case whereNodeGraphicalPath<whereAxisRootNode>::ListboxScrollbarSlider:
664 //         cout << "shift-double-click in a listbox scrollbar slider...doing nothing" << endl;
665          return false;
666       default:
667          assert(false);
668    }
669
670    // How do we know whether to expand-all or un-expand all?
671    // Well, if there is no listbox up, then we should _definitely_ unexpand.
672    // And, if there is a listbox up containing all items (no explicitly expanded items),
673    //    then we should _definitely_ expand.
674    // But, if there is a listbox up with _some_ explicitly expanded items, then
675    //    are we supposed to expand out the remaining ones; or un-expand the expanded
676    //    ones?  It could go either way.  For now, we'll say that we should un-expand.
677
678    bool anyChanges = false; // so far...
679    where4tree<whereAxisRootNode> *ptr = thePath.getLastPathNode(rootPtr);
680    ptr->toggle_highlight(); // doesn't redraw
681
682    if (ptr->getListboxPixWidth() > 0) {
683       bool noExplicitlyExpandedChildren = true; // so far...
684       for (unsigned childlcv=0; childlcv < ptr->getNumChildren(); childlcv++)
685          if (ptr->getChildIsExpandedFlag(childlcv)) {
686             noExplicitlyExpandedChildren = false; 
687             break;
688          }
689
690       if (noExplicitlyExpandedChildren)
691          // There is a listbox up, and it contains ALL children.
692          // So, obviously, we want to expand them all...
693          anyChanges = rootPtr->path2ExpandAllChildren(consts, thePath.getPath(), 0);
694       else
695          // There is a listbox up, but there are also some expanded children.
696          // This call (whether to expand the remaining listbox items, or whether
697          // to un-expand the expanded items) could go either way; we choose the
698          // latter (for now, at least)
699          anyChanges = rootPtr->path2UnExpandAllChildren(consts, thePath.getPath(), 0);
700    }
701    else
702       // No listbox is up; hence, all children are expanded.
703       // So obviously, we wish to un-expand all the children.
704       anyChanges = rootPtr->path2UnExpandAllChildren(consts, thePath.getPath(), 0);
705
706    if (!anyChanges)
707       return false;
708
709    rethink_nominal_centerx();
710
711    // We have changed axis width and/or height.  Let's inform tcl, so it may
712    // rethink the scrollbar ranges.
713    resizeScrollbars();
714
715    // Now, let's update our own stored horiz & vert scrollbar offset values
716    adjustHorizSBOffset(); // obtain FirstPix from the actual tk scrollbar
717    adjustVertSBOffset (); // obtain FirstPix from the actual tk scrollbar
718
719    return true;
720 }
721
722 bool whereAxis::processCtrlDoubleClick(int x, int y) {
723    // returns true iff changes were made
724
725    whereNodeGraphicalPath<whereAxisRootNode> thePath = point2path(x, y);
726
727    if (thePath.whatDoesPathEndIn() != whereNodeGraphicalPath<whereAxisRootNode>::ExpandedNode)
728       return false;
729
730 //   cout << "ctrl-double-click:" << endl;
731
732    // How do we know whether to select-all or unselect-all?
733    // Well, if noone is selected, then we should _definitely_ select all.
734    // And, if everyone is selected, then we should _definitely_ unselect all.
735    // But, if _some_ items are selected, what should we do?  It could go either way.
736    // For now, we'll say unselect.
737
738    where4tree<whereAxisRootNode> *ptr = thePath.getLastPathNode(rootPtr);
739
740    // change highlightedness of the root node (i.e. the double-click should undo
741    // the effects of the earlier single-click, now that we know that a double-click
742    // was the user's intention all along)
743    ptr->toggle_highlight(); // doesn't redraw
744
745    if (ptr->getNumChildren()==0)
746       return true; // changes were made
747
748    bool allChildrenSelected = true;
749    bool noChildrenSelected = true;
750    for (unsigned childlcv=0; childlcv < ptr->getNumChildren(); childlcv++)
751       if (ptr->getChildTree(childlcv)->isHighlighted()) {
752          noChildrenSelected = false;
753          if (!allChildrenSelected)
754             break;
755       }
756       else {
757          allChildrenSelected = false;
758          if (!noChildrenSelected)
759             break;
760       }
761
762    assert(!(allChildrenSelected && noChildrenSelected));
763
764    if (allChildrenSelected || !noChildrenSelected) {
765       // unselect all children
766       for (unsigned childlcv=0; childlcv < ptr->getNumChildren(); childlcv++)
767          ptr->getChildTree(childlcv)->unhighlight();
768       return true; // changes were made
769    }
770    else {
771       assert(noChildrenSelected);
772       for (unsigned childlcv=0; childlcv < ptr->getNumChildren(); childlcv++)
773          ptr->getChildTree(childlcv)->highlight();
774       return true; // changes were made
775    }
776 }
777
778 bool whereAxis::processDoubleClick(int x, int y) {
779    // returns true iff a complete redraw is called for
780
781    bool scrollToWhenDone = false; // for now...
782
783    whereNodeGraphicalPath<whereAxisRootNode> thePath = point2path(x, y);
784
785    switch (thePath.whatDoesPathEndIn()) {
786       case whereNodeGraphicalPath<whereAxisRootNode>::Nothing:
787 //         cout << "looks like a double-click in nothing" << endl;
788          return false;
789       case whereNodeGraphicalPath<whereAxisRootNode>::ListboxScrollbarUpArrow:
790       case whereNodeGraphicalPath<whereAxisRootNode>::ListboxScrollbarDownArrow:
791       case whereNodeGraphicalPath<whereAxisRootNode>::ListboxScrollbarPageup:
792       case whereNodeGraphicalPath<whereAxisRootNode>::ListboxScrollbarPagedown:
793          // in this case, do the same as a single-click
794          processNonSliderButtonPress(thePath);
795          return false; // no need to redraw further
796       case whereNodeGraphicalPath<whereAxisRootNode>::ListboxScrollbarSlider:
797 //         cout << "double-click in a listbox scrollbar slider...doing nothing" << endl;
798          return false;
799       case whereNodeGraphicalPath<whereAxisRootNode>::ExpandedNode: {
800          // double-click in a "regular" node (not in listbox): un-expand
801 //         cout << "double-click on a non-listbox item" << endl;
802
803          if (thePath.getSize() == 0) {
804 //            cout << "double-click un-expansion on the root..ignoring" << endl;
805             return false;
806          }
807
808          if (!rootPtr->path2lbItemUnexpand(consts, thePath.getPath(), 0)) {
809             // probably an attempt to expand a leaf item (w/o a triangle next to it)
810 //            cout << "could not un-expand this subtree (a leaf?)...continuing" << endl;
811             return false;
812          }
813
814          // Now let's scroll to the un-expanded item.
815          rethink_nominal_centerx();
816          resizeScrollbars();
817          adjustHorizSBOffset();
818          adjustVertSBOffset();
819          softScrollToEndOfPath(thePath.getPath());
820          return true;
821       }
822       case whereNodeGraphicalPath<whereAxisRootNode>::ListboxItem: {
823          // double-click in a listbox item
824 //         cout << "double-click on a listbox item" << endl;
825          // first thing's first: now that we know the user intended to do a double-click
826          // all along, we should undo the effects of the single-click which came earlier.
827          thePath.getLastPathNode(rootPtr)->toggle_highlight(); // doesn't redraw
828
829          const bool anyChanges = rootPtr->path2lbItemExpand(consts,
830                                                             thePath.getPath(), 0);
831
832          if (!anyChanges) {
833             // The only real change we made was the toggle_highlight().  This is
834             // a case where we can do the redrawing ourselves (and fast).
835             thePath.getParentOfLastPathNode(rootPtr)->
836                  draw(consts.theTkWindow, consts, Tk_WindowId(consts.theTkWindow),
837                       thePath.get_endpath_centerx(),
838                       thePath.get_endpath_topy(),
839                       false, // not root only
840                       true // listbox only
841                       );
842             return false;
843          }
844          else {
845             // expansion was successful...later, we'll scroll to the expanded item.
846             // NOTE: rootPtr->path2lbItemExpand will have modified "thePath"
847             //       for us (by changing the last item from a listbox item to
848             //       an expanded one, which is the proper thing to do).
849
850             scrollToWhenDone = true;
851          }
852          break;
853       }
854       default:
855          assert(false);
856    }
857
858    // expansion or un-expansion successful
859
860    rethink_nominal_centerx();
861
862    // We have changed axis width and/or height.  Let's inform tcl, so it may
863    // rethink the scrollbar ranges.
864    resizeScrollbars();
865
866    // Now, let's update our own stored horiz & vert scrollbar offset values
867    adjustHorizSBOffset(); // obtain FirstPix from the actual tk scrollbar
868    adjustVertSBOffset (); // obtain FirstPix from the actual tk scrollbar
869
870    if (scrollToWhenDone) {
871       const int overallWindowBorderPix = 0;
872
873       whereNodeGraphicalPath<whereAxisRootNode>
874            path_to_scroll_to(thePath.getPath(),
875                         consts, rootPtr,
876                         nominal_centerx,
877                            // root centerx (abs. pos., regardless of sb [intentional])
878                         overallWindowBorderPix
879                            // topy of root (abs. pos., regardless of sb [intentional])
880                         );
881
882       int newlyExpandedElemCenterX = path_to_scroll_to.get_endpath_centerx();
883       int newlyExpandedElemTopY    = path_to_scroll_to.get_endpath_topy();
884       int newlyExpandedElemMiddleY = newlyExpandedElemTopY +
885                                      path_to_scroll_to.getLastPathNode(rootPtr)->getNodeData().getHeightAsRoot() / 2;
886
887       (void)set_scrollbars(newlyExpandedElemCenterX, x,
888                            newlyExpandedElemMiddleY, y,
889                            true);
890    }
891
892    return true;
893 }
894
895 bool whereAxis::softScrollToPathItem(const whereNodePosRawPath &thePath,
896                                      unsigned index) {
897    // scrolls s.t. the (centerx, topy) of path index is in middle of screen.
898
899    whereNodePosRawPath newPath = thePath;
900      // make a copy, because we're gonna hack it up a bit.
901    newPath.rigSize(index);
902    return softScrollToEndOfPath(newPath);
903 }
904
905 bool whereAxis::forciblyScrollToPathItem(const whereNodePosRawPath &thePath,
906                                          unsigned pathLen) {
907    // Simply a stub which generates a new path and calls forciblyScrollToEndOfPath()   
908    // "index" indicates the length of the new path; in particular, 0 will give
909    // and empty path (the root node).
910
911    // make a copy of "thePath", truncate it to "pathLen", and forciblyScrollToEndOfPath
912    whereNodePosRawPath newPath = thePath;
913    newPath.rigSize(pathLen);
914    return forciblyScrollToEndOfPath(newPath);
915 }
916
917 bool whereAxis::softScrollToEndOfPath(const whereNodePosRawPath &thePath) {
918    const int overallWindowBorderPix = 0;
919    whereNodeGraphicalPath<whereAxisRootNode>
920          scrollToPath(thePath, consts, rootPtr,
921                       nominal_centerx,
922                          // ignores scrollbar settings (an absolute coord),
923                       overallWindowBorderPix
924                          // absolute coord of root topy
925                       );
926    const int last_item_centerx = scrollToPath.get_endpath_centerx();
927    const int last_item_topy    = scrollToPath.get_endpath_topy();
928       // note: if the path ends in an lb item, these coords refer to the PARENT
929       //       node, which is expanded.
930
931    switch (scrollToPath.whatDoesPathEndIn()) {
932       case whereNodeGraphicalPath<whereAxisRootNode>::ExpandedNode: {
933 //         cout << "soft scrolling to expanded node" << endl;
934          const int last_item_middley =
935             last_item_topy +
936             scrollToPath.getLastPathNode(rootPtr)->getNodeData().getHeightAsRoot() / 2;
937          return set_scrollbars(last_item_centerx,
938                                Tk_Width(consts.theTkWindow) / 2,
939                                last_item_middley,
940                                Tk_Height(consts.theTkWindow) / 2,
941                                true);
942       }
943       case whereNodeGraphicalPath<whereAxisRootNode>::ListboxItem: {
944 //         cout << "soft scrolling to lb item" << endl;
945
946          // First, let's scroll within the listbox (no redrawing yet)
947          where4tree<whereAxisRootNode> *parent = scrollToPath.getParentOfLastPathNode(rootPtr);
948
949          Tk_FontMetrics lbFontMetrics; // filled in by Tk_GetFontMetrics()
950          Tk_GetFontMetrics(consts.listboxFontStruct, &lbFontMetrics);
951          const unsigned itemHeight = consts.listboxVertPadAboveItem +
952                                      lbFontMetrics.ascent +
953                                      consts.listboxVertPadAfterItemBaseline;
954    
955          int scrollToVertPix = 0; // relative to listbox top
956          const unsigned childnum = scrollToPath.getPath().getLastItem();
957          for (unsigned childlcv=0; childlcv < childnum; childlcv++)
958             if (!parent->getChildIsExpandedFlag(childlcv))
959                scrollToVertPix += itemHeight;
960
961          int destItemRelToListboxTop = scrollToVertPix;   
962          if (parent->getScrollbar().isValid()) {
963             (void)parent->scroll_listbox(consts, scrollToVertPix -
964                                          parent->getScrollbar().getPixFirst());
965
966             destItemRelToListboxTop -= parent->getScrollbar().getPixFirst();
967          }
968
969          if (destItemRelToListboxTop < 0) {
970             cout << "note: softScrollToEndOfPath() failed to scroll properly to item w/in listbox" << endl;
971          }
972
973          return set_scrollbars(scrollToPath.get_endpath_centerx() -
974                                   parent->horiz_pix_everything_below_root(consts)/2 +
975                                   parent->getListboxPixWidth() / 2,
976                                   // listbox centerx
977                                Tk_Width(consts.theTkWindow) / 2,
978                                scrollToPath.get_endpath_topy() +
979                                   parent->getNodeData().getHeightAsRoot() +
980                                   consts.vertPixParent2ChildTop +
981                                   destItemRelToListboxTop + itemHeight / 2,
982                                   // should be the middley of the lb item
983                                Tk_Height(consts.theTkWindow) / 2,
984                                true);
985       }
986       default: assert(false);
987    }
988
989    assert(false);
990    return false; // placate compiler
991 }
992
993 bool whereAxis::forciblyScrollToEndOfPath(const whereNodePosRawPath &thePath) {
994    // Forcibly expands path items as needed, then calls softScrollToEndOfPath()
995
996    const bool anyChanges = rootPtr->expandEntirePath(consts, thePath, 0);
997    if (anyChanges) {
998       rethink_nominal_centerx();
999       resizeScrollbars();
1000       adjustHorizSBOffset();
1001       adjustVertSBOffset();
1002    }
1003
1004    softScrollToEndOfPath(thePath);
1005
1006    return anyChanges;
1007 }
1008
1009 void whereAxis::navigateTo(unsigned pathLen) {
1010    (void)forciblyScrollToPathItem(lastClickPath, pathLen);
1011 }
1012
1013 int whereAxis::find(const string &str) {
1014    // does a blind search for the given string.  Expands things along the
1015    // way if needed.  Returns 0 if not found, 1 if found & no expansions
1016    // were needed, 2 if found & expansion(s) _were_ needed
1017
1018    // Our strategy: (1) make a path out of the string.
1019    //               (2) call a routine that "forcefully" scrolls to the end of that
1020    //                   path.  We say "forcefully" beceause this routine will
1021    //                   expand items along the way, if necessary.
1022
1023    // Uses and alters "beginSearchFromPtr"
1024
1025    whereNodePosRawPath thePath;
1026    int result = rootPtr->string2Path(thePath, consts, str, beginSearchFromPtr, true);
1027
1028    if (result == 0)
1029       if (beginSearchFromPtr == NULL)
1030          return 0; // not found
1031       else {
1032          // try again with beginSearchFromPtr of NULL (wrap-around search)
1033 //         cout << "search wrap-around" << endl;
1034          beginSearchFromPtr = NULL;
1035          return find(str);
1036       }
1037
1038    // found.  Update beginSearchFromPtr.
1039    where4tree<whereAxisRootNode> *beginSearchFromPtr = rootPtr;
1040    for (unsigned i=0; i < thePath.getSize(); i++)
1041       beginSearchFromPtr = beginSearchFromPtr->getChildTree(thePath[i]);
1042
1043    if (result==1)
1044       (void)softScrollToEndOfPath(thePath);
1045    else {
1046       assert(result==2);
1047       bool aflag;
1048       aflag = (forciblyScrollToEndOfPath(thePath));
1049       // rethinks nominal centerx, resizes scrollbars, etc.
1050       assert(aflag);
1051    }
1052
1053    return result;
1054 }
1055
1056 bool whereAxis::adjustHorizSBOffset(float newFirst) {
1057    // does not redraw.  Returns true iff any changes.
1058
1059    // First, we need to make the change to the tk scrollbar
1060    newFirst = moveScrollBar(interp, horizSBName, newFirst);
1061
1062    // Then, we update our C++ variables
1063    int widthOfEverything = rootPtr->entire_width(consts);
1064    int oldHorizScrollBarOffset = horizScrollBarOffset;
1065    horizScrollBarOffset = -(int)(newFirst * widthOfEverything);
1066       // yes, horizScrollBarOffset is always negative (unless it's zero)
1067    return (horizScrollBarOffset != oldHorizScrollBarOffset);
1068 }
1069
1070 bool whereAxis::adjustHorizSBOffsetFromDeltaPix(int deltapix) {
1071    // does not redraw.  Returns true iff any changes.
1072
1073    const int widthOfEverything = rootPtr->entire_width(consts);
1074    float newFirst = (float)(-horizScrollBarOffset + deltapix) / widthOfEverything;
1075    return adjustHorizSBOffset(newFirst);
1076 }
1077
1078 //template <class NODEDATA>
1079 //bool whereAxis<NODEDATA>::adjustHorizSBOffsetFromDeltaPages(int deltapages) {
1080 //   // does not redraw.  Returns true iff any changes.
1081 //
1082 //   // First, update the tk scrollbar
1083 //   const int widthOfEverything = rootPtr->entire_width(consts);
1084 //   const int widthOfVisible = Tk_Width(consts.theTkWindow);
1085 //   float newFirst = (float)(-horizScrollBarOffset + widthOfVisible*deltapages) /
1086 //                     widthOfEverything;
1087 //   return adjustHorizSBOffset(newFirst);
1088 //}
1089
1090 bool whereAxis::adjustHorizSBOffset() {
1091    // Does not redraw.  Obtains PixFirst from actual tk scrollbar.
1092    float first, last; // fractions (0.0 to 1.0)
1093    getScrollBarValues(interp, horizSBName, first, last);
1094
1095    return adjustHorizSBOffset(first);
1096 }
1097
1098 bool whereAxis::adjustVertSBOffset(float newFirst) {
1099    // does not redraw.  Returns true iff any changes.
1100    // First, we need to make the change to the tk scrollbar
1101    newFirst = moveScrollBar(interp, vertSBName, newFirst);
1102
1103    // Then, we update our C++ variables
1104    int heightOfEverything = rootPtr->entire_height(consts);
1105    int oldVertScrollBarOffset = vertScrollBarOffset;
1106    vertScrollBarOffset = -(int)(newFirst * heightOfEverything);
1107       // yes, vertScrollBarOffset is always negative (unless it's zero)
1108    return (vertScrollBarOffset != oldVertScrollBarOffset);
1109 }
1110
1111 bool whereAxis::adjustVertSBOffsetFromDeltaPix(int deltapix) {
1112    // does not redraw.  Returns true iff any changes were made.
1113
1114    const int heightOfEverything = rootPtr->entire_height(consts);
1115    float newFirst = (float)(-vertScrollBarOffset + deltapix) / heightOfEverything;
1116    return adjustVertSBOffset(newFirst);
1117 }
1118
1119 //template <class NODEDATA>
1120 //bool whereAxis<NODEDATA>::adjustVertSBOffsetFromDeltaPages(int deltapages) {
1121 //   // does not redraw
1122 //
1123 //   // First, update the tk scrollbar
1124 //   const int heightOfEverything = rootPtr->entire_height(consts);
1125 //   const int heightOfVisible = Tk_Height(consts.theTkWindow);
1126 //   float newFirst = (float)(-vertScrollBarOffset + heightOfVisible*deltapages) /
1127 //                    heightOfEverything;
1128 //   return adjustVertSBOffset(newFirst);
1129 //}
1130
1131 bool whereAxis::adjustVertSBOffset() {
1132    // Does not redraw.  Obtains PixFirst from actual tk scrollbar.
1133    float first, last; // fractions (0.0 to 1.0)
1134    getScrollBarValues(interp, vertSBName, first, last);
1135
1136    return adjustVertSBOffset(first);
1137 }
1138
1139 /* ************************************************************************ */
1140
1141 void whereAxis::rethinkNavigateMenu() {
1142    // We loop through the items of "lastClickPath".  For each item,
1143    // we get the name of the root node, and append the full-path entry
1144    // to the navigate menu.
1145
1146    where4tree<whereAxisRootNode> *currTree = rootPtr;   
1147
1148    // Note: in tk4.0, menu indices start at 1, not 0 (0 is reserved for tearoff)
1149    string commandStr = navigateMenuName + " delete 1 100";
1150    myTclEval(interp, commandStr);
1151
1152    string theString;
1153
1154    unsigned itemlcv = 0;
1155    while (true) {
1156       if (itemlcv >= 1)
1157          theString += "/";
1158       theString += currTree->getNodeData().getName();
1159
1160 //      cout << "adding " << theString << " to the navigate menu" << endl;
1161
1162       string commandStr = navigateMenuName + " add command -label \"" + theString +
1163                           "\" -command \"whereAxisNavigateTo " + string(itemlcv) + "\"";
1164       myTclEval(interp, commandStr);
1165
1166       if (itemlcv >= lastClickPath.getSize())
1167          break;
1168
1169       unsigned theItem = lastClickPath[itemlcv++];
1170       currTree = currTree->getChildTree(theItem);
1171    }
1172 }
1173
1174 bool whereAxis::selectUnSelectFromFullPathName(const string &name, bool selectFlag) {
1175    // returns true iff found
1176    const char *str = name.string_of();
1177    if (str == NULL)
1178       return false;
1179
1180    return rootPtr->selectUnSelectFromFullPathName(str, selectFlag);
1181 }
1182
1183 vector< vector<resourceHandle> >
1184 whereAxis::getSelections(bool &wholeProgram,
1185                          vector<unsigned> &wholeProgramFocus) const {
1186    // returns a vector[num-hierarchies] of vector of selections.
1187    // The number of hierarchies is defined as the number of children of the
1188    // root node.  If "Whole Program" was selection, it isn't returned with
1189    // the main result; it's returned by modifying the 2 params
1190    const unsigned numHierarchies = rootPtr->getNumChildren();
1191
1192    vector < vector<resourceHandle> > result(numHierarchies);
1193
1194    bool wholeProgramImplicit = true; // so far...
1195
1196    for (unsigned i=0; i < numHierarchies; i++) {
1197       where4tree<whereAxisRootNode> *hierarchyRoot = rootPtr->getChildTree(i);
1198       vector <const whereAxisRootNode *> thisHierarchySelections = hierarchyRoot->getSelections();
1199
1200       if (thisHierarchySelections.size()==0)
1201          // add hierarchy's root item
1202          thisHierarchySelections += &hierarchyRoot->getNodeData();
1203       else
1204          // since the hierarchy selection was not empty, we do _not_
1205          // want to implicitly select whole-program
1206          wholeProgramImplicit = false;
1207
1208       result[i].resize(thisHierarchySelections.size());
1209       for (unsigned j=0; j < thisHierarchySelections.size(); j++)
1210          result[i][j] = thisHierarchySelections[j]->getUniqueId();
1211    }
1212
1213    wholeProgram = wholeProgramImplicit || rootPtr->isHighlighted();
1214    if (wholeProgram) {
1215       // write to wholeProgramFocus:
1216       wholeProgramFocus.resize(numHierarchies);
1217       for (unsigned i=0; i < numHierarchies; i++) {
1218          where4tree<whereAxisRootNode> *hierarchyRoot = rootPtr->getChildTree(i);
1219          const whereAxisRootNode &hierarchyRootData = hierarchyRoot->getNodeData();
1220          unsigned hierarchyRootUniqueId = hierarchyRootData.getUniqueId();
1221          wholeProgramFocus[i] = hierarchyRootUniqueId;
1222       }
1223    }
1224
1225    return result;
1226 }
1227
1228 void whereAxis::clearSelections() {
1229    rootPtr->recursiveClearSelections();
1230 }