llalertdialog.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565
  1. /**
  2. * @file llalertdialog.cpp
  3. * @brief LLAlertDialog base class
  4. *
  5. * $LicenseInfo:firstyear=2001&license=viewergpl$
  6. *
  7. * Copyright (c) 2001-2009, Linden Research, Inc.
  8. *
  9. * Second Life Viewer Source Code
  10. * The source code in this file ("Source Code") is provided by Linden Lab
  11. * to you under the terms of the GNU General Public License, version 2.0
  12. * ("GPL"), unless you have obtained a separate licensing agreement
  13. * ("Other License"), formally executed by you and Linden Lab. Terms of
  14. * the GPL can be found in doc/GPL-license.txt in this distribution, or
  15. * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
  16. *
  17. * There are special exceptions to the terms and conditions of the GPL as
  18. * it is applied to this Source Code. View the full text of the exception
  19. * in the file doc/FLOSS-exception.txt in this software distribution, or
  20. * online at
  21. * http://secondlifegrid.net/programs/open_source/licensing/flossexception
  22. *
  23. * By copying, modifying or distributing this software, you acknowledge
  24. * that you have read and understood your obligations described above,
  25. * and agree to abide by those obligations.
  26. *
  27. * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
  28. * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
  29. * COMPLETENESS OR PERFORMANCE.
  30. * $/LicenseInfo$
  31. */
  32. #include "linden_common.h"
  33. #include "llalertdialog.h"
  34. #include "llbutton.h"
  35. #include "llcheckboxctrl.h"
  36. #include "llkeyboard.h"
  37. #include "llfunctorregistry.h"
  38. #include "lliconctrl.h"
  39. #include "lllineeditor.h"
  40. #include "llnotifications.h"
  41. #include "lltextbox.h"
  42. #include "lluictrlfactory.h"
  43. #include "llxmlnode.h"
  44. constexpr S32 MAX_ALLOWED_MSG_WIDTH = 400;
  45. constexpr F32 DEFAULT_BUTTON_DELAY = 0.5f;
  46. constexpr S32 DIALOG_VPAD = 16;
  47. constexpr S32 DIALOG_HPAD = 25;
  48. constexpr S32 BTN_HPAD = 8;
  49. // Static members
  50. LLControlGroup* LLAlertDialog::sSettings = NULL;
  51. LLAlertDialog::URLLoader* LLAlertDialog::sURLLoader = NULL;
  52. LLAlertDialog::lua_alert_cb_t LLAlertDialog::sLuaCallback = NULL;
  53. //static
  54. void LLAlertDialog::initClass()
  55. {
  56. LLNotificationChannel::buildChannel("Alerts", "Visible",
  57. LLNotificationFilters::filterBy<std::string>(&LLNotification::getType,
  58. "alert"));
  59. LLNotificationChannel::buildChannel("AlertModal", "Visible",
  60. LLNotificationFilters::filterBy<std::string>(&LLNotification::getType,
  61. "alertmodal"));
  62. gNotifications.getChannel("Alerts")->connectChanged(boost::bind(&onNewNotification,
  63. _1,
  64. false));
  65. gNotifications.getChannel("AlertModal")->connectChanged(boost::bind(&onNewNotification,
  66. _1,
  67. true));
  68. }
  69. //static
  70. bool LLAlertDialog::onNewNotification(const LLSD& notify, bool is_modal)
  71. {
  72. LLNotificationPtr notif = gNotifications.find(notify["id"].asUUID());
  73. if (notif)
  74. {
  75. if (notify["sigtype"].asString() == "add" ||
  76. notify["sigtype"].asString() == "load")
  77. {
  78. LLAlertDialog* dialog = new LLAlertDialog(notif, is_modal);
  79. dialog->show();
  80. }
  81. else if (notify["sigtype"].asString() == "change")
  82. {
  83. LLAlertDialog* dialog = getNamedInstance(notif->getID()).get();
  84. if (dialog)
  85. {
  86. dialog->show();
  87. }
  88. else
  89. {
  90. LLAlertDialog* dialog = new LLAlertDialog(notif, is_modal);
  91. dialog->show();
  92. }
  93. }
  94. }
  95. return false;
  96. }
  97. LLAlertDialog::LLAlertDialog(LLNotificationPtr notification, bool modal)
  98. // dummy size, will reshape below
  99. : LLModalDialog(notification->getLabel(), 100, 100, modal),
  100. LLInstanceTracker<LLAlertDialog, LLUUID>(notification->getID()),
  101. mDefaultOption(0),
  102. mCheck(NULL),
  103. mCaution(notification->getPriority() >= NOTIFICATION_PRIORITY_HIGH),
  104. mLabel(notification->getName()),
  105. mLineEditor(NULL),
  106. mNote(notification)
  107. {
  108. mFont = LLFontGL::getFontSansSerif();
  109. const S32 line_height = llfloor(mFont->getLineHeight() + 0.99f);
  110. constexpr S32 EDITOR_HEIGHT = 20;
  111. LLNotificationFormPtr form = mNote->getForm();
  112. std::string edit_text_name;
  113. std::string edit_text_contents;
  114. bool is_password = false;
  115. setBackgroundVisible(true);
  116. setBackgroundOpaque(true);
  117. typedef std::vector<std::pair<std::string, std::string> > options_t;
  118. options_t supplied_options;
  119. // For now, get LLSD to iterator over form elements
  120. LLSD form_sd = form->asLLSD();
  121. S32 option_index = 0;
  122. for (LLSD::array_const_iterator it = form_sd.beginArray();
  123. it != form_sd.endArray(); ++it)
  124. {
  125. std::string type = (*it)["type"].asString();
  126. if (type == "button")
  127. {
  128. if ((*it)["default"])
  129. {
  130. mDefaultOption = option_index;
  131. }
  132. supplied_options.emplace_back((*it)["name"].asString(),
  133. (*it)["text"].asString());
  134. if (option_index == mNote->getURLOption())
  135. {
  136. mButtonData.emplace_back(this, nullptr, mNote->getURL());
  137. }
  138. else
  139. {
  140. mButtonData.emplace_back(this);
  141. }
  142. ++option_index;
  143. }
  144. else if (type == "text")
  145. {
  146. edit_text_contents = (*it)["value"].asString();
  147. edit_text_name = (*it)["name"].asString();
  148. }
  149. else if (type == "password")
  150. {
  151. edit_text_contents = (*it)["value"].asString();
  152. edit_text_name = (*it)["name"].asString();
  153. is_password = true;
  154. }
  155. }
  156. // Buttons
  157. options_t options;
  158. if (supplied_options.empty())
  159. {
  160. options.emplace_back("close", "Close");
  161. // Add data for ok button.
  162. mButtonData.emplace_back(this);
  163. mDefaultOption = 0;
  164. }
  165. else
  166. {
  167. options = supplied_options;
  168. }
  169. S32 num_options = options.size();
  170. // Calc total width of buttons
  171. S32 button_width = 0;
  172. S32 sp = mFont->getWidth("OO");
  173. for (S32 i = 0; i < num_options; ++i)
  174. {
  175. S32 w = S32(mFont->getWidth(options[i].second) + 0.99f) + sp +
  176. 2 * gButtonHPad;
  177. button_width = llmax(w, button_width);
  178. }
  179. S32 btn_total_width = button_width;
  180. if (num_options > 1)
  181. {
  182. btn_total_width = num_options * button_width +
  183. (num_options - 1) * BTN_HPAD;
  184. }
  185. // Message: create text box using raw string, as text has been structured
  186. // deliberately. Use size of created text box to generate dialog box size.
  187. std::string msg = mNote->getMessage();
  188. llwarns << "Alert: " << msg << llendl;
  189. LLTextBox* msg_box = new LLTextBox("Alert message", msg,
  190. (F32)MAX_ALLOWED_MSG_WIDTH, mFont);
  191. const LLRect& text_rect = msg_box->getRect();
  192. S32 dialog_width = llmax(btn_total_width, text_rect.getWidth()) +
  193. 2 * DIALOG_HPAD;
  194. S32 dialog_height = text_rect.getHeight() + 3 * DIALOG_VPAD + gBtnHeight;
  195. if (hasTitleBar())
  196. {
  197. dialog_height += line_height; // room for title bar
  198. }
  199. // It's ok for the edit text body to be empty, but we want the name to
  200. // exist if we're going to draw it
  201. if (!edit_text_name.empty())
  202. {
  203. dialog_height += EDITOR_HEIGHT + DIALOG_VPAD;
  204. dialog_width = llmax(dialog_width,
  205. (S32)(mFont->getWidth(edit_text_contents) + 0.99f));
  206. }
  207. if (mCaution)
  208. {
  209. // Make room for the caution icon.
  210. dialog_width += 32 + DIALOG_HPAD;
  211. }
  212. reshape(dialog_width, dialog_height, false);
  213. S32 msg_y = getRect().getHeight() - DIALOG_VPAD;
  214. S32 msg_x = DIALOG_HPAD;
  215. if (hasTitleBar())
  216. {
  217. msg_y -= line_height; // room for title
  218. }
  219. if (mCaution)
  220. {
  221. LLIconCtrl* icon =
  222. new LLIconCtrl("icon", LLRect(msg_x, msg_y, msg_x + 32, msg_y - 32),
  223. "notify_caution_icon.tga");
  224. icon->setMouseOpaque(false);
  225. addChild(icon);
  226. msg_x += 32 + DIALOG_HPAD;
  227. msg_box->setColor(LLUI::sColorsGroup->getColor("AlertCautionTextColor"));
  228. }
  229. else
  230. {
  231. msg_box->setColor(LLUI::sColorsGroup->getColor("AlertTextColor"));
  232. }
  233. LLRect rect;
  234. rect.setLeftTopAndSize(msg_x, msg_y, text_rect.getWidth(),
  235. text_rect.getHeight());
  236. msg_box->setRect(rect);
  237. addChild(msg_box);
  238. // Buttons
  239. S32 button_left = (getRect().getWidth() - btn_total_width) / 2;
  240. for (S32 i = 0; i < num_options; ++i)
  241. {
  242. LLRect button_rect;
  243. button_rect.setOriginAndSize(button_left, DIALOG_VPAD, button_width,
  244. gBtnHeight);
  245. LLButton* btn = new LLButton(options[i].first, button_rect, "", "", "",
  246. NULL, NULL, mFont, options[i].second,
  247. options[i].second);
  248. mButtonData[i].mButton = btn;
  249. btn->setClickedCallback(&LLAlertDialog::onButtonPressed,
  250. (void*)(&mButtonData[i]));
  251. addChild(btn);
  252. if (i == mDefaultOption)
  253. {
  254. btn->setFocus(true);
  255. }
  256. button_left += button_width + BTN_HPAD;
  257. }
  258. // (Optional) Edit Box
  259. if (!edit_text_name.empty())
  260. {
  261. S32 y = (DIALOG_VPAD + DIALOG_VPAD / 2) + gBtnHeight;
  262. mLineEditor = new LLLineEditor(edit_text_name,
  263. LLRect(DIALOG_HPAD, y + EDITOR_HEIGHT,
  264. dialog_width - DIALOG_HPAD, y),
  265. edit_text_contents,
  266. LLFontGL::getFontSansSerif(),
  267. STD_STRING_STR_LEN);
  268. // Make sure all edit keys get handled properly (DEV-22396)
  269. mLineEditor->setHandleEditKeysDirectly(true);
  270. addChild(mLineEditor);
  271. }
  272. if (mLineEditor)
  273. {
  274. mLineEditor->setDrawAsterixes(is_password);
  275. setEditTextArgs(notification->getSubstitutions());
  276. }
  277. std::string ignore_label;
  278. LLNotificationForm::EIgnoreType form_type = form->getIgnoreType();
  279. if (form_type == LLNotificationForm::IGNORE_WITH_DEFAULT_RESPONSE)
  280. {
  281. setCheckBox(gNotifications.getGlobalString("skipnexttime"),
  282. ignore_label);
  283. }
  284. else if (form_type == LLNotificationForm::IGNORE_WITH_LAST_RESPONSE)
  285. {
  286. setCheckBox(gNotifications.getGlobalString("alwayschoose"),
  287. ignore_label);
  288. }
  289. }
  290. // All logic for deciding not to show an alert is done here, so that the alert
  291. // is valid until show() is called.
  292. bool LLAlertDialog::show()
  293. {
  294. // If this is a caution message, change the color and add an icon.
  295. setBackgroundColor(mCaution ? LLUI::sAlertCautionBoxColor
  296. : LLUI::sAlertBoxColor);
  297. startModal();
  298. gFloaterViewp->adjustToFitScreen(this);
  299. open();
  300. setFocus(true);
  301. if (mLineEditor)
  302. {
  303. mLineEditor->setFocus(true);
  304. mLineEditor->selectAll();
  305. }
  306. if (mDefaultOption >= 0)
  307. {
  308. // Delay before enabling default button
  309. mDefaultBtnTimer.start();
  310. mDefaultBtnTimer.setTimerExpirySec(DEFAULT_BUTTON_DELAY);
  311. }
  312. // Attach to floater if necessary
  313. LLUUID context_key = mNote->getPayload()["context"].asUUID();
  314. LLFloaterNotificationContext* contextp =
  315. dynamic_cast<LLFloaterNotificationContext*>(
  316. LLNotificationContext::getNamedInstance(context_key).get());
  317. if (contextp && contextp->getFloater())
  318. {
  319. contextp->getFloater()->addDependentFloater(this, false);
  320. }
  321. // Send the alert box data to the Lua automation script callback, when
  322. // any. HB
  323. if (sLuaCallback)
  324. {
  325. std::vector<std::string> buttons;
  326. for (size_t i = 0, count = mButtonData.size(); i < count; ++i)
  327. {
  328. LLButton* buttontp = mButtonData[i].mButton;
  329. if (buttontp)
  330. {
  331. buttons.emplace_back(buttontp->getCurrentLabel());
  332. }
  333. else if (!mButtonData[i].mURL.empty())
  334. {
  335. buttons.emplace_back(mButtonData[i].mURL);
  336. }
  337. else
  338. {
  339. buttons.emplace_back("<unset>");
  340. }
  341. }
  342. sLuaCallback(mNote->getName(), mNote->getID(), mNote->getMessage(),
  343. buttons);
  344. }
  345. return true;
  346. }
  347. bool LLAlertDialog::setCheckBox(const std::string& check_title,
  348. const std::string& check_control)
  349. {
  350. const S32 line_height = llfloor(mFont->getLineHeight() + 0.99f);
  351. // Extend dialog for "check next time"
  352. S32 max_msg_width = getRect().getWidth() - 2 * DIALOG_HPAD;
  353. S32 check_width = S32(mFont->getWidth(check_title) + 0.99f) + 16;
  354. max_msg_width = llmax(max_msg_width, check_width);
  355. S32 dialog_width = max_msg_width + 2 * DIALOG_HPAD;
  356. S32 dialog_height = getRect().getHeight();
  357. dialog_height += line_height + line_height / 2;
  358. reshape(dialog_width, dialog_height, false);
  359. S32 msg_x = (getRect().getWidth() - max_msg_width) / 2;
  360. LLRect check_rect;
  361. check_rect.setOriginAndSize(msg_x,
  362. DIALOG_VPAD + gBtnHeight + line_height / 2,
  363. max_msg_width, line_height);
  364. mCheck = new LLCheckBoxCtrl("check", check_rect, check_title, mFont,
  365. onClickIgnore, this);
  366. addChild(mCheck);
  367. return true;
  368. }
  369. void LLAlertDialog::setVisible(bool visible)
  370. {
  371. LLModalDialog::setVisible(visible);
  372. if (visible)
  373. {
  374. centerOnScreen();
  375. make_ui_sound("UISndAlert");
  376. }
  377. }
  378. void LLAlertDialog::onClose(bool app_quitting)
  379. {
  380. LLModalDialog::onClose(app_quitting);
  381. }
  382. bool LLAlertDialog::hasTitleBar() const
  383. {
  384. return isMinimizeable() || isCloseable() ||
  385. // Or if it has a title...
  386. (getCurrentTitle() != "" && getCurrentTitle() != " ");
  387. }
  388. //virtual
  389. bool LLAlertDialog::handleKeyHere(KEY key, MASK mask)
  390. {
  391. if (KEY_RETURN == key && mask == MASK_NONE)
  392. {
  393. LLModalDialog::handleKeyHere(key, mask);
  394. return true;
  395. }
  396. else if (KEY_RIGHT == key)
  397. {
  398. focusNextItem(false);
  399. return true;
  400. }
  401. else if (KEY_LEFT == key)
  402. {
  403. focusPrevItem(false);
  404. return true;
  405. }
  406. else if (KEY_TAB == key && mask == MASK_NONE)
  407. {
  408. focusNextItem(false);
  409. return true;
  410. }
  411. else if (KEY_TAB == key && mask == MASK_SHIFT)
  412. {
  413. focusPrevItem(false);
  414. return true;
  415. }
  416. else
  417. {
  418. return LLModalDialog::handleKeyHere(key, mask);
  419. }
  420. }
  421. //virtual
  422. void LLAlertDialog::draw()
  423. {
  424. // If the default button timer has just expired, activate the default
  425. // button
  426. if (mDefaultBtnTimer.hasExpired() && mDefaultBtnTimer.getStarted())
  427. {
  428. // prevent this block from being run more than once:
  429. mDefaultBtnTimer.stop();
  430. setDefaultBtn(mButtonData[mDefaultOption].mButton);
  431. }
  432. gl_drop_shadow(0, getRect().getHeight(), getRect().getWidth(), 0,
  433. LLUI::sColorDropShadow, LLUI::sDropShadowFloater);
  434. LLModalDialog::draw();
  435. }
  436. void LLAlertDialog::setEditTextArgs(const LLSD& edit_args)
  437. {
  438. if (mLineEditor)
  439. {
  440. std::string msg = mLineEditor->getText();
  441. mLineEditor->setText(msg);
  442. }
  443. else
  444. {
  445. llwarns << "Call done on dialog with no line editor" << llendl;
  446. }
  447. }
  448. bool LLAlertDialog::simulateClickButton(S32 i)
  449. {
  450. if (i < 0 || i >= (S32)mButtonData.size())
  451. {
  452. return false;
  453. }
  454. onButtonPressed(&mButtonData[i]);
  455. return true;
  456. }
  457. //static
  458. void LLAlertDialog::onButtonPressed(void* userdata)
  459. {
  460. ButtonData* button_data = (ButtonData*)userdata;
  461. LLAlertDialog* self = button_data->mSelf;
  462. if (!self || !button_data) return;
  463. LLSD response = self->mNote->getResponseTemplate();
  464. if (self->mLineEditor)
  465. {
  466. response[self->mLineEditor->getName()] = self->mLineEditor->getValue();
  467. }
  468. response[button_data->mButton->getName()] = true;
  469. // If we declared a URL and chose the URL option, go to the url
  470. if (!button_data->mURL.empty() && sURLLoader)
  471. {
  472. sURLLoader->load(button_data->mURL);
  473. }
  474. self->mNote->respond(response); // New notification reponse
  475. self->close(); // Delete self
  476. }
  477. //static
  478. void LLAlertDialog::onClickIgnore(LLUICtrl* ctrl, void* user_data)
  479. {
  480. LLAlertDialog* self = (LLAlertDialog*)user_data;
  481. if (!self) return;
  482. // Checkbox sometimes means "hide and do the default" and other times means
  483. // "warn me again". Yuck. JC
  484. bool check = ctrl->getValue();
  485. if (self->mNote->getForm()->getIgnoreType() ==
  486. LLNotificationForm::IGNORE_SHOW_AGAIN)
  487. {
  488. // Question was "show again" so invert value to get "ignore"
  489. check = !check;
  490. }
  491. self->mNote->setIgnored(check);
  492. }