//---------------------------------------------------------------------------
/*
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 "RTCMegaMaster.h"		// Dispatch List

#include "Engine_Commands.h"
#include "Train_Control.h"
#include "Op_Control.h"
#include "Track_Signal_Thread.h"
#include "Track_Signal.h"
#include "Volume_Control.h"
#include "Setup_Control.h"
#include "Debug_Controls.h"
#include "Quick_Controls.h"
#include "Comm_Thread.h"
#include "Clipbrd.hpp"

#include <stdio.h>
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma link "_GClass"
#pragma link "AbVSlide"
#pragma link "AbHSlide"
#pragma link "AbSwitch"
#pragma link "AbCBitBt"
#pragma link "AbDial"
#pragma link "AbVInd"
#pragma link "ColorButton"
#pragma resource "*.dfm"

//TVolume *Volume;	// NOTE: this is public static variable, you can never use
					// this when you create multiple instances of a Form
//---------------------------------------------------------------------------
extern TQuickCtrl * ptrQuick[QUICKPAGEMAX];			// Quick Control Pointers
extern TOp * ptrOp[ABSOLUTEMAX];   		// Operation Control Pointers - indexed by actual engine number because 0 is a factory reset engine
extern TCommThread * ptrTIUCommThread;			// pointer to TIU communications thread
//---------------------------------------------------------------------------
__fastcall TVolume::TVolume(TComponent* Owner)
	: TForm(Owner)
{
assert(false);	// never called since I used the overloaded constructor below
}
//---------------------------------------------------------------------------

__fastcall TVolume::TVolume(TComponent* Owner, int Remote, int Consist, int TIU, String ConName, char * ptrLUEngineList)
	: TForm(Owner)
{
// Owner is always MainForm
//
// grab the Remote, Engine and TIU numbers
RemoteNo = Remote;
assert(RemoteNo >= 0 && RemoteNo <= REMOTEMAX);
ConsistNo =  Consist;
assert(ConsistNo >= 0 && ConsistNo <= LASHUPMAX);	// 0 factory reset, 1-99, 100 "all engine operation", 101-120 LashUps
TIUNo = TIU;
assert(TIUNo >= 1 && TIUNo <= TIUMAX);
ConsistName = ConName;

LocalTransparencyOverride = false;

// LashUp must have a LashUpEngineList
assert((ConsistNo >= 101 && ptrLUEngineList != NULL) || (ConsistNo <= 100 && ptrLUEngineList == NULL));
LashUpEngineListVol = NULL;

if (ConsistNo <= ENGINEMAX) {	// Engines Factory Reset, 0-99
	// Normal Engine
    // no LashUpEngineList for regular engines
	Volume_Controls_Label->Caption = "Sounds for Engine " + String(ConsistNo) + " - " +
		ConsistName + " - TIU " + String(TIUNo);
    } else {
    if (ConsistNo >= LASHUPMIN) {	// LashUps
	    // LashUp Engines
    	LashUpEngineListVol = new char [LASHUP_ENGINE_LIST_LENGTH];
	    assert(LashUpEngineListVol != NULL);
	    assert(ptrLUEngineList[0] != '\0');
	    strncpy(LashUpEngineListVol, ptrLUEngineList, LASHUP_ENGINE_LIST_LENGTH);	// LashUp information
		Volume_Controls_Label->Caption = "Sounds for LashUp " + String(ConsistNo - 100) + " - " +
			ConsistName + " - TIU " + String(TIUNo);
        } else {
        // All Engine Operation 100
		Volume_Controls_Label->Caption = "Sounds for All Engine Operation - TIU " + String(TIUNo);
        }
    }
this->Caption = Volume_Controls_Label->Caption;

// pointer to Quick Control (if any)
ptrQuick_MasterVolume = NULL;
// pointer to Operations Sound Switch (always there)
ptrOp_SoundsSwitch = NULL;

// set pointers when creating the Operations window if the Quick Control window
// is displayed and if our TIU and Engine Number are in there
for (int iPage = 1; iPage <= QUICKPAGEMAX ; iPage++) { // Quick Controls 1-5
    if (ptrQuick[iPage - 1]) {
        TQuickCtrl * pQuick = ptrQuick[iPage - 1];
        int iCnt = pQuick->TotalCtrlCount;
        for (int index = 0 ; index < iCnt ; index++) {	
            if (pQuick->EngNumberContainer[index]->Text.ToInt() == ConsistNo) {
                ptrQuick_MasterVolume = pQuick->VolumeSliderContainer[index];
                break;                                                                   	
                }
            }
        }
    }

// set pointer to Sound Switch on Operations Window Sound Switch
ptrOp_SoundsSwitch = ptrOp[ConsistNo]->SoundsSwitch;

LastSentMasterVolumeValue = 0;

Show();  	      		// Same as setting Visible to true
if (DEBUGGING) {		// debug modes
    IdleSoundKnob->ScaleSettings->Steps = 10;	// all Idle Sounds
    IdleSoundKnob->ScaleSettings->SubSteps = 1;
    IdleSoundKnob->SignalSettings->DigitalTo = 255;
    IdleSoundKnob->SignalSettings->ValueTo = 255;
    IdleSoundKnob->ColorFocus = clLime;
    IdleSoundKnob->ChangeLarge = 20;
	}
if (Setup->Allow255Sounds->Checked) {		// all Engine Sounds
    EngineSoundKnob->ScaleSettings->Steps = 10;
    EngineSoundKnob->ScaleSettings->SubSteps = 1;
    EngineSoundKnob->SignalSettings->DigitalTo = 255;
    EngineSoundKnob->SignalSettings->ValueTo = 255;
    EngineSoundKnob->ColorFocus = clLime;
    EngineSoundKnob->ChangeLarge = 20;
    }

MasterVolume->SetFocus();	// focus to Master Volume slider

// Convert Engine Number into chars for the command
int HeadEngineNo;
if (ConsistNo == ALLENGINE) return;	// do nothing for All Engine Operation
if (ConsistNo >= LASHUPMIN) {
#ifdef OLDWAY
	// use first engine in LashUp list (its a DCS engine # in the lashup list so we must subtract one)
    // This DCS V5 encoding is no longer supported
	// LeadEngineNo = (LashUpEngineListVol[1] & 0x7F) - 1;
#endif
    // New use of GetLashUpEngine tested ok
    HeadEngineNo = MainForm->GetLashUpEngine(LashUpEngineListVol, 0);  // get head engine
    //  ---------------------------------------------------------------------
    } else {
    HeadEngineNo = ConsistNo;	// use engine number
    }

// Setup last master volume setting from head engine
LastSentMasterVolumeValue = SavedMasterVolumeValue = MainForm->EngineData->Cells[VOL_MASTER][HeadEngineNo].ToInt();
//
//
// Errors in Engines
//
// There are errors in the responses to the Ixxx command from some engines.
// So far, it has been missing hotkey flags (unless they are there and I don't process them
// correctly) or extra hotkey flags.
//
// This should at some point be moved into an external file where it can be
// modified by users
//
// NO ERRORS found yet in Sound control
//
if (Setup->Radio_COM_Mode && Setup->TXOnlyComm->Checked) {	//  In TX Only Mode, we can't get softkeys
	if (DEBUGGING) DB->Debug_Print("Hotkey : No Sound Hotkeys set - TX Only Mode", clBlue);
    } else {
    //
    // Activate Hot Keys per engine response
    //
    // NOTE: EngineNo in the command does not seem to matter
    // NOTE: the Engine Number as digits in the command do matter
	//
	byte VbyteResp[CMDMAX];
    byte byteResp[CMDMAX]; 	// Array for decoded binary response
    int iTIUCount;			// integer for response count
    int respCode;			// integer for comm response code
	char Command[10];
	(void)snprintf(Command, sizeof Command, "I%d", HeadEngineNo + 1);   // Volumne Controls Constructor
	bool bretv = ptrTIUCommThread->Send_String_FIFO(HeadEngineNo, Command, TIUNo, RemoteNo, 173, I_WAIT, NO_RECORD, NULL, &respCode, &iTIUCount, VbyteResp);
	if (!bretv) { // retry once
    	if (DEBUGGING) DB->Debug_Print("TVolume() Retrying " + String(Command));
    	bretv = ptrTIUCommThread->Send_String_FIFO(HeadEngineNo, Command, TIUNo, RemoteNo, 173, I_WAIT, NO_RECORD, NULL, &respCode, &iTIUCount, VbyteResp);
        }
	// Get the info about what switches the sound system supports
	if (bretv && (iTIUCount >= 173)) {
		if (Setup->Radio_COM_Mode->Checked) {     // in Radio mode, we get 173 bytes if an engine is found
            // Handle Radio COM
			// Got a good response
            // Response to Ixxx command:
            // 996AA5A65A5A555556A5555A55A566595999559A655556A656AA6566656A6565656566566566656A656A6669AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA555555555555555555555555555555555555555556556595AA6A55955555555555555555555556559A99696569A555555A5AA9A955555555555555555556 (0x00)
			//   9D  F1  0F  00  52  05  50  26  4C  45  20  53  57  31  35  30  30  23  31  35  35  36  FF  FF  FF  FF  FF  FF  FF  FF  FF  FF  FF  FF  FF  FF  FF  FF  FF  FF  FF  FF  FF  FF  FF  FF  FF  FF  FF  FF  FF  FF  FF  FF  00  00  00  00  00  00  00  00  00  00  02  60  BF  40  00  00  00  00  00  02  CE  38  78  00  0F  FC  00  00  00  00  01
			//                     ^^^^ is 0x00/0x10 for Steam, 0x05/0x85 for Diesel, and 0x25 Gas/Electric
			// see spreadsheet named "Command ('Ixxx') Response.xls"  for decoding details
            // No format change necessary
			memcpy(byteResp, VbyteResp, CMDMAX);
            // byteResp[] has the decoded response string
            }
		if (Setup->WTIU_WiFi_Mode->Checked) {    // in WTIU mode, we don't get an exact number of bytes because blanks at end of engine name are suppressed

			// The string starts with "I1"
            // Handle WTIU ASCII Response I10:05;P&LE SW1500#1556;FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,00,00,00,00,00,00,00,00,00,00,02,60,BF,40,00,00,00,00,00,02,CE,38,78,00,0F,FC,00,00,00,00,00,63;01 okay
            // Got a good response
            //  Find last ';'
            // parse out the header info
            byte rawbyte[CMDMAX];
			int retv = sscanf(VbyteResp, "I%*d:%*x;%*[^;];%[^;]", rawbyte);
            assert(retv == 1);
            // Convert each character hex value ("FF") to hex (0xff)and put into byteResp
            byteCharacterStringTobyteHexString(rawbyte, &byteResp[22], 64);
            // byteResp[] has the decoded response string
			}

        // Get the hotkey info
        // that info to enable or disable certain switches and set their names
        if (DEBUGGING) DB->Debug_Print("Hotkey Begin: ----------(Sounds) ----------");
        // Beginning of response to Ixxx command:
        // 99A55AA95A5A555556A555555596665956A665556565695A......
        //   D8  5E  0F  00  52  00  41  26  53  20  30  2D
        //                     ^^^^ is 0 for steam, 5 for Diesel and Gas/Electric
        // see spreadsheet named "Command ('Ixxx') Response.xls"  for decoding details
        //
        //-------------------------------------------------------
        const int Byte01Value = byteResp[53 + 1];
        const int Coors = 0x03 & Byte01Value;
        if (!Coors) {
            CoorsGroupBox->Enabled = false;	// disable the controls
            CoorsGroupBox->Visible = false;	// disable the controls
            } else {
            if (DEBUGGING) DB->Debug_Print("Hotkey : Coors");
            }
        //-------------------------------------------------------
        const int Byte02Value = byteResp[53 + 2];
        const int Unknown = Byte02Value & 0x80;
        if (Unknown) {
            if (DEBUGGING) DB->Debug_Print("Hotkey : Unknown Coors");
            }
        //-------------------------------------------------------
        const int Byte10Value = byteResp[53 + 10];
        const int Boiler = Byte10Value & 0x20;
        if (!Boiler) {
            BoilerGroupBox->Visible = false;	// disable the control
            BoilerGroupBox->Enabled = false;	// disable the control
            } else {
            if (DEBUGGING) DB->Debug_Print("Hotkey : Boiler Startup and Release");
            }
        //-------------------------------------------------------
        const int Byte21Value = byteResp[53 + 21];
        const int TrainWreck = 0x08 & Byte21Value;
        if (!TrainWreck) {
            STWButton->Enabled = false;	// disable the control
            STWButton->Visible = false;	// disable the control
            } else {
            if (DEBUGGING) DB->Debug_Print("Hotkey : Train Wreck");
            }

        //-------------------------------------------------------
        const int Byte22Value = byteResp[53 + 22];
        const int CouplerSlack = 0x08 & Byte22Value;
        if (!CouplerSlack) {
            SCSButton->Enabled = false;	// disable the control
            SCSButton->Visible = false;	// disable the control
            } else {
            if (DEBUGGING) DB->Debug_Print("Hotkey : Coupler Slack");
            }
        const int CouplerClose = 0x10 & Byte22Value;	// SCC
        if (!CouplerClose) {
            SCCButton->Enabled = false;	// disable the control
            SCCButton->Visible = false;	// disable the control
            } else {
            if (DEBUGGING) DB->Debug_Print("Hotkey : Coupler Close");
            }
        if (DEBUGGING) DB->Debug_Print("Hotkey End: ----------(Sounds) ----------");
        } else {
        // Engine number not there, no added hotkey switches will be shown
        if (DEBUGGING) DB->Debug_Print("Hotkey : No Sound Control Hotkeys set - \"I\" Command Failed", clRed);
        }
    }
}
//---------------------------------------------------------------------------
__fastcall TVolume::~TVolume(void)
{
// pointer to Quick Control (if any)
ptrQuick_MasterVolume = NULL;
// pointer to Operations Sound Switch (always there)
ptrOp_SoundsSwitch = NULL;
//
if (LashUpEngineListVol != NULL) {
	delete[] LashUpEngineListVol;
    LashUpEngineListVol = NULL;
    }
//assert(Volume == NULL); 	// this form must never be auto created
}
//---------------------------------------------------------------------------

void __fastcall TVolume::EngineSoundOnSwitchStatusChanged(TObject *Sender)
{
// Sounds on or off
//
// Note: the sound on/off button on the App, turns off the sound by
// setting V1 and v2 to zero. In RTC, I set V0 to zero.
//
if (EngineSoundOnSwitch->Checked) {
	// Sounds on
    MasterVolume->Value = SavedMasterVolumeValue;	// this will cause a v0 command to be sent
    } else {
    // set volume to Zero
   	// Sounds OFF - mute
    SavedMasterVolumeValue = MasterVolume->Value;
    MasterVolume->Value = 0;	// this will cause a v0 command to be sent
    }

// reflect the change on to any open Quick Controls window
if (ptrQuick_MasterVolume) {	// MasterVolume : TAbVSlider
	EventOnValueChange = ptrQuick_MasterVolume->OnValueChange;
    ptrQuick_MasterVolume->OnValueChange = NULL;
	ptrQuick_MasterVolume->Value = LastSentMasterVolumeValue;
    ptrQuick_MasterVolume->OnValueChange = EventOnValueChange;
    }

// reflect the change on the Operations window which must be created for this window to exist
if (ptrOp_SoundsSwitch) {	// SoundsSwitch : TAbRockerSwitch
    EventOnStatusChanged = ptrOp_SoundsSwitch->OnStatusChanged;
    ptrOp_SoundsSwitch->OnStatusChanged = NULL;
    ptrOp_SoundsSwitch->Checked = EngineSoundOnSwitch->Checked;
    ptrOp_SoundsSwitch->OnStatusChanged = EventOnStatusChanged;
	}
}
//---------------------------------------------------------------------------

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

void __fastcall TVolume::EngineSoundButtonClick(TObject *Sender)
{
// Play Engine Sound
PlayEngineSound();
}
//---------------------------------------------------------------------------

void __fastcall TVolume::EngineSoundKnobKeyDown(TObject *Sender,
      WORD &Key, TShiftState Shift)
{
// if ENTER is pressed, play the sound
if (Key == VK_RETURN) {
	PlayEngineSound();
    }
}
//---------------------------------------------------------------------------

void TVolume::PlayEngineSound()
{
// Play Engine Sound
// Map 1 on control to 177 in the command unless Allow255Sounds is set
// Send sound command based on value of knob
int Mag;
if (Setup->Allow255Sounds->Checked)
	Mag = EngineSoundKnob->Value;	// in Debug send actual value of knob
    else
	Mag = EngineSoundKnob->Value + 176;

assert(Mag != 0);
char str[15];
snprintf(str, sizeof str, "n%d", Mag);
(void)ptrTIUCommThread->Send_String_FIFO(ConsistNo, str, TIUNo, RemoteNo, 11, SHORT_WAIT, RECORD_OK, LashUpEngineListVol);
}
//---------------------------------------------------------------------------

void TVolume::PlayIdleSound()
{
// Play Idle Sound
// Map 1 on control to 1 in the command. Usually 9 sounds, in debugging mode 255 sounds
// Send idle sound command based on value of knob
const int Mag = IdleSoundKnob->Value;
assert(Mag != 0);
// Convert value into chars
char str[15];
snprintf(str, sizeof str, "i%d", Mag);
(void)ptrTIUCommThread->Send_String_FIFO(ConsistNo, str, TIUNo, RemoteNo, 11, SHORT_WAIT, RECORD_OK, LashUpEngineListVol);
}
//---------------------------------------------------------------------------

void __fastcall TVolume::IdleSoundKnobKeyDown(TObject *Sender, WORD &Key,
      TShiftState Shift)
{
// if ENTER is pressed, play the sound
if (Key == VK_RETURN) {
    PlayIdleSound();
    }
}
//---------------------------------------------------------------------------

void __fastcall TVolume::IdleSoundButtonClick(TObject *Sender)
{
// Play Idle Sound
PlayIdleSound();	
}
//---------------------------------------------------------------------------
/*
There are three solutions to the "non rentrant" prolem.
1. Use a Critical Section to protect each command so two cannot be
	sent the same time. With this method, commands are queued up and
    sent as each one complete. This causes a lot of commands to be sent
    as the scroller is dragged.
2. Use the ->Tag property to protect each command to ignore commands
	until the previous command is complete. With this method, commands
    are just thrown away. At the end of each protection period, a check is
    made to be sure that the final value of the scroller matches the
    last command that is sent. If not, the routine is called one more time
    to send that last command to match everything up.
3. Disconnect the function from the event while the function is running.
	That is, set OnEvent to NULL while function is running and then restore
    the pointer as the function exits.

Now, a combination of all three is used

*/
//---------------------------------------------------------------------------

