/*
===========================================================================

Doom 3 GPL Source Code
Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.

This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code").

Doom 3 Source Code 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.

Doom 3 Source Code 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 Doom 3 Source Code.  If not, see <http://www.gnu.org/licenses/>.

In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code.  If not, please request a copy in writing from id Software at the address below.

If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.

===========================================================================
*/

#include "tools/edit_gui_common.h"


#include "../../sys/win32/rc/guied_resource.h"
#include "../../renderer/tr_local.h"
#include "../../sys/win32/win_local.h"
#include "../../ui/DeviceContext.h"
#include "../../ui/EditWindow.h"
#include "../../ui/ListWindow.h"
#include "../../ui/BindWindow.h"
#include "../../ui/RenderWindow.h"
#include "../../ui/ChoiceWindow.h"

#include "GEApp.h"
#include "GEItemPropsDlg.h"
#include "GEItemScriptsDlg.h"

// Modifiers
#include "GEModifierGroup.h"
#include "GEMoveModifier.h"
#include "GESizeModifier.h"
#include "GEStateModifier.h"
#include "GEZOrderModifier.h"
#include "GEInsertModifier.h"
#include "GEHideModifier.h"
#include "GEDeleteModifier.h"

static float g_ZoomScales[rvGEWorkspace::ZOOM_MAX] = { 0, 0.25f, 0.33f, 0.50f, 0.66f, 1.0f, 1.5f, 2.0f, 3.0f };

static const int ID_GUIED_SELECT_FIRST = 9800;
static const int ID_GUIED_SELECT_LAST  = 9900;

idList<rvGEClipboardItem*> rvGEWorkspace::mClipboard;

rvGEWorkspace::rvGEWorkspace ( rvGEApp* app ) : mApplication ( app )
{
	mWnd				= 0;
	mInterface			= 0;
	mZoom				= ZOOM_100;
	mScrollHorz			= false;
	mScrollVert			= false;
	mModified			= false;
	mNew				= false;
	mDragScroll			= false;
	mSourceControlState = SCS_CHECKEDOUT;
	mFilename			= "guis/Untitled.gui";
	mDragType			= rvGESelectionMgr::HT_NONE;
	mHandCursor			= LoadCursor ( app->GetInstance(), MAKEINTRESOURCE(IDC_GUIED_HAND) );
	mDontAdd			= false;

	mSelections.SetWorkspace ( this );
}

rvGEWorkspace::~rvGEWorkspace ( )
{
	// Make sure all the wrappers get cleaned up
	rvGEWindowWrapper::GetWrapper ( mInterface->GetDesktop ( ) )->EnumChildren ( CleanupEnumProc, NULL );

	DestroyCursor ( mHandCursor );

	delete mInterface;
}

/*
================
rvGEWorkspace::CleanupEnumProc

Window enumeration procedure that deletes all the wrapper classes
================
*/
bool rvGEWorkspace::CleanupEnumProc ( rvGEWindowWrapper* wrapper, void* data )
{
	bool result;

	if ( !wrapper )
	{
		return true;
	}

	result = wrapper->EnumChildren ( CleanupEnumProc, data );

	// Cleanup the window wrapper
	delete wrapper;

	return result;
}

/*
================
rvGEWorkspace::GetZoomScale

Returns the scale of the current zoom level
================
*/
float rvGEWorkspace::GetZoomScale ( void )
{
	return g_ZoomScales [ mZoom ];
}

/*
================
rvGEWorkspace::Attach

Attaches the workspace to the given window.  This is usually done after the
window is created and the file has been loaded.
================
*/
bool rvGEWorkspace::Attach ( HWND wnd )
{
	assert ( wnd );

	mWnd = wnd;

	// Initialize the pixel format for this window
	SetupPixelFormat ( );

	// Jam the workspace pointer into the userdata window long so
	// we can retrieve the workspace from the window later
	SetWindowLong ( mWnd, GWL_USERDATA, (LONG) this );

	UpdateTitle ( );

	return true;
}

/*
================
rvGEWorkspace::Detach

Detaches the workspace from the window it is currently attached to
================
*/
void rvGEWorkspace::Detach ( void )
{
	assert ( mWnd );

	SetWindowLong ( mWnd, GWL_USERDATA, 0 );
	mWnd = NULL;
}

/*
================
rvGEWorkspace::SetupPixelFormat

Setup the pixel format for the opengl context
================
*/
bool rvGEWorkspace::SetupPixelFormat ( void )
{
	HDC	 hDC    = GetDC ( mWnd );
	bool result = true;

	int pixelFormat = ChoosePixelFormat(hDC, &win32.pfd);
	if (pixelFormat > 0)
	{
		if (SetPixelFormat(hDC, pixelFormat, &win32.pfd) == NULL)
		{
			result = false;
		}
	}
	else
	{
		result = false;
	}

	ReleaseDC ( mWnd, hDC );

	return result;
}

/*
================
rvGEWorkspace::RenderGrid

Renders the grid on top of the user interface
================
*/
void rvGEWorkspace::RenderGrid ( void )
{
	float	x;
	float	y;
	float	step;
	idVec4&	color = mApplication->GetOptions().GetGridColor ( );

	// See if the grid is off before rendering it
	if ( !mApplication->GetOptions().GetGridVisible ( ))
	{
		return;
	}

	qglEnable(GL_BLEND);
	qglBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

	qglColor4f ( color[0], color[1], color[2], 0.5f );

	qglBegin ( GL_LINES );
	step = mApplication->GetOptions().GetGridWidth ( ) * g_ZoomScales[mZoom];
	for ( x = mRect.x + mRect.w; x >= mRect.x ; x -= step )
	{
		qglVertex2f ( x, mRect.y );
		qglVertex2f ( x, mRect.y + mRect.h );
	}
	step = mApplication->GetOptions().GetGridHeight ( ) * g_ZoomScales[mZoom];
	for ( y = mRect.y + mRect.h; y >= mRect.y ; y -= step )
	{
		qglVertex2f ( mRect.x, y );
		qglVertex2f ( mRect.x + mRect.w, y );
	}
	qglEnd ( );

	qglDisable(GL_BLEND);
	qglColor3f ( color[0], color[1], color[2] );

	qglBegin ( GL_LINES );
	step = mApplication->GetOptions().GetGridWidth ( ) * g_ZoomScales[mZoom];
	for ( x = mRect.x + mRect.w; x >= mRect.x ; x -= step * 4 )
	{
		qglVertex2f ( x, mRect.y );
		qglVertex2f ( x, mRect.y + mRect.h );
	}
	step = mApplication->GetOptions().GetGridHeight ( ) * g_ZoomScales[mZoom];
	for ( y = mRect.y + mRect.h; y >= mRect.y ; y -= step * 4 )
	{
		qglVertex2f ( mRect.x, y );
		qglVertex2f ( mRect.x + mRect.w, y );
	}
	qglEnd ( );
}

