//---------------------------------------------------------------------------
/*
Remote Train Control Program for Windows

© Copyright 2020 by Mark DiVecchio

    This file is part of Remote Train Control.

    Remote Train Control is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Remote Train Control is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Remote Train Control.  If not, see <http://www.gnu.org/licenses/>.

*/
// Home Page : http://www.silogic.com/trains/RTC_Running.html
//---------------------------------------------------------------------------
#include <vcl.h>
#include <tchar.h>
#pragma hdrstop
#include "Engine_Commands.h"
#include "Train_Control.h"
#include "Timer_Thread.h"
#include "PC_Thread.h"
#include "Prog_Control.h"
#include "Debug_Controls.h"
#include "Setup_Control.h"
#include "Comm_Thread.h"
#include "Layout.h"
#include "BlockName_Query.h"
#include "Clipbrd.hpp"
#include "Aspect.h"

#include <mmsystem.h>	// for PlaySound
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma link "HideButton"
#pragma link "_GClass"
#pragma link "AbLED"
#pragma resource "*.dfm"

//---------------------------------------------------------------------------
// Signal Numbers
extern int iCabSignal[PCMAX][2];					// Signal number of each of two cab signals in PC Control window
													// [HOMESIGNAL] is Top Signal, [DISTANTSIGNAL] is Bottom Signal
extern TCommThread * ptrTIUCommThread;				// pointer to TIU communications thread
extern struct SIGNAL Signals[SIGNALMAX];				// Flags to indicate a Signal device with up to 2 signals was found
//
//---------------------------------------------------------------------------
extern TProgram_Control *ptrProgram_Control[PCMAX];	// pointer(s) to Program Control Windows
extern TThreadList * Event_FIFO[PCMAX];	    		// FIFO Event List for Program Control Threads - Thread Safe

bool Request_Events[PCMAX];							// Event Support shared with PC_Thread.cpp
//---------------------------------------------------------------------------
// Auto-create
TLayoutForm *LayoutForm;

extern String AspectNames[];						// Aspect Names (defined in Train_Control.cpp)

//---------------------------------------------------------------------------
String MulticastAddress = "239.0.0.10"; // 224.0.0.0 to 224.0.0.255    - Local Subnetwork - misses a lot of packets - I don't use multicast
String TXAddress = "192.168.1.127"; // Almost all packets get through - unicast
//---------------------------------------------------------------------------
__fastcall TLayoutForm::TLayoutForm(TComponent* Owner)
	: TForm(Owner)
{
LocalTransparencyOverride = false;
AutoScroll = true;	// this seems to get lost in dfm file on move to CB10.1
RegFileRootKey =  MainForm->RegFileRootKey;

for (int i = 0 ; i < FLAGLIMIT ; i++) {
	AllLED[i] = NULL;
    AllLL[i] = NULL;
    }
FlagGrid->ColWidths[FLAGNUM] = 50;
FlagGrid->ColWidths[FLAGX] = 30;
FlagGrid->ColWidths[FLAGY] = 30;
FlagGrid->ColWidths[FLAGORD] = 15;
FlagGrid->ColWidths[FLAGNAME] = 225;
FlagGrid->Width = 375;	// sum of ColWidths + 25 for the scrollbar
FlagGrid->RowCount = FLAGLIMIT + 1;
TransparentText = true;

SignalGrid->ColCount = 10;
SignalGrid->ColWidths[SIGNALNUM] = 50;
SignalGrid->ColWidths[SIGNALX] = 30;
SignalGrid->ColWidths[SIGNALY] = 30;
SignalGrid->ColWidths[SIGNALASPECT] = 30;
SignalGrid->ColWidths[SIGNALMODE] = 30;
SignalGrid->ColWidths[SIGNALNUMBERPLATE] = 30;
SignalGrid->ColWidths[SIGNALNAME] = 80;
SignalGrid->ColWidths[SIGNALDIR] = 30;
SignalGrid->ColWidths[SIGNALTYPE] = 30;
SignalGrid->ColWidths[SIGNALSP1] = 30;
SignalGrid->Width = 395;	// sum of ColWidths + 25 for the scrollbar
SignalGrid->RowCount = SIGNALLIMIT + 1;

ShowCursorCoords->Checked = false;
// Required for multiple monitors and save/restore position/size
Position=poDesigned;
DefaultMonitor=dmDesktop;
//
// read Bitmap file from registry if it exist
TRegIniFile * const RegFile = new TRegIniFile(RegFileRootKey);
assert(RegFile);
__try {
    Caption = RegFile->ReadString("Layout","BitmapFile" + String(IntToHex(__TCPLUSPLUS__, 4)), String(MainForm->cStartDir) + "\\Layout.bmp");
    TransparentText = RegFile->ReadInteger("Layout", "TransparentText", TransparentText);
    TransparentTextMenuItem->Checked = TransparentText;
    ToggleFlagNameColor = RegFile->ReadInteger("Layout", "ToggleFlagNameColor", ToggleFlagNameColor);
    ToggleFlagNameColorMenuItem->Checked = ToggleFlagNameColor;
    // read window position
	Top = RegFile->ReadInteger("Layout", "Top" + String(IntToHex(__TCPLUSPLUS__, 4)), Top);
	Left = RegFile->ReadInteger("Layout", "Left" + String(IntToHex(__TCPLUSPLUS__, 4)), Left);
    // if using multiple monitors and one was removed, pull the window
    // back onto the remaining monitor
    int MC = Screen->MonitorCount;
    if (MC == 1) {
        if (Left < 0 ) Left = 0;
        int Wth = Screen->Monitors[0]->Width;
		if (Left > Wth) Left -= Wth;
        if (Top < 0 ) Top = 0;
        int Hth = Screen->Monitors[0]->Height;
        if (Top > Hth) Top -= Hth;
        }
    Height = RegFile->ReadInteger("Layout", "Height" + String(IntToHex(__TCPLUSPLUS__, 4)), Height);
    Width = RegFile->ReadInteger("Layout", "Width" + String(IntToHex(__TCPLUSPLUS__, 4)), Width);
    //
    HorzScrollPosition = RegFile->ReadInteger("Layout", "HorzScrollBar" + String(IntToHex(__TCPLUSPLUS__, 4)), ImageScrollBox->HorzScrollBar->Position);
    VertScrollPosition = RegFile->ReadInteger("Layout", "VertScrollBar" + String(IntToHex(__TCPLUSPLUS__, 4)), ImageScrollBox->VertScrollBar->Position);
    // location of the direct edit Flag Grid
    FlagGrid->Top = RegFile->ReadInteger("Layout", "GridTop" + String(IntToHex(__TCPLUSPLUS__, 4)), FlagGrid->Top);
    FlagGrid->Left = RegFile->ReadInteger("Layout", "GridLeft" + String(IntToHex(__TCPLUSPLUS__, 4)), FlagGrid->Left);
    // Signals
    INIFileName = RegFile->ReadString("Signal","InitFile" + String(IntToHex(__TCPLUSPLUS__, 4)), String(MainForm->cStartDir) + "\\Signal.ini");
    SignalGrid->Top = RegFile->ReadInteger("Signal", "GridTop" + String(IntToHex(__TCPLUSPLUS__, 4)), SignalGrid->Top);
    SignalGrid->Left = RegFile->ReadInteger("Signal", "GridLeft" + String(IntToHex(__TCPLUSPLUS__, 4)), SignalGrid->Left);
    }
__finally {
// Deallocate RegFile
    delete RegFile;
    }
//
if (Caption.IsEmpty()) {
    // use cStartDir
	OpenDialog->InitialDir = MainForm->cStartDir;
    } else {
    // use directory from Script FileName
	OpenDialog->InitialDir = ExtractFilePath(Caption);
    }
if (FileExists(Caption)) {
	ShowImage(Caption);
    }
Corrupt = false;
FlagDataFile = ChangeFileExt(Caption, ".ini");
LoadFlagData();

if (INIFileName.IsEmpty()) {
    // use cStartDir
	SignalDataFile = String(MainForm->cStartDir) + "\\Signal.ini";
    } else {
    // use INIFileName
    SignalDataFile = INIFileName;
    }
Corrupt = false;
LoadSignalData();

SignalHelp = NULL;
// Setup udp port
RadioSend = NULL, RadioRecv = NULL;
LayoutDataSent = true;
RadioWiFiRXConnected = false;
RadioWiFiTXConnected = false;
//
FServerAddr                = WSocketResolveHost("127.0.0.1");
if (FServerAddr.s_addr == htonl(INADDR_LOOPBACK)) {
    // Replace loopback address by real localhost IP addr
    FServerAddr            = WSocketResolveHost(LocalHostName());
    }
//
StartTX();          // Start Signals udp TX
//
// Events Support
for (int iPCNo = 1 ; iPCNo <= PCMAX ; iPCNo++) {
	Request_Events[iPCNo - 1] = false;
    }

ShowIt();	// set the window size and postion and then make it visible
}
//---------------------------------------------------------------------------

__fastcall TLayoutForm::~TLayoutForm(void)
{
// AllLED[] and AllLL[] are deleted automatically

// Shut Down the udp RX WiFi - this is never used
if (RadioRecv) {
	RadioRecv->Close();
	delete RadioRecv;
    RadioRecv = NULL;
    }

// Shut Down the udp TX WiFi
if (RadioSend) {
	RadioSend->Close();
	delete RadioSend;
    RadioSend = NULL;
    }

if (SignalHelp) {
	delete SignalHelp;
    SignalHelp = NULL;
    }

if (MainForm->bSaveRegistryInfo) {
    TRegIniFile * const RegFile = new TRegIniFile(RegFileRootKey);
    assert(RegFile);
    __try {
        RegFile->WriteString("Layout", "BitmapFile" + String(IntToHex(__TCPLUSPLUS__, 4)), Caption);
        RegFile->WriteInteger("Layout", "TransparentText", TransparentText);
        RegFile->WriteInteger("Layout", "Top" + String(IntToHex(__TCPLUSPLUS__, 4)), Top);
        RegFile->WriteInteger("Layout", "Height" + String(IntToHex(__TCPLUSPLUS__, 4)), Height);
        RegFile->WriteInteger("Layout", "Left" + String(IntToHex(__TCPLUSPLUS__, 4)), Left);
        RegFile->WriteInteger("Layout", "Width" + String(IntToHex(__TCPLUSPLUS__, 4)), Width);
        RegFile->WriteInteger("Layout", "HorzScrollBar" + String(IntToHex(__TCPLUSPLUS__, 4)), ImageScrollBox->HorzScrollBar->Position);
        RegFile->WriteInteger("Layout", "VertScrollBar" + String(IntToHex(__TCPLUSPLUS__, 4)), ImageScrollBox->VertScrollBar->Position);

        RegFile->WriteInteger("Layout", "ToggleFlagNameColor", ToggleFlagNameColor);
        // location of the direct edit Flags StringGrid
        RegFile->WriteInteger("Layout", "GridTop" + String(IntToHex(__TCPLUSPLUS__, 4)), FlagGrid->Top > 0 ? FlagGrid->Top : 0);
        RegFile->WriteInteger("Layout", "GridLeft" + String(IntToHex(__TCPLUSPLUS__, 4)), FlagGrid->Left > 0 ? FlagGrid->Left : 0);
        //
        RegFile->WriteString("Signal", "InitFile" + String(IntToHex(__TCPLUSPLUS__, 4)), INIFileName);
        //
        // location of the direct edit Signals StringGrid
        RegFile->WriteInteger("Signal", "GridTop" + String(IntToHex(__TCPLUSPLUS__, 4)), SignalGrid->Top > 0 ? SignalGrid->Top : 140);
        RegFile->WriteInteger("Signal", "GridLeft" + String(IntToHex(__TCPLUSPLUS__, 4)), SignalGrid->Left > 0 ? SignalGrid->Left : 0);

        }
    __finally {
        // Deallocate RegFile
        delete RegFile;
        }
    }

if (Corrupt) return;
TStringList * const FlagDat = new TStringList();
assert(FlagDat);		// make sure that construction worked
FlagDat->Clear();
__try {
    try {
        // Save Flag Data to disk with overwrite
        FlagDat->Sorted = false;
        // transfer data from Grid
        for (int FlagNum = 0 ; FlagNum < FlagGrid->RowCount ; FlagNum++) {
            // move it into the FlagDat Object
            FlagDat->Add(FlagGrid->Rows[FlagNum]->CommaText);
            }
        // write it
        FlagDat->SaveToFile(FlagDataFile);
        if (DEBUGGING) DB->Debug_Print("Flag Data Saved to " + FlagDataFile);
        }
        catch (int field) {
            MainForm->DebugMessage("Edited Flag Data Field (" + String(field) + ") is Corrupt - Not Saved : " + FlagDataFile);
			Corrupt = true;
            }
        catch (...) {
            MainForm->DebugMessage("Edited Flag Data is Corrupt - Not Saved : " + FlagDataFile);
			Corrupt = true;
            }
    	}
	__finally {
    delete FlagDat;
    }


if (Corrupt) return;
TStringList * const SignalDat = new TStringList();
assert(SignalDat);		// make sure that construction worked
SignalDat->Clear();
__try {
    try {
        // Save Signal Data to disk with overwrite
        SignalDat->Sorted = false;
        // transfer data from Grid
        for (int SignalNum = 0 ; SignalNum < SignalGrid->RowCount ; SignalNum++) {
            // move it into the SignalDat Object
            SignalDat->Add(SignalGrid->Rows[SignalNum]->CommaText);
            }
        // write it
        SignalDat->SaveToFile(SignalDataFile);
        if (DEBUGGING) DB->Debug_Print("Signal Data Saved to " + SignalDataFile);
        }
        catch (int field) {
            MainForm->DebugMessage("Signal Data Field (" + String(field) + ") is Corrupt - Not Saved : " + SignalDataFile);
			Corrupt = true;
            }
        catch (...) {
            MainForm->DebugMessage("Signal Data is Corrupt - Not Saved : " + SignalDataFile);
			Corrupt = true;
            }
    	}
	__finally {
    delete SignalDat;
    }
}
//---------------------------------------------------------------------------

