GRAFDATA.C

/***************************************************************************** 
*
* Grafdata.c - This module handles the non-drawing functions of the graph,
* such as allocating linked structures and their memory, freeing it,
* unlinking, starting and stopping the timer,
* setting up the first graph (CPU), and all the numeric functions for
* the different counter types.
*
* Microsoft Confidential
* Copyright 1992 - 1998 Microsoft Corporation
*
*
****************************************************************************/


//==========================================================================//
// Includes //
//==========================================================================//

#include <stdio.h> // for sprintf

#include "perfmon.h" // main perfmon declarations
#include "grafdata.h" // external declarations for this file
#include <float.h> // for FLT_MAX constant

#include "addline.h" // for AddLine, EditLine
#include "counters.h" // for CounterEntry
#include "graph.h" // for SizeGraphComponents
#include "pmemory.h" // for MemoryXXX (mallloc-type) routines
#include "perfdata.h" // for UpdateLines
#include "playback.h" // for PlayingBackLog, PlaybackLines
#include "legend.h"
#include "system.h" // for SystemGet
#include "utils.h"
#include "line.h" // for LineFree
#include "valuebar.h" // for StatusTimer

#include "fileopen.h" // for FileGetName
#include "fileutil.h" // for FileRead...
#include "menuids.h" // for IDM_VIEWCHART
#include "perfmops.h" // for ExportFileHeader
#include "status.h" // for StatusLineReady

extern BOOL SaveFileHandler(HWND hWnd,DWORD type) ;

// this macro is used in doing a simple DDA (Digital Differential Analyzer)
// * 10 + 5 is to make the result round up with .5
#define DDA_DISTRIBUTE(TotalTics, numOfData) \
((TotalTics * 10 / numOfData) + 5) / 10

#define szSmallValueFormat TEXT("%10.3f")
#define szLargeValueFormat TEXT("%10.0f")

//==========================================================================//
// Local Data //
//==========================================================================//




//==========================================================================//
// Local Functions //
//==========================================================================//


/****************************************************************************
* eUpdateMinMaxAve -
****************************************************************************/
void eUpdateMinMaxAve (FLOAT eValue, PLINESTRUCT pLineStruct, INT iValidValues,
INT iTotalValidPoints, INT iDataPoint, INT gMaxPoints)
{
INT i ;
INT iDataNum = iTotalValidPoints ;
FLOAT eMin,
eMax,
eSum,
eNewValue ;


eMax = eMin = eValue ;

if (PlayingBackLog())
{
eSum = (FLOAT) 0.0 ;
}
else
{
eSum = eValue ;
}

if (iValidValues == iTotalValidPoints)
{
for (i=0 ; i < iValidValues ; i++)
{
if (i == iDataPoint)
{
// skip the data point we are going to replace
continue ;
}

eNewValue = pLineStruct->lnValues[i] ;

eSum += eNewValue ;

if (eNewValue > eMax)
{
eMax = eNewValue ;
}

if (eNewValue < eMin)
{
eMin = eNewValue ;
}
}
}
else
{
// special case when we start the new line in the middle of the chart
for (i = iDataPoint, iTotalValidPoints-- ;
iTotalValidPoints > 0 ;
iTotalValidPoints-- )
{
i-- ;
if (i < 0)
{
// for the wrap-around case..
i = gMaxPoints - 1 ;
}

if (i == iDataPoint)
{
// skip the data point we are going to replace
continue ;
}
eNewValue = pLineStruct->lnValues[i] ;

eSum += eNewValue ;

if (eNewValue > eMax)
{
eMax = eNewValue ;
}

if (eNewValue < eMin)
{
eMin = eNewValue ;
}
}
}

pLineStruct->lnMinValue = eMin ;
pLineStruct->lnMaxValue = eMax ;

if (iDataNum)
{
pLineStruct->lnAveValue = eSum / (FLOAT) iDataNum ;
}
else
{
pLineStruct->lnAveValue = (FLOAT) 0.0 ;
}
}


// UpdateValueBarData is used to update the value bar data
// It is called when the user switches to a diff. line from
// the legned window.
VOID UpdateValueBarData (PGRAPHSTRUCT pGraph)
{
PLINESTRUCT pCurrentGraphLine ;
INT KnownValue,
MaxValues,
iValidValues,
iDataPoint ;
FLOAT eValue ;
pCurrentGraphLine = CurrentGraphLine (hWndGraph) ;

if (!pCurrentGraphLine || pCurrentGraphLine->bFirstTime)
{
// we have not collect enough samples
return ;
}

KnownValue = pGraph->gKnownValue ;
MaxValues = pGraph->gMaxValues ;

// The valid values is the number of valid entries in the
// data buffer. After we wrap the buffer, all the values are
// valid.
iValidValues = pGraph->gTimeLine.iValidValues ;

// Get the index to the data point we are updating.

iDataPoint = KnownValue % MaxValues ;
eValue = pCurrentGraphLine->lnValues[iDataPoint] ;

// get the statistical data for this line
eUpdateMinMaxAve(eValue, pCurrentGraphLine,
iValidValues, pCurrentGraphLine->lnValidValues,
iDataPoint, MaxValues) ;
} // UpdateValueBarData


