/** 
 * @file llviewerwindow.cpp
 * @brief Implementation of the LLViewerWindow class.
 *
 * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc.
 * $License$
 */

#include "llviewerprecompiledheaders.h"

// system library includes
#include <stdio.h>
#include <iostream>
#include <fstream>

#include "llviewerwindow.h"
#include "llviewquery.h"
#include "llxmltree.h"
#include "llviewercamera.h"
//#include "imdebug.h"

#ifdef SABINRIG
#include "cbw.h"
#endif //SABINRIG

//
// TODO: Many of these includes are unnecessary.  Remove them.
//

// linden library includes
#include "audioengine.h"		// mute on minimize
#include "indra_constants.h"
#include "linked_lists.h"
#include "llassetstorage.h"
#include "llfontgl.h"
#include "llmediaengine.h"		// mute on minimize
#include "llrect.h"
#include "llsky.h"
#include "llstring.h"
#include "llui.h"
#include "lluuid.h"
#include "llview.h"
#include "llxfermanager.h"
#include "message.h"
#include "object_flags.h"
#include "text_out.h"
#include "lltimer.h"
#include "timing.h"
#include "llviewermenu.h"

// newview includes
#include "llagent.h"
#include "llalertdialog.h"
#include "llbox.h"
#include "llcameraview.h"
#include "llchatbar.h"
#include "llconsole.h"
#include "llviewercontrol.h"
#include "llcylinder.h"
#include "lldebugview.h"
#include "lldir.h"
#include "lldrawable.h"
#include "lldrawpoolalpha.h"
#include "lldrawpoolbump.h"
#include "lldrawpoolwater.h"
#include "llmaniptranslate.h"
#include "llface.h"
#include "llfeaturemanager.h"
#include "llfilepicker.h"
#include "llfloater.h"
#include "llfloaterbuildoptions.h"
#include "llfloaterchat.h"
#include "llfloatercustomize.h"
#include "llfloatereditui.h" // HACK JAMESDEBUG for ui editor
#include "llfloaterland.h"
#include "llfloatermap.h"
#include "llfloatermute.h"
#include "llfloaternamedesc.h"
#include "llfloatersnapshot.h"
#include "llfloatertools.h"
#include "llfloaterworldmap.h"
#include "llfocusmgr.h"
#include "llframestatview.h"
#include "llgesturemgr.h"
#include "llglheaders.h"
#include "llhippo.h"
#include "llhoverview.h"
#include "llhudmanager.h"
#include "llhudview.h"
#include "llimagebmp.h"
#include "llimagej2c.h"
#include "llinventoryview.h"
#include "llkeyboard.h"
#include "lllineeditor.h"
#include "llmenugl.h"
#include "llmodaldialog.h"
#include "llmorphview.h"
#include "llmoveview.h"
#include "llnotify.h"
#include "lloverlaybar.h"
#include "llpreviewtexture.h"
#include "llprogressview.h"
#include "llresmgr.h"
#include "llrootview.h"
#include "llselectmgr.h"
#include "llsphere.h"
#include "llstartup.h"
#include "llstatusbar.h"
#include "llstatview.h"
#include "llsurface.h"
#include "llsurfacepatch.h"
#include "llimview.h"
#include "lltexlayer.h"
#include "lltextbox.h"
#include "lltextureview.h"
#include "lltool.h"
#include "lltoolbar.h"
#include "lltoolcomp.h"
#include "lltooldraganddrop.h"
#include "lltoolface.h"
#include "lltoolfocus.h"
#include "lltoolgrab.h"
#include "lltoolmgr.h"
#include "lltoolmorph.h"
#include "lltoolpie.h"
#include "lltoolplacer.h"
#include "lltoolselect.h"
#include "lltoolselectland.h"
#include "lltoolview.h"
#include "llvieweruictrlfactory.h"
#include "lluploaddialog.h"
#include "llviewercamera.h"
#include "llviewergesture.h"
#include "llviewerimagelist.h"
#include "llviewerinventory.h"
#include "llviewerkeyboard.h"
#include "llviewermenu.h"
#include "llviewermessage.h"
#include "llviewerobjectlist.h"
#include "llviewerparcelmgr.h"
#include "llviewerregion.h"
#include "llviewerstats.h"
#include "llvoavatar.h"
#include "llvovolume.h"
#include "llworld.h"
#include "llworldmapview.h"
#include "moviemaker.h"
#include "pipeline.h"
#include "viewer.h"

#if LL_WINDOWS
#include "llwindebug.h"
#include <tchar.h> // For Unicode conversion methods
#endif

//
// Globals
//

LLBottomPanel* gBottomPanel = NULL;

extern BOOL gDebugClicks;
extern BOOL gDisplaySwapBuffers;
extern S32 gJamesInt;

LLViewerWindow	*gViewerWindow = NULL;
LLVelocityBar	*gVelocityBar = NULL;

LLVector3d		gLastHitPosGlobal;
LLVector3d		gLastHitObjectOffset;
LLUUID			gLastHitObjectID;
S32				gLastHitObjectFace = -1;
BOOL			gLastHitLand = FALSE;
F32				gLastHitUCoord;
F32				gLastHitVCoord;


LLVector3d		gLastHitNonFloraPosGlobal;
LLVector3d		gLastHitNonFloraObjectOffset;
LLUUID			gLastHitNonFloraObjectID;
S32				gLastHitNonFloraObjectFace = -1;
BOOL			gLastHitParcelWall = FALSE;

S32				gLastHitUIElement = 0;
LLHUDIcon*		gLastHitHUDIcon = NULL;

BOOL			gDebugSelect = FALSE;
U8				gLastPickAlpha = 255;
BOOL			gUseGLPick = FALSE;

// On the next pick pass (whenever that happens)
// should we try to pick individual faces?
// Cleared to FALSE every time a pick happens.
BOOL			gPickFaces = FALSE;

LLFrameTimer	gMouseIdleTimer;
LLFrameTimer	gAwayTimer;
LLFrameTimer	gAwayTriggerTimer;
LLFrameTimer	gAlphaFadeTimer;

BOOL			gShowOverlayTitle = FALSE;
BOOL			gPickTransparent = TRUE;

BOOL			gDebugFastUIRender = FALSE;

BOOL			gbCapturing = FALSE;
MovieMaker		gMovieMaker;

S32 CHAT_BAR_HEIGHT = 28; 
S32 OVERLAY_BAR_HEIGHT = 20;

const U8 NO_FACE = 255;
BOOL gQuietSnapshot = FALSE;

const F32 MIN_AFK_TIME = 2.f; // minimum time after setting away state before coming back
const F32 MAX_FAST_FRAME_TIME = 0.5f;
const F32 FAST_FRAME_INCREMENT = 0.1f;

const S32 PICK_HALF_WIDTH = 5;
const S32 PICK_DIAMETER = 2 * PICK_HALF_WIDTH+1;

const F32 MIN_DISPLAY_SCALE = 0.85f;

#ifdef SABINRIG
/// ALL RIG STUFF
bool rigControl = false;
bool voltDisplay = true;
bool nominalX = false;
bool nominalY = false;
static F32 nomerX = 0.0f;
static F32 nomerY = 0.0f;
const BOARD_NUM = 0; // rig stuff!
const ADRANGE = BIP10VOLTS; // rig stuff!
static unsigned short DataVal; // rig stuff!
static F32 oldValueX = 0;
static F32 newValueX = 50;
static F32 oldValueY = 0;
static F32 newValueY = 50;
static S32 mouseX = 50;
static S32 mouseY = 50;
static float VoltageX = 50; // rig stuff!
static float VoltageY = 50; // rig stuff!
static float nVoltX = 0;
static float nVoltY = 0;
static F32 temp1 = 50.f;
static F32 temp2 = 20.f;
LLCoordGL new_gl;
#endif //SABINRIG

char	LLViewerWindow::sSnapshotBaseName[LL_MAX_PATH];
char	LLViewerWindow::sSnapshotDir[LL_MAX_PATH];

char	LLViewerWindow::sMovieBaseName[LL_MAX_PATH];

extern void toggle_debug_menus(void*);

#ifdef SABINRIG
// static
void LLViewerWindow::printFeedback()
{
	if(rigControl == true)
	{
		cbAIn (BOARD_NUM, 0, ADRANGE, &DataVal);
		cbToEngUnits (BOARD_NUM,ADRANGE,DataVal,&VoltageX); //Convert raw to voltage for X-axis
		cbAIn (BOARD_NUM, 1, ADRANGE, &DataVal);
		cbToEngUnits (BOARD_NUM,ADRANGE,DataVal,&VoltageY); //Convert raw to voltage for Y-axis
		if(voltDisplay == true)
		{
			llinfos <<  "Current Voltages - X:" << VoltageX << " Y:" << VoltageY << llendl; //Display voltage
		}

		if(nVoltX == 0)
		{
			nVoltX = VoltageX;
			nVoltY = VoltageY; //First time setup of nominal values.
		}

		newValueX = VoltageX;
		newValueY = VoltageY; //Take in current voltage and set to a separate value for adjustment.

		mouseX = mCurrentMousePoint.mX;
		mouseY = mCurrentMousePoint.mY; //Take in current cursor position and set to separate value for adjustment.

		if( abs(newValueX - nVoltX) > nomerX )
		{
			if( (newValueX - oldValueX) < 0)
			{
				mouseX += (S32)( ((newValueX - oldValueX)*.5)) * -temp;
			}
			else
			{
				mouseX += (S32)( ((newValueX - oldValueX)*.5) * temp1);
			}
		}
		else
		{
			mouseX = getWindowWidth() / 2;
		}
		if( abs(newValueY - nVoltY) > nomerY )
		{
			if( (newValueY - oldValueY) < 0)
			{
				mouseY += (S32)( ((newValueY - oldValueY)*(newValueY - oldValueY)) * -temp2);
			}
			else
			{
				mouseY += (S32)( ((newValueY - oldValueY)*(newValueY - oldValueY)) * temp2);
			}
		}
		else
		{
			mouseY = getWindowHeight() / 2;
		}
		//mouseX += (S32)( (newValueX - nVoltX) * temp1 + 0.5 );
		// (newValueX - oldValueX) = difference between current position and nominal position
		// * temp1 = the amplification of the number that sets sensitivity
		// + 0.5 = fixes rounding errors
		

		//mouseY += (S32)( (newValueY - nVoltY) * temp2 + 0.5 ); //Algorithm to adjust voltage for mouse adjustment.

		oldValueX = newValueX;
		oldValueY = newValueY;

		new_gl.mX = mouseX;
		new_gl.mY = mouseY; //Setup final coordinate to move mouse to.

		setCursorPosition(new_gl); //Set final cursor position
	}
}
#endif //SABINRIG


//
// LLViewerWindow
//

BOOL LLViewerWindow::handleMouseDown(LLWindow *window,  LLCoordGL pos, MASK mask)
{
	S32 x = pos.mX;
	S32 y = pos.mY;
	x = llround((F32)x / mDisplayScale.mV[VX]);
	y = llround((F32)y / mDisplayScale.mV[VY]);

	if (gDebugClicks)
	{
		llinfos << "ViewerWindow left mouse down at " << x << "," << y << llendl;
	}

	mLeftMouseDown = TRUE;

	// Make sure we get a coresponding mouseup event, even if the mouse leaves the window
	mWindow->captureMouse();

	// Indicate mouse was active
	gMouseIdleTimer.reset();

	// Hide tooltips on mousedown
	if( mToolTip )
	{
		mToolTip->setVisible( FALSE );
	}

	// Also hide hover info on mousedown
	if (gHoverView)
	{
		gHoverView->cancelHover();
	}

	if (gToolMgr)
	{
		// Don't let the user move the mouse out of the window until mouse up.
		if( gToolMgr->getCurrentTool(mask)->clipMouseWhenDown() )
		{
			mWindow->setMouseClipping(TRUE);
		}
	}

	LLMouseHandler* mouse_captor = gFocusMgr.getMouseCapture();
	if( mouse_captor )
	{
		S32 local_x;
		S32 local_y;
		mouse_captor->screenPointToLocal( x, y, &local_x, &local_y );
		if (LLView::sDebugMouseHandling)
		{
			llinfos << "Left Mouse Down handled by captor " << mouse_captor->getName() << llendl;
		}

		return mouse_captor->handleMouseDown(local_x, local_y, mask);
	}

	// Topmost view gets a chance before the hierarchy
	LLView* top_view = gFocusMgr.getTopView();
	if (top_view)
	{
		S32 local_x, local_y;
		top_view->screenPointToLocal( x, y, &local_x, &local_y );
		if (top_view->pointInView(local_x, local_y) && top_view->handleMouseDown(local_x, local_y, mask)) return TRUE;
	}

	// Give the UI views a chance to process the click
	if( mRootView->handleMouseDown(x, y, mask) )
	{
		if (LLView::sDebugMouseHandling)
		{
			llinfos << "Left Mouse Down" << LLView::sMouseHandlerMessage << llendl;
			LLView::sMouseHandlerMessage = "";
		}
		return TRUE;
	}
	else if (LLView::sDebugMouseHandling)
	{
		llinfos << "Left Mouse Down not handled by view" << llendl;
	}

	if (gDisconnected)
	{
		return FALSE;
	}

	if (gToolMgr)
	{
		if(gToolMgr->getCurrentTool(mask)->handleMouseDown( x, y, mask ) )
		{
			// This is necessary to force clicks in the world to cause edit
			// boxes that might have keyboard focus to relinquish it, and hence
			// cause a commit to update their value.  JC
			gFocusMgr.setKeyboardFocus(NULL, NULL);
			return TRUE;
		}
	}

	return FALSE;
}

BOOL LLViewerWindow::handleDoubleClick(LLWindow *window,  LLCoordGL pos, MASK mask)
{
	S32 x = pos.mX;
	S32 y = pos.mY;
	x = llround((F32)x / mDisplayScale.mV[VX]);
	y = llround((F32)y / mDisplayScale.mV[VY]);

	if (gDebugClicks)
	{
		llinfos << "ViewerWindow left mouse double-click at " << x << "," << y << llendl;
	}

	mLeftMouseDown = TRUE;

	// Hide tooltips
	if( mToolTip )
	{
		mToolTip->setVisible( FALSE );
	}

	LLMouseHandler* mouse_captor = gFocusMgr.getMouseCapture();
	if( mouse_captor )
	{
		S32 local_x;
		S32 local_y;
		mouse_captor->screenPointToLocal( x, y, &local_x, &local_y );
		if (LLView::sDebugMouseHandling)
		{
			llinfos << "Left Mouse Down handled by captor " << mouse_captor->getName() << llendl;
		}

		return mouse_captor->handleDoubleClick(local_x, local_y, mask);
	}

	// Check for hit on UI.
	LLView* top_view = gFocusMgr.getTopView();
	if (top_view)
	{
		S32 local_x, local_y;
		top_view->screenPointToLocal( x, y, &local_x, &local_y );
		if (top_view->pointInView(local_x, local_y) && top_view->handleDoubleClick(local_x, local_y, mask)) return TRUE;
	}

	if (mRootView->handleDoubleClick(x, y, mask)) 
	{
		if (LLView::sDebugMouseHandling)
		{
			llinfos << "Left Mouse Down" << LLView::sMouseHandlerMessage << llendl;
			LLView::sMouseHandlerMessage = "";
		}
		return TRUE;
	}
	else if (LLView::sDebugMouseHandling)
	{
		llinfos << "Left Mouse Down not handled by view" << llendl;
	}

	// Why is this here?  JC 9/3/2002
	if (gNoRender) 
	{
		return TRUE;
	}

	if (gToolMgr)
	{
		if(gToolMgr->getCurrentTool(mask)->handleDoubleClick( x, y, mask ) )
		{
			return TRUE;
		}
	}

	// if we got this far and nothing handled a double click, pass a normal mouse down
	return handleMouseDown(window, pos, mask);
}

BOOL LLViewerWindow::handleMouseUp(LLWindow *window,  LLCoordGL pos, MASK mask)
{
	S32 x = pos.mX;
	S32 y = pos.mY;
	x = llround((F32)x / mDisplayScale.mV[VX]);
	y = llround((F32)y / mDisplayScale.mV[VY]);

	if (gDebugClicks)
	{
		llinfos << "ViewerWindow left mouse up" << llendl;
	}

	mLeftMouseDown = FALSE;

	// Indicate mouse was active
	gMouseIdleTimer.reset();

	// Hide tooltips on mouseup
	if( mToolTip )
	{
		mToolTip->setVisible( FALSE );
	}

	// Also hide hover info on mouseup
	if (gHoverView)	gHoverView->cancelHover();

	BOOL handled = FALSE;

	mWindow->releaseMouse();

	LLTool *tool = NULL;
	if (gToolMgr)
	{
		tool = gToolMgr->getCurrentTool(mask);

		if( tool->clipMouseWhenDown() )
		{
			mWindow->setMouseClipping(FALSE);
		}
	}

	LLMouseHandler* mouse_captor = gFocusMgr.getMouseCapture();
	if( mouse_captor )
	{
		S32 local_x;
		S32 local_y;
		mouse_captor->screenPointToLocal( x, y, &local_x, &local_y );
		if (LLView::sDebugMouseHandling)
		{
			llinfos << "Left Mouse Up handled by captor " << mouse_captor->getName() << llendl;
		}

		return mouse_captor->handleMouseUp(local_x, local_y, mask);
	}

	LLView* top_view = gFocusMgr.getTopView();
	if (top_view)
	{
		S32 local_x, local_y;
		top_view->screenPointToLocal( x, y, &local_x, &local_y );
		handled = top_view->pointInView(local_x, local_y) && top_view->handleMouseUp(local_x, local_y, mask);
	}

	if( !handled )
	{
		handled = mRootView->handleMouseUp(x, y, mask);
	}

	if (LLView::sDebugMouseHandling)
	{
		if (handled)
		{
			llinfos << "Left Mouse Up" << LLView::sMouseHandlerMessage << llendl;
			LLView::sMouseHandlerMessage = "";
		}
		else 
		{
			llinfos << "Left Mouse Up not handled by view" << llendl;
		}
	}

	if( !handled )
	{
		if (tool)
		{
			handled = tool->handleMouseUp(x, y, mask);
		}
	}

	// Always handled as far as the OS is concerned.
	return TRUE;
}


BOOL LLViewerWindow::handleRightMouseDown(LLWindow *window,  LLCoordGL pos, MASK mask)
{
	S32 x = pos.mX;
	S32 y = pos.mY;
	x = llround((F32)x / mDisplayScale.mV[VX]);
	y = llround((F32)y / mDisplayScale.mV[VY]);

	if (gDebugClicks)
	{
		llinfos << "ViewerWindow right mouse down at " << x << "," << y << llendl;
	}

	mRightMouseDown = TRUE;

	// Make sure we get a coresponding mouseup event, even if the mouse leaves the window
	mWindow->captureMouse();

	// Hide tooltips
	if( mToolTip )
	{
		mToolTip->setVisible( FALSE );
	}

	// Also hide hover info on mousedown
	if (gHoverView)
	{
		gHoverView->cancelHover();
	}

	if (gToolMgr)
	{
		// Don't let the user move the mouse out of the window until mouse up.
		if( gToolMgr->getCurrentTool(mask)->clipMouseWhenDown() )
		{
			mWindow->setMouseClipping(TRUE);
		}
	}

	LLMouseHandler* mouse_captor = gFocusMgr.getMouseCapture();
	if( mouse_captor )
	{
		S32 local_x;
		S32 local_y;
		mouse_captor->screenPointToLocal( x, y, &local_x, &local_y );
		if (LLView::sDebugMouseHandling)
		{
			llinfos << "Right Mouse Down handled by captor " << mouse_captor->getName() << llendl;
		}
		return mouse_captor->handleRightMouseDown(local_x, local_y, mask);
	}

	LLView* top_view = gFocusMgr.getTopView();
	if (top_view)
	{
		S32 local_x, local_y;
		top_view->screenPointToLocal( x, y, &local_x, &local_y );
		if (top_view->pointInView(local_x, local_y) && top_view->handleRightMouseDown(local_x, local_y, mask)) return TRUE;
	}

	if( mRootView->handleRightMouseDown(x, y, mask) )
	{
		if (LLView::sDebugMouseHandling)
		{
			llinfos << "Right Mouse Down" << LLView::sMouseHandlerMessage << llendl;
			LLView::sMouseHandlerMessage = "";
		}
		return TRUE;
	}
	else if (LLView::sDebugMouseHandling)
	{
		llinfos << "Right Mouse Down not handled by view" << llendl;
	}

	if (gToolMgr)
	{
		if(gToolMgr->getCurrentTool(mask)->handleRightMouseDown( x, y, mask ) )
		{
			// This is necessary to force clicks in the world to cause edit
			// boxes that might have keyboard focus to relinquish it, and hence
			// cause a commit to update their value.  JC
			gFocusMgr.setKeyboardFocus(NULL, NULL);
			return TRUE;
		}
	}

	//FIXME: this should be rolled into the composite tool logic, not hardcoded at the top level
	if (gToolPie && (CAMERA_MODE_CUSTOMIZE_AVATAR != gAgent.getCameraMode()) )
	{
		// If the current tool didn't process the click, we should show
		// the pie menu.  This can be done by passing the event to the pie
		// menu tool.
		gToolPie->handleRightMouseDown(x, y, mask);
		// show_context_menu( x, y, mask );
	}

	return TRUE;
}

