//*****************************************
//******** PC Configurator V1.00 **********
//*** (C) - 2001 B.Bowling/A. Grippo ******
//** All derivatives from this software ***
//**  are required to keep this header ****
//*****************************************

static char *rcsId() { return "$Id$"; }

#include "stdafx.h"
#include "megatune.h"
#include "msDatabase.h"
#include "repository.h"
#include "Dttune.h"
#include "veconst.h"

#ifdef _DEBUG
#  define new DEBUG_NEW
#  undef THIS_FILE
   static char THIS_FILE[] = __FILE__;
#endif

//------------------------------------------------------------------------------

extern msDatabase mdb;
extern repository rep;

//------------------------------------------------------------------------------

static long   wht       RGB(255, 255, 255);
static long   red       RGB(255,   0,   0);
static long   grn       RGB(  0, 255,   0);
static long   blu       RGB(  0,   0, 255);
static long   blk       RGB(  0,   0,   0);
static long   lgr       RGB(220, 220, 220);
static long   dgr       RGB(127, 127, 127);

static CPen   pwhite    (PS_SOLID, 0, wht);
static CBrush brushblk, brushred;

double Dttune::Xrot, Dttune::Yrot, Dttune::Zrot;
bool   Dttune::flat = false;

//------------------------------------------------------------------------------
// Dttune dialog

Dttune::Dttune(CWnd * pParent /*=NULL*/ )
 : CDialog(Dttune::IDD, pParent),
   currcurx  (0),
   currcury  (0),
   ivebin    (0),
   rpm       (0),
   map       (0),
   gauges    (eeGauges)
{
   p3d = new plot3d(this);
   //{{AFX_DATA_INIT(Dttune)
	//}}AFX_DATA_INIT
}

Dttune::~Dttune()
{
   delete p3d;
}

//------------------------------------------------------------------------------

BEGIN_MESSAGE_MAP(Dttune, CDialog)
   //{{AFX_MSG_MAP(Dttune)
      ON_WM_KEYDOWN()
      ON_WM_PAINT()
      ON_WM_TIMER()
      ON_BN_CLICKED(IDC_BURNVE, OnBurn)
      ON_NOTIFY(UDN_DELTAPOS, IDC_TREQFUEL_SPIN, OnReqfuelSpin)
      ON_BN_CLICKED(IDC_EGO,    OnEgo)
      ON_BN_CLICKED(IDC_ENRICH, OnEnrich)
      ON_BN_CLICKED(IDC_PW1,    OnPw1)
      ON_BN_CLICKED(IDC_PW2,    OnPw2)
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

//------------------------------------------------------------------------------

void Dttune::DoDataExchange(CDataExchange * pDX)
{
   CDialog::DoDataExchange(pDX);
   //{{AFX_DATA_MAP(Dttune)
      DDX_Control(pDX, IDC_TREQFUEL, m_tReqFuel);
	//}}AFX_DATA_MAP
}

//------------------------------------------------------------------------------

void Dttune::setMsg(char *newMsg, int line, long clr)
{
   static char oldMsg[5][100] = {"", "", "", "", ""};
   CDC *dc = GetDC();
   if (dc) {
      dc->SetBkMode(TRANSPARENT);
      dc->SetTextColor(blk);
      dc->TextOut(wux1 + 10, wuy1 + 3 + (line*16), oldMsg[line]);
      dc->SetTextColor(clr);
      dc->TextOut(wux1 + 10, wuy1 + 3 + (line*16), newMsg);
      strcpy(oldMsg[line], newMsg);
   }
   ReleaseDC(dc);
}

//------------------------------------------------------------------------------

void Dttune::timer(int state)
{
   if (state == on) {
      if (SetTimer(1, mdb.timerInterval, NULL) == 0)
         MessageBox("ERROR: Cannot install timer.\nKill other useless Windows Apps.");
   }
   else {
      KillTimer(1);
      Sleep(mdb.timerInterval);
   }
}

//------------------------------------------------------------------------------

