PageRenderTime 45ms CodeModel.GetById 2ms app.highlight 37ms RepoModel.GetById 1ms app.codeStats 0ms

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