/* QuickCase:W KNB Version 1.00 */
#include "MONITOR.h"
#include <conio.h>
#include "ctl3d.h"   // Prototypes for dynalink ctl3dv2.dll (from Microsoft)

// Globale Variablen

// Commands sent by the program "Sim97", built later
static BYTE MonitorCMD_StartRecording[SZRECEIVEQUEUE];
static BYTE MonitorCMD_EndRecording[SZRECEIVEQUEUE];

// MEssage DIspatcher MOdification
static BOOL MEDIMOWatchingData = TRUE;       // Ist TRUE, wenn auf Daten von der seriellen Schnittstelle gelauscht wird
static BOOL MEDIMONAutistic = FALSE;         // Vom Input abgehängt, darf nur in WM_PAINT gesetzt werden !
static BOOL MEDIMONEnterAutistic = FALSE;    // Sagt Bescheid, daß in den Autistic-Zustand eingetreten werden soll

static char Feedback[384];                   // String für die Statuszeile

static DWORD MemAvail4NRecords;              // Anzahl Meßdatensätze, für die Speicher alloziert werden kann

static double ReceiveChannels[CHANNELMAX];   // Hier kommen die Daten zunächst an
// TransmissionEndDetected wird auch TRUE, wenn der Hauptspeicher zum Schreiben erschöpft ist
static BOOL TransmissionEndDetected = FALSE; // Wird TRUE, wenn ein spezieller Chunk gefunden wurde
// Wird TRUE gesetzt, wenn der programmgesteurte Initialaufruf erfogt
static BOOL AnschlueMsgProcInInitialTest = FALSE;

// Struktur Mon. Alle Elemente, die nur von "Mon-Privaten Methoden" modifiziert werden
// tragen den Kommentar "INTERN"; die aus CompiledKnowledge abgeleiteten "FROMCOMP"
static struct Monitor_tag {
  // ScreenView
  POINT statusLeftTop;               // INTERN Koordinaten des Statuspanerechtecks
  POINT statusLeftBottom;            // INTERN
  POINT statusRightBottom;           // INTERN
  POINT statusRightTop;              // INTERN
  POINT autistLeftTop;               // INTERN Koordinaten der Kontrollpane für eingeschr. Zustand
  POINT autistLeftBottom;            // INTERN
  POINT autistRightBottom;           // INTERN
  POINT autistRightTop;              // INTERN
  POINT auCountLeftTop;              // INTERN Koordinaten Messungen-Zähler-Pane
  POINT auCountLeftBottom;           // INTERN
  POINT auCountRightBottom;          // INTERN
  POINT auCountRightTop;             // INTERN
  POINT auValueLeftTop;              // INTERN Koordinaten Wert-Anzeige-Pane
  POINT auValueLeftBottom;           // INTERN
  POINT auValueRightBottom;          // INTERN
  POINT auValueRightTop;             // INTERN
  POINT auNiceLeftTop;               // INTERN Koordinaten Daten-Schönplot-Pane
  POINT auNiceLeftBottom;            // INTERN
  POINT auNiceRightBottom;           // INTERN
  POINT auNiceRightTop;              // INTERN
  POINT mainLeftTop;                 // INTERN Koordinaten der Hauptausgabepane
  POINT mainLeftBottom;              // INTERN
  POINT mainRightBottom;             // INTERN
  POINT mainRightTop;                // INTERN
  int nDataPanesPlotMarked;          // INTERN Anzahl benutzter Datenpanes
  int nDataPanesWriteMarked;         // INTERN Anzahl zum Schreiben markierter Kanäle
  RECT datapanes[CHANNELMAX];        // INTERN Koordinaten der bis zu 16 Datenpanes (-1 bedeutet nicht bereit zum Zeichnen)
  RECT namepanes[CHANNELMAX];        // INTERN Koordinaten der zugehörigen Beschriftungspanes
  // DataChannels
  BOOL DCChaMarked4Plot[CHANNELMAX]; // FROMCOMP Soll geplottet werden
  BOOL DCChaMarked4Write[CHANNELMAX];// FROMCOMP Soll geschrieben werden
  double DCData[CHANNELMAX];         // Die zuletzt hereingekommenen Werte (für bis zu 16 Kanäle)
  double DCDataMinima[CHANNELMAX];   // Minima
  double DCDataMaxima[CHANNELMAX];   // Maxima
  int DCDataCheckSum;
  int DCDataHour;
  int DCDataMinu;
  int DCDataSeco;
  int DCDataFram;
  char DCNames[CHANNELMAX][9];       // FROMCOMP Variablennamen
  char WriteFileName[256];           // Pfad u. Name deR Abspeicher-Datei
  long ScanCount;                    // INTERN Zähler für Messungen
} Mon;

static struct Communication_tag {
  char ComName[5];                   // Serielle Schnittstelle ("COM1" oder "COM2")
  int ID;                            // Rückgabewert von OpenComm
  DCB TheDCB;                        // Device Control Block
  WORD FAR *ComEvent;                // Eventmaskenzeiger (zum Polling)
  BOOL SetUpOK;                      // Serielle Schnittstelle geöffnet
  SERUSETYPE UsingComPort;           // NOTHING, USINGCOM1 oder USINGCOM2
} Com;

static struct KolterDAC4Info_tag {
  double ch0Minimum;
  double ch0Maximum;
  double ch1Minimum;
  double ch1Maximum;
  double ch2Minimum;
  double ch2Maximum;
  double ch3Minimum;
  double ch3Maximum;
} KolterDAC4Info;

static StripChartStruct SS[CHANNELMAX];

static HFONT WorkFont;      // Wird von WinMain erzeugt, durchgehend benutzt und auch von WinMain wieder zerstört
static HFONT SmallWorkFont; // Dito, nur ein etwas kleinerer Font

// These must be global or static (needed for commdlg - functionality)
static OPENFILENAME ofn;
static char szDirName[256];
static char szFile[256];
static char szFileTitle[256];
static char chReplace;
static UINT i;
static UINT cbString;
static char szTitle[256];
static char szFilter[]="Empfehlung (*.dat)\0*.dat\0Willkürlich (*.*)\0*.*\0ASCII-Extension (*.asc)\0*.asc\0Text-Extension (*.txt)\0*.txt\0";
// End of commdlg - globals

// The compiled knowledge vector (consists of knowledge nodes)
// Speicherbelegung nicht dynamisieren, QCWin verschluckt sich gern bei sowas
static KNOWLEDGENODE CompiledKnowledge[KNOWLEDGEMAX];

// Hier wird beim Aufzeichnen zwischengespeichert
static GLOBALHANDLE RCRD_TcHourHandle;              // Handle für StundenObjekt
static GLOBALHANDLE RCRD_TcMinuHandle;              // Handle für MinutenObjekt
static GLOBALHANDLE RCRD_TcSecoHandle;              // Handle für SekundenObjekt
static GLOBALHANDLE RCRD_TcFramHandle;              // Handle für FrameObjekt
static int huge *RCRD_TcHourRecords;                // Vektor Stunden                 2 Byte
static int huge *RCRD_TcMinuRecords;                // Vektor Minuten                 2 Byte
static int huge *RCRD_TcSecoRecords;                // Vektor Sekunden                2 Byte
static int huge *RCRD_TcFramRecords;                // Vektor Frames                  2 Byte
static GLOBALHANDLE RCRD_DataHandles[CHANNELMAX];   // Handles für die Kanäle
static float huge *RCRD_DataRecords[CHANNELMAX];    // Vektoren für die Kanäle       64 Byte
static GLOBALHANDLE RCRD_CheckSumHandle;            // Handle für ChecksummenObjekt
static int huge *RCRD_CheckSum;                     // Checksumme                     2 Byte
static DWORD RCRDItemsIn;                           // Zähler und Indexwert || alles:74 Bytes pro Chunk
// Ende Zwischenspeichermaterial


// Command table of knowledge compiler
static struct commandstable_tag {
  char *String;
  commandType InternalNumber;
} commandstable[] = {
  {"Template>!<",CMD_ONLYATEMPLATE},
  {"Data",CMD_DATA},
  {"SignedDifference",CMD_SIGNEDDIFFERENCE},
  {"AbsoluteDifference",CMD_ABSOLUTEDIFFERENCE},
  {"Fuck!",NONCMD_ENDOFCOMMANDTABLE}
};
// End Command table of knowledge compiler

// Globale Variablen des Knowledge-Compilers
static long CompilerProgramLength;
static long CompilerPos;
static int CompilerStandsOnLine;  // Besser gesagt die Nummer des zuletzt bearbeiteten Kommandos
static BOOL CompilerError = FALSE;
static char *CompilerPAlias;
static char CompilerErrDrop[256]; // Hier wir ein fehlerverursachender Programmteil abgelegt
static char CompilerNextUnproofedLine[256]; // Wird gemerkt, um sie suchen zu können, falls sie einen Fehler auslöst
static FILE *CompilerLog;

// Argumentpuffer für die bottom-level-Kommandobearbeiter des Knowledge-Compilers
static char KnoArg1[256];
static char KnoArg2[256];
static char KnoArg3[256];
static char KnoArg4[256];
static char KnoArg5[256];
static char KnoArg6[256];
static char KnoArg7[256];

int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow)
{
  /***********************************************************************/
  /* HANDLE hInstance;       handle for this instance                    */
  /* HANDLE hPrevInstance;   handle for possible previous instances      */
  /* LPSTR  lpszCmdLine;     long pointer to exec command line           */
  /* int    nCmdShow;        Show code for main window display           */
  /***********************************************************************/

  MSG msg;           /* MSG structure to store your messages        */
  int nRc;           /* return value from Register Classes          */

  lstrcpy(szAppName, "MONITOR");
  hInst = hInstance;

  if(!hPrevInstance)
  {
    /* register window classes if first instance of application         */
    if ((nRc = nCwRegisterClasses()) == -1)
    {
      /* registering one of the windows failed                         */
      LoadString(hInst,IDS_ERR_REGISTER_CLASS,szString,sizeof(szString));
      MessageBox(NULL,szString,NULL,MB_ICONEXCLAMATION);
      return nRc;
    }

    Ctl3dRegister(hInst);
    Ctl3dAutoSubclass(hInst);

    WorkFont=CreateFont(-11,                // Height
                        0x0,                // Width
                        0x0,                // Escapement
                        0x0,                // Orientation
                        400,                // Weight
                        0x0,                // Italic
                        0x0,                // Underline
                        0x0,                // Strikeout
                        0x0,                // CharSet
                        0x3,                // OutPrecision
                        0x2,                // ClipPrecision
                        0x1,                // Quality
                        0x12,               // PitchAndFamily
                        "Arial");           // FaceName

    SmallWorkFont=CreateFont(-9,            // Height
                        0x0,                // Width
                        0x0,                // Escapement
                        0x0,                // Orientation
                        400,                // Weight
                        0x0,                // Italic
                        0x0,                // Underline
                        0x0,                // Strikeout
                        0x0,                // CharSet
                        0x3,                // OutPrecision
                        0x2,                // ClipPrecision
                        0x1,                // Quality
                        0x12,               // PitchAndFamily
                        "Arial");           // FaceName
  }

  /* create application's Main window                                    */
  hWndMain = CreateWindow(szAppName,               /* Window class name           */
                          "Monitor",               /* Window's title              */
                          WS_CAPTION      |        /* Title and Min/Max           */
                          WS_SYSMENU      |        /* Add system menu box         */
                          WS_MINIMIZEBOX  |        /* Add minimize box            */
                          WS_MAXIMIZEBOX  |        /* Add maximize box            */
                          WS_THICKFRAME   |        /* thick sizeable frame        */
                          WS_MAXIMIZE     |        /* create maximized window     */
                          WS_CLIPCHILDREN |         /* don't draw in child windows areas */
                          WS_OVERLAPPED,
                          CW_USEDEFAULT, 0,        /* Use default X, Y            */
                          CW_USEDEFAULT, 0,        /* Use default X, Y            */
                          NULL,                    /* Parent window's handle      */
                          NULL,                    /* Default to Class Menu       */
                          hInst,                   /* Instance of window          */
                          NULL);                   /* Create struct for WM_CREATE */


  if(hWndMain == NULL)
  {
    LoadString(hInst,IDS_ERR_CREATE_WINDOW,szString,sizeof(szString));
    MessageBox(NULL,szString,NULL,MB_ICONEXCLAMATION);
    return IDS_ERR_CREATE_WINDOW;
  }

  // Empfangskommandotabelle für Kommunikation mit Programm "Monitor" aufbauen
  // Die Kommandos werden an den ersten 20 Bytes erkannt
  memset(MonitorCMD_StartRecording,'#',sizeof(MonitorCMD_StartRecording));
  lstrcpy(MonitorCMD_StartRecording,"StartAufzeichnung###");

  memset(MonitorCMD_EndRecording,'#',sizeof(MonitorCMD_EndRecording));
  lstrcpy(MonitorCMD_EndRecording,"EndAufzeichnung#####");

  // Wertebereiche für die DAC4-Karte von Kolter setzen
  KolterDAC4DefTransforms(0.,200.,       // Minimum und Maximum für Kanal 0 == Geschwindigkeit, kommt von Sim97-Kanal 3
                          0.,100.,       // Minimum und Maximum für Kanal 1 == "Last" kommt von Sim97-Kanal 4
                          0.,6000.,      // Minimum und Maximum für Kanal 2 == Drehzahl, kommt von Sim97-Kanal 14
                          0.,4095.);     // Minimum und Maximum für Kanal 3 == nicht belegt

  ShowWindow(hWndMain,SW_SHOWMAXIMIZED);    /* display main window      */

  // Wechsle auf Laufwerk C, nehme an, das es existiert
  _chdrive(3);
  // Nachsehen, ob Monitor´s Systemverzeichnisse existieren, gegebenenfalls erzeugen
  mkdir("\\monitor");
  mkdir("\\monitor\\system");
  mkdir("\\monitor\\data");
  // Die drei Zeilen bleiben ohne Wirkung, fall die Dateien schon existieren

//  Konventioneller Message Dispatcher
//  while(GetMessage(&msg,NULL,0,0))        /* Until WM_QUIT message    */
//  {
//    TranslateMessage(&msg);
//    DispatchMessage(&msg);
//  }

  // Message Dispatcher für Hintergrundtask
  do
  {
    if (MEDIMOWatchingData) // Ist zur Zeit immer TRUE
    {
      if (TransmissionEndDetected) // Keine Werte (mehr) anliegend
      {
        TransmissionEndDetected = FALSE;

        if (MEDIMONAutistic) // Ist während des Aufzeichnens eigetreten
          goto TREND; // Abbruch des Aufzeichnens "von Innen" ohne Nachrichtenschleife
      }

      while (PeekMessage(&msg,0,0,0,PM_REMOVE))
      {
        if (MEDIMONAutistic) // Proram ignores normal input, only ExecuteNextMonitorStep() works
        {
          if (IsEscapeCondition()) // Einzige Verbindung zum Benutzer
          {
TREND:      MEDIMONAutistic = FALSE;
            MakeDisplayNonAutistic();
            WriteoutRecordingSession();
          }
        }
        else
        {
          if (msg.message == WM_QUIT)
            goto QUITAPPL;

          TranslateMessage(&msg);
          DispatchMessage(&msg);
        }
      }

      ExecuteNextMonitorStep();
    }
    else
    {
      if (!GetMessage(&msg,0,0,0))
        goto QUITAPPL;

      TranslateMessage(&msg);
      DispatchMessage(&msg);
    }
  } while (TRUE);

QUITAPPL:

  /* Do clean up before exiting from the application                     */
  DeleteObject(WorkFont);
  DeleteObject(SmallWorkFont);
  CwUnRegisterClasses();
  Ctl3dUnregister(hInst);
  return msg.wParam;
} /*  End of WinMain                                                    */

/************************************************************************/
/*                                                                      */
/* Main Window Procedure                                                */
/*                                                                      */
/* This procedure provides service routines for the Windows events      */
/* (messages) that Windows sends to the window, as well as the user     */
/* initiated events (messages) that are generated when the user selects */
/* the action bar and pulldown menu controls or the corresponding       */
/* keyboard accelerators.                                               */
/*                                                                      */
/************************************************************************/

LONG FAR PASCAL WndProc(HWND hWnd, WORD Message, WORD wParam, LONG lParam)
{
  static HMENU       hMenu=0;            /* handle for the menu                 */
  static HBITMAP     hBitmap=0;          /* handle for bitmaps                  */
  static HDC         hDC;                /* handle for the display device       */
  static PAINTSTRUCT ps;                 /* holds PAINT information             */
  static int         nRc=0;              /* return code                         */

  switch (Message)
  {
    case WM_COMMAND:
         /* The Windows messages for action bar and pulldown menu items */
         /* are processed here.                                         */
         switch (wParam)
         {
           case IDM_E_NAMINGDATAFILE: // Schreibdatei benennen
                {
                  // Immer Standard-Datenfriedhof anbieten
                  if (chdir("c:\\monitor\\data") != 0) // Fehlgeschlagen
                  {
                    InternalErrorBox(IDS_ERR_CANNOTCHANGETOMYDIR);
                  }

                  if (!GetSaveFileName((OPENFILENAME FAR *) &ofn))
                  {
                    break; // "Abbrechen" beim FileDialog geklickt
                  }
                  else
                  {
                    lstrcpy(Mon.WriteFileName,szFile); // Memorize resultfilename & path in global variable
                    _fstrlwr(Mon.WriteFileName);
                    UpdateMonStruct(TRUE);
                    OutputMessage(Feedback);
                  }
                }
                break;

           case IDM_E_ECHTZEITBEOBACHTUNGUNDAUF:
                /* Place User Code to respond to the                   */
                /* Menu Item Named "&Echtzeitbeobachtung und Aufzeichnung" here. */
                {
                  FARPROC lpfnOBSERVEMsgProc;

                  lpfnOBSERVEMsgProc = MakeProcInstance((FARPROC)OBSERVEMsgProc, hInst);
                  nRc = DialogBox(hInst, (LPSTR)"observe", hWnd, lpfnOBSERVEMsgProc);
                  FreeProcInstance(lpfnOBSERVEMsgProc);
                }
                break;

           case IDM_E_WELTWISSEN:
                {
                  FARPROC lpfnKNOWLEDGMsgProc;

                  lpfnKNOWLEDGMsgProc = MakeProcInstance((FARPROC)KNOWLEDGMsgProc, hInst);
                  nRc = DialogBox(hInst, (LPSTR)"worldKnowledge", hWnd, lpfnKNOWLEDGMsgProc);
                  FreeProcInstance(lpfnKNOWLEDGMsgProc);

                  // Wenn Fehler beim Complilieren (geschieht beim verlassen), dann das ganze nochmal
                  // KNOWLEDGMsgProc erkennt den Unterschied an der globalen Var. CompilerError
                  if (CompilerError)
                  {
                    // Nochmal in den Editor zum Verbessern
                    PostMessage(hWnd,WM_COMMAND,IDM_E_WELTWISSEN,0L);
                  }
                  else
                  {
                    // Zum Variablenanwählen
                    PostMessage(hWnd,WM_COMMAND,IDM_E_ECHTZEITBEOBACHTUNGUNDAUF,0L);
                  }
                }
                break;

           case IDM_E_ANSCHLSSE:
                {
                  FARPROC lpfnANSCHLUEMsgProc;

                  lpfnANSCHLUEMsgProc = MakeProcInstance((FARPROC)ANSCHLUEMsgProc,hInst);

                  if (lParam == LPARAM_INITIALTEST)
                    AnschlueMsgProcInInitialTest = TRUE;

                  nRc = DialogBox(hInst,(LPSTR)"Anschluesse",hWnd,lpfnANSCHLUEMsgProc);
                  FreeProcInstance(lpfnANSCHLUEMsgProc);
                }
                break;

           case IDM_E_MANUSTART: // Auch bei maschinellem Start verwendet
                {
                  UpdateMonStruct(FALSE);  // Nur die angewählten Panes zusammenzählen

                  if (Mon.nDataPanesWriteMarked == 0)
                  {
                    MessageBox(hWnd,
                               "Es muß mindestens eine Variable zum Aufzeichnen markiert sein",
                               "Nicht erlaubt weil unsinnig",
                               MB_OK|MB_TASKMODAL);
                    SendMessage(hWnd,WM_COMMAND,IDM_E_ECHTZEITBEOBACHTUNGUNDAUF,0L);
                    break;
                  }
                  // Woraus folgt, daß auch eine zum Beobachten markiert ist

                  UpdateMonStruct(TRUE);
                  MEDIMONEnterAutistic = TRUE;        // Los ! (wird in WM_PAINT verarbeitet)
                  InvalidateRect(hWndMain,NULL,TRUE); // Löse WM_PAINT aus
                }
                break;

           case IDM_HLP_INHALT:
                /* Place User Code to respond to the                   */
                /* Menu Item Named "&Inhalt" here.                     */
                break;

           case IDM_HLP_KURZBERSICHT:
                /* Place User Code to respond to the                   */
                /* Menu Item Named "&Kurzübersicht" here.              */
                {
                  FARPROC lpfnSHORTINMsgProc;

                  lpfnSHORTINMsgProc = MakeProcInstance((FARPROC)SHORTINMsgProc, hInst);
                  nRc = DialogBox(hInst, (LPSTR)"ShortIn", hWnd, lpfnSHORTINMsgProc);
                  FreeProcInstance(lpfnSHORTINMsgProc);
                }
                break;

           case IDM_HLP_INFO:
                /* Place User Code to respond to the                   */
                /* Menu Item Named "I&nfo" here.                       */
                {
                  FARPROC lpfnINFOBOXMsgProc;

                  lpfnINFOBOXMsgProc = MakeProcInstance((FARPROC)INFOBOXMsgProc, hInst);
                  nRc = DialogBox(hInst, (LPSTR)"infobox", hWnd, lpfnINFOBOXMsgProc);
                  FreeProcInstance(lpfnINFOBOXMsgProc);
                }
                break;

           case IDM_EXIT:
                PostMessage(hWnd,WM_CLOSE,0,0L);
                break;

           case IDM_INTERNAL_MACHINETEST: // Wird nur von WM_CREATE gepostet
                {
                  // Test, ob Maschine und Windows geeignet
                  BOOL testPassed = InfoWhatMachineAndSystem();

                  if (testPassed)
                  {
                    // Zum nächsten Setup-Schritt
                    PostMessage(hWnd,WM_COMMAND,IDM_INTERNAL_QUESTPAGIN,0L);
                  }
                  else
                  {
                    // Wenn Programm auf dieser Kiste nicht lauffähig dann Ende.
                    PostMessage(hWnd,WM_COMMAND,IDM_EXIT,0L);
                  }
                }
                break;


           case IDM_INTERNAL_QUESTPAGIN:
                {
                  // Test, ob Virtuelle Speicherverwaltung ausgeschaltet ist
                  BOOL testPassed = InfoIsVirtMemOff();

                  if (testPassed)
                  {
                    // Zum nächsten Setup-Schritt
                    PostMessage(hWnd,WM_COMMAND,IDM_INTERNAL_TASKCOUNT,0L);
                  }
                  else
                  {
                    MessageBox(NULL,
                               "Schalten Sie die virtuelle Speicherverwaltung aus, während Sie mit Monitor arbeiten\
(Stellen Sie ein: Systemsteuerung - 386 erweitert - Virtueller Speicher - Typ keine)",
"Virtuelle Speicherverwaltung entdeckt",MB_OK|MB_ICONSTOP|MB_TASKMODAL);

                    // Wenn Programm auf dieser Kiste nicht lauffähig dann Ende.
                    PostMessage(hWnd,WM_COMMAND,IDM_EXIT,0L);
                  }
                }
                break;

           case IDM_INTERNAL_TASKCOUNT:
                {
                  // Warnung bei unangenehm vielen Tasks
                  int numTasksRunning = GetNumTasks();

                  // Damit mir nicht andere Anwendungen den Speicher zumüllen oder mir
                  // sonst irgendwie dazwischenfunken:
                  // Wegen den Soundgeschichten vom Lexmark-Drucker kommt man auf den Kisten,
                  // auf denen ich arbeite nicht auf eine Basisanzahl von 2, sondern von 3
                  if (numTasksRunning > 3) // Programmanager und Monitor + Lautsprecher-Treiber
                  {
                    char headLine[64];
                    sprintf(headLine,"Warnung: Es laufen %d Tasks",numTasksRunning);
                    MessageBox(hWnd,
                               "Beim Aufzeichnen soll das System möglichst wenig durch gleichzeitig \
geladene andere Anwendungen belastet sein. In der Taskliste sollten nur der Programmanager und \
Monitor auftauchen (Tasks die dort nicht auftauchen, können Sie auch nicht beenden).",
                               headLine,
                               MB_OK|MB_ICONEXCLAMATION|MB_TASKMODAL);

                    // Mehr als eine Warnung ist nicht drin !
                  }

                  // Initial die Schnittstellenauswahl (die Verfügbarkeitsüberprüfung enthält) anstoßen
                  SendMessage(hWnd,WM_COMMAND,IDM_E_ANSCHLSSE,LPARAM_INITIALTEST);
                  // Initial das Speicherbelegen anstoßen
                  SendMessage(hWnd,WM_COMMAND,IDM_INTERNAL_MEMSETUP,0L);
                  // Erzwinge das Initiale Öffnen der Weltwissen-DialogBox, damit
                  // der Weltwissen-Compiliervorgang angeworfen wird.
                  PostMessage(hWnd,WM_COMMAND,IDM_E_WELTWISSEN,0L);
                }
                break;

           case IDM_INTERNAL_MEMSETUP: // Wird initial und nach dem Dateischreiben der Daten gepostet
                if (!Care4RecordingMemory(C4RM_FREE))
                {
                  InternalErrorBox(IDS_ERR_MEMVECTORSFREE);
                }

                InfoAvailableMemory();
                UpdateMonStruct(TRUE);

                if (!Care4RecordingMemory(C4RM_ALLOC))
                {
                  InternalErrorBox(IDS_ERR_MEMVECTORSALLOC);
                }
                break;

           case IDM_INTERNAL_N_COM_QU:
                // Wie IDM_COM1_COM2_QUEST, aber Noisy COM QUestion, immer vom Programm selbst
                MessageBeep(MB_ICONHAND);
                PostMessage(hWnd,WM_COMMAND,IDM_COM1_COM2_QUEST,0L);
                break;

           case IDM_COM1_COM2_QUEST:
                {
                  int nCom1;
                  int nCom2;
                  char errMsg[256];

                  nCom1=OpenComm("COM1",SZRECEIVEQUEUE,SZTRANSMITQUEUE);

                  if (nCom1 >= 0) // geöffnet
                  {
                    CloseComm(nCom1);
                  }

                  nCom2=OpenComm("COM2",SZRECEIVEQUEUE,SZTRANSMITQUEUE);

                  if (nCom2 >= 0) // geöffnet
                  {
                    CloseComm(nCom2);
                  }

                  if (nCom1 < 0)
                  {
                    if (nCom1 == IE_OPEN && Com.UsingComPort == USINGCOM1) // Von uns selbst benutzt
                    {
                      sprintf(errMsg,"COM1: wird von Monitor benutzt; ");
                    }
                    else
                    {
                      LoadString(hInst,GetErrIDS4OpenComm(nCom1),szString,sizeof(szString));
                      sprintf(errMsg,"COM1: %s; ",szString);
                    }
                  }
                  else
                  {
                    sprintf(errMsg,"COM1: frei; ");
                  }

                  if (nCom2 < 0)
                  {
                    if (nCom2 == IE_OPEN && Com.UsingComPort == USINGCOM2) // Von uns selbst benutzt
                    {
                      strcat(errMsg,"COM2: wird von Monitor benutzt");
                    }
                    else
                    {
                      LoadString(hInst,GetErrIDS4OpenComm(nCom2),szString,sizeof(szString));
                      strcat(errMsg,"COM2: ");
                      strcat(errMsg,szString);
                    }
                  }
                  else
                  {
                    strcat(errMsg,"COM2: frei");
                  }

                  MessageBox(NULL,errMsg,"Diagnose Anschlüsse",MB_OK|MB_ICONINFORMATION|MB_TASKMODAL);
                }
                break;

           default:
                return DefWindowProc(hWnd, Message, wParam, lParam);
         }
         break;        /* End of WM_COMMAND                             */

    case WM_CREATE:
         {
           int i;

           InitPaneNames();
           lstrcpy(Mon.WriteFileName,"NONAME");
           lstrcpy(Com.ComName,"COM1");
           Com.SetUpOK = FALSE;


           // Some Common Dialog initializations
           ofn.lStructSize=sizeof(OPENFILENAME);
           ofn.hwndOwner=hWndMain;
           ofn.lpstrFilter=szFilter;
           ofn.nFilterIndex=1; // Simudateienfilter
           ofn.lpstrFile=szFile;
           ofn.nMaxFile=sizeof(szFile);
           ofn.lpstrFileTitle=szFileTitle;
           ofn.nMaxFileTitle=sizeof(szFileTitle);
           ofn.lpstrTitle=szTitle;  // Neu !!!!
           // ofn.lpstrInitialDir=szDirName; Returns you to initial directory
           ofn.lpstrInitialDir=NULL;
           ofn.Flags= OFN_PATHMUSTEXIST | OFN_HIDEREADONLY | OFN_FILEMUSTEXIST;
           ofn.nFileOffset=0;
	       ofn.nFileExtension=0;
	       ofn.lpstrDefExt="*";
	       ofn.lCustData=NULL;
	       ofn.lpfnHook=NULL;
	       ofn.lpTemplateName=NULL;
           // End of Common Dialog initializations

           // Stelle fest, ob Prozessor und Betriebssystem für "Monitor" überhaupt geeignet sind
           PostMessage(hWnd,WM_COMMAND,IDM_INTERNAL_MACHINETEST,0L);
           // IDM_INTERNAL_MACHINETEST postet die Nachricht für die nächste Setup-Stufe
         }
         break;       /*  End of WM_CREATE                              */

    case WM_MOVE:     /*  code for moving the window                    */
         break;

    case WM_SIZE:     /*  code for sizing client area                   */
         break;       /* End of WM_SIZE                                 */

    case WM_PAINT:    /* code for the window's client area              */
         {
           UpdateMonStruct(TRUE);
           /* Obtain a handle to the device context                       */
           /* BeginPaint will sends WM_ERASEBKGND if appropriate          */
           memset(&ps,0x00,sizeof(PAINTSTRUCT));
           hDC = BeginPaint(hWnd,&ps);
           /* Included in case the background is not a pure color         */
           SetBkMode(hDC,TRANSPARENT);

           // Rahmen zeichnen für N Variablen; Seiteneffekt: Update der Struktur Mon
           DrawBackground4N(hWnd,hDC,Mon.nDataPanesPlotMarked);
           OutputMessage(Feedback); // Nach jedem DrawBackground4N anzuwenden
           DrawPixelLineale(hDC,50,100);
           DrawPaneNames(hDC);
           DrawYAchsenBeschriftung(hDC);

           // Stripcharts initialisieren
           {
             int chan;
             int currentPane = 0;

             for(chan=0;chan < CHANNELMAX;chan++)
             {
               if (Mon.DCChaMarked4Plot[chan])
               {
                 StripChartManager(hDC,                               // A handle to a device context (always)
                                   currentPane,                       // Chart Identifier
                                   Mon.datapanes[currentPane].left,   // Wanted left (if SETUP)
                                   Mon.datapanes[currentPane].top,    // Wanted top  (if SETUP)
                                   Mon.datapanes[currentPane].right,  // Wanted right (if SETUP)
                                   Mon.datapanes[currentPane].bottom, // Wanted bottom (if SETUP)
                                   0.,                                // Data Item to plot (if PLOT)
                                   Mon.DCDataMinima[chan],            // Minimal displayable value (if SETUP)
                                   Mon.DCDataMaxima[chan],            // Maximal displayable value (if SETUP)
                                   SETUP);                            // SETUP or PLOT or SHUTDOWN

                 currentPane++;
               }
             }

             Mon.ScanCount = 0;
           }

           EndPaint(hWnd,&ps);

           if (MEDIMONEnterAutistic) // Signal, in die Aufzeichnung einzutreten
           {
             if (Care4RecordingMemory(C4RM_QUESTPREPARED)) // Ist der Speicher vorbereitet ?
             {
               MakeDisplayAutistic();
             }
             else
             {
               InternalErrorBox(IDS_ERR_MEMVECTORSALLOC);
             }

             MEDIMONEnterAutistic = FALSE; // Dieses Flag ist sofort zurückzusetzen
           }
         }
         break;

    case WM_SYSCOLORCHANGE:
         Ctl3dColorChange();
         break;

    case WM_CLOSE:  /* close the window                                 */
         /* Destroy child windows, modeless dialogs, then, this window  */
         StripChartManager((HDC)1,   // A handle to a device context (always), must not be valid in shutdown
                           0,        // Chart Identifier (0 to 15)
                           0,        // Wanted left (if SETUP)
                           0,        // Wanted top  (if SETUP)
                           0,        // Wanted right (if SETUP)
                           0,        // Wanted bottom (if SETUP)
                           0,        // Data Item to plot (if PLOT)
                           0.,       // Minimal displayable value (if SETUP)
                           0.,       // Maximal displayable value (if SETUP)
                           SHUTDOWN);// SETUP or PLOT or SHUTDOWN

         Care4RecordingMemory(C4RM_FREE);

         CloseComm(Com.ID);

         DestroyWindow(hWnd);

         if (hWnd == hWndMain)
           PostQuitMessage(0);  /* Quit the application                 */

         break;

    default:
         /* For any message for which you don't specifically provide a  */
         /* service routine, you should return the message to Windows   */
         /* for default message processing.                             */
         return DefWindowProc(hWnd, Message, wParam, lParam);
  }
  return 0L;
}     /* End of WndProc                                         */