BOOL LLViewerWindow::handleRightMouseUp(LLWindow *window,  LLCoordGL pos, MASK mask)
{
	S32 x = pos.mX;
	S32 y = pos.mY;
	x = llround((F32)x / mDisplayScale.mV[VX]);
	y = llround((F32)y / mDisplayScale.mV[VY]);

	// Don't care about caps lock for mouse events.
	if (gDebugClicks)
	{
		llinfos << "ViewerWindow right mouse up" << llendl;
	}

	mRightMouseDown = FALSE;

	// Indicate mouse was active
	gMouseIdleTimer.reset();

	// Hide tooltips on mouseup
	if( mToolTip )
	{
		mToolTip->setVisible( FALSE );
	}

	// Also hide hover info on mouseup
	if (gHoverView)	gHoverView->cancelHover();

	BOOL handled = FALSE;

	mWindow->releaseMouse();

	LLTool *tool = NULL;
	if (gToolMgr)
	{
		tool = gToolMgr->getCurrentTool(mask);

		if( tool->clipMouseWhenDown() )
		{
			mWindow->setMouseClipping(FALSE);
		}
	}

	LLMouseHandler* mouse_captor = gFocusMgr.getMouseCapture();
	if( mouse_captor )
	{
		S32 local_x;
		S32 local_y;
		mouse_captor->screenPointToLocal( x, y, &local_x, &local_y );
		if (LLView::sDebugMouseHandling)
		{
			llinfos << "Right Mouse Up handled by captor " << mouse_captor->getName() << llendl;
		}
		return mouse_captor->handleRightMouseUp(local_x, local_y, mask);
	}

	LLView* top_view = gFocusMgr.getTopView();
	if (top_view)
	{
		S32 local_x, local_y;
		top_view->screenPointToLocal( x, y, &local_x, &local_y );
		handled = top_view->pointInView(local_x, local_y) && top_view->handleRightMouseUp(local_x, local_y, mask);
	}

	if( !handled )
	{
		handled = mRootView->handleRightMouseUp(x, y, mask);
	}

	if (LLView::sDebugMouseHandling)
	{
		if (handled)
		{
			llinfos << "Right Mouse Up" << LLView::sMouseHandlerMessage << llendl;
			LLView::sMouseHandlerMessage = "";
		}
		else 
		{
			llinfos << "Right Mouse Up not handled by view" << llendl;
		}
	}

	if( !handled )
	{
		if (tool)
		{
			handled = tool->handleRightMouseUp(x, y, mask);
		}
	}

	// Always handled as far as the OS is concerned.
	return TRUE;
}


void LLViewerWindow::handleMouseMove(LLWindow *window,  LLCoordGL pos, MASK mask)
{
	S32 x = pos.mX;
	S32 y = pos.mY;

	x = llround((F32)x / mDisplayScale.mV[VX]);
	y = llround((F32)y / mDisplayScale.mV[VY]);

	mMouseInWindow = TRUE;

	// Save mouse point for access during idle() and display()

	LLCoordGL prev_saved_mouse_point = mCurrentMousePoint;
	LLCoordGL mouse_point(x, y);
	saveLastMouse(mouse_point);
	BOOL mouse_actually_moved = (prev_saved_mouse_point.mX != mCurrentMousePoint.mX) || (prev_saved_mouse_point.mY != mCurrentMousePoint.mY);

	gMouseIdleTimer.reset();

	mWindow->showCursorFromMouseMove();

	if (gAwayTimer.getElapsedTimeF32() > MIN_AFK_TIME)
	{
		gAgent.clearAFK();
	}

	if(mToolTip && mouse_actually_moved)
	{
		mToolTipBlocked = FALSE;  // Blocking starts on keyboard events and (only) ends here.
		if( mToolTip->getVisible() && !mToolTipStickyRect.pointInRect( x, y ) )
		{
			mToolTip->setVisible( FALSE );
		}
	}

	// Activate the hover picker on mouse move.
	if (gHoverView)
	{
		gHoverView->setTyping(FALSE);
	}
}

void LLViewerWindow::handleMouseLeave(LLWindow *window)
{
	// Note: we won't get this if we have captured the mouse.
	llassert( gFocusMgr.getMouseCapture() == NULL );
	mMouseInWindow = FALSE;
	if (mToolTip)
	{
		mToolTip->setVisible( FALSE );
	}
}

BOOL LLViewerWindow::handleCloseRequest(LLWindow *window)
{
	// User has indicated they want to close, but we may need to ask
	// about modified documents.
	app_request_quit();
	// Don't quit immediately
	return FALSE;
}

void LLViewerWindow::handleQuit(LLWindow *window)
{
	app_force_quit(NULL);
}

void LLViewerWindow::handleResize(LLWindow *window,  S32 width,  S32 height)
{
	reshape(width, height);
}

// The top-level window has gained focus (e.g. via ALT-TAB)
void LLViewerWindow::handleFocus(LLWindow *window)
{
	gFocusMgr.setAppHasFocus(TRUE);
	LLModalDialog::onAppFocusGained();
	if (gToolMgr)
	{
		gToolMgr->onAppFocusGained();
	}

	gShowTextEditCursor = TRUE;

	// See if we're coming in with modifier keys held down
	if (gKeyboard)
	{
		gKeyboard->resetMaskKeys();
	}
}

// The top-level window has lost focus (e.g. via ALT-TAB)
void LLViewerWindow::handleFocusLost(LLWindow *window)
{
	gFocusMgr.setAppHasFocus(FALSE);
	//LLModalDialog::onAppFocusLost();
	if( gToolMgr )
	{
		gToolMgr->onAppFocusLost();
	}
	gFocusMgr.setMouseCapture( NULL, NULL );

	// restore mouse cursor
	gViewerWindow->showCursor();
	gViewerWindow->getWindow()->setMouseClipping(FALSE);

	// JC - Leave keyboard focus, so if you're popping in and out editing
	// a script, you don't have to click in the editor again and again.
	// gFocusMgr.setKeyboardFocus( NULL, NULL );
	gShowTextEditCursor = FALSE;

	// If losing focus while keys are down, reset them.
	if (gKeyboard)
	{
		gKeyboard->resetKeys();
	}
}


BOOL LLViewerWindow::handleTranslatedKeyDown(KEY key,  MASK mask, BOOL repeated)
{
	if (gAwayTimer.getElapsedTimeF32() > MIN_AFK_TIME)
	{
		gAgent.clearAFK();
	}

	// HACK: We want to interpret KEY_RETURN later when it arrives as a Unicode char,
	// not as a keydown.  Otherwise when client frame rate is really low, hitting
	// return sends your chat text before it's all entered/processed.
	if (key == KEY_RETURN && mask == MASK_NONE)
	{
		return FALSE;
	}

	return gViewerKeyboard.handleKey(key, mask, repeated);
}

BOOL LLViewerWindow::handleTranslatedKeyUp(KEY key,  MASK mask)
{
	return FALSE;
}


void LLViewerWindow::handleScanKey(KEY key, BOOL key_down, BOOL key_up, BOOL key_level)
{
	return gViewerKeyboard.scanKey(key, key_down, key_up, key_level);
}




BOOL LLViewerWindow::handleActivate(LLWindow *window, BOOL activated)
{
	if (activated)
	{
		mActive = TRUE;
		send_agent_resume();
		gAgent.clearAFK();
		if (mWindow->getFullscreen() && !mIgnoreActivate)
		{
			if (!gQuit)
			{
				if (gStartupState >= STATE_STARTED)
				{
					// if we're in world, show a progress bar to hide reloading of textures
					llinfos << "Restoring GL during activate" << llendl;
					restoreGL("Restoring...");
				}
				else
				{
					// otherwise restore immediately
					restoreGL();
				}
			}
			else
			{
				llwarns << "Activating while quitting" << llendl;
			}
		}

		// Unmute audio
		if (!gSavedSettings.getBOOL("MuteAudio"))
		{
			if (gAudiop) gAudiop->setMuted(FALSE);
			F32 volume = gSavedSettings.getF32("MediaAudioVolume");
			if(LLMediaEngine::getInstance())
			{
				LLMediaEngine::getInstance()->setVolume(volume);
				LLMediaEngine::updateClass(volume);
			}
		}
	}
	else
	{
		mActive = FALSE;
		gAgent.setAFK();
		send_agent_pause();
		if (mWindow->getFullscreen() && !mIgnoreActivate)
		{
			llinfos << "Stopping GL during deactivation" << llendl;
			stopGL();
		}
		// Mute audio
		if (gSavedSettings.getBOOL("MuteWhenMinimized"))
		{
			llinfos << "Muting audio on minimize" << llendl;
			if (gAudiop) gAudiop->setMuted(TRUE);
			F32 volume = 0.f;
			LLMediaEngine::getInstance()->setVolume(volume);
			LLMediaEngine::updateClass(volume);
		}
	}
	return TRUE;
}


void LLViewerWindow::handleMenuSelect(LLWindow *window,  S32 menu_item)
{
}


BOOL LLViewerWindow::handlePaint(LLWindow *window,  S32 x,  S32 y, S32 width,  S32 height)
{
#if LL_WINDOWS
	if (gNoRender)
	{
		HWND window_handle = llwindow_get_hwnd(window);
		PAINTSTRUCT ps; 
		HDC hdc; 
 
		RECT wnd_rect;
		wnd_rect.left = 0;
		wnd_rect.top = 0;
		wnd_rect.bottom = 200;
		wnd_rect.right = 500;

		hdc = BeginPaint(window_handle, &ps); 
		//SetBKColor(hdc, RGB(255, 255, 255));
		FillRect(hdc, &wnd_rect, CreateSolidBrush(RGB(255, 255, 255)));

		LLString name_str;
		gAgent.getName(name_str);

		S32 len;
		char temp_str[255];
		sprintf(temp_str, "%s FPS %3.1f Phy FPS %2.1f Time Dil %1.3f",
				name_str.c_str(),
				gViewerStats->mFPSStat.getMeanPerSec(),
				gViewerStats->mSimPhysicsFPS.getPrev(0),
				gViewerStats->mSimTimeDilation.getPrev(0));
		len = strlen(temp_str);
		TextOutA(hdc, 0, 0, temp_str, len); 


		LLVector3d pos_global = gAgent.getPositionGlobal();
		sprintf(temp_str, "Avatar pos %6.1lf %6.1lf %6.1lf", pos_global.mdV[0], pos_global.mdV[1], pos_global.mdV[2]);
		len = strlen(temp_str);
		TextOutA(hdc, 0, 25, temp_str, len); 

		TextOutA(hdc, 0, 50, "Set \"DisableRendering FALSE\" in settings.ini file to reenable", 61);
		EndPaint(window_handle, &ps); 
		return TRUE;
	}
#endif
	return FALSE;
}


void LLViewerWindow::handleScrollWheel(LLWindow *window,  S32 clicks)
{
	gViewerWindow->handleScrollWheel( clicks );
}

void LLViewerWindow::handleWindowBlock(LLWindow *window)
{
	send_agent_pause();
}

void LLViewerWindow::handleWindowUnblock(LLWindow *window)
{
	send_agent_resume();
}

void LLViewerWindow::handleDataCopy(LLWindow *window, S32 data_type, void *data)
{
	switch (data_type)
	{
	case 0:
		// received URL
		if (LLURLSimString::unpack_data(data))
		{
			if(gFloaterWorldMap)
			{
				gFloaterWorldMap->trackURL(LLURLSimString::sInstance.mSimName,
										   LLURLSimString::sInstance.mX,
										   LLURLSimString::sInstance.mY,
										   LLURLSimString::sInstance.mZ);

				LLFloaterWorldMap::show(NULL, TRUE);
			}

			// bring window to foreground, as it has just been "launched" from a URL
			mWindow->bringToFront();
		}
		break;
	}
}


//
// Classes
//
LLViewerWindow::LLViewerWindow(
	char* title, char* name,
	S32 x, S32 y,
	S32 width, S32 height,
	BOOL fullscreen, BOOL ignore_pixel_depth)
	:
	mActive(TRUE),
	mWantFullscreen(fullscreen),
	mShowFullscreenProgress(FALSE),
	mWindowRect(0, height, width, 0),
	mVirtualWindowRect(0, height, width, 0),
	mLeftMouseDown(FALSE),
	mRightMouseDown(FALSE),
	mToolTip(NULL),
	mToolTipBlocked(FALSE),
	mMouseInWindow( FALSE ),
	mLastMask( MASK_NONE ),
	mToolStored( NULL ),
	mSuppressToolbox( FALSE ),
	mHideCursorPermanent( FALSE ),
	mPickPending(FALSE),
	mIgnoreActivate( FALSE ),
	mRenderFullFrame(FALSE)
{
	// Default to application directory.
	strcpy(LLViewerWindow::sSnapshotBaseName, "Snapshot");
	strcpy(LLViewerWindow::sMovieBaseName, "SLmovie");
	LLViewerWindow::sSnapshotDir[0] = '\0';

	mFastFrameTimer.stop();

	// create window
	mWindow = LLWindowManager::createWindow(
		title, name, x, y, width, height, 0,
		fullscreen, 
		gNoRender,
		gSavedSettings.getBOOL("DisableVerticalSync"),
		!gNoRender,
		ignore_pixel_depth);
#if LL_WINDOWS
	if (!LLWinDebug::setupExceptionHandler())
	{
		llwarns << " Someone took over my exception handler (post createWindow)!" << llendl;
	}
#endif

	if (NULL == mWindow)
	{
		LLSplashScreen::update("Shutting down...");
#if LL_LINUX
		llwarns << "Unable to create window, be sure screen is set at 32-bit color and your graphics driver is configured correctly.  See README-linux.txt for further information."
				<< llendl;
#else
		llwarns << "Unable to create window, be sure screen is set at 32-bit color in Control Panels->Display->Settings"
				<< llendl;
#endif
		app_force_exit(1);
	}
	
	// Get the real window rect the window was created with (since there are various OS-dependent reasons why
	// the size of a window or fullscreen context may have been adjusted slightly...)
	F32 ui_scale_factor = gSavedSettings.getF32("UIScaleFactor");
	
	mDisplayScale.setVec(llmax(1.f / mWindow->getPixelAspectRatio(), 1.f), llmax(mWindow->getPixelAspectRatio(), 1.f));
	mDisplayScale *= ui_scale_factor;
	LLUI::setScaleFactor(mDisplayScale);

	{
		LLCoordWindow size;
		mWindow->getSize(&size);
		mWindowRect.set(0, size.mY, size.mX, 0);
		mVirtualWindowRect.set(0, llround((F32)size.mY / mDisplayScale.mV[VY]), llround((F32)size.mX / mDisplayScale.mV[VX]), 0);
	}
	
	LLFontManager::initClass();

	//
	// We want to set this stuff up BEFORE we initialize the pipeline, so we can turn off
	// stuff like AGP if we think that it'll crash the viewer.
	//
	gFeatureManagerp->initGraphicsFeatureMasks();
	if (gFeatureManagerp->isSafe()
		|| (gSavedSettings.getS32("LastFeatureVersion") != gFeatureManagerp->getVersion()))
	{
		gFeatureManagerp->applyRecommendedFeatures();
	}

	S32 idx = gSavedSettings.getS32("GraphicsCardMemorySetting");
	// -1 indicates use default (max)
	if (idx == -1)
	{
		idx = LLViewerImageList::getMaxVideoRamSetting(-2); // get max recommended setting
		gSavedSettings.setS32("GraphicsCardMemorySetting", idx);
	}
	
	if (!gNoRender)
	{
		//
		// Initialize GL stuff
		//

		gPipeline.init();
		stop_glerror();
		initGLDefaults();
		LLViewerImage::initClass();
	}

	//
	// Done initing GL stuff.
	//

	// set callbacks
	mWindow->setCallbacks(this);

	// Init the image list.  Must happen after GL is initialized and before the images that
	// LLViewerWindow needs are requested.
	gImageList.init();
	gBumpImageList.init();

	// Create container for all sub-views
	mRootView = new LLRootView("root", mVirtualWindowRect, FALSE);
	mRootView->setRenderInFastFrame(FALSE);

	if (!gNoRender)
	{
		// Init default fonts
		initFonts();
	}

	// Init Resource Manager
	gResMgr = new LLResMgr();

	// Make avatar head look forward at start
	mCurrentMousePoint.mX = getWindowWidth() / 2;
	mCurrentMousePoint.mY = getWindowHeight() / 2;

	mPickBuffer = new U8[PICK_DIAMETER * PICK_DIAMETER * 4];

	gShowOverlayTitle = gSavedSettings.getBOOL("ShowOverlayTitle");
	mOverlayTitle = gSavedSettings.getString("OverlayTitle");
	// Can't have spaces in settings.ini strings, so use underscores instead and convert them.
	LLString::replaceChar(mOverlayTitle, '_', ' ');

	gAwayTimer.stop();

	LLAlertDialog::setDisplayCallback(alertCallback); // call this before calling any modal dialogs

	// sync the keyboard's setting with the saved setting
	gSavedSettings.getControl("NumpadControl")->firePropertyChanged();
}

void LLViewerWindow::initGLDefaults()
{
	//LLGLState::reset();
	//gGLSDefault.set();
	//LLGLState::verify(TRUE);

	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glColorMaterial( GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE );

	F32 ambient[4] = {0.f,0.f,0.f,0.f };
	F32 diffuse[4] = {1.f,1.f,1.f,1.f };
	glMaterialfv(GL_FRONT_AND_BACK,GL_AMBIENT,ambient);
	glMaterialfv(GL_FRONT_AND_BACK,GL_DIFFUSE,diffuse);
	
	glPixelStorei(GL_PACK_ALIGNMENT,1);
	glPixelStorei(GL_UNPACK_ALIGNMENT,1);

	// lights for objects
	glShadeModel( GL_SMOOTH );

	glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambient);

	glCullFace(GL_BACK);

	// RN: Need this for translation and stretch manip.
	gCone.prerender();
	gBox.prerender();
	gSphere.prerender();
	gCylinder.prerender();

	LLVOAvatar::initVertexPrograms();
}