void __fastcall TVolume::MasterVolumeValueChange(TObject *Sender)
{
if (DEBUGGINGX(1)) DB->Debug_Print("MasterVolumeValueChange : " + String(MasterVolume->Value));
// Send speed command based on value of slider
// https://stackoverflow.com/questions/31649315/c-builder-button-click-function-reentry
LOCKEM()

//MasterVolume->Enabled = false;

// if the sound was set to zero and now it is being set to something higher,
// make sure that the SoundSwitches are set to sound on
if (LastSentMasterVolumeValue == 0 && MasterVolume->Value > 0) {
	// reflect the change on the Operations window which must be created for this window to exist
	if (ptrOp_SoundsSwitch) {	// SoundsSwitch : TAbRockerSwitch
	    if (!ptrOp_SoundsSwitch->Checked) { 	// if Off, turn the switch on
	    	EventOnStatusChanged = ptrOp_SoundsSwitch->OnStatusChanged;
		    ptrOp_SoundsSwitch->OnStatusChanged = NULL;
    		ptrOp_SoundsSwitch->Checked = true;
		    ptrOp_SoundsSwitch->OnStatusChanged = EventOnStatusChanged;
			}
        }
	// reflect the change on this window's Sound Switch
    if (!EngineSoundOnSwitch->Checked) { 	// if Off, turn the switch on
   		EventOnStatusChanged = EngineSoundOnSwitch->OnStatusChanged;
    	EngineSoundOnSwitch->OnStatusChanged = NULL;
   		EngineSoundOnSwitch->Checked = true;
    	EngineSoundOnSwitch->OnStatusChanged = EventOnStatusChanged;
        }
	}

LastSentMasterVolumeValue = MasterVolume->Value;	// save the current setting volume
//
// Send volume command based on value of slider
const int Vol = MasterVolume->Value;
// v0 = Master Volume, v1 = Engine Volume, v2 = Accent Volume, v3 = Horn Volume, v4 = Bell Volume
char str[15];
snprintf(str, sizeof str, "v0%03d", Vol);
(void) ptrTIUCommThread->Send_String_FIFO(ConsistNo, str, TIUNo, RemoteNo, 11, SHORT_WAIT, RECORD_OK, LashUpEngineListVol);
//

// reflect the change on to any open Quick Controls window
if (ptrQuick_MasterVolume) {
	EventOnValueChange = ptrQuick_MasterVolume->OnValueChange;
    ptrQuick_MasterVolume->OnValueChange = NULL;
	ptrQuick_MasterVolume->Value = LastSentMasterVolumeValue;
    ptrQuick_MasterVolume->OnValueChange = EventOnValueChange;
    }

//MasterVolume->Enabled = true;
//
#ifdef NOTNEEDED
// Check for slider movement while setting volume
if (LastSentMasterVolumeValue != MasterVolume->Value) {
    if (DEBUGGINGX(1)) DB->Debug_Print("MasterVolumeValueChange Volume Changed on Exit : " + String(MasterVolume->Value) +
    	" from " + String(LastSentMasterVolumeValue));
    MasterVolumeValueChangeX(Sender);	// call me again to process the changed master volume value
	}
#endif
}
//---------------------------------------------------------------------------