void __fastcall TLayoutForm::HideButton1Click(TObject *Sender)
{
LayoutForm->Hide();	
}
//---------------------------------------------------------------------------
// Workaround for SetLayeredWindowAttributes() to make transparent windows
extern lpfnSetLayeredWindowAttributes m_pSetLayeredWindowAttributes;
//---------------------------------------------------------------------------

void __fastcall TLayoutForm::FormActivate(TObject *Sender)
{
#if __BORLANDC__ >= 0x720      // CB10.1 only
AlphaBlend = false; // Remove Transparency
#else
// Remove WS_EX_LAYERED from this window styles
SetWindowLong(Handle, GWL_EXSTYLE, GetWindowLong(Handle, GWL_EXSTYLE) & ~WS_EX_LAYERED);
#endif
}
//---------------------------------------------------------------------------

void __fastcall TLayoutForm::FormDeactivate(TObject *Sender)
{
if (Setup->TransparentSpin->Value == Setup->TransparentSpin->MaxValue) return;	// Off
if (LocalTransparencyOverride) return;
#if __BORLANDC__ >= 0x720      // CB10.1 only
AlphaBlendValue = (255 * Setup->TransparentSpin->Value) / 100;
AlphaBlend = true;  // Add Transparency
#else
// Set WS_EX_LAYERED on this window
SetWindowLong(Handle, GWL_EXSTYLE, GetWindowLong(Handle, GWL_EXSTYLE) | WS_EX_LAYERED);
//SetLayeredWindowAttributes
m_pSetLayeredWindowAttributes(Handle, NULL, (255 * Setup->TransparentSpin->Value) / 100, LWA_ALPHA);
#endif
}
//---------------------------------------------------------------------------

void __fastcall TLayoutForm::FormClick(TObject *Sender)
{
LocalTransparencyOverride = !LocalTransparencyOverride;	// Toggle transparency locally	
}
//---------------------------------------------------------------------------

void __fastcall TLayoutForm::LayoutImageClick(TObject *Sender)
{
//
if (CreateAFlag) {
    //
    // User has clicked on the background bitmap so we are to create a new flag
    //

    int NextFlagNo;
    // get xy Mouse position
    TPoint p_rel_scr = Mouse->CursorPos;
    TPoint p_rel_frm = LayoutImage->ScreenToClient(p_rel_scr );
    int x = (int) p_rel_frm.x;
    int y = (int) p_rel_frm.y;

    // find an empty flag slot
    for (NextFlagNo = 1 ; NextFlagNo <= FLAGLIMIT ; NextFlagNo++) {
        if (FlagGrid->Cells[FLAGX][NextFlagNo] == 0 && FlagGrid->Cells[FLAGY][NextFlagNo] == 0) {
            // empty slot - NextFlag is set
            break;
            }
        }
    if (NextFlagNo > FLAGLIMIT) {
        // no more room
        ShowMessage("        Flag count (100) exceeded.       ");
        CreateAFlag = false;	// done
        ShowCursorCoords->Checked = false;
        return;
        }

    // If left mouse, add a Flag
    Coords->Caption = "[" + String(NextFlagNo) + "](" + String(x) + "," + String(y) + ")";

    // Ask for name of flag and its position with respect to the LED
    // if Ordinal == 0 don't display the Name, only display the flag
    String FlagName = " ";
    int Ordinal = 1;
    BlockNameQuery->SetOrdinal(Ordinal);
    BlockNameQuery->FlagNameEdit->Text = FlagName;
    BlockNameQuery->FlagLabel->Caption = "Enter Flag #" + String(NextFlagNo) + " Name";
    BlockNameQuery->Caption = "Flag Name and Position";
    BlockNameQuery->PositionPanel->Visible = true;
    #if __BORLANDC__ >= 0x720
    // Defined for CB10.1 C++ programs that use the VCL.
    BlockNameQuery->PopupMode = pmExplicit;
    BlockNameQuery->PopupParent = LayoutForm;
    BlockNameQuery->Position =  poDesigned;
    #endif
    int retv = BlockNameQuery->ShowModal();
    if (retv == mrCancel) {
        CreateAFlag = false;	// done
        ShowCursorCoords->Checked = false;
        return;	// entry canceled, skip the flag
        }
    FlagName = BlockNameQuery->FlagNameEdit->Text;
    Ordinal = BlockNameQuery->Ordinal;
    // Save Flag in the FlagGrid
    FlagGrid->Cells[FLAGX][NextFlagNo] = x;	// X coord
    FlagGrid->Cells[FLAGY][NextFlagNo] = y;	// Y coord
    FlagGrid->Cells[FLAGORD][NextFlagNo] = Ordinal;	// text ordinal position
    FlagGrid->Cells[FLAGNAME][NextFlagNo] = FlagName;	// FlagName
    // create the flag icon
    CreateFlag(NextFlagNo, x, y, Ordinal, FlagName);	// NextFlag starts at 0
    CreateAFlag = false;	// done
    ShowCursorCoords->Checked = false;
	}
}
//---------------------------------------------------------------------------

void __fastcall TLayoutForm::FlagMouseDownLocal(TObject *Sender, TMouseButton Button,
	  TShiftState Shift, int X, int Y)
{
ShowCursorCoords->Checked = false;

TAbLED *pAbLED = static_cast<TAbLED*>(Sender);
if (!pAbLED) return;	// check that the static_cast worked

ShiftKeyState = GetKeyState(VK_SHIFT);  // get shift key state

// get xy Mouse position
TPoint p_rel_scr = Mouse->CursorPos;
//TPoint p_rel_frm = LayoutImage->ScreenToClient(p_rel_scr);
//int x = (int) p_rel_frm.x;
//int y = (int) p_rel_frm.y;
int FlagNo = pAbLED->Name.SubString(4,4).ToInt();	// limits LED flag # to 4 digits 	
Coords->Caption = "[" + String(FlagNo) + "](" + FlagGrid->Cells[FLAGX][FlagNo] + "," + FlagGrid->Cells[FLAGY][FlagNo] + ")";

FlagN->Caption = GetFlagName(FlagNo);

// if left mouse button, toggle the flag. Manually, we set only the flag state, not an engine number or direction
if (Button == mbLeft) {
    pAbLED->Checked = !pAbLED->Checked;
    if (pAbLED->Checked) {
    	pAbLED->Tag = UNOCCUPIED;			// Set Flag to unoccupied and it will be green
        } else if (ShiftKeyState < 0) {     // Shift is pressed so use soft set
            pAbLED->Tag = SOFTOCCUPY;	 	// Soft indication put in the Tag
            } else {
            pAbLED->Tag = MANUALFLAG;	        // Manual indication put in the Tag
            pAbLED->LED->ColorOff = clYellow;	// Manually activated flag color
            }
    if (ToggleFlagNameColor) {
       AllLL[FlagNo - 1]->Font->Color = pAbLED->Checked ? clBlack : clRed;
       }
    int flagvalue = pAbLED->Tag;	// Engine number occupying the block put in the Tag
    // Send Block Occupancy event to PCThread if requested by script calling ReqBlockEvents(true).
    // Tag contains UNOCCUPIED == 0, (EngineNo 1 to (FLAGLIMIT-1) | WB/EB/NB/SB) or MANUALFLAG == -1 or SOFTOCCUPY == -2
	// On a manually set flag, the direction is set to unknown
    for (int iDestPCNo = 1 ; iDestPCNo <= PCMAX ; iDestPCNo++) {
        if (Request_Events[iDestPCNo - 1]) {	// is this PCNo requesting Block Occupancy events?
             if (ptrProgram_Control[iDestPCNo - 1] != NULL) {
                if (ptrProgram_Control[iDestPCNo - 1]->ptrPC_Thread != NULL) {
                    EventItem * EvItem = new EventItem;	// deleted here on failure or upon success, in ProcessEventEvent()
                    EvItem->iFromPCNo = 99;	// 99 indicates from the Layout Window
                    EvItem->iDestPCNo = iDestPCNo;
                    EvItem->EventType = BLOCK_OCCUPANCY_EVENT;
                    EvItem->iP1 = FlagNo;
                    EvItem->iP2 = flagvalue;    // this can be -2, -1, 0, >0
                    EvItem->iP3 = 0;            // direction of travel set to NDT
                    //
                    // add to the end of the Event list
                    if (Event_FIFO[iDestPCNo - 1] != NULL) {
                        TList *myList = Event_FIFO[iDestPCNo - 1]->LockList();
                        assert(myList);
                        Event_FIFO[iDestPCNo - 1]->Add((void *)(EvItem));   // Thread Safe
                        // Signal PC_Thread that an event is ready (may be this or another PC_Thread)
                        SetEvent(ptrProgram_Control[iDestPCNo - 1]->EventPCEvent);
                        Event_FIFO[iDestPCNo - 1]->UnlockList();
                        }
                    } else {
                    // quietly fail if pointers don't exist
                    }
                } else {
                // quietly fail if pointers don't exist
                }
            }
        }

    // Set hint for the user
    if (flagvalue == UNOCCUPIED) {
        pAbLED->Hint = "Flag #" + String(FlagNo) + " " + AllLL[FlagNo - 1]->Caption + " Set to Unoccupied";
    	} else {
        if (flagvalue == MANUALFLAG || flagvalue == SOFTOCCUPY) {
        	pAbLED->Hint = "Flag #" + String(FlagNo) + " " + AllLL[FlagNo - 1]->Caption +
                ((flagvalue == MANUALFLAG) ? " Reserved Manually" : " Soft Reserved Manually");
            } else {
            // Programmatically set value (Engine number and Direction)
            String sDir;
            switch ((flagvalue & 0xFF00) >> 8) {
                case WB:
                    sDir = "WB ";
                    break;
                case EB:
                    sDir = "EB ";
                    break;
                case NB:
                    sDir = "NB ";
                    break;
                case SB:
                    sDir = "SB ";
                    break;
                default:
                    sDir = "";
                    break;
                }
            pAbLED->Hint = "Flag #" + String(FlagNo) + " " + AllLL[FlagNo - 1]->Caption + " by " + sDir + "Engine #" + String(flagvalue & 0x00FF);
            }
        }
    return;
    }
// if right mouse button, edit the flag
if (Button == mbRight) {
// ask for name of flag
    String FlagName = FlagN->Caption;
    int Ordinal = FlagGrid->Cells[FLAGORD][FlagNo].ToInt();
    BlockNameQuery->SetOrdinal(Ordinal);
    BlockNameQuery->FlagNameEdit->Text = FlagName;
    BlockNameQuery->FlagLabel->Caption = "Enter Flag #" + String(FlagNo) + " Name";
    #if __BORLANDC__ >= 0x720
    // Defined for CB10.1 C++ programs that use the VCL.
    BlockNameQuery->PopupMode = pmExplicit;
    BlockNameQuery->PopupParent = LayoutForm;
    BlockNameQuery->Position =  poDesigned;
    #endif
    int retv = BlockNameQuery->ShowModal();
    if (retv == mrCancel) {
        return;	// entry canceled, skip the flag
        }
	FlagName = BlockNameQuery->FlagNameEdit->Text;
    // delete the flag when the Name is blank
	if (FlagName == "" || FlagName == " ") {	// BCB4.0 saves as "", CB10.1 as " "
    	// confirm
		int retv = MessageDlg("Delete Flag # " + String(FlagNo), mtConfirmation, TMsgDlgButtons() << mbOK << mbCancel, 0);
		if (retv == mrCancel) {
			return;
		    }
        // delete the flag and text
        if (AllLED[FlagNo - 1]) {
        	delete AllLED[FlagNo - 1];	 	// adjust for 1 based numbering in Lua
            AllLED[FlagNo - 1] = NULL;
            }
		if (AllLL[FlagNo - 1]) {
        	delete AllLL[FlagNo - 1];		// adjust for 1 based numbering in Lua
            AllLL[FlagNo - 1] = NULL;
            }

        FlagGrid->Cells[FLAGX][FlagNo] = 0;	// X coord
        FlagGrid->Cells[FLAGY][FlagNo] = 0;	// Y coord
        FlagGrid->Cells[FLAGORD][FlagNo] = 0;	// text ordinal position
        FlagGrid->Cells[FLAGNAME][FlagNo] = " ";	// FlagName
        return;
        }
    Ordinal = BlockNameQuery->Ordinal;
    FlagGrid->Cells[FLAGORD][FlagNo] = Ordinal;	// text ordinal position
    // TODO : we don't correctly restore the Checked or Tag values or LED color
    //bool SaveChecked = AllLED[FlagNo - 1]->Checked;
    // when redrawing an edited flag, use the original x,y position from the FlagGrid
    CreateFlag(FlagNo, FlagGrid->Cells[FLAGX][FlagNo].ToInt(), FlagGrid->Cells[FLAGY][FlagNo].ToInt(), Ordinal, FlagName);     
    //AllLED[FlagNo - 1]->Checked = SaveChecked;
    }
}
//---------------------------------------------------------------------------