void LLViewerWindow::initBase()
{
	S32 height = getWindowHeight();
	S32 width = getWindowWidth();

	LLRect full_window(0, height, width, 0);

	adjustRectanglesForFirstUse(full_window);

	////////////////////
	//
	// Set the gamma
	//

	F32 gamma = gSavedSettings.getF32("RenderGamma");
	if (gamma != 0.0f)
	{
		gViewerWindow->getWindow()->setGamma(gamma);
	}

	// Create global views

	// Create the floater view at the start so that other views can add children to it. 
	// (But wait to add it as a child of the root view so that it will be in front of the 
	// other views.)

	// Constrain floaters to inside the menu and status bar regions.
	LLRect floater_view_rect = full_window;
	// make space for menu bar if we have one
	floater_view_rect.mTop -= MENU_BAR_HEIGHT;
	floater_view_rect.mBottom += STATUS_BAR_HEIGHT + 12 + 16 + 2;

	// Check for non-first startup
	S32 floater_view_bottom = gSavedSettings.getS32("FloaterViewBottom");
	if (floater_view_bottom >= 0)
	{
		floater_view_rect.mBottom = floater_view_bottom;
	}
	gFloaterView = new LLFloaterView("Floater View", floater_view_rect );
	gFloaterView->setVisible(TRUE);

	gSnapshotFloaterView = new LLSnapshotFloaterView("Snapshot Floater View", full_window);
	gSnapshotFloaterView->setVisible(TRUE);

	// Console
	llassert( !gConsole );
	LLRect console_rect = full_window;
	console_rect.mTop    -= 24;
	console_rect.mBottom += STATUS_BAR_HEIGHT + 12 + 16 + 12;
	console_rect.mLeft   += 24; //gSavedSettings.getS32("StatusBarButtonWidth") + gSavedSettings.getS32("StatusBarPad");

	if (gSavedSettings.getBOOL("ChatFullWidth"))
	{
		console_rect.mRight -= 10;
	}
	else
	{
		// Make console rect somewhat narrow so having inventory open is
		// less of a problem.
		console_rect.mRight  = console_rect.mLeft + 2 * width / 3;
	}

	gConsole = new LLConsole(
		"console",
		gSavedSettings.getS32("ConsoleBufferSize"),
		console_rect,
		gSavedSettings.getS32("ChatFontSize"),
		gSavedSettings.getF32("ChatPersistTime") );
	gConsole->setFollows(FOLLOWS_LEFT | FOLLOWS_RIGHT | FOLLOWS_BOTTOM);
	mRootView->addChild(gConsole);

	// Debug view over the console
	gDebugView = new LLDebugView("gDebugView", full_window);
	gDebugView->setFollowsAll();
	gDebugView->setVisible(TRUE);
	mRootView->addChild(gDebugView);

	// HUD elements just below floaters
	LLRect hud_rect = full_window;
	hud_rect.mTop -= 24;
	hud_rect.mBottom += STATUS_BAR_HEIGHT;
	gHUDView = new LLHUDView("hud_view", hud_rect);
	gHUDView->setFollowsAll();
	mRootView->addChild(gHUDView);

	// Add floater view at the end so it will be on top, and give it tab priority over others
	mRootView->addChild(gFloaterView, -1);
	mRootView->addChild(gSnapshotFloaterView);

	// notify above floaters!
	LLRect notify_rect = full_window;
	//notify_rect.mTop -= 24;
	notify_rect.mBottom += STATUS_BAR_HEIGHT;
	gNotifyBoxView = new LLNotifyBoxView("notify", notify_rect, FALSE, FOLLOWS_ALL);
	mRootView->addChild(gNotifyBoxView, -2);

	// Tooltips go above floaters
	mToolTip = new LLTextBox( "tool tip", LLRect(0, 1, 1, 0 ) );
	mToolTip->setHPad( 4 );
	mToolTip->setVPad( 2 );
	mToolTip->setColor( gColors.getColor( "ToolTipTextColor" ) );
	mToolTip->setBorderColor( gColors.getColor( "ToolTipBorderColor" ) );
	mToolTip->setBorderVisible( FALSE );
	mToolTip->setBackgroundColor( gColors.getColor( "ToolTipBgColor" ) );
	mToolTip->setBackgroundVisible( TRUE );
	mToolTip->setDropshadowVisible( FALSE );
	mToolTip->setBorderDropshadowVisible( TRUE );
	mToolTip->setVisible( FALSE );

	// Add the progress bar view (startup view), which overrides everything
	mProgressView = new LLProgressView("ProgressView", full_window);
	mRootView->addChild(mProgressView);
	setShowProgress(FALSE);
	setProgressCancelButtonVisible(FALSE, "");
}


void adjust_rect_top_left(const LLString& control, const LLRect& window)
{
	LLRect r = gSavedSettings.getRect(control);
	if (r.mLeft == 0 && r.mBottom == 0)
	{
		r.setLeftTopAndSize(0, window.getHeight(), r.getWidth(), r.getHeight());
		gSavedSettings.setRect(control, r);
	}
}

void adjust_rect_top_right(const LLString& control, const LLRect& window)
{
	LLRect r = gSavedSettings.getRect(control);
	if (r.mLeft == 0 && r.mBottom == 0)
	{
		r.setLeftTopAndSize(window.getWidth() - r.getWidth(),
			window.getHeight(),
			r.getWidth(), 
			r.getHeight());
		gSavedSettings.setRect(control, r);
	}
}

void adjust_rect_bottom_center(const LLString& control, const LLRect& window)
{
	LLRect r = gSavedSettings.getRect(control);
	if (r.mLeft == 0 && r.mBottom == 0)
	{
		r.setOriginAndSize(
			window.getWidth()/2 - r.getWidth()/2,
			0,
			r.getWidth(),
			r.getHeight());
		gSavedSettings.setRect(control, r);
	}
}

// Many rectangles can't be placed until we know the screen size.
// These rectangles have their bottom-left corner as 0,0
void LLViewerWindow::adjustRectanglesForFirstUse(const LLRect& window)
{
	LLRect r;

	adjust_rect_bottom_center("FloaterMoveRect", window);

	adjust_rect_bottom_center("FloaterCameraRect", window);

	adjust_rect_top_left("FloaterCustomizeAppearanceRect", window);

	adjust_rect_top_left("FloaterLandRect5", window);

	adjust_rect_top_left("FloaterFindRect2", window);

	adjust_rect_top_left("FloaterGestureRect", window);

	adjust_rect_top_right("FloaterMapRect", window);

	adjust_rect_top_left("FloaterBuildOptionsRect", window);

	// bottom-right
	r = gSavedSettings.getRect("FloaterInventoryRect");
	if (r.mLeft == 0 && r.mBottom == 0)
	{
		r.setOriginAndSize(
			window.getWidth() - r.getWidth(),
			0,
			r.getWidth(),
			r.getHeight());
		gSavedSettings.setRect("FloaterInventoryRect", r);
	}
}


void LLViewerWindow::initWorldUI()
{
	pre_init_menus();

	S32 height = mRootView->getRect().getHeight();
	S32 width = mRootView->getRect().getWidth();
	LLRect full_window(0, height, width, 0);

	LLRect bar_rect(-1, STATUS_BAR_HEIGHT, width+1, -1);
	gToolBar = new LLToolBar("toolbar", bar_rect);

	LLRect chat_bar_rect(-1,CHAT_BAR_HEIGHT, width+1, -1);
	chat_bar_rect.translate(0, STATUS_BAR_HEIGHT-1);
	gChatBar = new LLChatBar("chat", chat_bar_rect);

	bar_rect.translate(0, STATUS_BAR_HEIGHT-1);
	bar_rect.translate(0, CHAT_BAR_HEIGHT-1);
	gOverlayBar = new LLOverlayBar("overlay", bar_rect);

	// panel containing chatbar, toolbar, and overlay, over floaters
	LLRect bottom_rect(-1, 2*STATUS_BAR_HEIGHT + CHAT_BAR_HEIGHT, width+1, -1);
	gBottomPanel = new LLBottomPanel("bottom panel", bottom_rect);

	// the order here is important
	gBottomPanel->addChild(gChatBar);
	gBottomPanel->addChild(gToolBar);
	gBottomPanel->addChild(gOverlayBar);
	mRootView->addChild(gBottomPanel);

	// View for hover information
	gHoverView = new LLHoverView("gHoverView", full_window);
	gHoverView->setVisible(TRUE);
	mRootView->addChild(gHoverView);

	//
	// Map
	//
	// TODO: Move instance management into class
	gFloaterMap = new LLFloaterMap("Map");
	gFloaterMap->setFollows(FOLLOWS_TOP|FOLLOWS_RIGHT);
	gFloaterMap->setVisible( gSavedSettings.getBOOL("ShowMiniMap") );

	// keep onscreen
	gFloaterView->adjustToFitScreen(gFloaterMap, FALSE);

	if (gSavedSettings.getBOOL("ShowCameraControls"))
	{
		LLFloaterCamera::show(NULL);
	}
	
	if (gSavedSettings.getBOOL("ShowMovementControls"))
	{
		LLFloaterMove::show(NULL);
	}
	
	// Must have one global chat floater so it can actually store
	// the history.  JC
	gFloaterChat = new LLFloaterChat();
	gFloaterChat->setVisible( FALSE );

	if ( gSavedPerAccountSettings.getBOOL("LogShowHistory") ) gFloaterChat->loadHistory();

	gIMView = new LLIMView("gIMView", LLRect() );
	gIMView->setFollowsAll();

	LLRect morph_view_rect = full_window;
	morph_view_rect.stretch( -STATUS_BAR_HEIGHT );
	morph_view_rect.mTop = full_window.mTop - 32;
	gMorphView = new LLMorphView("gMorphView", morph_view_rect );
	mRootView->addChild(gMorphView);
	gMorphView->setVisible(FALSE);

	gFloaterMute = new LLFloaterMute();
	gFloaterMute->setVisible(FALSE);

	LLWorldMapView::initClass();

	LLRect world_map_rect = gSavedSettings.getRect("FloaterWorldMapRect");
	// if 0,0,0,0 then use fullscreen
	if (world_map_rect.mTop == 0 
		&& world_map_rect.mLeft == 0
		&& world_map_rect.mRight == 0
		&& world_map_rect.mBottom == 0)
	{
		world_map_rect.set(0, height-TOOL_BAR_HEIGHT, width, STATUS_BAR_HEIGHT);
		world_map_rect.stretch(-4);
		gSavedSettings.setRect("FloaterWorldMapRect", world_map_rect);
	}
	gFloaterWorldMap = new LLFloaterWorldMap();
	gFloaterWorldMap->setVisible(FALSE);

	//
	// Tools for building
	//

	// Toolbox floater
	init_menus();

	gFloaterTools = new LLFloaterTools();
	gFloaterTools->setVisible(FALSE);

	// Status bar
	//FIXME change this back
	S32 menu_bar_height = gMenuBarView->getRect().getHeight();
	LLRect root_rect = gViewerWindow->getRootView()->getRect();
	LLRect status_rect(0, root_rect.getHeight(), root_rect.getWidth(), root_rect.getHeight() - menu_bar_height);
	gStatusBar = new LLStatusBar("status", status_rect);
	gStatusBar->setFollows(FOLLOWS_LEFT | FOLLOWS_RIGHT | FOLLOWS_TOP);

	gStatusBar->reshape(root_rect.getWidth(), gStatusBar->getRect().getHeight(), TRUE);
	gStatusBar->translate(0, root_rect.getHeight() - gStatusBar->getRect().getHeight());

	gViewerWindow->getRootView()->addChild(gStatusBar);
}


LLViewerWindow::~LLViewerWindow()
{
	gSavedSettings.setS32("FloaterViewBottom", gFloaterView->getRect().mBottom);

	// Cleanup global views
	if (gMorphView)
	{
		gMorphView->setVisible(FALSE);
	}
	
	// Delete all child views.
	delete mRootView;
	mRootView = NULL;

	// Automatically deleted as children of mRootView.  Fix the globals.
	gFloaterTools = NULL;
	gStatusBar = NULL;
	gFloaterCamera = NULL;
	gIMView = NULL;
	gHoverView = NULL;

	gFloaterView		= NULL;
	gMorphView			= NULL;

	gFloaterChat = NULL;
	gFloaterMute = NULL;

	gFloaterMap	= NULL;
	gHUDView = NULL;

	gNotifyBoxView = NULL;

	delete mToolTip;
	mToolTip = NULL;

	delete gResMgr;
	gResMgr = NULL;
	
	//--------------------------------------------------------
	// Shutdown GL cleanly.  Order is very important here.
	//--------------------------------------------------------
	LLFontGL::destroyDefaultFonts();
	LLFontManager::cleanupClass();
	stop_glerror();

	gSky.cleanup();
	stop_glerror();

	gImageList.shutdown();
	stop_glerror();

	gBumpImageList.shutdown();
	stop_glerror();

	LLWorldMapView::cleanupTextures();

	LLViewerImage::cleanupClass();
	
	delete[] mPickBuffer;
	mPickBuffer = NULL;

	llinfos << "Stopping GL during shutdown" << llendl;
	if (!gNoRender)
	{
		stopGL(FALSE);
		stop_glerror();
	}

	if (gSelectMgr)
	{
		llinfos << "Cleaning up select manager" << llendl;
		gSelectMgr->cleanup();
	}

	llinfos << "Cleaning up pipeline" << llendl;
	gPipeline.cleanup();
	stop_glerror();
	llinfos << "Destroying Window" << llendl;
	destroyWindow();
}


void LLViewerWindow::setCursor( ECursorType c )
{
	mWindow->setCursor( c );
}

void LLViewerWindow::showCursor()
{
	mWindow->showCursor();
}

void LLViewerWindow::hideCursor()
{
	// Hide tooltips
	if(mToolTip ) mToolTip->setVisible( FALSE );

	// Also hide hover info
	if (gHoverView)	gHoverView->cancelHover();

	// And hide the cursor
	mWindow->hideCursor();
}

void LLViewerWindow::sendShapeToSim()
{
	LLMessageSystem* msg = gMessageSystem;
	if(!msg) return;
	msg->newMessageFast(_PREHASH_AgentHeightWidth);
	msg->nextBlockFast(_PREHASH_AgentData);
	msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID());
	msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
	msg->addU32Fast(_PREHASH_CircuitCode, gMessageSystem->mOurCircuitCode);
	msg->nextBlockFast(_PREHASH_HeightWidthBlock);
	msg->addU32Fast(_PREHASH_GenCounter, 0);
	U16 height16 = (U16) mWindowRect.getHeight();
	U16 width16 = (U16) mWindowRect.getWidth();
	msg->addU16Fast(_PREHASH_Height, height16);
	msg->addU16Fast(_PREHASH_Width, width16);
	gAgent.sendReliableMessage();
}

// Must be called after window is created to set up agent
// camera variables and UI variables.
void LLViewerWindow::reshape(S32 width, S32 height)
{
	// Destroying the window at quit time generates spurious
	// reshape messages.  We don't care about these, and we
	// don't want to send messages because the message system
	// may have been destructed.
	if (!gQuit)
	{
		if (gNoRender)
		{
			return;
		}

		glViewport(0, 0, width, height );

		if (height > 0 && gCamera)
		{
			gCamera->setViewHeightInPixels( height );
			if (mWindow->getFullscreen())
			{
				// force to 4:3 aspect for odd resolutions
				gCamera->setAspect( getDisplayAspectRatio() );
			}
			else
			{
				gCamera->setAspect( width / (F32) height);
			}
		}

		// update our window rectangle
		mWindowRect.mRight = mWindowRect.mLeft + width;
		mWindowRect.mTop = mWindowRect.mBottom + height;
		calcDisplayScale();
	
		BOOL display_scale_changed = mDisplayScale != LLUI::sGLScaleFactor;
		LLUI::setScaleFactor(mDisplayScale);

		// update our window rectangle
		mVirtualWindowRect.mRight = mVirtualWindowRect.mLeft + llround((F32)width / mDisplayScale.mV[VX]);
		mVirtualWindowRect.mTop = mVirtualWindowRect.mBottom + llround((F32)height / mDisplayScale.mV[VY]);

		setupViewport();

		// Inform lower views of the change
		// round up when converting coordinates to make sure there are no gaps at edge of window
		LLView::sForceReshape = display_scale_changed;
		mRootView->reshape(llceil((F32)width / mDisplayScale.mV[VX]), llceil((F32)height / mDisplayScale.mV[VY]));
		LLView::sForceReshape = FALSE;

		// clear font width caches
		if (display_scale_changed)
		{
			LLHUDText::reshape();
		}

		sendShapeToSim();


		// store the mode the user wants (even if not there yet)
		gSavedSettings.setBOOL("FullScreen", mWantFullscreen);

		// store new settings for the mode we are in, regardless
		if (mWindow->getFullscreen())
		{
			gSavedSettings.setS32("FullScreenWidth", width);
			gSavedSettings.setS32("FullScreenHeight", height);
		}
		else
		{
			// Only save size if not maximized
			BOOL maximized = mWindow->getMaximized();
			gSavedSettings.setBOOL("WindowMaximized", maximized);

			LLCoordScreen window_size;
			if (!maximized
				&& mWindow->getSize(&window_size))
			{
				gSavedSettings.setS32("WindowWidth", window_size.mX);
				gSavedSettings.setS32("WindowHeight", window_size.mY);
			}
		}

		gViewerStats->setStat(LLViewerStats::ST_WINDOW_WIDTH, (F64)width);
		gViewerStats->setStat(LLViewerStats::ST_WINDOW_HEIGHT, (F64)height);
	}
}


void LLViewerWindow::draw()
{
#if LL_DEBUG
	LLView::sIsDrawing = TRUE;
#endif
	stop_glerror();
	
	LLUI::setLineWidth(1.f);
	//popup alerts from the UI
	LLAlertInfo alert;
	while (LLPanel::nextAlert(alert))
	{
		alertXml(alert.mLabel, alert.mArgs);
	}

	LLUI::setLineWidth(1.f);
	// Reset any left-over transforms
	glMatrixMode(GL_MODELVIEW);
	
	glLoadIdentity();

	//S32 screen_x, screen_y;

	// HACK for timecode debugging
	if (gSavedSettings.getBOOL("DisplayTimecode"))
	{
		// draw timecode block
		char text[256];

		glLoadIdentity();

		microsecondsToTimecodeString(gFrameTime,text);
		const LLFontGL* font = gResMgr->getRes( LLFONT_SANSSERIF );
		font->renderUTF8(text, 0,
						llround((getWindowWidth()/2)-100.f),
						llround((getWindowHeight()-60.f)),
			LLColor4( 1.f, 1.f, 1.f, 1.f ),
			LLFontGL::LEFT, LLFontGL::TOP);
	}

	// Draw all nested UI views.
	// No translation needed, this view is glued to 0,0

	glPushMatrix();
	{
		// scale view by UI global scale factor and aspect ratio correction factor
		glScalef(mDisplayScale.mV[VX], mDisplayScale.mV[VY], 1.f);

		LLVector2 old_scale_factor = LLUI::sGLScaleFactor;
		if (gCamera)
		{
			// apply camera zoom transform (for high res screenshots)
			F32 zoom_factor = gCamera->getZoomFactor();
			S16 sub_region = gCamera->getZoomSubRegion();
			if (zoom_factor > 1.f)
			{
				//decompose subregion number to x and y values
				int pos_y = sub_region / llceil(zoom_factor);
				int pos_x = sub_region - (pos_y*llceil(zoom_factor));
				// offset for this tile
				glTranslatef((F32)gViewerWindow->getWindowWidth() * -(F32)pos_x, 
							(F32)gViewerWindow->getWindowHeight() * -(F32)pos_y, 
							0.f);
				glScalef(zoom_factor, zoom_factor, 1.f);
				LLUI::sGLScaleFactor *= zoom_factor;
			}
		}

		{
			LLGLSTexture gls_texture;
			show_text_gl();
		}
		
		if (gToolMgr)
		{
			// Draw tool specific overlay on world
			gToolMgr->getCurrentTool( gKeyboard->currentMask(TRUE) )->draw();
		}

		if( gAgent.cameraMouselook() )
		{
			drawMouselookInstructions();
				stop_glerror();
		}

		// Draw all nested UI views.
		// No translation needed, this view is glued to 0,0
		mRootView->draw();

		// Draw optional on-top-of-everyone view
		LLView* top_view = gFocusMgr.getTopView();
		if (top_view && top_view->getVisible())
		{
			S32 screen_x, screen_y;
			top_view->localPointToScreen(0, 0, &screen_x, &screen_y);

			glMatrixMode(GL_MODELVIEW);
			LLUI::pushMatrix();
			LLUI::translate( (F32) screen_x, (F32) screen_y, 0.f);
			top_view->draw();	
			LLUI::popMatrix();
		}

		// Draw tooltips
		// Adjust their rectangle so they don't go off the top or bottom
		// of the screen.
		if( mToolTip && mToolTip->getVisible() )
		{
			glMatrixMode(GL_MODELVIEW);
			LLUI::pushMatrix();
			{
				S32 tip_height = mToolTip->getRect().getHeight();

				S32 screen_x, screen_y;
				mToolTip->localPointToScreen(0, -24 - tip_height, 
											 &screen_x, &screen_y);

				// If tooltip would draw off the bottom of the screen,
				// show it from the cursor tip position.
				if (screen_y < tip_height) 
				{
					mToolTip->localPointToScreen(0, 0, &screen_x, &screen_y);
				}
				LLUI::translate( (F32) screen_x, (F32) screen_y, 0);
				mToolTip->draw();
			}
			LLUI::popMatrix();
		}

		if( gShowOverlayTitle && !mOverlayTitle.empty() )
		{
			// Used for special titles such as "Second Life - Special E3 2003 Beta"
			const S32 DIST_FROM_TOP = 20;
			LLGLSTexture gls_texture;
			LLFontGL::sSansSerifBig->renderUTF8(
				mOverlayTitle, 0,
				llround( gViewerWindow->getWindowWidth() * 0.5f),
				gViewerWindow->getWindowHeight() - DIST_FROM_TOP,
				LLColor4(1, 1, 1, 0.4f),
				LLFontGL::HCENTER, LLFontGL::TOP);
		}

		LLUI::sGLScaleFactor = old_scale_factor;
	}
	glPopMatrix();


#if LL_DEBUG
	LLView::sIsDrawing = FALSE;
#endif
}

