/*
PROGRAM NAME	Mtx_Map_Edit.dll

USED BY			BBC Academy Wood Norton BNCS installation.

VERSION			1.0
DATE			5th April 2015
PROGRAMMER		Andy Woodhouse

Updated:
VERSION			1.1
DATE			15th May 2017
PROGRAMMER		Andy Woodhouse
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 an editor that
a)	Edits the source/destination allocation map for a Wood Norton 12 groups/ 30 items per panel matrix
b)	Edits the name of the source/destination groups
c)	Enbles/disables the use of the group buttons

The assumed initial set up of the INI files is that databases 0, 1, 4, 5 and 8 are set to point to the correct
file locations (eg all in the DEV_XYZ.INI file).Databases 2,3,6,7 and 9 are not used by this editor.

WOOD NORTON MATRIX DATABASES
============================
The Wood Norton system was created before some of the current conventions used by version 4.5 were invented.
The following table lists the database id's used in the Wood Norton Operations.

	ID	Usage
	0	Source names. These are usually the longer names used on user interface screens. Maximum usually 16 characters
	1	Destination names. These are usually the long names used on user interface screens.
	2	Source names - short. Used for UMD displays which are often just 8 characters
	3	Destination names - short.
	4	Source Group names.	In WN system, there are normally a maximum of 12 source groups available. Less than 12 may be active.
	5	Destination Group Names. In WN system, there are normally a maximum of 12 destination groups available.
	6	BNCS V4.5 suggests this database be used to hold source pixel maps.
	7	BNCS V4.5 suggests this database be used to hold destination pixel maps.
	8	Mapping database. See below for allocations
	9	BNCS 4.5 calls database 9 "Router"

	Database 8 id allocations.
	0001	Source maps. Most matricies use 0001 to 0012.
	 to		Use map 0021 when a large flat (no groups) mapping is required.
	0099

	0101	Destination maps. As with the source maps, the majority of matricies use only 0101 to 0112
	 to		Use map 0121 when a large flat (no groups) mapping is required.
	0199

	0201	Map for source groups onto their 12 button grid. Setting a map entry to 000 disables that group button.

	0251	Map for destination groups onto their 12 button grid. Setting a map entry to 000 disables that group button.

Known (Design) Limitations
==========================
It is assumed that only one copy of this editor is active on the network. Changes to source names by other editors are not
reflected in this task until the edit page is re-opened.

*/ 

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

#define HOME			1
#define HOME_STUB		"HomePage"
#define XPT_MAP			2
#define XPT_MAP_STUB	"Xpoint_Map"
#define GROUP_MAP		3
#define GROUP_MAP_STUB	"GroupMap"
#define GRP_NAMES		4
#define GRP_NAMES_STUB	"GroupName"
#define PANEL_EXT		".bncs_ui"

#define SRC_SELECTED	"WNSourceSelected"			// Statesheet names used
#define SRC_DESELECTED	"WNSourceDeselected"

#define TICK			"/images/EnabledTick.png"	// Images of a tick and a cross
#define CROSS			"/images/DisabledTick.png"

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

