/*
**	Command & Conquer Generals(tm)
**	Copyright 2025 Electronic Arts Inc.
**
**	This program is free software: you can redistribute it and/or modify
**	it under the terms of the GNU General Public License as published by
**	the Free Software Foundation, either version 3 of the License, or
**	(at your option) any later version.
**
**	This program is distributed in the hope that it will be useful,
**	but WITHOUT ANY WARRANTY; without even the implied warranty of
**	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
**	GNU General Public License for more details.
**
**	You should have received a copy of the GNU General Public License
**	along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

////////////////////////////////////////////////////////////////////////////////
//																																						//
//  (c) 2001-2003 Electronic Arts Inc.																				//
//																																						//
////////////////////////////////////////////////////////////////////////////////

// FILE: GUIEditWindowManager.cpp /////////////////////////////////////////////////////////////////
// Created:    Colin Day, July 2001
// Desc:       Window manager for the GUI edit tool, we want this up
//						 fast and to look like what we use in the game so we're going
//						 to use the WW3D window manager, and just override the
//						 drawing functions to draw lines and images to the
//						 display.  We will also be adding our own functionality
//						 here for editing and interacting with the GUI windows.
///////////////////////////////////////////////////////////////////////////////////////////////////

#include <stdlib.h>
#include <stdio.h>

#include "Common/Debug.h"
#include "GameClient/GadgetSlider.h"

#include "GUIEditWindowManager.h"
#include "EditWindow.h"
#include "GUIEdit.h"
#include "HierarchyView.h"

// PUBLIC DATA ////////////////////////////////////////////////////////////////////////////////////
GUIEditWindowManager *TheGUIEditWindowManager = NULL;  ///< editor use only

///////////////////////////////////////////////////////////////////////////////////////////////////
// PRIVATE FUNCTIONS //////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////

//-------------------------------------------------------------------------------------------------
/** Is the given widnow in the clipboard at the top level.  NOTE that
	* children are NOT included in this search */
//-------------------------------------------------------------------------------------------------
Bool GUIEditWindowManager::isWindowInClipboard( GameWindow *window,
																								GameWindow **list )
{
	GameWindow *other;

	// sanity
	if( list == NULL || window == NULL )
		return FALSE;

	// just run through the window list in the clipboard to check
	for( other = *list; other; other = other->winGetNext() )
	{

		if( window == other )
			return TRUE;  // found

	}  // end for window

	return FALSE;  // not found

}  // end isWindowInClipboard

//-------------------------------------------------------------------------------------------------
/** Add the window to clipboard list */
//-------------------------------------------------------------------------------------------------
void GUIEditWindowManager::linkToClipboard( GameWindow *window,
																						GameWindow **list )
{

	// sanity
	if( window == NULL || list == NULL )
		return;

	// debug sanity checking, can't add if already in it
	if( isWindowInClipboard( window, list ) )
		return;

	window->winSetPrev( NULL );
	window->winSetNext( *list );
	if( *list )
		(*list)->winSetPrev( window );
	*list = window;

}  // end linkToClipboard

//-------------------------------------------------------------------------------------------------
/** Unlink window from the clipboard list */
//-------------------------------------------------------------------------------------------------
void GUIEditWindowManager::unlinkFromClipboard( GameWindow *window,
																								GameWindow **list )
{
	GameWindow *next, *prev;

	// sanity
	if( window == NULL || list == NULL )
		return;

	// debug sanity checking, can't remove if not in
	if( isWindowInClipboard( window, list ) == FALSE )
		return;

	prev = window->winGetPrev();
	next = window->winGetNext();

	if( next )
		next->winSetPrev( prev );
	if( prev )
		prev->winSetNext( next );
	else
		*list = next;

}  // end unlinkFromClipboard

//-------------------------------------------------------------------------------------------------
/** Remove selection entries for child windows that also have ANY of
	* their parents in the selection list. */
//-------------------------------------------------------------------------------------------------
void GUIEditWindowManager::removeSupervisedChildSelections( void )
{
	WindowSelectionEntry *select, *next;
	GameWindow *window, *parent;

	// iterate selections
	for( select = TheEditor->getSelectList(); select; select = next )
	{
		Bool parentSelected;

		// get next entry
		next = select->next;

		// get window data
		window = select->window;
		parent = window->winGetParent();

		// check to see if any parent is selected
		parentSelected = FALSE;
		while( parent )
		{

			if( TheEditor->isWindowSelected( parent ) )
				parentSelected = TRUE;
			parent = parent->winGetParent();

		}  // end while

		//
		// if there is a parent selected then we can remove this
		// selection
		//
		if( parentSelected == TRUE )
			TheEditor->unSelectWindow( window );

	}  // end for select

}  // end removeSupervisedChildSelections

//-------------------------------------------------------------------------------------------------
/** Traverse the selected window list, any child windows we encounter
	* are removed from their parent and placed in the window list at
	* the top level.  Window position adjustments are made to compensate
	* for the transition from "parent space" to "screen space" */
//-------------------------------------------------------------------------------------------------
/*
void GUIEditWindowManager::orphanSelectedChildren( void )
{
	WindowSelectionEntry *select;
	GameWindow *window, *parent;

	for( select = TheEditor->getSelectList(); select; select = select->next )
	{
			
		// get window information
		window = select->window;
		parent = window->winGetParent();

		// we have a parent we're a candidate
		if( parent )
		{
			ICoord2D pos;

			// get the screen position of the window BEFORE we remove the parent
			window->winGetScreenPosition( &pos.x, &pos.y );

			// remove the child from the parent and add to top level of window system
			window->winSetParent( NULL );

			//
			// adjust the position, which previously was relative to the parent
			// but now is relative to the screen
			//
			window->winSetPosition( pos.x, pos.y );

		}  // end if, we had a parent

	}  // end for select

}  // end orphanSelectedChildren
*/

///////////////////////////////////////////////////////////////////////////////////////////////////
// PUBLIC FUNCTIONS ///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////

//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
GUIEditWindowManager::GUIEditWindowManager( void )
{

	m_clipboard = NULL;
	m_clipboardDup = NULL;

	m_copySpacing = 8;
	m_numCopiesPasted = 0;

}  // end GUIEditWindowManager

