//---------------------------------------------------------------------------
/*
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
/*
This Form works with Engines, All Engine Mode and Lashups.
The Visible property on the "ClearPreferencesButton" is set to false.
*/
//---------------------------------------------------------------------------
#include <vcl.h>
#include <tchar.h>
#pragma hdrstop

#include "Engine_Commands.h"
#include "Train_Control.h"
#include "Op_Control.h"
#include "Volume_Control.h"
#include "Quick_Controls.h"
#include "Comm_Thread.h"
#include "RTCMegaMaster.h"
#include "LashUp_Selection.h"

#include <stdio.h>
#include <stdlib.h>
#include "Setup_Control.h"
#include <typeinfo.h>
#include "Debug_Controls.h"
#include "math.h"
#include "Clipbrd.hpp"

//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma link "_GClass"
#pragma link "AbCBitBt"
#pragma link "AbSwitch"
#pragma link "HideButton"

#pragma link "AbLED"
#pragma link "AbHSlide"
#pragma link "ColorButton"
#pragma link "QuitButton"
#pragma resource "*.dfm"
//---------------------------------------------------------------------------
//TQuickCtrl *QuickCtrl; 	// NOTE: this is public static variable, you can never use
						// this when you create multiple instances of a Form
//---------------------------------------------------------------------------
// Pointers to Operations Screens 0=factory reset, 1-99 Engines, 100 All Engine, 101-120 Lashups
//
extern TOp * ptrOp[ABSOLUTEMAX];   			// Operation Control Pointers - indexed by actual engine number because 0 is a factory reset engine
extern TVolume * ptrVolume[ABSOLUTEMAX];	// Volume Control Pointers - indexed by actual engine number because 0 is a factory reset engine
extern TQuickCtrl * ptrQuick[QUICKPAGEMAX];	// Quick Control Pointers

extern TCommThread * ptrTIUCommThread;			// pointer to TIU communications thread

char * EngineList[ABSOLUTEMAX];					// pointers to LashUpEngineLists
//---------------------------------------------------------------------------
//
// Sometimes the IDE cannot find event handlers if you double click on
// the Events in the Object Inspector. This is in BCB4.0. In CB10.1 it seems
// to find it. With both compilers, it seems to link ok and operates correctly.
//
// Caused by the addition of the declaration of the __closure *XXXXOnValueChange
// inside of XXXXValueChange. It seems to work correctly, if I declare
// the __closure outside of the function.
//
static void __fastcall (__closure *VolumeSliderOnValueChange)(TObject *Sender);
static void __fastcall (__closure *VolumeOnValueChange)(TObject *Sender);
static void __fastcall (__closure *SmokeOnStatusChanged)(TObject *Sender);
static void __fastcall (__closure *DIROnStatusChanged)(TObject *Sender);
static void __fastcall (__closure *SpeedEditOnValueChanged)(TObject *Sender);
static void __fastcall (__closure *SpeedSliderOnValueChange)(TObject *Sender);
static void __fastcall (__closure *BellOnStatusChanged)(TObject *Sender);
//---------------------------------------------------------------------------

__fastcall TQuickCtrl::TQuickCtrl(TComponent* Owner)
	: TForm(Owner)
{
assert(false);	// never called since I used the overloaded constructor below
}
//---------------------------------------------------------------------------

__fastcall TQuickCtrl::TQuickCtrl(TComponent* Owner, int Remote, int Page, float ScaleFactor)
    : TForm(Owner)
{
// grab the Remote and Page numbers (Code only supports 1 page of Quick Controls)
RemoteNo = Remote;
assert(RemoteNo >= 0 && RemoteNo <= REMOTEMAX);
PageNo = Page; // first page of Quick Controls
assert(PageNo >= 1 && PageNo <= TIUMAX);
//
SF = ScaleFactor;
assert(SF >= 1.0 && SF <= 2.0);	// Anything greater than 2.0 does not look good

bSetupComplete = false;
GetEngData();					// save engine data in this form for later use

Caption = "Quick Controls - Page " + String(PageNo);

LocalTransparencyOverride = false;

// Scale the Form
if (SF > 1.0) ScaleControl(this, SF);

TotalCtrlCount = iCtrlCount = 1;	// There is always one control window
// Fill the Containers
EngineBoxContainer[iCtrlCount - 1] = EngineBox1;
EngNumberContainer[iCtrlCount - 1] = EngNumber1;
// Initial Values
EngNumberContainer[iCtrlCount - 1]->Color = clRed;
UpDownContainer[iCtrlCount - 1] = UpDown1;
EngineNameContainer[iCtrlCount - 1] = EngineName1;
SpeedEditContainer[iCtrlCount - 1] = SpeedEdit1;
// Initial Values
SpeedEditContainer[iCtrlCount - 1]->Value = 0;
LastSpeedContainer[iCtrlCount - 1] = 0;
DIRToggleContainer[iCtrlCount - 1] = DIRToggle1;
BellSwitchContainer[iCtrlCount - 1] = BellSwitch1;
SmokeSwitchContainer[iCtrlCount - 1] = SmokeSwitch1;
VolumeSliderContainer[iCtrlCount - 1] = VolumeSlider1;
WhistleButtonContainer[iCtrlCount - 1] = WhistleButton1;
HornButtonContainer[iCtrlCount - 1] = HornButton1;
StartupButtonContainer[iCtrlCount - 1] = StartupButton1;
ShutdownButtonContainer[iCtrlCount - 1] = ShutdownButton1;

// expand to whistle and coupler
SFSButtonContainer[iCtrlCount - 1] = SFSButton1;
SRSButtonContainer[iCtrlCount - 1] = SRSButton1;
SXSButtonContainer[iCtrlCount - 1] = SXSButton1;
CPLRFButtonContainer[iCtrlCount - 1] = CPLRFButton1;
CPLRRButtonContainer[iCtrlCount - 1] = CPLRRButton1;

// Scale each control on the form
// start by iterating through the controls of the container

if (SF > 1.0) {
	for (int i = 0; i < this->ControlCount; i++) {	// controls at the top level
	    // Extract the control at index i
	    TControl *control = this->Controls[i];
        ScaleControl(control, SF);        // Scale it Up
		}
	for (int i = 0; i < EngineBox1->ControlCount; i++) { 	// controls in the EngineBox
	    // Extract the control at index i
	    TControl *control = EngineBox1->Controls[i];
        ScaleControl(control, SF);        // Scale it Up
		}
    }

EngineBoxWidth = EngineBox1->Width;	// save Engine Box width to make more Boxes

// Clear the RunningState and Lashup Engine List variables
for (int CN = 0 ; CN <= (ABSOLUTEMAX - 1) ; CN++) {
    RunningState[CN] = clRed;
    EngineList[CN] = NULL;
    TIUNo[CN] = 1;  // default TIU 1
	}

// Each class used in the EngineBox
RegisterClass(__classid(TGroupBox));
RegisterClass(__classid(TAbNumEdit));
RegisterClass(__classid(TAbRockerSwitch));
RegisterClass(__classid(TAbColBitBtn));
RegisterClass(__classid(TAbSpinButton));
RegisterClass(__classid(TRadioGroup));
RegisterClass(__classid(TLabel));
RegisterClass(__classid(TButton));
RegisterClass(__classid(TAbToggleSwitch));
RegisterClass(__classid(TAbLED));
RegisterClass(__classid(TAbNumSpin));
RegisterClass(__classid(TComboBox));
RegisterClass(__classid(TPopupMenu));
RegisterClass(__classid(TAbHSlider));
RegisterClass(__classid(TColorButton));
RegisterClass(__classid(TUpDown));
RegisterClass(__classid(TMaskEdit));

// Note: Quick_Controls can't be called unless we are already connected to the TIU

// if any engine setup here fails to communicate with the engine (for example, because the engine
// is not on the track) just ignore any errors.
// Required for multiple monitors and save/restore position/size
Position=poDesigned;
DefaultMonitor=dmDesktop;
//
// Read preferences from the Registry
RegFileRootKey = MainForm->RegFileRootKey;
// Read Preferences from registry if they exist
TRegIniFile * const RegFile = new TRegIniFile(RegFileRootKey);
assert(RegFile != NULL);
__try {
    // Quick controls loaded on a per TIU basis
	TotalCtrlCount = RegFile->ReadInteger("Quick", "ControlCount" + String(PageNo), TotalCtrlCount);
	// Setup Consist 1 which always displays
    if (TotalCtrlCount < 10) NewControlWindow->Enabled = true;
    if (TotalCtrlCount > 1) DeleteControlWindow->Enabled = true;
    //
	Top = RegFile->ReadInteger("Quick", "Top" + String(PageNo), 25);
	Left = RegFile->ReadInteger("Quick", "Left" + String(PageNo), 25);
    // 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;
        }
    //
	UpDown1->Position = RegFile->ReadInteger("Quick", "Engine" + String(PageNo) + "1", UpDown1->Position);
	// set Engine/Lashup Name, TIU number and Data
	SetEngineName(0, EngineName1, NULL, UpDown1->Position);
    // Caption for the 0th Control. We don't know the TIU number at this time
    EngineBox1->Caption = String(UpDown1->Position <= ALLENGINE ? "Engine " : "Lashup ") +
        String(UpDown1->Position - (UpDown1->Position <= ALLENGINE ? 0 : ALLENGINE));	// Set Engine Number as Engine Box Caption

    // Then setup other Quick Control Windows
	if (TotalCtrlCount > 1) {
		for (int index =  2; index <= TotalCtrlCount; index++) {
        	// first additional window is 2, then 3 and so on up to iCtrlCount
            // this bumps iCtrlCount until it reaches TotalCtrlCount
            iCtrlCount++;
            int ExtraOffset = 6 * SF;
			Width = Width + (EngineBoxWidth + ExtraOffset);
			NewControlWindow->Left = NewControlWindow->Left + (EngineBoxWidth + ExtraOffset);
            PreviousPage->Left = PreviousPage->Left + (EngineBoxWidth + ExtraOffset);
            NextPage->Left = NextPage->Left + (EngineBoxWidth + ExtraOffset);
			DeleteControlWindow->Left = DeleteControlWindow->Left + (EngineBoxWidth + ExtraOffset);
			ESTOP->Width = ESTOP->Width + (EngineBoxWidth + ExtraOffset);
			HideButton->Left = HideButton->Left + (EngineBoxWidth + ExtraOffset)/2;
			CloseButton->Left = CloseButton->Left + (EngineBoxWidth + ExtraOffset)/2;
			ConstructNewQuickControlWindow(NULL);	// open the next control
            // read settings from the Registry for windows 2 and up
			UpDownContainer[index - 1]->Position = RegFile->ReadInteger("Quick", "Engine" + String(PageNo) + String(index), UpDownContainer[index - 1]->Position);
           	// set Engine/Consist Name, TIU Number and Data
            // SetEngineName is called when the engine spinner is changed
			SetEngineName(index - 1, EngineNameContainer[index - 1], NULL, UpDownContainer[index - 1]->Position);
            // Caption for the 1st through the TotalCtrlCount Controls. We don't know the TIU number at this time
		    EngineBoxContainer[index - 1]->Caption = String(UpDownContainer[index - 1]->Position <= ALLENGINE ? "Engine " : "Lashup ") +
            	String(UpDownContainer[index - 1]->Position - (UpDownContainer[index - 1]->Position <= ALLENGINE ? 0 : ALLENGINE));	// Set Engine Number as Engine Box Caption
		    //
	    	}
	    }
    }
__finally {
	// Deallocate RegFile
	delete RegFile;
    }

// Experimental - use Drop Down Menu style in CB10.1
#if __BORLANDC__ >= 0x720
StartupButton1->Style = (TCustomButton::TButtonStyle)bsSplitButton;
StartupButton1->DropDownMenu = EnginePopUpMenu1;
#endif

// Make sure that we processed every engine
assert(iCtrlCount == TotalCtrlCount);

// Disable Emergency Stop button if requested
if (Setup->DisableEmergencyStopButton->Checked) {
	ESTOP->Visible = false;
    ESTOP->Enabled = false;
    ESTOP->Hint = "Disabled in Setup";
    ESTOP->Cursor = crNo;
    }

if (DEBUGGING) {	// Debug button
    ClearPreferencesButton->Visible = true;
    }

bSetupComplete = true;
}
//---------------------------------------------------------------------------