void __fastcall TVolume::EngineVolumeValueChange(TObject *Sender)
{
static float LastSentEngineVolumeValue;
if (DEBUGGINGX(1)) DB->Debug_Print("EngineVolumeValueChange : " + String(EngineVolume->Value));
// Send speed command based on value of slider
// https://stackoverflow.com/questions/31649315/c-builder-button-click-function-reentry
LOCKEM()

//EngineVolume->Enabled = false;

LastSentEngineVolumeValue = EngineVolume->Value;	// save the current setting volume
// Send volume command based on value of slider
const int Vol = EngineVolume->Value;
// v0 = Master Volume, v1 = Engine Volume, v2 = Accent Volume, v3 = Horn Volume, v4 = Bell Volume
char str[15];
snprintf(str, sizeof str, "v1%03d", Vol);
(void) ptrTIUCommThread->Send_String_FIFO(ConsistNo, str, TIUNo, RemoteNo, 11, SHORT_WAIT, RECORD_OK, LashUpEngineListVol);
//
//EngineVolume->Enabled = true;
//
#ifdef NOTNEEDED
// Check for slider movement while setting volume
if (LastSentEngineVolumeValue != EngineVolume->Value) {
    if (DEBUGGINGX(1)) DB->Debug_Print("EngineVolumeValueChange Volume Changed on Exit : " + String(EngineVolume->Value) +
    	" from " + String(LastSentEngineVolumeValue));
    EngineVolumeValueChangeX(Sender);	// call me again to process the changed engine volume value
	}
#endif
}
//---------------------------------------------------------------------------

