// OScopeCtrl.cpp : implementation file//
#include "stdafx.h"
#include "math.h"
#include "OScopeCtrl.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__ ;
#endif
/////////////////////////////////////////////////////////////////////////////
// COScopeCtrl
COScopeCtrl::COScopeCtrl()
{
// since plotting is based on a LineTo for each new point
// we need a starting point (i.e. a "previous" point)
// use 0.0 as the default first point.
// these are public member variables, and can be changed outside
// (after construction). Therefore m_perviousPosition could be set to
// a more appropriate value prior to the first call to SetPosition.
m_dPreviousPosition = 0.0 ;
// public variable for the number of decimal places on the y axis
m_nYDecimals = 3 ;
// set some initial values for the scaling until "SetRange" is called.
// these are protected varaibles and must be set with SetRange
// in order to ensure that m_dRange is updated accordingly
m_dLowerLimit = -10.0 ;
m_dUpperLimit = 10.0 ;
m_dRange = m_dUpperLimit - m_dLowerLimit ; // protected member variable
// m_nShiftPixels determines how much the plot shifts (in terms of pixels)
// with the addition of a new data point
m_nShiftPixels = 4 ;
m_nHalfShiftPixels = m_nShiftPixels/2 ; // protected
m_nPlotShiftPixels = m_nShiftPixels + m_nHalfShiftPixels ; // protected
// background, grid and data colors
// these are public variables and can be set directly
m_crBackColor = RGB( 0, 0, 0) ; // see also SetBackgroundColor
m_crGridColor = RGB( 0, 255, 255) ; // see also SetGridColor
m_crPlotColor = RGB(255, 255, 255) ; // see also SetPlotColor
// protected variables
m_penPlot.CreatePen(PS_SOLID, 0, m_crPlotColor) ;
m_brushBack.CreateSolidBrush(m_crBackColor) ;
// public member variables, can be set directly
m_strXUnitsString.Format("Samples") ; // can also be set with SetXUnits
m_strYUnitsString.Format("Y units") ; // can also be set with SetYUnits
// protected bitmaps to restore the memory DC's
m_pbitmapOldGrid = NULL ;
m_pbitmapOldPlot = NULL ;
} // COScopeCtrl
/////////////////////////////////////////////////////////////////////////////
COScopeCtrl::~COScopeCtrl()
{
// just to be picky restore the bitmaps for the two memory dc's
// (these dc's are being destroyed so there shouldn't be any leaks)
if (m_pbitmapOldGrid != NULL)
m_dcGrid.SelectObject(m_pbitmapOldGrid) ;
if (m_pbitmapOldPlot != NULL)
m_dcPlot.SelectObject(m_pbitmapOldPlot) ;
} // ~COScopeCtrl
BEGIN_MESSAGE_MAP(COScopeCtrl, CWnd)
//{{AFX_MSG_MAP(COScopeCtrl)
ON_WM_PAINT()
ON_WM_SIZE()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
/////////////////////////////////////////////////////////////////////////////
// COScopeCtrl message handlers
/////////////////////////////////////////////////////////////////////////////
BOOL COScopeCtrl::Create(DWORD dwStyle, const RECT& rect,
CWnd* pParentWnd, UINT nID)
{
BOOL result ;
static CString className = AfxRegisterWndClass(CS_HREDRAW | CS_VREDRAW) ;
result = CWnd::CreateEx(WS_EX_CLIENTEDGE | WS_EX_STATICEDGE,
className, NULL, dwStyle,
rect.left, rect.top, rect.right-rect.left, rect.bottom-rect.top,
pParentWnd->GetSafeHwnd(), (HMENU)nID) ;
if (result != 0)
InvalidateCtrl() ;
return result ;
} // Create
/////////////////////////////////////////////////////////////////////////////
void COScopeCtrl::SetRange(double dLower, double dUpper, int nDecimalPlaces)
{
ASSERT(dUpper > dLower) ;
m_dLowerLimit = dLower ;
m_dUpperLimit = dUpper ;
m_nYDecimals = nDecimalPlaces ;
m_dRange = m_dUpperLimit - m_dLowerLimit ;
m_dVerticalFactor = (double)m_nPlotHeight / m_dRange ;
// clear out the existing garbage, re-start with a clean plot
InvalidateCtrl() ;
} // SetRange
/////////////////////////////////////////////////////////////////////////////
void COScopeCtrl::SetXUnits(CString string)
{
m_strXUnitsString = string ;
// clear out the existing garbage, re-start with a clean plot
InvalidateCtrl() ;
} // SetXUnits
/////////////////////////////////////////////////////////////////////////////
void COScopeCtrl::SetYUnits(CString string)
{
m_strYUnitsString = string ;
// clear out the existing garbage, re-start with a clean plot
InvalidateCtrl() ;
} // SetYUnits
/////////////////////////////////////////////////////////////////////////////
void COScopeCtrl::SetGridColor(COLORREF color)
{
m_crGridColor = color ;
// clear out the existing garbage, re-start with a clean plot
InvalidateCtrl() ;
} // SetGridColor
/////////////////////////////////////////////////////////////////////////////
void COScopeCtrl::SetPlotColor(COLORREF color)
{
m_crPlotColor = color ;
m_penPlot.DeleteObject() ;
m_penPlot.CreatePen(PS_SOLID, 0, m_crPlotColor) ;
// clear out the existing garbage, re-start with a clean plot
InvalidateCtrl() ;
} // SetPlotColor
/////////////////////////////////////////////////////////////////////////////
void COScopeCtrl::SetBackgroundColor(COLORREF color)
{
m_crBackColor = color ;
m_brushBack.DeleteObject() ;
m_brushBack.CreateSolidBrush(m_crBackColor) ;
// clear out the existing garbage, re-start with a clean plot
InvalidateCtrl() ;
} // SetBackgroundColor
/////////////////////////////////////////////////////////////////////////////
void COScopeCtrl::InvalidateCtrl()
{
// There is a lot of drawing going on here - particularly in terms of
// drawing the grid. Don't panic, this is all being drawn (only once)
// to a bitmap. The result is then BitBlt'd to the control whenever needed.
int i ;
int nCharacters ;
int nTopGridPix, nMidGridPix, nBottomGridPix ;
CPen *oldPen ;
CPen solidPen(PS_SOLID, 0, m_crGridColor) ;
CFont axisFont, yUnitFont, *oldFont ;
CString strTemp ;
// in case we haven't established the memory dc's
CClientDC dc(this) ;
// if we don't have one yet, set up a memory dc for the grid
if (m_dcGrid.GetSafeHdc() == NULL)
{
m_dcGrid.CreateCompatibleDC(&dc) ;
m_bitmapGrid.CreateCompatibleBitmap(&dc, m_nClientWidth, m_nClientHeight) ;
m_pbitmapOldGrid = m_dcGrid.SelectObject(&m_bitmapGrid) ;
}
m_dcGrid.SetBkColor (m_crBackColor) ;
// fill the grid background
m_dcGrid.FillRect(m_rectClient, &m_brushBack) ;
// draw the plot rectangle:
// determine how wide the y axis scaling values are
nCharacters = abs((int)log10(fabs(m_dUpperLimit))) ;
nCharacters = max(nCharacters, abs((int)log10(fabs(m_dLowerLimit)))) ;
// add the units digit, decimal point and a minus sign, and an extra space
// as well as the number of decimal places to display
nCharacters = nCharacters + 4 + m_nYDecimals ;
// adjust the plot rectangle dimensions
// assume 6 pixels per character (this may need to be adjusted)
m_rectPlot.left = m_rectClient.left + 6*(nCharacters) ;
m_nPlotWidth = m_rectPlot.Width() ;
// draw the plot rectangle
oldPen = m_dcGrid.SelectObject (&solidPen) ;
m_dcGrid.MoveTo (m_rectPlot.left, m_rectPlot.top) ;
m_dcGrid.LineTo (m_rectPlot.right+1, m_rectPlot.top) ;
m_dcGrid.LineTo (m_rectPlot.right+1, m_rectPlot.bottom+1) ;
m_dcGrid.LineTo (m_rectPlot.left, m_rectPlot.bottom+1) ;
m_dcGrid.LineTo (m_rectPlot.left, m_rectPlot.top) ;
m_dcGrid.SelectObject (oldPen) ;
// draw the dotted lines,
// use SetPixel instead of a dotted pen - this allows for a
// finer dotted line and a more "technical" look
nMidGridPix = (m_rectPlot.top + m_rectPlot.bottom)/2 ;
nTopGridPix = nMidGridPix - m_nPlotHeight/4 ;
nBottomGridPix = nMidGridPix + m_nPlotHeight/4 ;
for (i=m_rectPlot.left; i<m_rectPlot.right; i+=4)
{
m_dcGrid.SetPixel (i, nTopGridPix, m_crGridColor) ;
m_dcGrid.SetPixel (i, nMidGridPix, m_crGridColor) ;
m_dcGrid.SetPixel (i, nBottomGridPix, m_crGridColor) ;
}
// create some fonts (horizontal and vertical)
// use a height of 14 pixels and 300 weight
// (these may need to be adjusted depending on the display)
axisFont.CreateFont (14, 0, 0, 0, 300,
FALSE, FALSE, 0, ANSI_CHARSET,
OUT_DEFAULT_PRECIS,
CLIP_DEFAULT_PRECIS,
DEFAULT_QUALITY,
DEFAULT_PITCH|FF_SWISS, "Arial") ;
yUnitFont.CreateFont (14, 0, 900, 0, 300,
FALSE, FALSE, 0, ANSI_CHARSET,
OUT_DEFAULT_PRECIS,
CLIP_DEFAULT_PRECIS,
DEFAULT_QUALITY,
DEFAULT_PITCH|FF_SWISS, "Arial") ;
// grab the horizontal font
oldFont = m_dcGrid.SelectObject(&axisFont) ;
// y max
m_dcGrid.SetTextColor (m_crGridColor) ;
m_dcGrid.SetTextAlign (TA_RIGHT|TA_TOP) ;
strTemp.Format ("%.*lf", m_nYDecimals, m_dUpperLimit) ;
m_dcGrid.TextOut (m_rectPlot.left-4, m_rectPlot.top, strTemp) ;
// y min
m_dcGrid.SetTextAlign (TA_RIGHT|TA_BASELINE) ;
strTemp.Format ("%.*lf", m_nYDecimals, m_dLowerLimit) ;
m_dcGrid.TextOut (m_rectPlot.left-4, m_rectPlot.bottom, strTemp) ;
// x min
m_dcGrid.SetTextAlign (TA_LEFT|TA_TOP) ;
m_dcGrid.TextOut (m_rectPlot.left, m_rectPlot.bottom+4, "0") ;
// x max
m_dcGrid.SetTextAlign (TA_RIGHT|TA_TOP) ;
strTemp.Format ("%d", m_nPlotWidth/m_nShiftPixels) ;
m_dcGrid.TextOut (m_rectPlot.right, m_rectPlot.bottom+4, strTemp) ;
// x units
m_dcGrid.SetTextAlign (TA_CENTER|TA_TOP) ;
m_dcGrid.TextOut ((m_rectPlot.left+m_rectPlot.right)/2,
m_rectPlot.bottom+4, m_strXUnitsString) ;
// restore the font
m_dcGrid.SelectObject(oldFont) ;
// y units
oldFont = m_dcGrid.SelectObject(&yUnitFont) ;
m_dcGrid.SetTextAlign (TA_CENTER|TA_BASELINE) ;
m_dcGrid.TextOut ((m_rectClient.left+m_rectPlot.left)/2,
(m_rectPlot.bottom+m_rectPlot.top)/2, m_strYUnitsString) ;
m_dcGrid.SelectObject(oldFont) ;
// at this point we are done filling the the grid bitmap,
// no more drawing to this bitmap is needed until the setting are changed
// if we don't have one yet, set up a memory dc for the plot
if (m_dcPlot.GetSafeHdc() == NULL)
{
m_dcPlot.CreateCompatibleDC(&dc) ;
m_bitmapPlot.CreateCompatibleBitmap(&dc, m_nClientWidth, m_nClientHeight) ;
m_pbitmapOldPlot = m_dcPlot.SelectObject(&m_bitmapPlot) ;
}
// make sure the plot bitmap is cleared
m_dcPlot.SetBkColor (m_crBackColor) ;
m_dcPlot.FillRect(m_rectClient, &m_brushBack) ;
// finally, force the plot area to redraw
InvalidateRect(m_rectClient) ;
} // InvalidateCtrl
/////////////////////////////////////////////////////////////////////////////
double COScopeCtrl::AppendPoint(double dNewPoint)
{
// append a data point to the plot
// return the previous point
double dPrevious ;
dPrevious = m_dCurrentPosition ;
m_dCurrentPosition = dNewPoint ;
DrawPoint() ;
Invalidate() ;
return dPrevious ;
} // AppendPoint
////////////////////////////////////////////////////////////////////////////
void COScopeCtrl::OnPaint()
{
CPaintDC dc(this) ; // device context for painting
CDC memDC ;
CBitmap memBitmap ;
CBitmap* oldBitmap ; // bitmap originally found in CMemDC
// no real plotting work is performed here,
// just putting the existing bitmaps on the client
// to avoid flicker, establish a memory dc, draw to it
// and then BitBlt it to the client
memDC.CreateCompatibleDC(&dc) ;
memBitmap.CreateCompatibleBitmap(&dc, m_nClientWidth, m_nClientHeight) ;
oldBitmap = (CBitmap *)memDC.SelectObject(&memBitmap) ;
if (memDC.GetSafeHdc() != NULL)
{
// first drop the grid on the memory dc
memDC.BitBlt(0, 0, m_nClientWidth, m_nClientHeight,
&m_dcGrid, 0, 0, SRCCOPY) ;
// now add the plot on top as a "pattern" via SRCPAINT.
// works well with dark background and a light plot
memDC.BitBlt(0, 0, m_nClientWidth, m_nClientHeight,
&m_dcPlot, 0, 0, SRCPAINT) ; //SRCPAINT
// finally send the result to the display
dc.BitBlt(0, 0, m_nClientWidth, m_nClientHeight,
&memDC, 0, 0, SRCCOPY) ;
}
memDC.SelectObject(oldBitmap) ;
} // OnPaint
/////////////////////////////////////////////////////////////////////////////
void COScopeCtrl::DrawPoint()
{
// this does the work of "scrolling" the plot to the left
// and appending a new data point all of the plotting is
// directed to the memory based bitmap associated with m_dcPlot
// the will subsequently be BitBlt'd to the client in OnPaint
int currX, prevX, currY, prevY ;
CPen *oldPen ;
CRect rectCleanUp ;
if (m_dcPlot.GetSafeHdc() != NULL)
{
// shift the plot by BitBlt'ing it to itself
// note: the m_dcPlot covers the entire client
// but we only shift bitmap that is the size
// of the plot rectangle
// grab the right side of the plot (exluding m_nShiftPixels on the left)
// move this grabbed bitmap to the left by m_nShiftPixels
m_dcPlot.BitBlt(m_rectPlot.left, m_rectPlot.top+1,
m_nPlotWidth, m_nPlotHeight, &m_dcPlot,
m_rectPlot.left+m_nShiftPixels, m_rectPlot.top+1,
SRCCOPY) ;
// establish a rectangle over the right side of plot
// which now needs to be cleaned up proir to adding the new point
rectCleanUp = m_rectPlot ;
rectCleanUp.left = rectCleanUp.right - m_nShiftPixels ;
// fill the cleanup area with the background
m_dcPlot.FillRect(rectCleanUp, &m_brushBack) ;
// draw the next line segement
// grab the plotting pen
oldPen = m_dcPlot.SelectObject(&m_penPlot) ;
// move to the previous point
prevX = m_rectPlot.right-m_nPlotShiftPixels ;
prevY = m_rectPlot.bottom -
(long)((m_dPreviousPosition - m_dLowerLimit) * m_dVerticalFactor) ;
m_dcPlot.MoveTo (prevX, prevY) ;
// draw to the current point
currX = m_rectPlot.right-m_nHalfShiftPixels ;
currY = m_rectPlot.bottom -
(long)((m_dCurrentPosition - m_dLowerLimit) * m_dVerticalFactor) ;
m_dcPlot.LineTo (currX, currY) ;
// restore the pen
m_dcPlot.SelectObject(oldPen) ;
// if the data leaks over the upper or lower plot boundaries
// fill the upper and lower leakage with the background
// this will facilitate clipping on an as needed basis
// as opposed to always calling IntersectClipRect
if ((prevY <= m_rectPlot.top) || (currY <= m_rectPlot.top))
m_dcPlot.FillRect(CRect(prevX, m_rectClient.top, currX+1, m_rectPlot.top+1), &m_brushBack) ;
if ((prevY >= m_rectPlot.bottom) || (currY >= m_rectPlot.bottom))
m_dcPlot.FillRect(CRect(prevX, m_rectPlot.bottom+1, currX+1, m_rectClient.bottom+1), &m_brushBack) ;
// store the current point for connection to the next point
m_dPreviousPosition = m_dCurrentPosition ;
}
} // end DrawPoint
/////////////////////////////////////////////////////////////////////////////
void COScopeCtrl::OnSize(UINT nType, int cx, int cy)
{
CWnd::OnSize(nType, cx, cy) ;
// NOTE: OnSize automatically gets called during the setup of the control
GetClientRect(m_rectClient) ;
// set some member variables to avoid multiple function calls
m_nClientHeight = m_rectClient.Height() ;
m_nClientWidth = m_rectClient.Width() ;
// the "left" coordinate and "width" will be modified in
// InvalidateCtrl to be based on the width of the y axis scaling
m_rectPlot.left = 20 ;
m_rectPlot.top = 10 ;
m_rectPlot.right = m_rectClient.right-10 ;
m_rectPlot.bottom = m_rectClient.bottom-25 ;
// set some member variables to avoid multiple function calls
m_nPlotHeight = m_rectPlot.Height() ;
m_nPlotWidth = m_rectPlot.Width() ;
// set the scaling factor for now, this can be adjusted
// in the SetRange functions
m_dVerticalFactor = (double)m_nPlotHeight / m_dRange ;
} // OnSize
/////////////////////////////////////////////////////////////////////////////
void COScopeCtrl::Reset()
{
// to clear the existing data (in the form of a bitmap)
// simply invalidate the entire control
InvalidateCtrl() ;
}