/*
PROGRAM NAME	SysControl.dll

USED BY			BBC Academy Studio C installation

VERSION			1.01
DATE V1			15th September 2015
DATE V1.01		10th January 2018.		Improve House light switch control code
PROGRAMMER		Andy Woodhouse

TARGET SCREEN SIZE	1280 x 1024
PANEL SIZE - Menu Bar	 120 x 924
PANEL SIZE - Ops Area	1160 x 924

Overview
========
SysControl is a BNCS version 4.5 panel used to control the Studio C rehearse/transmit state,
the house light state, and recall settings in the multiviewer.

Using Rehearse and Transmit buttons on the control panel the user can change the studio state between three conditions :

State		Properties
=====		==========
Off			Comms System disabled, blue lights off, red lights off.
Rehearse	Comms System enabled, blue lights on, red lights off.
Transmit	Comms System enabled, blue lights off, red lights on.

Initial
State		Action					New State
========	======					=========
Off			Push Rehearse button	Rehearse
Off			Push Transmit button	Transmit
Rehearse	Push Rehearse button	Off
Rehearse	Push Transmit button	Transmit
Transmit	Push Rehearse button	Rehearse

The control of the necessary relays is implemented by an automatic panel.The messaging between the SysControl panel
and the automatic via a slot in the "Tally Infodriver" (BNCS Driver 661).The allowed values and their
meanings are :

Value	State
=====	=====
0		Off
1		Rehearse
2		Transmit

There are three more slots in the "Tally Infodriver" that are used to drive indicators on this panel.Each slot indicates
which gallery is the controlling agency for the associated area.The control values are :

Value	Meaning
=====	=======
0		Area control is unallocated
1		Area is controlled by Gallery A
2		Area is controlled by Gallery C

The infodriver slots that use the above codes and the associated areas are :

Slot	Area
====	====
4		Studio A and B floor(also red and blue lights in studio galleries)
5		Studio C floor
6		Studio C News Studio

Setting the area allocations is done by another panel.

The other control feature that is enabled in the panel is switching the colour bars and stereo audio line - up tone to line output.

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Lighting Controls
=================
There are several light control elements that can be switched by the SysControl function. These elements include the studio floor lights,
the firelane lights, the mini-studio lights, the gallery lights, and potentially Studio A Floor lights and Studio A firelane lights.

The lights have multiple points of control - including several conventional switches. The control relays need to be toggled to their
opposite state to effect a change of state, but we have feedback from the lighting power system to tell us the currrent power output state.
The light control relays are switched by outputs of the BNCS GPI output crate, and the status is returned by an input to the same crate.
The state of the GPI contacts is available to this panel code by using standard revertive processing.

To illustrate the process, let us consider a request to switch off the houselights. This has two variables hl_status and hl_command.
If hl_status = false (0) the lighting is currently off. hl_command could be in either state.

Houselights off button pressed -> button processing code for the button
{
	if (hl_status == 0) return;	// Already off, nothing to do.

	// Toggle the value of hl_command and output new state
	hl_command = (hl_command > 0) ? 0 : 1;	// toggle the value of hl_command
	gpiSwitch(gpi_device, ((hl_command == 0) ? false : true), hl_switch_id);
	timerStart(HL_OFF_TIMER, 500);		// Start timeout counter
}

// Timer event code

switch(id):
{
	case HL_Off_Timer:
		timerStop(HL_OFF_TIMER);		// Stop the timer
		if (hl_state == 0) return ;		// Now have the correct Off state
		hl_command = (hl_command > 0) ? 0 : 1;	// toggle the value of hl_command to send to relay
		gpiSwitch(gpi_device, ((hl_command == 0) ? false : true), hl_switch_id);
		break;
}

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Multiviewer Layout Recall
=========================
Multiviewer layouts are created using the X-Edit tool and saved in the multiviewer. Once saved, an event can be declared
that causes the layout to be recalled. This can be any chosen event cause such as a GPI input close, or a Tally light activating.

As there are many unused tallies on the serial link for Studio C, this is the chosen event trigger for this installation. In the
Kaleido-X, a TSL tally is chosen to be the trigger for the new layout, and the event definition inserted by the X-Edit. Once the
TSL address for the triger is chosen, the entry in config/stc_mv_layout.xml needs to be edited to enable the new layout to be known
to BNCS. Once the xml definition is in place, a running panel can be forced to relaod the settings into active memory using a button
on the mulitiviewer panel.

To implement the recall, the active button is used to get the TSL ID from the data loaded from the xml file. The appropriate tally bit
is then set active and a timer triggered. when the timer fires, it resets the taly bit to off, ready for any subsequent use.


*/