void __fastcall TVolume::AccentVolumeValueChange(TObject *Sender)
{
static float LastSentAccentVolumeValue;
if (DEBUGGINGX(1)) DB->Debug_Print("AccentVolumeValueChange : " + String(AccentVolume->Value));
// Send speed command based on value of slider
// https://stackoverflow.com/questions/31649315/c-builder-button-click-function-reentry
LOCKEM()

//AccentVolume->Enabled = false;

LastSentAccentVolumeValue = AccentVolume->Value;	// save the current setting volume
// Send volume command based on value of slider
const int Vol = AccentVolume->Value;
// v0 = Master Volume, v1 = Engine Volume, v2 = Accent Volume, v3 = Horn Volume, v4 = Bell Volume
char str[15];
snprintf(str, sizeof str, "v2%03d", Vol);
(void) ptrTIUCommThread->Send_String_FIFO(ConsistNo, str, TIUNo, RemoteNo, 11, SHORT_WAIT, RECORD_OK, LashUpEngineListVol);
//
//AccentVolume->Enabled = true;
//
#ifdef NOTNEEDED
// Check for slider movement while setting volume
if (LastSentAccentVolumeValue != AccentVolume->Value) {
    if (DEBUGGINGX(1)) DB->Debug_Print("AccentVolumeValueChange Volume Changed on Exit : " + String(AccentVolume->Value) +
    	" from " + String(LastSentAccentVolumeValue));
    AccentVolumeValueChangeX(Sender);	// call me again to process the changed accent volume value
	}
#endif
}
//---------------------------------------------------------------------------