__fastcall TQuickCtrl::~TQuickCtrl(void)
{

// unallocate any Lashup Lists
for (int CN = LASHUPMIN ; CN <= LASHUPMAX ; CN++) {
	if (EngineList[CN] != NULL) {
		delete EngineList[CN];
		EngineList[CN] = NULL;
		}
	}

if (MainForm->bSaveRegistryInfo) {
    // write preferences to the Registry - Note: MainForm->RegFileRootKey was destroyed by the time this ran
    // so I had to save the value locally
    TRegIniFile * const RegFile = new TRegIniFile(RegFileRootKey);
    assert(RegFile != NULL);
    __try {
        // Quick controls saved on a per TIU basis
        RegFile->WriteInteger("Quick", "ControlCount" + String(PageNo), TotalCtrlCount);
        // Save Consist 1 which always displays
        RegFile->WriteInteger("Quick", "Engine" + String(PageNo) + "1", UpDown1->Position);
        //
		RegFile->WriteInteger("Quick", "Top" + String(PageNo), Top > 0 ? Top : 0);
        RegFile->WriteInteger("Quick", "Left" + String(PageNo), Left > 0 ? Left : 0);
        //
        // Loop through Consist Windows 2 and up and save the settings
        if (iCtrlCount > 1) {
            for (int i =  2; i <= iCtrlCount; i++) {
                // first one is Window 2, then 3 and so on up to iControlCount
                // write settings to the Registry for windows 2 and up
                RegFile->WriteInteger("Quick", "Engine" + String(PageNo) + String(i), UpDownContainer[i - 1]->Position);
                }
            }
        }
    __finally {
        // Deallocate RegFile
        delete RegFile;
        }
    }
//assert(QuickCtrl == NULL); 	// this form must never be auto created
}
//---------------------------------------------------------------------------
// Subclass TControl and expose a method to adjust font size.
// from: https://www.experts-exchange.com/questions/26213865/How-can-I-change-Font-property-at-runtime-in-Borland-Builder.html
// NOTE: when I enabled inlining, the first solution got the error E2247. I changed
// the code to define the function in a different way and that seemed to work.
#ifdef NOINLINEWAY
class TMyControl : public TControl
{
public:
	void SetFontSize(float dFactor)
    {
    Font->Size = Font->Size * dFactor;
    // Gets [C++ Warning] W8070 Function should return value
    // when inlining is enabled, gets:
    // E2247 'TControl::Font' is not accessible
	}
};
#else
class TMyControl : public TControl
{
public:
	void __fastcall SetFontSize(float dFactor);
};
void __fastcall TMyControl::SetFontSize(float dFactor)
    {
    Font->Size = Font->Size * dFactor;
	}

#endif
//---------------------------------------------------------------------------

void __fastcall TQuickCtrl::ScaleControl(TControl * CC, float SF)
{
// adjust the size and postion of the controls by the scale factor
//CC->Font->Size = CC->Font->Size * SF; 	// Font is protected so I can't change Font Size this way
// https://www.experts-exchange.com/questions/26213865/How-can-I-change-Font-property-at-runtime-in-Borland-Builder.html
TMyControl *mycontrol;
mycontrol = (TMyControl *)CC;
mycontrol->Left = mycontrol->Left * SF; 	// Left-Top-Height-Width not protected
mycontrol->Top = mycontrol->Top * SF;		// so doing it this way is not really necessary
mycontrol->Height = mycontrol->Height * SF;
mycontrol->Width = mycontrol->Width * SF;
mycontrol->SetFontSize(SF * 0.8);
}
//---------------------------------------------------------------------------

void __fastcall TQuickCtrl::ClearPreferencesButtonClick(TObject *Sender)
{
// clear preferences
// ClearPreferencesButton->Visible is set false so button is normally not visible
TRegIniFile * const RegFile = new TRegIniFile(RegFileRootKey);
assert(RegFile != NULL);
// Erase only Quick Control Section
__try {
	RegFile->EraseSection("Quick");
    }
__finally {
	delete RegFile;
	}
}
//---------------------------------------------------------------------------

void __fastcall TQuickCtrl::SetEngineName(int ControlNo, TLabel * EngineName,
	TButton * ConsistTypeButtonNOTUSED, int ConsistNo)
{
//
// This routine sets the Engine Name along with doing other things.
// It is the last function called when a Quick Control displayed
//
TOp * ptrOperation = ptrOp[ConsistNo];
TVolume * ptrVol = ptrVolume[ConsistNo];

// set control pointers for any Volume window that might be open
if (ptrVol) {
	ptrVol->ptrQuick_MasterVolume = VolumeSliderContainer[ControlNo];
    // Master Volume
	// The VolumeSlider on the Quick Controls window uses OnValueChange......
    void __fastcall (__closure *EventOnValueChange)(TObject *Sender);
	EventOnValueChange = VolumeSliderContainer[ConsistNo]->OnValueChange;
    VolumeSliderContainer[ConsistNo]->OnValueChange = NULL;
	VolumeSliderContainer[ConsistNo]->Value = ptrVol->MasterVolume->Value;
    VolumeSliderContainer[ConsistNo]->OnValueChange = EventOnValueChange;
	}
// set control pointers for any Operations window that might be open
if (ptrOperation) {
	// engine is started so send any operations windows pointers to my controls
	ptrOperation->ptrQuick_Dir = DIRToggleContainer[ControlNo];
	ptrOperation->ptrQuick_Bell = BellSwitchContainer[ControlNo];
	ptrOperation->ptrQuick_Smoke = SmokeSwitchContainer[ControlNo];
	ptrOperation->ptrQuick_Speed = SpeedEditContainer[ControlNo];
    ptrOperation->ptrQuick_EngNumber = EngNumberContainer[ControlNo];
    ptrOperation->ptrQuick_RunState = RunningState;		// pointer to a TColor array indexed by Engine NUmber
    ptrOperation->ptrQuick_Sound = VolumeSliderContainer[ControlNo];
    // expand to whistle and coupler buttons
    ptrOperation->ptrQuick_Whistle = WhistleButtonContainer[ControlNo];
    ptrOperation->ptrQuick_Horn = HornButtonContainer[ControlNo];
    ptrOperation->ptrQuick_SFS = SFSButtonContainer[ControlNo];
    ptrOperation->ptrQuick_SRS = SRSButtonContainer[ControlNo];
    ptrOperation->ptrQuick_SXS = SXSButtonContainer[ControlNo];
    ptrOperation->ptrQuick_CPLRF = CPLRFButtonContainer[ControlNo];
    ptrOperation->ptrQuick_CPLRR = CPLRRButtonContainer[ControlNo];
    //
    EngNumberContainer[ControlNo]->Color = clLime;	// engine is started, set green
    RunningState[ConsistNo] = clLime;
    //
    // Copy all settings from Operations window to this Quick Control
    //
    // DIRToggle
    // The Dir Switch on the Quick Controls window uses OnStatusChanged
    void __fastcall (__closure *DirOnValueChanged)(TObject *Sender);
    DirOnValueChanged = DIRToggleContainer[ControlNo]->OnStatusChanged;
    DIRToggleContainer[ControlNo]->OnStatusChanged = NULL;
    DIRToggleContainer[ControlNo]->Checked = ptrOperation->DIRToggle->Checked;
    DIRToggleContainer[ControlNo]->OnStatusChanged = DirOnValueChanged;
    // BellSwitch
    // The Whistle Button on the Quick Controls window uses OnStatusChanged
    void __fastcall (__closure *BellOnValueChanged)(TObject *Sender);
    BellOnValueChanged = BellSwitchContainer[ControlNo]->OnStatusChanged;
    BellSwitchContainer[ControlNo]->OnStatusChanged = NULL;
    BellSwitchContainer[ControlNo]->Checked = ptrOperation->BellSwitch->Checked;
    BellSwitchContainer[ControlNo]->OnStatusChanged = BellOnValueChanged;
    // Smoke Switch
    // The Smoke Switch on the Quick Controls window uses OnStatusChanged
    void __fastcall (__closure *SmokeOnValueChanged)(TObject *Sender);
    SmokeOnValueChanged = SmokeSwitchContainer[ControlNo]->OnStatusChanged;
    SmokeSwitchContainer[ControlNo]->OnStatusChanged = NULL;
    SmokeSwitchContainer[ControlNo]->Checked = ptrOperation->SmokeSwitch->Checked;
    SmokeSwitchContainer[ControlNo]->OnStatusChanged = SmokeOnValueChanged;
    // Speed Edit
    // The Speed Edit on the Quick Controls window uses OnValueChanged
    void __fastcall (__closure *SpeedOnValueChanged)(TObject *Sender);
	SpeedOnValueChanged = SpeedEditContainer[ControlNo]->OnValueChanged;
    SpeedEditContainer[ControlNo]->OnValueChanged = NULL;
    int Spd = ptrOperation->SpeedSlider->Value;
	SpeedEditContainer[ControlNo]->Value = Spd;
    SpeedEditContainer[ControlNo]->OnValueChanged = SpeedOnValueChanged;
    LastSpeedContainer[ControlNo] = Spd;
    // SXS Button
    SXSButtonContainer[ControlNo]->Visible = ptrOperation->SXSButton->Visible;
	}

// set Consist Name Caption
// Make sure the ShowAccelChar is false for EngineName->Caption
EngineName->ShowAccelChar = false;

if ((Setup->AllowZero->Checked) && ConsistNo == FACTORYRESET) {
	EngineName->Caption = "Factory Reset";
	// TIUNo of selected Engine
	TIUNo[ConsistNo] = 1;   // default TIU 1
    return;
    }
if (ConsistNo >= ENGINEMIN && ConsistNo <= ENGINEMAX) {
	EngineName->Caption = EngNameContainer[ConsistNo];
    TIUNo[ConsistNo] = MainForm->EngineData->Cells [TIU_NUMBER] [ConsistNo].ToIntDef(0);
    if (TIUNo[ConsistNo] == 0 && EngineName->Caption != "*") {
        MainForm->DebugMessagePopup("Warning", MainForm->TIUNoEngineMsg, "Engine # " + String(ConsistNo));
        return;
        }
    return;
    }
if (ConsistNo == ALLENGINE) {
	EngineName->Caption = EngNameContainer[ALLENGINE];
    // For TIU Number, use the number in the MainForm TIU Spinner
    TIUNo[ConsistNo] = MainForm->TIUSpin->Value;
    return;
    }
if (ConsistNo >= LASHUPMIN && ConsistNo <= LASHUPMAX) {
	EngineName->Caption = EngNameContainer[ConsistNo];
    if (EngineList[ConsistNo] == NULL) return;  // the LashUpEngineList is not constructed yet
    int HeadEngine = MainForm->GetLashUpEngine(EngineList[ConsistNo], 0);  // get head engine
	if (HeadEngine == 0) return;    // the lashup does not exist anymore
    TIUNo[ConsistNo] = MainForm->EngineData->Cells [TIU_NUMBER] [HeadEngine].ToIntDef(0);
    if (TIUNo[ConsistNo] == 0 && MainForm->EngineData->Cells [ENG_NAME] [HeadEngine] != "*") {
        MainForm->DebugMessagePopup("Warning", MainForm->TIUNoLashupMsg, "Lashup # " + String(ConsistNo));
        return;
        }
    return;
    }
if (ConsistNo < FACTORYRESET || ConsistNo > LASHUPMAX) {
    MainForm->DebugMessagePopup("Warning", "    Invalid Engine/LashUp Number    ");
	EngineName->Caption = "Unknown";
    return;
	}
// No Flow
}
//---------------------------------------------------------------------------

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