// ++++++++++++++++++++++++++++++++++++++++++++
// Constructor - equivalent to ApplCore STARTUP
// ++++++++++++++++++++++++++++++++++++++++++++
Mtx_Map_Edit::Mtx_Map_Edit(bncs_client_callback *parent, const char *path ) : bncs_script_helper(parent, path)
{
	// Initialise global variables
	homePanel = "";				// Holds the name of the home panel for the workstation screen size
	xptMapPanel = "";			// Holds the name of the crosspoint map editing panel
	groupMapPanel = "";			// Holds the name of the group map edit panel
	groupNamePanel= "";			// Holds the name of the group name editing panel

	matrix_id = 0;				// Holds the BNCS driver id being edited.
	database_id = 0;			// Holds the database number (0 or 1) being edited.
	db_size = 0;				// Holds the number of sources/destinations in current matrix.
	active_panel = 0;			// Holds the currently active panel id.
	selected_matrix = 0;		// Holds the id for the currently selected matrix to edit. Used by the HOME panel.
	selected_function = 0;		// Holds the id for the currently selected edit function (sorece, destination, map, group name, group enable). Used by the HOME panel.
	select_mtx = 1;				// Holds the matrix ID for the user selected number (set using up/down pushes)
	num_xpts = 0;				// Number of crosspoints in currently selected matrix.
	matrix_id = 0;				// BNCS driver id for currently edited matrix data
	database_id = 0;			// Database number in use for the edit
	grp_sel = 0;				// No group yet selected. Used by XPT_EDIT panel
	map_sel = 0;				// No map button yet selected. Used by XPT_EDIT panel
	xpt_sel = 0;				// Holds the selected xpt id. Used by XPT_EDIT panel
	grp_map_id = 0;				// Holds the map id being edited. Used by GROUP_MAP panel.
	grp_db = 0;					// Holds database id for the source/destination. Used by GROUP_MAP panel.
	grp_name_db = 0;			// Group name database number being edited. 
	editid = 0;					// Number of group being edited. Used by GROUP_NAME edit panel.
	for (int i = 0; i < NUM_GROUPS; i++)
	{
		for (int j = 0; j < ENTRYS_PER_PAGE; j++) mtx_map[i][j] = 0;	// This 2-D array holds the matrix map of the currently edited matrix.
																		// Note that indexing uses C convention (eg 0 - 11), not BNCS (1 - 12)
	}
	
	for (int i = 0; i < NUM_GROUPS;i++) groupNames[i]="";			// List of 12 names used in group name edit

	matrix_selected = false;			// True when a matrix is selected on the home panel
	function_selected = false;			// True when an edit function is selected on the home panel
	edit_enabled = false;				// True when HOME EDIT button is enabled.
	mtx_changed = false;				// True when Matrix Map has been edited, and Save Network button is enabled.
	for (int i = 0; i < NUM_GROUPS; i++) page_changed[i] = false;
	SetMapBtnState = false;				// True if the Map Crosspoint button is enabled			
	for (int i = 0 ; i < NUM_GROUPS; i++) page_changed[i] = false;		// Keeps a list of flags to show if the page of maps has been updated (value=true) or not (value=false)
	for (int i = 0; i < MAX_MATRIX_SIZE; i++) xpt_is_mapped[i];			// Used by the routine that looks for unmapped crosspoints
	for (int i = 0; i < NUM_GROUPS; i++) grp_enabled[i];				// Keeps list of flags to show if associated group button should be enabled
	grp_map_save = false;
	grp_name_changed = false;			// True is a group name has been edited, and the SAVE button is active.
	for (int i = 0; i < NUM_GROUPS;i++) grp_name_grpchanged[i] = false;	// Holds the current set of names being edited.

	// 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_Map_Edit:: No screenwidth parameter found in workstations.xml for workstation %1.\n").arg(workstation()));
			wid = bncs_string("1024");
			homePanel = HOME_STUB + wid + PANEL_EXT;
			xptMapPanel = XPT_MAP_STUB + wid + PANEL_EXT;
			groupMapPanel = GROUP_MAP_STUB + wid + PANEL_EXT;
			groupNamePanel = GRP_NAMES_STUB + wid + PANEL_EXT;
			break;

		case 1024:
			wid = bncs_string("1024");
			homePanel = HOME_STUB + wid + PANEL_EXT;
			xptMapPanel = XPT_MAP_STUB + wid + PANEL_EXT;
			groupMapPanel = GROUP_MAP_STUB + wid + PANEL_EXT;
			groupNamePanel = GRP_NAMES_STUB + wid + PANEL_EXT;
			break;

		case 1280:
			wid = bncs_string("1280");
			homePanel = HOME_STUB + wid + PANEL_EXT;
			xptMapPanel = XPT_MAP_STUB + wid + PANEL_EXT;
			groupMapPanel = GROUP_MAP_STUB + wid + PANEL_EXT;
			groupNamePanel = GRP_NAMES_STUB + wid + PANEL_EXT;
			break;

		default:
			debug(bncs_string("\nMtx_Map_Edit:: Unsupported screenwidth parameter found in workstations.xml for workstation %1.\n").arg(workstation()));
			wid = bncs_string("1024");
			homePanel = HOME_STUB + wid + PANEL_EXT;
			xptMapPanel = XPT_MAP_STUB + wid + PANEL_EXT;
			groupMapPanel = GROUP_MAP_STUB + wid + PANEL_EXT;
			groupNamePanel = GRP_NAMES_STUB + wid + PANEL_EXT;
			break;
	}


	// Initialise all panels so that buttons can be set as required for initial states
	panelInit(HOME, homePanel);
	panelInit(XPT_MAP, xptMapPanel);
	panelInit(GROUP_MAP, groupMapPanel);
	panelInit(GRP_NAMES, groupNamePanel);

	controlDisable(HOME, "Edit");					// Disable the edit button on the home panel
	controlDisable(XPT_MAP, "SetMap");				// Disable the set map button on the mapping panel.
	controlDisable(XPT_MAP, "Save_to_Network");		// Disable save changes button
	controlDisable(XPT_MAP, "ClearMap");			// Disable clear map button
	controlDisable(GROUP_MAP, "Save_to_Network");	// Disable save changes button on group map enable/disable panel
	controlDisable(GRP_NAMES, "Save_to_Network");	// Disable save changes button on group names editor
	controlDisable(GRP_NAMES, "Kbd");				// Disable keyboard on group names editor

	// Show our main panel
	panelShow(HOME, homePanel);
}


// +++++++++++++++++++++++++++++++++++++++++++++
// Destructor - equivalent to ApplCore CLOSEDOWN
// +++++++++++++++++++++++++++++++++++++++++++++
Mtx_Map_Edit::~Mtx_Map_Edit()
{
}


// ++++++++++++++++++++++++++++++++++++++++++++++
// All button pushes and notifications come here.
// ++++++++++++++++++++++++++++++++++++++++++++++
void Mtx_Map_Edit::buttonCallback(buttonNotify *b)
{
	// Most of the work is done by local methods, one per panel.

	if(b->panel() == HOME)
	{
		// Select the data to be edited.
		processHome(b);
	}

	if (b->panel() == XPT_MAP)
	{
		// Edit a crosspoint map.
		processXptMap(b);
	}
	
	if (b->panel() == GROUP_MAP)
	{
		// Edit the group source and destination enables.
		processGroupMapEnables(b);
	}

	if (b->panel() == GRP_NAMES)
	{
		// Edit the group source/destination names
		processGroupNameEdit(b);
	}
}


// +++++++++++++++++++++++++
// All revertives come here.
// +++++++++++++++++++++++++
int Mtx_Map_Edit::revertiveCallback(revertiveNotify *r)
{
	// No revertives checked by this panel

	return 0;
}


// +++++++++++++++++++++++++++++++++++++++++
// All database name changes come back here.
// +++++++++++++++++++++++++++++++++++++++++
void Mtx_Map_Edit::databaseCallback(revertiveNotify *r)
{
	// No database callbacks used by this editor.
}



// ++++++++++++++++++++++++++++++++++++++
// 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_Map_Edit::parentCallback(parentNotify *p)
{
	if (p->command() == "return")
	{
		if (p->value() == "all")
		{	// Persisting values for bncs_vis_ed
			bncs_stringlist sl = "";
			
			return sl.toString( '\n' );
		}
	}

	// ***** CONNECTIONS EVENTS HELPER LIST *****
	else if (p->command() == "_events")
	{	// Helper-list of everything in this component generated by hostNotify's
		bncs_stringlist sl = "";
		
		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 = "";
		
		return sl.toString( '\n' );
	}

	return "";
}


