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

© Copyright 2019 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
//---------------------------------------------------------------------------
/*

*/
//---------------------------------------------------------------------------
/*
Drag and Drop uses TDragObject which works in both BCB 4.0 and CB 10.1.
If this program were only for CB 10.1, we could have used TDragObjectEx which
is deleted automatically when the drop is complete. With TDragObject, I have to
create it in the constructor and then delete it in the destructor.
*/
//---------------------------------------------------------------------------

#include <vcl.h>
#include <tchar.h>
#pragma hdrstop

// RTC Near Field Communications
#include "RTCMegaMaster.h"
#include "RTC_NFC.h"

#include "Engine_Commands.h"
#include "Train_Control.h"
#include "Timer_Thread.h"
#include "PC_Thread.h"
#include "Prog_Control.h"
#include "AIU_Control.h"
#include "Op_Control.h"
#include "Volume_Control.h"
#include "Setup_Control.h"
#include "Debug_Controls.h"
#include "DL_Thread.h"
#include "Conv_Mode.h"
#include "DCS_Setup_Controls.h"
#include "LashUp_Selection.h"
#include "Quick_Controls.h"
#include "Route_Scene.h"
#include "Hot_Com.h"
#include "Comm_Thread.h"
#include "Message_Thread.h"
#include "Delay_Win_Msg.h"
#include "DetectedTags.h"
#include "Layout.h"
#include "Clipbrd.hpp"
#include "Route_Scene_Thread.h"
#include <mmsystem.h>

//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma link "_GClass"
#pragma link "AbCBitBt"
#pragma link "HideButton"
#pragma link "AbLED"
#pragma link "AbSwitch"
#pragma link "QuitButton"
#pragma link "AbNumEdit"
#pragma link "ColorButton"
#pragma link "AbDial"
//#pragma link "LED3Panel"
#pragma resource "*.dfm"

//---------------------------------------------------------------------------
extern TCommThread * ptrTIUCommThread;				// pointer to TIU communications thread
extern volatile bool bRFIDCOMConnected;				// RFID Serial Port Connection Flag
extern volatile bool bRFIDUDPWiFiConnected;			// RFID UDP WiFi Port Connection Complete Flag
extern volatile bool bRFIDTCPWiFiConnected;			// RFID TCP WiFi Port Connection Complete Flag
extern TMyTimerThread * ptrTimer_Thread;			// pointer to single Timer Thread
extern TAbNumEdit *ptrPC_ETime[PCMAX];				// pointer(s) to ETime control in each Program Control window
extern TProgram_Control *ptrProgram_Control[PCMAX];	// pointer(s) to Program Control Windows
//---------------------------------------------------------------------------
// Cab Signal Numbers and names
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 String AspectNames[];						// Aspect Names (defined in Train_Control.cpp)
//---------------------------------------------------------------------------

//---------------------------------------------------------------------------
__fastcall TProgram_Control::TProgram_Control(TComponent* Owner)
	: TForm(Owner)
{
assert(false);	// never called since I used the overloaded constructor below
}
//---------------------------------------------------------------------------
__fastcall TProgram_Control::TProgram_Control(TComponent* Owner, int Engine, int Remote, int TIU, int PCNo)
	: TForm(Owner)
{
// grab the Remote & TIU Numbers
MyRemoteNo = Remote;
assert(MyRemoteNo >= 0 && MyRemoteNo <= REMOTEMAX);
MyTIUNo = TIU;
assert(MyTIUNo >= 1 && MyTIUNo <= TIUMAX);

MyPCNo = PCNo;	// Support multiple Program Control windows (1:PCMAX)
assert(PCNo >= 1 && PCNo <= PCMAX);

ptrPC_Thread = NULL;
PCMode = NOT_RUNNING;

// Set default title on creation
Caption = "Program Control " + String(MyPCNo);
Title->Caption = Caption;

LocalTransparencyOverride = false;

//https://stackoverflow.com/questions/6049053/increase-size-limit-of-trichedit
// expand the size of RichEdit control
PCMessages->MaxLength = System::MaxInt-2;

// reset all function buttons
Reset_PC();

MyEngineNo = Engine;
EngineSpin->ValueAsInt = Engine;

if (DEBUGGING || Setup->Show_Pause_Button) PauseButton->Visible = true;

RegFileRootKey =  MainForm->RegFileRootKey;

if (DEBUGGING) {
    PCThreadLED->Visible = true;	// make the PC Thread LED visible
    PCThreadLED->Checked = false;	// extinquish PC Thread LED
    }

RecallIndex = 0;

FunctionPCEvent = CreateEvent(NULL, false, false, NULL);	// Auto Reset - nonsignaled initial state
assert(FunctionPCEvent != NULL);
LoopPCEvent = CreateEvent(NULL, false, false, NULL);	// Auto Reset - nonsignaled initial state
assert(LoopPCEvent != NULL);
SetupPCEvent = CreateEvent(NULL, false, false, NULL);	// Auto Reset - nonsignaled initial state
assert(SetupPCEvent != NULL);
TerminationPCEvent = CreateEvent(NULL, false, false, NULL);	// Auto Reset - nonsignaled initial state
assert(TerminationPCEvent != NULL);
CleanupPCEvent = CreateEvent(NULL, false, false, NULL);	// Auto Reset - nonsignaled initial state
assert(CleanupPCEvent != NULL);
EventPCEvent = CreateEvent(NULL, false, false, NULL);	// Auto Reset - nonsignaled initial state
assert(EventPCEvent != NULL);
InterpreterPCEvent = CreateEvent(NULL, false, false, NULL);	// Auto Reset - nonsignaled initial state
assert(InterpreterPCEvent != NULL);
InputPCEvent = CreateEvent(NULL, false, false, NULL);	// Auto Reset - nonsignaled initial state
assert(InputPCEvent != NULL);

MRUComboBox->DropDownCount = MRUMAX;

// Load strings into lua interactive Combo Box if file exists
if (FileExists(String(MainForm->cStartDir) + "\\LuaHistoryPC" + String(MyPCNo) + ".txt")) {
	InteractiveCommandComboBox->Items->LoadFromFile(String(MainForm->cStartDir) + "\\LuaHistoryPC" + String(MyPCNo) + ".txt");
	InteractiveCommandComboBox->ItemIndex = -1;
	}

// Experimental - use Drop Down Menu style in CB10.1
#if __BORLANDC__ >= 0x720
enum DECLSPEC_DENUM TButtonStyle : unsigned char { bsPushButton, bsCommandLink, bsSplitButton };
// bsSplitButton defined in Vcl.StdCtrls.hpp is really not defined
BrowseforFileButton->Style = (TCustomButton::TButtonStyle)bsSplitButton;
BrowseforFileButton->DropDownMenu = FilePopupMenu;
//
// bsSplitButton defined in Vcl.StdCtrls.hpp is really not defined
ShowLayoutButton->Style = (TCustomButton::TButtonStyle)bsSplitButton;
ShowLayoutButton->DropDownMenu = PCPopupMenu;
#endif

LuaCommandChange = false;

// Setup tabed buttons
ButtonControl->ActivePage = ButtonSheet1;
//
// Folowing settings required for multiple monitors and save/restore position/size
Position=poDesigned;
DefaultMonitor=dmDesktop;
//
// read Preferences from registry if they exist
TRegIniFile * const RegFile = new TRegIniFile(RegFileRootKey+ "\\Program_Control");
assert(RegFile != NULL);
__try {
    // read MRU from registry if they exist
	//http://bcbjournal.org/articles/vol1/9711/Implementing_a_recent-files_list.htm
	for (int idx = 0 ; idx < MRUMAX ; idx++) {
		String S = RegFile->ReadString("PC" + String(MyPCNo), "MRU" + String(MyPCNo) + String(idx + 1), "");
		if (!S.IsEmpty()) {
			MRUComboBox->Items->Append(S);
			} else {
			break;
			}
		}
	// next line changes MRUComboBox->Text
	MRUComboBox->ItemIndex = RegFile->ReadInteger("PC" + String(MyPCNo), "MRUItemIndex" + String(MyPCNo), -1);
    if (MRUComboBox->ItemIndex < 0) MRUComboBox->ItemIndex = 0;
	MRUComboBox->Hint = MRUComboBox->Text;
   	AutoScroll->Checked = RegFile->ReadInteger("PC" + String(MyPCNo), "AutoScroll", AutoScroll->Checked);
    // update the popup menu attached to the Main window [Prog Ctrl] button
    MainForm->PCPopupMenu->Items->Items[MyPCNo - 1]->Caption =  "PC#" + String(MyPCNo) + " - " + ExtractFileName(MRUComboBox->Text);
    MainForm->PCPopupMenu->Items->Items[MyPCNo - 1]->Hint = MRUComboBox->Text;
	}
__finally {
	// Deallocate RegFile
	delete RegFile;
	}
//
if (MRUComboBox->Text.IsEmpty()) {
    // use WorkingDir
    OpenDialog->InitialDir = MainForm->WorkingDir;
    MRUComboBox->Text = OpenDialog->InitialDir + "\\*.lua";
   	MRUComboBox->Hint = MRUComboBox->Text;
    } else {
    // use directory from Script FileName
	OpenDialog->InitialDir = ExtractFilePath(MRUComboBox->Text);
   	MRUComboBox->Hint = MRUComboBox->Text;
	}

// Added 3 Mar 24 result of Beta Test
if (Setup->DisableEmergencyStopButton->Checked) {
	ESTOP->Visible = false;
    ESTOP->Enabled = false;
	ESTOP->Hint = "Disabled in Setup";
	ESTOP->Cursor = crNo;
	}

HomeSignal = NULL;
DistantSignal = NULL;
SignalMast->Visible = false;
//
EngineSpin->Enabled = true;
StopButtonOp->Visible = false;
}
//---------------------------------------------------------------------------