// Takes a single keydown event, usually when UI is visible
BOOL LLViewerWindow::handleKey(KEY key, MASK mask)
{
	if (gFocusMgr.getKeyboardFocus() && !(mask &	 (MASK_CONTROL | MASK_ALT)))
	{
		// We have keyboard focus, and it's not an accelerator

		if (key < 0x80)
		{
			// Not a special key, so likely (we hope) to generate a character.  Let it fall through to character handler first.
			return gFocusMgr.childHasKeyboardFocus(mRootView);
		}
	}

	// HACK look for UI editing keys
	if (LLView::sEditingUI)
	{
		if (LLFloaterEditUI::handleKey(key, mask))
		{
			return TRUE;
		}
	}

	// Hide tooltips on keypress
	if(mToolTip )
	{
		mToolTipBlocked = TRUE; // block until next time mouse is moved
		mToolTip->setVisible( FALSE );
	}

	// Also hide hover info on keypress
	if (gHoverView)
	{
		gHoverView->cancelHover();

		gHoverView->setTyping(TRUE);
	}

	// Explicit hack for debug menu.
	if ((MASK_ALT & mask) &&
		(MASK_CONTROL & mask) &&
		('D' == key || 'd' == key))
	{
		toggle_debug_menus(NULL);
	}

	// Example "bug" for bug reporter web page
	if ((MASK_SHIFT & mask) 
		&& (MASK_ALT & mask)
		&& (MASK_CONTROL & mask)
		&& ('H' == key || 'h' == key))
	{
		trigger_hippo_bug(NULL);
	}

	// handle escape key
	if (key == KEY_ESCAPE && mask == MASK_NONE)
	{
		if (gMenuHolder && gMenuHolder->hideMenus())
		{
			return TRUE;
		}
		//FIXME: get this to play well with mouselook and hidden cursor modes, etc.
		//if (gFocusMgr.getMouseCapture())
		//{
		//	gFocusMgr.setMouseCapture(NULL, NULL);
		//	return TRUE;
		//}
	}

	// let menus handle navigation keys
	if (gMenuBarView && gMenuBarView->handleKey(key, mask, TRUE))
	{
		return TRUE;
	}

	// Traverses up the hierarchy
	LLUICtrl* keyboard_focus = gFocusMgr.getKeyboardFocus();
	if( keyboard_focus )
	{
		// arrow keys move avatar while chatting hack
		if (gChatBar && gChatBar->inputEditorHasFocus())
		{
			if (gChatBar->getCurrentChat().empty() || gSavedSettings.getBOOL("ArrowKeysMoveAvatar"))
			{
				switch(key)
				{
				case KEY_LEFT:
				case KEY_RIGHT:
				case KEY_UP:
				case KEY_DOWN:
				case KEY_PAGE_UP:
				case KEY_PAGE_DOWN:
				case KEY_HOME:
					// when chatbar is empty or ArrowKeysMoveAvatar set, pass arrow keys on to avatar...
					return FALSE;
				default:
					break;
				}
			}
		}

		if (keyboard_focus->handleKey(key, mask, FALSE))
		{
			return TRUE;
		}
	}

	if (gToolMgr)
	{
		if( gToolMgr->getCurrentTool(mask)->handleKey(key, mask) )
		{
			return TRUE;
		}
	}

	// Try for a new-format gesture
	if (gGestureManager.triggerGesture(key, mask))
	{
		return TRUE;
	}

	// See if this is a gesture trigger.  If so, eat the key and
	// don't pass it down to the menus.
	if (gGestureList.trigger(key, mask))
	{
		return TRUE;
	}

	// Topmost view gets a chance before the hierarchy
	//FIXME: get rid of this?
	LLView* top_view = gFocusMgr.getTopView();
	if (top_view)
	{
		if( top_view->handleKey( key, mask, TRUE ) )
		{
			return TRUE;
		}
	}

	// give floaters first chance to handle TAB key
	// so frontmost floater gets focus
	if (key == KEY_TAB)
	{
		// if nothing has focus, go to first or last UI element as appropriate
		if (mask & MASK_CONTROL || gFocusMgr.getKeyboardFocus() == NULL)
		{
			if (gMenuHolder) gMenuHolder->hideMenus();

			// if CTRL-tabbing (and not just TAB with no focus), go into window cycle mode
			gFloaterView->setCycleMode((mask & MASK_CONTROL) != 0);

			// do CTRL-TAB and CTRL-SHIFT-TAB logic
			if (mask & MASK_SHIFT)
			{
				mRootView->focusPrevRoot();
			}
			else
			{
				mRootView->focusNextRoot();
			}
			return TRUE;
		}
	}
	
	// give menus a chance to handle keys
	if (gMenuBarView && gMenuBarView->handleAcceleratorKey(key, mask))
	{
		return TRUE;
	}

	// don't pass keys on to world when something in ui has focus
	return gFocusMgr.childHasKeyboardFocus(mRootView);
}


BOOL LLViewerWindow::handleUnicodeChar(llwchar uni_char, MASK mask)
{
	// HACK:  We delay processing of return keys until they arrive as a Unicode char,
	// so that if you're typing chat text at low frame rate, we don't send the chat
	// until all keystrokes have been entered. JC
	// HACK: Numeric keypad <enter> on Mac is Unicode 3
	// HACK: Control-M on Windows is Unicode 13
	if ((uni_char == 13 && mask != MASK_CONTROL)
		|| (uni_char == 3 && mask == MASK_NONE))
	{
		return gViewerKeyboard.handleKey(KEY_RETURN, mask, gKeyboard->getKeyRepeated(KEY_RETURN));
	}

	// Traverses up the hierarchy
	LLView* keyboard_focus = gFocusMgr.getKeyboardFocus();
	if( keyboard_focus )
	{
		if (keyboard_focus->handleUnicodeChar(uni_char, FALSE))
		{
			return TRUE;
		}

		// Topmost view gets a chance before the hierarchy
		LLView* top_view = gFocusMgr.getTopView();
		if (top_view && top_view->handleUnicodeChar( uni_char, FALSE ) )
		{
			return TRUE;
		}

		return TRUE;
	}

	return FALSE;
}


void LLViewerWindow::handleScrollWheel(S32 clicks)
{
	gMouseIdleTimer.reset();

	// Hide tooltips
	if( mToolTip )
	{
		mToolTip->setVisible( FALSE );
	}

	LLMouseHandler* mouse_captor = gFocusMgr.getMouseCapture();
	if( mouse_captor )
	{
		S32 local_x;
		S32 local_y;
		mouse_captor->screenPointToLocal( mCurrentMousePoint.mX, mCurrentMousePoint.mY, &local_x, &local_y );
		mouse_captor->handleScrollWheel(local_x, local_y, clicks);
		if (LLView::sDebugMouseHandling)
		{
			llinfos << "Scroll Wheel handled by captor " << mouse_captor->getName() << llendl;
		}
		return;
	}

	LLView* top_view = gFocusMgr.getTopView();
	if (top_view)
	{
		S32 local_x;
		S32 local_y;
		top_view->screenPointToLocal( mCurrentMousePoint.mX, mCurrentMousePoint.mY, &local_x, &local_y );
		if (top_view->handleScrollWheel(local_x, local_y, clicks)) return;
	}

	if (mRootView->handleScrollWheel(mCurrentMousePoint.mX, mCurrentMousePoint.mY, clicks) )
	{
		if (LLView::sDebugMouseHandling)
		{
			llinfos << "Scroll Wheel" << LLView::sMouseHandlerMessage << llendl;
			LLView::sMouseHandlerMessage = "";
		}
		return;
	}
	else if (LLView::sDebugMouseHandling)
	{
		llinfos << "Scroll Wheel not handled by view" << llendl;
	}

	if (gWorldPointer)
	{
		// Zoom the camera in and out behavior
		gAgent.handleScrollWheel(clicks);
	}

	return;
}

void LLViewerWindow::moveCursorToCenter()
{
#if 1 // old version

#if 0 // Dave's changes - this reportedly is making the drift worse on some systems?
	S32 x = llround((F32) mVirtualWindowRect.getWidth() / 2);
	S32 y = llround((F32) mVirtualWindowRect.getHeight() / 2);
#else
	S32 x = mVirtualWindowRect.getWidth() / 2;
	S32 y = mVirtualWindowRect.getHeight() / 2;
#endif
	
	//on a forced move, all deltas get zeroed out to prevent jumping
	mCurrentMousePoint.set(x,y);
	mLastMousePoint.set(x,y);
	mCurrentMouseDelta.set(0,0);	

	LLUI::setCursorPositionScreen(x, y);
	
#else // Richard's version - fails on intel macs

	S32 x = llround((F32) mWindowRect.getWidth() / 2);
	S32 y = llround((F32) mWindowRect.getHeight() / 2);
	
	LLCoordWindow window_point;
	mWindow->convertCoords(LLCoordGL(x, y), &window_point);
	mWindow->setCursorPosition(window_point);
	
	// read back cursor position
	mWindow->getCursorPosition(&window_point);
	LLCoordGL new_mouse_pos;
	mWindow->convertCoords(window_point, &new_mouse_pos);
	new_mouse_pos.mX = llround((F32)new_mouse_pos.mX / mDisplayScale.mV[VX]);
	new_mouse_pos.mY = llround((F32)new_mouse_pos.mY / mDisplayScale.mV[VY]);

	//on a forced move, all deltas get zeroed out to prevent jumping
	mCurrentMousePoint = new_mouse_pos;
	mLastMousePoint = new_mouse_pos;
	mCurrentMouseDelta.set(0,0);
#endif
}

//////////////////////////////////////////////////////////////////////
//
// Hover handlers
//

// Update UI based on stored mouse position from mouse-move
// event processing.
BOOL LLViewerWindow::handlePerFrameHover()
{
	static std::string last_handle_msg;

	//RN: fix for asynchronous notification of mouse leaving window not working
	LLCoordWindow mouse_pos;
	mWindow->getCursorPosition(&mouse_pos);
	if (mouse_pos.mX < 0 || 
		mouse_pos.mY < 0 ||
		mouse_pos.mX > mWindowRect.getWidth() ||
		mouse_pos.mY > mWindowRect.getHeight())
	{
		mMouseInWindow = FALSE;
	}
	else
	{
		mMouseInWindow = TRUE;
	}

	S32 dx = mCurrentMousePoint.mX - mLastMousePoint.mX;
	S32 dy = mCurrentMousePoint.mY - mLastMousePoint.mY;
	mCurrentMouseDelta.set(dx,dy);
	LLVector2 mouse_vel((F32)dx, (F32)dy);
	mMouseVelocityStat.addValue(mouse_vel.magVec());

	if (gNoRender)
	{
		return TRUE;
	}

	S32 x = mCurrentMousePoint.mX;
	S32 y = mCurrentMousePoint.mY;
	MASK mask = gKeyboard->currentMask(TRUE);

	// clean up current focus
	LLUICtrl* cur_focus = gFocusMgr.getKeyboardFocus();
	if (cur_focus)
	{
		if (!cur_focus->isInVisibleChain() || !cur_focus->isInEnabledChain())
		{
			gFocusMgr.releaseFocusIfNeeded(cur_focus);

			LLView* parent = cur_focus->getParent();
			LLView* focus_root = cur_focus->findRootMostFocusRoot();
			while(parent)
			{
				if (parent->isCtrl() && 
					(((LLUICtrl*)parent)->hasTabStop() || parent == focus_root) && 
					!((LLUICtrl*)parent)->getIsChrome() && 
					parent->isInVisibleChain() && 
					parent->isInEnabledChain())
				{
					if (!parent->focusFirstItem())
					{
						((LLUICtrl*)parent)->setFocus(TRUE);
					}
					break;
				}
				parent = parent->getParent();
			}
		}
		else if (cur_focus->isFocusRoot())
		{
			// focus roots keep trying to delegate focus to their first valid descendant
			// this assumes that focus roots are not valid focus holders on their own
			cur_focus->focusFirstItem();
		}
	}

	// Show joints while in edit mode and hold down alt key.
	if (gHUDManager)
	{
		if (gSavedSettings.getBOOL("AltShowsPhysical")
			|| (gFloaterTools && gFloaterTools->getVisible()))
		{
			gHUDManager->toggleShowPhysical( mask & MASK_ALT );
		}
	}

	BOOL handled = FALSE;

	BOOL handled_by_top_view = FALSE;
	LLView* top_view = gFocusMgr.getTopView();

	LLMouseHandler* mouse_captor = gFocusMgr.getMouseCapture();
	if( mouse_captor )
	{
		// Pass hover events to object capturing mouse events.
		S32 local_x;
		S32 local_y; 
		mouse_captor->screenPointToLocal( x, y, &local_x, &local_y );
		handled = mouse_captor->handleHover(local_x, local_y, mask);
		if (LLView::sDebugMouseHandling)
		{
			llinfos << "Hover handled by captor " << mouse_captor->getName() << llendl;
		}

		if( !handled )
		{
			lldebugst(LLERR_USER_INPUT) << "hover not handled by mouse captor" << llendl;
		}
	}
	else
	{
		if (top_view)
		{
			S32 local_x, local_y;
			top_view->screenPointToLocal( x, y, &local_x, &local_y );
			handled = top_view->pointInView(local_x, local_y) && top_view->handleHover(local_x, local_y, mask);
			handled_by_top_view = TRUE;
		}

		if ( !handled )
		{
			// x and y are from last time mouse was in window
			// mMouseInWindow tracks *actual* mouse location
			if (mMouseInWindow && mRootView->handleHover(x, y, mask) )
			{
				if (LLView::sDebugMouseHandling && LLView::sMouseHandlerMessage != last_handle_msg)
				{
					last_handle_msg = LLView::sMouseHandlerMessage;
					llinfos << "Hover" << LLView::sMouseHandlerMessage << llendl;
				}
				LLView::sMouseHandlerMessage = "";
				handled = TRUE;
			}
			else if (LLView::sDebugMouseHandling)
			{
				if (last_handle_msg != "")
				{
					last_handle_msg = "";
					llinfos << "Hover not handled by view" << llendl;
				}
			}
		}

		if( !handled )
		{
			lldebugst(LLERR_USER_INPUT) << "hover not handled by top view or root" << llendl;		
		}
	}

	//FIXME: sometimes tools handle the mouse as a captor, so this
	// logic is a little confusing
	LLTool *tool = NULL;
	if (gToolMgr && gHoverView)
	{
		tool = gToolMgr->getCurrentTool(mask);

		if(!handled && tool)
		{
			handled = tool->handleHover(x, y, mask);

			if (!mWindow->isCursorHidden())
			{
				gHoverView->updateHover(tool);
			}
		}
		else
		{
			// Cancel hovering if any UI element handled the event.
			gHoverView->cancelHover();
		}

		// Suppress the toolbox view if our source tool was the pie tool,
		// and we've overridden to something else.
		mSuppressToolbox = 
			(gToolMgr->getCurrentTool(MASK_NONE) == gToolPie) &&
			(gToolMgr->getCurrentTool(mask) != gToolPie);

	}

	//llinfos << (mToolTipBlocked ? "BLOCKED" : "NOT BLOCKED") << llendl;
	// Show a new tool tip (or update one that is alrady shown)
	BOOL tool_tip_handled = FALSE;
	LLString tool_tip_msg;
	F32 tooltip_delay = gSavedSettings.getF32( "ToolTipDelay" );
	//HACK: hack for tool-based tooltips which need to pop up more quickly
	//Also for show xui names as tooltips debug mode
	if ((mouse_captor && !mouse_captor->isView()) || LLUI::sShowXUINames)
	{
		tooltip_delay = gSavedSettings.getF32( "DragAndDropToolTipDelay" );
	}
	if( handled && 
		!mToolTipBlocked &&
		(gMouseIdleTimer.getElapsedTimeF32() > tooltip_delay) &&
		!mWindow->isCursorHidden() )
	{
		LLRect screen_sticky_rect;

		if (mouse_captor)
		{
			S32 local_x, local_y;
			mouse_captor->screenPointToLocal( x, y, &local_x, &local_y );
			tool_tip_handled = mouse_captor->handleToolTip( local_x, local_y, tool_tip_msg, &screen_sticky_rect );
		}
		else if (handled_by_top_view)
		{
			S32 local_x, local_y;
			top_view->screenPointToLocal( x, y, &local_x, &local_y );
			tool_tip_handled = top_view->handleToolTip( local_x, local_y, tool_tip_msg, &screen_sticky_rect );
		}
		else
		{
			tool_tip_handled = mRootView->handleToolTip(x, y, tool_tip_msg, &screen_sticky_rect );
		}

		if( tool_tip_handled && !tool_tip_msg.empty() )
		{
			mToolTipStickyRect = screen_sticky_rect;
			mToolTip->setWrappedText( tool_tip_msg, 200 );
			mToolTip->reshapeToFitText();
			mToolTip->setOrigin( x, y );
			LLRect virtual_window_rect(0, getWindowHeight(), getWindowWidth(), 0);
			mToolTip->translateIntoRect( virtual_window_rect, FALSE );
			mToolTip->setVisible( TRUE );
		}
	}		

	if (tool != gToolNull && tool != gToolDragAndDrop && !gSavedSettings.getBOOL("FreezeTime"))
	{ 
		LLMouseHandler *captor = gFocusMgr.getMouseCapture();
		// With the null tool or drag and drop tool, don't muck
		// with visibility.

		if (gFloaterTools->isMinimized() ||
			(tool != gToolPie						// not default tool
			&& tool != gToolGun						// not coming out of mouselook
			&& !mSuppressToolbox					// not override in third person
			&& gCurrentToolset != gFaceEditToolset	// not special mode
			&& gCurrentToolset != gMouselookToolset
			&& (!captor || captor->isView()))		// not dragging
			)			
		{
			// Force floater tools to be visible (unless minimized)
			if (!gFloaterTools->isMinimized() && !gFloaterTools->getVisible())
			{
				gFloaterTools->open();
			}
			// Update the location of the blue box tool popup
			LLCoordGL select_center_screen;
			gFloaterTools->updatePopup( select_center_screen, mask );
		}
		else
		{
			gFloaterTools->setVisible(FALSE);
		}
	}
	if (gToolBar)
	{
		gToolBar->refresh();
	}

	if (gChatBar)
	{
		gChatBar->refresh();
	}

	if (gOverlayBar)
	{
		gOverlayBar->refresh();
	}

	// Update rectangles for the various toolbars
	if (gToolBar && gChatBar && gOverlayBar && gNotifyBoxView && gConsole)
	{
		LLRect bar_rect(-1, STATUS_BAR_HEIGHT, getWindowWidth()+1, -1);
		if (gToolBar->getVisible())
		{
			gToolBar->setRect(bar_rect);
			bar_rect.translate(0, STATUS_BAR_HEIGHT-1);
		}

		if (gChatBar->getVisible())
		{
			// fix up the height
			LLRect chat_bar_rect = bar_rect;
			chat_bar_rect.mTop = chat_bar_rect.mBottom + CHAT_BAR_HEIGHT + 1;
			gChatBar->setRect(chat_bar_rect);
			bar_rect.translate(0, CHAT_BAR_HEIGHT-1);
		}

		LLRect notify_box_rect = gNotifyBoxView->getRect();
		notify_box_rect.mBottom = bar_rect.mBottom;
		gNotifyBoxView->reshape(notify_box_rect.getWidth(), notify_box_rect.getHeight());
		gNotifyBoxView->setRect(notify_box_rect);

		// make sure floaters snap to visible rect by adjusting floater view rect
		LLRect floater_rect = gFloaterView->getRect();
		if (floater_rect.mBottom != bar_rect.mBottom+1)
		{
			floater_rect.mBottom = bar_rect.mBottom+1;
			// Don't bounce the floaters up and down.
			gFloaterView->reshape(floater_rect.getWidth(), floater_rect.getHeight(), 
									TRUE, ADJUST_VERTICAL_NO);
			gFloaterView->setRect(floater_rect);
		}

		if (gOverlayBar->getVisible())
		{
			LLRect overlay_rect = bar_rect;
			overlay_rect.mTop = overlay_rect.mBottom + OVERLAY_BAR_HEIGHT;

			// Fitt's Law: Push buttons flush with bottom of screen if
			// nothing else visible.
			if (!gToolBar->getVisible()
				&& !gChatBar->getVisible())
			{
				// FIXME: this is highly depenent on the XML describing the position of the buttons
				overlay_rect.translate(0, 0);
			}

			gOverlayBar->setRect(overlay_rect);
			gOverlayBar->updateRect();
			bar_rect.translate(0, gOverlayBar->getRect().getHeight());

			gFloaterView->setSnapOffsetBottom(OVERLAY_BAR_HEIGHT);
		}
		else
		{
			gFloaterView->setSnapOffsetBottom(0);
		}

		// fix rectangle of bottom panel focus indicator
		if(gBottomPanel && gBottomPanel->getFocusIndicator())
		{
			LLRect focus_rect = gBottomPanel->getFocusIndicator()->getRect();
			focus_rect.mTop = (gToolBar->getVisible() ? STATUS_BAR_HEIGHT : 0) + 
				(gChatBar->getVisible() ? CHAT_BAR_HEIGHT : 0) - 2;
			gBottomPanel->getFocusIndicator()->setRect(focus_rect);
		}

		// Always update console
		LLRect console_rect = gConsole->getRect();
		console_rect.mBottom = bar_rect.mBottom + 8;
		gConsole->reshape(console_rect.getWidth(), console_rect.getHeight());
		gConsole->setRect(console_rect);
	}

	mLastMousePoint = mCurrentMousePoint;

	// last ditch force of edit menu to selection manager
	if (gEditMenuHandler == NULL && gSelectMgr && gSelectMgr->getObjectCount())
	{
		gEditMenuHandler = gSelectMgr;
	}

	if (gFloaterView->getCycleMode())
	{
		// sync all floaters with their focus state
		gFloaterView->highlightFocusedFloater();
		gSnapshotFloaterView->highlightFocusedFloater();
		if ((gKeyboard->currentMask(TRUE) & MASK_CONTROL) == 0)
		{
			// control key no longer held down, finish cycle mode
			gFloaterView->setCycleMode(FALSE);

			gFloaterView->syncFloaterTabOrder();
		}
		else
		{
			// user holding down CTRL, don't update tab order of floaters
		}
	}
	else
	{
		// update focused floater
		gFloaterView->highlightFocusedFloater();
		gSnapshotFloaterView->highlightFocusedFloater();
		// make sure floater visible order is in sync with tab order
		gFloaterView->syncFloaterTabOrder();
	}

	if (gSavedSettings.getBOOL("ChatBarStealsFocus") && gChatBar && gFocusMgr.getKeyboardFocus() == NULL && gChatBar->getVisible())
	{
		gChatBar->startChat(NULL);
	}

	// sync land selection with edit and about land dialogs
	if (gParcelMgr && !LLFloaterLand::floaterVisible() && (!gFloaterTools || !gFloaterTools->getVisible()))
	{
		gParcelMgr->deselectLand();
	}

	return handled;
}


