/** * @file llinspectobject.cpp * * $LicenseInfo:firstyear=2009&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, Linden Research, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ #include "llviewerprecompiledheaders.h" #include "llinspectobject.h" // Viewer #include "llagent.h" // To standup #include "llfloatersidepanelcontainer.h" #include "llinspect.h" #include "llmediaentry.h" #include "llselectmgr.h" #include "llslurl.h" #include "llviewermenu.h" // handle_object_touch(), handle_buy() #include "llviewermedia.h" #include "llviewermediafocus.h" #include "llviewerobjectlist.h" // to select the requested object #include "llvoavatarself.h" // Linden libraries #include "llbutton.h" // setLabel(), not virtual! #include "llclickaction.h" #include "llfloaterreg.h" #include "llmenubutton.h" #include "llresmgr.h" // getMonetaryString #include "llsafehandle.h" #include "lltextbox.h" // for description truncation #include "lltoggleablemenu.h" #include "lltrans.h" #include "lluictrl.h" class LLViewerObject; ////////////////////////////////////////////////////////////////////////////// // LLInspectObject ////////////////////////////////////////////////////////////////////////////// // Object Inspector, a small information window used when clicking // in the ambient inspector widget for objects in the 3D world. class LLInspectObject : public LLInspect { 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); // 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 /*virtual*/ void onClose(bool app_quitting); // override the inspector mouse leave so timer is only paused if // gear menu is not open /* virtual */ void onMouseLeave(S32 x, S32 y, MASK mask); 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 updateMediaCurrentURL(); void updateSecureBrowsing(); void onClickBuy(); void onClickPay(); void onClickTakeFreeCopy(); void onClickTouch(); void onClickSit(); void onClickOpen(); void onClickMoreInfo(); void onClickZoomIn(); private: LLUUID mObjectID; LLUUID mPreviousObjectID; S32 mObjectFace; viewer_media_t mMediaImpl; LLMediaEntry* mMediaEntry; LLSafeHandle mObjectSelection; boost::signals2::connection mSelectionUpdateSlot; }; LLInspectObject::LLInspectObject(const LLSD& sd) : LLInspect( LLSD() ), // single_instance, doesn't really need key mObjectID(NULL), // set in onOpen() mObjectFace(0), mObjectSelection(NULL), mMediaImpl(NULL), mMediaEntry(NULL) { // 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), cb_info::UNTRUSTED_BLOCK }); mCommitCallbackRegistrar.add("InspectObject.Pay", {boost::bind(&LLInspectObject::onClickPay, this), cb_info::UNTRUSTED_BLOCK }); mCommitCallbackRegistrar.add("InspectObject.TakeFreeCopy", {boost::bind(&LLInspectObject::onClickTakeFreeCopy, this), cb_info::UNTRUSTED_BLOCK }); mCommitCallbackRegistrar.add("InspectObject.Touch", {boost::bind(&LLInspectObject::onClickTouch, this), cb_info::UNTRUSTED_BLOCK}); mCommitCallbackRegistrar.add("InspectObject.Sit", {boost::bind(&LLInspectObject::onClickSit, this), cb_info::UNTRUSTED_BLOCK }); mCommitCallbackRegistrar.add("InspectObject.Open", {boost::bind(&LLInspectObject::onClickOpen, this), cb_info::UNTRUSTED_BLOCK }); mCommitCallbackRegistrar.add("InspectObject.MoreInfo", {boost::bind(&LLInspectObject::onClickMoreInfo, this), cb_info::UNTRUSTED_BLOCK }); mCommitCallbackRegistrar.add("InspectObject.ZoomIn", {boost::bind(&LLInspectObject::onClickZoomIn, this), cb_info::UNTRUSTED_BLOCK}); } LLInspectObject::~LLInspectObject() { if (mSelectionUpdateSlot.connected()) { mSelectionUpdateSlot.disconnect(); } } /*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("object_name")->setValue(""); getChild("object_creator")->setValue(""); getChild("object_description")->setValue(""); getChild("object_media_url")->setValue(""); // Set buttons invisible until we know what this object can do hideButtons(); // Hide floater when name links clicked LLTextBox* textbox = getChild("object_creator"); textbox->setURLClickedCallback(boost::bind(&LLInspectObject::closeFloater, this, false) ); // Hook up functionality getChild("buy_btn")->setCommitCallback( boost::bind(&LLInspectObject::onClickBuy, this)); getChild("pay_btn")->setCommitCallback( boost::bind(&LLInspectObject::onClickPay, this)); getChild("take_free_copy_btn")->setCommitCallback( boost::bind(&LLInspectObject::onClickTakeFreeCopy, this)); getChild("touch_btn")->setCommitCallback( boost::bind(&LLInspectObject::onClickTouch, this)); getChild("sit_btn")->setCommitCallback( boost::bind(&LLInspectObject::onClickSit, this)); getChild("open_btn")->setCommitCallback( boost::bind(&LLInspectObject::onClickOpen, this)); getChild("more_info_btn")->setCommitCallback( boost::bind(&LLInspectObject::onClickMoreInfo, this)); if (!mSelectionUpdateSlot.connected()) { // Watch for updates to selection properties off the network mSelectionUpdateSlot = LLSelectMgr::getInstance()->mUpdateSignal.connect( boost::bind(&LLInspectObject::update, this)); } return true; } // 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) { // Start animation LLInspect::onOpen(data); // Extract appropriate avatar id mObjectID = data["object_id"]; if(data.has("object_face")) { mObjectFace = data["object_face"]; } LLInspect::repositionInspector(data); // 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) { // Media focus and this code fight over the select manager. // Make sure any media is unfocused before changing the selection here. LLViewerMediaFocus::getInstance()->clearFocus(); LLSelectMgr::instance().deselectAll(); mObjectSelection = LLSelectMgr::instance().selectObjectAndFamily(obj,false,true); // Mark this as a transient selection struct SetTransient : public LLSelectedNodeFunctor { bool apply(LLSelectNode* node) { node->setTransient(true); return true; } } functor; mObjectSelection->applyToNodes(&functor); // Does this face have media? const LLTextureEntry* tep = obj->getTE(mObjectFace); if (!tep) return; mMediaEntry = tep->hasMedia() ? tep->getMediaData() : NULL; if(!mMediaEntry) return; mMediaImpl = LLViewerMedia::getInstance()->getMediaImplFromTextureID(mMediaEntry->getMediaID()); } } // virtual void LLInspectObject::onClose(bool app_quitting) { // Release selection to deselect mObjectSelection = NULL; mPreviousObjectID = mObjectID; getChild("gear_btn")->hideMenu(); } 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; // If we don't have fresh object info yet and it's the object we inspected last time, // keep showing the previously retrieved data until we get the update. if (!nodep->mValid && nodep->getObject()->getID() == mPreviousObjectID) { return; } updateButtons(nodep); updateName(nodep); updateDescription(nodep); updateCreator(nodep); updatePrice(nodep); LLViewerObject* obj = nodep->getObject(); if(!obj) return; if ( mObjectFace < 0 || mObjectFace >= obj->getNumTEs() ) { return; } // Does this face have media? const LLTextureEntry* tep = obj->getTE(mObjectFace); if (!tep) return; mMediaEntry = tep->hasMedia() ? tep->getMediaData() : NULL; if(!mMediaEntry) return; mMediaImpl = LLViewerMedia::getInstance()->getMediaImplFromTextureID(mMediaEntry->getMediaID()); updateMediaCurrentURL(); updateSecureBrowsing(); } void LLInspectObject::hideButtons() { getChild("buy_btn")->setVisible(false); getChild("pay_btn")->setVisible(false); getChild("take_free_copy_btn")->setVisible(false); getChild("touch_btn")->setVisible(false); getChild("sit_btn")->setVisible(false); getChild("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("take_free_copy_btn")->setVisible(true); } else if (for_sale) { getChild("buy_btn")->setVisible(true); } else if ( enable_pay_object() ) { getChild("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("sit_btn")->setVisible(true); updateSitLabel(nodep); } else if (object->flagHandleTouch() || (parent && parent->flagHandleTouch())) { getChild("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("open_btn")->setVisible(true); } else { // By default, we can sit on anything getChild("sit_btn")->setVisible(true); updateSitLabel(nodep); } // No flash focusFirstItem(false, false); } void LLInspectObject::updateSitLabel(LLSelectNode* nodep) { LLButton* sit_btn = getChild("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("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("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("object_description"); textbox->setValue(desc); } void LLInspectObject::updateMediaCurrentURL() { if(!mMediaEntry) return; LLTextBox* textbox = getChild("object_media_url"); std::string media_url = ""; textbox->setValue(media_url); textbox->setToolTip(media_url); LLStringUtil::format_map_t args; if(mMediaImpl.notNull() && mMediaImpl->hasMedia()) { LLPluginClassMedia* media_plugin = NULL; media_plugin = mMediaImpl->getMediaPlugin(); if(media_plugin) { if(media_plugin->pluginSupportsMediaTime()) { args["[CurrentURL]"] = mMediaImpl->getMediaURL(); } else { args["[CurrentURL]"] = media_plugin->getLocation(); } media_url = LLTrans::getString("CurrentURL", args); } } else if(mMediaEntry->getCurrentURL() != "") { args["[CurrentURL]"] = mMediaEntry->getCurrentURL(); media_url = LLTrans::getString("CurrentURL", args); } textbox->setText(media_url); textbox->setToolTip(media_url); } 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("agent", creator_id, "about").getSLURLString(); 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("group", owner_id, "about").getSLURLString(); } else { owner_id = nodep->mPermissions->getOwner(); owner_url = LLSLURL("agent", owner_id, "about").getSLURLString(); } 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("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("price_text")->setValue(line); getChild("price_icon")->setVisible(show_price_icon); } void LLInspectObject::updateSecureBrowsing() { bool is_secure_browsing = false; if(mMediaImpl.notNull() && mMediaImpl->hasMedia()) { LLPluginClassMedia* media_plugin = NULL; std::string current_url = ""; media_plugin = mMediaImpl->getMediaPlugin(); if(media_plugin) { if(media_plugin->pluginSupportsMediaTime()) { current_url = mMediaImpl->getMediaURL(); } else { current_url = media_plugin->getLocation(); } } std::string prefix = std::string("https://"); std::string test_prefix = current_url.substr(0, prefix.length()); LLStringUtil::toLower(test_prefix); if(test_prefix == prefix) { is_secure_browsing = true; } } getChild("secure_browsing")->setVisible(is_secure_browsing); } // For the object inspector, only unpause the fade timer // if the gear menu is not open void LLInspectObject::onMouseLeave(S32 x, S32 y, MASK mask) { LLToggleableMenu* gear_menu = getChild("gear_btn")->getMenu(); if ( gear_menu && gear_menu->getVisible() ) { return; } if(childHasVisiblePopupMenu()) { return; } mOpenTimer.unpause(); } 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() { bool is_sitting = false; if (mObjectSelection) { LLSelectNode* node = mObjectSelection->getFirstRootNode(); if (node && node->mValid) { LLViewerObject* root_object = node->getObject(); if (root_object && isAgentAvatarValid() && gAgentAvatarp->isSitting() && gAgentAvatarp->getRoot() == root_object) { is_sitting = true; } } } if (is_sitting) { gAgent.standUp(); } else { handle_object_sit(mObjectID); } closeFloater(); } void LLInspectObject::onClickOpen() { LLFloaterReg::showInstance("openobject"); closeFloater(); } void LLInspectObject::onClickMoreInfo() { LLFloaterReg::showInstance("task_properties"); closeFloater(); } void LLInspectObject::onClickZoomIn() { handle_look_at_selection("zoom"); closeFloater(); } ////////////////////////////////////////////////////////////////////////////// // LLInspectObjectUtil ////////////////////////////////////////////////////////////////////////////// void LLInspectObjectUtil::registerFloater() { LLFloaterReg::add("inspect_object", "inspect_object.xml", &LLFloaterReg::build); }