diff options
author | Monroe Williams <monroe@lindenlab.com> | 2007-08-02 01:18:34 +0000 |
---|---|---|
committer | Monroe Williams <monroe@lindenlab.com> | 2007-08-02 01:18:34 +0000 |
commit | 7138fb673ac3df46b9cb5f23d0d74e70fdd2b6b3 (patch) | |
tree | 3c34a3a180b5275bd4166b0056765c5868f56447 /indra | |
parent | f6a10b3214d79df4e8f5768acaa68edbd2de5620 (diff) |
Merge down from Branch_1-18-1:
svn merge --ignore-ancestry svn+ssh://svn.lindenlab.com/svn/linden/release@66449 svn+ssh://svn.lindenlab.com/svn/linden/branches/Branch_1-18-1@67131
Diffstat (limited to 'indra')
129 files changed, 10606 insertions, 2468 deletions
diff --git a/indra/llcharacter/llmotion.cpp b/indra/llcharacter/llmotion.cpp index a53956223c..73aefa6640 100644 --- a/indra/llcharacter/llmotion.cpp +++ b/indra/llcharacter/llmotion.cpp @@ -106,6 +106,11 @@ void LLMotion::setDeactivateCallback( void (*cb)(void *), void* userdata ) mDeactivateCallbackUserData = userdata; } +BOOL LLMotion::isBlending() +{ + return mPose.getWeight() < 1.f; +} + //----------------------------------------------------------------------------- // activate() //----------------------------------------------------------------------------- @@ -122,10 +127,16 @@ void LLMotion::activate() void LLMotion::deactivate() { mActive = FALSE; + mPose.setWeight(0.f); if (mDeactivateCallback) (*mDeactivateCallback)(mDeactivateCallbackUserData); onDeactivate(); } +BOOL LLMotion::canDeprecate() +{ + return TRUE; +} + // End diff --git a/indra/llcharacter/llmotion.h b/indra/llcharacter/llmotion.h index 66882a2c11..77edba667f 100644 --- a/indra/llcharacter/llmotion.h +++ b/indra/llcharacter/llmotion.h @@ -79,13 +79,14 @@ public: void setStopped(BOOL stopped) { mStopped = stopped; } + BOOL isBlending(); + void activate(); void deactivate(); BOOL isActive() { return mActive; } - public: //------------------------------------------------------------------------- // animation callbacks to be implemented by subclasses @@ -125,6 +126,11 @@ public: // called when a motion is deactivated virtual void onDeactivate() = 0; + // can we crossfade this motion with a new instance when restarted? + // should ultimately always be TRUE, but lack of emote blending, etc + // requires this + virtual BOOL canDeprecate(); + // optional callback routine called when animation deactivated. void setDeactivateCallback( void (*cb)(void *), void* userdata ); diff --git a/indra/llcharacter/llmotioncontroller.cpp b/indra/llcharacter/llmotioncontroller.cpp index 592a6bae2c..bef5ea5dbb 100644 --- a/indra/llcharacter/llmotioncontroller.cpp +++ b/indra/llcharacter/llmotioncontroller.cpp @@ -174,34 +174,44 @@ void LLMotionController::deleteAllMotions() //----------------------------------------------------------------------------- void LLMotionController::addLoadedMotion(LLMotion* motionp) { + std::set<LLUUID> motions_to_kill; + + // gather all inactive, loaded motions if (mLoadedMotions.size() > MAX_MOTION_INSTANCES) { // too many motions active this frame, kill all blenders mPoseBlender.clearBlenders(); - for (U32 i = 0; i < mLoadedMotions.size(); i++) + for (motion_list_t::iterator loaded_motion_it = mLoadedMotions.begin(); + loaded_motion_it != mLoadedMotions.end(); + ++loaded_motion_it) { - LLMotion* cur_motionp = mLoadedMotions.front(); - mLoadedMotions.pop_front(); + LLMotion* cur_motionp = *loaded_motion_it; // motion isn't playing, delete it if (!isMotionActive(cur_motionp)) { - mCharacter->requestStopMotion(cur_motionp); - mAllMotions.erase(cur_motionp->getID()); - delete cur_motionp; - if (mLoadedMotions.size() <= MAX_MOTION_INSTANCES) - { - break; - } - } - else - { - // put active motion on back - mLoadedMotions.push_back(cur_motionp); + motions_to_kill.insert(cur_motionp->getID()); } } } + + // clean up all inactive, loaded motions + for (std::set<LLUUID>::iterator motion_it = motions_to_kill.begin(); + motion_it != motions_to_kill.end(); + ++motion_it) + { + // look up the motion again by ID to get canonical instance + // and kill it only if that one is inactive + LLUUID motion_id = *motion_it; + LLMotion* motionp = findMotion(motion_id); + if (motionp && !isMotionActive(motionp)) + { + removeMotion(motion_id); + } + } + + // add new motion to loaded list mLoadedMotions.push_back(motionp); } @@ -260,14 +270,24 @@ BOOL LLMotionController::addMotion( const LLUUID& id, LLMotionConstructor constr void LLMotionController::removeMotion( const LLUUID& id) { LLMotion* motionp = findMotion(id); + + removeMotionInstance(motionp); + + mAllMotions.erase(id); +} + +// removes instance of a motion from all runtime structures, but does +// not erase entry by ID, as this could be a duplicate instance +// use removeMotion(id) to remove all references to a given motion by id. +void LLMotionController::removeMotionInstance(LLMotion* motionp) +{ if (motionp) { - stopMotionLocally(id, TRUE); + stopMotionInstance(motionp, TRUE); mLoadingMotions.erase(motionp); mLoadedMotions.remove(motionp); mActiveMotions.remove(motionp); - mAllMotions.erase(id); delete motionp; } } @@ -328,28 +348,39 @@ LLMotion* LLMotionController::createMotion( const LLUUID &id ) //----------------------------------------------------------------------------- BOOL LLMotionController::startMotion(const LLUUID &id, F32 start_offset) { - // look for motion in our list of created motions - LLMotion *motion = createMotion(id); + // do we have an instance of this motion for this character? + LLMotion *motion = findMotion(id); + + // motion that is stopping will be allowed to stop but + // replaced by a new instance of that motion + if (motion + && motion->canDeprecate() + && motion->getFadeWeight() > 0.01f // not LOD-ed out + && (motion->isBlending() || motion->getStopTime() != 0.f)) + { + deprecateMotionInstance(motion); + // force creation of new instance + motion = NULL; + } + + // create new motion instance + if (!motion) + { + motion = createMotion(id); + } if (!motion) { return FALSE; } - //if the motion is already active, then we're done - else if (isMotionActive(motion)) // motion is playing and... + //if the motion is already active and allows deprecation, then let it keep playing + else if (motion->canDeprecate() && isMotionActive(motion)) { - if (motion->isStopped()) // motion has been stopped - { - deactivateMotion(motion, false); - } - else if (mTime < motion->mSendStopTimestamp) // motion is still active - { - return TRUE; - } + return TRUE; } // llinfos << "Starting motion " << name << llendl; - return activateMotion(motion, mTime - start_offset); + return activateMotionInstance(motion, mTime - start_offset); } @@ -360,6 +391,12 @@ BOOL LLMotionController::stopMotionLocally(const LLUUID &id, BOOL stop_immediate { // if already inactive, return false LLMotion *motion = findMotion(id); + + return stopMotionInstance(motion, stop_immediate); +} + +BOOL LLMotionController::stopMotionInstance(LLMotion* motion, BOOL stop_immediate) +{ if (!motion) { return FALSE; @@ -376,7 +413,7 @@ BOOL LLMotionController::stopMotionLocally(const LLUUID &id, BOOL stop_immediate if (stop_immediate) { - deactivateMotion(motion, false); + deactivateMotionInstance(motion); } return TRUE; } @@ -472,7 +509,7 @@ void LLMotionController::updateMotionsByType(LLMotion::LLMotionBlendType anim_ty { if (motionp->isStopped() && mTime > motionp->getStopTime() + motionp->getEaseOutDuration()) { - deactivateMotion(motionp, false); + deactivateMotionInstance(motionp); } else if (motionp->isStopped() && mTime > motionp->getStopTime()) { @@ -490,7 +527,7 @@ void LLMotionController::updateMotionsByType(LLMotion::LLMotionBlendType anim_ty if (mLastTime <= motionp->mSendStopTimestamp) { mCharacter->requestStopMotion( motionp ); - stopMotionLocally(motionp->getID(), FALSE); + stopMotionInstance(motionp, FALSE); } } else if (mTime >= motionp->mActivationTimestamp) @@ -518,7 +555,7 @@ void LLMotionController::updateMotionsByType(LLMotion::LLMotionBlendType anim_ty if (mLastTime <= motionp->mSendStopTimestamp) { mCharacter->requestStopMotion( motionp ); - stopMotionLocally(motionp->getID(), FALSE); + stopMotionInstance(motionp, FALSE); } } @@ -526,7 +563,8 @@ void LLMotionController::updateMotionsByType(LLMotion::LLMotionBlendType anim_ty { if (motionp->isStopped() && mTime > motionp->getStopTime() + motionp->getEaseOutDuration()) { - deactivateMotion(motionp, true); + posep->setWeight(0.f); + deactivateMotionInstance(motionp); } continue; } @@ -552,7 +590,8 @@ void LLMotionController::updateMotionsByType(LLMotion::LLMotionBlendType anim_ty } else { - deactivateMotion(motionp, true); + posep->setWeight(0.f); + deactivateMotionInstance(motionp); continue; } } @@ -597,7 +636,7 @@ void LLMotionController::updateMotionsByType(LLMotion::LLMotionBlendType anim_ty if (mLastTime <= motionp->mSendStopTimestamp) { mCharacter->requestStopMotion( motionp ); - stopMotionLocally(motionp->getID(), FALSE); + stopMotionInstance(motionp, FALSE); } } @@ -641,7 +680,7 @@ void LLMotionController::updateMotionsByType(LLMotion::LLMotionBlendType anim_ty // propagate this to the network // as not all viewers are guaranteed to have access to the same logic mCharacter->requestStopMotion( motionp ); - stopMotionLocally(motionp->getID(), FALSE); + stopMotionInstance(motionp, FALSE); } } @@ -713,7 +752,7 @@ void LLMotionController::updateMotion() // this motion should be playing if (!motionp->isStopped()) { - activateMotion(motionp, mTime); + activateMotionInstance(motionp, mTime); } } else if (status == LLMotion::STATUS_FAILURE) @@ -721,6 +760,7 @@ void LLMotionController::updateMotion() llinfos << "Motion " << motionp->getID() << " init failed." << llendl; sRegistry.markBad(motionp->getID()); mLoadingMotions.erase(curiter); + mAllMotions.erase(motionp->getID()); delete motionp; } @@ -753,9 +793,9 @@ void LLMotionController::updateMotion() //----------------------------------------------------------------------------- -// activateMotion() +// activateMotionInstance() //----------------------------------------------------------------------------- -BOOL LLMotionController::activateMotion(LLMotion *motion, F32 time) +BOOL LLMotionController::activateMotionInstance(LLMotion *motion, F32 time) { if (mLoadingMotions.find(motion) != mLoadingMotions.end()) { @@ -798,23 +838,38 @@ BOOL LLMotionController::activateMotion(LLMotion *motion, F32 time) } //----------------------------------------------------------------------------- -// deactivateMotion() +// deactivateMotionInstance() //----------------------------------------------------------------------------- -BOOL LLMotionController::deactivateMotion(LLMotion *motion, bool remove_weight) +BOOL LLMotionController::deactivateMotionInstance(LLMotion *motion) { - if( remove_weight ) + motion->deactivate(); + + motion_set_t::iterator found_it = mDeprecatedMotions.find(motion); + if (found_it != mDeprecatedMotions.end()) { - // immediately remove pose weighting instead of letting it time out - LLPose *posep = motion->getPose(); - posep->setWeight(0.f); + // deprecated motions need to be completely excised + removeMotionInstance(motion); + mDeprecatedMotions.erase(found_it); + } + else + { + // for motions that we are keeping, simply remove from active queue + mActiveMotions.remove(motion); } - - motion->deactivate(); - mActiveMotions.remove(motion); return TRUE; } +void LLMotionController::deprecateMotionInstance(LLMotion* motion) +{ + mDeprecatedMotions.insert(motion); + + //fade out deprecated motion + stopMotionInstance(motion, FALSE); + //no longer canonical + mAllMotions.erase(motion->getID()); +} + //----------------------------------------------------------------------------- // isMotionActive() //----------------------------------------------------------------------------- @@ -837,7 +892,7 @@ bool LLMotionController::isMotionLoading(LLMotion* motion) //----------------------------------------------------------------------------- LLMotion *LLMotionController::findMotion(const LLUUID& id) { - return mAllMotions[id]; + return get_if_there<LLUUID, LLMotion*>(mAllMotions, id, NULL); } //----------------------------------------------------------------------------- diff --git a/indra/llcharacter/llmotioncontroller.h b/indra/llcharacter/llmotioncontroller.h index 19bab09a11..d5ea568479 100644 --- a/indra/llcharacter/llmotioncontroller.h +++ b/indra/llcharacter/llmotioncontroller.h @@ -159,16 +159,21 @@ public: LLMotion *findMotion( const LLUUID& id ); protected: + // internal operations act on motion instances directly + // as there can be duplicate motions per id during blending overlap void deleteAllMotions(); void addLoadedMotion(LLMotion *motion); - BOOL activateMotion(LLMotion *motion, F32 time); - BOOL deactivateMotion(LLMotion *motion, bool remove_weight); + BOOL activateMotionInstance(LLMotion *motion, F32 time); + BOOL deactivateMotionInstance(LLMotion *motion); + void deprecateMotionInstance(LLMotion* motion); + BOOL stopMotionInstance(LLMotion *motion, BOOL stop_imemdiate); + void removeMotionInstance(LLMotion* motion); void updateRegularMotions(); void updateAdditiveMotions(); void resetJointSignatures(); void updateMotionsByType(LLMotion::LLMotionBlendType motion_type); -protected: +protected: F32 mTimeFactor; static LLMotionRegistry sRegistry; LLPoseBlender mPoseBlender; @@ -183,11 +188,13 @@ protected: // Once an animations is loaded, it will be initialized and put on the mLoadedMotions deque. // Any animation that is currently playing also sits in the mActiveMotions list. - std::map<LLUUID, LLMotion*> mAllMotions; + typedef std::map<LLUUID, LLMotion*> motion_map_t; + motion_map_t mAllMotions; motion_set_t mLoadingMotions; motion_list_t mLoadedMotions; motion_list_t mActiveMotions; + motion_set_t mDeprecatedMotions; LLFrameTimer mTimer; F32 mTime; diff --git a/indra/llcharacter/llpose.cpp b/indra/llcharacter/llpose.cpp index e1b24d62cb..1f6d208b84 100644 --- a/indra/llcharacter/llpose.cpp +++ b/indra/llcharacter/llpose.cpp @@ -255,9 +255,9 @@ void LLJointStateBlender::blendJointStates(BOOL apply_now) joint_state_index < JSB_NUM_JOINT_STATES && mJointStates[joint_state_index] != NULL; joint_state_index++) { - U32 current_usage = mJointStates[joint_state_index]->getUsage(); - F32 current_weight = mJointStates[joint_state_index]->getWeight(); LLJointState* jsp = mJointStates[joint_state_index]; + U32 current_usage = jsp->getUsage(); + F32 current_weight = jsp->getWeight(); if (current_weight == 0.f) { @@ -272,17 +272,14 @@ void LLJointStateBlender::blendJointStates(BOOL apply_now) // add in pos for this jointstate modulated by weight added_pos += jsp->getPosition() * (new_weight_sum - sum_weights[POS_WEIGHT]); - //sum_weights[POS_WEIGHT] = new_weight_sum; } - // now do scale if(current_usage & LLJointState::SCALE) { F32 new_weight_sum = llmin(1.f, current_weight + sum_weights[SCALE_WEIGHT]); // add in scale for this jointstate modulated by weight added_scale += jsp->getScale() * (new_weight_sum - sum_weights[SCALE_WEIGHT]); - //sum_weights[SCALE_WEIGHT] = new_weight_sum; } if (current_usage & LLJointState::ROT) @@ -291,7 +288,6 @@ void LLJointStateBlender::blendJointStates(BOOL apply_now) // add in rotation for this jointstate modulated by weight added_rot = nlerp((new_weight_sum - sum_weights[ROT_WEIGHT]), added_rot, jsp->getRotation()) * added_rot; - //sum_weights[ROT_WEIGHT] = new_weight_sum; } } else @@ -306,13 +302,13 @@ void LLJointStateBlender::blendJointStates(BOOL apply_now) F32 new_weight_sum = llmin(1.f, current_weight + sum_weights[POS_WEIGHT]); // blend positions from both - blended_pos = lerp(mJointStates[joint_state_index]->getPosition(), blended_pos, sum_weights[POS_WEIGHT] / new_weight_sum); + blended_pos = lerp(jsp->getPosition(), blended_pos, sum_weights[POS_WEIGHT] / new_weight_sum); sum_weights[POS_WEIGHT] = new_weight_sum; } else { // copy position from current - blended_pos = mJointStates[joint_state_index]->getPosition(); + blended_pos = jsp->getPosition(); sum_weights[POS_WEIGHT] = current_weight; } } @@ -325,13 +321,13 @@ void LLJointStateBlender::blendJointStates(BOOL apply_now) F32 new_weight_sum = llmin(1.f, current_weight + sum_weights[SCALE_WEIGHT]); // blend scales from both - blended_scale = lerp(mJointStates[joint_state_index]->getScale(), blended_scale, sum_weights[SCALE_WEIGHT] / new_weight_sum); + blended_scale = lerp(jsp->getScale(), blended_scale, sum_weights[SCALE_WEIGHT] / new_weight_sum); sum_weights[SCALE_WEIGHT] = new_weight_sum; } else { // copy scale from current - blended_scale = mJointStates[joint_state_index]->getScale(); + blended_scale = jsp->getScale(); sum_weights[SCALE_WEIGHT] = current_weight; } } @@ -344,13 +340,13 @@ void LLJointStateBlender::blendJointStates(BOOL apply_now) F32 new_weight_sum = llmin(1.f, current_weight + sum_weights[ROT_WEIGHT]); // blend rotations from both - blended_rot = nlerp(sum_weights[ROT_WEIGHT] / new_weight_sum, mJointStates[joint_state_index]->getRotation(), blended_rot); + blended_rot = nlerp(sum_weights[ROT_WEIGHT] / new_weight_sum, jsp->getRotation(), blended_rot); sum_weights[ROT_WEIGHT] = new_weight_sum; } else { // copy rotation from current - blended_rot = mJointStates[joint_state_index]->getRotation(); + blended_rot = jsp->getRotation(); sum_weights[ROT_WEIGHT] = current_weight; } } diff --git a/indra/llcharacter/llpose.h b/indra/llcharacter/llpose.h index 5d17bd8458..b3cc994960 100644 --- a/indra/llcharacter/llpose.h +++ b/indra/llcharacter/llpose.h @@ -61,7 +61,7 @@ public: S32 getNumJointStates() const; }; -const S32 JSB_NUM_JOINT_STATES = 4; +const S32 JSB_NUM_JOINT_STATES = 6; class LLJointStateBlender { diff --git a/indra/llcommon/lltimer.cpp b/indra/llcommon/lltimer.cpp index 993574b528..f335458f24 100644 --- a/indra/llcommon/lltimer.cpp +++ b/indra/llcommon/lltimer.cpp @@ -492,7 +492,7 @@ void secondsToTimecodeString(F32 current_time, char *tcstring) std::list<LLEventTimer*> LLEventTimer::sActiveList; LLEventTimer::LLEventTimer(F32 period) -: mTimer() +: mEventTimer() { mPeriod = period; sActiveList.push_back(this); @@ -508,9 +508,9 @@ void LLEventTimer::updateClass() for (std::list<LLEventTimer*>::iterator iter = sActiveList.begin(); iter != sActiveList.end(); ) { LLEventTimer* timer = *iter++; - F32 et = timer->mTimer.getElapsedTimeF32(); + F32 et = timer->mEventTimer.getElapsedTimeF32(); if (et > timer->mPeriod) { - timer->mTimer.reset(); + timer->mEventTimer.reset(); timer->tick(); } } diff --git a/indra/llcommon/lltimer.h b/indra/llcommon/lltimer.h index 69e786c50a..575bf9fc0b 100644 --- a/indra/llcommon/lltimer.h +++ b/indra/llcommon/lltimer.h @@ -131,7 +131,7 @@ public: static void updateClass(); protected: - LLTimer mTimer; + LLTimer mEventTimer; F32 mPeriod; private: diff --git a/indra/llmath/llrect.h b/indra/llmath/llrect.h index 92e415155b..12f702a197 100644 --- a/indra/llmath/llrect.h +++ b/indra/llmath/llrect.h @@ -193,47 +193,28 @@ public: mBottom = llmin(mBottom, mTop); } - friend const LLRectBase& operator|=(LLRectBase &a, const LLRectBase &b) // Return rect including a & b + void unionWith(const LLRectBase &other) { - a.mLeft = llmin(a.mLeft, b.mLeft); - a.mRight = llmax(a.mRight, b.mRight); - a.mBottom = llmin(a.mBottom, b.mBottom); - a.mTop = llmax(a.mTop, b.mTop); - return a; + mLeft = llmin(mLeft, other.mLeft); + mRight = llmax(mRight, other.mRight); + mBottom = llmin(mBottom, other.mBottom); + mTop = llmax(mTop, other.mTop); } - friend LLRectBase operator|(const LLRectBase &a, const LLRectBase &b) // Return rect including a & b + void intersectWith(const LLRectBase &other) { - LLRectBase<Type> result; - result.mLeft = llmin(a.mLeft, b.mLeft); - result.mRight = llmax(a.mRight, b.mRight); - result.mBottom = llmin(a.mBottom, b.mBottom); - result.mTop = llmax(a.mTop, b.mTop); - return result; - } - - friend const LLRectBase& operator&=(LLRectBase &a, const LLRectBase &b) // set a to rect where a intersects b - { - a.mLeft = llmax(a.mLeft, b.mLeft); - a.mRight = llmin(a.mRight, b.mRight); - a.mBottom = llmax(a.mBottom, b.mBottom); - a.mTop = llmin(a.mTop, b.mTop); - if (a.mLeft > a.mRight) + mLeft = llmax(mLeft, other.mLeft); + mRight = llmin(mRight, other.mRight); + mBottom = llmax(mBottom, other.mBottom); + mTop = llmin(mTop, other.mTop); + if (mLeft > mRight) { - a.mLeft = a.mRight; + mLeft = mRight; } - if (a.mBottom > a.mTop) + if (mBottom > mTop) { - a.mBottom = a.mTop; + mBottom = mTop; } - return a; - } - - friend LLRectBase operator&(const LLRectBase &a, const LLRectBase &b) // Return rect where a intersects b - { - LLRectBase result = a; - result &= b; - return result; } friend std::ostream &operator<<(std::ostream &s, const LLRectBase &rect) diff --git a/indra/llui/llbutton.cpp b/indra/llui/llbutton.cpp index d93748d069..bdf1c12b79 100644 --- a/indra/llui/llbutton.cpp +++ b/indra/llui/llbutton.cpp @@ -197,6 +197,9 @@ void LLButton::init(void (*click_callback)(void*), void *callback_data, const LL mHighlightColor = ( LLUI::sColorsGroup->getColor( "ButtonUnselectedFgColor" ) ); mUnselectedBgColor = ( LLUI::sColorsGroup->getColor( "ButtonUnselectedBgColor" ) ); mSelectedBgColor = ( LLUI::sColorsGroup->getColor( "ButtonSelectedBgColor" ) ); + + mImageOverlayAlignment = LLFontGL::HCENTER; + mImageOverlayColor = LLColor4::white; } LLButton::~LLButton() @@ -607,7 +610,7 @@ void LLButton::draw() // draw overlay image if (mImageOverlay.notNull()) { - const S32 IMG_PAD = 4; + const S32 IMG_PAD = 5; // get max width and height (discard level 0) S32 overlay_width = mImageOverlay->getWidth(0); S32 overlay_height = mImageOverlay->getHeight(0); @@ -628,7 +631,7 @@ void LLButton::draw() overlay_width, overlay_height, mImageOverlay, - LLColor4::white); + mImageOverlayColor); break; case LLFontGL::HCENTER: gl_draw_scaled_image( @@ -637,7 +640,7 @@ void LLButton::draw() overlay_width, overlay_height, mImageOverlay, - LLColor4::white); + mImageOverlayColor); break; case LLFontGL::RIGHT: gl_draw_scaled_image( @@ -646,7 +649,7 @@ void LLButton::draw() overlay_width, overlay_height, mImageOverlay, - LLColor4::white); + mImageOverlayColor); break; default: // draw nothing @@ -866,7 +869,7 @@ void LLButton::setHoverImages( const LLString& image_name, const LLString& selec setImageHoverSelected(selected_name); } -void LLButton::setImageOverlay(const LLString &image_name, LLFontGL::HAlign alignment) +void LLButton::setImageOverlay(const LLString &image_name, LLFontGL::HAlign alignment, const LLColor4& color) { if (image_name.empty()) { @@ -877,6 +880,7 @@ void LLButton::setImageOverlay(const LLString &image_name, LLFontGL::HAlign alig LLUUID overlay_image_id = LLUI::findAssetUUIDByName(image_name); mImageOverlay = LLUI::sImageProvider->getUIImageByID(overlay_image_id); mImageOverlayAlignment = alignment; + mImageOverlayColor = color; } } diff --git a/indra/llui/llbutton.h b/indra/llui/llbutton.h index ba77220a7b..a638b5be49 100644 --- a/indra/llui/llbutton.h +++ b/indra/llui/llbutton.h @@ -124,7 +124,7 @@ public: void setDisabledSelectedLabelColor( const LLColor4& c ) { mDisabledSelectedLabelColor = c; } - void setImageOverlay(const LLString &image_name, LLFontGL::HAlign alignment = LLFontGL::HCENTER); + void setImageOverlay(const LLString &image_name, LLFontGL::HAlign alignment = LLFontGL::HCENTER, const LLColor4& color = LLColor4::white); LLPointer<LLImageGL> getImageOverlay() { return mImageOverlay; } @@ -190,6 +190,7 @@ protected: LLPointer<LLImageGL> mImageOverlay; LLFontGL::HAlign mImageOverlayAlignment; + LLColor4 mImageOverlayColor; LLPointer<LLImageGL> mImageUnselected; LLUIString mUnselectedLabel; diff --git a/indra/llui/llctrlselectioninterface.h b/indra/llui/llctrlselectioninterface.h index 698d609593..9df7475a6c 100644 --- a/indra/llui/llctrlselectioninterface.h +++ b/indra/llui/llctrlselectioninterface.h @@ -31,6 +31,8 @@ public: virtual BOOL getCanSelect() const = 0; + virtual S32 getItemCount() const = 0; + virtual BOOL selectFirstItem() = 0; virtual BOOL selectNthItem( S32 index ) = 0; @@ -56,7 +58,6 @@ class LLCtrlListInterface : public LLCtrlSelectionInterface public: virtual ~LLCtrlListInterface(); - virtual S32 getItemCount() const = 0; virtual void addColumn(const LLSD& column, EAddPosition pos = ADD_BOTTOM) = 0; virtual void clearColumns() = 0; virtual void setColumnLabel(const LLString& column, const LLString& label) = 0; diff --git a/indra/llui/llfloater.cpp b/indra/llui/llfloater.cpp index 22acf46125..17b4dffa3f 100644 --- a/indra/llui/llfloater.cpp +++ b/indra/llui/llfloater.cpp @@ -292,28 +292,32 @@ void LLFloater::init(const LLString& title, { // Resize bars (sides) const S32 RESIZE_BAR_THICKNESS = 3; - mResizeBar[0] = new LLResizeBar( + mResizeBar[LLResizeBar::LEFT] = new LLResizeBar( "resizebar_left", + this, LLRect( 0, mRect.getHeight(), RESIZE_BAR_THICKNESS, 0), - min_width, min_height, LLResizeBar::LEFT ); + min_width, S32_MAX, LLResizeBar::LEFT ); addChild( mResizeBar[0] ); - mResizeBar[1] = new LLResizeBar( + mResizeBar[LLResizeBar::TOP] = new LLResizeBar( "resizebar_top", + this, LLRect( 0, mRect.getHeight(), mRect.getWidth(), mRect.getHeight() - RESIZE_BAR_THICKNESS), - min_width, min_height, LLResizeBar::TOP ); + min_height, S32_MAX, LLResizeBar::TOP ); addChild( mResizeBar[1] ); - mResizeBar[2] = new LLResizeBar( + mResizeBar[LLResizeBar::RIGHT] = new LLResizeBar( "resizebar_right", + this, LLRect( mRect.getWidth() - RESIZE_BAR_THICKNESS, mRect.getHeight(), mRect.getWidth(), 0), - min_width, min_height, LLResizeBar::RIGHT ); + min_width, S32_MAX, LLResizeBar::RIGHT ); addChild( mResizeBar[2] ); - mResizeBar[3] = new LLResizeBar( + mResizeBar[LLResizeBar::BOTTOM] = new LLResizeBar( "resizebar_bottom", + this, LLRect( 0, RESIZE_BAR_THICKNESS, mRect.getWidth(), 0), - min_width, min_height, LLResizeBar::BOTTOM ); + min_height, S32_MAX, LLResizeBar::BOTTOM ); addChild( mResizeBar[3] ); @@ -601,7 +605,14 @@ void LLFloater::setResizeLimits( S32 min_width, S32 min_height ) { if( mResizeBar[i] ) { - mResizeBar[i]->setResizeLimits( min_width, min_height ); + if (i == LLResizeBar::LEFT || i == LLResizeBar::RIGHT) + { + mResizeBar[i]->setResizeLimits( min_width, S32_MAX ); + } + else + { + mResizeBar[i]->setResizeLimits( min_height, S32_MAX ); + } } if( mResizeHandle[i] ) { @@ -653,6 +664,25 @@ const LLString& LLFloater::getTitle() const return mDragHandle ? mDragHandle->getTitle() : LLString::null; } +void LLFloater::setShortTitle( const LLString& short_title ) +{ + mShortTitle = short_title; +} + +LLString LLFloater::getShortTitle() +{ + if (mShortTitle.empty()) + { + return mDragHandle ? mDragHandle->getTitle() : LLString::null; + } + else + { + return mShortTitle; + } +} + + + BOOL LLFloater::canSnapTo(LLView* other_view) { if (NULL == other_view) @@ -991,12 +1021,22 @@ void LLFloater::setHost(LLMultiFloater* host) } } -void LLFloater::moveResizeHandleToFront() +void LLFloater::moveResizeHandlesToFront() { - // 0 is the bottom right - if( mResizeHandle[0] ) + for( S32 i = 0; i < 4; i++ ) { - sendChildToFront(mResizeHandle[0]); + if( mResizeBar[i] ) + { + sendChildToFront(mResizeBar[i]); + } + } + + for( S32 i = 0; i < 4; i++ ) + { + if( mResizeHandle[i] ) + { + sendChildToFront(mResizeHandle[i]); + } } } @@ -1193,6 +1233,10 @@ BOOL LLFloater::getEditModeEnabled() void LLFloater::show(LLFloater* floaterp) { if (floaterp) floaterp->open(); + if (floaterp->getHost()) + { + floaterp->getHost()->open(); + } } //static @@ -1206,7 +1250,7 @@ BOOL LLFloater::visible(LLFloater* floaterp) { if (floaterp) { - return floaterp->isInVisibleChain(); + return !floaterp->isMinimized() && floaterp->isInVisibleChain(); } return FALSE; } @@ -1233,12 +1277,15 @@ void LLFloater::onClickTearOff(void *userdata) // reparent to floater view gFloaterView->addChild(self); - new_rect.setLeftTopAndSize(host_floater->getRect().mLeft + 5, host_floater->getRect().mTop - LLFLOATER_HEADER_SIZE - 5, self->mRect.getWidth(), self->mRect.getHeight()); - self->open(); /* Flawfinder: ignore */ - self->setRect(new_rect); + + // only force position for floaters that don't have that data saved + if (self->mRectControl.empty()) + { + new_rect.setLeftTopAndSize(host_floater->getRect().mLeft + 5, host_floater->getRect().mTop - LLFLOATER_HEADER_SIZE - 5, self->mRect.getWidth(), self->mRect.getHeight()); + self->setRect(new_rect); + } gFloaterView->adjustToFitScreen(self, FALSE); - self->setCanDrag(TRUE); // give focus to new window to keep continuity for the user self->setFocus(TRUE); } @@ -1248,6 +1295,8 @@ void LLFloater::onClickTearOff(void *userdata) if (new_host) { new_host->showFloater(self); + // make sure host is visible + new_host->open(); } } } @@ -1499,26 +1548,30 @@ void LLFloater::setCanResize(BOOL can_resize) const S32 RESIZE_BAR_THICKNESS = 3; mResizeBar[0] = new LLResizeBar( "resizebar_left", + this, LLRect( 0, mRect.getHeight(), RESIZE_BAR_THICKNESS, 0), - mMinWidth, mMinHeight, LLResizeBar::LEFT ); + mMinWidth, S32_MAX, LLResizeBar::LEFT ); addChild( mResizeBar[0] ); mResizeBar[1] = new LLResizeBar( "resizebar_top", + this, LLRect( 0, mRect.getHeight(), mRect.getWidth(), mRect.getHeight() - RESIZE_BAR_THICKNESS), - mMinWidth, mMinHeight, LLResizeBar::TOP ); + mMinHeight, S32_MAX, LLResizeBar::TOP ); addChild( mResizeBar[1] ); mResizeBar[2] = new LLResizeBar( "resizebar_right", + this, LLRect( mRect.getWidth() - RESIZE_BAR_THICKNESS, mRect.getHeight(), mRect.getWidth(), 0), - mMinWidth, mMinHeight, LLResizeBar::RIGHT ); + mMinWidth, S32_MAX, LLResizeBar::RIGHT ); addChild( mResizeBar[2] ); mResizeBar[3] = new LLResizeBar( "resizebar_bottom", + this, LLRect( 0, RESIZE_BAR_THICKNESS, mRect.getWidth(), 0), - mMinWidth, mMinHeight, LLResizeBar::BOTTOM ); + mMinHeight, S32_MAX, LLResizeBar::BOTTOM ); addChild( mResizeBar[3] ); @@ -1855,7 +1908,7 @@ LLRect LLFloaterView::findNeighboringPosition( LLFloater* reference_floater, LLF sibling->getVisible() && expanded_base_rect.rectInRect(&sibling->getRect())) { - base_rect |= sibling->getRect(); + base_rect.unionWith(sibling->getRect()); } } @@ -2550,18 +2603,22 @@ BOOL LLMultiFloater::closeAllFloaters() return TRUE; //else all tabs were successfully closed... } -void LLMultiFloater::growToFit(LLFloater* floaterp, S32 width, S32 height) +void LLMultiFloater::growToFit(S32 content_width, S32 content_height) { - floater_data_map_t::iterator found_data_it; - found_data_it = mFloaterDataMap.find(floaterp->getHandle()); - if (found_data_it != mFloaterDataMap.end()) + S32 new_width = llmax(mRect.getWidth(), content_width + LLPANEL_BORDER_WIDTH * 2); + S32 new_height = llmax(mRect.getHeight(), content_height + LLFLOATER_HEADER_SIZE + TABCNTR_HEADER_HEIGHT); + + if (isMinimized()) { - // store new width and height with this floater so that it will keep its size when detached - found_data_it->second.mWidth = width; - found_data_it->second.mHeight = height; + mPreviousRect.setLeftTopAndSize(mPreviousRect.mLeft, mPreviousRect.mTop, new_width, new_height); + } + else + { + S32 old_height = mRect.getHeight(); + reshape(new_width, new_height); + // keep top left corner in same position + translate(0, old_height - new_height); } - - resizeToContents(); } /** @@ -2618,12 +2675,18 @@ void LLMultiFloater::addFloater(LLFloater* floaterp, BOOL select_added_floater, floaterp->setCanMinimize(FALSE); floaterp->setCanResize(FALSE); floaterp->setCanDrag(FALSE); + floaterp->storeRectControl(); + + if (mAutoResize) + { + growToFit(floater_data.mWidth, floater_data.mHeight); + } //add the panel, add it to proper maps - mTabContainer->addTabPanel(floaterp, floaterp->getTitle(), FALSE, onTabSelected, this, 0, FALSE, insertion_point); + mTabContainer->addTabPanel(floaterp, floaterp->getShortTitle(), FALSE, onTabSelected, this, 0, FALSE, insertion_point); mFloaterDataMap[floaterp->getHandle()] = floater_data; - resizeToContents(); + updateResizeLimits(); if ( select_added_floater ) { @@ -2673,7 +2736,6 @@ void LLMultiFloater::showFloater(LLFloater* floaterp) { addFloater(floaterp, TRUE); } - setVisibleAndFrontmost(); } void LLMultiFloater::removeFloater(LLFloater* floaterp) @@ -2696,9 +2758,11 @@ void LLMultiFloater::removeFloater(LLFloater* floaterp) } mTabContainer->removeTabPanel(floaterp); floaterp->setBackgroundVisible(TRUE); + floaterp->setCanDrag(TRUE); floaterp->setHost(NULL); + floaterp->applyRectControl(); - resizeToContents(); + updateResizeLimits(); tabOpen((LLFloater*)mTabContainer->getCurrentPanel(), false); } @@ -2840,18 +2904,8 @@ BOOL LLMultiFloater::postBuild() return FALSE; } -void LLMultiFloater::resizeToContents() +void LLMultiFloater::updateResizeLimits() { - // we're already in the middle of a reshape, don't interrupt it - floater_data_map_t::iterator floater_it; - S32 new_width = 0; - S32 new_height = 0; - for (floater_it = mFloaterDataMap.begin(); floater_it != mFloaterDataMap.end(); ++floater_it) - { - new_width = llmax(new_width, floater_it->second.mWidth + LLPANEL_BORDER_WIDTH * 2); - new_height = llmax(new_height, floater_it->second.mHeight + LLFLOATER_HEADER_SIZE + TABCNTR_HEADER_HEIGHT); - } - S32 new_min_width = 0; S32 new_min_height = 0; S32 tab_idx; @@ -2867,21 +2921,23 @@ void LLMultiFloater::resizeToContents() setResizeLimits(new_min_width, new_min_height); S32 cur_height = mRect.getHeight(); + S32 new_width = llmax(mRect.getWidth(), new_min_width); + S32 new_height = llmax(mRect.getHeight(), new_min_height); - if (mAutoResize) + if (isMinimized()) { - reshape(new_width, new_height); + mPreviousRect.setLeftTopAndSize(mPreviousRect.mLeft, mPreviousRect.mTop, llmax(mPreviousRect.getWidth(), new_width), llmax(mPreviousRect.getHeight(), new_height)); } else { - reshape(llmax(new_min_width, mRect.getWidth()), llmax(new_min_height, mRect.getHeight())); - } + reshape(new_width, new_height); - // make sure upper left corner doesn't move - translate(0, cur_height - mRect.getHeight()); + // make sure upper left corner doesn't move + translate(0, cur_height - mRect.getHeight()); - // Try to keep whole view onscreen, don't allow partial offscreen. - gFloaterView->adjustToFitScreen(this, FALSE); + // Try to keep whole view onscreen, don't allow partial offscreen. + gFloaterView->adjustToFitScreen(this, FALSE); + } } // virtual @@ -2937,6 +2993,7 @@ void LLFloater::initFloaterXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactor { LLString name(getName()); LLString title(getTitle()); + LLString short_title(getShortTitle()); LLString rect_control(""); BOOL resizable = isResizable(); S32 min_width = getMinWidth(); @@ -2948,6 +3005,7 @@ void LLFloater::initFloaterXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactor node->getAttributeString("name", name); node->getAttributeString("title", title); + node->getAttributeString("short_title", short_title); node->getAttributeString("rect_control", rect_control); node->getAttributeBOOL("can_resize", resizable); node->getAttributeBOOL("can_minimize", minimizable); @@ -2974,6 +3032,8 @@ void LLFloater::initFloaterXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactor minimizable, close_btn); + setShortTitle(short_title); + BOOL can_tear_off; if (node->getAttributeBOOL("can_tear_off", can_tear_off)) { @@ -2988,17 +3048,13 @@ void LLFloater::initFloaterXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactor LLFloater::setFloaterHost((LLMultiFloater*) this); } - LLXMLNodePtr child; - for (child = node->getFirstChild(); child.notNull(); child = child->getNextSibling()) - { - factory->createWidget(this, child); - } + initChildrenXML(node, factory); + if (node->hasName("multi_floater")) { LLFloater::setFloaterHost(last_host); } - BOOL result = postBuild(); if (!result) @@ -3011,4 +3067,6 @@ void LLFloater::initFloaterXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactor { this->open(); /* Flawfinder: ignore */ } + + moveResizeHandlesToFront(); } diff --git a/indra/llui/llfloater.h b/indra/llui/llfloater.h index 1491f75683..942ae34d27 100644 --- a/indra/llui/llfloater.h +++ b/indra/llui/llfloater.h @@ -123,8 +123,10 @@ public: void setTitle( const LLString& title ); const LLString& getTitle() const; + void setShortTitle( const LLString& short_title ); + LLString getShortTitle(); virtual void setMinimized(BOOL b); - void moveResizeHandleToFront(); + void moveResizeHandlesToFront(); void addDependentFloater(LLFloater* dependent, BOOL reposition = TRUE); void addDependentFloater(LLViewHandle dependent_handle, BOOL reposition = TRUE); LLFloater* getDependee() { return (LLFloater*)LLFloater::getFloaterByHandle(mDependeeHandle); } @@ -222,6 +224,7 @@ protected: LLRect mPreviousRect; BOOL mForeground; LLViewHandle mDependeeHandle; + LLString mShortTitle; BOOL mFirstLook; // TRUE if the _next_ time this floater is visible will be the first time in the session that it is visible. @@ -351,12 +354,11 @@ public: /*virtual*/ void draw(); /*virtual*/ void setVisible(BOOL visible); /*virtual*/ BOOL handleKeyHere(KEY key, MASK mask, BOOL called_from_parent); - /*virtual*/ EWidgetType getWidgetType() const; /*virtual*/ LLString getWidgetTag() const; virtual void setCanResize(BOOL can_resize); - virtual void growToFit(LLFloater* floaterp, S32 width, S32 height); + virtual void growToFit(S32 content_width, S32 content_height); virtual void addFloater(LLFloater* floaterp, BOOL select_added_floater, LLTabContainerCommon::eInsertionPoint insertion_point = LLTabContainerCommon::END); virtual void showFloater(LLFloater* floaterp); @@ -378,7 +380,7 @@ public: void setTabContainer(LLTabContainerCommon* tab_container) { if (!mTabContainer) mTabContainer = tab_container; } static void onTabSelected(void* userdata, bool); - virtual void resizeToContents(); + virtual void updateResizeLimits(); protected: struct LLFloaterData diff --git a/indra/llui/llfocusmgr.cpp b/indra/llui/llfocusmgr.cpp index da53e3d104..5c8eb8b4af 100644 --- a/indra/llui/llfocusmgr.cpp +++ b/indra/llui/llfocusmgr.cpp @@ -132,8 +132,7 @@ void LLFocusMgr::setKeyboardFocus(LLUICtrl* new_focus, FocusLostCallback on_focu if (lock) { - mLockedView = mKeyboardFocus; - mKeyboardLockedFocusLostCallback = on_focus_lost; + lockFocus(); } } @@ -292,6 +291,12 @@ void LLFocusMgr::removeTopCtrlWithoutCallback( LLUICtrl* top_view ) } } +void LLFocusMgr::lockFocus() +{ + mLockedView = mKeyboardFocus; + mKeyboardLockedFocusLostCallback = mKeyboardFocusLostCallback; +} + void LLFocusMgr::unlockFocus() { mLockedView = NULL; diff --git a/indra/llui/llfocusmgr.h b/indra/llui/llfocusmgr.h index dc8025d4c0..e764f5459c 100644 --- a/indra/llui/llfocusmgr.h +++ b/indra/llui/llfocusmgr.h @@ -61,6 +61,7 @@ public: // All Three void releaseFocusIfNeeded( LLView* top_view ); + void lockFocus(); void unlockFocus(); BOOL focusLocked() { return mLockedView != NULL; } diff --git a/indra/llui/llmenugl.cpp b/indra/llui/llmenugl.cpp index d7ccfe1daa..52fdf3cd5a 100644 --- a/indra/llui/llmenugl.cpp +++ b/indra/llui/llmenugl.cpp @@ -1391,9 +1391,9 @@ void LLMenuItemBranchGL::updateBranchParent(LLView* parentp) } } -void LLMenuItemBranchGL::onVisibilityChange( BOOL curVisibilityIn ) +void LLMenuItemBranchGL::onVisibilityChange( BOOL new_visibility ) { - if (curVisibilityIn == FALSE && mBranch->getVisible() && !mBranch->getTornOff()) + if (new_visibility == FALSE && !mBranch->getTornOff()) { mBranch->setVisible(FALSE); } diff --git a/indra/llui/llpanel.cpp b/indra/llui/llpanel.cpp index 37feffd4b0..c130214b99 100644 --- a/indra/llui/llpanel.cpp +++ b/indra/llui/llpanel.cpp @@ -31,9 +31,18 @@ #include "llviewborder.h" #include "llbutton.h" +// LLLayoutStack +#include "llgl.h" +#include "llglheaders.h" +#include "llresizebar.h" +#include "llcriticaldamp.h" + LLPanel::panel_map_t LLPanel::sPanelMap; LLPanel::alert_queue_t LLPanel::sAlertQueue; +const S32 RESIZE_BAR_OVERLAP = 1; +const S32 PANEL_STACK_GAP = RESIZE_BAR_HEIGHT; + void LLPanel::init() { // mRectControl @@ -99,13 +108,16 @@ void LLPanel::addBorder(LLViewBorder::EBevel border_bevel, addChild( mBorder ); } +void LLPanel::removeBorder() +{ + delete mBorder; + mBorder = NULL; +} + LLPanel::~LLPanel() { - if( !mRectControl.empty() ) - { - LLUI::sConfigGroup->setRect( mRectControl, mRect ); - } + storeRectControl(); sPanelMap.erase(mViewHandle); } @@ -159,44 +171,41 @@ void LLPanel::setCtrlsEnabled( BOOL b ) void LLPanel::draw() { - if( getVisible() ) + // draw background + if( mBgVisible ) { - // draw background - if( mBgVisible ) - { - //RN: I don't see the point of this - S32 left = 0;//LLPANEL_BORDER_WIDTH; - S32 top = mRect.getHeight();// - LLPANEL_BORDER_WIDTH; - S32 right = mRect.getWidth();// - LLPANEL_BORDER_WIDTH; - S32 bottom = 0;//LLPANEL_BORDER_WIDTH; + //RN: I don't see the point of this + S32 left = 0;//LLPANEL_BORDER_WIDTH; + S32 top = mRect.getHeight();// - LLPANEL_BORDER_WIDTH; + S32 right = mRect.getWidth();// - LLPANEL_BORDER_WIDTH; + S32 bottom = 0;//LLPANEL_BORDER_WIDTH; - if (mBgOpaque ) - { - gl_rect_2d( left, top, right, bottom, mBgColorOpaque ); - } - else - { - gl_rect_2d( left, top, right, bottom, mBgColorAlpha ); - } + if (mBgOpaque ) + { + gl_rect_2d( left, top, right, bottom, mBgColorOpaque ); } - - if( mDefaultBtn) + else { - if (gFocusMgr.childHasKeyboardFocus( this ) && mDefaultBtn->getEnabled()) - { - LLUICtrl* focus_ctrl = gFocusMgr.getKeyboardFocus(); - BOOL focus_is_child_button = focus_ctrl->getWidgetType() == WIDGET_TYPE_BUTTON && static_cast<LLButton *>(focus_ctrl)->getCommitOnReturn(); - // only enable default button when current focus is not a return-capturing button - mDefaultBtn->setBorderEnabled(!focus_is_child_button); - } - else - { - mDefaultBtn->setBorderEnabled(FALSE); - } + gl_rect_2d( left, top, right, bottom, mBgColorAlpha ); } + } - LLView::draw(); + if( mDefaultBtn) + { + if (gFocusMgr.childHasKeyboardFocus( this ) && mDefaultBtn->getEnabled()) + { + LLUICtrl* focus_ctrl = gFocusMgr.getKeyboardFocus(); + BOOL focus_is_child_button = focus_ctrl->getWidgetType() == WIDGET_TYPE_BUTTON && static_cast<LLButton *>(focus_ctrl)->getCommitOnReturn(); + // only enable default button when current focus is not a return-capturing button + mDefaultBtn->setBorderEnabled(!focus_is_child_button); + } + else + { + mDefaultBtn->setBorderEnabled(FALSE); + } } + + LLView::draw(); } void LLPanel::refresh() @@ -552,7 +561,7 @@ LLXMLNodePtr LLPanel::getXML(bool save_children) const return node; } -LLView* LLPanel::fromXML(LLXMLNodePtr node, LLView* parentp, LLUICtrlFactory *factory) +LLView* LLPanel::fromXML(LLXMLNodePtr node, LLView* parent, LLUICtrlFactory *factory) { LLString name("panel"); node->getAttributeString("name", name); @@ -561,11 +570,21 @@ LLView* LLPanel::fromXML(LLXMLNodePtr node, LLView* parentp, LLUICtrlFactory *fa // Fall back on a default panel, if there was no special factory. if (!panelp) { - panelp = new LLPanel("tab panel"); + LLRect rect; + createRect(node, rect, parent, LLRect()); + panelp = new LLPanel(name, rect); + panelp->initPanelXML(node, parent, factory); + // preserve panel's width and height, but override the location + const LLRect& panelrect = panelp->getRect(); + S32 w = panelrect.getWidth(); + S32 h = panelrect.getHeight(); + rect.setLeftTopAndSize(rect.mLeft, rect.mTop, w, h); + panelp->setRect(rect); + } + else + { + panelp->initPanelXML(node, parent, factory); } - - panelp->initPanelXML(node, parentp, factory); - return panelp; } @@ -577,11 +596,7 @@ BOOL LLPanel::initPanelXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *f setPanelParameters(node, parent); - LLXMLNodePtr child; - for (child = node->getFirstChild(); child.notNull(); child = child->getNextSibling()) - { - factory->createWidget(this, child); - } + initChildrenXML(node, factory); LLString xml_filename; node->getAttributeString("filename", xml_filename); @@ -590,8 +605,16 @@ BOOL LLPanel::initPanelXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *f if (!xml_filename.empty()) { + // Preserve postion of embedded panel but allow panel to dictate width/height + LLRect rect(getRect()); didPost = factory->buildPanel(this, xml_filename, NULL); - } else { + S32 w = getRect().getWidth(); + S32 h = getRect().getHeight(); + rect.setLeftTopAndSize(rect.mLeft, rect.mTop, w, h); + setRect(rect); + } + else + { didPost = FALSE; } @@ -604,10 +627,32 @@ BOOL LLPanel::initPanelXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *f return didPost; } -void LLPanel::setPanelParameters(LLXMLNodePtr node, LLView* parentp) +void LLPanel::initChildrenXML(LLXMLNodePtr node, LLUICtrlFactory* factory) +{ + LLXMLNodePtr child; + for (child = node->getFirstChild(); child.notNull(); child = child->getNextSibling()) + { + // look for string declarations for programmatic text + if (child->hasName("string")) + { + LLString string_name; + child->getAttributeString("name", string_name); + if (!string_name.empty()) + { + mUIStrings[string_name] = LLUIString(child->getTextContents()); + } + } + else + { + factory->createWidget(this, child); + } + } +} + +void LLPanel::setPanelParameters(LLXMLNodePtr node, LLView* parent) { /////// Rect, follows, tool_tip, enabled, visible attributes /////// - initFromXML(node, parentp); + initFromXML(node, parent); /////// Border attributes /////// BOOL border = FALSE; @@ -632,6 +677,10 @@ void LLPanel::setPanelParameters(LLXMLNodePtr node, LLView* parentp) addBorder(bevel_style, border_style, border_thickness); } + else + { + removeBorder(); + } /////// Background attributes /////// BOOL background_visible = FALSE; @@ -656,6 +705,30 @@ void LLPanel::setPanelParameters(LLXMLNodePtr node, LLView* parentp) setLabel(label); } +LLString LLPanel::getFormattedUIString(const LLString& name, const LLString::format_map_t& args) const +{ + ui_string_map_t::const_iterator found_it = mUIStrings.find(name); + if (found_it != mUIStrings.end()) + { + // make a copy as format works in place + LLUIString formatted_string = found_it->second; + formatted_string.setArgList(args); + return formatted_string.getString(); + } + return LLString::null; +} + +LLUIString LLPanel::getUIString(const LLString& name) const +{ + ui_string_map_t::const_iterator found_it = mUIStrings.find(name); + if (found_it != mUIStrings.end()) + { + return found_it->second; + } + return LLUIString(LLString::null); +} + + void LLPanel::childSetVisible(const LLString& id, bool visible) { LLView* child = getChildByName(id, true); @@ -1045,3 +1118,493 @@ void LLPanel::childDisplayNotFound() LLAlertDialog::showXml("FloaterNotFound", args); } +void LLPanel::storeRectControl() +{ + if( !mRectControl.empty() ) + { + LLUI::sConfigGroup->setRect( mRectControl, mRect ); + } +} + + +// +// LLLayoutStack +// +struct LLLayoutStack::LLEmbeddedPanel +{ + LLEmbeddedPanel(LLPanel* panelp, eLayoutOrientation orientation, S32 min_width, S32 min_height, BOOL auto_resize) : + mPanel(panelp), + mMinWidth(min_width), + mMinHeight(min_height), + mAutoResize(auto_resize), + mOrientation(orientation), + mVisibleAmt(1.f) // default to fully visible + { + LLResizeBar::Side side = (orientation == HORIZONTAL) ? LLResizeBar::RIGHT : LLResizeBar::BOTTOM; + LLRect resize_bar_rect = panelp->getRect(); + + S32 min_dim; + if (orientation == HORIZONTAL) + { + min_dim = mMinHeight; + } + else + { + min_dim = mMinWidth; + } + mResizeBar = new LLResizeBar("resizer", mPanel, LLRect(), min_dim, S32_MAX, side); + mResizeBar->setEnableSnapping(FALSE); + // panels initialized as hidden should not start out partially visible + if (!mPanel->getVisible()) + { + mVisibleAmt = 0.f; + } + } + + LLPanel* mPanel; + S32 mMinWidth; + S32 mMinHeight; + BOOL mAutoResize; + LLResizeBar* mResizeBar; + eLayoutOrientation mOrientation; + F32 mVisibleAmt; +}; + +LLLayoutStack::LLLayoutStack(eLayoutOrientation orientation) : + mOrientation(orientation), + mMinWidth(0), + mMinHeight(0) +{ +} + +LLLayoutStack::~LLLayoutStack() +{ +} + +void LLLayoutStack::draw() +{ + updateLayout(); + { + // clip if outside nominal bounds + LLLocalClipRect clip(getLocalRect(), mRect.getWidth() > mMinWidth || mRect.getHeight() > mMinHeight); + e_panel_list_t::iterator panel_it; + for (panel_it = mPanels.begin(); panel_it != mPanels.end(); ++panel_it) + { + LLRect clip_rect = (*panel_it)->mPanel->getRect(); + // scale clipping rectangle by visible amount + if (mOrientation == HORIZONTAL) + { + clip_rect.mRight = clip_rect.mLeft + llround(clip_rect.getWidth() * (*panel_it)->mVisibleAmt); + } + else + { + clip_rect.mBottom = clip_rect.mTop - llround(clip_rect.getHeight() * (*panel_it)->mVisibleAmt); + } + LLLocalClipRect clip(clip_rect, (*panel_it)->mVisibleAmt < 1.f); + // only force drawing invisible children if visible amount is non-zero + drawChild((*panel_it)->mPanel, 0, 0, (*panel_it)->mVisibleAmt > 0.f); + } + } +} + +void LLLayoutStack::removeCtrl(LLUICtrl* ctrl) +{ + LLEmbeddedPanel* embedded_panelp = findEmbeddedPanel((LLPanel*)ctrl); + + if (embedded_panelp) + { + mPanels.erase(std::find(mPanels.begin(), mPanels.end(), embedded_panelp)); + delete embedded_panelp; + } + + calcMinExtents(); + + LLView::removeCtrl(ctrl); +} + +void LLLayoutStack::reshape(S32 width, S32 height, BOOL called_from_parent) +{ + LLView::reshape(width, height, called_from_parent); + //updateLayout(); +} + +LLXMLNodePtr LLLayoutStack::getXML(bool save_children) const +{ + LLXMLNodePtr node = LLView::getXML(); + return node; +} + +//static +LLView* LLLayoutStack::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) +{ + LLString orientation_string("vertical"); + node->getAttributeString("orientation", orientation_string); + + eLayoutOrientation orientation = VERTICAL; + + if (orientation_string == "horizontal") + { + orientation = HORIZONTAL; + } + else if (orientation_string == "vertical") + { + orientation = VERTICAL; + } + else + { + llwarns << "Unknown orientation " << orientation_string << ", using vertical" << llendl; + } + + LLLayoutStack* layout_stackp = new LLLayoutStack(orientation); + + layout_stackp->initFromXML(node, parent); + + LLXMLNodePtr child; + for (child = node->getFirstChild(); child.notNull(); child = child->getNextSibling()) + { + if (child->hasName("layout_panel")) + { + S32 min_width = 0; + S32 min_height = 0; + BOOL auto_resize = TRUE; + + child->getAttributeS32("min_width", min_width); + child->getAttributeS32("min_height", min_height); + child->getAttributeBOOL("auto_resize", auto_resize); + + LLPanel* panelp = (LLPanel*)LLPanel::fromXML(child, layout_stackp, factory); + panelp->setFollowsNone(); + if (panelp) + { + layout_stackp->addPanel(panelp, min_width, min_height, auto_resize); + } + } + } + + return layout_stackp; +} + +S32 LLLayoutStack::getMinWidth() +{ + return mMinWidth; +} + +S32 LLLayoutStack::getMinHeight() +{ + return mMinHeight; +} + +void LLLayoutStack::addPanel(LLPanel* panel, S32 min_width, S32 min_height, BOOL auto_resize, S32 index) +{ + LLEmbeddedPanel* embedded_panel = new LLEmbeddedPanel(panel, mOrientation, min_width, min_height, auto_resize); + + mPanels.insert(mPanels.begin() + llclamp(index, 0, (S32)mPanels.size()), embedded_panel); + addChild(panel); + addChild(embedded_panel->mResizeBar); + + // bring all resize bars to the front so that they are clickable even over the panels + // with a bit of overlap + for (e_panel_list_t::iterator panel_it = mPanels.begin(); panel_it != mPanels.end(); ++panel_it) + { + e_panel_list_t::iterator next_it = panel_it; + ++next_it; + + LLResizeBar* resize_barp = (*panel_it)->mResizeBar; + sendChildToFront(resize_barp); + // last resize bar is disabled, since its not between any two panels + if ( next_it == mPanels.end() ) + { + resize_barp->setEnabled(FALSE); + } + else + { + resize_barp->setEnabled(TRUE); + } + } + + //updateLayout(); +} + +void LLLayoutStack::removePanel(LLPanel* panel) +{ + removeChild(panel); + //updateLayout(); +} + +void LLLayoutStack::updateLayout(BOOL force_resize) +{ + calcMinExtents(); + + // calculate current extents + S32 cur_width = 0; + S32 cur_height = 0; + + const F32 ANIM_OPEN_TIME = 0.02f; + const F32 ANIM_CLOSE_TIME = 0.02f; + + e_panel_list_t::iterator panel_it; + for (panel_it = mPanels.begin(); panel_it != mPanels.end(); ++panel_it) + { + LLPanel* panelp = (*panel_it)->mPanel; + if (panelp->getVisible()) + { + (*panel_it)->mVisibleAmt = lerp((*panel_it)->mVisibleAmt, 1.f, LLCriticalDamp::getInterpolant(ANIM_OPEN_TIME)); + if ((*panel_it)->mVisibleAmt > 0.99f) + { + (*panel_it)->mVisibleAmt = 1.f; + } + } + else // not visible + { + (*panel_it)->mVisibleAmt = lerp((*panel_it)->mVisibleAmt, 0.f, LLCriticalDamp::getInterpolant(ANIM_CLOSE_TIME)); + if ((*panel_it)->mVisibleAmt < 0.001f) + { + (*panel_it)->mVisibleAmt = 0.f; + } + } + if (mOrientation == HORIZONTAL) + { + // all panels get expanded to max of all the minimum dimensions + cur_height = llmax(mMinHeight, panelp->getRect().getHeight()); + cur_width += llround(panelp->getRect().getWidth() * (*panel_it)->mVisibleAmt); + if (panel_it != mPanels.end()) + { + cur_width += PANEL_STACK_GAP; + } + } + else //VERTICAL + { + cur_width = llmax(mMinWidth, panelp->getRect().getWidth()); + cur_height += llround(panelp->getRect().getHeight() * (*panel_it)->mVisibleAmt); + if (panel_it != mPanels.end()) + { + cur_height += PANEL_STACK_GAP; + } + } + } + + S32 num_resizable_panels = 0; + S32 shrink_headroom_available = 0; + S32 shrink_headroom_total = 0; + for (panel_it = mPanels.begin(); panel_it != mPanels.end(); ++panel_it) + { + // panels that are not fully visible do not count towards shrink headroom + if ((*panel_it)->mVisibleAmt < 1.f) + continue; + // if currently resizing a panel or the panel is flagged as not automatically resizing + // only track total available headroom, but don't use it for automatic resize logic + if ((*panel_it)->mResizeBar->hasMouseCapture() || (!(*panel_it)->mAutoResize && !force_resize)) + { + if (mOrientation == HORIZONTAL) + { + shrink_headroom_total += (*panel_it)->mPanel->getRect().getWidth() - (*panel_it)->mMinWidth; + } + else //VERTICAL + { + shrink_headroom_total += (*panel_it)->mPanel->getRect().getHeight() - (*panel_it)->mMinHeight; + } + } + else + { + num_resizable_panels++; + if (mOrientation == HORIZONTAL) + { + shrink_headroom_available += (*panel_it)->mPanel->getRect().getWidth() - (*panel_it)->mMinWidth; + shrink_headroom_total += (*panel_it)->mPanel->getRect().getWidth() - (*panel_it)->mMinWidth; + } + else //VERTICAL + { + shrink_headroom_available += (*panel_it)->mPanel->getRect().getHeight() - (*panel_it)->mMinHeight; + shrink_headroom_total += (*panel_it)->mPanel->getRect().getHeight() - (*panel_it)->mMinHeight; + } + } + } + + // positive means panels need to grow, negative means shrink + S32 pixels_to_distribute; + if (mOrientation == HORIZONTAL) + { + pixels_to_distribute = mRect.getWidth() - cur_width; + } + else //VERTICAL + { + pixels_to_distribute = mRect.getHeight() - cur_height; + } + + S32 cur_x = 0; + S32 cur_y = mRect.getHeight(); + + for (panel_it = mPanels.begin(); panel_it != mPanels.end(); ++panel_it) + { + LLPanel* panelp = (*panel_it)->mPanel; + + S32 cur_width = panelp->getRect().getWidth(); + S32 cur_height = panelp->getRect().getHeight(); + S32 new_width = llmax((*panel_it)->mMinWidth, cur_width); + S32 new_height = llmax((*panel_it)->mMinHeight, cur_height); + + S32 delta_size = 0; + + // if panel can automatically resize (not animating, and resize flag set)... + if ((*panel_it)->mVisibleAmt == 1.f && (force_resize || (*panel_it)->mAutoResize) && !(*panel_it)->mResizeBar->hasMouseCapture()) + { + if (mOrientation == HORIZONTAL) + { + // if we're shrinking + if (pixels_to_distribute < 0) + { + // shrink proportionally to amount over minimum + delta_size = llround((F32)pixels_to_distribute * (F32)(cur_width - (*panel_it)->mMinWidth) / (F32)shrink_headroom_available); + } + else + { + // grow all elements equally + delta_size = llround((F32)pixels_to_distribute / (F32)num_resizable_panels); + } + new_width = llmax((*panel_it)->mMinWidth, panelp->getRect().getWidth() + delta_size); + } + else + { + new_width = llmax(mMinWidth, mRect.getWidth()); + } + + if (mOrientation == VERTICAL) + { + if (pixels_to_distribute < 0) + { + // shrink proportionally to amount over minimum + delta_size = llround((F32)pixels_to_distribute * (F32)(cur_height - (*panel_it)->mMinHeight) / (F32)shrink_headroom_available); + } + else + { + delta_size = llround((F32)pixels_to_distribute / (F32)num_resizable_panels); + } + new_height = llmax((*panel_it)->mMinHeight, panelp->getRect().getHeight() + delta_size); + } + else + { + new_height = llmax(mMinHeight, mRect.getHeight()); + } + } + else // don't resize + { + if (mOrientation == HORIZONTAL) + { + new_height = llmax(mMinHeight, mRect.getHeight()); + } + else // VERTICAL + { + new_width = llmax(mMinWidth, mRect.getWidth()); + } + } + + // adjust running headroom count based on new sizes + shrink_headroom_total += delta_size; + + panelp->reshape(new_width, new_height); + panelp->setOrigin(cur_x, cur_y - new_height); + + LLRect panel_rect = panelp->getRect(); + LLRect resize_bar_rect = panel_rect; + if (mOrientation == HORIZONTAL) + { + resize_bar_rect.mLeft = panel_rect.mRight - RESIZE_BAR_OVERLAP; + resize_bar_rect.mRight = panel_rect.mRight + PANEL_STACK_GAP + RESIZE_BAR_OVERLAP; + } + else + { + resize_bar_rect.mTop = panel_rect.mBottom + RESIZE_BAR_OVERLAP; + resize_bar_rect.mBottom = panel_rect.mBottom - PANEL_STACK_GAP - RESIZE_BAR_OVERLAP; + } + (*panel_it)->mResizeBar->setRect(resize_bar_rect); + + if (mOrientation == HORIZONTAL) + { + cur_x += llround(new_width * (*panel_it)->mVisibleAmt) + PANEL_STACK_GAP; + } + else //VERTICAL + { + cur_y -= llround(new_height * (*panel_it)->mVisibleAmt) + PANEL_STACK_GAP; + } + } + + // update resize bars with new limits + LLResizeBar* last_resize_bar = NULL; + for (panel_it = mPanels.begin(); panel_it != mPanels.end(); ++panel_it) + { + LLPanel* panelp = (*panel_it)->mPanel; + + if (mOrientation == HORIZONTAL) + { + (*panel_it)->mResizeBar->setResizeLimits((*panel_it)->mMinWidth, (*panel_it)->mMinWidth + shrink_headroom_total); + } + else //VERTICAL + { + (*panel_it)->mResizeBar->setResizeLimits((*panel_it)->mMinHeight, (*panel_it)->mMinHeight + shrink_headroom_total); + } + // hide resize bars for invisible panels + (*panel_it)->mResizeBar->setVisible(panelp->getVisible()); + if (panelp->getVisible()) + { + last_resize_bar = (*panel_it)->mResizeBar; + } + } + + // hide last resize bar as there is nothing past it + if (last_resize_bar) + { + last_resize_bar->setVisible(FALSE); + } + + // not enough room to fit existing contents + if (!force_resize && + ((cur_y != -PANEL_STACK_GAP) || (cur_x != mRect.getWidth() + PANEL_STACK_GAP))) + { + // do another layout pass with all stacked elements contributing + // even those that don't usually resize + llassert_always(force_resize == FALSE); + updateLayout(TRUE); + } +} + +LLLayoutStack::LLEmbeddedPanel* LLLayoutStack::findEmbeddedPanel(LLPanel* panelp) +{ + e_panel_list_t::iterator panel_it; + for (panel_it = mPanels.begin(); panel_it != mPanels.end(); ++panel_it) + { + if ((*panel_it)->mPanel == panelp) + { + return *panel_it; + } + } + return NULL; +} + +void LLLayoutStack::calcMinExtents() +{ + mMinWidth = 0; + mMinHeight = 0; + + e_panel_list_t::iterator panel_it; + for (panel_it = mPanels.begin(); panel_it != mPanels.end(); ++panel_it) + { + if (mOrientation == HORIZONTAL) + { + mMinHeight = llmax(mMinHeight, (*panel_it)->mMinHeight); + mMinWidth += (*panel_it)->mMinWidth; + if (panel_it != mPanels.begin()) + { + mMinWidth += PANEL_STACK_GAP; + } + } + else //VERTICAL + { + mMinWidth = llmax(mMinWidth, (*panel_it)->mMinWidth); + mMinHeight += (*panel_it)->mMinHeight; + if (panel_it != mPanels.begin()) + { + mMinHeight += PANEL_STACK_GAP; + } + } + } +} diff --git a/indra/llui/llpanel.h b/indra/llui/llpanel.h index f06797a7f8..aa5f6e314f 100644 --- a/indra/llui/llpanel.h +++ b/indra/llui/llpanel.h @@ -15,6 +15,7 @@ #include "llcallbackmap.h" #include "lluictrl.h" #include "llviewborder.h" +#include "lluistring.h" #include "v4color.h" #include <list> #include <queue> @@ -71,6 +72,8 @@ public: LLViewBorder::EStyle border_style = LLViewBorder::STYLE_LINE, S32 border_thickness = LLPANEL_BORDER_WIDTH ); + void removeBorder(); + virtual ~LLPanel(); virtual void draw(); virtual void refresh(); // called in setFocus() @@ -97,6 +100,7 @@ public: LLString getLabel() const { return mLabel; } void setRectControl(const LLString& rect_control) { mRectControl.assign(rect_control); } + void storeRectControl(); void setBorderVisible( BOOL b ); @@ -116,8 +120,12 @@ public: virtual LLXMLNodePtr getXML(bool save_children = true) const; static LLView* fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory); BOOL initPanelXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory); + void initChildrenXML(LLXMLNodePtr node, LLUICtrlFactory* factory); void setPanelParameters(LLXMLNodePtr node, LLView *parentp); + LLString getFormattedUIString(const LLString& name, const LLString::format_map_t& args = LLUIString::sNullArgs) const; + LLUIString getUIString(const LLString& name) const; + // ** Wrappers for setting child properties by name ** -TomY // Override to set not found list @@ -196,6 +204,8 @@ public: typedef std::queue<LLAlertInfo> alert_queue_t; static alert_queue_t sAlertQueue; + typedef std::map<LLString, LLUIString> ui_string_map_t; + private: // common constructor void init(); @@ -221,6 +231,8 @@ protected: LLString mLabel; S32 mLastTabGroup; + ui_string_map_t mUIStrings; + typedef std::map<LLString, EWidgetType> requirements_map_t; requirements_map_t mRequirements; @@ -228,4 +240,50 @@ protected: static panel_map_t sPanelMap; }; +class LLLayoutStack : public LLView +{ +public: + typedef enum e_layout_orientation + { + HORIZONTAL, + VERTICAL + } eLayoutOrientation; + + LLLayoutStack(eLayoutOrientation orientation); + virtual ~LLLayoutStack(); + + /*virtual*/ void draw(); + /*virtual*/ void reshape(S32 width, S32 height, BOOL called_from_parent); + /*virtual*/ LLXMLNodePtr getXML(bool save_children = true) const; + /*virtual*/ void removeCtrl(LLUICtrl* ctrl); + virtual EWidgetType getWidgetType() const { return WIDGET_TYPE_LAYOUT_STACK; } + virtual LLString getWidgetTag() const { return LL_LAYOUT_STACK_TAG; } + + static LLView* fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory); + + S32 getMinWidth(); + S32 getMinHeight(); + + void addPanel(LLPanel* panel, S32 min_width, S32 min_height, BOOL auto_resize, S32 index = S32_MAX); + void removePanel(LLPanel* panel); + void updateLayout(BOOL force_resize = FALSE); + +protected: + struct LLEmbeddedPanel; + + LLEmbeddedPanel* findEmbeddedPanel(LLPanel* panelp); + void calcMinExtents(); + S32 getMinStackSize(); + S32 getCurStackSize(); + +protected: + eLayoutOrientation mOrientation; + + typedef std::vector<LLEmbeddedPanel*> e_panel_list_t; + e_panel_list_t mPanels; + + S32 mMinWidth; + S32 mMinHeight; +}; + #endif diff --git a/indra/llui/llradiogroup.cpp b/indra/llui/llradiogroup.cpp index 69c0da6933..b58ae09b5d 100644 --- a/indra/llui/llradiogroup.cpp +++ b/indra/llui/llradiogroup.cpp @@ -149,7 +149,7 @@ BOOL LLRadioGroup::handleKeyHere(KEY key, MASK mask, BOOL called_from_parent) { BOOL handled = FALSE; // do any of the tab buttons have keyboard focus? - if (getEnabled() && !called_from_parent) + if (getEnabled() && !called_from_parent && mask == MASK_NONE) { switch(key) { @@ -421,6 +421,69 @@ LLView* LLRadioGroup::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory return radio_group; } +// LLCtrlSelectionInterface functions +BOOL LLRadioGroup::setCurrentByID( const LLUUID& id ) +{ + return FALSE; +} + +LLUUID LLRadioGroup::getCurrentID() +{ + return LLUUID::null; +} + +BOOL LLRadioGroup::setSelectedByValue(LLSD value, BOOL selected) +{ + S32 idx = 0; + std::string value_string = value.asString(); + for (button_list_t::const_iterator iter = mRadioButtons.begin(); + iter != mRadioButtons.end(); ++iter) + { + if((*iter)->getName() == value_string) + { + setSelectedIndex(idx); + return TRUE; + } + idx++; + } + + return FALSE; +} + +LLSD LLRadioGroup::getSimpleSelectedValue() +{ + return getValue(); +} + +BOOL LLRadioGroup::isSelected(LLSD value) +{ + S32 idx = 0; + std::string value_string = value.asString(); + for (button_list_t::const_iterator iter = mRadioButtons.begin(); + iter != mRadioButtons.end(); ++iter) + { + if((*iter)->getName() == value_string) + { + if (idx == mSelectedIndex) + { + return TRUE; + } + } + idx++; + } + return FALSE; +} + +BOOL LLRadioGroup::operateOnSelection(EOperation op) +{ + return FALSE; +} + +BOOL LLRadioGroup::operateOnAll(EOperation op) +{ + return FALSE; +} + LLRadioCtrl::LLRadioCtrl(const LLString& name, const LLRect& rect, const LLString& label, const LLFontGL* font, void (*commit_callback)(LLUICtrl*, void*), void* callback_userdata) : @@ -438,3 +501,4 @@ void LLRadioCtrl::setValue(const LLSD& value) LLCheckBoxCtrl::setValue(value); mButton->setTabStop(value.asBoolean()); } + diff --git a/indra/llui/llradiogroup.h b/indra/llui/llradiogroup.h index 01b4a61b82..2a856ee6cf 100644 --- a/indra/llui/llradiogroup.h +++ b/indra/llui/llradiogroup.h @@ -15,6 +15,7 @@ #include "lluictrl.h" #include "llcheckboxctrl.h" +#include "llctrlselectioninterface.h" class LLFontGL; @@ -32,7 +33,7 @@ public: }; class LLRadioGroup -: public LLUICtrl +: public LLUICtrl, public LLCtrlSelectionInterface { public: // Build a radio group. The number (0...n-1) of the currently selected @@ -63,7 +64,6 @@ public: static LLView* fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory); void setIndexEnabled(S32 index, BOOL enabled); - S32 getItemCount() { return mRadioButtons.size(); } // return the index value of the selected item S32 getSelectedIndex() const; @@ -87,6 +87,23 @@ public: // button. static void onClickButton(LLUICtrl* radio, void* userdata); + //======================================================================== + LLCtrlSelectionInterface* getSelectionInterface() { return (LLCtrlSelectionInterface*)this; }; + + // LLCtrlSelectionInterface functions + /*virtual*/ S32 getItemCount() const { return mRadioButtons.size(); } + /*virtual*/ BOOL getCanSelect() const { return TRUE; } + /*virtual*/ BOOL selectFirstItem() { return setSelectedIndex(0); } + /*virtual*/ BOOL selectNthItem( S32 index ) { return setSelectedIndex(index); } + /*virtual*/ S32 getFirstSelectedIndex() { return getSelectedIndex(); } + /*virtual*/ BOOL setCurrentByID( const LLUUID& id ); + /*virtual*/ LLUUID getCurrentID(); // LLUUID::null if no items in menu + /*virtual*/ BOOL setSelectedByValue(LLSD value, BOOL selected); + /*virtual*/ LLSD getSimpleSelectedValue(); + /*virtual*/ BOOL isSelected(LLSD value); + /*virtual*/ BOOL operateOnSelection(EOperation op); + /*virtual*/ BOOL operateOnAll(EOperation op); + protected: // protected function shared by the two constructors. void init(BOOL border); diff --git a/indra/llui/llresizebar.cpp b/indra/llui/llresizebar.cpp index 0128b4ebbc..b3bce755b1 100644 --- a/indra/llui/llresizebar.cpp +++ b/indra/llui/llresizebar.cpp @@ -18,16 +18,18 @@ #include "llfocusmgr.h" #include "llwindow.h" -LLResizeBar::LLResizeBar( const LLString& name, const LLRect& rect, S32 min_width, S32 min_height, Side side ) +LLResizeBar::LLResizeBar( const LLString& name, LLView* resizing_view, const LLRect& rect, S32 min_size, S32 max_size, Side side ) : LLView( name, rect, TRUE ), mDragLastScreenX( 0 ), mDragLastScreenY( 0 ), mLastMouseScreenX( 0 ), mLastMouseScreenY( 0 ), - mMinWidth( min_width ), - mMinHeight( min_height ), - mSide( side ) + mMinSize( min_size ), + mMaxSize( max_size ), + mSide( side ), + mSnappingEnabled(TRUE), + mResizingView(resizing_view) { // set up some generically good follow code. switch( side ) @@ -129,12 +131,11 @@ BOOL LLResizeBar::handleHover(S32 x, S32 y, MASK mask) // Make sure the mouse in still over the application. We don't want to make the parent // so big that we can't see the resize handle any more. LLRect valid_rect = getRootView()->getRect(); - LLView* resizing_view = getParent(); - if( valid_rect.localPointInRect( screen_x, screen_y ) && resizing_view ) + if( valid_rect.localPointInRect( screen_x, screen_y ) && mResizingView ) { // Resize the parent - LLRect orig_rect = resizing_view->getRect(); + LLRect orig_rect = mResizingView->getRect(); LLRect scaled_rect = orig_rect; S32 new_width = orig_rect.getWidth(); @@ -143,76 +144,63 @@ BOOL LLResizeBar::handleHover(S32 x, S32 y, MASK mask) switch( mSide ) { case LEFT: - new_width = orig_rect.getWidth() - delta_x; - if( new_width < mMinWidth ) - { - new_width = mMinWidth; - delta_x = orig_rect.getWidth() - mMinWidth; - } + new_width = llclamp(orig_rect.getWidth() - delta_x, mMinSize, mMaxSize); + delta_x = orig_rect.getWidth() - new_width; scaled_rect.translate(delta_x, 0); break; case TOP: - new_height = orig_rect.getHeight() + delta_y; - if( new_height < mMinHeight ) - { - new_height = mMinHeight; - delta_y = mMinHeight - orig_rect.getHeight(); - } + new_height = llclamp(orig_rect.getHeight() + delta_y, mMinSize, mMaxSize); + delta_y = new_height - orig_rect.getHeight(); break; case RIGHT: - new_width = orig_rect.getWidth() + delta_x; - if( new_width < mMinWidth ) - { - new_width = mMinWidth; - delta_x = mMinWidth - orig_rect.getWidth(); - } + new_width = llclamp(orig_rect.getWidth() + delta_x, mMinSize, mMaxSize); + delta_x = new_width - orig_rect.getWidth(); break; case BOTTOM: - new_height = orig_rect.getHeight() - delta_y; - if( new_height < mMinHeight ) - { - new_height = mMinHeight; - delta_y = orig_rect.getHeight() - mMinHeight; - } + new_height = llclamp(orig_rect.getHeight() - delta_y, mMinSize, mMaxSize); + delta_y = orig_rect.getHeight() - new_height; scaled_rect.translate(0, delta_y); break; } scaled_rect.mTop = scaled_rect.mBottom + new_height; scaled_rect.mRight = scaled_rect.mLeft + new_width; - resizing_view->setRect(scaled_rect); + mResizingView->setRect(scaled_rect); LLView* snap_view = NULL; - switch( mSide ) + if (mSnappingEnabled) { - case LEFT: - snap_view = resizing_view->findSnapEdge(scaled_rect.mLeft, mouse_dir, SNAP_LEFT, SNAP_PARENT_AND_SIBLINGS, LLUI::sConfigGroup->getS32("SnapMargin")); - break; - case TOP: - snap_view = resizing_view->findSnapEdge(scaled_rect.mTop, mouse_dir, SNAP_TOP, SNAP_PARENT_AND_SIBLINGS, LLUI::sConfigGroup->getS32("SnapMargin")); - break; - case RIGHT: - snap_view = resizing_view->findSnapEdge(scaled_rect.mRight, mouse_dir, SNAP_RIGHT, SNAP_PARENT_AND_SIBLINGS, LLUI::sConfigGroup->getS32("SnapMargin")); - break; - case BOTTOM: - snap_view = resizing_view->findSnapEdge(scaled_rect.mBottom, mouse_dir, SNAP_BOTTOM, SNAP_PARENT_AND_SIBLINGS, LLUI::sConfigGroup->getS32("SnapMargin")); - break; + switch( mSide ) + { + case LEFT: + snap_view = mResizingView->findSnapEdge(scaled_rect.mLeft, mouse_dir, SNAP_LEFT, SNAP_PARENT_AND_SIBLINGS, LLUI::sConfigGroup->getS32("SnapMargin")); + break; + case TOP: + snap_view = mResizingView->findSnapEdge(scaled_rect.mTop, mouse_dir, SNAP_TOP, SNAP_PARENT_AND_SIBLINGS, LLUI::sConfigGroup->getS32("SnapMargin")); + break; + case RIGHT: + snap_view = mResizingView->findSnapEdge(scaled_rect.mRight, mouse_dir, SNAP_RIGHT, SNAP_PARENT_AND_SIBLINGS, LLUI::sConfigGroup->getS32("SnapMargin")); + break; + case BOTTOM: + snap_view = mResizingView->findSnapEdge(scaled_rect.mBottom, mouse_dir, SNAP_BOTTOM, SNAP_PARENT_AND_SIBLINGS, LLUI::sConfigGroup->getS32("SnapMargin")); + break; + } } // register "snap" behavior with snapped view - resizing_view->snappedTo(snap_view); + mResizingView->snappedTo(snap_view); // restore original rectangle so the appropriate changes are detected - resizing_view->setRect(orig_rect); + mResizingView->setRect(orig_rect); // change view shape as user operation - resizing_view->userSetShape(scaled_rect); + mResizingView->userSetShape(scaled_rect); // update last valid mouse cursor position based on resized view's actual size - LLRect new_rect = resizing_view->getRect(); + LLRect new_rect = mResizingView->getRect(); switch(mSide) { case LEFT: diff --git a/indra/llui/llresizebar.h b/indra/llui/llresizebar.h index 7a77cce8a6..cf78879cba 100644 --- a/indra/llui/llresizebar.h +++ b/indra/llui/llresizebar.h @@ -17,7 +17,7 @@ class LLResizeBar : public LLView public: enum Side { LEFT, TOP, RIGHT, BOTTOM }; - LLResizeBar(const LLString& name, const LLRect& rect, S32 min_width, S32 min_height, Side side ); + LLResizeBar(const LLString& name, LLView* resizing_view, const LLRect& rect, S32 min_size, S32 max_size, Side side ); virtual EWidgetType getWidgetType() const; virtual LLString getWidgetTag() const; @@ -27,7 +27,8 @@ public: virtual BOOL handleMouseDown(S32 x, S32 y, MASK mask); virtual BOOL handleMouseUp(S32 x, S32 y, MASK mask); - void setResizeLimits( S32 min_width, S32 min_height ) { mMinWidth = min_width; mMinHeight = min_height; } + void setResizeLimits( S32 min_size, S32 max_size ) { mMinSize = min_size; mMaxSize = max_size; } + void setEnableSnapping(BOOL enable) { mSnappingEnabled = enable; } protected: S32 mDragLastScreenX; @@ -35,9 +36,11 @@ protected: S32 mLastMouseScreenX; S32 mLastMouseScreenY; LLCoordGL mLastMouseDir; - S32 mMinWidth; - S32 mMinHeight; + S32 mMinSize; + S32 mMaxSize; Side mSide; + BOOL mSnappingEnabled; + LLView* mResizingView; }; const S32 RESIZE_BAR_HEIGHT = 3; diff --git a/indra/llui/llscrollcontainer.cpp b/indra/llui/llscrollcontainer.cpp index 0e3c73f633..589afb114a 100644 --- a/indra/llui/llscrollcontainer.cpp +++ b/indra/llui/llscrollcontainer.cpp @@ -485,8 +485,7 @@ void LLScrollableContainerView::draw() BOOL show_h_scrollbar = FALSE; calcVisibleSize( mScrolledView->getRect(), &visible_width, &visible_height, &show_h_scrollbar, &show_v_scrollbar ); - LLGLEnable scissor_test(GL_SCISSOR_TEST); - LLUI::setScissorRegionLocal(LLRect(mInnerRect.mLeft, + LLLocalClipRect clip(LLRect(mInnerRect.mLeft, mInnerRect.mBottom + (show_h_scrollbar ? SCROLLBAR_SIZE : 0) + visible_height, visible_width, mInnerRect.mBottom + (show_h_scrollbar ? SCROLLBAR_SIZE : 0) diff --git a/indra/llui/llscrolllistctrl.cpp b/indra/llui/llscrolllistctrl.cpp index 1d07d3f36b..e802b3426b 100644 --- a/indra/llui/llscrolllistctrl.cpp +++ b/indra/llui/llscrolllistctrl.cpp @@ -31,7 +31,7 @@ #include "llkeyboard.h" #include "llresizebar.h" -const S32 LIST_BORDER_PAD = 2; // white space inside the border and to the left of the scrollbar +const S32 LIST_BORDER_PAD = 0; // white space inside the border and to the left of the scrollbar const S32 MIN_COLUMN_WIDTH = 20; const S32 LIST_SNAP_PADDING = 5; @@ -397,6 +397,7 @@ LLScrollListCtrl::LLScrollListCtrl(const LLString& name, const LLRect& rect, mCommitOnKeyboardMovement(TRUE), mCommitOnSelectionChange(FALSE), mSelectionChanged(FALSE), + mNeedsScroll(FALSE), mCanSelect(TRUE), mDisplayColumnHeaders(FALSE), mCollapseEmptyColumns(FALSE), @@ -1419,14 +1420,16 @@ void LLScrollListCtrl::drawItems() S32 x = mItemListRect.mLeft; S32 y = mItemListRect.mTop - mLineHeight; - S32 num_page_lines = mPageLines; + // allow for partial line at bottom + S32 num_page_lines = mPageLines + 1; LLRect item_rect; LLGLSUIDefault gls_ui; { - + LLLocalClipRect clip(mItemListRect); + S32 cur_x = x; S32 cur_y = y; @@ -1538,6 +1541,11 @@ void LLScrollListCtrl::draw() { if( getVisible() ) { + if (mNeedsScroll) + { + scrollToShowSelected(); + mNeedsScroll = FALSE; + } LLRect background(0, mRect.getHeight(), mRect.getWidth(), 0); // Draw background if (mBackgroundVisible) @@ -1690,6 +1698,7 @@ BOOL LLScrollListCtrl::handleMouseDown(S32 x, S32 y, MASK mask) gFocusMgr.setMouseCapture(this); selectItemAt(x, y, mask); + mNeedsScroll = TRUE; } return TRUE; @@ -1699,17 +1708,16 @@ BOOL LLScrollListCtrl::handleMouseUp(S32 x, S32 y, MASK mask) { if (hasMouseCapture()) { + // release mouse capture immediately so + // scroll to show selected logic will work + gFocusMgr.setMouseCapture(NULL); if(mask == MASK_NONE) { selectItemAt(x, y, mask); + mNeedsScroll = TRUE; } } - if (hasMouseCapture()) - { - gFocusMgr.setMouseCapture(NULL); - } - // always commit when mouse operation is completed inside list if (mItemListRect.pointInRect(x,y)) { @@ -1750,7 +1758,8 @@ LLScrollListItem* LLScrollListCtrl::hitItem( S32 x, S32 y ) mItemListRect.getWidth(), mLineHeight ); - int num_page_lines = mPageLines; + // allow for partial line at bottom + S32 num_page_lines = mPageLines + 1; S32 line = 0; item_list::iterator iter; @@ -1783,6 +1792,7 @@ BOOL LLScrollListCtrl::handleHover(S32 x,S32 y,MASK mask) if(mask == MASK_NONE) { selectItemAt(x, y, mask); + mNeedsScroll = TRUE; } } else if (mCanSelect) @@ -1830,7 +1840,7 @@ BOOL LLScrollListCtrl::handleKeyHere(KEY key,MASK mask, BOOL called_from_parent { // commit implicit in call selectPrevItem(FALSE); - scrollToShowSelected(); + mNeedsScroll = TRUE; handled = TRUE; } break; @@ -1839,7 +1849,7 @@ BOOL LLScrollListCtrl::handleKeyHere(KEY key,MASK mask, BOOL called_from_parent { // commit implicit in call selectNextItem(FALSE); - scrollToShowSelected(); + mNeedsScroll = TRUE; handled = TRUE; } break; @@ -1847,7 +1857,7 @@ BOOL LLScrollListCtrl::handleKeyHere(KEY key,MASK mask, BOOL called_from_parent if (mAllowKeyboardMovement || hasFocus()) { selectNthItem(getFirstSelectedIndex() - (mScrollbar->getPageSize() - 1)); - scrollToShowSelected(); + mNeedsScroll = TRUE; if (mCommitOnKeyboardMovement && !mCommitOnSelectionChange) { @@ -1860,7 +1870,7 @@ BOOL LLScrollListCtrl::handleKeyHere(KEY key,MASK mask, BOOL called_from_parent if (mAllowKeyboardMovement || hasFocus()) { selectNthItem(getFirstSelectedIndex() + (mScrollbar->getPageSize() - 1)); - scrollToShowSelected(); + mNeedsScroll = TRUE; if (mCommitOnKeyboardMovement && !mCommitOnSelectionChange) { @@ -1873,7 +1883,7 @@ BOOL LLScrollListCtrl::handleKeyHere(KEY key,MASK mask, BOOL called_from_parent if (mAllowKeyboardMovement || hasFocus()) { selectFirstItem(); - scrollToShowSelected(); + mNeedsScroll = TRUE; if (mCommitOnKeyboardMovement && !mCommitOnSelectionChange) { @@ -1886,7 +1896,7 @@ BOOL LLScrollListCtrl::handleKeyHere(KEY key,MASK mask, BOOL called_from_parent if (mAllowKeyboardMovement || hasFocus()) { selectNthItem(getItemCount() - 1); - scrollToShowSelected(); + mNeedsScroll = TRUE; if (mCommitOnKeyboardMovement && !mCommitOnSelectionChange) { @@ -1925,6 +1935,7 @@ BOOL LLScrollListCtrl::handleKeyHere(KEY key,MASK mask, BOOL called_from_parent } else if (selectSimpleItemByPrefix(wstring_to_utf8str(mSearchString), FALSE)) { + mNeedsScroll = TRUE; // update search string only on successful match mSearchTimer.reset(); @@ -1964,6 +1975,7 @@ BOOL LLScrollListCtrl::handleUnicodeCharHere(llwchar uni_char, BOOL called_from_ if (selectSimpleItemByPrefix(wstring_to_utf8str(mSearchString + (llwchar)uni_char), FALSE)) { // update search string only on successful match + mNeedsScroll = TRUE; mSearchString += uni_char; mSearchTimer.reset(); @@ -2009,6 +2021,7 @@ BOOL LLScrollListCtrl::handleUnicodeCharHere(llwchar uni_char, BOOL called_from_ if (item->getEnabled() && LLStringOps::toLower(item_label[0]) == uni_char) { selectItem(item); + mNeedsScroll = TRUE; cellp->highlightText(0, 1); mSearchTimer.reset(); @@ -2030,8 +2043,6 @@ BOOL LLScrollListCtrl::handleUnicodeCharHere(llwchar uni_char, BOOL called_from_ } } - // make sure selected item is on screen - scrollToShowSelected(); return TRUE; } @@ -2183,6 +2194,13 @@ void LLScrollListCtrl::setScrollPos( S32 pos ) void LLScrollListCtrl::scrollToShowSelected() { + // don't scroll automatically when capturing mouse input + // as that will change what is currently under the mouse cursor + if (hasMouseCapture()) + { + return; + } + S32 index = getFirstSelectedIndex(); if (index < 0) { @@ -3013,8 +3031,9 @@ LLColumnHeader::LLColumnHeader(const LLString& label, const LLRect &rect, LLScro const S32 RESIZE_BAR_THICKNESS = 3; mResizeBar = new LLResizeBar( "resizebar", + this, LLRect( mRect.getWidth() - RESIZE_BAR_THICKNESS, mRect.getHeight(), mRect.getWidth(), 0), - MIN_COLUMN_WIDTH, mRect.getHeight(), LLResizeBar::RIGHT ); + MIN_COLUMN_WIDTH, S32_MAX, LLResizeBar::RIGHT ); addChild(mResizeBar); mResizeBar->setEnabled(FALSE); diff --git a/indra/llui/llscrolllistctrl.h b/indra/llui/llscrolllistctrl.h index 793f97dc64..9604b0569c 100644 --- a/indra/llui/llscrolllistctrl.h +++ b/indra/llui/llscrolllistctrl.h @@ -585,6 +585,7 @@ protected: BOOL mCommitOnKeyboardMovement; BOOL mCommitOnSelectionChange; BOOL mSelectionChanged; + BOOL mNeedsScroll; BOOL mCanSelect; BOOL mDisplayColumnHeaders; BOOL mCollapseEmptyColumns; diff --git a/indra/llui/llslider.cpp b/indra/llui/llslider.cpp index 3a01013943..06fd2830a3 100644 --- a/indra/llui/llslider.cpp +++ b/indra/llui/llslider.cpp @@ -30,6 +30,7 @@ LLSlider::LLSlider( F32 min_value, F32 max_value, F32 increment, + BOOL volume, const LLString& control_name) : LLUICtrl( name, rect, TRUE, on_commit_callback, callback_userdata, @@ -39,6 +40,7 @@ LLSlider::LLSlider( mMinValue( min_value ), mMaxValue( max_value ), mIncrement( increment ), + mVolumeSlider( volume ), mMouseOffset( 0 ), mDragStartThumbRect( 0, mRect.getHeight(), THUMB_WIDTH, 0 ), mThumbRect( 0, mRect.getHeight(), THUMB_WIDTH, 0 ), @@ -49,7 +51,7 @@ LLSlider::LLSlider( mMouseDownCallback( NULL ), mMouseUpCallback( NULL ) { - // prperly handle setting the starting thumb rect + // properly handle setting the starting thumb rect // do it this way to handle both the operating-on-settings // and standalone ways of using this setControlName(control_name, NULL); @@ -74,13 +76,15 @@ void LLSlider::setValue(F32 value, BOOL from_event) value -= mMinValue; value += mIncrement/2.0001f; value -= fmod(value, mIncrement); - mValue = mMinValue + value; + value += mMinValue; - if (!from_event) + if (!from_event && mValue != value) { - setControlValue(mValue); + setControlValue(value); } - + + mValue = value; + F32 t = (mValue - mMinValue) / (mMaxValue - mMinValue); S32 left_edge = THUMB_WIDTH/2; @@ -91,6 +95,18 @@ void LLSlider::setValue(F32 value, BOOL from_event) mThumbRect.mRight = x + (THUMB_WIDTH/2); } +void LLSlider::setValueAndCommit(F32 value) +{ + F32 old_value = mValue; + setValue(value); + + if (mValue != old_value) + { + onCommit(); + } +} + + F32 LLSlider::getValueF32() const { return mValue; @@ -107,8 +123,7 @@ BOOL LLSlider::handleHover(S32 x, S32 y, MASK mask) x = llclamp( x, left_edge, right_edge ); F32 t = F32(x - left_edge) / (right_edge - left_edge); - setValue(t * (mMaxValue - mMinValue) + mMinValue ); - onCommit(); + setValueAndCommit(t * (mMaxValue - mMinValue) + mMinValue ); getWindow()->setCursor(UI_CURSOR_ARROW); lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << " (active)" << llendl; @@ -158,8 +173,7 @@ BOOL LLSlider::handleMouseDown(S32 x, S32 y, MASK mask) if (MASK_CONTROL & mask) // if CTRL is modifying { - setValue(mInitialValue); - onCommit(); + setValueAndCommit(mInitialValue); } else { @@ -196,13 +210,11 @@ BOOL LLSlider::handleKeyHere(KEY key, MASK mask, BOOL called_from_parent) handled = TRUE; break; case KEY_LEFT: - setValue(getValueF32() - getIncrement()); - onCommit(); + setValueAndCommit(getValueF32() - getIncrement()); handled = TRUE; break; case KEY_RIGHT: - setValue(getValueF32() + getIncrement()); - onCommit(); + setValueAndCommit(getValueF32() + getIncrement()); handled = TRUE; break; default: @@ -224,33 +236,93 @@ void LLSlider::draw() LLRect rect(mDragStartThumbRect); F32 opacity = mEnabled ? 1.f : 0.3f; + LLColor4 center_color = (mThumbCenterColor % opacity); + LLColor4 outline_color = (mThumbOutlineColor % opacity); + LLColor4 track_color = (mTrackColor % opacity); + LLImageGL* thumb_imagep = NULL; + // Track + if (mVolumeSlider) + { + LLRect track(0, mRect.getHeight(), mRect.getWidth(), 0); + + track.mBottom += 3; + track.mTop -= 1; + track.mRight -= 1; + + gl_triangle_2d(track.mLeft, track.mBottom, + track.mRight, track.mBottom, + track.mRight, track.mTop, + center_color, + TRUE); + gl_triangle_2d(track.mLeft, track.mBottom, + track.mRight, track.mBottom, + track.mRight, track.mTop, + outline_color, + FALSE); + } + else + { + LLUUID thumb_image_id; + thumb_image_id.set(LLUI::sAssetsGroup->getString("rounded_square.tga")); + thumb_imagep = LLUI::sImageProvider->getUIImageByID(thumb_image_id); - LLUUID thumb_image_id; - thumb_image_id.set(LLUI::sAssetsGroup->getString("rounded_square.tga")); - LLImageGL* thumb_imagep = LLUI::sImageProvider->getUIImageByID(thumb_image_id); - - S32 height_offset = (mRect.getHeight() - TRACK_HEIGHT) / 2; - LLRect track_rect(0, mRect.getHeight() - height_offset, mRect.getWidth(), height_offset ); + S32 height_offset = (mRect.getHeight() - TRACK_HEIGHT) / 2; + LLRect track_rect(0, mRect.getHeight() - height_offset, mRect.getWidth(), height_offset ); - track_rect.stretch(-1); - gl_draw_scaled_image_with_border(track_rect.mLeft, track_rect.mBottom, 16, 16, track_rect.getWidth(), track_rect.getHeight(), - thumb_imagep, mTrackColor % opacity); - //gl_rect_2d( track_rect, mThumbOutlineColor % opacity ); + track_rect.stretch(-1); + gl_draw_scaled_image_with_border(track_rect.mLeft, track_rect.mBottom, 16, 16, track_rect.getWidth(), track_rect.getHeight(), + thumb_imagep, track_color); + } + // Thumb if (!thumb_imagep) { - gl_rect_2d(mThumbRect, mThumbCenterColor, TRUE); - if (hasMouseCapture()) + if (mVolumeSlider) + { + if (hasMouseCapture()) + { + LLRect rect(mDragStartThumbRect); + gl_rect_2d( rect, outline_color ); + rect.stretch(-1); + gl_rect_2d( rect, mThumbCenterColor % 0.3f ); + + if (hasFocus()) + { + LLRect thumb_rect = mThumbRect; + thumb_rect.stretch(llround(lerp(1.f, 3.f, gFocusMgr.getFocusFlashAmt()))); + gl_rect_2d(thumb_rect, gFocusMgr.getFocusColor()); + } + gl_rect_2d( mThumbRect, mThumbOutlineColor ); + } + else + { + if (hasFocus()) + { + LLRect thumb_rect = mThumbRect; + thumb_rect.stretch(llround(lerp(1.f, 3.f, gFocusMgr.getFocusFlashAmt()))); + gl_rect_2d(thumb_rect, gFocusMgr.getFocusColor()); + } + LLRect rect(mThumbRect); + gl_rect_2d(rect, outline_color); + rect.stretch(-1); + gl_rect_2d( rect, center_color); + } + } + else { - gl_rect_2d(mDragStartThumbRect, mThumbCenterColor % opacity, FALSE); + gl_rect_2d(mThumbRect, mThumbCenterColor, TRUE); + if (hasMouseCapture()) + { + gl_rect_2d(mDragStartThumbRect, center_color, FALSE); + } } } else if( hasMouseCapture() ) { gl_draw_scaled_image_with_border(mDragStartThumbRect.mLeft, mDragStartThumbRect.mBottom, 16, 16, mDragStartThumbRect.getWidth(), mDragStartThumbRect.getHeight(), - thumb_imagep, mThumbCenterColor % 0.3f, TRUE); + thumb_imagep, mThumbCenterColor % 0.3f, TRUE); if (hasFocus()) { @@ -258,20 +330,12 @@ void LLSlider::draw() LLRect highlight_rect = mThumbRect; highlight_rect.stretch(llround(lerp(1.f, 3.f, lerp_amt))); gl_draw_scaled_image_with_border(highlight_rect.mLeft, highlight_rect.mBottom, 16, 16, highlight_rect.getWidth(), highlight_rect.getHeight(), - thumb_imagep, gFocusMgr.getFocusColor()); + thumb_imagep, gFocusMgr.getFocusColor()); } - gl_draw_scaled_image_with_border(mThumbRect.mLeft, mThumbRect.mBottom, 16, 16, mThumbRect.getWidth(), mThumbRect.getHeight(), - thumb_imagep, mThumbOutlineColor, TRUE); - - //// Start Thumb - //gl_rect_2d( mDragStartThumbRect, mThumbOutlineColor % 0.3f ); - //rect.stretch(-1); - //gl_rect_2d( rect, mThumbCenterColor % 0.3f ); + thumb_imagep, mThumbOutlineColor, TRUE); - //// Thumb - //gl_rect_2d( mThumbRect, mThumbOutlineColor ); } else { @@ -281,22 +345,12 @@ void LLSlider::draw() LLRect highlight_rect = mThumbRect; highlight_rect.stretch(llround(lerp(1.f, 3.f, lerp_amt))); gl_draw_scaled_image_with_border(highlight_rect.mLeft, highlight_rect.mBottom, 16, 16, highlight_rect.getWidth(), highlight_rect.getHeight(), - thumb_imagep, gFocusMgr.getFocusColor()); + thumb_imagep, gFocusMgr.getFocusColor()); } gl_draw_scaled_image_with_border(mThumbRect.mLeft, mThumbRect.mBottom, 16, 16, mThumbRect.getWidth(), mThumbRect.getHeight(), - thumb_imagep, mThumbCenterColor % opacity, TRUE); - //rect = mThumbRect; - - //gl_rect_2d( mThumbRect, mThumbOutlineColor % opacity ); - // - //rect.stretch(-1); - - //// Thumb - //gl_rect_2d( rect, mThumbCenterColor % opacity ); - + thumb_imagep, center_color, TRUE); } - LLUICtrl::draw(); } } @@ -310,6 +364,7 @@ LLXMLNodePtr LLSlider::getXML(bool save_children) const node->createChild("min_val", TRUE)->setFloatValue(getMinValue()); node->createChild("max_val", TRUE)->setFloatValue(getMaxValue()); node->createChild("increment", TRUE)->setFloatValue(getIncrement()); + node->createChild("volume", TRUE)->setBoolValue(getVolumeSlider()); return node; } @@ -336,6 +391,8 @@ LLView* LLSlider::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *fa F32 increment = 0.1f; node->getAttributeF32("increment", increment); + BOOL volume = node->hasName("volume_slider") ? TRUE : FALSE; + node->getAttributeBOOL("volume", volume); LLSlider* slider = new LLSlider(name, rect, @@ -344,7 +401,8 @@ LLView* LLSlider::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *fa initial_value, min_value, max_value, - increment); + increment, + volume); slider->initFromXML(node, parent); diff --git a/indra/llui/llslider.h b/indra/llui/llslider.h index d88da42064..d9cdf11d5d 100644 --- a/indra/llui/llslider.h +++ b/indra/llui/llslider.h @@ -26,6 +26,7 @@ public: F32 min_value, F32 max_value, F32 increment, + BOOL volume, const LLString& control_name = LLString::null ); virtual EWidgetType getWidgetType() const; @@ -46,6 +47,7 @@ public: F32 getMinValue() const { return mMinValue; } F32 getMaxValue() const { return mMaxValue; } F32 getIncrement() const { return mIncrement; } + BOOL getVolumeSlider() const { return mVolumeSlider; } void setMinValue(F32 min_value) {mMinValue = min_value;} void setMaxValue(F32 max_value) {mMaxValue = max_value;} void setIncrement(F32 increment) {mIncrement = increment;} @@ -59,12 +61,16 @@ public: virtual void draw(); protected: + void setValueAndCommit(F32 value); + +protected: F32 mValue; F32 mInitialValue; F32 mMinValue; F32 mMaxValue; F32 mIncrement; + BOOL mVolumeSlider; S32 mMouseOffset; LLRect mDragStartThumbRect; diff --git a/indra/llui/llsliderctrl.cpp b/indra/llui/llsliderctrl.cpp index 8b5cd4690e..4ae8c5d222 100644 --- a/indra/llui/llsliderctrl.cpp +++ b/indra/llui/llsliderctrl.cpp @@ -37,6 +37,7 @@ LLSliderCtrl::LLSliderCtrl(const LLString& name, const LLRect& rect, S32 text_left, BOOL show_text, BOOL can_edit_text, + BOOL volume, void (*commit_callback)(LLUICtrl*, void*), void* callback_user_data, F32 initial_value, F32 min_value, F32 max_value, F32 increment, @@ -45,6 +46,7 @@ LLSliderCtrl::LLSliderCtrl(const LLString& name, const LLRect& rect, mFont(font), mShowText( show_text ), mCanEditText( can_edit_text ), + mVolumeSlider( volume ), mPrecision( 3 ), mLabelBox( NULL ), mLabelWidth( label_width ), @@ -84,7 +86,7 @@ LLSliderCtrl::LLSliderCtrl(const LLString& name, const LLRect& rect, "slider", slider_rect, LLSliderCtrl::onSliderCommit, this, - initial_value, min_value, max_value, increment, + initial_value, min_value, max_value, increment, volume, control_which ); addChild( mSlider ); @@ -423,6 +425,8 @@ LLXMLNodePtr LLSliderCtrl::getXML(bool save_children) const node->createChild("can_edit_text", TRUE)->setBoolValue(mCanEditText); + node->createChild("volume", TRUE)->setBoolValue(mVolumeSlider); + node->createChild("decimal_digits", TRUE)->setIntValue(mPrecision); if (mLabelBox) @@ -474,6 +478,9 @@ LLView* LLSliderCtrl::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory BOOL can_edit_text = FALSE; node->getAttributeBOOL("can_edit_text", can_edit_text); + BOOL volume = FALSE; + node->getAttributeBOOL("volume", volume); + F32 initial_value = 0.f; node->getAttributeF32("initial_val", initial_value); @@ -521,6 +528,7 @@ LLView* LLSliderCtrl::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory rect.getWidth() - text_left, show_text, can_edit_text, + volume, callback, NULL, initial_value, diff --git a/indra/llui/llsliderctrl.h b/indra/llui/llsliderctrl.h index 2185e42eb1..0780350462 100644 --- a/indra/llui/llsliderctrl.h +++ b/indra/llui/llsliderctrl.h @@ -40,6 +40,7 @@ public: S32 text_left, BOOL show_text, BOOL can_edit_text, + BOOL volume, void (*commit_callback)(LLUICtrl*, void*), void* callback_userdata, F32 initial_value, F32 min_value, F32 max_value, F32 increment, @@ -104,7 +105,8 @@ private: const LLFontGL* mFont; BOOL mShowText; BOOL mCanEditText; - + BOOL mVolumeSlider; + S32 mPrecision; LLTextBox* mLabelBox; S32 mLabelWidth; diff --git a/indra/llui/lltabcontainer.cpp b/indra/llui/lltabcontainer.cpp index ef527b32c2..65551e5c48 100644 --- a/indra/llui/lltabcontainer.cpp +++ b/indra/llui/lltabcontainer.cpp @@ -50,6 +50,7 @@ LLTabContainerCommon::LLTabContainerCommon( : LLPanel(name, rect, bordered), mCurrentTabIdx(-1), + mTabsHidden(FALSE), mScrolled(FALSE), mScrollPos(0), mScrollPosPixels(0), @@ -75,6 +76,7 @@ LLTabContainerCommon::LLTabContainerCommon( : LLPanel(name, rect_control, bordered), mCurrentTabIdx(-1), + mTabsHidden(FALSE), mScrolled(FALSE), mScrollPos(0), mScrollPosPixels(0), @@ -127,11 +129,11 @@ void LLTabContainerCommon::addPlaceholder(LLPanel* child, const LLString& label) addTabPanel(child, label, FALSE, NULL, NULL, 0, TRUE); } -void LLTabContainerCommon::lockTabs() +void LLTabContainerCommon::lockTabs(S32 num_tabs) { - // count current tabs and ensure no new tabs get + // count current tabs or use supplied value and ensure no new tabs get // inserted between them - mLockedTabCount = getTabCount(); + mLockedTabCount = num_tabs > 0 ? num_tabs : getTabCount(); } void LLTabContainerCommon::removeTabPanel(LLPanel* child) @@ -522,12 +524,12 @@ void LLTabContainerCommon::setTabPanelFlashing(LLPanel* child, BOOL state ) } } -void LLTabContainerCommon::setTabImage(LLPanel* child, std::string img_name) +void LLTabContainerCommon::setTabImage(LLPanel* child, std::string img_name, const LLColor4& color) { LLTabTuple* tuple = getTabByPanel(child); if( tuple ) { - tuple->mButton->setImageOverlay(img_name, LLFontGL::RIGHT); + tuple->mButton->setImageOverlay(img_name, LLFontGL::RIGHT, color); } } @@ -647,6 +649,8 @@ LLView* LLTabContainerCommon::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtr } } + node->getAttributeBOOL("hide_tabs", tab_container->mTabsHidden); + tab_container->setPanelParameters(node, parent); if (LLFloater::getFloaterHost()) @@ -1016,10 +1020,11 @@ void LLTabContainer::setPanelTitle(S32 index, const LLString& title) { if (index >= 0 && index < (S32)mTabList.size()) { - LLButton* tab_button = mTabList[index]->mButton; + LLTabTuple* tuple = mTabList[index]; + LLButton* tab_button = tuple->mButton; const LLFontGL* fontp = gResMgr->getRes( LLFONT_SANSSERIF_SMALL ); mTotalTabWidth -= tab_button->getRect().getWidth(); - tab_button->reshape(llclamp(fontp->getWidth(title) + TAB_PADDING, mMinTabWidth, mMaxTabWidth), tab_button->getRect().getHeight()); + tab_button->reshape(llclamp(fontp->getWidth(title) + TAB_PADDING + tuple->mPadding, mMinTabWidth, mMaxTabWidth), tab_button->getRect().getHeight()); mTotalTabWidth += tab_button->getRect().getWidth(); tab_button->setLabelSelected(title); tab_button->setLabelUnselected(title); @@ -1225,63 +1230,60 @@ void LLTabContainer::draw() LLPanel::draw(); - // Show all the buttons - for(tuple_list_t::iterator iter = mTabList.begin(); iter != mTabList.end(); ++iter) + // if tabs are hidden, don't draw them and leave them in the invisible state + if (!mTabsHidden) { - LLTabTuple* tuple = *iter; - tuple->mButton->setVisible( TRUE ); - } - - // Draw some of the buttons... + // Show all the buttons + for(tuple_list_t::iterator iter = mTabList.begin(); iter != mTabList.end(); ++iter) + { + LLTabTuple* tuple = *iter; + tuple->mButton->setVisible( TRUE ); + } - LLGLEnable scissor_test(has_scroll_arrows ? GL_SCISSOR_TEST : GL_FALSE); - if( has_scroll_arrows ) - { - // ...but clip them. - S32 x1 = mLeftArrowBtn->getRect().mRight; - S32 y1 = 0; - S32 x2 = mRightArrowBtn->getRect().mLeft; - S32 y2 = 1; - if (mTabList.size() > 0) + // Draw some of the buttons... + LLRect clip_rect = getLocalRect(); + if (has_scroll_arrows) { - y2 = mTabList[0]->mButton->getRect().mTop; + // ...but clip them. + clip_rect.mLeft = mLeftArrowBtn->getRect().mRight; + clip_rect.mRight = mRightArrowBtn->getRect().mLeft; } - LLUI::setScissorRegionLocal(LLRect(x1, y2, x2, y1)); - } + LLLocalClipRect clip(clip_rect); - S32 max_scroll_visible = mTabList.size() - mMaxScrollPos + mScrollPos; - S32 idx = 0; - for(tuple_list_t::iterator iter = mTabList.begin(); iter != mTabList.end(); ++iter) - { - LLTabTuple* tuple = *iter; + S32 max_scroll_visible = mTabList.size() - mMaxScrollPos + mScrollPos; + S32 idx = 0; + for(tuple_list_t::iterator iter = mTabList.begin(); iter != mTabList.end(); ++iter) + { + LLTabTuple* tuple = *iter; - tuple->mButton->translate( left - tuple->mButton->getRect().mLeft, 0 ); - left += tuple->mButton->getRect().getWidth(); + tuple->mButton->translate( left - tuple->mButton->getRect().mLeft, 0 ); + left += tuple->mButton->getRect().getWidth(); - if( idx < mScrollPos ) - { - if( tuple->mButton->getFlashing() ) + if( idx < mScrollPos ) { - mLeftArrowBtn->setFlashing( TRUE ); + if( tuple->mButton->getFlashing() ) + { + mLeftArrowBtn->setFlashing( TRUE ); + } } - } - else - if( max_scroll_visible < idx ) - { - if( tuple->mButton->getFlashing() ) + else + if( max_scroll_visible < idx ) { - mRightArrowBtn->setFlashing( TRUE ); + if( tuple->mButton->getFlashing() ) + { + mRightArrowBtn->setFlashing( TRUE ); + } } - } - LLUI::pushMatrix(); - { - LLUI::translate((F32)tuple->mButton->getRect().mLeft, (F32)tuple->mButton->getRect().mBottom, 0.f); - tuple->mButton->draw(); + LLUI::pushMatrix(); + { + LLUI::translate((F32)tuple->mButton->getRect().mLeft, (F32)tuple->mButton->getRect().mBottom, 0.f); + tuple->mButton->draw(); + } + LLUI::popMatrix(); + + idx++; } - LLUI::popMatrix(); - - idx++; } mLeftArrowBtn->setFlashing(FALSE); @@ -1608,12 +1610,12 @@ BOOL LLTabContainer::handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop, EDrag return LLView::handleDragAndDrop(x, y, mask, drop, type, cargo_data, accept, tooltip); } -void LLTabContainer::setTabImage(LLPanel* child, std::string image_name) +void LLTabContainer::setTabImage(LLPanel* child, std::string image_name, const LLColor4& color) { LLTabTuple* tuple = getTabByPanel(child); if( tuple ) { - tuple->mButton->setImageOverlay(image_name, LLFontGL::RIGHT); + tuple->mButton->setImageOverlay(image_name, LLFontGL::RIGHT, color); const LLFontGL* fontp = gResMgr->getRes( LLFONT_SANSSERIF_SMALL ); // remove current width from total tab strip width @@ -1622,7 +1624,11 @@ void LLTabContainer::setTabImage(LLPanel* child, std::string image_name) S32 image_overlay_width = tuple->mButton->getImageOverlay().notNull() ? tuple->mButton->getImageOverlay()->getWidth(0) : 0; - tuple->mButton->reshape(llclamp(fontp->getWidth(tuple->mButton->getLabelSelected()) + TAB_PADDING + image_overlay_width, mMinTabWidth, mMaxTabWidth), + + tuple->mPadding = image_overlay_width; + + tuple->mButton->setRightHPad(tuple->mPadding + LLBUTTON_H_PAD); + tuple->mButton->reshape(llclamp(fontp->getWidth(tuple->mButton->getLabelSelected()) + TAB_PADDING + tuple->mPadding, mMinTabWidth, mMaxTabWidth), tuple->mButton->getRect().getHeight()); // add back in button width to total tab strip width mTotalTabWidth += tuple->mButton->getRect().getWidth(); diff --git a/indra/llui/lltabcontainer.h b/indra/llui/lltabcontainer.h index a395fd94af..4665983402 100644 --- a/indra/llui/lltabcontainer.h +++ b/indra/llui/lltabcontainer.h @@ -67,7 +67,7 @@ public: BOOL placeholder = FALSE, eInsertionPoint insertion_point = END) = 0; virtual void addPlaceholder(LLPanel* child, const LLString& label); - virtual void lockTabs(); + virtual void lockTabs(S32 num_tabs = 0); virtual void enableTabButton(S32 which, BOOL enable); @@ -94,7 +94,7 @@ public: BOOL getTabPanelFlashing(LLPanel* child); void setTabPanelFlashing(LLPanel* child, BOOL state); - virtual void setTabImage(LLPanel* child, std::string img_name); + virtual void setTabImage(LLPanel* child, std::string img_name, const LLColor4& color = LLColor4::white); void setTitle( const LLString& title ); const LLString getPanelTitle(S32 index); @@ -135,7 +135,8 @@ protected: mOnChangeCallback( cb ), mUserData( userdata ), mOldState(FALSE), - mPlaceholderText(placeholder) + mPlaceholderText(placeholder), + mPadding(0) {} LLTabContainerCommon* mTabContainer; @@ -145,11 +146,13 @@ protected: void* mUserData; BOOL mOldState; LLTextBox* mPlaceholderText; + S32 mPadding; }; typedef std::vector<LLTabTuple*> tuple_list_t; tuple_list_t mTabList; S32 mCurrentTabIdx; + BOOL mTabsHidden; BOOL mScrolled; LLFrameTimer mScrollTimer; @@ -208,7 +211,7 @@ public: /*virtual*/ void removeTabPanel( LLPanel* child ); /*virtual*/ void setPanelTitle(S32 index, const LLString& title); - /*virtual*/ void setTabImage(LLPanel* child, std::string img_name); + /*virtual*/ void setTabImage(LLPanel* child, std::string img_name, const LLColor4& color = LLColor4::white); /*virtual*/ void setRightTabBtnOffset( S32 offset ); /*virtual*/ void setMinTabWidth(S32 width); diff --git a/indra/llui/lltexteditor.cpp b/indra/llui/lltexteditor.cpp index eb59765927..ea5897e28e 100644 --- a/indra/llui/lltexteditor.cpp +++ b/indra/llui/lltexteditor.cpp @@ -2993,8 +2993,7 @@ void LLTextEditor::draw() if( getVisible() ) { { - LLGLEnable scissor_test(GL_SCISSOR_TEST); - LLUI::setScissorRegionLocal(LLRect(0, mRect.getHeight(), mRect.getWidth() - (mScrollbar->getVisible() ? SCROLLBAR_SIZE : 0), 0)); + LLLocalClipRect clip(LLRect(0, mRect.getHeight(), mRect.getWidth() - (mScrollbar->getVisible() ? SCROLLBAR_SIZE : 0), 0)); bindEmbeddedChars( mGLFont ); diff --git a/indra/llui/llui.cpp b/indra/llui/llui.cpp index c65500b56c..696be050ce 100644 --- a/indra/llui/llui.cpp +++ b/indra/llui/llui.cpp @@ -57,6 +57,8 @@ LLVector2 LLUI::sGLScaleFactor(1.f, 1.f); LLWindow* LLUI::sWindow = NULL; LLHtmlHelp* LLUI::sHtmlHelp = NULL; BOOL LLUI::sShowXUINames = FALSE; +std::stack<LLRect> LLUI::sClipRectStack; + // // Functions // @@ -90,7 +92,7 @@ void make_ui_sound(const LLString& name) { llinfos << "ui sound name: " << name << llendl; } - LLUI::sAudioCallback(uuid, LLUI::sConfigGroup->getF32("AudioLevelUI")); + LLUI::sAudioCallback(uuid); } } } @@ -1791,3 +1793,59 @@ void LLUI::setHtmlHelp(LLHtmlHelp* html_help) { LLUI::sHtmlHelp = html_help; } + +//static +void LLUI::pushClipRect(const LLRect& rect) +{ + LLRect combined_clip_rect = rect; + if (!sClipRectStack.empty()) + { + combined_clip_rect.intersectWith(sClipRectStack.top()); + } + sClipRectStack.push(combined_clip_rect); + setScissorRegionScreen(combined_clip_rect); +} + +//static +void LLUI::popClipRect() +{ + sClipRectStack.pop(); + if (!sClipRectStack.empty()) + { + setScissorRegionScreen(sClipRectStack.top()); + } +} + +LLClipRect::LLClipRect(const LLRect& rect, BOOL enabled) : mScissorState(GL_SCISSOR_TEST, enabled), mEnabled(enabled) +{ + if (mEnabled) + { + LLUI::pushClipRect(rect); + } +} + +LLClipRect::~LLClipRect() +{ + if (mEnabled) + { + LLUI::popClipRect(); + } +} + +LLLocalClipRect::LLLocalClipRect(const LLRect &rect, BOOL enabled) : mScissorState(GL_SCISSOR_TEST, enabled), mEnabled(enabled) +{ + if (mEnabled) + { + LLRect scissor_rect = rect; + scissor_rect.translate(LLFontGL::sCurOrigin.mX, LLFontGL::sCurOrigin.mY); + LLUI::pushClipRect(scissor_rect); + } +} + +LLLocalClipRect::~LLLocalClipRect() +{ + if (mEnabled) + { + LLUI::popClipRect(); + } +} diff --git a/indra/llui/llui.h b/indra/llui/llui.h index dbe79338e5..fefe75f43e 100644 --- a/indra/llui/llui.h +++ b/indra/llui/llui.h @@ -16,6 +16,8 @@ #include "llrect.h" #include "llcoord.h" #include "llhtmlhelp.h" +#include "llgl.h" +#include <stack> class LLColor4; class LLVector3; @@ -123,7 +125,7 @@ extern BOOL gShowTextEditCursor; extern LLString gLanguage; class LLImageProviderInterface; -typedef void (*LLUIAudioCallback)(const LLUUID& uuid, F32 volume); +typedef void (*LLUIAudioCallback)(const LLUUID& uuid); class LLUI { @@ -144,8 +146,8 @@ public: //helper functions (should probably move free standing rendering helper functions here) static LLString locateSkin(const LLString& filename); - static void setScissorRegionScreen(const LLRect& rect); - static void setScissorRegionLocal(const LLRect& rect); // works assuming LLUI::translate has been called + static void pushClipRect(const LLRect& rect); + static void popClipRect(); static void setCursorPositionScreen(S32 x, S32 y); static void setCursorPositionLocal(LLView* viewp, S32 x, S32 y); static void setScaleFactor(const LLVector2& scale_factor); @@ -153,6 +155,11 @@ public: static LLUUID findAssetUUIDByName(const LLString& name); static LLVector2 getWindowSize(); static void setHtmlHelp(LLHtmlHelp* html_help); + +private: + static void setScissorRegionScreen(const LLRect& rect); + static void setScissorRegionLocal(const LLRect& rect); // works assuming LLUI::translate has been called + public: static LLControlGroup* sConfigGroup; static LLControlGroup* sColorsGroup; @@ -163,6 +170,8 @@ public: static LLWindow* sWindow; static BOOL sShowXUINames; static LLHtmlHelp* sHtmlHelp; + static std::stack<LLRect> sClipRectStack; + }; // UI widgets @@ -251,6 +260,7 @@ typedef enum e_widget_type WIDGET_TYPE_TEXTURE_VIEW, WIDGET_TYPE_MEMORY_VIEW, WIDGET_TYPE_FRAME_STAT_VIEW, + WIDGET_TYPE_LAYOUT_STACK, WIDGET_TYPE_DONTCARE, WIDGET_TYPE_COUNT } EWidgetType; @@ -272,38 +282,38 @@ public: } // default show and hide methods - static T* showInstance(const LLSD& seed) + static T* showInstance(const LLSD& seed = LLSD()) { T* instance = INSTANCE_ADAPTOR::getInstance(seed); INSTANCE_ADAPTOR::show(instance); return instance; } - static void hideInstance(const LLSD& seed) + static void hideInstance(const LLSD& seed = LLSD()) { T* instance = INSTANCE_ADAPTOR::getInstance(seed); INSTANCE_ADAPTOR::hide(instance); } - static void toggleInstance(const LLSD& seed) + static void toggleInstance(const LLSD& seed = LLSD()) { - if (!INSTANCE_ADAPTOR::instanceVisible(seed)) + if (INSTANCE_ADAPTOR::instanceVisible(seed)) { - INSTANCE_ADAPTOR::showInstance(seed); + INSTANCE_ADAPTOR::hideInstance(seed); } else { - INSTANCE_ADAPTOR::hideInstance(seed); + INSTANCE_ADAPTOR::showInstance(seed); } } - static BOOL instanceVisible(const LLSD& seed) + static BOOL instanceVisible(const LLSD& seed = LLSD()) { T* instance = INSTANCE_ADAPTOR::findInstance(seed); return instance != NULL && INSTANCE_ADAPTOR::visible(instance); } - static T* getInstance(const LLSD& seed) + static T* getInstance(const LLSD& seed = LLSD()) { T* instance = INSTANCE_ADAPTOR::findInstance(seed); if (instance == NULL) @@ -312,6 +322,7 @@ public: } return instance; } + }; // Creates a UI singleton by ignoring the identifying parameter @@ -326,12 +337,12 @@ public: LLUISingleton() : LLUIInstanceMgr<T, INSTANCE_ADAPTOR>() { sInstance = (T*)this; } ~LLUISingleton() { sInstance = NULL; } - static T* findInstance(const LLSD& seed) + static T* findInstance(const LLSD& seed = LLSD()) { return sInstance; } - static T* createInstance(const LLSD& seed) + static T* createInstance(const LLSD& seed = LLSD()) { if (sInstance == NULL) { @@ -346,4 +357,24 @@ protected: template <class T, class U> T* LLUISingleton<T,U>::sInstance = NULL; +class LLClipRect +{ +public: + LLClipRect(const LLRect& rect, BOOL enabled = TRUE); + virtual ~LLClipRect(); +protected: + LLGLState mScissorState; + BOOL mEnabled; +}; + +class LLLocalClipRect +{ +public: + LLLocalClipRect(const LLRect& rect, BOOL enabled = TRUE); + virtual ~LLLocalClipRect(); +protected: + LLGLState mScissorState; + BOOL mEnabled; +}; + #endif diff --git a/indra/llui/lluictrl.cpp b/indra/llui/lluictrl.cpp index abf796fde0..46bb977f7e 100644 --- a/indra/llui/lluictrl.cpp +++ b/indra/llui/lluictrl.cpp @@ -236,7 +236,7 @@ public: /*virtual*/ void operator() (LLView * parent, viewList_t &children) const { children.sort(CompareByDefaultTabGroup(parent->getCtrlOrder(), parent->getDefaultTabGroup())); - } + } }; BOOL LLUICtrl::focusFirstItem(BOOL prefer_text_fields) diff --git a/indra/llui/lluictrlfactory.cpp b/indra/llui/lluictrlfactory.cpp index 1d149b63e2..b4551c1852 100644 --- a/indra/llui/lluictrlfactory.cpp +++ b/indra/llui/lluictrlfactory.cpp @@ -69,7 +69,7 @@ const LLString LLUICtrlFactory::sUICtrlNames[WIDGET_TYPE_COUNT] = LLString("web_browser"), //WIDGET_TYPE_WEBBROWSER LLString("slider"), //WIDGET_TYPE_SLIDER, actually LLSliderCtrl LLString("slider_bar"), //WIDGET_TYPE_SLIDER_BAR, actually LLSlider - LLString("volume_slider"), //WIDGET_TYPE_VOLUME_SLIDER, actually LLVolumeSliderCtrl + LLString("volume_slider"), //WIDGET_TYPE_VOLUME_SLIDER, actually LLSlider + "volume" param LLString("spinner"), //WIDGET_TYPE_SPINNER, actually LLSpinCtrl LLString("text_editor"), //WIDGET_TYPE_TEXT_EDITOR LLString("texture_picker"),//WIDGET_TYPE_TEXTURE_PICKER @@ -135,6 +135,7 @@ const LLString LLUICtrlFactory::sUICtrlNames[WIDGET_TYPE_COUNT] = LLString("texture_view"), //WIDGET_TYPE_TEXTURE_VIEW LLString("memory_view"), //WIDGET_TYPE_MEMORY_VIEW LLString("frame_stat_view"), //WIDGET_TYPE_FRAME_STAT_VIEW + LLString("layout_stack"), //WIDGET_TYPE_LAYOUT_STACK LLString("DONT_CARE"), //WIDGET_TYPE_DONTCARE }; @@ -177,6 +178,7 @@ LLUICtrlFactory::LLUICtrlFactory() LLUICtrlCreator<LLScrollListCtrl>::registerCreator(LL_SCROLL_LIST_CTRL_TAG, this); LLUICtrlCreator<LLSliderCtrl>::registerCreator(LL_SLIDER_CTRL_TAG, this); LLUICtrlCreator<LLSlider>::registerCreator(LL_SLIDER_TAG, this); + LLUICtrlCreator<LLSlider>::registerCreator(LL_VOLUME_SLIDER_CTRL_TAG, this); LLUICtrlCreator<LLSpinCtrl>::registerCreator(LL_SPIN_CTRL_TAG, this); LLUICtrlCreator<LLTextBox>::registerCreator(LL_TEXT_BOX_TAG, this); LLUICtrlCreator<LLRadioGroup>::registerCreator(LL_RADIO_GROUP_TAG, this); @@ -190,6 +192,7 @@ LLUICtrlFactory::LLUICtrlFactory() LLUICtrlCreator<LLMenuGL>::registerCreator(LL_MENU_GL_TAG, this); LLUICtrlCreator<LLMenuBarGL>::registerCreator(LL_MENU_BAR_GL_TAG, this); LLUICtrlCreator<LLScrollingPanelList>::registerCreator(LL_SCROLLING_PANEL_LIST_TAG, this); + LLUICtrlCreator<LLLayoutStack>::registerCreator(LL_LAYOUT_STACK_TAG, this); setupPaths(); @@ -745,6 +748,37 @@ LLScrollingPanelList* LLUICtrlFactory::getScrollingPanelList(LLPanel* panelp, co return (LLScrollingPanelList*)panelp->getCtrlByNameAndType(name, WIDGET_TYPE_SCROLLING_PANEL_LIST); } + +LLCtrlListInterface* LLUICtrlFactory::getListInterfaceByName(LLPanel* panelp, const LLString& name) +{ + LLView* viewp = panelp->getCtrlByNameAndType(name, WIDGET_TYPE_DONTCARE); + if (viewp && viewp->isCtrl()) + { + return ((LLUICtrl*)viewp)->getListInterface(); + } + return NULL; +} + +LLCtrlSelectionInterface* LLUICtrlFactory::getSelectionInterfaceByName(LLPanel* panelp, const LLString& name) +{ + LLView* viewp = panelp->getCtrlByNameAndType(name, WIDGET_TYPE_DONTCARE); + if (viewp && viewp->isCtrl()) + { + return ((LLUICtrl*)viewp)->getSelectionInterface(); + } + return NULL; +} + +LLCtrlScrollInterface* LLUICtrlFactory::getScrollInterfaceByName(LLPanel* panelp, const LLString& name) +{ + LLView* viewp = panelp->getCtrlByNameAndType(name, WIDGET_TYPE_DONTCARE); + if (viewp && viewp->isCtrl()) + { + return ((LLUICtrl*)viewp)->getScrollInterface(); + } + return NULL; +} + void LLUICtrlFactory::registerCreator(LLString ctrlname, creator_function_t function) { LLString::toLower(ctrlname); diff --git a/indra/llui/lluictrlfactory.h b/indra/llui/lluictrlfactory.h index c7280aa4a2..04a8a83cfa 100644 --- a/indra/llui/lluictrlfactory.h +++ b/indra/llui/lluictrlfactory.h @@ -45,6 +45,9 @@ class LLWebBrowserCtrl; class LLViewBorder; class LLColorSwatchCtrl; class LLScrollingPanelList; +class LLCtrlListInterface; +class LLCtrlSelectionInterface; +class LLCtrlScrollInterface; // Widget @@ -103,6 +106,11 @@ public: static LLMenuItemCallGL* getMenuItemCallByName(LLPanel* panelp, const LLString& name); static LLScrollingPanelList* getScrollingPanelList(LLPanel* panelp, const LLString& name); + // interface getters + static LLCtrlListInterface* getListInterfaceByName(LLPanel* panelp, const LLString& name); + static LLCtrlSelectionInterface* getSelectionInterfaceByName(LLPanel* panelp, const LLString& name); + static LLCtrlScrollInterface* getScrollInterfaceByName(LLPanel* panelp, const LLString& name); + LLPanel* createFactoryPanel(LLString name); virtual LLView* createCtrlWidget(LLPanel *parent, LLXMLNodePtr node); diff --git a/indra/llui/lluistring.cpp b/indra/llui/lluistring.cpp index 8c5b587158..900a867164 100644 --- a/indra/llui/lluistring.cpp +++ b/indra/llui/lluistring.cpp @@ -10,6 +10,9 @@ #include "lluistring.h" +const LLString::format_map_t LLUIString::sNullArgs; + + // public LLUIString::LLUIString(const LLString& instring, const LLString::format_map_t& args) diff --git a/indra/llui/lluistring.h b/indra/llui/lluistring.h index 8c2e3c481c..c3113cbe74 100644 --- a/indra/llui/lluistring.h +++ b/indra/llui/lluistring.h @@ -75,6 +75,8 @@ public: void insert(S32 charidx, const LLWString& wchars); void replace(S32 charidx, llwchar wc); + static const LLString::format_map_t sNullArgs; + private: void format(); diff --git a/indra/llui/llview.cpp b/indra/llui/llview.cpp index dbc635830b..b87d82653a 100644 --- a/indra/llui/llview.cpp +++ b/indra/llui/llview.cpp @@ -305,6 +305,10 @@ void LLView::moveChildToFrontOfTabGroup(LLUICtrl* child) void LLView::addChild(LLView* child, S32 tab_group) { + if (mParentView == child) + { + llerrs << "Adding view " << child->getName() << " as child of itself" << llendl; + } // remove from current parent if (child->mParentView) { @@ -328,6 +332,10 @@ void LLView::addChild(LLView* child, S32 tab_group) void LLView::addChildAtEnd(LLView* child, S32 tab_group) { + if (mParentView == child) + { + llerrs << "Adding view " << child->getName() << " as child of itself" << llendl; + } // remove from current parent if (child->mParentView) { @@ -732,18 +740,22 @@ void LLView::setEnabled(BOOL enabled) // virtual void LLView::setVisible(BOOL visible) { - if( !visible && (gFocusMgr.getTopCtrl() == this) ) - { - gFocusMgr.setTopCtrl( NULL ); - } - if ( mVisible != visible ) { - // tell all children of this view that the visibility may have changed - onVisibilityChange ( visible ); - } + if( !visible && (gFocusMgr.getTopCtrl() == this) ) + { + gFocusMgr.setTopCtrl( NULL ); + } - mVisible = visible; + mVisible = visible; + + // notify children of visibility change if root, or part of visible hierarchy + if (!getParent() || getParent()->isInVisibleChain()) + { + // tell all children of this view that the visibility may have changed + onVisibilityChange( visible ); + } + } } // virtual @@ -758,7 +770,7 @@ BOOL LLView::setLabelArg(const LLString& key, const LLString& text) return FALSE; } -void LLView::onVisibilityChange ( BOOL curVisibilityIn ) +void LLView::onVisibilityChange ( BOOL new_visibility ) { for ( child_list_iter_t child_it = mChildList.begin(); child_it != mChildList.end(); ++child_it) { @@ -766,7 +778,7 @@ void LLView::onVisibilityChange ( BOOL curVisibilityIn ) // only views that are themselves visible will have their overall visibility affected by their ancestors if (viewp->getVisible()) { - viewp->onVisibilityChange ( curVisibilityIn ); + viewp->onVisibilityChange ( new_visibility ); } } } @@ -1370,64 +1382,61 @@ LLView* LLView::childrenHandleRightMouseUp(S32 x, S32 y, MASK mask) void LLView::draw() { - if (getVisible()) + if (sDebugRects) { - if (sDebugRects) - { - drawDebugRect(); + drawDebugRect(); - // Check for bogus rectangle - if (mRect.mRight <= mRect.mLeft - || mRect.mTop <= mRect.mBottom) - { - llwarns << "Bogus rectangle for " << getName() << " with " << mRect << llendl; - } + // Check for bogus rectangle + if (mRect.mRight <= mRect.mLeft + || mRect.mTop <= mRect.mBottom) + { + llwarns << "Bogus rectangle for " << getName() << " with " << mRect << llendl; } + } - LLRect rootRect = getRootView()->getRect(); - LLRect screenRect; + LLRect rootRect = getRootView()->getRect(); + LLRect screenRect; - // draw focused control on top of everything else - LLView* focus_view = gFocusMgr.getKeyboardFocus(); - if (focus_view && focus_view->getParent() != this) - { - focus_view = NULL; - } + // draw focused control on top of everything else + LLView* focus_view = gFocusMgr.getKeyboardFocus(); + if (focus_view && focus_view->getParent() != this) + { + focus_view = NULL; + } - for (child_list_reverse_iter_t child_iter = mChildList.rbegin(); child_iter != mChildList.rend(); ++child_iter) - { - LLView *viewp = *child_iter; - ++sDepth; + for (child_list_reverse_iter_t child_iter = mChildList.rbegin(); child_iter != mChildList.rend(); ++child_iter) + { + LLView *viewp = *child_iter; + ++sDepth; - if (viewp->getVisible() && viewp != focus_view) + if (viewp->getVisible() && viewp != focus_view) + { + // Only draw views that are within the root view + localRectToScreen(viewp->getRect(),&screenRect); + if ( rootRect.rectInRect(&screenRect) ) { - // Only draw views that are within the root view - localRectToScreen(viewp->getRect(),&screenRect); - if ( rootRect.rectInRect(&screenRect) ) + glMatrixMode(GL_MODELVIEW); + LLUI::pushMatrix(); { - glMatrixMode(GL_MODELVIEW); - LLUI::pushMatrix(); - { - LLUI::translate((F32)viewp->getRect().mLeft, (F32)viewp->getRect().mBottom, 0.f); - viewp->draw(); - } - LLUI::popMatrix(); + LLUI::translate((F32)viewp->getRect().mLeft, (F32)viewp->getRect().mBottom, 0.f); + viewp->draw(); } + LLUI::popMatrix(); } - - --sDepth; } - if (focus_view && focus_view->getVisible()) - { - drawChild(focus_view); - } + --sDepth; + } - // HACK - if (sEditingUI && this == sEditingUIView) - { - drawDebugRect(); - } + if (focus_view && focus_view->getVisible()) + { + drawChild(focus_view); + } + + // HACK + if (sEditingUI && this == sEditingUIView) + { + drawDebugRect(); } } @@ -1480,13 +1489,13 @@ void LLView::drawDebugRect() } } -void LLView::drawChild(LLView* childp, S32 x_offset, S32 y_offset) +void LLView::drawChild(LLView* childp, S32 x_offset, S32 y_offset, BOOL force_draw) { if (childp && childp->getParent() == this) { ++sDepth; - if (childp->getVisible()) + if (childp->getVisible() || force_draw) { glMatrixMode(GL_MODELVIEW); LLUI::pushMatrix(); @@ -1616,7 +1625,7 @@ void LLView::updateRect() LLView* viewp = *child_it; if (viewp->getVisible()) { - child_spanning_rect |= viewp->mRect; + child_spanning_rect.unionWith(viewp->mRect); } } diff --git a/indra/llui/llview.h b/indra/llui/llview.h index f9875e8cca..18f453f621 100644 --- a/indra/llui/llview.h +++ b/indra/llui/llview.h @@ -359,7 +359,7 @@ public: virtual void draw(); void drawDebugRect(); - void drawChild(LLView* childp, S32 x_offset = 0, S32 y_offset = 0); + void drawChild(LLView* childp, S32 x_offset = 0, S32 y_offset = 0, BOOL force_draw = FALSE); virtual const LLString& getName() const; diff --git a/indra/mac_updater/mac_updater.cpp b/indra/mac_updater/mac_updater.cpp index 91bf24ec11..ecef6e6b77 100644 --- a/indra/mac_updater/mac_updater.cpp +++ b/indra/mac_updater/mac_updater.cpp @@ -971,10 +971,12 @@ void *updatethreadproc(void*) if(len < sizeof(temp)-1) { // End of file or error. - if(pclose(mounter) != 0) + int result = pclose(mounter); + if(result != 0) { - llinfos << "Failed to mount disk image, exiting."<< llendl; - throw 0; + // NOTE: We used to abort here, but pclose() started returning + // -1, possibly when the size of the DMG passed a certain point + llinfos << "Unexpected result closing pipe: " << result << llendl; } mounter = NULL; } @@ -1000,6 +1002,7 @@ void *updatethreadproc(void*) else { llinfos << "Disk image device node not found!" << llendl; + throw 0; } // Get an FSRef to the new application on the disk image diff --git a/indra/newview/English.lproj/InfoPlist.strings b/indra/newview/English.lproj/InfoPlist.strings index 5991f9150f..aa9b6c054a 100644 --- a/indra/newview/English.lproj/InfoPlist.strings +++ b/indra/newview/English.lproj/InfoPlist.strings @@ -1,5 +1,5 @@ /* Localized versions of Info.plist keys */ CFBundleName = "Second Life"; -CFBundleShortVersionString = "Second Life version 1.18.1.1"; -CFBundleGetInfoString = "Second Life version 1.18.1.1, Copyright 2004-2007 Linden Research, Inc."; +CFBundleShortVersionString = "Second Life version 1.18.1.2"; +CFBundleGetInfoString = "Second Life version 1.18.1.2, Copyright 2004-2007 Linden Research, Inc."; diff --git a/indra/newview/Info-SecondLife.plist b/indra/newview/Info-SecondLife.plist index 9bef82e0bc..91d1849688 100644 --- a/indra/newview/Info-SecondLife.plist +++ b/indra/newview/Info-SecondLife.plist @@ -32,7 +32,7 @@ </dict> </array> <key>CFBundleVersion</key> - <string>1.18.1.1</string> + <string>1.18.1.2</string> <key>CSResourcesFileMapped</key> <true/> </dict> diff --git a/indra/newview/llagent.cpp b/indra/newview/llagent.cpp index 45f0b448e2..7ed11c1154 100644 --- a/indra/newview/llagent.cpp +++ b/indra/newview/llagent.cpp @@ -41,6 +41,7 @@ #include "llface.h" #include "llfirstuse.h" #include "llfloater.h" +#include "llfloateractivespeakers.h" #include "llfloateravatarinfo.h" #include "llfloaterbuildoptions.h" #include "llfloaterchat.h" @@ -97,6 +98,7 @@ #include "pipeline.h" #include "roles_constants.h" #include "viewer.h" +#include "llvoiceclient.h" // Ventrella #include "llfollowcam.h" @@ -326,7 +328,7 @@ LLAgent::LLAgent() mEffectColor(0.f, 1.f, 1.f, 1.f), mHaveHomePosition(FALSE), mHomeRegionHandle( 0 ), - mNearChatRadius(10.f), + mNearChatRadius(CHAT_NORMAL_RADIUS / 2.f), mGodLevel( GOD_NOT ), @@ -2824,7 +2826,7 @@ void LLAgent::endAnimationUpdateUI() gMorphView->setVisible(FALSE); } - gIMView->setFloaterOpen( FALSE ); + gIMMgr->setFloaterOpen( FALSE ); gConsole->setVisible( TRUE ); if (mAvatarObject) @@ -3239,6 +3241,19 @@ void LLAgent::updateCamera() setLookAt(LOOKAT_TARGET_FOCUS, NULL, mCameraPositionAgent); } + // Send the camera position to the spatialized voice system. + if(gVoiceClient && getRegion()) + { + LLMatrix3 rot; + rot.setRows(gCamera->getAtAxis(), gCamera->getLeftAxis (), gCamera->getUpAxis()); + + // MBW -- XXX -- Setting velocity to 0 for now. May figure it out later... + gVoiceClient->setCameraPosition( + getRegion()->getPosGlobalFromRegion(gCamera->getOrigin()),// position + LLVector3::zero, // velocity + rot); // rotation matrix + } + // update the travel distance stat // this isn't directly related to the camera // but this seemed like the best place to do this @@ -3249,7 +3264,7 @@ void LLAgent::updateCamera() mDistanceTraveled += delta.magVec(); } mLastPositionGlobal = global_pos; - + if (LLVOAvatar::sVisibleInFirstPerson && mAvatarObject.notNull() && !mAvatarObject->mIsSitting && cameraMouselook()) { LLVector3 head_pos = mAvatarObject->mHeadp->getWorldPosition() + @@ -4445,18 +4460,23 @@ void LLAgent::setFocusOnAvatar(BOOL focus_on_avatar, BOOL animate) //----------------------------------------------------------------------------- // heardChat() //----------------------------------------------------------------------------- -void LLAgent::heardChat(const LLChat& chat) +void LLAgent::heardChat(const LLUUID& id) { - if (chat.mChatType == CHAT_TYPE_START - || chat.mChatType == CHAT_TYPE_STOP) + // log text and voice chat to speaker mgr + // for keeping track of active speakers, etc. + gLocalSpeakerMgr->speakerChatted(id); + + // don't respond to your own voice + if (id == getID()) return; + + if (ll_rand(2) == 0) { - return; - } + LLViewerObject *chatter = gObjectList.findObject(mLastChatterID); + setLookAt(LOOKAT_TARGET_AUTO_LISTEN, chatter, LLVector3::zero); + } - mLastChatterID = chat.mFromID; + mLastChatterID = id; mChatTimer.reset(); - - mNearChatRadius = CHAT_NORMAL_RADIUS / 2.f; } //----------------------------------------------------------------------------- @@ -5048,14 +5068,6 @@ void LLAgent::initOriginGlobal(const LLVector3d &origin_global) void update_group_floaters(const LLUUID& group_id) { - // *HACK: added to do a live update of the groups floater if it is - // open. - LLFloaterGroups* fg = LLFloaterGroups::getInstance(gAgent.getID()); - if(fg) - { - fg->reset(); - } - LLFloaterGroupInfo::refreshGroup(group_id); // update avatar info @@ -5065,10 +5077,10 @@ void update_group_floaters(const LLUUID& group_id) fa->resetGroupList(); } - if (gIMView) + if (gIMMgr) { // update the talk view - gIMView->refresh(); + gIMMgr->refresh(); } } @@ -5159,6 +5171,7 @@ void LLAgent::processAgentGroupDataUpdate(LLMessageSystem *msg, void **) if (need_floater_update) { update_group_floaters(group.mID); + gAgent.fireEvent(new LLEvent(&gAgent, "new group"), ""); } } @@ -5488,6 +5501,11 @@ bool LLAgent::teleportCore(bool is_local) gAgent.setTeleportState( LLAgent::TELEPORT_START ); } make_ui_sound("UISndTeleportOut"); + + // MBW -- Let the voice client know a teleport has begun so it can leave the existing channel. + // This was breaking the case of teleporting within a single sim. Backing it out for now. +// gVoiceClient->leaveChannel(); + return true; } diff --git a/indra/newview/llagent.h b/indra/newview/llagent.h index c6d11b5ae5..438239e717 100644 --- a/indra/newview/llagent.h +++ b/indra/newview/llagent.h @@ -170,7 +170,7 @@ public: void setObjectTracking(BOOL track) { mTrackFocusObject = track; } // void setLookingAtAvatar(BOOL looking); - void heardChat(const LLChat& chat); + void heardChat(const LLUUID& id); void lookAtLastChat(); LLUUID getLastChatter() { return mLastChatterID; } F32 getTypingTime() { return mTypingTimer.getElapsedTimeF32(); } diff --git a/indra/newview/llaudiosourcevo.cpp b/indra/newview/llaudiosourcevo.cpp index 86cc1e206e..9d83e5d80e 100644 --- a/indra/newview/llaudiosourcevo.cpp +++ b/indra/newview/llaudiosourcevo.cpp @@ -63,7 +63,7 @@ void LLAudioSourceVO::updateGain() { mute = TRUE; } - else if (gMuteListp->isMuted(mOwnerID)) + else if (gMuteListp->isMuted(mOwnerID, LLMute::flagObjectSounds)) { mute = TRUE; } diff --git a/indra/newview/llcallingcard.cpp b/indra/newview/llcallingcard.cpp index e93ce8bdff..37bdbbfee5 100644 --- a/indra/newview/llcallingcard.cpp +++ b/indra/newview/llcallingcard.cpp @@ -274,6 +274,7 @@ S32 LLAvatarTracker::addBuddyList(const LLAvatarTracker::buddy_map_t& buds) << "]" << llendl; } } + notifyObservers(); return new_buddy_count; } diff --git a/indra/newview/llchatbar.cpp b/indra/newview/llchatbar.cpp index 1a65903dcd..f5f0691dc6 100644 --- a/indra/newview/llchatbar.cpp +++ b/indra/newview/llchatbar.cpp @@ -27,6 +27,7 @@ #include "llgesturemgr.h" #include "llkeyboard.h" #include "lllineeditor.h" +#include "llstatusbar.h" #include "lltextbox.h" #include "lluiconstants.h" #include "llviewergesture.h" // for triggering gestures @@ -50,15 +51,18 @@ const F32 AGENT_TYPING_TIMEOUT = 5.f; // seconds LLChatBar *gChatBar = NULL; -LLChatBarGestureObserver* LLChatBar::sObserver = NULL; +// legacy calllback glue +void toggleChatHistory(void* user_data); class LLChatBarGestureObserver : public LLGestureManagerObserver { public: - LLChatBarGestureObserver() {} + LLChatBarGestureObserver(LLChatBar* chat_barp) : mChatBar(chat_barp){} virtual ~LLChatBarGestureObserver() {} - virtual void changed() { gChatBar->refreshGestures(); } + virtual void changed() { mChatBar->refreshGestures(); } +private: + LLChatBar* mChatBar; }; @@ -66,12 +70,29 @@ public: // Functions // -LLChatBar::LLChatBar(const std::string& name, const LLRect& rect) +//inline constructor +// for chat bars embedded in floaters, etc +LLChatBar::LLChatBar(const std::string& name) +: LLPanel(name, LLRect(), BORDER_NO), + mInputEditor(NULL), + mGestureLabelTimer(), + mLastSpecialChatChannel(0), + mIsBuilt(FALSE), + mDynamicLayout(FALSE), + mGestureCombo(NULL), + mObserver(NULL) +{ +} + +LLChatBar::LLChatBar(const std::string& name, const LLRect& rect) : LLPanel(name, rect, BORDER_NO), mInputEditor(NULL), mGestureLabelTimer(), mLastSpecialChatChannel(0), - mIsBuilt(FALSE) + mIsBuilt(FALSE), + mDynamicLayout(TRUE), + mGestureCombo(NULL), + mObserver(NULL) { setIsChrome(TRUE); @@ -87,29 +108,6 @@ LLChatBar::LLChatBar(const std::string& name, const LLRect& rect) // Start visible if we left the app while chatting. setVisible( gSavedSettings.getBOOL("ChatVisible") ); - mInputEditor = LLUICtrlFactory::getLineEditorByName(this, "Chat Editor"); - if (mInputEditor) - { - mInputEditor->setCallbackUserData(this); - mInputEditor->setKeystrokeCallback(&onInputEditorKeystroke); - mInputEditor->setFocusLostCallback(&onInputEditorFocusLost); - mInputEditor->setFocusReceivedCallback( &onInputEditorGainFocus ); - mInputEditor->setCommitOnFocusLost( FALSE ); - mInputEditor->setRevertOnEsc( FALSE ); - mInputEditor->setIgnoreTab(TRUE); - mInputEditor->setPassDelete(TRUE); - mInputEditor->setMaxTextLength(1023); - mInputEditor->setEnableLineHistory(TRUE); - } - - // Build the list of gestures - refreshGestures(); - - sObserver = new LLChatBarGestureObserver; - gGestureManager.addObserver(sObserver); - - mIsBuilt = TRUE; - // Apply custom layout. layout(); @@ -122,23 +120,43 @@ LLChatBar::LLChatBar(const std::string& name, const LLRect& rect) LLChatBar::~LLChatBar() { - delete sObserver; - sObserver = NULL; + delete mObserver; + mObserver = NULL; // LLView destructor cleans up children } BOOL LLChatBar::postBuild() { - childSetAction("History", LLFloaterChat::toggle, this); + childSetAction("History", toggleChatHistory, this); childSetAction("Say", onClickSay, this); childSetAction("Shout", onClickShout, this); - childSetCommitCallback("Gesture", onCommitGesture, this); - LLButton * sayp = static_cast<LLButton*>(getChildByName("Say")); + + // attempt to bind to an existing combo box named gesture + setGestureCombo(LLUICtrlFactory::getComboBoxByName(this, "Gesture")); + + LLButton * sayp = static_cast<LLButton*>(getChildByName("Say", TRUE)); if(sayp) { setDefaultBtn(sayp); } + mInputEditor = LLUICtrlFactory::getLineEditorByName(this, "Chat Editor"); + if (mInputEditor) + { + mInputEditor->setCallbackUserData(this); + mInputEditor->setKeystrokeCallback(&onInputEditorKeystroke); + mInputEditor->setFocusLostCallback(&onInputEditorFocusLost); + mInputEditor->setFocusReceivedCallback( &onInputEditorGainFocus ); + mInputEditor->setCommitOnFocusLost( FALSE ); + mInputEditor->setRevertOnEsc( FALSE ); + mInputEditor->setIgnoreTab(TRUE); + mInputEditor->setPassDelete(TRUE); + + mInputEditor->setMaxTextLength(1023); + } + + mIsBuilt = TRUE; + return TRUE; } @@ -186,7 +204,8 @@ BOOL LLChatBar::handleKeyHere( KEY key, MASK mask, BOOL called_from_parent ) handled = TRUE; } } - else if ( KEY_ESCAPE == key ) + // only do this in main chatbar + else if ( KEY_ESCAPE == key && gChatBar == this) { stopChat(); @@ -199,6 +218,8 @@ BOOL LLChatBar::handleKeyHere( KEY key, MASK mask, BOOL called_from_parent ) void LLChatBar::layout() { + if (!mDynamicLayout) return; + S32 rect_width = mRect.getWidth(); S32 count = 9; // number of elements in LLToolBar S32 pad = 4; @@ -261,13 +282,16 @@ void LLChatBar::refresh() // hide in mouselook, but keep previous visibility state //BOOL mouselook = gAgent.cameraMouselook(); // call superclass setVisible so that we don't overwrite the saved setting - LLPanel::setVisible(gSavedSettings.getBOOL("ChatVisible")); + if (mDynamicLayout) + { + LLPanel::setVisible(gSavedSettings.getBOOL("ChatVisible")); + } // HACK: Leave the name of the gesture in place for a few seconds. const F32 SHOW_GESTURE_NAME_TIME = 2.f; if (mGestureLabelTimer.getStarted() && mGestureLabelTimer.getElapsedTimeF32() > SHOW_GESTURE_NAME_TIME) { - LLCtrlListInterface* gestures = childGetListInterface("Gesture"); + LLCtrlListInterface* gestures = mGestureCombo ? mGestureCombo->getListInterface() : NULL; if (gestures) gestures->selectFirstItem(); mGestureLabelTimer.stop(); } @@ -277,6 +301,8 @@ void LLChatBar::refresh() gAgent.stopTyping(); } + childSetValue("History", LLFloaterChat::instanceVisible(LLSD())); + childSetEnabled("Say", mInputEditor->getText().size() > 0); childSetEnabled("Shout", mInputEditor->getText().size() > 0); @@ -284,16 +310,18 @@ void LLChatBar::refresh() void LLChatBar::refreshGestures() { - LLCtrlListInterface* gestures = childGetListInterface("Gesture"); - if (gestures) + LLCtrlListInterface* gestures = mGestureCombo ? mGestureCombo->getListInterface() : NULL; + if (mGestureCombo && gestures) { //store current selection so we can maintain it - LLString cur_gesture = childGetValue("Gesture").asString(); + LLString cur_gesture = mGestureCombo->getValue().asString(); gestures->selectFirstItem(); - LLString label = childGetValue("Gesture").asString(); + LLString label = mGestureCombo->getValue().asString();; // clear gestures->clearRows(); - // add gestures + + // collect list of unique gestures + std::map <std::string, BOOL> unique; LLGestureManager::item_map_t::iterator it; for (it = gGestureManager.mActive.begin(); it != gGestureManager.mActive.end(); ++it) { @@ -302,10 +330,18 @@ void LLChatBar::refreshGestures() { if (!gesture->mTrigger.empty()) { - gestures->addSimpleElement(gesture->mTrigger); + unique[gesture->mTrigger] = TRUE; } } } + + // ad unique gestures + std::map <std::string, BOOL>::iterator it2; + for (it2 = unique.begin(); it2 != unique.end(); ++it2) + { + gestures->addSimpleElement((*it2).first); + } + gestures->sortByColumn(0, TRUE); // Insert label after sorting gestures->addSimpleElement(label, ADD_TOP); @@ -362,6 +398,23 @@ LLString LLChatBar::getCurrentChat() return mInputEditor ? mInputEditor->getText() : LLString::null; } +void LLChatBar::setGestureCombo(LLComboBox* combo) +{ + mGestureCombo = combo; + if (mGestureCombo) + { + mGestureCombo->setCommitCallback(onCommitGesture); + mGestureCombo->setCallbackUserData(this); + + // now register observer since we have a place to put the results + mObserver = new LLChatBarGestureObserver(this); + gGestureManager.addObserver(mObserver); + + // refresh list from current active gestures + refreshGestures(); + } +} + //----------------------------------------------------------------------- // Internal functions //----------------------------------------------------------------------- @@ -453,13 +506,13 @@ void LLChatBar::sendChat( EChatType type ) sendChatFromViewer(utf8_revised_text, type, TRUE); } } - childSetValue("Chat Editor", LLSD(LLString::null)); + childSetValue("Chat Editor", LLString::null); gAgent.stopTyping(); // If the user wants to stop chatting on hitting return, lose focus // and go out of chat mode. - if (gSavedSettings.getBOOL("CloseChatOnReturn")) + if (gChatBar == this && gSavedSettings.getBOOL("CloseChatOnReturn")) { stopChat(); } @@ -684,7 +737,7 @@ void LLChatBar::sendChatFromViewer(const LLWString &wtext, EChatType type, BOOL void LLChatBar::onCommitGesture(LLUICtrl* ctrl, void* data) { LLChatBar* self = (LLChatBar*)data; - LLCtrlListInterface* gestures = self->childGetListInterface("Gesture"); + LLCtrlListInterface* gestures = self->mGestureCombo ? self->mGestureCombo->getListInterface() : NULL; if (gestures) { S32 index = gestures->getFirstSelectedIndex(); @@ -708,6 +761,14 @@ void LLChatBar::onCommitGesture(LLUICtrl* ctrl, void* data) } } self->mGestureLabelTimer.start(); - // free focus back to chat bar - self->childSetFocus("Gesture", FALSE); + if (self->mGestureCombo != NULL) + { + // free focus back to chat bar + self->mGestureCombo->setFocus(FALSE); + } +} + +void toggleChatHistory(void* user_data) +{ + LLFloaterChat::toggleInstance(LLSD()); } diff --git a/indra/newview/llchatbar.h b/indra/newview/llchatbar.h index 65724a1f45..33198f5fe6 100644 --- a/indra/newview/llchatbar.h +++ b/indra/newview/llchatbar.h @@ -13,23 +13,21 @@ #include "llframetimer.h" #include "llchat.h" -class LLButton; -class LLComboBox; class LLLineEditor; class LLMessageSystem; -class LLTextBox; -class LLTextEditor; class LLUICtrl; class LLUUID; class LLFrameTimer; -class LLStatGraph; class LLChatBarGestureObserver; +class LLComboBox; class LLChatBar : public LLPanel { public: - LLChatBar(const std::string& name, const LLRect& rect ); + // constructor for inline chat-bars (e.g. hosted in chat history window) + LLChatBar(const std::string& name); + LLChatBar(const std::string& name, const LLRect& rect); ~LLChatBar(); virtual BOOL postBuild(); @@ -51,6 +49,10 @@ public: BOOL inputEditorHasFocus(); LLString getCurrentChat(); + // since chat bar logic is reused for chat history + // gesture combo box might not be a direct child + void setGestureCombo(LLComboBox* combo); + // Send a chat (after stripping /20foo channel chats). // "Animate" means the nodding animation for regular text. void sendChatFromViewer(const LLWString &wtext, EChatType type, BOOL animate); @@ -61,7 +63,6 @@ public: LLWString stripChannelNumber(const LLWString &mesg, S32* channel); // callbacks - static void onClickHistory( void* userdata ); static void onClickSay( void* userdata ); static void onClickShout( void* userdata ); @@ -89,8 +90,10 @@ protected: S32 mLastSpecialChatChannel; BOOL mIsBuilt; - - static LLChatBarGestureObserver* sObserver; + BOOL mDynamicLayout; + LLComboBox* mGestureCombo; + + LLChatBarGestureObserver* mObserver; }; extern LLChatBar *gChatBar; diff --git a/indra/newview/llcolorswatch.cpp b/indra/newview/llcolorswatch.cpp index 41f8f1d714..7973e4d952 100644 --- a/indra/newview/llcolorswatch.cpp +++ b/indra/newview/llcolorswatch.cpp @@ -240,21 +240,6 @@ void LLColorSwatchCtrl::setEnabled( BOOL enabled ) } -////////////////////////////////////////////////////////////////////////////// -// called when parent filters down a visibility changed message -void LLColorSwatchCtrl::onVisibilityChange ( BOOL curVisibilityIn ) -{ - // visibility changed - moved away to different tab for instance - cancel selection - //if ( ! curVisibilityIn) - //{ - // LLFloaterColorPicker* pickerp = (LLFloaterColorPicker*)LLFloater::getFloaterByHandle(mPickerHandle); - // if (pickerp) - // { - // pickerp->cancelSelection(); - // } - //} -} - void LLColorSwatchCtrl::setValue(const LLSD& value) { set(LLColor4(value), TRUE, TRUE); diff --git a/indra/newview/llcolorswatch.h b/indra/newview/llcolorswatch.h index 999dce1296..4b44c5aef9 100644 --- a/indra/newview/llcolorswatch.h +++ b/indra/newview/llcolorswatch.h @@ -68,8 +68,6 @@ public: virtual LLXMLNodePtr getXML(bool save_children = true) const; static LLView* fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory); - virtual void onVisibilityChange ( BOOL curVisibilityIn ); - static void onColorChanged ( void* data, EColorPickOp pick_op = COLOR_CHANGE ); protected: diff --git a/indra/newview/lldebugmessagebox.cpp b/indra/newview/lldebugmessagebox.cpp index c62459f461..d3e7f08a95 100644 --- a/indra/newview/lldebugmessagebox.cpp +++ b/indra/newview/lldebugmessagebox.cpp @@ -33,25 +33,25 @@ LLDebugVarMessageBox::LLDebugVarMessageBox(const std::string& title, EDebugVarTy switch(var_type) { case VAR_TYPE_F32: - mSlider1 = new LLSliderCtrl("slider 1", LLRect(20,130,190,110), title, NULL, 70, 130, TRUE, TRUE, NULL, NULL, *((F32*)var), -100.f, 100.f, 0.1f, NULL); + mSlider1 = new LLSliderCtrl("slider 1", LLRect(20,130,190,110), title, NULL, 70, 130, TRUE, TRUE, FALSE, NULL, NULL, *((F32*)var), -100.f, 100.f, 0.1f, NULL); mSlider1->setPrecision(3); addChild(mSlider1); mSlider2 = NULL; mSlider3 = NULL; break; case VAR_TYPE_S32: - mSlider1 = new LLSliderCtrl("slider 1", LLRect(20,100,190,80), title, NULL, 70, 130, TRUE, TRUE, NULL, NULL, (F32)*((S32*)var), -255.f, 255.f, 1.f, NULL); + mSlider1 = new LLSliderCtrl("slider 1", LLRect(20,100,190,80), title, NULL, 70, 130, TRUE, TRUE, FALSE, NULL, NULL, (F32)*((S32*)var), -255.f, 255.f, 1.f, NULL); mSlider1->setPrecision(0); addChild(mSlider1); mSlider2 = NULL; mSlider3 = NULL; break; case VAR_TYPE_VEC3: - mSlider1 = new LLSliderCtrl("slider 1", LLRect(20,130,190,110), "x: ", NULL, 70, 130, TRUE, TRUE, NULL, NULL, ((LLVector3*)var)->mV[VX], -100.f, 100.f, 0.1f, NULL); + mSlider1 = new LLSliderCtrl("slider 1", LLRect(20,130,190,110), "x: ", NULL, 70, 130, TRUE, TRUE, FALSE, NULL, NULL, ((LLVector3*)var)->mV[VX], -100.f, 100.f, 0.1f, NULL); mSlider1->setPrecision(3); - mSlider2 = new LLSliderCtrl("slider 2", LLRect(20,100,190,80), "y: ", NULL, 70, 130, TRUE, TRUE, NULL, NULL, ((LLVector3*)var)->mV[VY], -100.f, 100.f, 0.1f, NULL); + mSlider2 = new LLSliderCtrl("slider 2", LLRect(20,100,190,80), "y: ", NULL, 70, 130, TRUE, TRUE, FALSE, NULL, NULL, ((LLVector3*)var)->mV[VY], -100.f, 100.f, 0.1f, NULL); mSlider2->setPrecision(3); - mSlider3 = new LLSliderCtrl("slider 3", LLRect(20,70,190,50), "z: ", NULL, 70, 130, TRUE, TRUE, NULL, NULL, ((LLVector3*)var)->mV[VZ], -100.f, 100.f, 0.1f, NULL); + mSlider3 = new LLSliderCtrl("slider 3", LLRect(20,70,190,50), "z: ", NULL, 70, 130, TRUE, TRUE, FALSE, NULL, NULL, ((LLVector3*)var)->mV[VZ], -100.f, 100.f, 0.1f, NULL); mSlider3->setPrecision(3); addChild(mSlider1); addChild(mSlider2); diff --git a/indra/newview/lldebugview.cpp b/indra/newview/lldebugview.cpp index d20c431987..4e0cc94a26 100644 --- a/indra/newview/lldebugview.cpp +++ b/indra/newview/lldebugview.cpp @@ -17,7 +17,6 @@ #include "llconsole.h" #include "lltextureview.h" #include "llresmgr.h" -#include "llaudiostatus.h" #include "imageids.h" #include "llvelocitybar.h" #include "llviewerwindow.h" @@ -89,19 +88,6 @@ LLDebugView::LLDebugView(const std::string& name, const LLRect &rect) mStatViewp->setVisible(FALSE); addChild(mStatViewp); - // - // Audio debugging stuff - // - const S32 AUDIO_STATUS_LEFT = rect.getWidth()/2-100; - const S32 AUDIO_STATUS_WIDTH = 320; - const S32 AUDIO_STATUS_TOP = (rect.getHeight()/2)+400; - const S32 AUDIO_STATUS_HEIGHT = 320; - r.setLeftTopAndSize( AUDIO_STATUS_LEFT, AUDIO_STATUS_TOP, AUDIO_STATUS_WIDTH, AUDIO_STATUS_HEIGHT ); - LLAudiostatus* gAudioStatus = new LLAudiostatus("AudioStatus", r); - gAudioStatus->setFollowsTop(); - gAudioStatus->setFollowsRight(); - addChild(gAudioStatus); - const S32 VELOCITY_LEFT = 10; // 370; const S32 VELOCITY_WIDTH = 500; const S32 VELOCITY_TOP = 140; diff --git a/indra/newview/llemote.h b/indra/newview/llemote.h index c330d0bfc0..ede07b6db1 100644 --- a/indra/newview/llemote.h +++ b/indra/newview/llemote.h @@ -88,6 +88,8 @@ public: // called when a motion is deactivated virtual void onDeactivate(); + virtual BOOL canDeprecate() { return FALSE; } + static BOOL getIndexFromName( const char* name, U32* index ); protected: diff --git a/indra/newview/llfasttimerview.cpp b/indra/newview/llfasttimerview.cpp index f5e6545369..7441d646ef 100644 --- a/indra/newview/llfasttimerview.cpp +++ b/indra/newview/llfasttimerview.cpp @@ -911,8 +911,7 @@ void LLFastTimerView::draw() //draw line graph history { LLGLSNoTexture no_texture; - LLGLEnable scissor(GL_SCISSOR_TEST); - LLUI::setScissorRegionLocal(graph_rect); + LLLocalClipRect clip(graph_rect); //normalize based on last frame's maximum static U64 last_max = 0; diff --git a/indra/newview/llfirstuse.cpp b/indra/newview/llfirstuse.cpp index 9f05e59ac8..2982dba7b2 100644 --- a/indra/newview/llfirstuse.cpp +++ b/indra/newview/llfirstuse.cpp @@ -15,6 +15,7 @@ // viewer includes #include "llnotify.h" +#include "llfloatervoicewizard.h" #include "llviewercontrol.h" #include "llui.h" #include "viewer.h" @@ -228,3 +229,15 @@ void LLFirstUse::useSculptedPrim() } } + +// static +void LLFirstUse::useVoice() +{ + if (gDisableVoice) return; + if (gSavedSettings.getWarning("FirstVoice")) + { + gSavedSettings.setWarning("FirstVoice", FALSE); + + LLFloaterVoiceWizard::showInstance(); + } +} diff --git a/indra/newview/llfirstuse.h b/indra/newview/llfirstuse.h index 134699d1ec..977ff7ddeb 100644 --- a/indra/newview/llfirstuse.h +++ b/indra/newview/llfirstuse.h @@ -81,6 +81,7 @@ public: static void useFlexible(); static void useDebugMenus(); static void useSculptedPrim(); + static void useVoice(); protected: static std::set<LLString> sConfigVariables; diff --git a/indra/newview/llfloateranimpreview.cpp b/indra/newview/llfloateranimpreview.cpp index 7085407999..edbfa4244c 100644 --- a/indra/newview/llfloateranimpreview.cpp +++ b/indra/newview/llfloateranimpreview.cpp @@ -973,7 +973,7 @@ void LLFloaterAnimPreview::onBtnOK(void* userdata) LLKeyframeDataCache::removeKeyframeData(floaterp->mMotionID); } - floaterp->onClose(false); + floaterp->close(false); } //----------------------------------------------------------------------------- diff --git a/indra/newview/llfloateravatarpicker.cpp b/indra/newview/llfloateravatarpicker.cpp index b541b279d0..0ba0ab89b1 100644 --- a/indra/newview/llfloateravatarpicker.cpp +++ b/indra/newview/llfloateravatarpicker.cpp @@ -188,7 +188,12 @@ void LLFloaterAvatarPicker::onSelectionChange(const std::deque<LLFolderViewItem* self->mAvatarIDs.clear(); self->mAvatarNames.clear(); - self->childSetEnabled("Select", FALSE); + // if we have calling cards, disable select button until + // the inventory picks a valid calling card + if (!items.empty()) + { + self->childSetEnabled("Select", FALSE); + } if (!self->mListNames) { diff --git a/indra/newview/llfloaterchat.cpp b/indra/newview/llfloaterchat.cpp index f4769cc18f..6e76db4270 100644 --- a/indra/newview/llfloaterchat.cpp +++ b/indra/newview/llfloaterchat.cpp @@ -14,6 +14,7 @@ #include "llviewerprecompiledheaders.h" #include "llfloaterchat.h" +#include "llfloateractivespeakers.h" #include "llfloaterscriptdebug.h" #include "llchat.h" @@ -29,11 +30,13 @@ #include "llcheckboxctrl.h" #include "llcombobox.h" #include "llconsole.h" +#include "llfloaterchatterbox.h" #include "llfloatermute.h" #include "llkeyboard.h" //#include "lllineeditor.h" #include "llmutelist.h" //#include "llresizehandle.h" +#include "llchatbar.h" #include "llstatusbar.h" #include "llviewertexteditor.h" #include "llviewergesture.h" // for triggering gestures @@ -50,7 +53,6 @@ // // Constants // -const char FLOATER_TITLE[] = "Chat History"; const F32 INSTANT_MSG_SIZE = 8.0f; const F32 CHAT_MSG_SIZE = 8.0f; const LLColor4 INSTANT_MSG_COLOR(1, 1, 1, 1); @@ -60,25 +62,25 @@ const S32 MAX_CHATTER_COUNT = 16; // // Global statics // -LLFloaterChat* gFloaterChat = NULL; - LLColor4 get_text_color(const LLChat& chat); // // Member Functions // -LLFloaterChat::LLFloaterChat() -: LLFloater("chat floater", "FloaterChatRect", FLOATER_TITLE, - RESIZE_YES, 440, 100, DRAG_ON_TOP, MINIMIZE_NO, CLOSE_YES) +LLFloaterChat::LLFloaterChat(const LLSD& seed) +: LLFloater("chat floater", "FloaterChatRect", "", + RESIZE_YES, 440, 100, DRAG_ON_TOP, MINIMIZE_NO, CLOSE_YES), + mPanel(NULL) { - - gUICtrlFactory->buildFloater(this,"floater_chat_history.xml"); + mFactoryMap["chat_panel"] = LLCallbackMap(createChatPanel, NULL); + mFactoryMap["active_speakers_panel"] = LLCallbackMap(createSpeakersPanel, NULL); + // do not automatically open singleton floaters (as result of getInstance()) + BOOL no_open = FALSE; + gUICtrlFactory->buildFloater(this,"floater_chat_history.xml",&getFactoryMap(),no_open); - childSetAction("Mute resident",onClickMute,this); - childSetAction("Chat", onClickChat, this); - childSetCommitCallback("chatter combobox",onCommitUserSelect,this); childSetCommitCallback("show mutes",onClickToggleShowMute,this); //show mutes childSetVisible("Chat History Editor with mute",FALSE); + childSetAction("toggle_active_speakers_btn", onClickToggleActiveSpeakers, this); setDefaultBtn("Chat"); } @@ -92,33 +94,53 @@ void LLFloaterChat::setVisible(BOOL visible) LLFloater::setVisible( visible ); gSavedSettings.setBOOL("ShowChatHistory", visible); +} - // Hide the chat overlay when our history is visible. - gConsole->setVisible( !visible ); +void LLFloaterChat::draw() +{ + // enable say and shout only when text available + + childSetValue("toggle_active_speakers_btn", childIsVisible("active_speakers_panel")); + + LLChatBar* chat_barp = (LLChatBar*)getChildByName("chat_panel", TRUE); + if (chat_barp) + { + chat_barp->refresh(); + } + + mPanel->refreshSpeakers(); + LLFloater::draw(); } +BOOL LLFloaterChat::postBuild() +{ + mPanel = (LLPanelActiveSpeakers*)LLUICtrlFactory::getPanelByName(this, "active_speakers_panel"); + + LLChatBar* chat_barp = (LLChatBar*)getChildByName("chat_panel", TRUE); + if (chat_barp) + { + chat_barp->setGestureCombo(LLUICtrlFactory::getComboBoxByName(this, "Gesture")); + } + return TRUE; +} // public virtual void LLFloaterChat::onClose(bool app_quitting) { - LLFloater::setVisible( FALSE ); - if (!app_quitting) { gSavedSettings.setBOOL("ShowChatHistory", FALSE); } - - // Hide the chat overlay when our history is visible. - gConsole->setVisible( TRUE ); + setVisible(FALSE); } - -// public -void LLFloaterChat::show() +void LLFloaterChat::onVisibilityChange(BOOL new_visibility) { - open(); /*Flawfinder: ignore*/ + // Hide the chat overlay when our history is visible. + gConsole->setVisible( !new_visibility ); } + void add_timestamped_line(LLViewerTextEditor* edit, const LLString& line, const LLColor4& color) { bool prepend_newline = true; @@ -142,7 +164,7 @@ void log_chat_text(const LLChat& chat) } // static void LLFloaterChat::addChatHistory(const LLChat& chat, bool log_to_file) -{ +{ if ( gSavedPerAccountSettings.getBOOL("LogChat") && log_to_file) { log_chat_text(chat); @@ -165,10 +187,9 @@ void LLFloaterChat::addChatHistory(const LLChat& chat, bool log_to_file) } // could flash the chat button in the status bar here. JC - if (!gFloaterChat) return; - - LLViewerTextEditor* history_editor = (LLViewerTextEditor*)gFloaterChat->getChildByName("Chat History Editor"); - LLViewerTextEditor* history_editor_with_mute = (LLViewerTextEditor*)gFloaterChat->getChildByName("Chat History Editor with mute"); + LLFloaterChat* chat_floater = LLFloaterChat::getInstance(LLSD()); + LLViewerTextEditor* history_editor = (LLViewerTextEditor*)chat_floater->getChildByName("Chat History Editor", TRUE); + LLViewerTextEditor* history_editor_with_mute = (LLViewerTextEditor*)chat_floater->getChildByName("Chat History Editor with mute", TRUE); history_editor->setParseHTML(TRUE); history_editor_with_mute->setParseHTML(TRUE); @@ -184,77 +205,24 @@ void LLFloaterChat::addChatHistory(const LLChat& chat, bool log_to_file) LLColor4 muted_color = lerp(color, LLColor4::grey, 0.5f); add_timestamped_line(history_editor_with_mute, chat.mText, color); } - - if (!chat.mMuted - && chat.mSourceType != CHAT_SOURCE_SYSTEM - && chat.mFromID.notNull() - && chat.mFromID != gAgent.getID()) + + // add objects as transient speakers that can be muted + if (chat.mSourceType == CHAT_SOURCE_OBJECT) { - - LLComboBox* chatter_combo = LLUICtrlFactory::getComboBoxByName(gFloaterChat,"chatter combobox"); - - if(!chatter_combo) - { - return; - } - - if (!chatter_combo->setCurrentByID(chat.mFromID)) - { - // if we have too many items... - if (chatter_combo->getItemCount() >= MAX_CHATTER_COUNT) - { - chatter_combo->remove(0); - } - - LLMute mute(chat.mFromID, chat.mFromName); - if (chat.mSourceType == CHAT_SOURCE_OBJECT) - { - mute.mType = LLMute::OBJECT; - } - else if (chat.mSourceType == CHAT_SOURCE_AGENT) - { - mute.mType = LLMute::AGENT; - } - LLString item = mute.getDisplayName(); - chatter_combo->add(item, chat.mFromID); - chatter_combo->setCurrentByIndex(chatter_combo->getItemCount() - 1); - gFloaterChat->childSetEnabled("Mute resident",TRUE); - } + chat_floater->mPanel->setSpeaker(chat.mFromID, chat.mFromName, LLSpeaker::STATUS_NOT_IN_CHANNEL, LLSpeaker::SPEAKER_OBJECT); } } // static void LLFloaterChat::setHistoryCursorAndScrollToEnd() { - if (gFloaterChat) - { - LLViewerTextEditor* history_editor = (LLViewerTextEditor*)gFloaterChat->getChildByName("Chat History Editor"); - LLViewerTextEditor* history_editor_with_mute = (LLViewerTextEditor*)gFloaterChat->getChildByName("Chat History Editor with mute"); - - history_editor->setCursorAndScrollToEnd(); - history_editor_with_mute->setCursorAndScrollToEnd(); - } -} - - -// static -void LLFloaterChat::toggle(void*) -{ - if (gFloaterChat->getVisible()) - { - gFloaterChat->close(); - } - else - { - gFloaterChat->show(); - } + LLViewerTextEditor* history_editor = (LLViewerTextEditor*)LLFloaterChat::getInstance(LLSD())->getChildByName("Chat History Editor", TRUE); + LLViewerTextEditor* history_editor_with_mute = (LLViewerTextEditor*)LLFloaterChat::getInstance(LLSD())->getChildByName("Chat History Editor with mute", TRUE); + + history_editor->setCursorAndScrollToEnd(); + history_editor_with_mute->setCursorAndScrollToEnd(); } -// static -BOOL LLFloaterChat::visible(void*) -{ - return (gFloaterChat && gFloaterChat->getVisible()); -} //static void LLFloaterChat::onClickMute(void *data) @@ -279,30 +247,6 @@ void LLFloaterChat::onClickMute(void *data) } //static -void LLFloaterChat::onClickChat(void*) -{ - // we need this function as a level of indirection because otherwise startChat would - // cast the data pointer to a character string, and dump garbage in the chat - LLChatBar::startChat(NULL); -} - -//static -void LLFloaterChat::onCommitUserSelect(LLUICtrl* caller, void* data) -{ - LLFloaterChat* floater = (LLFloaterChat*)data; - LLComboBox* combo = (LLComboBox*)caller; - - if (combo->getCurrentIndex() == -1) - { - floater->childSetEnabled("Mute resident",FALSE); - } - else - { - floater->childSetEnabled("Mute resident",TRUE); - } -} - -//static void LLFloaterChat::onClickToggleShowMute(LLUICtrl* caller, void *data) { LLFloaterChat* floater = (LLFloaterChat*)data; @@ -310,8 +254,8 @@ void LLFloaterChat::onClickToggleShowMute(LLUICtrl* caller, void *data) //LLCheckBoxCtrl* BOOL show_mute = LLUICtrlFactory::getCheckBoxByName(floater,"show mutes")->get(); - LLViewerTextEditor* history_editor = (LLViewerTextEditor*)floater->getChildByName("Chat History Editor"); - LLViewerTextEditor* history_editor_with_mute = (LLViewerTextEditor*)floater->getChildByName("Chat History Editor with mute"); + LLViewerTextEditor* history_editor = (LLViewerTextEditor*)floater->getChildByName("Chat History Editor", TRUE); + LLViewerTextEditor* history_editor_with_mute = (LLViewerTextEditor*)floater->getChildByName("Chat History Editor with mute", TRUE); if (!history_editor || !history_editor_with_mute) return; @@ -427,7 +371,7 @@ LLColor4 get_text_color(const LLChat& chat) //static void LLFloaterChat::loadHistory() { - LLLogChat::loadHistory("chat", &chatFromLogFile, (void *)gFloaterChat); + LLLogChat::loadHistory("chat", &chatFromLogFile, (void *)LLFloaterChat::getInstance(LLSD())); } //static @@ -438,3 +382,40 @@ void LLFloaterChat::chatFromLogFile(LLString line, void* userdata) chat.mText = line; addChatHistory(chat, FALSE); } + +//static +void* LLFloaterChat::createSpeakersPanel(void* data) +{ + return new LLPanelActiveSpeakers(gLocalSpeakerMgr, TRUE); +} + +//static +void* LLFloaterChat::createChatPanel(void* data) +{ + LLChatBar* chatp = new LLChatBar("floating_chat_bar"); + return chatp; +} + +//static +void LLFloaterChat::hideInstance(const LLSD& id) +{ + LLFloaterChat* floaterp = LLFloaterChat::getInstance(LLSD()); + // don't do anything when hosted in the chatterbox + if(floaterp->getHost()) + { + LLFloaterChatterBox::hideInstance(LLSD()); + } + else + { + LLUISingleton<LLFloaterChat>::hideInstance(id); + } +} + +// static +void LLFloaterChat::onClickToggleActiveSpeakers(void* userdata) +{ + LLFloaterChat* self = (LLFloaterChat*)userdata; + + self->childSetVisible("active_speakers_panel", !self->childIsVisible("active_speakers_panel")); +} + diff --git a/indra/newview/llfloaterchat.h b/indra/newview/llfloaterchat.h index bf93afb455..6da3b9bb1b 100644 --- a/indra/newview/llfloaterchat.h +++ b/indra/newview/llfloaterchat.h @@ -24,17 +24,20 @@ class LLViewerTextEditor; class LLMessageSystem; class LLUUID; class LLCheckBoxCtrl; +class LLPanelActiveSpeakers; class LLFloaterChat -: public LLFloater +: public LLFloater, public LLUISingleton<LLFloaterChat> { public: - LLFloaterChat(); + LLFloaterChat(const LLSD& seed); ~LLFloaterChat(); - void show(); - virtual void onClose(bool app_quitting); virtual void setVisible( BOOL b ); + virtual void draw(); + virtual BOOL postBuild(); + virtual void onClose(bool app_quitting); + virtual void onVisibilityChange(BOOL cur_visibility); static void setHistoryCursorAndScrollToEnd(); @@ -45,17 +48,17 @@ public: // Add chat to history alone. static void addChatHistory(const LLChat& chat, bool log_to_file = true); - static void toggle(void*); - static BOOL visible(void*); - static void onClickMute(void *data); - static void onClickChat(void *); - static void onCommitUserSelect(LLUICtrl* caller, void* data); static void onClickToggleShowMute(LLUICtrl* caller, void *data); + static void onClickToggleActiveSpeakers(void* userdata); static void chatFromLogFile(LLString line, void* userdata); static void loadHistory(); -}; + static void* createSpeakersPanel(void* data); + static void* createChatPanel(void* data); + static void hideInstance(const LLSD& id); -extern LLFloaterChat* gFloaterChat; +protected: + LLPanelActiveSpeakers* mPanel; +}; #endif diff --git a/indra/newview/llfloaterchatterbox.cpp b/indra/newview/llfloaterchatterbox.cpp new file mode 100644 index 0000000000..ab992bdb45 --- /dev/null +++ b/indra/newview/llfloaterchatterbox.cpp @@ -0,0 +1,322 @@ +/** + * @file llfloaterchatterbox.cpp + * @author Richard + * @date 2007-05-08 + * @brief Implementation of the chatterbox integrated conversation ui + * + * Copyright (c) 2007-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + + +#include "llviewerprecompiledheaders.h" + +#include "llfloaterchatterbox.h" +#include "llvieweruictrlfactory.h" +#include "llfloaterchat.h" +#include "llfloaterfriends.h" +#include "llfloatergroups.h" +#include "llviewercontrol.h" +#include "llimview.h" +#include "llimpanel.h" + +// +// LLFloaterMyFriends +// + +LLFloaterMyFriends::LLFloaterMyFriends(const LLSD& seed) +{ + mFactoryMap["friends_panel"] = LLCallbackMap(LLFloaterMyFriends::createFriendsPanel, NULL); + mFactoryMap["groups_panel"] = LLCallbackMap(LLFloaterMyFriends::createGroupsPanel, NULL); + // do not automatically open singleton floaters (as result of getInstance()) + BOOL no_open = FALSE; + gUICtrlFactory->buildFloater(this, "floater_my_friends.xml", &getFactoryMap(), no_open); +} + +LLFloaterMyFriends::~LLFloaterMyFriends() +{ +} + +BOOL LLFloaterMyFriends::postBuild() +{ + mTabs = LLUICtrlFactory::getTabContainerByName(this, "friends_and_groups"); + + return TRUE; +} + + +void LLFloaterMyFriends::onClose(bool app_quitting) +{ + setVisible(FALSE); +} + +//static +LLFloaterMyFriends* LLFloaterMyFriends::showInstance(const LLSD& id) +{ + LLFloaterMyFriends* floaterp = LLUIInstanceMgr<LLFloaterMyFriends>::showInstance(id); + // garbage values in id will be interpreted as 0, or the friends tab + floaterp->mTabs->selectTab(id); + + return floaterp; +} + +//static +void LLFloaterMyFriends::hideInstance(const LLSD& id) +{ + if(instanceVisible(id)) + { + LLFloaterChatterBox::hideInstance(LLSD()); + } +} + +// is the specified panel currently visible +//static +BOOL LLFloaterMyFriends::instanceVisible(const LLSD& id) +{ + // if singleton not created yet, trivially return false + if (!findInstance(id)) return FALSE; + + LLFloaterMyFriends* floaterp = getInstance(id); + return floaterp->isInVisibleChain() && floaterp->mTabs->getCurrentPanelIndex() == id.asInteger(); +} + +//static +void* LLFloaterMyFriends::createFriendsPanel(void* data) +{ + return new LLPanelFriends(); +} + +//static +void* LLFloaterMyFriends::createGroupsPanel(void* data) +{ + return new LLPanelGroups(); +} + +// +// LLFloaterChatterBox +// +LLFloaterChatterBox::LLFloaterChatterBox(const LLSD& seed) : + mActiveVoiceFloater(NULL) +{ + mAutoResize = FALSE; + + gUICtrlFactory->buildFloater(this, "floater_chatterbox.xml", NULL, FALSE); + addFloater(LLFloaterMyFriends::getInstance(0), TRUE); + if (gSavedSettings.getBOOL("ChatHistoryTornOff")) + { + LLFloaterChat* floater_chat = LLFloaterChat::getInstance(LLSD()); + // add then remove to set up relationship for re-attach + addFloater(floater_chat, FALSE); + removeFloater(floater_chat); + // reparent to floater view + gFloaterView->addChild(floater_chat); + } + else + { + addFloater(LLFloaterChat::getInstance(LLSD()), FALSE); + } + mTabContainer->lockTabs(); +} + +LLFloaterChatterBox::~LLFloaterChatterBox() +{ +} + +BOOL LLFloaterChatterBox::handleKeyHere(KEY key, MASK mask, BOOL called_from_parent) +{ + if (getEnabled() + && mask == MASK_CONTROL) + { + if (key == 'W') + { + LLFloater* floater = getActiveFloater(); + // is user closeable and is system closeable + if (floater && floater->canClose()) + { + if (floater->isCloseable()) + { + floater->close(); + } + else + { + // close chatterbox window if frontmost tab is reserved, non-closeable tab + // such as contacts or near me + close(); + } + } + return TRUE; + } + } + + return LLMultiFloater::handleKeyHere(key, mask, called_from_parent); +} + +void LLFloaterChatterBox::draw() +{ + // clear new im notifications when chatterbox is visible + if (!isMinimized()) + { + gIMMgr->clearNewIMNotification(); + } + LLFloater* current_active_floater = getCurrentVoiceFloater(); + // set icon on tab for floater currently associated with active voice channel + if(mActiveVoiceFloater != current_active_floater) + { + // remove image from old floater's tab + if (mActiveVoiceFloater) + { + mTabContainer->setTabImage(mActiveVoiceFloater, ""); + } + } + + // update image on current active tab + if (current_active_floater) + { + LLColor4 icon_color = LLColor4::white; + LLVoiceChannel* channelp = LLVoiceChannel::getCurrentVoiceChannel(); + if (channelp) + { + if (channelp->isActive()) + { + icon_color = LLColor4::green; + } + else if (channelp->getState() == LLVoiceChannel::STATE_ERROR) + { + icon_color = LLColor4::red; + } + else // active, but not connected + { + icon_color = LLColor4::yellow; + } + } + mTabContainer->setTabImage(current_active_floater, "active_voice_tab.tga", icon_color); + } + + mActiveVoiceFloater = current_active_floater; + + LLFloater::draw(); +} + +void LLFloaterChatterBox::onOpen() +{ + gSavedSettings.setBOOL("ShowCommunicate", TRUE); +} + +void LLFloaterChatterBox::onClose(bool app_quitting) +{ + setVisible(FALSE); + gSavedSettings.setBOOL("ShowCommunicate", FALSE); +} + +void LLFloaterChatterBox::removeFloater(LLFloater* floaterp) +{ + if (floaterp->getName() == "chat floater") + { + // only my friends floater now locked + mTabContainer->lockTabs(1); + gSavedSettings.setBOOL("ChatHistoryTornOff", TRUE); + floaterp->setCanClose(TRUE); + } + LLMultiFloater::removeFloater(floaterp); +} + +void LLFloaterChatterBox::addFloater(LLFloater* floaterp, + BOOL select_added_floater, + LLTabContainerCommon::eInsertionPoint insertion_point) +{ + // make sure my friends and chat history both locked when re-attaching chat history + if (floaterp->getName() == "chat floater") + { + // select my friends tab + mTabContainer->selectFirstTab(); + // add chat history to the right of the my friends tab + //*TODO: respect select_added_floater so that we don't leave first tab selected + LLMultiFloater::addFloater(floaterp, select_added_floater, LLTabContainer::RIGHT_OF_CURRENT); + // make sure first two tabs are now locked + mTabContainer->lockTabs(2); + gSavedSettings.setBOOL("ChatHistoryTornOff", FALSE); + floaterp->setCanClose(FALSE); + } + else + { + LLMultiFloater::addFloater(floaterp, select_added_floater, insertion_point); + } + + // make sure active voice icon shows up for new tab + if (floaterp == mActiveVoiceFloater) + { + mTabContainer->setTabImage(floaterp, "active_voice_tab.tga"); + } +} + + +//static +LLFloaterChatterBox* LLFloaterChatterBox::showInstance(const LLSD& seed) +{ + LLFloaterChatterBox* floater = LLUISingleton<LLFloaterChatterBox>::showInstance(seed); + + // if TRUE, show tab for active voice channel, otherwise, just show last tab + if (seed.asBoolean()) + { + LLFloater* floater_to_show = getCurrentVoiceFloater(); + if (floater_to_show) + { + floater_to_show->open(); + } + else + { + // just open chatterbox if there is no active voice window + LLUISingleton<LLFloaterChatterBox>::getInstance(seed)->open(); + } + } + + return floater; +} + +//static +BOOL LLFloaterChatterBox::instanceVisible(const LLSD &seed) +{ + if (seed.asBoolean()) + { + LLFloater* floater_to_show = getCurrentVoiceFloater(); + if (floater_to_show) + { + return floater_to_show->isInVisibleChain(); + } + } + + return LLUISingleton<LLFloaterChatterBox>::instanceVisible(seed); +} + +//static +LLFloater* LLFloaterChatterBox::getCurrentVoiceFloater() +{ + if (!LLVoiceClient::voiceEnabled()) + { + return NULL; + } + if (LLVoiceChannelProximal::getInstance() == LLVoiceChannel::getCurrentVoiceChannel()) + { + // show near me tab if in proximal channel + return LLFloaterChat::getInstance(LLSD()); + } + else + { + LLFloaterChatterBox* floater = LLFloaterChatterBox::getInstance(LLSD()); + // iterator over all IM tabs (skip friends and near me) + for (S32 i = 0; i < floater->getFloaterCount(); i++) + { + LLPanel* panelp = floater->mTabContainer->getPanelByIndex(i); + if (panelp->getName() == "im_floater") + { + // only LLFloaterIMPanels are called "im_floater" + LLFloaterIMPanel* im_floaterp = (LLFloaterIMPanel*)panelp; + if (im_floaterp->getVoiceChannel() == LLVoiceChannel::getCurrentVoiceChannel()) + { + return im_floaterp; + } + } + } + } + return NULL; +} diff --git a/indra/newview/llfloaterchatterbox.h b/indra/newview/llfloaterchatterbox.h new file mode 100644 index 0000000000..38cc7d6e4b --- /dev/null +++ b/indra/newview/llfloaterchatterbox.h @@ -0,0 +1,67 @@ +/** + * @file llfloaterchatterbox.h + * @author Richard + * @date 2007-05-04 + * @brief Integrated friends and group management/communication tool + * + * Copyright (c) 2007-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLFLOATERCHATTERBOX_H +#define LL_LLFLOATERCHATTERBOX_H + +#include "llfloater.h" +#include "llstring.h" + +class LLTabContainerCommon; + +class LLFloaterMyFriends : public LLFloater, public LLUISingleton<LLFloaterMyFriends> +{ +public: + LLFloaterMyFriends(const LLSD& seed); + virtual ~LLFloaterMyFriends(); + + virtual BOOL postBuild(); + + void onClose(bool app_quitting); + + // override LLUISingleton behavior + static LLFloaterMyFriends* showInstance(const LLSD& id); + static void hideInstance(const LLSD& id); + static BOOL instanceVisible(const LLSD& id); + + static void* createFriendsPanel(void* data); + static void* createGroupsPanel(void* data); + +protected: + LLTabContainerCommon* mTabs; +}; + +class LLFloaterChatterBox : public LLMultiFloater, public LLUISingleton<LLFloaterChatterBox> +{ +public: + LLFloaterChatterBox(const LLSD& seed); + virtual ~LLFloaterChatterBox(); + + /*virtual*/ BOOL handleKeyHere(KEY key, MASK mask, BOOL called_from_parent); + /*virtual*/ void draw(); + /*virtual*/ void onOpen(); + /*virtual*/ void onClose(bool app_quitting); + + /*virtual*/ void removeFloater(LLFloater* floaterp); + /*virtual*/ void addFloater(LLFloater* floaterp, + BOOL select_added_floater, + LLTabContainerCommon::eInsertionPoint insertion_point = LLTabContainerCommon::END); + + static LLFloaterChatterBox* showInstance(const LLSD& seed); + static BOOL instanceVisible(const LLSD& seed); + + static LLFloater* getCurrentVoiceFloater(); + +protected: + LLFloater* mActiveVoiceFloater; +}; + + +#endif // LL_LLFLOATERCHATTERBOX_H diff --git a/indra/newview/llfloaterfriends.cpp b/indra/newview/llfloaterfriends.cpp index 7e0a64c420..592cb4186c 100644 --- a/indra/newview/llfloaterfriends.cpp +++ b/indra/newview/llfloaterfriends.cpp @@ -43,20 +43,18 @@ class LLLocalFriendsObserver : public LLFriendObserver { public: - LLLocalFriendsObserver(LLFloaterFriends* floater) : mFloater(floater) {} + LLLocalFriendsObserver(LLPanelFriends* floater) : mFloater(floater) {} virtual ~LLLocalFriendsObserver() { mFloater = NULL; } virtual void changed(U32 mask) { mFloater->updateFriends(mask); } protected: - LLFloaterFriends* mFloater; + LLPanelFriends* mFloater; }; -LLFloaterFriends* LLFloaterFriends::sInstance = NULL; - -LLFloaterFriends::LLFloaterFriends() : - LLFloater("Friends"), +LLPanelFriends::LLPanelFriends() : + LLPanel(), LLEventTimer(1000000), mObserver(NULL), mMenuState(0), @@ -64,81 +62,38 @@ LLFloaterFriends::LLFloaterFriends() : mAllowRightsChange(TRUE), mNumRightsChanged(0) { - mTimer.stop(); - sInstance = this; + mEventTimer.stop(); mObserver = new LLLocalFriendsObserver(this); LLAvatarTracker::instance().addObserver(mObserver); - gSavedSettings.setBOOL("ShowFriends", TRUE); - // Builds and adds to gFloaterView - gUICtrlFactory->buildFloater(this, "floater_friends.xml"); - refreshUI(); } -LLFloaterFriends::~LLFloaterFriends() +LLPanelFriends::~LLPanelFriends() { LLAvatarTracker::instance().removeObserver(mObserver); delete mObserver; - sInstance = NULL; - gSavedSettings.setBOOL("ShowFriends", FALSE); } -void LLFloaterFriends::tick() +void LLPanelFriends::tick() { - mTimer.stop(); + mEventTimer.stop(); mPeriod = 1000000; mAllowRightsChange = TRUE; updateFriends(LLFriendObserver::ADD); } -// static -void LLFloaterFriends::show(void*) -{ - if(sInstance) - { - sInstance->open(); /*Flawfinder: ignore*/ - } - else - { - LLFloaterFriends* self = new LLFloaterFriends; - self->open(); /*Flawfinder: ignore*/ - } -} - - -// static -BOOL LLFloaterFriends::visible(void*) -{ - return sInstance && sInstance->getVisible(); -} - - -// static -void LLFloaterFriends::toggle(void*) -{ - if (sInstance) - { - sInstance->close(); - } - else - { - show(); - } -} - - -void LLFloaterFriends::updateFriends(U32 changed_mask) +void LLPanelFriends::updateFriends(U32 changed_mask) { LLUUID selected_id; - LLCtrlListInterface *friends_list = sInstance->childGetListInterface("friend_list"); + LLCtrlListInterface *friends_list = childGetListInterface("friend_list"); if (!friends_list) return; - LLCtrlScrollInterface *friends_scroll = sInstance->childGetScrollInterface("friend_list"); + LLCtrlScrollInterface *friends_scroll = childGetScrollInterface("friend_list"); if (!friends_scroll) return; // We kill the selection warning, otherwise we'll spam with warning popups // if the maximum amount of friends are selected mShowMaxSelectWarning = false; - LLDynamicArray<LLUUID> selected_friends = sInstance->getSelectedIDs(); + LLDynamicArray<LLUUID> selected_friends = getSelectedIDs(); if(changed_mask & (LLFriendObserver::ADD | LLFriendObserver::REMOVE | LLFriendObserver::ONLINE)) { refreshNames(); @@ -149,8 +104,8 @@ void LLFloaterFriends::updateFriends(U32 changed_mask) if(mNumRightsChanged > 0) { mPeriod = RIGHTS_CHANGE_TIMEOUT; - mTimer.start(); - mTimer.reset(); + mEventTimer.start(); + mEventTimer.reset(); mAllowRightsChange = FALSE; } else @@ -175,12 +130,12 @@ void LLFloaterFriends::updateFriends(U32 changed_mask) } // virtual -BOOL LLFloaterFriends::postBuild() +BOOL LLPanelFriends::postBuild() { - mFriendsList = LLUICtrlFactory::getScrollListByName(this, "friend_list"); mFriendsList->setMaxSelectable(MAX_FRIEND_SELECT); mFriendsList->setMaxiumumSelectCallback(onMaximumSelect); + mFriendsList->setCommitOnSelectionChange(TRUE); childSetCommitCallback("friend_list", onSelectName, this); childSetDoubleClickCallback("friend_list", onClickIM); @@ -195,13 +150,17 @@ BOOL LLFloaterFriends::postBuild() childSetAction("pay_btn", onClickPay, this); childSetAction("add_btn", onClickAddFriend, this); childSetAction("remove_btn", onClickRemove, this); - childSetAction("close_btn", onClickClose, this); + + setDefaultBtn("im_btn"); + + updateFriends(LLFriendObserver::ADD); + refreshUI(); return TRUE; } -void LLFloaterFriends::addFriend(const std::string& name, const LLUUID& agent_id) +void LLPanelFriends::addFriend(const std::string& name, const LLUUID& agent_id) { LLAvatarTracker& at = LLAvatarTracker::instance(); const LLRelationship* relationInfo = at.getBuddyInfo(agent_id); @@ -255,17 +214,26 @@ void LLFloaterFriends::addFriend(const std::string& name, const LLUUID& agent_id mFriendsList->addElement(element, ADD_BOTTOM); } -void LLFloaterFriends::refreshRightsChangeList(U8 state) +void LLPanelFriends::refreshRightsChangeList() { LLDynamicArray<LLUUID> friends = getSelectedIDs(); - const LLRelationship* friend_status = NULL; - if(friends.size() > 0) friend_status = LLAvatarTracker::instance().getBuddyInfo(friends[0]); + S32 num_selected = friends.size(); LLSD row; - bool can_change_visibility = false; - bool can_change_modify = false; - bool can_change_online_multiple = true; - bool can_change_map_multiple = true; + bool can_offer_teleport = num_selected >= 1; + + // aggregate permissions over all selected friends + bool friends_see_online = true; + bool friends_see_on_map = true; + bool friends_modify_objects = true; + + // do at least some of the friends selected have these rights? + bool some_friends_see_online = false; + bool some_friends_see_on_map = false; + bool some_friends_modify_objects = false; + + bool selected_friends_online = true; + LLTextBox* processing_label = LLUICtrlFactory::getTextBoxByName(this, "process_rights_label"); if(!mAllowRightsChange) @@ -273,7 +241,9 @@ void LLFloaterFriends::refreshRightsChangeList(U8 state) if(processing_label) { processing_label->setVisible(true); - state = 0; + // ignore selection for now + friends.clear(); + num_selected = 0; } } else @@ -284,82 +254,111 @@ void LLFloaterFriends::refreshRightsChangeList(U8 state) } } - if(state == 1) - { - if(friend_status && !friend_status->isOnline()) - { - childSetEnabled("offer_teleport_btn", false); - } - can_change_visibility = true; - can_change_modify = true; - } - else if (state == 2) + const LLRelationship* friend_status = NULL; + for(LLDynamicArray<LLUUID>::iterator itr = friends.begin(); itr != friends.end(); ++itr) { - can_change_visibility = true; - can_change_modify = false; - for(LLDynamicArray<LLUUID>::iterator itr = friends.begin(); itr != friends.end(); ++itr) + friend_status = LLAvatarTracker::instance().getBuddyInfo(*itr); + if (friend_status) { - friend_status = LLAvatarTracker::instance().getBuddyInfo(*itr); + bool can_see_online = friend_status->isRightGrantedTo(LLRelationship::GRANT_ONLINE_STATUS); + bool can_see_on_map = friend_status->isRightGrantedTo(LLRelationship::GRANT_MAP_LOCATION); + bool can_modify_objects = friend_status->isRightGrantedTo(LLRelationship::GRANT_MODIFY_OBJECTS); + + // aggregate rights of this friend into total selection + friends_see_online &= can_see_online; + friends_see_on_map &= can_see_on_map; + friends_modify_objects &= can_modify_objects; + + // can at least one of your selected friends do any of these? + some_friends_see_online |= can_see_online; + some_friends_see_on_map |= can_see_on_map; + some_friends_modify_objects |= can_modify_objects; + if(!friend_status->isOnline()) { - childSetEnabled("offer_teleport_btn", false); - } - if(!friend_status->isRightGrantedTo(LLRelationship::GRANT_ONLINE_STATUS)) - { - can_change_online_multiple = false; - can_change_map_multiple = false; - } - else if(!friend_status->isRightGrantedTo(LLRelationship::GRANT_MAP_LOCATION)) - { - can_change_map_multiple = false; + can_offer_teleport = false; + selected_friends_online = false; } } - + else // missing buddy info, don't allow any operations + { + can_offer_teleport = false; + + friends_see_online = false; + friends_see_on_map = false; + friends_modify_objects = false; + + some_friends_see_online = false; + some_friends_see_on_map = false; + some_friends_modify_objects = false; + } } - LLCheckboxCtrl* check; + // seeing a friend on the map requires seeing online status as a prerequisite + friends_see_on_map &= friends_see_online; + mMenuState = 0; - check = LLUICtrlFactory::getCheckBoxByName(this, "online_status_cb"); - check->setEnabled(can_change_visibility); - check->set(FALSE); - if(!mAllowRightsChange) check->setVisible(FALSE); - else check->setVisible(TRUE); - if(friend_status) + // make checkboxes visible after we have finished processing rights + childSetVisible("online_status_cb", mAllowRightsChange); + childSetVisible("map_status_cb", mAllowRightsChange); + childSetVisible("modify_status_cb", mAllowRightsChange); + + if (num_selected == 0) // nothing selected { - check->set(friend_status->isRightGrantedTo(LLRelationship::GRANT_ONLINE_STATUS) && can_change_online_multiple); - if(friend_status->isRightGrantedTo(LLRelationship::GRANT_ONLINE_STATUS)) mMenuState |= LLRelationship::GRANT_ONLINE_STATUS; + childSetEnabled("im_btn", FALSE); + childSetEnabled("offer_teleport_btn", FALSE); + + childSetEnabled("online_status_cb", FALSE); + childSetValue("online_status_cb", FALSE); + childSetTentative("online_status_cb", FALSE); + + childSetEnabled("map_status_cb", FALSE); + childSetValue("map_status_cb", FALSE); + childSetTentative("map_status_cb", FALSE); + + childSetEnabled("modify_status_cb", FALSE); + childSetValue("modify_status_cb", FALSE); + childSetTentative("modify_status_cb", FALSE); } - - check = LLUICtrlFactory::getCheckBoxByName(this, "map_status_cb"); - check->setEnabled(false); - check->set(FALSE); - if(!mAllowRightsChange) check->setVisible(FALSE); - else check->setVisible(TRUE); - if(friend_status) + else // we have at least one friend selected... { - check->setEnabled(friend_status->isRightGrantedTo(LLRelationship::GRANT_ONLINE_STATUS) && can_change_visibility && can_change_online_multiple); - check->set(friend_status->isRightGrantedTo(LLRelationship::GRANT_MAP_LOCATION) && can_change_map_multiple); - if(friend_status->isRightGrantedTo(LLRelationship::GRANT_MAP_LOCATION)) mMenuState |= LLRelationship::GRANT_MAP_LOCATION; - } + // only allow IMs to groups when everyone in the group is online + // to be consistent with context menus in inventory and because otherwise + // offline friends would be silently dropped from the session + childSetEnabled("im_btn", selected_friends_online || num_selected == 1); - check = LLUICtrlFactory::getCheckBoxByName(this, "modify_status_cb"); - check->setEnabled(can_change_modify); - check->set(FALSE); - if(!mAllowRightsChange) check->setVisible(FALSE); - else check->setVisible(TRUE); - if(can_change_modify) - { - if(friend_status) + childSetEnabled("offer_teleport_btn", can_offer_teleport); + + childSetEnabled("online_status_cb", TRUE); + childSetValue("online_status_cb", some_friends_see_online); + childSetTentative("online_status_cb", some_friends_see_online != friends_see_online); + if (friends_see_online) + { + mMenuState |= LLRelationship::GRANT_ONLINE_STATUS; + } + + childSetEnabled("map_status_cb", TRUE); + childSetValue("map_status_cb", some_friends_see_on_map); + childSetTentative("map_status_cb", some_friends_see_on_map != friends_see_on_map); + if(friends_see_on_map) + { + mMenuState |= LLRelationship::GRANT_MAP_LOCATION; + } + + // for now, don't allow modify rights change for multiple select + childSetEnabled("modify_status_cb", num_selected == 1); + childSetValue("modify_status_cb", some_friends_modify_objects); + childSetTentative("modify_status_cb", some_friends_modify_objects != friends_modify_objects); + if(friends_modify_objects) { - check->set(friend_status->isRightGrantedTo(LLRelationship::GRANT_MODIFY_OBJECTS)); - if(friend_status->isRightGrantedTo(LLRelationship::GRANT_MODIFY_OBJECTS)) mMenuState |= LLRelationship::GRANT_MODIFY_OBJECTS; + mMenuState |= LLRelationship::GRANT_MODIFY_OBJECTS; } } } -void LLFloaterFriends::refreshNames() +void LLPanelFriends::refreshNames() { LLDynamicArray<LLUUID> selected_ids = getSelectedIDs(); S32 pos = mFriendsList->getScrollPos(); @@ -389,7 +388,7 @@ void LLFloaterFriends::refreshNames() } -void LLFloaterFriends::refreshUI() +void LLPanelFriends::refreshUI() { BOOL single_selected = FALSE; BOOL multiple_selected = FALSE; @@ -423,40 +422,34 @@ void LLFloaterFriends::refreshUI() childSetEnabled("im_btn", single_selected); childSetEnabled("friend_rights", single_selected); - //Note: We reset this in refreshRightsChangeList since we already have to iterate - //through all selected friends there - childSetEnabled("offer_teleport_btn", single_selected); - - refreshRightsChangeList((single_selected + multiple_selected)); + refreshRightsChangeList(); } -// static -LLDynamicArray<LLUUID> LLFloaterFriends::getSelectedIDs() +LLDynamicArray<LLUUID> LLPanelFriends::getSelectedIDs() { LLUUID selected_id; LLDynamicArray<LLUUID> friend_ids; - if(sInstance) + std::vector<LLScrollListItem*> selected = mFriendsList->getAllSelected(); + for(std::vector<LLScrollListItem*>::iterator itr = selected.begin(); itr != selected.end(); ++itr) { - std::vector<LLScrollListItem*> selected = sInstance->mFriendsList->getAllSelected(); - for(std::vector<LLScrollListItem*>::iterator itr = selected.begin(); itr != selected.end(); ++itr) - { - friend_ids.push_back((*itr)->getUUID()); - } + friend_ids.push_back((*itr)->getUUID()); } return friend_ids; } // static -void LLFloaterFriends::onSelectName(LLUICtrl* ctrl, void* user_data) +void LLPanelFriends::onSelectName(LLUICtrl* ctrl, void* user_data) { - if(sInstance) + LLPanelFriends* panelp = (LLPanelFriends*)user_data; + + if(panelp) { - sInstance->refreshUI(); + panelp->refreshUI(); } } //static -void LLFloaterFriends::onMaximumSelect(void* user_data) +void LLPanelFriends::onMaximumSelect(void* user_data) { LLString::format_map_t args; args["[MAX_SELECT]"] = llformat("%d", MAX_FRIEND_SELECT); @@ -464,10 +457,12 @@ void LLFloaterFriends::onMaximumSelect(void* user_data) }; // static -void LLFloaterFriends::onClickProfile(void* user_data) +void LLPanelFriends::onClickProfile(void* user_data) { - //llinfos << "LLFloaterFriends::onClickProfile()" << llendl; - LLDynamicArray<LLUUID> ids = getSelectedIDs(); + LLPanelFriends* panelp = (LLPanelFriends*)user_data; + + //llinfos << "LLPanelFriends::onClickProfile()" << llendl; + LLDynamicArray<LLUUID> ids = panelp->getSelectedIDs(); if(ids.size() > 0) { LLUUID agent_id = ids[0]; @@ -478,10 +473,12 @@ void LLFloaterFriends::onClickProfile(void* user_data) } // static -void LLFloaterFriends::onClickIM(void* user_data) +void LLPanelFriends::onClickIM(void* user_data) { - //llinfos << "LLFloaterFriends::onClickIM()" << llendl; - LLDynamicArray<LLUUID> ids = getSelectedIDs(); + LLPanelFriends* panelp = (LLPanelFriends*)user_data; + + //llinfos << "LLPanelFriends::onClickIM()" << llendl; + LLDynamicArray<LLUUID> ids = panelp->getSelectedIDs(); if(ids.size() > 0) { if(ids.size() == 1) @@ -494,8 +491,8 @@ void LLFloaterFriends::onClickIM(void* user_data) { char buffer[MAX_STRING]; /* Flawfinder: ignore */ snprintf(buffer, MAX_STRING, "%s %s", first, last); /* Flawfinder: ignore */ - gIMView->setFloaterOpen(TRUE); - gIMView->addSession( + gIMMgr->setFloaterOpen(TRUE); + gIMMgr->addSession( buffer, IM_NOTHING_SPECIAL, agent_id); @@ -503,17 +500,18 @@ void LLFloaterFriends::onClickIM(void* user_data) } else { - gIMView->setFloaterOpen(TRUE); - gIMView->addSession("Friends Conference", + gIMMgr->setFloaterOpen(TRUE); + gIMMgr->addSession("Friends Conference", IM_SESSION_CONFERENCE_START, ids[0], ids); } + make_ui_sound("UISndStartIM"); } } // static -void LLFloaterFriends::requestFriendship(const LLUUID& target_id, const LLString& target_name) +void LLPanelFriends::requestFriendship(const LLUUID& target_id, const LLString& target_name) { // HACK: folder id stored as "message" LLUUID calling_card_folder_id = gInventory.findCategoryUUIDForType(LLAssetType::AT_CALLINGCARD); @@ -532,7 +530,7 @@ struct LLAddFriendData }; // static -void LLFloaterFriends::callbackAddFriend(S32 option, void* data) +void LLPanelFriends::callbackAddFriend(S32 option, void* data) { LLAddFriendData* add = (LLAddFriendData*)data; if (option == 0) @@ -543,7 +541,7 @@ void LLFloaterFriends::callbackAddFriend(S32 option, void* data) } // static -void LLFloaterFriends::onPickAvatar(const std::vector<std::string>& names, +void LLPanelFriends::onPickAvatar(const std::vector<std::string>& names, const std::vector<LLUUID>& ids, void* ) { @@ -553,7 +551,7 @@ void LLFloaterFriends::onPickAvatar(const std::vector<std::string>& names, } // static -void LLFloaterFriends::requestFriendshipDialog(const LLUUID& id, +void LLPanelFriends::requestFriendshipDialog(const LLUUID& id, const std::string& name) { if(id == gAgentID) @@ -573,16 +571,24 @@ void LLFloaterFriends::requestFriendshipDialog(const LLUUID& id, } // static -void LLFloaterFriends::onClickAddFriend(void* user_data) +void LLPanelFriends::onClickAddFriend(void* user_data) { - LLFloaterAvatarPicker::show(onPickAvatar, user_data, FALSE, TRUE); + LLPanelFriends* panelp = (LLPanelFriends*)user_data; + LLFloater* root_floater = gFloaterView->getParentFloater(panelp); + LLFloaterAvatarPicker* picker = LLFloaterAvatarPicker::show(onPickAvatar, user_data, FALSE, TRUE); + if (root_floater) + { + root_floater->addDependentFloater(picker); + } } // static -void LLFloaterFriends::onClickRemove(void* user_data) +void LLPanelFriends::onClickRemove(void* user_data) { - //llinfos << "LLFloaterFriends::onClickRemove()" << llendl; - LLDynamicArray<LLUUID> ids = getSelectedIDs(); + LLPanelFriends* panelp = (LLPanelFriends*)user_data; + + //llinfos << "LLPanelFriends::onClickRemove()" << llendl; + LLDynamicArray<LLUUID> ids = panelp->getSelectedIDs(); LLStringBase<char>::format_map_t args; if(ids.size() > 0) { @@ -610,9 +616,11 @@ void LLFloaterFriends::onClickRemove(void* user_data) } // static -void LLFloaterFriends::onClickOfferTeleport(void*) +void LLPanelFriends::onClickOfferTeleport(void* user_data) { - LLDynamicArray<LLUUID> ids = getSelectedIDs(); + LLPanelFriends* panelp = (LLPanelFriends*)user_data; + + LLDynamicArray<LLUUID> ids = panelp->getSelectedIDs(); if(ids.size() > 0) { handle_lure(ids); @@ -620,43 +628,40 @@ void LLFloaterFriends::onClickOfferTeleport(void*) } // static -void LLFloaterFriends::onClickPay(void*) +void LLPanelFriends::onClickPay(void* user_data) { - LLDynamicArray<LLUUID> ids = getSelectedIDs(); + LLPanelFriends* panelp = (LLPanelFriends*)user_data; + + LLDynamicArray<LLUUID> ids = panelp->getSelectedIDs(); if(ids.size() == 1) { handle_pay_by_id(ids[0]); } } -// static -void LLFloaterFriends::onClickClose(void* user_data) +void LLPanelFriends::onClickOnlineStatus(LLUICtrl* ctrl, void* user_data) { - //llinfos << "LLFloaterFriends::onClickClose()" << llendl; - if(sInstance) - { - sInstance->onClose(false); - } -} + LLPanelFriends* panelp = (LLPanelFriends*)user_data; -void LLFloaterFriends::onClickOnlineStatus(LLUICtrl* ctrl, void* user_data) -{ bool checked = ctrl->getValue(); - sInstance->updateMenuState(LLRelationship::GRANT_ONLINE_STATUS, checked); - sInstance->applyRightsToFriends(LLRelationship::GRANT_ONLINE_STATUS, checked); + panelp->updateMenuState(LLRelationship::GRANT_ONLINE_STATUS, checked); + panelp->applyRightsToFriends(LLRelationship::GRANT_ONLINE_STATUS, checked); } -void LLFloaterFriends::onClickMapStatus(LLUICtrl* ctrl, void* user_data) +void LLPanelFriends::onClickMapStatus(LLUICtrl* ctrl, void* user_data) { + LLPanelFriends* panelp = (LLPanelFriends*)user_data; bool checked = ctrl->getValue(); - sInstance->updateMenuState(LLRelationship::GRANT_MAP_LOCATION, checked); - sInstance->applyRightsToFriends(LLRelationship::GRANT_MAP_LOCATION, checked); + panelp->updateMenuState(LLRelationship::GRANT_MAP_LOCATION, checked); + panelp->applyRightsToFriends(LLRelationship::GRANT_MAP_LOCATION, checked); } -void LLFloaterFriends::onClickModifyStatus(LLUICtrl* ctrl, void* user_data) +void LLPanelFriends::onClickModifyStatus(LLUICtrl* ctrl, void* user_data) { + LLPanelFriends* panelp = (LLPanelFriends*)user_data; + bool checked = ctrl->getValue(); - LLDynamicArray<LLUUID> ids = getSelectedIDs(); + LLDynamicArray<LLUUID> ids = panelp->getSelectedIDs(); LLStringBase<char>::format_map_t args; if(ids.size() > 0) { @@ -670,33 +675,35 @@ void LLFloaterFriends::onClickModifyStatus(LLUICtrl* ctrl, void* user_data) args["[FIRST_NAME]"] = first; args["[LAST_NAME]"] = last; } - if(checked) gViewerWindow->alertXml("GrantModifyRights", args, handleModifyRights, NULL); - else gViewerWindow->alertXml("RevokeModifyRights", args, handleModifyRights, NULL); + if(checked) gViewerWindow->alertXml("GrantModifyRights", args, handleModifyRights, user_data); + else gViewerWindow->alertXml("RevokeModifyRights", args, handleModifyRights, user_data); } else return; } } -void LLFloaterFriends::handleModifyRights(S32 option, void* user_data) +void LLPanelFriends::handleModifyRights(S32 option, void* user_data) { - if(sInstance) + LLPanelFriends* panelp = (LLPanelFriends*)user_data; + + if(panelp) { if(!option) { - sInstance->updateMenuState(LLRelationship::GRANT_MODIFY_OBJECTS, !((sInstance->getMenuState() & LLRelationship::GRANT_MODIFY_OBJECTS) != 0)); - sInstance->applyRightsToFriends(LLRelationship::GRANT_MODIFY_OBJECTS, ((sInstance->getMenuState() & LLRelationship::GRANT_MODIFY_OBJECTS) != 0)); + panelp->updateMenuState(LLRelationship::GRANT_MODIFY_OBJECTS, !((panelp->getMenuState() & LLRelationship::GRANT_MODIFY_OBJECTS) != 0)); + panelp->applyRightsToFriends(LLRelationship::GRANT_MODIFY_OBJECTS, ((panelp->getMenuState() & LLRelationship::GRANT_MODIFY_OBJECTS) != 0)); } - sInstance->refreshUI(); + panelp->refreshUI(); } } -void LLFloaterFriends::updateMenuState(S32 flag, BOOL value) +void LLPanelFriends::updateMenuState(S32 flag, BOOL value) { if(value) mMenuState |= flag; else mMenuState &= ~flag; } -void LLFloaterFriends::applyRightsToFriends(S32 flag, BOOL value) +void LLPanelFriends::applyRightsToFriends(S32 flag, BOOL value) { LLMessageSystem* msg = gMessageSystem; msg->newMessageFast(_PREHASH_GrantUserRights); @@ -725,7 +732,7 @@ void LLFloaterFriends::applyRightsToFriends(S32 flag, BOOL value) // static -void LLFloaterFriends::handleRemove(S32 option, void* user_data) +void LLPanelFriends::handleRemove(S32 option, void* user_data) { LLDynamicArray<LLUUID>* ids = static_cast<LLDynamicArray<LLUUID>*>(user_data); for(LLDynamicArray<LLUUID>::iterator itr = ids->begin(); itr != ids->end(); ++itr) diff --git a/indra/newview/llfloaterfriends.h b/indra/newview/llfloaterfriends.h index c6ef365517..6895e469e5 100644 --- a/indra/newview/llfloaterfriends.h +++ b/indra/newview/llfloaterfriends.h @@ -11,7 +11,7 @@ #ifndef LL_LLFLOATERFRIENDS_H #define LL_LLFLOATERFRIENDS_H -#include "llfloater.h" +#include "llpanel.h" #include "llstring.h" #include "lluuid.h" #include "lltimer.h" @@ -20,24 +20,23 @@ class LLFriendObserver; /** - * @class LLFloaterFriends + * @class LLPanelFriends * @brief An instance of this class is used for displaying your friends * and gives you quick access to all agents which a user relationship. * * @sa LLFloater */ -class LLFloaterFriends : public LLFloater, public LLEventTimer +class LLPanelFriends : public LLPanel, public LLEventTimer { public: - virtual ~LLFloaterFriends(); + LLPanelFriends(); + virtual ~LLPanelFriends(); /** * @brief This method either creates or brings to the front the * current instantiation of this floater. There is only once since * you can currently only look at your local friends. */ - static void show(void* ignored = NULL); - virtual void tick(); /** @@ -48,11 +47,6 @@ public: virtual BOOL postBuild(); - static BOOL visible(void* unused = NULL); - - // Toggles visibility of floater - static void toggle(void* unused = NULL); - // Show a dialog explaining what friendship entails, then request // friendship. JC static void requestFriendshipDialog(const LLUUID& target_id, @@ -75,19 +69,18 @@ private: }; // protected members - LLFloaterFriends(); void reloadNames(); void refreshNames(); void refreshUI(); - void refreshRightsChangeList(U8 state); + void refreshRightsChangeList(); void applyRightsToFriends(S32 flag, BOOL value); void updateMenuState(S32 flag, BOOL value); S32 getMenuState() { return mMenuState; } void addFriend(const std::string& name, const LLUUID& agent_id); // return LLUUID::null if nothing is selected - static LLDynamicArray<LLUUID> getSelectedIDs(); + LLDynamicArray<LLUUID> getSelectedIDs(); // callback methods static void onSelectName(LLUICtrl* ctrl, void* user_data); @@ -103,8 +96,6 @@ private: static void onClickOfferTeleport(void* user_data); static void onClickPay(void* user_data); - static void onClickClose(void* user_data); - static void onClickOnlineStatus(LLUICtrl* ctrl, void* user_data); static void onClickMapStatus(LLUICtrl* ctrl, void* user_data); static void onClickModifyStatus(LLUICtrl* ctrl, void* user_data); @@ -113,9 +104,6 @@ private: static void handleModifyRights(S32 option, void* user_data); private: - // static data - static LLFloaterFriends* sInstance; - // member data LLFriendObserver* mObserver; LLUUID mAddFriendID; diff --git a/indra/newview/llfloatergroups.cpp b/indra/newview/llfloatergroups.cpp index 4535a11ec3..60cfa3b809 100644 --- a/indra/newview/llfloatergroups.cpp +++ b/indra/newview/llfloatergroups.cpp @@ -1,6 +1,6 @@ /** * @file llfloatergroups.cpp - * @brief LLFloaterGroups class implementation + * @brief LLPanelGroups class implementation * * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. * $License$ @@ -30,115 +30,135 @@ #include "lltextbox.h" #include "llvieweruictrlfactory.h" #include "llviewerwindow.h" - -const LLRect FLOATER_RECT(0, 258, 280, 0); -const char FLOATER_TITLE[] = "Groups"; +#include "llimview.h" // static -LLMap<const LLUUID, LLFloaterGroups*> LLFloaterGroups::sInstances; +std::map<const LLUUID, LLFloaterGroupPicker*> LLFloaterGroupPicker::sInstances; +// helper functions +void init_group_list(LLScrollListCtrl* ctrl, const LLUUID& highlight_id); ///---------------------------------------------------------------------------- -/// Class LLFloaterGroups +/// Class LLFloaterGroupPicker ///---------------------------------------------------------------------------- -//LLEventListener -//virtual -bool LLFloaterGroups::handleEvent(LLPointer<LLEvent> event, const LLSD& userdata) +// static +LLFloaterGroupPicker* LLFloaterGroupPicker::findInstance(const LLSD& seed) { - if (event->desc() == "new group") + instance_map_t::iterator found_it = sInstances.find(seed.asUUID()); + if (found_it != sInstances.end()) { - reset(); - return true; + return found_it->second; } - - return LLView::handleEvent(event, userdata); + return NULL; } -// Call this with an agent id and AGENT_GROUPS for an agent's -// groups, otherwise, call with an object id and SET_OBJECT_GROUP -// when modifying an object. // static -LLFloaterGroups* LLFloaterGroups::show(const LLUUID& id, EGroupDialog type) +LLFloaterGroupPicker* LLFloaterGroupPicker::createInstance(const LLSD &seed) +{ + LLFloaterGroupPicker* pickerp = new LLFloaterGroupPicker(seed); + gUICtrlFactory->buildFloater(pickerp, "floater_choose_group.xml"); + return pickerp; +} + +LLFloaterGroupPicker::LLFloaterGroupPicker(const LLSD& seed) : + mSelectCallback(NULL), + mCallbackUserdata(NULL) +{ + mID = seed.asUUID(); + sInstances.insert(std::make_pair(mID, this)); +} + +LLFloaterGroupPicker::~LLFloaterGroupPicker() +{ + sInstances.erase(mID); +} + +void LLFloaterGroupPicker::setSelectCallback(void (*callback)(LLUUID, void*), + void* userdata) { - LLFloaterGroups* instance = NULL; - if(sInstances.checkData(id)) + mSelectCallback = callback; + mCallbackUserdata = userdata; +} + +BOOL LLFloaterGroupPicker::postBuild() +{ + init_group_list(LLUICtrlFactory::getScrollListByName(this, "group list"), gAgent.getGroupID()); + + childSetAction("OK", onBtnOK, this); + + childSetAction("Cancel", onBtnCancel, this); + + setDefaultBtn("OK"); + + childSetDoubleClickCallback("group list", onBtnOK); + childSetUserData("group list", this); + + childEnable("OK"); + + return TRUE; +} + +void LLFloaterGroupPicker::onBtnOK(void* userdata) +{ + LLFloaterGroupPicker* self = (LLFloaterGroupPicker*)userdata; + if(self) self->ok(); +} + +void LLFloaterGroupPicker::onBtnCancel(void* userdata) +{ + LLFloaterGroupPicker* self = (LLFloaterGroupPicker*)userdata; + if(self) self->close(); +} + + +void LLFloaterGroupPicker::ok() +{ + LLCtrlListInterface *group_list = childGetListInterface("group list"); + LLUUID group_id; + if (group_list) { - instance = sInstances.getData(id); - if (instance->getType() != type) - { - // not the type we want ==> destroy it and rebuild below - instance->destroy(); - instance = NULL; - } - else - { - // Move the existing view to the front - instance->open(); /* Flawfinder: ignore */ - } + group_id = group_list->getCurrentID(); } - - if (!instance) + if(mSelectCallback) { - S32 left = 0; - S32 top = 0; - LLRect rect = FLOATER_RECT; - rect.translate( left - rect.mLeft, top - rect.mTop ); - instance = new LLFloaterGroups("groups", rect, FLOATER_TITLE, id); - if(instance) - { - sInstances.addData(id, instance); - //instance->init(type); - instance->mType = type; - switch (type) - { - case AGENT_GROUPS: - gUICtrlFactory->buildFloater(instance, "floater_groups.xml"); - break; - case CHOOSE_ONE: - gUICtrlFactory->buildFloater(instance, "floater_choose_group.xml"); - break; - } - instance->center(); - instance->open(); /*Flawfinder: ignore*/ - } + mSelectCallback(group_id, mCallbackUserdata); } - return instance; + + close(); } -// static -LLFloaterGroups* LLFloaterGroups::getInstance(const LLUUID& id) +///---------------------------------------------------------------------------- +/// Class LLPanelGroups +///---------------------------------------------------------------------------- + +//LLEventListener +//virtual +bool LLPanelGroups::handleEvent(LLPointer<LLEvent> event, const LLSD& userdata) { - if(sInstances.checkData(id)) + if (event->desc() == "new group") { - return sInstances.getData(id); + reset(); + return true; } - return NULL; + + return LLView::handleEvent(event, userdata); } // Default constructor -LLFloaterGroups::LLFloaterGroups(const std::string& name, - const LLRect& rect, - const std::string& title, - const LLUUID& id) : - LLFloater(name, rect, title), - mID(id), - mType(AGENT_GROUPS), - mOKCallback(NULL), - mCallbackUserdata(NULL) +LLPanelGroups::LLPanelGroups() : + LLPanel() { + gAgent.addListener(this, "new group"); } -// Destroys the object -LLFloaterGroups::~LLFloaterGroups() +LLPanelGroups::~LLPanelGroups() { - gFocusMgr.releaseFocusIfNeeded( this ); - - sInstances.removeData(mID); + gAgent.removeListener(this); } // clear the group list, and get a fresh set of info. -void LLFloaterGroups::reset() +void LLPanelGroups::reset() { LLCtrlListInterface *group_list = childGetListInterface("group list"); if (group_list) @@ -148,215 +168,126 @@ void LLFloaterGroups::reset() childSetTextArg("groupcount", "[COUNT]", llformat("%d",gAgent.mGroups.count())); childSetTextArg("groupcount", "[MAX]", llformat("%d",MAX_AGENT_GROUPS)); - initAgentGroups(gAgent.getGroupID()); + init_group_list(LLUICtrlFactory::getScrollListByName(this, "group list"), gAgent.getGroupID()); enableButtons(); } -void LLFloaterGroups::setOkCallback(void (*callback)(LLUUID, void*), - void* userdata) -{ - mOKCallback = callback; - mCallbackUserdata = userdata; -} - -BOOL LLFloaterGroups::postBuild() +BOOL LLPanelGroups::postBuild() { childSetCommitCallback("group list", onGroupList, this); - if(mType == AGENT_GROUPS) - { - childSetTextArg("groupcount", "[COUNT]", llformat("%d",gAgent.mGroups.count())); - childSetTextArg("groupcount", "[MAX]", llformat("%d",MAX_AGENT_GROUPS)); - - initAgentGroups(gAgent.getGroupID()); - - childSetAction("Activate", onBtnActivate, this); + childSetTextArg("groupcount", "[COUNT]", llformat("%d",gAgent.mGroups.count())); + childSetTextArg("groupcount", "[MAX]", llformat("%d",MAX_AGENT_GROUPS)); - childSetAction("Info", onBtnInfo, this); + init_group_list(LLUICtrlFactory::getScrollListByName(this, "group list"), gAgent.getGroupID()); - childSetAction("Leave", onBtnLeave, this); + childSetAction("Activate", onBtnActivate, this); - childSetAction("Create", onBtnCreate, this); + childSetAction("Info", onBtnInfo, this); - childSetAction("Search...", onBtnSearch, this); + childSetAction("IM", onBtnIM, this); - childSetAction("Close", onBtnCancel, this); + childSetAction("Leave", onBtnLeave, this); - setDefaultBtn("Info"); + childSetAction("Create", onBtnCreate, this); - childSetDoubleClickCallback("group list", onBtnInfo); - childSetUserData("group list", this); - } - else - { - initAgentGroups(gAgent.getGroupID()); - - childSetAction("OK", onBtnOK, this); + childSetAction("Search...", onBtnSearch, this); - childSetAction("Cancel", onBtnCancel, this); + setDefaultBtn("IM"); - setDefaultBtn("OK"); + childSetDoubleClickCallback("group list", onBtnIM); + childSetUserData("group list", this); - childSetDoubleClickCallback("group list", onBtnOK); - childSetUserData("group list", this); - } - - enableButtons(); + reset(); return TRUE; } -void LLFloaterGroups::initAgentGroups(const LLUUID& highlight_id) +void LLPanelGroups::enableButtons() { - S32 count = gAgent.mGroups.count(); - LLUUID id; LLCtrlListInterface *group_list = childGetListInterface("group list"); - if (!group_list) return; - - group_list->operateOnAll(LLCtrlListInterface::OP_DELETE); - - for(S32 i = 0; i < count; ++i) + LLUUID group_id; + if (group_list) { - id = gAgent.mGroups.get(i).mID; - LLGroupData* group_datap = &gAgent.mGroups.get(i); - LLString style = "NORMAL"; - if(highlight_id == id) - { - style = "BOLD"; - } - - LLSD element; - element["id"] = id; - element["columns"][0]["column"] = "name"; - element["columns"][0]["value"] = group_datap->mName; - element["columns"][0]["font"] = "SANSSERIF"; - element["columns"][0]["font-style"] = style; - - group_list->addElement(element, ADD_SORTED); + group_id = group_list->getCurrentID(); } + if(group_id != gAgent.getGroupID()) { - LLString style = "NORMAL"; - if (highlight_id.isNull()) - { - style = "BOLD"; - } - LLSD element; - element["id"] = LLUUID::null; - element["columns"][0]["column"] = "name"; - element["columns"][0]["value"] = "none"; - element["columns"][0]["font"] = "SANSSERIF"; - element["columns"][0]["font-style"] = style; - - group_list->addElement(element, ADD_TOP); + childEnable("Activate"); } - - group_list->selectByValue(highlight_id); - - childSetFocus("group list"); -} - -void LLFloaterGroups::enableButtons() -{ - LLCtrlListInterface *group_list = childGetListInterface("group list"); - LLUUID group_id; - if (group_list) + else { - group_id = group_list->getCurrentID(); + childDisable("Activate"); } - if(mType == AGENT_GROUPS) + if (group_id.notNull()) { - if(group_id != gAgent.getGroupID()) - { - childEnable("Activate"); - } - else - { - childDisable("Activate"); - } - if (group_id.notNull()) - { - childEnable("Info"); - childEnable("Leave"); - } - else - { - childDisable("Info"); - childDisable("Leave"); - } - if(gAgent.mGroups.count() < MAX_AGENT_GROUPS) - { - childEnable("Create"); - } - else - { - childDisable("Create"); - } + childEnable("Info"); + childEnable("IM"); + childEnable("Leave"); } else { - childEnable("OK"); + childDisable("Info"); + childDisable("IM"); + childDisable("Leave"); + } + if(gAgent.mGroups.count() < MAX_AGENT_GROUPS) + { + childEnable("Create"); + } + else + { + childDisable("Create"); } } -void LLFloaterGroups::onBtnCreate(void* userdata) +void LLPanelGroups::onBtnCreate(void* userdata) { - LLFloaterGroups* self = (LLFloaterGroups*)userdata; + LLPanelGroups* self = (LLPanelGroups*)userdata; if(self) self->create(); } -void LLFloaterGroups::onBtnActivate(void* userdata) +void LLPanelGroups::onBtnActivate(void* userdata) { - LLFloaterGroups* self = (LLFloaterGroups*)userdata; + LLPanelGroups* self = (LLPanelGroups*)userdata; if(self) self->activate(); } -void LLFloaterGroups::onBtnInfo(void* userdata) +void LLPanelGroups::onBtnInfo(void* userdata) { - LLFloaterGroups* self = (LLFloaterGroups*)userdata; + LLPanelGroups* self = (LLPanelGroups*)userdata; if(self) self->info(); } -void LLFloaterGroups::onBtnLeave(void* userdata) +void LLPanelGroups::onBtnIM(void* userdata) { - LLFloaterGroups* self = (LLFloaterGroups*)userdata; - if(self) self->leave(); + LLPanelGroups* self = (LLPanelGroups*)userdata; + if(self) self->startIM(); } -void LLFloaterGroups::onBtnSearch(void* userdata) +void LLPanelGroups::onBtnLeave(void* userdata) { - LLFloaterGroups* self = (LLFloaterGroups*)userdata; - if(self) self->search(); -} - -void LLFloaterGroups::onBtnOK(void* userdata) -{ - LLFloaterGroups* self = (LLFloaterGroups*)userdata; - if(self) self->ok(); -} - -void LLFloaterGroups::onBtnCancel(void* userdata) -{ - LLFloaterGroups* self = (LLFloaterGroups*)userdata; - if(self) self->close(); + LLPanelGroups* self = (LLPanelGroups*)userdata; + if(self) self->leave(); } -void LLFloaterGroups::onGroupList(LLUICtrl* ctrl, void* userdata) +void LLPanelGroups::onBtnSearch(void* userdata) { - LLFloaterGroups* self = (LLFloaterGroups*)userdata; - if(self) self->highlightGroupList(ctrl); + LLPanelGroups* self = (LLPanelGroups*)userdata; + if(self) self->search(); } -void LLFloaterGroups::create() +void LLPanelGroups::create() { - llinfos << "LLFloaterGroups::create" << llendl; + llinfos << "LLPanelGroups::create" << llendl; LLFloaterGroupInfo::showCreateGroup(NULL); } -void LLFloaterGroups::activate() +void LLPanelGroups::activate() { - llinfos << "LLFloaterGroups::activate" << llendl; + llinfos << "LLPanelGroups::activate" << llendl; LLCtrlListInterface *group_list = childGetListInterface("group list"); LLUUID group_id; if (group_list) @@ -372,9 +303,9 @@ void LLFloaterGroups::activate() gAgent.sendReliableMessage(); } -void LLFloaterGroups::info() +void LLPanelGroups::info() { - llinfos << "LLFloaterGroups::info" << llendl; + llinfos << "LLPanelGroups::info" << llendl; LLCtrlListInterface *group_list = childGetListInterface("group list"); LLUUID group_id; if (group_list && (group_id = group_list->getCurrentID()).notNull()) @@ -383,9 +314,36 @@ void LLFloaterGroups::info() } } -void LLFloaterGroups::leave() +void LLPanelGroups::startIM() +{ + //llinfos << "LLPanelFriends::onClickIM()" << llendl; + LLCtrlListInterface *group_list = childGetListInterface("group list"); + LLUUID group_id; + + if (group_list && (group_id = group_list->getCurrentID()).notNull()) + { + LLGroupData group_data; + if (gAgent.getGroupData(group_id, group_data)) + { + gIMMgr->setFloaterOpen(TRUE); + gIMMgr->addSession( + group_data.mName, + IM_SESSION_GROUP_START, + group_id); + make_ui_sound("UISndStartIM"); + } + else + { + // this should never happen, as starting a group IM session + // relies on you belonging to the group and hence having the group data + make_ui_sound("UISndInvalidOp"); + } + } +} + +void LLPanelGroups::leave() { - llinfos << "LLFloaterGroups::leave" << llendl; + llinfos << "LLPanelGroups::leave" << llendl; LLCtrlListInterface *group_list = childGetListInterface("group list"); LLUUID group_id; if (group_list && (group_id = group_list->getCurrentID()).notNull()) @@ -407,13 +365,13 @@ void LLFloaterGroups::leave() } } -void LLFloaterGroups::search() +void LLPanelGroups::search() { LLFloaterDirectory::showGroups(); } // static -void LLFloaterGroups::callbackLeaveGroup(S32 option, void* userdata) +void LLPanelGroups::callbackLeaveGroup(S32 option, void* userdata) { LLUUID* group_id = (LLUUID*)userdata; if(option == 0 && group_id) @@ -430,25 +388,58 @@ void LLFloaterGroups::callbackLeaveGroup(S32 option, void* userdata) delete group_id; } -void LLFloaterGroups::ok() +void LLPanelGroups::onGroupList(LLUICtrl* ctrl, void* userdata) { - llinfos << "LLFloaterGroups::ok" << llendl; - LLCtrlListInterface *group_list = childGetListInterface("group list"); - LLUUID group_id; - if (group_list) + LLPanelGroups* self = (LLPanelGroups*)userdata; + if(self) self->enableButtons(); +} + +void init_group_list(LLScrollListCtrl* ctrl, const LLUUID& highlight_id) +{ + S32 count = gAgent.mGroups.count(); + LLUUID id; + LLCtrlListInterface *group_list = ctrl->getListInterface(); + if (!group_list) return; + + group_list->operateOnAll(LLCtrlListInterface::OP_DELETE); + + for(S32 i = 0; i < count; ++i) { - group_id = group_list->getCurrentID(); + id = gAgent.mGroups.get(i).mID; + LLGroupData* group_datap = &gAgent.mGroups.get(i); + LLString style = "NORMAL"; + if(highlight_id == id) + { + style = "BOLD"; + } + + LLSD element; + element["id"] = id; + element["columns"][0]["column"] = "name"; + element["columns"][0]["value"] = group_datap->mName; + element["columns"][0]["font"] = "SANSSERIF"; + element["columns"][0]["font-style"] = style; + + group_list->addElement(element, ADD_SORTED); } - if(mOKCallback) + + // add "none" to list at top { - mOKCallback(group_id, mCallbackUserdata); + LLString style = "NORMAL"; + if (highlight_id.isNull()) + { + style = "BOLD"; + } + LLSD element; + element["id"] = LLUUID::null; + element["columns"][0]["column"] = "name"; + element["columns"][0]["value"] = "none"; + element["columns"][0]["font"] = "SANSSERIF"; + element["columns"][0]["font-style"] = style; + + group_list->addElement(element, ADD_TOP); } - close(); + group_list->selectByValue(highlight_id); } -void LLFloaterGroups::highlightGroupList(LLUICtrl*) -{ - llinfos << "LLFloaterGroups::highlightGroupList" << llendl; - enableButtons(); -} diff --git a/indra/newview/llfloatergroups.h b/indra/newview/llfloatergroups.h index 6f73c97553..65bed95ea8 100644 --- a/indra/newview/llfloatergroups.h +++ b/indra/newview/llfloatergroups.h @@ -21,89 +21,80 @@ //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #include "lluuid.h" -#include "llmap.h" -#include "llevent.h" #include "llfloater.h" +#include <map> class LLUICtrl; class LLTextBox; class LLScrollListCtrl; class LLButton; +class LLFloaterGroupPicker; -class LLFloaterGroups : public LLFloater +class LLFloaterGroupPicker : public LLFloater, public LLUIInstanceMgr<LLFloaterGroupPicker> { + friend class LLUIInstanceMgr<LLFloaterGroupPicker>; public: + ~LLFloaterGroupPicker(); + void setSelectCallback( void (*callback)(LLUUID, void*), + void* userdata); + BOOL postBuild(); + +protected: + LLFloaterGroupPicker(const LLSD& seed); + void ok(); + static LLFloaterGroupPicker* findInstance(const LLSD& seed); + static LLFloaterGroupPicker* createInstance(const LLSD& seed); + static void onBtnOK(void* userdata); + static void onBtnCancel(void* userdata); + +protected: + LLUUID mID; + void (*mSelectCallback)(LLUUID id, void* userdata); + void* mCallbackUserdata; + + typedef std::map<const LLUUID, LLFloaterGroupPicker*> instance_map_t; + static instance_map_t sInstances; +}; + +class LLPanelGroups : public LLPanel +{ +public: + LLPanelGroups(); + virtual ~LLPanelGroups(); + //LLEventListener /*virtual*/ bool handleEvent(LLPointer<LLEvent> event, const LLSD& userdata); - enum EGroupDialog - { - AGENT_GROUPS, - CHOOSE_ONE - }; - // Call this with an agent id and AGENT_GROUPS for an agent's - // groups, otherwise, call with an object id and SET_OBJECT_GROUP - // when modifying an object. - static LLFloaterGroups* show(const LLUUID& id, EGroupDialog type); - - // Return the instance requested if it already exists. Otherwise, - // return NULL. - static LLFloaterGroups* getInstance(const LLUUID& id); - // clear the group list, and get a fresh set of info. void reset(); - void setOkCallback( void (*callback)(LLUUID, void*), - void* userdata); - - EGroupDialog getType() const { return mType; } - protected: // initialize based on the type BOOL postBuild(); // highlight_id is a group id to highlight - void initAgentGroups(const LLUUID& highlight_id); void enableButtons(); + static void onGroupList(LLUICtrl* ctrl, void* userdata); static void onBtnCreate(void* userdata); static void onBtnActivate(void* userdata); static void onBtnInfo(void* userdata); + static void onBtnIM(void* userdata); static void onBtnLeave(void* userdata); static void onBtnSearch(void* userdata); static void onBtnVote(void* userdata); - static void onBtnOK(void* userdata); - static void onBtnCancel(void* userdata); - static void onGroupList(LLUICtrl* ctrl, void* userdata); static void onDoubleClickGroup(void* userdata); void create(); void activate(); void info(); + void startIM(); void leave(); void search(); void callVote(); - void ok(); - void highlightGroupList(LLUICtrl*); static void callbackLeaveGroup(S32 option, void* userdata); -protected: - LLUUID mID; - - EGroupDialog mType; - - void (*mOKCallback)(LLUUID id, void* userdata); - void* mCallbackUserdata; - -protected: - static LLMap<const LLUUID, LLFloaterGroups*> sInstances; - -public: - // do not call these directly - LLFloaterGroups(const std::string& name, const LLRect& rect, const std::string& title, - const LLUUID& id); - virtual ~LLFloaterGroups(); }; diff --git a/indra/newview/llfloaterland.cpp b/indra/newview/llfloaterland.cpp index 4b18c0c91a..421066688e 100644 --- a/indra/newview/llfloaterland.cpp +++ b/indra/newview/llfloaterland.cpp @@ -22,6 +22,7 @@ #include "llfloateravatarpicker.h" #include "llbutton.h" #include "llcheckboxctrl.h" +#include "llradiogroup.h" #include "llcombobox.h" #include "llfloaterauction.h" #include "llfloateravatarinfo.h" @@ -90,6 +91,14 @@ static const char RAW_HTML[] = "Raw HTML"; static const BOOL BUY_GROUP_LAND = TRUE; static const BOOL BUY_PERSONAL_LAND = FALSE; +// Values for the parcel voice settings radio group +enum +{ + kRadioVoiceChatEstate = 0, + kRadioVoiceChatPrivate = 1, + kRadioVoiceChatDisable = 2 +}; + // Statics LLFloaterLand* LLFloaterLand::sInstance = NULL; LLParcelSelectionObserver* LLFloaterLand::sObserver = NULL; @@ -808,9 +817,20 @@ void LLPanelLandGeneral::draw() // static void LLPanelLandGeneral::onClickSetGroup(void* userdata) { - LLFloaterGroups* fg; - fg = LLFloaterGroups::show(gAgent.getID(), LLFloaterGroups::CHOOSE_ONE); - fg->setOkCallback( cbGroupID, userdata ); + LLPanelLandGeneral* panelp = (LLPanelLandGeneral*)userdata; + LLFloaterGroupPicker* fg; + + LLFloater* parent_floater = gFloaterView->getParentFloater(panelp); + + fg = LLFloaterGroupPicker::showInstance(LLSD(gAgent.getID())); + fg->setSelectCallback( cbGroupID, userdata ); + + if (parent_floater) + { + LLRect new_rect = gFloaterView->findNeighboringPosition(parent_floater, fg); + fg->setOrigin(new_rect.mLeft, new_rect.mBottom); + parent_floater->addDependentFloater(fg); + } } // static @@ -2319,6 +2339,9 @@ BOOL LLPanelLandMedia::postBuild() mCheckSoundLocal = LLUICtrlFactory::getCheckBoxByName(this, "check sound local"); childSetCommitCallback("check sound local", onCommitAny, this); + mRadioVoiceChat = LLUICtrlFactory::getRadioGroupByName(this, "parcel_voice_channel"); + childSetCommitCallback("parcel_voice_channel", onCommitAny, this); + mMusicURLEdit = LLUICtrlFactory::getLineEditorByName(this, "music_url"); childSetCommitCallback("music_url", onCommitAny, this); @@ -2362,6 +2385,9 @@ void LLPanelLandMedia::refresh() mCheckSoundLocal->set(FALSE); mCheckSoundLocal->setEnabled(FALSE); + mRadioVoiceChat->setSelectedIndex(kRadioVoiceChatEstate); + mRadioVoiceChat->setEnabled(FALSE); + mMusicURLEdit->setText(""); mMusicURLEdit->setEnabled(FALSE); @@ -2389,6 +2415,20 @@ void LLPanelLandMedia::refresh() mCheckSoundLocal->set( parcel->getSoundLocal() ); mCheckSoundLocal->setEnabled( can_change_media ); + if(parcel->getVoiceEnabled()) + { + if(parcel->getVoiceUseEstateChannel()) + mRadioVoiceChat->setSelectedIndex(kRadioVoiceChatEstate); + else + mRadioVoiceChat->setSelectedIndex(kRadioVoiceChatPrivate); + } + else + { + mRadioVoiceChat->setSelectedIndex(kRadioVoiceChatDisable); + } + + mRadioVoiceChat->setEnabled( can_change_media ); + // don't display urls if you're not able to change it // much requested change in forums so people can't 'steal' urls // NOTE: bug#2009 means this is still vunerable - however, bug @@ -2448,16 +2488,39 @@ void LLPanelLandMedia::onCommitAny(LLUICtrl *ctrl, void *userdata) // Extract data from UI BOOL sound_local = self->mCheckSoundLocal->get(); + int voice_setting = self->mRadioVoiceChat->getSelectedIndex(); std::string music_url = self->mMusicURLEdit->getText(); std::string media_url = self->mMediaURLEdit->getText(); U8 media_auto_scale = self->mMediaAutoScaleCheck->get(); LLUUID media_id = self->mMediaTextureCtrl->getImageAssetID(); + BOOL voice_enabled; + BOOL voice_estate_chan; + + switch(voice_setting) + { + default: + case kRadioVoiceChatEstate: + voice_enabled = TRUE; + voice_estate_chan = TRUE; + break; + case kRadioVoiceChatPrivate: + voice_enabled = TRUE; + voice_estate_chan = FALSE; + break; + case kRadioVoiceChatDisable: + voice_enabled = FALSE; + voice_estate_chan = FALSE; + break; + } + // Remove leading/trailing whitespace (common when copying/pasting) LLString::trim(music_url); LLString::trim(media_url); // Push data into current parcel + parcel->setParcelFlag(PF_ALLOW_VOICE_CHAT, voice_enabled); + parcel->setParcelFlag(PF_USE_ESTATE_VOICE_CHAN, voice_estate_chan); parcel->setParcelFlag(PF_SOUND_LOCAL, sound_local); parcel->setMusicURL(music_url.c_str()); parcel->setMediaURL(media_url.c_str()); diff --git a/indra/newview/llfloaterland.h b/indra/newview/llfloaterland.h index 6c0fb7319f..ab7ff3921c 100644 --- a/indra/newview/llfloaterland.h +++ b/indra/newview/llfloaterland.h @@ -21,6 +21,7 @@ const F32 CACHE_REFRESH_TIME = 2.5f; class LLTextBox; class LLCheckBoxCtrl; +class LLRadioGroup; class LLComboBox; class LLButton; class LLNameListCtrl; @@ -348,6 +349,7 @@ public: protected: LLCheckBoxCtrl* mCheckSoundLocal; + LLRadioGroup* mRadioVoiceChat; LLLineEditor* mMusicURLEdit; LLLineEditor* mMediaURLEdit; LLTextureCtrl* mMediaTextureCtrl; diff --git a/indra/newview/llfloaternamedesc.cpp b/indra/newview/llfloaternamedesc.cpp index 602a198a03..2553c36f66 100644 --- a/indra/newview/llfloaternamedesc.cpp +++ b/indra/newview/llfloaternamedesc.cpp @@ -193,7 +193,7 @@ void LLFloaterNameDesc::onBtnOK( void* userdata ) fp->childGetValue("name_form").asString(), fp->childGetValue("description_form").asString(), bitrate, LLAssetType::AT_NONE, LLInventoryType::IT_NONE); - fp->onClose(false); + fp->close(false); } // static @@ -203,5 +203,5 @@ void LLFloaterNameDesc::onBtnOK( void* userdata ) void LLFloaterNameDesc::onBtnCancel( void* userdata ) { LLFloaterNameDesc *fp =(LLFloaterNameDesc *)userdata; - fp->onClose(false); + fp->close(false); } diff --git a/indra/newview/llfloaterpostcard.cpp b/indra/newview/llfloaterpostcard.cpp index 3eaff0101c..e44dfe7d85 100644 --- a/indra/newview/llfloaterpostcard.cpp +++ b/indra/newview/llfloaterpostcard.cpp @@ -203,7 +203,7 @@ void LLFloaterPostcard::onClickCancel(void* data) { LLFloaterPostcard *self = (LLFloaterPostcard *)data; - self->onClose(false); + self->close(false); } } diff --git a/indra/newview/llfloaterpreference.cpp b/indra/newview/llfloaterpreference.cpp index 8a1a0dcaaa..ef68e5bec2 100644 --- a/indra/newview/llfloaterpreference.cpp +++ b/indra/newview/llfloaterpreference.cpp @@ -34,6 +34,7 @@ #include "llpanelmsgs.h" #include "llpanelweb.h" #include "llprefschat.h" +#include "llprefsvoice.h" #include "llprefsim.h" #include "llresizehandle.h" #include "llresmgr.h" @@ -124,6 +125,10 @@ LLPreferenceCore::LLPreferenceCore(LLTabContainerCommon* tab_container, LLButton mTabContainer->addTabPanel(mPrefsChat->getPanel(), mPrefsChat->getPanel()->getLabel(), FALSE, onTabChanged, mTabContainer); mPrefsChat->getPanel()->setDefaultBtn(default_btn); + mPrefsVoice = new LLPrefsVoice(); + mTabContainer->addTabPanel(mPrefsVoice, mPrefsVoice->getLabel(), FALSE, onTabChanged, mTabContainer); + mPrefsVoice->setDefaultBtn(default_btn); + mPrefsIM = new LLPrefsIM(); mTabContainer->addTabPanel(mPrefsIM->getPanel(), mPrefsIM->getPanel()->getLabel(), FALSE, onTabChanged, mTabContainer); mPrefsIM->getPanel()->setDefaultBtn(default_btn); @@ -205,8 +210,8 @@ void LLPreferenceCore::apply() mDisplayPanel->apply(); mDisplayPanel2->apply(); mDisplayPanel3->apply(); - mAudioPanel->apply(); mPrefsChat->apply(); + mPrefsVoice->apply(); mPrefsIM->apply(); mMsgPanel->apply(); #if LL_LIBXUL_ENABLED @@ -225,6 +230,7 @@ void LLPreferenceCore::cancel() mDisplayPanel3->cancel(); mAudioPanel->cancel(); mPrefsChat->cancel(); + mPrefsVoice->cancel(); mPrefsIM->cancel(); mMsgPanel->cancel(); #if LL_LIBXUL_ENABLED @@ -368,7 +374,7 @@ void LLFloaterPreference::onBtnOK( void* userdata ) if (fp->canClose()) { fp->apply(); - fp->onClose(false); + fp->close(false); gSavedSettings.saveToFile( gSettingsFileName, TRUE ); diff --git a/indra/newview/llfloaterpreference.h b/indra/newview/llfloaterpreference.h index dcfa123137..1de960c640 100644 --- a/indra/newview/llfloaterpreference.h +++ b/indra/newview/llfloaterpreference.h @@ -29,6 +29,7 @@ class LLPanelNetwork; class LLPanelWeb; class LLMessageSystem; class LLPrefsChat; +class LLPrefsVoice; class LLPrefsIM; class LLPanelMsgs; class LLScrollListCtrl; @@ -63,6 +64,7 @@ private: LLPanelAudioPrefs *mAudioPanel; // LLPanelDebug *mDebugPanel; LLPrefsChat *mPrefsChat; + LLPrefsVoice *mPrefsVoice; LLPrefsIM *mPrefsIM; LLPanelMsgs *mMsgPanel; LLPanelWeb *mWebPanel; diff --git a/indra/newview/llfloaterregioninfo.cpp b/indra/newview/llfloaterregioninfo.cpp index 1c287a187a..efbf8a7826 100644 --- a/indra/newview/llfloaterregioninfo.cpp +++ b/indra/newview/llfloaterregioninfo.cpp @@ -1410,11 +1410,21 @@ void LLPanelEstateInfo::addAllowedGroup(S32 option, void* user_data) { if (option != 0) return; - LLFloaterGroups* widget; - widget = LLFloaterGroups::show(gAgent.getID(), LLFloaterGroups::CHOOSE_ONE); + LLPanelEstateInfo* panelp = (LLPanelEstateInfo*)user_data; + + LLFloater* parent_floater = gFloaterView->getParentFloater(panelp); + + LLFloaterGroupPicker* widget; + widget = LLFloaterGroupPicker::showInstance(LLSD(gAgent.getID())); if (widget) { - widget->setOkCallback(addAllowedGroup2, user_data); + widget->setSelectCallback(addAllowedGroup2, user_data); + if (parent_floater) + { + LLRect new_rect = gFloaterView->findNeighboringPosition(parent_floater, widget); + widget->setOrigin(new_rect.mLeft, new_rect.mBottom); + parent_floater->addDependentFloater(widget); + } } } @@ -1910,6 +1920,7 @@ BOOL LLPanelEstateInfo::postBuild() initCtrl("deny_anonymous"); initCtrl("deny_identified"); initCtrl("deny_transacted"); + initCtrl("voice_chat_check"); initHelpBtn("estate_manager_help", "HelpEstateEstateManager"); initHelpBtn("use_global_time_help", "HelpEstateUseGlobalTime"); @@ -1919,6 +1930,7 @@ BOOL LLPanelEstateInfo::postBuild() initHelpBtn("allow_resident_help", "HelpEstateAllowResident"); initHelpBtn("allow_group_help", "HelpEstateAllowGroup"); initHelpBtn("ban_resident_help", "HelpEstateBanResident"); + initHelpBtn("voice_chat_help", "HelpEstateVoiceChat"); // set up the use global time checkbox childSetCommitCallback("use_global_time_check", onChangeUseGlobalTime, this); @@ -2084,6 +2096,9 @@ void LLPanelEstateInfo::setEstateFlags(U32 flags) { childSetValue("externally_visible_check", LLSD(flags & REGION_FLAGS_EXTERNALLY_VISIBLE ? TRUE : FALSE) ); childSetValue("fixed_sun_check", LLSD(flags & REGION_FLAGS_SUN_FIXED ? TRUE : FALSE) ); + childSetValue( + "voice_chat_check", + LLSD(flags & REGION_FLAGS_ALLOW_VOICE ? TRUE : FALSE)); childSetValue("allow_direct_teleport", LLSD(flags & REGION_FLAGS_ALLOW_DIRECT_TELEPORT ? TRUE : FALSE) ); childSetValue("deny_anonymous", LLSD(flags & REGION_FLAGS_DENY_ANONYMOUS ? TRUE : FALSE) ); childSetValue("deny_identified", LLSD(flags & REGION_FLAGS_DENY_IDENTIFIED ? TRUE : FALSE) ); @@ -2099,6 +2114,11 @@ U32 LLPanelEstateInfo::computeEstateFlags() { flags |= REGION_FLAGS_EXTERNALLY_VISIBLE; } + + if ( childGetValue("voice_chat_check").asBoolean() ) + { + flags |= REGION_FLAGS_ALLOW_VOICE; + } if (childGetValue("allow_direct_teleport").asBoolean()) { diff --git a/indra/newview/llfloaterscriptdebug.cpp b/indra/newview/llfloaterscriptdebug.cpp index 0ba75b71f6..4d2f012396 100644 --- a/indra/newview/llfloaterscriptdebug.cpp +++ b/indra/newview/llfloaterscriptdebug.cpp @@ -93,7 +93,7 @@ LLFloater* LLFloaterScriptDebug::addOutputWindow(const LLUUID &object_id) LLFloater::setFloaterHost(NULL); // Tabs sometimes overlap resize handle - sInstance->moveResizeHandleToFront(); + sInstance->moveResizeHandlesToFront(); return floaterp; } diff --git a/indra/newview/llfloatersnapshot.cpp b/indra/newview/llfloatersnapshot.cpp index 80e32f9c00..5feb8b2f0d 100644 --- a/indra/newview/llfloatersnapshot.cpp +++ b/indra/newview/llfloatersnapshot.cpp @@ -361,8 +361,7 @@ void LLSnapshotLivePreview::draw() F32 shine_interp = llmin(1.f, mShineAnimTimer.getElapsedTimeF32() / SHINE_TIME); // draw "shine" effect - LLGLEnable scissor_test(GL_SCISSOR_TEST); - LLUI::setScissorRegionLocal(LLRect(0, mRect.getHeight(), mRect.getWidth(), 0)); + LLLocalClipRect clip(getLocalRect()); { // draw diagonal stripe with gradient that passes over screen S32 x1 = gViewerWindow->getWindowWidth() * llround((clamp_rescale(shine_interp, 0.f, 1.f, -1.f - SHINE_WIDTH, 1.f))); diff --git a/indra/newview/llfloatertools.cpp b/indra/newview/llfloatertools.cpp index b708a4261f..0da0958c9b 100644 --- a/indra/newview/llfloatertools.cpp +++ b/indra/newview/llfloatertools.cpp @@ -53,7 +53,6 @@ #include "llviewerparcelmgr.h" #include "llviewerwindow.h" #include "llviewercontrol.h" -#include "llvolumesliderctrl.h" #include "viewer.h" #include "llvieweruictrlfactory.h" @@ -175,7 +174,6 @@ BOOL LLFloaterTools::postBuild() childSetAction("button land",LLFloaterTools::setEditTool, (void*)gToolParcel); mTextStatus = LLUICtrlFactory::getTextBoxByName(this,"text status"); mRadioZoom = LLUICtrlFactory::getCheckBoxByName(this,"radio zoom"); - mSliderZoom = LLViewerUICtrlFactory::getVolumeSliderByName(this,"slider zoom"); childSetCommitCallback("slider zoom",commit_slider_zoom,this); mRadioOrbit = LLUICtrlFactory::getCheckBoxByName(this,"radio orbit"); childSetCommitCallback("radio orbit",commit_radio_orbit,this); @@ -476,8 +474,8 @@ void LLFloaterTools::updatePopup(LLCoordGL center, MASK mask) mRadioZoom ->setVisible( focus_visible ); mRadioOrbit ->setVisible( focus_visible ); mRadioPan ->setVisible( focus_visible ); - mSliderZoom ->setVisible( focus_visible ); - + childSetVisible("slider zoom", focus_visible); + mRadioZoom ->set( !gCameraBtnOrbit && !gCameraBtnPan && !(mask == MASK_ORBIT) && @@ -494,7 +492,7 @@ void LLFloaterTools::updatePopup(LLCoordGL center, MASK mask) (mask == (MASK_PAN | MASK_ALT)) ); // multiply by correction factor because volume sliders go [0, 0.5] - mSliderZoom ->setValue( gAgent.getCameraZoomFraction() * 0.5f); + childSetValue( "slider zoom", gAgent.getCameraZoomFraction() * 0.5f); // Move buttons BOOL move_visible = (tool == gToolGrab); @@ -833,9 +831,8 @@ void commit_radio_pan(LLUICtrl *, void*) void commit_slider_zoom(LLUICtrl *ctrl, void*) { - LLVolumeSliderCtrl* slider = (LLVolumeSliderCtrl*)ctrl; // renormalize value, since max "volume" level is 0.5 for some reason - F32 zoom_level = (F32)slider->getValue().asReal() * 2.f; // / 0.5f; + F32 zoom_level = (F32)ctrl->getValue().asReal() * 2.f; // / 0.5f; gAgent.setCameraZoomFraction(zoom_level); } diff --git a/indra/newview/llfloatertools.h b/indra/newview/llfloatertools.h index 7c85444578..156f768459 100644 --- a/indra/newview/llfloatertools.h +++ b/indra/newview/llfloatertools.h @@ -24,7 +24,6 @@ class LLPanelContents; class LLPanelFace; class LLPanelLandInfo; class LLComboBox; -class LLVolumeSliderCtrl; class LLParcelSelection; class LLObjectSelection; @@ -100,7 +99,6 @@ public: LLCheckBoxCtrl *mRadioOrbit; LLCheckBoxCtrl *mRadioZoom; LLCheckBoxCtrl *mRadioPan; - LLVolumeSliderCtrl *mSliderZoom; // Move buttons LLCheckBoxCtrl *mRadioMove; diff --git a/indra/newview/llgesturemgr.cpp b/indra/newview/llgesturemgr.cpp index be1c26381b..3c51d7dcf1 100644 --- a/indra/newview/llgesturemgr.cpp +++ b/indra/newview/llgesturemgr.cpp @@ -83,7 +83,7 @@ void LLGestureManager::activateGesture(const LLUUID& item_id) mDeactivateSimilarNames.clear(); const BOOL inform_server = TRUE; - const BOOL deactivate_similar = TRUE; + const BOOL deactivate_similar = FALSE; activateGestureWithAsset(item_id, asset_id, inform_server, deactivate_similar); } @@ -403,7 +403,7 @@ void LLGestureManager::replaceGesture(const LLUUID& item_id, LLMultiGesture* new LLLoadInfo* info = new LLLoadInfo; info->mItemID = item_id; info->mInformServer = TRUE; - info->mDeactivateSimilar = TRUE; + info->mDeactivateSimilar = FALSE; const BOOL high_priority = TRUE; gAssetStorage->getAssetData(asset_id, @@ -474,6 +474,8 @@ BOOL LLGestureManager::triggerAndReviseString(const std::string &utf8str, std::s LLString cur_token_lower = cur_token; LLString::toLower(cur_token_lower); + // collect gestures that match + std::vector <LLMultiGesture *> matching; item_map_t::iterator it; for (it = mActive.begin(); it != mActive.end(); ++it) { @@ -481,16 +483,32 @@ BOOL LLGestureManager::triggerAndReviseString(const std::string &utf8str, std::s // Gesture asset data might not have arrived yet if (!gesture) continue; - + if (!stricmp(gesture->mTrigger.c_str(), cur_token_lower.c_str())) { + matching.push_back(gesture); + } + + gesture = NULL; + } + + + if (matching.size() > 0) + { + // choose one at random + { + S32 random = ll_rand(matching.size()); + + gesture = matching[random]; + playGesture(gesture); if (!gesture->mReplaceText.empty()) { if( !first_token ) { - revised_string->append( " " ); + if (revised_string) + revised_string->append( " " ); } // Don't muck with the user's capitalization if we don't have to. @@ -499,30 +517,34 @@ BOOL LLGestureManager::triggerAndReviseString(const std::string &utf8str, std::s LLString::toLower(output_lower); if( cur_token_lower == output_lower ) { - revised_string->append( cur_token ); + if (revised_string) + revised_string->append( cur_token ); } else { - revised_string->append( output ); + if (revised_string) + revised_string->append( output ); } } found_gestures = TRUE; - break; } - gesture = NULL; } } - - if( !gesture ) + + if(!gesture) { + // This token doesn't match a gesture. Pass it through to the output. if( !first_token ) { - revised_string->append( " " ); + if (revised_string) + revised_string->append( " " ); } - revised_string->append( cur_token ); + if (revised_string) + revised_string->append( cur_token ); } first_token = FALSE; + gesture = NULL; } return found_gestures; } @@ -530,7 +552,10 @@ BOOL LLGestureManager::triggerAndReviseString(const std::string &utf8str, std::s BOOL LLGestureManager::triggerGesture(KEY key, MASK mask) { + std::vector <LLMultiGesture *> matching; item_map_t::iterator it; + + // collect matching gestures for (it = mActive.begin(); it != mActive.end(); ++it) { LLMultiGesture* gesture = (*it).second; @@ -541,10 +566,20 @@ BOOL LLGestureManager::triggerGesture(KEY key, MASK mask) if (gesture->mKey == key && gesture->mMask == mask) { - playGesture(gesture); - return TRUE; + matching.push_back(gesture); } } + + // choose one and play it + if (matching.size() > 0) + { + U32 random = ll_rand(matching.size()); + + LLMultiGesture* gesture = matching[random]; + + playGesture(gesture); + return TRUE; + } return FALSE; } diff --git a/indra/newview/llgesturemgr.h b/indra/newview/llgesturemgr.h index b79dc0687f..c68ca4f265 100644 --- a/indra/newview/llgesturemgr.h +++ b/indra/newview/llgesturemgr.h @@ -85,7 +85,7 @@ public: BOOL triggerGesture(KEY key, MASK mask); // Trigger all gestures referenced as substrings in this string - BOOL triggerAndReviseString(const std::string &str, std::string *revised_string); + BOOL triggerAndReviseString(const std::string &str, std::string *revised_string = NULL); // Does some gesture have this key bound? BOOL isKeyBound(KEY key, MASK mask); diff --git a/indra/newview/llhudobject.cpp b/indra/newview/llhudobject.cpp index 1c8c45a078..0bcbbbb140 100644 --- a/indra/newview/llhudobject.cpp +++ b/indra/newview/llhudobject.cpp @@ -22,6 +22,7 @@ #include "llhudeffectlookat.h" //Ventrella +#include "llvoicevisualizer.h" #include "llanimalcontrols.h" #include "lllocalanimationobject.h" #include "llcape.h" @@ -205,6 +206,9 @@ LLHUDObject *LLHUDObject::addHUDObject(const U8 type) case LL_HUD_EFFECT_LOOKAT: hud_objectp = new LLHUDEffectLookAt(type); break; + case LL_HUD_EFFECT_VOICE_VISUALIZER: + hud_objectp = new LLVoiceVisualizer(type); + break; case LL_HUD_EFFECT_POINTAT: hud_objectp = new LLHUDEffectPointAt(type); break; diff --git a/indra/newview/llhudobject.h b/indra/newview/llhudobject.h index 9bd14796d5..3b5589c05b 100644 --- a/indra/newview/llhudobject.h +++ b/indra/newview/llhudobject.h @@ -68,7 +68,8 @@ public: LL_HUD_EFFECT_SPIRAL, LL_HUD_EFFECT_EDIT, LL_HUD_EFFECT_LOOKAT, - LL_HUD_EFFECT_POINTAT + LL_HUD_EFFECT_POINTAT, + LL_HUD_EFFECT_VOICE_VISUALIZER // Ventrella }; protected: static void sortObjects(); diff --git a/indra/newview/llhudtext.cpp b/indra/newview/llhudtext.cpp index aba3401d3c..70065c48d6 100644 --- a/indra/newview/llhudtext.cpp +++ b/indra/newview/llhudtext.cpp @@ -839,7 +839,8 @@ void LLHUDText::updateAll() } if (src_textp->mSoftScreenRect.rectInRect(&dst_textp->mSoftScreenRect)) { - LLRectf intersect_rect = src_textp->mSoftScreenRect & dst_textp->mSoftScreenRect; + LLRectf intersect_rect = src_textp->mSoftScreenRect; + intersect_rect.intersectWith(dst_textp->mSoftScreenRect); intersect_rect.stretch(-BUFFER_SIZE * 0.5f); F32 src_center_x = src_textp->mSoftScreenRect.getCenterX(); diff --git a/indra/newview/llimpanel.cpp b/indra/newview/llimpanel.cpp index 015f90a180..3a873bc1a8 100644 --- a/indra/newview/llimpanel.cpp +++ b/indra/newview/llimpanel.cpp @@ -13,7 +13,6 @@ #include "indra_constants.h" #include "llfocusmgr.h" #include "llfontgl.h" -#include "llhttpclient.h" #include "llrect.h" #include "llerror.h" #include "llstring.h" @@ -23,12 +22,14 @@ #include "llagent.h" #include "llbutton.h" #include "llcallingcard.h" +#include "llchat.h" #include "llconsole.h" #include "llfloater.h" #include "llinventory.h" #include "llinventorymodel.h" #include "llinventoryview.h" #include "llfloateravatarinfo.h" +#include "llfloaterchat.h" #include "llkeyboard.h" #include "lllineeditor.h" #include "llresmgr.h" @@ -41,8 +42,13 @@ #include "llvieweruictrlfactory.h" #include "lllogchat.h" #include "llfloaterhtml.h" -#include "llviewerregion.h" #include "llweb.h" +#include "llhttpclient.h" +#include "llfloateractivespeakers.h" // LLSpeakerMgr +#include "llfloatergroupinfo.h" +#include "llsdutil.h" +#include "llnotify.h" +#include "llmutelist.h" // // Constants @@ -59,6 +65,10 @@ static LLString sTitleString = "Instant Message with [NAME]"; static LLString sTypingStartString = "[NAME]: ..."; static LLString sSessionStartString = "Starting session with [NAME] please wait."; +LLVoiceChannel::voice_channel_map_t LLVoiceChannel::sVoiceChannelMap; +LLVoiceChannel::voice_channel_map_uri_t LLVoiceChannel::sVoiceChannelURIMap; +LLVoiceChannel* LLVoiceChannel::sCurrentVoiceChannel = NULL; + void session_starter_helper(const LLUUID& temp_session_id, const LLUUID& other_participant_id, EInstantMessage im_type) @@ -141,45 +151,669 @@ bool send_start_session_messages(const LLUUID& temp_session_id, return false; } -// Member Functions +class LLVoiceCallCapResponder : public LLHTTPClient::Responder +{ +public: + LLVoiceCallCapResponder(const LLUUID& session_id) : mSessionID(session_id) {}; + + virtual void error(U32 status, const std::string& reason); // called with bad status codes + virtual void result(const LLSD& content); + +private: + LLUUID mSessionID; +}; + + +void LLVoiceCallCapResponder::error(U32 status, const std::string& reason) +{ + llwarns << "LLVoiceCallCapResponder::error(" + << status << ": " << reason << ")" + << llendl; + LLVoiceChannel* channelp = LLVoiceChannel::getChannelByID(mSessionID); + if (channelp) + { + channelp->deactivate(); + } +} + +void LLVoiceCallCapResponder::result(const LLSD& content) +{ + LLVoiceChannel* channelp = LLVoiceChannel::getChannelByID(mSessionID); + if (channelp) + { + //*TODO: DEBUG SPAM + LLSD::map_const_iterator iter; + for(iter = content.beginMap(); iter != content.endMap(); ++iter) + { + llinfos << "LLVoiceCallCapResponder::result got " + << iter->first << llendl; + } + + channelp->setChannelInfo( + content["voice_credentials"]["channel_uri"].asString(), + content["voice_credentials"]["channel_credentials"].asString()); + } +} + +// +// LLVoiceChannel +// +LLVoiceChannel::LLVoiceChannel(const LLUUID& session_id, const LLString& session_name) : + mSessionID(session_id), + mState(STATE_NO_CHANNEL_INFO), + mSessionName(session_name), + mIgnoreNextSessionLeave(FALSE) +{ + mNotifyArgs["[VOICE_CHANNEL_NAME]"] = mSessionName; + + if (!sVoiceChannelMap.insert(std::make_pair(session_id, this)).second) + { + // a voice channel already exists for this session id, so this instance will be orphaned + // the end result should simply be the failure to make voice calls + llwarns << "Duplicate voice channels registered for session_id " << session_id << llendl; + } + + LLVoiceClient::getInstance()->addStatusObserver(this); +} + +LLVoiceChannel::~LLVoiceChannel() +{ + // CANNOT do this here, since it will crash on quit in the LLVoiceChannelProximal singleton destructor. + // Do it in all other subclass destructors instead. + // deactivate(); + + // Don't use LLVoiceClient::getInstance() here -- this can get called during atexit() time and that singleton MAY have already been destroyed. + if(gVoiceClient) + { + gVoiceClient->removeStatusObserver(this); + } + + sVoiceChannelMap.erase(mSessionID); + sVoiceChannelURIMap.erase(mURI); +} + +void LLVoiceChannel::setChannelInfo( + const LLString& uri, + const LLString& credentials) +{ + setURI(uri); + + mCredentials = credentials; + + if (mState == STATE_NO_CHANNEL_INFO) + { + if(!mURI.empty() && !mCredentials.empty()) + { + setState(STATE_READY); + + // if we are supposed to be active, reconnect + // this will happen on initial connect, as we request credentials on first use + if (sCurrentVoiceChannel == this) + { + // just in case we got new channel info while active + // should move over to new channel + activate(); + } + } + else + { + //*TODO: notify user + llwarns << "Received invalid credentials for channel " << mSessionName << llendl; + deactivate(); + } + } +} + +void LLVoiceChannel::onChange(EStatusType type, const std::string &channelURI, bool proximal) +{ + if (channelURI != mURI) + { + return; + } + + if (type < BEGIN_ERROR_STATUS) + { + handleStatusChange(type); + } + else + { + handleError(type); + } +} + +void LLVoiceChannel::handleStatusChange(EStatusType type) +{ + // status updates + switch(type) + { + case STATUS_LOGIN_RETRY: + mLoginNotificationHandle = LLNotifyBox::showXml("VoiceLoginRetry")->getHandle(); + break; + case STATUS_LOGGED_IN: + if (!mLoginNotificationHandle.isDead()) + { + LLNotifyBox* notifyp = (LLNotifyBox*)LLPanel::getPanelByHandle(mLoginNotificationHandle); + if (notifyp) + { + notifyp->close(); + } + mLoginNotificationHandle.markDead(); + } + break; + case STATUS_LEFT_CHANNEL: + if (callStarted() && !mIgnoreNextSessionLeave) + { + // if forceably removed from channel + // update the UI and revert to default channel + LLNotifyBox::showXml("VoiceChannelDisconnected", mNotifyArgs); + deactivate(); + } + mIgnoreNextSessionLeave = FALSE; + break; + case STATUS_JOINING: + if (callStarted()) + { + setState(STATE_RINGING); + } + break; + case STATUS_JOINED: + if (callStarted()) + { + setState(STATE_CONNECTED); + } + default: + break; + } +} + +// default behavior is to just deactivate channel +// derived classes provide specific error messages +void LLVoiceChannel::handleError(EStatusType type) +{ + deactivate(); + setState(STATE_ERROR); +} + +BOOL LLVoiceChannel::isActive() +{ + // only considered active when currently bound channel matches what our channel + return callStarted() && LLVoiceClient::getInstance()->getCurrentChannel() == mURI; +} + +BOOL LLVoiceChannel::callStarted() +{ + return mState >= STATE_CALL_STARTED; +} + +void LLVoiceChannel::deactivate() +{ + if (mState >= STATE_RINGING) + { + // ignore session leave event + mIgnoreNextSessionLeave = TRUE; + } + + if (callStarted()) + { + setState(STATE_HUNG_UP); + } + if (sCurrentVoiceChannel == this) + { + // default channel is proximal channel + sCurrentVoiceChannel = LLVoiceChannelProximal::getInstance(); + sCurrentVoiceChannel->activate(); + } +} + +void LLVoiceChannel::activate() +{ + if (callStarted()) + { + return; + } + + // deactivate old channel and mark ourselves as the active one + if (sCurrentVoiceChannel != this) + { + if (sCurrentVoiceChannel) + { + sCurrentVoiceChannel->deactivate(); + } + sCurrentVoiceChannel = this; + } + + if (mState == STATE_NO_CHANNEL_INFO) + { + // responsible for setting status to active + getChannelInfo(); + } + else + { + setState(STATE_CALL_STARTED); + } +} + +void LLVoiceChannel::getChannelInfo() +{ + // pretend we have everything we need + if (sCurrentVoiceChannel == this) + { + setState(STATE_CALL_STARTED); + } +} + +//static +LLVoiceChannel* LLVoiceChannel::getChannelByID(const LLUUID& session_id) +{ + voice_channel_map_t::iterator found_it = sVoiceChannelMap.find(session_id); + if (found_it == sVoiceChannelMap.end()) + { + return NULL; + } + else + { + return found_it->second; + } +} + +//static +LLVoiceChannel* LLVoiceChannel::getChannelByURI(LLString uri) +{ + voice_channel_map_uri_t::iterator found_it = sVoiceChannelURIMap.find(uri); + if (found_it == sVoiceChannelURIMap.end()) + { + return NULL; + } + else + { + return found_it->second; + } +} + + +void LLVoiceChannel::updateSessionID(const LLUUID& new_session_id) +{ + sVoiceChannelMap.erase(sVoiceChannelMap.find(mSessionID)); + mSessionID = new_session_id; + sVoiceChannelMap.insert(std::make_pair(mSessionID, this)); +} + +void LLVoiceChannel::setURI(LLString uri) +{ + sVoiceChannelURIMap.erase(mURI); + mURI = uri; + sVoiceChannelURIMap.insert(std::make_pair(mURI, this)); +} + +void LLVoiceChannel::setState(EState state) +{ + switch(state) + { + case STATE_RINGING: + gIMMgr->addSystemMessage(mSessionID, "ringing", mNotifyArgs); + break; + case STATE_CONNECTED: + gIMMgr->addSystemMessage(mSessionID, "connected", mNotifyArgs); + break; + case STATE_HUNG_UP: + gIMMgr->addSystemMessage(mSessionID, "hang_up", mNotifyArgs); + break; + default: + break; + } + + mState = state; +} + + +//static +void LLVoiceChannel::initClass() +{ + sCurrentVoiceChannel = LLVoiceChannelProximal::getInstance(); +} + +// +// LLVoiceChannelGroup +// + +LLVoiceChannelGroup::LLVoiceChannelGroup(const LLUUID& session_id, const LLString& session_name) : + LLVoiceChannel(session_id, session_name) +{ +} + +LLVoiceChannelGroup::~LLVoiceChannelGroup() +{ + deactivate(); +} + +void LLVoiceChannelGroup::deactivate() +{ + if (callStarted()) + { + LLVoiceClient::getInstance()->leaveNonSpatialChannel(); + } + LLVoiceChannel::deactivate(); +} + +void LLVoiceChannelGroup::activate() +{ + if (callStarted()) return; + + LLVoiceChannel::activate(); + + if (callStarted()) + { + // we have the channel info, just need to use it now + LLVoiceClient::getInstance()->setNonSpatialChannel( + mURI, + mCredentials); + } +} + +void LLVoiceChannelGroup::getChannelInfo() +{ + LLViewerRegion* region = gAgent.getRegion(); + if (region) + { + std::string url = region->getCapability("ChatSessionRequest"); + LLSD data; + data["method"] = "call"; + data["session-id"] = mSessionID; + LLHTTPClient::post(url, + data, + new LLVoiceCallCapResponder(mSessionID)); + } +} + +void LLVoiceChannelGroup::handleError(EStatusType status) +{ + std::string notify; + switch(status) + { + case ERROR_CHANNEL_LOCKED: + case ERROR_CHANNEL_FULL: + notify = "VoiceChannelFull"; + break; + case ERROR_UNKNOWN: + break; + default: + break; + } + + // notification + if (!notify.empty()) + { + LLNotifyBox::showXml(notify, mNotifyArgs); + // echo to im window + gIMMgr->addMessage(mSessionID, LLUUID::null, SYSTEM_FROM, LLNotifyBox::getTemplateMessage(notify, mNotifyArgs).c_str()); + } + + LLVoiceChannel::handleError(status); +} + +// +// LLVoiceChannelProximal +// +LLVoiceChannelProximal::LLVoiceChannelProximal() : + LLVoiceChannel(LLUUID::null, LLString::null) +{ + activate(); +} + +LLVoiceChannelProximal::~LLVoiceChannelProximal() +{ + // DO NOT call deactivate() here, since this will only happen at atexit() time. +} + +BOOL LLVoiceChannelProximal::isActive() +{ + return callStarted() && LLVoiceClient::getInstance()->inProximalChannel(); +} + +void LLVoiceChannelProximal::activate() +{ + if (callStarted()) return; + + LLVoiceChannel::activate(); + + if (callStarted()) + { + // this implicitly puts you back in the spatial channel + LLVoiceClient::getInstance()->leaveNonSpatialChannel(); + } +} + +void LLVoiceChannelProximal::onChange(EStatusType type, const std::string &channelURI, bool proximal) +{ + if (!proximal) + { + return; + } + + if (type < BEGIN_ERROR_STATUS) + { + handleStatusChange(type); + } + else + { + handleError(type); + } +} + +void LLVoiceChannelProximal::handleStatusChange(EStatusType status) +{ + // status updates + switch(status) + { + case STATUS_LEFT_CHANNEL: + // do not notify user when leaving proximal channel + return; + default: + break; + } + LLVoiceChannel::handleStatusChange(status); +} + + +void LLVoiceChannelProximal::handleError(EStatusType status) +{ + std::string notify; + switch(status) + { + case ERROR_CHANNEL_LOCKED: + case ERROR_CHANNEL_FULL: + notify = "ProximalVoiceChannelFull"; + break; + default: + break; + } + + // notification + if (!notify.empty()) + { + LLNotifyBox::showXml(notify, mNotifyArgs); + } + + LLVoiceChannel::handleError(status); +} + +void LLVoiceChannelProximal::deactivate() +{ + if (callStarted()) + { + setState(STATE_HUNG_UP); + } +} + // +// LLVoiceChannelP2P +// +LLVoiceChannelP2P::LLVoiceChannelP2P(const LLUUID& session_id, const LLString& session_name, const LLUUID& other_user_id) : + LLVoiceChannelGroup(session_id, session_name), + mOtherUserID(other_user_id) +{ + // make sure URI reflects encoded version of other user's agent id + setURI(LLVoiceClient::getInstance()->sipURIFromID(other_user_id)); +} + +LLVoiceChannelP2P::~LLVoiceChannelP2P() +{ + deactivate(); +} + +void LLVoiceChannelP2P::handleStatusChange(EStatusType type) +{ + // status updates + switch(type) + { + case STATUS_LEFT_CHANNEL: + if (callStarted() && !mIgnoreNextSessionLeave) + { + if (mState == STATE_RINGING) + { + // other user declined call + LLNotifyBox::showXml("P2PCallDeclined", mNotifyArgs); + } + else + { + // other user hung up + LLNotifyBox::showXml("VoiceChannelDisconnectedP2P", mNotifyArgs); + } + deactivate(); + } + mIgnoreNextSessionLeave = FALSE; + return; + default: + break; + } -LLFloaterIMPanel::LLFloaterIMPanel(const std::string& name, - const LLRect& rect, - const std::string& session_label, - const LLUUID& session_id, - const LLUUID& other_participant_id, - EInstantMessage dialog) : + LLVoiceChannelGroup::handleStatusChange(type); +} + +void LLVoiceChannelP2P::handleError(EStatusType type) +{ + switch(type) + { + case ERROR_NOT_AVAILABLE: + LLNotifyBox::showXml("P2PCallNoAnswer", mNotifyArgs); + break; + default: + break; + } + + LLVoiceChannelGroup::handleError(type); +} + +void LLVoiceChannelP2P::activate() +{ + if (callStarted()) return; + + LLVoiceChannel::activate(); + + if (callStarted()) + { + // no session handle yet, we're starting the call + if (mSessionHandle.empty()) + { + LLVoiceClient::getInstance()->callUser(mOtherUserID); + } + // otherwise answering the call + else + { + LLVoiceClient::getInstance()->answerInvite(mSessionHandle, mOtherUserID); + // using the session handle invalidates it. Clear it out here so we can't reuse it by accident. + mSessionHandle.clear(); + } + } +} + +void LLVoiceChannelP2P::getChannelInfo() +{ + // pretend we have everything we need, since P2P doesn't use channel info + if (sCurrentVoiceChannel == this) + { + setState(STATE_CALL_STARTED); + } +} + +// receiving session from other user who initiated call +void LLVoiceChannelP2P::setSessionHandle(const LLString& handle) +{ + BOOL needs_activate = FALSE; + if (callStarted()) + { + // defer to lower agent id when already active + if (mOtherUserID < gAgent.getID()) + { + // pretend we haven't started the call yet, so we can connect to this session instead + deactivate(); + needs_activate = TRUE; + } + else + { + // we are active and have priority, invite the other user again + // under the assumption they will join this new session + mSessionHandle.clear(); + LLVoiceClient::getInstance()->callUser(mOtherUserID); + return; + } + } + + mSessionHandle = handle; + // The URI of a p2p session should always be the other end's SIP URI. + setURI(LLVoiceClient::getInstance()->sipURIFromID(mOtherUserID)); + + if (needs_activate) + { + activate(); + } +} + +// +// LLFloaterIMPanel +// +LLFloaterIMPanel::LLFloaterIMPanel( + const std::string& name, + const LLRect& rect, + const std::string& session_label, + const LLUUID& session_id, + const LLUUID& other_participant_id, + EInstantMessage dialog) : LLFloater(name, rect, session_label), mInputEditor(NULL), mHistoryEditor(NULL), mSessionUUID(session_id), - mSessionInitRequested(FALSE), + mVoiceChannel(NULL), mSessionInitialized(FALSE), + mOtherParticipantUUID(other_participant_id), mDialog(dialog), mTyping(FALSE), mOtherTyping(FALSE), mTypingLineStartIndex(0), mSentTypingState(TRUE), + mShowSpeakersOnConnect(TRUE), + mAutoConnect(FALSE), + mSpeakerPanel(NULL), mFirstKeystrokeTimer(), mLastKeystrokeTimer() { init(session_label); } -LLFloaterIMPanel::LLFloaterIMPanel(const std::string& name, - const LLRect& rect, - const std::string& session_label, - const LLUUID& session_id, - const LLUUID& other_participant_id, - const LLDynamicArray<LLUUID>& ids, - EInstantMessage dialog) : +LLFloaterIMPanel::LLFloaterIMPanel( + const std::string& name, + const LLRect& rect, + const std::string& session_label, + const LLUUID& session_id, + const LLUUID& other_participant_id, + const LLDynamicArray<LLUUID>& ids, + EInstantMessage dialog) : LLFloater(name, rect, session_label), mInputEditor(NULL), mHistoryEditor(NULL), mSessionUUID(session_id), - mSessionInitRequested(FALSE), + mVoiceChannel(NULL), mSessionInitialized(FALSE), mOtherParticipantUUID(other_participant_id), mDialog(dialog), @@ -187,6 +821,10 @@ LLFloaterIMPanel::LLFloaterIMPanel(const std::string& name, mOtherTyping(FALSE), mTypingLineStartIndex(0), mSentTypingState(TRUE), + mShowSpeakersOnConnect(TRUE), + mAutoConnect(FALSE), + mSpeakers(NULL), + mSpeakerPanel(NULL), mFirstKeystrokeTimer(), mLastKeystrokeTimer() { @@ -197,11 +835,53 @@ LLFloaterIMPanel::LLFloaterIMPanel(const std::string& name, void LLFloaterIMPanel::init(const LLString& session_label) { + LLString xml_filename; + switch(mDialog) + { + case IM_SESSION_GROUP_START: + mFactoryMap["active_speakers_panel"] = LLCallbackMap(createSpeakersPanel, this); + xml_filename = "floater_instant_message_group.xml"; + mVoiceChannel = new LLVoiceChannelGroup(mSessionUUID, session_label); + break; + case IM_SESSION_INVITE: + mFactoryMap["active_speakers_panel"] = LLCallbackMap(createSpeakersPanel, this); + if (gAgent.isInGroup(mSessionUUID)) + { + xml_filename = "floater_instant_message_group.xml"; + } + else // must be invite to ad hoc IM + { + xml_filename = "floater_instant_message_ad_hoc.xml"; + } + mVoiceChannel = new LLVoiceChannelGroup(mSessionUUID, session_label); + break; + case IM_SESSION_P2P_INVITE: + xml_filename = "floater_instant_message.xml"; + mVoiceChannel = new LLVoiceChannelP2P(mSessionUUID, session_label, mOtherParticipantUUID); + break; + case IM_SESSION_CONFERENCE_START: + mFactoryMap["active_speakers_panel"] = LLCallbackMap(createSpeakersPanel, this); + xml_filename = "floater_instant_message_ad_hoc.xml"; + mVoiceChannel = new LLVoiceChannelGroup(mSessionUUID, session_label); + break; + // just received text from another user + case IM_NOTHING_SPECIAL: + xml_filename = "floater_instant_message.xml"; + mVoiceChannel = new LLVoiceChannelP2P(mSessionUUID, session_label, mOtherParticipantUUID); + break; + default: + llwarns << "Unknown session type" << llendl; + xml_filename = "floater_instant_message.xml"; + break; + } + + mSpeakers = new LLIMSpeakerMgr(mVoiceChannel); + gUICtrlFactory->buildFloater(this, - "floater_instant_message.xml", - NULL, - FALSE); - + xml_filename, + &getFactoryMap(), + FALSE); + setLabel(session_label); setTitle(session_label); mInputEditor->setMaxTextLength(1023); @@ -238,18 +918,30 @@ void LLFloaterIMPanel::init(const LLString& session_label) addHistoryLine( session_start, - LLColor4::grey, + gSavedSettings.getColor4("SystemChatColor"), false); } } } +LLFloaterIMPanel::~LLFloaterIMPanel() +{ + delete mSpeakers; + mSpeakers = NULL; + + //kicks you out of the voice channel if it is currently active + + // HAVE to do this here -- if it happens in the LLVoiceChannel destructor it will call the wrong version (since the object's partially deconstructed at that point). + mVoiceChannel->deactivate(); + + delete mVoiceChannel; + mVoiceChannel = NULL; +} + BOOL LLFloaterIMPanel::postBuild() { requires("chat_editor", WIDGET_TYPE_LINE_EDITOR); - requires("profile_btn", WIDGET_TYPE_BUTTON); - requires("close_btn", WIDGET_TYPE_BUTTON); requires("im_history", WIDGET_TYPE_TEXT_EDITOR); requires("live_help_dialog", WIDGET_TYPE_TEXT_BOX); requires("title_string", WIDGET_TYPE_TEXT_BOX); @@ -262,22 +954,28 @@ BOOL LLFloaterIMPanel::postBuild() mInputEditor->setFocusReceivedCallback( onInputEditorFocusReceived ); mInputEditor->setFocusLostCallback( onInputEditorFocusLost ); mInputEditor->setKeystrokeCallback( onInputEditorKeystroke ); + mInputEditor->setCommitCallback( onCommitChat ); mInputEditor->setCallbackUserData(this); mInputEditor->setCommitOnFocusLost( FALSE ); mInputEditor->setRevertOnEsc( FALSE ); - LLButton* profile_btn = LLUICtrlFactory::getButtonByName(this, "profile_btn"); - profile_btn->setClickedCallback(&LLFloaterIMPanel::onClickProfile, this); + childSetAction("profile_callee_btn", onClickProfile, this); + childSetAction("group_info_btn", onClickGroupInfo, this); - LLButton* close_btn = LLUICtrlFactory::getButtonByName(this, "close_btn"); - close_btn->setClickedCallback(&LLFloaterIMPanel::onClickClose, this); + childSetAction("start_call_btn", onClickStartCall, this); + childSetAction("end_call_btn", onClickEndCall, this); + childSetAction("send_btn", onClickSend, this); + childSetAction("toggle_active_speakers_btn", onClickToggleActiveSpeakers, this); + + //LLButton* close_btn = LLUICtrlFactory::getButtonByName(this, "close_btn"); + //close_btn->setClickedCallback(&LLFloaterIMPanel::onClickClose, this); mHistoryEditor = LLViewerUICtrlFactory::getViewerTextEditorByName(this, "im_history"); mHistoryEditor->setParseHTML(TRUE); - if (IM_SESSION_GROUP_START == mDialog) + if ( IM_SESSION_GROUP_START == mDialog ) { - profile_btn->setEnabled(FALSE); + childSetEnabled("profile_btn", FALSE); } LLTextBox* title = LLUICtrlFactory::getTextBoxByName(this, "title_string"); sTitleString = title->getText(); @@ -290,16 +988,91 @@ BOOL LLFloaterIMPanel::postBuild() this, "session_start_string"); sSessionStartString = session_start->getText(); + if (mSpeakerPanel) + { + mSpeakerPanel->refreshSpeakers(); + } + if (mDialog == IM_NOTHING_SPECIAL) + { + childSetCommitCallback("mute_btn", onClickMuteVoice, this); + childSetCommitCallback("speaker_volume", onVolumeChange, this); + } + + setDefaultBtn("send_btn"); return TRUE; } return FALSE; } +void* LLFloaterIMPanel::createSpeakersPanel(void* data) +{ + LLFloaterIMPanel* floaterp = (LLFloaterIMPanel*)data; + floaterp->mSpeakerPanel = new LLPanelActiveSpeakers(floaterp->mSpeakers, TRUE); + return floaterp->mSpeakerPanel; +} + +//static +void LLFloaterIMPanel::onClickMuteVoice(LLUICtrl* source, void* user_data) +{ + LLFloaterIMPanel* floaterp = (LLFloaterIMPanel*)user_data; + if (floaterp) + { + BOOL is_muted = gMuteListp->isMuted(floaterp->mOtherParticipantUUID, LLMute::flagVoiceChat); + + LLMute mute(floaterp->mOtherParticipantUUID, floaterp->getTitle(), LLMute::AGENT); + if (!is_muted) + { + gMuteListp->add(mute, LLMute::flagVoiceChat); + } + else + { + gMuteListp->remove(mute, LLMute::flagVoiceChat); + } + } +} + +//static +void LLFloaterIMPanel::onVolumeChange(LLUICtrl* source, void* user_data) +{ + LLFloaterIMPanel* floaterp = (LLFloaterIMPanel*)user_data; + if (floaterp) + { + gVoiceClient->setUserVolume(floaterp->mOtherParticipantUUID, (F32)source->getValue().asReal()); + } +} + + // virtual void LLFloaterIMPanel::draw() -{ +{ + LLViewerRegion* region = gAgent.getRegion(); + + BOOL enable_connect = (region && region->getCapability("ChatSessionRequest") != "") + && mSessionInitialized + && LLVoiceClient::voiceEnabled(); + + // hide/show start call and end call buttons + childSetVisible("end_call_btn", mVoiceChannel->getState() >= LLVoiceChannel::STATE_CALL_STARTED); + childSetVisible("start_call_btn", mVoiceChannel->getState() < LLVoiceChannel::STATE_CALL_STARTED); + childSetEnabled("start_call_btn", enable_connect); + childSetEnabled("send_btn", !childGetValue("chat_editor").asString().empty()); + + if (mAutoConnect && enable_connect) + { + onClickStartCall(this); + mAutoConnect = FALSE; + } + + // show speakers window when voice first connects + if (mShowSpeakersOnConnect && mVoiceChannel->isActive()) + { + childSetVisible("active_speakers_panel", TRUE); + mShowSpeakersOnConnect = FALSE; + } + childSetValue("toggle_active_speakers_btn", childIsVisible("active_speakers_panel")); + if (mTyping) { // Time out if user hasn't typed for a while. @@ -318,6 +1091,19 @@ void LLFloaterIMPanel::draw() } } + if (mSpeakerPanel) + { + mSpeakerPanel->refreshSpeakers(); + } + else + { + // refresh volume and mute checkbox + childSetEnabled("speaker_volume", mVoiceChannel->isActive()); + childSetValue("speaker_volume", gVoiceClient->getUserVolume(mOtherParticipantUUID)); + + childSetValue("mute_btn", gMuteListp->isMuted(mOtherParticipantUUID, LLMute::flagVoiceChat)); + childSetEnabled("mute_btn", mVoiceChannel->isActive()); + } LLFloater::draw(); } @@ -342,16 +1128,22 @@ private: BOOL LLFloaterIMPanel::inviteToSession(const LLDynamicArray<LLUUID>& ids) { + LLViewerRegion* region = gAgent.getRegion(); + if (!region) + { + return FALSE; + } + S32 count = ids.count(); - if( isAddAllowed() && (count > 0) ) + if( isInviteAllowed() && (count > 0) ) { - llinfos << "LLFloaterIMPanel::inviteToSession() - adding participants" << llendl; + llinfos << "LLFloaterIMPanel::inviteToSession() - inviting participants" << llendl; - std::string url = - gAgent.getRegion()->getCapability("ChatSessionRequest"); + std::string url = region->getCapability("ChatSessionRequest"); LLSD data; + data["params"] = LLSD::emptyArray(); for (int i = 0; i < count; i++) { @@ -378,6 +1170,13 @@ BOOL LLFloaterIMPanel::inviteToSession(const LLDynamicArray<LLUUID>& ids) return TRUE; } +void LLFloaterIMPanel::addHistoryLine(const LLUUID& source, const std::string &utf8msg, const LLColor4& color, bool log_to_file) +{ + addHistoryLine(utf8msg, color, log_to_file); + mSpeakers->speakerChatted(source); + mSpeakers->setSpeakerTyping(source, FALSE); +} + void LLFloaterIMPanel::addHistoryLine(const std::string &utf8msg, const LLColor4& color, bool log_to_file) { LLMultiFloater* hostp = getHost(); @@ -391,7 +1190,7 @@ void LLFloaterIMPanel::addHistoryLine(const std::string &utf8msg, const LLColor4 // Now we're adding the actual line of text, so erase the // "Foo is typing..." text segment, and the optional timestamp // if it was present. JC - removeTypingIndicator(); + removeTypingIndicator(NULL); // Actually add the line LLString timestring; @@ -470,7 +1269,7 @@ BOOL LLFloaterIMPanel::handleKeyHere( KEY key, MASK mask, BOOL called_from_paren // but not shift-return or control-return if ( !gSavedSettings.getBOOL("PinTalkViewOpen") && !(mask & MASK_CONTROL) && !(mask & MASK_SHIFT) ) { - gIMView->toggle(NULL); + gIMMgr->toggle(NULL); } } else if ( KEY_ESCAPE == key ) @@ -481,7 +1280,7 @@ BOOL LLFloaterIMPanel::handleKeyHere( KEY key, MASK mask, BOOL called_from_paren // Close talk panel with escape if( !gSavedSettings.getBOOL("PinTalkViewOpen") ) { - gIMView->toggle(NULL); + gIMMgr->toggle(NULL); } } } @@ -522,7 +1321,7 @@ BOOL LLFloaterIMPanel::handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop, BOOL LLFloaterIMPanel::dropCallingCard(LLInventoryItem* item, BOOL drop) { - BOOL rv = isAddAllowed(); + BOOL rv = isInviteAllowed(); if(rv && item && item->getCreatorUUID().notNull()) { if(drop) @@ -542,7 +1341,7 @@ BOOL LLFloaterIMPanel::dropCallingCard(LLInventoryItem* item, BOOL drop) BOOL LLFloaterIMPanel::dropCategory(LLInventoryCategory* category, BOOL drop) { - BOOL rv = isAddAllowed(); + BOOL rv = isInviteAllowed(); if(rv && category) { LLInventoryModel::cat_array_t cats; @@ -571,11 +1370,11 @@ BOOL LLFloaterIMPanel::dropCategory(LLInventoryCategory* category, BOOL drop) return rv; } -BOOL LLFloaterIMPanel::isAddAllowed() const +BOOL LLFloaterIMPanel::isInviteAllowed() const { - return ((IM_SESSION_CONFERENCE_START == mDialog) - || (IM_SESSION_INVITE) ); + return ( (IM_SESSION_CONFERENCE_START == mDialog) + || (IM_SESSION_INVITE == mDialog) ); } @@ -600,6 +1399,15 @@ void LLFloaterIMPanel::onClickProfile( void* userdata ) } // static +void LLFloaterIMPanel::onClickGroupInfo( void* userdata ) +{ + // Bring up the Profile window + LLFloaterIMPanel* self = (LLFloaterIMPanel*) userdata; + + LLFloaterGroupInfo::showFromUUID(self->mSessionUUID); +} + +// static void LLFloaterIMPanel::onClickClose( void* userdata ) { LLFloaterIMPanel* self = (LLFloaterIMPanel*) userdata; @@ -610,6 +1418,44 @@ void LLFloaterIMPanel::onClickClose( void* userdata ) } // static +void LLFloaterIMPanel::onClickStartCall(void* userdata) +{ + LLFloaterIMPanel* self = (LLFloaterIMPanel*) userdata; + + self->mVoiceChannel->activate(); +} + +// static +void LLFloaterIMPanel::onClickEndCall(void* userdata) +{ + LLFloaterIMPanel* self = (LLFloaterIMPanel*) userdata; + + self->getVoiceChannel()->deactivate(); +} + +// static +void LLFloaterIMPanel::onClickSend(void* userdata) +{ + LLFloaterIMPanel* self = (LLFloaterIMPanel*)userdata; + self->sendMsg(); +} + +// static +void LLFloaterIMPanel::onClickToggleActiveSpeakers(void* userdata) +{ + LLFloaterIMPanel* self = (LLFloaterIMPanel*)userdata; + + self->childSetVisible("active_speakers_panel", !self->childIsVisible("active_speakers_panel")); +} + +// static +void LLFloaterIMPanel::onCommitChat(LLUICtrl* caller, void* userdata) +{ + LLFloaterIMPanel* self= (LLFloaterIMPanel*) userdata; + self->sendMsg(); +} + +// static void LLFloaterIMPanel::onInputEditorFocusReceived( LLUICtrl* caller, void* userdata ) { LLFloaterIMPanel* self= (LLFloaterIMPanel*) userdata; @@ -660,7 +1506,7 @@ void LLFloaterIMPanel::onClose(bool app_quitting) mSessionUUID); gAgent.sendReliableMessage(); } - gIMView->removeSession(mSessionUUID); + gIMMgr->removeSession(mSessionUUID); destroy(); } @@ -748,10 +1594,9 @@ void LLFloaterIMPanel::sendMsg() } history_echo += utf8_text; - BOOL other_was_typing = mOtherTyping; - addHistoryLine(history_echo); + addHistoryLine(gAgent.getID(), history_echo); if (other_was_typing) { @@ -762,6 +1607,8 @@ void LLFloaterIMPanel::sendMsg() } else { + //queue up the message to send once the session is + //initialized mQueuedMsgsForInit.append(utf8_text); } @@ -775,15 +1622,31 @@ void LLFloaterIMPanel::sendMsg() mSentTypingState = TRUE; } +void LLFloaterIMPanel::updateSpeakersList(LLSD speaker_updates) +{ + mSpeakers->processSpeakerListUpdate(speaker_updates); +} + +void LLFloaterIMPanel::setSpeakersListFromMap(LLSD speaker_map) +{ + mSpeakers->processSpeakerMap(speaker_map); +} + +void LLFloaterIMPanel::setSpeakersList(LLSD speaker_list) +{ + mSpeakers->processSpeakerList(speaker_list); +} + void LLFloaterIMPanel::sessionInitReplyReceived(const LLUUID& session_id) { mSessionUUID = session_id; + mVoiceChannel->updateSessionID(session_id); mSessionInitialized = TRUE; //we assume the history editor hasn't moved at all since //we added the starting session message //so, we count how many characters to remove - S32 chars_to_remove = mHistoryEditor->getText().length() - + S32 chars_to_remove = mHistoryEditor->getText().length() - mSessionStartMsgPos; mHistoryEditor->removeTextFromEnd(chars_to_remove); @@ -793,13 +1656,18 @@ void LLFloaterIMPanel::sessionInitReplyReceived(const LLUUID& session_id) iter != mQueuedMsgsForInit.endArray(); ++iter) { - deliver_message(iter->asString(), - mSessionUUID, - mOtherParticipantUUID, - mDialog); + deliver_message( + iter->asString(), + mSessionUUID, + mOtherParticipantUUID, + mDialog); } } +void LLFloaterIMPanel::requestAutoConnect() +{ + mAutoConnect = TRUE; +} void LLFloaterIMPanel::setTyping(BOOL typing) { @@ -816,6 +1684,8 @@ void LLFloaterIMPanel::setTyping(BOOL typing) // Will send typing state after a short delay. mSentTypingState = FALSE; } + + mSpeakers->setSpeakerTyping(gAgent.getID(), TRUE); } else { @@ -825,6 +1695,7 @@ void LLFloaterIMPanel::setTyping(BOOL typing) sendTypingState(FALSE); mSentTypingState = TRUE; } + mSpeakers->setSpeakerTyping(gAgent.getID(), FALSE); } mTyping = typing; @@ -853,7 +1724,6 @@ void LLFloaterIMPanel::sendTypingState(BOOL typing) gAgent.sendReliableMessage(); } - void LLFloaterIMPanel::processIMTyping(const LLIMInfo* im_info, BOOL typing) { if (typing) @@ -864,7 +1734,7 @@ void LLFloaterIMPanel::processIMTyping(const LLIMInfo* im_info, BOOL typing) else { // other user stopped typing - removeTypingIndicator(); + removeTypingIndicator(im_info); } } @@ -877,14 +1747,17 @@ void LLFloaterIMPanel::addTypingIndicator(const std::string &name) mTypingLineStartIndex = mHistoryEditor->getText().length(); LLUIString typing_start = sTypingStartString; typing_start.setArg("[NAME]", name); - addHistoryLine(typing_start, LLColor4::grey, false); + addHistoryLine(typing_start, gSavedSettings.getColor4("SystemChatColor"), false); mOtherTypingName = name; mOtherTyping = TRUE; } + // MBW -- XXX -- merge from release broke this (argument to this function changed from an LLIMInfo to a name) + // Richard will fix. +// mSpeakers->setSpeakerTyping(im_info->mFromID, TRUE); } -void LLFloaterIMPanel::removeTypingIndicator() +void LLFloaterIMPanel::removeTypingIndicator(const LLIMInfo* im_info) { if (mOtherTyping) { @@ -893,6 +1766,10 @@ void LLFloaterIMPanel::removeTypingIndicator() S32 chars_to_remove = mHistoryEditor->getText().length() - mTypingLineStartIndex; mHistoryEditor->removeTextFromEnd(chars_to_remove); + if (im_info) + { + mSpeakers->setSpeakerTyping(im_info->mFromID, FALSE); + } } } @@ -905,4 +1782,3 @@ void LLFloaterIMPanel::chatFromLogFile(LLString line, void* userdata) self->mHistoryEditor->appendColoredText(line, false, true, LLColor4::grey); } - diff --git a/indra/newview/llimpanel.h b/indra/newview/llimpanel.h index cb8042e010..dbe09fb396 100644 --- a/indra/newview/llimpanel.h +++ b/indra/newview/llimpanel.h @@ -13,15 +13,122 @@ #include "lluuid.h" #include "lldarray.h" #include "llinstantmessage.h" +#include "llvoiceclient.h" class LLLineEditor; class LLViewerTextEditor; class LLInventoryItem; class LLInventoryCategory; +class LLIMSpeakerMgr; +class LLPanelActiveSpeakers; + +class LLVoiceChannel : public LLVoiceClientStatusObserver +{ +public: + typedef enum e_voice_channel_state + { + STATE_NO_CHANNEL_INFO, + STATE_ERROR, + STATE_HUNG_UP, + STATE_READY, + STATE_CALL_STARTED, + STATE_RINGING, + STATE_CONNECTED + } EState; + + LLVoiceChannel(const LLUUID& session_id, const LLString& session_name); + virtual ~LLVoiceChannel(); + + void setChannelInfo(const LLString& uri, const LLString& credentials); + /*virtual*/ void onChange(EStatusType status, const std::string &channelURI, bool proximal); + + virtual void handleStatusChange(EStatusType status); + virtual void handleError(EStatusType status); + virtual void deactivate(); + virtual void activate(); + virtual void getChannelInfo(); + virtual BOOL isActive(); + virtual BOOL callStarted(); + EState getState() { return mState; } + + void updateSessionID(const LLUUID& new_session_id); + + static LLVoiceChannel* getChannelByID(const LLUUID& session_id); + static LLVoiceChannel* getChannelByURI(LLString uri); + static LLVoiceChannel* getCurrentVoiceChannel() { return sCurrentVoiceChannel; } + static void initClass(); + +protected: + void setState(EState state); + void setURI(LLString uri); + + LLString mURI; + LLString mCredentials; + LLUUID mSessionID; + EState mState; + LLString mSessionName; + LLString::format_map_t mNotifyArgs; + BOOL mIgnoreNextSessionLeave; + LLViewHandle mLoginNotificationHandle; + + typedef std::map<LLUUID, LLVoiceChannel*> voice_channel_map_t; + static voice_channel_map_t sVoiceChannelMap; + + typedef std::map<LLString, LLVoiceChannel*> voice_channel_map_uri_t; + static voice_channel_map_uri_t sVoiceChannelURIMap; + + static LLVoiceChannel* sCurrentVoiceChannel; +}; + +class LLVoiceChannelGroup : public LLVoiceChannel +{ +public: + LLVoiceChannelGroup(const LLUUID& session_id, const LLString& session_name); + virtual ~LLVoiceChannelGroup(); + + /*virtual*/ void handleError(EStatusType status); + /*virtual*/ void activate(); + /*virtual*/ void deactivate(); + /*virtual*/ void getChannelInfo(); +}; + +class LLVoiceChannelProximal : public LLVoiceChannel, public LLSingleton<LLVoiceChannelProximal> +{ +public: + LLVoiceChannelProximal(); + virtual ~LLVoiceChannelProximal(); + + /*virtual*/ void onChange(EStatusType status, const std::string &channelURI, bool proximal); + /*virtual*/ void handleStatusChange(EStatusType status); + /*virtual*/ void handleError(EStatusType status); + /*virtual*/ BOOL isActive(); + /*virtual*/ void activate(); + /*virtual*/ void deactivate(); + +}; + +class LLVoiceChannelP2P : public LLVoiceChannelGroup +{ +public: + LLVoiceChannelP2P(const LLUUID& session_id, const LLString& session_name, const LLUUID& other_user_id); + virtual ~LLVoiceChannelP2P(); + + /*virtual*/ void handleStatusChange(EStatusType status); + /*virtual*/ void handleError(EStatusType status); + /*virtual*/ void activate(); + /*virtual*/ void getChannelInfo(); + + void setSessionHandle(const LLString& handle); + +private: + LLString mSessionHandle; + LLUUID mOtherUserID; +}; class LLFloaterIMPanel : public LLFloater { public: + // The session id is the id of the session this is for. The target // refers to the user (or group) that where this session serves as // the default. For example, if you open a session though a @@ -40,7 +147,7 @@ public: const LLUUID& target_id, const LLDynamicArray<LLUUID>& ids, EInstantMessage dialog); - + virtual ~LLFloaterIMPanel(); /*virtual*/ BOOL postBuild(); @@ -53,6 +160,10 @@ public: // Return TRUE if successful, otherwise FALSE. BOOL inviteToSession(const LLDynamicArray<LLUUID>& agent_ids); + void addHistoryLine(const LLUUID& source, + const std::string &utf8msg, + const LLColor4& color = LLColor4::white, + bool log_to_file = true); void addHistoryLine(const std::string &utf8msg, const LLColor4& color = LLColor4::white, bool log_to_file = true); @@ -71,15 +182,32 @@ public: static void onInputEditorFocusReceived( LLUICtrl* caller, void* userdata ); static void onInputEditorFocusLost(LLUICtrl* caller, void* userdata); static void onInputEditorKeystroke(LLLineEditor* caller, void* userdata); + static void onCommitChat(LLUICtrl* caller, void* userdata); static void onTabClick( void* userdata ); - static void onClickProfile( void* userdata ); // Profile button pressed + static void onClickProfile( void* userdata ); + static void onClickGroupInfo( void* userdata ); static void onClickClose( void* userdata ); + static void onClickStartCall( void* userdata ); + static void onClickEndCall( void* userdata ); + static void onClickSend( void* userdata ); + static void onClickToggleActiveSpeakers( void* userdata ); + static void* createSpeakersPanel(void* data); + + //callbacks for P2P muting and volume control + static void onClickMuteVoice(LLUICtrl* source, void* user_data); + static void onVolumeChange(LLUICtrl* source, void* user_data); const LLUUID& getSessionID() const { return mSessionUUID; } const LLUUID& getOtherParticipantID() const { return mOtherParticipantUUID; } + void updateSpeakersList(LLSD speaker_updates); + void setSpeakersListFromMap(LLSD speaker_list); + void setSpeakersList(LLSD speaker_list); + LLVoiceChannel* getVoiceChannel() { return mVoiceChannel; } EInstantMessage getDialogType() const { return mDialog; } + void requestAutoConnect(); + void sessionInitReplyReceived(const LLUUID& im_session_id); // Handle other participant in the session typing. @@ -98,7 +226,7 @@ private: BOOL dropCategory(LLInventoryCategory* category, BOOL drop); // test if local agent can add agents. - BOOL isAddAllowed() const; + BOOL isInviteAllowed() const; // Called whenever the user starts or stops typing. // Sends the typing state to the other user if necessary. @@ -108,7 +236,7 @@ private: void addTypingIndicator(const std::string &name); // Remove the "User is typing..." indicator. - void removeTypingIndicator(); + void removeTypingIndicator(const LLIMInfo* im_info); void sendTypingState(BOOL typing); @@ -125,7 +253,8 @@ private: // 911 ==> Gaurdian_Angel_Group_ID ^ gAgent.getID() LLUUID mSessionUUID; - BOOL mSessionInitRequested; + LLVoiceChannel* mVoiceChannel; + BOOL mSessionInitialized; LLSD mQueuedMsgsForInit; @@ -150,10 +279,17 @@ private: // Where does the "User is typing..." line start? S32 mTypingLineStartIndex; - //Where does the "Starting session..." line start? + // Where does the "Starting session..." line start? S32 mSessionStartMsgPos; - + BOOL mSentTypingState; + + BOOL mShowSpeakersOnConnect; + + BOOL mAutoConnect; + + LLIMSpeakerMgr* mSpeakers; + LLPanelActiveSpeakers* mSpeakerPanel; // Optimization: Don't send "User is typing..." until the // user has actually been typing for a little while. Prevents diff --git a/indra/newview/llimview.cpp b/indra/newview/llimview.cpp index 5a6cbb045d..d8643ab9c1 100644 --- a/indra/newview/llimview.cpp +++ b/indra/newview/llimview.cpp @@ -1,5 +1,5 @@ /** - * @file llimview.cpp + * @file LLIMMgr.cpp * @brief Container for Instant Messaging * * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. @@ -12,9 +12,9 @@ #include "llfontgl.h" #include "llrect.h" -#include "lldbstrings.h" #include "llerror.h" #include "llbutton.h" +#include "llhttpclient.h" #include "llsdutil.h" #include "llstring.h" #include "linked_lists.h" @@ -26,8 +26,8 @@ #include "llviewerwindow.h" #include "llresmgr.h" #include "llfloaterchat.h" +#include "llfloaterchatterbox.h" #include "llfloaternewim.h" -#include "llhttpclient.h" #include "llhttpnode.h" #include "llimpanel.h" #include "llresizebar.h" @@ -42,40 +42,48 @@ #include "llcallingcard.h" #include "lltoolbar.h" #include "llviewermessage.h" +#include "llnotify.h" #include "llviewerregion.h" +#include "llfirstuse.h" + const EInstantMessage GROUP_DIALOG = IM_SESSION_GROUP_START; const EInstantMessage DEFAULT_DIALOG = IM_NOTHING_SPECIAL; // // Globals // -LLIMView* gIMView = NULL; +LLIMMgr* gIMMgr = NULL; // // Statics // +//*FIXME: make these all either UIStrings or Strings static LLString sOnlyUserMessage; -static LLString sOfflineMessage; +static LLUIString sOfflineMessage; static std::map<std::string,LLString> sEventStringsMap; static std::map<std::string,LLString> sErrorStringsMap; static std::map<std::string,LLString> sForceCloseSessionMap; +static LLUIString sInviteMessage; // // Helper Functions // // returns true if a should appear before b -static BOOL group_dictionary_sort( LLGroupData* a, LLGroupData* b ) -{ - return (LLString::compareDict( a->mName, b->mName ) < 0); -} +//static BOOL group_dictionary_sort( LLGroupData* a, LLGroupData* b ) +//{ +// return (LLString::compareDict( a->mName, b->mName ) < 0); +//} // the other_participant_id is either an agent_id, a group_id, or an inventory // folder item_id (collection of calling cards) -static LLUUID compute_session_id(EInstantMessage dialog, - const LLUUID& other_participant_id) + +// static +LLUUID LLIMMgr::computeSessionID( + EInstantMessage dialog, + const LLUUID& other_participant_id) { LLUUID session_id; if (IM_SESSION_GROUP_START == dialog) @@ -87,6 +95,11 @@ static LLUUID compute_session_id(EInstantMessage dialog, { session_id.generate(); } + else if (IM_SESSION_INVITE == dialog) + { + // use provided session id for invites + session_id = other_participant_id; + } else { LLUUID agent_id = gAgent.getID(); @@ -121,87 +134,25 @@ LLFloaterIM::LLFloaterIM() BOOL LLFloaterIM::postBuild() { - requires("only_user_message", WIDGET_TYPE_TEXT_BOX); - requires("offline_message", WIDGET_TYPE_TEXT_BOX); - requires("generic_request_error", WIDGET_TYPE_TEXT_BOX); - requires("insufficient_perms_error", WIDGET_TYPE_TEXT_BOX); - requires("generic_request_error", WIDGET_TYPE_TEXT_BOX); - requires("add_session_event", WIDGET_TYPE_TEXT_BOX); - requires("message_session_event", WIDGET_TYPE_TEXT_BOX); - requires("removed_from_group", WIDGET_TYPE_TEXT_BOX); - - if (checkRequirements()) - { - sOnlyUserMessage = childGetText("only_user_message"); - sOfflineMessage = childGetText("offline_message"); - - sErrorStringsMap["generic"] = - childGetText("generic_request_error"); - sErrorStringsMap["unverified"] = - childGetText("insufficient_perms_error"); - sErrorStringsMap["no_user_911"] = - childGetText("user_no_help"); + sOnlyUserMessage = getFormattedUIString("only_user_message"); + sOfflineMessage = getUIString("offline_message"); - sEventStringsMap["add"] = childGetText("add_session_event"); - sEventStringsMap["message"] = - childGetText("message_session_event"); + sErrorStringsMap["generic"] = + getFormattedUIString("generic_request_error"); + sErrorStringsMap["unverified"] = + getFormattedUIString("insufficient_perms_error"); + sErrorStringsMap["no_user_911"] = + getFormattedUIString("user_no_help"); - sForceCloseSessionMap["removed"] = - childGetText("removed_from_group"); + sEventStringsMap["add"] = childGetText("add_session_event"); + sEventStringsMap["message"] = + getFormattedUIString("message_session_event"); - return TRUE; - } - return FALSE; -} - -//// virtual -//BOOL LLFloaterIM::handleKeyHere(KEY key, MASK mask, BOOL called_from_parent) -//{ -// BOOL handled = FALSE; -// if (getEnabled() -// && mask == (MASK_CONTROL|MASK_SHIFT)) -// { -// if (key == 'W') -// { -// LLFloater* floater = getActiveFloater(); -// if (floater) -// { -// if (mTabContainer->getTabCount() == 1) -// { -// // trying to close last tab, close -// // entire window. -// close(); -// handled = TRUE; -// } -// } -// } -// } -// return handled || LLMultiFloater::handleKeyHere(key, mask, called_from_parent); -//} + sForceCloseSessionMap["removed"] = + getFormattedUIString("removed_from_group"); -void LLFloaterIM::onClose(bool app_quitting) -{ - if (!app_quitting) - { - gSavedSettings.setBOOL("ShowIM", FALSE); - } - setVisible(FALSE); -} - -//virtual -void LLFloaterIM::addFloater(LLFloater* floaterp, BOOL select_added_floater, LLTabContainer::eInsertionPoint insertion_point) -{ -/* - Code removed via patch from VWR-1626 - - // this code is needed to fix the bug where new IMs received will resize the IM floater. - // SL-29075, SL-24556, and others - LLRect parent_rect = getRect(); - S32 dheight = LLFLOATER_HEADER_SIZE + TABCNTR_HEADER_HEIGHT; - LLRect rect(0, parent_rect.getHeight()-dheight, parent_rect.getWidth(), 0); - floaterp->reshape(rect.getWidth(), rect.getHeight(), TRUE); -*/ - LLMultiFloater::addFloater(floaterp, select_added_floater, insertion_point); + sInviteMessage = getUIString("invite_message"); + return TRUE; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -213,7 +164,7 @@ void LLFloaterIM::addFloater(LLFloater* floaterp, BOOL select_added_floater, LLT class LLIMViewFriendObserver : public LLFriendObserver { public: - LLIMViewFriendObserver(LLIMView* tv) : mTV(tv) {} + LLIMViewFriendObserver(LLIMMgr* tv) : mTV(tv) {} virtual ~LLIMViewFriendObserver() {} virtual void changed(U32 mask) { @@ -223,7 +174,30 @@ public: } } protected: - LLIMView* mTV; + LLIMMgr* mTV; +}; + + +class LLIMMgr::LLIMSessionInvite +{ +public: + LLIMSessionInvite(const LLUUID& session_id, const LLString& session_name, const LLUUID& caller_id,const LLString& caller_name, EInstantMessage type, const LLString& session_handle, const LLString& notify_box) : + mSessionID(session_id), + mSessionName(session_name), + mCallerID(caller_id), + mCallerName(caller_name), + mType(type), + mSessionHandle(session_handle), + mNotifyBox(notify_box) + {}; + + LLUUID mSessionID; + LLString mSessionName; + LLUUID mCallerID; + LLString mCallerName; + EInstantMessage mType; + LLString mSessionHandle; + LLString mNotifyBox; }; @@ -234,7 +208,7 @@ protected: // This is a helper function to determine what kind of im session // should be used for the given agent. // static -EInstantMessage LLIMView::defaultIMTypeForAgent(const LLUUID& agent_id) +EInstantMessage LLIMMgr::defaultIMTypeForAgent(const LLUUID& agent_id) { EInstantMessage type = IM_NOTHING_SPECIAL; if(is_agent_friend(agent_id)) @@ -248,20 +222,20 @@ EInstantMessage LLIMView::defaultIMTypeForAgent(const LLUUID& agent_id) } // static -//void LLIMView::onPinButton(void*) +//void LLIMMgr::onPinButton(void*) //{ // BOOL state = gSavedSettings.getBOOL( "PinTalkViewOpen" ); // gSavedSettings.setBOOL( "PinTalkViewOpen", !state ); //} // static -void LLIMView::toggle(void*) +void LLIMMgr::toggle(void*) { static BOOL return_to_mouselook = FALSE; // Hide the button and show the floater or vice versa. - llassert( gIMView ); - BOOL old_state = gIMView->getFloaterOpen(); + llassert( gIMMgr ); + BOOL old_state = gIMMgr->getFloaterOpen(); // If we're in mouselook and we triggered the Talk View, we want to talk. if( gAgent.cameraMouselook() && old_state ) @@ -292,53 +266,39 @@ void LLIMView::toggle(void*) return_to_mouselook = FALSE; } - gIMView->setFloaterOpen( new_state ); + gIMMgr->setFloaterOpen( new_state ); } // // Member Functions // -LLIMView::LLIMView(const std::string& name, const LLRect& rect) : - LLView(name, rect, FALSE), +LLIMMgr::LLIMMgr() : mFriendObserver(NULL), mIMReceived(FALSE) { - gIMView = this; mFriendObserver = new LLIMViewFriendObserver(this); LLAvatarTracker::instance().addObserver(mFriendObserver); - mTalkFloater = new LLFloaterIM(); + //*HACK: use floater to initialize string constants from xml file + // then delete it right away + LLFloaterIM* dummy_floater = new LLFloaterIM(); + delete dummy_floater; - // New IM Panel - mNewIMFloater = new LLFloaterNewIM(); - mTalkFloater->addFloater(mNewIMFloater, TRUE); - - // Tabs sometimes overlap resize handle - mTalkFloater->moveResizeHandleToFront(); + mPendingVoiceInvitations = LLSD::emptyMap(); } -LLIMView::~LLIMView() +LLIMMgr::~LLIMMgr() { LLAvatarTracker::instance().removeObserver(mFriendObserver); delete mFriendObserver; // Children all cleaned up by default view destructor. } -EWidgetType LLIMView::getWidgetType() const -{ - return WIDGET_TYPE_TALK_VIEW; -} - -LLString LLIMView::getWidgetTag() const -{ - return LL_TALK_VIEW_TAG; -} - // Add a message to a session. -void LLIMView::addMessage( +void LLIMMgr::addMessage( const LLUUID& session_id, - const LLUUID& other_participant_id, + const LLUUID& target_id, const char* from, const char* msg, const char* session_name, @@ -347,11 +307,30 @@ void LLIMView::addMessage( const LLUUID& region_id, const LLVector3& position) { + LLUUID other_participant_id = target_id; + bool is_from_system = target_id.isNull(); + + // don't process muted IMs + if (gMuteListp->isMuted( + other_participant_id, + LLMute::flagTextChat) && !gMuteListp->isLinden(from)) + { + return; + } + + //not sure why...but if it is from ourselves we set the target_id + //to be NULL + if( other_participant_id == gAgent.getID() ) + { + other_participant_id = LLUUID::null; + } + LLFloaterIMPanel* floater; LLUUID new_session_id = session_id; if (new_session_id.isNull()) { - new_session_id = compute_session_id(dialog, other_participant_id); + //no session ID...compute new one + new_session_id = computeSessionID(dialog, other_participant_id); } floater = findFloaterBySession(new_session_id); if (!floater) @@ -363,20 +342,10 @@ void LLIMView::addMessage( << " by participant " << other_participant_id << llendl; } } - if(floater) - { - floater->addHistoryLine(msg); - } - else - { - //if we have recently requsted to be dropped from a session - //but are still receiving messages from the session, don't make - //a new floater - if ( mSessionsDropRequested.has(session_id.asString()) ) - { - return ; - } + // create IM window as necessary + if(!floater) + { const char* name = from; if(session_name && (strlen(session_name)>1)) { @@ -384,7 +353,12 @@ void LLIMView::addMessage( } - floater = createFloater(new_session_id, other_participant_id, name, dialog, FALSE); + floater = createFloater( + new_session_id, + other_participant_id, + name, + dialog, + FALSE); // When we get a new IM, and if you are a god, display a bit // of information about the source. This is to help liaisons @@ -404,27 +378,41 @@ void LLIMView::addMessage( //<< "*** region_id: " << region_id << std::endl //<< "*** position: " << position << std::endl; - floater->addHistoryLine(bonus_info.str()); + floater->addHistoryLine(bonus_info.str(), gSavedSettings.getColor4("SystemChatColor")); } - floater->addHistoryLine(msg); make_ui_sound("UISndNewIncomingIMSession"); } - if( !mTalkFloater->getVisible() && !floater->getVisible()) + // now add message to floater + if ( is_from_system ) // chat came from system + { + floater->addHistoryLine( + other_participant_id, + msg, + gSavedSettings.getColor4("SystemChatColor")); + } + else + { + floater->addHistoryLine(other_participant_id, msg); + } + + LLFloaterChatterBox* chat_floater = LLFloaterChatterBox::getInstance(LLSD()); + + if( !chat_floater->getVisible() && !floater->getVisible()) { //if the IM window is not open and the floater is not visible (i.e. not torn off) - LLFloater* previouslyActiveFloater = mTalkFloater->getActiveFloater(); + LLFloater* previouslyActiveFloater = chat_floater->getActiveFloater(); // select the newly added floater (or the floater with the new line added to it). // it should be there. - mTalkFloater->selectFloater(floater); + chat_floater->selectFloater(floater); //there was a previously unseen IM, make that old tab flashing //it is assumed that the most recently unseen IM tab is the one current selected/active if ( previouslyActiveFloater && getIMReceived() ) { - mTalkFloater->setFloaterFlashing(previouslyActiveFloater, TRUE); + chat_floater->setFloaterFlashing(previouslyActiveFloater, TRUE); } //notify of a new IM @@ -432,37 +420,87 @@ void LLIMView::addMessage( } } -void LLIMView::notifyNewIM() +void LLIMMgr::addSystemMessage(const LLUUID& session_id, const LLString& message_name, const LLString::format_map_t& args) +{ + LLUIString message; + + // null session id means near me (chat history) + if (session_id.isNull()) + { + LLFloaterChat* floaterp = LLFloaterChat::getInstance(); + + message = floaterp->getUIString(message_name); + message.setArgList(args); + + LLChat chat(message); + chat.mSourceType = CHAT_SOURCE_SYSTEM; + LLFloaterChat::getInstance()->addChatHistory(chat); + } + else // going to IM session + { + LLFloaterIMPanel* floaterp = findFloaterBySession(session_id); + if (floaterp) + { + message = floaterp->getUIString(message_name); + message.setArgList(args); + + gIMMgr->addMessage(session_id, LLUUID::null, SYSTEM_FROM, message.getString().c_str()); + } + } +} + +void LLIMMgr::notifyNewIM() { - if(!gIMView->getFloaterOpen()) + if(!gIMMgr->getFloaterOpen()) { mIMReceived = TRUE; } } -BOOL LLIMView::getIMReceived() const +void LLIMMgr::clearNewIMNotification() +{ + mIMReceived = FALSE; +} + +BOOL LLIMMgr::getIMReceived() const { return mIMReceived; } // This method returns TRUE if the local viewer has a session // currently open keyed to the uuid. -BOOL LLIMView::isIMSessionOpen(const LLUUID& uuid) +BOOL LLIMMgr::isIMSessionOpen(const LLUUID& uuid) { LLFloaterIMPanel* floater = findFloaterBySession(uuid); if(floater) return TRUE; return FALSE; } +LLUUID LLIMMgr::addP2PSession(const std::string& name, + const LLUUID& other_participant_id, + const LLString& voice_session_handle) +{ + LLUUID session_id = addSession(name, IM_NOTHING_SPECIAL, other_participant_id); + + LLFloaterIMPanel* floater = findFloaterBySession(session_id); + if(floater) + { + LLVoiceChannelP2P* voice_channelp = (LLVoiceChannelP2P*)floater->getVoiceChannel(); + voice_channelp->setSessionHandle(voice_session_handle); + } + + return session_id; +} + // This adds a session to the talk view. The name is the local name of // the session, dialog specifies the type of session. If the session // exists, it is brought forward. Specifying id = NULL results in an // im session to everyone. Returns the uuid of the session. -LLUUID LLIMView::addSession(const std::string& name, +LLUUID LLIMMgr::addSession(const std::string& name, EInstantMessage dialog, const LLUUID& other_participant_id) { - LLUUID session_id = compute_session_id(dialog, other_participant_id); + LLUUID session_id = computeSessionID(dialog, other_participant_id); LLFloaterIMPanel* floater = findFloaterBySession(session_id); if(!floater) @@ -478,7 +516,7 @@ LLUUID LLIMView::addSession(const std::string& name, TRUE); noteOfflineUsers(floater, ids); - mTalkFloater->showFloater(floater); + LLFloaterChatterBox::getInstance(LLSD())->showFloater(floater); } else { @@ -491,7 +529,7 @@ LLUUID LLIMView::addSession(const std::string& name, // Adds a session using the given session_id. If the session already exists // the dialog type is assumed correct. Returns the uuid of the session. -LLUUID LLIMView::addSession(const std::string& name, +LLUUID LLIMMgr::addSession(const std::string& name, EInstantMessage dialog, const LLUUID& other_participant_id, const LLDynamicArray<LLUUID>& ids) @@ -501,7 +539,7 @@ LLUUID LLIMView::addSession(const std::string& name, return LLUUID::null; } - LLUUID session_id = compute_session_id(dialog, + LLUUID session_id = computeSessionID(dialog, other_participant_id); LLFloaterIMPanel* floater = findFloaterBySession(session_id); @@ -520,7 +558,7 @@ LLUUID LLIMView::addSession(const std::string& name, noteOfflineUsers(floater, ids); } - mTalkFloater->showFloater(floater); + LLFloaterChatterBox::getInstance(LLSD())->showFloater(floater); //mTabContainer->selectTabPanel(panel); floater->setInputFocus(TRUE); return floater->getSessionID(); @@ -528,134 +566,272 @@ LLUUID LLIMView::addSession(const std::string& name, // This removes the panel referenced by the uuid, and then restores // internal consistency. The internal pointer is not deleted. -void LLIMView::removeSession(const LLUUID& session_id) +void LLIMMgr::removeSession(const LLUUID& session_id) { LLFloaterIMPanel* floater = findFloaterBySession(session_id); if(floater) { mFloaters.erase(floater->getHandle()); - mTalkFloater->removeFloater(floater); + LLFloaterChatterBox::getInstance(LLSD())->removeFloater(floater); //mTabContainer->removeTabPanel(floater); - if(session_id.notNull() - && (floater->getDialogType() != IM_NOTHING_SPECIAL)) - { - mSessionsDropRequested[session_id.asString()] = LLSD(); - } } } -void LLIMView::refresh() +void LLIMMgr::inviteToSession( + const LLUUID& session_id, + const LLString& session_name, + const LLUUID& caller_id, + const LLString& caller_name, + EInstantMessage type, + const LLString& session_handle) { - S32 old_scroll_pos = mNewIMFloater->getScrollPos(); - mNewIMFloater->clearAllTargets(); + //ignore voice invites from voice-muted residents + if (gMuteListp->isMuted(caller_id)) + { + return; + } - // build a list of groups. - LLLinkedList<LLGroupData> group_list( group_dictionary_sort ); + LLString notify_box_type; - LLGroupData* group; - S32 count = gAgent.mGroups.count(); - S32 i; - // read/sort groups on the first pass. - for(i = 0; i < count; ++i) + BOOL ad_hoc_invite = FALSE; + if(type == IM_SESSION_P2P_INVITE) { - group = &(gAgent.mGroups.get(i)); - group_list.addDataSorted( group ); + notify_box_type = "VoiceInviteP2P"; } - - // add groups to the floater on the second pass. - for(group = group_list.getFirstData(); - group; - group = group_list.getNextData()) + else if (gAgent.isInGroup(session_id)) { - mNewIMFloater->addGroup(group->mID, (void*)(&GROUP_DIALOG), TRUE, FALSE); + notify_box_type = "VoiceInviteGroup"; + } + else + { + notify_box_type = "VoiceInviteAdHoc"; + ad_hoc_invite = TRUE; } - // build a set of buddies in the current buddy list. - LLCollectAllBuddies collector; - LLAvatarTracker::instance().applyFunctor(collector); - LLCollectAllBuddies::buddy_map_t::iterator it; - LLCollectAllBuddies::buddy_map_t::iterator end; - it = collector.mOnline.begin(); - end = collector.mOnline.end(); - for( ; it != end; ++it) + LLIMSessionInvite* invite = new LLIMSessionInvite( + session_id, + session_name, + caller_id, + caller_name, + type, + session_handle, + notify_box_type); + + LLVoiceChannel* channelp = LLVoiceChannel::getChannelByID(session_id); + if (channelp && channelp->callStarted()) { - mNewIMFloater->addAgent((*it).second, (void*)(&DEFAULT_DIALOG), TRUE); + // you have already started a call to the other user, so just accept the invite + inviteUserResponse(0, invite); + return; } - it = collector.mOffline.begin(); - end = collector.mOffline.end(); - for( ; it != end; ++it) + + if (type == IM_SESSION_P2P_INVITE || ad_hoc_invite) { - mNewIMFloater->addAgent((*it).second, (void*)(&DEFAULT_DIALOG), FALSE); + // is the inviter a friend? + if (LLAvatarTracker::instance().getBuddyInfo(caller_id) == NULL) + { + // if not, and we are ignoring voice invites from non-friends + // then silently decline + if (gSavedSettings.getBOOL("VoiceCallsFriendsOnly")) + { + // invite not from a friend, so decline + inviteUserResponse(1, invite); + return; + } + } } - mNewIMFloater->setScrollPos( old_scroll_pos ); + if ( !mPendingVoiceInvitations.has(session_id.asString()) ) + { + if (caller_name.empty()) + { + gCacheName->getName(caller_id, onInviteNameLookup, invite); + } + else + { + LLString::format_map_t args; + args["[NAME]"] = caller_name; + args["[GROUP]"] = session_name; + + LLNotifyBox::showXml(notify_box_type, + args, + inviteUserResponse, + (void*)invite); + + } + mPendingVoiceInvitations[session_id.asString()] = LLSD(); + } } -// JC - This used to set console visibility. It doesn't any more. -void LLIMView::setFloaterOpen(BOOL set_open) +//static +void LLIMMgr::onInviteNameLookup(const LLUUID& id, const char* first, const char* last, BOOL is_group, void* userdata) { - gSavedSettings.setBOOL("ShowIM", set_open); + LLIMSessionInvite* invite = (LLIMSessionInvite*)userdata; - //RN "visible" and "open" are considered synonomous for now - if (set_open) + invite->mCallerName = llformat("%s %s", first, last); + invite->mSessionName = invite->mCallerName; + + LLString::format_map_t args; + args["[NAME]"] = invite->mCallerName; + + LLNotifyBox::showXml(invite->mNotifyBox, + args, + inviteUserResponse, + (void*)invite); +} + +class LLViewerChatterBoxInvitationAcceptResponder : + public LLHTTPClient::Responder +{ +public: + LLViewerChatterBoxInvitationAcceptResponder( + const LLUUID& session_id, + bool is_voice_invitation) { - mTalkFloater->open(); /*Flawfinder: ignore*/ + mSessionID = session_id; + mIsVoiceInvitiation = is_voice_invitation; } - else + + void result(const LLSD& content) { - mTalkFloater->close(); + if ( gIMMgr) + { + LLFloaterIMPanel* floaterp = + gIMMgr->findFloaterBySession(mSessionID); + + if (floaterp) + { + floaterp->setSpeakersList(content["agents"]); + + if ( mIsVoiceInvitiation ) + { + floaterp->requestAutoConnect(); + LLFloaterIMPanel::onClickStartCall(floaterp); + } + } + + if ( mIsVoiceInvitiation ) + { + gIMMgr->clearPendingVoiceInviation(mSessionID); + } + } } - if( set_open ) + void error(U32 statusNum, const std::string& reason) { - // notifyNewIM(); - - // We're showing the IM, so mark view as non-pending - mIMReceived = FALSE; + //throw something back to the viewer here? + if ( gIMMgr && mIsVoiceInvitiation ) + { + gIMMgr->clearPendingVoiceInviation(mSessionID); + } } -} +private: + LLUUID mSessionID; + bool mIsVoiceInvitiation; +}; -BOOL LLIMView::getFloaterOpen() +//static +void LLIMMgr::inviteUserResponse(S32 option, void* user_data) { - return mTalkFloater->getVisible(); -} - -void LLIMView::pruneSessions() -{ - if(mNewIMFloater) + LLIMSessionInvite* invitep = (LLIMSessionInvite*)user_data; + + switch(option) { - BOOL removed = TRUE; - LLFloaterIMPanel* floater = NULL; - while(removed) + case 0: // accept + { + if (invitep->mType == IM_SESSION_P2P_INVITE) + { + // create a normal IM session + invitep->mSessionID = gIMMgr->addP2PSession( + invitep->mSessionName, + invitep->mCallerID, + invitep->mSessionHandle); + + LLFloaterIMPanel* im_floater = + gIMMgr->findFloaterBySession( + invitep->mSessionID); + if (im_floater) + { + im_floater->requestAutoConnect(); + LLFloaterIMPanel::onClickStartCall(im_floater); + } + + gIMMgr->clearPendingVoiceInviation(invitep->mSessionID); + } + else + { + gIMMgr->addSession( + invitep->mSessionName, + invitep->mType, + invitep->mSessionID); + + std::string url = gAgent.getRegion()->getCapability( + "ChatSessionRequest"); + + LLSD data; + data["method"] = "accept invitation"; + data["session-id"] = invitep->mSessionID; + LLHTTPClient::post( + url, + data, + new LLViewerChatterBoxInvitationAcceptResponder( + invitep->mSessionID, + true)); + } + } + break; + case 2: // mute (also implies ignore, so this falls through to the "ignore" case below) + { + // mute the sender of this invite + if (!gMuteListp->isMuted(invitep->mCallerID)) + { + LLMute mute(invitep->mCallerID, invitep->mCallerName, LLMute::AGENT); + gMuteListp->add(mute); + } + } + /* FALLTHROUGH */ + + case 1: // ignore { - removed = FALSE; - std::set<LLViewHandle>::iterator handle_it; - for(handle_it = mFloaters.begin(); - handle_it != mFloaters.end(); - ++handle_it) + if (invitep->mType == IM_SESSION_P2P_INVITE) { - floater = (LLFloaterIMPanel*)LLFloater::getFloaterByHandle(*handle_it); - if(floater && !mNewIMFloater->isUUIDAvailable(floater->getOtherParticipantID())) + if(gVoiceClient) { - // remove this floater - removed = TRUE; - mFloaters.erase(handle_it++); - floater->close(); - break; + gVoiceClient->declineInvite(invitep->mSessionHandle); } } } + break; } + + delete invitep; } +void LLIMMgr::refresh() +{ +} -void LLIMView::disconnectAllSessions() +void LLIMMgr::setFloaterOpen(BOOL set_open) { - if(mNewIMFloater) + if (set_open) { - mNewIMFloater->setEnabled(FALSE); + LLFloaterChatterBox::showInstance(LLSD()); } + else + { + LLFloaterChatterBox::hideInstance(LLSD()); + } +} + + +BOOL LLIMMgr::getFloaterOpen() +{ + return LLFloaterChatterBox::instanceVisible(LLSD()); +} + +void LLIMMgr::disconnectAllSessions() +{ LLFloaterIMPanel* floater = NULL; std::set<LLViewHandle>::iterator handle_it; for(handle_it = mFloaters.begin(); @@ -670,7 +846,7 @@ void LLIMView::disconnectAllSessions() if (floater) { floater->setEnabled(FALSE); - floater->onClose(TRUE); + floater->close(TRUE); } } } @@ -679,7 +855,7 @@ void LLIMView::disconnectAllSessions() // This method returns the im panel corresponding to the uuid // provided. The uuid can either be a session id or an agent // id. Returns NULL if there is no matching panel. -LLFloaterIMPanel* LLIMView::findFloaterBySession(const LLUUID& session_id) +LLFloaterIMPanel* LLIMMgr::findFloaterBySession(const LLUUID& session_id) { LLFloaterIMPanel* rv = NULL; std::set<LLViewHandle>::iterator handle_it; @@ -698,17 +874,25 @@ LLFloaterIMPanel* LLIMView::findFloaterBySession(const LLUUID& session_id) } -BOOL LLIMView::hasSession(const LLUUID& session_id) +BOOL LLIMMgr::hasSession(const LLUUID& session_id) { return (findFloaterBySession(session_id) != NULL); } +void LLIMMgr::clearPendingVoiceInviation(const LLUUID& session_id) +{ + if ( mPendingVoiceInvitations.has(session_id.asString()) ) + { + mPendingVoiceInvitations.erase(session_id.asString()); + } +} + // create a floater and update internal representation for // consistency. Returns the pointer, caller (the class instance since // it is a private method) is not responsible for deleting the // pointer. Add the floater to this but do not select it. -LLFloaterIMPanel* LLIMView::createFloater( +LLFloaterIMPanel* LLIMMgr::createFloater( const LLUUID& session_id, const LLUUID& other_participant_id, const std::string& session_label, @@ -720,7 +904,7 @@ LLFloaterIMPanel* LLIMView::createFloater( llwarns << "Creating LLFloaterIMPanel with null session ID" << llendl; } - llinfos << "LLIMView::createFloater: from " << other_participant_id + llinfos << "LLIMMgr::createFloater: from " << other_participant_id << " in session " << session_id << llendl; LLFloaterIMPanel* floater = new LLFloaterIMPanel(session_label, LLRect(), @@ -729,12 +913,12 @@ LLFloaterIMPanel* LLIMView::createFloater( other_participant_id, dialog); LLTabContainerCommon::eInsertionPoint i_pt = user_initiated ? LLTabContainerCommon::RIGHT_OF_CURRENT : LLTabContainerCommon::END; - mTalkFloater->addFloater(floater, FALSE, i_pt); + LLFloaterChatterBox::getInstance(LLSD())->addFloater(floater, FALSE, i_pt); mFloaters.insert(floater->getHandle()); return floater; } -LLFloaterIMPanel* LLIMView::createFloater( +LLFloaterIMPanel* LLIMMgr::createFloater( const LLUUID& session_id, const LLUUID& other_participant_id, const std::string& session_label, @@ -747,7 +931,7 @@ LLFloaterIMPanel* LLIMView::createFloater( llwarns << "Creating LLFloaterIMPanel with null session ID" << llendl; } - llinfos << "LLIMView::createFloater: from " << other_participant_id + llinfos << "LLIMMgr::createFloater: from " << other_participant_id << " in session " << session_id << llendl; LLFloaterIMPanel* floater = new LLFloaterIMPanel(session_label, LLRect(), @@ -757,18 +941,18 @@ LLFloaterIMPanel* LLIMView::createFloater( ids, dialog); LLTabContainerCommon::eInsertionPoint i_pt = user_initiated ? LLTabContainerCommon::RIGHT_OF_CURRENT : LLTabContainerCommon::END; - mTalkFloater->addFloater(floater, FALSE, i_pt); + LLFloaterChatterBox::getInstance(LLSD())->addFloater(floater, FALSE, i_pt); mFloaters.insert(floater->getHandle()); return floater; } -void LLIMView::noteOfflineUsers(LLFloaterIMPanel* floater, +void LLIMMgr::noteOfflineUsers(LLFloaterIMPanel* floater, const LLDynamicArray<LLUUID>& ids) { S32 count = ids.count(); if(count == 0) { - floater->addHistoryLine(sOnlyUserMessage); + floater->addHistoryLine(sOnlyUserMessage, gSavedSettings.getColor4("SystemChatColor")); } else { @@ -785,25 +969,25 @@ void LLIMView::noteOfflineUsers(LLFloaterIMPanel* floater, LLUIString offline = sOfflineMessage; offline.setArg("[FIRST]", first); offline.setArg("[LAST]", last); - floater->addHistoryLine(offline); + floater->addHistoryLine(offline, gSavedSettings.getColor4("SystemChatColor")); } } } } -void LLIMView::processIMTypingStart(const LLIMInfo* im_info) +void LLIMMgr::processIMTypingStart(const LLIMInfo* im_info) { processIMTypingCore(im_info, TRUE); } -void LLIMView::processIMTypingStop(const LLIMInfo* im_info) +void LLIMMgr::processIMTypingStop(const LLIMInfo* im_info) { processIMTypingCore(im_info, FALSE); } -void LLIMView::processIMTypingCore(const LLIMInfo* im_info, BOOL typing) +void LLIMMgr::processIMTypingCore(const LLIMInfo* im_info, BOOL typing) { - LLUUID session_id = compute_session_id(im_info->mIMType, im_info->mFromID); + LLUUID session_id = computeSessionID(im_info->mIMType, im_info->mFromID); LLFloaterIMPanel* floater = findFloaterBySession(session_id); if (floater) { @@ -811,8 +995,9 @@ void LLIMView::processIMTypingCore(const LLIMInfo* im_info, BOOL typing) } } -void LLIMView::updateFloaterSessionID(const LLUUID& old_session_id, - const LLUUID& new_session_id) +void LLIMMgr::updateFloaterSessionID( + const LLUUID& old_session_id, + const LLUUID& new_session_id) { LLFloaterIMPanel* floater = findFloaterBySession(old_session_id); if (floater) @@ -821,9 +1006,9 @@ void LLIMView::updateFloaterSessionID(const LLUUID& old_session_id, } } -void LLIMView::onDropRequestReplyReceived(const LLUUID& session_id) -{ - mSessionsDropRequested.erase(session_id.asString()); +LLFloaterChatterBox* LLIMMgr::getFloater() +{ + return LLFloaterChatterBox::getInstance(LLSD()); } void onConfirmForceCloseError(S32 option, void* data) @@ -831,25 +1016,24 @@ void onConfirmForceCloseError(S32 option, void* data) //only 1 option really LLFloaterIMPanel* floater = ((LLFloaterIMPanel*) data); - if ( floater ) floater->onClose(FALSE); + if ( floater ) floater->close(FALSE); } -class LLViewerIMSessionStartReply : public LLHTTPNode +class LLViewerChatterBoxSessionStartReply : public LLHTTPNode { public: virtual void describe(Description& desc) const { - desc.shortInfo("Used for receiving a reply to a request to initialize an IM session"); + desc.shortInfo("Used for receiving a reply to a request to initialize an ChatterBox session"); desc.postAPI(); desc.input( "{\"client_session_id\": UUID, \"session_id\": UUID, \"success\" boolean, \"reason\": string"); desc.source(__FILE__, __LINE__); } - virtual void post( - ResponsePtr response, - const LLSD& context, - const LLSD& input) const + virtual void post(ResponsePtr response, + const LLSD& context, + const LLSD& input) const { LLSD body; LLUUID temp_session_id; @@ -863,16 +1047,21 @@ public: if ( success ) { session_id = body["session_id"].asUUID(); - gIMView->updateFloaterSessionID( + gIMMgr->updateFloaterSessionID( temp_session_id, session_id); + LLFloaterIMPanel* floaterp = gIMMgr->findFloaterBySession(session_id); + if (floaterp) + { + floaterp->setSpeakersList(body["agents"]); + } } else { //throw an error dialog and close the temp session's //floater LLFloaterIMPanel* floater = - gIMView->findFloaterBySession(temp_session_id); + gIMMgr->findFloaterBySession(temp_session_id); if (floater) { LLString::format_map_t args; @@ -880,22 +1069,22 @@ public: sErrorStringsMap[body["error"].asString()]; args["[RECIPIENT]"] = floater->getTitle(); - gViewerWindow->alertXml( - "IMSessionStartError", - args, - onConfirmForceCloseError, - floater); + gViewerWindow->alertXml("ChatterBoxSessionStartError", + args, + onConfirmForceCloseError, + floater); + } } } }; -class LLViewerIMSessionEventReply : public LLHTTPNode +class LLViewerChatterBoxSessionEventReply : public LLHTTPNode { public: virtual void describe(Description& desc) const { - desc.shortInfo("Used for receiving a reply to a IM session event"); + desc.shortInfo("Used for receiving a reply to a ChatterBox session event"); desc.postAPI(); desc.input( "{\"event\": string, \"reason\": string, \"success\": boolean, \"session_id\": UUID"); @@ -917,7 +1106,7 @@ public: { //throw an error dialog LLFloaterIMPanel* floater = - gIMView->findFloaterBySession(session_id); + gIMMgr->findFloaterBySession(session_id); if (floater) { LLString::format_map_t args; @@ -927,14 +1116,14 @@ public: sEventStringsMap[body["event"].asString()]; args["[RECIPIENT]"] = floater->getTitle(); - gViewerWindow->alertXml("IMSessionEventError", + gViewerWindow->alertXml("ChatterBoxSessionEventError", args); } } } }; -class LLViewerForceCloseIMSession: public LLHTTPNode +class LLViewerForceCloseChatterBoxSession: public LLHTTPNode { public: virtual void post(ResponsePtr response, @@ -948,7 +1137,7 @@ public: reason = input["body"]["reason"].asString(); LLFloaterIMPanel* floater = - gIMView ->findFloaterBySession(session_id); + gIMMgr ->findFloaterBySession(session_id); if ( floater ) { @@ -957,7 +1146,7 @@ public: args["[NAME]"] = floater->getTitle(); args["[REASON]"] = sForceCloseSessionMap[reason]; - gViewerWindow->alertXml("ForceCloseIMSession", + gViewerWindow->alertXml("ForceCloseChatterBoxSession", args, onConfirmForceCloseError, floater); @@ -965,28 +1154,6 @@ public: } }; -class LLViewerIMSessionDropReply : public LLHTTPNode -{ -public: - virtual void post(ResponsePtr response, - const LLSD& context, - const LLSD& input) const - { - LLUUID session_id; - bool success; - - success = input["body"]["success"].asBoolean(); - session_id = input["body"]["session_id"].asUUID(); - - if ( !success ) - { - //throw an error alert? - } - - gIMView->onDropRequestReplyReceived(session_id); - } -}; - class LLViewerChatterBoxSessionAgentListUpdates : public LLHTTPNode { public: @@ -995,29 +1162,38 @@ public: const LLSD& context, const LLSD& input) const { + LLFloaterIMPanel* floaterp = gIMMgr->findFloaterBySession(input["body"]["session_id"].asUUID()); + if (floaterp) + { + floaterp->updateSpeakersList(input["body"]["updates"]); + } } }; class LLViewerChatterBoxInvitation : public LLHTTPNode { public: + virtual void post( - ResponsePtr responder, + ResponsePtr response, const LLSD& context, const LLSD& input) const { if ( input["body"].has("instantmessage") ) { + LLString capability = input["body"]["capabilities"]["call"].asString(); + LLSD message_params = input["body"]["instantmessage"]["message_params"]; + //do something here to have the IM invite behave + //just like a normal IM //this is just replicated code from process_improved_im //and should really go in it's own function -jwolk if (gNoRender) { return; } - char buffer[DB_IM_MSG_BUF_SIZE * 2]; /* Flawfinder: ignore */ LLChat chat; @@ -1032,7 +1208,11 @@ public: (time_t) message_params["timestamp"].asInteger(); BOOL is_busy = gAgent.getBusy(); - BOOL is_muted = gMuteListp->isMuted(from_id, name); + BOOL is_muted = gMuteListp->isMuted( + from_id, + name.c_str(), + LLMute::flagTextChat); + BOOL is_linden = gMuteListp->isLinden( name.c_str()); char separator_string[3]=": "; /* Flawfinder: ignore */ @@ -1049,7 +1229,8 @@ public: chat.mMuted = is_muted && !is_linden; chat.mFromID = from_id; chat.mFromName = name; - if (!is_linden && is_busy) + + if (!is_linden && (is_busy || is_muted)) { return; } @@ -1077,10 +1258,9 @@ public: BOOL is_this_agent = FALSE; if(from_id == gAgentID) { - from_id = LLUUID::null; is_this_agent = TRUE; } - gIMView->addMessage( + gIMMgr->addMessage( session_id, from_id, name.c_str(), @@ -1102,11 +1282,7 @@ public: chat.mText = buffer; LLFloaterChat::addChat(chat, TRUE, is_this_agent); - //if we succesfully accepted the invitation - //send a message back down - - //TODO - When availble, have this response just be part - //of an automatic response system + //K now we want to accept the invitation std::string url = gAgent.getRegion()->getCapability( "ChatSessionRequest"); @@ -1118,28 +1294,46 @@ public: LLHTTPClient::post( url, data, - NULL); + new LLViewerChatterBoxInvitationAcceptResponder( + input["body"]["session_id"], + false)); } } //end if invitation has instant message + else if ( input["body"].has("voice") ) + { + if (gNoRender) + { + return; + } + + if(!LLVoiceClient::voiceEnabled()) + { + // Don't display voice invites unless the user has voice enabled. + return; + } + + gIMMgr->inviteToSession( + input["body"]["session_id"].asUUID(), + input["body"]["session_name"].asString(), + input["body"]["from_id"].asUUID(), + input["body"]["from_name"].asString(), + IM_SESSION_INVITE); + } } }; -LLHTTPRegistration<LLViewerIMSessionStartReply> - gHTTPRegistrationMessageImsessionstartreply( +LLHTTPRegistration<LLViewerChatterBoxSessionStartReply> + gHTTPRegistrationMessageChatterboxsessionstartreply( "/message/ChatterBoxSessionStartReply"); -LLHTTPRegistration<LLViewerIMSessionEventReply> - gHTTPRegistrationMessageImsessioneventreply( +LLHTTPRegistration<LLViewerChatterBoxSessionEventReply> + gHTTPRegistrationMessageChatterboxsessioneventreply( "/message/ChatterBoxSessionEventReply"); -LLHTTPRegistration<LLViewerForceCloseIMSession> - gHTTPRegistrationMessageForceCloseImSession( +LLHTTPRegistration<LLViewerForceCloseChatterBoxSession> + gHTTPRegistrationMessageForceclosechatterboxsession( "/message/ForceCloseChatterBoxSession"); -LLHTTPRegistration<LLViewerIMSessionDropReply> - gHTTPRegistrationMessageImSessionDropReply( - "/message/ChatterBoxSessionLeaveReply"); - LLHTTPRegistration<LLViewerChatterBoxSessionAgentListUpdates> gHTTPRegistrationMessageChatterboxsessionagentlistupdates( "/message/ChatterBoxSessionAgentListUpdates"); diff --git a/indra/newview/llimview.h b/indra/newview/llimview.h index aac6fd63ce..f8a36107d6 100644 --- a/indra/newview/llimview.h +++ b/indra/newview/llimview.h @@ -1,5 +1,5 @@ /** - * @file llimview.h + * @file LLIMMgr.h * @brief Container for Instant Messaging * * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. @@ -13,31 +13,32 @@ #include "llinstantmessage.h" #include "lluuid.h" -class LLFloaterNewIM; +class LLFloaterChatterBox; class LLUUID; class LLFloaterIMPanel; class LLFriendObserver; class LLFloaterIM; -class LLIMView : public LLView +class LLIMMgr : public LLSingleton<LLIMMgr> { public: - LLIMView(const std::string& name, const LLRect& rect); - ~LLIMView(); - - virtual EWidgetType getWidgetType() const; - virtual LLString getWidgetTag() const; + LLIMMgr(); + virtual ~LLIMMgr(); // Add a message to a session. The session can keyed to sesion id // or agent id. - void addMessage(const LLUUID& session_id, const LLUUID& target_id, - const char* from, const char* msg, + void addMessage(const LLUUID& session_id, + const LLUUID& target_id, + const char* from, + const char* msg, const char* session_name = NULL, EInstantMessage dialog = IM_NOTHING_SPECIAL, U32 parent_estate_id = 0, const LLUUID& region_id = LLUUID::null, const LLVector3& position = LLVector3::zero); + void addSystemMessage(const LLUUID& session_id, const LLString& message_name, const LLString::format_map_t& args); + // This method returns TRUE if the local viewer has a session // currently open keyed to the uuid. The uuid can be keyed by // either session id or agent id. @@ -62,11 +63,23 @@ public: const LLUUID& other_participant_id, const LLDynamicArray<LLUUID>& ids); + // Creates a P2P session with the requisite handle for responding to voice calls + LLUUID addP2PSession(const std::string& name, + const LLUUID& other_participant_id, + const LLString& voice_session_handle); + // This removes the panel referenced by the uuid, and then // restores internal consistency. The internal pointer is not // deleted. void removeSession(const LLUUID& session_id); + void inviteToSession(const LLUUID& session_id, + const LLString& session_name, + const LLUUID& caller, + const LLString& caller_name, + EInstantMessage type, + const LLString& session_handle = LLString::null); + //Updates a given session's session IDs. Does not open, //create or do anything new. If the old session doesn't //exist, then nothing happens. @@ -80,6 +93,7 @@ public: void refresh(); void notifyNewIM(); + void clearNewIMNotification(); // IM received that you haven't seen yet BOOL getIMReceived() const; @@ -87,10 +101,7 @@ public: void setFloaterOpen(BOOL open); /*Flawfinder: ignore*/ BOOL getFloaterOpen(); - LLFloaterIM * getFloater() { return mTalkFloater; } - - // close any sessions which are not available in the newimpanel. - void pruneSessions(); + LLFloaterChatterBox* getFloater(); // This method is used to go through all active sessions and // disable all of them. This method is usally called when you are @@ -111,9 +122,13 @@ public: // is no matching panel. LLFloaterIMPanel* findFloaterBySession(const LLUUID& session_id); - void onDropRequestReplyReceived(const LLUUID& session_id); + static LLUUID computeSessionID(EInstantMessage dialog, const LLUUID& other_participant_id); + void clearPendingVoiceInviation(const LLUUID& session_id); + private: + class LLIMSessionInvite; + // create a panel and update internal representation for // consistency. Returns the pointer, caller (the class instance // since it is a private method) is not responsible for deleting @@ -139,9 +154,8 @@ private: void processIMTypingCore(const LLIMInfo* im_info, BOOL typing); -public: - LLFloaterIM* mTalkFloater; - LLFloaterNewIM* mNewIMFloater; + static void inviteUserResponse(S32 option, void* user_data); + static void onInviteNameLookup(const LLUUID& id, const char* first, const char* last, BOOL is_group, void* userdata); private: std::set<LLViewHandle> mFloaters; @@ -150,7 +164,7 @@ private: // An IM has been received that you haven't seen yet. BOOL mIMReceived; - LLSD mSessionsDropRequested; + LLSD mPendingVoiceInvitations; }; @@ -158,13 +172,10 @@ class LLFloaterIM : public LLMultiFloater { public: LLFloaterIM(); - ///*virtual*/ BOOL handleKeyHere(KEY key, MASK mask, BOOL called_from_parent); /*virtual*/ BOOL postBuild(); - /*virtual*/ void onClose(bool app_quitting); - /*virtual*/ void addFloater(LLFloater* floaterp, BOOL select_added_floater, LLTabContainer::eInsertionPoint insertion_point = LLTabContainerCommon::END); }; // Globals -extern LLIMView *gIMView; +extern LLIMMgr *gIMMgr; #endif // LL_LLIMView_H diff --git a/indra/newview/llinventorybridge.cpp b/indra/newview/llinventorybridge.cpp index 4a1d496d6f..8a08bd2a4b 100644 --- a/indra/newview/llinventorybridge.cpp +++ b/indra/newview/llinventorybridge.cpp @@ -2573,8 +2573,8 @@ void LLCallingCardBridge::performAction(LLFolderView* folder, LLInventoryModel* if (item && (item->getCreatorUUID() != gAgent.getID()) && (!item->getCreatorUUID().isNull())) { - gIMView->setFloaterOpen(TRUE); - gIMView->addSession(item->getName(), IM_NOTHING_SPECIAL, item->getCreatorUUID()); + gIMMgr->setFloaterOpen(TRUE); + gIMMgr->addSession(item->getName(), IM_NOTHING_SPECIAL, item->getCreatorUUID()); } } else if ("lure" == action) @@ -2651,7 +2651,7 @@ void LLCallingCardBridge::buildContextMenu(LLMenuGL& menu, U32 flags) BOOL good_card = (item && (LLUUID::null != item->getCreatorUUID()) && (item->getCreatorUUID() != gAgent.getID())); - + BOOL user_online = (LLAvatarTracker::instance().isBuddyOnline(item->getCreatorUUID())); items.push_back("Send Instant Message"); items.push_back("Offer Teleport..."); items.push_back("Conference Chat"); @@ -2659,6 +2659,9 @@ void LLCallingCardBridge::buildContextMenu(LLMenuGL& menu, U32 flags) if (!good_card) { disabled_items.push_back("Send Instant Message"); + } + if (!good_card || !user_online) + { disabled_items.push_back("Offer Teleport..."); disabled_items.push_back("Conference Chat"); } diff --git a/indra/newview/llinventorymodel.cpp b/indra/newview/llinventorymodel.cpp index f7aa4d4251..793571e111 100644 --- a/indra/newview/llinventorymodel.cpp +++ b/indra/newview/llinventorymodel.cpp @@ -1466,11 +1466,18 @@ bool LLInventoryModel::loadSkeleton( { LLViewerInventoryCategory* cat = categories[i]; cat_set_t::iterator cit = temp_cats.find(cat); + if (cit == temp_cats.end()) + { + continue; // cache corruption?? not sure why this happens -SJB + } LLViewerInventoryCategory* tcat = *cit; // we can safely ignore anything loaded from file, but // not sent down in the skeleton. - if(cit == not_cached) continue; + if(cit == not_cached) + { + continue; + } if(cat->getVersion() != tcat->getVersion()) { // if the cached version does not match the server version, diff --git a/indra/newview/llmutelist.cpp b/indra/newview/llmutelist.cpp index 8b07dfbfeb..62c7d40a90 100644 --- a/indra/newview/llmutelist.cpp +++ b/indra/newview/llmutelist.cpp @@ -169,11 +169,11 @@ BOOL LLMuteList::isLinden(const LLString& name) const } -BOOL LLMuteList::add(const LLMute& mute) +BOOL LLMuteList::add(const LLMute& mute, U32 flags) { - // Can't mute Lindens + // Can't mute text from Lindens if ((mute.mType == LLMute::AGENT || mute.mType == LLMute::BY_NAME) - && isLinden(mute.mName)) + && isLinden(mute.mName) && (flags & LLMute::flagTextChat || flags == 0)) { gViewerWindow->alertXml("MuteLinden"); return FALSE; @@ -218,25 +218,59 @@ BOOL LLMuteList::add(const LLMute& mute) } else { - std::pair<mute_set_t::iterator, bool> result = mMutes.insert(mute); - if (result.second) + // Need a local (non-const) copy to set up flags properly. + LLMute localmute = mute; + + // If an entry for the same entity is already in the list, remove it, saving flags as necessary. + mute_set_t::iterator it = mMutes.find(localmute); + if (it != mMutes.end()) { - llinfos << "Muting " << mute.mName << " id " << mute.mID << llendl; - updateAdd(mute); - notifyObservers(); - //Kill all particle systems owned by muted task - if(mute.mType == LLMute::AGENT || mute.mType == LLMute::OBJECT) - { - gWorldPointer->mPartSim.cleanMutedParticles(mute.mID); - } + // This mute is already in the list. Save the existing entry's flags if that's warranted. + localmute.mFlags = it->mFlags; + + mMutes.erase(it); + // Don't need to call notifyObservers() here, since it will happen after the entry has been re-added below. + } + else + { + // There was no entry in the list previously. Fake things up by making it look like the previous entry had all properties unmuted. + localmute.mFlags = LLMute::flagAll; + } - return TRUE; + if(flags) + { + // The user passed some combination of flags. Make sure those flag bits are turned off (i.e. those properties will be muted). + localmute.mFlags &= (~flags); } else { - return FALSE; + // The user passed 0. Make sure all flag bits are turned off (i.e. all properties will be muted). + localmute.mFlags = 0; + } + + // (re)add the mute entry. + { + std::pair<mute_set_t::iterator, bool> result = mMutes.insert(localmute); + if (result.second) + { + llinfos << "Muting " << localmute.mName << " id " << localmute.mID << " flags " << localmute.mFlags << llendl; + updateAdd(mute); + notifyObservers(); + if(!(localmute.mFlags & LLMute::flagParticles)) + { + //Kill all particle systems owned by muted task + if(localmute.mType == LLMute::AGENT || localmute.mType == LLMute::OBJECT) + { + gWorldPointer->mPartSim.cleanMutedParticles(localmute.mID); + } + } + return TRUE; + } } } + + // If we were going to return success, we'd have done it by now. + return FALSE; } void LLMuteList::updateAdd(const LLMute& mute) @@ -251,14 +285,14 @@ void LLMuteList::updateAdd(const LLMute& mute) msg->addUUIDFast(_PREHASH_MuteID, mute.mID); msg->addStringFast(_PREHASH_MuteName, mute.mName); msg->addS32("MuteType", mute.mType); - msg->addU32("MuteFlags", 0x0); // future + msg->addU32("MuteFlags", mute.mFlags); gAgent.sendReliableMessage(); mIsLoaded = TRUE; } -BOOL LLMuteList::remove(const LLMute& mute) +BOOL LLMuteList::remove(const LLMute& mute, U32 flags) { BOOL found = FALSE; @@ -266,8 +300,46 @@ BOOL LLMuteList::remove(const LLMute& mute) mute_set_t::iterator it = mMutes.find(mute); if (it != mMutes.end()) { - updateRemove(*it); + LLMute localmute = *it; + bool remove = true; + if(flags) + { + // If the user passed mute flags, we may only want to turn some flags on. + localmute.mFlags |= flags; + + if(localmute.mFlags == LLMute::flagAll) + { + // Every currently available mute property has been masked out. + // Remove the mute entry entirely. + } + else + { + // Only some of the properties are masked out. Update the entry. + remove = false; + } + } + else + { + // The caller didn't pass any flags -- just remove the mute entry entirely. + } + + // Always remove the entry from the set -- it will be re-added with new flags if necessary. mMutes.erase(it); + + if(remove) + { + // The entry was actually removed. Notify the server. + updateRemove(localmute); + llinfos << "Unmuting " << localmute.mName << " id " << localmute.mID << " flags " << localmute.mFlags << llendl; + } + else + { + // Flags were updated, the mute entry needs to be retransmitted to the server and re-added to the list. + mMutes.insert(localmute); + updateAdd(localmute); + llinfos << "Updating mute entry " << localmute.mName << " id " << localmute.mID << " flags " << localmute.mFlags << llendl; + } + // Must be after erase. notifyObservers(); found = TRUE; @@ -361,7 +433,7 @@ BOOL LLMuteList::loadFromFile(const LLString& filename) buffer, " %d %254s %254[^|]| %u\n", &type, id_buffer, name_buffer, &flags); LLUUID id = LLUUID(id_buffer); - LLMute mute(id, name_buffer, (LLMute::EType)type); + LLMute mute(id, name_buffer, (LLMute::EType)type, flags); if (mute.mID.isNull() || mute.mType == LLMute::BY_NAME) { @@ -410,19 +482,27 @@ BOOL LLMuteList::saveToFile(const LLString& filename) { it->mID.toString(id_string); const LLString& name = it->mName; - fprintf(fp, "%d %s %s|\n", (S32)it->mType, id_string, name.c_str()); + fprintf(fp, "%d %s %s|%u\n", (S32)it->mType, id_string, name.c_str(), it->mFlags); } fclose(fp); return TRUE; } -BOOL LLMuteList::isMuted(const LLUUID& id, const LLString& name) const +BOOL LLMuteList::isMuted(const LLUUID& id, const LLString& name, U32 flags) const { // don't need name or type for lookup LLMute mute(id); mute_set_t::const_iterator mute_it = mMutes.find(mute); - if (mute_it != mMutes.end()) return TRUE; + if (mute_it != mMutes.end()) + { + // If any of the flags the caller passed are set, this item isn't considered muted for this caller. + if(flags & mute_it->mFlags) + { + return FALSE; + } + return TRUE; + } // empty names can't be legacy-muted if (name.empty()) return FALSE; diff --git a/indra/newview/llmutelist.h b/indra/newview/llmutelist.h index 7f9b1b3bf6..f035e4c2b1 100644 --- a/indra/newview/llmutelist.h +++ b/indra/newview/llmutelist.h @@ -23,8 +23,22 @@ public: // Legacy mutes are BY_NAME and have null UUID. enum EType { BY_NAME = 0, AGENT = 1, OBJECT = 2, GROUP = 3, COUNT = 4 }; - LLMute(const LLUUID& id, const LLString& name = "", EType type = BY_NAME) - : mID(id), mName(name), mType(type) { } + // Bits in the mute flags. For backwards compatibility (since any mute list entries that were created before the flags existed + // will have a flags field of 0), some of the flags are "inverted". + // Note that it's possible, through flags, to completely disable an entry in the mute list. The code should detect this case + // and remove the mute list entry instead. + enum + { + flagTextChat = 0x00000001, // If set, don't mute user's text chat + flagVoiceChat = 0x00000002, // If set, don't mute user's voice chat + flagParticles = 0x00000004, // If set, don't mute user's particles + flagObjectSounds = 0x00000008, // If set, mute user's object sounds + + flagAll = 0x0000000F // Mask of all currently defined flags + }; + + LLMute(const LLUUID& id, const LLString& name = "", EType type = BY_NAME, U32 flags = 0) + : mID(id), mName(name), mType(type),mFlags(flags) { } // Returns name + suffix based on type // For example: "James Tester (resident)" @@ -39,6 +53,7 @@ public: LLUUID mID; // agent or object id LLString mName; // agent or object name EType mType; // needed for UI display of existing mutes + U32 mFlags; // flags pertaining to this mute entry }; class LLMuteList @@ -50,14 +65,17 @@ public: void addObserver(LLMuteListObserver* observer); void removeObserver(LLMuteListObserver* observer); - // Add either a normal or a BY_NAME mute. - BOOL add(const LLMute& mute); + // Add either a normal or a BY_NAME mute, for any or all properties. + BOOL add(const LLMute& mute, U32 flags = 0); - // Remove both normal and legacy mutes. - BOOL remove(const LLMute& mute); + // Remove both normal and legacy mutes, for any or all properties. + BOOL remove(const LLMute& mute, U32 flags = 0); // Name is required to test against legacy text-only mutes. - BOOL isMuted(const LLUUID& id, const LLString& name = LLString::null) const; + BOOL isMuted(const LLUUID& id, const LLString& name = LLString::null, U32 flags = 0) const; + + // Alternate (convenience) form for places we don't need to pass the name, but do need flags + BOOL isMuted(const LLUUID& id, U32 flags) const { return isMuted(id, LLString::null, flags); }; BOOL isLinden(const LLString& name) const; diff --git a/indra/newview/llnetmap.cpp b/indra/newview/llnetmap.cpp index 4fde8988f5..754c096296 100644 --- a/indra/newview/llnetmap.cpp +++ b/indra/newview/llnetmap.cpp @@ -226,7 +226,7 @@ void LLNetMap::draw() { LLGLSNoTexture no_texture; - LLUI::setScissorRegionLocal(LLRect(0, mRect.getHeight(), mRect.getWidth(), 0)); + LLLocalClipRect clip(getLocalRect()); glMatrixMode(GL_MODELVIEW); diff --git a/indra/newview/lloverlaybar.cpp b/indra/newview/lloverlaybar.cpp index 8e36297c93..0847864be0 100644 --- a/indra/newview/lloverlaybar.cpp +++ b/indra/newview/lloverlaybar.cpp @@ -14,24 +14,26 @@ #include "lloverlaybar.h" #include "audioengine.h" -#include "llparcel.h" - #include "llagent.h" #include "llbutton.h" -#include "llviewercontrol.h" +#include "llfocusmgr.h" #include "llimview.h" -#include "lltextbox.h" -#include "llvoavatar.h" #include "llmediaengine.h" -#include "viewer.h" +#include "llpanelaudiovolume.h" +#include "llparcel.h" +#include "lltextbox.h" #include "llui.h" +#include "llviewercontrol.h" +#include "llviewerimagelist.h" #include "llviewermenu.h" // handle_reset_view() #include "llviewerparcelmgr.h" -#include "llwebbrowserctrl.h" #include "llvieweruictrlfactory.h" -#include "llviewerimagelist.h" #include "llviewerwindow.h" -#include "llfocusmgr.h" +#include "llvoiceclient.h" +#include "llvoavatar.h" +#include "llvoiceremotectrl.h" +#include "llwebbrowserctrl.h" +#include "viewer.h" // // Globals @@ -47,38 +49,54 @@ extern S32 MENU_BAR_HEIGHT; //static -void* LLOverlayBar::createMediaRemote(void* userdata) +void* LLOverlayBar::createMasterRemote(void* userdata) { - - LLOverlayBar *self = (LLOverlayBar*)userdata; + LLOverlayBar *self = (LLOverlayBar*)userdata; + self->mMasterRemote = new LLMediaRemoteCtrl ( "master_volume", + "volume", + LLRect(), + "panel_master_volume.xml"); + return self->mMasterRemote; +} - +void* LLOverlayBar::createMediaRemote(void* userdata) +{ + LLOverlayBar *self = (LLOverlayBar*)userdata; self->mMediaRemote = new LLMediaRemoteCtrl ( "media_remote", - "media", - LLRect(), - "panel_media_remote.xml"); + "media", + LLRect(), + "panel_media_remote.xml"); return self->mMediaRemote; } - - void* LLOverlayBar::createMusicRemote(void* userdata) { - LLOverlayBar *self = (LLOverlayBar*)userdata; - self->mMusicRemote = new LLMediaRemoteCtrl ( "music_remote", - "music", - LLRect(), - "panel_music_remote.xml" ); + "music", + LLRect(), + "panel_music_remote.xml" ); return self->mMusicRemote; } +void* LLOverlayBar::createVoiceRemote(void* userdata) +{ + LLOverlayBar *self = (LLOverlayBar*)userdata; + self->mVoiceRemote = new LLVoiceRemoteCtrl("voice_remote"); + return self->mVoiceRemote; +} + LLOverlayBar::LLOverlayBar(const std::string& name, const LLRect& rect) -: LLPanel(name, rect, FALSE) // not bordered + : LLPanel(name, rect, FALSE), // not bordered + mMasterRemote(NULL), + mMusicRemote(NULL), + mMediaRemote(NULL), + mVoiceRemote(NULL), + mMediaState(STOPPED), + mMusicState(STOPPED) { setMouseOpaque(FALSE); setIsChrome(TRUE); @@ -86,8 +104,10 @@ LLOverlayBar::LLOverlayBar(const std::string& name, const LLRect& rect) isBuilt = FALSE; LLCallbackMap::map_t factory_map; + factory_map["master_volume"] = LLCallbackMap(LLOverlayBar::createMasterRemote, this); factory_map["media_remote"] = LLCallbackMap(LLOverlayBar::createMediaRemote, this); factory_map["music_remote"] = LLCallbackMap(LLOverlayBar::createMusicRemote, this); + factory_map["voice_remote"] = LLCallbackMap(LLOverlayBar::createVoiceRemote, this); gUICtrlFactory->buildPanel(this, "panel_overlaybar.xml", &factory_map); @@ -97,34 +117,17 @@ LLOverlayBar::LLOverlayBar(const std::string& name, const LLRect& rect) childSetAction("Mouselook",onClickMouselook,this); childSetAction("Stand Up",onClickStandUp,this); - mMusicRemote->addObserver ( this ); - - if ( gAudiop ) - { - //HACK / NOT HACK - //maintenance patch - bhear obsoletes this, do not merge (poppy) - F32 audioLevelMusic = gSavedSettings.getF32 ( "AudioLevelMusic" ); - mMusicRemote->setVolume ( audioLevelMusic ); - gAudiop->setInternetStreamGain ( audioLevelMusic ); - mMusicRemote->setTransportState ( LLMediaRemoteCtrl::Stop, FALSE ); - }; - mIsFocusRoot = TRUE; - - mMediaRemote->addObserver ( this ); - mMediaRemote->setVolume ( gSavedSettings.getF32 ( "MediaAudioVolume" ) ); - isBuilt = true; + // make overlay bar conform to window size + setRect(rect); layoutButtons(); } LLOverlayBar::~LLOverlayBar() { // LLView destructor cleans up children - - mMusicRemote->remObserver ( this ); - mMediaRemote->remObserver ( this ); } EWidgetType LLOverlayBar::getWidgetType() const @@ -148,20 +151,28 @@ void LLOverlayBar::reshape(S32 width, S32 height, BOOL called_from_parent) } } - void LLOverlayBar::layoutButtons() { S32 width = mRect.getWidth(); - if (width > 800) width = 800; + if (width > 1024) width = 1024; S32 count = getChildCount(); const S32 PAD = gSavedSettings.getS32("StatusBarPad"); - F32 segment_width = (F32)(width) / (F32)count; + const S32 num_media_controls = 3; + S32 media_remote_width = mMediaRemote ? mMediaRemote->getRect().getWidth() : 0; + S32 music_remote_width = mMusicRemote ? mMusicRemote->getRect().getWidth() : 0; + S32 voice_remote_width = mVoiceRemote ? mVoiceRemote->getRect().getWidth() : 0; + S32 master_remote_width = mMasterRemote ? mMasterRemote->getRect().getWidth() : 0; - S32 btn_width = lltrunc(segment_width - PAD); + // total reserved width for all media remotes + const S32 ENDPAD = 20; + S32 remote_total_width = media_remote_width + PAD + music_remote_width + PAD + voice_remote_width + PAD + master_remote_width + ENDPAD; - S32 remote_width = mMusicRemote->getRect().getWidth(); + // calculate button widths + F32 segment_width = (F32)(width - remote_total_width) / (F32)(count - num_media_controls); + + S32 btn_width = lltrunc(segment_width - PAD); // Evenly space all views LLRect r; @@ -171,22 +182,47 @@ void LLOverlayBar::layoutButtons() { LLView *view = *child_iter; r = view->getRect(); - r.mLeft = (width) - llround((i+1)*segment_width); + r.mLeft = (width) - llround(remote_total_width + (i-num_media_controls+1)*segment_width); r.mRight = r.mLeft + btn_width; view->setRect(r); i++; } // Fix up remotes to have constant width because they can't shrink - r = mMusicRemote->getRect(); - r.mRight = r.mLeft + remote_width; - mMusicRemote->setRect(r); - - r = mMediaRemote->getRect(); - r.mLeft = mMusicRemote->getRect().mRight + PAD; - r.mRight = r.mLeft + remote_width; - mMediaRemote->setRect(r); - + S32 right = mRect.getWidth() - remote_total_width - PAD; + if (mMediaRemote) + { + r = mMediaRemote->getRect(); + r.mLeft = right + PAD; + right = r.mLeft + media_remote_width; + r.mRight = right; + mMediaRemote->setRect(r); + } + if (mMusicRemote) + { + r = mMusicRemote->getRect(); + r.mLeft = right + PAD; + right = r.mLeft + music_remote_width; + r.mRight = right; + mMusicRemote->setRect(r); + } + if (mVoiceRemote) + { + r = mVoiceRemote->getRect(); + r.mLeft = right + PAD; + right = r.mLeft + voice_remote_width; + r.mRight = right; + mVoiceRemote->setRect(r); + } + if (mMasterRemote) + { + r = mMasterRemote->getRect(); + r.mLeft = right + PAD; + right = r.mLeft + master_remote_width; + r.mRight = right; + mMasterRemote->setRect(r); + } + updateRect(); } @@ -266,7 +302,7 @@ void LLOverlayBar::draw() // Per-frame updates of visibility void LLOverlayBar::refresh() { - BOOL im_received = gIMView->getIMReceived(); + BOOL im_received = gIMMgr->getIMReceived(); childSetVisible("IM Received", im_received); childSetEnabled("IM Received", im_received); @@ -297,10 +333,10 @@ void LLOverlayBar::refresh() } - if ( gAudiop ) + if ( mMusicRemote && gAudiop ) { LLParcel* parcel = gParcelMgr->getAgentParcel(); - if (!parcel + if (!parcel || !parcel->getMusicURL() || !parcel->getMusicURL()[0] || !gSavedSettings.getBOOL("AudioStreamingMusic")) @@ -316,50 +352,29 @@ void LLOverlayBar::refresh() } // if there is a url and a texture and media is enabled and available and media streaming is on... (phew!) - if ( LLMediaEngine::getInstance () && - LLMediaEngine::getInstance ()->getUrl ().length () && - LLMediaEngine::getInstance ()->getImageUUID ().notNull () && - LLMediaEngine::getInstance ()->isEnabled () && - LLMediaEngine::getInstance ()->isAvailable () && - gSavedSettings.getBOOL ( "AudioStreamingVideo" ) ) + if ( mMediaRemote ) { - // display remote control - mMediaRemote->setVisible ( TRUE ); - mMediaRemote->setEnabled ( TRUE ); - - if ( LLMediaEngine::getInstance ()->getMediaRenderer () ) + if (LLMediaEngine::getInstance () && + LLMediaEngine::getInstance ()->getUrl ().length () && + LLMediaEngine::getInstance ()->getImageUUID ().notNull () && + LLMediaEngine::getInstance ()->isEnabled () && + LLMediaEngine::getInstance ()->isAvailable () && + gSavedSettings.getBOOL ( "AudioStreamingVideo" ) ) { - if ( LLMediaEngine::getInstance ()->getMediaRenderer ()->isPlaying () || - LLMediaEngine::getInstance ()->getMediaRenderer ()->isLooping () ) - { - mMediaRemote->setTransportState ( LLMediaRemoteCtrl::Pause, TRUE ); - } - else - if ( LLMediaEngine::getInstance ()->getMediaRenderer ()->isPaused () ) - { - mMediaRemote->setTransportState ( LLMediaRemoteCtrl::Play, TRUE ); - } - else - { - mMediaRemote->setTransportState ( LLMediaRemoteCtrl::Stop, TRUE ); - }; - }; + // display remote control + mMediaRemote->setVisible ( TRUE ); + mMediaRemote->setEnabled ( TRUE ); + } + else + { + mMediaRemote->setVisible ( FALSE ); + mMediaRemote->setEnabled ( FALSE ); + } } - else + if (mVoiceRemote) { - mMediaRemote->setVisible ( FALSE ); - mMediaRemote->setEnabled ( FALSE ); - mMediaRemote->setTransportState ( LLMediaRemoteCtrl::Stop, TRUE ); - }; - - BOOL any_button = (childIsVisible("IM Received") - || childIsVisible("Set Not Busy") - || childIsVisible("Release Keys") - || childIsVisible("Mouselook") - || childIsVisible("Stand Up") - || mMusicRemote->getVisible() - || mMediaRemote->getVisible() ); - + mVoiceRemote->setVisible(LLVoiceClient::voiceEnabled()); + } // turn off the whole bar in mouselook if (gAgent.cameraMouselook()) @@ -368,8 +383,8 @@ void LLOverlayBar::refresh() } else { - setVisible(any_button); - }; + setVisible(TRUE); + } } //----------------------------------------------------------------------- @@ -379,7 +394,7 @@ void LLOverlayBar::refresh() // static void LLOverlayBar::onClickIMReceived(void*) { - gIMView->setFloaterOpen(TRUE); + gIMMgr->setFloaterOpen(TRUE); } @@ -415,134 +430,162 @@ void LLOverlayBar::onClickStandUp(void*) } //////////////////////////////////////////////////////////////////////////////// -// -// -void -LLOverlayBar:: -onVolumeChange ( const LLMediaRemoteCtrlObserver::EventType& eventIn ) -{ - LLUICtrl* control = eventIn.getControl (); - F32 value = eventIn.getValue (); +// static media helpers +// *TODO: Move this into an audio manager abstraction - if ( control == mMusicRemote ) +//static +void LLOverlayBar::mediaPlay(void*) +{ + if (!gOverlayBar) { - if (gAudiop) - { - gAudiop->setInternetStreamGain ( value ); - }; - gSavedSettings.setF32 ( "AudioLevelMusic", value ); + return; } - else - if ( control == mMediaRemote ) + gOverlayBar->mMediaState = PLAYING; // desired state + LLParcel* parcel = gParcelMgr->getAgentParcel(); + if (parcel) { - LLMediaEngine::getInstance ()->setVolume ( value ); - gSavedSettings.setF32 ( "MediaAudioVolume", value ); - - }; + LLString path(""); + LLMediaEngine::getInstance()->convertImageAndLoadUrl( true, false, path ); + } } - -//////////////////////////////////////////////////////////////////////////////// -// -// -void -LLOverlayBar:: -onStopButtonPressed ( const LLMediaRemoteCtrlObserver::EventType& eventIn ) +//static +void LLOverlayBar::mediaPause(void*) { - LLUICtrl* control = eventIn.getControl (); - - if ( control == mMusicRemote ) + if (!gOverlayBar) { - if ( gAudiop ) - { - gAudiop->stopInternetStream (); - }; - mMusicRemote->setTransportState ( LLMediaRemoteCtrl::Stop, FALSE ); + return; } - else - if ( control == mMediaRemote ) + gOverlayBar->mMediaState = PAUSED; // desired state + LLMediaEngine::getInstance()->pause(); +} +//static +void LLOverlayBar::mediaStop(void*) +{ + if (!gOverlayBar) { - LLMediaEngine::getInstance ()->stop (); - mMediaRemote->setTransportState ( LLMediaRemoteCtrl::Stop, TRUE ); - }; + return; + } + gOverlayBar->mMediaState = STOPPED; // desired state + LLMediaEngine::getInstance()->stop(); } -//////////////////////////////////////////////////////////////////////////////// -// -// -void LLOverlayBar::onPlayButtonPressed( const LLMediaRemoteCtrlObserver::EventType& eventIn ) +//static +void LLOverlayBar::musicPlay(void*) { - LLUICtrl* control = eventIn.getControl (); - - LLParcel* parcel = gParcelMgr->getAgentParcel(); - if ( control == mMusicRemote ) + if (!gOverlayBar) { - if (gAudiop) + return; + } + gOverlayBar->mMusicState = PLAYING; // desired state + if (gAudiop) + { + LLParcel* parcel = gParcelMgr->getAgentParcel(); + if ( parcel ) { - if ( parcel ) + // this doesn't work properly when crossing parcel boundaries - even when the + // stream is stopped, it doesn't return the right thing - commenting out for now. +// if ( gAudiop->isInternetStreamPlaying() == 0 ) { - // this doesn't work properly when crossing parcel boundaries - even when the - // stream is stopped, it doesn't return the right thing - commenting out for now. - //if ( gAudiop->isInternetStreamPlaying() == 0 ) - //{ - const char* music_url = parcel->getMusicURL(); - - gAudiop->startInternetStream(music_url); - - mMusicRemote->setTransportState ( LLMediaRemoteCtrl::Play, FALSE ); - //} + gAudiop->startInternetStream(parcel->getMusicURL()); } - }; - - // CP: this is the old way of doing things (click play each time on a parcel to start stream) - //if (gAudiop) - //{ - // if (gAudiop->isInternetStreamPlaying() > 0) - // { - // gAudiop->pauseInternetStream ( 0 ); - // } - // else - // { - // if (parcel) - // { - // const char* music_url = parcel->getMusicURL(); - // gAudiop->startInternetStream(music_url); - // } - // } - //}; - //mMusicRemote->setTransportState ( LLMediaRemoteCtrl::Stop, FALSE ); + } } - else - if ( control == mMediaRemote ) +} +//static +void LLOverlayBar::musicPause(void*) +{ + if (!gOverlayBar) { - LLParcel* parcel = gParcelMgr->getAgentParcel(); - if (parcel) + return; + } + gOverlayBar->mMusicState = PAUSED; // desired state + if (gAudiop) + { + gAudiop->pauseInternetStream(1); + } +} +//static +void LLOverlayBar::musicStop(void*) +{ + if (!gOverlayBar) + { + return; + } + gOverlayBar->mMusicState = STOPPED; // desired state + if (gAudiop) + { + gAudiop->stopInternetStream(); + } +} + +//static +void LLOverlayBar::enableMusicButtons(LLPanel* panel) +{ + BOOL play_enabled = FALSE; + BOOL play_visible = TRUE; + BOOL pause_visible = FALSE; + BOOL stop_enabled = FALSE; + if ( gAudiop && gOverlayBar && gSavedSettings.getBOOL("AudioStreamingMusic")) + { + play_enabled = TRUE; + S32 is_playing = gAudiop->isInternetStreamPlaying(); + if (is_playing == 1) { - LLString path( "" ); - LLMediaEngine::getInstance ()->convertImageAndLoadUrl( true, false, path ); - mMediaRemote->setTransportState ( LLMediaRemoteCtrl::Play, TRUE ); + play_visible = FALSE; + pause_visible = TRUE; + stop_enabled = TRUE; } - }; + else if (is_playing == 2) + { + play_visible = TRUE; + pause_visible = FALSE; + stop_enabled = TRUE; + } + } + panel->childSetEnabled("music_play", play_enabled); + panel->childSetEnabled("music_pause", play_enabled); + panel->childSetVisible("music_play", play_visible); + panel->childSetVisible("music_pause", pause_visible); + panel->childSetEnabled("music_stop", stop_enabled); } -//////////////////////////////////////////////////////////////////////////////// -// -// -void LLOverlayBar::onPauseButtonPressed( const LLMediaRemoteCtrlObserver::EventType& eventIn ) +//static +void LLOverlayBar::enableMediaButtons(LLPanel* panel) { - LLUICtrl* control = eventIn.getControl (); + // Media + BOOL play_enabled = FALSE; + BOOL play_visible = TRUE; + BOOL pause_visible = FALSE; + BOOL stop_enabled = FALSE; - if ( control == mMusicRemote ) + if ( LLMediaEngine::getInstance() && gOverlayBar && gSavedSettings.getBOOL("AudioStreamingVideo") ) { - if (gAudiop) + play_enabled = TRUE; + if (LLMediaEngine::getInstance()->getMediaRenderer()) { - gAudiop->pauseInternetStream ( 1 ); - }; - mMusicRemote->setTransportState ( LLMediaRemoteCtrl::Play, FALSE ); + if ( LLMediaEngine::getInstance()->getMediaRenderer()->isPlaying() || + LLMediaEngine::getInstance()->getMediaRenderer()->isLooping() ) + { + play_visible = FALSE; + pause_visible = TRUE; + stop_enabled = TRUE; + } + else if ( LLMediaEngine::getInstance()->getMediaRenderer()->isPaused() ) + { + play_visible = TRUE; + pause_visible = FALSE; + stop_enabled = TRUE; + } + } } - else - if ( control == mMediaRemote ) - { - LLMediaEngine::getInstance ()->pause (); - mMediaRemote->setTransportState ( LLMediaRemoteCtrl::Pause, TRUE ); - }; + panel->childSetEnabled("media_play", play_enabled); + panel->childSetEnabled("media_pause", play_enabled); + panel->childSetVisible("media_play", play_visible); + panel->childSetVisible("media_pause", pause_visible); + panel->childSetEnabled("media_stop", stop_enabled); +} + +void LLOverlayBar::toggleAudioVolumeFloater(void* user_data) +{ + LLFloaterAudioVolume::toggleInstance(LLSD()); } diff --git a/indra/newview/lloverlaybar.h b/indra/newview/lloverlaybar.h index 78f544df57..d7fff6ba1e 100644 --- a/indra/newview/lloverlaybar.h +++ b/indra/newview/lloverlaybar.h @@ -25,11 +25,10 @@ class LLUUID; class LLFrameTimer; class LLStatGraph; class LLSlider; -class LLVolumeSliderCtrl; +class LLVoiceRemoteCtrl; class LLOverlayBar -: public LLPanel, - public LLMediaRemoteCtrlObserver +: public LLPanel { public: LLOverlayBar(const std::string& name, const LLRect& rect ); @@ -38,14 +37,16 @@ public: virtual EWidgetType getWidgetType() const; virtual LLString getWidgetTag() const; - virtual void reshape(S32 width, S32 height, BOOL called_from_parent); - - void refresh(); + /*virtual*/ void refresh(); + /*virtual*/ void draw(); + /*virtual*/ void reshape(S32 width, S32 height, BOOL called_from_parent); void layoutButtons(); - /*virtual*/ void draw(); - + // helpers for returning desired state + BOOL mediaPlaying() { return mMediaState == PLAYING; } + BOOL musicPlaying() { return mMusicState == PLAYING; } + static void onClickIMReceived(void* data); static void onClickSetNotBusy(void* data); static void onClickReleaseKeys(void* data); @@ -53,23 +54,35 @@ public: static void onClickStandUp(void* data); static void onClickResetView(void* data); - // observer overrides - void onVolumeChange ( const LLMediaRemoteCtrlObserver::EventType& eventIn ); - void onStopButtonPressed ( const LLMediaRemoteCtrlObserver::EventType& eventIn ); - void onPlayButtonPressed ( const LLMediaRemoteCtrlObserver::EventType& eventIn ); - void onPauseButtonPressed ( const LLMediaRemoteCtrlObserver::EventType& eventIn ); - - LLMediaRemoteCtrl* getMusicRemoteControl () { return mMusicRemote; }; + //static media helper functions + static void mediaPlay(void*); + static void mediaPause(void*); + static void mediaStop(void*); + + static void musicPlay(void*); + static void musicPause(void*); + static void musicStop(void*); -protected: + static void toggleAudioVolumeFloater(void*); + + static void enableMediaButtons(LLPanel* panel); + static void enableMusicButtons(LLPanel* panel); +protected: + static void* createMasterRemote(void* userdata); static void* createMusicRemote(void* userdata); static void* createMediaRemote(void* userdata); + static void* createVoiceRemote(void* userdata); protected: + LLMediaRemoteCtrl* mMasterRemote; LLMediaRemoteCtrl* mMusicRemote; LLMediaRemoteCtrl* mMediaRemote; + LLVoiceRemoteCtrl* mVoiceRemote; BOOL isBuilt; + enum { STOPPED=0, PLAYING=1, PAUSED=2 }; + BOOL mMediaState; + BOOL mMusicState; }; extern LLOverlayBar* gOverlayBar; diff --git a/indra/newview/llpanelavatar.cpp b/indra/newview/llpanelavatar.cpp index 2567684fa9..ab2e30014d 100644 --- a/indra/newview/llpanelavatar.cpp +++ b/indra/newview/llpanelavatar.cpp @@ -1398,6 +1398,7 @@ void LLPanelAvatar::setAvatarID(const LLUUID &avatar_id, const LLString &name, setOnlineStatus(online_status); BOOL own_avatar = (mAvatarID == gAgent.getID() ); + BOOL avatar_is_friend = LLAvatarTracker::instance().getBuddyInfo(mAvatarID) != NULL; mPanelSecondLife->enableControls(own_avatar && mAllowEdit); mPanelWeb->enableControls(own_avatar && mAllowEdit); @@ -1515,7 +1516,7 @@ void LLPanelAvatar::setAvatarID(const LLUUID &avatar_id, const LLString &name, childSetToolTip("Show on Map",childGetValue("ShowOnMapFriendOnline").asString()); } childSetVisible("Add Friend...", true); - childSetEnabled("Add Friend...", true); + childSetEnabled("Add Friend...", !avatar_is_friend); childSetVisible("Pay...",TRUE); childSetEnabled("Pay...",FALSE); } @@ -1589,12 +1590,12 @@ void LLPanelAvatar::resetGroupList() void LLPanelAvatar::onClickIM(void* userdata) { LLPanelAvatar* self = (LLPanelAvatar*) userdata; - gIMView->setFloaterOpen(TRUE); + gIMMgr->setFloaterOpen(TRUE); std::string name; LLNameEditor* nameedit = LLViewerUICtrlFactory::getNameEditorByName(self->mPanelSecondLife, "name"); if (nameedit) name = nameedit->getText(); - gIMView->addSession(name, IM_NOTHING_SPECIAL, self->mAvatarID); + gIMMgr->addSession(name, IM_NOTHING_SPECIAL, self->mAvatarID); } @@ -1624,7 +1625,7 @@ void LLPanelAvatar::onClickAddFriend(void* userdata) LLNameEditor* name_edit = LLViewerUICtrlFactory::getNameEditorByName(self->mPanelSecondLife, "name"); if (name_edit) { - LLFloaterFriends::requestFriendshipDialog(self->getAvatarID(), + LLPanelFriends::requestFriendshipDialog(self->getAvatarID(), name_edit->getText()); } } diff --git a/indra/newview/llpanelgroup.cpp b/indra/newview/llpanelgroup.cpp index 23f7a4aba6..7a288a1730 100644 --- a/indra/newview/llpanelgroup.cpp +++ b/indra/newview/llpanelgroup.cpp @@ -105,16 +105,7 @@ void LLPanelGroupTab::handleClickHelp() LLAlertDialog* dialogp = gViewerWindow->alertXml("GenericAlert", args); if (dialogp) { - LLView* viewp = this; - LLFloater* root_floater = NULL; - while(viewp) - { - if(viewp->getWidgetType() == WIDGET_TYPE_FLOATER) - { - root_floater = (LLFloater*)viewp; - } - viewp = viewp->getParent(); - } + LLFloater* root_floater = gFloaterView->getParentFloater(this);; if (root_floater) { root_floater->addDependentFloater(dialogp); diff --git a/indra/newview/llpanellogin.cpp b/indra/newview/llpanellogin.cpp index c3d4551032..e32a64eb8e 100644 --- a/indra/newview/llpanellogin.cpp +++ b/indra/newview/llpanellogin.cpp @@ -24,6 +24,7 @@ #include "llcombobox.h" #include "llviewercontrol.h" #include "llfloaterabout.h" +#include "llfloatertest.h" #include "llfloaterpreference.h" #include "llfocusmgr.h" #include "lllineeditor.h" @@ -404,23 +405,30 @@ BOOL LLPanelLogin::handleKeyHere(KEY key, MASK mask, BOOL called_from_parent) return TRUE; } - #if LL_LIBXUL_ENABLED + if (('T' == key) && (MASK_CONTROL == mask)) + { + new LLFloaterSimple("floater_test.xml"); + return TRUE; + } + +#if LL_LIBXUL_ENABLED if ( KEY_F1 == key ) { llinfos << "Spawning HTML help window" << llendl; gViewerHtmlHelp.show(); return TRUE; - }; - #if ! LL_RELEASE_FOR_DOWNLOAD - if ( KEY_F2 == key ) - { - llinfos << "Spawning floater TOS window" << llendl; - LLFloaterTOS* tos_dialog = LLFloaterTOS::show(LLFloaterTOS::TOS_TOS,""); - tos_dialog->startModal(); - return TRUE; - }; - #endif - #endif + } + +# if !LL_RELEASE_FOR_DOWNLOAD + if ( KEY_F2 == key ) + { + llinfos << "Spawning floater TOS window" << llendl; + LLFloaterTOS* tos_dialog = LLFloaterTOS::show(LLFloaterTOS::TOS_TOS,""); + tos_dialog->startModal(); + return TRUE; + } +# endif +#endif if (!called_from_parent) { diff --git a/indra/newview/llpanelpermissions.cpp b/indra/newview/llpanelpermissions.cpp index da7fc16cef..d0061e281e 100644 --- a/indra/newview/llpanelpermissions.cpp +++ b/indra/newview/llpanelpermissions.cpp @@ -787,14 +787,24 @@ void LLPanelPermissions::onClickOwner(void *data) void LLPanelPermissions::onClickGroup(void* data) { + LLPanelPermissions* panelp = (LLPanelPermissions*)data; LLUUID owner_id; LLString name; BOOL owners_identical = gSelectMgr->selectGetOwner(owner_id, name); + LLFloater* parent_floater = gFloaterView->getParentFloater(panelp); + if(owners_identical && (owner_id == gAgent.getID())) { - LLFloaterGroups* fg; - fg = LLFloaterGroups::show(gAgent.getID(), LLFloaterGroups::CHOOSE_ONE); - fg->setOkCallback( cbGroupID, data ); + LLFloaterGroupPicker* fg; + fg = LLFloaterGroupPicker::showInstance(LLSD(gAgent.getID())); + fg->setSelectCallback( cbGroupID, data ); + + if (parent_floater) + { + LLRect new_rect = gFloaterView->findNeighboringPosition(parent_floater, fg); + fg->setOrigin(new_rect.mLeft, new_rect.mBottom); + parent_floater->addDependentFloater(fg); + } } } diff --git a/indra/newview/llpreview.cpp b/indra/newview/llpreview.cpp index c4f958420b..c1885ad898 100644 --- a/indra/newview/llpreview.cpp +++ b/indra/newview/llpreview.cpp @@ -42,7 +42,8 @@ LLPreview::LLPreview(const std::string& name) : mForceClose(FALSE), mUserResized(FALSE), mCloseAfterSave(FALSE), - mAssetStatus(PREVIEW_ASSET_UNLOADED) + mAssetStatus(PREVIEW_ASSET_UNLOADED), + mItem(NULL) { // don't add to instance list, since ItemID is null mAuxItem = new LLInventoryItem; // (LLPointer is auto-deleted) diff --git a/indra/newview/llpreviewscript.cpp b/indra/newview/llpreviewscript.cpp index 2d4a6d11d9..0c11226710 100644 --- a/indra/newview/llpreviewscript.cpp +++ b/indra/newview/llpreviewscript.cpp @@ -1074,9 +1074,6 @@ LLPreviewLSL::LLPreviewLSL(const std::string& name, const LLRect& rect, gUICtrlFactory->buildFloater(this,"floater_script_preview.xml", &factory_map); - moveResizeHandleToFront(); - - const LLInventoryItem* item = getItem(); childSetCommitCallback("desc", LLPreview::onText, this); @@ -1600,8 +1597,6 @@ LLLiveLSLEditor::LLLiveLSLEditor(const std::string& name, LLCallbackMap::map_t factory_map; factory_map["script ed panel"] = LLCallbackMap(LLLiveLSLEditor::createScriptEdPanel, this); - moveResizeHandleToFront(); - gUICtrlFactory->buildFloater(this,"floater_live_lsleditor.xml", &factory_map); diff --git a/indra/newview/llpreviewsound.cpp b/indra/newview/llpreviewsound.cpp index 7df0e48762..22b47eb8ca 100644 --- a/indra/newview/llpreviewsound.cpp +++ b/indra/newview/llpreviewsound.cpp @@ -8,15 +8,16 @@ #include "llviewerprecompiledheaders.h" -#include "llpreviewsound.h" +#include "audioengine.h" +#include "llagent.h" // gAgent #include "llbutton.h" -#include "llresmgr.h" #include "llinventory.h" #include "llinventoryview.h" -#include "audioengine.h" -#include "llviewermessage.h" // send_guid_sound_trigger -#include "llagent.h" // gAgent #include "lllineeditor.h" +#include "llpreviewsound.h" +#include "llresmgr.h" +#include "llviewercontrol.h" +#include "llviewermessage.h" // send_guid_sound_trigger #include "llvieweruictrlfactory.h" extern LLAudioEngine* gAudiop; @@ -82,7 +83,7 @@ void LLPreviewSound::auditionSound( void *userdata ) if(item && gAudiop) { LLVector3d lpos_global = gAgent.getPositionGlobal(); - - gAudiop->triggerSound(item->getAssetUUID(), gAgent.getID(), SOUND_GAIN, lpos_global); + F32 volume = SOUND_GAIN * gSavedSettings.getF32("AudioLevelSFX"); + gAudiop->triggerSound(item->getAssetUUID(), gAgent.getID(), volume, lpos_global); } } diff --git a/indra/newview/llpreviewtexture.cpp b/indra/newview/llpreviewtexture.cpp index 974ee27476..2462bb7bdf 100644 --- a/indra/newview/llpreviewtexture.cpp +++ b/indra/newview/llpreviewtexture.cpp @@ -415,7 +415,7 @@ void LLPreviewTexture::updateAspectRatio() S32 old_left = mRect.mLeft; if (getHost()) { - getHost()->growToFit(this, view_width, view_height); + getHost()->growToFit(view_width, view_height); } else { diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp index 117f020793..20761ded11 100644 --- a/indra/newview/llstartup.cpp +++ b/indra/newview/llstartup.cpp @@ -65,6 +65,8 @@ #include "lleventnotifier.h" #include "llface.h" #include "llfeaturemanager.h" +#include "llfirstuse.h" +#include "llfloateractivespeakers.h" #include "llfloaterchat.h" #include "llfloatergesture.h" #include "llfloaterland.h" @@ -133,6 +135,7 @@ #include "llfasttimerview.h" #include "llfloatermap.h" #include "llweb.h" +#include "llvoiceclient.h" #if LL_LIBXUL_ENABLED #include "llmozlib.h" @@ -1402,6 +1405,9 @@ BOOL idle_startup() gAutoLogin = FALSE; show_connect_box = TRUE; } + + // Pass the user information to the voice chat server interface. + gVoiceClient->userAuthorized(firstname, lastname, gAgentID); } else { @@ -1700,14 +1706,7 @@ BOOL idle_startup() { if (gViewerWindow) { - if (gSavedSettings.getBOOL("MuteAudio")) - { - LLMediaEngine::updateClass( 0.0f ); - } - else - { - LLMediaEngine::updateClass( gSavedSettings.getF32( "MediaAudioVolume" ) ); - } + audio_update_volume(true); } #if LL_QUICKTIME_ENABLED // windows only right now but will be ported to mac @@ -2287,6 +2286,9 @@ BOOL idle_startup() // On first start, ask user for gender dialog_choose_gender_first_start(); + // setup voice + LLFirstUse::useVoice(); + // Start automatic replay if the flag is set. if (gSavedSettings.getBOOL("StatsAutoRun")) { @@ -2314,7 +2316,7 @@ BOOL idle_startup() } } } - + // Clean up the userauth stuff. if (gUserAuthp) { @@ -2323,13 +2325,9 @@ BOOL idle_startup() } gStartupState++; - //RN: unmute audio now that we are entering world - //JC: But only if the user wants audio working. - if (gAudiop) - { - BOOL mute = gSavedSettings.getBOOL("MuteAudio"); - gAudiop->setMuted(mute); - } + + // Unmute audio if desired and setup volumes + audio_update_volume(); // reset keyboard focus to sane state of pointing at world gFocusMgr.setKeyboardFocus(NULL, NULL); diff --git a/indra/newview/llstatusbar.cpp b/indra/newview/llstatusbar.cpp index bc27d9af9f..3df559dee5 100644 --- a/indra/newview/llstatusbar.cpp +++ b/indra/newview/llstatusbar.cpp @@ -50,11 +50,14 @@ #include "llviewerparcelmgr.h" #include "llviewerthrottle.h" #include "llvieweruictrlfactory.h" +#include "llvoiceclient.h" // for gVoiceClient #include "lltoolmgr.h" #include "llfocusmgr.h" #include "viewer.h" +//#include "llfirstuse.h" + // // Globals // @@ -76,6 +79,18 @@ const F32 ICON_FLASH_FREQUENCY = 2.f; const S32 GRAPHIC_FUDGE = 4; const S32 TEXT_HEIGHT = 18; +static void onClickParcelInfo(void*); +static void onClickBalance(void*); +static void onClickBuyCurrency(void*); +static void onClickHealth(void*); +static void onClickFly(void*); +static void onClickPush(void*); +static void onClickVoice(void*); +static void onClickBuild(void*); +static void onClickScripts(void*); +static void onClickBuyLand(void*); +static void onClickScriptDebug(void*); + std::vector<std::string> LLStatusBar::sDays; std::vector<std::string> LLStatusBar::sMonths; const U32 LLStatusBar::MAX_DATE_STRING_LENGTH = 2000; @@ -106,15 +121,6 @@ LLStatusBar::LLStatusBar(const std::string& name, const LLRect& rect) // build date necessary data (must do after panel built) setupDate(); - mBtnScriptOut = LLUICtrlFactory::getButtonByName( this, "scriptout" ); - mBtnHealth = LLUICtrlFactory::getButtonByName( this, "health" ); - mBtnFly = LLUICtrlFactory::getButtonByName( this, "fly" ); - mBtnBuild = LLUICtrlFactory::getButtonByName( this, "build" ); - mBtnScripts = LLUICtrlFactory::getButtonByName( this, "scripts" ); - mBtnPush = LLUICtrlFactory::getButtonByName( this, "restrictpush" ); - mBtnBuyLand = LLUICtrlFactory::getButtonByName( this, "buyland" ); - mBtnBuyCurrency = LLUICtrlFactory::getButtonByName( this, "buycurrency" ); - mTextParcelName = LLUICtrlFactory::getTextBoxByName( this, "ParcelNameText" ); mTextBalance = LLUICtrlFactory::getTextBoxByName( this, "BalanceText" ); @@ -176,6 +182,7 @@ BOOL LLStatusBar::postBuild() childSetAction("build", onClickBuild, this ); childSetAction("scripts", onClickScripts, this ); childSetAction("restrictpush", onClickPush, this ); + childSetAction("status_voice", onClickVoice, this ); childSetActionTextbox("ParcelNameText", onClickParcelInfo ); childSetActionTextbox("BalanceText", onClickBalance ); @@ -285,13 +292,13 @@ void LLStatusBar::refresh() { childGetRect( "scriptout", buttonRect ); r.setOriginAndSize( x, y, buttonRect.getWidth(), buttonRect.getHeight()); - mBtnScriptOut->setRect(r); - mBtnScriptOut->setVisible(TRUE); + childSetRect("scriptout",r); + childSetVisible("scriptout", true); x += buttonRect.getWidth(); } else { - mBtnScriptOut->setVisible(FALSE); + childSetVisible("scriptout", false); } if ((region && region->getAllowDamage()) || @@ -300,19 +307,19 @@ void LLStatusBar::refresh() // set visibility based on flashing if( mHealthTimer->hasExpired() ) { - mBtnHealth->setVisible( TRUE ); + childSetVisible("health", true); } else { BOOL flash = S32(mHealthTimer->getElapsedSeconds() * ICON_FLASH_FREQUENCY) & 1; - mBtnHealth->setVisible( flash ); + childSetVisible("health", flash); } mTextHealth->setVisible(TRUE); // Health childGetRect( "health", buttonRect ); r.setOriginAndSize( x, y-GRAPHIC_FUDGE, buttonRect.getWidth(), buttonRect.getHeight()); - mBtnHealth->setRect(r); + childSetRect("health", r); x += buttonRect.getWidth(); const S32 health_width = S32( LLFontGL::sSansSerifSmall->getWidth("100%") ); @@ -323,7 +330,7 @@ void LLStatusBar::refresh() else { // invisible if region doesn't allow damage - mBtnHealth->setVisible(FALSE); + childSetVisible("health", false); mTextHealth->setVisible(FALSE); } @@ -332,24 +339,24 @@ void LLStatusBar::refresh() { // No Fly Zone childGetRect( "fly", buttonRect ); - mBtnFly->setVisible(TRUE); + childSetVisible( "fly", true ); r.setOriginAndSize( x, y-GRAPHIC_FUDGE, buttonRect.getWidth(), buttonRect.getHeight()); - mBtnFly->setRect(r); + childSetRect( "fly", r ); x += buttonRect.getWidth(); } else { - mBtnFly->setVisible(FALSE); + childSetVisible("fly", false); } BOOL no_build = parcel && !parcel->getAllowModify(); - mBtnBuild->setVisible( no_build ); + childSetVisible("build", no_build); if (no_build) { childGetRect( "build", buttonRect ); // No Build Zone r.setOriginAndSize( x, y-GRAPHIC_FUDGE, buttonRect.getWidth(), buttonRect.getHeight()); - mBtnBuild->setRect(r); + childSetRect( "build", r ); x += buttonRect.getWidth(); } @@ -361,37 +368,46 @@ void LLStatusBar::refresh() { no_scripts = TRUE; } - mBtnScripts->setVisible( no_scripts ); + childSetVisible("scripts", no_scripts); if (no_scripts) { // No scripts childGetRect( "scripts", buttonRect ); r.setOriginAndSize( x, y-GRAPHIC_FUDGE, buttonRect.getWidth(), buttonRect.getHeight()); - mBtnScripts->setRect(r); + childSetRect( "scripts", r ); x += buttonRect.getWidth(); } BOOL no_region_push = (region && region->getRestrictPushObject()); BOOL no_push = no_region_push || (parcel && parcel->getRestrictPushObject()); - mBtnPush->setVisible( no_push ); + childSetVisible("restrictpush", no_push); if (no_push) { childGetRect( "restrictpush", buttonRect ); - // No Push Zone r.setOriginAndSize( x, y-GRAPHIC_FUDGE, buttonRect.getWidth(), buttonRect.getHeight()); - mBtnPush->setRect(r); + childSetRect( "restrictpush", r ); + x += buttonRect.getWidth(); + } + + BOOL have_voice = gVoiceClient->getAreaVoiceDisabled() ? FALSE : TRUE; + childSetVisible("status_voice", have_voice); + if (have_voice) + { + childGetRect( "status_voice", buttonRect ); + r.setOriginAndSize( x, y-GRAPHIC_FUDGE, buttonRect.getWidth(), buttonRect.getHeight()); + childSetRect( "status_voice", r ); x += buttonRect.getWidth(); } BOOL canBuyLand = parcel && !parcel->isPublic() && gParcelMgr->canAgentBuyParcel(parcel, false); - mBtnBuyLand->setVisible(canBuyLand); + childSetVisible("buyland", canBuyLand); if (canBuyLand) { childGetRect( "buyland", buttonRect ); r.setOriginAndSize( x, y, buttonRect.getWidth(), buttonRect.getHeight()); - mBtnBuyLand->setRect(r); + childSetRect( "buyland", r ); x += buttonRect.getWidth(); } @@ -460,7 +476,7 @@ void LLStatusBar::setVisibleForMouselook(bool visible) mTextTime->setVisible(visible); mSGBandwidth->setVisible(visible); mSGPacketLoss->setVisible(visible); - mBtnBuyCurrency->setVisible(visible); + childSetVisible("buycurrency", visible); setBackgroundVisible(visible); } @@ -569,58 +585,55 @@ S32 LLStatusBar::getSquareMetersLeft() const return mSquareMetersCredit - mSquareMetersCommitted; } -// static -void LLStatusBar::onClickParcelInfo(void* data) +static void onClickParcelInfo(void* data) { gParcelMgr->selectParcelAt(gAgent.getPositionGlobal()); LLFloaterLand::show(); } -// static -void LLStatusBar::onClickBalance(void* data) +static void onClickBalance(void* data) { LLFloaterBuyCurrency::buyCurrency(); } -// static -void LLStatusBar::onClickBuyCurrency(void* data) +static void onClickBuyCurrency(void* data) { LLFloaterBuyCurrency::buyCurrency(); } -// static -void LLStatusBar::onClickHealth(void* ) +static void onClickHealth(void* ) { LLNotifyBox::showXml("NotSafe"); } -// static -void LLStatusBar::onClickScriptDebug(void*) +static void onClickScriptDebug(void*) { LLFloaterScriptDebug::show(LLUUID::null); } -// static -void LLStatusBar::onClickFly(void* ) +static void onClickFly(void* ) { LLNotifyBox::showXml("NoFly"); } -// static -void LLStatusBar::onClickPush(void* ) +static void onClickPush(void* ) { LLNotifyBox::showXml("PushRestricted"); } -// static -void LLStatusBar::onClickBuild(void*) +static void onClickVoice(void* ) +{ + LLNotifyBox::showXml("VoiceAvailablity"); + //LLFirstUse::useVoice(); +} + +static void onClickBuild(void*) { LLNotifyBox::showXml("NoBuild"); } -// static -void LLStatusBar::onClickScripts(void*) +static void onClickScripts(void*) { LLViewerRegion* region = gAgent.getRegion(); if(region && region->getRegionFlags() & REGION_FLAGS_ESTATE_SKIP_SCRIPTS) @@ -637,8 +650,7 @@ void LLStatusBar::onClickScripts(void*) } } -// static -void LLStatusBar::onClickBuyLand(void*) +static void onClickBuyLand(void*) { gParcelMgr->selectParcelAt(gAgent.getPositionGlobal()); gParcelMgr->startBuyLand(); diff --git a/indra/newview/llstatusbar.h b/indra/newview/llstatusbar.h index 94f758f054..2bbf2bab2d 100644 --- a/indra/newview/llstatusbar.h +++ b/indra/newview/llstatusbar.h @@ -61,19 +61,7 @@ public: S32 getSquareMetersCommitted() const; S32 getSquareMetersLeft() const; -protected: - static void onClickParcelInfo(void*); - static void onClickBalance(void*); - static void onClickBuyCurrency(void*); - static void onClickRegionInfo(void*); - static void onClickHealth(void*); - static void onClickFly(void*); - static void onClickPush(void*); - static void onClickBuild(void*); - static void onClickScripts(void*); - static void onClickBuyLand(void*); - static void onClickScriptDebug(void*); - +protected: // simple method to setup the part that holds the date void setupDate(); @@ -82,15 +70,6 @@ protected: LLTextBox *mTextHealth; LLTextBox *mTextTime; - LLButton *mBtnScriptOut; - LLButton *mBtnHealth; - LLButton *mBtnFly; - LLButton *mBtnBuild; - LLButton *mBtnScripts; - LLButton *mBtnPush; - LLButton *mBtnBuyLand; - - LLTextBox* mTextParcelName; LLStatGraph *mSGBandwidth; diff --git a/indra/newview/lltoolbar.cpp b/indra/newview/lltoolbar.cpp index e6cfefa62d..879e99973d 100644 --- a/indra/newview/lltoolbar.cpp +++ b/indra/newview/lltoolbar.cpp @@ -26,6 +26,7 @@ #include "llvoavatar.h" #include "lltooldraganddrop.h" #include "llinventoryview.h" +#include "llfloaterchatterbox.h" #include "llfloaterfriends.h" #include "llfloatersnapshot.h" #include "lltoolmgr.h" @@ -94,15 +95,12 @@ LLToolBar::LLToolBar(const std::string& name, const LLRect& r) BOOL LLToolBar::postBuild() { - childSetAction("im_btn", onClickIM, this); - childSetControlName("im_btn", "ShowIM"); + childSetAction("communicate_btn", onClickCommunicate, this); + childSetControlName("communicate_btn", "ShowCommunicate"); childSetAction("chat_btn", onClickChat, this); childSetControlName("chat_btn", "ChatVisible"); - childSetAction("friends_btn", onClickFriends, this); - childSetControlName("friends_btn", "ShowFriends"); - childSetAction("appearance_btn", onClickAppearance, this); childSetControlName("appearance_btn", ""); @@ -313,24 +311,9 @@ void LLToolBar::refresh() // static -void LLToolBar::onClickIM(void* user_data) +void LLToolBar::onClickCommunicate(void* user_data) { - if(gIMView->getFloaterOpen()) - { - // this is if we want Ctrl-T to be simply a toggle - // gIMView->setFloaterOpen( FALSE ); - // three-state behavior follows - if(gFocusMgr.childHasKeyboardFocus(gIMView->getFloater())) - { - gIMView->setFloaterOpen( FALSE ); - } else { - gIMView->getFloater()->setFocus( TRUE ); - } - } - else - { - gIMView->setFloaterOpen( TRUE ); - } + LLFloaterChatterBox::toggleInstance(LLSD()); } @@ -340,14 +323,6 @@ void LLToolBar::onClickChat(void* user_data) handle_chat(NULL); } - -// static -void LLToolBar::onClickFriends(void*) -{ - LLFloaterFriends::toggle(); -} - - // static void LLToolBar::onClickAppearance(void*) { diff --git a/indra/newview/lltoolbar.h b/indra/newview/lltoolbar.h index f9eee1d4fb..b6e6a073e4 100644 --- a/indra/newview/lltoolbar.h +++ b/indra/newview/lltoolbar.h @@ -47,9 +47,8 @@ public: void refresh(); // callbacks - static void onClickIM(void*); + static void onClickCommunicate(void*); static void onClickChat(void* data); - static void onClickFriends(void* data); static void onClickAppearance(void* data); static void onClickClothing(void* data); static void onClickFly(void*); diff --git a/indra/newview/lltooldraganddrop.cpp b/indra/newview/lltooldraganddrop.cpp index 930d6fa5f2..72a8cefdbd 100644 --- a/indra/newview/lltooldraganddrop.cpp +++ b/indra/newview/lltooldraganddrop.cpp @@ -1219,7 +1219,6 @@ BOOL LLToolDragAndDrop::handleDropTextureProtections(LLViewerObject* hit_obj, return FALSE; } } -std::cout << "ASSET ID: " << new_item->getAssetUUID() << "\n"; hit_obj->updateInventory(new_item, TASK_INVENTORY_ASSET_KEY, true); } else if(!item->getPermissions().allowOperationBy(PERM_TRANSFER, @@ -1233,7 +1232,6 @@ std::cout << "ASSET ID: " << new_item->getAssetUUID() << "\n"; // *FIX: may want to make sure agent can paint hit_obj. // make sure the object has the texture in it's inventory. -std::cout << "ASSET ID: " << new_item->getAssetUUID() << "\n"; hit_obj->updateInventory(new_item, TASK_INVENTORY_ASSET_KEY, true); } return TRUE; diff --git a/indra/newview/llviewercontrol.h b/indra/newview/llviewercontrol.h index 2203df4287..c93b62c75f 100644 --- a/indra/newview/llviewercontrol.h +++ b/indra/newview/llviewercontrol.h @@ -34,9 +34,11 @@ protected: LLTextEditor* mComment; }; +// These functions found in llcontroldef.cpp *TODO: clean this up! //setting variables are declared in this function void declare_settings(); void fixup_settings(); +void settings_setup_listeners(); // saved at end of session extern LLControlGroup gSavedSettings; diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp index bc0be1b5a2..abcdb8cdbd 100644 --- a/indra/newview/llviewermenu.cpp +++ b/indra/newview/llviewermenu.cpp @@ -59,6 +59,8 @@ #include "llfloater.h" #include "llfloaterabout.h" #include "llfloaterbuycurrency.h" +#include "llfloateractivespeakers.h" +#include "llfloateranimpreview.h" #include "llfloateravatarinfo.h" #include "llfloateravatartextures.h" #include "llfloaterbuildoptions.h" @@ -71,6 +73,7 @@ #include "llfloatercustomize.h" #include "llfloaterdirectory.h" #include "llfloatereditui.h" +#include "llfloaterchatterbox.h" #include "llfloaterfriends.h" #include "llfloatergesture.h" #include "llfloatergodtools.h" @@ -2454,7 +2457,7 @@ void set_god_level(U8 god_level) U8 old_god_level = gAgent.getGodLevel(); gAgent.setGodLevel( god_level ); show_debug_menus(); - gIMView->refresh(); + gIMMgr->refresh(); gParcelMgr->notifyObservers(); // Some classifieds change visibility on god mode @@ -2657,7 +2660,7 @@ void request_friendship(const LLUUID& dest_id) } if (!fullname.empty()) { - LLFloaterFriends::requestFriendship(dest_id, fullname); + LLPanelFriends::requestFriendship(dest_id, fullname); LLNotifyBox::showXml("OfferedFriendship", args); } else @@ -5355,7 +5358,7 @@ class LLShowFloater : public view_listener_t } else if (floater_name == "friends") { - LLFloaterFriends::toggle(NULL); + LLFloaterMyFriends::toggleInstance(0); } else if (floater_name == "preferences") { @@ -5367,11 +5370,11 @@ class LLShowFloater : public view_listener_t } else if (floater_name == "chat history") { - LLFloaterChat::toggle(NULL); + LLFloaterChat::toggleInstance(LLSD()); } else if (floater_name == "im") { - LLToolBar::onClickIM(NULL); + LLFloaterChatterBox::toggleInstance(LLSD()); } else if (floater_name == "inventory") { @@ -5480,6 +5483,10 @@ class LLShowFloater : public view_listener_t { LLFloaterAbout::show(NULL); } + else if (floater_name == "active speakers") + { + LLFloaterActiveSpeakers::toggleInstance(LLSD()); + } return true; } }; @@ -5493,7 +5500,7 @@ class LLFloaterVisible : public view_listener_t bool new_value = false; if (floater_name == "friends") { - new_value = LLFloaterFriends::visible(NULL); + new_value = LLFloaterMyFriends::instanceVisible(0); } else if (floater_name == "toolbar") { @@ -5505,7 +5512,7 @@ class LLFloaterVisible : public view_listener_t } else if (floater_name == "im") { - new_value = gIMView && gIMView->mTalkFloater && gIMView->mTalkFloater->getVisible(); + new_value = LLFloaterMyFriends::instanceVisible(0); } else if (floater_name == "mute list") { @@ -5523,6 +5530,10 @@ class LLFloaterVisible : public view_listener_t { new_value = gDebugView->mStatViewp->getVisible(); } + else if (floater_name == "active speakers") + { + new_value = LLFloaterActiveSpeakers::instanceVisible(LLSD()); + } gMenuHolder->findControl(control_name)->setValue(new_value); return true; } @@ -5639,19 +5650,7 @@ class LLShowAgentGroups : public view_listener_t { bool handleEvent(LLPointer<LLEvent> event, const LLSD& userdata) { - LLUUID agent_id; - if (userdata.asString() == "agent") - { - agent_id = gAgent.getID(); - } - else - { - agent_id = userdata.asUUID(); - } - if(agent_id.notNull()) - { - LLFloaterGroups::show(agent_id, LLFloaterGroups::AGENT_GROUPS); - } + LLFloaterMyFriends::toggleInstance(1); return true; } }; @@ -6151,10 +6150,10 @@ class LLAvatarSendIM : public view_listener_t name.append( last->getString() ); } - gIMView->setFloaterOpen(TRUE); + gIMMgr->setFloaterOpen(TRUE); //EInstantMessage type = have_agent_callingcard(gLastHitObjectID) // ? IM_SESSION_ADD : IM_SESSION_CARDLESS_START; - gIMView->addSession(name, + gIMMgr->addSession(name, IM_NOTHING_SPECIAL, avatar->getID()); } diff --git a/indra/newview/llviewermessage.cpp b/indra/newview/llviewermessage.cpp index 6a9b5a8031..7cf9ca9568 100644 --- a/indra/newview/llviewermessage.cpp +++ b/indra/newview/llviewermessage.cpp @@ -51,6 +51,7 @@ #include "llviewercontrol.h" #include "lldrawpool.h" #include "llfirstuse.h" +#include "llfloateractivespeakers.h" #include "llfloaterbuycurrency.h" #include "llfloaterbuyland.h" #include "llfloaterchat.h" @@ -1317,7 +1318,7 @@ void process_improved_im(LLMessageSystem *msg, void **user_data) time_t timestamp = (time_t)t; BOOL is_busy = gAgent.getBusy(); - BOOL is_muted = gMuteListp->isMuted(from_id, name); + BOOL is_muted = gMuteListp->isMuted(from_id, name, LLMute::flagTextChat); BOOL is_linden = gMuteListp->isLinden(name); BOOL is_owned_by_me = FALSE; @@ -1368,7 +1369,7 @@ void process_improved_im(LLMessageSystem *msg, void **user_data) { // return a standard "busy" message, but only do it to online IM // (i.e. not other auto responses and not store-and-forward IM) - if (!gIMView->hasSession(session_id)) + if (!gIMMgr->hasSession(session_id)) { // if there is not a panel for this conversation (i.e. it is a new IM conversation // initiated by the other party) then... @@ -1393,14 +1394,10 @@ void process_improved_im(LLMessageSystem *msg, void **user_data) snprintf(buffer, sizeof(buffer), "%s%s%s", name, separator_string, (message+message_offset)); /* Flawfinder: ignore */ - if(from_id == gAgentID) - { - from_id = LLUUID::null; - } llinfos << "process_improved_im: session_id( " << session_id << " ), from_id( " << from_id << " )" << llendl; // add to IM panel, but do not bother the user - gIMView->addMessage( + gIMMgr->addMessage( session_id, from_id, name, @@ -1450,15 +1447,12 @@ void process_improved_im(LLMessageSystem *msg, void **user_data) formatted_time(timestamp, time_buf)); } snprintf(buffer, sizeof(buffer), "%s%s%s%s", name, separator_string, saved,(message+message_offset)); /* Flawfinder: ignore */ - if(from_id == gAgentID) - { - from_id = LLUUID::null; - } + llinfos << "process_improved_im: session_id( " << session_id << " ), from_id( " << from_id << " )" << llendl; if (!is_muted || is_linden) { - gIMView->addMessage( + gIMMgr->addMessage( session_id, from_id, name, @@ -1489,14 +1483,14 @@ void process_improved_im(LLMessageSystem *msg, void **user_data) case IM_TYPING_START: { LLPointer<LLIMInfo> im_info = new LLIMInfo(gMessageSystem); - gIMView->processIMTypingStart(im_info); + gIMMgr->processIMTypingStart(im_info); } break; case IM_TYPING_STOP: { LLPointer<LLIMInfo> im_info = new LLIMInfo(gMessageSystem); - gIMView->processIMTypingStop(im_info); + gIMMgr->processIMTypingStop(im_info); } break; @@ -1729,12 +1723,9 @@ void process_improved_im(LLMessageSystem *msg, void **user_data) return; } - // System messages, specifically "Foo Bar has left this session" - // are not shown unless you actually have that session open. - // Band-aid. JC - if (offline == IM_ONLINE - && chat.mFromName == SYSTEM_FROM - && !gIMView->hasSession(session_id)) + // Only show messages if we have a session open (which + // should happen after you get an "invitation" + if ( !gIMMgr->hasSession(session_id) ) { return; } @@ -1754,10 +1745,9 @@ void process_improved_im(LLMessageSystem *msg, void **user_data) BOOL is_this_agent = FALSE; if(from_id == gAgentID) { - from_id = LLUUID::null; is_this_agent = TRUE; } - gIMView->addMessage( + gIMMgr->addMessage( session_id, from_id, name, @@ -1808,7 +1798,7 @@ void process_improved_im(LLMessageSystem *msg, void **user_data) else { // original code resumes - gIMView->addMessage(session_id, from_id, name, message); + gIMMgr->addMessage(session_id, from_id, name, message); } break; @@ -2074,7 +2064,7 @@ void process_offer_callingcard(LLMessageSystem* msg, void**) if(!source_name.empty()) { if (gAgent.getBusy() - || gMuteListp->isMuted(source_id, source_name)) + || gMuteListp->isMuted(source_id, source_name, LLMute::flagTextChat)) { // automatically decline offer callingcard_offer_callback(1, (void*)offerdata); @@ -2150,7 +2140,7 @@ void process_chat_from_simulator(LLMessageSystem *msg, void **user_data) BOOL is_linden = FALSE; if (gMuteListp) { - is_muted = gMuteListp->isMuted(from_id, from_name) + is_muted = gMuteListp->isMuted(from_id, from_name, LLMute::flagTextChat) || gMuteListp->isMuted(owner_id); is_linden = gMuteListp->isLinden(from_name); } @@ -2174,16 +2164,15 @@ void process_chat_from_simulator(LLMessageSystem *msg, void **user_data) gWorldPointer->mPartSim.addPartSource(psc); } - // only pay attention to other people chatting + // record last audible utterance if (is_audible - && (is_linden || (!is_muted && !is_busy)) - && chatter != gAgent.getAvatarObject()) + && (is_linden || (!is_muted && !is_busy))) { - gAgent.heardChat(chat); - if (ll_rand(2) == 0) + if (chat.mChatType != CHAT_TYPE_START + && chat.mChatType != CHAT_TYPE_STOP) { - gAgent.setLookAt(LOOKAT_TARGET_AUTO_LISTEN, chatter, LLVector3::zero); - } + gAgent.heardChat(chat.mFromID); + } } is_owned_by_me = chatter->permYouOwner(); @@ -2214,6 +2203,8 @@ void process_chat_from_simulator(LLMessageSystem *msg, void **user_data) // Look for the start of typing so we can put "..." in the bubbles. if (CHAT_TYPE_START == chat.mChatType) { + gLocalSpeakerMgr->setSpeakerTyping(from_id, TRUE); + // Might not have the avatar constructed yet, eg on login. if (chatter && chatter->isAvatar()) { @@ -2223,6 +2214,8 @@ void process_chat_from_simulator(LLMessageSystem *msg, void **user_data) } else if (CHAT_TYPE_STOP == chat.mChatType) { + gLocalSpeakerMgr->setSpeakerTyping(from_id, FALSE); + // Might not have the avatar constructed yet, eg on login. if (chatter && chatter->isAvatar()) { @@ -2234,6 +2227,7 @@ void process_chat_from_simulator(LLMessageSystem *msg, void **user_data) // We have a real utterance now, so can stop showing "..." and proceed. if (chatter && chatter->isAvatar()) { + gLocalSpeakerMgr->setSpeakerTyping(from_id, FALSE); ((LLVOAvatar*)chatter)->stopTyping(); if (!is_muted && !is_busy) @@ -2362,7 +2356,7 @@ void process_teleport_start(LLMessageSystem *msg, void**) gTeleportDisplay = TRUE; gAgent.setTeleportState( LLAgent::TELEPORT_START ); make_ui_sound("UISndTeleportOut"); - + // Don't call LLFirstUse::useTeleport here because this could be // due to being killed, which would send you home, not to a Telehub } @@ -2743,7 +2737,7 @@ void process_agent_movement_complete(LLMessageSystem* msg, void**) { avatarp->mFootPlane.clearVec(); } - + // reset always run status msg->newMessageFast(_PREHASH_SetAlwaysRun); msg->nextBlockFast(_PREHASH_AgentData); @@ -3207,7 +3201,7 @@ void process_sound_trigger(LLMessageSystem *msg, void **) if (!gParcelMgr->canHearSound(pos_global)) return; // Don't play sounds triggered by someone you muted. - if (gMuteListp->isMuted(owner_id)) return; + if (gMuteListp->isMuted(owner_id, LLMute::flagObjectSounds)) return; // Don't play sounds from an object you muted if (gMuteListp->isMuted(object_id)) return; @@ -3219,7 +3213,8 @@ void process_sound_trigger(LLMessageSystem *msg, void **) return; } - gAudiop->triggerSound(sound_id, owner_id, gain, pos_global); + F32 volume = gain * gSavedSettings.getF32("AudioLevelSFX"); + gAudiop->triggerSound(sound_id, owner_id, volume, pos_global); } void process_preload_sound(LLMessageSystem *msg, void **user_data) @@ -3241,7 +3236,7 @@ void process_preload_sound(LLMessageSystem *msg, void **user_data) if (!objectp) return; if (gMuteListp->isMuted(object_id)) return; - if (gMuteListp->isMuted(owner_id)) return; + if (gMuteListp->isMuted(owner_id, LLMute::flagObjectSounds)) return; LLAudioSource *sourcep = objectp->getAudioSource(owner_id); if (!sourcep) return; @@ -3279,7 +3274,7 @@ void process_attached_sound(LLMessageSystem *msg, void **user_data) if (gMuteListp->isMuted(object_id)) return; - if (gMuteListp->isMuted(owner_id)) return; + if (gMuteListp->isMuted(owner_id, LLMute::flagObjectSounds)) return; objectp->setAttachedSound(sound_id, owner_id, gain, flags); } diff --git a/indra/newview/llviewerobject.cpp b/indra/newview/llviewerobject.cpp index e53f779473..bbb69594da 100644 --- a/indra/newview/llviewerobject.cpp +++ b/indra/newview/llviewerobject.cpp @@ -156,6 +156,7 @@ LLViewerObject::LLViewerObject(const LLUUID &id, const LLPCode pcode, LLViewerRe mLatestRecvPacketID(0), mData(NULL), mAudioSourcep(NULL), + mAudioGain(1.f), mAppAngle(0.f), mPixelArea(1024.f), mInventory(NULL), @@ -2697,6 +2698,12 @@ void LLViewerObject::setPixelAreaAndAngle(LLAgent &agent) BOOL LLViewerObject::updateLOD() { + // Update volume of looping sounds + if (mAudioSourcep && mAudioSourcep->isLoop()) + { + F32 volume = mAudioGain * gSavedSettings.getF32("AudioLevelSFX"); + mAudioSourcep->setGain(volume); + } return FALSE; } @@ -4037,7 +4044,7 @@ void LLViewerObject::unpackParticleSource(const S32 block_num, const LLUUID& own { LLPointer<LLViewerPartSourceScript> pss = LLViewerPartSourceScript::unpackPSS(this, NULL, block_num); //If the owner is muted, don't create the system - if(gMuteListp->isMuted(owner_id)) return; + if(gMuteListp->isMuted(owner_id, LLMute::flagParticles)) return; // We need to be able to deal with a particle source that hasn't changed, but still got an update! if (pss) @@ -4086,7 +4093,7 @@ void LLViewerObject::unpackParticleSource(LLDataPacker &dp, const LLUUID& owner_ { LLPointer<LLViewerPartSourceScript> pss = LLViewerPartSourceScript::unpackPSS(this, NULL, dp); //If the owner is muted, don't create the system - if(gMuteListp->isMuted(owner_id)) return; + if(gMuteListp->isMuted(owner_id, LLMute::flagParticles)) return; // We need to be able to deal with a particle source that hasn't changed, but still got an update! if (pss) { @@ -4204,7 +4211,9 @@ void LLViewerObject::setAttachedSound(const LLUUID &audio_uuid, const LLUUID& ow if (mAudioSourcep) { BOOL queue = flags & LL_SOUND_FLAG_QUEUE; - mAudioSourcep->setGain(gain); + mAudioGain = gain; + F32 volume = gain * gSavedSettings.getF32("AudioLevelSFX"); + mAudioSourcep->setGain(volume); mAudioSourcep->setLoop(flags & LL_SOUND_FLAG_LOOP); mAudioSourcep->setSyncMaster(flags & LL_SOUND_FLAG_SYNC_MASTER); mAudioSourcep->setSyncSlave(flags & LL_SOUND_FLAG_SYNC_SLAVE); @@ -4239,12 +4248,12 @@ void LLViewerObject::adjustAudioGain(const F32 gain) { return; } - - if (!mAudioSourcep) + if (mAudioSourcep) { - return; + mAudioGain = gain; + F32 volume = mAudioGain * gSavedSettings.getF32("AudioLevelSFX"); + mAudioSourcep->setGain(volume); } - mAudioSourcep->setGain(gain); } //---------------------------------------------------------------------------- diff --git a/indra/newview/llviewerobject.h b/indra/newview/llviewerobject.h index f6aaadd40b..f52466cc2d 100644 --- a/indra/newview/llviewerobject.h +++ b/indra/newview/llviewerobject.h @@ -544,8 +544,9 @@ protected: U8* mData; LLPointer<LLViewerPartSourceScript> mPartSourcep; // Particle source associated with this object. - LLAudioSourceVO *mAudioSourcep; - + LLAudioSourceVO* mAudioSourcep; + F32 mAudioGain; + F32 mAppAngle; // Apparent visual arc in degrees F32 mPixelArea; // Apparent area in pixels diff --git a/indra/newview/llviewerparcelmgr.cpp b/indra/newview/llviewerparcelmgr.cpp index ed97cd3ec2..017641b278 100644 --- a/indra/newview/llviewerparcelmgr.cpp +++ b/indra/newview/llviewerparcelmgr.cpp @@ -1772,13 +1772,10 @@ void optionally_start_music(const LLString& music_url) // now only play music when you enter a new parcel if the control is in PLAY state // changed as part of SL-4878 - if ( gOverlayBar->getMusicRemoteControl ()->getTransportState () == LLMediaRemoteCtrl::Play ) + if ( gOverlayBar && gOverlayBar->musicPlaying() ) { - if (gAudiop) - { - gAudiop->startInternetStream(music_url.c_str()); - } - }; + LLOverlayBar::musicPlay(NULL); + } } } @@ -1791,12 +1788,7 @@ void callback_start_music(S32 option, void* data) { gSavedSettings.setBOOL("AudioStreamingMusic", TRUE); llinfos << "Starting first parcel music " << music_url << llendl; - if (gAudiop) - { - gAudiop->startInternetStream(music_url->c_str()); - LLMediaRemoteCtrl* ctrl = gOverlayBar->getMusicRemoteControl(); - ctrl->setTransportState( LLMediaRemoteCtrl::Play, FALSE ); - } + LLOverlayBar::musicPlay(NULL); } else { diff --git a/indra/newview/llviewerregion.cpp b/indra/newview/llviewerregion.cpp index 4040a6d21b..c94aec8801 100644 --- a/indra/newview/llviewerregion.cpp +++ b/indra/newview/llviewerregion.cpp @@ -1254,9 +1254,10 @@ void LLViewerRegion::setSeedCapability(const std::string& url) capabilityNames.append("UntrustedSimulatorMessage"); capabilityNames.append("ParcelVoiceInfoRequest"); capabilityNames.append("ChatSessionRequest"); + capabilityNames.append("ProvisionVoiceAccountRequest"); llinfos << "posting to seed " << url << llendl; - + LLHTTPClient::post(url, capabilityNames, BaseCapabilitiesComplete::build(this)); } diff --git a/indra/newview/llviewertexteditor.cpp b/indra/newview/llviewertexteditor.cpp index 3969230197..2fff820602 100644 --- a/indra/newview/llviewertexteditor.cpp +++ b/indra/newview/llviewertexteditor.cpp @@ -1247,8 +1247,8 @@ void LLViewerTextEditor::openEmbeddedSound( LLInventoryItem* item ) const F32 SOUND_GAIN = 1.0f; if(gAudiop) { - gAudiop->triggerSound( - item->getAssetUUID(), gAgentID, SOUND_GAIN, lpos_global); + F32 volume = SOUND_GAIN * gSavedSettings.getF32("AudioLevelSFX"); + gAudiop->triggerSound(item->getAssetUUID(), gAgentID, volume, lpos_global); } showCopyToInvDialog( item ); } @@ -1417,7 +1417,7 @@ LLView* LLViewerTextEditor::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlF LLViewerTextEditor* text_editor = new LLViewerTextEditor(name, rect, max_text_length, - text, + "", font, allow_embedded_items); @@ -1433,7 +1433,18 @@ LLView* LLViewerTextEditor::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlF node->getAttributeBOOL("hide_scrollbar",hide_scrollbar); text_editor->setHideScrollbarForShortDocs(hide_scrollbar); + BOOL hide_border = !text_editor->mBorder->getVisible(); + node->getAttributeBOOL("hide_border", hide_border); + text_editor->setBorderVisible(!hide_border); + + BOOL parse_html = text_editor->mParseHTML; + node->getAttributeBOOL("allow_html", parse_html); + text_editor->setParseHTML(parse_html); + text_editor->initFromXML(node, parent); + // add text after all parameters have been set + text_editor->appendStyledText(text, FALSE, FALSE, NULL); + return text_editor; } diff --git a/indra/newview/llviewerwindow.cpp b/indra/newview/llviewerwindow.cpp index dd2c1c6d76..dbce01246f 100644 --- a/indra/newview/llviewerwindow.cpp +++ b/indra/newview/llviewerwindow.cpp @@ -19,6 +19,8 @@ #include "llviewercamera.h" //#include "imdebug.h" +#include "llvoiceclient.h" // for push-to-talk button handling + #ifdef SABINRIG #include "cbw.h" #endif //SABINRIG @@ -67,9 +69,11 @@ #include "llfeaturemanager.h" #include "llfilepicker.h" #include "llfloater.h" +#include "llfloateractivespeakers.h" #include "llfloaterbuildoptions.h" #include "llfloaterbuyland.h" #include "llfloaterchat.h" +#include "llfloaterchatterbox.h" #include "llfloatercustomize.h" #include "llfloatereditui.h" // HACK JAMESDEBUG for ui editor #include "llfloaterland.h" @@ -588,6 +592,7 @@ BOOL LLViewerWindow::handleMouseDown(LLWindow *window, LLCoordGL pos, MASK mask // Hide tooltips on mousedown if( mToolTip ) { + mToolTipBlocked = TRUE; mToolTip->setVisible( FALSE ); } @@ -1088,6 +1093,21 @@ BOOL LLViewerWindow::handleRightMouseUp(LLWindow *window, LLCoordGL pos, MASK m return TRUE; } +BOOL LLViewerWindow::handleMiddleMouseDown(LLWindow *window, LLCoordGL pos, MASK mask) +{ + gVoiceClient->middleMouseState(true); + + // Always handled as far as the OS is concerned. + return TRUE; +} + +BOOL LLViewerWindow::handleMiddleMouseUp(LLWindow *window, LLCoordGL pos, MASK mask) +{ + gVoiceClient->middleMouseState(false); + + // Always handled as far as the OS is concerned. + return TRUE; +} void LLViewerWindow::handleMouseMove(LLWindow *window, LLCoordGL pos, MASK mask) { @@ -1104,7 +1124,8 @@ void LLViewerWindow::handleMouseMove(LLWindow *window, LLCoordGL pos, MASK mask LLCoordGL prev_saved_mouse_point = mCurrentMousePoint; LLCoordGL mouse_point(x, y); saveLastMouse(mouse_point); - BOOL mouse_actually_moved = (prev_saved_mouse_point.mX != mCurrentMousePoint.mX) || (prev_saved_mouse_point.mY != mCurrentMousePoint.mY); + BOOL mouse_actually_moved = !gFocusMgr.getMouseCapture() && // mouse is not currenty captured + ((prev_saved_mouse_point.mX != mCurrentMousePoint.mX) || (prev_saved_mouse_point.mY != mCurrentMousePoint.mY)); // mouse moved from last recorded position gMouseIdleTimer.reset(); @@ -1216,6 +1237,9 @@ void LLViewerWindow::handleFocusLost(LLWindow *window) BOOL LLViewerWindow::handleTranslatedKeyDown(KEY key, MASK mask, BOOL repeated) { + // Let the voice chat code check for its PTT key. Note that this never affects event processing. + gVoiceClient->keyDown(key, mask); + if (gAwayTimer.getElapsedTimeF32() > MIN_AFK_TIME) { gAgent.clearAFK(); @@ -1235,6 +1259,9 @@ BOOL LLViewerWindow::handleTranslatedKeyDown(KEY key, MASK mask, BOOL repeated) BOOL LLViewerWindow::handleTranslatedKeyUp(KEY key, MASK mask) { + // Let the voice chat code check for its PTT key. Note that this never affects event processing. + gVoiceClient->keyUp(key, mask); + return FALSE; } @@ -1277,16 +1304,7 @@ BOOL LLViewerWindow::handleActivate(LLWindow *window, BOOL activated) } // Unmute audio - if (!gSavedSettings.getBOOL("MuteAudio")) - { - if (gAudiop) gAudiop->setMuted(FALSE); - F32 volume = gSavedSettings.getF32("MediaAudioVolume"); - if(LLMediaEngine::getInstance()) - { - LLMediaEngine::getInstance()->setVolume(volume); - LLMediaEngine::updateClass(volume); - } - } + audio_update_volume(); } else { @@ -1301,14 +1319,7 @@ BOOL LLViewerWindow::handleActivate(LLWindow *window, BOOL activated) stopGL(); } // Mute audio - if (gSavedSettings.getBOOL("MuteWhenMinimized")) - { - llinfos << "Muting audio on minimize" << llendl; - if (gAudiop) gAudiop->setMuted(TRUE); - F32 volume = 0.f; - LLMediaEngine::getInstance()->setVolume(volume); - LLMediaEngine::updateClass(volume); - } + audio_update_volume(); } return TRUE; } @@ -1708,7 +1719,7 @@ void LLViewerWindow::initBase() LLRect notify_rect = full_window; //notify_rect.mTop -= 24; notify_rect.mBottom += STATUS_BAR_HEIGHT; - gNotifyBoxView = new LLNotifyBoxView("notify", notify_rect, FALSE, FOLLOWS_ALL); + gNotifyBoxView = new LLNotifyBoxView("notify_container", notify_rect, FALSE, FOLLOWS_ALL); mRootView->addChild(gNotifyBoxView, -2); // Tooltips go above floaters @@ -1862,16 +1873,12 @@ void LLViewerWindow::initWorldUI() LLFloaterMove::show(NULL); } - // Must have one global chat floater so it can actually store - // the history. JC - gFloaterChat = new LLFloaterChat(); - gFloaterChat->setVisible( FALSE ); - - if ( gSavedPerAccountSettings.getBOOL("LogShowHistory") ) gFloaterChat->loadHistory(); + gIMMgr = LLIMMgr::getInstance(); - gIMView = new LLIMView("gIMView", LLRect() ); - gIMView->setFollowsAll(); - mRootView->addChild(gIMView); + if ( gSavedPerAccountSettings.getBOOL("LogShowHistory") ) + { + LLFloaterChat::getInstance(LLSD())->loadHistory(); + } LLRect morph_view_rect = full_window; morph_view_rect.stretch( -STATUS_BAR_HEIGHT ); @@ -1921,6 +1928,7 @@ void LLViewerWindow::initWorldUI() // sync bg color with menu bar gStatusBar->setBackgroundColor( gMenuBarView->getBackgroundColor() ); + LLFloaterChatterBox::createInstance(LLSD()); gViewerWindow->getRootView()->addChild(gStatusBar); @@ -1950,13 +1958,12 @@ LLViewerWindow::~LLViewerWindow() gFloaterTools = NULL; gStatusBar = NULL; gFloaterCamera = NULL; - gIMView = NULL; + gIMMgr = NULL; gHoverView = NULL; gFloaterView = NULL; gMorphView = NULL; - gFloaterChat = NULL; gFloaterMute = NULL; gFloaterMap = NULL; diff --git a/indra/newview/llviewerwindow.h b/indra/newview/llviewerwindow.h index 91bf0d9918..bb60c8283e 100644 --- a/indra/newview/llviewerwindow.h +++ b/indra/newview/llviewerwindow.h @@ -65,6 +65,8 @@ public: /*virtual*/ void handleQuit(LLWindow *window); /*virtual*/ BOOL handleRightMouseDown(LLWindow *window, LLCoordGL pos, MASK mask); /*virtual*/ BOOL handleRightMouseUp(LLWindow *window, LLCoordGL pos, MASK mask); + /*virtual*/ BOOL handleMiddleMouseDown(LLWindow *window, LLCoordGL pos, MASK mask); + /*virtual*/ BOOL handleMiddleMouseUp(LLWindow *window, LLCoordGL pos, MASK mask); /*virtual*/ void handleMouseMove(LLWindow *window, LLCoordGL pos, MASK mask); /*virtual*/ void handleMouseLeave(LLWindow *window); /*virtual*/ void handleResize(LLWindow *window, S32 x, S32 y); diff --git a/indra/newview/llvoavatar.cpp b/indra/newview/llvoavatar.cpp index 1af8efaf79..60cce34b72 100644 --- a/indra/newview/llvoavatar.cpp +++ b/indra/newview/llvoavatar.cpp @@ -96,6 +96,12 @@ //#include "vtune/vtuneapi.h" +//Ventrella +#include "llgesturemgr.h" //needed to trigger the voice gestculations +#include "llvoicevisualizer.h" +#include "llvoiceclient.h" +//end Ventrella + // Direct imports, evil extern LLSky gSky; extern void set_avatar_character(void* charNameArg); @@ -127,9 +133,7 @@ const F32 PELVIS_LAG_WALKING = 0.4f; // ...while walking const F32 PELVIS_LAG_MOUSELOOK = 0.15f; const F32 MOUSELOOK_PELVIS_FOLLOW_FACTOR = 0.5f; -//Ventrella const F32 PELVIS_LAG_WHEN_FOLLOW_CAM_IS_ON = 0.0001f; // not zero! - something gets divided by this! -//end Ventrella #define PELVIS_ROT_THRESHOLD_SLOW 60.0f // amount of deviation allowed between #define PELVIS_ROT_THRESHOLD_FAST 2.0f // the pelvis and the view direction @@ -201,10 +205,6 @@ const F32 CHAT_FADE_TIME = 8.0; const F32 BUBBLE_CHAT_TIME = CHAT_FADE_TIME * 3.f; const S32 MAX_BUBBLES = 7; - -const bool USING_VENTRELLA_AVATAR_MOTION_TEST = false; - - S32 LLVOAvatar::sMaxVisible = 50; LLVOAvatar::ETextureIndex LLVOAvatar::sBakedTextureIndices[BAKED_TEXTURE_COUNT] = @@ -435,121 +435,16 @@ public: // called after parameters have been set // must return true to indicate success and be available for activation virtual LLMotionInitStatus onInitialize(LLCharacter *character) - { - //Ventrella - // I'm replacing the code below because I need to change - // the logic in order to add other body parts - /* + { mCharacter = character; - - if (!mChestState.setJoint( character->getJoint("mChest"))) - { - return STATUS_FAILURE; - } - - mChestState.setUsage(LLJointState::ROT); - - addJointState( &mChestState ); - return STATUS_SUCCESS; - */ - - bool success = true; if ( !mChestState.setJoint( character->getJoint( "mChest" ) ) ) { success = false; } - if ( USING_VENTRELLA_AVATAR_MOTION_TEST ) - { - if ( !mNeckState.setJoint ( character->getJoint( "mNeck" )) ) { success = false; } - - if ( !mCollarLeftState.setJoint ( character->getJoint( "mCollarLeft" )) ) { success = false; } - if ( !mShoulderLeftState.setJoint ( character->getJoint( "mShoulderLeft" )) ) { success = false; } - if ( !mElbowLeftState.setJoint ( character->getJoint( "mElbowLeft" )) ) { success = false; } - if ( !mWristLeftState.setJoint ( character->getJoint( "mWristLeft" )) ) { success = false; } - - if ( !mCollarRightState.setJoint ( character->getJoint( "mCollarRight" )) ) { success = false; } - if ( !mShoulderRightState.setJoint ( character->getJoint( "mShoulderRight" )) ) { success = false; } - if ( !mElbowRightState.setJoint ( character->getJoint( "mElbowRight" )) ) { success = false; } - if ( !mWristRightState.setJoint ( character->getJoint( "mWristRight" )) ) { success = false; } - - if ( !mHipLeftState.setJoint ( character->getJoint( "mHipLeft" )) ) { success = false; } - if ( !mKneeLeftState.setJoint ( character->getJoint( "mKneeLeft" )) ) { success = false; } - if ( !mAnkleLeftState.setJoint ( character->getJoint( "mAnkleLeft" )) ) { success = false; } - - if ( !mHipRightState.setJoint ( character->getJoint( "mHipRight" )) ) { success = false; } - if ( !mKneeRightState.setJoint ( character->getJoint( "mKneeRight" )) ) { success = false; } - if ( !mAnkleRightState.setJoint ( character->getJoint( "mAnkleRight" )) ) { success = false; } - } - if ( success ) { mChestState.setUsage(LLJointState::ROT); addJointState( &mChestState ); - - if ( USING_VENTRELLA_AVATAR_MOTION_TEST ) - { - //------------------------------------------- - // neck - //------------------------------------------- - mNeckState.setUsage(LLJointState::ROT); - addJointState( &mNeckState ); - - //------------------------------------------- - // left arm - //------------------------------------------- - mCollarLeftState.setUsage(LLJointState::ROT); - addJointState( &mCollarLeftState ); - - mShoulderLeftState.setUsage(LLJointState::ROT); - addJointState( &mShoulderLeftState ); - - mElbowLeftState.setUsage(LLJointState::ROT); - addJointState( &mElbowLeftState ); - - mWristLeftState.setUsage(LLJointState::ROT); - addJointState( &mWristLeftState ); - - - //------------------------------------------- - // right arm - //------------------------------------------- - mCollarRightState.setUsage(LLJointState::ROT); - addJointState( &mCollarRightState ); - - mShoulderRightState.setUsage(LLJointState::ROT); - addJointState( &mShoulderRightState ); - - mElbowRightState.setUsage(LLJointState::ROT); - addJointState( &mElbowRightState ); - - mWristRightState.setUsage(LLJointState::ROT); - addJointState( &mWristRightState ); - - //------------------------------------------- - // left leg - //------------------------------------------- - mHipLeftState.setUsage(LLJointState::ROT); - addJointState( &mHipLeftState ); - - mKneeLeftState.setUsage(LLJointState::ROT); - addJointState( &mKneeLeftState ); - - mAnkleLeftState.setUsage(LLJointState::ROT); - addJointState( &mAnkleLeftState ); - - - //------------------------------------------- - // right leg - //------------------------------------------- - mHipRightState.setUsage(LLJointState::ROT); - addJointState( &mHipRightState ); - - mKneeRightState.setUsage(LLJointState::ROT); - addJointState( &mKneeRightState ); - - mAnkleRightState.setUsage(LLJointState::ROT); - addJointState( &mAnkleRightState ); - } } if ( success ) @@ -560,7 +455,6 @@ public: { return STATUS_FAILURE; } - //end Ventrella } // called when a motion is activated @@ -579,37 +473,9 @@ public: mChestState.setRotation(LLQuaternion(breathe_amt, LLVector3(0.f, 1.f, 0.f))); - //Ventrella - if ( USING_VENTRELLA_AVATAR_MOTION_TEST ) - { - F32 wave = ( sinf ( time * 2.0f ) * 0.5f ); - - mChestState.setRotation ( LLQuaternion( wave, LLVector3( -1.0f, 0.0f, 0.0f ) ) ); - - mCollarLeftState.setRotation ( LLQuaternion( wave, LLVector3( 1.0f, 0.0f, 0.0f ) ) ); - mShoulderLeftState.setRotation ( LLQuaternion( wave, LLVector3( 1.0f, 0.0f, 0.0f ) ) ); - mElbowLeftState.setRotation ( LLQuaternion( wave, LLVector3( 0.0f, 0.0f, 1.0f ) ) ); - mWristLeftState.setRotation ( LLQuaternion( wave, LLVector3( 1.0f, 0.0f, 0.0f ) ) ); - - mCollarRightState.setRotation ( LLQuaternion( wave, LLVector3( -1.0f, 0.0f, 0.0f ) ) ); - mShoulderRightState.setRotation ( LLQuaternion( wave, LLVector3( 1.0f, 0.0f, 0.0f ) ) ); - mElbowRightState.setRotation ( LLQuaternion( wave, LLVector3( 0.0f, 0.0f, 1.0f ) ) ); - mWristRightState.setRotation ( LLQuaternion( wave, LLVector3( 1.0f, 0.0f, 0.0f ) ) ); - - mHipLeftState.setRotation ( LLQuaternion( wave, LLVector3( 0.0f, 1.0f, 0.0f ) ) ); - mKneeLeftState.setRotation ( LLQuaternion( wave, LLVector3( 0.0f, -1.0f, 0.0f ) ) ); - mAnkleLeftState.setRotation ( LLQuaternion( wave, LLVector3( 0.0f, 1.0f, 0.0f ) ) ); - - mHipRightState.setRotation ( LLQuaternion( wave, LLVector3( 0.0f, 1.0f, 0.0f ) ) ); - mKneeRightState.setRotation ( LLQuaternion( wave, LLVector3( 0.0f, -1.0f, 0.0f ) ) ); - mAnkleRightState.setRotation ( LLQuaternion( wave, LLVector3( 0.0f, 1.0f, 0.0f ) ) ); - } - //end Ventrella - return TRUE; } - // called when a motion is deactivated virtual void onDeactivate() {} @@ -617,26 +483,7 @@ public: //------------------------------------------------------------------------- // joint states to be animated //------------------------------------------------------------------------- - LLJointState mChestState; - - //Ventrella - LLJointState mNeckState; - LLJointState mCollarLeftState; - LLJointState mShoulderLeftState; - LLJointState mElbowLeftState; - LLJointState mWristLeftState; - LLJointState mCollarRightState; - LLJointState mShoulderRightState; - LLJointState mElbowRightState; - LLJointState mWristRightState; - LLJointState mHipLeftState; - LLJointState mKneeLeftState; - LLJointState mAnkleLeftState; - LLJointState mHipRightState; - LLJointState mKneeRightState; - LLJointState mAnkleRightState; - //end Ventrella - + LLJointState mChestState; F32 mBreatheRate; LLCharacter* mCharacter; }; @@ -799,6 +646,10 @@ LLVOAvatar::LLVOAvatar( LLMemType mt(LLMemType::MTYPE_AVATAR); //VTResume(); // VTune + + // mVoiceVisualizer is created by the hud effects manager and uses the HUD Effects pipeline + bool needsSendToSim = false; // currently, this HUD effect doesn't need to pack and unpack data to do its job + mVoiceVisualizer = ( LLVoiceVisualizer *)gHUDManager->createViewerEffect( LLHUDObject::LL_HUD_EFFECT_VOICE_VISUALIZER, needsSendToSim ); lldebugs << "LLVOAvatar Constructor (0x" << this << ") id:" << mID << llendl; @@ -1049,9 +900,13 @@ LLVOAvatar::LLVOAvatar( createMotion( ANIM_AGENT_CUSTOMIZE_DONE); //VTPause(); // VTune + + //Ventrella + mVoiceVisualizer->setVoiceEnabled( gVoiceClient->getVoiceEnabled( mID ) ); + mCurrentGesticulationLevel = 0; + //END Ventrella } - //------------------------------------------------------------------------ // LLVOAvatar::~LLVOAvatar() //------------------------------------------------------------------------ @@ -1123,6 +978,8 @@ void LLVOAvatar::markDead() sNumVisibleChatBubbles--; } + mVoiceVisualizer->markDead(); + mBeam = NULL; LLViewerObject::markDead(); } @@ -2439,7 +2296,84 @@ BOOL LLVOAvatar::idleUpdate(LLAgent &agent, LLWorld &world, const F64 &time) updateCharacter(agent); + //Ventrella + bool voiceEnabled = gVoiceClient->getVoiceEnabled( mID ) && gVoiceClient->inProximalChannel(); + // disable voice visualizer when in mouselook + mVoiceVisualizer->setVoiceEnabled( voiceEnabled && !(mIsSelf && gAgent.cameraMouselook()) ); + if ( voiceEnabled ) + { + //---------------------------------------------------------------- + // Only do gesture triggering for your own avatar, and only when you're in a proximal channel. + //---------------------------------------------------------------- + if( mIsSelf ) + { + //---------------------------------------------------------------------------------------- + // The following takes the voice signal and uses that to trigger gesticulations. + //---------------------------------------------------------------------------------------- + int lastGesticulationLevel = mCurrentGesticulationLevel; + mCurrentGesticulationLevel = mVoiceVisualizer->getCurrentGesticulationLevel(); + + //--------------------------------------------------------------------------------------------------- + // If "current gesticulation level" changes, we catch this, and trigger the new gesture + //--------------------------------------------------------------------------------------------------- + if ( lastGesticulationLevel != mCurrentGesticulationLevel ) + { + if ( mCurrentGesticulationLevel != VOICE_GESTICULATION_LEVEL_OFF ) + { + LLString gestureString = "unInitialized"; + if ( mCurrentGesticulationLevel == 0 ) { gestureString = "/voicelevel1"; } + else if ( mCurrentGesticulationLevel == 1 ) { gestureString = "/voicelevel2"; } + else if ( mCurrentGesticulationLevel == 2 ) { gestureString = "/voicelevel3"; } + else { printf( "oops - CurrentGesticulationLevel can be only 0, 1, or 2\n" ); } + + // this is the call that Karl S. created for triggering gestures from within the code. + gGestureManager.triggerAndReviseString( gestureString ); + } + } + + } //if( mIsSelf ) + //----------------------------------------------------------------------------------------------------------------- + // If the avatar is speaking, then the voice amplitude signal is passed to the voice visualizer. + // Also, here we trigger voice visualizer start and stop speaking, so it can animate the voice symbol. + // + // Notice the calls to "gAwayTimer.reset()". This resets the timer that determines how long the avatar has been + // "away", so that the avatar doesn't lapse into away-mode (and slump over) while the user is still talking. + //----------------------------------------------------------------------------------------------------------------- + if ( gVoiceClient->getIsSpeaking( mID ) ) + { + if ( ! mVoiceVisualizer->getCurrentlySpeaking() ) + { + mVoiceVisualizer->setStartSpeaking(); + + //printf( "gAwayTimer.reset();\n" ); + } + + mVoiceVisualizer->setSpeakingAmplitude( gVoiceClient->getCurrentPower( mID ) ); + + if( mIsSelf ) + { + gAgent.clearAFK(); + } + } + else + { + if ( mVoiceVisualizer->getCurrentlySpeaking() ) + { + mVoiceVisualizer->setStopSpeaking(); + } + } + + //-------------------------------------------------------------------------------------------- + // here we get the approximate head position and set as sound source for the voice symbol + // (the following version uses a tweak of "mHeadOffset" which handle sitting vs. standing) + //-------------------------------------------------------------------------------------------- + LLVector3 headOffset = LLVector3( 0.0f, 0.0f, mHeadOffset.mV[2] ); + mVoiceVisualizer->setVoiceSourceWorldPosition( mRoot.getWorldPosition() + headOffset ); + + }//if ( voiceEnabled ) + //End Ventrella + if (LLVOAvatar::sJointDebug) { @@ -2449,7 +2383,6 @@ BOOL LLVOAvatar::idleUpdate(LLAgent &agent, LLWorld &world, const F64 &time) LLJoint::sNumUpdates = 0; LLJoint::sNumTouches = 0; - if (gNoRender) { return TRUE; @@ -3252,14 +3185,7 @@ void LLVOAvatar::updateCharacter(LLAgent &agent) LLVector3 pelvisDir( mRoot.getWorldMatrix().getFwdRow4().mV ); F32 pelvis_rot_threshold = clamp_rescale(speed, 0.1f, 1.0f, PELVIS_ROT_THRESHOLD_SLOW, PELVIS_ROT_THRESHOLD_FAST); - - //Ventrella - //if ( gAgent.getCameraMode() == CAMERA_MODE_FOLLOW ) - //{ - // pelvis_rot_threshold = clamp_rescale(speed, 0.1f, 1.0f, 1.0f, 1.0f); - //} - //end Ventrella - + if (self_in_mouselook) { pelvis_rot_threshold *= MOUSELOOK_PELVIS_FOLLOW_FACTOR; @@ -3337,13 +3263,6 @@ void LLVOAvatar::updateCharacter(LLAgent &agent) pelvis_lag_time = PELVIS_LAG_WALKING; } - //Ventrella - //if ( gAgent.getCameraMode() == CAMERA_MODE_FOLLOW ) - //{ - // pelvis_lag_time = PELVIS_LAG_WHEN_FOLLOW_CAM_IS_ON; - //} - //end Ventrella - F32 u = llclamp((deltaTime / pelvis_lag_time), 0.0f, 1.0f); mRoot.setWorldRotation( slerp(u, mRoot.getWorldRotation(), wQv) ); @@ -3461,13 +3380,13 @@ void LLVOAvatar::updateCharacter(LLAgent &agent) // AUDIO_STEP_LO_SPEED, AUDIO_STEP_HI_SPEED, // AUDIO_STEP_LO_GAIN, AUDIO_STEP_HI_GAIN ); - F32 gain = gSavedSettings.getF32("AudioLevelFootsteps"); + F32 gain = .30f * gSavedSettings.getF32("AudioLevelAmbient"); LLUUID& step_sound_id = getStepSound(); LLVector3d foot_pos_global = gAgent.getPosGlobalFromAgent(foot_pos_agent); if (gParcelMgr && gParcelMgr->canHearSound(foot_pos_global) - && gMuteListp && !gMuteListp->isMuted(getID())) + && gMuteListp && !gMuteListp->isMuted(getID(), LLMute::flagObjectSounds)) { gAudiop->triggerSound(step_sound_id, getID(), gain, foot_pos_global); } @@ -3476,6 +3395,32 @@ void LLVOAvatar::updateCharacter(LLAgent &agent) mRoot.updateWorldMatrixChildren(); + // Send the speaker position to the spatialized voice system. + if(mIsSelf) + { + LLMatrix3 rot; + LLVector3d pos; +#if 1 + // character rotation (stable, shouldn't move with animations) + rot = mRoot.getWorldRotation().getMatrix3(); +#else + // actual head rotation (moves with animations, probably a bit too much) + rot.setRows( + LLVector3::x_axis * mSkullp->getWorldRotation(), + LLVector3::y_axis * mSkullp->getWorldRotation(), + LLVector3::z_axis * mSkullp->getWorldRotation()); +#endif + + pos = getPositionGlobal(); + pos += LLVector3d(mHeadOffset); + + // MBW -- XXX -- Setting velocity to 0 for now. May figure it out later... + gVoiceClient->setAvatarPosition( + pos, // position + LLVector3::zero, // velocity + rot); // rotation matrix + } + if (!mDebugText.size() && mText.notNull()) { mText->markDead(); @@ -4398,18 +4343,20 @@ BOOL LLVOAvatar::processSingleAnimationStateChange( const LLUUID& anim_id, BOOL { LLVector3d char_pos_global = gAgent.getPosGlobalFromAgent(getCharacterPosition()); if (gParcelMgr && gParcelMgr->canHearSound(char_pos_global) - && gMuteListp && !gMuteListp->isMuted(getID())) + && gMuteListp && !gMuteListp->isMuted(getID(), LLMute::flagObjectSounds)) { // RN: uncomment this to play on typing sound at fixed volume once sound engine is fixed // to support both spatialized and non-spatialized instances of the same sound //if (mIsSelf) //{ - // gAudiop->triggerSound(LLUUID(gSavedSettings.getString("UISndTyping")), 0.8f); + // F32 volume = gain * gSavedSettings.getF32("AudioLevelUI") + // gAudiop->triggerSound(LLUUID(gSavedSettings.getString("UISndTyping")), volume); //} //else { LLUUID sound_id = LLUUID(gSavedSettings.getString("UISndTyping")); - gAudiop->triggerSound(sound_id, getID(), 1.f, char_pos_global); + F32 volume = gSavedSettings.getF32("AudioLevelSFX"); + gAudiop->triggerSound(sound_id, getID(), volume, char_pos_global); } } } diff --git a/indra/newview/llvoavatar.h b/indra/newview/llvoavatar.h index c98826d15b..0eac13206b 100644 --- a/indra/newview/llvoavatar.h +++ b/indra/newview/llvoavatar.h @@ -32,6 +32,11 @@ #include "llxmltree.h" #include "llwearable.h" +//Ventrella +//#include "llvoiceclient.h" +#include "llvoicevisualizer.h" +//End Ventrella + const S32 VOAVATAR_SCRATCH_TEX_WIDTH = 512; const S32 VOAVATAR_SCRATCH_TEX_HEIGHT = 512; @@ -927,6 +932,17 @@ protected: LLTexLayerSet* getLayerSet(ETextureIndex index) const; LLHost getObjectHost() const; S32 getLocalDiscardLevel( S32 index); + +//Ventrella + //----------------------------------------------------------------------------------------------- + // the Voice Visualizer is responsible for detecting the user's voice signal, and when the + // user speaks, it puts a voice symbol over the avatar's head, and triggering gesticulations + //----------------------------------------------------------------------------------------------- + private: + LLVoiceVisualizer * mVoiceVisualizer; + int mCurrentGesticulationLevel; +//End Ventrella + }; #endif // LL_VO_AVATAR_H diff --git a/indra/newview/llvoiceclient.cpp b/indra/newview/llvoiceclient.cpp new file mode 100644 index 0000000000..0dba3588e5 --- /dev/null +++ b/indra/newview/llvoiceclient.cpp @@ -0,0 +1,4056 @@ +/** + * @file llvoiceclient.cpp + * @brief Implementation of LLVoiceClient class which is the interface to the voice client process. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include <boost/tokenizer.hpp> + +#include "llviewerprecompiledheaders.h" +#include "llvoiceclient.h" + +#include "llsdutil.h" + +#include "llvoavatar.h" +#include "llbufferstream.h" +#include "llfile.h" +#include "expat/expat.h" +#include "llcallbacklist.h" +#include "llviewerregion.h" +#include "llviewernetwork.h" // for gUserServerChoice +#include "llfloateractivespeakers.h" // for LLSpeakerMgr +#include "llbase64.h" +#include "llviewercontrol.h" +#include "llkeyboard.h" +#include "viewer.h" // for gDisconnected, gDisableVoice +#include "llmutelist.h" // to check for muted avatars +#include "llagent.h" +#include "llcachename.h" +#include "llimview.h" // for LLIMMgr +#include "llimpanel.h" // for LLVoiceChannel +#include "llparcel.h" +#include "llviewerparcelmgr.h" +#include "llfirstuse.h" +#include "llviewerwindow.h" + +// for base64 decoding +#include "apr-1/apr_base64.h" + +// for SHA1 hash +#include "apr-1/apr_sha1.h" + +// If we are connecting to agni AND the user's last name is "Linden", join this channel instead of looking up the sim name. +// If we are connecting to agni and the user's last name is NOT "Linden", disable voice. +#define AGNI_LINDENS_ONLY_CHANNEL "SL" +static bool sConnectingToAgni = false; +F32 LLVoiceClient::OVERDRIVEN_POWER_LEVEL = 0.7f; + +const F32 SPEAKING_TIMEOUT = 1.f; + +const int VOICE_MAJOR_VERSION = 1; +const int VOICE_MINOR_VERSION = 0; + +LLVoiceClient *gVoiceClient = NULL; + +// Don't retry connecting to the daemon more frequently than this: +const F32 CONNECT_THROTTLE_SECONDS = 1.0f; + +// Don't send positional updates more frequently than this: +const F32 UPDATE_THROTTLE_SECONDS = 0.1f; + +const F32 LOGIN_RETRY_SECONDS = 10.0f; +const int MAX_LOGIN_RETRIES = 12; + +class LLViewerVoiceAccountProvisionResponder : + public LLHTTPClient::Responder +{ +public: + LLViewerVoiceAccountProvisionResponder(int retries) + { + mRetries = retries; + } + + virtual void error(U32 status, const std::string& reason) + { + if ( mRetries > 0 ) + { + if ( gVoiceClient ) gVoiceClient->requestVoiceAccountProvision( + mRetries - 1); + } + else + { + //TODO: throw an error message? + if ( gVoiceClient ) gVoiceClient->giveUp(); + } + } + + virtual void result(const LLSD& content) + { + if ( gVoiceClient ) + { + gVoiceClient->login( + content["username"].asString(), + content["password"].asString()); + } + } + +private: + int mRetries; +}; + +/** + * @class LLVivoxProtocolParser + * @brief This class helps construct new LLIOPipe specializations + * @see LLIOPipe + * + * THOROUGH_DESCRIPTION + */ +class LLVivoxProtocolParser : public LLIOPipe +{ + LOG_CLASS(LLVivoxProtocolParser); +public: + LLVivoxProtocolParser(); + virtual ~LLVivoxProtocolParser(); + +protected: + /* @name LLIOPipe virtual implementations + */ + //@{ + /** + * @brief Process the data in buffer + */ + virtual EStatus process_impl( + const LLChannelDescriptors& channels, + buffer_ptr_t& buffer, + bool& eos, + LLSD& context, + LLPumpIO* pump); + //@} + + std::string mInput; + + // Expat control members + XML_Parser parser; + int responseDepth; + bool ignoringTags; + bool isEvent; + int ignoreDepth; + + // Members for processing responses. The values are transient and only valid within a call to processResponse(). + int returnCode; + int statusCode; + std::string statusString; + std::string uuidString; + std::string actionString; + std::string connectorHandle; + std::string accountHandle; + std::string sessionHandle; + std::string eventSessionHandle; + + // Members for processing events. The values are transient and only valid within a call to processResponse(). + std::string eventTypeString; + int state; + std::string uriString; + bool isChannel; + std::string nameString; + std::string audioMediaString; + std::string displayNameString; + int participantType; + bool isLocallyMuted; + bool isModeratorMuted; + bool isSpeaking; + int volume; + F32 energy; + + // Members for processing text between tags + std::string textBuffer; + bool accumulateText; + + void reset(); + + void processResponse(std::string tag); + +static void XMLCALL ExpatStartTag(void *data, const char *el, const char **attr); +static void XMLCALL ExpatEndTag(void *data, const char *el); +static void XMLCALL ExpatCharHandler(void *data, const XML_Char *s, int len); + + void StartTag(const char *tag, const char **attr); + void EndTag(const char *tag); + void CharData(const char *buffer, int length); + +}; + +LLVivoxProtocolParser::LLVivoxProtocolParser() +{ + parser = NULL; + parser = XML_ParserCreate(NULL); + + reset(); +} + +void LLVivoxProtocolParser::reset() +{ + responseDepth = 0; + ignoringTags = false; + accumulateText = false; + textBuffer.clear(); +} + +//virtual +LLVivoxProtocolParser::~LLVivoxProtocolParser() +{ + if (parser) + XML_ParserFree(parser); +} + +// virtual +LLIOPipe::EStatus LLVivoxProtocolParser::process_impl( + const LLChannelDescriptors& channels, + buffer_ptr_t& buffer, + bool& eos, + LLSD& context, + LLPumpIO* pump) +{ + LLBufferStream istr(channels, buffer.get()); + std::ostringstream ostr; + while (istr.good()) + { + char buf[1024]; + istr.read(buf, sizeof(buf)); + mInput.append(buf, istr.gcount()); + } + + // MBW -- XXX -- This should no longer be necessary. Or even possible. + // We've read all the data out of the buffer. Make sure it doesn't accumulate. +// buffer->clear(); + + // Look for input delimiter(s) in the input buffer. If one is found, send the message to the xml parser. + int start = 0; + int delim; + while((delim = mInput.find("\n\n\n", start)) != std::string::npos) + { + // Turn this on to log incoming XML + if(0) + { + int foo = mInput.find("Set3DPosition", start); + int bar = mInput.find("ParticipantPropertiesEvent", start); + if(foo != std::string::npos && (foo < delim)) + { + // This is a Set3DPosition response. Don't print it, since these are way too spammy. + } + else if(bar != std::string::npos && (bar < delim)) + { + // This is a ParticipantPropertiesEvent response. Don't print it, since these are way too spammy. + } + else + { + llinfos << "parsing: " << mInput.substr(start, delim - start) << llendl; + } + } + + // Reset internal state of the LLVivoxProtocolParser (no effect on the expat parser) + reset(); + + XML_ParserReset(parser, NULL); + XML_SetElementHandler(parser, ExpatStartTag, ExpatEndTag); + XML_SetCharacterDataHandler(parser, ExpatCharHandler); + XML_SetUserData(parser, this); + XML_Parse(parser, mInput.data() + start, delim - start, false); + + start = delim + 3; + } + + if(start != 0) + mInput = mInput.substr(start); + +// llinfos << "at end, mInput is: " << mInput << llendl; + + if(!gVoiceClient->mConnected) + { + // If voice has been disabled, we just want to close the socket. This does so. + llinfos << "returning STATUS_STOP" << llendl; + return STATUS_STOP; + } + + return STATUS_OK; +} + +void XMLCALL LLVivoxProtocolParser::ExpatStartTag(void *data, const char *el, const char **attr) +{ + if (data) + { + LLVivoxProtocolParser *object = (LLVivoxProtocolParser*)data; + object->StartTag(el, attr); + } +} + +// -------------------------------------------------------------------------------- + +void XMLCALL LLVivoxProtocolParser::ExpatEndTag(void *data, const char *el) +{ + if (data) + { + LLVivoxProtocolParser *object = (LLVivoxProtocolParser*)data; + object->EndTag(el); + } +} + +// -------------------------------------------------------------------------------- + +void XMLCALL LLVivoxProtocolParser::ExpatCharHandler(void *data, const XML_Char *s, int len) +{ + if (data) + { + LLVivoxProtocolParser *object = (LLVivoxProtocolParser*)data; + object->CharData(s, len); + } +} + +// -------------------------------------------------------------------------------- + + +void LLVivoxProtocolParser::StartTag(const char *tag, const char **attr) +{ + // Reset the text accumulator. We shouldn't have strings that are inturrupted by new tags + textBuffer.clear(); + // only accumulate text if we're not ignoring tags. + accumulateText = !ignoringTags; + + if (responseDepth == 0) + { + isEvent = strcmp("Event", tag) == 0; + + if (strcmp("Response", tag) == 0 || isEvent) + { + // Grab the attributes + while (*attr) + { + const char *key = *attr++; + const char *value = *attr++; + + if (strcmp("requestId", key) == 0) + { + uuidString = value; + } + else if (strcmp("action", key) == 0) + { + actionString = value; + } + else if (strcmp("type", key) == 0) + { + eventTypeString = value; + } + } + } + //llinfos << tag << " (" << responseDepth << ")" << llendl; + } + else + { + if (ignoringTags) + { + //llinfos << "ignoring tag " << tag << " (depth = " << responseDepth << ")" << llendl; + } + else + { + //llinfos << tag << " (" << responseDepth << ")" << llendl; + + // Ignore the InputXml stuff so we don't get confused + if (strcmp("InputXml", tag) == 0) + { + ignoringTags = true; + ignoreDepth = responseDepth; + accumulateText = false; + + //llinfos << "starting ignore, ignoreDepth is " << ignoreDepth << llendl; + } + else if (strcmp("CaptureDevices", tag) == 0) + { + gVoiceClient->clearCaptureDevices(); + } + else if (strcmp("RenderDevices", tag) == 0) + { + gVoiceClient->clearRenderDevices(); + } + } + } + responseDepth++; +} + +// -------------------------------------------------------------------------------- + +void LLVivoxProtocolParser::EndTag(const char *tag) +{ + const char *string = textBuffer.c_str(); + bool clearbuffer = true; + + responseDepth--; + + if (ignoringTags) + { + if (ignoreDepth == responseDepth) + { + //llinfos << "end of ignore" << llendl; + ignoringTags = false; + } + else + { + //llinfos << "ignoring tag " << tag << " (depth = " << responseDepth << ")" << llendl; + } + } + + if (!ignoringTags) + { + //llinfos << "processing tag " << tag << " (depth = " << responseDepth << ")" << llendl; + + // Closing a tag. Finalize the text we've accumulated and reset + if (strcmp("ReturnCode", tag) == 0) + returnCode = strtol(string, NULL, 10); + else if (strcmp("StatusCode", tag) == 0) + statusCode = strtol(string, NULL, 10); + else if (strcmp("ConnectorHandle", tag) == 0) + connectorHandle = string; + else if (strcmp("AccountHandle", tag) == 0) + accountHandle = string; + else if (strcmp("SessionHandle", tag) == 0) + { + if (isEvent) + eventSessionHandle = string; + else + sessionHandle = string; + } + else if (strcmp("StatusString", tag) == 0) + statusString = string; + else if (strcmp("State", tag) == 0) + state = strtol(string, NULL, 10); + else if (strcmp("URI", tag) == 0) + uriString = string; + else if (strcmp("IsChannel", tag) == 0) + isChannel = strcmp(string, "true") == 0; + else if (strcmp("Name", tag) == 0) + nameString = string; + else if (strcmp("AudioMedia", tag) == 0) + audioMediaString = string; + else if (strcmp("ChannelName", tag) == 0) + nameString = string; + else if (strcmp("ParticipantURI", tag) == 0) + uriString = string; + else if (strcmp("DisplayName", tag) == 0) + displayNameString = string; + else if (strcmp("AccountName", tag) == 0) + nameString = string; + else if (strcmp("ParticipantTyppe", tag) == 0) + participantType = strtol(string, NULL, 10); + else if (strcmp("IsLocallyMuted", tag) == 0) + isLocallyMuted = strcmp(string, "true") == 0; + else if (strcmp("IsModeratorMuted", tag) == 0) + isModeratorMuted = strcmp(string, "true") == 0; + else if (strcmp("IsSpeaking", tag) == 0) + isSpeaking = strcmp(string, "true") == 0; + else if (strcmp("Volume", tag) == 0) + volume = strtol(string, NULL, 10); + else if (strcmp("Energy", tag) == 0) + energy = (F32)strtod(string, NULL); + else if (strcmp("MicEnergy", tag) == 0) + energy = (F32)strtod(string, NULL); + else if (strcmp("ChannelName", tag) == 0) + nameString = string; + else if (strcmp("ChannelURI", tag) == 0) + uriString = string; + else if (strcmp("ChannelListResult", tag) == 0) + { + gVoiceClient->addChannelMapEntry(nameString, uriString); + } + else if (strcmp("Device", tag) == 0) + { + // This closing tag shouldn't clear the accumulated text. + clearbuffer = false; + } + else if (strcmp("CaptureDevice", tag) == 0) + { + gVoiceClient->addCaptureDevice(textBuffer); + } + else if (strcmp("RenderDevice", tag) == 0) + { + gVoiceClient->addRenderDevice(textBuffer); + } + + if(clearbuffer) + { + textBuffer.clear(); + accumulateText= false; + } + + if (responseDepth == 0) + { + // We finished all of the XML, process the data + processResponse(tag); + } + } +} + +// -------------------------------------------------------------------------------- + +void LLVivoxProtocolParser::CharData(const char *buffer, int length) +{ + /* + This method is called for anything that isn't a tag, which can be text you + want that lies between tags, and a lot of stuff you don't want like file formatting + (tabs, spaces, CR/LF, etc). + + Only copy text if we are in accumulate mode... + */ + if (accumulateText) + textBuffer.append(buffer, length); +} + +// -------------------------------------------------------------------------------- + +void LLVivoxProtocolParser::processResponse(std::string tag) +{ +// llinfos << tag << llendl; + + if (isEvent) + { + if (eventTypeString == "LoginStateChangeEvent") + { + gVoiceClient->loginStateChangeEvent(accountHandle, statusCode, statusString, state); + } + else if (eventTypeString == "SessionNewEvent") + { + gVoiceClient->sessionNewEvent(accountHandle, eventSessionHandle, state, nameString, uriString); + } + else if (eventTypeString == "SessionStateChangeEvent") + { + gVoiceClient->sessionStateChangeEvent(uriString, statusCode, statusString, eventSessionHandle, state, isChannel, nameString); + } + else if (eventTypeString == "ParticipantStateChangeEvent") + { + gVoiceClient->participantStateChangeEvent(uriString, statusCode, statusString, state, nameString, displayNameString, participantType); + + } + else if (eventTypeString == "ParticipantPropertiesEvent") + { + gVoiceClient->participantPropertiesEvent(uriString, statusCode, statusString, isLocallyMuted, isModeratorMuted, isSpeaking, volume, energy); + } + else if (eventTypeString == "AuxAudioPropertiesEvent") + { + gVoiceClient->auxAudioPropertiesEvent(energy); + } + } + else + { + if (actionString == "Connector.Create.1") + { + gVoiceClient->connectorCreateResponse(statusCode, statusString, connectorHandle); + } + else if (actionString == "Account.Login.1") + { + gVoiceClient->loginResponse(statusCode, statusString, accountHandle); + } + else if (actionString == "Session.Create.1") + { + gVoiceClient->sessionCreateResponse(statusCode, statusString, sessionHandle); + } + else if (actionString == "Session.Connect.1") + { + gVoiceClient->sessionConnectResponse(statusCode, statusString); + } + else if (actionString == "Session.Terminate.1") + { + gVoiceClient->sessionTerminateResponse(statusCode, statusString); + } + else if (actionString == "Account.Logout.1") + { + gVoiceClient->logoutResponse(statusCode, statusString); + } + else if (actionString == "Connector.InitiateShutdown.1") + { + gVoiceClient->connectorShutdownResponse(statusCode, statusString); + } + else if (actionString == "Account.ChannelGetList.1") + { + gVoiceClient->channelGetListResponse(statusCode, statusString); + } +/* + else if (actionString == "Connector.AccountCreate.1") + { + + } + else if (actionString == "Connector.MuteLocalMic.1") + { + + } + else if (actionString == "Connector.MuteLocalSpeaker.1") + { + + } + else if (actionString == "Connector.SetLocalMicVolume.1") + { + + } + else if (actionString == "Connector.SetLocalSpeakerVolume.1") + { + + } + else if (actionString == "Session.ListenerSetPosition.1") + { + + } + else if (actionString == "Session.SpeakerSetPosition.1") + { + + } + else if (actionString == "Session.Set3DPosition.1") + { + + } + else if (actionString == "Session.AudioSourceSetPosition.1") + { + + } + else if (actionString == "Session.GetChannelParticipants.1") + { + + } + else if (actionString == "Account.ChannelCreate.1") + { + + } + else if (actionString == "Account.ChannelUpdate.1") + { + + } + else if (actionString == "Account.ChannelDelete.1") + { + + } + else if (actionString == "Account.ChannelCreateAndInvite.1") + { + + } + else if (actionString == "Account.ChannelFolderCreate.1") + { + + } + else if (actionString == "Account.ChannelFolderUpdate.1") + { + + } + else if (actionString == "Account.ChannelFolderDelete.1") + { + + } + else if (actionString == "Account.ChannelAddModerator.1") + { + + } + else if (actionString == "Account.ChannelDeleteModerator.1") + { + + } +*/ + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////// + +class LLVoiceClientPrefsListener: public LLSimpleListener +{ + bool handleEvent(LLPointer<LLEvent> event, const LLSD& userdata) + { + // Note: Ignore the specific event value, look up the ones we want + + gVoiceClient->setVoiceEnabled(gSavedSettings.getBOOL("EnableVoiceChat")); + gVoiceClient->setUsePTT(gSavedSettings.getBOOL("PTTCurrentlyEnabled")); + std::string keyString = gSavedSettings.getString("PushToTalkButton"); + gVoiceClient->setPTTKey(keyString); + gVoiceClient->setPTTIsToggle(gSavedSettings.getBOOL("PushToTalkToggle")); + gVoiceClient->setEarLocation(gSavedSettings.getS32("VoiceEarLocation")); + std::string serverName = gSavedSettings.getString("VivoxDebugServerName"); + gVoiceClient->setVivoxDebugServerName(serverName); + + std::string inputDevice = gSavedSettings.getString("VoiceInputAudioDevice"); + gVoiceClient->setCaptureDevice(inputDevice); + std::string outputDevice = gSavedSettings.getString("VoiceOutputAudioDevice"); + gVoiceClient->setRenderDevice(outputDevice); + + return true; + } +}; +static LLVoiceClientPrefsListener voice_prefs_listener; + +class LLVoiceClientMuteListObserver : public LLMuteListObserver +{ + /* virtual */ void onChange() { gVoiceClient->muteListChanged();} +}; +static LLVoiceClientMuteListObserver mutelist_listener; +static bool sMuteListListener_listening = false; + +/////////////////////////////////////////////////////////////////////////////////////////////// + +class LLVoiceClientCapResponder : public LLHTTPClient::Responder +{ +public: + LLVoiceClientCapResponder(void){}; + + virtual void error(U32 status, const std::string& reason); // called with bad status codes + virtual void result(const LLSD& content); + +private: +}; + +void LLVoiceClientCapResponder::error(U32 status, const std::string& reason) +{ + llwarns << "LLVoiceClientCapResponder::error(" + << status << ": " << reason << ")" + << llendl; +} + +void LLVoiceClientCapResponder::result(const LLSD& content) +{ + LLSD::map_const_iterator iter; + for(iter = content.beginMap(); iter != content.endMap(); ++iter) + { + llinfos << "LLVoiceClientCapResponder::result got " + << iter->first << llendl; + } + + if ( content.has("voice_credentials") ) + { + LLSD voice_credentials = content["voice_credentials"]; + std::string uri; + std::string credentials; + + if ( voice_credentials.has("channel_uri") ) + { + uri = voice_credentials["channel_uri"].asString(); + } + if ( voice_credentials.has("channel_credentials") ) + { + credentials = + voice_credentials["channel_credentials"].asString(); + } + + gVoiceClient->setSpatialChannel(uri, credentials); + } +} + + + +#if LL_WINDOWS +static HANDLE sGatewayHandle = 0; + +static bool isGatewayRunning() +{ + bool result = false; + if(sGatewayHandle != 0) + { + DWORD waitresult = WaitForSingleObject(sGatewayHandle, 0); + if(waitresult != WAIT_OBJECT_0) + { + result = true; + } + } + return result; +} +static void killGateway() +{ + if(sGatewayHandle != 0) + { + TerminateProcess(sGatewayHandle,0); + } +} + +#else // Mac and linux + +static pid_t sGatewayPID = 0; +static bool isGatewayRunning() +{ + bool result = false; + if(sGatewayPID != 0) + { + // A kill with signal number 0 has no effect, just does error checking. It should return an error if the process no longer exists. + if(kill(sGatewayPID, 0) == 0) + { + result = true; + } + } + return result; +} + +static void killGateway() +{ + if(sGatewayPID != 0) + { + kill(sGatewayPID, SIGTERM); + } +} + +#endif + +/////////////////////////////////////////////////////////////////////////////////////////////// + +LLVoiceClient::LLVoiceClient() +{ + gVoiceClient = this; + mWriteInProgress = false; + mAreaVoiceDisabled = false; + mPTT = true; + mUserPTTState = false; + mMuteMic = false; + mSessionTerminateRequested = false; + mCommandCookie = 0; + mNonSpatialChannel = false; + mNextSessionSpatial = true; + mNextSessionNoReconnect = false; + mSessionP2P = false; + mCurrentParcelLocalID = 0; + mLoginRetryCount = 0; + mVivoxErrorStatusCode = 0; + + mNextSessionResetOnClose = false; + mSessionResetOnClose = false; + mSpeakerVolume = 0; + mMicVolume = 0; + + // Initial dirty state + mSpatialCoordsDirty = false; + mPTTDirty = true; + mVolumeDirty = true; + mSpeakerVolumeDirty = true; + mMicVolumeDirty = true; + mCaptureDeviceDirty = false; + mRenderDeviceDirty = false; + + // Load initial state from prefs. + mVoiceEnabled = gSavedSettings.getBOOL("EnableVoiceChat"); + mUsePTT = gSavedSettings.getBOOL("EnablePushToTalk"); + std::string keyString = gSavedSettings.getString("PushToTalkButton"); + setPTTKey(keyString); + mPTTIsToggle = gSavedSettings.getBOOL("PushToTalkToggle"); + mEarLocation = gSavedSettings.getS32("VoiceEarLocation"); + setVoiceVolume(gSavedSettings.getF32("AudioLevelVoice")); + std::string captureDevice = gSavedSettings.getString("VoiceInputAudioDevice"); + setCaptureDevice(captureDevice); + std::string renderDevice = gSavedSettings.getString("VoiceOutputAudioDevice"); + setRenderDevice(renderDevice); + + // Set up our listener to get updates on all prefs values we care about. + gSavedSettings.getControl("EnableVoiceChat")->addListener(&voice_prefs_listener); + gSavedSettings.getControl("PTTCurrentlyEnabled")->addListener(&voice_prefs_listener); + gSavedSettings.getControl("PushToTalkButton")->addListener(&voice_prefs_listener); + gSavedSettings.getControl("PushToTalkToggle")->addListener(&voice_prefs_listener); + gSavedSettings.getControl("VoiceEarLocation")->addListener(&voice_prefs_listener); + gSavedSettings.getControl("VivoxDebugServerName")->addListener(&voice_prefs_listener); + gSavedSettings.getControl("VoiceInputAudioDevice")->addListener(&voice_prefs_listener); + gSavedSettings.getControl("VoiceOutputAudioDevice")->addListener(&voice_prefs_listener); + + mTuningMode = false; + mTuningEnergy = 0.0f; + mTuningMicVolume = 0; + mTuningMicVolumeDirty = true; + mTuningSpeakerVolume = 0; + mTuningSpeakerVolumeDirty = true; + mTuningCaptureRunning = false; + + // gMuteListp isn't set up at this point, so we defer this until later. +// gMuteListp->addObserver(&mutelist_listener); + + mParticipantMapChanged = false; + + // stash the pump for later use + // This now happens when init() is called instead. + mPump = NULL; + +#if LL_DARWIN || LL_LINUX + // MBW -- XXX -- THIS DOES NOT BELONG HERE + // When the vivox daemon dies, the next write attempt on our socket generates a SIGPIPE, which kills us. + // This should cause us to ignore SIGPIPE and handle the error through proper channels. + // This should really be set up elsewhere. Where should it go? + signal(SIGPIPE, SIG_IGN); + + // Since we're now launching the gateway with fork/exec instead of system(), we need to deal with zombie processes. + // Ignoring SIGCHLD should prevent zombies from being created. Alternately, we could use wait(), but I'd rather not do that. + signal(SIGCHLD, SIG_IGN); +#endif + + // set up state machine + setState(stateDisabled); + + gIdleCallbacks.addFunction(idle, this); +} + +//--------------------------------------------------- + +LLVoiceClient::~LLVoiceClient() +{ +} + +//---------------------------------------------- + + + +void LLVoiceClient::init(LLPumpIO *pump) +{ + // constructor will set up gVoiceClient + LLVoiceClient::getInstance()->mPump = pump; +} + +void LLVoiceClient::terminate() +{ + if(gVoiceClient) + { + gVoiceClient->sessionTerminateSendMessage(); + gVoiceClient->logout(); + gVoiceClient->connectorShutdown(); + gVoiceClient->closeSocket(); // Need to do this now -- bad things happen if the destructor does it later. + + // This will do unpleasant things on windows. +// killGateway(); + + // Don't do this anymore -- LLSingleton will take care of deleting the object. +// delete gVoiceClient; + + // Hint to other code not to access the voice client anymore. + gVoiceClient = NULL; + } +} + + +///////////////////////////// +// utility functions + +bool LLVoiceClient::writeString(const std::string &str) +{ + bool result = false; + if(mConnected) + { + apr_status_t err; + apr_size_t size = (apr_size_t)str.size(); + apr_size_t written = size; + +// llinfos << "sending: " << str << llendl; + + // MBW -- XXX -- check return code - sockets will fail (broken, etc.) + err = apr_socket_send( + mSocket->getSocket(), + (const char*)str.data(), + &written); + + if(err == 0) + { + // Success. + result = true; + } + // MBW -- XXX -- handle partial writes (written is number of bytes written) + // Need to set socket to non-blocking before this will work. +// else if(APR_STATUS_IS_EAGAIN(err)) +// { +// // +// } + else + { + // Assume any socket error means something bad. For now, just close the socket. + char buf[MAX_STRING]; + llwarns << "apr error " << err << " ("<< apr_strerror(err, buf, MAX_STRING) << ") sending data to vivox daemon." << llendl; + daemonDied(); + } + } + + return result; +} + + +///////////////////////////// +// session control messages +void LLVoiceClient::connectorCreate() +{ + std::ostringstream stream; + std::string logpath; + std::string loglevel = "0"; + + // Transition to stateConnectorStarted when the connector handle comes back. + setState(stateConnectorStarting); + + std::string savedLogLevel = gSavedSettings.getString("VivoxDebugLevel"); + + if(savedLogLevel != "-1") + { + llinfos << "creating connector with logging enabled" << llendl; + loglevel = "10"; + logpath = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, ""); + } + + stream + << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Connector.Create.1\">" + << "<ClientName>V2 SDK</ClientName>" + << "<AccountManagementServer>" << mAccountServerURI << "</AccountManagementServer>" + << "<Logging>" + << "<Enabled>false</Enabled>" + << "<Folder>" << logpath << "</Folder>" + << "<FileNamePrefix>Connector</FileNamePrefix>" + << "<FileNameSuffix>.log</FileNameSuffix>" + << "<LogLevel>" << loglevel << "</LogLevel>" + << "</Logging>" + << "</Request>\n\n\n"; + + writeString(stream.str()); +} + +void LLVoiceClient::connectorShutdown() +{ + setState(stateConnectorStopping); + + if(!mConnectorHandle.empty()) + { + std::ostringstream stream; + stream + << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Connector.InitiateShutdown.1\">" + << "<ConnectorHandle>" << mConnectorHandle << "</ConnectorHandle>" + << "</Request>" + << "\n\n\n"; + + mConnectorHandle.clear(); + + writeString(stream.str()); + } +} + +void LLVoiceClient::userAuthorized(const std::string& firstName, const std::string& lastName, const LLUUID &agentID) +{ + mAccountFirstName = firstName; + mAccountLastName = lastName; + + mAccountDisplayName = firstName; + mAccountDisplayName += " "; + mAccountDisplayName += lastName; + + llinfos << "name \"" << mAccountDisplayName << "\" , ID " << agentID << llendl; + + std::string userserver = gUserServerName; + LLString::toLower(userserver); + if((gUserServerChoice == USERSERVER_AGNI) || + ((gUserServerChoice == USERSERVER_OTHER) && (userserver.find("agni") != std::string::npos))) + { + sConnectingToAgni = true; + } + + // MBW -- XXX -- Enable this when the bhd.vivox.com server gets a real ssl cert. + if(sConnectingToAgni) + { + // Use the release account server + mAccountServerName = "bhr.vivox.com"; + mAccountServerURI = "https://www." + mAccountServerName + "/api2/"; + } + else + { + // Use the development account server + mAccountServerName = gSavedSettings.getString("VivoxDebugServerName"); + mAccountServerURI = "https://www." + mAccountServerName + "/api2/"; + } + + mAccountName = nameFromID(agentID); +} + +void LLVoiceClient::requestVoiceAccountProvision(S32 retries) +{ + if ( gAgent.getRegion() && mVoiceEnabled ) + { + std::string url = + gAgent.getRegion()->getCapability( + "ProvisionVoiceAccountRequest"); + + if ( url == "" ) return; + + LLHTTPClient::post( + url, + LLSD(), + new LLViewerVoiceAccountProvisionResponder(retries)); + } +} + +void LLVoiceClient::login( + const std::string& accountName, + const std::string &password) +{ + if((getState() >= stateLoggingIn) && (getState() < stateLoggedOut)) + { + // Already logged in. This is an internal error. + llerrs << "called from wrong state." << llendl; + } + else if ( accountName != mAccountName ) + { + //TODO: error? + llinfos << "Wrong account name! " << accountName + << " instead of " << mAccountName << llendl; + } + else + { + mAccountPassword = password; + } +} + +void LLVoiceClient::idle(void* user_data) +{ + LLVoiceClient* self = (LLVoiceClient*)user_data; + self->stateMachine(); +} + +const char *LLVoiceClient::state2string(LLVoiceClient::state inState) +{ + const char *result = "UNKNOWN"; + + // Prevent copy-paste errors when updating this list... +#define CASE(x) case x: result = #x; break + + switch(inState) + { + CASE(stateDisabled); + CASE(stateStart); + CASE(stateDaemonLaunched); + CASE(stateConnecting); + CASE(stateIdle); + CASE(stateConnectorStart); + CASE(stateConnectorStarting); + CASE(stateConnectorStarted); + CASE(stateMicTuningNoLogin); + CASE(stateLoginRetry); + CASE(stateLoginRetryWait); + CASE(stateNeedsLogin); + CASE(stateLoggingIn); + CASE(stateLoggedIn); + CASE(stateNoChannel); + CASE(stateMicTuningLoggedIn); + CASE(stateSessionCreate); + CASE(stateSessionConnect); + CASE(stateJoiningSession); + CASE(stateSessionJoined); + CASE(stateRunning); + CASE(stateLeavingSession); + CASE(stateSessionTerminated); + CASE(stateLoggingOut); + CASE(stateLoggedOut); + CASE(stateConnectorStopping); + CASE(stateConnectorStopped); + CASE(stateConnectorFailed); + CASE(stateConnectorFailedWaiting); + CASE(stateLoginFailed); + CASE(stateLoginFailedWaiting); + CASE(stateJoinSessionFailed); + CASE(stateJoinSessionFailedWaiting); + CASE(stateJail); + } + +#undef CASE + + return result; +} + +const char *LLVoiceClientStatusObserver::status2string(LLVoiceClientStatusObserver::EStatusType inStatus) +{ + const char *result = "UNKNOWN"; + + // Prevent copy-paste errors when updating this list... +#define CASE(x) case x: result = #x; break + + switch(inStatus) + { + CASE(STATUS_JOINING); + CASE(STATUS_JOINED); + CASE(STATUS_LEFT_CHANNEL); + CASE(ERROR_CHANNEL_FULL); + CASE(ERROR_CHANNEL_LOCKED); + CASE(ERROR_UNKNOWN); + default: + break; + } + +#undef CASE + + return result; +} + +void LLVoiceClient::setState(state inState) +{ + llinfos << "entering state " << state2string(inState) << llendl; + + mState = inState; +} + +void LLVoiceClient::stateMachine() +{ + if(gDisconnected) + { + // The viewer has been disconnected from the sim. Disable voice. + setVoiceEnabled(false); + } + + if(!mVoiceEnabled) + { + if(getState() != stateDisabled) + { + // User turned off voice support. Send the cleanup messages, close the socket, and reset. + if(!mConnected) + { + // if voice was turned off after the daemon was launched but before we could connect to it, we may need to issue a kill. + llinfos << "Disabling voice before connection to daemon, terminating." << llendl; + killGateway(); + } + + sessionTerminateSendMessage(); + logout(); + connectorShutdown(); + closeSocket(); + removeAllParticipants(); + + setState(stateDisabled); + } + } + + // Check for parcel boundary crossing + { + LLViewerRegion *region = gAgent.getRegion(); + LLParcel *parcel = NULL; + + if(gParcelMgr) + { + parcel = gParcelMgr->getAgentParcel(); + } + + if(region && parcel) + { + S32 parcelLocalID = parcel->getLocalID(); + std::string regionName = region->getName(); + std::string capURI = region->getCapability("ParcelVoiceInfoRequest"); + +// llinfos << "Region name = \"" << regionName <<"\", " << "parcel local ID = " << parcelLocalID << llendl; + + // The region name starts out empty and gets filled in later. + // Also, the cap gets filled in a short time after the region cross, but a little too late for our purposes. + // If either is empty, wait for the next time around. + if(!regionName.empty() && !capURI.empty()) + { + if((parcelLocalID != mCurrentParcelLocalID) || (regionName != mCurrentRegionName)) + { + // We have changed parcels. Initiate a parcel channel lookup. + mCurrentParcelLocalID = parcelLocalID; + mCurrentRegionName = regionName; + + parcelChanged(); + } + } + } + } + + switch(getState()) + { + case stateDisabled: + if(mVoiceEnabled && (!mAccountName.empty() || mTuningMode)) + { + setState(stateStart); + } + break; + + case stateStart: + if(gDisableVoice) + { + // Voice is locked out, we must not launch the vivox daemon. + setState(stateJail); + } + else if(!isGatewayRunning()) + { + if(true) + { + // Launch the voice daemon + std::string exe_path = gDirUtilp->getAppRODataDir(); + exe_path += gDirUtilp->getDirDelimiter(); +#if LL_WINDOWS + exe_path += "SLVoice.exe"; +#else + // This will be the same for mac and linux + exe_path += "SLVoice"; +#endif + // See if the vivox executable exists + llstat s; + if(!LLFile::stat(exe_path.c_str(), &s)) + { + // vivox executable exists. Build the command line and launch the daemon. + std::string args = " -p tcp -h -c"; + std::string cmd; + std::string loglevel = gSavedSettings.getString("VivoxDebugLevel"); + + if(loglevel.empty()) + { + loglevel = "-1"; // turn logging off completely + } + + args += " -ll "; + args += loglevel; + +// llinfos << "Args for SLVoice: " << args << llendl; + +#if LL_WINDOWS + PROCESS_INFORMATION pinfo; + STARTUPINFOA sinfo; + memset(&sinfo, 0, sizeof(sinfo)); + std::string exe_dir = gDirUtilp->getAppRODataDir(); + cmd = "SLVoice.exe"; + cmd += args; + + // So retarded. Windows requires that the second parameter to CreateProcessA be a writable (non-const) string... + char *args2 = new char[args.size() + 1]; + strcpy(args2, args.c_str()); + + if(!CreateProcessA(exe_path.c_str(), args2, NULL, NULL, FALSE, 0, NULL, exe_dir.c_str(), &sinfo, &pinfo)) + { +// DWORD dwErr = GetLastError(); + } + else + { + // foo = pinfo.dwProcessId; // get your pid here if you want to use it later on + // CloseHandle(pinfo.hProcess); // stops leaks - nothing else + sGatewayHandle = pinfo.hProcess; + CloseHandle(pinfo.hThread); // stops leaks - nothing else + } + + delete args2; +#else // LL_WINDOWS + // This should be the same for mac and linux + { + std::vector<std::string> arglist; + arglist.push_back(exe_path.c_str()); + + // Split the argument string into separate strings for each argument + typedef boost::tokenizer<boost::char_separator<char> > tokenizer; + boost::char_separator<char> sep(" "); + tokenizer tokens(args, sep); + tokenizer::iterator token_iter; + + for(token_iter = tokens.begin(); token_iter != tokens.end(); ++token_iter) + { + arglist.push_back(*token_iter); + } + + // create an argv vector for the child process + char **fakeargv = new char*[arglist.size() + 1]; + int i; + for(i=0; i < arglist.size(); i++) + fakeargv[i] = const_cast<char*>(arglist[i].c_str()); + + fakeargv[i] = NULL; + + pid_t id = vfork(); + if(id == 0) + { + // child + execv(exe_path.c_str(), fakeargv); + + // If we reach this point, the exec failed. + // Use _exit() instead of exit() per the vfork man page. + _exit(0); + } + + // parent + delete[] fakeargv; + sGatewayPID = id; + } +#endif // LL_WINDOWS + mDaemonHost = LLHost("127.0.0.1", 44124); + } + else + { + llinfos << exe_path << "not found." << llendl + } + } + else + { + // We can connect to a client gateway running on another host. This is useful for testing. + // To do this, launch the gateway on a nearby host like this: + // vivox-gw.exe -p tcp -i 0.0.0.0:44124 + // and put that host's IP address here. + mDaemonHost = LLHost("127.0.0.1", 44124); + } + + mUpdateTimer.start(); + mUpdateTimer.setTimerExpirySec(CONNECT_THROTTLE_SECONDS); + + setState(stateDaemonLaunched); + + // Dirty the states we'll need to sync with the daemon when it comes up. + mPTTDirty = true; + mSpeakerVolumeDirty = true; + // These only need to be set if they're not default (i.e. empty string). + mCaptureDeviceDirty = !mCaptureDevice.empty(); + mRenderDeviceDirty = !mRenderDevice.empty(); + } + break; + + case stateDaemonLaunched: +// llinfos << "Connecting to vivox daemon" << llendl; + if(mUpdateTimer.hasExpired()) + { + mUpdateTimer.setTimerExpirySec(CONNECT_THROTTLE_SECONDS); + + if(!mSocket) + { + mSocket = LLSocket::create(gAPRPoolp, LLSocket::STREAM_TCP); + } + + mConnected = mSocket->blockingConnect(mDaemonHost); + if(mConnected) + { + setState(stateConnecting); + } + else + { + // If the connect failed, the socket may have been put into a bad state. Delete it. + closeSocket(); + } + } + break; + + case stateConnecting: + // Can't do this until we have the pump available. + if(mPump) + { + // MBW -- Note to self: pumps and pipes examples in + // indra/test/io.cpp + // indra/test/llpipeutil.{cpp|h} + + // Attach the pumps and pipes + + LLPumpIO::chain_t readChain; + + readChain.push_back(LLIOPipe::ptr_t(new LLIOSocketReader(mSocket))); + readChain.push_back(LLIOPipe::ptr_t(new LLVivoxProtocolParser())); + + mPump->addChain(readChain, NEVER_CHAIN_EXPIRY_SECS); + + setState(stateIdle); + } + + break; + + case stateIdle: + // Initial devices query + getCaptureDevicesSendMessage(); + getRenderDevicesSendMessage(); + + mLoginRetryCount = 0; + + setState(stateConnectorStart); + + break; + + case stateConnectorStart: + if(!mVoiceEnabled) + { + // We were never logged in. This will shut down the connector. + setState(stateLoggedOut); + } + else if(!mAccountServerURI.empty()) + { + connectorCreate(); + } + else if(mTuningMode) + { + setState(stateMicTuningNoLogin); + } + break; + + case stateConnectorStarting: // waiting for connector handle + // connectorCreateResponse() will transition from here to stateConnectorStarted. + break; + + case stateConnectorStarted: // connector handle received + if(!mVoiceEnabled) + { + // We were never logged in. This will shut down the connector. + setState(stateLoggedOut); + } + else if(!mAccountName.empty()) + { + LLViewerRegion *region = gAgent.getRegion(); + + if(region) + { + if ( region->getCapability("ProvisionVoiceAccountRequest") != "" ) + { + if ( mAccountPassword.empty() ) + { + requestVoiceAccountProvision(); + } + setState(stateNeedsLogin); + } + } + } + break; + + case stateMicTuningNoLogin: + case stateMicTuningLoggedIn: + { + // Both of these behave essentially the same. The only difference is where the exit transition goes to. + if(mTuningMode && mVoiceEnabled && !mSessionTerminateRequested) + { + if(!mTuningCaptureRunning) + { + // duration parameter is currently unused, per Mike S. + tuningCaptureStartSendMessage(10000); + } + + if(mTuningMicVolumeDirty || mTuningSpeakerVolumeDirty || mCaptureDeviceDirty || mRenderDeviceDirty) + { + std::ostringstream stream; + + if(mTuningMicVolumeDirty) + { + stream + << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.SetMicLevel.1\">" + << "<Level>" << mTuningMicVolume << "</Level>" + << "</Request>\n\n\n"; + } + + if(mTuningSpeakerVolumeDirty) + { + stream + << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.SetSpeakerLevel.1\">" + << "<Level>" << mTuningSpeakerVolume << "</Level>" + << "</Request>\n\n\n"; + } + + if(mCaptureDeviceDirty) + { + buildSetCaptureDevice(stream); + } + + if(mRenderDeviceDirty) + { + buildSetRenderDevice(stream); + } + + mTuningMicVolumeDirty = false; + mTuningSpeakerVolumeDirty = false; + mCaptureDeviceDirty = false; + mRenderDeviceDirty = false; + + if(!stream.str().empty()) + { + writeString(stream.str()); + } + } + } + else + { + // transition out of mic tuning + if(mTuningCaptureRunning) + { + tuningCaptureStopSendMessage(); + } + + if(getState() == stateMicTuningNoLogin) + { + setState(stateConnectorStart); + } + else + { + setState(stateNoChannel); + } + } + } + break; + + case stateLoginRetry: + if(mLoginRetryCount == 0) + { + // First retry -- display a message to the user + notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LOGIN_RETRY); + } + + mLoginRetryCount++; + + if(mLoginRetryCount > MAX_LOGIN_RETRIES) + { + llinfos << "too many login retries, giving up." << llendl; + setState(stateLoginFailed); + } + else + { + llinfos << "will retry login in " << LOGIN_RETRY_SECONDS << " seconds." << llendl; + mUpdateTimer.start(); + mUpdateTimer.setTimerExpirySec(LOGIN_RETRY_SECONDS); + setState(stateLoginRetryWait); + } + break; + + case stateLoginRetryWait: + if(mUpdateTimer.hasExpired()) + { + setState(stateNeedsLogin); + } + break; + + case stateNeedsLogin: + if(!mAccountPassword.empty()) + { + setState(stateLoggingIn); + loginSendMessage(); + } + break; + + case stateLoggingIn: // waiting for account handle + // loginResponse() will transition from here to stateLoggedIn. + break; + + case stateLoggedIn: // account handle received + // Initial kick-off of channel lookup logic + parcelChanged(); + + notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LOGGED_IN); + + // Set up the mute list observer if it hasn't been set up already. + if((!sMuteListListener_listening) && (gMuteListp)) + { + gMuteListp->addObserver(&mutelist_listener); + sMuteListListener_listening = true; + } + + setState(stateNoChannel); + break; + + case stateNoChannel: + if(mSessionTerminateRequested || !mVoiceEnabled) + { + // MBW -- XXX -- Is this the right way out of this state? + setState(stateSessionTerminated); + } + else if(mTuningMode) + { + setState(stateMicTuningLoggedIn); + } + else if(!mNextSessionHandle.empty()) + { + setState(stateSessionConnect); + } + else if(!mNextSessionURI.empty()) + { + setState(stateSessionCreate); + } + break; + + case stateSessionCreate: + sessionCreateSendMessage(); + notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_JOINING); + setState(stateJoiningSession); + break; + + case stateSessionConnect: + sessionConnectSendMessage(); + notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_JOINING); + setState(stateJoiningSession); + break; + + case stateJoiningSession: // waiting for session handle + // sessionCreateResponse() will transition from here to stateSessionJoined. + if(!mVoiceEnabled) + { + // User bailed out during connect -- jump straight to teardown. + setState(stateSessionTerminated); + } + else if(mSessionTerminateRequested) + { + if(!mSessionHandle.empty()) + { + // Only allow direct exits from this state in p2p calls (for cancelling an invite). + // Terminating a half-connected session on other types of calls seems to break something in the vivox gateway. + if(mSessionP2P) + { + sessionTerminateSendMessage(); + setState(stateSessionTerminated); + } + } + } + break; + + case stateSessionJoined: // session handle received + // MBW -- XXX -- It appears that I need to wait for BOTH the Session.Create response and the SessionStateChangeEvent with state 4 + // before continuing from this state. They can happen in either order, and if I don't wait for both, things can get stuck. + // For now, the Session.Create response handler sets mSessionHandle and the SessionStateChangeEvent handler transitions to stateSessionJoined. + // This is a cheap way to make sure both have happened before proceeding. + if(!mSessionHandle.empty()) + { + // Events that need to happen when a session is joined could go here. + // Maybe send initial spatial data? + notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_JOINED); + + // Dirty state that may need to be sync'ed with the daemon. + mPTTDirty = true; + mSpeakerVolumeDirty = true; + mSpatialCoordsDirty = true; + + setState(stateRunning); + + // Start the throttle timer + mUpdateTimer.start(); + mUpdateTimer.setTimerExpirySec(UPDATE_THROTTLE_SECONDS); + } + else if(!mVoiceEnabled) + { + // User bailed out during connect -- jump straight to teardown. + setState(stateSessionTerminated); + } + else if(mSessionTerminateRequested) + { + // Only allow direct exits from this state in p2p calls (for cancelling an invite). + // Terminating a half-connected session on other types of calls seems to break something in the vivox gateway. + if(mSessionP2P) + { + sessionTerminateSendMessage(); + setState(stateSessionTerminated); + } + } + break; + + case stateRunning: // steady state + // sessionTerminateSendMessage() will transition from here to stateLeavingSession + + // Disabling voice or disconnect requested. + if(!mVoiceEnabled || mSessionTerminateRequested) + { + sessionTerminateSendMessage(); + } + else + { + + // Figure out whether the PTT state needs to change + { + bool newPTT; + if(mUsePTT) + { + // If configured to use PTT, track the user state. + newPTT = mUserPTTState; + } + else + { + // If not configured to use PTT, it should always be true (otherwise the user will be unable to speak). + newPTT = true; + } + + if(mMuteMic) + { + // This always overrides any other PTT setting. + newPTT = false; + } + + // Dirty if state changed. + if(newPTT != mPTT) + { + mPTT = newPTT; + mPTTDirty = true; + } + } + + if(mNonSpatialChannel) + { + // When in a non-spatial channel, never send positional updates. + mSpatialCoordsDirty = false; + } + else + { + // Do the calculation that enforces the listener<->speaker tether (and also updates the real camera position) + enforceTether(); + } + + // Send an update if the ptt state has changed (which shouldn't be able to happen that often -- the user can only click so fast) + // or every 10hz, whichever is sooner. + if(mVolumeDirty || mPTTDirty || mSpeakerVolumeDirty || mUpdateTimer.hasExpired()) + { + mUpdateTimer.setTimerExpirySec(UPDATE_THROTTLE_SECONDS); + sendPositionalUpdate(); + } + } + break; + + case stateLeavingSession: // waiting for terminate session response + // The handler for the Session.Terminate response will transition from here to stateSessionTerminated. + break; + + case stateSessionTerminated: + // Always reset the terminate request flag when we get here. + mSessionTerminateRequested = false; + + notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL); + + if(mVoiceEnabled) + { + // SPECIAL CASE: if going back to spatial but in a parcel with an empty URI, transfer the non-spatial flag now. + // This fixes the case where you come out of a group chat in a parcel with voice disabled, and get stuck unable to rejoin spatial chat thereafter. + if(mNextSessionSpatial && mNextSessionURI.empty()) + { + mNonSpatialChannel = !mNextSessionSpatial; + } + + // Just leaving a channel, go back to stateNoChannel (the "logged in but have no channel" state). + setState(stateNoChannel); + } + else + { + // Shutting down voice, continue with disconnecting. + logout(); + } + + break; + + case stateLoggingOut: // waiting for logout response + // The handler for the Account.Logout response will transition from here to stateLoggedOut. + break; + case stateLoggedOut: // logout response received + // shut down the connector + connectorShutdown(); + break; + + case stateConnectorStopping: // waiting for connector stop + // The handler for the Connector.InitiateShutdown response will transition from here to stateConnectorStopped. + break; + + case stateConnectorStopped: // connector stop received + // Clean up and reset everything. + closeSocket(); + removeAllParticipants(); + setState(stateDisabled); + break; + + case stateConnectorFailed: + setState(stateConnectorFailedWaiting); + break; + case stateConnectorFailedWaiting: + break; + + case stateLoginFailed: + setState(stateLoginFailedWaiting); + break; + case stateLoginFailedWaiting: + // No way to recover from these. Yet. + break; + + case stateJoinSessionFailed: + // Transition to error state. Send out any notifications here. + llwarns << "stateJoinSessionFailed: (" << mVivoxErrorStatusCode << "): " << mVivoxErrorStatusString << llendl; + notifyStatusObservers(LLVoiceClientStatusObserver::ERROR_UNKNOWN); + setState(stateJoinSessionFailedWaiting); + break; + + case stateJoinSessionFailedWaiting: + // Joining a channel failed, either due to a failed channel name -> sip url lookup or an error from the join message. + // Region crossings may leave this state and try the join again. + if(mSessionTerminateRequested) + { + setState(stateSessionTerminated); + } + break; + + case stateJail: + // We have given up. Do nothing. + break; + } + + if(mParticipantMapChanged) + { + mParticipantMapChanged = false; + notifyObservers(); + } + +} + +void LLVoiceClient::closeSocket(void) +{ + mSocket.reset(); + mConnected = false; +} + +void LLVoiceClient::loginSendMessage() +{ + std::ostringstream stream; + stream + << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.Login.1\">" + << "<ConnectorHandle>" << mConnectorHandle << "</ConnectorHandle>" + << "<AccountName>" << mAccountName << "</AccountName>" + << "<AccountPassword>" << mAccountPassword << "</AccountPassword>" + << "<AudioSessionAnswerMode>VerifyAnswer</AudioSessionAnswerMode>" + << "</Request>\n\n\n"; + + writeString(stream.str()); +} + +void LLVoiceClient::logout() +{ + mAccountPassword = ""; + setState(stateLoggingOut); + logoutSendMessage(); +} + +void LLVoiceClient::logoutSendMessage() +{ + if(!mAccountHandle.empty()) + { + std::ostringstream stream; + stream + << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.Logout.1\">" + << "<AccountHandle>" << mAccountHandle << "</AccountHandle>" + << "</Request>" + << "\n\n\n"; + + mAccountHandle.clear(); + + writeString(stream.str()); + } +} + +void LLVoiceClient::channelGetListSendMessage() +{ + std::ostringstream stream; + stream + << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.ChannelGetList.1\">" + << "<AccountHandle>" << mAccountHandle << "</AccountHandle>" + << "</Request>\n\n\n"; + + writeString(stream.str()); +} + +void LLVoiceClient::sessionCreateSendMessage() +{ + llinfos << "requesting join: " << mNextSessionURI << llendl; + + mSessionURI = mNextSessionURI; + mNonSpatialChannel = !mNextSessionSpatial; + mSessionResetOnClose = mNextSessionResetOnClose; + mNextSessionResetOnClose = false; + if(mNextSessionNoReconnect) + { + // Clear the stashed URI so it can't reconnect + mNextSessionURI.clear(); + } + // Only p2p sessions are created with "no reconnect". + mSessionP2P = mNextSessionNoReconnect; + + std::ostringstream stream; + stream + << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.Create.1\">" + << "<AccountHandle>" << mAccountHandle << "</AccountHandle>" + << "<URI>" << mSessionURI << "</URI>"; + + static const std::string allowed_chars = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + "0123456789" + "-._~"; + + if(!mNextSessionHash.empty()) + { + stream + << "<Password>" << LLURI::escape(mNextSessionHash, allowed_chars) << "</Password>" + << "<PasswordHashAlgorithm>SHA1UserName</PasswordHashAlgorithm>"; + } + + stream + << "<Name>" << mChannelName << "</Name>" + << "</Request>\n\n\n"; + writeString(stream.str()); +} + +void LLVoiceClient::sessionConnectSendMessage() +{ + llinfos << "connecting to session handle: " << mNextSessionHandle << llendl; + + mSessionHandle = mNextSessionHandle; + mSessionURI = mNextP2PSessionURI; + mNextSessionHandle.clear(); // never want to re-use these. + mNextP2PSessionURI.clear(); + mNonSpatialChannel = !mNextSessionSpatial; + mSessionResetOnClose = mNextSessionResetOnClose; + mNextSessionResetOnClose = false; + // Joining by session ID is only used to answer p2p invitations, so we know this is a p2p session. + mSessionP2P = true; + + std::ostringstream stream; + + stream + << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.Connect.1\">" + << "<SessionHandle>" << mSessionHandle << "</SessionHandle>" + << "<AudioMedia>default</AudioMedia>" + << "</Request>\n\n\n"; + writeString(stream.str()); +} + +void LLVoiceClient::sessionTerminate() +{ + mSessionTerminateRequested = true; +} + +void LLVoiceClient::sessionTerminateSendMessage() +{ + llinfos << "leaving session: " << mSessionURI << llendl; + + switch(getState()) + { + case stateNoChannel: + // In this case, we want to pretend the join failed so our state machine doesn't get stuck. + // Skip the join failed transition state so we don't send out error notifications. + setState(stateJoinSessionFailedWaiting); + break; + case stateJoiningSession: + case stateSessionJoined: + case stateRunning: + if(!mSessionHandle.empty()) + { + sessionTerminateByHandle(mSessionHandle); + setState(stateLeavingSession); + } + else + { + llwarns << "called with no session handle" << llendl; + setState(stateSessionTerminated); + } + break; + case stateJoinSessionFailed: + case stateJoinSessionFailedWaiting: + setState(stateSessionTerminated); + break; + + default: + llwarns << "called from unknown state" << llendl; + break; + } +} + +void LLVoiceClient::sessionTerminateByHandle(std::string &sessionHandle) +{ + llinfos << "Sending Session.Terminate with handle " << sessionHandle << llendl; + + std::ostringstream stream; + stream + << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.Terminate.1\">" + << "<SessionHandle>" << sessionHandle << "</SessionHandle>" + << "</Request>" + << "\n\n\n"; + + writeString(stream.str()); +} + +void LLVoiceClient::getCaptureDevicesSendMessage() +{ + std::ostringstream stream; + stream + << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.GetCaptureDevices.1\">" + << "</Request>\n\n\n"; + + writeString(stream.str()); +} + +void LLVoiceClient::getRenderDevicesSendMessage() +{ + std::ostringstream stream; + stream + << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.GetRenderDevices.1\">" + << "</Request>\n\n\n"; + + writeString(stream.str()); +} + +void LLVoiceClient::clearCaptureDevices() +{ + // MBW -- XXX -- do something here + llinfos << "called" << llendl; + mCaptureDevices.clear(); +} + +void LLVoiceClient::addCaptureDevice(const std::string& name) +{ + // MBW -- XXX -- do something here + llinfos << name << llendl; + + mCaptureDevices.push_back(name); +} + +LLVoiceClient::deviceList *LLVoiceClient::getCaptureDevices() +{ + return &mCaptureDevices; +} + +void LLVoiceClient::setCaptureDevice(const std::string& name) +{ + if(name == "Default") + { + if(!mCaptureDevice.empty()) + { + mCaptureDevice.clear(); + mCaptureDeviceDirty = true; + } + } + else + { + if(mCaptureDevice != name) + { + mCaptureDevice = name; + mCaptureDeviceDirty = true; + } + } +} + +void LLVoiceClient::clearRenderDevices() +{ + // MBW -- XXX -- do something here + llinfos << "called" << llendl; + mRenderDevices.clear(); +} + +void LLVoiceClient::addRenderDevice(const std::string& name) +{ + // MBW -- XXX -- do something here + llinfos << name << llendl; + mRenderDevices.push_back(name); +} + +LLVoiceClient::deviceList *LLVoiceClient::getRenderDevices() +{ + return &mRenderDevices; +} + +void LLVoiceClient::setRenderDevice(const std::string& name) +{ + if(name == "Default") + { + if(!mRenderDevice.empty()) + { + mRenderDevice.clear(); + mRenderDeviceDirty = true; + } + } + else + { + if(mRenderDevice != name) + { + mRenderDevice = name; + mRenderDeviceDirty = true; + } + } + +} + +void LLVoiceClient::tuningStart() +{ + mTuningMode = true; + if(getState() >= stateNoChannel) + { + sessionTerminate(); + } +} + +void LLVoiceClient::tuningStop() +{ + mTuningMode = false; +} + +bool LLVoiceClient::inTuningMode() +{ + bool result = false; + switch(getState()) + { + case stateMicTuningNoLogin: + case stateMicTuningLoggedIn: + result = true; + default: + break; + } + return result; +} + +void LLVoiceClient::tuningRenderStartSendMessage(const std::string& name, bool loop) +{ + if(!inTuningMode()) + return; + + mTuningAudioFile = name; + std::ostringstream stream; + stream + << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.RenderAudioStart.1\">" + << "<SoundFilePath>" << mTuningAudioFile << "</SoundFilePath>" + << "<Loop>" << (loop?"1":"0") << "</Loop>" + << "</Request>\n\n\n"; + + writeString(stream.str()); +} + +void LLVoiceClient::tuningRenderStopSendMessage() +{ + if(!inTuningMode()) + return; + + std::ostringstream stream; + stream + << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.RenderAudioStop.1\">" + << "<SoundFilePath>" << mTuningAudioFile << "</SoundFilePath>" + << "</Request>\n\n\n"; + + writeString(stream.str()); +} + +void LLVoiceClient::tuningCaptureStartSendMessage(int duration) +{ + if(!inTuningMode()) + return; + + std::ostringstream stream; + stream + << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.CaptureAudioStart.1\">" + << "<Duration>" << duration << "</Duration>" + << "</Request>\n\n\n"; + + writeString(stream.str()); + + mTuningCaptureRunning = true; +} + +void LLVoiceClient::tuningCaptureStopSendMessage() +{ + if(!inTuningMode()) + return; + + std::ostringstream stream; + stream + << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.CaptureAudioStop.1\">" + << "</Request>\n\n\n"; + + writeString(stream.str()); + + mTuningCaptureRunning = false; +} + +void LLVoiceClient::tuningSetMicVolume(float volume) +{ + int scaledVolume = ((int)(volume * 100.0f)) - 100; + if(scaledVolume != mTuningMicVolume) + { + mTuningMicVolume = scaledVolume; + mTuningMicVolumeDirty = true; + } +} + +void LLVoiceClient::tuningSetSpeakerVolume(float volume) +{ + int scaledVolume = ((int)(volume * 100.0f)) - 100; + if(scaledVolume != mTuningSpeakerVolume) + { + mTuningSpeakerVolume = ((int)(volume * 100.0f)) - 100; + mTuningSpeakerVolumeDirty = true; + } +} + +float LLVoiceClient::tuningGetEnergy(void) +{ + return mTuningEnergy; +} + +bool LLVoiceClient::deviceSettingsAvailable() +{ + bool result = true; + + if(!mConnected) + result = false; + + if(mRenderDevices.empty()) + result = false; + + return result; +} + +void LLVoiceClient::refreshDeviceLists(bool clearCurrentList) +{ + if(clearCurrentList) + { + clearCaptureDevices(); + clearRenderDevices(); + } + getCaptureDevicesSendMessage(); + getRenderDevicesSendMessage(); +} + +void LLVoiceClient::daemonDied() +{ + // The daemon died, so the connection is gone. Reset everything and start over. + llwarns << "Connection to vivox daemon lost. Resetting state."<< llendl; + + closeSocket(); + removeAllParticipants(); + + // Try to relaunch the daemon + setState(stateDisabled); +} + +void LLVoiceClient::giveUp() +{ + // All has failed. Clean up and stop trying. + closeSocket(); + removeAllParticipants(); + + setState(stateJail); +} + +void LLVoiceClient::sendPositionalUpdate(void) +{ + std::ostringstream stream; + + if(mSpatialCoordsDirty) + { + LLVector3 l, u, a; + + // Always send both speaker and listener positions together. + stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.Set3DPosition.1\">" + << "<SessionHandle>" << mSessionHandle << "</SessionHandle>"; + + stream << "<SpeakerPosition>"; + + l = mAvatarRot.getLeftRow(); + u = mAvatarRot.getUpRow(); + a = mAvatarRot.getFwdRow(); + +// llinfos << "Sending speaker position " << mSpeakerPosition << llendl; + + stream + << "<Position>" + << "<X>" << mAvatarPosition[VX] << "</X>" + << "<Y>" << mAvatarPosition[VZ] << "</Y>" + << "<Z>" << mAvatarPosition[VY] << "</Z>" + << "</Position>" + << "<Velocity>" + << "<X>" << mAvatarVelocity[VX] << "</X>" + << "<Y>" << mAvatarVelocity[VZ] << "</Y>" + << "<Z>" << mAvatarVelocity[VY] << "</Z>" + << "</Velocity>" + << "<AtOrientation>" + << "<X>" << l.mV[VX] << "</X>" + << "<Y>" << u.mV[VX] << "</Y>" + << "<Z>" << a.mV[VX] << "</Z>" + << "</AtOrientation>" + << "<UpOrientation>" + << "<X>" << l.mV[VZ] << "</X>" + << "<Y>" << u.mV[VY] << "</Y>" + << "<Z>" << a.mV[VZ] << "</Z>" + << "</UpOrientation>" + << "<LeftOrientation>" + << "<X>" << l.mV [VY] << "</X>" + << "<Y>" << u.mV [VZ] << "</Y>" + << "<Z>" << a.mV [VY] << "</Z>" + << "</LeftOrientation>"; + + stream << "</SpeakerPosition>"; + + stream << "<ListenerPosition>"; + + LLVector3d earPosition; + LLVector3 earVelocity; + LLMatrix3 earRot; + + switch(mEarLocation) + { + case earLocCamera: + default: + earPosition = mCameraPosition; + earVelocity = mCameraVelocity; + earRot = mCameraRot; + break; + + case earLocAvatar: + earPosition = mAvatarPosition; + earVelocity = mAvatarVelocity; + earRot = mAvatarRot; + break; + + case earLocMixed: + earPosition = mAvatarPosition; + earVelocity = mAvatarVelocity; + earRot = mCameraRot; + break; + } + + l = earRot.getLeftRow(); + u = earRot.getUpRow(); + a = earRot.getFwdRow(); + +// llinfos << "Sending listener position " << mListenerPosition << llendl; + + stream + << "<Position>" + << "<X>" << earPosition[VX] << "</X>" + << "<Y>" << earPosition[VZ] << "</Y>" + << "<Z>" << earPosition[VY] << "</Z>" + << "</Position>" + << "<Velocity>" + << "<X>" << earVelocity[VX] << "</X>" + << "<Y>" << earVelocity[VZ] << "</Y>" + << "<Z>" << earVelocity[VY] << "</Z>" + << "</Velocity>" + << "<AtOrientation>" + << "<X>" << l.mV[VX] << "</X>" + << "<Y>" << u.mV[VX] << "</Y>" + << "<Z>" << a.mV[VX] << "</Z>" + << "</AtOrientation>" + << "<UpOrientation>" + << "<X>" << l.mV[VZ] << "</X>" + << "<Y>" << u.mV[VY] << "</Y>" + << "<Z>" << a.mV[VZ] << "</Z>" + << "</UpOrientation>" + << "<LeftOrientation>" + << "<X>" << l.mV [VY] << "</X>" + << "<Y>" << u.mV [VZ] << "</Y>" + << "<Z>" << a.mV [VY] << "</Z>" + << "</LeftOrientation>"; + + stream << "</ListenerPosition>"; + + stream << "</Request>\n\n\n"; + } + + if(mPTTDirty) + { + // Send a local mute command. + // NOTE that the state of "PTT" is the inverse of "local mute". + // (i.e. when PTT is true, we send a mute command with "false", and vice versa) + +// llinfos << "Sending MuteLocalMic command with parameter " << (mPTT?"false":"true") << llendl; + + stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Connector.MuteLocalMic.1\">" + << "<ConnectorHandle>" << mConnectorHandle << "</ConnectorHandle>" + << "<Value>" << (mPTT?"false":"true") << "</Value>" + << "</Request>\n\n\n"; + + } + + if(mVolumeDirty) + { + participantMap::iterator iter = mParticipantMap.begin(); + + for(; iter != mParticipantMap.end(); iter++) + { + participantState *p = iter->second; + + if(p->mVolumeDirty) + { + int volume = p->mOnMuteList?0:p->mUserVolume; + + llinfos << "Setting volume for avatar " << p->mAvatarID << " to " << volume << llendl; + + // Send a mute/unumte command for the user (actually "volume for me"). + stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.SetParticipantVolumeForMe.1\">" + << "<SessionHandle>" << mSessionHandle << "</SessionHandle>" + << "<ParticipantURI>" << p->mURI << "</ParticipantURI>" + << "<Volume>" << volume << "</Volume>" + << "</Request>\n\n\n"; + + p->mVolumeDirty = false; + } + } + } + + if(mSpeakerMuteDirty) + { + const char *muteval = ((mSpeakerVolume == -100)?"true":"false"); + llinfos << "Setting speaker mute to " << muteval << llendl; + + stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Connector.MuteLocalSpeaker.1\">" + << "<ConnectorHandle>" << mConnectorHandle << "</ConnectorHandle>" + << "<Value>" << muteval << "</Value>" + << "</Request>\n\n\n"; + } + + if(mSpeakerVolumeDirty) + { + llinfos << "Setting speaker volume to " << mSpeakerVolume << llendl; + + stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Connector.SetLocalSpeakerVolume.1\">" + << "<ConnectorHandle>" << mConnectorHandle << "</ConnectorHandle>" + << "<Value>" << mSpeakerVolume << "</Value>" + << "</Request>\n\n\n"; + } + + if(mMicVolumeDirty) + { + llinfos << "Setting mic volume to " << mMicVolume << llendl; + + stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Connector.SetLocalMicVolume.1\">" + << "<ConnectorHandle>" << mConnectorHandle << "</ConnectorHandle>" + << "<Value>" << mMicVolume << "</Value>" + << "</Request>\n\n\n"; + } + + + // MBW -- XXX -- Maybe check to make sure the capture/render devices are in the current list here? + if(mCaptureDeviceDirty) + { + buildSetCaptureDevice(stream); + } + + if(mRenderDeviceDirty) + { + buildSetRenderDevice(stream); + } + + mSpatialCoordsDirty = false; + mPTTDirty = false; + mVolumeDirty = false; + mSpeakerVolumeDirty = false; + mMicVolumeDirty = false; + mSpeakerMuteDirty = false; + mCaptureDeviceDirty = false; + mRenderDeviceDirty = false; + + if(!stream.str().empty()) + { + writeString(stream.str()); + } +} + +void LLVoiceClient::buildSetCaptureDevice(std::ostringstream &stream) +{ + llinfos << "Setting input device = \"" << mCaptureDevice << "\"" << llendl; + + stream + << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.SetCaptureDevice.1\">" + << "<CaptureDeviceSpecifier>" << mCaptureDevice << "</CaptureDeviceSpecifier>" + << "</Request>" + << "\n\n\n"; +} + +void LLVoiceClient::buildSetRenderDevice(std::ostringstream &stream) +{ + llinfos << "Setting output device = \"" << mRenderDevice << "\"" << llendl; + + stream + << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.SetRenderDevice.1\">" + << "<RenderDeviceSpecifier>" << mRenderDevice << "</RenderDeviceSpecifier>" + << "</Request>" + << "\n\n\n"; +} + +///////////////////////////// +// Response/Event handlers + +void LLVoiceClient::connectorCreateResponse(int statusCode, std::string &statusString, std::string &connectorHandle) +{ + if(statusCode != 0) + { + llwarns << "Connector.Create response failure: " << statusString << llendl; + setState(stateConnectorFailed); + } + else + { + // Connector created, move forward. + mConnectorHandle = connectorHandle; + if(getState() == stateConnectorStarting) + { + setState(stateConnectorStarted); + } + } +} + +void LLVoiceClient::loginResponse(int statusCode, std::string &statusString, std::string &accountHandle) +{ + llinfos << "Account.Login response (" << statusCode << "): " << statusString << llendl; + + // Status code of 20200 means "bad password". We may want to special-case that at some point. + + if ( statusCode == 401 ) + { + // Login failure which is probably caused by the delay after a user's password being updated. + llinfos << "Account.Login response failure (" << statusCode << "): " << statusString << llendl; + setState(stateLoginRetry); + } + else if(statusCode != 0) + { + llwarns << "Account.Login response failure (" << statusCode << "): " << statusString << llendl; + setState(stateLoginFailed); + } + else + { + // Login succeeded, move forward. + mAccountHandle = accountHandle; + // MBW -- XXX -- This needs to wait until the LoginStateChangeEvent is received. +// if(getState() == stateLoggingIn) +// { +// setState(stateLoggedIn); +// } + } +} + +void LLVoiceClient::channelGetListResponse(int statusCode, std::string &statusString) +{ + if(statusCode != 0) + { + llwarns << "Account.ChannelGetList response failure: " << statusString << llendl; + switchChannel(); + } + else + { + // Got the channel list, try to do a lookup. + std::string uri = findChannelURI(mChannelName); + if(uri.empty()) + { + // Lookup failed, can't join a channel for this area. + llinfos << "failed to map channel name: " << mChannelName << llendl; + } + else + { + // We have a sip URL for this area. + llinfos << "mapped channel " << mChannelName << " to URI "<< uri << llendl; + } + + // switchChannel with an empty uri string will do the right thing (leave channel and not rejoin) + switchChannel(uri); + } +} + +void LLVoiceClient::sessionCreateResponse(int statusCode, std::string &statusString, std::string &sessionHandle) +{ + if(statusCode != 0) + { + llwarns << "Session.Create response failure (" << statusCode << "): " << statusString << llendl; +// if(statusCode == 1015) +// { +// if(getState() == stateJoiningSession) +// { +// // this happened during a real join. Going to sessionTerminated should cause a retry in appropriate cases. +// llwarns << "session handle \"" << sessionHandle << "\", mSessionStateEventHandle \"" << mSessionStateEventHandle << "\""<< llendl; +// if(!sessionHandle.empty()) +// { +// // This session is bad. Terminate it. +// mSessionHandle = sessionHandle; +// sessionTerminateByHandle(sessionHandle); +// setState(stateLeavingSession); +// } +// else if(!mSessionStateEventHandle.empty()) +// { +// mSessionHandle = mSessionStateEventHandle; +// sessionTerminateByHandle(mSessionStateEventHandle); +// setState(stateLeavingSession); +// } +// else +// { +// setState(stateSessionTerminated); +// } +// } +// else +// { +// // We didn't think we were in the middle of a join. Don't change state. +// llwarns << "Not in stateJoiningSession, ignoring" << llendl; +// } +// } +// else + { + mVivoxErrorStatusCode = statusCode; + mVivoxErrorStatusString = statusString; + setState(stateJoinSessionFailed); + } + } + else + { + llinfos << "Session.Create response received (success), session handle is " << sessionHandle << llendl; + if(getState() == stateJoiningSession) + { + // This is also grabbed in the SessionStateChangeEvent handler, but it might be useful to have it early... + mSessionHandle = sessionHandle; + } + else + { + // We should never get a session.create response in any state except stateJoiningSession. Things are out of sync. Kill this session. + sessionTerminateByHandle(sessionHandle); + } + } +} + +void LLVoiceClient::sessionConnectResponse(int statusCode, std::string &statusString) +{ + if(statusCode != 0) + { + llwarns << "Session.Connect response failure (" << statusCode << "): " << statusString << llendl; +// if(statusCode == 1015) +// { +// llwarns << "terminating existing session" << llendl; +// sessionTerminate(); +// } +// else + { + mVivoxErrorStatusCode = statusCode; + mVivoxErrorStatusString = statusString; + setState(stateJoinSessionFailed); + } + } + else + { + llinfos << "Session.Connect response received (success)" << llendl; + } +} + +void LLVoiceClient::sessionTerminateResponse(int statusCode, std::string &statusString) +{ + if(statusCode != 0) + { + llwarns << "Session.Terminate response failure: (" << statusCode << "): " << statusString << llendl; + if(getState() == stateLeavingSession) + { + // This is probably "(404): Server reporting Failure. Not a member of this conference." + // Do this so we don't get stuck. + setState(stateSessionTerminated); + } + } + +} + +void LLVoiceClient::logoutResponse(int statusCode, std::string &statusString) +{ + if(statusCode != 0) + { + llwarns << "Account.Logout response failure: " << statusString << llendl; + // MBW -- XXX -- Should this ever fail? do we care if it does? + } + + if(getState() == stateLoggingOut) + { + setState(stateLoggedOut); + } +} + +void LLVoiceClient::connectorShutdownResponse(int statusCode, std::string &statusString) +{ + if(statusCode != 0) + { + llwarns << "Connector.InitiateShutdown response failure: " << statusString << llendl; + // MBW -- XXX -- Should this ever fail? do we care if it does? + } + + mConnected = false; + + if(getState() == stateConnectorStopping) + { + setState(stateConnectorStopped); + } +} + +void LLVoiceClient::sessionStateChangeEvent( + std::string &uriString, + int statusCode, + std::string &statusString, + std::string &sessionHandle, + int state, + bool isChannel, + std::string &nameString) +{ + switch(state) + { + case 4: // I see this when joining the session + llinfos << "joined session " << uriString << ", name " << nameString << " handle " << mNextSessionHandle << llendl; + + // Session create succeeded, move forward. + mSessionStateEventHandle = sessionHandle; + mSessionStateEventURI = uriString; + if(sessionHandle == mSessionHandle) + { + // This is the session we're joining. + if(getState() == stateJoiningSession) + { + setState(stateSessionJoined); + //RN: the uriString being returned by vivox here is actually your account uri, not the channel + // you are attempting to join, so ignore it + //llinfos << "received URI " << uriString << "(previously " << mSessionURI << ")" << llendl; + //mSessionURI = uriString; + } + } + else if(sessionHandle == mNextSessionHandle) + { +// llinfos << "received URI " << uriString << ", name " << nameString << " for next session (handle " << mNextSessionHandle << ")" << llendl; + } + else + { + llwarns << "joining unknown session handle " << sessionHandle << ", URI " << uriString << ", name " << nameString << llendl; + // MBW -- XXX -- Should we send a Session.Terminate here? + } + + break; + case 5: // I see this when leaving the session + llinfos << "left session " << uriString << ", name " << nameString << " handle " << mNextSessionHandle << llendl; + + // Set the session handle to the empty string. If we get back to stateJoiningSession, we'll want to wait for the new session handle. + if(sessionHandle == mSessionHandle) + { + // MBW -- XXX -- I think this is no longer necessary, now that we've got mNextSessionURI/mNextSessionHandle + // mSessionURI.clear(); + // clear the session handle here just for sanity. + mSessionHandle.clear(); + if(mSessionResetOnClose) + { + mSessionResetOnClose = false; + mNonSpatialChannel = false; + mNextSessionSpatial = true; + parcelChanged(); + } + + removeAllParticipants(); + + switch(getState()) + { + case stateJoiningSession: + case stateSessionJoined: + case stateRunning: + case stateLeavingSession: + case stateJoinSessionFailed: + case stateJoinSessionFailedWaiting: + // normal transition + llinfos << "left session " << sessionHandle << "in state " << state2string(getState()) << llendl; + setState(stateSessionTerminated); + break; + + case stateSessionTerminated: + // this will happen sometimes -- there are cases where we send the terminate and then go straight to this state. + llwarns << "left session " << sessionHandle << "in state " << state2string(getState()) << llendl; + break; + + default: + llwarns << "unexpected SessionStateChangeEvent (left session) in state " << state2string(getState()) << llendl; + setState(stateSessionTerminated); + break; + } + + // store status values for later notification of observers + mVivoxErrorStatusCode = statusCode; + mVivoxErrorStatusString = statusString; + } + else + { + llinfos << "leaving unknown session handle " << sessionHandle << ", URI " << uriString << ", name " << nameString << llendl; + } + + mSessionStateEventHandle.clear(); + mSessionStateEventURI.clear(); + break; + default: + llwarns << "unknown state: " << state << llendl; + break; + } +} + +void LLVoiceClient::loginStateChangeEvent( + std::string &accountHandle, + int statusCode, + std::string &statusString, + int state) +{ + llinfos << "state is " << state << llendl; + /* + According to Mike S., status codes for this event are: + login_state_logged_out=0, + login_state_logged_in = 1, + login_state_logging_in = 2, + login_state_logging_out = 3, + login_state_resetting = 4, + login_state_error=100 + */ + + switch(state) + { + case 1: + if(getState() == stateLoggingIn) + { + setState(stateLoggedIn); + } + break; + + default: +// llwarns << "unknown state: " << state << llendl; + break; + } +} + +void LLVoiceClient::sessionNewEvent( + std::string &accountHandle, + std::string &eventSessionHandle, + int state, + std::string &nameString, + std::string &uriString) +{ +// llinfos << "state is " << state << llendl; + + switch(state) + { + case 0: + { + llinfos << "session handle = " << eventSessionHandle << ", name = " << nameString << ", uri = " << uriString << llendl; + + LLUUID caller_id; + if(IDFromName(nameString, caller_id)) + { + gIMMgr->inviteToSession(LLIMMgr::computeSessionID(IM_SESSION_P2P_INVITE, caller_id), + LLString::null, + caller_id, + LLString::null, + IM_SESSION_P2P_INVITE, + eventSessionHandle); + } + else + { + llwarns << "Could not generate caller id from uri " << uriString << llendl; + } + } + break; + + default: + llwarns << "unknown state: " << state << llendl; + break; + } +} + +void LLVoiceClient::participantStateChangeEvent( + std::string &uriString, + int statusCode, + std::string &statusString, + int state, + std::string &nameString, + std::string &displayNameString, + int participantType) +{ + participantState *participant = NULL; + llinfos << "state is " << state << llendl; + + switch(state) + { + case 7: // I see this when a participant joins + participant = addParticipant(uriString); + if(participant) + { + participant->mName = nameString; + llinfos << "added participant \"" << participant->mName + << "\" (" << participant->mAvatarID << ")"<< llendl; + } + break; + case 9: // I see this when a participant leaves + participant = findParticipant(uriString); + if(participant) + { + removeParticipant(participant); + } + break; + default: +// llwarns << "unknown state: " << state << llendl; + break; + } +} + +void LLVoiceClient::participantPropertiesEvent( + std::string &uriString, + int statusCode, + std::string &statusString, + bool isLocallyMuted, + bool isModeratorMuted, + bool isSpeaking, + int volume, + F32 energy) +{ + participantState *participant = findParticipant(uriString); + if(participant) + { + participant->mPTT = !isLocallyMuted; + participant->mIsSpeaking = isSpeaking; + if (isSpeaking) + { + participant->mSpeakingTimeout.reset(); + } + participant->mPower = energy; + participant->mVolume = volume; + } + else + { + llwarns << "unknown participant: " << uriString << llendl; + } +} + +void LLVoiceClient::auxAudioPropertiesEvent(F32 energy) +{ +// llinfos << "got energy " << energy << llendl; + mTuningEnergy = energy; +} + +void LLVoiceClient::muteListChanged() +{ + // The user's mute list has been updated. Go through the current participant list and sync it with the mute list. + + participantMap::iterator iter = mParticipantMap.begin(); + + for(; iter != mParticipantMap.end(); iter++) + { + participantState *p = iter->second; + + // Check to see if this participant is on the mute list already + updateMuteState(p); + } +} + +///////////////////////////// +// Managing list of participants +LLVoiceClient::participantState::participantState(const std::string &uri) : + mURI(uri), mPTT(false), mIsSpeaking(false), mPower(0.0), mServiceType(serviceTypeUnknown), + mOnMuteList(false), mUserVolume(100), mVolumeDirty(false), mAvatarIDValid(false) +{ +} + +LLVoiceClient::participantState *LLVoiceClient::addParticipant(const std::string &uri) +{ + participantState *result = NULL; + + participantMap::iterator iter = mParticipantMap.find(uri); + + if(iter != mParticipantMap.end()) + { + // Found a matching participant already in the map. + result = iter->second; + } + + if(!result) + { + // participant isn't already in one list or the other. + result = new participantState(uri); + mParticipantMap.insert(participantMap::value_type(uri, result)); + mParticipantMapChanged = true; + + // Try to do a reverse transform on the URI to get the GUID back. + { + LLUUID id; + if(IDFromName(uri, id)) + { + result->mAvatarIDValid = true; + result->mAvatarID = id; + + updateMuteState(result); + } + } + + llinfos << "participant \"" << result->mURI << "\" added." << llendl; + } + + return result; +} + +void LLVoiceClient::updateMuteState(participantState *p) +{ + if(p->mAvatarIDValid) + { + bool isMuted = gMuteListp->isMuted(p->mAvatarID, LLMute::flagVoiceChat); + if(p->mOnMuteList != isMuted) + { + p->mOnMuteList = isMuted; + p->mVolumeDirty = true; + mVolumeDirty = true; + } + } +} + +void LLVoiceClient::removeParticipant(LLVoiceClient::participantState *participant) +{ + if(participant) + { + participantMap::iterator iter = mParticipantMap.find(participant->mURI); + + llinfos << "participant \"" << participant->mURI << "\" (" << participant->mAvatarID << ") removed." << llendl; + + mParticipantMap.erase(iter); + delete participant; + mParticipantMapChanged = true; + } +} + +void LLVoiceClient::removeAllParticipants() +{ + llinfos << "called" << llendl; + + while(!mParticipantMap.empty()) + { + removeParticipant(mParticipantMap.begin()->second); + } +} + +LLVoiceClient::participantMap *LLVoiceClient::getParticipantList(void) +{ + return &mParticipantMap; +} + + +LLVoiceClient::participantState *LLVoiceClient::findParticipant(const std::string &uri) +{ + participantState *result = NULL; + + // Don't find any participants if we're not connected. This is so that we don't continue to get stale data + // after the daemon dies. + if(mConnected) + { + participantMap::iterator iter = mParticipantMap.find(uri); + + if(iter != mParticipantMap.end()) + { + result = iter->second; + } + } + + return result; +} + + +LLVoiceClient::participantState *LLVoiceClient::findParticipantByAvatar(LLVOAvatar *avatar) +{ + participantState * result = NULL; + + // You'd think this would work, but it doesn't... +// std::string uri = sipURIFromAvatar(avatar); + + // Currently, the URI is just the account name. + std::string loginName = nameFromAvatar(avatar); + result = findParticipant(loginName); + + if(result != NULL) + { + if(!result->mAvatarIDValid) + { + result->mAvatarID = avatar->getID(); + result->mAvatarIDValid = true; + + // We just figured out the avatar ID, so the participant list has "changed" from the perspective of anyone who uses that to identify participants. + mParticipantMapChanged = true; + + updateMuteState(result); + } + + + } + + return result; +} + +LLVoiceClient::participantState* LLVoiceClient::findParticipantByID(const LLUUID& id) +{ + participantState * result = NULL; + + // Currently, the URI is just the account name. + std::string loginName = nameFromID(id); + result = findParticipant(loginName); + + return result; +} + + +void LLVoiceClient::clearChannelMap(void) +{ + mChannelMap.clear(); +} + +void LLVoiceClient::addChannelMapEntry(std::string &name, std::string &uri) +{ +// llinfos << "Adding channel name mapping: " << name << " -> " << uri << llendl; + mChannelMap.insert(channelMap::value_type(name, uri)); +} + +std::string LLVoiceClient::findChannelURI(std::string &name) +{ + std::string result; + + channelMap::iterator iter = mChannelMap.find(name); + + if(iter != mChannelMap.end()) + { + result = iter->second; + } + + return result; +} + +void LLVoiceClient::parcelChanged() +{ + if(getState() >= stateLoggedIn) + { + // If the user is logged in, start a channel lookup. + llinfos << "sending ParcelVoiceInfoRequest (" << mCurrentRegionName << ", " << mCurrentParcelLocalID << ")" << llendl; + + std::string url = gAgent.getRegion()->getCapability("ParcelVoiceInfoRequest"); + LLSD data; + data["method"] = "call"; + LLHTTPClient::post( + url, + data, + new LLVoiceClientCapResponder); + } + else + { + // The transition to stateLoggedIn needs to kick this off again. + llinfos << "not logged in yet, deferring" << llendl; + } +} + +void LLVoiceClient::switchChannel( + std::string uri, + bool spatial, + bool noReconnect, + std::string hash) +{ + bool needsSwitch = false; + + llinfos << "called in state " << state2string(getState()) << " with uri \"" << uri << "\"" << llendl; + + switch(getState()) + { + case stateJoinSessionFailed: + case stateJoinSessionFailedWaiting: + case stateNoChannel: + // Always switch to the new URI from these states. + needsSwitch = true; + break; + + default: + if(mSessionTerminateRequested) + { + // If a terminate has been requested, we need to compare against where the URI we're already headed to. + if(mNextSessionURI != uri) + needsSwitch = true; + } + else + { + // Otherwise, compare against the URI we're in now. + if(mSessionURI != uri) + needsSwitch = true; + } + break; + } + + if(needsSwitch) + { + mNextSessionURI = uri; + mNextSessionHash = hash; + mNextSessionHandle.clear(); + mNextP2PSessionURI.clear(); + mNextSessionSpatial = spatial; + mNextSessionNoReconnect = noReconnect; + + if(uri.empty()) + { + // Leave any channel we may be in + llinfos << "leaving channel" << llendl; + } + else + { + llinfos << "switching to channel " << uri << llendl; + } + + if(getState() <= stateNoChannel) + { + // We're already set up to join a channel, just needed to fill in the session URI + } + else + { + // State machine will come around and rejoin if uri/handle is not empty. + sessionTerminate(); + } + } +} + +void LLVoiceClient::joinSession(std::string handle, std::string uri) +{ + mNextSessionURI.clear(); + mNextSessionHash.clear(); + mNextP2PSessionURI = uri; + mNextSessionHandle = handle; + mNextSessionSpatial = false; + mNextSessionNoReconnect = false; + + if(getState() <= stateNoChannel) + { + // We're already set up to join a channel, just needed to fill in the session handle + } + else + { + // State machine will come around and rejoin if uri/handle is not empty. + sessionTerminate(); + } +} + +void LLVoiceClient::setNonSpatialChannel( + const std::string &uri, + const std::string &credentials) +{ + switchChannel(uri, false, false, credentials); +} + +void LLVoiceClient::setSpatialChannel( + const std::string &uri, + const std::string &credentials) +{ + mSpatialSessionURI = uri; + mAreaVoiceDisabled = mSpatialSessionURI.empty(); + + llinfos << "got spatial channel uri: \"" << uri << "\"" << llendl; + + if(mNonSpatialChannel || !mNextSessionSpatial) + { + // User is in a non-spatial chat or joining a non-spatial chat. Don't switch channels. + llinfos << "in non-spatial chat, not switching channels" << llendl; + } + else + { + switchChannel(mSpatialSessionURI, true, false, credentials); + } +} + +void LLVoiceClient::callUser(LLUUID &uuid) +{ + std::string userURI = sipURIFromID(uuid); + + switchChannel(userURI, false, true); +} + +void LLVoiceClient::answerInvite(std::string &sessionHandle, LLUUID& other_user_id) +{ + joinSession(sessionHandle, sipURIFromID(other_user_id)); +} + +void LLVoiceClient::declineInvite(std::string &sessionHandle) +{ + sessionTerminateByHandle(sessionHandle); +} + +void LLVoiceClient::leaveNonSpatialChannel() +{ + switchChannel(mSpatialSessionURI); +} + +std::string LLVoiceClient::getCurrentChannel() +{ + if((getState() == stateRunning) && !mSessionTerminateRequested) + { + return mSessionURI; + } + + return ""; +} + +bool LLVoiceClient::inProximalChannel() +{ + bool result = false; + + if((getState() == stateRunning) && !mSessionTerminateRequested) + { + result = !mNonSpatialChannel; + } + + return result; +} + +std::string LLVoiceClient::sipURIFromID(const LLUUID &id) +{ + std::string result; + result = "sip:"; + result += nameFromID(id); + result += "@"; + result += mAccountServerName; + + return result; +} + +std::string LLVoiceClient::sipURIFromAvatar(LLVOAvatar *avatar) +{ + std::string result; + if(avatar) + { + result = "sip:"; + result += nameFromID(avatar->getID()); + result += "@"; + result += mAccountServerName; + } + + return result; +} + +std::string LLVoiceClient::nameFromAvatar(LLVOAvatar *avatar) +{ + std::string result; + if(avatar) + { + result = nameFromID(avatar->getID()); + } + return result; +} + +std::string LLVoiceClient::nameFromID(const LLUUID &uuid) +{ + std::string result; + U8 rawuuid[UUID_BYTES + 1]; + uuid.toCompressedString((char*)rawuuid); + + // Prepending this apparently prevents conflicts with reserved names inside the vivox and diamondware code. + result = "x"; + + // Base64 encode and replace the pieces of base64 that are less compatible + // with e-mail local-parts. + // See RFC-4648 "Base 64 Encoding with URL and Filename Safe Alphabet" + result += LLBase64::encode(rawuuid, UUID_BYTES); + LLString::replaceChar(result, '+', '-'); + LLString::replaceChar(result, '/', '_'); + + // If you need to transform a GUID to this form on the Mac OS X command line, this will do so: + // echo -n x && (echo e669132a-6c43-4ee1-a78d-6c82fff59f32 |xxd -r -p |openssl base64|tr '/+' '_-') + + return result; +} + +bool LLVoiceClient::IDFromName(const std::string name, LLUUID &uuid) +{ + bool result = false; + + // This will only work if the name is of the proper form. + // As an example, the account name for Monroe Linden (UUID 1673cfd3-8229-4445-8d92-ec3570e5e587) is: + // "xFnPP04IpREWNkuw1cOXlhw==" + + if((name.size() == 25) && (name[0] == 'x') && (name[23] == '=') && (name[24] == '=')) + { + // The name appears to have the right form. + + // Reverse the transforms done by nameFromID + std::string temp = name; + LLString::replaceChar(temp, '-', '+'); + LLString::replaceChar(temp, '_', '/'); + + U8 rawuuid[UUID_BYTES + 1]; + int len = apr_base64_decode_binary(rawuuid, temp.c_str() + 1); + if(len == UUID_BYTES) + { + // The decode succeeded. Stuff the bits into the result's UUID + // MBW -- XXX -- there's no analogue of LLUUID::toCompressedString that allows you to set a UUID from binary data. + // The data field is public, so we cheat thusly: + memcpy(uuid.mData, rawuuid, UUID_BYTES); + result = true; + } + } + + return result; +} + +std::string LLVoiceClient::displayNameFromAvatar(LLVOAvatar *avatar) +{ + return avatar->getFullname(); +} + +std::string LLVoiceClient::sipURIFromName(std::string &name) +{ + std::string result; + result = "sip:"; + result += name; + result += "@"; + result += mAccountServerName; + +// LLString::toLower(result); + + return result; +} + +///////////////////////////// +// Sending updates of current state + +void LLVoiceClient::enforceTether(void) +{ + LLVector3d tethered = mCameraRequestedPosition; + + // constrain 'tethered' to within 50m of mAvatarPosition. + { + F32 max_dist = 50.0f; + LLVector3d camera_offset = mCameraRequestedPosition - mAvatarPosition; + F32 camera_distance = (F32)camera_offset.magVec(); + if(camera_distance > max_dist) + { + tethered = mAvatarPosition + + (max_dist / camera_distance) * camera_offset; + } + } + + if(dist_vec(mCameraPosition, tethered) > 0.1) + { + mCameraPosition = tethered; + mSpatialCoordsDirty = true; + } +} + +void LLVoiceClient::setCameraPosition(const LLVector3d &position, const LLVector3 &velocity, const LLMatrix3 &rot) +{ + mCameraRequestedPosition = position; + + if(mCameraVelocity != velocity) + { + mCameraVelocity = velocity; + mSpatialCoordsDirty = true; + } + + if(mCameraRot != rot) + { + mCameraRot = rot; + mSpatialCoordsDirty = true; + } +} + +void LLVoiceClient::setAvatarPosition(const LLVector3d &position, const LLVector3 &velocity, const LLMatrix3 &rot) +{ + if(dist_vec(mAvatarPosition, position) > 0.1) + { + mAvatarPosition = position; + mSpatialCoordsDirty = true; + } + + if(mAvatarVelocity != velocity) + { + mAvatarVelocity = velocity; + mSpatialCoordsDirty = true; + } + + if(mAvatarRot != rot) + { + mAvatarRot = rot; + mSpatialCoordsDirty = true; + } +} + +bool LLVoiceClient::channelFromRegion(LLViewerRegion *region, std::string &name) +{ + bool result = false; + + if(region) + { + name = region->getName(); + } + + if(!name.empty()) + result = true; + + return result; +} + +void LLVoiceClient::leaveChannel(void) +{ + if(getState() == stateRunning) + { +// llinfos << "leaving channel for teleport/logout" << llendl; + mChannelName.clear(); + sessionTerminate(); + } +} + +void LLVoiceClient::setMuteMic(bool muted) +{ + mMuteMic = muted; +} + +void LLVoiceClient::setUserPTTState(bool ptt) +{ + mUserPTTState = ptt; +} + +bool LLVoiceClient::getUserPTTState() +{ + return mUserPTTState; +} + +void LLVoiceClient::toggleUserPTTState(void) +{ + mUserPTTState = !mUserPTTState; +} + +void LLVoiceClient::setVoiceEnabled(bool enabled) +{ + if (enabled != mVoiceEnabled) + { + mVoiceEnabled = enabled; + if (enabled) + { + LLVoiceChannel::getCurrentVoiceChannel()->activate(); + } + else + { + // for now, leave active channel, to auto join when turning voice back on + //LLVoiceChannel::getCurrentVoiceChannel->deactivate(); + } + } +} + +bool LLVoiceClient::voiceEnabled() +{ + return gSavedSettings.getBOOL("EnableVoiceChat") && !gDisableVoice; +} + +void LLVoiceClient::setUsePTT(bool usePTT) +{ + if(usePTT && !mUsePTT) + { + // When the user turns on PTT, reset the current state. + mUserPTTState = false; + } + mUsePTT = usePTT; +} + +void LLVoiceClient::setPTTIsToggle(bool PTTIsToggle) +{ + if(!PTTIsToggle && mPTTIsToggle) + { + // When the user turns off toggle, reset the current state. + mUserPTTState = false; + } + + mPTTIsToggle = PTTIsToggle; +} + + +void LLVoiceClient::setPTTKey(std::string &key) +{ + if(key == "MiddleMouse") + { + mPTTIsMiddleMouse = true; + } + else + { + mPTTIsMiddleMouse = false; + if(!LLKeyboard::keyFromString(key, &mPTTKey)) + { + // If the call failed, don't match any key. + key = KEY_NONE; + } + } +} + +void LLVoiceClient::setEarLocation(S32 loc) +{ + if(mEarLocation != loc) + { + llinfos << "Setting mEarLocation to " << loc << llendl; + + mEarLocation = loc; + mSpatialCoordsDirty = true; + } +} + +void LLVoiceClient::setVoiceVolume(F32 volume) +{ + int scaledVolume = ((int)(volume * 100.0f)) - 100; + if(scaledVolume != mSpeakerVolume) + { + if((scaledVolume == -100) || (mSpeakerVolume == -100)) + { + mSpeakerMuteDirty = true; + } + + mSpeakerVolume = scaledVolume; + mSpeakerVolumeDirty = true; + } +} + +void LLVoiceClient::setMicGain(F32 volume) +{ + int scaledVolume = ((int)(volume * 100.0f)) - 100; + if(scaledVolume != mMicVolume) + { + mMicVolume = scaledVolume; + mMicVolumeDirty = true; + } +} + +void LLVoiceClient::setVivoxDebugServerName(std::string &serverName) +{ + if(!mAccountServerName.empty()) + { + // The name has been filled in already, which means we know whether we're connecting to agni or not. + if(!sConnectingToAgni) + { + // Only use the setting if we're connecting to a development grid -- always use bhr when on agni. + mAccountServerName = serverName; + } + } +} + +void LLVoiceClient::keyDown(KEY key, MASK mask) +{ +// llinfos << "key is " << LLKeyboard::stringFromKey(key) << llendl; + + if (gKeyboard->getKeyRepeated(key)) + { + // ignore auto-repeat keys + return; + } + + if(!mPTTIsMiddleMouse) + { + if(mPTTIsToggle) + { + if(key == mPTTKey) + { + toggleUserPTTState(); + } + } + else if(mPTTKey != KEY_NONE) + { + setUserPTTState(gKeyboard->getKeyDown(mPTTKey)); + } + } +} +void LLVoiceClient::keyUp(KEY key, MASK mask) +{ + if(!mPTTIsMiddleMouse) + { + if(!mPTTIsToggle && (mPTTKey != KEY_NONE)) + { + setUserPTTState(gKeyboard->getKeyDown(mPTTKey)); + } + } +} +void LLVoiceClient::middleMouseState(bool down) +{ + if(mPTTIsMiddleMouse) + { + if(mPTTIsToggle) + { + if(down) + { + toggleUserPTTState(); + } + } + else + { + setUserPTTState(down); + } + } +} + +///////////////////////////// +// Accessors for data related to nearby speakers +BOOL LLVoiceClient::getVoiceEnabled(const LLUUID& id) +{ + BOOL result = FALSE; + participantState *participant = findParticipantByID(id); + if(participant) + { + // I'm not sure what the semantics of this should be. + // For now, if we have any data about the user that came through the chat channel, assume they're voice-enabled. + result = TRUE; + } + + return result; +} + +BOOL LLVoiceClient::getIsSpeaking(const LLUUID& id) +{ + BOOL result = FALSE; + + participantState *participant = findParticipantByID(id); + if(participant) + { + if (participant->mSpeakingTimeout.getElapsedTimeF32() > SPEAKING_TIMEOUT) + { + participant->mIsSpeaking = FALSE; + } + result = participant->mIsSpeaking; + } + + return result; +} + +F32 LLVoiceClient::getCurrentPower(const LLUUID& id) +{ + F32 result = 0; + participantState *participant = findParticipantByID(id); + if(participant) + { + result = participant->mPower; + } + + return result; +} + + +LLString LLVoiceClient::getDisplayName(const LLUUID& id) +{ + LLString result; + participantState *participant = findParticipantByID(id); + if(participant) + { + result = participant->mDisplayName; + } + + return result; +} + + +BOOL LLVoiceClient::getUsingPTT(const LLUUID& id) +{ + BOOL result = FALSE; + + participantState *participant = findParticipantByID(id); + if(participant) + { + // I'm not sure what the semantics of this should be. + // Does "using PTT" mean they're configured with a push-to-talk button? + // For now, we know there's no PTT mechanism in place, so nobody is using it. + } + + return result; +} + +BOOL LLVoiceClient::getPTTPressed(const LLUUID& id) +{ + BOOL result = FALSE; + + participantState *participant = findParticipantByID(id); + if(participant) + { + result = participant->mPTT; + } + + return result; +} + +BOOL LLVoiceClient::getOnMuteList(const LLUUID& id) +{ + BOOL result = FALSE; + + participantState *participant = findParticipantByID(id); + if(participant) + { + result = participant->mOnMuteList; + } + + return result; +} + +// External accessiors. Maps 0.0 to 1.0 to internal values 0-400 with .5 == 100 +// internal = 400 * external^2 +F32 LLVoiceClient::getUserVolume(const LLUUID& id) +{ + F32 result = 0.0f; + + participantState *participant = findParticipantByID(id); + if(participant) + { + S32 ires = participant->mUserVolume; // 0-400 + result = sqrtf(((F32)ires) / 400.f); + } + + return result; +} + +void LLVoiceClient::setUserVolume(const LLUUID& id, F32 volume) +{ + participantState *participant = findParticipantByID(id); + if (participant) + { + // volume can amplify by as much as 4x! + S32 ivol = (S32)(400.f * volume * volume); + participant->mUserVolume = llclamp(ivol, 0, 400); + participant->mVolumeDirty = TRUE; + mVolumeDirty = TRUE; + } +} + + + +LLVoiceClient::serviceType LLVoiceClient::getServiceType(const LLUUID& id) +{ + serviceType result = serviceTypeUnknown; + + participantState *participant = findParticipantByID(id); + if(participant) + { + result = participant->mServiceType; + } + + return result; +} + +std::string LLVoiceClient::getGroupID(const LLUUID& id) +{ + std::string result; + + participantState *participant = findParticipantByID(id); + if(participant) + { + result = participant->mGroupID; + } + + return result; +} + +BOOL LLVoiceClient::getAreaVoiceDisabled() +{ + return mAreaVoiceDisabled; +} + +void LLVoiceClient::addObserver(LLVoiceClientParticipantObserver* observer) +{ + mObservers.insert(observer); +} + +void LLVoiceClient::removeObserver(LLVoiceClientParticipantObserver* observer) +{ + mObservers.erase(observer); +} + +void LLVoiceClient::notifyObservers() +{ + for (observer_set_t::iterator it = mObservers.begin(); + it != mObservers.end(); + ) + { + LLVoiceClientParticipantObserver* observer = *it; + observer->onChange(); + // In case onChange() deleted an entry. + it = mObservers.upper_bound(observer); + } +} + +void LLVoiceClient::addStatusObserver(LLVoiceClientStatusObserver* observer) +{ + mStatusObservers.insert(observer); +} + +void LLVoiceClient::removeStatusObserver(LLVoiceClientStatusObserver* observer) +{ + mStatusObservers.erase(observer); +} + +void LLVoiceClient::notifyStatusObservers(LLVoiceClientStatusObserver::EStatusType status) +{ + if(status == LLVoiceClientStatusObserver::ERROR_UNKNOWN) + { + switch(mVivoxErrorStatusCode) + { + case 20713: status = LLVoiceClientStatusObserver::ERROR_CHANNEL_FULL; break; + case 20714: status = LLVoiceClientStatusObserver::ERROR_CHANNEL_LOCKED; break; + } + + // Reset the error code to make sure it won't be reused later by accident. + mVivoxErrorStatusCode = 0; + } + + if (status == LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL + //NOT_FOUND || TEMPORARILY_UNAVAILABLE || REQUEST_TIMEOUT + && (mVivoxErrorStatusCode == 404 || mVivoxErrorStatusCode == 480 || mVivoxErrorStatusCode == 408)) + { + // call failed because other user was not available + // treat this as an error case + status = LLVoiceClientStatusObserver::ERROR_NOT_AVAILABLE; + + // Reset the error code to make sure it won't be reused later by accident. + mVivoxErrorStatusCode = 0; + } + + llinfos << " " << LLVoiceClientStatusObserver::status2string(status) << ", session URI " << mSessionURI << llendl; + + for (status_observer_set_t::iterator it = mStatusObservers.begin(); + it != mStatusObservers.end(); + ) + { + LLVoiceClientStatusObserver* observer = *it; + observer->onChange(status, mSessionURI, !mNonSpatialChannel); + // In case onError() deleted an entry. + it = mStatusObservers.upper_bound(observer); + } + +} + +//static +void LLVoiceClient::onAvatarNameLookup(const LLUUID& id, const char* first, const char* last, BOOL is_group, void* user_data) +{ + participantState* statep = gVoiceClient->findParticipantByID(id); + + if (statep) + { + statep->mDisplayName = llformat("%s %s", first, last); + } + + gVoiceClient->notifyObservers(); +} + +class LLViewerParcelVoiceInfo : public LLHTTPNode +{ + virtual void post( + LLHTTPNode::ResponsePtr response, + const LLSD& context, + const LLSD& input) const + { + //the parcel you are in has changed something about its + //voice information + + if ( input.has("body") ) + { + LLSD body = input["body"]; + + //body has "region_name" (str), "parcel_local_id"(int), + //"voice_credentials" (map). + + //body["voice_credentials"] has "channel_uri" (str), + //body["voice_credentials"] has "channel_credentials" (str) + if ( body.has("voice_credentials") ) + { + LLSD voice_credentials = body["voice_credentials"]; + std::string uri; + std::string credentials; + + if ( voice_credentials.has("channel_uri") ) + { + uri = voice_credentials["channel_uri"].asString(); + } + if ( voice_credentials.has("channel_credentials") ) + { + credentials = + voice_credentials["channel_credentials"].asString(); + } + + gVoiceClient->setSpatialChannel(uri, credentials); + } + } + } +}; + +class LLViewerRequiredVoiceVersion : public LLHTTPNode +{ + static BOOL sAlertedUser; + virtual void post( + LLHTTPNode::ResponsePtr response, + const LLSD& context, + const LLSD& input) const + { + //You received this messsage (most likely on region cross or + //teleport) + if ( input.has("body") && input["body"].has("major_version") ) + { + int major_voice_version = + input["body"]["major_version"].asInteger(); +// int minor_voice_version = +// input["body"]["minor_version"].asInteger(); + + if (gVoiceClient && + (major_voice_version > VOICE_MAJOR_VERSION) ) + { + if (!sAlertedUser) + { + //sAlertedUser = TRUE; + gViewerWindow->alertXml("VoiceVersionMismatch"); + gSavedSettings.setBOOL("EnableVoiceChat", FALSE); // toggles listener + } + } + } + } +}; +BOOL LLViewerRequiredVoiceVersion::sAlertedUser = FALSE; + +LLHTTPRegistration<LLViewerParcelVoiceInfo> + gHTTPRegistrationMessageParcelVoiceInfo( + "/message/ParcelVoiceInfo"); + +LLHTTPRegistration<LLViewerRequiredVoiceVersion> + gHTTPRegistrationMessageRequiredVoiceVersion( + "/message/RequiredVoiceVersion"); diff --git a/indra/newview/llvoiceclient.h b/indra/newview/llvoiceclient.h new file mode 100644 index 0000000000..d45b113e63 --- /dev/null +++ b/indra/newview/llvoiceclient.h @@ -0,0 +1,503 @@ +/** + * @file llvoiceclient.h + * @brief Declaration of LLVoiceClient class which is the interface to the voice client process. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ +#ifndef LL_VOICE_CLIENT_H +#define LL_VOICE_CLIENT_H + +// This would create a circular reference -- just do a forward definition of necessary class names. +//#include "llvoavatar.h" +class LLVOAvatar; +class LLVivoxProtocolParser; + +#include "lliopipe.h" +#include "llpumpio.h" +#include "llchainio.h" +#include "lliosocket.h" +#include "v3math.h" +#include "llframetimer.h" +#include "llviewerregion.h" + +class LLVoiceClientParticipantObserver +{ +public: + virtual ~LLVoiceClientParticipantObserver() { } + virtual void onChange() = 0; +}; + +class LLVoiceClientStatusObserver +{ +public: + typedef enum e_voice_status_type + { + STATUS_LOGIN_RETRY, + STATUS_LOGGED_IN, + STATUS_JOINING, + STATUS_JOINED, + STATUS_LEFT_CHANNEL, + BEGIN_ERROR_STATUS, + ERROR_CHANNEL_FULL, + ERROR_CHANNEL_LOCKED, + ERROR_NOT_AVAILABLE, + ERROR_UNKNOWN + } EStatusType; + + virtual ~LLVoiceClientStatusObserver() { } + virtual void onChange(EStatusType status, const std::string &channelURI, bool proximal) = 0; + + static const char *status2string(EStatusType inStatus); +}; + +class LLVoiceClient: public LLSingleton<LLVoiceClient> +{ + LOG_CLASS(LLVoiceClient); + public: + LLVoiceClient(); + ~LLVoiceClient(); + + public: + static void init(LLPumpIO *pump); // Call this once at application startup (creates connector) + static void terminate(); // Call this to clean up during shutdown + + protected: + bool writeString(const std::string &str); + + public: + + enum serviceType + { + serviceTypeUnknown, // Unknown, returned if no data on the avatar is available + serviceTypeA, // spatialized local chat + serviceTypeB, // remote multi-party chat + serviceTypeC // one-to-one and small group chat + }; + static F32 OVERDRIVEN_POWER_LEVEL; + + ///////////////////////////// + // session control messages + void connect(); + + void connectorCreate(); + void connectorShutdown(); + + void requestVoiceAccountProvision(S32 retries = 3); + void userAuthorized( + const std::string& firstName, + const std::string& lastName, + const LLUUID &agentID); + void login(const std::string& accountName, const std::string &password); + void loginSendMessage(); + void logout(); + void logoutSendMessage(); + + void channelGetListSendMessage(); + void sessionCreateSendMessage(); + void sessionConnectSendMessage(); + void sessionTerminate(); + void sessionTerminateSendMessage(); + void sessionTerminateByHandle(std::string &sessionHandle); + + void getCaptureDevicesSendMessage(); + void getRenderDevicesSendMessage(); + + void clearCaptureDevices(); + void addCaptureDevice(const std::string& name); + void setCaptureDevice(const std::string& name); + + void clearRenderDevices(); + void addRenderDevice(const std::string& name); + void setRenderDevice(const std::string& name); + + void tuningStart(); + void tuningStop(); + bool inTuningMode(); + + void tuningRenderStartSendMessage(const std::string& name, bool loop); + void tuningRenderStopSendMessage(); + + void tuningCaptureStartSendMessage(int duration); + void tuningCaptureStopSendMessage(); + + void tuningSetMicVolume(float volume); + void tuningSetSpeakerVolume(float volume); + float tuningGetEnergy(void); + + // This returns true when it's safe to bring up the "device settings" dialog in the prefs. + // i.e. when the daemon is running and connected, and the device lists are populated. + bool deviceSettingsAvailable(); + + // Requery the vivox daemon for the current list of input/output devices. + // If you pass true for clearCurrentList, deviceSettingsAvailable() will be false until the query has completed + // (use this if you want to know when it's done). + // If you pass false, you'll have no way to know when the query finishes, but the device lists will not appear empty in the interim. + void refreshDeviceLists(bool clearCurrentList = true); + + // Call this if the connection to the daemon terminates unexpectedly. It will attempt to reset everything and relaunch. + void daemonDied(); + + // Call this if we're just giving up on voice (can't provision an account, etc.). It will clean up and go away. + void giveUp(); + + ///////////////////////////// + // Response/Event handlers + void connectorCreateResponse(int statusCode, std::string &statusString, std::string &connectorHandle); + void loginResponse(int statusCode, std::string &statusString, std::string &accountHandle); + void channelGetListResponse(int statusCode, std::string &statusString); + void sessionCreateResponse(int statusCode, std::string &statusString, std::string &sessionHandle); + void sessionConnectResponse(int statusCode, std::string &statusString); + void sessionTerminateResponse(int statusCode, std::string &statusString); + void logoutResponse(int statusCode, std::string &statusString); + void connectorShutdownResponse(int statusCode, std::string &statusString); + + void loginStateChangeEvent(std::string &accountHandle, int statusCode, std::string &statusString, int state); + void sessionNewEvent(std::string &accountHandle, std::string &eventSessionHandle, int state, std::string &nameString, std::string &uriString); + void sessionStateChangeEvent(std::string &uriString, int statusCode, std::string &statusString, std::string &sessionHandle, int state, bool isChannel, std::string &nameString); + void participantStateChangeEvent(std::string &uriString, int statusCode, std::string &statusString, int state, std::string &nameString, std::string &displayNameString, int participantType); + void participantPropertiesEvent(std::string &uriString, int statusCode, std::string &statusString, bool isLocallyMuted, bool isModeratorMuted, bool isSpeaking, int volume, F32 energy); + void auxAudioPropertiesEvent(F32 energy); + + void muteListChanged(); + + ///////////////////////////// + // Sending updates of current state + void setCameraPosition(const LLVector3d &position, const LLVector3 &velocity, const LLMatrix3 &rot); + void setAvatarPosition(const LLVector3d &position, const LLVector3 &velocity, const LLMatrix3 &rot); + bool channelFromRegion(LLViewerRegion *region, std::string &name); + void leaveChannel(void); // call this on logout or teleport begin + + + void setMuteMic(bool muted); // Use this to mute the local mic (for when the client is minimized, etc), ignoring user PTT state. + void setUserPTTState(bool ptt); + bool getUserPTTState(); + void toggleUserPTTState(void); + void setVoiceEnabled(bool enabled); + static bool voiceEnabled(); + void setUsePTT(bool usePTT); + void setPTTIsToggle(bool PTTIsToggle); + void setPTTKey(std::string &key); + void setEarLocation(S32 loc); + void setVoiceVolume(F32 volume); + void setMicGain(F32 volume); + void setUserVolume(const LLUUID& id, F32 volume); // set's volume for specified agent, from 0-1 (where .5 is nominal) + void setVivoxDebugServerName(std::string &serverName); + + // PTT key triggering + void keyDown(KEY key, MASK mask); + void keyUp(KEY key, MASK mask); + void middleMouseState(bool down); + + ///////////////////////////// + // Accessors for data related to nearby speakers + BOOL getVoiceEnabled(const LLUUID& id); // true if we've received data for this avatar + BOOL getIsSpeaking(const LLUUID& id); + F32 getCurrentPower(const LLUUID& id); // "power" is related to "amplitude" in a defined way. I'm just not sure what the formula is... + BOOL getPTTPressed(const LLUUID& id); // This is the inverse of the "locally muted" property. + BOOL getOnMuteList(const LLUUID& id); + F32 getUserVolume(const LLUUID& id); + LLString getDisplayName(const LLUUID& id); + + // MBW -- XXX -- Not sure how to get this data out of the TVC + BOOL getUsingPTT(const LLUUID& id); + serviceType getServiceType(const LLUUID& id); // type of chat the user is involved in (see bHear scope doc for definitions of A/B/C) + std::string getGroupID(const LLUUID& id); // group ID if the user is in group chat (empty string if not applicable) + + ///////////////////////////// + BOOL getAreaVoiceDisabled(); // returns true if the area the avatar is in is speech-disabled. + // Use this to determine whether to show a "no speech" icon in the menu bar. + + struct participantState + { + public: + participantState(const std::string &uri); + std::string mURI; + std::string mName; + std::string mDisplayName; + bool mPTT; + bool mIsSpeaking; + LLFrameTimer mSpeakingTimeout; + F32 mLastSpokeTimestamp; + F32 mPower; + int mVolume; + serviceType mServiceType; + std::string mGroupID; + bool mOnMuteList; // true if this avatar is on the user's mute list (and should be muted) + int mUserVolume; + bool mVolumeDirty; // true if this participant needs a volume command sent (either mOnMuteList or mUserVolume has changed) + bool mAvatarIDValid; + LLUUID mAvatarID; + }; + typedef std::map<std::string, participantState*> participantMap; + + participantState *findParticipant(const std::string &uri); + participantState *findParticipantByAvatar(LLVOAvatar *avatar); + participantState *findParticipantByID(const LLUUID& id); + + participantMap *getParticipantList(void); + + void addObserver(LLVoiceClientParticipantObserver* observer); + void removeObserver(LLVoiceClientParticipantObserver* observer); + + void addStatusObserver(LLVoiceClientStatusObserver* observer); + void removeStatusObserver(LLVoiceClientStatusObserver* observer); + + static void onAvatarNameLookup(const LLUUID& id, const char* first, const char* last, BOOL is_group, void* user_data); + typedef std::vector<std::string> deviceList; + + deviceList *getCaptureDevices(); + deviceList *getRenderDevices(); + + void setNonSpatialChannel( + const std::string &uri, + const std::string &credentials); + void setSpatialChannel( + const std::string &uri, + const std::string &credentials); + void callUser(LLUUID &uuid); + void answerInvite(std::string &sessionHandle, LLUUID& other_user_id); + void declineInvite(std::string &sessionHandle); + void leaveNonSpatialChannel(); + + // Returns the URI of the current channel, or an empty string if not currently in a channel. + // NOTE that it will return an empty string if it's in the process of joining a channel. + std::string getCurrentChannel(); + + // returns true iff the user is currently in a proximal (local spatial) channel. + // Note that gestures should only fire if this returns true. + bool inProximalChannel(); + + std::string sipURIFromID(const LLUUID &id); + + private: + + // internal state for a simple state machine. This is used to deal with the asynchronous nature of some of the messages. + // Note: if you change this list, please make corresponding changes to LLVoiceClient::state2string(). + enum state + { + stateDisabled, // Voice is turned off. + stateStart, // Class is initialized, socket is created + stateDaemonLaunched, // Daemon has been launched + stateConnecting, // connect() call has been issued + stateIdle, // socket is connected, ready for messaging + stateConnectorStart, // connector needs to be started + stateConnectorStarting, // waiting for connector handle + stateConnectorStarted, // connector handle received + stateMicTuningNoLogin, // mic tuning before login + stateLoginRetry, // need to retry login (failed due to changing password) + stateLoginRetryWait, // waiting for retry timer + stateNeedsLogin, // send login request + stateLoggingIn, // waiting for account handle + stateLoggedIn, // account handle received + stateNoChannel, // + stateMicTuningLoggedIn, // mic tuning for a logged in user + stateSessionCreate, // need to send Session.Create command + stateSessionConnect, // need to send Session.Connect command + stateJoiningSession, // waiting for session handle + stateSessionJoined, // session handle received + stateRunning, // in session, steady state + stateLeavingSession, // waiting for terminate session response + stateSessionTerminated, // waiting for terminate session response + + stateLoggingOut, // waiting for logout response + stateLoggedOut, // logout response received + stateConnectorStopping, // waiting for connector stop + stateConnectorStopped, // connector stop received + + // We go to this state if the login fails because the account needs to be provisioned. + + // error states. No way to recover from these yet. + stateConnectorFailed, + stateConnectorFailedWaiting, + stateLoginFailed, + stateLoginFailedWaiting, + stateJoinSessionFailed, + stateJoinSessionFailedWaiting, + + stateJail // Go here when all else has failed. Nothing will be retried, we're done. + }; + + state mState; + bool mSessionTerminateRequested; + bool mNonSpatialChannel; + + void setState(state inState); + state getState(void) { return mState; }; + static const char *state2string(state inState); + + void stateMachine(); + static void idle(void *user_data); + + LLHost mDaemonHost; + LLSocket::ptr_t mSocket; + bool mConnected; + + void closeSocket(void); + + LLPumpIO *mPump; + friend class LLVivoxProtocolParser; + + std::string mAccountName; + std::string mAccountPassword; + std::string mAccountDisplayName; + std::string mAccountFirstName; + std::string mAccountLastName; + + std::string mNextP2PSessionURI; // URI of the P2P session to join next + std::string mNextSessionURI; // URI of the session to join next + std::string mNextSessionHandle; // Session handle of the session to join next + std::string mNextSessionHash; // Password hash for the session to join next + bool mNextSessionSpatial; // Will next session be a spatial chat? + bool mNextSessionNoReconnect; // Next session should not auto-reconnect (i.e. user -> user chat) + bool mNextSessionResetOnClose; // If this is true, go back to spatial chat when the next session terminates. + + std::string mSessionStateEventHandle; // session handle received in SessionStateChangeEvents + std::string mSessionStateEventURI; // session URI received in SessionStateChangeEvents + + bool mTuningMode; + float mTuningEnergy; + std::string mTuningAudioFile; + int mTuningMicVolume; + bool mTuningMicVolumeDirty; + int mTuningSpeakerVolume; + bool mTuningSpeakerVolumeDirty; + bool mTuningCaptureRunning; + + std::string mSpatialSessionURI; + + bool mSessionResetOnClose; + + int mVivoxErrorStatusCode; + std::string mVivoxErrorStatusString; + + std::string mChannelName; // Name of the channel to be looked up + bool mAreaVoiceDisabled; + std::string mSessionURI; // URI of the session we're in. + bool mSessionP2P; // true if this session is a p2p call + + S32 mCurrentParcelLocalID; // Used to detect parcel boundary crossings + std::string mCurrentRegionName; // Used to detect parcel boundary crossings + + std::string mConnectorHandle; // returned by "Create Connector" message + std::string mAccountHandle; // returned by login message + std::string mSessionHandle; // returned by ? + U32 mCommandCookie; + + std::string mAccountServerName; + std::string mAccountServerURI; + + int mLoginRetryCount; + + participantMap mParticipantMap; + bool mParticipantMapChanged; + + deviceList mCaptureDevices; + deviceList mRenderDevices; + + std::string mCaptureDevice; + std::string mRenderDevice; + bool mCaptureDeviceDirty; + bool mRenderDeviceDirty; + + participantState *addParticipant(const std::string &uri); + // Note: after removeParticipant returns, the participant* that was passed to it will have been deleted. + // Take care not to use the pointer again after that. + void removeParticipant(participantState *participant); + void removeAllParticipants(); + + void updateMuteState(participantState *participant); + + typedef std::map<std::string, std::string> channelMap; + channelMap mChannelMap; + + // These are used by the parser when processing a channel list response. + void clearChannelMap(void); + void addChannelMapEntry(std::string &name, std::string &uri); + std::string findChannelURI(std::string &name); + + // This should be called when the code detects we have changed parcels. + // It initiates the call to the server that gets the parcel channel. + void parcelChanged(); + + void switchChannel(std::string uri = "", bool spatial = true, bool noReconnect = false, std::string hash = ""); + void joinSession(std::string handle, std::string uri); + + std::string nameFromAvatar(LLVOAvatar *avatar); + std::string nameFromID(const LLUUID &id); + bool IDFromName(const std::string name, LLUUID &uuid); + std::string displayNameFromAvatar(LLVOAvatar *avatar); + std::string sipURIFromAvatar(LLVOAvatar *avatar); + std::string sipURIFromName(std::string &name); + + void sendPositionalUpdate(void); + + void buildSetCaptureDevice(std::ostringstream &stream); + void buildSetRenderDevice(std::ostringstream &stream); + + void enforceTether(void); + + bool mSpatialCoordsDirty; + + LLVector3d mCameraPosition; + LLVector3d mCameraRequestedPosition; + LLVector3 mCameraVelocity; + LLMatrix3 mCameraRot; + + LLVector3d mAvatarPosition; + LLVector3 mAvatarVelocity; + LLMatrix3 mAvatarRot; + + bool mPTTDirty; + bool mPTT; + + bool mUsePTT; + bool mPTTIsMiddleMouse; + KEY mPTTKey; + bool mPTTIsToggle; + bool mUserPTTState; + bool mMuteMic; + + // Set to true when the mute state of someone in the participant list changes. + // The code will have to walk the list to find the changed participant(s). + bool mVolumeDirty; + + enum + { + earLocCamera = 0, // ear at camera + earLocAvatar, // ear at avatar + earLocMixed // ear at avatar location/camera direction + }; + + S32 mEarLocation; + + bool mSpeakerVolumeDirty; + bool mSpeakerMuteDirty; + int mSpeakerVolume; + + int mMicVolume; + bool mMicVolumeDirty; + + bool mVoiceEnabled; + bool mWriteInProgress; + std::string mWriteString; + size_t mWriteOffset; + + LLTimer mUpdateTimer; + + typedef std::set<LLVoiceClientParticipantObserver*> observer_set_t; + observer_set_t mObservers; + + void notifyObservers(); + + typedef std::set<LLVoiceClientStatusObserver*> status_observer_set_t; + status_observer_set_t mStatusObservers; + + void notifyStatusObservers(LLVoiceClientStatusObserver::EStatusType status); +}; + +extern LLVoiceClient *gVoiceClient; + +#endif //LL_VOICE_CLIENT_H + + diff --git a/indra/newview/llvoicevisualizer.cpp b/indra/newview/llvoicevisualizer.cpp new file mode 100644 index 0000000000..fb3fd8af15 --- /dev/null +++ b/indra/newview/llvoicevisualizer.cpp @@ -0,0 +1,439 @@ +/** + * @file llvoicevisualizer.cpp + * @brief Draws in-world speaking indicators. + * + * Copyright (c) 2000-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +//---------------------------------------------------------------------- +// Voice Visualizer +// author: JJ Ventrella +// (information about this stuff can be found in "llvoicevisualizer.h") +//---------------------------------------------------------------------- +#include "llviewerprecompiledheaders.h" +#include "llviewercontrol.h" +#include "llglheaders.h" +#include "llsphere.h" +#include "llvoicevisualizer.h" +#include "llviewercamera.h" +#include "llviewerobject.h" +#include "llimagegl.h" +#include "llviewerimage.h" +#include "llviewerimagelist.h" +#include "llvoiceclient.h" + +//brent's wave image +//29de489d-0491-fb00-7dab-f9e686d31e83 + + +//-------------------------------------------------------------------------------------- +// sound symbol constants +//-------------------------------------------------------------------------------------- +const F32 HEIGHT_ABOVE_HEAD = 0.3f; // how many meters vertically above the av's head the voice symbol will appear +const F32 RED_THRESHOLD = LLVoiceClient::OVERDRIVEN_POWER_LEVEL; // value above which speaking amplitude causes the voice symbol to turn red +const F32 GREEN_THRESHOLD = 0.2f; // value above which speaking amplitude causes the voice symbol to turn green +const F32 FADE_OUT_DURATION = 0.4f; // how many seconds it takes for a pair of waves to fade away +const F32 EXPANSION_RATE = 1.0f; // how many seconds it takes for the waves to expand to twice their original size +const F32 EXPANSION_MAX = 1.5f; // maximum size scale to which the waves can expand before popping back to 1.0 +const F32 WAVE_WIDTH_SCALE = 0.03f; // base width of the waves +const F32 WAVE_HEIGHT_SCALE = 0.02f; // base height of the waves +const F32 BASE_BRIGHTNESS = 0.7f; // gray level of the voice indicator when quiet (below green threshold) +const F32 DOT_SIZE = 0.05f; // size of the dot billboard texture +const F32 DOT_OPACITY = 0.7f; // how opaque the dot is +const F32 WAVE_MOTION_RATE = 1.5f; // scalar applied to consecutive waves as a function of speaking amplitude + +//-------------------------------------------------------------------------------------- +// gesticulation constants +//-------------------------------------------------------------------------------------- +const F32 DEFAULT_MINIMUM_GESTICULATION_AMPLITUDE = 0.2f; +const F32 DEFAULT_MAXIMUM_GESTICULATION_AMPLITUDE = 1.0f; + +//-------------------------------------------------------------------------------------- +// other constants +//-------------------------------------------------------------------------------------- +const F32 ONE_HALF = 1.0f; // to clarify intent and reduce magic numbers in the code. +const LLVector3 WORLD_UPWARD_DIRECTION = LLVector3( 0.0f, 0.0f, 1.0f ); // Z is up in SL + +//----------------------------------------------- +// constructor +//----------------------------------------------- +LLVoiceVisualizer::LLVoiceVisualizer( const U8 type ) +:LLHUDEffect( type ) +{ + mCurrentTime = mTimer.getTotalSeconds(); + mPreviousTime = mCurrentTime; + mVoiceSourceWorldPosition = LLVector3( 0.0f, 0.0f, 0.0f ); + mSpeakingAmplitude = 0.0f; + mCurrentlySpeaking = false; + mVoiceEnabled = false; + mMinGesticulationAmplitude = DEFAULT_MINIMUM_GESTICULATION_AMPLITUDE; + mMaxGesticulationAmplitude = DEFAULT_MAXIMUM_GESTICULATION_AMPLITUDE; + mSoundSymbol.mActive = true; + mSoundSymbol.mPosition = LLVector3( 0.0f, 0.0f, 0.0f ); + + mTimer.reset(); + + LLUUID sound_level_img[] = + { + LLUUID(gSavedSettings.getString("VoiceImageLevel0")), + LLUUID(gSavedSettings.getString("VoiceImageLevel1")), + LLUUID(gSavedSettings.getString("VoiceImageLevel2")), + LLUUID(gSavedSettings.getString("VoiceImageLevel3")), + LLUUID(gSavedSettings.getString("VoiceImageLevel4")), + LLUUID(gSavedSettings.getString("VoiceImageLevel5")), + LLUUID(gSavedSettings.getString("VoiceImageLevel6")) + }; + + for (int i=0; i<NUM_VOICE_SYMBOL_WAVES; i++) + { + mSoundSymbol.mWaveFadeOutStartTime [i] = mCurrentTime; + mSoundSymbol.mTexture [i] = gImageList.getUIImageByID(sound_level_img[i]); + mSoundSymbol.mWaveActive [i] = false; + mSoundSymbol.mWaveOpacity [i] = 1.0f; + mSoundSymbol.mWaveExpansion [i] = 1.0f; + } + +}//--------------------------------------------------- + +//--------------------------------------------------- +void LLVoiceVisualizer::setMinGesticulationAmplitude( F32 m ) +{ + mMinGesticulationAmplitude = m; + +}//--------------------------------------------------- + +//--------------------------------------------------- +void LLVoiceVisualizer::setMaxGesticulationAmplitude( F32 m ) +{ + mMaxGesticulationAmplitude = m; + +}//--------------------------------------------------- + +//--------------------------------------------------- +void LLVoiceVisualizer::setVoiceEnabled( bool v ) +{ + mVoiceEnabled = v; + +}//--------------------------------------------------- + +//--------------------------------------------------- +void LLVoiceVisualizer::setStartSpeaking() +{ + mCurrentlySpeaking = true; + mSoundSymbol.mActive = true; + +}//--------------------------------------------------- + + +//--------------------------------------------------- +bool LLVoiceVisualizer::getCurrentlySpeaking() +{ + return mCurrentlySpeaking; + +}//--------------------------------------------------- + + +//--------------------------------------------------- +void LLVoiceVisualizer::setStopSpeaking() +{ + mCurrentlySpeaking = false; + mSpeakingAmplitude = 0.0f; + +}//--------------------------------------------------- + + +//--------------------------------------------------- +void LLVoiceVisualizer::setSpeakingAmplitude( F32 a ) +{ + mSpeakingAmplitude = a; + +}//--------------------------------------------------- + + +//--------------------------------------------------- +// this method is inherited from HUD Effect +//--------------------------------------------------- +void LLVoiceVisualizer::render() +{ + if ( ! mVoiceEnabled ) + { + return; + } + + if ( mSoundSymbol.mActive ) + { + mPreviousTime = mCurrentTime; + mCurrentTime = mTimer.getTotalSeconds(); + + //--------------------------------------------------------------- + // set the sound symbol position over the source (avatar's head) + //--------------------------------------------------------------- + mSoundSymbol.mPosition = mVoiceSourceWorldPosition + WORLD_UPWARD_DIRECTION * HEIGHT_ABOVE_HEAD; + + //--------------------------------------------------------------- + // some gl state + //--------------------------------------------------------------- + LLGLEnable tex( GL_TEXTURE_2D ); + LLGLEnable blend( GL_BLEND ); + + //------------------------------------------------------------- + // create coordinates of the geometry for the dot + //------------------------------------------------------------- + LLVector3 l = gCamera->getLeftAxis() * DOT_SIZE; + LLVector3 u = gCamera->getUpAxis() * DOT_SIZE; + + LLVector3 bottomLeft = mSoundSymbol.mPosition + l - u; + LLVector3 bottomRight = mSoundSymbol.mPosition - l - u; + LLVector3 topLeft = mSoundSymbol.mPosition + l + u; + LLVector3 topRight = mSoundSymbol.mPosition - l + u; + + //----------------------------- + // bind texture 0 (the dot) + //----------------------------- + mSoundSymbol.mTexture[0]->bind(); + + //------------------------------------------------------------- + // now render the dot + //------------------------------------------------------------- + glColor4fv( LLColor4( 1.0f, 1.0f, 1.0f, DOT_OPACITY ).mV ); + + glBegin( GL_TRIANGLE_STRIP ); + glTexCoord2i( 0, 0 ); glVertex3fv( bottomLeft.mV ); + glTexCoord2i( 1, 0 ); glVertex3fv( bottomRight.mV ); + glTexCoord2i( 0, 1 ); glVertex3fv( topLeft.mV ); + glEnd(); + + glBegin( GL_TRIANGLE_STRIP ); + glTexCoord2i( 1, 0 ); glVertex3fv( bottomRight.mV ); + glTexCoord2i( 1, 1 ); glVertex3fv( topRight.mV ); + glTexCoord2i( 0, 1 ); glVertex3fv( topLeft.mV ); + glEnd(); + + + + //-------------------------------------------------------------------------------------- + // if currently speaking, trigger waves (1 through 6) based on speaking amplitude + //-------------------------------------------------------------------------------------- + if ( mCurrentlySpeaking ) + { + F32 min = 0.2f; + F32 max = 0.7f; + F32 fraction = ( mSpeakingAmplitude - min ) / ( max - min ); + + // in case mSpeakingAmplitude > max.... + if ( fraction > 1.0f ) + { + fraction = 1.0f; + } + + S32 level = 1 + (int)( fraction * ( NUM_VOICE_SYMBOL_WAVES - 2 ) ); + + for (int i=0; i<level+1; i++) + { + mSoundSymbol.mWaveActive [i] = true; + mSoundSymbol.mWaveOpacity [i] = 1.0f; + mSoundSymbol.mWaveFadeOutStartTime [i] = mCurrentTime; + } + + } // if currently speaking + + //--------------------------------------------------- + // determine color + //--------------------------------------------------- + F32 red = 0.0f; + F32 green = 0.0f; + F32 blue = 0.0f; + if ( mSpeakingAmplitude < RED_THRESHOLD ) + { + if ( mSpeakingAmplitude < GREEN_THRESHOLD ) + { + red = BASE_BRIGHTNESS; + green = BASE_BRIGHTNESS; + blue = BASE_BRIGHTNESS; + } + else + { + //--------------------------------------------------- + // fade from gray to bright green + //--------------------------------------------------- + F32 fraction = ( mSpeakingAmplitude - GREEN_THRESHOLD ) / ( 1.0f - GREEN_THRESHOLD ); + red = BASE_BRIGHTNESS - ( fraction * BASE_BRIGHTNESS ); + green = BASE_BRIGHTNESS + fraction * ( 1.0f - BASE_BRIGHTNESS ); + blue = BASE_BRIGHTNESS - ( fraction * BASE_BRIGHTNESS ); + } + } + else + { + //--------------------------------------------------- + // redish + //--------------------------------------------------- + red = 1.0f; + green = 0.2f; + blue = 0.2f; + } + + for (int i=0; i<NUM_VOICE_SYMBOL_WAVES; i++) + { + if ( mSoundSymbol.mWaveActive[i] ) + { + F32 fadeOutFraction = (F32)( mCurrentTime - mSoundSymbol.mWaveFadeOutStartTime[i] ) / FADE_OUT_DURATION; + + mSoundSymbol.mWaveOpacity[i] = 1.0f - fadeOutFraction; + + if ( mSoundSymbol.mWaveOpacity[i] < 0.0f ) + { + mSoundSymbol.mWaveFadeOutStartTime [i] = mCurrentTime; + mSoundSymbol.mWaveOpacity [i] = 0.0f; + mSoundSymbol.mWaveActive [i] = false; + } + + //---------------------------------------------------------------------------------- + // This is where we calculate the expansion of the waves - that is, the + // rate at which they are scaled greater than 1.0 so that they grow over time. + //---------------------------------------------------------------------------------- + F32 timeSlice = (F32)( mCurrentTime - mPreviousTime ); + F32 waveSpeed = mSpeakingAmplitude * WAVE_MOTION_RATE; + mSoundSymbol.mWaveExpansion[i] *= ( 1.0f + EXPANSION_RATE * timeSlice * waveSpeed ); + + if ( mSoundSymbol.mWaveExpansion[i] > EXPANSION_MAX ) + { + mSoundSymbol.mWaveExpansion[i] = 1.0f; + } + + //---------------------------------------------------------------------------------- + // create geometry for the wave billboard textures + //---------------------------------------------------------------------------------- + F32 width = i * WAVE_WIDTH_SCALE * mSoundSymbol.mWaveExpansion[i]; + F32 height = i * WAVE_HEIGHT_SCALE * mSoundSymbol.mWaveExpansion[i]; + + LLVector3 l = gCamera->getLeftAxis() * width; + LLVector3 u = gCamera->getUpAxis() * height; + + LLVector3 bottomLeft = mSoundSymbol.mPosition + l - u; + LLVector3 bottomRight = mSoundSymbol.mPosition - l - u; + LLVector3 topLeft = mSoundSymbol.mPosition + l + u; + LLVector3 topRight = mSoundSymbol.mPosition - l + u; + + glColor4fv( LLColor4( red, green, blue, mSoundSymbol.mWaveOpacity[i] ).mV ); + mSoundSymbol.mTexture[i]->bind(); + + //--------------------------------------------------- + // now, render the mofo + //--------------------------------------------------- + glBegin( GL_TRIANGLE_STRIP ); + glTexCoord2i( 0, 0 ); glVertex3fv( bottomLeft.mV ); + glTexCoord2i( 1, 0 ); glVertex3fv( bottomRight.mV ); + glTexCoord2i( 0, 1 ); glVertex3fv( topLeft.mV ); + glEnd(); + + glBegin( GL_TRIANGLE_STRIP ); + glTexCoord2i( 1, 0 ); glVertex3fv( bottomRight.mV ); + glTexCoord2i( 1, 1 ); glVertex3fv( topRight.mV ); + glTexCoord2i( 0, 1 ); glVertex3fv( topLeft.mV ); + glEnd(); + + } //if ( mSoundSymbol.mWaveActive[i] ) + + }// for loop + + }//if ( mSoundSymbol.mActive ) + +}//--------------------------------------------------- + + + + + +//--------------------------------------------------- +void LLVoiceVisualizer::setVoiceSourceWorldPosition( const LLVector3 &p ) +{ + mVoiceSourceWorldPosition = p; + +}//--------------------------------------------------- + +//--------------------------------------------------- +VoiceGesticulationLevel LLVoiceVisualizer::getCurrentGesticulationLevel() +{ + VoiceGesticulationLevel gesticulationLevel = VOICE_GESTICULATION_LEVEL_OFF; //default + + //----------------------------------------------------------------------------------------- + // Within the range of gesticulation amplitudes, the sound signal is split into + // three equal amplitude regimes, each specifying one of three gesticulation levels. + //----------------------------------------------------------------------------------------- + F32 range = mMaxGesticulationAmplitude - mMinGesticulationAmplitude; + + if ( mSpeakingAmplitude > mMinGesticulationAmplitude + range * 0.66666f ) { gesticulationLevel = VOICE_GESTICULATION_LEVEL_HIGH; } + else if ( mSpeakingAmplitude > mMinGesticulationAmplitude + range * 0.33333f ) { gesticulationLevel = VOICE_GESTICULATION_LEVEL_MEDIUM; } + else if ( mSpeakingAmplitude > mMinGesticulationAmplitude + range * 0.00000f ) { gesticulationLevel = VOICE_GESTICULATION_LEVEL_LOW; } + + return gesticulationLevel; + +}//--------------------------------------------------- + + + +//------------------------------------ +// Destructor +//------------------------------------ +LLVoiceVisualizer::~LLVoiceVisualizer() +{ +}//---------------------------------------------- + + +//--------------------------------------------------- +// "packData" is inherited from HUDEffect +//--------------------------------------------------- +void LLVoiceVisualizer::packData(LLMessageSystem *mesgsys) +{ + // Pack the default data + LLHUDEffect::packData(mesgsys); + + // TODO -- pack the relevant data for voice effects + // we'll come up with some cool configurations....TBD + //U8 packed_data[41]; + //mesgsys->addBinaryDataFast(_PREHASH_TypeData, packed_data, 41); + U8 packed_data = 0; + mesgsys->addBinaryDataFast(_PREHASH_TypeData, &packed_data, 1); +} + + +//--------------------------------------------------- +// "unpackData" is inherited from HUDEffect +//--------------------------------------------------- +void LLVoiceVisualizer::unpackData(LLMessageSystem *mesgsys, S32 blocknum) +{ + // TODO -- find the speaker, unpack binary data, set the properties of this effect + /* + LLHUDEffect::unpackData(mesgsys, blocknum); + LLUUID source_id; + LLUUID target_id; + S32 size = mesgsys->getSizeFast(_PREHASH_Effect, blocknum, _PREHASH_TypeData); + if (size != 1) + { + llwarns << "Voice effect with bad size " << size << llendl; + return; + } + mesgsys->getBinaryDataFast(_PREHASH_Effect, _PREHASH_TypeData, packed_data, 1, blocknum); + */ +} + + +//------------------------------------------------------------------ +// this method is inherited from HUD Effect +//------------------------------------------------------------------ +void LLVoiceVisualizer::markDead() +{ + mCurrentlySpeaking = false; + mVoiceEnabled = false; + mSoundSymbol.mActive = false; + +}//------------------------------------------------------------------ + + + + + + + + diff --git a/indra/newview/llvoicevisualizer.h b/indra/newview/llvoicevisualizer.h new file mode 100644 index 0000000000..bd54bd435b --- /dev/null +++ b/indra/newview/llvoicevisualizer.h @@ -0,0 +1,119 @@ +/** + * @file llvoicevisualizer.h + * @brief Draws in-world speaking indicators. + * + * Copyright (c) 2000-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +//-------------------------------------------------------------------- +// +// VOICE VISUALIZER +// author: JJ Ventrella, Linden Lab +// (latest update to this info: Jan 18, 2007) +// +// The Voice Visualizer is responsible for taking realtime signals from actual users speaking and +// visualizing this speech in two forms: +// +// (1) as a dynamic sound symbol (also referred to as the "voice indicator" that appears over the avatar's head +// (2) as gesticulation events that are used to trigger avatr gestures +// +// The input for the voice visualizer is a continual stream of voice amplitudes. + +//----------------------------------------------------------------------------- +#ifndef LL_VOICE_VISUALIZER_H +#define LL_VOICE_VISUALIZER_H + +#include "llhudeffect.h" + +//----------------------------------------------------------------------------------------------- +// The values of voice gesticulation represent energy levels for avatar animation, based on +// amplitude surge events parsed from the voice signal. These are made available so that +// the appropriate kind of avatar animation can be triggered, and thereby simulate the physical +// motion effects of speech. It is recommended that multiple body parts be animated as well as +// lips, such as head, shoulders, and hands, with large gestures used when the energy level is high. +//----------------------------------------------------------------------------------------------- +enum VoiceGesticulationLevel +{ + VOICE_GESTICULATION_LEVEL_OFF = -1, + VOICE_GESTICULATION_LEVEL_LOW = 0, + VOICE_GESTICULATION_LEVEL_MEDIUM, + VOICE_GESTICULATION_LEVEL_HIGH, + NUM_VOICE_GESTICULATION_LEVELS +}; + +const static int NUM_VOICE_SYMBOL_WAVES = 7; + +//---------------------------------------------------- +// LLVoiceVisualizer class +//---------------------------------------------------- +class LLVoiceVisualizer : public LLHUDEffect +{ + //--------------------------------------------------- + // public methods + //--------------------------------------------------- + public: + LLVoiceVisualizer ( const U8 type ); //constructor + ~LLVoiceVisualizer(); //destructor + + friend class LLHUDObject; + + void setVoiceSourceWorldPosition( const LLVector3 &p ); // this should be the position of the speaking avatar's head + void setMinGesticulationAmplitude( F32 ); // the lower range of meaningful amplitude for setting gesticulation level + void setMaxGesticulationAmplitude( F32 ); // the upper range of meaningful amplitude for setting gesticulation level + void setStartSpeaking(); // tell me when the av starts speaking + void setVoiceEnabled( bool ); // tell me whether or not the user is voice enabled + void setSpeakingAmplitude( F32 ); // tell me how loud the av is speaking (ranges from 0 to 1) + void setStopSpeaking(); // tell me when the av stops speaking + bool getCurrentlySpeaking(); // the get for the above set + VoiceGesticulationLevel getCurrentGesticulationLevel(); // based on voice amplitude, I'll give you the current "energy level" of avatar speech + + void render(); // inherited from HUD Effect + void packData(LLMessageSystem *mesgsys); // inherited from HUD Effect + void unpackData(LLMessageSystem *mesgsys, S32 blocknum); // inherited from HUD Effect + void markDead(); // inherited from HUD Effect + + //---------------------------------------------------------------------------------------------- + // "setMaxGesticulationAmplitude" and "setMinGesticulationAmplitude" allow for the tuning of the + // gesticulation level detector to be responsive to different kinds of signals. For instance, we + // may find that the average voice amplitude rarely exceeds 0.7 (in a range from 0 to 1), and + // therefore we may want to set 0.7 as the max, so we can more easily catch all the variance + // within that range. Also, we may find that there is often noise below a certain range like 0.1, + // and so we would want to set 0.1 as the min so as not to accidentally use this as signal. + //---------------------------------------------------------------------------------------------- + void setMaxGesticulationAmplitude(); + void setMinGesticulationAmplitude(); + + //--------------------------------------------------- + // private members + //--------------------------------------------------- + private: + + struct SoundSymbol + { + F32 mWaveExpansion [ NUM_VOICE_SYMBOL_WAVES ]; + bool mWaveActive [ NUM_VOICE_SYMBOL_WAVES ]; + F64 mWaveFadeOutStartTime [ NUM_VOICE_SYMBOL_WAVES ]; + F32 mWaveOpacity [ NUM_VOICE_SYMBOL_WAVES ]; + LLPointer<LLImageGL> mTexture [ NUM_VOICE_SYMBOL_WAVES ]; + bool mActive; + LLVector3 mPosition; + }; + + LLFrameTimer mTimer; // so I can ask the current time in seconds + F64 mCurrentTime; // current time in seconds, captured every step + F64 mPreviousTime; // copy of "current time" from last frame + SoundSymbol mSoundSymbol; // the sound symbol that appears over the avatar's head + bool mVoiceEnabled; // if off, no rendering should happen + bool mCurrentlySpeaking; // is the user currently speaking? + LLVector3 mVoiceSourceWorldPosition; // give this to me every step - I need it to update the sound symbol + F32 mSpeakingAmplitude; // this should be set as often as possible when the user is speaking + F32 mMaxGesticulationAmplitude; // this is the upper-limit of the envelope of detectable gesticulation leves + F32 mMinGesticulationAmplitude; // this is the lower-limit of the envelope of detectable gesticulation leves + +};//----------------------------------------------------------------- + // end of LLVoiceVisualizer class +//------------------------------------------------------------------ + +#endif //LL_VOICE_VISUALIZER_H + diff --git a/indra/newview/llvovolume.cpp b/indra/newview/llvovolume.cpp index a126237e00..57b046c082 100644 --- a/indra/newview/llvovolume.cpp +++ b/indra/newview/llvovolume.cpp @@ -780,6 +780,8 @@ BOOL LLVOVolume::updateLOD() mLODChanged = TRUE; } + lod_changed |= LLViewerObject::updateLOD(); + return lod_changed; } @@ -1108,7 +1110,7 @@ BOOL LLVOVolume::updateGeometry(LLDrawable *drawable) mSculptChanged = FALSE; mFaceMappingChanged = FALSE; - return TRUE; + return LLViewerObject::updateGeometry(drawable); } void LLVOVolume::updateFaceSize(S32 idx) diff --git a/indra/newview/llworldmapview.cpp b/indra/newview/llworldmapview.cpp index 6c99d99732..ab95b9eda6 100644 --- a/indra/newview/llworldmapview.cpp +++ b/indra/newview/llworldmapview.cpp @@ -292,8 +292,7 @@ void LLWorldMapView::draw() const S32 half_height = height / 2; LLVector3d camera_global = gAgent.getCameraPositionGlobal(); - LLGLEnable scissor_test(GL_SCISSOR_TEST); - LLUI::setScissorRegionLocal(LLRect(0, height, width, 0)); + LLLocalClipRect clip(getLocalRect()); { LLGLSNoTexture no_texture; diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py index c93b302312..9136a06c24 100755 --- a/indra/newview/viewer_manifest.py +++ b/indra/newview/viewer_manifest.py @@ -69,25 +69,42 @@ class ViewerManifest(LLManifest): self.path("lsl_guide.html") self.path("gpu_table.txt") + def channel_unique(self): + return self.args['channel'].replace("Second Life", "").strip() + def channel_oneword(self): + return "".join(self.channel_unique().split()) + def channel_lowerword(self): + return self.channel_oneword().lower() + def flags_list(self): """ Convenience function that returns the command-line flags for the grid""" - if(self.args['grid'] == ''): - return "" - elif(self.args['grid'] == 'firstlook'): - return '-settings settings_firstlook.xml' - else: - return ("-settings settings_beta.xml --%(grid)s -helperuri http://preview-%(grid)s.secondlife.com/helpers/" % {'grid':self.args['grid']}) + channel_flags = '' + grid_flags = '' + if not self.default_grid(): + if self.default_channel(): + # beta grid viewer + channel_flags = '-settings settings_beta.xml' + grid_flags = "--%(grid)s -helperuri http://preview-%(grid)s.secondlife.com/helpers/" % {'grid':self.args['grid']} + + if not self.default_channel(): + # some channel on some grid + channel_flags = '-settings settings_%s.xml -channel "%s"' % (self.channel_lowerword(), self.args['channel']) + return " ".join((grid_flags, channel_flags)).strip() def login_url(self): """ Convenience function that returns the appropriate login url for the grid""" if(self.args.get('login_url')): return self.args['login_url'] else: - if(self.args['grid'] == ''): - return 'http://secondlife.com/app/login/' - elif(self.args['grid'] == 'firstlook'): - return 'http://secondlife.com/app/login/firstlook/' + if(self.default_grid()): + if(self.default_channel()): + # agni release + return 'http://secondlife.com/app/login/' + else: + # first look (or other) on agni + return 'http://secondlife.com/app/login/%s/' % self.channel_lowerword() else: + # beta grid return 'http://secondlife.com/app/login/beta/' def replace_login_url(self): @@ -97,14 +114,14 @@ class ViewerManifest(LLManifest): class WindowsManifest(ViewerManifest): def final_exe(self): - # *NOTE: these are the only two executable names that the crash reporter recognizes - if self.args['grid'] == '': - return "SecondLife.exe" - elif self.args['grid'] == 'firstlook': - return "SecondLifeFirstLook.exe" + if self.default_channel(): + if self.default_grid(): + return "SecondLife.exe" + else: + return "SecondLifePreview.exe" else: - return "SecondLifePreview.exe" - # return "SecondLifePreview%s.exe" % (self.args['grid'], ) + return ''.join(self.args['channel'].split()) + '.exe' + def construct(self): super(WindowsManifest, self).construct() @@ -154,6 +171,20 @@ class WindowsManifest(ViewerManifest): self.path("res/*/*") self.end_prefix() + # Vivox runtimes + if self.prefix(src="vivox-runtime/i686-win32", dst=""): + self.path("SLVoice.exe") + self.path("SLVoiceAgent.exe") + self.path("libeay32.dll") + self.path("srtp.dll") + self.path("ssleay32.dll") + self.path("tntk.dll") + self.path("alut.dll") + self.path("vivoxsdk.dll") + self.path("ortp.dll") + self.path("wrap_oal.dll") + self.end_prefix() + # pull in the crash logger and updater from other projects self.path(src="../win_crash_logger/win_crash_logger.exe", dst="win_crash_logger.exe") self.path(src="../win_updater/updater.exe", dst="updater.exe") @@ -203,49 +234,75 @@ class WindowsManifest(ViewerManifest): return result def package_finish(self): - version_vars_template = """ + # a standard map of strings for replacing in the templates + substitution_strings = { + 'version' : '.'.join(self.args['version']), + 'version_short' : '.'.join(self.args['version'][:-1]), + 'version_dashes' : '-'.join(self.args['version']), + 'final_exe' : self.final_exe(), + 'grid':self.args['grid'], + 'grid_caps':self.args['grid'].upper(), + # escape quotes becase NSIS doesn't handle them well + 'flags':self.flags_list().replace('"', '$\\"'), + 'channel':self.args['channel'], + 'channel_oneword':self.channel_oneword(), + 'channel_unique':self.channel_unique(), + } + + version_vars = """ !define INSTEXE "%(final_exe)s" !define VERSION "%(version_short)s" !define VERSION_LONG "%(version)s" !define VERSION_DASHES "%(version_dashes)s" - """ - if(self.args['grid'] == ''): - installer_file = "Second Life %(version_dashes)s Setup.exe" - grid_vars_template = """ - OutFile "%(outfile)s" - !define INSTFLAGS "%(flags)s" - !define INSTNAME "SecondLife" - !define SHORTCUT "Second Life" - !define URLNAME "secondlife" - Caption "Second Life ${VERSION}" - """ + """ % substitution_strings + if self.default_channel(): + if self.default_grid(): + # release viewer + installer_file = "Second Life %(version_dashes)s Setup.exe" + grid_vars_template = """ + OutFile "%(installer_file)s" + !define INSTFLAGS "%(flags)s" + !define INSTNAME "SecondLife" + !define SHORTCUT "Second Life" + !define URLNAME "secondlife" + Caption "Second Life ${VERSION}" + """ + else: + # beta grid viewer + installer_file = "Second Life %(version_dashes)s (%(grid_caps)s) Setup.exe" + grid_vars_template = """ + OutFile "%(installer_file)s" + !define INSTFLAGS "%(flags)s" + !define INSTNAME "SecondLife%(grid_caps)s" + !define SHORTCUT "Second Life (%(grid_caps)s)" + !define URLNAME "secondlife%(grid)s" + !define UNINSTALL_SETTINGS 1 + Caption "Second Life %(grid)s ${VERSION}" + """ else: - installer_file = "Second Life %(version_dashes)s (%(grid_caps)s) Setup.exe" + # some other channel on some grid + installer_file = "Second Life %(version_dashes)s %(channel_unique)s Setup.exe" grid_vars_template = """ - OutFile "%(outfile)s" + OutFile "%(installer_file)s" !define INSTFLAGS "%(flags)s" - !define INSTNAME "SecondLife%(grid_caps)s" - !define SHORTCUT "Second Life (%(grid_caps)s)" - !define URLNAME "secondlife%(grid)s" + !define INSTNAME "SecondLife%(channel_oneword)s" + !define SHORTCUT "%(channel)s" + !define URLNAME "secondlife" !define UNINSTALL_SETTINGS 1 - Caption "Second Life %(grid)s ${VERSION}" + Caption "%(channel)s ${VERSION}" """ if(self.args.has_key('installer_name')): installer_file = self.args['installer_name'] else: - installer_file = installer_file % {'version_dashes' : '-'.join(self.args['version']), - 'grid_caps' : self.args['grid'].upper()} - tempfile = "../secondlife_setup.nsi" - # the following is an odd sort of double-string replacement + installer_file = installer_file % substitution_strings + substitution_strings['installer_file'] = installer_file + + tempfile = "../secondlife_setup_tmp.nsi" + # the following replaces strings in the nsi template + # it also does python-style % substitution self.replace_in("installers/windows/installer_template.nsi", tempfile, { - "%%VERSION%%":version_vars_template%{'version_short' : '.'.join(self.args['version'][:-1]), - 'version' : '.'.join(self.args['version']), - 'version_dashes' : '-'.join(self.args['version']), - 'final_exe' : self.final_exe()}, - "%%GRID_VARS%%":grid_vars_template%{'grid':self.args['grid'], - 'grid_caps':self.args['grid'].upper(), - 'outfile':installer_file, - 'flags':self.flags_list()}, + "%%VERSION%%":version_vars, + "%%GRID_VARS%%":grid_vars_template % substitution_strings, "%%INSTALL_FILES%%":self.nsi_file_commands(True), "%%DELETE_FILES%%":self.nsi_file_commands(False)}) @@ -307,16 +364,26 @@ class DarwinManifest(ViewerManifest): def package_finish(self): + channel_standin = 'Second Life' # hah, our default channel is not usable on its own + if not self.default_channel(): + channel_standin = self.args['channel'] + imagename="SecondLife_" + '_'.join(self.args['version']) - if(self.args['grid'] != ''): - imagename = imagename + '_' + self.args['grid'].upper() + if self.default_channel(): + if not self.default_grid(): + # beta case + imagename = imagename + '_' + self.args['grid'].upper() + else: + # first look, etc + imagename = imagename + '_' + self.channel_oneword().upper() sparsename = imagename + ".sparseimage" finalname = imagename + ".dmg" # make sure we don't have stale files laying about self.remove(sparsename, finalname) - self.run_command('hdiutil create "%(sparse)s" -volname "Second Life" -fs HFS+ -type SPARSE -megabytes 300' % {'sparse':sparsename}) + self.run_command('hdiutil create "%(sparse)s" -volname "Second Life" -fs HFS+ -type SPARSE -megabytes 300' % { + 'sparse':sparsename}) # mount the image and get the name of the mount point and device node hdi_output = self.run_command('hdiutil attach -private "' + sparsename + '"') @@ -324,15 +391,17 @@ class DarwinManifest(ViewerManifest): volpath = re.search('HFS\s+(.+)', hdi_output).group(1).strip() # Copy everything in to the mounted .dmg - # TODO change name of .app once mac_updater can handle it. - for s,d in { - self.get_dst_prefix():"Second Life.app", - "lsl_guide.html":"Linden Scripting Language Guide.html", - "releasenotes.txt":"Release Notes.txt", - "installers/darwin/mac_image_hidden":".hidden", - "installers/darwin/mac_image_background.tga":"background.tga", - "installers/darwin/mac_image_DS_Store":".DS_Store"}.items(): + if self.default_channel() and not self.default_grid(): + app_name = "Second Life " + self.args['grid'] + else: + app_name = channel_standin.strip() + for s,d in {self.get_dst_prefix():app_name + ".app", + "lsl_guide.html":"Linden Scripting Language Guide.html", + "releasenotes.txt":"Release Notes.txt", + "installers/darwin/mac_image_hidden":".hidden", + "installers/darwin/mac_image_background.tga":"background.tga", + "installers/darwin/mac_image_DS_Store":".DS_Store"}.items(): print "Copying to dmg", s, d self.copy_action(self.src_path_of(s), os.path.join(volpath, d)) |