__fastcall TProgram_Control::~TProgram_Control(void)
{
// save message window always as rich text file - always save the message window
if (PCMessages->Lines->Count > 0) {
    PCMessages->PlainText = false;
    const String FileName = String(MainForm->cStartDir) + "\\" + "PC_Messages" + String(MyPCNo) + ".rtf";
    PCMessages->Lines->SaveToFile(FileName);
    }

ptrPC_ETime[MyPCNo - 1] = NULL;		// stop local elapsed time clock
//
if (MainForm->bSaveRegistryInfo) {
	TRegIniFile * const RegFile = new TRegIniFile(RegFileRootKey + "\\Program_Control");
    assert(RegFile != NULL);
    __try {
	    // erase PC key
    	RegFile->EraseSection("PC" + String(MyPCNo));
        // save window position
    	RegFile->WriteInteger("PC" + String(MyPCNo), "Top", Top > 0 ? Top : 0);
	    RegFile->WriteInteger("PC" + String(MyPCNo), "Height", Height);
        RegFile->WriteInteger("PC" + String(MyPCNo), "Left", Left > 0 ? Left : 0);
        RegFile->WriteInteger("PC" + String(MyPCNo), "Width", Width);
	    // then resave the MRU List
	    int Cnt = MRUComboBox->Items->Count;
	    if (Cnt > 0) {
    	    for (int idx = 0 ; idx < Cnt ; idx++) {
        	    RegFile->WriteString("PC" + String(MyPCNo), "MRU" + String(MyPCNo) + String(idx + 1), MRUComboBox->Items->Strings[idx]);
            	}
	        }
	    RegFile->WriteInteger("PC" + String(MyPCNo), "MRUItemIndex" + String(MyPCNo), MRUComboBox->ItemIndex);
        RegFile->WriteInteger("PC" + String(MyPCNo), "AutoScroll", AutoScroll->Checked);
        }
    __finally {
	    // Deallocate RegFile
    	delete RegFile;
        }
    }
//
//if (DEBUGGINGX(1)) DB->Debug_Print("TProgram_Control(" + String(MyPCNo) + ")::~TProgram_Control ThreadID " + IntToHex((int)GetCurrentThreadId(), 8), (TColor) clGreen);
//
CloseHandle(FunctionPCEvent);	// not really needed - the system closes the handle automatically when the process terminates
FunctionPCEvent = INVALID_HANDLE_VALUE;
CloseHandle(LoopPCEvent);		// not really needed - the system closes the handle automatically when the process terminates
LoopPCEvent = INVALID_HANDLE_VALUE;
CloseHandle(SetupPCEvent);		// not really needed - the system closes the handle automatically when the process terminates
SetupPCEvent = INVALID_HANDLE_VALUE;
CloseHandle(TerminationPCEvent);// not really needed - the system closes the handle automatically when the process terminates
TerminationPCEvent = INVALID_HANDLE_VALUE;
CloseHandle(CleanupPCEvent);	// not really needed - the system closes the handle automatically when the process terminates
CleanupPCEvent = INVALID_HANDLE_VALUE;
CloseHandle(EventPCEvent);	// not really needed - the system closes the handle automatically when the process terminates
EventPCEvent = INVALID_HANDLE_VALUE;
CloseHandle(InterpreterPCEvent);	// not really needed - the system closes the handle automatically when the process terminates
InterpreterPCEvent = INVALID_HANDLE_VALUE;
CloseHandle(InputPCEvent);	// not really needed - the system closes the handle automatically when the process terminates
InputPCEvent = INVALID_HANDLE_VALUE;

// Save strings from lua interactive Combo Box to disk
if (LuaCommandChange && (InteractiveCommandComboBox->Items->Count > 0)) {
	InteractiveCommandComboBox->Items->SaveToFile(String(MainForm->cStartDir) + "\\LuaHistoryPC" + String(MyPCNo) + ".txt");
    }

// clear the pointer to this PC window since it is about to be destroyed
ptrProgram_Control[MyPCNo - 1] = NULL;
}
//---------------------------------------------------------------------------