void __fastcall TQuickCtrl::ConstructNewQuickControlWindow(TObject *Sender)
{
//
TMemoryStream *Strm = new TMemoryStream;
__try
	{
	// RTTI

	// Engine Box
    Strm->Position = 0;
    Strm->WriteComponent( EngineBox1 );
    Strm->Position = 0;
	TGroupBox * EBox;
	EBox = (TGroupBox*) Strm->ReadComponent( NULL );
	EBox->Name = "EngineBox" + String(iCtrlCount);
	EBox->Caption = "Engine " + String(iCtrlCount);
    // offset to left because EngineBox is indented by EngineBox1->Left
	EBox->Left = Width - EngineBox1->Left - (EngineBoxWidth + (42 * SF)) /*162*/;
	EBox->Parent = EngineBox1->Parent;
	InsertComponent( EBox );
    EngineBoxContainer[iCtrlCount - 1] = EBox;

    // PopUp Menu
    Strm->Position = 0;
    Strm->WriteComponent( EnginePopUpMenu1 );
    Strm->Position = 0;
    TPopupMenu * PU;
    PU = (TPopupMenu*) Strm->ReadComponent( NULL );
    PU->Name = "EnginePopUpMenu" + String(iCtrlCount);
    PU->Items->Items[0]->Name = "VolumetoMax" + String(iCtrlCount);
    PU->Items->Items[1]->Name = "VolumetoMed" + String(iCtrlCount);
    PU->Items->Items[2]->Name = "VolumeMute" + String(iCtrlCount);
    PU->Items->Items[0]->OnClick = VolumetoMaximumClick;
    PU->Items->Items[1]->OnClick = VolumetoMediumClick;
    PU->Items->Items[2]->OnClick = VolumeMuteClick;
    //PU->Parent = EngineBoxContainer[iCtrlCount - 1];    // PopUp Menu does not have a parent
    														// PopUp Menu must be deleted individually
    InsertComponent( PU );
    EngineBoxContainer[iCtrlCount - 1]->PopupMenu = PU;

    // Engine Name
    Strm->Position = 0;
    Strm->WriteComponent( EngineName1 );
    Strm->Position = 0;
    TLabel* LabelName;
    LabelName = (TLabel*) Strm->ReadComponent( NULL );
    LabelName->Name = "EngineName" + String(iCtrlCount);
    LabelName->Parent = EngineBoxContainer[iCtrlCount - 1];	// needed so component shows up inside of Box
    InsertComponent( LabelName );
    EngineNameContainer[iCtrlCount - 1] = LabelName;

	// Engine/Consist Number
    // TMaskEdit
    Strm->Position = 0;
    Strm->WriteComponent( EngNumber1 );
    Strm->Position = 0;
    TMaskEdit* EngNumMask;
    EngNumMask = (TMaskEdit*) Strm->ReadComponent( NULL );
    EngNumMask->Name = "EngNumber" + String(iCtrlCount);
    EngNumMask->Parent = EngineBoxContainer[iCtrlCount - 1];	// needed so component shows up inside of Box
    EngNumMask->OnChange = EngNumber1->OnChange; // Event Handler
    InsertComponent( EngNumMask );
    EngNumberContainer[iCtrlCount - 1] = EngNumMask;
    EngNumberContainer[iCtrlCount - 1]->Text = EngNumber1->Text;
    EBox->Caption = "Engine " + EngNumberContainer[iCtrlCount - 1]->Text + "TIU "+
        String(MainForm->EngineData->Cells [TIU_NUMBER] [EngNumber1->Text.ToInt()].ToIntDef(0));	// set Engine Box Caption

    // TUpDown
    //
    // NOTE BUG:  ReadComponent/WriteComponent of a TUpDown fails if the Associate property is any
    // value but NULL. It was for this reason that I could not used the TAbNumSpin, it uses another
    // control that must be linked just like the Associate property.
    //
    const int UpDownWidth = UpDown1->Width;								// Needed to correct width - don't know why
    Strm->Position = 0;
	UpDown1->Associate = NULL;			// See note above
    Strm->WriteComponent( UpDown1 );
    UpDown1->Associate = EngNumber1;	// See note above
    Strm->Position = 0;
    TUpDown* EngNumUpDown;
    EngNumUpDown = (TUpDown*) Strm->ReadComponent( NULL );
    UpDown1->Width = UpDownWidth;									// Needed to correct width - don't know why
    EngNumUpDown->Name = "UpDown" + String(iCtrlCount);
    EngNumUpDown->Parent = EngineBoxContainer[iCtrlCount - 1];	// needed so component shows up inside of Box
    EngNumUpDown->OnChanging = UpDown1->OnChanging; 		// Event Handler
    InsertComponent( EngNumUpDown );
    UpDownContainer[iCtrlCount - 1] = EngNumUpDown;
    EngNumUpDown->Associate = EngNumberContainer[iCtrlCount - 1];
    UpDownContainer[iCtrlCount - 1]->Width = UpDownWidth;			// Needed to correct width - don't know why
    const int EngIndex = EngNumUpDown->Position;	// needed for float to integer conversion
    EngNumberContainer[iCtrlCount - 1]->Color = RunningState[EngIndex];	// engine running state

     // Startup
    Strm->Position = 0;
    Strm->WriteComponent( StartupButton1 );
    Strm->Position = 0;
    TButton * Button;
    Button = (TButton*) Strm->ReadComponent( NULL );
    Button->Name = "StartupButton" + String(iCtrlCount);
    Button->Parent = EngineBoxContainer[iCtrlCount - 1];	// needed so component shows up inside of Box
    Button->OnClick = StartupButton1->OnClick; // Event Handler
	// Experimental - use Drop Down Menu style in CB10.1
	#if __BORLANDC__ >= 0x720
	Button->Style = (TCustomButton::TButtonStyle)bsSplitButton;
	Button->DropDownMenu = EnginePopUpMenu1;
	#endif
    InsertComponent( Button );
    StartupButtonContainer[iCtrlCount -1] = Button;

    // Volume
    Strm->Position = 0;
    Strm->WriteComponent( VolumeSlider1 );
    Strm->Position = 0;
    TAbHSlider * HSlider;
    HSlider = (TAbHSlider*) Strm->ReadComponent( NULL );
    HSlider->Name = "VolumeSlider" + String(iCtrlCount);
    HSlider->Parent = EngineBoxContainer[iCtrlCount - 1];	// needed so component shows up inside of Box
    HSlider->OnValueChange = VolumeSlider1->OnValueChange; //Change;	// Event Handler
    InsertComponent( HSlider );
    VolumeSliderContainer[iCtrlCount - 1] = HSlider;

    // Whistle
    Strm->Position = 0;
    Strm->WriteComponent( WhistleButton1 );
    Strm->Position = 0;
    TAbColBitBtn* WBitBtn;
    WBitBtn = (TAbColBitBtn*) Strm->ReadComponent( NULL );
    WBitBtn->Name = "WhistleButton" + String(iCtrlCount);
    WBitBtn->Parent = EngineBoxContainer[iCtrlCount - 1];	// needed so component shows up inside of Box
    WBitBtn->OnMouseDown = WhistleButton1->OnMouseDown;	// Event Handlers
    WBitBtn->OnMouseUp = WhistleButton1->OnMouseUp;
    InsertComponent( WBitBtn );
    WhistleButtonContainer[iCtrlCount - 1] = WBitBtn;

    // Horn
    Strm->Position = 0;
    Strm->WriteComponent( HornButton1 );
    Strm->Position = 0;
    TAbColBitBtn* HBitBtn;
    HBitBtn = (TAbColBitBtn*) Strm->ReadComponent( NULL );
    HBitBtn->Name = "HornButton" + String(iCtrlCount);
    HBitBtn->Parent = EngineBoxContainer[iCtrlCount - 1];	// needed so component shows up inside of Box
    HBitBtn->OnMouseDown = HornButton1->OnMouseDown;	// Event Handlers - same as WhistleButton !!
    HBitBtn->OnMouseUp = HornButton1->OnMouseUp;
    InsertComponent( HBitBtn );
    HornButtonContainer[iCtrlCount - 1] = HBitBtn;

    // SFS
    Strm->Position = 0;
    Strm->WriteComponent( SFSButton1 );
    Strm->Position = 0;
    TAbColBitBtn* ColBitBtn;
    ColBitBtn = (TAbColBitBtn*) Strm->ReadComponent( NULL );
    ColBitBtn->Name = "SFSButton" + String(iCtrlCount);
    ColBitBtn->Parent = EngineBoxContainer[iCtrlCount - 1];	// needed so component shows up inside of Box
    ColBitBtn->OnMouseUp = SFSButton1->OnMouseUp;	// Event Handlers
    ColBitBtn->OnMouseDown = SFSButton1->OnMouseDown;	// Event Handlers
    InsertComponent( ColBitBtn );
    SFSButtonContainer[iCtrlCount - 1] = ColBitBtn;

    // SRS
    Strm->Position = 0;
    Strm->WriteComponent( SRSButton1 );
    Strm->Position = 0;
    ColBitBtn = (TAbColBitBtn*) Strm->ReadComponent( NULL );
    ColBitBtn->Name = "SRSButton" + String(iCtrlCount);
    ColBitBtn->Parent = EngineBoxContainer[iCtrlCount - 1];	// needed so component shows up inside of Box
    ColBitBtn->OnMouseUp = SRSButton1->OnMouseUp;	// Event Handlers
    ColBitBtn->OnMouseDown = SRSButton1->OnMouseDown;	// Event Handlers
    InsertComponent( ColBitBtn );
    SRSButtonContainer[iCtrlCount - 1] = ColBitBtn;

    // SXS
    Strm->Position = 0;
    Strm->WriteComponent( SXSButton1 );
    Strm->Position = 0;
    ColBitBtn = (TAbColBitBtn*) Strm->ReadComponent( NULL );
    ColBitBtn->Name = "SXSButton" + String(iCtrlCount);
    ColBitBtn->Parent = EngineBoxContainer[iCtrlCount - 1];	// needed so component shows up inside of Box
    ColBitBtn->OnMouseUp = SXSButton1->OnMouseUp;	// Event Handlers
    ColBitBtn->OnMouseDown = SXSButton1->OnMouseDown;	// Event Handlers
    InsertComponent( ColBitBtn );
    SXSButtonContainer[iCtrlCount - 1] = ColBitBtn;

    // Bell
    Strm->Position = 0;
    Strm->WriteComponent( BellSwitch1 );
    Strm->Position = 0;
    TAbRockerSwitch * RockerSwitch;
    RockerSwitch = (TAbRockerSwitch*) Strm->ReadComponent( NULL );
    RockerSwitch->Name = "BellSwitch" + String(iCtrlCount);
    RockerSwitch->Parent = EngineBoxContainer[iCtrlCount - 1];	// needed so component shows up inside of Box
    RockerSwitch->OnStatusChanged = BellSwitch1->OnStatusChanged;	// Event Handler
    InsertComponent( RockerSwitch );
    BellSwitchContainer[iCtrlCount - 1] = RockerSwitch;

    // Smoke
    Strm->Position = 0;
    Strm->WriteComponent( SmokeSwitch1 );
    Strm->Position = 0;
    RockerSwitch = (TAbRockerSwitch*) Strm->ReadComponent( NULL );
    RockerSwitch->Name = "SmokeSwitch" + String(iCtrlCount);
    RockerSwitch->Parent = EngineBoxContainer[iCtrlCount - 1];	// needed so component shows up inside of Box
    RockerSwitch->OnStatusChanged = SmokeSwitch1->OnStatusChanged;	// Event Handler
    InsertComponent( RockerSwitch );
    SmokeSwitchContainer[iCtrlCount - 1] = RockerSwitch;

    // Speed Label
    Strm->Position = 0;
    Strm->WriteComponent( SLabel1 );
    Strm->Position = 0;
    TLabel* SLabel;
    SLabel = (TLabel*) Strm->ReadComponent( NULL );
    SLabel->Name = "SLabel" + String(iCtrlCount);
    SLabel->Parent = EngineBoxContainer[iCtrlCount - 1];	// needed so component shows up inside of Box
    InsertComponent( SLabel );

    // Speed Value
    Strm->Position = 0;
    Strm->WriteComponent( SpeedEdit1 );
    Strm->Position = 0;
    TAbNumEdit* NumEdit;
    NumEdit = (TAbNumEdit*) Strm->ReadComponent( NULL );
    NumEdit->Name = "SpeedEdit" + String(iCtrlCount);
    NumEdit->Parent = EngineBoxContainer[iCtrlCount - 1];	// needed so component shows up inside of Box
    NumEdit->OnValueChanged = SpeedEdit1->OnValueChanged;	// Event Handler
    InsertComponent( NumEdit );
    SpeedEditContainer[iCtrlCount - 1] = NumEdit;

    // Speed Buttons
    Strm->Position = 0;
    Strm->WriteComponent( ButtonPlusOne1 );
    Strm->Position = 0;
    Button = (TButton*) Strm->ReadComponent( NULL );
    Button->Name = "ButtonPlusOne" + String(iCtrlCount);
    Button->Parent = EngineBoxContainer[iCtrlCount - 1];	// needed so component shows up inside of Box
    Button->OnClick = ButtonPlusOne1->OnClick;	// Event Handler
    InsertComponent( Button );

    Strm->Position = 0;
    Strm->WriteComponent( ButtonPlusFive1 );
    Strm->Position = 0;
    Button = (TButton*) Strm->ReadComponent( NULL );
    Button->Name = "ButtonPlusFive" + String(iCtrlCount);
    Button->Parent = EngineBoxContainer[iCtrlCount - 1];	// needed so component shows up inside of Box
    Button->OnClick = ButtonPlusFive1->OnClick;	// Event Handler
    InsertComponent( Button );

    Strm->Position = 0;
    Strm->WriteComponent( ButtonMinusOne1 );
    Strm->Position = 0;
    Button = (TButton*) Strm->ReadComponent( NULL );
    Button->Name = "ButtonMinusOne" + String(iCtrlCount);
    Button->Parent = EngineBoxContainer[iCtrlCount - 1];	// needed so component shows up inside of Box
    Button->OnClick = ButtonMinusOne1->OnClick;	// Event Handler
    InsertComponent( Button );

    Strm->Position = 0;
    Strm->WriteComponent( ButtonMinusFive1 );
    Strm->Position = 0;
    Button = (TButton*) Strm->ReadComponent( NULL );
    Button->Name = "ButtonMinusFive" + String(iCtrlCount);
    Button->Parent = EngineBoxContainer[iCtrlCount - 1];	// needed so component shows up inside of Box
    Button->OnClick = ButtonMinusFive1->OnClick;	// Event Handler
    InsertComponent( Button );

    // Dir Toggle Switch
    Strm->Position = 0;
    Strm->WriteComponent( DIRToggle1 );
    Strm->Position = 0;
    TAbToggleSwitch * ToggleSwitch; // = new TAbRockerSwitch(this);
    ToggleSwitch = (TAbToggleSwitch*) Strm->ReadComponent( NULL );
    ToggleSwitch->Name = "DIRToggle" + String(iCtrlCount);
    ToggleSwitch->Parent = EngineBoxContainer[iCtrlCount - 1];	// needed so component shows up inside of Box
    ToggleSwitch->OnStatusChanged = DIRToggle1->OnStatusChanged;	// Event Handler
    InsertComponent( ToggleSwitch );
    DIRToggleContainer[iCtrlCount - 1] = ToggleSwitch;

    // Coupler Buttons
    Strm->Position = 0;
    Strm->WriteComponent( CPLRRButton1 );
    Strm->Position = 0;
    ColBitBtn = (TAbColBitBtn*) Strm->ReadComponent( NULL );
    ColBitBtn->Name = "RearCouplerButton" + String(iCtrlCount);
    ColBitBtn->Parent = EngineBoxContainer[iCtrlCount - 1];	// needed so component shows up inside of Box
    ColBitBtn->OnMouseUp = CPLRRButton1->OnMouseUp;	// Event Handlers
    ColBitBtn->OnMouseDown = CPLRRButton1->OnMouseDown;	// Event Handlers
    InsertComponent( ColBitBtn );
    CPLRRButtonContainer[iCtrlCount - 1] = ColBitBtn;

    Strm->Position = 0;
    Strm->WriteComponent( CPLRFButton1 );
    Strm->Position = 0;
    ColBitBtn = (TAbColBitBtn*) Strm->ReadComponent( NULL );
    ColBitBtn->Name = "FrontCouplerButton" + String(iCtrlCount);
    ColBitBtn->Parent = EngineBoxContainer[iCtrlCount - 1];	// needed so component shows up inside of Box
    ColBitBtn->OnMouseUp = CPLRFButton1->OnMouseUp;	// Event Handlers
    ColBitBtn->OnMouseDown = CPLRFButton1->OnMouseDown;	// Event Handlers
    InsertComponent( ColBitBtn );
    CPLRFButtonContainer[iCtrlCount - 1] = ColBitBtn;

    // Stop Button
    Strm->Position = 0;
    Strm->WriteComponent( StopButton1 );
    Strm->Position = 0;
    Button = (TButton*) Strm->ReadComponent( NULL );
    Button->Name = "StopButton" + String(iCtrlCount);
    Button->Parent = EngineBoxContainer[iCtrlCount - 1];	// needed so component shows up inside of Box
    Button->OnMouseDown= StopButton1->OnMouseDown;	// Event Handler
    InsertComponent( Button );

    // Shutdown Button
    Strm->Position = 0;
    Strm->WriteComponent( ShutdownButton1 );
    Strm->Position = 0;
    Button = (TButton*) Strm->ReadComponent( NULL );
    Button->Name = "Shutdown" + String(iCtrlCount);
    Button->Parent = EngineBoxContainer[iCtrlCount - 1];	// needed so component shows up inside of Box
    Button->OnClick = ShutdownButton1->OnClick;	// Event Handler
    InsertComponent( Button );
    ShutdownButtonContainer[iCtrlCount -1] = Button;
    }
__finally {
    delete Strm;
    }
}
//---------------------------------------------------------------------------