//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
GUIEditWindowManager::~GUIEditWindowManager( void )
{

	// the duplicate list is only used in the actual act of pasting
	assert( m_clipboardDup == NULL );

	// free all data on the clipboard
	resetClipboard();

}  // end ~GUIEditWindowManager

//-------------------------------------------------------------------------------------------------
/** initialize the system */
//-------------------------------------------------------------------------------------------------
void GUIEditWindowManager::init( void )
{

	// extending functionality
	W3DGameWindowManager::init();

}  // end init

//-------------------------------------------------------------------------------------------------
/** Destroy a game window */
//-------------------------------------------------------------------------------------------------
Int GUIEditWindowManager::winDestroy( GameWindow *window )
{

	//
	// delete the edit data from window if present, the editor is the
	// ONLY place where this edit data is allocated anyway, it is
	// NOT present in the game
	//
	GameWindowEditData *editData = window->winGetEditData();
	if( editData )
	{

		// delete it
		delete editData;

		// set the edit data to NULL in the window
		window->winSetEditData( NULL );

	}  // end if

	// call our base class functionality
	return W3DGameWindowManager::winDestroy( window );

}  // end winDestroy

//-------------------------------------------------------------------------------------------------
/** Create a new window by setting up its parameters and callbacks. */
//-------------------------------------------------------------------------------------------------
GameWindow *GUIEditWindowManager::winCreate( GameWindow *parent, 
																						 UnsignedInt status, 
	 																				   Int x, Int y,
																						 Int width, Int height,
																						 GameWinSystemFunc system,
																						 WinInstanceData *instData )
{
	GameWindow *window;

	// call base class create
	window = W3DGameWindowManager::winCreate( parent, status, x, y,
																						width, height, system,
																						instData );

	// allocate edit data for the window
	if( window )
	{
		GameWindowEditData *editData = new GameWindowEditData;

		// set edit data to nothing
		memset( editData, 0, sizeof( GameWindowEditData ) );

		// attach edit data to window
		window->winSetEditData( editData );

	}  // end if

	return window;

}  // end winCreate

//-------------------------------------------------------------------------------------------------
/** Destroy all windows in the clipboard */
//-------------------------------------------------------------------------------------------------
void GUIEditWindowManager::resetClipboard( void )
{
	GameWindow *window, *next;

	// iterate through all windows
	for( window = m_clipboard; window; window = next )
	{

		// get next window
		next = window->winGetNext();

		//
		// in order for the destroy to really work correctly, the system
		// assumes that the window is actually part of the system, so take it
		// away from the clipboard and add it back to the regular system
		//
		unlinkFromClipboard( window, &m_clipboard );
		linkWindow( window );

		//
		// delete the window, note that these aren't part of the active editor,
		// the live only in the clipboard land so we don't need to notify
		// the edit window or the editor that this is going away ... they can't
		// possible be selected or anything
		//
		winDestroy( window );

	}  // end for window

	// immediately process the destroyed windows just to be clean here
	processDestroyList();

	// nothing in the buffer now
	m_clipboard = NULL;
	m_numCopiesPasted = 0;

}  // end resetClipboard

//-------------------------------------------------------------------------------------------------
/** Is the clipboard empty */
//-------------------------------------------------------------------------------------------------
Bool GUIEditWindowManager::isClipboardEmpty( void )
{

	if( m_clipboard )
		return TRUE;

	return FALSE;

}  // end isClipboardEmtpy

//-------------------------------------------------------------------------------------------------
/** Remove the selected windows from the current layout and put them into
	* the clipboard.  If the parent of a window is not being cut with the
	* child the child will have no parent and will be adjusted to absolute
	* screen coordinates with no parent */
//-------------------------------------------------------------------------------------------------
void GUIEditWindowManager::cutSelectedToClipboard( void )
{

	//
	// reset the clipboard just to be safe, this is done in the copy but
	// it's nice cause we depend on it here for deleting
	//
	resetClipboard();

	// first copy the selected windows to the clipboard
	copySelectedToClipboard();

	//
	// now delete the selected windows from the scene if we actually
	// copied them to the clipboard
	//
	if( m_clipboard )
		TheEditor->deleteSelected();

}  // end cutSelectedToClipboard

//-------------------------------------------------------------------------------------------------
/** Given the current window in a list pointed to by 'root', if that
	* window is selected, duplicate it and place it on the clipboard.  If
	* it's not selected check it's children to see if any of them are
	* selected and should be duplicated.  Then just move on down the list
	* and check the other sibling windows as well */
//-------------------------------------------------------------------------------------------------
void GUIEditWindowManager::duplicateSelected( GameWindow *root )
{

	// end of recursion
	if( root == NULL )
		return;

	// if widow is selected duplicate and continue on
	if( TheEditor->isWindowSelected( root ) )
	{
		GameWindow *duplicate;

		// perform the duplication of window and all children
		duplicate = duplicateWindow( root, NULL );
		if( duplicate )
		{

			//
			// if our source window had a parent, that means that the child
			// was selected, but the parent wasn't.  Therefore the new
			// duplicate has no parent, but needs to have its position 
			// adjusted to be relative to the screen instead of the now
			// missing parent
			//
			GameWindow *parent = root->winGetParent();
			if( parent )
			{
				ICoord2D parentPos;
				ICoord2D pos;

				parent->winGetScreenPosition( &parentPos.x, &parentPos.y );
				duplicate->winGetScreenPosition( &pos.x, &pos.y );
				pos.x += parentPos.x;
				pos.y += parentPos.y;
				duplicate->winSetPosition( pos.x, pos.y );
				
			}  // end if

			// add window to the clipboard
			linkToClipboard( duplicate, &m_clipboard );

		}  // end if

	}  // end if
	else
	{

		//
		// the window itself is not selected, but some of it's children
		// may be so check them
		//
		duplicateSelected( root->winGetChild() );

	}  // end else

	// move on to the next window
	duplicateSelected( root->winGetNext() );

}  // end duplicateSelected

