diff options
89 files changed, 6954 insertions, 589 deletions
diff --git a/indra/CMakeLists.txt b/indra/CMakeLists.txt index bdbfc55fa2..410d25ad97 100755 --- a/indra/CMakeLists.txt +++ b/indra/CMakeLists.txt @@ -63,10 +63,11 @@ add_subdirectory(${VIEWER_PREFIX}test) # viewer media plugins add_subdirectory(${LIBS_OPEN_PREFIX}media_plugins) -# llplugin testbed code (is this the right way to include it?) -if (LL_TESTS AND NOT LINUX) - add_subdirectory(${VIEWER_PREFIX}test_apps/llplugintest) -endif (LL_TESTS AND NOT LINUX) + # llplugin testbed code (is this the right way to include it?) + if (LL_TESTS AND NOT LINUX) + add_subdirectory(${VIEWER_PREFIX}test_apps/llplugintest) + add_subdirectory(${VIEWER_PREFIX}test_apps/llfbconnecttest) + endif (LL_TESTS AND NOT LINUX) if (LINUX) add_subdirectory(${VIEWER_PREFIX}linux_crash_logger) diff --git a/indra/llmessage/llhttpclient.cpp b/indra/llmessage/llhttpclient.cpp index 3561459bb4..2d16237161 100755 --- a/indra/llmessage/llhttpclient.cpp +++ b/indra/llmessage/llhttpclient.cpp @@ -217,7 +217,8 @@ static void request( Injector* body_injector, LLCurl::ResponderPtr responder, const F32 timeout = HTTP_REQUEST_EXPIRY_SECS, - const LLSD& headers = LLSD() + const LLSD& headers = LLSD(), + bool follow_redirects = true ) { if (!LLHTTPClient::hasPump()) @@ -231,7 +232,7 @@ static void request( } LLPumpIO::chain_t chain; - LLURLRequest* req = new LLURLRequest(method, url); + LLURLRequest* req = new LLURLRequest(method, url, follow_redirects); if(!req->isValid())//failed { if (responder) @@ -334,7 +335,8 @@ void LLHTTPClient::getByteRange( S32 bytes, ResponderPtr responder, const LLSD& hdrs, - const F32 timeout) + const F32 timeout, + bool follow_redirects /* = true */) { LLSD headers = hdrs; if(offset > 0 || bytes > 0) @@ -342,37 +344,42 @@ void LLHTTPClient::getByteRange( std::string range = llformat("bytes=%d-%d", offset, offset+bytes-1); headers["Range"] = range; } - request(url,LLURLRequest::HTTP_GET, NULL, responder, timeout, headers); + request(url,LLURLRequest::HTTP_GET, NULL, responder, timeout, headers, follow_redirects); } void LLHTTPClient::head( const std::string& url, ResponderPtr responder, const LLSD& headers, - const F32 timeout) + const F32 timeout, + bool follow_redirects /* = true */) { - request(url, LLURLRequest::HTTP_HEAD, NULL, responder, timeout, headers); + request(url, LLURLRequest::HTTP_HEAD, NULL, responder, timeout, headers, follow_redirects); } -void LLHTTPClient::get(const std::string& url, ResponderPtr responder, const LLSD& headers, const F32 timeout) +void LLHTTPClient::get(const std::string& url, ResponderPtr responder, const LLSD& headers, const F32 timeout, + bool follow_redirects /* = true */) { - request(url, LLURLRequest::HTTP_GET, NULL, responder, timeout, headers); + request(url, LLURLRequest::HTTP_GET, NULL, responder, timeout, headers, follow_redirects); } -void LLHTTPClient::getHeaderOnly(const std::string& url, ResponderPtr responder, const LLSD& headers, const F32 timeout) +void LLHTTPClient::getHeaderOnly(const std::string& url, ResponderPtr responder, const LLSD& headers, + const F32 timeout, bool follow_redirects /* = true */) { - request(url, LLURLRequest::HTTP_HEAD, NULL, responder, timeout, headers); + request(url, LLURLRequest::HTTP_HEAD, NULL, responder, timeout, headers, follow_redirects); } -void LLHTTPClient::getHeaderOnly(const std::string& url, ResponderPtr responder, const F32 timeout) +void LLHTTPClient::getHeaderOnly(const std::string& url, ResponderPtr responder, const F32 timeout, + bool follow_redirects /* = true */) { - getHeaderOnly(url, responder, LLSD(), timeout); + getHeaderOnly(url, responder, LLSD(), timeout, follow_redirects); } -void LLHTTPClient::get(const std::string& url, const LLSD& query, ResponderPtr responder, const LLSD& headers, const F32 timeout) +void LLHTTPClient::get(const std::string& url, const LLSD& query, ResponderPtr responder, const LLSD& headers, + const F32 timeout, bool follow_redirects /* = true */) { LLURI uri; uri = LLURI::buildHTTP(url, LLSD::emptyArray(), query); - get(uri.asString(), responder, headers, timeout); + get(uri.asString(), responder, headers, timeout, follow_redirects); } // A simple class for managing data returned from a curl http request. diff --git a/indra/llmessage/llhttpclient.h b/indra/llmessage/llhttpclient.h index a7236ba169..5de257a4f6 100755 --- a/indra/llmessage/llhttpclient.h +++ b/indra/llmessage/llhttpclient.h @@ -63,10 +63,15 @@ public: const std::string& url, ResponderPtr, const LLSD& headers = LLSD(), - const F32 timeout=HTTP_REQUEST_EXPIRY_SECS); - static void getByteRange(const std::string& url, S32 offset, S32 bytes, ResponderPtr, const LLSD& headers=LLSD(), const F32 timeout=HTTP_REQUEST_EXPIRY_SECS); - static void get(const std::string& url, ResponderPtr, const LLSD& headers = LLSD(), const F32 timeout=HTTP_REQUEST_EXPIRY_SECS); - static void get(const std::string& url, const LLSD& query, ResponderPtr, const LLSD& headers = LLSD(), const F32 timeout=HTTP_REQUEST_EXPIRY_SECS); + const F32 timeout=HTTP_REQUEST_EXPIRY_SECS, + bool follow_redirects = true); + static void getByteRange(const std::string& url, S32 offset, S32 bytes, ResponderPtr, + const LLSD& headers=LLSD(), const F32 timeout=HTTP_REQUEST_EXPIRY_SECS, + bool follow_redirects = true); + static void get(const std::string& url, ResponderPtr, const LLSD& headers = LLSD(), + const F32 timeout=HTTP_REQUEST_EXPIRY_SECS, bool follow_redirects = true); + static void get(const std::string& url, const LLSD& query, ResponderPtr, const LLSD& headers = LLSD(), + const F32 timeout=HTTP_REQUEST_EXPIRY_SECS, bool follow_redirects = true); static void put( const std::string& url, @@ -74,8 +79,10 @@ public: ResponderPtr, const LLSD& headers = LLSD(), const F32 timeout=HTTP_REQUEST_EXPIRY_SECS); - static void getHeaderOnly(const std::string& url, ResponderPtr, const F32 timeout=HTTP_REQUEST_EXPIRY_SECS); - static void getHeaderOnly(const std::string& url, ResponderPtr, const LLSD& headers, const F32 timeout=HTTP_REQUEST_EXPIRY_SECS); + static void getHeaderOnly(const std::string& url, ResponderPtr, const F32 timeout=HTTP_REQUEST_EXPIRY_SECS, + bool follow_redirects = true); + static void getHeaderOnly(const std::string& url, ResponderPtr, const LLSD& headers, + const F32 timeout=HTTP_REQUEST_EXPIRY_SECS, bool follow_redirects = true); static void post( const std::string& url, diff --git a/indra/llmessage/llurlrequest.cpp b/indra/llmessage/llurlrequest.cpp index 627d591839..49f8144ee7 100755 --- a/indra/llmessage/llurlrequest.cpp +++ b/indra/llmessage/llurlrequest.cpp @@ -150,16 +150,19 @@ std::string LLURLRequest::actionAsVerb(LLURLRequest::ERequestAction action) return VERBS[action]; } -LLURLRequest::LLURLRequest(LLURLRequest::ERequestAction action) : - mAction(action) +LLURLRequest::LLURLRequest(LLURLRequest::ERequestAction action, bool follow_redirects /* = true */) : + mAction(action), + mFollowRedirects(follow_redirects) { initialize(); } LLURLRequest::LLURLRequest( LLURLRequest::ERequestAction action, - const std::string& url) : - mAction(action) + const std::string& url, + bool follow_redirects /* = true */) : + mAction(action), + mFollowRedirects(follow_redirects) { initialize(); setURL(url); @@ -479,12 +482,18 @@ bool LLURLRequest::configure() case HTTP_HEAD: mDetail->mCurlRequest->setopt(CURLOPT_HEADER, 1); mDetail->mCurlRequest->setopt(CURLOPT_NOBODY, 1); - mDetail->mCurlRequest->setopt(CURLOPT_FOLLOWLOCATION, 1); + if (mFollowRedirects) + { + mDetail->mCurlRequest->setopt(CURLOPT_FOLLOWLOCATION, 1); + } rv = true; break; case HTTP_GET: mDetail->mCurlRequest->setopt(CURLOPT_HTTPGET, 1); - mDetail->mCurlRequest->setopt(CURLOPT_FOLLOWLOCATION, 1); + if (mFollowRedirects) + { + mDetail->mCurlRequest->setopt(CURLOPT_FOLLOWLOCATION, 1); + } // Set Accept-Encoding to allow response compression mDetail->mCurlRequest->setoptString(CURLOPT_ENCODING, ""); diff --git a/indra/llmessage/llurlrequest.h b/indra/llmessage/llurlrequest.h index 44d358d906..20d6e30d17 100755 --- a/indra/llmessage/llurlrequest.h +++ b/indra/llmessage/llurlrequest.h @@ -95,7 +95,7 @@ public: * * @param action One of the ERequestAction enumerations. */ - LLURLRequest(ERequestAction action); + LLURLRequest(ERequestAction action, bool follow_redirects = true); /** * @brief Constructor. @@ -103,7 +103,7 @@ public: * @param action One of the ERequestAction enumerations. * @param url The url of the request. It should already be encoded. */ - LLURLRequest(ERequestAction action, const std::string& url); + LLURLRequest(ERequestAction action, const std::string& url, bool follow_redirects = true); /** * @brief Destructor. @@ -219,10 +219,11 @@ protected: }; EState mState; ERequestAction mAction; + bool mFollowRedirects; LLURLRequestDetail* mDetail; LLIOPipe::ptr_t mCompletionCallback; - S32 mRequestTransferedBytes; - S32 mResponseTransferedBytes; + S32 mRequestTransferedBytes; + S32 mResponseTransferedBytes; static CURLcode _sslCtxCallback(CURL * curl, void *sslctx, void *param); diff --git a/indra/llui/llchatentry.cpp b/indra/llui/llchatentry.cpp index 6a1b48a08a..f6c4b69308 100755 --- a/indra/llui/llchatentry.cpp +++ b/indra/llui/llchatentry.cpp @@ -163,16 +163,6 @@ bool LLChatEntry::useLabel() return !getLength() && !mLabel.empty(); } -void LLChatEntry::onFocusReceived() -{ - -} - -void LLChatEntry::onFocusLost() -{ - -} - BOOL LLChatEntry::handleSpecialKey(const KEY key, const MASK mask) { BOOL handled = FALSE; diff --git a/indra/llui/llchatentry.h b/indra/llui/llchatentry.h index 49c8d21450..a20a505ae1 100755 --- a/indra/llui/llchatentry.h +++ b/indra/llui/llchatentry.h @@ -62,8 +62,6 @@ public: virtual void draw(); virtual void onCommit(); - /*virtual*/ void onFocusReceived(); - /*virtual*/ void onFocusLost(); void enableSingleLineMode(bool single_line_mode); boost::signals2::connection setTextExpandedCallback(const commit_signal_t::slot_type& cb); diff --git a/indra/llui/llfloater.cpp b/indra/llui/llfloater.cpp index 09e27a264a..db9b6b78d2 100755 --- a/indra/llui/llfloater.cpp +++ b/indra/llui/llfloater.cpp @@ -507,22 +507,11 @@ LLFloater::~LLFloater() { LLFloaterReg::removeInstance(mInstanceName, mKey); -// delete mNotificationContext; -// mNotificationContext = NULL; - - //// am I not hosted by another floater? - //if (mHostHandle.isDead()) - //{ - // LLFloaterView* parent = (LLFloaterView*) getParent(); - - // if( parent ) - // { - // parent->removeChild( this ); - // } - //} - - // Just in case we might still have focus here, release it. - releaseFocus(); + if( gFocusMgr.childHasKeyboardFocus(this)) + { + // Just in case we might still have focus here, release it. + releaseFocus(); + } // This is important so that floaters with persistent rects (i.e., those // created with rect control rather than an LLRect) are restored in their @@ -1346,7 +1335,7 @@ void LLFloater::setFocus( BOOL b ) { return; } - LLUICtrl* last_focus = gFocusMgr.getLastFocusForGroup(this); + LLView* last_focus = gFocusMgr.getLastFocusForGroup(this); // a descendent already has focus BOOL child_had_focus = hasFocus(); @@ -2788,7 +2777,7 @@ void LLFloaterView::adjustToFitScreen(LLFloater* floater, BOOL allow_partial_out } // move window fully onscreen - if (floater->translateIntoRect( getSnapRect(), allow_partial_outside ? FLOATER_MIN_VISIBLE_PIXELS : S32_MAX )) + if (floater->translateIntoRect( gFloaterView->getRect(), allow_partial_outside ? FLOATER_MIN_VISIBLE_PIXELS : S32_MAX )) { floater->clearSnapTarget(); } @@ -3258,6 +3247,11 @@ bool LLFloater::isShown() const return ! isMinimized() && isInVisibleChain(); } +bool LLFloater::isDetachedAndNotMinimized() +{ + return !getHost() && !isMinimized(); +} + /* static */ bool LLFloater::isShown(const LLFloater* floater) { diff --git a/indra/llui/llfloater.h b/indra/llui/llfloater.h index 4dba1e645f..26ac4a98ad 100755 --- a/indra/llui/llfloater.h +++ b/indra/llui/llfloater.h @@ -238,6 +238,7 @@ public: void center(); LLMultiFloater* getHost(); + bool isDetachedAndNotMinimized(); void applyTitle(); std::string getCurrentTitle() const; diff --git a/indra/llui/llfocusmgr.cpp b/indra/llui/llfocusmgr.cpp index 724d190307..f03c8d444b 100755 --- a/indra/llui/llfocusmgr.cpp +++ b/indra/llui/llfocusmgr.cpp @@ -469,7 +469,7 @@ void LLFocusMgr::setAppHasFocus(BOOL focus) mAppHasFocus = focus; } -LLUICtrl* LLFocusMgr::getLastFocusForGroup(LLView* subtree_root) const +LLView* LLFocusMgr::getLastFocusForGroup(LLView* subtree_root) const { if (subtree_root) { @@ -477,7 +477,7 @@ LLUICtrl* LLFocusMgr::getLastFocusForGroup(LLView* subtree_root) const if (found_it != mImpl->mFocusHistory.end()) { // found last focus for this subtree - return static_cast<LLUICtrl*>(found_it->second.get()); + return found_it->second.get(); } } return NULL; diff --git a/indra/llui/llfocusmgr.h b/indra/llui/llfocusmgr.h index 25ae1d2579..1c7326260c 100755 --- a/indra/llui/llfocusmgr.h +++ b/indra/llui/llfocusmgr.h @@ -97,7 +97,7 @@ public: void triggerFocusFlash(); BOOL getAppHasFocus() const { return mAppHasFocus; } void setAppHasFocus(BOOL focus); - LLUICtrl* getLastFocusForGroup(LLView* subtree_root) const; + LLView* getLastFocusForGroup(LLView* subtree_root) const; void clearLastFocusForGroup(LLView* subtree_root); // If setKeyboardFocus(NULL) is called, and there is a non-NULL default diff --git a/indra/llui/llfolderview.cpp b/indra/llui/llfolderview.cpp index 8aa1eb7cd5..20eade892d 100755 --- a/indra/llui/llfolderview.cpp +++ b/indra/llui/llfolderview.cpp @@ -185,7 +185,7 @@ LLFolderView::LLFolderView(const Params& p) mAutoOpenCandidate = NULL; mAutoOpenTimer.stop(); mKeyboardSelection = FALSE; - mIndentation = p.folder_indentation; + mIndentation = getParentFolder() ? getParentFolder()->getIndentation() + mLocalIndentation : 0; //clear label // go ahead and render root folder as usual @@ -323,9 +323,11 @@ static LLFastTimer::DeclareTimer FTM_FILTER("Filter Folder View"); void LLFolderView::filter( LLFolderViewFilter& filter ) { + // Entry point of inventory filtering (CHUI-849) LLFastTimer t2(FTM_FILTER); - filter.setFilterCount(llclamp(LLUI::sSettingGroups["config"]->getS32("FilterItemsPerFrame"), 1, 5000)); + filter.resetTime(llclamp(LLUI::sSettingGroups["config"]->getS32(mParentPanel->getVisible() ? "FilterItemsMaxTimePerFrameVisible" : "FilterItemsMaxTimePerFrameUnvisible"), 1, 100)); + // Note: we filter the model, not the view getViewModelItem()->filter(filter); } @@ -661,7 +663,7 @@ void LLFolderView::draw() // get preferable text height... S32 pixel_height = mStatusTextBox->getTextPixelHeight(); - bool height_changed = local_rect.getHeight() != pixel_height; + bool height_changed = (local_rect.getHeight() < pixel_height); if (height_changed) { // ... if it does not match current height, lets rearrange current view. @@ -1601,15 +1603,17 @@ void LLFolderView::update() { mNeedsAutoSelect = TRUE; } - // filter to determine visibility before arranging + + // Filter to determine visibility before arranging filter(getFolderViewModel()->getFilter()); - // Clear the modified setting on the filter only if the filter count is non-zero after running the filter process - // Note: if the filter count is zero, then the filter most likely halted before completing the entire set of items - if (getFolderViewModel()->getFilter().isModified() && (getFolderViewModel()->getFilter().getFilterCount() > 0)) + + // Clear the modified setting on the filter only if the filter finished after running the filter process + // Note: if the filter count has timed out, that means the filter halted before completing the entire set of items + if (getFolderViewModel()->getFilter().isModified() && (!getFolderViewModel()->getFilter().isTimedOut())) { getFolderViewModel()->getFilter().clearModified(); } - + // automatically show matching items, and select first one if we had a selection if (mNeedsAutoSelect) { @@ -1648,12 +1652,14 @@ void LLFolderView::update() } BOOL is_visible = isInVisibleChain(); - - //Puts folders/items in proper positions - if ( is_visible ) + + // Puts folders/items in proper positions + // arrange() takes the model filter flag into account and call sort() if necessary (CHUI-849) + // It also handles the open/close folder animation + if (is_visible) { sanitizeSelection(); - if( needsArrange() ) + if (needsArrange()) { S32 height = 0; S32 width = 0; diff --git a/indra/llui/llfolderviewitem.cpp b/indra/llui/llfolderviewitem.cpp index fdb4108afb..f061313645 100755 --- a/indra/llui/llfolderviewitem.cpp +++ b/indra/llui/llfolderviewitem.cpp @@ -274,6 +274,7 @@ void LLFolderViewItem::refresh() } mLabelWidthDirty = true; + // Dirty the filter flag of the model from the view (CHUI-849) vmi.dirtyFilter(); } @@ -306,7 +307,12 @@ std::set<LLFolderViewItem*> LLFolderViewItem::getSelectionList() const // addToFolder() returns TRUE if it succeeds. FALSE otherwise void LLFolderViewItem::addToFolder(LLFolderViewFolder* folder) { - folder->addItem(this); + folder->addItem(this); + + // Compute indentation since parent folder changed + mIndentation = (getParentFolder()) + ? getParentFolder()->getIndentation() + mLocalIndentation + : 0; } @@ -939,15 +945,26 @@ LLFolderViewFolder::~LLFolderViewFolder( void ) void LLFolderViewFolder::addToFolder(LLFolderViewFolder* folder) { folder->addFolder(this); + + // Compute indentation since parent folder changed + mIndentation = (getParentFolder()) + ? getParentFolder()->getIndentation() + mLocalIndentation + : 0; } static LLFastTimer::DeclareTimer FTM_ARRANGE("Arrange"); -// Finds width and height of this object and its children. Also -// makes sure that this view and its children are the right size. +// Make everything right and in the right place ready for drawing (CHUI-849) +// * Sort everything correctly if necessary +// * Turn widgets visible/invisible according to their model filtering state +// * Takes animation state into account for opening/closing of folders (this makes widgets visible/invisible) +// * Reposition visible widgets so that they line up correctly with no gap +// * Compute the width and height of the current folder and its children +// * Makes sure that this view and its children are the right size S32 LLFolderViewFolder::arrange( S32* width, S32* height ) { - // sort before laying out contents + // Sort before laying out contents + // Note that we sort from the root (CHUI-849) getRoot()->getFolderViewModel()->sort(this); LLFastTimer t2(FTM_ARRANGE); @@ -1102,7 +1119,7 @@ S32 LLFolderViewFolder::arrange( S32* width, S32* height ) BOOL LLFolderViewFolder::needsArrange() { - return mLastArrangeGeneration < getRoot()->getArrangeGeneration(); + return mLastArrangeGeneration < getRoot()->getArrangeGeneration(); } // Passes selection information on to children and record selection @@ -1613,16 +1630,13 @@ void LLFolderViewFolder::addFolder(LLFolderViewFolder* folder) } void LLFolderViewFolder::requestArrange() -{ - if ( mLastArrangeGeneration != -1) - { - mLastArrangeGeneration = -1; - // flag all items up to root - if (mParentFolder) - { - mParentFolder->requestArrange(); - } - } +{ + mLastArrangeGeneration = -1; + // flag all items up to root + if (mParentFolder) + { + mParentFolder->requestArrange(); + } } void LLFolderViewFolder::toggleOpen() diff --git a/indra/llui/llfolderviewitem.h b/indra/llui/llfolderviewitem.h index ca31931e19..a9b0201236 100755 --- a/indra/llui/llfolderviewitem.h +++ b/indra/llui/llfolderviewitem.h @@ -316,7 +316,6 @@ protected: F32 mAutoOpenCountdown; S32 mLastArrangeGeneration; S32 mLastCalculatedWidth; - S32 mMostFilteredDescendantGeneration; bool mNeedsSort; public: diff --git a/indra/llui/llfolderviewmodel.cpp b/indra/llui/llfolderviewmodel.cpp index 3593804554..3363dc5316 100755 --- a/indra/llui/llfolderviewmodel.cpp +++ b/indra/llui/llfolderviewmodel.cpp @@ -48,7 +48,7 @@ std::string LLFolderViewModelCommon::getStatusText() void LLFolderViewModelCommon::filter() { - getFilter().setFilterCount(llclamp(LLUI::sSettingGroups["config"]->getS32("FilterItemsPerFrame"), 1, 5000)); + getFilter().resetTime(llclamp(LLUI::sSettingGroups["config"]->getS32("FilterItemsMaxTimePerFrameVisible"), 1, 100)); mFolderView->getViewModelItem()->filter(getFilter()); } diff --git a/indra/llui/llfolderviewmodel.h b/indra/llui/llfolderviewmodel.h index 1b61212c0e..b1bcc8bbb4 100755 --- a/indra/llui/llfolderviewmodel.h +++ b/indra/llui/llfolderviewmodel.h @@ -87,12 +87,11 @@ public: virtual void setModified(EFilterModified behavior = FILTER_RESTART) = 0; // +-------------------------------------------------------------------+ - // + Count + // + Time // +-------------------------------------------------------------------+ - virtual void setFilterCount(S32 count) = 0; - virtual S32 getFilterCount() const = 0; - virtual void decrementFilterCount() = 0; - + virtual void resetTime(S32 timeout) = 0; + virtual bool isTimedOut() = 0; + // +-------------------------------------------------------------------+ // + Default // +-------------------------------------------------------------------+ @@ -308,26 +307,28 @@ public: virtual bool potentiallyVisible() { return passedFilter() // we've passed the filter - || getLastFilterGeneration() < mRootViewModel.getFilter().getFirstSuccessGeneration() // or we don't know yet + || (getLastFilterGeneration() < mRootViewModel.getFilter().getFirstSuccessGeneration()) // or we don't know yet || descendantsPassedFilter(); } virtual bool passedFilter(S32 filter_generation = -1) { - if (filter_generation < 0) + if (filter_generation < 0) + { filter_generation = mRootViewModel.getFilter().getFirstSuccessGeneration(); - - bool passed_folder_filter = mPassedFolderFilter && mLastFolderFilterGeneration >= filter_generation; - bool passed_filter = mPassedFilter && mLastFilterGeneration >= filter_generation; - return passed_folder_filter - && (descendantsPassedFilter(filter_generation) - || passed_filter); + } + bool passed_folder_filter = mPassedFolderFilter && (mLastFolderFilterGeneration >= filter_generation); + bool passed_filter = mPassedFilter && (mLastFilterGeneration >= filter_generation); + return passed_folder_filter && (passed_filter || descendantsPassedFilter(filter_generation)); } virtual bool descendantsPassedFilter(S32 filter_generation = -1) { - if (filter_generation < 0) filter_generation = mRootViewModel.getFilter().getFirstSuccessGeneration(); - return mMostFilteredDescendantGeneration >= filter_generation; + if (filter_generation < 0) + { + filter_generation = mRootViewModel.getFilter().getFirstSuccessGeneration(); + } + return mMostFilteredDescendantGeneration >= filter_generation; } diff --git a/indra/llui/lllayoutstack.cpp b/indra/llui/lllayoutstack.cpp index e33ac1d5c2..c89c0203b4 100755 --- a/indra/llui/lllayoutstack.cpp +++ b/indra/llui/lllayoutstack.cpp @@ -214,8 +214,15 @@ LLLayoutStack::Params::Params() open_time_constant("open_time_constant", 0.02f), close_time_constant("close_time_constant", 0.03f), resize_bar_overlap("resize_bar_overlap", 1), - border_size("border_size", LLCachedControl<S32>(*LLUI::sSettingGroups["config"], "UIResizeBarHeight", 0)) -{} + border_size("border_size", LLCachedControl<S32>(*LLUI::sSettingGroups["config"], "UIResizeBarHeight", 0)), + show_drag_handle("show_drag_handle", false), + drag_handle_first_indent("drag_handle_first_indent", 0), + drag_handle_second_indent("drag_handle_second_indent", 0), + drag_handle_thickness("drag_handle_thickness", 5), + drag_handle_shift("drag_handle_shift", 2) +{ + addSynonym(border_size, "drag_handle_gap"); +} LLLayoutStack::LLLayoutStack(const LLLayoutStack::Params& p) : LLView(p), @@ -227,8 +234,14 @@ LLLayoutStack::LLLayoutStack(const LLLayoutStack::Params& p) mClip(p.clip), mOpenTimeConstant(p.open_time_constant), mCloseTimeConstant(p.close_time_constant), - mResizeBarOverlap(p.resize_bar_overlap) -{} + mResizeBarOverlap(p.resize_bar_overlap), + mShowDragHandle(p.show_drag_handle), + mDragHandleFirstIndent(p.drag_handle_first_indent), + mDragHandleSecondIndent(p.drag_handle_second_indent), + mDragHandleThickness(p.drag_handle_thickness), + mDragHandleShift(p.drag_handle_shift) +{ +} LLLayoutStack::~LLLayoutStack() { @@ -262,6 +275,26 @@ void LLLayoutStack::draw() drawChild(panelp, 0, 0, !clip_rect.isEmpty()); } } + + const LLView::child_list_t * child_listp = getChildList(); + BOOST_FOREACH(LLView * childp, * child_listp) + { + LLResizeBar * resize_barp = dynamic_cast<LLResizeBar*>(childp); + if (resize_barp && resize_barp->isShowDragHandle() && resize_barp->getVisible() && resize_barp->getRect().isValid()) + { + LLRect screen_rect = resize_barp->calcScreenRect(); + if (LLUI::getRootView()->getLocalRect().overlaps(screen_rect) && LLUI::sDirtyRect.overlaps(screen_rect)) + { + LLUI::pushMatrix(); + { + const LLRect& rb_rect(resize_barp->getRect()); + LLUI::translate(rb_rect.mLeft, rb_rect.mBottom); + resize_barp->draw(); + } + LLUI::popMatrix(); + } + } + } } void LLLayoutStack::removeChild(LLView* view) @@ -390,7 +423,6 @@ void LLLayoutStack::updateLayout() BOOST_FOREACH(LLLayoutPanel* panelp, mPanels) { F32 panel_dim = llmax(panelp->getExpandedMinDim(), panelp->mTargetDim); - F32 panel_visible_dim = panelp->getVisibleDim(); LLRect panel_rect; if (mOrientation == HORIZONTAL) @@ -407,27 +439,61 @@ void LLLayoutStack::updateLayout() getRect().getWidth(), llround(panel_dim)); } - panelp->setIgnoreReshape(true); - panelp->setShape(panel_rect); - panelp->setIgnoreReshape(false); LLRect resize_bar_rect(panel_rect); - + LLResizeBar * resize_barp = panelp->getResizeBar(); + bool show_drag_handle = resize_barp->isShowDragHandle(); F32 panel_spacing = (F32)mPanelSpacing * panelp->getVisibleAmount(); + F32 panel_visible_dim = panelp->getVisibleDim(); + S32 panel_spacing_round = (S32)(llround(panel_spacing)); + if (mOrientation == HORIZONTAL) { - resize_bar_rect.mLeft = panel_rect.mRight - mResizeBarOverlap; - resize_bar_rect.mRight = panel_rect.mRight + (S32)(llround(panel_spacing)) + mResizeBarOverlap; - cur_pos += panel_visible_dim + panel_spacing; + + if (show_drag_handle && panel_spacing_round > mDragHandleThickness) + { + resize_bar_rect.mLeft = panel_rect.mRight + mDragHandleShift; + resize_bar_rect.mRight = resize_bar_rect.mLeft + mDragHandleThickness; + } + else + { + resize_bar_rect.mLeft = panel_rect.mRight - mResizeBarOverlap; + resize_bar_rect.mRight = panel_rect.mRight + panel_spacing_round + mResizeBarOverlap; + } + + if (show_drag_handle) + { + resize_bar_rect.mBottom += mDragHandleSecondIndent; + resize_bar_rect.mTop -= mDragHandleFirstIndent; + } + } else //VERTICAL { - resize_bar_rect.mTop = panel_rect.mBottom + mResizeBarOverlap; - resize_bar_rect.mBottom = panel_rect.mBottom - (S32)(llround(panel_spacing)) - mResizeBarOverlap; - cur_pos -= panel_visible_dim + panel_spacing; + + if (show_drag_handle && panel_spacing_round > mDragHandleThickness) + { + resize_bar_rect.mTop = panel_rect.mBottom - mDragHandleShift; + resize_bar_rect.mBottom = resize_bar_rect.mTop - mDragHandleThickness; + } + else + { + resize_bar_rect.mTop = panel_rect.mBottom + mResizeBarOverlap; + resize_bar_rect.mBottom = panel_rect.mBottom - panel_spacing_round - mResizeBarOverlap; + } + + if (show_drag_handle) + { + resize_bar_rect.mLeft += mDragHandleFirstIndent; + resize_bar_rect.mRight -= mDragHandleSecondIndent; + } } + + panelp->setIgnoreReshape(true); + panelp->setShape(panel_rect); + panelp->setIgnoreReshape(false); panelp->mResizeBar->setShape(resize_bar_rect); } @@ -475,14 +541,13 @@ void LLLayoutStack::createResizeBar(LLLayoutPanel* panelp) { if (lp->mResizeBar == NULL) { - LLResizeBar::Side side = (mOrientation == HORIZONTAL) ? LLResizeBar::RIGHT : LLResizeBar::BOTTOM; - LLResizeBar::Params resize_params; resize_params.name("resize"); resize_params.resizing_view(lp); resize_params.min_size(lp->getRelevantMinDim()); - resize_params.side(side); + resize_params.side((mOrientation == HORIZONTAL) ? LLResizeBar::RIGHT : LLResizeBar::BOTTOM); resize_params.snapping_enabled(false); + resize_params.show_drag_handle(mShowDragHandle); LLResizeBar* resize_bar = LLUICtrlFactory::create<LLResizeBar>(resize_params); lp->mResizeBar = resize_bar; LLView::addChild(resize_bar, 0); @@ -864,3 +929,4 @@ void LLLayoutStack::updateResizeBarLimits() previous_visible_panelp = visible_panelp; } } + diff --git a/indra/llui/lllayoutstack.h b/indra/llui/lllayoutstack.h index 02c664f1a0..b570974bd6 100755 --- a/indra/llui/lllayoutstack.h +++ b/indra/llui/lllayoutstack.h @@ -62,6 +62,11 @@ public: Optional<F32> open_time_constant, close_time_constant; Optional<S32> resize_bar_overlap; + Optional<bool> show_drag_handle; + Optional<S32> drag_handle_first_indent; + Optional<S32> drag_handle_second_indent; + Optional<S32> drag_handle_thickness; + Optional<S32> drag_handle_shift; Params(); }; @@ -126,6 +131,11 @@ private: F32 mCloseTimeConstant; bool mNeedsLayout; S32 mResizeBarOverlap; + bool mShowDragHandle; + S32 mDragHandleFirstIndent; + S32 mDragHandleSecondIndent; + S32 mDragHandleThickness; + S32 mDragHandleShift; }; // end class LLLayoutStack diff --git a/indra/llui/llmenugl.cpp b/indra/llui/llmenugl.cpp index f7bf39c897..f854e1785d 100755 --- a/indra/llui/llmenugl.cpp +++ b/indra/llui/llmenugl.cpp @@ -3146,6 +3146,13 @@ void LLMenuGL::showPopup(LLView* spawning_view, LLMenuGL* menu, S32 x, S32 y) const S32 CURSOR_HEIGHT = 22; // Approximate "normal" cursor size const S32 CURSOR_WIDTH = 12; + if (menu->getChildList()->empty()) + { + return; + } + + menu->setVisible( TRUE ); + //Do not show menu if all menu items are disabled BOOL item_enabled = false; for (LLView::child_list_t::const_iterator itor = menu->getChildList()->begin(); @@ -3156,8 +3163,9 @@ void LLMenuGL::showPopup(LLView* spawning_view, LLMenuGL* menu, S32 x, S32 y) item_enabled = item_enabled || menu_item->getEnabled(); } - if(menu->getChildList()->empty() || !item_enabled) + if(!item_enabled) { + menu->setVisible( FALSE ); return; } @@ -3173,8 +3181,6 @@ void LLMenuGL::showPopup(LLView* spawning_view, LLMenuGL* menu, S32 x, S32 y) menu->mFirstVisibleItem = NULL; } - menu->setVisible( TRUE ); - // Fix menu rect if needed. menu->needsArrange(); menu->arrangeAndClear(); diff --git a/indra/llui/llresizebar.cpp b/indra/llui/llresizebar.cpp index 15e56cbfe5..e67b22c977 100755 --- a/indra/llui/llresizebar.cpp +++ b/indra/llui/llresizebar.cpp @@ -28,14 +28,53 @@ #include "llresizebar.h" +#include "lllocalcliprect.h" #include "llmath.h" #include "llui.h" #include "llmenugl.h" #include "llfocusmgr.h" #include "llwindow.h" +class LLImagePanel : public LLPanel +{ +public: + struct Params : public LLInitParam::Block<Params, LLPanel::Params> + { + Optional<bool> horizontal; + Params() : horizontal("horizontal", false) {} + }; + LLImagePanel(const Params& p) : LLPanel(p), mHorizontal(p.horizontal) {} + virtual ~LLImagePanel() {} + + void draw() + { + const LLRect& parent_rect = getParent()->getRect(); + const LLRect& rect = getRect(); + LLRect clip_rect( -rect.mLeft, parent_rect.getHeight() - rect.mBottom - 2 + , parent_rect.getWidth() - rect.mLeft - (mHorizontal ? 2 : 0), -rect.mBottom); + LLLocalClipRect clip(clip_rect); + LLPanel::draw(); + } + +private: + bool mHorizontal; +}; + +static LLDefaultChildRegistry::Register<LLImagePanel> t1("resize_bar_image_panel"); + +LLResizeBar::Params::Params() +: max_size("max_size", S32_MAX), + snapping_enabled("snapping_enabled", true), + resizing_view("resizing_view"), + side("side"), + allow_double_click_snapping("allow_double_click_snapping", true), + show_drag_handle("show_drag_handle", false) +{ + name = "resize_bar"; +} + LLResizeBar::LLResizeBar(const LLResizeBar::Params& p) -: LLView(p), +: LLPanel(p), mDragLastScreenX( 0 ), mDragLastScreenY( 0 ), mLastMouseScreenX( 0 ), @@ -46,7 +85,9 @@ LLResizeBar::LLResizeBar(const LLResizeBar::Params& p) mSnappingEnabled(p.snapping_enabled), mAllowDoubleClickSnapping(p.allow_double_click_snapping), mResizingView(p.resizing_view), - mResizeListener(NULL) + mResizeListener(NULL), + mShowDragHandle(p.show_drag_handle), + mImagePanel(NULL) { setFollowsNone(); // set up some generically good follow code. @@ -75,8 +116,37 @@ LLResizeBar::LLResizeBar(const LLResizeBar::Params& p) default: break; } + + if (mShowDragHandle) + { + LLViewBorder::Params border_params; + border_params.border_thickness = 1; + border_params.highlight_light_color = LLUIColorTable::instance().getColor("ResizebarBorderLight"); + border_params.shadow_dark_color = LLUIColorTable::instance().getColor("ResizebarBorderDark"); + + addBorder(border_params); + setBorderVisible(TRUE); + + LLImagePanel::Params image_panel; + mDragHandleImage = LLUI::getUIImage(LLResizeBar::RIGHT == mSide ? "Vertical Drag Handle" : "Horizontal Drag Handle"); + image_panel.bg_alpha_image = mDragHandleImage; + image_panel.background_visible = true; + image_panel.horizontal = (LLResizeBar::BOTTOM == mSide); + mImagePanel = LLUICtrlFactory::create<LLImagePanel>(image_panel); + setImagePanel(mImagePanel); + } } +BOOL LLResizeBar::postBuild() +{ + if (mShowDragHandle) + { + setBackgroundVisible(TRUE); + setTransparentColor(LLUIColorTable::instance().getColor("ResizebarBody")); + } + + return LLPanel::postBuild(); +} BOOL LLResizeBar::handleMouseDown(S32 x, S32 y, MASK mask) { @@ -342,3 +412,39 @@ BOOL LLResizeBar::handleDoubleClick(S32 x, S32 y, MASK mask) return TRUE; } +void LLResizeBar::setImagePanel(LLPanel * panelp) +{ + const LLView::child_list_t * children = getChildList(); + if (getChildCount() == 2) + { + LLPanel * image_panelp = dynamic_cast<LLPanel*>(children->back()); + if (image_panelp) + { + removeChild(image_panelp); + delete image_panelp; + } + } + + addChild(panelp); + sendChildToBack(panelp); +} + +LLPanel * LLResizeBar::getImagePanel() const +{ + return getChildCount() > 0 ? (LLPanel *)getChildList()->back() : NULL; +} + +void LLResizeBar::draw() +{ + if (mShowDragHandle) + { + S32 image_width = mDragHandleImage->getTextureWidth(); + S32 image_height = mDragHandleImage->getTextureHeight(); + const LLRect& panel_rect = getRect(); + S32 image_left = (panel_rect.getWidth() - image_width) / 2 - 1; + S32 image_bottom = (panel_rect.getHeight() - image_height) / 2; + mImagePanel->setRect(LLRect(image_left, image_bottom + image_height, image_left + image_width, image_bottom)); + } + + LLPanel::draw(); +} diff --git a/indra/llui/llresizebar.h b/indra/llui/llresizebar.h index 8190a95a71..bcf8ea0b40 100755 --- a/indra/llui/llresizebar.h +++ b/indra/llui/llresizebar.h @@ -27,15 +27,14 @@ #ifndef LL_RESIZEBAR_H #define LL_RESIZEBAR_H -#include "llview.h" -#include "llcoord.h" +#include "llpanel.h" -class LLResizeBar : public LLView +class LLResizeBar : public LLPanel { public: enum Side { LEFT, TOP, RIGHT, BOTTOM }; - struct Params : public LLInitParam::Block<Params, LLView::Params> + struct Params : public LLInitParam::Block<Params, LLPanel::Params> { Mandatory<LLView*> resizing_view; Mandatory<Side> side; @@ -44,24 +43,19 @@ public: Optional<S32> max_size; Optional<bool> snapping_enabled; Optional<bool> allow_double_click_snapping; + Optional<bool> show_drag_handle; - Params() - : max_size("max_size", S32_MAX), - snapping_enabled("snapping_enabled", true), - resizing_view("resizing_view"), - side("side"), - allow_double_click_snapping("allow_double_click_snapping", true) - { - name = "resize_bar"; - } + Params(); }; protected: LLResizeBar(const LLResizeBar::Params& p); friend class LLUICtrlFactory; + + /*virtual*/ BOOL postBuild(); public: -// virtual void draw(); No appearance + virtual void draw(); virtual BOOL handleHover(S32 x, S32 y, MASK mask); virtual BOOL handleMouseDown(S32 x, S32 y, MASK mask); virtual BOOL handleMouseUp(S32 x, S32 y, MASK mask); @@ -72,20 +66,26 @@ public: void setAllowDoubleClickSnapping(BOOL allow) { mAllowDoubleClickSnapping = allow; } bool canResize() { return getEnabled() && mMaxSize > mMinSize; } void setResizeListener(boost::function<void(void*)> listener) {mResizeListener = listener;} + BOOL isShowDragHandle() const { return mShowDragHandle; } + void setImagePanel(LLPanel * panelp); + LLPanel * getImagePanel() const; private: - S32 mDragLastScreenX; - S32 mDragLastScreenY; - S32 mLastMouseScreenX; - S32 mLastMouseScreenY; - LLCoordGL mLastMouseDir; - S32 mMinSize; - S32 mMaxSize; - const Side mSide; - BOOL mSnappingEnabled; - BOOL mAllowDoubleClickSnapping; - LLView* mResizingView; - boost::function<void(void*)> mResizeListener; + S32 mDragLastScreenX; + S32 mDragLastScreenY; + S32 mLastMouseScreenX; + S32 mLastMouseScreenY; + LLCoordGL mLastMouseDir; + S32 mMinSize; + S32 mMaxSize; + const Side mSide; + BOOL mSnappingEnabled; + BOOL mAllowDoubleClickSnapping; + BOOL mShowDragHandle; + LLView* mResizingView; + boost::function<void(void*)> mResizeListener; + LLPointer<LLUIImage> mDragHandleImage; + LLPanel * mImagePanel; }; #endif // LL_RESIZEBAR_H diff --git a/indra/llui/lltextbase.cpp b/indra/llui/lltextbase.cpp index a45c4ced2e..c2ab01de3a 100755 --- a/indra/llui/lltextbase.cpp +++ b/indra/llui/lltextbase.cpp @@ -653,6 +653,10 @@ void LLTextBase::drawText() mSpellCheckEnd = end; } } + else + { + mMisspellRanges.clear(); + } LLTextSegmentPtr cur_segment = *seg_iter; @@ -2602,21 +2606,18 @@ void LLTextBase::setCursorAtLocalPos( S32 local_x, S32 local_y, bool round, bool void LLTextBase::changeLine( S32 delta ) { S32 line = getLineNumFromDocIndex(mCursorPos); - - S32 new_line = line; - if( (delta < 0) && (line > 0 ) ) - { - new_line = line - 1; - } - else if( (delta > 0) && (line < (getLineCount() - 1)) ) - { - new_line = line + 1; - } - - LLRect visible_region = getVisibleDocumentRect(); - - S32 new_cursor_pos = getDocIndexFromLocalCoord(mDesiredXPixel, mLineInfoList[new_line].mRect.mBottom + mVisibleTextRect.mBottom - visible_region.mBottom, TRUE); - setCursorPos(new_cursor_pos, true); + S32 max_line_nb = getLineCount() - 1; + max_line_nb = (max_line_nb < 0 ? 0 : max_line_nb); + + S32 new_line = llclamp(line + delta, 0, max_line_nb); + + if (new_line != line) + { + LLRect visible_region = getVisibleDocumentRect(); + S32 new_cursor_pos = getDocIndexFromLocalCoord(mDesiredXPixel, + mLineInfoList[new_line].mRect.mBottom + mVisibleTextRect.mBottom - visible_region.mBottom, TRUE); + setCursorPos(new_cursor_pos, true); + } } bool LLTextBase::scrolledToStart() diff --git a/indra/llwindow/llwindowwin32.cpp b/indra/llwindow/llwindowwin32.cpp index 43c0090993..ca9410de2e 100755 --- a/indra/llwindow/llwindowwin32.cpp +++ b/indra/llwindow/llwindowwin32.cpp @@ -159,9 +159,8 @@ LLWinImm LLWinImm::sTheInstance; LLWinImm::LLWinImm() : mHImmDll(NULL) { // Check system metrics - if ( !GetSystemMetrics( SM_DBCSENABLED ) ) + if ( !GetSystemMetrics( SM_IMMENABLED ) ) return; - mHImmDll = LoadLibraryA("Imm32"); if (mHImmDll != NULL) diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 227644f14f..477600ca01 100755 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -188,6 +188,7 @@ set(viewer_SOURCE_FILES llexpandabletextbox.cpp llexternaleditor.cpp llface.cpp + llfacebookconnect.cpp llfasttimerview.cpp llfavoritesbar.cpp llfeaturemanager.cpp @@ -461,6 +462,9 @@ set(viewer_SOURCE_FILES llpathfindingobjectlist.cpp llpathfindingpathtool.cpp llpersistentnotificationstorage.cpp + llpersonfolderview.cpp + llpersonmodelcommon.cpp + llpersontabview.cpp llphysicsmotion.cpp llphysicsshapebuilderutil.cpp llpipelinelistener.cpp @@ -503,6 +507,7 @@ set(viewer_SOURCE_FILES llsidetraypanelcontainer.cpp llsky.cpp llslurl.cpp + llsociallist.cpp llspatialpartition.cpp llspeakers.cpp llspeakingindicatormanager.cpp @@ -769,6 +774,7 @@ set(viewer_HEADER_FILES llexpandabletextbox.h llexternaleditor.h llface.h + llfacebookconnect.h llfasttimerview.h llfavoritesbar.h llfeaturemanager.h @@ -1030,6 +1036,9 @@ set(viewer_HEADER_FILES llpathfindingobjectlist.h llpathfindingpathtool.h llpersistentnotificationstorage.h + llpersonfolderview.h + llpersonmodelcommon.h + llpersontabview.h llphysicsmotion.h llphysicsshapebuilderutil.h llpipelinelistener.h @@ -1073,6 +1082,7 @@ set(viewer_HEADER_FILES llsidetraypanelcontainer.h llsky.h llslurl.h + llsociallist.h llspatialpartition.h llspeakers.h llspeakingindicatormanager.h diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index f356cff9d8..5019f632be 100755 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -3468,16 +3468,27 @@ <key>Value</key> <real>10.0</real> </map> - <key>FilterItemsPerFrame</key> + <key>FilterItemsMaxTimePerFrameVisible</key> <map> - <key>Comment</key> - <string>Maximum number of inventory items to match against search filter every frame (lower to increase framerate while searching, higher to improve search speed)</string> - <key>Persist</key> - <integer>1</integer> - <key>Type</key> - <string>S32</string> - <key>Value</key> - <integer>500</integer> + <key>Comment</key> + <string>Max time devoted to items filtering per frame for visible inventory listings (in milliseconds)</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>S32</string> + <key>Value</key> + <integer>10</integer> + </map> + <key>FilterItemsMaxTimePerFrameUnvisible</key> + <map> + <key>Comment</key> + <string>Max time devoted to items filtering per frame for non visible inventory listings (in milliseconds)</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>S32</string> + <key>Value</key> + <integer>1</integer> </map> <key>FindLandArea</key> <map> diff --git a/indra/newview/llagent.cpp b/indra/newview/llagent.cpp index 8c42defa73..d16945070a 100755 --- a/indra/newview/llagent.cpp +++ b/indra/newview/llagent.cpp @@ -46,6 +46,7 @@ #include "llenvmanager.h" #include "llfirstuse.h" #include "llfloatercamera.h" +#include "llfloaterimcontainer.h" #include "llfloaterreg.h" #include "llfloatertools.h" #include "llgroupactions.h" @@ -91,6 +92,7 @@ #include "llworld.h" #include "llworldmap.h" #include "stringize.h" +#include "boost/foreach.hpp" using namespace LLAvatarAppearanceDefines; @@ -2037,7 +2039,16 @@ void LLAgent::endAnimationUpdateUI() { skip_list.insert(LLFloaterReg::findInstance("mini_map")); } - + + LLFloaterIMContainer* im_box = LLFloaterReg::getTypedInstance<LLFloaterIMContainer>("im_container"); + LLFloaterIMContainer::floater_list_t conversations; + im_box->getDetachedConversationFloaters(conversations); + BOOST_FOREACH(LLFloater* conversation, conversations) + { + llinfos << "skip_list.insert(session_floater): " << conversation->getTitle() << llendl; + skip_list.insert(conversation); + } + gFloaterView->popVisibleAll(skip_list); #endif mViewsPushed = FALSE; diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index fdc2cdb78d..dd52050ed4 100755 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -2634,20 +2634,38 @@ bool LLAppViewer::initConfiguration() // What can happen is that someone can use IE (or potentially // other browsers) and do the rough equivalent of command // injection and steal passwords. Phoenix. SL-55321 + + LLSLURL option_slurl; + if(clp.hasOption("url")) { - LLStartUp::setStartSLURL(LLSLURL(clp.getOption("url")[0])); + option_slurl = LLSLURL(clp.getOption("url")[0]); + LLStartUp::setStartSLURL(option_slurl); if(LLStartUp::getStartSLURL().getType() == LLSLURL::LOCATION) { LLGridManager::getInstance()->setGridChoice(LLStartUp::getStartSLURL().getGrid()); - - } + } } else if(clp.hasOption("slurl")) { - LLSLURL start_slurl(clp.getOption("slurl")[0]); - LLStartUp::setStartSLURL(start_slurl); + option_slurl = LLSLURL(clp.getOption("slurl")[0]); + LLStartUp::setStartSLURL(option_slurl); } + + //RN: if we received a URL, hand it off to the existing instance. + // don't call anotherInstanceRunning() when doing URL handoff, as + // it relies on checking a marker file which will not work when running + // out of different directories + + if (option_slurl.isValid() && + (gSavedSettings.getBOOL("SLURLPassToOtherInstance"))) + { + if (sendURLToOtherInstance(option_slurl.getSLURLString())) + { + // successfully handed off URL to existing instance, exit + return false; + } + } const LLControlVariable* skinfolder = gSavedSettings.getControl("SkinCurrent"); if(skinfolder && LLStringUtil::null != skinfolder->getValue().asString()) diff --git a/indra/newview/llconversationmodel.cpp b/indra/newview/llconversationmodel.cpp index c74ce24872..6e95df8383 100755 --- a/indra/newview/llconversationmodel.cpp +++ b/indra/newview/llconversationmodel.cpp @@ -386,6 +386,10 @@ void LLConversationItemSession::buildContextMenu(LLMenuGL& menu, U32 flags) addVoiceOptions(items); items.push_back(std::string("chat_history")); } + else if(this->getType() == CONV_SESSION_NEARBY) + { + items.push_back(std::string("chat_history")); + } hide_context_entries(menu, items, disabled_items); } diff --git a/indra/newview/llconversationmodel.h b/indra/newview/llconversationmodel.h index 8766585049..d8cdcdfc97 100755 --- a/indra/newview/llconversationmodel.h +++ b/indra/newview/llconversationmodel.h @@ -252,11 +252,10 @@ public: const std::string& getName() const { return mEmpty; } const std::string& getFilterText() { return mEmpty; } void setModified(EFilterModified behavior = FILTER_RESTART) { } - - void setFilterCount(S32 count) { } - S32 getFilterCount() const { return 0; } - void decrementFilterCount() { } - + + void resetTime(S32 timeout) { } + bool isTimedOut() { return false; } + bool isDefault() const { return true; } bool isNotDefault() const { return false; } void markDefault() { } diff --git a/indra/newview/llconversationview.cpp b/indra/newview/llconversationview.cpp index b6c53e5e30..42104ea20a 100755 --- a/indra/newview/llconversationview.cpp +++ b/indra/newview/llconversationview.cpp @@ -118,6 +118,13 @@ void LLConversationViewSession::setFlashState(bool flash_state) mFlashTimer->stopFlashing(); } +void LLConversationViewSession::setHighlightState(bool hihglight_state) +{ + mFlashStateOn = hihglight_state; + mFlashStarted = true; + mFlashTimer->stopFlashing(); +} + void LLConversationViewSession::startFlashing() { if (isInVisibleChain() && mFlashStateOn && !mFlashStarted) @@ -340,16 +347,20 @@ void LLConversationViewSession::setVisibleIfDetached(BOOL visible) { // Do this only if the conversation floater has been torn off (i.e. no multi floater host) and is not minimized // Note: minimized dockable floaters are brought to front hence unminimized when made visible and we don't want that here - LLFolderViewModelItem* item = mViewModelItem; - LLUUID session_uuid = dynamic_cast<LLConversationItem*>(item)->getUUID(); - LLFloater* session_floater = LLFloaterIMSessionTab::getConversation(session_uuid); - - if (session_floater && !session_floater->getHost() && !session_floater->isMinimized()) + LLFloater* session_floater = getSessionFloater(); + if (session_floater && session_floater->isDetachedAndNotMinimized()) { session_floater->setVisible(visible); } } +LLFloater* LLConversationViewSession::getSessionFloater() +{ + LLFolderViewModelItem* item = mViewModelItem; + LLUUID session_uuid = dynamic_cast<LLConversationItem*>(item)->getUUID(); + return LLFloaterIMSessionTab::getConversation(session_uuid); +} + LLConversationViewParticipant* LLConversationViewSession::findParticipant(const LLUUID& participant_id) { // This is *not* a general tree parsing algorithm. We search only in the mItems list diff --git a/indra/newview/llconversationview.h b/indra/newview/llconversationview.h index 3eb2e63792..879d496dc7 100755 --- a/indra/newview/llconversationview.h +++ b/indra/newview/llconversationview.h @@ -86,6 +86,9 @@ public: virtual void refresh(); /*virtual*/ void setFlashState(bool flash_state); + void setHighlightState(bool hihglight_state); + + LLFloater* getSessionFloater(); private: diff --git a/indra/newview/lldonotdisturbnotificationstorage.cpp b/indra/newview/lldonotdisturbnotificationstorage.cpp index 82affcf068..71bc4f15d2 100755 --- a/indra/newview/lldonotdisturbnotificationstorage.cpp +++ b/indra/newview/lldonotdisturbnotificationstorage.cpp @@ -115,7 +115,8 @@ void LLDoNotDisturbNotificationStorage::saveNotifications() { LLNotificationPtr notificationPtr = historyIter->second; - if (!notificationPtr->isRespondedTo() && !notificationPtr->isCancelled() && !notificationPtr->isExpired()) + if (!notificationPtr->isRespondedTo() && !notificationPtr->isCancelled() && + !notificationPtr->isExpired() && !notificationPtr->isPersistent()) { data.append(notificationPtr->asLLSD(true)); } @@ -210,12 +211,8 @@ void LLDoNotDisturbNotificationStorage::loadNotifications() } - if(imToastExists) - { - LLFloaterReg::showInstance("im_container"); - } - - if(group_ad_hoc_toast_exists) + bool isConversationLoggingAllowed = gSavedPerAccountSettings.getS32("KeepConversationLogTranscripts") > 0; + if(group_ad_hoc_toast_exists && isConversationLoggingAllowed) { LLFloaterReg::showInstance("conversation"); } @@ -266,11 +263,6 @@ void LLDoNotDisturbNotificationStorage::updateNotifications() } } - if(imToastExists) - { - LLFloaterReg::showInstance("im_container"); - } - if(imToastExists || offerExists) { make_ui_sound("UISndNewIncomingIMSession"); diff --git a/indra/newview/llfacebookconnect.cpp b/indra/newview/llfacebookconnect.cpp new file mode 100644 index 0000000000..86da748f0f --- /dev/null +++ b/indra/newview/llfacebookconnect.cpp @@ -0,0 +1,322 @@ +/** + * @file llfacebookconnect.h + * @author Merov, Cho, Gil + * @brief Connection to Facebook Service + * + * $LicenseInfo:firstyear=2013&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2013, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfacebookconnect.h" + +#include "llagent.h" +#include "llcallingcard.h" // for LLAvatarTracker +#include "llcommandhandler.h" +#include "llhttpclient.h" +#include "llurlaction.h" + +/////////////////////////////////////////////////////////////////////////////// +// + +class LLFacebookConnectHandler : public LLCommandHandler +{ +public: + LLFacebookConnectHandler() : LLCommandHandler("fbc", UNTRUSTED_THROTTLE) { } + + bool handle(const LLSD& tokens, const LLSD& query_map, LLMediaCtrl* web) + { + if (tokens.size() > 0) + { + if (tokens[0].asString() == "connect") + { + if (query_map.has("code")) + { + LLFacebookConnect::instance().connectToFacebook(query_map["code"]); + } + return true; + } + } + return false; + } +}; +LLFacebookConnectHandler gFacebookConnectHandler; + +/////////////////////////////////////////////////////////////////////////////// +// +class LLFacebookConnectResponder : public LLHTTPClient::Responder +{ + LOG_CLASS(LLFacebookConnectResponder); +public: + + virtual void completed(U32 status, const std::string& reason, const LLSD& content) + { + if (isGoodStatus(status)) + { + LL_DEBUGS("FacebookConnect") << "Connect successful. content: " << content << LL_ENDL; + + // Grab some graph data now that we are connected + LLFacebookConnect::instance().setConnected(true); + LLFacebookConnect::instance().loadFacebookFriends(); + } + else + { + LL_WARNS("FacebookConnect") << "Failed to get a response. reason: " << reason << " status: " << status << LL_ENDL; + } + } + + void completedHeader(U32 status, const std::string& reason, const LLSD& content) + { + if (status == 302) + { + LLFacebookConnect::instance().openFacebookWeb(content["location"]); + } + } + +}; + +/////////////////////////////////////////////////////////////////////////////// +// +class LLFacebookPostResponder : public LLHTTPClient::Responder +{ + LOG_CLASS(LLFacebookPostResponder); +public: + + virtual void completed(U32 status, const std::string& reason, const LLSD& content) + { + if (isGoodStatus(status)) + { + LL_DEBUGS("FacebookConnect") << "Post successful. content: " << content << LL_ENDL; + } + else + { + LL_WARNS("FacebookConnect") << "Failed to get a post response. reason: " << reason << " status: " << status << LL_ENDL; + } + } + + void completedHeader(U32 status, const std::string& reason, const LLSD& content) + { + if (status == 302) + { + LLFacebookConnect::instance().openFacebookWeb(content["location"]); + } + } + +}; + +/////////////////////////////////////////////////////////////////////////////// +// +class LLFacebookDisconnectResponder : public LLHTTPClient::Responder +{ + LOG_CLASS(LLFacebookDisconnectResponder); +public: + + virtual void completed(U32 status, const std::string& reason, const LLSD& content) + { + if (isGoodStatus(status)) + { + LL_DEBUGS("FacebookConnect") << "Disconnect successful. content: " << content << LL_ENDL; + + // Clear all facebook stuff + LLFacebookConnect::instance().setConnected(false); + LLFacebookConnect::instance().clearContent(); + } + else + { + LL_WARNS("FacebookConnect") << "Failed to get a response. reason: " << reason << " status: " << status << LL_ENDL; + } + } +}; + +/////////////////////////////////////////////////////////////////////////////// +// +class LLFacebookConnectedResponder : public LLHTTPClient::Responder +{ + LOG_CLASS(LLFacebookConnectedResponder); +public: + + LLFacebookConnectedResponder(bool show_login_if_not_connected) : mShowLoginIfNotConnected(show_login_if_not_connected) {} + + virtual void completed(U32 status, const std::string& reason, const LLSD& content) + { + if (isGoodStatus(status)) + { + LL_DEBUGS("FacebookConnect") << "Connect successful. content: " << content << LL_ENDL; + + // Grab some graph data if already connected + LLFacebookConnect::instance().setConnected(true); + LLFacebookConnect::instance().loadFacebookFriends(); + } + else + { + LL_WARNS("FacebookConnect") << "Failed to get a response. reason: " << reason << " status: " << status << LL_ENDL; + + // show the facebook login page if not connected yet + if ((status == 404) && mShowLoginIfNotConnected) + { + LLFacebookConnect::instance().connectToFacebook(); + } + } + } + +private: + bool mShowLoginIfNotConnected; +}; + +/////////////////////////////////////////////////////////////////////////////// +// +class LLFacebookFriendsResponder : public LLHTTPClient::Responder +{ + LOG_CLASS(LLFacebookFriendsResponder); +public: + + virtual void completed(U32 status, const std::string& reason, const LLSD& content) + { + if (isGoodStatus(status)) + { + LL_DEBUGS("FacebookConnect") << "Getting Facebook friends successful. content: " << content << LL_ENDL; + LLFacebookConnect::instance().storeContent(content); + } + else + { + LL_WARNS("FacebookConnect") << "Failed to get a response. reason: " << reason << " status: " << status << LL_ENDL; + } + } + + void completedHeader(U32 status, const std::string& reason, const LLSD& content) + { + if (status == 302) + { + LLFacebookConnect::instance().openFacebookWeb(content["location"]); + } + } +}; + + +/////////////////////////////////////////////////////////////////////////////// +// +LLFacebookConnect::LLFacebookConnect() +: mConnectedToFbc(false), + mContent(), + mGeneration(0) +{ +} + +void LLFacebookConnect::openFacebookWeb(std::string url) +{ + LLUrlAction::openURLExternal(url); +} + +std::string LLFacebookConnect::getFacebookConnectURL(const std::string& route) +{ + //static std::string sFacebookConnectUrl = gAgent.getRegion()->getCapability("FacebookConnect"); + static std::string sFacebookConnectUrl = "https://pdp15.lindenlab.com/fbc/agent/" + gAgentID.asString(); // TEMPORARY HACK FOR FB DEMO - Cho + std::string url = sFacebookConnectUrl + route; + llinfos << url << llendl; + return url; +} + +void LLFacebookConnect::connectToFacebook(const std::string& auth_code) +{ + LLSD body; + if (!auth_code.empty()) + body["code"] = auth_code; + + LLHTTPClient::put(getFacebookConnectURL("/connection"), body, new LLFacebookConnectResponder()); +} + +void LLFacebookConnect::disconnectFromFacebook() +{ + LLHTTPClient::del(getFacebookConnectURL("/connection"), new LLFacebookDisconnectResponder()); +} + +void LLFacebookConnect::tryToReconnectToFacebook() +{ + if (!mConnectedToFbc) + { + const bool follow_redirects=false; + const F32 timeout=HTTP_REQUEST_EXPIRY_SECS; + LLHTTPClient::get(getFacebookConnectURL("/connection"), new LLFacebookConnectedResponder(false), + LLSD(), timeout, follow_redirects); + } +} + +void LLFacebookConnect::getConnectionToFacebook() +{ + const bool follow_redirects=false; + const F32 timeout=HTTP_REQUEST_EXPIRY_SECS; + LLHTTPClient::get(getFacebookConnectURL("/connection"), new LLFacebookConnectedResponder(true), + LLSD(), timeout, follow_redirects); +} + +void LLFacebookConnect::loadFacebookFriends() +{ + const bool follow_redirects=false; + const F32 timeout=HTTP_REQUEST_EXPIRY_SECS; + LLHTTPClient::get(getFacebookConnectURL("/friend"), new LLFacebookFriendsResponder(), + LLSD(), timeout, follow_redirects); +} + +void LLFacebookConnect::postCheckinMessage(const std::string& message, const std::string& link, const std::string& name, const std::string& caption, const std::string& description, const std::string& picture) +{ + LLSD body; + if (!message.empty()) + body["message"] = message; + if (!link.empty()) + body["link"] = link; + if (!name.empty()) + body["name"] = name; + if (!caption.empty()) + body["caption"] = caption; + if (!description.empty()) + body["description"] = description; + if (!picture.empty()) + body["picture"] = picture; + + // Note: we can use that route for different publish action. We should be able to use the same responder. + LLHTTPClient::post(getFacebookConnectURL("/share/wall"), body, new LLFacebookPostResponder()); +} + +void LLFacebookConnect::storeContent(const LLSD& content) +{ + mGeneration++; + mContent = content; +} + +const LLSD& LLFacebookConnect::getContent() const +{ + return mContent; +} + +void LLFacebookConnect::clearContent() +{ + mGeneration++; + mContent = LLSD(); +} + + + + + + + + diff --git a/indra/newview/llfacebookconnect.h b/indra/newview/llfacebookconnect.h new file mode 100644 index 0000000000..8886630222 --- /dev/null +++ b/indra/newview/llfacebookconnect.h @@ -0,0 +1,73 @@ +/** + * @file llfacebookconnect.h + * @author Merov, Cho, Gil + * @brief Connection to Facebook Service + * + * $LicenseInfo:firstyear=2013&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2013, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLFACEBOOKCONNECT_H +#define LL_LLFACEBOOKCONNECT_H + +#include "llsingleton.h" + +/** + * @class LLFacebookConnect + * + * Manages authentication to, and interaction with, a web service allowing the + * the viewer to get Facebook OpenGraph data. + */ +class LLFacebookConnect : public LLSingleton<LLFacebookConnect> +{ + LOG_CLASS(LLFacebookConnect); +public: + void connectToFacebook(const std::string& auth_code = ""); + void disconnectFromFacebook(); + void tryToReconnectToFacebook(); + void getConnectionToFacebook(); + + void loadFacebookFriends(); + void postCheckinMessage(const std::string& message, const std::string& link, const std::string& name, const std::string& caption, const std::string& description, const std::string& picture); + + void clearContent(); + void storeContent(const LLSD& content); + const LLSD& getContent() const; + + void setConnected(bool connected) { mConnectedToFbc = connected; } + bool getConnected() { return mConnectedToFbc; } + S32 generation() { return mGeneration; } + + void openFacebookWeb(std::string url); +private: + + friend class LLSingleton<LLFacebookConnect>; + + LLFacebookConnect(); + ~LLFacebookConnect() {}; + std::string getFacebookConnectURL(const std::string& route = ""); + + bool mConnectedToFbc; + LLSD mContent; + S32 mGeneration; +}; + +#endif // LL_LLFACEBOOKCONNECT_H diff --git a/indra/newview/llfloaterimcontainer.cpp b/indra/newview/llfloaterimcontainer.cpp index 58817485fb..21ba3a444b 100755 --- a/indra/newview/llfloaterimcontainer.cpp +++ b/indra/newview/llfloaterimcontainer.cpp @@ -54,6 +54,7 @@ #include "llworld.h" #include "llsdserialize.h" #include "llviewerobjectlist.h" +#include "boost/foreach.hpp" // // LLFloaterIMContainer @@ -63,7 +64,8 @@ LLFloaterIMContainer::LLFloaterIMContainer(const LLSD& seed, const Params& param mExpandCollapseBtn(NULL), mConversationsRoot(NULL), mConversationsEventStream("ConversationsEvents"), - mInitialized(false) + mInitialized(false), + mIsFirstLaunch(true) { mEnableCallbackRegistrar.add("IMFloaterContainer.Check", boost::bind(&LLFloaterIMContainer::isActionChecked, this, _2)); mCommitCallbackRegistrar.add("IMFloaterContainer.Action", boost::bind(&LLFloaterIMContainer::onCustomAction, this, _2)); @@ -204,6 +206,7 @@ BOOL LLFloaterIMContainer::postBuild() // a scroller for folder view LLRect scroller_view_rect = mConversationsListPanel->getRect(); scroller_view_rect.translate(-scroller_view_rect.mLeft, -scroller_view_rect.mBottom); + scroller_view_rect.mBottom += getChild<LLLayoutStack>("conversations_pane_buttons_stack")->getRect().getHeight(); LLScrollContainer::Params scroller_params(LLUICtrlFactory::getDefaultParams<LLFolderViewScrollContainer>()); scroller_params.rect(scroller_view_rect); @@ -221,7 +224,8 @@ BOOL LLFloaterIMContainer::postBuild() mExpandCollapseBtn->setClickedCallback(boost::bind(&LLFloaterIMContainer::onExpandCollapseButtonClicked, this)); mStubCollapseBtn = getChild<LLButton>("stub_collapse_btn"); mStubCollapseBtn->setClickedCallback(boost::bind(&LLFloaterIMContainer::onStubCollapseButtonClicked, this)); - getChild<LLButton>("speak_btn")->setClickedCallback(boost::bind(&LLFloaterIMContainer::onSpeakButtonClicked, this)); + mSpeakBtn = getChild<LLButton>("speak_btn"); + mSpeakBtn->setClickedCallback(boost::bind(&LLFloaterIMContainer::onSpeakButtonClicked, this)); childSetAction("add_btn", boost::bind(&LLFloaterIMContainer::onAddButtonClicked, this)); @@ -342,8 +346,11 @@ void LLFloaterIMContainer::onStubCollapseButtonClicked() void LLFloaterIMContainer::onSpeakButtonClicked() { - LLAgent::toggleMicrophone("speak"); - updateSpeakBtnState(); + //LLAgent::toggleMicrophone("speak"); + //updateSpeakBtnState(); + + LLParticipantList* session_model = dynamic_cast<LLParticipantList*>(mConversationsItems[LLUUID(NULL)]); + session_model->addTestAvatarAgents(); } void LLFloaterIMContainer::onExpandCollapseButtonClicked() { @@ -659,10 +666,32 @@ void LLFloaterIMContainer::setVisible(BOOL visible) LLMultiFloater::setVisible(visible); } +void LLFloaterIMContainer::getDetachedConversationFloaters(floater_list_t& floaters) +{ + typedef conversations_widgets_map::value_type conv_pair; + BOOST_FOREACH(conv_pair item, mConversationsWidgets) + { + LLConversationViewSession* widget = dynamic_cast<LLConversationViewSession*>(item.second); + if (widget) + { + LLFloater* session_floater = widget->getSessionFloater(); + if (session_floater && session_floater->isDetachedAndNotMinimized()) + { + floaters.push_back(session_floater); + } + } + } +} + void LLFloaterIMContainer::setVisibleAndFrontmost(BOOL take_focus, const LLSD& key) { LLMultiFloater::setVisibleAndFrontmost(take_focus, key); selectConversationPair(getSelectedSession(), false, take_focus); + if (mInitialized && mIsFirstLaunch) + { + collapseMessagesPane(gSavedPerAccountSettings.getBOOL("ConversationsMessagePaneCollapsed")); + mIsFirstLaunch = false; + } } void LLFloaterIMContainer::updateResizeLimits() @@ -779,13 +808,6 @@ void LLFloaterIMContainer::reshapeFloaterAndSetResizeLimits(bool collapse, S32 d setCanMinimize(at_least_one_panel_is_expanded); assignResizeLimits(); - - // force set correct size for the title after show/hide minimize button - LLRect cur_rect = getRect(); - LLRect force_rect = cur_rect; - force_rect.mRight = cur_rect.mRight + 1; - setRect(force_rect); - setRect(cur_rect); } void LLFloaterIMContainer::assignResizeLimits() @@ -793,15 +815,12 @@ void LLFloaterIMContainer::assignResizeLimits() bool is_conv_pane_expanded = !mConversationsPane->isCollapsed(); bool is_msg_pane_expanded = !mMessagesPane->isCollapsed(); - // With two panels visible number of borders is three, because the borders - // between the panels are merged into one - S32 number_of_visible_borders = llmin((is_conv_pane_expanded? 2 : 0) + (is_msg_pane_expanded? 2 : 0), 3); - S32 summary_width_of_visible_borders = number_of_visible_borders * LLPANEL_BORDER_WIDTH; - S32 conv_pane_target_width = is_conv_pane_expanded? - (is_msg_pane_expanded? - mConversationsPane->getRect().getWidth() - : mConversationsPane->getExpandedMinDim()) - : mConversationsPane->getMinDim(); + S32 summary_width_of_visible_borders = (is_msg_pane_expanded ? mConversationsStack->getPanelSpacing() : 0) + 1; + + S32 conv_pane_target_width = is_conv_pane_expanded + ? ( is_msg_pane_expanded?mConversationsPane->getRect().getWidth():mConversationsPane->getExpandedMinDim() ) + : mConversationsPane->getMinDim(); + S32 msg_pane_min_width = is_msg_pane_expanded ? mMessagesPane->getExpandedMinDim() : 0; S32 new_min_width = conv_pane_target_width + msg_pane_min_width + summary_width_of_visible_borders; @@ -1143,7 +1162,7 @@ void LLFloaterIMContainer::doToSelectedConversation(const std::string& command, } else if("chat_history" == command) { - if (selectedIDS.size() > 0) + if (selectedIDS.size() > 0) { LLAvatarActions::viewChatHistory(selectedIDS.front()); } @@ -1156,6 +1175,17 @@ void LLFloaterIMContainer::doToSelectedConversation(const std::string& command, } } } + //if there is no LLFloaterIMSession* instance for selected conversation it might be Nearby chat + else + { + if(conversationItem->getType() == LLConversationItem::CONV_SESSION_NEARBY) + { + if("chat_history" == command) + { + LLFloaterReg::showInstance("preview_conversation", LLSD(LLUUID::null), true); + } + } + } } void LLFloaterIMContainer::doToSelected(const LLSD& userdata) @@ -1211,7 +1241,19 @@ bool LLFloaterIMContainer::enableContextMenuItem(const LLSD& userdata) //Enable Chat history item for ad-hoc and group conversations if ("can_chat_history" == item && uuids.size() > 0) { - return LLLogChat::isTranscriptExist(uuids.front()); + //Disable menu item if selected participant is user agent + if(uuids.front() != gAgentID) + { + if (getCurSelectedViewModelItem()->getType() == LLConversationItem::CONV_SESSION_NEARBY) + { + return LLLogChat::isNearbyTranscriptExist(); + } + else + { + bool is_group = (getCurSelectedViewModelItem()->getType() == LLConversationItem::CONV_SESSION_GROUP); + return LLLogChat::isTranscriptExist(uuids.front(),is_group); + } + } } // If nothing is selected(and selected item is not group chat), everything needs to be disabled @@ -1904,7 +1946,6 @@ void LLFloaterIMContainer::reSelectConversation() void LLFloaterIMContainer::updateSpeakBtnState() { - LLButton* mSpeakBtn = getChild<LLButton>("speak_btn"); mSpeakBtn->setToggleState(LLVoiceClient::getInstance()->getUserPTTState()); mSpeakBtn->setEnabled(LLAgent::isActionAllowed("speak")); } @@ -1925,6 +1966,17 @@ void LLFloaterIMContainer::flashConversationItemWidget(const LLUUID& session_id, } } +void LLFloaterIMContainer::highlightConversationItemWidget(const LLUUID& session_id, bool is_highlighted) +{ + //Finds the conversation line item to highlight using the session_id + LLConversationViewSession * widget = dynamic_cast<LLConversationViewSession *>(get_ptr_in_map(mConversationsWidgets,session_id)); + + if (widget) + { + widget->setHighlightState(is_highlighted); + } +} + bool LLFloaterIMContainer::isScrolledOutOfSight(LLConversationViewSession* conversation_item_widget) { llassert(conversation_item_widget != NULL); @@ -1940,23 +1992,28 @@ bool LLFloaterIMContainer::isScrolledOutOfSight(LLConversationViewSession* conve BOOL LLFloaterIMContainer::handleKeyHere(KEY key, MASK mask ) { + BOOL handled = FALSE; + if(mask == MASK_ALT) { if (KEY_RETURN == key ) { expandConversation(); + handled = TRUE; } if ((KEY_DOWN == key ) || (KEY_RIGHT == key)) { selectNextorPreviousConversation(true); + handled = TRUE; } if ((KEY_UP == key) || (KEY_LEFT == key)) { selectNextorPreviousConversation(false); + handled = TRUE; } } - return TRUE; + return handled; } bool LLFloaterIMContainer::selectAdjacentConversation(bool focus_selected) @@ -2013,7 +2070,9 @@ void LLFloaterIMContainer::expandConversation() } } -void LLFloaterIMContainer::closeFloater(bool app_quitting/* = false*/) +// For conversations, closeFloater() (linked to Ctrl-W) does not actually close the floater but the active conversation. +// This is intentional so it doesn't confuse the user. onClickCloseBtn() closes the whole floater. +void LLFloaterIMContainer::onClickCloseBtn() { // Always unminimize before trying to close. // Most of the time the user will never see this state. @@ -2022,7 +2081,31 @@ void LLFloaterIMContainer::closeFloater(bool app_quitting/* = false*/) LLMultiFloater::setMinimized(FALSE); } - LLFloater::closeFloater(app_quitting); + LLFloater::closeFloater(); +} + +void LLFloaterIMContainer::closeFloater(bool app_quitting/* = false*/) +{ + // Check for currently active session + LLUUID session_id = getSelectedSession(); + // If current session is Nearby Chat or there is only one session remaining, close the floater + if (mConversationsItems.size() == 1 || session_id == LLUUID() || app_quitting) + { + onClickCloseBtn(); + } + + // Otherwise, close current conversation + LLFloaterIMSessionTab* active_conversation = LLFloaterIMSessionTab::getConversation(session_id); + if (active_conversation) + { + active_conversation->closeFloater(); + } +} + +void LLFloaterIMContainer::handleReshape(const LLRect& rect, bool by_user) +{ + LLMultiFloater::handleReshape(rect, by_user); + storeRectControl(); } // EOF diff --git a/indra/newview/llfloaterimcontainer.h b/indra/newview/llfloaterimcontainer.h index e39d20ec35..74c3640bad 100755 --- a/indra/newview/llfloaterimcontainer.h +++ b/indra/newview/llfloaterimcontainer.h @@ -63,6 +63,8 @@ public: /*virtual*/ void setVisible(BOOL visible); /*virtual*/ void setVisibleAndFrontmost(BOOL take_focus=TRUE, const LLSD& key = LLSD()); /*virtual*/ void updateResizeLimits(); + /*virtual*/ void handleReshape(const LLRect& rect, bool by_user); + void onCloseFloater(LLUUID& id); /*virtual*/ void addFloater(LLFloater* floaterp, @@ -130,6 +132,7 @@ private: void onStubCollapseButtonClicked(); void processParticipantsStyleUpdate(); void onSpeakButtonClicked(); + /*virtual*/ void onClickCloseBtn(); void collapseConversationsPane(bool collapse, bool save_is_allowed=true); @@ -169,6 +172,7 @@ private: LLButton* mExpandCollapseBtn; LLButton* mStubCollapseBtn; + LLButton* mSpeakBtn; LLPanel* mStubPanel; LLTextBox* mStubTextBox; LLLayoutPanel* mMessagesPane; @@ -176,6 +180,7 @@ private: LLLayoutStack* mConversationsStack; bool mInitialized; + bool mIsFirstLaunch; LLUUID mSelectedSession; std::string mGeneralTitle; @@ -190,9 +195,12 @@ public: void updateSpeakBtnState(); static bool isConversationLoggingAllowed(); void flashConversationItemWidget(const LLUUID& session_id, bool is_flashes); + void highlightConversationItemWidget(const LLUUID& session_id, bool is_highlighted); bool isScrolledOutOfSight(LLConversationViewSession* conversation_item_widget); boost::signals2::connection mMicroChangedSignal; S32 getConversationListItemSize() { return mConversationsWidgets.size(); } + typedef std::list<LLFloater*> floater_list_t; + void getDetachedConversationFloaters(floater_list_t& floaters); private: LLConversationViewSession* createConversationItemWidget(LLConversationItem* item); diff --git a/indra/newview/llfloaterimnearbychat.cpp b/indra/newview/llfloaterimnearbychat.cpp index 49f36a2f32..7e472466ed 100755 --- a/indra/newview/llfloaterimnearbychat.cpp +++ b/indra/newview/llfloaterimnearbychat.cpp @@ -568,7 +568,10 @@ void LLFloaterIMNearbyChat::sendChat( EChatType type ) if (0 == channel) { // discard returned "found" boolean - LLGestureMgr::instance().triggerAndReviseString(utf8text, &utf8_revised_text); + if(!LLGestureMgr::instance().triggerAndReviseString(utf8text, &utf8_revised_text)) + { + utf8_revised_text = utf8text; + } } else { diff --git a/indra/newview/llfloaterimsession.cpp b/indra/newview/llfloaterimsession.cpp index 8ec85e1160..848d5c34d2 100755 --- a/indra/newview/llfloaterimsession.cpp +++ b/indra/newview/llfloaterimsession.cpp @@ -442,8 +442,11 @@ void LLFloaterIMSession::addSessionParticipants(const uuid_vec_t& uuids) } else { - // remember whom we have invited, to notify others later, when the invited ones actually join - mInvitedParticipants.insert(mInvitedParticipants.end(), uuids.begin(), uuids.end()); + if(findInstance(mSessionID)) + { + // remember whom we have invited, to notify others later, when the invited ones actually join + mInvitedParticipants.insert(mInvitedParticipants.end(), uuids.begin(), uuids.end()); + } inviteToSession(uuids); } @@ -469,13 +472,18 @@ void LLFloaterIMSession::addP2PSessionParticipants(const LLSD& notification, con temp_ids.insert(temp_ids.end(), uuids.begin(), uuids.end()); // then we can close the current session - onClose(false); + if(findInstance(mSessionID)) + { + onClose(false); + + // remember whom we have invited, to notify others later, when the invited ones actually join + mInvitedParticipants.insert(mInvitedParticipants.end(), uuids.begin(), uuids.end()); + } // we start a new session so reset the initialization flag mSessionInitialized = false; - // remember whom we have invited, to notify others later, when the invited ones actually join - mInvitedParticipants.insert(mInvitedParticipants.end(), uuids.begin(), uuids.end()); + // Start a new ad hoc voice call if we invite new participants to a P2P call, // or start a text chat otherwise. diff --git a/indra/newview/llfloaterimsessiontab.cpp b/indra/newview/llfloaterimsessiontab.cpp index ce6e639305..cc2859c099 100755 --- a/indra/newview/llfloaterimsessiontab.cpp +++ b/indra/newview/llfloaterimsessiontab.cpp @@ -212,7 +212,7 @@ void LLFloaterIMSessionTab::assignResizeLimits() mRightPartPanel->setIgnoreReshape(is_participants_pane_collapsed); S32 participants_pane_target_width = is_participants_pane_collapsed? - 0 : (mParticipantListPanel->getRect().getWidth() + LLPANEL_BORDER_WIDTH); + 0 : (mParticipantListPanel->getRect().getWidth() + mParticipantListAndHistoryStack->getPanelSpacing()); S32 new_min_width = participants_pane_target_width + mRightPartPanel->getExpandedMinDim() + mFloaterExtraWidth; @@ -241,7 +241,10 @@ BOOL LLFloaterIMSessionTab::postBuild() mTearOffBtn->setCommitCallback(boost::bind(&LLFloaterIMSessionTab::onTearOffClicked, this)); mGearBtn = getChild<LLButton>("gear_btn"); - + mAddBtn = getChild<LLButton>("add_btn"); + mVoiceButton = getChild<LLButton>("voice_call_btn"); + mTranslationCheckBox = getChild<LLUICtrl>("translate_chat_checkbox_lp"); + mParticipantListPanel = getChild<LLLayoutPanel>("speakers_list_panel"); mRightPartPanel = getChild<LLLayoutPanel>("right_part_holder"); @@ -372,7 +375,7 @@ void LLFloaterIMSessionTab::draw() void LLFloaterIMSessionTab::enableDisableCallBtn() { - getChildView("voice_call_btn")->setEnabled( + mVoiceButton->setEnabled( mSessionID.notNull() && mSession && mSession->mSessionInitialized @@ -758,7 +761,7 @@ void LLFloaterIMSessionTab::reshapeChatLayoutPanel() void LLFloaterIMSessionTab::showTranslationCheckbox(BOOL show) { - getChild<LLUICtrl>("translate_chat_checkbox_lp")->setVisible(mIsNearbyChat? show : FALSE); + mTranslationCheckBox->setVisible(mIsNearbyChat && show); } // static @@ -805,15 +808,10 @@ void LLFloaterIMSessionTab::reloadEmptyFloaters() void LLFloaterIMSessionTab::updateCallBtnState(bool callIsActive) { - LLButton* voiceButton = getChild<LLButton>("voice_call_btn"); - voiceButton->setImageOverlay( - callIsActive? getString("call_btn_stop") : getString("call_btn_start")); - - voiceButton->setToolTip( - callIsActive? getString("end_call_button_tooltip") : getString("start_call_button_tooltip")); + mVoiceButton->setImageOverlay(callIsActive? getString("call_btn_stop") : getString("call_btn_start")); + mVoiceButton->setToolTip(callIsActive? getString("end_call_button_tooltip") : getString("start_call_button_tooltip")); enableDisableCallBtn(); - } void LLFloaterIMSessionTab::onSlide(LLFloaterIMSessionTab* self) @@ -898,6 +896,7 @@ void LLFloaterIMSessionTab::restoreFloater() mExpandCollapseLineBtn->setImageOverlay(getString("expandline_icon")); setMessagePaneExpanded(true); saveCollapsedState(); + mInputEditor->enableSingleLineMode(false); enableResizeCtrls(true, true, true); } } @@ -953,8 +952,8 @@ void LLFloaterIMSessionTab::updateGearBtn() if(prevVisibility != mGearBtn->getVisible()) { LLRect gear_btn_rect = mGearBtn->getRect(); - LLRect add_btn_rect = getChild<LLButton>("add_btn")->getRect(); - LLRect call_btn_rect = getChild<LLButton>("voice_call_btn")->getRect(); + LLRect add_btn_rect = mAddBtn->getRect(); + LLRect call_btn_rect = mVoiceButton->getRect(); S32 gap_width = call_btn_rect.mLeft - add_btn_rect.mRight; S32 right_shift = gear_btn_rect.getWidth() + gap_width; if(mGearBtn->getVisible()) @@ -968,24 +967,24 @@ void LLFloaterIMSessionTab::updateGearBtn() add_btn_rect.translate(-right_shift,0); call_btn_rect.translate(-right_shift,0); } - getChild<LLButton>("add_btn")->setRect(add_btn_rect); - getChild<LLButton>("voice_call_btn")->setRect(call_btn_rect); + mAddBtn->setRect(add_btn_rect); + mVoiceButton->setRect(call_btn_rect); } } void LLFloaterIMSessionTab::initBtns() { LLRect gear_btn_rect = mGearBtn->getRect(); - LLRect add_btn_rect = getChild<LLButton>("add_btn")->getRect(); - LLRect call_btn_rect = getChild<LLButton>("voice_call_btn")->getRect(); + LLRect add_btn_rect = mAddBtn->getRect(); + LLRect call_btn_rect = mVoiceButton->getRect(); S32 gap_width = call_btn_rect.mLeft - add_btn_rect.mRight; S32 right_shift = gear_btn_rect.getWidth() + gap_width; add_btn_rect.translate(-right_shift,0); call_btn_rect.translate(-right_shift,0); - getChild<LLButton>("add_btn")->setRect(add_btn_rect); - getChild<LLButton>("voice_call_btn")->setRect(call_btn_rect); + mAddBtn->setRect(add_btn_rect); + mVoiceButton->setRect(call_btn_rect); } // static @@ -1083,21 +1082,26 @@ void LLFloaterIMSessionTab::saveCollapsedState() } BOOL LLFloaterIMSessionTab::handleKeyHere(KEY key, MASK mask ) { + BOOL handled = FALSE; + if(mask == MASK_ALT) { LLFloaterIMContainer* floater_container = LLFloaterIMContainer::getInstance(); if (KEY_RETURN == key && !isTornOff()) { floater_container->expandConversation(); + handled = TRUE; } if ((KEY_UP == key) || (KEY_LEFT == key)) { floater_container->selectNextorPreviousConversation(false); + handled = TRUE; } if ((KEY_DOWN == key ) || (KEY_RIGHT == key)) { floater_container->selectNextorPreviousConversation(true); + handled = TRUE; } } - return TRUE; + return handled; } diff --git a/indra/newview/llfloaterimsessiontab.h b/indra/newview/llfloaterimsessiontab.h index 302d5a8066..ba80d2369a 100755 --- a/indra/newview/llfloaterimsessiontab.h +++ b/indra/newview/llfloaterimsessiontab.h @@ -182,6 +182,9 @@ protected: LLButton* mTearOffBtn; LLButton* mCloseBtn; LLButton* mGearBtn; + LLButton* mAddBtn; + LLButton* mVoiceButton; + LLUICtrl* mTranslationCheckBox; private: // Handling selection and contextual menu diff --git a/indra/newview/llfolderviewmodelinventory.cpp b/indra/newview/llfolderviewmodelinventory.cpp index 586965e5a0..c28657dbcd 100755 --- a/indra/newview/llfolderviewmodelinventory.cpp +++ b/indra/newview/llfolderviewmodelinventory.cpp @@ -74,6 +74,7 @@ void LLFolderViewModelInventory::sort( LLFolderViewFolder* folder ) it != end_it; ++it) { + // Recursive call to sort() on child (CHUI-849) LLFolderViewFolder* child_folderp = *it; sort(child_folderp); @@ -129,12 +130,12 @@ void LLFolderViewModelItemInventory::requestSort() void LLFolderViewModelItemInventory::setPassedFilter(bool passed, S32 filter_generation, std::string::size_type string_offset, std::string::size_type string_size) { LLFolderViewModelItemCommon::setPassedFilter(passed, filter_generation, string_offset, string_size); - - bool passed_filter_before = mPrevPassedAllFilters; + bool before = mPrevPassedAllFilters; mPrevPassedAllFilters = passedFilter(filter_generation); - if (passed_filter_before != mPrevPassedAllFilters) + if (before != mPrevPassedAllFilters) { + // Need to rearrange the folder if the filtered state of the item changed LLFolderViewFolder* parent_folder = mFolderViewItem->getParentFolder(); if (parent_folder) { @@ -150,11 +151,11 @@ bool LLFolderViewModelItemInventory::filterChildItem( LLFolderViewModelItem* ite bool continue_filtering = true; if (item->getLastFilterGeneration() < filter_generation) { - // recursive application of the filter for child items + // Recursive application of the filter for child items (CHUI-849) continue_filtering = item->filter( filter ); } - // track latest generation to pass any child items, for each folder up to root + // Update latest generation to pass filter in parent and propagate up to root if (item->passedFilter()) { LLFolderViewModelItemInventory* view_model = this; @@ -174,53 +175,61 @@ bool LLFolderViewModelItemInventory::filter( LLFolderViewFilter& filter) const S32 filter_generation = filter.getCurrentGeneration(); const S32 must_pass_generation = filter.getFirstRequiredGeneration(); - if (getLastFilterGeneration() >= must_pass_generation + if (getLastFilterGeneration() >= must_pass_generation && getLastFolderFilterGeneration() >= must_pass_generation && !passedFilter(must_pass_generation)) { // failed to pass an earlier filter that was a subset of the current one - // go ahead and flag this item as done + // go ahead and flag this item as not pass setPassedFilter(false, filter_generation); setPassedFolderFilter(false, filter_generation); return true; } - const bool passed_filter_folder = (getInventoryType() == LLInventoryType::IT_CATEGORY) - ? filter.checkFolder(this) - : true; + // *TODO : Revise the logic for fast pass on less restrictive filter case + /* + const S32 sufficient_pass_generation = filter.getFirstSuccessGeneration(); + if (getLastFilterGeneration() >= sufficient_pass_generation + && getLastFolderFilterGeneration() >= sufficient_pass_generation + && passedFilter(sufficient_pass_generation)) + { + // passed an earlier filter that was a superset of the current one + // go ahead and flag this item as pass + setPassedFilter(true, filter_generation); + setPassedFolderFilter(true, filter_generation); + return true; + } + */ + + const bool passed_filter_folder = (getInventoryType() == LLInventoryType::IT_CATEGORY) ? filter.checkFolder(this) : true; setPassedFolderFilter(passed_filter_folder, filter_generation); - if(!mChildren.empty() + bool continue_filtering = true; + + if (!mChildren.empty() && (getLastFilterGeneration() < must_pass_generation // haven't checked descendants against minimum required generation to pass - || descendantsPassedFilter(must_pass_generation))) // or at least one descendant has passed the minimum requirement + || descendantsPassedFilter(must_pass_generation))) // or at least one descendant has passed the minimum requirement { // now query children - for (child_list_t::iterator iter = mChildren.begin(), end_iter = mChildren.end(); - iter != end_iter && filter.getFilterCount() > 0; - ++iter) + for (child_list_t::iterator iter = mChildren.begin(), end_iter = mChildren.end(); iter != end_iter; ++iter) { - if (!filterChildItem((*iter), filter)) + continue_filtering = filterChildItem((*iter), filter); + if (!continue_filtering) { break; } } } - // if we didn't use all filter iterations - // that means we filtered all of our descendants - // so filter ourselves now - if (filter.getFilterCount() > 0) + // If we didn't use all the filter time that means we filtered all of our descendants so we can filter ourselves now + if (continue_filtering) { - filter.decrementFilterCount(); - + // This is where filter check on the item done (CHUI-849) const bool passed_filter = filter.check(this); setPassedFilter(passed_filter, filter_generation, filter.getStringMatchOffset(this), filter.getFilterStringSize()); - return true; - } - else - { - return false; + continue_filtering = !filter.isTimedOut(); } + return continue_filtering; } LLFolderViewModelInventory* LLInventoryPanel::getFolderViewModel() @@ -307,8 +316,8 @@ bool LLInventorySort::operator()(const LLFolderViewModelItemInventory* const& a, } } -LLFolderViewModelItemInventory::LLFolderViewModelItemInventory( class LLFolderViewModelInventory& root_view_model ) - : LLFolderViewModelItemCommon(root_view_model), - mPrevPassedAllFilters(false) +LLFolderViewModelItemInventory::LLFolderViewModelItemInventory( class LLFolderViewModelInventory& root_view_model ) : + LLFolderViewModelItemCommon(root_view_model), + mPrevPassedAllFilters(false) { } diff --git a/indra/newview/llfolderviewmodelinventory.h b/indra/newview/llfolderviewmodelinventory.h index 890d03d1c9..9dcfdfa185 100755 --- a/indra/newview/llfolderviewmodelinventory.h +++ b/indra/newview/llfolderviewmodelinventory.h @@ -59,9 +59,8 @@ public: virtual BOOL startDrag(EDragAndDropType* type, LLUUID* id) const = 0; virtual LLToolDragAndDrop::ESource getDragSource() const = 0; - protected: - bool mPrevPassedAllFilters; + bool mPrevPassedAllFilters; }; class LLInventorySort diff --git a/indra/newview/llgroupactions.cpp b/indra/newview/llgroupactions.cpp index a0f2918bd7..302d21c2e4 100755 --- a/indra/newview/llgroupactions.cpp +++ b/indra/newview/llgroupactions.cpp @@ -116,6 +116,80 @@ public: }; LLGroupHandler gGroupHandler; +// This object represents a pending request for specified group member information +// which is needed to check whether avatar can leave group +class LLFetchGroupMemberData : public LLGroupMgrObserver +{ +public: + LLFetchGroupMemberData(const LLUUID& group_id) : + mGroupId(group_id), + mRequestProcessed(false), + LLGroupMgrObserver(group_id) + { + llinfos << "Sending new group member request for group_id: "<< group_id << llendl; + LLGroupMgr* mgr = LLGroupMgr::getInstance(); + // register ourselves as an observer + mgr->addObserver(this); + // send a request + mgr->sendGroupPropertiesRequest(group_id); + mgr->sendCapGroupMembersRequest(group_id); + } + + ~LLFetchGroupMemberData() + { + if (!mRequestProcessed) + { + // Request is pending + llwarns << "Destroying pending group member request for group_id: " + << mGroupId << llendl; + } + // Remove ourselves as an observer + LLGroupMgr::getInstance()->removeObserver(this); + } + + void changed(LLGroupChange gc) + { + if (gc == GC_MEMBER_DATA && !mRequestProcessed) + { + LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mGroupId); + if (!gdatap) + { + llwarns << "LLGroupMgr::getInstance()->getGroupData() was NULL" << llendl; + } + else if (!gdatap->isMemberDataComplete()) + { + llwarns << "LLGroupMgr::getInstance()->getGroupData()->isMemberDataComplete() was FALSE" << llendl; + } + else + { + processGroupData(); + mRequestProcessed = true; + } + } + } + + LLUUID getGroupId() { return mGroupId; } + virtual void processGroupData() = 0; +protected: + LLUUID mGroupId; +private: + bool mRequestProcessed; +}; + +class LLFetchLeaveGroupData: public LLFetchGroupMemberData +{ +public: + LLFetchLeaveGroupData(const LLUUID& group_id) + : LLFetchGroupMemberData(group_id) + {} + void processGroupData() + { + LLGroupActions::processLeaveGroupDataResponse(mGroupId); + } +}; + +LLFetchLeaveGroupData* gFetchLeaveGroupData = NULL; + // static void LLGroupActions::search() { @@ -208,23 +282,52 @@ bool LLGroupActions::onJoinGroup(const LLSD& notification, const LLSD& response) void LLGroupActions::leave(const LLUUID& group_id) { if (group_id.isNull()) + { return; + } - S32 count = gAgent.mGroups.count(); - S32 i; - for (i = 0; i < count; ++i) + LLGroupData group_data; + if (gAgent.getGroupData(group_id, group_data)) { - if(gAgent.mGroups.get(i).mID == group_id) - break; + LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(group_id); + if (!gdatap || !gdatap->isMemberDataComplete()) + { + if (gFetchLeaveGroupData != NULL) + { + delete gFetchLeaveGroupData; + gFetchLeaveGroupData = NULL; + } + gFetchLeaveGroupData = new LLFetchLeaveGroupData(group_id); + } + else + { + processLeaveGroupDataResponse(group_id); + } } - if (i < count) +} + +//static +void LLGroupActions::processLeaveGroupDataResponse(const LLUUID group_id) +{ + LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(group_id); + LLUUID agent_id = gAgent.getID(); + LLGroupMgrGroupData::member_list_t::iterator mit = gdatap->mMembers.find(agent_id); + //get the member data for the group + if ( mit != gdatap->mMembers.end() ) { - LLSD args; - args["GROUP"] = gAgent.mGroups.get(i).mName; - LLSD payload; - payload["group_id"] = group_id; - LLNotificationsUtil::add("GroupLeaveConfirmMember", args, payload, onLeaveGroup); + LLGroupMemberData* member_data = (*mit).second; + + if ( member_data && member_data->isOwner() && gdatap->mMemberCount == 1) + { + LLNotificationsUtil::add("OwnerCannotLeaveGroup"); + return; + } } + LLSD args; + args["GROUP"] = gdatap->mName; + LLSD payload; + payload["group_id"] = group_id; + LLNotificationsUtil::add("GroupLeaveConfirmMember", args, payload, onLeaveGroup); } // static diff --git a/indra/newview/llgroupactions.h b/indra/newview/llgroupactions.h index 3f9852f194..afc4686dd7 100755 --- a/indra/newview/llgroupactions.h +++ b/indra/newview/llgroupactions.h @@ -114,6 +114,14 @@ public: private: static bool onJoinGroup(const LLSD& notification, const LLSD& response); static bool onLeaveGroup(const LLSD& notification, const LLSD& response); + + /** + * This function is called by LLFetchLeaveGroupData upon receiving a response to a group + * members data request. + */ + static void processLeaveGroupDataResponse(const LLUUID group_id); + + friend class LLFetchLeaveGroupData; }; #endif // LL_LLGROUPACTIONS_H diff --git a/indra/newview/llimview.cpp b/indra/newview/llimview.cpp index 2c20409381..214b177a1b 100755 --- a/indra/newview/llimview.cpp +++ b/indra/newview/llimview.cpp @@ -103,6 +103,7 @@ BOOL LLSessionTimeoutTimer::tick() } +void notify_of_message(const LLSD& msg, bool is_dnd_msg); void process_dnd_im(const LLSD& notification) { @@ -129,15 +130,9 @@ void process_dnd_im(const LLSD& notification) fromID, false, false); //will need slight refactor to retrieve whether offline message or not (assume online for now) + } - LLFloaterIMContainer* im_box = LLFloaterReg::getTypedInstance<LLFloaterIMContainer>("im_container"); - - if (im_box) - { - im_box->flashConversationItemWidget(sessionID, true); - } - - } + notify_of_message(data, true); } @@ -158,88 +153,89 @@ static void on_avatar_name_cache_toast(const LLUUID& agent_id, LLNotificationsUtil::add("IMToast", args, args, boost::bind(&LLFloaterIMContainer::showConversation, LLFloaterIMContainer::getInstance(), msg["session_id"].asUUID())); } -void on_new_message(const LLSD& msg) +void notify_of_message(const LLSD& msg, bool is_dnd_msg) { - 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); + std::string user_preferences; + LLUUID participant_id = msg[is_dnd_msg ? "FROM_ID" : "from_id"].asUUID(); + LLUUID session_id = msg[is_dnd_msg ? "SESSION_ID" : "session_id"].asUUID(); + LLIMModel::LLIMSession* session = LLIMModel::instance().findIMSession(session_id); - // 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; + // 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; - LLFloaterIMContainer* im_box = LLFloaterReg::getTypedInstance<LLFloaterIMContainer>("im_container"); + LLFloaterIMContainer* im_box = LLFloaterReg::getTypedInstance<LLFloaterIMContainer>("im_container"); LLFloaterIMSessionTab* session_floater = LLFloaterIMSessionTab::getConversation(session_id); + bool store_dnd_message = false; // flag storage of a dnd message if (!LLFloater::isVisible(im_box) || im_box->isMinimized()) { conversations_floater_status = CLOSED; } else if (!im_box->hasFocus() && - !(session_floater && LLFloater::isVisible(session_floater) - && !session_floater->isMinimized() && session_floater->hasFocus())) + !(session_floater && LLFloater::isVisible(session_floater) + && !session_floater->isMinimized() && session_floater->hasFocus())) { conversations_floater_status = NOT_ON_TOP; } else if (im_box->getSelectedSession() != session_id) { conversations_floater_status = ON_TOP; - } + } else { conversations_floater_status = ON_TOP_AND_ITEM_IS_SELECTED; } - // determine user prefs for this session - if (session_id.isNull()) - { - user_preferences = gSavedSettings.getString("NotificationNearbyChatOptions"); - } - else if(session->isP2PSessionType()) - { - if (LLAvatarTracker::instance().isBuddy(participant_id)) - { - user_preferences = gSavedSettings.getString("NotificationFriendIMOptions"); - } - else - { - user_preferences = gSavedSettings.getString("NotificationNonFriendIMOptions"); - } - } - else if(session->isAdHocSessionType()) - { - user_preferences = gSavedSettings.getString("NotificationConferenceIMOptions"); - } - else if(session->isGroupSessionType()) - { - user_preferences = gSavedSettings.getString("NotificationGroupChatOptions"); - } - - // actions: + // determine user prefs for this session + if (session_id.isNull()) + { + user_preferences = gSavedSettings.getString("NotificationNearbyChatOptions"); + } + else if(session->isP2PSessionType()) + { + if (LLAvatarTracker::instance().isBuddy(participant_id)) + { + user_preferences = gSavedSettings.getString("NotificationFriendIMOptions"); + } + else + { + user_preferences = gSavedSettings.getString("NotificationNonFriendIMOptions"); + } + } + else if(session->isAdHocSessionType()) + { + user_preferences = gSavedSettings.getString("NotificationConferenceIMOptions"); + } + else if(session->isGroupSessionType()) + { + user_preferences = gSavedSettings.getString("NotificationGroupChatOptions"); + } - // 0. nothing - exit - if (("none" == user_preferences || - ON_TOP_AND_ITEM_IS_SELECTED == conversations_floater_status) - && session_floater->isMessagePaneExpanded()) - { - return; - } + // actions: - // 1. open floater and [optional] surface it - if ("openconversations" == user_preferences && - (CLOSED == conversations_floater_status - || NOT_ON_TOP == conversations_floater_status)) - { - if(!gAgent.isDoNotDisturb()) - { + // 0. nothing - exit + if (("none" == user_preferences || + ON_TOP_AND_ITEM_IS_SELECTED == conversations_floater_status) + && session_floater->isMessagePaneExpanded()) + { + return; + } + + // 1. open floater and [optional] surface it + if (("openconversations" == user_preferences && + (CLOSED == conversations_floater_status + || NOT_ON_TOP == conversations_floater_status)) + || is_dnd_msg ) + { + if(!gAgent.isDoNotDisturb()) + { // Open conversations floater LLFloaterReg::showInstance("im_container"); im_box->collapseMessagesPane(false); @@ -261,57 +257,100 @@ void on_new_message(const LLSD& msg) } } } - 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() - && !session_floater->isShown()) - { - LLAvatarNameCache::get(participant_id, boost::bind(&on_avatar_name_cache_toast, _1, _2, msg)); - } - } - } + else + { + store_dnd_message = true; + } - // 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); - } - } + // 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) + || is_dnd_msg) + { + if(!LLMuteList::getInstance()->isMuted(participant_id)) + { + if(gAgent.isDoNotDisturb()) + { + store_dnd_message = true; + } + else + { + if (is_dnd_msg && (ON_TOP == conversations_floater_status || + NOT_ON_TOP == conversations_floater_status || + CLOSED == conversations_floater_status)) + { + im_box->highlightConversationItemWidget(session_id, true); + } + else + { + im_box->flashConversationItemWidget(session_id, true); + } + } + } + } - // 4. Toast - if ((("toast" == user_preferences) && - (CLOSED == conversations_floater_status - || NOT_ON_TOP == conversations_floater_status)) - || !session_floater->isMessagePaneExpanded()) + // 3. Flash FUI button + if (("toast" == user_preferences || "flash" == user_preferences) && + (CLOSED == conversations_floater_status + || NOT_ON_TOP == conversations_floater_status) + && !is_dnd_msg) //prevent flashing FUI button because the conversation floater will have already opened + { + if(!LLMuteList::getInstance()->isMuted(participant_id)) + { + if(!gAgent.isDoNotDisturb()) + { + gToolBarView->flashCommand(LLCommandId("chat"), true); + } + else + { + store_dnd_message = 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)); - } - } + // 4. Toast + if ((("toast" == user_preferences) && + (ON_TOP_AND_ITEM_IS_SELECTED != 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()) + { + if(!is_dnd_msg) + { + if(gAgent.isDoNotDisturb()) + { + store_dnd_message = true; + } + else + { + LLAvatarNameCache::get(participant_id, boost::bind(&on_avatar_name_cache_toast, _1, _2, msg)); + } + } + } + } + if (store_dnd_message) + { + // If in DND mode, allow notification to be stored so upon DND exit + // the user will be notified with some limitations (see 'is_dnd_msg' flag checks) + if(session_id.notNull() + && participant_id.notNull() + && !session_floater->isShown()) + { + LLAvatarNameCache::get(participant_id, boost::bind(&on_avatar_name_cache_toast, _1, _2, msg)); + } + } +} + +void on_new_message(const LLSD& msg) +{ + notify_of_message(msg, false); } LLIMModel::LLIMModel() diff --git a/indra/newview/llinventoryfilter.cpp b/indra/newview/llinventoryfilter.cpp index 92f2d33073..3c6974cf6d 100755 --- a/indra/newview/llinventoryfilter.cpp +++ b/indra/newview/llinventoryfilter.cpp @@ -70,11 +70,8 @@ LLInventoryFilter::LLInventoryFilter(const Params& p) mFilterSubString(p.substring), mCurrentGeneration(0), mFirstRequiredGeneration(0), - mFirstSuccessGeneration(0), - mFilterCount(0) + mFirstSuccessGeneration(0) { - mNextFilterGeneration = mCurrentGeneration + 1; - // copy mFilterOps into mDefaultFilterOps markDefault(); } @@ -92,9 +89,7 @@ bool LLInventoryFilter::check(const LLFolderViewModelItem* item) return passed_clipboard; } - std::string::size_type string_offset = mFilterSubString.size() ? listener->getSearchableName().find(mFilterSubString) : std::string::npos; - - BOOL passed = (mFilterSubString.size() == 0 || string_offset != std::string::npos); + bool passed = (mFilterSubString.size() ? listener->getSearchableName().find(mFilterSubString) != std::string::npos : true); passed = passed && checkAgainstFilterType(listener); passed = passed && checkAgainstPermissions(listener); passed = passed && checkAgainstFilterLinks(listener); @@ -105,17 +100,12 @@ bool LLInventoryFilter::check(const LLFolderViewModelItem* item) bool LLInventoryFilter::check(const LLInventoryItem* item) { - std::string::size_type string_offset = mFilterSubString.size() ? item->getName().find(mFilterSubString) : std::string::npos; - + const bool passed_string = (mFilterSubString.size() ? item->getName().find(mFilterSubString) != std::string::npos : true); const bool passed_filtertype = checkAgainstFilterType(item); const bool passed_permissions = checkAgainstPermissions(item); - const BOOL passed_clipboard = checkAgainstClipboard(item->getUUID()); - const bool passed = (passed_filtertype - && passed_permissions - && passed_clipboard - && (mFilterSubString.size() == 0 || string_offset != std::string::npos)); + const bool passed_clipboard = checkAgainstClipboard(item->getUUID()); - return passed; + return passed_filtertype && passed_permissions && passed_clipboard && passed_string; } bool LLInventoryFilter::checkFolder(const LLFolderViewModelItem* item) const @@ -439,7 +429,7 @@ void LLInventoryFilter::updateFilterTypes(U64 types, U64& current_types) current_types = types; if (more_bits_set && fewer_bits_set) { - // neither less or more restrive, both simultaneously + // neither less or more restrictive, both simultaneously // so we need to filter from scratch setModified(FILTER_RESTART); } @@ -714,7 +704,7 @@ void LLInventoryFilter::resetDefault() void LLInventoryFilter::setModified(EFilterModified behavior) { mFilterText.clear(); - mCurrentGeneration = mNextFilterGeneration++; + mCurrentGeneration++; if (mFilterModified == FILTER_NONE) { @@ -1021,21 +1011,19 @@ LLInventoryFilter::EFolderShow LLInventoryFilter::getShowFolderState() const return mFilterOps.mShowFolderState; } -void LLInventoryFilter::setFilterCount(S32 count) -{ - mFilterCount = count; -} -S32 LLInventoryFilter::getFilterCount() const +bool LLInventoryFilter::isTimedOut() { - return mFilterCount; + return mFilterTime.hasExpired(); } -void LLInventoryFilter::decrementFilterCount() -{ - mFilterCount--; +void LLInventoryFilter::resetTime(S32 timeout) +{ + mFilterTime.reset(); + F32 time_in_sec = (F32)(timeout)/1000.0; + mFilterTime.setTimerExpirySec(time_in_sec); } -S32 LLInventoryFilter::getCurrentGeneration() const +S32 LLInventoryFilter::getCurrentGeneration() const { return mCurrentGeneration; } diff --git a/indra/newview/llinventoryfilter.h b/indra/newview/llinventoryfilter.h index 4912b5ca91..ce516af0b9 100755 --- a/indra/newview/llinventoryfilter.h +++ b/indra/newview/llinventoryfilter.h @@ -215,12 +215,11 @@ public: void setModified(EFilterModified behavior = FILTER_RESTART); // +-------------------------------------------------------------------+ - // + Count + // + Time // +-------------------------------------------------------------------+ - void setFilterCount(S32 count); - S32 getFilterCount() const; - void decrementFilterCount(); - + void resetTime(S32 timeout); + bool isTimedOut(); + // +-------------------------------------------------------------------+ // + Default // +-------------------------------------------------------------------+ @@ -262,13 +261,15 @@ private: const std::string mName; S32 mCurrentGeneration; + // The following makes checking for pass/no pass possible even if the item is not checked against the current generation + // Any item that *did not pass* the "required generation" will *not pass* the current one + // Any item that *passes* the "success generation" will *pass* the current one S32 mFirstRequiredGeneration; S32 mFirstSuccessGeneration; - S32 mNextFilterGeneration; - S32 mFilterCount; EFilterModified mFilterModified; - + LLTimer mFilterTime; + std::string mFilterText; std::string mEmptyLookupMessage; }; diff --git a/indra/newview/llinventorypanel.cpp b/indra/newview/llinventorypanel.cpp index cf1fd4c0d0..e5b9e11d48 100755 --- a/indra/newview/llinventorypanel.cpp +++ b/indra/newview/llinventorypanel.cpp @@ -192,7 +192,7 @@ LLFolderView * LLInventoryPanel::createFolderRoot(LLUUID root_id ) p.show_item_link_overlays = mShowItemLinkOverlays; p.root = NULL; p.options_menu = "menu_inventory.xml"; - + return LLUICtrlFactory::create<LLFolderView>(p); } @@ -396,6 +396,7 @@ LLInventoryFilter::EFolderShow LLInventoryPanel::getShowFolderState() return getFilter().getShowFolderState(); } +// Called when something changed in the global model (new item, item coming through the wire, rename, move, etc...) (CHUI-849) void LLInventoryPanel::modelChanged(U32 mask) { static LLFastTimer::DeclareTimer FTM_REFRESH("Inventory Refresh"); diff --git a/indra/newview/lllogchat.cpp b/indra/newview/lllogchat.cpp index 2d7454b636..379bbc5f8d 100755 --- a/indra/newview/lllogchat.cpp +++ b/indra/newview/lllogchat.cpp @@ -631,7 +631,7 @@ void LLLogChat::deleteTranscripts() } // static -bool LLLogChat::isTranscriptExist(const LLUUID& avatar_id) +bool LLLogChat::isTranscriptExist(const LLUUID& avatar_id, bool is_group) { std::vector<std::string> list_of_transcriptions; LLLogChat::getListOfTranscriptFiles(list_of_transcriptions); @@ -641,20 +641,53 @@ bool LLLogChat::isTranscriptExist(const LLUUID& avatar_id) LLAvatarName avatar_name; LLAvatarNameCache::get(avatar_id, &avatar_name); std::string avatar_user_name = avatar_name.getAccountName(); - std::replace(avatar_user_name.begin(), avatar_user_name.end(), '.', '_'); - - BOOST_FOREACH(std::string& transcript_file_name, list_of_transcriptions) + if(!is_group) { - if (std::string::npos != transcript_file_name.find(avatar_user_name)) + std::replace(avatar_user_name.begin(), avatar_user_name.end(), '.', '_'); + BOOST_FOREACH(std::string& transcript_file_name, list_of_transcriptions) { - return true; + if (std::string::npos != transcript_file_name.find(avatar_user_name)) + { + return true; + } } } + else + { + std::string file_name; + gCacheName->getGroupName(avatar_id, file_name); + file_name = makeLogFileName(file_name); + BOOST_FOREACH(std::string& transcript_file_name, list_of_transcriptions) + { + if (transcript_file_name == file_name) + { + return true; + } + } + } + } return false; } +bool LLLogChat::isNearbyTranscriptExist() +{ + std::vector<std::string> list_of_transcriptions; + LLLogChat::getListOfTranscriptFiles(list_of_transcriptions); + + std::string file_name; + file_name = makeLogFileName("chat"); + BOOST_FOREACH(std::string& transcript_file_name, list_of_transcriptions) + { + if (transcript_file_name == file_name) + { + return true; + } + } + return false; +} + //*TODO mark object's names in a special way so that they will be distinguishable form avatar name //which are more strict by its nature (only firstname and secondname) //Example, an object's name can be written like "Object <actual_object's_name>" diff --git a/indra/newview/lllogchat.h b/indra/newview/lllogchat.h index e819f00dd9..bd70dbaac9 100755 --- a/indra/newview/lllogchat.h +++ b/indra/newview/lllogchat.h @@ -67,7 +67,8 @@ public: std::vector<std::string>& listOfFilesToMove); static void deleteTranscripts(); - static bool isTranscriptExist(const LLUUID& avatar_id); + static bool isTranscriptExist(const LLUUID& avatar_id, bool is_group=false); + static bool isNearbyTranscriptExist(); private: static std::string cleanFileName(std::string filename); diff --git a/indra/newview/llpanelmaininventory.cpp b/indra/newview/llpanelmaininventory.cpp index d6535c88e9..53deded2f2 100755 --- a/indra/newview/llpanelmaininventory.cpp +++ b/indra/newview/llpanelmaininventory.cpp @@ -130,6 +130,8 @@ BOOL LLPanelMainInventory::postBuild() mFilterTabs = getChild<LLTabContainer>("inventory filter tabs"); mFilterTabs->setCommitCallback(boost::bind(&LLPanelMainInventory::onFilterSelected, this)); + mCounterCtrl = getChild<LLUICtrl>("ItemcountText"); + //panel->getFilter().markDefault(); // Set up the default inv. panel/filter settings. @@ -566,7 +568,7 @@ void LLPanelMainInventory::draw() void LLPanelMainInventory::updateItemcountText() { // *TODO: Calling setlocale() on each frame may be inefficient. - LLLocale locale(LLStringUtil::getLocale()); + //LLLocale locale(LLStringUtil::getLocale()); std::string item_count_string; LLResMgr::getInstance()->getIntegerString(item_count_string, gInventory.getItemCount()); @@ -589,8 +591,7 @@ void LLPanelMainInventory::updateItemcountText() text = getString("ItemcountUnknown"); } - // *TODO: Cache the LLUICtrl* for the ItemcountText control - getChild<LLUICtrl>("ItemcountText")->setValue(text); + mCounterCtrl->setValue(text); } void LLPanelMainInventory::onFocusReceived() diff --git a/indra/newview/llpanelmaininventory.h b/indra/newview/llpanelmaininventory.h index 899931aa89..394b004e20 100755 --- a/indra/newview/llpanelmaininventory.h +++ b/indra/newview/llpanelmaininventory.h @@ -121,6 +121,7 @@ private: LLFilterEditor* mFilterEditor; LLTabContainer* mFilterTabs; + LLUICtrl* mCounterCtrl; LLHandle<LLFloater> mFinderHandle; LLInventoryPanel* mActivePanel; bool mResortActivePanel; diff --git a/indra/newview/llpanelpeople.cpp b/indra/newview/llpanelpeople.cpp index 4138558bad..69b6bdf737 100755 --- a/indra/newview/llpanelpeople.cpp +++ b/indra/newview/llpanelpeople.cpp @@ -28,6 +28,8 @@ // libs #include "llavatarname.h" +#include "llconversationview.h" +#include "llfloaterimcontainer.h" #include "llfloaterreg.h" #include "llfloatersidepanelcontainer.h" #include "llmenubutton.h" @@ -48,34 +50,49 @@ #include "llavataractions.h" #include "llavatarlist.h" #include "llavatarlistitem.h" +#include "llavatarnamecache.h" #include "llcallingcard.h" // for LLAvatarTracker +#include "llcallbacklist.h" +#include "llerror.h" +#include "llfacebookconnect.h" #include "llfloateravatarpicker.h" -//#include "llfloaterminiinspector.h" #include "llfriendcard.h" #include "llgroupactions.h" #include "llgrouplist.h" #include "llinventoryobserver.h" #include "llnetmap.h" #include "llpanelpeoplemenus.h" +#include "llparticipantlist.h" +#include "llpersonfolderview.h" +#include "llpersonmodelcommon.h" +#include "llpersontabview.h" #include "llsidetraypanelcontainer.h" #include "llrecentpeople.h" #include "llviewercontrol.h" // for gSavedSettings #include "llviewermenu.h" // for gMenuHolder #include "llvoiceclient.h" #include "llworld.h" +#include "llsociallist.h" #include "llspeakers.h" +#include "llfloaterwebcontent.h" + +#include "llagentui.h" +#include "llslurl.h" #define FRIEND_LIST_UPDATE_TIMEOUT 0.5 #define NEARBY_LIST_UPDATE_INTERVAL 1 +#define FBCTEST_LIST_UPDATE_INTERVAL 0.25 static const std::string NEARBY_TAB_NAME = "nearby_panel"; static const std::string FRIENDS_TAB_NAME = "friends_panel"; static const std::string GROUP_TAB_NAME = "groups_panel"; static const std::string RECENT_TAB_NAME = "recent_panel"; static const std::string BLOCKED_TAB_NAME = "blocked_panel"; // blocked avatars - +static const std::string FBCTEST_TAB_NAME = "fbctest_panel"; +static const std::string FBCTESTTWO_TAB_NAME = "fbctesttwo_panel"; static const std::string COLLAPSED_BY_USER = "collapsed_by_user"; + /** Comparator for comparing avatar items by last interaction date */ class LLAvatarItemRecentComparator : public LLAvatarItemComparator { @@ -489,23 +506,73 @@ public: } }; +/** + * Periodically updates the FBC test list after a login is initiated. + * + * The period is defined by FBCTEST_LIST_UPDATE_INTERVAL constant. + */ +class LLFbcTestListUpdater : public LLAvatarListUpdater +{ + LOG_CLASS(LLFbcTestListUpdater); + +public: + LLFbcTestListUpdater(callback_t cb) + : LLAvatarListUpdater(cb, FBCTEST_LIST_UPDATE_INTERVAL) + { + setActive(false); + } + + /*virtual*/ void setActive(bool val) + { + if (val) + { + // update immediately and start regular updates + update(); + mEventTimer.start(); + } + else + { + // stop regular updates + mEventTimer.stop(); + } + } + + /*virtual*/ BOOL tick() + { + update(); + return FALSE; + } +private: +}; + //============================================================================= LLPanelPeople::LLPanelPeople() : LLPanel(), + mPersonFolderView(NULL), + mTryToConnectToFbc(true), mTabContainer(NULL), mOnlineFriendList(NULL), mAllFriendList(NULL), mNearbyList(NULL), mRecentList(NULL), mGroupList(NULL), - mMiniMap(NULL) + mMiniMap(NULL), + mFacebookListGeneration(0) { mFriendListUpdater = new LLFriendListUpdater(boost::bind(&LLPanelPeople::updateFriendList, this)); mNearbyListUpdater = new LLNearbyListUpdater(boost::bind(&LLPanelPeople::updateNearbyList, this)); mRecentListUpdater = new LLRecentListUpdater(boost::bind(&LLPanelPeople::updateRecentList, this)); + mFacebookListUpdater = new LLFbcTestListUpdater(boost::bind(&LLPanelPeople::updateFacebookList, this)); mButtonsUpdater = new LLButtonsUpdater(boost::bind(&LLPanelPeople::updateButtons, this)); + mCommitCallbackRegistrar.add("People.loginFBC", boost::bind(&LLPanelPeople::onLoginFbcButtonClicked, this)); + mCommitCallbackRegistrar.add("People.requestFBC", boost::bind(&LLPanelPeople::onFacebookAppRequestClicked, this)); + mCommitCallbackRegistrar.add("People.sendFBC", boost::bind(&LLPanelPeople::onFacebookAppSendClicked, this)); + mCommitCallbackRegistrar.add("People.testaddFBC", boost::bind(&LLPanelPeople::onFacebookTestAddClicked, this)); + mCommitCallbackRegistrar.add("People.testaddFBCFolderView", boost::bind(&LLPanelPeople::addTestParticipant, this)); + mCommitCallbackRegistrar.add("People.testFBCCheckin", boost::bind(&LLPanelPeople::onFacebookCheckinClicked, this)); + mCommitCallbackRegistrar.add("People.AddFriend", boost::bind(&LLPanelPeople::onAddFriendButtonClicked, this)); mCommitCallbackRegistrar.add("People.AddFriendWizard", boost::bind(&LLPanelPeople::onAddFriendWizButtonClicked, this)); mCommitCallbackRegistrar.add("People.DelFriend", boost::bind(&LLPanelPeople::onDeleteFriendButtonClicked, this)); @@ -532,11 +599,14 @@ LLPanelPeople::~LLPanelPeople() delete mNearbyListUpdater; delete mFriendListUpdater; delete mRecentListUpdater; + delete mFacebookListUpdater; if(LLVoiceClient::instanceExists()) { LLVoiceClient::getInstance()->removeObserver(this); } + + if (mFbcTestBrowserHandle.get()) mFbcTestBrowserHandle.get()->die(); } void LLPanelPeople::onFriendsAccordionExpandedCollapsed(LLUICtrl* ctrl, const LLSD& param, LLAvatarList* avatar_list) @@ -571,6 +641,7 @@ BOOL LLPanelPeople::postBuild() getChild<LLFilterEditor>("friends_filter_input")->setCommitCallback(boost::bind(&LLPanelPeople::onFilterEdit, this, _2)); getChild<LLFilterEditor>("groups_filter_input")->setCommitCallback(boost::bind(&LLPanelPeople::onFilterEdit, this, _2)); getChild<LLFilterEditor>("recent_filter_input")->setCommitCallback(boost::bind(&LLPanelPeople::onFilterEdit, this, _2)); + getChild<LLFilterEditor>("fbc_filter_input")->setCommitCallback(boost::bind(&LLPanelPeople::onFilterEdit, this, _2)); mTabContainer = getChild<LLTabContainer>("tabs"); mTabContainer->setCommitCallback(boost::bind(&LLPanelPeople::onTabSelected, this, _2)); @@ -616,6 +687,54 @@ BOOL LLPanelPeople::postBuild() mAllFriendList->setContextMenu(&LLPanelPeopleMenus::gPeopleContextMenu); mOnlineFriendList->setContextMenu(&LLPanelPeopleMenus::gPeopleContextMenu); + //===Temporary ======================================================================== + + LLPanel * social_tab = getChild<LLPanel>(FBCTEST_TAB_NAME); + mFacebookFriends = social_tab->getChild<LLSocialList>("facebook_friends"); + // Note: we use the same updater for both test lists (brute force but OK since it's temporary) + social_tab->setVisibleCallback(boost::bind(&Updater::setActive, mFacebookListUpdater, _2)); + + //===Test START======================================================================== + + LLPanel * socialtwo_tab = getChild<LLPanel>(FBCTESTTWO_TAB_NAME); + socialtwo_tab->setVisibleCallback(boost::bind(&Updater::setActive, mFacebookListUpdater, _2)); + + //Create folder view + LLPersonModelCommon* base_item = new LLPersonModelCommon(mPersonFolderViewModel); + + LLPersonFolderView::Params folder_view_params(LLUICtrlFactory::getDefaultParams<LLPersonFolderView>()); + + folder_view_params.parent_panel = socialtwo_tab; + folder_view_params.listener = base_item; + folder_view_params.view_model = &mPersonFolderViewModel; + folder_view_params.root = NULL; + folder_view_params.use_ellipses = true; + folder_view_params.use_label_suffix = true; + folder_view_params.options_menu = "menu_conversation.xml"; + folder_view_params.name = "fbcfolderview"; + mPersonFolderView = LLUICtrlFactory::create<LLPersonFolderView>(folder_view_params); + + //Create scroller + LLRect scroller_view_rect = socialtwo_tab->getRect(); + scroller_view_rect.mTop -= 2+27; // 27 is the height of the top toolbar + scroller_view_rect.mRight -= 4; + scroller_view_rect.mLeft += 2; + LLScrollContainer::Params scroller_params(LLUICtrlFactory::getDefaultParams<LLFolderViewScrollContainer>()); + scroller_params.rect(scroller_view_rect); + + LLScrollContainer* scroller = LLUICtrlFactory::create<LLFolderViewScrollContainer>(scroller_params); + socialtwo_tab->addChildInBack(scroller); + scroller->addChild(mPersonFolderView); + scroller->setFollowsAll(); + mPersonFolderView->setScrollContainer(scroller); + mPersonFolderView->setFollowsAll(); + + gIdleCallbacks.addFunction(idle, this); + + //===Test END======================================================================== + + + setSortOrder(mRecentList, (ESortOrder)gSavedSettings.getU32("RecentPeopleSortOrder"), false); setSortOrder(mAllFriendList, (ESortOrder)gSavedSettings.getU32("FriendsSortOrder"), false); setSortOrder(mNearbyList, (ESortOrder)gSavedSettings.getU32("NearbyPeopleSortOrder"), false); @@ -664,6 +783,15 @@ BOOL LLPanelPeople::postBuild() // Must go after setting commit callback and initializing all pointers to children. mTabContainer->selectTabByName(NEARBY_TAB_NAME); + mFBCGearButton = getChild<LLMenuButton>("fbc_options_btn"); + + LLToggleableMenu* fbc_menu = LLUICtrlFactory::getInstance()->createFromFile<LLToggleableMenu>("menu_gear_fbc.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); + if(fbc_menu) + { + mFBCMenuHandle = fbc_menu->getHandle(); + mFBCGearButton->setMenu(fbc_menu); + } + LLVoiceClient::getInstance()->addObserver(this); // call this method in case some list is empty and buttons can be in inconsistent state @@ -686,6 +814,12 @@ void LLPanelPeople::onChange(EStatusType status, const std::string &channelURI, updateButtons(); } +void LLPanelPeople::idle(void * user_data) +{ + LLPanelPeople * self = static_cast<LLPanelPeople *>(user_data); + self->mPersonFolderView->update(); +} + void LLPanelPeople::updateFriendListHelpText() { // show special help text for just created account to help finding friends. EXT-4836 @@ -786,6 +920,62 @@ void LLPanelPeople::updateRecentList() mRecentList->setDirty(); } +void LLPanelPeople::updateFacebookList() +{ + if (mTryToConnectToFbc) + { + // try to reconnect to facebook! + LLFacebookConnect::instance().tryToReconnectToFacebook(); + + // don't try again + mTryToConnectToFbc = false; + + // stop updating + mFacebookListUpdater->setActive(false); + } + + if (LLFacebookConnect::instance().generation() != mFacebookListGeneration) + { + mFacebookListGeneration = LLFacebookConnect::instance().generation(); + LLSD friends = LLFacebookConnect::instance().getContent(); + + mFacebookFriends->clear(); + LLPersonTabModel::tab_type tab_type; + LLAvatarTracker& avatar_tracker = LLAvatarTracker::instance(); + + for (LLSD::map_const_iterator i = friends.beginMap(); i != friends.endMap(); ++i) + { + std::string name = i->second["name"].asString(); + LLUUID agent_id = i->second.has("agent_id") ? i->second["agent_id"].asUUID() : LLUUID(NULL); + bool second_life_buddy = agent_id.notNull() ? avatar_tracker.isBuddy(agent_id) : false; + + //add to avatar list + mFacebookFriends->addNewItem(agent_id, name, false); + + if(!second_life_buddy) + { + //FB+SL but not SL friend + if (agent_id.notNull()) + { + tab_type = LLPersonTabModel::FB_SL_NON_SL_FRIEND; + } + //FB only friend + else + { + tab_type = LLPersonTabModel::FB_ONLY_FRIEND; + } + + //Add to person tab model + LLPersonTabModel * person_tab_model = dynamic_cast<LLPersonTabModel *>(mPersonFolderView->getPersonTabModelByIndex(tab_type)); + if (person_tab_model) + { + addParticipantToModel(person_tab_model, agent_id, name); + } + } + } + } +} + void LLPanelPeople::updateButtons() { std::string cur_tab = getActiveTabName(); @@ -870,6 +1060,13 @@ LLUUID LLPanelPeople::getCurrentItemID() const if (cur_tab == BLOCKED_TAB_NAME) return LLUUID::null; // FIXME? + + if (cur_tab == FBCTEST_TAB_NAME) + return LLUUID::null; + + if (cur_tab == FBCTESTTWO_TAB_NAME) + return LLUUID::null; + llassert(0 && "unknown tab selected"); return LLUUID::null; @@ -893,6 +1090,10 @@ void LLPanelPeople::getCurrentItemIDs(uuid_vec_t& selected_uuids) const mGroupList->getSelectedUUIDs(selected_uuids); else if (cur_tab == BLOCKED_TAB_NAME) selected_uuids.clear(); // FIXME? + else if (cur_tab == FBCTEST_TAB_NAME) + return; + else if (cur_tab == FBCTESTTWO_TAB_NAME) + return; else llassert(0 && "unknown tab selected"); @@ -989,23 +1190,23 @@ void LLPanelPeople::onFilterEdit(const std::string& search_string) { // store accordion tabs opened/closed state before any manipulation with accordion tabs if (!saved_filter.empty()) - { - notifyChildren(LLSD().with("action","store_state")); - } + { + notifyChildren(LLSD().with("action","store_state")); + } mOnlineFriendList->setNameFilter(filter); mAllFriendList->setNameFilter(filter); - setAccordionCollapsedByUser("tab_online", false); - setAccordionCollapsedByUser("tab_all", false); - showFriendsAccordionsIfNeeded(); + setAccordionCollapsedByUser("tab_online", false); + setAccordionCollapsedByUser("tab_all", false); + showFriendsAccordionsIfNeeded(); // restore accordion tabs state _after_ all manipulations if(saved_filter.empty()) - { - notifyChildren(LLSD().with("action","restore_state")); - } -} + { + notifyChildren(LLSD().with("action","restore_state")); + } + } else if (cur_tab == GROUP_TAB_NAME) { mGroupList->setNameFilter(filter); @@ -1014,6 +1215,11 @@ void LLPanelPeople::onFilterEdit(const std::string& search_string) { mRecentList->setNameFilter(filter); } + else if (cur_tab == FBCTESTTWO_TAB_NAME) + { + mPersonFolderViewModel.getFilter().setFilterSubString(filter); + mPersonFolderView->requestArrange(); + } } void LLPanelPeople::onTabSelected(const LLSD& param) @@ -1225,7 +1431,7 @@ void LLPanelPeople::onFriendsViewSortMenuItemClicked(const LLSD& userdata) mAllFriendList->showPermissions(show_permissions); mOnlineFriendList->showPermissions(show_permissions); } -} + } void LLPanelPeople::onGroupsViewSortMenuItemClicked(const LLSD& userdata) { @@ -1446,4 +1652,107 @@ bool LLPanelPeople::isAccordionCollapsedByUser(const std::string& name) return isAccordionCollapsedByUser(getChild<LLUICtrl>(name)); } +void LLPanelPeople::addTestParticipant() +{ + std::string suffix("Aa"); + std::string prefix("FB Name"); + LLPersonTabModel * person_tab_model; + LLUUID agentID; + std::string name; + LLPersonTabModel::tab_type tab_type; + + for(int i = 0; i < 300; ++i) + { + //Adds FB+SL people that aren't yet SL friends + if(i < 10) + { + tab_type = LLPersonTabModel::FB_SL_NON_SL_FRIEND; + agentID = gAgent.getID(); + } + //Adds FB only friends + else + { + tab_type = LLPersonTabModel::FB_ONLY_FRIEND; + agentID = LLUUID(NULL); + } + + person_tab_model = dynamic_cast<LLPersonTabModel *>(mPersonFolderView->getPersonTabModelByIndex(tab_type)); + name = prefix + " " + suffix; + addParticipantToModel(person_tab_model, agentID, name); + // Next suffix : Aa, Ab, Ac ... Az, Ba, Bb, Bc ... Bz, Ca, Cb ... + suffix[1]+=1; + if (suffix[1]=='{') + { + suffix[1]='a'; + suffix[0]+=1; + if (suffix[0]=='[') + suffix[0]='A'; + } + } +} + +void LLPanelPeople::addParticipantToModel(LLPersonTabModel * person_folder_model, const LLUUID& agent_id, const std::string& name) +{ + LLPersonModel* person_model = NULL; + + LLAvatarName avatar_name; + bool has_name = agent_id.notNull() ? LLAvatarNameCache::get(agent_id, &avatar_name) : false; + std::string avatar_name_string; + + if(has_name) + { + avatar_name_string = avatar_name.getDisplayName(); + } + + person_model = new LLPersonModel(agent_id, avatar_name_string, name, mPersonFolderViewModel); + person_folder_model->addParticipant(person_model); +} + +void LLPanelPeople::onLoginFbcButtonClicked() +{ + if (LLFacebookConnect::instance().getConnected()) + { + LLFacebookConnect::instance().disconnectFromFacebook(); + } + else + { + LLFacebookConnect::instance().getConnectionToFacebook(); + } +} + +void LLPanelPeople::onFacebookCheckinClicked() +{ + // Get the local SLURL + LLSLURL slurl; + LLAgentUI::buildSLURL(slurl); + + LLFacebookConnect::instance().postCheckinMessage("Here I am in SL!", slurl.getSLURLString(), slurl.getRegion(), "", "", ""); +} + +void LLPanelPeople::onFacebookAppRequestClicked() +{ +} + +void LLPanelPeople::onFacebookAppSendClicked() +{ +} + +static LLFastTimer::DeclareTimer FTM_AVATAR_LIST_TEST("avatar list test"); + +void LLPanelPeople::onFacebookTestAddClicked() +{ + LLFastTimer _(FTM_AVATAR_LIST_TEST); + + mFacebookFriends->clear(); + + LL_INFOS("LLPanelPeople") << "start adding 300 users" << LL_ENDL; + + for(int i = 0; i < 300; ++i) + { + mFacebookFriends->addNewItem(LLUUID(), "Test", false); + } + + LL_INFOS("LLPanelPeople") << "finished adding 300 users" << LL_ENDL; +} + // EOF diff --git a/indra/newview/llpanelpeople.h b/indra/newview/llpanelpeople.h index 4740964dee..f617517392 100755 --- a/indra/newview/llpanelpeople.h +++ b/indra/newview/llpanelpeople.h @@ -22,7 +22,7 @@ * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ - */ + */ #ifndef LL_LLPANELPEOPLE_H #define LL_LLPANELPEOPLE_H @@ -30,12 +30,17 @@ #include <llpanel.h> #include "llcallingcard.h" // for avatar tracker +#include "llpersonmodelcommon.h" +#include "llfloaterwebcontent.h" #include "llvoiceclient.h" class LLAvatarList; +class LLAvatarListSocial; class LLAvatarName; class LLFilterEditor; class LLGroupList; +class LLPersonFolderView; +class LLSocialList; class LLMenuButton; class LLTabContainer; @@ -55,6 +60,13 @@ public: // when voice is available /*virtual*/ void onChange(EStatusType status, const std::string &channelURI, bool proximal); + static void idle(void * user_data); + + void addTestParticipant(); + void addParticipantToModel(LLPersonTabModel * session_model, const LLUUID& agent_id, const std::string& name); + + bool mTryToConnectToFbc; + // internals class Updater; @@ -75,6 +87,7 @@ private: void updateFriendList(); void updateNearbyList(); void updateRecentList(); + void updateFacebookList(); bool isItemsFreeOfFriends(const uuid_vec_t& uuids); @@ -106,6 +119,12 @@ private: void onGroupsViewSortMenuItemClicked(const LLSD& userdata); void onRecentViewSortMenuItemClicked(const LLSD& userdata); + void onLoginFbcButtonClicked(); + void onFacebookAppRequestClicked(); + void onFacebookAppSendClicked(); + void onFacebookTestAddClicked(); + void onFacebookCheckinClicked(); + bool onFriendsViewSortMenuItemCheck(const LLSD& userdata); bool onRecentViewSortMenuItemCheck(const LLSD& userdata); bool onNearbyViewSortMenuItemCheck(const LLSD& userdata); @@ -132,16 +151,25 @@ private: LLAvatarList* mNearbyList; LLAvatarList* mRecentList; LLGroupList* mGroupList; + LLSocialList* mFacebookFriends; + S32 mFacebookListGeneration; LLNetMap* mMiniMap; std::vector<std::string> mSavedOriginalFilters; std::vector<std::string> mSavedFilters; + LLHandle<LLView> mFBCMenuHandle; + LLHandle<LLFloater> mFbcTestBrowserHandle; Updater* mFriendListUpdater; Updater* mNearbyListUpdater; Updater* mRecentListUpdater; + Updater* mFacebookListUpdater; Updater* mButtonsUpdater; + LLMenuButton* mFBCGearButton; LLHandle< LLFloater > mPicker; + + LLPersonFolderViewModel mPersonFolderViewModel; + LLPersonFolderView* mPersonFolderView; }; #endif //LL_LLPANELPEOPLE_H diff --git a/indra/newview/llparticipantlist.cpp b/indra/newview/llparticipantlist.cpp index c53760bca1..b5c9f4a310 100755 --- a/indra/newview/llparticipantlist.cpp +++ b/indra/newview/llparticipantlist.cpp @@ -27,6 +27,7 @@ #include "llviewerprecompiledheaders.h" #include "llavatarnamecache.h" +#include "llerror.h" #include "llimview.h" #include "llfloaterimcontainer.h" #include "llparticipantlist.h" @@ -401,6 +402,23 @@ void LLParticipantList::addAvatarIDExceptAgent(const LLUUID& avatar_id) adjustParticipant(avatar_id); } +static LLFastTimer::DeclareTimer FTM_FOLDERVIEW_TEST("add test avatar agents"); + + +void LLParticipantList::addTestAvatarAgents() +{ + LLFastTimer _(FTM_FOLDERVIEW_TEST); + + LL_INFOS("LLParticipantList") << "start adding 300 users" << LL_ENDL; + + for(int i = 0; i < 300; ++i) + { + addAvatarIDExceptAgent(LLUUID().generateNewID()); + } + + LL_INFOS("LLParticipantList") << "finished adding 300 users" << LL_ENDL; +} + void LLParticipantList::adjustParticipant(const LLUUID& speaker_id) { LLPointer<LLSpeaker> speakerp = mSpeakerMgr->findSpeaker(speaker_id); diff --git a/indra/newview/llparticipantlist.h b/indra/newview/llparticipantlist.h index 3a3ae76604..936e289c08 100755 --- a/indra/newview/llparticipantlist.h +++ b/indra/newview/llparticipantlist.h @@ -50,6 +50,7 @@ public: * @param[in] avatar_id - Avatar UUID to be added into the list */ void addAvatarIDExceptAgent(const LLUUID& avatar_id); + void addTestAvatarAgents(); /** * Refreshes the participant list. diff --git a/indra/newview/llpersonfolderview.cpp b/indra/newview/llpersonfolderview.cpp new file mode 100644 index 0000000000..7e969fc96c --- /dev/null +++ b/indra/newview/llpersonfolderview.cpp @@ -0,0 +1,149 @@ +/** +* @file llpersonfolderview.cpp +* @brief Implementation of llpersonfolderview +* @author Gilbert@lindenlab.com +* +* $LicenseInfo:firstyear=2013&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2013, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ + +#include "llviewerprecompiledheaders.h" + +#include "llpersonfolderview.h" + +#include "llpersontabview.h" + + +LLPersonFolderView::LLPersonFolderView(const Params &p) : +LLFolderView(p), + mConversationsEventStream("ConversationsEventsTwo") +{ + rename("Persons"); // For tracking! + mConversationsEventStream.listen("ConversationsRefresh", boost::bind(&LLPersonFolderView::onConversationModelEvent, this, _1)); + + createPersonTabs(); +} + +LLPersonFolderView::~LLPersonFolderView() +{ + mConversationsEventStream.stopListening("ConversationsRefresh"); +} + +BOOL LLPersonFolderView::handleMouseDown( S32 x, S32 y, MASK mask ) +{ + LLFolderViewItem * item = getCurSelectedItem(); + + //Will disable highlight on tab + if(item) + { + LLPersonTabView * person_tab= dynamic_cast<LLPersonTabView *>(item); + if(person_tab) + { + person_tab->highlight = false; + } + else + { + person_tab = dynamic_cast<LLPersonTabView *>(item->getParent()); + person_tab->highlight = false; + } + } + + mKeyboardSelection = FALSE; + mSearchString.clear(); + + LLEditMenuHandler::gEditMenuHandler = this; + + return LLView::handleMouseDown( x, y, mask ); +} + +void LLPersonFolderView::createPersonTabs() +{ + createPersonTab(LLPersonTabModel::FB_SL_NON_SL_FRIEND, "SL residents you may want to friend"); + createPersonTab(LLPersonTabModel::FB_ONLY_FRIEND, "Invite people you know to SL"); +} + +void LLPersonFolderView::createPersonTab(LLPersonTabModel::tab_type tab_type, const std::string& tab_name) +{ + //Create a person tab + LLPersonTabModel* item = new LLPersonTabModel(tab_type, tab_name, *mViewModel); + LLPersonTabView::Params params; + params.name = item->getDisplayName(); + params.root = this; + params.listener = item; + params.tool_tip = params.name; + LLPersonTabView * widget = LLUICtrlFactory::create<LLPersonTabView>(params); + widget->addToFolder(this); + + mIndexToFolderMap[tab_type] = item->getID(); + mPersonFolderModelMap[item->getID()] = item; + mPersonFolderViewMap[item->getID()] = widget; +} + +bool LLPersonFolderView::onConversationModelEvent(const LLSD &event) +{ + std::string type = event.get("type").asString(); + LLUUID folder_id = event.get("folder_id").asUUID(); + LLUUID person_id = event.get("person_id").asUUID(); + + if(type == "add_participant") + { + LLPersonTabModel * person_tab_model = dynamic_cast<LLPersonTabModel *>(mPersonFolderModelMap[folder_id]); + LLPersonTabView * person_tab_view = dynamic_cast<LLPersonTabView *>(mPersonFolderViewMap[folder_id]); + + if(person_tab_model) + { + LLPersonModel * person_model = person_tab_model->findParticipant(person_id); + + if(person_model) + { + LLPersonView * person_view = createConversationViewParticipant(person_model); + person_view->addToFolder(person_tab_view); + } + } + } + + return false; +} + +LLPersonView * LLPersonFolderView::createConversationViewParticipant(LLPersonModel * item) +{ + LLPersonView::Params params; + + params.name = item->getDisplayName(); + params.root = this; + params.listener = item; + + //24 should be loaded from .xml somehow + params.rect = LLRect (0, 24, getRect().getWidth(), 0); + params.tool_tip = params.name; + + return LLUICtrlFactory::create<LLPersonView>(params); +} + +LLPersonTabModel * LLPersonFolderView::getPersonTabModelByIndex(LLPersonTabModel::tab_type tab_type) +{ + return mPersonFolderModelMap[mIndexToFolderMap[tab_type]]; +} + +LLPersonTabView * LLPersonFolderView::getPersonTabViewByIndex(LLPersonTabModel::tab_type tab_type) +{ + return mPersonFolderViewMap[mIndexToFolderMap[tab_type]]; +} diff --git a/indra/newview/llpersonfolderview.h b/indra/newview/llpersonfolderview.h new file mode 100644 index 0000000000..85dec6515d --- /dev/null +++ b/indra/newview/llpersonfolderview.h @@ -0,0 +1,70 @@ +/** +* @file llpersonfolderview.h +* @brief Header file for llpersonfolderview +* @author Gilbert@lindenlab.com +* +* $LicenseInfo:firstyear=2013&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2013, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ +#ifndef LL_LLPERSONFOLDERVIEW_H +#define LL_LLPERSONFOLDERVIEW_H + +#include "llevents.h" +#include "llfolderview.h" +#include "llpersonmodelcommon.h" + +class LLPersonTabView; +class LLPersonView; +class LLPersonModel; + +typedef std::map<LLUUID, LLPersonTabModel *> person_folder_model_map; +typedef std::map<LLUUID, LLPersonTabView *> person_folder_view_map; + +class LLPersonFolderView : public LLFolderView +{ +public: + struct Params : public LLInitParam::Block<Params, LLFolderView::Params> + { + Params() + {} + }; + + LLPersonFolderView(const Params &p); + ~LLPersonFolderView(); + + BOOL handleMouseDown( S32 x, S32 y, MASK mask ); + + void createPersonTabs(); + void createPersonTab(LLPersonTabModel::tab_type tab_type, const std::string& tab_name); + bool onConversationModelEvent(const LLSD &event); + LLPersonView * createConversationViewParticipant(LLPersonModel * item); + + LLPersonTabModel * getPersonTabModelByIndex(LLPersonTabModel::tab_type tab_type); + LLPersonTabView * getPersonTabViewByIndex(LLPersonTabModel::tab_type tab_type); + + person_folder_model_map mPersonFolderModelMap; + person_folder_view_map mPersonFolderViewMap; + std::map<LLPersonTabModel::tab_type, LLUUID> mIndexToFolderMap; + LLEventStream mConversationsEventStream; +}; + +#endif // LL_LLPERSONFOLDERVIEW_H + diff --git a/indra/newview/llpersonmodelcommon.cpp b/indra/newview/llpersonmodelcommon.cpp new file mode 100644 index 0000000000..73239dcb8d --- /dev/null +++ b/indra/newview/llpersonmodelcommon.cpp @@ -0,0 +1,313 @@ +/** +* @file llavatarfolder.cpp +* @brief Implementation of llavatarfolder +* @author Gilbert@lindenlab.com +* +* $LicenseInfo:firstyear=2013&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2013, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ + +#include "llviewerprecompiledheaders.h" + +#include "llpersonmodelcommon.h" + +#include "llevents.h" +#include "llsdutil.h" +#include "llstring.h" + +// +// LLPersonModelCommon +// + +LLPersonModelCommon::LLPersonModelCommon(std::string display_name, LLFolderViewModelInterface& root_view_model) : + LLFolderViewModelItemCommon(root_view_model), + mLabelSuffix(""), + mID(LLUUID().generateNewID()) +{ + renameItem(display_name); +} + +LLPersonModelCommon::LLPersonModelCommon(std::string display_name, std::string suffix, LLFolderViewModelInterface& root_view_model) : +LLFolderViewModelItemCommon(root_view_model), + mID(LLUUID().generateNewID()) +{ + mLabelSuffix = suffix; + renameItem(display_name); +} + +LLPersonModelCommon::LLPersonModelCommon(LLFolderViewModelInterface& root_view_model) : + LLFolderViewModelItemCommon(root_view_model), + mName(""), + mLabelSuffix(""), + mSearchableName(""), + mPrevPassedAllFilters(false), + mID(LLUUID().generateNewID()) +{ +} + +LLPersonModelCommon::~LLPersonModelCommon() +{ + +} + +BOOL LLPersonModelCommon::renameItem(const std::string& new_name) +{ + mName = new_name; + mSearchableName = new_name + " " + mLabelSuffix; + LLStringUtil::toUpper(mSearchableName); + return TRUE; +} + +void LLPersonModelCommon::postEvent(const std::string& event_type, LLPersonTabModel* folder, LLPersonModel* person) +{ + LLUUID folder_id = folder->getID(); + LLUUID person_id = person->getID(); + LLSD event(LLSDMap("type", event_type)("folder_id", folder_id)("person_id", person_id)); + LLEventPumps::instance().obtain("ConversationsEventsTwo").post(event); +} + +// Virtual action callbacks +void LLPersonModelCommon::performAction(LLInventoryModel* model, std::string action) +{ +} + +void LLPersonModelCommon::openItem( void ) +{ +} + +void LLPersonModelCommon::closeItem( void ) +{ +} + +void LLPersonModelCommon::previewItem( void ) +{ +} + +void LLPersonModelCommon::showProperties(void) +{ +} + +bool LLPersonModelCommon::filter( LLFolderViewFilter& filter) +{ +/* + Hack: for the moment, we always apply the filter if we're called + if (!filter.isModified()) + { + llinfos << "Merov : LLPersonModelCommon::filter, exit, no modif" << llendl; + return true; + } + */ + if (!mChildren.empty()) + { + // If the current instance has children, it's a "person folder" and always passes filters (we do not filter out empty folders) + setPassedFilter(1, filter.getCurrentGeneration()); + // Call filter recursively on all children + for (child_list_t::iterator iter = mChildren.begin(), end_iter = mChildren.end(); + iter != end_iter; + ++iter) + { + LLPersonModelCommon* item = dynamic_cast<LLPersonModelCommon*>(*iter); + item->filter(filter); + } + } + else + { + // If there's no children, the current instance is a person and we check and set the passed filter flag on it + const bool passed_filter = filter.check(this); + setPassedFilter(passed_filter, filter.getCurrentGeneration(), filter.getStringMatchOffset(this), filter.getFilterStringSize()); + } + + filter.clearModified(); + return true; +} + +void LLPersonModelCommon::setPassedFilter(bool passed, S32 filter_generation, std::string::size_type string_offset, std::string::size_type string_size) +{ + LLFolderViewModelItemCommon::setPassedFilter(passed, filter_generation, string_offset, string_size); + bool before = mPrevPassedAllFilters; + mPrevPassedAllFilters = passedFilter(filter_generation); + + if (before != mPrevPassedAllFilters) + { + // Need to rearrange the folder if the filtered state of the item changed + LLFolderViewFolder* parent_folder = mFolderViewItem->getParentFolder(); + if (parent_folder) + { + parent_folder->requestArrange(); + } + } +} + + +// +// LLPersonTabModel +// + +LLPersonTabModel::LLPersonTabModel(tab_type tab_type, std::string display_name, LLFolderViewModelInterface& root_view_model) : +LLPersonModelCommon(display_name,root_view_model), +mTabType(tab_type) +{ + +} + +LLPersonTabModel::LLPersonTabModel(LLFolderViewModelInterface& root_view_model) : +LLPersonModelCommon(root_view_model) +{ + +} + +void LLPersonTabModel::addParticipant(LLPersonModel* participant) +{ + addChild(participant); + postEvent("add_participant", this, participant); +} + +void LLPersonTabModel::removeParticipant(LLPersonModel* participant) +{ + removeChild(participant); + postEvent("remove_participant", this, participant); +} + +void LLPersonTabModel::removeParticipant(const LLUUID& participant_id) +{ + LLPersonModel* participant = findParticipant(participant_id); + if (participant) + { + removeParticipant(participant); + } +} + +void LLPersonTabModel::clearParticipants() +{ + clearChildren(); +} + +LLPersonModel* LLPersonTabModel::findParticipant(const LLUUID& person_id) +{ + LLPersonModel * person_model = NULL; + child_list_t::iterator iter; + + for(iter = mChildren.begin(); iter != mChildren.end(); ++iter) + { + person_model = static_cast<LLPersonModel *>(*iter); + + if(person_model->getID() == person_id) + { + break; + } + } + + return iter == mChildren.end() ? NULL : person_model; +} + +// +// LLPersonModel +// + +LLPersonModel::LLPersonModel(const LLUUID& agent_id, const std::string display_name, const std::string suffix, LLFolderViewModelInterface& root_view_model) : +LLPersonModelCommon(display_name, suffix, root_view_model), +mAgentID(agent_id) +{ +} + +LLPersonModel::LLPersonModel(LLFolderViewModelInterface& root_view_model) : +LLPersonModelCommon(root_view_model), +mAgentID(LLUUID(NULL)) +{ +} + +LLUUID LLPersonModel::getAgentID() +{ + return mAgentID; +} + +// +// LLPersonViewFilter +// + +LLPersonViewFilter::LLPersonViewFilter() : + mEmptyLookupMessage(""), + mFilterSubString(""), + mName(""), + mFilterModified(FILTER_NONE), + mCurrentGeneration(0) +{ +} + +void LLPersonViewFilter::setFilterSubString(const std::string& string) +{ + std::string filter_sub_string_new = string; + LLStringUtil::trimHead(filter_sub_string_new); + LLStringUtil::toUpper(filter_sub_string_new); + + if (mFilterSubString != filter_sub_string_new) + { + // *TODO : Add logic to support more and less restrictive filtering + setModified(FILTER_RESTART); + mFilterSubString = filter_sub_string_new; + } +} + +bool LLPersonViewFilter::showAllResults() const +{ + return mFilterSubString.size() > 0; +} + +bool LLPersonViewFilter::check(const LLFolderViewModelItem* item) +{ + return (mFilterSubString.size() ? (item->getSearchableName().find(mFilterSubString) != std::string::npos) : true); +} + +std::string::size_type LLPersonViewFilter::getStringMatchOffset(LLFolderViewModelItem* item) const +{ + return mFilterSubString.size() ? item->getSearchableName().find(mFilterSubString) : std::string::npos; +} + +std::string::size_type LLPersonViewFilter::getFilterStringSize() const +{ + return mFilterSubString.size(); +} + +bool LLPersonViewFilter::isActive() const +{ + return mFilterSubString.size(); +} + +bool LLPersonViewFilter::isModified() const +{ + return mFilterModified != FILTER_NONE; +} + +void LLPersonViewFilter::clearModified() +{ + mFilterModified = FILTER_NONE; +} + +void LLPersonViewFilter::setEmptyLookupMessage(const std::string& message) +{ + mEmptyLookupMessage = message; +} + +std::string LLPersonViewFilter::getEmptyLookupMessage() const +{ + return mEmptyLookupMessage; +} + diff --git a/indra/newview/llpersonmodelcommon.h b/indra/newview/llpersonmodelcommon.h new file mode 100644 index 0000000000..74598eaee0 --- /dev/null +++ b/indra/newview/llpersonmodelcommon.h @@ -0,0 +1,248 @@ +/** +* @file llavatarfolder.h +* @brief Header file for llavatarfolder +* @author Gilbert@lindenlab.com +* +* $LicenseInfo:firstyear=2013&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2013, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ +#ifndef LL_LLPERSONMODELCOMMON_H +#define LL_LLPERSONMODELCOMMON_H + +#include "../llui/llfolderviewitem.h" +#include "../llui/llfolderviewmodel.h" + +class LLPersonTabModel; +class LLPersonModel; + +// Conversation items: we hold a list of those and create an LLFolderViewItem widget for each +// that we tuck into the mConversationsListPanel. +class LLPersonModelCommon : public LLFolderViewModelItemCommon +{ +public: + + LLPersonModelCommon(std::string name, LLFolderViewModelInterface& root_view_model); + LLPersonModelCommon(std::string display_name, std::string suffix, LLFolderViewModelInterface& root_view_model); + LLPersonModelCommon(LLFolderViewModelInterface& root_view_model); + virtual ~LLPersonModelCommon(); + + // Stub those things we won't really be using in this conversation context + virtual const std::string& getName() const { return mName; } + virtual const std::string& getDisplayName() const { return mName; } + virtual const std::string& getSearchableName() const { return mSearchableName; } + + virtual LLPointer<LLUIImage> getIcon() const { return NULL; } + virtual LLPointer<LLUIImage> getOpenIcon() const { return getIcon(); } + virtual LLFontGL::StyleFlags getLabelStyle() const { return LLFontGL::NORMAL; } + virtual std::string getLabelSuffix() const { return mLabelSuffix; } + virtual BOOL isItemRenameable() const { return TRUE; } + virtual BOOL renameItem(const std::string& new_name); + virtual BOOL isItemMovable( void ) const { return FALSE; } + virtual BOOL isItemRemovable( void ) const { return FALSE; } + virtual BOOL isItemInTrash( void) const { return FALSE; } + virtual BOOL removeItem() { return FALSE; } + virtual void removeBatch(std::vector<LLFolderViewModelItem*>& batch) { } + virtual void move( LLFolderViewModelItem* parent_listener ) { } + virtual BOOL isItemCopyable() const { return FALSE; } + virtual BOOL copyToClipboard() const { return FALSE; } + virtual BOOL cutToClipboard() const { return FALSE; } + virtual BOOL isClipboardPasteable() const { return FALSE; } + virtual void pasteFromClipboard() { } + virtual void pasteLinkFromClipboard() { } + virtual void buildContextMenu(LLMenuGL& menu, U32 flags) { } + virtual BOOL isUpToDate() const { return TRUE; } + virtual bool hasChildren() const { return FALSE; } + + virtual bool potentiallyVisible() { return true; } + + virtual bool filter( LLFolderViewFilter& filter); + + virtual bool descendantsPassedFilter(S32 filter_generation = -1) { return true; } + virtual void setPassedFilter(bool passed, S32 filter_generation, std::string::size_type string_offset = std::string::npos, std::string::size_type string_size = 0); + virtual bool passedFilter(S32 filter_generation = -1) { return mPassedFilter; } + + // The action callbacks + virtual void performAction(LLInventoryModel* model, std::string action); + virtual void openItem( void ); + virtual void closeItem( void ); + virtual void previewItem( void ); + virtual void selectItem(void) { } + virtual void showProperties(void); + + // This method will be called to determine if a drop can be + // performed, and will set drop to TRUE if a drop is + // requested. + // Returns TRUE if a drop is possible/happened, FALSE otherwise. + virtual BOOL dragOrDrop(MASK mask, BOOL drop, + EDragAndDropType cargo_type, + void* cargo_data, + std::string& tooltip_msg) { return FALSE; } + + const LLUUID& getID() {return mID;} + void postEvent(const std::string& event_type, LLPersonTabModel* session, LLPersonModel* participant); + +protected: + + std::string mName; // Name of the person + std::string mLabelSuffix; + std::string mSearchableName; // Name used in string matching for this person + bool mPrevPassedAllFilters; + LLUUID mID; +}; + +class LLPersonTabModel : public LLPersonModelCommon +{ +public: + enum tab_type + { + FB_SL_NON_SL_FRIEND, + FB_ONLY_FRIEND, + }; + + LLPersonTabModel(tab_type tab_type, std::string display_name, LLFolderViewModelInterface& root_view_model); + LLPersonTabModel(LLFolderViewModelInterface& root_view_model); + + LLPointer<LLUIImage> getIcon() const { return NULL; } + void addParticipant(LLPersonModel* participant); + void removeParticipant(LLPersonModel* participant); + void removeParticipant(const LLUUID& participant_id); + void clearParticipants(); + LLPersonModel* findParticipant(const LLUUID& person_id); + + tab_type mTabType; + +private: +}; + +class LLPersonModel : public LLPersonModelCommon +{ +public: + LLPersonModel(const LLUUID& agent_id, const std::string display_name, const std::string suffix, LLFolderViewModelInterface& root_view_model); + LLPersonModel(LLFolderViewModelInterface& root_view_model); + + LLUUID getAgentID(); + +private: + LLUUID mAgentID; +}; + +// Filtering functional object + +class LLPersonViewFilter : public LLFolderViewFilter +{ +public: + + enum ESortOrderType + { + SO_NAME = 0, // Sort by name + SO_ONLINE_STATUS = 0x1 // Sort by online status (i.e. online or not) + }; + // Default sort order is by name + static const U32 SO_DEFAULT = SO_NAME; + + LLPersonViewFilter(); + ~LLPersonViewFilter() {} + + // +-------------------------------------------------------------------+ + // + Execution And Results + // +-------------------------------------------------------------------+ + bool check(const LLFolderViewModelItem* item); + bool checkFolder(const LLFolderViewModelItem* folder) const { return true; } + + void setEmptyLookupMessage(const std::string& message); + std::string getEmptyLookupMessage() const; + + bool showAllResults() const; + + std::string::size_type getStringMatchOffset(LLFolderViewModelItem* item) const; + std::string::size_type getFilterStringSize() const; + + // +-------------------------------------------------------------------+ + // + Status + // +-------------------------------------------------------------------+ + bool isActive() const; + bool isModified() const; + void clearModified(); + const std::string& getName() const { return mName; } + const std::string& getFilterText() { return mName; } + void setModified(EFilterModified behavior = FILTER_RESTART) { mFilterModified = behavior; mCurrentGeneration++; } + + // +-------------------------------------------------------------------+ + // + Time + // +-------------------------------------------------------------------+ + // Note : we currently filter the whole person list at once, no need to timeout then. + void resetTime(S32 timeout) { } + bool isTimedOut() { return false; } + + // +-------------------------------------------------------------------+ + // + Default + // +-------------------------------------------------------------------+ + // Note : we don't support runtime default setting for person filter + bool isDefault() const { return !isActive(); } + bool isNotDefault() const { return isActive(); } + void markDefault() { } + void resetDefault() { setModified(); } + + // +-------------------------------------------------------------------+ + // + Generation + // +-------------------------------------------------------------------+ + // Note : For the moment, we do not support restrictive filtering so all generation indexes are pointing to the current generation + S32 getCurrentGeneration() const { return mCurrentGeneration; } + S32 getFirstSuccessGeneration() const { return mCurrentGeneration; } + S32 getFirstRequiredGeneration() const { return mCurrentGeneration; } + + // Non Virtual Methods (i.e. specific to this class) + void setFilterSubString(const std::string& string); + +private: + std::string mName; + std::string mEmptyLookupMessage; + std::string mFilterSubString; + EFilterModified mFilterModified; + S32 mCurrentGeneration; +}; + +class LLPersonViewSort +{ +public: + LLPersonViewSort(U32 order = LLPersonViewFilter::SO_DEFAULT) : mSortOrder(order) { } + + bool operator()(const LLPersonModelCommon* const& a, const LLPersonModelCommon* const& b) const {return false;} + operator U32() const { return mSortOrder; } +private: + // Note: we're treating this value as a sort order bitmask as done in other places in the code (e.g. inventory) + U32 mSortOrder; +}; + + +class LLPersonFolderViewModel + : public LLFolderViewModel<LLPersonViewSort, LLPersonModelCommon, LLPersonModelCommon, LLPersonViewFilter> +{ +public: + typedef LLFolderViewModel<LLPersonViewSort, LLPersonModelCommon, LLPersonModelCommon, LLPersonViewFilter> base_t; + + void sort(LLFolderViewFolder* folder) { base_t::sort(folder);} + bool startDrag(std::vector<LLFolderViewModelItem*>& items) { return false; } // We do not allow drag of conversation items +}; + + +#endif // LL_LLPERSONMODELCOMMON_H + diff --git a/indra/newview/llpersontabview.cpp b/indra/newview/llpersontabview.cpp new file mode 100644 index 0000000000..34ffc6ffce --- /dev/null +++ b/indra/newview/llpersontabview.cpp @@ -0,0 +1,465 @@ +/** +* @file llpersontabview.cpp +* @brief Implementation of llpersontabview +* @author Gilbert@lindenlab.com +* +* $LicenseInfo:firstyear=2013&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2013, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ + +#include "llviewerprecompiledheaders.h" + +#include "llpersontabview.h" + +#include "llavataractions.h" +#include "llfloaterreg.h" +#include "llpersonmodelcommon.h" + +static LLDefaultChildRegistry::Register<LLPersonTabView> r_person_tab_view("person_tab_view"); + +const LLColor4U DEFAULT_WHITE(255, 255, 255); + +LLPersonTabView::Params::Params() +{} + +LLPersonTabView::LLPersonTabView(const LLPersonTabView::Params& p) : +LLFolderViewFolder(p), +highlight(false), +mImageHeader(LLUI::getUIImage("Accordion_Off")), +mImageHeaderOver(LLUI::getUIImage("Accordion_Over")), +mImageHeaderFocused(LLUI::getUIImage("Accordion_Selected")) +{ +} + +S32 LLPersonTabView::getLabelXPos() +{ + return getIndentation() + mArrowSize + 15;//Should be a .xml variable but causes crash; +} + +LLPersonTabView::~LLPersonTabView() +{ + +} + +BOOL LLPersonTabView::handleMouseDown( S32 x, S32 y, MASK mask ) +{ + bool selected_item = LLFolderViewFolder::handleMouseDown(x, y, mask); + + if(selected_item) + { + gFocusMgr.setKeyboardFocus( this ); + highlight = true; + } + + return selected_item; +} + +void LLPersonTabView::draw() +{ + static LLUIColor sFgColor = LLUIColorTable::instance().getColor("MenuItemEnabledColor", DEFAULT_WHITE); + static const LLFolderViewItem::Params& default_params = LLUICtrlFactory::getDefaultParams<LLPersonTabView>(); + + const LLFontGL * font = LLFontGL::getFontSansSerif(); + F32 text_left = (F32)getLabelXPos(); + F32 y = (F32)getRect().getHeight() - font->getLineHeight() - (F32)mTextPad; + LLColor4 color = sFgColor; + F32 right_x = 0; + + drawHighlight(); + updateLabelRotation(); + drawOpenFolderArrow(default_params, sFgColor); + + drawLabel(font, text_left, y, color, right_x); + + LLView::draw(); +} + +void LLPersonTabView::drawHighlight() +{ + S32 width = getRect().getWidth(); + S32 height = mItemHeight; + S32 x = 1; + S32 y = getRect().getHeight() - mItemHeight; + + if(highlight) + { + mImageHeaderFocused->draw(x,y,width,height); + } + else + { + mImageHeader->draw(x,y,width,height); + } + + if(mIsMouseOverTitle) + { + mImageHeaderOver->draw(x,y,width,height); + } + +} + +// +// LLPersonView +// + +static LLDefaultChildRegistry::Register<LLPersonView> r_person_view("person_view"); + +bool LLPersonView::sChildrenWidthsInitialized = false; +ChildWidthVec LLPersonView::mChildWidthVec; + +LLPersonView::Params::Params() : +facebook_icon("facebook_icon"), +avatar_icon("avatar_icon"), +last_interaction_time_textbox("last_interaction_time_textbox"), +permission_edit_theirs_icon("permission_edit_theirs_icon"), +permission_edit_mine_icon("permission_edit_mine_icon"), +permission_map_icon("permission_map_icon"), +permission_online_icon("permission_online_icon"), +info_btn("info_btn"), +profile_btn("profile_btn"), +output_monitor("output_monitor") +{} + +LLPersonView::LLPersonView(const LLPersonView::Params& p) : +LLFolderViewItem(p), +mImageOver(LLUI::getUIImage("ListItem_Over")), +mImageSelected(LLUI::getUIImage("ListItem_Select")), +mFacebookIcon(NULL), +mAvatarIcon(NULL), +mLastInteractionTimeTextbox(NULL), +mPermissionEditTheirsIcon(NULL), +mPermissionEditMineIcon(NULL), +mPermissionMapIcon(NULL), +mPermissionOnlineIcon(NULL), +mInfoBtn(NULL), +mProfileBtn(NULL), +mOutputMonitorCtrl(NULL) +{ +} + +S32 LLPersonView::getLabelXPos() +{ + S32 label_x_pos; + + if(mAvatarIcon->getVisible()) + { + label_x_pos = getIndentation() + mAvatarIcon->getRect().getWidth() + mIconPad; + } + else + { + label_x_pos = getIndentation() + mFacebookIcon->getRect().getWidth() + mIconPad; + } + + + return label_x_pos; +} + +void LLPersonView::addToFolder(LLFolderViewFolder * person_folder_view) +{ + const LLFontGL * font = LLFontGL::getFontSansSerifSmall(); + + LLFolderViewItem::addToFolder(person_folder_view); + //Added item to folder could change folder's mHasVisibleChildren flag so call arrange + person_folder_view->requestArrange(); + + mPersonTabModel = static_cast<LLPersonTabModel *>(getParentFolder()->getViewModelItem()); + + if(mPersonTabModel->mTabType == LLPersonTabModel::FB_SL_NON_SL_FRIEND) + { + mAvatarIcon->setVisible(TRUE); + mFacebookIcon->setVisible(TRUE); + + S32 label_width = font->getWidth(mLabel); + F32 text_left = (F32)getLabelXPos(); + + LLRect mFacebookIconRect = mFacebookIcon->getRect(); + S32 new_left = text_left + label_width + 7; + mFacebookIconRect.set(new_left, + mFacebookIconRect.mTop, + new_left + mFacebookIconRect.getWidth(), + mFacebookIconRect.mBottom); + mFacebookIcon->setRect(mFacebookIconRect); + } + else if(mPersonTabModel->mTabType == LLPersonTabModel::FB_ONLY_FRIEND) + { + mFacebookIcon->setVisible(TRUE); + } + +} + +LLPersonView::~LLPersonView() +{ + +} + +BOOL LLPersonView::postBuild() +{ + if(!sChildrenWidthsInitialized) + { + initChildrenWidthVec(this); + sChildrenWidthsInitialized = true; + } + + initChildVec(); + updateChildren(); + + LLPersonModel * person_model = static_cast<LLPersonModel *>(getViewModelItem()); + + mAvatarIcon->setValue(person_model->getAgentID()); + mInfoBtn->setClickedCallback(boost::bind(&LLFloaterReg::showInstance, "inspect_avatar", LLSD().with("avatar_id", person_model->getAgentID()), FALSE)); + mProfileBtn->setClickedCallback(boost::bind(&LLAvatarActions::showProfile, person_model->getAgentID())); + + return LLFolderViewItem::postBuild(); +} + +void LLPersonView::onMouseEnter(S32 x, S32 y, MASK mask) +{ + if(mPersonTabModel->mTabType == LLPersonTabModel::FB_SL_NON_SL_FRIEND) + { + mInfoBtn->setVisible(TRUE); + mProfileBtn->setVisible(TRUE); + } + + updateChildren(); + LLFolderViewItem::onMouseEnter(x, y, mask); +} + +void LLPersonView::onMouseLeave(S32 x, S32 y, MASK mask) +{ + if(mPersonTabModel->mTabType == LLPersonTabModel::FB_SL_NON_SL_FRIEND) + { + mInfoBtn->setVisible(FALSE); + mProfileBtn->setVisible(FALSE); + } + + updateChildren(); + LLFolderViewItem::onMouseLeave(x, y, mask); +} + +BOOL LLPersonView::handleMouseDown( S32 x, S32 y, MASK mask) +{ + if(!LLView::childrenHandleMouseDown(x, y, mask)) + { + gFocusMgr.setMouseCapture( this ); + } + + if (!mIsSelected) + { + if(mask & MASK_CONTROL) + { + getRoot()->changeSelection(this, !mIsSelected); + } + else if (mask & MASK_SHIFT) + { + getParentFolder()->extendSelectionTo(this); + } + else + { + getRoot()->setSelection(this, FALSE); + } + make_ui_sound("UISndClick"); + } + else + { + // If selected, we reserve the decision of deselecting/reselecting to the mouse up moment. + // This is necessary so we maintain selection consistent when starting a drag. + mSelectPending = TRUE; + } + + mDragStartX = x; + mDragStartY = y; + return TRUE; +} + +void LLPersonView::draw() +{ + static LLUIColor sFgColor = LLUIColorTable::instance().getColor("MenuItemEnabledColor", DEFAULT_WHITE); + static LLUIColor sHighlightFgColor = LLUIColorTable::instance().getColor("MenuItemHighlightFgColor", DEFAULT_WHITE); + + const LLFontGL * font = LLFontGL::getFontSansSerifSmall(); + F32 text_left = (F32)getLabelXPos(); + F32 y = (F32)getRect().getHeight() - font->getLineHeight() - (F32)mTextPad; + LLColor4 color = mIsSelected ? sHighlightFgColor : sFgColor; + F32 right_x = 0; + + drawHighlight(); + if(mLabel.length()) + { + drawLabel(mLabel, font, text_left, y, color, right_x); + } + if(mLabelSuffix.length()) + { + drawLabel(mLabelSuffix, font, mFacebookIcon->getRect().mRight + 7, y, color, right_x); + } + + LLView::draw(); +} + +void LLPersonView::drawHighlight() +{ + static LLUIColor outline_color = LLUIColorTable::instance().getColor("EmphasisColor", DEFAULT_WHITE); + + S32 width = getRect().getWidth(); + S32 height = mItemHeight; + S32 x = 1; + S32 y = 0; + + if(mIsSelected) + { + mImageSelected->draw(x, y, width, height); + //Draw outline + gl_rect_2d(x, + height, + width, + y, + outline_color, FALSE); + } + + if(mIsMouseOverTitle) + { + mImageOver->draw(x, y, width, height); + } +} + +void LLPersonView::drawLabel(const std::string text, const LLFontGL * font, const F32 x, const F32 y, const LLColor4& color, F32 &right_x) +{ + font->renderUTF8(text, 0, x, y, color, + LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, + S32_MAX, getRect().getWidth() - (S32) x - mLabelPaddingRight, &right_x, TRUE); +} + +void LLPersonView::initFromParams(const LLPersonView::Params & params) +{ + LLIconCtrl::Params facebook_icon_params(params.facebook_icon()); + applyXUILayout(facebook_icon_params, this); + mFacebookIcon = LLUICtrlFactory::create<LLIconCtrl>(facebook_icon_params); + addChild(mFacebookIcon); + + LLAvatarIconCtrl::Params avatar_icon_params(params.avatar_icon()); + applyXUILayout(avatar_icon_params, this); + mAvatarIcon = LLUICtrlFactory::create<LLAvatarIconCtrl>(avatar_icon_params); + addChild(mAvatarIcon); + + LLTextBox::Params last_interaction_time_textbox(params.last_interaction_time_textbox()); + applyXUILayout(last_interaction_time_textbox, this); + mLastInteractionTimeTextbox = LLUICtrlFactory::create<LLTextBox>(last_interaction_time_textbox); + addChild(mLastInteractionTimeTextbox); + + LLIconCtrl::Params permission_edit_theirs_icon(params.permission_edit_theirs_icon()); + applyXUILayout(permission_edit_theirs_icon, this); + mPermissionEditTheirsIcon = LLUICtrlFactory::create<LLIconCtrl>(permission_edit_theirs_icon); + addChild(mPermissionEditTheirsIcon); + + LLIconCtrl::Params permission_edit_mine_icon(params.permission_edit_mine_icon()); + applyXUILayout(permission_edit_mine_icon, this); + mPermissionEditMineIcon = LLUICtrlFactory::create<LLIconCtrl>(permission_edit_mine_icon); + addChild(mPermissionEditMineIcon); + + LLIconCtrl::Params permission_map_icon(params.permission_map_icon()); + applyXUILayout(permission_map_icon, this); + mPermissionMapIcon = LLUICtrlFactory::create<LLIconCtrl>(permission_map_icon); + addChild(mPermissionMapIcon); + + LLIconCtrl::Params permission_online_icon(params.permission_online_icon()); + applyXUILayout(permission_online_icon, this); + mPermissionOnlineIcon = LLUICtrlFactory::create<LLIconCtrl>(permission_online_icon); + addChild(mPermissionOnlineIcon); + + LLButton::Params info_btn(params.info_btn()); + applyXUILayout(info_btn, this); + mInfoBtn = LLUICtrlFactory::create<LLButton>(info_btn); + addChild(mInfoBtn); + + LLButton::Params profile_btn(params.profile_btn()); + applyXUILayout(profile_btn, this); + mProfileBtn = LLUICtrlFactory::create<LLButton>(profile_btn); + addChild(mProfileBtn); + + LLOutputMonitorCtrl::Params output_monitor(params.output_monitor()); + applyXUILayout(output_monitor, this); + mOutputMonitorCtrl = LLUICtrlFactory::create<LLOutputMonitorCtrl>(output_monitor); + addChild(mOutputMonitorCtrl); +} + +void LLPersonView::initChildrenWidthVec(LLPersonView* self) +{ + S32 output_monitor_width = self->getRect().getWidth() - self->mOutputMonitorCtrl->getRect().mLeft; + S32 profile_btn_width = self->mOutputMonitorCtrl->getRect().mLeft - self->mProfileBtn->getRect().mLeft; + S32 info_btn_width = self->mProfileBtn->getRect().mLeft - self->mInfoBtn->getRect().mLeft; + S32 permission_online_icon_width = self->mInfoBtn->getRect().mLeft - self->mPermissionOnlineIcon->getRect().mLeft; + S32 permissions_map_icon_width = self->mPermissionOnlineIcon->getRect().mLeft - self->mPermissionMapIcon->getRect().mLeft; + S32 permission_edit_mine_icon_width = self->mPermissionMapIcon->getRect().mLeft - self->mPermissionEditMineIcon->getRect().mLeft; + S32 permission_edit_theirs_icon_width = self->mPermissionEditMineIcon->getRect().mLeft - self->mPermissionEditTheirsIcon->getRect().mLeft; + S32 last_interaction_time_textbox_width = self->mPermissionEditTheirsIcon->getRect().mLeft - self->mLastInteractionTimeTextbox->getRect().mLeft; + + self->mChildWidthVec.push_back(output_monitor_width); + self->mChildWidthVec.push_back(profile_btn_width); + self->mChildWidthVec.push_back(info_btn_width); + self->mChildWidthVec.push_back(permission_online_icon_width); + self->mChildWidthVec.push_back(permissions_map_icon_width); + self->mChildWidthVec.push_back(permission_edit_mine_icon_width); + self->mChildWidthVec.push_back(permission_edit_theirs_icon_width); + self->mChildWidthVec.push_back(last_interaction_time_textbox_width); +} + +void LLPersonView::initChildVec() +{ + mChildVec.push_back(mOutputMonitorCtrl); + mChildVec.push_back(mProfileBtn); + mChildVec.push_back(mInfoBtn); + mChildVec.push_back(mPermissionOnlineIcon); + mChildVec.push_back(mPermissionMapIcon); + mChildVec.push_back(mPermissionEditMineIcon); + mChildVec.push_back(mPermissionEditTheirsIcon); + mChildVec.push_back(mLastInteractionTimeTextbox); +} + +void LLPersonView::updateChildren() +{ + mLabelPaddingRight = 0; + LLView * control; + S32 control_width; + LLRect control_rect; + + llassert(mChildWidthVec.size() == mChildVec.size()); + + for(S32 i = 0; i < mChildWidthVec.size(); ++i) + { + control = mChildVec[i]; + + if(!control->getVisible()) + { + continue; + } + + control_width = mChildWidthVec[i]; + mLabelPaddingRight += control_width; + + control_rect = control->getRect(); + control_rect.setLeftTopAndSize( + getLocalRect().getWidth() - mLabelPaddingRight, + control_rect.mTop, + control_rect.getWidth(), + control_rect.getHeight()); + + control->setShape(control_rect); + + } +} diff --git a/indra/newview/llpersontabview.h b/indra/newview/llpersontabview.h new file mode 100644 index 0000000000..6f244c2794 --- /dev/null +++ b/indra/newview/llpersontabview.h @@ -0,0 +1,151 @@ +/** +* @file llpersontabview.h +* @brief Header file for llpersontabview +* @author Gilbert@lindenlab.com +* +* $LicenseInfo:firstyear=2013&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2013, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ +#ifndef LL_LLPERSONTABVIEW_H +#define LL_LLPERSONTABVIEW_H + +#include "llavatariconctrl.h" +#include "llbutton.h" +#include "llfolderviewitem.h" +#include "lloutputmonitorctrl.h" +#include "lltextbox.h" + +class LLPersonTabModel; + +class LLPersonTabView : public LLFolderViewFolder +{ + +public: + + struct Params : public LLInitParam::Block<Params, LLFolderViewFolder::Params> + { + Params(); + }; + + LLPersonTabView(const LLPersonTabView::Params& p); + virtual ~LLPersonTabView(); + + S32 getLabelXPos(); + bool highlight; + + BOOL handleMouseDown( S32 x, S32 y, MASK mask ); + +protected: + void draw(); + void drawHighlight(); + +private: + + // Background images + LLPointer<LLUIImage> mImageHeader; + LLPointer<LLUIImage> mImageHeaderOver; + LLPointer<LLUIImage> mImageHeaderFocused; + +}; + +typedef std::vector<S32> ChildWidthVec; +typedef std::vector<LLView *> ChildVec; + +class LLPersonView : public LLFolderViewItem +{ + +public: + + struct Params : public LLInitParam::Block<Params, LLFolderViewItem::Params> + { + Params(); + Optional<LLIconCtrl::Params> facebook_icon; + Optional<LLAvatarIconCtrl::Params> avatar_icon; + Optional<LLTextBox::Params> last_interaction_time_textbox; + Optional<LLIconCtrl::Params> permission_edit_theirs_icon; + Optional<LLIconCtrl::Params> permission_edit_mine_icon; + Optional<LLIconCtrl::Params> permission_map_icon; + Optional<LLIconCtrl::Params> permission_online_icon; + Optional<LLButton::Params> info_btn; + Optional<LLButton::Params> profile_btn; + Optional<LLOutputMonitorCtrl::Params> output_monitor; + }; + + LLPersonView(const LLPersonView::Params& p); + virtual ~LLPersonView(); + + S32 getLabelXPos(); + void addToFolder(LLFolderViewFolder * person_folder_view); + void initFromParams(const LLPersonView::Params & params); + BOOL postBuild(); + void onMouseEnter(S32 x, S32 y, MASK mask); + void onMouseLeave(S32 x, S32 y, MASK mask); + BOOL handleMouseDown( S32 x, S32 y, MASK mask); + +protected: + + void draw(); + void drawHighlight(); + void drawLabel(const std::string text, const LLFontGL * font, const F32 x, const F32 y, const LLColor4& color, F32 &right_x); + +private: + + //Short-cut to tab model + LLPersonTabModel * mPersonTabModel; + + LLPointer<LLUIImage> mImageOver; + LLPointer<LLUIImage> mImageSelected; + LLIconCtrl * mFacebookIcon; + LLAvatarIconCtrl* mAvatarIcon; + LLTextBox * mLastInteractionTimeTextbox; + LLIconCtrl * mPermissionEditTheirsIcon; + LLIconCtrl * mPermissionEditMineIcon; + LLIconCtrl * mPermissionMapIcon; + LLIconCtrl * mPermissionOnlineIcon; + LLButton * mInfoBtn; + LLButton * mProfileBtn; + LLOutputMonitorCtrl * mOutputMonitorCtrl; + + typedef enum e_avatar_item_child { + ALIC_SPEAKER_INDICATOR, + ALIC_PROFILE_BUTTON, + ALIC_INFO_BUTTON, + ALIC_PERMISSION_ONLINE, + ALIC_PERMISSION_MAP, + ALIC_PERMISSION_EDIT_MINE, + ALIC_PERMISSION_EDIT_THEIRS, + ALIC_INTERACTION_TIME, + ALIC_COUNT, + } EAvatarListItemChildIndex; + + //Widths of controls are same for every instance so can be static + static ChildWidthVec mChildWidthVec; + //Control pointers are different for each instance so non-static + ChildVec mChildVec; + + static bool sChildrenWidthsInitialized; + static void initChildrenWidthVec(LLPersonView* self); + void initChildVec(); + void updateChildren(); +}; + +#endif // LL_LLPERSONTABVIEW_H + diff --git a/indra/newview/llsociallist.cpp b/indra/newview/llsociallist.cpp new file mode 100644 index 0000000000..9f827cf04f --- /dev/null +++ b/indra/newview/llsociallist.cpp @@ -0,0 +1,154 @@ +/** +* @file llsociallist.cpp +* @brief Implementation of llsociallist +* @author Gilbert@lindenlab.com +* +* $LicenseInfo:firstyear=2013&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2013, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ + + +#include "llviewerprecompiledheaders.h" + +#include "llsociallist.h" + +#include "llavataractions.h" +#include "llfloaterreg.h" +#include "llavatariconctrl.h" +#include "llavatarnamecache.h" +#include "lloutputmonitorctrl.h" +#include "lltextutil.h" + +static LLDefaultChildRegistry::Register<LLSocialList> r("social_list"); + +LLSocialList::LLSocialList(const Params&p) : LLFlatListViewEx(p) +{ + +} + +LLSocialList::~LLSocialList() +{ + +} + +void LLSocialList::draw() +{ + LLFlatListView::draw(); +} + +void LLSocialList::refresh() +{ + +} + +void LLSocialList::addNewItem(const LLUUID& id, const std::string& name, BOOL is_online, EAddPosition pos) +{ + LLSocialListItem * item = new LLSocialListItem(); + LLAvatarName avatar_name; + bool has_avatar_name = id.notNull() && LLAvatarNameCache::get(id, &avatar_name); + + item->mAvatarId = id; + if(id.notNull()) + { + item->mIcon->setValue(id); + } + + item->setName(has_avatar_name ? name + " (" + avatar_name.getDisplayName() + ")" : name, mNameFilter); + addItem(item, id, pos); +} + +LLSocialListItem::LLSocialListItem() +{ + buildFromFile("panel_avatar_list_item.xml"); +} + +LLSocialListItem::~LLSocialListItem() +{ + +} + +BOOL LLSocialListItem::postBuild() +{ + mIcon = getChild<LLAvatarIconCtrl>("avatar_icon"); + mLabelTextBox = getChild<LLTextBox>("avatar_name"); + + mLastInteractionTime = getChild<LLTextBox>("last_interaction"); + mIconPermissionOnline = getChild<LLIconCtrl>("permission_online_icon"); + mIconPermissionMap = getChild<LLIconCtrl>("permission_map_icon"); + mIconPermissionEditMine = getChild<LLIconCtrl>("permission_edit_mine_icon"); + mIconPermissionEditTheirs = getChild<LLIconCtrl>("permission_edit_theirs_icon"); + mSpeakingIndicator = getChild<LLOutputMonitorCtrl>("speaking_indicator"); + mInfoBtn = getChild<LLButton>("info_btn"); + mProfileBtn = getChild<LLButton>("profile_btn"); + + mLastInteractionTime->setVisible(false); + mIconPermissionOnline->setVisible(false); + mIconPermissionMap->setVisible(false); + mIconPermissionEditMine->setVisible(false); + mIconPermissionEditTheirs->setVisible(false); + mSpeakingIndicator->setVisible(false); + mInfoBtn->setVisible(false); + mProfileBtn->setVisible(false); + + mInfoBtn->setClickedCallback(boost::bind(&LLSocialListItem::onInfoBtnClick, this)); + mProfileBtn->setClickedCallback(boost::bind(&LLSocialListItem::onProfileBtnClick, this)); + + return TRUE; +} + +void LLSocialListItem::setName(const std::string& name, const std::string& highlight) +{ + mLabel = name; + LLTextUtil::textboxSetHighlightedVal(mLabelTextBox, mLabelTextBoxStyle, name, highlight); +} + +void LLSocialListItem::setValue(const LLSD& value) +{ + getChildView("selected_icon")->setVisible( value["selected"]); +} + +void LLSocialListItem::onMouseEnter(S32 x, S32 y, MASK mask) +{ + getChildView("hovered_icon")->setVisible( true); + mInfoBtn->setVisible(true); + mProfileBtn->setVisible(true); + + LLPanel::onMouseEnter(x, y, mask); +} + +void LLSocialListItem::onMouseLeave(S32 x, S32 y, MASK mask) +{ + getChildView("hovered_icon")->setVisible( false); + mInfoBtn->setVisible(false); + mProfileBtn->setVisible(false); + + LLPanel::onMouseLeave(x, y, mask); +} + +void LLSocialListItem::onInfoBtnClick() +{ + LLFloaterReg::showInstance("inspect_avatar", LLSD().with("avatar_id", mAvatarId)); +} + +void LLSocialListItem::onProfileBtnClick() +{ + LLAvatarActions::showProfile(mAvatarId); +} diff --git a/indra/newview/llsociallist.h b/indra/newview/llsociallist.h new file mode 100644 index 0000000000..bc667fc400 --- /dev/null +++ b/indra/newview/llsociallist.h @@ -0,0 +1,102 @@ +/** +* @file llsociallist.h +* @brief Header file for llsociallist +* @author Gilbert@lindenlab.com +* +* $LicenseInfo:firstyear=2013&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2013, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ +#ifndef LL_LLSOCIALLIST_H +#define LL_LLSOCIALLIST_H + +#include "llflatlistview.h" +#include "llstyle.h" + + +/** + * Generic list of avatars. + * + * Updates itself when it's dirty, using optional name filter. + * To initiate update, modify the UUID list and call setDirty(). + * + * @see getIDs() + * @see setDirty() + * @see setNameFilter() + */ + +class LLAvatarIconCtrl; +class LLIconCtrl; +class LLOutputMonitorCtrl; + +class LLSocialList : public LLFlatListViewEx +{ +public: + + struct Params : public LLInitParam::Block<Params, LLFlatListViewEx::Params> + { + }; + + LLSocialList(const Params&p); + virtual ~LLSocialList(); + + virtual void draw(); + void refresh(); + void addNewItem(const LLUUID& id, const std::string& name, BOOL is_online, EAddPosition pos = ADD_BOTTOM); + + + + std::string mNameFilter; +}; + +class LLSocialListItem : public LLPanel +{ + public: + LLSocialListItem(); + ~LLSocialListItem(); + + BOOL postBuild(); + void setName(const std::string& name, const std::string& highlight = LLStringUtil::null); + void setValue(const LLSD& value); + void onMouseEnter(S32 x, S32 y, MASK mask); + void onMouseLeave(S32 x, S32 y, MASK mask); + void onInfoBtnClick(); + void onProfileBtnClick(); + + LLUUID mAvatarId; + + LLTextBox * mLabelTextBox; + std::string mLabel; + LLStyle::Params mLabelTextBoxStyle; + + + LLAvatarIconCtrl * mIcon; + LLTextBox * mLastInteractionTime; + LLIconCtrl * mIconPermissionOnline; + LLIconCtrl * mIconPermissionMap; + LLIconCtrl * mIconPermissionEditMine; + LLIconCtrl * mIconPermissionEditTheirs; + LLOutputMonitorCtrl * mSpeakingIndicator; + LLButton * mInfoBtn; + LLButton * mProfileBtn; +}; + + +#endif // LL_LLSOCIALLIST_H diff --git a/indra/newview/lltoastimpanel.cpp b/indra/newview/lltoastimpanel.cpp index 75e6e3d13a..025ef3945d 100755 --- a/indra/newview/lltoastimpanel.cpp +++ b/indra/newview/lltoastimpanel.cpp @@ -28,6 +28,7 @@ #include "lltoastimpanel.h" #include "llagent.h" +#include "llavatarnamecache.h" #include "llfloaterreg.h" #include "llgroupactions.h" #include "llgroupiconctrl.h" @@ -61,6 +62,15 @@ LLToastIMPanel::LLToastIMPanel(LLToastIMPanel::Params &p) : LLToastPanel(p.notif style_params.font.name(font_name); style_params.font.size(font_size); + LLIMModel::LLIMSession* im_session = LLIMModel::getInstance()->findIMSession(p.session_id); + mIsGroupMsg = (im_session->mSessionType == LLIMModel::LLIMSession::GROUP_SESSION); + if(mIsGroupMsg) + { + mAvatarName->setValue(im_session->mName); + LLAvatarName avatar_name; + LLAvatarNameCache::get(p.avatar_id, &avatar_name); + p.message = "[From " + avatar_name.getDisplayName() + "]\n" + p.message; + } //Handle IRC styled /me messages. std::string prefix = p.message.substr(0, 4); @@ -81,12 +91,17 @@ LLToastIMPanel::LLToastIMPanel(LLToastIMPanel::Params &p) : LLToastPanel(p.notif mMessage->setText(p.message, style_params); } - mAvatarName->setValue(p.from); + if(!mIsGroupMsg) + { + mAvatarName->setValue(p.from); + } mTime->setValue(p.time); mSessionID = p.session_id; mAvatarID = p.avatar_id; mNotification = p.notification; + + initIcon(); S32 maxLinesCount; @@ -147,7 +162,14 @@ void LLToastIMPanel::spawnNameToolTip() LLToolTip::Params params; params.background_visible(false); - params.click_callback(boost::bind(&LLFloaterReg::showInstance, "inspect_avatar", LLSD().with("avatar_id", mAvatarID), FALSE)); + if(!mIsGroupMsg) + { + params.click_callback(boost::bind(&LLFloaterReg::showInstance, "inspect_avatar", LLSD().with("avatar_id", mAvatarID), FALSE)); + } + else + { + params.click_callback(boost::bind(&LLFloaterReg::showInstance, "inspect_group", LLSD().with("group_id", mSessionID), FALSE)); + } params.delay_time(0.0f); // spawn instantly on hover params.image(LLUI::getUIImage("Info_Small")); params.message(""); diff --git a/indra/newview/lltoastimpanel.h b/indra/newview/lltoastimpanel.h index 3eb11fb3bc..767617dabc 100755 --- a/indra/newview/lltoastimpanel.h +++ b/indra/newview/lltoastimpanel.h @@ -73,6 +73,8 @@ private: LLTextBox* mAvatarName; LLTextBox* mTime; LLTextBox* mMessage; + + bool mIsGroupMsg; }; #endif // LLTOASTIMPANEL_H_ diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp index beca08203f..bb9ad8c606 100755 --- a/indra/newview/llviewermenu.cpp +++ b/indra/newview/llviewermenu.cpp @@ -52,6 +52,7 @@ #include "lldaycyclemanager.h" #include "lldebugview.h" #include "llenvmanager.h" +#include "llfacebookconnect.h" #include "llfilepicker.h" #include "llfirstuse.h" #include "llfloaterbuy.h" @@ -5970,6 +5971,21 @@ void handle_report_abuse() LLFloaterReporter::showFromMenu(COMPLAINT_REPORT); } +void handle_facebook_connect() +{ + if (LLFacebookConnect::instance().getConnected()) + { + LLFacebookConnect::instance().disconnectFromFacebook(); + } + else + { + LLFacebookConnect::instance().getConnectionToFacebook(); + } +} + +//bool is_facebook_connected(); + + void handle_buy_currency() { LLBuyCurrencyHTML::openCurrencyFloater(); @@ -8719,4 +8735,7 @@ void initialize_menus() view_listener_t::addMenu(new LLEditableSelected(), "EditableSelected"); view_listener_t::addMenu(new LLEditableSelectedMono(), "EditableSelectedMono"); view_listener_t::addMenu(new LLToggleUIHints(), "ToggleUIHints"); + + // Facebook Connect + commit.add("Facebook.Connect", boost::bind(&handle_facebook_connect)); } diff --git a/indra/newview/llviewermenu.h b/indra/newview/llviewermenu.h index 143420e227..b916d95b7a 100755 --- a/indra/newview/llviewermenu.h +++ b/indra/newview/llviewermenu.h @@ -135,6 +135,10 @@ bool enable_pay_object(); bool enable_buy_object(); bool handle_go_to(); +// Facebook Connect +void handle_facebook_connect(); +//bool is_facebook_connected(); + // Export to XML or Collada void handle_export_selected( void * ); diff --git a/indra/newview/llviewermessage.cpp b/indra/newview/llviewermessage.cpp index ace16396db..a8b5177c31 100755 --- a/indra/newview/llviewermessage.cpp +++ b/indra/newview/llviewermessage.cpp @@ -2649,7 +2649,8 @@ void process_improved_im(LLMessageSystem *msg, void **user_data) { send_do_not_disturb_message(msg, from_id); } - else + + if (!is_muted) { LL_INFOS("Messaging") << "Received IM_GROUP_INVITATION message." << LL_ENDL; // Read the binary bucket for more information. diff --git a/indra/newview/llviewerregion.cpp b/indra/newview/llviewerregion.cpp index b8b53aa6e4..fba835f642 100755 --- a/indra/newview/llviewerregion.cpp +++ b/indra/newview/llviewerregion.cpp @@ -1585,6 +1585,8 @@ void LLViewerRegionImpl::buildCapabilityNames(LLSD& capabilityNames) capabilityNames.append("EnvironmentSettings"); capabilityNames.append("EstateChangeInfo"); capabilityNames.append("EventQueueGet"); + capabilityNames.append("FacebookConnect"); + //capabilityNames.append("FacebookRedirect"); if (gSavedSettings.getBOOL("UseHTTPInventory")) { diff --git a/indra/newview/llviewerwindow.cpp b/indra/newview/llviewerwindow.cpp index fe4d5b3e4d..04e5447abf 100755 --- a/indra/newview/llviewerwindow.cpp +++ b/indra/newview/llviewerwindow.cpp @@ -1811,7 +1811,6 @@ void LLViewerWindow::initBase() gFloaterView = main_view->getChild<LLFloaterView>("Floater View"); gFloaterView->setFloaterSnapView(main_view->getChild<LLView>("floater_snap_region")->getHandle()); gSnapshotFloaterView = main_view->getChild<LLSnapshotFloaterView>("Snapshot Floater View"); - // Console llassert( !gConsole ); diff --git a/indra/newview/skins/default/colors.xml b/indra/newview/skins/default/colors.xml index 42568775d9..fc6458c67e 100755 --- a/indra/newview/skins/default/colors.xml +++ b/indra/newview/skins/default/colors.xml @@ -875,4 +875,19 @@ <color name="blue" value="0 0 1 1"/> + + <!--Resize bar colors --> + + <color + name="ResizebarBorderLight" + value="0.231 0.231 0.231 1"/> + + <color + name="ResizebarBorderDark" + value="0.133 0.133 0.133 1"/> + + <color + name="ResizebarBody" + value="0.208 0.208 0.208 1"/> + </colors> diff --git a/indra/newview/skins/default/textures/icons/Facebook.png b/indra/newview/skins/default/textures/icons/Facebook.png Binary files differnew file mode 100644 index 0000000000..8287d56f88 --- /dev/null +++ b/indra/newview/skins/default/textures/icons/Facebook.png diff --git a/indra/newview/skins/default/textures/textures.xml b/indra/newview/skins/default/textures/textures.xml index fcab966dee..18146943a5 100755 --- a/indra/newview/skins/default/textures/textures.xml +++ b/indra/newview/skins/default/textures/textures.xml @@ -199,6 +199,8 @@ with the same filename but different name <texture name="ExternalBrowser_Off" file_name="icons/ExternalBrowser_Off.png" preload="false" /> <texture name="Edit_Wrench" file_name="icons/Edit_Wrench.png" preload="false" /> + <texture name="Facebook_Icon" file_name="icons/Facebook.png" preload="false" /> + <texture name="Favorite_Star_Active" file_name="navbar/Favorite_Star_Active.png" preload="false" /> <texture name="Favorite_Star_Off" file_name="navbar/Favorite_Star_Off.png" preload="false" /> <texture name="Favorite_Star_Press" file_name="navbar/Favorite_Star_Press.png" preload="false" /> @@ -773,4 +775,7 @@ with the same filename but different name <texture name="Popup_Caution" file_name="icons/pop_up_caution.png"/> <texture name="Camera_Drag_Dot" file_name="world/CameraDragDot.png"/> <texture name="NavBar Separator" file_name="navbar/separator.png"/> + + <texture name="Horizontal Drag Handle" file_name="widgets/horizontal_drag_handle.png"/> + <texture name="Vertical Drag Handle" file_name="widgets/vertical_drag_handle.png"/> </textures> diff --git a/indra/newview/skins/default/textures/widgets/horizontal_drag_handle.png b/indra/newview/skins/default/textures/widgets/horizontal_drag_handle.png Binary files differnew file mode 100644 index 0000000000..642eac4065 --- /dev/null +++ b/indra/newview/skins/default/textures/widgets/horizontal_drag_handle.png diff --git a/indra/newview/skins/default/textures/widgets/vertical_drag_handle.png b/indra/newview/skins/default/textures/widgets/vertical_drag_handle.png Binary files differnew file mode 100644 index 0000000000..b06b70cf36 --- /dev/null +++ b/indra/newview/skins/default/textures/widgets/vertical_drag_handle.png diff --git a/indra/newview/skins/default/xui/en/floater_im_container.xml b/indra/newview/skins/default/xui/en/floater_im_container.xml index 65f623a47e..da016462db 100755 --- a/indra/newview/skins/default/xui/en/floater_im_container.xml +++ b/indra/newview/skins/default/xui/en/floater_im_container.xml @@ -24,24 +24,28 @@ value="Conv_toolbar_expand"/> <layout_stack animate="true" - bottom="-1" + bottom="-5" + drag_handle_gap="6" + drag_handle_first_indent="27" + drag_handle_second_indent="10" follows="all" layout="topleft" left="0" name="conversations_stack" orientation="horizontal" right="-1" + show_drag_handle="true" top="0"> <layout_panel auto_resize="false" user_resize="true" name="conversations_layout_panel" min_dim="38" - expanded_min_dim="156"> + expanded_min_dim="136"> <layout_stack animate="false" follows="left|top|right" - height="35" + height="27" layout="topleft" left="0" name="conversations_pane_buttons_stack" @@ -50,7 +54,6 @@ top="0"> <layout_panel auto_resize="true" - height="35" name="conversations_pane_buttons_expanded"> <menu_button follows="top|left" @@ -64,7 +67,7 @@ left="5" name="sort_btn" tool_tip="View/sort options" - top="5" + top="1" width="31" /> <button follows="top|left" @@ -74,7 +77,7 @@ image_selected="Toolbar_Middle_Selected" image_unselected="Toolbar_Middle_Off" layout="topleft" - top="5" + top="1" left_pad="2" name="add_btn" tool_tip="Start a new conversation" @@ -87,7 +90,7 @@ image_selected="Toolbar_Middle_Selected" image_unselected="Toolbar_Middle_Off" layout="topleft" - top="5" + top="1" left_pad="2" name="speak_btn" tool_tip="Speak with people using your microphone" @@ -95,9 +98,8 @@ </layout_panel> <layout_panel auto_resize="false" - height="35" name="conversations_pane_buttons_collapsed" - width="41"> + width="31"> <button follows="right|top" height="25" @@ -106,8 +108,8 @@ image_selected="Toolbar_Middle_Selected" image_unselected="Toolbar_Middle_Off" layout="topleft" - top="5" - left="1" + top="1" + left="0" name="expand_collapse_btn" tool_tip="Collapse/Expand this list" width="31" /> @@ -119,7 +121,7 @@ layout="topleft" name="conversations_list_panel" opaque="true" - top="35" + top_pad="0" left="5" right="-1"/> </layout_panel> @@ -127,7 +129,7 @@ auto_resize="true" user_resize="true" name="messages_layout_panel" - expanded_min_dim="222"> + expanded_min_dim="212"> <panel_container bottom="-1" follows="all" @@ -136,44 +138,44 @@ name="im_box_tab_container" right="-1" top="0"> - <panel - bottom="-1" - follows="all" - layout="topleft" - name="stub_panel" - opaque="true" - top_pad="0" - left="0" - right="-1"> - <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" + <panel + bottom="-1" + follows="all" layout="topleft" - top="5" - right="-10" - name="stub_collapse_btn" - tool_tip="Collapse this pane" - width="31" /> - <text - type="string" - clip_partial="false" - follows="left|top|right" - layout="topleft" - left="15" - right="-15" - name="stub_textbox" - top="25" - height="40" - valign="center" - parse_urls="true" - wrap="true"> - This conversation is in a separate window. [secondlife:/// Bring it back.] - </text> - </panel> + name="stub_panel" + opaque="true" + top_pad="0" + left="0" + right="-1"> + <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" + layout="topleft" + top="1" + right="-10" + name="stub_collapse_btn" + tool_tip="Collapse this pane" + width="31" /> + <text + type="string" + clip_partial="false" + follows="left|top|right" + layout="topleft" + left="15" + right="-15" + name="stub_textbox" + top="25" + height="40" + valign="center" + parse_urls="true" + wrap="true"> + This conversation is in a separate window. [secondlife:/// Bring it back.] + </text> + </panel> </panel_container> </layout_panel> </layout_stack> 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 2152a9f6e9..8da4213c65 100755 --- a/indra/newview/skins/default/xui/en/floater_im_session.xml +++ b/indra/newview/skins/default/xui/en/floater_im_session.xml @@ -70,26 +70,23 @@ top="0" left="0" right="-1" - bottom="-3"> + bottom="-1"> <layout_stack animate="false" + bottom="-1" default_tab_group="2" follows="all" - right="-5" - bottom="-1" - top="0" - left="5" - border_size="0" + left="3" layout="topleft" - orientation="vertical" name="main_stack" - tab_group="1"> + right="-3" + orientation="vertical" + tab_group="1" + top="0"> <layout_panel auto_resize="false" name="toolbar_panel" - height="35" - right="-1" - left="1"> + height="25"> <menu_button menu_filename="menu_im_session_showmodes.xml" follows="top|left" @@ -102,7 +99,7 @@ left="5" name="view_options_btn" tool_tip="View/sort options" - top="5" + top="1" width="31" /> <menu_button menu_filename="menu_im_conversation.xml" @@ -113,7 +110,7 @@ image_selected="Toolbar_Middle_Selected" image_unselected="Toolbar_Middle_Off" layout="topleft" - top="5" + top="1" left_pad="2" name="gear_btn" visible="false" @@ -128,7 +125,7 @@ image_selected="Toolbar_Middle_Selected" image_unselected="Toolbar_Middle_Off" layout="topleft" - top="5" + top="1" left_pad="2" name="add_btn" tool_tip="Add someone to this conversation" @@ -141,7 +138,7 @@ image_selected="Toolbar_Middle_Selected" image_unselected="Toolbar_Middle_Off" layout="topleft" - top="5" + top="1" left_pad="2" name="voice_call_btn" tool_tip="Open voice connection" @@ -166,8 +163,8 @@ image_selected="Toolbar_Middle_Selected" image_unselected="Toolbar_Middle_Off" layout="topleft" - top="5" - right="-67" + top="1" + right="-70" name="close_btn" tool_tip="End this conversation" width="31" /> @@ -179,7 +176,7 @@ image_selected="Toolbar_Middle_Selected" image_unselected="Toolbar_Middle_Off" layout="topleft" - top="5" + top="1" left_pad="2" name="expand_collapse_btn" tool_tip="Collapse/Expand this pane" @@ -194,18 +191,21 @@ layout="topleft" left_pad="2" name="tear_off_btn" - top="5" + top="1" width="31" /> </layout_panel> <layout_panel name="body_panel" - top="1" - bottom="-1"> + height="235"> <layout_stack default_tab_group="2" + drag_handle_gap="6" + drag_handle_first_indent="0" + drag_handle_second_indent="1" follows="all" orientation="horizontal" name="im_panels" + show_drag_handle="true" tab_group="1" top="0" right="-1" @@ -217,14 +217,12 @@ min_dim="0" width="150" user_resize="true" - auto_resize="false" - bottom="-1" /> + auto_resize="false" /> <layout_panel default_tab_group="3" tab_group="2" name="right_part_holder" - min_width="221" - bottom="-1"> + min_width="172"> <layout_stack animate="true" default_tab_group="2" @@ -233,7 +231,7 @@ name="translate_and_chat_stack" tab_group="1" top="0" - left="0" + left="1" right="-1" bottom="-1"> <layout_panel @@ -259,7 +257,7 @@ parse_highlights="true" parse_urls="true" right="-1" - left="5" + left="0" top="0" bottom="-1" /> </layout_panel> @@ -268,10 +266,7 @@ </layout_stack> </layout_panel> <layout_panel - top_delta="0" - top="0" - height="26" - bottom="-1" + height="35" auto_resize="false" name="chat_layout_panel"> <layout_stack @@ -281,15 +276,11 @@ orientation="horizontal" name="input_panels" top="0" - bottom="-2" + bottom="-1" left="0" right="-1"> <layout_panel - name="input_editor_layout_panel" - auto_resize="true" - user_resize="false" - top="0" - bottom="-1"> + name="input_editor_layout_panel"> <chat_editor layout="topleft" expand_lines_count="5" @@ -302,32 +293,27 @@ max_length="1023" spellcheck="true" tab_group="3" - top="1" - bottom="-2" - left="4" - right="-4" + bottom="-8" + left="5" + right="-5" wrap="true" /> </layout_panel> <layout_panel auto_resize="false" - user_resize="false" name="input_button_layout_panel" - width="30" - top="0" - bottom="-1"> + width="32"> <button - layout="topleft" left="1" - right="-1" - top="1" - height="22" + top="4" + height="25" follows="left|right|top" 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" /> + tool_tip="Shows/hides message panel" + width="28" /> </layout_panel> </layout_stack> </layout_panel> diff --git a/indra/newview/skins/default/xui/en/menu_gear_fbc.xml b/indra/newview/skins/default/xui/en/menu_gear_fbc.xml new file mode 100644 index 0000000000..2c341b6ecc --- /dev/null +++ b/indra/newview/skins/default/xui/en/menu_gear_fbc.xml @@ -0,0 +1,51 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes" ?> +<toggleable_menu + name="menu_group_plus" + left="0" bottom="0" visible="false" + mouse_opaque="false"> + <menu_item_check + label="Facebook App Settings" + layout="topleft" + name="Facebook App Settings"> + <menu_item_check.on_click + function="Advanced.WebContentTest" + parameter="http://www.facebook.com/settings?tab=applications" /> + </menu_item_check> + <menu_item_check + label="Facebook App Request" + layout="topleft" + name="Facebook App Request"> + <menu_item_check.on_click + function="People.requestFBC" + parameter="http://www.facebook.com/settings?tab=applications" /> + </menu_item_check> + <menu_item_check + label="Facebook App Send" + layout="topleft" + name="Facebook App Send"> + <menu_item_check.on_click + function="People.sendFBC" + parameter="http://www.facebook.com/settings?tab=applications" /> + </menu_item_check> + <menu_item_check + label="Facebook Add 300 test users to AvatarList" + layout="topleft" + name="Facebook App Add"> + <menu_item_check.on_click + function="People.testaddFBC"/> + </menu_item_check> + <menu_item_check + label="Facebook Add 300 test users to FolderView" + layout="topleft" + name="Facebook App Add"> + <menu_item_check.on_click + function="People.testaddFBCFolderView"/> + </menu_item_check> + <menu_item_check + label="Facebook post checkin message" + layout="topleft" + name="Facebook Checkin"> + <menu_item_check.on_click + function="People.testFBCCheckin"/> + </menu_item_check> +</toggleable_menu>
\ No newline at end of file diff --git a/indra/newview/skins/default/xui/en/menu_viewer.xml b/indra/newview/skins/default/xui/en/menu_viewer.xml index a11cd13fdb..b34816fb14 100755 --- a/indra/newview/skins/default/xui/en/menu_viewer.xml +++ b/indra/newview/skins/default/xui/en/menu_viewer.xml @@ -16,6 +16,13 @@ parameter="agent" /> </menu_item_call> <menu_item_call + label="Connect to Facebook..." + name="ConnectToFacebook"> + <menu_item_call.on_click + function="Facebook.Connect" /> + </menu_item_call> + <menu_item_separator/> + <menu_item_call label="Appearance..." name="ChangeOutfit"> <menu_item_call.on_click @@ -3018,6 +3025,13 @@ parameter="http://google.com"/> </menu_item_call> <menu_item_call + label="FB Connect Test" + name="FB Connect Test"> + <menu_item_call.on_click + function="Advanced.WebContentTest" + parameter="https://cryptic-ridge-1632.herokuapp.com/"/> + </menu_item_call> + <menu_item_call label="Dump SelectMgr" name="Dump SelectMgr"> <menu_item_call.on_click diff --git a/indra/newview/skins/default/xui/en/notifications.xml b/indra/newview/skins/default/xui/en/notifications.xml index 970a11c6c4..9d1973f267 100755 --- a/indra/newview/skins/default/xui/en/notifications.xml +++ b/indra/newview/skins/default/xui/en/notifications.xml @@ -3709,6 +3709,17 @@ Leave Group? </notification> <notification + icon="alertmodal.tga" + name="OwnerCannotLeaveGroup" + type="alertmodal"> + Unable to leave group. You cannot leave the group because you are the last owner of the group. Please assign another member to the owner role first. + <tag>group</tag> + <usetemplate + name="okbutton" + yestext="OK"/> + </notification> + + <notification icon="alert.tga" name="ConfirmKick" type="alert"> @@ -6573,7 +6584,7 @@ Your object named <nolink>[OBJECTFROMNAME]</nolink> has given you th icon="notify.tga" name="JoinGroup" persist="true" - type="notify"> + type="offer"> <tag>group</tag> [MESSAGE] <form name="form"> diff --git a/indra/newview/skins/default/xui/en/panel_people.xml b/indra/newview/skins/default/xui/en/panel_people.xml index 7ce2627be9..bc6b82e328 100755 --- a/indra/newview/skins/default/xui/en/panel_people.xml +++ b/indra/newview/skins/default/xui/en/panel_people.xml @@ -365,6 +365,23 @@ Looking for people to hang out with? Try the [secondlife:///app/worldmap World M top="0" width="307" /> </accordion_tab> + <accordion_tab + layout="topleft" + height="173" + name="tab_suggested_friends" + title="People you may want to friend"> + <avatar_list + allow_select="true" + follows="all" + height="173" + layout="topleft" + left="0" + multi_select="true" + name="suggested_friends" + show_permissions_granted="true" + top="0" + width="307" /> + </accordion_tab> </accordion> <text follows="all" @@ -633,5 +650,200 @@ Looking for people to hang out with? Try the [secondlife:///app/worldmap World M top="0" right="-1" /> </panel> + +<!-- ================================= FBC TEST tab (Temporary) ========================== --> + + <panel + background_opaque="true" + background_visible="true" + bg_alpha_color="DkGray" + bg_opaque_color="DkGray" + follows="all" + height="383" + label="FBC TEST" + layout="topleft" + left="0" + help_topic="people_fbctest_tab" + name="fbctest_panel" + top="0" + width="313"> + <accordion + background_visible="true" + bg_alpha_color="DkGray2" + bg_opaque_color="DkGray2" + follows="all" + height="356" + layout="topleft" + left="3" + name="friends_accordion" + top="0" + width="307"> + <accordion_tab + layout="topleft" + height="172" + min_height="150" + name="tab_facebook" + title="Facebook Friends"> + <social_list + allow_select="true" + follows="all" + height="172" + layout="topleft" + left="0" + multi_select="true" + name="facebook_friends" + show_permissions_granted="true" + top="0" + width="307" /> + </accordion_tab> + </accordion> + <panel + background_visible="true" + follows="left|right|bottom" + height="27" + label="bottom_panel" + layout="topleft" + left="3" + name="bottom_panel" + top_pad="0" + width="313"> + <menu_button + follows="bottom|left" + tool_tip="Options" + height="25" + image_hover_unselected="Toolbar_Left_Over" + image_overlay="OptionsMenu_Off" + image_selected="Toolbar_Left_Selected" + image_unselected="Toolbar_Left_Off" + layout="topleft" + name="fbc_options_btn" + top="1" + width="31" /> + <button + follows="bottom|left" + height="25" + image_hover_unselected="Toolbar_Middle_Over" + image_overlay="AddItem_Off" + image_selected="Toolbar_Middle_Selected" + image_unselected="Toolbar_Middle_Off" + layout="topleft" + left_pad="1" + name="fbc_login_btn" + tool_tip="Log in to FBC" + width="31"> + <commit_callback + function="People.loginFBC" /> + </button> + <icon + follows="bottom|left|right" + height="25" + image_name="Toolbar_Right_Off" + layout="topleft" + left_pad="1" + name="dummy_icon" + width="244" + /> + </panel> + </panel> + +<!-- ================================= FBC TEST TWO tab (Final, to be renamed) ========================== --> + + <panel + background_opaque="true" + background_visible="true" + bg_alpha_color="DkGray" + bg_opaque_color="DkGray" + follows="all" + height="383" + label="FBC TEST TWO" + layout="topleft" + left="0" + help_topic="people_fbctest_tab" + name="fbctesttwo_panel" + top="0"> + <panel + follows="left|top|right" + height="27" + label="bottom_panel" + layout="topleft" + left="0" + name="fbc_buttons_panel" + right="-1" + top="0"> + <filter_editor + follows="left|top|right" + height="23" + layout="topleft" + left="6" + label="Filter People" + max_length_chars="300" + name="fbc_filter_input" + text_color="Black" + text_pad_left="10" + top="4" + width="177" /> + <button + commit_callback.function="People.Gear" + follows="right" + height="25" + image_hover_unselected="Toolbar_Middle_Over" + image_overlay="OptionsMenu_Off" + image_selected="Toolbar_Middle_Selected" + image_unselected="Toolbar_Middle_Off" + layout="topleft" + left_pad="8" + name="gear_btn" + tool_tip="Actions on selected person" + top="3" + width="31" /> + <menu_button + follows="right" + height="25" + image_hover_unselected="Toolbar_Middle_Over" + image_overlay="Conv_toolbar_sort" + image_selected="Toolbar_Middle_Selected" + image_unselected="Toolbar_Middle_Off" + layout="topleft" + left_pad="2" + menu_filename="menu_people_friends_view.xml" + menu_position="bottomleft" + name="fbc_view_btn" + tool_tip="View/sort options" + top_delta="0" + width="31" /> + <button + follows="right" + height="25" + image_hover_unselected="Toolbar_Middle_Over" + image_overlay="AddItem_Off" + image_selected="Toolbar_Middle_Selected" + image_unselected="Toolbar_Middle_Off" + layout="topleft" + left_pad="2" + name="fbc_add_btn" + tool_tip="Offer friendship to a resident" + top_delta="0" + width="31"> + <commit_callback + function="People.AddFriendWizard" /> + </button> + <dnd_button + follows="right" + height="25" + image_hover_unselected="Toolbar_Middle_Over" + image_overlay="TrashItem_Off" + image_selected="Toolbar_Middle_Selected" + image_unselected="Toolbar_Middle_Off" + left_pad="2" + layout="topleft" + name="fbc_del_btn" + tool_tip="Remove selected person as a friend" + top_delta="0" + width="31"> + <commit_callback + function="People.DelFriend" /> + </dnd_button> + </panel> + </panel> </tab_container> </panel> diff --git a/indra/newview/skins/default/xui/en/widgets/person_tab_view.xml b/indra/newview/skins/default/xui/en/widgets/person_tab_view.xml new file mode 100644 index 0000000000..af5aec2c34 --- /dev/null +++ b/indra/newview/skins/default/xui/en/widgets/person_tab_view.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes" ?> +<person_tab_view + folder_arrow_image="Folder_Arrow" + folder_indentation="5" + item_height="24" + item_top_pad="3" + mouse_opaque="true" + follows="left|top|right" + text_pad="6" + text_pad_left="4" + text_pad_right="4" + arrow_size="10" + max_folder_item_overlap="2"/> diff --git a/indra/newview/skins/default/xui/en/widgets/person_view.xml b/indra/newview/skins/default/xui/en/widgets/person_view.xml new file mode 100644 index 0000000000..46c1b7ff75 --- /dev/null +++ b/indra/newview/skins/default/xui/en/widgets/person_view.xml @@ -0,0 +1,127 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes" ?> +<person_view + folder_arrow_image="Folder_Arrow" + folder_indentation="5" + item_height="24" + item_top_pad="3" + mouse_opaque="true" + follows="left|top|right" + icon_pad="4" + icon_width="20" + text_pad="6" + text_pad_left="4" + text_pad_right="4" + arrow_size="10" + max_folder_item_overlap="2"> + <facebook_icon + follows="left" + height="14" + image_name="Facebook_Icon" + left="5" + bottom="6" + name="facebook_icon" + tool_tip="Facebook User" + visible="false" + width="14" /> + <avatar_icon + follows="left" + layout="topleft" + height="20" + default_icon_name="Generic_Person" + left="5" + top="2" + visible="false" + width="20" /> + <last_interaction_time_textbox + layout="topleft" + follows="right" + font="SansSerifSmall" + height="15" + left_pad="5" + right="-164" + name="last_interaction_time_textbox" + text_color="LtGray_50" + value="0s" + visible="false" + width="35" /> + <permission_edit_theirs_icon + layout="topleft" + height="16" + follows="right" + image_name="Permission_Edit_Objects_Theirs" + left_pad="3" + right="-129" + name="permission_edit_theirs_icon" + tool_tip="You can edit this friend's objects" + top="4" + visible="false" + width="16" /> + <permission_edit_mine_icon + layout="topleft" + height="16" + follows="right" + image_name="Permission_Edit_Objects_Mine" + left_pad="3" + right="-110" + name="permission_edit_mine_icon" + tool_tip="This friend can edit, delete or take your objects" + top="4" + visible="false" + width="16" /> + <permission_map_icon + height="16" + follows="right" + image_name="Permission_Visible_Map" + left_pad="3" + tool_tip="This friend can locate you on the map" + right="-91" + name="permission_map_icon" + visible="false" + width="16" /> + <permission_online_icon + height="16" + follows="right" + image_name="Permission_Visible_Online" + left_pad="3" + right="-72" + name="permission_online_icon" + tool_tip="This friend can see when you're online" + visible="false" + width="16" /> + <info_btn + follows="right" + height="16" + image_pressed="Info_Press" + image_unselected="Info_Over" + left_pad="3" + right="-53" + name="info_btn" + tool_tip="More info" + tab_stop="false" + visible="false" + width="16" /> + <profile_btn + layout="topleft" + follows="right" + height="20" + image_overlay="Web_Profile_Off" + left_pad="5" + right="-28" + name="profile_btn" + tab_stop="false" + tool_tip="View profile" + top="2" + visible="false" + width="20" /> + <output_monitor + auto_update="true" + follows="right" + draw_border="false" + height="16" + right="-3" + mouse_opaque="true" + name="speaking_indicator" + visible="false" + width="20" /> + </person_view> + diff --git a/indra/newview/tests/lltranslate_test.cpp b/indra/newview/tests/lltranslate_test.cpp index fd9527d631..8ce56326d8 100755 --- a/indra/newview/tests/lltranslate_test.cpp +++ b/indra/newview/tests/lltranslate_test.cpp @@ -308,8 +308,8 @@ void LLCurl::Responder::errorWithContent(U32, std::string const&, LLSD const&) { void LLCurl::Responder::result(LLSD const&) {} LLCurl::Responder::~Responder() {} -void LLHTTPClient::get(const std::string&, const LLSD&, ResponderPtr, const LLSD&, const F32) {} -void LLHTTPClient::get(const std::string&, LLPointer<LLCurl::Responder>, const LLSD&, const F32) {} +void LLHTTPClient::get(const std::string&, const LLSD&, ResponderPtr, const LLSD&, const F32, bool) {} +void LLHTTPClient::get(const std::string&, LLPointer<LLCurl::Responder>, const LLSD&, const F32, bool) {} LLBufferStream::LLBufferStream(const LLChannelDescriptors& channels, LLBufferArray* buffer) : std::iostream(&mStreamBuf), mStreamBuf(channels, buffer) {} diff --git a/indra/test_apps/llfbconnecttest/CMakeLists.txt b/indra/test_apps/llfbconnecttest/CMakeLists.txt new file mode 100644 index 0000000000..f56329a010 --- /dev/null +++ b/indra/test_apps/llfbconnecttest/CMakeLists.txt @@ -0,0 +1,304 @@ +# -*- cmake -*- +project(llfbconnecttest) + +include(00-Common) +include(FindOpenGL) +include(LLCommon) +include(LLPlugin) +include(Linking) +include(LLSharedLibs) +include(PluginAPI) +include(LLImage) +include(LLMath) +include(LLMessage) +include(LLRender) +include(LLWindow) +include(Glut) +include(Glui) + +include_directories( + ${LLPLUGIN_INCLUDE_DIRS} + ${LLCOMMON_INCLUDE_DIRS} + ${LLIMAGE_INCLUDE_DIRS} + ${LLMATH_INCLUDE_DIRS} + ${LLMESSAGE_INCLUDE_DIRS} + ${LLRENDER_INCLUDE_DIRS} + ${LLWINDOW_INCLUDE_DIRS} +) + +if (DARWIN) + include(CMakeFindFrameworks) + find_library(COREFOUNDATION_LIBRARY CoreFoundation) +endif (DARWIN) + +### llfbconnecttest + +set(llfbconnecttest_SOURCE_FILES + llfbconnecttest.cpp + llfbconnecttest.h + bookmarks.txt + ) + +add_executable(llfbconnecttest + WIN32 + MACOSX_BUNDLE + ${llfbconnecttest_SOURCE_FILES} +) + +set_target_properties(llfbconnecttest + PROPERTIES + WIN32_EXECUTABLE + FALSE +) + +target_link_libraries(llfbconnecttest + ${GLUT_LIBRARY} + ${GLUI_LIBRARY} + ${OPENGL_LIBRARIES} + ${LLPLUGIN_LIBRARIES} + ${LLMESSAGE_LIBRARIES} + ${LLCOMMON_LIBRARIES} + ${PLUGIN_API_WINDOWS_LIBRARIES} +) + +if (DARWIN) + # The testbed needs to use a couple of CoreFoundation calls now, to deal with being a bundled app. + target_link_libraries(llfbconnecttest + ${COREFOUNDATION_LIBRARY} + ) +endif (DARWIN) + +add_dependencies(llfbconnecttest + stage_third_party_libs + SLPlugin + media_plugin_webkit + ${LLPLUGIN_LIBRARIES} + ${LLMESSAGE_LIBRARIES} + ${LLCOMMON_LIBRARIES} +) + +# turn off weird GLUI pragma +add_definitions(-DGLUI_NO_LIB_PRAGMA) + +if (DARWIN OR LINUX) + # glui.h contains code that triggers the "overloaded-virtual" warning in gcc. + set_source_files_properties(llfbconnecttest.cpp PROPERTIES COMPILE_FLAGS "-Wno-overloaded-virtual") +endif (DARWIN OR LINUX) + +# Gather build products of the various dependencies into the build directory for the testbed. + +if (DARWIN) + # path inside the app bundle where we'll need to copy plugins and other related files + set(PLUGINS_DESTINATION_DIR + ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/llfbconnecttest.app/Contents/Resources + ) + + # create the Contents/Resources directory + add_custom_command( + TARGET llfbconnecttest POST_BUILD + COMMAND ${CMAKE_COMMAND} + ARGS + -E + make_directory + ${PLUGINS_DESTINATION_DIR} + COMMENT "Creating Resources directory in app bundle." + ) +else (DARWIN) + set(PLUGINS_DESTINATION_DIR + ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/ + ) +endif (DARWIN) + +get_target_property(BUILT_SLPLUGIN SLPlugin LOCATION) +add_custom_command(TARGET llfbconnecttest POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy ${BUILT_SLPLUGIN} ${PLUGINS_DESTINATION_DIR} + DEPENDS ${BUILT_SLPLUGIN} +) + +get_target_property(BUILT_LLCOMMON llcommon LOCATION) +add_custom_command(TARGET llfbconnecttest POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy ${BUILT_LLCOMMON} ${PLUGINS_DESTINATION_DIR} + DEPENDS ${BUILT_LLCOMMON} +) + + +if (DARWIN OR WINDOWS) + get_target_property(BUILT_WEBKIT_PLUGIN media_plugin_webkit LOCATION) + add_custom_command(TARGET llfbconnecttest POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy ${BUILT_WEBKIT_PLUGIN} ${PLUGINS_DESTINATION_DIR} + DEPENDS ${BUILT_WEBKIT_PLUGIN} + ) + + # copy over bookmarks file if llfbconnecttest gets built + get_target_property(BUILT_LLFBCONNECTTEST llfbconnecttest LOCATION) + add_custom_command(TARGET llfbconnecttest POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/bookmarks.txt ${CMAKE_CURRENT_BINARY_DIR}/ + DEPENDS ${BUILT_LLFBCONNECTTEST} + ) + # also copy it to the same place as SLPlugin, which is what the mac wants... + add_custom_command(TARGET llfbconnecttest POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/bookmarks.txt ${PLUGINS_DESTINATION_DIR} + DEPENDS ${BUILT_LLFBCONNECTTEST} + ) +endif (DARWIN OR WINDOWS) + +if (DARWIN) + add_custom_command(TARGET llfbconnecttest POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy ${ARCH_PREBUILT_DIRS_RELEASE}/libllqtwebkit.dylib ${PLUGINS_DESTINATION_DIR} + DEPENDS ${ARCH_PREBUILT_DIRS_RELEASE}/libllqtwebkit.dylib + ) +endif (DARWIN) + +if(WINDOWS) + #******************** + # Plugin test library deploy + # + # Debug config runtime files required for the FB connect test + set(fbconnecttest_debug_src_dir "${ARCH_PREBUILT_DIRS_DEBUG}") + set(fbconnecttest_debug_files + libeay32.dll + libglib-2.0-0.dll + libgmodule-2.0-0.dll + libgobject-2.0-0.dll + libgthread-2.0-0.dll + qtcored4.dll + qtguid4.dll + qtnetworkd4.dll + qtopengld4.dll + qtwebkitd4.dll + ssleay32.dll + ) + copy_if_different( + ${fbconnecttest_debug_src_dir} + "${CMAKE_CURRENT_BINARY_DIR}/Debug" + out_targets + ${fbconnecttest_debug_files} + ) + set(fbconnect_test_targets ${fbconnect_test_targets} ${out_targets}) + + # Debug config runtime files required for the FB connect test (Qt image format plugins) + set(fbconecttest_debug_src_dir "${ARCH_PREBUILT_DIRS_DEBUG}/imageformats") + set(fbconecttest_debug_files + qgifd4.dll + qicod4.dll + qjpegd4.dll + qmngd4.dll + qsvgd4.dll + qtiffd4.dll + ) + copy_if_different( + ${fbconecttest_debug_src_dir} + "${CMAKE_CURRENT_BINARY_DIR}/Debug/imageformats" + out_targets + ${fbconecttest_debug_files} + ) + set(fbconnect_test_targets ${fbconnect_test_targets} ${out_targets}) + + # Debug config runtime files required for the FB connect test (Qt codec plugins) + set(fbconnecttest_debug_src_dir "${ARCH_PREBUILT_DIRS_DEBUG}/codecs") + set(fbconnecttest_debug_files + qcncodecsd4.dll + qjpcodecsd4.dll + qkrcodecsd4.dll + qtwcodecsd4.dll + ) + copy_if_different( + ${fbconnecttest_debug_src_dir} + "${CMAKE_CURRENT_BINARY_DIR}/Debug/codecs" + out_targets + ${fbconnecttest_debug_files} + ) + set(fbconnect_test_targets ${fbconnect_test_targets} ${out_targets}) + + # Release & ReleaseDebInfo config runtime files required for the FB connect test + set(fbconnecttest_release_src_dir "${ARCH_PREBUILT_DIRS_RELEASE}") + set(fbconnecttest_release_files + libeay32.dll + libglib-2.0-0.dll + libgmodule-2.0-0.dll + libgobject-2.0-0.dll + libgthread-2.0-0.dll + qtcore4.dll + qtgui4.dll + qtnetwork4.dll + qtopengl4.dll + qtwebkit4.dll + qtxmlpatterns4.dll + ssleay32.dll + ) + copy_if_different( + ${fbconnecttest_release_src_dir} + "${CMAKE_CURRENT_BINARY_DIR}/Release" + out_targets + ${fbconnecttest_release_files} + ) + set(fbconnect_test_targets ${fbconnect_test_targets} ${out_targets}) + + copy_if_different( + ${fbconnecttest_release_src_dir} + "${CMAKE_CURRENT_BINARY_DIR}/RelWithDebInfo" + out_targets + ${fbconnecttest_release_files} + ) + set(fbconnect_test_targets ${fbconnect_test_targets} ${out_targets}) + + # Release & ReleaseDebInfo config runtime files required for the FB connect test (Qt image format plugins) + set(fbconnecttest_release_src_dir "${ARCH_PREBUILT_DIRS_RELEASE}/imageformats") + set(fbconnecttest_release_files + qgif4.dll + qico4.dll + qjpeg4.dll + qmng4.dll + qsvg4.dll + qtiff4.dll + ) + copy_if_different( + ${fbconnecttest_release_src_dir} + "${CMAKE_CURRENT_BINARY_DIR}/Release/imageformats" + out_targets + ${fbconnecttest_release_files} + ) + set(fbconnect_test_targets ${fbconnect_test_targets} ${out_targets}) + + copy_if_different( + ${fbconnecttest_release_src_dir} + "${CMAKE_CURRENT_BINARY_DIR}/RelWithDebInfo/imageformats" + out_targets + ${fbconnecttest_release_files} + ) + set(fbconnect_test_targets ${fbconnect_test_targets} ${out_targets}) + + # Release & ReleaseDebInfo config runtime files required for the FB connect test (Qt codec plugins) + set(fbconnecttest_release_src_dir "${ARCH_PREBUILT_DIRS_RELEASE}/codecs") + set(fbconnecttest_release_files + qcncodecs4.dll + qjpcodecs4.dll + qkrcodecs4.dll + qtwcodecs4.dll + ) + copy_if_different( + ${fbconnecttest_release_src_dir} + "${CMAKE_CURRENT_BINARY_DIR}/Release/codecs" + out_targets + ${fbconnecttest_release_files} + ) + set(fbconnect_test_targets ${fbconnect_test_targets} ${out_targets}) + + copy_if_different( + ${fbconnecttest_release_src_dir} + "${CMAKE_CURRENT_BINARY_DIR}/RelWithDebInfo/codecs" + out_targets + ${fbconnecttest_release_files} + ) + set(fbconnect_test_targets ${fbconnect_test_targets} ${out_targets}) + + add_custom_target(copy_fbconnecttest_libs ALL + DEPENDS + ${fbconnect_test_targets} + ) + + add_dependencies(llfbconnecttest copy_fbconnecttest_libs) + +endif(WINDOWS) + +ll_deploy_sharedlibs_command(llfbconnecttest) diff --git a/indra/test_apps/llfbconnecttest/bookmarks.txt b/indra/test_apps/llfbconnecttest/bookmarks.txt new file mode 100644 index 0000000000..3995627ea9 --- /dev/null +++ b/indra/test_apps/llfbconnecttest/bookmarks.txt @@ -0,0 +1,4 @@ +# format is description, url (don't put ',' chars in description :) +# if no ',' found, whole line is used for both description and url +Google Home Page,http://www.google.com +Facebook Home Page,http://www.facebook.com diff --git a/indra/test_apps/llfbconnecttest/llfbconnecttest.cpp b/indra/test_apps/llfbconnecttest/llfbconnecttest.cpp new file mode 100644 index 0000000000..483a15c468 --- /dev/null +++ b/indra/test_apps/llfbconnecttest/llfbconnecttest.cpp @@ -0,0 +1,2394 @@ +/** + * @file LLFBConnectTest.cpp + * @brief Facebook Connect Test App + * + * $LicenseInfo:firstyear=2008&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2011, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "linden_common.h" +#include "indra_constants.h" + +#include "llapr.h" +#include "llerrorcontrol.h" + +#include <math.h> +#include <iomanip> +#include <sstream> +#include <ctime> + +#include "llfbconnecttest.h" + +#if __APPLE__ + #include <GLUT/glut.h> + #include <CoreFoundation/CoreFoundation.h> +#else + #define FREEGLUT_STATIC + #include "GL/freeglut.h" + #define GLUI_FREEGLUT +#endif + +#if LL_WINDOWS +#pragma warning(disable: 4263) +#pragma warning(disable: 4264) +#endif +#include "glui.h" + + +LLFBConnectTest* gApplication = 0; +static void gluiCallbackWrapper( int control_id ); + +//////////////////////////////////////////////////////////////////////////////// +// +static bool isTexture( GLuint texture ) +{ + bool result = false; + + // glIsTexture will sometimes return false for real textures... do this instead. + if(texture != 0) + result = true; + + return result; +} + +//////////////////////////////////////////////////////////////////////////////// +// +mediaPanel::mediaPanel() +{ + mMediaTextureHandle = 0; + mPickTextureHandle = 0; + mMediaSource = NULL; + mPickTexturePixels = NULL; +} + +//////////////////////////////////////////////////////////////////////////////// +// +mediaPanel::~mediaPanel() +{ + // delete OpenGL texture handles + if ( isTexture( mPickTextureHandle ) ) + { + std::cerr << "remMediaPanel: deleting pick texture " << mPickTextureHandle << std::endl; + glDeleteTextures( 1, &mPickTextureHandle ); + mPickTextureHandle = 0; + } + + if ( isTexture( mMediaTextureHandle ) ) + { + std::cerr << "remMediaPanel: deleting media texture " << mMediaTextureHandle << std::endl; + glDeleteTextures( 1, &mMediaTextureHandle ); + mMediaTextureHandle = 0; + } + + if(mPickTexturePixels) + { + delete mPickTexturePixels; + } + + if(mMediaSource) + { + delete mMediaSource; + } + +} + +//////////////////////////////////////////////////////////////////////////////// +// +LLFBConnectTest::LLFBConnectTest( int app_window, int window_width, int window_height ) : + mVersionMajor( 2 ), + mVersionMinor( 0 ), + mVersionPatch( 0 ), + mMaxPanels( 25 ), + mViewportAspect( 0 ), + mAppWindow( app_window ), + mCurMouseX( 0 ), + mCurMouseY( 0 ), + mFuzzyMedia( true ), + mSelectedPanel( 0 ), + mDistanceCameraToSelectedGeometry( 0.0f ), + //mMediaBrowserControlEnableCookies( 0 ), + mMediaBrowserControlBackButton( 0 ), + mMediaBrowserControlForwardButton( 0 ), + //mMediaTimeControlVolume( 100 ), + //mMediaTimeControlSeekSeconds( 0 ), + //mGluiMediaTimeControlWindowFlag( true ), + mGluiMediaBrowserControlWindowFlag( true ), + mMediaBrowserControlBackButtonFlag( true ), + mMediaBrowserControlForwardButtonFlag( true ), + mHomeWebUrl( "https://cryptic-ridge-1632.herokuapp.com/" ) +{ + // debugging spam + std::cout << std::endl << " GLUT version: " << "3.7.6" << std::endl; // no way to get real version from GLUT + std::cout << std::endl << " GLUI version: " << GLUI_Master.get_version() << std::endl; + std::cout << std::endl << "Media Plugin Test version: " << mVersionMajor << "." << mVersionMinor << "." << mVersionPatch << std::endl; + + // bookmark title + mBookmarks.push_back( std::pair< std::string, std::string >( "--- Bookmarks ---", "" ) ); + + // insert hardcoded URLs here as required for testing + //mBookmarks.push_back( std::pair< std::string, std::string >( "description", "url" ) ); + + // read bookmarks from file. + // note: uses command in ./CmakeLists.txt which copies bookmmarks file from source directory + // to app directory (WITHOUT build configuration dir) (this is cwd in Windows within MSVC) + // For example, test_apps\llplugintest and not test_apps\llplugintest\Release + // This may need to be changed for Mac/Linux builds. + // See https://jira.lindenlab.com/browse/DEV-31350 for large list of media URLs from AGNI + const std::string bookmarks_filename( "bookmarks.txt" ); + std::ifstream file_handle( bookmarks_filename.c_str() ); + if ( file_handle.is_open() ) + { + std::cout << "Reading bookmarks for test" << std::endl; + while( ! file_handle.eof() ) + { + std::string line; + std::getline( file_handle, line ); + if ( file_handle.eof() ) + break; + + if ( line.substr( 0, 1 ) != "#" ) + { + size_t comma_pos = line.find_first_of( ',' ); + if ( comma_pos != std::string::npos ) + { + std::string description = line.substr( 0, comma_pos ); + std::string url = line.substr( comma_pos + 1 ); + mBookmarks.push_back( std::pair< std::string, std::string >( description, url ) ); + } + else + { + mBookmarks.push_back( std::pair< std::string, std::string >( line, line ) ); + }; + }; + }; + std::cout << "Read " << mBookmarks.size() << " bookmarks" << std::endl; + } + else + { + std::cout << "Unable to read bookmarks from file: " << bookmarks_filename << std::endl; + }; + + // initialize linden lab APR module + ll_init_apr(); + + // Set up llerror logging + { + LLError::initForApplication("."); + LLError::setDefaultLevel(LLError::LEVEL_INFO); + //LLError::setTagLevel("Plugin", LLError::LEVEL_DEBUG); + } + + // lots of randomness in this app + srand( ( unsigned int )time( 0 ) ); + + // build GUI + makeChrome(); + + // OpenGL initialilzation + glClearColor( 0.0f, 0.0f, 0.0f, 1.0f ); + glClearDepth( 1.0f ); + glEnable( GL_DEPTH_TEST ); + glEnable( GL_COLOR_MATERIAL ); + glColorMaterial( GL_FRONT, GL_AMBIENT_AND_DIFFUSE ); + glDepthFunc( GL_LEQUAL ); + glEnable( GL_TEXTURE_2D ); + glDisable( GL_BLEND ); + glColor3f( 1.0f, 1.0f, 1.0f ); + glPixelStorei( GL_UNPACK_ALIGNMENT, 1 ); + glPixelStorei( GL_UNPACK_ROW_LENGTH, 0 ); + + // start with a sane view + resetView(); + + // initial media panel + const int num_initial_panels = 1; + for( int i = 0; i < num_initial_panels; ++i ) + { + //addMediaPanel( mBookmarks[ rand() % ( mBookmarks.size() - 1 ) + 1 ].second ); + addMediaPanel( mHomeWebUrl ); + }; +} + +//////////////////////////////////////////////////////////////////////////////// +// +LLFBConnectTest::~LLFBConnectTest() +{ + // delete all media panels + for( int i = 0; i < (int)mMediaPanels.size(); ++i ) + { + remMediaPanel( mMediaPanels[ i ] ); + }; + + // Stop the plugin read thread if it's running. + LLPluginProcessParent::setUseReadThread(false); +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLFBConnectTest::reshape( int width, int height ) +{ + // update viewport (the active window inside the chrome) + int viewport_x, viewport_y; + int viewport_height, viewport_width; + GLUI_Master.get_viewport_area( &viewport_x, &viewport_y, &viewport_width, &viewport_height ); + mViewportAspect = (float)( viewport_width ) / (float)( viewport_height ); + glViewport( viewport_x, viewport_y, viewport_width, viewport_height ); + + // save these as we'll need them later + mWindowWidth = width; + mWindowHeight = height; + + // adjust size of URL bar so it doesn't get clipped + mUrlEdit->set_w( mWindowWidth - 360 ); + + // GLUI requires this + if ( glutGetWindow() != mAppWindow ) + glutSetWindow( mAppWindow ); + + // trigger re-display + glutPostRedisplay(); +}; + +//////////////////////////////////////////////////////////////////////////////// +// +void LLFBConnectTest::bindTexture(GLuint texture, GLint row_length, GLint alignment) +{ + glEnable( GL_TEXTURE_2D ); + + glBindTexture( GL_TEXTURE_2D, texture ); + glPixelStorei( GL_UNPACK_ROW_LENGTH, row_length ); + glPixelStorei( GL_UNPACK_ALIGNMENT, alignment ); +} + +//////////////////////////////////////////////////////////////////////////////// +// +bool LLFBConnectTest::checkGLError(const char *name) +{ + bool result = false; + GLenum error = glGetError(); + + if(error != GL_NO_ERROR) + { + // For some reason, glGenTextures is returning GL_INVALID_VALUE... + std::cout << name << " ERROR 0x" << std::hex << error << std::dec << std::endl; + result = true; + } + + return result; +} + +//////////////////////////////////////////////////////////////////////////////// +// +GLfloat LLFBConnectTest::distanceToCamera( GLfloat point_x, GLfloat point_y, GLfloat point_z ) +{ + GLdouble camera_pos_x = 0.0f; + GLdouble camera_pos_y = 0.0f; + GLdouble camera_pos_z = 0.0f; + + GLdouble modelMatrix[16]; + GLdouble projMatrix[16]; + GLint viewport[4]; + + glGetDoublev(GL_MODELVIEW_MATRIX, modelMatrix); + glGetDoublev(GL_PROJECTION_MATRIX, projMatrix); + glGetIntegerv(GL_VIEWPORT, viewport); + + gluUnProject( + (viewport[2]-viewport[0])/2 , (viewport[3]-viewport[1])/2, + 0.0, + modelMatrix, projMatrix, viewport, + &camera_pos_x, &camera_pos_y, &camera_pos_z ); + + GLfloat distance = + sqrt( ( camera_pos_x - point_x ) * ( camera_pos_x - point_x ) + + ( camera_pos_y - point_y ) * ( camera_pos_y - point_y ) + + ( camera_pos_z - point_z ) * ( camera_pos_z - point_z ) ); + + return distance; +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLFBConnectTest::drawGeometry( int panel, bool selected ) +{ + // texture coordinates for each panel + GLfloat non_opengl_texture_coords[ 8 ] = { 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f }; + GLfloat opengl_texture_coords[ 8 ] = { 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f }; + + GLfloat *texture_coords = mMediaPanels[ panel ]->mAppTextureCoordsOpenGL?opengl_texture_coords:non_opengl_texture_coords; + + // base coordinates for each panel + GLfloat base_vertex_pos[ 8 ] = { 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f }; + + // calculate posiitons + const int num_panels = (int)mMediaPanels.size(); + const int num_rows = (int)sqrt( (float)num_panels ); + const int num_cols = num_panels / num_rows; + const int panel_x = ( panel / num_rows ); + const int panel_y = ( panel % num_rows ); + + // default spacing is small - make it larger if checkbox set - for testing positional audio + float spacing = 0.1f; + //if ( mLargePanelSpacing ) + // spacing = 2.0f; + + const GLfloat offset_x = num_cols * ( 1.0 + spacing ) / 2; + const GLfloat offset_y = num_rows * ( 1.0 + spacing ) / 2; + + // Adjust for media aspect ratios + { + float aspect = 1.0f; + + if(mMediaPanels[ panel ]->mMediaHeight != 0) + { + aspect = (float)mMediaPanels[ panel ]->mMediaWidth / (float)mMediaPanels[ panel ]->mMediaHeight; + } + + if(aspect > 1.0f) + { + // media is wider than it is high -- adjust the top and bottom in + for( int corner = 0; corner < 4; ++corner ) + { + float temp = base_vertex_pos[corner * 2 + 1]; + + if(temp < 0.5f) + temp += 0.5 - (0.5f / aspect); + else + temp -= 0.5 - (0.5f / aspect); + + base_vertex_pos[corner * 2 + 1] = temp; + } + } + else if(aspect < 1.0f) + { + // media is higher than it is wide -- adjust the left and right sides in + for( int corner = 0; corner < 4; ++corner ) + { + float temp = base_vertex_pos[corner * 2]; + + if(temp < 0.5f) + temp += 0.5f - (0.5f * aspect); + else + temp -= 0.5f - (0.5f * aspect); + + base_vertex_pos[corner * 2] = temp; + } + } + } + + glBegin( GL_QUADS ); + for( int corner = 0; corner < 4; ++corner ) + { + glTexCoord2f( texture_coords[ corner * 2 ], texture_coords[ corner * 2 + 1 ] ); + GLfloat x = base_vertex_pos[ corner * 2 ] + panel_x * ( 1.0 + spacing ) - offset_x + spacing / 2.0f; + GLfloat y = base_vertex_pos[ corner * 2 + 1 ] + panel_y * ( 1.0 + spacing ) - offset_y + spacing / 2.0f; + + glVertex3f( x, y, 0.0f ); + }; + glEnd(); + + // calculate distance to this panel if it's selected + if ( selected ) + { + GLfloat point_x = base_vertex_pos[ 0 ] + panel_x * ( 1.0 + spacing ) - offset_x + spacing / 2.0f; + GLfloat point_y = base_vertex_pos[ 0 + 1 ] + panel_y * ( 1.0 + spacing ) - offset_y + spacing / 2.0f; + GLfloat point_z = 0.0f; + mDistanceCameraToSelectedGeometry = distanceToCamera( point_x, point_y, point_z ); + }; +} + +////////////////////////////////////////////////////////////////////////////// +// +void LLFBConnectTest::startPanelHighlight( float red, float green, float blue, float line_width ) +{ + glPushAttrib( GL_ALL_ATTRIB_BITS ); + glEnable( GL_POLYGON_OFFSET_FILL ); + glPolygonOffset( -2.5f, -2.5f ); + glPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); + glLineWidth( line_width ); + glColor3f( red, green, blue ); + glDisable( GL_TEXTURE_2D ); +} + +////////////////////////////////////////////////////////////////////////////// +// +void LLFBConnectTest::endPanelHighlight() +{ + glPopAttrib(); +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLFBConnectTest::draw( int draw_type ) +{ + for( int panel = 0; panel < (int)mMediaPanels.size(); ++panel ) + { + // drawing pick texture + if ( draw_type == DrawTypePickTexture ) + { + // only bother with pick if we have something to render + // Actually, we need to pick even if we're not ready to render. + // Otherwise you can't select and remove a panel which has gone bad. + //if ( mMediaPanels[ panel ]->mReadyToRender ) + { + glMatrixMode( GL_TEXTURE ); + glPushMatrix(); + + // pick texture is a power of 2 so no need to scale + glLoadIdentity(); + + // bind to media texture + glLoadIdentity(); + bindTexture( mMediaPanels[ panel ]->mPickTextureHandle ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST ); + + // draw geometry using pick texture + drawGeometry( panel, false ); + + glMatrixMode( GL_TEXTURE ); + glPopMatrix(); + }; + } + else + if ( draw_type == DrawTypeMediaTexture ) + { + bool texture_valid = false; + bool plugin_exited = false; + + if(mMediaPanels[ panel ]->mMediaSource) + { + texture_valid = mMediaPanels[ panel ]->mMediaSource->textureValid(); + plugin_exited = mMediaPanels[ panel ]->mMediaSource->isPluginExited(); + } + + // save texture matrix (changes for each panel) + glMatrixMode( GL_TEXTURE ); + glPushMatrix(); + + // only process texture if the media is ready to draw + // (we still want to draw the geometry) + if ( mMediaPanels[ panel ]->mReadyToRender && texture_valid ) + { + // bind to media texture + bindTexture( mMediaPanels[ panel ]->mMediaTextureHandle ); + + if ( mFuzzyMedia ) + { + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); + } + else + { + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST ); + } + + // scale to fit panel + glScalef( mMediaPanels[ panel ]->mTextureScaleX, + mMediaPanels[ panel ]->mTextureScaleY, + 1.0f ); + }; + + float intensity = plugin_exited?0.25f:1.0f; + + // highlight the selected panel + if ( mSelectedPanel && ( mMediaPanels[ panel ]->mId == mSelectedPanel->mId ) ) + { + startPanelHighlight( intensity, intensity, 0.0f, 5.0f ); + drawGeometry( panel, true ); + endPanelHighlight(); + } + else + // this panel not able to render yet since it + // doesn't have enough information + if ( !mMediaPanels[ panel ]->mReadyToRender ) + { + startPanelHighlight( intensity, 0.0f, 0.0f, 2.0f ); + drawGeometry( panel, false ); + endPanelHighlight(); + } + else + // just display a border around the media + { + startPanelHighlight( 0.0f, intensity, 0.0f, 2.0f ); + drawGeometry( panel, false ); + endPanelHighlight(); + }; + + if ( mMediaPanels[ panel ]->mReadyToRender && texture_valid ) + { + // draw visual geometry + drawGeometry( panel, false ); + } + + // restore texture matrix (changes for each panel) + glMatrixMode( GL_TEXTURE ); + glPopMatrix(); + }; + }; +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLFBConnectTest::display() +{ + // GLUI requires this + if ( glutGetWindow() != mAppWindow ) + glutSetWindow( mAppWindow ); + + // start with a clean slate + glClearColor( 0.0f, 0.0f, 0.0f, 1.0f ); + glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); + + // set up OpenGL view + glMatrixMode( GL_PROJECTION ); + glLoadIdentity(); + glFrustum( -mViewportAspect * 0.04f, mViewportAspect * 0.04f, -0.04f, 0.04f, 0.1f, 50.0f ); + glMatrixMode( GL_MODELVIEW ); + glLoadIdentity(); + glTranslatef( 0.0, 0.0, 0.0f ); + glTranslatef( mViewPos[ 0 ], mViewPos[ 1 ], -mViewPos[ 2 ] ); + glMultMatrixf( mViewRotation ); + + // draw pick texture + draw( DrawTypePickTexture ); + + // read colors and get coordinate values + glReadPixels( mCurMouseX, mCurMouseY, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, mPixelReadColor ); + + // clear the pick render (otherwise it may depth-fight with the textures rendered later) + glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); + + // draw visible geometry + draw( DrawTypeMediaTexture ); + + glutSwapBuffers(); +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLFBConnectTest::idle() +{ +// checkGLError("LLFBConnectTest::idle"); + + // GLUI requires this + if ( glutGetWindow() != mAppWindow ) + glutSetWindow( mAppWindow ); + + // random creation/destruction of panels enabled? +/* + const time_t panel_timeout_time = 5; + if ( mRandomPanelCount ) + { + // time for a change + static time_t last_panel_time = 0; + if ( time( NULL ) - last_panel_time > panel_timeout_time ) + { + if ( rand() % 2 == 0 ) + { + if ( mMediaPanels.size() < 16 ) + { + std::cout << "Randomly adding new panel" << std::endl; + addMediaPanel( mBookmarks[ rand() % ( mBookmarks.size() - 1 ) + 1 ].second ); + }; + } + else + { + if ( mMediaPanels.size() > 0 ) + { + std::cout << "Deleting selected panel" << std::endl; + remMediaPanel( mSelectedPanel ); + }; + }; + time( &last_panel_time ); + }; + }; + + // random selection of bookmarks enabled? + const time_t bookmark_timeout_time = 5; + if ( mRandomBookmarks ) + { + // time for a change + static time_t last_bookmark_time = 0; + if ( time( NULL ) - last_bookmark_time > bookmark_timeout_time ) + { + // go to a different random bookmark on each panel + for( int panel = 0; panel < (int)mMediaPanels.size(); ++panel ) + { + std::string uri = mBookmarks[ rand() % ( mBookmarks.size() - 1 ) + 1 ].second; + + std::cout << "Random: navigating to : " << uri << std::endl; + + std::string mime_type = mimeTypeFromUrl( uri ); + + if ( mime_type != mMediaPanels[ panel ]->mMimeType ) + { + replaceMediaPanel( mMediaPanels[ panel ], uri ); + } + else + { + mMediaPanels[ panel ]->mMediaSource->loadURI( uri ); + mMediaPanels[ panel ]->mMediaSource->start(); + }; + }; + + time( &last_bookmark_time ); + }; + }; +*/ + // update UI + if ( mSelectedPanel ) + { + // set volume based on slider if we have time media + //if ( mGluiMediaTimeControlWindowFlag ) + //{ + // mSelectedPanel->mMediaSource->setVolume( (float)mMediaTimeControlVolume / 100.0f ); + //}; + + // NOTE: it is absurd that we need cache the state of GLUI controls + // but enabling/disabling controls drags framerate from 500+ + // down to 15. Not a problem for plugin system - only this test + // enable/disable time based UI controls based on type of plugin + if ( mSelectedPanel->mMediaSource->pluginSupportsMediaTime() ) + { + /* + if ( ! mGluiMediaTimeControlWindowFlag ) + { + mGluiMediaTimeControlWindow->enable(); + mGluiMediaTimeControlWindowFlag = true; + }; + */ + } + else + { + /* + if ( mGluiMediaTimeControlWindowFlag ) + { + mGluiMediaTimeControlWindow->disable(); + mGluiMediaTimeControlWindowFlag = false; + }; + */ + }; + + // enable/disable browser based UI controls based on type of plugin + if ( mSelectedPanel->mMediaSource->pluginSupportsMediaBrowser() ) + { + if ( ! mGluiMediaBrowserControlWindowFlag ) + { + mGluiMediaBrowserControlWindow->enable(); + mGluiMediaBrowserControlWindowFlag = true; + }; + } + else + { + if ( mGluiMediaBrowserControlWindowFlag ) + { + mGluiMediaBrowserControlWindow->disable(); + mGluiMediaBrowserControlWindowFlag = false; + }; + }; + + // enable/disable browser back button depending on browser history + if ( mSelectedPanel->mMediaSource->getHistoryBackAvailable() ) + { + if ( ! mMediaBrowserControlBackButtonFlag ) + { + mMediaBrowserControlBackButton->enable(); + mMediaBrowserControlBackButtonFlag = true; + }; + } + else + { + if ( mMediaBrowserControlBackButtonFlag ) + { + mMediaBrowserControlBackButton->disable(); + mMediaBrowserControlBackButtonFlag = false; + }; + }; + + // enable/disable browser forward button depending on browser history + if ( mSelectedPanel->mMediaSource->getHistoryForwardAvailable() ) + { + if ( ! mMediaBrowserControlForwardButtonFlag ) + { + mMediaBrowserControlForwardButton->enable(); + mMediaBrowserControlForwardButtonFlag = true; + }; + } + else + { + if ( mMediaBrowserControlForwardButtonFlag ) + { + mMediaBrowserControlForwardButton->disable(); + mMediaBrowserControlForwardButtonFlag = false; + }; + }; + + // NOTE: This is *very* slow and not worth optimising + updateStatusBar(); + }; + + // update all the panels + for( int panel_index = 0; panel_index < (int)mMediaPanels.size(); ++panel_index ) + { + mediaPanel *panel = mMediaPanels[ panel_index ]; + + // call plugins idle function so it can potentially update itself + panel->mMediaSource->idle(); + + // update each media panel + updateMediaPanel( panel ); + + LLRect dirty_rect; + if ( ! panel->mMediaSource->textureValid() ) + { + //std::cout << "texture invalid, skipping update..." << std::endl; + } + else + if ( panel && + ( panel->mMediaWidth != panel->mMediaSource->getWidth() || + panel->mMediaHeight != panel->mMediaSource->getHeight() ) ) + { + //std::cout << "Resize in progress, skipping update..." << std::endl; + } + else + if ( panel->mMediaSource->getDirty( &dirty_rect ) ) + { + const unsigned char* pixels = panel->mMediaSource->getBitsData(); + if ( pixels && isTexture(panel->mMediaTextureHandle)) + { + int x_offset = dirty_rect.mLeft; + int y_offset = dirty_rect.mBottom; + int width = dirty_rect.mRight - dirty_rect.mLeft; + int height = dirty_rect.mTop - dirty_rect.mBottom; + + if((dirty_rect.mRight <= panel->mTextureWidth) && (dirty_rect.mTop <= panel->mTextureHeight)) + { + // Offset the pixels pointer properly + pixels += ( y_offset * panel->mMediaSource->getTextureDepth() * panel->mMediaSource->getBitsWidth() ); + pixels += ( x_offset * panel->mMediaSource->getTextureDepth() ); + + // set up texture + bindTexture( panel->mMediaTextureHandle, panel->mMediaSource->getBitsWidth() ); + if ( mFuzzyMedia ) + { + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); + } + else + { + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST ); + }; + + checkGLError("glTexParameteri"); + + if(panel->mMediaSource->getTextureFormatSwapBytes()) + { + glPixelStorei(GL_UNPACK_SWAP_BYTES, 1); + checkGLError("glPixelStorei"); + } + + // draw portion that changes into texture + glTexSubImage2D( GL_TEXTURE_2D, 0, + x_offset, + y_offset, + width, + height, + panel->mMediaSource->getTextureFormatPrimary(), + panel->mMediaSource->getTextureFormatType(), + pixels ); + + if(checkGLError("glTexSubImage2D")) + { + std::cerr << " panel ID=" << panel->mId << std::endl; + std::cerr << " texture size = " << panel->mTextureWidth << " x " << panel->mTextureHeight << std::endl; + std::cerr << " media size = " << panel->mMediaWidth << " x " << panel->mMediaHeight << std::endl; + std::cerr << " dirty rect = " << dirty_rect.mLeft << ", " << dirty_rect.mBottom << ", " << dirty_rect.mRight << ", " << dirty_rect.mTop << std::endl; + std::cerr << " texture width = " << panel->mMediaSource->getBitsWidth() << std::endl; + std::cerr << " format primary = 0x" << std::hex << panel->mMediaSource->getTextureFormatPrimary() << std::dec << std::endl; + std::cerr << " format type = 0x" << std::hex << panel->mMediaSource->getTextureFormatType() << std::dec << std::endl; + std::cerr << " pixels = " << (void*)pixels << std::endl; + } + + if(panel->mMediaSource->getTextureFormatSwapBytes()) + { + glPixelStorei(GL_UNPACK_SWAP_BYTES, 0); + checkGLError("glPixelStorei"); + } + + panel->mMediaSource->resetDirty(); + + panel->mReadyToRender = true; + } + else + { + std::cerr << "dirty rect is outside current media size, skipping update" << std::endl; + } + }; + }; + }; + + // GLUI requires this + if ( glutGetWindow() != mAppWindow ) + glutSetWindow( mAppWindow ); + + // trigger re-display + glutPostRedisplay(); +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLFBConnectTest::windowPosToTexturePos( int window_x, int window_y, + int& media_x, int& media_y, + int& id ) +{ + if ( ! mSelectedPanel ) + { + media_x = 0; + media_y = 0; + id = 0; + return; + }; + + // record cursor poisiton for a readback next frame + mCurMouseX = window_x; + // OpenGL app == coordinate system this way + // NOTE: unrelated to settings in plugin - this + // is just for this app + mCurMouseY = mWindowHeight - window_y; + + // extract x (0..1023, y (0..1023) and id (0..15) from RGB components + unsigned long pixel_read_color_bits = ( mPixelReadColor[ 0 ] << 16 ) | ( mPixelReadColor[ 1 ] << 8 ) | mPixelReadColor[ 2 ]; + int texture_x = pixel_read_color_bits & 0x3ff; + int texture_y = ( pixel_read_color_bits >> 10 ) & 0x3ff; + id = ( pixel_read_color_bits >> 20 ) & 0x0f; + + // scale to size of media (1024 because we use 10 bits for X and Y from 24) + media_x = (int)( ( (float)mSelectedPanel->mMediaWidth * (float)texture_x ) / 1024.0f ); + media_y = (int)( ( (float)mSelectedPanel->mMediaHeight * (float)texture_y ) / 1024.0f ); + + // we assume the plugin uses an inverted coordinate scheme like OpenGL + // if not, the plugin code inverts the Y coordinate for us - we don't need to + media_y = mSelectedPanel->mMediaHeight - media_y; + + if ( media_x > 0 && media_y > 0 ) + { + //std::cout << " mouse coords: " << mCurMouseX << " x " << mCurMouseY << " and id = " << id << std::endl; + //std::cout << "raw texture coords: " << texture_x << " x " << texture_y << " and id = " << id << std::endl; + //std::cout << " media coords: " << media_x << " x " << media_y << " and id = " << id << std::endl; + //std::cout << std::endl; + }; +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLFBConnectTest::selectPanelById( int id ) +{ + for( int panel = 0; panel < (int)mMediaPanels.size(); ++panel ) + { + if ( mMediaPanels[ panel ]->mId == id ) + { + selectPanel(mMediaPanels[ panel ]); + return; + }; + }; +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLFBConnectTest::selectPanel( mediaPanel* panel ) +{ + if( mSelectedPanel == panel ) + return; + + // turn off volume before we delete it + if( mSelectedPanel && mSelectedPanel->mMediaSource ) + { + mSelectedPanel->mMediaSource->setVolume( 0.0f ); + mSelectedPanel->mMediaSource->setPriority( LLPluginClassMedia::PRIORITY_LOW ); + }; + + mSelectedPanel = panel; + + if( mSelectedPanel && mSelectedPanel->mMediaSource ) + { + //mSelectedPanel->mMediaSource->setVolume( (float)mMediaTimeControlVolume / 100.0f ); + mSelectedPanel->mMediaSource->setPriority( LLPluginClassMedia::PRIORITY_NORMAL ); + + if(!mSelectedPanel->mStartUrl.empty()) + { + mUrlEdit->set_text(const_cast<char*>(mSelectedPanel->mStartUrl.c_str()) ); + } + }; +} + +//////////////////////////////////////////////////////////////////////////////// +// +mediaPanel* LLFBConnectTest::findMediaPanel( LLPluginClassMedia* source ) +{ + mediaPanel *result = NULL; + + for( int panel = 0; panel < (int)mMediaPanels.size(); ++panel ) + { + if ( mMediaPanels[ panel ]->mMediaSource == source ) + { + result = mMediaPanels[ panel ]; + } + } + + return result; +} + +//////////////////////////////////////////////////////////////////////////////// +// +mediaPanel* LLFBConnectTest::findMediaPanel( const std::string &target_name ) +{ + mediaPanel *result = NULL; + + for( int panel = 0; panel < (int)mMediaPanels.size(); ++panel ) + { + if ( mMediaPanels[ panel ]->mTarget == target_name ) + { + result = mMediaPanels[ panel ]; + } + } + + return result; +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLFBConnectTest::navigateToNewURI( std::string uri ) +{ + if ( uri.length() ) + { + std::string mime_type = mimeTypeFromUrl( uri ); + + if ( !mSelectedPanel->mMediaSource->isPluginExited() && (mime_type == mSelectedPanel->mMimeType) ) + { + std::cout << "MIME type is the same" << std::endl; + mSelectedPanel->mMediaSource->loadURI( uri ); + mSelectedPanel->mMediaSource->start(); + mBookmarkList->do_selection( 0 ); + } + else + { + std::cout << "MIME type changed or plugin had exited" << std::endl; + replaceMediaPanel( mSelectedPanel, uri ); + mBookmarkList->do_selection( 0 ); + } + }; +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLFBConnectTest::initUrlHistory( std::string uris ) +{ + if ( uris.length() > 0 ) + { + std::cout << "init URL : " << uris << std::endl; + LLSD historySD; + + char *cstr, *p; + cstr = new char[uris.size()+1]; + strcpy(cstr, uris.c_str()); + const char *DELIMS = " ,;"; + p = strtok(cstr, DELIMS); + while (p != NULL) { + historySD.insert(0, p); + p = strtok(NULL, DELIMS); + } + mSelectedPanel->mMediaSource->initializeUrlHistory(historySD); + delete[] cstr; + } +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLFBConnectTest::gluiCallback( int control_id ) +{ + if ( control_id == mIdBookmarks ) + { + std::string uri = mBookmarks[ mSelBookmark ].second; + + navigateToNewURI( uri ); + } + else + if ( control_id == mIdUrlEdit) + { + std::string uri = mUrlEdit->get_text(); + + navigateToNewURI( uri ); + } +/* + else + if ( control_id == mIdUrlInitHistoryEdit ) + { + std::string uri = mUrlInitHistoryEdit->get_text(); + + initUrlHistory( uri ); + } + else + if ( control_id == mIdControlAddPanel ) + { + addMediaPanel( mBookmarks[ rand() % ( mBookmarks.size() - 1 ) + 1 ].second ); + } + else + if ( control_id == mIdControlRemPanel ) + { + remMediaPanel( mSelectedPanel ); + } + else + if ( control_id == mIdDisableTimeout ) + { + // Set the "disable timeout" flag for all active plugins. + for( int i = 0; i < (int)mMediaPanels.size(); ++i ) + { + mMediaPanels[ i ]->mMediaSource->setDisableTimeout(mDisableTimeout); + } + } + else + if ( control_id == mIdUsePluginReadThread ) + { + LLPluginProcessParent::setUseReadThread(mUsePluginReadThread); + } + else + if ( control_id == mIdControlCrashPlugin ) + { + // send message to plugin and ask it to crash + // (switch out for ReleaseCandidate version :) ) + if(mSelectedPanel && mSelectedPanel->mMediaSource) + { + mSelectedPanel->mMediaSource->crashPlugin(); + } + } + else + if ( control_id == mIdControlHangPlugin ) + { + // send message to plugin and ask it to hang + // (switch out for ReleaseCandidate version :) ) + if(mSelectedPanel && mSelectedPanel->mMediaSource) + { + mSelectedPanel->mMediaSource->hangPlugin(); + } + } + else +*/ + if ( control_id == mIdControlExitApp ) + { + // text for exiting plugin system cleanly + delete this; // clean up + exit( 0 ); + } +/* + else + if ( control_id == mIdMediaTimeControlPlay ) + { + if ( mSelectedPanel ) + { + mSelectedPanel->mMediaSource->setLoop( false ); + mSelectedPanel->mMediaSource->start(); + }; + } + else + if ( control_id == mIdMediaTimeControlLoop ) + { + if ( mSelectedPanel ) + { + mSelectedPanel->mMediaSource->setLoop( true ); + mSelectedPanel->mMediaSource->start(); + }; + } + else + if ( control_id == mIdMediaTimeControlPause ) + { + if ( mSelectedPanel ) + mSelectedPanel->mMediaSource->pause(); + } + else + if ( control_id == mIdMediaTimeControlStop ) + { + if ( mSelectedPanel ) + { + mSelectedPanel->mMediaSource->stop(); + }; + } + else + if ( control_id == mIdMediaTimeControlSeek ) + { + if ( mSelectedPanel ) + { + // get value from spinner + float seconds_to_seek = mMediaTimeControlSeekSeconds; + mSelectedPanel->mMediaSource->seek( seconds_to_seek ); + mSelectedPanel->mMediaSource->start(); + }; + } + else + if ( control_id == mIdMediaTimeControlRewind ) + { + if ( mSelectedPanel ) + { + mSelectedPanel->mMediaSource->setLoop( false ); + mSelectedPanel->mMediaSource->start(-2.0f); + }; + } + else + if ( control_id == mIdMediaTimeControlFastForward ) + { + if ( mSelectedPanel ) + { + mSelectedPanel->mMediaSource->setLoop( false ); + mSelectedPanel->mMediaSource->start(2.0f); + }; + } + else +*/ + if ( control_id == mIdMediaBrowserControlBack ) + { + if ( mSelectedPanel ) + mSelectedPanel->mMediaSource->browse_back(); + } + else + if ( control_id == mIdMediaBrowserControlStop ) + { + if ( mSelectedPanel ) + mSelectedPanel->mMediaSource->browse_stop(); + } + else + if ( control_id == mIdMediaBrowserControlForward ) + { + if ( mSelectedPanel ) + mSelectedPanel->mMediaSource->browse_forward(); + } + else + if ( control_id == mIdMediaBrowserControlHome ) + { + if ( mSelectedPanel ) + mSelectedPanel->mMediaSource->loadURI( mHomeWebUrl ); + } + else + if ( control_id == mIdMediaBrowserControlReload ) + { + if ( mSelectedPanel ) + mSelectedPanel->mMediaSource->browse_reload( true ); + } +/* + else + if ( control_id == mIdMediaBrowserControlClearCache ) + { + if ( mSelectedPanel ) + mSelectedPanel->mMediaSource->clear_cache(); + } + else + if ( control_id == mIdMediaBrowserControlClearCookies ) + { + if ( mSelectedPanel ) + mSelectedPanel->mMediaSource->clear_cookies(); + } + else + if ( control_id == mIdMediaBrowserControlEnableCookies ) + { + if ( mSelectedPanel ) + { + if ( mMediaBrowserControlEnableCookies ) + { + mSelectedPanel->mMediaSource->enable_cookies( true ); + } + else + { + mSelectedPanel->mMediaSource->enable_cookies( false ); + } + }; + }; +*/ +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLFBConnectTest::keyboard( int key ) +{ + //if ( key == 'a' || key == 'A' ) + // addMediaPanel( mBookmarks[ rand() % ( mBookmarks.size() - 1 ) + 1 ].second ); + //else + //if ( key == 'r' || key == 'R' ) + // remMediaPanel( mSelectedPanel ); + //else + //if ( key == 'd' || key == 'D' ) + // dumpPanelInfo(); + //else + if ( key == 27 ) + { + std::cout << "Application finished - exiting..." << std::endl; + delete this; + exit( 0 ); + }; + + mSelectedPanel->mMediaSource->keyEvent( LLPluginClassMedia::KEY_EVENT_DOWN, key, 0 , LLSD()); + mSelectedPanel->mMediaSource->keyEvent( LLPluginClassMedia::KEY_EVENT_UP, key, 0, LLSD()); +}; + +//////////////////////////////////////////////////////////////////////////////// +// +void LLFBConnectTest::mouseButton( int button, int state, int x, int y ) +{ + if ( button == GLUT_LEFT_BUTTON ) + { + if ( state == GLUT_DOWN ) + { + int media_x, media_y, id; + windowPosToTexturePos( x, y, media_x, media_y, id ); + + if ( mSelectedPanel ) + mSelectedPanel->mMediaSource->mouseEvent( LLPluginClassMedia::MOUSE_EVENT_DOWN, 0, media_x, media_y, 0 ); + } + else + if ( state == GLUT_UP ) + { + int media_x, media_y, id; + windowPosToTexturePos( x, y, media_x, media_y, id ); + + // only select a panel if we're on a panel + // (HACK: strictly speaking this rules out clicking on + // the origin of a panel but that's very unlikely) + if ( media_x > 0 && media_y > 0 ) + { + selectPanelById( id ); + + if ( mSelectedPanel ) + mSelectedPanel->mMediaSource->mouseEvent( LLPluginClassMedia::MOUSE_EVENT_UP, 0, media_x, media_y, 0 ); + }; + }; + }; +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLFBConnectTest::mousePassive( int x, int y ) +{ + int media_x, media_y, id; + windowPosToTexturePos( x, y, media_x, media_y, id ); + + if ( mSelectedPanel ) + mSelectedPanel->mMediaSource->mouseEvent( LLPluginClassMedia::MOUSE_EVENT_MOVE, 0, media_x, media_y, 0 ); +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLFBConnectTest::mouseMove( int x, int y ) +{ + int media_x, media_y, id; + windowPosToTexturePos( x, y, media_x, media_y, id ); + + if ( mSelectedPanel ) + mSelectedPanel->mMediaSource->mouseEvent( LLPluginClassMedia::MOUSE_EVENT_MOVE, 0, media_x, media_y, 0 ); +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLFBConnectTest::makeChrome() +{ + // IDs used by GLUI + int start_id = 0x1000; + + // right side window - geometry manipulators +#if __APPLE__ + // the Apple GLUT implementation doesn't seem to set the graphic offset of subwindows correctly when they overlap in certain ways. + // Use a separate controls window in this case. + // GLUI window at right containing manipulation controls and other buttons + int x = glutGet(GLUT_WINDOW_X) + glutGet(GLUT_WINDOW_WIDTH) + 4; + int y = glutGet(GLUT_WINDOW_Y); + GLUI* right_glui_window = GLUI_Master.create_glui( "", 0, x, y ); +#else + GLUI* right_glui_window = GLUI_Master.create_glui_subwindow( mAppWindow, GLUI_SUBWINDOW_RIGHT ); +#endif + mViewRotationCtrl = right_glui_window->add_rotation( "Rotation", mViewRotation ); + mViewTranslationCtrl = right_glui_window->add_translation( "Translate", GLUI_TRANSLATION_XY, mViewPos ); + mViewTranslationCtrl->set_speed( 0.01f ); + mViewScaleCtrl = right_glui_window->add_translation( "Scale", GLUI_TRANSLATION_Z, &mViewPos[ 2 ] ); + mViewScaleCtrl->set_speed( 0.05f ); + right_glui_window->set_main_gfx_window( mAppWindow ); + + // right side window - app controls + /* + mIdControlAddPanel = start_id++; + right_glui_window->add_statictext( "" ); + right_glui_window->add_separator(); + right_glui_window->add_statictext( "" ); + right_glui_window->add_button( "Add panel", mIdControlAddPanel, gluiCallbackWrapper ); + right_glui_window->add_statictext( "" ); + mIdControlRemPanel = start_id++; + right_glui_window->add_button( "Rem panel", mIdControlRemPanel, gluiCallbackWrapper ); + right_glui_window->add_statictext( "" ); + right_glui_window->add_separator(); + right_glui_window->add_statictext( "" ); + mIdControlCrashPlugin = start_id++; + right_glui_window->add_button( "Crash plugin", mIdControlCrashPlugin, gluiCallbackWrapper ); + mIdControlHangPlugin = start_id++; + right_glui_window->add_button( "Hang plugin", mIdControlHangPlugin, gluiCallbackWrapper ); + */ + right_glui_window->add_statictext( "" ); + right_glui_window->add_separator(); + right_glui_window->add_statictext( "" ); + mIdControlExitApp = start_id++; + right_glui_window->add_button( "Exit app", mIdControlExitApp, gluiCallbackWrapper ); + + //// top window - holds bookmark UI + mIdBookmarks = start_id++; + mSelBookmark = 0; + GLUI* glui_window_top = GLUI_Master.create_glui_subwindow( mAppWindow, GLUI_SUBWINDOW_TOP ); + mBookmarkList = glui_window_top->add_listbox( "", &mSelBookmark, mIdBookmarks, gluiCallbackWrapper ); + // only add the first 50 bookmarks - list can be very long sometimes (30,000+) + // when testing list of media URLs from AGNI for example + for( unsigned int each = 0; each < mBookmarks.size() && each < 50; ++each ) + mBookmarkList->add_item( each, const_cast< char* >( mBookmarks[ each ].first.c_str() ) ); + glui_window_top->set_main_gfx_window( mAppWindow ); + + glui_window_top->add_column( false ); + mIdUrlEdit = start_id++; + mUrlEdit = glui_window_top->add_edittext( "Url:", GLUI_EDITTEXT_TEXT, 0, mIdUrlEdit, gluiCallbackWrapper ); + mUrlEdit->set_w( 600 ); + //GLUI* glui_window_top2 = GLUI_Master.create_glui_subwindow( mAppWindow, GLUI_SUBWINDOW_TOP ); + //mIdUrlInitHistoryEdit = start_id++; + //mUrlInitHistoryEdit = glui_window_top2->add_edittext( "Init History (separate by commas or semicolons):", + // GLUI_EDITTEXT_TEXT, 0, mIdUrlInitHistoryEdit, gluiCallbackWrapper ); + //mUrlInitHistoryEdit->set_w( 800 ); + + // top window - media controls for "time" media types (e.g. movies) +/* + mGluiMediaTimeControlWindow = GLUI_Master.create_glui_subwindow( mAppWindow, GLUI_SUBWINDOW_TOP ); + mGluiMediaTimeControlWindow->set_main_gfx_window( mAppWindow ); + mIdMediaTimeControlPlay = start_id++; + mGluiMediaTimeControlWindow->add_button( "PLAY", mIdMediaTimeControlPlay, gluiCallbackWrapper ); + mGluiMediaTimeControlWindow->add_column( false ); + mIdMediaTimeControlLoop = start_id++; + mGluiMediaTimeControlWindow->add_button( "LOOP", mIdMediaTimeControlLoop, gluiCallbackWrapper ); + mGluiMediaTimeControlWindow->add_column( false ); + mIdMediaTimeControlPause = start_id++; + mGluiMediaTimeControlWindow->add_button( "PAUSE", mIdMediaTimeControlPause, gluiCallbackWrapper ); + mGluiMediaTimeControlWindow->add_column( false ); + + GLUI_Button *button; + mIdMediaTimeControlRewind = start_id++; + button = mGluiMediaTimeControlWindow->add_button( "<<", mIdMediaTimeControlRewind, gluiCallbackWrapper ); + button->set_w(30); + mGluiMediaTimeControlWindow->add_column( false ); + mIdMediaTimeControlFastForward = start_id++; + button = mGluiMediaTimeControlWindow->add_button( ">>", mIdMediaTimeControlFastForward, gluiCallbackWrapper ); + button->set_w(30); + + mGluiMediaTimeControlWindow->add_column( true ); + + mIdMediaTimeControlStop = start_id++; + mGluiMediaTimeControlWindow->add_button( "STOP", mIdMediaTimeControlStop, gluiCallbackWrapper ); + mGluiMediaTimeControlWindow->add_column( false ); + mIdMediaTimeControlVolume = start_id++; + GLUI_Spinner* spinner = mGluiMediaTimeControlWindow->add_spinner( "Volume", 2, &mMediaTimeControlVolume, mIdMediaTimeControlVolume, gluiCallbackWrapper); + spinner->set_float_limits( 0, 100 ); + mGluiMediaTimeControlWindow->add_column( true ); + mIdMediaTimeControlSeekSeconds = start_id++; + spinner = mGluiMediaTimeControlWindow->add_spinner( "", 2, &mMediaTimeControlSeekSeconds, mIdMediaTimeControlSeekSeconds, gluiCallbackWrapper); + spinner->set_float_limits( 0, 200 ); + spinner->set_w( 32 ); + spinner->set_speed( 0.025f ); + mGluiMediaTimeControlWindow->add_column( false ); + mIdMediaTimeControlSeek = start_id++; + mGluiMediaTimeControlWindow->add_button( "SEEK", mIdMediaTimeControlSeek, gluiCallbackWrapper ); + mGluiMediaTimeControlWindow->add_column( false ); +*/ + + // top window - media controls for "browser" media types (e.g. web browser) + mGluiMediaBrowserControlWindow = GLUI_Master.create_glui_subwindow( mAppWindow, GLUI_SUBWINDOW_TOP ); + mGluiMediaBrowserControlWindow->set_main_gfx_window( mAppWindow ); + mIdMediaBrowserControlBack = start_id++; + mMediaBrowserControlBackButton = mGluiMediaBrowserControlWindow->add_button( "BACK", mIdMediaBrowserControlBack, gluiCallbackWrapper ); + mGluiMediaBrowserControlWindow->add_column( false ); + mIdMediaBrowserControlStop = start_id++; + mGluiMediaBrowserControlWindow->add_button( "STOP", mIdMediaBrowserControlStop, gluiCallbackWrapper ); + mGluiMediaBrowserControlWindow->add_column( false ); + mIdMediaBrowserControlForward = start_id++; + mMediaBrowserControlForwardButton = mGluiMediaBrowserControlWindow->add_button( "FORWARD", mIdMediaBrowserControlForward, gluiCallbackWrapper ); + mGluiMediaBrowserControlWindow->add_column( false ); + mIdMediaBrowserControlHome = start_id++; + mGluiMediaBrowserControlWindow->add_button( "HOME", mIdMediaBrowserControlHome, gluiCallbackWrapper ); + mGluiMediaBrowserControlWindow->add_column( false ); + mIdMediaBrowserControlReload = start_id++; + mGluiMediaBrowserControlWindow->add_button( "RELOAD", mIdMediaBrowserControlReload, gluiCallbackWrapper ); + mGluiMediaBrowserControlWindow->add_column( false ); + /* + mIdMediaBrowserControlClearCache = start_id++; + mGluiMediaBrowserControlWindow->add_button( "CLEAR CACHE", mIdMediaBrowserControlClearCache, gluiCallbackWrapper ); + mGluiMediaBrowserControlWindow->add_column( false ); + mIdMediaBrowserControlClearCookies = start_id++; + mGluiMediaBrowserControlWindow->add_button( "CLEAR COOKIES", mIdMediaBrowserControlClearCookies, gluiCallbackWrapper ); + mGluiMediaBrowserControlWindow->add_column( false ); + mIdMediaBrowserControlEnableCookies = start_id++; + mMediaBrowserControlEnableCookies = 0; + mGluiMediaBrowserControlWindow->add_checkbox( "Enable Cookies", &mMediaBrowserControlEnableCookies, mIdMediaBrowserControlEnableCookies, gluiCallbackWrapper ); + + // top window - misc controls + GLUI* glui_window_misc_control = GLUI_Master.create_glui_subwindow( mAppWindow, GLUI_SUBWINDOW_TOP ); + mIdRandomPanelCount = start_id++; + mRandomPanelCount = 0; + glui_window_misc_control->add_checkbox( "Randomize panel count", &mRandomPanelCount, mIdRandomPanelCount, gluiCallbackWrapper ); + glui_window_misc_control->set_main_gfx_window( mAppWindow ); + glui_window_misc_control->add_column( true ); + mIdRandomBookmarks = start_id++; + mRandomBookmarks = 0; + glui_window_misc_control->add_checkbox( "Randomize bookmarks", &mRandomBookmarks, mIdRandomBookmarks, gluiCallbackWrapper ); + glui_window_misc_control->set_main_gfx_window( mAppWindow ); + glui_window_misc_control->add_column( true ); + + mIdDisableTimeout = start_id++; + mDisableTimeout = 0; + glui_window_misc_control->add_checkbox( "Disable plugin timeout", &mDisableTimeout, mIdDisableTimeout, gluiCallbackWrapper ); + glui_window_misc_control->set_main_gfx_window( mAppWindow ); + glui_window_misc_control->add_column( true ); + + mIdUsePluginReadThread = start_id++; + mUsePluginReadThread = 0; + glui_window_misc_control->add_checkbox( "Use plugin read thread", &mUsePluginReadThread, mIdUsePluginReadThread, gluiCallbackWrapper ); + glui_window_misc_control->set_main_gfx_window( mAppWindow ); + glui_window_misc_control->add_column( true ); + + mIdLargePanelSpacing = start_id++; + mLargePanelSpacing = 0; + glui_window_misc_control->add_checkbox( "Large Panel Spacing", &mLargePanelSpacing, mIdLargePanelSpacing, gluiCallbackWrapper ); + glui_window_misc_control->set_main_gfx_window( mAppWindow ); + glui_window_misc_control->add_column( true ); +*/ + // bottom window - status + mBottomGLUIWindow = GLUI_Master.create_glui_subwindow( mAppWindow, GLUI_SUBWINDOW_BOTTOM ); + mStatusText = mBottomGLUIWindow->add_statictext( "" ); + mBottomGLUIWindow->set_main_gfx_window( mAppWindow ); +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLFBConnectTest::resetView() +{ + mViewRotationCtrl->reset(); + + mViewScaleCtrl->set_x( 0.0f ); + mViewScaleCtrl->set_y( 0.0f ); + mViewScaleCtrl->set_z( 1.3f ); + + mViewTranslationCtrl->set_x( 0.0f ); + mViewTranslationCtrl->set_y( 0.0f ); + mViewTranslationCtrl->set_z( 0.0f ); +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLFBConnectTest::makePickTexture( int id, GLuint* texture_handle, unsigned char** texture_pixels ) +{ + int pick_texture_width = 1024; + int pick_texture_height = 1024; + int pick_texture_depth = 3; + unsigned char* ptr = new unsigned char[ pick_texture_width * pick_texture_height * pick_texture_depth ]; + for( int y = 0; y < pick_texture_height; ++y ) + { + for( int x = 0; x < pick_texture_width * pick_texture_depth ; x += pick_texture_depth ) + { + unsigned long bits = 0L; + bits |= ( id << 20 ) | ( y << 10 ) | ( x / 3 ); + unsigned char r_component = ( bits >> 16 ) & 0xff; + unsigned char g_component = ( bits >> 8 ) & 0xff; + unsigned char b_component = bits & 0xff; + + ptr[ y * pick_texture_width * pick_texture_depth + x + 0 ] = r_component; + ptr[ y * pick_texture_width * pick_texture_depth + x + 1 ] = g_component; + ptr[ y * pick_texture_width * pick_texture_depth + x + 2 ] = b_component; + }; + }; + + glGenTextures( 1, texture_handle ); + + checkGLError("glGenTextures"); + std::cout << "glGenTextures returned " << *texture_handle << std::endl; + + bindTexture( *texture_handle ); + glTexImage2D( GL_TEXTURE_2D, 0, + GL_RGB, + pick_texture_width, pick_texture_height, + 0, GL_RGB, GL_UNSIGNED_BYTE, ptr ); + + *texture_pixels = ptr; +} + +//////////////////////////////////////////////////////////////////////////////// +// +std::string LLFBConnectTest::mimeTypeFromUrl( std::string& url ) +{ + // default to web + std::string mime_type = "text/html"; + + // we may need a more advanced MIME type accessor later :-) + if ( url.find( ".mov" ) != std::string::npos ) // Movies + mime_type = "video/quicktime"; + else + if ( url.find( ".txt" ) != std::string::npos ) // Apple Text descriptors + mime_type = "video/quicktime"; + else + if ( url.find( ".mp3" ) != std::string::npos ) // Apple Text descriptors + mime_type = "video/quicktime"; + else + if ( url.find( "example://" ) != std::string::npos ) // Example plugin + mime_type = "example/example"; + + return mime_type; +} + +//////////////////////////////////////////////////////////////////////////////// +// +std::string LLFBConnectTest::pluginNameFromMimeType( std::string& mime_type ) +{ +#if LL_DARWIN + std::string plugin_name( "media_plugin_null.dylib" ); + if ( mime_type == "video/quicktime" ) + plugin_name = "media_plugin_quicktime.dylib"; + else + if ( mime_type == "text/html" ) + plugin_name = "media_plugin_webkit.dylib"; + +#elif LL_WINDOWS + std::string plugin_name( "media_plugin_null.dll" ); + + if ( mime_type == "video/quicktime" ) + plugin_name = "media_plugin_quicktime.dll"; + else + if ( mime_type == "text/html" ) + plugin_name = "media_plugin_webkit.dll"; + else + if ( mime_type == "example/example" ) + plugin_name = "media_plugin_example.dll"; + +#elif LL_LINUX + std::string plugin_name( "libmedia_plugin_null.so" ); + + if ( mime_type == "video/quicktime" ) + plugin_name = "libmedia_plugin_quicktime.so"; + else + if ( mime_type == "text/html" ) + plugin_name = "libmedia_plugin_webkit.so"; +#endif + return plugin_name; +} + +//////////////////////////////////////////////////////////////////////////////// +// +mediaPanel* LLFBConnectTest::addMediaPanel( std::string url ) +{ + // Get the plugin filename using the URL + std::string mime_type = mimeTypeFromUrl( url ); + std::string plugin_name = pluginNameFromMimeType( mime_type ); + + // create a random size for the new media + int media_width; + int media_height; + getRandomMediaSize( media_width, media_height, mime_type ); + media_width = 1024; + media_height = 1536; + + // make a new plugin + LLPluginClassMedia* media_source = new LLPluginClassMedia(this); + + // enable cookies so the FB login works + media_source->enable_cookies(true); + + // tell the plugin what size we asked for + media_source->setSize( media_width, media_height ); + + // Use the launcher start and initialize the plugin +#if LL_DARWIN || LL_LINUX + std::string launcher_name( "SLPlugin" ); +#elif LL_WINDOWS + std::string launcher_name( "SLPlugin.exe" ); +#endif + + // for this test app, use the cwd as the user data path (ugh). +#if LL_WINDOWS + std::string user_data_path = ".\\"; +#else + char cwd[ FILENAME_MAX ]; + if (NULL == getcwd( cwd, FILENAME_MAX - 1 )) + { + std::cerr << "Couldn't get cwd - probably too long - failing to init." << std::endl; + return NULL; + } + std::string user_data_path = std::string( cwd ) + "/"; +#endif + media_source->setUserDataPath(user_data_path); + media_source->init( launcher_name, user_data_path, plugin_name, false ); + //media_source->setDisableTimeout(mDisableTimeout); + + // make a new panel and save parameters + mediaPanel* panel = new mediaPanel; + panel->mMediaSource = media_source; + panel->mStartUrl = url; + panel->mMimeType = mime_type; + panel->mMediaWidth = media_width; + panel->mMediaHeight = media_height; + panel->mTextureWidth = 0; + panel->mTextureHeight = 0; + panel->mTextureScaleX = 0; + panel->mTextureScaleY = 0; + panel->mMediaTextureHandle = 0; + panel->mPickTextureHandle = 0; + panel->mAppTextureCoordsOpenGL = false; // really need an 'undefined' state here too + panel->mReadyToRender = false; + + // look through current media panels to find an unused index number + bool id_exists = true; + for( int nid = 0; nid < mMaxPanels; ++nid ) + { + // does this id exist already? + id_exists = false; + for( int pid = 0; pid < (int)mMediaPanels.size(); ++pid ) + { + if ( nid == mMediaPanels[ pid ]->mId ) + { + id_exists = true; + break; + }; + }; + + // id wasn't found so we can use it + if ( ! id_exists ) + { + panel->mId = nid; + break; + }; + }; + + // if we get here and this flag is set, there is no room for any more panels + if ( id_exists ) + { + std::cout << "No room for any more panels" << std::endl; + } + else + { + // now we have the ID we can use it to make the + // pick texture (id is baked into texture pixels) + makePickTexture( panel->mId, &panel->mPickTextureHandle, &panel->mPickTexturePixels ); + + // save this in the list of panels + mMediaPanels.push_back( panel ); + + // select the panel that was just created + selectPanel( panel ); + + // load and start the URL + panel->mMediaSource->loadURI( url ); + panel->mMediaSource->start(); + + std::cout << "Adding new media panel for " << url << "(" << media_width << "x" << media_height << ") with index " << panel->mId << " - total panels = " << mMediaPanels.size() << std::endl; + } + + return panel; +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLFBConnectTest::updateMediaPanel( mediaPanel* panel ) +{ +// checkGLError("LLFBConnectTest::updateMediaPanel"); + + if ( ! panel ) + return; + + if(!panel->mMediaSource || !panel->mMediaSource->textureValid()) + { + panel->mReadyToRender = false; + return; + } + + // take a reference copy of the plugin values since they + // might change during this lifetime of this function + int plugin_media_width = panel->mMediaSource->getWidth(); + int plugin_media_height = panel->mMediaSource->getHeight(); + int plugin_texture_width = panel->mMediaSource->getBitsWidth(); + int plugin_texture_height = panel->mMediaSource->getBitsHeight(); + + // If the texture isn't created or the media or texture dimensions changed AND + // the sizes are valid then we need to delete the old media texture (if necessary) + // then make a new one. + if ((panel->mMediaTextureHandle == 0 || + panel->mMediaWidth != plugin_media_width || + panel->mMediaHeight != plugin_media_height || + panel->mTextureWidth != plugin_texture_width || + panel->mTextureHeight != plugin_texture_height) && + ( plugin_media_width > 0 && plugin_media_height > 0 && + plugin_texture_width > 0 && plugin_texture_height > 0 ) ) + { + std::cout << "Valid media size (" << plugin_media_width << " x " << plugin_media_height + << ") and texture size (" << plugin_texture_width << " x " << plugin_texture_height + << ") for panel with ID=" << panel->mId << " - making texture" << std::endl; + + // delete old GL texture + if ( isTexture( panel->mMediaTextureHandle ) ) + { + std::cerr << "updateMediaPanel: deleting texture " << panel->mMediaTextureHandle << std::endl; + glDeleteTextures( 1, &panel->mMediaTextureHandle ); + panel->mMediaTextureHandle = 0; + } + + std::cerr << "before: pick texture is " << panel->mPickTextureHandle << ", media texture is " << panel->mMediaTextureHandle << std::endl; + + // make a GL texture based on the dimensions the plugin told us + GLuint new_texture = 0; + glGenTextures( 1, &new_texture ); + + checkGLError("glGenTextures"); + + std::cout << "glGenTextures returned " << new_texture << std::endl; + + panel->mMediaTextureHandle = new_texture; + + bindTexture( panel->mMediaTextureHandle ); + + std::cout << "Setting texture size to " << plugin_texture_width << " x " << plugin_texture_height << std::endl; + glTexImage2D( GL_TEXTURE_2D, 0, + GL_RGB, + plugin_texture_width, plugin_texture_height, + 0, GL_RGB, GL_UNSIGNED_BYTE, + 0 ); + + + std::cerr << "after: pick texture is " << panel->mPickTextureHandle << ", media texture is " << panel->mMediaTextureHandle << std::endl; + }; + + // update our record of the media and texture dimensions + // NOTE: do this after we we check for sizes changes + panel->mMediaWidth = plugin_media_width; + panel->mMediaHeight = plugin_media_height; + panel->mTextureWidth = plugin_texture_width; + panel->mTextureHeight = plugin_texture_height; + if ( plugin_texture_width > 0 ) + { + panel->mTextureScaleX = (double)panel->mMediaWidth / (double)panel->mTextureWidth; + }; + if ( plugin_texture_height > 0 ) + { + panel->mTextureScaleY = (double)panel->mMediaHeight / (double)panel->mTextureHeight; + }; + + // update the flag which tells us if the media source uses OprnGL coords or not. + panel->mAppTextureCoordsOpenGL = panel->mMediaSource->getTextureCoordsOpenGL(); + + // Check to see if we have enough to render this panel. + // If we do, set a flag that the display functions use so + // they only render a panel with media if it's ready. + if ( panel->mMediaWidth < 0 || + panel->mMediaHeight < 0 || + panel->mTextureWidth < 1 || + panel->mTextureHeight < 1 || + panel->mMediaTextureHandle == 0 ) + { + panel->mReadyToRender = false; + }; +} + +//////////////////////////////////////////////////////////////////////////////// +// +mediaPanel* LLFBConnectTest::replaceMediaPanel( mediaPanel* panel, std::string url ) +{ + // no media panels so we can't change anything - have to add + if ( mMediaPanels.size() == 0 ) + return NULL; + + // sanity check + if ( ! panel ) + return NULL; + + int index; + for(index = 0; index < (int)mMediaPanels.size(); index++) + { + if(mMediaPanels[index] == panel) + break; + } + + if(index >= (int)mMediaPanels.size()) + { + // panel isn't in mMediaPanels + return NULL; + } + + std::cout << "Replacing media panel with index " << panel->mId << std::endl; + + int panel_id = panel->mId; + + if(mSelectedPanel == panel) + mSelectedPanel = NULL; + + delete panel; + + // Get the plugin filename using the URL + std::string mime_type = mimeTypeFromUrl( url ); + std::string plugin_name = pluginNameFromMimeType( mime_type ); + + // create a random size for the new media + int media_width; + int media_height; + getRandomMediaSize( media_width, media_height, mime_type ); + + // make a new plugin + LLPluginClassMedia* media_source = new LLPluginClassMedia(this); + + // tell the plugin what size we asked for + media_source->setSize( media_width, media_height ); + + // Use the launcher start and initialize the plugin +#if LL_DARWIN || LL_LINUX + std::string launcher_name( "SLPlugin" ); +#elif LL_WINDOWS + std::string launcher_name( "SLPlugin.exe" ); +#endif + + // for this test app, use the cwd as the user data path (ugh). +#if LL_WINDOWS + std::string user_data_path = ".\\"; +#else + char cwd[ FILENAME_MAX ]; + if (NULL == getcwd( cwd, FILENAME_MAX - 1 )) + { + std::cerr << "Couldn't get cwd - probably too long - failing to init." << std::endl; + return NULL; + } + std::string user_data_path = std::string( cwd ) + "/"; +#endif + + media_source->setUserDataPath(user_data_path); + media_source->init( launcher_name, user_data_path, plugin_name, false ); + //media_source->setDisableTimeout(mDisableTimeout); + + // make a new panel and save parameters + panel = new mediaPanel; + panel->mMediaSource = media_source; + panel->mStartUrl = url; + panel->mMimeType = mime_type; + panel->mMediaWidth = media_width; + panel->mMediaHeight = media_height; + panel->mTextureWidth = 0; + panel->mTextureHeight = 0; + panel->mTextureScaleX = 0; + panel->mTextureScaleY = 0; + panel->mMediaTextureHandle = 0; + panel->mPickTextureHandle = 0; + panel->mAppTextureCoordsOpenGL = false; // really need an 'undefined' state here too + panel->mReadyToRender = false; + + panel->mId = panel_id; + + // Replace the entry in the panels array + mMediaPanels[index] = panel; + + // now we have the ID we can use it to make the + // pick texture (id is baked into texture pixels) + makePickTexture( panel->mId, &panel->mPickTextureHandle, &panel->mPickTexturePixels ); + + // select the panel that was just created + selectPanel( panel ); + + // load and start the URL + panel->mMediaSource->loadURI( url ); + panel->mMediaSource->start(); + + return panel; +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLFBConnectTest::getRandomMediaSize( int& width, int& height, std::string mime_type ) +{ + // Make a new media source with a random size which we'll either + // directly or the media plugin will tell us what it wants later. + // Use a random size so we can test support for weird media sizes. + // (Almost everything else will get filled in later once the + // plugin responds) + // NB. Do we need to enforce that width is on 4 pixel boundary? + width = ( ( rand() % 170 ) + 30 ) * 4; + height = ( ( rand() % 170 ) + 30 ) * 4; + + // adjust this random size if it's a browser so we get + // a more useful size for testing.. + if ( mime_type == "text/html" || mime_type == "example/example" ) + { + width = ( ( rand() % 100 ) + 100 ) * 4; + height = ( width * ( ( rand() % 400 ) + 1000 ) ) / 1000; + }; +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLFBConnectTest::remMediaPanel( mediaPanel* panel ) +{ + // always leave one panel + if ( mMediaPanels.size() == 1 ) + return; + + // sanity check - don't think this can happen but see above for a case where it might... + if ( ! panel ) + return; + + std::cout << "Removing media panel with index " << panel->mId << " - total panels = " << mMediaPanels.size() - 1 << std::endl; + + if(mSelectedPanel == panel) + mSelectedPanel = NULL; + + delete panel; + + // remove from storage list + for( int i = 0; i < (int)mMediaPanels.size(); ++i ) + { + if ( mMediaPanels[ i ] == panel ) + { + mMediaPanels.erase( mMediaPanels.begin() + i ); + break; + }; + }; + + // select the first panel + selectPanel( mMediaPanels[ 0 ] ); +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLFBConnectTest::updateStatusBar() +{ + if ( ! mSelectedPanel ) + return; + + // cache results - this is a very slow function + static int cached_id = -1; + static int cached_media_width = -1; + static int cached_media_height = -1; + static int cached_texture_width = -1; + static int cached_texture_height = -1; + static bool cached_supports_browser_media = true; + static bool cached_supports_time_media = false; + static int cached_movie_time = -1; + static GLfloat cached_distance = -1.0f; + + static std::string cached_plugin_version = ""; + if ( + cached_id == mSelectedPanel->mId && + cached_media_width == mSelectedPanel->mMediaWidth && + cached_media_height == mSelectedPanel->mMediaHeight && + cached_texture_width == mSelectedPanel->mTextureWidth && + cached_texture_height == mSelectedPanel->mTextureHeight && + cached_supports_browser_media == mSelectedPanel->mMediaSource->pluginSupportsMediaBrowser() && + cached_supports_time_media == mSelectedPanel->mMediaSource->pluginSupportsMediaTime() && + cached_plugin_version == mSelectedPanel->mMediaSource->getPluginVersion() && + cached_movie_time == (int)mSelectedPanel->mMediaSource->getCurrentTime() && + cached_distance == mDistanceCameraToSelectedGeometry + ) + { + // nothing changed so don't spend time here + return; + }; + + std::ostringstream stream( "" ); + + stream.str( "" ); + stream.clear(); + + stream << "Id: "; + stream << std::setw( 2 ) << std::setfill( '0' ); + stream << mSelectedPanel->mId; + stream << " | "; + stream << "Media: "; + stream << std::setw( 3 ) << std::setfill( '0' ); + stream << mSelectedPanel->mMediaWidth; + stream << " x "; + stream << std::setw( 3 ) << std::setfill( '0' ); + stream << mSelectedPanel->mMediaHeight; + stream << " | "; + stream << "Texture: "; + stream << std::setw( 4 ) << std::setfill( '0' ); + stream << mSelectedPanel->mTextureWidth; + stream << " x "; + stream << std::setw( 4 ) << std::setfill( '0' ); + stream << mSelectedPanel->mTextureHeight; + + stream << " | "; + stream << "Distance: "; + stream << std::setw( 6 ); + stream << std::setprecision( 3 ); + stream << std::setprecision( 3 ); + stream << mDistanceCameraToSelectedGeometry; + stream << " | "; + + if ( mSelectedPanel->mMediaSource->pluginSupportsMediaBrowser() ) + stream << "BROWSER"; + else + if ( mSelectedPanel->mMediaSource->pluginSupportsMediaTime() ) + stream << "TIME "; + stream << " | "; + stream << mSelectedPanel->mMediaSource->getPluginVersion(); + stream << " | "; + if ( mSelectedPanel->mMediaSource->pluginSupportsMediaTime() ) + { + stream << std::setw( 3 ) << std::setfill( '0' ); + stream << (int)mSelectedPanel->mMediaSource->getCurrentTime(); + stream << " / "; + stream << std::setw( 3 ) << std::setfill( '0' ); + stream << (int)mSelectedPanel->mMediaSource->getDuration(); + stream << " @ "; + stream << (int)mSelectedPanel->mMediaSource->getCurrentPlayRate(); + stream << " | "; + }; + + glutSetWindow( mBottomGLUIWindow->get_glut_window_id() ); + mStatusText->set_text( const_cast< char*>( stream.str().c_str() ) ); + glutSetWindow( mAppWindow ); + + // caching + cached_id = mSelectedPanel->mId; + cached_media_width = mSelectedPanel->mMediaWidth; + cached_media_height = mSelectedPanel->mMediaHeight; + cached_texture_width = mSelectedPanel->mTextureWidth; + cached_texture_height = mSelectedPanel->mTextureHeight; + cached_supports_browser_media = mSelectedPanel->mMediaSource->pluginSupportsMediaBrowser(); + cached_supports_time_media = mSelectedPanel->mMediaSource->pluginSupportsMediaTime(); + cached_plugin_version = mSelectedPanel->mMediaSource->getPluginVersion(); + cached_movie_time = (int)mSelectedPanel->mMediaSource->getCurrentTime(); +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLFBConnectTest::dumpPanelInfo() +{ + std::cout << std::endl << "===== Media Panels =====" << std::endl; + for( int i = 0; i < (int)mMediaPanels.size(); ++i ) + { + std::cout << std::setw( 2 ) << std::setfill( '0' ); + std::cout << i + 1 << "> "; + std::cout << "Id: "; + std::cout << std::setw( 2 ) << std::setfill( '0' ); + std::cout << mMediaPanels[ i ]->mId; + std::cout << " | "; + std::cout << "Media: "; + std::cout << std::setw( 3 ) << std::setfill( '0' ); + std::cout << mMediaPanels[ i ]->mMediaWidth; + std::cout << " x "; + std::cout << std::setw( 3 ) << std::setfill( '0' ); + std::cout << mMediaPanels[ i ]->mMediaHeight; + std::cout << " | "; + std::cout << "Texture: "; + std::cout << std::setw( 4 ) << std::setfill( '0' ); + std::cout << mMediaPanels[ i ]->mTextureWidth; + std::cout << " x "; + std::cout << std::setw( 4 ) << std::setfill( '0' ); + std::cout << mMediaPanels[ i ]->mTextureHeight; + std::cout << " | "; + if ( mMediaPanels[ i ] == mSelectedPanel ) + std::cout << "(selected)"; + + std::cout << std::endl; + }; + std::cout << "========================" << std::endl; +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLFBConnectTest::handleMediaEvent(LLPluginClassMedia* self, EMediaEvent event) +{ + // Uncomment this to make things much, much quieter. +// return; + + switch(event) + { + case MEDIA_EVENT_CONTENT_UPDATED: + // too spammy -- don't log these +// std::cerr << "Media event: MEDIA_EVENT_CONTENT_UPDATED " << std::endl; + break; + + case MEDIA_EVENT_TIME_DURATION_UPDATED: + // too spammy -- don't log these +// std::cerr << "Media event: MEDIA_EVENT_TIME_DURATION_UPDATED, time is " << self->getCurrentTime() << " of " << self->getDuration() << std::endl; + break; + + case MEDIA_EVENT_SIZE_CHANGED: + std::cerr << "Media event: MEDIA_EVENT_SIZE_CHANGED " << std::endl; + break; + + case MEDIA_EVENT_CURSOR_CHANGED: + std::cerr << "Media event: MEDIA_EVENT_CURSOR_CHANGED, new cursor is " << self->getCursorName() << std::endl; + break; + + case MEDIA_EVENT_NAVIGATE_BEGIN: + std::cerr << "Media event: MEDIA_EVENT_NAVIGATE_BEGIN " << std::endl; + break; + + case MEDIA_EVENT_NAVIGATE_COMPLETE: + std::cerr << "Media event: MEDIA_EVENT_NAVIGATE_COMPLETE, result string is: " << self->getNavigateResultString() << std::endl; + break; + + case MEDIA_EVENT_PROGRESS_UPDATED: + std::cerr << "Media event: MEDIA_EVENT_PROGRESS_UPDATED, loading at " << self->getProgressPercent() << "%" << std::endl; + break; + + case MEDIA_EVENT_STATUS_TEXT_CHANGED: + std::cerr << "Media event: MEDIA_EVENT_STATUS_TEXT_CHANGED, new status text is: " << self->getStatusText() << std::endl; + break; + + case MEDIA_EVENT_NAME_CHANGED: + std::cerr << "Media event: MEDIA_EVENT_NAME_CHANGED, new name is: " << self->getMediaName() << std::endl; + glutSetWindowTitle( self->getMediaName().c_str() ); + break; + + case MEDIA_EVENT_LOCATION_CHANGED: + { + std::cerr << "Media event: MEDIA_EVENT_LOCATION_CHANGED, new uri is: " << self->getLocation() << std::endl; + mediaPanel* panel = findMediaPanel(self); + if(panel != NULL) + { + panel->mStartUrl = self->getLocation(); + if(panel == mSelectedPanel) + { + mUrlEdit->set_text(const_cast<char*>(panel->mStartUrl.c_str()) ); + } + } + } + break; + + case MEDIA_EVENT_NAVIGATE_ERROR_PAGE: + std::cerr << "Media event: MEDIA_EVENT_NAVIGATE_ERROR_PAGE, uri is: " << self->getClickURL() << std::endl; + break; + + case MEDIA_EVENT_CLICK_LINK_HREF: + { + std::cerr << "Media event: MEDIA_EVENT_CLICK_LINK_HREF, uri is " << self->getClickURL() << ", target is " << self->getClickTarget() << std::endl; + // retrieve the event parameters + std::string url = self->getClickURL(); + std::string target = self->getClickTarget(); + + if(target == "_external") + { + // this should open in an external browser, but since this is a test app we don't care. + } + else if(target == "_blank") + { + // Create a new panel with the specified URL. + addMediaPanel(url); + } + else // other named target + { + mediaPanel *target_panel = findMediaPanel(target); + if(target_panel) + { + target_panel = replaceMediaPanel(target_panel, url); + } + else + { + target_panel = addMediaPanel(url); + } + + if(target_panel) + { + target_panel->mTarget = target; + } + } + } + break; + + case MEDIA_EVENT_CLICK_LINK_NOFOLLOW: + std::cerr << "Media event: MEDIA_EVENT_CLICK_LINK_NOFOLLOW, uri is " << self->getClickURL() << std::endl; + break; + + case MEDIA_EVENT_PLUGIN_FAILED: + std::cerr << "Media event: MEDIA_EVENT_PLUGIN_FAILED" << std::endl; + break; + + case MEDIA_EVENT_PLUGIN_FAILED_LAUNCH: + std::cerr << "Media event: MEDIA_EVENT_PLUGIN_FAILED_LAUNCH" << std::endl; + break; + + case MEDIA_EVENT_CLOSE_REQUEST: + std::cerr << "Media event: MEDIA_EVENT_CLOSE_REQUEST" << std::endl; + break; + + case MEDIA_EVENT_PICK_FILE_REQUEST: + std::cerr << "Media event: MEDIA_EVENT_PICK_FILE_REQUEST" << std::endl; + // TODO: display an actual file picker + self->sendPickFileResponse("cake"); + break; + + case MEDIA_EVENT_GEOMETRY_CHANGE: + std::cerr << "Media event: MEDIA_EVENT_GEOMETRY_CHANGE, uuid is " << self->getClickUUID() + << ", x = " << self->getGeometryX() + << ", y = " << self->getGeometryY() + << ", width = " << self->getGeometryWidth() + << ", height = " << self->getGeometryHeight() + << std::endl; + break; + + case MEDIA_EVENT_AUTH_REQUEST: + { + //std::cerr << "Media event: MEDIA_EVENT_AUTH_REQUEST, url " << self->getAuthURL() ", realm " << self->getAuthRealm() << std::endl; + + // TODO: display an auth dialog + self->sendAuthResponse(false, "", ""); + } + break; + + case MEDIA_EVENT_LINK_HOVERED: + { + std::cerr << "Media event: MEDIA_EVENT_LINK_HOVERED, hover text is: " << self->getHoverText() << std::endl; + } + break; + + default: + { + std::cerr << "Media event: <unknown>, code is: " << int(event) << std::endl; + } + break; + } +} + +//////////////////////////////////////////////////////////////////////////////// +// +static void gluiCallbackWrapper( int control_id ) +{ + if ( gApplication ) + gApplication->gluiCallback( control_id ); +} + +//////////////////////////////////////////////////////////////////////////////// +// +void glutReshape( int width, int height ) +{ + if ( gApplication ) + gApplication->reshape( width, height ); +}; + +//////////////////////////////////////////////////////////////////////////////// +// +void glutDisplay() +{ + if ( gApplication ) + gApplication->display(); +}; + +//////////////////////////////////////////////////////////////////////////////// +// +void glutIdle(int update_ms) +{ + GLUI_Master.set_glutTimerFunc( update_ms, glutIdle, update_ms); + + if ( gApplication ) + gApplication->idle(); + +}; + +//////////////////////////////////////////////////////////////////////////////// +// +void glutKeyboard( unsigned char key, int x, int y ) +{ + if ( gApplication ) + gApplication->keyboard( key ); +}; + +//////////////////////////////////////////////////////////////////////////////// +// +void glutMousePassive( int x, int y ) +{ + if ( gApplication ) + gApplication->mousePassive( x, y ); +} + +//////////////////////////////////////////////////////////////////////////////// +// +void glutMouseMove( int x , int y ) +{ + if ( gApplication ) + gApplication->mouseMove( x, y ); +} + +//////////////////////////////////////////////////////////////////////////////// +// +void glutMouseButton( int button, int state, int x, int y ) +{ + if ( gApplication ) + gApplication->mouseButton( button, state, x, y ); +} + +//////////////////////////////////////////////////////////////////////////////// +// +int main( int argc, char* argv[] ) +{ +#if LL_DARWIN + // Set the current working directory to <application bundle>/Contents/Resources/ + CFURLRef resources_url = CFBundleCopyResourcesDirectoryURL(CFBundleGetMainBundle()); + if(resources_url != NULL) + { + CFStringRef resources_string = CFURLCopyFileSystemPath(resources_url, kCFURLPOSIXPathStyle); + CFRelease(resources_url); + if(resources_string != NULL) + { + char buffer[PATH_MAX] = ""; + if(CFStringGetCString(resources_string, buffer, sizeof(buffer), kCFStringEncodingUTF8)) + { + chdir(buffer); + } + CFRelease(resources_string); + } + } +#endif + + glutInit( &argc, argv ); + glutInitDisplayMode( GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGB ); + + const int app_window_x = 80; + const int app_window_y = 0; + const int app_window_width = 960; + const int app_window_height = 960; + + glutInitWindowPosition( app_window_x, app_window_y ); + glutInitWindowSize( app_window_width, app_window_height ); + + int app_window_handle = glutCreateWindow( "LLFBConnectTest" ); + + glutDisplayFunc( glutDisplay ); + + GLUI_Master.set_glutReshapeFunc( glutReshape ); + GLUI_Master.set_glutKeyboardFunc( glutKeyboard ); + GLUI_Master.set_glutMouseFunc( glutMouseButton ); + + glutPassiveMotionFunc( glutMousePassive ); + glutMotionFunc( glutMouseMove ); + + glutSetWindow( app_window_handle ); + + gApplication = new LLFBConnectTest( app_window_handle, app_window_width, app_window_height ); + + // update at approximately 60hz + int update_ms = 1000 / 60; + + GLUI_Master.set_glutTimerFunc( update_ms, glutIdle, update_ms); + + glutMainLoop(); + + delete gApplication; +} diff --git a/indra/test_apps/llfbconnecttest/llfbconnecttest.h b/indra/test_apps/llfbconnecttest/llfbconnecttest.h new file mode 100644 index 0000000000..6f442a55b3 --- /dev/null +++ b/indra/test_apps/llfbconnecttest/llfbconnecttest.h @@ -0,0 +1,207 @@ +/** + * @file LLFBConnectTest.cpp + * @brief Facebook Connect Test App + * + * $LicenseInfo:firstyear=2008&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_FB_CONNECT_H +#define LL_FB_CONNECT_H + +#include <vector> +#include <string> +#include "llpluginclassmedia.h" +#include "llgl.h" + +// Forward declarations +class GLUI_Rotation; +class GLUI_Translation; +class GLUI_Listbox; +class GLUI_EditText; +class GLUI_StaticText; +class GLUI; +class GLUI_Button; + +//////////////////////////////////////////////////////////////////////////////// +// +struct mediaPanel +{ + public: + mediaPanel(); + ~mediaPanel(); + int mId; + std::string mStartUrl; + std::string mMimeType; + std::string mTarget; + LLPluginClassMedia *mMediaSource; + int mMediaWidth; + int mMediaHeight; + int mTextureWidth; + int mTextureHeight; + double mTextureScaleX; + double mTextureScaleY; + GLuint mMediaTextureHandle; + GLuint mPickTextureHandle; + unsigned char* mPickTexturePixels; + bool mAppTextureCoordsOpenGL; + bool mReadyToRender; +}; + +//////////////////////////////////////////////////////////////////////////////// +// +class LLFBConnectTest : public LLPluginClassMediaOwner +{ + public: + LLFBConnectTest( int app_window, int window_width, int window_height ); + ~LLFBConnectTest(); + + void reshape( int width, int height ); + void display(); + void idle(); + void gluiCallback( int control_id ); + void keyboard( int key ); + void mousePassive( int x, int y ); + void mouseButton( int button, int state, int x, int y ); + void mouseMove( int x, int y ); + + void bindTexture(GLuint texture, GLint row_length = 0, GLint alignment = 1); + bool checkGLError(const char *name = "OpenGL"); + void drawGeometry( int panel, bool selected ); + void startPanelHighlight( float red, float green, float blue, float line_width ); + void endPanelHighlight(); + enum { DrawTypePickTexture, DrawTypeMediaTexture }; + void draw( int draw_type ); + void windowPosToTexturePos( int window_x, int window_y, int& media_x, int& media_y, int& id ); + + mediaPanel* addMediaPanel( std::string url ); + void updateMediaPanel( mediaPanel* panel ); + void remMediaPanel( mediaPanel* panel ); + mediaPanel* replaceMediaPanel( mediaPanel* panel, std::string url ); + void getRandomMediaSize( int& width, int& height, std::string mime_type ); + void navigateToNewURI( std::string uri ); + void initUrlHistory( std::string uri ); + void selectPanelById( int id ); + void selectPanel( mediaPanel* panel ); + mediaPanel* findMediaPanel( LLPluginClassMedia* panel ); + mediaPanel* findMediaPanel( const std::string &target_name ); + void makePickTexture( int id, GLuint* texture_handle, unsigned char** texture_pixels ); + void makeChrome(); + void resetView(); + + void dumpPanelInfo(); + void updateStatusBar(); + + GLfloat distanceToCamera( GLfloat point_x, GLfloat point_y, GLfloat point_z ); + + + // Inherited from LLPluginClassMediaOwner + /*virtual*/ void handleMediaEvent(LLPluginClassMedia* self, LLPluginClassMediaOwner::EMediaEvent); + + private: + const int mVersionMajor; + const int mVersionMinor; + const int mVersionPatch; + const int mMaxPanels; + int mAppWindow; + int mWindowWidth; + int mWindowHeight; + int mCurMouseX; + int mCurMouseY; + unsigned char mPixelReadColor[ 3 ]; + bool mFuzzyMedia; + const std::string mHomeWebUrl; + + std::vector< mediaPanel* > mMediaPanels; + mediaPanel* mSelectedPanel; + std::string mimeTypeFromUrl( std::string& url ); + std::string pluginNameFromMimeType( std::string& mime_type ); + + GLUI_Rotation* mViewRotationCtrl; + GLUI_Translation* mViewScaleCtrl; + GLUI_Translation* mViewTranslationCtrl; + float mViewportAspect; + float mViewPos[ 3 ]; + float mViewRotation[ 16 ]; + + float mDistanceCameraToSelectedGeometry; + + int mIdControlAddPanel; + int mIdControlRemPanel; + + std::vector< std::pair< std::string, std::string > > mBookmarks; + GLUI_Listbox* mBookmarkList; + int mIdBookmarks; + int mIdUrlEdit; + GLUI_EditText* mUrlEdit; + //int mIdUrlInitHistoryEdit; + //GLUI_EditText* mUrlInitHistoryEdit; + int mSelBookmark; + //int mIdRandomPanelCount; + //int mRandomPanelCount; + //int mIdRandomBookmarks; + //int mRandomBookmarks; + //int mIdDisableTimeout; + //int mDisableTimeout; + //int mIdUsePluginReadThread; + //int mUsePluginReadThread; + //int mIdLargePanelSpacing; + //int mLargePanelSpacing; + //int mIdControlCrashPlugin; + //int mIdControlHangPlugin; + int mIdControlExitApp; + + //GLUI* mGluiMediaTimeControlWindow; + //int mIdMediaTimeControlPlay; + //int mIdMediaTimeControlLoop; + //int mIdMediaTimeControlPause; + //int mIdMediaTimeControlStop; + //int mIdMediaTimeControlSeek; + //int mIdMediaTimeControlVolume; + //int mMediaTimeControlVolume; + //int mIdMediaTimeControlSeekSeconds; + //int mMediaTimeControlSeekSeconds; + //int mIdMediaTimeControlRewind; + //int mIdMediaTimeControlFastForward; + + GLUI* mGluiMediaBrowserControlWindow; + int mIdMediaBrowserControlBack; + GLUI_Button* mMediaBrowserControlBackButton; + int mIdMediaBrowserControlStop; + int mIdMediaBrowserControlForward; + GLUI_Button* mMediaBrowserControlForwardButton; + bool mGluiMediaTimeControlWindowFlag; + bool mGluiMediaBrowserControlWindowFlag; + bool mMediaBrowserControlBackButtonFlag; + bool mMediaBrowserControlForwardButtonFlag; + int mIdMediaBrowserControlHome; + int mIdMediaBrowserControlReload; + int mIdMediaBrowserControlClearCache; + int mIdMediaBrowserControlClearCookies; + int mIdMediaBrowserControlEnableCookies; + int mMediaBrowserControlEnableCookies; + + GLUI* mBottomGLUIWindow; + GLUI_StaticText* mStatusText; +}; + +#endif // LL_FB_CONNECT_H + |