
static char *rcsId() { return "$Id$"; }

#include "stdafx.h"
#include <math.h>
#include "BarMeter.h"

#define ROUND(x) (int)((x) + 0.5 - (double)((x) < 0))

/////////////////////////////////////////////////////////////////////////////
// CBarMeter

IMPLEMENT_DYNCREATE(CBarMeter, CCmdTarget)

COLORREF CBarMeter::nearestColor(COLORREF c)
{
#if 0
   return m_dcGrid.GetNearestColor(c);
   CDC *dc = GetDC();
   c = dc->GetNearestColor(c);
   ReleaseDC(dc);
#endif
   return c;
}

CBarMeter::CBarMeter()
{
   m_dPI = 4.0 * atan(1.0);  // for trig calculations

   // initialized rectangle locations, will be modified on first drawing
   m_rectDraw    = CRect(0, 0, 0, 0);
   m_nRectWidth  = 0;
   m_nRectHeight = 0;

   // draw the whole thing the first time
   m_boolForceRedraw = true;
   m_dRadiansPerValue = 0.0;                // will be modified on first drawing

   // false if we are printing
   m_boolUseBitmaps = true;

   // default unit, scaling and needle position
   m_dMinScale   = -10.0;
   m_dMaxScale   =  10.0;
   m_dAlertScale =  10.0;
   m_dNeedlePos  =   0.0;
   m_strTitle    = "";
   m_strUnit     = "";

   // for numerical values
   m_nFontScale     = 100;
   m_nRangeDecimals = 1;
   m_nValueDecimals = 1;

   // switches
   m_swTitle = true;
   m_swGrid  = true;
   m_swRange = true;
   m_swValue = true;
   m_swUnit  = true;

   // title color
   m_colorTitle     = RGB( 80,  80,  80);
   // normal grid color
   m_colorGrid      = RGB( 96,  96,  96);
   // alert grid color
   m_colorGridAlert = RGB(255,   0,   0);
   // current numerical value color
   m_colorValue     = RGB(  0,   0,   0);
   // background color
   m_colorBGround   = RGB(202, 202, 202);
   // needle color
   m_colorNeedle    = RGB(  0, 155,   0);
   // range color
   m_colorRange     = RGB(  0,   0,   0);
   // value color
   m_colorValue     = RGB(  0,   0,   0);

   // set pen/brush colors
   ActuateColors();
}

// Changed to stop error
CBarMeter::~CBarMeter()
{
   //m_dcGrid.SelectObject(m_pbitmapOldGrid) ;
   //m_dcGrid.DeleteDC() ;

   //m_dcNeedle.SelectObject(m_pbitmapOldNeedle) ;
   //m_dcNeedle.DeleteDC() ;

   //m_bitmapGrid.DeleteObject() ;
   //m_bitmapNeedle.DeleteObject() ;
}

BEGIN_MESSAGE_MAP(CBarMeter, CCmdTarget)
   //{{AFX_MSG_MAP(CBarMeter)
   // NOTE - the ClassWizard will add and remove mapping macros here.
   //}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CBarMeter message handlers

void CBarMeter::ShowMeter(CDC * pDC, CRect rectBorder)
{
// check for a new meter or a resize of the old one.
   // (if the rectangles have changed, then redraw from scratch).
   // If we are printing, always draw from scratch without bitmaps.
   if (m_rectOwner != rectBorder)
      m_boolForceRedraw = true;

   if (m_boolForceRedraw || (pDC->IsPrinting())) {
      m_boolForceRedraw = false;
      // first store the rectangle for the owner
      // and determine the rectangle to draw to
      m_rectOwner = rectBorder;
      if (pDC->IsPrinting()) {
         m_boolUseBitmaps = false;
         m_rectDraw = m_rectOwner;                 // draw directly to the owner
      }
      else {
         m_boolUseBitmaps  = true;
         m_rectDraw.left   = 0;                    // draw to a bitmap rectangle
         m_rectDraw.top    = 0;
         m_rectDraw.right  = rectBorder.Width();
         m_rectDraw.bottom = rectBorder.Height();
      }
      m_nRectWidth  = m_rectDraw.Width();
      m_nRectHeight = m_rectDraw.Height();

      // if we already have a memory dc, destroy it
      // (this occurs for a re-size of the meter)
      if (m_dcGrid.GetSafeHdc()) {
         m_dcGrid.SelectObject(m_pbitmapOldGrid);
         m_dcGrid.DeleteDC();

         m_dcNeedle.SelectObject(m_pbitmapOldNeedle);
         m_dcNeedle.DeleteDC();

         m_bitmapGrid.DeleteObject();
         m_bitmapNeedle.DeleteObject();
      }

      if (m_boolUseBitmaps) {
         // create a memory based dc for drawing the grid
         m_dcGrid.CreateCompatibleDC(pDC);
         m_bitmapGrid.CreateCompatibleBitmap(pDC, m_nRectWidth, m_nRectHeight);
         m_pbitmapOldGrid = m_dcGrid.SelectObject(&m_bitmapGrid);

         // create a memory based dc for drawing the needle
         m_dcNeedle.CreateCompatibleDC(pDC);
         m_bitmapNeedle.CreateCompatibleBitmap(pDC, m_nRectWidth, m_nRectHeight);
         m_pbitmapOldNeedle = m_dcNeedle.SelectObject(&m_bitmapNeedle);
      }
      else {                              // no bitmaps, draw to the destination
         // use the destination dc for the grid
         m_dcGrid.m_hDC = pDC->m_hDC;
         m_dcGrid.m_hAttribDC = pDC->m_hAttribDC;

         // use the destination dc for the grid
         m_dcNeedle.m_hDC = pDC->m_hDC;
         m_dcNeedle.m_hAttribDC = pDC->m_hAttribDC;
      }

      // draw the grid in the "grid dc"
      DrawGrid();
      // draw the needle in the "needle dc"
      DrawNeedle();
   }

   // display the new image, combining the needle with the grid
   if (m_boolUseBitmaps)
      ShowMeterImage(pDC);
}