/*
================
rvGEWorkspace::Render

Renders the workspace to the given DC
================
*/
void rvGEWorkspace::Render ( HDC hdc )
{
	int		front;
	int		back;
	float	scale;

	scale = g_ZoomScales[mZoom];

	// Switch GL contexts to our dc
	if (!qwglMakeCurrent( hdc, win32.hGLRC ))
	{
		common->Printf("ERROR: wglMakeCurrent failed.. Error:%i\n", qglGetError());
		common->Printf("Please restart Q3Radiant if the Map view is not working\n");
		return;
	}

	// Prepare the view and clear it
	GL_State( GLS_DEFAULT );
	qglViewport(0, 0, mWindowWidth, mWindowHeight );
	qglScissor(0, 0, mWindowWidth, mWindowHeight );
	qglClearColor ( 0.75f, 0.75f, 0.75f, 0 );

	qglDisable(GL_DEPTH_TEST);
	qglDisable(GL_CULL_FACE);
	qglClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	// Render the workspace below
	qglMatrixMode(GL_PROJECTION);
	qglLoadIdentity();
	qglOrtho(0,mWindowWidth, mWindowHeight, 0, -1, 1);
	qglMatrixMode(GL_MODELVIEW);
	qglLoadIdentity();

	qglColor3f ( mApplication->GetOptions().GetWorkspaceColor()[0], mApplication->GetOptions().GetWorkspaceColor()[1], mApplication->GetOptions().GetWorkspaceColor()[2] );
	qglBegin ( GL_QUADS );
	qglVertex2f ( mRect.x, mRect.y );
	qglVertex2f ( mRect.x + mRect.w, mRect.y );
	qglVertex2f ( mRect.x + mRect.w, mRect.y + mRect.h );
	qglVertex2f ( mRect.x, mRect.y + mRect.h );
	qglEnd ( );

	// Prepare the renderSystem view to draw the GUI in
	viewDef_t viewDef;
	memset ( &viewDef, 0, sizeof(viewDef) );
	tr.viewDef = &viewDef;
	tr.viewDef->renderView.x = mRect.x;
	tr.viewDef->renderView.y = mWindowHeight - mRect.y - mRect.h;
	tr.viewDef->renderView.width = mRect.w;
	tr.viewDef->renderView.height = mRect.h;
	tr.viewDef->scissor.x1 = 0;
	tr.viewDef->scissor.y1 = 0;
	tr.viewDef->scissor.x2 = mRect.w;
	tr.viewDef->scissor.y2 = mRect.h;
	tr.viewDef->isEditor = true;
	renderSystem->BeginFrame(mWindowWidth, mWindowHeight );

	// Draw the gui
	mInterface->Redraw ( 0 ); // eventLoop->Milliseconds() );

	// We are done using the renderSystem now
	renderSystem->EndFrame( &front, &back );

	if ( mApplication->GetActiveWorkspace ( ) == this )
	{
		mApplication->GetStatusBar().SetTriangles ( backEnd.pc.c_drawIndexes/3 );
	}

	// Prepare the viewport for drawing selections, etc.
	GL_State( GLS_DEFAULT );
	qglDisable( GL_TEXTURE_CUBE_MAP_EXT );
//	qglDisable(GL_BLEND);
	qglDisable(GL_CULL_FACE);

	qglViewport(0, 0, mWindowWidth, mWindowHeight );
	qglScissor(0, 0, mWindowWidth, mWindowHeight );
	qglMatrixMode(GL_PROJECTION);
	qglLoadIdentity();
	qglOrtho(0,mWindowWidth, mWindowHeight, 0, -1, 1);
	qglMatrixMode(GL_MODELVIEW);
	qglLoadIdentity();

	RenderGrid ( );

	mSelections.Render ( );

	qglFinish ( );
	qwglSwapBuffers(hdc);

	qglEnable( GL_TEXTURE_CUBE_MAP_EXT );
	qglEnable( GL_CULL_FACE);
}

/*
================
rvGEWorkspace::UpdateTitle

Updates the window title with the name of the file and the zoom level and weither its open or not
================
*/
void rvGEWorkspace::UpdateTitle ( void )
{
	// Set the window title based on the current filename
	SetWindowText ( mWnd, va("%s%s (%d%%)", idStr(mFilename).StripPath ( ).c_str( ), mModified?"*":"", (int) (g_ZoomScales[mZoom] * 100)) );

	gApp.GetStatusBar().SetZoom ( (int)(g_ZoomScales[mZoom] * 100.0f) );
}

/*
================
rvGEWorkspace::UpdateRectangle

Updates the rectangle (not counting scrolling)
================
*/
void rvGEWorkspace::UpdateRectangle ( bool useScroll )
{
	RECT	rcClient;
	float	x;
	float	y;
	float	scale;

	scale = g_ZoomScales[mZoom];

	// Grab the current client rectangle of the window and cache off the width and height
	GetClientRect ( mWnd, &rcClient );
	mWindowWidth = rcClient.right - rcClient.left;
	mWindowHeight = rcClient.bottom - rcClient.top;

	// The workspace is always centered in the window
	x = mRect.x = mWindowWidth / 2 - (SCREEN_WIDTH * scale) / 2;
	y = mRect.y = mWindowHeight / 2 - (SCREEN_HEIGHT * scale) / 2;
	mRect.w = (SCREEN_WIDTH * scale);
	mRect.h = (SCREEN_HEIGHT * scale);

	// When using the scroll position offset the rectangle based on the scrollbar positions
	if ( useScroll )
	{
		// Adjust the start of the rectangle for the scroll positiond
		mRect.y -= (float)GetScrollPos ( mWnd, SB_VERT ) / 1000.0f;
		mRect.x -= (float)GetScrollPos ( mWnd, SB_HORZ ) / 1000.0f;
	}
}

/*
================
rvGEWorkspace::Scroll

Adjusts the given scrollbar by the given offset
================
*/
void rvGEWorkspace::Scroll ( int scrollbar, int offset )
{
	SCROLLINFO si;

	if ( scrollbar == SB_HORZ && !mScrollHorz )
	{
		return;
	}
	else if ( scrollbar == SB_VERT && !mScrollVert )
	{
		return;
	}

	// Get all the vertial scroll bar information
	si.cbSize = sizeof (si);
	si.fMask  = SIF_ALL;

	// Save the position for comparison later on
	GetScrollInfo ( mWnd, scrollbar, &si);

	si.nPos += (1000 * offset);
	if ( si.nPos < si.nMin ) si.nPos = si.nMin;
	if ( si.nPos > si.nMax ) si.nPos = si.nMax;

	si.fMask = SIF_POS;
	SetScrollInfo (mWnd, scrollbar, &si, TRUE);
	GetScrollInfo (mWnd, scrollbar, &si);

	UpdateRectangle ( );
}

int rvGEWorkspace::HandleScroll ( int scrollbar, WPARAM wParam, LPARAM lParam )
{
	SCROLLINFO si;

	// Get all the vertial scroll bar information
	si.cbSize = sizeof (si);
	si.fMask  = SIF_ALL;

	// Save the position for comparison later on
	GetScrollInfo ( mWnd, scrollbar, &si);

	switch (LOWORD (wParam))
	{
		// user clicked left or up arrow
		case SB_LINELEFT:
			si.nPos -= 1000;
			break;

		// user clicked right or down arrow
		case SB_LINERIGHT:
			si.nPos += 1000;
			break;

		// user clicked shaft left of the scroll box
		case SB_PAGELEFT:
			si.nPos -= si.nPage;
			break;

		// user clicked shaft right of the scroll box
		case SB_PAGERIGHT:
			si.nPos += si.nPage;
			break;

		// user dragged the scroll box
		case SB_THUMBTRACK:
			si.nPos = si.nTrackPos;
			break;

		default :
			break;
	}

	// Set the position and then retrieve it.  Due to adjustments
	//   by Windows it may not be the same as the value set.
	si.fMask = SIF_POS;
	SetScrollInfo (mWnd, scrollbar, &si, TRUE);
	GetScrollInfo (mWnd, scrollbar, &si);

	UpdateRectangle ( );

	return 0;
}