void Dttune::setupGauges(bool forceRedraw)
{
   int deci = 0;
   switch (gauges) {
      case eeGauges:
         meterTop.SetTitle("EGO Correction");
         meterTop.SetUnit("%");
         meterTop.SetRange(rep.lorEGC, rep.hirEGC);
         meterTop.SetAlert(100.0);

         meterBot.SetTitle("Acceleration Enrichment");
         meterBot.SetUnit("%");
         meterBot.SetRange(rep.lotACE, rep.hitACE);
         meterBot.SetAlert(rep.rdtACE > 0.0 ? rep.rdtACE : rep.hitACE);
         break;

      case aeGauges:
         meterTop.SetTitle("Warmup Enrichment");
         meterTop.SetUnit("%");
         meterTop.SetRange(rep.lotWU, rep.hitWU);
         meterTop.SetAlert(rep.rdtWU > 0.0 ? rep.rdtWU : rep.hitWU);

         meterBot.SetTitle("Acceleration Enrichment");
         meterBot.SetUnit("%");
         meterBot.SetRange(rep.lotACE, rep.hitACE);
         meterBot.SetAlert(rep.rdtACE > 0.0 ? rep.rdtACE : rep.hitACE);
         break;

      case pw1Gauges:
         meterTop.SetTitle("Pulse Width 1");
         meterTop.SetUnit ("mSec");
         meterTop.SetRange(rep.lofPW, rep.hifPW);
         meterTop.SetAlert(rep.rdfPW > 0.0 ? rep.rdfPW : rep.hifPW);

         meterBot.SetTitle("Duty Cycle 1");
         meterBot.SetUnit ("%");
         meterBot.SetRange(rep.lofDC, rep.hifDC);
         meterBot.SetAlert(rep.rdfDC > 0.0 ? rep.rdfDC : rep.hifDC);
         deci = 1;
         break;

      case pw2Gauges:
         meterTop.SetTitle("Pulse Width 2");
         meterTop.SetUnit ("mSec");
         meterTop.SetRange(rep.lofPW, rep.hifPW);
         meterTop.SetAlert(rep.rdfPW > 0.0 ? rep.rdfPW : rep.hifPW);

         meterBot.SetTitle("Duty Cycle 2");
         meterBot.SetUnit ("%");
         meterBot.SetRange(rep.lofDC, rep.hifDC);
         meterBot.SetAlert(rep.rdfDC > 0.0 ? rep.rdfDC : rep.hifDC);
         deci = 1;
         break;
   }

   meterTop.SetValueDecimals(deci);
   meterTop.SetRangeDecimals(deci);
   meterBot.SetValueDecimals(deci);
   meterBot.SetRangeDecimals(deci);

   if (forceRedraw) {
      CPaintDC dc(this);

      CRect r;
      GetDlgItem(IDC_METERWARM)->GetWindowRect(&r);
      ScreenToClient(&r);
      meterTop.ShowMeter(&dc, r);

      GetDlgItem(IDC_METERACCEL)->GetWindowRect(&r);
      ScreenToClient(&r);
      meterBot.ShowMeter(&dc, r);
   }
}

//------------------------------------------------------------------------------

void logSize(CWnd *w, char *title);