VOID UpdateLGData (PGRAPHSTRUCT pGraph)
{
PLINESTRUCT pLine ;
PLINESTRUCT pCurrentGraphLine ;
INT KnownValue,
MaxValues,
iValidValues,
iDataPoint ;
FLOAT eValue ;
// Known Value is the where data is placed in the buffer.
pGraph->gKnownValue++;

KnownValue = pGraph->gKnownValue ;

// Update the high water mark for valid data in the lnValues
// (aka DataPoint) buffer.


MaxValues = pGraph->gMaxValues ;

// The valid values is the number of valid entries in the
// data buffer. After we wrap the buffer, all the values are
// valid.
iValidValues = pGraph->gTimeLine.iValidValues ;

if (iValidValues < MaxValues)
iValidValues = (KnownValue % MaxValues) + 1 ;

pGraph->gTimeLine.iValidValues = iValidValues ;

// Get the index to the data point we are updating.

iDataPoint = KnownValue % MaxValues ;

// loop through lines,
// If one of the lines is highlighted then do the calculations
// for average, min, & max on that line.

pCurrentGraphLine = CurrentGraphLine (hWndGraph) ;

for (pLine=pGraph->pLineFirst; pLine; pLine=pLine->pLineNext)
{ // for

if (pLine->bFirstTime)
{
// skip until we have collect enough samples to plot the first data
continue ;
}

if (pLine->lnValidValues < MaxValues)
{
(pLine->lnValidValues)++ ;
}

// Get the new value for this line.
eValue = CounterEntry (pLine) ;

if (pLine == pCurrentGraphLine)
{ // if
// get the statistical data for this line
eUpdateMinMaxAve (eValue, pLine,
iValidValues, pLine->lnValidValues,
iDataPoint, MaxValues) ;
} // if

// Now put the new value into the data array
pLine->lnValues[iDataPoint] = eValue ;
}

GetLocalTime (&(pGraph->pDataTime[iDataPoint])) ;
} // UpdateLGData



BOOL HandleGraphTimer (void)
{
PGRAPHSTRUCT pGraph;

//NOTE: get a strategy for these "no-go" states
if (!(pGraph = pGraphs) || !pGraphs->pSystemFirst)
return(FALSE);


if (!UpdateLines(&(pGraphs->pSystemFirst), pGraphs->pLineFirst))
return (TRUE) ;

UpdateLGData(pGraph);

return(TRUE);
}


VOID ClearGraphTimer(PGRAPHSTRUCT pGraph)
{
KillTimer(pGraph->hWnd, GRAPH_TIMER_ID);
}


VOID SetGraphTimer(PGRAPHSTRUCT pGraph)
{
SetTimer(pGraph->hWnd, GRAPH_TIMER_ID, pGraph->gInterval, NULL) ;
}

VOID ResetGraphTimer(PGRAPHSTRUCT pGraph)
{
KillTimer(pGraph->hWnd, GRAPH_TIMER_ID);
SetGraphTimer(pGraph);
}


VOID GetGraphConfig(PGRAPHSTRUCT pGraph)
{

LoadRefreshSettings(pGraph);
LoadLineGraphSettings(pGraph);


// Init the structure
pGraph->pLineFirst = NULL;

//NOTE: put the rest of this in Config

pGraph->gOptions.bLegendChecked = TRUE;
pGraph->gOptions.bMenuChecked = TRUE;
pGraph->gOptions.bLabelsChecked = TRUE;
pGraph->gOptions.bVertGridChecked = FALSE;
pGraph->gOptions.bHorzGridChecked = FALSE;
pGraph->gOptions.bStatusBarChecked = TRUE;
pGraph->gOptions.GraphVGrid = TRUE;
pGraph->gOptions.GraphHGrid = TRUE;
pGraph->gOptions.HistVGrid = TRUE;
pGraph->gOptions.HistHGrid = TRUE;

pGraph->gOptions.iGraphOrHistogram = LINE_GRAPH; // vs. BAR_GRAPH
pGraph->gOptions.iVertMax = DEF_GRAPH_VMAX;

return;
}


BOOL InsertGraph (HWND hWnd)
{
PGRAPHSTRUCT pGraph;

pGraph = MemoryAllocate (sizeof (GRAPHSTRUCT)) ;
if (!pGraph)
return (FALSE) ;


pGraphs = pGraph;


GetGraphConfig(pGraph);
pGraph->bManualRefresh = FALSE ;

pGraph->gMaxValues = DEFAULT_MAX_VALUES;
pGraph->pptDataPoints =
(PPOINT) MemoryAllocate (sizeof (POINT) * pGraph->gMaxValues) ;

pGraph->pDataTime =
(SYSTEMTIME *) MemoryAllocate (sizeof(SYSTEMTIME) * pGraph->gMaxValues) ;

pGraph->hWnd = hWnd ;
pGraph->bModified = FALSE ;

pGraph->Visual.iColorIndex = 0 ;
pGraph->Visual.iWidthIndex = 0 ;
pGraph->Visual.iStyleIndex = 0 ;

return(TRUE) ;
}



