Source file: /~heha/mb-iwp/USB-1210/1210-201023.zip/src/1210Dlg.cpp

#include "1210.h"
#include <math.h>
#include <limits>
#include <uxtheme.h>
#define __field_ecount_opt(x)
#include <gdiplus.h>

const int FINFI=0x7F800000;
#define FINF *(float*)&FINFI

const int MAX_SAMPLE_COUNT=64*1024;
I16 m_hCard;
unsigned m_wSelectedChans=0x0F;	// Bitmaske
U16  m_wRange;
double fSampleRate=20000;	// Hz
double fBlockTime=0.1;		// s

//#undef max
//#undef min

template<class T> void setMax(T&a,T b) {if (!(a>=b)) a=b;}
template<class T> void setMin(T&a,T b) {if (!(a<=b)) a=b;}

template<class T> struct Range{
  T a,e;
  T delta() const {return e-a;}
//  void reset() {init(std::numeric_limits<T>::max(),std::numeric_limits<T>::min());}
  bool init(T b, T c) {bool r=false;if(a!=b){a=b;r=true;}if(e!=c){e=c;r=true;}return r;}
  bool init() {return init(0,0);}
  bool expand(T b) {return expand(b,b);}
  bool expand(T b, T c) {bool r=false;if(!(a<=b)){a=b;r=true;}if(!(e>=c)){e=c;r=true;}return r;}
};
//Range<float> range={-10,10};

static struct Samp{
  short*buf;
  int buflen;		// in int16_t
  F64 rate;		// Tatsächliche Samplerate
  U16 wChan[4];
  U16 wGain[4];
  U16 nChan;
  int polylen,mux;
//  int getInt(int i) const{return buf[i];}
//  int get
  float getFloat(int i,int j) const{
    return toFloat(buf[i*nChan+j],j);
  }
  Range<short>ranges[4];
  float fak[4];
  void detectRanges();
  float toFloat(int adcval, int chan) const;
}samp;


float Samp::toFloat(int adcval, int chan) const{
// if (adcval<=-32767) return -FINF;
// if (adcval>=+32767) return +FINF;
  return fak[chan]*adcval;
}


void Samp::detectRanges() {
  int k;
  for (k=0; k<nChan; k++) ranges[k].init();
  short*samples=samp.buf;
  for (int i=0; i<samp.buflen; i++) for (k=0; k<nChan; k++) ranges[k].expand(*samples++);
}

class Scale{
public:
  virtual void make(float,float,float,float)=0;	// Zweipunktkalibrierung
  virtual float scale(float) const=0;
  virtual float unscale(float) const=0;
};

class LinScale:public Scale{
  float f,O;
  virtual void make(float a,float e,float A,float E);
  virtual float scale(float x) const {return x*f+O;}
  virtual float unscale(float X) const {return (X-O)/f;}
public:
  LinScale(float s=1,float o=0):f(s),O(o) {}
};

void LinScale::make(float a,float e,float A,float E) {
  e-=a; if (!e) return;	// illegal
  E-=A; if (!E) return;	// illegal
  f=E/e;
  O=A-a*f;
}

class Trace;

RECT rcPlot;
HWND hPlot;
HBITMAP bufbm;

static void plotCalcRect(const RECT&rc) {
  SetRect(&rcPlot,rc.left+44,rc.top+8,rc.right-10,rc.bottom-24);
}

static void updatePlot(bool all=false) {
//    plotFindMinMax(r);	// Für alle Plots (Quatsch bei mehreren Skalen)
//    int vzlmax=r.e;
//    setMax(vzlmax,-r.a);
//    setMax(vzlmax,50);
//    Range<float>rf;
//    rf.e=(m_wRange==AD_B_10_V?10.0f:2.0f)*vzlmax/0x8000;
//    rf.a=-rf.e;
//    if (range.e==rf.e) 
//    else range=rf;	// alles updaten
//  }
  InvalidateRect(hPlot,all?0:&rcPlot,FALSE);
}