int __fastcall TQuickCtrl::PickControlNumber(TObject *Sender)
{
int Num;
TControl * ControlUsed = (TControl *)Sender;
// setup pointer to buffer being saved
Num = -1;
for (int i = QUICKCOUNT ; i > 0 ; i--) {
	if (ControlUsed->Name.Pos(String(i))) return i - 1;
	}
// major failure if it gets here
assert(Num != -1);
return Num;
}
//---------------------------------------------------------------------------

void __fastcall TQuickCtrl::StartupButtonClick(TObject *Sender)
{
// Startup
//
// https://stackoverflow.com/questions/31649315/c-builder-button-click-function-reentry
LOCKEM()

const TCursor OldCursor = Screen->Cursor; 	// save cursor
Screen->Cursor = crHourGlass;

const int ControlNo = PickControlNumber(Sender);
const int ConsistNo = UpDownContainer[ControlNo]->Position;
const SHORT ShiftKeyState = StartupButtonContainer[ControlNo]->Focused() ? GetKeyState(VK_SHIFT) : 0;	// Use Extended Startup (Engines Only)
//
// Use the Engine Number or the Head Engine Number of a Lashup to set switches to the correct state
//
byte iStatus1, iDir;
unsigned short iMasterVol, iSpeed;
//
int HeadEngine = -1;
if (ConsistNo <= ENGINEMAX) HeadEngine = ConsistNo;

// New use of GetLashUpEngine tested ok
if (ConsistNo >= LASHUPMIN && ConsistNo <= LASHUPMAX) {
	HeadEngine = MainForm->GetLashUpEngine(EngineList[ConsistNo], 0);  // get head engine
	// make sure we have the head engine TIU number
	if (HeadEngine >= ENGINEMIN) {
		TIUNo[HeadEngine] = MainForm->EngineData->Cells [TIU_NUMBER] [HeadEngine].ToIntDef(0);
		if (TIUNo[HeadEngine] == 0 && MainForm->EngineData->Cells [ENG_NAME] [HeadEngine] != "*") {
			MainForm->DebugMessagePopup("Warning", MainForm->TIUNoLashupMsg, "Engine # " + String(HeadEngine));
            Screen->Cursor = OldCursor;		// restore cursor
			return;
			}
		}
	}

if (HeadEngine >= ENGINEMIN) {
    // RAM 0x25 is smoke switch status from the HeadEngine
    bool bretv = MainForm->FetchByte(0x25, &iStatus1, HeadEngine, TIUNo[HeadEngine]);
    if (!bretv)
        iStatus1 = 0;
    // RAM 0x0E/0F is Master Volume
    bretv = MainForm->FetchWord(0x0E, &iMasterVol, HeadEngine, TIUNo[HeadEngine]);
    if (!bretv)
        iMasterVol = 100;           // default
        else
        iMasterVol = iMasterVol / 327;
    // RAM 0x62/62 is goal speed
    bretv = MainForm->FetchWord(0x62, &iSpeed, HeadEngine, TIUNo[HeadEngine]);
    if (!bretv)
        iSpeed = 0;             // default
        else
        iSpeed = iSpeed >> 3;	// divide by 8
    // RAM 0x67 is Direction
    bretv = MainForm->FetchByte(0x67, &iDir, HeadEngine, TIUNo[HeadEngine]);
    if (!bretv)
        iDir = 0;         // default
	} else {
	// could not get head engine number
	MainForm->DebugMessagePopup("Error", "Could not get LashUp head engine number", "");
    Screen->Cursor = OldCursor;		// restore cursor
	return;
	}
// Create Lashup if its a lashup
//
if (ConsistNo >= LASHUPMIN) (void)ptrTIUCommThread->Send_String_FIFO(ConsistNo, "U", TIUNo[HeadEngine], RemoteNo, 11, VERY_LONG_WAIT, RECORD_OK, EngineList[ConsistNo] + 1);
		// uses LashUpEngineList but skiping over the 0x2C, starts right at the head engine number
		// LashUp is always sent to the TIU as engine number 101 (DCS EngineNo #102 0x66).
        // It takes quite a while for the TIU to setup the LashUp (almost 2 seconds)
        // NOTE NOTE : this command is handled as a special case inside of ptrTIUCommThread->Send_String_FIFO()
        // and Playback - "U"
        // Fails quietly

// Use the Engine Number or the Head Engine Number of a Lashup to set switches to the correct state
//
//  ---------------------------------------------------------------------
// Wondrous apparition provided by magicians -- John Fogerty
//  ---------------------------------------------------------------------
if (HeadEngine != -1) {
    // we already read up smoke switch status, master volume, speed and direction from the HeadEngine, so set controls
    // the SmokeSwitch on the Quick Controls window uses OnStatusChanged........
    SmokeOnStatusChanged = SmokeSwitchContainer[ControlNo]->OnStatusChanged;
    SmokeSwitchContainer[ControlNo]->OnStatusChanged = NULL;
    SmokeSwitchContainer[ControlNo]->Checked = (iStatus1 & 0x80);
    SmokeSwitchContainer[ControlNo]->OnStatusChanged = SmokeOnStatusChanged;
    // the VolumeSlider on the Quick Controls window uses OnValueChange........
    VolumeSliderOnValueChange = VolumeSliderContainer[ControlNo]->OnValueChange;
    VolumeSliderContainer[ControlNo]->OnValueChange = NULL;
    VolumeSliderContainer[ControlNo]->Value = iMasterVol;
    VolumeSliderContainer[ControlNo]->OnValueChange = VolumeSliderOnValueChange;
    // the SpeedEdit on the Quick Controls window uses OnValueChanged........
    SpeedEditOnValueChanged = SpeedEditContainer[ControlNo]->OnValueChanged;
    SpeedEditContainer[ControlNo]->OnValueChanged = NULL;
    SpeedEditContainer[ControlNo]->Value = iSpeed;
    SpeedEditContainer[ControlNo]->OnValueChanged = SpeedEditOnValueChanged;
    LastSpeedContainer[ControlNo] = iSpeed;
    // the DIRToggle on the Quick Controls window uses OnStatusChanged......
    DIROnStatusChanged = DIRToggleContainer[ControlNo]->OnStatusChanged;
    DIRToggleContainer[ControlNo]->OnStatusChanged = NULL;
    DIRToggleContainer[ControlNo]->Checked = !(iDir & 0x01);	//  0 = FORWARD, 1 = REVERSE
    DIRToggleContainer[ControlNo]->OnStatusChanged = DIROnStatusChanged;
	}

// Set the mute popup menu check mark
if (VolumeSliderContainer[ControlNo]->Value == 0) {
    EngineBoxContainer[ControlNo]->PopupMenu->Items->Items[2]->Checked = true;	// muted
    } else {
    EngineBoxContainer[ControlNo]->PopupMenu->Items->Items[2]->Checked = false;
    }

// Startup    // Command "u4" for normal, "u6" for Extended
// Carl Ross suggested to set Command Mode in case the power on Watchdog was missed
(void)ptrTIUCommThread->Send_String_FIFO(ConsistNo, "m4", TIUNo[ConsistNo], RemoteNo, 11, SHORT_WAIT, RECORD_OK, EngineList[ConsistNo], NULL, NULL); // in StartupButtonClick()
MainForm->Delay(m4DELAY);

// if either shift key is pressed whilst clicking "Startup" button, use the extended startup sound
if (Setup->ExtendedSUSD->Checked || (ShiftKeyState < 0)) {
	(void)ptrTIUCommThread->Send_String_FIFO(ConsistNo, COMMAND_STARTUP_EXTENDED, TIUNo[ConsistNo], RemoteNo, 11, LONG_WAIT, RECORD_OK, EngineList[ConsistNo], NULL, NULL); // in StartupButtonClick()
    } else {
	(void)ptrTIUCommThread->Send_String_FIFO(ConsistNo, COMMAND_STARTUP, TIUNo[ConsistNo], RemoteNo, 11, LONG_WAIT, RECORD_OK, EngineList[ConsistNo], NULL, NULL); // in StartupButtonClick()
    }
MainForm->StartedConsists->Value++; // in StartupButtonClick()
//---------------------------------------------------------------------------
// Note : "d0" and "s0" often fail right after startup when engine is first powered on - I added a short delay
//---------------------------------------------------------------------------

#if USED0ANDS0AFTERSTARTUP
MainForm->Delay(WAITAFTERSTARTUP);
DIRToggleContainer[ControlNo]->Checked = true;	// this might send a DIR Forward command
// set DIR forward  "d0"  - this might not be necessary but will not hurt
(void)ptrTIUCommThread->Send_String_FIFO(ConsistNo, DIRECTION_FORWARD, TIUNo[ConsistNo], RemoteNo, 11, SHORT_WAIT, RECORD_OK, EngineList[ConsistNo], NULL, NULL);
SpeedEditContainer[ControlNo]->Value = 0; 	// speed always starts out as 0
LastSpeedContainer[ControlNo] = 0;
#else
MainForm->Delay(WAITAFTERSTARTUP);
#endif

// make sure that bell switch is off
BellSwitchContainer[ControlNo]->Checked = false;	// this might send a Bell off command
// set saved Smoke value
SmokeSwitchStatusChanged(Sender);
//
SetRunningState(ConsistNo, clLime); // set engine number to lime/green
//
Screen->Cursor = OldCursor;		// restore cursor
}
//---------------------------------------------------------------------------

void __fastcall TQuickCtrl::WhistleButtonMouseDown(TObject *Sender,
      TMouseButton Button, TShiftState Shift, int X, int Y)
{
// Whistle Button Down
// https://stackoverflow.com/questions/31649315/c-builder-button-click-function-reentry
LOCKEM()
//
const int ControlNo = PickControlNumber(Sender);
const int ConsistNo = UpDownContainer[ControlNo]->Position;
// start whistle
TOp * ptrOperation = ptrOp[ConsistNo];
if (ptrOperation) {	// reflect the change on to any open Operations window
   	TAbColBitBtn * ptrWhistleButton = ptrOperation->WhistleButton;
    ptrWhistleButton->GradBtnFace->Visible = true;
   	}
#ifdef BUTTONMETHOD
(void)ptrTIUCommThread->Send_String_FIFO(ConsistNo, WHISTLE_ON, TIUNo[ConsistNo], RemoteNo, 11, SHORT_WAIT, RECORD_OK, EngineList[ConsistNo], NULL, NULL);
#else
// Send CM_WHISTLE message to ourselves which will perform this function
(void) PostMessage(Handle, CM_WHISTLE, true, ConsistNo);	// true = button down
#endif
}
//---------------------------------------------------------------------------

