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