#include <windows.h>
#include <stdio.h>
#include <bncs_string.h>
#include <bncs_config.h>
#include "SysControl.h"

#define MENU_STRIP	20
#define HOMEPAGE 1
#define RXTX 2
#define HOUSELIGHTS 3
#define MULTIVIEWER 4

#define MENU_UI	"Home_Menu.bncs_ui"
#define MENU_PAGE_UI "Home_Page.bncs_ui"
#define RXTX_NAME "TX_Control.bncs_ui"
#define HOUSELIGHTS_NAME "HouseLights.bncs_ui"
#define MVIEW_NAME "Multiviewer.bncs_ui"

#define STCTXSTAT "StC TX State"
#define STCUMDDRV "StC UMD Driver"
#define VIDEOTOLINE "StC Studio Out"

// Definitions for GPI controls used by lighting control operation. These definitions match entries in
// instances.xml so that panels may monitor the status of the connections.
// GPI Outputs
#define STCHOUSELIGHT_CNTL 13		// Control contact for Studio C floor houselighting
#define STCFIRELANE_CNTL 14			// Control contact for Studio C Firelane
#define STCFIRELANE_WARNING 15		// Control contact to drive an alert about the firelane lights being switched off
#define STCGAL_MON_CNTL 16			// Control contact for Studio C gallery lighting control - monitor side of production desk
#define STCGAL_APPS_CNTL 17			// Control contact for Studio C gallery lighting control - apps room side of production desk
#define STCMINI_HOUSELIGHT 18		// Control contact for Studio C mini-studio houselight control
#define STCMINI_PRODLIGHT1 19		// Control contact for Studio C mini-gallery production lighting
#define STCMINI_PRODLIGHT2 20		// Control contact for Studio C mini-gallery production lighting
// GPI Inputs
#define STCHOUSELIGHT_STATUS 39		// Input from houselighting power to show current state of houselights
#define STCFIRELANE_STATUS 40		// Input from firelane power to show curent state of the firelane lighting
#define STCMINIHOUSE_STATUS 41		// Input from mini-studio houselighting power to show current state of mini-studio houselights

// Timer IDs used.
#define HL_OFF_TIMER	1		// Timer used by StC Houselights off code
#define HL_ON_TIMER		2		// Timer used by StC Houselights on code
#define FL_OFF_TIMER	3		// Timer used by Firelane lights off code
#define FL_ON_TIMER		4		// Timer used by Firelane Lights on code
#define MS_HL_OFF_TIMER	5		// Timer used by StC Mini-studio Houselights off code
#define MS_HL_ON_TIMER	6		// Timer used by StC Mini-Studio Houselights on code
#define MV_RECALL_TIMER	7		// Timer used to form pulse for multiviewer layout recall

#define LGT_TIMEOUT	800			// Number of milliseconds after which we check that the desired status has been set.
#define MVPULSEDUR 500			// Duration of multiviewer recall activation pulse.

// Statesheet name definitions
#define MENU_SELECTED "WNSourceSelected"
#define MENU_DESELECTED	"WNSourceDeselected"

// Define the value indications in the area control slots
#define STATE_OFF 0
#define STATE_REH 1
#define STATE_TX 2

#define MAX_MV_CONFIG 20		// Maximum number of multiviewer configurations
#define MVDESELECTED "WNSourceDeselected"
#define MVSELECTED "WNSourceSelected"
#define TSLTALLYSTART 1000

// Make our class visible to the outside world
EXPORT_BNCS_SCRIPT(SysControl)


// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// Constructor - equivalent to ApplCore STARTUP
SysControl::SysControl(bncs_client_callback *parent, const char *path) : bncs_script_helper(parent, path)
{
	// Initialise global variables
	for (int i = 0; i <= MAX_MV_CONFIG; i++) tslSlots[i] = 0;		// Clear the array of cue ids used for preset recall.

	mvSelectedBtn = 0;						// No active selections on the multiviewer panel.
	txStatus = 0;							// Will be updated by the poll of the status infodriver
	txStatusDevice = 0;
	txStatusSlot = 0;

	stc_gpi_drv1 = 0;						// GPI driver ID for Studio C
	hl_status = 0;							// Studio C Houselight status 0=Off, 1 = On
	hl_command = 0;							// Studio C Houselights command value.
	fl_status = 0;							// Studio C Firelane status
	fl_command = 0;							// Studio C firelane relay command
	gl_mon_command = 0;						// Studio C Gallery houselight control monitor wall side 
	gl_apps_command = 0;					// Studio C Gallery houselight control apps room side 
	ms_hl_status = 0;						// Studio C mini-studio houselight status
	ms_hl_command = 0;						// Studio C mini-studio houselight command

	umd_driver = 0;							// BNCS driver ID for the TSL UMD driver ID
	sdi_driver_id = 0;							// Driver to monitor for video to line signal
	sdi_line_out = 0;							// Holds the line output destination read from instances.xml

	// Initialise the panels ready to take display data
	panelInit(MENU_STRIP, MENU_UI);
	panelInit(HOMEPAGE, MENU_PAGE_UI);
	panelInit(RXTX, RXTX_NAME);
	panelInit(HOUSELIGHTS, HOUSELIGHTS_NAME);
	panelInit(MULTIVIEWER, MVIEW_NAME);

	// Set up the displays and associated data
	// ++++++++++
	textPut("statesheet", MENU_SELECTED, MENU_STRIP, "Page_1");		// Highlight the homepanel menu button
	currentPage = HOMEPAGE;

	// Set up the workstation data on the home page.
	// ++++++++++
	bncs_string wsIdentity = bncs_string("Workstation %1").arg(workstation());	// Start the id string	
	bncs_string tokenId = bncs_string("workstations.%1").arg(workstation());	// Prepare to access workstations.xml for the panel name
	bncs_config wsCfg(tokenId);													// Create a pointer to the workstation data
	wsIdentity += " - ";														// Add the seperator
	wsIdentity += wsCfg.attr("name");											// Get the name of the workstation
	textPut("text", wsIdentity, HOMEPAGE, "WS_ID");								// Show the name on the home screen

	// Set up the Rehearse/Tx panel data
	// ++++++++++
	getDev(STCTXSTAT, &txStatusDevice, &txStatusSlot);
	getDev(STCUMDDRV, &umd_driver);
	getDev(VIDEOTOLINE, &sdi_driver_id, &sdi_line_out);

	// ++++++++++ Houselighting panel
	// At time of panel creation, Studio A does not have remote control of houselights, So diable the controls.
	controlDisable(HOUSELIGHTS, "StA_HL_Off");
	controlDisable(HOUSELIGHTS, "StA_HL_On");
	controlDisable(HOUSELIGHTS, "StA_FL_Off");
	controlDisable(HOUSELIGHTS, "StA_FL_On");
	controlDisable(HOUSELIGHTS, "StA_HL_Stat");
	controlDisable(HOUSELIGHTS, "StA_FL_Stat");
	
	// Get the device number of GPI outputs and inputs
	getDev("Studio C Floor House Lights", &stc_gpi_drv1);	
	
	// ++++++++++ Multiviewer panel
	loadMultiviewPresets();							// Load the names of the multiviewer presets, enable only controls with non-defaults nome
	controlDisable(MULTIVIEWER, "Confirm");			// Disable the confirm recall instruction
	controlDisable(MULTIVIEWER, "ClearSelection");	// Disable the cancel selection button
	layoutID = 0;
	layoutSelected = false;							// No multiviewer buttons are currently selected.
	recallTallyId = 0;								// Tally control slot in TSL driver for config racall

	// Show the two elements that make up the home panel.
	panelShow(MENU_STRIP, MENU_UI);
	panelShow(HOMEPAGE, MENU_PAGE_UI);

	// Register for revertives.
	if ((txStatusDevice != 0) && (txStatusSlot != 0)) infoRegister(txStatusDevice, txStatusSlot, txStatusSlot); // Slightly defensive programming
	if (stc_gpi_drv1 != 0)
	{
		gpiRegister(stc_gpi_drv1, STCHOUSELIGHT_CNTL, STCMINI_PRODLIGHT2);	// Register for GPI data from GPI output section of interface
		gpiRegister(stc_gpi_drv1, STCHOUSELIGHT_STATUS, STCMINIHOUSE_STATUS, true);	// Add registration for status inputs
	}
	if (sdi_driver_id != 0) routerRegister(sdi_driver_id, sdi_line_out, sdi_line_out);	// Monitor the video to line source

	// Poll for initial states
	if ((txStatusDevice !=0) && (txStatusSlot != 0)) infoPoll(txStatusDevice, txStatusSlot, txStatusSlot);
	if (stc_gpi_drv1 != 0)
	{
		// The logical code to issue here is two poll requests, one for the current output relay state
		// and one for the status probes. Looking at Captains log, only the first poll returned an answer.
		// So the poll below is a "Hack" with the request including GPI destination we do not need to know
		// but all come back in a single revertive, so no effect on network traffic.
		gpiPoll(stc_gpi_drv1, STCHOUSELIGHT_CNTL, STCMINIHOUSE_STATUS);
	}

	if (sdi_driver_id != 0) routerPoll(sdi_driver_id, sdi_line_out, sdi_line_out); // Poll video to line source

	// Disable the test signal to line as we still do not have access to the AES router in the sound desk....
	controlDisable(RXTX, "Bars");
	controlDisable(RXTX, "Tone");


}	// End of SysControl::SysControl(bncs_client_callback *parent, const char *path) : bncs_script_helper(parent, path)


// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// Destructor - equivalent to ApplCore CLOSEDOWN
SysControl::~SysControl()
{
	if (txStatusDevice != 0) infoUnregister(txStatusDevice);
	if (stc_gpi_drv1 != 0) gpiUnregister(stc_gpi_drv1);
	if (sdi_driver_id != 0) routerUnregister(sdi_driver_id);
}

// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// All button pushes and notifications come here
void SysControl::buttonCallback( buttonNotify *b )
{

	// Check for button pushes on the menu panel
	if (b->panel() == MENU_STRIP)
	{
		processMenuSelect(b);
		return;
	}

	if (b->panel() == RXTX)
	{
		processRXTXbuttons(b);
		return;
	}

	if (b->panel() == HOUSELIGHTS)
	{
		processHouselights(b);
		return;
	}

	if (b->panel() == MULTIVIEWER)
	{
		processMultiviewer(b);
		return;
	}
}


// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// All revertives come here
int SysControl::revertiveCallback( revertiveNotify * r )
{
	// Look for revertives from the tally status infodriver, TX status slot
	if ((r->device() == txStatusDevice) && (r->index() == txStatusSlot))
	{
		txStatus = r->sInfo().toInt();
		return 0;
	}

	// Check for revertive from GPI driver.
	if (r->device() == stc_gpi_drv1)
	{
		switch (r->index())
		{
		case STCHOUSELIGHT_CNTL:	// Studio C houselight control command value has changed
			hl_command = r->info();	// Note the new value
			break;

		case STCFIRELANE_CNTL:		// Studio C firelane control command value has changed
			fl_command = r->info();
			break;

		case STCFIRELANE_WARNING:	// Studio C Firelane warning alarm output has changed.
			// Not yet in use
			break;

		case STCGAL_MON_CNTL:		// Studio C Gallery houselight control at monitor side has changed
			gl_mon_command = r->info();
			break;

		case STCGAL_APPS_CNTL:		// Studio C Gallery houselight control at apps room side has changed
			gl_apps_command = r->info();
			break;

		case STCMINI_HOUSELIGHT:	// Studio C mini-studio houselight control has changed
			ms_hl_command = r->info();
			break;

		case STCMINI_PRODLIGHT1:	// Studio C mini-studio production lighting control 1 has changed
			break;

		case STCMINI_PRODLIGHT2:	// Studio C mini-studio production lighting control 2 has changed
			break;

		case STCHOUSELIGHT_STATUS:	// State of the houselighting has changed
			hl_status = r->info();
			textPut("colour.background", hl_status == 0 ? "black" : "white", HOUSELIGHTS, "StC_HL_Stat");
			break;

		case STCFIRELANE_STATUS:	// State of the fire lane lighting has changed
			fl_status = r->info();
			textPut("colour.background", fl_status == 0 ? "red" : "white", HOUSELIGHTS, "StC_FL_Stat");
			break;

		case STCMINIHOUSE_STATUS:	// State of the mini-studio houselighting has changed
			ms_hl_status = r->info();
			textPut("colour.background", ms_hl_status == 0 ? "black" : "white", HOUSELIGHTS, "StC_Mini_Stat");
			break;

		default:
			break;
		}
		return 0;
	}	// End of if( (r->device() == stc_gpi_drv1)

	if (r->device() == sdi_driver_id)
	{
		// Video to line source has changed. Record the new name on the status panel.
		textPut("text", r->sInfo(), RXTX, "Video_to_Line");
	}


	return 0;	// Default exit
}


// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// All database name changes come back here.
// No database dependancies in this maodule.
void SysControl::databaseCallback( revertiveNotify * r )
{
}


// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// All parent notifications come here - ie when this script is just one 
// component of another dialog then our host might want to tell us things
bncs_string SysControl::parentCallback( parentNotify *p )
{
	if( p->command() == "return" )
	{
		if( p->value() == "all" )
		{	// Persisting values for bncs_vis_ed
			bncs_stringlist sl;
			
			sl << bncs_string( "myParam=%1" ).arg( m_myParam );
			
			return sl.toString( '\n' );
		}

		else if( p->value() == "myParam" )
		{	// Specific value being asked for by a textGet
			return( bncs_string( "%1=%2" ).arg( p->value() ).arg( m_myParam ) );
		}

	}
	else if( p->command() == "instance" && p->value() != m_instance )
	{	// Our instance is being set/changed
		m_instance = p->value();
		//Do something instance-change related here
	}

	else if( p->command() == "myParam" )
	{	// Persisted value or 'Command' being set here
		m_myParam = p->value();
	}

	// ***** CONNECTIONS EVENTS HELPER LIST *****
	else if( p->command() == "_events" )
	{	// Helper-list of everything in this component generated by hostNotify's
		bncs_stringlist sl;

		sl << "notify=*";		
		
		return sl.toString( '\n' );
	}

	// ***** CONNECTIONS COMMANDS HELPER LIST *****
	else if( p->command() == "_commands" )
	{	// Helper-list of any commands/parameters you might want to set at run-time
		bncs_stringlist sl;
		
		sl << "myParam=[value]";
		
		return sl.toString( '\n' );
	}

	return "";

}	// End of SysControl::parentCallback( parentNotify *p )


// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// Timer events come here
void SysControl::timerCallback( int id )
{
	switch( id )
	{
	case HL_OFF_TIMER:
		timerStop(id);
		if (hl_status == 0) return;		// Desired houselight state acheived
		hl_command = (hl_command == 0) ? 1 : 0;	// Toggle command value
		gpiSwitch(stc_gpi_drv1, ((hl_command == 0) ? false : true), STCHOUSELIGHT_CNTL, true);
		break;

	case HL_ON_TIMER:
		timerStop(id);
		if (hl_status == 1) return;		// Desired houselight state acheived
		hl_command = (hl_command == 0) ? 1 : 0;	// Toggle command value
		gpiSwitch(stc_gpi_drv1, ((hl_command == 0) ? false : true), STCHOUSELIGHT_CNTL, true);
		break;

	case FL_OFF_TIMER:
		timerStop(id);
		if (fl_status == 0) return;		// Desired firelane lighting state acheived
		fl_command = (fl_command == 0) ? 1 : 0;	// Toggle the command state
		gpiSwitch(stc_gpi_drv1, ((fl_command == 0) ? false : true), STCFIRELANE_CNTL, true);
		break;

	case FL_ON_TIMER:
		timerStop(id);
		if (fl_status == 1) return;		// Desired firelane lighting state acheived
		fl_command = (fl_command == 0) ? 1 : 0;	// Toggle the command state
		gpiSwitch(stc_gpi_drv1, ((fl_command == 0) ? false : true), STCFIRELANE_CNTL, true);
		break;

	case MS_HL_OFF_TIMER:
		timerStop(id);
		if (ms_hl_status == 0) return;		// Desired firelane lighting state acheived
		ms_hl_command = (ms_hl_command == 0) ? 1 : 0;	// Toggle the command state
		gpiSwitch(stc_gpi_drv1, ((ms_hl_command == 0) ? false : true), STCMINI_HOUSELIGHT, true);
		break;

	case MS_HL_ON_TIMER:
		timerStop(id);
		if (ms_hl_status == 1) return;		// Desired firelane lighting state acheived
		ms_hl_command = (ms_hl_command == 0) ? 1 : 0;	// Toggle the command state
		gpiSwitch(stc_gpi_drv1, ((ms_hl_command == 0) ? false : true), STCMINI_HOUSELIGHT, true);
		break;

	case MV_RECALL_TIMER:
		timerStop(id);
		if (recallTallyId != 0) infoWrite(umd_driver, "0", recallTallyId, true);	// Clear the flag.
		break;

	default:	// Unhandled timer event
		timerStop(id);
		break;
	}

}	// End of SysControl::timerCallback( int id )


