/** * @file lltoastnotifypanel.cpp * @brief Panel for notify toasts. * * $LicenseInfo:firstyear=2001&license=viewergpl$ * * Copyright (c) 2001-2009, Linden Research, Inc. * * Second Life Viewer Source Code * The source code in this file ("Source Code") is provided by Linden Lab * to you under the terms of the GNU General Public License, version 2.0 * ("GPL"), unless you have obtained a separate licensing agreement * ("Other License"), formally executed by you and Linden Lab. Terms of * the GPL can be found in doc/GPL-license.txt in this distribution, or * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 * * There are special exceptions to the terms and conditions of the GPL as * it is applied to this Source Code. View the full text of the exception * in the file doc/FLOSS-exception.txt in this software distribution, or * online at * http://secondlifegrid.net/programs/open_source/licensing/flossexception * * By copying, modifying or distributing this software, you acknowledge * that you have read and understood your obligations described above, * and agree to abide by those obligations. * * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, * COMPLETENESS OR PERFORMANCE. * $/LicenseInfo$ */ #include "llviewerprecompiledheaders.h" #include "lltoastnotifypanel.h" // project includes #include "llviewercontrol.h" // library includes #include "lldbstrings.h" #include "llnotifications.h" #include "lluiconstants.h" #include "llrect.h" #include "lltrans.h" #include "llnotificationsutil.h" const S32 BOTTOM_PAD = VPAD * 3; const S32 IGNORE_BTN_TOP_DELTA = 3*VPAD;//additional ignore_btn padding S32 BUTTON_WIDTH = 90; //static const LLFontGL* LLToastNotifyPanel::sFont = NULL; const LLFontGL* LLToastNotifyPanel::sFontSmall = NULL; LLToastNotifyPanel::LLToastNotifyPanel(LLNotificationPtr& notification) : LLToastPanel(notification), mTextBox(NULL), mInfoPanel(NULL), mControlPanel(NULL), mNumOptions(0), mNumButtons(0), mAddedDefaultBtn(false) { LLUICtrlFactory::getInstance()->buildPanel(this, "panel_notification.xml"); mInfoPanel = getChild("info_panel"); mControlPanel = getChild("control_panel"); BUTTON_WIDTH = gSavedSettings.getS32("ToastButtonWidth"); // customize panel's attributes // is it intended for displaying a tip mIsTip = notification->getType() == "notifytip"; // is it a script dialog mIsScriptDialog = (notification->getName() == "ScriptDialog" || notification->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 = notification->getPriority() >= NOTIFICATION_PRIORITY_HIGH; // setup parameters // get a notification message mMessage = notification->getMessage(); // init font variables if (!sFont) { sFont = LLFontGL::getFontSansSerif(); sFontSmall = LLFontGL::getFontSansSerifSmall(); } // clicking on a button does not steal current focus setIsChrome(TRUE); // initialize setFocusRoot(!mIsTip); // get a form for the notification LLNotificationFormPtr form(notification->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("caution_text_box"); } else { mTextBox = getChild("text_editor_box"); } // *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; mTextBox->setMaxTextLength(MAX_LENGTH); mTextBox->setVisible(TRUE); mTextBox->setValue(notification->getMessage()); // add buttons for a script notification if (mIsTip) { adjustPanelForTipNotice(); } else { std::vector 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") { 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 */ 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); } } // adjust panel's height to the text size mInfoPanel->setFollowsAll(); snapToMessageHeight(mTextBox, MAX_LENGTH); } 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 is_ignore_btn = form_element["index"].asInteger() == -1; const LLFontGL* font = is_ignore_btn ? sFontSmall: sFont; // for ignore button 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_RIGHT | FOLLOWS_LEFT | FOLLOWS_BOTTOM); 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 && is_ignore_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(p); mNumButtons++; btn->autoResize(); if (form_element["default"].asBoolean()) { setDefaultBtn(btn); } return btn; } LLToastNotifyPanel::~LLToastNotifyPanel() { std::for_each(mBtnCallbackData.begin(), mBtnCallbackData.end(), DeletePointer()); if (LLNotificationsUtil::find(mNotification->getID()) != NULL) { LLNotifications::getInstance()->cancel(mNotification); } } void LLToastNotifyPanel::updateButtonsLayout(const std::vector& 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; for (std::vector::const_iterator it = buttons.begin(); it != buttons.end(); it++) { 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); } if (mIsScriptDialog && ignore_btn != NULL) { LLRect ignore_btn_rect(ignore_btn->getRect()); S32 buttons_per_row = max_width / BUTTON_WIDTH; //assume that h_pad far less than BUTTON_WIDTH S32 ignore_btn_left = buttons_per_row * BUTTON_WIDTH + (buttons_per_row - 1) * h_pad - ignore_btn_rect.getWidth(); if (ignore_btn_left + ignore_btn_rect.getWidth() > max_width)// make sure that the ignore button is in panel { ignore_btn_left = max_width - ignore_btn_rect.getWidth() - 2 * HPAD; } 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); mControlPanel->addChild(ignore_btn, -1); } } 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() { LLRect info_rect = mInfoPanel->getRect(); LLRect this_rect = getRect(); //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; } self->mNotification->respond(response); // disable all buttons self->mControlPanel->setEnabled(FALSE); }