// +++++++++++++++++++++++
// Timer events come here.
// +++++++++++++++++++++++
void Mtx_Map_Edit::timerCallback( int id )
{
	// No timers used in this application. Just stop any that have a timeout.
	timerStop(id);
}


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


// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// function processHome() is called to service button presses on the HOME panel.
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
void Mtx_Map_Edit::processHome(buttonNotify *b)
{
	if (b->id().startsWith("XPT_map_"))		// Is pressed button a matrix selection button?
	{
		if (matrix_selected == true)		// Check for an already active matrix selection button, and deselect it.
		{
			textPut("statesheet", SRC_DESELECTED, HOME, bncs_string("XPT_map_%1").arg(selected_matrix));
			matrix_selected = false;
		}

		selected_matrix = b->id().firstInt();	// Extract the new button id, and keep a note of the selection.
		textPut("statesheet", SRC_SELECTED, HOME, b->id());
		matrix_selected = true;

		checkEditEnable();					// Check if EDIT button should be enabled

		return;
	}

	if (b->id().startsWith("FN_"))			// Has an edit function button been selected?
	{
		if (function_selected == true)		// Was there a previously active button? If so de-select.
		{
			textPut("statesheet", SRC_DESELECTED, HOME, bncs_string("FN_%1").arg(selected_function));
			function_selected = false;
		}

		selected_function = b->id().firstInt();					// Note the new function id
		textPut("statesheet", SRC_SELECTED, HOME, b->id());		// Highlight new button
		function_selected = true;

		checkEditEnable();										// Check if EDIT button should be enabled

		return;
	}

	if (b->id() == "Clear")					// Has the Clear Selection button been pressed?
	{
		if (matrix_selected)				// Clear any existing selections
		{
			textPut("statesheet", SRC_DESELECTED, HOME, bncs_string("XPT_map_%1").arg(selected_matrix));
			matrix_selected = false;
		}

		if (function_selected == true)		// Clear existing selection
		{
			textPut("statesheet", SRC_DESELECTED, HOME, bncs_string("FN_%1").arg(selected_function));
			function_selected = false;
		}

		checkEditEnable();										// Check if EDIT button should be enabled

		return;
	}

	if (b->id() == "Edit")					// Has the EDIT button been pressed?
	{
		// A matrix and an edit function have been selected to enable the EDIT button.
		// Select the panel, load data and start the actions.
		switch (selected_matrix)			// Use recorded matrix identifier to select BNCS driver ID to find.
		{
		case 1:		// CAR SDI
			getDev("CAR SDI Router", &matrix_id);
			break;

		case 2:		// CAR Audio
			getDev("CAR Audio Router", &matrix_id);
			break;

		case 3:		// CAR HD
			getDev("CAR HD-SDI Router", &matrix_id);
			break;

		case 4:
			getDev("V2 Mixer SDI Router", &matrix_id);
			break;

		case 11:	// Studio C SDI
			getDev("StC SDI Router", &matrix_id);
			break;

		case 12:	// Studio C Analogue Audio
			getDev("StC Audio Router", &matrix_id);
			break;

		case 13:	// Studio C Digital Audio
			getDev("StC AES Router", &matrix_id);
			break;

		case 21:	// CTA SDI
			getDev("CTA SDI Router", &matrix_id);
			break;

		case 22:	// CTA Audio
			getDev("CTA Audio Router", &matrix_id);
			break;

		case 31:	//B4 SDI
			getDev("B4 SDI Router", &matrix_id);
			break;

		case 32:	// B4 Audio
			getDev("B4 Audio Router", &matrix_id);
			break;

		case 41:	// User entered value
			matrix_id = select_mtx;	// Copy matrix nmber	
			break;
		default:
			matrix_id = 0;
			break;
		}

		// Now switch action, based on the selected action field
		switch (selected_function)
		{
		case 1:			// Edit Source crosspoint map
			database_id = SRC_DB;					// Note we are using the source database
			getRouterSize(matrix_id, database_id);	// Get the number of crosspoints
			loadMtxMap(matrix_id, database_id);		// Prepare the editor panel for use
			panelRemove(HOME);						// Remove the home panel
			panelShow(XPT_MAP, xptMapPanel);		// Display router map edit panel

			break;

		case 2:			// Edit Destination crosspoint map
			database_id = DST_DB;					// Note we are using the destination database
			getRouterSize(matrix_id, database_id);	// Get the number of crosspoints
			loadMtxMap(matrix_id, database_id);		// Prepare the editor panel for use
			panelRemove(HOME);						// Remove the home panel
			active_panel = XPT_MAP;
			panelShow(XPT_MAP, xptMapPanel);		// Display router map edit panel

			break;

		case 3:			// Edit Source group names
			database_id = GRP_SRC_DB;				// Using group source names database
			grp_name_db = GRP_SRC_DB;
			loadGroupNames();						// Load names into memory, load onto edit buttons
			panelRemove(HOME);
			active_panel = GRP_NAMES;
			panelShow(GRP_NAMES, groupMapPanel);	// Display name editor panel.

			break;

		case 4:			// Destination group names
			database_id = GRP_DST_DB;				// Using group destination names database
			grp_name_db = GRP_DST_DB;
			loadGroupNames();						// Load names into memory, load onto edit buttons
			panelRemove(HOME);						// Remove home panel
			active_panel = GRP_NAMES;
			panelShow(GRP_NAMES, groupNamePanel);	// Display name editor panel.

			break;

		case 5:			// Source group map enable/disable
			database_id = MAP_DB;					// Editing the map database
			grp_map_id = SRC_GRP_MAP;				// Source group map entry
			grp_db = GRP_SRC_DB;					// Database that holds the names
			prepGroupMapPanel();					// Prepare the group map enable/disable screen.
			panelRemove(HOME);						// Remove the home panel
			active_panel = GROUP_MAP;
			panelShow(GROUP_MAP, groupMapPanel);	// Display enable/disable group button

			break;

		case 6:			// Destination group map enable/disable
			database_id = MAP_DB;					// Editing the map database
			grp_map_id = DST_GRP_MAP;				// Destination group map entry
			grp_db = GRP_DST_DB;					// Database that holds the names
			prepGroupMapPanel();					// Prepare the group map enable/disable screen.
			panelRemove(HOME);						// Remove the home panel
			active_panel = GROUP_MAP;
			panelShow(GROUP_MAP, groupMapPanel);	// Display enable/disable group button

			break;
		}

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

	if (b->id() == "HU")
	{
		setMatrix(100);		// Add 100 
		return;
	}

	if (b->id() == "TU")
	{
		setMatrix(10);		// Add 10
		return;
	}

	if (b->id() == "UU")
	{
		setMatrix(1);		// Add 1
		return;
	}

	if (b->id() == "HD")
	{
		setMatrix(-100);	// Subtract 100 
		return;
	}

	if (b->id() == "TD")
	{
		setMatrix(-10);		// Subtract 10 
		return;
	}

	if (b->id() == "UD")
	{
		setMatrix(-1);		// Subtract 1
		return;
	}

}	// End of Mtx_Map_Edit::processHome(buttonNotify *b)


// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// function processXptMap(buttonNotify *b) is called to process button pushes on the crosspoint map editing panel.
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
void Mtx_Map_Edit::processXptMap(buttonNotify *b)
{
	if (b->id().startsWith("Group_"))		// Change the displayed group?
	{
		if (map_sel != 0)					// Is there an active selection on the current page?
		{
			textPut("statesheet", SRC_DESELECTED, XPT_MAP, bncs_string("Element_%1").arg(map_sel));	// Clear highlight off previous group.
			map_sel = 0;
		}

		if (grp_sel != 0)					// Remove the highlight from the previous group button
		{
			textPut("statesheet", SRC_DESELECTED, XPT_MAP, bncs_string("Group_%1").arg(grp_sel));	// Clear highlight off previous group.
			grp_sel = 0;
		}

		// Set the highlight on the new group
		grp_sel = b->id().firstInt();			// Note the new group
		textPut("statesheet", SRC_SELECTED, XPT_MAP, bncs_string("Group_%1").arg(grp_sel));			// Highlight the new group.

		// Show the map
		ShowMapGroup(grp_sel);
		updateSetMapBtn();
		showUnmappedXpt();					// Show list of unmapped crosspoint ids

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


	if (b->id().startsWith("Element_"))			// Map button pressed.
	{
		if (map_sel != 0)					// Is there already an active element button. If so, clear it.
		{
			textPut("statesheet", SRC_DESELECTED, XPT_MAP, bncs_string("Element_%1").arg(map_sel));
			map_sel = 0;
		}

		map_sel = b->id().firstInt();			// Extract the button number
		textPut("statesheet", SRC_SELECTED, XPT_MAP, bncs_string("Element_%1").arg(map_sel));
		controlEnable(XPT_MAP, "ClearMap");
		updateSetMapBtn();

		return;
	}

	if (b->id() == "xpoints")					// New crosspont selected?
	{
		bncs_string sel_idx;					// Holds the string returned by the control
		
		textGet("selectedindex", XPT_MAP, "xpoints", sel_idx);	// Ask which element is selected in the listbox.
		xpt_sel = sel_idx.toInt() + 1;			// Convert from listbox numbering (base 0) to BNCS numbering (base 1)
		updateMappedList();						// Update the list of mappings
		updateSetMapBtn();						// Enable/disable make map button

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


	if (b->id() == "SetMap")				// Make map button pressed?
	{
		bncs_string xpt_name;

		// The Set Map button is only enabled when both a crosspoint and a map button is selected
		mtx_map[grp_sel - 1][map_sel - 1] = xpt_sel;		// Update the mapping matrix.
		page_changed[grp_sel - 1] = true;					// Note that the update has happened on this group
		routerName(matrix_id, database_id, xpt_sel, xpt_name);		// Read source/dest name from router
		textPut("text", xpt_name, XPT_MAP, bncs_string("Element_%1").arg(map_sel));
		updateMappedList();									// Update the list of where current crosspoint is mapped

		// See if the global change flag is set, if not set it and enable the save to network botton
		if (mtx_changed != true)
		{
			mtx_changed = true;								// Set the global change flag
			controlEnable(XPT_MAP, "Save_to_Network");		// Enable the SAVE to network button.
		}

		showUnmappedXpt();					// Show list of unmapped crosspoint ids

		return;
	}

	if (b->id() == "ClearMap")				// Clear the map at selected position
	{
		if (map_sel != 0)		// Defensive - should only come here with a mapping button selected
		{
			mtx_map[grp_sel - 1][map_sel - 1] = 0;			// Update the mapping matrix to "no source".
			page_changed[grp_sel - 1] = true;				// Set flag to show an update has happened on this group.
			textPut("text=[Not Mapped]", XPT_MAP, bncs_string("Element_%1").arg(map_sel));	// Put "no name" string onto element button.
			if (xpt_sel !=0)								// If there is a crosspoint selection
			{
				updateMappedList();							// Update the list of where current crosspoint is mapped
			}

			// See if the global change flag is set, if not set it and enable the save to network botton
			if (mtx_changed != true)
			{
				mtx_changed = true;							// Set the global change flag
				controlEnable(XPT_MAP, "Save_to_Network");	// Enable the SAVE to network button.
			}
		}

		showUnmappedXpt();					// Show list of unmapped crosspoint ids

		return;
	}

	if (b->id() == "Clear")					// Clear Selections?						
	{
		// Is there a map button pushed?
		if (map_sel > 0)
		{
			textPut("statesheet", SRC_DESELECTED, XPT_MAP, bncs_string("Element_%1").arg(map_sel));	// Clear highlight
			controlDisable(XPT_MAP, "ClearMap");
			map_sel = 0;					// Update selection data
		}

		// Is there a crospoint selected in the listbox?
		if (xpt_sel > 0)
		{
			textPut("selected=none", XPT_MAP, "xpoints");	// Clear selection in matrix crosspoint listbox
			textPut("clear", XPT_MAP, "Mapped_To");			// Clear the listbox that shows the mapped allocations for crosspoint XXXX
			xpt_sel = 0;									// Update the selection data
		}

		// Update the Set Map Point button
		updateSetMapBtn();

		return;
	}

	if (b->id() == "Cancel")
	{
		// User has decided to abandon the edit without saving changes. Double press was required.
		textPut("clear", XPT_MAP, "xpoints");				// Clear sorce/destination name listbox
		textPut("clear", XPT_MAP, "Mapped_To");				// Clear mapped to display
		textPut("clear", XPT_MAP, "Unmapped");				// Clear the list of unmapped crosspoints
		for (int i = 1; i <= NUM_GROUPS; i++)				// Clear the group name array
		{
			textPut("text=---", XPT_MAP, bncs_string("Group_%1").arg(i));
		}

		for (int m = 1; m <= ENTRYS_PER_PAGE; m++)			// Clear the map display
			textPut("text=---", XPT_MAP, bncs_string("Element_%1").arg(m));

		if (map_sel != 0) textPut("statesheet", SRC_DESELECTED, XPT_MAP, bncs_string("Element_%1").arg(map_sel));	// Clear element button highlight
		if (grp_sel != 0) textPut("statesheet", SRC_DESELECTED, XPT_MAP, bncs_string("Group_%1").arg(grp_sel));		// Clear group highlight
		matrix_id = 0;
		database_id = 0;
		grp_sel = 0;
		map_sel = 0;
		xpt_sel = 0;
		controlDisable(XPT_MAP, "SetMap");
		controlDisable(XPT_MAP, "ClearMap");
		controlDisable(XPT_MAP, "Save_to_Network");
		mtx_changed = false;
		SetMapBtnState = false;

		panelRemove(XPT_MAP);			// Remove the mapping edit panel

		// Clear any selections on the HOME panel
		if (matrix_selected)
		{
			textPut("statesheet", SRC_DESELECTED, HOME, bncs_string("XPT_map_%1").arg(selected_matrix));
			matrix_selected = false;
		}
		if (function_selected == true)
		{
			// Clear existing selection
			textPut("statesheet", SRC_DESELECTED, HOME, bncs_string("FN_%1").arg(selected_function));
			function_selected = false;
		}
		checkEditEnable();				// Update the state of the edit button

		active_panel = HOME;
		panelShow(HOME, homePanel);
		
		return;
	}

	if (b->id() == "Save_to_Network")
	{
		// Only get here if a change was made, resulting in the Save_to_Network button being enabled.
		// Need to save each row of the mtx_map that has been changed. This status is flagged by the
		// boolean array page_changed.

		for (int pg = 0; pg < NUM_GROUPS; pg++)
		{
			if (page_changed[pg] == true)
			{
				// This set of 30 entries has been updated - create a string and save it to the database
				bncs_string new_map = "";
				int db_index = 0;
				
				for (int i = 0; i < (ENTRYS_PER_PAGE-1); i++)	// Form comma delimited string from all but last map entry
				{
					new_map = new_map + bncs_string(mtx_map[pg][i], '0', 3, 10) + ",";
				}
				// Add the final term without a terminal comma.
				new_map = new_map + bncs_string(mtx_map[pg][ENTRYS_PER_PAGE - 1], '0', 3, 10);

				// Save the new map to dev_xxx.ini
				db_index = ((database_id == 0) ? MAP_SRC_OFFSET : MAP_DST_OFFSET) + 1 + pg;		// Compute the index to update in map db
				routerModify(matrix_id, MAP_DB, db_index, new_map, true);
			}
		}

		for (int i = 0; i < NUM_GROUPS; i++) page_changed[i] = false;	// Clear the group "changed" flags.
		controlDisable(XPT_MAP, "Save_to_Network");						// Disable the save to network button.
		mtx_changed = false;

		return;
	}
}	// End of function void processXptMap(buttonNotify *b)


// ++++++++++++++++++++++++++++++++++++++++++++++
// Process Button pushes on the Map Enables panel
// ++++++++++++++++++++++++++++++++++++++++++++++
void Mtx_Map_Edit::processGroupMapEnables(buttonNotify *b)
{
	int idx = 0;
	
	if (b->id().startsWith("Status_"))		// Click on enable/disable button
	{
		idx = b->id().firstInt();														// Extract the id from the name
		grp_enabled[idx - 1] = grp_enabled[idx - 1] ? false : true;						// Update the record of the state
		textPut("pixmap", grp_enabled[idx - 1] ? TICK : CROSS, GROUP_MAP, b->id());		// Update the on screen display

		if (grp_map_save == false)			// Update global edit flag and enable SAVE button
		{
			controlEnable(GROUP_MAP, "Save_to_Network");
			grp_map_save = true;
		}
		
		return;
	}

	if (b->id() == "Cancel")				// Did user press the cancel button
	{
		// Leave panel ready for next invoke of panel.
		grp_map_save = false;												// No items to save.
		controlDisable(GROUP_MAP, "Save_to_Network");						// Disable the save button
		textPut("text=", GROUP_MAP, "ID_Data");								// Clear ident display
		for (int i = 1; i <= NUM_GROUPS; i++)								// Clear group names array
			textPut("text=", GROUP_MAP, bncs_string("Grp_Name_%1").arg(i));

		// Clear any selections on the HOME panel
		if (matrix_selected)
		{
			textPut("statesheet", SRC_DESELECTED, HOME, bncs_string("XPT_map_%1").arg(selected_matrix));
			matrix_selected = false;
		}
		if (function_selected == true)		// Clear existing selection
		{
			textPut("statesheet", SRC_DESELECTED, HOME, bncs_string("FN_%1").arg(selected_function));
			function_selected = false;
		}
		checkEditEnable();				// Update the state of the edit button

		panelRemove(GROUP_MAP);			// Go back to the home panel
		active_panel = HOME;
		panelShow(HOME, homePanel);

		return;
	}

	if (b->id() == "Save_to_Network")
	{
		bncs_string btn_map = "";
		int id;

		// Only get here if the button was enabled by a change of enable/disable state.
		for (int idx = 0; idx < (NUM_GROUPS - 1); idx++)
		{
			id = (grp_enabled[idx] == true) ? idx + 1 : 0;				// Return either group index or 0
			btn_map = btn_map + bncs_string(id, '0', 3, 10) + ",";
		}
		// Add final element without comma at end.
		id = (grp_enabled[NUM_GROUPS-1] == true) ? NUM_GROUPS : 0;		// Return either group index or 0
		btn_map = btn_map + bncs_string(id, '0', 3, 10);
		
		routerModify(matrix_id, MAP_DB, grp_map_id, btn_map, false);	// Save the string to the network

		grp_map_save = false;											// Clear the flag for Save eneabled
		controlDisable(GROUP_MAP, "Save_to_Network");

		return;
	}
}	// End of void processGroupMapEnables(buttonNotify *b)


// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// function processGroupNameEdit(buttonNotify *b) is called to 
// procees button presses on the Group Name Editor panel.
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
void Mtx_Map_Edit::processGroupNameEdit(buttonNotify *b)
{
	bncs_string newname;

	if (b->id().startsWith("Group_"))		// One of the Group buttons pressed?
	{
		editid = b->id().firstInt();												// Keep a note of the index that is being edited
		textPut("text", groupNames[editid - 1], GRP_NAMES, "Kbd");					// Send the current name to the Keyboard ready for edit
		for (int idx = 1; idx <= NUM_GROUPS; idx++) 
			controlDisable(GRP_NAMES, bncs_string("Group_%1").arg(idx));			// Disable all group buttons whilst edit is active
		controlDisable(GRP_NAMES, "Cancel");										// Disable the Cancel button
		if (grp_name_changed == true) controlDisable(GRP_NAMES, "Save_to_Network");	// If Save Changes was active, disable
		controlEnable(GRP_NAMES, "Kbd");											// Enable the keyboard

		return;
	}

	if (b->id() == "Kbd")					// The keyboard return button pressed?
	{
		textGet("text", GRP_NAMES, "Kbd", newname);									// Retrieve text from keyboard
		groupNames[editid - 1] = newname;											// Save new name in the array of group names
		textPut("text", newname, GRP_NAMES, bncs_string("Group_%1").arg(editid));	// Update the display on the group button
		textPut("text=", GRP_NAMES, "Kbd");											// Clear the text buffer on the keyboard
		controlDisable(GRP_NAMES, "Kbd");											// Disable the keyboard
		controlEnable(GRP_NAMES, "Cancel");											// Enable the Cancel and Exit button
		for (int idx = 1; idx <= NUM_GROUPS; idx++) 
			controlEnable(GRP_NAMES, bncs_string("Group_%1").arg(idx));				// Enable the group buttons
		grp_name_grpchanged[editid - 1] = true;										// Note which name has been edited
		if (grp_name_changed == false) grp_name_changed = true;						// Make sure the global changed flag is true
		if (grp_name_changed == true) controlEnable(GRP_NAMES, "Save_to_Network");	// If there is a global enabled flag, enebale the Save button

		return;
	}

	if (b->id() == "Cancel")				// Cancel and Exit button pressed?
	{
		// Clear all displays ready for next use of panel.
		textPut("text=", GRP_NAMES, "ID_Data");										// Clear matrix data display
		for (int i = 1; i <= NUM_GROUPS; i++)
			textPut("text=", GRP_NAMES, bncs_string("Group_%1").arg(i));			// Clear the group buttons
		if (grp_name_changed == true) controlDisable(GRP_NAMES, "Save_to_Network");	// Disable save to network if enabled
		grp_name_changed = false;
		for (int i = 0; i < NUM_GROUPS; i++) grp_name_grpchanged[i] = false;		// Clear individual group edited flags
		
		if (matrix_selected)				// Clear any selections on the HOME panel
		{
			textPut("statesheet", SRC_DESELECTED, HOME, bncs_string("XPT_map_%1").arg(selected_matrix));
			matrix_selected = false;
		}
		if (function_selected == true)		// Clear existing selection
		{
			textPut("statesheet", SRC_DESELECTED, HOME, bncs_string("FN_%1").arg(selected_function));
			function_selected = false;
		}
		checkEditEnable();					// Update the state of the edit button on HOME panel

		panelRemove(GRP_NAMES);
		active_panel = HOME;
		panelShow(HOME, homePanel);

		return;
	}

	if (b->id() == "Save_to_Network")		// Save to the network INI files
	{
		// Save all changed names back to INI dtaabase.
		// Only get here if at least one group name has changed.
		for (int grp = 1; grp <= NUM_GROUPS; grp++)									// Scan all groups for changes
		{
			if (grp_name_grpchanged[grp - 1] == true)								// Look at the group edit flag
			{
				routerModify(matrix_id, grp_name_db, grp, groupNames[grp - 1], false);	// Send the update to the network
				grp_name_grpchanged[grp - 1] = false;								// Clear the "changed" flag for this group
			}
		}
		controlDisable(GRP_NAMES, "Save_to_Network");								// Disable save to network if enabled
		grp_name_changed = false;													// Clear overall change flag
	
		return;
	}
}	// End of void Mtx_Map_Edit::processGroupNameEdit(buttonNotify *b)


// +++++++++++++++++++++++++++++++++++++++++++
// function checkEditEnable(void) is called to
// see if the HOME page EDIT button should be
// enabled or disabled.
// +++++++++++++++++++++++++++++++++++++++++++
void Mtx_Map_Edit::checkEditEnable(void)
{
	if (matrix_selected && function_selected)	// If the user has selected a matrix and an edit function
	{
		if (edit_enabled == false)				// Enable the edit button if it is not already enabled.
		{
			controlEnable(HOME, "Edit");
			edit_enabled = true;
		}
	}
	else										// Neither or only one of the required elements is selected
	{
		if (edit_enabled == true)
		{
			controlDisable(HOME, "Edit");
			edit_enabled = false;
		}
	}
}	// End of function checkEditEnable(void)


// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// function loadMtxMap(matrix_id, database_id) is called to load the current map for matrix with BNCS id 
// of matrix_id and database database_id (either 0 or 1). The function assumes that the matrix_id exists.
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
void Mtx_Map_Edit::loadMtxMap(int m_id, int db_id)
{
	// Local variables for this function
	bncs_string id, name, map_entry, p1, p2;
	int mapx;

	// Get the database size
	db_size = getRouterSize(m_id, db_id);

	// Add the matrix ident data to listbox container
	id = "";														// First build the information string.
	id = id + "  Matrix " + bncs_string(m_id, '0', 3, 10) + " ";
	if (db_id == 0) id = id + "Source Database";
	if (db_id == 1) id = id + "Destination Database";
	textPut("text", id, XPT_MAP, "MatrixIdent");

	// Clear the mapping array
	for (int i = 0; i < NUM_GROUPS; i++)
	{
		for (int j = 0; j < ENTRYS_PER_PAGE; j++) mtx_map[i][j] = 0;
		page_changed[i] = false;
	}
	mtx_changed = false;
	
	// Load the listbox with the matrix data
	textPut("clear", XPT_MAP, "xpoints");	// Clear current listbox contents

	for (int i = 1; i <= db_size; i++)					// For each entry in the database
	{
		id = bncs_string("%1 - ").arg(i, '0', 3, 10);	// Form a three digit id value, followed by " - "
		routerName(matrix_id, database_id, i, name);	// Get the name matching that index
		id = id + name;									// Append the name to the index
		textPut("add", id, XPT_MAP, "xpoints");			// Insert into list box
	}

	// Name the group buttons
	for (int gr = 1; gr <= NUM_GROUPS; gr++)
	{
		routerName(matrix_id, (GRP_SRC_DB + db_id), gr, name);		// (GRP_SRC_DB + db_id)
		textPut("text", name, XPT_MAP, bncs_string("Group_%1").arg(gr));
	}

	// Read the database maps into the array
	for (int i = 0; i < NUM_GROUPS; i++)
	{
		// Remember that the array is 0 based, BNCS is 1 based.
		mapx = ((db_id == 0) ? MAP_SRC_OFFSET : MAP_DST_OFFSET) + 1 + i;
		routerName(matrix_id, MAP_DB, mapx, map_entry);		// Read a map group entry from DEV_XXX.INI
		
		if (map_entry.length() != 0)
		{
			bncs_stringlist sl;
			int num_items;

			sl.fromString(map_entry);		// Convert comma delimted list into stringlist
			num_items = sl.count();
			if (num_items > ENTRYS_PER_PAGE) num_items = ENTRYS_PER_PAGE; // Defensive programming - limit number of entry's in the map block

			for (int m = 0; m < num_items; m++) mtx_map[i][m] = sl[m];
		}
	}

	// Show the buttons of Group 1
	textPut("statesheet", SRC_SELECTED, XPT_MAP, "Group_1");
	grp_sel = 1;
	ShowMapGroup(1);		// Call function to set the display
	showUnmappedXpt();

}	// End of void Mtx_Map_Edit::loadMtxMap(int m_id, int db_id)


// +++++++++++++++++++++++++++++++++++++++++++++++++++++
// function ShowMapGroup(int group_id) is called to show
// the current mapping on the map buttons.
// +++++++++++++++++++++++++++++++++++++++++++++++++++++
void Mtx_Map_Edit::ShowMapGroup(int group_id)
{
	// Assumes no saving to be done, but there may be an active selection that needs to be cleared.
	int mapped = 0;
	bncs_string name = "";

	for (int idx = 1; idx <= ENTRYS_PER_PAGE; idx++)
	{
		mapped = mtx_map[group_id - 1][idx - 1];
		if (mapped != 0)
		{
			routerName(matrix_id, database_id, mapped, name);
			textPut("text", name, XPT_MAP, bncs_string("Element_%1").arg(idx));
		}
		else
		{
			textPut("text=[Not Mapped]", XPT_MAP, bncs_string("Element_%1").arg(idx));
		}
	}
}


// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// function updateMappedList() updates the display of the group/elements
// onto which the selected crosspoint has been mapped.                  
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
void Mtx_Map_Edit::updateMappedList(void)
{
	bncs_string group_name = "";	// Holds the name of the group in scan for mappings
	bncs_string is_mapped = "";

	textPut("clear", XPT_MAP, "Mapped_To");					// Clear the mapped to listbox

	// Scan the map array, looking for a matched index
	for (int grp = 0; grp < NUM_GROUPS; grp++)
	{
		routerName(matrix_id, GRP_SRC_DB + database_id, grp + 1, group_name);

		for (int item = 0; item < ENTRYS_PER_PAGE; item++)
		{
			if (xpt_sel == mtx_map[grp][item])
			{
				is_mapped = "Grp: " + group_name + ", button " + bncs_string(item + 1);
				textPut("add", is_mapped, XPT_MAP, "Mapped_To");
			}
		}
	}	// End of for (int grp = 0; grp < NUM_GROUPS; grp++)
}	// End of function Mtx_Map_Edit::updateMappedList(void)


// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// function updateSetMapBtn() is called to enable or disable the
// Set Map button. Enabled if crosspoint and element selected,  
// Deselected otherwise.                                        
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
void Mtx_Map_Edit::updateSetMapBtn(void)
{
	if ((map_sel > 0) && (xpt_sel > 0))
	{
		// Enable the Set Map point button
		controlEnable(XPT_MAP, "SetMap");
		SetMapBtnState = true;
	}
	else
	{
		// Disable the Set Map point button
		controlDisable(XPT_MAP, "SetMap");
		SetMapBtnState = false;
	}
}	// End of updateSetMapBtn(void)


// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// function showUnmappedXpt(void) updates the listbox on the XPT_MAP panel that 
// shows which crosspoints are not mapped onto one of the group/element buttons.
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
void Mtx_Map_Edit::showUnmappedXpt(void)
{
	int xpt;
	bncs_string unused_idx;

	// Clear the flag array
for (int i = 0; i < MAX_MATRIX_SIZE; i++) xpt_is_mapped[i] = false;

num_xpts = getRouterSize(matrix_id, database_id);		// Find the true number of destinations

// Scan the map array. For each entry that is non zero set a true flag for the index in the flag array.
for (int grp = 0; grp < NUM_GROUPS; grp++)
{
	for (int element = 0; element < ENTRYS_PER_PAGE; element++)
	{
		xpt = mtx_map[grp][element];
		if ((xpt != 0) && (xpt <= num_xpts)) xpt_is_mapped[xpt] = true;
	}
}

textPut("clear", XPT_MAP, "Unmapped");	// Clear the report listbox

// Scan the flag array, reporting any indicies that report false
for (int i = 1; i <= num_xpts; i++)
{
	if (xpt_is_mapped[i] == false)
	{
		unused_idx = bncs_string(i, '0', 3, 10);
		textPut("add", unused_idx, XPT_MAP, "Unmapped");		// Add id to listbox
	}
}

}



// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// Prepare panel GRP_NAMES for editing Group source/destination names.
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
void Mtx_Map_Edit::loadGroupNames(void)
{
	bncs_string grp_name, panelid;

	// Update the panel ident strip
	panelid = "Matrix no. " + bncs_string(matrix_id, '0', 3, 10) + " " + ((grp_name_db == GRP_SRC_DB) ? "Source" : "Destination") + " Group Name Edit";
	textPut("text", panelid, GRP_NAMES, "ID_Data");

	// Read the group names and place them in the panel button text fields
	for (int grp = 0; grp < NUM_GROUPS; grp++)
	{
		routerName(matrix_id, grp_name_db, grp + 1, grp_name);
		groupNames[grp] = grp_name;
		textPut("text", grp_name, GRP_NAMES, bncs_string("Group_%1").arg(grp + 1));
	}

	grp_name_changed = false;												// Overall no name changed flag is clear.
	for (int i = 0; i < NUM_GROUPS; i++) grp_name_grpchanged[i] = false;	// No names yet edited - set all clear (false)
}	// End of loadGroupNames(void)


// +++++++++++++++++++++++++++++++++++++++++++++++++++++
// function prepGroupMapPanel(void) is called to prepare
// the Group Map enable/disable display.
// +++++++++++++++++++++++++++++++++++++++++++++++++++++
void Mtx_Map_Edit::prepGroupMapPanel(void)
{
	// Matrix number is in variable matrix_id, map id to edit is in variable grp_map_id
	bncs_string my_initial_map, grp_name, panelid;
	bncs_stringlist my_map_list;
	int len = 0;

	// Update the panel ident strip
	panelid = "Matrix no. " + bncs_string(matrix_id, '0', 3, 10) + " " + ((grp_db == GRP_SRC_DB) ? "Source" : "Destination") + " Map Group Enables";
	textPut("text", panelid, GROUP_MAP, "ID_Data");

	// Read the group names, and copy them to the panel buttons
	for (int grp = 0; grp < NUM_GROUPS; grp++)
	{
		routerName(matrix_id, grp_db, grp + 1, grp_name);
		textPut("text", grp_name, GROUP_MAP, bncs_string("Grp_Name_%1").arg(grp + 1));
	}

	for (int i = 0; i < NUM_GROUPS; i++) grp_enabled[i] = false;	// Initialise map state array to all false

	routerName(matrix_id, MAP_DB, grp_map_id, my_initial_map);		// Read the current map;
	my_map_list = bncs_stringlist(my_initial_map);					// Convert comma delimited list to stringlist

	len = NUM_GROUPS;												// Assume we have 12 entries or more in the string list
	if (my_map_list.count() < len) len = my_map_list.count();		// If there are less entries only look for that number

	for (int flag = 0; flag < len; flag++)							// Scan the contents of the array. Initial values all set to false
		if (my_map_list[flag] > 0) grp_enabled[flag] = true;		// Toggle groups that are already enabled

	// Show the current enabled/disabled state.
	for (int i = 0; i < NUM_GROUPS; i++) textPut("pixmap", grp_enabled[i] ? TICK : CROSS, GROUP_MAP, bncs_string("Status_%1").arg(i + 1));
}	// End of void Mtx_Map_Edit::prepGroupMapPanel(void)


// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// function setMatrix(int step) changes the matrix id of the user set value.
// Input parameter is the adjustment to be made. After adjustment the updated
// value must be between 1 and 999
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
void Mtx_Map_Edit::setMatrix(int step)
{
	bncs_string mtx_num;

	if (((select_mtx + step) > 0) && ((select_mtx + step) < 1000))		// Make sure that updated value will be in range.
	{
		select_mtx += step;		// Update the matrix id
		mtx_num = "Matrix " + bncs_string(select_mtx, '0',3,10);
		textPut("text", mtx_num, HOME, "XPT_map_41");
	}
}