/*
================
rvGEWorkspace::UpdateScrollbars

Updates the states and the ranges of the scrollbars as well as the rectangle
================
*/
void rvGEWorkspace::UpdateScrollbars ( void )
{
	SCROLLINFO info;

	// First update the rectangle without applying scroll positions so
	// we know the real sizes and coordinates
	UpdateRectangle ( false );

	// Setup the veritcal scrollbar
	info.cbSize = sizeof(info);
	info.fMask = SIF_RANGE|SIF_PAGE;
	info.nMax = (mRect.h - mWindowHeight + 10) * 1000 / 2;
	info.nMin = -info.nMax;
	info.nPage = (int)((float)info.nMax * (float)((float)mWindowHeight / mRect.h));
	info.nMax += info.nPage;

	// If there is something to scroll then turn on the vertical scroll bar
	// if its not on and update the scroll info.
	if ( info.nMax > 0 )
	{
		if ( !mScrollVert )
		{
			mScrollVert = true;
			ShowScrollBar ( mWnd, SB_VERT, mScrollVert );
		}
		SetScrollInfo ( mWnd, SB_VERT, &info, TRUE );
	}
	// Nothing to scroll, turn off the scrollbar if its on.
	else if ( mScrollVert )
	{
		mScrollVert = false;
		SetScrollPos ( mWnd, SB_VERT, 0, FALSE );
		ShowScrollBar ( mWnd, SB_VERT, mScrollVert );
	}

	// Setup the horizontal scrollbar
	info.nMax = (mRect.w - mWindowWidth + 10) * 1000 / 2;
	info.nMin = -info.nMax;
	info.nPage = (int)((float)info.nMax * (float)((float)mWindowWidth / mRect.w));
	info.nMax += info.nPage;

	// If there is something to scroll then turn on the vertical scroll bar
	// if its not on and update the scroll info.
	if ( info.nMax > 0 )
	{
		if ( !mScrollHorz )
		{
			mScrollHorz = true;
			ShowScrollBar ( mWnd, SB_HORZ, mScrollHorz );
		}

		SetScrollInfo ( mWnd, SB_HORZ, &info, TRUE );
	}
	// Nothing to scroll, turn off the scrollbar if its on.
	else if ( mScrollHorz )
	{
		mScrollHorz = false;
		SetScrollPos ( mWnd, SB_HORZ, 0, FALSE );
		ShowScrollBar ( mWnd, SB_HORZ, mScrollHorz );
	}

	// Need to update the rectangle again to take the scrollbar changes into account
	UpdateRectangle ( true );
}

/*
================
rvGEWorkspace::UpdateCursor

Called to update the cursor when the mouse is within the workspace window
================
*/
void rvGEWorkspace::UpdateCursor ( rvGESelectionMgr::EHitTest type )
{
	switch ( type )
	{
		case rvGESelectionMgr::HT_SELECT:
			SetCursor ( LoadCursor ( NULL, MAKEINTRESOURCE(IDC_ARROW) ) );
			break;

		case rvGESelectionMgr::HT_MOVE:
			SetCursor ( LoadCursor ( NULL, MAKEINTRESOURCE(IDC_SIZEALL) ) );
			break;

		case rvGESelectionMgr::HT_SIZE_LEFT:
		case rvGESelectionMgr::HT_SIZE_RIGHT:
			SetCursor ( LoadCursor ( NULL, MAKEINTRESOURCE(IDC_SIZEWE ) ) );
			break;

		case rvGESelectionMgr::HT_SIZE_TOP:
		case rvGESelectionMgr::HT_SIZE_BOTTOM:
			SetCursor ( LoadCursor ( NULL, MAKEINTRESOURCE(IDC_SIZENS ) ) );
			break;

		case rvGESelectionMgr::HT_SIZE_TOPRIGHT:
		case rvGESelectionMgr::HT_SIZE_BOTTOMLEFT:
			SetCursor ( LoadCursor ( NULL, MAKEINTRESOURCE(IDC_SIZENESW ) ) );
			break;

		case rvGESelectionMgr::HT_SIZE_BOTTOMRIGHT:
		case rvGESelectionMgr::HT_SIZE_TOPLEFT:
			SetCursor ( LoadCursor ( NULL, MAKEINTRESOURCE(IDC_SIZENWSE ) ) );
			break;
	}
}

void rvGEWorkspace::UpdateCursor ( float x, float y )
{
	idVec2						point;
	rvGESelectionMgr::EHitTest	type;

	// First convert the worspace coord to a window coord
	point = WorkspaceToWindow ( idVec2( x, y ) );

	// See if it hits anything
	type = mSelections.HitTest ( point.x, point.y );

	// If it hits something then use it to update the cursor
	if ( rvGESelectionMgr::HT_NONE != type )
	{
		UpdateCursor ( type );
	}
	else
	{
		SetCursor ( LoadCursor ( NULL, MAKEINTRESOURCE(IDC_ARROW ) ) );
	}
}

void rvGEWorkspace::UpdateCursor ( void )
{
	if ( mDragType == rvGESelectionMgr::HT_NONE )
	{
		POINT	point;
		idVec2	cursor;

		GetCursorPos ( &point );
		cursor.Set ( point.x, point.y );
		WindowToWorkspace ( cursor );

		UpdateCursor ( cursor.x, cursor.y );
	}
	else
	{
		UpdateCursor ( mDragType );
	}
}

/*
================
rvGEWorkspace::HandleMessage

Handles window messages to the workspace
================
*/
void rvGEWorkspace::HandleMessage ( UINT msg, WPARAM wParam, LPARAM lParam )
{
	switch ( msg )
	{
		case WM_CLOSE:
		{

			if ( IsModified ( ) )
			{
				if ( IDYES == gApp.MessageBox ( va("Save changes to the document \"%s\" before closing?", GetFilename() ), MB_YESNO|MB_ICONQUESTION ) )
				{
					SendMessage ( mApplication->GetMDIFrame(), WM_COMMAND, MAKELONG(ID_GUIED_FILE_SAVE,0), 0 );
				}
			}


			GetApplication ( )->GetNavigator().SetWorkspace(NULL);
			GetApplication ( )->GetTransformer().SetWorkspace(NULL);
			GetApplication ( )->GetProperties().SetWorkspace(NULL);
			break;
		}

		case WM_CAPTURECHANGED:
			if ( (HWND)lParam != mWnd )
			{
				mDragScroll = false;
				mDragType	= rvGESelectionMgr::HT_NONE;
			}
			break;

		case WM_SETCURSOR:
		{
			POINT point;
			idVec2 cursor;
			GetCursorPos ( &point );
			cursor.Set ( point.x, point.y );
			WindowToWorkspace ( cursor );
			if ( mDragType == rvGESelectionMgr::HT_NONE )
			{
				UpdateCursor ( cursor.x, cursor.y );
			}
			else
			{
				UpdateCursor ( mDragType );
			}
			break;
		}

		case WM_MOUSEWHEEL:
			if ( (short)HIWORD(wParam) > 0 )
			{
				ZoomIn ( );
			}
			else if ( (short)HIWORD(wParam) < 0 )
			{
				ZoomOut ( );
			}
			break;

		case WM_MOUSEMOVE:
			HandleMouseMove ( wParam, lParam );
			break;

		case WM_MBUTTONDOWN:
			HandleMButtonDown ( wParam, lParam );
			break;

		case WM_MBUTTONUP:
			HandleMButtonUp ( wParam, lParam );
			break;

		case WM_LBUTTONDOWN:
			HandleLButtonDown ( wParam, lParam );
			break;

		case WM_LBUTTONUP:
			HandleLButtonUp ( wParam, lParam );
			break;

		case WM_LBUTTONDBLCLK:
			HandleLButtonDblClk ( wParam, lParam );
			break;

		case WM_INITMENUPOPUP:
			SendMessage ( mApplication->GetMDIFrame(), msg, wParam, lParam );
			break;

		case WM_COMMAND:
			HandleCommand ( wParam, lParam );
			break;

		case WM_RBUTTONDOWN:
			HandleRButtonDown ( wParam, lParam );
			break;

		case WM_SIZE:
			UpdateScrollbars();
			break;

		case WM_VSCROLL:
			HandleScroll ( SB_VERT, wParam, lParam );
			break;

		case WM_HSCROLL:
			HandleScroll ( SB_HORZ, wParam, lParam );
			break;

		case WM_KEYDOWN:
			HandleKeyDown ( wParam, lParam );
			break;
	}
}
/*
================
rvGEWorkspace::HandleCommand

Handles command messages destined for the workspace window.  This is for
special workspace commands, any unhandled commands are forwarded to the main window
================
*/
int	rvGEWorkspace::HandleCommand ( WPARAM wParam, LPARAM lParam )
{
	// Select command
	if ( LOWORD(wParam) >= ID_GUIED_SELECT_FIRST && LOWORD(wParam) <= ID_GUIED_SELECT_LAST )
	{
		idWindow*			window  = mSelectMenu[LOWORD(wParam)-ID_GUIED_SELECT_FIRST];
		rvGEWindowWrapper*	wrapper = rvGEWindowWrapper::GetWrapper ( window );

		// Handle multi select as well
		if ( GetAsyncKeyState ( VK_SHIFT ) & 0x8000 )
		{
			if ( wrapper->IsSelected ( ) )
			{
				mSelections.Remove ( window );
			}
			else
			{
				mSelections.Add ( window );
			}
		}
		else
		{
			mSelections.Set ( window );
		}
	}

	return SendMessage ( mApplication->GetMDIFrame(), WM_COMMAND, wParam, lParam );
}