BOOL Dttune::OnInitDialog()
{
   CDialog::OnInitDialog();

   logSize(this, "tune");

   if (!mdb.loaded()) mdb.getConst(); // If not here, grab from MS.
   for (int jk = 0; jk < 8; jk++) {
      mdb.rpmrange[jk] = mdb.Const(Drpmrangeve + jk) * 100;
      mdb.maprange[jk] = mdb.Const(Dkparangeve + jk);
      if (mdb.alphaN()) mdb.maprange[jk] = mdb.pctFromTps(BYTE(mdb.maprange[jk]));
   }

   double rpmborder = (mdb.rpmrange[7] - mdb.rpmrange[0]) * 0.0;
   minrpm = mdb.rpmrange[0] - rpmborder;
   maxrpm = mdb.rpmrange[7] + rpmborder;
   double mapborder = (mdb.maprange[7] - mdb.maprange[0]) * 0.0;
   minmap = mdb.maprange[0] - mapborder;
   maxmap = mdb.maprange[7] + mapborder;

   mdb.reqFuel = mdb.Const(Dreq_fuel) / 10.0;
   char s[10];
   sprintf(s, "%.1f", mdb.reqFuel);
   m_tReqFuel.SetWindowText(s);

   veBar = static_cast<CProgressCtrl *>(GetDlgItem(IDC_TTVE_BAR));

   CRect r;
   GetDlgItem(IDC_VEP)->GetWindowRect(&r);
   ScreenToClient(&r);
   vepx1 = r.left;
   vepx2 = r.right;
   vepy1 = r.top;
   vepy2 = r.bottom;

   GetDlgItem(IDC_WU)->GetWindowRect(&r);
   ScreenToClient(&r);
   wux1 = r.left;
   wux2 = r.right;
   wuy1 = r.top;
   wuy2 = r.bottom;

   sloperpm = double(vepx2 - vepx1) / (maxrpm - minrpm);
   slopemap = double(vepy2 - vepy1) / (maxmap - minmap);

   brushblk.DeleteObject();
   brushblk.CreateSolidBrush(blk);
   brushred.DeleteObject();
   brushred.CreateSolidBrush(red);

   veplotrect.SetRect(vepx1 - 12, vepy1 - 12, vepx2 + 12, vepy2 + 12);
   vewarmrect.SetRect(wux1  - 12, wuy1  - 12, wux2  + 12, wuy2  + 12);

#define veMin   0
#define veMax 100
   p3d->setDeviceCoordinates(vepx1, vepy1, vepx2, vepy2);
   p3d->setWorldCoordinates(Point3(minrpm, minmap, veMin-100), Point3(maxrpm, maxmap, veMax+100));
#define RAD(d) ((d/180.0)*PI)
   Xrot = RAD(250);
   Yrot = RAD(  0);
   Zrot = RAD(340);
   setPerspective(true);

   // Meter defaults
   meterrpm.SetRange(rep.lotRPM, rep.hitRPM);
   meterrpm.SetTitle("Engine RPM");
   meterrpm.SetUnit ("RPM");
   meterrpm.SetAlert(rep.rdtRPM > 0.0 ? rep.rdtRPM : rep.hitRPM);
   meterrpm.SetValueDecimals(0);
   meterrpm.SetRangeDecimals(0);

   if (mdb.alphaN() == 1) {
      metermap.SetRange(rep.lotTR, rep.hitTR);
      metermap.SetTitle("TPS");
      metermap.SetUnit("%");
      metermap.SetAlert(rep.rdtTR > 0.0 ? rep.rdtTR : rep.hitTR);
   }
   else {
      metermap.SetRange(rep.lotMAP, rep.hitMAP);
      metermap.SetTitle("Engine MAP");
      metermap.SetUnit("kPa");
      metermap.SetAlert(rep.rdtMAP > 0.0 ? rep.rdtMAP : rep.hitMAP);
   }
   metermap.SetValueDecimals(0);
   metermap.SetRangeDecimals(0);

   meterve.SetRange(rep.lotVEG, rep.hitVEG);
   meterve.SetTitle("VE Bucket");
   meterve.SetUnit("%");
   meterve.SetAlert(rep.rdtVEG > 0.0 ? rep.rdtVEG : rep.hitVEG);
   meterve.SetValueDecimals(0);
   meterve.SetRangeDecimals(0);

   meterEGO.SetRange(rep.lotEGO, rep.hitEGO);
   meterEGO.SetAlert(rep.rdtEGO > 0.0 ? rep.rdtEGO : mdb.Const(Degoswitchv)/255.0*5.0);

   metero2.SetRange(rep.lotEGO, rep.hitEGO);
   metero2.SetTitle("Exhaust Gas Oxygen");
   metero2.SetUnit("volts");
   metero2.SetAlert(rep.rdtEGO > 0.0 ? rep.rdtEGO : mdb.Const(Degoswitchv)/255.0*5.0);
   metero2.SetValueDecimals(2);
   metero2.SetRangeDecimals(2);

   static_cast<CButton *>(GetDlgItem(IDC_EGO))->SetCheck(true);
   gauges = eeGauges;

   setupGauges(false);

   timer(on);

   return TRUE;
}

//------------------------------------------------------------------------------

static char *locMsg(int curX, int curY, int vebin, char *suffix)
{
   static char message[200];
   char *lbl = " kPa";
   int   val = mdb.Const(Dve+vebin); // What the hell is going on here, shouldn't this be MAP/TP????
   if (mdb.alphaN() == 1) {
      lbl = "%";
      val = mdb.pctFromTps(BYTE(val));
   }
   sprintf(message, "(%4d RPM, %3d%s) = %3d%% %s", mdb.rpmrange[curX], mdb.maprange[curY], lbl, val, suffix);
   return message;
}