void __fastcall TQuickCtrl::WhistleButtonMouseUp(TObject *Sender,
      TMouseButton Button, TShiftState Shift, int X, int Y)
{
// Whistle Button Up
const int ControlNo = PickControlNumber(Sender);
const int ConsistNo = UpDownContainer[ControlNo]->Position;
TOp * ptrOperation = ptrOp[ConsistNo];
if (ptrOperation) {	// reflect the change on to any open Operations window
   	TAbColBitBtn * ptrWhistleButton = ptrOperation->WhistleButton;
    ptrWhistleButton->GradBtnFace->Visible = false;
  	}
// stop whistle : -2  : FFFD one's complement
#ifdef BUTTONMETHOD
(void)ptrTIUCommThread->Send_String_FIFO(ConsistNo, WHISTLE_OFF, TIUNo[ConsistNo], RemoteNo, 11, SHORT_WAIT, RECORD_OK, EngineList[ConsistNo], NULL, NULL);
#else
// Send CM_WHISTLE message to ourselves which will perform this function
(void) PostMessage(Handle, CM_WHISTLE, false, ConsistNo);	// false = button up
#endif
}
//---------------------------------------------------------------------------

void __fastcall TQuickCtrl::CMWhistle(TMessage &Message)
{
// Handle Message : CM_WHISTLE
//
//if (DEBUGGING) DB->Debug_Print("CMWhistle");
const int ConsistNo = Message.LParam;		// set button state from Long Parameter
bool Dn = Message.WParam;		// set button state from Word Parameter
if (Dn) {
	// start whistle
	(void)ptrTIUCommThread->Send_String_FIFO(ConsistNo, WHISTLE_ON, TIUNo[ConsistNo], RemoteNo, 11, SHORT_WAIT, RECORD_OK, EngineList[ConsistNo], NULL, NULL);
    }
if (!Dn) {
	// stop whistle
	(void)ptrTIUCommThread->Send_String_FIFO(ConsistNo, WHISTLE_OFF, TIUNo[ConsistNo], RemoteNo, 11, SHORT_WAIT, RECORD_OK, EngineList[ConsistNo], NULL, NULL);
	}
}
//---------------------------------------------------------------------------

void __fastcall TQuickCtrl::SFSButton1MouseDown(TObject *Sender,
      TMouseButton Button, TShiftState Shift, int X, int Y)
{
// SFS on
// https://stackoverflow.com/questions/31649315/c-builder-button-click-function-reentry
LOCKEM()
//
const int ControlNo = PickControlNumber(Sender);
const int ConsistNo = UpDownContainer[ControlNo]->Position;
// Forward Sound
TOp * ptrOperation = ptrOp[ConsistNo];
if (ptrOperation) {	// reflect the change on to any open Operations window
   	TAbColBitBtn * ptrSFSButton = ptrOperation->SFSButton;
    ptrSFSButton->GradBtnFace->Visible = true;
   	}

#ifdef BUTTONMETHOD
(void)ptrTIUCommThread->Send_String_FIFO(ConsistNo, DOUBLE_TOOT, TIUNo[ConsistNo], RemoteNo, 11, SHORT_WAIT, RECORD_OK, EngineList[ConsistNo], NULL, NULL);
#else
// Send CM_SFS message to ourselves which will perform this function
(void) PostMessage(Handle, CM_SFS, ControlNo, ConsistNo);
#endif
}
//---------------------------------------------------------------------------

void __fastcall TQuickCtrl::SFSButton1MouseUp(TObject *Sender,
      TMouseButton Button, TShiftState Shift, int X, int Y)
{
// SFS on
const int ControlNo = PickControlNumber(Sender);
const int ConsistNo = UpDownContainer[ControlNo]->Position;
// Forward Sound
TOp * ptrOperation = ptrOp[ConsistNo];
if (ptrOperation) {	// reflect the change on to any open Operations window
   	TAbColBitBtn * ptrSFSButton = ptrOperation->SFSButton;
    ptrSFSButton->GradBtnFace->Visible = false;
  	}
}
//---------------------------------------------------------------------------

void __fastcall TQuickCtrl::CMSFS(TMessage &Message)
{
// SFS on
const int ConsistNo = Message.LParam;		// set button state from Long Parameter
// Forward Sound
(void)ptrTIUCommThread->Send_String_FIFO(ConsistNo, DOUBLE_TOOT, TIUNo[ConsistNo], RemoteNo, 11, SHORT_WAIT, RECORD_OK, EngineList[ConsistNo], NULL, NULL);
}
//---------------------------------------------------------------------------

void __fastcall TQuickCtrl::SRSButton1MouseDown(TObject *Sender,
      TMouseButton Button, TShiftState Shift, int X, int Y)
{
// SRS on
// https://stackoverflow.com/questions/31649315/c-builder-button-click-function-reentry
LOCKEM()
//
const int ControlNo = PickControlNumber(Sender);
const int ConsistNo = UpDownContainer[ControlNo]->Position;
// Reverse Sound
TOp * ptrOperation = ptrOp[ConsistNo];
if (ptrOperation) {	// reflect the change on to any open Operations window
   	TAbColBitBtn * ptrSRSButton = ptrOperation->SRSButton;
    ptrSRSButton->GradBtnFace->Visible = true;
   	}
#ifdef BUTTONMETHOD
(void)ptrTIUCommThread->Send_String_FIFO(ConsistNo, TRIPLE_TOOT, TIUNo[ConsistNo], RemoteNo, 11, SHORT_WAIT, RECORD_OK, EngineList[ConsistNo], NULL, NULL);
#else
// Send CM_SRS message to ourselves which will perform this function
(void) PostMessage(Handle, CM_SRS, ControlNo, ConsistNo);
#endif
}
//---------------------------------------------------------------------------

void __fastcall TQuickCtrl::SRSButton1MouseUp(TObject *Sender,
      TMouseButton Button, TShiftState Shift, int X, int Y)
{
// SRS on
const int ControlNo = PickControlNumber(Sender);
const int ConsistNo = UpDownContainer[ControlNo]->Position;
// Reverse Sound
TOp * ptrOperation = ptrOp[ConsistNo];
if (ptrOperation) {	// reflect the change on to any open Operations window
   	TAbColBitBtn * ptrSRSButton = ptrOperation->SRSButton;
    ptrSRSButton->GradBtnFace->Visible = false;
  	}
}
//---------------------------------------------------------------------------

void __fastcall TQuickCtrl::CMSRS(TMessage &Message)
{
// SRS on
const int ConsistNo = Message.LParam;		// set button state from Long Parameter
// Reverse Sound
(void)ptrTIUCommThread->Send_String_FIFO(ConsistNo, TRIPLE_TOOT, TIUNo[ConsistNo], RemoteNo, 11, SHORT_WAIT, RECORD_OK, EngineList[ConsistNo], NULL, NULL);
}
//---------------------------------------------------------------------------

void __fastcall TQuickCtrl::SXSButtonMouse1Down(TObject *Sender,
      TMouseButton Button, TShiftState Shift, int X, int Y)
{
// SXS on
// https://stackoverflow.com/questions/31649315/c-builder-button-click-function-reentry
LOCKEM()
//
const int ControlNo = PickControlNumber(Sender);
const int ConsistNo = UpDownContainer[ControlNo]->Position;
// Crossing Sound
TOp * ptrOperation = ptrOp[ConsistNo];
if (ptrOperation) {	// reflect the change on to any open Operations window
   	TAbColBitBtn * ptrSXSButton = ptrOperation->SXSButton;
    ptrSXSButton->GradBtnFace->Visible = true;
   	}
#ifdef BUTTONMETHOD
(void)ptrTIUCommThread->Send_String_FIFO(ConsistNo, CROSSING, TIUNo[ConsistNo], RemoteNo, 11, SHORT_WAIT, RECORD_OK, EngineList[ConsistNo], NULL, NULL);
#else
// Send CM_SXS message to ourselves which will perform this function
(void) PostMessage(Handle, CM_SXS, ControlNo, ConsistNo);
#endif
}
//---------------------------------------------------------------------------

void __fastcall TQuickCtrl::SXSButton1MouseUp(TObject *Sender,
      TMouseButton Button, TShiftState Shift, int X, int Y)
{
// SXS on
const int ControlNo = PickControlNumber(Sender);
const int ConsistNo = UpDownContainer[ControlNo]->Position;
// Crossing Sound
TOp * ptrOperation = ptrOp[ConsistNo];
if (ptrOperation) {	// reflect the change on to any open Operations window
   	TAbColBitBtn * ptrSXSButton = ptrOperation->SXSButton;
    ptrSXSButton->GradBtnFace->Visible = false;
  	}
}
//---------------------------------------------------------------------------

void __fastcall TQuickCtrl::CMSXS(TMessage &Message)
{
// SXS on
const int ConsistNo = Message.LParam;		// set button state from Long Parameter
// Crossing Sound
(void)ptrTIUCommThread->Send_String_FIFO(ConsistNo, CROSSING, TIUNo[ConsistNo], RemoteNo, 11, SHORT_WAIT, RECORD_OK, EngineList[ConsistNo], NULL, NULL);
}
//---------------------------------------------------------------------------

void __fastcall TQuickCtrl::BellSwitchStatusChanged(TObject *Sender)
{
// bell on or off
const int ControlNo = PickControlNumber(Sender);
const int ConsistNo = UpDownContainer[ControlNo]->Position;
if (BellSwitchContainer[ControlNo]->Checked) {
	// start bell
	(void)ptrTIUCommThread->Send_String_FIFO(ConsistNo, BELL_ON, TIUNo[ConsistNo], RemoteNo, 11, SHORT_WAIT, RECORD_OK, EngineList[ConsistNo], NULL, NULL);
    TOp * ptrOperation = ptrOp[ConsistNo];
    if (ptrOperation) {	// reflect the change on to any open Operations window
    	TAbRockerSwitch * ptrBellSwitch = ptrOperation->BellSwitch;
        // The BellSwitch on the Op window uses OnStatusChanged.....
	    BellOnStatusChanged = ptrBellSwitch->OnStatusChanged;
    	ptrBellSwitch->OnStatusChanged = NULL;
        ptrBellSwitch->Checked = true;
		ptrBellSwitch->OnStatusChanged = BellOnStatusChanged;
    	}
    } else {
    // stop bell : -4  : FFFB one's complement
	(void)ptrTIUCommThread->Send_String_FIFO(ConsistNo, BELL_OFF, TIUNo[ConsistNo], RemoteNo, 11, SHORT_WAIT, RECORD_OK, EngineList[ConsistNo], NULL, NULL);
    TOp * ptrOperation = ptrOp[ConsistNo];
    if (ptrOperation) {	// reflect the change on to any open Operations window
    	TAbRockerSwitch * ptrBellSwitch = ptrOperation->BellSwitch;
        // The BellSwitch on the Op window uses OnStatusChanged.....
	    BellOnStatusChanged = ptrBellSwitch->OnStatusChanged;
    	ptrBellSwitch->OnStatusChanged = NULL;
        ptrBellSwitch->Checked = false;
		ptrBellSwitch->OnStatusChanged = BellOnStatusChanged;
    	}
    }
}
//---------------------------------------------------------------------------

void __fastcall TQuickCtrl::DIRToggleStatusChanged(TObject *Sender)
{
// Direction
const int ControlNo = PickControlNumber(Sender);
const int ConsistNo = UpDownContainer[ControlNo]->Position;
// Send Direction forward or Direction Reverse
if (DIRToggleContainer[ControlNo]->Checked) {
    // Foward
	(void)ptrTIUCommThread->Send_String_FIFO(ConsistNo, DIRECTION_FORWARD, TIUNo[ConsistNo], RemoteNo, 11, SHORT_WAIT, RECORD_OK, EngineList[ConsistNo], NULL, NULL);
    TOp * ptrOperation = ptrOp[ConsistNo];
    if (ptrOperation) {	// reflect the change on to any open Operations window
    	TAbToggleSwitch * ptrDIRToggle = ptrOperation->DIRToggle;
		// The DIRToggle on the Op window uses OnStatusChanged.....
	    DIROnStatusChanged = ptrDIRToggle->OnStatusChanged;
    	ptrDIRToggle->OnStatusChanged = NULL;
        ptrDIRToggle->Checked = true;
		ptrDIRToggle->OnStatusChanged = DIROnStatusChanged;
        }
	} else {
   	// Reverse
	(void)ptrTIUCommThread->Send_String_FIFO(ConsistNo, DIRECTION_REVERSE, TIUNo[ConsistNo], RemoteNo, 11, SHORT_WAIT, RECORD_OK, EngineList[ConsistNo], NULL, NULL);
    TOp * ptrOperation = ptrOp[ConsistNo];
    if (ptrOperation) {	// reflect the change on to any open Operations window
    	TAbToggleSwitch * ptrDIRToggle = ptrOperation->DIRToggle;
        // The DIRToggle on the Op window uses OnStatusChanged.....
	    DIROnStatusChanged = ptrDIRToggle->OnStatusChanged;
    	ptrDIRToggle->OnStatusChanged = NULL;
        ptrDIRToggle->Checked = false;
		ptrDIRToggle->OnStatusChanged = DIROnStatusChanged;
    	}
    }
SpeedEditContainer[ControlNo]->Value = 0;
LastSpeedContainer[ControlNo] = 0;
}
//---------------------------------------------------------------------------