///////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////// Callbacks above - Methods below ///////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////


void SysControl::processMenuSelect(buttonNotify *b)
{
	// We already know that the user has pusehed a menu selector button. We need to check the name of the button
	// they pushed is "Page_XX", where XX will be a number.
	int newpage;

	if (b->id().startsWith("Page_"))
	{
		newpage = b->id().firstInt();

		if (currentPage != newpage)		// Is this a different page to the current selection?
		{
			// Change the displayed menu page
			panelRemove(currentPage);			// Remove the current selected page
			textPut("statesheet", MENU_DESELECTED, MENU_STRIP, bncs_string("Page_%1").arg(currentPage));	// Remove the highlight

			switch (newpage)
			{
			case HOMEPAGE:
				panelShow(HOMEPAGE, MENU_PAGE_UI);
				break;

			case RXTX:
				panelShow(RXTX, RXTX_NAME);
				break;

			case HOUSELIGHTS:
				panelShow(HOUSELIGHTS, HOUSELIGHTS_NAME);
				break;

			case MULTIVIEWER:
				panelShow(MULTIVIEWER, MVIEW_NAME);
				break;
			}

			currentPage = newpage;		// Remember the new page selection
			textPut("statesheet", MENU_SELECTED, MENU_STRIP, bncs_string("Page_%1").arg(currentPage));	// Remove the highlight

		}	// End of if (currentPage != newpage)
	}	// End of if (b->id().startsWith("Page_"))

	return;
}

void SysControl::processRXTXbuttons(buttonNotify *b)
{
	if (b->id() == "Rehearse")
	{
		switch (txStatus)
		{
		case STATE_OFF:		// Currently in "Off", go to rehearse state
			infoWrite(txStatusDevice, STATE_REH, txStatusSlot, true);
			break;

		case STATE_REH:		// Already at Rehearse state, go to Off
			infoWrite(txStatusDevice, STATE_OFF, txStatusSlot, true);
			break;

		case STATE_TX:
			infoWrite(txStatusDevice, STATE_REH, txStatusSlot, true);
			break;
		}
		return;
	}

	if (b->id() == "TX")
	{
		infoWrite(txStatusDevice, "2", txStatusSlot, true);
		return;
	}
}

void SysControl::processHouselights(buttonNotify *b)
{
	// Handle calls from buttons on the houselights panel.

	if (b->id() == "StC_HL_Off")
	{
		if (hl_status == 0) return;		// Already off - nothing to action.

		// Toggle the value of hl_command and output the new state
		hl_command = (hl_command > 0) ? 0 : 1;
		gpiSwitch(stc_gpi_drv1, ((hl_command == 0) ? false : true), STCHOUSELIGHT_CNTL, true);
		timerStart(HL_OFF_TIMER, LGT_TIMEOUT);
		return;
	}

	if (b->id() == "StC_HL_On")
	{
		if (hl_status == 1) return;

		// Toggle the value of hl_command and output the new state
		hl_command = (hl_command > 0) ? 0 : 1;
		gpiSwitch(stc_gpi_drv1, ((hl_command == 0) ? false : true), STCHOUSELIGHT_CNTL, true);
		timerStart(HL_ON_TIMER, LGT_TIMEOUT);
		return;
	}

	if (b->id() == "StC_FL_Off")
	{
		if (fl_status == 0) return;

		fl_command = (fl_command > 0) ? 0 : 1;
		gpiSwitch(stc_gpi_drv1, ((fl_command == 0) ? false : true), STCFIRELANE_CNTL, true);
		timerStart(FL_OFF_TIMER, LGT_TIMEOUT);
		return;
	}

	if (b->id() == "StC_FL_On")
	{
		if (fl_status == 1) return;

		fl_command = (fl_command > 0) ? 0 : 1;
		gpiSwitch(stc_gpi_drv1, ((fl_command == 0) ? false : true), STCFIRELANE_CNTL, true);
		timerStart(FL_ON_TIMER, LGT_TIMEOUT);
		return;
	}

	if (b->id() == "StCMS_HL_Off")
	{
		if (ms_hl_status == 0) return;		// Mini Studio houselights are already on. No action required.

		// Toggle the value of the command relay
		ms_hl_command = (ms_hl_command > 0) ? 0 : 1;
		gpiSwitch(stc_gpi_drv1, ((ms_hl_command == 0) ? false : true), STCMINI_HOUSELIGHT, true);
		timerStart(MS_HL_OFF_TIMER, LGT_TIMEOUT);				// Timeout checks lights have gone on
		return;
	}

	if (b->id() == "StCMS_HL_On")
	{
		if (ms_hl_status == 1) return;		// Mini Studio houselights are already off. No action required.

		// Toggle the value of the command relay
		ms_hl_command = (ms_hl_command > 0) ? 0 : 1;
		gpiSwitch(stc_gpi_drv1, ((ms_hl_command == 0) ? false : true), STCMINI_HOUSELIGHT, true);
		timerStart(MS_HL_ON_TIMER, LGT_TIMEOUT);				// Timeout checks lights have gone on
		return;
	}

	// Production Gallery lighting - Vision stack side
	if (b->id() == "StC_GHL_Stack")
	{
		gl_mon_command = (gl_mon_command > 0) ? 0 : 1;		// Toggle command
		gpiSwitch(stc_gpi_drv1, (gl_mon_command) ? 1 : 0, STCGAL_MON_CNTL, true);
		return;
	}


	// Production Gallery lighting - Apps room side
	if (b->id() == "StC_GHL_Apps")
	{
		gl_apps_command = (gl_apps_command > 0) ? 0 : 1;
		gpiSwitch(stc_gpi_drv1, (gl_apps_command) ? 1 : 0, STCGAL_APPS_CNTL, true);
		return;
	}

	// Production Gallery lighting - Toggle both
	if (b->id() == "StC_GHL_Both")
	{
		gl_mon_command = (gl_mon_command > 0) ? 0 : 1;		// Toggle command
		gpiSwitch(stc_gpi_drv1, (gl_mon_command) ? 1 : 0, STCGAL_MON_CNTL, true);

		gl_apps_command = (gl_apps_command > 0) ? 0 : 1;
		gpiSwitch(stc_gpi_drv1, (gl_apps_command) ? 1 : 0, STCGAL_APPS_CNTL, true);
		return; 
	}
}


