5192 lines
137 KiB
C++
5192 lines
137 KiB
C++
/**
|
|
* @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"
|
|
|
|
#include "llvoiceclient.h" // for push-to-talk button handling
|
|
|
|
#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 "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 "llfloateractivespeakers.h"
|
|
#include "llfloaterbuildoptions.h"
|
|
#include "llfloaterbuyland.h"
|
|
#include "llfloaterchat.h"
|
|
#include "llfloaterchatterbox.h"
|
|
#include "llfloatercustomize.h"
|
|
#include "llfloatereditui.h" // HACK JAMESDEBUG for ui editor
|
|
#include "llfloaterland.h"
|
|
#include "llfloaterinspect.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 "lltexturecache.h"
|
|
#include "lltexturefetch.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;
|
|
#if !LL_SOLARIS
|
|
MovieMaker gMovieMaker;
|
|
#endif
|
|
|
|
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
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// LLDebugText
|
|
//
|
|
|
|
class LLDebugText
|
|
{
|
|
private:
|
|
struct Line
|
|
{
|
|
Line(const std::string& in_text, S32 in_x, S32 in_y) : text(in_text), x(in_x), y(in_y) {}
|
|
std::string text;
|
|
S32 x,y;
|
|
};
|
|
|
|
LLViewerWindow *mWindow;
|
|
|
|
typedef std::vector<Line> line_list_t;
|
|
line_list_t mLineList;
|
|
LLColor4 mTextColor;
|
|
|
|
public:
|
|
LLDebugText(LLViewerWindow* window) : mWindow(window) {}
|
|
|
|
void addText(S32 x, S32 y, const std::string &text)
|
|
{
|
|
mLineList.push_back(Line(text, x, y));
|
|
}
|
|
|
|
void update()
|
|
{
|
|
std::string wind_vel_text;
|
|
std::string wind_vector_text;
|
|
std::string rwind_vel_text;
|
|
std::string rwind_vector_text;
|
|
std::string audio_text;
|
|
|
|
// Draw the statistics in a light gray
|
|
// and in a thin font
|
|
mTextColor = LLColor4( 0.86f, 0.86f, 0.86f, 1.f );
|
|
|
|
// Draw stuff growing up from right lower corner of screen
|
|
U32 xpos = mWindow->getWindowWidth() - 350;
|
|
U32 ypos = 64;
|
|
const U32 y_inc = 20;
|
|
|
|
if (gSavedSettings.getBOOL("DebugShowTime"))
|
|
{
|
|
const U32 y_inc2 = 15;
|
|
for (std::map<S32,LLFrameTimer>::reverse_iterator iter = gDebugTimers.rbegin();
|
|
iter != gDebugTimers.rend(); ++iter)
|
|
{
|
|
S32 idx = iter->first;
|
|
LLFrameTimer& timer = iter->second;
|
|
F32 time = timer.getElapsedTimeF32();
|
|
S32 hours = (S32)(time / (60*60));
|
|
S32 mins = (S32)((time - hours*(60*60)) / 60);
|
|
S32 secs = (S32)((time - hours*(60*60) - mins*60));
|
|
addText(xpos, ypos, llformat(" Debug %d: %d:%02d:%02d", idx, hours,mins,secs)); ypos += y_inc2;
|
|
}
|
|
|
|
F32 time = gFrameTimeSeconds;
|
|
S32 hours = (S32)(time / (60*60));
|
|
S32 mins = (S32)((time - hours*(60*60)) / 60);
|
|
S32 secs = (S32)((time - hours*(60*60) - mins*60));
|
|
addText(xpos, ypos, llformat("Time: %d:%02d:%02d", hours,mins,secs)); ypos += y_inc;
|
|
}
|
|
|
|
if (gDisplayCameraPos)
|
|
{
|
|
std::string camera_view_text;
|
|
std::string camera_center_text;
|
|
std::string agent_view_text;
|
|
std::string agent_left_text;
|
|
std::string agent_center_text;
|
|
std::string agent_root_center_text;
|
|
|
|
LLVector3d tvector; // Temporary vector to hold data for printing.
|
|
|
|
// Update camera center, camera view, wind info every other frame
|
|
tvector = gAgent.getPositionGlobal();
|
|
agent_center_text = llformat("AgentCenter %f %f %f",
|
|
(F32)(tvector.mdV[VX]), (F32)(tvector.mdV[VY]), (F32)(tvector.mdV[VZ]));
|
|
|
|
if (gAgent.getAvatarObject())
|
|
{
|
|
tvector = gAgent.getPosGlobalFromAgent(gAgent.getAvatarObject()->mRoot.getWorldPosition());
|
|
agent_root_center_text = llformat("AgentRootCenter %f %f %f",
|
|
(F32)(tvector.mdV[VX]), (F32)(tvector.mdV[VY]), (F32)(tvector.mdV[VZ]));
|
|
}
|
|
else
|
|
{
|
|
agent_root_center_text = "---";
|
|
}
|
|
|
|
|
|
tvector = LLVector4(gAgent.getFrameAgent().getAtAxis());
|
|
agent_view_text = llformat("AgentAtAxis %f %f %f",
|
|
(F32)(tvector.mdV[VX]), (F32)(tvector.mdV[VY]), (F32)(tvector.mdV[VZ]));
|
|
|
|
tvector = LLVector4(gAgent.getFrameAgent().getLeftAxis());
|
|
agent_left_text = llformat("AgentLeftAxis %f %f %f",
|
|
(F32)(tvector.mdV[VX]), (F32)(tvector.mdV[VY]), (F32)(tvector.mdV[VZ]));
|
|
|
|
tvector = gAgent.getCameraPositionGlobal();
|
|
camera_center_text = llformat("CameraCenter %f %f %f",
|
|
(F32)(tvector.mdV[VX]), (F32)(tvector.mdV[VY]), (F32)(tvector.mdV[VZ]));
|
|
|
|
tvector = LLVector4(gCamera->getAtAxis());
|
|
camera_view_text = llformat("CameraAtAxis %f %f %f",
|
|
(F32)(tvector.mdV[VX]), (F32)(tvector.mdV[VY]), (F32)(tvector.mdV[VZ]));
|
|
|
|
addText(xpos, ypos, agent_center_text); ypos += y_inc;
|
|
addText(xpos, ypos, agent_root_center_text); ypos += y_inc;
|
|
addText(xpos, ypos, agent_view_text); ypos += y_inc;
|
|
addText(xpos, ypos, agent_left_text); ypos += y_inc;
|
|
addText(xpos, ypos, camera_center_text); ypos += y_inc;
|
|
addText(xpos, ypos, camera_view_text); ypos += y_inc;
|
|
}
|
|
|
|
if (gDisplayWindInfo)
|
|
{
|
|
wind_vel_text = llformat("Wind velocity %.2f m/s", gWindVec.magVec());
|
|
wind_vector_text = llformat("Wind vector %.2f %.2f %.2f", gWindVec.mV[0], gWindVec.mV[1], gWindVec.mV[2]);
|
|
rwind_vel_text = llformat("RWind vel %.2f m/s", gRelativeWindVec.magVec());
|
|
rwind_vector_text = llformat("RWind vec %.2f %.2f %.2f", gRelativeWindVec.mV[0], gRelativeWindVec.mV[1], gRelativeWindVec.mV[2]);
|
|
|
|
addText(xpos, ypos, wind_vel_text); ypos += y_inc;
|
|
addText(xpos, ypos, wind_vector_text); ypos += y_inc;
|
|
addText(xpos, ypos, rwind_vel_text); ypos += y_inc;
|
|
addText(xpos, ypos, rwind_vector_text); ypos += y_inc;
|
|
}
|
|
if (gDisplayWindInfo)
|
|
{
|
|
if (gAudiop)
|
|
{
|
|
audio_text= llformat("Audio for wind: %d", gAudiop->isWindEnabled());
|
|
}
|
|
addText(xpos, ypos, audio_text); ypos += y_inc;
|
|
}
|
|
if (gDisplayFOV)
|
|
{
|
|
addText(xpos, ypos, llformat("FOV: %2.1f deg", RAD_TO_DEG * gCamera->getView()));
|
|
ypos += y_inc;
|
|
}
|
|
if (gSavedSettings.getBOOL("DebugShowRenderInfo"))
|
|
{
|
|
if (gPipeline.getUseVertexShaders() == 0)
|
|
{
|
|
addText(xpos, ypos, "Shaders Disabled");
|
|
ypos += y_inc;
|
|
}
|
|
addText(xpos, ypos, llformat("%d MB Vertex Data", LLVertexBuffer::sAllocatedBytes/(1024*1024)));
|
|
ypos += y_inc;
|
|
|
|
addText(xpos, ypos, llformat("%d Pending Lock", LLVertexBuffer::sLockedList.size()));
|
|
ypos += y_inc;
|
|
|
|
addText(xpos, ypos, llformat("%d Vertex Buffers", LLVertexBuffer::sGLCount));
|
|
ypos += y_inc;
|
|
}
|
|
|
|
// only display these messages if we are actually rendering beacons at this moment
|
|
if (LLPipeline::getRenderBeacons(NULL) && LLPipeline::getProcessBeacons(NULL))
|
|
{
|
|
if (LLPipeline::getRenderParticleBeacons(NULL))
|
|
{
|
|
addText(xpos, ypos, "Viewing particle beacons (blue)");
|
|
ypos += y_inc;
|
|
}
|
|
if (LLPipeline::toggleRenderTypeControlNegated((void*)LLPipeline::RENDER_TYPE_PARTICLES))
|
|
{
|
|
addText(xpos, ypos, "Hiding particles");
|
|
ypos += y_inc;
|
|
}
|
|
if (LLPipeline::getRenderPhysicalBeacons(NULL))
|
|
{
|
|
addText(xpos, ypos, "Viewing physical object beacons (green)");
|
|
ypos += y_inc;
|
|
}
|
|
if (LLPipeline::getRenderScriptedBeacons(NULL))
|
|
{
|
|
addText(xpos, ypos, "Viewing scripted object beacons (red)");
|
|
ypos += y_inc;
|
|
}
|
|
else
|
|
if (LLPipeline::getRenderScriptedTouchBeacons(NULL))
|
|
{
|
|
addText(xpos, ypos, "Viewing scripted object with touch function beacons (red)");
|
|
ypos += y_inc;
|
|
}
|
|
if (LLPipeline::getRenderSoundBeacons(NULL))
|
|
{
|
|
addText(xpos, ypos, "Viewing sound beacons (yellow)");
|
|
ypos += y_inc;
|
|
}
|
|
}
|
|
}
|
|
|
|
void draw()
|
|
{
|
|
for (line_list_t::iterator iter = mLineList.begin();
|
|
iter != mLineList.end(); ++iter)
|
|
{
|
|
const Line& line = *iter;
|
|
LLFontGL::sMonospace->renderUTF8(line.text, 0, (F32)line.x, (F32)line.y, mTextColor,
|
|
LLFontGL::LEFT, LLFontGL::TOP,
|
|
LLFontGL::NORMAL, S32_MAX, S32_MAX, NULL, FALSE);
|
|
}
|
|
mLineList.clear();
|
|
}
|
|
|
|
};
|
|
|
|
void LLViewerWindow::updateDebugText()
|
|
{
|
|
mDebugText->update();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// 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;
|
|
}
|
|
|
|
if (gMenuBarView)
|
|
{
|
|
// stop ALT-key access to menu
|
|
gMenuBarView->resetMenuTrigger();
|
|
}
|
|
|
|
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 )
|
|
{
|
|
mToolTipBlocked = TRUE;
|
|
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()->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
|
|
LLUICtrl* top_ctrl = gFocusMgr.getTopCtrl();
|
|
BOOL mouse_over_top_ctrl = FALSE;
|
|
if (top_ctrl)
|
|
{
|
|
S32 local_x, local_y;
|
|
top_ctrl->screenPointToLocal( x, y, &local_x, &local_y );
|
|
if (top_ctrl->pointInView(local_x, local_y))
|
|
{
|
|
mouse_over_top_ctrl = TRUE;
|
|
if(top_ctrl->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 = "";
|
|
}
|
|
if (top_ctrl && top_ctrl->hasFocus() && !mouse_over_top_ctrl)
|
|
{
|
|
// always defocus top view if we click off of it
|
|
top_ctrl->setFocus(FALSE);
|
|
}
|
|
return TRUE;
|
|
}
|
|
else if (LLView::sDebugMouseHandling)
|
|
{
|
|
llinfos << "Left Mouse Down not handled by view" << llendl;
|
|
}
|
|
|
|
if (top_ctrl && top_ctrl->hasFocus() && !mouse_over_top_ctrl)
|
|
{
|
|
// always defocus top view if we click off of it
|
|
top_ctrl->setFocus(FALSE);
|
|
}
|
|
|
|
if (gDisconnected)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if (gToolMgr)
|
|
{
|
|
if(gToolMgr->getCurrentTool()->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.
|
|
LLUICtrl* top_ctrl = gFocusMgr.getTopCtrl();
|
|
BOOL mouse_over_top_ctrl = FALSE;
|
|
if (top_ctrl)
|
|
{
|
|
S32 local_x, local_y;
|
|
top_ctrl->screenPointToLocal( x, y, &local_x, &local_y );
|
|
if (top_ctrl->pointInView(local_x, local_y))
|
|
{
|
|
mouse_over_top_ctrl = TRUE;
|
|
if(top_ctrl->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 = "";
|
|
}
|
|
if (top_ctrl && top_ctrl->hasFocus() && !mouse_over_top_ctrl)
|
|
{
|
|
// always defocus top view if we click off of it
|
|
top_ctrl->setFocus(FALSE);
|
|
}
|
|
return TRUE;
|
|
}
|
|
else if (LLView::sDebugMouseHandling)
|
|
{
|
|
llinfos << "Left Mouse Down not handled by view" << llendl;
|
|
}
|
|
|
|
if (top_ctrl && top_ctrl->hasFocus() && !mouse_over_top_ctrl)
|
|
{
|
|
// always defocus top view if we click off of it
|
|
top_ctrl->setFocus(FALSE);
|
|
}
|
|
|
|
// Why is this here? JC 9/3/2002
|
|
if (gNoRender)
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
if (gToolMgr)
|
|
{
|
|
if(gToolMgr->getCurrentTool()->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();
|
|
|
|
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);
|
|
}
|
|
|
|
LLUICtrl* top_ctrl = gFocusMgr.getTopCtrl();
|
|
if (top_ctrl)
|
|
{
|
|
S32 local_x, local_y;
|
|
top_ctrl->screenPointToLocal( x, y, &local_x, &local_y );
|
|
handled = top_ctrl->pointInView(local_x, local_y) && top_ctrl->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;
|
|
}
|
|
|
|
if (gMenuBarView)
|
|
{
|
|
// stop ALT-key access to menu
|
|
gMenuBarView->resetMenuTrigger();
|
|
}
|
|
|
|
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()->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);
|
|
}
|
|
|
|
LLUICtrl* top_ctrl = gFocusMgr.getTopCtrl();
|
|
BOOL mouse_over_top_ctrl = FALSE;
|
|
if (top_ctrl)
|
|
{
|
|
S32 local_x, local_y;
|
|
top_ctrl->screenPointToLocal( x, y, &local_x, &local_y );
|
|
if (top_ctrl->pointInView(local_x, local_y))
|
|
{
|
|
mouse_over_top_ctrl = TRUE;
|
|
if(top_ctrl->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 = "";
|
|
}
|
|
if (top_ctrl && top_ctrl->hasFocus() && !mouse_over_top_ctrl)
|
|
{
|
|
// always defocus top view if we click off of it
|
|
top_ctrl->setFocus(FALSE);
|
|
}
|
|
return TRUE;
|
|
}
|
|
else if (LLView::sDebugMouseHandling)
|
|
{
|
|
llinfos << "Right Mouse Down not handled by view" << llendl;
|
|
}
|
|
|
|
if (top_ctrl && top_ctrl->hasFocus() && !mouse_over_top_ctrl)
|
|
{
|
|
// always defocus top view if we click off of it
|
|
top_ctrl->setFocus(FALSE);
|
|
}
|
|
|
|
if (gToolMgr)
|
|
{
|
|
if(gToolMgr->getCurrentTool()->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;
|
|
}
|
|
}
|
|
|
|
// *HACK: 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();
|
|
|
|
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);
|
|
}
|
|
|
|
LLUICtrl* top_ctrl = gFocusMgr.getTopCtrl();
|
|
if (top_ctrl)
|
|
{
|
|
S32 local_x, local_y;
|
|
top_ctrl->screenPointToLocal( x, y, &local_x, &local_y );
|
|
handled = top_ctrl->pointInView(local_x, local_y) && top_ctrl->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;
|
|
}
|
|
|
|
BOOL LLViewerWindow::handleMiddleMouseDown(LLWindow *window, LLCoordGL pos, MASK mask)
|
|
{
|
|
gVoiceClient->middleMouseState(true);
|
|
|
|
// Always handled as far as the OS is concerned.
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL LLViewerWindow::handleMiddleMouseUp(LLWindow *window, LLCoordGL pos, MASK mask)
|
|
{
|
|
gVoiceClient->middleMouseState(false);
|
|
|
|
// 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 = !gFocusMgr.getMouseCapture() && // mouse is not currenty captured
|
|
((prev_saved_mouse_point.mX != mCurrentMousePoint.mX) || (prev_saved_mouse_point.mY != mCurrentMousePoint.mY)); // mouse moved from last recorded position
|
|
|
|
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_user_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 );
|
|
|
|
if (gMenuBarView)
|
|
{
|
|
// stop ALT-key access to menu
|
|
gMenuBarView->resetMenuTrigger();
|
|
}
|
|
|
|
// 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)
|
|
{
|
|
// Let the voice chat code check for its PTT key. Note that this never affects event processing.
|
|
gVoiceClient->keyDown(key, mask);
|
|
|
|
if (gAwayTimer.getElapsedTimeF32() > MIN_AFK_TIME)
|
|
{
|
|
gAgent.clearAFK();
|
|
}
|
|
|
|
// *NOTE: 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)
|
|
{
|
|
// Let the voice chat code check for its PTT key. Note that this never affects event processing.
|
|
gVoiceClient->keyUp(key, 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 (LLStartUp::getStartupState() >= 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
|
|
audio_update_volume();
|
|
}
|
|
else
|
|
{
|
|
mActive = FALSE;
|
|
if (gAllowIdleAFK) {
|
|
gAgent.setAFK();
|
|
}
|
|
send_agent_pause();
|
|
if (mWindow->getFullscreen() && !mIgnoreActivate)
|
|
{
|
|
llinfos << "Stopping GL during deactivation" << llendl;
|
|
stopGL();
|
|
}
|
|
// Mute audio
|
|
audio_update_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 = (HWND)window->getPlatformWindow();
|
|
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]; /* Flawfinder: ignore */
|
|
snprintf(temp_str, sizeof(temp_str), "%s FPS %3.1f Phy FPS %2.1f Time Dil %1.3f", /* Flawfinder: ignore */
|
|
name_str.c_str(),
|
|
gViewerStats->mFPSStat.getMeanPerSec(),
|
|
gViewerStats->mSimPhysicsFPS.getPrev(0),
|
|
gViewerStats->mSimTimeDilation.getPrev(0));
|
|
len = strlen(temp_str); /* Flawfinder: ignore */
|
|
TextOutA(hdc, 0, 0, temp_str, len);
|
|
|
|
|
|
LLVector3d pos_global = gAgent.getPositionGlobal();
|
|
snprintf(temp_str, sizeof(temp_str), "Avatar pos %6.1lf %6.1lf %6.1lf", pos_global.mdV[0], pos_global.mdV[1], pos_global.mdV[2]); /* Flawfinder: ignore */
|
|
len = strlen(temp_str); /* Flawfinder: ignore */
|
|
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 )
|
|
{
|
|
// Default to application directory.
|
|
strcpy(LLViewerWindow::sSnapshotBaseName, "Snapshot"); /* Flawfinder: ignore */
|
|
strcpy(LLViewerWindow::sMovieBaseName, "SLmovie"); /* Flawfinder: ignore */
|
|
LLViewerWindow::sSnapshotDir[0] = '\0';
|
|
|
|
|
|
// 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 || LL_SOLARIS
|
|
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 or README-solaris.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();
|
|
|
|
// Initialize OpenGL Renderer
|
|
|
|
if (!gFeatureManagerp->isFeatureAvailable("RenderVBO") ||
|
|
!gGLManager.mHasVertexBufferObject)
|
|
{
|
|
gSavedSettings.setBOOL("RenderVBOEnable", FALSE);
|
|
}
|
|
LLVertexBuffer::initClass(gSavedSettings.getBOOL("RenderVBOEnable"));
|
|
|
|
//
|
|
// 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 we crashed while initializng GL stuff last time, disable certain features
|
|
if (gSavedSettings.getBOOL("RenderInitError"))
|
|
{
|
|
mInitAlert = "DisplaySettingsNoShaders";
|
|
gSavedSettings.setBOOL("VertexShaderEnable", FALSE);
|
|
}
|
|
|
|
if (!gNoRender)
|
|
{
|
|
//
|
|
// Initialize GL stuff
|
|
//
|
|
|
|
// Set this flag in case we crash while initializing GL
|
|
gSavedSettings.setBOOL("RenderInitError", TRUE);
|
|
gSavedSettings.saveToFile( gSettingsFileName, TRUE );
|
|
|
|
gPipeline.init();
|
|
stop_glerror();
|
|
initGLDefaults();
|
|
|
|
gSavedSettings.setBOOL("RenderInitError", FALSE);
|
|
gSavedSettings.saveToFile( gSettingsFileName, TRUE );
|
|
}
|
|
|
|
//
|
|
// 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();
|
|
LLViewerImage::initClass();
|
|
gBumpImageList.init();
|
|
|
|
// Create container for all sub-views
|
|
mRootView = new LLRootView("root", mVirtualWindowRect, 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, '_', ' ');
|
|
|
|
LLAlertDialog::setDisplayCallback(alertCallback); // call this before calling any modal dialogs
|
|
|
|
// sync the keyboard's setting with the saved setting
|
|
gSavedSettings.getControl("NumpadControl")->firePropertyChanged();
|
|
|
|
mDebugText = new LLDebugText(this);
|
|
|
|
}
|
|
|
|
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_container", 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->setFontStyle(LLFontGL::NORMAL);
|
|
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_right("FloaterLagMeter", 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);
|
|
|
|
if ( gToolBar == NULL ) // Don't re-enter if objects are alreay created
|
|
{
|
|
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);
|
|
}
|
|
|
|
gIMMgr = LLIMMgr::getInstance();
|
|
|
|
if ( gSavedPerAccountSettings.getBOOL("LogShowHistory") )
|
|
{
|
|
LLFloaterChat::getInstance(LLSD())->loadHistory();
|
|
}
|
|
|
|
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
|
|
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());
|
|
// sync bg color with menu bar
|
|
gStatusBar->setBackgroundColor( gMenuBarView->getBackgroundColor() );
|
|
|
|
LLFloaterChatterBox::createInstance(LLSD());
|
|
|
|
gViewerWindow->getRootView()->addChild(gStatusBar);
|
|
|
|
// menu holder appears on top to get first pass at all mouse events
|
|
gViewerWindow->getRootView()->sendChildToFront(gMenuHolder);
|
|
}
|
|
}
|
|
|
|
|
|
LLViewerWindow::~LLViewerWindow()
|
|
{
|
|
delete mDebugText;
|
|
|
|
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;
|
|
gIMMgr = NULL;
|
|
gHoverView = NULL;
|
|
|
|
gFloaterView = NULL;
|
|
gMorphView = 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();
|
|
|
|
llinfos << "Cleaning up pipeline" << llendl;
|
|
gPipeline.cleanup();
|
|
stop_glerror();
|
|
|
|
LLViewerImage::cleanupClass();
|
|
|
|
delete[] mPickBuffer;
|
|
mPickBuffer = NULL;
|
|
|
|
if (gSelectMgr)
|
|
{
|
|
llinfos << "Cleaning up select manager" << llendl;
|
|
gSelectMgr->cleanup();
|
|
}
|
|
|
|
LLVertexBuffer::cleanupClass();
|
|
|
|
llinfos << "Stopping GL during shutdown" << llendl;
|
|
if (!gNoRender)
|
|
{
|
|
stopGL(FALSE);
|
|
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);
|
|
}
|
|
}
|
|
|
|
|
|
// Hide normal UI when a logon fails
|
|
void LLViewerWindow::setNormalControlsVisible( BOOL visible )
|
|
{
|
|
if ( gBottomPanel )
|
|
gBottomPanel->setVisible( visible );
|
|
|
|
if ( gMenuBarView )
|
|
gMenuBarView->setVisible( visible );
|
|
|
|
if ( gStatusBar )
|
|
gStatusBar->setVisible( visible );
|
|
}
|
|
|
|
|
|
|
|
|
|
void LLViewerWindow::drawDebugText()
|
|
{
|
|
mDebugText->draw();
|
|
}
|
|
|
|
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]; /* Flawfinder: ignore */
|
|
|
|
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;
|
|
drawDebugText();
|
|
}
|
|
|
|
if (gToolMgr)
|
|
{
|
|
// Draw tool specific overlay on world
|
|
gToolMgr->getCurrentTool()->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
|
|
LLUICtrl* top_ctrl = gFocusMgr.getTopCtrl();
|
|
if (top_ctrl && top_ctrl->getVisible())
|
|
{
|
|
S32 screen_x, screen_y;
|
|
top_ctrl->localPointToScreen(0, 0, &screen_x, &screen_y);
|
|
|
|
glMatrixMode(GL_MODELVIEW);
|
|
LLUI::pushMatrix();
|
|
LLUI::translate( (F32) screen_x, (F32) screen_y, 0.f);
|
|
top_ctrl->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;
|
|
}
|
|
|
|
// *TODO: get this to play well with mouselook and hidden
|
|
// cursor modes, etc, and re-enable.
|
|
//if (gFocusMgr.getMouseCapture())
|
|
//{
|
|
// gFocusMgr.setMouseCapture(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:
|
|
// let CTRL UP through for chat line history
|
|
if( MASK_CONTROL == mask )
|
|
{
|
|
break;
|
|
}
|
|
case KEY_DOWN:
|
|
// let CTRL DOWN through for chat line history
|
|
if( MASK_CONTROL == mask )
|
|
{
|
|
break;
|
|
}
|
|
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()->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
|
|
// *FIX: get rid of this?
|
|
LLUICtrl* top_ctrl = gFocusMgr.getTopCtrl();
|
|
if (top_ctrl)
|
|
{
|
|
if( top_ctrl->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)
|
|
|| LLMenuGL::getKeyboardMode()
|
|
|| (gMenuBarView && gMenuBarView->getHighlightedItem() && gMenuBarView->getHighlightedItem()->isActive());
|
|
}
|
|
|
|
|
|
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));
|
|
}
|
|
|
|
// let menus handle navigation (jump) keys
|
|
if (gMenuBarView && gMenuBarView->handleUnicodeChar(uni_char, TRUE))
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
// 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
|
|
LLUICtrl* top_ctrl = gFocusMgr.getTopCtrl();
|
|
if (top_ctrl && top_ctrl->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;
|
|
}
|
|
|
|
LLUICtrl* top_ctrl = gFocusMgr.getTopCtrl();
|
|
if (top_ctrl)
|
|
{
|
|
S32 local_x;
|
|
S32 local_y;
|
|
top_ctrl->screenPointToLocal( mCurrentMousePoint.mX, mCurrentMousePoint.mY, &local_x, &local_y );
|
|
if (top_ctrl->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()
|
|
{
|
|
S32 x = mVirtualWindowRect.getWidth() / 2;
|
|
S32 y = mVirtualWindowRect.getHeight() / 2;
|
|
|
|
//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);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
//
|
|
// 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 = lltrunc((F32) (mCurrentMousePoint.mX - mLastMousePoint.mX) * LLUI::sGLScaleFactor.mV[VX]);
|
|
S32 dy = lltrunc((F32) (mCurrentMousePoint.mY - mLastMousePoint.mY) * LLUI::sGLScaleFactor.mV[VY]);
|
|
|
|
LLVector2 mouse_vel;
|
|
|
|
if (gSavedSettings.getBOOL("MouseSmooth"))
|
|
{
|
|
static F32 fdx = 0.f;
|
|
static F32 fdy = 0.f;
|
|
|
|
F32 amount = 16.f;
|
|
fdx = fdx + ((F32) dx - fdx) * llmin(gFrameIntervalSeconds*amount,1.f);
|
|
fdy = fdy + ((F32) dy - fdy) * llmin(gFrameIntervalSeconds*amount,1.f);
|
|
|
|
mCurrentMouseDelta.set(llround(fdx), llround(fdy));
|
|
mouse_vel.setVec(fdx,fdy);
|
|
}
|
|
else
|
|
{
|
|
mCurrentMouseDelta.set(dx, dy);
|
|
mouse_vel.setVec((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();
|
|
}
|
|
}
|
|
|
|
gPipeline.sRenderProcessBeacons = FALSE;
|
|
KEY key = gKeyboard->currentKey();
|
|
if (((mask & MASK_CONTROL) && ('N' == key || 'n' == key)) || gSavedSettings.getBOOL("BeaconAlwaysOn"))
|
|
{
|
|
gPipeline.sRenderProcessBeacons = TRUE;
|
|
}
|
|
|
|
BOOL handled = FALSE;
|
|
|
|
BOOL handled_by_top_ctrl = FALSE;
|
|
LLUICtrl* top_ctrl = gFocusMgr.getTopCtrl();
|
|
|
|
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_ctrl)
|
|
{
|
|
S32 local_x, local_y;
|
|
top_ctrl->screenPointToLocal( x, y, &local_x, &local_y );
|
|
handled = top_ctrl->pointInView(local_x, local_y) && top_ctrl->handleHover(local_x, local_y, mask);
|
|
handled_by_top_ctrl = 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;
|
|
}
|
|
}
|
|
|
|
// *NOTE: sometimes tools handle the mouse as a captor, so this
|
|
// logic is a little confusing
|
|
LLTool *tool = NULL;
|
|
if (gToolMgr && gHoverView)
|
|
{
|
|
tool = gToolMgr->getCurrentTool();
|
|
|
|
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->getBaseTool() == gToolPie) &&
|
|
(gToolMgr->getCurrentTool() != 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_ctrl)
|
|
{
|
|
S32 local_x, local_y;
|
|
top_ctrl->screenPointToLocal( x, y, &local_x, &local_y );
|
|
tool_tip_handled = top_ctrl->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 != gToolInspect && tool != gToolDragAndDrop && !gSavedSettings.getBOOL("FreezeTime"))
|
|
{
|
|
LLMouseHandler *captor = gFocusMgr.getMouseCapture();
|
|
// With the null, inspect, 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
|
|
&& gToolMgr->getCurrentToolset() != gFaceEditToolset // not special mode
|
|
&& gToolMgr->getCurrentToolset() != gMouselookToolset
|
|
&& (!captor || captor->isView())) // not dragging
|
|
)
|
|
{
|
|
// Force floater tools to be visible (unless minimized)
|
|
if (!gFloaterTools->getVisible())
|
|
{
|
|
gFloaterTools->open(); /* Flawfinder: ignore */
|
|
}
|
|
// 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())
|
|
{
|
|
// *NOTE: 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->getSelection()->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);
|
|
}
|
|
|
|
// cleanup unused selections when no modal dialogs are open
|
|
if (gParcelMgr && LLModalDialog::activeCount() == 0)
|
|
{
|
|
gParcelMgr->deselectUnused();
|
|
}
|
|
|
|
if (gSelectMgr && LLModalDialog::activeCount() == 0)
|
|
{
|
|
gSelectMgr->deselectUnused();
|
|
}
|
|
|
|
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;
|
|
LLObjectSelectionHandle selection = gSelectMgr->getSelection();
|
|
|
|
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 && selection->getSelectType() == SELECT_TYPE_HUD) ||
|
|
(!for_hud && selection->getSelectType() != SELECT_TYPE_HUD))
|
|
{
|
|
gSelectMgr->renderSilhouettes(for_hud);
|
|
|
|
stop_glerror();
|
|
|
|
// setup HUD render
|
|
if (selection->getSelectType() == SELECT_TYPE_HUD && gSelectMgr->getSelection()->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 (selection->getSelectType() == SELECT_TYPE_HUD)
|
|
{
|
|
F32 zoom = gAgent.getAvatarObject()->mHUDCurZoom;
|
|
glScalef(zoom, zoom, zoom);
|
|
}
|
|
for( object = gSelectMgr->getSelection()->getFirstObject(); object; object = gSelectMgr->getSelection()->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();
|
|
if (tool)
|
|
{
|
|
if(tool->isAlwaysRendered())
|
|
{
|
|
tool->render();
|
|
}
|
|
else
|
|
{
|
|
if( !gSelectMgr->getSelection()->isEmpty() )
|
|
{
|
|
BOOL moveable_object_selected = FALSE;
|
|
BOOL all_selected_objects_move = TRUE;
|
|
BOOL all_selected_objects_modify = TRUE;
|
|
BOOL selecting_linked_set = !gSavedSettings.getBOOL("EditLinkedParts");
|
|
for( object = gSelectMgr->getSelection()->getFirstObject(); object; object = gSelectMgr->getSelection()->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 (selection->getSelectType() == SELECT_TYPE_HUD && selection->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]);
|
|
|
|
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;
|
|
|
|
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, 0.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, 0.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;
|
|
}
|
|
|
|
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)
|
|
{
|
|
LLViewerObject* parent = (LLViewerObject*)(objectp->getParent());
|
|
if (NULL == parent) {
|
|
// if you are the parent
|
|
parent = objectp;
|
|
}
|
|
std::vector<LLPointer<LLViewerObject>,std::allocator<LLPointer<LLViewerObject> > > children = parent->getChildren();
|
|
for( std::vector<LLPointer<LLViewerObject>,std::allocator<LLPointer<LLViewerObject> > >::iterator i= children.begin(); i!= children.end(); ++i )
|
|
{
|
|
//go through
|
|
LLViewerObject* foo = *i;
|
|
foo->getRotation();
|
|
}
|
|
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 = face;
|
|
LLFace* facep = objectp->mDrawable->getFace(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;
|
|
}
|
|
|
|
// 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 plane_mouse_dot = (plane_normal_global_d * mouse_direction_global_d);
|
|
LLVector3d plane_origin_camera_rel = plane_point_global - gAgent.getCameraPositionGlobal();
|
|
F64 mouse_look_at_scale = (plane_normal_global_d * plane_origin_camera_rel)
|
|
/ plane_mouse_dot;
|
|
if (llabs(plane_mouse_dot) < 0.00001)
|
|
{
|
|
// if mouse is parallel to plane, return closest point on line through plane origin
|
|
// that is parallel to camera plane by scaling mouse direction vector
|
|
// by distance to plane origin, modulated by deviation of mouse direction from plane origin
|
|
LLVector3d plane_origin_dir = plane_origin_camera_rel;
|
|
plane_origin_dir.normVec();
|
|
|
|
mouse_look_at_scale = plane_origin_camera_rel.magVec() / (plane_origin_dir * 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, const LLString& extension_in)
|
|
{
|
|
if (! raw)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
LLString extension(extension_in);
|
|
if (extension.empty())
|
|
{
|
|
extension = (gSavedSettings.getBOOL("CompressSnapshotsToDisk")) ? ".j2c" : ".bmp";
|
|
}
|
|
|
|
LLFilePicker::ESaveFilter pick_type;
|
|
if (extension == ".j2c")
|
|
pick_type = LLFilePicker::FFSAVE_J2C;
|
|
else if (extension == ".bmp")
|
|
pick_type = LLFilePicker::FFSAVE_BMP;
|
|
else if (extension == ".tga")
|
|
pick_type = LLFilePicker::FFSAVE_TGA;
|
|
else
|
|
pick_type = LLFilePicker::FFSAVE_ALL; // ???
|
|
|
|
// Get a directory if this is the first time.
|
|
if (strlen(sSnapshotDir) == 0) /* Flawfinder: ignore */
|
|
{
|
|
LLString proposed_name( sSnapshotBaseName );
|
|
proposed_name.append( extension );
|
|
|
|
// pick a directory in which to save
|
|
LLFilePicker& picker = LLFilePicker::instance();
|
|
if (!picker.getSaveFile(pick_type, proposed_name.c_str()))
|
|
{
|
|
// Clicked cancel
|
|
return FALSE;
|
|
}
|
|
|
|
// Copy the directory + file name
|
|
char directory[LL_MAX_PATH]; /* Flawfinder: ignore */
|
|
strncpy(directory, picker.getFirstFile(), LL_MAX_PATH -1); /* Flawfinder: ignore */
|
|
directory[LL_MAX_PATH -1] = '\0';
|
|
|
|
// Smash the file extension
|
|
S32 length = strlen(directory); /* Flawfinder: ignore */
|
|
S32 index = length;
|
|
|
|
// Back up over extension
|
|
index -= extension.length();
|
|
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)
|
|
{
|
|
strncpy(LLViewerWindow::sSnapshotBaseName, directory + index + 1, LL_MAX_PATH -1); /* Flawfinder: ignore */
|
|
LLViewerWindow::sSnapshotBaseName[LL_MAX_PATH -1] = '\0';
|
|
}
|
|
|
|
index++;
|
|
directory[index] = '\0';
|
|
strncpy(LLViewerWindow::sSnapshotDir, directory, LL_MAX_PATH -1); /* Flawfinder: ignore */
|
|
LLViewerWindow::sSnapshotDir[LL_MAX_PATH -1] = '\0';
|
|
}
|
|
}
|
|
|
|
// Look for an unused file name
|
|
LLString filepath;
|
|
S32 i = 1;
|
|
S32 err = 0;
|
|
|
|
do
|
|
{
|
|
filepath = sSnapshotDir;
|
|
filepath += sSnapshotBaseName;
|
|
filepath += llformat("_%.3d",i);
|
|
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<LLImageFormatted> formatted_image = LLImageFormatted::createFromExtension(extension);
|
|
LLImageBase::setSizeOverride(TRUE);
|
|
BOOL success = formatted_image->encode(raw);
|
|
if( success )
|
|
{
|
|
success = formatted_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) /* Flawfinder: ignore */
|
|
{
|
|
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]; /* Flawfinder: ignore */
|
|
strncpy(directory, picker.getFirstFile(), LL_MAX_PATH -1); /* Flawfinder: ignore */
|
|
directory[LL_MAX_PATH -1] = '\0';
|
|
|
|
// Smash the file extension
|
|
S32 length = strlen(directory); /* Flawfinder: ignore */
|
|
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)
|
|
{
|
|
strncpy(LLViewerWindow::sMovieBaseName, directory + index + 1, LL_MAX_PATH -1); /* Flawfinder: ignore */
|
|
LLViewerWindow::sMovieBaseName[LL_MAX_PATH -1] = '\0';
|
|
}
|
|
|
|
index++;
|
|
directory[index] = '\0';
|
|
strncpy(LLViewerWindow::sSnapshotDir, directory, LL_MAX_PATH -1); /* Flawfinder: ignore */
|
|
LLViewerWindow::sSnapshotDir[LL_MAX_PATH -1] = '\0';
|
|
}
|
|
}
|
|
|
|
// Look for an unused file name
|
|
LLString filepath;
|
|
S32 i = 1;
|
|
S32 err = 0;
|
|
|
|
do
|
|
{
|
|
char extension[100]; /* Flawfinder: ignore */
|
|
#if LL_DARWIN
|
|
snprintf( extension, sizeof(extension), "_%.3d.mov", i ); /* Flawfinder: ignore */
|
|
#else
|
|
snprintf( extension, sizeof(extension), "_%.3d.avi", i ); /* Flawfinder: ignore */
|
|
#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;
|
|
#if !LL_SOLARIS
|
|
gMovieMaker.StartCapture((char *)filepath.c_str(), x, y);
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
#if !LL_SOLARIS
|
|
gMovieMaker.EndCapture();
|
|
#endif
|
|
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;
|
|
}
|
|
|
|
// 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") && LLPipeline::sShowHUDAttachments;
|
|
if (hide_hud)
|
|
{
|
|
LLPipeline::sShowHUDAttachments = FALSE;
|
|
}
|
|
|
|
// 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, 0.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;
|
|
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::sShowHUDAttachments = TRUE;
|
|
}
|
|
|
|
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)
|
|
{
|
|
gFocusMgr.setMouseCapture( new_captor );
|
|
}
|
|
|
|
LLMouseHandler* LLViewerWindow::getMouseCaptor() const
|
|
{
|
|
return 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();
|
|
}
|
|
|
|
LLUICtrl* LLViewerWindow::getTopCtrl() const
|
|
{
|
|
return gFocusMgr.getTopCtrl();
|
|
}
|
|
|
|
BOOL LLViewerWindow::hasTopCtrl(LLView* view) const
|
|
{
|
|
return view == gFocusMgr.getTopCtrl();
|
|
}
|
|
|
|
void LLViewerWindow::setTopCtrl(LLUICtrl* new_top)
|
|
{
|
|
gFocusMgr.setTopCtrl( new_top );
|
|
}
|
|
|
|
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;
|
|
|
|
// Pause texture decode threads (will get unpaused during main loop)
|
|
gTextureCache->pause();
|
|
gImageDecodeThread->pause();
|
|
gTextureFetch->pause();
|
|
|
|
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();
|
|
|
|
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();
|
|
LLDynamicTexture::restoreGL();
|
|
LLVOAvatar::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();
|
|
}
|