BOOL FAR PASCAL OBSERVEMsgProc(HWND hWndDlg, WORD Message, WORD wParam, LONG lParam)
{
  int run;
  WORD iItemsIn;
  HWND listHandle;
  static int iAllMarkedObserve[CHANNELMAX+1]; // Indizes aller View-markierten Items (beachte: 1 größer als CHANNELMAX)
  static int iAllMarkedWrite[CHANNELMAX];     // Indizes aller Write-markierten Items
  static int nAreViewMarked; // Anzahl dieser Indizes

  switch(Message)
  {
    case WM_INITDIALOG:
         // Konstruktion der aktuellen View-Liste aus dem compilierten Wissen
         listHandle = GetDlgItem(hWndDlg,obsListObserve);
         iItemsIn = 0;

         for(run=0;run < KNOWLEDGEMAX;run++)
         {
           if (CompiledKnowledge[run].iAm != 0)
           {
             SendMessage(listHandle,LB_ADDSTRING,0,(LONG)(LPSTR)CompiledKnowledge[run].nameInKnowledge);

             if (CompiledKnowledge[run].plotMarked)
             {
               SendMessage(listHandle,LB_SETSEL,1,MAKELONG(iItemsIn,0));
             }

             iItemsIn++;
           }
         }

         // Konstruktion der aktuellen Write-Liste aus dem compilierten Wissen
         listHandle = GetDlgItem(hWndDlg,obsListWrite);
         iItemsIn = 0;

         for(run=0;run < KNOWLEDGEMAX;run++)
         {
           if (CompiledKnowledge[run].iAm != 0)
           {
             SendMessage(listHandle,LB_ADDSTRING,0,(LONG)(LPSTR)CompiledKnowledge[run].nameInKnowledge);

             if (CompiledKnowledge[run].writeMarked)
             {
               SendMessage(listHandle,LB_SETSEL,1,MAKELONG(iItemsIn,0));
             }

             iItemsIn++;
           }
         }

         cwCenter(hWndDlg, 0);
         break;

    case WM_CLOSE:
         PostMessage(hWndDlg,WM_COMMAND,IDCANCEL,0L);
         break;

    case WM_COMMAND:
         switch(wParam)
         {
           case obsListObserve: // View List box: Beschränke maximale Anzahl von Markierungen
                if (HIWORD(lParam) == LBN_SELCHANGE) // Selection changes
                {
                  int selCount;
                  int iUndoMark; // Index der Markierung, die rückgängig gemacht werden muß
                  static int iSIMax[CHANNELMAX]; // Idices of selected items in situation CHANNELMAX items are selected
                  static int iSIMaxPlusOne[CHANNELMAX+1]; // Idices of selected items in situation CHANNELMAX+1 items are selected

                  listHandle=LOWORD(lParam);
                  selCount=(int)SendMessage(listHandle,LB_GETSELCOUNT,0,0L);

                  // Jedesmal die Indizes aller markierten Items merken (für obsListWrite - case)
                  nAreViewMarked = selCount;
                  SendMessage(listHandle,LB_GETSELITEMS,selCount,(DWORD)(int FAR *)iAllMarkedObserve);

                  // Das gab einen seltsamen Fehler, deshalb workaround (der hat´s nicht gebracht)
                  // Verhindere, daß mehr Items angewählt werden können, als darstellbar sind
                  if (selCount == CHANNELMAX)
                  {
                    // Maximale Anzahl von Markierungen erreicht, deren Indizes merken
                    SendMessage(listHandle,LB_GETSELITEMS,CHANNELMAX,(DWORD)(int FAR *)iSIMax);
                  }

                  if (selCount == CHANNELMAX+1)
                  {
                    // Eine ist zu viel, alle Indizes merken
                    SendMessage(listHandle,LB_GETSELITEMS,CHANNELMAX+1,(DWORD)(int FAR *)iSIMaxPlusOne);
                    iUndoMark=IntInArg1NotInArg2(iSIMaxPlusOne,sizeof(iSIMaxPlusOne),iSIMax,sizeof(iSIMax));
                    SendMessage(listHandle,LB_SETSEL,0,MAKELONG(iUndoMark,0));
                  }
                }

                // Wenn ein Item sein viewMark verliert, muß auch sein "Geschwister" in
                // der Write-Liste sein writeMark verlieren, das geht am einfachsten mit
                // einer "synthetischen" LBN_SELCHANGE-Notification:

                SendMessage(hWndDlg,WM_COMMAND,obsListWrite,MAKELONG(GetDlgItem(hWndDlg,obsListWrite),LBN_SELCHANGE));
                break;

           case obsListWrite:
                // Write List box: Zum Schreiben darf nur markiert werden, was auch zum
                // Anschauen markiert ist (sonst würde die Zahl der aktiven Maße evtl. zu groß)
                if (HIWORD(lParam) == LBN_SELCHANGE) // Selection changes
                {
                  int nAreWriteMarked;             // Anzahl dieser Indizes

                  listHandle = LOWORD(lParam);
                  nAreWriteMarked=(int)SendMessage(listHandle,LB_GETSELCOUNT,0,0L);
                  SendMessage(listHandle,LB_GETSELITEMS,nAreWriteMarked,(DWORD)(int FAR *)iAllMarkedWrite);
                  // Stelle fest, ob ein Item writemark hat, das kein viewmark hat
                  // (es kann immer nur eins sein, außerdem sind die Listen parallel)
                  {
                    int i;
                    int j;
                    BOOL FoundInSuperset;

                    for(i=0;i < nAreWriteMarked;i++)
                    {
                      FoundInSuperset = FALSE;

                      for(j=0;j < nAreViewMarked;j++)
                      {
                        if (iAllMarkedWrite[i] == iAllMarkedObserve[j])
                        {
                          FoundInSuperset = TRUE;
                        }
                      }

                      if (!FoundInSuperset) // Hat writemark, aber kein viewmark
                      {
                        SendMessage(listHandle,LB_SETSEL,0,(DWORD)MAKELONG(iAllMarkedWrite[i],0));
                      }
                    }
                  }
                }
                break;

           case IDOK:
                // Für die View-Liste aktuelle plotMark-Einstellungen zurücklesen
                listHandle = GetDlgItem(hWndDlg,obsListObserve);
                iItemsIn = 0; // Ist hier der Index, bei dem gefragt wird

                for(run=0;run < KNOWLEDGEMAX;run++)
                {
                  if (CompiledKnowledge[run].iAm != 0) // Definiert, dann steht er auch in der Liste
                  {
                    if (SendMessage(listHandle,LB_GETSEL,iItemsIn,0L))
                    {
                      CompiledKnowledge[run].plotMarked = TRUE;
                    }
                    else
                    {
                      CompiledKnowledge[run].plotMarked = FALSE;
                    }

                    iItemsIn++;
                  }
                }

                // Für die Write-Liste aktuelle writeMark-Einstellungen zurücklesen
                listHandle = GetDlgItem(hWndDlg,obsListWrite);
                iItemsIn = 0; // Ist hier der Index, bei dem gefragt wird

                for(run=0;run < KNOWLEDGEMAX;run++)
                {
                  if (CompiledKnowledge[run].iAm != 0) // Definiert, dann steht er auch in der Liste
                  {
                    if (SendMessage(listHandle,LB_GETSEL,iItemsIn,0L))
                    {
                      CompiledKnowledge[run].writeMarked = TRUE;
                    }
                    else
                    {
                      CompiledKnowledge[run].writeMarked = FALSE;
                    }

                    iItemsIn++;
                  }
                }

                UpdateMonStruct(TRUE);

                EndDialog(hWndDlg, TRUE);
                InvalidateRect(hWndMain,NULL,TRUE);
                break;

           case IDCANCEL:
                // Ignore data values entered into the controls
                // and dismiss the dialog window returning FALSE
                EndDialog(hWndDlg, FALSE);
                break;
         }
         break; // End of WM_COMMAND

    default:
         return FALSE;
  }
  return TRUE;
} // End of OBSERVEMsgProc

/************************************************************************/
/*                                                                      */
/* Dialog Window Procedure                                              */
/*                                                                      */
/* This procedure is associated with the dialog box that is included in */
/* the function name of the procedure. It provides the service routines */
/* for the events (messages) that occur because the end user operates   */
/* one of the dialog box's buttons, entry fields, or controls.          */
/*                                                                      */
/************************************************************************/

BOOL FAR PASCAL SHORTINMsgProc(HWND hWndDlg, WORD Message, WORD wParam, LONG lParam)
{

 switch(Message)
   {
    case WM_INITDIALOG:
         cwCenter(hWndDlg, 0);
         /* initialize working variables                                */
         break; /* End of WM_INITDIALOG                                 */

    case WM_CLOSE:
         /* Closing the Dialog behaves the same as Cancel               */
         PostMessage(hWndDlg, WM_COMMAND, IDCANCEL, 0L);
         break; /* End of WM_CLOSE                                      */

    case WM_COMMAND:
         switch(wParam)
           {
            case IDOK:
                 EndDialog(hWndDlg, TRUE);
                 break;
            case IDCANCEL:
                 /* Ignore data values entered into the controls        */
                 /* and dismiss the dialog window returning FALSE       */
                 EndDialog(hWndDlg, FALSE);
                 break;
           }
         break;    /* End of WM_COMMAND                                 */

    default:
        return FALSE;
   }
 return TRUE;
} /* End of SHORTINMsgProc                                      */

/************************************************************************/
/*                                                                      */
/* Dialog Window Procedure                                              */
/*                                                                      */
/* This procedure is associated with the dialog box that is included in */
/* the function name of the procedure. It provides the service routines */
/* for the events (messages) that occur because the end user operates   */
/* one of the dialog box's buttons, entry fields, or controls.          */
/*                                                                      */
/************************************************************************/

BOOL FAR PASCAL INFOBOXMsgProc(HWND hWndDlg, WORD Message, WORD wParam, LONG lParam)
{

 switch(Message)
   {
    case WM_INITDIALOG:
         {
           char runtimeTxt[64];
           // Der Satz mit dem Copyrightvermerk wird nachbearbeitet, weil man mit QCWin
           // das Copyright-Zeichen (0xA9 im ANSI/ISO-Zeichensatz) nicht eintippen kann.
           sprintf(runtimeTxt,"Copyright \xa9 1997 Dipl.-Psych. Walter L. Piechulla");

           SetWindowText(GetDlgItem(hWndDlg,infStL2),runtimeTxt);
         }

         cwCenter(hWndDlg, 0);
         break;

    case WM_CLOSE:
         /* Closing the Dialog behaves the same as Cancel               */
         PostMessage(hWndDlg, WM_COMMAND, IDCANCEL, 0L);
         break; /* End of WM_CLOSE                                      */

    case WM_COMMAND:
         switch(wParam)
           {
            case IDOK:
                 EndDialog(hWndDlg, TRUE);
                 break;
            case IDCANCEL:
                 /* Ignore data values entered into the controls        */
                 /* and dismiss the dialog window returning FALSE       */
                 EndDialog(hWndDlg, FALSE);
                 break;
           }
         break;    /* End of WM_COMMAND                                 */

    default:
        return FALSE;
   }
 return TRUE;
} /* End of INFOBOXMsgProc                                      */

BOOL FAR PASCAL KNOWLEDGMsgProc(HWND hWndDlg, WORD Message, WORD wParam, LONG lParam)
{
  BOOL thereIsKnowledge;
  HWND editHandle;
  FILE *srcFile;
  int nKnowLines;
  char oneLine[256];
  int run;
  int curdrive;
  LPSTR editMirror = NULL;

  switch(Message)
  {
    case WM_INITDIALOG:
         cwCenter(hWndDlg,0);

         // Einlesen des Wissens-Quellcodes
         editHandle=GetDlgItem(hWndDlg,knoEdit);

         // Wechsle jetzt Arbeitslaufwerk und Verzeichnis
         if (_chdrive(3) == -1) // Annahme: Laufwerk C wird immer das Arbeitslaufwerk sein
         {
           InternalErrorBox(IDS_ERR_CANNOTCHANGETODRIVEC);
         }

         if (chdir("c:\\monitor\\system") != 0) // Fehlgeschlagen
         {
           InternalErrorBox(IDS_ERR_CANNOTCHANGETOMYDIR);
         }

         if ((srcFile=fopen("KNOWBASE.SRC","r")) != NULL) // Wissensbasis vorhanden
         {
           char *ok = fgets(oneLine,256,srcFile);

           if (ok != NULL) // Lesen klappt
           {
             if (!strcmp(oneLine,"Monitor Version 1.0 Knowledge Base Source\n"))
             {
               thereIsKnowledge=TRUE;
             }
             else
             {
               fclose(srcFile);
               thereIsKnowledge=FALSE;
             }
           }
           else
           {
             fclose(srcFile);
             thereIsKnowledge=FALSE;
           }
         }
         else
         {
           fclose(srcFile);
           thereIsKnowledge=FALSE;
         }

         if (thereIsKnowledge) // dann weiterlesen
         {
           fscanf(srcFile,"Lines = %d\n",&nKnowLines);

           // Den Textpuffer editMirror groß genug machen
           editMirror = (LPSTR)wmalloc(256*(nKnowLines+5)); // !!!Zur Zeit sind 4 Muster vorhanden
           strset(editMirror,0);

           // Musterkommandos einfügen
           LoadString(hInst,IDS_DIRECTPLOT,szString,sizeof(szString));
           TranslateCRLFToEdit(szString);
           lstrcat(editMirror,szString);

           LoadString(hInst,IDS_SIGNEDDIFFERENCE,szString,sizeof(szString));
           TranslateCRLFToEdit(szString);
           lstrcat(editMirror,szString);

           LoadString(hInst,IDS_ABSOLUTEDIFFERENCE,szString,sizeof(szString));
           TranslateCRLFToEdit(szString);
           lstrcat(editMirror,szString);

           // Leerzeile einfügen
           lstrcat(editMirror,"\r\n");

           for(run=0;run < nKnowLines;run++)
           {
             if (fgets(oneLine,256-2,srcFile) != NULL) // -2 wegen \n-Konvertierung (brauche 2 Bytes extra)
             {
               TranslateCRLFToEdit(oneLine);
               lstrcat(editMirror,oneLine);
             }
           }

           SendMessage(editHandle,WM_SETTEXT,0,(LONG)editMirror);
         }
         else
         {
           SendMessage(editHandle,WM_SETTEXT,0,(LONG)(LPSTR)"Datei KNOWBASE.SRC konnte nicht gelesen werden!");
         }
         fclose(srcFile);

         // Wenn es sich um einen Neuaufruf aufgrund eines Fehlers beim Compilieren handelt
         if (CompilerError)
         {
           int errSelBeg;
           int errSelEnd;
           char *msg = wmalloc(128);

           sprintf(msg,"Weltwissen - Fehler in Kommando %d",CompilerStandsOnLine);
           SetWindowText(hWndDlg,msg);
           wfree(msg);

           // Berechne den Teil des Textes, der die Fehlerzeile darstellt und markiere sie
           FindSelectionFromSubstring(&errSelBeg,&errSelEnd,editMirror,CompilerErrDrop);
           PostMessage(editHandle,EM_SETSEL,0,MAKELONG(errSelBeg,errSelEnd));
         }
         else
         {
           // Anfangs soll nichts markiert sein - das funktioniert nur gepostet:
           PostMessage(editHandle,EM_SETSEL,0,MAKELONG(0,0));
         }

         wfree(editMirror); // Zum Schluß den Einlesepuffer wieder freigeben
         break;

    case WM_CLOSE:
         /* Closing the Dialog behaves the same as Cancel               */
         PostMessage(hWndDlg,WM_COMMAND,IDCANCEL,0L);
         break;

    case WM_COMMAND:
         switch(wParam)
         {
           case knoEdit:
                if (HIWORD(lParam) == EN_ERRSPACE)
                {
                  LoadString(hInst,IDS_EN_ERRSPACE,szString,sizeof(szString));
                  MessageBox(NULL,szString,"Monitor: Editor-Speicherproblem",MB_OK|MB_TASKMODAL);
                }
                break;

           case ID_CLOSEIT: // Veranlasst die Interpretation
                {
                  FILE *wriFile;
                  LONG txtLen;

                  // Text aus dem Editfeld zurücklesen
                  editHandle=GetDlgItem(hWndDlg,knoEdit);
                  txtLen=(LONG)SendMessage(editHandle,WM_GETTEXTLENGTH,0,0L);
                  txtLen += 1; // Für '\0'-Zeichen
                  editMirror=(LPSTR)wmalloc(txtLen);

                  if (editMirror == NULL)
                  {
                    InternalErrorBox(IDS_ERR_KNOWLEDGEREADBACK);
                    EndDialog(hWndDlg,TRUE);
                    break;
                  }

                  SendMessage(editHandle,WM_GETTEXT,(WORD)txtLen,(LONG)editMirror); // Zurücklesen

                  // Blanks killen (sonst wächst die Datei immer weiter)
                  KCkillTrailingBlanks(editMirror);

                  // Backup der letzten Version anlegen
                  chmod("KNOWBASE.BAK",S_IWRITE);
                  chmod("KNOWBASE.SRC",S_IWRITE);

                  remove("KNOWBASE.BAK");
                  rename("KNOWBASE.SRC","KNOWBASE.BAK");

                  chmod("KNOWBASE.BAK",S_IREAD);

                  wriFile=fopen("KNOWBASE.SRC","w");

                  if (wriFile == NULL)
                  {
                    InternalErrorBox(IDS_ERR_KNOWLEDGESRCCREATE);
                    wfree(editMirror);
                    EndDialog(hWndDlg,TRUE);
                    break;
                  }

                  WriteKnowledgeSourceText(wriFile,editMirror);
                  fclose(wriFile); // Aktuellen Quelltext abspeichern
                  chmod("KNOWBASE.SRC",S_IREAD); // Wissensquelldatei mit Schreibschutz versehen

                  // CompilerError triggert kompletten Neuaufrug der DialogBox, wenn TRUE
                  if (KCCompileKnowledge(editMirror))
                  {
                    CompilerError = FALSE;
                  }
                  else
                  {
                    CompilerError = TRUE;
                  }

                  wfree(editMirror);
                }
                EndDialog(hWndDlg,TRUE);
                break;

           case IDCANCEL:
                /* Ignore data values entered into the controls        */
                /* and dismiss the dialog window returning FALSE       */
                EndDialog(hWndDlg,FALSE);
                break;
         }
         break; // End of WM_COMMAND

    default:
        return FALSE;
  }
  return TRUE;
} // End of KNOWLEDGMsgProc

