phoenix-firestorm/indra/newview/llinspectobject.cpp

567 lines
16 KiB
C++

/**
* @file llinspectobject.cpp
*
* $LicenseInfo:firstyear=2009&license=viewergpl$
*
* Copyright (c) 2009, Linden Research, Inc.
*
* Second Life Viewer Source Code
* The source code in this file ("Source Code") is provided by Linden Lab
* to you under the terms of the GNU General Public License, version 2.0
* ("GPL"), unless you have obtained a separate licensing agreement
* ("Other License"), formally executed by you and Linden Lab. Terms of
* the GPL can be found in doc/GPL-license.txt in this distribution, or
* online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
*
* There are special exceptions to the terms and conditions of the GPL as
* it is applied to this Source Code. View the full text of the exception
* in the file doc/FLOSS-exception.txt in this software distribution, or
* online at
* http://secondlifegrid.net/programs/open_source/licensing/flossexception
*
* By copying, modifying or distributing this software, you acknowledge
* that you have read and understood your obligations described above,
* and agree to abide by those obligations.
*
* ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
* WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
* COMPLETENESS OR PERFORMANCE.
* $/LicenseInfo$
*/
#include "llviewerprecompiledheaders.h"
#include "llinspectobject.h"
// Viewer
#include "llnotifications.h" // *TODO: Eliminate, add LLNotificationsUtil wrapper
#include "llselectmgr.h"
#include "llslurl.h"
#include "llviewermenu.h" // handle_object_touch(), handle_buy()
#include "llviewerobjectlist.h" // to select the requested object
// Linden libraries
#include "llbutton.h" // setLabel(), not virtual!
#include "llclickaction.h"
#include "llcontrol.h" // LLCachedControl
#include "llfloater.h"
#include "llfloaterreg.h"
#include "llmenubutton.h"
#include "llresmgr.h" // getMonetaryString
#include "llsafehandle.h"
#include "lltextbox.h" // for description truncation
#include "lltrans.h"
#include "llui.h" // positionViewNearMouse()
#include "lluictrl.h"
class LLViewerObject;
// *TODO: Abstract out base class for LLInspectObject and LLInspectObject
//////////////////////////////////////////////////////////////////////////////
// LLInspectObject
//////////////////////////////////////////////////////////////////////////////
// Object Inspector, a small information window used when clicking
// in the ambient inspector widget for objects in the 3D world.
class LLInspectObject : public LLFloater
{
friend class LLFloaterReg;
public:
// object_id - Root object ID for which to show information
// Inspector will be positioned relative to current mouse position
LLInspectObject(const LLSD& object_id);
virtual ~LLInspectObject();
/*virtual*/ BOOL postBuild(void);
/*virtual*/ void draw();
// Because floater is single instance, need to re-parse data on each spawn
// (for example, inspector about same avatar but in different position)
/*virtual*/ void onOpen(const LLSD& avatar_id);
// Release the selection and do other cleanup
void onClose();
// Inspectors close themselves when they lose focus
/*virtual*/ void onFocusLost();
private:
// Refresh displayed data with information from selection manager
void update();
void hideButtons();
void updateButtons(LLSelectNode* nodep);
void updateSitLabel(LLSelectNode* nodep);
void updateTouchLabel(LLSelectNode* nodep);
void updateName(LLSelectNode* nodep);
void updateDescription(LLSelectNode* nodep);
void updatePrice(LLSelectNode* nodep);
void updateCreator(LLSelectNode* nodep);
void onClickBuy();
void onClickPay();
void onClickTakeFreeCopy();
void onClickTouch();
void onClickSit();
void onClickOpen();
void onClickMoreInfo();
private:
LLUUID mObjectID;
LLFrameTimer mOpenTimer;
LLFrameTimer mCloseTimer;
LLSafeHandle<LLObjectSelection> mObjectSelection;
};
LLInspectObject::LLInspectObject(const LLSD& sd)
: LLFloater( LLSD() ), // single_instance, doesn't really need key
mObjectID(), // set in onOpen()
mCloseTimer(),
mOpenTimer()
{
// can't make the properties request until the widgets are constructed
// as it might return immediately, so do it in postBuild.
mCommitCallbackRegistrar.add("InspectObject.Buy", boost::bind(&LLInspectObject::onClickBuy, this));
mCommitCallbackRegistrar.add("InspectObject.Pay", boost::bind(&LLInspectObject::onClickPay, this));
mCommitCallbackRegistrar.add("InspectObject.TakeFreeCopy", boost::bind(&LLInspectObject::onClickTakeFreeCopy, this));
mCommitCallbackRegistrar.add("InspectObject.Touch", boost::bind(&LLInspectObject::onClickTouch, this));
mCommitCallbackRegistrar.add("InspectObject.Sit", boost::bind(&LLInspectObject::onClickSit, this));
mCommitCallbackRegistrar.add("InspectObject.Open", boost::bind(&LLInspectObject::onClickOpen, this));
mCommitCallbackRegistrar.add("InspectObject.MoreInfo", boost::bind(&LLInspectObject::onClickMoreInfo, this));
}
LLInspectObject::~LLInspectObject()
{
}
/*virtual*/
BOOL LLInspectObject::postBuild(void)
{
// The XML file has sample data in it. Clear that out so we don't
// flicker when data arrives off network.
getChild<LLUICtrl>("object_name")->setValue("");
getChild<LLUICtrl>("object_creator")->setValue("");
getChild<LLUICtrl>("object_description")->setValue("");
// Set buttons invisible until we know what this object can do
hideButtons();
// Hide floater when name links clicked
LLTextBox* textbox = getChild<LLTextBox>("object_creator");
textbox->mURLClickSignal.connect(
boost::bind(&LLInspectObject::closeFloater, this, false) );
// Hook up functionality
getChild<LLUICtrl>("buy_btn")->setCommitCallback(
boost::bind(&LLInspectObject::onClickBuy, this));
getChild<LLUICtrl>("pay_btn")->setCommitCallback(
boost::bind(&LLInspectObject::onClickPay, this));
getChild<LLUICtrl>("take_free_copy_btn")->setCommitCallback(
boost::bind(&LLInspectObject::onClickTakeFreeCopy, this));
getChild<LLUICtrl>("touch_btn")->setCommitCallback(
boost::bind(&LLInspectObject::onClickTouch, this));
getChild<LLUICtrl>("sit_btn")->setCommitCallback(
boost::bind(&LLInspectObject::onClickSit, this));
getChild<LLUICtrl>("open_btn")->setCommitCallback(
boost::bind(&LLInspectObject::onClickOpen, this));
getChild<LLUICtrl>("more_info_btn")->setCommitCallback(
boost::bind(&LLInspectObject::onClickMoreInfo, this));
// Watch for updates to selection properties off the network
LLSelectMgr::getInstance()->mUpdateSignal.connect(
boost::bind(&LLInspectObject::update, this) );
mCloseSignal.connect( boost::bind(&LLInspectObject::onClose, this) );
return TRUE;
}
void LLInspectObject::draw()
{
static LLCachedControl<F32> FADE_OUT_TIME(*LLUI::sSettingGroups["config"], "InspectorFadeTime", 1.f);
if (mOpenTimer.getStarted())
{
F32 alpha = clamp_rescale(mOpenTimer.getElapsedTimeF32(), 0.f, FADE_OUT_TIME, 0.f, 1.f);
LLViewDrawContext context(alpha);
LLFloater::draw();
}
else if (mCloseTimer.getStarted())
{
F32 alpha = clamp_rescale(mCloseTimer.getElapsedTimeF32(), 0.f, FADE_OUT_TIME, 1.f, 0.f);
LLViewDrawContext context(alpha);
LLFloater::draw();
if (mCloseTimer.getElapsedTimeF32() > FADE_OUT_TIME)
{
closeFloater(false);
}
}
else
{
LLFloater::draw();
}
}
// Multiple calls to showInstance("inspect_avatar", foo) will provide different
// LLSD for foo, which we will catch here.
//virtual
void LLInspectObject::onOpen(const LLSD& data)
{
mCloseTimer.stop();
mOpenTimer.start();
// Extract appropriate avatar id
mObjectID = data["object_id"];
// Position the inspector relative to the mouse cursor
// Similar to how tooltips are positioned
// See LLToolTipMgr::createToolTip
if (data.has("pos"))
{
LLUI::positionViewNearMouse(this, data["pos"]["x"].asInteger(), data["pos"]["y"].asInteger());
}
else
{
LLUI::positionViewNearMouse(this);
}
// Promote hovered object to a complete selection, which will also force
// a request for selected object data off the network
LLViewerObject* obj = gObjectList.findObject( mObjectID );
if (obj)
{
LLSelectMgr::instance().deselectAll();
mObjectSelection = LLSelectMgr::instance().selectObjectAndFamily(obj);
// Mark this as a transient selection
struct SetTransient : public LLSelectedNodeFunctor
{
bool apply(LLSelectNode* node)
{
node->setTransient(TRUE);
return true;
}
} functor;
mObjectSelection->applyToNodes(&functor);
}
}
void LLInspectObject::onClose()
{
// Release selection to deselect
mObjectSelection = NULL;
getChild<LLMenuButton>("gear_btn")->hideMenu();
}
//virtual
void LLInspectObject::onFocusLost()
{
// Start closing when we lose focus
mCloseTimer.start();
mOpenTimer.stop();
}
void LLInspectObject::update()
{
// Performance optimization, because we listen to updates from select mgr
// but we're never destroyed.
if (!getVisible()) return;
LLObjectSelection* selection = LLSelectMgr::getInstance()->getSelection();
if (!selection) return;
LLSelectNode* nodep = selection->getFirstRootNode();
if (!nodep) return;
updateButtons(nodep);
updateName(nodep);
updateDescription(nodep);
updateCreator(nodep);
updatePrice(nodep);
}
void LLInspectObject::hideButtons()
{
getChild<LLUICtrl>("buy_btn")->setVisible(false);
getChild<LLUICtrl>("pay_btn")->setVisible(false);
getChild<LLUICtrl>("take_free_copy_btn")->setVisible(false);
getChild<LLUICtrl>("touch_btn")->setVisible(false);
getChild<LLUICtrl>("sit_btn")->setVisible(false);
getChild<LLUICtrl>("open_btn")->setVisible(false);
}
// *TODO: Extract this method from lltoolpie.cpp and put somewhere shared
extern U8 final_click_action(LLViewerObject*);
// Choose the "most relevant" operation for this object, and show a button for
// that operation as the left-most button in the inspector.
void LLInspectObject::updateButtons(LLSelectNode* nodep)
{
// We'll start with everyone hidden and show the ones we need
hideButtons();
LLViewerObject* object = nodep->getObject();
LLViewerObject *parent = (LLViewerObject*)object->getParent();
bool for_copy = anyone_copy_selection(nodep);
bool for_sale = enable_buy_object();
S32 price = nodep->mSaleInfo.getSalePrice();
U8 click_action = final_click_action(object);
if (for_copy
|| (for_sale && price == 0))
{
// Free copies have priority over other operations
getChild<LLUICtrl>("take_free_copy_btn")->setVisible(true);
}
else if (for_sale)
{
getChild<LLUICtrl>("buy_btn")->setVisible(true);
}
else if ( enable_pay_object() )
{
getChild<LLUICtrl>("pay_btn")->setVisible(true);
}
else if (click_action == CLICK_ACTION_SIT)
{
// Click-action sit must come before "open" because many objects on
// which you can sit have scripts, and hence can be opened
getChild<LLUICtrl>("sit_btn")->setVisible(true);
updateSitLabel(nodep);
}
else if (object->flagHandleTouch()
|| (parent && parent->flagHandleTouch()))
{
getChild<LLUICtrl>("touch_btn")->setVisible(true);
updateTouchLabel(nodep);
}
else if ( enable_object_open() )
{
// Open is last because anything with a script in it can be opened
getChild<LLUICtrl>("open_btn")->setVisible(true);
}
else
{
// By default, we can sit on anything
getChild<LLUICtrl>("sit_btn")->setVisible(true);
updateSitLabel(nodep);
}
// No flash
focusFirstItem(FALSE, FALSE);
}
void LLInspectObject::updateSitLabel(LLSelectNode* nodep)
{
LLButton* sit_btn = getChild<LLButton>("sit_btn");
if (!nodep->mSitName.empty())
{
sit_btn->setLabel( nodep->mSitName );
}
else
{
sit_btn->setLabel( getString("Sit") );
}
}
void LLInspectObject::updateTouchLabel(LLSelectNode* nodep)
{
LLButton* sit_btn = getChild<LLButton>("touch_btn");
if (!nodep->mTouchName.empty())
{
sit_btn->setLabel( nodep->mTouchName );
}
else
{
sit_btn->setLabel( getString("Touch") );
}
}
void LLInspectObject::updateName(LLSelectNode* nodep)
{
std::string name;
if (!nodep->mName.empty())
{
name = nodep->mName;
}
else
{
name = LLTrans::getString("TooltipNoName");
}
getChild<LLUICtrl>("object_name")->setValue(name);
}
void LLInspectObject::updateDescription(LLSelectNode* nodep)
{
const char* const DEFAULT_DESC = "(No Description)";
std::string desc;
if (!nodep->mDescription.empty()
&& nodep->mDescription != DEFAULT_DESC)
{
desc = nodep->mDescription;
}
LLTextBox* textbox = getChild<LLTextBox>("object_description");
textbox->setValue(desc);
// Truncate description text to fit in widget
// *HACK: OMG, use lower-left corner to truncate text
// Don't round the position, we want the left of the character
S32 corner_index = textbox->getDocIndexFromLocalCoord( 0, 0, FALSE);
LLWString desc_wide = textbox->getWText();
// index == length if position is past last character
if (corner_index < (S32)desc_wide.length())
{
desc_wide = desc_wide.substr(0, corner_index);
textbox->setWText(desc_wide);
}
}
void LLInspectObject::updateCreator(LLSelectNode* nodep)
{
// final information for display
LLStringUtil::format_map_t args;
std::string text;
// Leave text blank until data loaded
if (nodep->mValid)
{
// Utilize automatic translation of SLURL into name to display
// a clickable link
// Objects cannot be created by a group, so use agent URL format
LLUUID creator_id = nodep->mPermissions->getCreator();
std::string creator_url =
LLSLURL::buildCommand("agent", creator_id, "about");
args["[CREATOR]"] = creator_url;
// created by one user but owned by another
std::string owner_url;
LLUUID owner_id;
bool group_owned = nodep->mPermissions->isGroupOwned();
if (group_owned)
{
owner_id = nodep->mPermissions->getGroup();
owner_url = LLSLURL::buildCommand("group", owner_id, "about");
}
else
{
owner_id = nodep->mPermissions->getOwner();
owner_url = LLSLURL::buildCommand("agent", owner_id, "about");
}
args["[OWNER]"] = owner_url;
if (creator_id == owner_id)
{
// common case, created and owned by one user
text = getString("Creator", args);
}
else
{
text = getString("CreatorAndOwner", args);
}
}
getChild<LLUICtrl>("object_creator")->setValue(text);
}
void LLInspectObject::updatePrice(LLSelectNode* nodep)
{
// *TODO: Only look these up once and use for both updateButtons and here
bool for_copy = anyone_copy_selection(nodep);
bool for_sale = enable_buy_object();
S32 price = nodep->mSaleInfo.getSalePrice();
bool show_price_icon = false;
std::string line;
if (for_copy
|| (for_sale && price == 0))
{
line = getString("PriceFree");
show_price_icon = true;
}
else if (for_sale)
{
LLStringUtil::format_map_t args;
args["[AMOUNT]"] = LLResMgr::getInstance()->getMonetaryString(price);
line = getString("Price", args);
show_price_icon = true;
}
getChild<LLUICtrl>("price_text")->setValue(line);
getChild<LLUICtrl>("price_icon")->setVisible(show_price_icon);
}
void LLInspectObject::onClickBuy()
{
handle_buy();
closeFloater();
}
void LLInspectObject::onClickPay()
{
handle_give_money_dialog();
closeFloater();
}
void LLInspectObject::onClickTakeFreeCopy()
{
LLObjectSelection* selection = LLSelectMgr::getInstance()->getSelection();
if (!selection) return;
LLSelectNode* nodep = selection->getFirstRootNode();
if (!nodep) return;
// Figure out if this is a "free buy" or a "take copy"
bool for_copy = anyone_copy_selection(nodep);
// Prefer to just take a free copy
if (for_copy)
{
handle_take_copy();
}
else
{
// Buy for free (confusing, but that's how it is)
handle_buy();
}
closeFloater();
}
void LLInspectObject::onClickTouch()
{
handle_object_touch();
closeFloater();
}
void LLInspectObject::onClickSit()
{
handle_object_sit_or_stand();
closeFloater();
}
void LLInspectObject::onClickOpen()
{
LLFloaterReg::showInstance("openobject");
closeFloater();
}
void LLInspectObject::onClickMoreInfo()
{
// *TODO: Show object info side panel, once that is implemented.
LLNotifications::instance().add("ClickUnimplemented");
closeFloater();
}
//////////////////////////////////////////////////////////////////////////////
// LLInspectObjectUtil
//////////////////////////////////////////////////////////////////////////////
void LLInspectObjectUtil::registerFloater()
{
LLFloaterReg::add("inspect_object", "inspect_object.xml",
&LLFloaterReg::build<LLInspectObject>);
}