/*
================
rvGEWorkspace::HandleMButtonDown

Handles the middle mouse down message in the workspace
================
*/
int	rvGEWorkspace::HandleMButtonDown ( WPARAM wParam, LPARAM lParam )
{
	if ( mDragType != rvGESelectionMgr::HT_NONE )
	{
		return 0;
	}

	mDragPoint.Set ( LOWORD(lParam), HIWORD(lParam) );
	mDragScroll = true;
	SetCursor ( mHandCursor );
	SetCapture ( mWnd );

	WindowToWorkspace ( mDragPoint );

	return 0;
}

/*
================
rvGEWorkspace::HandleMButtonUp

Handles the middle mouse up message in the workspace
================
*/
int	rvGEWorkspace::HandleMButtonUp ( WPARAM wParam, LPARAM lParam )
{
	if ( mDragScroll )
	{
		mDragScroll = false;
		ReleaseCapture ( );
	}

	return 0;
}

/*
================
rvGEWorkspace::HandleRButtonDown

Handles the left mouse down message in the workspace
================
*/
int	rvGEWorkspace::HandleRButtonDown ( WPARAM wParam, LPARAM lParam )
{
	POINT point = { LOWORD(lParam), HIWORD(lParam) };
	HMENU menu;

	// Add the select menu
	mSelectMenu.Clear ( );

	// Cache where the menu is being brought up so we can
	// figure out which windows are under the point
	mSelectMenuPos[0] = point.x;
	mSelectMenuPos[1] = point.y;
	WindowToWorkspace ( mSelectMenuPos );

	// Build a list of all the windows under the menu point
	rvGEWindowWrapper::GetWrapper ( mInterface->GetDesktop() )->EnumChildren ( BuildSelectMenuEnumProc, this );

	// Add the desktop window always
	mSelectMenu.Append ( mInterface->GetDesktop() );

	//
	menu = GetSubMenu ( LoadMenu ( mApplication->GetInstance(), MAKEINTRESOURCE(IDR_GUIED_ITEM_POPUP) ), 0 );

	HMENU popup = CreatePopupMenu ( );

	int i;
	for ( i = 0; i < mSelectMenu.Num(); i ++ )
	{
		rvGEWindowWrapper* wrapper = rvGEWindowWrapper::GetWrapper ( mSelectMenu[i] );
		AppendMenu ( popup, MF_STRING|MF_ENABLED|(wrapper->IsSelected()?MF_CHECKED:0), ID_GUIED_SELECT_FIRST + i, mSelectMenu[i]->GetName() );
	}

	InsertMenu ( menu, 1, MF_POPUP|MF_BYPOSITION, (LONG) popup, "Select" );

	// Bring up the popup menu
	ClientToScreen ( mWnd, &point );
	TrackPopupMenu ( menu, TPM_RIGHTBUTTON|TPM_LEFTALIGN, point.x, point.y, 0, mWnd, NULL );

	DestroyMenu ( popup );
	DestroyMenu ( menu );

	return 0;
}

/*
================
rvGEWorkspace::HandleLButtonDown

Handles the left mouse down message in the workspace
================
*/
int	rvGEWorkspace::HandleLButtonDown ( WPARAM wParam, LPARAM lParam )
{
	if ( mDragScroll )
	{
		return 0;
	}

	idVec2 point ( LOWORD(lParam), HIWORD(lParam) );
	WindowToWorkspace ( point );

	// Make sure whatever modifications get generated cant be merged into whats already there
	mModifiers.BlockNextMerge ( );

	mDragPoint.Set ( LOWORD(lParam), HIWORD(lParam) );
	mDragTime = Sys_Milliseconds ( );
	mDragX    = true;
	mDragY    = true;

	// If we have selections then start a drag
	if ( mSelections.Num ( ) )
	{
		mDragType = mSelections.HitTest ( mDragPoint.x, mDragPoint.y );
	}

	rvGEWindowWrapper* wrapper;
	wrapper = rvGEWindowWrapper::GetWrapper ( mInterface->GetDesktop ( ) );

	idWindow* window = wrapper->WindowFromPoint ( point.x, point.y );

	// dissallow selection of the desktop.
	if ( gApp.GetOptions().GetIgnoreDesktopSelect() && window == mInterface->GetDesktop ( ) )
	{
		window = NULL;
	}

	if ( mDragType == rvGESelectionMgr::HT_MOVE || mDragType == rvGESelectionMgr::HT_NONE )
	{
		if ( window  )
		{
			bool selected;

			selected = mSelections.IsSelected ( window );

			if ( GetAsyncKeyState ( VK_SHIFT ) & 0x8000 )
			{
				if ( !selected )
				{
					mSelections.Add ( window );
					mDragType = rvGESelectionMgr::HT_MOVE;
				}
				else
				{
					mSelections.Remove ( window );
				}
			}
			else if ( !selected && mDragType == rvGESelectionMgr::HT_NONE  )
			{
				mSelections.Set ( window );
				mDragType = rvGESelectionMgr::HT_MOVE;
			}
		}
		else
		{
			mSelections.Clear ( );
		}
	}

	if ( mSelections.IsExpression ( ) )
	{
		mDragType = rvGESelectionMgr::HT_SELECT;
	}
	// Windows capture
	else if ( mDragType != rvGESelectionMgr::HT_NONE )
	{
		SetCapture ( mWnd );
	}

	WindowToWorkspace ( mDragPoint );

	return 0;
}

/*
================
rvGEWorkspace::HandleLButtonUp

Handles the left mouse up message in the workspace
================
*/
int	rvGEWorkspace::HandleLButtonUp ( WPARAM wParam, LPARAM lParam )
{
	if ( mDragType != rvGESelectionMgr::HT_NONE )
	{
		ReleaseCapture ( );
		mModifiers.BlockNextMerge ( );

		// Update the transformer
		mApplication->GetTransformer().Update ( );
	}

	// No more dragging
	mDragType = rvGESelectionMgr::HT_NONE;

	return 0;
}