/************************************************************************/
/*                                                                      */
/* Dialog Window Procedure                                              */
/*                                                                      */
/* This procedure is associated with the dialog box that is included in */
/* the function name of the procedure. It provides the service routines */
/* for the events (messages) that occur because the end user operates   */
/* one of the dialog box's buttons, entry fields, or controls.          */
/*                                                                      */
/************************************************************************/

BOOL FAR PASCAL QUERYSRCMsgProc(HWND hWndDlg, WORD Message, WORD wParam, LONG lParam)
{

 switch(Message)
   {
    case WM_INITDIALOG:
         cwCenter(hWndDlg, 0);
         /* initialize working variables                                */
         break; /* End of WM_INITDIALOG                                 */

    case WM_CLOSE:
         /* Closing the Dialog behaves the same as Cancel               */
         PostMessage(hWndDlg, WM_COMMAND, IDCANCEL, 0L);
         break; /* End of WM_CLOSE                                      */

    case WM_COMMAND:
         switch(wParam)
           {
            case IDOK:
                 EndDialog(hWndDlg, TRUE);
                 break;
            case IDCANCEL:
                 /* Ignore data values entered into the controls        */
                 /* and dismiss the dialog window returning FALSE       */
                 EndDialog(hWndDlg, FALSE);
                 break;
           }
         break;    /* End of WM_COMMAND                                 */

    default:
        return FALSE;
   }
 return TRUE;
} /* End of QUERYSRCMsgProc                                      */

/************************************************************************/
/*                                                                      */
/* Dialog Window Procedure                                              */
/*                                                                      */
/* This procedure is associated with the dialog box that is included in */
/* the function name of the procedure. It provides the service routines */
/* for the events (messages) that occur because the end user operates   */
/* one of the dialog box's buttons, entry fields, or controls.          */
/*                                                                      */
/************************************************************************/

BOOL FAR PASCAL ANSCHLUEMsgProc(HWND hWndDlg, WORD Message, WORD wParam, LONG lParam)
{
  BOOL fromCOM1toCOM2;
  BOOL fromCOM2toCOM1;
  int activationState;
  char newComName[5];
  int nCom;

  switch(Message)
  {
    case WM_INITDIALOG:
         cwCenter(hWndDlg, 0);
         // Richte dich nach Com.ComName

         if (!strcmp(Com.ComName,"COM1"))
           PostMessage(GetDlgItem(hWndDlg,ansRadioCom1),BM_SETCHECK,1,0L);

         if (!strcmp(Com.ComName,"COM2"))
           PostMessage(GetDlgItem(hWndDlg,ansRadioCom2),BM_SETCHECK,1,0L);

         if (AnschlueMsgProcInInitialTest)
         {
           SetWindowText(hWndDlg,"Anschlüsse-Startuptest");
         }
         break; /* End of WM_INITDIALOG                                 */

    case WM_CLOSE:
         /* Closing the Dialog behaves the same as Cancel               */
         PostMessage(hWndDlg, WM_COMMAND, IDCANCEL, 0L);
         break; /* End of WM_CLOSE                                      */

    case WM_COMMAND:
         switch(wParam)
         {
           case ansRadioCom1: // Radiobutton text: "COM1 - Schnittstelle"
                break;

           case ansRadioCom2: // Radiobutton text: "COM2 - Schnittstelle"
                break;

           case IDOK:
                fromCOM1toCOM2 = FALSE;
                fromCOM2toCOM1 = FALSE;
                // Bei Änderungen (d.h. erstes Einstellen oder Verstellen) Handlungsbedarf
                activationState=(int)SendMessage(GetDlgItem(hWndDlg,ansRadioCom1),BM_GETCHECK,0,0L);

                // Waren auf com2 oder noch gar keiner benutzt
                if (activationState && (Com.UsingComPort == USINGCOM2 || Com.UsingComPort == NOTHING))
                {
                  fromCOM2toCOM1 = TRUE;
                  lstrcpy(newComName,"COM1");
                }

                activationState=(int)SendMessage(GetDlgItem(hWndDlg,ansRadioCom2),BM_GETCHECK,0,0L);

                // Waren auf com1 oder noch gar keiner benutzt
                if (activationState && (Com.UsingComPort == USINGCOM1 || Com.UsingComPort == NOTHING))
                {
                  fromCOM1toCOM2 = TRUE;
                  lstrcpy(newComName,"COM2");
                }

                if (fromCOM1toCOM2 || fromCOM2toCOM1) // Handlungsbedarf gefunden
                {
                  nCom=OpenComm(newComName,SZRECEIVEQUEUE,SZTRANSMITQUEUE); // Versuchsweise öffnen

                  if (nCom >= 0) // Gewünschte COM kann geöffnet werden
                  {
                    AnschlueMsgProcInInitialTest = FALSE; // Test abgeschlossen
                    lstrcpy(Com.ComName,newComName); // Neu COM setzen

                    if (Com.SetUpOK) // Es ist schon eine geöffnet
                    {
                      CloseComm(Com.ID);   // Diese Schließen
                      Com.SetUpOK = FALSE; // Und Öffnen/Initialisieren bei ComCare4Protocol erzwingen
                    }
                  }
                  else // Gewünschte COM kann nicht geöffnet werden
                  {
                    CloseComm(Com.ID);          // Alte trotzdem Schließen
                    Com.SetUpOK = FALSE;        // Und Öffnen/Initialisieren bei ComCare4Protocol erzwingen
                    lstrcpy(Com.ComName,"NO!!"); // Aber ComCare4Protocol warnen, Com nicht freigegeben
                    // Diagnose Öffnen (mit akkust. Signal)
                    PostMessage(hWndMain,WM_COMMAND,IDM_INTERNAL_N_COM_QU,0L);
                  }

                  CloseComm(nCom); // Versuchsweises öffnen rückgängig
                }

                EndDialog(hWndDlg, TRUE);
                break;

           case IDCANCEL:
                 /* Ignore data values entered into the controls        */
                 /* and dismiss the dialog window returning FALSE       */
                EndDialog(hWndDlg, FALSE);
                break;
         }
         break;    /* End of WM_COMMAND                                 */

    default:
        return FALSE;
  }
  return TRUE;
} /* End of ANSCHLUEMsgProc                                      */


/************************************************************************/
/*                                                                      */
/* nCwRegisterClasses Function                                          */
/*                                                                      */
/* The following function registers all the classes of all the windows  */
/* associated with this application. The function returns an error code */
/* if unsuccessful, otherwise it returns 0.                             */
/*                                                                      */
/************************************************************************/

int nCwRegisterClasses(void)
{
 WNDCLASS   wndclass;    /* struct to define a window class             */
 memset(&wndclass, 0x00, sizeof(WNDCLASS));


 /* load WNDCLASS with window's characteristics                         */
 wndclass.style = CS_HREDRAW | CS_VREDRAW | CS_BYTEALIGNWINDOW;
 wndclass.lpfnWndProc = WndProc;
 /* Extra storage for Class and Window objects                          */
 wndclass.cbClsExtra = 0;
 wndclass.cbWndExtra = 0;
 wndclass.hInstance = hInst;
 wndclass.hIcon = LoadIcon(hInst,"MONICON");
 wndclass.hCursor = LoadCursor(NULL,IDC_ARROW);
 /* Create brush for erasing background                                 */
 wndclass.hbrBackground = CreateSolidBrush(RGB(192,192,192));
 wndclass.lpszMenuName = szAppName;   /* Menu Name is App Name */
 wndclass.lpszClassName = szAppName; /* Class Name is App Name */
 if(!RegisterClass(&wndclass))
   return -1;


 return(0);
} /* End of nCwRegisterClasses                                          */

/************************************************************************/
/*  cwCenter Function                                                   */
/*                                                                      */
/*  centers a window based on the client area of its parent             */
/*                                                                      */
/************************************************************************/

void cwCenter(hWnd, top)
HWND hWnd;
int top;
{
 POINT      pt;
 RECT       swp;
 RECT       rParent;
 int        iwidth;
 int        iheight;

 /* get the rectangles for the parent and the child                     */
 GetWindowRect(hWnd, &swp);
 GetClientRect(hWndMain, &rParent);

 /* calculate the height and width for MoveWindow                       */
 iwidth = swp.right - swp.left;
 iheight = swp.bottom - swp.top;

 /* find the center point and convert to screen coordinates             */
 pt.x = (rParent.right - rParent.left) / 2;
 pt.y = (rParent.bottom - rParent.top) / 2;
 ClientToScreen(hWndMain, &pt);

 /* calculate the new x, y starting point                               */
 pt.x = pt.x - (iwidth / 2);
 pt.y = pt.y - (iheight / 2);

 /* top will adjust the window position, up or down                     */
 if(top)
   pt.y = pt.y + top;

 /* move the window                                                     */
 MoveWindow(hWnd, pt.x, pt.y, iwidth, iheight, FALSE);
}

/************************************************************************/
/*  CwUnRegisterClasses Function                                        */
/*                                                                      */
/*  Deletes any refrences to windows resources created for this         */
/*  application, frees memory, deletes instance, handles and does       */
/*  clean up prior to exiting the window                                */
/*                                                                      */
/************************************************************************/

void CwUnRegisterClasses(void)
{
 WNDCLASS   wndclass;    /* struct to define a window class             */
 memset(&wndclass, 0x00, sizeof(WNDCLASS));

 GetClassInfo(hInst, szAppName, &wndclass);
 DeleteObject(wndclass.hbrBackground);
 UnregisterClass(szAppName, hInst);
}    /* End of CwUnRegisterClasses                                      */


// Berechnet und zeichnet den Rahmen zur Darstellung von N Variablen, RahmenKoordinaten werden global gemerkt,
// da andere Zeichenroutinen sie brauchen

void  FAR PASCAL DrawBackground4N(HWND hWnd,HDC aHDC,int NVariables)
{
  RECT clientRect;
  int smallVBand;
  int smallHBand;
  int tinyVBand;
  int tinyHBand;
  POINT impossible;
  // Diese 4 stellen nur Zwischenergebnisse dar:
  POINT LeftTop;
  POINT LeftBottom;
  POINT RightBottom;
  POINT RightTop;

  smallVBand  = GetSystemMetrics(SM_CYSIZE);  // Height of bitmaps contained in the title bar
  smallHBand  = GetSystemMetrics(SM_CXSIZE);  // Width of bitmaps contained in the title bar

  impossible.x = -1;
  impossible.y = -1;

  tinyVBand = GetSystemMetrics(SM_CYFRAME); // Height of window frame that can be sized
  tinyVBand *= 2;                           // Gibt guten Minimalabstand für zwei Panes

  tinyHBand = GetSystemMetrics(SM_CXFRAME); // Width of window frame that can be sized
  tinyHBand *= 2;                           // Gibt guten Minimalabstand für zwei Panes

  GetClientRect(hWnd,&clientRect);;

  // Die Statuspane berechnen
  LeftTop.x = clientRect.left;
  LeftTop.y = clientRect.top;

  LeftBottom.x = LeftTop.x;
  LeftBottom.y = LeftTop.y + smallVBand + tinyVBand;

  RightBottom.x = clientRect.right;
  RightBottom.y = LeftBottom.y;

  RightTop.x = RightBottom.x;
  RightTop.y = LeftTop.y;

  if (DrawOuterTextPane(aHDC,LeftTop,LeftBottom,RightBottom,RightTop)) // Wenn darstellbar
  {
    POINT txtLeftTop;
    POINT txtLeftBottom;
    POINT txtRightBottom;
    POINT txtRightTop;
    int reallyTinyVBand;

    txtLeftTop     = LeftTop;
    txtLeftBottom  = LeftBottom;
    txtRightBottom = RightBottom;
    txtRightTop    = RightTop;
    reallyTinyVBand  = tinyVBand/2;

    txtLeftTop.x += 0;
    txtLeftTop.y += 2;
    txtLeftBottom.x += 0;
    txtLeftBottom.y -= 2;
    txtRightBottom.x -= 0;
    txtRightBottom.y -= 2;
    txtRightTop.x -= 0;
    txtRightTop.y += 2;

    if (DrawTextPane(aHDC,txtLeftTop,txtLeftBottom,txtRightBottom,txtRightTop,FALSE)) // Wenn darstellbar
    {
      // Globales Vermerken der Koordinaten
      Mon.statusLeftTop     = txtLeftTop;
      Mon.statusLeftBottom  = txtLeftBottom;
      Mon.statusRightBottom = txtRightBottom;
      Mon.statusRightTop    = txtRightTop;
    }
    else
      Mon.statusLeftTop = Mon.statusLeftBottom = Mon.statusRightBottom = Mon.statusRightTop = impossible;
  }

  // Die Hauptausgabepane berechnen
  LeftTop.x += tinyVBand;
  LeftBottom.x += tinyVBand;
  RightTop.x -= tinyVBand;
  RightBottom.x -= tinyVBand;
  LeftTop.y = RightTop.y = LeftBottom.y + tinyVBand;
  LeftBottom.y = RightBottom.y = clientRect.bottom - tinyVBand;

  if (DrawOutStandPane(aHDC,LeftTop,LeftBottom,RightBottom,RightTop)) // Wenn darstellbar
  {
    // Globales Vermerken der Koordinaten
    Mon.mainLeftTop     = LeftTop;
    Mon.mainLeftBottom  = LeftBottom;
    Mon.mainRightBottom = RightBottom;
    Mon.mainRightTop    = RightTop;
  }
  else
    Mon.mainLeftTop = Mon.mainLeftBottom = Mon.mainRightBottom = Mon.mainRightTop = impossible;

  // Die 4 Punkte des (gedachten) inneren Rahmens berechnen
  LeftTop.x = clientRect.left + 5*smallHBand + tinyHBand;
  LeftTop.y = Mon.mainLeftTop.y + smallVBand;

  LeftBottom.x = LeftTop.x;
  LeftBottom.y = Mon.mainLeftBottom.y - 2*smallVBand;

  RightBottom.x = clientRect.right - (5*smallHBand + tinyHBand);
  RightBottom.y = LeftBottom.y;

  RightTop.x = RightBottom.x;
  RightTop.y = LeftTop.y;

  if (RightTop.x <= LeftTop.x || LeftBottom.y <= LeftTop.y || NVariables < 1) // Zu wenig Platz
  {
    Mon.nDataPanesPlotMarked = -1;
  }
  else
  {
    // Die Datenpanekoordinaten berechnen
    int nStege;
    int stegRaum;
    int onePanesHeight;
    POINT hlpLeftTop;
    POINT hlpLeftBottom;
    POINT hlpRightBottom;
    POINT hlpRightTop;
    int i;

    nStege = NVariables -1;         // Anzahl kleine horizontale Bänder zwischen den Datenpanes
    stegRaum = nStege * tinyVBand;  // Der Platz, den diese alle zusammen wenehmen
    onePanesHeight = (LeftBottom.y - LeftTop.y - stegRaum) / NVariables;

    for(i=0;i < NVariables;i++)
    {
      // Datenpanes zeichnen
      Mon.datapanes[i].left   = LeftTop.x;
      Mon.datapanes[i].right  = RightTop.x;
      Mon.datapanes[i].top    = LeftTop.y + tinyVBand + i*onePanesHeight + i*tinyVBand;
      Mon.datapanes[i].bottom = Mon.datapanes[i].top + onePanesHeight;

      hlpLeftTop.x     = Mon.datapanes[i].left;
      hlpLeftTop.y     = Mon.datapanes[i].top;
      hlpLeftBottom.x  = Mon.datapanes[i].left;
      hlpLeftBottom.y  = Mon.datapanes[i].bottom;
      hlpRightBottom.x = Mon.datapanes[i].right;
      hlpRightBottom.y = Mon.datapanes[i].bottom;
      hlpRightTop.x    = Mon.datapanes[i].right;
      hlpRightTop.y    = Mon.datapanes[i].top;

      if (!DrawInFallPane(aHDC,hlpLeftTop,hlpLeftBottom,hlpRightBottom,hlpRightTop,FALSE))
      {
        // Es ist zu wenig Platz zum Zeichnen da, was vermerkt werden muß:
        Mon.datapanes[i].left   = -1;
        Mon.datapanes[i].right  = -1;
        Mon.datapanes[i].top    = -1;
        Mon.datapanes[i].bottom = -1;
      }

      // Namenpanes zeichnen
      Mon.namepanes[i].left   = Mon.mainLeftTop.x + tinyHBand;
      Mon.namepanes[i].right  = Mon.datapanes[i].left - tinyHBand;
      Mon.namepanes[i].top    = Mon.datapanes[i].top;
      Mon.namepanes[i].bottom = Mon.datapanes[i].bottom;

      hlpLeftTop.x     = Mon.namepanes[i].left;
      hlpLeftTop.y     = Mon.namepanes[i].top;
      hlpLeftBottom.x  = Mon.namepanes[i].left;
      hlpLeftBottom.y  = Mon.namepanes[i].bottom;
      hlpRightBottom.x = Mon.namepanes[i].right;
      hlpRightBottom.y = Mon.namepanes[i].bottom;
      hlpRightTop.x    = Mon.namepanes[i].right;
      hlpRightTop.y    = Mon.namepanes[i].top;

      if (!DrawInFallPane(aHDC,hlpLeftTop,hlpLeftBottom,hlpRightBottom,hlpRightTop,FALSE))
      {
        // Es ist zu wenig Platz zum Zeichnen da, was vermerkt werden muß:
        Mon.datapanes[i].left   = -1;
        Mon.datapanes[i].right  = -1;
        Mon.datapanes[i].top    = -1;
        Mon.datapanes[i].bottom = -1;
      }

      // Korrekturen für später ausetzende Routinen, die den Rand nicht mitzählen dürfen
      Mon.datapanes[i].left   += 1;
      Mon.datapanes[i].right  -= 1;
      Mon.datapanes[i].top    += 1;
      Mon.datapanes[i].bottom -= 1;

      Mon.namepanes[i].left   += 1;
      Mon.namepanes[i].right  -= 1;
      Mon.namepanes[i].top    += 1;
      Mon.namepanes[i].bottom -= 1;
    }
  }
}

// Malen einer optisch hervorstehend wirkenden Pane. Sehr gut ausgetestet.
// Der Abstand zwischen zwei Mon sollte mindestens 2*SM_CXFRAME horizontal
// und 2*SM_CYFRAME vertikal betragen, sonst sieht das ganze nicht gut aus.
BOOL FAR PASCAL DrawOutStandPane(HDC hdc,POINT LeftTop,POINT LeftBottom,POINT RightBottom,POINT RightTop)
{
  POINT rectPoints[4];

  // Test auf vernünftiges Rechteck
  if (RightTop.x > LeftTop.x+1 && LeftBottom.y > LeftTop.y+1) // Nur wenn überhaupt genug Platz ist
  {
    HBRUSH frameBrush = CreateSolidBrush(RGB(192,192,192));
    HBRUSH backedupBrush = SelectObject(hdc,frameBrush);
    HPEN usePen;
    HPEN backedupPen;

    rectPoints[0] = LeftTop;
    rectPoints[1] = LeftBottom;
    rectPoints[2] = RightBottom;
    rectPoints[3] = RightTop;

    Polygon(hdc,(LPPOINT)&rectPoints,4);

    // 3D-Effekt hinzufügen:

    // 1. Übermalen der Polygon-Linien left & top mit Weiß
    usePen = CreatePen(PS_SOLID,1,RGB(255,255,255));
    backedupPen = SelectObject(hdc,usePen);

    MoveTo(hdc,LeftBottom.x,LeftBottom.y-1);
    LineTo(hdc,LeftTop.x,LeftTop.y);
    LineTo(hdc,RightTop.x,RightTop.y);

    SelectObject(hdc,backedupPen);
    DeleteObject(usePen);

    // 2. Übermalen der Polygon-Linien bottom & right mit Dunkelgrau
    usePen = CreatePen(PS_SOLID,1,RGB(128,128,128));
    backedupPen = SelectObject(hdc,usePen);

    MoveTo(hdc,LeftBottom.x,LeftBottom.y);
    LineTo(hdc,RightBottom.x,RightBottom.y);
    LineTo(hdc,RightTop.x,RightTop.y-1);


    // 3. Malen der Polygon-Linien left-1 & top-1 mit Dunkelgrau
    MoveTo(hdc,LeftBottom.x-1,LeftBottom.y);
    LineTo(hdc,LeftTop.x-1,LeftTop.y-1);
    LineTo(hdc,RightTop.x+1,RightTop.y-1);

    SelectObject(hdc,backedupPen);
    DeleteObject(usePen);

    // 4. Malen der Polygon-Linien bottom+1 & right+1 mit Schwarz
    usePen = CreatePen(PS_SOLID,1,RGB(0,0,0));
    backedupPen = SelectObject(hdc,usePen);

    MoveTo(hdc,LeftBottom.x-1,LeftBottom.y+1);
    LineTo(hdc,RightBottom.x+1,RightBottom.y+1);
    LineTo(hdc,RightTop.x+1,RightTop.y-2);

    SelectObject(hdc,backedupPen);
    DeleteObject(usePen);


    SelectObject(hdc,backedupBrush);
    DeleteObject(frameBrush);

    return TRUE; // Hat gezeichnet
  }
  else
  {
    return FALSE; // Rechteck zu klein zum Zeichnen oder falsch berechnet
  }
}

// Malen einer optisch zurückweichend wirkenden Pane. Sehr gut ausgetestet.
// Der Abstand zwischen zwei Panes sollte mindestens 2*SM_CXFRAME horizontal
// und 2*SM_CYFRAME vertikal betragen, sonst sieht das ganze nicht gut aus.
BOOL FAR PASCAL DrawInFallPane(HDC hdc,POINT LeftTop,POINT LeftBottom,POINT RightBottom,POINT RightTop,BOOL WhiteArea)
{
  POINT rectPoints[4];

  // Test auf vernünftiges Rechteck
  if (RightTop.x > LeftTop.x+1 && LeftBottom.y > LeftTop.y+1) // Nur wenn überhaupt genug Platz ist
  {
    HBRUSH frameBrush;
    HBRUSH backedupBrush;
    HPEN usePen;
    HPEN backedupPen;

    if (WhiteArea)
    {
      frameBrush = CreateSolidBrush(RGB(255,255,255));
      backedupBrush = SelectObject(hdc,frameBrush);
    }
    else
    {
      frameBrush = CreateSolidBrush(RGB(192,192,192));
      backedupBrush = SelectObject(hdc,frameBrush);
    }

    rectPoints[0] = LeftTop;
    rectPoints[1] = LeftBottom;
    rectPoints[2] = RightBottom;
    rectPoints[3] = RightTop;

    Polygon(hdc,(LPPOINT)&rectPoints,4);

    // 3D-Effekt hinzufügen:

    // 1. Übermalen der Polygon-Linien left & top mit Schwarz
    usePen = CreatePen(PS_SOLID,1,RGB(0,0,0));
    backedupPen = SelectObject(hdc,usePen);

    MoveTo(hdc,LeftBottom.x,LeftBottom.y-1);
    LineTo(hdc,LeftTop.x,LeftTop.y);
    LineTo(hdc,RightTop.x,RightTop.y);

    SelectObject(hdc,backedupPen);
    DeleteObject(usePen);

    // 2. Übermalen der Polygon-Linien bottom & right mit Dunkelgrau
    usePen = CreatePen(PS_SOLID,1,RGB(128,128,128));
    backedupPen = SelectObject(hdc,usePen);

    MoveTo(hdc,LeftBottom.x,LeftBottom.y);
    LineTo(hdc,RightBottom.x,RightBottom.y);
    LineTo(hdc,RightTop.x,RightTop.y-1);


    // 3. Malen der Polygon-Linien left-1 & top-1 mit Dunkelgrau
    MoveTo(hdc,LeftBottom.x-1,LeftBottom.y);
    LineTo(hdc,LeftTop.x-1,LeftTop.y-1);
    LineTo(hdc,RightTop.x+1,RightTop.y-1);

    SelectObject(hdc,backedupPen);
    DeleteObject(usePen);

    // 4. Malen der Polygon-Linien bottom+1 & right+1 mit Weiß
    usePen = CreatePen(PS_SOLID,1,RGB(255,255,255));
    backedupPen = SelectObject(hdc,usePen);

    MoveTo(hdc,LeftBottom.x-1,LeftBottom.y+1);
    LineTo(hdc,RightBottom.x+1,RightBottom.y+1);
    LineTo(hdc,RightTop.x+1,RightTop.y-2);

    SelectObject(hdc,backedupPen);
    DeleteObject(usePen);


    SelectObject(hdc,backedupBrush);
    DeleteObject(frameBrush);

    return TRUE; // Hat gezeichnet
  }
  else
  {
    return FALSE; // Rechteck zu klein zum Zeichnen oder falsch berechnet
  }
}

