/** * @file lltoast.cpp * @brief This class implements a placeholder for any notification panel. * * $LicenseInfo:firstyear=2000&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, Linden Research, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ #include "llviewerprecompiledheaders.h" // must be first include #include "lltoast.h" #include "llbutton.h" #include "llfocusmgr.h" #include "llnotifications.h" #include "llviewercontrol.h" using namespace LLNotificationsUI; std::list LLToast::sModalToastsList; //-------------------------------------------------------------------------- LLToastLifeTimer::LLToastLifeTimer(LLToast* toast, F32 period) : mToast(toast), LLEventTimer(period) { } /*virtual*/ bool LLToastLifeTimer::tick() { if (mEventTimer.hasExpired()) { mToast->expire(); } return false; } void LLToastLifeTimer::stop() { mEventTimer.stop(); } void LLToastLifeTimer::start() { mEventTimer.start(); } void LLToastLifeTimer::restart() { mEventTimer.reset(); } BOOL LLToastLifeTimer::getStarted() { return mEventTimer.getStarted(); } void LLToastLifeTimer::setPeriod(F32 period) { mPeriod = period; } F32 LLToastLifeTimer::getRemainingTimeF32() { F32 et = mEventTimer.getElapsedTimeF32(); if (!getStarted() || et > mPeriod) return 0.0f; return mPeriod - et; } //-------------------------------------------------------------------------- LLToast::Params::Params() : can_fade("can_fade", true), can_be_stored("can_be_stored", true), is_modal("is_modal", false), is_tip("is_tip", false), enable_hide_btn("enable_hide_btn", true), force_show("force_show", false), force_store("force_store", false), fading_time_secs("fading_time_secs", gSavedSettings.getS32("ToastFadingTime")), lifetime_secs("lifetime_secs", gSavedSettings.getS32("NotificationToastLifeTime")) {}; LLToast::LLToast(const LLToast::Params& p) : LLModalDialog(LLSD(), p.is_modal), mToastLifetime(p.lifetime_secs), mToastFadingTime(p.fading_time_secs), mNotificationID(p.notif_id), mSessionID(p.session_id), mCanFade(p.can_fade), mCanBeStored(p.can_be_stored), mHideBtnEnabled(p.enable_hide_btn), mHideBtn(NULL), mPanel(NULL), mNotification(p.notification), mIsHidden(false), mHideBtnPressed(false), mIsTip(p.is_tip), mWrapperPanel(NULL), mIsFading(false), mIsHovered(false) { mTimer.reset(new LLToastLifeTimer(this, p.lifetime_secs)); buildFromFile("panel_toast.xml"); setCanDrag(FALSE); mWrapperPanel = getChild("wrapper_panel"); setBackgroundOpaque(TRUE); // *TODO: obsolete updateTransparency(); if(p.panel()) { insertPanel(p.panel); } if(mHideBtnEnabled) { mHideBtn = getChild("hide_btn"); mHideBtn->setClickedCallback(boost::bind(&LLToast::hide,this)); } // init callbacks if present if(!p.on_delete_toast().empty()) { mOnDeleteToastSignal.connect(p.on_delete_toast()); } if (isModal()) { sModalToastsList.push_front(this); } } void LLToast::reshape(S32 width, S32 height, BOOL called_from_parent) { // We shouldn't use reshape from LLModalDialog since it changes toasts position. // Toasts position should be controlled only by toast screen channel, see LLScreenChannelBase. // see EXT-8044 LLFloater::reshape(width, height, called_from_parent); } //-------------------------------------------------------------------------- BOOL LLToast::postBuild() { if(!mCanFade) { mTimer->stop(); } return TRUE; } //-------------------------------------------------------------------------- void LLToast::setHideButtonEnabled(bool enabled) { if(mHideBtn) mHideBtn->setEnabled(enabled); } //-------------------------------------------------------------------------- LLToast::~LLToast() { if(LLApp::isQuitting()) { mOnFadeSignal.disconnect_all_slots(); mOnDeleteToastSignal.disconnect_all_slots(); mOnToastDestroyedSignal.disconnect_all_slots(); mOnToastHoverSignal.disconnect_all_slots(); mToastMouseEnterSignal.disconnect_all_slots(); mToastMouseLeaveSignal.disconnect_all_slots(); } else { mOnToastDestroyedSignal(this); } if (isModal()) { std::list::iterator iter = std::find(sModalToastsList.begin(), sModalToastsList.end(), this); if (iter != sModalToastsList.end()) { sModalToastsList.erase(iter); } } } //-------------------------------------------------------------------------- void LLToast::hide() { if (!mIsHidden) { setVisible(FALSE); setFading(false); mTimer->stop(); mIsHidden = true; mOnFadeSignal(this); } } /*virtual*/ void LLToast::setFocus(BOOL b) { if (b && !hasFocus() && mPanel && mWrapperPanel && !mWrapperPanel->getChildList()->empty()) { LLModalDialog::setFocus(TRUE); // mostly for buttons mPanel->setFocus(TRUE); } else { LLModalDialog::setFocus(b); } } void LLToast::onFocusLost() { if(mWrapperPanel && !isBackgroundVisible()) { // Lets make wrapper panel behave like a floater updateTransparency(); } } void LLToast::onFocusReceived() { if(mWrapperPanel && !isBackgroundVisible()) { // Lets make wrapper panel behave like a floater updateTransparency(); } } void LLToast::setLifetime(S32 seconds) { mToastLifetime = seconds; } void LLToast::setFadingTime(S32 seconds) { mToastFadingTime = seconds; } void LLToast::closeToast() { mOnDeleteToastSignal(this); setSoundFlags(SILENT); closeFloater(); } S32 LLToast::getTopPad() { if(mWrapperPanel) { return getRect().getHeight() - mWrapperPanel->getRect().getHeight(); } return 0; } S32 LLToast::getRightPad() { if(mWrapperPanel) { return getRect().getWidth() - mWrapperPanel->getRect().getWidth(); } return 0; } //-------------------------------------------------------------------------- void LLToast::setCanFade(bool can_fade) { mCanFade = can_fade; if(!mCanFade) { mTimer->stop(); } } //-------------------------------------------------------------------------- void LLToast::expire() { if (mCanFade) { if (mIsFading) { // Fade timer expired. Time to hide. hide(); } else { // "Life" time has ended. Time to fade. setFading(true); mTimer->restart(); } } } void LLToast::setFading(bool transparent) { mIsFading = transparent; updateTransparency(); if (transparent) { mTimer->setPeriod(mToastFadingTime); } else { mTimer->setPeriod(mToastLifetime); } } F32 LLToast::getTimeLeftToLive() { F32 time_to_live = mTimer->getRemainingTimeF32(); if (!mIsFading) { time_to_live += mToastFadingTime; } return time_to_live; } //-------------------------------------------------------------------------- void LLToast::reshapeToPanel() { LLPanel* panel = getPanel(); if(!panel) return; LLRect panel_rect = panel->getLocalRect(); panel->setShape(panel_rect); LLRect toast_rect = getRect(); toast_rect.setLeftTopAndSize(toast_rect.mLeft, toast_rect.mTop, panel_rect.getWidth() + getRightPad(), panel_rect.getHeight() + getTopPad()); setShape(toast_rect); } void LLToast::insertPanel(LLPanel* panel) { mPanel = panel; mWrapperPanel->addChild(panel); reshapeToPanel(); } //-------------------------------------------------------------------------- void LLToast::draw() { LLFloater::draw(); if(!isBackgroundVisible()) { // Floater background is invisible, lets make wrapper panel look like a // floater - draw shadow. drawShadow(mWrapperPanel); // Shadow will probably overlap close button, lets redraw the button if(mHideBtn) { drawChild(mHideBtn); } } } //-------------------------------------------------------------------------- void LLToast::setVisible(BOOL show) { if(mIsHidden) { // this toast is invisible after fade until its ScreenChannel will allow it // // (EXT-1849) according to this bug a toast can be resurrected from // invisible state if it faded during a teleportation // then it fades a second time and causes a crash return; } if (show && getVisible()) { return; } if(show) { if(!mTimer->getStarted() && mCanFade) { mTimer->start(); } } else { //hide "hide" button in case toast was hidden without mouse_leave if(mHideBtn) mHideBtn->setVisible(show); } LLFloater::setVisible(show); if (mPanel && !mPanel->isDead() && mWrapperPanel && !mWrapperPanel->getChildList()->empty() // LLInspectToast can take over, but LLToast still appears to act like a data storage && mPanel->getParent() == mWrapperPanel ) { mPanel->setVisible(show); } } void LLToast::updateHoveredState() { S32 x, y; LLUI::getInstance()->getMousePositionScreen(&x, &y); LLRect panel_rc = mWrapperPanel->calcScreenRect(); LLRect button_rc; if(mHideBtn) { button_rc = mHideBtn->calcScreenRect(); } if (!panel_rc.pointInRect(x, y) && !button_rc.pointInRect(x, y)) { // mouse is not over this toast mIsHovered = false; } else { bool is_overlapped_by_other_floater = false; const child_list_t* child_list = gFloaterView->getChildList(); // find this toast in gFloaterView child list to check whether any floater // with higher Z-order is visible under the mouse pointer overlapping this toast child_list_const_reverse_iter_t r_iter = std::find(child_list->rbegin(), child_list->rend(), this); if (r_iter != child_list->rend()) { // skip this toast and proceed to views above in Z-order for (++r_iter; r_iter != child_list->rend(); ++r_iter) { LLView* view = *r_iter; is_overlapped_by_other_floater = view->isInVisibleChain() && view->calcScreenRect().pointInRect(x, y); if (is_overlapped_by_other_floater) { break; } } } mIsHovered = !is_overlapped_by_other_floater; } LLToastLifeTimer* timer = getTimer(); if (timer) { // Started timer means the mouse had left the toast previously. // If toast is hovered in the current frame we should handle // a mouse enter event. if(timer->getStarted() && mIsHovered) { mOnToastHoverSignal(this, MOUSE_ENTER); updateTransparency(); //toasts fading is management by Screen Channel sendChildToFront(mHideBtn); if(mHideBtn && mHideBtn->getEnabled()) { mHideBtn->setVisible(TRUE); } mToastMouseEnterSignal(this, getValue()); } // Stopped timer means the mouse had entered the toast previously. // If the toast is not hovered in the current frame we should handle // a mouse leave event. else if(!timer->getStarted() && !mIsHovered) { mOnToastHoverSignal(this, MOUSE_LEAVE); updateTransparency(); //toasts fading is management by Screen Channel if(mHideBtn && mHideBtn->getEnabled()) { if( mHideBtnPressed ) { mHideBtnPressed = false; return; } mHideBtn->setVisible(FALSE); } mToastMouseLeaveSignal(this, getValue()); } } } void LLToast::setBackgroundOpaque(BOOL b) { if(mWrapperPanel && !isBackgroundVisible()) { mWrapperPanel->setBackgroundOpaque(b); } else { LLModalDialog::setBackgroundOpaque(b); } } void LLToast::updateTransparency() { ETypeTransparency transparency_type; if (mCanFade) { // Notification toasts (including IM/chat toasts) change their transparency on hover. if (isHovered()) { transparency_type = TT_ACTIVE; } else { transparency_type = mIsFading ? TT_FADING : TT_INACTIVE; } } else { // Transparency of alert toasts depends on focus. transparency_type = hasFocus() ? TT_ACTIVE : TT_INACTIVE; } LLFloater::updateTransparency(transparency_type); } void LLNotificationsUI::LLToast::stopTimer() { if(mCanFade) { setFading(false); mTimer->stop(); } } void LLNotificationsUI::LLToast::startTimer() { if(mCanFade) { setFading(false); mTimer->start(); } } //-------------------------------------------------------------------------- bool LLToast::handleMouseDown(S32 x, S32 y, MASK mask) { if(mHideBtn && mHideBtn->getEnabled()) { mHideBtnPressed = mHideBtn->getRect().pointInRect(x, y); } return LLModalDialog::handleMouseDown(x, y, mask); } //-------------------------------------------------------------------------- bool LLToast::isNotificationValid() { if(mNotification) { return !mNotification->isCancelled(); } return false; } //-------------------------------------------------------------------------- S32 LLToast::notifyParent(const LLSD& info) { if (info.has("action") && "hide_toast" == info["action"].asString()) { hide(); return 1; } return LLModalDialog::notifyParent(info); } //static void LLToast::updateClass() { for (auto& toast : LLInstanceTracker::instance_snapshot()) { toast.updateHoveredState(); } } // static void LLToast::cleanupToasts() { LLInstanceTracker::instance_snapshot().deleteAll(); }