//-------------------------------------------------------------------------------------------------
/** Copy the selected windows to the clipboard */
//-------------------------------------------------------------------------------------------------
void GUIEditWindowManager::copySelectedToClipboard( void )
{

	// reset the contents of the clipboard
	resetClipboard();

	//
	// to simplify things, child windows that ALSO have their
	// parent window selected will become unselected, anything that
	// happens to the parent will automatically include the child
	//
	removeSupervisedChildSelections();

	//
	// go through the window list and duplicate anything selected, note
	// that the clipboard will contain the list of windows in reverse
	// order from the original order in the layout
	//
	duplicateSelected( m_windowList );

}  // end copySelectedToClipboard

//-------------------------------------------------------------------------------------------------
/** Take the string representation of the name of the window passed in,
	* and add a number to it at the end.  If there is already a number
	* on it at the end, that number will be incremented */
//-------------------------------------------------------------------------------------------------
void GUIEditWindowManager::incrementName( GameWindow *window )
{
	WinInstanceData *instData = window->winGetInstanceData();

	// bleah, this is ugly and hokey, but if I rewrite
	// this I will botch it (cuz I'm currently not sure
	// how to test it)
	char name[MAX_WINDOW_NAME_LEN];
	strcpy(name, instData->m_decoratedNameString.str());

	Int len = strlen( name );
	char numberBuffer[ MAX_WINDOW_NAME_LEN ];
	Int i, j;
	char c;
	Bool hasNumberSuffix = FALSE;
	Int numberStartIndex = 0;

	// trivial case, just append a number to the end and get outta here
	if( len == 0 )
	{
		
		strcat( name, "1" );
		goto cleanup;

	}  // end if

	//
	// start from the end of the string and back up to the start picking
	// off each character, checking if it's a number, and adding that number
	// to a string buffer.  After all numbers are read from the name
	// the string is turned real number, incremented, and replaces the
	// existing number string
	//
	for( i = 0; i < MAX_WINDOW_NAME_LEN; i++ )
		numberBuffer[ i ] = ' ';
	numberBuffer[ MAX_WINDOW_NAME_LEN - 1 ] = '\0';
	for( i = len - 1, j = MAX_WINDOW_NAME_LEN - 2; i >= 0; i-- )
	{

		c = name[ i ];
		if( isdigit( c ) )
		{

			hasNumberSuffix = TRUE;
			numberBuffer[ j-- ] = c;

		}  // end if
		else
		{
			
			numberStartIndex = i + 1;
			break;  // exit for i

		}  // end else

	}  // end for i

	//
	// if we have a number suffix change it to a number, increment it, and
	// paste it over the old number
	//
	if( hasNumberSuffix == TRUE )
	{
		Int number = atoi( numberBuffer );
		Int charsAdded;

		// increment the number
		number++;

		// turn number back into string
		itoa( number, numberBuffer, 10 );

		// put number string OVER the original number string in the name
		len = strlen( numberBuffer );
		charsAdded = 0;
		for( i = 0; i < len; i++ )
		{
			if( i + numberStartIndex <= MAX_WINDOW_NAME_LEN - 2 )
			{

				name[ i + numberStartIndex ] = numberBuffer[ i ];
				charsAdded++;

			}  // end if
		}
		name[ numberStartIndex + charsAdded ] = 0;  // terminate at end of new string

	}  // end if
	else
	{

		// no number at end, easy ... just append a number '1'
		strcat( name, "1" );

	}  // end else

cleanup:

	instData->m_decoratedNameString = name;

}  // end incrementName

//-------------------------------------------------------------------------------------------------
/** Validate the names for all the windows and child windows in the copy
	* buffer that they are unique and don't collide with any windows in
	* the real system.  This is in preparation for a paste of the copy
	* buffer into the real working layout */
//-------------------------------------------------------------------------------------------------
void GUIEditWindowManager::validateClipboardNames( GameWindow *root )
{
	Bool nameOK = FALSE;
	WinInstanceData *instData;
	Int sanityLoop = 0;

	// end of recursion
	if( root == NULL )
		return;

	// get our inst data
	instData = root->winGetInstanceData();

	//
	// we will check our name against all names in the active layout AND
	// in the clipboard, if it's a dupe we will pick a new name and
	// try it over and over until it's OK
	//
	while( nameOK == FALSE )
	{

		// if we're not a dupe with anything anywhere we're ok
		if( !TheEditor->isNameDuplicate( m_windowList, root, instData->m_decoratedNameString ) &&
				!TheEditor->isNameDuplicate( m_clipboardDup, root, instData->m_decoratedNameString ) )
			nameOK = TRUE;
		else
			incrementName( root );  // have to change our name, so sorry :(

		//
		// and just because I don't want the possibility of a infinite loop
		// EVER to occur here this is just a sanity check that will probably
		// never get run
		//
		if( ++sanityLoop > 99999999 )
		{
			char buffer[ 1024 ];

			sprintf( buffer, "Unable to make unique name for '%s', please manually edit the name before saving.",
							 instData->m_decoratedNameString.str() );
			MessageBox( TheEditor->getWindowHandle(), buffer, "Error Mapping Names", MB_OK );
			nameOK = TRUE;

		}  // end if

	}  // end if

	// validate our children names
	GameWindow *child;
	for( child = root->winGetChild(); child; child = child->winGetNext() )
		validateClipboardNames( child );

	// validate the next window in the list
	validateClipboardNames( root->winGetNext() );

}  // end validateClipboardNames