void LLViewerWindow::saveLastMouse(const LLCoordGL &point)
{
	// Store last mouse location.
	// If mouse leaves window, pretend last point was on edge of window
	if (point.mX < 0)
	{
		mCurrentMousePoint.mX = 0;
	}
	else if (point.mX > getWindowWidth())
	{
		mCurrentMousePoint.mX = getWindowWidth();
	}
	else
	{
		mCurrentMousePoint.mX = point.mX;
	}

	if (point.mY < 0)
	{
		mCurrentMousePoint.mY = 0;
	}
	else if (point.mY > getWindowHeight() )
	{
		mCurrentMousePoint.mY = getWindowHeight();
	}
	else
	{
		mCurrentMousePoint.mY = point.mY;
	}
}


// Draws the selection outlines for the currently selected objects
// Must be called after displayObjects is called, which sets the mGLName parameter
// NOTE: This function gets called 3 times:
//  render_ui_3d: 			FALSE, FALSE, TRUE
//  renderObjectsForSelect:	TRUE, pick_parcel_wall, FALSE
//  render_hud_elements:	FALSE, FALSE, FALSE
void LLViewerWindow::renderSelections( BOOL for_gl_pick, BOOL pick_parcel_walls, BOOL for_hud )
{
	LLViewerObject* object;

	if (!for_hud && !for_gl_pick)
	{
		// Call this once and only once
		gSelectMgr->updateSilhouettes();
	}
	
	// Draw fence around land selections
	if (for_gl_pick)
	{
		if (pick_parcel_walls)
		{
			gParcelMgr->renderParcelCollision();
		}
	}
	else if (( for_hud && gSelectMgr->getSelectType() == SELECT_TYPE_HUD) ||
			 (!for_hud && gSelectMgr->getSelectType() != SELECT_TYPE_HUD))
	{		
		gSelectMgr->renderSilhouettes(for_hud);
		
		stop_glerror();

		// setup HUD render
		if (gSelectMgr->getSelectType() == SELECT_TYPE_HUD && gSelectMgr->getObjectCount())
		{
			LLBBox hud_bbox = gAgent.getAvatarObject()->getHUDBBox();

			// set up transform to encompass bounding box of HUD
			glMatrixMode(GL_PROJECTION);
			glPushMatrix();
			glLoadIdentity();
			F32 depth = llmax(1.f, hud_bbox.getExtentLocal().mV[VX] * 1.1f);
			glOrtho(-0.5f * gCamera->getAspect(), 0.5f * gCamera->getAspect(), -0.5f, 0.5f, 0.f, depth);
			
			glMatrixMode(GL_MODELVIEW);
			glPushMatrix();
			glLoadIdentity();
			glLoadMatrixf(OGL_TO_CFR_ROTATION);		// Load Cory's favorite reference frame
			glTranslatef(-hud_bbox.getCenterLocal().mV[VX] + (depth *0.5f), 0.f, 0.f);
		}

		// Render light for editing
		if (LLSelectMgr::sRenderLightRadius)
		{
			LLGLEnable gls_blend(GL_BLEND);
			LLGLEnable gls_cull(GL_CULL_FACE);
			LLGLDepthTest gls_depth(GL_TRUE, GL_TRUE);
			glMatrixMode(GL_MODELVIEW);
			glPushMatrix();
			if (gSelectMgr->getSelectType() == SELECT_TYPE_HUD)
			{
				F32 zoom = gAgent.getAvatarObject()->mHUDCurZoom;
				glScalef(zoom, zoom, zoom);
			}
			for( object = gSelectMgr->getFirstObject(); object; object = gSelectMgr->getNextObject() )
			{
				LLDrawable* drawable = object->mDrawable;
				if (drawable && drawable->isLight())
				{
					LLVOVolume* vovolume = drawable->getVOVolume();
					glPushMatrix();

					LLVector3 center = drawable->getPositionAgent();
					glTranslatef(center[0], center[1], center[2]);
					F32 scale = vovolume->getLightRadius();
					glScalef(scale, scale, scale);

					LLColor4 color(vovolume->getLightColor(), .5f);
					glColor4fv(color.mV);
					
					F32 pixel_area = 100000.f;
					// Render Outside
					gSphere.render(pixel_area);

					// Render Inside
					glCullFace(GL_FRONT);
					gSphere.render(pixel_area);
					glCullFace(GL_BACK);
					
					glPopMatrix();
				}
			}
			glPopMatrix();
		}				
		
		// NOTE: The average position for the axis arrows of the selected objects should
		// not be recalculated at this time.  If they are, then group rotations will break.

		// Draw arrows at average center of all selected objects
		LLTool* tool = gToolMgr->getCurrentTool( gKeyboard->currentMask(TRUE) );
		if (tool)
		{
			if(tool->isAlwaysRendered())
			{
				tool->render();
			}
			else
			{
				if( !gSelectMgr->isEmpty() )
				{
					BOOL moveable_object_selected = FALSE;
					BOOL all_selected_objects_move = TRUE;
					BOOL all_selected_objects_modify = TRUE;
					BOOL selecting_linked_set = gSavedSettings.getBOOL("SelectLinkedSet");
					for( object = gSelectMgr->getFirstObject(); object; object = gSelectMgr->getNextObject() )
					{
						BOOL this_object_movable = FALSE;
						if (object->permMove() && (object->permModify() || selecting_linked_set))
						{
							moveable_object_selected = TRUE;
							this_object_movable = TRUE;
						}
						all_selected_objects_move = all_selected_objects_move && this_object_movable;
						all_selected_objects_modify = all_selected_objects_modify && object->permModify();
					}

					BOOL draw_handles = TRUE;

					if (tool == gToolTranslate && (!moveable_object_selected || !all_selected_objects_move))
					{
						draw_handles = FALSE;
					}

					if (tool == gToolRotate && (!moveable_object_selected || !all_selected_objects_move))
					{
						draw_handles = FALSE;
					}

					if ( !all_selected_objects_modify && tool == gToolStretch )
					{
						draw_handles = FALSE;
					}
				
					if( draw_handles )
					{
						tool->render();
					}
				}
			}
			if (gSelectMgr->getSelectType() == SELECT_TYPE_HUD && gSelectMgr->getObjectCount())
			{
				glMatrixMode(GL_PROJECTION);
				glPopMatrix();

				glMatrixMode(GL_MODELVIEW);
				glPopMatrix();
				stop_glerror();
			}
		}
	}
}

// Return a point near the clicked object representative of the place the object was clicked.
LLVector3d LLViewerWindow::clickPointInWorldGlobal(S32 x, S32 y_from_bot, LLViewerObject* clicked_object) const
{
	// create a normalized vector pointing from the camera center into the 
	// world at the location of the mouse click
	LLVector3 mouse_direction_global = mouseDirectionGlobal( x, y_from_bot );

	LLVector3d relative_object = clicked_object->getPositionGlobal() - gAgent.getCameraPositionGlobal();

	// make mouse vector as long as object vector, so it touchs a point near
	// where the user clicked on the object
	mouse_direction_global *= (F32) relative_object.magVec();

	LLVector3d new_pos;
	new_pos.setVec(mouse_direction_global);
	// transform mouse vector back to world coords
	new_pos += gAgent.getCameraPositionGlobal();

	return new_pos;
}


BOOL LLViewerWindow::clickPointOnSurfaceGlobal(const S32 x, const S32 y, LLViewerObject *objectp, LLVector3d &point_global) const
{
	BOOL intersect = FALSE;

//	U8 shape = objectp->mPrimitiveCode & LL_PCODE_BASE_MASK;
	if (!intersect)
	{
		point_global = clickPointInWorldGlobal(x, y, objectp);
		llinfos << "approx intersection at " <<  (objectp->getPositionGlobal() - point_global) << llendl;
	}
	else
	{
		llinfos << "good intersection at " <<  (objectp->getPositionGlobal() - point_global) << llendl;
	}

	return intersect;
}

void LLViewerWindow::hitObjectOrLandGlobalAsync(S32 x, S32 y_from_bot, MASK mask, void (*callback)(S32 x, S32 y, MASK mask), BOOL pick_transparent, BOOL pick_parcel_walls)
{
	if (gNoRender)
	{
		return;
	}

	S32 scaled_x = llround((F32)x * mDisplayScale.mV[VX]);
	S32 scaled_y = llround((F32)y_from_bot * mDisplayScale.mV[VY]);

	F32 delta_time = gAlphaFadeTimer.getElapsedTimeAndResetF32();

	BOOL in_build_mode = gFloaterTools && gFloaterTools->getVisible();
	if (in_build_mode || LLDrawPoolAlpha::sShowDebugAlpha)
	{
		// build mode allows interaction with all transparent objects
		// "Show Debug Alpha" means no object actually transparent
		pick_transparent = TRUE;
	}
	gPickTransparent = pick_transparent;

	if (gPickTransparent)
	{
		gPickAlphaTargetThreshold = 0.f;
		gPickAlphaThreshold = llmax(gPickAlphaTargetThreshold, gPickAlphaThreshold - (delta_time * 0.7f));
	}
	else
	{
		gPickAlphaTargetThreshold = 1.f;
		gPickAlphaThreshold = llmin(gPickAlphaTargetThreshold, gPickAlphaThreshold + (delta_time * 0.7f));
	}

	gUseGLPick = FALSE;
	mPickCallback = callback;

	// Default to not hitting anything
	gLastHitPosGlobal.zeroVec();
	gLastHitObjectOffset.zeroVec();
	gLastHitObjectID.setNull();
	gLastHitObjectFace = -1;

	gLastHitNonFloraPosGlobal.zeroVec();
	gLastHitNonFloraObjectOffset.zeroVec();
	gLastHitNonFloraObjectID.setNull();
	gLastHitNonFloraObjectFace = -1;

	gLastHitParcelWall = FALSE;

	LLCamera pick_camera;
	pick_camera.setOrigin(gCamera->getOrigin());
	pick_camera.setOriginAndLookAt(gCamera->getOrigin(),
								   gCamera->getUpAxis(),
								   gCamera->getOrigin() + mouseDirectionGlobal(x, y_from_bot));
	pick_camera.setView(0.5f*DEG_TO_RAD);
	pick_camera.setNear(gCamera->getNear());
	pick_camera.setFar(gCamera->getFar());
	pick_camera.setAspect(1.f);

	// save our drawing state
	glMatrixMode(GL_MODELVIEW);
	glPushMatrix();
	glLoadIdentity();
	
	glMatrixMode(GL_PROJECTION);
	glPushMatrix();
	glLoadIdentity();

	// build perspective transform and picking viewport
	// Perform pick on a PICK_DIAMETER x PICK_DIAMETER pixel region around cursor point.
	// Don't limit the select distance for this pick.
	// make viewport big enough to handle antialiased frame buffers
	gCamera->setPerspective(FOR_SELECTION, scaled_x - (PICK_HALF_WIDTH + 2), scaled_y - (PICK_HALF_WIDTH + 2), PICK_DIAMETER + 4, PICK_DIAMETER + 4, FALSE);
	pick_camera.calcAgentFrustumPlanes(gCamera->mAgentFrustum);
	// make viewport big enough to handle antialiased frame buffers
	glViewport(scaled_x - (PICK_HALF_WIDTH + 2), scaled_y - (PICK_HALF_WIDTH + 2), PICK_DIAMETER + 4, PICK_DIAMETER + 4);
	stop_glerror();

	glClearColor(0.f, 0.f, 0.f, 1.f);
	glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

	// Draw the objects so the user can select them.
	// The starting ID is 1, since land is zero.
	gObjectList.renderObjectsForSelect(pick_camera, pick_parcel_walls);

	stop_glerror();

	// restore drawing state
	glMatrixMode(GL_PROJECTION);
	glPopMatrix();
	glMatrixMode(GL_MODELVIEW);
	glPopMatrix();

	setupViewport();

	mPickPoint.set(x, y_from_bot);
	mPickOffset.set(0, 0);
	mPickMask = mask;
	mPickPending = TRUE;

	// delay further event processing until we receive results of pick
	mWindow->delayInputProcessing();
}

void LLViewerWindow::hitUIElementImmediate(S32 x, S32 y, void (*callback)(S32 x, S32 y, MASK mask))
{
	// Performs the GL UI pick.
	// Stores its results in global, gLastHitUIElement
	if (gNoRender)
	{
		return;
	}
	
	hitUIElementAsync(x, y, gKeyboard->currentMask(TRUE), NULL);
	performPick();
	if (callback)
	{
		callback(x, y, gKeyboard->currentMask(TRUE));
	}
}

//RN: this currently doesn't do anything
void LLViewerWindow::hitUIElementAsync(S32 x, S32 y_from_bot, MASK mask, void (*callback)(S32 x, S32 y, MASK mask))
{
	if (gNoRender)
	{
		return;
	}

// 	F32 delta_time = gAlphaFadeTimer.getElapsedTimeAndResetF32();

	gUseGLPick = FALSE;
	mPickCallback = callback;

	// Default to not hitting anything
	gLastHitUIElement = 0;

	LLCamera pick_camera;
	pick_camera.setOrigin(gCamera->getOrigin());
	pick_camera.setOriginAndLookAt(gCamera->getOrigin(),
								   gCamera->getUpAxis(),
								   gCamera->getOrigin() + mouseDirectionGlobal(x, y_from_bot));
	pick_camera.setView(0.5f*DEG_TO_RAD);
	pick_camera.setNear(gCamera->getNear());
	pick_camera.setFar(gCamera->getFar());
	pick_camera.setAspect(1.f);

	// save our drawing state
	glMatrixMode(GL_MODELVIEW);
	glPushMatrix();
	glLoadIdentity();
	
	glMatrixMode(GL_PROJECTION);
	glPushMatrix();
	glLoadIdentity();

	// build orthogonal transform and picking viewport
	// Perform pick on a PICK_DIAMETER x PICK_DIAMETER pixel region around cursor point.
	// Don't limit the select distance for this pick.
	gViewerWindow->setup2DRender();
	const LLVector2& display_scale = gViewerWindow->getDisplayScale();
	glScalef(display_scale.mV[VX], display_scale.mV[VY], 1.f);

	glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

	// make viewport big enough to handle antialiased frame buffers
	glViewport(x - (PICK_HALF_WIDTH + 2), y_from_bot - (PICK_HALF_WIDTH + 2), PICK_DIAMETER + 4, PICK_DIAMETER + 4);
	stop_glerror();

	glClearColor(0.f, 0.f, 0.f, 1.f);
	glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);

	// Draw the objects so the user can select them.
	// The starting ID is 1, since land is zero.
	//gViewerWindow->drawForSelect();

	stop_glerror();

	// restore drawing state
	glMatrixMode(GL_PROJECTION);
	glPopMatrix();
	glMatrixMode(GL_MODELVIEW);
	glPopMatrix();

	setupViewport();

	mPickPoint.set(x, y_from_bot);
	mPickOffset.set(0, 0);
	mPickMask = mask;
	mPickPending = TRUE;
}

