646 lines
14 KiB
C++
646 lines
14 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", 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<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)
|
|
{
|
|
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)
|
|
{
|
|
if(!mPanel->isDead())
|
|
{
|
|
mPanel->setVisible(show);
|
|
}
|
|
}
|
|
}
|
|
|
|
void LLToast::updateHoveredState()
|
|
{
|
|
S32 x, y;
|
|
LLUI::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 (LLInstanceTracker<LLToast>::instance_iter iter = LLInstanceTracker<LLToast>::beginInstances();
|
|
iter != LLInstanceTracker<LLToast>::endInstances(); )
|
|
{
|
|
LLToast& toast = *iter++;
|
|
|
|
toast.updateHoveredState();
|
|
}
|
|
}
|
|
|
|
// static
|
|
void LLToast::cleanupToasts()
|
|
{
|
|
LLToast * toastp = NULL;
|
|
|
|
while (LLInstanceTracker<LLToast>::instanceCount() > 0)
|
|
{
|
|
{ // Need to scope iter to allow deletion
|
|
LLInstanceTracker<LLToast>::instance_iter iter = LLInstanceTracker<LLToast>::beginInstances();
|
|
toastp = &(*iter);
|
|
}
|
|
|
|
//LL_INFOS() << "Cleaning up toast id " << toastp->getNotificationID() << LL_ENDL;
|
|
|
|
// LLToast destructor will remove it from the LLInstanceTracker.
|
|
if (!toastp)
|
|
break; // Don't get stuck in the loop if a null pointer somehow got on the list
|
|
|
|
delete toastp;
|
|
}
|
|
}
|
|
|