Skip to content
Snippets Groups Projects
Select Git revision
  • main default protected
  • next protected
  • UI-EvenMoreTweaks
  • merge/materials_featurette protected
  • merge/webrtc protected
  • darl/linux-sh-installer
  • xenhat/maint/bolt
  • xenhat/features/cinematic-mode-new
  • screensquare
  • ssestuff
  • spdlog
  • 7.1.7.2486-beta
  • 7.1.4.2442-beta
  • 7.1.4.2413-beta
  • 7.1.3.2338-beta
  • 7.1.3.2332-beta
  • 7.1.2.2304-beta
  • 7.1.1.2251-beta
  • 7.0.1.2244-beta
  • 7.0.1.2240-beta
  • 7.0.1.2230-beta
  • 7.0.1.2206-beta
22 results

lltoastnotifypanel.cpp

Blame
  • Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    lltoastnotifypanel.cpp 17.82 KiB
    /**
     * @file lltoastnotifypanel.cpp
     * @brief Panel for notify toasts.
     *
     * $LicenseInfo:firstyear=2001&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"
    
    #include "lltoastnotifypanel.h"
    
    // project includes
    #include "llviewercontrol.h"
    
    // library includes
    #include "lldbstrings.h"
    #include "lllslconstants.h"
    #include "llnotifications.h"
    #include "lluiconstants.h"
    #include "llrect.h"
    #include "lltrans.h"
    #include "llnotificationsutil.h"
    #include "llviewermessage.h"
    #include "llfloaterimsession.h"
    #include "llavataractions.h"
    
    const S32 BOTTOM_PAD = VPAD * 3;
    const S32 IGNORE_BTN_TOP_DELTA = 3*VPAD;//additional ignore_btn padding
    S32 BUTTON_WIDTH = 90;
    // *TODO: magic numbers(???) - copied from llnotify.cpp(250)
    const S32 MAX_LENGTH = 512 + 20 + DB_FIRST_NAME_BUF_SIZE + DB_LAST_NAME_BUF_SIZE + DB_INV_ITEM_NAME_BUF_SIZE; 
    
    
    //static
    const LLFontGL* LLToastNotifyPanel::sFont = NULL;
    const LLFontGL* LLToastNotifyPanel::sFontSmall = NULL;
    
    LLToastNotifyPanel::button_click_signal_t LLToastNotifyPanel::sButtonClickSignal;
    
    LLToastNotifyPanel::LLToastNotifyPanel(const LLNotificationPtr& notification, const LLRect& rect, bool show_images) 
    :	LLToastPanel(notification),
    	LLInstanceTracker<LLToastNotifyPanel, LLUUID>(notification->getID())
    {
    	init(rect, show_images);
    }
    void LLToastNotifyPanel::addDefaultButton()
    {
    	LLSD form_element;
    	form_element.with("name", "OK").with("text", LLTrans::getString("ok")).with("default", true);
    	LLButton* ok_btn = createButton(form_element, FALSE);
    	LLRect new_btn_rect(ok_btn->getRect());
    
    	new_btn_rect.setOriginAndSize(llabs(getRect().getWidth() - BUTTON_WIDTH)/ 2, BOTTOM_PAD,
    			//auto_size for ok button makes it very small, so let's make it wider
    			BUTTON_WIDTH, new_btn_rect.getHeight());
    	ok_btn->setRect(new_btn_rect);
    	addChild(ok_btn, -1);
    	mNumButtons = 1;
    	mAddedDefaultBtn = true;
    }
    LLButton* LLToastNotifyPanel::createButton(const LLSD& form_element, BOOL is_option)
    {
    	InstanceAndS32* userdata = new InstanceAndS32;
    	userdata->mSelf = this;
    	userdata->mButtonName = is_option ? form_element["name"].asString() : "";
    
    	mBtnCallbackData.push_back(userdata);
    
    	LLButton::Params p;
    	bool make_small_btn = form_element["index"].asInteger() == -1 || form_element["index"].asInteger() == -2;
    	const LLFontGL* font = make_small_btn ? sFontSmall: sFont; // for block and ignore buttons in script dialog
    	p.name = form_element["name"].asString();
    	p.label = form_element["text"].asString();
    	p.font = font;
    	p.rect.height = BTN_HEIGHT;
    	p.click_callback.function(boost::bind(&LLToastNotifyPanel::onClickButton, userdata));
    	p.rect.width = BUTTON_WIDTH;
    	p.auto_resize = false;
    	p.follows.flags(FOLLOWS_LEFT | FOLLOWS_BOTTOM);
    	p.enabled = !form_element.has("enabled") || form_element["enabled"].asBoolean();
    	if (mIsCaution)
    	{
    		p.image_color(LLUIColorTable::instance().getColor("ButtonCautionImageColor"));
    		p.image_color_disabled(LLUIColorTable::instance().getColor("ButtonCautionImageColor"));
    	}
    	// for the scriptdialog buttons we use fixed button size. This  is a limit!
    	if (!mIsScriptDialog && font->getWidth(form_element["text"].asString()) > BUTTON_WIDTH)
    	{
    		p.rect.width = 1;
    		p.auto_resize = true;
    	}
    	else if (mIsScriptDialog && make_small_btn)
    	{
    		// this is ignore button, make it smaller
    		p.rect.height = BTN_HEIGHT_SMALL;
    		p.rect.width = 1;
    		p.auto_resize = true;
    	}
    	LLButton* btn = LLUICtrlFactory::create<LLButton>(p);
    	mNumButtons++;
    	btn->autoResize();
    	if (form_element["default"].asBoolean())
    	{
    		setDefaultBtn(btn);
    	}
    
    	return btn;
    }
    
    LLToastNotifyPanel::~LLToastNotifyPanel() 
    {
    	mButtonClickConnection.disconnect();
    
    	std::for_each(mBtnCallbackData.begin(), mBtnCallbackData.end(), DeletePointer());
    	if (mIsTip)
    		{
    			LLNotifications::getInstance()->cancel(mNotification);
    		}
    	}
    
    void LLToastNotifyPanel::updateButtonsLayout(const std::vector<index_button_pair_t>& buttons, S32 h_pad)
    {
    	S32 left = 0;
    	//reserve place for ignore button
    	S32 bottom_offset = mIsScriptDialog ? (BTN_HEIGHT + IGNORE_BTN_TOP_DELTA + BOTTOM_PAD) : BOTTOM_PAD;
    	S32 max_width = mControlPanel->getRect().getWidth();
    	LLButton* ignore_btn = NULL;
    	LLButton* mute_btn = NULL;
    	for (std::vector<index_button_pair_t>::const_iterator it = buttons.begin(); it != buttons.end(); it++)
    	{
    		if (-2 == it->first)
    		{
    			mute_btn = it->second;
    			continue;
    		}
    		if (it->first == -1)
    		{
    			ignore_btn = it->second;
    			continue;
    		}
    		LLButton* btn = it->second;
    		LLRect btn_rect(btn->getRect());
    		if (left + btn_rect.getWidth() > max_width)// whether there is still some place for button+h_pad in the mControlPanel
    		{
    			// looks like we need to add button to the next row
    			left = 0;
    			bottom_offset += (BTN_HEIGHT + VPAD);
    		}
    		//we arrange buttons from bottom to top for backward support of old script
    		btn_rect.setOriginAndSize(left, bottom_offset, btn_rect.getWidth(),	btn_rect.getHeight());
    		btn->setRect(btn_rect);
    		left = btn_rect.mLeft + btn_rect.getWidth() + h_pad;
    		mControlPanel->addChild(btn, -1);
    	}
    
    	U32 ignore_btn_width = 0;
    	U32 mute_btn_pad = 0;
    	if (mIsScriptDialog && ignore_btn != NULL)
    	{
    		LLRect ignore_btn_rect(ignore_btn->getRect());
    		S32 ignore_btn_left = max_width - ignore_btn_rect.getWidth();
    		ignore_btn_rect.setOriginAndSize(ignore_btn_left, BOTTOM_PAD,// always move ignore button at the bottom
    				ignore_btn_rect.getWidth(), ignore_btn_rect.getHeight());
    		ignore_btn->setRect(ignore_btn_rect);
    		ignore_btn_width = ignore_btn_rect.getWidth();
    		mControlPanel->addChild(ignore_btn, -1);
    		mute_btn_pad = 4 * HPAD; //only use a 4 * HPAD padding if an ignore button exists
    	}
    
    	if (mIsScriptDialog && mute_btn != NULL)
    	{
    		LLRect mute_btn_rect(mute_btn->getRect());
    		// Place mute (Block) button to the left of the ignore button.
    		S32 mute_btn_left = max_width - mute_btn_rect.getWidth() - ignore_btn_width - mute_btn_pad;
    		mute_btn_rect.setOriginAndSize(mute_btn_left, BOTTOM_PAD,// always move mute button at the bottom
    				mute_btn_rect.getWidth(), mute_btn_rect.getHeight());
    		mute_btn->setRect(mute_btn_rect);
    		mControlPanel->addChild(mute_btn);
    	}
    }
    
    void LLToastNotifyPanel::adjustPanelForScriptNotice(S32 button_panel_width, S32 button_panel_height)
    {
    	//adjust layout
    	// we need to keep min width and max height to make visible all buttons, because width of the toast can not be changed
    	reshape(getRect().getWidth(), mInfoPanel->getRect().getHeight() + button_panel_height + VPAD);
    	mControlPanel->reshape( button_panel_width, button_panel_height);
    }
    
    void LLToastNotifyPanel::adjustPanelForTipNotice()
    {
    	//we don't need display ControlPanel for tips because they doesn't contain any buttons. 
    	mControlPanel->setVisible(FALSE);
    	reshape(getRect().getWidth(), mInfoPanel->getRect().getHeight());
    
    	if (mNotification->getPayload().has("respond_on_mousedown")
    		&& mNotification->getPayload()["respond_on_mousedown"] )
    	{
    		mInfoPanel->setMouseDownCallback(
    			boost::bind(&LLNotification::respond,
    						mNotification,
    						mNotification->getResponseTemplate()));
    	}
    }
    
    // static
    void LLToastNotifyPanel::onClickButton(void* data)
    {
    	InstanceAndS32* self_and_button = (InstanceAndS32*)data;
    	LLToastNotifyPanel* self = self_and_button->mSelf;
    	std::string button_name = self_and_button->mButtonName;
    
    	LLSD response = self->mNotification->getResponseTemplate();
    	if (!self->mAddedDefaultBtn && !button_name.empty())
    	{
    		response[button_name] = true;
    	}
    
    	// disable all buttons
    	self->mControlPanel->setEnabled(FALSE);
    
    	// this might repost notification with new form data/enabled buttons
    	self->mNotification->respond(response);
    }
    
    void LLToastNotifyPanel::init( LLRect rect, bool show_images )
    {
        deleteAllChildren();
    
        mTextBox = NULL;
        mInfoPanel = NULL;
        mControlPanel = NULL;
        mNumOptions = 0;
        mNumButtons = 0;
        mAddedDefaultBtn = false;
    
    	LLRect current_rect = getRect();
    
    	setXMLFilename("");
    	buildFromFile("panel_notification.xml");
    
        if(rect != LLRect::null)
        {
            this->setShape(rect);
        }
        mInfoPanel = getChild<LLPanel>("info_panel");
    
        mControlPanel = getChild<LLPanel>("control_panel");
        BUTTON_WIDTH = gSavedSettings.getS32("ToastButtonWidth");
        // customize panel's attributes
        // is it intended for displaying a tip?
        mIsTip = mNotification->getType() == "notifytip";
        // is it a script dialog?
        mIsScriptDialog = (mNotification->getName() == "ScriptDialog" || mNotification->getName() == "ScriptDialogGroup");
        // is it a caution?
        //
        // caution flag can be set explicitly by specifying it in the notification payload, or it can be set implicitly if the
        // notify xml template specifies that it is a caution
        // tip-style notification handle 'caution' differently -they display the tip in a different color
        mIsCaution = mNotification->getPriority() >= NOTIFICATION_PRIORITY_HIGH;
    
        // setup parameters
        // get a notification message
        mMessage = mNotification->getMessage();
        // init font variables
        if (!sFont)
        {
            sFont = LLFontGL::getFontSansSerif();
            sFontSmall = LLFontGL::getFontSansSerifSmall();
        }
        // initialize
        setFocusRoot(!mIsTip);
        // get a form for the notification
        LLNotificationFormPtr form(mNotification->getForm());
        // get number of elements
        mNumOptions = form->getNumElements();
    
        // customize panel's outfit
        // preliminary adjust panel's layout
        //move to the end 
        //mIsTip ? adjustPanelForTipNotice() : adjustPanelForScriptNotice(form);
    
        // adjust text options according to the notification type
        // add a caution textbox at the top of a caution notification
        if (mIsCaution && !mIsTip)
        {
            mTextBox = getChild<LLTextBox>("caution_text_box");
        }
        else
        {
            mTextBox = getChild<LLTextEditor>("text_editor_box"); 
        }
    
        mTextBox->setMaxTextLength(MAX_LENGTH);
        mTextBox->setVisible(TRUE);
        mTextBox->setPlainText(!show_images);
        mTextBox->setValue(mNotification->getMessage());
    	mTextBox->setIsFriendCallback(LLAvatarActions::isFriend);
    
        // add buttons for a script notification
        if (mIsTip)
        {
            adjustPanelForTipNotice();
        }
        else
        {
            std::vector<index_button_pair_t> buttons;
            buttons.reserve(mNumOptions);
            S32 buttons_width = 0;
            // create all buttons and accumulate they total width to reshape mControlPanel
            for (S32 i = 0; i < mNumOptions; i++)
            {
                LLSD form_element = form->getElement(i);
                if (form_element["type"].asString() != "button")
                {
                    // not a button.
                    continue;
                }
                if (form_element["name"].asString() == TEXTBOX_MAGIC_TOKEN)
                {
                    // a textbox pretending to be a button.
                    continue;
                }
                LLButton* new_button = createButton(form_element, TRUE);
                buttons_width += new_button->getRect().getWidth();
                S32 index = form_element["index"].asInteger();
                buttons.push_back(index_button_pair_t(index,new_button));
            }
            if (buttons.empty())
            {
                addDefaultButton();
            }
            else
            {
                const S32 button_panel_width = mControlPanel->getRect().getWidth();// do not change width of the panel
                S32 button_panel_height = mControlPanel->getRect().getHeight();
                //try get an average h_pad to spread out buttons
                S32 h_pad = (button_panel_width - buttons_width) / (S32(buttons.size()));
                if(h_pad < 2*HPAD)
                {
                    /*
                     * Probably it is a scriptdialog toast
                     * for a scriptdialog toast h_pad can be < 2*HPAD if we have a lot of buttons.
                     * In last case set default h_pad to avoid heaping of buttons 
                     */
                    S32 button_per_row = button_panel_width / BUTTON_WIDTH;
                    h_pad = (button_panel_width % BUTTON_WIDTH) / (button_per_row - 1);// -1  because we do not need space after last button in a row   
                    if(h_pad < 2*HPAD) // still not enough space between buttons ?
                    {
                        h_pad = 2*HPAD;
                    }
                }
                if (mIsScriptDialog)
                {
                    // we are using default width for script buttons so we can determinate button_rows
                    //to get a number of rows we divide the required width of the buttons to button_panel_width
                    S32 button_rows = llceil(F32(buttons.size() - 1) * (BUTTON_WIDTH + h_pad) / button_panel_width);
                    //S32 button_rows = (buttons.size() - 1) * (BUTTON_WIDTH + h_pad) / button_panel_width;
                    //reserve one row for the ignore_btn
                    button_rows++;
                    //calculate required panel height for scripdialog notification.
                    button_panel_height = button_rows * (BTN_HEIGHT + VPAD)	+ IGNORE_BTN_TOP_DELTA + BOTTOM_PAD;
                }
                else
                {
                    // in common case buttons can have different widths so we need to calculate button_rows according to buttons_width
                    //S32 button_rows = llceil(F32(buttons.size()) * (buttons_width + h_pad) / button_panel_width);
                    S32 button_rows = llceil(F32((buttons.size() - 1) * h_pad + buttons_width) / button_panel_width);
                    //calculate required panel height 
                    button_panel_height = button_rows * (BTN_HEIGHT + VPAD)	+ BOTTOM_PAD;
                }
    
                // we need to keep min width and max height to make visible all buttons, because width of the toast can not be changed
                adjustPanelForScriptNotice(button_panel_width, button_panel_height);
                updateButtonsLayout(buttons, h_pad);
                // save buttons for later use in disableButtons()
                //mButtons.assign(buttons.begin(), buttons.end());
            }
        }
    
    	//.xml file intially makes info panel only follow left/right/top. This is so that when control buttons are added the info panel 
    	//can shift upward making room for the buttons inside mControlPanel. After the buttons are added, the info panel can then be set to follow 'all'.
    	mInfoPanel->setFollowsAll();
        snapToMessageHeight(mTextBox, MAX_LENGTH);
    
    	// reshape the panel to its previous size
    	if (current_rect.notEmpty())
    	{
    		reshape(current_rect.getWidth(), current_rect.getHeight());
    	}
    }
    
    bool LLToastNotifyPanel::isControlPanelEnabled() const
    {
    	bool cp_enabled = mControlPanel->getEnabled();
    	bool some_buttons_enabled = false;
    	if (cp_enabled)
    	{
    		LLView::child_list_const_iter_t child_it = mControlPanel->beginChild();
    		LLView::child_list_const_iter_t child_it_end = mControlPanel->endChild();
    		for(; child_it != child_it_end; ++child_it)
    		{
    			LLButton * buttonp = dynamic_cast<LLButton *>(*child_it);
    			if (buttonp && buttonp->getEnabled())
    			{
    				some_buttons_enabled = true;
    				break;
    			}
    		}
    	}
    
    	return cp_enabled && some_buttons_enabled;
    }
    
    //////////////////////////////////////////////////////////////////////////
    
    LLIMToastNotifyPanel::LLIMToastNotifyPanel(LLNotificationPtr& pNotification, const LLUUID& session_id, const LLRect& rect /* = LLRect::null */,
    										   bool show_images /* = true */, LLTextBase* parent_text)
    :	mSessionID(session_id), LLToastNotifyPanel(pNotification, rect, show_images),
    	mParentText(parent_text)
    {
    	compactButtons();
    }
    
    LLIMToastNotifyPanel::~LLIMToastNotifyPanel()
    {
    }
    
    void LLIMToastNotifyPanel::reshape(S32 width, S32 height, BOOL called_from_parent /* = TRUE */)
    {
    	LLToastPanel::reshape(width, height, called_from_parent);
    	snapToMessageHeight();
    }
    
    void LLIMToastNotifyPanel::snapToMessageHeight()
    {
    	if(!mTextBox)
    	{
    		return;
    	}
    
    	//Add message height if it is visible
    	if (mTextBox->getVisible())
    	{
    		S32 new_panel_height = computeSnappedToMessageHeight(mTextBox, MAX_LENGTH);
    
    		//reshape the panel with new height
    		if (new_panel_height != getRect().getHeight())
    		{
    			LLToastNotifyPanel::reshape( getRect().getWidth(), new_panel_height);
    		}
    	}
    }
    
    void LLIMToastNotifyPanel::compactButtons()
    {
    	//we can't set follows in xml since it broke toasts behavior
    	setFollows(FOLLOWS_LEFT|FOLLOWS_RIGHT|FOLLOWS_TOP);
    
    	const child_list_t* children = getControlPanel()->getChildList();
    	S32 offset = 0;
    	// Children were added by addChild() which uses push_front to insert them into list,
    	// so to get buttons in correct order reverse iterator is used (EXT-5906) 
    	for (child_list_t::const_reverse_iterator it = children->rbegin(); it != children->rend(); it++)
    	{
    		LLButton * button = dynamic_cast<LLButton*> (*it);
    		if (button != NULL)
    		{
    			button->setOrigin( offset,button->getRect().mBottom);
    			button->setLeftHPad(2 * HPAD);
    			button->setRightHPad(2 * HPAD);
    			// set zero width before perform autoResize()
    			button->setRect(LLRect(button->getRect().mLeft,
    				button->getRect().mTop, 
    				button->getRect().mLeft,
    				button->getRect().mBottom));
    			button->setAutoResize(true);
    			button->autoResize();
    			offset += HPAD + button->getRect().getWidth();
    			button->setFollowsNone();
    		}
    	}
    
    	if (mParentText)
    	{
    		mParentText->needsReflow();
    	}
    }
    
    void LLIMToastNotifyPanel::updateNotification()
    	{
    	init(LLRect(), true);
    	}
    
    void LLIMToastNotifyPanel::init( LLRect rect, bool show_images )
    {
    	LLToastNotifyPanel::init(LLRect(), show_images);
    
    	compactButtons();
    }
    
    // EOF