initialize graph data to NaN;
[dyninst.git] / visiClients / histVisi / src / PDGraph.C
1 /*
2  * Copyright (c) 1996-2000 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 // 
43 // PDGraph.C
44 //
45 // Definition of PDGraph class.
46 // A PDGraph object is the C++ portion of a custom Tcl widget to
47 // support the drawing of time-based histograms.
48 //
49 // Note that the implementation of the PDGraph class is separated into
50 // several files.  This file contains the implementation of the PDGraph 
51 // class itself, along with the implementation of the application domain 
52 // classes nested in the PDGraph object.  The custom Tcl subwidgets are
53 // implemented in other files:
54 //
55 //   Class                File
56 //   -----                ----
57 //   PDGraph::TimeAxisW   PDGTimeAxis.C
58 //   PDGraph::ValueAxisW  PDGValueAxis.C
59 //   PDGraph::DataW       PDGData.C
60 //
61 //---------------------------------------------------------------------------
62 // $Id: PDGraph.C,v 1.13 2001/08/23 14:44:52 schendel Exp $
63 //---------------------------------------------------------------------------
64 #include <limits.h>
65 #include <iostream.h>
66 #if defined(i386_unknown_nt4_0)
67 #  include <strstrea.h>
68 #else
69 #  include <strstream.h>
70 #endif
71 #include <math.h>
72
73 #include "common/h/String.h"
74 #include <assert.h>
75 #include <string.h>
76 #include "tcl.h"
77 #include "tk.h"
78
79 #include "common/h/Dictionary.h"
80 #include "PDGraph.h"
81 #include "paradyn/src/UIthread/minmax.h"
82 #include "pdutilOld/h/makenan.h"
83
84 #define ZOOM_THUMB_SIZE             (0.2)
85 #define ZOOM_UNIT_SIZE              (0.05)
86 #define PAN_UNIT_SIZE               (0.05)
87
88
89
90 //---------------------------------------------------------------------------
91 // utility functions for use in this file
92 //---------------------------------------------------------------------------
93
94 // FocusToPosition
95 // PositionToFocus
96 //
97 // Converts between a Paradyn focus (the amount of 
98 // the Histogram shown, as a fraction) and the Tcl scrollbar
99 // thumb position (the fraction of the distance from the top of
100 // the scrollbar to the bottom)
101 inline
102 static 
103 double FocusToPosition( double f )
104 {
105     return f - ZOOM_THUMB_SIZE;
106 }
107
108
109 inline
110 static
111 double  PositionToFocus( double p )
112 {
113     return p + ZOOM_THUMB_SIZE;
114 }
115
116
117
118
119 //
120 // We need some mechanism for accessing the instance data for a widget
121 // when all we are given is a Tk_Window handle.  We could use an implementation
122 // secret (that one of the members of the Tk_Window structure is a piece
123 // of client data; most built-in widgets use this client data to point
124 // to their instance data).  Many other GUI environments (e.g., 
125 // Motif and Windows) use this method to associate the instance
126 // data with the GUI representation of a widget.  If there is a documented
127 // mechanism for doing this in Tk, it is non-obvious.
128 //
129 // We chose to use an external mapping table because (a) the need
130 // to lookup C++ objects from Tk_Windows occurs infrequently, especially
131 // if we retain the information in the objects that need it, and (b)
132 // we had a mapping class available in the dictionary_hash class.
133 //
134 dictionary_hash<Tk_Window,ClientData> PDGraph::winInstDataMap( HashTkWindow );
135
136
137
138
139 //---------------------------------------------------------------------------
140 // PDGraph data
141 //---------------------------------------------------------------------------
142
143 #if !defined(i386_unknown_nt4_0)
144 #  define    DEF_PDGRAPH_FOREGROUND    "Black"
145 #  define    DEF_PDGRAPH_BACKGROUND    "#d9d9d9"
146 #  define    DEF_PDGRAPH_FONT        "Helvetica 12"
147 #else // !defined(i386_unknown_nt4_0)
148 #  define    DEF_PDGRAPH_FOREGROUND    "SystemButtonText"
149 #  define    DEF_PDGRAPH_BACKGROUND    "SystemButtonFace"
150 #  define    DEF_PDGRAPH_FONT        "{MS Sans Serif} 8"
151 #endif // !defined(i386_unknown_nt4_0)
152
153
154 #define DEF_LINE_COLORS             \
155 "blue yellow red turquoise1 DarkViolet magenta {medium sea green} orange2"
156
157 #define DEF_LINE_PATTERNS           \
158     "solid gray50 gray75 gray25"
159
160 // Tk options supported by the PDGraph widget
161 Tk_ConfigSpec PDGraph::configSpecs[] =
162 {
163     {
164         TK_CONFIG_BORDER,
165         "-background",
166         "background",
167         "Background",
168         DEF_PDGRAPH_BACKGROUND,
169         Tk_Offset(PDGraph, bgBorder),
170         TK_CONFIG_COLOR_ONLY,
171         NULL
172     },
173     {
174         TK_CONFIG_BORDER,
175         "-background",
176         "background",
177         "Background",
178         "white",
179         Tk_Offset(PDGraph, bgBorder),
180         TK_CONFIG_MONO_ONLY,
181         NULL
182     },
183     {
184         TK_CONFIG_SYNONYM,
185         "-bd",
186         "borderWidth",
187         NULL,
188         NULL,
189         0, 
190         0,
191         NULL
192     },
193     {
194         TK_CONFIG_SYNONYM, 
195         "-bg", 
196         "background",
197         NULL,
198         NULL, 
199         0, 
200         0,
201         NULL
202     },
203     {
204         TK_CONFIG_PIXELS, 
205         "-borderwidth", 
206         "borderWidth", 
207         "BorderWidth",
208         "2", 
209         Tk_Offset(PDGraph, borderWidth), 
210         0,
211         NULL
212     },
213     {
214         TK_CONFIG_SYNONYM, 
215         "-fg", 
216         "foreground",  
217         NULL,
218         NULL, 
219         0, 
220         0,
221         NULL
222     },
223     {
224         TK_CONFIG_BORDER, 
225         "-foreground", 
226         "foreground", 
227         "Foreground",
228         DEF_PDGRAPH_FOREGROUND,
229         Tk_Offset(PDGraph, fgBorder), 
230         TK_CONFIG_COLOR_ONLY,
231         NULL
232     },
233     {
234         TK_CONFIG_BORDER, 
235         "-foreground", 
236         "foreground", 
237         "Foreground",
238         "black", 
239         Tk_Offset(PDGraph, fgBorder), 
240         TK_CONFIG_MONO_ONLY,
241         NULL
242     },
243     {
244         TK_CONFIG_RELIEF, 
245         "-relief", 
246         "relief", 
247         "Relief",
248         "raised", 
249         Tk_Offset(PDGraph, relief), 
250         0,
251         NULL
252     },
253     {
254         TK_CONFIG_FONT,
255         "-font",
256         "font",
257         "Font",
258         DEF_PDGRAPH_FONT,
259         Tk_Offset( PDGraph, font ),
260         0,
261         NULL
262     },
263     {
264         TK_CONFIG_STRING,
265         "-lineColors",
266         "lineColors",
267         "LineColors",
268         DEF_LINE_COLORS,
269         Tk_Offset( PDGraph, lineColors ),
270         0,
271         NULL
272     },
273     {
274         TK_CONFIG_STRING,
275         "-linePatterns",
276         "linePatterns",
277         "LinePatterns",
278         DEF_LINE_PATTERNS,
279         Tk_Offset( PDGraph, linePatterns ),
280         0,
281         NULL
282     },
283     {
284         TK_CONFIG_BOOLEAN, 
285         "-dbl", 
286         "doubleBuffer", 
287         "DoubleBuffer",
288         "true", 
289         Tk_Offset(PDGraph, doubleBuffer), 
290         0,
291         NULL
292     },
293     {
294         TK_CONFIG_END, 
295         NULL, 
296         NULL, 
297         NULL,
298         NULL, 
299         0, 
300         0,
301         NULL
302     }
303 };
304
305
306
307
308 //---------------------------------------------------------------------------
309 // PDGraph methods
310 //---------------------------------------------------------------------------
311
312 PDGraph::PDGraph( void )
313  :  timeAxis( NULL ),
314     valAxis( NULL ),
315     dataw( NULL ),
316     tkwin( NULL ),
317     tkdisplay( NULL ),
318     legendWin( NULL ),
319     interp( NULL ),
320     widgetCmd( NULL ),
321     borderWidth( 0 ),
322     bgBorder( NULL ),
323     fgBorder( NULL ),
324     relief( TK_RELIEF_FLAT ),
325     gc( None ),
326     font( NULL ),
327     lineColors( NULL ),
328     linePatterns( NULL ),
329     doubleBuffer( 1 ),        // true
330     redrawPending( false )
331 {
332 }
333
334
335
336 PDGraph::~PDGraph( void )
337 {
338     if( tkdisplay != NULL )
339     {
340         Tk_FreeOptions(configSpecs, (char *)this, tkdisplay, 0);
341         if(gc != None)
342         {
343             Tk_FreeGC( tkdisplay, gc);
344         }
345
346         Curve::ReleaseLineSpecs( tkdisplay );
347     }
348
349     if( lineColors != NULL )
350     {
351         Tcl_Free( lineColors );
352         lineColors = NULL;
353     }
354
355     if( linePatterns != NULL )
356     {
357         Tcl_Free( linePatterns );
358         linePatterns = NULL;
359     }
360 }
361
362
363 // InitTclTk - initialize the Tcl/Tk members for the widget
364 //
365 int
366 PDGraph::InitTclTk( Tcl_Interp* interp, Tk_Window mwin, int argc, char* argv[] )
367 {
368     ClientData cd;
369     ostrstream wpathstr;
370     bool found;
371
372
373     // save the given interpreter
374     this->interp = interp;
375
376     
377     // build a frame to enclose our widgets
378     wpathstr << argv[1] << ends;
379     Tk_Window tmpMainW = Tk_CreateWindowFromPath( interp,
380                                                     mwin,
381                                                     wpathstr.str(),
382                                                     NULL );
383     wpathstr.rdbuf()->freeze( 0 );
384     wpathstr.seekp( 0 );
385     if( tmpMainW == NULL )
386     {
387         return TCL_ERROR;
388     }
389     Tk_SetClass( tmpMainW, "Pdgraph" );
390
391
392     // build and arrange subwidgets -
393     // call out to Tcl proc to handle this
394     ostrstream cmdstr;
395     
396     cmdstr << "::PDGraph::init " << argv[1] << ends;
397     Tcl_Obj* cmdobj = Tcl_NewStringObj( cmdstr.str(), -1 );
398     cmdstr.rdbuf()->freeze( 0 );
399     cmdstr.seekp( 0 );
400     if( Tcl_EvalObj( interp, cmdobj ) != TCL_OK )
401     {
402         Tk_DestroyWindow( tmpMainW );
403         return TCL_ERROR;
404     }
405
406     // obtain access to our subwidgets
407     wpathstr << argv[1] << ".legend" << ends;
408     Tk_Window tmpLegendW = Tk_NameToWindow( interp, wpathstr.str(), mwin );
409     wpathstr.rdbuf()->freeze( 0 );
410     wpathstr.seekp( 0 );
411     if( tmpLegendW == NULL )
412     {
413         Tk_DestroyWindow( tmpMainW );
414         return TCL_ERROR;
415     }
416
417     wpathstr << argv[1] << ".valaxis" << ends;
418     Tk_Window valAxisWin = Tk_NameToWindow( interp, wpathstr.str(), mwin );
419     wpathstr.rdbuf()->freeze( 0 );
420     wpathstr.seekp( 0 );
421     if( valAxisWin == NULL )
422     {
423         Tk_DestroyWindow( tmpMainW );
424         return TCL_ERROR;
425     }
426     found = winInstDataMap.find( valAxisWin, cd );
427     assert( found );
428     valAxis = (ValueAxisW*)cd;
429
430     wpathstr << argv[1] << ".timeaxis" << ends;
431     Tk_Window timeAxisWin = Tk_NameToWindow( interp, wpathstr.str(), mwin );
432     wpathstr.rdbuf()->freeze( 0 );
433     wpathstr.seekp( 0 );
434     if( timeAxisWin == NULL )
435     {
436         Tk_DestroyWindow( tmpMainW );
437         return TCL_ERROR;
438     }
439     found = winInstDataMap.find( timeAxisWin, cd );
440     assert( found );
441     timeAxis = (TimeAxisW*)cd;
442
443     wpathstr << argv[1] << ".data" << ends;
444     Tk_Window dataWin = Tk_NameToWindow( interp, wpathstr.str(), mwin );
445     wpathstr.rdbuf()->freeze( 0 );
446     wpathstr.seekp( 0 );
447     if( dataWin == NULL )
448     {
449         Tk_DestroyWindow( tmpMainW );
450         return TCL_ERROR;
451     }
452     found = winInstDataMap.find( dataWin, cd );
453     assert( found );
454     dataw = (DataW*)cd;
455
456     // now that we've successfully created our subwindows,
457     // keep hold of them
458     tkwin = tmpMainW;
459     tkdisplay = Tk_Display(tkwin);
460     legendWin = tmpLegendW;
461    
462     
463     // register an instance command with the interpreter
464     // so our widget can handle its own commands
465     widgetCmd = Tcl_CreateCommand(interp,
466         Tk_PathName(tmpMainW),
467         PDGraph::InstanceCmdCB,
468         (ClientData)this,
469         PDGraph::InstanceCmdDeletedCB);
470
471     Tk_CreateEventHandler(tkwin,
472         ExposureMask|StructureNotifyMask,
473         PDGraph::EventCB,
474         (ClientData)this);
475
476     // handle any configuration options given
477     if( Configure( interp, argc - 2, argv + 2, 0 ) != TCL_OK )
478     {
479         Tk_DestroyWindow(tkwin);
480         return TCL_ERROR;
481     }
482
483     // ensure we have graphics contexts to use
484     if( Curve::InitLineSpecs( interp, mwin, lineColors, linePatterns, fgBorder ) != TCL_OK )
485     {
486         Tk_DestroyWindow( tkwin );
487         return TCL_ERROR;
488     }
489
490     // intialize the C++ objects for our subwidgets
491     timeAxis->InitCPP( this );
492     valAxis->InitCPP( this, timeAxis->DetermineHeight() + 1);
493     dataw->InitCPP( this, valAxis->DetermineLabelHeight() );
494
495     // register our association of Tk_Window and instance data
496     winInstDataMap[tkwin] = (ClientData)this;
497
498
499     cmdstr << "::PDGraph::Legend::init_font " << argv[1] << ends;
500     cmdobj = Tcl_NewStringObj( cmdstr.str(), -1 );
501     cmdstr.rdbuf()->freeze( 0 );
502     cmdstr.seekp( 0 );
503     if( Tcl_EvalObj( interp, cmdobj ) != TCL_OK )
504     {
505         Tk_DestroyWindow( tmpMainW );
506         return TCL_ERROR;
507     }
508
509     return TCL_OK;
510 }
511
512
513
514
515 // ClassCmdCB - implementation of "pdgraph" Tcl command
516 //
517 int
518 PDGraph::ClassCmdCB( ClientData cd, Tcl_Interp* interp, int argc, char* argv[] )
519 {
520     Tk_Window mwin = (Tk_Window)cd;
521
522
523     // validate command argument count
524     if (argc < 2)
525     {
526         ostrstream estr;
527
528         estr << "wrong # args: should be \""
529             << argv[0]
530             << " pathName ?options?\""
531             << ends;
532         Tcl_SetObjResult( interp, Tcl_NewStringObj( estr.str(), -1 ));
533         estr.rdbuf()->freeze( 0 );
534
535         return TCL_ERROR;
536     }
537
538     // obtain an object to represent the widget
539     PDGraph* pGraph = new PDGraph;
540
541     // initialize the object
542     if( pGraph->InitTclTk( interp, mwin, argc, argv ) != TCL_OK )
543     {
544         delete pGraph;
545         return TCL_ERROR;
546     }
547
548     // set interpreter result to pathname associated with main window
549     Tcl_SetObjResult( interp,
550                         Tcl_NewStringObj( Tk_PathName( pGraph->tkwin ), -1 ));
551     
552     return TCL_OK;
553 }
554
555
556
557 // ZoomTo - zoom the graph to the indicate position
558 //
559 int
560 PDGraph::ZoomTo( double position )
561 {
562     int ret = TCL_OK;
563
564
565     assert( (position >= 0.0) && (position <= 1.0 - ZOOM_THUMB_SIZE) );
566
567     // determine new focus and start values
568     double newfocus = PositionToFocus( position );
569     double newstart = visScopeInfo.start + (visScopeInfo.focus - newfocus) / 2;
570
571     // guard against going outside the bounds for Tk's scrollbar positions
572     if( newstart < 0.0 )
573     {
574         newstart = 0.0;
575     }
576     if( newstart + newfocus > 1.0 )
577     {
578         newfocus = (1.0 - newstart);
579     }
580
581     assert( (newstart >= 0.0) && (newstart <= 1.0) );
582     assert( (newstart + newfocus) <= 1.0 );
583     visScopeInfo.focus = newfocus;
584     visScopeInfo.start = newstart;
585
586     // update the subwindows that need to know about
587     // the change - (in this case, the data window
588     // only, since the time axis already knows about
589     // the change)
590     dataw->UpdateConfiguration();
591
592     // make sure that the display gets updated
593     RequestRedraw();
594     timeAxis->RequestRedraw();
595
596     // set the interpreter's result so that the Tcl script can update the
597     // scrollbar positions accordingly
598     ostrstream ostr;
599
600     // make the result hold the zoom scrollbar position, followed by the
601     // pan scrollbar position
602     ostr << position << " " << position + ZOOM_THUMB_SIZE << " " 
603         << newstart << " " << newstart + newfocus               
604         << ends;
605     Tcl_SetObjResult( interp, Tcl_NewStringObj( ostr.str(), -1 ));
606     ostr.rdbuf()->freeze( 0 );
607
608     return ret;
609 }
610
611
612 // PanTo - pan the graph to the indicated position
613 //
614 int
615 PDGraph::PanTo( double position )
616 {
617     int ret = TCL_OK;
618
619
620     // guard against going outside the bounds for Tk's scrollbar positions
621     if( position < 0.0 )
622     {
623         position = 0.0;
624     }
625     if( (position + visScopeInfo.focus) > 1.0 )
626     {
627         visScopeInfo.focus = (1.0 - position);
628     }
629
630     // update the focus and start to reflect the new visible configuration
631     visScopeInfo.start = position;
632
633     // update the subwindows that need to know about
634     // the change - (in this case, the data window
635     // only, since the time axis already knows about
636     // the change)
637     dataw->UpdateConfiguration();
638
639     // ...post an eventual redraw request
640     RequestRedraw();
641     timeAxis->RequestRedraw();
642
643     // return our positions, so our container can update its scrollbars
644     ostrstream resstr;
645     resstr << position << " " << position + visScopeInfo.focus << ends;
646     Tcl_SetObjResult( interp, Tcl_NewStringObj( resstr.str(), -1 ));
647     resstr.rdbuf()->freeze( 0 );
648
649     return ret;
650 }
651
652
653 // HandleGetSelectedCommand - respond to the get_selected Tcl command
654 // on a pdgraph instance.  Builds a list of curve IDs for selected items.
655 //
656 int
657 PDGraph::HandleGetSelectedCommand( int argc, char* argv[] )
658 {
659     ostrstream rstr;        // result stream
660     int ret = TCL_OK;
661
662
663     if( argc == 2 )
664     {
665         // determine the selected curves
666         // (ask legend to give us a list of the selection)
667         ostrstream cmdstr;
668
669         cmdstr << "::PDGraph::Legend::get_selected "
670             << Tk_PathName( legendWin )
671             << ends;
672         ret = Tcl_EvalObj( interp, Tcl_NewStringObj( cmdstr.str(), -1 ));
673         cmdstr.rdbuf()->freeze( 0 );
674     }
675     else
676     {
677         // handle cases where we couldn't parse the command
678         // provide some usage information
679         rstr << "wrong # getselected args: should be '"
680             << argv[0]
681             << " getselected'"
682             << ends;
683
684         Tcl_SetObjResult( interp, Tcl_NewStringObj( rstr.str(), -1 ));
685         rstr.rdbuf()->freeze( 0 );
686
687         ret = TCL_ERROR;
688     }
689
690     return ret;
691 }
692
693
694
695 // HandleSmoothCommand - Responds to the smooth and unsmooth commands
696 //
697 int
698 PDGraph::HandleSmoothCommand( int argc, char* argv[], bool smooth )
699 {
700     ostrstream rstr;        // result stream
701     int ret = TCL_OK;
702     vector<Group*>    checkGroups;    // groups that need max value rechecks
703
704
705     if( argc == 3 )
706     {
707         // extract the items out of the list
708         int objc;
709         Tcl_Obj** objv;
710
711         ret = Tcl_ListObjGetElements( interp,
712                                         Tcl_NewStringObj( argv[2], -1 ),
713                                         &objc, &objv );
714         if( ret == TCL_OK )
715         {
716             int i;
717
718             // for each curve mentioned in the list
719             for( i = 0; i < objc; i++ )
720             {
721                 long lval;
722
723                 //convert the item (a string) to a number (a curveID)
724                 ret = Tcl_GetLongFromObj( interp, objv[i], &lval );
725                 if( ret == TCL_OK )
726                 {
727                     // make the change to the curve
728                     assert( curves[lval] != NULL );
729                     if( smooth )
730                     {
731                         curves[lval]->Smooth();
732                     }
733                     else
734                     {
735                         curves[lval]->Unsmooth();
736                     }
737
738                     // add curve's group to list of groups whose
739                     // max values need to be rechecked
740                     checkGroups += curves[lval]->group;
741
742                     // update the legend with the curve's new name
743                     ostrstream cmdstr;
744
745                     cmdstr << "::PDGraph::Legend::update_item_name "
746                         << Tk_PathName( legendWin ) << " "
747                         << lval << " "
748                         << '\"' << curves[lval]->GetName() << '\"'
749                         << ends;
750                     ret = Tcl_EvalObj( interp,
751                                         Tcl_NewStringObj( cmdstr.str(), -1 ) );
752                     cmdstr.rdbuf()->freeze( 0 );
753                     if( ret != TCL_OK )
754                     {
755                         break;
756                     }
757                 }
758                 else
759                 {
760                     break;
761                 }
762             }
763
764             // determine new axis max values
765             //
766             // (By smoothing or unsmoothing curves, we may have updated
767             // the max value shown on a curve.  Unfortunately, we can't
768             // just consider the curves we changed, since the smoothing/
769             // unsmoothing operation may have caused a data point for
770             // another curve to become the new maximum value.  So we check
771             // each curve associated with each axis that had a curve that
772             // was smoothed or unsmoothed.)
773             unsigned int g;
774             for( g = 0; g < checkGroups.size(); g++ )
775             {
776                 Group* grp = checkGroups[g];
777                 assert( grp != NULL );
778
779                 double newMax = 0.0;
780                 unsigned int j;
781
782                 // check for new max value for curves of this group
783                 for( j = 0; j < grp->curves.size(); j++ )
784                 {
785                     if( grp->curves[j]->GetMaxActiveValue() > newMax )
786                     {
787                         newMax = grp->curves[j]->GetMaxActiveValue();
788                     }
789                 }
790
791                 // now update with the new max value
792                 UpdateAxisMaxValue( grp->axis, newMax );
793             }
794         }
795
796         // update our display based on our new configuration
797         dataw->UpdateConfiguration();
798     }
799     else
800     {
801         // handle cases where we couldn't parse the command
802         rstr << "wrong # "
803             << (smooth ? "smooth " : "unsmooth ")
804             << "args: should be '"
805             << argv[0]
806             << " remove <cid_list>'"
807             << ends;
808
809         Tcl_SetObjResult( interp, Tcl_NewStringObj( rstr.str(), -1 ) );
810         rstr.rdbuf()->freeze( 0 );
811
812         ret = TCL_ERROR;
813     }
814     return ret;
815 }
816
817
818
819 // HandleShowCommand - respond to the show and hide commands
820 //
821 int
822 PDGraph::HandleShowCommand( int argc, char* argv[], bool show )
823 {
824     ostrstream rstr;        // result stream
825     int ret = TCL_OK;
826
827     if( argc == 3 )
828     {
829         // extract the items out of the list
830         int objc;
831         Tcl_Obj** objv;
832
833         ret = Tcl_ListObjGetElements( interp,
834                                         Tcl_NewStringObj( argv[2], -1 ),
835                                         &objc, &objv );
836         if( ret == TCL_OK )
837         {
838             int i;
839
840             // for each curve mentioned in the list
841             for( i = 0; i < objc; i++ )
842             {
843                 long lval;
844
845                 //convert the item (a string) to a number (a curveID)
846                 ret = Tcl_GetLongFromObj( interp, objv[i], &lval );
847                 if( ret == TCL_OK )
848                 {
849                     assert( curves[lval] != NULL );
850
851                     if( show )
852                     {
853                         curves[lval]->Show();
854                     }
855                     else
856                     {
857                         curves[lval]->Hide();
858                     }
859                 }
860                 else
861                 {
862                     break;
863                 }
864             }
865
866             // update our display
867             UpdateGeometry();
868             dataw->UpdateConfiguration();
869         }
870     }
871     else
872     {
873         // handle cases where we couldn't parse the command
874         rstr << "wrong # "
875             << (show ? "show " : "hide ")
876             << "args: should be '"
877             << argv[0]
878             << " remove <cid_list>'"
879             << ends;
880
881         Tcl_SetObjResult( interp, Tcl_NewStringObj( rstr.str(), -1 ) );
882         rstr.rdbuf()->freeze( 0 );
883
884         ret = TCL_ERROR;
885     }
886
887     return ret;
888
889 }
890
891
892
893 // HandleRemoveCommand - respond to the remove command by removing
894 // the indicated curves
895 //
896 int
897 PDGraph::HandleRemoveCommand( int argc, char* argv[] )
898 {
899     ostrstream rstr;        // result stream
900     int ret = TCL_OK;
901
902     if( argc == 3 )
903     {
904         // extract the items out of the list
905         int objc;
906         Tcl_Obj** objv;
907
908         ret = Tcl_ListObjGetElements( interp,
909                                         Tcl_NewStringObj( argv[2], -1 ),
910                                         &objc, &objv );
911         if( ret == TCL_OK )
912         {
913             int i;
914
915             // for each curve mentioned in the list
916             for( i = 0; i < objc; i++ )
917             {
918                 long cid;
919
920                 //convert the item (a string) to a number (a curveID)
921                 ret = Tcl_GetLongFromObj( interp, objv[i], &cid );
922                 if( ret == TCL_OK )
923                 {
924                     ostrstream cmdstr;
925
926                     cmdstr << "::PDGraph::Legend::remove_item "
927                         << Tk_PathName( legendWin )
928                         << " "
929                         << cid
930                         << ends;
931                     ret = Tcl_EvalObj( interp,
932                                         Tcl_NewStringObj( cmdstr.str(), -1 ));
933                     cmdstr.rdbuf()->freeze( 0 );
934                     if( ret == TCL_OK )
935                     {
936                         // remove the curve
937                         if( curves[cid] != NULL )
938                         {
939                             Remove( curves[cid] );
940                             curves[cid] = NULL;
941                         }
942                     }
943                     else
944                     {
945                         break;
946                     }
947                 }
948             }
949
950             // update our display
951             UpdateGeometry();
952             dataw->UpdateConfiguration();
953         }
954     }
955     else
956     {
957         // handle cases where we couldn't parse the command
958         rstr << "wrong # remove args: should be '"
959             << argv[0]
960             << " remove <cid_list>'"
961             << ends;
962
963         Tcl_SetObjResult( interp, Tcl_NewStringObj( rstr.str(), -1 ));
964         rstr.rdbuf()->freeze( 0 );
965
966         ret = TCL_ERROR;
967     }
968
969     return ret;
970 }
971
972
973
974 // HandleZoomCommand - respond to the zoom command by validating command
975 // and zooming graph if needed
976 //
977 int
978 PDGraph::HandleZoomCommand( int argc, char* argv[] )
979 {
980     int ret = TCL_OK;
981     ostrstream estr;
982     double position = 0.0;
983     bool ok = true;
984
985
986     if( argc > 2 )
987     {
988         if( !strcmp( argv[2], "scroll" ) )
989         {
990             // it is a scroll +/- 1 page or unit
991             if( argc == 5 )
992             {
993                 // attempt to interpre the units
994                 if( !strcmp( argv[4], "pages" ) )
995                 {
996                     // determine where to zoom to
997                     if( !strcmp( argv[3], "1" ) )
998                     {
999                         // page up
1000                         position = FocusToPosition( visScopeInfo.focus ) + 
1001                                                     ZOOM_THUMB_SIZE;
1002                     }
1003                     else if( !strcmp( argv[3], "-1" ) )
1004                     {
1005                         // page down
1006                         position = FocusToPosition( visScopeInfo.focus ) - 
1007                                                     ZOOM_THUMB_SIZE;
1008                     }
1009                     else
1010                     {
1011                         // indicate malformed request
1012                         estr << "bad zoom option";
1013                         ok = false;
1014                     }
1015                 }
1016                 else if( !strcmp( argv[4], "units" ) )
1017                 {
1018                     // determine where to zoom to
1019                     if( !strcmp( argv[3], "1" ) )
1020                     {
1021                         // unit up
1022                         position = FocusToPosition( visScopeInfo.focus ) + 
1023                                                     ZOOM_UNIT_SIZE;
1024                     }
1025                     else if( !strcmp( argv[3], "-1" ) )
1026                     {
1027                         // unit down
1028                         position = FocusToPosition( visScopeInfo.focus ) - 
1029                                                     ZOOM_UNIT_SIZE;
1030                     }
1031                     else
1032                     {
1033                         // indicate malformed request
1034                         estr << "bad zoom option";
1035                         ok = false;
1036                     }
1037                 }
1038                 else
1039                 {
1040                     // indicate reason for failure
1041                     estr << "bad zoom option";
1042                     ok = false;
1043                 }
1044             }
1045             else
1046             {
1047                 // indicate reason for failure
1048                 estr << "wrong # zoom args";
1049                 ok = false;
1050             }
1051         }
1052         else if( !strcmp( argv[2], "moveto" ) )
1053         {
1054             // it is a scroll to specific position
1055             if( argc == 4 )
1056             {
1057                 // determine where to zoom to
1058                 position = atof( argv[3] );
1059             }
1060             else
1061             {
1062                 estr << "wrong # zoom args";
1063                 ok = false;
1064             }
1065         }
1066         else
1067         {
1068             // indicate bad zoom option
1069             estr << "bad zoom option";
1070             ok = false;
1071         }
1072     }
1073     else
1074     {
1075         // we can't tell which zoom variant they intended
1076         estr << "wrong # zoom args";
1077         ok = false;
1078     }
1079
1080
1081     if( ok )
1082     {
1083         // validate position range
1084         if( position < 0.0 )
1085         {
1086             position = 0.0;
1087         }
1088         else if( position > 1.0 - ZOOM_THUMB_SIZE )
1089         {
1090             position = 1.0 - ZOOM_THUMB_SIZE;
1091         }
1092
1093         // move to new position
1094         ret = ZoomTo( position );
1095     }
1096     else
1097     {
1098         // handle cases where we couldn't parse the command
1099         // provide some usage information
1100         estr << ": should be '"
1101             << argv[0]
1102             << " zoom scroll <number> <units>' or '"
1103             << argv[0]
1104             << " zoom moveto <position>'"
1105             << ends;
1106
1107         // set the result
1108         Tcl_SetObjResult( interp, Tcl_NewStringObj( estr.str(), -1 ));
1109         estr.rdbuf()->freeze( 0 );
1110
1111         ret = TCL_ERROR;
1112     }
1113
1114     return ret;
1115 }
1116
1117
1118 // HandlePanCommand - respond to the pan command by validating params and
1119 // panning graph if needed
1120 //
1121 int PDGraph::HandlePanCommand( int argc, char* argv[] )
1122 {
1123     int ret = TCL_OK;
1124     ostrstream estr;
1125     double position = 0.0;
1126     bool ok = true;
1127
1128
1129     if( argc > 2 )
1130     {
1131         if( !strcmp( argv[2], "scroll" ) )
1132         {
1133             // it is a scroll +/- 1 page or unit
1134             if( argc == 5 )
1135             {
1136                 // attempt to interpret the units
1137                 if( !strcmp( argv[4], "pages" ) )
1138                 {
1139                     // determine where to move to
1140                     if( !strcmp( argv[3], "1" ) )
1141                     {
1142                         // page left
1143                         position = visScopeInfo.start + visScopeInfo.focus;
1144                     }
1145                     else if( !strcmp( argv[3], "-1" ) )
1146                     {
1147                         // page right
1148                         position = visScopeInfo.start - visScopeInfo.focus;
1149                     }
1150                     else
1151                     {
1152                         // indicate malformed request
1153                         ok = false;
1154                         estr << "bad pan option";
1155                     }
1156                 }
1157                 else if( !strcmp( argv[4], "units" ) )
1158                 {
1159                     // determine where to move to
1160                     if( !strcmp( argv[3], "1" ) )
1161                     {
1162                         // unit up
1163                         position = visScopeInfo.start + PAN_UNIT_SIZE;
1164                     }
1165                     else if( !strcmp( argv[3], "-1" ) )
1166                     {
1167                         // unit down
1168                         position = visScopeInfo.start - PAN_UNIT_SIZE;
1169                     }
1170                     else
1171                     {
1172                         // indicate malformed request
1173                         estr << "bad pan option";
1174                         ok = false;
1175                     }
1176                 }
1177                 else
1178                 {
1179                     // indicate reason for failure
1180                     ok = false;
1181                     estr << "bad pan option";
1182                 }
1183             }
1184             else
1185             {
1186                 // indicate reason for failure
1187                 estr << "wrong # pan args";
1188                 ok = false;
1189             }
1190         }
1191         else if( !strcmp( argv[2], "moveto" ) )
1192         {
1193             // it is a scroll to specific position
1194             if( argc == 4 )
1195             {
1196                 // determine where to move to
1197                 position = atof( argv[3] );
1198             }
1199             else
1200             {
1201                 estr << "wrong # pan args";
1202                 ok = false;
1203             }
1204         }
1205         else
1206         {
1207             estr << "bad pan option";
1208             ok = false;
1209         }
1210     }
1211     else
1212     {
1213         // we can't tell which variant they intended
1214         estr << "wrong # pan args";
1215         ok = false;
1216     }
1217
1218
1219     if( ok )
1220     {
1221         // validate position range
1222         if( position < 0.0 )
1223         {
1224             position = 0.0;
1225         }
1226         else if( (position + visScopeInfo.focus) > 1.0 )
1227         {
1228             position -= (position + visScopeInfo.focus - 1.0);
1229         }
1230
1231         // move to new position
1232         ret = PanTo( position );
1233     }
1234     else
1235     {
1236         // handle cases where we couldn't parse the command
1237         // by providing some usage information
1238         estr << ": should be '"
1239             << argv[0]
1240             << " pan scroll <number> <units>' or '"
1241             << argv[0]
1242             << " pan moveto <position>'"
1243             << ends;
1244
1245         // set the result
1246         Tcl_SetObjResult( interp, Tcl_NewStringObj( estr.str(), -1 ));
1247         estr.rdbuf()->freeze( 0 );
1248
1249         ret = TCL_ERROR;
1250     }
1251
1252     return ret;
1253 }
1254
1255
1256
1257 // HandleCmd - respond to an instance command.
1258 // This method acts as a switchboard to route commands to their correct
1259 // handling methods
1260 //
1261 int
1262 PDGraph::HandleCmd( Tcl_Interp* interp, int argc, char* argv[] )
1263 {
1264     int result = TCL_OK;
1265     size_t length;
1266     char c;
1267
1268
1269     // verify argument count
1270     if( argc < 2 )
1271     {
1272         ostrstream ostr;
1273
1274         ostr << "wrong # args: should be \""
1275             << argv[0]
1276             << " option ?arg arg ...?\""
1277             << ends;
1278         Tcl_SetObjResult( interp, Tcl_NewStringObj( ostr.str(), -1 ));
1279         ostr.rdbuf()->freeze( 0 );
1280
1281         return TCL_ERROR;
1282     }
1283
1284     // bump our reference count so we can't be
1285     // deleted during this command processing
1286     Tcl_Preserve((ClientData)this);
1287
1288
1289     c = argv[1][0];
1290     length = strlen(argv[1]);
1291     if((c == 'c') && (strncmp(argv[1], "cget", length) == 0) && (length >= 2))
1292     {
1293         // handle a 'cget' command
1294         if (argc != 3)
1295         {
1296             ostrstream ostr;
1297
1298             ostr << "wrong # args: should be \""
1299                 << argv[0]
1300                 << " cget option\""
1301                 << ends;
1302             Tcl_SetObjResult( interp, Tcl_NewStringObj( ostr.str(), -1 ));
1303             ostr.rdbuf()->freeze( 0 );
1304
1305             goto error;
1306         }
1307         result = Tk_ConfigureValue( interp,
1308             tkwin,
1309             configSpecs,
1310             (char*)this,
1311             argv[2],
1312             0);
1313     }
1314     else if((c == 'c') && (strncmp(argv[1], "configure", length) == 0) && (length >= 2))
1315     {
1316         // handle a 'configure' command
1317         if( argc == 2 )
1318         {
1319             result = Tk_ConfigureInfo( interp,
1320                 tkwin,
1321                 configSpecs,
1322                 (char*)this,
1323                 NULL,
1324                 0);
1325         }
1326         else if( argc == 3 )
1327         {
1328             result = Tk_ConfigureInfo( interp,
1329                 tkwin,
1330                 configSpecs,
1331                 (char*)this,
1332                 argv[2],
1333                 0);
1334         }
1335         else
1336         {
1337             // handle any widget-specific configuration options
1338             result = Configure( interp, argc-2, argv+2, TK_CONFIG_ARGV_ONLY );
1339         }
1340     }
1341     else if( (c == 'p') && !strncmp( "pan", argv[1], length ) )
1342     {
1343         // handle a change in the pan position
1344         result = HandlePanCommand( argc, argv );
1345     }
1346     else if( (c == 'z') && !strncmp( "zoom", argv[1], length ) )
1347     {
1348         // handle a change in the zoom position
1349         result = HandleZoomCommand( argc, argv );
1350     }
1351     else if( (c == 'g') && !strncmp( "getselected", argv[1], length ) )
1352     {
1353         // handle request for selection
1354         result = HandleGetSelectedCommand( argc, argv );
1355     }
1356     else if( (c == 's') && !strncmp( "smooth", argv[1], length ) )
1357     {
1358         result = HandleSmoothCommand( argc, argv, true );
1359     }
1360     else if( (c == 'u') && !strncmp( "unsmooth", argv[1], length ) )
1361     {
1362         result = HandleSmoothCommand( argc, argv, false );
1363     }
1364     else if( (c == 'h') && !strncmp( "hide", argv[1], length ) )
1365     {
1366         result = HandleShowCommand( argc, argv, false );
1367     }
1368     else if( (c == 's') && !strncmp( "show", argv[1], length ) )
1369     {
1370         result = HandleShowCommand( argc, argv, true );
1371     }
1372     else if( (c == 'r') && !strncmp( "remove", argv[1], length ) )
1373     {
1374         result = HandleRemoveCommand( argc, argv );
1375     }
1376     else
1377     {
1378         ostrstream ostr;
1379
1380         ostr << "bad option \""
1381             << argv[1]
1382             << "\": must be cget, configure, zoom, pan, getselected, smooth, unsmooth, or remove"
1383             << ends;
1384         Tcl_SetObjResult( interp, Tcl_NewStringObj( ostr.str(), -1 ));
1385         ostr.rdbuf()->freeze( 0 );
1386
1387         goto error;
1388     }
1389
1390     // schedule a redraw to reflect our new configuration
1391     RequestRedraw();
1392
1393     // drop our reference count
1394     Tcl_Release((ClientData)this);
1395     return result;
1396
1397 error:
1398     // drop our reference count
1399     Tcl_Release((ClientData)this);
1400     return TCL_ERROR;
1401 }
1402
1403
1404
1405 // Configure - handle Tcl configuration of the widget from the
1406 // given arguments
1407 //
1408 int
1409 PDGraph::Configure(Tcl_Interp* interp, int argc, char* argv[], int flags )
1410 {
1411     // handle Tk options
1412     if( Tk_ConfigureWidget(interp,
1413         tkwin,
1414         configSpecs,
1415         argc, argv,
1416         (char *)this, flags) != TCL_OK)
1417     {
1418         return TCL_ERROR;
1419     }
1420
1421     // set the background for our windows based on our 
1422     // (possibly) updated background option
1423     Tk_SetWindowBackground(tkwin, Tk_3DBorderColor(bgBorder)->pixel);
1424
1425     // create a graphics context for drawing
1426     if( gc == None )
1427     {
1428         XGCValues gcValues;
1429         unsigned long mask = GCFunction | GCGraphicsExposures | GCFont | 
1430              GCForeground | GCBackground;
1431
1432         gcValues.foreground = Tk_3DBorderColor(fgBorder)->pixel;
1433         gcValues.background = Tk_3DBorderColor(bgBorder)->pixel;
1434         gcValues.function = GXcopy;
1435         gcValues.graphics_exposures = False;
1436         gcValues.font = Tk_FontId( font );
1437         gc = Tk_GetGC( tkwin, mask, &gcValues );
1438     }
1439
1440     // register our desired geometry
1441     Tk_GeometryRequest(tkwin, 200, 150);
1442     Tk_SetInternalBorder(tkwin, borderWidth);
1443
1444     // update our font metrics
1445     Tk_GetFontMetrics( font, &fontm );
1446
1447     // arrange for ourselves to be redisplayed with
1448     // our new configuration
1449     RequestRedraw();
1450
1451     return TCL_OK;
1452 }
1453
1454
1455 // HandleConfigureNotification - respond to a notification that
1456 // the widget's configuration has changed
1457 //
1458 void
1459 PDGraph::HandleConfigureNotification( void )
1460 {
1461     // allow the subwindows to refigure their respective geometry,
1462     // in case it changed with the new configuration
1463     UpdateGeometry();
1464
1465     // schedule a redraw if needed
1466     RequestRedraw();
1467 }
1468
1469
1470 // HandleEvent - respond to the given X event
1471 //
1472 void
1473 PDGraph::HandleEvent( XEvent* eventPtr )
1474 {
1475     switch( eventPtr->type )
1476     {
1477     case Expose:
1478         // schedule a redraw if needed
1479         RequestRedraw();
1480         break;
1481
1482     case ConfigureNotify:
1483         HandleConfigureNotification();
1484         break;
1485
1486     case DestroyNotify:
1487         // remove our instance command
1488         Tcl_DeleteCommandFromToken(interp, widgetCmd);
1489
1490         // cancel any pending redraw as unnecessary
1491         CancelRedraw();
1492
1493         // schedule our own termination
1494         Tcl_EventuallyFree((ClientData)this, PDGraph::DestroyCB);
1495
1496         break;
1497
1498     default:
1499         // we don't handle the other event types
1500         ;
1501     }
1502 }
1503
1504
1505 // HandleInstanceCommandDeleted - respond to situation when
1506 // instance command has been deleted
1507 void
1508 PDGraph::HandleInstanceCmdDeleted( void ) 
1509 {
1510     // destroy the widget in case it hasn't been destroyed already
1511     if( tkwin != NULL )
1512     {
1513         Tk_Window savedwin = tkwin;
1514
1515         // indicate that we're on our way out
1516         tkwin = NULL;
1517
1518         // now destroy our windows - this will
1519         // eventually result in our own destruction
1520         Tk_DestroyWindow( savedwin );
1521     }
1522 }
1523
1524
1525
1526 // DrawBorder - handle the drawing of our border to our Tk window
1527 void
1528 PDGraph::DrawBorder( void ) const
1529 {
1530     Pixmap pm = None;
1531     Drawable d;
1532
1533     if(!Tk_IsMapped(tkwin))
1534     {
1535         return;
1536     }
1537
1538     // draw into offscreen pixmap if desired
1539     if( doubleBuffer )
1540     {
1541         pm = Tk_GetPixmap(tkdisplay,
1542             Tk_WindowId(tkwin),
1543             Tk_Width(tkwin), Tk_Height(tkwin),
1544             DefaultDepthOfScreen(Tk_Screen(tkwin)));
1545         d = pm;
1546     }
1547     else
1548     {
1549         d = Tk_WindowId(tkwin);
1550     }
1551
1552     // draw the desired border
1553     Tk_Fill3DRectangle(tkwin,
1554         d,
1555         bgBorder,
1556         0, 0,
1557         Tk_Width(tkwin), Tk_Height(tkwin),
1558         borderWidth, relief);
1559
1560
1561     // copy from the offscreen pixmap if needed
1562     if( doubleBuffer )
1563     {
1564         XCopyArea(tkdisplay,
1565             pm,
1566             Tk_WindowId(tkwin),
1567             gc,
1568             0, 0,
1569             Tk_Width(tkwin), Tk_Height(tkwin),
1570             0, 0);
1571         Tk_FreePixmap(tkdisplay, pm);
1572     }
1573 }
1574
1575
1576 // Draw - output a representation of our data to our window
1577 // (Mostly, this work is handled directly by the subwidgets.)
1578 void
1579 PDGraph::Draw( void )
1580 {
1581     // indicate that we're doing our update
1582     redrawPending = false;
1583
1584     // draw our border, if needed
1585     if( borderWidth > 0 )
1586     {
1587         DrawBorder();
1588     }
1589 }
1590
1591
1592 // InstallClassCommand - installs the "pdgraph" command
1593 // into the given Tcl interpreter
1594 //
1595 int
1596 PDGraph::InstallClassCommand( Tcl_Interp* interp )
1597 {
1598     // install our class command
1599     Tcl_Command cmd = Tcl_CreateCommand( interp,
1600                 "pdgraph",
1601                 PDGraph::ClassCmdCB,
1602                 (ClientData)Tk_MainWindow( interp ),
1603                 NULL );
1604     if( cmd == NULL )
1605     {
1606         return TCL_ERROR;
1607     }
1608
1609     // install our subwidget class commands
1610     if( PDGraph::TimeAxisW::InstallClassCommand( interp ) != TCL_OK )
1611     {
1612         Tcl_DeleteCommandFromToken( interp, cmd );
1613         return TCL_ERROR;
1614     }
1615
1616     if( PDGraph::ValueAxisW::InstallClassCommand( interp ) != TCL_OK )
1617     {
1618         Tcl_DeleteCommandFromToken( interp, cmd );
1619         return TCL_ERROR;
1620     }
1621
1622     if( PDGraph::DataW::InstallClassCommand( interp ) != TCL_OK )
1623     {
1624         Tcl_DeleteCommandFromToken( interp, cmd );
1625         return TCL_ERROR;
1626     }
1627     return TCL_OK;
1628 }
1629
1630
1631
1632 // UpdateHistogramInfo - updates the graph's idea about the
1633 // current characteristics of the histogram
1634 //
1635 void
1636 PDGraph::UpdateHistogramInfo(double startTimestamp,
1637                               unsigned int nBuckets,
1638                               double bucketWidth)
1639 {
1640     assert( startTimestamp >= histInfo.startTimestamp );
1641     assert( bucketWidth > 0.0 );
1642     assert( nBuckets > 0 );
1643
1644
1645     // check whether we're changing the time base
1646     bool changingTimeBase = !((bucketWidth == histInfo.bucketWidth) &&
1647                              (startTimestamp == histInfo.startTimestamp ));
1648
1649     // short circuit if we're not changing anything
1650     if( !changingTimeBase && (nBuckets == histInfo.nBuckets) )
1651     {
1652         return;
1653     }
1654
1655     // update histogram characteristics
1656     histInfo.startTimestamp = startTimestamp;
1657     histInfo.nBuckets = nBuckets;
1658     histInfo.bucketWidth = bucketWidth;
1659
1660     // update timeAxis based on the new information
1661     timeAxis->UpdateConfiguration();
1662
1663     // reset curves in preparation for new data
1664     for( unsigned int idx = 0; idx < curves.size(); idx++ )
1665     {
1666         if( curves[idx] != NULL )
1667         {
1668             curves[idx]->nPoints = 0;
1669         }
1670     }
1671
1672     // redraw all curves to reflect new histogram characteristics
1673     RequestRedraw();
1674     timeAxis->RequestRedraw();
1675     dataw->RequestRedraw();
1676 }
1677
1678
1679 // AddCurve - add a curve to the graph with the given metric and
1680 // resource names
1681 //
1682 int
1683 PDGraph::AddCurve( const char* metricName,
1684                     const char* metricLabel,
1685                     const char* resourceName )
1686 {
1687     unsigned int idx = 0;
1688
1689
1690     // find the group (if any) that represents this metric
1691     // a match is based on the metric label
1692     PDGraph::Group* group = FindGroup( metricLabel );
1693     if( group == NULL )
1694     {
1695         // build a new object to represent group
1696         group = new Group( metricLabel );
1697         assert( group != NULL );
1698
1699         // keep hold of the new object
1700         groups += group;
1701     }
1702     assert( group != NULL );
1703
1704     // obtain an object to represent the curve
1705     PDGraph::Curve* curve = new PDGraph::Curve( metricName,
1706                                                 resourceName,
1707                                                 histInfo.nBuckets );
1708     group->Add( curve );
1709
1710     // find a location in the curve vector for the new curve
1711     while( idx < curves.size() )
1712     {
1713         if( curves[idx] == NULL )
1714         {
1715             curves[idx] = curve;
1716             break;
1717         }
1718         idx++;
1719     }
1720
1721     // grow the curve vector if necessary
1722     if( idx == curves.size() )
1723     {
1724         curves += curve;
1725     }
1726     CurveID cid = idx;
1727
1728     
1729     // now count the number of curves we know about
1730     unsigned int nCurves = 0;
1731     for( idx = 0; idx < curves.size(); idx++ )
1732     {
1733         if( curves[idx] != NULL )
1734         {
1735             nCurves++;
1736         }
1737     }
1738
1739     // update the legend
1740     ostrstream cmdstr;
1741
1742
1743     cmdstr << "::PDGraph::Legend::add_item "
1744         << Tk_PathName( legendWin ) << " "  // legend window name
1745         << nCurves - 1 << " "               // curve index within legend
1746         << cid << " "                       // curve ID (note - not the same as index)
1747         << '{' << curve->GetName() << "} "    // curve metric name
1748         << '\"' << Tk_NameOfColor( curve->GetLineSpec().color ) << "\" " // curve line color
1749         << '\"' << curve->GetLineSpec().stippleName << '\"'     // curve line stipple
1750         << ends;
1751     if( Tcl_EvalObj( interp, Tcl_NewStringObj( cmdstr.str(), -1 ) ) != TCL_OK )
1752     {
1753         // release the curve
1754         Remove( curve );
1755
1756         // release hold on curve
1757         curves[cid] = NULL;
1758
1759         // indicate failure to add
1760         cid = -1;
1761     }
1762     cmdstr.rdbuf()->freeze( 0 );
1763
1764     // update our subwindow geometry to make space for the new curve
1765     UpdateGeometry();
1766
1767     return cid;
1768 }
1769
1770
1771
1772
1773 // SetCurveData - associate data with the indicated curve
1774 //
1775 void
1776 PDGraph::SetCurveData( CurveID cid,
1777                        int firstSample,
1778                        int nSamples,
1779                        const float* data )
1780 {
1781     assert( cid != -1 );
1782     assert( ((unsigned)cid) < curves.size() );
1783
1784     // access the indicated curve
1785     PDGraph::Curve* curve = curves[cid];
1786     assert( curve != NULL );
1787
1788     // check for overflow
1789     if( firstSample + nSamples > (int)histInfo.nBuckets )
1790     {
1791         // TODO - how to indicate error of exceeding specified max sample ?
1792         return;
1793     }
1794
1795     // update curve's data with the new data
1796     curve->SetData( firstSample, nSamples, data );
1797
1798     // handle drawing of new curve points
1799     if( curve->isVisible )
1800     {
1801         Group* group = curve->group;
1802
1803         // draw new points for the curve...
1804
1805         // ...check if we found a new maximum value so we can rescale...
1806         if( curve->GetMaxActiveValue() > group->axis->maxValue )
1807         {
1808             UpdateAxisMaxValue( group->axis, curve->GetMaxActiveValue() );
1809         }
1810         else
1811         {
1812             dataw->HandleNewData( curve, firstSample, nSamples );
1813         }
1814     }
1815 }
1816
1817
1818
1819 // UpdateAxisMaxValue - updates the maximum value of an axis, and 
1820 // updates the display to reflect the new max value
1821 // 
1822 void
1823 PDGraph::UpdateAxisMaxValue( ValueAxis* axis, double maxValue )
1824 {
1825     // we found a new maximum Y value
1826     // update the entire group's view of the new max value and 
1827     // recompute the characteristics of the axis
1828     axis->maxValue = maxValue;
1829
1830     // determine the maxmimum number of ticks we
1831     // can support in the given axis height
1832     //
1833     // Note that we go to great lengths to be sure
1834     // that we do our "max ticks" calculation as
1835     // signed integer arithmetic, so we can recognize
1836     // the case when the window is too short.
1837     int valAxisHeight = (Tk_IsMapped( valAxis->GetWindow() ) ? 
1838                             Tk_Height( valAxis->GetWindow() ) : 
1839                             Tk_ReqHeight( valAxis->GetWindow() ));
1840     int nTicksMax = (valAxisHeight - 
1841             ((int)timeAxis->DetermineHeight()) - 
1842             ((int)valAxis->DetermineLabelHeight())) / fontm.linespace;
1843     if( nTicksMax <= 0 )
1844     {
1845         // this could happen if we've not yet mapped the
1846         // value axis window, or if the window has
1847         // become too short
1848         //
1849         // we still want to compute an interval layout,
1850         // so we force the max number of ticks to a 
1851         // "reasonable" value
1852         nTicksMax = 5;
1853     }
1854     axis->ComputeIntervals( nTicksMax );
1855
1856     // allow subwindows to recompute its visual information
1857     // based on new configuration
1858     dataw->UpdateConfiguration();
1859
1860     // update the display
1861     RequestRedraw();
1862     valAxis->RequestRedraw();
1863 }
1864
1865 // UpdateGeometry - update the geometry of the subwidgets based on
1866 // the information held in the C++ objects
1867 //
1868 void
1869 PDGraph::UpdateGeometry( void )
1870 {
1871     // update value axes width
1872     // (width required is based on number of groups with visible curves)
1873     unsigned int widthRequired = 2;
1874     unsigned int gid;
1875     for( gid = 0; gid < groups.size(); gid++ )
1876     {
1877         if( groups[gid]->IsVisible() )
1878         {
1879             widthRequired += valAxis->DetermineWidth( groups[gid]->axis );
1880         }
1881     }
1882     Tk_GeometryRequest( valAxis->GetWindow(),
1883                         widthRequired,
1884                         Tk_Height( valAxis->GetWindow() ) );
1885 }
1886
1887
1888 // FindGroup - looks up the Group with the given metric label as its title
1889 // if no group is so titled, returns NULL
1890 //
1891 PDGraph::Group*
1892 PDGraph::FindGroup( const char* metricLabel ) const
1893 {
1894     // find the group (if any) that represents this metric
1895     // a match is based on the metric label
1896     PDGraph::Group* group = NULL;
1897     unsigned int gid;
1898     for( gid = 0; gid < groups.size(); gid++ )
1899     {
1900         assert( groups[gid] != NULL );
1901         if( groups[gid]->axis->title == metricLabel )
1902         {
1903             // we've found a match on the metric - 
1904             // the new curve belongs in this group
1905             group = groups[gid];
1906             break;
1907         }
1908     }
1909
1910     return group;
1911 }
1912
1913
1914
1915
1916
1917 // Remove - Removes the given group from the set of groups known to
1918 // the graph
1919 //
1920 void
1921 PDGraph::Remove( PDGraph::Group* group )
1922 {
1923     unsigned int gid;
1924
1925     // find the group
1926     for( gid = 0; gid < groups.size(); gid++ )
1927     {
1928         assert( groups[gid] != NULL );
1929         if( groups[gid] == group )
1930         {
1931             // shift other group entries over removed entry
1932             while( gid < (groups.size() - 1))
1933             {
1934                 groups[gid] = groups[gid+1];
1935                 gid++;
1936             }
1937             groups.resize( groups.size() - 1 );
1938
1939             // we're done
1940             break;
1941         }
1942     }
1943 }
1944
1945
1946 // Remove - Removes the given curve from the set of curves known to the
1947 // graph.  If the removal of the curve results in a group becoming empty,
1948 // also handles removal of the associated group.
1949 //
1950 void
1951 PDGraph::Remove( PDGraph::Curve* curve )
1952 {
1953     // release the curve object
1954     curve->group->Remove( curve );
1955     if( curve->group->curves.size() == 0 )
1956     {
1957         Remove( curve->group );
1958         delete curve->group;
1959     }
1960     delete curve;
1961 }
1962
1963
1964 // Looks up client data associated with the given Tk window.
1965 // See comments by the declaration of the winInstDataMap member.
1966 bool
1967 PDGraph::FindInstanceData( char* name, Tcl_Interp* interp, ClientData& cd )
1968 {
1969     Tk_Window tkwin = Tk_NameToWindow( interp, name, Tk_MainWindow( interp ) );
1970     bool found = false;
1971
1972
1973     if( tkwin != NULL )
1974     {
1975         found = winInstDataMap.find( tkwin, cd );
1976     }
1977     
1978     return found;
1979 }
1980
1981
1982
1983 //---------------------------------------------------------------------------
1984 // class callback functions
1985 //---------------------------------------------------------------------------
1986
1987 int
1988 PDGraph::InstanceCmdCB( ClientData cd, Tcl_Interp* interp, int argc, char* argv[] )
1989 {
1990     PDGraph* pGraph = (PDGraph*)cd;
1991     return pGraph->HandleCmd( interp, argc, argv );
1992 }
1993
1994
1995 void
1996 PDGraph::DrawCB(ClientData clientData)
1997 {
1998     PDGraph* pGraph = (PDGraph*)clientData;
1999     pGraph->Draw();
2000 }
2001
2002
2003
2004 void
2005 PDGraph::EventCB(ClientData cd, XEvent* eventPtr)
2006 {
2007     PDGraph* pGraph = (PDGraph*)cd;
2008     pGraph->HandleEvent( eventPtr );
2009 }
2010
2011
2012
2013
2014 void
2015 PDGraph::DestroyCB(char* cd)
2016 {
2017     PDGraph* pGraph = (PDGraph*) cd;
2018     delete pGraph;
2019 }
2020
2021
2022
2023 void
2024 PDGraph::InstanceCmdDeletedCB( ClientData cd )
2025 {
2026     PDGraph* pGraph = (PDGraph*)cd;
2027     pGraph->HandleInstanceCmdDeleted();
2028 }
2029
2030
2031
2032 //---------------------------------------------------------------------------
2033 // PDGraph::Group implementation
2034 //---------------------------------------------------------------------------
2035
2036 PDGraph::Group::Group( const char* title )
2037   : axis( new PDGraph::ValueAxis( title, 0, 0.0001, 10 ) ),
2038     nVisible( 0 )
2039 {
2040 }
2041
2042
2043
2044 PDGraph::Group::~Group( void )
2045 {
2046 }
2047
2048
2049 // Add - add a curve to the group
2050 //
2051 void
2052 PDGraph::Group::Add( PDGraph::Curve* curve )
2053 {
2054     // keep hold of this curve
2055     curves += curve;
2056     curve->group = this;
2057
2058     if( curve->isVisible )
2059     {
2060         nVisible++;
2061     }
2062 }
2063
2064
2065 // Remove - remove a curve from the group
2066 //
2067 void
2068 PDGraph::Group::Remove( PDGraph::Curve* curve )
2069 {
2070     unsigned int idx;
2071
2072
2073     // find the curve
2074     for( idx = 0; idx < curves.size(); idx++ )
2075     {
2076         if( curves[idx] == curve )
2077         {
2078             // we found our curve -
2079             // remove by shifting items over empty space
2080             while( idx < (curves.size() - 1) )
2081             {
2082                 curves[idx] = curves[idx+1];
2083                 
2084                 idx++;
2085             }
2086
2087             // now shrink the vector
2088             curves.resize( curves.size() - 1 );
2089             break;
2090         }
2091     }
2092 }
2093
2094
2095 //---------------------------------------------------------------------------
2096 // PDGraph::Curve implementation
2097 //---------------------------------------------------------------------------
2098 const unsigned int PDGraph::Curve::smoothingWindowSize  = 8;
2099
2100 vector<PDGraph::Curve::LineSpec> PDGraph::Curve::lineColorSpecs;
2101 vector<PDGraph::Curve::LineSpec> PDGraph::Curve::linePatternSpecs;
2102 unsigned int PDGraph::Curve::nextLineSpecIdx = 0;
2103
2104
2105
2106 PDGraph::Curve::Curve( const char* metricName,
2107                        const char* resourceName,
2108                        unsigned int maxPoints )
2109   : isSmoothed( true ),
2110     nPoints( 0 ),
2111     pts( NULL ),
2112     spts( NULL ),
2113     group( NULL ),
2114     maxActiveValue( DBL_MIN ),
2115     xpts( NULL ),
2116     lineSpecIdx( nextLineSpecIdx++ ),
2117     useColor( true ),
2118     isVisible( true )
2119 {
2120     // construct name of curve
2121     name = metricName;
2122     name += "<";
2123     name += resourceName;
2124     name += ">";
2125
2126     // obtain space for our data
2127     pts = new double[maxPoints];
2128     for(unsigned i=0; i<maxPoints; i++) {
2129       pts[i] = make_Nan();
2130     }
2131     spts = new double[maxPoints];
2132     for(unsigned j=0; j<maxPoints; j++) {
2133       spts[j] = make_Nan();
2134     }
2135     xpts = new XPoint[maxPoints];
2136 }
2137
2138
2139 PDGraph::Curve::~Curve( void )
2140 {
2141     delete[] pts;
2142     delete[] spts;
2143     delete[] xpts;
2144 }
2145
2146
2147 // ComputeXPoints - computes the coordinates to be used to represent
2148 // the given subset of our currently active data within the given
2149 // onscreen rectangle.  Also takes into account the
2150 // current start and focus values to adjust to the current zoom 
2151 // and pan settings of the graph.
2152 //
2153 void
2154 PDGraph::Curve::ComputeXPoints( unsigned int startIdx,
2155                                 unsigned int nxPoints,
2156                                 XRectangle& rect,
2157                                 double start,
2158                                 double focus,
2159                                 unsigned int nBuckets )
2160 {
2161     double axisMaxValue = group->axis->nIntervals * group->axis->intervalWidth;
2162     double* data = GetActiveData();
2163     short ybase = rect.y + rect.height;
2164     unsigned int i;
2165
2166
2167     for( i = 0; i < nxPoints; i++ )
2168     {
2169         int idx = startIdx + i;
2170
2171         xpts[idx].x = (short)(rect.x + ((idx - start * nBuckets) / 
2172                                             (focus * nBuckets)) * rect.width);
2173         if( !isnan(data[idx]) )
2174         {
2175             // compute onscreen location for datapoint
2176             if( axisMaxValue != 0 )
2177             {
2178                 xpts[idx].y = (short)(ybase - 
2179                                 (data[idx] / axisMaxValue) * rect.height);
2180             }
2181             else
2182             {
2183                 // y axis max is zero - to avoid division by zero,
2184                 // we peg the value to zero
2185                 xpts[idx].y = ybase;
2186             }
2187         }
2188         else
2189         {
2190             // peg undefined values to zero
2191             xpts[idx].y = ybase;
2192         }
2193     }
2194 }
2195
2196
2197 // SetData - associates the given data with our curve,
2198 // starting at the indicated data point index
2199 //
2200 void
2201 PDGraph::Curve::SetData( unsigned int startIdx,
2202                          unsigned int nNewPoints,
2203                          const float* data )
2204 {
2205     // update our data with the new data
2206     unsigned int i;
2207     for( i = 0; i < nNewPoints; i++ )
2208     {
2209         pts[startIdx + i] = data[i];
2210     }
2211
2212     // update notion of number of points owned by the curve
2213     if( startIdx + nNewPoints > nPoints )
2214     {
2215         nPoints = startIdx + nNewPoints;
2216     }
2217
2218     // smooth data if desired
2219     if( isSmoothed )
2220     {
2221         ComputeSmoothedData( 0, nPoints, smoothingWindowSize );
2222     }
2223
2224     // check for a new maximum value in the active data
2225     UpdateMaxActiveValue( startIdx );
2226 }
2227
2228
2229
2230 // ComputeSmoothedData - using the given smoothing window size,
2231 // computes the smoothed data points for the current set of raw data
2232 // within the given data point interval
2233 //
2234 void
2235 PDGraph::Curve::ComputeSmoothedData( unsigned int firstSample,
2236                                         unsigned int lastSample,
2237                                         unsigned int winSize )
2238 {
2239     unsigned int i;
2240     unsigned int j;
2241     unsigned int k;
2242
2243     // handle startup case where we don't have
2244     // winSize samples before first smoothed value
2245     if (firstSample <= winSize)
2246     {
2247         // search forward for next valid data
2248         while( (firstSample <= winSize) && (isnan(pts[firstSample])))
2249         {
2250             spts[firstSample] = pts[firstSample];
2251             firstSample++;
2252         }
2253
2254         i = firstSample;
2255         while((i <= winSize) && (!isnan(pts[i])))
2256         {
2257             double sum = 0;
2258
2259             for(j = firstSample; j <= i; j++)
2260             {
2261                 sum += pts[j];
2262             }
2263             spts[i] = sum / (i+1);
2264             i++;
2265         }
2266         firstSample = i;
2267         k = i;
2268     }
2269     else
2270     {
2271         k = firstSample;
2272     }
2273
2274     while(k <= lastSample)
2275     {
2276         double sum = 0;
2277
2278         // search forward for next valid data
2279         j = k - winSize;
2280
2281         while( (j <= k) && (isnan(pts[j])))
2282         {
2283             spts[j] = pts[j];
2284             j++;
2285         }
2286
2287         // compute average value over window
2288         int nptsInAvg = 0;
2289         while( (j <= k) && (!isnan(pts[j])))
2290         {
2291             sum += pts[j];
2292             j++;
2293             nptsInAvg++;
2294         }
2295         if(nptsInAvg > 0) 
2296           spts[k] = sum / nptsInAvg;
2297         k++;
2298     }
2299 }
2300
2301
2302 // Draw - Draw a representation of the indicated subset of current data
2303 // to the given Display and Drawable.
2304 //
2305 void
2306 PDGraph::Curve::Draw( Display* disp,
2307                       Drawable d,
2308                       unsigned int startIdx,
2309                       unsigned int nPointsToDraw ) const
2310 {
2311     assert( (startIdx + nPointsToDraw - 1) < nPoints );
2312
2313     unsigned int idx = startIdx;
2314     while( idx < startIdx + nPointsToDraw )
2315     {
2316         // find next non-NaN point to draw
2317         while( (idx < startIdx + nPointsToDraw) && isnan( pts[idx] ) )
2318         {
2319             idx++;
2320         }
2321         if( idx == startIdx + nPointsToDraw )
2322         {
2323             // we're done
2324             break;
2325         }
2326         assert( !isnan(pts[idx]) );
2327
2328         // start segment correctly with endpoint or
2329         // connected to previous data
2330         if( (idx == 0) || isnan( pts[idx - 1] ) )
2331         {
2332             DrawEndpoint( disp, d, xpts[idx] );
2333         }
2334         else if( idx != 0 )
2335         {
2336             // connect line to previous point
2337             XDrawLine( disp, d, GetLineSpec().gc,
2338                         xpts[idx-1].x, xpts[idx-1].y,
2339                         xpts[idx].x, xpts[idx].y );
2340         }
2341         idx++;
2342
2343         while( (idx < startIdx + nPointsToDraw) && !isnan( pts[idx] ) )
2344         {
2345             // draw line to connect to previous point
2346             XDrawLine( disp, d, GetLineSpec().gc,
2347                         xpts[idx-1].x, xpts[idx-1].y,
2348                         xpts[idx].x, xpts[idx].y );
2349
2350             idx++;
2351         }
2352
2353         // draw an endpoint at the current point if needed
2354         if( idx < (startIdx + nPointsToDraw - 1) )
2355         {
2356             assert( isnan( pts[idx] ) );
2357             assert( !isnan( pts[idx-1] ));
2358
2359             DrawEndpoint( disp, d, xpts[idx-1] );
2360         }
2361     }
2362 }
2363
2364
2365
2366 // InitLineSpecs - try to obtain a set of line specifications from
2367 // the Tk option data
2368 //
2369 int
2370 PDGraph::Curve::InitLineSpecs( Tcl_Interp* interp,
2371                                Tk_Window win,
2372                                char* lineColors,
2373                                char* linePatterns,
2374                                Tk_3DBorder fgBorder )
2375 {
2376     unsigned int nColors = 0;
2377     int ret = TCL_OK;
2378
2379
2380     // we want an easy way to parse the line specification
2381     // since we don't have a simple string tokenizer available,
2382     // we convert the string into a Tcl list object, and extract
2383     // the list items.  Unfortunately, we can't build a Tcl list
2384     // directly from the C string, so we take an intermediate step
2385     // through a Tcl string object.
2386     assert( lineColors != NULL );
2387     Tcl_Obj* lcObj = Tcl_NewStringObj( lineColors, -1 );
2388     if( lcObj != NULL )
2389     {
2390         int objc;
2391         Tcl_Obj** objv;
2392
2393         ret = Tcl_ListObjGetElements( interp, lcObj, &objc, &objv );
2394         if( ret == TCL_OK )
2395         {
2396             if( objc > 0 )
2397             {
2398                 int i;
2399             
2400                 for( i = 0; i < objc; i++ )
2401                 {
2402                     char* colorName = Tcl_GetStringFromObj( objv[i], NULL );
2403                     if( colorName != NULL )
2404                     {
2405                         // attempt to obtain a GC for the given color
2406                         XColor* color = Tk_GetColor( interp, win, colorName );
2407                         if( color != NULL )
2408                         {
2409                             XGCValues gcv;
2410
2411                             gcv.foreground = color->pixel;
2412                             lineColorSpecs +=
2413                                 LineSpec( Tk_GetGC( win, GCForeground, &gcv ),
2414                                                     color,
2415                                                     "solid" );
2416
2417                             nColors++;
2418                         }
2419                     }
2420                 }
2421             }
2422             else
2423             {
2424                 // no line colors were specified
2425                 Tcl_SetObjResult( interp, 
2426                         Tcl_NewStringObj( "no line colors specified", -1 ) );
2427                 ret = TCL_ERROR;
2428             }
2429         }
2430     }
2431
2432     if( (ret == TCL_OK) && (nColors == 0) )
2433     {
2434         // we weren't able to obtain any color line specifications
2435         Tcl_SetObjResult( interp, Tcl_NewStringObj( "unable to parse line color specification", -1 ) );
2436         ret = TCL_ERROR;
2437     }
2438
2439     if( ret != TCL_OK )
2440     {
2441         return ret;
2442     }
2443
2444 #if READY
2445     // note - the black-and-white code is currently disabled
2446     // the problem is in the drawing of the lines within the
2447     // legend widget with the indicated stipple.  Apparently
2448     // there is a bug in the canvas widget in Tk 8.0.5 (and
2449     // anecdotal evidence says that it hasn't been fixed in
2450     // Tk 8.1) that keeps from correctly handling the stipple
2451     // option.
2452     //
2453     // A potential workaround is to use window canvas items
2454     // instead of line items, so as to have complete control
2455     // over the drawing of the line.
2456     // 
2457     unsigned int nPatterns = 0;
2458
2459     // now handle black and white (pattern) specs
2460     assert( linePatterns != NULL );
2461     Tcl_Obj* lpObj = Tcl_NewStringObj( linePatterns, -1 );
2462     if( lpObj != NULL )
2463     {
2464         int objc;
2465         Tcl_Obj** objv;
2466
2467         ret = Tcl_ListObjGetElements( interp, lpObj, &objc, &objv );
2468         if( ret == TCL_OK )
2469         {
2470             if( objc > 0 )
2471             {
2472                 int i;
2473             
2474                 for( i = 0; i < objc; i++ )
2475                 {
2476                     char* pattName = Tcl_GetStringFromObj( objv[i], NULL );
2477                     if( pattName != NULL )
2478                     {
2479                         // get the foreground color of the given window
2480                         XColor* color = Tk_3DBorderColor(fgBorder);
2481
2482
2483                         // obtain the pixmap for the given stipple
2484                         bool useStipple = !strcmp( pattName, "solid" );
2485                         Pixmap stipple = NULL;
2486
2487                         if( useStipple )
2488                         {
2489                             stipple = Tk_GetBitmap( interp, win, Tk_GetUid( pattName ) );
2490                         }
2491
2492                         // attempt to obtain a GC for the given color and pattern
2493                         if( (color != NULL) && ((stipple != NULL) || !useStipple) )
2494                         {
2495                             XGCValues gcv;
2496                             unsigned long valMask = GCForeground;
2497
2498                             gcv.foreground = color->pixel;
2499                             if( useStipple )
2500                             {
2501                                 gcv.stipple = stipple;
2502                                 valMask |= GCStipple;
2503                             }
2504                             
2505                             linePatternSpecs +=
2506                                 LineSpec( Tk_GetGC( win, valMask, &gcv ),
2507                                                     color,
2508                                                     pattName );
2509
2510                             nPatterns++;
2511                         }
2512
2513                         if( stipple != NULL )
2514                         {
2515                             Tk_FreeBitmap( Tk_Display( win ), stipple );
2516                         }
2517                     }
2518                 }
2519             }
2520             else
2521             {
2522                 // no line colors were specified
2523                 Tcl_SetObjResult( interp, 
2524                         Tcl_NewStringObj( "no line patterns specified", -1 ) );
2525                 ret = TCL_ERROR;
2526             }
2527         }
2528     }
2529
2530     if( (ret == TCL_OK) && (nPatterns == 0) )
2531     {
2532         // we weren't able to obtain any line pattern specifications
2533         Tcl_SetObjResult( interp,
2534             Tcl_NewStringObj( "unable to parse line pattern specification", -1 ) );
2535         ret = TCL_ERROR;
2536     }
2537 #endif // READY
2538
2539     return ret;
2540 }
2541
2542
2543
2544 // ReleaseLineSpecs - release line specification resources
2545 //
2546 void
2547 PDGraph::Curve::ReleaseLineSpecs( Display* disp )
2548 {
2549     unsigned int i;
2550
2551     for( i = 0; i < lineColorSpecs.size(); i++ )
2552     {
2553         Tk_FreeGC( disp, lineColorSpecs[i].gc );
2554         Tk_FreeColor( lineColorSpecs[i].color );
2555     }
2556     lineColorSpecs.resize( 0 );
2557
2558     for( i = 0; i < linePatternSpecs.size(); i++ )
2559     {
2560         Tk_FreeGC( disp, linePatternSpecs[i].gc );
2561         Tk_FreeColor( linePatternSpecs[i].color );
2562     }
2563     linePatternSpecs.resize( 0 );
2564 }
2565
2566
2567 // Smooth - Indicates that we should use smoothed data as our
2568 // active data.  If we are not already smoothed, computes smoothed data.
2569 //
2570 void
2571 PDGraph::Curve::Smooth( void )
2572 {
2573     if( !isSmoothed )
2574     {
2575         isSmoothed = true;
2576
2577         // ensure we have up-to-date smoothed data
2578         ComputeSmoothedData( 0, nPoints, smoothingWindowSize );
2579
2580         // reset the max value of the active data
2581         maxActiveValue = DBL_MIN;
2582         UpdateMaxActiveValue( 0 );
2583     }
2584 }
2585
2586
2587
2588
2589 // Unsmooth - Indicates that we should use raw data as our active data.
2590 //
2591 void
2592 PDGraph::Curve::Unsmooth( void )
2593 {
2594     if( isSmoothed )
2595     {
2596         isSmoothed = false;
2597
2598         // reset the max value of the active data
2599         maxActiveValue = DBL_MIN;
2600         UpdateMaxActiveValue( 0 );
2601     }
2602 }
2603
2604
2605
2606 // UpdateMaxActiveValue - determines the max value from the active
2607 // data set, considering all data values with index i or larger
2608 //
2609 void
2610 PDGraph::Curve::UpdateMaxActiveValue( unsigned int startIdx )
2611 {
2612     unsigned int i;
2613
2614
2615     double* pts = GetActiveData();
2616     for( i = startIdx; i < nPoints; i++ )
2617     {
2618         if( pts[i] > maxActiveValue )
2619         {
2620             maxActiveValue = pts[i];
2621         }
2622     }
2623 }
2624
2625
2626
2627
2628
2629 //---------------------------------------------------------------------------
2630 // PDGraph::ValueAxis impelementation
2631 //---------------------------------------------------------------------------
2632
2633 // ComputeIntervals - given the maximum number of ticks
2634 // allowed, computes a visually appealing number of intervals
2635 // for the Axis
2636 //
2637 void
2638 PDGraph::ValueAxis::ComputeIntervals( unsigned int nTicksMax )
2639 {
2640     // exact number of intervals needed for (nIntervalsExact * vInterval == maxValue)
2641     register double nIntervalsExact;
2642     register unsigned int nDigitsBefore;    // digits before the decimal point
2643     register unsigned int nDigitsAfter;     // digits after the decimal point
2644     unsigned int precision;
2645
2646
2647     if(maxValue <= 0.00000001)
2648     {
2649         nIntervalsExact = 0.99;
2650         intervalWidth = 1.0;
2651     }
2652     else if(maxValue < 0.0)
2653     {
2654         nIntervalsExact = -maxValue;
2655         intervalWidth = -1.0;
2656     }
2657     else
2658     {
2659         nIntervalsExact = maxValue;
2660         intervalWidth = 1.0;
2661     }
2662     nDigitsAfter = 0;
2663     if (nIntervalsExact < 1.0)
2664     {
2665         nDigitsBefore = 1;
2666         while (nIntervalsExact < 0.1)
2667         {
2668             nIntervalsExact *= 10.0;
2669             intervalWidth /= 10.0;
2670             nDigitsAfter++;
2671         }
2672     }
2673     else
2674     {
2675         nDigitsBefore = 0;
2676         while (nIntervalsExact >= 1.0)
2677         {
2678             nIntervalsExact /= 10.0;
2679             intervalWidth *= 10.0;
2680             nDigitsAfter--;
2681             nDigitsBefore++;
2682         }
2683     }
2684     precision = 0;
2685     while (1 + (unsigned int)(3.0 * nIntervalsExact) < nTicksMax)
2686     {
2687         if (1 + (unsigned int)(6.0 * nIntervalsExact) < nTicksMax)
2688             if (1 + (unsigned int)(15.0 * nIntervalsExact) < nTicksMax)
2689             {
2690                 intervalWidth /= 10.0;
2691                 nIntervalsExact *= 10.0;
2692             }
2693             else
2694             {
2695                 intervalWidth /= 5.0;
2696                 nIntervalsExact *= 5.0;
2697             }
2698         else
2699         {
2700             intervalWidth /= 2.0;
2701             nIntervalsExact *= 2.0;
2702         }
2703         precision++;
2704         nDigitsAfter++;
2705     }
2706     nIntervals = 1 + (unsigned int)nIntervalsExact;
2707     if(maxValue == (nIntervals - 1) * intervalWidth)
2708     {
2709         nIntervals--;
2710     }
2711
2712     // determine correct format to use for labels
2713     if( (nDigitsBefore + (nDigitsAfter > 0 ? nDigitsAfter + 1 : 0)) > precision + 5)
2714     {
2715         sprintf(labelFormat, "%%%d.%de", 0, precision - 1);
2716     }
2717     else if(nDigitsAfter > 0)
2718     {
2719         sprintf(labelFormat, "%%%d.%df", 0, nDigitsAfter);
2720     }
2721     else
2722     {
2723         sprintf(labelFormat, "%%%d.0f", 0);
2724     }
2725 }
2726
2727 //---------------------------------------------------------------------------
2728 // explicit template instantiations needed by this class
2729 //---------------------------------------------------------------------------
2730 #include "paradyn/src/UIthread/minmax.C"
2731 #include "common/src/Dictionary.C"
2732
2733 template class dictionary_hash<Tk_Window, ClientData>;
2734 template class vector<dictionary_hash<Tk_Window, ClientData>::entry>;
2735 template class vector<Tk_Window>;
2736
2737 template class vector<PDGraph::Curve*>;
2738 template class vector<PDGraph::Curve::LineSpec>;
2739 template class vector<PDGraph::Group*>;
2740
2741 template class vector<unsigned int>;
2742 template class vector<void*>;
2743