void LLViewerWindow::performPick()
{
	if (gNoRender || !mPickPending)
	{
		return;
	}

	finishFastFrame();

	mPickPending = FALSE;
	U32	te_offset = NO_FACE;
	
	// find pick region that is fully onscreen
	LLCoordGL scaled_pick_point = mPickPoint;
	scaled_pick_point.mX = llclamp(llround((F32)mPickPoint.mX * mDisplayScale.mV[VX]), PICK_HALF_WIDTH, gViewerWindow->getWindowDisplayWidth() - PICK_HALF_WIDTH);
	scaled_pick_point.mY = llclamp(llround((F32)mPickPoint.mY * mDisplayScale.mV[VY]), PICK_HALF_WIDTH, gViewerWindow->getWindowDisplayHeight() - PICK_HALF_WIDTH);

	glReadPixels(scaled_pick_point.mX - PICK_HALF_WIDTH, scaled_pick_point.mY - PICK_HALF_WIDTH, PICK_DIAMETER, PICK_DIAMETER, GL_RGBA, GL_UNSIGNED_BYTE, mPickBuffer);

	S32 pixel_index = PICK_HALF_WIDTH * PICK_DIAMETER + PICK_HALF_WIDTH;
	S32 name = (U32)mPickBuffer[(pixel_index * 4) + 0] << 16 | (U32)mPickBuffer[(pixel_index * 4) + 1] << 8 | (U32)mPickBuffer[(pixel_index * 4) + 2];
	gLastPickAlpha = mPickBuffer[(pixel_index * 4) + 3];

	if (name >= (S32)GL_NAME_UI_RESERVED && name < (S32)GL_NAME_INDEX_OFFSET)
	{
		// hit a UI element
		gLastHitUIElement = name;
		if (mPickCallback)
		{
			mPickCallback(mPickPoint.mX, mPickPoint.mY, mPickMask);
		}
	}

	//imdebug("rgba rbga=bbba b=8 w=%d h=%d %p", PICK_DIAMETER, PICK_DIAMETER, mPickBuffer);

	S32 x_offset = mPickPoint.mX - llround((F32)scaled_pick_point.mX / mDisplayScale.mV[VX]);
	S32 y_offset = mPickPoint.mY - llround((F32)scaled_pick_point.mY / mDisplayScale.mV[VY]);

	
	// we hit nothing, scan surrounding pixels for something useful
	if (!name)
	{
		S32 closest_distance = 10000;
		//S32 closest_pick_name = 0;
		for (S32 col = 0; col < PICK_DIAMETER; col++)
		{
			for (S32 row = 0; row < PICK_DIAMETER; row++)
			{
				S32 distance_squared = (llabs(col - x_offset - PICK_HALF_WIDTH) * llabs(col - x_offset - PICK_HALF_WIDTH)) + (llabs(row - y_offset - PICK_HALF_WIDTH) * llabs(row - y_offset - PICK_HALF_WIDTH));
				pixel_index = row * PICK_DIAMETER + col;
				S32 test_name = (U32)mPickBuffer[(pixel_index * 4) + 0] << 16 | (U32)mPickBuffer[(pixel_index * 4) + 1] << 8 | (U32)mPickBuffer[(pixel_index * 4) + 2];
				gLastPickAlpha = mPickBuffer[(pixel_index * 4) + 3];
				if (test_name && distance_squared < closest_distance)
				{
					closest_distance = distance_squared;
					name = test_name;
					gLastPickAlpha = mPickBuffer[(pixel_index * 4) + 3];
					mPickOffset.mX = col - PICK_HALF_WIDTH;
					mPickOffset.mY = row - PICK_HALF_WIDTH;
				}
			}
		}
	}

	if (name)
	{
		mPickPoint.mX += llround((F32)mPickOffset.mX * mDisplayScale.mV[VX]);
		mPickPoint.mY += llround((F32)mPickOffset.mY * mDisplayScale.mV[VY]);
	}

	if (gPickFaces)
	{
		te_offset = ((U32)name >> 20);
		name &= 0x000fffff;
		// don't clear gPickFaces, as we still need to check for UV coordinates
	}

	LLViewerObject	*objectp = NULL;

	// Frontmost non-foreground object that isn't trees or grass
	LLViewerObject* nonflora_objectp = NULL;
	S32 nonflora_name = -1;
	S32 nonflora_te_offset = NO_FACE;

	if (name == (S32)GL_NAME_PARCEL_WALL)
	{
		gLastHitParcelWall = TRUE;
	}

	gLastHitHUDIcon = NULL;

	objectp = gObjectList.getSelectedObject(name);
	if (objectp)
	{
		if (objectp->mbCanSelect)
		{
			te_offset = (te_offset == 16) ? NO_FACE : te_offset;

			// If the hit object isn't a plant, store it as the frontmost non-flora object.
			LLPCode pcode = objectp->getPCode();
			if( (LL_PCODE_LEGACY_GRASS != pcode) &&
				(LL_PCODE_LEGACY_TREE != pcode) &&
				(LL_PCODE_TREE_NEW != pcode))
			{
				nonflora_objectp = objectp;
				nonflora_name = name;
				nonflora_te_offset = te_offset;
			}
		}
		else
		{
			//llinfos << "Hit object you can't select" << llendl;
		}
	}
	else
	{
		// was this name referring to a hud icon?
		gLastHitHUDIcon = LLHUDIcon::handlePick(name);
	}

	analyzeHit( 
		mPickPoint.mX, mPickPoint.mY, objectp, te_offset,
		&gLastHitObjectID, &gLastHitObjectFace, &gLastHitPosGlobal, &gLastHitLand, &gLastHitUCoord, &gLastHitVCoord );

	if (objectp && !gLastHitObjectID.isNull())
	{
		gLastHitObjectOffset = gAgent.calcFocusOffset(objectp, mPickPoint.mX, mPickPoint.mY);
	}

	if( objectp == nonflora_objectp )
	{
		gLastHitNonFloraObjectID	= gLastHitObjectID;
		gLastHitNonFloraObjectFace	= gLastHitObjectFace;
		gLastHitNonFloraPosGlobal	= gLastHitPosGlobal;
		gLastHitNonFloraObjectOffset= gLastHitObjectOffset;
	}
	else
	{
		analyzeHit(  mPickPoint.mX, mPickPoint.mY, nonflora_objectp, nonflora_te_offset,
			&gLastHitNonFloraObjectID, &gLastHitNonFloraObjectFace, &gLastHitNonFloraPosGlobal,
			&gLastHitLand, &gLastHitUCoord, &gLastHitVCoord);

		if( nonflora_objectp )
		{
			gLastHitNonFloraObjectOffset = gAgent.calcFocusOffset(nonflora_objectp, mPickPoint.mX, mPickPoint.mY);
		}
	}

	if (mPickCallback)
	{
		mPickCallback(mPickPoint.mX, mPickPoint.mY, mPickMask);
	}

	gPickFaces = FALSE;
}

// Performs the GL object/land pick.
// Stores its results in globals, gHit*
void LLViewerWindow::hitObjectOrLandGlobalImmediate(S32 x, S32 y_from_bot, void (*callback)(S32 x, S32 y, MASK mask), BOOL pick_transparent)
{
	if (gNoRender)
	{
		return;
	}
	
	hitObjectOrLandGlobalAsync(x, y_from_bot, gKeyboard->currentMask(TRUE), NULL, pick_transparent);
	performPick();
	if (callback)
	{
		callback(x, y_from_bot, gKeyboard->currentMask(TRUE));
	}
}

LLViewerObject* LLViewerWindow::getObjectUnderCursor(const F32 depth)
{
	S32 x = getCurrentMouseX();
	S32 y = getCurrentMouseY();
	
	LLVector3		mouse_direction_global = mouseDirectionGlobal(x,y);
	LLVector3		camera_pos_global = gCamera->getOrigin();
	LLVector3		pick_end = camera_pos_global + mouse_direction_global * depth;
	LLVector3		collision_point;
	return gPipeline.pickObject(camera_pos_global, pick_end, collision_point);
}

void LLViewerWindow::analyzeHit(
	S32				x,				// input
	S32				y_from_bot,		// input
	LLViewerObject* objectp,		// input
	U32				te_offset,		// input
	LLUUID*			hit_object_id_p,// output
	S32*			hit_face_p,		// output
	LLVector3d*		hit_pos_p,		// output
	BOOL*			hit_land,		// output
	F32*			hit_u_coord,	// output
	F32*			hit_v_coord)	// output
{
	// Clean up inputs
	S32 face = -1;
	
	if (te_offset != NO_FACE ) 
	{
		face = te_offset;
	}

	*hit_land = FALSE;

	if (objectp)
	{
		if( objectp->getPCode() == LLViewerObject::LL_VO_SURFACE_PATCH )
		{
			// Hit land
			*hit_land = TRUE;

			// put global position into land_pos
			LLVector3d land_pos;
			if (mousePointOnLandGlobal(x, y_from_bot, &land_pos))
			{
				*hit_object_id_p = LLUUID::null;
				*hit_face_p = -1;

				// Fudge the land focus a little bit above ground.
				*hit_pos_p = land_pos + LLVector3d(0.f, 0.f, 0.1f);
				//llinfos << "DEBUG Hit Land " << *hit_pos_p  << llendl;
				return;
			}
			else
			{
				//llinfos << "Hit land but couldn't find position" << llendl;
				// Fall through to "Didn't hit anything"
			}
		}
		else
		{
			*hit_object_id_p = objectp->mID;
			*hit_face_p = face;

			// Hit an object
			if (objectp->isAvatar())
			{
				*hit_pos_p = gAgent.getPosGlobalFromAgent(((LLVOAvatar*)objectp)->mPelvisp->getWorldPosition());
			}
			else if (objectp->mDrawable.notNull())
			{
				*hit_pos_p = gAgent.getPosGlobalFromAgent(objectp->getRenderPosition());
			}
			else
			{
				// regular object
				*hit_pos_p = objectp->getPositionGlobal();
			}

			if (gPickFaces && face > -1 &&
				objectp->mDrawable.notNull() && objectp->getPCode() == LL_PCODE_VOLUME &&
				face < objectp->mDrawable->getNumFaces())
			{
				// render red-blue gradient to get 1/256 precision
				// then render green grid to get final 1/4096 precision
				S32 scaled_x = llround((F32)x * mDisplayScale.mV[VX]);
				S32 scaled_y = llround((F32)y_from_bot * mDisplayScale.mV[VY]);
				const S32 UV_PICK_WIDTH = 41;
				const S32 UV_PICK_HALF_WIDTH = (UV_PICK_WIDTH - 1) / 2;
				U8 uv_pick_buffer[UV_PICK_WIDTH * UV_PICK_WIDTH * 4];
				S32 pick_face = ((LLVOVolume*)objectp)->getAllTEsSame() ? 0 : face;
				LLFace* facep = objectp->mDrawable->getFace(objectp->getFaceIndexOffset() + pick_face);
				gCamera->setPerspective(FOR_SELECTION, scaled_x - UV_PICK_HALF_WIDTH, scaled_y - UV_PICK_HALF_WIDTH, UV_PICK_WIDTH, UV_PICK_WIDTH, FALSE);
				glViewport(scaled_x - UV_PICK_HALF_WIDTH, scaled_y - UV_PICK_HALF_WIDTH, UV_PICK_WIDTH, UV_PICK_WIDTH);
				gPipeline.renderFaceForUVSelect(facep);

				glReadPixels(scaled_x - UV_PICK_HALF_WIDTH, scaled_y - UV_PICK_HALF_WIDTH, UV_PICK_WIDTH, UV_PICK_WIDTH, GL_RGBA, GL_UNSIGNED_BYTE, uv_pick_buffer);
				U8* center_pixel = &uv_pick_buffer[4 * ((UV_PICK_WIDTH * UV_PICK_HALF_WIDTH) + UV_PICK_HALF_WIDTH + 1)];
				*hit_u_coord = (F32)((center_pixel[VGREEN] & 0xf) + (16.f * center_pixel[VRED])) / 4095.f;
				*hit_v_coord = (F32)((center_pixel[VGREEN] >> 4) + (16.f * center_pixel[VBLUE])) / 4095.f;
			}
			else
			{
				*hit_u_coord = 0.f;
				*hit_v_coord = 0.f;
			}

			//llinfos << "DEBUG Hit Object " << *hit_pos_p << llendl;
			return;
		}
	}

	// Didn't hit anything.
	*hit_object_id_p = LLUUID::null;
	*hit_face_p = -1;
	*hit_pos_p = LLVector3d::zero;
	*hit_u_coord = 0.f;
	*hit_v_coord = 0.f;
	//llinfos << "DEBUG Hit Nothing " << llendl;
}


void LLViewerWindow::requestFastFrame(LLView *view) 
{ 
	if (!mPickPending && 
		mWindow->getSwapMethod() != LLWindow::SWAP_METHOD_UNDEFINED &&
		gStartupState >= STATE_STARTED && 
		gSavedSettings.getBOOL("RenderFastUI") && 
		!gbCapturing)
	{
		if (!mFastFrameTimer.getStarted())
		{
			// we're double buffered, so when first requesting a fast ui update 
			// we need to render the scene again so that the front and back buffers
			// are synced
			mRenderFullFrame = TRUE;
		}
		// calculation new expiration time and reset timer
		F32 expiration;
		if (mFastFrameTimer.hasExpired())
		{
			expiration =  FAST_FRAME_INCREMENT;
		}
		else
		{
			expiration = llmin(MAX_FAST_FRAME_TIME, mFastFrameTimer.getTimeToExpireF32() + FAST_FRAME_INCREMENT);
		}

		mFastFrameTimer.start();
		mFastFrameTimer.setTimerExpirySec(expiration);

		LLView::sFastFrameView = view->getRootMostFastFrameView();
		if (!LLView::sFastFrameView)
		{
			LLView::sFastFrameView = view;
		}
	}
}


// Returns unit vector relative to camera
// indicating direction of point on screen x,y
LLVector3 LLViewerWindow::mouseDirectionGlobal(const S32 x, const S32 y) const
{
	// find vertical field of view
	F32			fov = gCamera->getView();

	// find screen resolution
	S32			height = getWindowHeight();
	S32			width = getWindowWidth();

	// calculate pixel distance to screen
	F32			distance = (height / 2.f) / (tan(fov / 2.f));

	// calculate click point relative to middle of screen
	F32			click_x = x - width / 2.f;
	F32			click_y = y - height / 2.f;

	// compute mouse vector
	LLVector3	mouse_vector =	distance * gCamera->getAtAxis()
								- click_x * gCamera->getLeftAxis()
								+ click_y * gCamera->getUpAxis();

	mouse_vector.normVec();

	return mouse_vector;
}


// Returns unit vector relative to camera in camera space
// indicating direction of point on screen x,y
LLVector3 LLViewerWindow::mouseDirectionCamera(const S32 x, const S32 y) const
{
	// find vertical field of view
	F32			fov_height = gCamera->getView();
	F32			fov_width = fov_height * gCamera->getAspect();

	// find screen resolution
	S32			height = getWindowHeight();
	S32			width = getWindowWidth();

	// calculate click point relative to middle of screen
	F32			click_x = (((F32)x / (F32)width) - 0.5f) * fov_width * -1.f;
	F32			click_y = (((F32)y / (F32)height) - 0.5f) * fov_height;

	// compute mouse vector
	LLVector3	mouse_vector =	LLVector3(0.f, 0.f, -1.f);
	LLQuaternion mouse_rotate;
	mouse_rotate.setQuat(click_y, click_x, 0.f);

	mouse_vector = mouse_vector * mouse_rotate;
	// project to z = -1 plane;
	mouse_vector = mouse_vector * (-1.f / mouse_vector.mV[VZ]);

	return mouse_vector;
}



BOOL LLViewerWindow::mousePointOnPlaneGlobal(LLVector3d& point, const S32 x, const S32 y, 
										const LLVector3d &plane_point_global, 
										const LLVector3 &plane_normal_global)
{
	LLVector3d	mouse_direction_global_d;

	mouse_direction_global_d.setVec(mouseDirectionGlobal(x,y));
	LLVector3d	plane_normal_global_d;
	plane_normal_global_d.setVec(plane_normal_global);
	F64	mouse_look_at_scale = (plane_normal_global_d * (plane_point_global - gAgent.getCameraPositionGlobal()))
								/ (plane_normal_global_d * mouse_direction_global_d);
	point = gAgent.getCameraPositionGlobal() + mouse_look_at_scale * mouse_direction_global_d;

	return mouse_look_at_scale > 0.0;
}


// Returns global position
BOOL LLViewerWindow::mousePointOnLandGlobal(const S32 x, const S32 y, LLVector3d *land_position_global)
{
	LLVector3		mouse_direction_global = mouseDirectionGlobal(x,y);
	F32				mouse_dir_scale;
	BOOL			hit_land = FALSE;
	LLViewerRegion	*regionp;
	F32			land_z;
	const F32	FIRST_PASS_STEP = 1.0f;		// meters
	const F32	SECOND_PASS_STEP = 0.1f;	// meters
	LLVector3d	camera_pos_global;

	camera_pos_global = gAgent.getCameraPositionGlobal();
	LLVector3d		probe_point_global;
	LLVector3		probe_point_region;

	// walk forwards to find the point
	for (mouse_dir_scale = FIRST_PASS_STEP; mouse_dir_scale < gAgent.mDrawDistance; mouse_dir_scale += FIRST_PASS_STEP)
	{
		LLVector3d mouse_direction_global_d;
		mouse_direction_global_d.setVec(mouse_direction_global * mouse_dir_scale);
		probe_point_global = camera_pos_global + mouse_direction_global_d;

		regionp = gWorldPointer->resolveRegionGlobal(probe_point_region, probe_point_global);

		if (!regionp)
		{
			// ...we're outside the world somehow
			continue;
		}

		S32 i = (S32) (probe_point_region.mV[VX]/regionp->getLand().getMetersPerGrid());
		S32 j = (S32) (probe_point_region.mV[VY]/regionp->getLand().getMetersPerGrid());
		S32 grids_per_edge = (S32) regionp->getLand().mGridsPerEdge;
		if ((i >= grids_per_edge) || (j >= grids_per_edge))
		{
			//llinfos << "LLViewerWindow::mousePointOnLand probe_point is out of region" << llendl;
			continue;
		}

		land_z = regionp->getLand().resolveHeightRegion(probe_point_region);

		//llinfos << "mousePointOnLand initial z " << land_z << llendl;

		if (probe_point_region.mV[VZ] < land_z)
		{
			// ...just went under land

			// cout << "under land at " << probe_point << " scale " << mouse_vec_scale << endl;

			hit_land = TRUE;
			break;
		}
	}


	if (hit_land)
	{
		// Don't go more than one step beyond where we stopped above.
		// This can't just be "mouse_vec_scale" because floating point error
		// will stop the loop before the last increment.... X - 1.0 + 0.1 + 0.1 + ... + 0.1 != X
		F32 stop_mouse_dir_scale = mouse_dir_scale + FIRST_PASS_STEP;

		// take a step backwards, then walk forwards again to refine position
		for ( mouse_dir_scale -= FIRST_PASS_STEP; mouse_dir_scale <= stop_mouse_dir_scale; mouse_dir_scale += SECOND_PASS_STEP)
		{
			LLVector3d mouse_direction_global_d;
			mouse_direction_global_d.setVec(mouse_direction_global * mouse_dir_scale);
			probe_point_global = camera_pos_global + mouse_direction_global_d;

			regionp = gWorldPointer->resolveRegionGlobal(probe_point_region, probe_point_global);

			if (!regionp)
			{
				// ...we're outside the world somehow
				continue;
			}

			/*
			i = (S32) (local_probe_point.mV[VX]/regionp->getLand().getMetersPerGrid());
			j = (S32) (local_probe_point.mV[VY]/regionp->getLand().getMetersPerGrid());
			if ((i >= regionp->getLand().mGridsPerEdge) || (j >= regionp->getLand().mGridsPerEdge))
			{
				// llinfos << "LLViewerWindow::mousePointOnLand probe_point is out of region" << llendl;
				continue;
			}
			land_z = regionp->getLand().mSurfaceZ[ i + j * (regionp->getLand().mGridsPerEdge) ];
			*/

			land_z = regionp->getLand().resolveHeightRegion(probe_point_region);

			//llinfos << "mousePointOnLand refine z " << land_z << llendl;

			if (probe_point_region.mV[VZ] < land_z)
			{
				// ...just went under land again

				*land_position_global = probe_point_global;
				return TRUE;
			}
		}
	}

	return FALSE;
}