//-------------------------------------------------------------------------------------------------
/** Paste the contents of the clipboard into the window world */
//-------------------------------------------------------------------------------------------------
void GUIEditWindowManager::pasteClipboard( void )
{
	GameWindow *window, *next;
	GameWindow *firstWindow = NULL;

	// check for empty clipboard
	if( m_clipboard == NULL )
	{

		MessageBox( TheEditor->getWindowHandle(),
								"Cannot perform paste, the clipboard is empty.",
								"Clipboard Empty",
								MB_OK );
		return;

	}  // end if

	// create a duplicate of everything in the clipboard
	assert( m_clipboardDup == NULL );
	createClipboardDuplicate();

	//
	// we are about to paste the contents of the clipboard dupe into the world,
	// scan through the clipboard dupe and adjust any names that will cause
	// collisions in the current layout
	//
	validateClipboardNames( m_clipboardDup );

	// keep a pointer to the first window we will add
	firstWindow = m_clipboardDup;

	// move everything from the duplicate list into the real world
	for( window = m_clipboardDup; window; window = next )
	{

		// get next window
		next = window->winGetNext();

		// remove from clipboard
		unlinkFromClipboard( window, &m_clipboardDup );

		// place in real layout
		linkWindow( window );

		//
		// since we just pasted in a new window, offset it a little bit
		// from the original location based on how many times we're pasted
		// this set of copies.  This way they won't all stack on top of
		// each other
		//
		ICoord2D pos, safePos;

		window->winGetPosition( &pos.x, &pos.y );
		pos.x += m_copySpacing * (m_numCopiesPasted + 1);
		pos.y += m_copySpacing * (m_numCopiesPasted + 1);

		// kee the location legal
		TheEditor->computeSafeLocation( window, pos.x, pos.y,
																		&safePos.x, &safePos.y );

		// set the new location
		TheEditor->moveWindowTo( window, safePos.x, safePos.y );

		// notify the hierarchy of the new window added
		TheHierarchyView->addWindow( window, HIERARCHY_ADD_AT_TOP );

	}  // end for window

	// the clipboard duplicate list is only for the act of pasting
	assert( m_clipboardDup == NULL );

	// we've now completed another successful copy pasted in
	m_numCopiesPasted++;

	//
	// now that all windows were added to the layout, and we have
	// the first window that we added ... since we know they were added
	// at the head of the window list we can traverse from the first
	// window added to the head of the window list to access each new
	// window we just pasted.  As a convenience we will unselct anything
	// selected and select all the windows we added
	//
	TheEditor->clearSelections();
	window = firstWindow;
	while( window )
	{

		TheEditor->selectWindow( window );
		window = window->winGetPrev();

		// notify the editor that each of the windows was created
		TheEditor->notifyNewWindow( window );

	}  // end while

	// if we did in fact paste a window then our file contents have changed
	if( firstWindow )
		TheEditor->setUnsaved( TRUE );

}  // end pasteClipboard

//-------------------------------------------------------------------------------------------------
/** Convinience funtion to copy the Draw state info for a given instance */
//-------------------------------------------------------------------------------------------------
void InstDrawCopy (  WinInstanceData *instData, WinInstanceData *sourceInstData)
{
	// do the copy of the colors for each state
	memcpy( &instData->m_enabledDrawData,
					&sourceInstData->m_enabledDrawData,
					sizeof( WinDrawData ) * MAX_DRAW_DATA );
	memcpy( &instData->m_disabledDrawData,
					&sourceInstData->m_disabledDrawData,
					sizeof( WinDrawData ) * MAX_DRAW_DATA );
	memcpy( &instData->m_hiliteDrawData,
					&sourceInstData->m_hiliteDrawData,
					sizeof( WinDrawData ) * MAX_DRAW_DATA );
}

