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