//////////////////////////////////////////////////////
void CBarMeter::UpdateNeedle(CDC * pDC, double dPos)
{
   // do not support updates if we are not working with
   // bitmaps images
   if (!m_boolUseBitmaps) {
      MessageBox("Bitmaps not supported");
      return;
   }

   // must have created the grid if we are going to
   // update the needle (the needle locations are
   // calculateed based on the grid)
   if (!m_dcGrid.GetSafeHdc())
      return;

   // if the needle hasn't changed, don't bother updating
   if (m_dNeedlePos == dPos)
      return;

   // store the position in the member variable
   // for availability elsewhere
   m_dNeedlePos = dPos;

   // draw the new needle image
   DrawNeedle();

   // combine the needle with the grid and display the result
   if (m_boolUseBitmaps)
      ShowMeterImage(pDC);
}

//////////////////////////////////////////
void CBarMeter::DrawGrid()
{
   double         dTemp;

   CPen          *pPenOld;
   CBrush        *pBrushOld;
   CFont         *pFontOld;
   CString        tempString;

   bool           disable_title;
   bool           disable_range;
   bool           disable_unit;

   m_rectGfx = m_rectDraw;

   ///////////////////////
   // clear background //
   ///////////////////////
   // new pen / brush
   pPenOld = NULL;
   pBrushOld = NULL;
   if (m_PenG_BGround.m_hObject)
      pPenOld = m_dcGrid.SelectObject(&m_PenG_BGround);
   if (m_BrushG_BGround.m_hObject)
      pBrushOld = m_dcGrid.SelectObject(&m_BrushG_BGround);

   m_dcGrid.Rectangle(m_rectGfx);
   //m_rectGfx.DeflateRect(3, 3);
   //m_dcGrid.Draw3dRect(m_rectGfx, nearestColor((~RGB(255, 255, 255)) & 0xFFFFFF), nearestColor((~RGB(0, 0, 0)) & 0xFFFFFF));
   //m_rectGfx.DeflateRect(1, 1);
   //m_dcGrid.Draw3dRect(m_rectGfx, nearestColor((~RGB(220, 220, 220)) & 0xFFFFFF), nearestColor((~RGB(127, 127, 127)) & 0xFFFFFF));
   m_rectGfx.DeflateRect(4, 4);
return;
   // old pen / brush
   if (pPenOld  ) m_dcGrid.SelectObject(pPenOld  );
   if (pBrushOld) m_dcGrid.SelectObject(pBrushOld);

   ///////////////////////
   // check size //
   ///////////////////////
   disable_title = false;
   disable_range = false;
   disable_unit  = false;
   if ((m_rectGfx.Height() < 50) || (m_rectGfx.Width() < 50)) {
      disable_title = true;
      disable_range = true;
      disable_unit  = true;
   }
   if ((m_rectGfx.Height() < 20) || (m_rectGfx.Width() < 20))
      return;

   // pie angle
   if (!disable_range && m_swRange) {
      m_dLimitAngleDeg = 130.0;
   }
   else {
      m_dLimitAngleDeg = 150.0;
   }

   // make a square
   if (m_rectGfx.Height() > m_rectGfx.Width()) {
      m_rectGfx.DeflateRect(0, (m_rectGfx.Height() - m_rectGfx.Width()) / 2);
   }
   if (m_rectGfx.Height() < m_rectGfx.Width()) {
      m_rectGfx.DeflateRect((m_rectGfx.Width() - m_rectGfx.Height()) / 2, 0);
   }

   ///////////////////////
   // create font //
   ///////////////////////
   m_nFontHeight = m_rectGfx.Height() / 4;

   if (((m_rectGfx.Width()) > 0) && ((m_rectGfx.Height()) > 0)) {
      int     height = 0;
      int     width  = 0;
      double  scale  = 1.0;

      do {
         m_nFontHeight = (int) ((double) m_nFontHeight * scale);
         m_fontValue.DeleteObject();
         if (!m_fontValue.CreateFont(m_nFontHeight, 0, 0, 0, 400,
                                     false, false, 0, ANSI_CHARSET,
                                     OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
                                     DEFAULT_QUALITY, DEFAULT_PITCH | FF_SWISS, "Arial"))
            return;

         tempString.Format("%.*f", m_nRangeDecimals, m_dMinScale);
         int len1 = tempString.GetLength();

         tempString.Format("%.*f", m_nRangeDecimals, m_dMaxScale);
         int len2 = tempString.GetLength();

         width = len1 > len2 ? len1 : len2;

         TEXTMETRIC TM;
         pFontOld = m_dcGrid.SelectObject(&m_fontValue);
         m_dcGrid.GetTextMetrics(&TM);
         height = TM.tmHeight;
         width  = TM.tmAveCharWidth * width;
         m_dcGrid.SelectObject(pFontOld);

         scale -= 0.01;

      } while (((height > (m_rectGfx.Height() / 8) * m_nFontScale / 100) || (width > (m_rectGfx.Width() / 3))) && (scale > 0.0));
   }

   // place for title
   if (!disable_title && m_swTitle && m_strTitle != "") {
      TEXTMETRIC TM;

      pFontOld = m_dcGrid.SelectObject(&m_fontValue);
      m_dcGrid.GetTextMetrics(&TM);
      m_rectGfx.top += TM.tmHeight + 3;
      m_rectGfx.DeflateRect(TM.tmHeight, 0);
      m_dcGrid.SelectObject(pFontOld);
   }

   // determine the angular scaling
   m_dLimitAngleRad   = m_dLimitAngleDeg * m_dPI / 180.0;
   m_dRadiansPerValue = (2.0 * m_dLimitAngleRad) / (m_dMaxScale - m_dMinScale);

   // determine the center point
   m_nCXPix = m_rectGfx.left + m_rectGfx.Width() / 2;
   m_nCYPix = m_rectGfx.top + m_rectGfx.Height() / 2;

   // determine the size and location of the meter "pie"
   m_nRadiusPix = m_rectGfx.Height() * 50 / 100;
   m_nHalfBaseWidth = m_nRadiusPix / 50 + 1;
   dTemp = m_nCXPix - m_nRadiusPix * sin(m_dLimitAngleRad);
   m_nLeftLimitXPix = ROUND(dTemp);
   dTemp = m_nCYPix - m_nRadiusPix * cos(m_dLimitAngleRad);
   m_nLeftLimitYPix = ROUND(dTemp);

   dTemp = m_nCXPix + m_nRadiusPix * sin(m_dLimitAngleRad);
   m_nRightLimitXPix = ROUND(dTemp);
   m_nRightLimitYPix = m_nLeftLimitYPix;

   // determine the placement of the current value text
   m_rectValue.left   = m_rectGfx.left + 10;
   m_rectValue.top    = m_rectGfx.top + 10;
   m_rectValue.right  = m_rectGfx.right - 10;
   m_rectValue.bottom = m_nCYPix - m_nRadiusPix / 2;

   m_rectValue.bottom = m_nCYPix + m_nRadiusPix / 4; // efahl

   // determine the placement of the minimum value
   m_rectMinValue.left   = m_rectGfx.left + 1;
   m_rectMinValue.top    = m_rectGfx.bottom - m_rectGfx.Height() / 5;
   m_rectMinValue.right  = m_rectGfx.left + m_rectGfx.Width() / 2 + 1;
   m_rectMinValue.bottom = m_rectGfx.bottom - m_rectGfx.Height() / 50;

   // determine the placement of the maximum value
   m_rectMaxValue.right  = m_rectGfx.right - 1;
   m_rectMaxValue.top    = m_rectGfx.bottom - m_rectGfx.Height() / 5;
   m_rectMaxValue.left   = m_rectGfx.left + m_rectGfx.Width() / 2 + 1;
   m_rectMaxValue.bottom = m_rectGfx.bottom - m_rectGfx.Height() / 50;

   if (m_strUnit == "")
      m_rectValue.bottom += m_nFontHeight;
   else
      m_rectValue.bottom += m_nFontHeight / 2;

   ///////////////////////
   // draw grid //
   ///////////////////////
   if (m_swGrid) {
      double         dX, dY;
      int            nLeftBoundX, nRightBoundX, nLeftBoundY, nRightBoundY;
      int            tick;
      double         len;
      double         start_perc, end_perc;
      int            start_tick, end_tick;

      // alert arc start/end
      if (m_dAlertScale < m_dMinScale) m_dAlertScale = m_dMinScale;
      if (m_dAlertScale > m_dMaxScale) m_dAlertScale = m_dMaxScale;
      start_perc = m_dMaxScale - m_dMinScale;
      if (start_perc) {
         start_perc = ((m_dAlertScale - m_dMinScale) / start_perc) * 2.0 - 1.0;
      }
      else {
         start_perc = 1;
      }
      end_perc = 1.0;

      // alert tick start/end
      start_tick = ROUND(start_perc * 10.0 + (0.5 * ((double) (start_perc > 0.0))));
      end_tick   = int(end_perc * 10.0);

      // new pen / brush
      pPenOld   = NULL;
      pBrushOld = NULL;
      if (m_PenG_Grid.m_hObject)
         pPenOld = m_dcGrid.SelectObject(&m_PenG_Grid);
      if (m_BrushG_Grid.m_hObject)
         pBrushOld = m_dcGrid.SelectObject(&m_BrushG_Grid);

      // determine the bounding rectangle for the pie slice
      // and draw it
      nLeftBoundX  = m_nCXPix - m_nRadiusPix;
      nRightBoundX = m_nCXPix + m_nRadiusPix;
      nLeftBoundY  = m_nCYPix - m_nRadiusPix;
      nRightBoundY = m_nCYPix + m_nRadiusPix;

      // arc
      m_dcGrid.Arc(nLeftBoundX, nLeftBoundY, nRightBoundX + 1, nRightBoundY + 1,
                   ROUND(m_nCXPix + m_nRadiusPix * sin(m_dLimitAngleRad * start_perc)), ROUND(m_nCYPix - m_nRadiusPix * cos(m_dLimitAngleRad * start_perc)),
                   m_nLeftLimitXPix, m_nLeftLimitYPix);

      // tick marks
      for (tick = -10; tick < start_tick; tick++) {
         dX = m_nCXPix + m_nRadiusPix * sin(m_dLimitAngleRad * tick * 0.1);
         dY = m_nCYPix - m_nRadiusPix * cos(m_dLimitAngleRad * tick * 0.1);
         m_dcGrid.MoveTo(ROUND(dX), ROUND(dY));
         if (tick % 2) len = 1.0;
         else len = 0.95;
         dX = m_nCXPix + 0.92 * len * m_nRadiusPix * sin(m_dLimitAngleRad * tick * 0.1);
         dY = m_nCYPix - 0.92 * len * m_nRadiusPix * cos(m_dLimitAngleRad * tick * 0.1);
         m_dcGrid.LineTo(ROUND(dX), ROUND(dY));
      }

      // old pen / brush
      if (pPenOld)
         m_dcGrid.SelectObject(pPenOld);
      if (pBrushOld)
         m_dcGrid.SelectObject(pBrushOld);

      ///////////////////////
      // draw alert grid //
      ///////////////////////
      if ((start_perc >= -1.0) && (start_perc < 1.0)) {
         // new pen / brush
         pPenOld   = NULL;
         pBrushOld = NULL;
         if (m_PenG_GridAlert.m_hObject)
            pPenOld = m_dcGrid.SelectObject(&m_PenG_GridAlert);
         if (m_BrushG_GridAlert.m_hObject)
            pBrushOld = m_dcGrid.SelectObject(&m_BrushG_GridAlert);

         // arc
         m_dcGrid.Arc(nLeftBoundX, nLeftBoundY, nRightBoundX + 1, nRightBoundY + 1,
                      ROUND(m_nCXPix + m_nRadiusPix * sin(m_dLimitAngleRad * end_perc)), ROUND(m_nCYPix - m_nRadiusPix * cos(m_dLimitAngleRad * end_perc)),
                      ROUND(m_nCXPix + m_nRadiusPix * sin(m_dLimitAngleRad * start_perc)), ROUND(m_nCYPix - m_nRadiusPix * cos(m_dLimitAngleRad * start_perc)));

         // tick marks
         for (tick = start_tick; tick <= end_tick; tick++) {
            dX = m_nCXPix + m_nRadiusPix * sin(m_dLimitAngleRad * tick * 0.1);
            dY = m_nCYPix - m_nRadiusPix * cos(m_dLimitAngleRad * tick * 0.1);
            m_dcGrid.MoveTo(ROUND(dX), ROUND(dY));
            if (tick % 2) len = 1.0;
            else len = 0.95;
            dX = m_nCXPix + 0.92 * len * m_nRadiusPix * sin(m_dLimitAngleRad * tick * 0.1);
            dY = m_nCYPix - 0.92 * len * m_nRadiusPix * cos(m_dLimitAngleRad * tick * 0.1);
            m_dcGrid.LineTo(ROUND(dX), ROUND(dY));
         }

         // old pen / brush
         if (pPenOld  ) m_dcGrid.SelectObject(pPenOld);
         if (pBrushOld) m_dcGrid.SelectObject(pBrushOld);
      }
   }

   // grab the font and set the text color
   pFontOld = m_dcGrid.SelectObject(&m_fontValue);
   m_dcGrid.SetTextColor(nearestColor((~m_colorRange) & 0xFFFFFF));
   m_dcGrid.SetBkColor(nearestColor((~m_colorBGround) & 0xFFFFFF));
   m_nTextBaseSpacing = m_rectMinValue.Height() / 4;

   if (!disable_title && m_swTitle && m_strTitle != "") {
      // show the title
      m_dcGrid.SetTextAlign(TA_CENTER | TA_BOTTOM);
      m_dcGrid.SetTextColor(nearestColor((~m_colorTitle) & 0xFFFFFF));
      m_dcGrid.SetBkColor(nearestColor((~m_colorBGround) & 0xFFFFFF));
      m_dcGrid.TextOut((m_rectGfx.left + m_rectGfx.right) / 2,
                       m_rectGfx.top - 3, m_strTitle);
   }

   if (!disable_range && m_swRange) {
      // show the max and min (limit) values
      m_dcGrid.SetTextAlign(TA_CENTER | TA_BASELINE);
      m_dcGrid.SetTextColor(nearestColor((~m_colorRange) & 0xFFFFFF));
      m_dcGrid.SetBkColor(nearestColor((~m_colorBGround) & 0xFFFFFF));
      tempString.Format("%.*f", m_nRangeDecimals, m_dMinScale);
      m_dcGrid.TextOut((m_rectMinValue.left + m_rectMinValue.right) / 2,
                       m_rectMinValue.bottom - m_nTextBaseSpacing, tempString);
      tempString.Format("%.*f", m_nRangeDecimals, m_dMaxScale);
      m_dcGrid.TextOut((m_rectMaxValue.left + m_rectMaxValue.right) / 2,
                       m_rectMaxValue.bottom - m_rectMaxValue.Height() / 4, tempString);
   }

   if (!disable_unit && m_swUnit && m_strUnit != "") {
      // show the unit
      m_dcGrid.SetTextAlign(TA_CENTER | TA_BOTTOM);
      m_dcGrid.SetTextColor(nearestColor((~m_colorValue) & 0xFFFFFF));
      m_dcGrid.SetBkColor(nearestColor((~m_colorBGround) & 0xFFFFFF));
      m_dcGrid.TextOut((m_rectGfx.left + m_rectGfx.right) / 2,
                       //m_nCYPix - m_nRadiusPix / 2 + m_nFontHeight + m_nFontHeight / 2,
                       m_nCYPix + m_nRadiusPix / 4 + m_nFontHeight + m_nFontHeight / 2, // efahl
                       m_strUnit);
   }

   // restore the font
   m_dcGrid.SelectObject(pFontOld);

}