void __fastcall TVolume::HornVolumeValueChange(TObject *Sender)
{
static float LastSentHornVolumeValue;
if (DEBUGGINGX(1)) DB->Debug_Print("HornVolumeValueChange : " + String(HornWhistleVolume->Value));
// Send speed command based on value of slider
// https://stackoverflow.com/questions/31649315/c-builder-button-click-function-reentry
LOCKEM()

//HornWhistleVolume->Enabled = false;

LastSentHornVolumeValue = HornWhistleVolume->Value;	// save the current setting volume
// Send volume command based on value of slider
const int Vol = HornWhistleVolume->Value;
// v0 = Master Volume, v1 = Engine Volume, v2 = Accent Volume, v3 = Horn Volume, v4 = Bell Volume
char str[15];
snprintf(str, sizeof str, "v3%03d", Vol);
(void) ptrTIUCommThread->Send_String_FIFO(ConsistNo, str, TIUNo, RemoteNo, 11, SHORT_WAIT, RECORD_OK, LashUpEngineListVol);
//
//HornWhistleVolume->Enabled = true;
//
#ifdef NOTNEEDED
// Check for slider movement while setting volume
if (LastSentHornVolumeValue != HornWhistleVolume->Value) {
    if (DEBUGGINGX(1)) DB->Debug_Print("HornWhistleVolumeValueChange Volume Changed on Exit : " + String(HornWhistleVolume->Value) +
    	" from " + String(LastSentHornVolumeValue));
    HornVolumeValueChangeX(Sender);	// call me again to process the changed horn volume value
	}
#endif
}
//---------------------------------------------------------------------------