void __fastcall TLayoutForm::SignalMouseDownLocal(TObject *Sender, TMouseButton Button,
	  TShiftState Shift, int X, int Y)
{
//
TAbLED * ActualLED;
ActualLED = dynamic_cast <TAbLED *> (Sender);
if (ActualLED) {
	TPanel * ActualPanel;
	ActualPanel = dynamic_cast <TPanel *> (ActualLED->Parent);
	if (ActualPanel) {
        int iSignalNo;
        // Signal number is limited to 4 digits in the Name
        //cDir = ActualPanel->Name[1];   // First character - ordinal direction
        assert(ActualPanel->Name.Length() >= 6);
        iSignalNo = ActualPanel->Name.SubString(ActualPanel->Name.Length() - 3, 4).ToInt();
		// old method to extract cDir and iSignalNo replaced by above two lines
	    //_stscanf(ActualPanel->Name.c_str(), TEXT("%cB%4d"), &cDir, &iSignalNo);	// Signal number limited to 4 digits
        // cDir is the first letter of the ordinal direction E W N S
        // iSignalNo is the signal number
        // In the SignalGrid:

        //	SignalGrid->Cells[SIGNALNUM][iSignalNo] = iSignalNo;			// Index
        //	SignalGrid->Cells[SIGNALASPECT][iSignalNo] = iAspect;			// Signal Aspect   // < 0 means signal is dark
        int iMode = SignalGrid->Cells[SIGNALMODE][iSignalNo].ToInt(); 		// Mode Number of the signal
        //	SignalGrid->Cells[SIGNALNUMBERPLATE][iSignalNo] = iNumberPlate;	// Number Plate
        //	SignalGrid->Cells[SIGNALNAME][iSignalNo] = sSignalName;			// SignalName (2-3-4 characters)
        //	SignalGrid->Cells[SIGNALX][iSignalNo] = X;				// Column
        //	SignalGrid->Cells[SIGNALY][iSignalNo] = Y;				// Row
        //	SignalGrid->Cells[SIGNALDIR][iSignalNo] = iDir;			// Direction WB = 1, EB = 2, NB = 3, SB = 4
        //	SignalGrid->Cells[SIGNALTYPE][iSignalNo] = iType;		// Type
        int iType = SignalGrid->Cells[SIGNALTYPE][iSignalNo].ToInt();
        //	SignalGrid->Cells[SIGNALSP1][iSignalNo] = 0;			// Spare
        assert(iSignalNo == SignalGrid->Cells[SIGNALNUM][iSignalNo]);
        // ActualPanel->Name is the name of the signal ie: WB0025 or Westbound0025  // Signal number is limited to 4 digits in the Name
        // ActualPanel->Hint is the aspect name
        // ActualLED->Hint is the indication
        if (DEBUGGING) {
		    MainForm->DebugMessagePopup("Mode " + String(iMode) + " Signal #" +
        		String(iSignalNo), SignalGrid->Cells[SIGNALNAME][iSignalNo] /*ActualPanel->Name*/ + " : " +
            	ActualPanel->Hint,	// Aspect Name
	            ActualLED->Hint);	// Indication
                }
        // Open the Aspects Editor Window (Manually Created)
	    TAspects * AspPtr = new TAspects(this, SignalGrid->Cells[SIGNALASPECT][iSignalNo].ToInt(), iMode, iType,
            "Mode " + String(iMode) + " Signal #" + String(iSignalNo),
            SignalGrid->Cells[SIGNALNAME][iSignalNo] + " : " + ActualPanel->Hint,	// Aspect Name
            ActualLED->Hint);   // create the Form & Pass "this" as the owner
	    assert(AspPtr != NULL);	// make sure that construction worked
        #if __BORLANDC__ >= 0x720
        // Defined for CB10.1 C++ programs that use the VCL.
        AspPtr->PopupMode = pmExplicit;
        AspPtr->PopupParent = LayoutForm;
        AspPtr->Position =  poDesigned;
        #endif
		int ModalResult = AspPtr->ShowModal();
	    if (ModalResult == mrOk) {	// if [OK] was clicked, then change the aspect
			SetAspect(iSignalNo, AspPtr->FinalAspect);		// set signal on Signal Window
			String sName;
			int iNumberPlate;	// for Number Plate for signal on Layout window
			int iType;          // for Signal Type for signal on Layout window
			sName = GetSignalName(iSignalNo, iNumberPlate, iType);
            EnterCriticalSection(&MainForm->Signals_CS);
            // check the Program Control window's Cab Display to see if there is a signal we should set
            for (int iPCNo = 1 ; iPCNo <= PCMAX ; iPCNo++){
                if (iCabSignal[iPCNo - 1][HOMESIGNAL] == iSignalNo) {
                    // set PC Window Home Signal
                    ptrTIUCommThread->SetCabAspect(HOMESIGNAL, AspPtr->FinalAspect, iPCNo);
                    ptrTIUCommThread->SetCabLabels(HOMESIGNAL, sName, iNumberPlate, iType, iPCNo);
                    break;
                    }
                if (iCabSignal[iPCNo - 1][DISTANTSIGNAL] == iSignalNo) {
                    // set PC Window Distant Signal
                    ptrTIUCommThread->SetCabAspect(DISTANTSIGNAL, AspPtr->FinalAspect, iPCNo);
                    ptrTIUCommThread->SetCabLabels(DISTANTSIGNAL, sName, iNumberPlate, iType, iPCNo);
                    break;
                    }
                }
            LeaveCriticalSection(&MainForm->Signals_CS);
    		}
	    delete AspPtr;
    	AspPtr = NULL;

	    }
    }
    // Note: cDir never used
}
//---------------------------------------------------------------------------
void __fastcall TLayoutForm::SignalMouseDownLocal1(TObject *Sender, TMouseButton Button,
	  TShiftState Shift, int X, int Y)
{
ShowCursorCoords->Checked = !ShowCursorCoords->Checked;	// toggle show cursor coordinates
}
//---------------------------------------------------------------------------

void __fastcall TLayoutForm::SignalMouseDownLocal2(TObject *Sender, TMouseButton Button,
	  TShiftState Shift, int X, int Y)
{
ShowCursorCoords->Checked = !ShowCursorCoords->Checked;	// toggle show cursor coordinates
}
//---------------------------------------------------------------------------

void __fastcall TLayoutForm::SignalMouseDownLocal3(TObject *Sender, TMouseButton Button,
	  TShiftState Shift, int X, int Y)
{
ShowCursorCoords->Checked = !ShowCursorCoords->Checked;	// toggle show cursor coordinates
}
//---------------------------------------------------------------------------

void __fastcall TLayoutForm::ShowImage(String FileName)
{
if (FileName.IsEmpty()) return;

// It is necessary to specify Graphics::TBitmap found in <Graphics.hpp>
Graphics::TBitmap* bm = new Graphics::TBitmap;

try
   {
   // Loads the user selected file
   bm->LoadFromFile(FileName);
   LayoutImage->Width = bm->Width;
   LayoutImage->Height = bm->Height;
   // Assigns the TBitmap to the TImage
   LayoutImage->Picture->Graphic = bm;
   // Sends the TBitmap to the canvas to display (not needed)
   //LayoutImage->Canvas->Draw(100, 200, bm);
   Caption = FileName;
   }
   __finally
   {
   // Cleanup
   delete bm;
   }

LayoutImage->Hint = "To lay down a Flag, right click for the popup menu.";
}
//---------------------------------------------------------------------------

