/indra/newview/lltoastalertpanel.cpp

https://bitbucket.org/lindenlab/viewer-beta/ · C++ · 525 lines · 379 code · 81 blank · 65 comment · 77 complexity · 598564598e076725fb0b590393ac43c4 MD5 · raw file

  1. /**
  2. * @file lltoastalertpanel.cpp
  3. * @brief Panel for alert toasts.
  4. *
  5. * $LicenseInfo:firstyear=2001&license=viewerlgpl$
  6. * Second Life Viewer Source Code
  7. * Copyright (C) 2010, Linden Research, Inc.
  8. *
  9. * This library is free software; you can redistribute it and/or
  10. * modify it under the terms of the GNU Lesser General Public
  11. * License as published by the Free Software Foundation;
  12. * version 2.1 of the License only.
  13. *
  14. * This library is distributed in the hope that it will be useful,
  15. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  17. * Lesser General Public License for more details.
  18. *
  19. * You should have received a copy of the GNU Lesser General Public
  20. * License along with this library; if not, write to the Free Software
  21. * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  22. *
  23. * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
  24. * $/LicenseInfo$
  25. */
  26. #include "llviewerprecompiledheaders.h" // must be first include
  27. #include "linden_common.h"
  28. #include "llboost.h"
  29. #include "lltoastalertpanel.h"
  30. #include "llfontgl.h"
  31. #include "lltextbox.h"
  32. #include "llbutton.h"
  33. #include "llcheckboxctrl.h"
  34. #include "llkeyboard.h"
  35. #include "llfocusmgr.h"
  36. #include "lliconctrl.h"
  37. #include "llui.h"
  38. #include "lllineeditor.h"
  39. #include "lluictrlfactory.h"
  40. #include "llnotifications.h"
  41. #include "llfunctorregistry.h"
  42. #include "llrootview.h"
  43. #include "lltransientfloatermgr.h"
  44. #include "llviewercontrol.h" // for gSavedSettings
  45. const S32 MAX_ALLOWED_MSG_WIDTH = 400;
  46. const F32 DEFAULT_BUTTON_DELAY = 0.5f;
  47. const S32 MSG_PAD = 8;
  48. /*static*/ LLControlGroup* LLToastAlertPanel::sSettings = NULL;
  49. /*static*/ LLToastAlertPanel::URLLoader* LLToastAlertPanel::sURLLoader;
  50. //-----------------------------------------------------------------------------
  51. // Private methods
  52. static const S32 VPAD = 16;
  53. static const S32 HPAD = 25;
  54. static const S32 BTN_HPAD = 8;
  55. LLToastAlertPanel::LLToastAlertPanel( LLNotificationPtr notification, bool modal)
  56. : LLToastPanel(notification),
  57. mDefaultOption( 0 ),
  58. mCheck(NULL),
  59. mCaution(notification->getPriority() >= NOTIFICATION_PRIORITY_HIGH),
  60. mLabel(notification->getName()),
  61. mLineEditor(NULL)
  62. {
  63. const LLFontGL* font = LLFontGL::getFontSansSerif();
  64. const S32 LINE_HEIGHT = llfloor(font->getLineHeight() + 0.99f);
  65. const S32 EDITOR_HEIGHT = 20;
  66. LLNotificationFormPtr form = mNotification->getForm();
  67. std::string edit_text_name;
  68. std::string edit_text_contents;
  69. S32 edit_text_max_chars = 0;
  70. bool is_password = false;
  71. LLToastPanel::setBackgroundVisible(FALSE);
  72. LLToastPanel::setBackgroundOpaque(TRUE);
  73. typedef std::vector<std::pair<std::string, std::string> > options_t;
  74. options_t supplied_options;
  75. // for now, get LLSD to iterator over form elements
  76. LLSD form_sd = form->asLLSD();
  77. S32 option_index = 0;
  78. for (LLSD::array_const_iterator it = form_sd.beginArray(); it != form_sd.endArray(); ++it)
  79. {
  80. std::string type = (*it)["type"].asString();
  81. if (type == "button")
  82. {
  83. if((*it)["default"])
  84. {
  85. mDefaultOption = option_index;
  86. }
  87. supplied_options.push_back(std::make_pair((*it)["name"].asString(), (*it)["text"].asString()));
  88. ButtonData data;
  89. if (option_index == mNotification->getURLOption())
  90. {
  91. data.mURL = mNotification->getURL();
  92. data.mURLExternal = mNotification->getURLOpenExternally();
  93. }
  94. mButtonData.push_back(data);
  95. option_index++;
  96. }
  97. else if (type == "text")
  98. {
  99. edit_text_contents = (*it)["value"].asString();
  100. edit_text_name = (*it)["name"].asString();
  101. edit_text_max_chars = (*it)["max_length_chars"].asInteger();
  102. }
  103. else if (type == "password")
  104. {
  105. edit_text_contents = (*it)["value"].asString();
  106. edit_text_name = (*it)["name"].asString();
  107. is_password = true;
  108. }
  109. }
  110. // Buttons
  111. options_t options;
  112. if (supplied_options.empty())
  113. {
  114. options.push_back(std::make_pair(std::string("close"), LLNotifications::instance().getGlobalString("implicitclosebutton")));
  115. // add data for ok button.
  116. ButtonData ok_button;
  117. mButtonData.push_back(ok_button);
  118. mDefaultOption = 0;
  119. }
  120. else
  121. {
  122. options = supplied_options;
  123. }
  124. S32 num_options = options.size();
  125. // Calc total width of buttons
  126. S32 button_width = 0;
  127. S32 sp = font->getWidth(std::string("OO"));
  128. for( S32 i = 0; i < num_options; i++ )
  129. {
  130. S32 w = S32(font->getWidth( options[i].second ) + 0.99f) + sp + 2 * LLBUTTON_H_PAD;
  131. button_width = llmax( w, button_width );
  132. }
  133. S32 btn_total_width = button_width;
  134. if( num_options > 1 )
  135. {
  136. btn_total_width = (num_options * button_width) + ((num_options - 1) * BTN_HPAD);
  137. }
  138. // Message: create text box using raw string, as text has been structure deliberately
  139. // Use size of created text box to generate dialog box size
  140. std::string msg = mNotification->getMessage();
  141. llwarns << "Alert: " << msg << llendl;
  142. LLTextBox::Params params;
  143. params.name("Alert message");
  144. params.font(font);
  145. params.tab_stop(false);
  146. params.wrap(true);
  147. params.follows.flags(FOLLOWS_LEFT | FOLLOWS_TOP);
  148. params.allow_scroll(true);
  149. LLTextBox * msg_box = LLUICtrlFactory::create<LLTextBox> (params);
  150. // Compute max allowable height for the dialog text, so we can allocate
  151. // space before wrapping the text to fit.
  152. S32 max_allowed_msg_height =
  153. gFloaterView->getRect().getHeight()
  154. - LINE_HEIGHT // title bar
  155. - 3*VPAD - BTN_HEIGHT;
  156. // reshape to calculate real text width and height
  157. msg_box->reshape( MAX_ALLOWED_MSG_WIDTH, max_allowed_msg_height );
  158. msg_box->setValue(msg);
  159. S32 pixel_width = msg_box->getTextPixelWidth();
  160. S32 pixel_height = msg_box->getTextPixelHeight();
  161. // We should use some space to prevent set textbox's scroller visible when it is unnecessary.
  162. msg_box->reshape( llmin(MAX_ALLOWED_MSG_WIDTH,pixel_width + 2 * msg_box->getHPad() + HPAD),
  163. llmin(max_allowed_msg_height,pixel_height + 2 * msg_box->getVPad()) ) ;
  164. const LLRect& text_rect = msg_box->getRect();
  165. S32 dialog_width = llmax( btn_total_width, text_rect.getWidth() ) + 2 * HPAD;
  166. S32 dialog_height = text_rect.getHeight() + 3 * VPAD + BTN_HEIGHT;
  167. if (hasTitleBar())
  168. {
  169. dialog_height += LINE_HEIGHT; // room for title bar
  170. }
  171. // it's ok for the edit text body to be empty, but we want the name to exist if we're going to draw it
  172. if (!edit_text_name.empty())
  173. {
  174. dialog_height += EDITOR_HEIGHT + VPAD;
  175. dialog_width = llmax(dialog_width, (S32)(font->getWidth( edit_text_contents ) + 0.99f));
  176. }
  177. if (mCaution)
  178. {
  179. // Make room for the caution icon.
  180. dialog_width += 32 + HPAD;
  181. }
  182. LLToastPanel::reshape( dialog_width, dialog_height, FALSE );
  183. S32 msg_y = LLToastPanel::getRect().getHeight() - VPAD;
  184. S32 msg_x = HPAD;
  185. if (hasTitleBar())
  186. {
  187. msg_y -= LINE_HEIGHT; // room for title
  188. }
  189. static LLUIColor alert_caution_text_color = LLUIColorTable::instance().getColor("AlertCautionTextColor");
  190. if (mCaution)
  191. {
  192. LLIconCtrl* icon = LLUICtrlFactory::getInstance()->createFromFile<LLIconCtrl>("alert_icon.xml", this, LLPanel::child_registry_t::instance());
  193. if(icon)
  194. {
  195. icon->setRect(LLRect(msg_x, msg_y, msg_x+32, msg_y-32));
  196. LLToastPanel::addChild(icon);
  197. }
  198. msg_x += 32 + HPAD;
  199. msg_box->setColor( alert_caution_text_color );
  200. }
  201. LLRect rect;
  202. rect.setLeftTopAndSize( msg_x, msg_y, text_rect.getWidth(), text_rect.getHeight() );
  203. msg_box->setRect( rect );
  204. LLToastPanel::addChild(msg_box);
  205. // (Optional) Edit Box
  206. if (!edit_text_name.empty())
  207. {
  208. S32 y = VPAD + BTN_HEIGHT + VPAD/2;
  209. mLineEditor = LLUICtrlFactory::getInstance()->createFromFile<LLLineEditor>("alert_line_editor.xml", this, LLPanel::child_registry_t::instance());
  210. if (mLineEditor)
  211. {
  212. LLRect leditor_rect = LLRect( HPAD, y+EDITOR_HEIGHT, dialog_width-HPAD, y);
  213. mLineEditor->setName(edit_text_name);
  214. mLineEditor->reshape(leditor_rect.getWidth(), leditor_rect.getHeight());
  215. mLineEditor->setRect(leditor_rect);
  216. mLineEditor->setMaxTextChars(edit_text_max_chars);
  217. mLineEditor->setText(edit_text_contents);
  218. // decrease limit of line editor of teleport offer dialog to avoid truncation of
  219. // location URL in invitation message, see EXT-6891
  220. if ("OfferTeleport" == mNotification->getName())
  221. {
  222. mLineEditor->setMaxTextLength(gSavedSettings.getS32(
  223. "teleport_offer_invitation_max_length"));
  224. }
  225. else
  226. {
  227. mLineEditor->setMaxTextLength(STD_STRING_STR_LEN - 1);
  228. }
  229. LLToastPanel::addChild(mLineEditor);
  230. mLineEditor->setDrawAsterixes(is_password);
  231. setEditTextArgs(notification->getSubstitutions());
  232. mLineEditor->setFollowsLeft();
  233. mLineEditor->setFollowsRight();
  234. // find form text input field
  235. LLSD form_text;
  236. for (LLSD::array_const_iterator it = form_sd.beginArray(); it != form_sd.endArray(); ++it)
  237. {
  238. std::string type = (*it)["type"].asString();
  239. if (type == "text")
  240. {
  241. form_text = (*it);
  242. }
  243. }
  244. // if form text input field has width attribute
  245. if (form_text.has("width"))
  246. {
  247. // adjust floater width to fit line editor
  248. S32 editor_width = form_text["width"];
  249. LLRect editor_rect = mLineEditor->getRect();
  250. U32 width_delta = editor_width - editor_rect.getWidth();
  251. LLRect toast_rect = getRect();
  252. reshape(toast_rect.getWidth() + width_delta, toast_rect.getHeight());
  253. }
  254. }
  255. }
  256. // Buttons
  257. S32 button_left = (LLToastPanel::getRect().getWidth() - btn_total_width) / 2;
  258. for( S32 i = 0; i < num_options; i++ )
  259. {
  260. LLRect button_rect;
  261. LLButton* btn = LLUICtrlFactory::getInstance()->createFromFile<LLButton>("alert_button.xml", this, LLPanel::child_registry_t::instance());
  262. if(btn)
  263. {
  264. btn->setName(options[i].first);
  265. btn->setRect(button_rect.setOriginAndSize( button_left, VPAD, button_width, BTN_HEIGHT ));
  266. btn->setLabel(options[i].second);
  267. btn->setFont(font);
  268. btn->setClickedCallback(boost::bind(&LLToastAlertPanel::onButtonPressed, this, _2, i));
  269. mButtonData[i].mButton = btn;
  270. LLToastPanel::addChild(btn);
  271. if( i == mDefaultOption )
  272. {
  273. btn->setFocus(TRUE);
  274. }
  275. }
  276. button_left += button_width + BTN_HPAD;
  277. }
  278. std::string ignore_label;
  279. if (form->getIgnoreType() == LLNotificationForm::IGNORE_WITH_DEFAULT_RESPONSE)
  280. {
  281. setCheckBox(LLNotifications::instance().getGlobalString("skipnexttime"), ignore_label);
  282. }
  283. else if (form->getIgnoreType() == LLNotificationForm::IGNORE_WITH_LAST_RESPONSE)
  284. {
  285. setCheckBox(LLNotifications::instance().getGlobalString("alwayschoose"), ignore_label);
  286. }
  287. // *TODO: check necessity of this code
  288. //gFloaterView->adjustToFitScreen(this, FALSE);
  289. if (mLineEditor)
  290. {
  291. mLineEditor->selectAll();
  292. }
  293. if(mDefaultOption >= 0)
  294. {
  295. // delay before enabling default button
  296. mDefaultBtnTimer.start();
  297. mDefaultBtnTimer.setTimerExpirySec(DEFAULT_BUTTON_DELAY);
  298. }
  299. LLTransientFloaterMgr::instance().addControlView(
  300. LLTransientFloaterMgr::GLOBAL, this);
  301. }
  302. bool LLToastAlertPanel::setCheckBox( const std::string& check_title, const std::string& check_control )
  303. {
  304. mCheck = LLUICtrlFactory::getInstance()->createFromFile<LLCheckBoxCtrl>("alert_check_box.xml", this, LLPanel::child_registry_t::instance());
  305. if(!mCheck)
  306. {
  307. return false;
  308. }
  309. const LLFontGL* font = mCheck->getFont();
  310. const S32 LINE_HEIGHT = llfloor(font->getLineHeight() + 0.99f);
  311. // Extend dialog for "check next time"
  312. S32 max_msg_width = LLToastPanel::getRect().getWidth() - 2 * HPAD;
  313. S32 check_width = S32(font->getWidth(check_title) + 0.99f) + 16;
  314. max_msg_width = llmax(max_msg_width, check_width);
  315. S32 dialog_width = max_msg_width + 2 * HPAD;
  316. S32 dialog_height = LLToastPanel::getRect().getHeight();
  317. dialog_height += LINE_HEIGHT;
  318. dialog_height += LINE_HEIGHT / 2;
  319. LLToastPanel::reshape( dialog_width, dialog_height, FALSE );
  320. S32 msg_x = (LLToastPanel::getRect().getWidth() - max_msg_width) / 2;
  321. // set check_box's attributes
  322. LLRect check_rect;
  323. mCheck->setRect(check_rect.setOriginAndSize(msg_x, VPAD+BTN_HEIGHT+LINE_HEIGHT/2, max_msg_width, LINE_HEIGHT));
  324. mCheck->setLabel(check_title);
  325. mCheck->setCommitCallback(boost::bind(&LLToastAlertPanel::onClickIgnore, this, _1));
  326. LLToastPanel::addChild(mCheck);
  327. return true;
  328. }
  329. void LLToastAlertPanel::setVisible( BOOL visible )
  330. {
  331. // only make the "ding" sound if it's newly visible
  332. if( visible && !LLToastPanel::getVisible() )
  333. {
  334. make_ui_sound("UISndAlert");
  335. }
  336. LLToastPanel::setVisible( visible );
  337. }
  338. LLToastAlertPanel::~LLToastAlertPanel()
  339. {
  340. LLTransientFloaterMgr::instance().removeControlView(
  341. LLTransientFloaterMgr::GLOBAL, this);
  342. }
  343. BOOL LLToastAlertPanel::hasTitleBar() const
  344. {
  345. // *TODO: check necessity of this code
  346. /*
  347. return (getCurrentTitle() != "" && getCurrentTitle() != " ") // has title
  348. || isMinimizeable()
  349. || isCloseable();
  350. */
  351. return false;
  352. }
  353. BOOL LLToastAlertPanel::handleKeyHere(KEY key, MASK mask )
  354. {
  355. if( KEY_RETURN == key && mask == MASK_NONE )
  356. {
  357. LLButton* defaultBtn = getDefaultButton();
  358. if(defaultBtn && defaultBtn->getVisible() && defaultBtn->getEnabled())
  359. {
  360. // If we have a default button, click it when return is pressed
  361. defaultBtn->onCommit();
  362. }
  363. return TRUE;
  364. }
  365. else if (KEY_RIGHT == key)
  366. {
  367. LLToastPanel::focusNextItem(FALSE);
  368. return TRUE;
  369. }
  370. else if (KEY_LEFT == key)
  371. {
  372. LLToastPanel::focusPrevItem(FALSE);
  373. return TRUE;
  374. }
  375. else if (KEY_TAB == key && mask == MASK_NONE)
  376. {
  377. LLToastPanel::focusNextItem(FALSE);
  378. return TRUE;
  379. }
  380. else if (KEY_TAB == key && mask == MASK_SHIFT)
  381. {
  382. LLToastPanel::focusPrevItem(FALSE);
  383. return TRUE;
  384. }
  385. else
  386. {
  387. return TRUE;
  388. }
  389. }
  390. // virtual
  391. void LLToastAlertPanel::draw()
  392. {
  393. // if the default button timer has just expired, activate the default button
  394. if(mDefaultBtnTimer.hasExpired() && mDefaultBtnTimer.getStarted())
  395. {
  396. mDefaultBtnTimer.stop(); // prevent this block from being run more than once
  397. LLToastPanel::setDefaultBtn(mButtonData[mDefaultOption].mButton);
  398. }
  399. static LLUIColor shadow_color = LLUIColorTable::instance().getColor("ColorDropShadow");
  400. static LLUICachedControl<S32> shadow_lines ("DropShadowFloater");
  401. gl_drop_shadow( 0, LLToastPanel::getRect().getHeight(), LLToastPanel::getRect().getWidth(), 0,
  402. shadow_color, shadow_lines);
  403. LLToastPanel::draw();
  404. }
  405. void LLToastAlertPanel::setEditTextArgs(const LLSD& edit_args)
  406. {
  407. if (mLineEditor)
  408. {
  409. std::string msg = mLineEditor->getText();
  410. mLineEditor->setText(msg);
  411. }
  412. else
  413. {
  414. llwarns << "LLToastAlertPanel::setEditTextArgs called on dialog with no line editor" << llendl;
  415. }
  416. }
  417. void LLToastAlertPanel::onButtonPressed( const LLSD& data, S32 button )
  418. {
  419. ButtonData* button_data = &mButtonData[button];
  420. LLSD response = mNotification->getResponseTemplate();
  421. if (mLineEditor)
  422. {
  423. response[mLineEditor->getName()] = mLineEditor->getValue();
  424. }
  425. response[button_data->mButton->getName()] = true;
  426. // If we declared a URL and chose the URL option, go to the url
  427. if (!button_data->mURL.empty() && sURLLoader != NULL)
  428. {
  429. sURLLoader->load(button_data->mURL, button_data->mURLExternal);
  430. }
  431. mNotification->respond(response); // new notification reponse
  432. }
  433. void LLToastAlertPanel::onClickIgnore(LLUICtrl* ctrl)
  434. {
  435. // checkbox sometimes means "hide and do the default" and
  436. // other times means "warn me again". Yuck. JC
  437. BOOL check = ctrl->getValue().asBoolean();
  438. if (mNotification->getForm()->getIgnoreType() == LLNotificationForm::IGNORE_SHOW_AGAIN)
  439. {
  440. // question was "show again" so invert value to get "ignore"
  441. check = !check;
  442. }
  443. mNotification->setIgnored(check);
  444. }