class Axis{
public:
  Range<float>range,extent;	// range = Datenbereich, extent = in Pixel
  Scale*scale;
  float scaleInf(float x) {
  /*if (isfinite(x))*/ return scale->scale(x);
  }
  void make() {scale->make(range.a,range.e,extent.a,extent.e);}
  static double goodDivider(double x,int*nk=0);
};

// a divider of 1, 2 or 5:
// if 0.5<x<=1 return 1, x<=2 return 2, x<=5 return 5, x<10 return 10 etc.
double Axis::goodDivider(double x,int*nk) {
  if (x<=0) return 1;		// never return <=0
  double b=ceil(log10(x));	// decimal base of x
  if (nk) *nk=b<0?-lrint(b):0;
  double e=pow(10.0,b);
  x/=e;				// now in range <0.1 1]
  if (x>0.5) x=1;
  else if (x>0.2) x=0.5;
  else x=0.2;
  return x*e;
}

static class XAxis:public Axis{
public:
  bool updateRange();
  void paint(HDC dc);
}xaxis;

bool XAxis::updateRange() {
  bool r=extent.init(rcPlot.left,rcPlot.right-1);	// -1 for plot line width
  r|=range.init(0,samp.polylen-1);
  if (r) make();
  return r;
}

void XAxis::paint(HDC dc) {
  SetTextAlign(dc,TA_CENTER|TA_TOP|TA_UPDATECP);
  double fulltime=samp.buflen/samp.rate;
  TCHAR prefix[2]={0};
  if (fulltime<10) {fulltime*=1000; prefix[0]=TEXT('m');}
  if (fulltime<10) {fulltime*=1000; prefix[0]=TEXT('µ');}
  int nk;
  double divi=goodDivider(fulltime/10,&nk);
  int width=extent.delta();
  for (double x=0; x<=fulltime; x+=divi) {
    int X=lrint(width*x/fulltime);
    TCHAR s[10];
    int l=_sntprintf(s,elemof(s),TEXT("%.*f%s"),nk,x,prefix);
    MoveToEx(dc,X+extent.a,rcPlot.bottom+2,0);
    LineTo(dc,X+extent.a,rcPlot.bottom+8);
    TextOut(dc,0,0,s,l);// draw the text on X-Axis
  }
}

static class YAxis:public Axis{
public:
  bool updateRange();
  void paint(HDC dc);
  HPEN peMinorTick;
  void allocGdi();
  void deallocGdi();
}yaxis;

bool YAxis::updateRange() {
  bool r=extent.init(rcPlot.bottom-1,rcPlot.top);
  Range<float>all;
//  all.reset();
  all.a=+FINF;
  all.e=-FINF;
  for (int k=0; k<samp.nChan; k++) all.expand(samp.toFloat(samp.ranges[k].a,k),samp.toFloat(samp.ranges[k].e,k));
  float vzlmax=all.e;
  setMax(vzlmax,-all.a);
  setMax(vzlmax,0.001F);	// Mit der Auflösung nicht übertreiben!! TODO: Programmatisch lösen!
  vzlmax=goodDivider(vzlmax);
  r|=range.init(-vzlmax,vzlmax);
  if (r) make();
  return r;
}

void YAxis::allocGdi() {
  peMinorTick=CreatePen(PS_SOLID,1,RGB(132,130,132));
}

void YAxis::deallocGdi() {
  DeletePen(peMinorTick);
}