void Dttune::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
   int dx = 0;
   int dy = 0;
   int dz = 0;

   if (nChar == 'Z') {
      flat = !flat;
      setPerspective(!flat);
      dz = 1;
   }
   else if (nChar == 'M' || nChar == 'N') {
      static double inc = PI/36.0;
      if (nChar == 'N') Xrot += inc; if (Xrot >= TWO_PI) Xrot = 0.0;
    //if (nChar == '?') Yrot += inc; if (Yrot >= TWO_PI) Yrot = 0.0;
      if (nChar == 'M') Zrot += inc; if (Zrot >= TWO_PI) Zrot = 0.0;
      setPerspective(true); dz = 1;
      char msg[100];
#define DEG(x) ((x/PI)*180.0)
      sprintf(msg, "%3.0f %3.0f %3.0f", DEG(Xrot), DEG(Yrot), DEG(Zrot));
      setMsg(msg, 4, wht);
   }
   else if (nChar == 'S') {   // Burn flash - "s"
      OnBurn();
   }
   else if (nChar == 'X') {   // Exit
      timer(off);
      if (MessageBox("Quit tuning?", "Tuning Go Bye-Bye", MB_YESNO | MB_DEFBUTTON2 | MB_ICONQUESTION | MB_SYSTEMMODAL) == IDYES) {
         CDialog::OnOK();
      }
      timer(on);
   }
   else if (nChar == VK_F3 || nChar == 'Q') {   // Richen VE - F3 or Q
      if (mdb.Const(Dve+ivebin) < 255) {
         mdb.putConstByte(Dve+ivebin, mdb.Const(Dve+ivebin)+1, true);

         setMsg(locMsg(currcurx, currcury, ivebin, "Enriching"), 1, red);
         setMsg("VE Table not saved to FLASH", 4, red);

         veBar->SetPos(int(100.0 * (mdb.Const(Dve+ivebin)-rep.lotVEB) / (rep.hitVEB-rep.lotVEB)));
         dz = 1;
      }
   }
   else if (nChar == VK_F4 || nChar == 'W') {    // Enlean VE - F4 or "W"
      if (mdb.Const(Dve + ivebin) > 0) {
         mdb.putConstByte(Dve+ivebin, mdb.Const(Dve+ivebin)-1, true);

         setMsg(locMsg(currcurx, currcury, ivebin, "Enleaning"), 1, red);
         setMsg("VE Table not saved to FLASH", 4, red);

         veBar->SetPos(int(100.0 * (mdb.Const(Dve+ivebin)-rep.lotVEB) / (rep.hitVEB-rep.lotVEB)));
         dz = -1;
      }
   }
   else if (nChar == 'F') {
      // Find the intersection nearest the pointer.
      for (int ix = 0; ix < 7 && rpm > mdb.rpmrange[ix]; ix++) ;
      if (ix > 0 && rpm-mdb.rpmrange[ix-1] < mdb.rpmrange[ix]-rpm) ix--;
      dx = ix - currcurx;

      for (int iy = 0; iy < 7 && map > mdb.maprange[iy]; iy++) ;
      if (iy > 0 && map-mdb.maprange[iy-1] < mdb.maprange[iy]-map) iy--;
      dy = iy - currcury;
   }
   else if (nChar == VK_RETURN) { // Return key == next intersection.
      if (currcurx < 7)
         dx = 1;
      else {
         dx = -7;
         dy = currcury < 7 ? 1 : -7;
      }
   }
   else if (nChar == VK_F5 || nChar == 'H' || nChar == VK_LEFT) {
      if (currcurx > 0) dx = -1;
   }
   else if (nChar == VK_F7 || nChar == 'K' || nChar == VK_UP) {
      if (currcury < 7) dy = +1;
   }
   else if (nChar == VK_F6 || nChar == 'J' || nChar == VK_DOWN) {
      if (currcury > 0) dy = -1;
   }
   else if (nChar == VK_F8 || nChar == 'L' || nChar == VK_RIGHT) {
      if (currcurx < 7) dx = +1;
   }

   if (dx || dy || dz) {
      // Reposition cursor.
      currcurx += dx;
      currcury += dy;
      ivebin    = currcurx + 8 * currcury;
      if (dz) drawGrid();
      p3d->drawCursor(Point3(mdb.rpmrange[currcurx], mdb.maprange[currcury], mdb.Const(Dve+ivebin)));
   }

   setMsg(locMsg(currcurx, currcury, ivebin, ""), 1, red);
   
   CDialog::OnKeyDown(nChar, nRepCnt, nFlags);
}

