/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
MODULE NAME		PkgTakeUndo_3L.DLL

USED BY			BBC Academy Wood Norton BNCS installation.

VERSION			1.0
DATE			29th April 2017
PROGRAMMER		Andy Woodhouse

Overview
========
This module implements three operations for the Wood Norton packed router method:
  1) TAKE function, 
  2) UNDO last Take, 
  3) Selection Clear

Limits on tally data from the BNCS combiner led to implementing routing of multiple levels of
signals using a local package design that uses two extra device drivers - one holding the 
package names, the second holding a comma delimited list of sources and destinations.

An unused level of router is indicated by replacing the source or destination index by a minus
"-" character. The route request can thus monitor all signal levels and show the currently routed
sources on all levels.

Most areas at Wood Norton use a maximum of two signal levels (typically SDI video and analogue
audio). Studio C has three possible levels - SDI video, analogue audio, and AES audio.

This component is designed to support routing operations in Studio C, and the underlying source
and destinations are shown above and below the TAKE button. If this component is used in a 
router are that has only two levels, the component locks out any operations on layer 3. The
component can also work when a single layer package router is used. 

Similar component exist for two layer (PkgTakeUndo_2L) and single layer (PkgTakeUndo_1L)
package routing.

A panel designer using this component has to supply a number of data items through the visual
editor button properties of the script component that hosts this components. These parameters
include the name of an object in config/object_settings.xml that holds the properties of the 
individual levels of the router area. One of the settings in the object has the names of the
levels, and this is used in the level enable boxes. The designer also provides the names of 
statesheets that define he colours used on the TAKE button, and teh text size and colours on
the level mimic displays.

Whilst this component stores and reports the instance string, no use is made of the data as all
data is available in the named object.

The component expects to receive a package source and destination index via the connections
engine, and it can send a message to the host stating "selected=-1" which can be sent to source
and destination selectors to cause them to remove highlights.

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */

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

#define MAIN_PANEL	1
#define PANEL_NAME "PkgTakeUndo3L.bncs_ui"

#define LVL_ENABLE	"/images/tick.png"
#define LVL_DISABLE	"/images/cross.png"

#define BNCS_SRC_DB		0
#define BNCS_DEST_DB	1

#define TAKE_TIMER	1
#define TAKE_TIMER_PERIOD	1000
#define TAKE_TIMEOUT_COUNT	10

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


///////////////////////////////////////////////
// Constructor - equivalent to ApplCore STARTUP
PkgTakeUndo_3L::PkgTakeUndo_3L( bncs_client_callback * parent, const char * path ) : bncs_script_helper( parent, path )
{
	// Initialise our working variables
	m_instance = "";			// Defined for completeness - not actually used by this component.
	m_matrixobject = "";		// The object name that tells us the level drivers and crosspoint lists for thie chosen matrix set
	m_uselevels = "";			// Holds the level identification comma delimited string. Enables user to choose display order for routing elements
	m_srcpkg = "";
	m_destpkg = "";
	m_srcnamesdatabase = "";
	m_destnamesdatabase = "";
	m_takelow = "";
	m_takehigh = "";
	m_mimicstate_L1 = "";
	m_mimicstate_L2 = "";
	m_mimicstate_L3 = "";
	m_unusedleveltext = "";
	
	mySrcPkg = -1;			// Source package number to delected value
	myDestPkg = -1;			// Destination package number to deselected value
	srcNameDB = 0;			// Database number to use for source name lookup
	destNameDB = 1;			// Database number to use for destination name lookup
	PkgDriver = 0;			// BNCS Driver ID for package names
	XptDriver = 0;			// BNCS Driver ID for crosspoint lists
	myLevel1 = 0;			// Holds the user-set layer order data in our first level
	myLevel2 = 0;			// Holds the user set layer order data in our second level
	myLevel3 = 0;			// Holds the user set layer order data in our third level
	L1_matrixID = 0;		// BNCS Driver ID for level 1
	L2_matrixID = 0;		// BNCS Driver ID for level 2
	L3_matrixID = 0;		// BNCS Driver ID for level 3
	mySrcL1 = 0;			// Source number for level 1
	mySrcL2 = 0;			// Source number for level 2
	mySrcL3 = 0;			// Source number for level 3
	myDestL1 = 0;			// Destination number for level 1
	myDestL2 = 0;			// Destination number for level 2
	myDestL3 = 0;			// Destination number for level 3
	myUndoSrcL1 = 0;		// Level 1 source number for UNDO operation
	myUndoSrcL2 = 0;		// Level 2 source number for UNDO operation
	myUndoSrcL3 = 0;		// Level 3 source number for UNDO operation
	myUndoDestL1 = 0;		// Level 1 destination number for UNDO operation
	myUndoDestL2 = 0;		// Level 2 destination number for UNDO operation
	myUndoDestL3 = 0;		// Level 3 destination number for UNDO operation
	matrix_L1_Dests = 0;	// Holds the number of destinations in the matrix used for level 1
	matrix_L2_Dests = 0;	// Holds the number of destinations in the matrix used for level 2
	matrix_L3_Dests = 0;	// Holds the number of destinations in the matrix used for level 3
	TakeCounter = 0;		// Used in enabling TAKE button for finite period.

	Level_1_Enabled = true;	// Flag to control level 1 inclusion in TAKE
	Level_2_Enabled = true;	// Flag to control level 2 inclusion in TAKE
	Level_3_Enabled = true;	// Flag to control level 3 inclusion in TAKE
	TakeEnabled = false;	// Flag showing if Take Button is enabled
	UndoEnabled = false;	// Flag showing if Undo button is enabled
	takeTimerRunning = false;	// Timeout counter timer is inactive

	// Clear down the revertive storage.
	for (int idx = 0; idx < MAX_MATRIX_DEST; idx++)
	{
		revert_L1[idx] = -1;
		revert_L2[idx] = -1;
		revert_L3[idx] = -1;
	}

	// Show our panel.
	panelShow(MAIN_PANEL, PANEL_NAME);

	// Set Take and UNDO buttons to initial states
	if (TakeEnabled) controlEnable(MAIN_PANEL, "Take");
	else controlDisable(MAIN_PANEL, "Take");

	if (UndoEnabled) controlEnable(MAIN_PANEL, "Undo");
	else controlDisable(MAIN_PANEL, "Undo");

	// Set the level enable graphics (tick/cross) as appropriate.
	textPut("pixmap", Level_1_Enabled ? LVL_ENABLE : LVL_DISABLE, MAIN_PANEL, "Enable_L1");
	textPut("pixmap", Level_2_Enabled ? LVL_ENABLE : LVL_DISABLE, MAIN_PANEL, "Enable_L2");
	textPut("pixmap", Level_3_Enabled ? LVL_ENABLE : LVL_DISABLE, MAIN_PANEL, "Enable_L3");
}


////////////////////////////////////////////////
// Destructor - equivalent to ApplCore CLOSEDOWN
PkgTakeUndo_3L::~PkgTakeUndo_3L()
{
	// If there is a non-zero matrix ID we hold a revertive registration - remove before exit
	if (L1_matrixID > 0)
	{
		routerUnregister(L1_matrixID);
		L1_matrixID = 0;
	}

	if (L2_matrixID > 0)
	{
		routerUnregister(L2_matrixID);
		L2_matrixID = 0;
	}

	if (L3_matrixID > 0)
	{
		routerUnregister(L3_matrixID);
		L3_matrixID = 0;
	}

	if (takeTimerRunning) 
	{
		timerStop(TAKE_TIMER); 
		takeTimerRunning = false;
	}
}