void __fastcall TLayoutForm::CreateFlag(int FlagNo, int X, int Y, int ord, String FlagName)
{
if (AllLED[FlagNo - 1]) {	// adjust for 1 based numbering in Lua
	delete AllLED[FlagNo - 1];
    AllLED[FlagNo - 1] = NULL;
    }
if (AllLL[FlagNo - 1]) {	// adjust for 1 based numbering in Lua
	delete AllLL[FlagNo - 1];
    AllLL[FlagNo - 1] = NULL;
    }

TPoint CC;
CC.x = X;
CC.y = Y - HidePanel->Height;

// Adjust top left corner if the Image is scrolled
if (LayoutForm) {
	CC.x = CC.x - LayoutForm->ImageScrollBox->HorzScrollBar->Position;
	CC.y = CC.y - LayoutForm->ImageScrollBox->VertScrollBar->Position;
    }

TAbLED * LED1 = new TAbLED(LayoutForm);	// parent set so deleted automatcally
LED1->Parent = ImageScrollBox;
// attempt to center the LED on where the user clicked
LED1->Top = CC.y + (LED1->Height >> 1) + 1;
LED1->Left = CC.x - (LED1->Height >> 2) + 2;
//
LED1->Name = "LED" + String(FlagNo);
LED1->LED->ColorOn = clLime;
LED1->LED->ColorOff = clRed;	// computer controlled occupied flag color
LED1->Checked = true;
LED1->Flashing = false;
LED1->Caption = "";
LED1->OnMouseDown = FlagMouseDownLocal;
LED1->OnMouseMove = LayoutMouseMove;
LED1->Cursor = crHandPoint;
LED1->Width = 13;				// for locating the Flag Name
LED1->Height = 13;
LED1->Tag = 0; 					// Engine number occupying the block put in the Tag but here unoccupied
LED1->Hint = "Flag #" + String(FlagNo) + " (Left click to toggle Flag - Right click to edit Flag name)";
LED1->ShowHint = true;
AllLED[FlagNo - 1] = LED1;	// adjust for 1 based numbering in Lua
//
TLabel * LL = new TLabel(LayoutForm);	// parent set so deleted automatcally
LL->Parent = ImageScrollBox;
LL->Visible = false;
LL->ShowAccelChar = false;
LL->AutoSize = true;
LL->Font->Name = "Arial";
LL->Font->Size = 16;
LL->Font->Style = LL->Font->Style << fsBold;
LL->Font->Color = clBlack;
LL->Top = LED1->Top;	//CC.y;
LL->Left = LED1->Left;	//CC.x;
if (!TransparentText) LL->Color = clDkGray;
LL->Transparent = TransparentText;
LL->Hint = "Flag #" + String(FlagNo) + " (Right click on flag to edit this text)";
LL->ShowHint = true;
LL->Caption = FlagName;
LL->OnMouseMove = LayoutMouseMove;

AllLL[FlagNo - 1] = LL;	// adjust for 1 based numbering in Lua
// place the flag name at one of the ordinal points
switch (ord) {
	case 0:		// don't display the Flag Name at all
    default:
    	LL->Visible = false;
        break;
    case 1:		// 3 o'clock
        LL->Top += (LED1->Height - LL->Height + 1) / 2;
        LL->Left += LED1->Width;
        LL->Visible = true;
    	break;
    case 2:		// 6 o'clock
    	LL->Top += (LED1->Height);
        LL->Left -= (LL->Width / 2) - (LED1->Width / 2);
        LL->Visible = true;
    	break;
    case 3:		// 9 o'clock
	    LL->Top += (LED1->Height - LL->Height + 1) / 2;
        LL->Left -= LL->Width + 5;
        LL->Visible = true;
        break;
    case 4:		// 12 o'clock
    	LL->Top -= (LED1->Height + 10);
        LL->Left -= (LL->Width / 2) - (LED1->Width / 2);
        LL->Visible = true;
    	break;
	}

// Flagname is stored here for case of a flag edit
FlagGrid->Cells[FLAGNAME][FlagNo] = FlagName;	// put the name in the string grid
}
//---------------------------------------------------------------------------

int __fastcall TLayoutForm::CreateSignal(int iSignalNo, int iDir, int iType, int X, int Y, String sSignalName, int iMode, int iAspect, int iNumberPlate)
{
assert(abs(iSignalNo) <= SIGNALLIMIT);
//
// A negative iSignalNo means just delete the signal and don't recreate it
// A negative iAspect means the signal displays as dark
//
// First delete the old signal if it exists
if (SignalGrid->Cells[SIGNALMODE][abs(iSignalNo)].ToInt() > 0) {
    TPanel * Hd = dynamic_cast <TPanel *> (SignalGrid->Objects[SIGNALNUM][abs(iSignalNo)]);
    assert(Hd);
    delete Hd;
    SignalGrid->Objects[SIGNALNUM][abs(iSignalNo)] = NULL;
    SignalGrid->Cells[SIGNALMODE][abs(iSignalNo)] = 0;	// mode == 0 indicates no signal here
	}
// if mode number is > 0 then create a new signal
if (iSignalNo > 0 && iMode > 0) {
	ConstructSignal(iSignalNo, iDir, X, Y, sSignalName, iMode, iAspect, iNumberPlate, 0, 0);
    SignalGrid->Cells[SIGNALNUM][iSignalNo] = iSignalNo;			// Index
    SignalGrid->Cells[SIGNALASPECT][iSignalNo] = iAspect;			// Signal Aspect (< 0 means signal is dark)
    SignalGrid->Cells[SIGNALMODE][iSignalNo] = iMode;       		// Mode Number of the signal
    SignalGrid->Cells[SIGNALNUMBERPLATE][iSignalNo] = iNumberPlate;	// Number Plate
    SignalGrid->Cells[SIGNALNAME][iSignalNo] = sSignalName;			// SignalName (2-3-4 characters)
    SignalGrid->Cells[SIGNALX][iSignalNo] = X;				// Column
    SignalGrid->Cells[SIGNALY][iSignalNo] = Y;				// Row
	SignalGrid->Cells[SIGNALDIR][iSignalNo] = iDir;			// Direction WB = 1, EB = 2, NB = 3, SB = 4
    SignalGrid->Cells[SIGNALTYPE][iSignalNo] = iType;		// Type
    SignalGrid->Cells[SIGNALSP1][iSignalNo] = 0;			// Spare
    if (iMode == HdMLED) {  // is this a physical 3 LED signal?
        SendSignal(iSignalNo, ((iAspect < 0) ? AsDark : iAspect)); // once the signal is created, set its aspect (< 0 means signal is dark)
        // if I put this call to SendSignal in the function ConstructSignal(), I get an invalid conversion of string to integer.
        }
    }
// returns iAspect just as a confirmation
return iAspect;
}
//---------------------------------------------------------------------------

void __fastcall TLayoutForm::ConstructSignal(int SignalNo, int iDir, int Col, int Row, String SignalName, int iMode, int iAspect,
	int NumPlate, int iSP1, int iSP2)
{
TPanel * pSignal = NULL;
switch(iMode) {
	case Hd1LED:
        pSignal =  new TLED1Panel(LayoutForm);
        assert(pSignal);
        // Signal number limited to 4 digits
		// alternative
		//pSignal->Name = ((iDir == WB) ? "Westbound" : ((iDir == EB) ? "Eastbound" : ((iDir == NB) ? "Northbound" : "Southbound"))) + Format((String)"%4.4d", ARRAYOFCONST((SignalNo)));
		pSignal->Name = ((iDir == WB) ? "WB" : ((iDir == EB) ? "EB" : ((iDir == NB) ? "NB" : "SB"))) + Format((String)"%4.4d", ARRAYOFCONST((SignalNo)));
		pSignal->Parent = ImageScrollBox;
        TLED1Panel * Actual1;
		Actual1 = dynamic_cast <TLED1Panel *> (pSignal);
		if (Actual1) {
			Actual1->LEDHighSpeed->Checked = true;
            Actual1->LEDHighSpeed->OnMouseDown = SignalMouseDownLocal;
            Actual1->LEDHighSpeed->OnMouseMove = LayoutMouseMove;
			if (SignalName != NULL) Actual1->SignalName->Caption = SignalName;
            Actual1->SignalName->OnMouseDown = SignalMouseDownLocal1;
            Actual1->SignalName->OnMouseMove = LayoutMouseMove;
            Actual1->NumberPlate->OnMouseDown = SignalMouseDownLocal1;
            Actual1->NumberPlate->OnMouseMove = LayoutMouseMove;
			if (NumPlate != 0) {
				Actual1->NumberPlate->Caption = String(NumPlate);
				Actual1->NumberPlate->Visible = true;
				} else {
				Actual1->NumberPlate->Caption = "A";
				Actual1->NumberPlate->Visible = true;
                }
   			if (iAspect != -1) {
				Actual1->Lights((iAspect < 0) ? AsDark : iAspect); // < 0 means signal is dark
                }
			}
		break;
	case Hd2LED:
		pSignal =  new TLED2Panel(LayoutForm);
		assert(pSignal);
        // Signal number limited to 4 digits
		// alternative
		//pSignal->Name = ((iDir == WB) ? "Westbound" : ((iDir == EB) ? "Eastbound" : ((iDir == NB) ? "Northbound" : "Southbound"))) + Format((String)"%4.4d", ARRAYOFCONST((SignalNo)));
		pSignal->Name = ((iDir == WB) ? "WB" : ((iDir == EB) ? "EB" : ((iDir == NB) ? "NB" : "SB"))) + Format((String)"%4.4d", ARRAYOFCONST((SignalNo)));
		pSignal->Parent = ImageScrollBox;
        TLED2Panel * Actual2;
		Actual2 = dynamic_cast <TLED2Panel *> (pSignal);
		if (Actual2) {
			Actual2->LEDHighSpeed->Checked = true;
            Actual2->LEDHighSpeed->OnMouseDown = SignalMouseDownLocal;
            Actual2->LEDHighSpeed->OnMouseMove = LayoutMouseMove;
			Actual2->LEDMediumSpeed->Checked = true;
            Actual2->LEDMediumSpeed->OnMouseDown = SignalMouseDownLocal;
            Actual2->LEDMediumSpeed->OnMouseMove = LayoutMouseMove;
			if (SignalName != NULL) {
            	Actual2->SignalName->Caption = SignalName;
                }
            Actual2->SignalName->OnMouseDown = SignalMouseDownLocal2;
            Actual2->SignalName->OnMouseMove = LayoutMouseMove;
            Actual2->NumberPlate->OnMouseDown = SignalMouseDownLocal2;
            Actual2->NumberPlate->OnMouseMove = LayoutMouseMove;
			if (NumPlate != 0) {
				Actual2->NumberPlate->Caption = String(NumPlate);
				Actual2->NumberPlate->Visible = true;
				} else {
				Actual2->NumberPlate->Caption = "A";
				Actual2->NumberPlate->Visible = true;
                }
  			if (iAspect != -1) {
				Actual2->Lights((iAspect < 0) ? AsDark : iAspect);  // < 0 means signal is dark
                }
			}
		break;
	case Hd3LED:
		pSignal =  new TLED3Panel(LayoutForm);
		assert(pSignal);
        // Signal number limited to 4 digits
		// alternative
        //pSignal->Name = ((iDir == WB) ? "Westbound" : ((iDir == EB) ? "Eastbound" : ((iDir == NB) ? "Northbound" : "Southbound"))) + Format((String)"%4.4d", ARRAYOFCONST((SignalNo)));
		pSignal->Name = ((iDir == WB) ? "WB" : ((iDir == EB) ? "EB" : ((iDir == NB) ? "NB" : "SB"))) + Format((String)"%4.4d", ARRAYOFCONST((SignalNo)));
		pSignal->Parent = ImageScrollBox;
        TLED3Panel * Actual3;
		Actual3 = dynamic_cast <TLED3Panel *> (pSignal);
		if (Actual3) {
			Actual3->LEDHighSpeed->Checked = true;
            Actual3->LEDHighSpeed->OnMouseDown = SignalMouseDownLocal;
            Actual3->LEDHighSpeed->OnMouseMove = LayoutMouseMove;
			Actual3->LEDMediumSpeed->Checked = true;
            Actual3->LEDMediumSpeed->OnMouseDown = SignalMouseDownLocal;
            Actual3->LEDMediumSpeed->OnMouseMove = LayoutMouseMove;
			Actual3->LEDLowSpeed->Checked = true;
            Actual3->LEDLowSpeed->OnMouseDown = SignalMouseDownLocal;
            Actual3->LEDLowSpeed->OnMouseMove = LayoutMouseMove;
            if (SignalName != NULL) Actual3->SignalName->Caption = SignalName;
            Actual3->SignalName->OnMouseDown = SignalMouseDownLocal3;
            Actual3->SignalName->OnMouseMove = LayoutMouseMove;
            Actual3->NumberPlate->OnMouseDown = SignalMouseDownLocal3;
            Actual3->NumberPlate->OnMouseMove = LayoutMouseMove;
            if (NumPlate != 0) {
                Actual3->NumberPlate->Caption = String(NumPlate);
                Actual3->NumberPlate->Visible = true;
                } else {
                Actual3->NumberPlate->Caption = "A";
   	            Actual3->NumberPlate->Visible = true;
                }
   			if (iAspect != -1) {
	            Actual3->Lights((iAspect < 0) ? AsDark : iAspect);  // < 0 means signal is dark
                }
            }
    	break;
	case HdMLED:    // Mike Hewett's 3 light signal
		pSignal =  new TLEDMPanel(LayoutForm);
		assert(pSignal);
        // Signal number limited to 4 digits
		// alternative
        //pSignal->Name = ((iDir == WB) ? "Westbound" : ((iDir == EB) ? "Eastbound" : ((iDir == NB) ? "Northbound" : "Southbound"))) + Format((String)"%4.4d", ARRAYOFCONST((SignalNo)));
		pSignal->Name = ((iDir == WB) ? "WB" : ((iDir == EB) ? "EB" : ((iDir == NB) ? "NB" : "SB"))) + Format((String)"%4.4d", ARRAYOFCONST((SignalNo)));
		pSignal->Parent = ImageScrollBox;
        TLEDMPanel * ActualM;
  		ActualM = dynamic_cast <TLEDMPanel *> (pSignal);
		if (ActualM) {
			ActualM->LEDHighSpeed->Checked = true;
            ActualM->LEDHighSpeed->OnMouseDown = SignalMouseDownLocal;
            ActualM->LEDHighSpeed->OnMouseMove = LayoutMouseMove;
			ActualM->LEDMediumSpeed->Checked = true;
            ActualM->LEDMediumSpeed->OnMouseDown = SignalMouseDownLocal;
            ActualM->LEDMediumSpeed->OnMouseMove = LayoutMouseMove;
			ActualM->LEDLowSpeed->Checked = true;
            ActualM->LEDLowSpeed->OnMouseDown = SignalMouseDownLocal;
            ActualM->LEDLowSpeed->OnMouseMove = LayoutMouseMove;
            if (SignalName != NULL) ActualM->SignalName->Caption = SignalName;
            ActualM->SignalName->OnMouseDown = SignalMouseDownLocal3;
            ActualM->SignalName->OnMouseMove = LayoutMouseMove;
            ActualM->NumberPlate->OnMouseDown = SignalMouseDownLocal3;
            ActualM->NumberPlate->OnMouseMove = LayoutMouseMove;
            if (NumPlate != 0) {
                ActualM->NumberPlate->Caption = String(NumPlate);
                ActualM->NumberPlate->Visible = true;
                } else {
                ActualM->NumberPlate->Caption = "A";
   	            ActualM->NumberPlate->Visible = true;
                }
   			if (iAspect != -1) {
	            ActualM->Lights((iAspect < 0) ? AsDark : iAspect);  // < 0 means signal is dark
                // Send initial value of signal
                //SendSignal(SignalNo, (iAspect < 0) ? AsDark : iAspect);    // This causes a fault in SendSignal
                                                    // I had to move it back up into CreateSignal()
                }
            }
    	break;
    default:
    	assert(false);
        break;
    }

// Adjust top left corner if the Image is displayed and if it is scrolled
if (LayoutForm) {
	Col = Col - LayoutForm->ImageScrollBox->HorzScrollBar->Position;
	Row = Row - LayoutForm->ImageScrollBox->VertScrollBar->Position;
    }
pSignal->Top = Row;
pSignal->Left = Col;

pSignal->Cursor = crHandPoint;
//pSignal->OnMouseDown = SignalMouseDownLocal;

// save pointer to Signal in Objects
SignalGrid->Objects[SIGNALNUM][SignalNo] = pSignal;
// Signalname is stored here for case of a signal edit
SignalGrid->Cells[SIGNALNAME][SignalNo] = SignalName;	// put the name in the string grid (2-3-4 characters)
}
//---------------------------------------------------------------------------