//------------------------------------------------------------------------------

void Dttune::setPerspective(bool threeD)
{
   if (threeD)
      p3d->setRotation(Xrot,     Yrot,     Zrot);
   else
      p3d->setRotation(RAD(  0), RAD(180), RAD(180));
}

void Dttune::drawGrid()
{
   p3d->clear();

   // Draw the plot grid.
   Point3 line[8], p1, p2;
   int i, j, xval, yval, vval;
   for (i = 0; i < 8; i++) { // Vertical lines.
      xval = mdb.rpmrange[i];
      for (j = 0; j < 8; j++) {
         yval = mdb.maprange[j];
         vval = mdb.Const(Dve+(i + 8 * j));
         line[j] = Point3(xval, yval, vval);
      }
      if (i == 0 || i == 97) {
         p1 = line[0]; p1.z = 0;
         p2 = line[7]; p2.z = 0;
         p3d->drawLineSegment(p1, line[0]);
         p3d->drawLineSegment(p1, p2);
         p3d->drawLineSegment(p2, line[7]);
      }
      p3d->drawLineSegment8(line);
   }

   for (j = 0; j < 8; j++) { // Horizontal lines.
      yval = mdb.maprange[j];
      for (i = 0; i < 8; i++) {
         xval = mdb.rpmrange[i];
         vval = mdb.Const(Dve+(i + 8 * j));
         line[i] = Point3(xval, yval, vval);
      }
      if (j == 0 || j == 97) {
         p1 = line[0]; p1.z = 0;
         p2 = line[7]; p2.z = 0;
         p3d->drawLineSegment(p1, p2);
         p3d->drawLineSegment(p2, line[7]);
      }
      p3d->drawLineSegment8(line);
   }
}

//------------------------------------------------------------------------------

void Dttune::OnPaint()
{
   CPaintDC dc(this);

   dc.SelectObject(&pwhite);

   // Outline message window.
   CRect rectsav = vewarmrect;
   dc.Rectangle(rectsav);
   rectsav.DeflateRect(3, 3);
   dc.Draw3dRect(rectsav, (~wht) & 0xFFFFFF, (~blk) & 0xFFFFFF);
   rectsav.DeflateRect(1, 1);
   dc.Draw3dRect(rectsav, (~lgr) & 0xFFFFFF, (~dgr) & 0xFFFFFF);
   rectsav.DeflateRect(4, 4);
   dc.FillRect(rectsav, &brushblk);

   // Update the gauges.
   CRect r;
//   this->GetWindowRect(&r);
   GetDlgItem(IDC_METERRPM)->GetWindowRect(&r);
   ScreenToClient(&r);
   meterrpm.ShowMeter(&dc, r);

   GetDlgItem(IDC_METERMAP)->GetWindowRect(&r);
   ScreenToClient(&r);
   metermap.ShowMeter(&dc, r);

   GetDlgItem(IDC_METERVE)->GetWindowRect(&r);
   ScreenToClient(&r);
   meterve.ShowMeter(&dc, r);

   GetDlgItem(IDC_METERO2)->GetWindowRect(&r);
   ScreenToClient(&r);
   metero2.ShowMeter(&dc, r);

   GetDlgItem(IDC_METEREGOBAR)->GetWindowRect(&r);
   ScreenToClient(&r);
   meterEGO.ShowMeter(&dc, r);

   GetDlgItem(IDC_METERWARM)->GetWindowRect(&r);
   ScreenToClient(&r);
   meterTop.ShowMeter(&dc, r);

   GetDlgItem(IDC_METERACCEL)->GetWindowRect(&r);
   ScreenToClient(&r);
   meterBot.ShowMeter(&dc, r);

   // Draw the cursor.
   drawGrid();
   p3d->drawCursor(Point3(mdb.rpmrange[currcurx], mdb.maprange[currcury], mdb.Const(Dve+(currcurx + 8 * currcury))));

   // Send the messages.
   setMsg("MegaTune - F1 for help", 0, wht);
   if (!mdb.loaded()) mdb.getConst();
}