void PlaybackSetGraphLines (HWND hWndChart,
PLINE pLineFirst,
int iDisplayTic,
int iLogTic,
BOOL CalcData)
{
PLINE pLine ;
FLOAT eValue ;

for (pLine = pLineFirst ;
pLine ;
pLine = pLine->pLineNext)
{ // for
eValue = CounterEntry (pLine) ;
pLine->lnValues[iDisplayTic] = eValue ;
pLine->aiLogIndexes[iDisplayTic] = iLogTic ;

// only need to do this on request.
if (CalcData)
{
eUpdateMinMaxAve (eValue, pLine, iDisplayTic, iDisplayTic,
iDisplayTic, iDisplayTic) ;
} // if
} // for
} // PlaybackSetGraphLines



BOOL ChartInsertLine (PGRAPHSTRUCT pGraph,
PLINE pLine)
/*
Effect: Insert the line pLine into the graph pGraph and
allocate space for the graph's number of values.

Returns: Whether the line could be added and space allocated.

See Also: LineAllocate (line.c), ChartDeleteLine.
*/
{ // ChartInsertLine
PLINE pLineEquivalent ;
INT i ;
FLOAT *pTempPts;
HPEN tempPen ;

pGraph->bModified = TRUE ;

pLineEquivalent = FindEquivalentLine (pLine, pGraph->pLineFirst) ;
if (pLineEquivalent)
{
pLineEquivalent->Visual = pLine->Visual ;
pLineEquivalent->iScaleIndex = pLine->iScaleIndex ;
pLineEquivalent->eScale = pLine->eScale ;

tempPen = pLineEquivalent->hPen ;
pLineEquivalent->hPen = pLine->hPen ;
pLine->hPen = tempPen ;
return FALSE ;
}
else
{
if (!pGraph->pLineFirst && !PlayingBackLog())
{
SetGraphTimer (pGraph) ;
}

if (!SystemAdd (&pGraph->pSystemFirst, pLine->lnSystemName))
return FALSE;

LineAppend (&pGraph->pLineFirst, pLine) ;

pLine->lnMinValue = FLT_MAX ;
pLine->lnMaxValue = - FLT_MAX ;
pLine->lnAveValue = 0.0F ;
pLine->lnValidValues = 0 ;

pLine->lnValues =
(FLOAT *) MemoryAllocate (sizeof (FLOAT) * pGraph->gMaxValues) ;

for (i = pGraph->gMaxValues, pTempPts = pLine->lnValues ;
i > 0 ;
i-- )
{
*pTempPts++ = (FLOAT) 0.0 ;
}

if (PlayingBackLog ())
{
pLine->aiLogIndexes =
(int *) MemoryAllocate (sizeof (LONG) * pGraph->gMaxValues) ;
}

// Add the line to the legend, resize the legend window, and then
// select the new line as the current legend item. Do it in this
// sequence to avoid the legend scroll bar momentarily appearing and
// then disappearing, since the resize will obviate the scroll bar.

LegendAddItem (hWndGraphLegend, pLine) ;

if (!bDelayAddAction)
{
SizeGraphComponents (hWndGraph) ;
LegendSetSelection (hWndGraphLegend,
LegendNumItems (hWndGraphLegend) - 1) ;

if (PlayingBackLog ())
PlaybackChart (pGraph->hWnd) ;
}
}

return (TRUE) ;
} // ChartInsertLine


VOID ChartDeleteLine (PGRAPHSTRUCT pGraph,
PLINESTRUCT pLine)
{
PLINESTRUCT npLine;


pGraph->bModified = TRUE ;

if (pGraph->pLineFirst == pLine)
pGraph->pLineFirst = pLine->pLineNext;
else
{
for (npLine = pGraph->pLineFirst; npLine; npLine = npLine->pLineNext)
{
if (npLine->pLineNext == pLine)
npLine->pLineNext = pLine->pLineNext;
}
}

if (!pGraph->pLineFirst)
{
ResetGraph (pGraph) ;
}
else
{
BuildValueListForSystems (
pGraph->pSystemFirst,
pGraph->pLineFirst) ;
}

// Delete the legend entry for this line.
// If the line was highlighted then this will undo the highlight.

LegendDeleteItem (hWndGraphLegend, pLine) ;

LineFree (pLine) ;

SizeGraphComponents (hWndGraph) ;


}


FLOAT Counter_Queuelen(PLINESTRUCT pLine)
{

return((FLOAT)0.0);
// pLine;
}