// Malen einer optisch etwas vorspringenden Pane. Sehr gut ausgetestet.
// Damit kann ein Trägerbalken vor Verwendung von DrawTextPane gezeichnet werden.
// Der Abstand zwischen zwei Panes sollte mindestens SM_CXFRAME horizontal
// und SM_CYFRAME vertikal betragen, sonst sieht das ganze nicht gut aus.
BOOL FAR PASCAL DrawOuterTextPane(HDC hdc,POINT LeftTop,POINT LeftBottom,POINT RightBottom,POINT RightTop)
{
  POINT rectPoints[4];

  // Test auf vernünftiges Rechteck
  if (RightTop.x > LeftTop.x+1 && LeftBottom.y > LeftTop.y+1) // Nur wenn überhaupt genug Platz ist
  {
    HBRUSH frameBrush = CreateSolidBrush(RGB(192,192,192));
    HBRUSH backedupBrush = SelectObject(hdc,frameBrush);
    HPEN usePen;
    HPEN backedupPen;

    rectPoints[0] = LeftTop;
    rectPoints[1] = LeftBottom;
    rectPoints[2] = RightBottom;
    rectPoints[3] = RightTop;

    Polygon(hdc,(LPPOINT)&rectPoints,4);

    // 3D-Effekt hinzufügen:

    // 1. Übermalen der Polygon-Linien left & top mit Weiß
    usePen = CreatePen(PS_SOLID,1,RGB(255,255,255));
    backedupPen = SelectObject(hdc,usePen);

    MoveTo(hdc,LeftBottom.x,LeftBottom.y-1);
    LineTo(hdc,LeftTop.x,LeftTop.y);
    LineTo(hdc,RightTop.x,RightTop.y);

    SelectObject(hdc,backedupPen);
    DeleteObject(usePen);

    // 2. Übermalen der Polygon-Linien bottom & right mit Schwarz
    usePen = CreatePen(PS_SOLID,1,RGB(0,0,0));
    backedupPen = SelectObject(hdc,usePen);

    MoveTo(hdc,LeftBottom.x,LeftBottom.y);
    LineTo(hdc,RightBottom.x,RightBottom.y);
    LineTo(hdc,RightTop.x,RightTop.y-1);

    SelectObject(hdc,backedupPen);
    DeleteObject(usePen);


    SelectObject(hdc,backedupBrush);
    DeleteObject(frameBrush);

    return TRUE; // Hat gezeichnet
  }
  else
  {
    return FALSE; // Rechteck zu klein zum Zeichnen oder falsch berechnet
  }
}


// Malen einer optisch etwas zurückweichend wirkenden Pane. Sehr gut ausgetestet.
// Der Abstand zwischen zwei Panes sollte mindestens SM_CXFRAME horizontal
// und SM_CYFRAME vertikal betragen, sonst sieht das ganze nicht gut aus.
BOOL FAR PASCAL DrawTextPane(HDC hdc,POINT LeftTop,POINT LeftBottom,POINT RightBottom,POINT RightTop,BOOL WhiteArea)
{
  POINT rectPoints[4];

  // Test auf vernünftiges Rechteck
  if (RightTop.x > LeftTop.x+1 && LeftBottom.y > LeftTop.y+1) // Nur wenn überhaupt genug Platz ist
  {
    HBRUSH frameBrush;
    HBRUSH backedupBrush;
    HPEN usePen;
    HPEN backedupPen;

    if (WhiteArea)
    {
      frameBrush = CreateSolidBrush(RGB(255,255,255));
      backedupBrush = SelectObject(hdc,frameBrush);
    }
    else
    {
      frameBrush = CreateSolidBrush(RGB(192,192,192));
      backedupBrush = SelectObject(hdc,frameBrush);
    }

    rectPoints[0] = LeftTop;
    rectPoints[1] = LeftBottom;
    rectPoints[2] = RightBottom;
    rectPoints[3] = RightTop;

    Polygon(hdc,(LPPOINT)&rectPoints,4);

    // 3D-Effekt hinzufügen:

    // 1. Übermalen der Polygon-Linien left & top mit Dunkelgrau
    usePen = CreatePen(PS_SOLID,1,RGB(128,128,128));
    backedupPen = SelectObject(hdc,usePen);

    MoveTo(hdc,LeftBottom.x,LeftBottom.y-1);
    LineTo(hdc,LeftTop.x,LeftTop.y);
    LineTo(hdc,RightTop.x,RightTop.y);

    SelectObject(hdc,backedupPen);
    DeleteObject(usePen);

    // 2. Übermalen der Polygon-Linien bottom & right mit Weiß
    usePen = CreatePen(PS_SOLID,1,RGB(255,255,255));
    backedupPen = SelectObject(hdc,usePen);

    MoveTo(hdc,LeftBottom.x,LeftBottom.y);
    LineTo(hdc,RightBottom.x,RightBottom.y);
    LineTo(hdc,RightTop.x,RightTop.y-1);

    SelectObject(hdc,backedupPen);
    DeleteObject(usePen);


    SelectObject(hdc,backedupBrush);
    DeleteObject(frameBrush);

    return TRUE; // Hat gezeichnet
  }
  else
  {
    return FALSE; // Rechteck zu klein zum Zeichnen oder falsch berechnet
  }
}

// Malt oben und unten eine Skala der Einheit 1 Pixel, damit das Lineal wie aufgeblendet
// erscheint, wird Schritt für Schritt gearbeitet, auch wenn der Code dadurch etwas länger wird
void FAR PASCAL DrawPixelLineale(HDC hdc,int resolutionSmall,int resolutionBig)
{
  int lastPaneIndex = Mon.nDataPanesPlotMarked - 1;
  int tinyLen = GetSystemMetrics(SM_CYFRAME);
  int smallYSpace = 2 * tinyLen;
  int begX;
  int begY;
  int endX;
  int endY;
  HFONT backupFont;

  if (Mon.nDataPanesPlotMarked == -1 || Mon.nDataPanesPlotMarked == 0 || Mon.datapanes[0].left == -1)
    return;

  // Oberes Lineal zeichnen
  begX = Mon.datapanes[0].left;
  begY = Mon.datapanes[0].top - smallYSpace;
  endX = Mon.datapanes[0].right;
  endY = begY;

  {
    int i;
    int Y = begY;
    int upY = Y - tinyLen;
    int linLaenge = endX - begX;


    for(i=0;i <= linLaenge;i++)
    {
      if (!(i % resolutionSmall))
      {
        MoveTo(hdc,begX+i,Y);
        LineTo(hdc,begX+i,upY);
      }

      if (!(i % resolutionBig))
      {
        MoveTo(hdc,begX+i,Y+tinyLen);
        LineTo(hdc,begX+i,upY);
      }
    }
  }

  // Unteres Lineal zeichnen
  begX = Mon.datapanes[lastPaneIndex].left;
  begY = Mon.datapanes[lastPaneIndex].bottom + smallYSpace;
  endX = Mon.datapanes[lastPaneIndex].right;
  endY = begY;

  {
    int i;
    int Y = begY;
    int downY = Y + tinyLen;
    int linLaenge = endX - begX;  // begX liegt dabei rechts, endX links

    for(i=0;i <= linLaenge;i++)
    {
      if (!(i % resolutionSmall))
      {
        MoveTo(hdc,begX+i,Y);
        LineTo(hdc,begX+i,downY);
      }

      if (!(i % resolutionBig))
      {
        MoveTo(hdc,begX+i,Y-tinyLen);
        LineTo(hdc,begX+i,downY);
      }
    }
  }

  // Beschriftung der Lineale
  backupFont = SelectObject(hdc,SmallWorkFont);

  // Oberes Lineal beschriften
  begX = Mon.datapanes[0].left;
  begY = Mon.datapanes[0].top - smallYSpace;
  endX = Mon.datapanes[0].right;
  endY = begY;

  {
    int i;
    int Y = begY;
    int upY = Y - tinyLen;
    int linLaenge = endX - begX;  // begX liegt dabei rechts, endX links
    WORD prevSet = SetTextAlign(hdc,TA_BOTTOM|TA_CENTER);
    char theNumber[64];

    for(i=0;i <= linLaenge;i++)
    {
      if (!(i % resolutionBig))
      {
        wsprintf(theNumber,"%d",i);
        TextOut(hdc,begX+i,upY,theNumber,lstrlen(theNumber));
      }
    }

    //SetTextAlign(hdc,TA_BOTTOM|TA_RIGHT);
    //TextOut(hdc,begX-5*GetSystemMetrics(SM_CXFRAME),upY,"Messungen",9);

    SetTextAlign(hdc,prevSet);
  }

  // Unteres Lineal beschriften
  begX = Mon.datapanes[lastPaneIndex].left;
  begY = Mon.datapanes[lastPaneIndex].bottom + smallYSpace;
  endX = Mon.datapanes[lastPaneIndex].right;
  endY = begY;

  {
    int i;
    int Y = begY;
    int downY = Y + tinyLen;
    int linLaenge = endX - begX;  // begX liegt dabei rechts, endX links
    WORD prevSet = SetTextAlign(hdc,TA_TOP|TA_CENTER);
    char theNumber[64];

    for(i=0;i <= linLaenge;i++)
    {
      if (!(i % resolutionBig))
      {
        wsprintf(theNumber,"%d",i);
        TextOut(hdc,begX+i,downY,theNumber,lstrlen(theNumber));
      }
    }

    //SetTextAlign(hdc,TA_TOP|TA_RIGHT);
    //TextOut(hdc,begX-5*GetSystemMetrics(SM_CXFRAME),downY,"Messungen",9);

    SetTextAlign(hdc,prevSet);
  }

  SelectObject(hdc,backupFont);
}

// Wird bei Creation des Fensters aufgerufen
void FAR PASCAL InitPaneNames(void)
{
  int i;
  char name[9];

  for(i=1;i <= CHANNELMAX;i++)
  {
    wsprintf(name,"Kanal%d",i);
    lstrcpy(Mon.DCNames[i-1],name);
  }
}

void FAR PASCAL DrawPaneNames(HDC hdc)
{
  int i;
  int currentPane = 0;
  HFONT backupFont;

  backupFont = SelectObject(hdc,WorkFont);

  for(i=0;i < CHANNELMAX;i++)
  {
    if (Mon.DCChaMarked4Plot[i])
    {
      DrawText(hdc,Mon.DCNames[i],-1,&(Mon.namepanes[currentPane]),DT_CENTER|DT_VCENTER|DT_SINGLELINE);
      currentPane++;
    }
  }

  SelectObject(hdc,backupFont);
}

void FAR PASCAL DrawYAchsenBeschriftung(HDC hdc)
{
  int i;
  int tinyLen = GetSystemMetrics(SM_CYFRAME);
  int currentPane = 0;
  POINT p;
  HFONT backupFont = SelectObject(hdc,SmallWorkFont);
  char txt[128];
  RECT thinkRect;   // Gedachtes Rechteck als Positionierungshilfe für DrawText
  int halfTRHeight; // Halbe Höhe des gedachten Rechtecks
  int widthTR;      // Breite des gadachten Rechtecks
  HPEN backupPen;
  HPEN greenPen = CreatePen(PS_SOLID,1,RGB(0,128,0));

  // Geignete Größe kann aus Mon erschlossen werden
  for(i=0;i < CHANNELMAX;i++)
  {
    if (Mon.DCChaMarked4Plot[i]) // Suche erste definierte, liegt dann im Slot 0
    {
      // Beschriftungen sind nicht breiter als Namenspanes
      widthTR = Mon.namepanes[0].right - Mon.namepanes[0].left;
      // Halbe Höhe muß nur groß genug sein, damit die Arbeitsschrift hineinpaßt
      halfTRHeight = (Mon.namepanes[0].bottom - Mon.namepanes[0].top) / 2;
      break;
    }
  }

  for(i=0;i < CHANNELMAX;i++)
  {
    if (Mon.DCChaMarked4Plot[i])
    {
      // Strich bei Minimum
      p.x = Mon.datapanes[currentPane].right;
      p.y = Mon.datapanes[currentPane].bottom;

      if (Mon.DCDataMinima[i] == 0.)
      {
        backupPen = SelectObject(hdc,greenPen);
      }

      MoveTo(hdc,p.x+tinyLen,p.y);
      LineTo(hdc,p.x+3*tinyLen,p.y);

      if (Mon.DCDataMinima[i] == 0.)
      {
        SelectObject(hdc,backupPen);
      }

      // Minimum-Beschriftung
      if (Mon.DCDataMinima[i] == 0.)
      {
        sprintf(txt,"0");
      }
      else
      {
        sprintf(txt,"%.2lf",Mon.DCDataMinima[i]);
      }

      thinkRect.left   = p.x + 4*tinyLen;
      thinkRect.top    = p.y - halfTRHeight;
      thinkRect.right  = thinkRect.left + widthTR;
      thinkRect.bottom = p.y + halfTRHeight;
      DrawText(hdc,txt,-1,&thinkRect,DT_LEFT|DT_VCENTER|DT_SINGLELINE);

      // Strich bei Maximum
      p.x = Mon.datapanes[currentPane].right;
      p.y = Mon.datapanes[currentPane].top;

      if (Mon.DCDataMaxima[i] == 0.)
      {
        backupPen = SelectObject(hdc,greenPen);
      }

      MoveTo(hdc,p.x+tinyLen,p.y);
      LineTo(hdc,p.x+3*tinyLen,p.y);

      if (Mon.DCDataMaxima[i] == 0.)
      {
        SelectObject(hdc,backupPen);
      }

      // Maximum-Beschriftung
      if (Mon.DCDataMaxima[i] == 0.)
      {
        sprintf(txt,"0");
      }
      else
      {
        sprintf(txt,"%.2lf",Mon.DCDataMaxima[i]);
      }

      thinkRect.left   = p.x + 4*tinyLen;
      thinkRect.top    = p.y - halfTRHeight;
      thinkRect.right  = thinkRect.left + widthTR;
      thinkRect.bottom = p.y + halfTRHeight;
      DrawText(hdc,txt,-1,&thinkRect,DT_LEFT|DT_VCENTER|DT_SINGLELINE);

      // Die Lage von Y == 0 nur mit einem Strich andeuten
      {
        if (Mon.DCDataMinima[i] < 0.)
        {
          int visualRange = Mon.datapanes[currentPane].bottom - Mon.datapanes[currentPane].top;
          double offFromMin = -Mon.DCDataMinima[i];
          double cartesian = (offFromMin / Range(Mon.DCDataMaxima[i],Mon.DCDataMinima[i])) * (double)visualRange;

          p.y = Mon.datapanes[currentPane].bottom - (int)cartesian;
          p.x = Mon.datapanes[currentPane].right + tinyLen;

          backupPen = SelectObject(hdc,greenPen);
          MoveTo(hdc,p.x,p.y);
          LineTo(hdc,p.x+tinyLen,p.y);
          SelectObject(hdc,backupPen);
        }
      }

      currentPane++;
    }
  }

  // Zustände vor Aufruf wiederherstellen
  SelectObject(hdc,backupFont);
  DeleteObject(greenPen);
}

// Daten abholen und Zeichnen, wird von modifiziertem Message-Dispatcher in
// WinMain als Hintergrundtask aufgerufen, wenn keine Nachrichten anliegen
void FAR PASCAL ExecuteNextMonitorStep(void)
{
  HDC hdc;
  int hit;                              // Kanal auf dem der Cursor steht
  static int cursorWasOnChaNo = -9999;  // Initialisiere mit garantiert nicht vorkommendem Wert

  if(!ComCare4Protocol()) // Nur TRUE, wenn eine Schnittstelle bereit ist
  {
    TransmissionEndDetected = TRUE; // Aufzeichnung abbrechen weil keine Werte da
    goto SKIPCIDTDC;                // Überspringe CopyInDatToDatChunk
  }

  if (CopyInDatToDatChunk()) // Es konnte ein neuer DatenChunk entgegengenommen und verarbeitet werden
  {
    int chan;
    int currentPane = 0;
    hdc = GetDC(hWndMain);

    if (TransmissionEndDetected) // Weil CopyInDatToDatChunk ein ENDRECORDING-Kommando entdeckt hat
      goto SKIPCIDTDC;

    // Die Werte in Mon.DCData[] sind ERGEBNISWERTE, die Arbeit, von den Receiver-Kanälen
    // die Ergebniswerte zu berechnen und die Ergebnisse richtig in Mon.DCData[] abzulegen,
    // wird von CopyInDatToDatChunk geleistet

    for(chan=0;chan < CHANNELMAX;chan++)
    {
      if (Mon.DCChaMarked4Plot[chan])
      {
        StripChartManager(hdc,                    // A handle to a device context (always)
                          currentPane,            // Chart Identifier (0 to 31)
                          0,                      // Wanted left (if SETUP), here unused
                          0,                      // Wanted top  (if SETUP), here unused
                          0,                      // Wanted right (if SETUP), here unused
                          0,                      // Wanted bottom (if SETUP), here unused
                          Mon.DCData[chan],       // Data Item to plot (if PLOT)
                          0.,                     // Minimal displayable value (if SETUP), here unused
                          0.,                     // Maximal displayable value (if SETUP), here unused
                          PLOT);                  // SETUP or PLOT or SHUTDOWN

         currentPane++;
      }
    }

    Mon.ScanCount++;  // Zähler für verarbeitete Datenchunks inkrementieren

    ReleaseDC(hWndMain,hdc);

    // Schreiben der Daten aus Mon.DCData[Kanal 0-15] in den Speicher. Synchron.

    if (MEDIMONAutistic) // Ja, es wird gerade aufgezeichnet
    {
      int currentColumn = 0;

      // Schreiben der Daten aus Mon.DCData[Kanal 0-15] in den Speicher. Synchron !

      for(chan=0;chan < CHANNELMAX;chan++)
      {
        if (Mon.DCChaMarked4Write[chan])
        {
          RCRD_DataRecords[currentColumn][RCRDItemsIn] = (float)Mon.DCData[chan];
          currentColumn++;
        }
      }

      RCRD_CheckSum[RCRDItemsIn] = Mon.DCDataCheckSum; // Wird gar nicht in Datei geschrieben !
      RCRD_TcHourRecords[RCRDItemsIn] = Mon.DCDataHour;
      RCRD_TcMinuRecords[RCRDItemsIn] = Mon.DCDataMinu;
      RCRD_TcSecoRecords[RCRDItemsIn] = Mon.DCDataSeco;
      RCRD_TcFramRecords[RCRDItemsIn] = Mon.DCDataFram;

      if (RCRDItemsIn+1 == MemAvail4NRecords) // Speicher erschöpft
      {
        TransmissionEndDetected = TRUE; //--> Abbruch des "autistic mode" durch Message Dispatcher
      }

      RCRDItemsIn++; // Zähler und Indexwert

      // Fertig mit Schreiben der Daten aus Mon.DCData[Kanal 0-15] in den Speicher

      // Die Spezialanzeigen versorgen

      OutputScanCount();
      hit = TxtPaneChaNoCursorIsOn(); // hits.x für die Hervorhebungen verwenden !

      if (hit == cursorWasOnChaNo) // Keine Änderung
      {
        if (hit == -1)
        {
          // Außerhalb sensitiver Bereiche -> Zustand "Timecode-Plot" beibehalten
          OutputTCAsLiteral(Mon.DCDataHour,Mon.DCDataMinu,Mon.DCDataSeco,Mon.DCDataFram);
        }
        else
        {
          // Ausgabe des Wertes auf die dafür vorgesehene Pane
          OutputDoubleAsLiteral(Mon.DCData[hit],TRUE);
          // Den nächsten Datenpunkt des Kanals plotten lassen
          NicePlotValue(Mon.DCData[hit],Mon.DCDataMinima[hit],Mon.DCDataMaxima[hit],TRUE); // Nur Weiterzeichnen
        }
      }
      else // Mauszeiger zeigt auf neue Pane
      {
        if (hit == -1)
        {
          // Außerhalb sensitiver Bereiche -> in Zustand "Timecode-Plot" eintreten
          KillNamingOfHeadPane();
          OutputDoubleAsLiteral(Mon.DCData[hit],FALSE); // Nur Fläche löschen
          OutputTCAsLiteral(Mon.DCDataHour,Mon.DCDataMinu,Mon.DCDataSeco,Mon.DCDataFram);
          NicePlotValue(Mon.DCData[hit],Mon.DCDataMinima[hit],Mon.DCDataMaxima[hit],FALSE);         // Nur Fläche löschen
        }
        else
        {
          // Ausgabe des Wertes auf die dafür vorgesehene Pane
          DoNamingOfHeadPane(hit);
          OutputDoubleAsLiteral(Mon.DCData[hit],TRUE);
          // Ersten Datenpunkt des Kanals plotten lassen
          NicePlotValue(Mon.DCData[hit],Mon.DCDataMinima[hit],Mon.DCDataMaxima[hit],FALSE); // Fläche löschen lassen
          NicePlotValue(Mon.DCData[hit],Mon.DCDataMinima[hit],Mon.DCDataMaxima[hit],TRUE);  // Weiterzeichnen
        }
      }

      cursorWasOnChaNo = hit; // Aktualisieren
    }
  }
SKIPCIDTDC: ;
}

// Stelle fest, ob neue Daten auf den Receiver-Kanälen anliegen. Wenn ja, berechne die gewünschten
// Zielwerte und schreibe sie für die Weiterverarbeitung (Anzeigen oder Schreiben) nach Mon.DCData[].
// Die Gesamtzahl der zu berechnenden Maße / durchzuführenden Durchreichungen ist höchstens CHANNELMAX,
// weil nur Mon.DCData[]-Elemente geschrieben werden können, die auch angezeigt werden.

