diff options
author | Leyla Farazha <leyla@lindenlab.com> | 2009-10-28 17:12:47 -0700 |
---|---|---|
committer | Leyla Farazha <leyla@lindenlab.com> | 2009-10-28 17:12:47 -0700 |
commit | 437449044583d50c8cad694e65e5a4a23a55d768 (patch) | |
tree | c7637a8dbc31d933ca05e999eea7f9fd34daa305 | |
parent | 9426de7e10fa7cfe84048ae7dd58a793e981ee33 (diff) | |
parent | d993ce14d83530544c0e0f05af10d235999d9a5f (diff) |
tab container fixes..
68 files changed, 3232 insertions, 1464 deletions
diff --git a/indra/llui/lltextbase.cpp b/indra/llui/lltextbase.cpp index 653505a12e..2b1d677ffb 100644 --- a/indra/llui/lltextbase.cpp +++ b/indra/llui/lltextbase.cpp @@ -419,9 +419,6 @@ void LLTextBase::drawCursor() return; } - if (!mTextRect.contains(cursor_rect)) - return; - // Draw the cursor // (Flash the cursor every half second starting a fixed time after the last keystroke) F32 elapsed = mCursorBlinkTimer.getElapsedTimeF32(); @@ -2381,7 +2378,6 @@ S32 LLNormalTextSegment::getNumChars(S32 num_pixels, S32 segment_offset, S32 lin { if (text[last_char] == '\n') { - last_char++; break; } } @@ -2401,9 +2397,14 @@ S32 LLNormalTextSegment::getNumChars(S32 num_pixels, S32 segment_offset, S32 lin // If at the beginning of a line, and a single character won't fit, draw it anyway num_chars = 1; } - if (mStart + segment_offset + num_chars == mEditor.getLength()) + + // include *either* the EOF or newline character in this run of text + // but not both + S32 last_char_in_run = mStart + segment_offset + num_chars; + // check length first to avoid indexing off end of string + if (last_char_in_run >= mEditor.getLength() + || text[last_char_in_run] == '\n') { - // include terminating NULL num_chars++; } return num_chars; diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index dd3937a6ef..5210ff66ed 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -371,6 +371,7 @@ set(viewer_SOURCE_FILES llsky.cpp llslurl.cpp llspatialpartition.cpp + llspeakers.cpp llsplitbutton.cpp llsprite.cpp llstartup.cpp @@ -484,6 +485,7 @@ set(viewer_SOURCE_FILES llvoclouds.cpp llvograss.cpp llvoground.cpp + llvoicechannel.cpp llvoiceclient.cpp llvoiceremotectrl.cpp llvoicevisualizer.cpp @@ -847,6 +849,7 @@ set(viewer_HEADER_FILES llsky.h llslurl.h llspatialpartition.h + llspeakers.h llsplitbutton.h llsprite.h llstartup.h @@ -960,6 +963,7 @@ set(viewer_HEADER_FILES llvoclouds.h llvograss.h llvoground.h + llvoicechannel.h llvoiceclient.h llvoiceremotectrl.h llvoicevisualizer.h diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 3682d48577..6bc95b9cdb 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -7742,6 +7742,39 @@ <key>Value</key> <integer>1</integer> </map> + <key>FriendsListShowIcons</key> + <map> + <key>Comment</key> + <string>Show/hide online and all friends icons in the friend list</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>Boolean</string> + <key>Value</key> + <integer>1</integer> + </map> + <key>NearbyListShowIcons</key> + <map> + <key>Comment</key> + <string>Show/hide people icons in nearby list</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>Boolean</string> + <key>Value</key> + <integer>1</integer> + </map> + <key>RecentListShowIcons</key> + <map> + <key>Comment</key> + <string>Show/hide people icons in recent list</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>Boolean</string> + <key>Value</key> + <integer>1</integer> + </map> <key>FriendsSortOrder</key> <map> <key>Comment</key> @@ -10460,6 +10493,17 @@ <key>Value</key> <real>90.0</real> </map> + <key>YouAreHereDistance</key> + <map> + <key>Comment</key> + <string>Radius of distance for banner that indicates if the resident is "on" the Place.(meters from avatar to requested place)</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>F32</string> + <key>Value</key> + <real>10.0</real> + </map> <key>YieldTime</key> <map> <key>Comment</key> diff --git a/indra/newview/llagent.cpp b/indra/newview/llagent.cpp index f62606cc50..75a72e5b17 100644 --- a/indra/newview/llagent.cpp +++ b/indra/newview/llagent.cpp @@ -42,7 +42,7 @@ #include "lldrawable.h" #include "llfirstuse.h" #include "llfloaterreg.h" -#include "llfloateractivespeakers.h" +#include "llspeakers.h" #include "llfloatercamera.h" #include "llfloatercustomize.h" @@ -5391,12 +5391,6 @@ void update_group_floaters(const LLUUID& group_id) //*TODO Implement group update for Profile View // still actual as of July 31, 2009 (DZ) - if (gIMMgr) - { - // update the talk view - gIMMgr->refresh(); - } - gAgent.fireEvent(new LLOldEvents::LLEvent(&gAgent, "new group"), ""); } diff --git a/indra/newview/llagentui.cpp b/indra/newview/llagentui.cpp index 1a69f1d975..09f7c49f23 100644 --- a/indra/newview/llagentui.cpp +++ b/indra/newview/llagentui.cpp @@ -89,6 +89,11 @@ std::string LLAgentUI::buildSLURL(const bool escaped /*= true*/) return slurl; } +//static +BOOL LLAgentUI::checkAgentDistance(const LLVector3& pole, F32 radius) +{ + return (gAgent.getPositionAgent() - pole).length() < radius; +} BOOL LLAgentUI::buildLocationString(std::string& str, ELocationFormat fmt,const LLVector3& agent_pos_region) { LLViewerRegion* region = gAgent.getRegion(); diff --git a/indra/newview/llagentui.h b/indra/newview/llagentui.h index 47ecb04547..c7aafb71e7 100644 --- a/indra/newview/llagentui.h +++ b/indra/newview/llagentui.h @@ -52,6 +52,11 @@ public: static BOOL buildLocationString(std::string& str, ELocationFormat fmt = LOCATION_FORMAT_LANDMARK); //build location string using a region position of the avatar. static BOOL buildLocationString(std::string& str, ELocationFormat fmt,const LLVector3& agent_pos_region); + /** + * @brief Check whether the agent is in neighborhood of the pole Within same region + * @return true if the agent is in neighborhood. + */ + static BOOL checkAgentDistance(const LLVector3& local_pole, F32 radius); }; #endif //LLAGENTUI_H diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index e184d99ffc..06c9171d67 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -49,7 +49,6 @@ #include "llviewerstats.h" #include "llmd5.h" #include "llpumpio.h" -#include "llimpanel.h" #include "llmimetypes.h" #include "llslurl.h" #include "llstartup.h" @@ -76,6 +75,7 @@ #include "llteleporthistory.h" #include "lllocationhistory.h" #include "llfasttimerview.h" +#include "llvoicechannel.h" #include "llweb.h" #include "llsecondlifeurls.h" diff --git a/indra/newview/llavatariconctrl.h b/indra/newview/llavatariconctrl.h index 426fcec514..65b5c86ed5 100644 --- a/indra/newview/llavatariconctrl.h +++ b/indra/newview/llavatariconctrl.h @@ -103,6 +103,8 @@ public: const std::string& getFirstName() const { return mFirstName; } const std::string& getLastName() const { return mLastName; } + void setDrawTooltip(bool value) { mDrawTooltip = value;} + protected: LLUUID mAvatarId; std::string mFirstName; diff --git a/indra/newview/llavatarlist.cpp b/indra/newview/llavatarlist.cpp index 3a07c6e5ef..e93d0dfa50 100644 --- a/indra/newview/llavatarlist.cpp +++ b/indra/newview/llavatarlist.cpp @@ -38,6 +38,7 @@ #include "llcallingcard.h" // for LLAvatarTracker #include "llcachename.h" #include "llvoiceclient.h" +#include "llviewercontrol.h" // for gSavedSettings static LLDefaultChildRegistry::Register<LLAvatarList> r("avatar_list"); @@ -45,6 +46,21 @@ static LLDefaultChildRegistry::Register<LLAvatarList> r("avatar_list"); // Used to limit time spent for avatar list update per frame. static const unsigned ADD_LIMIT = 50; +void LLAvatarList::toggleIcons() +{ + // Save the new value for new items to use. + mShowIcons = !mShowIcons; + gSavedSettings.setBOOL(mIconParamName, mShowIcons); + + // Show/hide icons for all existing items. + std::vector<LLPanel*> items; + getItems(items); + for( std::vector<LLPanel*>::const_iterator it = items.begin(); it != items.end(); it++) + { + static_cast<LLAvatarListItem*>(*it)->setAvatarIconVisible(mShowIcons); + } +} + static bool findInsensitive(std::string haystack, const std::string& needle_upper) { LLStringUtil::toUpper(haystack); @@ -73,13 +89,22 @@ LLAvatarList::LLAvatarList(const Params& p) setComparator(&NAME_COMPARATOR); } +void LLAvatarList::setShowIcons(std::string param_name) +{ + mIconParamName= param_name; + mShowIcons = gSavedSettings.getBOOL(mIconParamName); +} + // virtual void LLAvatarList::draw() { - if (mDirty) - refresh(); + // *NOTE dzaporozhan + // Call refresh() after draw() to avoid flickering of avatar list items. LLFlatListView::draw(); + + if (mDirty) + refresh(); } void LLAvatarList::setNameFilter(const std::string& filter) @@ -202,6 +227,7 @@ void LLAvatarList::addNewItem(const LLUUID& id, const std::string& name, BOOL is item->setContextMenu(mContextMenu); item->childSetVisible("info_btn", false); + item->setAvatarIconVisible(mShowIcons); addItem(item, id, pos); } diff --git a/indra/newview/llavatarlist.h b/indra/newview/llavatarlist.h index a83a72b26c..f60f1f00f3 100644 --- a/indra/newview/llavatarlist.h +++ b/indra/newview/llavatarlist.h @@ -70,7 +70,11 @@ public: void setContextMenu(LLAvatarListItem::ContextMenu* menu) { mContextMenu = menu; } + void toggleIcons(); void sortByName(); + void setShowIcons(std::string param_name); + bool getIconsVisible() const { return mShowIcons; } + const std::string getIconParamName() const{return mIconParamName;} virtual BOOL handleRightMouseDown(S32 x, S32 y, MASK mask); protected: @@ -86,7 +90,9 @@ private: bool mIgnoreOnlineStatus; bool mDirty; + bool mShowIcons; + std::string mIconParamName; std::string mNameFilter; uuid_vector_t mIDs; diff --git a/indra/newview/llavatarlistitem.cpp b/indra/newview/llavatarlistitem.cpp index ebc79aae48..4ecb9537ba 100644 --- a/indra/newview/llavatarlistitem.cpp +++ b/indra/newview/llavatarlistitem.cpp @@ -42,6 +42,7 @@ #include "llavatariconctrl.h" #include "llbutton.h" +S32 LLAvatarListItem::sIconWidth = 0; LLAvatarListItem::LLAvatarListItem() : LLPanel(), @@ -55,6 +56,12 @@ LLAvatarListItem::LLAvatarListItem() mOnlineStatus(E_UNKNOWN) { LLUICtrlFactory::getInstance()->buildPanel(this, "panel_avatar_list_item.xml"); + // Remember avatar icon width including its padding from the name text box, + // so that we can hide and show the icon again later. + if (!sIconWidth) + { + sIconWidth = mAvatarName->getRect().mLeft - mAvatarIcon->getRect().mLeft; + } } LLAvatarListItem::~LLAvatarListItem() @@ -188,6 +195,21 @@ void LLAvatarListItem::setAvatarId(const LLUUID& id, bool ignore_status_changes) gCacheName->get(id, FALSE, boost::bind(&LLAvatarListItem::onNameCache, this, _2, _3)); } +void LLAvatarListItem::setAvatarIconVisible(bool visible) +{ + // Already done? Then do nothing. + if (mAvatarIcon->getVisible() == (BOOL)visible) + return; + + // Show/hide avatar icon. + mAvatarIcon->setVisible(visible); + + // Move the avatar name horizontally by icon size + its distance from the avatar name. + LLRect name_rect = mAvatarName->getRect(); + name_rect.mLeft += visible ? sIconWidth : -sIconWidth; + mAvatarName->setRect(name_rect); +} + void LLAvatarListItem::onInfoBtnClick() { LLFloaterReg::showInstance("inspect_avatar", LLSD().insert("avatar_id", mAvatarId)); diff --git a/indra/newview/llavatarlistitem.h b/indra/newview/llavatarlistitem.h index b9cfed4b7b..a8d3919217 100644 --- a/indra/newview/llavatarlistitem.h +++ b/indra/newview/llavatarlistitem.h @@ -64,6 +64,7 @@ public: void setOnline(bool online); void setName(const std::string& name); void setAvatarId(const LLUUID& id, bool ignore_status_changes = false); + void setAvatarIconVisible(bool visible); const LLUUID& getAvatarId() const; const std::string getAvatarName() const; @@ -87,7 +88,7 @@ private: void onNameCache(const std::string& first_name, const std::string& last_name); - LLAvatarIconCtrl*mAvatarIcon; + LLAvatarIconCtrl* mAvatarIcon; LLTextBox* mAvatarName; LLTextBox* mStatus; @@ -98,6 +99,7 @@ private: LLUUID mAvatarId; EOnlineStatus mOnlineStatus; + static S32 sIconWidth; // icon width + padding }; #endif //LL_LLAVATARLISTITEM_H diff --git a/indra/newview/llchannelmanager.cpp b/indra/newview/llchannelmanager.cpp index 77f941eef0..6427422572 100644 --- a/indra/newview/llchannelmanager.cpp +++ b/indra/newview/llchannelmanager.cpp @@ -40,6 +40,8 @@ #include "llbottomtray.h" #include "llviewerwindow.h" #include "llrootview.h" +#include "llsyswellwindow.h" +#include "llfloaterreg.h" #include <algorithm> @@ -128,7 +130,7 @@ void LLChannelManager::onLoginCompleted() S32 channel_right_bound = gViewerWindow->getWorldViewRect().mRight - gSavedSettings.getS32("NotificationChannelRightMargin"); S32 channel_width = gSavedSettings.getS32("NotifyBoxWidth"); mStartUpChannel->init(channel_right_bound - channel_width, channel_right_bound); - mStartUpChannel->setShowToasts(true); + mStartUpChannel->setMouseDownCallback(boost::bind(&LLSysWellWindow::onStartUpToastClick, LLFloaterReg::getTypedInstance<LLSysWellWindow>("syswell_window"), _2, _3, _4)); mStartUpChannel->setCommitCallback(boost::bind(&LLChannelManager::onStartUpToastClose, this)); mStartUpChannel->createStartUpToast(away_notifications, gSavedSettings.getS32("StartUpToastLifeTime")); diff --git a/indra/newview/llchathistory.cpp b/indra/newview/llchathistory.cpp index fbb57e35f4..aaca568320 100644 --- a/indra/newview/llchathistory.cpp +++ b/indra/newview/llchathistory.cpp @@ -38,9 +38,215 @@ #include "llscrollcontainer.h" #include "llavatariconctrl.h" +#include "llimview.h" +#include "llcallingcard.h" //for LLAvatarTracker +#include "llagentdata.h" +#include "llavataractions.h" +#include "lltrans.h" + static LLDefaultChildRegistry::Register<LLChatHistory> r("chat_history"); static const std::string MESSAGE_USERNAME_DATE_SEPARATOR(" ----- "); +std::string formatCurrentTime() +{ + time_t utc_time; + utc_time = time_corrected(); + std::string timeStr ="["+ LLTrans::getString("TimeHour")+"]:[" + +LLTrans::getString("TimeMin")+"] "; + + LLSD substitution; + + substitution["datetime"] = (S32) utc_time; + LLStringUtil::format (timeStr, substitution); + + return timeStr; +} + +class LLChatHistoryHeader: public LLPanel +{ +public: + static LLChatHistoryHeader* createInstance(const std::string& file_name) + { + LLChatHistoryHeader* pInstance = new LLChatHistoryHeader; + LLUICtrlFactory::getInstance()->buildPanel(pInstance, file_name); + return pInstance; + } + + BOOL handleMouseUp(S32 x, S32 y, MASK mask) + { + return LLPanel::handleMouseUp(x,y,mask); + } + + void onAvatarIconContextMenuItemClicked(const LLSD& userdata) + { + std::string level = userdata.asString(); + + if (level == "profile") + { + LLAvatarActions::showProfile(getAvatarId()); + } + else if (level == "im") + { + LLAvatarActions::startIM(getAvatarId()); + } + else if (level == "add") + { + std::string name; + name.assign(getFirstName()); + name.append(" "); + name.append(getLastName()); + + LLAvatarActions::requestFriendshipDialog(getAvatarId(), name); + } + else if (level == "remove") + { + LLAvatarActions::removeFriendDialog(getAvatarId()); + } + } + + BOOL postBuild() + { + LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; + + registrar.add("AvatarIcon.Action", boost::bind(&LLChatHistoryHeader::onAvatarIconContextMenuItemClicked, this, _2)); + + LLMenuGL* menu = LLUICtrlFactory::getInstance()->createFromFile<LLMenuGL>("menu_avatar_icon.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); + + mPopupMenuHandleAvatar = menu->getHandle(); + + return LLPanel::postBuild(); + } + + bool pointInChild(const std::string& name,S32 x,S32 y) + { + LLUICtrl* child = findChild<LLUICtrl>(name); + if(!child) + return false; + + LLView* parent = child->getParent(); + if(parent!=this) + { + x-=parent->getRect().mLeft; + y-=parent->getRect().mBottom; + } + + S32 local_x = x - child->getRect().mLeft ; + S32 local_y = y - child->getRect().mBottom ; + return child->pointInView(local_x, local_y); + } + + BOOL handleRightMouseDown(S32 x, S32 y, MASK mask) + { + if(pointInChild("avatar_icon",x,y) || pointInChild("user_name",x,y)) + { + showContextMenu(x,y); + return TRUE; + } + + return LLPanel::handleRightMouseDown(x,y,mask); + } + const LLUUID& getAvatarId () const { return mAvatarID;} + const std::string& getFirstName() const { return mFirstName; } + const std::string& getLastName () const { return mLastName; } + + void setup(const LLChat& chat) + { + mAvatarID = chat.mFromID; + mSourceType = chat.mSourceType; + gCacheName->get(mAvatarID, FALSE, boost::bind(&LLChatHistoryHeader::nameUpdatedCallback, this, _1, _2, _3, _4)); + if(chat.mFromID.isNull()) + { + mSourceType = CHAT_SOURCE_SYSTEM; + } + + + LLTextBox* userName = getChild<LLTextBox>("user_name"); + + if(!chat.mFromName.empty()) + userName->setValue(chat.mFromName); + else + { + std::string SL = LLTrans::getString("SECOND_LIFE"); + userName->setValue(SL); + } + + LLTextBox* timeBox = getChild<LLTextBox>("time_box"); + timeBox->setValue(formatCurrentTime()); + + LLAvatarIconCtrl* icon = getChild<LLAvatarIconCtrl>("avatar_icon"); + + if(mSourceType != CHAT_SOURCE_AGENT) + icon->setDrawTooltip(false); + + if(!chat.mFromID.isNull()) + { + icon->setValue(chat.mFromID); + } + + } + + void nameUpdatedCallback(const LLUUID& id,const std::string& first,const std::string& last,BOOL is_group) + { + if (id != mAvatarID) + return; + mFirstName = first; + mLastName = last; + } +protected: + void showContextMenu(S32 x,S32 y) + { + if(mSourceType == CHAT_SOURCE_SYSTEM) + showSystemContextMenu(x,y); + if(mSourceType == CHAT_SOURCE_AGENT) + showAvatarContextMenu(x,y); + if(mSourceType == CHAT_SOURCE_OBJECT) + showObjectContextMenu(x,y); + } + + void showSystemContextMenu(S32 x,S32 y) + { + } + void showObjectContextMenu(S32 x,S32 y) + { + } + + void showAvatarContextMenu(S32 x,S32 y) + { + LLMenuGL* menu = (LLMenuGL*)mPopupMenuHandleAvatar.get(); + + if(menu) + { + bool is_friend = LLAvatarTracker::instance().getBuddyInfo(mAvatarID) != NULL; + + menu->setItemEnabled("Add Friend", !is_friend); + menu->setItemEnabled("Remove Friend", is_friend); + + if(gAgentID == mAvatarID) + { + menu->setItemEnabled("Add Friend", false); + menu->setItemEnabled("Send IM", false); + menu->setItemEnabled("Remove Friend", false); + } + + menu->buildDrawLabels(); + menu->updateParent(LLMenuGL::sMenuContainer); + LLMenuGL::showPopup(this, menu, x, y); + } + } + + + +protected: + LLHandle<LLView> mPopupMenuHandleAvatar; + + LLUUID mAvatarID; + EChatSourceType mSourceType; + std::string mFirstName; + std::string mLastName; + +}; + + LLChatHistory::LLChatHistory(const LLChatHistory::Params& p) : LLTextEditor(p), mMessageHeaderFilename(p.message_header), @@ -78,35 +284,27 @@ LLView* LLChatHistory::getSeparator() return separator; } -LLView* LLChatHistory::getHeader(const LLUUID& avatar_id, std::string& from, std::string& time) +LLView* LLChatHistory::getHeader(const LLChat& chat) { - LLPanel* header = LLUICtrlFactory::getInstance()->createFromFile<LLPanel>(mMessageHeaderFilename, NULL, LLPanel::child_registry_t::instance()); - LLTextBox* userName = header->getChild<LLTextBox>("user_name"); - userName->setValue(from); - LLTextBox* timeBox = header->getChild<LLTextBox>("time_box"); - timeBox->setValue(time); - if(!avatar_id.isNull()) - { - LLAvatarIconCtrl* icon = header->getChild<LLAvatarIconCtrl>("avatar_icon"); - icon->setValue(avatar_id); - } + LLChatHistoryHeader* header = LLChatHistoryHeader::createInstance(mMessageHeaderFilename); + header->setup(chat); return header; } -void LLChatHistory::appendWidgetMessage(const LLUUID& avatar_id, std::string& from, std::string& time, std::string& message, LLStyle::Params& style_params) +void LLChatHistory::appendWidgetMessage(const LLChat& chat, LLStyle::Params& style_params) { LLView* view = NULL; std::string view_text; - if (mLastFromName == from) + if (mLastFromName == chat.mFromName) { view = getSeparator(); view_text = "\n"; } else { - view = getHeader(avatar_id, from, time); - view_text = from + MESSAGE_USERNAME_DATE_SEPARATOR + time + '\n'; + view = getHeader(chat); + view_text = chat.mFromName + MESSAGE_USERNAME_DATE_SEPARATOR + formatCurrentTime() + '\n'; } //Prepare the rect for the view LLRect target_rect = getDocumentView()->getRect(); @@ -125,10 +323,10 @@ void LLChatHistory::appendWidgetMessage(const LLUUID& avatar_id, std::string& fr appendWidget(p, view_text, false); //Append the text message - message += '\n'; + std::string message = chat.mText + '\n'; appendText(message, FALSE, style_params); - mLastFromName = from; + mLastFromName = chat.mFromName; blockUndo(); setCursorAndScrollToEnd(); } diff --git a/indra/newview/llchathistory.h b/indra/newview/llchathistory.h index 69066a0110..92dcfdd958 100644 --- a/indra/newview/llchathistory.h +++ b/indra/newview/llchathistory.h @@ -34,6 +34,7 @@ #define LLCHATHISTORY_H_ #include "lltexteditor.h" +#include "llchat.h" //Chat log widget allowing addition of a message as a widget class LLChatHistory : public LLTextEditor @@ -85,7 +86,7 @@ class LLChatHistory : public LLTextEditor * @param time time of a message. * @return pointer to LLView header object. */ - LLView* getHeader(const LLUUID& avatar_id, std::string& from, std::string& time); + LLView* getHeader(const LLChat& chat); public: ~LLChatHistory(); @@ -94,11 +95,11 @@ class LLChatHistory : public LLTextEditor * Appends a widget message. * If last user appended message, concurs with current user, * separator is added before the message, otherwise header is added. - * @param from owner of a message. + * @param chat - base chat message. * @param time time of a message. * @param message message itself. */ - void appendWidgetMessage(const LLUUID& avatar_id, std::string& from, std::string& time, std::string& message, LLStyle::Params& style_params); + void appendWidgetMessage(const LLChat& chat, LLStyle::Params& style_params); private: std::string mLastFromName; diff --git a/indra/newview/llfavoritesbar.cpp b/indra/newview/llfavoritesbar.cpp index 3b5b7f570e..a6afbc05be 100644 --- a/indra/newview/llfavoritesbar.cpp +++ b/indra/newview/llfavoritesbar.cpp @@ -503,13 +503,15 @@ void LLFavoritesBarCtrl::handleNewFavoriteDragAndDrop(LLInventoryItem *item, con return; } + LLPointer<LLViewerInventoryItem> viewer_item = new LLViewerInventoryItem(item); + if (dest) { - insertBeforeItem(mItems, dest->getLandmarkId(), item->getUUID()); + insertBeforeItem(mItems, dest->getLandmarkId(), viewer_item); } else { - mItems.push_back(gInventory.getItem(item->getUUID())); + mItems.push_back(viewer_item); } int sortField = 0; @@ -534,13 +536,22 @@ void LLFavoritesBarCtrl::handleNewFavoriteDragAndDrop(LLInventoryItem *item, con } } - copy_inventory_item( - gAgent.getID(), - item->getPermissions().getOwner(), - item->getUUID(), - favorites_id, - std::string(), - cb); + LLToolDragAndDrop* tool_dad = LLToolDragAndDrop::getInstance(); + if (tool_dad->getSource() == LLToolDragAndDrop::SOURCE_NOTECARD) + { + viewer_item->setType(LLAssetType::AT_FAVORITE); + copy_inventory_from_notecard(tool_dad->getObjectID(), tool_dad->getSourceID(), viewer_item.get(), gInventoryCallbacks.registerCB(cb)); + } + else + { + copy_inventory_item( + gAgent.getID(), + item->getPermissions().getOwner(), + item->getUUID(), + favorites_id, + std::string(), + cb); + } llinfos << "Copied inventory item #" << item->getUUID() << " to favorites." << llendl; } @@ -1263,10 +1274,9 @@ void LLFavoritesBarCtrl::updateItemsOrder(LLInventoryModel::item_array_t& items, items.insert(findItemByUUID(items, destItem->getUUID()), srcItem); } -void LLFavoritesBarCtrl::insertBeforeItem(LLInventoryModel::item_array_t& items, const LLUUID& beforeItemId, const LLUUID& insertedItemId) +void LLFavoritesBarCtrl::insertBeforeItem(LLInventoryModel::item_array_t& items, const LLUUID& beforeItemId, LLViewerInventoryItem* insertedItem) { LLViewerInventoryItem* beforeItem = gInventory.getItem(beforeItemId); - LLViewerInventoryItem* insertedItem = gInventory.getItem(insertedItemId); items.insert(findItemByUUID(items, beforeItem->getUUID()), insertedItem); } diff --git a/indra/newview/llfavoritesbar.h b/indra/newview/llfavoritesbar.h index ea2a3d08e2..e90d13f9d5 100644 --- a/indra/newview/llfavoritesbar.h +++ b/indra/newview/llfavoritesbar.h @@ -141,7 +141,7 @@ private: * inserts an item identified by insertedItemId BEFORE an item identified by beforeItemId. * this function assumes that an item identified by insertedItemId doesn't exist in items array. */ - void insertBeforeItem(LLInventoryModel::item_array_t& items, const LLUUID& beforeItemId, const LLUUID& insertedItemId); + void insertBeforeItem(LLInventoryModel::item_array_t& items, const LLUUID& beforeItemId, LLViewerInventoryItem* insertedItem); // finds an item by it's UUID in the items array LLInventoryModel::item_array_t::iterator findItemByUUID(LLInventoryModel::item_array_t& items, const LLUUID& id); diff --git a/indra/newview/llfloaterchatterbox.cpp b/indra/newview/llfloaterchatterbox.cpp index dea656b0e4..fbf09207fe 100644 --- a/indra/newview/llfloaterchatterbox.cpp +++ b/indra/newview/llfloaterchatterbox.cpp @@ -42,7 +42,7 @@ #include "llfloaterfriends.h" #include "llfloatergroups.h" #include "llviewercontrol.h" -#include "llimview.h" +#include "llvoicechannel.h" #include "llimpanel.h" // diff --git a/indra/newview/llfloaterinventory.cpp b/indra/newview/llfloaterinventory.cpp index c890f9f122..a47916b7d7 100644 --- a/indra/newview/llfloaterinventory.cpp +++ b/indra/newview/llfloaterinventory.cpp @@ -1438,7 +1438,11 @@ void LLInventoryPanel::modelChanged(U32 mask) } LLFolderViewFolder* new_parent = (LLFolderViewFolder*)mFolders->getItemByID(model_item->getParentUUID()); - if (view_item->getParentFolder() != new_parent) + + // added check against NULL for cases when Inventory panel contains startFolder. + // in this case parent is LLFolderView (LLInventoryPanel::mFolders) itself. + // this check is a fix for bug EXT-1859. + if (NULL != new_parent && view_item->getParentFolder() != new_parent) { view_item->getParentFolder()->extractItem(view_item); view_item->addToFolder(new_parent, mFolders); diff --git a/indra/newview/llfloatervoicedevicesettings.cpp b/indra/newview/llfloatervoicedevicesettings.cpp index b64257b11d..aca9198f59 100644 --- a/indra/newview/llfloatervoicedevicesettings.cpp +++ b/indra/newview/llfloatervoicedevicesettings.cpp @@ -43,7 +43,7 @@ #include "llsliderctrl.h" #include "llviewercontrol.h" #include "llvoiceclient.h" -#include "llimpanel.h" +#include "llvoicechannel.h" // Library includes (after viewer) #include "lluictrlfactory.h" diff --git a/indra/newview/llimfloater.cpp b/indra/newview/llimfloater.cpp index a20b5ea66c..b86795f696 100644 --- a/indra/newview/llimfloater.cpp +++ b/indra/newview/llimfloater.cpp @@ -42,7 +42,6 @@ #include "llchiclet.h" #include "llfloaterchat.h" #include "llfloaterreg.h" -#include "llimview.h" #include "lllineeditor.h" #include "lllogchat.h" #include "llpanelimcontrolpanel.h" @@ -50,6 +49,7 @@ #include "lltrans.h" #include "llchathistory.h" #include "llviewerwindow.h" +#include "llvoicechannel.h" #include "lltransientfloatermgr.h" @@ -61,7 +61,14 @@ LLIMFloater::LLIMFloater(const LLUUID& session_id) mLastMessageIndex(-1), mDialog(IM_NOTHING_SPECIAL), mChatHistory(NULL), - mInputEditor(NULL), + mInputEditor(NULL), + mSavedTitle(), + mTypingStart(), + mShouldSendTypingState(false), + mMeTyping(false), + mOtherTyping(false), + mTypingTimer(), + mTypingTimeoutTimer(), mPositioned(false), mSessionInitialized(false) { @@ -71,12 +78,15 @@ LLIMFloater::LLIMFloater(const LLUUID& session_id) mSessionInitialized = im_session->mSessionInitialized; mDialog = im_session->mType; - if (IM_NOTHING_SPECIAL == mDialog || IM_SESSION_P2P_INVITE == mDialog) - { + switch(mDialog){ + case IM_NOTHING_SPECIAL: + case IM_SESSION_P2P_INVITE: mFactoryMap["panel_im_control_panel"] = LLCallbackMap(createPanelIMControl, this); - } - else - { + break; + case IM_SESSION_CONFERENCE_START: + mFactoryMap["panel_im_control_panel"] = LLCallbackMap(createPanelAdHocControl, this); + break; + default: mFactoryMap["panel_im_control_panel"] = LLCallbackMap(createPanelGroupControl, this); } } @@ -95,6 +105,7 @@ void LLIMFloater::onFocusReceived() // virtual void LLIMFloater::onClose(bool app_quitting) { + setTyping(false); gIMMgr->leaveSession(mSessionID); } @@ -141,6 +152,7 @@ void LLIMFloater::onSendMsg( LLUICtrl* ctrl, void* userdata ) { LLIMFloater* self = (LLIMFloater*) userdata; self->sendMsg(); + self->setTyping(false); } void LLIMFloater::sendMsg() @@ -193,9 +205,10 @@ BOOL LLIMFloater::postBuild() if (other_party_id.notNull()) { mOtherParticipantUUID = other_party_id; - mControlPanel->setID(mOtherParticipantUUID); } + mControlPanel->setSessionId(mSessionID); + LLButton* slide_left = getChild<LLButton>("slide_left_btn"); slide_left->setVisible(mControlPanel->getVisible()); slide_left->setClickedCallback(boost::bind(&LLIMFloater::onSlide, this)); @@ -228,12 +241,37 @@ BOOL LLIMFloater::postBuild() LLLogChat::loadHistory(getTitle(), &chatFromLogFile, (void *)this); } + mTypingStart = LLTrans::getString("IM_typing_start_string"); + + // Disable input editor if session cannot accept text + LLIMModel::LLIMSession* im_session = + LLIMModel::instance().findIMSession(mSessionID); + if( im_session && !im_session->mTextIMPossible ) + { + mInputEditor->setEnabled(FALSE); + mInputEditor->setLabel(LLTrans::getString("IM_unavailable_text_label")); + } + //*TODO if session is not initialized yet, add some sort of a warning message like "starting session...blablabla" //see LLFloaterIMPanel for how it is done (IB) return LLDockableFloater::postBuild(); } +// virtual +void LLIMFloater::draw() +{ + if ( mMeTyping ) + { + // Time out if user hasn't typed for a while. + if ( mTypingTimeoutTimer.getElapsedTimeF32() > LLAgent::TYPING_TIMEOUT_SECS ) + { + setTyping(false); + } + } + + LLFloater::draw(); +} // static @@ -255,6 +293,15 @@ void* LLIMFloater::createPanelGroupControl(void* userdata) return self->mControlPanel; } +// static +void* LLIMFloater::createPanelAdHocControl(void* userdata) +{ + LLIMFloater *self = (LLIMFloater*)userdata; + self->mControlPanel = new LLPanelAdHocControlPanel(self->mSessionID); + self->mControlPanel->setXMLFilename("panel_adhoc_control_panel.xml"); + return self->mControlPanel; +} + void LLIMFloater::onSlide() { LLPanel* im_control_panel = getChild<LLPanel>("panel_im_control_panel"); @@ -379,10 +426,12 @@ void LLIMFloater::sessionInitReplyReceived(const LLUUID& im_session_id) { mSessionInitialized = true; + //will be different only for an ad-hoc im session if (mSessionID != im_session_id) { mSessionID = im_session_id; setKey(im_session_id); + mControlPanel->setSessionId(im_session_id); } //*TODO here we should remove "starting session..." warning message if we added it in postBuild() (IB) @@ -402,7 +451,8 @@ void LLIMFloater::sessionInitReplyReceived(const LLUUID& im_session_id) void LLIMFloater::updateMessages() { - std::list<LLSD> messages = LLIMModel::instance().getMessages(mSessionID, mLastMessageIndex+1); + std::list<LLSD> messages; + LLIMModel::instance().getMessages(mSessionID, messages, mLastMessageIndex+1); std::string agent_name; gCacheName->getFullName(gAgentID, agent_name); @@ -428,7 +478,11 @@ void LLIMFloater::updateMessages() if (from == agent_name) from = LLTrans::getString("You"); - mChatHistory->appendWidgetMessage(from_id, from, time, message, style_params); + LLChat chat(message); + chat.mFromID = from_id; + chat.mFromName = from; + + mChatHistory->appendWidgetMessage(chat, style_params); mLastMessageIndex = msg["index"].asInteger(); } @@ -440,9 +494,14 @@ void LLIMFloater::onInputEditorFocusReceived( LLFocusableElement* caller, void* { LLIMFloater* self= (LLIMFloater*) userdata; - //in disconnected state IM input editor should be disabled - self->mInputEditor->setEnabled(!gDisconnected); - + // Allow enabling the LLIMFloater input editor only if session can accept text + LLIMModel::LLIMSession* im_session = + LLIMModel::instance().findIMSession(self->mSessionID); + if( im_session && im_session->mTextIMPossible ) + { + //in disconnected state IM input editor should be disabled + self->mInputEditor->setEnabled(!gDisconnected); + } self->mChatHistory->setCursorAndScrollToEnd(); } @@ -450,7 +509,7 @@ void LLIMFloater::onInputEditorFocusReceived( LLFocusableElement* caller, void* void LLIMFloater::onInputEditorFocusLost(LLFocusableElement* caller, void* userdata) { LLIMFloater* self = (LLIMFloater*) userdata; - self->setTyping(FALSE); + self->setTyping(false); } // static @@ -460,19 +519,142 @@ void LLIMFloater::onInputEditorKeystroke(LLLineEditor* caller, void* userdata) std::string text = self->mInputEditor->getText(); if (!text.empty()) { - self->setTyping(TRUE); + self->setTyping(true); } else { // Deleting all text counts as stopping typing. - self->setTyping(FALSE); + self->setTyping(false); } } +void LLIMFloater::setTyping(bool typing) +{ + if ( typing ) + { + // Started or proceeded typing, reset the typing timeout timer + mTypingTimeoutTimer.reset(); + } + + if ( mMeTyping != typing ) + { + // Typing state is changed + mMeTyping = typing; + // So, should send current state + mShouldSendTypingState = true; + // In case typing is started, send state after some delay + mTypingTimer.reset(); + } + + // Don't want to send typing indicators to multiple people, potentially too + // much network traffic. Only send in person-to-person IMs. + if ( mShouldSendTypingState && mDialog == IM_NOTHING_SPECIAL ) + { + if ( mMeTyping ) + { + if ( mTypingTimer.getElapsedTimeF32() > 1.f ) + { + // Still typing, send 'start typing' notification + LLIMModel::instance().sendTypingState(mSessionID, mOtherParticipantUUID, TRUE); + mShouldSendTypingState = false; + } + } + else + { + // Send 'stop typing' notification immediately + LLIMModel::instance().sendTypingState(mSessionID, mOtherParticipantUUID, FALSE); + mShouldSendTypingState = false; + } + } -//just a stub for now -void LLIMFloater::setTyping(BOOL typing) + LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionID); + if (speaker_mgr) + speaker_mgr->setSpeakerTyping(gAgent.getID(), FALSE); + +} + +void LLIMFloater::processIMTyping(const LLIMInfo* im_info, BOOL typing) +{ + if ( typing ) + { + // other user started typing + addTypingIndicator(im_info); + } + else + { + // other user stopped typing + removeTypingIndicator(im_info); + } +} + +void LLIMFloater::processSessionUpdate(const LLSD& session_update) +{ + // *TODO : verify following code when moderated mode will be implemented + if ( false && session_update.has("moderated_mode") && + session_update["moderated_mode"].has("voice") ) + { + BOOL voice_moderated = session_update["moderated_mode"]["voice"]; + const std::string session_label = LLIMModel::instance().getName(mSessionID); + + if (voice_moderated) + { + setTitle(session_label + std::string(" ") + LLTrans::getString("IM_moderated_chat_label")); + } + else + { + setTitle(session_label); + } + + // *TODO : uncomment this when/if LLPanelActiveSpeakers panel will be added + //update the speakers dropdown too + //mSpeakerPanel->setVoiceModerationCtrlMode(voice_moderated); + } +} + +void LLIMFloater::addTypingIndicator(const LLIMInfo* im_info) { + // We may have lost a "stop-typing" packet, don't add it twice + if ( im_info && !mOtherTyping ) + { + mOtherTyping = true; + + // Create typing is started title string + LLUIString typing_start(mTypingStart); + typing_start.setArg("[NAME]", im_info->mName); + + // Save and set new title + mSavedTitle = getTitle(); + setTitle (typing_start); + + // Update speaker + LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionID); + if ( speaker_mgr ) + { + speaker_mgr->setSpeakerTyping(im_info->mFromID, TRUE); + } + } +} + +void LLIMFloater::removeTypingIndicator(const LLIMInfo* im_info) +{ + if ( mOtherTyping ) + { + mOtherTyping = false; + + // Revert the title to saved one + setTitle(mSavedTitle); + + if ( im_info ) + { + // Update speaker + LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionID); + if ( speaker_mgr ) + { + speaker_mgr->setSpeakerTyping(im_info->mFromID, FALSE); + } + } + + } } void LLIMFloater::chatFromLogFile(LLLogChat::ELogLineType type, std::string line, void* userdata) diff --git a/indra/newview/llimfloater.h b/indra/newview/llimfloater.h index 99810b6d6d..3da27ac941 100644 --- a/indra/newview/llimfloater.h +++ b/indra/newview/llimfloater.h @@ -55,6 +55,8 @@ public: // LLView overrides /*virtual*/ BOOL postBuild(); /*virtual*/ void setVisible(BOOL visible); + // Check typing timeout timer. + /*virtual*/ void draw(); // LLFloater overrides /*virtual*/ void onClose(bool app_quitting); @@ -85,6 +87,8 @@ public: void setPositioned(bool b) { mPositioned = b; }; void onVisibilityChange(const LLSD& new_visibility); + void processIMTyping(const LLIMInfo* im_info, BOOL typing); + void processSessionUpdate(const LLSD& session_update); private: // process focus events to set a currently active session @@ -94,15 +98,21 @@ private: static void onInputEditorFocusReceived( LLFocusableElement* caller, void* userdata ); static void onInputEditorFocusLost(LLFocusableElement* caller, void* userdata); static void onInputEditorKeystroke(LLLineEditor* caller, void* userdata); - void setTyping(BOOL typing); + void setTyping(bool typing); void onSlide(); static void* createPanelIMControl(void* userdata); static void* createPanelGroupControl(void* userdata); + static void* createPanelAdHocControl(void* userdata); // gets a rect that bounds possible positions for the LLIMFloater on a screen (EXT-1111) void getAllowedRect(LLRect& rect); static void chatFromLogFile(LLLogChat::ELogLineType type, std::string line, void* userdata); + // Add the "User is typing..." indicator. + void addTypingIndicator(const LLIMInfo* im_info); + + // Remove the "User is typing..." indicator. + void removeTypingIndicator(const LLIMInfo* im_info = NULL); LLPanelChatControlPanel* mControlPanel; LLUUID mSessionID; @@ -114,6 +124,14 @@ private: LLLineEditor* mInputEditor; bool mPositioned; + std::string mSavedTitle; + LLUIString mTypingStart; + bool mMeTyping; + bool mOtherTyping; + bool mShouldSendTypingState; + LLFrameTimer mTypingTimer; + LLFrameTimer mTypingTimeoutTimer; + bool mSessionInitialized; LLSD mQueuedMsgsForInit; }; diff --git a/indra/newview/llimpanel.cpp b/indra/newview/llimpanel.cpp index 163984f740..77ee90f681 100644 --- a/indra/newview/llimpanel.cpp +++ b/indra/newview/llimpanel.cpp @@ -54,6 +54,7 @@ #include "llconsole.h" #include "llgroupactions.h" #include "llfloater.h" +#include "llfloateractivespeakers.h" #include "llfloatercall.h" #include "llavataractions.h" #include "llimview.h" @@ -77,6 +78,7 @@ #include "llviewercontrol.h" #include "lluictrlfactory.h" #include "llviewerwindow.h" +#include "llvoicechannel.h" #include "lllogchat.h" #include "llweb.h" #include "llhttpclient.h" @@ -90,7 +92,6 @@ const S32 LINE_HEIGHT = 16; const S32 MIN_WIDTH = 200; const S32 MIN_HEIGHT = 130; -const U32 DEFAULT_RETRIES_COUNT = 3; // // Statics @@ -100,831 +101,6 @@ static std::string sTitleString = "Instant Message with [NAME]"; static std::string sTypingStartString = "[NAME]: ..."; static std::string sSessionStartString = "Starting session with [NAME] please wait."; -LLVoiceChannel::voice_channel_map_t LLVoiceChannel::sVoiceChannelMap; -LLVoiceChannel::voice_channel_map_uri_t LLVoiceChannel::sVoiceChannelURIMap; -LLVoiceChannel* LLVoiceChannel::sCurrentVoiceChannel = NULL; -LLVoiceChannel* LLVoiceChannel::sSuspendedVoiceChannel = NULL; - -BOOL LLVoiceChannel::sSuspended = FALSE; - - - -class LLVoiceCallCapResponder : public LLHTTPClient::Responder -{ -public: - LLVoiceCallCapResponder(const LLUUID& session_id) : mSessionID(session_id) {}; - - virtual void error(U32 status, const std::string& reason); // called with bad status codes - virtual void result(const LLSD& content); - -private: - LLUUID mSessionID; -}; - - -void LLVoiceCallCapResponder::error(U32 status, const std::string& reason) -{ - llwarns << "LLVoiceCallCapResponder::error(" - << status << ": " << reason << ")" - << llendl; - LLVoiceChannel* channelp = LLVoiceChannel::getChannelByID(mSessionID); - if ( channelp ) - { - if ( 403 == status ) - { - //403 == no ability - LLNotifications::instance().add( - "VoiceNotAllowed", - channelp->getNotifyArgs()); - } - else - { - LLNotifications::instance().add( - "VoiceCallGenericError", - channelp->getNotifyArgs()); - } - channelp->deactivate(); - } -} - -void LLVoiceCallCapResponder::result(const LLSD& content) -{ - LLVoiceChannel* channelp = LLVoiceChannel::getChannelByID(mSessionID); - if (channelp) - { - //*TODO: DEBUG SPAM - LLSD::map_const_iterator iter; - for(iter = content.beginMap(); iter != content.endMap(); ++iter) - { - llinfos << "LLVoiceCallCapResponder::result got " - << iter->first << llendl; - } - - channelp->setChannelInfo( - content["voice_credentials"]["channel_uri"].asString(), - content["voice_credentials"]["channel_credentials"].asString()); - } -} - -// -// LLVoiceChannel -// -LLVoiceChannel::LLVoiceChannel(const LLUUID& session_id, const std::string& session_name) : - mSessionID(session_id), - mState(STATE_NO_CHANNEL_INFO), - mSessionName(session_name), - mIgnoreNextSessionLeave(FALSE) -{ - mNotifyArgs["VOICE_CHANNEL_NAME"] = mSessionName; - - if (!sVoiceChannelMap.insert(std::make_pair(session_id, this)).second) - { - // a voice channel already exists for this session id, so this instance will be orphaned - // the end result should simply be the failure to make voice calls - llwarns << "Duplicate voice channels registered for session_id " << session_id << llendl; - } - - LLVoiceClient::getInstance()->addObserver(this); -} - -LLVoiceChannel::~LLVoiceChannel() -{ - // Don't use LLVoiceClient::getInstance() here -- this can get called during atexit() time and that singleton MAY have already been destroyed. - if(gVoiceClient) - { - gVoiceClient->removeObserver(this); - } - - sVoiceChannelMap.erase(mSessionID); - sVoiceChannelURIMap.erase(mURI); -} - -void LLVoiceChannel::setChannelInfo( - const std::string& uri, - const std::string& credentials) -{ - setURI(uri); - - mCredentials = credentials; - - if (mState == STATE_NO_CHANNEL_INFO) - { - if (mURI.empty()) - { - LLNotifications::instance().add("VoiceChannelJoinFailed", mNotifyArgs); - llwarns << "Received empty URI for channel " << mSessionName << llendl; - deactivate(); - } - else if (mCredentials.empty()) - { - LLNotifications::instance().add("VoiceChannelJoinFailed", mNotifyArgs); - llwarns << "Received empty credentials for channel " << mSessionName << llendl; - deactivate(); - } - else - { - setState(STATE_READY); - - // if we are supposed to be active, reconnect - // this will happen on initial connect, as we request credentials on first use - if (sCurrentVoiceChannel == this) - { - // just in case we got new channel info while active - // should move over to new channel - activate(); - } - } - } -} - -void LLVoiceChannel::onChange(EStatusType type, const std::string &channelURI, bool proximal) -{ - if (channelURI != mURI) - { - return; - } - - if (type < BEGIN_ERROR_STATUS) - { - handleStatusChange(type); - } - else - { - handleError(type); - } -} - -void LLVoiceChannel::handleStatusChange(EStatusType type) -{ - // status updates - switch(type) - { - case STATUS_LOGIN_RETRY: - //mLoginNotificationHandle = LLNotifyBox::showXml("VoiceLoginRetry")->getHandle(); - LLNotifications::instance().add("VoiceLoginRetry"); - break; - case STATUS_LOGGED_IN: - //if (!mLoginNotificationHandle.isDead()) - //{ - // LLNotifyBox* notifyp = (LLNotifyBox*)mLoginNotificationHandle.get(); - // if (notifyp) - // { - // notifyp->close(); - // } - // mLoginNotificationHandle.markDead(); - //} - break; - case STATUS_LEFT_CHANNEL: - if (callStarted() && !mIgnoreNextSessionLeave && !sSuspended) - { - // if forceably removed from channel - // update the UI and revert to default channel - LLNotifications::instance().add("VoiceChannelDisconnected", mNotifyArgs); - deactivate(); - } - mIgnoreNextSessionLeave = FALSE; - break; - case STATUS_JOINING: - if (callStarted()) - { - setState(STATE_RINGING); - } - break; - case STATUS_JOINED: - if (callStarted()) - { - setState(STATE_CONNECTED); - } - default: - break; - } -} - -// default behavior is to just deactivate channel -// derived classes provide specific error messages -void LLVoiceChannel::handleError(EStatusType type) -{ - deactivate(); - setState(STATE_ERROR); -} - -BOOL LLVoiceChannel::isActive() -{ - // only considered active when currently bound channel matches what our channel - return callStarted() && LLVoiceClient::getInstance()->getCurrentChannel() == mURI; -} - -BOOL LLVoiceChannel::callStarted() -{ - return mState >= STATE_CALL_STARTED; -} - -void LLVoiceChannel::deactivate() -{ - if (mState >= STATE_RINGING) - { - // ignore session leave event - mIgnoreNextSessionLeave = TRUE; - } - - if (callStarted()) - { - setState(STATE_HUNG_UP); - // mute the microphone if required when returning to the proximal channel - if (gSavedSettings.getBOOL("AutoDisengageMic") && sCurrentVoiceChannel == this) - { - gSavedSettings.setBOOL("PTTCurrentlyEnabled", true); - } - } - - if (sCurrentVoiceChannel == this) - { - // default channel is proximal channel - sCurrentVoiceChannel = LLVoiceChannelProximal::getInstance(); - sCurrentVoiceChannel->activate(); - } -} - -void LLVoiceChannel::activate() -{ - if (callStarted()) - { - return; - } - - // deactivate old channel and mark ourselves as the active one - if (sCurrentVoiceChannel != this) - { - // mark as current before deactivating the old channel to prevent - // activating the proximal channel between IM calls - LLVoiceChannel* old_channel = sCurrentVoiceChannel; - sCurrentVoiceChannel = this; - if (old_channel) - { - old_channel->deactivate(); - } - } - - if (mState == STATE_NO_CHANNEL_INFO) - { - // responsible for setting status to active - getChannelInfo(); - } - else - { - setState(STATE_CALL_STARTED); - } -} - -void LLVoiceChannel::getChannelInfo() -{ - // pretend we have everything we need - if (sCurrentVoiceChannel == this) - { - setState(STATE_CALL_STARTED); - } -} - -//static -LLVoiceChannel* LLVoiceChannel::getChannelByID(const LLUUID& session_id) -{ - voice_channel_map_t::iterator found_it = sVoiceChannelMap.find(session_id); - if (found_it == sVoiceChannelMap.end()) - { - return NULL; - } - else - { - return found_it->second; - } -} - -//static -LLVoiceChannel* LLVoiceChannel::getChannelByURI(std::string uri) -{ - voice_channel_map_uri_t::iterator found_it = sVoiceChannelURIMap.find(uri); - if (found_it == sVoiceChannelURIMap.end()) - { - return NULL; - } - else - { - return found_it->second; - } -} - -void LLVoiceChannel::updateSessionID(const LLUUID& new_session_id) -{ - sVoiceChannelMap.erase(sVoiceChannelMap.find(mSessionID)); - mSessionID = new_session_id; - sVoiceChannelMap.insert(std::make_pair(mSessionID, this)); -} - -void LLVoiceChannel::setURI(std::string uri) -{ - sVoiceChannelURIMap.erase(mURI); - mURI = uri; - sVoiceChannelURIMap.insert(std::make_pair(mURI, this)); -} - -void LLVoiceChannel::setState(EState state) -{ - switch(state) - { - case STATE_RINGING: - gIMMgr->addSystemMessage(mSessionID, "ringing", mNotifyArgs); - break; - case STATE_CONNECTED: - gIMMgr->addSystemMessage(mSessionID, "connected", mNotifyArgs); - break; - case STATE_HUNG_UP: - gIMMgr->addSystemMessage(mSessionID, "hang_up", mNotifyArgs); - break; - default: - break; - } - - mState = state; -} - -void LLVoiceChannel::toggleCallWindowIfNeeded(EState state) -{ - if (state == STATE_CONNECTED) - { - LLFloaterReg::showInstance("voice_call", mSessionID); - } - // By checking that current state is CONNECTED we make sure that the call window - // has been shown, hence there's something to hide. This helps when user presses - // the "End call" button right after initiating the call. - // *TODO: move this check to LLFloaterCall? - else if (state == STATE_HUNG_UP && mState == STATE_CONNECTED) - { - LLFloaterReg::hideInstance("voice_call", mSessionID); - } -} - -//static -void LLVoiceChannel::initClass() -{ - sCurrentVoiceChannel = LLVoiceChannelProximal::getInstance(); -} - - -//static -void LLVoiceChannel::suspend() -{ - if (!sSuspended) - { - sSuspendedVoiceChannel = sCurrentVoiceChannel; - sSuspended = TRUE; - } -} - -//static -void LLVoiceChannel::resume() -{ - if (sSuspended) - { - if (gVoiceClient->voiceEnabled()) - { - if (sSuspendedVoiceChannel) - { - sSuspendedVoiceChannel->activate(); - } - else - { - LLVoiceChannelProximal::getInstance()->activate(); - } - } - sSuspended = FALSE; - } -} - - -// -// LLVoiceChannelGroup -// - -LLVoiceChannelGroup::LLVoiceChannelGroup(const LLUUID& session_id, const std::string& session_name) : - LLVoiceChannel(session_id, session_name) -{ - mRetries = DEFAULT_RETRIES_COUNT; - mIsRetrying = FALSE; -} - -void LLVoiceChannelGroup::deactivate() -{ - if (callStarted()) - { - LLVoiceClient::getInstance()->leaveNonSpatialChannel(); - } - LLVoiceChannel::deactivate(); -} - -void LLVoiceChannelGroup::activate() -{ - if (callStarted()) return; - - LLVoiceChannel::activate(); - - if (callStarted()) - { - // we have the channel info, just need to use it now - LLVoiceClient::getInstance()->setNonSpatialChannel( - mURI, - mCredentials); - -#if 0 // *TODO - if (!gAgent.isInGroup(mSessionID)) // ad-hoc channel - { - // Add the party to the list of people with which we've recently interacted. - for (/*people in the chat*/) - LLRecentPeople::instance().add(buddy_id); - } -#endif - } -} - -void LLVoiceChannelGroup::getChannelInfo() -{ - LLViewerRegion* region = gAgent.getRegion(); - if (region) - { - std::string url = region->getCapability("ChatSessionRequest"); - LLSD data; - data["method"] = "call"; - data["session-id"] = mSessionID; - LLHTTPClient::post(url, - data, - new LLVoiceCallCapResponder(mSessionID)); - } -} - -void LLVoiceChannelGroup::setChannelInfo( - const std::string& uri, - const std::string& credentials) -{ - setURI(uri); - - mCredentials = credentials; - - if (mState == STATE_NO_CHANNEL_INFO) - { - if(!mURI.empty() && !mCredentials.empty()) - { - setState(STATE_READY); - - // if we are supposed to be active, reconnect - // this will happen on initial connect, as we request credentials on first use - if (sCurrentVoiceChannel == this) - { - // just in case we got new channel info while active - // should move over to new channel - activate(); - } - } - else - { - //*TODO: notify user - llwarns << "Received invalid credentials for channel " << mSessionName << llendl; - deactivate(); - } - } - else if ( mIsRetrying ) - { - // we have the channel info, just need to use it now - LLVoiceClient::getInstance()->setNonSpatialChannel( - mURI, - mCredentials); - } -} - -void LLVoiceChannelGroup::handleStatusChange(EStatusType type) -{ - // status updates - switch(type) - { - case STATUS_JOINED: - mRetries = 3; - mIsRetrying = FALSE; - default: - break; - } - - LLVoiceChannel::handleStatusChange(type); -} - -void LLVoiceChannelGroup::handleError(EStatusType status) -{ - std::string notify; - switch(status) - { - case ERROR_CHANNEL_LOCKED: - case ERROR_CHANNEL_FULL: - notify = "VoiceChannelFull"; - break; - case ERROR_NOT_AVAILABLE: - //clear URI and credentials - //set the state to be no info - //and activate - if ( mRetries > 0 ) - { - mRetries--; - mIsRetrying = TRUE; - mIgnoreNextSessionLeave = TRUE; - - getChannelInfo(); - return; - } - else - { - notify = "VoiceChannelJoinFailed"; - mRetries = DEFAULT_RETRIES_COUNT; - mIsRetrying = FALSE; - } - - break; - - case ERROR_UNKNOWN: - default: - break; - } - - // notification - if (!notify.empty()) - { - LLNotificationPtr notification = LLNotifications::instance().add(notify, mNotifyArgs); - // echo to im window - gIMMgr->addMessage(mSessionID, LLUUID::null, SYSTEM_FROM, notification->getMessage()); - } - - LLVoiceChannel::handleError(status); -} - -void LLVoiceChannelGroup::setState(EState state) -{ - // HACK: Open/close the call window if needed. - toggleCallWindowIfNeeded(state); - - switch(state) - { - case STATE_RINGING: - if ( !mIsRetrying ) - { - gIMMgr->addSystemMessage(mSessionID, "ringing", mNotifyArgs); - } - - mState = state; - break; - default: - LLVoiceChannel::setState(state); - } -} - -// -// LLVoiceChannelProximal -// -LLVoiceChannelProximal::LLVoiceChannelProximal() : - LLVoiceChannel(LLUUID::null, LLStringUtil::null) -{ - activate(); -} - -BOOL LLVoiceChannelProximal::isActive() -{ - return callStarted() && LLVoiceClient::getInstance()->inProximalChannel(); -} - -void LLVoiceChannelProximal::activate() -{ - if (callStarted()) return; - - LLVoiceChannel::activate(); - - if (callStarted()) - { - // this implicitly puts you back in the spatial channel - LLVoiceClient::getInstance()->leaveNonSpatialChannel(); - } -} - -void LLVoiceChannelProximal::onChange(EStatusType type, const std::string &channelURI, bool proximal) -{ - if (!proximal) - { - return; - } - - if (type < BEGIN_ERROR_STATUS) - { - handleStatusChange(type); - } - else - { - handleError(type); - } -} - -void LLVoiceChannelProximal::handleStatusChange(EStatusType status) -{ - // status updates - switch(status) - { - case STATUS_LEFT_CHANNEL: - // do not notify user when leaving proximal channel - return; - case STATUS_VOICE_DISABLED: - gIMMgr->addSystemMessage(LLUUID::null, "unavailable", mNotifyArgs); - return; - default: - break; - } - LLVoiceChannel::handleStatusChange(status); -} - - -void LLVoiceChannelProximal::handleError(EStatusType status) -{ - std::string notify; - switch(status) - { - case ERROR_CHANNEL_LOCKED: - case ERROR_CHANNEL_FULL: - notify = "ProximalVoiceChannelFull"; - break; - default: - break; - } - - // notification - if (!notify.empty()) - { - LLNotifications::instance().add(notify, mNotifyArgs); - } - - LLVoiceChannel::handleError(status); -} - -void LLVoiceChannelProximal::deactivate() -{ - if (callStarted()) - { - setState(STATE_HUNG_UP); - } -} - - -// -// LLVoiceChannelP2P -// -LLVoiceChannelP2P::LLVoiceChannelP2P(const LLUUID& session_id, const std::string& session_name, const LLUUID& other_user_id) : - LLVoiceChannelGroup(session_id, session_name), - mOtherUserID(other_user_id), - mReceivedCall(FALSE) -{ - // make sure URI reflects encoded version of other user's agent id - setURI(LLVoiceClient::getInstance()->sipURIFromID(other_user_id)); -} - -void LLVoiceChannelP2P::handleStatusChange(EStatusType type) -{ - // status updates - switch(type) - { - case STATUS_LEFT_CHANNEL: - if (callStarted() && !mIgnoreNextSessionLeave && !sSuspended) - { - if (mState == STATE_RINGING) - { - // other user declined call - LLNotifications::instance().add("P2PCallDeclined", mNotifyArgs); - } - else - { - // other user hung up - LLNotifications::instance().add("VoiceChannelDisconnectedP2P", mNotifyArgs); - } - deactivate(); - } - mIgnoreNextSessionLeave = FALSE; - return; - default: - break; - } - - LLVoiceChannel::handleStatusChange(type); -} - -void LLVoiceChannelP2P::handleError(EStatusType type) -{ - switch(type) - { - case ERROR_NOT_AVAILABLE: - LLNotifications::instance().add("P2PCallNoAnswer", mNotifyArgs); - break; - default: - break; - } - - LLVoiceChannel::handleError(type); -} - -void LLVoiceChannelP2P::activate() -{ - if (callStarted()) return; - - LLVoiceChannel::activate(); - - if (callStarted()) - { - // no session handle yet, we're starting the call - if (mSessionHandle.empty()) - { - mReceivedCall = FALSE; - LLVoiceClient::getInstance()->callUser(mOtherUserID); - } - // otherwise answering the call - else - { - LLVoiceClient::getInstance()->answerInvite(mSessionHandle); - - // using the session handle invalidates it. Clear it out here so we can't reuse it by accident. - mSessionHandle.clear(); - } - - // Add the party to the list of people with which we've recently interacted. - LLRecentPeople::instance().add(mOtherUserID); - } -} - -void LLVoiceChannelP2P::getChannelInfo() -{ - // pretend we have everything we need, since P2P doesn't use channel info - if (sCurrentVoiceChannel == this) - { - setState(STATE_CALL_STARTED); - } -} - -// receiving session from other user who initiated call -void LLVoiceChannelP2P::setSessionHandle(const std::string& handle, const std::string &inURI) -{ - BOOL needs_activate = FALSE; - if (callStarted()) - { - // defer to lower agent id when already active - if (mOtherUserID < gAgent.getID()) - { - // pretend we haven't started the call yet, so we can connect to this session instead - deactivate(); - needs_activate = TRUE; - } - else - { - // we are active and have priority, invite the other user again - // under the assumption they will join this new session - mSessionHandle.clear(); - LLVoiceClient::getInstance()->callUser(mOtherUserID); - return; - } - } - - mSessionHandle = handle; - - // The URI of a p2p session should always be the other end's SIP URI. - if(!inURI.empty()) - { - setURI(inURI); - } - else - { - setURI(LLVoiceClient::getInstance()->sipURIFromID(mOtherUserID)); - } - - mReceivedCall = TRUE; - - if (needs_activate) - { - activate(); - } -} - -void LLVoiceChannelP2P::setState(EState state) -{ - // HACK: Open/close the call window if needed. - toggleCallWindowIfNeeded(state); - - // you only "answer" voice invites in p2p mode - // so provide a special purpose message here - if (mReceivedCall && state == STATE_RINGING) - { - gIMMgr->addSystemMessage(mSessionID, "answering", mNotifyArgs); - mState = state; - return; - } - LLVoiceChannel::setState(state); -} - // // LLFloaterIMPanel @@ -1836,75 +1012,8 @@ void LLFloaterIMPanel::chatFromLogFile(LLLogChat::ELogLineType type, std::string self->mHistoryEditor->blockUndo(); } -void LLFloaterIMPanel::showSessionStartError( - const std::string& error_string) -{ - LLSD args; - args["REASON"] = LLTrans::getString(error_string); - args["RECIPIENT"] = getTitle(); - - LLSD payload; - payload["session_id"] = mSessionUUID; - - LLNotifications::instance().add( - "ChatterBoxSessionStartError", - args, - payload, - onConfirmForceCloseError); -} - -void LLFloaterIMPanel::showSessionEventError( - const std::string& event_string, - const std::string& error_string) -{ - LLSD args; - args["REASON"] = - LLTrans::getString(error_string); - args["EVENT"] = - LLTrans::getString(event_string); - args["RECIPIENT"] = getTitle(); - - LLNotifications::instance().add( - "ChatterBoxSessionEventError", - args); -} - -void LLFloaterIMPanel::showSessionForceClose( - const std::string& reason_string) -{ - LLSD args; - - args["NAME"] = getTitle(); - args["REASON"] = LLTrans::getString(reason_string); - - LLSD payload; - payload["session_id"] = mSessionUUID; - - LLNotifications::instance().add( - "ForceCloseChatterBoxSession", - args, - payload, - LLFloaterIMPanel::onConfirmForceCloseError); - -} - //static void LLFloaterIMPanel::onKickSpeaker(void* user_data) { } - -bool LLFloaterIMPanel::onConfirmForceCloseError(const LLSD& notification, const LLSD& response) -{ - //only 1 option really - LLUUID session_id = notification["payload"]["session_id"]; - - if ( gIMMgr ) - { - LLFloaterIMPanel* floaterp = gIMMgr->findFloaterBySession( - session_id); - - if ( floaterp ) floaterp->closeFloater(FALSE); - } - return false; -} diff --git a/indra/newview/llimpanel.h b/indra/newview/llimpanel.h index 4e306c7fab..39107d9a22 100644 --- a/indra/newview/llimpanel.h +++ b/indra/newview/llimpanel.h @@ -50,133 +50,6 @@ class LLIMSpeakerMgr; class LLPanelActiveSpeakers; class LLPanelChatControlPanel; -class LLVoiceChannel : public LLVoiceClientStatusObserver -{ -public: - typedef enum e_voice_channel_state - { - STATE_NO_CHANNEL_INFO, - STATE_ERROR, - STATE_HUNG_UP, - STATE_READY, - STATE_CALL_STARTED, - STATE_RINGING, - STATE_CONNECTED - } EState; - - LLVoiceChannel(const LLUUID& session_id, const std::string& session_name); - virtual ~LLVoiceChannel(); - - /*virtual*/ void onChange(EStatusType status, const std::string &channelURI, bool proximal); - - virtual void handleStatusChange(EStatusType status); - virtual void handleError(EStatusType status); - virtual void deactivate(); - virtual void activate(); - virtual void setChannelInfo( - const std::string& uri, - const std::string& credentials); - virtual void getChannelInfo(); - virtual BOOL isActive(); - virtual BOOL callStarted(); - const std::string& getSessionName() const { return mSessionName; } - - const LLUUID getSessionID() { return mSessionID; } - EState getState() { return mState; } - - void updateSessionID(const LLUUID& new_session_id); - const LLSD& getNotifyArgs() { return mNotifyArgs; } - - static LLVoiceChannel* getChannelByID(const LLUUID& session_id); - static LLVoiceChannel* getChannelByURI(std::string uri); - static LLVoiceChannel* getCurrentVoiceChannel() { return sCurrentVoiceChannel; } - static void initClass(); - - static void suspend(); - static void resume(); - -protected: - virtual void setState(EState state); - void toggleCallWindowIfNeeded(EState state); - void setURI(std::string uri); - - std::string mURI; - std::string mCredentials; - LLUUID mSessionID; - EState mState; - std::string mSessionName; - LLSD mNotifyArgs; - BOOL mIgnoreNextSessionLeave; - LLHandle<LLPanel> mLoginNotificationHandle; - - typedef std::map<LLUUID, LLVoiceChannel*> voice_channel_map_t; - static voice_channel_map_t sVoiceChannelMap; - - typedef std::map<std::string, LLVoiceChannel*> voice_channel_map_uri_t; - static voice_channel_map_uri_t sVoiceChannelURIMap; - - static LLVoiceChannel* sCurrentVoiceChannel; - static LLVoiceChannel* sSuspendedVoiceChannel; - static BOOL sSuspended; -}; - -class LLVoiceChannelGroup : public LLVoiceChannel -{ -public: - LLVoiceChannelGroup(const LLUUID& session_id, const std::string& session_name); - - /*virtual*/ void handleStatusChange(EStatusType status); - /*virtual*/ void handleError(EStatusType status); - /*virtual*/ void activate(); - /*virtual*/ void deactivate(); - /*vritual*/ void setChannelInfo( - const std::string& uri, - const std::string& credentials); - /*virtual*/ void getChannelInfo(); - -protected: - virtual void setState(EState state); - -private: - U32 mRetries; - BOOL mIsRetrying; -}; - -class LLVoiceChannelProximal : public LLVoiceChannel, public LLSingleton<LLVoiceChannelProximal> -{ -public: - LLVoiceChannelProximal(); - - /*virtual*/ void onChange(EStatusType status, const std::string &channelURI, bool proximal); - /*virtual*/ void handleStatusChange(EStatusType status); - /*virtual*/ void handleError(EStatusType status); - /*virtual*/ BOOL isActive(); - /*virtual*/ void activate(); - /*virtual*/ void deactivate(); - -}; - -class LLVoiceChannelP2P : public LLVoiceChannelGroup -{ -public: - LLVoiceChannelP2P(const LLUUID& session_id, const std::string& session_name, const LLUUID& other_user_id); - - /*virtual*/ void handleStatusChange(EStatusType status); - /*virtual*/ void handleError(EStatusType status); - /*virtual*/ void activate(); - /*virtual*/ void getChannelInfo(); - - void setSessionHandle(const std::string& handle, const std::string &inURI); - -protected: - virtual void setState(EState state); - -private: - std::string mSessionHandle; - LLUUID mOtherUserID; - BOOL mReceivedCall; -}; - class LLFloaterIMPanel : public LLFloater { public: @@ -256,15 +129,6 @@ public: void processIMTyping(const LLIMInfo* im_info, BOOL typing); static void chatFromLogFile(LLLogChat::ELogLineType type, std::string line, void* userdata); - //show error statuses to the user - void showSessionStartError(const std::string& error_string); - void showSessionEventError( - const std::string& event_string, - const std::string& error_string); - void showSessionForceClose(const std::string& reason); - - static bool onConfirmForceCloseError(const LLSD& notification, const LLSD& response); - private: // Called by UI methods. void sendMsg(); diff --git a/indra/newview/llimview.cpp b/indra/newview/llimview.cpp index b631c991ae..8a55ab41b9 100644 --- a/indra/newview/llimview.cpp +++ b/indra/newview/llimview.cpp @@ -70,6 +70,7 @@ #include "llviewerwindow.h" #include "llnotify.h" #include "llviewerregion.h" +#include "llvoicechannel.h" #include "lltrans.h" #include "llrecentpeople.h" @@ -144,7 +145,10 @@ LLIMModel::LLIMSession::LLIMSession(const LLUUID& session_id, const std::string& mInitialTargetIDs(ids), mVoiceChannel(NULL), mSpeakers(NULL), - mSessionInitialized(false) + mSessionInitialized(false), + mCallBackEnabled(true), + mTextIMPossible(true), + mOtherParticipantIsAvatar(true) { if (IM_NOTHING_SPECIAL == type || IM_SESSION_P2P_INVITE == type) { @@ -168,6 +172,13 @@ LLIMModel::LLIMSession::LLIMSession(const LLUUID& session_id, const std::string& //so we're already initialized mSessionInitialized = true; } + + if (IM_NOTHING_SPECIAL == type) + { + mCallBackEnabled = LLVoiceClient::getInstance()->isSessionCallBackPossible(mSessionID); + mTextIMPossible = LLVoiceClient::getInstance()->isSessionTextIMPossible(mSessionID); + mOtherParticipantIsAvatar = LLVoiceClient::getInstance()->isParticipantAvatar(mSessionID); + } } LLIMModel::LLIMSession::~LLIMSession() @@ -272,7 +283,8 @@ void LLIMModel::testMessages() } -bool LLIMModel::newSession(LLUUID session_id, std::string name, EInstantMessage type, LLUUID other_participant_id, const std::vector<LLUUID>& ids) +bool LLIMModel::newSession(const LLUUID& session_id, const std::string& name, const EInstantMessage& type, + const LLUUID& other_participant_id, const std::vector<LLUUID>& ids) { if (is_in_map(sSessionsMap, session_id)) { @@ -289,7 +301,7 @@ bool LLIMModel::newSession(LLUUID session_id, std::string name, EInstantMessage } -bool LLIMModel::clearSession(LLUUID session_id) +bool LLIMModel::clearSession(const LLUUID& session_id) { if (sSessionsMap.find(session_id) == sSessionsMap.end()) return false; delete (sSessionsMap[session_id]); @@ -297,16 +309,13 @@ bool LLIMModel::clearSession(LLUUID session_id) return true; } -//*TODO remake it, instead of returing the list pass it as as parameter (IB) -std::list<LLSD> LLIMModel::getMessages(LLUUID session_id, int start_index) +void LLIMModel::getMessages(const LLUUID& session_id, std::list<LLSD>& messages, int start_index) { - std::list<LLSD> return_list; - LLIMSession* session = findIMSession(session_id); if (!session) { llwarns << "session " << session_id << "does not exist " << llendl; - return return_list; + return; } int i = session->mMsgs.size() - start_index; @@ -317,7 +326,7 @@ std::list<LLSD> LLIMModel::getMessages(LLUUID session_id, int start_index) { LLSD msg; msg = *iter; - return_list.push_back(*iter); + messages.push_back(*iter); i--; } @@ -327,14 +336,9 @@ std::list<LLSD> LLIMModel::getMessages(LLUUID session_id, int start_index) arg["session_id"] = session_id; arg["num_unread"] = 0; mNoUnreadMsgsSignal(arg); - - // TODO: in the future is there a more efficient way to return these - //of course there is - return as parameter (IB) - return return_list; - } -bool LLIMModel::addToHistory(LLUUID session_id, std::string from, LLUUID from_id, std::string utf8_text) { +bool LLIMModel::addToHistory(const LLUUID& session_id, const std::string& from, const LLUUID& from_id, const std::string& utf8_text) { LLIMSession* session = findIMSession(session_id); @@ -383,8 +387,8 @@ bool LLIMModel::logToFile(const LLUUID& session_id, const std::string& from, con return false; } -//*TODO add const qualifier and pass by references (IB) -bool LLIMModel::addMessage(LLUUID session_id, std::string from, LLUUID from_id, std::string utf8_text, bool log2file /* = true */) { +bool LLIMModel::addMessage(const LLUUID& session_id, const std::string& from, const LLUUID& from_id, + const std::string& utf8_text, bool log2file /* = true */) { LLIMSession* session = findIMSession(session_id); if (!session) @@ -506,7 +510,7 @@ void LLIMModel::sendTypingState(LLUUID session_id, LLUUID other_participant_id, gAgent.sendReliableMessage(); } -void LLIMModel::sendLeaveSession(LLUUID session_id, LLUUID other_participant_id) +void LLIMModel::sendLeaveSession(const LLUUID& session_id, const LLUUID& other_participant_id) { if(session_id.notNull()) { @@ -890,20 +894,11 @@ public: { gIMMgr->clearPendingAgentListUpdates(mSessionID); gIMMgr->clearPendingInvitation(mSessionID); - - LLFloaterIMPanel* floaterp = - gIMMgr->findFloaterBySession(mSessionID); - - if ( floaterp ) + if ( 404 == statusNum ) { - if ( 404 == statusNum ) - { - std::string error_string; - error_string = "does not exist"; - - floaterp->showSessionStartError( - error_string); - } + std::string error_string; + error_string = "does not exist"; + gIMMgr->showSessionStartError(error_string, mSessionID); } } } @@ -955,6 +950,106 @@ LLUUID LLIMMgr::computeSessionID( return session_id; } +inline LLFloater* getFloaterBySessionID(const LLUUID session_id) +{ + LLFloater* floater = NULL; + if ( gIMMgr ) + { + floater = dynamic_cast < LLFloater* > + ( gIMMgr->findFloaterBySession(session_id) ); + } + if ( !floater ) + { + floater = dynamic_cast < LLFloater* > + ( LLIMFloater::findInstance(session_id) ); + } + return floater; +} + +void +LLIMMgr::showSessionStartError( + const std::string& error_string, + const LLUUID session_id) +{ + const LLFloater* floater = getFloaterBySessionID (session_id); + if (!floater) return; + + LLSD args; + args["REASON"] = LLTrans::getString(error_string); + args["RECIPIENT"] = floater->getTitle(); + + LLSD payload; + payload["session_id"] = session_id; + + LLNotifications::instance().add( + "ChatterBoxSessionStartError", + args, + payload, + LLIMMgr::onConfirmForceCloseError); +} + +void +LLIMMgr::showSessionEventError( + const std::string& event_string, + const std::string& error_string, + const LLUUID session_id) +{ + const LLFloater* floater = getFloaterBySessionID (session_id); + if (!floater) return; + + LLSD args; + args["REASON"] = + LLTrans::getString(error_string); + args["EVENT"] = + LLTrans::getString(event_string); + args["RECIPIENT"] = floater->getTitle(); + + LLNotifications::instance().add( + "ChatterBoxSessionEventError", + args); +} + +void +LLIMMgr::showSessionForceClose( + const std::string& reason_string, + const LLUUID session_id) +{ + const LLFloater* floater = getFloaterBySessionID (session_id); + if (!floater) return; + + LLSD args; + + args["NAME"] = floater->getTitle(); + args["REASON"] = LLTrans::getString(reason_string); + + LLSD payload; + payload["session_id"] = session_id; + + LLNotifications::instance().add( + "ForceCloseChatterBoxSession", + args, + payload, + LLIMMgr::onConfirmForceCloseError); +} + +//static +bool +LLIMMgr::onConfirmForceCloseError( + const LLSD& notification, + const LLSD& response) +{ + //only 1 option really + LLUUID session_id = notification["payload"]["session_id"]; + + LLFloater* floater = getFloaterBySessionID (session_id); + if ( floater ) + { + floater->closeFloater(FALSE); + } + return false; +} + + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Class LLIncomingCallDialog //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -1114,29 +1209,6 @@ void LLIncomingCallDialog::processCallResponse(S32 response) } } -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLIMViewFriendObserver -// -// Bridge to suport knowing when the inventory has changed. -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -class LLIMViewFriendObserver : public LLFriendObserver -{ -public: - LLIMViewFriendObserver(LLIMMgr* tv) : mTV(tv) {} - virtual ~LLIMViewFriendObserver() {} - virtual void changed(U32 mask) - { - if(mask & (LLFriendObserver::ADD | LLFriendObserver::REMOVE | LLFriendObserver::ONLINE)) - { - mTV->refresh(); - } - } -protected: - LLIMMgr* mTV; -}; - - bool inviteUserResponse(const LLSD& notification, const LLSD& response) { const LLSD& payload = notification["payload"]; @@ -1237,7 +1309,6 @@ bool inviteUserResponse(const LLSD& notification, const LLSD& response) // LLIMMgr::LLIMMgr() : - mFriendObserver(NULL), mIMReceived(FALSE) { static bool registered_dialog = false; @@ -1246,21 +1317,11 @@ LLIMMgr::LLIMMgr() : LLFloaterReg::add("incoming_call", "floater_incoming_call.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLIncomingCallDialog>); registered_dialog = true; } - - mFriendObserver = new LLIMViewFriendObserver(this); - LLAvatarTracker::instance().addObserver(mFriendObserver); mPendingInvitations = LLSD::emptyMap(); mPendingAgentListUpdates = LLSD::emptyMap(); } -LLIMMgr::~LLIMMgr() -{ - LLAvatarTracker::instance().removeObserver(mFriendObserver); - delete mFriendObserver; - // Children all cleaned up by default view destructor. -} - // Add a message to a session. void LLIMMgr::addMessage( const LLUUID& session_id, @@ -1299,9 +1360,15 @@ void LLIMMgr::addMessage( fixed_session_name = session_name; } - if (!LLIMModel::getInstance()->findIMSession(new_session_id)) + bool new_session = !hasSession(session_id); + if (new_session) { - LLIMModel::getInstance()->newSession(session_id, fixed_session_name, dialog, other_participant_id); + // *NOTE dzaporozhan + // Workaround for critical bug EXT-1918 + + // *TODO + // Investigate cases when session_id == NULL and find solution to handle those cases + LLIMModel::getInstance()->newSession(new_session_id, fixed_session_name, dialog, other_participant_id); } floater = findFloaterBySession(new_session_id); @@ -1318,15 +1385,16 @@ void LLIMMgr::addMessage( // create IM window as necessary if(!floater) { - - floater = createFloater( new_session_id, other_participant_id, fixed_session_name, dialog, FALSE); + } + if (new_session) + { // When we get a new IM, and if you are a god, display a bit // of information about the source. This is to help liaisons // when answering questions. @@ -1336,7 +1404,7 @@ void LLIMMgr::addMessage( std::ostringstream bonus_info; bonus_info << LLTrans::getString("***")+ " "+ LLTrans::getString("IMParentEstate") + ":" + " " << parent_estate_id - << ((parent_estate_id == 1) ? "," + LLTrans::getString("IMMainland") : "") + << ((parent_estate_id == 1) ? "," + LLTrans::getString("IMMainland") : "") << ((parent_estate_id == 5) ? "," + LLTrans::getString ("IMTeen") : ""); // once we have web-services (or something) which returns @@ -1439,10 +1507,7 @@ S32 LLIMMgr::getNumberOfUnreadIM() S32 num = 0; for(it = LLIMModel::sSessionsMap.begin(); it != LLIMModel::sSessionsMap.end(); ++it) { - if((*it).first != mBeingRemovedSessionID) - { - num += (*it).second->mNumUnread; - } + num += (*it).second->mNumUnread; } return num; @@ -1458,15 +1523,6 @@ BOOL LLIMMgr::getIMReceived() const return mIMReceived; } -// This method returns TRUE if the local viewer has a session -// currently open keyed to the uuid. -BOOL LLIMMgr::isIMSessionOpen(const LLUUID& uuid) -{ - LLFloaterIMPanel* floater = findFloaterBySession(uuid); - if(floater) return TRUE; - return FALSE; -} - LLUUID LLIMMgr::addP2PSession(const std::string& name, const LLUUID& other_participant_id, const std::string& voice_session_handle, @@ -1560,41 +1616,25 @@ bool LLIMMgr::leaveSession(const LLUUID& session_id) return true; } -// This removes the panel referenced by the uuid, and then restores -// internal consistency. The internal pointer is not deleted? Did you mean -// a pointer to the corresponding LLIMSession? Session data is cleared now. -// Put a copy of UUID to avoid problem when passed reference becames invalid -// if it has been come from the object removed in observer. -void LLIMMgr::removeSession(LLUUID session_id) +// Removes data associated with a particular session specified by session_id +void LLIMMgr::removeSession(const LLUUID& session_id) { - if (mBeingRemovedSessionID == session_id) - { - return; - } + llassert_always(hasSession(session_id)); + //*TODO remove this floater thing when Communicate Floater is being deleted (IB) LLFloaterIMPanel* floater = findFloaterBySession(session_id); if(floater) { mFloaters.erase(floater->getHandle()); LLFloaterChatterBox::getInstance()->removeFloater(floater); - //mTabContainer->removeTabPanel(floater); - - clearPendingInvitation(session_id); - clearPendingAgentListUpdates(session_id); } - // for some purposes storing ID of a sessios that is being removed - mBeingRemovedSessionID = session_id; - notifyObserverSessionRemoved(session_id); + clearPendingInvitation(session_id); + clearPendingAgentListUpdates(session_id); - //if we don't clear session data on removing the session - //we can't use LLBottomTray as observer of session creation/delettion and - //creating chiclets only on session created even, we need to handle chiclets creation - //the same way as LLFloaterIMPanels were managed. LLIMModel::getInstance()->clearSession(session_id); - // now this session is completely removed - mBeingRemovedSessionID.setNull(); + notifyObserverSessionRemoved(session_id); } void LLIMMgr::inviteToSession( @@ -1723,10 +1763,6 @@ void LLIMMgr::onInviteNameLookup(LLSD payload, const LLUUID& id, const std::stri } } -void LLIMMgr::refresh() -{ -} - void LLIMMgr::disconnectAllSessions() { LLFloaterIMPanel* floater = NULL; @@ -2038,6 +2074,12 @@ void LLIMMgr::processIMTypingCore(const LLIMInfo* im_info, BOOL typing) { floater->processIMTyping(im_info, typing); } + + LLIMFloater* im_floater = LLIMFloater::findInstance(session_id); + if ( im_floater ) + { + im_floater->processIMTyping(im_info, typing); + } } class LLViewerChatterBoxSessionStartReply : public LLHTTPNode @@ -2087,19 +2129,21 @@ public: } } + LLIMFloater* im_floater = LLIMFloater::findInstance(session_id); + if ( im_floater ) + { + if ( body.has("session_info") ) + { + im_floater->processSessionUpdate(body["session_info"]); + } + } + gIMMgr->clearPendingAgentListUpdates(session_id); } else { - //throw an error dialog and close the temp session's - //floater - LLFloaterIMPanel* floater = - gIMMgr->findFloaterBySession(temp_session_id); - - if ( floater ) - { - floater->showSessionStartError(body["error"].asString()); - } + //throw an error dialog and close the temp session's floater + gIMMgr->showSessionStartError(body["error"].asString(), temp_session_id); } gIMMgr->clearPendingAgentListUpdates(session_id); @@ -2132,15 +2176,10 @@ public: if ( !success ) { //throw an error dialog - LLFloaterIMPanel* floater = - gIMMgr->findFloaterBySession(session_id); - - if (floater) - { - floater->showSessionEventError( - body["event"].asString(), - body["error"].asString()); - } + gIMMgr->showSessionEventError( + body["event"].asString(), + body["error"].asString(), + session_id); } } }; @@ -2158,13 +2197,7 @@ public: session_id = input["body"]["session_id"].asUUID(); reason = input["body"]["reason"].asString(); - LLFloaterIMPanel* floater = - gIMMgr ->findFloaterBySession(session_id); - - if ( floater ) - { - floater->showSessionForceClose(reason); - } + gIMMgr->showSessionForceClose(reason, session_id); } }; @@ -2202,11 +2235,17 @@ public: const LLSD& context, const LLSD& input) const { - LLFloaterIMPanel* floaterp = gIMMgr->findFloaterBySession(input["body"]["session_id"].asUUID()); + LLUUID session_id = input["body"]["session_id"].asUUID(); + LLFloaterIMPanel* floaterp = gIMMgr->findFloaterBySession(session_id); if (floaterp) { floaterp->processSessionUpdate(input["body"]["info"]); } + LLIMFloater* im_floater = LLIMFloater::findInstance(session_id); + if ( im_floater ) + { + im_floater->processSessionUpdate(input["body"]["info"]); + } } }; diff --git a/indra/newview/llimview.h b/indra/newview/llimview.h index 68beb29034..ae8fd355ea 100644 --- a/indra/newview/llimview.h +++ b/indra/newview/llimview.h @@ -34,7 +34,7 @@ #define LL_LLIMVIEW_H #include "lldarray.h" -#include "llfloateractivespeakers.h" //for LLIMSpeakerMgr +#include "llspeakers.h" //for LLIMSpeakerMgr #include "llimpanel.h" //for voice channels #include "llmodaldialog.h" #include "llinstantmessage.h" @@ -70,6 +70,13 @@ public: LLIMSpeakerMgr* mSpeakers; bool mSessionInitialized; + + //true if calling back the session URI after the session has closed is possible. + //Currently this will be false only for PSTN P2P calls. + bool mCallBackEnabled; + + bool mTextIMPossible; + bool mOtherParticipantIsAvatar; }; @@ -104,18 +111,35 @@ public: boost::signals2::connection addNewMsgCallback( session_callback_t cb ) { return mNewMsgSignal.connect(cb); } boost::signals2::connection addNoUnreadMsgsCallback( session_callback_t cb ) { return mNoUnreadMsgsSignal.connect(cb); } - bool newSession(LLUUID session_id, std::string name, EInstantMessage type, LLUUID other_participant_id, + /** + * Create new session object in a model + */ + bool newSession(const LLUUID& session_id, const std::string& name, const EInstantMessage& type, const LLUUID& other_participant_id, const std::vector<LLUUID>& ids = std::vector<LLUUID>()); - bool clearSession(LLUUID session_id); - std::list<LLSD> getMessages(LLUUID session_id, int start_index = 0); - bool addMessage(LLUUID session_id, std::string from, LLUUID other_participant_id, std::string utf8_text, bool log2file = true); - bool addToHistory(LLUUID session_id, std::string from, LLUUID from_id, std::string utf8_text); + /** + * Remove all session data associated with a session specified by session_id + */ + bool clearSession(const LLUUID& session_id); - bool logToFile(const LLUUID& session_id, const std::string& from, const std::string& utf8_text); + /** + * Populate supplied std::list with messages starting from index specified by start_index + */ + void getMessages(const LLUUID& session_id, std::list<LLSD>& messages, int start_index = 0); - //used to get the name of the session, for use as the title - //currently just the other avatar name + /** + * Add a message to an IM Model - the message is saved in a message store associated with a session specified by session_id + * and also saved into a file if log2file is specified. + * It sends new message signal for each added message. + */ + bool addMessage(const LLUUID& session_id, const std::string& from, const LLUUID& other_participant_id, const std::string& utf8_text, bool log2file = true); + + /** + * Get a session's name. + * For a P2P chat - it's an avatar's name, + * For a group chat - it's a group's name + * For an ad-hoc chat - is received from the server and is in a from of "<Avatar's name> conference" + */ const std::string& getName(const LLUUID& session_id) const; /** @@ -150,7 +174,7 @@ public: */ LLIMSpeakerMgr* getSpeakerManager(const LLUUID& session_id) const; - static void sendLeaveSession(LLUUID session_id, LLUUID other_participant_id); + static void sendLeaveSession(const LLUUID& session_id, const LLUUID& other_participant_id); static bool sendStartSession(const LLUUID& temp_session_id, const LLUUID& other_participant_id, const std::vector<LLUUID>& ids, EInstantMessage dialog); static void sendTypingState(LLUUID session_id, LLUUID other_participant_id, BOOL typing); @@ -158,6 +182,19 @@ public: const LLUUID& other_participant_id, EInstantMessage dialog); void testMessages(); + +private: + + /** + * Add message to a list of message associated with session specified by session_id + */ + bool addToHistory(const LLUUID& session_id, const std::string& from, const LLUUID& from_id, const std::string& utf8_text); + + /** + * Save an IM message into a file + */ + //*TODO should also save uuid of a sender + bool logToFile(const LLUUID& session_id, const std::string& from, const std::string& utf8_text); }; class LLIMSessionObserver @@ -183,7 +220,7 @@ public: }; LLIMMgr(); - virtual ~LLIMMgr(); + virtual ~LLIMMgr() {}; // Add a message to a session. The session can keyed to sesion id // or agent id. @@ -200,11 +237,6 @@ public: void addSystemMessage(const LLUUID& session_id, const std::string& message_name, const LLSD& args); - // This method returns TRUE if the local viewer has a session - // currently open keyed to the uuid. The uuid can be keyed by - // either session id or agent id. - BOOL isIMSessionOpen(const LLUUID& uuid); - // This adds a session to the talk view. The name is the local // name of the session, dialog specifies the type of // session. Since sessions can be keyed off of first recipient or @@ -250,9 +282,6 @@ public: void processIMTypingStart(const LLIMInfo* im_info); void processIMTypingStop(const LLIMInfo* im_info); - // Rebuild stuff - void refresh(); - void notifyNewIM(); void clearNewIMNotification(); @@ -268,10 +297,6 @@ public: // good connection. void disconnectAllSessions(); - // This is a helper function to determine what kind of im session - // should be used for the given agent. - static EInstantMessage defaultIMTypeForAgent(const LLUUID& agent_id); - BOOL hasSession(const LLUUID& session_id); // This method returns the im panel corresponding to the uuid @@ -290,11 +315,18 @@ public: void clearPendingAgentListUpdates(const LLUUID& session_id); //HACK: need a better way of enumerating existing session, or listening to session create/destroy events + //@deprecated, is used only by LLToolBox, which is not used anywhere, right? (IB) const std::set<LLHandle<LLFloater> >& getIMFloaterHandles() { return mFloaters; } void addSessionObserver(LLIMSessionObserver *); void removeSessionObserver(LLIMSessionObserver *); + //show error statuses to the user + void showSessionStartError(const std::string& error_string, const LLUUID session_id); + void showSessionEventError(const std::string& event_string, const std::string& error_string, const LLUUID session_id); + void showSessionForceClose(const std::string& reason, const LLUUID session_id); + static bool onConfirmForceCloseError(const LLSD& notification, const LLSD& response); + /** * Start call in a session * @return false if voice channel doesn't exist @@ -308,10 +340,11 @@ public: bool endCall(const LLUUID& session_id); private: - // This removes the panel referenced by the uuid, and then - // restores internal consistency. The internal pointer is not - // deleted. - void removeSession(LLUUID session_id); + + /** + * Remove data associated with a particular session specified by session_id + */ + void removeSession(const LLUUID& session_id); // create a panel and update internal representation for // consistency. Returns the pointer, caller (the class instance @@ -340,8 +373,9 @@ private: void notifyObserverSessionIDUpdated(const LLUUID& old_session_id, const LLUUID& new_session_id); private: + + //*TODO should be deleted when Communicate Floater is being deleted std::set<LLHandle<LLFloater> > mFloaters; - LLFriendObserver* mFriendObserver; typedef std::list <LLIMSessionObserver *> session_observers_list_t; session_observers_list_t mSessionObservers; @@ -351,9 +385,6 @@ private: LLSD mPendingInvitations; LLSD mPendingAgentListUpdates; - // ID of a session that is being removed: observers are already told - // that this session is being removed, but it is still present in the sessions' map - LLUUID mBeingRemovedSessionID; }; class LLIncomingCallDialog : public LLModalDialog diff --git a/indra/newview/lllandmarkactions.cpp b/indra/newview/lllandmarkactions.cpp index b36b7cf50e..091346d3b4 100644 --- a/indra/newview/lllandmarkactions.cpp +++ b/indra/newview/lllandmarkactions.cpp @@ -348,7 +348,7 @@ LLLandmark* LLLandmarkActions::getLandmark(const LLUUID& landmarkInventoryItemID { LLViewerInventoryItem* item = gInventory.getItem(landmarkInventoryItemID); if (NULL == item) - return false; + return NULL; const LLUUID& asset_id = item->getAssetUUID(); return gLandmarkList.getAsset(asset_id, NULL); diff --git a/indra/newview/lllandmarkactions.h b/indra/newview/lllandmarkactions.h index d651259790..32f05e702b 100644 --- a/indra/newview/lllandmarkactions.h +++ b/indra/newview/lllandmarkactions.h @@ -108,7 +108,7 @@ public: /** * @brief Retrieve a landmark from gLandmarkList by inventory item's id * - * @return pointer to loaded landmark from gLandmarkList or NULL if landmark does not exist. + * @return pointer to loaded landmark from gLandmarkList or NULL if landmark does not exist or wasn't loaded. */ static LLLandmark* getLandmark(const LLUUID& landmarkInventoryItemID); diff --git a/indra/newview/llnearbychat.cpp b/indra/newview/llnearbychat.cpp index 148f72703c..12638ab855 100644 --- a/indra/newview/llnearbychat.cpp +++ b/indra/newview/llnearbychat.cpp @@ -170,22 +170,6 @@ LLColor4 nearbychat_get_text_color(const LLChat& chat) return text_color; } -std::string formatCurrentTime() -{ - time_t utc_time; - utc_time = time_corrected(); - std::string timeStr ="["+ LLTrans::getString("TimeHour")+"]:[" - +LLTrans::getString("TimeMin")+"] "; - - LLSD substitution; - - substitution["datetime"] = (S32) utc_time; - LLStringUtil::format (timeStr, substitution); - - return timeStr; -} - - void LLNearbyChat::add_timestamped_line(const LLChat& chat, const LLColor4& color) { S32 font_size = gSavedSettings.getS32("ChatFontSize"); @@ -210,16 +194,14 @@ void LLNearbyChat::add_timestamped_line(const LLChat& chat, const LLColor4& colo style_params.font(fontp); LLUUID uuid = chat.mFromID; std::string from = chat.mFromName; - std::string time = formatCurrentTime(); std::string message = chat.mText; - mChatHistory->appendWidgetMessage(uuid, from, time, message, style_params); + mChatHistory->appendWidgetMessage(chat, style_params); } void LLNearbyChat::addMessage(const LLChat& chat) { LLColor4 color = nearbychat_get_text_color(chat); - if (chat.mChatType == CHAT_TYPE_DEBUG_MSG) { if(gSavedSettings.getBOOL("ShowScriptErrors") == FALSE) diff --git a/indra/newview/llnearbychatbar.cpp b/indra/newview/llnearbychatbar.cpp index 5fa97926a5..217007fb15 100644 --- a/indra/newview/llnearbychatbar.cpp +++ b/indra/newview/llnearbychatbar.cpp @@ -45,6 +45,7 @@ #include "llviewerstats.h" #include "llcommandhandler.h" #include "llviewercontrol.h" +#include "llnavigationbar.h" S32 LLNearbyChatBar::sLastSpecialChatChannel = 0; @@ -177,6 +178,31 @@ void LLGestureComboBox::draw() LLComboBox::draw(); } +//virtual +void LLGestureComboBox::showList() +{ + LLComboBox::showList(); + + // Calculating amount of space between the navigation bar and gestures combo + LLNavigationBar* nb = LLNavigationBar::getInstance(); + S32 x, nb_bottom; + nb->localPointToScreen(0, 0, &x, &nb_bottom); + + S32 list_bottom; + mList->localPointToScreen(0, 0, &x, &list_bottom); + + S32 max_height = nb_bottom - list_bottom; + + LLRect rect = mList->getRect(); + // List overlapped navigation bar, downsize it + if (rect.getHeight() > max_height) + { + rect.setOriginAndSize(rect.mLeft, rect.mBottom, rect.getWidth(), max_height); + mList->setRect(rect); + mList->reshape(rect.getWidth(), rect.getHeight()); + } +} + LLNearbyChatBar::LLNearbyChatBar() : LLPanel() , mChatBox(NULL) diff --git a/indra/newview/llnearbychatbar.h b/indra/newview/llnearbychatbar.h index b902ff86cc..06204e6367 100644 --- a/indra/newview/llnearbychatbar.h +++ b/indra/newview/llnearbychatbar.h @@ -40,7 +40,7 @@ #include "llchiclet.h" #include "llvoiceclient.h" #include "lloutputmonitorctrl.h" -#include "llfloateractivespeakers.h" +#include "llspeakers.h" class LLGestureComboBox : public LLComboBox @@ -62,6 +62,9 @@ public: virtual void changed() { refreshGestures(); } protected: + + virtual void showList(); + LLFrameTimer mGestureLabelTimer; std::vector<LLMultiGesture*> mGestures; std::string mLabel; diff --git a/indra/newview/llnotificationtiphandler.cpp b/indra/newview/llnotificationtiphandler.cpp index 7239f49b7f..543198c1d2 100644 --- a/indra/newview/llnotificationtiphandler.cpp +++ b/indra/newview/llnotificationtiphandler.cpp @@ -86,6 +86,20 @@ bool LLTipHandler::processNotification(const LLSD& notify) if(notify["sigtype"].asString() == "add" || notify["sigtype"].asString() == "change") { + // archive message in nearby chat + LLNearbyChat* nearby_chat = LLFloaterReg::getTypedInstance<LLNearbyChat>("nearby_chat", LLSD()); + if(nearby_chat) + { + LLChat chat_msg(notification->getMessage()); + nearby_chat->addMessage(chat_msg); + + // don't show toast if Nearby Chat is opened + if (nearby_chat->getVisible()) + { + return true; + } + } + LLToastNotifyPanel* notify_box = new LLToastNotifyPanel(notification); LLToast::Params p; @@ -99,14 +113,6 @@ bool LLTipHandler::processNotification(const LLSD& notify) LLScreenChannel* channel = dynamic_cast<LLScreenChannel*>(mChannel); if(channel) channel->addToast(p); - - // archive message in nearby chat - LLNearbyChat* nearby_chat = LLFloaterReg::getTypedInstance<LLNearbyChat>("nearby_chat", LLSD()); - if(nearby_chat) - { - LLChat chat_msg(notification->getMessage()); - nearby_chat->addMessage(chat_msg); - } } else if (notify["sigtype"].asString() == "delete") { diff --git a/indra/newview/llpanelimcontrolpanel.cpp b/indra/newview/llpanelimcontrolpanel.cpp index 6eed956eb8..f9eeaf1e9e 100644 --- a/indra/newview/llpanelimcontrolpanel.cpp +++ b/indra/newview/llpanelimcontrolpanel.cpp @@ -34,6 +34,7 @@ #include "llpanelimcontrolpanel.h" +#include "llagent.h" #include "llavataractions.h" #include "llavatariconctrl.h" #include "llbutton.h" @@ -41,6 +42,53 @@ #include "llavatarlist.h" #include "llparticipantlist.h" #include "llimview.h" +#include "llvoicechannel.h" + +void LLPanelChatControlPanel::onCallButtonClicked() +{ + gIMMgr->startCall(mSessionId); +} + +void LLPanelChatControlPanel::onEndCallButtonClicked() +{ + gIMMgr->endCall(mSessionId); +} + +BOOL LLPanelChatControlPanel::postBuild() +{ + childSetAction("call_btn", boost::bind(&LLPanelChatControlPanel::onCallButtonClicked, this)); + childSetAction("end_call_btn", boost::bind(&LLPanelChatControlPanel::onEndCallButtonClicked, this)); + + return TRUE; +} + +void LLPanelChatControlPanel::draw() +{ + // hide/show start call and end call buttons + bool voice_enabled = LLVoiceClient::voiceEnabled(); + + LLIMModel::LLIMSession* session = LLIMModel::getInstance()->findIMSession(mSessionId); + if (!session) return; + + LLVoiceChannel* voice_channel = session->mVoiceChannel; + if (voice_channel && voice_enabled) + { + childSetVisible("end_call_btn", voice_channel->getState() >= LLVoiceChannel::STATE_CALL_STARTED); + childSetVisible("call_btn", voice_channel->getState() < LLVoiceChannel::STATE_CALL_STARTED); + } + + bool session_initialized = session->mSessionInitialized; + bool callback_enabled = session->mCallBackEnabled; + LLViewerRegion* region = gAgent.getRegion(); + + BOOL enable_connect = (region && region->getCapability("ChatSessionRequest") != "") + && session_initialized + && voice_enabled + && callback_enabled; + childSetEnabled("call_btn", enable_connect); + + LLPanel::draw(); +} LLPanelIMControlPanel::LLPanelIMControlPanel() { @@ -54,11 +102,11 @@ BOOL LLPanelIMControlPanel::postBuild() { childSetAction("view_profile_btn", boost::bind(&LLPanelIMControlPanel::onViewProfileButtonClicked, this)); childSetAction("add_friend_btn", boost::bind(&LLPanelIMControlPanel::onAddFriendButtonClicked, this)); - childSetAction("call_btn", boost::bind(&LLPanelIMControlPanel::onCallButtonClicked, this)); + childSetAction("share_btn", boost::bind(&LLPanelIMControlPanel::onShareButtonClicked, this)); childSetEnabled("add_friend_btn", !LLAvatarActions::isFriend(getChild<LLAvatarIconCtrl>("avatar_icon")->getAvatarId())); - - return TRUE; + + return LLPanelChatControlPanel::postBuild(); } void LLPanelIMControlPanel::onViewProfileButtonClicked() @@ -73,22 +121,29 @@ void LLPanelIMControlPanel::onAddFriendButtonClicked() LLAvatarActions::requestFriendshipDialog(avatar_icon->getAvatarId(), full_name); } -void LLPanelIMControlPanel::onCallButtonClicked() -{ - // *TODO: Implement -} - void LLPanelIMControlPanel::onShareButtonClicked() { // *TODO: Implement } -void LLPanelIMControlPanel::setID(const LLUUID& avatar_id) +void LLPanelIMControlPanel::setSessionId(const LLUUID& session_id) { + LLPanelChatControlPanel::setSessionId(session_id); + + LLIMModel& im_model = LLIMModel::instance(); + + LLUUID avatar_id = im_model.getOtherParticipantID(session_id); + // Disable "Add friend" button for friends. childSetEnabled("add_friend_btn", !LLAvatarActions::isFriend(avatar_id)); getChild<LLAvatarIconCtrl>("avatar_icon")->setValue(avatar_id); + + // Disable profile button if participant is not realy SL avatar + LLIMModel::LLIMSession* im_session = + im_model.findIMSession(session_id); + if( im_session && !im_session->mOtherParticipantIsAvatar ) + childSetEnabled("view_profile_btn", FALSE); } @@ -100,12 +155,11 @@ LLPanelGroupControlPanel::LLPanelGroupControlPanel(const LLUUID& session_id) BOOL LLPanelGroupControlPanel::postBuild() { childSetAction("group_info_btn", boost::bind(&LLPanelGroupControlPanel::onGroupInfoButtonClicked, this)); - childSetAction("call_btn", boost::bind(&LLPanelGroupControlPanel::onCallButtonClicked, this)); mAvatarList = getChild<LLAvatarList>("speakers_list"); mParticipantList = new LLParticipantList(mSpeakerManager, mAvatarList); - return TRUE; + return LLPanelChatControlPanel::postBuild(); } LLPanelGroupControlPanel::~LLPanelGroupControlPanel() @@ -127,13 +181,23 @@ void LLPanelGroupControlPanel::onGroupInfoButtonClicked() } -void LLPanelGroupControlPanel::onCallButtonClicked() +void LLPanelGroupControlPanel::setSessionId(const LLUUID& session_id) { - // *TODO: Implement + LLPanelChatControlPanel::setSessionId(session_id); + + mGroupID = LLIMModel::getInstance()->getOtherParticipantID(session_id); } -void LLPanelGroupControlPanel::setID(const LLUUID& id) +LLPanelAdHocControlPanel::LLPanelAdHocControlPanel(const LLUUID& session_id):LLPanelGroupControlPanel(session_id) +{ +} + +BOOL LLPanelAdHocControlPanel::postBuild() { - mGroupID = id; + mAvatarList = getChild<LLAvatarList>("speakers_list"); + mParticipantList = new LLParticipantList(mSpeakerManager, mAvatarList); + + return LLPanelChatControlPanel::postBuild(); } + diff --git a/indra/newview/llpanelimcontrolpanel.h b/indra/newview/llpanelimcontrolpanel.h index 138b1630c4..220b7b14ba 100644 --- a/indra/newview/llpanelimcontrolpanel.h +++ b/indra/newview/llpanelimcontrolpanel.h @@ -45,8 +45,16 @@ public: LLPanelChatControlPanel() {}; ~LLPanelChatControlPanel() {}; - // sets the group or avatar UUID - virtual void setID(const LLUUID& avatar_id)= 0; + virtual BOOL postBuild(); + virtual void draw(); + + void onCallButtonClicked(); + void onEndCallButtonClicked(); + + virtual void setSessionId(const LLUUID& session_id) { mSessionId = session_id; } + +private: + LLUUID mSessionId; }; @@ -58,13 +66,14 @@ public: BOOL postBuild(); - void setID(const LLUUID& avatar_id); + void setSessionId(const LLUUID& session_id); private: void onViewProfileButtonClicked(); void onAddFriendButtonClicked(); - void onCallButtonClicked(); void onShareButtonClicked(); + + LLUUID mAvatarID; }; @@ -76,19 +85,26 @@ public: BOOL postBuild(); - void setID(const LLUUID& id); + void setSessionId(const LLUUID& session_id); /*virtual*/ void draw(); -private: - void onGroupInfoButtonClicked(); - void onCallButtonClicked(); - +protected: LLUUID mGroupID; LLSpeakerMgr* mSpeakerManager; LLAvatarList* mAvatarList; LLParticipantList* mParticipantList; + +private: + void onGroupInfoButtonClicked(); }; +class LLPanelAdHocControlPanel : public LLPanelGroupControlPanel +{ +public: + LLPanelAdHocControlPanel(const LLUUID& session_id); + + BOOL postBuild(); +}; #endif // LL_LLPANELIMCONTROLPANEL_H diff --git a/indra/newview/llpanellandmarks.cpp b/indra/newview/llpanellandmarks.cpp index 83fb147a49..3d0db71045 100644 --- a/indra/newview/llpanellandmarks.cpp +++ b/indra/newview/llpanellandmarks.cpp @@ -38,10 +38,12 @@ #include "llsdutil.h" #include "llsdutil_math.h" +#include "llaccordionctrl.h" #include "llaccordionctrltab.h" #include "llagent.h" #include "llagentpicksinfo.h" #include "llagentui.h" +#include "llcallbacklist.h" #include "lldndbutton.h" #include "llfloaterworldmap.h" #include "llfolderviewitem.h" @@ -56,7 +58,7 @@ //static LLRegisterPanelClassWrapper<LLLandmarksPanel> t_landmarks("panel_landmarks"); static const std::string OPTIONS_BUTTON_NAME = "options_gear_btn"; -static const std::string ADD_LANDMARK_BUTTON_NAME = "add_landmark_btn"; +static const std::string ADD_BUTTON_NAME = "add_btn"; static const std::string ADD_FOLDER_BUTTON_NAME = "add_folder_btn"; static const std::string TRASH_BUTTON_NAME = "trash_btn"; @@ -75,6 +77,7 @@ LLLandmarksPanel::LLLandmarksPanel() , mListCommands(NULL) , mGearFolderMenu(NULL) , mGearLandmarkMenu(NULL) + , mDirtyFilter(false) { LLUICtrlFactory::getInstance()->buildPanel(this, "panel_landmarks.xml"); } @@ -98,16 +101,37 @@ BOOL LLLandmarksPanel::postBuild() initMyInventroyPanel(); initLibraryInventroyPanel(); + gIdleCallbacks.addFunction(LLLandmarksPanel::doIdle, this); return TRUE; } // virtual void LLLandmarksPanel::onSearchEdit(const std::string& string) { + static std::string prev_string(""); + + if (prev_string == string) return; + + // show all folders in Landmarks Accordion for empty filter + mLandmarksInventoryPanel->setShowFolderState(string.empty() ? + LLInventoryFilter::SHOW_ALL_FOLDERS : + LLInventoryFilter::SHOW_NON_EMPTY_FOLDERS + ); + filter_list(mFavoritesInventoryPanel, string); filter_list(mLandmarksInventoryPanel, string); filter_list(mMyInventoryPanel, string); filter_list(mLibraryInventoryPanel, string); + + prev_string = string; + mDirtyFilter = true; + + // give FolderView a chance to be refreshed. So, made all accordions visible + for (accordion_tabs_t::const_iterator iter = mAccordionTabs.begin(); iter != mAccordionTabs.end(); ++iter) + { + LLAccordionCtrlTab* tab = *iter; + tab->setVisible(true); + } } // virtual @@ -300,6 +324,8 @@ void LLLandmarksPanel::processParcelInfo(const LLParcelData& parcel_data) panel_pick, panel_places,params)); panel_pick->setSaveCallback(boost::bind(&LLLandmarksPanel::onPickPanelExit,this, panel_pick, panel_places,params)); + panel_pick->setCancelCallback(boost::bind(&LLLandmarksPanel::onPickPanelExit,this, + panel_pick, panel_places,params)); } } } @@ -389,6 +415,7 @@ void LLLandmarksPanel::initLandmarksPanel(LLInventorySubTreePanel* inventory_lis void LLLandmarksPanel::initAccordion(const std::string& accordion_tab_name, LLInventorySubTreePanel* inventory_list) { LLAccordionCtrlTab* accordion_tab = getChild<LLAccordionCtrlTab>(accordion_tab_name); + mAccordionTabs.push_back(accordion_tab); accordion_tab->setDropDownStateChangedCallback( boost::bind(&LLLandmarksPanel::onAccordionExpandedCollapsed, this, _2, inventory_list)); } @@ -433,11 +460,11 @@ void LLLandmarksPanel::initListCommandsHandlers() mListCommands = getChild<LLPanel>("bottom_panel"); mListCommands->childSetAction(OPTIONS_BUTTON_NAME, boost::bind(&LLLandmarksPanel::onActionsButtonClick, this)); - mListCommands->childSetAction(ADD_LANDMARK_BUTTON_NAME, boost::bind(&LLLandmarksPanel::onAddLandmarkButtonClick, this)); - mListCommands->childSetAction(ADD_FOLDER_BUTTON_NAME, boost::bind(&LLLandmarksPanel::onAddFolderButtonClick, this)); mListCommands->childSetAction(TRASH_BUTTON_NAME, boost::bind(&LLLandmarksPanel::onTrashButtonClick, this)); + mListCommands->getChild<LLButton>(ADD_BUTTON_NAME)->setHeldDownCallback(boost::bind(&LLLandmarksPanel::onAddButtonHeldDown, this)); + static const LLSD add_landmark_command("add_landmark"); + mListCommands->childSetAction(ADD_BUTTON_NAME, boost::bind(&LLLandmarksPanel::onAddAction, this, add_landmark_command)); - LLDragAndDropButton* trash_btn = mListCommands->getChild<LLDragAndDropButton>(TRASH_BUTTON_NAME); trash_btn->setDragAndDropHandler(boost::bind(&LLLandmarksPanel::handleDragAndDropToTrash, this , _4 // BOOL drop @@ -453,6 +480,7 @@ void LLLandmarksPanel::initListCommandsHandlers() mEnableCallbackRegistrar.add("Places.LandmarksGear.Enable", boost::bind(&LLLandmarksPanel::isActionEnabled, this, _2)); mGearLandmarkMenu = LLUICtrlFactory::getInstance()->createFromFile<LLMenuGL>("menu_places_gear_landmark.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); mGearFolderMenu = LLUICtrlFactory::getInstance()->createFromFile<LLMenuGL>("menu_places_gear_folder.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); + mMenuAdd = LLUICtrlFactory::getInstance()->createFromFile<LLMenuGL>("menu_place_add_button.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); } @@ -488,52 +516,26 @@ void LLLandmarksPanel::onActionsButtonClick() mGearFolderMenu->getChild<LLMenuItemCallGL>("collapse")->setVisible(cur_item->isOpen()); menu = mGearFolderMenu; } - if(menu) - { - menu->buildDrawLabels(); - menu->updateParent(LLMenuGL::sMenuContainer); - LLView* actions_btn = getChild<LLView>(OPTIONS_BUTTON_NAME); - S32 menu_x, menu_y; - actions_btn->localPointToOtherView(0,actions_btn->getRect().getHeight(),&menu_x,&menu_y, this); - menu_y += menu->getRect().getHeight(); - LLMenuGL::showPopup(this, menu, menu_x,menu_y); - } + showActionMenu(menu,OPTIONS_BUTTON_NAME); } -void LLLandmarksPanel::onAddLandmarkButtonClick() const +void LLLandmarksPanel::onAddButtonHeldDown() { - if(LLLandmarkActions::landmarkAlreadyExists()) - { - std::string location; - LLAgentUI::buildLocationString(location, LLAgentUI::LOCATION_FORMAT_FULL); - llwarns<<" Landmark already exists at location: "<< location<<llendl; - return; - } - LLSideTray::getInstance()->showPanel("panel_places", LLSD().insert("type", "create_landmark")); + showActionMenu(mMenuAdd,ADD_BUTTON_NAME); } -void LLLandmarksPanel::onAddFolderButtonClick() const +void LLLandmarksPanel::showActionMenu(LLMenuGL* menu, std::string spawning_view_name) { - LLFolderViewItem* item = getCurSelectedItem(); - if(item && mCurrentSelectedList == mLandmarksInventoryPanel) + if (menu) { - LLFolderViewEventListener* folder_bridge = NULL; - if(item-> getListener()->getInventoryType() == LLInventoryType::IT_LANDMARK) - { - // for a landmark get parent folder bridge - folder_bridge = item->getParentFolder()->getListener(); - } - else if (item-> getListener()->getInventoryType() == LLInventoryType::IT_CATEGORY) - { - // for a folder get its own bridge - folder_bridge = item->getListener(); - } - - menu_create_inventory_item(mCurrentSelectedList->getRootFolder() - , dynamic_cast<LLFolderBridge*>(folder_bridge) - , LLSD("category") - , gInventory.findCategoryUUIDForType(LLAssetType::AT_LANDMARK) - ); + menu->buildDrawLabels(); + menu->updateParent(LLMenuGL::sMenuContainer); + LLView* spawning_view = getChild<LLView> (spawning_view_name); + S32 menu_x, menu_y; + //show menu in co-ordinates of panel + spawning_view->localPointToOtherView(0, spawning_view->getRect().getHeight(), &menu_x, &menu_y, this); + menu_y += menu->getRect().getHeight(); + LLMenuGL::showPopup(this, menu, menu_x, menu_y); } } @@ -547,11 +549,39 @@ void LLLandmarksPanel::onAddAction(const LLSD& userdata) const std::string command_name = userdata.asString(); if("add_landmark" == command_name) { - onAddLandmarkButtonClick(); + if(LLLandmarkActions::landmarkAlreadyExists()) + { + std::string location; + LLAgentUI::buildLocationString(location, LLAgentUI::LOCATION_FORMAT_FULL); + llwarns<<" Landmark already exists at location: "<< location<<llendl; + return; + } + LLSideTray::getInstance()->showPanel("panel_places", LLSD().insert("type", "create_landmark")); } else if ("category" == command_name) { - onAddFolderButtonClick(); + LLFolderViewItem* item = getCurSelectedItem(); + if (item && mCurrentSelectedList == mLandmarksInventoryPanel) + { + LLFolderViewEventListener* folder_bridge = NULL; + if (item-> getListener()->getInventoryType() + == LLInventoryType::IT_LANDMARK) + { + // for a landmark get parent folder bridge + folder_bridge = item->getParentFolder()->getListener(); + } + else if (item-> getListener()->getInventoryType() + == LLInventoryType::IT_CATEGORY) + { + // for a folder get its own bridge + folder_bridge = item->getListener(); + } + + menu_create_inventory_item(mCurrentSelectedList->getRootFolder(), + dynamic_cast<LLFolderBridge*> (folder_bridge), LLSD( + "category"), gInventory.findCategoryUUIDForType( + LLAssetType::AT_LANDMARK)); + } } } @@ -854,6 +884,43 @@ bool LLLandmarksPanel::handleDragAndDropToTrash(BOOL drop, EDragAndDropType carg } +void LLLandmarksPanel::doIdle(void* landmarks_panel) +{ + LLLandmarksPanel* panel = (LLLandmarksPanel* ) landmarks_panel; + + if (panel->mDirtyFilter) + { + panel->updateFilteredAccordions(); + } + +} + +void LLLandmarksPanel::updateFilteredAccordions() +{ + LLInventoryPanel* inventory_list = NULL; + LLAccordionCtrlTab* accordion_tab = NULL; + for (accordion_tabs_t::const_iterator iter = mAccordionTabs.begin(); iter != mAccordionTabs.end(); ++iter) + { + accordion_tab = *iter; + inventory_list = dynamic_cast<LLInventorySubTreePanel*> (accordion_tab->getAccordionView()); + if (NULL == inventory_list) continue; + LLFolderView* fv = inventory_list->getRootFolder(); + + bool has_visible_children = fv->hasVisibleChildren(); + + accordion_tab->setVisible(has_visible_children); + } + + // we have to arrange accordion tabs for cases when filter string is less restrictive but + // all items are still filtered. + static LLAccordionCtrl* accordion = getChild<LLAccordionCtrl>("landmarks_accordion"); + accordion->arrange(); + + // now filter state is applied to accordion tabs + mDirtyFilter = false; +} + + ////////////////////////////////////////////////////////////////////////// // HELPER FUNCTIONS ////////////////////////////////////////////////////////////////////////// diff --git a/indra/newview/llpanellandmarks.h b/indra/newview/llpanellandmarks.h index 47c9f7647c..0e7abb4865 100644 --- a/indra/newview/llpanellandmarks.h +++ b/indra/newview/llpanellandmarks.h @@ -41,6 +41,7 @@ #include "llpanelpick.h" #include "llremoteparcelrequest.h" +class LLAccordionCtrlTab; class LLFolderViewItem; class LLMenuGL; class LLInventoryPanel; @@ -90,8 +91,8 @@ private: void initListCommandsHandlers(); void updateListCommands(); void onActionsButtonClick(); - void onAddLandmarkButtonClick() const; - void onAddFolderButtonClick() const; + void showActionMenu(LLMenuGL* menu, std::string spawning_view_name); + void onAddButtonHeldDown(); void onTrashButtonClick() const; void onAddAction(const LLSD& command_name) const; void onClipboardAction(const LLSD& command_name) const; @@ -114,6 +115,18 @@ private: */ bool handleDragAndDropToTrash(BOOL drop, EDragAndDropType cargo_type, EAcceptance* accept); + /** + * Static callback for gIdleCallbacks to perform actions out of drawing + */ + static void doIdle(void* landmarks_panel); + + /** + * Updates accordions according to filtered items in lists. + * + * It hides accordion for empty lists + */ + void updateFilteredAccordions(); + private: LLInventorySubTreePanel* mFavoritesInventoryPanel; LLInventorySubTreePanel* mLandmarksInventoryPanel; @@ -121,10 +134,15 @@ private: LLInventorySubTreePanel* mLibraryInventoryPanel; LLMenuGL* mGearLandmarkMenu; LLMenuGL* mGearFolderMenu; + LLMenuGL* mMenuAdd; LLInventorySubTreePanel* mCurrentSelectedList; LLPanel* mListCommands; bool mSortByDate; + bool mDirtyFilter; + + typedef std::vector<LLAccordionCtrlTab*> accordion_tabs_t; + accordion_tabs_t mAccordionTabs; }; #endif //LL_LLPANELLANDMARKS_H diff --git a/indra/newview/llpanelpeople.cpp b/indra/newview/llpanelpeople.cpp index 61d66873ea..4580eeb336 100644 --- a/indra/newview/llpanelpeople.cpp +++ b/indra/newview/llpanelpeople.cpp @@ -413,13 +413,17 @@ BOOL LLPanelPeople::postBuild() mOnlineFriendList = getChild<LLPanel>(FRIENDS_TAB_NAME)->getChild<LLAvatarList>("avatars_online"); mAllFriendList = getChild<LLPanel>(FRIENDS_TAB_NAME)->getChild<LLAvatarList>("avatars_all"); mOnlineFriendList->setNoItemsCommentText(getString("no_friends_online")); + mOnlineFriendList->setShowIcons("FriendsListShowIcons"); mAllFriendList->setNoItemsCommentText(getString("no_friends")); + mAllFriendList->setShowIcons("FriendsListShowIcons"); mNearbyList = getChild<LLPanel>(NEARBY_TAB_NAME)->getChild<LLAvatarList>("avatar_list"); mNearbyList->setNoItemsCommentText(getString("no_one_near")); + mNearbyList->setShowIcons("NearbyListShowIcons"); mRecentList = getChild<LLPanel>(RECENT_TAB_NAME)->getChild<LLAvatarList>("avatar_list"); mRecentList->setNoItemsCommentText(getString("no_people")); + mRecentList->setShowIcons("RecentListShowIcons"); mGroupList = getChild<LLGroupList>("group_list"); mGroupList->setNoItemsCommentText(getString("no_groups")); @@ -611,6 +615,10 @@ void LLPanelPeople::updateButtons() bool recent_tab_active = (cur_tab == RECENT_TAB_NAME); LLUUID selected_id; + std::vector<LLUUID> selected_uuids; + getCurrentItemIDs(selected_uuids); + bool item_selected = (selected_uuids.size() == 1); + buttonSetVisible("group_info_btn", group_tab_active); buttonSetVisible("chat_btn", group_tab_active); buttonSetVisible("add_friend_btn", nearby_tab_active || recent_tab_active); @@ -621,7 +629,6 @@ void LLPanelPeople::updateButtons() if (group_tab_active) { - bool item_selected = mGroupList->getSelectedItem() != NULL; bool cur_group_active = true; if (item_selected) @@ -629,7 +636,7 @@ void LLPanelPeople::updateButtons() selected_id = mGroupList->getSelectedUUID(); cur_group_active = (gAgent.getGroupID() == selected_id); } - + LLPanel* groups_panel = mTabContainer->getCurrentPanel(); groups_panel->childSetEnabled("activate_btn", item_selected && !cur_group_active); // "none" or a non-active group selected groups_panel->childSetEnabled("plus_btn", item_selected); @@ -640,18 +647,18 @@ void LLPanelPeople::updateButtons() bool is_friend = true; // Check whether selected avatar is our friend. - if ((selected_id = getCurrentItemID()).notNull()) + if (item_selected) { + selected_id = selected_uuids.front(); is_friend = LLAvatarTracker::instance().getBuddyInfo(selected_id) != NULL; } childSetEnabled("add_friend_btn", !is_friend); } - bool item_selected = selected_id.notNull(); buttonSetEnabled("teleport_btn", friends_tab_active && item_selected); buttonSetEnabled("view_profile_btn", item_selected); - buttonSetEnabled("im_btn", item_selected); + buttonSetEnabled("im_btn", (selected_uuids.size() >= 1)); // allow starting the friends conference for multiple selection buttonSetEnabled("call_btn", item_selected && false); // not implemented yet buttonSetEnabled("share_btn", item_selected && false); // not implemented yet buttonSetEnabled("group_info_btn", item_selected); @@ -877,7 +884,17 @@ void LLPanelPeople::onAddFriendWizButtonClicked() void LLPanelPeople::onDeleteFriendButtonClicked() { - LLAvatarActions::removeFriendDialog(getCurrentItemID()); + std::vector<LLUUID> selected_uuids; + getCurrentItemIDs(selected_uuids); + + if (selected_uuids.size() == 1) + { + LLAvatarActions::removeFriendDialog( selected_uuids.front() ); + } + else if (selected_uuids.size() > 1) + { + LLAvatarActions::removeFriendsDialog( selected_uuids ); + } } void LLPanelPeople::onGroupInfoButtonClicked() @@ -963,6 +980,8 @@ void LLPanelPeople::onFriendsViewSortMenuItemClicked(const LLSD& userdata) } else if (chosen_item == "view_icons") { + mAllFriendList->toggleIcons(); + mOnlineFriendList->toggleIcons(); } else if (chosen_item == "organize_offline") { @@ -992,6 +1011,7 @@ void LLPanelPeople::onNearbyViewSortMenuItemClicked(const LLSD& userdata) } else if (chosen_item == "view_icons") { + mNearbyList->toggleIcons(); } else if (chosen_item == "sort_distance") { @@ -1011,7 +1031,7 @@ void LLPanelPeople::onRecentViewSortMenuItemClicked(const LLSD& userdata) } else if (chosen_item == "view_icons") { - // *TODO: implement showing/hiding icons + mRecentList->toggleIcons(); } } diff --git a/indra/newview/llpanelplaceinfo.cpp b/indra/newview/llpanelplaceinfo.cpp index cb9f7184f0..5af27a5ec1 100644 --- a/indra/newview/llpanelplaceinfo.cpp +++ b/indra/newview/llpanelplaceinfo.cpp @@ -55,6 +55,8 @@ #include "llagent.h" #include "llagentui.h" #include "llavatarpropertiesprocessor.h" +#include "llcallbacklist.h" +#include "llexpandabletextbox.h" #include "llfloaterworldmap.h" #include "llfloaterbuycurrency.h" #include "llinventorymodel.h" @@ -65,6 +67,7 @@ #include "llviewerinventory.h" #include "llviewerparcelmgr.h" #include "llviewerregion.h" +#include "llviewercontrol.h" #include "llviewertexteditor.h" #include "llworldmap.h" #include "llsdutil_math.h" @@ -76,7 +79,6 @@ typedef std::pair<LLUUID, std::string> folder_pair_t; static bool cmp_folders(const folder_pair_t& left, const folder_pair_t& right); -static std::string getFullFolderName(const LLViewerInventoryCategory* cat); static void collectLandmarkFolders(LLInventoryModel::cat_array_t& cats); static LLRegisterPanelClassWrapper<LLPanelPlaceInfo> t_place_info("panel_place_info"); @@ -111,7 +113,7 @@ BOOL LLPanelPlaceInfo::postBuild() mForSalePanel = getChild<LLPanel>("for_sale_panel"); mYouAreHerePanel = getChild<LLPanel>("here_panel"); - LLViewerParcelMgr::getInstance()->addAgentParcelChangedCallback(boost::bind(&LLPanelPlaceInfo::updateYouAreHereBanner,this)); + gIdleCallbacks.addFunction(&LLPanelPlaceInfo::updateYouAreHereBanner, this); //Icon value should contain sale price of last selected parcel. mForSalePanel->getChild<LLIconCtrl>("icon_for_sale")-> @@ -120,7 +122,7 @@ BOOL LLPanelPlaceInfo::postBuild() mSnapshotCtrl = getChild<LLTextureCtrl>("logo"); mRegionName = getChild<LLTextBox>("region_title"); mParcelName = getChild<LLTextBox>("parcel_title"); - mDescEditor = getChild<LLTextEditor>("description"); + mDescEditor = getChild<LLExpandableTextBox>("description"); mMaturityRatingText = getChild<LLTextBox>("maturity_value"); mParcelOwner = getChild<LLTextBox>("owner_value"); @@ -456,13 +458,13 @@ void LLPanelPlaceInfo::processParcelInfo(const LLParcelData& parcel_data) mSnapshotCtrl->setImageAssetID(parcel_data.snapshot_id); } - if(!parcel_data.name.empty()) + if(!parcel_data.sim_name.empty()) { - mParcelName->setText(parcel_data.name); + mRegionName->setText(parcel_data.sim_name); } else { - mParcelName->setText(LLStringUtil::null); + mRegionName->setText(LLStringUtil::null); } if(!parcel_data.desc.empty()) @@ -509,19 +511,22 @@ void LLPanelPlaceInfo::processParcelInfo(const LLParcelData& parcel_data) region_z = llround(mPosRegion.mV[VZ]); } - std::string name = getString("not_available"); - if (!parcel_data.sim_name.empty()) + if (!parcel_data.name.empty()) { - name = llformat("%s (%d, %d, %d)", - parcel_data.sim_name.c_str(), region_x, region_y, region_z); - mRegionName->setText(name); + mParcelName->setText(llformat("%s (%d, %d, %d)", + parcel_data.name.c_str(), region_x, region_y, region_z)); + } + else + { + mParcelName->setText(getString("not_available")); } if (mInfoType == CREATE_LANDMARK) { if (parcel_data.name.empty()) { - mTitleEditor->setText(name); + mTitleEditor->setText(llformat("%s (%d, %d, %d)", + parcel_data.sim_name.c_str(), region_x, region_y, region_z)); } else { @@ -610,6 +615,9 @@ void LLPanelPlaceInfo::displaySelectedParcelInfo(LLParcel* parcel, parcel_data.name = parcel->getName(); parcel_data.sim_name = region->getName(); parcel_data.snapshot_id = parcel->getSnapshotID(); + mPosRegion.setVec((F32)fmod(pos_global.mdV[VX], (F64)REGION_WIDTH_METERS), + (F32)fmod(pos_global.mdV[VY], (F64)REGION_WIDTH_METERS), + (F32)pos_global.mdV[VZ]); parcel_data.global_x = pos_global.mdV[VX]; parcel_data.global_y = pos_global.mdV[VY]; parcel_data.global_z = pos_global.mdV[VZ]; @@ -986,18 +994,22 @@ void LLPanelPlaceInfo::populateFoldersList() mFolderCombo->add(it->second, LLSD(it->first)); } -void LLPanelPlaceInfo::updateYouAreHereBanner() +//static +void LLPanelPlaceInfo::updateYouAreHereBanner(void* userdata) { //YouAreHere Banner should be displayed only for selected places, // If you want to display it for landmark or teleport history item, you should check by mParcelId - bool is_you_are_here = false; - if (mSelectedParcelID != S32(-1) && !mLastSelectedRegionID.isNull()) - { - is_you_are_here = gAgent.getRegion()->getRegionID()== mLastSelectedRegionID && - mSelectedParcelID == LLViewerParcelMgr::getInstance()->getAgentParcel()->getLocalID(); - } - mYouAreHerePanel->setVisible(is_you_are_here); + LLPanelPlaceInfo* self = static_cast<LLPanelPlaceInfo*>(userdata); + if(!self->getVisible()) + return; + + static F32 radius = gSavedSettings.getF32("YouAreHereDistance"); + + BOOL display_banner = self->mLastSelectedRegionID == gAgent.getRegion()->getRegionID() && + LLAgentUI::checkAgentDistance(self->mPosRegion, radius); + + self->mYouAreHerePanel->setVisible(display_banner); } void LLPanelPlaceInfo::onForSaleBannerClick() @@ -1028,14 +1040,9 @@ void LLPanelPlaceInfo::onForSaleBannerClick() } - - -static bool cmp_folders(const folder_pair_t& left, const folder_pair_t& right) -{ - return left.second < right.second; -} -static std::string getFullFolderName(const LLViewerInventoryCategory* cat) +/*static*/ +std::string LLPanelPlaceInfo::getFullFolderName(const LLViewerInventoryCategory* cat) { std::string name = cat->getName(); LLUUID parent_id; @@ -1057,6 +1064,11 @@ static std::string getFullFolderName(const LLViewerInventoryCategory* cat) return name; } +static bool cmp_folders(const folder_pair_t& left, const folder_pair_t& right) +{ + return left.second < right.second; +} + static void collectLandmarkFolders(LLInventoryModel::cat_array_t& cats) { LLUUID landmarks_id = gInventory.findCategoryUUIDForType(LLAssetType::AT_LANDMARK); diff --git a/indra/newview/llpanelplaceinfo.h b/indra/newview/llpanelplaceinfo.h index 7b3a8f050b..07a2434d59 100644 --- a/indra/newview/llpanelplaceinfo.h +++ b/indra/newview/llpanelplaceinfo.h @@ -43,6 +43,7 @@ class LLButton; class LLComboBox; +class LLExpandableTextBox; class LLInventoryItem; class LLLineEditor; class LLPanelPickEdit; @@ -52,6 +53,7 @@ class LLTextBox; class LLTextEditor; class LLTextureCtrl; class LLViewerRegion; +class LLViewerInventoryCategory; class LLPanelPlaceInfo : public LLPanel, LLRemoteParcelInfoObserver { @@ -131,11 +133,13 @@ public: /*virtual*/ void processParcelInfo(const LLParcelData& parcel_data); /*virtual*/ void handleVisibilityChange (BOOL new_visibility); + + static std::string getFullFolderName(const LLViewerInventoryCategory* cat); private: void populateFoldersList(); - void updateYouAreHereBanner(); + static void updateYouAreHereBanner(void*);// added to gIdleCallbacks void onForSaleBannerClick(); /** @@ -161,7 +165,7 @@ private: LLTextureCtrl* mSnapshotCtrl; LLTextBox* mRegionName; LLTextBox* mParcelName; - LLTextEditor* mDescEditor; + LLExpandableTextBox*mDescEditor; LLTextBox* mMaturityRatingText; LLTextBox* mParcelOwner; LLTextBox* mLastVisited; diff --git a/indra/newview/llpanelplaces.cpp b/indra/newview/llpanelplaces.cpp index 5ab823b6e5..b2e9110e96 100644 --- a/indra/newview/llpanelplaces.cpp +++ b/indra/newview/llpanelplaces.cpp @@ -351,7 +351,16 @@ void LLPanelPlaces::setItem(LLInventoryItem* item) if (is_landmark_editable) { - mPlaceInfo->setLandmarkFolder(mItem->getParentUUID()); + if(!mPlaceInfo->setLandmarkFolder(mItem->getParentUUID()) && !mItem->getParentUUID().isNull()) + { + const LLViewerInventoryCategory* cat = gInventory.getCategory(mItem->getParentUUID()); + if(cat) + { + std::string cat_fullname = LLPanelPlaceInfo::getFullFolderName(cat); + LLComboBox* folderList = mPlaceInfo->getChild<LLComboBox>("folder_combo"); + folderList->add(cat_fullname, cat->getUUID(),ADD_TOP); + } + } } mPlaceInfo->displayItemInfo(mItem); diff --git a/indra/newview/llparticipantlist.cpp b/indra/newview/llparticipantlist.cpp index 25e773e8b8..e97eb1df2b 100644 --- a/indra/newview/llparticipantlist.cpp +++ b/indra/newview/llparticipantlist.cpp @@ -34,7 +34,7 @@ #include "llparticipantlist.h" #include "llavatarlist.h" -#include "llfloateractivespeakers.h" +#include "llspeakers.h" //LLParticipantList retrieves add, clear and remove events and updates view accordingly LLParticipantList::LLParticipantList(LLSpeakerMgr* data_source, LLAvatarList* avatar_list): @@ -48,6 +48,18 @@ LLParticipantList::LLParticipantList(LLSpeakerMgr* data_source, LLAvatarList* av mSpeakerMgr->addListener(mSpeakerAddListener, "add"); mSpeakerMgr->addListener(mSpeakerRemoveListener, "remove"); mSpeakerMgr->addListener(mSpeakerClearListener, "clear"); + + //Lets fill avatarList with existing speakers + LLAvatarList::uuid_vector_t& group_members = mAvatarList->getIDs(); + + LLSpeakerMgr::speaker_list_t speaker_list; + mSpeakerMgr->getSpeakerList(&speaker_list, true); + for(LLSpeakerMgr::speaker_list_t::iterator it = speaker_list.begin(); it != speaker_list.end(); it++) + { + group_members.push_back((*it)->mID); + } + mAvatarList->setDirty(); + mAvatarList->sortByName(); } LLParticipantList::~LLParticipantList() @@ -87,8 +99,12 @@ bool LLParticipantList::SpeakerAddListener::handleEvent(LLPointer<LLOldEvents::L bool LLParticipantList::SpeakerRemoveListener::handleEvent(LLPointer<LLOldEvents::LLEvent> event, const LLSD& userdata) { LLAvatarList::uuid_vector_t& group_members = mAvatarList->getIDs(); - group_members.erase(std::find(group_members.begin(), group_members.end(), event->getValue().asUUID())); - mAvatarList->setDirty(); + LLAvatarList::uuid_vector_t::iterator pos = std::find(group_members.begin(), group_members.end(), event->getValue().asUUID()); + if(pos != group_members.end()) + { + group_members.erase(pos); + mAvatarList->setDirty(); + } return true; } diff --git a/indra/newview/llscreenchannel.cpp b/indra/newview/llscreenchannel.cpp index 1683d113a9..73dcd1dd92 100644 --- a/indra/newview/llscreenchannel.cpp +++ b/indra/newview/llscreenchannel.cpp @@ -191,19 +191,22 @@ void LLScreenChannel::onToastFade(LLToast* toast) { std::vector<ToastElem>::iterator it = find(mToastList.begin(), mToastList.end(), static_cast<LLPanel*>(toast)); - bool delete_toast = !mCanStoreToasts || !toast->getCanBeStored(); - if(delete_toast) - { - mToastList.erase(it); - deleteToast(toast); - } - else + if(it != mToastList.end()) { - storeToast((*it)); - mToastList.erase(it); - } + bool delete_toast = !mCanStoreToasts || !toast->getCanBeStored(); + if(delete_toast) + { + mToastList.erase(it); + deleteToast(toast); + } + else + { + storeToast((*it)); + mToastList.erase(it); + } - redrawToasts(); + redrawToasts(); + } } //-------------------------------------------------------------------------- @@ -247,6 +250,7 @@ void LLScreenChannel::loadStoredToastsToChannel() for(it = mStoredToastList.begin(); it != mStoredToastList.end(); ++it) { + (*it).toast->setIsHidden(false); (*it).toast->resetTimer(); mToastList.push_back((*it)); } @@ -266,6 +270,7 @@ void LLScreenChannel::loadStoredToastByNotificationIDToChannel(LLUUID id) mOverflowToastHidden = false; LLToast* toast = (*it).toast; + toast->setIsHidden(false); toast->resetTimer(); mToastList.push_back((*it)); mStoredToastList.erase(it); @@ -519,6 +524,7 @@ void LLScreenChannel::createStartUpToast(S32 notif_num, F32 timer) LLRect toast_rect; LLToast::Params p; p.lifetime_secs = timer; + p.enable_hide_btn = false; mStartUpToastPanel = new LLToast(p); if(!mStartUpToastPanel) diff --git a/indra/newview/llspeakers.cpp b/indra/newview/llspeakers.cpp new file mode 100644 index 0000000000..2341fcfc6d --- /dev/null +++ b/indra/newview/llspeakers.cpp @@ -0,0 +1,639 @@ +/** + * @file llspeakers.cpp + * @brief Management interface for muting and controlling volume of residents currently speaking + * + * $LicenseInfo:firstyear=2005&license=viewergpl$ + * + * Copyright (c) 2005-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 "llspeakers.h" + +#include "llagent.h" +#include "llappviewer.h" +#include "llmutelist.h" +#include "llsdutil.h" +#include "lluicolortable.h" +#include "llviewerobjectlist.h" +#include "llvoavatar.h" +#include "llworld.h" + +const F32 SPEAKER_TIMEOUT = 10.f; // seconds of not being on voice channel before removed from list of active speakers +const LLColor4 INACTIVE_COLOR(0.3f, 0.3f, 0.3f, 0.5f); +const LLColor4 ACTIVE_COLOR(0.5f, 0.5f, 0.5f, 1.f); + +LLSpeaker::LLSpeaker(const LLUUID& id, const std::string& name, const ESpeakerType type) : + mStatus(LLSpeaker::STATUS_TEXT_ONLY), + mLastSpokeTime(0.f), + mSpeechVolume(0.f), + mHasSpoken(FALSE), + mHasLeftCurrentCall(FALSE), + mDotColor(LLColor4::white), + mID(id), + mTyping(FALSE), + mSortIndex(0), + mType(type), + mIsModerator(FALSE), + mModeratorMutedVoice(FALSE), + mModeratorMutedText(FALSE) +{ + if (name.empty() && type == SPEAKER_AGENT) + { + lookupName(); + } + else + { + mDisplayName = name; + } + + gVoiceClient->setUserVolume(id, LLMuteList::getInstance()->getSavedResidentVolume(id)); + + mActivityTimer.resetWithExpiry(SPEAKER_TIMEOUT); +} + + +void LLSpeaker::lookupName() +{ + gCacheName->get(mID, FALSE, boost::bind(&LLSpeaker::onAvatarNameLookup, this, _1, _2, _3, _4)); +} + +void LLSpeaker::onAvatarNameLookup(const LLUUID& id, const std::string& first, const std::string& last, BOOL is_group) +{ + mDisplayName = first + " " + last; +} + +LLSpeakerTextModerationEvent::LLSpeakerTextModerationEvent(LLSpeaker* source) +: LLEvent(source, "Speaker text moderation event") +{ +} + +LLSD LLSpeakerTextModerationEvent::getValue() +{ + return std::string("text"); +} + + +LLSpeakerVoiceModerationEvent::LLSpeakerVoiceModerationEvent(LLSpeaker* source) +: LLEvent(source, "Speaker voice moderation event") +{ +} + +LLSD LLSpeakerVoiceModerationEvent::getValue() +{ + return std::string("voice"); +} + +LLSpeakerListChangeEvent::LLSpeakerListChangeEvent(LLSpeakerMgr* source, const LLUUID& speaker_id) +: LLEvent(source, "Speaker added/removed from speaker mgr"), + mSpeakerID(speaker_id) +{ +} + +LLSD LLSpeakerListChangeEvent::getValue() +{ + return mSpeakerID; +} + +// helper sort class +struct LLSortRecentSpeakers +{ + bool operator()(const LLPointer<LLSpeaker> lhs, const LLPointer<LLSpeaker> rhs) const; +}; + +bool LLSortRecentSpeakers::operator()(const LLPointer<LLSpeaker> lhs, const LLPointer<LLSpeaker> rhs) const +{ + // Sort first on status + if (lhs->mStatus != rhs->mStatus) + { + return (lhs->mStatus < rhs->mStatus); + } + + // and then on last speaking time + if(lhs->mLastSpokeTime != rhs->mLastSpokeTime) + { + return (lhs->mLastSpokeTime > rhs->mLastSpokeTime); + } + + // and finally (only if those are both equal), on name. + return( lhs->mDisplayName.compare(rhs->mDisplayName) < 0 ); +} + + +// +// LLSpeakerMgr +// + +LLSpeakerMgr::LLSpeakerMgr(LLVoiceChannel* channelp) : + mVoiceChannel(channelp) +{ +} + +LLSpeakerMgr::~LLSpeakerMgr() +{ +} + +LLPointer<LLSpeaker> LLSpeakerMgr::setSpeaker(const LLUUID& id, const std::string& name, LLSpeaker::ESpeakerStatus status, LLSpeaker::ESpeakerType type) +{ + if (id.isNull()) return NULL; + + LLPointer<LLSpeaker> speakerp; + if (mSpeakers.find(id) == mSpeakers.end()) + { + speakerp = new LLSpeaker(id, name, type); + speakerp->mStatus = status; + mSpeakers.insert(std::make_pair(speakerp->mID, speakerp)); + mSpeakersSorted.push_back(speakerp); + fireEvent(new LLSpeakerListChangeEvent(this, speakerp->mID), "add"); + } + else + { + speakerp = findSpeaker(id); + if (speakerp.notNull()) + { + // keep highest priority status (lowest value) instead of overriding current value + speakerp->mStatus = llmin(speakerp->mStatus, status); + speakerp->mActivityTimer.resetWithExpiry(SPEAKER_TIMEOUT); + // RN: due to a weird behavior where IMs from attached objects come from the wearer's agent_id + // we need to override speakers that we think are objects when we find out they are really + // residents + if (type == LLSpeaker::SPEAKER_AGENT) + { + speakerp->mType = LLSpeaker::SPEAKER_AGENT; + speakerp->lookupName(); + } + } + } + + return speakerp; +} + +void LLSpeakerMgr::update(BOOL resort_ok) +{ + if (!gVoiceClient) + { + return; + } + + LLColor4 speaking_color = LLUIColorTable::instance().getColor("SpeakingColor"); + LLColor4 overdriven_color = LLUIColorTable::instance().getColor("OverdrivenColor"); + + if(resort_ok) // only allow list changes when user is not interacting with it + { + updateSpeakerList(); + } + + // update status of all current speakers + BOOL voice_channel_active = (!mVoiceChannel && gVoiceClient->inProximalChannel()) || (mVoiceChannel && mVoiceChannel->isActive()); + for (speaker_map_t::iterator speaker_it = mSpeakers.begin(); speaker_it != mSpeakers.end();) + { + LLUUID speaker_id = speaker_it->first; + LLSpeaker* speakerp = speaker_it->second; + + speaker_map_t::iterator cur_speaker_it = speaker_it++; + + if (voice_channel_active && gVoiceClient->getVoiceEnabled(speaker_id)) + { + speakerp->mSpeechVolume = gVoiceClient->getCurrentPower(speaker_id); + BOOL moderator_muted_voice = gVoiceClient->getIsModeratorMuted(speaker_id); + if (moderator_muted_voice != speakerp->mModeratorMutedVoice) + { + speakerp->mModeratorMutedVoice = moderator_muted_voice; + speakerp->fireEvent(new LLSpeakerVoiceModerationEvent(speakerp)); + } + + if (gVoiceClient->getOnMuteList(speaker_id) || speakerp->mModeratorMutedVoice) + { + speakerp->mStatus = LLSpeaker::STATUS_MUTED; + } + else if (gVoiceClient->getIsSpeaking(speaker_id)) + { + // reset inactivity expiration + if (speakerp->mStatus != LLSpeaker::STATUS_SPEAKING) + { + speakerp->mLastSpokeTime = mSpeechTimer.getElapsedTimeF32(); + speakerp->mHasSpoken = TRUE; + } + speakerp->mStatus = LLSpeaker::STATUS_SPEAKING; + // interpolate between active color and full speaking color based on power of speech output + speakerp->mDotColor = speaking_color; + if (speakerp->mSpeechVolume > LLVoiceClient::OVERDRIVEN_POWER_LEVEL) + { + speakerp->mDotColor = overdriven_color; + } + } + else + { + speakerp->mSpeechVolume = 0.f; + speakerp->mDotColor = ACTIVE_COLOR; + + if (speakerp->mHasSpoken) + { + // have spoken once, not currently speaking + speakerp->mStatus = LLSpeaker::STATUS_HAS_SPOKEN; + } + else + { + // default state for being in voice channel + speakerp->mStatus = LLSpeaker::STATUS_VOICE_ACTIVE; + } + } + } + // speaker no longer registered in voice channel, demote to text only + else if (speakerp->mStatus != LLSpeaker::STATUS_NOT_IN_CHANNEL) + { + if(speakerp->mType == LLSpeaker::SPEAKER_EXTERNAL) + { + // external speakers should be timed out when they leave the voice channel (since they only exist via SLVoice) + speakerp->mStatus = LLSpeaker::STATUS_NOT_IN_CHANNEL; + } + else + { + speakerp->mStatus = LLSpeaker::STATUS_TEXT_ONLY; + speakerp->mSpeechVolume = 0.f; + speakerp->mDotColor = ACTIVE_COLOR; + } + } + } + + if(resort_ok) // only allow list changes when user is not interacting with it + { + // sort by status then time last spoken + std::sort(mSpeakersSorted.begin(), mSpeakersSorted.end(), LLSortRecentSpeakers()); + } + + // for recent speakers who are not currently speaking, show "recent" color dot for most recent + // fading to "active" color + + S32 recent_speaker_count = 0; + S32 sort_index = 0; + speaker_list_t::iterator sorted_speaker_it; + for(sorted_speaker_it = mSpeakersSorted.begin(); + sorted_speaker_it != mSpeakersSorted.end(); ) + { + LLPointer<LLSpeaker> speakerp = *sorted_speaker_it; + + // color code recent speakers who are not currently speaking + if (speakerp->mStatus == LLSpeaker::STATUS_HAS_SPOKEN) + { + speakerp->mDotColor = lerp(speaking_color, ACTIVE_COLOR, clamp_rescale((F32)recent_speaker_count, -2.f, 3.f, 0.f, 1.f)); + recent_speaker_count++; + } + + // stuff sort ordinal into speaker so the ui can sort by this value + speakerp->mSortIndex = sort_index++; + + // remove speakers that have been gone too long + if (speakerp->mStatus == LLSpeaker::STATUS_NOT_IN_CHANNEL && speakerp->mActivityTimer.hasExpired()) + { + fireEvent(new LLSpeakerListChangeEvent(this, speakerp->mID), "remove"); + + mSpeakers.erase(speakerp->mID); + sorted_speaker_it = mSpeakersSorted.erase(sorted_speaker_it); + } + else + { + ++sorted_speaker_it; + } + } +} + +void LLSpeakerMgr::updateSpeakerList() +{ + // are we bound to the currently active voice channel? + if ((!mVoiceChannel && gVoiceClient->inProximalChannel()) || (mVoiceChannel && mVoiceChannel->isActive())) + { + LLVoiceClient::participantMap* participants = gVoiceClient->getParticipantList(); + if(participants) + { + LLVoiceClient::participantMap::iterator participant_it; + + // add new participants to our list of known speakers + for (participant_it = participants->begin(); participant_it != participants->end(); ++participant_it) + { + LLVoiceClient::participantState* participantp = participant_it->second; + setSpeaker(participantp->mAvatarID, participantp->mDisplayName, LLSpeaker::STATUS_VOICE_ACTIVE, (participantp->isAvatar()?LLSpeaker::SPEAKER_AGENT:LLSpeaker::SPEAKER_EXTERNAL)); + } + } + } +} + +LLPointer<LLSpeaker> LLSpeakerMgr::findSpeaker(const LLUUID& speaker_id) +{ + speaker_map_t::iterator found_it = mSpeakers.find(speaker_id); + if (found_it == mSpeakers.end()) + { + return NULL; + } + return found_it->second; +} + +void LLSpeakerMgr::getSpeakerList(speaker_list_t* speaker_list, BOOL include_text) +{ + speaker_list->clear(); + for (speaker_map_t::iterator speaker_it = mSpeakers.begin(); speaker_it != mSpeakers.end(); ++speaker_it) + { + LLPointer<LLSpeaker> speakerp = speaker_it->second; + // what about text only muted or inactive? + if (include_text || speakerp->mStatus != LLSpeaker::STATUS_TEXT_ONLY) + { + speaker_list->push_back(speakerp); + } + } +} + +const LLUUID LLSpeakerMgr::getSessionID() +{ + return mVoiceChannel->getSessionID(); +} + + +void LLSpeakerMgr::setSpeakerTyping(const LLUUID& speaker_id, BOOL typing) +{ + LLPointer<LLSpeaker> speakerp = findSpeaker(speaker_id); + if (speakerp.notNull()) + { + speakerp->mTyping = typing; + } +} + +// speaker has chatted via either text or voice +void LLSpeakerMgr::speakerChatted(const LLUUID& speaker_id) +{ + LLPointer<LLSpeaker> speakerp = findSpeaker(speaker_id); + if (speakerp.notNull()) + { + speakerp->mLastSpokeTime = mSpeechTimer.getElapsedTimeF32(); + speakerp->mHasSpoken = TRUE; + } +} + +BOOL LLSpeakerMgr::isVoiceActive() +{ + // mVoiceChannel = NULL means current voice channel, whatever it is + return LLVoiceClient::voiceEnabled() && mVoiceChannel && mVoiceChannel->isActive(); +} + + +// +// LLIMSpeakerMgr +// +LLIMSpeakerMgr::LLIMSpeakerMgr(LLVoiceChannel* channel) : LLSpeakerMgr(channel) +{ +} + +void LLIMSpeakerMgr::updateSpeakerList() +{ + // don't do normal updates which are pulled from voice channel + // rely on user list reported by sim + + // We need to do this to allow PSTN callers into group chats to show in the list. + LLSpeakerMgr::updateSpeakerList(); + + return; +} + +void LLIMSpeakerMgr::setSpeakers(const LLSD& speakers) +{ + if ( !speakers.isMap() ) return; + + if ( speakers.has("agent_info") && speakers["agent_info"].isMap() ) + { + LLSD::map_const_iterator speaker_it; + for(speaker_it = speakers["agent_info"].beginMap(); + speaker_it != speakers["agent_info"].endMap(); + ++speaker_it) + { + LLUUID agent_id(speaker_it->first); + + LLPointer<LLSpeaker> speakerp = setSpeaker( + agent_id, + LLStringUtil::null, + LLSpeaker::STATUS_TEXT_ONLY); + + if ( speaker_it->second.isMap() ) + { + speakerp->mIsModerator = speaker_it->second["is_moderator"]; + speakerp->mModeratorMutedText = + speaker_it->second["mutes"]["text"]; + } + } + } + else if ( speakers.has("agents" ) && speakers["agents"].isArray() ) + { + //older, more decprecated way. Need here for + //using older version of servers + LLSD::array_const_iterator speaker_it; + for(speaker_it = speakers["agents"].beginArray(); + speaker_it != speakers["agents"].endArray(); + ++speaker_it) + { + const LLUUID agent_id = (*speaker_it).asUUID(); + + LLPointer<LLSpeaker> speakerp = setSpeaker( + agent_id, + LLStringUtil::null, + LLSpeaker::STATUS_TEXT_ONLY); + } + } +} + +void LLIMSpeakerMgr::updateSpeakers(const LLSD& update) +{ + if ( !update.isMap() ) return; + + if ( update.has("agent_updates") && update["agent_updates"].isMap() ) + { + LLSD::map_const_iterator update_it; + for( + update_it = update["agent_updates"].beginMap(); + update_it != update["agent_updates"].endMap(); + ++update_it) + { + LLUUID agent_id(update_it->first); + LLPointer<LLSpeaker> speakerp = findSpeaker(agent_id); + + LLSD agent_data = update_it->second; + + if (agent_data.isMap() && agent_data.has("transition")) + { + if (agent_data["transition"].asString() == "LEAVE" && speakerp.notNull()) + { + speakerp->mStatus = LLSpeaker::STATUS_NOT_IN_CHANNEL; + speakerp->mDotColor = INACTIVE_COLOR; + speakerp->mActivityTimer.resetWithExpiry(SPEAKER_TIMEOUT); + } + else if (agent_data["transition"].asString() == "ENTER") + { + // add or update speaker + speakerp = setSpeaker(agent_id); + } + else + { + llwarns << "bad membership list update " << ll_print_sd(agent_data["transition"]) << llendl; + } + } + + if (speakerp.isNull()) continue; + + // should have a valid speaker from this point on + if (agent_data.isMap() && agent_data.has("info")) + { + LLSD agent_info = agent_data["info"]; + + if (agent_info.has("is_moderator")) + { + speakerp->mIsModerator = agent_info["is_moderator"]; + } + + if (agent_info.has("mutes")) + { + speakerp->mModeratorMutedText = agent_info["mutes"]["text"]; + } + } + } + } + else if ( update.has("updates") && update["updates"].isMap() ) + { + LLSD::map_const_iterator update_it; + for ( + update_it = update["updates"].beginMap(); + update_it != update["updates"].endMap(); + ++update_it) + { + LLUUID agent_id(update_it->first); + LLPointer<LLSpeaker> speakerp = findSpeaker(agent_id); + + std::string agent_transition = update_it->second.asString(); + if (agent_transition == "LEAVE" && speakerp.notNull()) + { + speakerp->mStatus = LLSpeaker::STATUS_NOT_IN_CHANNEL; + speakerp->mDotColor = INACTIVE_COLOR; + speakerp->mActivityTimer.resetWithExpiry(SPEAKER_TIMEOUT); + } + else if ( agent_transition == "ENTER") + { + // add or update speaker + speakerp = setSpeaker(agent_id); + } + else + { + llwarns << "bad membership list update " + << agent_transition << llendl; + } + } + } +} + + +// +// LLActiveSpeakerMgr +// + +LLActiveSpeakerMgr::LLActiveSpeakerMgr() : LLSpeakerMgr(NULL) +{ +} + +void LLActiveSpeakerMgr::updateSpeakerList() +{ + // point to whatever the current voice channel is + mVoiceChannel = LLVoiceChannel::getCurrentVoiceChannel(); + + // always populate from active voice channel + if (LLVoiceChannel::getCurrentVoiceChannel() != mVoiceChannel) + { + fireEvent(new LLSpeakerListChangeEvent(this, LLUUID::null), "clear"); + mSpeakers.clear(); + mSpeakersSorted.clear(); + mVoiceChannel = LLVoiceChannel::getCurrentVoiceChannel(); + } + LLSpeakerMgr::updateSpeakerList(); + + // clean up text only speakers + for (speaker_map_t::iterator speaker_it = mSpeakers.begin(); speaker_it != mSpeakers.end(); ++speaker_it) + { + LLUUID speaker_id = speaker_it->first; + LLSpeaker* speakerp = speaker_it->second; + if (speakerp->mStatus == LLSpeaker::STATUS_TEXT_ONLY) + { + // automatically flag text only speakers for removal + speakerp->mStatus = LLSpeaker::STATUS_NOT_IN_CHANNEL; + } + } + +} + + + +// +// LLLocalSpeakerMgr +// + +LLLocalSpeakerMgr::LLLocalSpeakerMgr() : LLSpeakerMgr(LLVoiceChannelProximal::getInstance()) +{ +} + +LLLocalSpeakerMgr::~LLLocalSpeakerMgr () +{ +} + +void LLLocalSpeakerMgr::updateSpeakerList() +{ + // pull speakers from voice channel + LLSpeakerMgr::updateSpeakerList(); + + if (gDisconnected)//the world is cleared. + { + return ; + } + + // pick up non-voice speakers in chat range + std::vector<LLUUID> avatar_ids; + std::vector<LLVector3d> positions; + LLWorld::getInstance()->getAvatars(&avatar_ids, &positions, gAgent.getPositionGlobal(), CHAT_NORMAL_RADIUS); + for(U32 i=0; i<avatar_ids.size(); i++) + { + setSpeaker(avatar_ids[i]); + } + + // check if text only speakers have moved out of chat range + for (speaker_map_t::iterator speaker_it = mSpeakers.begin(); speaker_it != mSpeakers.end(); ++speaker_it) + { + LLUUID speaker_id = speaker_it->first; + LLSpeaker* speakerp = speaker_it->second; + if (speakerp->mStatus == LLSpeaker::STATUS_TEXT_ONLY) + { + LLVOAvatar* avatarp = (LLVOAvatar*)gObjectList.findObject(speaker_id); + if (!avatarp || dist_vec(avatarp->getPositionAgent(), gAgent.getPositionAgent()) > CHAT_NORMAL_RADIUS) + { + speakerp->mStatus = LLSpeaker::STATUS_NOT_IN_CHANNEL; + speakerp->mDotColor = INACTIVE_COLOR; + speakerp->mActivityTimer.resetWithExpiry(SPEAKER_TIMEOUT); + } + } + } +} diff --git a/indra/newview/llspeakers.h b/indra/newview/llspeakers.h new file mode 100644 index 0000000000..e0f22bff4f --- /dev/null +++ b/indra/newview/llspeakers.h @@ -0,0 +1,172 @@ +/** + * @file llspeakers.h + * @brief Management interface for muting and controlling volume of residents currently speaking + * + * $LicenseInfo:firstyear=2005&license=viewergpl$ + * + * Copyright (c) 2005-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$ + */ + +#ifndef LL_LLSPEAKERS_H +#define LL_LLSPEAKERS_H + +#include "llevent.h" +#include "llspeakers.h" +#include "llvoicechannel.h" + +class LLSpeakerMgr; + +// data for a given participant in a voice channel +class LLSpeaker : public LLRefCount, public LLOldEvents::LLObservable, public LLHandleProvider<LLSpeaker>, public boost::signals2::trackable +{ +public: + typedef enum e_speaker_type + { + SPEAKER_AGENT, + SPEAKER_OBJECT, + SPEAKER_EXTERNAL // Speaker that doesn't map to an avatar or object (i.e. PSTN caller in a group) + } ESpeakerType; + + typedef enum e_speaker_status + { + STATUS_SPEAKING, + STATUS_HAS_SPOKEN, + STATUS_VOICE_ACTIVE, + STATUS_TEXT_ONLY, + STATUS_NOT_IN_CHANNEL, + STATUS_MUTED + } ESpeakerStatus; + + + LLSpeaker(const LLUUID& id, const std::string& name = LLStringUtil::null, const ESpeakerType type = SPEAKER_AGENT); + ~LLSpeaker() {}; + void lookupName(); + + void onAvatarNameLookup(const LLUUID& id, const std::string& first, const std::string& last, BOOL is_group); + + ESpeakerStatus mStatus; // current activity status in speech group + F32 mLastSpokeTime; // timestamp when this speaker last spoke + F32 mSpeechVolume; // current speech amplitude (timea average rms amplitude?) + std::string mDisplayName; // cache user name for this speaker + LLFrameTimer mActivityTimer; // time out speakers when they are not part of current voice channel + BOOL mHasSpoken; // has this speaker said anything this session? + BOOL mHasLeftCurrentCall; // has this speaker left the current voice call? + LLColor4 mDotColor; + LLUUID mID; + BOOL mTyping; + S32 mSortIndex; + ESpeakerType mType; + BOOL mIsModerator; + BOOL mModeratorMutedVoice; + BOOL mModeratorMutedText; +}; + +class LLSpeakerTextModerationEvent : public LLOldEvents::LLEvent +{ +public: + LLSpeakerTextModerationEvent(LLSpeaker* source); + /*virtual*/ LLSD getValue(); +}; + +class LLSpeakerVoiceModerationEvent : public LLOldEvents::LLEvent +{ +public: + LLSpeakerVoiceModerationEvent(LLSpeaker* source); + /*virtual*/ LLSD getValue(); +}; + +class LLSpeakerListChangeEvent : public LLOldEvents::LLEvent +{ +public: + LLSpeakerListChangeEvent(LLSpeakerMgr* source, const LLUUID& speaker_id); + /*virtual*/ LLSD getValue(); + +private: + const LLUUID& mSpeakerID; +}; + +class LLSpeakerMgr : public LLOldEvents::LLObservable +{ +public: + LLSpeakerMgr(LLVoiceChannel* channelp); + virtual ~LLSpeakerMgr(); + + LLPointer<LLSpeaker> findSpeaker(const LLUUID& avatar_id); + void update(BOOL resort_ok); + void setSpeakerTyping(const LLUUID& speaker_id, BOOL typing); + void speakerChatted(const LLUUID& speaker_id); + LLPointer<LLSpeaker> setSpeaker(const LLUUID& id, + const std::string& name = LLStringUtil::null, + LLSpeaker::ESpeakerStatus status = LLSpeaker::STATUS_TEXT_ONLY, + LLSpeaker::ESpeakerType = LLSpeaker::SPEAKER_AGENT); + + BOOL isVoiceActive(); + + typedef std::vector<LLPointer<LLSpeaker> > speaker_list_t; + void getSpeakerList(speaker_list_t* speaker_list, BOOL include_text); + LLVoiceChannel* getVoiceChannel() { return mVoiceChannel; } + const LLUUID getSessionID(); + +protected: + virtual void updateSpeakerList(); + + typedef std::map<LLUUID, LLPointer<LLSpeaker> > speaker_map_t; + speaker_map_t mSpeakers; + + speaker_list_t mSpeakersSorted; + LLFrameTimer mSpeechTimer; + LLVoiceChannel* mVoiceChannel; +}; + +class LLIMSpeakerMgr : public LLSpeakerMgr +{ +public: + LLIMSpeakerMgr(LLVoiceChannel* channel); + + void updateSpeakers(const LLSD& update); + void setSpeakers(const LLSD& speakers); +protected: + virtual void updateSpeakerList(); +}; + +class LLActiveSpeakerMgr : public LLSpeakerMgr, public LLSingleton<LLActiveSpeakerMgr> +{ +public: + LLActiveSpeakerMgr(); +protected: + virtual void updateSpeakerList(); +}; + +class LLLocalSpeakerMgr : public LLSpeakerMgr, public LLSingleton<LLLocalSpeakerMgr> +{ +public: + LLLocalSpeakerMgr(); + ~LLLocalSpeakerMgr (); +protected: + virtual void updateSpeakerList(); +}; + +#endif // LL_LLSPEAKERS_H diff --git a/indra/newview/llsyswellwindow.cpp b/indra/newview/llsyswellwindow.cpp index 86290e6695..93a931dc78 100644 --- a/indra/newview/llsyswellwindow.cpp +++ b/indra/newview/llsyswellwindow.cpp @@ -113,6 +113,12 @@ void LLSysWellWindow::connectListUpdaterToSignal(std::string notification_type) } //--------------------------------------------------------------------------------- +void LLSysWellWindow::onStartUpToastClick(S32 x, S32 y, MASK mask) +{ + onChicletClick(); +} + +//--------------------------------------------------------------------------------- void LLSysWellWindow::onChicletClick() { // 1 - remove StartUp toast and channel if present diff --git a/indra/newview/llsyswellwindow.h b/indra/newview/llsyswellwindow.h index fa6a1abea4..cbc5f7358f 100644 --- a/indra/newview/llsyswellwindow.h +++ b/indra/newview/llsyswellwindow.h @@ -76,6 +76,7 @@ public: void onItemClose(LLSysWellItem* item); void onStoreToast(LLPanel* info_panel, LLUUID id); void onChicletClick(); + void onStartUpToastClick(S32 x, S32 y, MASK mask); // size constants for the window and for its elements static const S32 MAX_WINDOW_HEIGHT = 200; diff --git a/indra/newview/lltoast.cpp b/indra/newview/lltoast.cpp index eba43d76a6..24824a095c 100644 --- a/indra/newview/lltoast.cpp +++ b/indra/newview/lltoast.cpp @@ -63,6 +63,7 @@ LLToast::LLToast(const LLToast::Params& p) mHideBtnEnabled(p.enable_hide_btn), mHideBtn(NULL), mNotification(p.notification), + mIsHidden(false), mHideBtnPressed(false) { LLUICtrlFactory::getInstance()->buildFloater(this, "panel_toast.xml", NULL); @@ -143,7 +144,8 @@ void LLToast::hide() { setVisible(FALSE); mTimer.stop(); - mOnFadeSignal(this); + mIsHidden = true; + mOnFadeSignal(this); } //-------------------------------------------------------------------------- @@ -159,9 +161,7 @@ void LLToast::tick() { if(mCanFade) { - setVisible(FALSE); - mTimer.stop(); - mOnFadeSignal(this); + hide(); } } @@ -206,6 +206,16 @@ void LLToast::draw() //-------------------------------------------------------------------------- void LLToast::setVisible(BOOL show) { + if(mIsHidden) + { + // this toast is invisible after fade until its ScreenChannel will allow it + // + // (EXT-1849) according to this bug a toast can be resurrected from + // invisible state if it faded during a teleportation + // then it fades a second time and causes a crash + return; + } + if(show) { setBackgroundOpaque(TRUE); diff --git a/indra/newview/lltoast.h b/indra/newview/lltoast.h index 1826c13ebc..0698c94880 100644 --- a/indra/newview/lltoast.h +++ b/indra/newview/lltoast.h @@ -125,6 +125,8 @@ public: void setCanBeStored(bool can_be_stored) { mCanBeStored = can_be_stored; } // bool getCanBeStored() { return mCanBeStored; } + // set whether this toast considered as hidden or not + void setIsHidden( bool is_toast_hidden ) { mIsHidden = is_toast_hidden; } // Registers signals/callbacks for events @@ -164,6 +166,7 @@ private: bool mCanBeStored; bool mHideBtnEnabled; bool mHideBtnPressed; + bool mIsHidden; // this flag is TRUE when a toast has faded or was hidden with (x) button (EXT-1849) }; } diff --git a/indra/newview/llviewerinventory.cpp b/indra/newview/llviewerinventory.cpp index 57a4117d5d..366e5602bd 100644 --- a/indra/newview/llviewerinventory.cpp +++ b/indra/newview/llviewerinventory.cpp @@ -42,7 +42,6 @@ #include "llconsole.h" #include "llinventorymodel.h" #include "llnotify.h" -#include "llimview.h" #include "llgesturemgr.h" #include "llinventorybridge.h" diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp index 058f44ef57..864cf9d57b 100644 --- a/indra/newview/llviewermenu.cpp +++ b/indra/newview/llviewermenu.cpp @@ -3483,7 +3483,6 @@ void set_god_level(U8 god_level) { U8 old_god_level = gAgent.getGodLevel(); gAgent.setGodLevel( god_level ); - gIMMgr->refresh(); LLViewerParcelMgr::getInstance()->notifyObservers(); // God mode changes sim visibility diff --git a/indra/newview/llviewermessage.cpp b/indra/newview/llviewermessage.cpp index 791ec07349..320f0f83ff 100644 --- a/indra/newview/llviewermessage.cpp +++ b/indra/newview/llviewermessage.cpp @@ -72,7 +72,6 @@ #include "llviewercontrol.h" #include "lldrawpool.h" #include "llfirstuse.h" -#include "llfloateractivespeakers.h" #include "llfloateranimpreview.h" #include "llfloaterbuycurrency.h" #include "llfloaterbuyland.h" @@ -5680,7 +5679,7 @@ void onCovenantLoadComplete(LLVFS *vfs, LLPanelLandCovenant::updateCovenantText(covenant_text); LLFloaterBuyLand::updateCovenantText(covenant_text, asset_uuid); - LLPanelPlaceInfo* panel = dynamic_cast<LLPanelPlaceInfo*>(LLSideTray::getInstance()->showPanel("panel_place_info", LLSD())); + LLPanelPlaceInfo* panel = LLSideTray::getInstance()->findChild<LLPanelPlaceInfo>("panel_place_info"); if (panel) { panel->updateCovenantText(covenant_text); diff --git a/indra/newview/llviewerwindow.cpp b/indra/newview/llviewerwindow.cpp index f141d33729..b0b69fbae6 100644 --- a/indra/newview/llviewerwindow.cpp +++ b/indra/newview/llviewerwindow.cpp @@ -142,7 +142,6 @@ #include "llstatview.h" #include "llsurface.h" #include "llsurfacepatch.h" -#include "llimview.h" #include "lltexlayer.h" #include "lltextbox.h" #include "lltexturecache.h" diff --git a/indra/newview/llvoavatar.cpp b/indra/newview/llvoavatar.cpp index a402aff8ab..f9c95afc31 100644 --- a/indra/newview/llvoavatar.cpp +++ b/indra/newview/llvoavatar.cpp @@ -6400,6 +6400,11 @@ LLBBox LLVOAvatar::getHUDBBox() const ++attachment_iter) { const LLViewerObject* attached_object = (*attachment_iter); + if (attached_object == NULL) + { + llwarns << "HUD attached object is NULL!" << llendl; + continue; + } // initialize bounding box to contain identity orientation and center point for attached object bbox.addPointLocal(attached_object->getPosition()); // add rotated bounding box for attached object diff --git a/indra/newview/llvoicechannel.cpp b/indra/newview/llvoicechannel.cpp new file mode 100644 index 0000000000..96fcf61e62 --- /dev/null +++ b/indra/newview/llvoicechannel.cpp @@ -0,0 +1,872 @@ +/** + * @file llvoicechannel.cpp + * @brief Voice Channel related classes + * + * $LicenseInfo:firstyear=2001&license=viewergpl$ + * + * Copyright (c) 2001-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 "llagent.h" +#include "llfloaterreg.h" +#include "llimview.h" +#include "llnotifications.h" +#include "llpanel.h" +#include "llrecentpeople.h" +#include "llviewercontrol.h" +#include "llvoicechannel.h" + + +LLVoiceChannel::voice_channel_map_t LLVoiceChannel::sVoiceChannelMap; +LLVoiceChannel::voice_channel_map_uri_t LLVoiceChannel::sVoiceChannelURIMap; +LLVoiceChannel* LLVoiceChannel::sCurrentVoiceChannel = NULL; +LLVoiceChannel* LLVoiceChannel::sSuspendedVoiceChannel = NULL; + +BOOL LLVoiceChannel::sSuspended = FALSE; + +// +// Constants +// +const U32 DEFAULT_RETRIES_COUNT = 3; + + +class LLVoiceCallCapResponder : public LLHTTPClient::Responder +{ +public: + LLVoiceCallCapResponder(const LLUUID& session_id) : mSessionID(session_id) {}; + + virtual void error(U32 status, const std::string& reason); // called with bad status codes + virtual void result(const LLSD& content); + +private: + LLUUID mSessionID; +}; + + +void LLVoiceCallCapResponder::error(U32 status, const std::string& reason) +{ + llwarns << "LLVoiceCallCapResponder::error(" + << status << ": " << reason << ")" + << llendl; + LLVoiceChannel* channelp = LLVoiceChannel::getChannelByID(mSessionID); + if ( channelp ) + { + if ( 403 == status ) + { + //403 == no ability + LLNotifications::instance().add( + "VoiceNotAllowed", + channelp->getNotifyArgs()); + } + else + { + LLNotifications::instance().add( + "VoiceCallGenericError", + channelp->getNotifyArgs()); + } + channelp->deactivate(); + } +} + +void LLVoiceCallCapResponder::result(const LLSD& content) +{ + LLVoiceChannel* channelp = LLVoiceChannel::getChannelByID(mSessionID); + if (channelp) + { + //*TODO: DEBUG SPAM + LLSD::map_const_iterator iter; + for(iter = content.beginMap(); iter != content.endMap(); ++iter) + { + llinfos << "LLVoiceCallCapResponder::result got " + << iter->first << llendl; + } + + channelp->setChannelInfo( + content["voice_credentials"]["channel_uri"].asString(), + content["voice_credentials"]["channel_credentials"].asString()); + } +} + +// +// LLVoiceChannel +// +LLVoiceChannel::LLVoiceChannel(const LLUUID& session_id, const std::string& session_name) : + mSessionID(session_id), + mState(STATE_NO_CHANNEL_INFO), + mSessionName(session_name), + mIgnoreNextSessionLeave(FALSE) +{ + mNotifyArgs["VOICE_CHANNEL_NAME"] = mSessionName; + + if (!sVoiceChannelMap.insert(std::make_pair(session_id, this)).second) + { + // a voice channel already exists for this session id, so this instance will be orphaned + // the end result should simply be the failure to make voice calls + llwarns << "Duplicate voice channels registered for session_id " << session_id << llendl; + } + + LLVoiceClient::getInstance()->addObserver(this); +} + +LLVoiceChannel::~LLVoiceChannel() +{ + // Don't use LLVoiceClient::getInstance() here -- this can get called during atexit() time and that singleton MAY have already been destroyed. + if(gVoiceClient) + { + gVoiceClient->removeObserver(this); + } + + sVoiceChannelMap.erase(mSessionID); + sVoiceChannelURIMap.erase(mURI); +} + +void LLVoiceChannel::setChannelInfo( + const std::string& uri, + const std::string& credentials) +{ + setURI(uri); + + mCredentials = credentials; + + if (mState == STATE_NO_CHANNEL_INFO) + { + if (mURI.empty()) + { + LLNotifications::instance().add("VoiceChannelJoinFailed", mNotifyArgs); + llwarns << "Received empty URI for channel " << mSessionName << llendl; + deactivate(); + } + else if (mCredentials.empty()) + { + LLNotifications::instance().add("VoiceChannelJoinFailed", mNotifyArgs); + llwarns << "Received empty credentials for channel " << mSessionName << llendl; + deactivate(); + } + else + { + setState(STATE_READY); + + // if we are supposed to be active, reconnect + // this will happen on initial connect, as we request credentials on first use + if (sCurrentVoiceChannel == this) + { + // just in case we got new channel info while active + // should move over to new channel + activate(); + } + } + } +} + +void LLVoiceChannel::onChange(EStatusType type, const std::string &channelURI, bool proximal) +{ + if (channelURI != mURI) + { + return; + } + + if (type < BEGIN_ERROR_STATUS) + { + handleStatusChange(type); + } + else + { + handleError(type); + } +} + +void LLVoiceChannel::handleStatusChange(EStatusType type) +{ + // status updates + switch(type) + { + case STATUS_LOGIN_RETRY: + //mLoginNotificationHandle = LLNotifyBox::showXml("VoiceLoginRetry")->getHandle(); + LLNotifications::instance().add("VoiceLoginRetry"); + break; + case STATUS_LOGGED_IN: + //if (!mLoginNotificationHandle.isDead()) + //{ + // LLNotifyBox* notifyp = (LLNotifyBox*)mLoginNotificationHandle.get(); + // if (notifyp) + // { + // notifyp->close(); + // } + // mLoginNotificationHandle.markDead(); + //} + break; + case STATUS_LEFT_CHANNEL: + if (callStarted() && !mIgnoreNextSessionLeave && !sSuspended) + { + // if forceably removed from channel + // update the UI and revert to default channel + LLNotifications::instance().add("VoiceChannelDisconnected", mNotifyArgs); + deactivate(); + } + mIgnoreNextSessionLeave = FALSE; + break; + case STATUS_JOINING: + if (callStarted()) + { + setState(STATE_RINGING); + } + break; + case STATUS_JOINED: + if (callStarted()) + { + setState(STATE_CONNECTED); + } + default: + break; + } +} + +// default behavior is to just deactivate channel +// derived classes provide specific error messages +void LLVoiceChannel::handleError(EStatusType type) +{ + deactivate(); + setState(STATE_ERROR); +} + +BOOL LLVoiceChannel::isActive() +{ + // only considered active when currently bound channel matches what our channel + return callStarted() && LLVoiceClient::getInstance()->getCurrentChannel() == mURI; +} + +BOOL LLVoiceChannel::callStarted() +{ + return mState >= STATE_CALL_STARTED; +} + +void LLVoiceChannel::deactivate() +{ + if (mState >= STATE_RINGING) + { + // ignore session leave event + mIgnoreNextSessionLeave = TRUE; + } + + if (callStarted()) + { + setState(STATE_HUNG_UP); + // mute the microphone if required when returning to the proximal channel + if (gSavedSettings.getBOOL("AutoDisengageMic") && sCurrentVoiceChannel == this) + { + gSavedSettings.setBOOL("PTTCurrentlyEnabled", true); + } + } + + if (sCurrentVoiceChannel == this) + { + // default channel is proximal channel + sCurrentVoiceChannel = LLVoiceChannelProximal::getInstance(); + sCurrentVoiceChannel->activate(); + } +} + +void LLVoiceChannel::activate() +{ + if (callStarted()) + { + return; + } + + // deactivate old channel and mark ourselves as the active one + if (sCurrentVoiceChannel != this) + { + // mark as current before deactivating the old channel to prevent + // activating the proximal channel between IM calls + LLVoiceChannel* old_channel = sCurrentVoiceChannel; + sCurrentVoiceChannel = this; + if (old_channel) + { + old_channel->deactivate(); + } + } + + if (mState == STATE_NO_CHANNEL_INFO) + { + // responsible for setting status to active + getChannelInfo(); + } + else + { + setState(STATE_CALL_STARTED); + } +} + +void LLVoiceChannel::getChannelInfo() +{ + // pretend we have everything we need + if (sCurrentVoiceChannel == this) + { + setState(STATE_CALL_STARTED); + } +} + +//static +LLVoiceChannel* LLVoiceChannel::getChannelByID(const LLUUID& session_id) +{ + voice_channel_map_t::iterator found_it = sVoiceChannelMap.find(session_id); + if (found_it == sVoiceChannelMap.end()) + { + return NULL; + } + else + { + return found_it->second; + } +} + +//static +LLVoiceChannel* LLVoiceChannel::getChannelByURI(std::string uri) +{ + voice_channel_map_uri_t::iterator found_it = sVoiceChannelURIMap.find(uri); + if (found_it == sVoiceChannelURIMap.end()) + { + return NULL; + } + else + { + return found_it->second; + } +} + +void LLVoiceChannel::updateSessionID(const LLUUID& new_session_id) +{ + sVoiceChannelMap.erase(sVoiceChannelMap.find(mSessionID)); + mSessionID = new_session_id; + sVoiceChannelMap.insert(std::make_pair(mSessionID, this)); +} + +void LLVoiceChannel::setURI(std::string uri) +{ + sVoiceChannelURIMap.erase(mURI); + mURI = uri; + sVoiceChannelURIMap.insert(std::make_pair(mURI, this)); +} + +void LLVoiceChannel::setState(EState state) +{ + switch(state) + { + case STATE_RINGING: + gIMMgr->addSystemMessage(mSessionID, "ringing", mNotifyArgs); + break; + case STATE_CONNECTED: + gIMMgr->addSystemMessage(mSessionID, "connected", mNotifyArgs); + break; + case STATE_HUNG_UP: + gIMMgr->addSystemMessage(mSessionID, "hang_up", mNotifyArgs); + break; + default: + break; + } + + mState = state; +} + +void LLVoiceChannel::toggleCallWindowIfNeeded(EState state) +{ + if (state == STATE_CONNECTED) + { + LLFloaterReg::showInstance("voice_call", mSessionID); + } + // By checking that current state is CONNECTED we make sure that the call window + // has been shown, hence there's something to hide. This helps when user presses + // the "End call" button right after initiating the call. + // *TODO: move this check to LLFloaterCall? + else if (state == STATE_HUNG_UP && mState == STATE_CONNECTED) + { + LLFloaterReg::hideInstance("voice_call", mSessionID); + } +} + +//static +void LLVoiceChannel::initClass() +{ + sCurrentVoiceChannel = LLVoiceChannelProximal::getInstance(); +} + + +//static +void LLVoiceChannel::suspend() +{ + if (!sSuspended) + { + sSuspendedVoiceChannel = sCurrentVoiceChannel; + sSuspended = TRUE; + } +} + +//static +void LLVoiceChannel::resume() +{ + if (sSuspended) + { + if (gVoiceClient->voiceEnabled()) + { + if (sSuspendedVoiceChannel) + { + sSuspendedVoiceChannel->activate(); + } + else + { + LLVoiceChannelProximal::getInstance()->activate(); + } + } + sSuspended = FALSE; + } +} + + +// +// LLVoiceChannelGroup +// + +LLVoiceChannelGroup::LLVoiceChannelGroup(const LLUUID& session_id, const std::string& session_name) : + LLVoiceChannel(session_id, session_name) +{ + mRetries = DEFAULT_RETRIES_COUNT; + mIsRetrying = FALSE; +} + +void LLVoiceChannelGroup::deactivate() +{ + if (callStarted()) + { + LLVoiceClient::getInstance()->leaveNonSpatialChannel(); + } + LLVoiceChannel::deactivate(); +} + +void LLVoiceChannelGroup::activate() +{ + if (callStarted()) return; + + LLVoiceChannel::activate(); + + if (callStarted()) + { + // we have the channel info, just need to use it now + LLVoiceClient::getInstance()->setNonSpatialChannel( + mURI, + mCredentials); + +#if 0 // *TODO + if (!gAgent.isInGroup(mSessionID)) // ad-hoc channel + { + // Add the party to the list of people with which we've recently interacted. + for (/*people in the chat*/) + LLRecentPeople::instance().add(buddy_id); + } +#endif + } +} + +void LLVoiceChannelGroup::getChannelInfo() +{ + LLViewerRegion* region = gAgent.getRegion(); + if (region) + { + std::string url = region->getCapability("ChatSessionRequest"); + LLSD data; + data["method"] = "call"; + data["session-id"] = mSessionID; + LLHTTPClient::post(url, + data, + new LLVoiceCallCapResponder(mSessionID)); + } +} + +void LLVoiceChannelGroup::setChannelInfo( + const std::string& uri, + const std::string& credentials) +{ + setURI(uri); + + mCredentials = credentials; + + if (mState == STATE_NO_CHANNEL_INFO) + { + if(!mURI.empty() && !mCredentials.empty()) + { + setState(STATE_READY); + + // if we are supposed to be active, reconnect + // this will happen on initial connect, as we request credentials on first use + if (sCurrentVoiceChannel == this) + { + // just in case we got new channel info while active + // should move over to new channel + activate(); + } + } + else + { + //*TODO: notify user + llwarns << "Received invalid credentials for channel " << mSessionName << llendl; + deactivate(); + } + } + else if ( mIsRetrying ) + { + // we have the channel info, just need to use it now + LLVoiceClient::getInstance()->setNonSpatialChannel( + mURI, + mCredentials); + } +} + +void LLVoiceChannelGroup::handleStatusChange(EStatusType type) +{ + // status updates + switch(type) + { + case STATUS_JOINED: + mRetries = 3; + mIsRetrying = FALSE; + default: + break; + } + + LLVoiceChannel::handleStatusChange(type); +} + +void LLVoiceChannelGroup::handleError(EStatusType status) +{ + std::string notify; + switch(status) + { + case ERROR_CHANNEL_LOCKED: + case ERROR_CHANNEL_FULL: + notify = "VoiceChannelFull"; + break; + case ERROR_NOT_AVAILABLE: + //clear URI and credentials + //set the state to be no info + //and activate + if ( mRetries > 0 ) + { + mRetries--; + mIsRetrying = TRUE; + mIgnoreNextSessionLeave = TRUE; + + getChannelInfo(); + return; + } + else + { + notify = "VoiceChannelJoinFailed"; + mRetries = DEFAULT_RETRIES_COUNT; + mIsRetrying = FALSE; + } + + break; + + case ERROR_UNKNOWN: + default: + break; + } + + // notification + if (!notify.empty()) + { + LLNotificationPtr notification = LLNotifications::instance().add(notify, mNotifyArgs); + // echo to im window + gIMMgr->addMessage(mSessionID, LLUUID::null, SYSTEM_FROM, notification->getMessage()); + } + + LLVoiceChannel::handleError(status); +} + +void LLVoiceChannelGroup::setState(EState state) +{ + // HACK: Open/close the call window if needed. + toggleCallWindowIfNeeded(state); + + switch(state) + { + case STATE_RINGING: + if ( !mIsRetrying ) + { + gIMMgr->addSystemMessage(mSessionID, "ringing", mNotifyArgs); + } + + mState = state; + break; + default: + LLVoiceChannel::setState(state); + } +} + +// +// LLVoiceChannelProximal +// +LLVoiceChannelProximal::LLVoiceChannelProximal() : + LLVoiceChannel(LLUUID::null, LLStringUtil::null) +{ + activate(); +} + +BOOL LLVoiceChannelProximal::isActive() +{ + return callStarted() && LLVoiceClient::getInstance()->inProximalChannel(); +} + +void LLVoiceChannelProximal::activate() +{ + if (callStarted()) return; + + LLVoiceChannel::activate(); + + if (callStarted()) + { + // this implicitly puts you back in the spatial channel + LLVoiceClient::getInstance()->leaveNonSpatialChannel(); + } +} + +void LLVoiceChannelProximal::onChange(EStatusType type, const std::string &channelURI, bool proximal) +{ + if (!proximal) + { + return; + } + + if (type < BEGIN_ERROR_STATUS) + { + handleStatusChange(type); + } + else + { + handleError(type); + } +} + +void LLVoiceChannelProximal::handleStatusChange(EStatusType status) +{ + // status updates + switch(status) + { + case STATUS_LEFT_CHANNEL: + // do not notify user when leaving proximal channel + return; + case STATUS_VOICE_DISABLED: + gIMMgr->addSystemMessage(LLUUID::null, "unavailable", mNotifyArgs); + return; + default: + break; + } + LLVoiceChannel::handleStatusChange(status); +} + + +void LLVoiceChannelProximal::handleError(EStatusType status) +{ + std::string notify; + switch(status) + { + case ERROR_CHANNEL_LOCKED: + case ERROR_CHANNEL_FULL: + notify = "ProximalVoiceChannelFull"; + break; + default: + break; + } + + // notification + if (!notify.empty()) + { + LLNotifications::instance().add(notify, mNotifyArgs); + } + + LLVoiceChannel::handleError(status); +} + +void LLVoiceChannelProximal::deactivate() +{ + if (callStarted()) + { + setState(STATE_HUNG_UP); + } +} + + +// +// LLVoiceChannelP2P +// +LLVoiceChannelP2P::LLVoiceChannelP2P(const LLUUID& session_id, const std::string& session_name, const LLUUID& other_user_id) : + LLVoiceChannelGroup(session_id, session_name), + mOtherUserID(other_user_id), + mReceivedCall(FALSE) +{ + // make sure URI reflects encoded version of other user's agent id + setURI(LLVoiceClient::getInstance()->sipURIFromID(other_user_id)); +} + +void LLVoiceChannelP2P::handleStatusChange(EStatusType type) +{ + // status updates + switch(type) + { + case STATUS_LEFT_CHANNEL: + if (callStarted() && !mIgnoreNextSessionLeave && !sSuspended) + { + if (mState == STATE_RINGING) + { + // other user declined call + LLNotifications::instance().add("P2PCallDeclined", mNotifyArgs); + } + else + { + // other user hung up + LLNotifications::instance().add("VoiceChannelDisconnectedP2P", mNotifyArgs); + } + deactivate(); + } + mIgnoreNextSessionLeave = FALSE; + return; + default: + break; + } + + LLVoiceChannel::handleStatusChange(type); +} + +void LLVoiceChannelP2P::handleError(EStatusType type) +{ + switch(type) + { + case ERROR_NOT_AVAILABLE: + LLNotifications::instance().add("P2PCallNoAnswer", mNotifyArgs); + break; + default: + break; + } + + LLVoiceChannel::handleError(type); +} + +void LLVoiceChannelP2P::activate() +{ + if (callStarted()) return; + + LLVoiceChannel::activate(); + + if (callStarted()) + { + // no session handle yet, we're starting the call + if (mSessionHandle.empty()) + { + mReceivedCall = FALSE; + LLVoiceClient::getInstance()->callUser(mOtherUserID); + } + // otherwise answering the call + else + { + LLVoiceClient::getInstance()->answerInvite(mSessionHandle); + + // using the session handle invalidates it. Clear it out here so we can't reuse it by accident. + mSessionHandle.clear(); + } + + // Add the party to the list of people with which we've recently interacted. + LLRecentPeople::instance().add(mOtherUserID); + } +} + +void LLVoiceChannelP2P::getChannelInfo() +{ + // pretend we have everything we need, since P2P doesn't use channel info + if (sCurrentVoiceChannel == this) + { + setState(STATE_CALL_STARTED); + } +} + +// receiving session from other user who initiated call +void LLVoiceChannelP2P::setSessionHandle(const std::string& handle, const std::string &inURI) +{ + BOOL needs_activate = FALSE; + if (callStarted()) + { + // defer to lower agent id when already active + if (mOtherUserID < gAgent.getID()) + { + // pretend we haven't started the call yet, so we can connect to this session instead + deactivate(); + needs_activate = TRUE; + } + else + { + // we are active and have priority, invite the other user again + // under the assumption they will join this new session + mSessionHandle.clear(); + LLVoiceClient::getInstance()->callUser(mOtherUserID); + return; + } + } + + mSessionHandle = handle; + + // The URI of a p2p session should always be the other end's SIP URI. + if(!inURI.empty()) + { + setURI(inURI); + } + else + { + setURI(LLVoiceClient::getInstance()->sipURIFromID(mOtherUserID)); + } + + mReceivedCall = TRUE; + + if (needs_activate) + { + activate(); + } +} + +void LLVoiceChannelP2P::setState(EState state) +{ + // HACK: Open/close the call window if needed. + toggleCallWindowIfNeeded(state); + + // you only "answer" voice invites in p2p mode + // so provide a special purpose message here + if (mReceivedCall && state == STATE_RINGING) + { + gIMMgr->addSystemMessage(mSessionID, "answering", mNotifyArgs); + mState = state; + return; + } + LLVoiceChannel::setState(state); +} diff --git a/indra/newview/llvoicechannel.h b/indra/newview/llvoicechannel.h new file mode 100644 index 0000000000..9966bdd5ab --- /dev/null +++ b/indra/newview/llvoicechannel.h @@ -0,0 +1,168 @@ +/** + * @file llvoicechannel.h + * @brief Voice channel related classes + * + * $LicenseInfo:firstyear=2001&license=viewergpl$ + * + * Copyright (c) 2001-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$ + */ + +#ifndef LL_VOICECHANNEL_H +#define LL_VOICECHANNEL_H + +#include "llhandle.h" +#include "llvoiceclient.h" + +class LLPanel; + +class LLVoiceChannel : public LLVoiceClientStatusObserver +{ +public: + typedef enum e_voice_channel_state + { + STATE_NO_CHANNEL_INFO, + STATE_ERROR, + STATE_HUNG_UP, + STATE_READY, + STATE_CALL_STARTED, + STATE_RINGING, + STATE_CONNECTED + } EState; + + LLVoiceChannel(const LLUUID& session_id, const std::string& session_name); + virtual ~LLVoiceChannel(); + + /*virtual*/ void onChange(EStatusType status, const std::string &channelURI, bool proximal); + + virtual void handleStatusChange(EStatusType status); + virtual void handleError(EStatusType status); + virtual void deactivate(); + virtual void activate(); + virtual void setChannelInfo( + const std::string& uri, + const std::string& credentials); + virtual void getChannelInfo(); + virtual BOOL isActive(); + virtual BOOL callStarted(); + const std::string& getSessionName() const { return mSessionName; } + + const LLUUID getSessionID() { return mSessionID; } + EState getState() { return mState; } + + void updateSessionID(const LLUUID& new_session_id); + const LLSD& getNotifyArgs() { return mNotifyArgs; } + + static LLVoiceChannel* getChannelByID(const LLUUID& session_id); + static LLVoiceChannel* getChannelByURI(std::string uri); + static LLVoiceChannel* getCurrentVoiceChannel() { return sCurrentVoiceChannel; } + static void initClass(); + + static void suspend(); + static void resume(); + +protected: + virtual void setState(EState state); + void toggleCallWindowIfNeeded(EState state); + void setURI(std::string uri); + + std::string mURI; + std::string mCredentials; + LLUUID mSessionID; + EState mState; + std::string mSessionName; + LLSD mNotifyArgs; + BOOL mIgnoreNextSessionLeave; + LLHandle<LLPanel> mLoginNotificationHandle; + + typedef std::map<LLUUID, LLVoiceChannel*> voice_channel_map_t; + static voice_channel_map_t sVoiceChannelMap; + + typedef std::map<std::string, LLVoiceChannel*> voice_channel_map_uri_t; + static voice_channel_map_uri_t sVoiceChannelURIMap; + + static LLVoiceChannel* sCurrentVoiceChannel; + static LLVoiceChannel* sSuspendedVoiceChannel; + static BOOL sSuspended; +}; + +class LLVoiceChannelGroup : public LLVoiceChannel +{ +public: + LLVoiceChannelGroup(const LLUUID& session_id, const std::string& session_name); + + /*virtual*/ void handleStatusChange(EStatusType status); + /*virtual*/ void handleError(EStatusType status); + /*virtual*/ void activate(); + /*virtual*/ void deactivate(); + /*vritual*/ void setChannelInfo( + const std::string& uri, + const std::string& credentials); + /*virtual*/ void getChannelInfo(); + +protected: + virtual void setState(EState state); + +private: + U32 mRetries; + BOOL mIsRetrying; +}; + +class LLVoiceChannelProximal : public LLVoiceChannel, public LLSingleton<LLVoiceChannelProximal> +{ +public: + LLVoiceChannelProximal(); + + /*virtual*/ void onChange(EStatusType status, const std::string &channelURI, bool proximal); + /*virtual*/ void handleStatusChange(EStatusType status); + /*virtual*/ void handleError(EStatusType status); + /*virtual*/ BOOL isActive(); + /*virtual*/ void activate(); + /*virtual*/ void deactivate(); + +}; + +class LLVoiceChannelP2P : public LLVoiceChannelGroup +{ +public: + LLVoiceChannelP2P(const LLUUID& session_id, const std::string& session_name, const LLUUID& other_user_id); + + /*virtual*/ void handleStatusChange(EStatusType status); + /*virtual*/ void handleError(EStatusType status); + /*virtual*/ void activate(); + /*virtual*/ void getChannelInfo(); + + void setSessionHandle(const std::string& handle, const std::string &inURI); + +protected: + virtual void setState(EState state); + +private: + std::string mSessionHandle; + LLUUID mOtherUserID; + BOOL mReceivedCall; +}; + +#endif // LL_VOICECHANNEL_H diff --git a/indra/newview/llvoiceclient.cpp b/indra/newview/llvoiceclient.cpp index 02f63a848b..2834284a9b 100644 --- a/indra/newview/llvoiceclient.cpp +++ b/indra/newview/llvoiceclient.cpp @@ -57,13 +57,13 @@ #include "llagent.h" #include "llcachename.h" #include "llimview.h" // for LLIMMgr -#include "llimpanel.h" // for LLVoiceChannel #include "llparcel.h" #include "llviewerparcelmgr.h" #include "llfirstuse.h" #include "llviewerwindow.h" #include "llviewercamera.h" #include "llvoavatarself.h" +#include "llvoicechannel.h" #include "llfloaterfriends.h" //VIVOX, inorder to refresh communicate panel #include "llfloaterchat.h" // for LLFloaterChat::addChat() diff --git a/indra/newview/skins/default/xui/en/floater_nearby_chat.xml b/indra/newview/skins/default/xui/en/floater_nearby_chat.xml index 0bd4b441c6..d24d1b7064 100644 --- a/indra/newview/skins/default/xui/en/floater_nearby_chat.xml +++ b/indra/newview/skins/default/xui/en/floater_nearby_chat.xml @@ -14,7 +14,7 @@ save_rect="true" title="Nearby Chat" save_visibility="true" - single_instance="true" + single_instance="true" width="320"> <chat_history allow_html="true" diff --git a/indra/newview/skins/default/xui/en/menu_people_friends_view_sort.xml b/indra/newview/skins/default/xui/en/menu_people_friends_view_sort.xml index cc17e9dd4b..eedb4383bb 100644 --- a/indra/newview/skins/default/xui/en/menu_people_friends_view_sort.xml +++ b/indra/newview/skins/default/xui/en/menu_people_friends_view_sort.xml @@ -23,9 +23,14 @@ parameter="sort_status" /> </menu_item_check> <menu_item_separator layout="topleft" /> - <menu_item_call name="view_icons" label="View People Icons"> - <menu_item_call.on_click function="People.Friends.ViewSort.Action" userdata="view_icons" /> - </menu_item_call> + <menu_item_check name="view_icons" label="View People Icons"> + <menu_item_check.on_click + function="People.Friends.ViewSort.Action" + parameter="view_icons" /> + <menu_item_check.on_check + function="CheckControl" + parameter="FriendsListShowIcons" /> + </menu_item_check> <menu_item_call name="organize_offline" label="Organize Offline Friends"> <menu_item_call.on_click function="People.Friends.ViewSort.Action" userdata="organize_offline" /> </menu_item_call> diff --git a/indra/newview/skins/default/xui/en/menu_people_nearby_view_sort.xml b/indra/newview/skins/default/xui/en/menu_people_nearby_view_sort.xml index f91a961388..c002cd078f 100644 --- a/indra/newview/skins/default/xui/en/menu_people_nearby_view_sort.xml +++ b/indra/newview/skins/default/xui/en/menu_people_nearby_view_sort.xml @@ -12,9 +12,14 @@ <menu_item_call.on_click function="People.Nearby.ViewSort.Action" userdata="sort_distance" /> </menu_item_call> <menu_item_separator layout="topleft" /> - <menu_item_call name="view_icons" label="View People Icons"> - <menu_item_call.on_click function="People.Nearby.ViewSort.Action" userdata="view_icons" /> - </menu_item_call> + <menu_item_check name="view_icons" label="View People Icons"> + <menu_item_check.on_click + function="People.Nearby.ViewSort.Action" + parameter="view_icons" /> + <menu_item_check.on_check + function="CheckControl" + parameter="NearbyListShowIcons" /> + </menu_item_check> <menu_item_separator layout="topleft" /> <menu_item_call name="show_blocked_list" label="Show Blocked Residents & Objects"> <menu_item_call.on_click function="SideTray.ShowPanel" userdata="panel_block_list_sidetray" /> diff --git a/indra/newview/skins/default/xui/en/menu_people_recent_view_sort.xml b/indra/newview/skins/default/xui/en/menu_people_recent_view_sort.xml index d09871cff3..cfd6dc78b6 100644 --- a/indra/newview/skins/default/xui/en/menu_people_recent_view_sort.xml +++ b/indra/newview/skins/default/xui/en/menu_people_recent_view_sort.xml @@ -23,9 +23,14 @@ parameter="sort_name" /> </menu_item_check> <menu_item_separator layout="topleft" /> - <menu_item_call name="view_icons" label="View People Icons"> - <menu_item_call.on_click function="People.Recent.ViewSort.Action" userdata="view_icons" /> - </menu_item_call> + <menu_item_check name="view_icons" label="View People Icons"> + <menu_item_check.on_click + function="People.Recent.ViewSort.Action" + parameter="view_icons" /> + <menu_item_check.on_check + function="CheckControl" + parameter="RecentListShowIcons" /> + </menu_item_check> <menu_item_separator layout="topleft" /> <menu_item_call name="show_blocked_list" label="Show Blocked Residents & Objects"> <menu_item_call.on_click function="SideTray.ShowPanel" userdata="panel_block_list_sidetray" /> diff --git a/indra/newview/skins/default/xui/en/menu_place_add_button.xml b/indra/newview/skins/default/xui/en/menu_place_add_button.xml new file mode 100644 index 0000000000..e3a39a1242 --- /dev/null +++ b/indra/newview/skins/default/xui/en/menu_place_add_button.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes" ?> +<menu + layout="topleft" + left="0" + mouse_opaque="false" + name="menu_folder_gear" + visible="false"> + <menu_item_call + label="Add Folder" + layout="topleft" + name="add_folder"> + <on_click + function="Places.LandmarksGear.Add.Action" + parameter="category" /> + <on_enable + function="Places.LandmarksGear.Enable" + parameter="category" /> + </menu_item_call> + <menu_item_call + label="Add Landmark" + layout="topleft" + name="add_landmark"> + <on_click + function="Places.LandmarksGear.Add.Action" + parameter="add_landmark" /> + </menu_item_call> +</menu> diff --git a/indra/newview/skins/default/xui/en/panel_adhoc_control_panel.xml b/indra/newview/skins/default/xui/en/panel_adhoc_control_panel.xml new file mode 100644 index 0000000000..8db745fab7 --- /dev/null +++ b/indra/newview/skins/default/xui/en/panel_adhoc_control_panel.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes" ?> +<panel + name="panel_im_control_panel" + width="146" + height="215" + border="false"> + <avatar_list + color="DkGray2" + follows="left|top|right|bottom" + height="130" + ignore_online_status="true" + layout="topleft" + left="3" + name="speakers_list" + opaque="false" + top="10" + width="140" /> + <button + name="call_btn" + label="Call" + width="90" + height="20" /> + <button + name="end_call_btn" + label="End Call" + width="90" + height="20" + visible="false"/> +</panel> diff --git a/indra/newview/skins/default/xui/en/panel_group_control_panel.xml b/indra/newview/skins/default/xui/en/panel_group_control_panel.xml index 9767a673f6..15b6b2a00d 100644 --- a/indra/newview/skins/default/xui/en/panel_group_control_panel.xml +++ b/indra/newview/skins/default/xui/en/panel_group_control_panel.xml @@ -7,12 +7,14 @@ <avatar_list color="DkGray2" follows="left|top|right|bottom" - height="150" + height="130" + ignore_online_status="true" layout="topleft" left="3" name="speakers_list" + opaque="false" top="10" - width="140"/> + width="140" /> <button name="group_info_btn" label="Group Info" @@ -24,4 +26,10 @@ label="Call" width="90" height="20" /> + <button + name="end_call_btn" + label="End Call" + width="90" + height="20" + visible="false"/> </panel> diff --git a/indra/newview/skins/default/xui/en/panel_group_roles.xml b/indra/newview/skins/default/xui/en/panel_group_roles.xml index 75ded4f249..af1919bd8f 100644 --- a/indra/newview/skins/default/xui/en/panel_group_roles.xml +++ b/indra/newview/skins/default/xui/en/panel_group_roles.xml @@ -486,7 +486,7 @@ things in this group. There's a broad variety of Abilities. </panel> </tab_container> <panel - height="170" + height="190" layout="topleft" follows="left|top" left="10" @@ -556,7 +556,7 @@ things in this group. There's a broad variety of Abilities. </scroll_list> </panel> <panel - height="215" + height="252" layout="topleft" left_delta="0" name="roles_footer" diff --git a/indra/newview/skins/default/xui/en/panel_im_control_panel.xml b/indra/newview/skins/default/xui/en/panel_im_control_panel.xml index 7dc94d1141..dca52def49 100644 --- a/indra/newview/skins/default/xui/en/panel_im_control_panel.xml +++ b/indra/newview/skins/default/xui/en/panel_im_control_panel.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="utf-8" standalone="yes" ?> <panel name="panel_im_control_panel" width="96" - height="215" + height="225" border="false"> <avatar_icon name="avatar_icon" @@ -24,6 +24,13 @@ width="90" height="20" /> + <button + height="20" + label="End Call" + name="end_call_btn" + visible="false" + width="90" /> + <button name="share_btn" label="Share" width="90" diff --git a/indra/newview/skins/default/xui/en/panel_landmarks.xml b/indra/newview/skins/default/xui/en/panel_landmarks.xml index c33f68eaf7..5293043ba7 100644 --- a/indra/newview/skins/default/xui/en/panel_landmarks.xml +++ b/indra/newview/skins/default/xui/en/panel_landmarks.xml @@ -114,22 +114,10 @@ image_disabled="AddItem_Disabled" layout="topleft" left_pad="5" - name="add_landmark_btn" + name="add_btn" picture_style="true" tool_tip="Add new landmark" width="18" /> - <button - follows="bottom|left" - height="18" - image_selected="AddItem_Press" - image_unselected="AddItem_Off" - image_disabled="AddItem_Disabled" - layout="topleft" - left_pad="5" - name="add_folder_btn" - picture_style="true" - tool_tip="Add new folder" - width="18" /> <dnd_button follows="bottom|right" height="18" diff --git a/indra/newview/skins/default/xui/en/strings.xml b/indra/newview/skins/default/xui/en/strings.xml index 2cc57aeaaf..4eacd72a7d 100644 --- a/indra/newview/skins/default/xui/en/strings.xml +++ b/indra/newview/skins/default/xui/en/strings.xml @@ -262,10 +262,6 @@ <string name="TrackYourCamera">Track your camera</string> <string name="ControlYourCamera">Control your camera</string> - <!-- IM --> - <string name="IM_logging_string">-- Instant message logging enabled --</string> - <string name="Unnamed">(Unnamed)</string> - <!-- Sim Access labels --> <string name="SIM_ACCESS_PG">PG</string> <string name="SIM_ACCESS_MATURE">Mature</string> @@ -2881,7 +2877,14 @@ If you continue to receive this message, contact the [SUPPORT_SITE]. Failed to start viewer </string> - <!-- IM system messages --> + <!-- IM system messages --> + <string name="IM_logging_string">-- Instant message logging enabled --</string> + <string name="IM_typing_start_string">[NAME] is typing...</string> + <string name="Unnamed">(Unnamed)</string> + <string name="IM_moderated_chat_label">(Moderated: Voices off by default)</string> + <string name="IM_unavailable_text_label">Text chat is not available for this call.</string> + + <string name="ringing-im"> Joining Voice Chat... </string> diff --git a/indra/newview/skins/default/xui/en/widgets/gesture_combo_box.xml b/indra/newview/skins/default/xui/en/widgets/gesture_combo_box.xml index 89e442baec..ab4ad94089 100644 --- a/indra/newview/skins/default/xui/en/widgets/gesture_combo_box.xml +++ b/indra/newview/skins/default/xui/en/widgets/gesture_combo_box.xml @@ -23,7 +23,8 @@ image_selected="DropDown_Selected" image_disabled="DropDown_Disabled" image_disabled_selected="DropDown_Disabled_Selected" /> - <gesture_combo_box.combo_list bg_writeable_color="MenuDefaultBgColor" /> + <gesture_combo_box.combo_list bg_writeable_color="MenuDefaultBgColor" + scroll_bar_bg_visible="true" /> <gesture_combo_box.combo_editor name="Combo Text Entry" select_on_focus="true" font="SansSerifSmall" /> |