void ClearGraphDisplay (PGRAPHSTRUCT pGraph)
{
PLINESTRUCT pLine;

// reset the timeline data
// pGraph->gKnownValue = -1 ;
pGraph->gKnownValue = 0 ;
pGraph->gTimeLine.iValidValues = 0 ;
pGraph->gTimeLine.xLastTime = 0 ;
memset (pGraph->pDataTime, 0, sizeof(SYSTEMTIME) * pGraph->gMaxValues) ;

// loop through lines,
// If one of the lines is highlighted then do the calculations
// for average, min, & max on that line.

for (pLine=pGraph->pLineFirst; pLine; pLine=pLine->pLineNext)
{ // for

pLine->bFirstTime = 2 ;
pLine->lnMinValue = FLT_MAX ;
pLine->lnMaxValue = - FLT_MAX ;
pLine->lnAveValue = 0.0F ;
pLine->lnValidValues = 0 ;
memset (pLine->lnValues, 0, sizeof(FLOAT) * pGraph->gMaxValues) ;
}

StatusTimer (hWndGraphStatus, TRUE) ;
WindowInvalidate (hWndGraphDisplay) ;
}

void ResetGraphView (HWND hWndGraph)
{
PGRAPHSTRUCT pGraph ;

pGraph = GraphData (hWndGraph) ;


if (!pGraph)
{
return ;
}

ChangeSaveFileName (NULL, IDM_VIEWCHART) ;

if (pGraph->pSystemFirst)
{
ResetGraph (pGraph) ;
}
} // ResetGraphView

void ResetGraph (PGRAPHSTRUCT pGraph)
{
ClearGraphTimer (pGraph) ;
ClearLegend (hWndGraphLegend) ;
if (pGraph->pLineFirst)
{
FreeLines (pGraph->pLineFirst) ;
pGraph->pLineFirst = NULL ;
}

if (pGraph->pSystemFirst)
{
FreeSystems (pGraph->pSystemFirst) ;
pGraph->pSystemFirst = NULL ;
}
// pGraph->gKnownValue = -1 ;
pGraph->gKnownValue = 0 ;
pGraph->gTimeLine.iValidValues = 0 ;
pGraph->gTimeLine.xLastTime = 0 ;

// reset visual data
pGraph->Visual.iColorIndex = 0 ;
pGraph->Visual.iWidthIndex = 0 ;
pGraph->Visual.iStyleIndex = 0 ;

memset (pGraph->pDataTime, 0, sizeof(SYSTEMTIME) * pGraph->gMaxValues) ;

SizeGraphComponents (hWndGraph) ;
InvalidateRect(hWndGraph, NULL, TRUE) ;
}



void PlaybackChart (HWND hWndChart)
{ // PlaybackChart
int iDisplayTics ; // num visual points to display
int iDisplayTic ;
int iLogTic ;
int iLogTicsMove ;
BOOL bFirstTime = TRUE;
int iLogTicsRemaining ;

if (!pGraphs->pLineFirst)
{
// no line to playback
return ;
}

iLogTicsRemaining = PlaybackLog.iSelectedTics ;


// we only have iDisplayTics-1 points since
// we have to use the first two sample points to
// get the first data points.
if (iLogTicsRemaining <= pGraphs->gMaxValues)
{
iDisplayTics = iLogTicsRemaining ;
pGraphs->gTimeLine.iValidValues = iDisplayTics - 1 ;
}
else
{
iDisplayTics = pGraphs->gMaxValues ;
pGraphs->gTimeLine.iValidValues = iDisplayTics ;
}

iDisplayTic = -1 ;
iLogTic = PlaybackLog.StartIndexPos.iPosition ;

while (iDisplayTics)
{

PlaybackLines (pGraphs->pSystemFirst,
pGraphs->pLineFirst,
iLogTic) ;

if (bFirstTime)
{
bFirstTime = FALSE ;

// get the second sample data to form the first data point
iLogTic++ ;
iLogTicsRemaining-- ;
PlaybackLines (pGraphs->pSystemFirst,
pGraphs->pLineFirst,
iLogTic) ;
}
iDisplayTic++ ;
PlaybackSetGraphLines (hWndChart, pGraphs->pLineFirst,
iDisplayTic, iLogTic, (iDisplayTics == 1)) ;

// setup DDA to get the index of the next sample point
iLogTicsMove = DDA_DISTRIBUTE (iLogTicsRemaining, iDisplayTics) ;
iLogTicsRemaining -= iLogTicsMove ;
iLogTic += iLogTicsMove ;

iDisplayTics-- ;

} // while

// point to the last value for valuebar display
pGraphs->gKnownValue = iDisplayTic ;

} // PlaybackChart



#if 0
PLINESTRUCT CurrentGraphLine (HWND hWndGraph)
{ // CurrentGraphLine
UNREFERENCED_PARAMETER (hWndGraph) ;

return (LegendCurrentLine (hWndGraphLegend)) ;
}
#endif


BOOL AddChart (HWND hWndParent)
{
PLINE pCurrentLine = CurrentGraphLine (hWndGraph) ;

return (AddLine (hWndParent,
&(pGraphs->pSystemFirst),
&(pGraphs->Visual),
pCurrentLine ? pCurrentLine->lnSystemName : NULL,
LineTypeChart)) ;
}


