llnotify.cpp 30 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157
  1. /**
  2. * @file llnotify.cpp
  3. * @brief Non-blocking notification that doesn't take keyboard focus.
  4. *
  5. * $LicenseInfo:firstyear=2003&license=viewergpl$
  6. *
  7. * Copyright (c) 2003-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 "llviewerprecompiledheaders.h"
  33. #include "llnotify.h"
  34. #include "llalertdialog.h"
  35. #include "llbutton.h"
  36. #include "llgl.h"
  37. #include "lliconctrl.h"
  38. #include "llrender.h"
  39. #include "lltextbox.h"
  40. #include "lltexteditor.h"
  41. #include "lluictrlfactory.h"
  42. #include "llxmlnode.h"
  43. #include "llchat.h"
  44. #include "llfloaterchat.h" // For addChatHistory()
  45. #include "llgroupnotify.h" // For getGroupNotifyBoxCount()
  46. #include "lloverlaybar.h" // For gOverlayBarp
  47. #include "llpanellogin.h" // For LLPanelLogin::isVisible()
  48. //MK
  49. #include "mkrlinterface.h"
  50. //mk
  51. #include "llslurl.h"
  52. #include "llstatusbar.h" // For gStatusBarp
  53. #include "llviewercontrol.h"
  54. #include "hbviewerautomation.h"
  55. #include "llviewerdisplay.h"
  56. #include "llviewertexturelist.h"
  57. // Globals
  58. // Instance created in LLViewerWindow::initBase()
  59. LLNotifyBoxView* gNotifyBoxViewp = NULL;
  60. constexpr S32 BOTTOM_PAD = VPAD * 3;
  61. constexpr F32 ANIMATION_TIME = 0.333f;
  62. // Static members
  63. bool LLNotifyBox::sShowNotifications = true;
  64. S32 LLNotifyBox::sNotifyBoxCount = 0;
  65. S32 LLNotifyBox::sNotifyTipCount = 0;
  66. std::map<std::string, LLNotifyBox*> LLNotifyBox::sOpenUniqueNotifyBoxes;
  67. LLNotifyBox::name_lookup_map_t LLNotifyBox::sNameLookupMap;
  68. LLUUID LLNotifyBox::sLastNotifyRestartId;
  69. //---------------------------------------------------------------------------
  70. // LLNotifyBox class
  71. //---------------------------------------------------------------------------
  72. //static
  73. void LLNotifyBox::initClass()
  74. {
  75. LLNotificationChannel::buildChannel("Notifications", "Visible",
  76. LLNotificationFilters::filterBy<std::string>(&LLNotification::getType,
  77. "notify"));
  78. LLNotificationChannel::buildChannel("NotificationTips", "Visible",
  79. LLNotificationFilters::filterBy<std::string>(&LLNotification::getType,
  80. "notifytip"));
  81. gNotifications.getChannel("Notifications")->connectChanged(&LLNotifyBox::onNotification);
  82. gNotifications.getChannel("NotificationTips")->connectChanged(&LLNotifyBox::onNotification);
  83. }
  84. //static
  85. bool LLNotifyBox::onNotification(const LLSD& notify)
  86. {
  87. LLNotificationPtr notifp = gNotifications.find(notify["id"].asUUID());
  88. if (!notifp) return false;
  89. std::string sigtype = notify["sigtype"].asString();
  90. LLNotifyBox* self = getNamedInstance(notifp->getID()).get();
  91. if (self && !self->isDead())
  92. {
  93. if (sigtype == "delete")
  94. {
  95. self->close();
  96. }
  97. else if (!self->mIsTip && (sigtype == "add" || sigtype == "change"))
  98. {
  99. // Bring existing notification to top
  100. gNotifyBoxViewp->showOnly(self);
  101. }
  102. return false;
  103. }
  104. if (sigtype == "add" || sigtype == "change")
  105. {
  106. std::string dialog_name = notifp->getName();
  107. bool is_script_dialog = (dialog_name == "ScriptDialog" ||
  108. dialog_name == "ScriptDialogOurs");
  109. bool is_ours = (dialog_name == "ScriptDialogOurs" ||
  110. dialog_name == "ScriptTextBoxOurs" ||
  111. dialog_name == "ScriptQuestionOurs" ||
  112. dialog_name == "LoadWebPageOurs" ||
  113. dialog_name == "ObjectGiveItemOurs");
  114. self = new LLNotifyBox(notifp, is_script_dialog, is_ours);
  115. gNotifyBoxViewp->addChild(self);
  116. // To avoid piling restart notifications, we close any old one when a
  117. // new one arrives.
  118. if (sigtype == "add" &&
  119. (dialog_name == "RegionRestartMinutes" ||
  120. dialog_name == "RegionRestartSeconds"))
  121. {
  122. closeLastNotifyRestart();
  123. sLastNotifyRestartId = notifp->getID();
  124. }
  125. // Added this, because these notifications are not logged otherwise. HB
  126. if (!is_script_dialog)
  127. {
  128. LL_DEBUGS("Notifications") << "Got notification: " << dialog_name
  129. << LL_ENDL;
  130. }
  131. if (gAutomationp)
  132. {
  133. if (is_script_dialog)
  134. {
  135. std::vector<std::string> buttons;
  136. for (size_t i = 0, count = self->mBtnCallbackData.size();
  137. i < count; ++i)
  138. {
  139. CallbackData* userdata = self->mBtnCallbackData[i];
  140. if (userdata)
  141. {
  142. buttons.emplace_back(userdata->mButtonName);
  143. }
  144. }
  145. gAutomationp->onScriptDialog(notifp->getID(), self->mMessage,
  146. buttons);
  147. }
  148. else
  149. {
  150. gAutomationp->onNotification(dialog_name, notifp->getID(),
  151. self->mMessage);
  152. }
  153. }
  154. }
  155. return false;
  156. }
  157. //static
  158. void LLNotifyBox::setShowNotifications(bool show)
  159. {
  160. sShowNotifications = show;
  161. bool is_first = show;
  162. bool focused = false;
  163. for (child_list_const_iter_t
  164. iter = gNotifyBoxViewp->getChildList()->begin(),
  165. end = gNotifyBoxViewp->getChildList()->end();
  166. iter != end; ++iter)
  167. {
  168. LLView* view = dynamic_cast<LLView*>(*iter);
  169. if (view && view->getName() == "groupnotify")
  170. {
  171. view->setVisible(show);
  172. if (show && !focused)
  173. {
  174. view->setFocus(true);
  175. focused = true;
  176. }
  177. }
  178. else
  179. {
  180. LLNotifyBox* box = dynamic_cast<LLNotifyBox*>(*iter);
  181. if (box && !box->isTip())
  182. {
  183. box->setVisible(is_first);
  184. is_first = false;
  185. if (show && !focused)
  186. {
  187. box->setFocus(true);
  188. focused = true;
  189. }
  190. }
  191. }
  192. }
  193. }
  194. //static
  195. void LLNotifyBox::substituteSLURL(const LLUUID& id, const std::string& slurl,
  196. const std::string& substitute)
  197. {
  198. if (!sNameLookupMap.count(id)) return;
  199. std::pair <name_lookup_map_t::iterator, name_lookup_map_t::iterator> range;
  200. range = sNameLookupMap.equal_range(id);
  201. for (name_lookup_map_t::iterator it = range.first, end = range.second;
  202. it != end; ++it)
  203. {
  204. const LLUUID& notif_id = it->second;
  205. LLNotifyBox* boxp = getNamedInstance(notif_id).get();
  206. if (boxp && !boxp->isDead() && boxp->mTextEditor)
  207. {
  208. boxp->mTextEditor->replaceTextAll(slurl, substitute, true);
  209. boxp->mTextEditor->setEnabled(false);
  210. }
  211. }
  212. }
  213. //static
  214. void LLNotifyBox::substitutionDone(const LLUUID& id)
  215. {
  216. sNameLookupMap.erase(id);
  217. }
  218. LLNotifyBox::LLNotifyBox(LLNotificationPtr notification, bool script_dialog,
  219. bool is_ours)
  220. : LLPanel(notification->getName(), LLRect(), BORDER_NO),
  221. LLEventTimer(notification->getExpiration() == LLDate() ?
  222. LLDate(LLTimer::getEpochSeconds() +
  223. (F64)gSavedSettings.getF32("NotifyTipDuration")) :
  224. notification->getExpiration()),
  225. LLInstanceTracker<LLNotifyBox, LLUUID>(notification->getID()),
  226. mNotification(notification),
  227. mIsTip(notification->getType() == "notifytip"),
  228. mAnimating(true),
  229. mNextBtn(NULL),
  230. mNumOptions(0),
  231. mNumButtons(0),
  232. mAddedDefaultBtn(false),
  233. mLayoutScriptDialog(script_dialog),
  234. mIsFromOurObject(is_ours),
  235. mUserInputBox(NULL),
  236. mTextEditor(NULL)
  237. {
  238. // We will start it later if actually needed...
  239. mNotifyShowingTimer.stop();
  240. LLFontGL* fontp = LLFontGL::getFontSansSerif();
  241. mMessage = notification->getMessage();
  242. //MK
  243. if (gRLenabled && gRLInterface.mContainsShowloc)
  244. {
  245. // Hide every occurrence of the Region and Parcel names if the location
  246. // restriction is active
  247. mMessage = gRLInterface.getCensoredLocation(mMessage);
  248. }
  249. if (gRLenabled &&
  250. (gRLInterface.mContainsShownames ||
  251. gRLInterface.mContainsShownametags))
  252. {
  253. mMessage = gRLInterface.getCensoredMessage(mMessage);
  254. }
  255. //mk
  256. setFocusRoot(!mIsTip);
  257. // Caution flag can be set explicitly by specifying it in the notification
  258. // payload, or it can be set implicitly if the notify xml template
  259. // specifies that it is a caution.
  260. //
  261. // Tip-style notifications handle 'caution' differently: they display the
  262. // tip in a different color.
  263. mIsCaution = notification->getPriority() >= NOTIFICATION_PRIORITY_HIGH;
  264. LLNotificationFormPtr form(notification->getForm());
  265. bool is_textbox = form->getElement("message").isDefined();
  266. mNumOptions = form->getNumElements();
  267. // Only animate first window, and never when showing the login panel for
  268. // notify tips. Also set rect appropriately.
  269. LLRect rect;
  270. if (mIsTip)
  271. {
  272. mAnimating = sNotifyTipCount <= 0 && !LLPanelLogin::isVisible();
  273. rect = getNotifyTipRect(mMessage, fontp);
  274. setFollows(FOLLOWS_BOTTOM | FOLLOWS_RIGHT);
  275. }
  276. else
  277. {
  278. mAnimating = sNotifyBoxCount <= 0 &&
  279. LLGroupNotifyBox::getGroupNotifyBoxCount() <= 0;
  280. rect = getNotifyRect(is_textbox ? 10 : mNumOptions, script_dialog,
  281. mIsCaution);
  282. setFollows(FOLLOWS_TOP | FOLLOWS_RIGHT);
  283. }
  284. setRect(rect);
  285. setBackgroundVisible(false);
  286. setBackgroundOpaque(true);
  287. const S32 top = getRect().getHeight() -
  288. (mIsTip ? (S32)fontp->getLineHeight() : 32);
  289. const S32 bottom = (S32)fontp->getLineHeight();
  290. S32 x = 2 * HPAD;
  291. S32 y = top;
  292. std::string icon_name;
  293. if (mIsTip)
  294. {
  295. // Use the tip notification icon
  296. icon_name = "notify_tip_icon.tga";
  297. }
  298. else if (mIsCaution)
  299. {
  300. // Use the caution notification icon
  301. icon_name = "notify_caution_icon.tga";
  302. }
  303. else
  304. {
  305. // Use the default notification icon
  306. icon_name = "notify_box_icon.tga";
  307. }
  308. LLIconCtrl* icon = new LLIconCtrl("icon", LLRect(x, y, x + 32, top - 32),
  309. icon_name);
  310. icon->setMouseOpaque(false);
  311. addChild(icon);
  312. x += 2 * HPAD + 32;
  313. // Set proper background color depending on whether notify box is a caution
  314. // or a Lua notification, or any other notification.
  315. if (mIsCaution)
  316. {
  317. mBackgroundColor = gColors.getColor("NotifyCautionBoxColor");
  318. }
  319. else if (notification->getName().find("Lua") == 0)
  320. {
  321. mBackgroundColor = gColors.getColor("NotifyLuaBoxColor");
  322. }
  323. else
  324. {
  325. mBackgroundColor = gColors.getColor("NotifyBoxColor");
  326. }
  327. // Add a caution textbox at the top of a caution notification
  328. if (mIsCaution && !mIsTip)
  329. {
  330. S32 caution_height = ((S32)fontp->getLineHeight() * 2) + VPAD;
  331. LLTextBox* caution_box;
  332. caution_box = new LLTextBox(std::string("caution_box"),
  333. LLRect(x, y, getRect().getWidth() - 2,
  334. caution_height),
  335. LLStringUtil::null, fontp, false);
  336. caution_box->setFontStyle(LLFontGL::BOLD);
  337. caution_box->setColor(gColors.getColor("NotifyCautionWarnColor"));
  338. caution_box->setBackgroundColor(mBackgroundColor);
  339. caution_box->setBorderVisible(false);
  340. caution_box->setWrappedText(notification->getMessage());
  341. addChild(caution_box);
  342. // Adjust the vertical position of the next control so that it appears
  343. // below the caution textbox
  344. y = y - caution_height;
  345. }
  346. else
  347. {
  348. const S32 btn_top = BOTTOM_PAD +
  349. (mNumOptions / 3) * (gBtnHeight + VPAD);
  350. // Tokenization on \n is handled by LLTextBox
  351. constexpr S32 MAX_LENGTH = 512 + 20 + DB_FIRST_NAME_BUF_SIZE +
  352. DB_LAST_NAME_BUF_SIZE +
  353. // For script dialogs: add space for title.
  354. DB_INV_ITEM_NAME_BUF_SIZE;
  355. mTextEditor = new LLTextEditor(std::string("box"),
  356. LLRect(x, y, getRect().getWidth() - 2,
  357. mIsTip ? bottom : btn_top + 16),
  358. MAX_LENGTH, LLStringUtil::null, fontp,
  359. false);
  360. mTextEditor->setWordWrap(true);
  361. mTextEditor->setMouseOpaque(true);
  362. mTextEditor->setBorderVisible(false);
  363. mTextEditor->setHideScrollbarForShortDocs(true);
  364. mTextEditor->setParseHTML(true);
  365. mTextEditor->setPreserveSegments(true);
  366. // The background color of the box is manually rendered under the text
  367. // box, therefore we want the actual text box to be transparent :
  368. mTextEditor->setReadOnlyBgColor(LLColor4::transparent);
  369. mTextEditor->setLinkColor(gColors.getColor("NotifyLinkColor"));
  370. LLColor4 text_color = gColors.getColor("NotifyTextColor");
  371. mTextEditor->setReadOnlyFgColor(text_color);
  372. mTextEditor->appendColoredText(mMessage, false, false, text_color);
  373. mTextEditor->setEnabled(false); // makes it read-only
  374. // can't tab to it (may be a problem for scrolling via keyboard)
  375. mTextEditor->setTabStop(false);
  376. addChild(mTextEditor);
  377. }
  378. if (mIsTip)
  379. {
  380. if (++sNotifyTipCount <= 0)
  381. {
  382. llwarns << "A notification was mishandled. sNotifyTipCount = "
  383. << sNotifyTipCount << ", resetting..." << llendl;
  384. sNotifyTipCount = 1;
  385. }
  386. if (!gSavedSettings.getBool("HideNotificationsInChat"))
  387. {
  388. // *TODO: Make a separate archive for these.
  389. LLChat chat(mMessage);
  390. chat.mSourceType = CHAT_SOURCE_SYSTEM;
  391. LLFloaterChat::addChatHistory(chat);
  392. LLFloaterChat::resolveSLURLs(chat);
  393. }
  394. }
  395. else
  396. {
  397. if (++sNotifyBoxCount <= 0)
  398. {
  399. llwarns << "A notification was mishandled. sNotifyBoxCount = "
  400. << sNotifyBoxCount << ", resetting..." << llendl;
  401. sNotifyBoxCount = 1;
  402. }
  403. if (gStatusBarp)
  404. {
  405. gStatusBarp->setDirty();
  406. }
  407. LLRect rect(getRect().getWidth() - 26, BOTTOM_PAD + 20,
  408. getRect().getWidth() - 2, BOTTOM_PAD);
  409. mNextBtn = new LLButton("next", rect, "notify_next.png",
  410. "notify_next.png", NULL, onClickNext, this,
  411. fontp);
  412. mNextBtn->setScaleImage(true);
  413. // *TODO: Translate
  414. mNextBtn->setToolTip(std::string("Next notification"));
  415. addChild(mNextBtn);
  416. std::string edit_text_name, edit_text_contents;
  417. LLButton* btn;
  418. for (S32 i = 0; i < mNumOptions; ++i)
  419. {
  420. LLSD form_element = form->getElement(i);
  421. std::string element_type = form_element["type"].asString();
  422. if (element_type == "button")
  423. {
  424. btn = addButton(form_element["name"].asString(),
  425. form_element["text"].asString(),
  426. true,
  427. form_element["default"].asBoolean());
  428. if (sNotifyBoxCount > 1)
  429. {
  430. // Avoid unwanted clicks when the notify box appears over
  431. // an existing one while the user was clicking on the
  432. // latter...
  433. btn->setEnabled(false);
  434. }
  435. }
  436. else if (element_type == "input")
  437. {
  438. edit_text_contents = form_element["value"].asString();
  439. edit_text_name = form_element["name"].asString();
  440. }
  441. }
  442. if (is_textbox)
  443. {
  444. S32 button_rows = script_dialog ? 2 : 1;
  445. constexpr S32 row_width = 3 * 80 + 4 * HPAD;
  446. const S32 row_height = gBtnHeight + VPAD;
  447. rect.setOriginAndSize(x, BOTTOM_PAD + button_rows * row_height,
  448. row_width,
  449. button_rows * row_height + gBtnHeight);
  450. mUserInputBox = new LLTextEditor(edit_text_name, rect, 254,
  451. edit_text_contents, fontp, false);
  452. mUserInputBox->setBorderVisible(true);
  453. mUserInputBox->setHideScrollbarForShortDocs(true);
  454. mUserInputBox->setWordWrap(true);
  455. mUserInputBox->setTabsToNextField(false);
  456. mUserInputBox->setCommitOnFocusLost(false);
  457. mUserInputBox->setHandleEditKeysDirectly(true);
  458. addChild(mUserInputBox, -1);
  459. }
  460. else
  461. {
  462. setIsChrome(true);
  463. }
  464. if (mNumButtons == 0)
  465. {
  466. btn = addButton("OK", "OK", false, true);
  467. if (sNotifyBoxCount > 1)
  468. {
  469. // Avoid unwanted clicks when the notify box appears over an
  470. // existing one while the user was clicking on the latter...
  471. btn->setEnabled(false);
  472. }
  473. mAddedDefaultBtn = true;
  474. }
  475. if (sNotifyBoxCount > 1)
  476. {
  477. mNotifyShowingTimer.start();
  478. }
  479. }
  480. if (!mTextEditor) return;
  481. // SLURLs resolving: fetch the Ids associated with avatar/group/experience
  482. // name SLURLs present in the text.
  483. uuid_list_t agent_ids = LLSLURL::findSLURLs(mMessage);
  484. if (agent_ids.empty()) return;
  485. // Keep track of which notification got which UUID
  486. const LLUUID& notif_id = notification->getID();
  487. for (uuid_list_t::iterator it = agent_ids.begin(), end = agent_ids.end();
  488. it != end; ++it)
  489. {
  490. sNameLookupMap.emplace(*it, notif_id);
  491. }
  492. // Launch the SLURLs resolution. Note that the substituteSLURL() callback
  493. // will be invoked immediately for names already in cache. That is why we
  494. // needed to push the untranslated SLURLs in the text editor (together with
  495. // the fact that doing so, it gets the SLURLs auto-parsed and puts a link
  496. // segment on them in the text editor, segment link that will be preserved
  497. // when the SLURL will be replaced with the corresponding name).
  498. LLSLURL::resolveSLURLs();
  499. }
  500. // virtual
  501. LLNotifyBox::~LLNotifyBox()
  502. {
  503. if (mIsTip)
  504. {
  505. --sNotifyTipCount;
  506. }
  507. else
  508. {
  509. --sNotifyBoxCount;
  510. if (gStatusBarp)
  511. {
  512. gStatusBarp->setDirty();
  513. }
  514. }
  515. std::for_each(mBtnCallbackData.begin(), mBtnCallbackData.end(),
  516. DeletePointer());
  517. mBtnCallbackData.clear();
  518. }
  519. // virtual
  520. LLButton* LLNotifyBox::addButton(const std::string& name,
  521. const std::string& label,
  522. bool is_option, bool is_default)
  523. {
  524. // Make caution notification buttons slightly narrower so that 3 of them
  525. // can fit without overlapping the "next" button
  526. S32 btn_width = mIsCaution ? 84 : 90;
  527. LLRect btn_rect;
  528. LLButton* btn;
  529. S32 btn_height = gBtnHeight;
  530. S32 ignore_pad = 0;
  531. S32 button_index = mNumButtons;
  532. S32 index = button_index;
  533. S32 x = HPAD * 4 + 32;
  534. static LLFontGL* default_font = LLFontGL::getFontSansSerif();
  535. LLFontGL* fontp = default_font;
  536. if (mLayoutScriptDialog)
  537. {
  538. // Add one "blank" option space, before the "Mute" and "Ignore" buttons
  539. index = button_index + 1;
  540. if (button_index == 0 || button_index == 1)
  541. {
  542. // Ignore and mute buttons are smaller
  543. btn_height = gBtnHeightSmall;
  544. ignore_pad = 10;
  545. static LLFontGL* small_font = LLFontGL::getFontSansSerifSmall();
  546. fontp = small_font;
  547. }
  548. }
  549. btn_rect.setOriginAndSize(x + (index % 3) * (btn_width + 2 * HPAD) +
  550. ignore_pad,
  551. BOTTOM_PAD + (index / 3) * (gBtnHeight + VPAD),
  552. btn_width - 2 * ignore_pad, btn_height);
  553. CallbackData* userdata = new CallbackData;
  554. userdata->mSelf = this;
  555. userdata->mButtonName = is_option ? name : "";
  556. mBtnCallbackData.push_back(userdata);
  557. btn = new LLButton(name, btn_rect, "", onClickButton, userdata);
  558. btn->setLabel(label);
  559. btn->setFont(fontp);
  560. if (mIsFromOurObject && name == "client_side_mute")
  561. {
  562. // hide the Mute button for our scripted objects
  563. btn->setVisible(false);
  564. }
  565. if (mIsCaution)
  566. {
  567. static LLCachedControl<LLColor4U> color(gColors,
  568. "ButtonCautionImageColor");
  569. btn->setImageColor(LLColor4(color));
  570. btn->setDisabledImageColor(LLColor4(color));
  571. }
  572. addChild(btn, -1);
  573. if (is_default)
  574. {
  575. setDefaultBtn(btn);
  576. }
  577. ++mNumButtons;
  578. return btn;
  579. }
  580. bool LLNotifyBox::handleMouseUp(S32 x, S32 y, MASK mask)
  581. {
  582. if (mIsTip)
  583. {
  584. mNotification->respond(mNotification->getResponseTemplate(LLNotification::WITH_DEFAULT_BUTTON));
  585. close();
  586. return true;
  587. }
  588. setFocus(true);
  589. return LLPanel::handleMouseUp(x, y, mask);
  590. }
  591. // virtual
  592. bool LLNotifyBox::handleRightMouseDown(S32 x, S32 y, MASK mask)
  593. {
  594. if (!mIsTip)
  595. {
  596. moveToBack(true);
  597. return true;
  598. }
  599. return LLPanel::handleRightMouseDown(x, y, mask);
  600. }
  601. // virtual
  602. void LLNotifyBox::draw()
  603. {
  604. // If we are teleporting, stop the timer and restart it when the teleport
  605. // completes
  606. if (gTeleportDisplay)
  607. {
  608. mEventTimer.stop();
  609. }
  610. else if (!mEventTimer.getStarted())
  611. {
  612. mEventTimer.start();
  613. }
  614. if (!mIsTip && !sShowNotifications)
  615. {
  616. setVisible(false);
  617. return;
  618. }
  619. F32 display_time = mAnimateTimer.getElapsedTimeF32();
  620. if (mNextBtn)
  621. {
  622. mNextBtn->setVisible(sNotifyBoxCount > 1);
  623. }
  624. if (mAnimating && display_time < ANIMATION_TIME)
  625. {
  626. gGL.matrixMode(LLRender::MM_MODELVIEW);
  627. LLUI::pushMatrix();
  628. S32 height = getRect().getHeight();
  629. F32 fraction = display_time / ANIMATION_TIME;
  630. F32 voffset = (1.f - fraction) * height;
  631. if (mIsTip)
  632. {
  633. voffset *= -1.f;
  634. }
  635. LLUI::translate(0.f, voffset, 0.f);
  636. drawBackground();
  637. LLPanel::draw();
  638. LLUI::popMatrix();
  639. if (mNotifyShowingTimer.getStarted())
  640. {
  641. // Do not start counting before we are done animating...
  642. mNotifyShowingTimer.reset();
  643. }
  644. }
  645. else
  646. {
  647. if (mAnimating)
  648. {
  649. mAnimating = false;
  650. if (!mIsTip)
  651. {
  652. // Hide everyone behind me once I am done animating
  653. gNotifyBoxViewp->showOnly(this);
  654. }
  655. }
  656. // If the time has come to enable buttons, then do so...
  657. static LLCachedControl<F32> enable_delay(gSavedSettings,
  658. "NotifyBoxButtonsEnableDelay");
  659. if (mNotifyShowingTimer.getStarted() &&
  660. mNotifyShowingTimer.getElapsedTimeF32() >= enable_delay)
  661. {
  662. mNotifyShowingTimer.stop();
  663. LLView* child = getFirstChild();
  664. while (child)
  665. {
  666. // Only enable buttons...
  667. LLButton* btn = dynamic_cast<LLButton*>(child);
  668. if (btn)
  669. {
  670. btn->setEnabled(true);
  671. }
  672. child = findNextSibling(child);
  673. }
  674. }
  675. drawBackground();
  676. LLPanel::draw();
  677. }
  678. }
  679. void LLNotifyBox::drawBackground() const
  680. {
  681. static const S32 tex_width = LLUIImage::sRoundedSquareWidth;
  682. static const S32 tex_height = LLUIImage::sRoundedSquareHeight;
  683. gGL.getTexUnit(0)->bind(LLUIImage::sRoundedSquare->getImage());
  684. LLColor4 bgcolor = LLColor4(mBackgroundColor);
  685. U32 edges = mIsTip ? ROUNDED_RECT_TOP : ROUNDED_RECT_BOTTOM;
  686. if (gFocusMgr.childHasKeyboardFocus(this))
  687. {
  688. constexpr S32 focus_width = 2;
  689. LLColor4 color = LLUI::sFloaterFocusBorderColor;
  690. gGL.color4fv(color.mV);
  691. gl_segmented_rect_2d_tex(-focus_width,
  692. getRect().getHeight() + focus_width,
  693. getRect().getWidth() + focus_width,
  694. -focus_width,
  695. tex_width, tex_height, 16, edges);
  696. color = LLColor4(LLUI::sColorDropShadow);
  697. gGL.color4fv(color.mV);
  698. gl_segmented_rect_2d_tex(0, getRect().getHeight(),
  699. getRect().getWidth(), 0,
  700. tex_width, tex_height, 16, edges);
  701. gGL.color4fv(bgcolor.mV);
  702. gl_segmented_rect_2d_tex(1, getRect().getHeight() - 1,
  703. getRect().getWidth() - 1, 1,
  704. tex_width, tex_height, 16, edges);
  705. }
  706. else
  707. {
  708. gGL.color4fv(bgcolor.mV);
  709. gl_segmented_rect_2d_tex(0, getRect().getHeight(),
  710. getRect().getWidth(), 0,
  711. tex_width, tex_height, 16, edges);
  712. }
  713. }
  714. void LLNotifyBox::close()
  715. {
  716. bool was_tip = mIsTip;
  717. die();
  718. if (!was_tip)
  719. {
  720. LLNotifyBox* front = gNotifyBoxViewp->getFirstNontipBox();
  721. if (front)
  722. {
  723. gNotifyBoxViewp->showOnly(front);
  724. // We are assuming that close is only called by user action (for
  725. // non-tips), so we then give focus to the next close button
  726. if (front->getDefaultButton())
  727. {
  728. front->getDefaultButton()->setFocus(true);
  729. }
  730. gFocusMgr.triggerFocusFlash(); // TODO it's ugly to call this here
  731. }
  732. }
  733. }
  734. void LLNotifyBox::format(std::string& msg,
  735. const LLStringUtil::format_map_t& args)
  736. {
  737. // *TODO: translate
  738. LLStringUtil::format_map_t targs = args;
  739. targs["[SECOND_LIFE]"] = "Second Life";
  740. targs["[VIEWER_NAME]"] = "the Cool VL Viewer";
  741. LLStringUtil::format(msg, targs);
  742. }
  743. //virtual
  744. bool LLNotifyBox::tick()
  745. {
  746. if (mIsTip)
  747. {
  748. close();
  749. }
  750. return false;
  751. }
  752. void LLNotifyBox::moveToBack(bool getfocus)
  753. {
  754. // Move this dialog to the back.
  755. gNotifyBoxViewp->sendChildToBack(this);
  756. if (!mIsTip && mNextBtn)
  757. {
  758. mNextBtn->setVisible(false);
  759. // And enable the next button on the frontmost one, if there is one
  760. if (gNotifyBoxViewp->getChildCount() > 0)
  761. {
  762. LLNotifyBox* front = gNotifyBoxViewp->getFirstNontipBox();
  763. if (front)
  764. {
  765. gNotifyBoxViewp->showOnly(front);
  766. if (getfocus)
  767. {
  768. // if are called from a user interaction we give focus to
  769. // the next next button
  770. if (front->mNextBtn != NULL)
  771. {
  772. front->mNextBtn->setFocus(true);
  773. }
  774. // *TODO: it is ugly to call this here
  775. gFocusMgr.triggerFocusFlash();
  776. }
  777. }
  778. }
  779. }
  780. }
  781. //static
  782. LLRect LLNotifyBox::getNotifyRect(S32 num_options, bool script_dialog,
  783. bool is_caution)
  784. {
  785. // Make caution-style dialog taller to accomodate extra text, as well as
  786. // causing the accept/decline buttons to be drawn in a different position,
  787. // to help prevent "quick-click-through" of many permissions prompts.
  788. static LLCachedControl<S32> caution_height(gSavedSettings,
  789. "PermissionsCautionNotifyBoxHeight");
  790. static LLCachedControl<S32> notify_height(gSavedSettings, "NotifyBoxHeight");
  791. static LLCachedControl<S32> script_height(gSavedSettings, "ScriptDialogHeight");
  792. S32 height = script_dialog ? script_height
  793. : (is_caution ? caution_height : notify_height);
  794. if (height < 150)
  795. {
  796. height = 150;
  797. }
  798. static LLCachedControl<S32> notify_width(gSavedSettings, "NotifyBoxWidth");
  799. static LLCachedControl<S32> script_width(gSavedSettings, "ScriptDialogWidth");
  800. S32 width = script_dialog ? script_width : notify_width;
  801. if (width < 250)
  802. {
  803. width = 250;
  804. }
  805. const S32 top = gNotifyBoxViewp->getRect().getHeight();
  806. const S32 right = gNotifyBoxViewp->getRect().getWidth();
  807. const S32 left = right - width;
  808. if (num_options < 1)
  809. {
  810. num_options = 1;
  811. }
  812. // Add one "blank" option space.
  813. if (script_dialog)
  814. {
  815. num_options += 1;
  816. }
  817. S32 additional_lines = (num_options - 1) / 3;
  818. height += additional_lines * (gBtnHeight + VPAD);
  819. return LLRect(left, top, right, top - height);
  820. }
  821. //static
  822. LLRect LLNotifyBox::getNotifyTipRect(const std::string& utf8message,
  823. LLFontGL* fontp)
  824. {
  825. S32 line_count = 1;
  826. LLWString message = utf8str_to_wstring(utf8message);
  827. S32 message_len = message.length();
  828. static LLCachedControl<S32> notify_width(gSavedSettings, "NotifyBoxWidth");
  829. S32 width = notify_width;
  830. if (width < 250)
  831. {
  832. width = 250;
  833. }
  834. // Make room for the icon area.
  835. const S32 text_area_width = width - HPAD * 4 - 32;
  836. const llwchar* wchars = message.c_str();
  837. const llwchar* start = wchars;
  838. const llwchar* end;
  839. S32 total_drawn = 0;
  840. bool done = false;
  841. do
  842. {
  843. ++line_count;
  844. for (end = start; *end != 0 && *end != '\n'; ++end)
  845. {
  846. }
  847. if (*end == 0)
  848. {
  849. end = wchars + message_len;
  850. done = true;
  851. }
  852. S32 remaining = end - start;
  853. while (remaining)
  854. {
  855. S32 drawn = fontp->maxDrawableChars(start, (F32)text_area_width,
  856. remaining, true);
  857. if (drawn == 0)
  858. {
  859. // Draw at least one character, even if it does not all fit
  860. // (avoids an infinite loop).
  861. drawn = 1;
  862. }
  863. total_drawn += drawn;
  864. start += drawn;
  865. remaining -= drawn;
  866. if (total_drawn < message_len)
  867. {
  868. if (wchars[total_drawn] != '\n')
  869. {
  870. // Wrap because line was too long
  871. ++line_count;
  872. }
  873. }
  874. else
  875. {
  876. done = true;
  877. }
  878. }
  879. ++total_drawn; // Account for '\n'
  880. start = ++end;
  881. }
  882. while (!done);
  883. S32 height = llceil((F32)(line_count + 1) * fontp->getLineHeight());
  884. S32 delta = 0;
  885. if (gOverlayBarp) // This should always be true...
  886. {
  887. if (LLPanelLogin::isVisible())
  888. {
  889. // Display above the login panel lower background strip. The offset
  890. // needs to be adjusted if you change the layout in panel_login.xml
  891. delta = 102 - gOverlayBarp->getRect().mTop;
  892. height += 12;
  893. }
  894. else
  895. {
  896. height += gOverlayBarp->getRect().getHeight();
  897. }
  898. }
  899. constexpr S32 MIN_NOTIFY_HEIGHT = 72;
  900. constexpr S32 MAX_NOTIFY_HEIGHT = 600;
  901. height = llclamp(height + VPAD, MIN_NOTIFY_HEIGHT, MAX_NOTIFY_HEIGHT);
  902. const S32 right = gNotifyBoxViewp->getRect().getWidth();
  903. const S32 left = right - width;
  904. // Make sure it goes slightly offscreen
  905. return LLRect(left, delta + height - 1, right, delta - 1);
  906. }
  907. //static
  908. void LLNotifyBox::onClickButton(void* data)
  909. {
  910. CallbackData* self_and_button = (CallbackData*)data;
  911. if (!self_and_button) return;
  912. LLNotifyBox* self = self_and_button->mSelf;
  913. std::string button_name = self_and_button->mButtonName;
  914. LLSD response = self->mNotification->getResponseTemplate();
  915. if (!self->mAddedDefaultBtn && !button_name.empty())
  916. {
  917. response[button_name] = true;
  918. }
  919. if (self->mUserInputBox)
  920. {
  921. response[self->mUserInputBox->getName()] = self->mUserInputBox->getValue();
  922. }
  923. self->mNotification->respond(response);
  924. }
  925. //static
  926. void LLNotifyBox::onClickNext(void* data)
  927. {
  928. LLNotifyBox* self = static_cast<LLNotifyBox*>(data);
  929. if (self)
  930. {
  931. self->moveToBack(true);
  932. }
  933. }
  934. //static
  935. void LLNotifyBox::closeLastNotifyRestart()
  936. {
  937. if (sLastNotifyRestartId.notNull())
  938. {
  939. LLNotificationPtr n = gNotifications.find(sLastNotifyRestartId);
  940. if (n)
  941. {
  942. gNotifications.cancel(n);
  943. }
  944. sLastNotifyRestartId.setNull();
  945. }
  946. }
  947. //---------------------------------------------------------------------------
  948. // LLNotifyBoxView class
  949. //---------------------------------------------------------------------------
  950. LLNotifyBoxView::LLNotifyBoxView(const std::string& name, const LLRect& rect,
  951. bool mouse_opaque, U32 follows)
  952. : LLUICtrl(name, rect, mouse_opaque, NULL, NULL, follows)
  953. {
  954. }
  955. LLNotifyBoxView::~LLNotifyBoxView()
  956. {
  957. gNotifyBoxViewp = NULL;
  958. }
  959. LLNotifyBox* LLNotifyBoxView::getFirstNontipBox() const
  960. {
  961. for (child_list_const_iter_t iter = getChildList()->begin(),
  962. end = getChildList()->end();
  963. iter != end; ++iter)
  964. {
  965. if (*iter && !isGroupNotifyBox(*iter))
  966. {
  967. LLNotifyBox* box = (LLNotifyBox*)(*iter);
  968. if (!box->isTip() && !box->isDead())
  969. {
  970. return box;
  971. }
  972. }
  973. }
  974. return NULL;
  975. }
  976. void LLNotifyBoxView::showOnly(LLView* view)
  977. {
  978. if (view)
  979. {
  980. // assumes that the argument is actually a child
  981. LLNotifyBox* shown = dynamic_cast<LLNotifyBox*>(view);
  982. if (!shown)
  983. {
  984. return;
  985. }
  986. // make every other notification invisible
  987. for (child_list_const_iter_t iter = getChildList()->begin(),
  988. end = getChildList()->end();
  989. iter != end; ++iter)
  990. {
  991. if (*iter && !isGroupNotifyBox(*iter))
  992. {
  993. LLNotifyBox* box = (LLNotifyBox*)(*iter);
  994. if (box != view && box->getVisible() && !box->isTip())
  995. {
  996. box->setVisible(false);
  997. }
  998. }
  999. }
  1000. shown->setVisible(true);
  1001. sendChildToFront(shown);
  1002. }
  1003. }
  1004. void LLNotifyBoxView::purgeMessagesMatching(const Matcher& matcher)
  1005. {
  1006. // Make a *copy* of the child list to iterate over since we will be
  1007. // removing items from the real list as we go.
  1008. LLView::child_list_t notification_queue(*getChildList());
  1009. for (LLView::child_list_iter_t iter = notification_queue.begin(),
  1010. end = notification_queue.end();
  1011. iter != end; ++iter)
  1012. {
  1013. if (*iter && !isGroupNotifyBox(*iter))
  1014. {
  1015. LLNotifyBox* notification = (LLNotifyBox*)*iter;
  1016. if (matcher.matches(notification->getNotification()))
  1017. {
  1018. removeChild(notification);
  1019. delete notification;
  1020. }
  1021. }
  1022. }
  1023. }
  1024. bool LLNotifyBoxView::isGroupNotifyBox(const LLView* view) const
  1025. {
  1026. return view && view->getName() == "groupnotify";
  1027. }