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

© Copyright 2015 by Mark DiVecchio

    This file is part of Remote Train Control.

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

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

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

*/
// Home Page : http://www.silogic.com/trains/RTC_Running.html
//---------------------------------------------------------------------------
#include <vcl.h>
#include <tchar.h>
#pragma hdrstop

#include "Train_Control.h"
#include "LashUp_Selection.h"
#include "Quick_Controls.h"
#include "Setup_Control.h"
#include "Debug_Controls.h"
#include "Comm_Thread.h"
#include "Clipbrd.hpp"
#include "LashUpListPopOut.h"

#include <stdio.h>
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma link "SyncListBox"
#pragma link "_GClass"
#pragma link "AbCBitBt"
#pragma resource "*.dfm"

TLashUp *LashUp;	// NOTE: this is public static variable, you can never use
                    // this when you create multiple instances of a Form
extern TCommThread * ptrTIUCommThread;			// Pointer to TIU communications thread
extern TQuickCtrl * ptrQuick[QUICKPAGEMAX];		// Quick Control Pointers
extern bool SuperTIUMode, TIUSuperMode[TIUMAX];         // Flags to indicate if Super TIU Mode is active
extern bool TIUSelection[TIUMAX];                  // Flags to indicate that a TIU is present

// TODO : - Middle Engine Check Boxes don't work correctly when there
// are more engines than fit in the list box
//
//---------------------------------------------------------------------------
__fastcall TLashUp::TLashUp(TComponent* Owner)
	: TForm(Owner)
{
// grab the Remote, Engine
RemoteNo = MainForm->RemoteNo;
assert(RemoteNo >= 0 && RemoteNo <= REMOTEMAX);
// Use HeadEngine to get its TIUNo
// setup LashUp default
LashUpNo = 100 + LUpSpin->ValueAsInt; 	// In radio mode, Lashup sent to TIU is always 101 (DCS #102 or 0x66)
					// always start with LashUp 1, normal values 101 to 120
                    // all are DCS number 102 (0x66)
                    // In WiFi mode, commands are prefixed by '|'
if (SuperTIUMode) {
     LashUp_Controls_Label->Caption = LashUp_Controls_Label->Caption + " SuperTIUMode ";
     }
this->Caption = LashUp_Controls_Label->Caption;

LocalTransparencyOverride = false;

//
LoadLashUpData(LashUpData);	// Load LashUp data for the first time
ReloadButtonClick(NULL);	// fill the engine list for the first time
 //
 // Sync the check box lists with the Selected Engine Lists
 // The synchronization is only down when the scroll bar is dragged,
 // not if the scroll is initiated by the arrow keys.
 //
HeadEngineSelected->SetSyncListBox((TListBox *)HERevCheckListBox);
MiddleEngineSelected->SetSyncListBox((TListBox *)MERevCheckListBox);
TailEngineSelected->SetSyncListBox((TListBox *)TERevCheckListBox);
EnginesinLashUpListBox->SetSyncListBox((TListBox *)RevCheckListBox);

//LashUpData->DefaultColWidth = 30;	// default
LashUpData->ColWidths[LASHUP_NUM] = 35;     // MCD LashUpNum + 100
LashUpData->ColWidths[LASHUP_NAME] = 175;	// lots of space for the LashUp name and TIU Number
LashUpData->ColWidths[LASHUP_LIST] = LashUpData->Width - LashUpData->ColWidths[LASHUP_NAME] - LashUpData->DefaultColWidth;	// lots of space for the Consist

LashUpPageControl->ActivePage = SelectLashUpSheet;
LashUpData->Row = (LashUpNo - 100);
LashUpUpDown->Position = LASHUPUPDOWNLIMIT - (LashUpNo - 100);
// set the LashUp Name into the Edit Control
NameofLashUp->Text = LashUpData->Cells[LASHUP_NAME][LashUpNo - 100];

LashUpVolButton->Enabled = false;
LashUpOpButton->Enabled = false;

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

if (MainForm->IsTIUConnected()) {
    LashUpStartUpButton->Enabled = true;
    StartupAllButton->Enabled = true;
    ShutdownAllButton->Enabled = true;
    LashUpShutdownButton->Enabled = true;
	} else {
    LashUpStartUpButton->Enabled = false;
    StartupAllButton->Enabled = false;
    ShutdownAllButton->Enabled = false;
    LashUpShutdownButton->Enabled = false;
    }

if (LashUpData->Cells[LASHUP_NAME][LashUpData->Row] == "*") {
	// PopUp Menu
	CreateNewLashUp->Enabled = true;
    EditLashUp->Enabled = false;
    BreakUpLashUp->Enabled = false;
    //Buttons
    EditLU->Enabled = false;
    BreakUpLU->Enabled = false;
    CreateLU->Enabled = true;
	} else {
    // PopUp Menu
	CreateNewLashUp->Enabled = false;
    EditLashUp->Enabled = true;
    BreakUpLashUp->Enabled = true;
    // Buttons
    EditLU->Enabled = true;
    BreakUpLU->Enabled = true;
    CreateLU->Enabled = false;
	CreateNewLashUp->Enabled = false;
    EditLashUp->Enabled = true;
    BreakUpLashUp->Enabled = true;
    }
ParseLashUpEngine(LashUpData->Cells[LASHUP_LIST][LashUpData->Row], EnginesinLashUpListBox, RevCheckListBox);
// make the edit tab invisible
CreateLashUpSheet->TabVisible = false;
// sometimes we lose this setting
LashUpData->ScrollBars = ssVertical;

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

__fastcall TLashUp::~TLashUp(void)
{
SaveLashUpData(LashUpData);
}
//---------------------------------------------------------------------------

void __fastcall TLashUp::LoadLashUpData(TStringGrid * LD)
{
// Fill the LashUp Data Grid from a disk file
// read LashUp Number from Registry if it exists
// sets bLashUpDataLoaded to true

// Loaded once at the beginning of the run
// and saved once at the end of the run
assert(LD != NULL);
if(bLashUpDataLoaded) return;	// if already loaded then do nothing
//
const String LashUpDataFile = String(MainForm->cStartDir) + "\\" + "LashUp.ini";
//
// Fill the Lashup Data Grid from a disk file
//  The Index parameter is the number of the row, where the value of the first row in the grid is zero.
//  The Index parameter is the number of the column, where the value of the first column in the grid is zero.

bLashUpDataCorrupt = false;
bLashUpDataLoaded = false;

__try {
    TStringList * ptrLashupDat = new TStringList();	// deleted in Mainform->CloseAllWindows()
    assert(ptrLashupDat != NULL);		// make sure that construction worked
    ptrLashupDat->Sorted = false;
    ptrLashupDat->Sorted = false;
    #if __BORLANDC__ >= 0x720
    ptrLashupDat->Duplicates = System::Classes::dupIgnore;	// needed because of an ambiguity in Emabarcadero CB10.1
    #else
    ptrLashupDat->Duplicates = dupIgnore;
    #endif
	try {
	   	if (FileExists(LashUpDataFile)) {
  			// read it
			ptrLashupDat->LoadFromFile(LashUpDataFile);
			// transfer data into TStringGrid from the TStringList
            LD->Rows[LASHUP_LABEL_ROW]->CommaText = ptrLashupDat->Strings[LASHUP_LABEL_ROW];
            assert(LD->Rows[LASHUP_LABEL_ROW]->Count == LASHUPDAT_COUNT);
   	    	// transfer data into TStringGrid from the TStringList
   	    	for (int LashUpNum = LASHUP_TOPROW ; LashUpNum < LD->RowCount ; LashUpNum++) {
	    		LD->Rows[LashUpNum]->CommaText = ptrLashupDat->Strings[LashUpNum];
                assert(LD->Rows[LashUpNum]->Count == LASHUPDAT_COUNT);
                // Validate Data
                const int field = ValidateLashUpData(LD, LashUpNum);
				if (field != -1) throw((LashUpNum * 10) + field);
				}
			if (DEBUGGING) DB->Debug_Print("LashUp Data Loaded from " + LashUpDataFile);
   	    	} else {
           	// LashUp Data File does not exist so create an empty TStringGrid
   			// Titles and LashUp Numbers- needed when the grid is first created
   			LD->Rows[LASHUP_LABEL_ROW]->CommaText = LASHUPDAT_TITLE;
			// ptrLashupDat is used in Quick_Controls
			ptrLashupDat->Add(LD->Rows[LASHUP_LABEL_ROW]->CommaText);
            assert(LD->Rows[LASHUP_LABEL_ROW]->Count == LASHUPDAT_COUNT);
   			for (int LashUpNum = LASHUP_TOPROW; LashUpNum < LD->RowCount ; LashUpNum++) {
      			// Insert LashUp Number and empty list of Engines
       			//LD->Rows[LashUpNum]->CommaText = String(LashUpNum + ALLENGINE) + LASHUPDAT_INIT;  // MCD LashUpNum + 100
       			LD->Rows[LashUpNum]->CommaText = String(LashUpNum) + LASHUPDAT_INIT;
				// ptrLashupDat is used in Quick_Controls
				ptrLashupDat->Add(LD->Rows[LashUpNum]->CommaText);
                assert(LD->Rows[LashUpNum]->Count == LASHUPDAT_COUNT);
				}
			if (DEBUGGING) DB->Debug_Print("LashUp Data Created (Empty)");
           	}
        if (ptrLashupDat) {
           delete ptrLashupDat;
           }
       	}
    catch (int field) {
    	ShowMessage("Saved LashUp Data File Field (" + String(field) + ") is Corrupt : " + LashUpDataFile);
       	bLashUpDataCorrupt = true;	// loaded but corrupt
       	MainForm->CaughtErrors->Value ++;
       	}

    catch ( ... ){
       	ShowMessage("Saved LashUp Data File is Corrupt : " + LashUpDataFile);
       	bLashUpDataCorrupt = true;	// loaded but corrupt
       	MainForm->CaughtErrors->Value ++;
       	}
    }
__finally {
   	bLashUpDataLoaded = true;	// loaded
    }
    
// read Preferences from registry if they exist
RegFileRootKey =  MainForm->RegFileRootKey;
TRegIniFile * const RegFile = new TRegIniFile(RegFileRootKey);
assert(RegFile != NULL);
__try {
	// LashUpNo loaded
	LashUpNo = RegFile->ReadInteger("Options", "LashUp", LashUpNo);
    }
__finally {
	// Deallocate RegFile
	delete RegFile;
    }
assert(LashUpNo >= LASHUPMIN && LashUpNo <=LASHUPMAX);	// LashUp Numbers only

// Make sure the selected LashUp is visible in the middle row if possible
if ((LashUpNo - 100) < 3)
	LD->TopRow = 1;
    else
	LD->TopRow = (LashUpNo - 100) - 2;
}
//---------------------------------------------------------------------------

void __fastcall TLashUp::SaveLashUpData(TStringGrid * LD)
{
// save all LashUp specific data
assert(LD != NULL);

assert(bLashUpDataLoaded);
// if the LashUp Data is not loaded then do nothing.
if (!bLashUpDataLoaded) return;

const String LashUpDataFile = String(MainForm->cStartDir) + "\\" + "LashUp.ini";
if (MainForm->bSaveRegistryInfo) {
    // write preferences to the Registry
    TRegIniFile * const RegFile = new TRegIniFile(RegFileRootKey);
    assert(RegFile != NULL);
    __try {
        // Current LashUpNo saved
        RegFile->WriteInteger("Options", "LashUp", LashUpNo);
        }
    __finally {
        // Deallocate RegFile
        delete RegFile;
        }
    }
//
if (bLashUpDataLoaded && !bLashUpDataCorrupt) { // don't save if it's not loaded or if we could not read it
	__try {
        TStringList * ptrLashupDat = new TStringList();	// deleted in Mainform->CloseAllWindows()
        assert(ptrLashupDat != NULL);		// make sure that construction worked
        ptrLashupDat->Sorted = false;
        #if __BORLANDC__ >= 0x720
        ptrLashupDat->Duplicates = System::Classes::dupIgnore;	// needed because of an ambiguity in Emabarcadero CB10.1
        #else
        ptrLashupDat->Duplicates = dupIgnore;
        #endif
    	try {
			// Save Lashup Data to disk with overwrite
            assert(LD->Rows[LASHUP_LABEL_ROW]->Count == LASHUPDAT_COUNT);
        	// transfer data from TStringGrid to TStringList
            ptrLashupDat->Add(LD->Rows[LASHUP_LABEL_ROW]->CommaText);
	   		for (int LashUpNum = LASHUP_TOPROW ; LashUpNum < LD->RowCount ; LashUpNum++) {
            	// Validate Data
                const int field = ValidateLashUpData(LD, LashUpNum);
				if (field != -1) throw((LashUpNum * 10) + field);
                assert(LD->Rows[LashUpNum]->Count == LASHUPDAT_COUNT);
				// LashUp data is valid so move it into the LashUpDat Object
				// transfer data from TSringGrid to TStringList
   				ptrLashupDat->Add(LD->Rows[LashUpNum]->CommaText);
   				}
			// write it
   			ptrLashupDat->SaveToFile(LashUpDataFile);
   			if (DEBUGGING) DB->Debug_Print("LashUp Data Saved to " + LashUpDataFile);
            if (ptrLashupDat != NULL) {
               delete ptrLashupDat;
               }
            }
        catch (int field) {
           	ShowMessage("Edited LashUp Data Field (" + String(field) + ") is Corrupt - Not Saved : " + LashUpDataFile);
           	bLashUpDataCorrupt = true;
           	MainForm->CaughtErrors->Value ++;
           	}
        catch (...) {
           	ShowMessage("Edited LashUp Data is Corrupt - Not Saved : " + LashUpDataFile);
       		bLashUpDataCorrupt = true;
       		MainForm->CaughtErrors->Value ++;
           	}
    	}
	__finally {
		if (bLashUpDataCorrupt) {
			bLashUpDataLoaded = false;  // indicate data should be reloaded
			} else {
			bLashUpDataLoaded = true; 	// the LashUp data remains in place
			}
		}
	}
}
//---------------------------------------------------------------------------

int __fastcall TLashUp::ValidateLashUpData(TStringGrid * LD, int LashUpNum)
{
// This function must be called within a try{} block because the ToInt()
// function will throw an error if it cannot parse an integer
//
int val;
// Index
val = LD->Cells[LASHUP_NUM][LashUpNum].ToIntDef(-1);
if (val < LASHUPMIN) {
    val += 100;
    LD->Cells[LASHUP_NUM][LashUpNum] = String(val);
    }
if (val < 100) return(LASHUP_NUM);
//if (val < ALLENGINE) val += ALLENGINE;                    // MCD LashUpNum + 100
//if ((val - ALLENGINE) != LashUpNum) return(LASHUP_NUM);   // MCD LashUpNum + 100
if (val != (LashUpNum + 100)) return(LASHUP_NUM);
// LashUp Name
val = LD->Cells[LASHUP_NAME][LashUpNum].Length();
if (val == 0) return(LASHUP_NAME);
if ((val == 1) && (LD->Cells[LASHUP_NAME][LashUpNum] != "*")) return(LASHUP_NAME);
// LashUp List
val = LD->Cells[LASHUP_LIST][LashUpNum].Length();
if ((val == 0) || (val > 30)) return(LASHUP_LIST);
if ((val == 1) && (LD->Cells[LASHUP_LIST][LashUpNum] != " ")) return(LASHUP_LIST);
//
return -1;  	// valid
}
//---------------------------------------------------------------------------

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

void __fastcall TLashUp::CopySelectedList(TListBox *SrcLB, TSyncListBox *DestLB,
	TCheckListBox * DestCLB,  bool ClearDest)
{
if (SrcLB->SelCount == 0) {
	//nothing selected
    return;
    }
if (ClearDest) {
 	DestLB->Clear();
    DestCLB->Clear();
    }
// copy selected items from source listbox
for (int Index = 0; Index < SrcLB->Items->Count; ++Index) {
   if (SrcLB->Selected[Index]) {
   	  String NewItem = SrcLB->Items->Strings[Index];
      // find out whether the new string already exists in the Selected list
      // If if does, we will not add it
      if(DestLB->Items->IndexOf(NewItem) == -1) {
    	    // add the new item to the end of the list
      		DestLB->Items->Add(NewItem);
            DestCLB->Items->Add("");	// Reverse Running CheckBox
			int iEng;
			const int retv = _stscanf(NewItem.c_str(), _TEXT("%d"), &iEng); // each engine number followed by an engine name
            assert (retv == 1);
			MainForm->SetConsistState(iEng, INACTIVE);
            }
      SrcLB->Selected[Index] = false;	// deselect Engine
      ReloadButtonClick(NULL);	// rescan the active list
      } // end if
   } // end for
} // end CopySelectedList
//---------------------------------------------------------------------------

void __fastcall TLashUp::HeadEngineButtonClick(TObject *Sender)
{
// STEP 1 : Select Head Engine
if (EngineListBox->Items->Count == 0) return;	// no engines at all
if (EngineListBox->SelCount != 1) {
	ShowMessage("Select exactly one Engine as the Head Engine");
    return;
    }
//
CopySelectedList(EngineListBox, HeadEngineSelected, HERevCheckListBox, true);	// clear destination
LashUpEncodedEdit->Text = "";   // Need to press the Build LashUp Button again
}
//---------------------------------------------------------------------------

void __fastcall TLashUp::ClearHeadEngineListButtonClick(TObject *Sender)
{
if (HeadEngineSelected->Items->Count == 0) {	// list empty
	return;
    }
int iEng;
_stscanf(HeadEngineSelected->Items->Strings[0].c_str(), TEXT("%d"), &iEng); // each engine number followed by an engine name
MainForm->SetConsistState(iEng, ACTIVE);
ReloadButtonClick(NULL);	// rescan the active list
HeadEngineSelected->Clear();
HERevCheckListBox->Clear();
LashUpEncodedEdit->Text = "";   // Need to press the Build LashUp Button again
}
//---------------------------------------------------------------------------

void __fastcall TLashUp::MiddleEngineButtonClick(TObject *Sender)
{
// STEP 3 : Select Middle Engines
if (EngineListBox->Items->Count == 0) return;	// no engines at all
// Maxumum of 8 middle engines
if ((EngineListBox->SelCount + MiddleEngineSelected->Items->Count) > MAXMIDDLE) {
	ShowMessage("Maximum of 8 engines can be selected as Middle Engines");
    return;
    }
//
CopySelectedList(EngineListBox, MiddleEngineSelected, MERevCheckListBox, false);	// dont' clear destination
LashUpEncodedEdit->Text = "";   // Need to press the Build LashUp Button again
}
//---------------------------------------------------------------------------

void __fastcall TLashUp::RemoveMiddleEnginefromListButtonClick(
      TObject *Sender)
{
// set engines Active
if (MiddleEngineSelected->SelCount != 0 && MiddleEngineSelected->Items->Count != 0) {
    // go from last engine to item 0
	for (int Index = (MiddleEngineSelected->Items->Count-1) ; Index >= 0 ; Index--) {
	    if (MiddleEngineSelected->Selected[Index]) {
        	// Activate each Middle Engine Removed
       	    int iEng;
		    _stscanf(MiddleEngineSelected->Items->Strings[Index].c_str(), TEXT("%d"), &iEng); // each engine number followed by an engine name
		    MainForm->SetConsistState(iEng, ACTIVE);
        	MiddleEngineSelected->Items->Delete(Index);
            MERevCheckListBox->Items->Delete(Index);
            }
        }

    ReloadButtonClick(NULL);	// rescan the active list
	} else {
    return;	// list empty
    }
LashUpEncodedEdit->Text = "";   // Need to press the Build LashUp Button again
}
//---------------------------------------------------------------------------

void __fastcall TLashUp::TailEngineButtonClick(TObject *Sender)
{
// STEP 2: Select Tail Engine
if (EngineListBox->Items->Count == 0) return;	// no engines at all
if (EngineListBox->SelCount != 1) {
	ShowMessage("Select exactly one Engine as the Tail Engine");
    return;
    }
//
CopySelectedList(EngineListBox, TailEngineSelected, TERevCheckListBox, true);	// clear destination
LashUpEncodedEdit->Text = "";   // Need to press the Build LashUp Button again
}
//---------------------------------------------------------------------------

void __fastcall TLashUp::ClearTailEngineListButtonClick(
      TObject *Sender)
{
if (TailEngineSelected->Items->Count == 0) {	// list empty
	return;
    }
int iEng;
_stscanf(TailEngineSelected->Items->Strings[0].c_str(), TEXT("%d"), &iEng); // each engine number followed by an engine name
MainForm->SetConsistState(iEng, ACTIVE);
ReloadButtonClick(NULL);	// rescan the active list
TailEngineSelected->Clear();
TERevCheckListBox->Clear();
LashUpEncodedEdit->Text = "";   // Need to press the Build LashUp Button again
}
//---------------------------------------------------------------------------

void __fastcall TLashUp::BuildConsistButtonClick(TObject *Sender)
{
// STEP 4 : Build LashUp
String MiddleEngines;
if (HeadEngineSelected->Items->Count != 1) {
	ShowMessage("    No Head Engine Selected    ");
    LashUpEncodedEdit->Text = "";
    return;
    }
if (TailEngineSelected->Items->Count != 1) {
	ShowMessage("    No Tail Engine Selected    ");
    LashUpEncodedEdit->Text = "";
    return;
    }
// The ItemIndex of the first item in the list box is 0.
if (HeadEngineSelected->Items->Strings[0] == TailEngineSelected->Items->Strings[0]) {
    ShowMessage ("  Each Engine may only be selected once  ");
    LashUpEncodedEdit->Text = "";
    return;
	}
// The ItemIndex of the first item in the list box is 0.
if (MiddleEngineSelected->Items->Count == 0) {
	MainForm->DebugMessagePopup("Warning", "No Middle Engines Selected, Hope that is what you wanted...");
    MiddleEngines = "";
    } else {
    MiddleEngines = MiddleEngineSelected->Items->CommaText + ",";
    }
// The ItemIndex of the first item in the list box is 0.
if (MiddleEngines.Pos(HeadEngineSelected->Items->Strings[0]) > 0 || MiddleEngines.Pos(TailEngineSelected->Items->Strings[0]) > 0) {
    ShowMessage ("  Each Engine may only be selected once  ");
    LashUpEncodedEdit->Text = "";
    return;
	}
// create LashUpEncodedEdit in the form of "1,-2,3,4,5"
// Minimum of 2 engines up to a maximum of MAXLASHUP engines
int LashUpCount = 0;
LashUpEncodedEdit->Text = "";
// Head Engine
int iEng;
_stscanf(HeadEngineSelected->Items->Strings[0].c_str(), TEXT("%d"), &iEng);
//
if (HERevCheckListBox->Checked[0]) iEng *= -1;
LashUpEncodedEdit->Text = LashUpEncodedEdit->Text + String(iEng);
LashUpCount++;
// Middle Engines
if (MiddleEngineSelected->Items->Count > 0 ) {
    for (int i = 0 ; i < MiddleEngineSelected->Items->Count ; i++) {
    	// for each middle engine
        _stscanf(MiddleEngineSelected->Items->Strings[i].c_str(), TEXT("%d"), &iEng);
        //
     	if (MERevCheckListBox->Checked[i]) iEng *= -1;
    	LashUpEncodedEdit->Text = LashUpEncodedEdit->Text + "," + String(iEng);
        LashUpCount++;
    	}
	}
// Tail Engine
_stscanf(TailEngineSelected->Items->Strings[0].c_str(), TEXT("%d"), &iEng);
//
if (TERevCheckListBox->Checked[0]) iEng *= -1;
LashUpEncodedEdit->Text = LashUpEncodedEdit->Text + "," + String(iEng);
LashUpCount++;
if (LashUpCount < 2 || LashUpCount > MAXLASHUP) {
    ShowMessage ("  LashUp list must contain between 2 and " + String(MAXLASHUP) + " engines");
    LashUpEncodedEdit->Text = "";
    return;
	}
}
//---------------------------------------------------------------------------

void __fastcall TLashUp::SaveLashUpButtonClick(TObject *Sender)
{
// STEP 7 : Save LashUp
if  (LashUpEncodedEdit->Text.IsEmpty()) {
	ShowMessage("Please press the Build LashUp button (Step 5)");
    return;	// continue editing
    }
if (LashUpName->Text.IsEmpty()) {
	ShowMessage("    Please Name the LashUp (Step 6)    ");
    return;	// continue editing
    }
// must be more than 1 character
if (LashUpName->Text.Length() <= 1) {
	ShowMessage("New LashUp Name must be more than one character");
    return;	// continue editing
    }
// Add the lashup to the StringGrid table and the Reverse Running List
String Name16 = LashUpName->Text;
Name16.SetLength(16);	// Name must be 16 characters or less (SetLength() truncates to 16 or less, never makes it longer)
LashUpName->Text = Name16;
NameofLashUp->Text = Name16;	// set the lashup name into the Edit Control
LashUpData->Cells[LASHUP_NAME][LashUpNo - 100] = LashUpName->Text;
LashUpData->Cells[LASHUP_LIST][LashUpNo - 100] = LashUpEncodedEdit->Text;

ParseLashUpEngine(LashUpEncodedEdit->Text, EnginesinLashUpListBox, RevCheckListBox);

// Save the new Lashup List
SaveLashUpData(LashUpData);
// Refresh the PopOut List
PopOutLashUpList->RefreshClick(NULL);

// if Quick Ctrls is running, update its Engine List
for (int iPage = 1; iPage <= QUICKPAGEMAX ; iPage++) { // Quick Controls 1-5
    if (ptrQuick[iPage - 1]) {
        TQuickCtrl * pQuick = ptrQuick[iPage - 1];
        pQuick->GetEngData();		// call 1st
        pQuick->GetLashUpData();	// call 2nd
        }
    }

// Clear the Form
HeadEngineSelected->Clear();
HERevCheckListBox->Clear();
MiddleEngineSelected->Clear();
MERevCheckListBox->Clear();
TailEngineSelected->Clear();
TERevCheckListBox->Clear();
LashUpEncodedEdit->Text = "";
LashUpName->Text = "";
//Return to Select LashUp Tab
CreateLashUpSheet->TabVisible = false;
LashUpPageControl->ActivePage = SelectLashUpSheet;
LashUpData->SetFocus();
//
}
//---------------------------------------------------------------------------

void __fastcall TLashUp::ParseLashUpEngine(String LashUpEncoded, TSyncListBox * EnginesinLashUpLB, TCheckListBox * RevCheckLB)
{
// Convert a CSV string like "1,-2,3,4" into a SyncListBox (engine name) and CheckListBox (Reverse Running)
// and builds the binary (Radio TIU) or ASCII (WTIU WiFi) engine string sent to the TIU
//
// TODO : 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)
//
// Then clear the Destination list boxes
EnginesinLashUpLB->Clear();
RevCheckLB->Clear();
// Don't fill the lists for an empty LashUp list
if (LashUpEncoded.IsEmpty()) return;	// empty
if (LashUpEncoded == " ") return;	// empty
//
// Fill the Reverse Running Check Box List
RevCheckLB->Items->CommaText = LashUpEncoded;
assert(RevCheckLB->Items->Count >= 2);		// must be at least 2 engines in the list

// Build the command string sent to the TIU
// For DCS V5 and lower, the string is in binary  eg for 12,18 the string is ",\r\x013ÿ"
// DCS V5 and lower is no longer supported
//
// For DCS V6 and higher and the WTIU WiFi, the string is in ASCII
//                                                eg for 12,18 the string is ",0D13ÿ"
unsigned int EngIndex = 0;
LashUpCommand[EngIndex++] = 0x2C;
LashUpCommand[EngIndex] = '\0';
//fill the engine list
for (int i = 0; i < RevCheckLB->Items->Count ; i++) {
    try {
        const int val = RevCheckLB->Items->Strings[i].ToInt();  // this may throw an EConvertError exception
        int iEng = abs(val);
        String NewItem = MainForm->EngineData->Cols[ENG_NAME]->Strings[iEng];
        EnginesinLashUpLB->Items->Append(String(iEng) + " " + NewItem);
        // Engine 1-99, DCS 2-100 (we add 1 because the LashUp string is in DCS Engine #)
        iEng++; // convert to DCS Engine Number
        if (val < 0) {
            RevCheckLB->Items->Strings[i] = NewItem;
            RevCheckLB->Checked[i] = true;
            iEng |= 0x80;                   // if Reverse Running, turn on the H.O. bit
            }
        // 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 error but don't crash
        LashUpCommand[0] = 0x00;	// Null terminator
        return;
        }
    }
LashUpCommand[EngIndex++] = 0xFF;   // Engine list always ends with 0xFF "ÿ"
LashUpCommand[EngIndex] = 0x00;     // Null terminator
return;
}
//---------------------------------------------------------------------------

void __fastcall TLashUp::LashUpDataClick(TObject *Sender)
{
if (LashUpData->Row == -1 || LashUpData->Col == -1) {
	LashUpData->Row = LASHUP_TOPROW;  	// ignore out of bounds (if possible)
    LashUpUpDown->Position = LASHUPUPDOWNLIMIT - LASHUP_TOPROW;
    }
// Set the LashUp Number into the Control
LUpSpin->ValueAsInt = LashUpData->Row;

if (PopOutLashUpList && (PopOutLashUpList->LashUpData->Row != LashUpData->Row)) {   // reflect the change onto the popout lashup list
    PopOutLashUpList->LashUpData->Row = LashUpData->Row;
    }
// Set the LashUp Number into the working variable
LashUpNo = LashUpData->Row + 100;
// set the LashUp Name into the Edit Control
NameofLashUp->Text = LashUpData->Cells[LASHUP_NAME][LashUpNo - 100];

LashUpStartUpButton->Enabled = false;
LashUpShutdownButton->Enabled = false;
LashUpOpButton->Enabled = false;
LashUpVolButton->Enabled = false;

if (LashUpData->Cells[LASHUP_NAME][LashUpData->Row] == "*") {
	// PopUp Menu
	CreateNewLashUp->Enabled = true;
    EditLashUp->Enabled = false;
    BreakUpLashUp->Enabled = false;
    //Buttons
    EditLU->Enabled = false;
    BreakUpLU->Enabled = false;
    CreateLU->Enabled = true;
	} else {
    // PopUp Menu
    CreateNewLashUp->Enabled = false;
    EditLashUp->Enabled = true;
    BreakUpLashUp->Enabled = true;
    // Buttons
    EditLU->Enabled = true;
    BreakUpLU->Enabled = true;
    CreateLU->Enabled = false;
    // if the port is connected, enabled the buttons
	if (MainForm->IsTIUConnected()) {
    	LashUpStartUpButton->Enabled = true;
    	LashUpShutdownButton->Enabled = true;
    	LashUpOpButton->Enabled = true;
    	LashUpVolButton->Enabled = true;
        }
    }
// Enable buttons if an operations form is already created
if (MainForm->IsConsistRunning(LashUpNo)) {
	// enable buttons
	LashUpVolButton->Enabled = true;
	LashUpOpButton->Enabled = true;
    //LashUpShutdownButton->Enabled = true;
	} else {
    // disable buttons
	LashUpVolButton->Enabled = false;
	LashUpOpButton->Enabled = false;
    //LashUpShutdownButton->Enabled = false;
	}
// Fill the LashUp Engine List (it might be empty)
ParseLashUpEngine(LashUpData->Cells[LASHUP_LIST][LashUpData->Row], EnginesinLashUpListBox, RevCheckListBox);
}
//---------------------------------------------------------------------------

void __fastcall TLashUp::LashUpDataMouseDown(TObject *Sender,
      TMouseButton Button, TShiftState Shift, int X, int Y)
{
int ARow, ACol;
if (Button == mbRight) {
	LashUpData->MouseToCell(X, Y, ACol, ARow);
    LashUpData->Row = ARow;
    LashUpData->Col = ACol;
    }

LashUpDataClick(Sender);

// Popup the PopupMenu
if (Button == mbRight) {
	LashUpData->PopupMenu->Popup(Left + X, Top + Y);
    }
// Pop Out the LashUp List
if (Button == mbMiddle) {
    // Refresh the PopOut List
    PopOutLashUpList->RefreshClick(NULL);
    //// cannot assign a TStringGrid to a TStringGrid : PopOutLashUpList->LashUpData->Assign(LashUpData);
    //for (int I = 0; I < PopOutLashUpList->LashUpData->ColCount; I++)
    //    PopOutLashUpList->LashUpData->Cols[I]->Assign(LashUpData->Cols[I]);
    //PopOutLashUpList->LashUpData->Row = LashUpData->Row;
	//PopOutLashUpList->LashUpData->Col = LashUpData->Col;
    //PopOutLashUpList->LashUp_List_Label->Caption = LashUp_List_Label->Caption;
	PopOutLashUpList->Visible = true;
    }
}
//---------------------------------------------------------------------------

void __fastcall TLashUp::LashUpStartUpButtonClick(TObject *Sender)
{
// this routine can be called even if the Lashup is already running
// if Port A is not connected fail
if (!MainForm->IsTIUConnected()) {
	MainForm->DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
	return;
    }
LOCKEM();

if (LashUpData->Cells[LASHUP_NAME][LashUpNo - 100] == "*") {
	ShowMessage("Select a LashUp before trying to Start it");
    return;
    }
// Check that no engines are missing "*"
for (int i = 0 ; i < EnginesinLashUpListBox->Items->Count ; i++) {
    if (EnginesinLashUpListBox->Items->Strings[i] == "*") {
    	ShowMessage("Can't start this LashUp, an Engine is missing from the Engine List");
        return;
        }
	}
//MainForm->AltKeyState = LashUpStartUpButton->Focused() ? GetKeyState(VK_MENU) : 0;		// Not used yet
MainForm->ShiftKeyState = LashUpStartUpButton->Focused() ? GetKeyState(VK_SHIFT) : 0;	// Use Extended Startup
//MainForm->CtrlKeyState = LashUpStartUpButton->Focused() ? GetKeyState(VK_CONTROL) : 0;	// Not used yet
// key pressed =  0xFF8X
// key not pressed = 0x000X
// "X" can be 0 or 1 - indicated key was toggled

// TIUNo of head engine of lashup
int HeadEngine;
HeadEngine = MainForm->GetLashUpEngine(LashUpCommand, 0);   // get head engine 1 to 99
//
int MyTIUNo = MainForm->EngineData->Cells [TIU_NUMBER] [HeadEngine].ToIntDef(0);
if (MyTIUNo == 0) {     // Make sure Engine was found on the layout
    MainForm->DebugMessagePopup("Warning", MainForm->TIUNoLashupMsg, "Engine # " + String(HeadEngine));
    return;
    }

if (!TIUSelection[MyTIUNo - 1]) {
    MainForm->DebugMessagePopup("Warning", "TIU #" + String(MyTIUNo) + " associated with the head engine is not active", "");
    return;
    }

// Enable controls until LashUpNo is changed or shutdown occurs
LashUpOpButton->Enabled = true;
LashUpVolButton->Enabled = true;
// Send CM_ LASHUPSTARTUP message to MainForm which will issue the startup command
// Pointer to LashUpCommand may go out of scope if PostMessage is used (I don't understand, LashUpCommand is global)
(void) SendMessage(MainForm->Handle, CM_LASHUPSTARTUP, (unsigned int) LashUpCommand, (MyTIUNo << 8) | LashUpNo);
										// TIUNo is 1  to 5, LashUpNo is 101 to 120
//
/*
// I considered using SendMessageTimeout() in place of SendMessage() but never saw a need
unsigned long EC;
LRESULT lretv = SendMessageTimeout(MainForm->Handle, CM_LASHUPSTARTUP, (unsigned int) LashUpCommand, (MyTIUNo << 8) | LashUpNo, SMTO_ABORTIFHUNG, 5000, &EC);
if (lretv) {
    // error
    }
*/
//
// KeyState values are cleared in MainForm
LashUpShutdownButton->Enabled = true;
}
//---------------------------------------------------------------------------

void __fastcall TLashUp::LashUpShutdownButtonClick(TObject *Sender)
{
// Shutdown is always allowed (even if the LashUp is not running)
// if Port A is not connected fail
if (!MainForm->IsTIUConnected()) {
	MainForm->DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
	return;
    }
LOCKEM()

if (LashUpData->Cells[LASHUP_NAME][LashUpNo - 100] == "*") {
	ShowMessage("Select a LashUp before trying to Shut it down");
    return;
    }
if (LashUpNo - 100 == LashUpData->Row) {
	LashUpVolButton->Enabled = false;
	LashUpOpButton->Enabled = false;
	}
//MainForm->AltKeyState = LashUpShutdownButton->Focused() ? GetKeyState(VK_MENU) : 0;		// Not used yet
MainForm->ShiftKeyState = LashUpShutdownButton->Focused() ? GetKeyState(VK_SHIFT) : 0;	// Use Extended Shutdown
//MainForm->CtrlKeyState = LashUpShutdownButton->Focused() ? GetKeyState(VK_CONTROL) : 0;	// Not used yet
// key pressed =  0xFF8X
// key not pressed = 0x000X
// "X" can be 0 or 1 - indicated key was toggled

// TIUNo of head engine of lashup
int HeadEngine;
HeadEngine = MainForm->GetLashUpEngine(LashUpCommand, 0);   // get head engine 1 to 99
//
int MyTIUNo = MainForm->EngineData->Cells [TIU_NUMBER] [HeadEngine].ToIntDef(0);
if (MyTIUNo == 0) {     // Make sure Engine was found on the layout
    MainForm->DebugMessagePopup("Warning", MainForm->TIUNoLashupMsg, "Engine # " + String(HeadEngine));
    return;
    }

LashUpData->SetFocus();
LashUpData->Invalidate();
// Send CM_ LASHUPSHUTDOWN message to MainForm which will set speed to zero and issue shutdown command
// Pointer to LashUpCommand may go out of scope if PostMessage is used (I don't understand, LashUpCommand is global)
(void) SendMessage(MainForm->Handle, CM_LASHUPSHUTDOWN, (unsigned int) LashUpCommand, (MyTIUNo << 8) | LashUpNo);
                                        // TIUNo is 1  to 5, LashUpNo is 101 to 120
// KeyState values are cleared in MainForm
}
//---------------------------------------------------------------------------

void __fastcall TLashUp::LashUpVolButtonClick(TObject *Sender)
{
// if Port A is not connected fail
if (!MainForm->IsTIUConnected()) {
	MainForm->DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
	return;
    }
LOCKEM();

if (LashUpData->Cells[LASHUP_NAME][LashUpNo - 100] == "*") { return; }	// no name
if (!MainForm->IsConsistRunning(LashUpNo)) return;	// not running

// TIUNo of head engine
int HeadEngine = MainForm->GetLashUpEngine(LashUpCommand, 0);   // get head engine 1 to 99
int MyTIUNo = MainForm->EngineData->Cells [TIU_NUMBER] [HeadEngine].ToIntDef(0);
if (MyTIUNo == 0) {     // Make sure Engine was found on the layout
    MainForm->DebugMessagePopup("Warning", MainForm->TIUNoLashupMsg, "Engine # " + String(HeadEngine));
    return;
    }

// Send CM_LASHUPSOUNDCTRLS message to MainForm which will open the Volume Control Form
// Pointer to LashUpCommand may go out of scope if PostMessage is used (I don't understand, LashUpCommand is global)
(void) SendMessage(MainForm->Handle, CM_LASHUPSOUNDCTRLS, (unsigned int) LashUpCommand, (MyTIUNo << 8) | LashUpNo);
		// TIUNo is 1  to 5, LashUpNo is 101 to 120
}
//---------------------------------------------------------------------------

void __fastcall TLashUp::RevCheckListBoxClickCheck(TObject *Sender)
{
// Check cannot be made readonly so if it gets checked, uncheck it
int i = RevCheckListBox->ItemIndex;
if( i != -1 )
	RevCheckListBox->Checked[i] = !RevCheckListBox->Checked[i];
}
//---------------------------------------------------------------------------

void __fastcall TLashUp::CreateNewLashUpClick(TObject *Sender)
{
if (LashUpData->Cells[LASHUP_NAME][LashUpNo - 100] != "*") {
	ShowMessage("Select an unused LashUp Number before creating a new LashUp");
    return;
    }
// Reload the Active Engine List as it might have changed
ReloadButtonClick(ReloadButton);
// clear the form
HERevCheckListBox->Clear();
MERevCheckListBox->Clear();
TERevCheckListBox->Clear();
HeadEngineSelected->Clear();
MiddleEngineSelected->Clear();
TailEngineSelected->Clear();
LashUpEncodedEdit->Text = "";
LashUpName->Text = "";
if (EngineListBox->Items->Count == 0) {
	// No Engines
    return;
	}
// switch to the create sheet
SaveLashUpButton->Caption = "Save As New";
CreateLashUpSheet->TabVisible = true;
LashUpPageControl->ActivePage = CreateLashUpSheet;
}
//---------------------------------------------------------------------------

void __fastcall TLashUp::EditLashUpClick(TObject *Sender)
{
// check for blank line
if (LashUpData->Cells[LASHUP_NAME][LashUpNo - 100] == "*") return;
if (MainForm->IsConsistRunning(LashUpNo)) {
	ShowMessage("Can't edit this LashUp because it is running");
    return;
    }
// Reload the Active Engine List as it might have changed
ReloadButtonClick(ReloadButton);
// Parse out the Engine List and fill out the Edit LashUP Sheet
// Head Engine
HeadEngineSelected->Clear();
HeadEngineSelected->Items->Add(EnginesinLashUpListBox->Items->Strings[0]);
HERevCheckListBox->Items->Add("");
HERevCheckListBox->Checked[0] = RevCheckListBox->Checked[0];

// Middle Engines
// if count == 2 , no middle engines
if (EnginesinLashUpListBox->Items->Count != 2) {
    // there are middle engines
	MiddleEngineSelected->Clear();

    // go from engine 1 to engine Count - 1
    for (int i = 1 ; i < EnginesinLashUpListBox->Items->Count - 1 ; i++) {
        MiddleEngineSelected->Items->Add(EnginesinLashUpListBox->Items->Strings[i]);
        MERevCheckListBox->Items->Add("");
        MERevCheckListBox->Checked[i - 1] = RevCheckListBox->Checked[i];
    	}
	}

// Tail Engine
TailEngineSelected->Clear();
TailEngineSelected->Items->Add(EnginesinLashUpListBox->Items->Strings[EnginesinLashUpListBox->Items->Count - 1]);
TERevCheckListBox->Items->Add("");
TERevCheckListBox->Checked[0] = RevCheckListBox->Checked[EnginesinLashUpListBox->Items->Count - 1];
// clear out step 5
LashUpEncodedEdit->Clear();
// Put Name in Step 6
LashUpName->Text = LashUpData->Cells[LASHUP_NAME][LashUpNo - 100];
// Reload the Active Engine List as it might have changed
ReloadButtonClick(ReloadButton);
// switch to the Edit sheet
CreateLashUpSheet->TabVisible = true;
SaveLashUpButton->Caption = "Save Edits";
LashUpPageControl->ActivePage = CreateLashUpSheet;	
}
//---------------------------------------------------------------------------

void __fastcall TLashUp::BreakUpLashUpClick(TObject *Sender)
{
// check for blank line
if (LashUpData->Cells[LASHUP_NAME][LashUpNo - 100] == "*") return;
if (MainForm->IsConsistRunning(LashUpNo)) {
	ShowMessage("Can't break up this LashUp because it is running");
    return;
    }

// Break up with confirmation - don't need to be connected to just delete
String AskString = "Do you really want to break up LashUp " + String(LashUpNo - 100) +  " - " +
	LashUpData->Cells[LASHUP_NAME][LashUpNo - 100] + "?";
int retv = Application->MessageBox(AskString.c_str(), TEXT("Confirmation"), MB_YESNO | MB_ICONQUESTION);
if (retv == IDNO) return;	// No - get out

// Feature Reset with confirmation and reset all volume settings - only if connected
if (MainForm->IsTIUConnected()) {	// If the TIU is connected, the ptrTIUCommThread is non-NULL
	String FeatureResetMsg = "Do you also want to Feature Reset all of the engines in LashUp " + String(LashUpNo - 100) +  " - " +
		LashUpData->Cells[LASHUP_NAME][LashUpNo - 100] + "?";
	int ResetEngine = Application->MessageBox(FeatureResetMsg.c_str(), TEXT("Warning"), MB_YESNO | MB_ICONQUESTION);

    TCursor OldCursor = Screen->Cursor; 	// save cursor
	Screen->Cursor = crHourGlass;
    // TIUNo of head engine
    int HeadEngine = MainForm->GetLashUpEngine(LashUpCommand, 0);   // get head engine 1 to 99
    int MyTIUNo = MainForm->EngineData->Cells [TIU_NUMBER] [HeadEngine].ToIntDef(0);
    if (MyTIUNo == 0) {     // Make sure Engine was found on the layout
        MainForm->DebugMessagePopup("Warning", MainForm->TIUNoLashupMsg, "Engine # " + String(HeadEngine));
        return;
        }
    // go from engine 0 to engine Item->Count
	for (int i = 0 ; i < EnginesinLashUpListBox->Items->Count ; i++) {
	    int iEng;
	    _stscanf(EnginesinLashUpListBox->Items->Strings[i].c_str(), TEXT("%d"), &iEng); // each engine number followed by an engine name

		if (ResetEngine == IDYES) {
			// set command mode - this is really not needed
			#ifdef NOTNEEDED
			(void)ptrTIUCommThread->Send_String_FIFO(iEng, "m4", MyTIUNo, RemoteNo, 11, SHORT_WAIT, RECORD_OK, NULL, NULL, NULL);
			#endif
	        // Feature Reset each engine from the LashUp
			(void)ptrTIUCommThread->Send_String_FIFO(iEng, "F0", MyTIUNo, RemoteNo, 11, SHORT_WAIT, RECORD_OK, NULL, NULL, NULL);	// Feature Reset
            // Get the volume values from the EngineData array
            // volume - v0 = Master Volume, v1 = Engine Volume, v2 = Accent Volume, v3 = Horn Volume, v4 = Bell Volume
            char str[15];
            // Reset all of the volume settings to the saved values.
            snprintf(str, sizeof str, "v0%03d", MainForm->EngineData->Cells[VOL_MASTER][iEng].ToIntDef(100)); 	// Master Volume
            (void) ptrTIUCommThread->Send_String_FIFO(iEng, str, MyTIUNo, RemoteNo, 11, SHORT_WAIT, RECORD_OK, NULL, NULL, NULL);
            snprintf(str, sizeof str, "v1%03d", MainForm->EngineData->Cells[VOL_ENGINE][iEng].ToIntDef(100)); 	// Engine Sounds
            (void) ptrTIUCommThread->Send_String_FIFO(iEng, str, MyTIUNo, RemoteNo, 11, SHORT_WAIT, RECORD_OK, NULL, NULL, NULL);
            snprintf(str, sizeof str, "v2%03d", MainForm->EngineData->Cells[VOL_ACCENT][iEng].ToIntDef(100)); 	// Accent Sounds
            (void) ptrTIUCommThread->Send_String_FIFO(iEng, str, MyTIUNo, RemoteNo, 11, SHORT_WAIT, RECORD_OK, NULL, NULL, NULL);
            snprintf(str, sizeof str, "v3%03d", MainForm->EngineData->Cells[VOL_HORN][iEng].ToIntDef(100)); 		// Horn Sounds
            (void) ptrTIUCommThread->Send_String_FIFO(iEng, str, MyTIUNo, RemoteNo, 11, SHORT_WAIT, RECORD_OK, NULL, NULL, NULL);
            snprintf(str, sizeof str, "v4%03d", MainForm->EngineData->Cells[VOL_BELL][iEng].ToIntDef(100));		// Bell Sounds
            (void) ptrTIUCommThread->Send_String_FIFO(iEng, str, MyTIUNo, RemoteNo, 11, SHORT_WAIT, RECORD_OK, NULL, NULL, NULL);
	        }
		}
    Screen->Cursor = OldCursor;
    }

// Activate each engine from the LashUp
TCursor OldCursor = Screen->Cursor; 	// save cursor
Screen->Cursor = crHourGlass;
// TIUNo of head engine
int HeadEngine = MainForm->GetLashUpEngine(LashUpCommand, 0);   // get head engine 1 to 99
int MyTIUNo = MainForm->EngineData->Cells [TIU_NUMBER] [HeadEngine].ToIntDef(0);
if (MyTIUNo == 0) {     // Make sure Engine was found on the layout
    MainForm->DebugMessagePopup("Warning", MainForm->TIUNoLashupMsg, "Engine # " + String(HeadEngine));
    return;
    }
// go from engine 0 to engine Items->Count
for (int i = 0 ; i < EnginesinLashUpListBox->Items->Count ; i++) {
    int iEng;
    _stscanf(EnginesinLashUpListBox->Items->Strings[i].c_str(), TEXT("%d"), &iEng); // each engine number followed by an engine name
    MainForm->SetConsistState(iEng, ACTIVE);
	// send m4 "command mode" command to all engines - this is really not needed
	#ifdef NOTNEEDED
	if (ptrTIUCommThread) (void) ptrTIUCommThread->Send_String_FIFO(iEng, "m4", MyTIUNo, RemoteNo, 11, SHORT_WAIT, RECORD_OK, NULL, NULL, NULL);	// Engine 1-99
	#endif
	}
ReloadButtonClick(NULL);	// rescan the active list
//
// delete the data in the row
LashUpData->Rows[LashUpNo - 100]->CommaText = String(LashUpNo - 100) + LASHUPDAT_INIT;
assert(LashUpData->Rows[LashUpNo - 100]->Count == LASHUPDAT_COUNT);
EnginesinLashUpListBox->Clear();
RevCheckListBox->Clear();
// clear the selected lashup name in the Edit Control
NameofLashUp->Text = "*";

// Save the new Lashup List
SaveLashUpData(LashUpData);
// Refresh the PopOut List
PopOutLashUpList->RefreshClick(NULL);

// if Quick Ctrls is running, update its Engine List
for (int iPage = 1; iPage <= QUICKPAGEMAX ; iPage++) { // Quick Controls 1-5
    if (ptrQuick[iPage - 1]) {
        TQuickCtrl * pQuick = ptrQuick[iPage - 1];
        pQuick->GetEngData();		// call 1st
        pQuick->GetLashUpData();	// call 2nd
        }
    }
Screen->Cursor = OldCursor;
// reset buttons
CreateLU->Enabled = true;
EditLU->Enabled = false;
}
//---------------------------------------------------------------------------

void __fastcall TLashUp::LUpSpinValueChanged(TObject *Sender)
{
// this routine is called whenever something or someone changes LashUpNo->Value
assert(LashUpNo >= LASHUPMIN && LashUpNo <= LASHUPMAX);	//

if (DEBUGGING) DB->Debug_Print("LashUp Number changed from " + String(LashUpNo - 100) + " to " + String(LUpSpin->ValueAsInt));

LashUpNo = LUpSpin->ValueAsInt + 100;

// Enable buttons if an operations form is already created
if (MainForm->IsConsistRunning(LashUpNo)) {
	// enable buttons
	LashUpVolButton->Enabled = true;
	LashUpOpButton->Enabled = true;
    //LashUpShutdownButton->Enabled = true;
	} else {
    // disable buttons
	LashUpVolButton->Enabled = false;
	LashUpOpButton->Enabled = false;
    //LashUpShutdownButton->Enabled = false;
	}

if (LashUpNo != 0) {
	NameofLashUp->Text = LashUpData->Cells[LASHUP_NAME][LashUpNo - 100];
  	// Highlight the LashUp in the StringGrid
	LashUpData->Col = LASHUP_NAME;
	LashUpData->Row = LashUpNo - 100;
    LashUpUpDown->Position = LASHUPUPDOWNLIMIT - (LashUpNo - 100);
	}
}
//---------------------------------------------------------------------------

void __fastcall TLashUp::LashUpOpButtonClick(TObject *Sender)
{
assert(LashUpNo >= LASHUPMIN && LashUpNo <= LASHUPMAX);
// if Port A is not connected fail
if (!MainForm->IsTIUConnected()) {
	MainForm->DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
	return;
    }

LOCKEM();

if (LashUpData->Cells[LASHUP_NAME][LashUpNo - 100] == "*") { return; }	// no name
if (!MainForm->IsConsistRunning(LashUpNo)) return;	// not running

// Show LashUp window.
MainForm ->LashUpShow(LashUpNo);	// note: by the time this routine can be called,
									// the LashUp window was already created by StartUp	
}
//---------------------------------------------------------------------------

void __fastcall TLashUp::CancelLashUpButtonClick(TObject *Sender)
{
// Clear the Form
HeadEngineSelected->Clear();
HERevCheckListBox->Clear();
MiddleEngineSelected->Clear();
MERevCheckListBox->Clear();
TailEngineSelected->Clear();
TERevCheckListBox->Clear();
LashUpEncodedEdit->Text = "";
LashUpName->Text = "";
//Return to Select LashUp Tab
CreateLashUpSheet->TabVisible = false;
LashUpPageControl->ActivePage = SelectLashUpSheet;
LashUpData->SetFocus();
}
//---------------------------------------------------------------------------

void __fastcall TLashUp::ReloadButtonClick(TObject *Sender)
{
// Fill the Engine List from Engine Data
EngineListBox->Clear();
//
for (int i = 1 ; i < ALLENGINE ; i++) { 	// Skip Title Line and All Engine Operation Line - scan for 99 engines
	if (MainForm->EngineData->Cols[ENG_NAME]->Strings[i] == "*") continue;	// skip "*" no engine
    // if INACTIVE, don't add it to the list.
    if (MainForm->QueryConsistState(i) == INACTIVE) continue;	// skip Inactive Engine
    //
    String NewItem = MainForm->EngineData->Cols[ENG_NAME]->Strings[i];
	EngineListBox->Items->Append(String(i) + " " + NewItem);
	}

//if (EngineListBox->Items->Count == 0) {
//    ShowMessage("Engine List is now empty");	// No Engines
//    }
}
//---------------------------------------------------------------------------

void __fastcall TLashUp::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 TLashUp::StartupAllButtonClick(TObject *Sender)
{
// if Port A is not connected fail
if (!MainForm->IsTIUConnected()) {
	MainForm->DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
	return;
    }
LOCKEM();

TCursor OldCursor = Screen->Cursor; 	// save cursor
Screen->Cursor = crHourGlass;
int  OldLashUpValue = LUpSpin->ValueAsInt;

// Start All LashUps - 101-120 (DCS 102)
// (total of 20 LashUps)
// NOTE: Only startsup LashUps that have not yet been started
for (int iLashUp = LASHUPMIN; iLashUp <= LASHUPMAX ; iLashUp++) {
   	if ((LashUpData->Cells[LASHUP_NAME][iLashUp - 100] != "*") && !MainForm->IsConsistRunning(iLashUp)) {
    	LUpSpin->ValueAsInt = iLashUp - 100;
        LashUpStartUpButtonClick(LashUpStartUpButton);	// StartUp LashUp and Open Operations Window
        MainForm->Delay(5000);	// wait 5 seconds after each startup
		}
    }
LUpSpin->ValueAsInt = OldLashUpValue;
Screen->Cursor = OldCursor;		// restore cursor
}
//---------------------------------------------------------------------------

void __fastcall TLashUp::ShutdownAllButtonClick(TObject *Sender)
{
// if Port A is not connected fail
if (!MainForm->IsTIUConnected()) {
	MainForm->DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
	return;
    }
LOCKEM();

TCursor OldCursor = Screen->Cursor; 	// save cursor
Screen->Cursor = crHourGlass;
int OldLashUpValue = LUpSpin->ValueAsInt;

// Shutdown LashUps - 101-120 (DCS 102
// (total of 20 LashUps)
// NOTE: Only shutsdown LashUps that have been started

for (int iLashUp = LASHUPMIN; iLashUp < LASHUPMAX ; iLashUp++) {
	if (MainForm->IsConsistRunning(iLashUp)) {
    	LUpSpin->ValueAsInt = iLashUp - 100;
		LashUpShutdownButtonClick(LashUpShutdownButton);	// Shutdown LashUp and close Operations Window
        MainForm->Delay(1000);	// wait 1 second after each shutdown
        }
    }
LUpSpin->ValueAsInt = OldLashUpValue;
Screen->Cursor = OldCursor;		// restore cursor
}
//---------------------------------------------------------------------------

void __fastcall TLashUp::LashUpDataDrawCell(TObject *Sender, int ACol,
      int ARow, TRect &Rect, TGridDrawState State)
{
// Draw the contents of each cell
LashUpData->Canvas->Font->Size = 12;
LashUpData->Canvas->Font->Style = LashUpData->Canvas->Font->Style >> fsItalic;
// if the cell is selected, highlight it
if( State.Contains(gdSelected) ) {
    LashUpData->Canvas->Font->Color = clWhite;
	LashUpData->Canvas->Brush->Color = clBlue;
	} else {
    if ((ARow > 0) && MainForm->IsLashUpRunning(ARow + 100) != NULL) {
		LashUpData->Canvas->Font->Color = clBlack;	// Started up LashUps are Lime
		LashUpData->Canvas->Brush->Color = clLime;
    	} else {
		LashUpData->Canvas->Font->Color = clBlack;
		LashUpData->Canvas->Brush->Color = clWhite;
        }
	}
// move a little to the right 
Rect.Left = Rect.Left + LISTLEFTOFFSET;
// fill the rectangle
LashUpData->Canvas->FillRect(Rect);
// draw the text
String text;
if ((ACol == LASHUP_NAME) && (ARow != LASHUP_LABEL_ROW) && (LashUpData->Cells[ENG_NAME][ARow] != "*") /* &&
        (MainForm->EngineData->Cells[ENG_STATE][HeadEngine] == ACTIVE)*/) {
    int iHeadEngine;   // get head engine 1 to 99
    String LashupL =  LashUpData->Cells[LASHUP_LIST][ARow];
    _stscanf(LashupL.c_str(), TEXT("%d"), &iHeadEngine);
    iHeadEngine = abs(iHeadEngine);
    text = LashUpData->Cells[ACol][ARow] + "  TIU" + String(MainForm->EngineData->Cells[TIU_NUMBER][iHeadEngine]);
    } else {
    text = LashUpData->Cells[ACol][ARow];
    }
LashUpData->Canvas->TextRect(Rect, Rect.Left, Rect.Top, text);
}
//---------------------------------------------------------------------------

void __fastcall TLashUp::EnginesinLashUpListBoxClick(TObject *Sender)
{
// unselect
EnginesinLashUpListBox->ItemIndex = -1;	
}
//---------------------------------------------------------------------------

void __fastcall TLashUp::LashUpDataKeyDown(TObject *Sender, WORD &Key,
      TShiftState Shift)
{
// if ENTER is pressed, Activate the LashUp if it's present
if (Key == VK_RETURN) {
	if (LashUpData->Cells[LASHUP_NAME][LashUpNo - 100] != "*") {
		LashUpStartUpButtonClick(LashUpStartUpButton);
    	} else {
    	// create a new LashUp
    	CreateNewLashUpClick(CreateNewLashUp);
    	}
    }
}
//---------------------------------------------------------------------------

void __fastcall TLashUp::LashUpDataDblClick(TObject *Sender)
{
// Startup the LashUp or if field is empty, Create New LashUp
if (!MainForm->IsTIUConnected()){
	MainForm->DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
	return;
    }
// double clicking in column header or row header is ignored
if (LashUpData->Col == LASHUP_NUM || LashUpData->Row == LASHUP_LABEL_ROW) return;

// LashUp column double clicked
if (LashUpData->Col == LASHUP_NAME) {
	if (LashUpData->Cells[LASHUP_NAME][LashUpData->Row] == "*") {
	    CreateNewLashUpClick(Sender);	// Create New LashUp
        return;
    	}
    }
// otherwise, startup the LashUp
LashUpStartUpButtonClick(LashUpStartUpButton);
}
//---------------------------------------------------------------------------

void __fastcall TLashUp::LashUpUpDownClick(TObject *Sender,
      TUDBtnType Button)
{
// A bug in BCB4.0 - when this routine is called, the value of Position has
// already changed. Correct operation in CB10.1 "use the OnClick event handler
//to respond to changes just before the value of Position is changed."
if (Button == Comctrls::btPrev) {
    if (LashUpData->Row < (LASHUPUPDOWNLIMIT - 1))
    	LashUpData->Row = LASHUPUPDOWNLIMIT - LashUpUpDown->Position + UPDOWNBUGFIX;
	}
if (Button == Comctrls::btNext) {
    if (LashUpData->Row > 1 )
    	LashUpData->Row = LASHUPUPDOWNLIMIT - LashUpUpDown->Position - UPDOWNBUGFIX;
	}
}
//---------------------------------------------------------------------------

// Workaround for SetLayeredWindowAttributes() to make transparent windows
extern lpfnSetLayeredWindowAttributes m_pSetLayeredWindowAttributes;
//---------------------------------------------------------------------------

void __fastcall TLashUp::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

// enable or disable buttons depending on TIU connection
if (MainForm->IsTIUConnected()) {
    LashUpStartUpButton->Enabled = true;
    StartupAllButton->Enabled = true;
    ShutdownAllButton->Enabled = true;
    LashUpShutdownButton->Enabled = true;
	} else {
    LashUpStartUpButton->Enabled = false;
    StartupAllButton->Enabled = false;
    ShutdownAllButton->Enabled = false;
    LashUpShutdownButton->Enabled = false;
    }

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

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

void __fastcall TLashUp::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 TLashUp::ESTOPClick(TObject *Sender)
{
// emergency stop
// Send CM_EMERGENCYSTOP message to MainForm
(void) PostMessage(MainForm->Handle, CM_EMERGENCYSTOP, 0, 0);
}
//---------------------------------------------------------------------------

void __fastcall TLashUp::OpenPopOutLashUpListClick(TObject *Sender)
{
// Popout the Lashup List
if (PopOutLashUpList == NULL) return;
// Refresh the PopOut List
PopOutLashUpList->RefreshClick(NULL);
//// cannot assign a TStringGrid to a TStringGrid : PopOutEngineList->EngineData->Assign(EngineData);
//for (int I = 0; I < PopOutLashUpList->LashUpData->ColCount; I++)
//    PopOutLashUpList->LashUpData->Cols[I]->Assign(LashUpData->Cols[I]);
//PopOutLashUpList->LashUpData->Row = LashUpData->Row;
//PopOutLashUpList->LashUpData->Col = LashUpData->Col;
//PopOutLashUpList->LashUp_List_Label->Caption = LashUp_List_Label->Caption;
PopOutLashUpList->Visible = true;
}
//---------------------------------------------------------------------------

void __fastcall TLashUp::SaveEditsButtonClick(TObject *Sender)
{
// save Lashup Data
SaveLashUpData(LashUpData);
}
//---------------------------------------------------------------------------

void __fastcall TLashUp::HERevCheckListBoxClick(TObject *Sender)
{
LashUpEncodedEdit->Text = "";   // Need to press the Build LashUp Button again
}
//---------------------------------------------------------------------------

void __fastcall TLashUp::MERevCheckListBoxClick(TObject *Sender)
{
LashUpEncodedEdit->Text = "";   // Need to press the Build LashUp Button again
}
//---------------------------------------------------------------------------

void __fastcall TLashUp::TERevCheckListBoxClick(TObject *Sender)
{
LashUpEncodedEdit->Text = "";   // Need to press the Build LashUp Button again
}
//---------------------------------------------------------------------------