//-------------------------------------------------------------------------------------------------
/** Duplciate a window and all its children */
//-------------------------------------------------------------------------------------------------
GameWindow *GUIEditWindowManager::duplicateWindow( GameWindow *source,
																									 GameWindow *parent )
{
	GameWindow *duplicate = NULL;
	UnsignedInt style, status;
	WinInstanceData *instData;
	WinInstanceData instDataCopy;
	ICoord2D pos, size;

	// sanity
	if( source == NULL )
		return NULL;

	// get the window instance data and make a copy of it for creating new stuff
	instData = source->winGetInstanceData();
	instDataCopy = *instData;
//	memcpy( &instDataCopy, instData, sizeof( instDataCopy ) );

	//
	// do NOT copy the display string instances, these MUST be allocated when
	// needed in real windows
	//
	instDataCopy.m_text = NULL;
	instDataCopy.m_tooltip = NULL;

	// get a few properties we're going to need
	style = source->winGetStyle();
	status = source->winGetStatus();
	source->winGetPosition( &pos.x, &pos.y );
	source->winGetSize( &size.x, &size.y );

	// create a new window or gadget of the appropriate type
	if( BitTest( style, GWS_PUSH_BUTTON ) )
	{

		duplicate = 
			TheWindowManager->gogoGadgetPushButton( parent,
																							status,
																							pos.x,
																							pos.y,
																							size.x,
																							size.y,
																							&instDataCopy,
																							source->winGetFont(),
																							FALSE );

	}  // end if
	else if( BitTest( style, GWS_RADIO_BUTTON ) )
	{
		RadioButtonData *radioData = (RadioButtonData *)source->winGetUserData();
		RadioButtonData radioDataCopy;

		radioDataCopy = *radioData;
//		memcpy( &radioDataCopy, radioData, sizeof( RadioButtonData ) );

		duplicate =
			TheWindowManager->gogoGadgetRadioButton( parent,
																							 status,
																							 pos.x,
																							 pos.y,
																							 size.x,
																							 size.y,
																							 &instDataCopy,
																							 &radioDataCopy,
																							 source->winGetFont(),
																							 FALSE );

	}  // end else if
	else if( BitTest( style, GWS_CHECK_BOX ) )
	{

		duplicate = 
			TheWindowManager->gogoGadgetCheckbox( parent,
																						status,
																						pos.x,
																						pos.y,
																						size.x,
																						size.y,
																						&instDataCopy,
																						source->winGetFont(),
																						FALSE );

	}  // end else if
	else if( BitTest( style, GWS_HORZ_SLIDER | GWS_VERT_SLIDER ) )
	{
		SliderData *sliderData = (SliderData *)source->winGetUserData();
		SliderData sliderDataCopy;

		sliderDataCopy = *sliderData;
//		memcpy( &sliderDataCopy, sliderData, sizeof( SliderData ) );

		duplicate = TheWindowManager->gogoGadgetSlider( parent,
																										status,
																										pos.x,
																										pos.y,
																										size.x,
																										size.y,
																										&instDataCopy,
																										&sliderDataCopy,
																										source->winGetFont(),
																										FALSE );

		//
		// copy the color and image schemes of the sub controls that make
		// up a slider
		//
		if( duplicate )
		{
			WinInstanceData *instData;
			WinInstanceData *sourceInstData;

			// do the slider thumb
			GameWindow *thumb = GadgetSliderGetThumb( duplicate );
			GameWindow *sourceThumb = GadgetSliderGetThumb( source );
			if( thumb && sourceThumb )
			{

				instData = thumb->winGetInstanceData();
				sourceInstData = sourceThumb->winGetInstanceData();
				
				// do the copy of the colors for each state
				memcpy( &instData->m_enabledDrawData,
								&sourceInstData->m_enabledDrawData,
								sizeof( WinDrawData ) * MAX_DRAW_DATA );
				memcpy( &instData->m_disabledDrawData,
								&sourceInstData->m_disabledDrawData,
								sizeof( WinDrawData ) * MAX_DRAW_DATA );
				memcpy( &instData->m_hiliteDrawData,
								&sourceInstData->m_hiliteDrawData,
								sizeof( WinDrawData ) * MAX_DRAW_DATA );

			}  // end if

		}  // end if

	}  // end else if
	else if( BitTest( style, GWS_COMBO_BOX ) )
	{
		ComboBoxData *comboData = (ComboBoxData *)source->winGetUserData();
		ComboBoxData comboDataCopy;

		memset( &comboDataCopy, 0, sizeof( ComboBoxData ) );		
		
		comboDataCopy.entryData = new EntryData;
		memset ( comboDataCopy.entryData, 0, sizeof(EntryData));
		comboDataCopy.listboxData = new ListboxData;
		memset ( comboDataCopy.listboxData, 0, sizeof(ListboxData));

	
		
		comboDataCopy.entryCount = comboData->entryCount;
		comboDataCopy.isEditable = comboData->isEditable;
		comboDataCopy.maxChars = comboData->maxChars;
		comboDataCopy.maxDisplay = comboData->maxDisplay;
		comboDataCopy.lettersAndNumbersOnly = comboData->lettersAndNumbersOnly;
		comboDataCopy.asciiOnly = comboData->asciiOnly;

		comboDataCopy.listboxData->autoPurge = comboData->listboxData->autoPurge;
		comboDataCopy.listboxData->autoScroll = comboData->listboxData->autoScroll;
		comboDataCopy.listboxData->scrollIfAtEnd = comboData->listboxData->scrollIfAtEnd;
		comboDataCopy.listboxData->forceSelect = comboData->listboxData->forceSelect;
		comboDataCopy.listboxData->listLength = comboData->listboxData->listLength;
		comboDataCopy.listboxData->multiSelect = comboData->listboxData->multiSelect;
		comboDataCopy.listboxData->scrollBar = comboData->listboxData->scrollBar;
		comboDataCopy.listboxData->columns = comboData->listboxData->columns;
		comboDataCopy.listboxData->columnWidth = comboData->listboxData->columnWidth;
		comboDataCopy.listboxData->columnWidthPercentage = comboData->listboxData->columnWidthPercentage;

		comboDataCopy.entryData->alphaNumericalOnly = comboData->entryData->alphaNumericalOnly;
		comboDataCopy.entryData->aSCIIOnly = comboData->entryData->aSCIIOnly;
		comboDataCopy.entryData->maxTextLen = comboData->entryData->maxTextLen;
		comboDataCopy.entryData->numericalOnly = comboData->entryData->numericalOnly;
		
		

		duplicate = 
			TheWindowManager->gogoGadgetComboBox( parent,
																					 status,
																					 pos.x,
																					 pos.y,
																					 size.x,
																					 size.y,
																					 &instDataCopy,
																					 &comboDataCopy,
																					 source->winGetFont(),
																					 FALSE );

		//
		// copy the color and image schemes of the sub controls that make
		// up a listbox
		//
		if( duplicate )
		{
			ComboBoxData *comboData = (ComboBoxData *)duplicate->winGetUserData();
			ComboBoxData *sourceComboData = (ComboBoxData *)source->winGetUserData();
			WinInstanceData *instData;
			WinInstanceData *sourceInstData;
			

			// drop down button
			GameWindow *dropDownButton = comboData->dropDownButton;
			GameWindow *sourceDropDownButton = sourceComboData->dropDownButton;
			if(dropDownButton && sourceDropDownButton)
			{
				instData = dropDownButton->winGetInstanceData();
				sourceInstData = sourceDropDownButton->winGetInstanceData();

				InstDrawCopy(instData,sourceInstData);			
			}

			// edit box
			GameWindow *editBox = comboData->editBox;
			GameWindow *sourceEditBox = sourceComboData->editBox;
			if(editBox && sourceEditBox)
			{
				instData = editBox->winGetInstanceData();
				sourceInstData = sourceEditBox->winGetInstanceData();

				InstDrawCopy(instData,sourceInstData);			
			}
			
			// ListBox
			GameWindow *listBox = comboData->listBox;
			GameWindow *sourceListBox = sourceComboData->listBox;
			if(listBox && sourceListBox)
			{
				instData = listBox->winGetInstanceData();
				sourceInstData = sourceListBox->winGetInstanceData();

				InstDrawCopy(instData,sourceInstData);			
				
				// up button
				GameWindow *upButton = comboData->listboxData->upButton;
				GameWindow *sourceUpButton = sourceComboData->listboxData->upButton;
				if( upButton && sourceUpButton )
				{
				
					instData = upButton->winGetInstanceData();
					sourceInstData = sourceUpButton->winGetInstanceData();
					
					// do the copy of the colors for each state
					memcpy( &instData->m_enabledDrawData,
									&sourceInstData->m_enabledDrawData,
									sizeof( WinDrawData ) * MAX_DRAW_DATA );
					memcpy( &instData->m_disabledDrawData,
									&sourceInstData->m_disabledDrawData,
									sizeof( WinDrawData ) * MAX_DRAW_DATA );
					memcpy( &instData->m_hiliteDrawData,
									&sourceInstData->m_hiliteDrawData,
									sizeof( WinDrawData ) * MAX_DRAW_DATA );

				}  // if

				// down button
				GameWindow *downButton = comboData->listboxData->downButton;
				GameWindow *sourceDownButton = sourceComboData->listboxData->downButton;
				if( downButton && sourceDownButton )
				{
				
					instData = downButton->winGetInstanceData();
					sourceInstData = sourceDownButton->winGetInstanceData();
					
					// do the copy of the colors for each state
					memcpy( &instData->m_enabledDrawData,
									&sourceInstData->m_enabledDrawData,
									sizeof( WinDrawData ) * MAX_DRAW_DATA );
					memcpy( &instData->m_disabledDrawData,
									&sourceInstData->m_disabledDrawData,
									sizeof( WinDrawData ) * MAX_DRAW_DATA );
					memcpy( &instData->m_hiliteDrawData,
									&sourceInstData->m_hiliteDrawData,
									sizeof( WinDrawData ) * MAX_DRAW_DATA );

				}  // if

				// slider
				GameWindow *slider = comboData->listboxData->slider;
				GameWindow *sourceSlider = sourceComboData->listboxData->slider;
				if( slider && sourceSlider )
				{
				
					instData = slider->winGetInstanceData();
					sourceInstData = sourceSlider->winGetInstanceData();
					
					// do the copy of the colors for each state
					memcpy( &instData->m_enabledDrawData,
									&sourceInstData->m_enabledDrawData,
									sizeof( WinDrawData ) * MAX_DRAW_DATA );
					memcpy( &instData->m_disabledDrawData,
									&sourceInstData->m_disabledDrawData,
									sizeof( WinDrawData ) * MAX_DRAW_DATA );
					memcpy( &instData->m_hiliteDrawData,
									&sourceInstData->m_hiliteDrawData,
									sizeof( WinDrawData ) * MAX_DRAW_DATA );

					// do the slider thumb
					GameWindow *thumb = GadgetSliderGetThumb( slider );
					GameWindow *sourceThumb = GadgetSliderGetThumb( sourceSlider );
					if( thumb && sourceThumb )
					{

						instData = thumb->winGetInstanceData();
						sourceInstData = sourceThumb->winGetInstanceData();
						
					// do the copy of the colors for each state
					memcpy( &instData->m_enabledDrawData,
									&sourceInstData->m_enabledDrawData,
									sizeof( WinDrawData ) * MAX_DRAW_DATA );
					memcpy( &instData->m_disabledDrawData,
									&sourceInstData->m_disabledDrawData,
									sizeof( WinDrawData ) * MAX_DRAW_DATA );
					memcpy( &instData->m_hiliteDrawData,
									&sourceInstData->m_hiliteDrawData,
									sizeof( WinDrawData ) * MAX_DRAW_DATA );

					}  // end if

				}  // if

			} // if

		}  // end if

	}  // end else if
	else if( BitTest( style, GWS_SCROLL_LISTBOX ) )
	{
		ListboxData *listData = (ListboxData *)source->winGetUserData();
		ListboxData listDataCopy;

		memset( &listDataCopy, 0, sizeof( listDataCopy ) );
		listDataCopy.autoPurge = listData->autoPurge;
		listDataCopy.autoScroll = listData->autoScroll;
		listDataCopy.scrollIfAtEnd = listData->scrollIfAtEnd;
		listDataCopy.forceSelect = listData->forceSelect;
		listDataCopy.listLength = listData->listLength;
		listDataCopy.multiSelect = listData->multiSelect;
		listDataCopy.scrollBar = listData->scrollBar;
		listDataCopy.columns = listData->columns;
		if(listData->columnWidth)
		{
			listDataCopy.columnWidth = new Int[listData->columns];
			for(Int i = 0; i < listData->columns; i ++)
			{
				listDataCopy.columnWidth[i] = listData->columnWidth[i];
			}
		}
		else
			listDataCopy.columnWidth = NULL;

		if(listData->columnWidthPercentage)
		{
			listDataCopy.columnWidthPercentage = new Int[listData->columns];
			for(Int i = 0; i < listData->columns; i ++)
			{
				listDataCopy.columnWidthPercentage[i] = listData->columnWidthPercentage[i];
			}
		}
		else
			listDataCopy.columnWidthPercentage = NULL;

		duplicate = 
			TheWindowManager->gogoGadgetListBox( parent,
																					 status,
																					 pos.x,
																					 pos.y,
																					 size.x,
																					 size.y,
																					 &instDataCopy,
																					 &listDataCopy,
																					 source->winGetFont(),
																					 FALSE );

		//
		// copy the color and image schemes of the sub controls that make
		// up a listbox
		//
		if( duplicate )
		{
			ListboxData *listData = (ListboxData *)duplicate->winGetUserData();
			ListboxData *sourceListData = (ListboxData *)source->winGetUserData();
			WinInstanceData *instData;
			WinInstanceData *sourceInstData;

			// up button
			GameWindow *upButton = listData->upButton;
			GameWindow *sourceUpButton = sourceListData->upButton;
			if( upButton && sourceUpButton )
			{
			
				instData = upButton->winGetInstanceData();
				sourceInstData = sourceUpButton->winGetInstanceData();
				
				// do the copy of the colors for each state
				memcpy( &instData->m_enabledDrawData,
								&sourceInstData->m_enabledDrawData,
								sizeof( WinDrawData ) * MAX_DRAW_DATA );
				memcpy( &instData->m_disabledDrawData,
								&sourceInstData->m_disabledDrawData,
								sizeof( WinDrawData ) * MAX_DRAW_DATA );
				memcpy( &instData->m_hiliteDrawData,
								&sourceInstData->m_hiliteDrawData,
								sizeof( WinDrawData ) * MAX_DRAW_DATA );

			}  // if

			// down button
			GameWindow *downButton = listData->downButton;
			GameWindow *sourceDownButton = sourceListData->downButton;
			if( downButton && sourceDownButton )
			{
			
				instData = downButton->winGetInstanceData();
				sourceInstData = sourceDownButton->winGetInstanceData();
				
				// do the copy of the colors for each state
				memcpy( &instData->m_enabledDrawData,
								&sourceInstData->m_enabledDrawData,
								sizeof( WinDrawData ) * MAX_DRAW_DATA );
				memcpy( &instData->m_disabledDrawData,
								&sourceInstData->m_disabledDrawData,
								sizeof( WinDrawData ) * MAX_DRAW_DATA );
				memcpy( &instData->m_hiliteDrawData,
								&sourceInstData->m_hiliteDrawData,
								sizeof( WinDrawData ) * MAX_DRAW_DATA );

			}  // if

			// slider
			GameWindow *slider = listData->slider;
			GameWindow *sourceSlider = sourceListData->slider;
			if( slider && sourceSlider )
			{
			
				instData = slider->winGetInstanceData();
				sourceInstData = sourceSlider->winGetInstanceData();
				
				// do the copy of the colors for each state
				memcpy( &instData->m_enabledDrawData,
								&sourceInstData->m_enabledDrawData,
								sizeof( WinDrawData ) * MAX_DRAW_DATA );
				memcpy( &instData->m_disabledDrawData,
								&sourceInstData->m_disabledDrawData,
								sizeof( WinDrawData ) * MAX_DRAW_DATA );
				memcpy( &instData->m_hiliteDrawData,
								&sourceInstData->m_hiliteDrawData,
								sizeof( WinDrawData ) * MAX_DRAW_DATA );

				// do the slider thumb
				GameWindow *thumb = GadgetSliderGetThumb( slider );
				GameWindow *sourceThumb = GadgetSliderGetThumb( sourceSlider );
				if( thumb && sourceThumb )
				{

					instData = thumb->winGetInstanceData();
					sourceInstData = sourceThumb->winGetInstanceData();
					
				// do the copy of the colors for each state
				memcpy( &instData->m_enabledDrawData,
								&sourceInstData->m_enabledDrawData,
								sizeof( WinDrawData ) * MAX_DRAW_DATA );
				memcpy( &instData->m_disabledDrawData,
								&sourceInstData->m_disabledDrawData,
								sizeof( WinDrawData ) * MAX_DRAW_DATA );
				memcpy( &instData->m_hiliteDrawData,
								&sourceInstData->m_hiliteDrawData,
								sizeof( WinDrawData ) * MAX_DRAW_DATA );

				}  // end if

			}  // if

		}  // end if

	}  // end else if
	else if( BitTest( style, GWS_ENTRY_FIELD ) )
	{
		EntryData *entryData = (EntryData *)source->winGetUserData();
		EntryData entryDataCopy;

		memset( &entryDataCopy, 0, sizeof( entryDataCopy ) );
		entryDataCopy.alphaNumericalOnly = entryData->alphaNumericalOnly;
		entryDataCopy.aSCIIOnly = entryData->aSCIIOnly;
		entryDataCopy.maxTextLen = entryData->maxTextLen;
		entryDataCopy.numericalOnly = entryData->numericalOnly;
		entryDataCopy.secretText = entryData->secretText;

		duplicate = 
			TheWindowManager->gogoGadgetTextEntry( parent,
																						 status,
																						 pos.x,
																						 pos.y,
																						 size.x,
																						 size.y,
																						 &instDataCopy,
																						 &entryDataCopy,
																						 source->winGetFont(),
																						 FALSE );

	}  // end else if
	else if( BitTest( style, GWS_STATIC_TEXT ) )
	{
		TextData *textData = (TextData *)source->winGetUserData();
		TextData textDataCopy;

		textDataCopy.centered = textData->centered;

		duplicate = 
			TheWindowManager->gogoGadgetStaticText( parent,
																							status,
																							pos.x,
																							pos.y,
																							size.x,
																							size.y,
																							&instDataCopy,
																							&textDataCopy,
																							source->winGetFont(),
																							FALSE );

	}  // end else if
	else if( BitTest( style, GWS_PROGRESS_BAR ) )
	{
		
		duplicate = 
			TheWindowManager->gogoGadgetProgressBar( parent,
																							 status,
																							 pos.x,
																							 pos.y,
																							 size.x,
																							 size.y,
																							 &instDataCopy, 
																							 source->winGetFont(),
																							 FALSE );

	}  // end else if
	else if( BitTest( style, GWS_USER_WINDOW ) )
	{

		// create plain ol generic window
		duplicate = TheWindowManager->winCreate( parent,
																						 status,
																						 pos.x,
																						 pos.y,
																						 size.x,
																						 size.y,
																						 NULL,
																						 &instDataCopy );

	}  // end else if
	else
	{

		MessageBox( TheEditor->getWindowHandle(), 
								"Cannot duplicate window, undefined style.", 
								"Internal Error", MB_OK );
		assert( 0 );
		memset( &instDataCopy, 0, sizeof( instDataCopy ) );  // see comment below
		return NULL;

	}  // end else

	// sanity
	if( duplicate == NULL )
	{

		MessageBox( TheEditor->getWindowHandle(), "Unable to duplicate window", 
								"Internal Error", MB_OK );
		assert( 0 );
		memset( &instDataCopy, 0, sizeof( instDataCopy ) );  // see comment below
		return NULL;

	}  // end if

	//
	// since we're using the real window system here to create things, we
	// want to immediately remove it from the real system list cause we're
	// not adding windows to the layout, only duplicating windows in 
	// isolation.  Note that child windows are linked to their parents
	// but that is totally isolated in the parent so that's OK and
	// necessary.
	//
	if( parent == NULL )
		unlinkWindow( duplicate );

	// copy edit data, only for the editor
	if( duplicate )
	{
		GameWindowEditData *editData = duplicate->winGetEditData();
		GameWindowEditData *sourceEditData = source->winGetEditData();

		// set edit data to zero or copy from source
		if( sourceEditData && editData )
			*editData = *sourceEditData;
//			memcpy( editData, sourceEditData, sizeof( GameWindowEditData ) );

	}  // end if

	//
	// duplicate all the children for the window, except if this window
	// is a gadget, those windows may have children but all of that
	// is already taken care of in the creation above ... a gadget should
	// be considered a single atomic unit
	//
	if( TheEditor->windowIsGadget( duplicate ) == FALSE )
	{
		GameWindow *child;

		for( child = source->winGetChild(); child; child = child->winGetNext() )
			duplicateWindow( child, duplicate );

	}  // end if

	//
	// the inst data copy was literally a copy, now it's going to go out of
	// scope and we DONT want any data to be freed that points back to the
	// original window e got it from.  So therefore we'll just blow it away
	//
	memset( &instDataCopy, 0, sizeof( instDataCopy ) );

	// return the duped window
	return duplicate;

}  // end duplicateWindow