///////////////////////////////////
void CBarMeter::DrawNeedle()
{
   CPoint         pPoints[6];
   CString        tempString;
   CPen          *pPenOld;
   CBrush        *pBrushOld;
   CFont         *pFontOld;
#if 0
   double         dAngleRad, dX, dY;
   double         dCosAngle, dSinAngle;
#endif

   if (!m_dcNeedle.GetSafeHdc())
      return;

   if (m_boolUseBitmaps) {
      // new pen / brush
      pPenOld = NULL;
      pBrushOld = NULL;
      if (m_PenN_BGround.m_hObject)
         pPenOld = m_dcNeedle.SelectObject(&m_PenN_BGround);
      if (m_BrushN_BGround.m_hObject)
         pBrushOld = m_dcNeedle.SelectObject(&m_BrushN_BGround);

      m_dcNeedle.Rectangle(m_rectDraw);

      // old pen / brush
      if (pPenOld)
         m_dcGrid.SelectObject(pPenOld);
      if (pBrushOld)
         m_dcGrid.SelectObject(pBrushOld);
   }

   ///////////////////////
   // check sizes //
   ///////////////////////
   bool disable_value = false;
   if ((m_rectGfx.Height() < 50) || (m_rectGfx.Width() < 50)) {
      disable_value = true;
   }
   if ((m_rectGfx.Height() < 10) || (m_rectGfx.Width() < 10))
      return;

   if (!disable_value && m_swValue) {
      pFontOld = m_dcNeedle.SelectObject(&m_fontValue);
      m_dcNeedle.SetTextAlign(TA_CENTER | TA_BASELINE);
      m_dcNeedle.SetTextColor(nearestColor(m_colorValue ^ m_colorBGround));
      m_dcNeedle.SetBkColor(RGB(0, 0, 0));
      tempString.Format("%.*f", m_nValueDecimals, m_dNeedlePos);

      m_dcNeedle.TextOut((m_rectValue.right + m_rectValue.left) / 2,
                         m_rectValue.bottom - m_nTextBaseSpacing, tempString);

      m_dcNeedle.SelectObject(pFontOld);
   }

   // new pen / brush
   pPenOld   = NULL;
   pBrushOld = NULL;
   if (m_PenN_Needle.m_hObject  ) pPenOld   = m_dcGrid.SelectObject(&m_PenN_Needle);
   if (m_BrushN_Needle.m_hObject) pBrushOld = m_dcGrid.SelectObject(&m_BrushN_Needle);

   // Turn on alert LEDs.
   if (m_nRectWidth > m_nRectHeight) { // Horizontal bar.
      int t      = m_rectGfx.top;
      int d      = m_rectGfx.bottom - t;
      int nLEDs  = (m_nRectWidth / (d+2) / 2) * 2;
      int l      = m_rectGfx.left + (m_rectGfx.right-m_rectGfx.left)/2 - int(double(nLEDs)/2.0*(d+2));
      int nAlert = int(nLEDs * (m_dAlertScale-m_dMinScale) / (m_dMaxScale-m_dMinScale));
      int nOn    = int(nLEDs * (m_dNeedlePos -m_dMinScale) / (m_dMaxScale-m_dMinScale));
      for (int iLED = 0; iLED < nLEDs; iLED++) {
         if (iLED >= nAlert) {
            m_dcGrid.SelectObject(&m_BrushG_GridAlert);
            m_dcGrid.SelectObject(&m_PenG_GridAlert);
         }
         if (iLED >  nOn   ) m_dcGrid.SelectObject(&m_BrushG_BGround);
         m_dcGrid.Ellipse(l, t, l+d, t+d);
         l += d+2;
      }
   }
   else { // Vertical bar.
      int l      = m_rectGfx.left;
      int d      = m_rectGfx.right - l;
      int nLEDs  = (m_nRectHeight / (d+2) / 2) * 2;
      int b      = m_rectGfx.bottom - ((m_rectGfx.bottom-m_rectGfx.top)/2 - int(double(nLEDs)/2.0*(d+2)));
      int nAlert = int(nLEDs * (m_dAlertScale-m_dMinScale) / (m_dMaxScale-m_dMinScale));
      int nOn    = int(nLEDs * (m_dNeedlePos -m_dMinScale) / (m_dMaxScale-m_dMinScale));
      for (int iLED = 0; iLED < nLEDs; iLED++) {
         if (iLED >= nAlert) {
            m_dcGrid.SelectObject(&m_BrushG_GridAlert);
            m_dcGrid.SelectObject(&m_PenG_GridAlert);
         }
         if (iLED >  nOn   ) m_dcGrid.SelectObject(&m_BrushG_BGround);
         m_dcGrid.Ellipse(l, b-d, l+d, b);
         b -= d+2;
      }
   }

   // old pen / brush
   if (pPenOld)
      m_dcGrid.SelectObject(pPenOld);
   if (pBrushOld)
      m_dcGrid.SelectObject(pBrushOld);
}