BOOL EditChart (HWND hWndParent)
{ // EditChart
return (EditLine (hWndParent,
&(pGraphs->pSystemFirst),
CurrentGraphLine (hWndGraph),
LineTypeChart)) ;
}

void GraphAddAction ()
{
PGRAPHSTRUCT pGraph ;

pGraph = GraphData (hWndGraph) ;

SizeGraphComponents (hWndGraph) ;

LegendSetSelection (hWndGraphLegend,
LegendNumItems (hWndGraphLegend) - 1) ;

if (PlayingBackLog ())
PlaybackChart (pGraph->hWnd) ;
}

BOOL OpenChartVer1 (HANDLE hFile,
DISKCHART *pDiskChart,
PGRAPHSTRUCT pGraph)
{ // OpenChartVer1
bDelayAddAction = TRUE ;
pGraph->Visual = pDiskChart->Visual ;
pGraph->gOptions = pDiskChart->gOptions ;
pGraph->gMaxValues = pDiskChart->gMaxValues ;
pGraph->bManualRefresh = pDiskChart->bManualRefresh ;
pGraphs->gInterval = (INT) (pGraph->gOptions.eTimeInterval * (FLOAT) 1000.0) ;
ReadLines (hFile, pDiskChart->dwNumLines,
&(pGraph->pSystemFirst), &(pGraph->pLineFirst), IDM_VIEWCHART) ;

bDelayAddAction = FALSE ;

GraphAddAction () ;

return (TRUE) ;
} // OpenChartVer1



BOOL OpenChart (HWND hWndGraph,
HANDLE hFile,
DWORD dwMajorVersion,
DWORD dwMinorVersion,
BOOL bChartFile)
{ // OpenChart
PGRAPHSTRUCT pGraph ;
DISKCHART DiskChart ;
BOOL bSuccess = TRUE ;

pGraph = pGraphs ;
if (!pGraph)
{
bSuccess = FALSE ;
goto Exit0 ;
}

if (!FileRead (hFile, &DiskChart, sizeof (DISKCHART)))
{
bSuccess = FALSE ;
goto Exit0 ;
}


switch (dwMajorVersion)
{
case (1):

SetHourglassCursor() ;

ResetGraphView (hWndGraph) ;

OpenChartVer1 (hFile, &DiskChart, pGraph) ;

// change to chart view if we are opening a
// chart file
if (bChartFile && iPerfmonView != IDM_VIEWCHART)
{
SendMessage (hWndMain, WM_COMMAND, (LONG)IDM_VIEWCHART, 0L) ;
}

if (iPerfmonView == IDM_VIEWCHART)
{
SetPerfmonOptions (&DiskChart.perfmonOptions) ;
}

SetArrowCursor() ;

break ;
} // switch

Exit0:

if (bChartFile)
{
CloseHandle (hFile) ;
}

return (bSuccess) ;
} // OpenChart

BOOL SaveChart (HWND hWndGraph, HANDLE hInputFile, BOOL bGetFileName)
{
PGRAPHSTRUCT pGraph ;
PLINE pLine ;
HANDLE hFile ;
DISKCHART DiskChart ;
PERFFILEHEADER FileHeader ;
TCHAR szFileName [256] ;
BOOL newFileName = FALSE ;

if (hInputFile)
{
// use the input file handle if it is available
// this is the case for saving workspace data
hFile = hInputFile ;
}
else
{
if (pChartFullFileName)
{
lstrcpy (szFileName, pChartFullFileName) ;
}
if (bGetFileName || pChartFullFileName == NULL)
{
// if (!pChartFullFileName)
// {
// StringLoad (IDS_GRAPH_FNAME, szFileName) ;
// }

if (!FileGetName (hWndGraph, IDS_CHARTFILE, szFileName))
{
return (FALSE) ;
}
newFileName = TRUE ;
}

hFile = FileHandleCreate (szFileName) ;

if (hFile && newFileName)
{
ChangeSaveFileName (szFileName, IDM_VIEWCHART) ;
}
else if (!hFile)
{
DlgErrorBox (hWndGraph, ERR_CANT_OPEN, szFileName) ;
}
}

if (!hFile)
return (FALSE) ;

pGraph = pGraphs ;
if (!pGraph)
{
if (!hInputFile)
{
CloseHandle (hFile) ;
}
return (FALSE) ;
}

if (!hInputFile)
{
// only need to write file header if not workspace
memset (&FileHeader, 0, sizeof (FileHeader)) ;
lstrcpy (FileHeader.szSignature, szPerfChartSignature) ;
FileHeader.dwMajorVersion = ChartMajorVersion ;
FileHeader.dwMinorVersion = ChartMinorVersion ;

if (!FileWrite (hFile, &FileHeader, sizeof (PERFFILEHEADER)))
{
goto Exit0 ;
}
}

DiskChart.Visual = pGraph->Visual ;
DiskChart.gOptions = pGraph->gOptions ;
DiskChart.gMaxValues = pGraph->gMaxValues ;
DiskChart.dwNumLines = NumLines (pGraph->pLineFirst) ;
DiskChart.bManualRefresh = pGraph->bManualRefresh ;
DiskChart.perfmonOptions = Options ;

if (!FileWrite (hFile, &DiskChart, sizeof (DISKCHART)))
{
goto Exit0 ;
}

for (pLine = pGraph->pLineFirst ;
pLine ;
pLine = pLine->pLineNext)
{ // for
if (!WriteLine (pLine, hFile))
{
goto Exit0 ;
}
} // for

if (!hInputFile)
{
CloseHandle (hFile) ;
}

return (TRUE) ;

Exit0:
if (!hInputFile)
{
CloseHandle (hFile) ;

// only need to report error if not workspace
DlgErrorBox (hWndGraph, ERR_SETTING_FILE, szFileName) ;
}
return (FALSE) ;

} // SaveChart

