//---------------------------------------------------------------------------
/*
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
//---------------------------------------------------------------------------
#include <vcl.h>
#include <tchar.h>
#pragma hdrstop

#include "Message_Popup.h"
#include "Train_Control.h"
#include "PBFileThread.h"
#include "Route_Scene_Thread.h"
#include "Hot_Com.h"
#include "Setup_Control.h"
#include "Comm_Thread.h"
#include "Debug_Controls.h"
#include "Command_Query.h"
#include "Clipbrd.hpp"

#include <stdio.h>
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma link "HideButton"
#pragma link "_GClass"
#pragma link "AbCBitBt"
#pragma resource "*.dfm"
//---------------------------------------------------------------------------
//THot_Command *Hot_Command;	// 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 TThread * ptrPBThread[20 * HOTPAGEMAX];		// pointers to Hot Com's playback threads
//---------------------------------------------------------------------------
__fastcall THot_Command::THot_Command(TComponent* Owner)
	: TForm(Owner)
{
assert(false);	// never called since I used the overloaded constructor below
}
//---------------------------------------------------------------------------
__fastcall THot_Command::THot_Command(TComponent* Owner, int Remote, int Engine, int TIU, int AIU, int Page)
	: TForm(Owner)
{
// grab the Remote, Engine, TIU and AIU numbers - Remote Number is used for all commands, Engine/TIU/AIU are used as default values only
RemoteNo = Remote;
assert(RemoteNo >= 0 && RemoteNo <= REMOTEMAX);
EngineNo =  Engine;
assert(EngineNo >= 0 && EngineNo <= LASHUPMAX);	// 0 factory reset, 1-99, 100 "all engine operation, 101-120 LashUps
TIUNo = TIU;
assert(TIUNo >= 1 && TIUNo <= TIUMAX);
AIUNo = AIU;
assert(AIUNo >= 0 && AIUNo <= AIUMAX);   // AIU can be 0 if TIU does not have any AIU connected
if (AIUNo == 0 ) AIUNo = 1;  		// set to 1 for editing Hot Buttons
PageNo = Page;
assert(PageNo >= 1 && PageNo <= HOTPAGEMAX);
FirstButtonNo = ((PageNo - 1 ) * 20) + 1;
assert((FirstButtonNo % 20 ) == 1);	// 1, 21, 41, 61, 81, etc
assert(FirstButtonNo < 101);

Hot_Controls_Label->Caption = "Hot Buttons : " + String(FirstButtonNo) + " - " + String(FirstButtonNo + 19);

LocalTransparencyOverride = false;

// clear the pointers to the threads. Up to 100, one for each button.
for (int iButtonBase = 1 ; iButtonBase <= (20 * HOTPAGEMAX) ; iButtonBase++) {
	ptrPBThread[iButtonBase-1] = NULL;
	}

if (Setup->DisableEmergencyStopButton->Checked) {
	ESTOP->Visible = false;
    ESTOP->Enabled = false;
    ESTOP->Hint = "Disabled in Setup";
    ESTOP->Cursor = crNo;
    }

// Fill the Hot Command Buttons from a disk file if it exists
HCDataFile = String(MainForm->cStartDir) + "\\HotCommandData" + String(Page) + ".ini";
// rename old filename if it exists
const String HCOldDataFile = String(MainForm->cStartDir) + "\\HotCommandData.ini";
if (FileExists(HCOldDataFile)) {
	RenameFile (HCOldDataFile, HCDataFile);
    }

HotCommandDat = new TStringList();	// deleted in destructor;
assert (HotCommandDat != NULL);	// Make sure that the constructor worked
HotCommandDat->Sorted = false;

// create a default comma separated hot command list
for (int iButtonBase = 0 ; iButtonBase < 20 ; iButtonBase++) {
    TCHAR str[256];
    //                                  COMMAND_NAME 0
    //                                             COMMAND_ENGINE 1
    //                                                COMMAND_TIU 2
    //                                                   COMMAND_AIU 3
    //                                                      COMMAND_PORT 4
    //                                                         COMMAND_LASHUPSTRING 5
    //                                                            COMMAND_STRINGCOMMAND 6
    _sntprintf(str, sizeof str, TEXT("\"Button%d\",%d,%d,%d,%d,%s,%s"), iButtonBase + FirstButtonNo, 1, 1, 1, 1, TEXT("\"\""), TEXT("\"\""));
    HotCommandDat->Add(str);
	}
//
// Fill the Command List Memo
//
CmdDataFile = String(MainForm->cStartDir) + "\\HotCommands.txt";
bCmdDataLoaded = false;
bCmdDataChanged = false;
// if the file exists, overwrite the internal data
if (FileExists(CmdDataFile)) {
	CommandListMemo->Clear();
    CommandListMemo->Lines->LoadFromFile(CmdDataFile);
    if (DEBUGGING) DB->Debug_Print("Command List Read from " + CmdDataFile);
    bCmdDataLoaded = true;
	}
//
// The next line fixes a potenial bug in the TMemo control. The data does not seem
// to be accessable until some function is performed on the TMemo. Several different
// operations made the data accessible and I chose this one.
//CommandListMemo->SelectAll();
// As nothing is selected, nothing is actually copied
CommandListMemo->CopyToClipboard();
//
// Use alternate words for Normal and Reverse if given in the  config.lua file
if (Setup->SwitchStraight != "Normal") {
	for (int i = 0 ; i < CommandListMemo->Lines->Count ; i++) {
        CommandListMemo->Lines->Strings[i] = StringReplace(CommandListMemo->Lines->Strings[i], "Normal", Setup->SwitchStraight, TReplaceFlags());
    	}
	}
if (Setup->SwitchDiverge != "Reverse") {
	for (int i = 0 ; i < CommandListMemo->Lines->Count ; i++) {
        CommandListMemo->Lines->Strings[i] = StringReplace(CommandListMemo->Lines->Strings[i], "Reverse", Setup->SwitchDiverge, TReplaceFlags());
    	}
	}
//
// Fill the Hot Command Data (HotCommandDat)
//
bHCDataCorrupt = false;
bHCDataLoaded = true;
bHCDataChanged = true;	// set true to create default file first time
__try {
   	try {
    	if (FileExists(HCDataFile)) {
			// read it
            HotCommandDat->Clear();
	    	HotCommandDat->LoadFromFile(HCDataFile);
            if (DEBUGGING) DB->Debug_Print("Hot Command Data Read from " + HCDataFile);
            for (int HCNum = 0 ; HCNum < 20 ; HCNum++) {
	            // Validate Data
    	        int field = ValidateHCDat(HCNum);
        	    if (field != -1) throw((HCNum * 10) + field);
                }
            bHCDataChanged = false;
            }
        }
    catch (int field) {
    	MainForm->DebugMessage("Saved Hot Command Data File Field (" + String(field) + ") is Corrupt : " + HCDataFile);
    	bHCDataCorrupt = true;
    	MainForm->CaughtErrors->Value ++;
    	}
    catch ( ... ){
    	MainForm->DebugMessage("Saved Hot Command File is Corrupt : " + HCDataFile);
    	bHCDataCorrupt = true;
    	MainForm->CaughtErrors->Value ++;
    	}
    }
    __finally {
    	bHCDataLoaded = true;
        SaveEditsButton->Enabled = false;
    	}
//
// Fill the Button name from saved file on disk
//
// Start by iterating through the controls of the container
for (int iButtonBase = 0; iButtonBase < HotPanel->ControlCount; iButtonBase++)  {
    // Let us extract the control at index iButtonNo
    TControl *control = HotPanel->Controls[iButtonBase];
    // Test for a TButton...
    TButton *button = dynamic_cast<TButton *>(control);
    // Test whether this was actually a button
    if (button)	{
        // It is a button, we can access TButton only properties...
        TStringList * CL = new TStringList();
		assert (CL != NULL);	// Make sure that the constructor worked
        CL->CommaText = HotCommandDat->Strings[iButtonBase];
        assert(CL->Count == HC_COUNT);
        button->Caption = CL->Strings[COMMAND_NAME];
        button->Name = "Button" + String(iButtonBase + FirstButtonNo);
        delete CL;
	    }
    }

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

int __fastcall THot_Command::ValidateHCDat(int iButtonNoBase)
{
int val, len;
assert(iButtonNoBase >= 0 && iButtonNoBase <= 19);	// iButtonNoBase 0 to 19
String CommandString;
TStringList * CL = new TStringList();
assert (CL != NULL);	// Make sure that the constructor worked
CL->CommaText = HotCommandDat->Strings[iButtonNoBase];
assert(CL->Count == HC_COUNT);
//
// Hot Command Name
len = CL->Strings[COMMAND_NAME].Length();
if (len == 0) {
	delete CL;
    return(COMMAND_NAME);
    }
// Hot Command Engine Number
val = CL->Strings[COMMAND_ENGINE].ToInt(); 	
if (val < 0 || val > 101) {
	delete CL;
    return(COMMAND_ENGINE);
    }
// Hot Command  COMMAND_TIU   TIU must be between 1 and 5
val = CL->Strings[COMMAND_TIU].ToInt(); 	
if (val < 1 || val > TIUMAX) {
	delete CL;
    return(COMMAND_TIU);
    }
// Hot Command  COMMAND_P1
val = CL->Strings[COMMAND_P1].ToInt();	
if (val < 0 || val > 255) {		// AIU field (1-5) is also used for general parameter so we let it be 0-255
	delete CL;
    return(COMMAND_P1);
    }
// Hot Command  COMMAND_P2
val = CL->Strings[COMMAND_P2].ToInt();	
if (val < 1 || val > 10) {
	delete CL;
    return(COMMAND_P2);
    }
// Hot Command  COMMAND_P3  - must be len == 0 or len > 3. ie "" or "1,2"
len = CL->Strings[COMMAND_P3].Length();
if (len == 1 || len == 2) {
	delete CL;
    return(COMMAND_P3);
    }
// Hot Command  COMMAND_COMMANDSTRING - insure that the Command String is in the CommandListMemo
CommandString = CL->Strings[COMMAND_COMMAND];
if (!CommandString.IsEmpty()) {
    int index = CommandListMemo->Lines->IndexOf(CommandString);
	if (index == -1) {
		delete CL;
	    return(COMMAND_COMMAND);
	    }
    }
//
delete CL;
return -1;  	// valid
}
//---------------------------------------------------------------------------

__fastcall THot_Command::~THot_Command(void)
{
//
if (HotCommandDat) {
	SaveHotCommandDat();
    delete HotCommandDat;
    HotCommandDat = NULL;
    }
if (bCmdDataChanged) {
    CommandListMemo->Lines->SaveToFile(CmdDataFile);
    if (DEBUGGING) DB->Debug_Print("Command List Saved to " + CmdDataFile);
    bCmdDataLoaded = true;
    bCmdDataChanged = false;
	}
}
//---------------------------------------------------------------------------

void __fastcall THot_Command::SaveHotCommandDat(void)
{
// Save Hot Command Data to disk with overwrite
if (bHCDataLoaded && !bHCDataCorrupt && bHCDataChanged) { // don't save if we could not read it or we didn't change it
    __try {
    	try {
			// transfer data from Grid
			HotCommandDat->SaveToFile(HCDataFile);
   			if (DEBUGGING) DB->Debug_Print("Hot Command Data Saved to " + HCDataFile);
            }
        catch (...) {
        	MainForm->DebugMessage("Unknown Error when writing Hot Command Data to file : " + HCDataFile);
        	bHCDataCorrupt = true;
        	MainForm->CaughtErrors->Value ++;
        	}
        }
    __finally {
	    SaveEditsButton->Enabled = false;
        }
    }
}
//---------------------------------------------------------------------------

void __fastcall THot_Command::ESTOPClick(TObject *Sender)
{
// emergency stop
// Send CM_EMERGENCYSTOP message to MainForm
(void) PostMessage(MainForm->Handle, CM_EMERGENCYSTOP, 0, 0);	
}
//---------------------------------------------------------------------------

void __fastcall THot_Command::ButtonMouseDown(TObject *Sender,
      TMouseButton Button, TShiftState Shift, int X, int Y)
{
int Engine;
int TIU;
// get the button Number
int iButtonNo = 0;
TButton * ButtonUsed = (TButton *)Sender;
_stscanf(ButtonUsed->Name.c_str(),TEXT("Button%d"), &iButtonNo);
assert(iButtonNo >= 1 && iButtonNo <= (20 * HOTPAGEMAX));
//
// left click -> Perform Function
if (Button == mbLeft) {
	// must be connected
    if (!MainForm->IsTIUConnected()){
		MainForm->DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
		return;
    	}
   	TStringList * CL = new TStringList();
    assert (CL != NULL);	// Make sure that the constructor worked
    CL->CommaText = HotCommandDat->Strings[iButtonNo - FirstButtonNo];
	assert(CL->Count == HC_COUNT);
    // is it a valid command?
    String Cmnd = CL->Strings[COMMAND_COMMAND];
    if (Cmnd.IsEmpty()) {
    	// no
        ShowMessage(TEXT("Command not valid or not defined.\nRight Click to Edit the Command"));
        delete CL;
        return;
        }

    // Check if command is a Playback of a recorded file
    if (Cmnd.Pos("Playback of a Recorded File") > 0) {
    	if (ptrPBThread[iButtonNo-1] != NULL) {
        	delete CL; 	// individual buttons cannot be clicked until previous playback is complete
            MainForm->DebugMessage("Playback for this button is already running");
            return;
            }
		String FName = CL->Strings[COMMAND_FILENAME];
		if (!FName.IsEmpty()) {
			// Start the playback thread - FreeOnTerminate is not set so we must delete the Thread.
            // The thread clears the ptrPBThread[] pointer array when it is complete.
		    ResetEvent(MainForm->TerminationPBEvent);	// reset any lingering events
    	   	if (DEBUGGING) DB->Debug_Print("Starting Playback Thread for : " + FName);
			// Create suspended - in BCB 4.0, the thread constructor does not wait for lower level constructors itself.
		    // The Execute() routine actually runs before some lower level construction is finished.
		    // Don't put constructions in the thread constructor. if you have to, start the thread suspended and then start it running
		    // per Remy LeBeau (TeamB) it is a Known bug that was fixed in BCB 6.0
			ptrPBThread[iButtonNo-1] = new TPlaybackFileThread(true, this, Handle, RemoteNo, FName, iButtonNo, 0);	// CALLEDBYSETUP or button number 1 to 100
			assert(ptrPBThread[iButtonNo-1]);
			#if __BORLANDC__ >= 0x720
			ptrPBThread[iButtonNo-1]->Start();			// start the thread for CB10.1
			#else
			ptrPBThread[iButtonNo-1]->Resume();			// start the thread for BCB4.0 - use deprecated in CB10.1, use Start() instead
			#endif
            } else {
            MainForm->DebugMessage("Playback File Name is missing");
            }
        delete CL;
        return;
    	}
    // Check if command is a setting/reseting of a Route/Scene
    if (Cmnd.Pos("Route/Scene") > 0) {
    	//if (SAThread[iButtonNo - FirstButtonNo] != NULL) {
        //	delete CL; 	// buttons cannot be used multiple times
        //    return;
        //    }
		char RSNum[10];
		snprintf(RSNum, sizeof RSNum, "%03d", CL->Strings[COMMAND_ROUTESCENE].ToInt());	
		const String FileName = String(MainForm->cStartDir) + "\\" + "SwitchAccessory" + String(RSNum) + ".ini";
		if (!FileExists(FileName)) {
            MainForm->DebugMessage("Route/Scene File does not Exist: " + FileName);
            delete CL;
            return;
        	}
        // Start the route/scene thread - FreeOnTerminate is set true so we don't need to
        // worry about deleting the Thread
        if (DEBUGGING) DB->Debug_Print("Starting Route/Scene Thread for : " + FileName);
		//SAThread[iButtonNo - FirstButtonNo] =
		TRouteSceneThread * RSThread;
	    if (Cmnd.Pos("Activate a Route/Scene") > 0) {	
			// Activate
			RSThread = new TRouteSceneThread(true, this, Handle, RemoteNo, FileName, CL->Strings[COMMAND_ROUTESCENE].ToInt(), RSACTIVATE);
            } else {
            // Deactivate
			RSThread = new TRouteSceneThread(true, this, Handle, RemoteNo, FileName, CL->Strings[COMMAND_ROUTESCENE].ToInt(), RSRESET);
			}
			#if __BORLANDC__ >= 0x720
			RSThread->Start();			// start the thread for CB10.1
			#else
			RSThread->Resume();			// start the thread for BCB4.0 - use deprecated in CB10.1, use Start() instead
			#endif
        delete CL;
		return;
        }

	// Check for LashUp
    unsigned char LashUpCommand[LASHUP_ENGINE_LIST_LENGTH];
    LashUpCommand[0] = NULL;
    Engine = CL->Strings[COMMAND_ENGINE].ToInt();	
    TIU = CL->Strings[COMMAND_TIU].ToInt();	
    if (Engine == 101){	// DCS #102
    	// LashUp
        if (CL->Strings[COMMAND_LASHUPSTRING].IsEmpty()) {
            ShowMessage(TEXT("LashUp String Required"));
            delete CL;
            return;
        	}
        // DCS v6 or newer and the WIU use ASCII to send the engine numbers.
        // convert comma separated list into binary  "," 0xXX 0xXX 0xXX 0xFF,
        // with HO bit on for reverse running
		if (CL->Strings[COMMAND_LASHUPSTRING].IsEmpty() || CL->Strings[COMMAND_LASHUPSTRING] == " ") {
			delete CL;
			return;	// empty
		    }
		//
		// Fill the List
		TStringList * EngList = new TStringList();
		assert (EngList != NULL);	// Make sure that the constructor worked
	    EngList->CommaText = CL->Strings[COMMAND_LASHUPSTRING];
		assert(EngList->Count >= 2);		// must be at least 2 engines in the list
		// Builds the ASCII command string sent to the TIU
		unsigned int EngIndex = 0;
		LashUpCommand[EngIndex++] = 0x2C;	// start with a comma ","
        LashUpCommand[EngIndex] = '\0';
		//fill the engine list
		for (int i = 0; i < EngList->Count ; i++) {
            try {

                const int val = EngList->Strings[i].ToInt();
                int iEng = abs(val);
                assert(iEng >= 1 && iEng <= ENGINEMAX);
                // 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) {
                    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] = NULL;	// Null terminator
		delete EngList;
	    }
    // Isolate out command
	const int iPos = Cmnd.Pos(TEXT("="));
	const char iCmd = Cmnd.c_str()[iPos];
	// MyCommand must be AnsiString because the c_str() taken needs to be a char type.
	AnsiString MyCommand = Cmnd.SubString(iPos + 1, 25).Trim();	// Must be AnsiString
    char str[64];
    int p1 = CL->Strings[COMMAND_P1].ToInt(); 	// General Operands
	const int p2 = CL->Strings[COMMAND_P2].ToInt();
	if (iCmd == 'A') {
	    // AIU and Port # sent to TIU are minus 1
		snprintf(str, sizeof str, MyCommand.c_str(), p1 - 1, p2 - 1);
        } else {
        if (iCmd == 's' || iCmd == 'n' || iCmd == 'v') {
			// Speed, Sound number and Volume level come back in the AIU slot
			if (iCmd == 's') {
				// Scale the speed if requested in Setup
				p1 = p1 / MainForm->GlobalMPHScaling;
				}
			snprintf(str, sizeof str, MyCommand.c_str(), p1);
        	} else {
			// General command with up to two operands, 1st in the AIU slot and 2nd in the Port slot
			snprintf(str, sizeof str, MyCommand.c_str(), p1, p2);
    	    }
        }
    // Send Command --  for 'U' command, remove the leading comma from the LashUp String
	(void) ptrTIUCommThread->Send_String_FIFO(Engine, str, TIU, RemoteNo, 11, SHORT_WAIT, RECORD_OK, (iCmd == 'U') ? LashUpCommand + 1 : LashUpCommand, NULL, NULL);
    // If this is a "Siding Power On" command, send an "I0" command
    if (iCmd == 'A' && (Cmnd.Pos("Siding Power On") > 0)) {
        // it is OK to record this command as we don't use the response
    	(void) ptrTIUCommThread->Send_String_FIFO(Engine, "I0", TIU, RemoteNo, 39, I_WAIT, RECORD_OK, (iCmd == 'U') ? LashUpCommand + 1 : LashUpCommand, NULL, NULL);
        }

    delete CL;
    return;
	}

// right click-> Edit Action
if (Button == mbRight) {
	//
	// Load the list of possible commands for the drop down list - format <command name>=<command>
	//
	const int iCount = CommandListMemo->Lines->Count;
	Command_Query_Form->ButtonCommand->Items->Clear();
	for (int j = 0 ; j < iCount ; j++){
		// move the command name into the drop down list
		const int iPos =  CommandListMemo->Lines->Strings[j].Pos("=");
	   	if (iPos != 0) {
        	Command_Query_Form->ButtonCommand->Items->Add(CommandListMemo->Lines->Strings[j].SubString(1, iPos - 1).Trim());
            continue;
            }
        // comments "//" and blank lines
        Command_Query_Form->ButtonCommand->Items->Add(CommandListMemo->Lines->Strings[j]);
		}
    Command_Query_Form->ButtonCommand->ItemIndex = 0;	// point to first line

	TStringList * CL = new TStringList();
    assert (CL != NULL);	// Make sure that the constructor worked
    int iButtonBase = iButtonNo - FirstButtonNo;
    assert(iButtonBase >= 0 && iButtonBase <= 19);	// iButtonNoBase 0 to 19
    CL->CommaText = HotCommandDat->Strings[iButtonBase];
	assert(CL->Count == HC_COUNT);
    try {
    	Command_Query_Form->ButtonNo = iButtonNo;	// button 1 to 100
		Command_Query_Form->Question->Caption = TEXT("Edit Hot Button #") + String(iButtonNo);
    	Command_Query_Form->ButtonName->Text = CL->Strings[COMMAND_NAME];
	    Command_Query_Form->ButtonEngNo->Value = CL->Strings[COMMAND_ENGINE].ToInt();
	    Command_Query_Form->ButtonTIUNo->Value = CL->Strings[COMMAND_TIU].ToInt();
        Command_Query_Form->P1->MaxValue = 255;
        Command_Query_Form->P2->MaxValue = 255;
	    Command_Query_Form->P1->Value = CL->Strings[COMMAND_P1].ToInt();
	    Command_Query_Form->P2->Value = CL->Strings[COMMAND_P2].ToInt();
	    Command_Query_Form->P3->Text = CL->Strings[COMMAND_P3];
        Command_Query_Form->CommandListMemo = CommandListMemo;
        if (Command_Query_Form->P3->Text.Pos(TEXT("."))) {
        	// it's a file name
            Command_Query_Form->P3->Hint = CL->Strings[COMMAND_P3];
            }
		if (!CL->Strings[COMMAND_COMMAND].IsEmpty()) {
	        int index = CommandListMemo->Lines->IndexOf(CL->Strings[COMMAND_COMMAND]);
            Command_Query_Form->ButtonCommand->ItemIndex = index; // IndexOf
            } else {
            Command_Query_Form->ButtonCommand->ItemIndex = 0;	// no command yet
            }
        }
    catch(...) {
        MainForm->DebugMessage("Invalid non-Integer data in " + HCDataFile);
        delete CL;
        return;
    	}
    #if __BORLANDC__ >= 0x720
    // Defined for CB10.1 C++ programs that use the VCL.
    Command_Query_Form->PopupMode = pmExplicit;
    Command_Query_Form->PopupParent = this;
    Command_Query_Form->Position =  poDesigned;
    #endif
	int retv = Command_Query_Form->ShowModal();
	if (retv == mrCancel) {	// no change
         delete CL;
    	 return;
         }

    try {
	    CL->Strings[COMMAND_NAME] = Command_Query_Form->ButtonName->Text;
    	CL->Strings[COMMAND_ENGINE] = Command_Query_Form->ButtonEngNo->Value;
	    CL->Strings[COMMAND_TIU] = Command_Query_Form->ButtonTIUNo->Value;
	    CL->Strings[COMMAND_P1] = Command_Query_Form->P1->Value;
	    CL->Strings[COMMAND_P2] = Command_Query_Form->P2->Value;
	    CL->Strings[COMMAND_P3] = Command_Query_Form->P3->Text;
        int index = Command_Query_Form->ButtonCommand->ItemIndex;
        CL->Strings[COMMAND_COMMAND] = CommandListMemo->Lines->Strings[index];
	    ButtonUsed->Caption = CL->Strings[COMMAND_NAME];
        }
    catch(...) {
        MainForm->DebugMessage("Invalid non-Integer data in the command");
        delete CL;
        return;
    	}
    // return it to the comma separated list
    assert(CL->Count == HC_COUNT);
    HotCommandDat->Strings[iButtonBase] = CL->CommaText;
    // Validate Data
    int field = ValidateHCDat(iButtonBase);
    if (field != -1) {
        MainForm->DebugMessage("Edited Hot Command Data Field (" + String(field) + ") is Corrupt - Not Saved : " + HCDataFile);
	    bHCDataChanged = false;
    	SaveEditsButton->Enabled = false;
        } else {
	    bHCDataChanged = true;
    	SaveEditsButton->Enabled = true;
        }
    delete CL;
    return;
	}
}
//---------------------------------------------------------------------------

void __fastcall THot_Command::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:
	    if (Shift.Contains(ssShift)) {
            ButtonMouseDown(Button11, mbLeft, Shift, 0,0);
        	} else {
            ButtonMouseDown(Button1, mbLeft, Shift, 0,0);
            }
        Key = 0;	// handled
        break;
    case VK_F2:
	    if (Shift.Contains(ssShift)) {
            ButtonMouseDown(Button12, mbLeft, Shift, 0,0);
        	} else {
            ButtonMouseDown(Button2, mbLeft, Shift, 0,0);
            }
        Key = 0;	// handled
        break;
    case VK_F3:
	    if (Shift.Contains(ssShift)) {
            ButtonMouseDown(Button13, mbLeft, Shift, 0,0);
        	} else {
            ButtonMouseDown(Button3, mbLeft, Shift, 0,0);
            }
        Key = 0;	// handled
        break;
    case VK_F4:
	    if (Shift.Contains(ssShift)) {
            ButtonMouseDown(Button14, mbLeft, Shift, 0,0);
        	} else {
            ButtonMouseDown(Button4, mbLeft, Shift, 0,0);
            }
        Key = 0;	// handled
        break;
    case VK_F5:
	    if (Shift.Contains(ssShift)) {
            ButtonMouseDown(Button15, mbLeft, Shift, 0,0);
        	} else {
            ButtonMouseDown(Button5, mbLeft, Shift, 0,0);
            }
        Key = 0;	// handled
        break;
    case VK_F6:
	    if (Shift.Contains(ssShift)) {
            ButtonMouseDown(Button16, mbLeft, Shift, 0,0);
        	} else {
            ButtonMouseDown(Button6, mbLeft, Shift, 0,0);
            }
        Key = 0;	// handled
        break;
    case VK_F7:
	    if (Shift.Contains(ssShift)) {
            ButtonMouseDown(Button17, mbLeft, Shift, 0,0);
        	} else {
            ButtonMouseDown(Button7, mbLeft, Shift, 0,0);
            }
        Key = 0;	// handled
        break;
    case VK_F8:
	    if (Shift.Contains(ssShift)) {
            ButtonMouseDown(Button18, mbLeft, Shift, 0,0);
        	} else {
            ButtonMouseDown(Button8, mbLeft, Shift, 0,0);
            }
        Key = 0;	// handled
        break;
    case VK_F9:
	    if (Shift.Contains(ssShift)) {
            ButtonMouseDown(Button19, mbLeft, Shift, 0,0);
        	} else {
            ButtonMouseDown(Button9, mbLeft, Shift, 0,0);
            }
        Key = 0;	// handled
        break;
    case VK_F10:
	    if (Shift.Contains(ssShift)) {
            ButtonMouseDown(Button20, mbLeft, Shift, 0,0);
        	} else {
            ButtonMouseDown(Button10, mbLeft, Shift, 0,0);
            }
        Key = 0;	// handled
        break;
    case VK_F11:
    case VK_F12:
		MainForm->FormKeyDown(Sender, Key, Shift);
        Key = 0;	// handled
		break;
    case VK_NEXT:
    	// Next PC
        NextPageClick(Sender);
    	Key = 0;	// handled
        break;
    case VK_PRIOR:
    	// Previous PC
        PreviousPageClick(Sender);
    	Key = 0;	// handled
        break;
	}	
}
//---------------------------------------------------------------------------

void __fastcall THot_Command::SaveEditsButtonClick(TObject *Sender)
{
// Save Edits with overwrite
SaveHotCommandDat();
}
//---------------------------------------------------------------------------

void __fastcall THot_Command::EditCommandList1Click(TObject *Sender)
{
HotPanel->Visible = false;
DoneEditButton->Visible = true;
HideButton->Visible = false;
}
//---------------------------------------------------------------------------

void __fastcall THot_Command::DoneEditButtonClick(TObject *Sender)
{
HotPanel->Visible = true;
DoneEditButton->Visible = false;
HideButton->Visible = true;
}
//---------------------------------------------------------------------------

void __fastcall THot_Command::CommandListMemoChange(TObject *Sender)
{
bCmdDataChanged = true;
}
//---------------------------------------------------------------------------

void __fastcall THot_Command::CMStopPlayback(TMessage &Message)
{
// turn off playback for a particular button
const int iButtonNo = Message.LParam;	// button 1 to 100
assert(iButtonNo >= 1 && iButtonNo <= (20 * HOTPAGEMAX));
// this message handler is called only when TPlayBackFile is called from THot_Command
if (ptrPBThread[iButtonNo-1] != NULL) {
	ptrPBThread[iButtonNo-1]->Terminate();
    ptrPBThread[iButtonNo-1]->WaitFor();	// do not use FreeOnTerminate
    delete ptrPBThread[iButtonNo-1];		// Free memory because FreeOnTerminate is not set
    ptrPBThread[iButtonNo-1] = NULL;		// release the pointer
    if (DEBUGGING) DB->Debug_Print("THot_Command : CMStopPlayback : Playback Stopped for Button " + String(iButtonNo));
    }
}
//---------------------------------------------------------------------------

void __fastcall THot_Command::TerminateAllPlaybackClick(TObject *Sender)
{
for (int iButtonNo = 1 ; iButtonNo <= (20 * HOTPAGEMAX) ; iButtonNo++) {
	if (ptrPBThread[iButtonNo-1] != NULL) ptrPBThread[iButtonNo-1]->Terminate();
    ptrPBThread[iButtonNo-1] = NULL;
    }
if (DEBUGGING) DB->Debug_Print("TerminateAllPlaybackClick : Playback Stopped");
}
//---------------------------------------------------------------------------
// Workaround for SetLayeredWindowAttributes() to make transparent windows
extern lpfnSetLayeredWindowAttributes m_pSetLayeredWindowAttributes;
//---------------------------------------------------------------------------

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

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

void __fastcall THot_Command::PreviousPageClick(TObject *Sender)
{
//
// send a message to MainForm to display the previous page of Hot Buttons
int NewPage = PageNo;
if (NewPage == 1) {
	NewPage = HOTPAGEMAX;
    } else {
    NewPage -= 1;
    }
(void) PostMessage(MainForm->Handle, CM_HOTCOM, NewPage, 0);
}
//---------------------------------------------------------------------------

void __fastcall THot_Command::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;
    }
}
//---------------------------------------------------------------------------