BOOL FAR PASCAL CopyInDatToDatChunk(void)
{
  int i;
  int chan;
  int slotInMon;      // Mon.DCData[Kanal], auf den das Ergebnis zu schreiben ist
  double helpDouble1; // Hilfsvariable
  double helpDouble2; // Hilfsvariable
  double resultValue; // Ergebniswert, der auf Mon.DCData[Kanal] geschrieben wird
  static double now = 0.;

  int nRead;                            // Anzahl gelesener Zeichen
  static BYTE inBuffer[SZRECEIVEQUEUE]; // Puffer, in den eine Kopie des Eingangspuffers kommt
  COMSTAT status;                       // Ist halt eine COMSTAT
  int commErr;                          // Rückgabewert der GetCommError
  SIM97CMD sentinel;                    // Wird 0 oder ein Sim97-Kommando

  // Hier findet das Lesen aus dem Eingangspuffer statt

  //if (*Com.ComEvent & EV_RXCHAR) // Nur wenn in der Zwischenzeit irgendwas empfangen wurde
  //{ // >>>>>
  //
  //} Eventmaske momentan (noch) nicht benutzt

  commErr=0; // Clear
  commErr=GetCommError(Com.ID,&status);

  if (commErr)
  {
    ExplainCommError(commErr);
    return FALSE;
  }
  else
  {
    if (status.cbInQue == SZRECEIVEQUEUE) // Chunk fertig empfangen
    {
      nRead=ReadComm(Com.ID,inBuffer,SZRECEIVEQUEUE);

      if (nRead != SZRECEIVEQUEUE)
      {
        InternalErrorBox(IDS_ERR_READCOMMSZFAILURE);
        return FALSE;
      }
      else if (sentinel=ChunkIsASim97Command(inBuffer))
      {
        ExecuteSim97Command(sentinel,inBuffer);
        return FALSE; // Dieser Aufruf hat keine Daten reingeschaufelt
      }
      else
      {
        // Übertragen vom Zwischenpuffer "inBuffer" in die Datenannahme
        // BYTE 0,1: Timecode Stunden
        Mon.DCDataHour = MakeInt(inBuffer[0],inBuffer[1]);
        // BYTE 2,3: Timecode Minuten
        Mon.DCDataMinu = MakeInt(inBuffer[2],inBuffer[3]);
        // BYTE 4,5: Timecode Sekunden
        Mon.DCDataSeco = MakeInt(inBuffer[4],inBuffer[5]);
        // BYTE 6,7: Timecode Frames
        Mon.DCDataFram = MakeInt(inBuffer[6],inBuffer[7]);
        // BYTE 8,9,10,11: Datum Kanal 0
        ReceiveChannels[0] = MakeFloat(inBuffer[8],inBuffer[9],inBuffer[10],inBuffer[11]);
        // BYTE 12,13,14,15: Datum Kanal 1
        ReceiveChannels[1] = MakeFloat(inBuffer[12],inBuffer[13],inBuffer[14],inBuffer[15]);
        // BYTE 16,17,18,19: Datum Kanal 2
        ReceiveChannels[2] = MakeFloat(inBuffer[16],inBuffer[17],inBuffer[18],inBuffer[19]);
        // BYTE 20,21,22,23: Datum Kanal 3
        ReceiveChannels[3] = MakeFloat(inBuffer[20],inBuffer[21],inBuffer[22],inBuffer[23]);
        // BYTE 24,25,26,27: Datum Kanal 4
        ReceiveChannels[4] = MakeFloat(inBuffer[24],inBuffer[25],inBuffer[26],inBuffer[27]);
        // BYTE 28,29,30,31: Datum Kanal 5
        ReceiveChannels[5] = MakeFloat(inBuffer[28],inBuffer[29],inBuffer[30],inBuffer[31]);
        // BYTE 32,33,34,35: Datum Kanal 6
        ReceiveChannels[6] = MakeFloat(inBuffer[32],inBuffer[33],inBuffer[34],inBuffer[35]);
        // BYTE 36,37,38,39: Datum Kanal 7
        ReceiveChannels[7] = MakeFloat(inBuffer[36],inBuffer[37],inBuffer[38],inBuffer[39]);
        // BYTE 40,41,42,43: Datum Kanal 8
        ReceiveChannels[8] = MakeFloat(inBuffer[40],inBuffer[41],inBuffer[42],inBuffer[43]);
        // BYTE 44,45,46,47: Datum Kanal 9
        ReceiveChannels[9] = MakeFloat(inBuffer[44],inBuffer[45],inBuffer[46],inBuffer[47]);
        // BYTE 48,49,50,51: Datum Kanal 10
        ReceiveChannels[10] = MakeFloat(inBuffer[48],inBuffer[49],inBuffer[50],inBuffer[51]);
        // BYTE 52,53,54,55: Datum Kanal 11
        ReceiveChannels[11] = MakeFloat(inBuffer[52],inBuffer[53],inBuffer[54],inBuffer[55]);
        // BYTE 56,57,58,59: Datum Kanal 12
        ReceiveChannels[12] = MakeFloat(inBuffer[56],inBuffer[57],inBuffer[58],inBuffer[59]);
        // BYTE 60,61,62,63: Datum Kanal 13
        ReceiveChannels[13] = MakeFloat(inBuffer[60],inBuffer[61],inBuffer[62],inBuffer[63]);
        // BYTE 64,65,66,67: Datum Kanal 14
        ReceiveChannels[14] = MakeFloat(inBuffer[64],inBuffer[65],inBuffer[66],inBuffer[67]);
        // BYTE 68,69,70,71: Datum Kanal 15
        ReceiveChannels[15] = MakeFloat(inBuffer[68],inBuffer[69],inBuffer[70],inBuffer[71]);
        // BYTE 72,73: Checksumme
        Mon.DCDataCheckSum = MakeInt(inBuffer[72],inBuffer[73]);

        if (!CheckChecksum(inBuffer,72,Mon.DCDataCheckSum))
        {
          // Unmöglicher Unsinn-Timecode zum Zeichen, daß in dieser Zeile ein Fehler vorliegt
          Mon.DCDataHour = Mon.DCDataMinu = Mon.DCDataSeco = Mon.DCDataFram = 99;
        }

        // Gib die Daten für achilles 85 der TU München auf den D/A-Wandler von Kolter aus

        // Wenn der Schwenkspiegelwert größer als 30 Winkelgrad (d.h. >= 20 Grad Kurswinkelfehler)
        // sind zu simulieren, dann gib Warnton aus. Der Spiegel selbst wird tatsächlich nur bis
        // maximal 20 Grad simuliert geschwenkt, bei 40 Grad simuliert erfolgt ein Not-Rücksetzen
        // auf 0 Grad simuliert.

        if (ReceiveChannels[6] < -20. || ReceiveChannels[6] > 20.)
        {
          // Warnton
          KolterDAC4Burst(4095,  // Kanal0: Geschwindigkeit
                          0.,  // Kanal1: Last
                          4095,  // Kanal2: Drehzahl
                          0.);   // Kanal3: Nicht belegt
        }
        else
        {
          // Normale Fahrzeuginnengeräuschsimulation
          KolterDAC4Burst(ReceiveChannels[3],   // Kanal0: Geschwindigkeit
                          ReceiveChannels[15],  // Kanal1: Last
                          ReceiveChannels[14],  // Kanal2: Drehzahl
                          0.);                  // Kanal3: Nicht belegt
        }

      }
    }
    else // Übertragung des Chunk ist noch nicht abgeschlossen
    {
      return FALSE; // Dieser Aufruf hat keine Daten reingeschaufelt
    }
  }

  // Ende Lesen aus dem Eingangspuffer

  // Lauf 1: Sämtliche Werte in den ReceiveChannels[] renormieren
  for(i=0;i < KNOWLEDGEMAX;i++)
  {
    if (CompiledKnowledge[i].iAm == DATA) // Das Wissen über die Datenkanäle steckt in den DATA-Knoten
    {
      resultValue = Renorm(ReceiveChannels[CompiledKnowledge[i].sourceChannel], // alter Wert
                           CompiledKnowledge[i].minimum,                        // altes Minimum
                           CompiledKnowledge[i].maximum,                        // altes Maximum
                           CompiledKnowledge[i].data1Double,                    // neues Minimum
                           CompiledKnowledge[i].data2Double);                   // neues Maximum

      ReceiveChannels[CompiledKnowledge[i].sourceChannel] = resultValue; // Jetzt alles renormiert !
    }
  }

  // Lauf 2: Berechnen der Maße bzw. Rohdaten durchreichen und Mapping auf Mon.DCData[]
  slotInMon = 0;

  for(i=0;i < KNOWLEDGEMAX;i++)
  {
    if (CompiledKnowledge[i].plotMarked) // Soll dargestellt werden
    {
      switch(CompiledKnowledge[i].iAm)
      {
        case UNDEFINED:
             // So erkennt man einen freien Slot im Array (darf nicht vorkommen)
             InternalErrorBox(IDS_ERR_UNDEFDPLOTMARKEDKNOSLOT);
             break;

        case DATA:
             Mon.DCData[slotInMon] = ReceiveChannels[CompiledKnowledge[i].sourceChannel];
             slotInMon++;
             break;

        case SIGNEDDIFFERENCE:
             // Differenz der Werte zweier Kanäle
             helpDouble1 = ReceiveChannels[CompiledKnowledge[i].sourceChannel];
             helpDouble2 = ReceiveChannels[CompiledKnowledge[i].sourceChannel2nd];
             resultValue = helpDouble1 - helpDouble2;
             Mon.DCData[slotInMon] = resultValue;
             slotInMon++;
             break;

        case ABSOLUTEDIFFERENCE:
             // Betrag der Differenz der Werte zweier Kanäle
             helpDouble1 = ReceiveChannels[CompiledKnowledge[i].sourceChannel];
             helpDouble2 = ReceiveChannels[CompiledKnowledge[i].sourceChannel2nd];
             resultValue = fabs(helpDouble1 - helpDouble2);
             Mon.DCData[slotInMon] = resultValue;
             slotInMon++;
             break;

        default:
             // Unbekannter KNOWTYPE in CompiledKnowledge[i].iAm
             InternalErrorBox(IDS_ERR_UNKNOWNKNOTYPEINKNOSLOT);
      }
    }
  }

  return TRUE;
}

double FAR PASCAL Renorm(double oldVal,double oldMin,double oldMax,double newMin,double newMax)
{
  // Wenn keine Renormierung eingestellt ist, kann man sich die Berechnung sparen
  if (newMin == oldMin && newMax == oldMax)
  {
    return(oldVal);
  }
  else
  {
    return(newMin + (((oldVal - oldMin) / Range(oldMax,oldMin)) * Range(newMax,newMin)));
  }
}

void FAR PASCAL ExplainCommError(int errCode)
{
  int errStrID;

  if (errCode & CE_RXOVER)     //  0x0001 Receive Queue overflow
    CommunicationErrorBox(IDS_ERR_CE_RXOVER);

  if (errCode & CE_OVERRUN)    //  0x0002 Receive Overrun Error
    CommunicationErrorBox(IDS_ERR_CE_OVERRUN);

  if (errCode & CE_RXPARITY)   //  0x004 Receive Parity Error
    CommunicationErrorBox(IDS_ERR_CE_OVERRUN);

  if (errCode & CE_FRAME)      //  0x0008 Receive Framing error
    CommunicationErrorBox(IDS_ERR_CE_FRAME);

  if (errCode & CE_BREAK)      //  0x0010 Break Detected
    CommunicationErrorBox(IDS_ERR_CE_BREAK);

  if (errCode & CE_CTSTO)      //  0x0020 CTS Timeout
    CommunicationErrorBox(IDS_ERR_CE_CTSTO);

  if (errCode & CE_DSRTO)      //  0x0040 DSR Timeout
    CommunicationErrorBox(IDS_ERR_CE_DSRTO);

  if (errCode & CE_RLSDTO)     //  0x0080 RLSD Timeout
    CommunicationErrorBox(IDS_ERR_CE_RLSDTO);

  if (errCode & CE_TXFULL)     //  0x0100 TX Queue is full
    CommunicationErrorBox(IDS_ERR_CE_TXFULL);

  if (errCode & CE_PTO)        //  0x0200 LPTx Timeout (unmöglich bei COM-Schnittstelle)
    CommunicationErrorBox(IDS_ERR_CE_PTO);

  if (errCode & CE_IOE)        //  0x0400 LPTx I/O Error (unmöglich bei COM-Schnittstelle)
    CommunicationErrorBox(IDS_ERR_CE_IOE);

  if (errCode & CE_DNS)        //  0x0800 LPTx Device not selected (unmöglich bei COM-Schnittstelle)
    CommunicationErrorBox(IDS_ERR_CE_DNS);

  if (errCode & CE_OOP)        //  0x1000 LPTx Out-Of-Paper (unmöglich bei COM-Schnittstelle)
    CommunicationErrorBox(IDS_ERR_CE_OOP);

  if (errCode & CE_MODE)       //  0x8000 Requested mode unsupported
    CommunicationErrorBox(IDS_ERR_CE_MODE);
}

void FAR PASCAL OutputMessage(LPSTR msg)
{
  HBRUSH frameBrush = CreateSolidBrush(RGB(192,192,192));
  HBRUSH backedupBrush;
  HPEN usePen = CreatePen(PS_SOLID,1,RGB(192,192,192));
  HPEN backedupPen;

  RECT drawRect;
  HFONT backupFont;
  HDC hdc;

  drawRect.left   = Mon.statusLeftTop.x;
  drawRect.top    = Mon.statusLeftTop.y;
  drawRect.right  = Mon.statusRightBottom.x;
  drawRect.bottom = Mon.statusRightBottom.y;

  hdc = GetDC(hWndMain);

  backupFont = SelectObject(hdc,WorkFont);
  backedupBrush = SelectObject(hdc,frameBrush);

  // Evtl. alten Text löschen
  backedupPen = SelectObject(hdc,usePen); // Rechteckrahmen muß auch grau

  Rectangle(hdc,
            Mon.statusLeftTop.x+1,
            Mon.statusLeftTop.y+1,
            Mon.statusRightBottom.x,
            Mon.statusRightBottom.y);

  SelectObject(hdc,backedupPen);
  DeleteObject(usePen);

  SetBkMode(hdc,TRANSPARENT);
  DrawText(hdc,msg,-1,&drawRect,DT_SINGLELINE|DT_LEFT|DT_VCENTER);

  SelectObject(hdc,backupFont);

  SelectObject(hdc,backedupBrush);
  DeleteObject(frameBrush);

  ReleaseDC(hWndMain,hdc);
}

// Das wäre wirklich sehr schön gewesen, aber die Methode ist leider zu langsam, ein
// BitBlt braucht einfach zu lange, man kann ihn nur bei sehr kleinen Flächen benutzen,
// für eine Echtzeitanwendung kommt folgendes leider nicht in Frage.
// Schöner Plot im autistic mode, benutze nur einen kleinen Streifen (schnell genug)
// Es kann durch die Mittelwertbildung bei bestimmten Inputwerten zu Verzerrungen (Artefakte)
// kommen
void FAR PASCAL NicePlotValue(double value,double minValue,double maxValue,BOOL notOnlyErase)
{
  static int beenHereCounter = 0; // Wird zur Stauchung der Zeitachse verwendet
  static double sumValue = 0;
  double meanValue;
  static HANDLE hBitmap;
  HDC hdc;
  HDC hdcMem;

  if (!notOnlyErase) // Soll nur gelöscht werden, genügen ein paar einfache Schritte
  {
    HRGN hRgn = CreateRectRgn(Mon.auNiceLeftTop.x,
                              Mon.auNiceLeftTop.y,
                              Mon.auNiceRightBottom.x+1,
                              Mon.auNiceRightBottom.y);
    HBRUSH hBrush = CreateSolidBrush(RGB(192,192,192));
    hdc = GetWindowDC(hWndMain);

    FillRgn(hdc,hRgn,hBrush);

    ReleaseDC(hWndMain,hdc);
    DeleteObject(hRgn);
    DeleteObject(hBrush);
    // Reinitialisierungen: wichtig beim Wechsel auf neue Plotvariable
    beenHereCounter = 0;
    sumValue = 0.;
  }
  else
  {
    int pntX = Mon.auNiceRightBottom.x; // Bildschirm-X des zu plottenden Pixels, immer gleich
    int pntY;                           // Bildschirm-Y des zu plottenden Pixels
    int rangeY;                         // Panehöhe
    int putX;
    int putY;
    int bitmapWidth  = (Mon.auNiceRightBottom.x - Mon.auNiceLeftBottom.x) - 2;
    int bitmapHeight = Mon.auNiceRightBottom.y - Mon.auNiceRightTop.y;
    int takeX = Mon.auNiceLeftTop.x + 1;
    int takeY = Mon.auNiceLeftTop.y;
    double offTheMin;
    double valueToPlot;

    // Stauchung der Zeitachse
    beenHereCounter++;

    if (beenHereCounter == NICEINTERVAL)
    {
      beenHereCounter = 0;
      meanValue = sumValue / NICEINTERVAL;
      sumValue = 0.;
      goto DOPLOT;
    }
    else
    {
      sumValue += value;
      return;
    }

DOPLOT:
    // Initial (allererster zu plottender Punkt) kann der Punkt falsch berechnet werden, daher:
    if (Mon.ScanCount < NICEINTERVAL)
      return;

    hdc = GetWindowDC(hWndMain);
    hdcMem = CreateCompatibleDC(hdc);
    hBitmap = CreateCompatibleBitmap(hdc,bitmapWidth,bitmapHeight);

    SelectObject(hdc,hBitmap);
    SelectObject(hdcMem,hBitmap);

    // Berechnung des Pixels, die Korrekturen beachten, mit denen die Eckpunkte behaftet sind
    // Sollte nicht mehr "verbessert" werden - scheinbare Fehler können Mittelwertartefakte sein
    if(meanValue < minValue) // Lower clipping
    {
      pntY = Mon.auNiceRightBottom.y+1; // Das geht nicht mit der else-Rechenmethode
    }
    else if (meanValue > maxValue) // Upper clipping
    {
      pntY = Mon.auNiceRightTop.y-1; // Das geht nicht mit der else-Rechenmethode
    }
    else
    {
      rangeY = (Mon.auNiceRightBottom.y+1) - (Mon.auNiceRightTop.y-1);
      offTheMin = Range(meanValue,minValue);
      valueToPlot = (offTheMin / Range(maxValue,minValue)) * (double)rangeY;
      pntY = Mon.auNiceRightBottom.y - (int)valueToPlot;
      pntY -= 1; // Mon.auNiceRightBottom.y ist nicht ganz ok
    }

    // Berechnung ist fertig

    // 1. Malen auf DC
    if (!(pntY <= Mon.auNiceRightTop.y-1 || pntY >= Mon.auNiceRightBottom.y+1)) // Exclude clipped values
    {
      SetPixel(hdc,pntX-1,pntY,RGB(0,0,0));
    }
    // 2. Kopieren auf MemDC
    BitBlt(hdcMem,0,0,bitmapWidth,bitmapHeight,hdc,takeX,takeY,SRCCOPY);

    // PaneInhalt nach links rausschieben
    putX = Mon.auNiceLeftTop.x;
    putY = Mon.auNiceLeftTop.y;

    BitBlt(hdc,putX,putY,bitmapWidth,bitmapHeight,hdcMem,0,0,SRCCOPY);

    ReleaseDC(hWndMain,hdc);
    DeleteDC(hdcMem);

    if (hBitmap)
      DeleteObject(hBitmap);
  }
}

// This is the stripchart function, in brackets the situations when a value is needed
void FAR PASCAL StripChartManager(HDC hdc,           // A handle to a device context (always)
                                  int ID,            // Chart Identifier (0 to 15)
                                  int wxs,           // Wanted left (if SETUP)
                                  int wys,           // Wanted top  (if SETUP)
                                  int wxe,           // Wanted right (if SETUP)
                                  int wye,           // Wanted bottom (if SETUP)
                                  double datum,      // Data Item to plot (if PLOT)
                                  double minimum,    // Minimal displayable value (if SETUP)
                                  double maximum,    // Maximal displayable value (if SETUP)
                                  STRIPTYPE doWhat)  // SETUP or PLOT or SHUTDOWN
{
  HPEN hBackUpPen;              // Holds the pen originally bound to the HDC
  static HPEN hLGreyPen = NULL; // The pen matching the background color
  static HPEN hDGreyPen = NULL; // Pen used to draw the moving bar
  HBRUSH hLGreyBrush;           // Zum Hintergrund-Übermalen
  RECT kasten;
  double offFromMin;     // Hilfsvariable
  double cartesian;      // Hilfsvariable

  // Initialize the y locations of tic marks
  if (doWhat == SETUP)
  {
    if (ID > 15)
    {
      MessageBox(NULL,"StripChartID > 15","ERROR",MB_OK|MB_TASKMODAL);
    }

    SS[ID].theMin = minimum;
    SS[ID].theMax = maximum;

    SS[ID].ys=wys;
    SS[ID].ye=wye;
    SS[ID].xs=wxs;
    SS[ID].xe=wxe;

    // Lösche den Hintergrund
    kasten.left=SS[ID].xs;
    kasten.top=SS[ID].ys;
    kasten.right=SS[ID].xe;
    kasten.bottom=SS[ID].ye+1;
    hLGreyBrush=CreateSolidBrush(RGB(192,192,192));
    FillRect(hdc,(LPRECT)&kasten,hLGreyBrush);
    DeleteObject(hLGreyBrush);

    // Left side
    SS[ID].x1=SS[ID].xs-1;
    SS[ID].x2=SS[ID].x1;
    SS[ID].y1=SS[ID].ys;
    SS[ID].y2=SS[ID].ye+1;

    // Top side
    SS[ID].x1=SS[ID].xs-1;
    SS[ID].x2=SS[ID].xe+1;
    SS[ID].y1=SS[ID].ys-1;
    SS[ID].y2=SS[ID].y1;

    // Right side
    SS[ID].x1=SS[ID].xe+1;
    SS[ID].x2=SS[ID].x1;
    SS[ID].y1=SS[ID].ys;
    SS[ID].y2=SS[ID].ye+1;

    // Bottom side
    SS[ID].x1=SS[ID].xs-1;
    SS[ID].x2=SS[ID].xe+1;
    SS[ID].y1=SS[ID].ye+1;
    SS[ID].y2=SS[ID].y1;

    // Height of the plot pane memorized as a double
    SS[ID].dPlotPaneHeight=(double)(SS[ID].ye-SS[ID].ys+1);

    // Draw the initial tracking bar which scans left to right across the screen.
    SS[ID].position=SS[ID].xs;
    MoveTo(hdc,SS[ID].position+1,SS[ID].ys);
    LineTo(hdc,SS[ID].position+1,SS[ID].ye);

    // Get the LightGreyPen
    if (hLGreyPen == NULL) // if not valid from an earlier call
      hLGreyPen = CreatePen(PS_SOLID,1,RGB(192,192,192));

    //  Get the DarkGreyPen
    if (hDGreyPen == NULL) // if not valid from an earlier call
      hDGreyPen = CreatePen(PS_SOLID,1,RGB(128,128,128));
  }
  else if (doWhat == PLOT)
  {
    // Get the new point to plot
    if(datum < SS[ID].theMin) // Lower clipping
    {
      SS[ID].new_value = SS[ID].ye+1; // Das geht nicht mit der else-Rechenmethode
    }
    else if (datum > SS[ID].theMax) // Upper clipping
    {
      SS[ID].new_value = SS[ID].ys-1; // Das geht nicht mit der else-Rechenmethode
    }
    else
    {
      offFromMin = Range(datum,SS[ID].theMin);
      cartesian = (offFromMin / Range(SS[ID].theMax,SS[ID].theMin)) * SS[ID].dPlotPaneHeight;
      SS[ID].new_value = SS[ID].ye - (int)cartesian;
    }

    // Check to see if we've come back to the beginning
    if(SS[ID].position == SS[ID].xs)
      SS[ID].old_value=SS[ID].new_value;

    // Update the tracking bar.  First draw new bar, then remove old bar.
    // The order is important; this helps reduce unsightly flicker.

    SS[ID].old_position=SS[ID].position;

    if(SS[ID].position++ == SS[ID].xe)
    {
      SS[ID].position=SS[ID].xs;
      SS[ID].old_position=SS[ID].xs-1;
    }

    // Drawing new bar, but leave alone right edge
    hBackUpPen=SelectObject(hdc,hDGreyPen);

    if (SS[ID].position+1 != SS[ID].xe+1)
    {
      MoveTo(hdc,SS[ID].position+1,SS[ID].ys);
      LineTo(hdc,SS[ID].position+1,SS[ID].ye+1);    // Added the +1, now it works

      // Exclude clipped values from painting
      if (!(SS[ID].old_value == SS[ID].ye+1 ||
            SS[ID].old_value == SS[ID].ys-1 ||
            SS[ID].new_value == SS[ID].ye+1 ||
            SS[ID].new_value == SS[ID].ys-1))
      {
        SetPixel(hdc,SS[ID].position+1,SS[ID].new_value,RGB(255,0,0)); // "Stift"
      }
    }

    // Removing old bar
    SelectObject(hdc,hLGreyPen);         // Have to delete old bar

    MoveTo(hdc,SS[ID].old_position+1,SS[ID].ys);
    LineTo(hdc,SS[ID].old_position+1,SS[ID].ye+1);  // Added the +1, now it works

    // Plot the A/D values by drawing a line from last value read to the new value.

    if(SS[ID].position == SS[ID].xs)
      SS[ID].old_position=SS[ID].xs;

    SelectObject(hdc,GetStockObject(BLACK_PEN));

    // Exclude clipped values from painting
    if (!(SS[ID].old_value == SS[ID].ye+1 ||
          SS[ID].old_value == SS[ID].ys-1 ||
          SS[ID].new_value == SS[ID].ye+1 ||
          SS[ID].new_value == SS[ID].ys-1))
    {
      MoveTo(hdc,SS[ID].old_position,SS[ID].old_value);
      LineTo(hdc,SS[ID].position,SS[ID].new_value);
    }

    SelectObject(hdc,hBackUpPen);

    // We've plotted the point, now it's the old point
    SS[ID].old_value=SS[ID].new_value;
  }
  else if (doWhat == SHUTDOWN)
  {
    if (hLGreyPen != NULL)
    {
      DeleteObject(hLGreyPen);
    }

    if (hDGreyPen != NULL)
    {
      DeleteObject(hDGreyPen);
    }
  }
}

// Bringt nach Änderungen Mon auf den neuesten Stand
// Bringt außerdem den String Feedback auf den neuesten Stand
// Stellt die Verbindung zwischen CompiledKnowledge und der
// noch schneller zu lesenden Struktur Mon her und ist daher nach jedem
// Recompilieren der Wissensbasis aufzurufen
void FAR PASCAL UpdateMonStruct(BOOL withFeedback)
{
  int i;
  int sum = 0;
  int lastMarkedDCCha = 0;

  // Anmerkung: mon.DCData[0 bis 15] stellt die Resultatkanäle dar !

  // Vorspiel: Anzeige/Schreib-steuernde Elemente der Struktur Mon ausleeren
  for(i=0;i < CHANNELMAX;i++)
  {
    Mon.DCChaMarked4Plot[i] = FALSE;
    Mon.DCChaMarked4Write[i] = FALSE;
  }

  // Teil 1: Transfer von CompiledKnowledge nach Mon vornehmen

  for(i=0;i < KNOWLEDGEMAX;i++)
  {
    if (CompiledKnowledge[i].iAm != 0) // definierter Knoten
    {
      if (CompiledKnowledge[i].plotMarked) // soll dargestellt werden
      {
        lstrcpy(Mon.DCNames[lastMarkedDCCha],CompiledKnowledge[i].nameInKnowledge);
        Mon.DCChaMarked4Plot[lastMarkedDCCha] = TRUE;
        Mon.DCDataMinima[lastMarkedDCCha] = CompiledKnowledge[i].minimum;
        Mon.DCDataMaxima[lastMarkedDCCha] = CompiledKnowledge[i].maximum;

        if (CompiledKnowledge[i].writeMarked) // soll geschrieben werden
        {
          Mon.DCChaMarked4Write[lastMarkedDCCha] = TRUE;
        }

        // Sonderfall der DATA: Mon erhält hier die die Minima und Maxima NACH Projektion
        if (CompiledKnowledge[i].iAm == DATA)
        {
          Mon.DCDataMinima[lastMarkedDCCha] = CompiledKnowledge[i].data1Double;
          Mon.DCDataMaxima[lastMarkedDCCha] = CompiledKnowledge[i].data2Double;
        }

        lastMarkedDCCha++;
      }
    }
  }

  // Teil 2: Mon-interne Aktualisierungen vornehmen

  for(i=0;i < CHANNELMAX;i++)
  {
    if (Mon.DCChaMarked4Plot[i])
      sum++;
  }

  Mon.nDataPanesPlotMarked = sum;

  sum = 0;

  for(i=0;i < CHANNELMAX;i++)
  {
    if (Mon.DCChaMarked4Write[i])
      sum++;
  }

  Mon.nDataPanesWriteMarked = sum;

  if (withFeedback)
  {
    if (MEDIMONAutistic || MEDIMONEnterAutistic)
    {
      sprintf(Feedback,"   Maximal %lu Meßdatensätze - Aufzeichnung für %s läuft - <ESC> = Aufzeichnung abbrechen",
      MemAvail4NRecords,
      Mon.WriteFileName);
    }
    else
    {
      sprintf(Feedback,
          "   Maximal %lu Meßdatensätze - Anzeigen: %d - Schreiben: %d - Schreibdatei: %s",
          MemAvail4NRecords,
          Mon.nDataPanesPlotMarked,
          Mon.nDataPanesWriteMarked,
          Mon.WriteFileName);
    }

    OutputMessage(Feedback); // evtl. verzichtbar, falls sowieso prinzipiell ein Neuzeichnen folgt
  }
}

