phoenix-firestorm/indra/newview/lltoast.cpp

642 lines
15 KiB
C++

/**
* @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*> 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", (F32)gSavedSettings.getS32("ToastFadingTime")),
lifetime_secs("lifetime_secs", (F32)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<LLPanel>("wrapper_panel");
setBackgroundOpaque(true); // *TODO: obsolete
updateTransparency();
if(p.panel())
{
insertPanel(p.panel);
}
if(mHideBtnEnabled)
{
mHideBtn = getChild<LLButton>("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<LLToast*>::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 = (F32)seconds;
}
void LLToast::setFadingTime(S32 seconds)
{
mToastFadingTime = (F32)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);
}
LLModalDialog::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()
{
if (!mWrapperPanel)
{
// Shouldn't be happening.
// mWrapperPanel should have been inited in the constructor
// This needs to be figured out and fixed
llassert(false);
return;
}
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<LLToast>::instance_snapshot())
{
toast.updateHoveredState();
}
}
// static
void LLToast::cleanupToasts()
{
LLInstanceTracker<LLToast>::instance_snapshot().deleteAll();
}