void __fastcall TQuickCtrl::ShutdownClick(TObject *Sender)
{
// Shutdown
// 
// https://stackoverflow.com/questions/31649315/c-builder-button-click-function-reentry
LOCKEM()
// 
const int ControlNo = PickControlNumber(Sender);
const int ConsistNo = UpDownContainer[ControlNo]->Position;
const SHORT ShiftKeyState = ShutdownButtonContainer[ControlNo]->Focused() ? GetKeyState(VK_SHIFT) : 0;	// Use Extended Shutdown (Engines Only)

// make sure that DIR is set to forward
DIRToggleContainer[ControlNo]->Checked = true;	// this will send a DIR Forward command unless DIR is already Forward
// make sure that bell switch is off
BellSwitchContainer[ControlNo]->Checked = false;	// this might send a Bell off command unless Bell is already off
//
if (ConsistNo >= LASHUPMIN && EngineList[ConsistNo] == NULL) {
    return; // fail if the Engine List is not created for Lashups
    }
// set speed to zero
(void)ptrTIUCommThread->Send_String_FIFO(ConsistNo, COMMAND_STOP, TIUNo[ConsistNo], RemoteNo, 11, SHORT_WAIT, RECORD_OK, EngineList[ConsistNo], NULL, NULL);
// Shutdown   // Command "u5" for normal, "u7" for extended
// if either shift key is pressed whilst clicking "Shutdown" button, use the extended shutdown sound
if (Setup->ExtendedSUSD->Checked || (ShiftKeyState < 0)) {
	(void)ptrTIUCommThread->Send_String_FIFO(ConsistNo, COMMAND_SHUTDOWN_EXTENDED, TIUNo[ConsistNo], RemoteNo, 11, SHORT_WAIT, RECORD_OK, EngineList[ConsistNo], NULL, NULL); // in Quick: ShutdownClick()
    } else {
	(void)ptrTIUCommThread->Send_String_FIFO(ConsistNo, COMMAND_SHUTDOWN, TIUNo[ConsistNo], RemoteNo, 11, SHORT_WAIT, RECORD_OK, EngineList[ConsistNo], NULL, NULL); // in Quick: ShutdownClick()
    }
//
SetRunningState(ConsistNo, clRed);
SpeedEditContainer[ControlNo]->Value = 0;
LastSpeedContainer[ControlNo] = 0;
}
//---------------------------------------------------------------------------

void __fastcall TQuickCtrl::StopButtonMouseDown(TObject *Sender,
      TMouseButton Button, TShiftState Shift, int X, int Y)
{
const int ControlNo = PickControlNumber(Sender);
// speed 0
SpeedEditContainer[ControlNo]->Value = 0;	//sends the speed 0 command unless the speed is already 0
LastSpeedContainer[ControlNo] = 0;
// special case, if the speed is 0, still send speed 0 command
SpeedEditValueChanged(Sender);
}
//---------------------------------------------------------------------------

void __fastcall TQuickCtrl::ButtonPlusOneClick(TObject *Sender)
{
// https://stackoverflow.com/questions/31649315/c-builder-button-click-function-reentry
LOCKEM()
//
const int ControlNo = PickControlNumber(Sender);
int Spd = SpeedEditContainer[ControlNo]->Value;
Spd += 1;
if (Spd > Setup->MaxSpeed->Value) Spd = Setup->MaxSpeed->Value;
SpeedEditContainer[ControlNo]->Value = Spd;
}
//---------------------------------------------------------------------------

void __fastcall TQuickCtrl::ButtonPlusFiveClick(TObject *Sender)
{
// https://stackoverflow.com/questions/31649315/c-builder-button-click-function-reentry
LOCKEM()
//
const int ControlNo = PickControlNumber(Sender);
int Spd = SpeedEditContainer[ControlNo]->Value;
Spd += 5;
if (Spd > Setup->MaxSpeed->Value) Spd = Setup->MaxSpeed->Value;
SpeedEditContainer[ControlNo]->Value = Spd;
}
//---------------------------------------------------------------------------

void __fastcall TQuickCtrl::ButtonMinusOneClick(TObject *Sender)
{
// https://stackoverflow.com/questions/31649315/c-builder-button-click-function-reentry
LOCKEM()
//
const int ControlNo = PickControlNumber(Sender);
int Spd = SpeedEditContainer[ControlNo]->Value;
Spd -= 1;
if (Spd <= 0) Spd = 0;
SpeedEditContainer[ControlNo]->Value = Spd;
}
//---------------------------------------------------------------------------

void __fastcall TQuickCtrl::ButtonMinusFiveClick(TObject *Sender)
{
// https://stackoverflow.com/questions/31649315/c-builder-button-click-function-reentry
LOCKEM()
//
const int ControlNo = PickControlNumber(Sender);
int Spd = SpeedEditContainer[ControlNo]->Value;
Spd -= 5;
if (Spd <= 0) Spd = 0;
SpeedEditContainer[ControlNo]->Value = Spd;
}
//---------------------------------------------------------------------------

void __fastcall TQuickCtrl::SpeedEditValueChanged(TObject *Sender)
{
// value changed
const int ControlNo = PickControlNumber(Sender);
const int ConsistNo = UpDownContainer[ControlNo]->Position;
const int MPH = SpeedEditContainer[ControlNo]->Value;

if ((MPH != 0) && (MPH == LastSpeedContainer[ControlNo])) return;					// NOP

TOp * ptrOperation = ptrOp[ConsistNo];
if (ptrOperation) {	// reflect the change on to any open Operations window
	// the SpeedSlider on the Op window uses OnValueChange........
   	TAbVSlider * ptrSpeedSlider = ptrOperation->SpeedSlider;
    SpeedSliderOnValueChange = ptrSpeedSlider->OnValueChange;
   	ptrSpeedSlider->OnValueChange = NULL;
    ptrSpeedSlider->Value = MPH;
	ptrSpeedSlider->OnValueChange = SpeedSliderOnValueChange;
   	}

// originally thought this was needed
//if (MPH == 0) {	// make sure that we are in command mode
//	(void)ptrTIUCommThread->Send_String_FIFO(ConsistNo, COMMAND_MODE, TIUNo, RemoteNo, 11, SHORT_WAIT, RECORD_OK, EngineList[ConsistNo], NULL, NULL);
//	}

if (Setup->AutoCoupler->Checked && (LastSpeedContainer[ControlNo] == 0) && (MPH != 0)) {	// do this only when moving off 0
    // send SCS before moving speed
	(void)ptrTIUCommThread->Send_String_FIFO(ConsistNo, COUPLER_CLASH, TIUNo[ConsistNo], RemoteNo, 11, SHORT_WAIT, RECORD_OK, EngineList[ConsistNo], NULL, NULL);
    }

// Scale the speed if requested in Setup
const int ScaledMPH = MPH / MainForm->GlobalMPHScaling;
// Convert value into chars
char str[10];
(void)snprintf(str, sizeof str, SPEED_SET, ScaledMPH);
(void)ptrTIUCommThread->Send_String_FIFO(ConsistNo, str, TIUNo[ConsistNo], RemoteNo, 11, SHORT_WAIT, RECORD_OK, EngineList[ConsistNo], NULL, NULL);

LastSpeedContainer[ControlNo] = MPH;	// new speed becomes the last speed
}
//---------------------------------------------------------------------------

void __fastcall TQuickCtrl::VolumetoMaximumClick(TObject *Sender)
{
//
const int ControlNo = PickControlNumber(Sender);
VolumeSliderContainer[ControlNo]->Value = 100;
// clear check
EngineBoxContainer[ControlNo]->PopupMenu->Items->Items[2]->Checked = false;
}
//---------------------------------------------------------------------------

void __fastcall TQuickCtrl::VolumetoMediumClick(TObject *Sender)
{
//
const int ControlNo = PickControlNumber(Sender);
VolumeSliderContainer[ControlNo]->Value = 70;
// clear check
EngineBoxContainer[ControlNo]->PopupMenu->Items->Items[2]->Checked = false;
}
//---------------------------------------------------------------------------

void __fastcall TQuickCtrl::VolumeMuteClick(TObject *Sender)
{
//
const int ControlNo = PickControlNumber(Sender);
VolumeSliderContainer[ControlNo]->Value = 0;
// set check
EngineBoxContainer[ControlNo]->PopupMenu->Items->Items[2]->Checked = true; 	// muted checkmark
}
//---------------------------------------------------------------------------

void __fastcall TQuickCtrl::SmokeSwitchStatusChanged(TObject *Sender)
{
const int ControlNo = PickControlNumber(Sender);
const int ConsistNo = UpDownContainer[ControlNo]->Position;
// Send Smoke on or Smoke off
if (SmokeSwitchContainer[ControlNo]->Checked) {
	// ON
	(void)ptrTIUCommThread->Send_String_FIFO(ConsistNo, SMOKE_EXHAUST_ON, TIUNo[ConsistNo], RemoteNo, 11, SHORT_WAIT, RECORD_OK, EngineList[ConsistNo], NULL, NULL);
    TOp * ptrOperation = ptrOp[ConsistNo];
    if (ptrOperation) {	// reflect the change on to any open Operations window
    	TAbRockerSwitch * ptrSmokeSwitch = ptrOperation->SmokeSwitch;
        // The SmokeSwitch on the Op window uses OnStatusChanged.....
	    SmokeOnStatusChanged = ptrSmokeSwitch->OnStatusChanged;
    	ptrSmokeSwitch->OnStatusChanged = NULL;
        ptrSmokeSwitch->Checked = true;
		ptrSmokeSwitch->OnStatusChanged = SmokeOnStatusChanged;
    	}
	} else {
	// OFF
	(void)ptrTIUCommThread->Send_String_FIFO(ConsistNo, SMOKE_EXHAUST_OFF, TIUNo[ConsistNo], RemoteNo, 11, SHORT_WAIT, RECORD_OK, EngineList[ConsistNo], NULL, NULL);
    TOp * ptrOperation = ptrOp[ConsistNo];
    if (ptrOperation) {	// reflect the change on to any open Operations window
    	TAbRockerSwitch * ptrSmokeSwitch = ptrOperation->SmokeSwitch;
        // The SmokeSwitch on the Op window uses OnStatusChanged.....
	    SmokeOnStatusChanged = ptrSmokeSwitch->OnStatusChanged;
    	ptrSmokeSwitch->OnStatusChanged = NULL;
        ptrSmokeSwitch->Checked = false;
		ptrSmokeSwitch->OnStatusChanged = SmokeOnStatusChanged;
    	}
	}
}
//---------------------------------------------------------------------------

void __fastcall TQuickCtrl::CPLRFButton1MouseDown(TObject *Sender,
      TMouseButton Button, TShiftState Shift, int X, int Y)
{
//CPLRF
const int ControlNo = PickControlNumber(Sender);
const int ConsistNo = UpDownContainer[ControlNo]->Position;
// Uncouple Front
TOp * ptrOperation = ptrOp[ConsistNo];
if (ptrOperation) {	// reflect the change on to any open Operations window
   	TAbColBitBtn * ptrCPLRFButton = ptrOperation->CPLRFButton;
    ptrCPLRFButton->GradBtnFace->Visible = true;
   	}
#ifdef BUTTONMETHOD
(void)ptrTIUCommThread->Send_String_FIFO(ConsistNo, COUPLER_FRONT, TIUNo[ConsistNo], RemoteNo, 11, SHORT_WAIT, RECORD_OK, EngineList[ConsistNo], NULL, NULL);
#else
// Send CM_CPLRF message to ourselves which will perform this function
(void) PostMessage(Handle, CM_CPLRF, ControlNo, ConsistNo);
#endif
}
//---------------------------------------------------------------------------

void __fastcall TQuickCtrl::CPLRFButton1MouseUp(TObject *Sender,
      TMouseButton Button, TShiftState Shift, int X, int Y)
{
// CPLRF
const int ControlNo = PickControlNumber(Sender);
const int ConsistNo = UpDownContainer[ControlNo]->Position;
// Coupler Front
TOp * ptrOperation = ptrOp[ConsistNo];
if (ptrOperation) {	// reflect the change on to any open Operations window
   	TAbColBitBtn * ptrCPLRFButton = ptrOperation->CPLRFButton;
    ptrCPLRFButton->GradBtnFace->Visible = false;
  	}
}
//---------------------------------------------------------------------------

void __fastcall TQuickCtrl::CMCPLRF(TMessage &Message)
{
// Uncouple Front
const int ConsistNo = Message.LParam;		// set button state from Long Parameter
(void)ptrTIUCommThread->Send_String_FIFO(ConsistNo, COUPLER_FRONT, TIUNo[ConsistNo], RemoteNo, 11, SHORT_WAIT, RECORD_OK, EngineList[ConsistNo], NULL, NULL);
}
//---------------------------------------------------------------------------

