/** * @file llalertdialog.cpp * @brief LLAlertDialog base class * * $LicenseInfo:firstyear=2001&license=viewergpl$ * * Copyright (c) 2001-2008, 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 "linden_common.h" #include "llboost.h" #include "llalertdialog.h" #include "llfontgl.h" #include "llresmgr.h" #include "lltextbox.h" #include "llbutton.h" #include "llcheckboxctrl.h" #include "llkeyboard.h" #include "llfocusmgr.h" #include "lliconctrl.h" #include "llui.h" #include "llxmlnode.h" #include "lllineeditor.h" #include "lluictrlfactory.h" const S32 MAX_ALLOWED_MSG_WIDTH = 400; const F32 DEFAULT_BUTTON_DELAY = 0.5f; const S32 MSG_PAD = 8; /*static*/ LLAlertDialog::template_map_t LLAlertDialog::sAlertTemplates; /*static*/ LLAlertDialog::template_map_t LLAlertDialog::sIgnorableTemplates; /*static*/ LLControlGroup* LLAlertDialog::sSettings = NULL; /*static*/ std::map LLAlertDialog::sUniqueActiveMap; /*static*/ LLAlertDialog::display_callback_t LLAlertDialog::sDisplayCallback; /*static*/ LLString LLAlertDialog::sStringSkipNextTime("Skip this dialog next time"); /*static*/ LLString LLAlertDialog::sStringAlwaysChoose("Always choose this option"); /*static*/ LLAlertDialog::URLLoader* LLAlertDialog::sURLLoader; //static LLAlertDialog* LLAlertDialog::createXml( const LLString& xml_desc, alert_callback_t callback, void *user_data) { LLString::format_map_t args; return createXml(xml_desc, args, callback, user_data); } //static LLAlertDialog* LLAlertDialog::createXml( const LLString& xml_desc, const LLString::format_map_t& args, alert_callback_t callback, void *user_data) { template_map_t::iterator iter = sAlertTemplates.find(xml_desc); if (iter != sAlertTemplates.end()) { LLAlertDialogTemplate* xml_template = iter->second; // deletes itself llwarns << "Alert: [" << xml_desc << "] " << llendl; LLAlertDialog* dialog = new LLAlertDialog( xml_template, args, callback, user_data); return dialog; } else { LLString::format_map_t args; args["[ALERT_NAME]"] = xml_desc; llwarns << "Missing Alert: [" << xml_desc << "]" << llendl; LLAlertDialog* dialogp = LLAlertDialog::showXml("MissingAlert", args); if (dialogp == NULL) { llerrs << "Bad or missing alerts.xml!" << llendl; } return NULL; } } //static LLAlertDialog* LLAlertDialog::showXml( const LLString& xml_desc, alert_callback_t callback, void *user_data) { LLString::format_map_t args; return showXml(xml_desc, args, callback, user_data); } //static LLAlertDialog* LLAlertDialog::showXml( const LLString& xml_desc, const LLString::format_map_t& args, alert_callback_t callback, void *user_data) { LLAlertDialog* dialog = createXml(xml_desc, args, callback, user_data); return dialog && dialog->show() ? dialog : NULL; } //static LLAlertDialog* LLAlertDialog::showCritical( const LLString& desc, alert_callback_t callback, void *user_data) { LLAlertDialogTemplate xml_template; LLString::format_map_t args; xml_template.mTitle = "Critical Error"; xml_template.mMessage = desc; xml_template.mModal = TRUE; xml_template.mOptions.push_back("Quit"); LLAlertDialog* dialog = new LLAlertDialog( &xml_template, args, callback, user_data); return dialog && dialog->show() ? dialog : NULL; } //----------------------------------------------------------------------------- // Private methods static const S32 VPAD = 16; static const S32 HPAD = 25; static const S32 BTN_HPAD = 8; static const LLFONT_ID font_name = LLFONT_SANSSERIF; LLAlertDialog::LLAlertDialog( const LLAlertDialogTemplate* xml_template, const LLString::format_map_t& args, alert_callback_t callback, void *user_data) : LLModalDialog( xml_template->mTitle, 100, 100, xml_template->mModal ), // dummy size. Will reshape below. mCallback( callback ), mUserData( user_data ), mNumOptions( 0 ), mDefaultOption( 0 ), mOptionChosen( -1 ), mCheck(NULL), mCaution(xml_template->mCaution), mUnique(xml_template->mUnique), mIgnorable(xml_template->mIgnorable), mLabel(xml_template->mLabel), mIgnoreLabel(xml_template->mIgnoreLabel), mButtonData(NULL), mLineEditor(NULL), mTextCallback(NULL), mURLOption(0) { mURL = xml_template->mURL; mURLOption = xml_template->mURLOption; createDialog(&(xml_template->mOptions), xml_template->mDefaultOption, xml_template->mMessage, args, xml_template->mEditLineText); setTitle(xml_template->mTitle); if (xml_template->mIgnorable) { if (xml_template->mIgnorable == IGNORE_USE_DEFAULT) { setCheckBox(sStringSkipNextTime, xml_template->mIgnoreLabel); } else // xml_template->mIgnorable == IGNORE_USE_SAVED { setCheckBox(sStringAlwaysChoose, xml_template->mIgnoreLabel); } } } // All logic for deciding not to show an alert is done here, // so that the alert is valid until show() is called. bool LLAlertDialog::show() { // If modal, check to see if we are not displaying alerts, // and do any application logic before showing modal alerts if (sDisplayCallback) { bool show = sDisplayCallback(isModal()); if (show == false) { mOptionChosen = mDefaultOption; llinfos << "Alert: " << mLabel << llendl; close(); return false; } } // Check to see if the user wants to ignore this alert if (mIgnorable > 0) { BOOL warn = sSettings->getWarning(mIgnoreLabel); if (!warn) { switch(mIgnorable) { case IGNORE_USE_DEFAULT: mOptionChosen = mDefaultOption; break; case IGNORE_USE_SAVED: mOptionChosen = sSettings->getS32("Default" + mIgnoreLabel); break; case IGNORE_SHOW_AGAIN: break; } close(); return false; } } // If this is a caution message, change the color and add an icon. if (mCaution) { setBackgroundColor( LLUI::sColorsGroup->getColor( "AlertCautionBoxColor" ) ); } else { setBackgroundColor( LLUI::sColorsGroup->getColor( "AlertBoxColor" ) ); } // Check to see if we are already displaying the alert if (mUnique) { std::map::iterator iter = sUniqueActiveMap.find(mLabel); if (iter != sUniqueActiveMap.end()) { gFloaterView->bringToFront(iter->second); mUnique = FALSE; // don't remove entry from map on destruction close(); return false; } sUniqueActiveMap[mLabel] = this; } startModal(); gFloaterView->adjustToFitScreen(this, FALSE); open(); /* Flawfinder: ignore */ setFocus(TRUE); if (mLineEditor) { mLineEditor->setFocus(TRUE); mLineEditor->selectAll(); } if(mDefaultOption >= 0) { // delay before enabling default button mDefaultBtnTimer.start(); mDefaultBtnTimer.setTimerExpirySec(DEFAULT_BUTTON_DELAY); } return true; } void LLAlertDialog::format(LLString& msg, const LLString::format_map_t& args) { // XUI:translate! LLString::format_map_t targs = args; targs["[SECOND_LIFE]"] = "Second Life"; targs["[_URL]"] = mURL; LLString::format(msg, targs); } void LLAlertDialog::createDialog(const std::vector* optionsp, S32 default_option, const LLString& msg_in, const LLString::format_map_t& args, const LLString& edit_text) { setBackgroundVisible(TRUE); setBackgroundOpaque(TRUE); const LLFontGL* font = LLResMgr::getInstance()->getRes( font_name ); const S32 LINE_HEIGHT = llfloor(font->getLineHeight() + 0.99f); const S32 EDITOR_HEIGHT = 20; // Buttons std::vector default_option_list; mNumOptions = optionsp->size(); if( 0 == mNumOptions ) { default_option_list.push_back("Close"); optionsp = &default_option_list; default_option = 0; mNumOptions = 1; } const std::vector& options = *optionsp; mButtonData = new ButtonData[mNumOptions]; // Calc total width of buttons S32 button_width = 0; S32 sp = font->getWidth("OO"); for( S32 i = 0; i < mNumOptions; i++ ) { S32 w = S32(font->getWidth( options[i] ) + 0.99f) + sp + 2 * LLBUTTON_H_PAD; button_width = llmax( w, button_width ); } S32 btn_total_width = button_width; if( mNumOptions > 1 ) { btn_total_width = (mNumOptions * button_width) + ((mNumOptions - 1) * BTN_HPAD); } // Message: create text box using raw string, as text has been structure deliberately // Use size of created text box to generate dialog box size LLString msg = msg_in; format( msg, args ); llwarns << "Alert: " << msg << llendl; LLTextBox* msg_box = new LLTextBox( "Alert message", msg, (F32)MAX_ALLOWED_MSG_WIDTH, font ); const LLRect& text_rect = msg_box->getRect(); S32 dialog_width = llmax( btn_total_width, text_rect.getWidth() ) + 2 * HPAD; S32 dialog_height = text_rect.getHeight() + 3 * VPAD + BTN_HEIGHT; if (hasTitleBar()) { dialog_height += LINE_HEIGHT; // room for title bar } if (edit_text.size() > 0) { dialog_width = llmax(dialog_width, S32(font->getWidth( edit_text ) + 0.99f)); dialog_height += EDITOR_HEIGHT; } if (mCaution) { // Make room for the caution icon. dialog_width += 32 + HPAD; } reshape( dialog_width, dialog_height, FALSE ); S32 msg_y = getRect().getHeight() - VPAD; S32 msg_x = HPAD; if (hasTitleBar()) { msg_y -= LINE_HEIGHT; // room for title } if (mCaution) { LLIconCtrl* icon = new LLIconCtrl("icon", LLRect(msg_x, msg_y, msg_x+32, msg_y-32), "notify_caution_icon.tga"); icon->setMouseOpaque(FALSE); addChild(icon); msg_x += 32 + HPAD; msg_box->setColor( LLUI::sColorsGroup->getColor( "AlertCautionTextColor" ) ); } else { msg_box->setColor( LLUI::sColorsGroup->getColor( "AlertTextColor" ) ); } LLRect rect; rect.setLeftTopAndSize( msg_x, msg_y, text_rect.getWidth(), text_rect.getHeight() ); msg_box->setRect( rect ); addChild(msg_box); // Buttons S32 button_left = (getRect().getWidth() - btn_total_width) / 2; for( S32 i = 0; i < mNumOptions; i++ ) { LLRect button_rect; button_rect.setOriginAndSize( button_left, VPAD, button_width, BTN_HEIGHT ); LLButton* btn = new LLButton( "btn", button_rect, "","", "", &LLAlertDialog::onButtonPressed, (void*)(&mButtonData[i]), font, options[i], options[i]); mButtonData[i].mSelf = this; mButtonData[i].mButton = btn; mButtonData[i].mOption = i; addChild(btn); if( i == default_option ) { btn->setFocus(TRUE); } button_left += button_width + BTN_HPAD; } // (Optional) Edit Box if (edit_text.size() > 0) { S32 y = VPAD + BTN_HEIGHT + VPAD/2; mLineEditor = new LLLineEditor("lineeditor", LLRect( HPAD, y+EDITOR_HEIGHT, dialog_width-HPAD, y), edit_text, LLFontGL::sSansSerif, STD_STRING_STR_LEN); addChild(mLineEditor); } } bool LLAlertDialog::setCheckBox( const LLString& check_title, const LLString& check_control ) { const LLFontGL* font = LLResMgr::getInstance()->getRes( font_name ); const S32 LINE_HEIGHT = llfloor(font->getLineHeight() + 0.99f); // Extend dialog for "check next time" S32 max_msg_width = getRect().getWidth() - 2 * HPAD; S32 check_width = S32(font->getWidth(check_title) + 0.99f) + 16; max_msg_width = llmax(max_msg_width, check_width); S32 dialog_width = max_msg_width + 2 * HPAD; S32 dialog_height = getRect().getHeight(); dialog_height += LINE_HEIGHT; dialog_height += LINE_HEIGHT / 2; reshape( dialog_width, dialog_height, FALSE ); S32 msg_x = (getRect().getWidth() - max_msg_width) / 2; LLRect check_rect; check_rect.setOriginAndSize(msg_x, VPAD+BTN_HEIGHT+LINE_HEIGHT/2, max_msg_width, LINE_HEIGHT); mCheck = new LLCheckboxCtrl("check", check_rect, check_title, font); addChild(mCheck); // mCheck is sometimes "show again" and sometimes "hide" :-( // If it's "Show Again", and we showed it, it must be checked. JC if (mIgnorable == IGNORE_SHOW_AGAIN) { mCheck->setValue(TRUE); } return true; } void LLAlertDialog::setVisible( BOOL visible ) { LLModalDialog::setVisible( visible ); if( visible ) { centerOnScreen(); make_ui_sound("UISndAlert"); } } void LLAlertDialog::onClose(bool app_quitting) { LLModalDialog::onClose(app_quitting); handleCallbacks(); } LLAlertDialog::~LLAlertDialog() { delete[] mButtonData; if (mUnique) { sUniqueActiveMap.erase(mLabel); } } void LLAlertDialog::handleCallbacks() { if (mOptionChosen >= 0) { if (mTextCallback && mLineEditor) { mTextCallback(mOptionChosen, mLineEditor->getText(), mUserData); } else if (mCallback) { mCallback(mOptionChosen, mUserData); } // If we declared a URL and chose the URL option, go to the url if (mOptionChosen == mURLOption) { if (!mURL.empty() && sURLLoader != NULL) { sURLLoader->load(mURL); } } // Only change warn state if we actually warned. if (mCheck && sSettings->getWarning(mIgnoreLabel)) { // mCheck sometimes means "hide and do the default" and // other times means "warn me again". Yuck. JC BOOL check = mCheck->getValue(); switch(mIgnorable) { case IGNORE_USE_DEFAULT: sSettings->setWarning(mIgnoreLabel, !check); break; case IGNORE_USE_SAVED: sSettings->setWarning(mIgnoreLabel, !check); sSettings->setS32("Default" + mIgnoreLabel, mOptionChosen); break; case IGNORE_SHOW_AGAIN: sSettings->setWarning(mIgnoreLabel, check); break; default: break; } } } } BOOL LLAlertDialog::hasTitleBar() const { return (getTitle() != "" && getTitle() != " ") // has title || isMinimizeable() || isCloseable(); } BOOL LLAlertDialog::handleKeyHere(KEY key, MASK mask ) { if( KEY_RETURN == key && mask == MASK_NONE ) { LLModalDialog::handleKeyHere( key, mask ); return TRUE; } else if (KEY_RIGHT == key) { focusNextItem(FALSE); return TRUE; } else if (KEY_LEFT == key) { focusPrevItem(FALSE); return TRUE; } else if (KEY_TAB == key && mask == MASK_NONE) { focusNextItem(FALSE); return TRUE; } else if (KEY_TAB == key && mask == MASK_SHIFT) { focusPrevItem(FALSE); return TRUE; } else { return LLModalDialog::handleKeyHere( key, mask ); } } // virtual void LLAlertDialog::draw() { // if the default button timer has just expired, activate the default button if(mDefaultBtnTimer.hasExpired() && mDefaultBtnTimer.getStarted()) { mDefaultBtnTimer.stop(); // prevent this block from being run more than once setDefaultBtn(mButtonData[mDefaultOption].mButton); } LLColor4 shadow_color = LLUI::sColorsGroup->getColor("ColorDropShadow"); S32 shadow_lines = LLUI::sConfigGroup->getS32("DropShadowFloater"); gl_drop_shadow( 0, getRect().getHeight(), getRect().getWidth(), 0, shadow_color, shadow_lines); LLModalDialog::draw(); } void LLAlertDialog::setOptionEnabled( S32 option, BOOL enable ) { if( (option >= 0) && (option < mNumOptions) ) { mButtonData[option].mButton->setEnabled( enable ); } } void LLAlertDialog::setEditTextCallback(alert_text_callback_t callback, void *user_data) { if (mLineEditor) { mTextCallback = callback; mUserData = user_data; } else { llwarns << "LLAlertDialog::setEditTextCallback called on dialog with no line editor" << llendl; } } void LLAlertDialog::setEditTextArgs(const LLString::format_map_t& edit_args) { if (mLineEditor) { LLString msg = mLineEditor->getText(); format(msg, edit_args); mLineEditor->setText(msg); } else { llwarns << "LLAlertDialog::setEditTextArgs called on dialog with no line editor" << llendl; } } void LLAlertDialog::setDrawAsterixes(BOOL enable) { if (mLineEditor) { if (enable) { mLineEditor->clear(); } mLineEditor->setDrawAsterixes(enable); } } // static void LLAlertDialog::onButtonPressed( void* userdata ) { ButtonData* button_data = (ButtonData*)userdata; LLAlertDialog* self = button_data->mSelf; self->mOptionChosen = button_data->mOption; self->close(); // deletes self } //============================================================================= //static const LLString& LLAlertDialog::getTemplateMessage(const LLString& xml_desc) { template_map_t::iterator iter = sAlertTemplates.find(xml_desc); if (iter != sAlertTemplates.end()) { return iter->second->mMessage; } else { return xml_desc; } } //static bool LLAlertDialog::parseAlerts(const LLString& xml_filename, LLControlGroup* settings, BOOL settings_only) { LLXMLNodePtr root; BOOL success = LLUICtrlFactory::getLayeredXMLNode(xml_filename, root); if (!success || root.isNull() || !root->hasName( "alerts" )) { llerrs << "Problem reading UI Alerts file: " << xml_filename << llendl; return false; } BOOL add_settings = FALSE; if (settings) { sSettings = settings; add_settings = TRUE; } llassert(sSettings); for (LLXMLNode* alert = root->getFirstChild(); alert != NULL; alert = alert->getNextSibling()) { if (alert->hasName("global")) { LLString global_name; if (alert->getAttributeString("name", global_name)) { if (global_name == "skipnexttime") { sStringSkipNextTime = alert->getTextContents(); } else if (global_name == "alwayschoose") { sStringAlwaysChoose = alert->getTextContents(); } } continue; } if (!alert->hasName("alert")) { continue; } LLAlertDialogTemplate* xml_template = settings_only ? NULL : new LLAlertDialogTemplate; // name= LLString alert_name; if (alert->getAttributeString("name", alert_name)) { if (xml_template) { xml_template->mLabel = alert_name; } } else { llwarns << "Unable to parse alert with no name" << llendl; delete xml_template; continue; } // title= LLString title; if (alert->getAttributeString("title", title)) { if (xml_template) { xml_template->mTitle = title; } } // modal= BOOL modal; if (alert->getAttributeBOOL("modal", modal)) { if (xml_template) { xml_template->mModal = modal; } } // caution= BOOL caution; if (alert->getAttributeBOOL("caution", caution)) { if (xml_template) { xml_template->mCaution = caution; } } // unique= BOOL unique; if (alert->getAttributeBOOL("unique", unique)) { if (xml_template) { xml_template->mUnique = unique; } } S32 default_option = 0; BOOL nodefault; if (alert->getAttributeBOOL("nodefault", nodefault)) { if (nodefault) { if (xml_template) { xml_template->mDefaultOption = -1; } default_option = -1; } } S32 btn_idx = 0; for (LLXMLNode* child = alert->getFirstChild(); child != NULL; child = child->getNextSibling()) { // if (child->hasName("message")) { if (xml_template) { xml_template->mMessage = child->getTextContents(); } } //