String __fastcall TLayoutForm::GetFlagName(int FlagNo)
{
// return the VCL "Caption" property of the flag.
// FlagNo is 1 to FLAGLIMIT
assert(FlagNo <= FLAGLIMIT);
// This routine should only access the flag's VCL "Caption" property
if (LayoutForm && AllLED[FlagNo - 1]) {
	return AllLL[FlagNo - 1]->Caption; 	// adjust for 1 based numbering in Lua
    }
return "No Such Flag #";
}
//---------------------------------------------------------------------------

int __fastcall TLayoutForm::SetFlagName(int FlagNo, String FlagName)
{
// Set the VCL "Caption" property of the LED and the FlagGrid "Cell".
// FlagNo is 1 to FLAGLIMIT
assert(FlagNo <= FLAGLIMIT);
// This routine should only affect the VCL "Caption" property of the flag and the FlagGrid "Cell".
if (LayoutForm && AllLED[FlagNo - 1]) {	// FlagNo is 1 to 100
	FlagGrid->Cells[FLAGNAME][FlagNo] = FlagName;
	AllLL[FlagNo - 1]->Caption = FlagName;	// adjust for 1 based numbering in Lua
    return FlagNo;
    }
return -1;	// no such flag number
}
//---------------------------------------------------------------------------

int __fastcall TLayoutForm::SetFlag(int FlagNo, int iState)
{
// set a boolean into the flag's "Tag" property and set the "Font" property of the text.
// FlagNo is 1 to FLAGLIMIT
assert(FlagNo <= FLAGLIMIT);
// This routine should only affect the VCL "ColorOff", "Checked" and "Tag" properties of
// the LED and the "Font" property of the text
TAbLED *pAbLED = AllLED[FlagNo - 1];
if (LayoutForm && pAbLED) {					// FlagNo is 1 to 100
    pAbLED->LED->ColorOff = clRed;			// programmatic occupancy flag color
	pAbLED->Checked = (iState == 0);
    pAbLED->Tag = iState; // Engine number occupying the block and its direction put in the Tag
    if (iState == MANUALFLAG) {
        pAbLED->LED->ColorOff = clYellow;
        }
	if (ToggleFlagNameColor) {
    	// set the Flag Name text color if requested by the user
		AllLL[pAbLED->Tag - 1]->Font->Color = pAbLED->Checked ? clBlack : clRed;
    	}
    // Set hint for the user
    if (iState == 0) {
        pAbLED->Hint = "Flag #" + String(FlagNo) + " " + AllLL[FlagNo - 1]->Caption + " Set to Unoccupied";
    	} else {
        if (iState == MANUALFLAG || iState == SOFTOCCUPY) {
        	pAbLED->Hint = "Flag #" + String(FlagNo) + " " + AllLL[FlagNo - 1]->Caption +
                ((iState == MANUALFLAG) ? " Reserved" : " Soft Reserved");
            } else {
            // Programmatically set value (Engine number and Direction)
            String sDir;
            switch ((iState & 0xFF00) >> 8) {
                case WB:
                    sDir = "WB ";
                    break;
                case EB:
                    sDir = "EB ";
                    break;
                case NB:
                    sDir = "NB ";
                    break;
                case SB:
                    sDir = "SB ";
                    break;                    
                default:
                    sDir = "";
                    break;
                }
            pAbLED->Hint = "Flag #" + String(FlagNo) + " " + AllLL[FlagNo - 1]->Caption + " by " + sDir + "Engine #" + String(iState & 0x00FF);
            }
        }
    return iState;
    }
return -1;	// no such flag number
}
//---------------------------------------------------------------------------

int __fastcall TLayoutForm::GetFlag(int FlagNo)
{
// return the VCL "Tag" property of the flag as the engine number and the direction of travel
// FlagNo is 1 to FLAGLIMIT
assert(FlagNo <= FLAGLIMIT);
// return -1 if the FlagNo does not exist
// This routine should only access the VCL "Tag" property of the flag
TAbLED *pAbLED = AllLED[FlagNo - 1];
if (LayoutForm && pAbLED) {	// FlagNo is 1 to 100 with the direction
	// return the engine number that is occupying the Flag
	return pAbLED->Tag;	// adjust for 1 based numbering in Lua
    }
return -1;	// no such flag number
}
//---------------------------------------------------------------------------

void __fastcall TLayoutForm::BrowseforBitmapFileButtonClick(TObject *Sender)
{
OpenDialog->DefaultExt = "bmp";
OpenDialog->Filter = "Bitmap files (*.bmp)|*.bmp";
OpenDialog->Options.Clear();
OpenDialog->Options = OpenDialog->Options << ofFileMustExist << ofHideReadOnly;
OpenDialog->InitialDir = ExtractFilePath(Caption);
OpenDialog->Title = "Select Bitmap File for Layout Background";
OpenDialog->FileName = Caption;

if (OpenDialog->Execute()) {
	// file picked
	Caption = OpenDialog->FileName;
    FlagDataFile = ChangeFileExt(Caption, ".ini");
    LoadFlagData();
    ShowImage(Caption);
	}
// not necessary to redraw all saved Flags as the call to ShowImage() does not wipe them
}
//---------------------------------------------------------------------------

void __fastcall TLayoutForm::LoadFlagData(void)
{
TStringList * const FlagDat = new TStringList();
assert(FlagDat);		// make sure that construction worked
FlagDat->Sorted = false;

__try {
   	try {
    	if (FileExists(FlagDataFile)) {
   			// read it
   	    	FlagDat->LoadFromFile(FlagDataFile);
   	    	// transfer data into TStringGrid from the TStringList
            FlagGrid->Rows[FLAG_LABEL_ROW]->CommaText = FlagDat->Strings[FLAG_LABEL_ROW];
   	    	for (int FlagNum = FLAG_TOPROW ; FlagNum < FlagGrid->RowCount ; FlagNum++) {
	    		FlagGrid->Rows[FlagNum]->CommaText = FlagDat->Strings[FlagNum];
                int x = FlagGrid->Cells[FLAGX][FlagNum].ToInt();	// adjust for 1 based numbering in Lua
                int y = FlagGrid->Cells[FLAGY][FlagNum].ToInt();	// adjust for 1 based numbering in Lua
                int ord = FlagGrid->Cells[FLAGORD][FlagNum].ToInt();// Text ordinal position
                String FlagName = FlagGrid->Cells[FLAGNAME][FlagNum];
				// BCB4.0 saves as "", CB10.1 as " ". Can't use IsEmpty()
				if (FlagName != "" && FlagName != " ") CreateFlag(FlagNum, x, y, ord, FlagName);
				}
			if (DEBUGGING) DB->Debug_Print("Flag Data Loaded from " + FlagDataFile);
   	    	} else {
           	// Flag Data Files does not exist so create an empty TStringGrid
            FlagGrid->Cells[FLAGNUM][0] = "Flag #";	// index
            FlagGrid->Cells[FLAGX][0] = "X";	// X coord
            FlagGrid->Cells[FLAGY][0] = "Y";	// Y coord
            FlagGrid->Cells[FLAGORD][0] = "O";	// Text ordinal position
            FlagGrid->Cells[FLAGNAME][0] = "Flag Name";	// FlagName
			for (int i = FLAG_TOPROW ; i < FlagGrid->RowCount ; i++) {
		    	FlagGrid->Cells[FLAGNUM][i] = i;	// index
			    FlagGrid->Cells[FLAGX][i] = 0;	// X coord
			    FlagGrid->Cells[FLAGY][i] = 0;	// Y coord
                FlagGrid->Cells[FLAGORD][i] = 0;	// Text ordinal position - zero means don't display Flag Name
				FlagGrid->Cells[FLAGNAME][i] = " ";	// FlagName
			    }
            if (DEBUGGING) DB->Debug_Print("Flag Data Created (Empty)");
           	}
	   	}
	catch (int field) {
   		MainForm->DebugMessage("Saved Flag Data File Field (" + String(field) + ") is Corrupt : " + FlagDataFile);
		Corrupt = true;
       	}
    catch ( ... ){
   		MainForm->DebugMessage("Saved Flag Data File is Corrupt : " + FlagDataFile);
		Corrupt = true;
   		}
    }
	__finally {
    delete FlagDat;
    }
}
//---------------------------------------------------------------------------

void __fastcall TLayoutForm::EditGridButtonClick(TObject *Sender)
{
FlagGrid->Visible = !FlagGrid->Visible;
SignalGrid->Visible = FlagGrid->Visible;
//
if (FlagGrid->Visible) {
	EditGridButton->Hint = "Close the Flags and Signals Definitions Grids";
	} else {
	EditGridButton->Hint = "Directly Edit the Flag and Signal Definitions";
    }
}
//---------------------------------------------------------------------------

