/* Relais-Steuerung mit 3x Trigger-Abfrage und 3x Trigger-Ausgängen
* Zum Programmierstil siehe http://www.tu-chemnitz.de/~heha/hs_freeware/mein_msvc.htm
* h#s 12/06 - 04/07
Zu tun:
* "maximiert" mit speichern
* "Alle LEDs an/aus auch in Liste mit MMB/RMB
+070524 Kommandozeilenargument = Dateiname, Registrierung von .relay
+070529 Geladener Dateiname in Titelzeile, mit Modifikationsanzeige
+070619 5 ToolInfos, "Direkthilfe", Fragezeichen in Titelleiste, graue „ausgewählte Zeile“
-070704 Breite für 5 statt 4 Spalten speichern, verschwindender Dateiname, ExecName-Bug
*/
#define WIN32_LEAN_AND_MEAN
#define _WIN32_WINNT 0x0501 // WM_THEMECHANGED aktivieren
#include <windows.h>
#include <windowsx.h> // Makros
#include <commdlg.h> // Datei öffnen/speichern
#include <commctrl.h> // Listenfenster für die Zeitschritte
#include <shellapi.h> // HDROP
#include <shlwapi.h> // nützliche Funktionen
#include <shlobj.h> // PathIsExe
#include <uxtheme.h> // für Fragezeichen-Knopf (Kontexthilfe)
#include <tmschema.h> // noch mehr XP-Müll
#define elemof(x) (sizeof(x)/sizeof((x)[0]))
#define T(x) TEXT(x)
#define nobreak
typedef enum {false,true} bool; // nicht für C++!
//Leider hat Win95 mit IE 3.0 ein reduziertes Angebot der
//shlwapi.dll, kein StrCatBuff, kein wnsprintf, kein wvnsprintf,
//daher Ersetzung durch „unsichere“ Funktionen aus kernel32.dll
//Weiterhin kennt Win95 die Schrift "MS Shell Dlg 2" nicht...
#ifdef WIN95
# undef StrCatBuff
# define StrCatBuff(d,s,l) lstrcat(d,s)
# define wnsprintf1(d,l,t,a1) wsprintf(d,t,a1)
# define wnsprintf2(d,l,t,a1,a2) wsprintf(d,t,a1,a2)
# define wnsprintf3(d,l,t,a1,a2,a3) wsprintf(d,t,a1,a2,a3)
# define wnsprintf6(d,l,t,a1,a2,a3,a4,a5,a6) wsprintf(d,t,a1,a2,a3,a4,a5,a6)
# undef wvnsprintf
# define wvnsprintf(d,l,t,a) wvsprintf(d,t,a)
#else
# define wnsprintf1 wnsprintf
# define wnsprintf2 wnsprintf
# define wnsprintf3 wnsprintf
# define wnsprintf6 wnsprintf
#endif
#ifdef DEBUG
# define _debug(x) DebugPrintf x
void _cdecl DebugPrintf(LPCTSTR s,...) {
TCHAR buf[256];
wvnsprintf(buf,elemof(buf),s,(va_list)(&s+1));
OutputDebugString(buf);
}
#else
# define _debug(x)
#endif
#ifdef WIN32
# define Send_WM_Command(ToWnd,CtlId,NotifyCode,FromWnd)\
SendMessage(ToWnd,WM_COMMAND,MAKELONG(CtlId,NotifyCode),(LPARAM)FromWnd);
#else // Win16
# define Send_WM_Command(ToWnd,CtlId,NotifyCode,FromWnd)\
SendMessage(ToWnd,WM_COMMAND,CtlId,MAKELONG((UINT)FromWnd,NotifyCode));
#endif
// Deklaration der zwei Einsprünge in (statisch gebundene) InpOut32.dll
void WINAPI Out32(WORD,BYTE);
BYTE WINAPI Inp32(WORD);
/*****************************************
* Globale Variablen und Typdefinitionen *
*****************************************/
WORD gLptBase=0x378; // Voreinstellung
HINSTANCE ghInstance;
HWND ghMainWnd;
HWND ghListWnd;
HWND ghStatusWnd;
BYTE gCurLptState[3]; // Momentaner LED-Zustand
bool gUseStbBsyAck=true;// für Sundox-Relaiskarte (BSY und ACK entfallen für Trigger)
bool gDoBeep=true; // Piepser bei jedem Schritt
bool gModified=false; // Inhalt von gFileName modifiziert?
UINT gEditErrors; // Bit pro Editfenster für Fehler (Hintergrund rot)
TCHAR gFileName[MAX_PATH]; // Zuletzt geladene Datei
POINT gMinTrackSize; // minimale Fenstergröße
const TCHAR gHelpFileName[]=T("Relais1.hlp");
static HWND gToolTip;
typedef struct{
BYTE hour,min,sec,hsec; // Kompakte Zeitangabe (ohne Tag, Wochentag o.ä.)
}MYTIME,*PMYTIME;
typedef struct{
BYTE sourcebit,polarity; // 1. Byte: Bit-Nummer, 2. Byte: H-Aktivität
}MYTRIGGER,*PMYTRIGGER;
// Diese Struktur "klebt" an jeder Zeile der ListView.
// Deshalb unterhält dieses Programm keine eigene verkettete Liste
typedef struct{
BYTE Flags; // ENUM: (UHRZEIT, DAUER, TRIGGER; BEGINLOOP, ENDLOOP, RELATIVZEIT)
BYTE LoopCount;// Runden-Anzahl (0=unendlich, nur für äußere Schleife)
BYTE RelState; // Gewünschter Relais-Zustand (Datenport)
BYTE TrigOut; // Trigger-Ausgang-Zustand (Steuerport), Optokoppler
union{
MYTIME Uhrzeit;
MYTIME Dauer;
MYTRIGGER Trigger;
};
LPTSTR ExecFileName;
}ITEM,*PITEM;
#define LOADITEMSIZE 8 // ExecFileName nicht (per GetPrivateProfileStruct) laden
enum {UHRZEIT=1, DAUER, TRIGGER, TYPEMASK=0x0F,
BEGINLOOP=0x10, ENDLOOP=0x20, RELATIVZEIT=0x40};
// GDI-Verwaltung
static const COLORREF gBrushColors[]={
RGB( 0,128,0), RGB( 0,255,0),
RGB(128, 0,0), RGB(255, 0,0),
RGB(128,128,0), RGB(255,255,0)};
static const TCHAR gSmallFontName[]=T("Small Fonts");
union{
struct{
HBRUSH hbr[elemof(gBrushColors)]; // {dunkel-hell}{grün-rot-gelb}
HBRUSH hbrError; // rot (Fehler im Editfenster)
HFONT hSmallFont; // zur ziffernmäßigen Beschriftung
};
HGDIOBJ hgdiobj[elemof(gBrushColors)+1];
}gGdiObj;
//Schleifen-Verwaltung
#define MAXDEPTH 6 // Verschachtelungstiefe für Schleifen
typedef struct {
int info; // Bit0=Trigger, Bit1=unendlich
int line; // Start- bzw. Fehler-Zeilennummer (0-basiert)
int depth;
int remainings[MAXDEPTH+1]; // Verbleibende Iterationen
int backjumps[MAXDEPTH+1]; // Rücksprünge
}LOOPINFO,*PLOOPINFO;
enum { // info-Bits:
INFO_LongerThan=1, // Bit0 = Trigger, Zeit länger als angegeben
INFO_Infinite=2, // Bit1 = unendlich (äußere Schleife)
INFO_ReenterLoop=4, // Bit2 = Schleifen-Wiederholung (Schleifenzähler nicht init.)
INFO_BreakInfinity=8, // Bit3 = nicht endlos "loopen" (zur Zeitberechnung!)
INFO_Overflow=0x10, // Fehler: zu viele Verschachtelungsebenen
INFO_Underflow=0x20, // Fehler: Stackunterlauf
INFO_InnerInfinity=0x30,// Fehler: innere Endlosschleife
INFO_MissingEnd=0x40, // Fehler: fehlendes ENDLOOP am Ende
INFO_NoItem=0x50, // Fehler: fehlendes Item oder Typ
INFO_ErrorMask=0xF0,
INFO_ErrorShift=4};
struct{
// int ListLine; // aktuelle Zeile in der Liste (die Relais werden danach(!) übernommen)
DWORD settimer_ms;
DWORD start_ms; // Startzeitpunkt, zur Laufzeitanzeige
LOOPINFO loopinfo; // zur Schleifen-Verwaltung
#define ListLine loopinfo.line
}gSched; // "Zeitplaner"
PITEM gpSelectedItem;
TCHAR StdMBoxTitle[64];
/****************
* aus WUTILS.C *
****************/
//Win32-typische Strukturen mit DWORD-Ausrichtung initialisieren
void _fastcall InitStruct(LPVOID p, UINT len) {
*((LPUINT)p)=len; len/=sizeof(UINT); len--;
if (len) do *++((LPUINT)p)=0; while (--len);
}
void _fastcall CopyUINT(UINT*d, const UINT*s, UINT len) {
if (len) do *d++=*s++; while (--len);
}
int _cdecl MBox(HWND Wnd, LPCTSTR Text, UINT Type, ...) {
TCHAR buf[256],buf2[256];
if (!HIWORD(Text)) {
LoadString(ghInstance,(UINT)(DWORD_PTR)Text,buf2,elemof(buf2));
Text=buf2;
}
wvnsprintf(buf,elemof(buf),Text,(va_list)(&Type+1));
return MessageBox(Wnd,buf,StdMBoxTitle,Type);
}
void WMMenuSelect(UINT id, UINT flags) {
HINSTANCE hCommCtrl;
TCHAR s[256];
SendMessage(ghStatusWnd,SB_SIMPLE,flags!=0xFFFF,0);
if (flags==0xFFFF) return;
s[0]=0;
if (flags&MF_SYSMENU) {
id&=0x8FF0;
if (flags&MF_POPUP) id=0x8FF0;
hCommCtrl=LoadLibrary(T("comctl32.dll")); // Da sind sie!
LoadString(hCommCtrl,id,s,sizeof(s));
FreeLibrary(hCommCtrl);
}else if (flags&MF_POPUP) {}
else LoadString(ghInstance,id,s,elemof(s));
SendMessage(ghStatusWnd,SB_SETTEXT,255|SBT_NOBORDERS,(LPARAM)(LPTSTR)s);
}
// Wie Windows NT auf allen Windows-Versionen (da fehlt ein shlwapi.Beep?)
BOOL MyBeep(DWORD dwFreq, DWORD dwDuration) {
if ((long)GetVersion()>=0) return Beep(dwFreq,dwDuration);
else{ // Win32s, Win9x/Me
if (dwFreq<1843200L/0x10000/2) return FALSE;
dwFreq=1843200L/2/dwFreq; // Teilerwert
_asm{
cli
mov al,0xB6 // Timer2: Rechteckgenerator, 16bit
out 0x43,al
mov eax,dwFreq
out 0x42,al // Timer2: Endwert Low-Teil setzen
xchg ah,al
out 0x42,al // Timer2: Endwert High-Teil setzen
in al,0x61
or al,0x03
out 0x61,al // Ein- und Ausgang des Timer2 freischalten
sti
}
Sleep(dwDuration);
_asm{
cli
in al,0x61
and al,~0x03
out 0x61,al // Ein- und Ausgang des Timer2 sperren
sti
}
return TRUE;
}
}
DWORD MyTimeToMs(PMYTIME time) {
return (((time->hour*60+time->min)*60+time->sec)*100+time->hsec)*10;
}
#define MS_PER_DAY (24UL*60*60*1000)
DWORD NowToMs(void) {
SYSTEMTIME st;
GetLocalTime(&st);
return ((st.wHour*60UL+st.wMinute)*60+st.wSecond)*1000+st.wMilliseconds;
}
int TimeInterval(PMYTIME time, PTSTR buf, UINT buflen) {
// Benutzt Windows-Funktion zur "hübschen" Darstellung einer Zeitspanne
DWORD time_ms=MyTimeToMs(time); // Millisekunden (Hundertstel unwichtig)
return StrFromTimeInterval(buf,buflen,time_ms,6);
}
int TimeAbsolute(PMYTIME time, PTSTR buf, UINT buflen) {
// Benutzt Windows-Funktion für (einigermaßen) lokal-angepasste Uhrzeit-Darstellung
SYSTEMTIME st;
st.wHour=time->hour;
st.wMinute=time->min;
st.wSecond=time->sec;
st.wMilliseconds=time->hsec*10;
return GetTimeFormat(LOCALE_USER_DEFAULT,TIME_FORCE24HOURFORMAT,&st,NULL,buf,buflen)-1;
}
/************************************************
* LED-Darstellung (etwa wie LptChk, WinDriver) *
************************************************/
void MakeGdiObj(void) {
int i;
for (i=0; i<elemof(gBrushColors); i++) {
gGdiObj.hbr[i]=CreateSolidBrush(gBrushColors[i]);
}
gGdiObj.hbrError=CreateSolidBrush(RGB(255,128,128));
gGdiObj.hSmallFont=CreateFont(-7,0,0,0,0,0,0,0,0,0,0,0,0,gSmallFontName);
}
void KillGdiObj(void) {
int i;
for (i=0; i<elemof(gGdiObj.hgdiobj); i++) {
DeleteObject(gGdiObj.hgdiobj[i]);
}
}
void DrawOneLed(HDC dc, LPCRECT pr, BYTE FarbIndex, TCHAR number) {
RECT r;
COLORREF ocolor;
int obkmode;
// Schwarzen Hintergrund malen
HBRUSH obrush=SelectBrush(dc,GetStockBrush(BLACK_BRUSH));
HPEN open=SelectPen(dc,GetStockPen(NULL_PEN));
CopyRect(&r,pr);
PatBlt(dc,r.left,r.top,r.right-r.left,r.bottom-r.top,PATCOPY);
// Kreis (LED) malen
SelectBrush(dc,gGdiObj.hbr[FarbIndex]);
InflateRect(&r,-1,-1);
Ellipse(dc,r.left,r.top,r.right,r.bottom);
// Beschriften
ocolor=SetTextColor(dc,FarbIndex&1?RGB(0,0,0):RGB(255,255,255));
obkmode=SetBkMode(dc,TRANSPARENT);
// ExtTextOut(dc,r.left,r.top,&r,&number,1,NULL);
DrawText(dc,&number,1,&r,DT_CENTER|DT_NOPREFIX|DT_SINGLELINE|DT_VCENTER);
SetBkMode(dc,obkmode);
SetTextColor(dc,ocolor);
// GDI aufräumen
SelectBrush(dc,obrush);
SelectPen(dc,open);
}
// Button besitzergezeichnet, zum direkten Schalten
void LedButtonHandleDraw(LPDRAWITEMSTRUCT dis,BYTE leds,BYTE numbits,BYTE FarbIndex) {
RECT r;
TCHAR c;
CopyRect(&r,&dis->rcItem);
if (dis->itemAction&(ODA_DRAWENTIRE|ODA_SELECT)) {
c=T('1');
do{
r.right=r.left+(r.bottom-r.top); // Höhe=Breite (eigentlich: Aspectratio einrechnen!!)
DrawOneLed(dis->hDC,&r,FarbIndex+(leds&1),c);
r.left=r.right;
leds>>=1;
c++;
}while(--numbits);
r.left=dis->rcItem.left; // restaurieren
}else{
r.right=r.left+(r.bottom-r.top)*numbits; // rechte Seite eingrenzen
}
if (dis->itemState&ODS_FOCUS || dis->itemAction==ODA_FOCUS) {
DrawFocusRect(dis->hDC,&r);
}
// Den Rest des Knopfes mit Hintergrundfarbe auffüllen, weil EraseBkgnd abgeschaltet wurde
SelectBrush(dis->hDC,(HBRUSH)SendMessage(ghMainWnd,WM_CTLCOLORDLG,(WPARAM)dis->hDC,(LPARAM)ghMainWnd));
PatBlt(dis->hDC,r.right,r.top,dis->rcItem.right-r.right,r.bottom-r.top,PATCOPY);
}
#define BN_BITALL0 0x1000 // Kode für "alles AUS"
#define BN_BIT0HIT 0x1001 // darauf die Bitnummer addiert
#define BN_BITALL1 0x1009 // Kode für "alles EIN"
WNDPROC gOldButtonProc;
// Zur Steuerung der LED-Anzeigen per Maus und Tatstatur
// An das Elternfenster (Dialog) wird WM_COMMAND mit
// BN_BIT0HIT .. BN_BIT7HIT gesendet.
LRESULT WINAPI LedButtonSubclassProc(HWND Wnd, UINT Msg, WPARAM wParam, LPARAM lParam) {
switch (Msg) {
// Tastaturbedienung
case WM_KEYDOWN: {
wParam-='0';
if (wParam<10) {
Send_WM_Command(GetParent(Wnd),GetWindowID(Wnd),BN_BITALL0+wParam,Wnd);
}
}break;
// Mausbedienung
case WM_LBUTTONDOWN:
case WM_LBUTTONDBLCLK: {
RECT r;
GetWindowRect(Wnd,&r);
wParam=(WPARAM)(GET_X_LPARAM(lParam)/(r.bottom-r.top));
if (wParam<8) {
Send_WM_Command(GetParent(Wnd),GetWindowID(Wnd),BN_BIT0HIT+wParam,Wnd);
}
}break;
// Alles AUS mit rechter Maustaste
case WM_RBUTTONDOWN:
case WM_RBUTTONDBLCLK: {
Send_WM_Command(GetParent(Wnd),GetWindowID(Wnd),BN_BITALL0,Wnd);
}break;
// alles EIN mit mittlerer Maustaste (sofern vorhanden)
case WM_MBUTTONDOWN:
case WM_MBUTTONDBLCLK: {
Send_WM_Command(GetParent(Wnd),GetWindowID(Wnd),BN_BITALL1,Wnd);
}break;
// Flackern vermeiden (nur Knöpfe mit BS_OWNERDRAW flackern per se!)
case WM_ERASEBKGND: return TRUE;
}
return CallWindowProc(gOldButtonProc,Wnd,Msg,wParam,lParam);
}
BOOL InvalidateItem(UINT id, PRECT pRect, BOOL bErase) {
return InvalidateRect(GetDlgItem(ghMainWnd,id),pRect,bErase);
}
void SetModified(bool newstate) {
if (!gModified || !newstate) {
TCHAR s[MAX_PATH];
gModified=newstate;
if (*gFileName) {
wnsprintf3(s,elemof(s),T("%s %c %s"),
PathFindFileName(gFileName),gModified?'*':'-',StdMBoxTitle);
}else{
wnsprintf2(s,elemof(s),T("%s %c"),
StdMBoxTitle,gModified?'*':0);
}
SetWindowText(ghMainWnd,s);
}
}
/*********************************
* Listbox und Besitzerzeichnung *
*********************************/
#define SELECTED (-2)
int ListGetSelected(void) {
return ListView_GetNextItem(ghListWnd,-1,LVNI_SELECTED);
}
// "Copy-Konstruktor", mit pSrc==NULL ist's ein "Default-Konstruktor"
PITEM NewItem(PITEM pSrc) {
PITEM pItem=LocalAlloc(LPTR,sizeof(ITEM));
if (pItem && pSrc) {
CopyUINT((UINT*)pItem,(UINT*)pSrc,(sizeof(ITEM)+sizeof(UINT)-1)/sizeof(UINT));
if (pSrc->ExecFileName) pItem->ExecFileName=StrDup(pSrc->ExecFileName);
}
return pItem;
}
// "Destruktor" - danach ist Zeiger ungültig
void DeleteItem(PITEM pItem) {
if (pItem->ExecFileName) LocalFree(pItem->ExecFileName);
LocalFree(pItem);
}
WNDPROC gOldLvProc;
LRESULT WINAPI LvSubclassProc(HWND Wnd, UINT Msg, WPARAM wParam, LPARAM lParam) {
switch (Msg) {
// Tastaturbedienung (LVN_KEYDOWN hatte Seiteneffekte!)
case WM_KEYDOWN: {
bool redraw=false;
unsigned k=(unsigned)wParam;
if (k!=VK_INSERT && !gpSelectedItem) break; // nichts tun
switch (k) {
case VK_INSERT: {
SendMessage(ghMainWnd,WM_COMMAND,121,0); // Knopf "Neue Zeile"
}return 0;
case VK_DELETE: {
SendMessage(ghMainWnd,WM_COMMAND,122,0); // Knopf "Löschen"
} return 0;
case VK_DOWN: if (GetKeyState(VK_CONTROL)<0) {
SendMessage(ghMainWnd,WM_COMMAND,123,0); return 0; // Knopf "Item runter"
}break;
case VK_UP: if (GetKeyState(VK_CONTROL)<0) {
SendMessage(ghMainWnd,WM_COMMAND,124,0); return 0; // Knopf "Item rauf"
}break;
case '0': gpSelectedItem->RelState=0; redraw=true; break;
case '9': gpSelectedItem->RelState=0xFF; redraw=true; break;
default: {
k-='1'; if (k<8) {gpSelectedItem->RelState^=1<<k; redraw=true; }
k-='A'-'1'; if (k<8) {gpSelectedItem->TrigOut^=1<<k; redraw=true; }
}
}
if (redraw) {
int idx=ListGetSelected();
ListView_RedrawItems(Wnd,idx,idx);
}
}break;
}
return CallWindowProc(gOldLvProc,Wnd,Msg,wParam,lParam);
}
#define ITEMHEIGHT 16
// Bereitstellen des Strings für Spalte 0
int LineColumn0(PITEM pItem, PTSTR buf, UINT buflen) {
TCHAR buf2[40],tem[40];
int i;
LoadString(ghInstance,pItem->Flags&TYPEMASK,tem,elemof(tem)); //UHRZEIT/DAUER/TRIGGER
switch (pItem->Flags&TYPEMASK) {
case UHRZEIT: {
i=TimeAbsolute(&pItem->Uhrzeit,buf2,elemof(buf2));
if (pItem->Flags&RELATIVZEIT) {
LoadString(ghInstance,17/* (r)*/,buf2+i,elemof(buf2)-i); // r-Suffix anhängen
}
}return wnsprintf1(buf,buflen,tem,buf2);
case DAUER: {
TimeInterval(&pItem->Dauer,buf2,elemof(buf2));
}return wnsprintf1(buf,buflen,tem,buf2);
case TRIGGER:
return wnsprintf2(buf,buflen,tem,
pItem->Trigger.sourcebit,
pItem->Trigger.polarity?'H':'L');
}
return 0;
}
// Bereitstellen des Strings für Spalte 1
int LineColumn1(PITEM pItem, PTSTR buf, UINT buflen) {
int ret=0;
if (!buflen) return ret;
buf[0]=0;
if (!(pItem->Flags&(BEGINLOOP|ENDLOOP))) return ret;
ret=LoadString(ghInstance,4+((pItem->Flags&(BEGINLOOP|ENDLOOP))>>4)-1,
buf,buflen); // Start(%d)/Ende/Schleife(%d)
if (pItem->Flags&BEGINLOOP) {
ret+=pItem->LoopCount
? wnsprintf1(buf+ret,buflen-ret,T(" (%ux)"),pItem->LoopCount)
: LoadString(ghInstance,39/*endlos*/,buf+ret,buflen-ret);
}
return ret;
}
// ListView besitzergezeichnet
// Auch das besitzergezeichnete ListView flackert per se.
void HandleDrawList(LPDRAWITEMSTRUCT dis) {
RECT r;
TCHAR c;
BYTE b;
TCHAR buf[40];
COLORREF ocolor,obkcolor; // Gemerkte Farben
int n,j;
if (dis->itemAction&(ODA_DRAWENTIRE|ODA_SELECT) && dis->itemData) {
if (dis->itemID==(unsigned)gSched.ListLine) {
// etwas gelber machen; falls gelb, dann weiß u.ä.
obkcolor=SetBkColor(dis->hDC,RGB(0,0,255)^GetBkColor(dis->hDC));
ocolor=SetTextColor(dis->hDC,GetSysColor(COLOR_INFOTEXT));
}else if (dis->itemState&ODS_SELECTED) { // Selektion zeichnen
if (dis->itemState&ODS_FOCUS) {
obkcolor=SetBkColor(dis->hDC,GetSysColor(COLOR_HIGHLIGHT));
ocolor=SetTextColor(dis->hDC,GetSysColor(COLOR_HIGHLIGHTTEXT));
}else{
obkcolor=SetBkColor(dis->hDC,GetSysColor(COLOR_BTNFACE));
ocolor=GetTextColor(dis->hDC);
}
}
//GetSubItemRect vom SubItem 0 funktioniert nicht, liefert ganze Breite, deshalb...
ListView_GetSubItemRect(ghListWnd,dis->itemID,1,LVIR_BOUNDS,&r); // mittlere Spalte (Schleifen)
n=LineColumn1((PITEM)dis->itemData,buf,elemof(buf));
ExtTextOut(dis->hDC,r.left+2,r.top+2,ETO_OPAQUE|ETO_CLIPPED,&r,buf,n,NULL);
j=r.left;
ListView_GetItemRect(ghListWnd,dis->itemID,&r,LVIR_BOUNDS); // linke Spalte (Zeiten)
r.right=j;
n=LineColumn0((PITEM)dis->itemData,buf,elemof(buf));
ExtTextOut(dis->hDC,r.left+2,r.top+2,ETO_OPAQUE|ETO_CLIPPED,&r,buf,n,NULL);
if (dis->itemState&ODS_SELECTED || dis->itemID==(unsigned)gSched.ListLine) {
SetBkColor(dis->hDC,obkcolor);
SetTextColor(dis->hDC,ocolor);
}
if (((PITEM)dis->itemData)->ExecFileName) {
ListView_GetSubItemRect(ghListWnd,dis->itemID,4,LVIR_BOUNDS,&r);
ExtTextOut(dis->hDC,r.left+2,r.top+2,ETO_OPAQUE|ETO_CLIPPED,&r,
((PITEM)dis->itemData)->ExecFileName,
lstrlen(((PITEM)dis->itemData)->ExecFileName),NULL);
}
ListView_GetSubItemRect(ghListWnd,dis->itemID,2,LVIR_BOUNDS,&r); // Relais
j=r.right;
if (dis->itemAction&ODA_DRAWENTIRE) { // nicht bei SELECTED neu zeichnen
b=((PITEM)dis->itemData)->RelState;
for (c=T('1'); c<='8'; c++) {
r.right=r.left+(r.bottom-r.top); // Höhe=Breite (eigentlich: Aspectratio einrechnen!!)
if (r.right>j) r.right=j;
DrawOneLed(dis->hDC,&r,b&1,c);
r.left=r.right;
b>>=1; // nächstes Bit
}
ListView_GetSubItemRect(ghListWnd,dis->itemID,3,LVIR_BOUNDS,&r); // Triggerausgänge
j=r.right;
b=((PITEM)dis->itemData)->TrigOut;
for (c=T('1'); c<=T('3'); c++) {
r.right=r.left+(r.bottom-r.top);
if (r.right>j) r.right=j;
DrawOneLed(dis->hDC,&r,4+(b&1),c);
r.left=r.right;
b>>=1; // nächstes Bit
}
}
}
if (dis->itemState&ODS_FOCUS || dis->itemAction==ODA_FOCUS) {
ListView_GetSubItemRect(ghListWnd,dis->itemID,0,LVIR_BOUNDS,&r);
j=r.left;
ListView_GetSubItemRect(ghListWnd,dis->itemID,1,LVIR_BOUNDS,&r);
r.left=j;
DrawFocusRect(dis->hDC,&r);
}
_debug((T("DrawItem Item=%i Action=%u State=%u\n"),
dis->itemID,dis->itemAction,dis->itemState));
}
int ListCreateColumn(int Spalte, UINT StringId, int Breite) {
LVCOLUMN lvc;
TCHAR s[64];
LoadString(ghInstance,StringId,s,elemof(s));
lvc.mask=LVCF_FMT|LVCF_WIDTH|LVCF_TEXT|LVCF_SUBITEM;
lvc.fmt=LVCFMT_LEFT;
lvc.cx=Breite;
lvc.pszText=s;
lvc.iSubItem=Spalte;
return ListView_InsertColumn(ghListWnd,Spalte,&lvc);
}
// Neues Element bei Index einfügen (Rest rutscht nach hinten)
int ListInsertItem(int idx, PITEM pItem, UINT state) {
LVITEM lvi;
if (state&LVIS_SELECTED && GetFocus()==ghListWnd) state|=LVIS_FOCUSED;
lvi.mask=LVIF_PARAM|LVIF_STATE;
lvi.iItem=idx;
lvi.iSubItem=0; //??notwendig??
lvi.state=state; // sollte übrige Selektionen entfernen...
lvi.stateMask=LVIS_SELECTED|LVIS_FOCUSED;
lvi.lParam=(LPARAM)pItem;
return ListView_InsertItem(ghListWnd,&lvi);
}
// von Index den lParam holen und zum Zeiger casten
PITEM ListGetItem(int iLine) {
LVITEM lvi;
if (iLine==SELECTED) iLine=ListGetSelected();
lvi.mask=LVIF_PARAM;
lvi.iItem=iLine;
lvi.iSubItem=0;
if (!ListView_GetItem(ghListWnd,&lvi)) return NULL;
return (PITEM)lvi.lParam;
}
void ListInvalidateItem(int iLine) {
RECT r;
if (iLine==SELECTED) {
iLine=ListGetSelected();
SetModified(true);
}
if (iLine<0) return;
ListView_GetItemRect(ghListWnd,iLine,&r,LVIR_BOUNDS);
InvalidateRect(ghListWnd,&r,TRUE);
}
/***********************************************
* Relais + Trigger schalten, Trigger abfragen *
***********************************************/
// Strobe ausgeben, sofern erforderlich
// (alternativ könnte man das Parallelport auf Auto-Strobe schalten...)
void MakeStrobe(void) {
if (!gUseStbBsyAck) return;
Out32(gLptBase+2,gCurLptState[2]^0x01);// STB=L
Out32(gLptBase+2,gCurLptState[2]); // STB=H
}
// Parallelport-Spiegelbytes initialisieren
void InitLptState(void) {
gCurLptState[0]=Inp32(gLptBase+0); // Zustände Port-Leitungen
gCurLptState[1]=Inp32(gLptBase+1); // Zustände Trigger-Eingang
gCurLptState[2]=Inp32(gLptBase+2)&~0x20; // DIR=OUT festnageln
MakeStrobe(); // in Flipflop schreiben
}
// Relais-Zustände erfragen
BYTE GetCurRelState(void) {
return gCurLptState[0];
}
// Relais-Zustände setzen und Anzeige nachführen
void SetCurRelState(BYTE NewState) {
if (NewState!=GetCurRelState()) {
gCurLptState[0]=NewState;
Out32(gLptBase+0,gCurLptState[0]);
MakeStrobe();
InvalidateRect(GetDlgItem(ghMainWnd,80),NULL,FALSE);
}
}
// Trigger-Ausgänge erfragen
BYTE GetCurTrigOutState(void) {
return ((gCurLptState[2]^0x0B)>>1)&0x07;
}
// Trigger-Ausgänge setzen und Anzeige nachführen
void SetCurTrigOutState(BYTE NewState) {
// im Fall von gUseStbBusyAck beinhaltet NewState 3 Bits,
// sonst 4 Bits? Zunächst stets 3 Bits, rechts ausgerichtet.
NewState&=7; // nur 3 Bits
if (NewState!=GetCurTrigOutState()) {
NewState<<=1;
NewState^=0x0A; // Bit 3 und 1 invertieren (wegen Parallelport)
gCurLptState[2]=0xC0|NewState; // 1100nnn0
Out32(gLptBase+2,gCurLptState[2]);
InvalidateItem(84,NULL,FALSE);
}
}
// Trigger-Eingänge erfragen und Anzeige nachführen
BYTE GetCurTrigInState(void) {
BYTE ret=Inp32(gLptBase+1);
if (ret!=gCurLptState[1]) {
gCurLptState[1]=ret;
InvalidateItem(83,NULL,FALSE);
}
ret=(gCurLptState[1]^0x80)>>3; // 5 Bits
if (gUseStbBsyAck) ret&=0x07; // 3 Bits
return ret;
}
/************
* Logdatei *
************/
static TCHAR LogFileName[MAX_PATH]; // Log-Dateiname
static HANDLE hLog=INVALID_HANDLE_VALUE; // Log-Dateihandle
void LogClose(void) {
if (hLog!=INVALID_HANDLE_VALUE) {
CloseHandle(hLog);
hLog=INVALID_HANDLE_VALUE;
}
}
void LogOpen(void) {
DWORD cb;
LogClose();
if (!LogFileName[0]) return;
hLog=CreateFile(LogFileName,
FILE_APPEND_DATA,
FILE_SHARE_READ|FILE_SHARE_DELETE,
NULL,
OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL|FILE_FLAG_WRITE_THROUGH,
0);
if (hLog==INVALID_HANDLE_VALUE) {
MBox(ghMainWnd,MAKEINTRESOURCE(23)/*Log-Datei*/,MB_OK,LogFileName);
return;
}
if (SetFilePointer(hLog,0,NULL,FILE_END)) {
static const TCHAR NewLine[]=T("\r\n");
WriteFile(hLog,NewLine,2,&cb,NULL);
}else{
#ifdef UNICODE
static const TCHAR UnicodeIntroducer[1]={0xFEFF}; // zur Erkennung von Unicode und Endian
WriteFile(hLog,UnicodeIntroducer,sizeof(UnicodeIntroducer),&cb,NULL);
#endif
}
}
void LogWrite(UINT MsgIndex) {
int i;
BYTE b,m;
DWORD cb;
TCHAR line[128];
TCHAR format[128],date[32],time[32],relais[9],trigger_in[4],trigger_out[4];
if (hLog==INVALID_HANDLE_VALUE) return; // nicht loggen
LoadString(ghInstance,MsgIndex,format,elemof(format));
GetDateFormat(LOCALE_USER_DEFAULT,DATE_SHORTDATE,NULL,NULL,date,elemof(date));
GetTimeFormat(LOCALE_USER_DEFAULT,TIME_FORCE24HOURFORMAT,NULL,NULL,time,elemof(time));
b=GetCurRelState();
for (i=0,m=1; i<8; i++,m<<=1) relais[i]=m&b?'1':'0';
relais[i]=0;
b=GetCurTrigInState();
for (i=0,m=1; i<3; i++,m<<=1) trigger_in[i]=m&b?'1':'0';
trigger_in[i]=0;
b=GetCurTrigOutState();
for (i=0,m=1; i<3; i++,m<<=1) trigger_out[i]=m&b?'1':'0';
trigger_out[i]=0;
cb=wnsprintf6(line,elemof(line),format,date,time,
gSched.ListLine+1,relais,trigger_in,trigger_out);
if (!WriteFile(hLog,line,cb,&cb,NULL) || !cb) MBox(ghMainWnd,MAKEINTRESOURCE(30),MB_OK);
FlushFileBuffers(hLog); // aus Angst, sollten die Flags bei CreateFile() schon erledigen
}
// Je nach Vorhandensein eines Log-Dateinamens Menü-Häkchen mitführen
void LogSetMenuCheck(void) {
CheckMenuRadioItem(GetMenu(ghMainWnd),1003,1004,LogFileName[0]?1003:1004,MF_BYCOMMAND);
}
/**********************************
* Zeitgesteuerter Programmablauf *
**********************************/
//Behandlung von BEGINLOOP zusammen mit <LoopInfo>:
// * Setzt Felder in <LoopInfo> entsprechend
// * Wertet "ReenterLoop" aus
//<false> bei Fehler
bool HandleBeginLoop(PITEM pItem, PLOOPINFO pLoopInfo) {
if (pItem->Flags&BEGINLOOP) {
if (!(pLoopInfo->info&INFO_ReenterLoop)) {
if (pLoopInfo->depth==MAXDEPTH) {
pLoopInfo->info|=INFO_Overflow; // Überlauf
return false;
}
pLoopInfo->backjumps[pLoopInfo->depth]=pLoopInfo->line;
pLoopInfo->remainings[pLoopInfo->depth]=pItem->LoopCount;
}
pLoopInfo->depth++;
}
return true; // OK
}
//Behandlung von ENDLOOP zusammen mit <LoopInfo>:
// * Dekrementiert Felder in <LoopInfo> entsprechend
// * Setzt LoopInfo.line auf nächste oder zu wiederholende Zeilennummer
// * Bei Schleifenwiederholung wird "ReenterLoop" gesetzt
//<false> bei Fehler
bool HandleEndLoop(PITEM pItem, PLOOPINFO pLoopInfo) {
pLoopInfo->info&=~INFO_ReenterLoop;
pLoopInfo->line++; // im Normalfall nächste Zeile
if (pItem->Flags&ENDLOOP) {
if (pLoopInfo->depth==0) {
pLoopInfo->info|=INFO_Underflow; // Unterlauf
return false;
}
pLoopInfo->depth--;
if (!pLoopInfo->remainings[pLoopInfo->depth]) {
pLoopInfo->info|=0x02; // Endlosschleife
if (pLoopInfo->depth) {
pLoopInfo->info|=INFO_InnerInfinity; // innere Endlosschleife
return false;
}
if (pLoopInfo->info&INFO_BreakInfinity) return true;
}else if (!--pLoopInfo->remainings[pLoopInfo->depth]) return true;
pLoopInfo->line=pLoopInfo->backjumps[pLoopInfo->depth];
pLoopInfo->info|=INFO_ReenterLoop; //Iteration
}//ENDLOOP
return true;
}
// gleichzeitig Test auf korrekte Schleifen-Konstruktion
DWORD EstimateRunningTime(PLOOPINFO pLoopInfo) {
int k=ListView_GetItemCount(ghListWnd);
PITEM pItem;
DWORD sum=0;
DWORD now=NowToMs();
DWORD aim;
pLoopInfo->info|=INFO_BreakInfinity;
for (; pLoopInfo->line<k; ) {
pItem=ListGetItem(pLoopInfo->line);
if (!pItem) {
pLoopInfo->info|=INFO_NoItem; // interner Fehler
return 0;
}
if (!HandleBeginLoop(pItem,pLoopInfo)) return 0;
switch (pItem->Flags&TYPEMASK) {
case UHRZEIT: {
aim=MyTimeToMs(&pItem->Uhrzeit); // Ziel
if (pItem->Flags&RELATIVZEIT) {
while (aim<sum) aim+=MS_PER_DAY; // Ziel nächster Tag
sum=aim; // Summation ist hier faktisch außer Kraft!
}else{
if (aim<now+sum) aim+=MS_PER_DAY; // Ziel nächster Tag
sum+=(aim-now); // Differenz = Dauer
}
}break;
case DAUER: {
sum+=MyTimeToMs(&pItem->Dauer); // Dauer
}break;
case TRIGGER: {
pLoopInfo->info|=INFO_LongerThan; // Zeit ist länger als Rückgabewert
}break;
default: {
pLoopInfo->info|=INFO_NoItem; // leere Zeile
}return 0;
}//switch
if (!HandleEndLoop(pItem,pLoopInfo)) return 0;
}//for
if (pLoopInfo->depth){
pLoopInfo->info|=INFO_MissingEnd; // offene Schleife
return 0;
}
return sum;
}
BOOL EnableItem(UINT id, BOOL state) {
HWND Wnd=GetDlgItem(ghMainWnd,id);
// Element mit Fokus disablen ist tödlich - ohne Maus
if (!state && GetFocus()==Wnd) {
HWND w=Wnd; // Das nächste "enabelte" Element suchen
do w=GetNextSibling(Wnd); while (w && !IsWindowEnabled(w));
if (w) SetFocus(w);
}
return EnableWindow(Wnd,state);
}
void SchedStart(void) {
gSched.ListLine=0;
gSched.start_ms=NowToMs();
gSched.loopinfo.info=gSched.loopinfo.depth=0; // alle Flags AUS
EnableItem(92,TRUE); // Stopp-Knopf EIN
EnableItem(91,FALSE); // Starten-Knopf AUS (Fokus rutscht auf "Stopp")
LogOpen();
LogWrite(27/*gestartet*/);
}
void SchedStop(bool bAbort) {
int PrevItem=gSched.ListLine;
gSched.ListLine=-1;
ListInvalidateItem(PrevItem); // Zeile wieder weiß machen
EnableItem(91,TRUE); // Starten-Knopf EIN
EnableItem(92,FALSE); // Stopp-Knopf AUS (Fokus rutscht auf "Schritt")
LogWrite(bAbort?29:28);
LogClose();
}
bool SchedCalcWait(void) {
// Dauer bis zum zeitlichen Ende des Schritts berechnen, Minimum für Trigger
PITEM pItem=ListGetItem(gSched.ListLine);
if (pItem) {
if (!HandleBeginLoop(pItem,&gSched.loopinfo)) {
SchedStop(true);
return FALSE;
}
// gSched.loopinfo.info|=INFO_ReenterLoop; // nicht noch einmal setzen!
// (Auch dann nicht, wenn nach Systemzeitänderung SchedCalcWait nochmal aufgerufen wird)
switch (pItem->Flags&TYPEMASK) {
case UHRZEIT: {
DWORD Then=MyTimeToMs(&pItem->Uhrzeit);
DWORD Now=NowToMs();
if (pItem->Flags&RELATIVZEIT) Now-=gSched.start_ms;
if (Then<Now) Then+=MS_PER_DAY; // am nächsten Tag
gSched.settimer_ms=Then-=Now;
}break;
case DAUER: gSched.settimer_ms=MyTimeToMs(&pItem->Uhrzeit); break;
case TRIGGER: gSched.settimer_ms=USER_TIMER_MINIMUM; break;
}
}else{ // offenbar Listenende erreicht
SchedStop(false); // Programm-Ende
}
return pItem!=NULL;
}
bool IsTriggered(PITEM pItem) {
switch (pItem->Flags&TYPEMASK) {
case TRIGGER: {
BYTE TriggerInput=GetCurTrigInState();
BYTE mask=1<<(pItem->Trigger.sourcebit-1);
mask&=TriggerInput;
if (pItem->Trigger.polarity) mask=!mask; // wenn H-aktiv: logisch negieren
if (mask) return false; // nicht getriggert
}break;
}
return true;
}
DWORD CALLBACK AsyncBeepThread(LPVOID Data) {
return MyBeep(LOWORD(Data),HIWORD(Data));
}
void AsyncBeep(DWORD dwFreq, DWORD dwDuration) {
CreateThread(NULL,0,AsyncBeepThread,(LPVOID)(DWORD_PTR)MAKELONG(dwFreq,dwDuration),0,&dwFreq);
}
BOOL SchedHandleNext(BOOL ByButton) {
PITEM pItem=ListGetItem(gSched.ListLine);
int PrevItem=gSched.ListLine;
if (!pItem) { // Problem!!
KillTimer(ghMainWnd,1);
return FALSE;
}
if (!ByButton && !IsTriggered(pItem)) return TRUE; // weiter TIMERn
KillTimer(ghMainWnd,1);
SetCurRelState(pItem->RelState);
SetCurTrigOutState(pItem->TrigOut);
if (pItem->ExecFileName) {
TCHAR FileName[MAX_PATH],Parameters[MAX_PATH];
lstrcpyn(FileName,pItem->ExecFileName,elemof(FileName));
lstrcpy(Parameters,PathGetArgs(FileName));
PathRemoveArgs(FileName);
PathUnquoteSpaces(FileName);
if ((DWORD_PTR)ShellExecute(ghMainWnd,NULL,FileName,Parameters,NULL,SW_SHOWNORMAL)<=32) {
Beep(200,500);
}
}
if (!ByButton && gDoBeep) AsyncBeep(400,200); // Geräusch (besser kleines .WAV: Relais-Klicken)
LogWrite(24/*langer Formatstring*/);
if (!HandleEndLoop(pItem,&gSched.loopinfo)) {
SchedStop(true);
return FALSE;
}
ListInvalidateItem(PrevItem); // Zeile wieder weiß machen
if (!SchedCalcWait()) return FALSE;
ListInvalidateItem(gSched.ListLine); // Zeile gelb machen
SetTimer(ghMainWnd,1,gSched.settimer_ms,NULL);
return TRUE;
}
/***********************
* Laden und Speichern *
***********************/
// Lädt Zeit-Daten aus INI-ähnlicher Datei in die Liste
bool LoadFile(LPCTSTR FileName) {
int i;
bool ret=false;
gpSelectedItem=NULL;
ListView_DeleteAllItems(ghListWnd);
for (i=0; ; i++) {
TCHAR Nummer[8],buf[MAX_PATH];
PITEM pItem;
wsprintf(Nummer,T("%i"),i);
pItem=NewItem(NULL);
if (!GetPrivateProfileStruct(T("Liste"),Nummer,
pItem,LOADITEMSIZE,FileName)) {
DeleteItem(pItem); // Ende der Liste
break;
}
if (GetPrivateProfileString(T("Exec"),Nummer,NULL,buf,elemof(buf),FileName)) {
pItem->ExecFileName=StrDup(buf);
}
ListInsertItem(i,pItem,0);
ret=true;
}
i=GetPrivateProfileInt(T("Liste"),T("Select"),0,FileName);
ListView_SetItemState(ghListWnd,i,LVIS_SELECTED,LVIS_SELECTED);
if (ret) SetModified(false);
return ret;
}
// Speichert Zeit-Daten aus Liste in INI-ähnliche Datei, mit Flush
bool SaveFile(LPCTSTR FileName) {
int i;
TCHAR Nummer[8];
bool ret=true;
WritePrivateProfileString(T("Liste"),NULL,NULL,FileName); // leeren
for (i=0; ; i++) {
LVITEM lvi;
PITEM pItem;
lvi.mask=LVIF_PARAM;
lvi.iItem=i;
if (!ListView_GetItem(ghListWnd,&lvi)) break; // lParam (Datenzeiger) holen
pItem=(PITEM)lvi.lParam;
wsprintf(Nummer,T("%i"),i);
if (!WritePrivateProfileStruct(T("Liste"),Nummer,
pItem,LOADITEMSIZE,FileName)) {
ret=false;
}
if (!WritePrivateProfileString(T("Exec"),Nummer,
pItem->ExecFileName,FileName)) ret=false; // setzen oder löschen
}
wsprintf(Nummer,T("%i"),ListGetSelected());
WritePrivateProfileString(T("Liste"),T("Select"),Nummer,FileName);
WritePrivateProfileString(NULL,NULL,NULL,FileName); // Flush (XP liefert immer FALSE)
if (ret) SetModified(false);
return ret;
}
void GetDefaultsFileName(PTSTR Name, UINT len) {
PTSTR p;
GetModuleFileName(0,Name,len);
p=PathFindFileName(Name);
len-=(UINT)(p-Name);
lstrcpyn(p,T("Relais1.ini"),len);
}
// alles mögliche per WritePrivateProfileString() schreiben
// Sonderfall Format == NULL: Value = String (svw. LPCTSTR)
// Sonderfall Format == Value == NULL: Eintrag löschen
// Achtung: Format == "%s": Value = Zeiger auf LPCTSTR!
BOOL vWriteProfile(LPCTSTR FileName, LPCTSTR Section, LPCTSTR Key, LPCTSTR Format, va_list Value) {
TCHAR buf[MAX_PATH];
if (Format) {
wvnsprintf(buf,elemof(buf),Format,Value);
Value=(va_list)buf;
}
return WritePrivateProfileString(Section,Key,(LPCTSTR)Value,FileName);
}
BOOL _cdecl WriteProfile(LPCTSTR FileName, LPCTSTR Section, LPCTSTR Key, LPCTSTR Format, ...) {
return vWriteProfile(FileName,Section,Key,Format,(va_list)(&Format+1));
}
void SaveDefaults(void) {
TCHAR FileName[MAX_PATH];
WINDOWPLACEMENT wp;
int Widths[5],i;
GetDefaultsFileName(FileName,elemof(FileName));
InitStruct(&wp,sizeof(wp));
GetWindowPlacement(ghMainWnd,&wp);
vWriteProfile(FileName,T("MainWnd"),T("WinPos"),T("%i,%i,%i,%i"),(va_list)&wp.rcNormalPosition);
for (i=0; i<5; i++) Widths[i]=ListView_GetColumnWidth(ghListWnd,i);
vWriteProfile(FileName,T("MainWnd"),T("Widths"),T("%i,%i,%i,%i,%i"),(va_list)Widths);
vWriteProfile(FileName,T("MainWnd"),T("LogFile"),NULL,(va_list)(*LogFileName?LogFileName:NULL));
WriteProfile(FileName,T("MainWnd"),T("LptBase"),T("0x%X"),gLptBase);
WriteProfile(FileName,T("MainWnd"),T("UseStbBsyAck"),T("%u"),gUseStbBsyAck);
WriteProfile(FileName,T("MainWnd"),T("DoBeep"),T("%u"),gDoBeep);
//070402: Letzten Schaltzustand der Ausgänge merken
WriteProfile(FileName,T("MainWnd"),T("LptState"),T("0x%X"),
(GetCurTrigOutState()<<8)|GetCurRelState());
vWriteProfile(FileName,T("MainWnd"),T("FileName"),NULL,(va_list)(*gFileName?gFileName:NULL));
WriteProfile(FileName,T("MainWnd"),T("Modified"),T("%u"),gModified);
if (!SaveFile(FileName)) MBox(ghMainWnd,MAKEINTRESOURCE(37)/*Fehler*/,MB_OK,FileName);
}
#define SIZE_SIZEBY 0xAFFE
// Kommaseparierte Integer-Liste in Array wandeln
int ScanInts(LPCTSTR s, int ints[], int elems) {
int i=0;
if (elems) do{
*ints++=StrToInt(s);
i++;
s=StrChr(s,',');
if (!s) break;
s++; // hinter Komma
}while (--elems);
return i; // Anzahl gewandelter Elemente
}
// Vorgaben aus "relais1.ini" laden sowie Listen-Inhalt aus AlternateFileName (Kommandozeile)
void LoadDefaults(LPCTSTR AlternateFileName) {
TCHAR FileName[MAX_PATH];
TCHAR buf[MAX_PATH];
int val;
WINDOWPLACEMENT wp;
POINT newsize;
int Widths[5],i;
InitStruct(&wp,sizeof(wp));
GetWindowPlacement(ghMainWnd,&wp);
gMinTrackSize.x=wp.rcNormalPosition.right-wp.rcNormalPosition.left;
gMinTrackSize.y=wp.rcNormalPosition.bottom-wp.rcNormalPosition.top;
GetDefaultsFileName(FileName,elemof(FileName));
if (AlternateFileName && *AlternateFileName) {
lstrcpyn(gFileName,AlternateFileName,elemof(gFileName));
PathUnquoteSpaces(gFileName);
}else{
lstrcpyn(gFileName,FileName,elemof(gFileName));
AlternateFileName=NULL;
}
if (LoadFile(gFileName)) {
// Fensterposition und -größe lesen
if (GetPrivateProfileString(T("MainWnd"),T("WinPos"),
T(""),buf,elemof(buf),FileName)
&& ScanInts(buf,&wp.rcNormalPosition.left,4)==4) {
// SetWindowPlacement ist Ursache für verschwindenden Fragezeichen-Knopf!
// SetWindowPlacement(ghMainWnd,&wp);
// Deshalb hier: SetWindowPos
SetWindowPos(ghMainWnd,0,
wp.rcNormalPosition.left,
wp.rcNormalPosition.top,
wp.rcNormalPosition.right-wp.rcNormalPosition.left,
wp.rcNormalPosition.bottom-wp.rcNormalPosition.top,
SWP_NOZORDER);
// Fenster in Bildschirm ziehen...
SendMessage(ghMainWnd,DM_REPOSITION,0,0);
newsize.x=wp.rcNormalPosition.right-wp.rcNormalPosition.left;
newsize.y=wp.rcNormalPosition.bottom-wp.rcNormalPosition.top;
SendMessage(ghMainWnd,WM_SIZE,SIZE_SIZEBY,
MAKELONG(newsize.x-gMinTrackSize.x,newsize.y-gMinTrackSize.y));
}
if (GetPrivateProfileString(T("MainWnd"),T("Widths"),
T(""),buf,elemof(buf),FileName)
&& ScanInts(buf,Widths,5)==5) {
for (i=0; i<5; i++) ListView_SetColumnWidth(ghListWnd,i,Widths[i]);
}
GetPrivateProfileString(T("MainWnd"),T("LogFile"),
T(""),LogFileName,elemof(LogFileName),FileName);
if (GetPrivateProfileString(T("MainWnd"),T("LptBase"),
T(""),buf,elemof(buf),FileName)
&& StrToIntEx(buf,STIF_SUPPORT_HEX,&val)
&& val>0x100
&& val<=0xFFFF) gLptBase=(WORD)val;
gUseStbBsyAck=GetPrivateProfileInt(T("MainWnd"),T("UseStbBsyAck"),
1,FileName);
InitLptState();
gDoBeep=GetPrivateProfileInt(T("MainWnd"),T("DoBeep"),
1,FileName);
if (GetPrivateProfileString(T("MainWnd"),T("LptState"),
T(""),buf,elemof(buf),FileName)
&& StrToIntEx(buf,STIF_SUPPORT_HEX,&val)
&& (unsigned)val<0x800) {
SetCurRelState((BYTE)val);
SetCurTrigOutState((BYTE)(val>>8));
}
if (!AlternateFileName) {
GetPrivateProfileString(T("MainWnd"),T("FileName"),
T(""),gFileName,elemof(gFileName),FileName);
SetModified(GetPrivateProfileInt(T("MainWnd"),T("Modified"),0,FileName));
}
}else{
PITEM pItem;
InitLptState();
pItem=NewItem(NULL);
pItem->Flags=UHRZEIT;
pItem->Uhrzeit.hour=12;
pItem->RelState=0x5A;
ListInsertItem(0,pItem,0);
pItem=NewItem(NULL);
pItem->Flags=DAUER;
pItem->Dauer.sec=3;
pItem->RelState=0xFF;
ListInsertItem(1,pItem,0);
}
}
void RegisterFileTypes(void) { // verknüpft ".relay" mit diesem Programm
DWORD l;
TCHAR ExeName[MAX_PATH];
l=GetModuleFileName(0,ExeName,elemof(ExeName)-5);
lstrcpy(ExeName+l,T(" \"%1\"")); l+=5;
RegSetValue(HKEY_CLASSES_ROOT,T(".relay"),REG_SZ,T("Relais-Steuerung"),16*sizeof(TCHAR));
RegSetValue(HKEY_CLASSES_ROOT,T(".relay\\shell\\open\\command"),REG_SZ,ExeName,l*sizeof(TCHAR));
}
/************************
* Dialog-Unterstützung *
************************/
void EnableWindows(HWND FirstWnd, UINT NumWnd, BOOL state) {
for(;NumWnd;NumWnd--) {
EnableWindow(FirstWnd,state);
FirstWnd=GetNextSibling(FirstWnd);
}
}
void EnableDlgItems(PITEM pItem) {
// Uhrzeit- oder Dauer-Dialogelemente
BYTE type=pItem->Flags&TYPEMASK;
EnableWindows(GetDlgItem(ghMainWnd,154),8,type==UHRZEIT || type==DAUER);
// Trigger-bezogene Dialogelemente
EnableWindows(GetDlgItem(ghMainWnd,158),4,type==TRIGGER);
EnableItem(53,type==UHRZEIT);
EnableItem(72,pItem->Flags&BEGINLOOP?TRUE:FALSE);
}
void SetItemInt_TwoChar(UINT idEdit, BYTE n) {
TCHAR buf[4];
wsprintf(buf,T("%02u"),n);
SetDlgItemText(ghMainWnd,idEdit,buf); // generiert EN_CHANGE
KillTimer(ghMainWnd,idEdit); // Validation abbrechen
}
void FillEdits(PITEM pItem) {
gEditErrors=0; // Annahme: keine Fehler
if (pItem) {
EnableWindows(GetDlgItem(ghMainWnd,49),24,TRUE); // alles EIN (flackert!?)
CheckRadioButton(ghMainWnd,50,52,50+(pItem->Flags&TYPEMASK)-UHRZEIT);
CheckDlgButton(ghMainWnd,53,pItem->Flags&RELATIVZEIT?1:0);
SetItemInt_TwoChar(54,pItem->Uhrzeit.hour);
SetItemInt_TwoChar(55,pItem->Uhrzeit.min);
SetItemInt_TwoChar(56,pItem->Uhrzeit.sec);
SetItemInt_TwoChar(57,pItem->Uhrzeit.hsec);
SetDlgItemInt(ghMainWnd,58,pItem->Trigger.sourcebit,FALSE);
KillTimer(ghMainWnd,58); // Validation abbrechen
CheckRadioButton(ghMainWnd,60,61,60+pItem->Trigger.polarity);
CheckDlgButton(ghMainWnd,70,pItem->Flags&BEGINLOOP?1:0);
CheckDlgButton(ghMainWnd,71,pItem->Flags&ENDLOOP?1:0);
SetDlgItemInt(ghMainWnd,72,pItem->LoopCount,FALSE);
SetDlgItemText(ghMainWnd,73,pItem->ExecFileName?pItem->ExecFileName:T(""));
EnableDlgItems(pItem);
KillTimer(ghMainWnd,72);
KillTimer(ghMainWnd,73);
}else{
EnableWindows(GetDlgItem(ghMainWnd,49),24,FALSE); // alles GRAU
}
}
void UpdateStatusLine(void) {
SYSTEMTIME st;
int slen;
long timediff;
TCHAR buf[80],buf2[80];
// 1. Uhrzeit-Anzeige aktualisieren
slen=LoadString(ghInstance,15/*Uhrzeit:*/,buf,elemof(buf));
GetLocalTime(&st);
GetTimeFormat(LOCALE_USER_DEFAULT,TIME_FORCE24HOURFORMAT,&st,NULL,
buf+slen,elemof(buf)-slen); // anhängen lassen
SendMessage(ghStatusWnd,SB_GETTEXT,0,(LPARAM)buf2);
if (lstrcmp(buf,buf2)) // nur bei Änderung Text setzen
SendMessage(ghStatusWnd,SB_SETTEXT,0,(LPARAM)buf);
// 2. Laufzeit-Anzeige aktualisieren: momentane Laufzeit
if (gSched.ListLine>=0) {
slen=LoadString(ghInstance,32/*Laufzeit:*/,buf,elemof(buf));
timediff=NowToMs()-gSched.start_ms;
if (timediff<0) timediff+=MS_PER_DAY;
StrFromTimeInterval(buf+slen,elemof(buf)-slen,timediff,6);
}else{ // oder ermittelte Gesamtlaufzeit
LOOPINFO li;
slen=LoadString(ghInstance,16/*Gesamtlaufzeit:*/,buf,elemof(buf));
li.line=li.depth=li.info=0;
timediff=EstimateRunningTime(&li);
if (li.info&INFO_ErrorMask) {
slen+=LoadString(ghInstance,12/*Fehler: */,buf+slen,elemof(buf)-slen);
slen+=LoadString(ghInstance,18+((li.info&INFO_ErrorMask)>>INFO_ErrorShift)-1,
buf+slen,elemof(buf)-slen);
}else{
if (li.info&INFO_LongerThan) {
slen+=LoadString(ghInstance,14/*mehr als*/,buf+slen,elemof(buf)-slen);
}
slen+=StrFromTimeInterval(buf+slen,elemof(buf)-slen,timediff,6);
if (li.info&INFO_Infinite) {
slen+=LoadString(ghInstance,13/* (pro Runde)*/,buf+slen,elemof(buf)-slen);
}
}
}
SendMessage(ghStatusWnd,SB_GETTEXT,1,(LPARAM)buf2);
if (lstrcmp(buf,buf2)) // nur bei Änderung Text setzen
SendMessage(ghStatusWnd,SB_SETTEXT,1,(LPARAM)buf);
}
int LoadFilterString(HINSTANCE hInstance, UINT uID, LPTSTR lpBuffer, int nBufferMax) {
int ret;
ret=LoadString(hInstance,uID,lpBuffer,nBufferMax);
if (ret) do{
lpBuffer=StrChr(lpBuffer,'|');
if (lpBuffer) *lpBuffer++=0;
}while (lpBuffer);
return ret;
}
/******************
* Scroll-Ziffern *
******************/
#define EN_QUERYMIN 0x1001 // unbeantwortet: 0
#define EN_QUERYMAX 0x1002 // per WM_COMMAND
#define EN_QUERYPAGE 0x1003 // unbeantwortet (0): 10
#define EN_QUERYWIDTH 0x1004 // unbeantwortet: 0
static void HandleScroll(HWND Wnd, int dir) {
HWND Parent=GetParent(Wnd);
UINT id=GetWindowLong(Wnd,GWL_ID);
BOOL ok;
int j,k;
int i=GetDlgItemInt(Parent,id,&ok,TRUE);
if (!ok) {
MessageBeep(MB_ICONEXCLAMATION);
return; // keine Zahl
}
if (dir==-10 || dir==10) {
j=(int)SendMessage(Parent,WM_COMMAND,MAKELONG(id,EN_QUERYPAGE),(LPARAM)Wnd);
if (j) dir=dir/10*j;
}
j=(int)SendMessage(Parent,WM_COMMAND,MAKELONG(id,dir>0?EN_QUERYMAX:EN_QUERYMIN),
(LPARAM)Wnd);
k=i;
i+=dir;
if (dir>0){
if (i>j) i=j; // auf Maximum begrenzen
}else{
if (i<j) i=j; // auf Minimum begrenzen
}
if (i==k) {
MessageBeep(MB_ICONEXCLAMATION);
return; // Anschlag erreicht
}
j=(int)SendMessage(Parent,WM_COMMAND,MAKELONG(id,EN_QUERYWIDTH),(LPARAM)Wnd);
if (j) { // hier: nur 2 erlaubt (allgemein: schwierig)
TCHAR s[8];
wnsprintf1(s,elemof(s),T("%02u"),i);
SetWindowText(Wnd,s);
}else{
SetDlgItemInt(Parent,id,i,TRUE);
}
}
static WNDPROC DefEditProc;
static LRESULT CALLBACK UpDownEditProc(HWND Wnd,UINT Msg,WPARAM wParam,LPARAM lParam) {
switch (Msg) {
case WM_VSCROLL: switch (LOWORD(wParam)) {
case SB_LINEUP: HandleScroll(Wnd,1); break;
case SB_LINEDOWN: HandleScroll(Wnd,-1); break;
case SB_PAGEUP: HandleScroll(Wnd,10); break;
case SB_PAGEDOWN: HandleScroll(Wnd,-10); break;
}break;
case WM_KEYDOWN: switch (wParam) {
case VK_UP: HandleScroll(Wnd,1); return 0; // Taste verschlucken
case VK_DOWN: HandleScroll(Wnd,-1); return 0;
case VK_PRIOR: HandleScroll(Wnd,10); return 0;
case VK_NEXT: HandleScroll(Wnd,-10); return 0;
}break;
}
return CallWindowProc(DefEditProc,Wnd,Msg,wParam,lParam);
}
void SubclassForUpDown(HWND Wnd, int id) {
DefEditProc=SubclassWindow(GetDlgItem(Wnd,id),UpDownEditProc);
}
/*********
* Hilfe *
*********/
// ToolInfoText an textlose Fenster "ankleben", der Text kommt mittels
// GetWindowText aus der Ressource. Nur für's Hauptfenster!
static void AddToolToImageButton(UINT id) {
TOOLINFO ti;
TCHAR buf[64];
ti.cbSize=sizeof(ti);
ti.uFlags=TTF_IDISHWND|TTF_SUBCLASS;
ti.hwnd=ghMainWnd;
(HWND)ti.uId=GetDlgItem(ghMainWnd,id);
ti.lpszText=buf;
GetWindowText((HWND)ti.uId,buf,elemof(buf));
SendMessage(gToolTip,TTM_ADDTOOL,0,(LPARAM)&ti);
}
static void SendWmHelp(HWND Wnd, HWND Child, int MouseX, int MouseY) {
HELPINFO hi;
hi.cbSize=sizeof(hi); // restliches HELPINFO auffüllen
hi.iContextType=HELPINFO_WINDOW;
hi.iCtrlId=GetDlgCtrlID(Child);
hi.hItemHandle=Child;
hi.dwContextId=GetWindowContextHelpId(Child);
hi.MousePos.x=MouseX;
hi.MousePos.y=MouseY;
SendMessage(Wnd,WM_HELP,0,(LPARAM)&hi);
}
// Bei WM_CONTEXTMENU Menü mit "Direkthilfe" einblenden,
// bei dessen Anwahl WM_HELP generieren
bool HandleContextMenu(HWND Wnd, HWND Child, LPARAM lParam) {
HMENU m;
TCHAR s[64];
if (Child==Wnd) return false;
m=CreatePopupMenu();
LoadString(ghInstance,40/*Direkthilfe*/,s,elemof(s));
AppendMenu(m,0,101,s);
if ((DWORD)lParam==(DWORD)-1) { // per Tastatur
RECT r;
GetWindowRect(Child,&r);
lParam=MAKELONG(r.left,r.bottom);
}
if (TrackPopupMenu(m,TPM_LEFTALIGN|TPM_TOPALIGN|TPM_RIGHTBUTTON|TPM_RETURNCMD,
GET_X_LPARAM(lParam),GET_Y_LPARAM(lParam),0,Wnd,NULL)==101) {
SendWmHelp(Wnd,Child,GET_X_LPARAM(lParam),GET_Y_LPARAM(lParam));
}
DestroyMenu(m);
return true;
}
static void GetQuestionButtonRect(HWND Wnd, LPRECT r) {
NONCLIENTMETRICS ncm;
ncm.cbSize=sizeof(ncm);
SystemParametersInfo(SPI_GETNONCLIENTMETRICS,sizeof(ncm),&ncm,0);
GetWindowRect(Wnd,r); // hier: X-Ausdehnung ermitteln
r->left=r->right-r->left
-GetSystemMetrics(SM_CYFRAME)
-ncm.iCaptionHeight*4
+1;
r->right=r->left+ncm.iCaptionHeight-2;
r->top=GetSystemMetrics(SM_CXFRAME)+2;
r->bottom=r->top+ncm.iCaptionHeight-4;
}
static BOOL NcInQuestionRect(HWND Wnd, POINT p) {
RECT r;
GetWindowRect(Wnd,&r);
p.x-=r.left;
p.y-=r.top;
GetQuestionButtonRect(Wnd,&r);
return PtInRect(&r,p);
}
static BOOL InQuestionRect(HWND Wnd, POINT p) {
ClientToScreen(Wnd,&p);
return NcInQuestionRect(Wnd,p);
}
static void DrawQuestionButton(HWND Wnd, bool Pushed, bool Hot) {
HDC dc=GetWindowDC(Wnd);
RECT r;
HTHEME th=0;
HANDLE hLib;
GetQuestionButtonRect(Wnd,&r);
hLib=LoadLibraryA("uxtheme.dll");
if (hLib) {
th=(HTHEME(_stdcall*)(HWND,LPCWSTR))GetProcAddress(hLib,"OpenThemeData")(Wnd,L"WINDOW");
}
if (th) { // Teletubbie-Optik
HRESULT (_stdcall*pDrawThemeBackground)(HTHEME,HDC,int,int,const RECT*,const RECT*);
(FARPROC)pDrawThemeBackground=GetProcAddress(hLib,"DrawThemeBackground");
pDrawThemeBackground(th,dc,WP_HELPBUTTON,Pushed?HBS_PUSHED:Hot?HBS_HOT:HBS_NORMAL,&r,NULL);
(HRESULT(_stdcall*)(HTHEME))GetProcAddress(hLib,"CloseThemeData")(th);
}else{
DrawFrameControl(dc,&r,DFC_CAPTION,Pushed?DFCS_CAPTIONHELP|DFCS_PUSHED:DFCS_CAPTIONHELP);
}
if (hLib) FreeLibrary(hLib);
ReleaseDC(Wnd,dc);
}
static void PointHelpModalLoop(HWND Wnd) {
MSG Msg;
bool DoExit=false;
SetCursor(LoadCursor(0,IDC_HELP));
while (GetMessage(&Msg,0,0,0)) {
switch (Msg.message) {
case WM_KEYDOWN:
switch (Msg.wParam) {
case VK_TAB:
case VK_ESCAPE:
case VK_F1: DoExit=true; break;
}break;
case WM_LBUTTONDOWN: {
POINT p={GET_X_LPARAM(Msg.lParam),GET_Y_LPARAM(Msg.lParam)};
HWND Child=ChildWindowFromPointEx(Wnd,p,CWP_SKIPINVISIBLE|CWP_SKIPTRANSPARENT);
ClientToScreen(Wnd,&p);
SendWmHelp(Wnd,Child,p.x,p.y);
}nobreak;
case WM_MBUTTONDOWN:
case WM_RBUTTONDOWN:
case WM_SYSKEYDOWN: DoExit=true; break;
}
TranslateMessage(&Msg);
DispatchMessage(&Msg);
if (DoExit) break;
}
}
static void QuestionModalLoop(HWND Wnd) {
MSG Msg;
bool Pushed=true;
SetCapture(Wnd);
DrawQuestionButton(Wnd,true,false);
while (GetMessage(&Msg,0,0,0)) {
switch (Msg.message) {
case WM_MOUSEMOVE: {
POINT p={GET_X_LPARAM(Msg.lParam),GET_Y_LPARAM(Msg.lParam)};
bool InQuest=InQuestionRect(Wnd,p)!=0;
if (Pushed ^ InQuest) {
Pushed=InQuest;
DrawQuestionButton(Wnd,Pushed,false);
}
}break;
case WM_LBUTTONUP: {
if (Pushed) {
PointHelpModalLoop(Wnd); // nächste modale Schleife
DrawQuestionButton(Wnd,false,false);
}
ReleaseCapture();
}return; // modale Schleife verlassen
}
DispatchMessage(&Msg);
}
}
// Filtert diverse NC-Nachrichten zur Darstellung und Behandlung des Hilfe-Fragezeichen-Knopfes
// Nur für Dialoge! Hier: Nicht für normale Fenster geeignet.
// Die Dialogprozedur muss TRUE zurückgeben, wenn diese Routine TRUE liefert.
BOOL HandleDlgNcMessagesForHelp(HWND Wnd,UINT Msg,WPARAM wParam,LPARAM lParam) {
switch (Msg) {
case WM_NCPAINT:
case WM_NCACTIVATE: {
SetWindowLong(Wnd,DWL_MSGRESULT,(LONG)DefWindowProc(Wnd,Msg,wParam,lParam));
DrawQuestionButton(Wnd,false,false);
}return TRUE;
case WM_NCHITTEST: {
// 1. Verhindert das Verschieben des Fensters beim Knopf drücken
// 2. Liefert wParam==HTHELP bei WM_NCLBUTTONDOWN
POINT p={GET_X_LPARAM(lParam),GET_Y_LPARAM(lParam)};
if (NcInQuestionRect(Wnd,p))
return SetDlgMsgResult(Wnd,WM_NCHITTEST,HTHELP);
}break;
case WM_NCMOUSEMOVE: { // Hot-Tracking (nur Teletubbie-Optik)
volatile static bool Hot;
if (Hot!=(wParam==HTHELP)) {
Hot=!Hot;
DrawQuestionButton(Wnd,false,Hot);
}
}break;
case WM_NCLBUTTONDOWN:
case WM_NCLBUTTONDBLCLK: {
if (wParam==HTHELP) {QuestionModalLoop(Wnd); return TRUE;} // Ereignis schlucken!!
}break;
case 0xAE: // Als Ergebnis bleibt der Knopf beim inaktiven Fenster hervorgehoben
case 0xAF: /*MessageBeep(0);*/ return TRUE; // unbekannte XP-Nachrichten
// scheinen die Titelleiste aufzuhellen (Kontrast reduzieren)
}
return FALSE;
}
/***********************
* 2 Dialog-Prozeduren *
***********************/
BOOL CALLBACK SetupDlgProc(HWND Wnd, UINT Msg, WPARAM wParam, LPARAM lParam) {
switch (Msg) {
case WM_INITDIALOG: {
const static WORD DefLpt[3]={0x378,0x278,0x3BC};
HWND hCombo=GetDlgItem(Wnd,101);
TCHAR buf[16];
int i;
wsprintf(buf,T("%Xh"),gLptBase);
SetWindowText(hCombo,buf); // Eingabezeile belegen
for (i=0; i<3; i++) {
wsprintf(buf,T("%Xh (LPT%u)"),DefLpt[i],i+1);
ComboBox_AddString(hCombo,buf);
if (DefLpt[i]==gLptBase) ComboBox_SetCurSel(hCombo,i);
} // Auswahl einer Voreinstellung, überschreibt Eingabezeile
CheckDlgButton(Wnd,102,gUseStbBsyAck);
CheckDlgButton(Wnd,103,gDoBeep);
}return TRUE;
case WM_COMMAND: switch (LOWORD(wParam)) {
case IDOK: {
TCHAR buf[8];
int value;
buf[0]='0'; buf[1]='x';
GetDlgItemText(Wnd,101,buf+2,elemof(buf)-2);
if (!StrToIntEx(buf,STIF_SUPPORT_HEX,&value) || value<0x100) {
Wnd=GetDlgItem(Wnd,101);
SetFocus(Wnd);
ComboBox_SetEditSel(Wnd,0,-1); // bei Fehler
break;
}
gLptBase=(WORD)value;
gUseStbBsyAck=(bool)IsDlgButtonChecked(Wnd,102);
gDoBeep=(bool)IsDlgButtonChecked(Wnd,103);
}nobreak;
case IDCANCEL: {
EndDialog(Wnd,wParam);
}break;
}break;
case WM_HELP: {
LPHELPINFO hi=(LPHELPINFO)lParam;
DWORD ids[4];
ids[0]=hi->iCtrlId;
ids[1]=MAKELONG(hi->iCtrlId,1101); // High-Teil = Ressourcen-ID des Dialogs
ids[2]=ids[3]=0;
WinHelp(hi->hItemHandle,gHelpFileName,HELP_WM_HELP,(DWORD_PTR)ids);
}break;
case WM_CONTEXTMENU: if (HandleContextMenu(Wnd,(HWND)wParam,lParam)) return TRUE; break;
}
return FALSE;
}
BOOL CALLBACK MainDlgProc(HWND Wnd, UINT Msg, WPARAM wParam, LPARAM lParam) {
if (HandleDlgNcMessagesForHelp(Wnd,Msg,wParam,lParam)) return TRUE;
switch (Msg) {
case WM_INITDIALOG: {
const int StatusParts[]={100,450,-1};
const int ListViewWidths[]={150,80,8*ITEMHEIGHT+2,3*ITEMHEIGHT+2,200};
UINT id;
ghMainWnd=Wnd;
GetWindowText(Wnd,StdMBoxTitle,elemof(StdMBoxTitle));
ghListWnd=GetDlgItem(Wnd,120); // ListView
ghStatusWnd=GetDlgItem(Wnd,102); // Statuszeile
for (id=54; id<=58; id++) SubclassForUpDown(Wnd,id); // Auf/Ab-Steuerung dranhängen
SubclassForUpDown(Wnd,72);
gSched.ListLine=-1; // angehalten
gToolTip=CreateWindowEx(WS_EX_TOPMOST,TOOLTIPS_CLASS,NULL,
WS_POPUP|TTS_NOPREFIX|TTS_ALWAYSTIP|TTS_BALLOON,0,0,0,0,Wnd,0,ghInstance,NULL);
for (id=121; id<=124; id++) { // vier bebilderte Knöpfe
SendDlgItemMessage(Wnd,id,BM_SETIMAGE,IMAGE_BITMAP,(LPARAM)LoadImage(
ghInstance,(LPCTSTR)id,IMAGE_BITMAP,16,15,LR_LOADMAP3DCOLORS));
AddToolToImageButton(id);
}
MakeGdiObj();
gOldButtonProc=SubclassWindow(GetDlgItem(Wnd,80/*LED Data*/),LedButtonSubclassProc);
AddToolToImageButton(80);
SubclassWindow(GetDlgItem(Wnd,83/*LED Status*/),LedButtonSubclassProc);
AddToolToImageButton(83);
SubclassWindow(GetDlgItem(Wnd,84/*LED Control*/),LedButtonSubclassProc);
AddToolToImageButton(84);
gOldLvProc=SubclassWindow(ghListWnd,LvSubclassProc);
//die Fensterunterklasse für die Status-LEDs ist nur für das Abbiegen von WM_ERASEBKGND.
for (id=0; id<elemof(ListViewWidths); id++) {
ListCreateColumn(id,7+id/*Zeit oder Trigger*/,ListViewWidths[id]);
}
SendMessage(ghStatusWnd,SB_SETPARTS,elemof(StatusParts),(LPARAM)(LPINT)StatusParts);
SetTimer(Wnd,2,200,NULL); // zur Aktualisierung der Statuszeile und der Trigger-LEDs
RegisterFileTypes(); // verknüpft ".relay" mit diesem Programm
LoadDefaults(PathGetArgs((PTSTR)lParam)); // ruft InitLptState auf - nach(!) dem Einlesen der Portadresse
LogSetMenuCheck();
}return TRUE;
case WM_MEASUREITEM: switch (wParam) {
case 120: ((LPMEASUREITEMSTRUCT)lParam)->itemHeight=ITEMHEIGHT;
}break;
case WM_DRAWITEM: switch (wParam) {
case 80: LedButtonHandleDraw((LPDRAWITEMSTRUCT)lParam,GetCurRelState(),8,0); break;
case 83: LedButtonHandleDraw((LPDRAWITEMSTRUCT)lParam,GetCurTrigInState(),3,2); break;
case 84: LedButtonHandleDraw((LPDRAWITEMSTRUCT)lParam,GetCurTrigOutState(),3,4); break;
case 120: HandleDrawList((LPDRAWITEMSTRUCT)lParam); break;
}break;
case WM_NOTIFY: switch (wParam) {
case 120: switch (((LPNMHDR)lParam)->code) {
case NM_SETFOCUS: {
int i=ListGetSelected();
if (i<0 && ListView_GetItemCount(ghListWnd)) i=0;
if (i>=0) ListView_SetItemState(ghListWnd,i,LVIS_FOCUSED,LVIS_FOCUSED);
}break;
case NM_KILLFOCUS: {
int i=ListView_GetNextItem(ghListWnd,-1,LVNI_FOCUSED);
if (i>=0) ListView_SetItemState(ghListWnd,i,0,LVIS_FOCUSED);
}break;
case LVN_ITEMCHANGED: {
LPNMLISTVIEW pnlv=(LPNMLISTVIEW)lParam;
PITEM pItem;
if (!(pnlv->uNewState&LVIS_SELECTED)) {
_debug((T("unselektiert\n")));
break;
}
pItem=ListGetItem(pnlv->iItem);
if (pItem) gpSelectedItem=pItem;
if (!gpSelectedItem) _debug((T("Murks2\n")));
FillEdits(gpSelectedItem);
}break;
case NM_CLICK:
case NM_DBLCLK: { // LEDs schalten mit Maus
LPNMITEMACTIVATE ia=(LPNMITEMACTIVATE)lParam;
if (ia->iItem>=0 && ia->iSubItem>=2) {
RECT r;
int LedNummer;
ListView_GetSubItemRect(ghListWnd,ia->iItem,ia->iSubItem,LVIR_BOUNDS,&r);
LedNummer=(ia->ptAction.x-r.left)/(r.bottom-r.top);
if ((unsigned)LedNummer<=7) {
PITEM pItem=ListGetItem(ia->iItem);
LedNummer=1<<LedNummer; // Bitmaske daraus machen
switch (ia->iSubItem) {
case 2: pItem->RelState^=(BYTE)LedNummer; break;
case 3: pItem->TrigOut ^=(BYTE)LedNummer; break;
}
ListView_RedrawItems(ia->hdr.hwndFrom,ia->iItem,ia->iItem);
}
}
if (ia->iItem<0) { // freier Bereich: neues Item erzeugen!
int i;
PITEM pItem=NewItem(gpSelectedItem);
if (!pItem) break; // Notbremse
i=ListView_GetItemCount(ghListWnd); // hinter das letzte Element
ListInsertItem(i,pItem,LVIS_SELECTED); // "selected" erzeugen
}
}break;
case LVN_DELETEITEM: { // anhängige Item-Struktur löschen
LPNMLISTVIEW lv=(LPNMLISTVIEW)lParam;
PITEM pItem=(PITEM)lv->lParam;
// Wenn gleich selektierte Struktur, dann nicht löschen (Zeile wird nur verschoben)
if (pItem != gpSelectedItem) DeleteItem(pItem);
}break;
}
}break;
// Könnte sein, dass die Dialogprozedur das bereits tut...
case WM_SYSCOLORCHANGE: {
SendMessage(ghListWnd,Msg,wParam,lParam);
}break;
case WM_THEMECHANGED: {
UINT id;
for (id=121; id<=124; id++) { // vier bebilderte Knöpfe
DeleteBitmap(SendDlgItemMessage(Wnd,id,BM_SETIMAGE,IMAGE_BITMAP,(LPARAM)LoadImage(
ghInstance,(LPCTSTR)id,IMAGE_BITMAP,16,15,LR_LOADMAP3DCOLORS)));
}
}break;
case WM_DROPFILES: {
HDROP hDrop=(HDROP)wParam;
UINT nDrop=DragQueryFile(hDrop,(UINT)-1,NULL,0);
if (nDrop!=1) MBox(Wnd,MAKEINTRESOURCE(31),MB_OK|MB_ICONEXCLAMATION,nDrop);
DragQueryFile(hDrop,0,gFileName,elemof(gFileName));
DragFinish(hDrop);
LoadFile(gFileName);
}break;
case WM_COMMAND: switch (LOWORD(wParam)) {
//Menü:
case 1001: { // Datei laden
OPENFILENAME ofn;
TCHAR FilterBuf[256];
InitStruct(&ofn,sizeof(ofn));
ofn.hwndOwner=Wnd;
ofn.lpstrFilter=FilterBuf;
ofn.lpstrFile=gFileName;
ofn.nMaxFile=elemof(gFileName);
LoadFilterString(ghInstance,34,FilterBuf,elemof(FilterBuf));
ofn.Flags=OFN_FILEMUSTEXIST|OFN_HIDEREADONLY;
ofn.lpstrDefExt=T("relay");
if (GetOpenFileName(&ofn)) LoadFile(gFileName);
}break;
case 1002: { // Datei speichern
OPENFILENAME ofn;
TCHAR FilterBuf[256];
InitStruct(&ofn,sizeof(ofn));
ofn.hwndOwner=Wnd;
ofn.lpstrFilter=FilterBuf;
ofn.lpstrFile=gFileName;
ofn.nMaxFile=elemof(gFileName);
LoadFilterString(ghInstance,34,FilterBuf,elemof(FilterBuf));
ofn.Flags=OFN_PATHMUSTEXIST|OFN_HIDEREADONLY|OFN_OVERWRITEPROMPT;
ofn.lpstrDefExt=T("relay");
if (GetSaveFileName(&ofn) && !SaveFile(gFileName))
MBox(Wnd,MAKEINTRESOURCE(36),MB_OK);
}break;
case 1003: { // Log-Datei
OPENFILENAME ofn;
TCHAR FilterBuf[256];
InitStruct(&ofn,sizeof(ofn));
ofn.hwndOwner=Wnd;
ofn.lpstrFilter=FilterBuf;
ofn.lpstrFile=LogFileName;
ofn.nMaxFile=elemof(LogFileName);
LoadFilterString(ghInstance,35,FilterBuf,elemof(FilterBuf));
ofn.Flags=OFN_PATHMUSTEXIST|OFN_HIDEREADONLY;
ofn.lpstrDefExt=T("log");
if (GetSaveFileName(&ofn)) {
LogSetMenuCheck();
if (hLog!=INVALID_HANDLE_VALUE) LogOpen(); // altes Log schließen, neues öffnen
}
}break;
case 1004: { // keine Log-Datei
LogFileName[0]=0;
LogSetMenuCheck();
}break;
case 1005: { // Vorgabe speichern
SaveDefaults();
}break;
case 1009: { // Beenden
SendMessage(Wnd,WM_CLOSE,0,0);
}break;
case 1101: { // Einstellungen
DialogBox(ghInstance,MAKEINTRESOURCE(1101),Wnd,SetupDlgProc);
}break;
case 1901: { // Hilfe
WinHelp(Wnd,gHelpFileName,HELP_CONTENTS,0);
}break;
case 1902: { // Kontexthilfe
SetCapture(Wnd);
PointHelpModalLoop(Wnd);
ReleaseCapture();
}break;
case 1909: { // Über
MBox(Wnd,MAKEINTRESOURCE(26),MB_OK);
}break;
//Dialogelemente:
case 50: // Umschalten auf "Uhrzeit"
case 51: // Umschalten auf "Dauer"
case 52: { // Umschalten auf "Trigger"
if (!gpSelectedItem) break;
gpSelectedItem->Flags&=~TYPEMASK;
gpSelectedItem->Flags|=(BYTE)(UHRZEIT+wParam-50);
EnableDlgItems(gpSelectedItem);
SendMessage(Wnd,WM_TIMER,wParam==52?58:54,0);
ListInvalidateItem(SELECTED);
}break;
case 53: { // Auswahlfeld "relative Uhrzeit"
if (!gpSelectedItem) break;
if (IsDlgButtonChecked(Wnd,53)) gpSelectedItem->Flags|=RELATIVZEIT;
else gpSelectedItem->Flags&=~RELATIVZEIT;
ListInvalidateItem(SELECTED);
}break;
case 70:
case 71: {
if (!gpSelectedItem) break;
if (IsDlgButtonChecked(Wnd,70)) gpSelectedItem->Flags|=BEGINLOOP;
else gpSelectedItem->Flags&=~BEGINLOOP;
if (IsDlgButtonChecked(Wnd,71)) gpSelectedItem->Flags|=ENDLOOP;
else gpSelectedItem->Flags&=~ENDLOOP;
EnableDlgItems(gpSelectedItem);
ListInvalidateItem(SELECTED);
}break;
case 54:
case 55:
case 56:
case 57:
case 58:
case 72:
case 73: switch (GET_WM_COMMAND_CMD(wParam,lParam)) {
case EN_CHANGE: SetTimer(Wnd,LOWORD(wParam),200,NULL); break;
case EN_QUERYMIN: if (LOWORD(wParam)==58) return SetDlgMsgResult(Wnd,WM_COMMAND,1); break;
case EN_QUERYMAX: {
BYTE max;
switch (LOWORD(wParam)) {
case 54: // Stunden
case 72: max=255; break; // Schleifenzahl
case 55: // Minuten
case 56: max=59; break; // Sekunden
case 57: max=99; break; // Hundertstel
case 58: max=3; break; // Trigger-Eingang
}
return SetDlgMsgResult(Wnd,WM_COMMAND,max);
}break;
case EN_QUERYWIDTH: switch (LOWORD(wParam)) {
case 54:
case 55:
case 56:
case 57: return SetDlgMsgResult(Wnd,WM_COMMAND,2);
}break;
}break;
case 60: // wenn L
case 61: { // wenn H
if (!gpSelectedItem) break;
gpSelectedItem->Trigger.polarity=(BYTE)(wParam-60);
ListInvalidateItem(SELECTED);
}break;
case 74: { // Knopf "Dateiauswahl"
OPENFILENAME ofn;
TCHAR FilterBuf[256];
TCHAR FileName[MAX_PATH];
TCHAR Parameters[MAX_PATH];
InitStruct(&ofn,sizeof(ofn));
ofn.hwndOwner=Wnd;
ofn.lpstrFilter=FilterBuf;
ofn.lpstrFile=FileName;
ofn.nMaxFile=elemof(FileName);
ofn.Flags=OFN_FILEMUSTEXIST|OFN_HIDEREADONLY;
GetDlgItemText(Wnd,73,FileName,elemof(FileName));
lstrcpy(Parameters,PathGetArgs(FileName)); // Argumente retten
PathRemoveArgs(FileName);
PathUnquoteSpaces(FileName);
LoadFilterString(ghInstance,33,FilterBuf,sizeof(FilterBuf));
if (GetOpenFileName(&ofn)) {
#ifdef UNICODE
if (PathIsExe(FileName)) {
#else
WCHAR uName[MAX_PATH];
MultiByteToWideChar(CP_ACP,0,FileName,-1,uName,elemof(uName));
if (PathIsExe(uName)) {
#endif
PathQuoteSpaces(FileName);
if (Parameters[0]) {
StrCatBuff(FileName,T(" "),elemof(FileName));
StrCatBuff(FileName,Parameters,elemof(FileName));
}
}
SetDlgItemText(Wnd,73,FileName);
}
}break;
case 80: { // "Knopf" mit 8 LEDs, liefert BN_BIT0HIT..BN_BIT7HIT
unsigned NumLed=GET_WM_COMMAND_CMD(wParam,lParam)-BN_BIT0HIT;
// Der folgende Vergleich filtert vor allem BN_CLICKED aus!
if (NumLed<8) SetCurRelState(GetCurRelState()^(1<<NumLed));
else if (NumLed==BN_BITALL0-BN_BIT0HIT) SetCurRelState(0x00);
else if (NumLed==BN_BITALL1-BN_BIT0HIT) SetCurRelState(0xFF);
}break;
case 81: { // alle EIN
SetCurRelState(0xFF);
}break;
case 82: { // alle AUS
SetCurRelState(0);
}break;
case 84: { // "Knopf" mit 3 LEDs, liefert BN_BIT0HIT..BN_BIT2HIT
unsigned NumLed=GET_WM_COMMAND_CMD(wParam,lParam)-BN_BIT0HIT;
if (NumLed<3) SetCurTrigOutState(GetCurTrigOutState()^(1<<NumLed));
else if (NumLed==BN_BITALL0-BN_BIT0HIT) SetCurTrigOutState(0x00);
else if (NumLed==BN_BITALL1-BN_BIT0HIT) SetCurTrigOutState(0xFF);
}break;
case 91: { // Starten
LOOPINFO li;
li.line=li.depth=li.info=0;
EstimateRunningTime(&li);
if (li.info&INFO_ErrorMask) {
TCHAR s[256];
LoadString(ghInstance,18+((li.info&INFO_ErrorMask)>>INFO_ErrorShift)-1,s,elemof(s));
if (MBox(Wnd,MAKEINTRESOURCE(38),MB_YESNO|MB_DEFBUTTON2,s)!=IDYES) break;
}
SchedStart();
if (SchedCalcWait()) { // kann stoppen, wenn keine Zeile da!
ListInvalidateItem(gSched.ListLine);
SetTimer(Wnd,1,gSched.settimer_ms,NULL);
}
}break;
case 92: { // Anhalten
KillTimer(Wnd,1);
SchedStop(true);
}break;
case 93: { // Schritt
if (gSched.ListLine<0) SendMessage(Wnd,WM_COMMAND,91,0);
else SchedHandleNext(TRUE);
}break;
case 120: {
#ifdef DEBUG
Beep(400,100); // Seit wann liefert eine ListView ein WM_COMMAND?
#endif
}break;
case 121: { // Neue Zeile
int i;
PITEM pItem=NewItem(gpSelectedItem);
if (!pItem) break; // Notbremse
i=ListGetSelected();
if (i<0) i=0;
ListInsertItem(i,pItem,LVIS_SELECTED); // "selected" erzeugen
}break;
case 122: { // Zeile löschen
int i=ListGetSelected();
int k=ListView_GetItemCount(ghListWnd);
if (i<0) {
MessageBeep(MB_ICONHAND);
break; // nichts selektiert: nichts löschen
}
gpSelectedItem=NULL;
ListView_DeleteItem(ghListWnd,i); // LVN_DELETEITEM löscht den Speicher
k--; // Eine Zeile weniger
if (k) { // Noch mindestens eine Zeile vorhanden?
if (i==k) i--; // Letzte Zeile gelöscht: vorherige (sonst nächste) markieren
ListView_SetItemState(ghListWnd,i,LVIS_SELECTED,LVIS_SELECTED);
}else FillEdits(NULL); // alle Eingabefelder grau
}break;
case 123: { // Zeile runter
int i=ListGetSelected();
int k=ListView_GetItemCount(ghListWnd);
if (!gpSelectedItem || gpSelectedItem!=ListGetItem(i)) {
_debug((T("Murks! i=%d k=%d %p %p\n"),i,k,gpSelectedItem,ListGetItem(i)));
break;
}
if (i<0) {
MessageBeep(MB_ICONHAND);
break; // nichts selektiert: nichts schieben
}
if (i+1==k) break; // schon letztes Element, nicht schieben
ListView_DeleteItem(ghListWnd,i);
i++;
ListInsertItem(i,gpSelectedItem,LVIS_SELECTED);
}break;
case 124: { // Zeile hoch
int i=ListGetSelected();
int k=ListView_GetItemCount(ghListWnd);
if (i<0) {
MessageBeep(MB_ICONHAND);
break; // nichts selektiert: nichts schieben
}
if (i==0) break; // schon erstes Element, nicht schieben
ListView_DeleteItem(ghListWnd,i);
i--;
ListInsertItem(i,gpSelectedItem,LVIS_SELECTED);
}break;
}break;
case WM_TIMECHANGE: {
// Falls Programm läuft und gerade auf "Uhrzeit" wartet, Timer umstellen!
}break;
case WM_TIMER: switch (wParam) {
// Nicht schlecht wäre auch das "Überleben" eines Reboot-Vorgangs!
case 1:
SchedHandleNext(FALSE); break;
case 2: {
UpdateStatusLine();
GetCurTrigInState(); // rote LEDs bei Änderung aktualisieren
}break;
// Editfenster-Validierung und Übernahme in die Liste
case 54:
case 55:
case 56:
case 57:
case 58:
case 72: {
UINT u;
BOOL b;
UINT bits1,bits2;
KillTimer(Wnd,wParam); // nicht-zyklisch!
u=GetDlgItemInt(Wnd,wParam,&b,FALSE);
if (b) switch (wParam) { // begrenzen
case 54: if (u>=7*24)b=FALSE; break; // <1 Woche (<256)
case 55: // <60 Minuten
case 56: if (u>=60) b=FALSE; break; // <60 Sekunden
case 57: if (u>=100) b=FALSE; break; // <100 Hundertstel
case 58: if (u<1||u>3)b=FALSE; break; // max. Eingänge 1..3 am Parallelport
case 72: if (u>255) b=FALSE; break;
}
bits1=1<<(wParam-54); // Fehler-Bit ermitteln
bits2=gEditErrors;
if (b) {
PITEM pItem=ListGetItem(SELECTED);
gEditErrors&=~bits1;
if (!pItem) break; // sollte nicht passieren (Notbremse)
switch (wParam) {
case 54: pItem->Uhrzeit.hour=(BYTE)u; break;
case 55: pItem->Uhrzeit.min =(BYTE)u; break;
case 56: pItem->Uhrzeit.sec =(BYTE)u; break;
case 57: pItem->Uhrzeit.hsec=(BYTE)u; break;
case 58: pItem->Trigger.sourcebit=(BYTE)u; break;
case 72: pItem->LoopCount =(BYTE)u; break;
}
ListInvalidateItem(SELECTED);
}else gEditErrors|=bits1;
if (gEditErrors!=bits2) InvalidateItem(wParam,NULL,TRUE);
}break;
case 73: { // Ausführungs-Dateiname und -Parameter (immer "fehlerfrei")
PITEM pItem=ListGetItem(SELECTED);
UINT BufChr;
KillTimer(Wnd,wParam); // einmalig
if (!pItem) break; // Notbremse
if (pItem->ExecFileName) {
LocalFree(pItem->ExecFileName); // alten Text freigeben
pItem->ExecFileName=NULL;
}
BufChr=SendDlgItemMessage(Wnd,wParam,WM_GETTEXTLENGTH,0,0);
if (BufChr) { // bei leerem String NULL-Zeiger belassen
BufChr++; // für "\0"
pItem->ExecFileName=LocalAlloc(LPTR,BufChr*sizeof(TCHAR));
if (!pItem->ExecFileName) break; // Notbremse
GetDlgItemText(Wnd,wParam,pItem->ExecFileName,BufChr); // neuen Text laden
}
ListInvalidateItem(SELECTED);
}break;
}break;
case WM_CTLCOLOREDIT: {
int id=GetDlgCtrlID((HWND)lParam)-54;
if ((unsigned)id>=sizeof(gEditErrors)*8) break;
if (!(gEditErrors&(1<<id))) break;
SetBkMode((HDC)wParam,TRANSPARENT);
SetBkColor((HDC)wParam,RGB(255,128,128));
}return (BOOL)gGdiObj.hbrError;
case WM_SIZE: {
static LPARAM PrevSize; // Client-Größe!
if (wParam==SIZE_MINIMIZED) break;
if (ghStatusWnd && wParam!=SIZE_SIZEBY) SendMessage(ghStatusWnd,Msg,wParam,lParam);
if (PrevSize || wParam==SIZE_SIZEBY) { // zweites (oder späteres) WM_SIZE: Items nachführen
RECT r;
HDWP dwp=BeginDeferWindowPos(9);
int i;
// Bits: 0=SchiebeX, 1=SchiebeY, 2=SizeX, 3=SizeY
static const BYTE MoveInfo[9]={1,1,1,1,0,0x0C,2,2,2};
HWND w=GetDlgItem(Wnd,121); // Erstes zu verschiebendes Fenster
BYTE info;
SIZE SizeChange;
if (wParam==SIZE_SIZEBY) PrevSize=0;
SizeChange.cx=GET_X_LPARAM(lParam)-GET_X_LPARAM(PrevSize);
SizeChange.cy=GET_Y_LPARAM(lParam)-GET_Y_LPARAM(PrevSize);
for (i=0; i<9; i++) {
info=MoveInfo[i];
GetWindowRect(w,&r);
r.right-=r.left; // = Breite
r.bottom-=r.top; // = Höhe
ScreenToClient(Wnd,(LPPOINT)&r); // .left und .top in Clientkoordinaten
if (info&1) r.left +=SizeChange.cx;
if (info&2) r.top +=SizeChange.cy;
if (info&4) r.right +=SizeChange.cx;
if (info&8) r.bottom+=SizeChange.cy;
dwp=DeferWindowPos(dwp,w,0,r.left,r.top,r.right,r.bottom,
SWP_DRAWFRAME|SWP_NOZORDER);
w=GetNextSibling(w);
}
EndDeferWindowPos(dwp);
} // gMinTrackSize wird bereits von LoadSettings() gesetzt
if (wParam!=SIZE_SIZEBY) PrevSize=lParam;
else {
RECT r;
GetClientRect(Wnd,&r);
PrevSize=MAKELONG(r.right-r.left,r.bottom-r.top);
}
}break;
case WM_GETMINMAXINFO: {
((LPMINMAXINFO)lParam)->ptMinTrackSize=gMinTrackSize;
}break;
case WM_MENUSELECT: {
#ifdef WIN32
WMMenuSelect(HIWORD(wParam)&MF_POPUP
?(UINT)GetSubMenu((HMENU)lParam,LOWORD(wParam))
:LOWORD(wParam),HIWORD(wParam));
#else
WMMenuSelect(wParam,LOWORD(lParam));
#endif
}break;
case WM_HELP: {
LPHELPINFO hi=(LPHELPINFO)lParam;
DWORD ids[4];
ids[0]=hi->iCtrlId;
ids[1]=MAKELONG(hi->iCtrlId,100); // High-Teil = Ressourcen-ID des Dialogs
ids[2]=ids[3]=0;
WinHelp(hi->hItemHandle,gHelpFileName,HELP_WM_HELP,(DWORD)ids);
// HELP_WM_HELP positioniert besser als HELP_CONTEXTPOPUP
}break;
case WM_CONTEXTMENU: if (HandleContextMenu(Wnd,(HWND)wParam,lParam)) return TRUE; break;
case WM_QUERYENDSESSION: {
if (gSched.ListLine>=0) switch (MBox(Wnd,
MAKEINTRESOURCE(25),MB_ICONQUESTION|MB_YESNO|MB_DEFBUTTON2)) {
case IDNO: /*SetWindowLong(Wnd,DWL_MSGRESULT,0);*/ return TRUE;
} // Windows nicht beenden!
}break;
case WM_ENDSESSION: {
SaveDefaults();
}break;
case WM_CLOSE: {
if (!SendMessage(Wnd,WM_QUERYENDSESSION,0,0)) return TRUE; // kein EndDialog
SendMessage(Wnd,WM_ENDSESSION,1,0);
WinHelp(Wnd,gHelpFileName,HELP_QUIT,0);
EndDialog(Wnd,0);
}break;
case WM_DESTROY: {
KillGdiObj();
}break;
}
return FALSE;
}
/******************************
* Hauptprogramm (lächerlich) *
******************************/
int _stdcall WinMainCRTStartup(void) {
WNDCLASS wc={
CS_HREDRAW|CS_VREDRAW,
DefDlgProc,
0, // ClsExtra
DLGWINDOWEXTRA,
ghInstance=GetModuleHandle(NULL), // hInstance
LoadIcon(ghInstance,MAKEINTRESOURCE(100)), // hIcon
LoadCursor(0,IDC_ARROW), // hCursor
(HBRUSH)(COLOR_WINDOW+1),
NULL,
T("Relais1")}; // Klassenname (in Übereinstimmung mit Dialog-Ressource 100)
RegisterClass(&wc);
InitCommonControls();
ExitProcess(DialogBoxParam(0,MAKEINTRESOURCE(100),0,MainDlgProc,(LPARAM)GetCommandLine()));
}
| Detected encoding: ASCII (7 bit) | 8
|