#define _WIN32_WINNT 0x0500
#if _MSC_VER > 1400
#define _INC_SWPRINTF_INL_
extern "C" int _declspec(dllimport) _cdecl swprintf(wchar_t*,const wchar_t*,...);
#endif
#include <windows.h>
#include <windowsx.h>
#include <shlwapi.h>
#include <commctrl.h>
#include <setupapi.h>
#include <devguid.h>
#include "he2325u.h"
#include <stdio.h>
#include <math.h>
#include <tchar.h>
#include <limits.h>
#include <float.h>
#include "dmm.h"
enum EVIA {VIACOM=1,VIAUSB};
HINSTANCE ghInst;
HWND ghMainWnd;
HICON ghIcons[2];
TCHAR ConfName[CONFMAX]; // e.g. "UT61B@USB1", not case sensitive
TCHAR HelpName[MAX_PATH];
static const COLORREF TraceColor[4]={0x008000,0x800000,0x800080,0x808000};
struct GDI{ // holds GDI resources needed for painting the client area
HFONT fntBig,fntEx,fntUniT; // four fonts
HFONT fntRo; // for extra (2,3,4) readout
HPEN penGrid, penGraph[4]; // pens used in scope mode and for backtrace
HPEN penWide[4]; // for the right-side vertical äbargraphô
HBRUSH brFill[4]; // for backtrace graph (fillig to zero)
HBRUSH brBar[4]; // for bargraph (less white)
HPEN penDiv; // visible field divider
HPEN penTrig;
HPEN penNull; // for border-less polygon
HBITMAP bmDblBuf; // bitmap and DC, only used for double buffering
HFONT fntAxes[2]; // two fonts for axes (backtrace)
HDC dcDblBuf;
void Make(int,int); // (Re)Creates fonts and doublebuffer bitmap for given client pixel size
void Make(); // Same but fetches the client size beforehand
void Delete(bool FontToo=true);// Releases all GDI resources, for minimized display
enum EUNITFONT{NA,INSTALLED,LOADED} unitfont; // availability of Uni-Tech's symbol and 7segment font
private:
void UnitfontLoad();
void UnitfontUnload();
static int CALLBACK EnumFontFamProc(const LOGFONT*,const TEXTMETRIC*,DWORD,LPARAM);
static HFONT MakeFont(int,int,int,LPCTSTR);
static void DeleteObj(HGDIOBJ&);
}gGdi;
TCHAR StdMBoxTitle[64];
// Assumption: The decimal character is never an MBCS character! Is this true?
WCHAR cDecimalW; // The decimal delimiter
char cDecimalA; // ANSI version
DWORD gDdeInst; // DDE instance
HSZ hszDmm; // String handle for "DMM"
HSZ hszTopic; // String handle for e.g. "UT71B@COM2"
UINT CF_XlTable; // Clipboard Format
EXTERN_C int _fltused; // MSVC6 needs this
int _fltused;
#ifdef UNICODE
#define cDecimal cDecimalW
#else
#define cDecimal cDecimalA
#endif
#define MAXDEPTH 400 // History length of backtrace, in samples
/* This program internally uses multiple character sets:
* ANSI - the current ANSI code page, for CP1252, cannot display Ohm but ░
* UNICODE - more exactly: UTF-16 BMP0, all characters except symbols like Diode, Beeper etc.
* UTF-8, same characters as UNICODE
* ASCII, only 7-bit characters
* OEM, the DOS code page, maybe
* UNI-T, according to the seven-segment display true type font, all displayable characters and symbols
*/
#ifdef UNICODE
#define MyLoadStringW(id,buf,len) LoadString(ghInst,id,buf,len)
#else
int MyLoadStringW(UINT id, LPWSTR buf, int len) {
HRSRC r=FindResource(ghInst,MAKEINTRESOURCE((id>>4)+1),RT_STRING);
if (!r) return 0;
PCWSTR p=(PCWSTR)LoadResource(ghInst,r);
for (id&=15; id; id--) {
p+=*p+1;
}
len--;
if (len<0) return *p+1; // size, in characters, needed for allocation
if (len>*p) len=*p;
p++;
memcpy(buf,p,len*2);
buf[len]=0;
return len;
}
#endif
const __int64 _sNaN = 0x7FF0000000000001; // signaling NaN with payload 1 (not used)
const __int64 _qNaN = 0xFFFFFFFFFFFFFFFF; // quiet NaN (used for missing samples and values)
const __int64 _pInf = 0x7FF0000000000000; // positive infinity (used for positive overflow)
const __int64 _nInf = 0xFFF0000000000000; // negative infinity (used for negative overflow)
#ifdef _M_IX86
EXTERN_C void _cdecl _ftol2_sse() {
void _cdecl _ftol(); // avoid parameter stacking with void
_ftol(); // Anyone an idea how to say the compiler to emit _ftol instead of _ftol2_sse?
} // (Same applies to __p__iob and __iob_func, see "sumatrapdf" source)
#endif
struct RECTS{
short left,top,right,bottom;
inline void operator=(const RECT&); // set
inline void operator>>(RECT&) const; // get
};
void RECTS::operator=(const RECT&r) {
left=short(r.left);
top=short(r.top);
right=short(r.right);
bottom=short(r.bottom);
}
void RECTS::operator>>(RECT&r) const {
r.left=left;
r.top=top;
r.right=right;
r.bottom=bottom;
}
// The data structure saved into registry (fixed part)
struct TConfig{
RECTS winpos; // "restored" window position
char showCmd; // SW_SHOWxxx
union{
BYTE checkBits; // checkbox group bits (currently 5)
struct{
BYTE doubleBuf:1; // double buffering for drawing operation
BYTE flashIcon:1; // flashing icon
BYTE sevenSeg:1; // sevensegment display
BYTE glassFx:1; // Vista Glass effect (TODO)
BYTE ddePoint:1; // Use decimal point instead of Windows setting
BYTE surviveReboot:1;// Auto-resume on reboot
};
};
WORD serialConfig; // some future flags for serial interface
DWORD baudRate; // baudrate or other settings used for interface, if not fixed by DMM construction
}Config;
int _cdecl MBox(HWND Wnd, UINT id, UINT Type, ...) {
TCHAR buf[256],fmt[256];
LoadString(ghInst,id,fmt,elemof(fmt));
_sntprintf(buf,elemof(buf),fmt,(va_list)(&Type+1));
return MessageBox(Wnd,buf,StdMBoxTitle,Type);
}
/********************************************************************************
* Loading plugin DLLs and enumerating all available multimeters in linked list *
********************************************************************************/
ENUMINFO *el;
// Enumerate one DLL entry point - or one built-in list
static bool EnumOne(bool(_stdcall*p)(int,ENUMINFO*)) {
bool ret=false;
for (int i=0;;i++) {
ENUMINFO*e=new ENUMINFO; // allocate one element
if (p(i,e)) {
ret=true; // at least one index is valid
e->next=el;
el=e;
}else{
delete e;
break;
}
}
return ret;
}
// Enumerate all built-in lists (currently one) and all available "*.dmm" plugin DLLs
static void EnumAll() {
EnumOne(EnumUT);
TCHAR sname[MAX_PATH];
GetModuleFileName(0,sname,elemof(sname));
lstrcpy(PathFindFileName(sname),T("*.dmm"));
WIN32_FIND_DATA fd;
HANDLE fh;
fh=FindFirstFile(sname,&fd);
if (fh!=INVALID_HANDLE_VALUE) {
do{
HINSTANCE hLib=LoadLibrary(fd.cFileName);
if (hLib) {
FARPROC p=GetProcAddress(hLib,"EnumAll");
if (p && EnumOne((bool(_stdcall*)(int,ENUMINFO*))p));
else FreeLibrary(hLib);
}
}while (FindNextFile(fh,&fd));
FindClose(fh);
}
}
int DoubleToString(double v, int nk, LPTSTR buf, UINT buflen) {
if (!_finite(v)) return _sntprintf(buf,buflen,T("NaN"));
int i=_sntprintf(buf,buflen,T("%.*f"),nk,v);
LPTSTR p=_tcschr(buf,'.');
if (p) *p=cDecimal;
return i;
}
struct FEATURE{
enum EFLAGS{NONE=0,PREPENDSPACE=1};
DWORD f; // When no readout is available, this (resource) string is displayed
LPWSTR GenStringW(LPWSTR,EFLAGS f=NONE) const;
bool Set(DWORD);
int Out(HDC dc, bool measure=false) const;
inline int Measure(HDC dc) const {return Out(dc,true);};
private:
static LPWSTR _addstringW(LPWSTR,DWORD,EFLAGS);
}Features; // A bitfield for various features reported by connected multimeter
struct BT{ // One backtrace item, a time+value item
DWORD tick; // time value, in ms
float data; // data value, in <unit>
};
int NumReadouts; // The number of readouts that the multimeter reports (mostly 1, up to 4)
struct RO:READOUT{ // expand structure by routines to interpret the data in various ways
// Backtrace members
BT*bt; // back-trace data, as time+value pairs
int btLen,btIdx; // length and pointer-to back-trace data
RECT btR; // area for back-trace
// struct RECTF{
// float left,top,right,bottom;
// }btFull,btZoom;
void BtOut(HDC dc,int left, int top, int right, int bottom); // show back-trace data
inline void BtClear() {if (bt) LocalFree(bt); bt=NULL; btLen=btIdx=0;};
void BtAdd(float v);
bool BtHandleMouse(UINT,UINT,int,int);
// number display members
enum EFLAGS{NONE=0,UNITSPACE=1,UNITPREFIX=2,WITHUNIT=4,LEADSPACE=8,CONVDECIMAL=16,UNITFONT=32,
NORMAL=16+4+2+1,SEVENSEG=1+2+32};
LPWSTR GenUnitW (LPWSTR,EFLAGS f=NONE) const;
LPSTR GenUnitA (LPSTR, EFLAGS f=NONE) const;
LPWSTR GenStringW(LPWSTR,EFLAGS f=NORMAL) const;
LPSTR GenStringA(LPSTR, EFLAGS f=NORMAL) const;
#ifdef UNICODE
inline LPTSTR GenUnit (LPTSTR d,EFLAGS f=NONE) const { return GenUnitW(d,f);};
inline LPTSTR GenString(LPTSTR d,EFLAGS f=NORMAL) const { return GenStringW(d,f);};
#else
inline LPTSTR GenUnit (LPTSTR d,EFLAGS f=NONE) const { return GenUnitA(d,f);};
inline LPTSTR GenString(LPTSTR d,EFLAGS f=NORMAL) const { return GenStringA(d,f);};
#endif
inline LPTSTR GenIconTitle(LPTSTR d) const { return GenString(d);};
DWORD Set(const READOUT*);
inline DWORD Clear();
int Out7Seg(HDC,int x=0,int y=0,bool measure=false) const;
inline int Measure7Seg(HDC dc) const { return Out7Seg(dc,0,0,true);};
private:
char _unitprefix(EFLAGS f) const;
char _unitcode(EFLAGS f) const;
}Readout[4];
SCOPEDATA Scope;
struct TD:TRACEDATA{
static int datalen(const SCOPEDATA&sd=Scope);
int operator[](int) const;
void Set(const TRACEDATA&, int len);
void ReAlloc(int oldlen, int newlen);
// LPCWSTR GetUnitW() const;
static void OutGrid(HDC, RECT*);
void OutTrig(HDC, const RECT*) const; // draw trigger cross
void OutTrace(HDC, const RECT*, HPEN) const;
void OutZMark(HDC, const RECT*, HPEN) const; // draw triangle left outside grid
static void OutBase(HDC, char, char, char, COLORREF);
void OutBase(HDC dc,COLORREF c) const{ OutBase(dc,devicode,unitcode,autorange,c);};
private:
void CalcPoint(const RECT*, POINT*, int,int) const;
void CalcPointList(const RECT*, POINT*) const;
}Trace[4];
// get length of trace data in bytes
int TD::datalen(const SCOPEDATA&sd) {
return (sd.datasize&0x0F)*sd.tracelen;
}
int TD::operator[](int i) const {
union{
const void*v;
const char*c;
const short*s;
//const int*i;
const BYTE*b;
const WORD*w;
//const DWORD*d;
}p;
p.v=data;
switch (Scope.datasize) {
case 1: return p.c[i];
case 2: return p.s[i];
//case 4: return p.i[i];
case 0x81: return p.b[i]-0x80;
case 0x82: return p.w[i]-0x8000;
//case 0x84: return p.d[i]-0x80000000L;
}
return 0;
}
// allocate or free space for copy of raw trace data
void TD::ReAlloc(int oldlen, int newlen) {
if (!data) oldlen=0;
if (oldlen==newlen) return; // do nothing
if (data) LocalFree((void*)data); data=NULL;
if (newlen) data=LocalAlloc(LMEM_FIXED,newlen);
}
// copies data, buffer must be previously allocated
void TD::Set(const TRACEDATA&td, int len) {
*(__int64*)this=*(__int64*)&td; // copy 8 bytes header info
RtlCopyMemory((void*)data,td.data,len);
}
DWORD SetScope(const SCOPEDATA*sd=NULL) {
DWORD ret=0;
int oldlen=TD::datalen();
int newlen=sd ? TD::datalen(*sd) : 0;
int newtraces=sd ? sd->n : 0;
int i;
for (i=0; i<elemof(Trace); i++) {
if (i==Scope.n) oldlen=0;
if (sd && i==sd->n) newlen=0;
Trace[i].ReAlloc(oldlen,newlen); // make or free space
}
if (sd) {
if (Scope.n!=sd->n) ret|=1<<19; // change GDI objects too
Scope=*sd; // copy SCOPEDATA structure
const TRACEDATA *td=(const TRACEDATA*)(sd+1); // TRACEDATA array follows
newlen=TD::datalen(*sd);
for (i=0; i<sd->n; i++) { // copy traces (must be 1,2,3 or 4 on call of SetScope!)
Trace[i].Set(*td++,newlen);
}
ret|=1<<18; // Assume that at least something has changed
}else{
if (Scope.n) {
Scope.n=0; // no traces = no scope
ret|=3<<18; // change GDI objects and scope display
}
}
return ret;
}
void TD::CalcPoint(const RECT*r, POINT*p, int x, int y) const {
int xx=r->right-r->left;
int yy=r->bottom-r->top;
int ym=(r->top+r->bottom)>>1;
p->x=r->left+MulDiv(xx,x,Scope.tracelen-1);
p->y=ym-MulDiv(yy,y+ofs,Scope.fullscale);
}
void TD::CalcPointList(const RECT*r, POINT*pl) const {
for (int i=0; i<Scope.tracelen; i++) {
CalcPoint(r,pl,i,(*this)[i]);
pl++;
}
}
void SetWindowTitle(void) {
if (IsIconic(ghMainWnd) && *Readout[0].digits) {
TCHAR buf[32];
Readout[0].GenIconTitle(buf);
SetWindowText(ghMainWnd,buf);
}else SetWindowText(ghMainWnd,ConfName);
}
// calculates base * 10^expo
double e10(char neg, int base, char expo) {
if (neg) base=-base;
return base*pow(10.0L,expo);
}
char comma() {
return Config.ddePoint?'.':cDecimalA;
}
void ptc(char*s) {
if (Config.ddePoint) return;
s=strchr(s,'.');
if (s) *s=cDecimalA;
}
// Check whether this DMM name is the current DMM (e.g. "UT60A")
bool IsCurrentDmm(LPTSTR s) {
LPTSTR p=_tcschr(ConfName,'@');
if (p) return StrCmpNI(s,ConfName,(int)(p-ConfName))==0;
return lstrcmpi(s,ConfName)==0;
}
// Check whether this port is the current port (e.g. "USB1")
bool IsCurrentPort(LPTSTR s) {
LPTSTR p=_tcschr(ConfName,'@');
if (p) return lstrcmpi(s,p+1)==0;
return false; // no port
}
// Check whether a string matches a DDE item
static int GetItemIdx(PCTSTR s) {
static const TCHAR Names[]=T("value\0unit\0trace\0min\0max\0\features\0");
int l=lstrlen(s);
if (!l) return 0;
PCTSTR p=Names;
for (int i=1; *p; i++, p+=lstrlen(p)+1) {
if (!StrCmpNI(p,s,l)) return i;
}
return -1; // no match
}
HDDEDATA CALLBACK DdeCallback(UINT uType,UINT uFmt,HCONV hConv,HSZ hsz1,HSZ hsz2,
HDDEDATA hData,ULONG_PTR,ULONG_PTR) {
HDDEDATA ret=DDE_FNOTPROCESSED;
#ifdef _DEBUG
char s[64];
_snprintf(s,64,"DdeCallback: uType=0x%X\n",uFmt);
OutputDebugStringA(s);
#endif
switch (uType) {
/* SERVER */
case XTYP_ADVSTART: if (uFmt==CF_TEXT || uFmt==CF_UNICODETEXT || uFmt==CF_XlTable) ret=(HDDEDATA)TRUE; break;
case XTYP_CONNECT: if (!DdeCmpStringHandles(hsz1,hszTopic)) ret=(HDDEDATA)TRUE; break;
case XTYP_WILDCONNECT: {
HSZ list[3]={hszDmm,hszTopic};
ret=DdeCreateDataHandle(gDdeInst,(LPBYTE)list,sizeof list,0,0,0,0);
}break;
case XTYP_ADVREQ:
case XTYP_REQUEST: {
RO::EFLAGS f=RO::EFLAGS(RO::UNITSPACE|RO::WITHUNIT|~Config.checkBits&RO::CONVDECIMAL);
TCHAR item[32],c;
int i,j,k;
DdeQueryString(gDdeInst,hsz2,item,elemof(item),CP_WINNEUTRAL);
for (i=0; c=item[i]; i++) {
if (!IsCharAlpha(c)) break;
}
j=i; if (c && !IsCharAlphaNumeric(c)) j++; // skip " ", "[", "(" or similar non-numeric delimiter
k=_ttoi(item+j); // get the index
if (k>=NumReadouts) return NULL; // wrong index attached
item[i]=0; // strip index
if (uFmt==CF_UNICODETEXT) {
WCHAR buf[32], *p=buf;
switch (GetItemIdx(item)) {
case 0:
case 1: p=Readout[k].GenStringW(p,f); break;
case 2: p=Readout[k].GenUnitW(p); break;
}
if (p!=buf) ret=DdeCreateDataHandle(gDdeInst,(LPBYTE)buf,DWORD((p-buf+1)<<1),0,hsz2,uFmt,0);
}else if (uFmt==CF_TEXT) {
char buf[32], *p=buf;
switch (GetItemIdx(item)) {
case 0:
case 1: p=Readout[k].GenStringA(p,f); break;
case 2: p=Readout[k].GenUnitA(p); break;
}
if (p!=buf) ret=DdeCreateDataHandle(gDdeInst,(LPBYTE)buf,DWORD(p-buf+1),0,hsz2,uFmt,0);
}else if (uFmt==CF_XlTable) {
#pragma pack(1)
struct XLTABLE{
WORD tdtTable,dimensions,rows,cols;
WORD tdtType,tdtSize;
union{
short tdtShort[4];
double tdtDouble;
char tdtChar[8];
};
}XlTable={
0x0010, // Header
4, // 4 Bytes folgen
1, // 1 Spalte
1, // 1 Zeile
4, // Error
sizeof(short),
{42}}; // "#N/A"
#pragma pack()
switch (GetItemIdx(item)) {
case 0:
case 1: if (!_isnan(Readout[k].value)) {
XlTable.tdtType=1; // Double
XlTable.tdtSize=sizeof(double);
XlTable.tdtDouble=Readout[k].value;
}break;
case 2: {
XlTable.tdtType=2; // String (ANSI)
XlTable.tdtSize=(XlTable.tdtChar[0]=(char)(Readout[k].GenUnitA(XlTable.tdtChar+1)-(XlTable.tdtChar+1)))+1;
}break;
case 3: if (Scope.n) {
ret=DdeCreateDataHandle(gDdeInst,NULL,12+Scope.tracelen*sizeof(double),0,hsz2,uFmt,0);
XLTABLE*p=(XLTABLE*)DdeAccessData(ret,NULL);
p->tdtTable=16;
p->dimensions=4;
p->rows=(WORD)Scope.tracelen;
p->cols=1;
p->tdtType=1; // Double values
p->tdtSize=(WORD)(Scope.tracelen*sizeof(double));
double *dp=&p->tdtDouble;
div_t d=div(Trace[0].devicode+81,3); d.quot-=27;
double factor=e10(false,"\1\2\5"[d.rem],(char)d.quot)/64;
for (int i=0; i<Scope.tracelen; i++) *dp++=Trace[0][i]*factor;
DdeUnaccessData(ret);
return ret; // But how to transfer the time base? Extra topic?
}
case 4: if (!_isnan(Readout[k].min)) {
XlTable.tdtType=1; // Double
XlTable.tdtSize=sizeof(double);
XlTable.tdtDouble=Readout[k].min;
}break;
case 5: if (!_isnan(Readout[k].max)) {
XlTable.tdtType=1; // Double
XlTable.tdtSize=sizeof(double);
XlTable.tdtDouble=Readout[k].max;
}break;
}
ret=DdeCreateDataHandle(gDdeInst,(LPBYTE)&XlTable,12+XlTable.tdtSize,0,hsz2,uFmt,0);
}
}break;
/* CLIENT */
default: ret=gLog.DdeCallback(uType,uFmt,hConv,hsz1,hsz2,hData); break;
}
return ret;
}
struct ENUMDATA{
LPTSTR tag;
bool nomatch;
bool alwaysnamed;
};
static BOOL CALLBACK EnumWindowsProc(HWND w, LPARAM tag) {
ENUMDATA*ed=(ENUMDATA*)tag;
if (w!=ghMainWnd) { // only foreign windows
TCHAR buf[32];
GetClassName(w,buf,elemof(buf));
if (!lstrcmpi(buf,T("DMM"))) { // with own class name
DWORD pid;
GetWindowThreadProcessId(w,&pid);
HANDLE h=OpenProcess(PROCESS_VM_READ,FALSE,pid);
if (h) {
TCHAR ForeignConfName[elemof(ConfName)];
ReadProcessMemory(h,ConfName,ForeignConfName,sizeof(ConfName),NULL);
CloseHandle(h);
// If the other process is still unnamed, it looks like a deadlock situation!
if (!*ForeignConfName) ed->alwaysnamed=false;
if (!lstrcmpi(ed->tag,ForeignConfName)) ed->nomatch=false;
}
}
}
return ed->nomatch;
}
void SetConfName(LPCTSTR NewName) {
if (ConfName!=NewName) lstrcpyn(ConfName,NewName,elemof(ConfName));
if (hszTopic) DdeFreeStringHandle(gDdeInst,hszTopic);
hszTopic=DdeCreateStringHandle(gDdeInst,NewName,CP_WINNEUTRAL);
}
static bool IsConfigUnused(LPTSTR Config) {
ENUMDATA ed;
ed.tag=Config;
ed.nomatch=ed.alwaysnamed=true;
EnumWindows(EnumWindowsProc,(DWORD)&ed);
if (!ed.alwaysnamed) OutputDebugStringA("Unconfigured instance detected!\r\n");
return ed.nomatch;
}
// Check whether the enumerated registry value tag name matches the ConfigName from command-line
static bool TagMatch(LPTSTR tag) {
// If no command-line argument given, the first entry that is not occupied by another instance will match
if (!*ConfName) return IsConfigUnused(tag);
LPTSTR p=_tcschr(tag,'@');
// If port name is given, compare port portion
if (*ConfName=='@') {
if (p && !lstrcmpi(p,ConfName)) return true;
}else{
// Then check for equal multimeter name
if (p) *p=0; // cut the string
if (!lstrcmpi(tag,ConfName)) {
if(p) *p='@'; // sew the string
return true;
}
if (p) *p='@';
}
return false;
}
static bool LoadConfig() {
bool found=false;
HKEY k;
if (!RegOpenKey(HKEY_CURRENT_USER,T("Software\\h#s\\DMM"),&k)) { // nothing saved ever
// Retrieve key that matches ConfigName (Windows will make a case-insensitive comparison)
DWORD len=sizeof(Config);
if (*ConfName && !RegQueryValueEx(k,ConfName,NULL,NULL,(LPBYTE)&Config,&len)) {
SetConfName(ConfName); // for DDE
found=true;
}else{
// If not successful, enum the available values until match.
// (This will never match if @ character is in the middle of ConfigName)
for (DWORD i=0;;i++) {
TCHAR tag[CONFMAX];
DWORD taglen=elemof(tag);
len=sizeof(Config);
LONG r=RegEnumValue(k,i,tag,&taglen,NULL,NULL,(LPBYTE)&Config,&len);
if (r==ERROR_NO_MORE_ITEMS) break;
if (!r && *tag && TagMatch(tag)) {
SetConfName(tag);
found=true;
break;
}
}
}
}
RegCloseKey(k);
if (found) {
// Reposition window as saved, but ShowWindow() is called later (don't flicker)
WINDOWPLACEMENT wp;
wp.length=sizeof(wp);
GetWindowPlacement(ghMainWnd,&wp);
Config.winpos>>wp.rcNormalPosition;
wp.showCmd=SW_HIDE;
SetWindowPlacement(ghMainWnd,&wp);
}else Config.showCmd=SW_SHOWDEFAULT; // later, show window as in STARTUPINFO
return found;
}
static void SaveConfig() {
// Store information for human registry hackers
if (!*ConfName) return; // Store nothing without multimeter+port selection
RegSetValue(HKEY_CURRENT_USER,T("Software\\h#s"),REG_SZ,T("haftmann#software"),17*sizeof(TCHAR));
HKEY k;
if (RegCreateKey(HKEY_CURRENT_USER,T("Software\\h#s\\DMM"),&k)) return;
// Store information for human registry hackers
TCHAR s[64];
int l=LoadString(ghInst,1,s,elemof(s));
RegSetValue(k,NULL,REG_SZ,s,l*sizeof(TCHAR));
// Retrieve Window placement info
WINDOWPLACEMENT wp;
wp.length=sizeof(wp);
GetWindowPlacement(ghMainWnd,&wp);
Config.winpos=wp.rcNormalPosition;
Config.showCmd=(char)wp.showCmd;
// Store configuration information
RegSetValueEx(k,ConfName,0,REG_BINARY,(LPBYTE)&Config,sizeof(Config));
RegCloseKey(k);
}
static void SetRunOnce() {
if (!*ConfName) return;
HKEY k;
if (RegCreateKey(HKEY_CURRENT_USER,T("Software\\Microsoft\\Windows\\CurrentVersion\\RunOnce"),&k)) return;
TCHAR buf[MAX_PATH+16];
buf[0]='"'; // prepend double quote (path may contain spaces)
int l1=GetModuleFileName(0,buf+1,elemof(buf)-1);
int l2=1+l1+2+lstrlen(ConfName)+1; // space needed, including '\0' terminator
if (l2<=elemof(buf)) { // fail SetRunOnce() silently when it doesn't fit
lstrcpy(buf+1+l1,T("\" ")); // append double quote and a space
lstrcpy(buf+l1+3,ConfName); // append configuration name as command line argument
RegSetValueEx(k,ConfName,0,REG_SZ,(PBYTE)buf,l2*sizeof(TCHAR));
}
RegCloseKey(k);
}
// Opens a numbered COM port (1-based, similar to HeOpen())
// and sets protocol to "<baud>,8,n,1" if <baud> was given
// Returns 0 (not INVALID_HANDLE_VALUE) on failure
static HANDLE ComOpen(int n, int baud=0) {
TCHAR ComName[12];
_sntprintf(ComName,elemof(ComName),T("\\\\.\\COM%u"),n);
HANDLE h=CreateFile(ComName,GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_EXISTING,FILE_FLAG_OVERLAPPED,NULL);
if ((int)h<0) h=0;
if (h && baud) {
DCB dcb;
RtlZeroMemory(&dcb,sizeof(dcb));
dcb.DCBlength=sizeof(dcb);
dcb.BaudRate=baud,
dcb.fBinary=TRUE;
dcb.fDtrControl=DTR_CONTROL_ENABLE; // DTR must be +12V, RTS must be -12V
dcb.ByteSize=8; // All other fields remain 0 (NOPARITY,ONESTOPBIT,NOHANDSHAKE)
SetCommState(h,&dcb);
}
return h;
}
// Almost universal port-enumeration procedure
// For Wine 1.4.1, registry keys must be set somehow:
// HKLM/System/CCS/Enum/ACPI/PNP0501/0/...
// ClassGUID = {4D36E978-E325-BFC1-08002BE10318}
// Device Parameters/PortName = COM1
static bool EnumPorts(bool(CALLBACK*cb)(void*,PTSTR),void*p) {
HANDLE devs=SetupDiGetClassDevs(&GUID_DEVCLASS_PORTS,NULL,0,DIGCF_PRESENT);
if (devs!=INVALID_HANDLE_VALUE) {
SP_DEVINFO_DATA devInfo;
devInfo.cbSize=sizeof devInfo;
for (int i=0; SetupDiEnumDeviceInfo(devs,i,&devInfo); i++) {
HKEY hKey;
TCHAR ComName[8];
DWORD slen=sizeof ComName;
*ComName=0;
if ((hKey=SetupDiOpenDevRegKey(devs,&devInfo,DICS_FLAG_GLOBAL,0,DIREG_DEV,KEY_READ))
==INVALID_HANDLE_VALUE) continue;
RegQueryValueEx(hKey,T("PortName"),NULL,NULL,(LPBYTE)ComName,&slen);
RegCloseKey(hKey);
if (!cb(p,ComName)) {
SetupDiDestroyDeviceInfoList(devs);
return false;
}
}
SetupDiDestroyDeviceInfoList(devs);
}
return true;
}
static bool CALLBACK AppendUniquePortCallback(void*p,LPTSTR ComName) {
if (*ComName=='C') {
int&nc=(int&)p;
int i=_ttoi(ComName+3);
HANDLE h=ComOpen(i);
if (h) {
CloseHandle(h);
if (nc) return false; // More than one COM port, cannot decide which to use
nc=i;
}
}
return true;
}
// Appends a port number if no port given and only one usable (COM or USB) port exist.
// This procedure only counts ports that are free (not used by another app)!
// As it's very probably that exactly one found USB port addresses a multimeter, ...
static void AppendUniquePort() {
if (_tcschr(ConfName,'@')) return; // A port number is given, don't search
int nc=0,nu=0;
char List[HE_NUM_MAX];
HeEnum(List); // Create the list of USB ports
char *p=(char*)memchr(List,0,HE_NUM_MAX); // scan for an available port
if (p) { // there is one, is there another?
p++;
nu=(int)(p-List);
p=(char*)memchr(p,0,List+HE_NUM_MAX-p);
if (p) return; // More than one USB port, cannot automatically decide which to use
}else{ // scan COM ports only if no USB port found
if (!EnumPorts(AppendUniquePortCallback,&nc)) return;
}
if (nc+nu==0) return; // Not any free port found!
int l=lstrlen(ConfName);
if (nc) _sntprintf(ConfName+l,elemof(ConfName)-l,T("@COM%u"),nc);
else _sntprintf(ConfName+l,elemof(ConfName)-l,T("@USB%u"),nu);
}
/***********************************
* GDI resources and font handling *
***********************************/
#define FONTNAME T("Arial") // Name of standard font used
#define SYMBNAME T("UniT-a2") // Name of private font containing 7-segment numbers and symbols
#define RGBREF(x) ((BYTE*)&x)
// Calculates a color between <c1> and <c2> where <weight> (0..256) is the <c1> part of mixing
COLORREF colormix(COLORREF c1, COLORREF c2, int weight) {
int nw=256-weight;
RGBREF(c1)[0]=(BYTE)((RGBREF(c1)[0]*weight+RGBREF(c2)[0]*nw)>>8);
RGBREF(c1)[1]=(BYTE)((RGBREF(c1)[1]*weight+RGBREF(c2)[1]*nw)>>8);
RGBREF(c1)[2]=(BYTE)((RGBREF(c1)[2]*weight+RGBREF(c2)[2]*nw)>>8);
return c1;
}
int CALLBACK GDI::EnumFontFamProc(const LOGFONT*,const TEXTMETRIC*,DWORD,LPARAM p) {
((GDI*)p)->unitfont=INSTALLED;
return 0; // stop enumerating
}
#ifndef UNICODE
#undef AddFontResourceEx
#define AddFontResourceEx(a,b,c) AddFontResource(a)
#undef RemoveFontResourceEx
#define RemoveFontResourceEx(a,b,c) RemoveFontResource(a)
#endif
void GDI::UnitfontLoad() {
if (unitfont) return;
HDC dc=GetDC(0); // Get screen DC
EnumFontFamilies(dc,SYMBNAME,EnumFontFamProc,(LPARAM)this);
ReleaseDC(0,dc);
if (unitfont) return;
// Try to load the font from current directory (CWD).
// As this program does not alter its directory, CWD ist most probably the program's .EXE directory.
if (AddFontResourceEx(SYMBNAME T(".ttf"),FR_PRIVATE|FR_NOT_ENUM,0)) unitfont=LOADED;
}
void GDI::UnitfontUnload() {
if (unitfont==LOADED && RemoveFontResourceEx(SYMBNAME T(".ttf"),FR_PRIVATE|FR_NOT_ENUM,0)) unitfont=NA;
}
void GDI::DeleteObj(HGDIOBJ&obj) {
if (obj && DeleteObject(obj)) obj=0;
}
void GDI::Delete(bool FontToo) {
if (dcDblBuf && DeleteDC(dcDblBuf)) dcDblBuf=0; // kicks out selected pen, font, and bitmap
HGDIOBJ *obj=(HGDIOBJ*)this;
for (int i=0; i<17; i++) DeleteObj(obj[i]);
if (FontToo) UnitfontUnload();
}
HFONT GDI::MakeFont(int x, int y, int weight, LPCTSTR name) {
HFONT ret=CreateFont(y,x,0,0,weight,0,0,0,DEFAULT_CHARSET,0,0,0,0,name);
return ret;
}
// Font re-initialization, called when
// * window size changes
// * measurement unit or prefix changes
// * "number of readout" change
// * appearance changes between scope and multimeter mode
void GDI::Make(int x, int y) {
Delete(false);
UnitfontLoad();
if (Config.doubleBuf) {
HDC dc=GetDC(0);
bmDblBuf=CreateCompatibleBitmap(dc,x,y);
dcDblBuf=CreateCompatibleDC(dc);
ReleaseDC(0,dc);
SelectBitmap(dcDblBuf,bmDblBuf);
}
SIZE exs; // character cell size for explanation font
LPCTSTR bigfontname=FONTNAME; int xx=x/10; int weight=FW_BOLD;
if (Config.sevenSeg && unitfont) bigfontname=SYMBNAME, xx=x>>3, weight=0;
penDiv=CreatePen(PS_DOT,0,RGB(196,196,196));
if (Scope.n) { // create some smaller fonts
for (int i=0; i<Scope.n; i++) {
penGraph[i]=CreatePen(PS_SOLID,x/Scope.tracelen,TraceColor[i]); // green pen with useful pen width
}
penGrid=CreatePen(PS_SOLID,0,0xC0C0C0); // lightgray pen for grid lines
penTrig=CreatePen(PS_SOLID,x/Scope.tracelen,0x000000); // black pen for trigger cross
fntBig=MakeFont(x>>5,y>>3,weight,bigfontname);
exs.cx=x>>5;
exs.cy=y>>3;
}else{ // create client-area-filling font
for (int i=0; i<NumReadouts; i++) {
penGraph[i]=CreatePen(PS_SOLID,0,TraceColor[i]); // same colors as for TraceColors
penWide [i]=CreatePen(PS_SOLID,3,TraceColor[i]);
brFill[i]=CreateSolidBrush(colormix(TraceColor[i],GetSysColor(COLOR_WINDOW),0x10));
brBar [i]=CreateSolidBrush(colormix(TraceColor[i],GetSysColor(COLOR_WINDOW),0x30));
}
penNull=CreatePen(PS_NULL,0,0);
fntBig=MakeFont(xx,MulDiv(y,NumReadouts>2?9:14,16),0,bigfontname);
exs.cy=MulDiv(y,27,100);
exs.cx=x/20;
}
fntEx=MakeFont(exs.cx,exs.cy,0,FONTNAME);
fntRo=MakeFont(exs.cx,exs.cy,0,FONTNAME);
if (unitfont) fntUniT=MakeFont(exs.cx,exs.cy,0,SYMBNAME);
}
void GDI::Make() {
RECT r;
GetClientRect(ghMainWnd,&r);
Make(r.right-r.left,r.bottom-r.top);
}
// Ensure visibility of a string
void DecreaseFontWidth(HDC dc, int ist, int xavail, HFONT &fnt) {
if (ist<=xavail && ist>=xavail>>1) return; // do nothing if string fits
LOGFONT lf;
GetObject(fnt,sizeof(lf),&lf);
int w=MulDiv(lf.lfWidth,xavail,ist);
if (w==lf.lfWidth) w--; // ensure that width really changes
int h=lf.lfHeight;
if (h>w<<2) h=w<<2; // limit height
if (w>h) w=h; // limit width
if (w==lf.lfWidth && h==lf.lfHeight) return; // do nothing if nothing changes
SelectObject(dc,GetStockFont(DEFAULT_GUI_FONT));
DeleteFont(fnt);
lf.lfWidth=w;
lf.lfHeight=h;
fnt=CreateFontIndirect(&lf); // re-create with changed width or height (can be time consuming!!)
SelectObject(dc,fnt);
}
void MakeStringFitW(HDC dc, int xavail, HFONT &fnt, LPCWSTR s, int slen) {
SIZE size;
GetTextExtentPoint32W(dc,s,slen,&size);
DecreaseFontWidth(dc,size.cx,xavail,fnt);
}
// Pick a zero-terminated string out of tightly concatenated strings
static const WCHAR* GetIndexStringW(const WCHAR*p, int i) {
if (i) do{
p+=lstrlenW(p)+1;
}while(--i);
return p;
}
static const char* GetIndexStringA(const char*p, int i) {
if (i) do{
p+=lstrlenA(p)+1;
}while(--i);
return p;
}
/**********************************************************************
* Internal data structures for evaluation and common helper funtions *
**********************************************************************/
// A one-letter list of 1000' Unit prefixes
static const WCHAR prelettersW[]=L"afpn╡m\0kMGTPE";
// The same as ANSI version, on systems without '╡', '╡' changes to 'u'
static char prelettersA[16];
// A list of units (Unicode)
static const WCHAR unitsW[]=
L"\0V\0\x2126\0F\0Hz\0A\0░C\0░F\0W\0%\0S\0H\0K\0001/min\0s\0VA\0var\0Wh\0VAh\0varh\0░";
// A list of units (ANSI - i.e. out of current Windows character set)
static char unitsA[64];
static void BuildPrelettersA() {
WideCharToMultiByte(CP_ACP,0,prelettersW,13,prelettersA,16,"u",NULL);
}
static void BuildUnitsA() {
char *pa=unitsA;
LPCWSTR pw=unitsW;
for (int i=0; i<14; i++) {
int lw=lstrlenW(pw)+1;
BOOL fail;
int la=WideCharToMultiByte(CP_ACP,0,pw,lw,pa,8,NULL,&fail);
if (fail) { // replace the unit by an ASCII version
const char *r=NULL;
switch (i) {
case 2: r="Ohm"; break;
case 6: r="degC"; break;
case 7: r="degF"; break;
case 20: r="deg"; break;
default: OutputDebugStringA("#2\n");
}
lstrcpyA(pa,r);
la=lstrlenA(r)+1;
}
pw+=lw;
pa+=la;
}
}
// Handle some cases for unitprefix
char RO::_unitprefix(EFLAGS f) const {
char c=0;
switch (unitprefix) {
case -2: c='c'; break; // centi
case -1: c='d'; break; // deci
// case 1: *d++='d'; c='a'; break; // deca, should be avoided
case 2: c='h'; break; // hecto
case -6: if (f&UNITFONT) c=0x2B; break;
}
return c;
}
// Handle some cases for unitcode
char RO::_unitcode(EFLAGS f) const {
char c=0;
if (f&UNITFONT) switch (unitcode) {
case 2: c=0x2A; // Ohm
case 6: c=0x23; // ░C
case 7: c=0x3F; // ░F
}
return c;
}
LPWSTR RO::GenUnitW(LPWSTR d,EFLAGS f) const {
if (unitprefix || unitcode || *unit) {
if (f&UNITSPACE) *d++=' ';
if (f&UNITPREFIX) {
WCHAR c=_unitprefix(f);
if (!c) c=prelettersW[(unitprefix+18)/3];
if (c) *d++=c;
}
char c=_unitcode(f);
if (c) {
*d++=c;
*d=0;
}else{
if (unitcode) {
lstrcpyW(d,GetIndexStringW(unitsW,unitcode));
d+=lstrlenW(d);
}else{
d+=MultiByteToWideChar(CP_UTF8,0,unit,-1,d,8)-1;
}
}
}
return d;
}
LPSTR RO::GenUnitA(LPSTR d,EFLAGS f) const {
if (unitprefix || unitcode || *unit) {
if (f&UNITSPACE) *d++=' ';
if (f&UNITPREFIX) {
char c=_unitprefix(f);
if (!c) c=prelettersA[(unitprefix+18)/3];
if (c) *d++=c;
}
if (unitcode) {
char c=_unitcode(f);
if (c) {
*d++=c;
*d=0;
}else{
lstrcpyA(d,GetIndexStringA(unitsA,unitcode));
d+=lstrlenA(d);
}
}else{
WCHAR buf[8];
MultiByteToWideChar(CP_UTF8,0,unit,-1,buf,8);
d+=WideCharToMultiByte(CP_ACP,0,buf,-1,d,8,NULL,NULL)-1;
}
}
return d;
}
void GenLogUnit(LPSTR d, const READOUT*ro) {
*(((RO*)ro)->GenUnitA(d))=0;
}
// generates string for large display, for DDE etc.
LPWSTR RO::GenStringW(LPWSTR d,EFLAGS f) const {
const char *s=digits;
if (*s) { // do nothing if empty
if (f&LEADSPACE && *s!='-' && min<0) *d++=' ';
if (f&UNITPREFIX) while (WCHAR c=*s++) {
if (f&UNITFONT) {
if (c>='A') c=0x40; // replace letters by seven-segment 'L'
if (c=='+') c=' '; // remove positive sign (the font has no '+')
}
if (f&CONVDECIMAL && c=='.') c=cDecimalW;
*d++=c;
}else{ // generate DDE version without prefix
int l=swprintf(d,_isnan(value)?L"NaN":L"%G",value);
if (f&CONVDECIMAL) {
LPWSTR p=wcschr(d,'.');
if (p) *p=cDecimalW;
}
d+=l;
}
if (f&WITHUNIT) d=GenUnitW(d,f);
*d=0;
}
return d;
}
// generates string for mimimized window title (ANSI version), or not compiled (UNICODE version)
LPSTR RO::GenStringA(LPSTR d,EFLAGS f) const {
const char *s=digits;
if (*s) { // do nothing if empty
if (f&LEADSPACE && *s!='-' && min<0) *d++=' ';
if (f&UNITPREFIX) while (char c=*s++) {
if (f&UNITFONT) {
if (c>='A') c=0x40; // replace letters by seven-segment 'L'
if (c=='+') c=' '; // remove positive sign (the font has no '+')
}
if (f&CONVDECIMAL && c=='.') c=cDecimalA;
*d++=c;
}else{ // generate DDE version without prefix
int l=sprintf(d,_isnan(value)?"NaN":"%G",value);
if (f&CONVDECIMAL) {
LPSTR p=strchr(d,'.');
if (p) *p=cDecimalA;
}
d+=l;
}
if (f&WITHUNIT) d=GenUnitA(d,f);
*d=0;
}
return d;
}
// sets the new ReadOut value und gives information what has been changed
DWORD RO::Set(const READOUT *ro) {
DWORD ret=0;
if (!RtlEqualMemory(&value,&ro->value,sizeof(double))) ret|=1; // value changed, catches NaN!
if (!RtlEqualMemory(&min,&ro->min,4*sizeof(double))) ret|=2; // range or error changed numerically
if (!RtlEqualMemory(&digits,&ro->digits,15)) ret|=4; // visual presentation changed
if (unitcode!=ro->unitcode || lstrcmpA(unit,ro->unit)) ret|=14;// unit and presentation changed
RtlCopyMemory(this,ro,sizeof(READOUT));
if (ret&8) {
BtClear();
// BtUnzoom();
}
if (_finite(value)) BtAdd((float)value);
return ret;
};
DWORD RO::Clear() {
static const READOUT emptyreadout={NaN,NaN,NaN};
return Set(&emptyreadout);
}
// Calculates the decimal point position and the indexed unit prefix.
// The "decimal point position" counts the digits _before_ decimal point.
FUNC(int) dp(char scalebase, char scaleadd, char*unitprefix) {
char dppos=4;
char unit=5;
if (scalebase) {
char unitsallowed=(scalebase>>5)&3; // extract special bits
char specialrangestart=scalebase>>7;
scalebase&=0x1F;
char scale=scalebase+scaleadd; // the scale that applies
char unitbase=scalebase/3; // the indexed unit prefix (1=p, 2=n .. 8=G) of range start
unit=scale/3; // the indexed unit prefix - maybe!
dppos=scale%3+1; // the position of decimal point: numbers before point
if (specialrangestart && !scaleadd) {
unit++; // correct unit prefix
dppos-=3; // correct decimal point position (should be never <0!)
}
if (unitsallowed && unit==unitbase+unitsallowed) { // forbidden unit prefix reached?
unit--; // correct unit prefix
dppos+=3; // correct decimal point position (should be never >5!)
}
}
if (unitprefix) *unitprefix=(unit-5)*3;// zero-centered 10**x exponent value
return dppos;
}
static const char featuresA[]=
"LowBat\0Cont\0Diode\0Rel\0Sigma\0Beta\0Hold\0Auto\0hFE\0EF\0Man\0Min\0Max\0AC\0DC\0"
"Ph1\0Ph2\0Ph3\0TSR\0RST\0cos phi\0phi";
static const WCHAR featuresW[]=
// LoBat Beep Diode Delta Sigma Beta
L"25EA\0\x25A1\x25C1\0\x25B6\x258E\0\x0394\0\x03B2\0\x03B2\0HOLD\0AUTO\0hFE\0EF\0MAN\0MIN\0MAX\0AC\0DC"
// arrow? phi phi
L"\0╪1\0╪2\0╪3\0TSR\0RST\0cos \x03C6\0\x03C6";
// character codes into UniT-a2.ttf of the first 8 symbols of EFEATURE:
static const WCHAR gUniT_Indices[]={0x24,0x26,0x29,0x21,0x25,0x28,0x22,0xC4};
// Collects bits, based by p, adresses in BitTable. NumBits=sizeof(BitTable)
// Does not invert bits while collecting.
// Skips bits marked with 0xFF (0377 octal) in BitTable.
FUNC(DWORD) CollectBits(const void*p, const BYTE*BitTable, BYTE NumBits) {
DWORD ret=0, mask=1;
do{
BYTE bitnum=*BitTable++;
if (bitnum!=0377
&& ((const DWORD*)p)[bitnum>>5]&(1<<(bitnum&0x1F))) ret|=mask;
mask<<=1;
}while (--NumBits);
return ret;
}
// Adds a feature to ExtraW string
LPWSTR FEATURE::_addstringW(LPWSTR d, DWORD i, EFLAGS fl) {
if (fl&PREPENDSPACE)
#ifdef UNICODE
*d++=0x2001; // add a wide 'm' space
#else
*d++=' ', *d++=' ';
#endif
lstrcpyW(d,GetIndexStringW(featuresW,i));
return d+lstrlenW(d);
}
// Build feature string out of bit mask
LPWSTR FEATURE::GenStringW(LPWSTR d, EFLAGS fl) const{
DWORD features=f;
for (int i=0; i<NUMFEATURES; i++) {
if (features&1) d=_addstringW(d,i,fl), *(BYTE*)&fl|=PREPENDSPACE;
features>>=1;
}
return d;
}
// if different, sets the new value and returns 0x10000, else 0
bool FEATURE::Set(DWORD n) {
if (f!=n) {
f=n;
return true;
}
return false;
}
/*************************************
* Multimeter Decoder data structure *
*************************************/
static struct WORKER{ // Worker thread data
// bool threadBreak; // terminates the thread's main loop that processes incoming data
// DWORD lastTick; // GetTickCount() value of last incoming byte
// int timeOutCounter; // Used by some OnTimer() handler
HANDLE hCom; // The currently open serial or USB device
OVERLAPPED o; // The hEvent forms multiple objects with hBreak
HANDLE hBreak; // A break event object to terminate the worker thread
HANDLE hThread; // The worker thread handle
HANDLE hMutex; // that protects the gathered data while painiting (interpreting)
// DWORD dataInput; // Number of bytes received since last invocation of pOnTimer()
// The "binary" decoded config info
struct CONF{
// LPTSTR dllname; // TODO: currently, no plugin DLLs are supported
// MMINFO mmi; // A copy of MMINFO data, most important, the three procedure pointers
// union{
struct PORT{
int num; // 1 .. 256
EVIA via; // 1 or 2
// BYTE res; // 0, reserved
// };
// LPARAM all; // for use as combobox item lParam parameter
}port;
bool Make();
}conf;
INDATA mm;
// LPARAM lParam;
// DWORD timer; // time, in ms, after the last byte received, OnTimer() gets called
// BUG: Cannot show "wrong data"!
// int recvlen; // bytes received (into recvbuf or more)
// int recvmax;
// BYTE recvbuf[400]; // can hold sample data of scope multimeters
DWORD ThreadProc();
static DWORD WINAPI ThreadProc(LPVOID);// the thread procedure
void Start(); // starts the thread
void Stop(); // stops and terminates the thread
void MakeTimer();
}w; // static worker thread data
#ifdef _DEBUG
void DebugPacket() {
if (!w.mm.rlen) return;
char buf[160],*p=buf;
int i=0;
p+=sprintf(p,"(%d) ",w.mm.rlen);
do{
p+=sprintf(p,"%02X ",w.mm.rbuf[i++]);
}while (p<buf+elemof(buf)-10 && i<w.mm.rlen);
if (i!=w.mm.rlen) p+=sprintf(p,"... ");
strcpy(p-1,"\r\n");
OutputDebugStringA(buf);
}
void _cdecl DebugOut(const char*t,...) {
char s[160];
va_list va;
va_start(va,t);
int l=_vsnprintf(s,elemof(s)-3,t,va);
if (l<0) l=elemof(s)-3; // return value for cropping: set to end of string
strcpy(s+l,"\r\n");
OutputDebugStringA(s);
}
# define DbgOut(x) DebugOut x
#else
# define DebugPacket()
# define DbgOut(x)
#endif
FUNC(bool) SetData(DWORD features, const READOUT*readout, int num, const SCOPEDATA* scope) {
if (unsigned(num)>elemof(Readout)) return false; // cannot process
if (scope && unsigned(scope->n-1)>elemof(Trace)-1) return false;
// cannot process, must be at least 1 trace
DWORD change=0;
if (num || features || scope) { // do nothing in case "0-0-0" - only the icon should flash
if (w.hMutex && WaitForSingleObject(w.hMutex,1000)) return false;
change=Features.Set(features)<<16;
if (!num && !scope) change<<=1; // without readout, no features!
if (NumReadouts!=num) NumReadouts=num, change|=1<<17;
int i;
for (i=0; i<num; i++) change|=Readout[i].Set(readout+i)<<(i<<4);
for (; i<elemof(Readout); i++) change|=Readout[i].Clear()<<(i<<4);
change|=SetScope(scope);
ReleaseMutex(w.hMutex);
}
if (num || scope || !features) change|=1<<24; // let icon flash
// if (change&0x49999L && !DdePostAdvise(gDdeInst,hszTopic,0)) { // WRONG THREAD!
PostMessage(ghMainWnd,WM_USER+22,0,change);
if (num) gLog.Line(num,readout,features,change);
return true;
}
// Send bytes to multimeter
FUNC(BOOL) SendBytes(const void*p, int len) {
OutputDebugStringA("*");
switch (w.conf.port.via) {
case VIACOM: {
DWORD bw;
return (WriteFile(w.hCom,p,len,&bw,&w.o)
|| GetLastError()==ERROR_IO_PENDING
&& !WaitForMultipleObjects(2,&w.o.hEvent,FALSE,INFINITE)
&& GetOverlappedResult(w.hCom,&w.o,&bw,FALSE))
&& (int)bw==len;
}
case VIAUSB: return HeWrite(w.hCom,(const BYTE*)p,len,&w.o);
}
return 0;
}
// Calculates minimum and maximum values out of unit, flags etc.
// This function clears the free-form unit, so you must add this later if necessary.
FUNC(void) GenMinMax(READOUT *ro, DWORD features, int maxnumber, char expo) {
ro->unit[0]=0;
ro->min=0;
ro->max=e10(false,maxnumber,expo);
switch (ro->unitcode) {
case 1: /*V*/ if (ro->max>1000) ro->max=1000;
if (!(features&(1<<F_AC))) features|=1<<F_Delta; break; // max. 1000 V
case 5: /*A*/ if (ro->max>10) ro->max=10;
if (!(features&(1<<F_AC))) features|=1<<F_Delta; break; // max. 10 A
case 6: /*░C*/ ro->min=-40; ro->max=1000; break;
case 7: /*░F*/ ro->min=-80; ro->max=1832; break;
case 9: /*%*/ ro->max=100; break;
case 20: /*░*/ ro->max=360; break;
}
if (features&(1<<F_Delta)) {
ro->min=-ro->max; // make symmetric ranges
if (ro->unitcode==6) ro->unitcode=12; // K for temperature difference
}
}
/*****************
* Worker thread *
*****************/
DWORD WORKER::ThreadProc() {
conf.Make();
hCom=0;
mm.rmax=400; // enough space?
mm.rlen=0;
mm.icto=20;
mm.trto=3000;
mm.counts=1999; // maximum for ICL7136 based multimeters
mm.baud=9600;
mm.msg=mm.init;
(mm.*mm.pHandler)();
mm.rbuf=new BYTE[mm.rmax+8];
do{
if (!hCom) {
switch (conf.port.via) {
case VIACOM: {
hCom=ComOpen(conf.port.num,mm.baud);
if (hCom) {
COMMTIMEOUTS to={mm.icto,0,mm.trto,0,0};
SetCommTimeouts(hCom,&to);
}
}break;
case VIAUSB: {
hCom=HeOpen(conf.port.num,mm.baud,FILE_FLAG_OVERLAPPED);
if ((int)hCom<0) hCom=0;
}break;
}
if (hCom) {
o.hEvent=CreateEvent(NULL,TRUE,FALSE,NULL);
SetData(19,NULL,0); // "Awaiting data"
mm.msg=mm.open;
mm.ok=true;
(mm.*mm.pHandler)();
if (!mm.ok) {
SetData(18,NULL,0); // "Internal error"
}
}else{
SetData(17,NULL,0); // "No interface!"
}
}else{
int br;
SetThreadPriority(hThread,THREAD_PRIORITY_ABOVE_NORMAL);
switch (conf.port.via) {
case VIACOM: {
if (ReadFile(hCom,mm.rbuf+mm.rlen,mm.rmax-mm.rlen+8,(DWORD*)&br,&o)
|| (GetLastError()==ERROR_IO_PENDING
&& !WaitForMultipleObjects(2,&o.hEvent,FALSE,INFINITE)
&& GetOverlappedResult(hCom,&o,(DWORD*)&br,FALSE))) break;
br=-1;
}break;
case VIAUSB: {
br=HeRead(hCom,mm.rbuf+mm.rlen,mm.rmax-mm.rlen+8,mm.icto*2,mm.trto,&o);
}break;
}
SetThreadPriority(hThread,THREAD_PRIORITY_NORMAL);
if (br<0) { // problems with interface, close and let re-open
mm.msg=mm.close;
mm.ok=true;
(mm.*mm.pHandler)();
CloseHandle(o.hEvent);
CloseHandle(hCom);
hCom=0;
}else if (br) {
mm.rlen+=br;
DebugPacket();
mm.msg=mm.data;
goto hand;
}else{
mm.msg=mm.tout;
hand:
mm.ok=false;
(mm.*mm.pHandler)();
if (mm.ok) {
memmove(mm.rbuf,mm.rbuf+mm.rmax,mm.rlen);
}else{
SetData(mm.rlen?21:22,NULL,0);
DbgOut(("Fail, rlen=%d",mm.rlen));
mm.rlen=0;
}
}
}
}while (WaitForSingleObject(hBreak,hCom?0:2000));
mm.msg=mm.close;
(mm.*mm.pHandler)();
SetScope();
delete[] mm.rbuf;
return CloseHandle(hCom);
}
DWORD CALLBACK WORKER::ThreadProc(LPVOID param) {
return ((WORKER*)param)->ThreadProc();
}
void WORKER::Stop() {
if (!hThread) return;
SetEvent(hBreak);
WaitForSingleObject(hThread,INFINITE);
CloseHandle(hBreak);
CloseHandle(hThread);
CloseHandle(hMutex);
RtlZeroMemory(this,sizeof(*this));
}
void WORKER::Start() {
Stop();
hMutex=CreateMutex(NULL,FALSE,NULL);
hBreak=CreateEvent(NULL,TRUE,FALSE,NULL);
DWORD ThreadId;
hThread=CreateThread(NULL,0,ThreadProc,this,0,&ThreadId);
}
/************************
* Configuration dialog *
************************/
// The system image list that contains useful "port" and "usb" images
static struct CONFDLG{
HIMAGELIST DmmImageList;
SP_CLASSIMAGELIST_DATA ild;
static void UpdateCombo(HWND);
static LONG_PTR CALLBACK DlgProc(HWND,UINT,WPARAM,LPARAM);
static bool RetrieveConfig(HWND, TCHAR[CONFMAX]);
}ConfDlg;
// A ComboBoxExItem structure with an attached enumeration procedure
class CBFILL:public COMBOBOXEXITEM{
HWND hCombo;
static bool CALLBACK cbenum(void*p,LPTSTR ComName) {((CBFILL*)p)->cbenum(ComName); return true;}
void cbenum(LPTSTR ComName);
public:
void cbenum(HWND w) {hCombo=w; EnumPorts(cbenum,this);}
};
void CBFILL::cbenum(LPTSTR ComName) {
if (*ComName=='C') { // don't scan LPT
pszText=ComName;
iOverlay=0; // this will overlay nothing
lParam=_ttoi(ComName+3); // one-based
bool current=IsCurrentPort(ComName);
if (!current) {
HANDLE h = ComOpen((int)lParam);
if (h) CloseHandle(h); else iOverlay=2; // this will overlay a red X: port not available
}
SendMessage(hCombo,CBEM_INSERTITEM,0,(LPARAM)this);
if (current) ComboBox_SetCurSel(hCombo,iItem);
iItem++;
}
}
// The serial and USB ports, it's now (141021) fucking fast
void CONFDLG::UpdateCombo(HWND hCombo) {
int i;
CBFILL cbei;
cbei.mask=CBEIF_TEXT|CBEIF_IMAGE|CBEIF_SELECTEDIMAGE|CBEIF_OVERLAY;
cbei.iItem=0;
SetupDiGetClassImageIndex(&ConfDlg.ild,(LPGUID)&GUID_DEVCLASS_PORTS,&cbei.iImage);
cbei.iSelectedImage=cbei.iImage;
ComboBox_ResetContent(hCombo);
cbei.cbenum(hCombo);
SetupDiGetClassImageIndex(&ConfDlg.ild,(LPGUID)&GUID_DEVCLASS_USB,&cbei.iImage);
cbei.iSelectedImage=cbei.iImage;
char List[HE_NUM_MAX];
HeEnum(List);
for (i=1; i<=HE_NUM_MAX; i++) {
char p=List[i-1];
if (p!=NOT_AVAILABLE && p!=REMOVED) {
TCHAR UsbName[8];
_sntprintf(UsbName,elemof(UsbName),T("USB%u"),i);
cbei.pszText=UsbName;
bool current=IsCurrentPort(UsbName);
if (current && p==CANNOT_OPEN) p=AVAILABLE;
// 1 = yellow "!", 2 = red "x", I'm looking for a gray-out overlay!!
static const BYTE overlay[4]={0,2,2,1};
cbei.iOverlay=overlay[-p];
SendMessage(hCombo,CBEM_INSERTITEM,0,(LPARAM)&cbei);
if (current) ComboBox_SetCurSel(hCombo,cbei.iItem);
cbei.iItem++;
}
}
}
// Decodes the textual ConfName (of command line argument or registry key) and fills the ConfInfo structure
// TODO: Create a list of builtins and plugin-provided multimeters beforehand
bool WORKER::CONF::Make() {
char buf[CONFMAX],*p;
#ifdef UNICODE
WideCharToMultiByte(CP_ACP,0,ConfName,-1,buf,elemof(buf),NULL,NULL);
#else
lstrcpyn(buf,ConfName,elemof(buf));
#endif
p=strchr(buf,'@');
if (!p) return false;
if (p) *p=0;
ENUMINFO*e;
for (e=el;e;e=e->next) {
if (!lstrcmpiA(buf,e->ModelName)) break;
}
if (!e) return false;
w.mm.pHandler=e->pHandler;
w.mm.lParam=e->lParam;
// mmi=Builtin[i];
p++;
switch ((TCHAR)CharUpper((LPTSTR)*p)) {
case 'U': port.via=VIAUSB; break;
case 'C': port.via=VIACOM; break;
default: return false;
}
port.num=atoi(p+3);
return port.num ? true : false;
}
// Delete(!) the current registry entry, and create a ConfName (save later)
static void ChangeConfName(LPCTSTR NewConfName) {
HKEY k;
if (!RegOpenKey(HKEY_CURRENT_USER,T("Software\\h#s\\DMM"),&k)) {
RegDeleteValue(k,ConfName);
RegCloseKey(k);
}
SetConfName(NewConfName);
SetWindowTitle();
}
// Called when OK or "Start new instance" is pressed,
// builds the NewConfName string and checks whether:
// * the string is complete
// * the string is not used by another running instance of DMM.exe
// This routine may show MessageBoxes and return <false>.
bool CONFDLG::RetrieveConfig(HWND Wnd, TCHAR NewConfName[CONFMAX]) {
TCHAR dmm[8], port[8];
COMBOBOXEXITEM cbei;
cbei.mask=CBEIF_TEXT;
HWND w=GetDlgItem(Wnd,100);
cbei.iItem=ComboBox_GetCurSel(w);
if (cbei.iItem==-1) goto err;
cbei.pszText=dmm;
cbei.cchTextMax=elemof(dmm);
SendMessage(w,CBEM_GETITEM,0,(LPARAM)&cbei);
w=GetDlgItem(Wnd,101);
if (IsWindowEnabled(w)) { // for now: always TRUE
cbei.iItem=ComboBox_GetCurSel(w);
if (cbei.iItem==-1) goto err;
cbei.pszText=port;
cbei.cchTextMax=elemof(port);
SendMessage(w,CBEM_GETITEM,0,(LPARAM)&cbei);
}else *port=0; // no port for test device - program logic cannot handle!!
_sntprintf(NewConfName,CONFMAX,T("%s@%s"),dmm,port);
if (!IsConfigUnused(NewConfName)) {
MBox(Wnd,8,IDOK|MB_ICONEXCLAMATION,NewConfName);
return false;
}
return true;
err:
MBox(Wnd,2,IDOK|MB_ICONEXCLAMATION);
return false;
}
int ShowDlgItem(HWND w, UINT id, int sh) {
return ShowWindow(GetDlgItem(w,id),sh);
}
LONG_PTR CALLBACK CONFDLG::DlgProc(HWND Wnd, UINT Msg, WPARAM wParam, LPARAM lParam) {
switch (Msg) {
case WM_INITDIALOG: {
ConfDlg.DmmImageList=ImageList_LoadImage(ghInst,MAKEINTRESOURCE(1),16,1,CLR_NONE,IMAGE_BITMAP,0);
HWND w=GetDlgItem(Wnd,100); // Multimeter selection
SendMessage(w,CBEM_SETIMAGELIST,0,(LPARAM)ConfDlg.DmmImageList);
COMBOBOXEXITEM cbei;
cbei.mask=CBEIF_TEXT|CBEIF_IMAGE|CBEIF_SELECTEDIMAGE;
cbei.iItem=0;
ENUMINFO *e;
for (e=el;e;e=e->next) {
WCHAR uni[32];
MultiByteToWideChar(CP_UTF8,0,e->ModelName,-1,uni,elemof(uni));
#ifdef UNICODE
cbei.pszText=uni;
#else
CHAR ans[32];
WideCharToMultiByte(CP_ACP,0,uni,-1,ans,elemof(ans),NULL,NULL);
cbei.pszText=ans;
#endif
cbei.iImage=cbei.iSelectedImage=e->IconIndex;
int j=(int)SendMessage(w,CBEM_INSERTITEM,0,(LPARAM)&cbei); // insert in backward order
if (IsCurrentDmm(cbei.pszText)) ComboBox_SetCurSel(w,j);
}
ConfDlg.ild.cbSize=sizeof(ConfDlg.ild);
SetupDiGetClassImageList(&ConfDlg.ild);
w=GetDlgItem(Wnd,101);
SendMessage(w,CBEM_SETIMAGELIST,0,(LPARAM)ConfDlg.ild.ImageList);
for (int i=0; i<8; i++) CheckDlgButton(Wnd,110+i,(Config.checkBits>>i)&1);
if (!gGdi.unitfont) ShowDlgItem(Wnd,112,SW_HIDE); // no 7segment font available
// TODO: Hide "glass style" option if no Vista detected
ShowDlgItem(Wnd,113,SW_HIDE);
if (cDecimalW=='.') ShowDlgItem(Wnd,114,SW_HIDE); // If this box is irrelevant
SetTimer(Wnd,2,100,NULL);
}return TRUE;
case WM_DEVICECHANGE: SetTimer(Wnd,2,1500,NULL); break;
case WM_TIMER: switch (wParam) {
case 2: {
KillTimer(Wnd,wParam);
HCURSOR curs=SetCursor(LoadCursor(0,IDC_WAIT));
UpdateCombo(GetDlgItem(Wnd,101));
SetCursor(curs);
}break;
}break;
case WM_COMMAND: switch (LOWORD(wParam)) {
case IDOK: {
TCHAR NewConfName[CONFMAX];
if (!RetrieveConfig(Wnd,NewConfName)) break;
BYTE b=0, m=1;
for (int i=0; i<8; i++) {
if (IsDlgButtonChecked(Wnd,110+i)) b|=m;
m<<=1;
}
Config.checkBits=b;
gGdi.Make();
ChangeConfName(NewConfName);
}nobreak;
case IDCANCEL: EndDialog(Wnd,wParam); break;
case 10: { // new window button
TCHAR NewConfName[CONFMAX];
if (!RetrieveConfig(Wnd,NewConfName)) break;
TCHAR ExeName[MAX_PATH];
GetModuleFileName(0,ExeName,elemof(ExeName));
STARTUPINFO si;
GetStartupInfo(&si); // inherit STARTUPINFO
PROCESS_INFORMATION pi;
TCHAR CmdLine[500];
_sntprintf(CmdLine,elemof(CmdLine),T("\"%s\" %s"),ExeName,NewConfName);
CreateProcess(NULL,CmdLine,NULL,NULL,FALSE,0,NULL,NULL,&si,&pi);
}
}break;
case WM_DESTROY: {
SetupDiDestroyClassImageList(&ConfDlg.ild);
ImageList_Destroy(ConfDlg.DmmImageList);
}break;
}
return FALSE;
}
// Set an initial screen appearance and window title
void SetInitScreen() {
SetWindowText(ghMainWnd,ConfName);
}
// Puts out features and uses the GDI's current position to place text
int FEATURE::Out(HDC dc, bool measure) const {
DWORD f=this->f;
int x=0;
for (int i=0; i<NUMFEATURES; i++) {
const WCHAR *p;
int l;
int spc=2;
if (f&1) {
if (i<8 && gGdi.unitfont) {
SelectFont(dc,gGdi.fntUniT);
p=gUniT_Indices+i;
l=1;
if (i==7) spc=4; // add more spaces (the font file has bugs!)
}else{
p=GetIndexStringW(featuresW,i);
l=lstrlenW(p);
}
WCHAR buf[16];
RtlCopyMemory(buf,p,l<<1);
if (i==F_AC && f&2) buf[l++]='+'; // special case "AC+DC"
else do buf[l++]=' '; while(--spc); // add some spaces
if (measure) {
SIZE size;
GetTextExtentPoint32W(dc,buf,l,&size);
x+=size.cx;
}else TextOutW(dc,0,0,buf,l);
if (i<8 && gGdi.unitfont) {
SelectFont(dc,gGdi.fntEx);
}
}
f>>=1;
}
return x;
}
#ifdef _M_IX86
#if _MSC_VER > 1400
# pragma optimize("g",off)
#endif
static void _stdcall Line(HDC dc, int x1, volatile int y1, volatile int x2, volatile int y2) {
Polyline(dc,(POINT*)&x1,2); // use the parameter list on stack as ready-made point array
}
#if _MSC_VER > 1400
# pragma optimize("g",on)
#endif
#else
static void Line(HDC dc, int x1, int y1, int x2, int y2) {
POINT p[2]={{x1,y1},{x2,y2}};
Polyline(dc,p,2);
}
#endif
// This function takes the client rect and adds some borders for the grid rect,
// returns <r> modified to reflect the grid size
void TD::OutGrid(HDC dc, RECT *r) {
int xx=r->right-r->left;
int yy=r->bottom-r->top;
r->top+=yy>>3;
InflateRect(r,-xx>>6,-yy>>6);
xx=r->right-r->left;
yy=r->bottom-r->top;
int i;
SelectPen(dc,gGdi.penGrid);
for (i=0; i<=Scope.grid_x; i++) {
int x=r->left+MulDiv(i,xx,Scope.grid_x);
Line(dc,x,r->top,x,r->bottom);
}
for (i=0; i<=Scope.grid_y; i++) {
int y=r->top+MulDiv(i,yy,Scope.grid_y);
Line(dc,r->left,y,r->right,y);
}
}
void TD::OutTrig(HDC dc, const RECT *r) const{
POINT p;
SelectPen(dc,gGdi.penTrig);
CalcPoint(r,&p,Scope.tx,Scope.ty-ofs);
int d=(r->right-r->left)/(Scope.grid_x<<2);
Line(dc,p.x-d,p.y,p.x+d,p.y);
d=(r->bottom-r->top)/(Scope.grid_y<<2);
Line(dc,p.x,p.y-d,p.x,p.y+d);
}
// Takes <r> as grid size and places the trace with given pen
void TD::OutZMark(HDC dc, const RECT *r, HPEN pen) const {
POINT p[3];
SelectPen(dc,pen);
CalcPoint(r,p,0,0);
p[0].x--;
p[1].x=p[2].x=1;
p[1].y=p[0].y-p[0].x;
p[2].y=p[0].y+p[0].x;
Polygon(dc,p,3);
}
// Takes <r> as grid size and places the trace with given pen
void TD::OutTrace(HDC dc, const RECT *r, HPEN pen) const {
POINT *p=(POINT*)LocalAlloc(LMEM_FIXED,Scope.tracelen*sizeof(POINT));
if (!p) return;
SelectPen(dc,pen);
CalcPointList(r,p);
Polyline(dc,p,Scope.tracelen);
LocalFree(p);
}
// TextAlign must be TA_UPDATECP
void TD::OutBase(HDC dc, char devicode, char unitcode, char autorange, COLORREF cr) {
TextOutW(dc,0,0,L" ",2); // make spacing
devicode+=6*9; // work-around division and modulus with negative values
char prefix=devicode/9;
devicode%=9;
char numzeroes=devicode/3;
devicode%=3;
WCHAR s[32],*p=s;
static const char n1[]="125";
*p++=n1[devicode];
for(;numzeroes;numzeroes--) *p++='0';
*p++=' ';
WCHAR c=prelettersW[prefix];
if (c) *p++=c;
lstrcpyW(p,GetIndexStringW(unitsW,unitcode));
COLORREF oc;
if (autorange) {
SetBkMode(dc,OPAQUE);
SetBkColor(dc,cr);
oc=SetTextColor(dc,GetSysColor(COLOR_WINDOW));
}else oc=SetTextColor(dc,cr);
TextOutW(dc,0,0,s,lstrlenW(s));
if (autorange) SetBkMode(dc,TRANSPARENT);
SetTextColor(dc,oc);
}
void RO::BtOut(HDC dc, int left, int top, int right, int bottom) {
if (!bt) return;
SetRect(&btR,left,top,right,bottom);
right--;
bottom--;
// check or expand min/max
float ya=(float)min,ye=(float)max; // scale to at least including range or zero
int i;
for (i=0; i<btLen; i++) {
float y=bt[i].data;
if (ya>y) ya=y;
if (ye<y) ye=y;
}
float yd=ye-ya; // get the difference
if (!yd) yd=1; // don't divide by zero later!
int yn=bottom+int((bottom-top)*ya/yd); // position of x axis
// get min/max of time axis
DWORD xa,xe;
xe=bt[btIdx-1].tick+1000; // reserve 1 second for a flat segment at end
xa=bt[btIdx<btLen?btIdx:0].tick;
if (xe-xa<10000) xa=xe-10000; // scale to at least 10 seconds
// calculate point array for PolyLine() and Polygon()
POINT*points=(POINT*)LocalAlloc(LPTR,(btLen+5)*sizeof(POINT));
POINT*pt=points;
i=btIdx; do{ // start with oldest value (i.e. the one that would be overwritten next time)
if (i>=btLen) i=0;
++pt; // start filling with index 1, zero index is polygon start
pt->x=left+MulDiv(right-left,bt[i].tick-xa,xe-xa);
pt->y=yn-int((bottom-top)*bt[i].data/yd);
i++;
}while (i!=btIdx);
++pt;
pt->x=pt[-1].x;
pt->y=yn; // down to zero line
++pt;
*pt=pt[-2]; // copy that point
++pt;
pt->x=right;
pt->y=pt[-1].y;
++pt;
pt->x=right;
pt->y=yn; // down to zero line for last index
points->x=points[1].x; // left to where the first point was
points->y=yn;
// output the graph, first the polygon, then the line
SelectPen(dc,gGdi.penNull);
SelectBrush(dc,gGdi.brFill[this-Readout]);
Polygon(dc,points,btLen+2);
SelectBrush(dc,gGdi.brBar [this-Readout]);
Polygon(dc,pt-3,4);
SelectPen(dc,gGdi.penGraph[this-Readout]);
Polyline(dc,points+1,btLen);
SelectPen(dc,gGdi.penWide [this-Readout]);
Polyline(dc,pt-2,2);
LocalFree(points);
}
void RO::BtAdd(float v) {
if (!bt) {
bt=(BT*)LocalAlloc(LPTR,MAXDEPTH*sizeof(BT));
if (!bt) return;
}
if (btLen<MAXDEPTH) btLen++;
if (btIdx>=btLen) btIdx=0; // wrap around
bt[btIdx].data=v;
bt[btIdx].tick=GetTickCount();
btIdx++; // point to next array position
}
// The font is already selected in device context!
// This function does not emit the unit!
int RO::Out7Seg(HDC dc, int x, int y, bool measure) const {
SIZE size;
// The font is badly designed, so place each digit to a given position
int aDX[2];
GetTextExtentExPoint(dc,T("0."),2,0,NULL,aDX,&size); // size of each cell
aDX[1]-=aDX[0]; // with of the dot
TCHAR buf[12], *p;
GenString(buf,SEVENSEG);
int adx[12], *pdx, dx, sx;
for (p=buf,pdx=adx,sx=0;;p++) {
switch(*p) {
case 0: goto breakloop;
case '.': dx=aDX[1]; break;
default: dx= p[1]=='.' ? aDX[0] : size.cx;
}
sx+=dx;
*pdx++=dx;
}
breakloop:
if (measure) return sx;
return ExtTextOut(dc,x,y,ETO_NUMERICSLATIN,NULL,buf,int(p-buf),adx);
}
void OutScope(HDC dc, RECT *r) {
TD::OutGrid(dc,r);
Trace[Scope.tsource&0x0F].OutTrig(dc,r);
int i;
for (i=0; i<Scope.n; i++) {
Trace[i].OutZMark(dc,r,gGdi.penGraph[i]);
Trace[i].OutTrace(dc,r,gGdi.penGraph[i]);
}
// Value (with unit) sent by multimeter
SelectFont(dc,gGdi.fntBig);
SetBkMode(dc,TRANSPARENT);
if (Config.sevenSeg && gGdi.unitfont) {
SetTextAlign(dc,TA_TOP|TA_LEFT|TA_UPDATECP);
MoveToEx(dc,r->left+4,r->top,NULL);
Readout[0].Out7Seg(dc);
WCHAR buf[32];
TextOutW(dc,0,0,buf,int(Readout[0].GenUnitW(buf,RO::EFLAGS(RO::UNITSPACE|RO::UNITPREFIX))-buf));
if (NumReadouts>=2) { // The secondary reading is placed to the top-right corner of window
SIZE size;
int len=int(Readout[1].GenUnitW(buf,RO::EFLAGS(RO::UNITSPACE|RO::UNITPREFIX))-buf);
GetTextExtentPoint32W(dc,buf,len,&size);
size.cx+=Readout[1].Measure7Seg(dc);
MoveToEx(dc,r->right-4-size.cx,r->top,NULL);
Readout[1].Out7Seg(dc);
TextOutW(dc,0,0,buf,len);
}
}else{
SetTextAlign(dc,TA_TOP|TA_LEFT);
WCHAR buf[32], *e=Readout[0].GenStringW(buf);
TextOutW(dc,r->left+4,r->top,buf,int(e-buf));
if (NumReadouts>=2) { // The secondary reading is placed to the top-right corner of window
SetTextAlign(dc,TA_TOP|TA_RIGHT);
e=Readout[1].GenStringW(buf);
TextOutW(dc,r->right-4,r->top,buf,int(e-buf));
}
}
SelectFont(dc,gGdi.fntEx);
SetTextAlign(dc,TA_TOP|TA_LEFT|TA_UPDATECP);
MoveToEx(dc,4,0,NULL);
Features.Out(dc);
SelectFont(dc,gGdi.fntEx);
for (i=0; i<Scope.n; i++) Trace[i].OutBase(dc,TraceColor[i]);
TD::OutBase(dc,Scope.timebase,0x0E,Scope.autorange,0x808080);
}
/*==============================================*
* Readouts are placed in the following manner: *
* 0. No readout (error in transmission): *
* +--------------------+ *
* | | *
* | error_message | *
* | | *
* +--------------------+ *
* 1. One readout (most devices): *
* +--------------------+ *
* | info | *
* | R E A D O U T | *
* +--------------------+ *
* 2. Two readouts (UT81): *
* +--------------------+ *
* |info readout2| *
* | R E A D O U T 1 | *
* +--------------------+ *
* 3. Three readouts (UT233): *
* +--------------------+ *
* | info | *
* | R E A D O U T 1 | *
* |readout2 readout3| *
* +--------------------+ *
* 4. Four readouts (not seen yet): *
* +--------------------+ *
* |info readout4| *
* | R E A D O U T 1 | *
* |readout2 readout3| *
* +--------------------+ *
* "R E A D O U T" denotes LARGE font, *
* "readout" denotes SMALL font. *
* *
* 5. Table readout (VIP System 3) *
* +--------------------+ *
* | rowhead | *
* |colhead data ... | *
* |... ... ... | *
* +--------------------+ *
* This readout especially for power meters *
* may need scroll bars? *
*==============================================*/
void HandlePaint(HDC dc,LPARAM options) {
RECT r;
GetClientRect(ghMainWnd,&r);
if (options&PRF_ERASEBKGND) {
HBRUSH brBack=GetSysColorBrush(COLOR_WINDOW);
SelectBrush(dc,brBack);
PatBlt(dc,0,0,r.right,r.bottom,PATCOPY);
}
if (w.hMutex && WaitForSingleObject(w.hMutex,200)) return;
// timeout - should never occur, SetData() is fast
if (Scope.n) OutScope(dc,&r);
else{
// Value (with unit) sent by multimeter
int slen;
SIZE size;
// readout dividers (at fixed position)
int ro0top=NumReadouts>=1 ? r.bottom>>2 : 0;
int ro0bottom=r.bottom - (NumReadouts>2 ? ro0top : 0);
WCHAR buf[32];
SetBkMode(dc,TRANSPARENT);
if (NumReadouts) {
int bu=(r.bottom*3)>>2; // bottom for unit (if value with 7-segment font)
if (NumReadouts>2) {
bu=MulDiv(bu,14,16);
}
Readout[0].BtOut(dc,0,ro0top,r.right,ro0bottom);
int fonty=ro0bottom-1+((ro0bottom-ro0top-2)>>4);
if (Config.sevenSeg && gGdi.unitfont) {
SetTextAlign(dc,TA_BOTTOM|TA_LEFT);
slen=int(Readout[0].GenUnitW(buf,RO::UNITPREFIX)-buf);
SelectFont(dc,gGdi.fntRo);
GetTextExtentPointW(dc,buf,slen,&size);
int x=r.right-size.cx-(r.right>>4); // divider number - unit
TextOutW(dc,x,bu,buf,slen);
x-=r.right>>4;
SelectFont(dc,gGdi.fntBig);
DecreaseFontWidth(dc,Readout[0].Measure7Seg(dc),x,gGdi.fntBig);
SetTextAlign(dc,TA_BOTTOM|TA_RIGHT);
Readout[0].Out7Seg(dc,x,fonty);
}else{
SetTextAlign(dc,TA_BOTTOM|TA_CENTER);
SelectFont(dc,gGdi.fntBig); // "LEADSPACE" prevents annoying jumping of centered output on sign change
slen=int(Readout[0].GenStringW(buf,RO::EFLAGS(RO::NORMAL|RO::LEADSPACE))-buf);
MakeStringFitW(dc,r.right,gGdi.fntBig,buf,slen);
TextOutW(dc,r.right/2,fonty,buf,slen);
}
}
// Additionals (like HOLD, MIN, MAX, DELTA, PEAK, Diode, hFE, Beta, Beep) sent by multimeter
SelectFont(dc,gGdi.fntEx);
int m=r.right; // middle or right?
int fonty=ro0top+((ro0top)>>4);
switch (NumReadouts) {
case 4: // Place fourth/secondary readout to the top-right corner of window
case 2: { // Features are on top-left corner
m>>=1;
int x=Features.Measure(dc);
SelectFont(dc,gGdi.fntRo);
WCHAR buf[32];
RO&ro=Readout[NumReadouts-1];
int slen=int(ro.GenStringW(buf)-buf);
GetTextExtentPoint32W(dc,buf,slen,&size);
DecreaseFontWidth(dc,size.cx,m,gGdi.fntRo);
ro.BtOut(dc,m,0,r.right,ro0top-1);
SetTextAlign(dc,TA_BOTTOM|TA_CENTER);
TextOutW(dc,m+(m>>1),fonty,buf,slen);
SelectFont(dc,gGdi.fntEx);
}nobreak;
case 3:
case 1: { // Features are on top-center
int x=Features.Measure(dc);
DecreaseFontWidth(dc,x,m,gGdi.fntEx);
MoveToEx(dc,(m-x)>>1,0,NULL); // Zentrierung
SetTextAlign(dc,TA_TOP|TA_LEFT|TA_UPDATECP);
Features.Out(dc);
SelectPen(dc,gGdi.penDiv);
Line(dc,0,ro0top,r.right,ro0top); // waagerechte Linie
Line(dc,m,0,m,ro0top); // senkrechte Linie (evtl. au▀erhalb)
}break;
case 0: { // Message string on center-center of window
SetTextAlign(dc,TA_BASELINE|TA_CENTER);
slen=MyLoadStringW(Features.f,buf,elemof(buf)); // Show a message
MakeStringFitW(dc,r.right,gGdi.fntEx,buf,slen);
TextOutW(dc,r.right/2,r.bottom/2,buf,slen);
}break;
}
if (NumReadouts>2) { // place two extra readouts below main readout
SelectFont(dc,gGdi.fntRo);
m=r.right>>1;
int fonty=r.bottom+((r.bottom-ro0bottom)>>4);
for (int i=1,x=0; i<=2; i++,x=r.right-m) {
WCHAR buf[32];
int slen=int(Readout[i].GenStringW(buf)-buf);
SIZE size;
GetTextExtentPoint32W(dc,buf,slen,&size);
DecreaseFontWidth(dc,size.cx,m,gGdi.fntRo);
Readout[i].BtOut(dc,x,ro0bottom,x+m,r.bottom);
SetTextAlign(dc,TA_BOTTOM|TA_CENTER);
TextOutW(dc,x+(m>>1),fonty,buf,slen);
}
SelectPen(dc,gGdi.penDiv);
--ro0bottom;
Line(dc,0,ro0bottom,r.right,ro0bottom); // waagerechte Linie (ⁿberhΣngende Diagrammelemente abschneiden)
Line(dc,m,ro0bottom,m,r.bottom); // senkrechte Linie
}
}
if (w.hMutex) ReleaseMutex(w.hMutex);
}
/*************************
* Main window procedure *
*************************/
static LONG_PTR CALLBACK MainWndProc(HWND Wnd, UINT Msg, WPARAM wParam, LPARAM lParam) {
switch (Msg) {
case WM_CREATE: {
ghMainWnd=Wnd;
// Valid command-line arguments are:
// device@port - initializes this device at this port
// device - locates first saved registry entry for this device (port may be random)
// @port - locates first saved registry entry for this port (device may be random)
// #number - locates n-th registry entry (one-based) TODO
// With this argument, LoadConfig places the window to the saved state ...
// If device is unknown, setup dialog appears.
// If port is unknown (given but doesn't exist), setup dialog appears.
// If port is not given, and multiple ports exist, setup dialog appears.
// Otherwise, the software starts operation with the only port found.
// The software never attempts to use a configuration name that is used by another instance.
HMENU m=GetSystemMenu(Wnd,FALSE);
AppendMenu(m,MF_SEPARATOR,0,NULL);
TCHAR s[64];
LoadString(ghInst,4,s,elemof(s));
AppendMenu(m,MF_STRING,0x40,s);
LoadString(ghInst,5,s,elemof(s));
AppendMenu(m,MF_STRING,0x50,s);
LoadString(ghInst,6,s,elemof(s));
AppendMenu(m,MF_STRING,0x60,s);
SendMessage(Wnd,WM_WININICHANGE,0,0);
EnumAll(); // load all plugins
lstrcpyn(ConfName,PathGetArgs(GetCommandLine()),elemof(ConfName));
AppendUniquePort();
LoadConfig();
PostMessage(Wnd,WM_USER+100,0,0);
}break;
case WM_USER+100: {
SetInitScreen();
if (w.conf.Make()) w.Start();
else PostMessage(Wnd,WM_COMMAND,0x40,0);
}break;
case WM_USER+22: { // lParam = bits indicating what portion of information has changed
if (lParam) {
if (lParam&1<<24) DdePostAdvise(gDdeInst,0,0);
if (IsIconic(Wnd)) {
if (lParam&0x2000F) SetWindowTitle(); // check Readout[0] change, not else
}else{
if (lParam&0xA8888) gGdi.Make();
if (gGdi.dcDblBuf) HandlePaint(gGdi.dcDblBuf,PRF_ERASEBKGND);
InvalidateRect(Wnd,NULL,TRUE);
}
}
if (lParam&(1<<24) && Config.flashIcon) {
SendMessage(Wnd,WM_SETICON,ICON_SMALL,(LPARAM)ghIcons[1]);
SetTimer(Wnd,2,100,NULL);
}
}break;
case WM_TIMER: switch (wParam) {
case 2: {
KillTimer(Wnd,wParam);
SendMessage(Wnd,WM_SETICON,ICON_SMALL,(LPARAM)ghIcons[0]);
}break;
}break;
case WM_WININICHANGE: {
TCHAR sDecimal[2];
GetProfileString(T("intl"),T("sDecimal"),T("."),sDecimal,elemof(sDecimal));
#ifdef UNICODE
cDecimalW=*sDecimal;
WideCharToMultiByte(CP_ACP,0,sDecimal,1,&cDecimalA,1,".",NULL);
#else
cDecimalA=*sDecimal;
MultiByteToWideChar(CP_ACP,0,sDecimal,1,&cDecimalW,1);
#endif
}return 0;
case WM_SIZE: {
if (wParam==SIZE_MINIMIZED) {
SetWindowTitle();
gGdi.Delete(); // no GDI resources are needed, so free all!
}else{
gGdi.Make(GET_X_LPARAM(lParam),GET_Y_LPARAM(lParam));
if (gGdi.dcDblBuf) HandlePaint(gGdi.dcDblBuf,PRF_ERASEBKGND);
}
}break;
case WM_PAINT: {
PAINTSTRUCT ps;
BeginPaint(Wnd,&ps);
SendMessage(Wnd,WM_PRINTCLIENT,(WPARAM)ps.hdc,PRF_ERASEBKGND);
EndPaint(Wnd,&ps);
}return 0;
case WM_PRINTCLIENT: {
if (gGdi.dcDblBuf) {
RECT r;
GetClientRect(Wnd,&r);
BitBlt((HDC)wParam,0,0,r.right,r.bottom,gGdi.dcDblBuf,0,0,SRCCOPY);
}else HandlePaint((HDC)wParam,lParam);
}break;
case WM_QUERYOPEN: {
SetWindowText(Wnd,ConfName);
}break;
case WM_COPY: {
HGLOBAL hMemW=GlobalAlloc(GMEM_MOVEABLE|GMEM_SHARE,32*sizeof(WCHAR));
HGLOBAL hMemA=GlobalAlloc(GMEM_MOVEABLE|GMEM_SHARE,32);
if (hMemW && hMemA && OpenClipboard(Wnd) && EmptyClipboard()) {
Readout[0].GenStringW((LPWSTR)GlobalLock(hMemW));
GlobalUnlock(hMemW);
Readout[0].GenStringA((LPSTR)GlobalLock(hMemA));
GlobalUnlock(hMemA);
SetClipboardData(CF_UNICODETEXT,hMemW);
SetClipboardData(CF_TEXT,hMemA);
CloseClipboard();
}else{
GlobalFree(hMemA);
GlobalFree(hMemW);
}
}break;
case WM_COMMAND: // from TranslateAccelerator
case WM_SYSCOMMAND: switch (wParam&0xFFF0) {
case 0x40: if (DialogBox(ghInst,MAKEINTRESOURCE(0x40),Wnd,(DLGPROC)CONFDLG::DlgProc)==IDOK) {
w.Stop();
SaveConfig();
SetInitScreen();
w.Start();
}break;
case 0x50: SendMessage(Wnd,WM_COPY,0,0); break;
case 0x60: gLog.OnOff(Wnd,3); break;
}break;
case WM_CLOSE: {
w.Stop();
gLog.OnOff(Wnd,2);
SaveConfig();
}break;
case WM_ENDSESSION: if (wParam) {
SaveConfig(); // don't halt thread, instead, ensure fast Windows shutdown
if (Config.surviveReboot) SetRunOnce();
}break;
case WM_DESTROY: {
gGdi.Delete();
PostQuitMessage(IDOK);
}break;
}
return DefWindowProc(Wnd,Msg,wParam,lParam);
}
void CALLBACK WinMainCRTStartup() {
ghInst=GetModuleHandle(NULL);
INITCOMMONCONTROLSEX icc={sizeof(icc),ICC_USEREX_CLASSES|ICC_DATE_CLASSES};
InitCommonControlsEx(&icc);
ghIcons[0]=(HICON)LoadImage(ghInst,MAKEINTRESOURCE(1),IMAGE_ICON,16,16,0);
ghIcons[1]=(HICON)LoadImage(ghInst,MAKEINTRESOURCE(2),IMAGE_ICON,16,16,0);
WNDCLASSEX wc={
sizeof(wc),
CS_BYTEALIGNWINDOW|CS_DBLCLKS|CS_HREDRAW|CS_VREDRAW,
MainWndProc,
0,
0,
ghInst,
LoadIcon(ghInst,MAKEINTRESOURCE(1)),
LoadCursor(0,IDC_ARROW),
0,
NULL,
T("DMM"),
ghIcons[0]};
RegisterClassEx(&wc);
BuildPrelettersA();
BuildUnitsA();
LoadString(ghInst,1,StdMBoxTitle,elemof(StdMBoxTitle));
GetModuleFileName(0,HelpName,elemof(HelpName));
lstrcpy(PathFindExtension(HelpName),T(".chm"));
// Problem: DDE im Haupt-Thread benutzt die gleiche Warteschlange
// wie die Benutzereingaben (gΣhn).
// DDE im Worker-Tread erfordert das Beackern der Warteschlange
// (also alle WaitForSingleObject ersetzen durch MsgWaitForMultipleObjects)
// und/oder das Erzeugen eines versteckten Fensters
// DdePostAdvise() ist zu bl÷d, in die richtige Warteschlange zu posten,
// da muss eine Hilfskonstruktion (PostMessage) her.
CF_XlTable=RegisterClipboardFormat(T("XlTable")); // fast Excel format
DdeInitialize(&gDdeInst,DdeCallback,APPCLASS_STANDARD
|CBF_FAIL_EXECUTES|CBF_FAIL_POKES
|CBF_SKIP_CONNECT_CONFIRMS|CBF_SKIP_REGISTRATIONS
|CBF_SKIP_DISCONNECTS|CBF_SKIP_UNREGISTRATIONS,0);
hszDmm=DdeCreateStringHandle(gDdeInst,T("DMM"),CP_WINNEUTRAL);
// HSZ hszt=DdeCreateStringHandle(gDdeInst,T("bla"),CP_WINNEUTRAL);
DdeNameService(gDdeInst,hszDmm,0,DNS_REGISTER|DNS_FILTERON);
// DdeFreeStringHandle(gDdeInst,hsz);
// gHszList.Create(T("\0") T("Value\0") T("Value2\0") T("Unit\0") T("Unit2\0") T("Trace\0"));
// hszMinMax=DdeCreateStringHandle(gDdeInst,T("MinMax"),CP_WINNEUTRAL);
// hszFeatures=DdeCreateStringHandle(gDdeInst,T("Features"),CP_WINNEUTRAL);
CreateWindowEx(WS_EX_OVERLAPPEDWINDOW,T("DMM"),ConfName,
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,0,400,200,
0,0,ghInst,NULL);
ShowWindow(ghMainWnd,Config.showCmd);
MSG Msg;
HACCEL hAccel=LoadAccelerators(ghInst,MAKEINTRESOURCE(1));
while (GetMessage(&Msg,0,0,0)) {
if (TranslateAccelerator(ghMainWnd,hAccel,&Msg)) continue;
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
// gHszList.Delete();
DdeUninitialize(gDdeInst);
ExitProcess((UINT)Msg.wParam);
}
Vorgefundene Kodierung: OEM (CP437) | 1
|
|