Updated processing of pd_flag variable so that startup logic is shared
[dyninst.git] / visiClients / tableVisi / src / tableVisi.C
1 /*
2  * Copyright (c) 1996-1999 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 // tableVisi.C
43 // Ariel Tamches
44
45 /*
46  * $Log: tableVisi.C,v $
47  * Revision 1.14  2000/07/28 17:23:01  pcroth
48  * Updated #includes to reflect util library split
49  *
50  * Revision 1.13  2000/03/21 23:58:39  pcroth
51  * Added guard to protect against division by zero when resizing scroll bars.
52  *
53  * Revision 1.12  1999/11/09 15:55:11  pcroth
54  * Updated uses of XCreateGC and XFreeGC to Tk_GetGC and Tk_FreeGC.
55  *
56  * Revision 1.11  1999/07/13 17:16:11  pcroth
57  * Fixed ordering problem of destroying GUI and destructing static variable
58  * pdLogo::all_logos.  On NT, the static variable is destroyed before the
59  * GUI, but a callback for the GUI ends up referencing the variable causing
60  * an access violation error.
61  *
62  * Revision 1.10  1999/03/13 15:24:04  pcroth
63  * Added support for building under Windows NT
64  *
65  * Revision 1.9  1996/08/16 21:36:56  tamches
66  * updated copyright for release 1.1
67  *
68  * Revision 1.8  1996/04/30 20:17:37  tamches
69  * double2string optimized for 0
70  * gcvt called instead of sprintf on the off-chance that
71  * it's a bit faster.
72  *
73  * Revision 1.7  1995/12/22 22:43:10  tamches
74  * selection
75  * deletion
76  * sort foci by value
77  *
78  * Revision 1.6  1995/12/19 00:46:19  tamches
79  * calls to tvMetric constructor use new args
80  * changeNumSigFigs now recognizes possibility of column pix width change
81  *
82  * Revision 1.5  1995/12/03 21:09:19  newhall
83  * changed units labeling to match type of data being displayed
84  *
85  * Revision 1.4  1995/11/20  20:20:20  tamches
86  * horizontal & vertical grid lines no longer expand past the
87  * last cells.
88  *
89  */
90
91 #include <iostream.h>
92
93 #include "common/h/headers.h"
94 #include "minmax.h"
95 #include "tkTools.h"
96 #include "tableVisi.h"
97
98 /* ************************************************************* */
99
100 extern "C" {int isnan(double);}
101
102 void tableVisi::updateConversionString() {
103    sprintf(conversionString, "%%.%dg", numSigFigs);
104 }
105
106 void tableVisi::double2string(char *buffer, double val) const {
107    if (isnan(val)) {
108       buffer[0] = '\0';
109       return;
110    }
111    
112    // Optimize for a common case:
113    if (val == 0) {
114       buffer[0] = '0';
115       buffer[1] = '\0';
116       return;
117    }
118
119 //   sprintf(buffer, conversionString, val);
120    gcvt(val, numSigFigs, buffer);
121
122 //   cout << "from " << buffer << " to " << flush;
123
124    // add commas
125    if (strlen(buffer)==0)
126       return;
127
128    const char *decPtr = strchr(buffer, '.');
129
130    if (decPtr && (decPtr - buffer <= 3))
131       return; // no commas will be added since there aren't at least 4 integer digits
132
133    if (decPtr == NULL) {
134       // try for exponential notation
135       decPtr = strchr(buffer, 'e');
136
137       if (decPtr==NULL)
138          decPtr = &buffer[strlen(buffer)]; // the '\0'
139    }
140
141    // invariant: decPtr now points to the character AFTER the
142    // last integer digit (i.e., the decimal point), or the '\0'
143    // if none exists.
144
145    // Now let's walk backwards, looking for places to insert commas
146    char integerPartBuffer[200]; // will include commas
147    char *integerPart = integerPartBuffer;
148    const char *walkPtr = decPtr-1;
149    unsigned copyCount=0;
150    while (walkPtr >= buffer) {
151       if (copyCount > 0 && copyCount % 3 == 0)
152          *integerPart++ = ',';
153
154       copyCount++;
155       *integerPart++ = *walkPtr--;
156    }
157
158    char decimalPart[200];
159       // will incl dec point, if applicable.  May be
160       // the empty-string.
161    strcpy(decimalPart, decPtr);
162
163    // note: integerPartBuffer is backwards...we must reverse it
164    char *bufferPtr = buffer;
165    while (--integerPart >= integerPartBuffer)
166       *bufferPtr++ = *integerPart;
167    strcpy(bufferPtr, decimalPart);
168
169 //   cout << buffer << endl;
170 }
171
172 /* ************************************************************* */
173
174 Tk_Font tableVisi::myTkGetFont(Tcl_Interp* interp, const string &fontName) const {
175    Tk_Font result = Tk_GetFont(interp,
176                                         theTkWindow,
177                                         fontName.string_of());
178    if (result == NULL) {
179       cerr << "could not find font " << fontName << endl;
180       exit(5);
181    }
182
183    return result;
184 }
185
186 XColor *tableVisi::myTkGetColor(Tcl_Interp *interp, const string &colorName) const {
187    XColor *result = Tk_GetColor(interp, theTkWindow, Tk_GetUid(colorName.string_of()));
188    if (result == NULL)
189       tclpanic(interp, "table visi: could not allocate color");
190
191    return result;
192 }
193
194 /* ************************************************************* */
195
196 tableVisi::tableVisi(Tcl_Interp *interp,
197                      Tk_Window iTkWindow,
198                      const string &metricFontName,
199                      const string &metricUnitsFontName,
200                      const string &focusFontName,
201                      const string &cellFontName,
202                      const string &iLineColorName,
203                      const string &iMetricColorName,
204                      const string &iMetricUnitsColorName,
205                      const string &iFocusColorName,
206                      const string &cellColorName,
207                      const string &backgroundColorName,
208                      const string &highlightedBackgroundColorName,
209                      unsigned iSigFigs
210                      ) {
211    // metrics[], foci[], indirectMetrics[], indirectFoci[], and cells[][]
212    // are all initialized to zero-sized arrays.
213
214    theTkWindow = iTkWindow;
215    theDisplay = Tk_Display(theTkWindow);
216
217    offscreenPixmap = (Pixmap)NULL;
218       // sorry, can't create a pixmap until the window becomes mapped.
219
220    backgroundColor = myTkGetColor(interp, backgroundColorName);
221    highlightedBackgroundColor = myTkGetColor(interp, highlightedBackgroundColorName);
222
223    offset_x = offset_y = 0;
224    all_cells_width = all_cells_height = 0;
225
226    // access fonts and font metrics
227    metricNameFont = myTkGetFont(interp, metricFontName);
228    Tk_GetFontMetrics( metricNameFont, &metricNameFontMetrics );
229
230    metricUnitsFont = myTkGetFont(interp, metricUnitsFontName);
231    Tk_GetFontMetrics( metricUnitsFont, &metricUnitsFontMetrics );
232
233    focusNameFont = myTkGetFont(interp, focusFontName);
234    Tk_GetFontMetrics( focusNameFont, &focusNameFontMetrics );
235
236    cellFont = myTkGetFont(interp, cellFontName);
237    Tk_GetFontMetrics( cellFont, &cellFontMetrics );
238
239    focusLongNameMode = true;
240    numSigFigs = iSigFigs;
241    updateConversionString();
242
243    theSelection = none;
244    // we leave selectedRow, selectedCol undefined
245
246    maxFocusNamePixWidth = 0;
247
248    // The GC's, like offscreenPixmap, can't be created until the
249    // window becomes mapped.
250    lineColor = myTkGetColor(interp, iLineColorName);
251    lineColorGC = NULL;
252
253    metricNameColor = myTkGetColor(interp, iMetricColorName);
254    metricNameGC = NULL;
255
256    metricUnitsColor = myTkGetColor(interp, iMetricUnitsColorName);
257    metricUnitsGC = NULL;
258   
259    focusNameColor = myTkGetColor(interp, iFocusColorName);
260    focusNameGC = NULL;
261
262    cellColor = myTkGetColor(interp, cellColorName);
263    cellGC = NULL;
264 }
265
266
267 tableVisi::~tableVisi() {
268    // arrays metrics[], foci[], indirectMetrics[], indirectFoci[], cells[][] will
269    // delete themselves.
270
271    if (!offscreenPixmap)
272       // the offscreen pixmap was never allocated(!)...so, we never
273       // got around to mapping the window!
274       return;
275
276    Tk_FreeGC(theDisplay, cellGC);
277    Tk_FreeGC(theDisplay, focusNameGC);
278    Tk_FreeGC(theDisplay, metricUnitsGC);
279    Tk_FreeGC(theDisplay, metricNameGC);
280    Tk_FreeGC(theDisplay, lineColorGC);
281    Tk_FreeGC(theDisplay, backgroundGC);
282    Tk_FreeGC(theDisplay, highlightedBackgroundGC);
283
284    Tk_FreePixmap(theDisplay, offscreenPixmap);
285 }
286
287 bool tableVisi::tryFirst() {
288    if (offscreenPixmap) {
289       // the offscreen pixmap has been allocated, so the window
290       // has presumably been mapped already
291       assert(Tk_WindowId(theTkWindow) != 0);
292       return true;
293    }
294
295    // the offscreen pixmap hasn't been allocated, so it's now time
296    // to check to see if it should be.
297    if (Tk_WindowId(theTkWindow) == 0)
298       return false; // nuts; not ready yet
299
300    // Ready to allocate graphical structures now!
301    offscreenPixmap = Tk_GetPixmap(Tk_Display(theTkWindow),
302                                    Tk_WindowId(theTkWindow),
303                                    1, 1, // dummy width, height
304                                    Tk_Depth(theTkWindow));
305    XGCValues values;
306    values.foreground = backgroundColor->pixel;
307    backgroundGC = Tk_GetGC(theTkWindow,
308                             GCForeground, &values);
309
310    values.foreground = highlightedBackgroundColor->pixel;
311    highlightedBackgroundGC = Tk_GetGC(theTkWindow,
312                                        GCForeground, &values);
313
314    values.foreground = lineColor->pixel;
315    lineColorGC = Tk_GetGC(theTkWindow,
316                            GCForeground, &values);
317
318    values.foreground = metricNameColor->pixel;
319    values.font = Tk_FontId( metricNameFont );
320    metricNameGC = Tk_GetGC(theTkWindow,
321                             GCForeground | GCFont, &values);
322
323    values.foreground = metricUnitsColor->pixel;
324    values.font = Tk_FontId( metricUnitsFont );
325    metricUnitsGC = Tk_GetGC(theTkWindow,
326                              GCForeground | GCFont, &values);
327
328    values.foreground = focusNameColor->pixel;
329    values.font = Tk_FontId( focusNameFont );
330    focusNameGC = Tk_GetGC(theTkWindow,
331                            GCForeground | GCFont, &values);
332
333    values.foreground = cellColor->pixel;
334    values.font = Tk_FontId( cellFont );
335    cellGC = Tk_GetGC(theTkWindow,
336                       GCForeground | GCFont, &values);
337
338    return true;
339 }
340
341 void tableVisi::resizeScrollbars(Tcl_Interp *interp) {
342    // used to be a tcl routine (resize1Scrollbar):
343
344    int visible_cell_width = Tk_Width(theTkWindow) -
345                                   getFocusAreaPixWidth();
346         if( visible_cell_width <= 0 )
347         {
348                 visible_cell_width = 1;
349         }
350    resizeScrollbar(interp, ".horizScrollbar",
351                    all_cells_width, // total
352                    visible_cell_width);
353
354    int visible_cell_height = Tk_Height(theTkWindow) -
355                                    getMetricAreaPixHeight();
356         if( visible_cell_height <= 0 )
357         {
358                 visible_cell_height = 1;
359         }
360    resizeScrollbar(interp, ".vertScrollbar",
361                    all_cells_height, // total
362                    visible_cell_height);
363 }
364
365 bool tableVisi::adjustHorizSBOffset(Tcl_Interp *interp) {
366    float first, last;
367    getScrollBarValues(interp, ".horizScrollbar", first, last);
368    return adjustHorizSBOffset(interp, first);
369 }
370
371 bool tableVisi::adjustVertSBOffset(Tcl_Interp *interp) {
372    float first, last;
373    getScrollBarValues(interp, ".vertScrollbar", first, last);
374    return adjustVertSBOffset(interp, first);
375 }
376
377 void tableVisi::resize(Tcl_Interp *interp) {
378    // does not redraw.  Does things like resize the offscreen pixmap
379    if (tryFirst()) {
380       if (offscreenPixmap) {
381          Tk_FreePixmap(Tk_Display(theTkWindow), offscreenPixmap);
382
383          offscreenPixmap = Tk_GetPixmap(Tk_Display(theTkWindow),
384                                          Tk_WindowId(theTkWindow),
385                                          Tk_Width(theTkWindow), Tk_Height(theTkWindow),
386                                          Tk_Depth(theTkWindow));
387       }
388    }
389
390    resizeScrollbars(interp);
391    adjustHorizSBOffset(interp);
392    adjustVertSBOffset(interp);
393 }
394
395 void tableVisi::drawHighlightBackground(Drawable theDrawable) const {
396    if (theSelection == none)
397       return;
398
399    int left, right;
400    if (theSelection == rowOnly) {
401       left = 0;
402       right = min(Tk_Width(theTkWindow)-1,
403                   (int)getFocusAreaPixWidth() + all_cells_width-1);
404    }
405    else {
406       // theSelection == colOnly || theSelection == cell
407       left = metric2xpix(selectedCol);
408       right = left + metrics[indirectMetrics[selectedCol]].getColPixWidth() - 1;
409
410       left = max(left, (int)getFocusAreaPixWidth()+1);
411       right = max(right, (int)getFocusAreaPixWidth()+1);
412    }
413
414    int top, bottom;
415    if (theSelection == rowOnly || theSelection == cell) {
416       top = focus2ypix(selectedRow);
417       bottom = top + getFocusLinePixHeight() - 1;
418
419       top = max(top, (int)getMetricAreaPixHeight()+1);
420       bottom = max(bottom, (int)getMetricAreaPixHeight()+1);
421    }
422    else {
423       // theSelection == colOnly
424       top = 0;
425       bottom = min((int)getMetricAreaPixHeight() + all_cells_height - 1,
426                    Tk_Height(theTkWindow)-1);
427    }
428
429    int width = right - left + 1;
430    int height = bottom - top + 1;
431
432    XFillRectangle(Tk_Display(theTkWindow), theDrawable,
433                   highlightedBackgroundGC,
434                   left, top, width, height);
435 }
436
437 void tableVisi::draw(bool xsynch) const {
438    if (!offscreenPixmap)
439       return; // we haven't done a tryFirst() yet
440
441    bool doubleBuffer = !xsynch;
442
443    Drawable theDrawable = doubleBuffer ? offscreenPixmap : Tk_WindowId(theTkWindow);
444
445    // XClearArea() works only on windows; the following works on pixmaps, too:
446    XFillRectangle(Tk_Display(theTkWindow), theDrawable,
447                   backgroundGC,
448                   0, 0, // x, y offset
449                   Tk_Width(theTkWindow), Tk_Height(theTkWindow));
450
451    // Is anything (a cell, an entire row, an entire column) highlighted?
452    // If so, let's do that now
453    drawHighlightBackground(theDrawable);
454
455    drawFocusNames(theDrawable);
456       // leftmost part of screen; unaffected by offset_x
457    drawMetricNames(theDrawable);
458       // topmost part of screen; unaffected by offset_y
459    drawCells(theDrawable);
460
461    if (doubleBuffer)
462       XCopyArea(Tk_Display(theTkWindow),
463                 offscreenPixmap, // src drawable
464                 Tk_WindowId(theTkWindow), // dest drawable
465                 backgroundGC, // only a dummy GC is needed here (well, sort of)
466                 0, 0, // src x, y offsets
467                 Tk_Width(theTkWindow), Tk_Height(theTkWindow),
468                 0, 0 // dest x, y offsets
469                 );
470 }
471
472 /*
473  * private metric helper functions
474  *
475  */
476 void tableVisi::drawMetricNames(Drawable theDrawable) const {
477    int curr_x = offset_x + getFocusAreaPixWidth();
478
479    const int minVisibleX = getFocusAreaPixWidth();
480    const int maxVisibleX = Tk_Width(theTkWindow) - 1;
481
482    const int metric_name_baseline = getMetricNameBaseline();
483    const int metric_units_baseline = getMetricUnitsBaseline();
484
485    // we need to clip 2 GC's: metricNameGC and metricUnitsGC.
486    // we don't need to clip lineGC, since our manual clipping is effective
487    XRectangle clipRect;
488    clipRect.x = getFocusAreaPixWidth();
489    clipRect.y = 0;
490    clipRect.width = Tk_Width(theTkWindow) - clipRect.x + 1;
491    clipRect.height = Tk_Height(theTkWindow);
492
493 #if !defined(i386_unknown_nt4_0)
494    XSetClipRectangles(Tk_Display(theTkWindow), metricNameGC,
495                       0, 0, &clipRect, 1, YXBanded);
496    XSetClipRectangles(Tk_Display(theTkWindow), metricUnitsGC,
497                       0, 0, &clipRect, 1, YXBanded);
498 #endif // !defined(i386_unknown_nt4_0)
499
500    for (unsigned metriclcv=0; metriclcv < indirectMetrics.size(); metriclcv++) {
501       if (curr_x > maxVisibleX)
502          break; // everthing else will be too far right
503
504       const tvMetric &theMetric = metrics[indirectMetrics[metriclcv]];
505       const int next_x = curr_x + theMetric.getColPixWidth();
506
507       if (next_x - 1 < minVisibleX) {
508          curr_x = next_x;
509          continue;
510       }
511
512       if (curr_x >= minVisibleX) // clipping
513          drawMetricVertLine(theDrawable, curr_x);
514
515       int curr_middle_x = (curr_x + next_x - 1) / 2;
516
517       // draw the metric name:
518       int metric_name_left = curr_middle_x - theMetric.getNamePixWidth() / 2;
519       const string &metricNameStr = theMetric.getName();
520       Tk_DrawChars(Tk_Display(theTkWindow), theDrawable,
521                   metricNameGC,
522                   metricNameFont,
523                   metricNameStr.string_of(), metricNameStr.length(),
524                   metric_name_left, metric_name_baseline);
525
526       // draw the metric units:
527       int metric_units_left = curr_middle_x - theMetric.getUnitsPixWidth() / 2;
528       const string &metricUnitsNameStr = theMetric.getUnitsName();
529       Tk_DrawChars(Tk_Display(theTkWindow), theDrawable,
530                   metricUnitsGC,
531                   metricUnitsFont,
532                   metricUnitsNameStr.string_of(), metricUnitsNameStr.length(),
533                   metric_units_left, metric_units_baseline);
534
535       curr_x = next_x;
536    }
537
538    if (curr_x >= minVisibleX) // clipping
539       drawMetricVertLine(theDrawable, curr_x);
540
541    XSetClipMask(Tk_Display(theTkWindow), metricNameGC, None);
542    XSetClipMask(Tk_Display(theTkWindow), metricUnitsGC, None);
543 }
544
545 void tableVisi::drawMetricVertLine(Drawable theDrawable, int x) const {
546    int line_height = getMetricAreaPixHeight() + get_total_cell_y_pix();
547    ipmin(line_height, Tk_Height(theTkWindow));
548
549    XDrawLine(Tk_Display(theTkWindow), theDrawable,
550              lineColorGC,
551              x, 0, x, 0+line_height-1);
552 }
553
554 unsigned tableVisi::getMetricAreaPixHeight() const {
555    return 3 + metricNameFontMetrics.linespace + 3 +
556               metricUnitsFontMetrics.linespace + 3;
557 }
558
559 unsigned tableVisi::getMetricNameBaseline() const {
560    return 3 + metricNameFontMetrics.ascent - 1;
561 }
562
563 unsigned tableVisi::getMetricUnitsBaseline() const {
564    return 3 + metricNameFontMetrics.linespace + 3 +
565               metricUnitsFontMetrics.ascent - 1;
566 }
567
568 bool tableVisi::xpix2col(int x, unsigned &theCol) const {
569    if (x < (int)getFocusAreaPixWidth())
570       return false; // too far left
571
572    x -= getFocusAreaPixWidth();
573
574    // Adjust for horiz scrollbar:
575    x -= offset_x;
576
577    if (x >= all_cells_width)
578       return false; // too far right
579
580    for (unsigned metriclcv = 0; metriclcv < indirectMetrics.size(); metriclcv++) {
581       const tvMetric &theMetric = metrics[indirectMetrics[metriclcv]];
582       if (x < (int)theMetric.getColPixWidth()) {
583          theCol = metriclcv;
584          return true;
585       }
586       x -= theMetric.getColPixWidth();
587    }
588
589    assert(false); // this case should have been caught in the "x >= all_cells_width"
590    return false; // placate compiler
591 }
592
593 int tableVisi::metric2xpix(unsigned theColumn) const {
594    int result = getFocusAreaPixWidth();
595
596    // Adjust for scrollbar:
597    result += offset_x; // subtracts from 'result' since offset_x <= 0
598
599    for (unsigned collcv=0; collcv < theColumn; collcv++)
600       result += metrics[indirectMetrics[collcv]].getColPixWidth();
601
602    return result;
603 }
604
605 /*
606  * private focus helper functions
607  *
608  */
609 void tableVisi::drawFocusNames(Drawable theDrawable) const {
610    int curr_y = offset_y + getMetricAreaPixHeight();
611
612    const int minVisibleY = getMetricAreaPixHeight();
613    const int maxVisibleY = Tk_Height(theTkWindow) - 1;
614
615    XRectangle clipRect;
616    clipRect.x = 0;
617    clipRect.y = getMetricAreaPixHeight();
618    clipRect.width = Tk_Width(theTkWindow);
619    clipRect.height = Tk_Height(theTkWindow) - clipRect.y + 1;
620
621 #if !defined(i386_unknown_nt4_0)
622    XSetClipRectangles(Tk_Display(theTkWindow), focusNameGC,
623                       0, 0, &clipRect, 1, YXBanded);
624 #endif // !defined(i386_unknown_nt4_0)
625
626    for (unsigned focuslcv = 0; focuslcv < indirectFoci.size(); focuslcv++) {
627       if (curr_y > maxVisibleY)
628          break;
629
630       const tvFocus &theFocus = foci[indirectFoci[focuslcv]];
631       const int next_y = curr_y + getFocusLinePixHeight();
632       if (next_y - 1 < minVisibleY) {
633          curr_y = next_y;
634          continue;
635       }
636
637       if (curr_y >= minVisibleY)
638          drawFocusHorizLine(theDrawable, curr_y);
639
640       int curr_y_baseline = curr_y + getVertPixFocusTop2Baseline();
641
642       const string &theString = focusLongNameMode ? theFocus.getLongName() :
643                                                     theFocus.getShortName();
644
645       Tk_DrawChars(Tk_Display(theTkWindow), theDrawable,
646                   focusNameGC,
647                   focusNameFont,
648                   theString.string_of(), theString.length(),
649                   getHorizPixBeforeFocusName(), curr_y_baseline);
650
651       curr_y = next_y;
652    }
653
654    XSetClipMask(Tk_Display(theTkWindow), focusNameGC, None);
655
656    if (curr_y >= minVisibleY)
657       drawFocusHorizLine(theDrawable, curr_y);
658 }
659
660 void tableVisi::drawFocusHorizLine(Drawable theDrawable, int y) const {
661    int line_width = getFocusAreaPixWidth() + get_total_cell_x_pix();
662    ipmin(line_width, get_visible_x_pix());
663    
664    XDrawLine(Tk_Display(theTkWindow), theDrawable,
665              lineColorGC,
666              0, y, 0+line_width-1, y);
667 }
668
669 unsigned tableVisi::getFocusLinePixHeight() const {
670    return 2 + focusNameFontMetrics.linespace + 2;
671 }
672
673 unsigned tableVisi::getVertPixFocusTop2Baseline() const {
674    return 2 + focusNameFontMetrics.ascent;
675 }
676
677 unsigned tableVisi::getFocusAreaPixWidth() const {
678    return getHorizPixBeforeFocusName() + maxFocusNamePixWidth +
679           getHorizPixBeforeFocusName();
680 }
681
682 bool tableVisi::ypix2row(int y, unsigned &theRow) const {
683    if (y < (int)getMetricAreaPixHeight())
684       return false; // too far up
685
686    y -= getMetricAreaPixHeight();
687
688    // Adjust for scrollbar:
689    y -= offset_y; // adds since offset_y is <= 0
690
691    unsigned result = y / getFocusLinePixHeight();
692    if (result >= indirectFoci.size())
693       return false; // too far down
694
695    theRow = result;
696    return true;
697 }
698
699 int tableVisi::focus2ypix(unsigned theRow) const {
700    int result = getMetricAreaPixHeight();
701    
702    // Adjust for scrollbar:
703    result += offset_y; // subtracts since offset_y is <= 0
704
705    result += theRow * getFocusLinePixHeight();
706
707    return result;
708 }
709
710 /*
711  * private cell helper functions
712  *
713  */
714 void tableVisi::drawCells(Drawable theDrawable) const {
715    int curr_x = offset_x + getFocusAreaPixWidth();
716
717    const int minVisibleX = 0;
718    const int maxVisibleX = Tk_Width(theTkWindow)-1;
719
720    // we need to clip the GCs used for drawing cells s.t. neither the
721    // metrics nor foci are overwritten.
722    XRectangle clipRect;
723    clipRect.x = getFocusAreaPixWidth();
724    clipRect.y = getMetricAreaPixHeight();
725    clipRect.width = Tk_Width(theTkWindow) - clipRect.x + 1;
726    clipRect.height = Tk_Height(theTkWindow) - clipRect.y + 1;
727
728 #if !defined(i386_unknown_nt4_0)
729    XSetClipRectangles(Tk_Display(theTkWindow), cellGC,
730                       0, 0, &clipRect, 1, YXBanded);
731 #endif // !defined(i386_unknown_nt4_0)
732
733    for (unsigned metriclcv = 0; metriclcv < indirectMetrics.size(); metriclcv++) {
734       if (curr_x > maxVisibleX)
735          break;
736
737       const tvMetric &theMetric = metrics[indirectMetrics[metriclcv]];
738       const int next_x = curr_x + theMetric.getColPixWidth();
739
740       if (next_x - 1 < minVisibleX) {
741          curr_x = next_x;
742          continue;
743       }
744
745       const vector<tvCell> &thisMetricCells = cells[indirectMetrics[metriclcv]];
746       drawCells1Col(theDrawable,
747                     (curr_x + next_x - 1) / 2, // middle x
748                     offset_y + getMetricAreaPixHeight(), // start y
749                     thisMetricCells);
750
751       curr_x = next_x;
752    }
753
754    XSetClipMask(Tk_Display(theTkWindow), cellGC, None);
755 }
756
757 void tableVisi::drawCells1Col(Drawable theDrawable, int middle_x, int top_y,
758                               const vector<tvCell> &thisMetricCells) const {
759    // uses getVertPixFocusTop2Baseline() and getFocusLinePixHeight()
760    int curr_y = top_y;
761
762    int minVisibleY = 0;
763    int maxVisibleY = Tk_Height(theTkWindow)-1;
764
765    for (unsigned focuslcv=0; focuslcv < indirectFoci.size(); focuslcv++) {
766       if (curr_y > maxVisibleY)
767          break;
768
769       const int next_y = curr_y + getFocusLinePixHeight();
770       if (next_y - 1 < minVisibleY) {
771          curr_y = next_y;
772          continue;
773       }
774
775       const tvCell &theCell = thisMetricCells[indirectFoci[focuslcv]];
776       if (!theCell.isValid()) {
777          curr_y = next_y;
778          continue;
779       }
780
781       // making a new "string" would be too expensive (calls new):
782       char buffer[200];
783       double2string(buffer, theCell.getData());
784
785       int buffer_len = strlen(buffer);
786       int string_pix_width = Tk_TextWidth(cellFont, buffer, buffer_len);
787  
788       Tk_DrawChars(Tk_Display(theTkWindow), theDrawable,
789                   cellGC,
790                   cellFont,
791                   buffer, buffer_len,
792                   middle_x - string_pix_width / 2,
793                   curr_y + getVertPixCellTop2Baseline());
794
795       curr_y = next_y;
796    }
797 }
798
799 unsigned tableVisi::getVertPixCellTop2Baseline() const {
800    return 2 + cellFontMetrics.ascent;
801 }
802
803
804 void
805 tableVisi::ReleaseResources( void )
806 {
807     // release Tk resources that should be
808     // gone by the time we destroy the GUI
809     Tk_FreeColor(cellColor);
810     Tk_FreeColor(focusNameColor);
811     Tk_FreeColor(metricUnitsColor);
812     Tk_FreeColor(metricNameColor);
813     Tk_FreeColor(lineColor);
814     Tk_FreeColor(backgroundColor);
815     Tk_FreeColor(highlightedBackgroundColor);
816
817     Tk_FreeFont(cellFont);
818     Tk_FreeFont(focusNameFont);
819     Tk_FreeFont(metricUnitsFont);
820     Tk_FreeFont(metricNameFont);
821 }
822
823
824
825 /* *************************************************************** */
826
827 void tableVisi::clearMetrics(Tcl_Interp *interp) {
828    metrics.resize(0);
829    indirectMetrics.resize(0);
830    cells.resize(0);
831    all_cells_width = 0;
832
833    if (offscreenPixmap)
834       resize(interp);
835 }
836
837 void tableVisi::clearFoci(Tcl_Interp *interp) {
838    foci.resize(0);
839    indirectFoci.resize(0);
840
841    unsigned numMetrics = getNumMetrics();
842    for (unsigned i=0; i < numMetrics; i++) {
843       vector<tvCell> &theVec = cells[i];
844       theVec.resize(0);
845    }
846
847    all_cells_height = 0;
848    maxFocusNamePixWidth = 0;
849
850    if (offscreenPixmap)
851       resize(interp);
852 }
853
854 void tableVisi::addMetric(unsigned iVisiLibMetId,
855                           const string &metricName, const string &metricUnits) {
856    tvMetric newTvMetric(iVisiLibMetId,
857                         metricName, metricUnits,
858                         metricNameFont,
859                         metricUnitsFont,
860                         cellFont,
861                         numSigFigs);
862    metrics += newTvMetric;
863    indirectMetrics += (metrics.size()-1);
864    cells += vector<tvCell>();
865
866    all_cells_width += newTvMetric.getColPixWidth();
867
868    assert(metrics.size() == indirectMetrics.size());
869    assert(cells.size() == metrics.size());
870 }
871
872 void tableVisi::changeUnitsLabel (unsigned which, const string &new_name) {
873    if (which < indirectMetrics.size()) {
874        tvMetric &theMetric = metrics[indirectMetrics[which]];
875        theMetric.changeUnitsName(new_name);
876    }
877 }
878
879 void tableVisi::addFocus(unsigned iVisiLibFocusId, const string &focusName) {
880    tvFocus newTvFocus(iVisiLibFocusId, focusName, focusNameFont);
881    foci += newTvFocus;
882    indirectFoci += (foci.size()-1);
883
884    unsigned numMetrics = metrics.size();
885    for (unsigned metriclcv=0; metriclcv < numMetrics; metriclcv++) {
886       vector<tvCell> &metricCells = cells[metriclcv];
887       metricCells += tvCell();
888    }
889
890    if (focusLongNameMode)
891       ipmax(maxFocusNamePixWidth, newTvFocus.getLongNamePixWidth());
892    else
893       ipmax(maxFocusNamePixWidth, newTvFocus.getShortNamePixWidth());
894
895    all_cells_height += getFocusLinePixHeight();
896
897    assert(foci.size() == indirectFoci.size());
898 }
899
900 void tableVisi::deleteMetric(unsigned theColumn) {
901         unsigned metriclcv;
902
903    // fry selection, if necessary
904    if (theSelection == cell || theSelection == colOnly)
905       if (selectedCol == theColumn)
906          theSelection = none;
907
908    unsigned actualMetricIndex = indirectMetrics[theColumn];
909
910    // Update some internal gfx vrbles:
911    all_cells_width -= metrics[actualMetricIndex].getColPixWidth();
912
913    unsigned newNumMetrics = indirectMetrics.size()-1;
914    for (metriclcv=theColumn; metriclcv < newNumMetrics; metriclcv++)
915       indirectMetrics[metriclcv] = indirectMetrics[metriclcv+1];
916    indirectMetrics.resize(newNumMetrics);
917
918    for (metriclcv=actualMetricIndex; metriclcv < newNumMetrics; metriclcv++)
919       metrics[metriclcv] = metrics[metriclcv+1];
920    metrics.resize(newNumMetrics);
921
922    for (metriclcv=actualMetricIndex; metriclcv < newNumMetrics; metriclcv++)
923       cells[metriclcv] = cells[metriclcv+1];
924    cells.resize(newNumMetrics);
925
926    // now look for items whose index need to be 1 lower
927    for (metriclcv=0; metriclcv < newNumMetrics; metriclcv++)
928       if (indirectMetrics[metriclcv] > actualMetricIndex)
929          indirectMetrics[metriclcv]--;
930       else if (indirectMetrics[metriclcv] == actualMetricIndex)
931          assert(false); // we should have deleted this guy
932
933    // now see if the selected column needs to made 1 lower
934    if (theSelection == cell || theSelection == colOnly)
935       if (selectedCol > theColumn)
936          selectedCol--;
937
938    // a little sanity checking
939    assert(indirectMetrics.size() == metrics.size());
940    assert(cells.size() == metrics.size());
941    for (metriclcv=0; metriclcv < newNumMetrics; metriclcv++)
942       assert(indirectMetrics[metriclcv] < newNumMetrics);
943    if (theSelection == cell || theSelection == colOnly)
944       assert(selectedCol < newNumMetrics);
945 }
946
947 void tableVisi::deleteFocus(unsigned theRow) {
948         unsigned focuslcv;
949         unsigned metriclcv;
950
951    // A shameless carbon copy of the routine "deleteMetric"
952
953    // fry selection, if necessary
954    if (theSelection == cell || theSelection == rowOnly)
955       if (selectedRow == theRow)
956          theSelection = none;
957
958    unsigned actualFocusIndex = indirectFoci[theRow];
959    
960    // update some internal gfx vrbles:
961    all_cells_height -= getFocusLinePixHeight();
962    
963    unsigned newNumFoci = indirectFoci.size()-1;
964    for (focuslcv=theRow; focuslcv < newNumFoci; focuslcv++)
965       indirectFoci[focuslcv] = indirectFoci[focuslcv+1];
966    indirectFoci.resize(newNumFoci);
967
968    for (focuslcv=actualFocusIndex; focuslcv < newNumFoci; focuslcv++)
969       foci[focuslcv] = foci[focuslcv+1];
970    foci.resize(newNumFoci);
971
972    for (metriclcv=0; metriclcv < metrics.size(); metriclcv++) {
973       vector<tvCell> &theColumn = cells[metriclcv];
974
975       for (focuslcv=actualFocusIndex; focuslcv < newNumFoci; focuslcv++)
976          theColumn[focuslcv] = theColumn[focuslcv+1];
977
978       theColumn.resize(newNumFoci);
979    }
980
981    // now look for items whose index need to be 1 lower
982    for (focuslcv=0; focuslcv < newNumFoci; focuslcv++)
983       if (indirectFoci[focuslcv] > actualFocusIndex)
984          indirectFoci[focuslcv]--;
985       else if (indirectFoci[focuslcv] == actualFocusIndex)
986          assert(false); // we should have deleted this guy
987
988    // now see if the selection needs to be made 1 lower
989    if (theSelection == cell || theSelection == rowOnly)
990       if (selectedRow > theRow)
991          selectedRow --;
992
993    // a little sanity checking
994    assert(indirectFoci.size() == foci.size());
995    for (focuslcv = 0; focuslcv < newNumFoci; focuslcv++)
996       assert(indirectFoci[focuslcv] < newNumFoci);
997    if (theSelection == cell || theSelection == rowOnly)
998       assert(selectedRow < newNumFoci);
999
1000    // Finish updating some internal gfx vrbles:
1001    maxFocusNamePixWidth = 0;
1002    for (focuslcv=0; focuslcv < newNumFoci; focuslcv++)
1003       if (focusLongNameMode)
1004          ipmax(maxFocusNamePixWidth, foci[focuslcv].getLongNamePixWidth());
1005       else
1006          ipmax(maxFocusNamePixWidth, foci[focuslcv].getShortNamePixWidth());
1007 }
1008
1009 int tableVisi::partitionMetrics(int left, int right) {
1010    const tvMetric &pivot = metrics[indirectMetrics[left]];
1011
1012    int l = left-1;
1013    int r = right+1;
1014
1015    while (true) {
1016       while (metrics[indirectMetrics[--r]] > pivot)
1017          ;
1018
1019       while (metrics[indirectMetrics[++l]] < pivot)
1020          ;
1021
1022       if (l < r) {
1023          unsigned temp = indirectMetrics[l];
1024          indirectMetrics[l] = indirectMetrics[r];
1025          indirectMetrics[r] = temp;
1026       }
1027       else
1028          return r;
1029    }
1030 }
1031
1032 void tableVisi::sortMetrics(int left, int right) {
1033    if (left < right) {
1034       int middle = partitionMetrics(left, right);
1035       sortMetrics(left, middle);
1036       sortMetrics(middle+1, right);
1037    }
1038 }
1039
1040 void tableVisi::sortMetrics() {
1041    unsigned realSelectedMetric;
1042    if (theSelection == cell || theSelection == colOnly)
1043       realSelectedMetric = indirectMetrics[selectedCol];
1044
1045    sortMetrics(0, metrics.size()-1);
1046
1047    if (theSelection == cell || theSelection == colOnly) {
1048       for (unsigned metriclcv=0; metriclcv < indirectMetrics.size(); metriclcv++) {
1049          if (indirectMetrics[metriclcv] == realSelectedMetric) {
1050             selectedCol = metriclcv;
1051             return;
1052          }
1053       }
1054       assert(false);
1055    }
1056 }
1057
1058 void tableVisi::unsortMetrics() {
1059    // After this call, the metric which used to be in sorted order X will now
1060    // be in sorted order indirectMetrics[X] (this array lookup must be done first tho)
1061    unsigned newSelectedMetric;
1062    if (theSelection == cell || theSelection == colOnly)
1063       newSelectedMetric = indirectMetrics[selectedCol];
1064
1065    for (unsigned i=0; i < indirectMetrics.size(); i++)
1066       indirectMetrics[i] = i;
1067
1068    if (theSelection == cell || theSelection == colOnly)
1069       selectedCol = newSelectedMetric;
1070 }
1071
1072 int tableVisi::partitionFoci(int left, int right) {
1073    const tvFocus &pivot = foci[indirectFoci[left]];
1074
1075    int l = left-1;
1076    int r = right+1;
1077
1078    while (true) {
1079       do {
1080          r--;
1081       } while (foci[indirectFoci[r]].greater_than(pivot, focusLongNameMode)); 
1082
1083       do {
1084          l++;
1085       } while (foci[indirectFoci[l]].less_than(pivot, focusLongNameMode));
1086
1087       if (l < r) {
1088          unsigned temp = indirectFoci[l];
1089          indirectFoci[l] = indirectFoci[r];
1090          indirectFoci[r] = temp;
1091       }
1092       else
1093          return r;
1094    }
1095 }
1096
1097 int tableVisi::partitionFociByValues(const vector<tvCell> &theMetricColumn,
1098                                      int left, int right) {
1099    // note: theMetricColumn comes straight from "cells"; index it via focus numbers
1100    //       (not by the sorted indexes but rather by the real indexes) to get the
1101    //       cell value, needed for doing the comparisoin.
1102
1103    const tvCell &pivot = theMetricColumn[indirectFoci[left]];
1104
1105    int l = left-1;
1106    int r = right+1;
1107
1108    while (true) {
1109        do {r--;} while (theMetricColumn[indirectFoci[r]] > pivot);
1110        do {l++;} while (theMetricColumn[indirectFoci[l]] < pivot);
1111
1112        if (l < r) {
1113           unsigned temp = indirectFoci[l];
1114           indirectFoci[l] = indirectFoci[r];
1115           indirectFoci[r] = temp;
1116        }
1117        else
1118           return r;
1119    }
1120 }
1121
1122 void tableVisi::sortFoci(int left, int right) {
1123    if (left < right) {
1124       int middle = partitionFoci(left, right);
1125       sortFoci(left, middle);
1126       sortFoci(middle+1, right);
1127    }
1128 }
1129
1130 void tableVisi::sortFoci() {
1131    unsigned selectedRealFocus;
1132    if (theSelection == rowOnly || theSelection == cell)
1133       selectedRealFocus = indirectFoci[selectedRow];
1134
1135    sortFoci(0, foci.size()-1);
1136
1137    if (theSelection == rowOnly || theSelection == cell) {
1138       // we need to keep selectedRow consistent...
1139       for (unsigned focuslcv=0; focuslcv < indirectFoci.size(); focuslcv++) {
1140          if (indirectFoci[focuslcv] == selectedRealFocus) {
1141             selectedRow = focuslcv;
1142             return;
1143          }
1144       }
1145       assert(false);
1146    }
1147 }
1148
1149 void tableVisi::sortFociByValues(const vector<tvCell> &theMetricColumn,
1150                                  int left, int right) {
1151    if (left < right) {
1152       int middle = partitionFociByValues(theMetricColumn, left, right);
1153       sortFociByValues(theMetricColumn, left, middle);
1154       sortFociByValues(theMetricColumn, middle+1, right);
1155    }
1156 }
1157
1158 bool tableVisi::sortFociByValues() {
1159    // returns true iff successful (if there was a selected col which to sort by)
1160    if (theSelection != colOnly && theSelection != cell)
1161       return false;
1162
1163    assert(theSelection == colOnly || theSelection == cell);
1164    unsigned selectedRealFocus;
1165    if (theSelection == cell) {
1166       // in this case, we must keep "selectedRow" consistent.
1167       // Let's say that before this call, selectedRow was 0 (the first row).
1168       // Where is it after this call?  Unfortunately, it seems that we'll have to
1169       // go looking through the new, sorted, indirectFoci[], looking for the
1170       // original "real" focus.  (Actually, another solution is to intrude into the
1171       // actual sort routine, updating "selectedRow" whenever it moves)
1172       selectedRealFocus = indirectFoci[selectedRow];
1173    }
1174
1175    sortFociByValues(cells[indirectMetrics[selectedCol]], 0, foci.size()-1);
1176
1177    if (theSelection == cell) {
1178       // as described above...
1179       for (unsigned focuslcv=0; focuslcv < indirectFoci.size(); focuslcv++) {
1180          if (indirectFoci[focuslcv] == selectedRealFocus) {
1181             selectedRow = focuslcv;
1182             return true;
1183          }
1184       }
1185       assert(false);
1186    }
1187
1188    return true;
1189 }
1190
1191 void tableVisi::unsortFoci() {
1192    // keep theSelection consistent.  Let's say the first focus (in sorted order) was
1193    // selected before calling this routine.  What sorted position will this focus be
1194    // after this routine?  Simple.  It will be located at the "true" position before
1195    // the sorting is done.
1196    if (theSelection == rowOnly || theSelection == cell)
1197       selectedRow = indirectFoci[selectedRow];
1198
1199    for (unsigned i=0; i < indirectFoci.size(); i++)
1200       indirectFoci[i] = i;
1201 }
1202
1203 bool tableVisi::setFocusNameMode(Tcl_Interp *interp, bool longNameMode) {
1204    // returns true iff any changes
1205    if (focusLongNameMode == longNameMode)
1206       return false;
1207
1208    focusLongNameMode = longNameMode;
1209
1210    // recalculate maxFocusNamePixWidth:
1211    maxFocusNamePixWidth=0;
1212    for (unsigned focuslcv=0; focuslcv < foci.size(); focuslcv++) {
1213       const tvFocus &theFocus = foci[focuslcv];
1214
1215       if (focusLongNameMode)
1216          ipmax(maxFocusNamePixWidth, theFocus.getLongNamePixWidth());
1217       else
1218          ipmax(maxFocusNamePixWidth, theFocus.getShortNamePixWidth());
1219    }
1220
1221    resize(interp);
1222
1223    return true;
1224 }
1225
1226 bool tableVisi::setSigFigs(unsigned newNumSigFigs) {
1227    if (newNumSigFigs == numSigFigs)
1228       return false;
1229
1230    numSigFigs = newNumSigFigs;
1231    updateConversionString();
1232    all_cells_width = 0;
1233       // we'll be recalcing this from scratch, since col widths can change
1234
1235    for (unsigned met=0; met < metrics.size(); met++) {
1236       // sorted order is not important here...
1237       tvMetric &theMetric = metrics[met];
1238
1239       theMetric.changeNumSigFigs(newNumSigFigs, cellFont);
1240
1241       all_cells_width += theMetric.getColPixWidth();
1242    }
1243
1244    return true;
1245 }
1246
1247 void tableVisi::invalidateCell(unsigned theMetric, unsigned theFocus) {
1248    cells[theMetric][theFocus].invalidate();
1249 }
1250
1251 void tableVisi::setCellValidData(unsigned theMetric, unsigned theFocus, double data) {
1252    cells[theMetric][theFocus].setValidData(data);
1253 }
1254
1255 /* *************************************************************** */
1256
1257 bool tableVisi::adjustHorizSBOffset(Tcl_Interp *interp, float newFirst) {
1258    // doesn't redraw; returns true iff any changes.
1259    newFirst = moveScrollBar(interp, ".horizScrollbar", newFirst);
1260
1261    int total_cell_width = get_total_cell_x_pix();
1262    int old_offset_x = offset_x;
1263    offset_x = -(int)(newFirst * total_cell_width); // yes, always <= 0
1264
1265    return (offset_x != old_offset_x);
1266 }
1267
1268 bool tableVisi::adjustVertSBOffset(Tcl_Interp *interp, float newFirst) {
1269    // doesn't redraw; returns true iff any changes.
1270    newFirst = moveScrollBar(interp, ".vertScrollbar", newFirst);
1271
1272    int total_cell_height = get_total_cell_y_pix();
1273    int old_offset_y = offset_y;
1274    offset_y = -(int)(newFirst * total_cell_height); // yes, always <= 0
1275
1276    return (offset_y != old_offset_y);
1277 }
1278
1279 bool tableVisi::processClick(int x, int y) {
1280    if (x < 0)
1281       return false; // click was too far left
1282    if (y < 0)
1283       return false; // click was too far up
1284
1285    int focusAreaPixWidth = getFocusAreaPixWidth();
1286    int metricAreaPixHeight = getMetricAreaPixHeight();
1287    int focusLinePixHeight = getFocusLinePixHeight();
1288
1289    if (x < focusAreaPixWidth) {
1290       // Judging by the x coords, it looks like a click on a focus name...
1291       if (y < metricAreaPixHeight)
1292          return false; // ...but it fell in that blank space, upper-left corner
1293
1294       // adjust for scrollbar setting & metric area:
1295       y -= offset_y; // will _add_ to y
1296       y -= metricAreaPixHeight;
1297
1298       assert(y >= 0);
1299       unsigned rowClickedOn = y / focusLinePixHeight;
1300       if (rowClickedOn >= getNumFoci()) {
1301          // clicked below all rows.  This implies visible-y-pix  > total-y-pix, which
1302          // implies that the vertical sb was turned off hence offset_y will be zero.
1303          assert(offset_y == 0);
1304          return false;
1305       }
1306
1307       if (theSelection == rowOnly && selectedRow == rowClickedOn) {
1308          // cout << "unselected focus row " << rowClickedOn << endl;
1309          theSelection = none;
1310          return true;
1311       }
1312       else {
1313          // cout << "Selected focus row " << rowClickedOn << endl;
1314          theSelection = rowOnly;
1315          selectedRow = rowClickedOn;
1316          return true;
1317       }
1318    }
1319    else if (y < metricAreaPixHeight) {
1320       // Judging by the y coords, it looks like a click on a metric name.
1321       // We already know that the x coord excludes the possibility of it being a click
1322       // on a focus name.
1323
1324       unsigned clickCol;
1325       if (!xpix2col(x, clickCol))
1326          return false; // too far right or left
1327       
1328       if (theSelection == colOnly && selectedCol == clickCol) {
1329          // unselect the col
1330          theSelection = none;
1331          // cout << "Unselected metric col " << selectedCol << endl;
1332       }
1333       else {
1334          theSelection = colOnly;
1335          selectedCol = clickCol;
1336          // cout << "selected metric col " << selectedCol << endl;
1337       }
1338
1339       return true;
1340    }
1341    else {
1342       // We did not click on a focus or on a metric.  The best guess now is for
1343       // a click on some cell.
1344
1345       unsigned clickCol;
1346       if (!xpix2col(x, clickCol))
1347          return false; // too far right or left
1348
1349       unsigned clickRow;
1350       if (!ypix2row(y, clickRow))
1351          return false; // too far up or down
1352  
1353       if (theSelection==cell && selectedRow == clickRow && selectedCol == clickCol) {
1354          // unselect this cell
1355          theSelection = none;
1356          // cout << "unselected cell (" << selectedRow << "," << selectedCol << ")" << endl;
1357       }
1358       else {
1359          theSelection = cell;
1360          selectedRow = clickRow;
1361          selectedCol = clickCol;
1362          // cout << "selected cell (" << selectedRow << "," << selectedCol << ")" << endl;
1363       }
1364
1365       return true;
1366    }
1367 }