// Übermalt Caption und Menuzeile, die in diesem Zustand keinen Effekt haben
// Darf nur aufgerufen werden, wenn mindestens eine Pane existiert
void FAR PASCAL MakeDisplayAutistic(void)
{
  HDC    hdc;
  RECT   rect;
  RECT   mouseCage;
  int    tinyHBand = GetSystemMetrics(SM_CXFRAME);
  int    reallyTinyVBand = GetSystemMetrics(SM_CYFRAME) / 2;

  hdc = GetWindowDC(hWndMain);

  GetClientRect(hWndMain,&rect); // brauche .right
  Mon.autistLeftTop.x     = tinyHBand;
  Mon.autistLeftTop.y     = GetSystemMetrics(SM_CYFRAME);
  Mon.autistLeftBottom.x  = Mon.autistLeftTop.x;
  Mon.autistLeftBottom.y  = (Mon.autistLeftTop.y-1) + GetSystemMetrics(SM_CYCAPTION) + GetSystemMetrics(SM_CYMENU);
  Mon.autistRightBottom.x = rect.right + Mon.autistLeftTop.x;
  Mon.autistRightBottom.y = Mon.autistLeftBottom.y;
  Mon.autistRightTop.x    = Mon.autistRightBottom.x;
  Mon.autistRightTop.y    = Mon.autistLeftTop.y;

  DrawOuterTextPane(hdc,Mon.autistLeftTop,Mon.autistLeftBottom,Mon.autistRightBottom,Mon.autistRightTop);

  { // Die Spezialausgabepanes für den autistischen Zustand berechnen
    // Schön ist die Breite, wenn sie der der Textausgabepanes entspricht
    int niceWidth = Mon.namepanes[0].right - Mon.namepanes[0].left;

    // Koordinaten Messungen-Zähler-Pane
    Mon.auCountLeftTop.x     = Mon.autistLeftTop.x + 2*tinyHBand;
    Mon.auCountLeftTop.y     = Mon.autistLeftTop.y + GetSystemMetrics(SM_CYFRAME);
    Mon.auCountLeftBottom.x  = Mon.auCountLeftTop.x;
    Mon.auCountLeftBottom.y  = Mon.autistLeftBottom.y - GetSystemMetrics(SM_CYFRAME);
    Mon.auCountRightBottom.x = Mon.auCountLeftBottom.x + niceWidth;
    Mon.auCountRightBottom.y = Mon.auCountLeftBottom.y;
    Mon.auCountRightTop.x    = Mon.auCountRightBottom.x;
    Mon.auCountRightTop.y    = Mon.auCountLeftTop.y;

    // Koordinaten Wert-Anzeige-Pane
    Mon.auValueLeftTop.x     = Mon.auCountRightTop.x + 2*tinyHBand;
    Mon.auValueLeftTop.y     = Mon.auCountLeftTop.y;
    Mon.auValueLeftBottom.x  = Mon.auValueLeftTop.x;
    Mon.auValueLeftBottom.y  = Mon.auCountLeftBottom.y;
    Mon.auValueRightBottom.x = Mon.auValueLeftBottom.x + niceWidth;
    Mon.auValueRightBottom.y = Mon.auValueLeftBottom.y;
    Mon.auValueRightTop.x    = Mon.auValueRightBottom.x;
    Mon.auValueRightTop.y    = Mon.auValueLeftTop.y;

    // Koordinaten Daten-Schönplot-Pane
    Mon.auNiceLeftTop.x      = Mon.auValueRightTop.x + 2*tinyHBand;
    Mon.auNiceLeftTop.y      = Mon.auValueLeftTop.y;
    Mon.auNiceLeftBottom.x   = Mon.auNiceLeftTop.x;
    Mon.auNiceLeftBottom.y   = Mon.auValueLeftBottom.y;
    Mon.auNiceRightBottom.x  = Mon.datapanes[0].right + Mon.mainLeftTop.x;
    Mon.auNiceRightBottom.y  = Mon.auNiceLeftBottom.y;
    Mon.auNiceRightTop.x     = Mon.auNiceRightBottom.x;
    Mon.auNiceRightTop.y     = Mon.auNiceLeftTop.y;

    // Zeichne Messungen-Zähler-Pane
    DrawTextPane(hdc,
                 Mon.auCountLeftTop,
                 Mon.auCountLeftBottom,
                 Mon.auCountRightBottom,
                 Mon.auCountRightTop,
                 FALSE);

    // Zeichne Wert-Anzeige-Pane
    DrawTextPane(hdc,
                 Mon.auValueLeftTop,
                 Mon.auValueLeftBottom,
                 Mon.auValueRightBottom,
                 Mon.auValueRightTop,
                 FALSE);

    // Zeichne Daten-Schönplot-Pane
    DrawTextPane(hdc,
                 Mon.auNiceLeftTop,
                 Mon.auNiceLeftBottom,
                 Mon.auNiceRightBottom,
                 Mon.auNiceRightTop,
                 FALSE);

    // Korrekturen für nachfolgend aufsetzende Routinen ausführen
    Mon.auNiceLeftTop.x += 1;
    Mon.auNiceLeftTop.y += 1;
    Mon.auNiceLeftBottom.x -= 1;
    Mon.auNiceLeftBottom.y -= 1;
    Mon.auNiceRightBottom.x -= 1;
    Mon.auNiceRightBottom.y -= 1;
    Mon.auNiceRightTop.x += 1;
    Mon.auNiceRightTop.y += 1;
  }

  ReleaseDC(hWndMain,hdc);

  // Mauscursor fangen, damit nicht auf andere Applikationen umgeschaltet werden kann
  GetWindowRect(hWndMain,&mouseCage);
  ClipCursor(&mouseCage);
  MEDIMONAutistic = TRUE;       // Der Zustandseintritt
}

// Veranlasse Neuzeichnen von Caption und Menuzeile
void FAR PASCAL MakeDisplayNonAutistic(void)
{
  ClipCursor(NULL); // Mauscursor befreien
  ShowWindow(hWndMain,SW_SHOWNA);
}

// Gibt den globalen Scanzähler auf die Zählpane aus (autistic mode only)
void FAR PASCAL OutputScanCount(void)
{
  RECT outR;
  HFONT backupFont;
  DWORD backupColor;
  char txtCount[64];
  HDC hdc = GetWindowDC(hWndMain);

  outR.left   = Mon.auCountLeftTop.x;
  outR.top    = Mon.auCountLeftTop.y;
  outR.right  = Mon.auCountRightBottom.x;
  outR.bottom = Mon.auCountRightBottom.y;
  wsprintf(txtCount,"%ld",Mon.ScanCount);
  backupColor = SetBkColor(hdc,RGB(192,192,192));
  SetBkMode(hdc,OPAQUE);
  backupFont = SelectObject(hdc,WorkFont);
  DrawText(hdc,txtCount,-1,&outR,DT_SINGLELINE|DT_CENTER|DT_VCENTER);
  SelectObject(hdc,backupFont);
  SetBkColor(hdc,backupColor);
  ReleaseDC(hWndMain,hdc);
}

// Gibt einen double-Datenwert auf die Literal-Daten-Pane aus (autistic mode only)
void FAR PASCAL OutputDoubleAsLiteral(double value,BOOL notOnlyErase)
{
  HRGN hRgn;
  RECT outR;
  HFONT backupFont;
  DWORD backupColor;
  HBRUSH hBrush = CreateSolidBrush(RGB(192,192,192));
  char txtValue[256];
  HDC hdc = GetWindowDC(hWndMain);

  outR.left   = Mon.auValueLeftTop.x;
  outR.top    = Mon.auValueLeftTop.y;
  outR.right  = Mon.auValueRightBottom.x;
  outR.bottom = Mon.auValueRightBottom.y;
  sprintf(txtValue,"%.2lf",value);

  hRgn = CreateRectRgn(outR.left+1,
                       outR.top+1,
                       outR.right-1,
                       outR.bottom-1);
  backupColor = SetBkColor(hdc,RGB(192,192,192));
  FillRgn(hdc,hRgn,hBrush);

  if (notOnlyErase)
  {
    backupFont = SelectObject(hdc,WorkFont);
    DrawText(hdc,txtValue,-1,&outR,DT_SINGLELINE|DT_CENTER|DT_VCENTER);
    SelectObject(hdc,backupFont);
  }

  SetBkColor(hdc,backupColor);
  DeleteObject(hRgn);
  DeleteObject(hBrush);
  ReleaseDC(hWndMain,hdc);
}

// Gibt einen Timecode-Wert auf die Literal-Daten-Pane aus (autistic mode only)
void FAR PASCAL OutputTCAsLiteral(int hours,int minutes,int seconds,int frames)
{
  RECT outR;
  HFONT backupFont;
  DWORD backupColor;
  char txtValue[256];
  HDC hdc = GetWindowDC(hWndMain);

  outR.left   = Mon.auValueLeftTop.x;
  outR.top    = Mon.auValueLeftTop.y;
  outR.right  = Mon.auValueRightBottom.x;
  outR.bottom = Mon.auValueRightBottom.y;

  sprintf(txtValue,
          "%02.f:%02.f:%02.f:%02.f",
          (float)hours,
          (float)minutes,
          (float)seconds,
          (float)frames);

  backupColor = SetBkColor(hdc,RGB(192,192,192));
  backupFont = SelectObject(hdc,WorkFont);
  DrawText(hdc,txtValue,-1,&outR,DT_SINGLELINE|DT_CENTER|DT_VCENTER);

  SelectObject(hdc,backupFont);
  SetBkColor(hdc,backupColor);

  ReleaseDC(hWndMain,hdc);
}

// Stellt fest, auf welche Textpane der Mauszeiger im autistic mode zeigt
int FAR PASCAL TxtPaneChaNoCursorIsOn(void)
{
  int i;
  int channelIndex;
  int targetPaneIndex = -9999; // Initialisierung mit Missmatch-Wert erforderlich
  int candidatePaneIndex;
  POINT c;
  HRGN hRgn;
  int result;

  GetCursorPos(&c);
  ScreenToClient(hWndMain,&c);

  for(i=0;i < Mon.nDataPanesPlotMarked;i++)
  {
    hRgn = CreateRectRgn(Mon.namepanes[i].left,
                         Mon.namepanes[i].top,
                         Mon.namepanes[i].right,
                         Mon.namepanes[i].bottom);

    if (PtInRegion(hRgn,c.x,c.y)) // Hit
    {
      targetPaneIndex = i;
    }

    DeleteObject(hRgn);
  }

  channelIndex = candidatePaneIndex = -1;

  for(i=0;i < CHANNELMAX;i++)
  {
    if (Mon.DCChaMarked4Plot[i])
    {
      candidatePaneIndex++;

      if (targetPaneIndex == candidatePaneIndex)
      {
        channelIndex = i;
      }
    }
  }

  result = channelIndex;    // Index des Kanals, der da liegt
  return(result);
}

// Das muß so sein, glaub es einfach
BOOL FAR PASCAL IsEscapeCondition(void)
{
  if (GetAsyncKeyState(VK_ESCAPE))
  {
    if (GetAsyncKeyState(VK_ESCAPE))
    {
      return TRUE;
    }
  }

  return FALSE;
}

// Bequeme Speichwerverwaltung, beachte daß lange Zeiger zurückgeliefert werden
BYTE FAR *wmalloc(LONG cbData)
{
  HANDLE hgm;

  hgm = GlobalAlloc(GMEM_MOVEABLE,cbData);

  if (!hgm) return NULL;

  return GlobalLock(hgm);
}

void wfree(BYTE FAR *pbData)
{
  HANDLE hgm = (HANDLE)GlobalHandle(HIWORD(pbData));

  GlobalUnlock(hgm);
  GlobalFree(hgm);
}

// Bequeme Zerlegung / Wiederzusammenbau von Werten für serielle Übertragung

int FAR PASCAL MakeInt(BYTE a,BYTE b)
{
  int trans;
  BYTE fld[2];

  fld[0] = a;
  fld[1] = b;

  memcpy(&trans,fld,2);
  return trans;
}

float FAR PASCAL MakeFloat(BYTE a,BYTE b,BYTE c,BYTE d)
{
  float trans;
  BYTE fld[4];

  fld[0] = a;
  fld[1] = b;
  fld[2] = c;
  fld[3] = d;

  memcpy(&trans,fld,4);
  return trans;
}

// Senderseitig benutzt:
//void FAR PASCAL IntParts(int value,BYTE *a,BYTE *b)
//{
//  int trans = value;
//  BYTE fld[2];
//
//  memcpy(fld,&trans,2);
//
//  *a = fld[0];
//  *b = fld[1];
//}

// Senderseitig benutzt:
//void FAR PASCAL FloatParts(float value,BYTE *a,BYTE *b,BYTE *c,BYTE *d)
//{
//  float trans = value;
//  BYTE fld[4];
//
//  memcpy(fld,&trans,4);
//
//  *a = fld[0];
//  *b = fld[1];
//  *c = fld[2];
//  *d = fld[3];
//}

// Generische Fehlermeldungsausgabe, benutzt für Fehler, die im Programm selbst begründet sind
void FAR PASCAL InternalErrorBox(int ID)
{
  MessageBeep(MB_ICONHAND);
  LoadString(hInst,ID,szString,sizeof(szString));
  MessageBox(NULL,szString,"Monitor: interner Fehler",MB_OK|MB_ICONHAND|MB_SYSTEMMODAL);
}

// Fehlermeldungsausgabe, benutzt für Fehler, die bei serieller Kommunikation auftreten
void FAR PASCAL CommunicationErrorBox(int ID)
{
  MessageBeep(MB_ICONHAND);
  LoadString(hInst,ID,szString,sizeof(szString));
  MessageBox(NULL,szString,"Monitor: Kommunikationsfehler",MB_OK|MB_ICONHAND|MB_SYSTEMMODAL);
}

// Translate from normal C convention to edit field convention
void TranslateCRLFToEdit(char *txt)
{
  char CRLF[3];
  char *target;

  CRLF[0] = 0x0d;
  CRLF[1] = 0x0a;
  CRLF[2] = 0x00;

  if ((target=strchr(txt,'\n')) != NULL)
  {
    *target = '\0';
    lstrcat(txt,CRLF);
  }
}

// Macht aus dem vom EditControl kommenden Text den passenden Text für die
// Knowledge Base Quelldatei
void FAR PASCAL WriteKnowledgeSourceText(FILE *target,LPSTR editText)
{
  char head[] = "Monitor Version 1.0 Knowledge Base Source\n";
  int numLines;

  // Zeile 1: Kopfzeile
  fprintf(target,"%s",head);

  { // Feststellen, wieviele Zeilen es werden
    char CR = 0x0d;
    char LF = 0x0a;
    char *partBuf;
    int look = 0;

    partBuf=(char*)wmalloc(512);

    numLines = 0;

    while (*(editText+look))
    {
      int localLook = 0;

      // Hole nächste Zeile in den Puffer
      while (*(editText+look) != CR && *(editText+look) != '\0')
      {
        *(partBuf+localLook) = *(editText+look);
        localLook++;
        look++;
      }

      *(partBuf+localLook) = '\0'; // String im Puffer abschließen

      // look auf nächsten Zeilenanfang setzen:

      // Erste Möglichkeit, es kommt nichts mehr
      if (*(editText+look) == '\0')
      {
        if (strstr(partBuf,"Template>!<") == NULL)
        {
          numLines++;
        }

        break;
      }

      // Zweite Möglichkeit, es kommt nichts mehr
      if (*(editText+look) == CR && *(editText+look+1) == LF && *(editText+look+2) == '\0')
      {
        if (strstr(partBuf,"Template>!<") == NULL)
        {
          numLines++;
        }

        break;
      }

      // Sonst dritte Möglichkeit,es geht weiter
      look++; // Steht nun auf LF
      look++; // Steht nun auf nächstem Zeilenanfang, bereit für nächten Schleifendurchlauf

      if (strstr(partBuf,"Template>!<") == NULL)
      {
        numLines++;
      }
    }

    wfree(partBuf);
  } // Variable numLines ist nun valide

  // Zeile 2: Zeilenanzahlinformation
  fprintf(target,"Lines = %d\n",numLines);

  { // Textzeilen schreiben
    char CR = 0x0d;
    char LF = 0x0a;
    char *partBuf;
    int look = 0;

    partBuf=(char*)wmalloc(512);

    while (*(editText+look))
    {
      int localLook = 0;

      // Hole nächste Zeile in den Puffer
      while (*(editText+look) != CR && *(editText+look) != '\0')
      {
        *(partBuf+localLook) = *(editText+look);
        localLook++;
        look++;
      }

      *(partBuf+localLook) = '\0'; // String im Puffer abschließen

      // look auf nächsten Zeilenanfang setzen:

      // Erste Möglichkeit, es kommt nichts mehr
      if (*(editText+look) == '\0')
      {
        if (strstr(partBuf,"Template>!<") == NULL) // Nur wenn kein Template
        {
          fprintf(target,"%s",partBuf);
          fprintf(target,"\n");
        }
        break;
      }

      // Zweite Möglichkeit, es kommt nichts mehr
      if (*(editText+look) == CR && *(editText+look+1) == LF && *(editText+look+2) == '\0')
      {
        if (strstr(partBuf,"Template>!<") == NULL) // Nur wenn kein Template
        {
          fprintf(target,"%s",partBuf);
          fprintf(target,"\n");
        }
        break;
      }

      // Sonst dritte Möglichkeit,es geht weiter
      if (strstr(partBuf,"Template>!<") == NULL) // Nur wenn kein Template
      {
        fprintf(target,"%s",partBuf);
        fprintf(target,"\n");
      }

      look++; // Steht nun auf LF
      look++; // Steht nun auf nächstem Zeilenanfang, bereit für nächten Schleifendurchlauf
    }

    wfree(partBuf);
  } // Text ist geschrieben
}

void FAR PASCAL DoNamingOfHeadPane(int channel)
{
  POINT nameLeftTop;
  POINT nameLeftBottom;
  POINT nameRightBottom;
  POINT nameRightTop;
  RECT paintRect;
  DWORD backupColor;
  HFONT backupFont;
  int tinyHBand = GetSystemMetrics(SM_CXFRAME);
  HDC hdc = GetWindowDC(hWndMain);

  // Mon.auNice... - Koordinaten sind 1 Pixel nach innen verschoben gemerkt!

  nameLeftTop.x     = Mon.auNiceRightTop.x+1 + 2*tinyHBand;
  nameLeftTop.y     = Mon.auNiceLeftTop.y-1;
  nameLeftBottom.x  = nameLeftTop.x;
  nameLeftBottom.y  = Mon.auNiceLeftBottom.y+1;
  nameRightBottom.x = Mon.autistRightTop.x - 2*tinyHBand;;
  nameRightBottom.y = nameLeftBottom.y;
  nameRightTop.x    = nameRightBottom.x;
  nameRightTop.y    = nameLeftTop.y;

  DrawOuterTextPane(hdc,nameLeftTop,nameLeftBottom,nameRightBottom,nameRightTop);

  paintRect.left   = nameLeftTop.x;
  paintRect.top    = nameLeftTop.y;
  paintRect.right  = nameRightBottom.x;
  paintRect.bottom = nameRightBottom.y;
  backupColor = SetBkColor(hdc,RGB(192,192,192));
  SetBkMode(hdc,OPAQUE);
  backupFont = SelectObject(hdc,WorkFont);
  DrawText(hdc,Mon.DCNames[channel],-1,&paintRect,DT_CENTER|DT_VCENTER|DT_SINGLELINE);
  SelectObject(hdc,backupFont);
  SetBkColor(hdc,backupColor);
  ReleaseDC(hWndMain,hdc);
}

// Übermalt die temporär auftauchende Namespane in der Kopfleiste wieder
void FAR PASCAL KillNamingOfHeadPane(void)
{
  POINT nameLeftTop;
  POINT nameLeftBottom;
  POINT nameRightBottom;
  POINT nameRightTop;
  int tinyHBand = GetSystemMetrics(SM_CXFRAME);
  HRGN hRegion;
  HBRUSH hBrush = CreateSolidBrush(RGB(192,192,192));
  HDC hdc = GetWindowDC(hWndMain);

  // Mon.auNice... - Koordinaten sind 1 Pixel nach innen verschoben gemerkt!

  nameLeftTop.x     = Mon.auNiceRightTop.x+1 + 2*tinyHBand;
  nameLeftTop.y     = Mon.auNiceLeftTop.y-1;
  nameLeftBottom.x  = nameLeftTop.x;
  nameLeftBottom.y  = Mon.auNiceLeftBottom.y+1;
  nameRightBottom.x = Mon.autistRightTop.x - 2*tinyHBand;;
  nameRightBottom.y = nameLeftBottom.y;
  nameRightTop.x    = nameRightBottom.x;
  nameRightTop.y    = nameLeftTop.y;

  hRegion = CreateRectRgn(nameLeftTop.x-1,nameLeftTop.y-1,nameRightBottom.x+1,nameRightBottom.y+1);
  FillRgn(hdc,hRegion,hBrush);

  ReleaseDC(hWndMain,hdc);
  DeleteObject(hRegion);
  DeleteObject(hBrush);
}

void FAR PASCAL FindSelectionFromSubstring(int *beg,int *end,LPSTR string,LPSTR sub)
{
  char *tosub;

  if ((tosub=strstr(string,sub)) == NULL) // Substring kommt gar nicht vor
  {
    *beg = *end = 0;
  }
  else
  {
    *beg = (int)(tosub - string);
    *end = *beg + strlen(sub);
  }
}

// Hilfsfunktion für OBSERVEMsgProc
// Liefert denjenigen Integer zurück, der in arg1 enthalten ist,nicht aber in arg2
// lenArg1 und lenArg2 geben die Größe der Arrays in Bytes an
// *arg1 muß der Zeiger auf das umfangreichere der beiden Arrays sein
int FAR PASCAL IntInArg1NotInArg2(int *arg1,int lenArg1,int *arg2,int lenArg2)
{
  int i;
  int j;
  int nSlotsArg1 = lenArg1/2;
  int nSlotsArg2 = lenArg2/2;
  int retVal = -1; // Bedeutet: keiner gefunden

  for(i=0;i < nSlotsArg1;i++)
  {
    BOOL foundOnOther = FALSE;

    for(j=0;j < nSlotsArg2;j++)
    {
      if (arg1[i] == arg2[j])
      {
        foundOnOther=TRUE;
      }
    }

    if (foundOnOther == FALSE) // Ein arg1[i] gefunden, das nicht in arg2 existiert
    {
      retVal = arg1[i];
      break;
    }
  }

  return retVal;
}

// Spannbreite zwischen zwei Zahlen, egal welche Vorzeichen
double FAR PASCAL Range(double value1,double value2)
{
  double bigOne;
  double smallOne;

  if (value1 > value2)
  {
    bigOne = value1;
    smallOne = value2;
  }
  else if (value2 > value1)
  {
    bigOne = value2;
    smallOne = value1;
  }
  else
  {
    return 0.;
  }

  if (bigOne > 0.)
  {
    return(bigOne - smallOne);
  }
  else if (bigOne < 0.)
  {
    return(-smallOne + bigOne);
  }
  else // bigOne == 0
  {
    return(-smallOne);
  }
}

// Stelle fest, worauf das Programm läuft, Returnwert besagt, ob Lauf prinzipiell möglich
BOOL FAR PASCAL InfoWhatMachineAndSystem(void)
{
  WORD version = GetVersion();
  DWORD winFlags = GetWinFlags();

  if (winFlags & WF_CPU086 ||
      winFlags & WF_CPU186 ||
      winFlags & WF_CPU286)
  {
    LoadString(hInst,IDS_TEST_WRONGMACHINE,szString,sizeof(szString));
    MessageBox(NULL,szString,"Maschinenidentifikation",MB_OK|MB_TASKMODAL);
    return FALSE;
  }

  if (LOBYTE(version) != 0x03)
  {
    LoadString(hInst,IDS_TEST_WRONGWINDOWS,szString,sizeof(szString));
    MessageBox(NULL,szString,"Windows-Versionsidentifikation",MB_OK|MB_TASKMODAL);
    return FALSE;
  }

  if (!(winFlags & WF_ENHANCED))
  {
    LoadString(hInst,IDS_TEST_WRONGMODE,szString,sizeof(szString));
    MessageBox(NULL,szString,"Windows-Modus",MB_OK|MB_TASKMODAL);
    return FALSE;
  }

  // Lauffähigkeit jetzt sichergestellt, Warnung vor 386ern scheint angebracht

  if (winFlags & WF_CPU386)
  {
    LoadString(hInst,IDS_TEST_386NOFUN,szString,sizeof(szString));
    MessageBox(NULL,szString,"Veraltete Maschine",MB_OK|MB_ICONEXCLAMATION|MB_TASKMODAL);
  }

  return TRUE;
}