void __fastcall TProgram_Control::Reset_PC()
{
//
// Reset switches and buttons on the Program Control window
//
LoopProgram = false;
PauseProgram = false;
ETime->ColorDefault = clRed;	// indicate no sketch is running
ResetCntClick(NULL);
//
// traverse the PageControl and set initial values into each TButton
if (DEBUGGINGX(8)) DB->Debug_Print("Reset_PC (" + String(MyPCNo) + "): Component Count (# pages) = " + ButtonControl->ControlCount, (TColor) clGreen);
for (int i = 0 ; i < ButtonControl->ControlCount; i++ ) {
    TComponent *child = ButtonControl->Controls[i];
    if (DEBUGGINGX(8)) DB->Debug_Print("Level 1 " + child->Name, (TColor) clGreen);
	TWinControl *winctrl = dynamic_cast<TWinControl *>(child);
	if (DEBUGGINGX(8)) DB->Debug_Print("Control Count (# panels) = " + String(winctrl->ControlCount), (TColor) clGreen);
    for (int i = 0 ; i < winctrl->ControlCount; i++ ) {
	    TComponent *child2 = winctrl->Controls[i];
    	if (DEBUGGINGX(8)) DB->Debug_Print("Level 2 " + child2->Name, (TColor) clGreen);
        TWinControl *winctrl2 = dynamic_cast<TWinControl *>(child2);
		if (DEBUGGINGX(8)) DB->Debug_Print("Control Count (# buttons) = " + String(winctrl2->ControlCount), (TColor) clGreen);
    	for (int i = 0 ; i < winctrl2->ControlCount; i++ ) {
	    	TButton *child3 = dynamic_cast<TButton *>(winctrl2->Controls[i]);
    		if (DEBUGGINGX(8)) DB->Debug_Print("Level 3 " + child3->Name, (TColor) clGreen);
			child3->Caption = child3->Name.SubString(9,2);
            child3->Tag = child3->Name.SubString(9,2).ToInt();	// value in Tag is used to call this function when
            													// button is pushed
			child3->Hint = "User Defined";
			child3->Enabled = false;
            child3->Font->Style = child3->Font->Style >> fsBold >> fsUnderline;
	    	}
    	}
	}
//
Counter01Label->Caption = "";
Counter01Label->Hint = "User Defined";
Counter01Label->Visible = false;
Counter01->Visible = false;
Counter01Label->ShowAccelChar = false;
Counter02Label->Caption = "";
Counter02Label->Hint = "User Defined";
Counter02Label->Visible = false;
Counter02->Visible = false;
Counter02Label->ShowAccelChar = false;
Counter03Label->Caption = "";
Counter03Label->Hint = "User Defined";
Counter03Label->Visible = false;
Counter03->Visible = false;
Counter03Label->ShowAccelChar = false;
Counter04Label->Caption = "";
Counter04Label->Hint = "User Defined";
Counter04Label->Visible = false;
Counter04->Visible = false;
Counter04Label->ShowAccelChar = false;
Counter05Label->Caption = "";
Counter05Label->Hint = "User Defined";
Counter05Label->Visible = false;
Counter05->Visible = false;
Counter05Label->ShowAccelChar = false;
Counter06Label->Caption = "";
Counter06Label->Hint = "User Defined";
Counter06Label->Visible = false;
Counter06->Visible = false;
Counter06Label->ShowAccelChar = false;

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

void __fastcall TProgram_Control::FormShow(TObject *Sender)
{
TRegIniFile * const RegFile = new TRegIniFile(RegFileRootKey+ "\\Program_Control");
assert(RegFile != NULL);
__try {
    // read window position/size from registry
    Top = RegFile->ReadInteger("PC" + String(MyPCNo), "Top", Top);
    Left = RegFile->ReadInteger("PC" + String(MyPCNo), "Left", 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("PC" + String(MyPCNo), "Height", Height);
    Width = RegFile->ReadInteger("PC" + String(MyPCNo), "Width", Width);
	}
__finally {
	// Deallocate RegFile
	delete RegFile;
	}

TopLeftPos = Point(DB->Left, DB->Top); // save for position of Find dialog
//
//---------------------------------------------------------------------------
/*
https://stackoverflow.com/questions/71032513/deleting-dynamically-created-component

From Remy Lebeau   8 Feb 2022

You should NOT use the Form's OnCreate and OnDestroy events in C++Builder. They
are a Delphi idiom that can lead to undefined behavior in C++ if you are not
careful. OnCreate can fire before your Form's constructor, and OnDestroy can
fire after your Form's destructor. As such, you should use the Form's actual
constructor and destructor instead.

*/
//---------------------------------------------------------------------------
//
DragAcceptFiles (Handle, True);		//  Accept drag and drop from the Explorer

PCThreadLED->Visible = MainForm->bDebug;

if (bRFIDCOMConnected || bRFIDUDPWiFiConnected || bRFIDTCPWiFiConnected) {
    RFIDLED->Visible = true;
    RFIDLED->Checked = true;
	} else {
    RFIDLED->Visible = false;
    RFIDLED->Checked = false;
    }
//if (DEBUGGINGX(1)) DB->Debug_Print("TProgram_Control FormShow (" + String(MyPCNo) + "): ThreadID " + IntToHex((int)GetCurrentThreadId(), 8), (TColor) clGreen);

if (DEBUGGING) PauseButton->Visible = true;
}
//---------------------------------------------------------------------------

void __fastcall TProgram_Control::ESTOPClick(TObject *Sender)
{
// emergency stop
// Send CM_EMERGENCYSTOP message to MainForm  (action can be disabled in Setup)
(void) PostMessage(MainForm->Handle, CM_EMERGENCYSTOP, 0, 0);
}
//---------------------------------------------------------------------------

void __fastcall TProgram_Control::HideButtonClick(TObject *Sender)
{
Hide();	      // Same as setting Visible to false
}
//---------------------------------------------------------------------------

void __fastcall TProgram_Control::CompileButtonClick(TObject *Sender)
{
if (!FileExists(MRUComboBox->Text)) {
    PCMessages->Lines->Append("Script File not found");
    return;
	}

if (ptrPC_Thread != NULL) {
    MainForm->DebugMessagePopup("Warning", "[STOP] the running script or interactive mode before you run another script", "");
    return;
    }

// stop any running PC_Thread
StopPCThread(Sender);
// reset any pending events
ResetEvent(FunctionPCEvent);
ResetEvent(LoopPCEvent);
ResetEvent(SetupPCEvent);
ResetEvent(TerminationPCEvent);
ResetEvent(CleanupPCEvent);
ResetEvent(EventPCEvent);
ResetEvent(InterpreterPCEvent);
ResetEvent(InputPCEvent);
// reset all function buttons
Reset_PC();
//
// start the Program Control Thread
//
// Use the value in the local copy of the EngineSpin control
MyEngineNo = EngineSpin->ValueAsInt;
//
if (ptrPC_Thread == NULL) {
	ETime->ColorDefault = clRed;	// indicate no sketch is running
	ResetCntClick(NULL);
    PCMode = COMPILE_LUASCRIPT;
	// Start a new Program Control Thread for compiling a lua script
	// Create suspended - in BCB 4.0, the thread constructor does not wait for lower level constructors itself.
    // The Execute() routine actually runs before some lower level construction is finished.
    // Don't put constructions in the thread constructor. if you have to, start the thread suspended and then start it running
    // per Remy LeBeau (TeamB) it is a Known bug that was fixed in BCB 6.0
	ptrPC_Thread = new TProg_Control_Thread(true, this, Handle, MyPCNo, MyEngineNo, MyRemoteNo, MyTIUNo, PCMode, MRUComboBox->Text);	// FreeOnTerminate
    assert(ptrPC_Thread != NULL);	// check for successful construction
	Title->Caption = "Compile Script";
 	// When the compile is complete, the thread will set FreeOnTerminate and will exit
    // setup EventData for the compiler - but probably never needed
	ptrPC_Thread->EventData.EventNo = 0;
	ptrPC_Thread->EventData.EngineNo = MyEngineNo;
	ptrPC_Thread->EventData.TIUNo = MyTIUNo;
    #if __BORLANDC__ >= 0x720
    ptrPC_Thread->Start();			// start the thread for CB10.1
    #else
    ptrPC_Thread->Resume();			// start the thread for BCB4.0 - use deprecated in CB10.1, use Start() instead
    #endif

    }
EngineSpin->Enabled = true;
StopButtonOp->Visible = false;
}
//---------------------------------------------------------------------------

void __fastcall TProgram_Control::RunScriptButtonClick(TObject *Sender)
{
if (!FileExists(MRUComboBox->Text)) {
    PCMessages->Lines->Append("Script File not found");
    return;
	}

// must be connected unless debug mode is on
if (!Setup->Comm_Not_Required && (!MainForm->bDebug && !MainForm->IsTIUConnected())) {
    MainForm->DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
	return;
    }

if (ptrPC_Thread != NULL) {
    MainForm->DebugMessagePopup("Warning", "[STOP] the running script or interactive mode before you run another script", "");
    return;
    }

// stop any running PC_Thread
StopPCThread(Sender);
// reset any pending events
ResetEvent(FunctionPCEvent);
ResetEvent(LoopPCEvent);
ResetEvent(SetupPCEvent);
ResetEvent(TerminationPCEvent);
ResetEvent(CleanupPCEvent);
ResetEvent(EventPCEvent);
ResetEvent(InterpreterPCEvent);
ResetEvent(InputPCEvent);
// reset all function buttons
Reset_PC();
// reset Cab Signals
LayoutForm->Reset(HomeSignal);
LayoutForm->Reset(DistantSignal);
SignalMast->Visible = false;
// unregister any registered Cab Signal Display
iCabSignal[MyPCNo - 1][HOMESIGNAL] = iCabSignal[MyPCNo - 1][DISTANTSIGNAL] = 0;
//
// start the Program Control Thread
//
// Use the value in the local copy of the EngineSpin control
MyEngineNo = EngineSpin->ValueAsInt;
//
if (ptrPC_Thread == NULL) {
	ETime->ColorDefault = clRed;	// indicate no sketch is running
	ResetCntClick(NULL);
    PCMode = RUN_LUASCRIPT;
	// Start a new Program Control Thread for running a lua script
	// Create suspended - in BCB 4.0, the thread constructor does not wait for lower level constructors itself.
    // The Execute() routine actually runs before some lower level construction is finished.
    // Don't put constructions in the thread constructor. if you have to, start the thread suspended and then start it running
    // per Remy LeBeau (TeamB) it is a Known bug that was fixed in BCB 6.0
	ptrPC_Thread = new TProg_Control_Thread(true, this, Handle, MyPCNo, MyEngineNo, MyRemoteNo, MyTIUNo, PCMode, MRUComboBox->Text);	// FreeOnTerminate
    assert(ptrPC_Thread != NULL);	// check for successful construction
    PCThreadLED->Checked = true;	// light PC Thread LED
    Title->Caption = "Script Running";
	// When the run is complete, the thread will set FreeOnTerminate and will exit
    // send SetupPCEvent along with EventData to start running the setup() function
	ptrPC_Thread->EventData.EventNo = 0;
	ptrPC_Thread->EventData.EngineNo = MyEngineNo;
	ptrPC_Thread->EventData.TIUNo = MyTIUNo;
	(void) SetEvent(SetupPCEvent);
	#if __BORLANDC__ >= 0x720
    ptrPC_Thread->Start();			// start the thread for CB10.1
    #else
    ptrPC_Thread->Resume();			// start the thread for BCB4.0 - use deprecated in CB10.1, use Start() instead
	#endif
    }
EngineSpin->Enabled = false;
StopButtonOp->Visible = true;		// Make the red [STOP] button visible 
InterpreterInputLinePanel->Visible = true;
}
//---------------------------------------------------------------------------

void __fastcall TProgram_Control::CloseButtonClick(TObject *Sender)
{
// any running script must be stopped
//
if (ptrPC_Thread != NULL) {
    MainForm->DebugMessagePopup("Warning", "[STOP] the running script or interactive mode before you close this window", "");
    return;
    }
//PCMessages->Lines->Clear();
ETime->ColorDefault = clRed;	// indicate no sketch is running
//ResetCntClick(Sender);
StopPCThread(Sender);
// remove the check mark from the PCPopupMenu on the mainform
MainForm->PCPopupMenu->Items->Items[MyPCNo - 1]->Checked = false;	// remove check mark to PCPopupMenu item
Close();
}
//---------------------------------------------------------------------------

void __fastcall TProgram_Control::FormClose(TObject *Sender,
      TCloseAction &Action)
{
Action = caFree;    // All allocated memory for this form is freed
}
//---------------------------------------------------------------------------


void __fastcall TProgram_Control::PauseButtonClick(TObject *Sender)
{
//
PauseProgram = !PauseProgram;
// This pause function is for debugging only, operation in CB10.1 may be unstable
// because Suspend() and Resume() are deprecated.
if (PauseProgram) {
	PCMessages->Lines->Append("[Pause]");
    // Stop ETime update
	ptrPC_ETime[MyPCNo - 1] = NULL;
    // Suspend PC Thread
	if (ptrPC_Thread) ptrPC_Thread->Suspend();	// use deprecated in CB10.1, use wait for Events instead
    Title->Caption = Title->Caption + " (Paused)";
    PCThreadLED->LED->ColorOn = clYellow;	// Change color of PC Thread LED
    // change button to "Resume"
    PauseButton->Caption = "Resume";
    } else {
	PCMessages->Lines->Append("[Resume]");
    // Resume PC_Thread
	if (ptrPC_Thread) {
		ptrPC_Thread->Resume();			// start the thread for BCB4.0 - use deprecated in CB10.1
		}
    // Resume ETime update
    ptrPC_ETime[MyPCNo - 1] = ETime;
    Title->Caption = Title->Caption.SetLength(Title->Caption.Length() - 9);
    PCThreadLED->LED->ColorOn = clLime;		// Change color of PC Thread LED
    // change button to "Pause"
    PauseButton->Caption = "Pause";
    }
}
//---------------------------------------------------------------------------

void __fastcall TProgram_Control::StopButtonOpMouseDown(TObject *Sender,
      TMouseButton Button, TShiftState Shift, int X, int Y)
{
if (StopButtonOp->Visible == false) return;	// only if button is visible and enabled
//
// Stop any sounds
PlaySound(NULL, NULL, SND_ASYNC);
// unregister any registered Cab Signal Display
iCabSignal[MyPCNo - 1][HOMESIGNAL] = iCabSignal[MyPCNo - 1][DISTANTSIGNAL] = 0;
PCMessages->SelAttributes->Color = clRed;
PCMessages->Lines->Append("[STOP]");		// displays in clRed
PCMessages->SelAttributes->Color = clBlack;
// Stop any Sleep(x) or MyWaitForInputLine(x) calls and stop the loop() function
if (ptrPC_Thread) {
	// the thread will set FreeOnTerminate and will exit
	(void) ptrPC_Thread->CleanupRequest();	// Sends CleanupPCEvent
    // ptrPC_Thread set to NULL in PC_Thread destructor
    PCMode = NOT_RUNNING;
    }
PCThreadLED->Checked = false;		// extinquish PC Thread LED
// enable the Engine Spinner & disable the Stop button
EngineSpin->Enabled = true;
// this causes the hourglass wait
//ptrPC_Thread->WaitFor();		// wait for the PC thread to stop
StopButtonOp->Visible = false;
StopButtonOpDummy->Caption = "WAIT";
InterpreterInputLinePanel->Visible = false;
}
//---------------------------------------------------------------------------

void __fastcall TProgram_Control::CMProgCtrlStop(TMessage &Message)
{
// Handle Message : Program Control Stop CM_PROGCTRLSTOP
// stop the script after running cleanup()
// stop the PC_Thread
if (ptrPC_Thread) {
	(void) ptrPC_Thread->CleanupRequest();	// Sends CleanupPCEvent after which the thread will terminate
    }
PCThreadLED->Checked = false;		// extinquish PC Thread LED
// enable the Engine spinner & disable the Stop button
EngineSpin->Enabled = true;
// this causes the hourglass wait
//ptrPC_Thread->WaitFor();		// wait for the PC thread to stop
StopButtonOp->Visible = false;
StopButtonOpDummy->Caption = "WAIT";
InterpreterInputLinePanel->Visible = false;
//
}
//---------------------------------------------------------------------------
void __fastcall TProgram_Control::CMPCThreadTerm(TMessage &Message)
{
// Handle Message : PC Thread has terminated CM_PCTHREADTERM
StopButtonOpDummy->Caption = "READY";
StopButtonOp->Visible = false;
}
//---------------------------------------------------------------------------

void __fastcall TProgram_Control::WMEnable(TWMEnable &Message)
// https://community.idera.com/developer-tools/b/blog/posts/keep-a-non-modal-window-active-when-a-modal-window-is-being-shown
{
// Handle Message : WM_ENABLE
if (!Message.Enabled) {
	PostMessage(Handle, CM_REENABLE, 1, 0);
    }
//
}
//---------------------------------------------------------------------------

void __fastcall TProgram_Control::CMReenable(TMessage &Message)
{
// Handle Message : Reenable window CM_REENABLE
//
EnableWindow(Handle, true);
//
}
//---------------------------------------------------------------------------

void __fastcall TProgram_Control::StopPCThread(TObject *Sender)
{
#ifdef OLDWAY
not used
// stop the PC_Thread then stop the Timer_Thread
if (ptrPC_Thread) {
	(void) ptrPC_Thread->StopRequest();	// Sends TerminationPCEvent
    (void) ptrPC_Thread->WaitFor();		// do not use FreeOnTerminate
	delete ptrPC_Thread;
    // ptrPC_Thread set to NULL in PC_Thread destructor
    PCThreadLED->Checked = false;		// extinquish PC Thread LED
    }
// Stop the Timer
if (ptrTimer_Thread) {
	ptrTimer_Thread->StopRequest();		// Stop the timer thread
	ptrTimer_Thread = NULL;
	RTime->ColorDefault = clRed;		// extinquish Timer indicator
    }
#else
if (ptrPC_Thread) {
	(void) ptrPC_Thread->CleanupRequest();	// Sends CleanupPCEvent
    // ptrPC_Thread set to NULL in PC_Thread destructor
    PCMode = NOT_RUNNING;
    }
#endif
//
}
//---------------------------------------------------------------------------

void __fastcall TProgram_Control::ResetCntClick(TObject *Sender)
{
ETime->Value = 0;	// clear the local elapsed time
//MainForm->Tags->Value = 0;  // total number of packets received from the detectors (includes startup packets)
// Note : above only reset by MainForm
Counter01->Value = 0;
Counter02->Value = 0;
Counter03->Value = 0;
Counter04->Value = 0;
Counter05->Value = 0;
Counter06->Value = 0;

UserLED1->Visible = false;
UserLED1->Checked = false;
UserLED1->LED->ColorOn = clLime;
UserLED2->Visible = false;
UserLED2->Checked = false;
UserLED2->LED->ColorOn = clLime;
UserLED3->Visible = false;
UserLED3->Checked = false;
UserLED3->LED->ColorOn = clLime;
UserLED4->Visible = false;
UserLED4->Checked = false;
UserLED4->LED->ColorOn = clLime;
}
//---------------------------------------------------------------------------
// Workaround for SetLayeredWindowAttributes() to make transparent windows
extern lpfnSetLayeredWindowAttributes m_pSetLayeredWindowAttributes;
//---------------------------------------------------------------------------

void __fastcall TProgram_Control::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 TProgram_Control::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 TProgram_Control::TitleClick(
      TObject *Sender)
{
LocalTransparencyOverride = !LocalTransparencyOverride;	// Toggle transparency locally
}
//---------------------------------------------------------------------------

void __fastcall TProgram_Control::WMDropFiles(TWMDropFiles &message)
{
// Get the number of files dropped
const UINT filecount = DragQueryFile((HDROP) message.Drop, 0xFFFFFFFF, NULL, 0);
if (filecount > 0) {
	//Decoded = false;
	String FullFileName;
	for (UINT ii = 0 ; ii < filecount ; ++ii){
    	Application->ProcessMessages();		// ok to use this during DragQueryFile() processing
		// Get the file name
		TCHAR FileName[MAX_PATH];
		DragQueryFile ((HDROP) message.Drop, ii, FileName, MAX_PATH);
		FullFileName = FileName;
		//Application->MessageBox(FullFileName.c_str(), TEXT("Result of DragQueryFile()"), MB_OK | MB_DEFBUTTON1);
		if (FileExists(FullFileName)) {
			// do something for each file
			PCMessages->Lines->Append("Dropped file " + FullFileName);
			} else {
			continue;
			}
		}
	// use last dropped path/filename
	MRUComboBox->Text = FullFileName;
	MRUComboBox->Hint = MRUComboBox->Text;
    OpenDialog->InitialDir = ExtractFilePath(MRUComboBox->Text);

    // Add new filename to DropDown List
    // insert into MRU List
    // don't insert a duplicate string
    int idx = MRUComboBox->Items->IndexOf(MRUComboBox->Text);
    if (idx == -1) {
        MRUComboBox->Items->Insert(0, MRUComboBox->Text);
        MRUComboBox->ItemIndex = 0;
        if (MRUComboBox->Items->Count > MRUMAX)
        MRUComboBox->Items->Delete(MRUMAX);
        } else {
        // set ItemIndex
        MRUComboBox->ItemIndex = idx;
        }
    // update the popup menu attached to the Main window [Prog Ctrl] button
    MainForm->PCPopupMenu->Items->Items[MyPCNo - 1]->Caption =  "PC#" + String(MyPCNo) + " - " + ExtractFileName(MRUComboBox->Text);
    MainForm->PCPopupMenu->Items->Items[MyPCNo - 1]->Hint = MRUComboBox->Text;
	}

Update();	// redraw Form
DragFinish ((HDROP) message.Drop);
return;
}
//---------------------------------------------------------------------------

void __fastcall TProgram_Control::BrowseforFileButtonClick(TObject *Sender)
{
OpenDialog->DefaultExt = "lua";
OpenDialog->Filter = "Lua Script files (*.lua;*.out)|*.lua;*.out";
OpenDialog->Options.Clear();
OpenDialog->Options = OpenDialog->Options << ofFileMustExist << ofHideReadOnly;
OpenDialog->InitialDir = ExtractFilePath(MRUComboBox->Text);
OpenDialog->Title = "Select Lua Script File";
OpenDialog->FileName = ExtractFileName(MRUComboBox->Text);

if (OpenDialog->Execute()) {
	// file picked
	MRUComboBox->Text = OpenDialog->FileName;
	MRUComboBox->Hint = MRUComboBox->Text;
    MRUComboBoxChange(Sender);		// update the MainForm PCPopupMenu
    // insert into MRU List
    // don't insert a duplicate string
    int idx = MRUComboBox->Items->IndexOf(MRUComboBox->Text);
    if (idx == -1) {
        MRUComboBox->Items->Insert(0, MRUComboBox->Text);
        MRUComboBox->ItemIndex = 0;
        if (MRUComboBox->Items->Count > MRUMAX) MRUComboBox->Items->Delete(MRUMAX);
        } else {
        // set ItemIndex
        MRUComboBox->ItemIndex = idx;
        }
    // update the popup menu attached to the Main window [Prog Ctrl] button
    MainForm->PCPopupMenu->Items->Items[MyPCNo - 1]->Caption =  "PC#" + String(MyPCNo) + " - " + ExtractFileName(MRUComboBox->Text);
    MainForm->PCPopupMenu->Items->Items[MyPCNo - 1]->Hint = MRUComboBox->Text;
	}
}
//---------------------------------------------------------------------------

void __fastcall TProgram_Control::CopyAllButtonClick(TObject *Sender)
{
PCMessages->SelectAll();
PCMessages->CopyToClipboard();
PCMessages->SelLength = 0;	// clear selection highlight
}
//---------------------------------------------------------------------------

void __fastcall TProgram_Control::FunctionButtonClick(TObject *Sender)
{
// must be connected unless in debug mode
if (!Setup->Comm_Not_Required && (!MainForm->bDebug && !MainForm->IsTIUConnected())) {
    MainForm->DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
	return;
    }
//
// Send a FunctionPCEvent to the PC_Thread along with the Tag of the button pushed
if (ptrPC_Thread) {
	ptrPC_Thread->EventData.EventNo = ((TComponent*)Sender)->Tag;	// button number
	// TIUNo only set at press of [Run Script]
	(void) SetEvent(FunctionPCEvent);
    }
}
//---------------------------------------------------------------------------


void __fastcall TProgram_Control::ClearButtonClick(TObject *Sender)
{
PCMessages->Lines->Clear();	// clear the messages
// take down the Cab Display unless a script is running
if (PCMode == NOT_RUNNING) {
    if (HomeSignal) {
        delete HomeSignal;
        HomeSignal = NULL;
        }
    if (DistantSignal) {
        delete DistantSignal;
        DistantSignal = NULL;
        }
    SignalMast->Visible = false;
    ShowCabSignals->Checked = false;
    // unregister any registered Cab Signal Display
    iCabSignal[MyPCNo - 1][HOMESIGNAL] = iCabSignal[MyPCNo - 1][DISTANTSIGNAL] = 0;
    }
}
//---------------------------------------------------------------------------

void __fastcall TProgram_Control::SavePCButtonClick(TObject *Sender)
{
//
// this is basically a copy of the function WritetoDiskClick(Sender) in Train_Control.cpp
String SourceBuffer;
TRichEdit * SelectedBuffer;

SelectedBuffer = PCMessages;
SourceBuffer = "Program_Control_" + String(MyPCNo) + "_";

assert(SelectedBuffer);
if(SelectedBuffer->Lines->Count == 0) return;	// buffer is empty

SaveDialog1->InitialDir = MainForm->WorkingDir;

String NameOfFile;
if (MainForm->PlainTextFile->Checked) {	// Plain text - make .txt file
	SaveDialog1->DefaultExt = String("txt");
	SaveDialog1->Filter = "Text files (*.txt)|*.txt";
    SaveDialog1->FileName = SourceBuffer + MainForm->FileNameSuffix->Text + ".txt";
    } else {							// Rich text - make .rtf file
	SaveDialog1->DefaultExt = String("rtf");
	SaveDialog1->Filter = "Rich text files (*.rtf)|*.rtf";
    SaveDialog1->FileName = SourceBuffer + MainForm->FileNameSuffix->Text + ".rtf";
    }
SaveDialog1->Title = "Save File";
SaveDialog1->Options = SaveDialog1->Options >> ofOverwritePrompt << ofEnableSizing;	// RTC will handle this case

if(SaveDialog1->Execute()) {
	NameOfFile = SaveDialog1->FileName;
    // Change working directory to whatever was selected
    MainForm->WorkingDir = ExtractFilePath(NameOfFile);
    // if file exists, confirm first
    if (FileExists(NameOfFile)) {
        // ask for confirmation
        int result = Application->MessageBox(TEXT("File Exists - Do you want to overwrite?"), Caption.c_str(), MB_YESNO | MB_ICONQUESTION);
    	// if No, exit
    	if (result  == IDNO) {
            return;
        	}
    	}
    // At this point, it's either a new file or the user has indicated overwrite
    // text only file if PlainText is checked
    bool savPlainText = SelectedBuffer->PlainText;
    SelectedBuffer->PlainText = MainForm->PlainTextFile->Checked;   // if Checked set PlainText to true
	SelectedBuffer->Lines->SaveToFile(NameOfFile);
	if (DEBUGGINGX(1)) DB->Debug_Print("WritetoDisk: " + NameOfFile + " - " + String(SelectedBuffer->Lines->Count) + " Lines");
    SelectedBuffer->PlainText = savPlainText;
	return;	// done
	} else {
    return;	// user canceled
    }
}
//---------------------------------------------------------------------------

void __fastcall TProgram_Control::EditScriptClick(TObject *Sender)
{
// Attempt to run a text editor on the script
HINSTANCE retv;
String FileName = "\"" + MRUComboBox->Text + "\"";
String DirName = "\"" + ExtractFilePath(MRUComboBox->Text) + "\"";
// Try these in order:  Notepad++, Metapad, Notepad, Wordpad
String ProgramToRun = "notepad++";
retv = ShellExecute(Handle,
       	NULL,
        ProgramToRun.c_str(),	// Program to Run
        FileName.c_str(),		// Passed Parameter
        DirName.c_str(),		// Directory
        SW_SHOWNORMAL);
if ((int)retv > 32) return;

ProgramToRun = "metapad";
retv = ShellExecute(Handle,
       	NULL,
        ProgramToRun.c_str(),	// Program to Run
        FileName.c_str(),		// Passed Parameter
        DirName.c_str(),		// Directory
        SW_SHOWNORMAL);
if ((int)retv > 32) return;

ProgramToRun = "notepad";
retv = ShellExecute(Handle,
       	NULL,
        ProgramToRun.c_str(),	// Program to Run
        FileName.c_str(),		// Passed Parameter
        DirName.c_str(),		// Directory
        SW_SHOWNORMAL);
if ((int)retv > 32) return;

TCHAR TProgramToRun[256];
ExpandEnvironmentStrings(TEXT("%ProgramFiles%\\Windows NT\\Accessories\\wordpad.exe"), TProgramToRun, sizeof(TProgramToRun) - 2);
/*  the use of the environment variables is deprecated; see SHGetFolderPath() instead (but note that the
exact function you need depends on the versions of Windows you want to support)
*/
retv = ShellExecute(Handle,
       	NULL,
        TProgramToRun,			// Program to Run
        FileName.c_str(),		// Passed Parameter
        DirName.c_str(),		// Directory
        SW_SHOWNORMAL);
if ((int)retv > 32) return;

Application->MessageBox(TEXT("Can't Run your Editor"), TEXT("Warning"), MB_OK  | MB_ICONWARNING);
}
//---------------------------------------------------------------------------

void __fastcall TProgram_Control::RemoveScriptfromList1Click(
      TObject *Sender)
{
// Delete item from list
MRUComboBox->Items->Delete(MRUComboBox->ItemIndex);
MRUComboBox->ItemIndex = -1;	// nothing selected
}
//---------------------------------------------------------------------------

void __fastcall TProgram_Control::CleartheListClick(TObject *Sender)
{
// clear the MRU list
//Applications should use
//ListBox1->Clear();
//rather than
//ListBox1->Items->Clear();
MRUComboBox->Clear();
// use WorkingDir
OpenDialog->InitialDir = MainForm->WorkingDir;
MRUComboBox->Text = OpenDialog->InitialDir + "\\*.lua";
MRUComboBox->Hint = MRUComboBox->Text;
}
//---------------------------------------------------------------------------

void __fastcall TProgram_Control::UserLED1Click(TObject *Sender)
{
UserLED1->LED->ColorOn = clLime;
}
//---------------------------------------------------------------------------

void __fastcall TProgram_Control::UserLED2Click(TObject *Sender)
{
UserLED2->LED->ColorOn = clLime;
}
//---------------------------------------------------------------------------

void __fastcall TProgram_Control::UserLED3Click(TObject *Sender)
{
UserLED3->LED->ColorOn = clLime;
}
//---------------------------------------------------------------------------

void __fastcall TProgram_Control::UserLED4Click(TObject *Sender)
{
UserLED4->LED->ColorOn = clLime;
}
//---------------------------------------------------------------------------

void __fastcall TProgram_Control::NextPCClick(TObject *Sender)
{
//
// send a message to MainForm to display the next Program Control window
// PC window numbers may not be sequential
#ifdef OLDWAY
WPARAM NewPCNo = MyPCNo;
if (NewPCNo == PCMAX) {
	NewPCNo = 1;
    } else {
    NewPCNo += 1;
    }
(void) PostMessage(MainForm->Handle, CM_PCWINDOWOPEN, NewPCNo, 0);
#else
WPARAM NewPCNo = MyPCNo;
// Find the next active Program Control Window
for (int iCount = 1 ; iCount < PCMAX ; iCount++) {
    if (NewPCNo == PCMAX) {
        NewPCNo = 1;
        } else {
        NewPCNo += 1;
        }
    if (ptrProgram_Control[NewPCNo - 1]) {
        (void) PostMessage(MainForm->Handle, CM_PCWINDOWOPEN, NewPCNo, 0);
        break;
        }
    }
#endif
}
//---------------------------------------------------------------------------

void __fastcall TProgram_Control::PreviousPCClick(TObject *Sender)
{
//
// send a message to MainForm to display the previous Program Control window
// PC window numbers may not be sequential
#ifdef OLDWAY
WPARAM NewPCNo = MyPCNo;
if (NewPCNo == 1) {
	NewPCNo = PCMAX;
    } else {
    NewPCNo -= 1;
    }
(void) PostMessage(MainForm->Handle, CM_PCWINDOWOPEN, NewPCNo, 0);
#else
WPARAM NewPCNo = MyPCNo;
// Find the next active Program Control Window
for (int iCount = 1 ; iCount < PCMAX ; iCount++) {
    if (NewPCNo == 1) {
        NewPCNo = PCMAX;
        } else {
        NewPCNo -= 1;
        }
    if (ptrProgram_Control[NewPCNo - 1]) {
        (void) PostMessage(MainForm->Handle, CM_PCWINDOWOPEN, NewPCNo, 0);
        break;
        }
    }
#endif
}
//---------------------------------------------------------------------------

void __fastcall TProgram_Control::InteractiveButtonClick(TObject *Sender)
{
// turn on the command input line and start interactive mode
//
if (PCMode == RUN_LUAINTERACTIVE) {
    MainForm->DebugMessagePopup("Warning", "Interactive mode is already running", "");
    return;
    }
if (ptrPC_Thread != NULL) {
    MainForm->DebugMessagePopup("Warning", "[STOP] the running script before you select interactive mode", "");
    return;
    }

// stop any running PC_Thread
StopPCThread(Sender);
// reset any pending events
ResetEvent(FunctionPCEvent);
ResetEvent(LoopPCEvent);
ResetEvent(SetupPCEvent);
ResetEvent(TerminationPCEvent);
ResetEvent(CleanupPCEvent);
ResetEvent(EventPCEvent);
ResetEvent(InterpreterPCEvent);
ResetEvent(InputPCEvent);
// reset all function buttons
Reset_PC();
// reset Cab Signals
LayoutForm->Reset(HomeSignal);
LayoutForm->Reset(DistantSignal);
SignalMast->Visible = false;
// unregister any registered Cab Signal Display
iCabSignal[MyPCNo - 1][HOMESIGNAL] = iCabSignal[MyPCNo - 1][DISTANTSIGNAL] = 0;
//
// start the Program Control Thread
//
// Use the value in the local copy of the EngineSpin control
MyEngineNo = EngineSpin->ValueAsInt;
//
if (ptrPC_Thread == NULL) {
	ETime->ColorDefault = clRed;	// indicate no sketch is running
	ResetCntClick(NULL);
    PCMode = RUN_LUAINTERACTIVE;
	// Start a new Program Control Thread for interpreting a lua script
	// Create suspended - in BCB 4.0, the thread constructor does not wait for lower level constructors itself.
    // The Execute() routine actually runs before some lower level construction is finished.
    // Don't put constructions in the thread constructor. if you have to, start the thread suspended and then start it running
    // per Remy LeBeau (TeamB) it is a Known bug that was fixed in BCB 6.0
	ptrPC_Thread = new TProg_Control_Thread(true, this, Handle, MyPCNo, MyEngineNo, MyRemoteNo, MyTIUNo, PCMode, MRUComboBox->Text);	// FreeOnTerminate
    assert(ptrPC_Thread != NULL);	// check for successful construction
    PCThreadLED->Checked = true;	// light PC Thread LED
    Title->Caption = "Lua Interactive Mode";
	// When interactive mode is complete, the thread will set FreeOnTerminate and will exit
    // setup EventData for the interpreter
	ptrPC_Thread->EventData.EventNo = 0;
	ptrPC_Thread->EventData.EngineNo = MyEngineNo;
	ptrPC_Thread->EventData.TIUNo = MyTIUNo;
    #if __BORLANDC__ >= 0x720
    ptrPC_Thread->Start();			// start the thread for CB10.1
    #else
    ptrPC_Thread->Resume();			// start the thread for BCB4.0 - use deprecated in CB10.1, use Start() instead
	#endif
    }
EngineSpin->Enabled = false;
StopButtonOp->Visible = true;		// Make the red [STOP] button visible
InteractiveCommandComboBox->Tag = 0; // indicate to InteractiveCommandComboBoxKeyPress
                                     // that InteractivePCEvent should be sent on <Enter>
InterpreterInputLinePanel->Visible = true;
// set focus to input line
InteractiveCommandComboBox->SetFocus();
}
//---------------------------------------------------------------------------

void __fastcall TProgram_Control::InteractiveCommandComboBoxKeyPress(
      TObject *Sender, char &Key)
{
assert(ptrPC_Thread);
if (DEBUGGINGX(9)) DB->Debug_Print("TProgram_Control Interactive Key Press (" + String(MyPCNo) + "): ThreadID " + IntToHex((int)GetCurrentThreadId(), 8), (TColor) clGreen);

// process the line when Enter is pressed
if (Key == VK_RETURN) {
    if (InteractiveCommandComboBox->Tag == 0) {
        // send InterpreterPCEvent along with EventData execute the command
	    (void) SetEvent(InterpreterPCEvent);	// execute command line in InteractiveCommandComboBox->Text
        } else {
        // send InputPCEvent
	    (void) SetEvent(InputPCEvent);	// the user typed line is in InteractiveCommandComboBox->Text
        }
    // add the command to the top of the ComboBox dropdown list if its not a duplicate
    int idx = InteractiveCommandComboBox->Items->IndexOf(InteractiveCommandComboBox->Text);
    if (idx == -1) {
    	InteractiveCommandComboBox->Items->Insert(0, InteractiveCommandComboBox->Text);
        }
	Key = 0;	// processed
    RecallIndex = 0;
    }
if (Key == VK_ESCAPE || Key == 0x1A) {	// escape or ctrl-z
	InterpreterInputLinePanel->Visible = false;
	// End the interactive mode
	ptrPC_Thread->StopRequest();
    // When the interactive mode is complete, the thread will set FreeOnTerminate and will exit
	Key = 0;	// processed
	EngineSpin->Enabled = true;
	StopButtonOp->Visible = false;
    PCMode = NOT_RUNNING;
    }
}
//---------------------------------------------------------------------------

void __fastcall TProgram_Control::InteractiveCommandComboBoxKeyDown(
      TObject *Sender, WORD &Key, TShiftState Shift)
{
if (Key == VK_UP) {	// up arrow, recall the previous command
    if (/*RecallIndex >= 0 && */ InteractiveCommandComboBox->Items->Count > 0) {
        if ((RecallIndex) < (InteractiveCommandComboBox->Items->Count - 1)) {
            RecallIndex++;
        	}
        assert(RecallIndex >= 0);
		InteractiveCommandComboBox->Text = InteractiveCommandComboBox->Items->Strings[RecallIndex];
        InteractiveCommandComboBox->SelLength = 0;
        InteractiveCommandComboBox->SelStart = InteractiveCommandComboBox->Text.Length();	// position cursor at end of command
        }
    Key = 0;	// processed
    return;
    }
if (Key == VK_DOWN) {	// down arrow, recall the next command
    if (/*RecallIndex >= 0 && */ InteractiveCommandComboBox->Items->Count > 0) {
        if ((RecallIndex) > 0) {
            RecallIndex--;
        	}
        assert(RecallIndex >= 0);
		InteractiveCommandComboBox->Text = InteractiveCommandComboBox->Items->Strings[RecallIndex];
        InteractiveCommandComboBox->SelLength = 0;
        InteractiveCommandComboBox->SelStart = InteractiveCommandComboBox->Text.Length();	// position cursor at end of command
        }
	Key = 0;	// processed
    return;
    }
}
//---------------------------------------------------------------------------

void __fastcall TProgram_Control::InteractiveCommandComboBoxKeyUp(
      TObject *Sender, WORD &Key, TShiftState Shift)
{
if (Key == VK_UP || Key == VK_DOWN) {	// ignore up and down arrows
	Key = 0;	// processed
    return;
    }
}
//---------------------------------------------------------------------------

void __fastcall TProgram_Control::FormKeyDown(TObject *Sender, WORD &Key,
      TShiftState Shift)
{
// Handle some function keys : https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes
assert(KeyPreview);
// VK_RETURN, VK_ESCAPE, UpArrow and DownArrow handled by InteractiveCommandComboBox
switch (Key) {
    case VK_F2:		// 113
    	if ((StopButtonOp->Visible == true) && Shift.Contains(ssCtrl)) {
    		// Stop
            TShiftState SS;
            StopButtonOpMouseDown(Sender, (TMouseButton) NULL, SS, 0, 0);
            Key = 0;	// handled
			}
        break;
#ifdef NOTWORKING
    case 'S':
    	// this does not work right. It brings up the the Save File diaglog after stopping the script
        // then RTC causes an exception on exit of the program - also happenes if just [Save All] is pushed
    	if ((StopButtonOp->Visible == true) && Shift.Contains(ssAlt)) {
    		// Stop
            TShiftState SS;
            StopButtonOpMouseDown(Sender, (TMouseButton) NULL, SS, 0, 0);
            Key = 0;	// handled
			}
        break;
#endif
    case VK_F3:		// 114
        // Run Script
        RunScriptButtonClick(Sender);
    	Key = 0;	// handled
        break;
    case VK_F4:		// 115
		// Compile Script
        CompileButtonClick(Sender);
    	Key = 0;	// handled
        break;
    case VK_F5:		// 116
    	// Interpreter
        InteractiveButtonClick(Sender);
    	Key = 0;	// handled
        break;
    case VK_F6:		// 117
    	// Clear Messages
        ClearButtonClick(Sender);
    	Key = 0;	// handled
        break;
    case VK_NEXT:
    	// Next PC
        NextPCClick(Sender);
    	Key = 0;	// handled
        break;
    case VK_PRIOR:
    	// Previous PC
        PreviousPCClick(Sender);
    	Key = 0;	// handled
        break;
    }
}
//---------------------------------------------------------------------------

void __fastcall TProgram_Control::CopyrightClick(TObject *Sender)
{
// Copy the Form 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 TProgram_Control::Cut1Click(TObject *Sender)
{
PCMessages->CutToClipboard();
}
//---------------------------------------------------------------------------

void __fastcall TProgram_Control::Copy1Click(TObject *Sender)
{
PCMessages->CopyToClipboard();
}
//---------------------------------------------------------------------------

void __fastcall TProgram_Control::SelectAll1Click(TObject *Sender)
{
PCMessages->SelectAll();
}
//---------------------------------------------------------------------------

void __fastcall TProgram_Control::Paste1Click(TObject *Sender)
{
PCMessages->PasteFromClipboard();
}
//---------------------------------------------------------------------------

void __fastcall TProgram_Control::ToggleWordWrapClick(TObject *Sender)
{
PCMessages->WordWrap = !PCMessages->WordWrap;
ToggleWordWrap->Checked = PCMessages->WordWrap;
if (PCMessages->WordWrap) PCMessages->ScrollBars = ssVertical;
	else PCMessages->ScrollBars = ssBoth;
}
//---------------------------------------------------------------------------

void __fastcall TProgram_Control::InteractiveCommandComboBoxChange(
      TObject *Sender)
{
LuaCommandChange = true;
}
//---------------------------------------------------------------------------

void __fastcall TProgram_Control::ShowDetectedTagsButtonClick(
      TObject *Sender)
{
if ((Setup->RFID_COM_Mode->Checked || Setup->RFID_WiFi_Mode->Checked) && !DetectedRFIDTags->Visible) {
	DetectedRFIDTags->Show();
    }
}
//---------------------------------------------------------------------------

void __fastcall TProgram_Control::ShowLayoutButtonClick(TObject *Sender)
{
MainForm->ShowLayoutWindowClick(NULL);
}
//---------------------------------------------------------------------------

void __fastcall TProgram_Control::FindButtonClick(TObject *Sender)
{
#ifdef OLDWAY
  FindDialog1->Position = Point(PCMessages->Left + PCMessages->Width, PCMessages->Top);
  FindDialog1->Execute();
#else
FindDialog1->Position = TopLeftPos;    // position of Find dialog
// for BCB 4.0, remove the Up and Down Buttons from the dialog
#if __BORLANDC__ < 0x560
FindDialog1->Options << frHideUpDown;
#endif
FindDialog1->Execute();
#endif
}
//---------------------------------------------------------------------------

void __fastcall TProgram_Control::FindDialog1Close(TObject *Sender)
{
TopLeftPos = FindDialog1->Position;        // new position of Find Dialog
}
//---------------------------------------------------------------------------
// https://learn.microsoft.com/en-us/windows/win32/controls/em-findtext
/*
Microsoft Rich Edit 2.0 and later: If set, the search is from the end
of the current selection to the end of the document. If not set, the
search is from the end of the current selection to the beginning of the document.
Microsoft Rich Edit 1.0: The FR_DOWN flag is ignored. The search is
always from the end of the current selection to the end of the document.
*/

void __fastcall TProgram_Control::FindDialog1Find(TObject *Sender)
{
#ifdef FIND_UP_DOES_NOT_WORK_WITH_THIS_CODE
  int FoundAt, StartPos, ToEnd;
  TSearchTypes mySearchTypes = TSearchTypes();
  // Begin the search after the current selection,
  // if there is one.
  // Otherwise, begin at the start of the text.
  if (FindDialog1->Options.Contains(frMatchCase))
	mySearchTypes << stMatchCase;
  if (FindDialog1->Options.Contains(frWholeWord))
	mySearchTypes << stWholeWord;
  if (PCMessages->SelLength)
	StartPos = PCMessages->SelStart + PCMessages->SelLength;
  else
	StartPos = 0;
  // ToEnd is the length from StartPos
  // to the end of the text in the rich edit control.
  ToEnd = PCMessages->Text.Length() - StartPos;
  FoundAt = PCMessages->FindText(FindDialog1->FindText, StartPos, ToEnd, mySearchTypes);
  if (FoundAt != -1)
  {
	PCMessages->SetFocus();
	PCMessages->SelStart = FoundAt;
	PCMessages->SelLength = FindDialog1->FindText.Length();
  }
  else Beep();
#else
long FoundAt;
long StartPos, FindLength;

TSearchTypes TheSearchTypes = TSearchTypes();

if (FindDialog1->Options.Contains(frDown)) {
    if (PCMessages->SelLength)
	    StartPos = PCMessages->SelStart + PCMessages->SelLength;
    else
	    StartPos = 0;
    FindLength = PCMessages->Text.Length() - StartPos;
    } else {
    StartPos = PCMessages->SelStart;
    FindLength = -PCMessages->SelStart;
    }

if (FindDialog1->Options.Contains(frMatchCase)) {
    TheSearchTypes << stMatchCase;
    }

if (FindDialog1->Options.Contains(frWholeWord)) {
    TheSearchTypes << stWholeWord;
    }

FoundAt = MyFindText(FindDialog1->FindText, StartPos, FindLength, TheSearchTypes,
    FindDialog1->Options.Contains(frDown));

if (FoundAt != -1) {
	PCMessages->SetFocus();
	PCMessages->SelStart = FoundAt;
	PCMessages->SelLength = FindDialog1->FindText.Length();
  }
  else Beep();
#endif
}
//---------------------------------------------------------------------------
//
// Replacement for Debug->Messages->FindText
// https://www.tek-tips.com/threads/tfinddialog-and-finding-text-in-an-upward-direction.397710/
//
int __fastcall TProgram_Control::MyFindText(String SearchStr, long StartPos, long FindLength, TSearchTypes Options, bool SearchDown)
{
TFindText Find;
Word Flags;
int Result = -1;
/*
The cpMin member of FINDTEXT.chrg always specifies the starting-point of the search,
and cpMax specifies the end point. When searching backward, cpMin must be equal to
or greater than cpMax. When searching forward, a value of -1 in cpMax extends the
search range to the end of the text.
*/
Find.chrg.cpMin = StartPos;
Find.chrg.cpMax = StartPos + FindLength;
Find.lpstrText = SearchStr.c_str();

Flags = 0;

if (Options.Contains(stWholeWord)) {
    Flags = Flags | FT_WHOLEWORD;
    } else {
    Flags = Flags & ~FT_WHOLEWORD;
    }
if (Options.Contains(stMatchCase)) {
    Flags = Flags | FT_MATCHCASE;
    } else {
    Flags = Flags & ~FT_MATCHCASE;
    }

if (SearchDown) {
    Flags = Flags | FR_DOWN;
    } else {
    Flags = Flags & ~FR_DOWN;     // Search Up, just does not work !
    //Flags = Flags | FR_DOWN;
    }

Result = SendMessage(PCMessages->Handle, EM_FINDTEXT, Flags, (long) &Find);

return Result;
}
//---------------------------------------------------------------------------

void __fastcall TProgram_Control::MRUComboBoxChange(TObject *Sender)
{
MRUComboBox->Hint = MRUComboBox->Text;
TMenuItem *ItemToEdit = MainForm->PCPopupMenu->Items->Items[MyPCNo - 1];
ItemToEdit->Caption =  "PC#" + String(MyPCNo) + " - " + ExtractFileName(MRUComboBox->Text);
ItemToEdit->Hint = MRUComboBox->Text;
// update the popup menu attached to the Main window [Prog Ctrl] button
MainForm->PCPopupMenu->Items->Items[MyPCNo - 1]->Caption =  "PC#" + String(MyPCNo) + " - " + ExtractFileName(MRUComboBox->Text);
MainForm->PCPopupMenu->Items->Items[MyPCNo - 1]->Hint = MRUComboBox->Text;
}
//---------------------------------------------------------------------------

void __fastcall TProgram_Control::MRUComboBoxKeyDown(TObject *Sender,
      WORD &Key, TShiftState Shift)
{
// Tab or Enter adds the text to the drop down list
if (Key == VK_RETURN || Key == VK_TAB) {
    // Add new filename to DropDown List
    // insert into MRU List
    // don't insert a duplicate string
    int idx = MRUComboBox->Items->IndexOf(MRUComboBox->Text);
    if (idx == -1) {
        MRUComboBox->Items->Insert(0, MRUComboBox->Text);
        MRUComboBox->ItemIndex = 0;
        if (MRUComboBox->Items->Count > MRUMAX)
        MRUComboBox->Items->Delete(MRUMAX);
        } else {
        // set ItemIndex
        MRUComboBox->ItemIndex = idx;
        }
    Key = 0;	// handled
    }
}
//---------------------------------------------------------------------------

void __fastcall TProgram_Control::ShowLayoutWindow1Click(TObject *Sender)
{
MainForm->ShowLayoutWindowClick(Sender);
}
//---------------------------------------------------------------------------

void __fastcall TProgram_Control::ShowLayoutClick(TObject *Sender)
{
MainForm->ShowLayoutWindowClick(Sender);
}
//---------------------------------------------------------------------------

void __fastcall TProgram_Control::ShowCabSignalsClick(TObject *Sender)
{
// show or hide the Signal Heads on the Program Control window
SignalMast->Visible = !SignalMast->Visible;
ShowCabSignals->Checked = SignalMast->Visible;
}
//---------------------------------------------------------------------------

void __fastcall TProgram_Control::CreateCabSignals(int HomeHeadType, int DistantHeadType)
{
// one or both head types can be zero in which case, the cab signal is deleted
//
if (HomeHeadType != 0 || DistantHeadType !=0) {
	SignalMast->Visible = true;
	ShowCabSignals->Checked = true;
	}
//
if (HomeSignal) {
	delete HomeSignal;
    HomeSignal = NULL;
    }
iCabSignal[MyPCNo - 1][HOMESIGNAL] = 0;
switch(HomeHeadType) {
	case Hd1LED:
        HomeSignal =  new TLED1Panel(SignalMast);
        HomeSignal->Parent = SignalMast;
    	HomeSignal->Top = 2;
		HomeSignal->Left = 1;
        HomeSignal->Name = "HomeSignal";
        HomeSignal->Tag = AsDark;
        HomeSignal->Constraints->MinWidth = PERMISSIVE_IN_OPEN_COUNTRY;	// Signal type
        HomeSignal->Constraints->MinHeight = Hd1LED;
		((TLED1Panel *)HomeSignal)->LEDHighSpeed->Checked = true;
       	SetCabSignal(HOMESIGNAL, AsDark, "**", 0, 0);	// Number Plate 'A'
		break;
    case Hd2LED:
        HomeSignal =  new TLED2Panel(SignalMast);
        HomeSignal->Parent = SignalMast;
    	HomeSignal->Top = 2;
		HomeSignal->Left = 1;
        HomeSignal->Name = "HomeSignal";
        HomeSignal->Tag = AsDark;
        HomeSignal->Constraints->MinWidth = PERMISSIVE_IN_OPEN_COUNTRY;	// Signal type
        HomeSignal->Constraints->MinHeight = Hd2LED;
		((TLED2Panel *)HomeSignal)->LEDHighSpeed->Checked = true;
		((TLED2Panel *)HomeSignal)->LEDMediumSpeed->Checked = true;
       	SetCabSignal(HOMESIGNAL, AsDark, "**", 0, 0);	// Number Plate 'A'
        break;
    case Hd3LED:
        HomeSignal =  new TLED3Panel(SignalMast);
        HomeSignal->Parent = SignalMast;
    	HomeSignal->Top = 2;
		HomeSignal->Left = 1;
        HomeSignal->Name = "HomeSignal";
        HomeSignal->Tag = AsDark;
        HomeSignal->Constraints->MinWidth = PERMISSIVE_IN_OPEN_COUNTRY;	// Signal type
        HomeSignal->Constraints->MinHeight = Hd3LED;
		((TLED3Panel *)HomeSignal)->LEDHighSpeed->Checked = true;
		((TLED3Panel *)HomeSignal)->LEDMediumSpeed->Checked = true;
		((TLED3Panel *)HomeSignal)->LEDLowSpeed->Checked = true;
		SetCabSignal(HOMESIGNAL, AsDark, "**", 0, 0);	// Number Plate 'A'
    	break;
	case HdMLED:
        HomeSignal =  new TLEDMPanel(SignalMast);
        HomeSignal->Parent = SignalMast;
    	HomeSignal->Top = 2;
		HomeSignal->Left = 1;
        HomeSignal->Name = "HomeSignal";
        HomeSignal->Tag = AsDark;
        HomeSignal->Constraints->MinWidth = PERMISSIVE_IN_OPEN_COUNTRY;	// Signal type
        HomeSignal->Constraints->MinHeight = HdMLED;
		((TLEDMPanel *)HomeSignal)->LEDHighSpeed->Checked = true;
        ((TLEDMPanel *)HomeSignal)->LEDMediumSpeed->Checked = true;
		((TLEDMPanel *)HomeSignal)->LEDLowSpeed->Checked = true;
       	SetCabSignal(HOMESIGNAL, AsDark, "**", 0, 0);	// Number Plate 'A'
		break;
    default:
    	// no signal in Home position is allowed
        break;
    }

if (DistantSignal) {
	delete DistantSignal;
    DistantSignal = NULL;
    }
iCabSignal[MyPCNo - 1][DISTANTSIGNAL] = 0;
switch(DistantHeadType) {
	case Hd1LED:
        DistantSignal =  new TLED1Panel(SignalMast);
        DistantSignal->Parent = SignalMast;
    	DistantSignal->Top = 111;
		DistantSignal->Left = 1;
        DistantSignal->Name = "DistantSignal";
        DistantSignal->Tag = AsDark;
        DistantSignal->Constraints->MinWidth = PERMISSIVE_IN_OPEN_COUNTRY;	// Signal type
        DistantSignal->Constraints->MinHeight = Hd1LED;
		((TLED1Panel *)DistantSignal)->LEDHighSpeed->Checked = true;
        SetCabSignal(DISTANTSIGNAL, AsDark, "**", 0, 0);	// Number Plate 'A'
    	break;
    case Hd2LED:
        DistantSignal =  new TLED2Panel(SignalMast);
        DistantSignal->Parent = SignalMast;
    	DistantSignal->Top = 111;
		DistantSignal->Left = 1;
        DistantSignal->Name = "DistantSignal";
        DistantSignal->Tag = AsDark;
        DistantSignal->Constraints->MinWidth = PERMISSIVE_IN_OPEN_COUNTRY;	// Signal type
        DistantSignal->Constraints->MinHeight = Hd2LED;
        ((TLED2Panel *)DistantSignal)->LEDHighSpeed->Checked = true;
		((TLED2Panel *)DistantSignal)->LEDMediumSpeed->Checked = true;
        SetCabSignal(DISTANTSIGNAL, AsDark, "**", 0, 0);	// Number Plate 'A'
    	break;
    case Hd3LED:
        DistantSignal =  new TLED3Panel(SignalMast);
        DistantSignal->Parent = SignalMast;
    	DistantSignal->Top = 111;
		DistantSignal->Left = 1;
        DistantSignal->Name = "DistantSignal";
        DistantSignal->Tag = AsDark;
        DistantSignal->Constraints->MinWidth = PERMISSIVE_IN_OPEN_COUNTRY;	// Signal type
        DistantSignal->Constraints->MinHeight = Hd3LED;
		((TLED3Panel *)DistantSignal)->LEDHighSpeed->Checked = true;
		((TLED3Panel *)DistantSignal)->LEDMediumSpeed->Checked = true;
		((TLED3Panel *)DistantSignal)->LEDLowSpeed->Checked = true;
        SetCabSignal(DISTANTSIGNAL, AsDark, "**", 0, 0);	// Number Plate 'A'
    	break;
	case HdMLED:
        DistantSignal =  new TLEDMPanel(SignalMast);
        DistantSignal->Parent = SignalMast;
    	DistantSignal->Top = 111;
		DistantSignal->Left = 1;
        DistantSignal->Name = "DistantSignal";
        DistantSignal->Tag = AsDark;
        DistantSignal->Constraints->MinWidth = PERMISSIVE_IN_OPEN_COUNTRY;	// Signal type
        DistantSignal->Constraints->MinHeight = HdMLED;
		((TLEDMPanel *)DistantSignal)->LEDHighSpeed->Checked = true;
        ((TLEDMPanel *)DistantSignal)->LEDMediumSpeed->Checked = true;
		((TLEDMPanel *)DistantSignal)->LEDLowSpeed->Checked = true;
        SetCabSignal(DISTANTSIGNAL, AsDark, "**", 0, 0);	// Number Plate 'A'
    	break;
    default:
    	// no signal in Distant position is allowed
        break;
    }

if (HomeHeadType == 0 && DistantHeadType == 0) {
	SignalMast->Visible = false;
	ShowCabSignals->Checked = false;
	}
}
//---------------------------------------------------------------------------

void __fastcall TProgram_Control::SetCabSignal(int HeadNo, int Aspect, String sName, int iNumberPlate, int iType)
{
if (!SignalMast->Visible) ShowCabSignalsClick(NULL);
// Aspect == -1 means don't change the aspect
TPanel * Head;
TLEDMPanel * ActualM;
TLED3Panel * Actual3;
TLED2Panel * Actual2;
TLED1Panel * Actual1;
switch(HeadNo) {
	case HOMESIGNAL:
    	Head = HomeSignal;
        if (!Head) return;	// no home signal created
		ActualM = dynamic_cast <TLEDMPanel *> (Head);
		if (ActualM) {
            ActualM->Names(sName, iNumberPlate, iType);
			if (Aspect != -1) {
    	    	ActualM->Lights(Aspect);
        	    }
            break;
            }
		Actual3 = dynamic_cast <TLED3Panel *> (Head);
		if (Actual3) {
            Actual3->Names(sName, iNumberPlate, iType);
			if (Aspect != -1) {
    	    	Actual3->Lights(Aspect);
        	    }
            break;
            }
		Actual2 = dynamic_cast <TLED2Panel *> (Head);
		if (Actual2) {
            Actual2->Names(sName, iNumberPlate, iType);
			if (Aspect != -1) {	// AsNone == 0
    	    	Actual2->Lights(Aspect);
        	    }
            break;
            }
		Actual1 = dynamic_cast <TLED1Panel *> (Head);
		if (Actual1) {
            Actual1->Names(sName, iNumberPlate, iType);
			if (Aspect != -1) {
    	    	Actual1->Lights(Aspect);
        	    }
            break;
            }
    	break;
    case DISTANTSIGNAL:
    	Head = DistantSignal;
        if (!Head) return;	// no distant signal created
		ActualM = dynamic_cast <TLEDMPanel *> (Head);
		if (ActualM) {
            ActualM->Names(sName, iNumberPlate, iType);
			if (Aspect != -1) {
    	    	ActualM->Lights(Aspect);
        	    }
            break;
            }
		Actual3 = dynamic_cast <TLED3Panel *> (Head);
		if (Actual3) {
            Actual3->Names(sName, iNumberPlate, iType);
			if (Aspect != -1) {
    	    	Actual3->Lights(Aspect);
        	    }
            break;
            }
		Actual2 = dynamic_cast <TLED2Panel *> (Head);
		if (Actual2) {
            Actual2->Names(sName, iNumberPlate, iType);
			if (Aspect != -1) {
    	    	Actual2->Lights(Aspect);
        	    }
            break;
            }
		Actual1 = dynamic_cast <TLED1Panel *> (Head);
		if (Actual1) {
            Actual1->Names(sName, iNumberPlate, iType);
			if (Aspect != -1) {
    	    	Actual1->Lights(Aspect);
        	    }
            break;
            }
    	break;
    default:
    	assert(false);
        break;
	}
return;
}
//---------------------------------------------------------------------------

void __fastcall TProgram_Control::GetCabAspect(int HeadNo, int &Aspect, int &iMode, int &Type)
{
if (!SignalMast->Visible) ShowCabSignalsClick(NULL);
TPanel * Head;
switch(HeadNo) {
	case HOMESIGNAL:
    	Head = HomeSignal;
        if (!Head) {
			Aspect = 1;
            Type = HdNONE;
            return;
            }
        Aspect = Head->Tag;
        Type = Head->Constraints->MinWidth;	// free spot to store the Signal Type for Cab Signals
        iMode = Head->Constraints->MinHeight;	// free spot to store the mode
    	break;
    case DISTANTSIGNAL:
    	Head = DistantSignal;
        if (!Head) {
			Aspect = 1;
            Type = HdNONE;
            return;
            }
        Aspect = Head->Tag;
        Type = Head->Constraints->MinWidth;	// free spot to store the Signal Type for Cab Signals
        iMode = Head->Constraints->MinHeight;	// free spot to store the mode
    	break;
    default:
    	assert(false);
        break;
	}
return;
}
//---------------------------------------------------------------------------

void __fastcall TProgram_Control::More_InformationClick(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 TProgram_Control::FunctionButtonMouseDown(
      TObject *Sender, TMouseButton Button, TShiftState Shift, int X,
      int Y)
{
// get button and set bold style
TButton * FuncBut = reinterpret_cast<TButton *>(Sender);
FuncBut->Font->Style = FuncBut->Font->Style << fsBold;
}
//---------------------------------------------------------------------------

void __fastcall TProgram_Control::FunctionButtonMouseUp(TObject *Sender,
      TMouseButton Button, TShiftState Shift, int X, int Y)
{
// get button and remove bold style
TButton * FuncBut = reinterpret_cast<TButton *>(Sender);
FuncBut->Font->Style = FuncBut->Font->Style >> fsBold;
}
//---------------------------------------------------------------------------

void __fastcall TProgram_Control::CopyScriptNametoClipboard1Click(
      TObject *Sender)
{
Clipboard()->AsText = MRUComboBox->Text;
}
//---------------------------------------------------------------------------

void __fastcall TProgram_Control::PasteScriptNamefromClipboad1Click(
      TObject *Sender)
{
if (Clipboard()->HasFormat(CF_TEXT)) {
    MRUComboBox->Text = Clipboard()->AsText;
    }
}
//---------------------------------------------------------------------------

void __fastcall TProgram_Control::ViewScriptDirClick(TObject *Sender)
{
//
HINSTANCE retv = ShellExecute(Handle,
       	NULL,
        TEXT("explorer"),		// Program to Run
        ExtractFileDir(MRUComboBox->Text).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));
    }        
}
//---------------------------------------------------------------------------