void YAxis::paint(HDC dc) {
  const int y_divider=4;

  	// calculate the interval for tick/grid
  int y_intv = -extent.delta() / y_divider;
  double delta=range.delta()/y_divider;
  int nk;
  goodDivider(delta,&nk);	// TODO: Diesen Step verwenden!!

  SetTextAlign(dc,TA_RIGHT|TA_BASELINE);
  int i;
  for (i=0; i<= y_divider; i++ ) {
    int y=extent.a-5-(i*y_intv);
    MoveToEx(dc,rcPlot.left-12,y,0);
    LineTo(dc,rcPlot.left-3,y);
    TCHAR s[10];
    int l=_sntprintf(s,elemof(s),TEXT("%.*f"),nk,delta*i+range.a);
    TextOut(dc,rcPlot.left-14,y+4,s,l);
  }
  SelectPen(dc,peMinorTick);
  for (i=0; i< y_divider; i++) {
    int y=extent.a -5 - (i*y_intv) - y_intv/2;
    MoveToEx(dc,rcPlot.left-9, y, 0);
    LineTo(dc,rcPlot.left-4, y);
  }
}


class Data{
public:
  virtual float get(int) const=0;
};

class PhysData:public Data{
  int ch;
public:
  PhysData(int c):ch(c) {}
  virtual float get(int i) const {return samp.getFloat(i,ch);}
};

class PowerData:public Data{
  int ch_u,ch_i;
public:
  PowerData(int chu,int chi):ch_u(chu),ch_i(chi) {}
  virtual float get(int i) const {return samp.getFloat(i,ch_u)*samp.getFloat(i,ch_i);}
};

class Trace{
//  static bool gdiplus=true;
  Gdiplus::Pen   *pnLine;
  Gdiplus::Brush *brLine,*brMinMax,*brStdDev,*brPoly;
  Gdiplus::PointF*ptLine,*ptMinMax,*ptStdDev,*ptPoly;
public:
  static const COLORREF colors[4];
  void deallocate();
  void allocGdi(int index);
  void deallocGdi();
  int polylen;
  int nLines;	// bei Mehrfachtrigger und <=4 Ergebnis-Linien, sonst 1
  bool lineAsPoly;
  Axis*xaxis,*yaxis;
  Data*ydata;
  void make();	// Generiere Punktkoordinaten für Hauptfenster, noch nicht für Miniatur
  void paintPoly(Gdiplus::Graphics&)const;	// Polygon zwischen Kurvenzügen
  void paintMinMax(Gdiplus::Graphics&)const;	// Unterste Polygonlage
  void paintStdDev(Gdiplus::Graphics&)const;	// Darüberliegende Polygonlage
  void paintLine(Gdiplus::Graphics&)const;	// Kurvenzug
  void paintMarkers(Gdiplus::Graphics&)const;	// Punkt-Marker
}traces[4];

const COLORREF Trace::colors[4]={RGB(255,0,0),RGB(255,255,0),RGB(128,128,255),RGB(64,128,64)};

void Trace::deallocate() {
  if (ptLine) {delete[] ptLine; ptLine=0;}
  if (ptMinMax) {delete[] ptMinMax; ptMinMax=0;}
  if (ptStdDev) {delete[] ptStdDev; ptStdDev=0;}
}

void Trace::allocGdi(int index) {
  COLORREF cr=colors[index];
  Gdiplus::Color c(GetRValue(cr),GetGValue(cr),GetBValue(cr));
  pnLine=new Gdiplus::Pen(c);
  brLine=new Gdiplus::SolidBrush(c);
  c.SetValue(c.GetValue()&0xC0FFFFFF);
  brStdDev=new Gdiplus::SolidBrush(c);
  c.SetValue(c.GetValue()&0x80FFFFFF);
  brMinMax=new Gdiplus::SolidBrush(c);
// Erst mal kein Polygon
}

void Trace::deallocGdi() {
  if (brPoly) {delete brPoly; brPoly=0;}
  if (brMinMax) {delete brMinMax; brMinMax=0;}
  if (brStdDev) {delete brStdDev; brStdDev=0;}
  if (brLine) {delete brLine; brLine=0;}
  if (pnLine) {delete pnLine; pnLine=0;}
}