void __fastcall TVolume::BellVolumeValueChange(TObject *Sender)
{
static float LastSentBellVolumeValue;
if (DEBUGGINGX(1)) DB->Debug_Print("BellVolumeValueChange : " + String(BellVolume->Value));
// Send speed command based on value of slider
// https://stackoverflow.com/questions/31649315/c-builder-button-click-function-reentry
LOCKEM()

//BellVolume->Enabled = false;

LastSentBellVolumeValue = BellVolume->Value;	// save the current setting volume
// Send volume command based on value of slider
const int Vol = BellVolume->Value;
// v0 = Master Volume, v1 = Engine Volume, v2 = Accent Volume, v3 = Horn Volume, v4 = Bell Volume
char str[15];
snprintf(str, sizeof str, "v4%03d", Vol);
(void) ptrTIUCommThread->Send_String_FIFO(ConsistNo, str, TIUNo, RemoteNo, 11, SHORT_WAIT, RECORD_OK, LashUpEngineListVol);
//
//BellVolume->Enabled = true;
//
#ifdef NOTNEEDED
// Check for slider movement while setting volume
if (LastSentBellVolumeValue != BellVolume->Value) {
    if (DEBUGGINGX(1)) DB->Debug_Print("BellVolumeValueChange Volume Changed on Exit : " + String(BellVolume->Value) +
    	" from " + String(LastSentBellVolumeValue));
    BellVolumeValueChangeX(Sender);	// call me again to process the changed bell volume value
	}
#endif
}
//---------------------------------------------------------------------------

void __fastcall TVolume::BoilerStartupButtonClick(TObject *Sender)
{
// FBS Boiler Startup
(void)ptrTIUCommThread->Send_String_FIFO(ConsistNo, "w1000", TIUNo, RemoteNo, 11, SHORT_WAIT, RECORD_OK, LashUpEngineListVol);
}
//---------------------------------------------------------------------------

void __fastcall TVolume::BoilerReleaseButtonClick(TObject *Sender)
{
// FPR Boiler Release
(void)ptrTIUCommThread->Send_String_FIFO(ConsistNo, "w800", TIUNo, RemoteNo, 11, SHORT_WAIT, RECORD_OK, LashUpEngineListVol);
}
//---------------------------------------------------------------------------

void __fastcall TVolume::SCSButtonClick(TObject *Sender)
{
// SCS Coupler Slack Sound
(void)ptrTIUCommThread->Send_String_FIFO(ConsistNo, "h1", TIUNo, RemoteNo, 11, SHORT_WAIT, RECORD_OK, LashUpEngineListVol);
}
//---------------------------------------------------------------------------

void __fastcall TVolume::SCCButtonClick(TObject *Sender)
{
// SCC Coupler Crash Sound
(void)ptrTIUCommThread->Send_String_FIFO(ConsistNo, "w40", TIUNo, RemoteNo, 11, SHORT_WAIT, RECORD_OK, LashUpEngineListVol);
}
//---------------------------------------------------------------------------

void __fastcall TVolume::STWButtonClick(TObject *Sender)
{
// STW Train Wreck
(void)ptrTIUCommThread->Send_String_FIFO(ConsistNo, "u3", TIUNo, RemoteNo, 11, SHORT_WAIT, RECORD_OK, LashUpEngineListVol);
}
//---------------------------------------------------------------------------

void __fastcall TVolume::FormKeyDown(TObject *Sender, WORD &Key,
      TShiftState Shift)
{
// Make sure KeyPreview is true for this form
assert(KeyPreview);
switch (Key) {
    case VK_F1:
        MasterVolume->SetFocus();
        Key = 0;	// handled
    	break;
    case VK_F2:
        EngineVolume->SetFocus();
        Key = 0;	// handled
    	break;
    case VK_F3:
        AccentVolume->SetFocus();
        Key = 0;	// handled
    	break;
    case VK_F4:
        HornWhistleVolume->SetFocus();
        Key = 0;	// handled
    	break;
    case VK_F5:
        BellVolume->SetFocus();
        Key = 0;	// handled
    	break;
    case VK_F6:
        EngineSoundKnob->SetFocus();
        Key = 0;	// handled
    	break;
    case VK_F7:
        IdleSoundKnob->SetFocus();
        Key = 0;	// handled
    	break;
    // Pass these function keys to the MainForm.
    case VK_F11:
    case VK_F12:
		MainForm->FormKeyDown(Sender, Key, Shift);
        Key = 0;	// handled
		break;
	}
}
//---------------------------------------------------------------------------

void __fastcall TVolume::CoorsColorButtonMouseDown(TObject *Sender,
      TMouseButton Button, TShiftState Shift, int X, int Y)
{
// Handle the Coors Start and Stop Button
if (CoorsColorButton->Tag == 0) {
	// Turn On
	// Coors Delivery Start
	(void)ptrTIUCommThread->Send_String_FIFO(ConsistNo, "u3", TIUNo, RemoteNo, 11, SHORT_WAIT, RECORD_OK, LashUpEngineListVol);
    CoorsColorButton->Tag = 1;
    CoorsColorButton->Caption = "Delivery Stop";	// Toggle Button Caption
    } else {
    // Turn Off
	// Coors Delivery Stop
	(void)ptrTIUCommThread->Send_String_FIFO(ConsistNo, "u0", TIUNo, RemoteNo, 11, SHORT_WAIT, RECORD_OK, LashUpEngineListVol);
    CoorsColorButton->Tag = 0;
    CoorsColorButton->Caption = "Delivery Start";	// Toggle Button Caption
    }	
}
//---------------------------------------------------------------------------

void __fastcall TVolume::CoorsDoorsOpenClick(TObject *Sender)
{
(void)ptrTIUCommThread->Send_String_FIFO(ConsistNo, "n121", TIUNo, RemoteNo, 11, SHORT_WAIT, RECORD_OK, LashUpEngineListVol);
}
//---------------------------------------------------------------------------

void __fastcall TVolume::CoorsDoorsCloseClick(TObject *Sender)
{
(void)ptrTIUCommThread->Send_String_FIFO(ConsistNo, "n122", TIUNo, RemoteNo, 11, SHORT_WAIT, RECORD_OK, LashUpEngineListVol);
}
//---------------------------------------------------------------------------

void __fastcall TVolume::EngineSoundKnobDblClick(TObject *Sender)
{
PlayEngineSound();
}
//---------------------------------------------------------------------------

void __fastcall TVolume::IdleSoundKnobDblClick(TObject *Sender)
{
PlayIdleSound();
}
//---------------------------------------------------------------------------

void __fastcall TVolume::EngineSoundUpDownClick(TObject *Sender, TUDBtnType Button)
{
if (Button == Comctrls::btNext) {
    EngineSoundKnob->Value = EngineSoundKnob->Value + 1;
	}
if (Button == Comctrls::btPrev) {
    EngineSoundKnob->Value = EngineSoundKnob->Value - 1;
	}
}
//---------------------------------------------------------------------------

void __fastcall TVolume::IdleSoundUpDownClick(TObject *Sender,
      TUDBtnType Button)
{
if (Button == Comctrls::btNext) {
    IdleSoundKnob->Value = IdleSoundKnob->Value + 1;
	}
if (Button == Comctrls::btPrev) {
    IdleSoundKnob->Value = IdleSoundKnob->Value - 1;
	}
}
//---------------------------------------------------------------------------

void __fastcall TVolume::OpControlsButtonClick(TObject *Sender)
{
if (ConsistNo <= ALLENGINE) { // Engine
	// Send CM_OPCONTROL message to MainForm which will reopen the Operations window
	(void) PostMessage(ParentHandle, CM_OPCONTROL, TIUNo, ConsistNo);
    }
if (ConsistNo >= LASHUPMIN) { // LashUp
	// Send CM_ LASHUPSTARTUP message to MainForm which will issue the startup command
	// Pointer to LashUpEngineListVol may go out of scope if PostMessage is used
	(void) SendMessage(ParentHandle, CM_LASHUPSTARTUP, (unsigned int) LashUpEngineListVol, (TIUNo << 8) | ConsistNo);
										// TIUNo is 1  to 5, ConsistNo is 101 to 120
    }
}
//---------------------------------------------------------------------------

void __fastcall TVolume::SoundStopButtonClick(TObject *Sender)
{
// This does not stop the nxxx repeating sound
// stop whistle : -2  : FFFD one's complement
(void)ptrTIUCommThread->Send_String_FIFO(ConsistNo, "bFFFD", TIUNo, RemoteNo, 11, SHORT_WAIT, RECORD_OK, LashUpEngineListVol);
}
//---------------------------------------------------------------------------

void __fastcall TVolume::IdleStopButtonClick(TObject *Sender)
{
// This does not stop the nxxx repeating sound
// stop whistle : -2  : FFFD one's complement
(void)ptrTIUCommThread->Send_String_FIFO(ConsistNo, "bFFFD", TIUNo, RemoteNo, 11, SHORT_WAIT, RECORD_OK, LashUpEngineListVol);
}
//---------------------------------------------------------------------------

void __fastcall TVolume::CopyrightClick(TObject *Sender)
{
if (DEBUGGING) {
    // put the Volume Controls Window into Debug mode
    IdleSoundKnob->ScaleSettings->Steps = 10;	// all Idle Sounds
    IdleSoundKnob->ScaleSettings->SubSteps = 1;
    IdleSoundKnob->SignalSettings->DigitalTo = 255;
    IdleSoundKnob->SignalSettings->ValueTo = 255;
    IdleSoundKnob->ColorFocus = clLime;
    IdleSoundKnob->ChangeLarge = 20;

    EngineSoundKnob->ScaleSettings->Steps = 10;
    EngineSoundKnob->ScaleSettings->SubSteps = 1;
    EngineSoundKnob->SignalSettings->DigitalTo = 255;
    EngineSoundKnob->SignalSettings->ValueTo = 255;
    EngineSoundKnob->ColorFocus = clLime;
    EngineSoundKnob->ChangeLarge = 20;
    } else {
    // 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;
        }
    }
}
//---------------------------------------------------------------------------
// Workaround for SetLayeredWindowAttributes() to make transparent windows
extern lpfnSetLayeredWindowAttributes m_pSetLayeredWindowAttributes;
//---------------------------------------------------------------------------

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