//-------------------------------------------------------------------------------------------------
/** Create a duplicate everything on the clipboard list and put it
	* on the m_clipboardDup list for pasting */
//-------------------------------------------------------------------------------------------------
void GUIEditWindowManager::createClipboardDuplicate( void )
{
	GameWindow *duplicate;
	GameWindow *window;
	GameWindow *lastWindow;

	// find last window in clipboard
	lastWindow = m_clipboard;
	while( lastWindow && lastWindow->winGetNext() != NULL )
		lastWindow = lastWindow->winGetNext();

	//
	// go through each window in the clipboard, note we're starting from
	// the bottom and going to the front to preserve the same order
	// as the clipboard
	//
	for( window = lastWindow; window; window = window->winGetPrev() )
	{

		// duplicate the window and all its children
		duplicate = duplicateWindow( window, NULL );

		// add duplicate to list
		if( duplicate )
			linkToClipboard( duplicate, &m_clipboardDup );

	}  // end for

}  // end createClipboardDuplicate

//-------------------------------------------------------------------------------------------------
/** Make the 'target' a child of the 'parent' */
//-------------------------------------------------------------------------------------------------
void GUIEditWindowManager::makeChildOf( GameWindow *target,
																				GameWindow *parent )
{

	// sanity
	if( target == NULL )
		return;

	// get target parent
	GameWindow *prevParent = target->winGetParent();

	// check for no parent
	if( parent == NULL )
	{

		// if target already has no parent nothing to do
		if( prevParent == NULL )
			return;

		//
		// we are removing a parent from the target and placing target
		// at the top of the window list.  Be sure to preserve the
		// screen position as the new coords so it's in the same place
		//
		ICoord2D screenPos;
		target->winGetScreenPosition( &screenPos.x, &screenPos.y );
		unlinkChildWindow( target );
		linkWindow( target );
		target->winSetPosition( screenPos.x, screenPos.y );
		return;

	}  // end if

	// check to see if this is already out parent, nothing to do
	if( prevParent == parent )
		return;

	// you cannot make a window a child of a gadget control
	if( TheEditor->windowIsGadget( parent ) )
	{

		MessageBox( TheEditor->getWindowHandle(),
								"You cannot give a gadget control a child.",
								"Child Not Allowed",
								MB_OK );
		return;

	}  // end if

	// get the target screen position before the move to child
	ICoord2D screenPosBeforeMove;
	target->winGetScreenPosition( &screenPosBeforeMove.x,
																&screenPosBeforeMove.y );

	// remove the target from the window or any child list
	if( prevParent )
		unlinkChildWindow( target );
	else
		unlinkWindow( target );

	// add as child of 'parent'
	addWindowToParent( target, parent );

	// adjust the position so it's relative to the new parent
	ICoord2D parentPos;
	parent->winGetScreenPosition( &parentPos.x, &parentPos.y );
	target->winSetPosition( screenPosBeforeMove.x - parentPos.x,
													screenPosBeforeMove.y - parentPos.y );

	// notify the hierarchy of the change
	TheHierarchyView->moveWindowChildOf( target, parent );

	//
	// run through the move code to keep the child in a legal position
	// inside the parent client area
	//
	ICoord2D origin, safeLoc;
	target->winGetScreenPosition( &origin.x, &origin.y );

	// kee the location legal
	TheEditor->computeSafeLocation( target, origin.x, origin.y, 
																	&safeLoc.x, &safeLoc.y );
	
	// move the target
	TheEditor->moveWindowTo( target, safeLoc.x, safeLoc.y );
	
}  // end makeChildOf