// Saves an image to the harddrive as "SnapshotX" where X >= 1.
BOOL LLViewerWindow::saveImageNumbered(LLImageRaw *raw)
{
	if (! raw)
	{
		return FALSE;
	}
	
	// Get a directory if this is the first time.
	if (strlen(sSnapshotDir) == 0)
	{
		LLString proposed_name( sSnapshotBaseName );
		proposed_name.append( ".bmp" );

		// pick a directory in which to save
		LLFilePicker& picker = LLFilePicker::instance();
		if (!picker.getSaveFile(LLFilePicker::FFSAVE_BMP, proposed_name.c_str()))
		{
			// Clicked cancel
			return FALSE;
		}

		// Copy the directory + file name
		char directory[LL_MAX_PATH];
		strcpy(directory, picker.getFirstFile());

		// Smash the file extension
		S32 length = strlen(directory);
		S32 index = length;

		// Back up over ".bmp"
		index -= 4;
		if (index >= 0 && directory[index] == '.')
		{
			directory[index] = '\0';
		}
		else
		{
			index = length;
		}

		// Find trailing backslash
		while (index >= 0 && directory[index] != gDirUtilp->getDirDelimiter()[0])
		{
			index--;
		}

		// If we found one, truncate the string there
		if (index >= 0)
		{
			if (index + 1 <= length)
			{
				strcpy(LLViewerWindow::sSnapshotBaseName, directory + index + 1);
			}

			index++;
			directory[index] = '\0';
			strcpy(LLViewerWindow::sSnapshotDir, directory);
		}
	}

	// Look for an unused file name
	LLString filepath;
	S32 i = 1;
	S32 err = 0;

	do
	{
		char extension[100];
		sprintf( extension, "_%.3d.bmp", i );
		filepath = sSnapshotDir;
		filepath += sSnapshotBaseName;
		filepath += extension;

		struct stat stat_info;
		err = gViewerWindow->mWindow->stat( filepath.c_str(), &stat_info );
		i++;
	}
	while( -1 != err );  // search until the file is not found (i.e., stat() gives an error).

	LLPointer<LLImageBMP> bmp_image = new LLImageBMP;
	LLImageBase::setSizeOverride(TRUE);
	BOOL success = bmp_image->encode(raw);
	if( success )
	{
		success = bmp_image->save(filepath);
	}
	else
	{
		llwarns << "Unable to encode bmp snapshot" << llendl;
	}
	LLImageBase::setSizeOverride(FALSE);

	return success;
}

void LLViewerWindow::saveMovieNumbered(void*)
{
	if (!gbCapturing)
	{
		// Get a directory if this is the first time.
		if (strlen(sSnapshotDir) == 0)
		{
			LLString proposed_name( sMovieBaseName );
#if LL_DARWIN
			proposed_name.append( ".mov" );
#else
			proposed_name.append( ".avi" );
#endif

			// pick a directory in which to save
			LLFilePicker &picker = LLFilePicker::instance();
			if (!picker.getSaveFile(LLFilePicker::FFSAVE_AVI, proposed_name.c_str()))
			{
				// Clicked cancel
				return;
			}

			// Copy the directory + file name
			char directory[LL_MAX_PATH];
			strcpy(directory, picker.getFirstFile());

			// Smash the file extension
			S32 length = strlen(directory);
			S32 index = length;

			// Back up over ".bmp"
			index -= 4;
			if (index >= 0 && directory[index] == '.')
			{
				directory[index] = '\0';
			}
			else
			{
				index = length;
			}

			// Find trailing backslash
			while (index >= 0 && directory[index] != gDirUtilp->getDirDelimiter()[0])
			{
				index--;
			}

			// If we found one, truncate the string there
			if (index >= 0)
			{
				if (index + 1 <= length)
				{
					strcpy(LLViewerWindow::sMovieBaseName, directory + index + 1);
				}

				index++;
				directory[index] = '\0';
				strcpy(LLViewerWindow::sSnapshotDir, directory);
			}
		}

		// Look for an unused file name
		LLString filepath;
		S32 i = 1;
		S32 err = 0;

		do
		{
			char extension[100];
#if LL_DARWIN
			sprintf( extension, "_%.3d.mov", i );
#else
			sprintf( extension, "_%.3d.avi", i );
#endif
			filepath.assign( sSnapshotDir );
			filepath.append( sMovieBaseName );
			filepath.append( extension );

			struct stat stat_info;
			err = gViewerWindow->mWindow->stat( filepath.c_str(), &stat_info );
			i++;
		}
		while( -1 != err );  // search until the file is not found (i.e., stat() gives an error).
		S32 x = gViewerWindow->getWindowWidth();
		S32 y = gViewerWindow->getWindowHeight();

		gbCapturing = TRUE;
		gMovieMaker.StartCapture((char *)filepath.c_str(), x, y);
	}
	else
	{
		gMovieMaker.EndCapture();
		gbCapturing = FALSE;
	}
}

static S32 BORDERHEIGHT = 0;
static S32 BORDERWIDTH = 0;

void LLViewerWindow::movieSize(S32 new_width, S32 new_height)
{
	LLCoordScreen size;
	gViewerWindow->mWindow->getSize(&size);
	if (  (size.mX != new_width + BORDERWIDTH)
		||(size.mY != new_height + BORDERHEIGHT))
	{
		S32 x = gViewerWindow->getWindowWidth();
		S32 y = gViewerWindow->getWindowHeight();
		BORDERWIDTH = size.mX - x;
		BORDERHEIGHT = size.mY- y;
		LLCoordScreen new_size(new_width + BORDERWIDTH, 
							   new_height + BORDERHEIGHT);
		BOOL disable_sync = gSavedSettings.getBOOL("DisableVerticalSync");
		if (gViewerWindow->mWindow->getFullscreen())
		{
			gViewerWindow->changeDisplaySettings(FALSE, 
												new_size, 
												disable_sync, 
												TRUE);
		}
		else
		{
			gViewerWindow->mWindow->setSize(new_size);
		}
	}
}

BOOL LLViewerWindow::saveSnapshot( const LLString& filepath, S32 image_width, S32 image_height, BOOL show_ui, BOOL do_rebuild, ESnapshotType type)
{
	llinfos << "Saving snapshot to: " << filepath << llendl;

	LLPointer<LLImageRaw> raw = new LLImageRaw;
	BOOL success = rawSnapshot(raw, image_width, image_height, TRUE, show_ui, do_rebuild);

	if (success)
	{
		LLPointer<LLImageBMP> bmp_image = new LLImageBMP;
		success = bmp_image->encode(raw);
		if( success )
		{
			success = bmp_image->save(filepath);
		}
		else
		{
			llwarns << "Unable to encode bmp snapshot" << llendl;
		}
	}
	else
	{
		llwarns << "Unable to capture raw snapshot" << llendl;
	}

	return success;
}


void LLViewerWindow::playSnapshotAnimAndSound()
{
	gAgent.sendAnimationRequest(ANIM_AGENT_SNAPSHOT, ANIM_REQUEST_START);
	send_sound_trigger(LLUUID(gSavedSettings.getString("UISndSnapshot")), 1.0f);
}


// Saves the image from the screen to the specified filename and path.
BOOL LLViewerWindow::rawSnapshot(LLImageRaw *raw, S32 image_width, S32 image_height, 
								 BOOL keep_window_aspect, BOOL show_ui, BOOL do_rebuild, ESnapshotType type)
{
	F32 image_aspect_ratio = ((F32)image_width) / ((F32)image_height);
	F32 window_aspect_ratio = ((F32)getWindowWidth()) / ((F32)getWindowHeight());

	if ((!gWorldPointer) ||
		(!raw))
	{
		return FALSE;
	}

	// IW 3/5/04 We don'a wan' nunna yer fest frumes har!
	finishFastFrame();

	// PRE SNAPSHOT

	glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
	setCursor(UI_CURSOR_WAIT);

	// Hide all the UI widgets first and draw a frame
	BOOL prev_draw_ui = gPipeline.hasRenderDebugFeatureMask(LLPipeline::RENDER_DEBUG_FEATURE_UI);

	if ( prev_draw_ui != show_ui)
	{
		LLPipeline::toggleRenderDebugFeature((void*)LLPipeline::RENDER_DEBUG_FEATURE_UI);
	}

	BOOL hide_hud = !gSavedSettings.getBOOL("RenderHUDInSnapshot") && gPipeline.hasRenderType(LLPipeline::RENDER_TYPE_HUD);
	if (hide_hud)
	{
		LLPipeline::toggleRenderType((void*)LLPipeline::RENDER_TYPE_HUD);
	}

	// Copy screen to a buffer
	// crop sides or top and bottom, if taking a snapshot of different aspect ratio
	// from window
	S32 snapshot_width = mWindowRect.getWidth();
	S32 snapshot_height =  mWindowRect.getHeight();
	if (!keep_window_aspect)
	{
		if (image_aspect_ratio > window_aspect_ratio)
		{
			snapshot_height  = llround((F32)snapshot_width / image_aspect_ratio);
		}
		else if (image_aspect_ratio < window_aspect_ratio)
		{
			snapshot_width = llround((F32)snapshot_height  * image_aspect_ratio);
		}
	}

	F32 scale_factor = llmax(1.f, (F32)image_width / snapshot_width, (F32)image_height / snapshot_height);
	raw->resize(llfloor(snapshot_width*scale_factor), llfloor(snapshot_height *scale_factor), type == SNAPSHOT_TYPE_DEPTH ? 4 : 3);

	BOOL high_res = scale_factor > 1.f;
	if (high_res)
	{
		send_agent_pause();
		//rescale fonts
		initFonts(scale_factor);
		LLHUDText::reshape();
	}

	// SNAPSHOT
	S32 window_width = mWindowRect.getWidth();
	S32 window_height = mWindowRect.getHeight();
	S32 buffer_x_offset = llfloor(((window_width - snapshot_width) * scale_factor) / 2.f);
	S32 buffer_y_offset = llfloor(((window_height - snapshot_height) * scale_factor) / 2.f);

	S32 output_buffer_offset_y = 0;

	F32 depth_conversion_factor_1 = (gCamera->getFar() + gCamera->getNear()) / (2.f * gCamera->getFar() * gCamera->getNear());
	F32 depth_conversion_factor_2 = (gCamera->getFar() - gCamera->getNear()) / (2.f * gCamera->getFar() * gCamera->getNear());

	for (int subimage_y = 0; subimage_y < scale_factor; ++subimage_y)
	{
		S32 subimage_y_offset = llclamp(buffer_y_offset - (subimage_y * window_height), 0, window_height);;
		// handle fractional columns
		U32 read_height = llmax(0, (window_height - subimage_y_offset) -
			llmax(0, (window_height * (subimage_y + 1)) - (buffer_y_offset + raw->getHeight())));

		S32 output_buffer_offset_x = 0;
		for (int subimage_x = 0; subimage_x < scale_factor; ++subimage_x)
		{
			gDisplaySwapBuffers = FALSE;
			if (type == SNAPSHOT_TYPE_OBJECT_ID)
			{
				glClearColor(0.f, 0.f, 0.f, 1.f);
				glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

				gCamera->setZoomParameters(scale_factor, subimage_x+(subimage_y*llceil(scale_factor)));
				setup3DRender();
				setupViewport();
				BOOL first_time_through = (subimage_x + subimage_y == 0);
				gPickTransparent = FALSE;
				gPickAlphaThreshold = 0.1f;
				gObjectList.renderObjectsForSelect(*gCamera, FALSE, !first_time_through);
			}
			else
			{
				display(do_rebuild, scale_factor, subimage_x+(subimage_y*llceil(scale_factor)));
			}
			glFlush();
			S32 subimage_x_offset = llclamp(buffer_x_offset - (subimage_x * window_width), 0, window_width);
			// handle fractional rows
			U32 read_width = llmax(0, (window_width - subimage_x_offset) -
									llmax(0, (window_width * (subimage_x + 1)) - (buffer_x_offset + raw->getWidth())));
			for(U32 out_y = 0; out_y < read_height ; out_y++)
			{
				if (type == SNAPSHOT_TYPE_OBJECT_ID || type == SNAPSHOT_TYPE_COLOR)
				{
					glReadPixels(
						subimage_x_offset, out_y + subimage_y_offset,
						read_width, 1,
						GL_RGB, GL_UNSIGNED_BYTE,
						raw->getData() + // current output pixel is beginning of buffer...
							( 
								(out_y * (raw->getWidth())) // ...plus iterated y...
								+ (window_width * subimage_x) // ...plus subimage start in x...
								+ (raw->getWidth() * window_height * subimage_y) // ...plus subimage start in y...
								- output_buffer_offset_x // ...minus buffer padding x...
								- (output_buffer_offset_y * (raw->getWidth()))  // ...minus buffer padding y...
							) * 3 // times 3 bytes per pixel
					);
				}
				else // SNAPSHOT_TYPE_DEPTH
				{
					S32 output_buffer_offset = ( 
								(out_y * (raw->getWidth())) // ...plus iterated y...
								+ (window_width * subimage_x) // ...plus subimage start in x...
								+ (raw->getWidth() * window_height * subimage_y) // ...plus subimage start in y...
								- output_buffer_offset_x // ...minus buffer padding x...
								- (output_buffer_offset_y * (raw->getWidth()))  // ...minus buffer padding y...
							) * 4; // times 4 bytes per pixel

					glReadPixels(
						subimage_x_offset, out_y + subimage_y_offset,
						read_width, 1,
						GL_DEPTH_COMPONENT, GL_FLOAT,
						raw->getData() + output_buffer_offset// current output pixel is beginning of buffer...
					);

					for (S32 i = output_buffer_offset; i < output_buffer_offset + (S32)read_width * 4; i += 4)
					{
						F32 depth_float = *(F32*)(raw->getData() + i);
					
						F32 linear_depth_float = 1.f / (depth_conversion_factor_1 - (depth_float * depth_conversion_factor_2));
						U8 depth_byte = F32_to_U8(linear_depth_float, gCamera->getNear(), gCamera->getFar());
						*(raw->getData() + i + 0) = depth_byte;
						*(raw->getData() + i + 1) = depth_byte;
						*(raw->getData() + i + 2) = depth_byte;
						*(raw->getData() + i + 3) = 255;
					}
				}
			}
			output_buffer_offset_x += subimage_x_offset;
			stop_glerror();
		}
		output_buffer_offset_y += subimage_y_offset;
	}

	// POST SNAPSHOT
	if (!gPipeline.hasRenderDebugFeatureMask(LLPipeline::RENDER_DEBUG_FEATURE_UI))
	{
		LLPipeline::toggleRenderDebugFeature((void*)LLPipeline::RENDER_DEBUG_FEATURE_UI);
	}

	if (hide_hud)
	{
		LLPipeline::toggleRenderType((void*)LLPipeline::RENDER_TYPE_HUD);
	}

	if (high_res)
	{
		initFonts(1.f);
		LLHUDText::reshape();
	}

	gDisplaySwapBuffers = TRUE;

	// Pre-pad image to number of pixels such that the line length is a multiple of 4 bytes (for BMP encoding)
	// Note: this formula depends on the number of components being 3.  Not obvious, but it's correct.
	image_width += (image_width * (type == SNAPSHOT_TYPE_DEPTH ? 4 : 3)) % 4; 	

	// Resize image
	raw->scale( image_width, image_height );  

	setCursor(UI_CURSOR_ARROW);

	if (do_rebuild)
	{
		// If we had to do a rebuild, that means that the lists of drawables to be rendered
		// was empty before we started.
		// Need to reset these, otherwise we call state sort on it again when render gets called the next time
		// and we stand a good chance of crashing on rebuild because the render drawable arrays have multiple copies of
		// objects on them.
		gPipeline.resetDrawOrders();
	}

	if (high_res)
	{
		send_agent_resume();
	}

	return TRUE;
}

void LLViewerWindow::destroyWindow()
{
	if (mWindow)
	{
		LLWindowManager::destroyWindow(mWindow);
	}
	mWindow = NULL;
}


void LLViewerWindow::drawMouselookInstructions()
{
	// Draw instructions for mouselook ("Press ESC to leave Mouselook" in a box at the top of the screen.)
	const char* instructions = "Press ESC to leave Mouselook.";
	const LLFontGL* font = gResMgr->getRes( LLFONT_SANSSERIF );

	const S32 INSTRUCTIONS_PAD = 5;
	LLRect instructions_rect;
	instructions_rect.setLeftTopAndSize( 
		INSTRUCTIONS_PAD,
		gViewerWindow->getWindowHeight() - INSTRUCTIONS_PAD,
		font->getWidth( instructions ) + 2 * INSTRUCTIONS_PAD,
		llround(font->getLineHeight() + 2 * INSTRUCTIONS_PAD));

	{
		LLGLSNoTexture gls_no_texture;
		glColor4f( 0.9f, 0.9f, 0.9f, 1.0f );
		gl_rect_2d( instructions_rect );
	}
	
	font->renderUTF8( 
		instructions, 0,
		instructions_rect.mLeft + INSTRUCTIONS_PAD,
		instructions_rect.mTop - INSTRUCTIONS_PAD,
		LLColor4( 0.0f, 0.0f, 0.0f, 1.f ),
		LLFontGL::LEFT, LLFontGL::TOP);
}


// These functions are here only because LLViewerWindow used to do the work that gFocusMgr does now.
// They let other objects continue to work without change.

void LLViewerWindow::setKeyboardFocus(LLUICtrl* new_focus,void (*on_focus_lost)(LLUICtrl* old_focus))
{
	gFocusMgr.setKeyboardFocus( new_focus, on_focus_lost );
}

LLUICtrl* LLViewerWindow::getKeyboardFocus()
{
	return gFocusMgr.getKeyboardFocus();
}

BOOL LLViewerWindow::hasKeyboardFocus(const LLUICtrl* possible_focus) const
{
	return possible_focus == gFocusMgr.getKeyboardFocus();
}

BOOL LLViewerWindow::childHasKeyboardFocus(const LLView* parent) const
{
	return gFocusMgr.childHasKeyboardFocus( parent );
}

void LLViewerWindow::setMouseCapture(LLMouseHandler* new_captor,void (*on_capture_lost)(LLMouseHandler* old_captor))
{
	gFocusMgr.setMouseCapture( new_captor, on_capture_lost );
}

LLMouseHandler* LLViewerWindow::getMouseCaptor() const
{
	return gFocusMgr.getMouseCapture();
}

BOOL LLViewerWindow::hasMouseCapture(const LLMouseHandler* possible_captor) const
{
	return possible_captor == gFocusMgr.getMouseCapture();
}

S32	LLViewerWindow::getWindowHeight()	const 	
{ 
	return mVirtualWindowRect.getHeight(); 
}

S32	LLViewerWindow::getWindowWidth() const 	
{ 
	return mVirtualWindowRect.getWidth(); 
}

S32	LLViewerWindow::getWindowDisplayHeight()	const 	
{ 
	return mWindowRect.getHeight(); 
}

S32	LLViewerWindow::getWindowDisplayWidth() const 	
{ 
	return mWindowRect.getWidth(); 
}

LLView* LLViewerWindow::getTopView() const
{
	return gFocusMgr.getTopView();
}

BOOL LLViewerWindow::hasTopView(LLView* view) const
{
	return view == gFocusMgr.getTopView();
}

void LLViewerWindow::setTopView(LLView* new_top,void (*on_top_lost)(LLView* old_top))
{
	gFocusMgr.setTopView( new_top, on_top_lost );
}

void LLViewerWindow::setupViewport(S32 x_offset, S32 y_offset)
{
	glViewport(x_offset, y_offset, mWindowRect.getWidth(), mWindowRect.getHeight());
}

void LLViewerWindow::setup3DRender()
{
	gCamera->setPerspective(NOT_FOR_SELECTION, 0, 0,  mWindowRect.getWidth(), mWindowRect.getHeight(), FALSE, gCamera->getNear(), MAX_FAR_PLANE);
}

void LLViewerWindow::setup2DRender()
{
	gl_state_for_2d(mWindowRect.getWidth(), mWindowRect.getHeight());
}

// Could cache the pointer from the last hitObjectOrLand here.
LLViewerObject *LLViewerWindow::lastObjectHit()
{
	return gObjectList.findObject( gLastHitObjectID );
}

const LLVector3d& LLViewerWindow::lastObjectHitOffset()
{
	return gLastHitObjectOffset;
}

// Could cache the pointer from the last hitObjectOrLand here.
LLViewerObject *LLViewerWindow::lastNonFloraObjectHit()
{
	return gObjectList.findObject( gLastHitNonFloraObjectID );
}

const LLVector3d& LLViewerWindow::lastNonFloraObjectHitOffset()
{
	return gLastHitNonFloraObjectOffset;
}


void LLViewerWindow::setShowProgress(const BOOL show)
{
	if (mProgressView)
	{
		mProgressView->setVisible(show);
	}
}