void Trace::make() {
  if (polylen!=samp.polylen) deallocate();
  polylen=samp.polylen;
  if (!nLines) nLines=1;
  if (!ptLine) ptLine=new Gdiplus::PointF[polylen*nLines];
  if (samp.mux>1 && !ptMinMax) ptMinMax=new Gdiplus::PointF[polylen<<1];
  if (samp.mux>1 && !ptStdDev) ptStdDev=new Gdiplus::PointF[polylen<<1];
  
  for (int i=0; i<polylen; i++) {
    float sum=0;
    double sq=0;
    Range<float>minmax;
    minmax.a=+FINF; minmax.e=-FINF;
    for (int j=0; j<samp.mux; j++) {
      float y=ydata->get(i*samp.mux+j);
      minmax.expand(y);
      sum+=y;
      sq+=(double)y*y;
    }
    sum/=samp.mux;
    float dev=float((double)sum*sum-sq/samp.mux);	// TODO: Stimmt das??
    ptLine[i].X=xaxis->scaleInf(i);
    ptLine[i].Y=yaxis->scaleInf(sum);
    if (samp.mux>1) {
      int j=polylen*2-1-i;
      ptMinMax[j].X=ptMinMax[i].X=ptLine[i].X;
      ptMinMax[i].Y=yaxis->scaleInf(minmax.e);
      ptMinMax[j].Y=yaxis->scaleInf(minmax.a);
      ptStdDev[j].X=ptMinMax[i].X=ptLine[i].X;
      ptStdDev[i].Y=yaxis->scaleInf(sum+dev);
      ptStdDev[j].Y=yaxis->scaleInf(sum-dev);
    }
  }
}

void Trace::paintPoly(Gdiplus::Graphics&g) const{
  if (brPoly && ptPoly) g.FillPolygon(brPoly,ptPoly,polylen<<1);
}

void Trace::paintMinMax(Gdiplus::Graphics&g) const{
  if (brMinMax && ptMinMax) g.FillPolygon(brMinMax,ptMinMax,polylen<<1);
}

void Trace::paintStdDev(Gdiplus::Graphics&g) const{
  if (brStdDev && ptStdDev) g.FillPolygon(brStdDev,ptStdDev,polylen<<1);
}

void Trace::paintLine(Gdiplus::Graphics&g) const{
  g.DrawLines(pnLine,ptLine,polylen);
}

static void plotPaintTraces(HDC dc) {
  int k;
  Gdiplus::Graphics g(dc);
  for (k=0; k<samp.nChan; k++) traces[k].paintPoly(g);
  if (samp.mux>1) {
    for (k=0; k<samp.nChan; k++) traces[k].paintMinMax(g);
    for (k=0; k<samp.nChan; k++) traces[k].paintStdDev(g);
  }
  for (k=0; k<samp.nChan; k++) traces[k].paintLine(g);
}


static void plotPaint(HDC dc) {
  RECT rc;
  GetClientRect(hPlot,&rc);
  if (!bufbm) bufbm=CreateCompatibleBitmap(dc,rc.right,rc.bottom);
  HDC memdc=CreateCompatibleDC(dc);
  SelectFont(memdc,GetCurrentObject(dc,OBJ_FONT));
  SetBkColor(memdc,GetBkColor(dc));
  HBITMAP obm=SelectBitmap(memdc,bufbm);
  SaveDC(memdc);
  ExcludeClipRect(memdc,rcPlot.left,rcPlot.top,rcPlot.right,rcPlot.bottom);
  FillRect(memdc,&rc,GetSysColorBrush(COLOR_3DFACE));// fill axes background
  RestoreDC(memdc,-1);
  FillRect(memdc,&rcPlot,GetStockBrush(BLACK_BRUSH));	// fill plotarea background
  if (samp.nChan) {

    yaxis.paint(memdc);
    xaxis.paint(memdc);

    plotPaintTraces(memdc);
  }
  BitBlt(dc,0,0,rc.right,rc.bottom,memdc,0,0,SRCCOPY);
  SelectBitmap(memdc,obm);
  DeleteDC(memdc);
}