#define TIME_TO_WRITE 2
BOOL ExportChartLabels (HANDLE hFile, PGRAPHSTRUCT pGraph)
{
TCHAR UnicodeBuff [LongTextLen] ;
CHAR TempBuff [LongTextLen * 2] ;
int StringLen ;
PLINESTRUCT pLine;
int TimeToWriteFile ;
int iIndex ;
LPTSTR lpItem ;

for (iIndex = 0 ; iIndex < 5 ; iIndex++)
{
// for iIndex == 0, get counter name
// for iIndex == 1, get instance name
// for iIndex == 2, get parent name
// for iIndex == 3, get object name
// for iIndex == 4, get computer name
if (iIndex == 4)
{
// the last label field, write date/time labels
strcpy (TempBuff, LineEndStr) ;
StringLen = strlen (TempBuff) ;
StringLoad (IDS_EXPORT_DATE, UnicodeBuff) ;
ConvertUnicodeStr (&TempBuff[StringLen], UnicodeBuff) ;
strcat (TempBuff, pDelimiter) ;
StringLen = strlen (TempBuff) ;

StringLoad (IDS_EXPORT_TIME, UnicodeBuff) ;
ConvertUnicodeStr (&TempBuff[StringLen], UnicodeBuff) ;
strcat (TempBuff, pDelimiter) ;
StringLen = strlen (TempBuff) ;
}
else
{
strcpy (TempBuff, LineEndStr) ;
strcat (TempBuff, pDelimiter) ;
strcat (TempBuff, pDelimiter) ;
StringLen = strlen (TempBuff) ;
}

TimeToWriteFile = 0 ;

for (pLine=pGraph->pLineFirst; pLine; pLine=pLine->pLineNext)
{
switch (iIndex)
{
case 0:
lpItem = (LPTSTR) pLine->lnCounterName ;
break ;

case 1:
lpItem = (LPTSTR) pLine->lnInstanceName ;
break ;

case 2:
lpItem = (LPTSTR) pLine->lnPINName ;
break ;

case 3:
lpItem = (LPTSTR) pLine->lnObjectName ;
break ;

case 4:
lpItem = (LPTSTR) pLine->lnSystemName ;
break ;
}

if (lpItem)
{
ConvertUnicodeStr (&TempBuff[StringLen], lpItem) ;
}
else
{
TempBuff[StringLen] = '\0' ;
}
strcat (TempBuff, pDelimiter);
StringLen = strlen (TempBuff) ;

if (++TimeToWriteFile > TIME_TO_WRITE)
{
// better write the buffers before they overflow.
// there are better ways to check for overflow
// but this is good enough

if (!FileWrite (hFile, TempBuff, StringLen))
{
goto Exit0 ;
}
StringLen = TimeToWriteFile = 0 ;
}
} // for each line

if (StringLen)
{
// write the last block of data
if (!FileWrite (hFile, TempBuff, StringLen))
{
goto Exit0 ;
}
}
} // for iIndex

return (TRUE) ;

Exit0:
return (FALSE) ;

} // ExportChartLabels

BOOL ExportLogChart (HANDLE hFile, PGRAPHSTRUCT pGraph)
{
TCHAR UnicodeBuff [LongTextLen] ;
CHAR TempBuff [LongTextLen * 2] ;
int StringLen ;
PLINESTRUCT pLine;
int TimeToWriteFile ;
FLOAT eValue ;
int iLogTic ;
BOOL bFirstTime = TRUE ;
SYSTEMTIME LogSystemTime ;
LOGPOSITION LogPosition ;

iLogTic = PlaybackLog.StartIndexPos.iPosition ;

// we have to export every point from the log file

for ( ; iLogTic <= PlaybackLog.StopIndexPos.iPosition ; iLogTic++)
{


PlaybackLines (pGraphs->pSystemFirst,
pGraphs->pLineFirst,
iLogTic) ;

if (!bFirstTime)
{
// export the values
TimeToWriteFile = 0 ;


if (!LogPositionN (iLogTic, &LogPosition))
{
goto Exit0 ;
}

LogPositionSystemTime (&LogPosition, &LogSystemTime) ;

strcpy (TempBuff, LineEndStr) ;
StringLen = strlen (TempBuff) ;

SystemTimeDateString (&LogSystemTime, UnicodeBuff) ;
ConvertUnicodeStr (&TempBuff[StringLen], UnicodeBuff) ;
strcat (TempBuff, pDelimiter) ;
StringLen = strlen (TempBuff) ;

SystemTimeTimeString (&LogSystemTime, UnicodeBuff, FALSE) ;
ConvertUnicodeStr (&TempBuff[StringLen], UnicodeBuff) ;
strcat (TempBuff, pDelimiter) ;
StringLen = strlen (TempBuff) ;

for (pLine=pGraph->pLineFirst; pLine; pLine=pLine->pLineNext)
{

eValue = CounterEntry (pLine) ;

TSPRINTF (UnicodeBuff,
eValue > (FLOAT)999999.0 ?
szLargeValueFormat : szSmallValueFormat,
eValue) ;
ConvertDecimalPoint (UnicodeBuff) ;
ConvertUnicodeStr (&TempBuff[StringLen], UnicodeBuff) ;
strcat (TempBuff, pDelimiter) ;
StringLen = strlen (TempBuff) ;

if (++TimeToWriteFile > TIME_TO_WRITE)
{
if (!FileWrite (hFile, TempBuff, StringLen))
{
goto Exit0 ;
}
StringLen = TimeToWriteFile = 0 ;
TempBuff[0] = '\0' ;
}
}

if (StringLen)
{
if (!FileWrite (hFile, TempBuff, StringLen))
{
goto Exit0 ;
}
}
}
else
{
// skip the first data point since we
// need 2 points to form the first value
bFirstTime = FALSE ;
}
}

return (TRUE) ;

Exit0:
return (FALSE) ;

} // ExportLogChart

BOOL ExportLineValue (HANDLE hFile, PGRAPHSTRUCT pGraph,
int CurrentIndex, int iDataPoint)
{
TCHAR UnicodeBuff [MiscTextLen] ;
CHAR TempBuff [LongTextLen] ;
int StringLen ;
PLINESTRUCT pLine;
int MaxValues ;
int TimeToWriteFile ;
SYSTEMTIME *pSystemTime ;
BOOL ValidValue ;

pSystemTime = pGraph->pDataTime ;
pSystemTime += CurrentIndex ;

if (pSystemTime->wYear == 0 && pSystemTime->wYear == 0)
{
// ignore value that has 0 system time
return (TRUE) ;
}

MaxValues = pGraph->gMaxValues ;
strcpy (TempBuff, LineEndStr) ;
StringLen = strlen (TempBuff) ;

SystemTimeDateString (pSystemTime, UnicodeBuff) ;
ConvertUnicodeStr (&TempBuff[StringLen], UnicodeBuff) ;
strcat (TempBuff, pDelimiter) ;
StringLen = strlen (TempBuff) ;

SystemTimeTimeString (pSystemTime, UnicodeBuff, FALSE) ;
ConvertUnicodeStr (&TempBuff[StringLen], UnicodeBuff) ;
strcat (TempBuff, pDelimiter) ;
StringLen = strlen (TempBuff) ;

TimeToWriteFile = 0 ;
for (pLine=pGraph->pLineFirst; pLine; pLine=pLine->pLineNext)
{
if (!pLine->bFirstTime)
{
ValidValue = FALSE ;
// check if this is a valid value
if (pLine->lnValidValues == MaxValues)
{
// this is the simple case where we have filled up
// the whole buffer
ValidValue = TRUE ;
}
else if (pLine->lnValidValues <= iDataPoint)
{
if (CurrentIndex <= iDataPoint &&
CurrentIndex > iDataPoint - pLine->lnValidValues)
{
ValidValue = TRUE ;
}
}
else
{
if (CurrentIndex <= iDataPoint ||
CurrentIndex > (MaxValues - pLine->lnValidValues + iDataPoint))
{
// this is the case when we start the new line in the middle
// and data buffer has been wrap-around.
ValidValue = TRUE ;
}
}

// only export the data when we determine it is valid
if (ValidValue)
{
TSPRINTF (UnicodeBuff,
pLine->lnValues[CurrentIndex] > (FLOAT)999999.0 ?
szLargeValueFormat : szSmallValueFormat,
pLine->lnValues[CurrentIndex]) ;
ConvertDecimalPoint (UnicodeBuff) ;
ConvertUnicodeStr (&TempBuff[StringLen], UnicodeBuff) ;
}
}
strcat (TempBuff, pDelimiter) ;
StringLen = strlen (TempBuff) ;

if (++TimeToWriteFile > TIME_TO_WRITE)
{
// better write the buffers before they overflow.
// there are better ways to check for overflow
// but this is good enough

if (!FileWrite (hFile, TempBuff, StringLen))
{
goto Exit0 ;
}
StringLen = TimeToWriteFile = 0 ;
TempBuff[0] = '\0' ;
}
}

if (StringLen)
{
// write the last block of data
if (!FileWrite (hFile, TempBuff, StringLen))
{
goto Exit0 ;
}
}

return (TRUE) ;

Exit0:
return (FALSE) ;

} // ExportLineValue