//-------------------------------------------------------------------------------------------------
/** Move the 'windowToMove' to be just in front of the 'aheadOf' window
	* in either the master window list or in the child list */
//-------------------------------------------------------------------------------------------------
void GUIEditWindowManager::moveAheadOf( GameWindow *windowToMove, 
																				GameWindow *aheadOf )
{

	// sanity
	if( windowToMove == NULL || aheadOf == NULL || windowToMove == aheadOf )
		return;

	//
	// get our screen position before the move ... we want to appear in the
	// same place after we're moved
	//
	ICoord2D beforeMoveScreenPos;
	windowToMove->winGetScreenPosition( &beforeMoveScreenPos.x, 
																			&beforeMoveScreenPos.y );

	// get the parent of that which we are moving
	GameWindow *prevParent = windowToMove->winGetParent();

	// take the window to move off the chain
	if( prevParent )
	{
		ICoord2D pos;

		// get he screen position of the window BEFORE we unlink it
		windowToMove->winGetScreenPosition( &pos.x, &pos.y );

		// take off the child list, this will remove our parent pointer as well
		unlinkChildWindow( windowToMove );

		//
		// now that we're unlinked, set the position to be accurate with
		// the screen position and no parent
		//
		windowToMove->winSetPosition( pos.x, pos.y );

	}  // end if
	else
	{

		// just take off the main list
		unlinkWindow( windowToMove );
	
	}  // end else

	// insert the window at the specified location
	insertWindowAheadOf( windowToMove, aheadOf );

	//
	// adjust our position to be in the same place in our new location
	// in the chain ... if we gained a parent we have to offset by
	// the difference between our parent screen and our screen positions.
	// if we lost a parent our position must be set to the absolute
	// screen position
	//
	GameWindow *newParent = windowToMove->winGetParent();
	ICoord2D parentPos = { 0 };  // init to top left of screen
	if( newParent )
		newParent->winGetScreenPosition( &parentPos.x, &parentPos.y );
	windowToMove->winSetPosition( beforeMoveScreenPos.x - parentPos.x,
																beforeMoveScreenPos.y - parentPos.y );

	// move the window hierarchy representation of this window
	TheHierarchyView->moveWindowAheadOf( windowToMove, aheadOf );

	//
	// run through the move code to keep the child in a legal position
	// inside the parent client area
	//
	ICoord2D origin, safeLoc;
	windowToMove->winGetScreenPosition( &origin.x, &origin.y );

	// kee the location legal
	TheEditor->computeSafeLocation( windowToMove, origin.x, origin.y, 
																	&safeLoc.x, &safeLoc.y );
	
	// move the target
	TheEditor->moveWindowTo( windowToMove, safeLoc.x, safeLoc.y );

}  // end moveAheadOf

