/*
PROGRAM NAME	Mtx_Pkg_Edit.DLL

USED BY			BBC Academy Wood Norton BNCS installation.

VERSION			1.0
DATE			12th April 2015
PROGRAMMER		Andy Woodhouse

Updated:
VERSION			1.1
DATE			15th May 2017
PROGRAMMER		Andy Woodhouse
Update Reason:	Add support for different sized screens. Layouts are available for 1024 and 1280 pel wide
				screens. The screen size is read from workstations.xml using parameter tag "screenwidth".
				If the tag is not found, a default of 1024 pels is used.

Overview
========
This DLL implements a scripted panel system that enables a user to edit:
a)	the matrix source and destination names,
b)	the package source and destination names,
c)	and the package contents
for all the major matrixies in the Wood Norton system.

The data needed to define the area names, matrix ids etc is held in file object_settings.xml, enabling the tokens to be read by 
the bncs_script_helper function getObjectSetting(object_name, object_setting). An example of the data is shown below.

		<object id="rtr_area_0">
			<setting id="areaname" value="V2 CAR" />
			<setting id="package_levels" value="2" />
			<setting id="driver_ids" value="100,120,123,124" />
			<setting id="driver_type" value="Package Names,Package Crosspoints,SDI Video,Audio" />
		</object>

Each area defined starts with an id of the form "rtr_area_X" where X is a number. At startup this DLL looks for the
total number of areas by scanning through the object setting file starting at rtr_area_0, then trying rtr_area_2 etc 
until a null value is returned for the areaname token. A maximum of 10 areas is supported by this version of the DLL.

*/


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

// Define the ID values for the panel displays
#define HOME		1
#define AV_EDIT		2
#define PKG_NAME	3
#define PKG_XPT		4

// Define the names of the bncs_ui for each of the panels
#define HOME_NAME_STUB		"HomePanel"
#define ROUTER_EDIT_STUB	"AV_Name_Edit"
#define PKG_NAME_EDIT_STUB	"Pkg_Name_Edit"
#define PKG_XPT_EDIT_STUB	"Pkg_Content_Edit"
#define PANEL_EXT			".bncs_ui"

// Statesheet names used by the panels
#define DESELECTED	"ctl_select_inactive"
#define SELECTED	"ctl_select_active"
#define UPDATE_HIGH	"WN_Update_High"
#define UPDATE_LOW	"WN_Update_Low"
#define CANCEL_HIGH	"WN_Cancel_High"
#define CANCEL_LOW	"WN_Cancel_Low"

#define NO_AREA_SELECTED -1
#define LONGNAME_MAX 16			// Maximum length of a long name
#define SHORTNAME_MAX 8			// Maximum length of a short name (for UMD displays)

#define SRCGRPMAPBASE 200		// Base address for source group maps is 200
#define DESTGRPMAPBASE 250		// Base address for destination group maps is 250

#define AVEDIT_TIMER	1
#define PKGEDIT_TIMER	2

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

// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// Constructor - equivalent to ApplCore STARTUP
Mtx_Pkg_Edit::Mtx_Pkg_Edit(bncs_client_callback *parent, const char *path ) : bncs_script_helper(parent, path)
{
	// Initialise "global" variables.
	homePanel = "";						// Holds name of the home panel.
	avNamePanel = "";					// Holds name of Name Editing panel
	pkgNamePanel = "";					// Holds name of package name edit panel
	pkgXptPanel = "";					// Holds name of crosspoint edit panel

	num_areas = 0;						// Set number of areas with matrix data to be edited.
	selectedArea = NO_AREA_SELECTED;	// No area yet active for edit
	editType = 0;
	dataType = 0;						// Value indicates data edit type. 0=unselected, 1=source names, 2=destination names, 3=Source package content, 4= Destination package content.
	routerLevel = 0;					// Holds the ID of the selected router level for name editing
	matrix_id = 0;						// Holds the current matrix id during an edit session
	baseDatabase = 0;					// Holds the current database, base value.
	databaseOffset = 0;					// Added to baseDatabase to get curent working edit database. Values 0 or 2.
	xptid = 0;							// The crosspoint id being edited
	mtxNameTimeOut = 0;					// Holds the timeout count value for the Update/Cancel timer.
	mtxNameIndex = 0;					// Holds the index of the currently selected source or destination package
	pkgNameTimeOut = 0;					// Holds the timeout counter value for Package name edit timer.
	pkgMatrixId = 0;					// Holds the matrix ID of the package being edited
	pkgBaseDatabase = 0;				// Holds the database ID of either 0 (sources) or 1 (destinations)
	namesDevID = 0;						// Driver ID of the package names.
	xptDevID = 0;						// Driver ID of the package crosspoints.
	curPkg = 0;							// ID of the current package being edited.
	numLevels = 0;						// Number of levels for the current package set
	currentLevel = 0;					// Level currently being edited
	xptVidNow = 0;						// Initial Video crosspoint ID
	xptAudNow = 0;						// Initial Audio crosspoint ID
	xptAESNow = 0;						// Initial AES crosspoint ID (Studio C only)
	xptVidNext = 0;						// Updated Video crosspoint ID
	xptAudNext = 0;						// Updated AES crosspoint ID
	xptAESNext = 0;						// Updated AES crosspoint ID
	xptBaseDatabase = 0;				// Base database for the edit

	doEdit_enabled = false;				// State of the DoEdit button on the HOME panel
	editWhat_enabled = false;
	mtx_name_edit = false;				// True when router name editing (not package name) edit is active.
	mtx_registered = false;				// Set true when a router is registered for revertives. Needed to get database updates.
	pkgXptEdit = false;					// Set true when a XPT level is being edited.

	// Initialise matrix data array.
	for (int idx = 0; idx < 10; idx++) {
		matrixData[idx].areaname = "";
		matrixData[idx].pkgLevels = 0;
		for (int i = 0; i < 5; i++) matrixData[idx].driverIDs[i] = 0;
		for (int i = 0; i < 5; i++) matrixData[idx].driverType[i] = bncs_string("");
	}

	// Read the workstation panel width from workstations.xml
	bncs_string wsw = getWorkstationSetting("screenwidth");
	bncs_string wid = "";

	// Create the names of the BNCS_UI panels required.
	switch (wsw.toInt())
	{
		case 0:			// Fall through to the default of 1024
			debug(bncs_string("\nMtx_Pkg_Edit:: No screenwidth parameter found in workstations.xml for workstation %1.\n").arg(workstation()));
			wid = bncs_string("1024");
			homePanel = HOME_NAME_STUB + wid + PANEL_EXT;
			avNamePanel = ROUTER_EDIT_STUB + wid + PANEL_EXT;
			pkgNamePanel = PKG_NAME_EDIT_STUB + wid + PANEL_EXT;
			pkgXptPanel = PKG_XPT_EDIT_STUB + wid + PANEL_EXT;
			break;

		case 1024:
			wid = bncs_string("1024");
			homePanel = HOME_NAME_STUB + wid + PANEL_EXT;
			avNamePanel = ROUTER_EDIT_STUB + wid + PANEL_EXT;
			pkgNamePanel = PKG_NAME_EDIT_STUB + wid + PANEL_EXT;
			pkgXptPanel = PKG_XPT_EDIT_STUB + wid + PANEL_EXT;
			break;

		case 1280:
			wid = bncs_string("1280");
			homePanel = HOME_NAME_STUB + wid + PANEL_EXT;
			avNamePanel = ROUTER_EDIT_STUB + wid + PANEL_EXT;
			pkgNamePanel = PKG_NAME_EDIT_STUB + wid + PANEL_EXT;
			pkgXptPanel = PKG_XPT_EDIT_STUB + wid + PANEL_EXT;
			break;

		default:
			debug(bncs_string("\nMtx_Pkg_Edit:: Unsupported screenwidth parameter found in workstations.xml for workstation %1.\n").arg(workstation()));
			wid = bncs_string("1024");
			homePanel = HOME_NAME_STUB + wid + PANEL_EXT;
			avNamePanel = ROUTER_EDIT_STUB + wid + PANEL_EXT;
			pkgNamePanel = PKG_NAME_EDIT_STUB + wid + PANEL_EXT;
			pkgXptPanel = PKG_XPT_EDIT_STUB + wid + PANEL_EXT;
			break;
	}

	// Initialise all panels so that buttons can be set as required for initial states
	panelInit(HOME, homePanel);
	panelInit(AV_EDIT, avNamePanel);
	panelInit(PKG_NAME, pkgNamePanel);
	panelInit(PKG_XPT, pkgXptPanel);

	// Configure the Home Panel
	controlDisable(HOME, "DoEdit");		// Only enable this button once the user has selected the area, edit type, and data.

	// Disable the edit type selection buttons until the area has been selected
	for (int i = 1; i < 4; i++) controlDisable(HOME, bncs_string("Type_%1").arg(i));
	for (int i = 1; i < 5; i++) controlDisable(HOME, bncs_string("Edit_what_%1").arg(i));
	for (int i = 1; i < 4; i++) controlDisable(HOME, bncs_string("Router_%1").arg(i));

	textPut("statesheet", SELECTED, HOME, "EditBtnBG");		// Highlight the ring around the edit button.

	// Process the area names. 10 buttons are on the version 1 panel, so we can loop looking for 10 area names.
	// If less names are found, then we note the maximum number found in variable num_areas
	for (int m_id = 0; m_id < 10; m_id++) controlDisable(HOME, bncs_string("Area_name_%1").arg(m_id));		// Start by disabling all buttons

	// Read matrix data into memory array. Note that the area names are indexed from 0 and that the array is zero based.
	for (int idx = 0; idx < 10; idx++) {
		// Look for the area name token.
		matrixData[idx].areaname  = getObjectSetting(bncs_string("rtr_area_%1").arg(idx), "areaname");		

		// Look for the number of package levels.
		matrixData[idx].pkgLevels = getObjectSetting(bncs_string("rtr_area_%1").arg(idx), "package_levels").toInt();	

		// Extract the comma delimited sting of driver id's
		myToken = getObjectSetting(bncs_string("rtr_area_%1").arg(idx), "driver_ids");		
		myList = bncs_stringlist(myToken);
		for (int i = 0; i < myList.count(); i++) matrixData[idx].driverIDs[i] = myList[i].toInt();

		// Extract the comma delimited sting of driver types
		myToken = getObjectSetting(bncs_string("rtr_area_%1").arg(idx), "driver_type");		
		myList = bncs_stringlist(myToken);
		for (int i = 0; i < myList.count(); i++) matrixData[idx].driverType[i] = myList[i];
	}

	// Scan looking for first blank entry.
	num_areas = 10;							// Assume all ten areas will be active.
	for (int idx = 0; idx < 10; idx++){
		if (matrixData[idx].areaname == "")
		{
			num_areas = idx;				// Less than 10 active areas - so update the count.
			break;
		}
		else
		{
			controlEnable(HOME, bncs_string("Area_name_%1").arg(idx));
			textPut("text", matrixData[idx].areaname, HOME, bncs_string("Area_name_%1").arg(idx));
		}
	}

	// Show the HOME panel.
	panelShow(HOME, homePanel);

}


// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// Destructor - equivalent to ApplCore CLOSEDOWN
Mtx_Pkg_Edit::~Mtx_Pkg_Edit()
{
	if (mtx_registered) routerUnregister(matrix_id);
	mtx_registered = false;
	timerStop(AVEDIT_TIMER);			// Just in case...
	timerStop(PKGEDIT_TIMER);
}


// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// All button pushes and notifications come here. As there are a lot of buttons on the various panels, this callback
// identifies teh relevant panel that originated the callback, then calls a support function to process the specifics
// of the button press.
void Mtx_Pkg_Edit::buttonCallback(buttonNotify *b)
{
	if(b->panel() == HOME)
	{
		processHomePanel(b);		// Process all button pushes on the home (function selection) panel.
		return;
	}

	if (b->panel() == AV_EDIT)		// Process all button pushes on the matrix name editor panel.
	{
		processAVedit(b);
		return;
	}

	if (b->panel() == PKG_NAME)		// Process all button pushes on the packages name editor panel.
	{
		processPkgNameEdit(b);
		return;
	}

	if (b->panel() == PKG_XPT)		// Process all button pushes on the package content editor panel.
	{
		processXptEdit(b);
		return;
	}
}


// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// All revertives come here.
//
// Revertives are not used in this program, but we do need a dummy service routine as the database change callback
// seems only to be active for registered routers. We may get a revertive whilst we are registered.
int Mtx_Pkg_Edit::revertiveCallback(revertiveNotify *r)
{
	return 0;
}


// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// All database name changes come back here.
void Mtx_Pkg_Edit::databaseCallback(revertiveNotify *r)
{
	if (mtx_name_edit)
	{
		// Editing of matrix source or destination names is active.
		// See if the router is the one we are registered to get reports from.
		if (r->device() == matrix_id)
		{
			// Need to update the display in the listbox if the database changed is the baseDatabase
			// We can use the listbox support element of updated.id.xxx=yyy
			// where xxx is the listbox id (one less than the BNCS r->index() parameter)
			// and yyy is a string of the form "024 = New Name"
			if (r->database() == baseDatabase)
			{
				bncs_string updateId = bncs_string("replace.id.%1").arg(r->index() - 1);
				bncs_string updatedEntry = bncs_string("%1 = %2").arg(r->index(), '0', 3).arg(r->sInfo());
				textPut(updateId, updatedEntry ,AV_EDIT, "MtxNames");
			}
		}	// End of if (r->device() == matrix_id) ...
	}	// End of if (mtx_name_edit) ...

}


// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// All parent notifications come here.
// When this script is just one component of another dialog then our host might want to tell us things.
bncs_string Mtx_Pkg_Edit::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 "";
}


// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// Timer events come here.
void Mtx_Pkg_Edit::timerCallback( int id )
{
	switch( id )
	{
	case AVEDIT_TIMER:
		// This callback is responsible for flashing the intensity of the Update and Cancel buttons on the matrix name panel.
		// A timeout counter is used to set the maximum availabiilty of the Update request.
		mtxNameTimeOut--;					// Decrement the tiemout counter
		if ((mtxNameTimeOut & 1) == 0)
		{
			textPut("statesheet", UPDATE_LOW, AV_EDIT, "Update");
			textPut("statesheet", CANCEL_HIGH, AV_EDIT, "Cancel");
		}
		else
		{
			textPut("statesheet", UPDATE_HIGH, AV_EDIT, "Update");
			textPut("statesheet", CANCEL_LOW, AV_EDIT, "Cancel");
		}
		
		if (mtxNameTimeOut == 0)
		{
			// Stop the timeout counter, treat as if user has done a cancel.
			bncs_string name;

			timerStop(id);											// Stop the timer
			routerName(matrix_id, baseDatabase, xptid, name);			
			textPut("text", name, AV_EDIT, "LongName");				// Restore the original name
			routerName(matrix_id, baseDatabase + 2, xptid, name);
			textPut("text", name, AV_EDIT, "ShortName");			// Restore the original short name
			controlDisable(AV_EDIT, "Update");
			controlDisable(AV_EDIT, "Cancel");
			controlEnable(AV_EDIT, "LongName");
			controlEnable(AV_EDIT, "ShortName");
			controlEnable(AV_EDIT, "KB_1");
		}
		break;

	case PKGEDIT_TIMER:		
		// Package name editor Update/Cancel timeout counter processing
		pkgNameTimeOut--;
		if ((pkgNameTimeOut & 1) == 0)
		{
			textPut("statesheet", UPDATE_LOW, PKG_NAME, "Update");
			textPut("statesheet", CANCEL_HIGH, PKG_NAME, "Cancel");
		}
		else
		{
			textPut("statesheet", UPDATE_HIGH, PKG_NAME, "Update");
			textPut("statesheet", CANCEL_LOW, PKG_NAME, "Cancel");
		}

		if (pkgNameTimeOut == 0)
		{
			// Stop the timeout counter, treat as if user has done a cancel.
			timerStop(id);
			textPut("statesheet", UPDATE_LOW, PKG_NAME, "Update");
			textPut("statesheet", CANCEL_HIGH, PKG_NAME, "Cancel");
			controlDisable(PKG_NAME, "Update");
			controlDisable(PKG_NAME, "Cancel");
			// Clear the displays. Re-enable name selectors and exit edit button
			textPut("text=", PKG_NAME, "PkgNo");
			textPut("text=", PKG_NAME, "PreEdit");
			textPut("text=", PKG_NAME, "PostEdit");
			controlEnable(PKG_NAME, "AllDone");
			controlEnable(PKG_NAME, "Group");
			controlEnable(PKG_NAME, "PackageName");
			textPut("index=-1", PKG_NAME, "PackageName");
		}

		break;

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


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


// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// function processHomePanel(b) is called by the primary button callback function
// to process button pushes on the HOME panel.
void Mtx_Pkg_Edit::processHomePanel(buttonNotify *b)
{
	if (b->id().startsWith("Area_name_") && (selectedArea == NO_AREA_SELECTED))	// Area selection button pushed?
	{
		textPut("statesheet", SELECTED, HOME, b->id());		// Show the new selection on button
		selectedArea = b->id().firstInt();					// Record the active button number

		// Disable all other area selection buttons
		for (int i = 0; i < num_areas; i++){
			if (i != selectedArea) controlDisable(HOME, bncs_string("Area_name_%1").arg(i));
		}

		enableSelectEdit();									// Activate valid edit type buttons

		return;
	}	// End of if (b->id().startsWith("Area_name_"))


	if (b->id().startsWith("Type_") && (editType == 0))	// Edit type button pushed?
	{
		// Only support the first button press. Disable the other buttons
		textPut("statesheet", SELECTED, HOME, b->id());		// Show the new selection
		editType = b->id().firstInt();						// 1=src/dest names, 2=src/dest pkg names, 3=src/dst package content

		for (int i = 1; i < 4; i++){						// Disable no-selected buttons
			if (i != editType) controlDisable(HOME, bncs_string("Type_%1").arg(i));
		}

		enableEditWhat();									// Activate the "Edit What" buttons

		return;
	}	// End of if (b->id().startsWith("Type_"))


	if (b->id().startsWith("Edit_what_"))	// Has an edit what button been pushed?
	{
		// Only get here if selectedArea and editType have been selected.
		textPut("statesheet", SELECTED, HOME, b->id());		// Highlight new selection
		dataType = b->id().firstInt();

		for (int i = 1; i < 5; i++){
			if (i != dataType) controlDisable(HOME, bncs_string("Edit_what_%1").arg(i));
		}

		// Now categorise the edit request. If the request is to edit the source or destination router names, enable the router selector
		if ((editType == 1) && ((dataType == 1) || (dataType == 2)))
		{
			// Copy matrix names into the buttons
			for (int i = 2; i < 5; i++)
			{
				textPut("text", matrixData[selectedArea].driverType[i], HOME, bncs_string("Router_%1").arg(i-1)) ;
				if (matrixData[selectedArea].driverType[i] != "") 
					controlEnable(HOME, bncs_string("Router_%1").arg(i-1));
			}
			return;
		}
		
		if (editType == 2)	// Edit package name - either source or destination.
		{
			controlEnable(HOME, "DoEdit");
			doEdit_enabled = true;
			return;
		}

		if (editType == 3)	// Edit package contents - either source or destination.
		{
			controlEnable(HOME, "DoEdit");
			doEdit_enabled = true;
			return;
		}

		return;
	}	// End of if (b->id().startsWith("Edit_what_"))


	if (b->id().startsWith("Router_"))	// Has a router level been pushed?
	{
		routerLevel = b->id().firstInt();
		textPut("statesheet", SELECTED, HOME, b->id());

		for (int i = 1; i < 4; i++)
			if (i != routerLevel) controlDisable(HOME, bncs_string("Router_%1").arg(i));
	
		controlEnable(HOME, "DoEdit");
		doEdit_enabled = true;
	}


	if (b->id() == "Cancel")		// User wants to stop the selection process
	{
		clearDoEdit();				// Disable DoEdit button if enabled.
		clearRouter();
		clearEditWhat();			// Disable Edit_what_ buttons if enabled.
		clearEditType();			// Disable Type_ buttons if enabled.
		if (selectedArea >= 0)		// Deselect any highlighted area button
		{
			textPut("statesheet", DESELECTED, HOME, bncs_string("Area_name_%1").arg(selectedArea));
			selectedArea = NO_AREA_SELECTED;
		}
		// Enable all of the area identification buttons.
		for (int ix = 0; ix < num_areas; ix++) controlEnable(HOME, bncs_string("Area_name_%1").arg(ix));
	}

	if (b->id() == "DoEdit")		// Has the user pushed the EDIT button?
	{
		// Use the selected buttons to decide which panel should be shown, and setup the data for the edit.
		if (editType == 1)	// Router source or destination name edit.
		{
			// We have the area numeric id in variable selectedArea (range 0 to 9), the edit type in variable editType (range 1 to 3),
			// the type of data to edit in dataType (1=source names, 2=destination names, 3=Source package content, 4= Destination package content), 
			// and the router level in variable routerLevel (1=video, 2=audio 1 [analogue/hybrid], 3=audio 2)
			bncs_string namedata;
			bncs_string mtxName;
			
			int mtxSize;

			panelRemove(HOME);			// Hide the home panel.

			// Build the name of the matrix to edit.
			namedata = matrixData[selectedArea].areaname + " " + matrixData[selectedArea].driverType[routerLevel+1];
			switch (dataType)
			{
			case 1:
				namedata += " Source Names";
				break;
			case 2:
				namedata += " Destination Names";
				break;
			default:
				break;
			}
			// Show the name on the panel, above the listbox
			textPut("text", namedata, AV_EDIT, "Matrix_Id");
			textPut("text=", AV_EDIT, "LongName");		// Clear the Long and short name displays
			textPut("text=", AV_EDIT, "ShortName");
			controlDisable(AV_EDIT, "Cancel");			// Disable the Cancel and Update buttons
			controlDisable(AV_EDIT, "Update");

			// Insert the matrix names data into the listbox.
			textPut("clear", AV_EDIT, "MtxNames");
			matrix_id = matrixData[selectedArea].driverIDs[routerLevel+1];
			baseDatabase = dataType - 1;		// dataType = 1 for sources, 2 for destinations
			mtxSize = getRouterSize(matrix_id, baseDatabase);	// Get the matrix size
			for (int idx = 1; idx <= mtxSize; idx++)			// Fill the list box
			{
				routerName(matrix_id, baseDatabase, idx, mtxName);	// Get the source or destination name
				textPut("add", bncs_string("%1 = ").arg(idx,'0',3) + mtxName, AV_EDIT, "MtxNames");	// Insert id value and name
			}

			// Register the router for revertives - so we get database update callbacks. We will ignore all revertives!
			routerRegister(matrix_id, 1, 1);
			mtx_registered = true;
			mtx_name_edit = true;

			controlHide(AV_EDIT, "KB_1");			// Do not display the keybaord until user has selected a name to edit.
			panelShow(AV_EDIT, avNamePanel);			// Show the Router name edit panel.

			return;
		}	// end of if (editType == 1)...

		if (editType == 2)
		{
			// editType == 2 means that the user has selected to edit a package source or destination name.
			// The dataType variable identifies if the edit is to be source or destination names.
			bncs_string namedata = "Edit ";

			panelRemove(HOME);						// Remove the home (edit type selection) panel.

			// Build the name of the matrix to edit.
			namedata += matrixData[selectedArea].areaname;
			switch (dataType)
			{
			case 1:		namedata += " Source ";	break;
			case 2:		namedata += " Destination "; break;
			default:	break;
			}
			namedata += matrixData[selectedArea].driverType[routerLevel];

			// Show the name on the panel, above the listbox
			textPut("text", namedata, PKG_NAME, "MatrixName");

			// Set up the package driver ID and database variables
			pkgMatrixId = matrixData[selectedArea].driverIDs[0];
			pkgBaseDatabase = dataType - 1;

			// Set up the group namegrid display 
			textPut("device", bncs_string(pkgMatrixId), PKG_NAME, "Group");
			textPut("device.map", bncs_string(pkgMatrixId), PKG_NAME, "Group");
			textPut("page=1", PKG_NAME, "Group");
			textPut("namesdb", bncs_string(pkgBaseDatabase + 4), PKG_NAME, "Group");	// Select the group source or destination database
			textPut("mapdb=8", PKG_NAME, "Group");
			switch (pkgBaseDatabase)
			{
			case 0:		// Source Package names edit
				textPut("mapoffset", bncs_string(SRCGRPMAPBASE), PKG_NAME, "Group");
				break;
			case 1:		// Destination Package names edit
				textPut("mapoffset", bncs_string(DESTGRPMAPBASE), PKG_NAME, "Group");
				break;
			default:
				break;
			}

			// Set up the source namegrid display
			textPut("device", bncs_string(pkgMatrixId), PKG_NAME, "PackageName");
			textPut("device.map", bncs_string(pkgMatrixId), PKG_NAME, "PackageName");
			textPut("page=1", PKG_NAME, "PackageName");
			textPut("namesdb", bncs_string(pkgBaseDatabase), PKG_NAME, "PackageName");
			textPut("mapdb=8", PKG_NAME, "PackageName");
			switch (pkgBaseDatabase)
			{
			case 0:		// Source Package names edit
				textPut("mapoffset", bncs_string(0), PKG_NAME, "PackageName");
				break;
			case 1:		// Destination Package names edit
				textPut("mapoffset", bncs_string(100), PKG_NAME, "PackageName");
				break;
			default:
				break;
			}

			// Set displays to initial states and contents
			textPut("text=", PKG_NAME, "PkgNo");
			textPut("text=", PKG_NAME, "PreEdit");
			textPut("text=", PKG_NAME, "PostEdit");
			controlHide(PKG_NAME, "KB_1");
			controlDisable(PKG_NAME, "Cancel");
			controlDisable(PKG_NAME, "Update");

			// Display the package name edit panel.
			panelShow(PKG_NAME, pkgNamePanel);
			
			// Select group 1 by pushing the button with an index of 1
			textPut("button.001=released", PKG_NAME, "Group");

			return;
		}	// end of if (editType == 2)

		if (editType == 3)
		{
			// editType == 3 means that the user wants to edit the package contents for an area that supports packaged routing.
			// For most of the areas, this means that the packager manages two levels - video and audio. Studio C is an exception
			// as it has both an analogue audio router (level 2) and a digital audio router (level 3 - part of Studer desk).
			bncs_string editTitle = "Edit ";

			panelRemove(HOME);						// Hide the HOME panel.

			// Show what we are editing
			editTitle += matrixData[selectedArea].areaname;
			switch (dataType)
			{
			case 3:		editTitle += " Source Package Content"; break;
			case 4:		editTitle += " Destination Package Content"; break;
			default:	break;
			}
			textPut("text", editTitle, PKG_XPT, "MatrixName");

			// Prepare the display areas. Start with a default minimal set.
			textPut("text=", PKG_XPT, "PkgNo");		// Blank the package ID display
			textPut("text=", PKG_XPT, "V_Name");	// Blank the element names
			textPut("text=", PKG_XPT, "A_Name");
			textPut("text=", PKG_XPT, "D_Name");
			textPut("text=", PKG_XPT, "V_ID");		// Blank the element IDs
			textPut("text=", PKG_XPT, "A_ID");
			textPut("text=", PKG_XPT, "D_ID");
			controlDisable(PKG_XPT, "Update");		// Disable undeeded controls
			controlDisable(PKG_XPT, "Element");
			controlDisable(PKG_XPT, "V_Name");
			controlDisable(PKG_XPT, "A_Name");
			controlDisable(PKG_XPT, "D_Name");
			controlDisable(PKG_XPT, "ClrVid");		// Disable the clear level buttons.
			controlDisable(PKG_XPT, "ClrAud");
			controlDisable(PKG_XPT, "ClrAES");

			// Inital crosspoint selection data set to "none".
			xptVidNow = 0;
			xptAudNow = 0;
			xptAESNow = 0;
			xptVidNext = 0;
			xptAudNext = 0;
			xptAESNext = 0;

			// Set up the working variables based on the selections.
			namesDevID = matrixData[selectedArea].driverIDs[0];		// Get the current package names driver ID
			xptDevID = matrixData[selectedArea].driverIDs[1];		// Get the current package contents driver ID
			numLevels = matrixData[selectedArea].pkgLevels;
			switch (dataType)
			{
				case 3:		xptBaseDatabase = 0; break;
				case 4:		xptBaseDatabase = 1; break;
				default:	xptBaseDatabase = 0; break;
			}

			// Populate the source group grid with the required data. 
			textPut("device", bncs_string(namesDevID), PKG_XPT, "Group");
			textPut("device.map", bncs_string(namesDevID), PKG_XPT, "Group");
			textPut("page=1", PKG_XPT, "Group");
			textPut("namesdb", bncs_string(xptBaseDatabase + 4), PKG_XPT, "Group");
			textPut("mapdb=8", PKG_XPT, "Group");
			switch (xptBaseDatabase)
			{
				case 0:		textPut("mapoffset", bncs_string(SRCGRPMAPBASE), PKG_XPT, "Group"); break;		// Source Package names edit
				case 1:		textPut("mapoffset", bncs_string(DESTGRPMAPBASE), PKG_XPT, "Group"); break;		// Destination Package names edit
				default:	break;
			}

			// Set up the source name display
			textPut("device", bncs_string(namesDevID), PKG_XPT, "PackageName");
			textPut("device.map", bncs_string(namesDevID), PKG_XPT, "PackageName");
			textPut("page=1", PKG_XPT, "PackageName");
			textPut("namesdb", bncs_string(xptBaseDatabase), PKG_XPT, "PackageName");
			textPut("mapdb=8", PKG_XPT, "PackageName");
			switch (xptBaseDatabase)
			{
				case 0:		textPut("mapoffset", bncs_string(0), PKG_XPT, "PackageName"); break;	// Source Package names edit
				case 1:		textPut("mapoffset", bncs_string(100), PKG_XPT, "PackageName"); break;	// Destination Package names edit
				default:	break;
			}

			panelShow(PKG_XPT, pkgXptPanel);		// Show crosspoint content editor panel

			// Push the first group button
			textPut("button.001=released", PKG_XPT, "Group");

			return;
		}	// End of if (editType == 3) ...

	}	// End of if (b->id() == "DoEdit")


}	// End of procesHomePanel().


// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// function enableSelectEdit() uses the data stored in object_settings.xml
// for the selected area to decide which edit type buttons should be enabled.

void Mtx_Pkg_Edit::enableSelectEdit(void)
{
	// We need to read the package name and crosspoint content device ids from object settings
	// to see what functions the selected button may need to edit.
	// Variable selectedArea has the id of the area, so we need to get object rtr_area_X to read the settings

	if ((matrixData[selectedArea].driverIDs[0] == 0) && (matrixData[selectedArea].driverIDs[1] == 0))		// If there are no package name or package content drivers, only have routers
	{
		// No package editing, only name editing
		controlEnable(HOME, "Type_1");				// Enable name edit button
		controlDisable(HOME, "Type_2");				// Disable source/destination package name edit button
		controlDisable(HOME, "Type_3");				// Disable package content edit button
	}
	else
	{
		controlEnable(HOME, "Type_1");						// Enable name edit button
		controlEnable(HOME, "Type_2");						// Enable source/destination package name edit button
		controlEnable(HOME, "Type_3");						// Enable package content edit button
		textPut("statesheet", DESELECTED, HOME, "Type_1");
		textPut("statesheet", DESELECTED, HOME, "Type_2");
		textPut("statesheet", DESELECTED, HOME, "Type_3");
	}

}	// End of void Mtx_Pkg_Edit::enableSelectEdit(void)


// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//
void Mtx_Pkg_Edit::enableEditWhat(void)
{
	if ((selectedArea >= 0) && (editType > 0))		// Check that there are sufficent selections to enable one or more edit_what buttons
	{
		if (editType == 1)							// Router source/destination names
		{
			controlEnable(HOME, "Edit_what_1");
			controlEnable(HOME, "Edit_what_2");
		}
		if (editType == 2)							// If the Package Name Edit button is selected
		{
			controlEnable(HOME, "Edit_what_1");		// Enable the package content buttons.
			controlEnable(HOME, "Edit_what_2");
		}
		if (editType == 3)							// Package Content Edit
		{
			controlEnable(HOME, "Edit_what_3");		// Enable source and destination content edit
			controlEnable(HOME, "Edit_what_4");
		}
		editWhat_enabled = true;
	}
	else
	{
		controlDisable(HOME, "Edit_what_1");
		controlDisable(HOME, "Edit_what_2");
		controlDisable(HOME, "Edit_what_3");
		controlDisable(HOME, "Edit_what_4");
		editWhat_enabled = false;
	}
}


// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// Check if DoEdit button enabled.
// Disable button if was enabled.
void Mtx_Pkg_Edit::clearDoEdit(void)
{
	if (doEdit_enabled)
	{
		controlDisable(HOME, "DoEdit");
		doEdit_enabled = false;
	}
}

// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// Clear all router level selection buttons.
void Mtx_Pkg_Edit::clearRouter(void)
{
	textPut("text=", HOME, "Router_1");
	textPut("text=", HOME, "Router_2");
	textPut("text=", HOME, "Router_3");
	if (routerLevel !=0) textPut("statesheet", DESELECTED, HOME, bncs_string("Router_%1").arg(routerLevel));
	controlDisable(HOME, "Router_1");
	controlDisable(HOME, "Router_2");
	controlDisable(HOME, "Router_3");
	routerLevel = 0;
}


// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// Clear all File type selection buttons.
void Mtx_Pkg_Edit::clearEditWhat(void)
{
	if (editWhat_enabled)
	{
		if (dataType > 0)			// Any button selected?
		{
			textPut("statesheet", DESELECTED, HOME, bncs_string("Edit_what_%1").arg(dataType));
			dataType = 0;
		}

		controlDisable(HOME, "Edit_what_1");
		controlDisable(HOME, "Edit_what_2");
		controlDisable(HOME, "Edit_what_3");
		controlDisable(HOME, "Edit_what_4");
		editWhat_enabled = false;
	}
}


// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// 
void Mtx_Pkg_Edit::clearEditType(void)
{
	if (editType > 0)					// If there is a selected button, clear the selection
	{
		textPut("statesheet", DESELECTED, HOME, bncs_string("Type_%1").arg(editType));
		editType = 0;
	}

	controlDisable(HOME, "Type_1");		// Disable the controls
	controlDisable(HOME, "Type_2");
	controlDisable(HOME, "Type_3");
}


// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// function processAVedit(b) is called to process button presses
// on the Audio Video Element edit panel.
void Mtx_Pkg_Edit::processAVedit(buttonNotify *b)
{
	if (b->id() == "MtxNames")
	{
		// User has selected a new index in the list box.
		// Use the index to show the long and short names.
		bncs_string name;
		
		xptid = b->value().firstInt(); // Extract the current index
		routerName(matrix_id, baseDatabase, xptid, name);
		textPut("text", name, AV_EDIT, "LongName");
		routerName(matrix_id, baseDatabase + 2, xptid, name);
		textPut("text", name, AV_EDIT, "ShortName");
		return;
	}	// End of if (b->id() == "MtxNames")

	if (b->id() == "LongName")
	{
		bncs_string lname;

		databaseOffset = 0;
		textGet("text", AV_EDIT, "LongName", lname);	// Fetch the name to edit
		textPut("text", lname, AV_EDIT, "KB_1");		// Copy to the keyboard
		controlShow(AV_EDIT, "KB_1");					// Show the keyboard
		controlDisable(AV_EDIT, "LongName");
		controlDisable(AV_EDIT, "ShortName");
		controlDisable(AV_EDIT, "Home");
		controlDisable(AV_EDIT, "MtxNames");
		controlDisable(AV_EDIT, "Cancel");
		controlDisable(AV_EDIT, "Update");
		return;
	}

	if (b->id() == "ShortName")
	{
		bncs_string sname;

		databaseOffset = 2;
		textGet("text", AV_EDIT, "ShortName", sname);	// Fetch the name to edit
		textPut("text", sname, AV_EDIT, "KB_1");		// Copy to the keyboard
		controlShow(AV_EDIT, "KB_1");					// Show the keyboard
		controlDisable(AV_EDIT, "LongName");
		controlDisable(AV_EDIT, "ShortName");
		controlDisable(AV_EDIT, "Home");
		controlDisable(AV_EDIT, "MtxNames");
		controlDisable(AV_EDIT, "Cancel");
		controlDisable(AV_EDIT, "Update");
		return;
	}

	if (b->id() == "KB_1")
	{
		bncs_string original, edited;

		// Need to fetch the edited name from the keyboard.
		// Enable the UPDATE/CANCEL buttons if the name has been changed.
		if (databaseOffset == 0)
			textGet("text", AV_EDIT, "LongName", original);
		else
			textGet("text", AV_EDIT, "ShortName", original);

		textGet("text", AV_EDIT, "KB_1", edited);

		// Truncate the string to the maximum allowed length
		if (databaseOffset == 0)
			edited.truncate(LONGNAME_MAX);
		else
			edited.truncate(SHORTNAME_MAX);

		textPut("text=", AV_EDIT, "KB_1");
		controlHide(AV_EDIT, "KB_1");

		if (original != edited)
		{
			edit_name = edited;

			if (databaseOffset == 0)
			{
				textPut("text", edit_name, AV_EDIT, "LongName");
				controlEnable(AV_EDIT, "LongName");
			}
			else
			{
				textPut("text", edit_name, AV_EDIT, "ShortName");
				controlEnable(AV_EDIT, "ShortName");
			}

			controlEnable(AV_EDIT, "Update");
			controlEnable(AV_EDIT, "Cancel");
			textPut("statesheet", UPDATE_LOW, AV_EDIT, "Update");
			textPut("statesheet", CANCEL_HIGH, AV_EDIT, "Cancel");
			mtxNameTimeOut = 10;
			timerStart(AVEDIT_TIMER, 1000);
		}
		else
		{
			controlEnable(AV_EDIT, "LongName");
			controlEnable(AV_EDIT, "ShortName");
			controlEnable(AV_EDIT, "MtxNames");
			controlEnable(AV_EDIT, "Home");
		}
		return;
	}

	// The Cancel button action
	if (b->id() == "Cancel")
	{
		bncs_string name;

		routerName(matrix_id, baseDatabase, xptid, name);
		textPut("text", name, AV_EDIT, "LongName");
		routerName(matrix_id, baseDatabase + 2, xptid, name);
		textPut("text", name, AV_EDIT, "ShortName");
		controlEnable(AV_EDIT, "LongName");
		controlEnable(AV_EDIT, "ShortName");
		controlEnable(AV_EDIT, "MtxNames");
		controlEnable(AV_EDIT, "Home");
		controlDisable(AV_EDIT, "Cancel");
		controlDisable(AV_EDIT, "Update");
	}

	// The Update button action
	if (b->id() == "Update")
	{
		// Send update to the network, then re-enable edit controls.
		routerModify(matrix_id, baseDatabase + databaseOffset, xptid, edit_name, false);
		controlDisable(AV_EDIT, "Cancel");
		controlDisable(AV_EDIT, "Update");
		controlEnable(AV_EDIT, "LongName");
		controlEnable(AV_EDIT, "ShortName");
		controlEnable(AV_EDIT, "MtxNames");
		controlEnable(AV_EDIT, "Home");
		return;
	}

	if (b->id() == "Home")
	{
		// Tidy the AV_EDIT form here.
		if (mtx_registered) routerUnregister(matrix_id); // Kill the matrix registration.
		mtx_registered = false;
		mtx_name_edit = false;
		matrix_id = 0;
		baseDatabase = 0;
		databaseOffset = 0;
		xptid = 0;
		textPut("text=", AV_EDIT, "LongName");			// Clear the long name display
		textPut("text=", AV_EDIT, "ShortName");			// Clear the short name display
		controlDisable(AV_EDIT, "Update");				// Disable Update button
		controlDisable(AV_EDIT, "Cancel");				// Disable Cancel button
		textPut("clear", AV_EDIT, "MtxNames");			// Clear listbox contents

		// Tidy the Home form
		clearDoEdit();				// Disable DoEdit button if enabled.
		clearRouter();
		clearEditWhat();			// Disable Edit_what_ buttons if enabled.
		clearEditType();			// Disable Type_ buttons if enabled.
		textPut("statesheet", DESELECTED, HOME, bncs_string("Area_name_%1").arg(selectedArea));
		selectedArea = NO_AREA_SELECTED;
		editType = 0;
		dataType = 0;
		routerLevel = 0;
		for (int ix = 0; ix < num_areas; ix++) controlEnable(HOME, bncs_string("Area_name_%1").arg(ix));

		// Then show the home form
		panelRemove(AV_EDIT);
		panelShow(HOME, homePanel);
		return;
	}
}	// End of void Mtx_Pkg_Edit::processAVedit(buttonNotify *b)


// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// function processPkgNameEdit(b) is called to process button presses
// on the package name edit panel.
void Mtx_Pkg_Edit::processPkgNameEdit(buttonNotify *b)
{
	// Look for a button press on the group selector grid
	if (b->id() == "Group")
	{
		// A group selection button has been pressed.
		if (b->command() == "index")
		{
			// If the index is greater than 0, it is a new page selection for the name bank
			if (b->value().toInt() > 0)	textPut("page", b->value(), PKG_NAME, "PackageName");
		}
		return;
	}	// End of if (b->id() == "Group") ...

	// Look for a button press on the name selector grid
	if (b->id() == "PackageName")
	{
		if ((b->command() == "index") && (b->value().toInt() > 0))
		{
			// We need to copy the name into the PreEdit display, into the Keyboard,
			// enable the keyboard, and disable the update cancel and AllDone buttons.
			textPut("text", b->value(), PKG_NAME, "PkgNo");		// Show the index in the display
			mtxNameIndex = b->value().firstInt();				// Note the index in global variable
			routerName(pkgMatrixId, dataType - 1, mtxNameIndex, preEditName);
			textPut("text", preEditName, PKG_NAME, "PreEdit");
			textPut("text=", PKG_NAME, "PostEdit");
			textPut("text", preEditName, PKG_NAME, "KB_1");
			controlShow(PKG_NAME, "KB_1");
			controlDisable(PKG_NAME, "AllDone");
			controlDisable(PKG_NAME, "Group");
			controlDisable(PKG_NAME, "PackageName");
		}
		return;
	}

	if (b->id() == "KB_1")	// Has the return key been pressed on the keyboard?
	{
		textGet("text", PKG_NAME, "KB_1", postEditName);		// Read the edited name from the keyboard
		textPut("text", postEditName, PKG_NAME, "PostEdit");	// Show edited name in the panel display
		controlHide(PKG_NAME, "KB_1");
		textPut("text=", PKG_NAME, "KB_1");

		// If the name has been changed then enable the Canel and Update buttons.
		if (preEditName != postEditName)
		{
			controlEnable(PKG_NAME, "Cancel");
			controlEnable(PKG_NAME, "Update");
			// Start the timeout counter
			pkgNameTimeOut = 10;
			timerStart(PKGEDIT_TIMER, 1000);
		}
		else
		{
			controlEnable(PKG_NAME, "Group");
			controlEnable(PKG_NAME, "PackageName");
			controlEnable(PKG_NAME, "AllDone");
			textPut("index=-1", PKG_NAME, "PackageName");		// Clear the selected crosspoint
			textPut("text=", PKG_NAME, "PkgNo");
			textPut("text=", PKG_NAME, "PreEdit");
			textPut("text=", PKG_NAME, "PostEdit");
			preEditName = "";
			postEditName = "";
		}

		return;
	}	// End of if (b->id() == "KB_1") ...

	if (b->id() == "Update")		// Update Files button pressed?
	{
		// Stop the timeout counter, set default colours on the cancel and update buttons.
		timerStop(PKGEDIT_TIMER);
		textPut("statesheet", UPDATE_LOW, PKG_NAME, "Update");
		textPut("statesheet", CANCEL_HIGH, PKG_NAME, "Cancel");
		controlDisable(PKG_NAME, "Update");
		controlDisable(PKG_NAME, "Cancel");

		// Send the updated name to the network. The database to update is the dataType variable value - 1.
		routerModify(pkgMatrixId, pkgBaseDatabase, mtxNameIndex, postEditName, false);

		// Clear the displays. Re-enable name selectors and exit edit button
		textPut("text=", PKG_NAME, "PkgNo");
		textPut("text=", PKG_NAME, "PreEdit");
		textPut("text=", PKG_NAME, "PostEdit");
		controlEnable(PKG_NAME, "AllDone");
		controlEnable(PKG_NAME, "Group");
		controlEnable(PKG_NAME, "PackageName");
		textPut("index=-1", PKG_NAME, "PackageName");

		return;
	}	// End of if (b->id() == "Update") ...

	if (b->id() == "Cancel")		// Cancel this name edit button pressed?
	{
		// Stop the timeout counter, set default colours on the cancel and update buttons.
		timerStop(PKGEDIT_TIMER);
		textPut("statesheet", UPDATE_LOW, PKG_NAME, "Update");
		textPut("statesheet", CANCEL_HIGH, PKG_NAME, "Cancel");
		controlDisable(PKG_NAME, "Update");
		controlDisable(PKG_NAME, "Cancel");

		// Clear the displays. Re-enable name selectors and exit edit button
		textPut("text=", PKG_NAME, "PkgNo");
		textPut("text=", PKG_NAME, "PreEdit");
		textPut("text=", PKG_NAME, "PostEdit");
		controlEnable(PKG_NAME, "AllDone");
		controlEnable(PKG_NAME, "Group");
		controlEnable(PKG_NAME, "PackageName");
		textPut("index=-1", PKG_NAME, "PackageName");

		return;
	}	// End of if (b->id() == "Cancel") ...



	if (b->id() == "AllDone")	// Has the "Go Home button been pressed?
	{
		// Tidy the PKG_NAME form here. ***
		textPut("text=", PKG_NAME, "PkgNo");
		textPut("text=", PKG_NAME, "PreEdit");
		textPut("text=", PKG_NAME, "PostEdit");
			
		// if ()   // If the timeout timer is running, kill it. 
		{
			textPut("statesheet", UPDATE_LOW, PKG_NAME, "Update");
			textPut("statesheet", CANCEL_HIGH, PKG_NAME, "Cancel");
		}


		// Tidy the Home form
		clearDoEdit();				// Disable DoEdit button if enabled.
		clearRouter();
		clearEditWhat();			// Disable Edit_what_ buttons if enabled.
		clearEditType();			// Disable Type_ buttons if enabled.
		textPut("statesheet", DESELECTED, HOME, bncs_string("Area_name_%1").arg(selectedArea));
		selectedArea = NO_AREA_SELECTED;
		editType = 0;
		dataType = 0;
		routerLevel = 0;
		for (int ix = 0; ix < num_areas; ix++) controlEnable(HOME, bncs_string("Area_name_%1").arg(ix));

		panelRemove(PKG_NAME);
		panelShow(HOME, homePanel);
	}	// End of if (b->id() == "AllDone")

}	// End of function void Mtx_Pkg_Edit::processPkgNameEdit(buttonNotify *b)


// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// function processXptEdit(b) is called to process button presses
// on the package content edit panel.
void Mtx_Pkg_Edit::processXptEdit(buttonNotify *b)
{
	// ==========================================================
	if (b->id() == "Group")		// Has someone pushed a group selection button
	{
		if (b->command() == "index")
		{
			// If the index is greater than 0, it is a new page selection for the name bank
			if (b->value().toInt() > 0)	textPut("page", b->value(), PKG_XPT, "PackageName");
		}
		return;
	}

	// ==========================================================
	if (b->id() == "PackageName")	// Has someone selected a source package?
	{
		if ((b->command() == "index") && (b->value().toInt() > 0))
		{
			bncs_string t1, xptlist, l1, l2, l3, ltmp;

			textPut("text", b->value(), PKG_XPT, "PkgNo");		// Show the index in the display
			curPkg = b->value().firstInt();						// Note the index in global variable
			routerName(namesDevID, xptBaseDatabase, curPkg, t1);
			textPut("text", t1, PKG_XPT, "PkgName");

			// Get the comma delimited list
			routerName(xptDevID, xptBaseDatabase, curPkg, xptlist);
			// Actions depend on the number of levels switched.
			switch (matrixData[selectedArea].pkgLevels)
			{
			case 2:
				xptlist.split(',', l1, l2);		// Split into video and audio level strings
				xptVidNow = l1.toInt();			// Part 1 contains the video crosspoint. If the l1 string == "-", toInt() returns 0
				xptVidNext = xptVidNow;			// Set the updated crosspoint data to match the current selection
				xptAudNow = l2.toInt();			// Repeat the process for the audio crosspoint
				xptAudNext = xptAudNow;
				xptAESNow = 0;					// Set layer 3 to not used value
				xptAESNext = 0;
				// Show the video level on the displays
				routerName(matrixData[selectedArea].driverIDs[2], xptBaseDatabase, xptVidNow, t1);	// Get the video name intp temp string t1
				textPut("text", l1, PKG_XPT, "V_ID");			// Put the id into the numeric display
				textPut("text", t1, PKG_XPT, "V_Name");			// Put the name in the video name display
				// Show the audio level on the displays
				routerName(matrixData[selectedArea].driverIDs[3], xptBaseDatabase, xptAudNow, t1);	// Get the audio name into temp string t1
				textPut("text", l2, PKG_XPT, "A_ID");			// Put the id into the numeric display
				textPut("text", t1, PKG_XPT, "A_Name");			// Put the name in the audio name display
				controlEnable(PKG_XPT, "ClrVid");				// Enable the video clear level button
				controlEnable(PKG_XPT, "ClrAud");				// Enable the audio clear level button
				controlEnable(PKG_XPT, "V_Name");				// Allow the user to select the video name to change the video selection
				controlEnable(PKG_XPT, "A_Name");				// Allow the user to select the audio name to change the audio selection
				break;

			case 3:			// Studio C has 3 levels
				xptlist.split(',', l1, ltmp);		// Split into video/audio1/audio2 level strings.
				xptVidNow = l1.toInt();				// l1 has the video id, convert string to integer.
				xptVidNext = xptVidNow;				// Before we start the edit, new id equals the old id.
				ltmp.split(',', l2, l3);			// Split the audio level pairs into two discrete levels.
				xptAudNow = l2.toInt();				// l2 has the analogue/primary audio selection.
				xptAudNext = xptAudNow;				// Set updated to equal present id
				xptAESNow = l3.toInt();				// Extract the second audio level (StC Studer router)
				xptAESNext = xptAESNow;				// Set updated to match the present id
				// Show the video level on the displays
				routerName(matrixData[selectedArea].driverIDs[2], xptBaseDatabase, xptVidNow, t1);
				textPut("text", l1, PKG_XPT, "V_ID");
				textPut("text", t1, PKG_XPT, "V_Name");
				// Show the first audio level on the displays
				routerName(matrixData[selectedArea].driverIDs[3], xptBaseDatabase, xptAudNow, t1);
				textPut("text", l2, PKG_XPT, "A_ID");
				textPut("text", t1, PKG_XPT, "A_Name");
				// Show the second audio level on the displays
				routerName(matrixData[selectedArea].driverIDs[4], xptBaseDatabase, xptAESNow, t1);
				textPut("text", l3, PKG_XPT, "D_ID");
				textPut("text", t1, PKG_XPT, "D_Name");
				controlEnable(PKG_XPT, "ClrVid");	// Enable the clear level selection buttons	
				controlEnable(PKG_XPT, "ClrAud");
				controlEnable(PKG_XPT, "ClrAES");
				controlEnable(PKG_XPT, "V_Name");	// Enable the name select push buttons.
				controlEnable(PKG_XPT, "A_Name");
				controlEnable(PKG_XPT, "D_Name");
				break;
			}

			pkgXptEdit = false;							// Not yet have an active level
			controlDisable(PKG_XPT, "AllDone");
			controlDisable(PKG_XPT, "Group");
			controlDisable(PKG_XPT, "PackageName");
		}
		return;
	}	// End of if (b->id() == "PackageName")

	
	// ==========================================================
	if (b->id() == "V_Name")		// User wants to edit video xpt level?
	{
		// Set up the selector for the SDI sources
		routerLevel = LevelId::Video;
		configPkgSelect(routerLevel);	// Set up the selector for the video level
		disableNamesClear();			// Disable the selector buttons for levels and clearing.

		return;
	}

	// ==========================================================
	if (b->id() == "A_Name")		// User wants to edit main audio xpt level?
	{
		routerLevel = LevelId::Audio1;				// Set the level to main audio
		configPkgSelect(routerLevel);
		disableNamesClear();

		return;
	}

	// ==========================================================
	if (b->id() == "D_Name")		// User wants to edit digital audio xpt level?
	{
		routerLevel = LevelId::Audio2;
		configPkgSelect(routerLevel);
		disableNamesClear();

		return;
	}

	// ==========================================================
	if (b->id() == "LevelGroup")		// Change the source XPT select group?
	{
		if (b->command() == "index")
		{
			// If the index is greater than 0, it is a new page selection for the name bank
			if (b->value().toInt() > 0)	textPut("page", b->value(), PKG_XPT, "Element");
		}
		return;
	}

	// ==========================================================
	if (b->id() == "Element")		// User has pressed an element selector.
	{
		// Button index has been returned in the b->value(). Save this as the new xpt, and show the name of the source
		// The detail depends on the active level
		bncs_string xptname;
		if (b->value() != -1)
		{
			switch (routerLevel)
			{
			case LevelId::Video:
				xptVidNext = b->value().toInt();	// Note the crosspoint id
				textPut("text", b->value(), PKG_XPT, "V_ID");
				routerName(matrixData[selectedArea].driverIDs[2], xptBaseDatabase, xptVidNext, xptname);
				textPut("text", xptname, PKG_XPT, "V_Name");
				break;

			case LevelId::Audio1:
				xptAudNext = b->value().toInt();	// Note the crosspoint id
				textPut("text", b->value(), PKG_XPT, "A_ID");
				routerName(matrixData[selectedArea].driverIDs[3], xptBaseDatabase, xptAudNext, xptname);
				textPut("text", xptname, PKG_XPT, "A_Name");
				break;

			case LevelId::Audio2:
				xptAESNext = b->value().toInt();	// Note the crosspoint id
				textPut("text", b->value(), PKG_XPT, "D_ID");
				routerName(matrixData[selectedArea].driverIDs[4], xptBaseDatabase, xptAESNext, xptname);
				textPut("text", xptname, PKG_XPT, "D_Name");
				break;
			}	// End of switch (routerLevel)

			// Re-enable edit level selectors
			controlEnable(PKG_XPT, "ClrVid");
			controlEnable(PKG_XPT, "ClrAud");
			controlEnable(PKG_XPT, "V_Name");
			controlEnable(PKG_XPT, "A_Name");
			if (matrixData[selectedArea].pkgLevels == 3)
			{
				controlEnable(PKG_XPT, "D_Name");
				controlEnable(PKG_XPT, "ClrAES");
			}

			// Clear down the group and element selections
			configPkgClear();

			// Check to see if an update may be needed, enable button if so.
			if ((xptVidNow != xptVidNext) || (xptAudNow != xptAudNext) || (xptAESNow != xptAESNext))
				controlEnable(PKG_XPT, "Update");
			else
				controlDisable(PKG_XPT, "Update");
		}	// End of if (b->value() != -1)
		return;
	}	// End of if  (b->id() == "Element")

	// ==========================================================
	if (b->id() == "ClrVid")		// User pressed the Clear video level button?
	{
		// Delete the video level crosspoint
		xptVidNext = 0;
		textPut("text=", PKG_XPT, "V_Name");
		textPut("text=0", PKG_XPT, "V_ID");
		// Check to see if an update may be needed, enable button if so.
		if ((xptVidNow != xptVidNext) || (xptAudNow != xptAudNext) || (xptAESNow != xptAESNext))
			controlEnable(PKG_XPT, "Update");
		else
			controlDisable(PKG_XPT, "Update");
		return;
	}

	// ==========================================================
	if (b->id() == "ClrAud")		// User pressed the Clear audio level button?
	{
		// Delete the audio level crosspoint
		xptAudNext = 0;
		textPut("text=", PKG_XPT, "A_Name");
		textPut("text=0", PKG_XPT, "A_ID");
		// Check to see if an update may be needed, enable button if so.
		if ((xptVidNow != xptVidNext) || (xptAudNow != xptAudNext) || (xptAESNow != xptAESNext))
			controlEnable(PKG_XPT, "Update");
		else
			controlDisable(PKG_XPT, "Update");
		return;
	}

	// ==========================================================
	if (b->id() == "ClrAES")		// // User pressed the Clear audio level 2 button?
	{
		// Delete the audio level crosspoint
		xptAESNext = 0;
		textPut("text=", PKG_XPT, "D_Name");
		textPut("text=0", PKG_XPT, "D_ID");
		// Check to see if an update may be needed, enable button if so.
		if ((xptVidNow != xptVidNext) || (xptAudNow != xptAudNext) || (xptAESNow != xptAESNext))
			controlEnable(PKG_XPT, "Update");
		else
			controlDisable(PKG_XPT, "Update");
		return;
	}

	// ==========================================================
	if (b->id() == "Update")			// Has user asked to update the files?
	{
		bncs_string xpt_list("");

		// Format the comma delimited crosspoint list.
		if (xptVidNext > 0)				// Start with the video level
			xpt_list = bncs_string("%1,").arg(xptVidNext);
		else
			xpt_list = bncs_string("-,");
			
		// Add the audio level
		if (xptAudNext > 0)
			xpt_list += bncs_string("%1").arg(xptAudNext);
		else
			xpt_list += bncs_string("-");

		// If the number of levels is 3, then add the third element
		if (numLevels == 3)
		{
			if (xptAESNext > 0)
				xpt_list += bncs_string(",%1").arg(xptAESNext);
			else
				xpt_list += bncs_string(",-");
		}

		// Now send the string to the network.
		routerModify(xptDevID, xptBaseDatabase, curPkg, xpt_list, false);

		// Tidy the displays
		clearPkgXptEdit();

		return;
	}	// End of if (b->id() == "")


	// ==========================================================
	if (b->id() == "Cancel")	// Has the user requested to adandon this crosspoint edit
	{
		// Whatever the state of the edits, need to clear all selections for the current edit, and return to source select.
		clearPkgXptEdit();
		return;

	}	// End of if (b->id() == "Cancel")


	// ==========================================================
	if (b->id() == "AllDone")	// Has the "Go Home button been pressed?
	{
		// Tidy the PKG_XPT form here. ***
		textPut("text=", PKG_XPT, "PkgNo");
		textPut("text=", PKG_XPT, "V_Name");
		textPut("text=", PKG_XPT, "A_Name");
		textPut("text=", PKG_XPT, "V_Name");
		textPut("text=", PKG_XPT, "V_ID");
		textPut("text=", PKG_XPT, "A_ID");
		textPut("text=", PKG_XPT, "D_ID");

		// if ()   // If the timeout timer is running, kill it. 
		{
			textPut("statesheet", UPDATE_LOW, PKG_NAME, "Update");
			textPut("statesheet", CANCEL_HIGH, PKG_NAME, "Cancel");
		}


		// Tidy the Home form
		clearDoEdit();				// Disable DoEdit button if enabled.
		clearRouter();
		clearEditWhat();			// Disable Edit_what_ buttons if enabled.
		clearEditType();			// Disable Type_ buttons if enabled.
		textPut("statesheet", DESELECTED, HOME, bncs_string("Area_name_%1").arg(selectedArea));
		selectedArea = NO_AREA_SELECTED;
		editType = 0;
		dataType = 0;
		routerLevel = 0;
		for (int ix = 0; ix < num_areas; ix++) controlEnable(HOME, bncs_string("Area_name_%1").arg(ix));

		panelRemove(PKG_XPT);
		panelShow(HOME, homePanel);
		return;
	}	// End of if (b->id() == "AllDone")

}	// End of function void Mtx_Pkg_Edit::processXptEdit(buttonNotify *b)


// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// Load the database and device parameters for the XPT edit.
// Parameter rl is the router level index into array matrixData[]
void Mtx_Pkg_Edit::configPkgSelect(int rl)
{
	// Set up the level group data
	textPut("device", bncs_string(matrixData[selectedArea].driverIDs[rl]), PKG_XPT, "LevelGroup");
	textPut("device.map", bncs_string(matrixData[selectedArea].driverIDs[rl]), PKG_XPT, "LevelGroup");
	textPut("page=1", PKG_XPT, "LevelGroup");
	textPut("namesdb", bncs_string(xptBaseDatabase + 4), PKG_XPT, "LevelGroup");
	textPut("mapdb=8", PKG_XPT, "LevelGroup");
	switch (xptBaseDatabase)
	{
		case 0:		textPut("mapoffset", bncs_string(SRCGRPMAPBASE), PKG_XPT, "LevelGroup"); break;	// Source Package names edit
		case 1:		textPut("mapoffset", bncs_string(DESTGRPMAPBASE), PKG_XPT, "LevelGroup"); break;// Destination Package names edit
		default:	break;
	}

	// Set up the level item data
	textPut("device", bncs_string(matrixData[selectedArea].driverIDs[rl]), PKG_XPT, "Element");
	textPut("device.map", bncs_string(matrixData[selectedArea].driverIDs[rl]), PKG_XPT, "Element");
	textPut("page=1", PKG_XPT, "Element");
	textPut("namesdb", bncs_string(xptBaseDatabase), PKG_XPT, "Element");
	textPut("mapdb=8", PKG_XPT, "Element");
	switch (xptBaseDatabase)
	{
		case 0:		textPut("mapoffset", bncs_string(0), PKG_XPT, "Element"); break;	// Source Package names edit
		case 1:		textPut("mapoffset", bncs_string(100), PKG_XPT, "Element"); break;	// Destination Package names edit
		default:	break;
	}

	controlEnable(PKG_XPT, "LevelGroup");
	controlEnable(PKG_XPT, "Element");

	textPut("button.001=released", PKG_XPT, "LevelGroup");
	return;
}


// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// Clear the database and device parameters for the XPT edit.
void Mtx_Pkg_Edit::configPkgClear(void)
{
	// Set up the level group data
	textPut("device", 0, PKG_XPT, "LevelGroup");
	textPut("device.map", 0, PKG_XPT, "LevelGroup");
	textPut("page=1", PKG_XPT, "LevelGroup");
	textPut("namesdb", 0, PKG_XPT, "LevelGroup");
	textPut("mapdb=8", PKG_XPT, "LevelGroup");
	switch (xptBaseDatabase)
	{
	case 0:		textPut("mapoffset", 0, "LevelGroup"); break;	// Source Package names edit
	case 1:		textPut("mapoffset", 0, PKG_XPT, "LevelGroup"); break;	// Destination Package names edit
	default:	break;
	}

	// Set up the level item data
	textPut("device", 0, PKG_XPT, "Element");
	textPut("device.map", 0, PKG_XPT, "Element");
	textPut("page=1", PKG_XPT, "Element");
	textPut("namesdb", 0, PKG_XPT, "Element");
	textPut("mapdb=8", PKG_XPT, "Element");
	switch (xptBaseDatabase)
	{
	case 0:		textPut("mapoffset", 0, PKG_XPT, "Element"); break;	// Source Package names edit
	case 1:		textPut("mapoffset", 0, PKG_XPT, "Element"); break;	// Destination Package names edit
	default:	break;
	}

	controlDisable(PKG_XPT, "LevelGroup");
	controlDisable(PKG_XPT, "Element");

	return;
}

// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
void Mtx_Pkg_Edit::disableNamesClear(void)
{
	controlDisable(PKG_XPT, "V_Name");
	controlDisable(PKG_XPT, "A_Name");
	controlDisable(PKG_XPT, "ClrVid");
	controlDisable(PKG_XPT, "ClrAud");
	if (numLevels == 3)							// Numlevels is set up when pkg edit invoked.
	{
		controlDisable(PKG_XPT, "D_Name");
		controlDisable(PKG_XPT, "ClrAES");
	}
}

// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
void Mtx_Pkg_Edit::enableNamesClear(void)
{
	controlEnable(PKG_XPT, "V_Name");
	controlEnable(PKG_XPT, "A_Name");
	controlEnable(PKG_XPT, "ClrVid");
	controlEnable(PKG_XPT, "ClrAud");
	if (numLevels == 3)							// Numlevels is set up when pkg edit invoked.
	{
		controlEnable(PKG_XPT, "D_Name");
		controlEnable(PKG_XPT, "ClrAES");
	}
}


// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// function clearPkgXptEdit(void) is called to tidy the edt data areas of the package
// content editing form. It is used by the Cancel and Update buttons.
void Mtx_Pkg_Edit::clearPkgXptEdit(void)
{
	configPkgClear();						// Clear the groups and items
	textPut("text=", PKG_XPT, "PkgNo");		// Clear the element listing displays
	textPut("text=", PKG_XPT, "V_Name");
	textPut("text=", PKG_XPT, "A_Name");
	textPut("text=", PKG_XPT, "D_Name");
	textPut("text=", PKG_XPT, "V_ID");
	textPut("text=", PKG_XPT, "A_ID");
	textPut("text=", PKG_XPT, "D_ID");
	textPut("text=", PKG_XPT, "PkgName");
	textPut("index=-1", PKG_XPT, "PackageName");	// Clear the selection in the source group

	//currentLevel;		// Level currently being edited
	xptVidNow = -1;		// Clear the initial and modified crosspoint data
	xptAudNow = -1;
	xptAESNow = -1;
	xptVidNext = -1;
	xptAudNext = -1;
	xptAESNext = -1;

	// Enable the package name selection panels
	controlEnable(PKG_XPT, "Group");
	controlEnable(PKG_XPT, "PackageName");
	// Disable the level selection buttons	
	controlDisable(PKG_XPT, "V_Name");
	controlDisable(PKG_XPT, "A_Name");
	controlDisable(PKG_XPT, "D_Name");
	controlDisable(PKG_XPT, "ClrVid");
	controlDisable(PKG_XPT, "ClrAud");
	controlDisable(PKG_XPT, "ClrAES");
	controlDisable(PKG_XPT, "Update");		// No items to update
	controlEnable(PKG_XPT, "AllDone");		// Allow the user to return to the home form.
}