// ************************************************************************************************
// function processMultiviewer(b) is called to process buttons on the multiviewer layouts recall
// panel.
void SysControl::processMultiviewer(buttonNotify *b)
{
	if (b->id().startsWith("Layout_"))
	{
		// User has pressed a layout recall button.
		if (layoutSelected)
		{
			// Clear down the previous selection
			textPut("statesheet", MVDESELECTED, MULTIVIEWER, bncs_string("Layout_%1").arg(layoutID));
		}
		layoutID = b->id().firstInt();		// Extract the number from the new selection
		
		textPut("statesheet", MVSELECTED, MULTIVIEWER, bncs_string("Layout_%1").arg(layoutID));		// Select the new button
		layoutSelected = true;				// Note that a layout has been selected for recall
		controlEnable(MULTIVIEWER, "ClearSelection");		// Enable the Cancel button
		// Now enable the Take button
		controlEnable(MULTIVIEWER, "Confirm");

		return;
	}

	if (b->id() == "ClearSelection")
	{
		// User wishes to cancel any selected recall button
		if (layoutSelected)
		{
			textPut("statesheet", MVDESELECTED, MULTIVIEWER, bncs_string("Layout_%1").arg(layoutID));
			layoutSelected = false;
			layoutID = 0;
			controlDisable(MULTIVIEWER, "ClearSelection");
			controlDisable(MULTIVIEWER, "Confirm");
		}
		return;
	}

	if (b->id() == "Confirm")
	{
		// Need to recall the config. this requires that we toggle the TSL Tally signal of the relevant address 
		// from 0 to 1 then back to zero. The latter operation uses a timer.
		if (layoutSelected)
		{
			recallTallyId = TSLTALLYSTART + tslSlots[layoutID];
			infoWrite(umd_driver, "1", recallTallyId, true);		// Assert the flag
			textPut("statesheet", MVDESELECTED, MULTIVIEWER, bncs_string("Layout_%1").arg(layoutID));	// Clear the selected buton
			layoutSelected = false;
			layoutID = 0;
			controlDisable(MULTIVIEWER, "ClearSelection");
			controlDisable(MULTIVIEWER, "Confirm");
			// Finally start a timer to ret the tally value to zero.
			timerStart(MV_RECALL_TIMER, MVPULSEDUR);
		}
		return;
	}

	if (b->id() == "Reload")
	{
		// User has requested a reload of the available configiurations
		if (layoutSelected)
		{
			textPut("statesheet", MVDESELECTED, MULTIVIEWER, bncs_string("Layout_%1").arg(layoutID));
			layoutSelected = false;
			layoutID = 0;
			controlDisable(MULTIVIEWER, "ClearSelection");
			controlDisable(MULTIVIEWER, "Confirm");
		}
		loadMultiviewPresets();

		return;
	}

}	// End of function processMultiviewer(b)


// ************************************************************************************************
// function loadMultiviewPresets() is called to load the names of the multiviewer presets into
// memory, and to enable/disable the selection buttons.
//
// The settings are found in a file "%CC_Root\%CC_CONFIG\config\stc_mv_layout.xml". The structure of the file is of the form:
//	<stc_mv_layout>
//		<set id = "gallery">
//			<cfg id = "MV_01" addr = "100" name = "Large News Operation - Audio meters" />
//			<cfg id = "MV_02" addr = "101" name = "Large News Operation" />
//				...
//			<cfg id = "MV_20" addr = "120" name = "Layout number 20" />
//		</set>
//	</stc_mv_layout>
//
// In the present implementation there are 20 layout presets available.
//
// This function uses class bncs_config to read the xml file. This function caches files that it opens, and this can mean
// that when an XML file has been updated, data is still read from the cache not from the updated file. This function
// therefore calls the clearCache function to force a clean read of teh file.
//
void SysControl::loadMultiviewPresets()
{
	int idx = 1;								// Counter used during file scan to limit the maximum number of lines read.
	bncs_string presetName;						// The name of the preset.
	bncs_config mvp("stc_mv_layout.gallery");	// Search to the head of the settings for the gallery set
	mvp.clearCache();							// Force the cache to be empty to force fresh read from file.

	while (mvp.isChildValid() && (idx <= MAX_MV_CONFIG))
	{
		tslSlots[idx] = mvp.childAttr("addr").toInt();
		presetName = mvp.childAttr("name");
		if (presetName != "")
		{
			// Not a blank name - so transfer to the button
			textPut("text", presetName, MULTIVIEWER, bncs_string("Layout_%1").arg(idx));
			controlEnable(MULTIVIEWER, bncs_string("Layout_%1").arg(idx));
		}
		else
		{
			// Found a blank name, so put a dummy label in the button.
			textPut("text", "- - -", MULTIVIEWER, bncs_string("Layout_%1").arg(idx));
			controlDisable(MULTIVIEWER, bncs_string("Layout_%1").arg(idx));
		}
		idx++;											// Increment the counter
		mvp.nextChild();								// Step to the next line entry
	}

}	// End of SysControl::loadMultiviewPresets()