/*
================
rvGEWorkspace::HandleLButtonDblClk

Handle a double click by opening properties
================
*/
int	rvGEWorkspace::HandleLButtonDblClk ( WPARAM wParam, LPARAM lParam )
{
	EditSelectedProperties ( );
	return 0;
}

/*
================
rvGEWorkspace::HandleMouseMove

Handles the moving of the mouse for dragging and cursor updating
================
*/
int	rvGEWorkspace::HandleMouseMove ( WPARAM wParam, LPARAM lParam )
{
	idVec2	cursor;

	cursor.Set ( (short)LOWORD(lParam), (short)HIWORD(lParam) );

	// Convert the window point to the workspace before updating the
	// cursor with the position
	WindowToWorkspace ( cursor );

	// Scrolling the window around
	if ( mDragScroll )
	{
		Scroll ( SB_HORZ, mDragPoint.x - cursor.x );
		Scroll ( SB_VERT, mDragPoint.y - cursor.y );

		SetCursor ( mHandCursor );

		mDragPoint = cursor;

		return 0;
	}

	// If not dragging then just update the cursor and return
	if ( mDragType == rvGESelectionMgr::HT_NONE )
	{
		UpdateCursor ( cursor.x, cursor.y );
		return 0;
	}

	// Dont allow a drag move start until the button has been down for 100 ms or so
	if ( mDragType == rvGESelectionMgr::HT_MOVE && Sys_Milliseconds() - mDragTime <= 50 )
	{
		return 0;
	}

	// Handle grid snapping
	if ( gApp.GetOptions().GetGridSnap ( ) )
	{
		cursor.x = (float)(((int)cursor.x + gApp.GetOptions().GetGridWidth()/2) / gApp.GetOptions().GetGridWidth() * gApp.GetOptions().GetGridWidth());
		cursor.y = (float)(((int)cursor.y + gApp.GetOptions().GetGridWidth()/2) / gApp.GetOptions().GetGridWidth() * gApp.GetOptions().GetGridWidth());
	}

	// If the cursor hasnt moved then there is nothing to update with the drag
	if ( (int) cursor.x == (int) mDragPoint.x && (int) cursor.y == (int) mDragPoint.y )
	{
		return 0;
	}

	switch ( mDragType )
	{
		case rvGESelectionMgr::HT_MOVE:
			AddModifierMove ( "Move", cursor.x - mDragPoint.x, cursor.y - mDragPoint.y, mApplication->GetOptions().GetGridSnap ( ) );
			break;

		case rvGESelectionMgr::HT_SIZE_BOTTOM:
			AddModifierSize ( "Size", 0, 0, 0, cursor.y - mDragPoint.y, mApplication->GetOptions().GetGridSnap ( ) );
			break;

		case rvGESelectionMgr::HT_SIZE_TOP:
			AddModifierSize ( "Size", 0, cursor.y - mDragPoint.y, 0, 0, mApplication->GetOptions().GetGridSnap ( ) );
			break;

		case rvGESelectionMgr::HT_SIZE_RIGHT:
			AddModifierSize ( "Size", 0, 0, cursor.x - mDragPoint.x, 0, mApplication->GetOptions().GetGridSnap ( ) );
			break;

		case rvGESelectionMgr::HT_SIZE_LEFT:
			AddModifierSize ( "Size", cursor.x - mDragPoint.x, 0, 0, 0, mApplication->GetOptions().GetGridSnap ( ) );
			break;

		case rvGESelectionMgr::HT_SIZE_TOPLEFT:
			AddModifierSize ( "Size", cursor.x - mDragPoint.x, cursor.y - mDragPoint.y, 0, 0, mApplication->GetOptions().GetGridSnap ( ) );
			break;

		case rvGESelectionMgr::HT_SIZE_TOPRIGHT:
			AddModifierSize ( "Size", 0, cursor.y - mDragPoint.y, cursor.x - mDragPoint.x, 0, mApplication->GetOptions().GetGridSnap ( ) );
			break;

		case rvGESelectionMgr::HT_SIZE_BOTTOMLEFT:
			AddModifierSize ( "Size", cursor.x - mDragPoint.x, 0, 0, cursor.y - mDragPoint.y, mApplication->GetOptions().GetGridSnap ( ) );
			break;

		case rvGESelectionMgr::HT_SIZE_BOTTOMRIGHT:
			AddModifierSize ( "Size", 0, 0, cursor.x - mDragPoint.x, cursor.y - mDragPoint.y, mApplication->GetOptions().GetGridSnap ( ) );
			break;
	}

	UpdateCursor ( mDragType );

	// If the x coordinate has changed then update it
	if ( (int)cursor.x != (int)mDragPoint.x && mDragX )
	{
		mDragPoint.x = cursor.x;
	}

	// If the y coordinate has changed then update it
	if ( (int)cursor.y != (int)mDragPoint.y && mDragY )
	{
		mDragPoint.y = cursor.y;
	}

	return 0;
}

/*
================
rvGEWorkspace::HandleKeyDown

Handles the the pressing of a key
================
*/
int	rvGEWorkspace::HandleKeyDown ( WPARAM wParam, LPARAM lParam )
{
	bool shift = (GetAsyncKeyState ( VK_SHIFT ) & 0x8000) ? true : false;

	switch ( wParam )
	{
		case VK_LEFT:
			if ( shift )
			{
				AddModifierSizeNudge ( -1, 0, false );
			}
			else
			{
				AddModifierMoveNudge ( -1, 0, false );
			}
			break;

		case VK_RIGHT:
			if ( shift )
			{
				AddModifierSizeNudge ( 1, 0, false );
			}
			else
			{
				AddModifierMoveNudge ( 1, 0, false );
			}
			break;

		case VK_DOWN:
			if ( shift )
			{
				AddModifierSizeNudge ( 0, 1, false );
			}
			else
			{
				AddModifierMoveNudge ( 0, 1, false );
			}
			break;

		case VK_UP:
			if ( shift )
			{
				AddModifierSizeNudge ( 0, -1, false );
			}
			else
			{
				AddModifierMoveNudge ( 0, -1, false );
			}
			break;

		case VK_ESCAPE:
			mSelections.Clear ( );
			mApplication->GetNavigator().Update ( );
			break;
	}

	return 0;
}

/*
================
rvGEWorkspace::WindowToWorkspace

Converts the given coordinates in windows space to the workspace's coordinates.
================
*/
idVec2& rvGEWorkspace::WindowToWorkspace ( idVec2& point )
{
	point.x = (point.x - mRect.x) / mRect.w * SCREEN_WIDTH;
	point.y = (point.y - mRect.y) / mRect.h * SCREEN_HEIGHT;

	return point;
}

idRectangle& rvGEWorkspace::WindowToWorkspace ( idRectangle& rect )
{
	rect.x = (rect.x - mRect.x) / mRect.w * SCREEN_WIDTH;
	rect.y = (rect.y - mRect.y) / mRect.h * SCREEN_HEIGHT;
	rect.w = rect.w / mRect.w * SCREEN_WIDTH;
	rect.h = rect.h / mRect.h * SCREEN_HEIGHT;

	return rect;
}

/*
================
rvGEWorkspace::WindowToWorkspace

Converts the given workspace coordinates to the windows coordinates.
================
*/
idVec2& rvGEWorkspace::WorkspaceToWindow ( idVec2& point )
{
	point.x = mRect.x + (point.x / SCREEN_WIDTH * mRect.w);
	point.y = mRect.y + (point.y / SCREEN_HEIGHT * mRect.h);

	return point;
}