void __fastcall TLayoutForm::TransparentTextMenuItemClick(TObject *Sender)
{
TransparentTextMenuItem->Checked = !TransparentTextMenuItem->Checked;
TransparentText = TransparentTextMenuItem->Checked;
// redraw all text
RedrawLayoutClick(Sender);
}
//---------------------------------------------------------------------------

void __fastcall TLayoutForm::ToggleFlagNameColorMenuItemClick(TObject *Sender)
{
ToggleFlagNameColorMenuItem->Checked = !ToggleFlagNameColorMenuItem->Checked;
ToggleFlagNameColor = ToggleFlagNameColorMenuItem->Checked;
}
//---------------------------------------------------------------------------

void __fastcall TLayoutForm::RedrawLayoutClick(TObject *Sender)
{
// redraw the layout
// delete all flags and redraw
for (int FlagNum = 1 ; FlagNum < FlagGrid->RowCount ; FlagNum++) {	
    int x = FlagGrid->Cells[FLAGX][FlagNum].ToInt();	// adjust for 1 based numbering in Lua
    int y = FlagGrid->Cells[FLAGY][FlagNum].ToInt();	// adjust for 1 based numbering in Lua
    int ord = FlagGrid->Cells[FLAGORD][FlagNum].ToInt();// text ordinal position
	String FlagName = FlagGrid->Cells[FLAGNAME][FlagNum];
	//if (!FlagName.IsEmpty()) CreateFlag(FlagNum, x, y, ord, FlagName);
	// BCB4.0 saves as "", CB10.1 as " ". Can't use IsEmpty()
	if (FlagName != "" && FlagName != " ") CreateFlag(FlagNum, x, y, ord, FlagName);
    }
// delete all signals and redraw
for (int iSignalNo = 1 ; iSignalNo < SignalGrid->RowCount ; iSignalNo++) {	
    int X = SignalGrid->Cells[SIGNALX][iSignalNo].ToInt();	// adjust for 1 based numbering in Lua
    int Y = SignalGrid->Cells[SIGNALY][iSignalNo].ToInt();	// adjust for 1 based numbering in Lua
    if (SignalGrid->Cells[SIGNALMODE][iSignalNo].ToInt() > 0) {
    	TPanel * Hd = dynamic_cast <TPanel *> (SignalGrid->Objects[SIGNALNUM][iSignalNo]);
        assert(Hd);
        Hd->Left = X;
        Hd->Top = Y;
		Hd->Visible = false;
        Hd->Visible = true;
        }
    }
}
//---------------------------------------------------------------------------

void __fastcall TLayoutForm::DeleteFlagClick(TObject *Sender)
{
// clear the flag
int aRow = FlagGrid->Row;

int retv = MessageDlg("Delete Flag # " + String(aRow), mtConfirmation, TMsgDlgButtons() << mbOK << mbCancel, 0);
if (retv == mrCancel) {
	return;
    }
// delete the flag and text
if (AllLED[aRow - 1]) {
	delete AllLED[aRow - 1];	 	// adjust for 1 based numbering in Lua
	AllLED[aRow - 1] = NULL;
    }
if (AllLL[aRow - 1]) {
	delete AllLL[aRow - 1];		// adjust for 1 based numbering in Lua
    AllLL[aRow - 1] = NULL;
    }
FlagGrid->Cells[FLAGX][aRow] = 0;	// X coord
FlagGrid->Cells[FLAGY][aRow] = 0;	// Y coord
FlagGrid->Cells[FLAGORD][aRow] = 0;	// text ordinal position
FlagGrid->Cells[FLAGNAME][aRow] = " ";	// FlagName
}
//---------------------------------------------------------------------------

void __fastcall TLayoutForm::FlagGridKeyDown(TObject *Sender, WORD &Key,
      TShiftState Shift)
{
assert(KeyPreview);
switch(Key) {
	case VK_RETURN:
		RedrawLayoutClick(Sender);
        Key = 0;	// handled
    	return;
    case VK_UP:
        if (Shift.Contains(ssCtrl)) {
        	FlagGrid->Top -= 20;
	        Key = 0;	// handled
            }
    	return;
    case VK_DOWN:
        if (Shift.Contains(ssCtrl)) {
        	FlagGrid->Top += 20;
    	    Key = 0;	// handled
            }
    	return;
    case VK_LEFT:
        if (Shift.Contains(ssCtrl)) {
        	FlagGrid->Left -= 20;
	        Key = 0;	// handled
            }
    	return;
    case VK_RIGHT:
        if (Shift.Contains(ssCtrl)) {
        	FlagGrid->Left += 20;
	        Key = 0;	// handled
            }
    	return;
    }
}
//---------------------------------------------------------------------------

void __fastcall TLayoutForm::SignalGridKeyDown(TObject *Sender, WORD &Key,
      TShiftState Shift)
{
assert(KeyPreview);
switch(Key) {
	case VK_RETURN:
		RedrawLayoutClick(Sender);
        Key = 0;	// handled
    	return;
    case VK_UP:
        if (Shift.Contains(ssCtrl)) {
        	SignalGrid->Top -= 20;
	        Key = 0;	// handled
            }
    	return;
    case VK_DOWN:
        if (Shift.Contains(ssCtrl)) {
        	SignalGrid->Top += 20;
    	    Key = 0;	// handled
            }
    	return;
    case VK_LEFT:
        if (Shift.Contains(ssCtrl)) {
        	SignalGrid->Left -= 20;
	        Key = 0;	// handled
            }
    	return;
    case VK_RIGHT:
        if (Shift.Contains(ssCtrl)) {
        	SignalGrid->Left += 20;
	        Key = 0;	// handled
            }
    	return;
    }
}
//---------------------------------------------------------------------------
void __fastcall TLayoutForm::ShowIt()
{
//
SetZOrder(false); 	// put window behind main form
//
// restore the scroll bar positions
ImageScrollBox->HorzScrollBar->Position = HorzScrollPosition;
ImageScrollBox->VertScrollBar->Position = VertScrollPosition;
//
Visible = true;		// make the window visible
}
//---------------------------------------------------------------------------

void __fastcall TLayoutForm::FlagGridMouseDown(TObject *Sender,
      TMouseButton Button, TShiftState Shift, int X, int Y)
{
int AY, AX;
if (Button == mbRight) {
	FlagGrid->MouseToCell(X, Y, AX, AY);
    FlagGrid->Row = AY;
	FlagGrid->Col = AX;
    }
}
//---------------------------------------------------------------------------

void __fastcall TLayoutForm::SignalGridMouseDown(TObject *Sender,
      TMouseButton Button, TShiftState Shift, int X, int Y)
{
int AY, AX;
if (Button == mbRight) {
	SignalGrid->MouseToCell(X, Y, AX, AY);
    SignalGrid->Row = AY;
	SignalGrid->Col = AX;
    }
// insert additonal operations here
}
//---------------------------------------------------------------------------

void __fastcall TLayoutForm::FormCanResize(TObject *Sender, int &NewWidth,
      int &NewHeight, bool &Resize)
{
if (Width == NewWidth && Height == NewHeight) {
	// no change so don't ask
	Resize = true;
    return;
    }
// check if it's ok to resize
// debug message
if (DEBUGGINGX(7)) DB->Debug_Print("Layout Window resized from (" + String(Width) + "," +
	String(Height) + ") to (" + String(NewWidth) + "," + String(NewHeight) +
    	")");
//
#ifdef NOTNEEDED
bool bRetv = InputQuery("Resize?", "OK to Resize?", "From (" + String(Width) + "," +
	String(Height) + ") to (" + String(NewWidth) + "," + String(NewHeight) +
    	") ?");
if (!bRetv) {
	Resize = false;
	return;	// entry canceled, skip the flag
    }
#endif
Resize = true;
}
//---------------------------------------------------------------------------

void __fastcall TLayoutForm::ShowCursorCoordsClick(TObject *Sender)
{
// show the cursor position until disabled
ShowCursorCoords->Checked = !ShowCursorCoords->Checked;
}
//---------------------------------------------------------------------------

void __fastcall TLayoutForm::LayoutMouseMove(TObject *Sender,
      TShiftState Shift, int X, int Y)
{
if (!ShowCursorCoords->Checked) return;
// get xy Mouse position
TPoint p_rel_scr = Mouse->CursorPos;
TPoint p_rel_frm = LayoutImage->ScreenToClient(p_rel_scr );
int x = (int) p_rel_frm.x;
int y = (int) p_rel_frm.y;
Coords->Caption = "(" + String(x) + "," + String(y) + ")";
}
//---------------------------------------------------------------------------

void __fastcall TLayoutForm::CreateFlag2Click(TObject *Sender)
{
//
CreateAFlag = true;		// enable the LayoutImageClick() routine to create a flag
ShowCursorCoords->Checked = true;
}
//---------------------------------------------------------------------------

void __fastcall TLayoutForm::LoadSignalData(void)
{
TStringList * const SignalDat = new TStringList();
assert(SignalDat);		// make sure that construction worked
SignalDat->Sorted = false;

__try {
   	try {
    	if (FileExists(SignalDataFile)) {
   			// read it
   	    	SignalDat->LoadFromFile(SignalDataFile);
   	    	// transfer data into TStringGrid from the TStringList
            SignalGrid->Rows[SIGNAL_LABEL_ROW]->CommaText = SignalDat->Strings[SIGNAL_LABEL_ROW];
   	    	for (int SignalNum = SIGNAL_TOPROW ; SignalNum < SignalGrid->RowCount ; SignalNum++) {
	    		SignalGrid->Rows[SignalNum]->CommaText = SignalDat->Strings[SignalNum];
                int Aspect = SignalGrid->Cells[SIGNALASPECT][SignalNum].ToInt();	// adjust for 1 based numbering in Lua
                String SignalName = SignalGrid->Cells[SIGNALNAME][SignalNum];	//  (2-3-4 characters)
				// BCB4.0 saves as "", CB10.1 as " ". Can't use IsEmpty()
                int X = SignalGrid->Cells[SIGNALX][SignalNum].ToInt();
                int Y = SignalGrid->Cells[SIGNALY][SignalNum].ToInt();
                int iMode = SignalGrid->Cells[SIGNALMODE][SignalNum].ToInt();
				int NumPlate = SignalGrid->Cells[SIGNALNUMBERPLATE][SignalNum].ToInt();
				int iDir = SignalGrid->Cells[SIGNALDIR][SignalNum].ToInt();			// WB = 1, EB = 2, NB = 2, SB = 4
                int iType = SignalGrid->Cells[SIGNALTYPE][SignalNum].ToInt();
                int iSP1 = SignalGrid->Cells[SIGNALSP1][SignalNum].ToInt();
				// if number of lights on the signal > 0 then create the signal
				if (iMode) ConstructSignal(SignalNum, iDir, X, Y, SignalName, iMode, Aspect, NumPlate, iType, iSP1);
				}
			if (DEBUGGING) DB->Debug_Print("Signal Data Loaded from " + SignalDataFile);
   	    	} else {
           	// Signal Data Files does not exist so create an empty TStringGrid
            SignalGrid->Cells[SIGNALNUM][0] = "Signal #";			// Index
            SignalGrid->Cells[SIGNALASPECT][0] = "Aspect";			// Color Value
            SignalGrid->Cells[SIGNALMODE][0] = "Mode";		        // Mode Number of the signal
            SignalGrid->Cells[SIGNALNUMBERPLATE][0] = "Num Plate";	// Number Plate
            SignalGrid->Cells[SIGNALNAME][0] = "Signal Name";		// SignalName (2-3-4 characters)
            SignalGrid->Cells[SIGNALX][0] = "X";				// Column
            SignalGrid->Cells[SIGNALY][0] = "Y";				// Row
            SignalGrid->Cells[SIGNALDIR][0] = "Dir";			// Direction
            SignalGrid->Cells[SIGNALTYPE][0] = "Type";          // Type
            SignalGrid->Cells[SIGNALSP1][0] = "SP2";            // Spare
			for (int SignalNum = SIGNAL_TOPROW ; SignalNum < SignalGrid->RowCount ; SignalNum++) {
                SignalGrid->Cells[SIGNALNUM][SignalNum] = SignalNum;		// Index
                SignalGrid->Cells[SIGNALASPECT][SignalNum] = 0;				// not visible
                SignalGrid->Cells[SIGNALMODE][SignalNum] = 0;			// Mode Number of the signal
                SignalGrid->Cells[SIGNALNUMBERPLATE][SignalNum] = 0;		// Number Plate
                SignalGrid->Cells[SIGNALNAME][SignalNum] = " ";				// SignalName (2-3-4 characters)
                SignalGrid->Cells[SIGNALX][SignalNum] = 0;				// Column
				SignalGrid->Cells[SIGNALY][SignalNum] = 0;				// Row
				SignalGrid->Cells[SIGNALDIR][SignalNum] = WB;			// Direction WB = 1, EB = 2, NB = 2, SB = 4
            	SignalGrid->Cells[SIGNALTYPE][SignalNum] = 0;			// Type
            	SignalGrid->Cells[SIGNALSP1][SignalNum] = 0;			// Spare
			    }
            if (DEBUGGING) DB->Debug_Print("Signal Data Created (Empty)");
           	}
	   	}
	catch (int field) {
   		MainForm->DebugMessage("Saved Signal Dat File Field (" + String(field) + ") is Corrupt : " + SignalDataFile);
        Corrupt = true;
       	}
    catch ( ... ){
   		MainForm->DebugMessage("Saved Signal Data File is Corrupt : " + SignalDataFile);
        Corrupt = true;
   		}
    }
	__finally {
    delete SignalDat;
    }
}
//---------------------------------------------------------------------------