void __fastcall TQuickCtrl::CPLRRButton1MouseDown(TObject *Sender,
      TMouseButton Button, TShiftState Shift, int X, int Y)
{
//CPLRR
const int ControlNo = PickControlNumber(Sender);
const int ConsistNo = UpDownContainer[ControlNo]->Position;
// Uncouple Rear
TOp * ptrOperation = ptrOp[ConsistNo];
if (ptrOperation) {	// reflect the change on to any open Operations window
   	TAbColBitBtn * ptrCPLRRButton = ptrOperation->CPLRRButton;
    ptrCPLRRButton->GradBtnFace->Visible = true;
   	}
#ifdef BUTTONMETHOD
(void)ptrTIUCommThread->Send_String_FIFO(ConsistNo, COUPLER_REAR, TIUNo[ConsistNo], RemoteNo, 11, SHORT_WAIT, RECORD_OK, EngineList[ConsistNo], NULL, NULL);
#else
// Send CM_CPLRR message to ourselves which will perform this function
(void) PostMessage(Handle, CM_CPLRR, ControlNo, ConsistNo);
#endif
}
//---------------------------------------------------------------------------

void __fastcall TQuickCtrl::CPLRRButton1MouseUp(TObject *Sender,
      TMouseButton Button, TShiftState Shift, int X, int Y)
{
// CPLRR
const int ControlNo = PickControlNumber(Sender);
const int ConsistNo = UpDownContainer[ControlNo]->Position;
// Coupler Rear
TOp * ptrOperation = ptrOp[ConsistNo];
if (ptrOperation) {	// reflect the change on to any open Operations window
   	TAbColBitBtn * ptrCPLRRButton = ptrOperation->CPLRRButton;
    ptrCPLRRButton->GradBtnFace->Visible = false;
  	}
}
//---------------------------------------------------------------------------

void __fastcall TQuickCtrl::CMCPLRR(TMessage &Message)
{
// Uncouple Rear
const int ConsistNo = Message.LParam;		// set button state from Long Parameter
(void)ptrTIUCommThread->Send_String_FIFO(ConsistNo, COUPLER_REAR, TIUNo[ConsistNo], RemoteNo, 11, SHORT_WAIT, RECORD_OK, EngineList[ConsistNo], NULL, NULL);
}
//---------------------------------------------------------------------------

void __fastcall TQuickCtrl::NewControlWindowClick(TObject *Sender)
{
if (TotalCtrlCount == QUICKCOUNT) return;		// Max of QUICKCOUNT Quick Control Windows
iCtrlCount++;
const int ExtraOffset = 6 * SF;
Width = Width + (EngineBoxWidth + ExtraOffset);
NewControlWindow->Left = NewControlWindow->Left + (EngineBoxWidth + ExtraOffset);
PreviousPage->Left = PreviousPage->Left + (EngineBoxWidth + ExtraOffset);
NextPage->Left = NextPage->Left + (EngineBoxWidth + ExtraOffset);
DeleteControlWindow->Left = DeleteControlWindow->Left + (EngineBoxWidth + ExtraOffset);
ESTOP->Width = ESTOP->Width + (EngineBoxWidth + ExtraOffset);
HideButton->Left = HideButton->Left + (EngineBoxWidth + ExtraOffset)/2;
CloseButton->Left = CloseButton->Left + (EngineBoxWidth + ExtraOffset)/2;
ConstructNewQuickControlWindow(Sender);	// open the next control
// set Engine/Consist Name
SetEngineName(iCtrlCount - 1, EngineNameContainer[iCtrlCount - 1], NULL, UpDownContainer[iCtrlCount - 1]->Position);//
TotalCtrlCount = iCtrlCount;
DeleteControlWindow->Enabled = true;
if (TotalCtrlCount == QUICKCOUNT) NewControlWindow->Enabled = false;
}
//---------------------------------------------------------------------------

void __fastcall TQuickCtrl::DeleteControlWindowClick(TObject *Sender)
{
if (TotalCtrlCount == 1) return;	// always leave one Window
const int ExtraOffset = 6 * SF;
Width = Width - (EngineBoxWidth + ExtraOffset);
NewControlWindow->Left = NewControlWindow->Left - (EngineBoxWidth + ExtraOffset);
PreviousPage->Left = PreviousPage->Left - (EngineBoxWidth + ExtraOffset);
NextPage->Left = NextPage->Left - (EngineBoxWidth + ExtraOffset);
DeleteControlWindow->Left = DeleteControlWindow->Left - (EngineBoxWidth + ExtraOffset);
ESTOP->Width = ESTOP->Width - (EngineBoxWidth + ExtraOffset);
HideButton->Left = HideButton->Left - (EngineBoxWidth + ExtraOffset)/2;
CloseButton->Left = CloseButton->Left - (EngineBoxWidth + ExtraOffset)/2;
assert ((iCtrlCount - 1) > 0);
delete EngineBoxContainer[iCtrlCount - 1]->PopupMenu;		// delete popup menu
EngineBoxContainer[iCtrlCount - 1]->PopupMenu = NULL;
delete EngineBoxContainer[iCtrlCount - 1];
EngineBoxContainer[iCtrlCount - 1] = NULL;
iCtrlCount--;
TotalCtrlCount = iCtrlCount;
NewControlWindow->Enabled = true;
if (TotalCtrlCount == 1) DeleteControlWindow->Enabled = false;
}
//---------------------------------------------------------------------------

void __fastcall TQuickCtrl::ESTOPClick(TObject *Sender)
{
// emergency stop
#ifdef BUTTONMETHOD
MainForm->EmergencyStopAllEngines();	// Stop Engines and take down windows
#else
// Send CM_EMERGENCYSTOP message to MainForm
(void) PostMessage(MainForm->Handle, CM_EMERGENCYSTOP, 0, 0);
#endif
}
//---------------------------------------------------------------------------

void __fastcall TQuickCtrl::FormKeyDown(TObject *Sender, WORD &Key,
      TShiftState Shift)
{
// Make sure KeyPreview is true for this form
assert(KeyPreview);
// Pass function keys to the MainForm, handle rest of the keys here.
switch (Key) {
    case VK_F1:
    case VK_F2:
    case VK_F3:
    case VK_F4:
    case VK_F5:
    case VK_F6:
    case VK_F7:
    case VK_F8:
    case VK_F9:
    case VK_F10:
    case VK_F11:
    case VK_F12:
		MainForm->FormKeyDown(Sender, Key, Shift);
        Key = 0;	// handled
		break;
	}
}
//---------------------------------------------------------------------------

void __fastcall TQuickCtrl::VolumeSliderValueChange(TObject *Sender)
{
static float LastSentVolumeValue;
const int ControlNo = PickControlNumber(Sender);
const int ConsistNo = UpDownContainer[ControlNo]->Position;
const int Vol = VolumeSliderContainer[ControlNo]->Value;
if (DEBUGGING) DB->Debug_Print("VolumeSliderValueChange : " + String(Vol));
// Send speed command based on value of slider
// https://stackoverflow.com/questions/31649315/c-builder-button-click-function-reentry
LOCKEM();

#ifdef NOTNEEDED
// disconnect event handler while processing the VolumeSliderValueChange();
// The VolumeSlider control on the Quick Controls windows uses OnValueChange....
VolumeSliderOnValueChange = VolumeSliderContainer[ControlNo]->OnValueChange;
VolumeSliderContainer[ControlNo]->OnValueChange = NULL;
#endif

LastSentVolumeValue = VolumeSliderContainer[ControlNo]->Value;	// save the current setting volume

// 0 = Master Volume, 1 = Engine Volume, 2 = Accent Volume, 3 = Horn Volume, 4 = Bell Volume
char str[15];
snprintf(str, sizeof str, VOLUME_CHANGE, MASTER_VOLUME, Vol);
(void) ptrTIUCommThread->Send_String_FIFO(ConsistNo, str, TIUNo[ConsistNo], RemoteNo, 11, SHORT_WAIT, RECORD_OK, EngineList[ConsistNo], NULL, NULL);
//
if (LastSentVolumeValue > 0) {
	// clear mute check in case it is set
	EngineBoxContainer[ControlNo]->PopupMenu->Items->Items[2]->Checked = false;
    }

TVolume * ptrVol = ptrVolume[ConsistNo];
if (ptrVol) {	// reflect the change on to any open Sound Controls window
   	TAbVSlider* ptrMasterVolume = ptrVol->MasterVolume;
    // The MasterVolume control on the Sound Controls windows uses OnValueChange....
    VolumeOnValueChange = ptrMasterVolume->OnValueChange;
   	ptrMasterVolume->OnValueChanged = NULL;
    ptrMasterVolume->Value = LastSentVolumeValue;
	ptrMasterVolume->OnValueChange = VolumeOnValueChange;
   	}

#ifdef NOTNEEDED
// Restore Event Handler
VolumeSliderContainer[ControlNo]->OnValueChange = VolumeSliderOnValueChange;
#endif

#ifdef NOTNEEDED
// Check for slider movement while setting volume
int NewVol = VolumeSliderContainer[ControlNo]->Value;
if (LastSentVolumeValue != NewVol) {
	if (DEBUGGING) DB->Debug_Print("MasterVolumeValueChange Volume Changed on Exit : " + String(NewVol) +
    	" from " + String(LastSentVolumeValue));
    VolumeSliderValueChange(Sender);	// call me again to process the changed speed value
	}
#endif
}
//---------------------------------------------------------------------------

void __fastcall TQuickCtrl::UpDown1Changing(TObject *Sender,
      bool &AllowChange)
{
const int ControlNo = PickControlNumber(Sender);
const int ConsistNo = UpDownContainer[ControlNo]->Position;
//
// When the ConsistNo is about to change, remove any connection to Operations or Sound windows
//
TOp * ptrOperation = ptrOp[ConsistNo];
TVolume * ptrVol = ptrVolume[ConsistNo];

// disconnect any sound control window
if (ptrVol) {
	ptrVol->ptrQuick_MasterVolume = NULL;
	}
// disconnect any operations window
if (ptrOperation) {
	// engine is started so send any operations windows pointers to my controls
	ptrOperation->ptrQuick_Dir = NULL;
	ptrOperation->ptrQuick_Bell = NULL;
	ptrOperation->ptrQuick_Smoke = NULL;
	ptrOperation->ptrQuick_Speed = NULL;
    ptrOperation->ptrQuick_EngNumber = NULL;
    ptrOperation->ptrQuick_RunState = NULL;
    ptrOperation->ptrQuick_Sound = NULL;
    // expand to whistle and coupler buttons
    ptrOperation->ptrQuick_Whistle = NULL;
    ptrOperation->ptrQuick_Horn = NULL;
    ptrOperation->ptrQuick_SFS = NULL;
    ptrOperation->ptrQuick_SRS = NULL;
    ptrOperation->ptrQuick_SXS = NULL;
    ptrOperation->ptrQuick_CPLRF = NULL;
    ptrOperation->ptrQuick_CPLRR = NULL;
    //
	}

AllowChange = true;	// then allow the change to take place in EngNumber1Change()
}
//---------------------------------------------------------------------------

void __fastcall TQuickCtrl::EngNumber1Change(TObject *Sender)
{
// https://stackoverflow.com/questions/31649315/c-builder-button-click-function-reentry
LOCKEM()

if (!bSetupComplete) return;	// skip this if still in setup of the window because Container pointers are not setup yet
// Engine, All Engine Mode or LashUp Number
const int ControlNo = PickControlNumber(Sender);

// Set New ConsistNo and LashUpEngineList
int ConsistNo;
try {
	ConsistNo = Trim(EngNumberContainer[ControlNo]->Text).ToInt();
	}
catch(...) {	// if the ToInt() fails
	EngNumberContainer[ControlNo]->Text = "1";
    ConsistNo = 1;
	}
assert(ConsistNo >= 0 && ConsistNo <= LASHUPMAX);	// 0 factory reset, 1-99, 100 "all engine operation, 101-120 LashUps

if (ConsistNo <= ALLENGINE) {
	// not a lashup - Engines 0, 1-99, 100
	EngineList[ConsistNo] = NULL;	// For Engine (0, 1-99, 100) set to NULL
    EngineNameContainer[ControlNo]->Hint = "Use arrow buttons to select the Engine/Lashup 1-99, 100, 101-120";
	if (DEBUGGING) DB->Debug_Print("EngNumber1Change ControlNo: " + String(ControlNo) + " Consist No: " + String(ConsistNo));
    } else {
    // Lashup 101-120
    ConvertCSVString(ConsistNo, ControlNo);
    }

    
// set Engine/Consist Name
SetEngineName(ControlNo, EngineNameContainer[ControlNo], NULL, ConsistNo);

EngineBoxContainer[ControlNo]->Caption = String(UpDownContainer[ControlNo]->Position <= ALLENGINE ? "Engine " : "Lashup ") +
	String(UpDownContainer[ControlNo]->Position - (UpDownContainer[ControlNo]->Position <= ALLENGINE ? 0 : ALLENGINE)) +
    " TIU " + String(TIUNo[UpDownContainer[ControlNo]->Position]);	// Set Engine Number as Engine Box Caption

// set running state
EngNumberContainer[ControlNo]->Color = RunningState[ConsistNo];
//
if (!bSetupComplete) return;	// skip this if still in setup of the window because Container pointers are not setup yet
// Show either the Whistle or Horn Button depending on Engine Type from EngineData
const int EngType = EngTypeContainer[UpDownContainer[ControlNo]->Position];
if (EngType == STEAM_ENGINE) {
	WhistleButtonContainer[ControlNo]->Visible = true;
	HornButtonContainer[ControlNo]->Visible = false;
	} else {
	WhistleButtonContainer[ControlNo]->Visible = false;
	HornButtonContainer[ControlNo]->Visible = true;
	}

// Show either "Smoke" or "Exhaust" depending on Engine Type from EngineData
if (EngType == STEAM_ENGINE) {
	SmokeSwitchContainer[ControlNo]->Text = "Smoke";
	} else {
	SmokeSwitchContainer[ControlNo]->Text = "Exhaust";
	}
//
TOp * ptrOperation = ptrOp[ConsistNo];
TVolume * ptrVol = ptrVolume[ConsistNo];

// set control pointers for any Volume window that might be open
if (ptrVol) {
	ptrVol->ptrQuick_MasterVolume = VolumeSliderContainer[ControlNo];
	}
// set control pointers for any Operations window that might be open
if (ptrOperation) {
	// engine is started so send any operations windows pointers to my controls
	ptrOperation->ptrQuick_Dir = DIRToggleContainer[ControlNo];
	ptrOperation->ptrQuick_Bell = BellSwitchContainer[ControlNo];
	ptrOperation->ptrQuick_Smoke = SmokeSwitchContainer[ControlNo];
	ptrOperation->ptrQuick_Speed = SpeedEditContainer[ControlNo];
    ptrOperation->ptrQuick_EngNumber = EngNumberContainer[ControlNo];
    ptrOperation->ptrQuick_RunState = RunningState;		// pointer to an array indexed by Engine NUmber
    ptrOperation->ptrQuick_Sound = VolumeSliderContainer[ControlNo];
    // expand to whistle and coupler buttons
    ptrOperation->ptrQuick_Whistle = WhistleButtonContainer[ControlNo];
    ptrOperation->ptrQuick_Horn = HornButtonContainer[ControlNo];
    ptrOperation->ptrQuick_SFS = SFSButtonContainer[ControlNo];
    ptrOperation->ptrQuick_SRS = SRSButtonContainer[ControlNo];
    ptrOperation->ptrQuick_SXS = SXSButtonContainer[ControlNo];
    ptrOperation->ptrQuick_CPLRF = CPLRFButtonContainer[ControlNo];
    ptrOperation->ptrQuick_CPLRR = CPLRRButtonContainer[ControlNo];
    //
	}
}
//---------------------------------------------------------------------------

