// 3DMeterCtrl.cpp : implementation file
//
#include "stdafx.h"
#include "math.h"
#include "3DMeterCtrl.h"
#include "MemDC.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
/////////////////////////////////////////////////////////////////////////////
// C3DMeterCtrl
C3DMeterCtrl::C3DMeterCtrl()
{
m_dCurrentValue = 0.0;
m_dMaxValue = 5.0;
m_dMinValue = -5.0;
m_nScaleDecimals = 1;
m_nValueDecimals = 3;
m_strUnits.Format("Volts");
m_colorNeedle = RGB(255, 0, 0);
}
C3DMeterCtrl::~C3DMeterCtrl()
{
if ((m_pBitmapOldBackground) && (m_bitmapBackground.GetSafeHandle()) && (m_dcBackground.GetSafeHdc()))
m_dcBackground.SelectObject(m_pBitmapOldBackground);
}
BEGIN_MESSAGE_MAP(C3DMeterCtrl, CStatic)
//{{AFX_MSG_MAP(C3DMeterCtrl)
ON_WM_PAINT()
ON_WM_SIZE()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
/////////////////////////////////////////////////////////////////////////////
// C3DMeterCtrl message handlers
void C3DMeterCtrl::OnPaint()
{
CPaintDC dc(this); // device context for painting
// Find out how big we are
GetClientRect (&m_rectCtrl);
// make a memory dc
CMemDC memDC(&dc, &m_rectCtrl);
// set up a memory dc for the background stuff
// if one isn't being used
if ((m_dcBackground.GetSafeHdc() == NULL) || (m_bitmapBackground.m_hObject == NULL))
{
m_dcBackground.CreateCompatibleDC(&dc);
m_bitmapBackground.CreateCompatibleBitmap(&dc, m_rectCtrl.Width(), m_rectCtrl.Height());
m_pBitmapOldBackground = m_dcBackground.SelectObject(&m_bitmapBackground);
// Fill this bitmap with the background.
// Note: This requires some serious drawing and calculating,
// therefore it is drawn "once" to a bitmap and
// the bitmap is stored and blt'd when needed.
DrawMeterBackground(&m_dcBackground, m_rectCtrl);
}
// drop in the background
memDC.BitBlt(0, 0, m_rectCtrl.Width(), m_rectCtrl.Height(), &m_dcBackground, 0, 0, SRCCOPY);
// add the needle to the background
DrawNeedle(&memDC);
// add the value to the background
DrawValue(&memDC);
}
void C3DMeterCtrl::DrawValue(CDC *pDC)
{
CFont *pFontOld;
CString strTemp;
// Pick up the font.
// Note: the font was determined in the drawing
// of the background
pFontOld = pDC->SelectObject(&m_fontValue);
// set the colors based on the system colors
pDC->SetTextColor(m_colorText);
pDC->SetBkColor(m_colorButton);
// draw the text in the recessed rectangle
pDC->SetTextAlign(TA_CENTER|TA_BASELINE);
strTemp.Format("%.*f", m_nValueDecimals, m_dCurrentValue);
pDC->TextOut(m_nValueCenter, m_nValueBaseline, strTemp);
// restore the color and the font
pDC->SetBkColor(m_colorWindow);
pDC->SelectObject(pFontOld);
}
void C3DMeterCtrl::UpdateNeedle(double dValue)
{
m_dCurrentValue = dValue;
Invalidate();
}
void C3DMeterCtrl::DrawNeedle(CDC *pDC)
{
int nResult;
double dAngleRad;
double dTemp;
CBrush brushFill, *pBrushOld;
CPen penDraw, *pPenOld;
CPoint pointNeedle[3];
// This function draws a triangular needle.
// The base of the needle is a horizontal line
// that runs through the center point of the arcs
// that make up the face of the meter.
// The tip of the needle is at an angle that is
// calculated based on the current value and the scale.
// The needle is constructed as a 3-point polygon
// (i.e. triangle). The triangle is drawn to the
// screen based on a clipping region that is derived
// from the meter face. See "DrawMeterBackground".
// calculate the first and last points of the needle.
pointNeedle[0].x = m_nBottomCX + m_nBottomRadius/20;
pointNeedle[0].y = m_nBottomCY;
pointNeedle[2].x = m_nBottomCX - m_nBottomRadius/20;
pointNeedle[2].y = m_nBottomCY;
// calculate the angle for the tip of the needle
dAngleRad = (m_dCurrentValue-m_dMinValue)*(m_dRightAngleRad-m_dLeftAngleRad)/(m_dMaxValue-m_dMinValue) + m_dLeftAngleRad;
// if the angle is beyond the meter, draw the needle
// at the limit (as if it is "pegged")
dAngleRad = max(dAngleRad, m_dRightAngleRad);
dAngleRad = min(dAngleRad, m_dLeftAngleRad);
// calculate the X position of the tip
dTemp = m_nBottomCX + m_nTopRadius*cos(dAngleRad);
pointNeedle[1].x = ROUND(dTemp);
// calculate the Y position of the tip
dTemp = m_nBottomCY - m_nTopRadius*sin(dAngleRad);
pointNeedle[1].y = ROUND(dTemp);
// select the clipping region based on the meter
pDC->SelectClipRgn(&m_rgnBoundary);
// create a pen and brush based on the needle color
brushFill.CreateSolidBrush(m_colorNeedle);
penDraw.CreatePen(PS_SOLID, 1, m_colorNeedle);
// select the pen and brush
pPenOld = pDC->SelectObject(&penDraw);
pBrushOld = pDC->SelectObject(&brushFill);
// draw the needle
pDC->Polygon(pointNeedle, 3);
// restore the clipping region
nResult = pDC->SelectClipRgn(NULL);
// restore the pen and brush
pDC->SelectObject(pPenOld);
pDC->SelectObject(pBrushOld);
}
void C3DMeterCtrl::DrawMeterBackground(CDC *pDC, CRect &rect)
{
int i, nAngleDeg, nRef;
int nHeight;
int nHalfPoints;
int nStartAngleDeg, nEndAngleDeg;
int nTickDeg;
int nAngleIncrementDeg;
double dTemp, dAngleRad, dX, dY;
double dRadPerDeg;
CString strTemp;
CPoint pointRecess[BOUNDARY_POINTS];
CBrush brushFill, *pBrushOld;
CFont *pFontOld;
CPen penDraw, *pPenOld;
TEXTMETRIC tm;
// determine the centerpoint of the radii for
// the meter face.
m_nBottomCX = (rect.left+rect.right)/2;
m_nBottomCY = rect.bottom - rect.Height()/16;
// determine the radii for the top of the meter
// and the bottom of the meter
m_nTopRadius = rect.Height()*6/8;
m_nBottomRadius = m_nTopRadius/2;
// Radians per Degree
// This helps me lay things out in degrees
// which are more intuitive than radians.
dRadPerDeg = 4.0*atan(1.0)/180.0;
// set the left and right limits for the meter face
nStartAngleDeg = 60;
nEndAngleDeg = 120;
nTickDeg = 15;
// this is the density of points along the arcs
nAngleIncrementDeg = 5;
// convert these to radians
// for computer (rather than human) use!
m_dLeftAngleRad = nEndAngleDeg*dRadPerDeg;
m_dRightAngleRad = nStartAngleDeg*dRadPerDeg;
// construct the meter face region
// This is a polygon starting at the top right of
// the meter face and moving across the top arc.
nRef = 0;
for (nAngleDeg=nStartAngleDeg; nAngleDeg<=nEndAngleDeg; nAngleDeg+=nAngleIncrementDeg)
{
// determine the current angle in radians
dAngleRad = nAngleDeg*dRadPerDeg;
// determine the X position
dTemp = m_nBottomCX + m_nTopRadius*cos(dAngleRad);
m_pointBoundary[nRef].x = ROUND(dTemp);
// determine the Y position
dTemp = m_nBottomCY - m_nTopRadius*sin(dAngleRad);
m_pointBoundary[nRef].y = ROUND(dTemp);
nRef++;
}
// at this point we have constructed the entire
// top arc of the meter face
nHalfPoints = nRef; // hold onto this for later use
// now add points to the polygon starting at the
// left side of the lower arc.
for (nAngleDeg=nEndAngleDeg; nAngleDeg>=nStartAngleDeg; nAngleDeg-=nAngleIncrementDeg)
{
dAngleRad = nAngleDeg*dRadPerDeg;
dTemp = m_nBottomCX + m_nBottomRadius*cos(dAngleRad);
m_pointBoundary[nRef].x = ROUND(dTemp);
dTemp = m_nBottomCY - m_nBottomRadius*sin(dAngleRad);
m_pointBoundary[nRef].y = ROUND(dTemp);
nRef++;
}
// Now construct a polygon that is just outside
// the meter face to use in drawing a "recess"
// around the meter face.
for (i=0; i<nRef; i++)
{
pointRecess[i].x = m_pointBoundary[i].x;
pointRecess[i].y = m_pointBoundary[i].y-1;
}
pointRecess[0].x = pointRecess[0].x + 1;
pointRecess[nRef-1].x = pointRecess[nRef-1].x - 1;
for (i=nHalfPoints; i<nRef; i++)
{
pointRecess[i].x = m_pointBoundary[i].x;
pointRecess[i].y = m_pointBoundary[i].y+1;
}
pointRecess[nHalfPoints].x = poin