///////////////////////////////////
void CBarMeter::ShowMeterImage(CDC * pDC)
{
   CDC            memDC;
   CBitmap        memBitmap;
   CBitmap       *oldBitmap;    // bitmap originally found in CMemDC

   // this function is only used when the needle and grid
   // have been drawn to bitmaps and they need to be combined
   // and sent to the destination
   if (!m_boolUseBitmaps)
      return;

   // to avoid flicker, establish a memory dc, draw to it
   // and then BitBlt it to the destination "pDC"
   memDC.CreateCompatibleDC(pDC);
   memBitmap.CreateCompatibleBitmap(pDC, m_nRectWidth, m_nRectHeight);
   oldBitmap = (CBitmap *) memDC.SelectObject(&memBitmap);

   // make sure we have the bitmaps
   if (!m_dcGrid.GetSafeHdc())
      return;
   if (!m_dcNeedle.GetSafeHdc())
      return;

   if (memDC.GetSafeHdc() != NULL) {
      // draw the inverted grid
      memDC.BitBlt(0, 0, m_nRectWidth, m_nRectHeight, &m_dcGrid, 0, 0, NOTSRCCOPY);
      // merge the needle image with the grid
      memDC.BitBlt(0, 0, m_nRectWidth, m_nRectHeight, &m_dcNeedle, 0, 0, SRCINVERT);
      // copy the resulting bitmap to the destination
      pDC->BitBlt(m_rectOwner.left, m_rectOwner.top, m_nRectWidth, m_nRectHeight,
                  &memDC, 0, 0, SRCCOPY);
   }

   memDC.SelectObject(oldBitmap);

}