// Ist virtuelle Speicherverwaltung aus ? wichtige Frage !
BOOL FAR PASCAL InfoIsVirtMemOff(void)
{
  FILE *sysini;
  char txtBuf[256];
  BOOL PageConflict = FALSE;
  BOOL PermConflict = FALSE;
  BOOL PageConflictResolved = FALSE;

  GetWindowsDirectory(txtBuf,sizeof(txtBuf));
  strcat(txtBuf,"\\system.ini");

  sysini=fopen(txtBuf,"r");

  if (sysini == NULL)
  {
    InternalErrorBox(IDS_ERR_NOACCESSTOSYSINI);
    return FALSE;
  }

  while (fgets(txtBuf,256,sysini))
  {
    if (strstr(txtBuf,"PagingFile"))
    {
      PageConflict = TRUE;
    }

    if (strstr(txtBuf,"PermSwapDosDrive"))
    {
      PermConflict = TRUE;
    }
  }

  rewind(sysini);

  while (fgets(txtBuf,256,sysini))
  {
    if (strstr(txtBuf,"Paging=no") || strstr(txtBuf,"Paging=0"))
    {
      PageConflictResolved = TRUE;
    }
  }

  fclose(sysini);

  if (PageConflict && PageConflictResolved)
  {
    PageConflict = FALSE;
  }

  if (PageConflict || PermConflict)
  {
    return FALSE;
  }
  else
  {
    return TRUE;
  }
}

// Speichertest (nur als vorläufige Information)
void FAR PASCAL InfoAvailableMemory(void)
{
  DWORD i;
  GLOBALHANDLE hTest;

  sprintf(Feedback,"   Speicherverwaltung");
  OutputMessage(Feedback);

  for(i=100000;;i-=1000)
  {
    hTest=GlobalAlloc(GMEM_FIXED|GMEM_ZEROINIT,i*SIZEOFDATACHUNK); // i Meßdatensätze

    if (hTest != NULL)
    {
      GlobalFree(hTest);
      MemAvail4NRecords = (i / 10) * 9; // Sicherheitshalber nur zu 90 % ausschöpfen
      break;
    }
  }
}

// Wird beim Hochfahren des Programms mit C4RM_ALLOC aufgerufen, um den benötigten
// Datenspeicher zu allozieren (soviel vorhanden ist bzw. maximal für 100000 Messungen).
// Beim Runterfahren (WM_CLOSE) mit C4RM_FREE
// Bevor die Aufzeichnung beginnt mit C4RM_QUESTPREPARED
// Nach dem Rausschreiben auf Datei wird wie beim Programmstart verfahren
BOOL FAR PASCAL Care4RecordingMemory(C4RMtype toDo)
{
  int channel;

  if (toDo == C4RM_FREE) // Free
  {
    if (RCRD_TcHourHandle != NULL)
    {
      GlobalUnWire(RCRD_TcHourHandle);
      GlobalFree(RCRD_TcHourHandle);
    }

    if (RCRD_TcMinuHandle != NULL)
    {
      GlobalUnWire(RCRD_TcMinuHandle);
      GlobalFree(RCRD_TcMinuHandle);
    }

    if (RCRD_TcSecoHandle != NULL)
    {
      GlobalUnWire(RCRD_TcSecoHandle);
      GlobalFree(RCRD_TcMinuHandle);
    }

    if (RCRD_TcFramHandle != NULL)
    {
      GlobalUnWire(RCRD_TcFramHandle);
      GlobalFree(RCRD_TcFramHandle);
    }

    if (RCRD_CheckSumHandle != NULL)
    {
      GlobalUnWire(RCRD_CheckSumHandle);
      GlobalFree(RCRD_CheckSumHandle);
    }

    for(channel=0;channel < CHANNELMAX;channel++)
    {
      if (RCRD_DataHandles[channel] != NULL)
      {
        GlobalUnWire(RCRD_DataHandles[channel]);
        GlobalFree(RCRD_DataHandles[channel]);
      }
    }

    RCRDItemsIn = 0;
    return TRUE;
  }
  else if (toDo == C4RM_QUESTPREPARED)
  {
    if (!RCRD_TcHourRecords ||
        !RCRD_TcMinuRecords ||
        !RCRD_TcSecoRecords ||
        !RCRD_TcFramRecords ||
        !RCRD_CheckSum)
    {
      return FALSE;
    }

    for(channel=0;channel < CHANNELMAX;channel++)
    {
      if (!RCRD_DataRecords[channel])
      {
        return FALSE;
      }
    }

    return TRUE;
  }
  else if (toDo == C4RM_ALLOC)
  {
    // Alloziere (immer gleich für alle 16 Kanäle). InfoAvailableMemory wurde schon aufgerufen
    // und hat in MemAvail4NRecords vermerkt, wieviele Meßdatensätze sicher alloziert werden können
    // Daher sollte die Allozierung kaum fehlschlagen

    // Vektor Timecode Stunden
    RCRD_TcHourHandle=GlobalAlloc(GMEM_FIXED|GMEM_ZEROINIT,MemAvail4NRecords*sizeof(int));
    if (RCRD_TcHourHandle == NULL) return FALSE;
    RCRD_TcHourRecords=(int huge *)GlobalWire(RCRD_TcHourHandle);
    if (RCRD_TcHourRecords == NULL) return FALSE;

    // Vektor Timecode Minuten
    RCRD_TcMinuHandle=GlobalAlloc(GMEM_FIXED|GMEM_ZEROINIT,MemAvail4NRecords*sizeof(int));
    if (RCRD_TcMinuHandle == NULL) return FALSE;
    RCRD_TcMinuRecords=(int huge *)GlobalWire(RCRD_TcMinuHandle);
    if (RCRD_TcMinuRecords == NULL) return FALSE;

    // Vektor Timecode Sekunden
    RCRD_TcSecoHandle=GlobalAlloc(GMEM_FIXED|GMEM_ZEROINIT,MemAvail4NRecords*sizeof(int));
    if (RCRD_TcSecoHandle == NULL) return FALSE;
    RCRD_TcSecoRecords=(int huge *)GlobalWire(RCRD_TcSecoHandle);
    if (RCRD_TcSecoRecords == NULL) return FALSE;

    // Vektor Timecode Frames
    RCRD_TcFramHandle=GlobalAlloc(GMEM_FIXED|GMEM_ZEROINIT,MemAvail4NRecords*sizeof(int));
    if (RCRD_TcFramHandle == NULL) return FALSE;
    RCRD_TcFramRecords=(int huge *)GlobalWire(RCRD_TcFramHandle);
    if (RCRD_TcFramRecords == NULL) return FALSE;

    // Vektor Checksummen
    RCRD_CheckSumHandle=GlobalAlloc(GMEM_FIXED|GMEM_ZEROINIT,MemAvail4NRecords*sizeof(int));
    if (RCRD_CheckSumHandle == NULL) return FALSE;
    RCRD_CheckSum=(int huge *)GlobalWire(RCRD_CheckSumHandle);
    if (RCRD_CheckSum == NULL) return FALSE;

    // Die Datenvektoren
    for(channel=0;channel < CHANNELMAX;channel++)
    {
      RCRD_DataHandles[channel]=GlobalAlloc(GMEM_FIXED|GMEM_ZEROINIT,MemAvail4NRecords*sizeof(float));
      if (RCRD_DataHandles[channel] == NULL) return FALSE;
      RCRD_DataRecords[channel]=(float huge *)GlobalWire(RCRD_DataHandles[channel]);
      if (RCRD_DataRecords[channel] == NULL) return FALSE;
    }

    // Zähler auf Null setzen
    RCRDItemsIn = 0;
  }

  return TRUE;
}

void FAR PASCAL WriteoutRecordingSession(void)
{
  DWORD run;
  int currentColumn;
  int chan;
  FILE *data;

  // Muß noch ein Dateiname bestimmt werden ?
  if (!strcmp(Mon.WriteFileName,"NONAME"))
  {
    SendMessage(hWndMain,WM_COMMAND,IDM_E_NAMINGDATAFILE,0L);
  }

  data=fopen(Mon.WriteFileName,"w");

  if (data == NULL)
  {
    InternalErrorBox(IDS_ERR_CANNOTOPENDATAFILE);
    return;
  }

  sprintf(Feedback,"   Daten schreiben");
  OutputMessage(Feedback);

  // Kopfzeile

  fprintf(data,"TC\t"); // Immer
  currentColumn = 0;

  for(chan=0;chan < CHANNELMAX;chan++)
  {
    if (Mon.DCChaMarked4Write[chan])
    {
      fprintf(data,"%s",Mon.DCNames[chan]);

      if (currentColumn+1 != Mon.nDataPanesWriteMarked)
      {
        fprintf(data,"\t");
      }
      else
      {
        fprintf(data,"\n");
      }

      currentColumn++;
    }
  }

  for(run=0;run < RCRDItemsIn;run++)
  {
    currentColumn = 0;

    // Schreibe TC
    fprintf(data,
            "%02.0f:%02.0f:%02.0f:%02.0f\t",
            (float)RCRD_TcHourRecords[run],
            (float)RCRD_TcMinuRecords[run],
            (float)RCRD_TcSecoRecords[run],
            (float)RCRD_TcFramRecords[run]);

    // Schreibe Daten
    for(chan=0;chan < CHANNELMAX;chan++)
    {
      if (Mon.DCChaMarked4Write[chan])
      {
        fprintf(data,"%.2f",RCRD_DataRecords[currentColumn][run]);

        if (currentColumn+1 != Mon.nDataPanesWriteMarked)
        {
          fprintf(data,"\t");
        }

        currentColumn++;
      }
    }

    if (run != RCRDItemsIn-1)
    {
      fprintf(data,"\n");
    }
  }

  fclose(data);
  // Speicher für die nächste Aufzeichnung vorbereiten
  PostMessage(hWndMain,WM_COMMAND,IDM_INTERNAL_MEMSETUP,0L);
}

// Communication functions ----------------------------------------------------------------------

// Sorgt dafür, daß die serielle Schnittstelle initialisiert ist
BOOL FAR PASCAL ComCare4Protocol(void)
{
  if (Com.SetUpOK)
  {
    return TRUE;
  }

  if (!strcmp(Com.ComName,"NO!!")) // Eingestellte Schnitt ließ sich nicht probehalber öffnen
  {
    Com.UsingComPort = NOTHING;
    return FALSE;
  }

  Com.ID = OpenComm(Com.ComName,SZRECEIVEQUEUE,SZTRANSMITQUEUE);

  if (Com.ID < 0)
  {
    InternalErrorBox(GetErrIDS4OpenComm(Com.ID));
    Com.UsingComPort = NOTHING;
  }
  else
  {
    if (!strcmp(Com.ComName,"COM1"))
    {
      Com.UsingComPort = USINGCOM1;
    }
    else if (!strcmp(Com.ComName,"COM2"))
    {
      Com.UsingComPort = USINGCOM2;
    }
  }

  // Set com event mask and get event mask address.
  Com.ComEvent=SetCommEventMask(Com.ID,EV_RXCHAR); // Event == Empfang irgendeines Zeichens

  // Configure COM2
  if (GetCommState(Com.ID,&Com.TheDCB) != 0)
  {
    InternalErrorBox(IDS_ERR_COM_GETCOMMSTATE);
    CloseComm(Com.ID);
    return FALSE;
  }

  Com.TheDCB.BaudRate=19200;
  Com.TheDCB.ByteSize=8;
  Com.TheDCB.Parity=NOPARITY;
  Com.TheDCB.StopBits=ONESTOPBIT;
  Com.TheDCB.fBinary=1;
  Com.TheDCB.fRtsDisable=0;
  Com.TheDCB.fParity=0;
  Com.TheDCB.fOutxCtsFlow=1;
  Com.TheDCB.fOutxDsrFlow=1;
  Com.TheDCB.fDtrDisable=0;
  Com.TheDCB.fOutX=0;
  Com.TheDCB.fInX=0;
  Com.TheDCB.fPeChar=0;
  Com.TheDCB.fNull=0;
  Com.TheDCB.fChEvt=0;
  Com.TheDCB.fDtrflow=1; // In meiner windows.h steht abweichend zur Docu nicht fDtrFlow
  Com.TheDCB.fRtsflow=1; // In meiner windows.h steht abweichend zur Docu nicht fRtsFlow
  Com.TheDCB.XonLim=0;   // wörtlich zu nehmen !
  Com.TheDCB.XoffLim=0;  // wörtlich zu nehmen !

  if (SetCommState(&Com.TheDCB) != 0)
  {
    InternalErrorBox(IDS_ERR_COM_SETCOMMSTATE);
    CloseComm(Com.ID);
    return FALSE;
  }

  Com.SetUpOK = TRUE;
  return TRUE;
}

int FAR PASCAL GetErrIDS4OpenComm(int ErrCode)
{
  switch (ErrCode)
  {
    case IE_BADID:
         return IDS_ERR_COM_IE_BADID;

    case IE_BAUDRATE:
         return IDS_ERR_COM_IE_BAUDRATE;

    case IE_BYTESIZE:
         return IDS_ERR_COM_IE_BYTESIZE;

    case IE_DEFAULT:
         return IDS_ERR_COM_IE_DEFAULT;

    case IE_HARDWARE:
         return IDS_ERR_COM_IE_HARDWARE;

    case IE_MEMORY:
         return IDS_ERR_COM_IE_MEMORY;

    case IE_NOPEN:
         return IDS_ERR_COM_IE_NOPEN;

    case IE_OPEN:
         return IDS_ERR_COM_IE_OPEN;

    default: return IDS_ERR_COM_UNKNOWN;
  }
}

BOOL FAR PASCAL CanComPortBeOpened(LPSTR name)
{
  int nCom;
  BOOL openable;

  nCom=OpenComm(name,SZRECEIVEQUEUE,SZTRANSMITQUEUE);

  if (nCom < 0)
    openable=FALSE;
  else
    openable=TRUE;

  CloseComm(nCom);

  return openable;
}

// Prüft, ob sich die ersten sumBytes Bytes in einem Puffer tatsächlich zu checkInt summieren
BOOL FAR PASCAL CheckChecksum(BYTE *bufPointer,int sumBytes,int checkInt)
{
  int run;
  int actualSum = 0;

  for(run=0;run < sumBytes;run++)
    actualSum+= *(bufPointer+run);

  if (actualSum == checkInt)
    return TRUE;
  else
    return FALSE;
}

SIM97CMD FAR PASCAL ChunkIsASim97Command(LPSTR chunk)
{
  // Die Kommandos sind wie Datenchunks 74 Byte lang, beginnen mit einem Text und sind
  // bis zum Ende des Chunks mit '#'-Zeichen aufgefüllt. Nach dem Ende des Textteils kommt
  // ein String-Terminator '\0'. Aus Performance-Gründen werden nur die ersten 20 Bytes
  // verglichen
  if (!_fmemcmp(chunk,MonitorCMD_StartRecording,20))
    return STARTRECORDING;

  if (!_fmemcmp(chunk,MonitorCMD_EndRecording,20))
    return ENDRECORDING;

  return ZEROMEANSNOCOMMAND; // which is 0
}

void FAR PASCAL ExecuteSim97Command(SIM97CMD cmd,LPSTR chunk)
{
  char stringPart[54];

  switch(cmd)
  {
    case STARTRECORDING:
         {
           long run;
           char targetFileName[13];
           // Aus dem Kommandochunk den Dateinamen herausholen und verwenden
           // Ab dem 21. Byte beginnt der Dateiname, er ist '\0'-terminiert
           lstrcpy(targetFileName,chunk+20); // Übernahme des Namens
           _fstrlwr(targetFileName);
           lstrcpy(Mon.WriteFileName,"c:\\monitor\\data\\"); // Anderes Verzeichnis bei Fernsteuerung nicht erlaubt
           lstrcat(Mon.WriteFileName,targetFileName);

           // Remot-Start der Zusatzaufgabe über Kabel zu DSR-Leitung am DAC-4 Kanal 4
           // Genügend lang Auf +10 Volt ziehen
           for(run=0;run < 50000;run++)
           {
             KolterDAC4LogicalZero(3); // 4. Kanal (unterste Buchse)
           }

           // Davon sollte Incar4 was mitgekriegt haben, obwohl es nur pollt
           // Zurück zu 0 Volt
           KolterDAC4LogicalNeutral(3); // 4. Kanal (unterste Buchse)

           PostMessage(hWndMain,WM_COMMAND,IDM_E_MANUSTART,0L);
         }
         break;

    case ENDRECORDING:
         KolterDAC4Burst(0.,0.,0.,0.); // Ruhe am Versuchsende
         TransmissionEndDetected = TRUE;
         break;
  }
}

// Compiler für das Weltwissen; bringt das Wissen aus dem Quelltext in eine schnell -------------
// zugängliche Form, die im Array CompiledKnowledge abgelegt wird

// Hauptfunktion des Compilers
BOOL FAR PASCAL KCCompileKnowledge(LPSTR program)
{
  commandType cmd;

  // Verwerfe mögliche alte Wissensrepräsentation
  memset(CompiledKnowledge,0x00,sizeof(CompiledKnowledge));

  // Schmeiße Kommentare aus dem Programmtext raus
  KCReplaceCommentsWithBlanks(program);

  CompilerProgramLength = strlen(program);
  CompilerPos = 0;
  CompilerPAlias = program;

  CompilerLog = fopen("COMPILER.LOG","w");

  if (CompilerLog == NULL)
  {
    InternalErrorBox(IDS_ERR_OPENCOMPILERLOG);
    return FALSE;
  }

  CompilerStandsOnLine = 0;

  // Hauptschleife
  while (CompilerPos < CompilerProgramLength)
  {
    cmd = KCGetCommand();
    CompilerStandsOnLine++; // Im Fehlerfall ist das die Nummer des schuldigen Kommandos

    if (cmd >= NONCMD_UNKNOWNCOMMAND)
    {
      KCKnoCompilerErrorMessage(cmd);

      // NONCMD_NORMALPROGRAMEND weicht vom Schema ab: es ist kein Fehler
      if (cmd == NONCMD_NORMALPROGRAMEND)
      {
        break;
      }

      fclose(CompilerLog);

      // Verwerfe compilierte Wissensbasis
      memset(CompiledKnowledge,0x00,sizeof(CompiledKnowledge));

      return FALSE; // Compilierung erfolglos, CompilerStandsOnLine Nummer des Fehlerkommandos
    }
    else
    {
      KCMemorizeUnproofedLine(); // Damit sie im Text zum Zeigen wiedergefunden werden kann

      if (FALSE == KCDoCommand(cmd))
      {
        fclose(CompilerLog);
        return FALSE;
      }
    }
  }

  fclose(CompilerLog);
  return TRUE; // Compilierung erfolgreich
}

// Findet die enum-Nummer des nächsten Kommandos oder enum-Nummer eines Fehlers
commandType FAR PASCAL KCGetCommand(void)
{
  char part[256];

  while (*CompilerPAlias && KCiswhite(*CompilerPAlias))
  {
    ++CompilerPAlias;
    ++CompilerPos;
  }

  if (*CompilerPAlias == '\0')
  {
    return NONCMD_NORMALPROGRAMEND;
  }

  if (isalpha(*CompilerPAlias)) // Muß ein Kommando sein
  {
    int lookAhead = 0;
    int i;

    memset(part,0x00,sizeof(part));

    while(*(CompilerPAlias+lookAhead) != '\0' && (!KCiswhite(*(CompilerPAlias+lookAhead))))
    {
      if (lookAhead == 254) // So groß ist kein Kommandoname, Fehler
      {
        lstrcpy(CompilerErrDrop,part); // Seiteneffekt: Drop string causing error
        return NONCMD_UNKNOWNCOMMAND;
      }

      part[lookAhead] = *(CompilerPAlias+lookAhead); // Immer ein Zeichen mehr betrachten...

      for(i=0;i < NONCMD_ENDOFCOMMANDTABLE;i++) // ...und nachsehen, ob es kein Kommando ist
      {
        if (!strcmp(part,commandstable[i].String)) // gefunden
        {
          return(commandstable[i].InternalNumber);
        }
      }

      lookAhead++; // ein Zeichen mehr betrachten
    }
  }

  lstrcpy(CompilerErrDrop,part); // Seiteneffekt: Drop string causing error
  return NONCMD_UNKNOWNCOMMAND;
}

// Führt ein Kommando aus
BOOL FAR PASCAL KCDoCommand(commandType cmd)
{
  switch (cmd)
  {
    case CMD_ONLYATEMPLATE:
         return(KCEatTemplate());

    case CMD_DATA:
         return(KCCmdData());

    case CMD_SIGNEDDIFFERENCE:
         return(KCCmdSignedDifference());
         break;

    case CMD_ABSOLUTEDIFFERENCE:
         return(KCCmdAbsoluteDifference());
         break;

    default:
         InternalErrorBox(IDS_ERR_FNCDOCOMMANDERR);
         return FALSE;
  }
}

// Hilfsfunktionen für den Wissenscompiler

BOOL FAR PASCAL KCiswhite(char c)
{
  if (c == ' ' || c == '\t' || c == '\r' || c == '\n')
  {
    return TRUE;
  }
  else
  {
    return FALSE;
  }
}

void FAR PASCAL KCeatString(char *string)
{
  int goAhead = strlen(string);
  CompilerPAlias += goAhead;
  CompilerPos += goAhead;
}

void FAR PASCAL KCeatWhite(void)
{
  while (*CompilerPAlias && KCiswhite(*CompilerPAlias))
  {
    ++CompilerPAlias;
    ++CompilerPos;
  }
}

// Holt nächstes Token
BOOL FAR PASCAL KCfetchToken(char *strTarget,char cEnd,char cForbidden,int trgSize)
{
  int i;
  int maxRead = trgSize - 1;

  for(i=0;i < maxRead;i++)
  {
    if (*CompilerPAlias == '\0')
    {
      CompilerPos = CompilerProgramLength; // Simuliere Programmende
      fprintf(CompilerLog,"Fehler: unerwartet Dateiende erreicht\n");
      return FALSE;
    }

    if (*CompilerPAlias == cForbidden)
    {
      CompilerPos = CompilerProgramLength; // Simuliere Programmende
      return FALSE;
    }

    if (*CompilerPAlias != cEnd)
    {
      strTarget[i] = *CompilerPAlias;
      CompilerPAlias++;
      CompilerPos++;
    }
    else
    {
      CompilerPAlias++;
      CompilerPos++;
      return TRUE;
    }
  }
}

// Überliest ein '(' oder '{'
BOOL FAR PASCAL KCfetchOpener(char cOpen)
{
  if (*CompilerPAlias != cOpen)
  {
    if (cOpen == '(')
    {
      KCKnoCompilerErrorMessage(NONCMD_EXPROUNDOPENER);
    }
    else if (cOpen == '{')
    {
      KCKnoCompilerErrorMessage(NONCMD_EXPCURLYOPENER);
    }
    else
    {
      InternalErrorBox(IDS_ERR_FETCHOPENEREXPECT);
    }

    CompilerPos = CompilerProgramLength; // Simuliere Programmende
    return FALSE;
  }
  else
  {
    CompilerPAlias++;
    CompilerPos++;
    return TRUE;
  }
}

// Schreibt Fehlermeldung in die Logdatei
void FAR PASCAL KCKnoCompilerErrorMessage(commandType noncmd)
{
  // Umweg nötig, weil die noncmd vom Typ commandType sind
  WORD errStringResource;

  switch (noncmd)
  {
    case NONCMD_UNKNOWNCOMMAND:
         errStringResource = IDS_KCERR_UNKNOWNCOMMAND;
         break;

    case NONCMD_NORMALPROGRAMEND:
         errStringResource = IDS_KCERR_NORMALPROGRAMEND;
         break;

    case NONCMD_EXPROUNDOPENER:
         errStringResource = IDS_KCERR_EXPROUNDOPENER;
         break;

    case NONCMD_EXPCURLYOPENER:
         errStringResource = IDS_KCERR_EXPCURLYOPENER;
         break;

    case NONCMD_ARGLISTCORRUPTED:
         errStringResource = IDS_KCERR_ARGLISTCORRUPTED;
         break;

    case NONCMD_ARG1MORETHANEIGHT:
         errStringResource = IDS_KCERR_ARG1MORETHANEIGHT;
         break;

    case NONCMD_ARG2MORETHANEIGHT:
         errStringResource = IDS_KCERR_ARG2MORETHANEIGHT;
         break;

    case NONCMD_ARG2MUSTBEINT:
         errStringResource = IDS_KCERR_ARG2MUSTBEINT;
         break;

    case NONCMD_ARG3MUSTBEINTORFLOAT:
         errStringResource = IDS_KCERR_ARG3MUSTBEINTORFLOAT;
         break;

    case NONCMD_ARG4MUSTBEINTORFLOAT:
         errStringResource = IDS_KCERR_ARG4MUSTBEINTORFLOAT;
         break;

    case NONCMD_SOURCECHANUMOUTOFRANGE:
         errStringResource = IDS_KCERR_SOURCECHANUMOUTOFRANG;
         break;

    case NONCMD_ARG1MUSTBERAWDATA:
         errStringResource = IDS_KCERR_ARG1MUSTBERAWDATA;
         break;

    case NONCMD_ARG2MUSTBERAWDATA:
         errStringResource = IDS_KCERR_ARG2MUSTBERAWDATA;
         break;

    case NONCMD_FORGOTTENARROWSYMBOL:
         errStringResource = IDS_KCERR_FORGOTTENARROWSYMBOL;
         break;

    case NONCMD_ARG6MUSTBEINTORFLOAT:
         errStringResource = IDS_KCERR_ARG6MUSTBEINTORFLOAT;
         break;

    case NONCMD_ARG7MUSTBEINTORFLOAT:
         errStringResource = IDS_KCERR_ARG7MUSTBEINTORFLOAT;
         break;

    default:
         errStringResource = IDS_KCERR_UNKNOWN;
  }

  LoadString(hInst,errStringResource,szString,sizeof(szString));
  fprintf(CompilerLog,"Wissensbasis: %s\n",szString);

  // Wenn alles ok, dann halt einfach die Schnauze
  if (noncmd != NONCMD_NORMALPROGRAMEND)
  {
    char msg[128];
    sprintf(msg,"Weltwissen - Fehler in Kommando %d",CompilerStandsOnLine);
    MessageBox(NULL,szString,msg,MB_OK|MB_TASKMODAL);
  }

  CompilerPos = CompilerProgramLength; // Simuliere Programmende
}