idRectangle& rvGEWorkspace::WorkspaceToWindow ( idRectangle& rect )
{
	rect.x = mRect.x + (rect.x / SCREEN_WIDTH * mRect.w);
	rect.y = mRect.y + (rect.y / SCREEN_HEIGHT * mRect.h);
	rect.w = rect.w / SCREEN_WIDTH * mRect.w;
	rect.h = rect.h / SCREEN_HEIGHT * mRect.h;

	return rect;
}

/*
================
rvGEWorkspace::ZoomIn

Zooms the workspace in by one zoom level
================
*/
rvGEWorkspace::EZoomLevel rvGEWorkspace::ZoomIn ( void )
{
	mZoom = mZoom + 1;
	if ( mZoom >= ZOOM_MAX )
	{
		mZoom = ZOOM_MAX - 1;
	}

	UpdateScrollbars ( );
	UpdateTitle ( );

	InvalidateRect ( mWnd, NULL, FALSE );

	return (EZoomLevel)mZoom;
}

/*
================
rvGEWorkspace::ZoomOut

Zooms the workspace out by one level
================
*/
rvGEWorkspace::EZoomLevel rvGEWorkspace::ZoomOut ( void )
{
	mZoom--;
	if ( mZoom <= ZOOM_MIN )
	{
		mZoom = ZOOM_MIN + 1;
	}

	UpdateScrollbars ( );
	UpdateTitle ( );

	InvalidateRect ( mWnd, NULL, FALSE );

	return (EZoomLevel)mZoom;
}

/*
================
rvGEWorkspace::CreateModifier

Creates a new modifier of the given type for the given window.  This function is called
specifically from the add modifiers function with the variable args list forwarded.
================
*/
rvGEModifier* rvGEWorkspace::CreateModifier ( EModifierType type, idWindow* window, va_list args )
{
	rvGEModifier* mod;

	switch ( type )
	{
		case MOD_DELETE:
			mod = new rvGEDeleteModifier ( "Delete", window );
			break;

		case MOD_HIDE:
			mod = new rvGEHideModifier ( "Hide", window, true );
			break;

		case MOD_UNHIDE:
			mod = new rvGEHideModifier ( "Hide", window, false );
			break;

		case MOD_SEND_BACKWARD:
			mod = new rvGEZOrderModifier ( "Send Backward", window, rvGEZOrderModifier::ZO_BACKWARD );
			break;

		case MOD_SEND_BACK:
			mod = new rvGEZOrderModifier ( "Send to Back", window, rvGEZOrderModifier::ZO_BACK );
			break;

		case MOD_BRING_FORWARD:
			mod = new rvGEZOrderModifier ( "Bring Forward", window, rvGEZOrderModifier::ZO_FORWARD );
			break;

		case MOD_BRING_FRONT:
			mod = new rvGEZOrderModifier ( "Bring to Front", window, rvGEZOrderModifier::ZO_FRONT );
			break;

		default:
			mod = NULL;
			break;
	}

	return mod;
}

/*
================
rvGEWorkspace::AddModifiers

Add the specific modifier for the given window
================
*/
void rvGEWorkspace::AddModifiers ( idWindow* window, EModifierType type, ... )
{
	va_list args;

	va_start(args,type) ;
	mModifiers.Append ( CreateModifier ( type, window, args ) );
	va_end (args) ;

	SetModified ( true );
}

void rvGEWorkspace::AddModifiers ( EModifierType type, ... )
{
	va_list args;

	// Nothing to move if there is no selection
	if ( !mSelections.Num ( ) )
	{
		return;
	}
	// More than one selection requires a modifier group
	else if ( mSelections.Num ( ) > 1 )
	{
		rvGEModifierGroup*	group = new rvGEModifierGroup;
		int					i;

		for ( i = 0; i < mSelections.Num(); i ++ )
		{
			va_start(args,type);
			group->Append ( CreateModifier ( type, mSelections[i], args ) );
			va_end (args);
		}

		mModifiers.Append ( group );
	}
	// Single modifier
	else
	{
		va_start(args,type) ;
		mModifiers.Append ( CreateModifier ( type, mSelections[0], args ) );
		va_end (args) ;
	}

	SetModified ( true );
}

bool rvGEWorkspace::BuildSelectMenuEnumProc ( rvGEWindowWrapper* wrapper, void* data )
{
	rvGEWorkspace*	workspace;

	workspace = (rvGEWorkspace*) data;
	assert ( workspace );

	if ( !wrapper )
	{
		return true;
	}

	wrapper->EnumChildren ( BuildSelectMenuEnumProc, data );

	if ( wrapper->IsDeleted ( ) || wrapper->IsHidden ( ) )
	{
		return true;
	}

	if ( wrapper->GetScreenRect ( ).Contains ( workspace->mSelectMenuPos[0], workspace->mSelectMenuPos[1] ) )
	{
		workspace->mSelectMenu.Append ( wrapper->GetWindow ( ));
	}

	return true;
}

bool rvGEWorkspace::ShowAllEnumProc ( rvGEWindowWrapper* wrapper, void* data )
{
	rvGEModifierGroup* group = (rvGEModifierGroup*) data;

	wrapper->EnumChildren ( ShowAllEnumProc, data );

	if ( wrapper->IsHidden ( ) )
	{
		group->Append ( new rvGEHideModifier ( "Show Hidden", wrapper->GetWindow ( ), false ) );
	}

	return true;
}

void rvGEWorkspace::AddModifierShowAll ( void )
{
	rvGEModifierGroup* group = new rvGEModifierGroup;

	rvGEWindowWrapper::GetWrapper( mInterface->GetDesktop ( ) )->EnumChildren ( ShowAllEnumProc, group );

	if ( !group->GetCount ( ) )
	{
		delete group;
	}
	else
	{
		mModifiers.Append ( group );
	}

	mApplication->GetNavigator().Refresh ( );
}

void rvGEWorkspace::DeleteSelected ( void )
{
	AddModifiers ( MOD_DELETE );
	mSelections.Clear ( );
	mApplication->GetNavigator().Update ( );
}

/*
================
rvGEWorkspace::NewWindow

Create a new window
================
*/
idWindow* rvGEWorkspace::NewWindow ( idDict* state, rvGEWindowWrapper::EWindowType type )
{
	idWindow*			window = new idWindow ( mInterface->GetDesktop()->GetDC(), mInterface );
	rvGEWindowWrapper*	wrapper;
	int					count;
	idStr				baseName;

	switch ( type )
	{
		case rvGEWindowWrapper::WT_NORMAL:
			window = new idWindow ( mInterface->GetDesktop()->GetDC(), mInterface );
			break;

		case rvGEWindowWrapper::WT_BIND:
			window = new idBindWindow ( mInterface->GetDesktop()->GetDC(), mInterface );
			break;

		case rvGEWindowWrapper::WT_RENDER:
			window = new idRenderWindow ( mInterface->GetDesktop()->GetDC(), mInterface );
			break;

		case rvGEWindowWrapper::WT_CHOICE:
			window = new idChoiceWindow ( mInterface->GetDesktop()->GetDC(), mInterface );
			break;

		case rvGEWindowWrapper::WT_EDIT:
			window = new idEditWindow ( mInterface->GetDesktop()->GetDC(), mInterface );
			break;

		default:
			assert ( false );
			return NULL;
	}

	baseName = state ? state->GetString("name","unnamed") : "unnamed";
	baseName.StripQuotes ( );

	count = 0;
	if ( mInterface->GetDesktop()->FindChildByName ( baseName ) )
	{
		count = 1;
		while ( 1 )
		{
			drawWin_t* dw = mInterface->GetDesktop()->FindChildByName ( va("%s%d",baseName.c_str(),count) );
			if ( !dw )
			{
				break;
			}
			assert ( dw->win );
			wrapper = rvGEWindowWrapper::GetWrapper ( dw->win );
			if ( wrapper && wrapper->IsDeleted ( ) )
			{
				break;
			}
			count++;
		}
	}

	idStr winName;
	idStr winTemplate;

	if ( count )
	{
		winName = va("%s%d", baseName.c_str(), count );
	}
	else
	{
		winName = baseName;
	}
	winTemplate = winName + " { }";

	idParser src( winTemplate, winTemplate.Length(), "", LEXFL_ALLOWMULTICHARLITERALS | LEXFL_NOSTRINGCONCAT | LEXFL_ALLOWBACKSLASHSTRINGCONCAT );
	window->Parse ( &src );

	wrapper = rvGEWindowWrapper::GetWrapper ( window );

	if ( state )
	{
		wrapper->SetState ( *state );
	}

	wrapper->SetStateKey ( "name", winName );
	wrapper->Finish ( );

	SetModified ( true );


	return window;
}