//////////////////////////////////////////////////////
void CBarMeter::ActuateColors()
{
   if (m_PenG_Grid.m_hObject)
      m_PenG_Grid.DeleteObject();
   if (m_PenG_Grid.m_hObject == NULL)
      m_PenG_Grid.CreatePen(PS_SOLID, 0, nearestColor((~m_colorGrid) & 0xFFFFFF));
   if (m_BrushG_Grid.m_hObject)
      m_BrushG_Grid.DeleteObject();
   if (m_BrushG_Grid.m_hObject == NULL)
      m_BrushG_Grid.CreateSolidBrush(nearestColor((~m_colorGrid) & 0xFFFFFF));

   if (m_PenG_GridAlert.m_hObject)
      m_PenG_GridAlert.DeleteObject();
   if (m_PenG_GridAlert.m_hObject == NULL)
      m_PenG_GridAlert.CreatePen(PS_SOLID, 0, nearestColor((~m_colorGridAlert) & 0xFFFFFF));
   if (m_BrushG_GridAlert.m_hObject)
      m_BrushG_GridAlert.DeleteObject();
   if (m_BrushG_GridAlert.m_hObject == NULL)
      m_BrushG_GridAlert.CreateSolidBrush(nearestColor((~m_colorGridAlert) & 0xFFFFFF));

   if (m_PenG_BGround.m_hObject)
      m_PenG_BGround.DeleteObject();
   if (m_PenG_BGround.m_hObject == NULL)
      m_PenG_BGround.CreatePen(PS_SOLID, 1, nearestColor((~m_colorBGround) & 0xFFFFFF));
   if (m_BrushG_BGround.m_hObject)
      m_BrushG_BGround.DeleteObject();
   if (m_BrushG_BGround.m_hObject == NULL)
      m_BrushG_BGround.CreateSolidBrush(nearestColor((~m_colorBGround) & 0xFFFFFF));

   if (m_PenN_Needle.m_hObject)
      m_PenN_Needle.DeleteObject();
   if (m_PenN_Needle.m_hObject == NULL)
      m_PenN_Needle.CreatePen(PS_SOLID, 0, nearestColor((~m_colorNeedle) & 0xFFFFFF));
   if (m_BrushN_Needle.m_hObject)
      m_BrushN_Needle.DeleteObject();
   if (m_BrushN_Needle.m_hObject == NULL)
      m_BrushN_Needle.CreateSolidBrush(nearestColor((~m_colorNeedle) & 0xFFFFFF));

   if (m_PenN_BGround.m_hObject)
      m_PenN_BGround.DeleteObject();
   if (m_PenN_BGround.m_hObject == NULL)
      m_PenN_BGround.CreatePen(PS_SOLID, 0, RGB(0, 0, 0));
   if (m_BrushN_BGround.m_hObject)
      m_BrushN_BGround.DeleteObject();
   if (m_BrushN_BGround.m_hObject == NULL)
      m_BrushN_BGround.CreateSolidBrush(nearestColor(RGB(0, 0, 0)));
}