////////////////////////////////////////////////
// All button pushes and notifications come here
void PkgTakeUndo_3L::buttonCallback(buttonNotify *b)
{
	if (b->panel() == MAIN_PANEL)
	{
		// Process our panel button pushes

		// ++++++++++++++++++++++++++++++++++++++++++++
		// Check for Clear/Deselect button pressed
		if (b->id() == "Clear")
		{
			// Disable TAKE button
			if (TakeEnabled) {disableTake();}

			// Disable UNDO button
			if (UndoEnabled) {disableUndo();}

			// Tidy our own data values
			clearSource();
			clearDestination();

			// Send message to host with our cleardown message
			hostNotify("selected=-1");
			return;
		}


		// ++++++++++++++++++++++++++++++++++++++++++++
		// Check for TAKE button pressed
		if (b->id() == "Take")
		{
			// Only get requests for TAKE action once valid source and destiona selected
			doTakeAction();
			return;
		}


		// ++++++++++++++++++++++++++++++++++++++++++++
		// Check for UNDO button pressed
		if (b->id() == "Undo")
		{
			// Only get requests for UNDO action after a TAKE and before any other actions
			doUndoAction();
			return;
		}


		// ++++++++++++++++++++++++++++++++++++++++++++
		// Check for level ENABLE/DISABLE buttons
		// Process level enable buttons for level 1
		if ((b->id() == "ID_L1") || (b->id() == "Enable_L1"))
		{
			Level_1_Enabled ^= true;	// Toggle the logic control for level 1
			textPut("pixmap", Level_1_Enabled ? LVL_ENABLE : LVL_DISABLE, MAIN_PANEL, "Enable_L1");
			return;
		}

		// Process level enable buttons for level 2
		if ((b->id() == "ID_L2") || (b->id() == "Enable_L2"))
		{
			Level_2_Enabled ^= true;	// Toggle the logic control for level 2
			textPut("pixmap", Level_2_Enabled ? LVL_ENABLE : LVL_DISABLE, MAIN_PANEL, "Enable_L2");
			return;
		}

		// Process level enable buttons for level 3
		if ((b->id() == "ID_L3") || (b->id() == "Enable_L3"))
		{
			Level_3_Enabled ^= true;	// Toggle the logic control for level 3
			textPut("pixmap", Level_3_Enabled ? LVL_ENABLE : LVL_DISABLE, MAIN_PANEL, "Enable_L3");
			return;
		}

	}	// End of if (b->panel() == MAIN_PANEL)

}	// End of void PkgTakeUndo_3L::buttonCallback(buttonNotify *b)


////////////////////////////////////////////////////////////////////////////////
// All revertives come here. We have up to 3 devices that are registered for
// revertive data. Their IDs are held in variables L1_matrixID, L2_matrixID and
// L3_matrixID.
int PkgTakeUndo_3L::revertiveCallback(revertiveNotify *r)
{
	if (r->device() == L1_matrixID)
	{
		// Revertive received from our current level 1 router. Save data to revertive store
		revert_L1[r->index()] = r->info();
		return 0;
	}

	if (r->device() == L2_matrixID)
	{
		// Revertive received from our current level 2 router
		revert_L2[r->index()] = r->info();
		return 0;
	}

	if (r->device() == L3_matrixID)
	{
		// Revertive received from our current level 3 router
		revert_L3[r->index()] = r->info();
		return 0;
	}

	return 0;
}


///////////////////////////////////////////
// All database name changes come back here
void PkgTakeUndo_3L::databaseCallback(revertiveNotify *r)
{
	// The probability of receiving a name whilst the TAKE or UNDO mimics are on show is very
	// small, but just in case it happens the code below will make updates to the mimics.

	if (r->device() == L1_matrixID)
	{
		// Does the database ID match our source database?
		if (r->database() == srcNameDB)
		{
			// Check if this matches the source number in the take or undo mimics.
			// If a match occurs, update the mimics.
			if (r->index() == mySrcL1)
			{
				textPut("text", r->sInfo(), MAIN_PANEL, "Src_L1");
			}
			if (r->index() == myUndoSrcL1)
			{
				textPut("text", r->sInfo(), MAIN_PANEL, "Src_UL1");
			}
		}

		// Does the database ID match our destination database?
		if (r->database() == destNameDB)
		{
			// Check if this matches the destination number in the take or undo mimics.
			// If a match occurs, update the mimics.
			if (r->index() == myDestL1)
			{
				textPut("text", r->sInfo(), MAIN_PANEL, "Dst_L1");
			}
			if (r->index() == myUndoDestL1)
			{
				textPut("text", r->sInfo(), MAIN_PANEL, "Dst_UL1");
			}
		}
	}	// End of if (r->device() == L1_matrixID)

	if (r->device() == L2_matrixID)
	{
		// Does the database ID match our source database?
		if (r->database() == srcNameDB)
		{
			// Check if this matches the source number in the take or undo mimics.
			// If a match occurs, update the mimics.
			if (r->index() == mySrcL2)
			{
				textPut("text", r->sInfo(), MAIN_PANEL, "Src_L2");
			}
			if (r->index() == myUndoSrcL2)
			{
				textPut("text", r->sInfo(), MAIN_PANEL, "Src_UL2");
			}
		}

		// Does the database ID match our destination database?
		if (r->database() == destNameDB)
		{
			// Check if this matches the destination number in the take or undo mimics.
			// If a match occurs, update the mimics.
			if (r->index() == myDestL2)
			{
				textPut("text", r->sInfo(), MAIN_PANEL, "Dst_L2");
			}
			if (r->index() == myUndoDestL2)
			{
				textPut("text", r->sInfo(), MAIN_PANEL, "Dst_UL2");
			}
		}
	}	// End of if (r->device() == L2_matrixID)

	if (r->device() == L3_matrixID)
	{
		// Does the database ID match our source database?
		if (r->database() == srcNameDB)
		{
			// Check if this matches the source number in the take or undo mimics.
			// If a match occurs, update the mimics.
			if (r->index() == mySrcL3)
			{
				textPut("text", r->sInfo(), MAIN_PANEL, "Src_L3");
			}
			if (r->index() == myUndoSrcL3)
			{
				textPut("text", r->sInfo(), MAIN_PANEL, "Src_UL3");
			}
		}

		// Does the database ID match our destination database?
		if (r->database() == destNameDB)
		{
			// Check if this matches the destination number in the take or undo mimics.
			// If a match occurs, update the mimics.
			if (r->index() == myDestL3)
			{
				textPut("text", r->sInfo(), MAIN_PANEL, "Dst_L3");
			}
			if (r->index() == myUndoDestL3)
			{
				textPut("text", r->sInfo(), MAIN_PANEL, "Dst_UL3");
			}
		}
	}	// End of if (r->device() == L3_matrixID)

}	// End of void PkgTakeUndo_3L::databaseCallback(revertiveNotify *r)