idWindow* rvGEWorkspace::AddWindow ( rvGEWindowWrapper::EWindowType type )
{
	idWindow*	window;
	idDict		state;

	state.Set ( "rect", "0,0,100,100" );
	state.Set ( "visible", "1" );

	window = NewWindow ( &state, type );
	assert ( window );

	mModifiers.Append ( new rvGEInsertModifier ( "New", window, mInterface->GetDesktop(), NULL ) );

	mSelections.Set ( window );
	mApplication->GetNavigator().Update ( );
	mApplication->GetTransformer().Update ( );
	mApplication->GetProperties().Update ( );

	return window;
}

bool rvGEWorkspace::EditSelectedProperties ( void )
{
	if ( !mSelections.Num ( ) || mSelections.Num() > 1 )
	{
		return false;
	}

	idDict dict;
	if ( GEItemPropsDlg_DoModal ( mWnd, mSelections[0], dict ) )
	{
		mModifiers.Append ( new rvGEStateModifier ( "Item Properties", mSelections[0], dict ) );
		SetModified ( true );
	}

	mApplication->GetNavigator().Update ( );
	mApplication->GetTransformer().Update ( );
	mApplication->GetProperties().Update ( );

	return true;
}

bool rvGEWorkspace::EditSelectedScripts ( void )
{
	if ( GEItemScriptsDlg_DoModal ( mWnd, mSelections[0] ) )
	{
		gApp.GetNavigator().Refresh ( );
		SetModified ( true );
	}

	return true;
}

void rvGEWorkspace::BringSelectedForward ( void )
{
	AddModifiers ( MOD_BRING_FORWARD );
	mApplication->GetNavigator().Update ( );
}

void rvGEWorkspace::BringSelectedToFront ( void )
{
	AddModifiers ( MOD_BRING_FRONT );
	mApplication->GetNavigator().Update ( );
}

void rvGEWorkspace::SendSelectedToBack ( void )
{
	AddModifiers ( MOD_SEND_BACK );
	mApplication->GetNavigator().Update ( );
}

void rvGEWorkspace::SendSelectedBackward ( void )
{
	AddModifiers ( MOD_SEND_BACKWARD );
	mApplication->GetNavigator().Update ( );
}

/*
================
rvGEWorkspace::MakeSelectedSameSize

Align the selected items to the first one using the given align type
================
*/
void rvGEWorkspace::MakeSelectedSameSize ( bool changeWidth, bool changeHeight )
{
	rvGEModifierGroup*	group;
	idRectangle			rectTo;
	int					i;

	group = new rvGEModifierGroup ( );

	rectTo = rvGEWindowWrapper::GetWrapper ( mSelections[0] )->GetClientRect ( );

	for ( i = 1; i < mSelections.Num(); i ++ )
	{
		idRectangle	rectFrom;
		float		width = 0;
		float		height = 0;

		rectFrom = rvGEWindowWrapper::GetWrapper(mSelections[i])->GetClientRect ();

		if ( changeWidth )
		{
			width = rectTo.w - rectFrom.w;
		}

		if ( changeHeight )
		{
			height = rectTo.h - rectFrom.h;
		}

		group->Append ( new rvGESizeModifier ( "Make Same Size", mSelections[i], 0, 0, width, height ) );
	}

	mModifiers.Append ( group );

	// Cant merge alignments
	mModifiers.BlockNextMerge ( );

	SetModified ( true );
}

/*
================
rvGEWorkspace::AlignSelected

Align the selected items to the first one using the given align type
================
*/
void rvGEWorkspace::AlignSelected ( EItemAlign align )
{
	static const char*	alignNames[]={"Lefts","Centers","Rights","Tops","Middles","Bottoms" };
	int					i;
	idStr				modName;
	rvGEModifierGroup*	group;

	assert ( mSelections.Num() > 1 );

	modName = "Align " + idStr(alignNames[align]);

	group   = new rvGEModifierGroup ( );

	idRectangle rectTo;
	rectTo = rvGEWindowWrapper::GetWrapper ( mSelections[0] )->GetScreenRect ( );

	// Everything gets aligned to the first selection so run
	// through all other selections and move them.
	for ( i = 1; i < mSelections.Num(); i ++ )
	{
		float		x;
		float		y;
		idRectangle	rectFrom;

		rectFrom = rvGEWindowWrapper::GetWrapper ( mSelections[i] )->GetScreenRect ( );

		switch ( align )
		{
			case ALIGN_LEFTS:
				x = rectTo[0] - rectFrom[0];
				y = 0;
				break;

			case ALIGN_RIGHTS:
				x = (rectTo[0]+rectTo[2]) - (rectFrom[0]+rectFrom[2]);
				y = 0;
				break;

			case ALIGN_CENTERS:
				x = (rectTo[0]+rectTo[2]/2) - (rectFrom[0]+rectFrom[2]/2);
				y = 0;
				break;

			case ALIGN_TOPS:
				y = rectTo[1] - rectFrom[1];
				x = 0;
				break;

			case ALIGN_BOTTOMS:
				x = 0;
				y = (rectTo[1]+rectTo[3]) - (rectFrom[1]+rectFrom[3]);
				break;

			case ALIGN_MIDDLES:
				x = 0;
				y = (rectTo[1]+rectTo[3]/2) - (rectFrom[1]+rectFrom[3]/2);
				break;

			default:
				assert ( false );
				break;
		}

		group->Append ( new rvGEMoveModifier ( modName, mSelections[i], x, y ) );
	}

	mModifiers.Append ( group );

	// Cant merge alignments
	mModifiers.BlockNextMerge ( );

	SetModified ( true );
}

/*
================
rvGEWorkspace::AddModifierMove

Adds a move modifier with the given offsets
================
*/
void rvGEWorkspace::AddModifierMove ( const char* modName, float x, float y, bool snap )
{
	idRectangle scaleRect;
	idRectangle newRect;

	scaleRect = mSelections.GetRect ( );
	WindowToWorkspace ( scaleRect );
	newRect   = scaleRect;
	newRect.x += x;
	newRect.y += y;

	if ( snap )
	{
		gApp.GetOptions ().SnapRectToGrid ( newRect, true, true, false, false );
	}

	rvGEModifierGroup*	group = new rvGEModifierGroup;
	for ( int i = 0; i < mSelections.Num(); i ++ )
	{
		if ( !mSelections[i]->GetParent ( ) )
		{
			continue;
		}

		// IF the parent window is being moved around as well then dont move this one.
		if ( rvGEWindowWrapper::GetWrapper ( mSelections[i]->GetParent ( ) )->IsSelected ( ) )
		{
			// We still need the modifier there so the selection can be restored and
			// so the rectangle gets updated
			group->Append ( new rvGEMoveModifier ( modName, mSelections[i], 0, 0 ) );
			continue;
		}

		group->Append ( new rvGEMoveModifier ( modName, mSelections[i], newRect.x-scaleRect.x, newRect.y-scaleRect.y ) );
	}

	mModifiers.Append ( group );

	SetModified ( true );
}