static void cb() {
//  OutputDebugString(TEXT("Hier\n"));
  UD_AI_AsyncDblBufferTransfer(m_hCard,samp.buf);
  samp.detectRanges();
  bool all=xaxis.updateRange()|yaxis.updateRange();
  for (int k=0; k<samp.nChan; k++) traces[k].make();
  updatePlot(all);
}

static bool onStart(HWND wnd) {
  int err = UD_AI_Channel_Config( m_hCard, UD_AI_Differential, UD_AI_Differential, UD_AI_Differential,  UD_AI_Differential );
  if (err) {MBox(wnd,4,MB_OK,err); return false;}
  err = UD_AI_Trigger_Config( m_hCard, UD_AI_CONVSRC_INT, UD_AI_TRGMOD_POST, UD_AI_TRGSRC_SOFT, 0, 0, 0, 0 );
  if (err) {MBox(wnd,5,MB_OK,err); return false;}
  err = UD_AI_AsyncDblBufferMode(m_hCard, true); // single-buffer mode
  if (err) {MBox(wnd,6,MB_OK,err); return false;}

  for (int k=0; k<4; k++) if (m_wSelectedChans&1<<k) {
    samp.wChan[samp.nChan] = k;
    samp.wGain[samp.nChan] = m_wRange;	// TODO: Per channel
    samp.nChan++;	// popcount(m_wSelectedChans)
  }
  if (!samp.nChan) return false;

  err = UD_GetActualRate(m_hCard,fSampleRate,&samp.rate);
  SendDlgItemMessage(wnd,21,UDM_SETPOS32,0,lrint(samp.rate));
  samp.buflen=lrint(samp.rate*fBlockTime)&~255;	// ganze 256 Byte
  setMax(samp.buflen,256);
  setMin(samp.buflen,MAX_SAMPLE_COUNT);

  samp.mux=samp.buflen>>10;	// 513..1024 Punkte anstreben
  if (!samp.mux) samp.mux=1;
  samp.polylen=samp.buflen/samp.mux;
	// allocate a memory for user DMA buffer
  DWORD mem_size = samp.buflen * samp.nChan * 2;	// 2 = Doppel-Puffer
  samp.buf = new short[mem_size];
  if (!samp.buf) {MBox(wnd,3,MB_OK); samp.nChan=0; return false;}
  for (k=0; k<samp.nChan; k++) {
    Trace&tr=traces[k];
    tr.xaxis=&xaxis;
    tr.yaxis=&yaxis;
    float gain=samp.wGain[k]==1?10.0F:2.0F;
    gain/=32768;
    samp.fak[k]=gain;
//TODO: Hier kommt der Shunt ins Spiel! Und dann noch die Extra-Traces für die Leistungsberechnung!
    tr.ydata=new PhysData(k);	// Callback-Zeiger allesamt bereit machen
    tr.allocGdi(samp.wChan[k]);
  }
  UD_AI_EventCallBack(m_hCard,1,DBEvent,cb);
  err = UD_AI_ContReadMultiChannels(m_hCard,samp.nChan,samp.wChan,samp.wGain,
   0,mem_size,samp.rate,ASYNCH_OP);
  if (err) {MBox(wnd,8,MB_OK,err); delete[] samp.buf; samp.nChan=0; return false;}
//  SetTimer(wnd,1,200,0);
  return true;
}

INT_PTR CALLBACK MainDlgProc(HWND wnd, UINT msg, WPARAM wParam, LPARAM lParam) {
  static HFONT bold;
  static ULONG_PTR token;

  switch (msg) {
    case WM_INITDIALOG: {
      HMENU sysm = GetSystemMenu(wnd,FALSE);
      TCHAR s[64];
      LoadString(hInstance,101,s,elemof(s));
      AppendMenu(sysm,MF_SEPARATOR,0,0);
      AppendMenu(sysm,MF_STRING,0x70,s);
      HICON icon=LoadIcon(hInstance,MAKEINTRESOURCE(100));
      SetClassLongPtr(wnd,GCL_HICON,(LONG_PTR)icon);	// Set big icon
      SetClassLongPtr(wnd,GCL_HICONSM,(LONG_PTR)icon);	// Set small icon
      Gdiplus::GdiplusStartupInput si;	// Defaultkonstruktor
      Gdiplus::GdiplusStartup(&token,&si,0);
      hPlot=GetDlgItem(wnd,42);
      RECT rc;
      GetClientRect(hPlot,&rc);
      plotCalcRect(rc);

      HWND hCombo=GetDlgItem(wnd,103);
      ComboBox_AddString(hCombo,TEXT("± 10 V"));
      ComboBox_AddString(hCombo,TEXT("± 2 V"));
      ComboBox_SetCurSel(hCombo,0);
      PostMessage(wnd,WM_COMMAND,MAKELONG(103,CBN_SELCHANGE),(LPARAM)hCombo);
	
	// set the default SampleRate
      SetDlgItemInt(wnd,101,lrint(fSampleRate),false);
      HWND ud=CreateUpDownControl(WS_CHILD|WS_BORDER|WS_VISIBLE|UDS_ALIGNRIGHT|UDS_ARROWKEYS|UDS_HOTTRACK|UDS_SETBUDDYINT,
        0,0,0,0,wnd,21,0,GetDlgItem(wnd,101),100,0,0);
      SendMessage(ud,UDM_SETRANGE32,1000,2000000);
      SendMessage(ud,UDM_SETPOS32,0,lrint(fSampleRate));
      SendDlgItemMessage(wnd,101,EM_SETMODIFY,TRUE,0);	// Startschuss für EN_CHANGE geben
      HFONT f=(HFONT)SendMessage(wnd,WM_GETFONT,0,0);
      LOGFONT lf;
      GetObject(f,sizeof lf,&lf);
      lf.lfWeight=700;
      bold=CreateFontIndirect(&lf);
      for (int k=0; k<4; k++) {
        HWND w=GetDlgItem(wnd,64+k);
//	if (k<2) SetWindowTheme(w,L"x",L"x");
	SetWindowFont(w,bold,FALSE);
        if (m_wSelectedChans&1<<k) SendMessage(w,BM_SETCHECK,BST_CHECKED,0);
      }
      xaxis.scale=new LinScale();
      yaxis.scale=new LinScale();
    }return TRUE;

    case WM_SYSCOMMAND: switch (wParam&0xFFFF0) {
      case 0x70: MBox(wnd,102,MB_OK); break;
    }break;

    
    case WM_COMMAND: switch (wParam) {
      case 64:
      case 65:
      case 66:
      case 67: m_wSelectedChans^=1<<(wParam-64); break;
      case MAKELONG(101,EN_CHANGE): /*if (Edit_GetModify((HWND)lParam))*/ {
        BOOL err=TRUE;
        unsigned u=SendDlgItemMessage(wnd,21,UDM_GETPOS32,0,(LPARAM)&err);
        if (!err && u) {
          fSampleRate=u;
//          updatePlot(true);
        }
      }break;
      case MAKELONG(103,CBN_SELCHANGE): {
	int nIndex = ComboBox_GetCurSel((HWND)lParam);
	switch (nIndex)	{
	  case 0: // +/-10V 
	  m_wRange = AD_B_10_V;
//	  range.e = 10.0;	 range.a = -10.0;
	  break;
	  case 1: // +/-2V
	  m_wRange = AD_B_2_V;
//	  range.e = 2.0;	range.a = -2.0;
	  break;
	}
	updatePlot(true);
      }break;
      case 11: onStart(wnd); break;
      case 12: {
	ULONG dwAccessCnt;
	I16 nErr = UD_AI_AsyncClear(m_hCard,&dwAccessCnt);
        delete[] samp.buf;
	for (int k=0; k<samp.nChan; k++) {
	  Trace&tr=traces[k];
	  tr.deallocate();
	  tr.deallocGdi();
	  delete tr.ydata;
	}
	samp.nChan=0;
//	KillTimer(wnd,1);
      }break;
      case IDCANCEL: EndDialog(wnd,wParam); break;
    }break;
    
    case WM_NOTIFY: {
      NMHDR&hdr=*(NMHDR*)lParam;
      switch (hdr.idFrom) {
        case 21: if (hdr.code==UDN_DELTAPOS) {
          NMUPDOWN&ud=*(NMUPDOWN*)lParam;
          ud.iDelta*=ud.iPos>=100000?10000:1000;
        }break;
	case 64:
	case 65:
	case 66:
	case 67: if (hdr.code==NM_CUSTOMDRAW) {
	  NMCUSTOMDRAW&cd=*(NMCUSTOMDRAW*)lParam;
	  if (cd.dwDrawStage==CDDS_PREPAINT) {
            SetTextColor(cd.hdc,Trace::colors[hdr.idFrom-64]);
            SetBkColor(cd.hdc,0);
	    RECT rc={0,0,11,8};
	    MapDialogRect(wnd,&rc);
	    int w=rc.right-rc.left;
	    CopyRect(&rc,&cd.rc);
	    rc.left+=w;
	    TCHAR s[64];
	    int l=GetWindowText(hdr.hwndFrom,s,elemof(s));
	    DrawText(cd.hdc,s,l,&rc,DT_LEFT|DT_VCENTER|DT_SINGLELINE|DT_NOPREFIX);
	    if (cd.uItemState&CDIS_FOCUS) {
	      DrawText(cd.hdc,s,l,&rc,DT_LEFT|DT_VCENTER|DT_SINGLELINE|DT_NOPREFIX|DT_CALCRECT);
	      InflateRect(&rc,1,1);
	      DrawFocusRect(cd.hdc,&rc);
	    }
	    return SetDlgMsgResult(wnd,WM_NOTIFY,CDRF_SKIPDEFAULT);
	  }
	}break;
      }
    }break;

    case WM_CTLCOLORSTATIC: {
      UINT id=GetDlgCtrlID((HWND)lParam);
      if (64<=id && id<68) {
        SetTextColor((HDC)wParam,Trace::colors[id-64]);
        SetBkColor((HDC)wParam,0);
        return (BOOL)GetSysColorBrush(COLOR_3DFACE);
      }
    }break;
    
    case WM_TIMER: switch (wParam) {
      case 1: {
	bool bStopped;
	ULONG dwAccessCnt;
	I16 nErr = UD_AI_AsyncCheck( m_hCard, &bStopped, &dwAccessCnt);
	if (!nErr && bStopped) {
	  KillTimer(wnd,1);
	  nErr = UD_AI_AsyncClear(m_hCard,&dwAccessCnt);
	  updatePlot();
	}else if (nErr==ErrorAdFifoFull){
	  KillTimer(wnd,1);
	  nErr = UD_AI_AsyncClear(m_hCard,&dwAccessCnt);
	  MBox(wnd,7,MB_OK);
        }
      }break;
    }break;
    
    case WM_DRAWITEM: {
      DRAWITEMSTRUCT&dis=*(DRAWITEMSTRUCT*)lParam;
      plotPaint(dis.hDC);
    }break;

    case WM_DESTROY: {
      KillTimer(wnd,1);
      DeleteFont(bold);
      Gdiplus::GdiplusShutdown(token);
    }break;
  }
  return FALSE;
}

Detected encoding: ANSI (CP1252)4
Wrong umlauts? - Assume file is ANSI (CP1252) encoded