////////////////////////////////////////////////////
// All parent notifications come here. Our host does
// need to tell us several persisting properties.
bncs_string PkgTakeUndo_3L::parentCallback(parentNotify *p)
{
	if (p->command() == "return")
	{
		if( p->value() == "all" )
		{	
			// Persisting values for bncs_vis_ed
			bncs_stringlist sl;
			
			sl << bncs_string("matrixobject=%1").arg(m_matrixobject );
			sl << bncs_string("uselevels=%1").arg(m_uselevels);
			sl << bncs_string("srcnamesdatabase=%1").arg(srcNameDB);
			sl << bncs_string("destnamesdatabase=%1").arg(destNameDB);
			sl << bncs_string("takelow=%1").arg(m_takelow);
			sl << bncs_string("takehigh=%1").arg(m_takehigh);
			sl << bncs_string("mimicstate_L1=%1").arg(m_mimicstate_L1);
			sl << bncs_string("mimicstate_L2=%1").arg(m_mimicstate_L2);
			sl << bncs_string("mimicstate_L3=%1").arg(m_mimicstate_L3);
			sl << bncs_string("takeundobgstate=%1").arg(m_takeundobgstate);
			sl << bncs_string("unusedleveltext=%1").arg(m_unusedleveltext);

			return sl.toString( '\n' );
		}

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

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

	}	// End of "return XXX" processing


	else if (p->command() == "srcpkg")
	{
		// We have been sent a source package number
		m_srcpkg = p->value();			// Keep a copy of text version
		mySrcPkg = m_srcpkg.toInt();	// Extract the integer id
		newSourcePackage();				// Call the processing block for source id change
		checkTakeReady();				// Enable/disable the take button.
		return "";
	}


	else if (p->command() == "destpkg")
	{
		// We have been sent a destination package number
		m_destpkg = p->value();			// Keep a copy of the text version
		myDestPkg = m_destpkg.toInt();	// Extract the integer id
		newDestinationPackage();		// Call the processing block for destination
		checkTakeReady();				// Enable/disable the take button.
		return "";
	}


	// Next check for persisting values such as the instance name.
	else if (p->command() == "instance" && p->value() != m_instance)
	{	
		// Our instance is being set/changed
		m_instance = p->value();
		
		// This module does not use teh instance data so just store it.
		return "";
	}


	else if (p->command() == "matrixobject")
	{	
		// Object name that has driver IDs for crosspoint lists and individual matrix level drivers.
		// Only make sense in combination with non-zero values in uselevels. So use a common routine that 
		// checks both settings for validity and calls the noMatrixObject() function if either string is invalid.
		m_matrixobject = p->value();	// Save the new string

		parseMatrixObject();			// Process results.

		return "";
	}


	else if (p->command() == "uselevels")
	{
		// We have received a user layer order preference control string. This can only be
		// interpreted in combination with matrixobject. Changes to either parmeter invoke the 
		// identical parsing routine.
		m_uselevels = p->value();	// Save the new string.

		parseMatrixObject();		// This function does the hard work of getting matricies in right order.

		return "";
	}
	// uselevels

	else if (p->command() == "srcnamesdatabase")
	{
		bncs_string txt_name = "";

		// Set the INI database number used for source name lookups
		m_srcnamesdatabase = p->value();	// Remember last received text string
		srcNameDB = m_srcnamesdatabase.toInt();

		// Update displays if any in source mimics are active.
		if ((mySrcL1 > 0) && (L1_matrixID > 0))
		{
			routerName(L1_matrixID, srcNameDB, mySrcL1, txt_name);
			txt_name.replace('|', ' ');		// Force single line display
			textPut("text", txt_name, MAIN_PANEL, "Src_L1");
		}

		if ((mySrcL2 > 0) && (L2_matrixID > 0))
		{
			routerName(L2_matrixID, srcNameDB, mySrcL2, txt_name);
			txt_name.replace('|', ' ');		// Force single line display
			textPut("text", txt_name, MAIN_PANEL, "Src_L2");
		}

		if ((mySrcL3 > 0) && (L3_matrixID > 0))
		{
			routerName(L3_matrixID, srcNameDB, mySrcL3, txt_name);
			txt_name.replace('|', ' ');		// Force single line display
			textPut("text", txt_name, MAIN_PANEL, "Src_L3");
		}

		if ((myUndoSrcL1 > 0) && (L1_matrixID > 0))
		{
			routerName(L1_matrixID, srcNameDB, myUndoSrcL1, txt_name);
			txt_name.replace('|', ' ');		// Force single line display
			textPut("text", txt_name, MAIN_PANEL, "Src_UL1");
		}

		if ((myUndoSrcL2 > 0) && (L2_matrixID > 0))
		{
			routerName(L2_matrixID, srcNameDB, myUndoSrcL2, txt_name);
			txt_name.replace('|', ' ');		// Force single line display
			textPut("text", txt_name, MAIN_PANEL, "Src_UL2");
		}

		if ((myUndoSrcL3 > 0) && (L3_matrixID > 0))
		{
			routerName(L3_matrixID, srcNameDB, myUndoSrcL3, txt_name);
			txt_name.replace('|', ' ');		// Force single line display
			textPut("text", txt_name, MAIN_PANEL, "Src_UL3");
		}

		return "";
	}


	else if (p->command() == "destnamesdatabase")
	{
		bncs_string txt_name = "";

		// Set the INI database number used for destination name lookups
		m_destnamesdatabase = p->value();	// Remember last received text string
		destNameDB = m_destnamesdatabase.toInt();

		// Update displays if any destination mimics are active.
		if ((myDestL1 > 0) && (L1_matrixID > 0))
		{
			routerName(L1_matrixID, destNameDB, myDestL1, txt_name);
			txt_name.replace('|', ' ');		// Force single line display
			textPut("text", txt_name, MAIN_PANEL, "Dst_L1");
		}

		if ((myDestL2 > 0) && (L2_matrixID > 0))
		{
			routerName(L2_matrixID, destNameDB, myDestL2, txt_name);
			txt_name.replace('|', ' ');		// Force single line display
			textPut("text", txt_name, MAIN_PANEL, "Dst_L2");
		}

		if ((myDestL1 > 0) && (L3_matrixID > 0))
		{
			routerName(L3_matrixID, destNameDB, myDestL3, txt_name);
			txt_name.replace('|', ' ');		// Force single line display
			textPut("text", txt_name, MAIN_PANEL, "Dst_L3");
		}

		if ((myUndoDestL1 > 0) && (L1_matrixID > 0))
		{
			routerName(L1_matrixID, destNameDB, myUndoDestL1, txt_name);
			txt_name.replace('|', ' ');		// Force single line display
			textPut("text", txt_name, MAIN_PANEL, "Dst_UL1");
		}

		if ((myUndoDestL2 > 0) && (L2_matrixID > 0))
		{
			routerName(L2_matrixID, destNameDB, myUndoDestL2, txt_name);
			txt_name.replace('|', ' ');		// Force single line display
			textPut("text", txt_name, MAIN_PANEL, "Dst_UL2");
		}

		if ((myUndoDestL3 > 0) && (L3_matrixID > 0))
		{
			routerName(L3_matrixID, destNameDB, myUndoDestL3, txt_name);
			txt_name.replace('|', ' ');		// Force single line display
			textPut("text", txt_name, MAIN_PANEL, "Dst_UL3");
		}

		return "";
	}


	else if (p->command() == "takelow")
	{
		// Set statesheet name used in TAKE button flashing, state 1 of 2. No need to update button
		// even ifTAKE enabled, value will self activate within maximum of 2 seconds.
		m_takelow = p->value();

		return "";
	}


	else if (p->command() == "takehigh")
	{
		// Set statesheet name used in TAKE button flashing, state 2 of 2 . No need to update button 
		// even if TAKE enabled, value will self activate within maximum of 2 seconds.
		m_takehigh = p->value();

		return "";
	}


	else if (p->command() == "mimicstate_L1")
	{
		// Set the statesheet name for the Level 1 mimics.
		m_mimicstate_L1 = p->value();

		textPut("statesheet", m_mimicstate_L1, MAIN_PANEL, "Src_L1");
		textPut("statesheet", m_mimicstate_L1, MAIN_PANEL, "Src_UL1");
		textPut("statesheet", m_mimicstate_L1, MAIN_PANEL, "Dst_L1");
		textPut("statesheet", m_mimicstate_L1, MAIN_PANEL, "Dst_UL1");

		return "";
	}


	else if (p->command() == "mimicstate_L2")
	{
		// Set the statesheet name for the Level 2 mimics.
		m_mimicstate_L2 = p->value();

		textPut("statesheet", m_mimicstate_L2, MAIN_PANEL, "Src_L2");
		textPut("statesheet", m_mimicstate_L2, MAIN_PANEL, "Src_UL2");
		textPut("statesheet", m_mimicstate_L2, MAIN_PANEL, "Dst_L2");
		textPut("statesheet", m_mimicstate_L2, MAIN_PANEL, "Dst_UL2");

		return "";
	}


	else if (p->command() == "mimicstate_L3")
	{
		// Set the statesheet name for the Level 3 mimics.
		m_mimicstate_L3 = p->value();

		textPut("statesheet", m_mimicstate_L3, MAIN_PANEL, "Src_L3");
		textPut("statesheet", m_mimicstate_L3, MAIN_PANEL, "Src_UL3");
		textPut("statesheet", m_mimicstate_L3, MAIN_PANEL, "Dst_L3");
		textPut("statesheet", m_mimicstate_L3, MAIN_PANEL, "Dst_UL3");

		return "";
	}


	else if (p->command() == "takeundobgstate")
	{
		// Set the statesheet name for the L.
		m_takeundobgstate = p->value();

		textPut("statesheet", m_takeundobgstate, MAIN_PANEL, "TUC_BGND");

		return "";
	}


	else if (p->command() == "unusedleveltext")
	{
		// Text to show on mimics if there is no routing on a level.
		m_unusedleveltext = p->value();		// Save the text

		// Update any active displays

		return "";
	}


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

		sl << "selected=-1";		
		
		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 << "srcpkg=[value]";
		sl << "destpkg=[value]";

		return sl.toString( '\n' );
	}

	return "";
}