BOOL ExportCurrentChart (HANDLE hFile, PGRAPHSTRUCT pGraph)
{
int KnownValue,
MaxValues,
iValidValues,
iDataPoint ;
BOOL SimpleCase = FALSE ;
int iIndex ;

MaxValues = pGraph->gMaxValues ;
KnownValue = pGraph->gKnownValue ;
iValidValues = pGraph->gTimeLine.iValidValues ;

if (iValidValues < MaxValues)
{
// data have not wrapped around, so the oldest time
// is started at 0.
SimpleCase = TRUE ;
iValidValues = (KnownValue % MaxValues) + 1 ;
}

iDataPoint = KnownValue % MaxValues ;

if (!SimpleCase)
{
for (iIndex = iDataPoint+1 ; iIndex < MaxValues ; iIndex++)
{
if (!ExportLineValue (hFile, pGraph, iIndex, iDataPoint))
{
goto Exit0 ;
}
}
}

for (iIndex = 0 ; iIndex <= iDataPoint ; iIndex++)
{
if (!ExportLineValue (hFile, pGraph, iIndex, iDataPoint))
{
goto Exit0 ;
}
}

return (TRUE) ;

Exit0:
return (FALSE) ;

} // ExportCurrentChart


void ExportChart (void)
{

PGRAPHSTRUCT pGraph ;
HANDLE hFile = 0 ;
LPTSTR pFileName = NULL ;
INT ErrCode = 0 ;

if (!(pGraph = pGraphs))
{
return ;
}

// see if there is anything to export..
if (!(pGraph->pLineFirst))
{
return ;
}

SetHourglassCursor() ;

if (ErrCode = ExportFileOpen (hWndGraph, &hFile, pGraph->gInterval, &pFileName))
{
goto Exit0 ;
}

if (!pFileName)
{
// this is the case when user cancel.
goto Exit0 ;
}

// export the column labels
if (!ExportChartLabels (hFile, pGraph))
{

ErrCode = ERR_EXPORT_FILE ;
goto Exit0 ;
}

// export the lines
if (PlayingBackLog())
{
if (!ExportLogChart (hFile, pGraph))
{
ErrCode = ERR_EXPORT_FILE ;
goto Exit0 ;
}
}
else
{
if (!ExportCurrentChart (hFile, pGraph))
{
ErrCode = ERR_EXPORT_FILE ;
goto Exit0 ;
}
}
Exit0:

SetArrowCursor() ;

if (hFile)
{
CloseHandle (hFile) ;
}

if (pFileName)
{
if (ErrCode)
{
DlgErrorBox (hWndGraph, ErrCode, pFileName) ;
}

MemoryFree (pFileName) ;
}

} // ExportChart


typedef struct CHARTDATAPOINTSTRUCT
{
int iLogIndex ;
int xDispDataPoint ;
} CHARTDATAPOINT, *PCHARTDATAPOINT ;

void PlaybackChartDataPoint (PCHARTDATAPOINT pChartDataPoint)
{ // PlaybackChartDataPoint
int iDisplayTics ; // num visual points to display
int iDisplayTic ;
int iLogTic ;
int iLogTicsMove ;
BOOL bFirstTime = TRUE;
int iLogTicsRemaining ;
int numOfData, xDispDataPoint, rectWidth, xPos ;
PGRAPHSTRUCT pGraph ;

pGraph = GraphData (hWndGraph) ;

iLogTicsRemaining = PlaybackLog.iSelectedTics ;


// we only have iDisplayTics-1 points since
// we have to use the first two sample points to
// get the first data points.
if (iLogTicsRemaining <= pGraphs->gMaxValues)
{
iDisplayTics = iLogTicsRemaining ;
}
else
{
iDisplayTics = pGraphs->gMaxValues ;
}

iDisplayTic = -1 ;
iLogTic = PlaybackLog.StartIndexPos.iPosition ;

numOfData = pGraph->gMaxValues - 1 ;
rectWidth = pGraph->rectData.right - pGraph->rectData.left ;
xDispDataPoint = pGraph->rectData.left ;

while (iDisplayTics && numOfData)
{

if (!bFirstTime)
{
iDisplayTic++ ;
}
else
{
bFirstTime = FALSE ;

// get the second sample data to form the first data point
iLogTic++ ;
iLogTicsRemaining-- ;

iDisplayTic++ ;
}

pChartDataPoint[iDisplayTic].iLogIndex = iLogTic ;
pChartDataPoint[iDisplayTic].xDispDataPoint = xDispDataPoint ;

// setup DDA to get the index of the next sample point
iLogTicsMove = DDA_DISTRIBUTE (iLogTicsRemaining, iDisplayTics) ;
iLogTicsRemaining -= iLogTicsMove ;
iLogTic += iLogTicsMove ;


xPos = DDA_DISTRIBUTE (rectWidth, numOfData) ;
xDispDataPoint += xPos ;
numOfData-- ;
rectWidth -= xPos ;

iDisplayTics-- ;

} // while

} // PlaybackChartDataPoint