void __fastcall TQuickCtrl::ConvertCSVString(int LashUpNo, int ControlNo)
{
// Convert a CSV string like "1,-2,3,4" into a binary (Radio TIU) or ASCII (WTIU WiFi) engine string sent to the TIU
// Set EngineList  (LashUpEngineList) For Lashup (101-120) set to engine list
//
// These three functions have similar code and should be consolidated into one function:
//
// void __fastcall TQuickCtrl::ConvertCSVString(int LashUpNo, int ControlNo)
// static int luaH_ConvertEngineList(lua_State *L, int iTIUNo, int iLashUpNo)
// void __fastcall TLashUp::ParseLashUpEngine(String LashUpEncoded, TSyncListBox * EnginesinLashUpLB, TCheckListBox * RevCheckLB)
//
    unsigned char * LashUpCommand = new unsigned char[LASHUP_ENGINE_LIST_LENGTH];

    if (EngineList[LashUpNo] != NULL) {
		delete EngineList[LashUpNo];		// if there is a lashup engine list allocated, delete it
        EngineList[LashUpNo] = NULL;
        }

    String LashUpEncoded = LashupListContainer[LashUpNo];
    EngineNameContainer[ControlNo]->Hint = "Lashup Engines " + LashUpEncoded;	// set lashup engines as the hint
    if (!LashUpEncoded.IsEmpty()) {
	    TStringList * EL = new TStringList();
    	EL->CommaText = LashUpEncoded;
	    // Builds the binary command string sent to the TIU
		unsigned int EngIndex = 0;
        LashUpCommand[EngIndex++] = 0x2C;	// Comma
		//fill the engine list
        for (int i = 0; i < EL->Count ; i++) {
        	int val;
			try {
	            val = EL->Strings[i].ToInt();
                // DCS V6 and later and WIU
                int iEng = abs(val);
               	// Engine 1-99, DCS 2-100 (so we add 1 here because the LashUp string is in DCS Engine #)
                iEng++; // convert to DCS Engine Number
                if (val < 0) {
                    iEng |= 0x80;	// H.O. bit indicates reverse running
                    }
                // DCS V6 and later and WTIU
                // DCS V5 and earlier is no longer supported
                sprintf(&LashUpCommand[EngIndex], "%02X", iEng);    // convert one byte to 2 ASCII characters
                EngIndex += 2;
                // Radio Mode  (ASCII)
                //5555669965AA656969596966656665656666AAAAAA9995696A95A95A4D
                //  00  66  75  34  2C  39  31  30  33  FF  EE  94  6A  AD
                //         'u' '4' ',' '9' '1' '0' '3' 'ÿ'
                // or
                // u4,9103ÿ
                //
                // WiFi Mode (ASCII)
                // u4,9103ÿ
                LashUpCommand[EngIndex] = '\0';
                assert(EngIndex < (LASHUP_ENGINE_LIST_LENGTH - 2));		// Check for string limit

                }
            catch(...) {
                // ignore errors but don't crash
                delete[] LashUpCommand;
                EngineList[LashUpNo] = NULL;
                if (DEBUGGINGX(3)) DB->Debug_Print("ConvertCSVString Failed - ControlNo: " + String(ControlNo) + " Lashup No: " + String(LashUpNo - 100));
                return;
            	}
            }
        LashUpCommand[EngIndex++] = 0xFF;	// end of list
		LashUpCommand[EngIndex] = 0x00;		// Null terminator
        EngineList[LashUpNo] = LashUpCommand;						// new engine list is deleted in the destructor eventually
        } else {
        EngineList[LashUpNo] = NULL;
        }
	if (DEBUGGINGX(3)) DB->Debug_Print("ConvertCSVString ControlNo: " + String(ControlNo) + " Lashup No: " + String(LashUpNo - 100));
}
//---------------------------------------------------------------------------

void __fastcall TQuickCtrl::SetRunningState(int ConsistNo, TColor ColorState)
{
// search through all controls and set each control with consist number to color given
for (int ControlNo =  0; ControlNo <= (iCtrlCount - 1); ControlNo++) {
    if (ConsistNo == Trim(EngNumberContainer[ControlNo]->Text).ToInt()) {
		EngNumberContainer[ControlNo]->Color = ColorState;
    	}
	}
RunningState[ConsistNo] = ColorState;
}
//---------------------------------------------------------------------------

void __fastcall TQuickCtrl::GetEngData()
{
//
// Called internally only twice- from the constructor and from the Popup Menu on request
// Called externally from the [Read] button proccessing
// Called externally from LashUp Selection in two places
//
// Copy Engine Type & Engine Name for later use
EngNameContainer[0] = "Factory Reset";						// Factory Reset Engine
EngTypeContainer[0] = DIESEL_ENGINE;
// Engines
for (int ConsistNo = ENGINEMIN ; ConsistNo <= ENGINEMAX ; ConsistNo++) {	// Engines 1-99
	// Need to have EngineData.ini loaded (which it always is)
    EngTypeContainer[ConsistNo] = MainForm->EngineData->Cells [ENG_TYPE] [ConsistNo].ToInt();
    EngNameContainer[ConsistNo] = MainForm->EngineData->Cells [ENG_NAME] [ConsistNo];
	}

// All Engines
EngNameContainer[ALLENGINE] = "All Engines";
EngTypeContainer[ALLENGINE] = DIESEL_ENGINE;
//

// LashUps
// Copy Lashup Name and Lashup string from ptrLashupDat for later use
for (int LashUpNo = LASHUPMIN ; LashUpNo <= LASHUPMAX ; LashUpNo++) {	// Lashups 101-120
    // Need to have LashUp.ini loaded (which it always is since LashUp is auto create)
    int HeadEngine = 0;
    // in a String, 0 is the first string, 1 is the second string, and so on.
    String ES = LashUp->LashUpData->Cells [LASHUP_LIST] [LashUpNo - 100];
    if (ES == " ") continue; // no lashup at this LashUpNo
    int val;
    _stscanf(ES.c_str(),TEXT("%d"), &val);
    HeadEngine = abs(val);
    assert(HeadEngine < ALLENGINE);

    if (HeadEngine != 0) {
        EngTypeContainer[LashUpNo] = EngTypeContainer[HeadEngine];		// set type to type of head engine
        } else {
        EngTypeContainer[LashUpNo] = DIESEL_ENGINE;
        };

    EngNameContainer[LashUpNo] = MainForm->EngineData->Cells [ENG_NAME] [HeadEngine];
    LashupListContainer[LashUpNo] = LashUp->LashUpData->Cells [LASHUP_LIST] [LashUpNo - 100];
	}

//delete LocalEngDat;
}
//---------------------------------------------------------------------------

void __fastcall TQuickCtrl::GetLashUpData()
{
//
// Loop through the 10 controls and build all of the Engine Lists
// Not called from within Quick_Controls.cpp
//
// Designed to be called from the Lashup Selection window when a lashup is added, deleted or edited
// and
// to be called from the Main window when an engine is added or deleted
//
for (int ControlNo = 0 ; ControlNo < QUICKCOUNT; ControlNo++) {
	if (EngNumberContainer[ControlNo] == NULL) return;	// no control in this position or following positons
    int ConsistNo;
    try {
        ConsistNo = Trim(EngNumberContainer[ControlNo]->Text).ToInt();
        }
    catch(...) {	// if the ToInt() fails
        EngNumberContainer[ControlNo]->Text = "1";
        ConsistNo = 1;
        return;
        }
    assert(ConsistNo);
   	ConvertCSVString(ConsistNo, ControlNo);
	}
}
//---------------------------------------------------------------------------

// DEV-NOTE:  GUIForm flattens the VCL controls
// VCL controls are nested.  I.E. Controls on a
// Panel would have the Panel as a parent and if
// that Panel is on a TForm, TForm's control count
// does not account for the nested controls on the
// Panel.
//
// GUIControl is passed a Form pointer and an index
// value, the index value will walk the controls on the
// form and any child controls counting up to the idx
// value passed in.  In this way, every control has a
// unique index value
//
// You can use this to iterate over every single control
// on a form.  Here is example code:
//
// int count = 0;
// TForm *pTForm = some_form
// TControl *pCtrl = 0;
// do
// {
//      pCtrl = GUIControl(pTForm, count++);
//
// }while(pCtrl);
//
// http://stackoverflow.com/questions/414928/is-there-any-way-to-get-all-the-controls-on-a-container-control
//

TControl *GUIChildControl(TControl *C, int &runcount, int idx)
{
TControl *rval = 0;
TWinControl *pC = dynamic_cast<TWinControl *>(C);
if(pC) {
    for(int i=0; i<pC->ControlCount && !rval; i++) {
        TControl *pCtrl = pC->Controls[i];
        runcount++;
        if( runcount == idx)
            rval = pCtrl;
	        else  {
            TWinControl *pCC = dynamic_cast<TWinControl *>(pCtrl);
            if (pCC) {
                if( pCC->ControlCount )
                    rval = GUIChildControl(pCtrl, runcount, idx);
            	}
        	}
    	}
	}
return(rval);
}

TControl *GUIControl(TForm *F, int idx)
{
TControl *rval = 0;
int RunCount = 0;
for(int i=0; i<F->ControlCount && !rval; i++) {
    TControl *pCtl = F->Controls[i];
    if(RunCount == idx )
        rval = pCtl;
        else
        rval = GUIChildControl( pCtl, RunCount, idx);
        RunCount++;
    }
return(rval);
}
//---------------------------------------------------------------------------
// Workaround for SetLayeredWindowAttributes() to make transparent windows
extern lpfnSetLayeredWindowAttributes m_pSetLayeredWindowAttributes;
//---------------------------------------------------------------------------

void __fastcall TQuickCtrl::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);
// Ask the window and its children to repaint
//Repaint();
#endif	
}
//---------------------------------------------------------------------------

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

void __fastcall TQuickCtrl::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 TQuickCtrl::PreviousPageClick(TObject *Sender)
{
//
// send a message to MainForm to display the previous page of Hot Buttons
int NewPageNo = PageNo;
if (NewPageNo == 1) {
	NewPageNo = QUICKPAGEMAX;
    } else {
    NewPageNo -= 1;
    }
PostMessage(MainForm->Handle, CM_QUICK, NewPageNo, 0);
}
//---------------------------------------------------------------------------

void __fastcall TQuickCtrl::NextPageClick(TObject *Sender)
{
//
// send a message to MainForm to display the next page of Hot Buttons
int NewPageNo = PageNo;
if (NewPageNo == QUICKPAGEMAX) {
	NewPageNo = 1;
    } else {
    NewPageNo += 1;
    }
PostMessage(MainForm->Handle, CM_QUICK, NewPageNo, 0);
}
//---------------------------------------------------------------------------

void __fastcall TQuickCtrl::RescanEnginesLashups1Click(TObject *Sender)
{
// rescan for changes to engines and lashups
// Should be done whenever engines or lashups are added or deleted and
// the Quick Controls window has been created
GetEngData();
}
//---------------------------------------------------------------------------


void __fastcall TQuickCtrl::CloseButtonClick(TObject *Sender)
{
Close();    
}
//---------------------------------------------------------------------------