//////////////////////////
// Timer events come here.
void PkgTakeUndo_3L::timerCallback( int id )
{
	switch( id )
	{
	case TAKE_TIMER:
		// Implement the take button timeout counter system
		TakeCounter--;		// Decrement the counter
		// Change state of button based odd counter or even counter value. LSB of TakeCounter tells us odd/even
		textPut("statesheet", (TakeCounter & 1) ? m_takelow: m_takehigh, MAIN_PANEL, "Take");
		if (TakeCounter == 0)
		{
			timerStop(id);
			takeTimerRunning = false;
			controlDisable(MAIN_PANEL, "Take");
			TakeEnabled = false;
		}
		break;

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


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


///////////////////////////////////////////////////////////////////////////////////////////////////
// function void PkgTakeUndo_3L::checkTakeReady(void) is called whenever a new source or 
// destination value is received. If there is both a valid source and a valid destination the TAKE
// button is enabled, and the TAKE timeout initiated, else TAKE is disabled
void PkgTakeUndo_3L::checkTakeReady(void)
{
	bool validSrc = false;
	bool validDest = false;

	validSrc = (mySrcPkg != -1) &&					// We have a valid source package number AND
		(((L1_matrixID > 0) && (mySrcL1 >= 0)) ||	// Level 1 has a valid source id OR
		 ((L2_matrixID > 0) && (mySrcL2 >= 0)) ||	// Level 2 has a valid source id OR
		 ((L3_matrixID > 0) && (mySrcL3 >= 0)));	// Level 3 has a valid source id

	validDest = (myDestPkg != -1) &&				// We have a valid destination package number AND
		(((L1_matrixID > 0) && (mySrcL1 >= 0)) ||	// Level 1 has a valid destination id OR
		 ((L2_matrixID > 0) && (mySrcL2 >= 0)) ||	// Level 2 has a valid destination id OR
		 ((L3_matrixID > 0) && (mySrcL3 >= 0)));	// Level 3 has a valid destination id

	if (validSrc && validDest)
	{
		// Enable the take button, start the timeout counter
		if (takeTimerRunning) timerStop(TAKE_TIMER);	// If the timer is already running 
		TakeCounter = TAKE_TIMEOUT_COUNT;				// Initialise counter
		timerStart(TAKE_TIMER, TAKE_TIMER_PERIOD);		// Start timeout counter
		takeTimerRunning = true;
		if (!TakeEnabled)
		{
			controlEnable(MAIN_PANEL, "Take");
			textPut("statesheet", m_takehigh, MAIN_PANEL, "Take");
			TakeEnabled = true;
		}
	}
	else
	{
		// Disable TAKE button and timeout count
		if (takeTimerRunning)
		{
			timerStop(TAKE_TIMER);	// Stop timer if it is running
			takeTimerRunning = false;
		}
		if (TakeEnabled)
		{
			textPut("statesheet", m_takelow, MAIN_PANEL, "Take");
			controlDisable (MAIN_PANEL, "Take");
			TakeEnabled = false;
		}
	}
}	// End of void PkgTakeUndo_3L::checkTakeReady(void)


///////////////////////////////////////////////////////////////////////////////////////////////////
// function void PkgTakeUndo_3L::noMatrixObject(void) is called when a new object name has been 
// passed to this module, but that name does not correspond to an object in object_settings.xml
// This module stores the null values in key variables, disables routing on all 3 levels and clears
// any text displays in the level name indicators.
void PkgTakeUndo_3L::noMatrixObject(void)
{
	XptDriver = 0;											// No crosspoint driver ID available
	
	if (L1_matrixID != 0) routerUnregister(L1_matrixID);	// Remove any router revertive registration
	L1_matrixID = 0;										// Set driver ID to 0
	
	if (L2_matrixID != 0) routerUnregister(L2_matrixID);	// Repeat process for level 2
	L2_matrixID = 0;
	
	if (L3_matrixID != 0) routerUnregister(L3_matrixID);	// and level 3
	L3_matrixID = 0;

	textPut("text=", MAIN_PANEL, "ID_L1");					// Clear any text from the level indicatoors
	textPut("text=", MAIN_PANEL, "ID_L2");
	textPut("text=", MAIN_PANEL, "ID_L3");

	// Disable all levels from routing
	Level_1_Enabled = false;
	Level_2_Enabled = false;
	Level_3_Enabled = false;
	textPut("pixmap", LVL_DISABLE, MAIN_PANEL, "Enable_L1");
	textPut("pixmap", LVL_DISABLE, MAIN_PANEL, "Enable_L2");
	textPut("pixmap", LVL_DISABLE, MAIN_PANEL, "Enable_L3");

	// Remove any revertive registrations for active drivers
	if (L1_matrixID > 0) routerUnregister(L1_matrixID);
	if (L2_matrixID > 0) routerUnregister(L2_matrixID);
	if (L3_matrixID > 0) routerUnregister(L3_matrixID);

	PkgDriver = 0;		// No package names driver available
	XptDriver = 0;		// No crosspoint list driver available
	L1_matrixID = 0;	// No level 1 driver id available
	L2_matrixID = 0;	// No level 2 driver id available
	L3_matrixID = 0;	// No level 3 driver id available

}	// End of void PkgTakeUndo_3L::noMatrixObject(void)


///////////////////////////////////////////////////////////////////////////////////////////////////
// function void PkgTakeUndo_3L::parseMatrixObject(void) is called when the name of the object that
// provides the crosspoint list driver ID and matrix driver IDs changes, or when the user 
// preference for the level display is changed. Both strings must have useful data
// before any major setup work is done.
//
void PkgTakeUndo_3L::parseMatrixObject(void)
{
	bncs_string numLevels = "";		// Holds the number of routing levels reported by the router object
	bncs_string level_ids = "";		// Holds the comma delimited list of matrix names for each matrix router in driverList
	bncs_string driverList = "";	// Holds the comma delimited list of driver IDS:-
									// Names ID, XPT list ID, matrix 1 ID,  ...., matrix N ID

	// Start by testing the matrixobject entity for validity. We use the presence of the
	//  number of levels string in the object as a test for valid object.
	numLevels = getObjectSetting(m_matrixobject, "package_levels");		// Get package levels string, save in numLevels.
	
	if ((numLevels == "") || (m_uselevels == ""))	// m_uselevels is comma delimited string from persiting config data.
	{
		noMatrixObject();		// Treat as null name, don't enable any displays
		return;					// Stop processing
	}

	// If processing arrives here, we have non-blank "matrixobject" and "uselevel" strings
	// Split the uselevel string into elements
	bncs_stringlist myLevelData(m_uselevels, ',');

	if (myLevelData.count() > 3)
	{
		// Too many levels in the uselevels parameter, expected maximum of 3.
		noMatrixObject();
		return;
	}

	// Extract the user list of level order
	switch (myLevelData.count())
	{
		case 1:
			// Using just first level. Set other layers to 0 to disable their operation
			myLevel1 = myLevelData[0].toInt();
			myLevel2 = 0;
			myLevel3 = 0;
			break;

		case 2:
			// Using first two levels
			myLevel1 = myLevelData[0].toInt();
			myLevel2 = myLevelData[1].toInt();
			myLevel3 = 0;
			break;

		case 3:
			// Definitions supplied for all three levels.
			myLevel1 = myLevelData[0].toInt();
			myLevel2 = myLevelData[1].toInt();
			myLevel3 = myLevelData[2].toInt();
			break;

	}	// End of switch(myLeveldata.count())

	// Be defensive - make sure at least one level order entry is active, else set no matrix objects.
	if ((myLevel1 == 0) && (myLevel2 == 0) && (myLevel3 == 0))
	{
		noMatrixObject();
		return;
	}

	// It seems likely that we now have data to set up system operations. Just in case the changes have been sent during
	// active routing, ensure this routine does a proper "tidy-up" before loading new driver data to working memories.
	disableTake();				// Sort possibility that TAKE is active, send cleardown to parent.
	disableUndo();				// Is UNDO active, if so remove UNDO data and button.
	hostNotify("selected=-1");	// Send clear selection commands to any connected source and destination selectors

	// Clear down text displays before executing number of levels dependant code
	textPut("text=", MAIN_PANEL, "ID_L1");		// Clear text from ID labels
	textPut("text=", MAIN_PANEL, "ID_L2");
	textPut("text=", MAIN_PANEL, "ID_L3");
	textPut("text=", MAIN_PANEL, "Src_L1");		// Clear text from source level name displays
	textPut("text=", MAIN_PANEL, "Src_L2");
	textPut("text=", MAIN_PANEL, "Src_L3");
	textPut("text=", MAIN_PANEL, "Dst_L1");		// Clear text from destination level name displays
	textPut("text=", MAIN_PANEL, "Dst_L2");
	textPut("text=", MAIN_PANEL, "Dst_L3");
	textPut("text=", MAIN_PANEL, "Src_UL1");	// Clear text from source level name displays
	textPut("text=", MAIN_PANEL, "Src_UL2");
	textPut("text=", MAIN_PANEL, "Src_UL3");
	textPut("text=", MAIN_PANEL, "Dst_UL1");	// Clear text from destination level name displays
	textPut("text=", MAIN_PANEL, "Dst_UL2");
	textPut("text=", MAIN_PANEL, "Dst_UL3");

	// Extract the names of the levels. First name is index 2 in comma delimited string level_ids
	level_ids = getObjectSetting(m_matrixobject, "driver_type");
	bncs_stringlist lvlnames(level_ids, ',');

	// Get the list of driver numbers and divide into individual elements
	driverList = getObjectSetting(m_matrixobject, "driver_ids");
	bncs_stringlist mtxDevices(driverList, ',');

	// Adjust as needed for number of levels reported by matrixobject. numLevels has string 
	// from matrix object stating number of levels used.


	/////////////////////////////////////////////////////////////////////////////////////
	// 3 or more Levels of Routing. If more than 3, assume the panel writer has thought
	// about that, and use levels 1 through 3:
	// Set level enables for levels 1, 2 and 3 (ie include "userlevels" masking)
	Level_1_Enabled = true && (myLevel1 != 0);
	Level_2_Enabled = true && (myLevel2 != 0);
	Level_3_Enabled = true && (myLevel3 != 0);

	// Set the crosses and ticks to relevant levels
	textPut("pixmap", Level_1_Enabled ? LVL_ENABLE : LVL_DISABLE, MAIN_PANEL, "Enable_L1");
	textPut("pixmap", Level_2_Enabled ? LVL_ENABLE : LVL_DISABLE, MAIN_PANEL, "Enable_L2");
	textPut("pixmap", Level_3_Enabled ? LVL_ENABLE : LVL_DISABLE, MAIN_PANEL, "Enable_L3");

	// Configure the "buttons" that control the enable or disable routing on this level.
	if (Level_1_Enabled)
	{
		controlEnable(MAIN_PANEL, "ID_L1");			// Enable level ident display
		controlEnable(MAIN_PANEL, "Enable_L1");		// Enable level tick/cross display
	}
	else
	{
		controlDisable(MAIN_PANEL, "ID_L1");		// Disable level ident display
		controlDisable(MAIN_PANEL, "Enable_L1");	// Disable level tick/cross display
	}
	 
	if (Level_2_Enabled)							// Repeat for level 2
	{
		controlEnable(MAIN_PANEL, "ID_L2");
		controlEnable(MAIN_PANEL, "Enable_L2");
	}
	else
	{
		controlDisable(MAIN_PANEL, "ID_L2");
		controlDisable(MAIN_PANEL, "Enable_L2");
	}

	if (Level_3_Enabled)							// Repeat for level 2
	{
		controlEnable(MAIN_PANEL, "ID_L3");
		controlEnable(MAIN_PANEL, "Enable_L3");
	}
	else
	{
		controlDisable(MAIN_PANEL, "ID_L3");
		controlDisable(MAIN_PANEL, "Enable_L3");
	}

	// Show names from matrixobject into level enable/disable buttons.
	if (myLevel1 > 0)	textPut("text", lvlnames[1 + myLevel1], MAIN_PANEL, "ID_L1");
	if (myLevel2 > 0)	textPut("text", lvlnames[1 + myLevel2], MAIN_PANEL, "ID_L2");
	if (myLevel3 > 0)	textPut("text", lvlnames[1 + myLevel3], MAIN_PANEL, "ID_L3");

	// Sort the matrix numbers
	PkgDriver = mtxDevices[0];		// BNCS Driver ID for package names
	XptDriver = mtxDevices[1];		// BNCS Driver ID for crosspoint lists
	if (Level_1_Enabled)
	{
		L1_matrixID = (myLevel1 > 0) ? mtxDevices[1 + myLevel1] : 0;	// BNCS Driver ID for level 1
	}
	if (Level_2_Enabled)
	{
		L2_matrixID = (myLevel2 > 0) ? mtxDevices[1 + myLevel2] : 0;	// BNCS Driver ID for level 2
	}
	if (Level_3_Enabled)
	{
		L3_matrixID = (myLevel3 > 0) ? mtxDevices[1 + myLevel3] : 0;	// BNCS Driver ID for level 3
	}

	RegisterRouters();		// Register for revertives for active routers, poll for router state 

}	// End of void PkgTakeUndo_3L::parseMatrixObject(void)


///////////////////////////////////////////////////////////////////////////////////////////////////
// function void clearSource(void) is called when we need to clear any source selection. It is
// assumed the calling function will also call diasableTake() if so required
void PkgTakeUndo_3L::clearSource(void)
{
	// Set source package id to deselected value of -1
	mySrcPkg = -1;

	// Set level source IDs to 0
	mySrcL1 = 0;
	mySrcL2 = 0;
	mySrcL3 = 0;

	// Clear out displays
	textPut("text=", MAIN_PANEL, "Src_L1");	// Blank the source displays
	textPut("text=", MAIN_PANEL, "Src_L2");
	textPut("text=", MAIN_PANEL, "Src_L3");

}	// End of void PkgTakeUndo_3L::clearSource(void)


///////////////////////////////////////////////////////////////////////////////////////////////////
// function void clearDestination(void) is called when we need to clear any destination selection.
void PkgTakeUndo_3L::clearDestination(void)
{
	// Set destination package id to deselected value of -1
	myDestPkg = -1;

	// Set level source IDs to 0
	myDestL1 = 0;
	myDestL2 = 0;
	myDestL3 = 0;

	// Clear out displays
	textPut("text=", MAIN_PANEL, "Dst_L1");	// Blank the source displays
	textPut("text=", MAIN_PANEL, "Dst_L2");
	textPut("text=", MAIN_PANEL, "Dst_L3");

}	// End of void PkgTakeUndo_3L::clearDestination(void)


///////////////////////////////////////////////////////////////////////////////////////////////////
// function void disableTake(void) is called whenever any other actions require an active TAKE
// state to be removed. Timeout counter timer is stopped, and the TAKE button is disabled.
// The source and destination selections are not changed.
void PkgTakeUndo_3L::disableTake(void)
{
	if (TakeEnabled)
	{
		// Take button is enabled so cease timer if active
		if (takeTimerRunning)
		{
			timerStop(TAKE_TIMER);
			takeTimerRunning = false;
		}

		textPut("statesheet", m_takelow, MAIN_PANEL, "Take");	// Set take button state to low intensity
		controlDisable(MAIN_PANEL, "Take");
		TakeEnabled = false;
	}
}	// End of void PkgTakeUndo_3L::disableTake(void)


///////////////////////////////////////////////////////////////////////////////////////////////////
// function void PkgTakeUndo_3L::disableUndo(void) is called when the control program needs to 
// disable the UNDO button. It clears down the name displays, and resets the control variables to 
// zero.
void PkgTakeUndo_3L::disableUndo(void)
{
	// If UNDO is already marked as disabled, just return.
	if (UndoEnabled)
	{
		// Disable the UNDO control.
		controlDisable(MAIN_PANEL, "Undo");

		// Clear the text in undo displays
		textPut("text=", MAIN_PANEL, "Src_UL1");
		textPut("text=", MAIN_PANEL, "Src_UL2");
		textPut("text=", MAIN_PANEL, "Src_UL3");
		textPut("text=", MAIN_PANEL, "Dst_UL1");
		textPut("text=", MAIN_PANEL, "Dst_UL2");
		textPut("text=", MAIN_PANEL, "Dst_UL3");

		// Clear undo selections
		myUndoSrcL1 = 0;		// Level 1 source number for UNDO operation
		myUndoSrcL2 = 0;		// Level 2 source number for UNDO operation
		myUndoSrcL3 = 0;		// Level 3 source number for UNDO operation
		myUndoDestL1 = 0;		// Level 1 destination number for UNDO operation
		myUndoDestL2 = 0;		// Level 2 destination number for UNDO operation
		myUndoDestL3 = 0;		// Level 3 destination number for UNDO operation

		UndoEnabled = false;
	}
}	// End of void PkgTakeUndo_3L::disableUndo(void)


///////////////////////////////////////////////////////////////////////////////////////////////////
// function void PkgTakeUndo_3L::RegisterRouters(void) is called after a change of object data to 
// register the new router set for revertives. It uses the matrix driver IDs stored in component
// wide variables:
//    PkgDriver		- BNCS Driver ID for package names
//    XptDriver		- BNCS Driver ID for crosspoint lists
//    L1_matrixID	- BNCS Driver ID for level 1
//    L2_matrixID	- BNCS Driver ID for level 2
//    L3_matrixID	- BNCS Driver ID for level 3
//
// If a matrix is not in use the driver ID should be set to 0. The registration has to encompass
// all possible destination, so the size of database 1 for each driver is accessed to define the
// upper limit.
//
void PkgTakeUndo_3L::RegisterRouters(void)
{
	// Process the real signal level routes in sequence
	matrix_L1_Dests = 0;	// Value if level 1 not used
	if (L1_matrixID > 0)	// Update level data if level is used
	{
		matrix_L1_Dests = getRouterSize(L1_matrixID, BNCS_DEST_DB);		// Ask for number of destinations
		if (matrix_L1_Dests > 0)	
		{
			// Clear the revertive store for this level
			for (int idx = 0; idx < MAX_MATRIX_DEST; idx++) revert_L1[idx] = -1;
			routerRegister(L1_matrixID, 1, matrix_L1_Dests);			// Register for revertives
			routerPoll(L1_matrixID, 1, matrix_L1_Dests);
		}
	}

	// Process level 2 router
	matrix_L2_Dests = 0;	// Value if level 2 not used
	if (L2_matrixID > 0)	// Update level data if level is used
	{
		matrix_L2_Dests = getRouterSize(L2_matrixID, BNCS_DEST_DB);	// Ask for number of destinations
		if (matrix_L2_Dests > 0)
		{
			// Clear the revertive store for this level
			for (int idx = 0; idx < MAX_MATRIX_DEST; idx++) revert_L2[idx] = -1;
			routerRegister(L2_matrixID, 1, matrix_L2_Dests);	// Register for revertives
			routerPoll(L2_matrixID, 1, matrix_L2_Dests);
		}
	}

	//	Process level 3 router
	matrix_L3_Dests = 0;	// Value if level 3 not used
	if (L3_matrixID > 0)	// Update level data if level is used
	{
		matrix_L3_Dests = getRouterSize(L3_matrixID, BNCS_DEST_DB);	// Ask for number of destinations
		if (matrix_L3_Dests > 0)
		{
			// Clear the revertive store for this level
			for (int idx = 0; idx < MAX_MATRIX_DEST; idx++) revert_L3[idx] = -1;
			routerRegister(L3_matrixID, 1, matrix_L3_Dests);	// Register for revertives
			routerPoll(L3_matrixID, 1, matrix_L3_Dests);
		}
	}

}	// End of void PkgTakeUndo_3L::RegisterRouters(void)


///////////////////////////////////////////////////////////////////////////////////////////////////
// function void newSourcePackage(void) is called when a new source package number is delivered to
// this component. If this has the value of -1 the request is a clear selection. Otherwise the
// package needs to be extracted into individual level selections. Variable "mySrcPkg" contains the
// new source package number. Note that we must use the myLevel1 to myLevel3 variables to control the
// source listings as these may affect the display order.
//
void PkgTakeUndo_3L::newSourcePackage(void)
{
	bncs_string srcxptlist = "";
	bncs_string src_name = "";

	if (XptDriver == 0)
	{
		// We do not have data needed for splitting the package
		return;
	}

	// Test for the "clear source" number
	if (mySrcPkg == -1)
	{
		// Check for TAKE or UNDO active and disable as needed
		disableTake();
		disableUndo();
		
		// Clear out mimic displays
		mySrcL1 = 0;
		mySrcL2 = 0;
		mySrcL3 = 0;
		textPut("text=", MAIN_PANEL, "Src_L1");	// Blank the source displays
		textPut("text=", MAIN_PANEL, "Src_L2");
		textPut("text=", MAIN_PANEL, "Src_L3");

		return;
	}

	if (UndoEnabled) disableUndo();		// UNDO data now invalid

	// Read the comma demimited list of level sources in this package
	routerName(XptDriver, BNCS_SRC_DB, mySrcPkg, srcxptlist);

	bncs_stringlist srcidlist(srcxptlist, ',');		// Convert into string list split at comma

	if (L1_matrixID > 0)	// Process level 1 if a driver ID exists
	{
		if (myLevel1 != 0)	// But only if the user re-order is non-zero for this level.
		{
			// Extract the source number, replacing a "-" with value -1
			mySrcL1 = (srcidlist[myLevel1 - 1] == "-") ? -1 : srcidlist[myLevel1 - 1].toInt();
			if (mySrcL1 == -1)
			{
				textPut("text", m_unusedleveltext, MAIN_PANEL, "Src_L1");
			}
			else
			{
				// Get the source name
				routerName(L1_matrixID, srcNameDB, mySrcL1, src_name);
				src_name.replace('|', ' ');		// Force single line display
				textPut("text", src_name, MAIN_PANEL, "Src_L1");
			}
		}
	}
	else
	{
		mySrcL1 = 0;
		textPut("text", m_unusedleveltext, MAIN_PANEL, "Src_L1");
	}

	// Process Level 2
	if (L2_matrixID > 0)	// Process level 2 if a driver ID exists
	{
		if (myLevel2 != 0)
		{
			// Extract the source number, replacing a "-" with value -1
			mySrcL2 = (srcidlist[myLevel2 - 1] == "-") ? -1 : srcidlist[myLevel2 - 1].toInt();
			if (mySrcL2 == -1)
			{
				textPut("text", m_unusedleveltext, MAIN_PANEL, "Src_L2");
			}
			else
			{
				// Get the source name
				routerName(L2_matrixID, srcNameDB, mySrcL2, src_name);
				src_name.replace('|', ' ');		// Force single line display
				textPut("text", src_name, MAIN_PANEL, "Src_L2");
			}
		}
	}
	else
	{
		mySrcL2 = 0;
		textPut("text", m_unusedleveltext, MAIN_PANEL, "Src_L2");
	}

	// Process level 3
	if (L3_matrixID > 0)	// Process level 3 if a driver ID exists
	{
		if (myLevel3 > 0)
		{
			// Extract the source number, replacing a "-" with value -1
			mySrcL3 = (srcidlist[myLevel3 - 1] == "-") ? -1 : srcidlist[myLevel3 - 1].toInt();
			if (mySrcL3 == -1)
			{
				textPut("text", m_unusedleveltext, MAIN_PANEL, "Src_L3");
			}
			else
			{
				// Get the source name
				routerName(L3_matrixID, srcNameDB, mySrcL3, src_name);
				src_name.replace('|', ' ');		// Force single line display
				textPut("text", src_name, MAIN_PANEL, "Src_L3");
			}
		}
	}
	else
	{
		mySrcL3 = 0;
		textPut("text", m_unusedleveltext, MAIN_PANEL, "Src_L3");
	}

}	// End of void PkgTakeUndo_3L::newSourcePackage(void)


///////////////////////////////////////////////////////////////////////////////////////////////////
// function void newDestinationPackage(void) is called when a new destination package number is 
// delivered to this component. If this has the value of -1 the request is a clear selection. 
// Otherwise the package needs to be extracted into individual level selections. Variable 
// "myDestPkg" contains the new destination package number. Note that we must use the myLevel1 
// to myLevel3 variables to control the destination listings as these may affect the display order.
//
void PkgTakeUndo_3L::newDestinationPackage(void)
{
	bncs_string destxptlist = "";
	bncs_string dest_name = "";

	if (XptDriver == 0)
	{
		// We do not have data needed to split the package
		return;
	}

	// Test for the clear destination number
	if (myDestPkg == -1)
	{
		// Check for TAKE or UNDO active
		if (TakeEnabled) disableTake();
		if (UndoEnabled) disableUndo();

		// Clear out any old assignments
		myDestL1 = 0;
		myDestL2 = 0;
		myDestL3 = 0;
		textPut("text=", MAIN_PANEL, "Dst_L1");		// Blank the destination displays
		textPut("text=", MAIN_PANEL, "Dst_L2");
		textPut("text=", MAIN_PANEL, "Dst_L3");

		return;
	}

	if (UndoEnabled) disableUndo();					// UNDO data now invalid

	// Read the comma demimited list of destintations
	routerName(XptDriver, BNCS_DEST_DB, myDestPkg, destxptlist);

	bncs_stringlist dstidlist(destxptlist, ',');	// Convert comma delimited string into a stringlist

	// ++++++++++++++++++++++++++++++++++++++++++++++++++++
	// Process level 1 if a driver ID exists
	if (L1_matrixID > 0)	
	{
		if (myLevel1 != 0)	// But only if the re-order is non-zero for this level.
		{
			// Extract the destination number, replacing a "-" with value -1
			myDestL1 = (dstidlist[myLevel1 - 1] == "-") ? -1 : dstidlist[myLevel1 - 1].toInt();
			if (myDestL1 == -1)
			{
				textPut("text", m_unusedleveltext, MAIN_PANEL, "Dst_L1");
			}
			else
			{
				// Get the destination name
				routerName(L1_matrixID, destNameDB, myDestL1, dest_name);
				dest_name.replace('|', ' ');		// Force single line display
				textPut("text", dest_name, MAIN_PANEL, "Dst_L1");
			}
		}
	}
	else
	{
		myDestL1 = 0;
		textPut("text", m_unusedleveltext, MAIN_PANEL, "Dst_L1");
	}

	// ++++++++++++++++++++++++++++++++++++++++++++++++++++
	// Process Level 2 if a driver ID exists
	if (L2_matrixID > 0)	// Process level 2 if a driver ID exists
	{
		if (myLevel2 != 0)
		{
			// Extract the destination number, replacing a "-" with value -1
			myDestL2 = (dstidlist[myLevel2 - 1] == "-") ? -1 : dstidlist[myLevel2 - 1].toInt();
			if (myDestL2 == -1)
			{
				textPut("text", m_unusedleveltext, MAIN_PANEL, "Dst_L2");
			}
			else
			{
				// Get the destination name
				routerName(L2_matrixID, destNameDB, myDestL2, dest_name);
				dest_name.replace('|', ' ');		// Force single line display
				textPut("text", dest_name, MAIN_PANEL, "Dst_L2");
			}
		}
	}
	else
	{
		myDestL2 = 0;
		textPut("text", m_unusedleveltext, MAIN_PANEL, "Dst_L2");
	}

	// ++++++++++++++++++++++++++++++++++++++++++++++++++++
	// Process level 3 if a driver ID exists
	if (L3_matrixID > 0)	// Process level 1 if a driver ID exists
	{
		if (myLevel3 > 0)
		{
			// Extract the destination number, replacing a "-" with value -1
			myDestL3 = (dstidlist[myLevel3 - 1] == "-") ? -1 : dstidlist[myLevel3 - 1].toInt();
			if (myDestL3 == -1)
			{
				textPut("text", m_unusedleveltext, MAIN_PANEL, "Dst_L3");
			}
			else
			{
				// Get the destination name
				routerName(L3_matrixID, destNameDB, myDestL3, dest_name);
				dest_name.replace('|', ' ');		// Force single line display
				textPut("text", dest_name, MAIN_PANEL, "Dst_L3");
			}
		}
	}
	else
	{
		myDestL3 = 0;
		textPut("text", m_unusedleveltext, MAIN_PANEL, "Dst_L3");
	}

}	// End of void PkgTakeUndo_3L::newDestinationPackage(void)


///////////////////////////////////////////////////////////////////////////////////////////////////
// function void PkgTakeUndo_3L::doTakeAction(void) is called when the panel user has pressed the
// TAKE button. This function processes the crosspoint make commands for the 3 levels, taking 
// account of the level enable flags. The function also prepares the data for the undo function
// based on previously received revertive data for the selected destinations.
void PkgTakeUndo_3L::doTakeAction(void)
{
	bncs_string xptname = "";

	// Save the data needed by UNDO. Copy destination from active destination. Get undo sources 
	// from stored revertive data.
	myUndoSrcL1 = (myDestL1 > 0) ? revert_L1[myDestL1] : -1;	// Get current source routed to our selected destination, if myDestL1 is "real"
	myUndoSrcL2 = (myDestL2 > 0) ? revert_L2[myDestL2] : -1;	// and save to the undo storage
	myUndoSrcL3 = (myDestL3 > 0) ? revert_L3[myDestL3] : -1;

	myUndoDestL1 = myDestL1;
	myUndoDestL2 = myDestL2;
	myUndoDestL3 = myDestL3;

	// Action the take request.
	disableTake();				// Disable the take button

	// Implement the TAKE actions level by level.
	// To qualify to send a route crosspoint request: 
	// 1) The level must be enabled (variables Level_1_Enabled, Level_2_Enabled, Level_3_Enabled)
	// 2) The source index must be >= 0 to support park source usage (variables mySrcL1, mySrcL2, mySrcL3)
	// 3) The destination index must be > 0 variables (variables myDestL1, myDestL2, myDestL3)
	// 4) Matrix ID for this level must be > 0
	if ((Level_1_Enabled) && (mySrcL1 >= 0) && (myDestL1 > 0) && (L1_matrixID > 0))
	{
		routerCrosspoint(L1_matrixID, mySrcL1, myDestL1, "", false);
	}

	if ((Level_2_Enabled) && (mySrcL2 >= 0) && (myDestL2 > 0) && (L2_matrixID > 0))
	{
		routerCrosspoint(L2_matrixID, mySrcL2, myDestL2, "", false);
	}

	if ((Level_3_Enabled) && (mySrcL3 >= 0) && (myDestL3 > 0) && (L3_matrixID > 0))
	{
		routerCrosspoint(L3_matrixID, mySrcL3, myDestL3, "", true);
	}

	// Clear the source and destination selections
	clearSource();
	clearDestination();
	hostNotify("selected=-1");

	// Finally show the UNDO data displays from our stored values.
	// Level 1 Source
	if ((myUndoSrcL1 > 0) && (L1_matrixID > 0))
	{
		routerName(L1_matrixID, srcNameDB, myUndoSrcL1, xptname);	// Read name from database
		xptname.replace('|', ' ');									// Force single line display
		textPut("text", xptname , MAIN_PANEL, "Src_UL1");			// Put into source mimic
	}
	else
	{
		textPut("text", m_unusedleveltext, MAIN_PANEL, "Src_UL1");	// Unused level
	}

	// Level 2 Source
	if ((myUndoSrcL2 > 0) && (L2_matrixID > 0))
	{
		routerName(L2_matrixID, srcNameDB, myUndoSrcL2, xptname);	// Read name from database
		xptname.replace('|', ' ');									// Force single line display
		textPut("text", xptname, MAIN_PANEL, "Src_UL2");			// Put into source mimic
	}
	else
	{
		textPut("text", m_unusedleveltext, MAIN_PANEL, "Src_UL2");	// Unused level
	}

	// Level 3 Source
	if ((myUndoSrcL3 > 0) && (L3_matrixID > 0))
	{
		routerName(L3_matrixID, srcNameDB, myUndoSrcL3, xptname);	// Read name from database
		xptname.replace('|', ' ');									// Force single line display
		textPut("text", xptname, MAIN_PANEL, "Src_UL3");			// Put into source mimic
	}
	else
	{
		textPut("text", m_unusedleveltext, MAIN_PANEL, "Src_UL3");	// Unused level
	}

	// Add the UNDO Destination names to displays
	if ((myUndoDestL1 > 0) && (L1_matrixID > 0))
	{
		routerName(L1_matrixID, destNameDB, myUndoDestL1, xptname);	// Read name from database
		xptname.replace('|', ' ');									// Force single line display
		textPut("text", xptname, MAIN_PANEL, "Dst_UL1");			// Put into source mimic
	}
	else
	{
		textPut("text", m_unusedleveltext, MAIN_PANEL, "Dst_UL1");	// Unused level
	}

	// Level 2
	if ((myUndoDestL2 > 0) && (L2_matrixID > 0))
	{
		routerName(L2_matrixID, destNameDB, myUndoDestL2, xptname);	// Read name from database
		xptname.replace('|', ' ');									// Force single line display
		textPut("text", xptname, MAIN_PANEL, "Dst_UL2");			// Put into source mimic
	}
	else
	{
		textPut("text", m_unusedleveltext, MAIN_PANEL, "Dst_UL2");	// Unused level
	}

	// Level 3
	if ((myUndoDestL3 > 0) && (L1_matrixID > 0))
	{
		routerName(L3_matrixID, destNameDB, myUndoDestL3, xptname);	// Read name from database
		xptname.replace('|', ' ');									// Force single line display
		textPut("text", xptname, MAIN_PANEL, "Dst_UL3");			// Put into source mimic
	}
	else
	{
		textPut("text", m_unusedleveltext, MAIN_PANEL, "Dst_UL3");	// Unused level
	}

	// Enable the UNDO button
	controlEnable(MAIN_PANEL, "Undo");
	UndoEnabled = true;

}	// End of void PkgTakeUndo_3L::doTakeAction(void)


///////////////////////////////////////////////////////////////////////////////////////////////////
// function void PkgTakeUndo_3L::doUndoAction(void) is called when the panel user has pressed the
// UNDO button. This function processes the crosspoint make commands for the 3 levels, taking 
// account of the level enable flags. It then clears down the UNDO displays and disables the UNDO
// button.
void PkgTakeUndo_3L::doUndoAction(void)
{
	// Implement the UNDO actions level by level.
	// To qualify to send a route crosspoint request: 
	// 1) The level must be enabled (variables Level_1_Enabled, Level_2_Enabled, Level_3_Enabled)
	// 2) The source index must be >= 0 to support park source usage (variables mySrcL1, mySrcL2, mySrcL3)
	// 3) The destination index must be > 0 variables (variables myDestL1, myDestL2, myDestL3)
	// 4) Matrix ID for this level must be > 0
	if ((Level_1_Enabled) && (myUndoSrcL1 >= 0) && (myUndoDestL1 > 0) && (L1_matrixID > 0))
	{
		routerCrosspoint(L1_matrixID, myUndoSrcL1, myUndoDestL1, "", false);
	}

	if ((Level_2_Enabled) && (myUndoSrcL2 >= 0) && (myUndoDestL2 > 0) && (L2_matrixID > 0))
	{
		routerCrosspoint(L2_matrixID, myUndoSrcL2, myUndoDestL2, "", false);
	}

	if ((Level_3_Enabled) && (myUndoSrcL3 >= 0) && (myUndoDestL3 > 0) && (L3_matrixID > 0))
	{
		routerCrosspoint(L3_matrixID, myUndoSrcL3, myUndoDestL3, "", true);
	}

	disableUndo();		// Disable UNDO button, clear displays etc.

}	// End of void PkgTakeUndo_3L::doUndoButton(void)


// -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ //
// -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ //
// -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ //
