/*++ Copyright (C) 1996-1999 Microsoft Corporation Module Name: grphdsp.cpp Abstract: --*/ #include "polyline.h" #include "winhelpr.h" #include #include #define ThreeDPad 1 #define BORDER ThreeDPad #define TEXT_MARGIN (ThreeDPad + 2) static HPEN hPenWhite; static HPEN hPenBlack; INT CGraphDisp::RGBToLightness ( COLORREF clrValue ) { INT iLightness; INT iRed; INT iGreen; INT iBlue; INT iMax; INT iMin; // The complete algorithm for computing lightness is: // Lightness = (Max(R,G,B)+ Min(R,G,B))/2*225. // Only need to compute enought to determine whether to draw black or white highlight. iRed = GetRValue( clrValue ); iGreen = GetGValue (clrValue ); iBlue = GetBValue (clrValue ); if ( iRed > iGreen ) { iMax = iRed; iMin = iGreen; } else { iMax = iGreen; iMin = iRed; } if ( iBlue > iMax ) { iMax = iBlue; } else if ( iBlue < iMin ) { iMin = iBlue; } iLightness = iMin + iMax; return iLightness; } CGraphDisp::CGraphDisp ( void ) : m_pCtrl ( NULL ), m_pGraph ( NULL ), m_pHiliteItem ( NULL ), m_hFontVertical ( NULL ), m_bBarConfigChanged ( TRUE ) { } CGraphDisp::~CGraphDisp ( void ) { if (m_hFontVertical != NULL) DeleteObject(m_hFontVertical); if (m_hPenTimeBar != 0) { DeleteObject ( m_hPenTimeBar ); m_hPenTimeBar = 0; } if (m_hPenGrid != 0) { DeleteObject ( m_hPenGrid ); m_hPenGrid = 0; } } BOOL CGraphDisp::Init ( CSysmonControl *pCtrl, PGRAPHDATA pGraph ) { BOOL bRetStatus = TRUE; m_pCtrl = pCtrl; m_pGraph = pGraph; m_clrCurrentGrid = m_pCtrl->clrGrid(); m_clrCurrentTimeBar = m_pCtrl->clrTimeBar(); // Create the highlight, timebar and grid pens. m_hPenTimeBar = CreatePen(PS_SOLID, 2, m_clrCurrentTimeBar ); // if can't do it, use a stock object (this can't fail) if (m_hPenTimeBar == NULL) m_hPenTimeBar = (HPEN)GetStockObject(BLACK_PEN); m_hPenGrid = CreatePen(PS_SOLID, 1, m_clrCurrentGrid ); // if can't do it, use a stock object (this can't fail) if (m_hPenGrid == NULL) m_hPenGrid = (HPEN)GetStockObject(BLACK_PEN); // Highlight pens are shared among all Sysmon instances. BEGIN_CRITICAL_SECTION if (hPenWhite == 0) { hPenWhite = CreatePen(PS_SOLID, 3, RGB(255,255,255)); hPenBlack = CreatePen(PS_SOLID, 3, RGB(0,0,0)); } END_CRITICAL_SECTION return bRetStatus; } void CGraphDisp::HiliteItem( PCGraphItem pItem ) { m_pHiliteItem = pItem; } VOID CGraphDisp::Draw( HDC hDC, HDC hAttribDC, BOOL fMetafile, BOOL fEntire, PRECT /* prcUpdate */ ) { RECT rectFrame; RECT rectTitle; CStepper locStepper; DWORD dwPrevLayout = 0; DWORD dwNewLayout = 0; if ( ( m_rect.right > m_rect.left ) && ( m_rect.bottom > m_rect.top ) ) { if ( NULL != hDC ) { dwPrevLayout = GetLayout ( hDC ); dwNewLayout = dwPrevLayout; if ( dwNewLayout & LAYOUT_RTL ) { dwNewLayout &= ~LAYOUT_RTL; SetLayout (hDC, dwNewLayout); } // Fill plot area Fill(hDC, m_pCtrl->clrBackPlot(), &m_rectPlot); rectFrame = m_rectPlot; // Draw 3D border around plot area if ( eAppear3D == m_pCtrl->Appearance() ) { InflateRect(&rectFrame,BORDER,BORDER); DrawEdge(hDC, &rectFrame, BDR_SUNKENOUTER, BF_RECT); } // Select colors for all text SetBkMode(hDC, TRANSPARENT); SetTextColor(hDC, m_pCtrl->clrFgnd()); // Draw the scale if (m_pGraph->Options.bLabelsChecked) { SelectFont(hDC, m_pCtrl->Font()) ; m_pGraph->Scale.Draw(hDC); } // Draw the main title if (m_pGraph->Options.pszGraphTitle != NULL) { SelectFont(hDC, m_pCtrl->Font()) ; SetTextAlign(hDC, TA_TOP|TA_CENTER); rectTitle = rectFrame; rectTitle.top = m_rect.top; FitTextOut( hDC, hAttribDC, 0, &rectTitle, m_pGraph->Options.pszGraphTitle, lstrlen(m_pGraph->Options.pszGraphTitle), TA_CENTER, FALSE ); } // Draw the Y axis title if (m_pGraph->Options.pszYaxisTitle != NULL && m_hFontVertical != NULL) { SelectFont(hDC, m_hFontVertical) ; SetTextAlign(hDC, TA_TOP|TA_CENTER); rectTitle = rectFrame; rectTitle.left = m_rect.left; FitTextOut( hDC, hAttribDC, 0, &rectTitle, m_pGraph->Options.pszYaxisTitle, lstrlen(m_pGraph->Options.pszYaxisTitle), TA_CENTER, TRUE); } // setup stepper reset to start locStepper = m_pGraph->TimeStepper; locStepper.Reset(); // Set clipping area. Fill executed above, so bFill= FALSE. StartUpdate(hDC, fMetafile, fEntire, 0, (m_rectPlot.right - m_rectPlot.left), FALSE ); // draw the grid lines DrawGrid(hDC, 0, m_rectPlot.right - m_rectPlot.left); m_pCtrl->LockCounterData(); switch (m_pGraph->Options.iDisplayType) { case LINE_GRAPH: // Finish and restart update so that wide lines are cropped at the timeline. FinishUpdate(hDC, fMetafile); StartUpdate( hDC, fMetafile, FALSE, 0, m_pGraph->TimeStepper.Position(), FALSE ); // Plot points from start of graph to time line PlotData(hDC, m_pGraph->TimeStepper.StepNum() + m_pGraph->History.nBacklog, m_pGraph->TimeStepper.StepNum(), &locStepper); FinishUpdate(hDC, fMetafile); // Plot points from time line to end of graph locStepper = m_pGraph->TimeStepper; // Restart update. Left-hand ends and internal gaps of wide lines are not cropped. StartUpdate( hDC, fMetafile, FALSE, locStepper.Position(), m_rectPlot.right - m_rectPlot.left, FALSE ); PlotData(hDC, m_pGraph->TimeStepper.StepCount() + m_pGraph->History.nBacklog, m_pGraph->TimeStepper.StepCount() - m_pGraph->TimeStepper.StepNum(), &locStepper); DrawTimeLine(hDC, m_pGraph->TimeStepper.Position()); if ( MIN_TIME_VALUE != m_pGraph->LogViewTempStart ) DrawStartStopLine(hDC, m_pGraph->LogViewStartStepper.Position()); if ( MAX_TIME_VALUE != m_pGraph->LogViewTempStop ) DrawStartStopLine(hDC, m_pGraph->LogViewStopStepper.Position()); break; case BAR_GRAPH: PlotBarGraph(hDC, FALSE); break; } FinishUpdate(hDC, fMetafile); if ( dwNewLayout != dwPrevLayout ) { SetLayout (hDC, dwPrevLayout); } m_pCtrl->UnlockCounterData(); } } } VOID CGraphDisp::UpdateTimeBar( HDC hDC, BOOL bPlotData ) { INT nBacklog; INT iUpdateCnt; INT i; CStepper locStepper; nBacklog = m_pGraph->History.nBacklog; // Work off backlogged sample intervals while ( nBacklog > 0) { // If we are going to wrap around, update in two steps if (nBacklog > m_pGraph->TimeStepper.StepCount() - m_pGraph->TimeStepper.StepNum()) { iUpdateCnt = m_pGraph->TimeStepper.StepCount() - m_pGraph->TimeStepper.StepNum(); } else { iUpdateCnt = nBacklog; } // step to position of current data locStepper = m_pGraph->TimeStepper; for (i=0; iTimeStepper.NextPosition(); if ( bPlotData ) { StartUpdate( hDC, FALSE, FALSE, locStepper.Position(), m_pGraph->TimeStepper.Position(), TRUE ); DrawGrid(hDC, locStepper.Position(), m_pGraph->TimeStepper.Position()); PlotData(hDC, nBacklog, iUpdateCnt, &locStepper); FinishUpdate ( hDC, FALSE ); } if (m_pGraph->TimeStepper.StepNum() >= m_pGraph->TimeStepper.StepCount()) m_pGraph->TimeStepper.Reset(); nBacklog -= iUpdateCnt; } if ( bPlotData ) { DrawTimeLine(hDC, m_pGraph->TimeStepper.Position()); } m_pGraph->History.nBacklog = 0; } VOID CGraphDisp::Update( HDC hDC ) { DWORD dwPrevLayout = 0; DWORD dwNewLayout = 0; m_pCtrl->LockCounterData(); if ( NULL != hDC ) { dwPrevLayout = GetLayout ( hDC ); dwNewLayout = dwPrevLayout; if ( dwNewLayout & LAYOUT_RTL ) { dwNewLayout &= ~LAYOUT_RTL; SetLayout (hDC, dwNewLayout); } if ( ( m_rect.right > m_rect.left ) && ( m_rect.bottom > m_rect.top ) ) { switch (m_pGraph->Options.iDisplayType) { case LINE_GRAPH: // Update the line graph and time bar based on history // backlog. Reset history backlog to 0, signalling collection // thread to post another WM_GRAPH_UPDATE message. UpdateTimeBar ( hDC, TRUE ); break; case BAR_GRAPH: PlotBarGraph(hDC, TRUE); break; } } // If updating histogram or report, update thetimebar step based on // history backlog. Reset history backlog to 0, signalling collection // thread to post another WM_GRAPH_UPDATE message. UpdateTimeBar ( hDC, FALSE ); if ( dwNewLayout != dwPrevLayout ) { SetLayout (hDC, dwPrevLayout); } } m_pCtrl->UnlockCounterData(); } void CGraphDisp::StartUpdate( HDC hDC, BOOL fMetafile, BOOL fEntire, INT xLeft, INT xRight, BOOL bFill ) { RECT rect; // Preserve clipping region if ( FALSE == fMetafile ) { m_rgnClipSave = CreateRectRgn(0,0,0,0); if (m_rgnClipSave != NULL) { if (GetClipRgn(hDC, m_rgnClipSave) != 1) { DeleteObject(m_rgnClipSave); m_rgnClipSave = NULL; } } xLeft += m_rectPlot.left; xRight += m_rectPlot.left; IntersectClipRect ( hDC, max ( m_rectPlot.left, xLeft ), m_rectPlot.top, min (m_rectPlot.right, xRight + 1), // Extra pixel for TimeBar m_rectPlot.bottom ) ; } else if( TRUE == fEntire ){ m_rgnClipSave = NULL; IntersectClipRect ( hDC, m_rectPlot.left, m_rectPlot.top, m_rectPlot.right, m_rectPlot.bottom ) ; } // Fill performed before this method for metafiles and complete draw. if ( !fMetafile && bFill ) { SetRect( &rect, max ( m_rectPlot.left, xLeft - 1 ), m_rectPlot.top - 1, min (m_rectPlot.right, xRight + 1), m_rectPlot.bottom); Fill(hDC, m_pCtrl->clrBackPlot(), &rect); } } void CGraphDisp::FinishUpdate( HDC hDC, BOOL fMetafile ) { // Restore saved clip region if ( !fMetafile ) { if (m_rgnClipSave != NULL) { SelectClipRgn(hDC, m_rgnClipSave); DeleteObject(m_rgnClipSave); m_rgnClipSave = NULL; } } } void CGraphDisp::DrawGrid(HDC hDC, INT xLeft, INT xRight) { INT xPos; INT nTics; INT *piScaleTic; INT i; if ( (m_pGraph->Options.bVertGridChecked) || (m_pGraph->Options.bHorzGridChecked) ) { if ( m_clrCurrentGrid != m_pCtrl->clrGrid() ) { m_clrCurrentGrid = m_pCtrl->clrGrid(); DeleteObject ( m_hPenGrid ); m_hPenGrid = CreatePen(PS_SOLID, 1, m_clrCurrentGrid ); // if can't do it, use a stock object (this can't fail) if (m_hPenGrid == NULL) m_hPenGrid = (HPEN)GetStockObject(BLACK_PEN); } } if (m_pGraph->Options.bVertGridChecked) { SelectObject(hDC, m_hPenGrid); m_GridStepper.Reset(); xPos = m_GridStepper.NextPosition(); while (xPos < xLeft) xPos =m_GridStepper.NextPosition(); while (xPos < xRight) { MoveToEx(hDC, xPos + m_rectPlot.left, m_rectPlot.bottom, NULL); LineTo(hDC, xPos + m_rectPlot.left, m_rectPlot.top - 1); xPos = m_GridStepper.NextPosition(); } } if (m_pGraph->Options.bHorzGridChecked) { xLeft += m_rectPlot.left; xRight += m_rectPlot.left; SelectObject(hDC,m_hPenGrid); nTics = m_pGraph->Scale.GetTicPositions(&piScaleTic); for (i=1; iGetLogEntry(iHistIndex, &dValue[1], &dValue[2], &dValue[0], &dwCtrStat); else stat = pItem->HistoryValue(iHistIndex, &dValue[0], &dwCtrStat); if (ERROR_SUCCESS == stat && IsSuccessSeverity(dwCtrStat)) { for (iVal = 0; iVal < nVals; iVal++) { dTemp = dValue[iVal] * pItem->Scale(); if (dTemp > m_dMax) dTemp = m_dMax; else if (dTemp < m_dMin) dTemp = m_dMin; // Plot minimum value as 1 pixel above the bottom of the plot area, since // clipping and fill regions crop the bottom and right pixels. y[iVal] = m_rectPlot.bottom - (INT)((dTemp - m_dMin) * m_dPixelScale); if ( y[iVal] == m_rectPlot.bottom ) { y[iVal] = m_rectPlot.bottom - 1; } } bReturn = TRUE; } else { bReturn = FALSE; } return bReturn; } void CGraphDisp::PlotData(HDC hDC, INT iHistIndx, INT nSteps, CStepper *pStepper) { INT i; INT x; INT y[3]; PCGraphItem pItem; CStepper locStepper; BOOL bSkip; BOOL bPrevGood; BOOL bLog; BOOL bLogMultiVal; if (m_pGraph->Options.iVertMax <= m_pGraph->Options.iVertMin) return; bSkip = TRUE; bLog = m_pCtrl->IsLogSource(); bLogMultiVal = bLog && !DisplaySingleLogSampleValue(); // If possible, back up to redraw previous segment if (pStepper->StepNum() > 0) { iHistIndx++; nSteps++; pStepper->PrevPosition(); } // Set background color, in case of dashed lines SetBkMode(hDC, TRANSPARENT); pItem = m_pCtrl->FirstCounter(); while (pItem != NULL) { locStepper = *pStepper; // Skip hilited item the first time if (!(pItem == m_pHiliteItem && bSkip)) { INT iPolyIndex = 0; POINT arrptDataPoints[MAX_GRAPH_SAMPLES] ; if ( pItem == m_pHiliteItem) { // Arbitrary 450 (out of 510) chosen as cutoff for white vs. black if ( 450 > RGBToLightness( m_pCtrl->clrBackPlot() ) ) SelectObject(hDC, hPenWhite); else SelectObject(hDC, hPenBlack); } else { SelectObject(hDC,pItem->Pen()); } bPrevGood = FALSE; // For each GOOD current value: // If the previous value is good, draw line from previous value to current value. // If the previous value is bad, MoveTo the current value point. // // For the first step, the previous value is false by definition, so the first operation // is a MoveTo. // // Polyline code: // For each GOOD current value: // Add the current (good) point to the polyline point array. // For each BAD current value: // If the polyline index is > 1 (2 points), draw the polyline and reset the polyline index to 0. // After all values: // If the polyline index is > 1 (2 points), draw the polyline. for (i = 0; i <= nSteps; i++) { // True = Good current value if ( CalcYPosition ( pItem, iHistIndx - i, bLog, y ) ) { x = m_rectPlot.left + locStepper.Position(); // Add point to polyline, since the current value is good. arrptDataPoints[iPolyIndex].x = x; arrptDataPoints[iPolyIndex].y = y[0]; iPolyIndex++; // No polyline optimization for extra Max and Min log points. if (bLogMultiVal) { MoveToEx(hDC, x, y[1], NULL); LineTo(hDC, x, y[2]); MoveToEx(hDC, x, y[0], NULL); } bPrevGood = TRUE; } else { // Current value is not good. bPrevGood = FALSE; // Current value is not good, so don't add to polyline point array. if ( iPolyIndex > 1 ) { // Draw polyline for any existing good points. Polyline(hDC, arrptDataPoints, iPolyIndex) ; } // Reset polyline point index to 0. iPolyIndex = 0; } locStepper.NextPosition(); } // Draw the final line. if ( iPolyIndex > 1 ) { // Draw polyline Polyline(hDC, arrptDataPoints, iPolyIndex) ; } // Exit loop after plotting hilited item if (pItem == m_pHiliteItem) break; } pItem = pItem->Next(); // After last item, go back to highlighted item if (pItem == NULL) { pItem = m_pHiliteItem; bSkip = FALSE; } } } void CGraphDisp::PlotBarGraph(HDC hDC, BOOL fUpdate) { if ( (m_pGraph->CounterTree.NumCounters() > 0 ) && (m_pGraph->Options.iVertMax > m_pGraph->Options.iVertMin) ) { CStepper BarStepper; PCGraphItem pItem; RECT rectBar; INT iValue,iPrevValue; HRESULT hr; LONG lCtrStat; double dValue = 0.0; double dMax; double dMin; double dAvg; double dTemp; HRGN hrgnRedraw,hrgnTemp; eReportValueTypeConstant eValueType; BOOL bLog; INT iNumCounters = m_pGraph->CounterTree.NumCounters(); BOOL bSkip = TRUE; INT iHighlightStepNum = 0; BOOL bLocalUpdate; HANDLE hPenSave; bLocalUpdate = fUpdate; hrgnRedraw = NULL; eValueType = m_pCtrl->ReportValueType(); // Todo: Move DisplaySingleLogSampleValue() to CSystemMonitor. bLog = m_pCtrl->IsLogSource(); // Force total redraw if the number of counters has changed in case // Update is called immediately after. if ( m_bBarConfigChanged ) { SetBarConfigChanged ( FALSE ); if ( bLocalUpdate ) { bLocalUpdate = FALSE; } // Clear and fill the entire plot region. hrgnRedraw = CreateRectRgn( m_rectPlot.left, m_rectPlot.top, m_rectPlot.right, m_rectPlot.bottom); if (hrgnRedraw) { SelectClipRgn(hDC, hrgnRedraw); Fill(hDC, m_pCtrl->clrBackPlot(), &m_rectPlot); DrawGrid(hDC, 0, (m_rectPlot.right - m_rectPlot.left)); DeleteObject(hrgnRedraw); hrgnRedraw = NULL; } } // Intialize stepper for number of bars to plot BarStepper.Init ( ( m_rectPlot.right - m_rectPlot.left), iNumCounters ); hPenSave = SelectPen ( hDC, GetStockObject(NULL_PEN) ); // Do for all counters pItem = m_pGraph->CounterTree.FirstCounter(); while ( NULL != pItem ) { hr = ERROR_SUCCESS; // Skip highlighted item the first time through if (!(pItem == m_pHiliteItem && bSkip)) { // Get display value if ( sysmonDefaultValue == eValueType ) { if (bLog) { hr = pItem->GetStatistics(&dMax, &dMin, &dAvg, &lCtrStat); } else hr = pItem->GetValue(&dValue, &lCtrStat); } else { if ( sysmonCurrentValue == eValueType ) { hr = pItem->GetValue(&dValue, &lCtrStat); } else { double dAvg; hr = pItem->GetStatistics(&dMax, &dMin, &dAvg, &lCtrStat); switch ( eValueType ) { case sysmonAverage: dValue = dAvg; break; case sysmonMinimum: dValue = dMin; break; case sysmonMaximum: dValue = dMax; break; default: assert (FALSE); } } } // Erase bar if the counter value is invalid. if (SUCCEEDED(hr) && IsSuccessSeverity(lCtrStat)) { // Convert value to pixel units dTemp = dValue * pItem->Scale(); if (dTemp > m_dMax) dTemp = m_dMax; else if (dTemp < m_dMin) dTemp = m_dMin; iValue = m_rectPlot.bottom - (INT)((dTemp - m_dMin) * m_dPixelScale); if ( iValue == m_rectPlot.bottom ) { // Draw single pixel for screen visibility. iValue--; } } else { // The current value is 0. Draw single pixel for screen visibility. iValue = m_rectPlot.bottom - 1; } if ( !bSkip ) { assert ( pItem == m_pHiliteItem ); BarStepper.StepTo ( iHighlightStepNum ); } // Setup left and right edges of bar rectBar.left = m_rectPlot.left + BarStepper.Position(); rectBar.right = m_rectPlot.left + BarStepper.NextPosition(); // If doing an update (never called for log sources) and not drawing the highlighted item if ( bLocalUpdate && !( ( pItem == m_pHiliteItem ) && !bSkip) ) { assert ( !m_bBarConfigChanged ); // Get previous plot value iPrevValue = 0; hr = pItem->HistoryValue(1, &dValue, (ULONG*)&lCtrStat); if (SUCCEEDED(hr) && IsSuccessSeverity(lCtrStat)) { // Convert value to pixel units dTemp = dValue * pItem->Scale(); if (dTemp > m_dMax) dTemp = m_dMax; else if (dTemp < m_dMin) dTemp = m_dMin; iPrevValue = m_rectPlot.bottom - (INT)((dTemp - m_dMin) * m_dPixelScale); if ( iPrevValue == m_rectPlot.bottom ) { // Single pixel was drawn for screen visibility. iPrevValue--; } } else { // The previous value was 0. Single pixel was drawn for screen visibility. iPrevValue = m_rectPlot.bottom - 1; } // If bar has grown (smaller y coord) if (iPrevValue > iValue) { // Draw the new part rectBar.bottom = iPrevValue; rectBar.top = iValue; if ( pItem == m_pHiliteItem) { // Arbitrary 450 (out of 510) chosen as cutoff for white vs. black if ( 450 > RGBToLightness( m_pCtrl->clrBackPlot() ) ) SelectBrush(hDC, GetStockObject(WHITE_BRUSH)); else SelectBrush(hDC, GetStockObject(BLACK_BRUSH)); } else { SelectBrush(hDC, pItem->Brush()); } // Bars are drawn with Null pen, so bottom and right are cropped by 1 pixel. // Add 1 pixel to compensate. Rectangle(hDC, rectBar.left, rectBar.top, rectBar.right + 1, rectBar.bottom + 1); } else if (iPrevValue < iValue) { // Else if bar has shrunk // Add part to be erased to redraw region // Erase to the top of the grid, to eliminate random pixels left over. rectBar.bottom = iValue; rectBar.top = m_rectPlot.top; // set to stop of grid rather than to prevValue hrgnTemp = CreateRectRgn(rectBar.left, rectBar.top, rectBar.right, rectBar.bottom); if (hrgnRedraw && hrgnTemp) { CombineRgn(hrgnRedraw,hrgnRedraw,hrgnTemp,RGN_OR); DeleteObject(hrgnTemp); } else { hrgnRedraw = hrgnTemp; } } } else { // Erase and redraw complete bar // Erase top first // Add part to be erased to redraw region // Erase to the top of the grid, to eliminate random pixels left over. if ( iValue != m_rectPlot.top ) { rectBar.bottom = iValue; rectBar.top = m_rectPlot.top; // set to stop of grid rather than to prevValue hrgnTemp = CreateRectRgn(rectBar.left, rectBar.top, rectBar.right, rectBar.bottom); if (hrgnRedraw && hrgnTemp) { CombineRgn(hrgnRedraw,hrgnRedraw,hrgnTemp,RGN_OR); DeleteObject(hrgnTemp); } else { hrgnRedraw = hrgnTemp; } } // Then draw the bar. rectBar.bottom = m_rectPlot.bottom; rectBar.top = iValue; if ( pItem == m_pHiliteItem) { // Arbitrary 450 (out of 510) chosen as cutoff for white vs. black if ( 450 > RGBToLightness( m_pCtrl->clrBackPlot() ) ) SelectBrush(hDC, GetStockObject(WHITE_BRUSH)); else SelectBrush(hDC, GetStockObject(BLACK_BRUSH)); } else { SelectBrush(hDC, pItem->Brush()); } // Bars are drawn with Null pen, so bottom and right are cropped by 1 pixel. // Add 1 pixel to compensate. Rectangle(hDC, rectBar.left, rectBar.top, rectBar.right + 1, rectBar.bottom + 1); } // Update // Exit loop after plotting highlighted item if (pItem == m_pHiliteItem) break; } else { if ( bSkip ) { // Save position of highlighted item the first time through iHighlightStepNum = BarStepper.StepNum(); } BarStepper.NextPosition(); } pItem = pItem->Next(); // After last item, go back to highlighted item if ( NULL == pItem && NULL != m_pHiliteItem ) { pItem = m_pHiliteItem; bSkip = FALSE; } } // Do for all counters // If redraw region accumulated, erase and draw grid lines if (hrgnRedraw) { SelectClipRgn(hDC, hrgnRedraw); Fill(hDC, m_pCtrl->clrBackPlot(), &m_rectPlot); DrawGrid(hDC, 0, (m_rectPlot.right - m_rectPlot.left)); DeleteObject(hrgnRedraw); } SelectObject(hDC, hPenSave); } } void CGraphDisp::SizeComponents(HDC hDC, PRECT pRect) { INT iStepNum; INT iScaleWidth; INT iTitleHeight; INT iAxisTitleWidth; RECT rectScale; SIZE size; INT iWidth; INT i; static INT aiWidthTable[] = {20,50,100,150,300,500,1000000}; static INT aiTicTable[] = {0,2,4,5,10,20,25}; m_rect = *pRect; // if no space, return if (m_rect.right <= m_rect.left || m_rect.bottom - m_rect.top <= 0) return; // For now use the horizontal font height for both horizontal and vertical text // because the GetTextExtentPoint32 is returning the wrong height for vertical text SelectFont(hDC, m_pCtrl->Font()); GetTextExtentPoint32(hDC, TEXT("Sample"), 6, &size); if (m_pGraph->Options.pszGraphTitle != NULL) { //SelectFont(hDC, m_pCtrl->Font()) ; //GetTextExtentPoint32(hDC, m_pGraph->Options.pszGraphTitle, // lstrlen(m_pGraph->Options.pszGraphTitle), &size); iTitleHeight = size.cy + TEXT_MARGIN; } else iTitleHeight = 0; if (m_pGraph->Options.pszYaxisTitle != NULL && m_hFontVertical != NULL) { //SelectFont(hDC, m_hFontVertical); //GetTextExtentPoint32(hDC, m_pGraph->Options.pszYaxisTitle, // lstrlen(m_pGraph->Options.pszYaxisTitle), &size); iAxisTitleWidth = size.cy + TEXT_MARGIN; } else iAxisTitleWidth = 0; if (m_pGraph->Options.bLabelsChecked) { //SelectFont(hDC, m_pCtrl->Font()); iScaleWidth = m_pGraph->Scale.GetWidth(hDC); } else iScaleWidth = 0; SetRect(&rectScale, pRect->left + iAxisTitleWidth, pRect->top + iTitleHeight, pRect->left + iAxisTitleWidth + iScaleWidth, pRect->bottom); m_pGraph->Scale.SetRect(&rectScale); // Just to set grid line positions SetRect(&m_rectPlot, pRect->left + iScaleWidth + iAxisTitleWidth + BORDER, pRect->top + iTitleHeight + BORDER, pRect->right - BORDER, pRect->bottom - BORDER); // Reinitialize steppers for new width iWidth = m_rectPlot.right - m_rectPlot.left; iStepNum = m_pGraph->TimeStepper.StepNum(); m_pGraph->TimeStepper.Init(iWidth, m_pGraph->History.nMaxSamples - 2); m_pGraph->TimeStepper.StepTo(iStepNum); iStepNum = m_pGraph->LogViewStartStepper.StepNum(); m_pGraph->LogViewStartStepper.Init(iWidth, m_pGraph->History.nMaxSamples - 2); m_pGraph->LogViewStartStepper.StepTo(iStepNum); iStepNum = m_pGraph->LogViewStopStepper.StepNum(); m_pGraph->LogViewStopStepper.Init(iWidth, m_pGraph->History.nMaxSamples - 2); m_pGraph->LogViewStopStepper.StepTo(iStepNum); // Find best grid count for this width for (i=0; iWidth > aiWidthTable[i]; i++) {}; m_GridStepper.Init(iWidth, aiTicTable[i]); // Compute conversion factors for plot, hit test. m_dMin = (double)m_pGraph->Options.iVertMin; m_dMax = (double)m_pGraph->Options.iVertMax; m_dPixelScale = (double)(m_rectPlot.bottom - m_rectPlot.top) / (m_dMax - m_dMin); } void CGraphDisp::DrawTimeLine(HDC hDC, INT x) { HPEN hPenSave; // No time line for log playback if (m_pCtrl->IsLogSource()) return; x += m_rectPlot.left + 1; if ( m_clrCurrentTimeBar != m_pCtrl->clrTimeBar() ) { LOGBRUSH lbrush; m_clrCurrentTimeBar = m_pCtrl->clrTimeBar(); DeleteObject ( m_hPenTimeBar ); // When called from Update(), DrawTimeLine is called after the clipping region // is deactivated. Create a geometric pen in order to specify flat end cap style. // This eliminates any extra pixels drawn at the end. lbrush.lbStyle = BS_SOLID; lbrush.lbColor = m_clrCurrentTimeBar; lbrush.lbHatch = 0; m_hPenTimeBar = ExtCreatePen ( PS_GEOMETRIC | PS_SOLID | PS_ENDCAP_FLAT, 2, &lbrush, 0, NULL ); // if can't do it, use a stock object (this can't fail) if (m_hPenTimeBar == NULL) m_hPenTimeBar = (HPEN)GetStockObject(BLACK_PEN); } hPenSave = SelectPen ( hDC, m_hPenTimeBar ); MoveToEx ( hDC, x, m_rectPlot.top, NULL ); // Specify 1 less pixel. All fills and clip regions clip bottom and // right pixels, so match their behavior. LineTo ( hDC, x, m_rectPlot.bottom - 1 ); SelectObject(hDC, hPenSave); } void CGraphDisp::DrawStartStopLine(HDC hDC, INT x) { HPEN hPenSave; // Log view start/stop lines only for log playback if (!m_pCtrl->IsLogSource()) return; if ( x > 0 && x < ( m_rectPlot.right - m_rectPlot.left ) ) { x += m_rectPlot.left; if ( m_clrCurrentGrid != m_pCtrl->clrGrid() ) { m_clrCurrentGrid = m_pCtrl->clrGrid(); DeleteObject ( m_hPenGrid ); m_hPenGrid = CreatePen(PS_SOLID, 1, m_clrCurrentGrid ); // if can't do it, use a stock object (this can't fail) if (m_hPenGrid == NULL) m_hPenGrid = (HPEN)GetStockObject(BLACK_PEN); } hPenSave = SelectPen(hDC, m_hPenGrid); MoveToEx(hDC, x, m_rectPlot.top, NULL); LineTo(hDC, x, m_rectPlot.bottom); SelectObject(hDC, hPenSave); } } void CGraphDisp::ChangeFont( HDC hDC ) { TEXTMETRIC TextMetrics, newTextMetrics; LOGFONT LogFont; HFONT hFontOld; // Select the new font hFontOld = SelectFont(hDC, m_pCtrl->Font()); // Get attributes GetTextMetrics(hDC, &TextMetrics); // Create LOGFONT for vertical font with same attributes LogFont.lfHeight = TextMetrics.tmHeight; LogFont.lfWidth = 0; LogFont.lfOrientation = LogFont.lfEscapement = 90*10; LogFont.lfWeight = TextMetrics.tmWeight; LogFont.lfStrikeOut = TextMetrics.tmStruckOut; LogFont.lfUnderline = TextMetrics.tmUnderlined; LogFont.lfItalic = TextMetrics.tmItalic; LogFont.lfCharSet = TextMetrics.tmCharSet; LogFont.lfPitchAndFamily = (BYTE)(TextMetrics.tmPitchAndFamily & 0xF0); GetTextFace(hDC, LF_FACESIZE, LogFont.lfFaceName); // Force a truetype font, because raster fonts can't rotate LogFont.lfOutPrecision = OUT_TT_ONLY_PRECIS; LogFont.lfClipPrecision = CLIP_DEFAULT_PRECIS; LogFont.lfQuality = DEFAULT_QUALITY; // Release the current font if (m_hFontVertical != NULL) DeleteObject(m_hFontVertical); // Create the font and save handle locally m_hFontVertical = CreateFontIndirect(&LogFont); SelectFont(hDC, m_hFontVertical); GetTextMetrics(hDC, &newTextMetrics); SelectFont(hDC, hFontOld); } PCGraphItem CGraphDisp::GetItemInLineGraph ( SHORT xPos, SHORT yPos ) { PCGraphItem pItem = NULL; PCGraphItem pReturn = NULL; INT iPrevStepNum; POINT ptPrev; POINT ptNext; POINTS ptMouse; CStepper locStepper; INT iHistIndex; BOOL bLog; BOOL bLogMultiVal; BOOL bFound = FALSE; INT yPosPrev[3]; INT yPosNext[3]; pItem = m_pCtrl->FirstCounter(); bLog = m_pCtrl->IsLogSource(); bLogMultiVal = bLog && !DisplaySingleLogSampleValue(); // Items exist? if (pItem != NULL) { locStepper = m_pGraph->TimeStepper; locStepper.Reset(); iPrevStepNum = locStepper.PrevStepNum(xPos - m_rectPlot.left); locStepper.StepTo(iPrevStepNum); ptPrev.x = m_rectPlot.left + locStepper.Position(); ptNext.x = m_rectPlot.left + locStepper.NextPosition(); ptMouse.x = xPos; ptMouse.y = yPos; // Item within rectangle? if ( iPrevStepNum > -1 ) { // Determine the history index of the preceding step. if ( iPrevStepNum <= m_pGraph->TimeStepper.StepNum() ) { iHistIndex = m_pGraph->TimeStepper.StepNum() - iPrevStepNum; } else { iHistIndex = m_pGraph->TimeStepper.StepNum() + (m_pGraph->TimeStepper.StepCount() - iPrevStepNum); } while ( (pItem != NULL) && !bFound ) { // Calculate y position of this value to compare against // y position of hit point. if ( CalcYPosition ( pItem, iHistIndex, bLog, yPosPrev ) ) { if ( iPrevStepNum < locStepper.StepCount() ) { if ( CalcYPosition ( pItem, iHistIndex - 1, bLog, yPosNext ) ) { ptPrev.y = yPosPrev[0]; ptNext.y = yPosNext[0]; bFound = HitTestLine( ptPrev, ptNext, ptMouse, eHitRegion ); // For log files, also check the vertical line from min to max // for the closest step. if ( !bFound && bLogMultiVal ) { INT iTemp = ptNext.x - ptPrev.x; iTemp = iTemp / 2; if ( ptMouse.x <= ( ptPrev.x + iTemp/2 ) ) { bFound = (( yPosPrev[2] - eHitRegion < yPos ) && ( yPos < yPosPrev[1] + eHitRegion )); } else { bFound = (( yPosNext[2] - eHitRegion < yPos ) && ( yPos < yPosNext[1] + eHitRegion )); } } } } else { // At the end, so just check the final point. if ( !bLogMultiVal ) { bFound = (( yPosPrev[0] - eHitRegion < yPos ) && ( yPos < yPosPrev[0] + eHitRegion )); } else { bFound = (( yPosPrev[2] - eHitRegion < yPos ) && ( yPos < yPosPrev[1] + eHitRegion )); } } } if ( bFound ) pReturn = pItem; else pItem = pItem->Next(); } } } return pReturn; } PCGraphItem CGraphDisp::GetItemInBarGraph ( SHORT xPos, SHORT /* yPos */ ) { PCGraphItem pItem = NULL; pItem = m_pCtrl->FirstCounter(); // Items exist? if (pItem != NULL) { CStepper BarStepper; INT iNumCounters = m_pGraph->CounterTree.NumCounters(); INT iCount; INT iHitStep; // Intialize stepper for number of bars in plot BarStepper.Init ( ( m_rectPlot.right - m_rectPlot.left), iNumCounters ); iHitStep = BarStepper.PrevStepNum ( xPos - m_rectPlot.left ); assert ( -1 != iHitStep ); // Find the counter displayed in the hit step. for ( iCount = 0; ( iCount < iHitStep ) && ( pItem != NULL ); iCount++ ) { pItem = pItem->Next(); } } return pItem; } PCGraphItem CGraphDisp::GetItem( INT xPos, INT yPos ) { PCGraphItem pReturn = NULL; if ( ( m_pGraph->Options.iVertMax > m_pGraph->Options.iVertMin) && ( yPos >= m_rectPlot.top ) && ( yPos <= m_rectPlot.bottom ) && ( xPos >= m_rectPlot.left ) && ( xPos <= m_rectPlot.right ) ) { m_pCtrl->LockCounterData(); if ( LINE_GRAPH == m_pGraph->Options.iDisplayType ) { assert ( SHRT_MAX >= xPos ); assert ( SHRT_MAX >= yPos ); pReturn = GetItemInLineGraph( (SHORT)xPos, (SHORT)yPos ); } else if ( BAR_GRAPH == m_pGraph->Options.iDisplayType ) { assert ( SHRT_MAX >= xPos ); assert ( SHRT_MAX >= yPos ); pReturn = GetItemInBarGraph( (SHORT)xPos, (SHORT)yPos ); } m_pCtrl->UnlockCounterData(); } return pReturn; }