/** * @file llscriptfloater.cpp * @brief LLScriptFloater class definition * * $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 "llscriptfloater.h" #include "llagentcamera.h" #include "llchannelmanager.h" #include "llchiclet.h" #include "llchicletbar.h" #include "llfloaterreg.h" #include "lllslconstants.h" #include "llnotifications.h" #include "llnotificationsutil.h" #include "llscreenchannel.h" #include "llsyswellwindow.h" #include "lltoastnotifypanel.h" #include "lltoastscripttextbox.h" #include "lltrans.h" #include "llviewerobjectlist.h" #include "llviewerwindow.h" #include "llfloaterimsession.h" ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// LLUUID notification_id_to_object_id(const LLUUID& notification_id) { LLNotificationPtr notification = LLNotifications::getInstance()->find(notification_id); if(notification) { return notification->getPayload()["object_id"].asUUID(); } return LLUUID::null; } ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// LLScriptFloater::LLScriptFloater(const LLSD& key) : LLDockableFloater(NULL, true, key) , mScriptForm(NULL) , mSaveFloaterPosition(false) { setMouseDownCallback(boost::bind(&LLScriptFloater::onMouseDown, this)); setOverlapsScreenChannel(true); mIsDockedStateForcedCallback = boost::bind(&LLAgentCamera::cameraMouselook, &gAgentCamera); } bool LLScriptFloater::toggle(const LLUUID& notification_id) { LLScriptFloater* floater = LLFloaterReg::findTypedInstance<LLScriptFloater>("script_floater", notification_id); // show existing floater if(floater) { if(floater->getVisible()) { floater->setVisible(false); return false; } else { floater->setVisible(true); floater->setFocus(false); } } // create and show new floater else { show(notification_id); } LLChicletPanel * chiclet_panelp = LLChicletBar::getInstance()->getChicletPanel(); if (NULL != chiclet_panelp) { chiclet_panelp->setChicletToggleState(notification_id, true); } return true; } LLScriptFloater* LLScriptFloater::show(const LLUUID& notification_id) { LLScriptFloater* floater = LLFloaterReg::getTypedInstance<LLScriptFloater>("script_floater", notification_id); floater->setNotificationId(notification_id); floater->createForm(notification_id); //LLDialog(LLGiveInventory and LLLoadURL) should no longer steal focus (see EXT-5445) floater->setAutoFocus(false); if(LLScriptFloaterManager::OBJ_SCRIPT == LLScriptFloaterManager::getObjectType(notification_id)) { floater->setSavePosition(true); floater->restorePosition(); } else { floater->dockToChiclet(true); } //LLDialog(LLGiveInventory and LLLoadURL) should no longer steal focus (see EXT-5445) LLFloaterReg::showTypedInstance<LLScriptFloater>("script_floater", notification_id, false); return floater; } void LLScriptFloater::setNotificationId(const LLUUID& id) { mNotificationId = id; // Lets save object id now while notification exists mObjectId = notification_id_to_object_id(id); } void LLScriptFloater::createForm(const LLUUID& notification_id) { // delete old form if(mScriptForm) { removeChild(mScriptForm); mScriptForm->die(); } LLNotificationPtr notification = LLNotifications::getInstance()->find(notification_id); if(NULL == notification) { return; } // create new form LLRect toast_rect = getRect(); if (isScriptTextbox(notification)) { mScriptForm = new LLToastScriptTextbox(notification); } else { // LLToastNotifyPanel will fit own content in vertical direction, // but it needs an initial rect to properly calculate its width // Use an initial rect of the script floater to make the floater // window more configurable. mScriptForm = new LLToastNotifyPanel(notification, toast_rect); } addChild(mScriptForm); // position form on floater mScriptForm->setOrigin(0, 0); // make floater size fit form size LLRect panel_rect = mScriptForm->getRect(); toast_rect.setLeftTopAndSize(toast_rect.mLeft, toast_rect.mTop, panel_rect.getWidth(), panel_rect.getHeight() + getHeaderHeight()); setShape(toast_rect); } void LLScriptFloater::onClose(bool app_quitting) { savePosition(); if(getNotificationId().notNull()) { // we shouldn't kill notification on exit since it may be used as persistent. if (app_quitting) { LLScriptFloaterManager::getInstance()->onRemoveNotification(getNotificationId()); } else { LLScriptFloaterManager::getInstance()->removeNotification(getNotificationId()); } } } void LLScriptFloater::setDocked(bool docked, bool pop_on_undock /* = true */) { LLDockableFloater::setDocked(docked, pop_on_undock); savePosition(); hideToastsIfNeeded(); } void LLScriptFloater::setVisible(bool visible) { LLDockableFloater::setVisible(visible); hideToastsIfNeeded(); if(!visible) { LLChicletPanel * chiclet_panelp = LLChicletBar::getInstance()->getChicletPanel(); if (NULL != chiclet_panelp) { LLIMChiclet * chicletp = chiclet_panelp->findChiclet<LLIMChiclet>(getNotificationId()); if(NULL != chicletp) { chicletp->setToggleState(false); } } } } void LLScriptFloater::onMouseDown() { if(getNotificationId().notNull()) { LLChicletPanel * chiclet_panelp = LLChicletBar::getInstance()->getChicletPanel(); if (NULL != chiclet_panelp) { LLIMChiclet * chicletp = chiclet_panelp->findChiclet<LLIMChiclet>(getNotificationId()); // Remove new message icon if (NULL == chicletp) { LL_ERRS() << "Dock chiclet for LLScriptFloater doesn't exist" << LL_ENDL; } else { chicletp->setShowNewMessagesIcon(false); } } } } void LLScriptFloater::savePosition() { if(getSavePosition() && mObjectId.notNull()) { LLScriptFloaterManager::FloaterPositionInfo fpi = {getRect(), isDocked()}; LLScriptFloaterManager::getInstance()->saveFloaterPosition(mObjectId, fpi); } } void LLScriptFloater::restorePosition() { LLScriptFloaterManager::FloaterPositionInfo fpi; if(LLScriptFloaterManager::getInstance()->getFloaterPosition(mObjectId, fpi)) { dockToChiclet(fpi.mDockState); if(!fpi.mDockState) { // Un-docked floater is opened in 0,0, now move it to saved position translate(fpi.mRect.mLeft - getRect().mLeft, fpi.mRect.mTop - getRect().mTop); } } else { dockToChiclet(true); } } void LLScriptFloater::onFocusLost() { if(getNotificationId().notNull()) { LLChicletPanel * chiclet_panelp = LLChicletBar::getInstance()->getChicletPanel(); if (NULL != chiclet_panelp) { chiclet_panelp->setChicletToggleState(getNotificationId(), false); } } } void LLScriptFloater::onFocusReceived() { // first focus will be received before setObjectId() call - don't toggle chiclet if(getNotificationId().notNull()) { LLChicletPanel * chiclet_panelp = LLChicletBar::getInstance()->getChicletPanel(); if (NULL != chiclet_panelp) { chiclet_panelp->setChicletToggleState(getNotificationId(), true); } } } void LLScriptFloater::dockToChiclet(bool dock) { if (getDockControl() == NULL) { LLChicletPanel * chiclet_panelp = LLChicletBar::getInstance()->getChicletPanel(); if (NULL != chiclet_panelp) { LLChiclet * chicletp = chiclet_panelp->findChiclet<LLChiclet>(getNotificationId()); if (NULL == chicletp) { LL_WARNS() << "Dock chiclet for LLScriptFloater doesn't exist" << LL_ENDL; return; } chiclet_panelp->scrollToChiclet(chicletp); // Stop saving position while we dock floater bool save = getSavePosition(); setSavePosition(false); setDockControl(new LLDockControl(chicletp, this, getDockTongue(), LLDockControl::BOTTOM)); setDocked(dock); // Restore saving setSavePosition(save); } } } void LLScriptFloater::hideToastsIfNeeded() { using namespace LLNotificationsUI; // find channel LLScreenChannel* channel = dynamic_cast<LLScreenChannel*>(LLChannelManager::getInstance()->findChannelByID( LLNotificationsUI::NOTIFICATION_CHANNEL_UUID)); // update notification channel state if(channel) { channel->updateShowToastsState(); channel->redrawToasts(); } } ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// LLScriptFloaterManager::LLScriptFloaterManager() : mDialogLimitationsSlot() { } void LLScriptFloaterManager::onAddNotification(const LLUUID& notification_id) { if(notification_id.isNull()) { LL_WARNS() << "Invalid notification ID" << LL_ENDL; return; } if (!mDialogLimitationsSlot.connected()) { LLPointer<LLControlVariable> cntrl_ptr = gSavedSettings.getControl("ScriptDialogLimitations"); if (cntrl_ptr.notNull()) { mDialogLimitationsSlot = cntrl_ptr->getCommitSignal()->connect(boost::bind(&clearScriptNotifications)); } else { LL_WARNS() << "Unable to set signal on setting 'ScriptDialogLimitations'" << LL_ENDL; } } // get scripted Object's ID LLUUID object_id = notification_id_to_object_id(notification_id); // Need to indicate of "new message" for object chiclets according to requirements // specified in the Message Bar design specification. See EXT-3142. bool set_new_message = false; EObjectType obj_type = getObjectType(notification_id); // LLDialog can spawn only one instance, LLLoadURL and LLGiveInventory can spawn unlimited number of instances if(OBJ_SCRIPT == obj_type) { static LLCachedControl<U32> script_dialog_limitations(gSavedSettings, "ScriptDialogLimitations", 0); script_notification_map_t::const_iterator it = mNotifications.end(); switch (script_dialog_limitations) { case SCRIPT_PER_CHANNEL: { // If an Object spawns more-than-one floater per channel, only the newest one is shown. // The previous is automatically closed. LLNotificationPtr notification = LLNotifications::instance().find(notification_id); if (notification) { it = findUsingObjectIdAndChannel(object_id, notification->getPayload()["chat_channel"].asInteger()); } break; } case SCRIPT_ATTACHMENT_PER_CHANNEL: { LLViewerObject* objectp = gObjectList.findObject(object_id); if (objectp && objectp->getAttachmentItemID().notNull()) //in user inventory { LLNotificationPtr notification = LLNotifications::instance().find(notification_id); if (notification) { it = findUsingObjectIdAndChannel(object_id, notification->getPayload()["chat_channel"].asInteger()); } } else { it = findUsingObjectId(object_id); } break; } case SCRIPT_HUD_PER_CHANNEL: { LLViewerObject* objectp = gObjectList.findObject(object_id); if (objectp && objectp->isHUDAttachment()) { LLNotificationPtr notification = LLNotifications::instance().find(notification_id); if (notification) { it = findUsingObjectIdAndChannel(object_id, notification->getPayload()["chat_channel"].asInteger()); } } else { it = findUsingObjectId(object_id); } break; } case SCRIPT_HUD_UNCONSTRAINED: { LLViewerObject* objectp = gObjectList.findObject(object_id); if (objectp && objectp->isHUDAttachment()) { // don't remove existing floaters break; } else { it = findUsingObjectId(object_id); } break; } case SCRIPT_PER_OBJECT: default: { // If an Object spawns more-than-one floater, only the newest one is shown. // The previous is automatically closed. it = findUsingObjectId(object_id); break; } } if(it != mNotifications.end()) { LLUUID old_id = it->first; // copy LLUUID to prevent use after free when it is erased below LLChicletPanel * chiclet_panelp = LLChicletBar::getInstance()->getChicletPanel(); if (NULL != chiclet_panelp) { LLIMChiclet * chicletp = chiclet_panelp->findChiclet<LLIMChiclet>(old_id); if (NULL != chicletp) { // Pass the new_message icon state further. set_new_message = chicletp->getShowNewMessagesIcon(); chicletp->hidePopupMenu(); } } LLScriptFloater* floater = LLFloaterReg::findTypedInstance<LLScriptFloater>("script_floater", old_id); if (floater) { // Generate chiclet with a "new message" indicator if a docked window was opened but not in focus. See EXT-3142. set_new_message |= !floater->hasFocus(); } removeNotification(old_id); } } mNotifications.insert(std::make_pair(notification_id, object_id)); LLChicletPanel * chiclet_panelp = LLChicletBar::getInstance()->getChicletPanel(); if (NULL != chiclet_panelp) { // Create inventory offer chiclet for offer type notifications if( OBJ_GIVE_INVENTORY == obj_type ) { chiclet_panelp->createChiclet<LLInvOfferChiclet>(notification_id); } else { chiclet_panelp->createChiclet<LLScriptChiclet>(notification_id); } } LLIMWellWindow::getInstance()->addObjectRow(notification_id, set_new_message); LLSD data; data["notification_id"] = notification_id; data["new_message"] = set_new_message; data["unread"] = 1; // each object has got only one floater mNewObjectSignal(data); toggleScriptFloater(notification_id, set_new_message); } void LLScriptFloaterManager::removeNotification(const LLUUID& notification_id) { LLNotificationPtr notification = LLNotifications::instance().find(notification_id); if (notification != NULL && !notification->isCancelled()) { LLNotificationsUtil::cancel(notification); } onRemoveNotification(notification_id); } void LLScriptFloaterManager::onRemoveNotification(const LLUUID& notification_id) { if(notification_id.isNull()) { LL_WARNS() << "Invalid notification ID" << LL_ENDL; return; } // remove related chiclet if (LLChicletBar::instanceExists()) { LLChicletPanel * chiclet_panelp = LLChicletBar::getInstance()->getChicletPanel(); if (NULL != chiclet_panelp) { chiclet_panelp->removeChiclet(notification_id); } } LLIMWellWindow* im_well_window = LLIMWellWindow::findInstance(); if (im_well_window) { im_well_window->removeObjectRow(notification_id); } mNotifications.erase(notification_id); // close floater LLScriptFloater* floater = LLFloaterReg::findTypedInstance<LLScriptFloater>("script_floater", notification_id); if(floater) { floater->savePosition(); floater->setNotificationId(LLUUID::null); floater->closeFloater(); } } void LLScriptFloaterManager::toggleScriptFloater(const LLUUID& notification_id, bool set_new_message) { LLSD data; data["notification_id"] = notification_id; data["new_message"] = set_new_message; mToggleFloaterSignal(data); // toggle floater LLScriptFloater::toggle(notification_id); } LLUUID LLScriptFloaterManager::findObjectId(const LLUUID& notification_id) { script_notification_map_t::const_iterator it = mNotifications.find(notification_id); if(mNotifications.end() != it) { return it->second; } return LLUUID::null; } LLUUID LLScriptFloaterManager::findNotificationId(const LLUUID& object_id) { if(object_id.notNull()) { script_notification_map_t::const_iterator it = findUsingObjectId(object_id); if(mNotifications.end() != it) { return it->first; } } return LLUUID::null; } // static LLScriptFloaterManager::EObjectType LLScriptFloaterManager::getObjectType(const LLUUID& notification_id) { if(notification_id.isNull()) { LL_WARNS() << "Invalid notification ID" << LL_ENDL; return OBJ_UNKNOWN; } static const object_type_map TYPE_MAP = initObjectTypeMap(); LLNotificationPtr notification = LLNotificationsUtil::find(notification_id); object_type_map::const_iterator it = TYPE_MAP.find(notification->getName()); if(it != TYPE_MAP.end()) { return it->second; } LL_WARNS() << "Unknown object type" << LL_ENDL; return OBJ_UNKNOWN; } // static std::string LLScriptFloaterManager::getObjectName(const LLUUID& notification_id) { using namespace LLNotificationsUI; LLNotificationPtr notification = LLNotifications::getInstance()->find(notification_id); if(!notification) { LL_WARNS() << "Invalid notification" << LL_ENDL; return LLStringUtil::null; } std::string text; switch(LLScriptFloaterManager::getObjectType(notification_id)) { case LLScriptFloaterManager::OBJ_SCRIPT: text = notification->getSubstitutions()["TITLE"].asString(); break; case LLScriptFloaterManager::OBJ_LOAD_URL: text = notification->getSubstitutions()["OBJECTNAME"].asString(); break; case LLScriptFloaterManager::OBJ_GIVE_INVENTORY: text = notification->getSubstitutions()["OBJECTFROMNAME"].asString(); break; default: text = LLTrans::getString("object"); break; } return text; } //static LLScriptFloaterManager::object_type_map LLScriptFloaterManager::initObjectTypeMap() { object_type_map type_map; type_map["ScriptDialog"] = OBJ_SCRIPT; type_map["ScriptDialogGroup"] = OBJ_SCRIPT; type_map["LoadWebPage"] = OBJ_LOAD_URL; type_map["ObjectGiveItem"] = OBJ_GIVE_INVENTORY; return type_map; } LLScriptFloaterManager::script_notification_map_t::const_iterator LLScriptFloaterManager::findUsingObjectId(const LLUUID& object_id) { script_notification_map_t::const_iterator it = mNotifications.begin(); for(; mNotifications.end() != it; ++it) { if(object_id == it->second) { return it; } } return mNotifications.end(); } LLScriptFloaterManager::script_notification_map_t::const_iterator LLScriptFloaterManager::findUsingObjectIdAndChannel(const LLUUID& object_id, S32 im_channel) { script_notification_map_t::const_iterator it = mNotifications.begin(); for (; mNotifications.end() != it; ++it) { if (object_id == it->second) { LLNotificationPtr notification = LLNotifications::instance().find(it->first); if (notification && (im_channel == notification->getPayload()["chat_channel"].asInteger())) { return it; } } } return mNotifications.end(); } void LLScriptFloaterManager::saveFloaterPosition(const LLUUID& object_id, const FloaterPositionInfo& fpi) { if(object_id.notNull()) { LLScriptFloaterManager::getInstance()->mFloaterPositions[object_id] = fpi; } else { LL_WARNS() << "Invalid object id" << LL_ENDL; } } bool LLScriptFloaterManager::getFloaterPosition(const LLUUID& object_id, FloaterPositionInfo& fpi) { floater_position_map_t::const_iterator it = mFloaterPositions.find(object_id); if(LLScriptFloaterManager::getInstance()->mFloaterPositions.end() != it) { fpi = it->second; return true; } return false; } void LLScriptFloaterManager::setFloaterVisible(const LLUUID& notification_id, bool visible) { LLScriptFloater* floater = LLFloaterReg::findTypedInstance<LLScriptFloater>( "script_floater", notification_id); if(floater) { floater->setVisible(visible); } } //static void LLScriptFloaterManager::clearScriptNotifications() { LLScriptFloaterManager* inst = LLScriptFloaterManager::getInstance(); static const object_type_map TYPE_MAP = initObjectTypeMap(); script_notification_map_t::const_iterator ntf_it = inst->mNotifications.begin(); while (inst->mNotifications.end() != ntf_it) { LLUUID notification_id = ntf_it->first; ntf_it++; // onRemoveNotification() erases notification LLNotificationPtr notification = LLNotifications::instance().find(notification_id); if (notification) { object_type_map::const_iterator map_it = TYPE_MAP.find(notification->getName()); if (map_it != TYPE_MAP.end() && map_it->second == OBJ_SCRIPT) { if (notification != NULL && !notification->isCancelled()) { LLNotificationsUtil::cancel(notification); } inst->onRemoveNotification(notification_id); } } } } ////////////////////////////////////////////////////////////////// bool LLScriptFloater::isScriptTextbox(LLNotificationPtr notification) { // get a form for the notification LLNotificationFormPtr form(notification->getForm()); if (form) { // get number of elements in the form int num_options = form->getNumElements(); // if ANY of the buttons have the magic lltextbox string as // name, then treat the whole dialog as a simple text entry // box (i.e. mixed button and textbox forms are not supported) for (int i=0; i<num_options; ++i) { LLSD form_element = form->getElement(i); if (form_element["name"].asString() == TEXTBOX_MAGIC_TOKEN) { return true; } } } return false; } // EOF