bool __fastcall TLayoutForm::Reset(TPanel * pSignal)
{
if (!pSignal) return false;
TLED3Panel * Actual3 = dynamic_cast <TLED3Panel *> (pSignal);
if (Actual3) {
	pSignal->Tag = AsDark;
	Actual3->LEDHighSpeed->Checked = true;
	Actual3->LEDMediumSpeed->Checked = true;
	Actual3->LEDLowSpeed->Checked = true;
	Actual3->SignalName->Caption = "**";
	Actual3->NumberPlate->Visible = false;
    Actual3->NumberPlate->Caption = "";
	Actual3->LEDHighSpeed->LED->ColorOn = clBlack;
	Actual3->LEDMediumSpeed->LED->ColorOn = clBlack;
	Actual3->LEDLowSpeed->LED->ColorOn = clBlack;
	return true;
	}
TLED2Panel * Actual2 = dynamic_cast <TLED2Panel *> (pSignal);
if (Actual2) {
	pSignal->Tag = AsDark;
	Actual2->LEDHighSpeed->Checked = true;
	Actual2->LEDMediumSpeed->Checked = true;
	Actual2->SignalName->Caption =  "**";;
	Actual2->NumberPlate->Visible = false;
    Actual2->NumberPlate->Caption = "";
	Actual2->LEDHighSpeed->LED->ColorOn = clBlack;
	Actual2->LEDMediumSpeed->LED->ColorOn = clBlack;
	return true;
	}
TLED1Panel * Actual1 = dynamic_cast <TLED1Panel *> (pSignal);
if (Actual1) {
	pSignal->Tag = AsDark;
	Actual1->LEDHighSpeed->Checked = true;
	Actual1->SignalName->Caption =  "**";;
	Actual1->NumberPlate->Visible = false;
    Actual1->NumberPlate->Caption = "";
	Actual1->LEDHighSpeed->LED->ColorOn = clBlack;
	return true;
	}
TLEDMPanel * ActualM = dynamic_cast <TLEDMPanel *> (pSignal);
if (ActualM) {
	pSignal->Tag = AsDark;
	ActualM->LEDHighSpeed->Checked = true;
	ActualM->LEDMediumSpeed->Checked = true;
	ActualM->LEDLowSpeed->Checked = true;
	ActualM->SignalName->Caption = "**";
	ActualM->NumberPlate->Visible = false;
    ActualM->NumberPlate->Caption = "";
	ActualM->LEDHighSpeed->LED->ColorOn = clBlack;
	ActualM->LEDMediumSpeed->LED->ColorOn = clBlack;
	ActualM->LEDLowSpeed->LED->ColorOn = clBlack;
	return true;
	}
return false;
}
//---------------------------------------------------------------------------

void __fastcall TLayoutForm::DeleteAllSignals(void)
{
//
for (int iSignalNo = SIGNAL_TOPROW ; iSignalNo < SignalGrid->RowCount ; iSignalNo++) {
    // delete the signal if it exists
    if (SignalGrid->Cells[SIGNALMODE][iSignalNo].ToInt() > 0) {
        TPanel * Hd = dynamic_cast <TPanel *> (SignalGrid->Objects[SIGNALNUM][iSignalNo]);
        if (Hd == NULL) continue;
        delete Hd;
        SignalGrid->Objects[SIGNALNUM][iSignalNo] = NULL;
        SignalGrid->Cells[SIGNALNUM][iSignalNo] = iSignalNo;		// Index
        SignalGrid->Cells[SIGNALASPECT][iSignalNo] = 0;				// not visible
        SignalGrid->Cells[SIGNALMODE][iSignalNo] = 0;			// Mode Number of the signal
        SignalGrid->Cells[SIGNALNUMBERPLATE][iSignalNo] = 0;		// Number Plate
        SignalGrid->Cells[SIGNALNAME][iSignalNo] = " ";				// SignalName (2-3-4 characters)
        SignalGrid->Cells[SIGNALX][iSignalNo] = 0;					// Column
        SignalGrid->Cells[SIGNALY][iSignalNo] = 0;					// Row
        SignalGrid->Cells[SIGNALDIR][iSignalNo] = WB;				// Direction
      	SignalGrid->Cells[SIGNALTYPE][iSignalNo] = 0;				// Type
       	SignalGrid->Cells[SIGNALSP1][iSignalNo] = 0;				// Spare
        }
    }
}
//---------------------------------------------------------------------------

String __fastcall TLayoutForm::GetSignalName(int SignalNo, int &iNumberPlate, int &iType)
{
// return the Signal's name, the Number Plate value and the Signal type
// SignalNo is 1 to SIGNALLIMIT
assert(SignalNo <= SIGNALLIMIT);
int iMode = SignalGrid->Cells[SIGNALMODE][SignalNo].ToInt();
if (iMode > 0) {
	iNumberPlate = SignalGrid->Cells[SIGNALNUMBERPLATE][SignalNo].ToInt();
    iType = SignalGrid->Cells[SIGNALTYPE][SignalNo].ToInt();
	return SignalGrid->Cells[SIGNALNAME][SignalNo];	// (2-3-4 characters)
    }
return "**";	// no such signal number, return "**"
}
//---------------------------------------------------------------------------

int __fastcall TLayoutForm::SetAspect(int SignalNo, int iAspect)
{
// Set the signal aspect
// SignalNo is 1 to SIGNALLIMIT
assert(SignalNo <= SIGNALLIMIT);
TLEDMPanel * ActualM;
TLED3Panel * Actual3;
TLED2Panel * Actual2;
TLED1Panel * Actual1;
//
int iMode = SignalGrid->Cells[SIGNALMODE][SignalNo].ToInt();
if (iMode > 0) {
    SignalGrid->Cells[SIGNALASPECT][SignalNo] = iAspect;
    // If this signal is Registered to a Cab Display, the SetAspect code
    // in the PC Thread will take care of reflecting the new aspect
    // into the Cab.
    ActualM = dynamic_cast <TLEDMPanel *> (SignalGrid->Objects[SIGNALNUM][SignalNo]);   // Mike Hewett's 3 light signal
    if (ActualM) {
        if (iAspect != -1) {	// AsNone == 0
			if (Setup->GeneralSounds->Checked && abs(iAspect) != AsNone) PlaySound(MAKEINTRESOURCE(RELAY_CLICK), HInstance, SND_RESOURCE | SND_ASYNC | SND_NODEFAULT | SND_NOSTOP);
            // udp packet send
            SendSignal(SignalNo, iAspect);
            ActualM->Lights((iAspect < 0) ? AsDark : iAspect);
            }
        return iAspect;;
        }

    Actual3 = dynamic_cast <TLED3Panel *> (SignalGrid->Objects[SIGNALNUM][SignalNo]);
    if (Actual3) {
        if (iAspect != -1) {	// AsNone == 0
			if (Setup->GeneralSounds->Checked && abs(iAspect) != AsNone) PlaySound(MAKEINTRESOURCE(RELAY_CLICK), HInstance, SND_RESOURCE | SND_ASYNC | SND_NODEFAULT | SND_NOSTOP);
            Actual3->Lights((iAspect < 0) ? AsDark : iAspect);
            }
        return iAspect;;
        }

    Actual2 = dynamic_cast <TLED2Panel *> (SignalGrid->Objects[SIGNALNUM][SignalNo]);
    if (Actual2) {
        if (iAspect != -1) {	// AsNone == 0
			if (Setup->GeneralSounds->Checked && abs(iAspect) != AsNone) PlaySound(MAKEINTRESOURCE(RELAY_CLICK), HInstance, SND_RESOURCE | SND_ASYNC | SND_NODEFAULT | SND_NOSTOP);
            Actual2->Lights((iAspect < 0) ? AsDark : iAspect);
            }
        return iAspect;
        }

    Actual1 = dynamic_cast <TLED1Panel *> (SignalGrid->Objects[SIGNALNUM][SignalNo]);
    if (Actual1) {
        if (iAspect != -1) {	// AsNone == 0
            if (Setup->GeneralSounds->Checked && abs(iAspect) != AsNone) PlaySound(MAKEINTRESOURCE(RELAY_CLICK), HInstance, SND_RESOURCE | SND_ASYNC | SND_NODEFAULT | SND_NOSTOP);
            Actual1->Lights((iAspect < 0) ? AsDark : iAspect);
            }
        return iAspect;
        }
        
    return AsNone;	// unknown casting error
	}
return AsNone;	// no such signal number, indicate error
}
//---------------------------------------------------------------------------

int __fastcall TLayoutForm::GetAspect(int SignalNo, int &iMode, int &iType)
{
// return the aspect number
// SignalNo is 1 to SIGNALLIMIT
assert(SignalNo <= SIGNALLIMIT);
int t1 =  SignalGrid->Cells[SIGNALMODE][SignalNo].ToInt();
if (t1 > 0) {
	iMode = t1;
	iType = SignalGrid->Cells[SIGNALTYPE][SignalNo].ToInt();
	// return the aspect
	return SignalGrid->Cells[SIGNALASPECT][SignalNo].ToInt();
    }
return AsNone;	// no such signal number, indicate error
}
//---------------------------------------------------------------------------

void __fastcall TLayoutForm::InfoClick(TObject *Sender)
{
// Info window - You must delete this form in the destructor
//
// Fill the Help Text Memo
//
const String SpeedsFile = String(MainForm->cStartDir) + "\\Speeds.txt";
if (SignalHelp == NULL) {
	SignalHelp = new TRTCHelp(this);
	assert(SignalHelp != NULL);		// make sure that construction worked
    SignalHelp->HelpInfo->Clear();
	SignalHelp->Caption = "Fixed Signals Information";
    // if the Speeds.txt file exists, overwrite the internal data
	if (FileExists(SpeedsFile)) {
	    SignalHelp->HelpInfo->Lines->LoadFromFile(SpeedsFile);
        if (DEBUGGING) DB->Debug_Print("Speed Text Read from " + SpeedsFile);
		} else {
	    SignalHelp->HelpInfo->Lines->Text = Information->Lines->Text;
        }
    }
SignalHelp->Show();		// show Info window	
}
//---------------------------------------------------------------------------

void __fastcall TLayoutForm::CopytoClipboardClick(TObject *Sender)
{
CopyrightClick(Sender);
}
//---------------------------------------------------------------------------

