diff options
author | prep@lindenlab.com <prep@lindenlab.com> | 2013-03-29 10:37:58 -0500 |
---|---|---|
committer | prep@lindenlab.com <prep@lindenlab.com> | 2013-03-29 10:37:58 -0500 |
commit | 0f24668b0a834e37a3a8767abc7b0ee51bf23fcd (patch) | |
tree | 4ae685f79c3964e1ac7246641fa9d957b25b5ee1 | |
parent | 8e6d2f8ddc3fe8a1eee3abe99eb5abd3575c5841 (diff) | |
parent | a62a824efb89aff3056d0659ee2c23ad6d15eeaa (diff) |
Merged in Chui beta6+ fixes
31 files changed, 618 insertions, 512 deletions
diff --git a/indra/llui/llchatentry.cpp b/indra/llui/llchatentry.cpp index 9e48dcde7e..6a1b48a08a 100644 --- a/indra/llui/llchatentry.cpp +++ b/indra/llui/llchatentry.cpp @@ -25,6 +25,7 @@ */ #include "linden_common.h" +#include "llscrollcontainer.h" #include "llchatentry.h" @@ -42,7 +43,9 @@ LLChatEntry::LLChatEntry(const Params& p) mHasHistory(p.has_history), mIsExpandable(p.is_expandable), mExpandLinesCount(p.expand_lines_count), - mPrevLinesCount(0) + mPrevLinesCount(0), + mSingleLineMode(false), + mPrevExpandedLineCount(S32_MAX) { // Initialize current history line iterator mCurrentHistoryLine = mLineHistory.begin(); @@ -82,20 +85,23 @@ boost::signals2::connection LLChatEntry::setTextExpandedCallback(const commit_si void LLChatEntry::expandText() { + S32 line_count = mSingleLineMode ? 1 : mExpandLinesCount; + int visible_lines_count = llabs(getVisibleLines(true).first - getVisibleLines(true).second); - bool can_expand = getLineCount() <= mExpandLinesCount; + bool can_changed = getLineCount() <= line_count || line_count < mPrevExpandedLineCount ; + mPrevExpandedLineCount = line_count; // true if pasted text has more lines than expand height limit and expand limit is not reached yet - bool text_pasted = (getLineCount() > mExpandLinesCount) && (visible_lines_count < mExpandLinesCount); + bool text_pasted = (getLineCount() > line_count) && (visible_lines_count < line_count); - if (mIsExpandable && (can_expand || text_pasted) && getLineCount() != mPrevLinesCount) + if (mIsExpandable && (can_changed || text_pasted || mSingleLineMode) && getLineCount() != mPrevLinesCount) { int lines_height = 0; if (text_pasted) { // text is pasted and now mLineInfoList.size() > mExpandLineCounts and mLineInfoList is not empty, - // so lines_height is the sum of the last 'mExpandLinesCount' lines height - lines_height = (mLineInfoList.end() - mExpandLinesCount)->mRect.mTop - mLineInfoList.back().mRect.mBottom; + // so lines_height is the sum of the last 'expanded_line_count' lines height + lines_height = (mLineInfoList.end() - line_count)->mRect.mTop - mLineInfoList.back().mRect.mBottom; } else { @@ -114,6 +120,8 @@ void LLChatEntry::expandText() { (*mTextExpandedSignal)(this, LLSD() ); } + + needsReflow(); } } @@ -235,3 +243,15 @@ BOOL LLChatEntry::handleSpecialKey(const KEY key, const MASK mask) return handled; } + +void LLChatEntry::enableSingleLineMode(bool single_line_mode) +{ + if (mScroller) + { + mScroller->setSize(single_line_mode ? 0 : -1); + } + + mSingleLineMode = single_line_mode; + mPrevLinesCount = -1; + setWordWrap(!single_line_mode); +} diff --git a/indra/llui/llchatentry.h b/indra/llui/llchatentry.h index 49181c8d78..49c8d21450 100644 --- a/indra/llui/llchatentry.h +++ b/indra/llui/llchatentry.h @@ -65,6 +65,7 @@ public: /*virtual*/ void onFocusReceived(); /*virtual*/ void onFocusLost(); + void enableSingleLineMode(bool single_line_mode); boost::signals2::connection setTextExpandedCallback(const commit_signal_t::slot_type& cb); private: @@ -95,9 +96,11 @@ private: line_history_t mLineHistory; // line history storage bool mHasHistory; // flag for enabled/disabled line history bool mIsExpandable; + bool mSingleLineMode; - int mExpandLinesCount; - int mPrevLinesCount; + S32 mExpandLinesCount; + S32 mPrevLinesCount; + S32 mPrevExpandedLineCount; }; #endif /* LLCHATENTRY_H_ */ diff --git a/indra/llui/llscrollbar.cpp b/indra/llui/llscrollbar.cpp index 5d3bf7a670..13887cbe73 100644 --- a/indra/llui/llscrollbar.cpp +++ b/indra/llui/llscrollbar.cpp @@ -642,3 +642,8 @@ void LLScrollbar::onLineDownBtnPressed( const LLSD& data ) { changeLine( mStepSize, TRUE ); } + +void LLScrollbar::setThickness(S32 thickness) +{ + mThickness = thickness < 0 ? LLUI::sSettingGroups["config"]->getS32("UIScrollbarSize") : thickness; +} diff --git a/indra/llui/llscrollbar.h b/indra/llui/llscrollbar.h index ff74f753b9..21fd2d631e 100644 --- a/indra/llui/llscrollbar.h +++ b/indra/llui/llscrollbar.h @@ -124,6 +124,9 @@ public: void onLineUpBtnPressed(const LLSD& data); void onLineDownBtnPressed(const LLSD& data); + + S32 getThickness() const { return mThickness; } + void setThickness(S32 thickness); private: void updateThumbRect(); diff --git a/indra/llui/llscrollcontainer.cpp b/indra/llui/llscrollcontainer.cpp index 2fd187a526..cbcce0ece5 100644 --- a/indra/llui/llscrollcontainer.cpp +++ b/indra/llui/llscrollcontainer.cpp @@ -73,7 +73,8 @@ LLScrollContainer::Params::Params() hide_scrollbar("hide_scrollbar"), min_auto_scroll_rate("min_auto_scroll_rate", 100), max_auto_scroll_rate("max_auto_scroll_rate", 1000), - reserve_scroll_corner("reserve_scroll_corner", false) + reserve_scroll_corner("reserve_scroll_corner", false), + size("size", -1) {} @@ -88,9 +89,12 @@ LLScrollContainer::LLScrollContainer(const LLScrollContainer::Params& p) mReserveScrollCorner(p.reserve_scroll_corner), mMinAutoScrollRate(p.min_auto_scroll_rate), mMaxAutoScrollRate(p.max_auto_scroll_rate), - mScrolledView(NULL) + mScrolledView(NULL), + mSize(p.size) { - static LLUICachedControl<S32> scrollbar_size ("UIScrollbarSize", 0); + static LLUICachedControl<S32> scrollbar_size_control ("UIScrollbarSize", 0); + S32 scrollbar_size = (mSize == -1 ? scrollbar_size_control : mSize); + LLRect border_rect( 0, getRect().getHeight(), getRect().getWidth(), 0 ); LLViewBorder::Params params; params.name("scroll border"); @@ -276,7 +280,6 @@ BOOL LLScrollContainer::handleDragAndDrop(S32 x, S32 y, MASK mask, EAcceptance* accept, std::string& tooltip_msg) { - static LLUICachedControl<S32> scrollbar_size ("UIScrollbarSize", 0); // Scroll folder view if needed. Never accepts a drag or drop. *accept = ACCEPT_NO; BOOL handled = autoScroll(x, y); @@ -292,7 +295,8 @@ BOOL LLScrollContainer::handleDragAndDrop(S32 x, S32 y, MASK mask, bool LLScrollContainer::autoScroll(S32 x, S32 y) { - static LLUICachedControl<S32> scrollbar_size ("UIScrollbarSize", 0); + static LLUICachedControl<S32> scrollbar_size_control ("UIScrollbarSize", 0); + S32 scrollbar_size = (mSize == -1 ? scrollbar_size_control : mSize); bool scrolling = false; if( mScrollbar[HORIZONTAL]->getVisible() || mScrollbar[VERTICAL]->getVisible() ) @@ -365,7 +369,9 @@ bool LLScrollContainer::autoScroll(S32 x, S32 y) void LLScrollContainer::calcVisibleSize( S32 *visible_width, S32 *visible_height, BOOL* show_h_scrollbar, BOOL* show_v_scrollbar ) const { const LLRect& doc_rect = getScrolledViewRect(); - static LLUICachedControl<S32> scrollbar_size ("UIScrollbarSize", 0); + static LLUICachedControl<S32> scrollbar_size_control ("UIScrollbarSize", 0); + S32 scrollbar_size = (mSize == -1 ? scrollbar_size_control : mSize); + S32 doc_width = doc_rect.getWidth(); S32 doc_height = doc_rect.getHeight(); @@ -406,7 +412,9 @@ void LLScrollContainer::calcVisibleSize( S32 *visible_width, S32 *visible_height void LLScrollContainer::draw() { - static LLUICachedControl<S32> scrollbar_size ("UIScrollbarSize", 0); + static LLUICachedControl<S32> scrollbar_size_control ("UIScrollbarSize", 0); + S32 scrollbar_size = (mSize == -1 ? scrollbar_size_control : mSize); + if (mAutoScrolling) { // add acceleration to autoscroll @@ -515,7 +523,9 @@ void LLScrollContainer::updateScroll() { return; } - static LLUICachedControl<S32> scrollbar_size ("UIScrollbarSize", 0); + static LLUICachedControl<S32> scrollbar_size_control ("UIScrollbarSize", 0); + S32 scrollbar_size = (mSize == -1 ? scrollbar_size_control : mSize); + LLRect doc_rect = mScrolledView->getRect(); S32 doc_width = doc_rect.getWidth(); S32 doc_height = doc_rect.getHeight(); @@ -716,3 +726,9 @@ S32 LLScrollContainer::getBorderWidth() const return 0; } +void LLScrollContainer::setSize(S32 size) +{ + mSize = size; + mScrollbar[VERTICAL]->setThickness(size); + mScrollbar[HORIZONTAL]->setThickness(size); +} diff --git a/indra/llui/llscrollcontainer.h b/indra/llui/llscrollcontainer.h index d87c95b3d7..4eb43539b8 100644 --- a/indra/llui/llscrollcontainer.h +++ b/indra/llui/llscrollcontainer.h @@ -68,6 +68,7 @@ public: max_auto_scroll_rate; Optional<LLUIColor> bg_color; Optional<LLScrollbar::callback_t> scroll_callback; + Optional<S32> size; Params(); }; @@ -116,6 +117,9 @@ public: bool autoScroll(S32 x, S32 y); + S32 getSize() const { return mSize; } + void setSize(S32 thickness); + protected: LLView* mScrolledView; diff --git a/indra/llui/llscrolllistctrl.cpp b/indra/llui/llscrolllistctrl.cpp index 8b9fb47d5c..7f04c92b27 100644 --- a/indra/llui/llscrolllistctrl.cpp +++ b/indra/llui/llscrolllistctrl.cpp @@ -1801,6 +1801,9 @@ BOOL LLScrollListCtrl::handleRightMouseDown(S32 x, S32 y, MASK mask) // (N.B. callbacks don't take const refs as id is local scope) bool is_group = (mContextMenuType == MENU_GROUP); LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; + registrar.add("Url.ShowProfile", boost::bind(&LLScrollListCtrl::showProfile, id, is_group)); + registrar.add("Url.SendIM", boost::bind(&LLScrollListCtrl::sendIM, id)); + registrar.add("Url.AddFriend", boost::bind(&LLScrollListCtrl::addFriend, id)); registrar.add("Url.Execute", boost::bind(&LLScrollListCtrl::showNameDetails, id, is_group)); registrar.add("Url.CopyLabel", boost::bind(&LLScrollListCtrl::copyNameToClipboard, id, is_group)); registrar.add("Url.CopyUrl", boost::bind(&LLScrollListCtrl::copySLURLToClipboard, id, is_group)); @@ -1821,11 +1824,33 @@ BOOL LLScrollListCtrl::handleRightMouseDown(S32 x, S32 y, MASK mask) return FALSE; } -void LLScrollListCtrl::showNameDetails(std::string id, bool is_group) +void LLScrollListCtrl::showProfile(std::string id, bool is_group) { // show the resident's profile or the group profile std::string sltype = is_group ? "group" : "agent"; std::string slurl = "secondlife:///app/" + sltype + "/" + id + "/about"; + LLUrlAction::showProfile(slurl); +} + +void LLScrollListCtrl::sendIM(std::string id) +{ + // send im to the resident + std::string slurl = "secondlife:///app/agent/" + id + "/about"; + LLUrlAction::sendIM(slurl); +} + +void LLScrollListCtrl::addFriend(std::string id) +{ + // add resident to friends list + std::string slurl = "secondlife:///app/agent/" + id + "/about"; + LLUrlAction::addFriend(slurl); +} + +void LLScrollListCtrl::showNameDetails(std::string id, bool is_group) +{ + // open the resident's details or the group details + std::string sltype = is_group ? "group" : "agent"; + std::string slurl = "secondlife:///app/" + sltype + "/" + id + "/about"; LLUrlAction::clickAction(slurl); } diff --git a/indra/llui/llscrolllistctrl.h b/indra/llui/llscrolllistctrl.h index 38450b6313..8fa06cc499 100644 --- a/indra/llui/llscrolllistctrl.h +++ b/indra/llui/llscrolllistctrl.h @@ -430,6 +430,9 @@ private: BOOL setSort(S32 column, BOOL ascending); S32 getLinesPerPage(); + static void showProfile(std::string id, bool is_group); + static void sendIM(std::string id); + static void addFriend(std::string id); static void showNameDetails(std::string id, bool is_group); static void copyNameToClipboard(std::string id, bool is_group); static void copySLURLToClipboard(std::string id, bool is_group); diff --git a/indra/llui/lltextbase.cpp b/indra/llui/lltextbase.cpp index 409f1c7f10..e70992129a 100644 --- a/indra/llui/lltextbase.cpp +++ b/indra/llui/lltextbase.cpp @@ -3201,7 +3201,23 @@ S32 LLNormalTextSegment::getNumChars(S32 num_pixels, S32 segment_offset, S32 lin LLFontGL::EWordWrapStyle word_wrap_style = (line_offset == 0) ? LLFontGL::WORD_BOUNDARY_IF_POSSIBLE : LLFontGL::ONLY_WORD_BOUNDARIES; - S32 num_chars = mStyle->getFont()->maxDrawableChars(text.c_str() + segment_offset + mStart, + + + LLWString offsetString(text.c_str() + segment_offset + mStart); + + if(getLength() < segment_offset + mStart) + { + llerrs << "getLength() < segment_offset + mStart\t getLength()\t" << getLength() << "\tsegment_offset:\t" + << segment_offset << "\tmStart:\t" << mStart << "\tsegments\t" << mEditor.mSegments.size() << "\tmax_chars\t" << max_chars << llendl; + } + + if(offsetString.length() + 1 < max_chars) + { + llerrs << "offsetString.length() + 1 < max_chars\t max_chars:\t" << max_chars << "\toffsetString.length():\t" << offsetString.length() + << getLength() << "\tsegment_offset:\t" << segment_offset << "\tmStart:\t" << mStart << "\tsegments\t" << mEditor.mSegments.size() << llendl; + } + + S32 num_chars = mStyle->getFont()->maxDrawableChars(offsetString.c_str(), (F32)num_pixels, max_chars, word_wrap_style); @@ -3474,3 +3490,7 @@ F32 LLImageTextSegment::draw(S32 start, S32 end, S32 selection_start, S32 select return 0.0; } +void LLTextBase::setWordWrap(bool wrap) +{ + mWordWrap = wrap; +} diff --git a/indra/llui/lltextbase.h b/indra/llui/lltextbase.h index ad566a36d3..20a73387b5 100644 --- a/indra/llui/lltextbase.h +++ b/indra/llui/lltextbase.h @@ -41,6 +41,7 @@ #include <boost/signals2.hpp> +class LLScrollContainer; class LLContextMenu; class LLUrlMatch; @@ -434,6 +435,9 @@ public: virtual void appendWidget(const LLInlineViewSegment::Params& params, const std::string& text, bool allow_undo); boost::signals2::connection setURLClickedCallback(const commit_signal_t::slot_type& cb); + void setWordWrap(bool wrap); + LLScrollContainer* getScrollContainer() const { return mScroller; } + protected: // helper structs struct compare_bottom; @@ -634,7 +638,7 @@ protected: // support widgets LLContextMenu* mPopupMenu; LLView* mDocumentView; - class LLScrollContainer* mScroller; + LLScrollContainer* mScroller; // transient state S32 mReflowIndex; // index at which to start reflow. S32_MAX indicates no reflow needed. diff --git a/indra/llui/lltexteditor.cpp b/indra/llui/lltexteditor.cpp index 3dc1b99edb..834f213097 100644 --- a/indra/llui/lltexteditor.cpp +++ b/indra/llui/lltexteditor.cpp @@ -956,12 +956,18 @@ S32 LLTextEditor::insert(S32 pos, const LLWString &wstr, bool group_with_next_op S32 LLTextEditor::remove(S32 pos, S32 length, bool group_with_next_op) { S32 end_pos = getEditableIndex(pos + length, true); + BOOL removedChar = FALSE; segment_vec_t segments_to_remove; // store text segments getSegmentsInRange(segments_to_remove, pos, pos + length, false); + + if(pos <= end_pos) + { + removedChar = execute( new TextCmdRemove( pos, group_with_next_op, end_pos - pos, segments_to_remove ) ); + } - return execute( new TextCmdRemove( pos, group_with_next_op, end_pos - pos, segments_to_remove ) ); + return removedChar; } S32 LLTextEditor::overwriteChar(S32 pos, llwchar wc) diff --git a/indra/llui/lltoolbar.cpp b/indra/llui/lltoolbar.cpp index f9703256c4..3d9f5cbbc2 100644 --- a/indra/llui/lltoolbar.cpp +++ b/indra/llui/lltoolbar.cpp @@ -1055,10 +1055,9 @@ BOOL LLToolBar::handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop, // Convert drag position into insert position and rank if (!isReadOnly() && handled && !drop) { - LLInventoryItem* inv_item = (LLInventoryItem*)cargo_data; - LLAssetType::EType type = inv_item->getType(); - if (type == LLAssetType::AT_WIDGET) + if (cargo_type == DAD_WIDGET) { + LLInventoryItem* inv_item = (LLInventoryItem*)cargo_data; LLCommandId dragged_command(inv_item->getUUID()); int orig_rank = getRankFromPosition(dragged_command); mDragRank = getRankFromPosition(x, y); diff --git a/indra/newview/app_settings/settings_per_account.xml b/indra/newview/app_settings/settings_per_account.xml index ada374f892..590f41283b 100644 --- a/indra/newview/app_settings/settings_per_account.xml +++ b/indra/newview/app_settings/settings_per_account.xml @@ -292,6 +292,17 @@ <key>Value</key> <integer>1</integer> </map> + <key>NearbyChatIsNotCollapsed</key> + <map> + <key>Comment</key> + <string>Saving expanded/collapsed state of the nearby chat between sessions</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>Boolean</string> + <key>Value</key> + <integer>0</integer> + </map> <key>ShowFavoritesOnLogin</key> <map> <key>Comment</key> diff --git a/indra/newview/llchiclet.cpp b/indra/newview/llchiclet.cpp index f50a19761a..88884042d4 100644 --- a/indra/newview/llchiclet.cpp +++ b/indra/newview/llchiclet.cpp @@ -174,6 +174,7 @@ void LLNotificationChiclet::onMenuItemClicked(const LLSD& user_data) if("close all" == action) { LLNotificationWellWindow::getInstance()->closeAll(); + LLIMWellWindow::getInstance()->closeAll(); } } diff --git a/indra/newview/llconversationview.cpp b/indra/newview/llconversationview.cpp index 956abcd586..b6c53e5e30 100755 --- a/indra/newview/llconversationview.cpp +++ b/indra/newview/llconversationview.cpp @@ -120,7 +120,7 @@ void LLConversationViewSession::setFlashState(bool flash_state) void LLConversationViewSession::startFlashing() { - if (mFlashStateOn && !mFlashStarted) + if (isInVisibleChain() && mFlashStateOn && !mFlashStarted) { mFlashStarted = true; mFlashTimer->startFlashing(); diff --git a/indra/newview/lldonotdisturbnotificationstorage.cpp b/indra/newview/lldonotdisturbnotificationstorage.cpp index be20adeb8a..82affcf068 100644 --- a/indra/newview/lldonotdisturbnotificationstorage.cpp +++ b/indra/newview/lldonotdisturbnotificationstorage.cpp @@ -132,6 +132,8 @@ void LLDoNotDisturbNotificationStorage::loadNotifications() { LLFastTimer _(FTM_LOAD_DND_NOTIFICATIONS); + LL_INFOS("LLDoNotDisturbNotificationStorage") << "start loading notifications" << LL_ENDL; + LLSD input; if (!readNotifications(input) ||input.isUndefined()) { @@ -225,6 +227,8 @@ void LLDoNotDisturbNotificationStorage::loadNotifications() //writes out empty .xml file (since LLCommunicationChannel::mHistory is empty) saveNotifications(); + + LL_INFOS("LLDoNotDisturbNotificationStorage") << "finished loading notifications" << LL_ENDL; } void LLDoNotDisturbNotificationStorage::updateNotifications() diff --git a/indra/newview/llfloaterimcontainer.cpp b/indra/newview/llfloaterimcontainer.cpp index 5e0cd8ef78..7296ec3ced 100644 --- a/indra/newview/llfloaterimcontainer.cpp +++ b/indra/newview/llfloaterimcontainer.cpp @@ -626,6 +626,12 @@ void LLFloaterIMContainer::setVisible(BOOL visible) LLMultiFloater::setVisible(visible); } +void LLFloaterIMContainer::setVisibleAndFrontmost(BOOL take_focus, const LLSD& key) +{ + LLMultiFloater::setVisibleAndFrontmost(take_focus, key); + selectConversationPair(getSelectedSession(), false, take_focus); +} + void LLFloaterIMContainer::updateResizeLimits() { LLMultiFloater::updateResizeLimits(); @@ -1331,7 +1337,10 @@ void LLFloaterIMContainer::showConversation(const LLUUID& session_id) selectConversationPair(session_id, true); LLFloaterIMSessionTab* session_floater = LLFloaterIMSessionTab::findConversation(session_id); - session_floater->restoreFloater(); + if (session_floater) + { + session_floater->restoreFloater(); + } } void LLFloaterIMContainer::clearAllFlashStates() @@ -1963,10 +1972,13 @@ bool LLFloaterIMContainer::selectNextorPreviousConversation(bool select_next, bo void LLFloaterIMContainer::expandConversation() { - LLConversationViewSession* widget = dynamic_cast<LLConversationViewSession*>(get_ptr_in_map(mConversationsWidgets,getSelectedSession())); - if (widget) + if(!mConversationsPane->isCollapsed()) { - widget->setOpen(!widget->isOpen()); + LLConversationViewSession* widget = dynamic_cast<LLConversationViewSession*>(get_ptr_in_map(mConversationsWidgets,getSelectedSession())); + if (widget) + { + widget->setOpen(!widget->isOpen()); + } } } diff --git a/indra/newview/llfloaterimcontainer.h b/indra/newview/llfloaterimcontainer.h index 2cbc1e99f9..52b672241f 100644 --- a/indra/newview/llfloaterimcontainer.h +++ b/indra/newview/llfloaterimcontainer.h @@ -60,6 +60,7 @@ public: /*virtual*/ void onOpen(const LLSD& key); /*virtual*/ void draw(); /*virtual*/ void setVisible(BOOL visible); + /*virtual*/ void setVisibleAndFrontmost(BOOL take_focus=TRUE, const LLSD& key = LLSD()); /*virtual*/ void updateResizeLimits(); void onCloseFloater(LLUUID& id); diff --git a/indra/newview/llfloaterimnearbychat.cpp b/indra/newview/llfloaterimnearbychat.cpp index b287950c21..d86e1b3fd7 100644 --- a/indra/newview/llfloaterimnearbychat.cpp +++ b/indra/newview/llfloaterimnearbychat.cpp @@ -283,6 +283,11 @@ void LLFloaterIMNearbyChat::onTearOffClicked() void LLFloaterIMNearbyChat::onOpen(const LLSD& key) { LLFloaterIMSessionTab::onOpen(key); + if(!isMessagePaneExpanded()) + { + restoreFloater(); + onCollapseToLine(this); + } showTranslationCheckbox(LLTranslate::isTranslationConfigured()); } @@ -322,11 +327,8 @@ void LLFloaterIMNearbyChat::onChatFontChange(LLFontGL* fontp) void LLFloaterIMNearbyChat::show() { - if (isChatMultiTab()) - { openFloater(getKey()); } -} bool LLFloaterIMNearbyChat::isChatVisible() const { @@ -480,11 +482,14 @@ void LLFloaterIMNearbyChat::onChatBoxKeystroke() if (LLGestureMgr::instance().matchPrefix(utf8_trigger, &utf8_out_str)) { std::string rest_of_match = utf8_out_str.substr(utf8_trigger.size()); - mInputEditor->setText(utf8_trigger + rest_of_match); // keep original capitalization for user-entered part + if (!rest_of_match.empty()) + { + mInputEditor->setText(utf8_trigger + rest_of_match); // keep original capitalization for user-entered part - // Select to end of line, starting from the character - // after the last one the user typed. - mInputEditor->selectNext(rest_of_match, false); + // Select to end of line, starting from the character + // after the last one the user typed. + mInputEditor->selectNext(rest_of_match, false); + } } else if (matchChatTypeTrigger(utf8_trigger, &utf8_out_str)) { @@ -741,15 +746,14 @@ void LLFloaterIMNearbyChat::startChat(const char* line) { if(!nearby_chat->isTornOff()) { - nearby_chat->show(); + LLFloaterIMContainer::getInstance()->selectConversation(LLUUID(NULL)); } if(nearby_chat->isMinimized()) { nearby_chat->setMinimized(false); } - nearby_chat->setVisible(TRUE); + nearby_chat->show(); nearby_chat->setFocus(TRUE); - nearby_chat->mInputEditor->setFocus(TRUE); if (line) { diff --git a/indra/newview/llfloaterimnearbychathandler.cpp b/indra/newview/llfloaterimnearbychathandler.cpp index 7afcf288ce..9ce5e12897 100644 --- a/indra/newview/llfloaterimnearbychathandler.cpp +++ b/indra/newview/llfloaterimnearbychathandler.cpp @@ -559,9 +559,7 @@ void LLFloaterIMNearbyChatHandler::processChat(const LLChat& chat_msg, LLFloaterIMContainer* im_box = LLFloaterReg::getTypedInstance<LLFloaterIMContainer>("im_container"); - if(( nearby_chat->hasFocus() - || im_box->hasFocus() - || ( chat_msg.mSourceType == CHAT_SOURCE_AGENT + if(( ( chat_msg.mSourceType == CHAT_SOURCE_AGENT && gSavedSettings.getBOOL("UseChatBubbles") ) || mChannel.isDead() || !mChannel.get()->getShowToasts() ) @@ -606,17 +604,20 @@ void LLFloaterIMNearbyChatHandler::processChat(const LLChat& chat_msg, toast_msg = chat_msg.mText; } - //Don't show nearby toast, if conversation is visible but not focused - LLFloaterIMSessionTab* session_floater = LLFloaterIMSessionTab::getConversation(LLUUID()); - if (session_floater && session_floater->isMessagePaneExpanded() - && session_floater->isInVisibleChain() && !session_floater->isMinimized() - && !(session_floater->getHost() && session_floater->getHost()->isMinimized())) + //Don't show nearby toast, if conversation is visible and selected + if ((nearby_chat->hasFocus()) || + ((im_box->getSelectedSession().isNull() && + ((LLFloater::isVisible(im_box) && !im_box->isMinimized() && im_box->isFrontmost()) + || (LLFloater::isVisible(nearby_chat) && !nearby_chat->isMinimized() && nearby_chat->isFrontmost()))))) { - return; + if(nearby_chat->isMessagePaneExpanded()) + { + return; + } } //Will show toast when chat preference is set - if((gSavedSettings.getString("NotificationNearbyChatOptions") == "toast") || !session_floater->isMessagePaneExpanded()) + if((gSavedSettings.getString("NotificationNearbyChatOptions") == "toast") || !nearby_chat->isMessagePaneExpanded()) { // Add a nearby chat toast. LLUUID id; diff --git a/indra/newview/llfloaterimsession.cpp b/indra/newview/llfloaterimsession.cpp index 0b1e47dbae..aaefd4aac7 100644 --- a/indra/newview/llfloaterimsession.cpp +++ b/indra/newview/llfloaterimsession.cpp @@ -618,6 +618,8 @@ void LLFloaterIMSession::onClose(bool app_quitting) // Last change: // EXT-3516 X Button should end IM session, _ button should hide gIMMgr->leaveSession(mSessionID); + // *TODO: Study why we need to restore the floater before we close it. + // Might be because we want to save some state data in some clean open state. LLFloaterIMSessionTab::restoreFloater(); // Clean up the conversation *after* the session has been ended LLFloaterIMSessionTab::onClose(app_quitting); diff --git a/indra/newview/llfloaterimsessiontab.cpp b/indra/newview/llfloaterimsessiontab.cpp index 5df1a382cd..ce6e639305 100644 --- a/indra/newview/llfloaterimsessiontab.cpp +++ b/indra/newview/llfloaterimsessiontab.cpp @@ -30,6 +30,7 @@ #include "llfloaterimsessiontab.h" #include "llagent.h" +#include "llagentcamera.h" #include "llavataractions.h" #include "llchatentry.h" #include "llchathistory.h" @@ -57,11 +58,14 @@ LLFloaterIMSessionTab::LLFloaterIMSessionTab(const LLSD& session_id) , mSpeakingIndicator(NULL) , mChatHistory(NULL) , mInputEditor(NULL) - , mInputEditorTopPad(0) + , mInputEditorPad(0) , mRefreshTimer(new LLTimer()) , mIsHostAttached(false) , mHasVisibleBeenInitialized(false) , mIsParticipantListExpanded(true) + , mChatLayoutPanel(NULL) + , mInputPanels(NULL) + , mChatLayoutPanelHeight(0) { setAutoFocus(FALSE); mSession = LLIMModel::getInstance()->findIMSession(mSessionID); @@ -125,8 +129,18 @@ void LLFloaterIMSessionTab::setVisible(BOOL visible) if(visible && !mHasVisibleBeenInitialized) { mHasVisibleBeenInitialized = true; - LLFloaterReg::getTypedInstance<LLFloaterIMContainer>("im_container")->setVisible(true); + if(!gAgentCamera.cameraMouselook()) + { + LLFloaterReg::getTypedInstance<LLFloaterIMContainer>("im_container")->setVisible(true); + } LLFloaterIMSessionTab::addToHost(mSessionID); + LLFloaterIMSessionTab* conversp = LLFloaterIMSessionTab::getConversation(mSessionID); + + if (conversp && conversp->isNearbyChat() && gSavedPerAccountSettings.getBOOL("NearbyChatIsNotCollapsed")) + { + onCollapseToLine(this); + } + mInputButtonPanel->setVisible(isTornOff()); } LLTransientDockableFloater::setVisible(visible); @@ -190,12 +204,29 @@ void LLFloaterIMSessionTab::addToHost(const LLUUID& session_id) } } +void LLFloaterIMSessionTab::assignResizeLimits() +{ + bool is_participants_pane_collapsed = mParticipantListPanel->isCollapsed(); + + // disable a layoutstack's functionality when participant list panel is collapsed + mRightPartPanel->setIgnoreReshape(is_participants_pane_collapsed); + + S32 participants_pane_target_width = is_participants_pane_collapsed? + 0 : (mParticipantListPanel->getRect().getWidth() + LLPANEL_BORDER_WIDTH); + + S32 new_min_width = participants_pane_target_width + mRightPartPanel->getExpandedMinDim() + mFloaterExtraWidth; + + setResizeLimits(new_min_width, getMinHeight()); + + this->mParticipantListAndHistoryStack->updateLayout(); +} + BOOL LLFloaterIMSessionTab::postBuild() { BOOL result; mBodyStack = getChild<LLLayoutStack>("main_stack"); - + mParticipantListAndHistoryStack = getChild<LLLayoutStack>("im_panels"); mCloseBtn = getChild<LLButton>("close_btn"); mCloseBtn->setCommitCallback(boost::bind(&LLFloater::onClickClose, this)); @@ -212,6 +243,8 @@ BOOL LLFloaterIMSessionTab::postBuild() mGearBtn = getChild<LLButton>("gear_btn"); mParticipantListPanel = getChild<LLLayoutPanel>("speakers_list_panel"); + mRightPartPanel = getChild<LLLayoutPanel>("right_part_holder"); + mToolbarPanel = getChild<LLLayoutPanel>("toolbar_panel"); mContentPanel = getChild<LLLayoutPanel>("body_panel"); mInputButtonPanel = getChild<LLLayoutPanel>("input_button_layout_panel"); @@ -232,12 +265,17 @@ BOOL LLFloaterIMSessionTab::postBuild() mChatHistory = getChild<LLChatHistory>("chat_history"); mInputEditor = getChild<LLChatEntry>("chat_editor"); - mInputEditor->setTextExpandedCallback(boost::bind(&LLFloaterIMSessionTab::reshapeChatHistory, this)); + + mChatLayoutPanel = getChild<LLLayoutPanel>("chat_layout_panel"); + mInputPanels = getChild<LLLayoutStack>("input_panels"); + + mInputEditor->setTextExpandedCallback(boost::bind(&LLFloaterIMSessionTab::reshapeChatLayoutPanel, this)); mInputEditor->setCommitOnFocusLost( FALSE ); mInputEditor->setPassDelete(TRUE); mInputEditor->setFont(LLViewerChat::getChatFont()); - mInputEditorTopPad = mChatHistory->getRect().mBottom - mInputEditor->getRect().mTop; + mChatLayoutPanelHeight = mChatLayoutPanel->getRect().getHeight(); + mInputEditorPad = mChatLayoutPanelHeight - mInputEditor->getRect().getHeight(); setOpenPositioning(LLFloaterEnums::POSITIONING_RELATIVE); @@ -288,6 +326,15 @@ BOOL LLFloaterIMSessionTab::postBuild() LLFloaterIMSessionTab::onSlide(this); } + // The resize limits for LLFloaterIMSessionTab should be updated, based on current values of width of conversation and message panels + mParticipantListPanel->getResizeBar()->setResizeListener(boost::bind(&LLFloaterIMSessionTab::assignResizeLimits, this)); + mFloaterExtraWidth = + getRect().getWidth() + - mParticipantListAndHistoryStack->getRect().getWidth() + - (mParticipantListPanel->isCollapsed()? 0 : LLPANEL_BORDER_WIDTH); + + assignResizeLimits(); + return result; } @@ -319,7 +366,7 @@ void LLFloaterIMSessionTab::draw() // Restart the refresh timer mRefreshTimer->setTimerExpirySec(REFRESH_INTERVAL); } - + LLTransientDockableFloater::draw(); } @@ -660,12 +707,13 @@ void LLFloaterIMSessionTab::updateHeaderAndToolbar() && mIsParticipantListExpanded && !mIsP2PChat; - mParticipantListPanel->setVisible(is_participant_list_visible); - + mParticipantListAndHistoryStack->collapsePanel(mParticipantListPanel, !is_participant_list_visible); + mParticipantListPanel->setVisible(is_participant_list_visible); // Display collapse image (<<) if the floater is hosted // or if it is torn off but has an open control panel. bool is_expanded = is_not_torn_off || is_participant_list_visible; + mExpandCollapseBtn->setImageOverlay(getString(is_expanded ? "collapse_icon" : "expand_icon")); mExpandCollapseBtn->setToolTip( is_not_torn_off? @@ -703,15 +751,9 @@ void LLFloaterIMSessionTab::forceReshape() } -void LLFloaterIMSessionTab::reshapeChatHistory() +void LLFloaterIMSessionTab::reshapeChatLayoutPanel() { - LLRect chat_rect = mChatHistory->getRect(); - LLRect input_rect = mInputEditor->getRect(); - - int delta_height = chat_rect.mBottom - (input_rect.mTop + mInputEditorTopPad); - - chat_rect.setLeftTopAndSize(chat_rect.mLeft, chat_rect.mTop, chat_rect.getWidth(), chat_rect.getHeight() + delta_height); - mChatHistory->setShape(chat_rect); + mChatLayoutPanel->reshape(mChatLayoutPanel->getRect().getWidth(), mInputEditor->getRect().getHeight() + mInputEditorPad, FALSE); } void LLFloaterIMSessionTab::showTranslationCheckbox(BOOL show) @@ -786,15 +828,19 @@ void LLFloaterIMSessionTab::onSlide(LLFloaterIMSessionTab* self) { if (!self->mIsP2PChat) { - bool expand = !self->mParticipantListPanel->getVisible(); - - // Expand/collapse the IM control panel - self->mParticipantListPanel->setVisible(expand); - gSavedSettings.setBOOL("IMShowControlPanel", expand); - self->mIsParticipantListExpanded = expand; - self->mExpandCollapseBtn->setImageOverlay(self->getString(expand ? "collapse_icon" : "expand_icon")); + // The state must toggle the collapsed state of the panel + bool should_be_expanded = self->mParticipantListPanel->isCollapsed(); + + // Update the expand/collapse flag of the participant list panel and save it + gSavedSettings.setBOOL("IMShowControlPanel", should_be_expanded); + self->mIsParticipantListExpanded = should_be_expanded; + + // Refresh for immediate feedback + self->refreshConversation(); } } + + self->assignResizeLimits(); } void LLFloaterIMSessionTab::onCollapseToLine(LLFloaterIMSessionTab* self) @@ -806,6 +852,7 @@ void LLFloaterIMSessionTab::onCollapseToLine(LLFloaterIMSessionTab* self) self->mExpandCollapseLineBtn->setImageOverlay(self->getString(expand ? "collapseline_icon" : "expandline_icon")); self->mContentPanel->setVisible(!expand); self->mToolbarPanel->setVisible(!expand); + self->mInputEditor->enableSingleLineMode(expand); self->reshapeFloater(expand); self->setMessagePaneExpanded(!expand); } @@ -818,20 +865,20 @@ void LLFloaterIMSessionTab::reshapeFloater(bool collapse) if(collapse) { mFloaterHeight = floater_rect.getHeight(); - S32 height = mContentPanel->getRect().getHeight() + mToolbarPanel->getRect().getHeight(); + S32 height = mContentPanel->getRect().getHeight() + mToolbarPanel->getRect().getHeight() + + mChatLayoutPanel->getRect().getHeight() - mChatLayoutPanelHeight + 2; floater_rect.mTop -= height; - enableResizeCtrls(true, true, false); } else { floater_rect.mTop = floater_rect.mBottom + mFloaterHeight; - enableResizeCtrls(true, true, true); - } + enableResizeCtrls(true, true, !collapse); + + saveCollapsedState(); setShape(floater_rect, true); mBodyStack->updateLayout(); - } void LLFloaterIMSessionTab::restoreFloater() @@ -850,6 +897,7 @@ void LLFloaterIMSessionTab::restoreFloater() mBodyStack->updateLayout(); mExpandCollapseLineBtn->setImageOverlay(getString("expandline_icon")); setMessagePaneExpanded(true); + saveCollapsedState(); enableResizeCtrls(true, true, true); } } @@ -1025,6 +1073,14 @@ LLConversationItem* LLFloaterIMSessionTab::getCurSelectedViewModelItem() return conversationItem; } +void LLFloaterIMSessionTab::saveCollapsedState() +{ + LLFloaterIMSessionTab* conversp = LLFloaterIMSessionTab::getConversation(mSessionID); + if(conversp->isNearbyChat()) + { + gSavedPerAccountSettings.setBOOL("NearbyChatIsNotCollapsed", isMessagePaneExpanded()); + } +} BOOL LLFloaterIMSessionTab::handleKeyHere(KEY key, MASK mask ) { if(mask == MASK_ALT) diff --git a/indra/newview/llfloaterimsessiontab.h b/indra/newview/llfloaterimsessiontab.h index f22e2b5744..302d5a8066 100644 --- a/indra/newview/llfloaterimsessiontab.h +++ b/indra/newview/llfloaterimsessiontab.h @@ -101,6 +101,7 @@ public: bool isMessagePaneExpanded(){return mMessagePaneExpanded;} void setMessagePaneExpanded(bool expanded){mMessagePaneExpanded = expanded;} void restoreFloater(); + void saveCollapsedState(); protected: @@ -140,6 +141,9 @@ protected: void appendMessage(const LLChat& chat, const LLSD &args = 0); std::string appendTime(); + void assignResizeLimits(); + + S32 mFloaterExtraWidth; bool mIsNearbyChat; bool mIsP2PChat; @@ -155,7 +159,9 @@ protected: LLUUID mSessionID; LLLayoutStack* mBodyStack; + LLLayoutStack* mParticipantListAndHistoryStack; LLLayoutPanel* mParticipantListPanel; // add the widgets to that see mConversationsListPanel + LLLayoutPanel* mRightPartPanel; LLLayoutPanel* mContentPanel; LLLayoutPanel* mToolbarPanel; LLLayoutPanel* mInputButtonPanel; @@ -168,17 +174,15 @@ protected: LLOutputMonitorCtrl* mSpeakingIndicator; LLChatHistory* mChatHistory; LLChatEntry* mInputEditor; - int mInputEditorTopPad; // padding between input field and chat history - + LLLayoutPanel * mChatLayoutPanel; + LLLayoutStack * mInputPanels; + LLButton* mExpandCollapseLineBtn; LLButton* mExpandCollapseBtn; LLButton* mTearOffBtn; LLButton* mCloseBtn; LLButton* mGearBtn; - S32 mFloaterHeight; - - private: // Handling selection and contextual menu void doToSelected(const LLSD& userdata); @@ -195,13 +199,17 @@ private: * and avoid overlapping, since input chat field can be vertically expanded. * Implementation: chat history bottom "follows" top+top_pad of input chat field */ - void reshapeChatHistory(); + void reshapeChatLayoutPanel(); bool checkIfTornOff(); bool mIsHostAttached; bool mHasVisibleBeenInitialized; LLTimer* mRefreshTimer; ///< Defines the rate at which refresh() is called. + + S32 mInputEditorPad; + S32 mChatLayoutPanelHeight; + S32 mFloaterHeight; }; diff --git a/indra/newview/llimview.cpp b/indra/newview/llimview.cpp index b379939cfa..425f9d67a2 100644 --- a/indra/newview/llimview.cpp +++ b/indra/newview/llimview.cpp @@ -160,163 +160,156 @@ static void on_avatar_name_cache_toast(const LLUUID& agent_id, void on_new_message(const LLSD& msg) { - std::string action; + std::string user_preferences; LLUUID participant_id = msg["from_id"].asUUID(); LLUUID session_id = msg["session_id"].asUUID(); LLIMModel::LLIMSession* session = LLIMModel::instance().findIMSession(session_id); - // determine action for this session + // do not show notification which goes from agent + if (gAgent.getID() == participant_id) + { + return; + } + + // determine state of conversations floater + enum {CLOSED, NOT_ON_TOP, ON_TOP, ON_TOP_AND_ITEM_IS_SELECTED} conversations_floater_status; + + conversations_floater_status = ON_TOP; + + LLFloaterIMContainer* im_box = LLFloaterReg::getTypedInstance<LLFloaterIMContainer>("im_container"); + LLFloaterIMSessionTab* session_floater = LLFloaterIMSessionTab::getConversation(session_id); + + if (!LLFloater::isVisible(im_box) || im_box->isMinimized()) + { + conversations_floater_status = CLOSED; + } + else if (!session_floater || !LLFloater::isVisible(session_floater) + || session_floater->isMinimized() || !session_floater->hasFocus()) + { + conversations_floater_status = NOT_ON_TOP; + } + else if (session_floater->hasFocus()) + { + conversations_floater_status = ON_TOP; + } + else if((session_floater->hasFocus()) && (im_box->getSelectedSession() == session_id)) + { + conversations_floater_status = ON_TOP_AND_ITEM_IS_SELECTED; + } + // determine user prefs for this session if (session_id.isNull()) { - action = gSavedSettings.getString("NotificationNearbyChatOptions"); + user_preferences = gSavedSettings.getString("NotificationNearbyChatOptions"); } else if(session->isP2PSessionType()) { if (LLAvatarTracker::instance().isBuddy(participant_id)) { - action = gSavedSettings.getString("NotificationFriendIMOptions"); + user_preferences = gSavedSettings.getString("NotificationFriendIMOptions"); } else { - action = gSavedSettings.getString("NotificationNonFriendIMOptions"); + user_preferences = gSavedSettings.getString("NotificationNonFriendIMOptions"); } } else if(session->isAdHocSessionType()) { - action = gSavedSettings.getString("NotificationConferenceIMOptions"); + user_preferences = gSavedSettings.getString("NotificationConferenceIMOptions"); } else if(session->isGroupSessionType()) { - action = gSavedSettings.getString("NotificationGroupChatOptions"); + user_preferences = gSavedSettings.getString("NotificationGroupChatOptions"); } - // do not show notification which goes from agent - if (gAgent.getID() == participant_id) - { - return; - } + // actions: - // execution of the action - - LLFloaterIMContainer* im_box = LLFloaterReg::getTypedInstance<LLFloaterIMContainer>("im_container"); - LLFloaterIMSessionTab* session_floater = LLFloaterIMSessionTab::getConversation(session_id); - - if (im_box->isFrontmost() && im_box->getSelectedSession() == session_id - && !(session_floater->getHost() ? im_box->isMinimized() : session_floater->isMinimized())) - { - return; - } - - //session floater not focused (visible or not) - bool session_floater_not_focused = session_floater && !session_floater->hasFocus(); - - //conv. floater is closed - bool conversation_floater_is_closed = - !( im_box - && im_box->isInVisibleChain() - && !im_box->isMinimized()); - - //conversation floater not focused (visible or not) - bool conversation_floater_not_focused = - conversation_floater_is_closed || !im_box->hasFocus(); - // sess. floater is open - bool session_floater_is_open = - session_floater - && session_floater->isInVisibleChain() - && !session_floater->isMinimized() - && !(session_floater->getHost() && session_floater->getHost()->isMinimized()); - - bool conversation_floater_collapsed = !session_floater->isMessagePaneExpanded(); - if (("toast" == action && !session_floater_is_open) || conversation_floater_collapsed) + // 0. nothing - exit + if (("none" == user_preferences || + ON_TOP_AND_ITEM_IS_SELECTED == conversations_floater_status) + && session_floater->isMessagePaneExpanded()) { - //User is not focused on conversation containing the message - if(session_floater_not_focused || conversation_floater_collapsed) - { - if(!LLMuteList::getInstance()->isMuted(participant_id)) - { - im_box->flashConversationItemWidget(session_id, true); - } - //The conversation floater isn't focused/open - if(conversation_floater_not_focused || conversation_floater_collapsed) - { - if(!LLMuteList::getInstance()->isMuted(participant_id) - && !gAgent.isDoNotDisturb()) - { - gToolBarView->flashCommand(LLCommandId("chat"), true); - } - - //Show IM toasts (upper right toasts) - // Skip toasting for system messages and for nearby chat - if(session_id.notNull() && participant_id.notNull()) - { - LLAvatarNameCache::get(participant_id, boost::bind(&on_avatar_name_cache_toast, _1, _2, msg)); - } - } - } + return; } - else if ("flash" == action) + // 1. open floater and [optional] surface it + if ("openconversations" == user_preferences && + (CLOSED == conversations_floater_status + || NOT_ON_TOP == conversations_floater_status)) { - if (!gAgent.isDoNotDisturb()) - { - im_box->flashConversationItemWidget(session_id, true); - if(conversation_floater_not_focused) - { - //User is not focused on conversation containing the message - gToolBarView->flashCommand(LLCommandId("chat"), true); - } - } - else if(session_id.notNull() && participant_id.notNull()) - { - //If a DND message, allow notification to be stored so upon DND exit - //useMostItrusiveIMNotification will be called to notify user a message exists - LLAvatarNameCache::get(participant_id, boost::bind(&on_avatar_name_cache_toast, _1, _2, msg)); - } - } - - else if("openconversations" == action) - { - //User is not focused on conversation containing the message - if(session_floater_not_focused) + if(!gAgent.isDoNotDisturb()) { - //Flash line item - im_box->flashConversationItemWidget(session_id, true); - - if(!gAgent.isDoNotDisturb()) - { - //Surface conversations floater - LLFloaterReg::showInstance("im_container"); - im_box->collapseMessagesPane(false); - if (session_floater) + // Open conversations floater + LLFloaterReg::showInstance("im_container"); + im_box->collapseMessagesPane(false); + if (session_floater) + { + if (session_floater->getHost()) { - if (session_floater->getHost()) + if (NULL != im_box && im_box->isMinimized()) { - if (NULL != im_box && im_box->isMinimized()) - { - LLFloater::onClickMinimize(im_box); - } + LLFloater::onClickMinimize(im_box); } - else + } + else + { + if (session_floater->isMinimized()) { - if (session_floater->isMinimized()) - { - LLFloater::onClickMinimize(session_floater); - } + LLFloater::onClickMinimize(session_floater); } } } - - //If in DND mode, allow notification to be stored so upon DND exit + } + else + { + //If in DND mode, allow notification to be stored so upon DND exit //useMostItrusiveIMNotification will be called to notify user a message exists - if(session_id.notNull() - && participant_id.notNull() - && gAgent.isDoNotDisturb() - && !session_floater_is_open) + if(session_id.notNull() + && participant_id.notNull() + && !session_floater->isShown()) { LLAvatarNameCache::get(participant_id, boost::bind(&on_avatar_name_cache_toast, _1, _2, msg)); - } - } + } + } + } + + // 2. Flash line item + if ("openconversations" == user_preferences + || ON_TOP == conversations_floater_status + || ("toast" == user_preferences && ON_TOP != conversations_floater_status) + || ("flash" == user_preferences && CLOSED == conversations_floater_status)) + { + if(!LLMuteList::getInstance()->isMuted(participant_id)) + { + im_box->flashConversationItemWidget(session_id, true); + } + } + + // 3. Flash FUI button + if (("toast" == user_preferences || "flash" == user_preferences) && + (CLOSED == conversations_floater_status + || NOT_ON_TOP == conversations_floater_status)) + { + if(!LLMuteList::getInstance()->isMuted(participant_id) + && !gAgent.isDoNotDisturb()) + { + gToolBarView->flashCommand(LLCommandId("chat"), true); + } + } + + // 4. Toast + if ((("toast" == user_preferences) && + (CLOSED == conversations_floater_status + || NOT_ON_TOP == conversations_floater_status)) + || !session_floater->isMessagePaneExpanded()) + + { + //Show IM toasts (upper right toasts) + // Skip toasting for system messages and for nearby chat + if(session_id.notNull() && participant_id.notNull()) + { + LLAvatarNameCache::get(participant_id, boost::bind(&on_avatar_name_cache_toast, _1, _2, msg)); + } } } diff --git a/indra/newview/llnotificationstorage.cpp b/indra/newview/llnotificationstorage.cpp index b6184f09bf..2923221c90 100644 --- a/indra/newview/llnotificationstorage.cpp +++ b/indra/newview/llnotificationstorage.cpp @@ -105,6 +105,8 @@ bool LLNotificationStorage::writeNotifications(const LLSD& pNotificationData) co bool LLNotificationStorage::readNotifications(LLSD& pNotificationData) const { + LL_INFOS("LLNotificationStorage") << "starting read '" << mFileName << "'" << LL_ENDL; + bool didFileRead; pNotificationData.clear(); @@ -126,6 +128,8 @@ bool LLNotificationStorage::readNotifications(LLSD& pNotificationData) const } } + LL_INFOS("LLNotificationStorage") << "ending read '" << mFileName << "'" << LL_ENDL; + return didFileRead; } diff --git a/indra/newview/llpersistentnotificationstorage.cpp b/indra/newview/llpersistentnotificationstorage.cpp index 11c12e6c10..666f10df96 100644 --- a/indra/newview/llpersistentnotificationstorage.cpp +++ b/indra/newview/llpersistentnotificationstorage.cpp @@ -87,6 +87,8 @@ void LLPersistentNotificationStorage::loadNotifications() { LLFastTimer _(FTM_LOAD_NOTIFICATIONS); + LL_INFOS("LLPersistentNotificationStorage") << "start loading notifications" << LL_ENDL; + LLNotifications::instance().getChannel("Persistent")-> connectChanged(boost::bind(&LLPersistentNotificationStorage::onPersistentChannelChanged, this, _1)); @@ -129,6 +131,8 @@ void LLPersistentNotificationStorage::loadNotifications() notification_channel->hideToast(notification->getID()); } } + + LL_INFOS("LLPersistentNotificationStorage") << "finished loading notifications" << LL_ENDL; } bool LLPersistentNotificationStorage::onPersistentChannelChanged(const LLSD& payload) diff --git a/indra/newview/llvoavatar.cpp b/indra/newview/llvoavatar.cpp index d492524789..6329d53913 100755 --- a/indra/newview/llvoavatar.cpp +++ b/indra/newview/llvoavatar.cpp @@ -680,7 +680,7 @@ LLVOAvatar::LLVOAvatar(const LLUUID& id, mBelowWater(FALSE), mLastAppearanceBlendTime(0.f), mAppearanceAnimating(FALSE), - mNameString(), + mNameIsSet(false), mTitle(), mNameAway(false), mNameDoNotDisturb(false), @@ -2637,8 +2637,7 @@ void LLVOAvatar::idleUpdateNameTagText(BOOL new_name) } // Rebuild name tag if state change detected - if (mNameString.empty() - || (mNameString.size() == 2 && mNameString[0] == 10 && mNameString[1] == 10) // *TODO : find out why mNameString is sometimes "" + if (!mNameIsSet || new_name || (!title && !mTitle.empty()) || (title && mTitle != title->getString()) @@ -2831,17 +2830,16 @@ void LLVOAvatar::addNameTagLine(const std::string& line, const LLColor4& color, { mNameText->addLine(line, color, (LLFontGL::StyleFlags)style, font); } - mNameString += line; - mNameString += '\n'; + mNameIsSet |= !line.empty(); } void LLVOAvatar::clearNameTag() { - mNameString.clear(); + mNameIsSet = false; if (mNameText) { mNameText->setLabel(""); - mNameText->setString( "" ); + mNameText->setString(""); } mTimeVisible.reset(); } diff --git a/indra/newview/llvoavatar.h b/indra/newview/llvoavatar.h index c2adaac18e..ac31e7252c 100755 --- a/indra/newview/llvoavatar.h +++ b/indra/newview/llvoavatar.h @@ -855,7 +855,7 @@ protected: static void getAnimLabels(LLDynamicArray<std::string>* labels); static void getAnimNames(LLDynamicArray<std::string>* names); private: - std::string mNameString; // UTF-8 title + name + status + bool mNameIsSet; std::string mTitle; bool mNameAway; bool mNameDoNotDisturb; diff --git a/indra/newview/llvoicevivox.cpp b/indra/newview/llvoicevivox.cpp index f94ab67026..9b5d981aa5 100644 --- a/indra/newview/llvoicevivox.cpp +++ b/indra/newview/llvoicevivox.cpp @@ -1259,7 +1259,7 @@ void LLVivoxVoiceClient::stateMachine() //MARK: stateCreatingSessionGroup case stateCreatingSessionGroup: - if(mSessionTerminateRequested || !mVoiceEnabled && mIsInitialized) + if(mSessionTerminateRequested || (!mVoiceEnabled && mIsInitialized)) { // *TODO: Question: is this the right way out of this state setState(stateSessionTerminated); @@ -1275,7 +1275,7 @@ void LLVivoxVoiceClient::stateMachine() //MARK: stateRetrievingParcelVoiceInfo case stateRetrievingParcelVoiceInfo: // wait until parcel voice info is received. - if(mSessionTerminateRequested || !mVoiceEnabled && mIsInitialized) + if(mSessionTerminateRequested || (!mVoiceEnabled && mIsInitialized)) { // if a terminate request has been received, // bail and go to the stateSessionTerminated @@ -1295,7 +1295,7 @@ void LLVivoxVoiceClient::stateMachine() // Otherwise, if you log in but don't join a proximal channel (such as when your login location has voice disabled), your friends list won't sync. sendFriendsListUpdates(); - if(mSessionTerminateRequested || !mVoiceEnabled && mIsInitialized) + if(mSessionTerminateRequested || (!mVoiceEnabled && mIsInitialized)) { // TODO: Question: Is this the right way out of this state? setState(stateSessionTerminated); @@ -1443,7 +1443,7 @@ void LLVivoxVoiceClient::stateMachine() //MARK: stateRunning case stateRunning: // steady state // Disabling voice or disconnect requested. - if(!mVoiceEnabled && mIsInitialized || mSessionTerminateRequested) + if((!mVoiceEnabled && mIsInitialized) || mSessionTerminateRequested) { leaveAudioSession(); } @@ -2673,33 +2673,19 @@ void LLVivoxVoiceClient::checkFriend(const LLUUID& id) { buddyListEntry *buddy = findBuddy(id); - // Make sure we don't add a name before it's been looked up. + // Make sure we don't add a name before it's been looked up in the avatar name cache LLAvatarName av_name; - if(LLAvatarNameCache::get(id, &av_name)) + if (LLAvatarNameCache::get(id, &av_name)) { - // *NOTE: For now, we feed legacy names to Vivox because I don't know - // if their service can support a mix of new and old clients with - // different sorts of names. + // *NOTE: We feed legacy names to Vivox because we don't know if their service + // can support a mix of new and old clients with different sorts of names. std::string name = av_name.getAccountName(); - - const LLRelationship* relationInfo = LLAvatarTracker::instance().getBuddyInfo(id); - bool canSeeMeOnline = false; - if(relationInfo && relationInfo->isRightGrantedTo(LLRelationship::GRANT_ONLINE_STATUS)) - canSeeMeOnline = true; - - // When we get here, mNeedsSend is true and mInSLFriends is false. Change them as necessary. - if(buddy) + if (buddy) { - // This buddy is already in both lists. - - if(name != buddy->mDisplayName) - { - // The buddy is in the list with the wrong name. Update it with the correct name. - LL_WARNS("Voice") << "Buddy " << id << " has wrong name (\"" << buddy->mDisplayName << "\" should be \"" << name << "\"), updating."<< LL_ENDL; - buddy->mDisplayName = name; - buddy->mNeedsNameUpdate = true; // This will cause the buddy to be resent. - } + // This buddy is already in both lists (vivox buddies and avatar cache). + // Trust the avatar cache more for the display name (vivox display name are notoriously wrong) + buddy->mDisplayName = name; } else { @@ -2708,20 +2694,19 @@ void LLVivoxVoiceClient::checkFriend(const LLUUID& id) buddy->mUUID = id; } - // In all the above cases, the buddy is in the SL friends list (which is how we got here). - buddy->mInSLFriends = true; - buddy->mCanSeeMeOnline = canSeeMeOnline; + const LLRelationship* relationInfo = LLAvatarTracker::instance().getBuddyInfo(id); + buddy->mCanSeeMeOnline = (relationInfo && relationInfo->isRightGrantedTo(LLRelationship::GRANT_ONLINE_STATUS)); + // In all the above cases, the buddy is in the SL friends list and tha name has been resolved (which is how we got here). buddy->mNameResolved = true; - + buddy->mInSLFriends = true; } else { - // This name hasn't been looked up yet. Don't do anything with this buddy list entry until it has. - if(buddy) + // This name hasn't been looked up yet in the avatar cache. Don't do anything with this buddy list entry until it has. + if (buddy) { buddy->mNameResolved = false; } - // Initiate a lookup. // The "lookup completed" callback will ensure that the friends list is rechecked after it completes. lookupName(id); @@ -2829,13 +2814,12 @@ void LLVivoxVoiceClient::sendFriendsListUpdates() { std::ostringstream stream; - if(buddy->mInSLFriends && (!buddy->mInVivoxBuddies || buddy->mNeedsNameUpdate)) + if(buddy->mInSLFriends && !buddy->mInVivoxBuddies) { if(mNumberOfAliases > 0) { // Add (or update) this entry in the vivox buddy list buddy->mInVivoxBuddies = true; - buddy->mNeedsNameUpdate = false; LL_DEBUGS("Voice") << "add/update " << buddy->mURI << " (" << buddy->mDisplayName << ")" << LL_ENDL; stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.BuddySet.1\">" @@ -5859,7 +5843,6 @@ LLVivoxVoiceClient::buddyListEntry::buddyListEntry(const std::string &uri) : mNameResolved = false; mInVivoxBuddies = false; mInSLFriends = false; - mNeedsNameUpdate = false; } void LLVivoxVoiceClient::processBuddyListEntry(const std::string &uri, const std::string &displayName) @@ -5884,25 +5867,21 @@ LLVivoxVoiceClient::buddyListEntry *LLVivoxVoiceClient::addBuddy(const std::stri buddyListEntry *result = NULL; buddyListMap::iterator iter = mBuddyListMap.find(uri); - if(iter != mBuddyListMap.end()) + if (iter != mBuddyListMap.end()) { // Found a matching buddy already in the map. LL_DEBUGS("Voice") << "adding existing buddy " << uri << LL_ENDL; result = iter->second; } - if(!result) + if (!result) { // participant isn't already in one list or the other. LL_DEBUGS("Voice") << "adding new buddy " << uri << LL_ENDL; result = new buddyListEntry(uri); result->mDisplayName = displayName; - if(IDFromName(uri, result->mUUID)) - { - // Extracted UUID from name successfully. - } - else + if (!IDFromName(uri, result->mUUID)) { LL_DEBUGS("Voice") << "Couldn't find ID for buddy " << uri << " (\"" << displayName << "\")" << LL_ENDL; } @@ -7272,7 +7251,7 @@ void LLVivoxProtocolParser::StartTag(const char *tag, const char **attr) void LLVivoxProtocolParser::EndTag(const char *tag) { const std::string& string = textBuffer; - + responseDepth--; if (ignoringTags) @@ -7371,6 +7350,8 @@ void LLVivoxProtocolParser::EndTag(const char *tag) } else if (!stricmp("Buddy", tag)) { + // NOTE : Vivox does *not* give reliable display name for Buddy tags + // We don't take those very seriously as a result... LLVivoxVoiceClient::getInstance()->processBuddyListEntry(uriString, displayNameString); } else if (!stricmp("BlockRule", tag)) diff --git a/indra/newview/llvoicevivox.h b/indra/newview/llvoicevivox.h index 574027de42..a6f40eb3e9 100644 --- a/indra/newview/llvoicevivox.h +++ b/indra/newview/llvoicevivox.h @@ -584,7 +584,6 @@ protected: bool mNameResolved; bool mInSLFriends; bool mInVivoxBuddies; - bool mNeedsNameUpdate; }; typedef std::map<std::string, buddyListEntry*> buddyListMap; diff --git a/indra/newview/skins/default/xui/en/floater_im_session.xml b/indra/newview/skins/default/xui/en/floater_im_session.xml index e081ea8e74..3b56e974d2 100644 --- a/indra/newview/skins/default/xui/en/floater_im_session.xml +++ b/indra/newview/skins/default/xui/en/floater_im_session.xml @@ -14,7 +14,6 @@ width="394" can_resize="true" can_tear_off="false" - min_width="340" min_height="190" positioning="relative"> <floater.string name="call_btn_start">Conv_toolbar_open_call</floater.string> @@ -43,58 +42,52 @@ <floater.string name="multiple_participants_added" value="[NAME] were invited to the conversation."/> - <floater.string + <floater.string name="tooltip_to_separate_window" value="Move this conversation to a separate window"/> - <floater.string + <floater.string name="tooltip_to_main_window" value="Move this conversation back to main window"/> - <floater.string + <floater.string name="start_call_button_tooltip" value="Open voice connection"/> - <floater.string + <floater.string name="end_call_button_tooltip" value="Close voice connection"/> - <floater.string + <floater.string name="expcol_button_not_tearoff_tooltip" value="Collapse this pane"/> - <floater.string + <floater.string name="expcol_button_tearoff_and_expanded_tooltip" value="Collapse participant list"/> - <floater.string + <floater.string name="expcol_button_tearoff_and_collapsed_tooltip" value="Expand participant list"/> <view - follows="all" - layout="topleft" - name="contents_view" - top="0" - left="0" - height="355" - width="394"> - <layout_stack - animate="false" - default_tab_group="2" - follows="all" - height="355" - width="394" - layout="topleft" - orientation="vertical" - name="main_stack" - tab_group="1" - top="0" - left="0"> - - <layout_panel - follows="left|top|right" + follows="all" + layout="topleft" + name="contents_view" + top="0" + left="0" + right="-1" + bottom="-1"> + <layout_stack + animate="false" + default_tab_group="2" + follows="all" + right="-5" + bottom="-1" layout="topleft" - name="toolbar_panel" + orientation="vertical" + name="main_stack" + tab_group="1" top="0" - left="0" - height="35" - min_height="35" - width="394"> - <menu_button + left="5"> + <layout_panel + auto_resize="false" + name="toolbar_panel" + height="35"> + <menu_button menu_filename="menu_im_session_showmodes.xml" follows="top|left" height="25" @@ -108,22 +101,22 @@ tool_tip="View/sort options" top="5" width="31" /> - <menu_button - menu_filename="menu_im_conversation.xml" - follows="top|left" - height="25" - image_hover_unselected="Toolbar_Middle_Over" - image_overlay="OptionsMenu_Off" - image_selected="Toolbar_Middle_Selected" - image_unselected="Toolbar_Middle_Off" - layout="topleft" - top="5" - left_pad="2" - name="gear_btn" - visible="false" - tool_tip="Actions on selected person" - width="31"/> - <button + <menu_button + menu_filename="menu_im_conversation.xml" + follows="top|left" + height="25" + image_hover_unselected="Toolbar_Middle_Over" + image_overlay="OptionsMenu_Off" + image_selected="Toolbar_Middle_Selected" + image_unselected="Toolbar_Middle_Off" + layout="topleft" + top="5" + left_pad="2" + name="gear_btn" + visible="false" + tool_tip="Actions on selected person" + width="31"/> + <button enabled="false" follows="top|left" height="25" @@ -137,7 +130,7 @@ name="add_btn" tool_tip="Add someone to this conversation" width="31"/> - <button + <button follows="top|left" height="25" image_hover_unselected="Toolbar_Middle_Over" @@ -150,19 +143,19 @@ name="voice_call_btn" tool_tip="Open voice connection" width="31"/> - <output_monitor - auto_update="true" - follows="top|left" - draw_border="false" - height="16" - layout="topleft" - top="10" - left_pad="10" - mouse_opaque="true" - name="speaking_indicator" - visible="false" - width="20" /> - <button + <output_monitor + auto_update="true" + follows="top|left" + draw_border="false" + height="16" + layout="topleft" + top="10" + left_pad="10" + mouse_opaque="true" + name="speaking_indicator" + visible="false" + width="20" /> + <button follows="right|top" height="25" image_hover_unselected="Toolbar_Middle_Over" @@ -171,229 +164,155 @@ image_unselected="Toolbar_Middle_Off" layout="topleft" top="5" - left="292" + right="-70" name="close_btn" tool_tip="End this conversation" width="31" /> - <button + <button follows="right|top" height="25" image_hover_unselected="Toolbar_Middle_Over" image_overlay="Conv_toolbar_collapse" image_selected="Toolbar_Middle_Selected" - image_unselected="Toolbar_Middle_Off" + image_unselected="Toolbar_Middle_Off" layout="topleft" top="5" left_pad="2" name="expand_collapse_btn" tool_tip="Collapse/Expand this pane" width="31" /> - <button + <button follows="right|top" height="25" image_hover_unselected="Toolbar_Middle_Over" image_overlay="Conv_toolbar_arrow_ne" image_selected="Toolbar_Middle_Selected" - image_unselected="Toolbar_Middle_Off" + image_unselected="Toolbar_Middle_Off" layout="topleft" - top="5" left_pad="2" name="tear_off_btn" + top="5" width="31" /> - </layout_panel> - <layout_panel - name="body_panel" - follows="all" - width="394" - height="235" - user_resize="false" - auto_resize="true"> - <layout_stack - animate="true" - default_tab_group="2" - follows="all" - height="275" - width="394" - layout="topleft" - orientation="horizontal" - name="im_panels" - tab_group="1" - top_pad="0" - left="0"> - <layout_panel - name="speakers_list_panel" - follows="all" - min_width="115" - width="150" - height="275" - user_resize="true" - auto_resize="false"> - </layout_panel> - <layout_panel - default_tab_group="3" - left="0" - tab_group="2" - follows="all" - top="0" - height="275" - width="244" - layout="topleft" - user_resize="true" - auto_resize="true" - visible="true" - name="left_part_holder" - min_width="221"> - <panel - name="trnsAndChat_panel" - follows="all" - layout="topleft" - visible="true" - height="240" - width="244"> - <layout_stack - animate="true" - default_tab_group="2" - follows="all" - height="240" - width="244" - layout="topleft" - visible="true" - orientation="vertical" - name="translate_and_chat_stack" - tab_group="1" - left_pad="0" - top="0" - left="0"> - <layout_panel - auto_resize="false" - height="26" - layout="topleft" - left_delta="0" - name="translate_chat_checkbox_lp" - top_delta="0" - visible="true" - width="210"> - <check_box - top="10" - control_name="TranslateChat" - enabled="true" - height="16" - label="Translate chat" - layout="topleft" - left="5" - name="translate_chat_checkbox" - width="230" /> </layout_panel> <layout_panel - height="233" - width="210" - layout="topleft" - follows="all" - left_delta="0" - top_delta="0" - bottom="0" - visible="true" - user_resize="true" - auto_resize="true" - name="chat_holder"> - <chat_history - font="SansSerifSmall" - follows="all" - visible="true" - height="225" - name="chat_history" - parse_highlights="true" - parse_urls="true" - right="-5" - left="5"> - </chat_history> + name="body_panel" + height="235"> + <layout_stack + default_tab_group="2" + follows="all" + orientation="horizontal" + name="im_panels" + tab_group="1" + top="0" + right="-1" + bottom="-1" + left="0"> + <layout_panel + name="speakers_list_panel" + expanded_min_dim="115" + min_dim="0" + width="150" + user_resize="true" + auto_resize="false" /> + <layout_panel + default_tab_group="3" + tab_group="2" + name="right_part_holder" + min_width="221"> + <layout_stack + animate="true" + default_tab_group="2" + follows="all" + orientation="vertical" + name="translate_and_chat_stack" + tab_group="1" + top="0" + left="0" + right="-1" + bottom="-1"> + <layout_panel + auto_resize="false" + height="26" + name="translate_chat_checkbox_lp"> + <check_box + top="10" + control_name="TranslateChat" + enabled="true" + height="16" + label="Translate chat" + left="5" + name="translate_chat_checkbox" + width="230" /> + </layout_panel> + <layout_panel + name="chat_holder"> + <chat_history + font="SansSerifSmall" + follows="all" + name="chat_history" + parse_highlights="true" + parse_urls="true" + right="-1" + left="5" + top="0" + bottom="-1" /> + </layout_panel> + </layout_stack> + </layout_panel> + </layout_stack> </layout_panel> - </layout_stack> - </panel> - </layout_panel> - </layout_stack> - </layout_panel> - <layout_panel + <layout_panel height="35" - layout="topleft" - follows="left|right|bottom" - left_delta="0" - right="0" - top_delta="0" - bottom="0" - visible="true" - user_resize="false" auto_resize="false" name="chat_layout_panel"> - <layout_stack - animate="true" - default_tab_group="2" - follows="all" - height="35" - right="0" - layout="topleft" - orientation="horizontal" - name="input_panels" - top_pad="0" - left="0"> - <layout_panel - height="35" - layout="topleft" - follows="left|right|bottom" - left_delta="0" - top_delta="0" - bottom="0" - visible="true" - user_resize="false" - auto_resize="true" - name="input_editor_layout_panel"> - <chat_editor - top="6" - expand_lines_count="5" - follows="left|right|bottom" - font="SansSerifSmall" - visible="true" - height="20" - is_expandable="true" - label="To" - text_tentative_color="TextFgTentativeColor" - layout="topleft" - name="chat_editor" - max_length="1023" - spellcheck="true" - tab_group="3" - width="160" - left="5" - right="-5" - wrap="true"> - </chat_editor> + <layout_stack + animate="false" + default_tab_group="2" + follows="all" + right="-1" + orientation="horizontal" + name="input_panels" + top="0" + bottom="-1" + left="0"> + <layout_panel + name="input_editor_layout_panel"> + <chat_editor + layout="topleft" + expand_lines_count="5" + follows="left|right|bottom" + font="SansSerifSmall" + height="20" + is_expandable="true" + text_tentative_color="TextFgTentativeColor" + name="chat_editor" + max_length="1023" + spellcheck="true" + tab_group="3" + bottom="-8" + left="5" + right="-5" + wrap="true" /> + </layout_panel> + <layout_panel + auto_resize="false" + name="input_button_layout_panel" + width="32"> + <button + left="1" + top="4" + follows="left|right|top" + height="25" + image_hover_unselected="Toolbar_Middle_Over" + image_overlay="Conv_expand_one_line" + image_selected="Toolbar_Middle_Selected" + image_unselected="Toolbar_Middle_Off" + name="minz_btn" + tool_tip="Shows/hides message panel" + width="28" /> + </layout_panel> + </layout_stack> </layout_panel> - <layout_panel - height="35" - layout="topleft" - follows="left|right|bottom" - left_delta="0" - top_delta="0" - bottom="0" - width="35" - visible="true" - user_resize="false" - auto_resize="false" - name="input_button_layout_panel"> - <button - follows="left|right|bottom" - height="25" - image_hover_unselected="Toolbar_Middle_Over" - image_overlay="Conv_expand_one_line" - image_selected="Toolbar_Middle_Selected" - image_unselected="Toolbar_Middle_Off" - layout="topleft" - name="minz_btn" - tool_tip="Shows/hides message panel" - width="28"/> - </layout_panel> - </layout_stack> - </layout_panel> - </layout_stack> + </layout_stack> </view> </floater> |