BOOL LLViewerWindow::getShowProgress() const
{
	return (mProgressView && mProgressView->getVisible());
}


void LLViewerWindow::moveProgressViewToFront()
{
	if( mProgressView && mRootView )
	{
		mRootView->removeChild( mProgressView );
		mRootView->addChild( mProgressView );
	}
}

void LLViewerWindow::setProgressString(const LLString& string)
{
	if (mProgressView)
	{
		mProgressView->setText(string);
	}
}

void LLViewerWindow::setProgressMessage(const LLString& msg)
{
	if(mProgressView)
	{
		mProgressView->setMessage(msg);
	}
}

void LLViewerWindow::setProgressPercent(const F32 percent)
{
	if (mProgressView)
	{
		mProgressView->setPercent(percent);
	}
}

void LLViewerWindow::setProgressCancelButtonVisible( BOOL b, const LLString& label )
{
	if (mProgressView)
	{
		mProgressView->setCancelButtonVisible( b, label );
	}
}


LLProgressView *LLViewerWindow::getProgressView() const
{
	return mProgressView;
}

void LLViewerWindow::dumpState()
{
	llinfos << "LLViewerWindow Active " << S32(mActive) << llendl;
	llinfos << "mWindow visible " << S32(mWindow->getVisible())
		<< " minimized " << S32(mWindow->getMinimized())
		<< llendl;
}

void LLViewerWindow::stopGL(BOOL save_state)
{
	if (!gGLManager.mIsDisabled)
	{
		llinfos << "Shutting down GL..." << llendl;
		gSky.destroyGL();
		stop_glerror();
	
		gImageList.destroyGL(save_state);
		stop_glerror();

		gBumpImageList.destroyGL();
		stop_glerror();

		LLFontGL::destroyGL();
		stop_glerror();

		LLVOAvatar::destroyGL();
		stop_glerror();

		LLDynamicTexture::destroyGL();
		stop_glerror();

		if(gParcelMgr) gParcelMgr->destroyGL();

		gPipeline.destroyGL();
		
		gCone.cleanupGL();
		gBox.cleanupGL();
		gSphere.cleanupGL();
		gCylinder.cleanupGL();
		
		gGLManager.mIsDisabled = TRUE;
		stop_glerror();
		
		llinfos << "Remaining allocated texture memory: " << LLImageGL::sGlobalTextureMemory << " bytes" << llendl;
	}
}

void LLViewerWindow::restoreGL(const LLString& progress_message)
{
	if (gGLManager.mIsDisabled)
	{
		llinfos << "Restoring GL..." << llendl;
		gGLManager.mIsDisabled = FALSE;

		// for future support of non-square pixels, and fonts that are properly stretched
		//LLFontGL::destroyDefaultFonts();
		initFonts();
		initGLDefaults();
		LLGLState::restoreGL();
		gSky.restoreGL();
		gPipeline.restoreGL();
		LLDrawPoolWater::restoreGL();
		LLManipTranslate::restoreGL();
		gImageList.restoreGL();
		gBumpImageList.restoreGL();
		gPipeline.setUseAGP(gSavedSettings.getBOOL("RenderUseAGP"));
		LLDynamicTexture::restoreGL();
		LLVOAvatar::restoreGL();
		if (gParcelMgr) gParcelMgr->restoreGL();

		if (gFloaterCustomize && gFloaterCustomize->getVisible())
		{
			LLVisualParamHint::requestHintUpdates();
		}

		if (!progress_message.empty())
		{
			gRestoreGLTimer.reset();
			gRestoreGL = TRUE;
			setShowProgress(TRUE);
			setProgressString(progress_message);
		}
		llinfos << "...Restoring GL done" << llendl;
#if LL_WINDOWS
		if (SetUnhandledExceptionFilter(LLWinDebug::handleException) != LLWinDebug::handleException)
		{
			llwarns << " Someone took over my exception handler (post restoreGL)!" << llendl;
		}
#endif

	}
}

void LLViewerWindow::initFonts(F32 zoom_factor)
{
	LLFontGL::destroyGL();
	LLFontGL::initDefaultFonts( gSavedSettings.getF32("FontScreenDPI"),
								mDisplayScale.mV[VX] * zoom_factor,
								mDisplayScale.mV[VY] * zoom_factor,
								gSavedSettings.getString("FontMonospace"),
								gSavedSettings.getF32("FontSizeMonospace"),
								gSavedSettings.getString("FontSansSerif"), 
								gSavedSettings.getString("FontSansSerifFallback"),
								gSavedSettings.getF32("FontSansSerifFallbackScale"),
								gSavedSettings.getF32("FontSizeSmall"),	
								gSavedSettings.getF32("FontSizeMedium"), 
								gSavedSettings.getF32("FontSizeLarge"),			 
								gSavedSettings.getF32("FontSizeHuge"),			 
								gSavedSettings.getString("FontSansSerifBold"),
								gSavedSettings.getF32("FontSizeMedium"),
								gDirUtilp->getAppRODataDir()
							);
}
void LLViewerWindow::toggleFullscreen(BOOL show_progress)
{
	if (mWindow)
	{
		mWantFullscreen = mWindow->getFullscreen() ? FALSE : TRUE;
		mShowFullscreenProgress = show_progress;
	}
}

void LLViewerWindow::getTargetWindow(BOOL& fullscreen, S32& width, S32& height) const
{
	fullscreen = mWantFullscreen;
	
	if (gViewerWindow->mWindow
	&&  gViewerWindow->mWindow->getFullscreen() == mWantFullscreen)
	{
		width = gViewerWindow->getWindowDisplayWidth();
		height = gViewerWindow->getWindowDisplayHeight();
	}
	else if (mWantFullscreen)
	{
		width = gSavedSettings.getS32("FullScreenWidth");
		height = gSavedSettings.getS32("FullScreenHeight");
	}
	else
	{
		width = gSavedSettings.getS32("WindowWidth");
		height = gSavedSettings.getS32("WindowHeight");
	}
}


BOOL LLViewerWindow::checkSettings()
{
	BOOL is_fullscreen = gViewerWindow->mWindow->getFullscreen();
	if (is_fullscreen && !mWantFullscreen)
	{
		gViewerWindow->changeDisplaySettings(FALSE, 
											 LLCoordScreen(gSavedSettings.getS32("WindowWidth"),
														   gSavedSettings.getS32("WindowHeight")),
											 TRUE,
											 mShowFullscreenProgress);
		return TRUE;
	}
	else if (!is_fullscreen && mWantFullscreen)
	{
		if (!LLStartUp::canGoFullscreen())
		{
			return FALSE;
		}
		
		gViewerWindow->changeDisplaySettings(TRUE, 
											 LLCoordScreen(gSavedSettings.getS32("FullScreenWidth"),
														   gSavedSettings.getS32("FullScreenHeight")),
											 gSavedSettings.getBOOL("DisableVerticalSync"),
											 mShowFullscreenProgress);
		return TRUE;
	}
	return FALSE;
}

void LLViewerWindow::restartDisplay(BOOL show_progress_bar)
{
	llinfos << "Restaring GL" << llendl;
	stopGL();
	if (show_progress_bar)
	{
		restoreGL("Changing Resolution...");
	}
	else
	{
		restoreGL();
	}
}

BOOL LLViewerWindow::changeDisplaySettings(BOOL fullscreen, LLCoordScreen size, BOOL disable_vsync, BOOL show_progress_bar)
{
	BOOL was_maximized = gSavedSettings.getBOOL("WindowMaximized");
	mWantFullscreen = fullscreen;
	mShowFullscreenProgress = show_progress_bar;
	gSavedSettings.setBOOL("FullScreen", mWantFullscreen);

	BOOL old_fullscreen = mWindow->getFullscreen();
	if (!old_fullscreen && fullscreen && !LLStartUp::canGoFullscreen())
	{
		// we can't do this now, so do it later
		
		gSavedSettings.setS32("FullScreenWidth", size.mX);
		gSavedSettings.setS32("FullScreenHeight", size.mY);
		//gSavedSettings.setBOOL("DisableVerticalSync", disable_vsync);
		
		return TRUE;	// a lie..., because we'll get to it later
	}

	// going from windowed to windowed
	if (!old_fullscreen && !fullscreen)
	{
		// if not maximized, use the request size
		if (!mWindow->getMaximized())
		{
			mWindow->setSize(size);
		}
		return TRUE;
	}

	// Close floaters that don't handle settings change
	LLFloaterSnapshot::hide(0);
	
	BOOL result_first_try = FALSE;
	BOOL result_second_try = FALSE;

	LLUICtrl* keyboard_focus = gFocusMgr.getKeyboardFocus();
	send_agent_pause();
	llinfos << "Stopping GL during changeDisplaySettings" << llendl;
	stopGL();
	mIgnoreActivate = TRUE;
	LLCoordScreen old_size;
	LLCoordScreen old_pos;
	mWindow->getSize(&old_size);
	BOOL got_position = mWindow->getPosition(&old_pos);

	if (!old_fullscreen && fullscreen && got_position)
	{
		// switching from windowed to fullscreen, so save window position
		gSavedSettings.setS32("WindowX", old_pos.mX);
		gSavedSettings.setS32("WindowY", old_pos.mY);
	}
	
	result_first_try = mWindow->switchContext(fullscreen, size, disable_vsync);
	if (!result_first_try)
	{
		// try to switch back
		result_second_try = mWindow->switchContext(old_fullscreen, old_size, disable_vsync);

		if (!result_second_try)
		{
			// we are stuck...try once again with a minimal resolution?
			send_agent_resume();
			mIgnoreActivate = FALSE;
			return FALSE;
		}
	}
	send_agent_resume();

	llinfos << "Restoring GL during resolution change" << llendl;
	if (show_progress_bar)
	{
		restoreGL("Changing Resolution...");
	}
	else
	{
		restoreGL();
	}

	if (!result_first_try)
	{
		LLStringBase<char>::format_map_t args;
		args["[RESX]"] = llformat("%d",size.mX);
		args["[RESY]"] = llformat("%d",size.mY);
		alertXml("ResolutionSwitchFail", args);
		size = old_size; // for reshape below
	}

	BOOL success = result_first_try || result_second_try;
	if (success)
	{
#if LL_WINDOWS
		// Only trigger a reshape after switching to fullscreen; otherwise rely on the windows callback
		// (otherwise size is wrong; this is the entire window size, reshape wants the visible window size)
		if (fullscreen)
#endif
		{
			reshape(size.mX, size.mY);
		}
	}

	if (!mWindow->getFullscreen() && success)
	{
		// maximize window if was maximized, else reposition
		if (was_maximized)
		{
			mWindow->maximize();
		}
		else
		{
			S32 windowX = gSavedSettings.getS32("WindowX");
			S32 windowY = gSavedSettings.getS32("WindowY");

			mWindow->setPosition(LLCoordScreen ( windowX, windowY ) );
		}
	}

	mIgnoreActivate = FALSE;
	gFocusMgr.setKeyboardFocus(keyboard_focus, NULL);
	mWantFullscreen = mWindow->getFullscreen();
	mShowFullscreenProgress = FALSE;
	
	return success;
}


F32 LLViewerWindow::getDisplayAspectRatio() const
{
	if (mWindow->getFullscreen())
	{
		if (gSavedSettings.getBOOL("FullScreenAutoDetectAspectRatio"))
		{
			return mWindow->getNativeAspectRatio();	
		}
		else
		{
			return gSavedSettings.getF32("FullScreenAspectRatio");
		}
	}
	else
	{
		return mWindow->getNativeAspectRatio();
	}
}


void LLViewerWindow::drawPickBuffer() const
{
	if (mPickBuffer)
	{
		LLGLDisable no_blend(GL_BLEND);
		LLGLDisable no_alpha_test(GL_ALPHA_TEST);
		LLGLSNoTexture no_texture;
		glPixelZoom(10.f, 10.f);
		glRasterPos2f(((F32)mPickPoint.mX * mDisplayScale.mV[VX] + 10.f), 
			((F32)mPickPoint.mY * mDisplayScale.mV[VY] + 10.f));
		glDrawPixels(PICK_DIAMETER, PICK_DIAMETER, GL_RGBA, GL_UNSIGNED_BYTE, mPickBuffer);
		glPixelZoom(1.f, 1.f);
		glColor4fv(LLColor4::white.mV);
		gl_rect_2d(llround((F32)mPickPoint.mX * mDisplayScale.mV[VX] - (F32)(PICK_HALF_WIDTH)), 
			llround((F32)mPickPoint.mY * mDisplayScale.mV[VY] + (F32)(PICK_HALF_WIDTH)),
			llround((F32)mPickPoint.mX * mDisplayScale.mV[VX] + (F32)(PICK_HALF_WIDTH)),
			llround((F32)mPickPoint.mY * mDisplayScale.mV[VY] - (F32)(PICK_HALF_WIDTH)),
			FALSE);
		gl_line_2d(llround((F32)mPickPoint.mX * mDisplayScale.mV[VX] - (F32)(PICK_HALF_WIDTH)), 
			llround((F32)mPickPoint.mY * mDisplayScale.mV[VY] + (F32)(PICK_HALF_WIDTH)),
			llround((F32)mPickPoint.mX * mDisplayScale.mV[VX] + 10.f), 
			llround((F32)mPickPoint.mY * mDisplayScale.mV[VY] + (F32)(PICK_DIAMETER) * 10.f + 10.f));
		gl_line_2d(llround((F32)mPickPoint.mX * mDisplayScale.mV[VX] + (F32)(PICK_HALF_WIDTH)),
			llround((F32)mPickPoint.mY * mDisplayScale.mV[VY] - (F32)(PICK_HALF_WIDTH)),
			llround((F32)mPickPoint.mX * mDisplayScale.mV[VX] + (F32)(PICK_DIAMETER) * 10.f + 10.f), 
			llround((F32)mPickPoint.mY * mDisplayScale.mV[VY] + 10.f));
		glTranslatef(10.f, 10.f, 0.f);
		gl_rect_2d(llround((F32)mPickPoint.mX * mDisplayScale.mV[VX]), 
			llround((F32)mPickPoint.mY * mDisplayScale.mV[VY] + (F32)(PICK_DIAMETER) * 10.f),
			llround((F32)mPickPoint.mX * mDisplayScale.mV[VX] + (F32)(PICK_DIAMETER) * 10.f),
			llround((F32)mPickPoint.mY * mDisplayScale.mV[VY]),
			FALSE);
		gl_rect_2d(llround((F32)mPickPoint.mX * mDisplayScale.mV[VX] + (F32)(PICK_HALF_WIDTH + mPickOffset.mX)* 10.f), 
			llround((F32)mPickPoint.mY * mDisplayScale.mV[VY] + (F32)(PICK_HALF_WIDTH + mPickOffset.mY + 1) * 10.f),
			llround((F32)mPickPoint.mX * mDisplayScale.mV[VX] + (F32)(PICK_HALF_WIDTH + mPickOffset.mX + 1) * 10.f),
			llround((F32)mPickPoint.mY * mDisplayScale.mV[VY] + (F32)(PICK_HALF_WIDTH  + mPickOffset.mY) * 10.f),
			FALSE);
		glPopMatrix();
	}
}

void LLViewerWindow::calcDisplayScale()
{
	F32 ui_scale_factor = gSavedSettings.getF32("UIScaleFactor");
	LLVector2 display_scale;
	display_scale.setVec(llmax(1.f / mWindow->getPixelAspectRatio(), 1.f), llmax(mWindow->getPixelAspectRatio(), 1.f));
	F32 height_normalization = gSavedSettings.getBOOL("UIAutoScale") ? ((F32)mWindowRect.getHeight() / display_scale.mV[VY]) / 768.f : 1.f;
	if(mWindow->getFullscreen())
	{
		display_scale *= (ui_scale_factor * height_normalization);
	}
	else
	{
		display_scale *= ui_scale_factor;
	}

	// limit minimum display scale
	if (display_scale.mV[VX] < MIN_DISPLAY_SCALE || display_scale.mV[VY] < MIN_DISPLAY_SCALE)
	{
		display_scale *= MIN_DISPLAY_SCALE / llmin(display_scale.mV[VX], display_scale.mV[VY]);
	}

	if (mWindow->getFullscreen())
	{
		display_scale.mV[0] = llround(display_scale.mV[0], 2.0f/(F32) mWindowRect.getWidth());
		display_scale.mV[1] = llround(display_scale.mV[1], 2.0f/(F32) mWindowRect.getHeight());
	}
	
	if (display_scale != mDisplayScale)
	{
		llinfos << "Setting display scale to " << display_scale << llendl;

		mDisplayScale = display_scale;
		// Init default fonts
		initFonts();
	}
}

//----------------------------------------------------------------------------

// static
bool LLViewerWindow::alertCallback(S32 modal)
{
	if (gNoRender)
	{
		return false;
	}
	else
	{
// 		if (modal) // we really always want to take you out of mouselook
		{
			// If we're in mouselook, the mouse is hidden and so the user can't click 
			// the dialog buttons.  In that case, change to First Person instead.
			if( gAgent.cameraMouselook() )
			{
				gAgent.changeCameraToDefault();
			}
		}
		return true;
	}
}

LLAlertDialog* LLViewerWindow::alertXml(const std::string& xml_filename,
							  LLAlertDialog::alert_callback_t callback, void* user_data)
{
	LLString::format_map_t args;
	return alertXml( xml_filename, args, callback, user_data );
}

LLAlertDialog* LLViewerWindow::alertXml(const std::string& xml_filename, const LLString::format_map_t& args,
							  LLAlertDialog::alert_callback_t callback, void* user_data)
{
	if (gNoRender)
	{
		llinfos << "Alert: " << xml_filename << llendl;
		if (callback)
		{
			callback(-1, user_data);
		}
		return NULL;
	}

	// If we're in mouselook, the mouse is hidden and so the user can't click 
	// the dialog buttons.  In that case, change to First Person instead.
	if( gAgent.cameraMouselook() )
	{
		gAgent.changeCameraToDefault();
	}

	// Note: object adds, removes, and destroys itself.
	return LLAlertDialog::showXml( xml_filename, args, callback, user_data );
}

LLAlertDialog* LLViewerWindow::alertXmlEditText(const std::string& xml_filename, const LLString::format_map_t& args,
									  LLAlertDialog::alert_callback_t callback, void* user_data,
									  LLAlertDialog::alert_text_callback_t text_callback, void *text_data,
									  const LLString::format_map_t& edit_args, BOOL draw_asterixes)
{
	if (gNoRender)
	{
		llinfos << "Alert: " << xml_filename << llendl;
		if (callback)
		{
			callback(-1, user_data);
		}
		return NULL;
	}

	// If we're in mouselook, the mouse is hidden and so the user can't click 
	// the dialog buttons.  In that case, change to First Person instead.
	if( gAgent.cameraMouselook() )
	{
		gAgent.changeCameraToDefault();
	}

	// Note: object adds, removes, and destroys itself.
	LLAlertDialog* alert = LLAlertDialog::createXml( xml_filename, args, callback, user_data );
	if (alert)
	{
		if (text_callback)
		{
			alert->setEditTextCallback(text_callback, text_data);
		}
		alert->setEditTextArgs(edit_args);
		alert->setDrawAsterixes(draw_asterixes);
		alert->show();
	}
	return alert;
}

LLBottomPanel::LLBottomPanel(const LLString &name, const LLRect &rect) : 
	LLPanel(name, rect, FALSE),
	mIndicator(NULL)
{
	// bottom panel is focus root, so Tab moves through the toolbar and button bar, and overlay
	setFocusRoot(TRUE);
	// don't capture mouse clicks that don't hit a child
	setMouseOpaque(FALSE);
	setFollows(FOLLOWS_LEFT | FOLLOWS_RIGHT | FOLLOWS_BOTTOM);
	setIsChrome(TRUE);
}

void LLBottomPanel::setFocusIndicator(LLView * indicator)
{
	mIndicator = indicator;
}

void LLBottomPanel::draw()
{
	if(mIndicator)
	{
		BOOL hasFocus = gFocusMgr.childHasKeyboardFocus(this);
		mIndicator->setVisible(hasFocus);
		mIndicator->setEnabled(hasFocus);
	}
	LLPanel::draw();
}