void __fastcall TLayoutForm::CopyrightClick(TObject *Sender)
{
//CopyWindowtoClipboardClick(Sender);
CopyLayouttoClipboardClick(Sender);
}
//---------------------------------------------------------------------------

void __fastcall TLayoutForm::CopyLayouttoClipboardClick(TObject *Sender)
{
//
// Copy the entire canvas to the clipboard
Graphics::TBitmap * bm;
bm = new Graphics::TBitmap;
try
	{
    #if __BORLANDC__ >= 0x720
	// Defined for CB10.1 C++ programs that use the VCL.
	bm->SetSize(LayoutImage->Width, LayoutImage->Height);
    #else
    bm->Width = LayoutImage->Width;
    bm->Height = LayoutImage->Height;
	#endif
	int SaveLeft = Left;
	int SaveHorzPos = ImageScrollBox->HorzScrollBar->Position;
    int SaveVertPos = ImageScrollBox->VertScrollBar->Position;
	int SaveWidth = Width;
    int SaveHeight = Height;
    //
	Left = 0;   // move window to left edge of screen(s)
    // Width cannot be set greater than the actual width of the screen(s)
	Width = LayoutImage->Width + 200; // When I use a large number here, I get an image in the clipboard of the correct size
    Height = LayoutImage->Height  + 200;
    //
	Repaint();
    //                                              LayoutImage->Canvas->Handle here does not copy the signals or the flags
	BitBlt(bm->Canvas->Handle, 0, 0, Width, Height, Canvas->Handle, 0, HidePanel->Height, SRCCOPY);
    //                                                                 HidePanel->Height here so we don't copy the menu/button panel
    //
	Width = SaveWidth;
    Height = SaveHeight;
	Left = SaveLeft;
    ImageScrollBox->HorzScrollBar->Position = SaveHorzPos;
	ImageScrollBox->VertScrollBar->Position = SaveVertPos;
    //
    Clipboard()->Assign(bm);
    }
__finally
	{
	delete bm;
    }
}
//---------------------------------------------------------------------------

void __fastcall TLayoutForm::CopyWindowtoClipboardClick(TObject *Sender)
{

// Copy the Window to the clipboard.
// In BCB4.0, there is no SetSize method associated with TBitmap
// so we have to use Width and Height separately.
//
// see:
// https://stackoverflow.com/questions/3190777/how-do-i-copy-a-form-as-an-image-to-the-clipboard
//
Graphics::TBitmap * bm;
bm = new Graphics::TBitmap;
try
	{
    #if __BORLANDC__ >= 0x720
	// Defined for CB10.1 C++ programs that use the VCL.
	bm->SetSize(ClientWidth, ClientHeight);
    #else
    bm->Width = ClientWidth;
    bm->Height = ClientHeight;
    #endif
	BitBlt(bm->Canvas->Handle, 0, 0, ClientWidth, ClientHeight, Canvas->Handle, 0, 0, SRCCOPY);
    Clipboard()->Assign(bm);
    }
__finally
	{
	delete bm;
    }

}
//---------------------------------------------------------------------------

void __fastcall TLayoutForm::MoreInformation1Click(TObject *Sender)
{
HINSTANCE retv = ShellExecute(NULL, TEXT("open"), TEXT("http://www.silogic.com/trains/RTC_Control_Language.html"), NULL, NULL, SW_SHOWNORMAL);
if ((int)retv <= 32) {
	// error
    if (DEBUGGING) DB->Debug_Print("Shell Execute error - return value = " + String((int) retv));
    }
}
//---------------------------------------------------------------------------

void __fastcall TLayoutForm::ViewLayoutDirClick(TObject *Sender)
{
//
HINSTANCE retv = ShellExecute(Handle,
       	NULL,
        TEXT("explorer"),		// Program to Run
        ExtractFilePath(Caption).c_str(),			// Passed Parameter
        NULL,	// Directory
        SW_SHOW);
if ((int)retv <= 32) {
	// error
    if (DEBUGGING) DB->Debug_Print("Shell Execute error - return value = " + String((int) retv));
    }
}
//---------------------------------------------------------------------------

void __fastcall TLayoutForm::StartMultiTX()     // this is never used
{
    // Construct the Radio TX WiFi Port
    RadioSend = new TWSocket(NULL);
    assert(RadioSend);	// make sure constructor worked
    //
    RadioSend->OnDataSent = RadioSendDataSent;
    RadioSend->OnSessionClosed = RadioSendSessionClosed;
    RadioSend->OnSessionConnected = RadioSendSessionConnected;
    RadioSend->ReuseAddr 			= true;
    RadioSend->Proto 				= "udp";
    RadioSend->Port  				= 8882;
    // http://wiki.overbyte.eu/wiki/index.php/TWSocket.MultiCast (example of Receive)
    // https://en.delphipraxis.net/topic/1429-multicast-configuration/ (TX shows Addr as "NIC to bind to")
    // http://www.mestdagh.biz/ics/faq/TWSocket.html#MulticastBF
    RadioSend->Addr  			= MulticastAddress;
    //RadioSend->MultiCastAddrStr = MulticastAddress;	// not used for TX, use Addr
    RadioSend->MultiCast 		= true;
    //RadioSend->Connect();
    while(RadioWiFiTXConnected == false) {
    	Sleep(10);
        }
    if (DEBUGGING) DB->Debug_Print("Layout TX udp WiFi ready to send (multicast) to IP " + RadioSend->Addr + " - Port " + RadioSend->Port);
}
//---------------------------------------------------------------------------

void __fastcall TLayoutForm::StartTX()
{
    // Construct the Radio TX WiFi Port
    RadioSend = new TWSocket(NULL);
    assert(RadioSend);	// make sure constructor worked
    //
    RadioSend->OnDataSent = RadioSendDataSent;
    RadioSend->OnSessionClosed = RadioSendSessionClosed;
    RadioSend->OnSessionConnected = RadioSendSessionConnected;
    RadioSend->ReuseAddr 			= true;
    RadioSend->Proto 				= "udp";
    RadioSend->Port  				= 8882;
    RadioSend->Addr  			= TXAddress;
    RadioSend->MultiCast 		= false;
    if (DEBUGGING) DB->Debug_Print("Layout TX udp WiFi ready to send (unicast) to IP " + RadioSend->Addr + " - Port " + RadioSend->Port);
}
//---------------------------------------------------------------------------

void __fastcall TLayoutForm::SendSignal(int SignalNo, int AspectNo)
{
// Send a UDP packet to set the aspect.
if (Signals[SignalNo].Found) {
    String Line = String(SignalNo) + "#" + String(AspectNo) + "#";
    if (DEBUGGING) DB->Debug_Print("TX to signal #" + String(SignalNo) + " - Aspect: " + AspectNames[abs(AspectNo)]);
    RecvDataReady = false;
    RadioSend->Proto = "udp";
    RadioSend->Addr  = Signals[SignalNo].Addr;      //TXAddress;
    RadioSend->Port  = Signals[SignalNo].Port;
    RadioSend->Connect();
    RadioSend->SendStr(Line);   // udp often drops the packet
    RadioSend->Close();
    LayoutDataSent = false;
    }
return;
//
}
//---------------------------------------------------------------------------

void __fastcall TLayoutForm::RadioSendDataSent(TObject *Sender, WORD ErrCode)
{
LayoutDataSent = true;		// indicate that Internal sent buffer is completely delivered to winsock.
if (ErrCode > 0) {
	if (DEBUGGINGX(4)) DB->Debug_Print("Layout TX WiFi Data Sent - ErrCode = " + String(ErrCode));
    }
}
//---------------------------------------------------------------------------

void __fastcall TLayoutForm::RadioSendSessionClosed(TObject *Sender,
      WORD ErrCode)
{
RadioWiFiTXConnected = false;
if (DEBUGGINGX(4)) DB->Debug_Print("Layout TX WiFi disconnected from Port " + RadioSend->Port);
}
//---------------------------------------------------------------------------

void __fastcall TLayoutForm::RadioSendSessionConnected(TObject *Sender,
      WORD ErrCode)
{
if (ErrCode != 0) {
	if (DEBUGGING) DB->Debug_Print("Can't connect to Layout TX WiFi. Error #" + String(ErrCode));
	} else {
    RadioWiFiTXConnected = true;
	if (DEBUGGINGX(4)) DB->Debug_Print("Layout TX WiFi connected to Port " + RadioSend->Port);
	}
}
//---------------------------------------------------------------------------
void __fastcall TLayoutForm::StartRX()
{
    RadioRecv = new TWSocket(NULL);
    assert(RadioRecv);	// make sure constructor worked

    RadioRecv->OnDataAvailable = RadioRecvDataAvailable;
    RadioRecv->OnSessionClosed = RadioRecvSessionClosed;
    RadioRecv->OnSessionConnected = RadioRecvSessionConnected;

    RadioRecv->ReuseAddr 			= true;
    RadioRecv->Proto   				= "udp";
    RadioRecv->Addr    				= "0.0.0.0";	// Listen on all available network connections simultaniously
    //RadioRecv->Addr    				= WSocket_inet_ntoa(FServerAddr);	// Listen to one network connection only
    RadioRecv->Port    				= 8882;
    // https://arduino-esp8266.readthedocs.io/en/latest/esp8266wifi/udp-examples.html
	RadioRecv->MultiCast 		= false;
    RadioRecv->MultiCastAddrStr = "";

    try {
        RadioRecv->Listen();
        while(RadioWiFiRXConnected == false) {
    		Sleep(10);
	        }
        if (DEBUGGING) DB->Debug_Print("Layout RX WiFi (Unicast) listening on Port " + RadioRecv->Port);
        } catch (const ESocketException &E) {
            // The socket is probably already in use
            if (DEBUGGING) DB->Debug_Print("Layout RX WiFi error : " + E.Message);
            throw;
        }

    RecvDataReady = false;
}
//---------------------------------------------------------------------------

void __fastcall TLayoutForm::RadioRecvSessionClosed(TObject *Sender,
      WORD ErrCode)
{
RadioWiFiRXConnected = false;
if (DEBUGGINGX(4)) DB->Debug_Print("Layout RX WiFi disconnected from Port " + RadioRecv->Port);
}
//---------------------------------------------------------------------------

void __fastcall TLayoutForm::RadioRecvSessionConnected(TObject *Sender,
      WORD ErrCode)
{
if (ErrCode != 0) {
	if (DEBUGGING) DB->Debug_Print("Can't connect to Layout RX WiFi. Error #" + String(ErrCode));
	} else {
   	RadioWiFiRXConnected = true;
	if (DEBUGGINGX(4)) DB->Debug_Print("Layout RX WiFi connected to Port " + RadioRecv->Port);
	}
}
//---------------------------------------------------------------------------
void __fastcall TLayoutForm::RadioRecvDataAvailable(TObject *Sender,
      WORD ErrCode)
{
if (ErrCode != 0) {
	if (DEBUGGINGX(4)) DB->Debug_Print("Layout RX WiFi Connection Error " + String(ErrCode));
	return;
	}


int DetectorLen = sizeof(Detector);
int Len = RadioRecv->ReceiveFrom(cBuffer, sizeof(cBuffer), Detector, DetectorLen);
if (Len <  0) {
    // ignore packets in error
    return;
    }

if (DEBUGGINGX(5)) {
	DB->Debug_Print("Layout RX WiFi Dotted Host Address = " + String(WSocket_inet_ntoa(FServerAddr)));
	DB->Debug_Print("Layout RX WiFi Sin Port = " + String(Detector.sin_port));
	DB->Debug_Print("Layout RX WiFi Dotted Sin Address = " + String(WSocket_inet_ntoa(Detector.sin_addr)));
    }

if (Len == 0) {
    if (DEBUGGING) DB->Debug_Print("Layout RX WiFi Connection Closed in OnDataAvaiable()");
    return;
    }

if (Len > 0) {
    cBuffer[Len] = '\0';				// NULL Terminate
	if (DEBUGGING) DB->Debug_Print("Layout UDP RX " + String((char *)cBuffer));
    RecvDataReady = true;
    }

}
//---------------------------------------------------------------------------