//------------------------------------------------------------------------------

void Dttune::OnTimer(UINT nIDEvent)
{
   unsigned char rtv[Rget];
   if (!mdb.getRuntime(rtv))
      setMsg("Disconnected from controller", 3, red);
   else {
      setMsg("Connected to controller", 3, wht);
      updateDisplay(rtv);
   }
   CDialog::OnTimer(nIDEvent);
}

//------------------------------------------------------------------------------

BEGIN_EVENTSINK_MAP(Dttune, CDialog)
   //{{AFX_EVENTSINK_MAP(Dttune)
   //}}AFX_EVENTSINK_MAP
END_EVENTSINK_MAP()

//------------------------------------------------------------------------------

void Dttune::updateDisplay(unsigned char runtimevars[])
{
   unsigned int   it, ik, ir, im;
   double         ft, slope, tmp1, tmp2;

   slope = -0.9706;
   ft    = slope * (double(runtimevars[Rclt]) - 32.49) + 170.0;

   // Put warmup tuning screen info here - next release!

   CDC *dc = GetDC();
   rpm     = runtimevars[Rrpm] * 100;
   meterrpm.UpdateNeedle(dc, rpm);

   it   = 0;
   tmp1 = 1.0E30;
   for (ik = 0; ik < 8; ik++) {
      slope = mdb.Const(Drpmrangeve + ik) * 100.0;
      tmp2 = ft - slope;
      tmp2 = tmp2 * tmp2;
      if (tmp2 < tmp1) {
         tmp1 = tmp2;
         it   = ik;
      }
   }
   ir = it;

   // Put in automatic VE cursor code here - next release!
   map = mdb.alphaN() ? mdb.throttle : mdb.map;
   metermap.UpdateNeedle(dc, map);

   it   = 0;
   tmp1 = 1.0E30;
   for (ik = 0; ik < 8; ik++) {
      slope = mdb.Const(Dkparangeve + ik);
      if (mdb.alphaN()) slope = mdb.pctFromTps(BYTE(slope));
      tmp2  = map - slope;
      tmp2  = tmp2 * tmp2;
      if (tmp2 < tmp1) {
         tmp1 = tmp2;
         it = ik;
      }
   }
   im = it;

   ivebin = ir + 8 * im;
   // put in automatic VE cursor code here - next release!
   // VE section

   double yo2 = runtimevars[Rego] * 0.0195312;
   metero2.UpdateNeedle(dc, yo2);
   meterEGO.UpdateNeedle(dc, yo2);

   ivebin = currcurx + 8 * currcury;
   double ytit = mdb.Const(Dve + ivebin);
   meterve.UpdateNeedle(dc, ytit);
   veBar->SetPos(int(100.0 * (ytit-rep.lotVEB) / (rep.hitVEB-rep.lotVEB)));

   switch (gauges) {
      case eeGauges:
         meterTop.UpdateNeedle(dc, double(runtimevars[Regocorr]));
         meterBot.UpdateNeedle(dc, double(runtimevars[Rtpsaccel]));
         break;
      case aeGauges:
         meterTop.UpdateNeedle(dc, double(runtimevars[Rwarmcorr]));
         meterBot.UpdateNeedle(dc, double(runtimevars[Rtpsaccel]));
         break;
      case pw1Gauges:
         meterTop.UpdateNeedle(dc, mdb.pulseWidth1);
         meterBot.UpdateNeedle(dc, mdb.dutyCycle1);
         break;
      case pw2Gauges:
         meterTop.UpdateNeedle(dc, mdb.pulseWidth2);
         meterBot.UpdateNeedle(dc, mdb.dutyCycle2);
         break;
   }
   ReleaseDC(dc);

   p3d->drawSpot(Point3(rpm, map, mdb.interpolateVE(rpm, map)));

   char msg[100];
   char *lbl = mdb.alphaN() == 1 ? "%" : " kPa";
   sprintf(msg, "(%4d RPM, %3d%s) = %3d%%", rpm, map, lbl, mdb.interpolateVE(rpm, map));
   setMsg(msg, 2, grn);

   static int oldPageNo = 0;
   if (mdb.pageNo() != oldPageNo) {
      drawGrid();
      oldPageNo = mdb.pageNo();
   }
}