/*
================
rvGEWorkspace::AddModifierSize

Adds a size modifier with the given offsets
================
*/
void rvGEWorkspace::AddModifierSize ( const char* modName, float l, float t, float r, float b, bool snap )
{
	idRectangle scaleRect;
	idRectangle	sizeRect;
	idRectangle newRect;

	scaleRect = mSelections.GetRect ( );
	WindowToWorkspace ( scaleRect );
	newRect = scaleRect;
	newRect.x += l;
	newRect.y += t;
	newRect.w += (r - l);
	newRect.h += (b - t);

	// Restrict sizing below 1 width
	if ( newRect.w <= 1 )
	{
		newRect.x	 = newRect.x - (l ? (1 - newRect.w) : 0);
		mDragPoint.x = newRect.x;
		newRect.w	 = 1;
		mDragX		 = false;
	}
	else
	{
		mDragX = true;
	}

	// Restrict sizing below 1 height
	if ( newRect.h <= 1 )
	{
		newRect.y	 = newRect.y - (t ? (1 - newRect.h) : 0);
		mDragPoint.y = newRect.y;
		newRect.h	 = 1;
		mDragY		 = false;
	}
	else
	{
		mDragY = true;
	}

	if ( snap )
	{
		gApp.GetOptions ().SnapRectToGrid ( newRect, l != 0.0f, t != 0.0f, r != 0.0f, b != 0.0f );
	}

	rvGEModifierGroup*	group = new rvGEModifierGroup;
	for ( int i = 0; i < mSelections.Num(); i ++ )
	{
		sizeRect  = rvGEWindowWrapper::GetWrapper ( mSelections[i] )->GetScreenRect ( );

		l = (newRect.x + ((sizeRect.x - scaleRect.x) / scaleRect.w) * newRect.w) - sizeRect.x;
		t = (newRect.y + ((sizeRect.y - scaleRect.y) / scaleRect.h) * newRect.h) - sizeRect.y;
		r = (sizeRect.w / scaleRect.w * newRect.w) - sizeRect.w + l;
		b = (sizeRect.h / scaleRect.h * newRect.h) - sizeRect.h + t;

		// This is sorta crufty but needs to be done.  When a parent is being sized at the same
		// time as a child you will get double movement because the child is relative to the parent.  Therefore
		// we need to subtract out the closest parents sizing.
		idWindow* parent = mSelections[i];
		while ( NULL != (parent = parent->GetParent ( ) ) )
		{
			rvGEWindowWrapper*	pwrapper = rvGEWindowWrapper::GetWrapper ( parent );
			float				offset;

			if ( !pwrapper->IsSelected ( ) )
			{
				continue;
			}

			sizeRect  = pwrapper->GetScreenRect ( );

			// Subtract out the left and right modifications
			offset = ((newRect.x + ((sizeRect.x - scaleRect.x) / scaleRect.w) * newRect.w) - sizeRect.x);
			l -= offset;
			r -= offset;

			// Subtract out the top and bottom modifications
			offset = ((newRect.y + ((sizeRect.y - scaleRect.y) / scaleRect.h) * newRect.h) - sizeRect.y);
			t -= offset;
			b -= offset;

			break;
		}

		group->Append ( new rvGESizeModifier ( modName, mSelections[i], l, t, r, b ) );
	}

	mModifiers.Append ( group );

	SetModified ( true );
}

/*
================
rvGEWorkspace::MakeSelectedAChild

Makes the selected windows a child of the first selected window
================
*/
void rvGEWorkspace::MakeSelectedAChild ( void )
{
	rvGEModifierGroup*	group;
	int					i;

	if ( !rvGEWindowWrapper::GetWrapper ( mSelections[0] )->CanHaveChildren ( ) )
	{
		gApp.MessageBox ( "Cannot add children to an htmlDef item", MB_OK|MB_ICONERROR );
		return;
	}

	group = new rvGEModifierGroup;

	for ( i = 1; i < mSelections.Num(); i ++ )
	{
		if ( mSelections[i]->GetParent ( ) == mSelections[0] )
		{
			continue;
		}

		if ( !mSelections[i]->GetParent ( ) )
		{
			continue;
		}

		group->Append ( new rvGEInsertModifier ( "Make Child", mSelections[i], mSelections[0], NULL ) );
	}

	mModifiers.Append ( group );

	// Navigator needs an update since the ordering has changed
	gApp.GetNavigator().Update ( );

	SetModified ( true );
}

void rvGEWorkspace::Copy ( void )
{
	int i;

	// Clear the current clipboard
	for ( i = 0; i < mClipboard.Num(); i ++ )
	{
		delete mClipboard[i];
	}

	mClipboard.Clear ( );

	for ( i = 0; i < mSelections.Num(); i ++ )
	{
		rvGEWindowWrapper* wrapper = rvGEWindowWrapper::GetWrapper ( mSelections[i] );
		assert ( wrapper );

		rvGEClipboardItem* item = new rvGEClipboardItem;
		item->mStateDict  = wrapper->GetStateDict ( );
		item->mScriptDict = wrapper->GetScriptDict ( );
		item->mVarDict    = wrapper->GetVariableDict ( );

		item->mStateDict.Set ( "windowType", rvGEWindowWrapper::WindowTypeToString ( wrapper->GetWindowType ( ) ) );

		mClipboard.Append ( item );
	}
}

void rvGEWorkspace::Paste ( void )
{
	int i;

	rvGEModifierGroup* group = new rvGEModifierGroup;

	mSelections.Clear ( );

	for ( i = 0; i < mClipboard.Num(); i ++ )
	{
		idDict							state;
		rvGEWindowWrapper::EWindowType	type;

		state.Copy ( mClipboard[i]->mStateDict );
		type = rvGEWindowWrapper::StringToWindowType ( state.GetString ( "windowType", "windowDef" ) );
		state.Delete ( "windowType" );

		idWindow* window = NewWindow ( &state, type );
		group->Append ( new rvGEInsertModifier ( "Paste", window, mInterface->GetDesktop(), NULL ) );
		mSelections.Add ( window );

		rvGEWindowWrapper::GetWrapper ( window )->GetScriptDict ( ) = mClipboard[i]->mScriptDict;
		rvGEWindowWrapper::GetWrapper ( window )->GetVariableDict ( ) = mClipboard[i]->mVarDict;
	}

	mModifiers.Append ( group );

	mApplication->GetNavigator().Update ( );

	SetModified ( true );
}

void rvGEWorkspace::HideSelected ( void )
{
	AddModifiers ( MOD_HIDE );
	mSelections.Clear ( );
	mApplication->GetNavigator().Refresh ( );
}

void rvGEWorkspace::UnhideSelected ( void )
{
	AddModifiers ( MOD_UNHIDE );
	mApplication->GetNavigator().Refresh ( );
}

void rvGEWorkspace::HideWindow ( idWindow* window )
{
	AddModifiers ( window, MOD_HIDE );
	mApplication->GetNavigator().Refresh ( );
}

void rvGEWorkspace::UnhideWindow ( idWindow* window )
{
	AddModifiers ( window, MOD_UNHIDE );
	mApplication->GetNavigator().Refresh ( );
}

/*
================
rvGEWorkspace::SetModified

Sets the modified state of the window and if source control is enabled it
will attempt to check out the file
================
*/
void rvGEWorkspace::SetModified ( bool mod )
{
	if ( mModified != mod )
	{

		mModified = mod;
		UpdateTitle ( );

	}
}