void FAR PASCAL KCMemorizeUnproofedLine(void)
{
  int i;
  char *pBegOfNext;

  memset(CompilerNextUnproofedLine,0x00,256);
  pBegOfNext = CompilerPAlias;

  for(i=0;i < 255;i++) // 255. Slot bleibt auf jeden Fall '\0'
  {
    if (*(pBegOfNext+i) == '\0' || *(pBegOfNext+i) == '\r')
    {
      *(CompilerNextUnproofedLine+i) = '\0';
      break;
    }
    else
    {
      *(CompilerNextUnproofedLine+i) = *(pBegOfNext+i);
    }
  }
}

// Wird verwendet, wenn sich die gemerkte Zeile als Fehlerträger herausgestellt hat
void FAR PASCAL KCCopyErrorContext(void)
{
  lstrcpy(CompilerErrDrop,CompilerNextUnproofedLine);
}

// Killt Leerzeichen am Ende eines Strings
void FAR PASCAL KCkillTrailingBlanks(char *text)
{
  int look = 0;

  while (*(text+look) != '\0')
    look++;

  look--;

  while (*(text+look) == ' ' || *(text+look) == '\t' || *(text+look) == '\r' || *(text+look) == '\n')
  {
    *(text+look) = '\0';
    look--;
  }
}
// Ersetzt Kommentare durch Leerzeichen, das Textlayout bleibt damit erhalten
void FAR PASCAL KCReplaceCommentsWithBlanks(LPSTR program)
{
  LPSTR pAlias = program;

  while (*pAlias != '\0')
  {
    if (*pAlias == '/')
    {
      if (*(pAlias+1) == '/') // Comment to end of line
      {
        while(*pAlias != '\r')
        {
          if (*pAlias == '\0')
          {
            return;
          }
          else
          {
            *pAlias=' ';
          }

          pAlias++;
        }
      }
    }

    pAlias++;
  }
}



// Frischt die Argumentslots (KnoArg1 bis KnoArg6) mit 0x00 auf
void FAR PASCAL KCClearKnoArgs(void)
{
  memset(KnoArg1,0x00,256);
  memset(KnoArg2,0x00,256);
  memset(KnoArg3,0x00,256);
  memset(KnoArg4,0x00,256);
  memset(KnoArg5,0x00,256);
  memset(KnoArg6,0x00,256);
  memset(KnoArg7,0x00,256);
}

// Ist ein String als Integer interpretierbar
BOOL FAR PASCAL KCInterpretableInt(LPSTR txt)
{
  BOOL violation = FALSE;
  int run;
  int end = strlen(txt);

  for(run=0;run < end;run++)
  {
    if (run == 0 && *(txt+run) == '-')
      continue;

    if (!(*(txt+run) == '0' ||
        *(txt+run) == '1' ||
        *(txt+run) == '2' ||
        *(txt+run) == '3' ||
        *(txt+run) == '4' ||
        *(txt+run) == '5' ||
        *(txt+run) == '6' ||
        *(txt+run) == '7' ||
        *(txt+run) == '8' ||
        *(txt+run) == '9'))
    {
      violation = TRUE;
      break;
    }
  }

  if (violation)
  {
    return FALSE;
  }
  else
  {
    return TRUE;
  }
}

// Ist ein String als Integer oder wahlweise Float interpretierbar
BOOL FAR PASCAL KCInterpretableIntOrFloat(LPSTR txt)
{
  BOOL violation = FALSE;
  int run;
  int end = strlen(txt);
  int numOfPoints = 0;

  for(run=0;run < end;run++)
  {
    if (run == 0 && *(txt+run) == '-')
      continue;

    if (!(*(txt+run) == '0' ||
        *(txt+run) == '1' ||
        *(txt+run) == '2' ||
        *(txt+run) == '3' ||
        *(txt+run) == '4' ||
        *(txt+run) == '5' ||
        *(txt+run) == '6' ||
        *(txt+run) == '7' ||
        *(txt+run) == '8' ||
        *(txt+run) == '9' ||
        *(txt+run) == '.'))
    {
      violation = TRUE;
      break;
    }

    if (*(txt+run) == '.')
    {
      numOfPoints++;
    }
  }

  if (violation || numOfPoints > 1)
  {
    return FALSE;
  }
  else
  {
    return TRUE;
  }
}

// Get the source channel number of a slot in CompiledKnowledge which is named "name"
// ein return von -1 bedeutet, daß der KnowledgeNode keine Rohdaten beschreibt
int FAR PASCAL KCGetSrcChanOfSlotNamed(LPSTR name)
{
  int i;

  for(i=0;i < KNOWLEDGEMAX;i++)
  {
    if (!strcmp(CompiledKnowledge[i].nameInKnowledge,name)) // Name gefunden
    {
      if (CompiledKnowledge[i].iAm != DATA) // Aber keine Rohdatenbeschreibung
      {
        return -1;
      }
      else
      {
        return CompiledKnowledge[i].sourceChannel;
      }
    }
  }
}

// Implementierung der Kommandos ----------------------------------------------

// Kommentarmechanismus als Sonderfall einer Kommandobehandlung
BOOL FAR PASCAL KCEatTemplate(void)
{
  KCClearKnoArgs();
  // Alles bis zum Ende der Zeile gilt als Template-Text, der übersprungen wird...
  KCeatString("Template>!<"); // Reine Schaumalsoschautsaus-Zeile
  KCeatWhite();
  //... das geht so am einfachsten
  if (!KCfetchToken(KnoArg1,'\r','@',256))  // @ nehme ich als verbotenes char, wenn eigentlich nichts verboten ist
    return FALSE;

  fprintf(CompilerLog,"Skipping: Template>!<%s\n",KnoArg1);
  return TRUE;
}

// Kommando: Ankommende Daten anzeigen, gegebenenfalls für Renormierungen sorgen
BOOL FAR PASCAL KCCmdData(void)
{
  int freeSlot;

  KCClearKnoArgs();

  KCeatString("Data");
  KCeatWhite();

  if (!KCfetchOpener('('))
  {
    return FALSE;
    KCCopyErrorContext();
  }

  if (!KCfetchToken(KnoArg1,',',')',256)) // erwarteter Begrenzer und verbotenes Zeichen
    goto ARGLISTCORRUPTED;

  if (!KCfetchToken(KnoArg2,',',')',256))
    goto ARGLISTCORRUPTED;

  if (!KCfetchToken(KnoArg3,',',')',256))
    goto ARGLISTCORRUPTED;

  if (!KCfetchToken(KnoArg4,',',')',256))
    goto ARGLISTCORRUPTED;

  if (!KCfetchToken(KnoArg5,',',')',256))
    goto ARGLISTCORRUPTED;

  if (!KCfetchToken(KnoArg6,',',')',256))
    goto ARGLISTCORRUPTED;

  if (!KCfetchToken(KnoArg7,')',',',256))
    goto ARGLISTCORRUPTED;

  KCkillTrailingBlanks(KnoArg1); // Name des Kanals
  KCkillTrailingBlanks(KnoArg2); // Kanalnummer
  KCkillTrailingBlanks(KnoArg3); // Minimum
  KCkillTrailingBlanks(KnoArg4); // Maximum
  KCkillTrailingBlanks(KnoArg5); // Funktionsloses Symbol "-->"
  KCkillTrailingBlanks(KnoArg6); // Renormiertes Minimum
  KCkillTrailingBlanks(KnoArg7); // Renormiertes Maximum

  // Argumente prüfen

  // Name des Kanals
  if (strlen(KnoArg1) > 8)
  {
    KCKnoCompilerErrorMessage(NONCMD_ARG1MORETHANEIGHT);
    KCCopyErrorContext();
    return FALSE;
  }

  // Kanalnummer
  if (!KCInterpretableInt(KnoArg2))
  {
    KCKnoCompilerErrorMessage(NONCMD_ARG2MUSTBEINT);
    KCCopyErrorContext();
    return FALSE;
  }

  if (atoi(KnoArg2) < 0 || atoi(KnoArg2) > 15)
  {
    KCKnoCompilerErrorMessage(NONCMD_SOURCECHANUMOUTOFRANGE);
    KCCopyErrorContext();
    return FALSE;
  }

  // Minimum
  if (!KCInterpretableIntOrFloat(KnoArg3))
  {
    KCKnoCompilerErrorMessage(NONCMD_ARG3MUSTBEINTORFLOAT);
    KCCopyErrorContext();
    return FALSE;
  }

  // Maximum
  if (!KCInterpretableIntOrFloat(KnoArg4))
  {
    KCKnoCompilerErrorMessage(NONCMD_ARG4MUSTBEINTORFLOAT);
    KCCopyErrorContext();
    return FALSE;
  }

  // Funktionsloses Symbol "-->"
  if (strcmp(KnoArg5,"-->"))
  {
    KCKnoCompilerErrorMessage(NONCMD_FORGOTTENARROWSYMBOL);
    KCCopyErrorContext();
    return FALSE;
  }

  // Renormiertes Minimum
  if (!KCInterpretableIntOrFloat(KnoArg6))
  {
    KCKnoCompilerErrorMessage(NONCMD_ARG6MUSTBEINTORFLOAT);
    KCCopyErrorContext();
    return FALSE;
  }

  // Renormiertes Maximum
  if (!KCInterpretableIntOrFloat(KnoArg7))
  {
    KCKnoCompilerErrorMessage(NONCMD_ARG7MUSTBEINTORFLOAT);
    KCCopyErrorContext();
    return FALSE;
  }

  // Ersten freien Slot finden
  for(freeSlot=0;freeSlot < KNOWLEDGEMAX;freeSlot++)
  {
    if (CompiledKnowledge[freeSlot].iAm == 0) // 0 entspricht UNDEFINED
    {
      break; // freien Slot gefunden
    }
  }

  // Wissen einfügen

  // Wissen-Knotentyp
  CompiledKnowledge[freeSlot].iAm = DATA;
  // Wissen-Initiales plotMark
  CompiledKnowledge[freeSlot].plotMarked = FALSE;
  // Wissen-Initiales writeMark
  CompiledKnowledge[freeSlot].writeMarked = FALSE;
  // Wissen-Name
  lstrcpy(CompiledKnowledge[freeSlot].nameInKnowledge,KnoArg1);
  // Wissen-Quellkanäle
  CompiledKnowledge[freeSlot].sourceChannel = atoi(KnoArg2);
  // Wissen-Minimum
  CompiledKnowledge[freeSlot].minimum = atof(KnoArg3);
  // Wissen-Maximum
  CompiledKnowledge[freeSlot].maximum = atof(KnoArg4);
  // Wissen-Renormiertes Minimum
  CompiledKnowledge[freeSlot].data1Double = atof(KnoArg6);
  // Wissen-Renormiertes Maximum
  CompiledKnowledge[freeSlot].data2Double = atof(KnoArg7);

  // Fertig, Meldung in die Logdatei schreiben
  fprintf(CompilerLog,"Data(%s... understood\n",KnoArg1);

  return TRUE; // Weiter geht´s nur im Fehlerfall

ARGLISTCORRUPTED:
  KCKnoCompilerErrorMessage(NONCMD_ARGLISTCORRUPTED);
  KCCopyErrorContext();
  return FALSE;
}

// Differenz der Werte zweier Kanäle
BOOL FAR PASCAL KCCmdSignedDifference(void)
{
  int freeSlot;

  KCClearKnoArgs();

  KCeatString("SignedDifference");
  KCeatWhite();

  if (!KCfetchOpener('('))
  {
    return FALSE;
    KCCopyErrorContext();
  }

  if (!KCfetchToken(KnoArg1,',',')',256))
    goto ARGLISTCORRUPTED;

  if (!KCfetchToken(KnoArg2,')',',',256))
    goto ARGLISTCORRUPTED;

  KCkillTrailingBlanks(KnoArg1); // Name des ersten Kanals
  KCkillTrailingBlanks(KnoArg2); // Name des zweiten Kanals

  // Argumente prüfen

  // Name des ersten Kanals
  if (strlen(KnoArg1) > 8)
  {
    KCKnoCompilerErrorMessage(NONCMD_ARG1MORETHANEIGHT);
    KCCopyErrorContext();
    return FALSE;
  }

  if (KCGetSrcChanOfSlotNamed(KnoArg1) == -1) // Beschreibt keine Rohdaten
  {
    KCKnoCompilerErrorMessage(NONCMD_ARG1MUSTBERAWDATA);
    KCCopyErrorContext();
    return FALSE;
  }

  // Name des zweiten Kanals
  if (strlen(KnoArg2) > 8)
  {
    KCKnoCompilerErrorMessage(NONCMD_ARG2MORETHANEIGHT);
    KCCopyErrorContext();
    return FALSE;
  }

  if (KCGetSrcChanOfSlotNamed(KnoArg2) == -1) // Beschreibt keine Rohdaten
  {
    KCKnoCompilerErrorMessage(NONCMD_ARG2MUSTBERAWDATA);
    KCCopyErrorContext();
    return FALSE;
  }

  // Ersten freien Slot finden
  for(freeSlot=0;freeSlot < KNOWLEDGEMAX;freeSlot++)
  {
    if (CompiledKnowledge[freeSlot].iAm == 0) // 0 entspricht UNDEFINED
    {
      break; // freien Slot gefunden
    }
  }

  // Wissen einfügen

  // Wissen-Knotentyp
  CompiledKnowledge[freeSlot].iAm = SIGNEDDIFFERENCE;
  // Wissen-Initiales plotMark
  CompiledKnowledge[freeSlot].plotMarked = FALSE;
  // Wissen-Initiales writeMark
  CompiledKnowledge[freeSlot].writeMarked = FALSE;
  // Wissen-Name
  {
    char synthName[9]; // Synthetischer Name

    synthName[0] = 'S';
    synthName[1] = 'D';
    synthName[2] = KnoArg1[0];
    synthName[3] = KnoArg1[1];
    synthName[4] = KnoArg1[2];
    synthName[5] = KnoArg2[0];
    synthName[6] = KnoArg2[1];
    synthName[7] = KnoArg2[2];
    synthName[8] = '\0';

    lstrcpy(CompiledKnowledge[freeSlot].nameInKnowledge,synthName);
  }
  // Wissen-Quellkanäle
  CompiledKnowledge[freeSlot].sourceChannel = KCGetSrcChanOfSlotNamed(KnoArg1);
  CompiledKnowledge[freeSlot].sourceChannel2nd = KCGetSrcChanOfSlotNamed(KnoArg2);
  // Wissen-Minimum
  // data1Double == renormiertes Minimum; data2Double == renormiertes Maximum
  CompiledKnowledge[freeSlot].minimum = 0; // Kleinste Mögliche Absolutabweichung
  // Wissen-Maximum
  {
    int index1 = KCGetSrcChanOfSlotNamed(KnoArg1);
    int index2 = KCGetSrcChanOfSlotNamed(KnoArg2);
    double biggerOfThe2Maxs;
    double smallerOfThe2Mins;
    double spanWidth;

    // Wir erwarten Vergleiche von Soll- und IstKurven, damit man die Abweichungen gut
    // Sieht, sollte man den in Y-Richtung darsdtellbaren bereich nicht zu groß wählen
    if (CompiledKnowledge[index1].data1Double < CompiledKnowledge[index2].data1Double)
      smallerOfThe2Mins = CompiledKnowledge[index1].data1Double;
    else
      smallerOfThe2Mins = CompiledKnowledge[index2].data1Double;

    if (CompiledKnowledge[index1].data2Double > CompiledKnowledge[index2].data2Double)
      biggerOfThe2Maxs = CompiledKnowledge[index1].data2Double;
    else
      biggerOfThe2Maxs = CompiledKnowledge[index2].data2Double;

    spanWidth = biggerOfThe2Maxs - smallerOfThe2Mins;
    // Wissen - minimal plotbarer Wert
    CompiledKnowledge[freeSlot].minimum = -spanWidth;
    // Wissen - maximal plotbarer Wert
    CompiledKnowledge[freeSlot].maximum = +spanWidth;
  }

  // Fertig, Meldung in die Logdatei schreiben
  fprintf(CompilerLog,"SignedDifference(%s,%s) understood\n",KnoArg1,KnoArg2);

  return TRUE; // Weiter geht´s nur im Fehlerfall

ARGLISTCORRUPTED:
  KCKnoCompilerErrorMessage(NONCMD_ARGLISTCORRUPTED);
  KCCopyErrorContext();
  return FALSE;
}

// Betrag der Differenz der Werte zweier Kanäle
BOOL FAR PASCAL KCCmdAbsoluteDifference(void)
{
  int freeSlot;

  KCClearKnoArgs();

  KCeatString("AbsoluteDifference");
  KCeatWhite();

  if (!KCfetchOpener('('))
  {
    return FALSE;
    KCCopyErrorContext();
  }

  if (!KCfetchToken(KnoArg1,',',')',256))
    goto ARGLISTCORRUPTED;

  if (!KCfetchToken(KnoArg2,')',',',256))
    goto ARGLISTCORRUPTED;

  KCkillTrailingBlanks(KnoArg1); // Name des ersten Kanals
  KCkillTrailingBlanks(KnoArg2); // Name des zweiten Kanals

  // Argumente prüfen

  // Name des ersten Kanals
  if (strlen(KnoArg1) > 8)
  {
    KCKnoCompilerErrorMessage(NONCMD_ARG1MORETHANEIGHT);
    KCCopyErrorContext();
    return FALSE;
  }

  if (KCGetSrcChanOfSlotNamed(KnoArg1) == -1) // Beschreibt keine Rohdaten
  {
    KCKnoCompilerErrorMessage(NONCMD_ARG1MUSTBERAWDATA);
    KCCopyErrorContext();
    return FALSE;
  }

  // Name des zweiten Kanals
  if (strlen(KnoArg2) > 8)
  {
    KCKnoCompilerErrorMessage(NONCMD_ARG2MORETHANEIGHT);
    KCCopyErrorContext();
    return FALSE;
  }

  if (KCGetSrcChanOfSlotNamed(KnoArg2) == -1) // Beschreibt keine Rohdaten
  {
    KCKnoCompilerErrorMessage(NONCMD_ARG2MUSTBERAWDATA);
    KCCopyErrorContext();
    return FALSE;
  }

  // Ersten freien Slot finden
  for(freeSlot=0;freeSlot < KNOWLEDGEMAX;freeSlot++)
  {
    if (CompiledKnowledge[freeSlot].iAm == 0) // 0 entspricht UNDEFINED
    {
      break; // freien Slot gefunden
    }
  }

  // Wissen einfügen

  // Wissen-Knotentyp
  CompiledKnowledge[freeSlot].iAm = ABSOLUTEDIFFERENCE;
  // Wissen-Initiales plotMark
  CompiledKnowledge[freeSlot].plotMarked = FALSE;
  // Wissen-Initiales writeMark
  CompiledKnowledge[freeSlot].writeMarked = FALSE;
  // Wissen-Name
  {
    char synthName[9]; // Synthetischer Name

    synthName[0] = 'A';
    synthName[1] = 'D';
    synthName[2] = KnoArg1[0];
    synthName[3] = KnoArg1[1];
    synthName[4] = KnoArg1[2];
    synthName[5] = KnoArg2[0];
    synthName[6] = KnoArg2[1];
    synthName[7] = KnoArg2[2];
    synthName[8] = '\0';

    lstrcpy(CompiledKnowledge[freeSlot].nameInKnowledge,synthName);
  }
  // Wissen-Quellkanäle
  CompiledKnowledge[freeSlot].sourceChannel = KCGetSrcChanOfSlotNamed(KnoArg1);
  CompiledKnowledge[freeSlot].sourceChannel2nd = KCGetSrcChanOfSlotNamed(KnoArg2);
  // Wissen-Minimum
  CompiledKnowledge[freeSlot].minimum = 0; // Kleinste Mögliche Absolutabweichung
  // Wissen-Maximum
  {
    int index1 = KCGetSrcChanOfSlotNamed(KnoArg1);
    int index2 = KCGetSrcChanOfSlotNamed(KnoArg2);
    double biggestPossibleValue;
    double smallestPossibleValue;

    if (CompiledKnowledge[index1].data1Double < CompiledKnowledge[index2].data1Double)
      smallestPossibleValue = CompiledKnowledge[index1].data1Double;
    else
      smallestPossibleValue = CompiledKnowledge[index2].data1Double;

    if (CompiledKnowledge[index1].data2Double > CompiledKnowledge[index2].data2Double)
      biggestPossibleValue = CompiledKnowledge[index1].data2Double;
    else
      biggestPossibleValue = CompiledKnowledge[index2].data2Double;

    // Größe mögliche Absolutabweichung
    CompiledKnowledge[freeSlot].maximum = biggestPossibleValue - smallestPossibleValue;
  }

  // Fertig, Meldung in die Logdatei schreiben
  fprintf(CompilerLog,"AbsoluteDifference(%s,%s) understood\n",KnoArg1,KnoArg2);

  return TRUE; // Weiter geht´s nur im Fehlerfall

ARGLISTCORRUPTED:
  KCKnoCompilerErrorMessage(NONCMD_ARGLISTCORRUPTED);
  KCCopyErrorContext();
  return FALSE;
}

// Ablegen von Extrema-Informationen, die für die Transformationen ins 12-Bit Range benutzt werden
// Einschränkung: es wird angenommen, daß sowohl Minimum, als auch Maximum POSITIV sind
void KolterDAC4DefTransforms(double ch0Min,double ch0Max,double ch1Min,double ch1Max,
                             double ch2Min,double ch2Max,double ch3Min,double ch3Max)
{
  // Übertragen in den globalen struct
  KolterDAC4Info.ch0Minimum=ch0Min;
  KolterDAC4Info.ch0Maximum=ch0Max;
  KolterDAC4Info.ch1Minimum=ch1Min;
  KolterDAC4Info.ch1Maximum=ch1Max;
  KolterDAC4Info.ch2Minimum=ch2Min;
  KolterDAC4Info.ch2Maximum=ch2Max;
  KolterDAC4Info.ch3Minimum=ch3Min;
  KolterDAC4Info.ch3Maximum=ch3Max;
}

void KolterDAC4Burst(double ch0Data,double ch1Data,double ch2Data,double ch3Data)
{
  unsigned int ch0Output = (unsigned int)Renorm(ch0Data,KolterDAC4Info.ch0Minimum,KolterDAC4Info.ch0Maximum,2048.,4095.);
  unsigned int ch1Output = (unsigned int)Renorm(ch1Data,KolterDAC4Info.ch1Minimum,KolterDAC4Info.ch1Maximum,2048.,4095.);
  unsigned int ch2Output = (unsigned int)Renorm(ch2Data,KolterDAC4Info.ch2Minimum,KolterDAC4Info.ch2Maximum,2048.,4095.);
  unsigned int ch3Output = (unsigned int)Renorm(ch3Data,KolterDAC4Info.ch3Minimum,KolterDAC4Info.ch3Maximum,2048.,4095.);

  // Beschreiben der ports
  outpw(0x0300,ch0Output); // Kanal 0
  outpw(0x0302,ch1Output); // Kanal 1
  outpw(0x0304,ch2Output); // Kanal 2
  outpw(0x0306,ch3Output); // Kanal 3
  inp(0x0300); // Alle Kanäle übergeben mit read-Befehl
}

// Funktionen zum Ansprechen von RS232-Leitungen über die Kolter DAC-4-Karte

void KolterDAC4LogicalOne(int channel)
{
  switch (channel)
  {
    case 0:
         outpw(0x0300,0); // -10 Volt: logisch 1
         inp(0x0300); // Alle Kanäle übergeben mit read-Befehl
         break;

    case 1:
         outpw(0x0302,0); // -10 Volt: logisch 1
         inp(0x0300); // Alle Kanäle übergeben mit read-Befehl
         break;

    case 2:
         outpw(0x0304,0); // -10 Volt: logisch 1
         inp(0x0300); // Alle Kanäle übergeben mit read-Befehl
         break;

    case 3:
         outpw(0x0306,0); // -10 Volt: logisch 1
         inp(0x0300); // Alle Kanäle übergeben mit read-Befehl
         break;
  }
}

void KolterDAC4LogicalZero(int channel)
{
  switch (channel)
  {
    case 0:
         outpw(0x0300,4095); // +10 Volt: logisch 0
         inp(0x0300); // Alle Kanäle übergeben mit read-Befehl
         break;

    case 1:
         outpw(0x0302,4095); // +10 Volt: logisch 0
         inp(0x0300); // Alle Kanäle übergeben mit read-Befehl
         break;

    case 2:
         outpw(0x0304,4095); // +10 Volt: logisch 0
         inp(0x0300); // Alle Kanäle übergeben mit read-Befehl
         break;

    case 3:
         outpw(0x0306,4095); // +10 Volt: logisch 0
         inp(0x0300); // Alle Kanäle übergeben mit read-Befehl
         break;
  }
}

void KolterDAC4LogicalNeutral(int channel)
{
  switch (channel)
  {
    case 0:
         outpw(0x0300,2048); // 0 Volt, keine Bedeutung für RS232
         inp(0x0300); // Alle Kanäle übergeben mit read-Befehl
         break;

    case 1:
         outpw(0x0302,2048); // 0 Volt, keine Bedeutung für RS232
         inp(0x0300); // Alle Kanäle übergeben mit read-Befehl
         break;

    case 2:
         outpw(0x0304,2048); // 0 Volt, keine Bedeutung für RS232
         inp(0x0300); // Alle Kanäle übergeben mit read-Befehl
         break;

    case 3:
         outpw(0x0306,2048); // 0 Volt, keine Bedeutung für RS232
         inp(0x0300); // Alle Kanäle übergeben mit read-Befehl
         break;
  }
}