//------------------------------------------------------------------------------

BOOL Dttune::OnCommand(WPARAM wParam, LPARAM lParam)
{
   if (wParam == IDCANCEL) {
      timer(off);
#if 0
      if (MessageBox("Quit tuning?", "Tuning Go Bye-Bye", MB_YESNO | MB_DEFBUTTON2 | MB_ICONQUESTION | MB_SYSTEMMODAL) != IDYES) {
         timer(on);
         return TRUE;
      }
#endif
   }
   return CDialog::OnCommand(wParam, lParam);
}

//------------------------------------------------------------------------------

void Dttune::OnBurn()
{
   mdb.burnConst();
   setMsg("VE Burned into Flash RAM", 4, grn);
}


//------------------------------------------------------------------------------

void Dttune::OnReqfuelSpin(NMHDR* pNMHDR, LRESULT* pResult)
{
	NM_UPDOWN* pNMUpDown = reinterpret_cast<NM_UPDOWN *>(pNMHDR);
   spinReqFuel(pNMUpDown->iDelta);
	*pResult = 0;
}

//------------------------------------------------------------------------------

BOOL Dttune::spinReqFuel(int delta)
{
   unsigned char reqFuel = mdb.Const(Dreq_fuel);
   if (delta == 1 && reqFuel > 1) {
      mdb.putConstByte(Dreq_fuel, reqFuel-1, true);
   }
   if (delta == -1 && reqFuel < 255) {
      mdb.putConstByte(Dreq_fuel, reqFuel+1, true);
   }

   mdb.reqFuel = mdb.Const(Dreq_fuel) / 10.0;
   char s[10];
   sprintf(s, "%.1f", mdb.reqFuel);
   m_tReqFuel.SetWindowText(s);

   return TRUE;
}

//------------------------------------------------------------------------------

BOOL Dttune::PreTranslateMessage(MSG* pMsg)
{
   static bool shifted = false;
   static bool ctrled  = false;

   if (pMsg->message == WM_KEYUP) {
      switch (pMsg->wParam) {
         case VK_SHIFT  : shifted = false; return TRUE;
         case VK_CONTROL: ctrled  = false; return TRUE;
      }
   }
   if (pMsg->message == WM_KEYDOWN) {
      switch (pMsg->wParam) {
         case VK_SHIFT  : shifted = true;  return TRUE;
         case VK_CONTROL: ctrled  = true;  return TRUE;

         case VK_LEFT:  if (shifted) return TRUE; // Avoid accidental cursor motion.
         case 'H':
            OnKeyDown(pMsg->wParam, 1, pMsg->lParam >> 16);
            return TRUE;
         case VK_UP:    if (ctrled && shifted) return spinReqFuel(-1);
                        if (shifted) pMsg->wParam = 'Q'; // Q = rich up 38
         case 'K':
            OnKeyDown(pMsg->wParam, 1, pMsg->lParam >> 16);
            return TRUE;
         case VK_RIGHT: if (shifted) return TRUE;
         case 'L':
            OnKeyDown(pMsg->wParam, 1, pMsg->lParam >> 16);
            return TRUE;
         case VK_DOWN:  if (ctrled && shifted) return spinReqFuel( 1);
                        if (shifted) pMsg->wParam = 'W'; // W = lean dn 40
         case 'J':
            OnKeyDown(pMsg->wParam, 1, pMsg->lParam >> 16);
            return TRUE;

         case 'M': case 'N': // ???
         case 'Z':
         case 'F':
         case 'Q':
         case 'W':
         case 'X':
         case 'S':
         case VK_RETURN:
            OnKeyDown(pMsg->wParam, 1, pMsg->lParam >> 16);
            return TRUE;
      }
   }
   return CDialog::PreTranslateMessage(pMsg);
}

//------------------------------------------------------------------------------

void Dttune::OnEnrich()
{
   gauges = aeGauges;
   setupGauges(true);
}

void Dttune::OnEgo()
{
   gauges = eeGauges;
   setupGauges(true);
}

void Dttune::OnPw1()
{
   gauges = pw1Gauges;
   setupGauges(true);
}

void Dttune::OnPw2()
{
   gauges = pw2Gauges;
   setupGauges(true);
}