//////////////////////////////////////////////////////
void CBarMeter::SetState(enum MeterMemberEnum meter_member, bool State)
{
   switch (meter_member) {
      case meter_title: m_swTitle = State; break;
      case meter_grid : m_swGrid  = State; break;
      case meter_value: m_swValue = State; break;
      case meter_range: m_swRange = State; break;
      case meter_unit : m_swUnit  = State; break;
   }
}

//////////////////////////////////////////////////////
void CBarMeter::SetColor(enum MeterMemberEnum meter_member, COLORREF Color)
{
   switch (meter_member) {
      case meter_title    : m_colorTitle     = Color; break;
      case meter_needle   : m_colorNeedle    = Color; break;
      case meter_grid     : m_colorGrid      = Color; break;
      case meter_gridalert: m_colorGridAlert = Color; break;
      case meter_value    : m_colorValue     = Color; break;
      case meter_range    : m_colorRange     = Color; break;
      case meter_bground  : m_colorBGround   = Color; break;
   }

   // set pen/brush colors
   ActuateColors();
}

//////////////////////////////////////////////////////
void CBarMeter::SetRange(double dMin, double dMax)
{
   // The function does NOT force the re-drawing of the meter.
   // The owner must explicitly call the ShowMeter function
   m_dMinScale = dMin;
   m_dMaxScale = dMax;
   m_boolForceRedraw = true;
}

