horizontal & vertical grid lines no longer expand past the
[dyninst.git] / visiClients / tableVisi / src / tableVisi.C
1 // tableVisi.C
2 // Ariel Tamches
3
4 /*
5  * $Log: tableVisi.C,v $
6  * Revision 1.4  1995/11/20 20:20:20  tamches
7  * horizontal & vertical grid lines no longer expand past the
8  * last cells.
9  *
10  * Revision 1.3  1995/11/15 01:05:16  tamches
11  * fixed bug which clipped cells incorrectly
12  *
13  * Revision 1.2  1995/11/08 21:46:46  tamches
14  * some ui bug fixes
15  *
16  * Revision 1.1  1995/11/04 00:45:20  tamches
17  * First version of new table visi
18  *
19  */
20
21 #include "minmax.h"
22 #include "tkTools.h"
23 #include "tableVisi.h"
24
25 /* ************************************************************* */
26
27 extern "C" {bool isnan(double);}
28 void tableVisi::double2string(char *buffer, double val) const {
29    // uses numSigFigs
30    if (isnan(val)) {
31       buffer[0] = '\0';
32       return;
33    }
34
35    char conversionString[100];
36    sprintf(conversionString, "%%.%dg", numSigFigs);
37    sprintf(buffer, conversionString, val);
38
39 //   cout << "from " << buffer << " to " << flush;
40
41    // add commas
42    if (strlen(buffer)==0)
43       return;
44
45    const char *decPtr = strchr(buffer, '.');
46
47    if (decPtr && (decPtr - buffer <= 3))
48       return; // no commas will be added since there aren't at least 4 integer digits
49
50    if (decPtr == NULL) {
51       // try for exponential notation
52       decPtr = strchr(buffer, 'e');
53
54       if (decPtr==NULL)
55          decPtr = &buffer[strlen(buffer)]; // the '\0'
56    }
57
58    // invariant: decPtr now points to the character AFTER the
59    // last integer digit (i.e., the decimal point), or the '\0'
60    // if none exists.
61
62    // Now let's walk backwards, looking for places to insert commas
63    char integerPartBuffer[200]; // will include commas
64    char *integerPart = integerPartBuffer;
65    const char *walkPtr = decPtr-1;
66    unsigned copyCount=0;
67    while (walkPtr >= buffer) {
68       if (copyCount > 0 && copyCount % 3 == 0)
69          *integerPart++ = ',';
70
71       copyCount++;
72       *integerPart++ = *walkPtr--;
73    }
74
75    char decimalPart[200];
76       // will incl dec point, if applicable.  May be
77       // the empty-string.
78    strcpy(decimalPart, decPtr);
79
80    // note: integerPartBuffer is backwards...we must reverse it
81    char *bufferPtr = buffer;
82    while (--integerPart >= integerPartBuffer)
83       *bufferPtr++ = *integerPart;
84    strcpy(bufferPtr, decimalPart);
85
86 //   cout << buffer << endl;
87 }
88
89 /* ************************************************************* */
90
91 XFontStruct *tableVisi::myXLoadQueryFont(const string &fontName) const {
92    XFontStruct *result = XLoadQueryFont(Tk_Display(theTkWindow),
93                                         fontName.string_of());
94    if (result == NULL) {
95       cerr << "could not find font " << fontName << endl;
96       exit(5);
97    }
98
99    return result;
100 }
101
102 XColor *tableVisi::myTkGetColor(Tcl_Interp *interp, const string &colorName) const {
103    XColor *result = Tk_GetColor(interp, theTkWindow, Tk_GetUid(colorName.string_of()));
104    if (result == NULL) {
105       cerr << "could not allocate color " << colorName << endl;
106       exit(5);
107    }
108    return result;
109 }
110
111 /* ************************************************************* */
112
113 tableVisi::tableVisi(Tcl_Interp *interp,
114                      Tk_Window iTkWindow,
115                      const string &metricFontName,
116                      const string &metricUnitsFontName,
117                      const string &focusFontName,
118                      const string &cellFontName,
119                      const string &iLineColorName,
120                      const string &iMetricColorName,
121                      const string &iMetricUnitsColorName,
122                      const string &iFocusColorName,
123                      const string &cellColorName,
124                      const string &backgroundColorName,
125                      unsigned iSigFigs
126                      ) {
127    // metrics[], foci[], indirectMetrics[], indirectFoci[], and cells[][]
128    // are all initialized to zero-sized arrays.
129
130    theTkWindow = iTkWindow;
131    theDisplay = Tk_Display(theTkWindow);
132
133    offscreenPixmap = (Pixmap)NULL;
134       // sorry, can't XCreatePixmap() until the window becomes mapped.
135
136    backgroundColor = myTkGetColor(interp, backgroundColorName);
137
138    offset_x = offset_y = 0;
139    all_cells_width = all_cells_height = 0;
140
141    metricNameFont = myXLoadQueryFont(metricFontName);
142    metricUnitsFont = myXLoadQueryFont(metricUnitsFontName);
143    focusNameFont = myXLoadQueryFont(focusFontName);
144    cellFont = myXLoadQueryFont(cellFontName);
145
146    focusLongNameMode = true;
147    numSigFigs = iSigFigs;
148 //   dataFormat = Current;
149
150    maxFocusNamePixWidth = 0;
151
152    // The GC's, like offscreenPixmap, can't be created until the
153    // window becomes mapped.
154    lineColor = myTkGetColor(interp, iLineColorName);
155    lineColorGC = NULL;
156
157    metricNameColor = myTkGetColor(interp, iMetricColorName);
158    metricNameGC = NULL;
159
160    metricUnitsColor = myTkGetColor(interp, iMetricUnitsColorName);
161    metricUnitsGC = NULL;
162   
163    focusNameColor = myTkGetColor(interp, iFocusColorName);
164    focusNameGC = NULL;
165
166    cellColor = myTkGetColor(interp, cellColorName);
167    cellGC = NULL;
168 }
169
170 tableVisi::~tableVisi() {
171    // arrays metrics[], foci[], indirectMetrics[], indirectFoci[], cells[][] will
172    // delete themselves.
173
174    Tk_FreeColor(cellColor);
175    Tk_FreeColor(focusNameColor);
176    Tk_FreeColor(metricUnitsColor);
177    Tk_FreeColor(metricNameColor);
178    Tk_FreeColor(lineColor);
179    Tk_FreeColor(backgroundColor);
180
181    XFreeFont(theDisplay, focusNameFont);
182    XFreeFont(theDisplay, metricUnitsFont);
183    XFreeFont(theDisplay, metricNameFont);
184
185    if (!offscreenPixmap)
186       // the offscreen pixmap was never allocated(!)...so, we never
187       // got around to mapping the window!
188       return;
189
190    XFreeGC(theDisplay, cellGC);
191    XFreeGC(theDisplay, focusNameGC);
192    XFreeGC(theDisplay, metricUnitsGC);
193    XFreeGC(theDisplay, metricNameGC);
194    XFreeGC(theDisplay, lineColorGC);
195    XFreeGC(theDisplay, backgroundGC);
196
197    XFreePixmap(theDisplay, offscreenPixmap);
198 }
199
200 bool tableVisi::tryFirst() {
201    if (offscreenPixmap) {
202       // the offscreen pixmap has been allocated, so the window
203       // has presumably been mapped already
204       assert(Tk_WindowId(theTkWindow) != 0);
205       return true;
206    }
207
208    // the offscreen pixmap hasn't been allocated, so it's now time
209    // to check to see if it should be.
210    if (Tk_WindowId(theTkWindow) == 0)
211       return false; // nuts; not ready yet
212
213    // Ready to allocate graphical structures now!
214    offscreenPixmap = XCreatePixmap(Tk_Display(theTkWindow),
215                                    Tk_WindowId(theTkWindow),
216                                    1, 1, // dummy width, height
217                                    Tk_Depth(theTkWindow));
218    XGCValues values;
219    values.foreground = backgroundColor->pixel;
220    backgroundGC = XCreateGC(Tk_Display(theTkWindow), Tk_WindowId(theTkWindow),
221                             GCForeground, &values);
222
223    values.foreground = lineColor->pixel;
224    lineColorGC = XCreateGC(Tk_Display(theTkWindow), Tk_WindowId(theTkWindow),
225                            GCForeground, &values);
226
227    values.foreground = metricNameColor->pixel;
228    values.font = metricNameFont->fid;
229    metricNameGC = XCreateGC(Tk_Display(theTkWindow), Tk_WindowId(theTkWindow),
230                             GCForeground | GCFont, &values);
231
232    values.foreground = metricUnitsColor->pixel;
233    values.font = metricUnitsFont->fid;
234    metricUnitsGC = XCreateGC(Tk_Display(theTkWindow), Tk_WindowId(theTkWindow),
235                              GCForeground | GCFont, &values);
236
237    values.foreground = focusNameColor->pixel;
238    values.font = focusNameFont->fid;
239    focusNameGC = XCreateGC(Tk_Display(theTkWindow), Tk_WindowId(theTkWindow),
240                            GCForeground | GCFont, &values);
241
242    values.foreground = cellColor->pixel;
243    values.font = cellFont->fid;
244    cellGC = XCreateGC(Tk_Display(theTkWindow), Tk_WindowId(theTkWindow),
245                       GCForeground | GCFont, &values);
246
247    return true;
248 }
249
250 void tableVisi::resizeScrollbars(Tcl_Interp *interp) {
251    // used to be a tcl routine (resize1Scrollbar):
252
253    const int visible_cell_width = Tk_Width(theTkWindow) -
254                                   getFocusAreaPixWidth();
255    resizeScrollbar(interp, ".horizScrollbar",
256                    all_cells_width, // total
257                    visible_cell_width);
258
259    const int visible_cell_height = Tk_Height(theTkWindow) -
260                                    getMetricAreaPixHeight();
261    resizeScrollbar(interp, ".vertScrollbar",
262                    all_cells_height, // total
263                    visible_cell_height);
264 }
265
266 bool tableVisi::adjustHorizSBOffset(Tcl_Interp *interp) {
267    float first, last;
268    getScrollBarValues(interp, ".horizScrollbar", first, last);
269    return adjustHorizSBOffset(interp, first);
270 }
271
272 bool tableVisi::adjustVertSBOffset(Tcl_Interp *interp) {
273    float first, last;
274    getScrollBarValues(interp, ".vertScrollbar", first, last);
275    return adjustVertSBOffset(interp, first);
276 }
277
278 void tableVisi::resize(Tcl_Interp *interp) {
279    // does not resize.  Does things like resize the offscreen pixmap
280    if (tryFirst()) {
281       if (offscreenPixmap) {
282          XFreePixmap(Tk_Display(theTkWindow), offscreenPixmap);
283
284          offscreenPixmap = XCreatePixmap(Tk_Display(theTkWindow),
285                                          Tk_WindowId(theTkWindow),
286                                          Tk_Width(theTkWindow), Tk_Height(theTkWindow),
287                                          Tk_Depth(theTkWindow));
288       }
289    }
290
291    resizeScrollbars(interp);
292    adjustHorizSBOffset(interp);
293    adjustVertSBOffset(interp);
294 }
295
296 void tableVisi::draw(bool xsynch) const {
297    if (!offscreenPixmap)
298       return; // we haven't done a tryFirst() yet
299
300    bool doubleBuffer = !xsynch;
301
302    Drawable theDrawable = doubleBuffer ? offscreenPixmap : Tk_WindowId(theTkWindow);
303
304    // XClearArea() works only on windows; the following works on pixmaps, too:
305    XFillRectangle(Tk_Display(theTkWindow), theDrawable,
306                   backgroundGC,
307                   0, 0, // x, y offset
308                   Tk_Width(theTkWindow), Tk_Height(theTkWindow));
309
310    drawFocusNames(theDrawable);
311       // leftmost part of screen; unaffected by offset_x
312    drawMetricNames(theDrawable);
313       // topmost part of screen; unaffected byy offset_y
314    drawCells(theDrawable);
315
316    if (doubleBuffer)
317       XCopyArea(Tk_Display(theTkWindow),
318                 offscreenPixmap, // src drawable
319                 Tk_WindowId(theTkWindow), // dest drawable
320                 backgroundGC, // only a dummy GC is needed here (well, sort of)
321                 0, 0, // src x, y offsets
322                 Tk_Width(theTkWindow), Tk_Height(theTkWindow),
323                 0, 0 // dest x, y offsets
324                 );
325 }
326
327 /*
328  * private metric helper functions
329  *
330  */
331 void tableVisi::drawMetricNames(Drawable theDrawable) const {
332    int curr_x = offset_x + getFocusAreaPixWidth();
333
334    const int minVisibleX = getFocusAreaPixWidth();
335    const int maxVisibleX = Tk_Width(theTkWindow) - 1;
336
337    const int metric_name_baseline = getMetricNameBaseline();
338    const int metric_units_baseline = getMetricUnitsBaseline();
339
340    // we need to clip 2 GC's: metricNameGC and metricUnitsGC.
341    // we don't need to clip lineGC, since our manual clipping is effective
342    XRectangle clipRect;
343    clipRect.x = getFocusAreaPixWidth();
344    clipRect.y = 0;
345    clipRect.width = Tk_Width(theTkWindow) - clipRect.x + 1;
346    clipRect.height = Tk_Height(theTkWindow);
347
348    XSetClipRectangles(Tk_Display(theTkWindow), metricNameGC,
349                       0, 0, &clipRect, 1, YXBanded);
350    XSetClipRectangles(Tk_Display(theTkWindow), metricUnitsGC,
351                       0, 0, &clipRect, 1, YXBanded);
352
353    for (unsigned metriclcv=0; metriclcv < indirectMetrics.size(); metriclcv++) {
354       if (curr_x > maxVisibleX)
355          break; // everthing else will be too far right
356
357       const tvMetric &theMetric = metrics[indirectMetrics[metriclcv]];
358       const int next_x = curr_x + theMetric.getColPixWidth();
359
360       if (next_x - 1 < minVisibleX) {
361          curr_x = next_x;
362          continue;
363       }
364
365       if (curr_x >= minVisibleX) // clipping
366          drawMetricVertLine(theDrawable, curr_x);
367
368       int curr_middle_x = (curr_x + next_x - 1) / 2;
369
370       // draw the metric name:
371       int metric_name_left = curr_middle_x - theMetric.getNamePixWidth() / 2;
372       const string &metricNameStr = theMetric.getName();
373       XDrawString(Tk_Display(theTkWindow), theDrawable,
374                   metricNameGC,
375                   metric_name_left, metric_name_baseline,
376                   metricNameStr.string_of(), metricNameStr.length());
377
378       // draw the metric units:
379       int metric_units_left = curr_middle_x - theMetric.getUnitsPixWidth() / 2;
380       const string &metricUnitsNameStr = theMetric.getUnitsName();
381       XDrawString(Tk_Display(theTkWindow), theDrawable,
382                   metricUnitsGC,
383                   metric_units_left, metric_units_baseline,
384                   metricUnitsNameStr.string_of(), metricUnitsNameStr.length());
385
386       curr_x = next_x;
387    }
388
389    if (curr_x >= minVisibleX) // clipping
390       drawMetricVertLine(theDrawable, curr_x);
391
392    XSetClipMask(Tk_Display(theTkWindow), metricNameGC, None);
393    XSetClipMask(Tk_Display(theTkWindow), metricUnitsGC, None);
394 }
395
396 void tableVisi::drawMetricVertLine(Drawable theDrawable, int x) const {
397    int line_height = getMetricAreaPixHeight() + get_total_cell_y_pix();
398    ipmin(line_height, Tk_Height(theTkWindow));
399
400    XDrawLine(Tk_Display(theTkWindow), theDrawable,
401              lineColorGC,
402              x, 0, x, 0+line_height-1);
403 }
404
405 unsigned tableVisi::getMetricAreaPixHeight() const {
406    return 3 + metricNameFont->ascent + metricNameFont->descent + 3 +
407               metricUnitsFont->ascent + metricUnitsFont->descent + 3;
408 }
409
410 unsigned tableVisi::getMetricNameBaseline() const {
411    return 3 + metricNameFont->ascent - 1;
412 }
413
414 unsigned tableVisi::getMetricUnitsBaseline() const {
415    return 3 + metricNameFont->ascent + metricNameFont->descent + 3 +
416               metricUnitsFont->ascent - 1;
417 }
418
419 /*
420  * private focus helper functions
421  *
422  */
423 void tableVisi::drawFocusNames(Drawable theDrawable) const {
424    int curr_y = offset_y + getMetricAreaPixHeight();
425
426    const int minVisibleY = getMetricAreaPixHeight();
427    const int maxVisibleY = Tk_Height(theTkWindow) - 1;
428
429    XRectangle clipRect;
430    clipRect.x = 0;
431    clipRect.y = getMetricAreaPixHeight();
432    clipRect.width = Tk_Width(theTkWindow);
433    clipRect.height = Tk_Height(theTkWindow) - clipRect.y + 1;
434
435    XSetClipRectangles(Tk_Display(theTkWindow), focusNameGC,
436                       0, 0, &clipRect, 1, YXBanded);
437    
438    for (unsigned focuslcv = 0; focuslcv < indirectFoci.size(); focuslcv++) {
439       if (curr_y > maxVisibleY)
440          break;
441
442       const tvFocus &theFocus = foci[indirectFoci[focuslcv]];
443       const int next_y = curr_y + getFocusLinePixHeight();
444       if (next_y - 1 < minVisibleY) {
445          curr_y = next_y;
446          continue;
447       }
448
449       if (curr_y >= minVisibleY)
450          drawFocusHorizLine(theDrawable, curr_y);
451
452       int curr_y_baseline = curr_y + getVertPixFocusTop2Baseline();
453
454       const string &theString = focusLongNameMode ? theFocus.getLongName() :
455                                                     theFocus.getShortName();
456
457       XDrawString(Tk_Display(theTkWindow), theDrawable,
458                   focusNameGC,
459                   getHorizPixBeforeFocusName(),
460                   curr_y_baseline,
461                   theString.string_of(), theString.length());
462
463       curr_y = next_y;
464    }
465
466    XSetClipMask(Tk_Display(theTkWindow), focusNameGC, None);
467
468    if (curr_y >= minVisibleY)
469       drawFocusHorizLine(theDrawable, curr_y);
470 }
471
472 void tableVisi::drawFocusHorizLine(Drawable theDrawable, int y) const {
473    int line_width = getFocusAreaPixWidth() + get_total_cell_x_pix();
474    ipmin(line_width, get_visible_x_pix());
475    
476    XDrawLine(Tk_Display(theTkWindow), theDrawable,
477              lineColorGC,
478              0, y, 0+line_width-1, y);
479 }
480
481 unsigned tableVisi::getFocusLinePixHeight() const {
482    return 2 + focusNameFont->ascent + focusNameFont->descent + 2;
483 }
484
485 unsigned tableVisi::getVertPixFocusTop2Baseline() const {
486    return 2 + focusNameFont->ascent;
487 }
488
489 unsigned tableVisi::getFocusAreaPixWidth() const {
490    return getHorizPixBeforeFocusName() + maxFocusNamePixWidth +
491           getHorizPixBeforeFocusName();
492 }
493
494 /*
495  * private cell helper functions
496  *
497  */
498 void tableVisi::drawCells(Drawable theDrawable) const {
499    int curr_x = offset_x + getFocusAreaPixWidth();
500
501    const int minVisibleX = 0;
502    const int maxVisibleX = Tk_Width(theTkWindow)-1;
503
504    // we need to clip the GCs used for drawing cells s.t. neither the
505    // metrics nor foci are overwritten.
506    XRectangle clipRect;
507    clipRect.x = getFocusAreaPixWidth();
508    clipRect.y = getMetricAreaPixHeight();
509    clipRect.width = Tk_Width(theTkWindow) - clipRect.x + 1;
510    clipRect.height = Tk_Height(theTkWindow) - clipRect.y + 1;
511
512    XSetClipRectangles(Tk_Display(theTkWindow), cellGC,
513                       0, 0, &clipRect, 1, YXBanded);
514    
515    for (unsigned metriclcv = 0; metriclcv < indirectMetrics.size(); metriclcv++) {
516       if (curr_x > maxVisibleX)
517          break;
518
519       const tvMetric &theMetric = metrics[indirectMetrics[metriclcv]];
520       const int next_x = curr_x + theMetric.getColPixWidth();
521
522       if (next_x - 1 < minVisibleX) {
523          curr_x = next_x;
524          continue;
525       }
526
527       const vector<tvCell> &thisMetricCells = cells[indirectMetrics[metriclcv]];
528       drawCells1Col(theDrawable,
529                     (curr_x + next_x - 1) / 2, // middle x
530                     offset_y + getMetricAreaPixHeight(), // start y
531                     thisMetricCells);
532
533       curr_x = next_x;
534    }
535
536    XSetClipMask(Tk_Display(theTkWindow), cellGC, None);
537 }
538
539 void tableVisi::drawCells1Col(Drawable theDrawable, int middle_x, int top_y,
540                               const vector<tvCell> &thisMetricCells) const {
541    // uses getVertPixFocusTop2Baseline() and getFocusLinePixHeight()
542    int curr_y = top_y;
543
544    int minVisibleY = 0;
545    int maxVisibleY = Tk_Height(theTkWindow)-1;
546
547    for (unsigned focuslcv=0; focuslcv < indirectFoci.size(); focuslcv++) {
548       if (curr_y > maxVisibleY)
549          break;
550
551       const int next_y = curr_y + getFocusLinePixHeight();
552       if (next_y - 1 < minVisibleY) {
553          curr_y = next_y;
554          continue;
555       }
556
557       const tvCell &theCell = thisMetricCells[indirectFoci[focuslcv]];
558       if (!theCell.isValid()) {
559          curr_y = next_y;
560          continue;
561       }
562
563       // making a new "string" would be toop expensive (calls new):
564       char buffer[200];
565       double2string(buffer, theCell.getData());
566
567       int buffer_len = strlen(buffer);
568       int string_pix_width = XTextWidth(cellFont, buffer, buffer_len);
569  
570       XDrawString(Tk_Display(theTkWindow), theDrawable,
571                   cellGC,
572                   middle_x - string_pix_width / 2,
573                   curr_y + getVertPixCellTop2Baseline(),
574                   buffer, buffer_len);
575
576       curr_y = next_y;
577    }
578 }
579
580 unsigned tableVisi::getVertPixCellTop2Baseline() const {
581    return 2 + cellFont->ascent;
582 }
583
584 /* *************************************************************** */
585
586 void tableVisi::clearMetrics(Tcl_Interp *interp) {
587    metrics.resize(0);
588    indirectMetrics.resize(0);
589    cells.resize(0);
590    all_cells_width = 0;
591
592    if (offscreenPixmap)
593       resize(interp);
594 }
595
596 void tableVisi::clearFoci(Tcl_Interp *interp) {
597    foci.resize(0);
598    indirectFoci.resize(0);
599
600    unsigned numMetrics = getNumMetrics();
601    for (unsigned i=0; i < numMetrics; i++) {
602       vector<tvCell> &theVec = cells[i];
603       theVec.resize(0);
604    }
605
606    all_cells_height = 0;
607    maxFocusNamePixWidth = 0;
608
609    if (offscreenPixmap)
610       resize(interp);
611 }
612
613 void tableVisi::addMetric(const string &metricName, const string &metricUnits) {
614    tvMetric newTvMetric(metricName, metricUnits, metricNameFont, metricUnitsFont);
615    metrics += newTvMetric;
616    indirectMetrics += (metrics.size()-1);
617    cells += vector<tvCell>();
618
619    all_cells_width += newTvMetric.getColPixWidth();
620
621    assert(metrics.size() == indirectMetrics.size());
622    assert(cells.size() == metrics.size());
623 }
624
625 void tableVisi::addFocus(const string &focusName) {
626    tvFocus newTvFocus(focusName, focusNameFont);
627    foci += newTvFocus;
628    indirectFoci += (foci.size()-1);
629
630    unsigned numMetrics = metrics.size();
631    for (unsigned metriclcv=0; metriclcv < numMetrics; metriclcv++) {
632       vector<tvCell> &metricCells = cells[metriclcv];
633       metricCells += tvCell();
634    }
635
636    if (focusLongNameMode)
637       ipmax(maxFocusNamePixWidth, newTvFocus.getLongNamePixWidth());
638    else
639       ipmax(maxFocusNamePixWidth, newTvFocus.getShortNamePixWidth());
640
641    all_cells_height += getFocusLinePixHeight();
642
643    assert(foci.size() == indirectFoci.size());
644 }
645
646 int tableVisi::partitionMetrics(int left, int right) {
647    const tvMetric &pivot = metrics[indirectMetrics[left]];
648
649    int l = left-1;
650    int r = right+1;
651
652    while (true) {
653       while (metrics[indirectMetrics[--r]] > pivot)
654          ;
655
656       while (metrics[indirectMetrics[++l]] < pivot)
657          ;
658
659       if (l < r) {
660          unsigned temp = indirectMetrics[l];
661          indirectMetrics[l] = indirectMetrics[r];
662          indirectMetrics[r] = temp;
663       }
664       else
665          return r;
666    }
667 }
668
669 void tableVisi::sortMetrics(int left, int right) {
670    if (left < right) {
671       int middle = partitionMetrics(left, right);
672       sortMetrics(left, middle);
673       sortMetrics(middle+1, right);
674    }
675 }
676
677 void tableVisi::sortMetrics() {
678    sortMetrics(0, metrics.size()-1);
679 }
680
681 void tableVisi::unsortMetrics() {
682    for (unsigned i=0; i < indirectMetrics.size(); i++)
683       indirectMetrics[i] = i;
684 }
685
686 int tableVisi::partitionFoci(int left, int right) {
687    const tvFocus &pivot = foci[indirectFoci[left]];
688
689    int l = left-1;
690    int r = right+1;
691
692    while (true) {
693       do {
694          r--;
695       } while (foci[indirectFoci[r]].greater_than(pivot, focusLongNameMode)); 
696
697       do {
698          l++;
699       } while (foci[indirectFoci[l]].less_than(pivot, focusLongNameMode));
700
701       if (l < r) {
702          unsigned temp = indirectFoci[l];
703          indirectFoci[l] = indirectFoci[r];
704          indirectFoci[r] = temp;
705       }
706       else
707          return r;
708    }
709 }
710
711 void tableVisi::sortFoci(int left, int right) {
712    if (left < right) {
713       int middle = partitionFoci(left, right);
714       sortFoci(left, middle);
715       sortFoci(middle+1, right);
716    }
717 }
718
719 void tableVisi::sortFoci() {
720    sortFoci(0, foci.size()-1);
721 }
722
723 void tableVisi::unsortFoci() {
724    for (unsigned i=0; i < indirectFoci.size(); i++)
725       indirectFoci[i] = i;
726 }
727
728 bool tableVisi::setFocusNameMode(Tcl_Interp *interp, bool longNameMode) {
729    // returns true iff any changes
730    if (focusLongNameMode == longNameMode)
731       return false;
732
733    focusLongNameMode = longNameMode;
734
735    // recalculate maxFocusNamePixWidth:
736    maxFocusNamePixWidth=0;
737    for (unsigned focuslcv=0; focuslcv < foci.size(); focuslcv++) {
738       const tvFocus &theFocus = foci[focuslcv];
739
740       if (focusLongNameMode)
741          ipmax(maxFocusNamePixWidth, theFocus.getLongNamePixWidth());
742       else
743          ipmax(maxFocusNamePixWidth, theFocus.getShortNamePixWidth());
744    }
745
746    resize(interp);
747
748    return true;
749 }
750
751 bool tableVisi::setSigFigs(unsigned newNumSigFigs) {
752    if (newNumSigFigs == numSigFigs)
753       return false;
754
755    numSigFigs = newNumSigFigs;
756    return true;
757 }
758
759 void tableVisi::invalidateCell(unsigned theMetric, unsigned theFocus) {
760    cells[theMetric][theFocus].invalidate();
761 }
762
763 void tableVisi::setCellValidData(unsigned theMetric, unsigned theFocus, double data) {
764    cells[theMetric][theFocus].setValidData(data);
765 }
766
767 /* *************************************************************** */
768
769 bool tableVisi::adjustHorizSBOffset(Tcl_Interp *interp, float newFirst) {
770    // doesn't redraw; returns true iff any changes.
771    newFirst = moveScrollBar(interp, ".horizScrollbar", newFirst);
772
773    int total_cell_width = get_total_cell_x_pix();
774    int old_offset_x = offset_x;
775    offset_x = -(int)(newFirst * total_cell_width); // yes, always <= 0
776
777    return (offset_x != old_offset_x);
778 }
779
780 bool tableVisi::adjustVertSBOffset(Tcl_Interp *interp, float newFirst) {
781    // doesn't redraw; returns true iff any changes.
782    newFirst = moveScrollBar(interp, ".vertScrollbar", newFirst);
783
784    int total_cell_height = get_total_cell_y_pix();
785    int old_offset_y = offset_y;
786    offset_y = -(int)(newFirst * total_cell_height); // yes, always <= 0
787
788    return (offset_y != old_offset_y);
789 }