PageRenderTime 80ms CodeModel.GetById 7ms app.highlight 68ms RepoModel.GetById 1ms app.codeStats 0ms

/indra/newview/llscriptfloater.cpp

https://bitbucket.org/lindenlab/viewer-beta/
C++ | 607 lines | 448 code | 82 blank | 77 comment | 64 complexity | 754552cb9a59ecc5a56f74e6c3bc9259 MD5 | raw file
  1/** 
  2 * @file llscriptfloater.cpp
  3 * @brief LLScriptFloater class definition
  4 *
  5 * $LicenseInfo:firstyear=2009&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"
 28#include "llscriptfloater.h"
 29#include "llagentcamera.h"
 30
 31#include "llchannelmanager.h"
 32#include "llchiclet.h"
 33#include "llchicletbar.h"
 34#include "llfloaterreg.h"
 35#include "lllslconstants.h"
 36#include "llnotifications.h"
 37#include "llnotificationsutil.h"
 38#include "llscreenchannel.h"
 39#include "llsyswellwindow.h"
 40#include "lltoastnotifypanel.h"
 41#include "lltoastscripttextbox.h"
 42#include "lltrans.h"
 43#include "llviewerwindow.h"
 44#include "llimfloater.h"
 45
 46//////////////////////////////////////////////////////////////////////////
 47//////////////////////////////////////////////////////////////////////////
 48//////////////////////////////////////////////////////////////////////////
 49
 50LLUUID notification_id_to_object_id(const LLUUID& notification_id)
 51{
 52	LLNotificationPtr notification = LLNotifications::getInstance()->find(notification_id);
 53	if(notification)
 54	{
 55		return notification->getPayload()["object_id"].asUUID();
 56	}
 57	return LLUUID::null;
 58}
 59
 60//////////////////////////////////////////////////////////////////////////
 61//////////////////////////////////////////////////////////////////////////
 62//////////////////////////////////////////////////////////////////////////
 63
 64LLScriptFloater::LLScriptFloater(const LLSD& key)
 65: LLDockableFloater(NULL, true, key)
 66, mScriptForm(NULL)
 67, mSaveFloaterPosition(false)
 68{
 69	setMouseDownCallback(boost::bind(&LLScriptFloater::onMouseDown, this));
 70	setOverlapsScreenChannel(true);
 71	mIsDockedStateForcedCallback = boost::bind(&LLAgentCamera::cameraMouselook, &gAgentCamera);
 72}
 73
 74bool LLScriptFloater::toggle(const LLUUID& notification_id)
 75{
 76	LLScriptFloater* floater = LLFloaterReg::findTypedInstance<LLScriptFloater>("script_floater", notification_id);
 77
 78	// show existing floater
 79	if(floater)
 80	{
 81		if(floater->getVisible())
 82		{
 83			floater->setVisible(false);
 84			return false;
 85		}
 86		else
 87		{
 88			floater->setVisible(TRUE);
 89			floater->setFocus(FALSE);
 90		}
 91	}
 92	// create and show new floater
 93	else
 94	{
 95		show(notification_id);
 96	}
 97
 98	LLChicletBar::getInstance()->getChicletPanel()->setChicletToggleState(notification_id, true);
 99	return true;
100}
101
102LLScriptFloater* LLScriptFloater::show(const LLUUID& notification_id)
103{
104	LLScriptFloater* floater = LLFloaterReg::getTypedInstance<LLScriptFloater>("script_floater", notification_id);
105	floater->setNotificationId(notification_id);
106	floater->createForm(notification_id);
107
108	//LLDialog(LLGiveInventory and LLLoadURL) should no longer steal focus (see EXT-5445)
109	floater->setAutoFocus(FALSE);
110
111	if(LLScriptFloaterManager::OBJ_SCRIPT == LLScriptFloaterManager::getObjectType(notification_id))
112	{
113		floater->setSavePosition(true);
114		floater->restorePosition();
115	}
116	else
117	{
118		floater->dockToChiclet(true);
119	}
120
121	//LLDialog(LLGiveInventory and LLLoadURL) should no longer steal focus (see EXT-5445)
122	LLFloaterReg::showTypedInstance<LLScriptFloater>("script_floater", notification_id, FALSE);
123
124	return floater;
125}
126
127void LLScriptFloater::setNotificationId(const LLUUID& id)
128{
129	mNotificationId = id;
130	// Lets save object id now while notification exists
131	mObjectId = notification_id_to_object_id(id);
132}
133
134void LLScriptFloater::createForm(const LLUUID& notification_id)
135{
136	// delete old form
137	if(mScriptForm)
138	{
139		removeChild(mScriptForm);
140		mScriptForm->die();
141	}
142
143	LLNotificationPtr notification = LLNotifications::getInstance()->find(notification_id);
144	if(NULL == notification)
145	{
146		return;
147	}
148
149	// create new form
150	LLRect toast_rect = getRect();
151	if (isScriptTextbox(notification))
152	{
153		mScriptForm = new LLToastScriptTextbox(notification);
154	}
155	else
156	{
157		// LLToastNotifyPanel will fit own content in vertical direction,
158		// but it needs an initial rect to properly calculate  its width
159		// Use an initial rect of the script floater to make the floater
160		// window more configurable.
161		mScriptForm = new LLToastNotifyPanel(notification, toast_rect); 
162	}
163	addChild(mScriptForm);
164
165	// position form on floater
166	mScriptForm->setOrigin(0, 0);
167
168	// make floater size fit form size
169	LLRect panel_rect = mScriptForm->getRect();
170	toast_rect.setLeftTopAndSize(toast_rect.mLeft, toast_rect.mTop, panel_rect.getWidth(), panel_rect.getHeight() + getHeaderHeight());
171	setShape(toast_rect);
172}
173
174void LLScriptFloater::onClose(bool app_quitting)
175{
176	savePosition();
177
178	if(getNotificationId().notNull())
179	{
180		// we shouldn't kill notification on exit since it may be used as persistent.
181		if (app_quitting)
182		{
183			LLScriptFloaterManager::getInstance()->onRemoveNotification(getNotificationId());
184		}
185		else
186		{
187			LLScriptFloaterManager::getInstance()->removeNotification(getNotificationId());
188		}
189	}
190}
191
192void LLScriptFloater::setDocked(bool docked, bool pop_on_undock /* = true */)
193{
194	LLDockableFloater::setDocked(docked, pop_on_undock);
195
196	savePosition();
197
198	hideToastsIfNeeded();
199}
200
201void LLScriptFloater::setVisible(BOOL visible)
202{
203	LLDockableFloater::setVisible(visible);
204
205	hideToastsIfNeeded();
206
207	if(!visible)
208	{
209		LLIMChiclet* chiclet = LLChicletBar::getInstance()->getChicletPanel()->findChiclet<LLIMChiclet>(getNotificationId());
210		if(chiclet)
211		{
212			chiclet->setToggleState(false);
213		}
214	}
215}
216
217void LLScriptFloater::onMouseDown()
218{
219	if(getNotificationId().notNull())
220	{
221		// Remove new message icon
222		LLIMChiclet* chiclet = LLChicletBar::getInstance()->getChicletPanel()->findChiclet<LLIMChiclet>(getNotificationId());
223		if (chiclet == NULL)
224		{
225			llerror("Dock chiclet for LLScriptFloater doesn't exist", 0);
226		}
227		else
228		{
229			chiclet->setShowNewMessagesIcon(false);
230		}
231	}
232}
233
234void LLScriptFloater::savePosition()
235{
236	if(getSavePosition() && mObjectId.notNull())
237	{
238		LLScriptFloaterManager::FloaterPositionInfo fpi = {getRect(), isDocked()};
239		LLScriptFloaterManager::getInstance()->saveFloaterPosition(mObjectId, fpi);
240	}
241}
242
243void LLScriptFloater::restorePosition()
244{
245	LLScriptFloaterManager::FloaterPositionInfo fpi;
246	if(LLScriptFloaterManager::getInstance()->getFloaterPosition(mObjectId, fpi))
247	{
248		dockToChiclet(fpi.mDockState);
249		if(!fpi.mDockState)
250		{
251			// Un-docked floater is opened in 0,0, now move it to saved position
252			translate(fpi.mRect.mLeft - getRect().mLeft, fpi.mRect.mTop - getRect().mTop);
253		}
254	}
255	else
256	{
257		dockToChiclet(true);
258	}
259}
260
261void LLScriptFloater::onFocusLost()
262{
263	if(getNotificationId().notNull())
264	{
265		LLChicletBar::getInstance()->getChicletPanel()->setChicletToggleState(getNotificationId(), false);
266	}
267}
268
269void LLScriptFloater::onFocusReceived()
270{
271	// first focus will be received before setObjectId() call - don't toggle chiclet
272	if(getNotificationId().notNull())
273	{
274		LLChicletBar::getInstance()->getChicletPanel()->setChicletToggleState(getNotificationId(), true);
275	}
276}
277
278void LLScriptFloater::dockToChiclet(bool dock)
279{
280	if (getDockControl() == NULL)
281	{
282		LLChiclet* chiclet = LLChicletBar::getInstance()->getChicletPanel()->findChiclet<LLChiclet>(getNotificationId());
283		if (chiclet == NULL)
284		{
285			llwarns << "Dock chiclet for LLScriptFloater doesn't exist" << llendl;
286			return;
287		}
288		else
289		{
290			LLChicletBar::getInstance()->getChicletPanel()->scrollToChiclet(chiclet);
291		}
292
293		// Stop saving position while we dock floater
294		bool save = getSavePosition();
295		setSavePosition(false);
296
297		setDockControl(new LLDockControl(chiclet, this, getDockTongue(),
298			LLDockControl::BOTTOM));
299
300		setDocked(dock);
301
302		// Restore saving
303		setSavePosition(save);
304	}
305}
306
307void LLScriptFloater::hideToastsIfNeeded()
308{
309	using namespace LLNotificationsUI;
310
311	// find channel
312	LLScreenChannel* channel = dynamic_cast<LLScreenChannel*>(LLChannelManager::getInstance()->findChannelByID(
313		LLUUID(gSavedSettings.getString("NotificationChannelUUID"))));
314	// update notification channel state
315	if(channel)
316	{
317		channel->updateShowToastsState();
318		channel->redrawToasts();
319	}
320}
321
322//////////////////////////////////////////////////////////////////////////
323//////////////////////////////////////////////////////////////////////////
324//////////////////////////////////////////////////////////////////////////
325
326void LLScriptFloaterManager::onAddNotification(const LLUUID& notification_id)
327{
328	if(notification_id.isNull())
329	{
330		llwarns << "Invalid notification ID" << llendl;
331		return;
332	}
333
334	// get scripted Object's ID
335	LLUUID object_id = notification_id_to_object_id(notification_id);
336	
337	// Need to indicate of "new message" for object chiclets according to requirements
338	// specified in the Message Bar design specification. See EXT-3142.
339	bool set_new_message = false;
340	EObjectType obj_type = getObjectType(notification_id);
341
342	// LLDialog can spawn only one instance, LLLoadURL and LLGiveInventory can spawn unlimited number of instances
343	if(OBJ_SCRIPT == obj_type)
344	{
345		// If an Object spawns more-than-one floater, only the newest one is shown. 
346		// The previous is automatically closed.
347		script_notification_map_t::const_iterator it = findUsingObjectId(object_id);
348		if(it != mNotifications.end())
349		{
350			LLIMChiclet* chiclet = LLChicletBar::getInstance()->getChicletPanel()->findChiclet<LLIMChiclet>(it->first);
351			if(chiclet)
352			{
353				// Pass the new_message icon state further.
354				set_new_message = chiclet->getShowNewMessagesIcon();
355			}
356
357			LLScriptFloater* floater = LLFloaterReg::findTypedInstance<LLScriptFloater>("script_floater", it->first);
358			if(floater)
359			{
360				// Generate chiclet with a "new message" indicator if a docked window was opened but not in focus. See EXT-3142.
361				set_new_message |= !floater->hasFocus();
362			}
363
364			removeNotification(it->first);
365		}
366	}
367
368	mNotifications.insert(std::make_pair(notification_id, object_id));
369
370	// Create inventory offer chiclet for offer type notifications
371	if( OBJ_GIVE_INVENTORY == obj_type )
372	{
373		LLChicletBar::instance().getChicletPanel()->createChiclet<LLInvOfferChiclet>(notification_id);
374	}
375	else
376	{
377		LLChicletBar::getInstance()->getChicletPanel()->createChiclet<LLScriptChiclet>(notification_id);
378	}
379
380	LLIMWellWindow::getInstance()->addObjectRow(notification_id, set_new_message);
381
382	LLSD data;
383	data["notification_id"] = notification_id;
384	data["new_message"] = set_new_message;
385	data["unread"] = 1; // each object has got only one floater
386	mNewObjectSignal(data);
387
388	toggleScriptFloater(notification_id, set_new_message);
389}
390
391void LLScriptFloaterManager::removeNotification(const LLUUID& notification_id)
392{
393	LLNotificationPtr notification = LLNotifications::instance().find(notification_id);
394	if (notification != NULL && !notification->isCancelled())
395	{
396		LLNotificationsUtil::cancel(notification);
397	}
398
399	onRemoveNotification(notification_id);
400}
401
402void LLScriptFloaterManager::onRemoveNotification(const LLUUID& notification_id)
403{
404	if(notification_id.isNull())
405	{
406		llwarns << "Invalid notification ID" << llendl;
407		return;
408	}
409
410	// remove related chiclet
411	if (LLChicletBar::instanceExists())
412	{
413		LLChicletBar::getInstance()->getChicletPanel()->removeChiclet(notification_id);
414	}
415
416	LLIMWellWindow* im_well_window = LLIMWellWindow::findInstance();
417	if (im_well_window)
418	{
419		im_well_window->removeObjectRow(notification_id);
420	}
421
422	mNotifications.erase(notification_id);
423
424	// close floater
425	LLScriptFloater* floater = LLFloaterReg::findTypedInstance<LLScriptFloater>("script_floater", notification_id);
426	if(floater)
427	{
428		floater->savePosition();
429		floater->setNotificationId(LLUUID::null);
430		floater->closeFloater();
431	}
432}
433
434void LLScriptFloaterManager::toggleScriptFloater(const LLUUID& notification_id, bool set_new_message)
435{
436	LLSD data;
437	data["notification_id"] = notification_id;
438	data["new_message"] = set_new_message;
439	mToggleFloaterSignal(data);
440
441	// toggle floater
442	LLScriptFloater::toggle(notification_id);
443}
444
445LLUUID LLScriptFloaterManager::findObjectId(const LLUUID& notification_id)
446{
447	script_notification_map_t::const_iterator it = mNotifications.find(notification_id);
448	if(mNotifications.end() != it)
449	{
450		return it->second;
451	}
452	return LLUUID::null;
453}
454
455LLUUID LLScriptFloaterManager::findNotificationId(const LLUUID& object_id)
456{
457	if(object_id.notNull())
458	{
459		script_notification_map_t::const_iterator it = findUsingObjectId(object_id);
460		if(mNotifications.end() != it)
461		{
462			return it->first;
463		}
464	}
465	return LLUUID::null;
466}
467
468// static
469LLScriptFloaterManager::EObjectType LLScriptFloaterManager::getObjectType(const LLUUID& notification_id)
470{
471	if(notification_id.isNull())
472	{
473		llwarns << "Invalid notification ID" << llendl;
474		return OBJ_UNKNOWN;
475	}
476
477	static const object_type_map TYPE_MAP = initObjectTypeMap();
478
479	LLNotificationPtr notification = LLNotificationsUtil::find(notification_id);
480	object_type_map::const_iterator it = TYPE_MAP.find(notification->getName());
481	if(it != TYPE_MAP.end())
482	{
483		return it->second;
484	}
485
486	llwarns << "Unknown object type" << llendl;
487	return OBJ_UNKNOWN;
488}
489
490// static
491std::string LLScriptFloaterManager::getObjectName(const LLUUID& notification_id)
492{
493	using namespace LLNotificationsUI;
494	LLNotificationPtr notification = LLNotifications::getInstance()->find(notification_id);
495	if(!notification)
496	{
497		llwarns << "Invalid notification" << llendl;
498		return LLStringUtil::null;
499	}
500
501	std::string text;
502
503	switch(LLScriptFloaterManager::getObjectType(notification_id))
504	{
505	case LLScriptFloaterManager::OBJ_SCRIPT:
506		text = notification->getSubstitutions()["TITLE"].asString();
507		break;
508	case LLScriptFloaterManager::OBJ_LOAD_URL:
509		text = notification->getSubstitutions()["OBJECTNAME"].asString();
510		break;
511	case LLScriptFloaterManager::OBJ_GIVE_INVENTORY:
512		text = notification->getSubstitutions()["OBJECTFROMNAME"].asString();
513		break;
514	default:
515		text = LLTrans::getString("object");
516		break;
517	}
518
519	return text;
520}
521
522//static
523LLScriptFloaterManager::object_type_map LLScriptFloaterManager::initObjectTypeMap()
524{
525	object_type_map type_map;
526	type_map["ScriptDialog"] = OBJ_SCRIPT;
527	type_map["ScriptDialogGroup"] = OBJ_SCRIPT;
528	type_map["LoadWebPage"] = OBJ_LOAD_URL;
529	type_map["ObjectGiveItem"] = OBJ_GIVE_INVENTORY;
530	return type_map;
531}
532
533LLScriptFloaterManager::script_notification_map_t::const_iterator LLScriptFloaterManager::findUsingObjectId(const LLUUID& object_id)
534{
535	script_notification_map_t::const_iterator it = mNotifications.begin();
536	for(; mNotifications.end() != it; ++it)
537	{
538		if(object_id == it->second)
539		{
540			return it;
541		}
542	}
543	return mNotifications.end();
544}
545
546void LLScriptFloaterManager::saveFloaterPosition(const LLUUID& object_id, const FloaterPositionInfo& fpi)
547{
548	if(object_id.notNull())
549	{
550		LLScriptFloaterManager::getInstance()->mFloaterPositions[object_id] = fpi;
551	}
552	else
553	{
554		llwarns << "Invalid object id" << llendl;
555	}
556}
557
558bool LLScriptFloaterManager::getFloaterPosition(const LLUUID& object_id, FloaterPositionInfo& fpi)
559{
560	floater_position_map_t::const_iterator it = mFloaterPositions.find(object_id);
561	if(LLScriptFloaterManager::getInstance()->mFloaterPositions.end() != it)
562	{
563		fpi = it->second;
564		return true;
565	}
566	return false;
567}
568
569void LLScriptFloaterManager::setFloaterVisible(const LLUUID& notification_id, bool visible)
570{
571	LLScriptFloater* floater = LLFloaterReg::findTypedInstance<LLScriptFloater>(
572		"script_floater", notification_id);
573	if(floater)
574	{
575		floater->setVisible(visible);
576	}
577}
578
579//////////////////////////////////////////////////////////////////
580
581bool LLScriptFloater::isScriptTextbox(LLNotificationPtr notification)
582{
583	// get a form for the notification
584	LLNotificationFormPtr form(notification->getForm());
585
586	if (form)
587	{
588		// get number of elements in the form
589		int num_options = form->getNumElements();
590	
591		// if ANY of the buttons have the magic lltextbox string as
592		// name, then treat the whole dialog as a simple text entry
593		// box (i.e. mixed button and textbox forms are not supported)
594		for (int i=0; i<num_options; ++i)
595		{
596			LLSD form_element = form->getElement(i);
597			if (form_element["name"].asString() == TEXTBOX_MAGIC_TOKEN)
598			{
599				return true;
600			}
601		}
602	}
603
604	return false;
605}
606
607// EOF