//////////////////////////////////////////////////////
void CBarMeter::SetAlert(double dAlert)
{
   // The function does NOT force the re-drawing of the meter.
   // The owner must explicitly call the ShowMeter function
   if (dAlert < m_dMinScale) dAlert = m_dMinScale;
   if (dAlert > m_dMaxScale) dAlert = m_dMaxScale;
   m_dAlertScale = dAlert;
   m_boolForceRedraw = true;
}

//////////////////////////////////////////////////////
void CBarMeter::SetFontScale(int nFontScale)
{
   // The function does NOT force the re-drawing of the meter.
   // The owner must explicitly call the ShowMeter function
   if (m_nFontScale < 1) m_nFontScale = 1;
   if (m_nFontScale > 100) m_nFontScale = 100;
   m_nFontScale = nFontScale;
   m_boolForceRedraw = true;
}

//////////////////////////////////////////////////////
void CBarMeter::SetRangeDecimals(int nRangeDecimals)
{
   // The function does NOT force the re-drawing of the meter.
   // The owner must explicitly call the ShowMeter function
   if (m_nRangeDecimals < 0) m_nRangeDecimals = 0;
   if (m_nRangeDecimals > 100) m_nRangeDecimals = 100;
   m_nRangeDecimals = nRangeDecimals;
   m_boolForceRedraw = true;
}

//////////////////////////////////////////////////////
void CBarMeter::SetValueDecimals(int nValueDecimals)
{
   // The function does NOT force the re-drawing of the meter.
   // The owner must explicitly call the ShowMeter function
   if (m_nValueDecimals < 0) m_nValueDecimals = 0;
   if (m_nValueDecimals > 100) m_nValueDecimals = 100;
   m_nValueDecimals = nValueDecimals;
   m_boolForceRedraw = true;
}

//////////////////////////////////////////////////////
void CBarMeter::SetTitle(CString strTitle)
{
   // The function does NOT force the re-drawing of the meter.
   // The owner must explicitly call the ShowMeter function
   m_strTitle = strTitle;
   m_boolForceRedraw = true;
}

//////////////////////////////////////////////////////
void CBarMeter::SetUnit(CString strUnit)
{
   // The function does NOT force the re-drawing of the meter.
   // The owner must explicitly call the ShowMeter function
   m_strUnit = strUnit;
   m_boolForceRedraw = true;
}

//////////////////////////////////////////////////////
void CBarMeter::GetColor(enum MeterMemberEnum meter_member, COLORREF * pColor)
{
   switch (meter_member) {
      case meter_title    : *pColor = m_colorTitle;     break;
      case meter_needle   : *pColor = m_colorNeedle;    break;
      case meter_grid     : *pColor = m_colorGrid;      break;
      case meter_gridalert: *pColor = m_colorGridAlert; break;
      case meter_value    : *pColor = m_colorValue;     break;
      case meter_range    : *pColor = m_colorRange;     break;
      case meter_bground  : *pColor = m_colorBGround;   break;
   }
}

//////////////////////////////////////////////////////
void CBarMeter::GetState(enum MeterMemberEnum meter_member, bool * pState)
{
   switch (meter_member) {
      case meter_title: *pState = m_swTitle; break;
      case meter_grid : *pState = m_swGrid;  break;
      case meter_value: *pState = m_swValue; break;
      case meter_range: *pState = m_swRange; break;
      case meter_unit : *pState = m_swUnit;  break;
   }
}

